GNet Proper Practices?

BlitzMax Forums/BlitzMax Programming/GNet Proper Practices?

Will(Posted 2007) [#1]
I've got a GNet based game running beautifully over my local network, but terribly over the web. According to activity monitor, its only requiring 10-15k/second (well within my connection capabilities) up and down - but things are lagged by 30 seconds to a minute!

Here's the way I'm doing things:

All level objects are GNetObjects that aren't updated - so on first connection, tons of GNetObjects are synced - but then not changed (so that you download the state of the world from the server, but it doesn't cost additional bandwidth afaik) This works fine - I get the worldstate instantly and the level appears.

The client creates a GNetObject for its keypresses, etc - which the host detects immediately (works)

The host creates two characters with GNetObjects for itself and teh client. The hosts character seems rather lagged when moving (10-15 seconds) while the clients cahracter can lag anywhere from 30 seconds to 2 minutes. Or completely spas out.

GNetSync is called at the end of every frame, both machines were running around 60fps. I think that means, on the client, 60x a second it was sending its keystate, and on the server, 60x a second, it was sending the position and animation states of 2 characters. This is relatively tiny amounts of data...

Also, at one point, it broke into the debugger with a message about unexpected error in ENet - before I could examine the execution tree, it crashed and debugger disappeared (routinely this happens with GNet projects for me).

How do you do GNet stuff?

Am I better off finding another networking library, because unless I'm misusing GNet, it's unsuitable for internet connections (everything works flawlessly on LAN)?


TaskMaster(Posted 2007) [#2]
You are sending info every frame? That is WAY too fast for the Internet to handle. if you figure that the average time for a packet to go from one place to another is around 60ms (that is a good time), then 60 packets per second is way to fast, that would be 3 seconds.

You should not need to send data nearly that often. A human can only, at best, press between 5 or 10 keys in a seconds time, there is no reason you should need to send data any faster than that. Even less would be better.


Will(Posted 2007) [#3]
If I sync 10 times a second, won't this be much too unresponsive? Here's the long-case scenario:

0ms, client clicks to fire
100ms, client syncs - sends shot to server (lets say 60ms travel time)
160ms, server receives shot
260ms, server syncs, notices & processes shot,
360ms, server syncs, sends shot's effects to client
320ms, shots effects reach client
420ms, client syncs, notices a& processes shots effects

Now, this is if the packets are hitting them at a bad time, ie: with the 100ms gap between syncs at its maximum, but still, won't syncing that infrequently result in very noticeable (and almost unplayable) delays?


Will(Posted 2007) [#4]
Just tried limiting the calling of GNetSync( to every 100ms, it's very delayed - given even if what the player sees isn't delayed by up to 260ms, with a control-lag of 260ms won't it be impossible for them to say- jump onto the edge of a moving platform?


DavidDC(Posted 2007) [#5]
It's amazing how difficult is it to find a clear and simple general-concepts tutorial on creating an internet-ready networked game.

All too often searches lead to socket theory etc, when all a beginner really wants to know is "the big picture" - i.e. how to ensure your game states stay synchronised, without sacrificing playability.

This is probably the best place to start, but even there it's hard to find the basics minus the lingo.

Anyway, that's just my (lack of) experience. This is such a key topic it'd be great if an expert would step in to clearly and simply fill in the blanks. Until someone does, my guess (blind-leading-blind) is:

1 Apart from init, only ever send state-change information. If it's not in the packet then it hasn't moved/done anything
2 Sync only 10 times a second
3 Use a static frame rate for game logic
4 Use fixed-step movement, rather than delta time
5 Client and server use same randomise seed
6 Run the full game logic from the client as well. So when the client player wants to move, they move instantly, just as if it was a single player game. If and when a server sync packet arrives, then client checks and updates positions as necessary.
7 Server knows best. Client always bows to server when game state discrepancies arise.

Such is the state of my ignorance! I've numbered the points for easy correction. Hint hint...

- David

PS - Will, are you using point 6?


Will(Posted 2007) [#6]
I'm using point 6 as much as possible, though I'm not moving the player until the server tells them too - because otherwise, every time they start moving they warp back moments later when the server catches up. Collision detection, physics, etc - all are run client-side as well and just modified when the server has updates.


TaskMaster(Posted 2007) [#7]
For movement, when the player wants to do something, the client should let them. If later the server says no, then the client should warp back.

If the server says yes, then they should be fine. The server should not have to send out their absolute position.

Think of it like this:

The client says I am this location, my heading is this, I am moving at this velocity, etc...

The server says OK, go ahead. Now the server knows that players last position, heading, and velocity. With a rough idea of ping times between the two, the server can take a good guess at where the player actually is knowing how long they have been moving forward already.

When the player wants to turn right, they send another packet, I am this location, my heading is this, I am moving at this velocity, I am turning to the right at this rate, etc...

The server should allow any move that is within reaosn and should be making sanity checks to make sure the player is not cheating. When the server receives a player move packet, it should check current location in memory against the location the player says he is at now, and do some simple math of how long since the last packet to determine if the amount of distance the player is reporting is possible or not within reason. If it isn't, then the server should send a packet saying No, you are here, this is your heading, this is your velocity, etc, and the client MUST obey.


Will(Posted 2007) [#8]
There's nothing for sending custom packets in GNet - everything must be a GNet Object - given this overhead, do you still consider these best practices for working with GNet?

Also, bear in mind that GNetObjects are synced to all peers - there's no real "server" beyond what you decide has authority.


TaskMaster(Posted 2007) [#9]
Absolutely. The method of sending the information is not the issue, it is what you do with the info that is important.

Client send the whole player object that holds his location, facing, heading, etc. Let the server receive it and compare it with what it knows about the players previous position, heading, etc...

The client should allow any movement it knows to be legal. Obviously it should not let a player walk through a wall. The server's responsibility is to make sure the player isn't cheating and share the players info with other players.


xlsior(Posted 2007) [#10]
Now, this is if the packets are hitting them at a bad time, ie: with the 100ms gap between syncs at its maximum, but still, won't syncing that infrequently result in very noticeable (and almost unplayable) delays?


Yes... Which means you'll need to do some magic to predict whats going to happen as well...e.g. rather than drawing the location where your oponent actually 'is', draw him where he will most likely be 60ms from now (intrapolate the movement directions and speed), give your bullets a wider collision/blast radius so you don't need a 100% accurate hit, etc.

You can also wait a moment processing a 'hit' until you verify with the other client that he didn't suddenly stop moving or stepped sideways in that past 60ms or so.

But either way, it's still a bit of voodoo to make a network game feel responsive when it's really can't be.


TaskMaster(Posted 2007) [#11]
Ah, but the server has final say in that matter. At least it does if you want to protect from hackers.

The server should be where it is determined if a bullet hit a player. And 60ms will not make such a big difference that it is hugely noticeable to a human. That is the trade off you have when you play a game on the Internet.


Will(Posted 2007) [#12]
TaskMaster - won't the difference be up to 260ms if the server decides the result of a hit?


Reducing syncs to every 100ms minimum fixed the crashing - but the timing effects are pretty bad.

The trouble with simulating everything on the playerside as well, and giving them significant reign over things like the player, etc - is the required upstream for the player with GNet - because its peer to peer and not server to client, everything the player sends is sent to every person who connects, so if it takes 5k/sec to inform just the server, with 10 players thats 50k/second for each client - which by sending just keystrokes i was hoping to minimize, and let the load sit on the server (which ought to be on a decent connection).

If it gets about 10-12k/second ADSL players can't join the game.


xlsior(Posted 2007) [#13]
If it gets about 10-12k/second ADSL players can't join the game


How so?

Most ADSL connections are 1.5Mb/s or higher, which is 1500kbit... A lot more than your 10-12k.


Dreamora(Posted 2007) [#14]
Yes but not upstream

Common here with our broadband for example are 256kbit - 512kbit up compared to 10k - 20k down

I'm on 350kbit for example ... thats 40kbit ... but I know enough people with only 256 which means not that much left again to keep the latency low if 75% of your upstream bandwidth potentially is used ...


not to mention that someone needs to host it and that one has # of Players * 10-12kb/second ... so with tihs kind of upstream he can host 4 player games and is dead.

I would suggest using pure ENet for the networking or BNetEx by Vertex.
I will see if I can get in contact with Anthony and if he is fine with it, I will upload Aurora Net Ex (my modification of the original Aurora Net which is using BNetEx as BNet support was dropped long ago). That will most likely offer quite enough stuff.

Or you do as I do right now: use pub.enet and create your own networking. Does not need much as enet does not offer that many features and therefor is quite simple to wrap into an own small host - client thing or even host - host thing like gnet


xlsior(Posted 2007) [#15]
Yes but not upstream


True -- forgot about that.


TaskMaster(Posted 2007) [#16]

TaskMaster - won't the difference be up to 260ms if the server decides the result of a hit?



Depends, do you have bullet flight time, or is it instant. Both have an advantage and a disadvantage.

If it is instant, the disadvantage is the lag will hurt a little, the advantage is the players do not see the bullet, they see a players gun go bang, and they see a player take a hit. The players aren't really going to know if the person doing the shooting was still aiming directly at them or not...

Something like this: the firing client, fires, if the client decides it should be a hit, then let the client treat it as a hit. It also sends information about the shot to the server, where the server can then decide if it was a hit, this is more of a check to make sure the client isn't cheating. Then the sever sends the hit info to everybody else. If the server knows the target had moved and it is a miss, then the player who fired the shot may notice that the targets health went back up. Unless of course you have a one shot one kill game. Then you may have to let it be a server check before th shooter client shows it as a hit.

If there is bullet travel time, then the advantage is the client doesn't have to decide hits or misses at all. Client fires, send a packet to the server, I am firing from here, in this direction, this kind of bullet, at this speed, etc...

Then during the lifetime of the bullet, if at any time the server decides the bullet has impacted another player, it sends that information to everybody. Bullet index 33 hit player 2 at this position doing this much damage. Let the clients receive this packet and play an explosion anim or whatever needs to happen. The disadvantage of this method is that the players see the bullet, and if there is a bit of lag and a player was able to just dodge a bullet but didn't update the server in time for the server to realize the bullet missed, then the server tells everyone you got hit. But most players chalk this up to Internet lag and live with it if it is only off by a few pixels and it was close. That is when you get that, "Oh man, I dodged that bullet" exclamation from the player. :)

The important thing is to have a good balance of not too many updates, but enough updates to make the game look good.

Take the multiplayer spacegame I worked on years ago for example, to give you an idea. It was 2D like asteroids. The server knew what type of ship each player was. So, the server new the player acceleration, rotation rate, max speed, etc. I sent a packet whenever a player pressed a key that would change something about his position. This seems like a lot, but remember, the average player, while playing will only press about 1 key per second when playing quickly. So, 1 packet a second is great!

If the player held the up arrow down, I sent a packet that told the server the player was pressing thrust. And, whenever a player sent a packet to the server, I always put all of the players position info in the packet. So even a a chat packet might have this in it: I am at position X,Y; my heading is z.zz; my velocity is a.aa; and here the the chat text I just typed (of course, it was just the data, the server knows exactly what data to expect, none of the "I am..." data is really sent). On the average a packet can be about 1.5kb, so as long as you don't send more than that, you are not really wasting any bandwidth by padding a single packet.

Also, I would send a location packet if the player does not press a button in 3 seconds, just to make sure the player and the server are synced up every once in a while. This also acts as a keep alive. So, the server knows if the player dropped out for some reason. And, when I got a keep alive packet from the player. I would match every packet against what the server thought the current location was, and if it was close, then I would update the server and tell all of the other players. If it was way off, I would send a packet telling the client where the server thought it should be and the client would update.

So, it might go something like this:

player presses thrust - send player info packet
server sends the players packet to all the other player
player stops thrusting - send player info packet
server sends the players packet to all the other player
player starts turning right - send player info packet
server sends the players packet to all the other player
player stop turning right - send player info packet
server sends the players packet to all the other player
player fires - send player shoots packet with player info attached to it
server sends the players packet to all the other player
server sends packet telling everybody there is now a bullet at this location heading in this direction, at this speed

There is no reason to keep sending packets that the player is turning, because the server knows how fast the player can turn, so it knows what direction the player is facing at all times, just because it knows he is turning. The server just needs to know when he stops turning. When the player says he stopped, he sends his current location, current heading (direction facing), current velocity, etc. This helps make up for the lag. Instead of the server being a little bit wrong about the facing because of lag, the player told the server exactly what heading he was facing when he quit turning.

Anyway, I hope this helps you somewhat. It is just my take on how to do it, and it does work fairly well. I wrote that game in VB years ago and it worked just fine. :)

Eventually I'll get around to writing an updated version in BMax.


DavidDC(Posted 2007) [#17]
That was a great explanation TaskMaster - thanks. Certainly clarified a few key areas for me.

- David


Will(Posted 2007) [#18]
New thought - Perhaps I can modify GNet to break the sync-in sync-out into seperate calls, so that I can mitigate the 100ms per-sync slowdown at the receiving end (ie: they are all checking new data as fast as possible, and only syncing out every 100ms)

[edit]
After looking into it, I'm just rewriting my networking layer to use Pub.ENet instead of GNet.
It's too valuable not having to send all of the network update data to all peers.