Advertisement

Sending data which changes infrequently from server to client

Started by October 18, 2017 09:23 AM
7 comments, last by caymanbruce 7 years, 1 month ago

I am having this problem but I struggle to find a way to properly address it.

I have a string type "username" field of the player instance that I want to send from the server to the client. Let's say there are 300 players in the game. I don't want to send the "username" too often because that will stack up the data package size both sent out from the server and received by the client. Sadly I am doing this right now and it is costly.

All I want is to show the player's username on the screen of the client. It will always be on top of each player's entity. If the players are always on the client that is very easy to code. I will just send all usernames once and assign them to all the players. Done. 

Tricky thing is, the players are often on and off the screen when they move, which means another player will move inside of a player's view or outside of his view. So every time the client receives the data package from the server, it will try to look for the players that matches the existing usernames, if the "username" field was not sent in every update package. This will require me to maintain a data structure that frequently adds and removes a "username" value and search for one that matches the package and the client side and then display it. I may need to match the players and the existing names on the client side by using their IDs.

This may seem obvious. But before I implement anything. I just want to know if there is any standard/common way to do this in game?

 

 

1) You shouldn't be unloading entire entities just because they're temporarily out of view.

2) Even if you do have to unload something, you can maintain a cache of important data about it if you like. But, see the previous point and the next one.

3) You shouldn't be matching things by names anyway. That's what IDs are for. And you would expect those to be sent with every message that concerns that entity.

Advertisement

Put out-of-view entities to sleep and only remove them if they haven't been seen for a longer time. A bunch of seconds works well for me, but depends on size of your entities, how often and for how long they might be out-of-view and so on.

In addition you could try to make each player's field of view larger on the server than it is on the client.  So entities get sent shortly before they actually appear on screen, and continue to get sent even if slightly off screen.

1 hour ago, Kylotan said:

1) You shouldn't be unloading entire entities just because they're temporarily out of view.

2) Even if you do have to unload something, you can maintain a cache of important data about it if you like. But, see the previous point and the next one.

3) You shouldn't be matching things by names anyway. That's what IDs are for. And you would expect those to be sent with every message that concerns that entity.

Yes suppose I am keeping some usernames on the clients for a longer period, the main thing I should do is to match usernames with incoming packages. I did mention in my post I will be searching them by ID. I just wonder how often do I need to add or remove a username from the client. When a player dies its ID will be assigned to a new player. So when I remove this player's username on the client and possibly add the new one which uses the previous player's ID, how should I deal with this? I am recycling the ID because I want to use uint 32 to reduce bandwidth.

1) Why do you think usernames, of all things, need special treatment? It's just another a bit of data that is attached to an entity. Send it once when the client first learns of the entity. Send it again whenever it changes (i.e. never - but we're talking about the general case here). Delete the entity from the client when you're sure the client no longer needs it (i.e. it is too far out of view to be worth updating any more).

2) Don't reassign IDs. 32 bits gives you enough IDs to be able to allocate 1000 per second for a whole month without risking running out. But generally a 64 bit ID is better; you can save bandwidth in more meaningful ways elsewhere.

19 hours ago, Kylotan said:

1) Why do you think usernames, of all things, need special treatment? It's just another a bit of data that is attached to an entity. Send it once when the client first learns of the entity. Send it again whenever it changes (i.e. never - but we're talking about the general case here). Delete the entity from the client when you're sure the client no longer needs it (i.e. it is too far out of view to be worth updating any more).

2) Don't reassign IDs. 32 bits gives you enough IDs to be able to allocate 1000 per second for a whole month without risking running out. But generally a 64 bit ID is better; you can save bandwidth in more meaningful ways elsewhere.

Sorry my bad it's not uint32 I actually initially use uint8 that's 0-255 but then I extend it to a varuint type I defined so it seems OK for now.

I am drilling down the problem that I have. I think the problem is that I don't break up the packets and send them separately to be able to do the basic delta encoding. Currently I am sending everything in every update package. That's why "username" field is there. 

Because of what I am doing at the moment, the client learns of the entities from a normal update package, which includes other players' names. And If other entity is not on the client side, it will be created with the incoming information.

Pseudo code:


entityStruct = receiveUpdate(); // Receive update package from the server

remoteEntity = decodeFromSchema(schema, entityStruct); // Decode the struct using a schema

if !playerList.has(remoteEntity.id) {

    createNewPlayer(remoteEntity) // Create new player with incoming update package, which includes the username

}    

I know, this won't scale well. So I think this is actually a delta encoding for sparse repeating fields problem. I have learned from StackOverflow.com that I can do that by sending different frames to the client. Such as delta frame and key frame. But I am using a schema technique which is similar to ProtoBuf to encode and decode my data packets, so I will need to send 2 or 3 types of information from the server at different frequencies.

3 types of information:

1) The full packet which is key frame.

2) Delta frame which contains the changes and this will be used for interpolation.

3) Maybe for "username" kind of thing that never changes?

By doing this, I am not sure where I should place the "createNewPlayer" method, as different information comes at different intervals and I can't collect a full copy of the remote player info at the moment I want to recreate it on the client side.

Advertisement

The simple way in which I handle things like this is to have 3 message types:

1. Entity-Created. This contains all information about an entity, and lets a client know it needs to create the entity locally.

2. Entity-Updated. This contains only information about an entity that has changed since the last time I sent it to this client.

3. Entity-Deleted. This tells the client to delete the entity.

If you want to go down the delta encoding route then you can see that Entity-Created messages are a special case of Entity-Updated messages where the previous state was null.

One thing I can tell from this message (and previous ones) is that you're not clearly separating the problem into different domains - you're worrying about byte-level representation at the same time as you're worrying about interpolation between states and about deciding what information to send and when. For example, you're allowing the size of your IDs and your choice of serialization format spill out into discussions of when to send usernames. Programming should be the other way around, as far as possible - you decide what you need to do at an abstract level, and only then decide the low level details, making sure they fit your high level requirements.

14 minutes ago, Kylotan said:

The simple way in which I handle things like this is to have 3 message types:

1. Entity-Created. This contains all information about an entity, and lets a client know it needs to create the entity locally.

2. Entity-Updated. This contains only information about an entity that has changed since the last time I sent it to this client.

3. Entity-Deleted. This tells the client to delete the entity.

If you want to go down the delta encoding route then you can see that Entity-Created messages are a special case of Entity-Updated messages where the previous state was null.

One thing I can tell from this message (and previous ones) is that you're not clearly separating the problem into different domains - you're worrying about byte-level representation at the same time as you're worrying about interpolation between states and about deciding what information to send and when. For example, you're allowing the size of your IDs and your choice of serialization format spill out into discussions of when to send usernames. Programming should be the other way around, as far as possible - you decide what you need to do at an abstract level, and only then decide the low level details, making sure they fit your high level requirements.

Yes I go into too much detail here.

I think I understand the high level requirements. I just want to experiment getting rid of the username first and then try delta encoding further. But then I was bogged down by the implementation because it is tightly related to my protocol design and the 3rd party schema tool I am using. And I am reading related topics here and there which just pop into my head but only to find they are not fit later.

I think I will go with my own way for now. I try to use a dirty field in the message body to tell the client what messages are sent in the update packet but later when I use a more customized protocol I might be able to do delta encoding more easily.

This topic is closed to new replies.

Advertisement