sschmid / Entitas

Entitas is a super fast Entity Component System (ECS) Framework specifically made for C# and Unity
MIT License
7.14k stars 1.11k forks source link

By using Entitas, is there a proper way to serialize a frame? #849

Open ijovi23 opened 5 years ago

ijovi23 commented 5 years ago

Hi,

As we all know that ECS makes data and behavior separated. So I wanna get a proper way to serialize all entities and their components (excluding rendering components). Given that a component is not supposed to present methods, how can I serialize them? It's useful to save a state and recover it at a later time.

Cheers

zhuchun commented 5 years ago

You can write a flag component like "RecordableComponent" and use JSON.net to serialize instances. However, I'm almost sure that this is not what you want by practice. For cool things like kill cam and rewindable gameplay, it's important to filter out unnecessary data, otherwise, the player's memory/harddisk would be blown up quickly because you're storing data at the 60Hz frequency.

So as you can see, it's not an Entitas related problem. IIRC, Zaks made a talk about how to implement a playback system, you can google that video, it's insightful.

ijovi23 commented 5 years ago

You can write a flag component like "RecordableComponent" and use JSON.net to serialize instances. However, I'm almost sure that this is not what you want by practice. For cool things like kill cam and rewindable gameplay, it's important to filter out unnecessary data, otherwise, the player's memory/harddisk would be blown up quickly because you're storing data at the 60Hz frequency.

So as you can see, it's not an Entitas related problem. IIRC, Zaks made a talk about how to implement a playback system, you can google that video, it's insightful.

Thanks for your reply. It's really not an easy thing to do, I think so too. I found the following demo but, its solution so damn sucks if the systems grow more and more complex. https://github.com/coding2233/Entitas-Replay-Demo

I won't store the state data at every frame. Instead, I can save a state every 30 frames, and save the user-action every frame. What you said 'RecordableComponent' may be not enough, cause when I have collected all Recordable Entities, I still don't know what to record. I need manually organize all components I want to record in a recording-system. Maybe the best way is writing a code generator to automatically generate a component includes all components that have the attribute "Recordable".

Considering server/client communication, it's necessary to do serializing/deserializing. The code may look like

    public RecordSystem(Contexts contexts)
    {
        _recordableEntities = contexts.game.GetGroup(GameMatcher.Recordable);
    }

    public void Execute()
    {
        Dictionary<int, byte[]> serialized = new Dictionary<int, byte[]>();
        foreach (GameEntity e in _recordableEntities.GetEntities())
        {
            serialized[e.EntityId] = e.GetComponents<IRecordableComponent>().SerializeToBytes()
        }

       //do next...
    }

JSON.Net needs more device resources, and bytes serialization such as protobuf breaks ECS rules.

ijovi23 commented 5 years ago

I recorded and serialized the content data of the component. Next I wanna find a way to know what component the data belongs to.


public interface IRecordableComponent {}

[Game]
public class PositionComponent : IComponent, IRecordableComponent
{
    public float x;
    public float y;
}

[Game]
public class RecordableComponent : IComponent {}

public class RecordSystem : IExecuteSystem
{
    readonly GameContext _context;

    public RecordSystem(Contexts contexts)
    {
        _context = contexts.game;
    }

    public void Execute()
    {
        Dictionary<int, IComponent[]> entityData = new Dictionary<int, IComponent[]>();
        var entities = _context.GetGroup(GameMatcher.Recordable);
        foreach (var e in entities)
        {
            var comps = e.GetComponents().Where((c) => c is IRecordableComponent).ToArray();
            entityData[e.creationIndex] = comps;
        }

        if (entityData.Count > 0)
        {
            Debug.Log(JsonConvert.SerializeObject(entityData));
        }
    }
}
zhuchun commented 5 years ago

If you are making a multiplayer game, don't use JSON, it's still too big unless your game is simple enough. We usually use a code generator to create contracts for State, Input and other stuff then serialize them as a dict/hashtable byte[]. Google GafferOnGames, there are lots of essential posts about networking on it and you will see how picky it is to transfer data in a real game. In short, every bit you saved is $$$ :wink:

ijovi23 commented 5 years ago

If you are making a multiplayer game, don't use JSON, it's still too big unless your game is simple enough. We usually use code generator to create contracts for State, Input and other stuff then serialize them as a dict/hashtable byte[]. Google GafferOnGames, there are lots of network essential posts on it and you will see how picky it is to transfer data in a real game. In short, every bit you saved is $$$ 😉

You are so cool!