Advertisement

Organizing Data for Network Transfer in an ECS

Started by February 18, 2021 10:23 PM
4 comments, last by lurgypai 3 years, 9 months ago

Hello! I'm new here, posting to get some feedback/exposure

So I've been working in my game for a while know, and I've got some ideas one what I want to do with the underlying libraries moving forward. I want to attempt to make something I can use to rapidly prototype online multiplayer games.

One of the biggest aspects of this is managing the states of the entities in the world. The first step towards this is creating a uniform tool to create and manage states for entities. I'm working inside an ECS I designed, and I was thinking about making a "DataComponent" The purpose would be to hold all of the state data of an entity so that it can be easily serialized/unserialized for network transfer, and so that new fields can be added/removed so that I can easily change the makeup of an entity. This would be accomplished using a map, mapping tags of some kind to underlying data, and providing tools for retrieving that data in the correct form.

I'm curious as to concerns about this approach, if it seems naive, or what roadblocks you think might come up in the near future. Personally, I'm immediately concerned about violating access limitations, since all of the data synced across multiple components/data that needs to be synced across the network would be held in the data component, any component would have access to the data from any other component.

If you'd like to look at the code base or maybe try out what I have so far, feel free to check out the github
https://github.com/Lurgypai/Stabby

Thanks!

None

For a small game it could work, with caveats. For a large game it would break down quickly.

The caveats are that you're going to want to control what you update, when, and why. It needs to be at places that make sense so it doesn't overwhelm the network with useless data, needs to be only the data you actually need to update, and needs to happen at reasonable times.

I don't see the DataComponent in the GitHub repository you linked so I can't know for certain. If you're sending the component's data every time Update is called, you're going to cause all kinds of problems with network saturation. If you're sending data the other side isn't interested in it's a waste.

Most engines use a mix of interest management, staleness, and communications events. Only limited items are marked as network synchronized. Stuff that is very interesting, such as items near the player and in view, are ranked with high interest, things that are far away, not visible, are low interest. Changes are monitored and clients know how stale their data is; the more desynchronized the higher it's priority. All of them combine for a score on how much to update; a monster up in your face is going to be spewing network traffic, something far away may not get updates for ages. Other systems are event driven rather than synchronization driven, you only need to specify that something started moving with it's direction and speed, not update it's position every frame. Finally, there are events that must get through, often that need to go immediately, unrelated to synchronization.

It is good that you are thinking about it, many games don't apply critical thought to networking code until far too late in the cycle. Know that any communication can fail at any time for reasons entirely outside your visibility. Also know that you cannot overcome the speed of light/electricity, every byte you send takes time, often many graphics frames. Keep both in mind, and many problems will go away.

Advertisement

Thanks for the feedback!

The DataComponent is a theoretical idea for organization, that has not been designed or implemented.

The network code is a bit more complex than just updating all the data. The data component would just help with organization/serialization.
As it is,
The client uses player input to predict the current local client state
Changes in input are sent to the server whenever the input changes
the server uses the input to generate a new state server side.
Following the way Quake 3 does it, the server then sends the changes in state to each client based on the last acknowledged state from the client.
The client checks if the current state matches the theoretical state, and updates accordingly.

All of this needs to be cleaned up and refined. The purpose of the data component was simply to provide collection for the state to be in, not to manage the more intelligent sending of information. I'm worried about breakdowns in access speed/managing “large” data components.

None

If you see a single component in an ECS design growing and growing over every other component, this is a sign of bad implementation of the ECS in my opinion. ECS for me means, “add as few as you need and as much as necessary” so question: Why do you need to keep track of a bunch of “states” on every entity, what important data do you track that aren't part of the usual game flow?

Thinking of a good server design, why is it important to send every entitie's states together as a block, isn't it enougth to update some states at a time and keep the remaining “as is”? If you don't need it, don't send it. The server architecture should anyways be designed to handle multiple messages asynchronously at the same time so it would be more efficient to send rather small messages that are processed in parallel instead of a single big one which might also fail and cause a huge stall in network transition (TCP)

Thanks so much for the feedback!

The basic purpose of the data component is just to hold the shared variables between components, and make it easy to handle serialization and flagging things that need network transfer.
Due to some old design decisions, the player has a “state component” which provides a single point to get all of the state information about a player (for instance, position is held in one component, while physics info like velocity is held in the collider component. To access the up to date and accurate data from these, you go through the player state component, where they're all listed together.)
As I needed to add new fields that needed to be synchronized on line, and the netcode became more complex, adding new fields became more complex and thus more error prone. So the idea was to make a component that would allow easy addition/removal of variables.

As far as sending states, the server does only send changes in information, but it does send them in blocks (at least for player states, other packets are separate obviously). Also, the game uses ENet, which uses UDP. I was reccomended that I send the player states in batches a while back as opposed to sending them individually. Is it better to send the changes individually when necessary? I wanted to ideally make it so that the server would send a new “world state” to the client that would consist of all of the necessary changes in data, and then the client could re-synchronize to this state if it needed to. Adding a bunch of smaller states that are kept track of individually would increase the complexity of maintaining the state I'd think.

Perhaps I should just start writing it so I can see how all of this works out, and just bail on the github branch if it fails.

None

This topic is closed to new replies.

Advertisement