2-player game, client and server at localhost

Michael Rybak accepted at ukr.net
Wed Aug 3 05:24:58 EDT 2005


I was a bit unhappy to read this, because what you describe here is
just what I've tried yesterday in my test-game with 2 balls, so if
I've pointed that out, you wouldn't have to say I
DLB> don't understand threading either, it appears.
:'(

Thank you very much for putting so much effort in making things clear
to me, *this* email of your made everything clear as never. Still I
have problems, and hope you still don't mind me asking.

Some comments:

>>      while 1:
>>          render()

>>          inp = get_player_input()
>>          thread.start_new(send_inp, (inp,))
>>          thread.start_new(accept_opponents_inp, ())

DLB>         You don't understand threading either, it appears. Here you are
DLB> starting a new thread for each input you want...
and

>>          while still_no_opponents_input_from_previous_iteration()
>>                time.sleep(0.05)
DLB>         You're still focused on a turn based system, it looks like...

Now, that's *not* how I'm tending to do it; that's what I was thinking
of while trying the turn-based approach, and describing here;
Christopher Subich asked how it was done initially, so I brought my
hanging system out of shame again, and added something I was thinking
about to improve it, while I already realize that's not the way to go
- from your previous comprehensive posts.


DLB> There should just be AN input thread, started at the very beginning,
DLB> and it should just wait for input, then update the global state so
DLB> the "update step" can do whatever.

As stated above, that's how I'm trying it right now. Still, if doing
it turn-base, I would have to create a new thread every time.
   I have some other questions though - please see below.

DLB>         Also, recommend you use threading, not thread as the module.
Surely will, why?

DLB> SERVER
DLB>         An input thread would look something like:

DLB> while True:
DLB>         data = socket.read() #assumes both clients write to same socket
DLB>                 #otherwise use a select(client_socket_list)
DLB>                 #followed by socket.read for the flagged socket(s)
DLB>         parse data
DLB>         lock global
DLB>         save in global state variables
DLB>         unlock global

Now, few questions. Do I need to time.sleep(0.xxx) in any of these
while True: loops, not to overwhelm CPU? I can measure the time at
beginning and end of each iteration to make things happen fixed number
of times per second, but should I? And another: do I get it right that
instead of "lock global" you mean:
      while global.locked:
          time.sleep(0.001)
      lock global
And I also wonder how do I make sure that 2 threads don't pass this
"while" loop simultaneously and both try locking global. There is a
probability, not?

 Now, for update thread:

DLB> That is all

DLB>         Update thread
DLB> while True:
DLB>         lock global
DLB>         copy state variables to local
DLB>         unlock global
DLB>         compute new world state (check for collisions -- "bullet" hits)
DLB>         for c in client_list
DLB>                 send world state to c

In my yesterday experiment, I have a separate thread for each of 2
clients, and what I do there is:

def thr_send_status(player_sock):
    while 1:
        t, sub_addr = player_sock.recvfrom(128) #player ready to accept
        player_sock.sendto(encode_status(g.get_status()), sub_addr)

I'm reading 1 byte from client every time before sending new update to
him. OK, Ok, I know that's not good, ok. Now, I like your idea much
more, where you say we first measure the processing speed of each
client, and send data to every client as often as he can process it:

DLB> Slow client receives the first at T0, but the last (packet 100) comes in
DLB> at T0+60. Fast client receives all packets in T0+10...

DLB>         It took 60 seconds to receive 10 seconds of data. Client sends
DLB> back to server a rate of 6. Other client sends back rate of 1.

DLB>         Server now uses a modulo function on packet sends...

DLB>         for c in client
DLB>                 if t mod c.rate  = 0
DLB>                         send current world packet to client


Yes, this I great, but what if speed changes? Should I retest this
around every 10 seconds? I mean, server sending too much data to a
client is ok for server, but very bad for a client, since it starts
hanging and loses synchronization, unless I use a timestamp to throw
away late states (I think I need to re-read all your previous posts,
you've explained about synchronizing already).
 While thinking about this, I've decided to go the wrong way, and to
wait for confirmation from client before sending next pack.

Still, it shouldn't slow anything a lot, because client does this:

    def thr_get_status(self, g, player_sock, player_id):
        while 1:
            player_sock.sendto("!", addr) #ready
            self.local_status = decode_status(player_sock.recvfrom(128)[0])
            g.set_status(self.local_status)


So they simply exchange data all the time in separate threads, even
through separate sockets (not to dispatch the confirmations from
different players into corresponding threads via global flags), which
makes the "ready" thing ok from speed point of view, but I prefer
your way, quoted above, so my question is still valid - do I
remeasure speed?

 I'm also almost sure it's wrong to have separate sockets and threads
for each player, you say I should select()/dispatch instead, but I'm
afraid of that some *thing* being wrong with select() for Windows.
Somehow, I'm doing a lot of thins the wrong way :(


Before describing another problem I've encountered, I thought I'd
remind you of what my test game is: each player controls it's ball by
moving mouse pointer, towards which his ball starts moving; that's it.

When I first tried this with threads, 1 client ran nearly perfect. But
when running 2 clients via localhost, they eat cpu away, and bad things
happen.

Now, you see, sending user motions to server appears to be faster (or
at least not slower) than getting new status, so, as a result, I have
the following picture: I place my pointer somewhere, and the ball runs
to it, and then runs a bit beyond it, because by the moment server
knows the ball is already at my pointer, client still thinks it's not.

Several seconds later, thins "a bit beyond" becomes "a lot beyond",
and finally both balls run away from game field, being helpless; seems
like I/O thread being much more productive than send_status one?

I thought that the reason is my stupid "ready" check before every
status update, and removed it. As a result, even single client via
local host was very slow, but no artifacts.

This all was when I had *no* time.sleeps in the while 1: loops.
So I added time.sleep(0.005) to server's send_status loop and to
client's send_user_action loop. Things became better, but still balls
are spinning around the mouse pointer instead of running under it.
Every time client tries moving towards the mouse, it's ball is already
at another place at server, and it runs in wrong direction.

Obviously, if I have these problems in such a primitive test at
localhost, I shouldn't even try running it online with the Snakes :(


Thanks again for being this helpful, Dennis, and thank you for your
patience!


-- 
Best Regards,
 Michael Rybak                       mailto:accepted at ukr.net

Antivirus alert: file .signature infected by signature virus.
Hi! I'm a signature virus! Copy me into your signature file to help me spread!




More information about the Python-list mailing list