5
\$\begingroup\$

I wrote a small test car driving multiplayer game with Box2D using TCP server-client communcations.

I ran 1 instance of server.exe and 2 instance of client.exe on the same machine that I code and compile the executables. I type inputs (WASD for a simple car movement) into one of the 2 clients and I can get both clients to update the simulation.

There are 2 cars in the simulation. As long as the cars do not collide, I get the same identical output on both client.exe. I can run the car(s) around for as long as I could they still update the same. However, if I start to collide the cars, very quickly they go out of sync.

My tools: Windows 7, C++, MSVS 2010, Box2D, freeGlut.

My Psuedocode:

// client.exe
void timer(int value)
{
   tcpServer.send(my_inputs);
   foreach(i = player including myself) inputs[i] = tcpServer.receive();
   foreach(i = player including myself) players[i].process(inputs[i]);
   myb2World.step(33, 8, 6); // Box2D world step simulation
   foreach(i = player including myself) renderer.render(player[i]);

   glutTimerFunc(33, timer, 0);
}

// server.exe
void serviceloop
{
    while(all clients alive)
    {
       foreach(c = clients) tcpClients[c].receive(&inputs[c]);

       // send input of each client to all clients
       foreach(source = clients)
       {
          foreach(dest = clients)
          {
              tcpClients[dest].send(inputs[source]);
          }
       }
    }
}

I have read all over the internet and SE the following claims (paraphrased):

  1. Box2D is deterministic as long as floating point architecture/implementation is the same.
  2. (For any deterministic engine) Determinism is gauranteed if playback of recorded inputs is on the same machine with exe compiled using same compiler and machine.

Additionally my server.exe and client.exe gameloop is single thread with blocking socket calls and fixed time step.

Question: Can anyone explain what I did wrong to get different Box2D output?

\$\endgroup\$
19
  • \$\begingroup\$ finally got attetion. Any clues anyone? \$\endgroup\$
    – Jake
    May 15, 2012 at 14:43
  • 3
    \$\begingroup\$ It seems like it would be trivial to attach a debugger or put in some tracing code and see at which operations they diverge. There's just not enough information here to help you without first building an entire toy simulation that might bear no resemblance to your own. \$\endgroup\$
    – user744
    May 17, 2012 at 13:21
  • \$\begingroup\$ I suspect that you have to (really) fix your timestep. gafferongames.com/game-physics/fix-your-timestep glutTImerFunc is not accurate enough. \$\endgroup\$
    – kaoD
    May 17, 2012 at 14:45
  • 1
    \$\begingroup\$ This design may be too sophisticated for your current skill level. Please try the architectural change that @JoeWreschnig suggests before continuing, it is less complex and will let you focus on the immediate problem. Personally I suggest you then move to a proper server authoritative and client predictive architecture, it is scalable, resistant to cheating and glitches in timing whereas deterministic simulations fall apart at the first disparity (as you've noticed). \$\endgroup\$ May 17, 2012 at 17:44
  • 1
    \$\begingroup\$ My suggestion was to debug to determine if Box2D is deterministic. The FAQ says it is, but obviously it could have a bug. Since you refuse to debug your code who knows. \$\endgroup\$
    – user744
    May 18, 2012 at 8:53

2 Answers 2

1
\$\begingroup\$

Maybe glutTimerFunc(33, timer, 0); produce different results on different machines. Another thing is that your code. takes different amount of time to execute every once you run it (especially on different computers), so that may be the source of desynchronization.

Anyway the right way to do what you want to achieve is to run main physics simulation on server and send results to clients. You can avoid lag by making clients run their own simulation for results to use in time between getting results from the server.

\$\endgroup\$
5
  • \$\begingroup\$ If you see my code again, I block until I receive all clients' inputs before I process them for client update. Clients do not step simulation more than once between two tcpServer.receive(); \$\endgroup\$
    – Jake
    May 17, 2012 at 15:19
  • 2
    \$\begingroup\$ Oh, yeah, sorry. Still second part of my answer is valid. You can't rely on clients simulations, clients may cheat. EDIT: just random thought: are bodies in collection in the same order on both clients? Order matter in floating point operations. \$\endgroup\$
    – zduny
    May 17, 2012 at 15:35
  • \$\begingroup\$ +1 Thanks for your very constructive suggestion. I actually got the bodies collection in the same order, but you made me realise there may be a race condition between glutKeyboardFunc and glutTimerFunc... I just need to check now. \$\endgroup\$
    – Jake
    May 18, 2012 at 3:22
  • 1
    \$\begingroup\$ glutKeyboardFunc isn't even in any of your example code, reinforcing my point that you need to do more before we can help you. This question is quickly devolving into "guess my mistake" and I can't even vote to close it because you put a bounty on it. :( \$\endgroup\$
    – user744
    May 18, 2012 at 8:54
  • \$\begingroup\$ Maybe you are imagining me sitting on a couch drinking coffee while waiting for others to guess my mistake. Sorry to say, I am actually trying all ways to debug. Perhaps if you are interested in the bounty, you could at least suggest which part of my code you want to see. Or simply comment whether my steps as posted presents any sync issues and guess where else I can look at. I will appreciate that, really. Afterall, guessing is not bad a technique especially when you have problems never encountered before. \$\endgroup\$
    – Jake
    May 18, 2012 at 12:51
-2
\$\begingroup\$

The problem is that the two cars are out of sync due to the cars not syncing, I guess. I would do something like this:

//client
void tick() {
    step(a,b,c)
    for(body in myb2World) {
        if(body.userdata.owner == this) {
            tcpServer.send(body);
        }
    }
}

//server
void serviceloop
{
    while(all clients alive)
    {
       foreach(c = clients) tcpClients[c].receive(&inputs[c]);

       // send input of each client to all clients
       foreach(source = clients)
       {
          foreach(dest = clients)
          {
              tcpClients[dest].send(inputs[source]);
          }
       }
    }
}

and at the client the bodies not of that client are updated with the correct state.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .