Hi unTactical,
I wrote a bot in C++ (Chatterbot) last Christmas like yours. Instead of sharing the send queue between all the bots, I just encapsulated a send queue in each bot object. On the other hand, a timer queue was shared between all of them.
Timer queue did a few things
Periodic away, periodic sanity check, send queue, and reconnect events
Conceptually, this is how it worked.
I would
1) Read data from config file
2) Initialize each bot
3) run Bot.Startup() which is the heart
It did not use threads
Bot.Startup() would do a few things
It would run FDEnt's Select() function, this would call each bot back that had data waiting on a socket
It would call timer's Exec() function, which would execute all expired pending timers
Then it would grab the remaining time before next timer and then repeat.
Keep in mind, the Select() sleeps for at most the time until the next timer.
The timer class does a callback on a functor, therefore, each bot had to overload the operator()( int )...the argument is the callback event.
If it was a send queue callback, it would call a send queue method to send awaiting data ... if any data was left to be sent, it would queue a timer event again.
Some code snippits:
void Bot::operator()( int event ) {
int sz;
char buff[ RECVBUFFSIZE + 1 ];
const char* p;
switch( event ) {
case TCPENT_CONNECT:
#ifdef BOT_DEBUG
Print( "Attempting to connect to %s ...\n", GetConAddr() );
#endif
if ( timeout != zerotime )
timer.Set( this, BOT_TIMEOUT, timeout );
//Print out some info here
break;
case TCPENT_CONNECTED:
#ifdef BOT_DEBUG
Print( "Connected!\nSending login information...\n" );
#endif
if ( !username || !password )
break;
timer.Remove( this, BOT_TIMEOUT );
//Login
TCPENT::Send( "%c%c%s\r\n%s\r\n", 0x03, 0x04, username, password );
if ( home )
TCPENT::Send( "/join %s\r\n", home );
break;
case TCPENT_READ:
#ifdef BOT_DEBUG
Print( "TCPENT_READ\n" );
#endif
sz = Read( buff, RECVBUFFSIZE );
if ( sz <= 0 )
break;
buff[ sz ] = 0;
//Construct inbuff
p = buff;
do {
const char* pbuff = p;
p = parseto( p, "\r\n" );
if ( p != pbuff ) {
if ( !inbuff ) {
buffsz = p - pbuff;
inbuff = new char[ buffsz + 1 ];
strncpy( inbuff, pbuff, buffsz );
}
else {
char* tmp = new char[ buffsz + p - pbuff + 1 ];
strncpy( tmp, inbuff, buffsz );
strncpy( tmp+buffsz, pbuff, p - pbuff );
buffsz += p - pbuff;
delete [] inbuff;
inbuff = tmp;
}
inbuff[ buffsz ] = 0;
}
if ( *p == '\r' || *p == '\n' ) {
Handle();
if ( inbuff )
delete [] inbuff;
inbuff = 0;
buffsz = 0;
}
} while ( *p++ );
break;
case TCPENT_DISCONNECT:
#ifdef BOT_DEBUG
Print( "Disconnected.\n" );
#endif
ClearTmp();
timer.Remove( this ); //Kill all the logtime timers
timer.Set( this, BOT_RECON, recon );
break;
case TCPENT_FAIL:
#ifdef BOT_DEBUG
Print( "Failed to connect. Aborting.\n" );
#endif
break;
case BOT_STATUS:
#ifdef BOT_DEBUG
Print( "BOT_STATUS\n" );
#endif
if ( home && currentchan ) {
if ( strccmp( home, currentchan ) )
Send( SENDQ_MINPRI, "/join %s\n", home );
}
//sendq.Send( DEFSENDPOLICY );
timer.Set( this, BOT_STATUS, stat );
break;
case BOT_SEND:
#ifdef BOT_DEBUG
Print( "BOT_SEND\n" );
#endif
sendq.Send( DEFSENDPOLICY );
if ( !sendq.Empty() )
timer.Set( this, BOT_SEND, sendtime );
break;
case BOT_IDLE:
#ifdef BOT_DEBUG
Print( "BOT_IDLE\n" );
#endif
char runtimebuff[TIMEBUFF];
GetRuntime( runtimebuff, TIMEBUFF );
Send( SENDQ_MINPRI, "/me is a %s %s - Runtime: %s.\n", BOTNAME, VERSION, runtimebuff );
timer.Set( this, BOT_IDLE, idle );
break;
case BOT_RECON:
#ifdef BOT_DEBUG
Print( "BOT_RECON\n" );
#endif
Connect();
break;
case BOT_TIMEOUT:
#ifdef BOT_DEBUG
Print( "BOT_TIMEOUT\n" );
Print( "Connect timeout.\n" );
#endif
Disconnect();
break;
case BOT_LOGGED:
#ifdef BOT_DEBUG
Print( "BOT_LOGGED\n" );
#endif
//Set all the logtime timers
timer.Set( this, BOT_IDLE, idle );
timer.Set( this, BOT_STATUS, stat );
timer.Set( this, BOT_AWAY, zerotime );
break;
case BOT_AWAY:
#ifdef BOT_DEBUG
Print( "BOT_AWAY\n" );
#endif
Send( SENDQ_MINPRI, "/away I'm a %s(%s)!\n", BOTNAME, sysinfo.sysname );
timer.Set( this, BOT_AWAY, away );
break;
}
}
This, above, is the callback infrastructure.
//Start the bot
timeval tout = timer.NextSch();
int res;
while ( run && ( res = Select( &tout ) ) >= 0 ) {
timer.Exec();
tout = timer.NextSch();
if ( res > 0 )
printf( "Select() didn't evaluate all descriptors!\n" );
}
if ( res < 0 )
printf( "Select() error!\n" );
timer.Clear();
Here's the heartbeat of the bot.
To be able to bootstrap the bots, on constructor they would queue a connect timer to execute immediately. That way, as soon as Startup() is called, the timer.Exec() would kick them all into action ... like a series of dominos.
I know in Java there isn't really select() infrastructure, but maybe this will help you make things more compartmentalized rather than sharing a single send queue between all of them.