sschmid / Entitas

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

How to use the GameMatcher.XXX.Remove() in ReactiveSystem? #742

Open AemaethMonster opened 6 years ago

AemaethMonster commented 6 years ago
public class FaceRemoveSystem : ReactiveSystem<GameEntity>
{
    public FaceRemoveSystem(Contexts contexts) : base(contexts.game) { }

    protected override ICollector<GameEntity> GetTrigger(IContext<GameEntity> context)
    {
        return context.CreateCollector(GameMatcher.Face.Removed());
    }

    protected override bool Filter(GameEntity entity)
    {
        return !entity.hasFace;
    }

    protected override void Execute(List<GameEntity> entities)
    {
        foreach (var e in entities)
        {
            Object.Destroy(e.face.gameobj);
            e.face.sequence.Kill();
        }
    }
}

My code↑. If Filter() return !entity.hasFace ,then in Execute(), e.face will raise a error. If Filter() return entity.hasFace, then it will never Execute(). Is that impossible to get the component's value, when it is just removed? Need help:)

ghost commented 6 years ago

You can no longer access the component data with a reactive system. The component is already deleted. It is possible if you register for a group event:

public class FaceRemoveSystem  : IInitializeSystem, ITearDownSystem {

    private readonly IGroup<GameEntity> group;

    public FaceRemoveSystem(GameContext game) {
        group = game.GetGroup(GameMatcher.Face);
    }

    public void Initialize() {
        group.OnEntityRemoved += Group_OnEntityRemoved;
    }

    public void TearDown() {
        group.OnEntityRemoved -= Group_OnEntityRemoved;
    }

    private void Group_OnEntityRemoved(IGroup<GameEntity> group, GameEntity entity, int index, IComponent component) {
        Object.Destroy(entity.face.gameobj);
        e.face.sequence.Kill();
    }
}

But be careful. This method is called immediately when the component is removed. This means there is no order and you should avoid such systems as much as possible. I hereby warned you :smile:

AemaethMonster commented 6 years ago

Thank you for the answer. Maybe I should write the same lines after every .RemoveFace(), instead of using the system like this.

Actually I think, when .RemoveComponent(), the component should be really removed at the very end of this frame. So that we can do some last thing before it disappear.

sschmid commented 6 years ago

We solved a similar issue by introducing the DestroyedComponent and deferring the actual destruction of the entity to the end of the frame. You can use the same approach for removed faces.

Once you call e.RemoveXyzComponent() the components is removed immediately. If you still need this data a later point of time, you must not remove it.

If removing the face only happens when you destroy an entity, you can just react do GameMatcher.Destroyed and filter for e.hasFace.

AemaethMonster commented 6 years ago

@sschmid DestroyedComponent is a nice solution that I need. But one problem raises.

What I write in DestroyedComponent.cs

public class DestroyedComponent : IComponent
{
    public string component_name;
}

In reactive system

protected override void Execute(List<IDestroyableEntity> entities)
{
    foreach (var e in entities)
    {
        // now we can access the ViewComponent and the DestroyedComponent
        if (e.destroyed.component_name == "Action" && e.hasAction)
        {
            e.action.timeline_x.Kill();
            e.action.timeline_y.Kill();
            e.action.timeline_agl.Kill();
            e.RemoveAction();
        }
        else if (e.destroyed.component_name == "Face" && e.hasFace)
        {
            sth
            e.RemoveFace();
        }
        else if .....
    }
}

The problem is that: If I remove two different components in a frame, like

e.ReplaceDestroyed("Action"); 
e.ReplaceDestroyed("Face");

, only one component will be removed.

I know why it happened. Because DestroyedSystem update every one frame. But I don't know how to fix it.

VergilGao commented 6 years ago

@Noicon

protected override void Execute(List<IDestroyableEntity> entities)
{
    foreach (var e in entities)
    {
        // now we can access the ViewComponent and the DestroyedComponent
        if (e.destroyed.component_name == "Action" && e.hasAction)
        {
            e.action.timeline_x.Kill();
            e.action.timeline_y.Kill();
            e.action.timeline_agl.Kill();
            e.RemoveAction();
        }

        /* else is not suitable here */ if (e.destroyed.component_name == "Face" && e.hasFace)
        {
            sth
            e.RemoveFace();
        }
        else if .....
    }
}
AemaethMonster commented 6 years ago

I don't think the problem is here... In one trigger of reactive system, e.destroyed.component_name is one-single string, it can't be "Face" and "Action" at the same time.

Actually, all problems in this issue already solve in #755 . By writing my own .Add, .Replace, and .Remove, even doesn't need a react system to release the timeline(sequence). Thank all of you!!

AtheosCode commented 6 years ago
public class DestroyedComponent : IComponent
{
    public List<string> component_name;
}

Is it possible to use list to store component that needs to be deleted?

public class CommandComponent:IComponent
{
    public List<CommandEnum> commandList;
}
public enum CommandEnum
{
    None = 0,
    AddRender,
    RemoveRender,
    Destory,
}
    public static void AddSafeCommand(this GameEntity entity, CommandEnum command)
    {
        var list = new List<CommandEnum>();
        if (entity.hasCommand)
        {
            list = entity.command.commandList;
        }
        list.Add(command);
        entity.ReplaceCommand(list);
    }

I treat all one-time operations in the system as a command, and then process them through the command system, but I don't know if the design is appropriate.

And it raises a new problem. If I disable the command system in editor debugging mode, the list data becomes very large in an instant.

 private readonly Contexts _contexts;
    private readonly IGroup<GameEntity> _commandGroup;
    public CommandSystem(Contexts contexts)
    {
        _contexts = contexts;
        _commandGroup = contexts.game.GetGroup(GameMatcher.Command);
    }

    public void Execute()
    {
        foreach (var entity in _commandGroup.GetEntities())
        {
            foreach (var command in entity.command.commandList)
            {
                switch (command)
                {
                    case CommandEnum.None:
                        break;
                    case CommandEnum.AddRender:
                        if (entity.hasResource)
                        {
                            entity.AddSafeRender(entity.resource.resource);
                        }
                        break;
                    case CommandEnum.RemoveRender:
                        entity.RemoveSafeRender();
                        break;
                    case CommandEnum.Destory:
                        entity.RemoveSafeRender();
                        if (entity.isEnabled)
                        {
                            entity.Destroy();
                        }
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
        }
    }