#45854 - pafpaf - Wed Jun 15, 2005 9:43 pm
Hello all! (first post)
I've started on multiplayer comunications some days ago, and
with the help from the other posts on the subject i've managed to
get it working on the hardware.
I?m trying to implement a multiplayer feature on one game and i
was wandering if this might be a possible :
________________________________
game logic begins
{
Process player 1
Transfer data to slave
player 1 waits for player 2 data
Process player 2
Transfer data to master
}
game logic ends
both players resume processing whats left to do (graphics etc...)
next frame
_____________________________
the thing is, i need to make more than one comunication per frame
this way i think i can't use a timer to keep it in sync.
the timer use for sync and the use of the irq in communications is
still not understood completely by me.
well thats it, sorry for such a long post and thanks in advance !
#45860 - Miked0801 - Wed Jun 15, 2005 10:03 pm
You'd be better off running a tic behind on both master and slave and running simultaneously (double buffering input).
game logic
{
while(1)
{
run gameloop using previous tics input
while(transfer not complete)
{
transfer inputs into master then from master to slaves
}
swap input buffers and go to next tic
}
}
This is how we've done it on all our games. It allows perfect error handling and keeps all games running lock-step with each other.
#45865 - pafpaf - Wed Jun 15, 2005 10:30 pm
i think i understand the idea you described.
my plan was to divide the processing of the game between the master
and the slave, but maybe there is not enough transfer rate to do so.
in your solution, both master and slave must process everything ?
the game runs at 30fps, does this influence the transfer rates?
should i limit the tranfers to the players inputs, and some flags, or is
it viable to transfer players structures?
thanks for such a quick reply !
#45882 - Miked0801 - Wed Jun 15, 2005 11:50 pm
We only transfer initial random seed and then inputs. We can transfer other stuff, but the throughput is low enough that this causes us to lose (as in not send) input packets.
Also, you don't have to run at 30Hz to do this, though interrupt overhead will eat seriously into your overall CPU time to the point where 30Hz is a good target. And yes, both process everything. Every system knows where every other "Player" is and processes their data identically so they stay in sync.
The only (very minor) draw back is the fact that input lags 1 tic on all systems and I promise you won't notice it. At 1 point, I lagged the input 3 tics, and it felt sluggish, 2 felt slightly sluggish, but 1 is just fine.
#45939 - pafpaf - Fri Jun 17, 2005 12:11 am
this is probably lame but, how necessary is it to use a timer and interrupt
for the comunications?
the source Scott Lininger wrote doesn?t use them and seems to work.
doesn?t the loops guarantee sync if i have a check code for each frame ?
#45953 - ScottLininger - Fri Jun 17, 2005 1:32 am
My multiplayer code doesn't use interrupts or timers, and it works fine. But it won't perform as fast as Mike's code, even though the "surface level" code structure that maintains sync is identical to what Mike posted above.
There are a couple reasons for this. With interrupts every gameboy can pull down data as it comes, and he's presumably only checking for dropped packets (via a checksum) once a frame. My code doesn't use a checksum -- it confirms every byte as it's sent and received. The master gameboy won't transfer the next byte until every slave echoes it back. It just sends and sends and sends until it knows the data arrived.
This is slow, but it's reliable, as far as I've tested.
So, like everything else in the homebrew world, the decision is probably more about what your game needs. If you end up using my code or something similar, I'd be really curious to see the results.
-Scott
#45994 - Miked0801 - Fri Jun 17, 2005 7:40 pm
Good call Scott. Yep - a checksum and packet number is sent every tic. The checksum is not only built from raw data, but local random seeds and key player positions. This way we find immediately if rogue game logic has caused us to go out of sync. The packet number also makes sure that we lockstep as well. A little more overhead on the send, but 12-16 bytes total per game loop is just fine - CPU/Interrupt/Handler code overhead is roughly 10% or so when running 30Hz with code located in ROM. In fast RAM, it'd go faster yet. THe logic for sending all is well continue is actually the hardest part to get right.
1. All are ready (post magic ready number when hit end of loop)?
2. Send data
3. Verify data and send magic number "all is good" if good
4. If fail, send magic number "bad data" until all resond in kind and goto 2
5. Master sends magic number "Go to next tic" to slaves
6. Slaves respond with we're gone now.
7. Master goes to next tic.
#46126 - pafpaf - Mon Jun 20, 2005 4:20 pm
hello again!
so far i've managed to get the coms working on the hardware with
timer and interrupts, but i have one more doubt... how should i calculate
the timer's setting ? i don?t remeber who but someone said to ditch a
macro on the sadge source, i supose the one about the timer...
i?ve searched everywhere and i can?t find this information.
(i know not even the official manual has this info... i?ve seen it but i don?t have it...).
thanks for all the help !
#46137 - Miked0801 - Mon Jun 20, 2005 6:28 pm
Keep lowering the timer value until you start getting serial interrupt errors. There is a lower threshhold at which you're trying to re-trigger the resend before the last one was complete. Just play until you get it right.
#50756 - pafpaf - Fri Aug 12, 2005 6:46 pm
hello!
thanks once again for all the help, but there is a problem i can?t solve...
If i start the master gba and then the slave it locks
If i turn the slave on first it works...
what could it be?
the code is almost like the one in socrates.
my apologies in advance for the gigantic post...
this is the code for the initial comms just to do sync and error checking :
// THIS IS THE FIRST FUNCTION I CALL FROM MAIN TO START THE COMMS
int coms_iniciar_multiplayer()
{
//depois o slave e colocado a zero quando fizer o check do SI
coms.master=true;
tentativas=0;
//limpa o reg para preparar para multiplayer
REG_RCNT = 0;
//troca para o modo multiplayer
REG_SIOCNT = SIO_MULTI;
//o estado das coms
coms.estado=MP_N_INICIADO;
//seleccionar o baud rate
//REG_SIOCNT |= SIO_115200 | SIO_IRQ;
REG_SIOCNT |= SIO_115200;
//esperar pela flag SD ficar a 1 BIT(3)
while (REG_SIOCNT & SIO_START || (!(REG_SIOCNT & SIO_NOTRDY )) )
{
//cancelar?
if (KEY_SELECT_PRESSED)
{
return 0;
}
}
//o que fica com o SI a 0 e o master
if (REG_SIOCNT & BIT(2))
{
coms.master=false;
}
// ja sei qual e o master e qual o slave
// prepara a primeira comunicacao para obter os ids e verificar que esta tudo bem
if (coms.master==true)
{
texto_charXY(18,0,"MASTER",6);
//setup do contador do timer para o master
REG_TM3CNT_L=0xFFFF-COMS_TIMER;
//liga o interrupto e atribui a func
EnableInterrupt(Int_Timer3);
SetInterrupt(Int_Timer3,coms_send_inicial);
// codigo de verificacao para a transmissao inicial
REG_SIOMLT_SEND=FLAG_COMS_MASTER;
//e o master entao inicia a transmissao
REG_SIOCNT |= SIO_START;
//liga o timer e o interrupto
REG_TM3CNT_H = TIMER_START | TIMER_IRQ;
// codigo de verificacao para a transmissao inicial
REG_SIOMLT_SEND=FLAG_COMS_MASTER;
}
else
{
texto_charXY(18,0,"SLAVE",5);
//e o slave, coloca o codigo de verif.
REG_SIOMLT_SEND=FLAG_COMS_SLAVE;
//liga o interrupto e atribui a func
EnableInterrupt(Int_Serial);
SetInterrupt(Int_Serial,coms_send_inicial);
//ligar o irq para o slave
REG_SIOCNT |= SIO_IRQ;
//e o slave, coloca o codigo de verif.
REG_SIOMLT_SEND=FLAG_COMS_SLAVE;
}
//espera ate a comunicacao ter sido estabelecida com sucesso
while (coms.estado!=MP_INICIADO)
{
//cancelar?
if (KEY_SELECT_PRESSED)
{
return 0;
}
if (coms.estado==MP_MASTER_REPETIR)
{
while (REG_SIOCNT & SIO_START)
{
//cancelar?
if (KEY_SELECT_PRESSED)
{
return 0;
}
}
//continua a tentar
if (coms.master==true)
{
// codigo de verificacao para a transmissao inicial
REG_SIOMLT_SEND=FLAG_COMS_MASTER;
// inicia a transmissao
REG_SIOCNT |= SIO_START;
//liga o timer e o interrupto
REG_TM3CNT_H = TIMER_START | TIMER_IRQ;
}
}
if (coms.master==true)
REG_SIOMLT_SEND=FLAG_COMS_MASTER;
else
REG_SIOMLT_SEND=FLAG_COMS_SLAVE;
}
//****************************************************************
// BOTH GBAS SHOULDN'T PASS THIS POINT UNTIL THEY ARE IN SYNC
// but they seem to...
//****************************************************************
//setup dos interruptos respectivos a func de transferencia
if (coms.master==true)
{
SetInterrupt(Int_Timer3,coms_enviar_packet);
coms.estado = MP_PRONTO;
}
else
{
SetInterrupt(Int_Serial,coms_enviar_packet);
coms.estado = MP_PRONTO;
}
return 0;
}
// THIS IS THE FUNC I SET FOR THE INTERRUPTS BOTH TIMER AND SERIAL
void coms_send_inicial()
{
//se e o master desliga o timer
if (coms.master==true)
{
REG_TM3CNT_H = 0;
//reset do irq do timer
REG_IF |= BIT(6);
}
else
{
//reset do irq das coms
REG_IF |= BIT(7);
}
u16 a=0,b=0,c=0;
coms.erro = ERRO_NENHUM;
//guarda os dados recebidos
a=REG_SIOMULTI0;
b=REG_SIOMULTI1;
c=REG_SIOMULTI2;
//verificar os ID's apos a tranmissao
if ( (REG_SIOCNT & BIT(4)) && coms.master==true)
{
coms.erro = ERRO_ID_MASTER;
}
else
{
if ( (!(REG_SIOCNT & BIT(4))) && coms.master==false)
{
coms.erro = ERRO_ID_SLAVE;
}
else
{
//verificar se estao apenas 2 gbas ligados
if (c!=FLAG_COMS_NOGBA)
{
coms.erro = ERRO_3_GBAS;
}
else
{
//flag de erro esta a zero ?
if (REG_SIOCNT & SIO_ERR)
{
coms.erro = ERRO_FLAG_ERRO_SIOCNT;
}
else
{
//verifica os codigos de comunicacao inicial
if (a==FLAG_COMS_MASTER && b==FLAG_COMS_SLAVE)
{
if (coms.master==true)
//print("\n master : codigos synck ok");
texto_charXY_alinhado(0,0,"master :sync ",0);
else
//print("\n slave : codigos synck ok");
texto_charXY_alinhado(0,0,"slave :sync ",0);
}
else
{
coms.erro = ERRO_FLAGS;
}
}
}
}
}
texto_charXY_alinhado(0,16,"err",0);texto_intXY(5,16,coms.erro);
//verificacao final
if (coms.erro == ERRO_NENHUM )
{
//liberta coms_iniciar_multiplayer()
coms.estado = MP_INICIADO;
//print("\n OK");
texto_charXY_alinhado(0,19,"OK",0);
}
else
{
if (coms.master==true)
coms.estado=MP_MASTER_REPETIR;
tentativas++;
/*
print("\n ERRO : ");Print_Int(coms.erro);
print(" master ? ");Print_Int(coms.master);
*/
texto_charXY_alinhado(0,3,"tentativas ",0);texto_intXY(12,3,tentativas);
}
}
//THESE ARE SOME OF THE THE DEFINES
#define COMS_TIMER 15000 //0x3E80
#define MP_N_INICIADO 0
#define MP_INICIADO 1
#define MP_TRANSFERIR 2
#define MP_PRONTO 3
#define MP_MASTER_REPETIR 4
#define ERRO_NENHUM 0
#define ERRO_ID_MASTER 1
#define ERRO_ID_SLAVE 2
#define ERRO_3_GBAS 3
#define ERRO_FLAG_ERRO_SIOCNT 4
#define ERRO_COMS 5
#define ERRO_FLAGS 6
#define FLAG_COMS_MASTER 66
#define FLAG_COMS_SLAVE 77
#define FLAG_COMS_NOGBA 0xFFFF
#51032 - pafpaf - Mon Aug 15, 2005 6:56 pm
nevermind...
i just needed to make the master gba wait for a keypress after
the initialization...