I spent some time getting the network load produced by the game down. The rest of this post, I test out the changes to see how much better everything became.
Methodology
Two ships connected; I’m going to leave their starting locations as random. I’m going to have them turn in circles and constantly fire. This will yield some explosions against the planet, and others against each other, but most of the missiles will be flying out into space.
Running against a deployed web server in the cloud
Using WIFI and my home internet connection.
Collecting data via portal.azure.com’s App Service Monitoring graph set to minutes. (This didn’t work, I had to go with my local wifi connection)
https://github.com/sunnywiz/dotnetmud2015/tree/chatty1
This is where we started at. Sample packet and network load:
https://github.com/sunnywiz/dotnetmud2015/tree/chatty2
The main change was to give custom JsonProperty names, as well as to reduce the number of decimal places being transmitted.
I use Decimal instead of Double because in double’s, there’s a chance that the underlying representation isn’t quite so digit friendly; decimals are exact for every digit. However, decimals are harder on the processor for doing math – supposedly. I haven’t tested that.
The result: Not too much better. 5.5 MB came down to 3.1MB on the receive side.
https://github.com/sunnywiz/dotnetmud2015/tree/chatty4
chatty3, which I’m skipping, changed the method signatures to two-character method names. Not too much saved there.
chatty4 added “blanking out” data that doesn’t change much. It does this by keeping track of what it believes the client thinks the state is, and doing a diff:
In order to pull this off, I had to go to a dictionary of objects to render, rather than an array. I also kept a “full” frame every 10 frames, similar to MPEG encoding G vs I frames.
I thought I’d get fancy and send nulls if various values (DX, DY) had not changed (for example, they do not change for bullets) – however, JSON sends a “:null” which can be longer than just sending the value. The more advanced version of “don’t include the property if it hasn’t changed” is possible, but it got too complicated, so I ignored that for now. So this version only does the image and name attributes, but that’s enough to get a nice reduction in size:
The savings: another 30%. Its still a lot of data. Not quite the gains I was hoping for.
At this point, an average entity on the screen is taking 100 bytes or so, instead of the 500 they were before.
Network Optimization: Where to go from here?
I’m going to call this good enough for now. The directions to go from here for network optimization:
- Instead of using the system JSON serialization, write my own serializer. This could do things like “If It hasn’t changed, don’t send it”.
- so instead of: {“DR”:0.0,”DX”:17.109,”DY”:299.512,”ID”:0,”IM”:null,”N”:null,”R”:446.7,”RA”:3.0,”X”:37.0,”Y”:1474.8}
- “dx17.109dy299.512id341r446.7x37y1474.8” that’s about half the size. But needs a lot more code to massage it.
- Could use a binary packing method as well. Quick search doesn’t find anything that is happy in both C# and Javascript.
- Put some logic in as to whether something needs to be sent every time or not. For example,
- bullets pretty much go in a straight line once fired. They don’t need to be sent every time. In fact, if the current X,Y is approximately where the previous update X,Y + DX,DY would put it, then there’s no need to send those values.
- Planets currently don’t move. (That will change). This is a broader case of “nothing has changed in this object from what the client expects, so just acknowledge the object still exists, nothing more”.
What’s next?
I think that authorization / identity of clients (and dealing with disconnection better) is in order.
Then I can do the “single person logged in, multiple clients” code
And then we can launch a spaceship from the text game.
And then we can design an actual playable game. (oooo!)