I do but what if the client is lagging over the number of cached frames ?
If by that you mean it lags for longer than the server can compensate for, then you fail hard, meaning that the server waits for moves and in the meantime any client moves that are missing are "corrected" by the server state, meaning stuck in place. You could tell the server to defer simulation (in other words, don't update the move id as it didn't actually simulate), but that would lead to the server having too many moves, and the client remaining "lagged" (they would not feel the lag but their events would happen later and later). You want to drop moves as the buffer fills up and then simulate as normal.
How do you get offest_ticks here ?
offset ticks is essentially
roundf(buffer_time * TICK_RATE)
This is action RPG-ish and if i had the choice i would go UDP but i'm using flash.
Hmm, in which case you really have little choice at the moment. I did stumble across this http://stackoverflow.com/questions/7728623/getting-started-with-rtmfp But otherwise you'll be sort of stuck, unless you ensure your move buffer is long enough to hide resend latency (might be best to ask other TCP familiars if you need to change your approach)
So you set a fixed buffer size, same for all clients ? And no matter the latency, if the client is over that size, he's not meeting the requirement to play that game ? If so how big usually is that buffer ?
I don't set the buffer size as a requirement actively. In my FPS, I have found that anything over 150-200 ms is uncomfortable to play with, but anything less and it becomes a waste of time to play. I still question whether that is due to other effects or just network latency. Play-testing will tell.
I'm still in the mist with setting the first frame ID (client to server), let's say i'm having a 1second lag when setting the ID, no map loading, just no luck. I'm going to set frame ID 0 at t1 and the next frame ID1 2 3 4 5 ... all at once at t2, the server will increment ID 0 by server step at t1+lag, then will receive 1 2 3 4 5 ids while only incrementing ID0 to ID1 at t2+lag when the id should be way further. There's something that i'm really not getting here :S
The way I handle it, which is nicer to do in Python, is to have a sorted list (heap). Dropped packets are recovered and inserted at the correct point in the list when recovery occurs (when we ask for the next move and NEXT_MOVE_ID - PREVIOUS_MOVE_ID is greater than one).
I don't maintain a server-side value for the move ID. The server just consumes one move per frame.
Here's my pop method for the jitter buffer
def pop(self):
if self._filling:
return None
result = self._buffer[0]
previous_item = self._previous_item
self._buffer.remove(result)
# Account for lost items
can_check_missing_items = previous_item is not None and not self._overflow
# Perform checks
if can_check_missing_items and self.check_for_lost_item(result, previous_item):
missing_items = self.find_lost_items(result, previous_item)
if missing_items:
new_result, *remainder = missing_items
# Add missing items to buffer
for item in remainder:
self.append(item)
# We just popped this, return to buffer
self.append(result)
# Take first item
result = new_result
# If buffer is empty
if not self._buffer:
empty_callback = self.on_empty
if callable(empty_callback):
empty_callback()
self._filling = True
self._previous_item = result
self._overflow = False
return result
Essentially what happens is, if the buffer overflows (is overfilled), we remove a lot of items, and we will see difference in the next ID, which makes it look like a lost ID, hence we have to account for that.
- check_for_lost_items does the ID check as I mentioned
- recover_missing_items basically calls into the recovery system and returns the intermediate moves that were lost
Because moves are noticed to have been dropped if we have adjacent moves:
(e.g T0, T1, T2, [missing moves] T5, where bold indicates the previous move)
We do the following:
- Get the missing moves
- The first missing move is out current move, the rest are added to the move buffer
- The move we removed that indicated we lost moves is also a later move, so we add it back. My buffer automatically sorts it to the correct position
- Return the aforementioned first recovered move.
You're not doing it like this, and so it might not be so useful, but it demonstrates the idea.
If you simply maintain an index into the lookup buffer, and increment that every time you request an item, you will be fine (ensuring it wraps around). Any items that overfill the buffer are dropped. You only want to fill the buffer to a fraction of its full capacity, meaning that if you suddenly receive too many moves, they wont be dropped. (Let's say you 3/4 fill it). That faction must correspond to the amount of delay you want to use (my 150-200ms). Use the stored move's ID for any ack/ correction procedures. Just think of the server value as a lookup only.
This way, if a client tries to speed-hack, they just have moves that are dropped, because the buffer becomes saturated eventually. The server consumes moves at the permitted tick rate, which the client cannot influence.