Entitas-csharp: Events aka Reactive-UI

Created on 31 Jan 2018  路  76Comments  路  Source: sschmid/Entitas-CSharp

Add new Attributes, Data Providers and Code Generators to generate what we know under the name Reactive UI (as explained in this Unite Talk video https://www.youtube.com/watch?v=Phx7IJ3XUzg)

[Event(false)]
public sealed class GameOverComponent : IComponent {
}

[Event(true)]
public sealed class PositionComponent : IComponent {
    public Vector3 value;
}

I plan to generate 2 different events, one that's independent from entities [Event(false)] and one that's bound to a specific entity [Event(true)]

public interface IGameOverListener {
    void OnGameOver(bool isGameOver);
}

public sealed class GameOverListenerComponent : IComponent {
    public IGameOverListener value;
}

public sealed class GameOverEventSystem : ReactiveSystem<GameEntity> {

    readonly IGroup<GameEntity> _listsners;

    public GameOverEventSystem(Contexts contexts) : base(contexts.game) {
        _listsners = contexts.game.GetGroup(GameMatcher.GameOverListener);
    }

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

    protected override bool Filter(GameEntity entity) {
        return true;
    }

    protected override void Execute(List<GameEntity> entities) {
        foreach (var e in entities) {
            foreach (var listener in _listsners) {
                listener.gameOverListener.value.OnGameOver(e.isGameOver);
            }
        }
    }
}
public interface IPositionListener {
    void OnPosition(Vector3 position);
}

public sealed class PositionListenerComponent : IComponent {
    public IPositionListener value;
}

public sealed class PositionEventSystem : ReactiveSystem<GameEntity> {

    public PositionEventSystem(Contexts contexts) : base(contexts.game) {
    }

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

    protected override bool Filter(GameEntity entity) {
        return entity.hasPosition && entity.hasPositionListener;
    }

    protected override void Execute(List<GameEntity> entities) {
        foreach (var e in entities) {
            e.positionListener.value.OnPosition(e.position.value);
        }
    }
}

This is the idea, the implementation might be different once I see it in action.

Discussion and feedback is welcome

feature request rfc

Most helpful comment

It might be good to change the false/true to something like EventScope.Context/EventScope.Entity to help with readability.

All 76 comments

I would say you always want the entity from Event(true) despite it's unique. So the generator could handle that for you. Furthermore the OnPositionChanged(Vector3 position, GameEntity entity) Method should also be called when the Group of PositionEventComponent is changing so that the registered interface get directly called (as Group Event, not reactive so it won't get called the next tick). Finally a Remove Event would be good.

Our team check this way and event handling like this is unprofitable in huge projects, while you can't subscribe for group of components.
For example, 2 cases:

  • it's ok if you subscribing for PlayerDataComponent, but what if you need update UI only when group of components PlayerDataComponent and PlayerAvatarResourcesComponent added
  • if you need to update online badge near concrete user from 2 users in multiplayer session avatar when group of PlayerDataComponent and PlayerIsOnlineComponent added/removed

Also few features will be useful here:

  • filtering components (for example, subscribe only for StoreItemComponent with Gift in ItemType field)
  • subscribe to adding and removing of components
  • subscribing to components-flags

Updated code

Iteration 1 will contain generated

  • Interfaces
  • Components
  • Systems
  • One EventSystems class that contains all systems ordered by priority

For the attributes i would suggest to name them differently instead of [Event(true)] and [Event(false)]. Currently you would have to know if "false" is an Entity bound event or not instead of just seeing it at a glance. One could read the parameter as "is pure event" or as "is entity event" (which it currently is)
Maybe call them [Event] and [EntityEvent] or something along these lines, to clearly differentiate them.

I suggest can call it one-to-one event and one-to-many event.

Current state:
You can flag components with [Event(true)] or [Event(false)]
[Event(bool bindToEntity)]

E.g.

[Unique, Event(false)]
public sealed class ScoreComponent : IComponent {
     public int value;
}

After generation you can use the IListener

public class GameHUDController : MonoBehaviour, IScoreListener {

    public Text scoreLabel;

    void Start() {
        Contexts.sharedInstance.game.CreateEntity().AddScoreListener(this);
    }

    public void OnScore(int value) {
        scoreLabel.text = value.ToString();
    }
}

The generated GameScoreEventSystem which notifies all IScoreListeners will be added to the generated EventSystems feature automatically.

When [Event(true)] the listener is on the same entity as the component of interest, e.g.

[Event(true)]
public sealed class PositionComponent : IComponent {
    public Vector3 value;
}

public class View : MonoBehaviour, IView, IPositionListener {

    public virtual Vector3 position { get { return transform.localPosition; } set { transform.localPosition = 

    public virtual void Link(IEntity entity, IContext context) {
        gameObject.Link(entity, context);
        var e = (GameEntity)entity;
        e.AddPositionListener(this);
    }

    public virtual void OnPosition(Vector3 value) {
        position = value;
    }
}

I will show this in the Shmup Part 3 video

Forgot to mention:

It's really awesome! :)

What if I want multiple listeners to [Event(true)]?

[Event(true)] is bound to one entity, there can only be one. Try using [Event(false)] instead

Event(true) usage is kind of narrow, will keep using entity.OnComponentAdded

Can it use Action under the hood to allow multiple listeners?
Something like this:

[GameC]
public sealed class HealthCompListenerComponent : Entitas.IComponent {

    public Action<float> value;
}

public partial class GameCEntity {

    public HealthCompListenerComponent healthCompListener { get { return (HealthCompListenerComponent)GetComponent(GameCComponentsLookup.HealthCompListener); } }
    public bool hasHealthCompListener { get { return HasComponent(GameCComponentsLookup.HealthCompListener); } }

    public void AddHealthCompListener(IHealthCompListener listener) {
        if (!hasHealthCompListener) {
            var index = GameCComponentsLookup.HealthCompListener;
            var component = CreateComponent<HealthCompListenerComponent>(index);
            component.value += listener;
            AddComponent(index, component);
        }
        else {
            healthCompListener.value += listener;
            listener.OnHealthComp(healthComp.Value);
        }
    }

    public void RemoveHealthCompListener(IHealthCompListener listener) {
        healthCompListener.value -= listener;
        if (healthCompListener.value == null) {
            RemoveComponent(GameCComponentsLookup.HealthCompListener);
        }
    }
}

Copied existing generators and changed to use Action Gist.
It needs extension method for convenience, didn't write generator for it
csharp public static void ListenHealthComp (this GameCEntity ent, Action<float> action) { if (!ent.hasHealthCompListener) { ent.AddHealthCompListener(null); } ent.healthCompListener.value += action; ent.ReplaceHealthCompListener(ent.healthCompListener.value); }

Here are my thoughts after digging in event generators:

  • Event(false) and Event(true) serve quite different purposes, and having same interface gives drawbacks. For example ent.AddHealthListener(*) doesn't tell if health is entity event or a global event. Or if at some point developer chooses to switch (false) to (true)version there will be no compilation errors, but behaviour will be broken.
  • Event(false) and Event(true) generators are tightly coupled. Wanted to change one of them and had to change other too

Agree, I'm not 100% happy with [Event(bool)]. Suggestions for names?
[Event] and [EntityEvent]?

IXyzEntityListener could pass the entity instead of the value

[Event] and [EntityEvent] sound good

Or One-To-One Event and Many-To-One Event? Btw XyzEntityListener could pass the entity instead of the value? Do u mean passing the whole entity instead of based on the data from component?

@optimisez

Do u mean passing the whole entity instead of the data from component?

yes

but not sure if that's a good idea as it might encourage modifying the entity

IXyzEntityListener could pass the entity instead of the value
but not sure if that's a good decision as it might encourage modifying the entity

I'd use entity argument in both Event and EntityEvent, sometimes there are additional components on entities.

[Event(false)]
public sealed class GameStateOverEventComponent : IComponent { }

public class UiController : MonoBehaviour, IGameStateOverEventListener
{
    ......

    public void OnGameStateOverEvent(bool isGameStateOverEvent) // this paramenter is not needed
    {
        //do something
    }
}

feature request: empty parameter event function

@roygear eventSystem tracks all value changes of Component. When there are class members system reacts to Added trigger, without class members it reacts to AddedOrRemoved trigger.

EventSystemGenerator.cs

      MemberData[] memberData = data.GetMemberData();
      string newValue1 = memberData.Length == 0 ? "AddedOrRemoved" : "Added";

Do you request separate Added, Removed systems or separate subscription?

In case of isGameOver that could be useful, in case like isFrozen that would lead to subscribe twice to the same function, one to display frozen state, another to turn it off. So it's the same problem rotated around

but not sure if that's a good idea as it might encourage modifying the entity

So is that the new feature u plan to work on? One use case I encounter when working on game object pooling is I need to pass the entity as parameter to the interface so I can get the entity at MonoBehaviour side to add event listener component as it's Event(true) event that's bound to a specific entity. Or maybe there is better solution for this problem?

Typo in generated code btw: "_listsners"

I love this feature, my animation controllers are doing a lot of polling right now so I'm looking forward to rewriting them in this way.

Please don't put the EventListenerComponent with the interface on the same entity with [Event(true)] instead on a new one with only listening purpose. It's very handy to have as many listeners on a component as you want by registration. In this way it feels like a real Observer/Listener pattern. Will the current solution call the Listener Method after registration automatically if there is already the component or will it only getting called after a new change?
Would call the [Event(true)] an [LinkEvent] or [LinkedEvent] because it's bounded to the entity.

@c0ffeeartc

I like to trigger a event then the listener do something without using any parameter.

public void OnGameStateOverEvent(){ // do things}

It could be done in previous manually way. Of course, use generator way is better if supported.

@StormRene when callbacks are on different entities, how system would trigger only callbacks subscribed to this entity?
How about keeping multiple callbacks in a component of an entity?

@c0ffeeartc creationIndex is the key for that. You are subscribing to the component with the specific creationIndex of that entity to determine if it's the correct one.

listenerEntity.AddHealthEventListener(targetIndex, this);

The Listener Component should look like this:

public class HealthEventListenerComponent : IComponent {
    [EntityIndex]
    public int targetCreationIndex;
    public IHealthEventListener listener;
}

So the system which calles it could do this:
麓 //Collector in Health <...> protected override void Execute(List<CoreEntity> entities) { foreach (var target in entities) { var listeners = context.GetEntitiesWithHealthEventListener(target.creationIndex); foreach (var listener in listeners) { listener.healthEventListener.listener.HealthChanged(target.health.value); } } } <...>

Additionally it's very very handy to have a system that has his GetTrigger on the HealthEventListener so that when a new one get registered this system checks if there is already a state and calls the new listener immediately. We are using for this directly the group.OnEntityAdded, because otherwise the initial call for the registration would be one tick later when the next entitas update is getting called. If you don't call them on registration you have to check for yourself in the view after you are adding the listener and adjust for the current state only to have the same code one the listener callback again. Not cool.

If we agree on passing entity as argument to Event and EntityEvent, can we use this interface void (Contexts contexts, GameEntity ent) ?

Imo there are two options:

void HealthChanged(int creationIndex, int newHealth);

or

void HealthChanged(GameEntity healthEntity, int newHealth);

Why do you need the Contexts?

I need Contexts to get access to everything 馃槃
For example event entity is only a signal that something happened, then view gathers data through contexts and updates itself.
Contexts and entity are present in eventSystem, why not share them. In case these won't pass to final interface, I'll just use Contexts.sharedInstance, and GetEntityWith* to do the same thing.

Why not getting all the necessary data through the Events? If you need other data on a specific event just register new listeners:

void TookDamageChanged(GameEntity e, int damage) {
    listenerEntity.AddAnotherListener(e.creationIndex, this);
}
void AnotherListenerChange(int newData) {
//go for it.
}

This way you are pure reactive. If you need to collect data from the contexts you should consider to first make a new system that collects the data and then react with a listener on the new component that this system is creating.

While its nice to have the classes generated for you by applying the annotation to the component, wouldn't it make more sense to generate using objects that define more elaborate triggers? It would be much more helpful to generate interfaces around a matcher rather than to create them over a single component.

@beck-daniel thought about that, we definitely need that to express more complex scenarios. I've got sth in my head already.

Many cases when for one entity it is necessary to call simultaneously several changes in the UI.
For example, we have several units, each of them has health. And when it changes, this change needs to be shown in two+ different places(hp bar, card info, stats screen etc).
We use group.OnEntityAdded and creation index id for this.

I changed the interface to include the entity:

public interface IPositionListener {
    void OnPosition(GameEntity entity, UnityEngine.Vector3 value);
}

Problem:
Imagine PositionComponent is tagged with [Core] and [Meta]

I have 2 options:

  1. generate ICorePositionListener and IMetaPositionListener and have a type-safe entity
  2. only generate IPositionListener but with IEntityas a parameter

Nr 1 would look like this

public class View : MonoBehaviour, IGamePositionListener {
    // ...
    e.AddGamePositionListener(this);
   // ...

    public void OnPosition(GameEntity entity, Vector3 value) {
        position = value;
    }
}

Nr 2 would look like this

public class View : MonoBehaviour, IPositionListener {
    // ...
    e.AddPositionListener(this);
   // ...

    public void OnPosition(IEntity entity, Vector3 value) {
        position = value;
    }
}

Any thought?
When IEntity:
Since we get the value we don't always have to cast the entity to e.g. CoreEntity.
We could have 1 OnPosition that can handle both entity types

When typed entity:
More convenient, but slightly uglier interface and component name, because it contains the context name

@sschmid First option seems quite nice as u can know the context. Btw will this implementation introduces more method calls and slow down the performance as method call is not cheap.

Maybe I can try to skip the context name, if I only detect 1 context.

@optimisez no additional method call, just signature change

I see you add entity to the parameter but I think maybe you can make it able to configure attribute at component to make it generate interface that has/without entity as parameter will be even better. Just like how Event(true) and Event(false) work previously that generate code based on attribute configuration. Although I'm not really sure will it affect much performance for 1 parameter vs 2 parameters.

Something like [EntityEvent( hasContextsArg = false, hasEntityArg = enumEntityArg.CreationIndex, hasEntityFieldsArgs = true)] or [EntityEvent( argFlags = eContexts | eEntityCreationIndex | eEntityFields]. And everyone gets what he wants. Sounds like a hell of generator 馃槅

Awesome. Make it generate interface that has no entity parameter by default if there's no eEntityFields. Then maybe [EntityEvent( argFlags = eContexts | eEntityCreationIndex | eEntityFields] is the better way to go.

Changed it to detect if the component only has 1 context and then not prefixing it with the context name. Will pass the entity by default as the sender. For event delegates it is very common to have the sender + args as parameters. The (realdworld) performance is the same.

Changed it to detect if the component only has 1 context and then not prefixing it with the context name.

Sorry to say late, but that's probably not a good decision. After adding context and regenerating it will bring compile errors for every subscriber.
It's great to have it open source to tweak however we like

Changed it to detect if the component only has 1 context and then not prefixing it with the context name.

Ya. @sschmid. Make it prefixing it with the context name even he component only has 1 context will be better as it won't introduce breaking change after changing component to more than 1 context.

@optimisez yes, I'm aware of that. Currently I prefer the shorter api though.

Question:

I'm not quite sure about how to handle all the cases, there are many different use-cases. Example

[Event(false, EventType.Added)]
public sealed class GameOverComponent : IComponent {
}

EventSystems will usually be executed in the view phase after the update systems. The behaviour of an reactive system is: if isGameOver = true, the entity gets collected and the system executes even if we set isGameOver= false in between.
Now what about events? Should IGameOverListener.OnGameOverAdded() be called or not? GameOver happened but we removed it before.

It probably shouldn't and I filter before

1) [Event(false, EventType.Added)] generates IGameOverListener.OnGameOverAdded()
2) [Event(false, EventType.Removed)] generates IGameOverListener.OnGameOverRemoved()
3) [Event(false)] generates IGameOverListener.OnGameOver()

I think make it able to supports all these 3 events as sometimes u just only want to trigger event when isGameOver = true or isGameOver = false. And sometimes u want trigger both true and false.

Currently I have:

Flag Components:

[Event(false, EventType.Added)] generates
IGameOverListener.OnGameOverAdded(GameEntity entity)

[Event(false, EventType.Removed)] generates
IGameOverListener.OnGameOverRemoved(GameEntity entity)

[Event(false, EventType.AddedOrRemoved)] generates
IGameOverListener.OnGameOver(GameEntity entity, bool isGameOver)

Normal Components:

[Event(false, EventType.Added)] generates
IPositionListener.OnPosition(GameEntity entity, Vector3 position)

[Event(false, EventType.Removed)] generates
IPositionListener.OnPositionRemoved(GameEntity entity)

[Event(false, EventType.AddedOrRemoved)] generates
IPositionListener.OnPositionAddedOrRemoved(GameEntity entity, Vector3 position)

I will probably add a check to the filter, so I ensure that it really isGameOver by the time we notify all listeners

[Event(false, EventType.AddedOrRemoved)] should generate
IPositionListener.OnPosition(GameEntity entity, Vector3 position)
and
IPositionListener.OnPositionRemoved(GameEntity entity)
otherwise you have to check if it's add or remove in the method if you want both. Seems to me ugly if I need to check everythime against isEnabled like this:

IPositionListener.OnPositionAddedOrRemoved(GameEntity entity, Vector3 position) {
    if(entity.isEnabled){
    } else {
    }
}

Another issue I encounter is let say I have UI A and UI B. When I close UI A and switch to UI B, I will remove all the UI A listeners. Then when I close UI B and open UI A, the UI A does not refresh all the data when I add all the UI A listeners. I think I need something like [Event(false, EventType.AddedOrRemoved, RefreshListener)] to generate one more system to make it call listener one time when adding the ListenerComponent to the entity.

Listeners should be everytime getting called with the current state when they are added. We first thought it would be good to make a reactive system that triggers on the listener component getting added. We later found out it's not a very good idea because they are getting called delayed and not directly (like one tick after, when entitas is getting updated). Therefore we generate a ReactiveSystem for changing data and a InitializeSystem which calls the listener on group added so they are getting called instantly. It's working very nice. Imo that's one of the rare situations where you really want a group added event.

Any ETA on updated feature?

Will pack a new version.

Fyi, things I won't implement:

  1. calling the listener delegate method when adding the listener
    If needed, call the delegate manually after adding the listener. This is more flexible and work in more scenarios compared to always calling the delegate when added.
  1. Group events
    All Entitas events are reactive and aggregate changes for now. No group events planned.

Please feel free to continue the discussion, I will only close because this issue is related to 1.1.0 which is out.

Made a repository EventsCE alternative to Entitas Events feature. Current step is replicate Events generation into Generated/EventsCE folder. After step is done other features are planned see Readme

@sschmid I think just keep the Event(true) and Event(false) attribute naming as I will use extensively and I dun really want to have breaking change anymore.

Another issue I encounter is let say I have UI A and UI B. When I close UI A and switch to UI B, I will remove all the UI A listeners. Then when I close UI B and open UI A, the UI A does not refresh all the data when I add all the UI A listeners. I think I need something like [Event(false, EventType.AddedOrRemoved, RefreshListener)] to generate one more system to make it call listener one time when adding the ListenerComponent to the entity.

For this problem, do you have any good solution for it? I think another way is to just get the component and replace back the component with the same data to make it call listener. Will it better than generate one more system to make it call listener one time when adding the ListenerComponent to the entity?

Another issue I encounter is let say I have UI A and UI B. When I close UI A and switch to UI B, I will remove all the UI A listeners. Then when I close UI B and open UI A, the UI A does not refresh all the data when I add all the UI A listeners. I think I need something like [Event(false, EventType.AddedOrRemoved, RefreshListener)] to generate one more system to make it call listener one time when adding the ListenerComponent to the entity.

For this problem, do you have any good solution for it? I think another way is to just get the component and replace back the component with the same data to make it call listener. Will it better than generate one more system to make it call listener one time when adding the ListenerComponent to the entity?

when adding listeners there is access to needed entity, call listener directly after adding listeners.

@optimisez I already explained the solution directly under your posted issue.

Listeners should be everytime getting called with the current state when they are added. We first thought it would be good to make a reactive system that triggers on the listener component getting added. We later found out it's not a very good idea because they are getting called delayed and not directly (like one tick after, when entitas is getting updated). Therefore we generate a ReactiveSystem for changing data and a InitializeSystem which calls the listener on group added so they are getting called instantly. It's working very nice. Imo that's one of the rare situations where you really want a group added event.

@StormRene If I understand correctly ur solution is using InitializeSystem that only called one time at initialization phase and will not call anymore later. My use case is I will need to keep switching UI back and forth. So, it will keep adding and removing the same listener and each time when I add back the listener, I will need to refresh the listener to make sure the UI always display the latest data.

Nope that's not what I said. From the beginnig of this discussion I'm saying it's important that listener are getting called directly after the registering. All the time. In this text I proposed a problem we found while developing our reactive ui system. Our first solution was write a reactive system with a collector on the specific listener so it can inform the listener. We found out it's not a good solution, because the listener will not be informed instantly but somewhere later (when the next tick is happening in entitas). This leads towards that we wrote all the time in our register listener methods that we also first grab all the data to initialize the object and getting informed with the same data one tick later. (duplicated code) So we corrected the whole thing and made a Initialize System per listener with directly onEntityAdded on the group. Like so:

public void Initialize() {
    this.listenerGroup = uiContext.GetGroup(UIMatcher.HealthStateListener);
    this.listenerGroup.OnEntityAdded += Execute;
}
private void Execute(Entitas.IGroup<UIEntity> group, UIEntity listener, int index, Entitas.IComponent component) {
    var target = coreContext.GetEntityWithId(listener.healthStateListener.targetEntityId);
    if(target) listener.healthStateListener.listener.HealthChanged(target.health.value);
}

So it will be instantly called on register and grab all the data if exists. Otherwise it will maybe getting informed if it will exist in the future by the solution I proposed earlier in this discussion.

Our listeners are getting added with OnEnable and destroyed with OnDisable.

In this text I proposed a problem we found while developing our reactive ui system. Our first solution was write a reactive system with a collector on the specific listener so it can inform the listener. We found out it's not a good solution, because the listener will not be informed instantly but somewhere later (when the next tick is happening in entitas).

Weird. If I understand correctly, @mzaks says before that reactive system will aggregate and execute everything it needs to execute in one shot. Meaning that it will be done in one frame. Can you share how you do it with reactive system previously?

@optimisez Yes that's true it will execute in one shot. But only when your MonoBehaviour.Update / FixedUpdate where your EventsFeature.Execute is getting called. If you instantiate a bunch of enabled GameObjects and they register itself in the OnEnable on some data they will be not showing the correct data between their particular OnEnable and the next Update call, because the listener is not already called. So with a little bad luck you are showing for a fraction of a second a menu without correct state. We started to write all the time many queries to set the data instantly with the reactive approach only to get the correct data later. It lead us to many redundant code so we said it would be nice if the state is already correctly set when the scope of OnEnable ends. group.onEntityAdded was the solution for that.

@StormRene this is why i dont use OnEnable or any other unity message. Instead have an initialisation method as part of an interface on my monobehaviour that gets called by the system instantiating the gameobject. Using the unity messages you're giving up control over execution time.

If you use gameObject.Link() anywhere in your code, then you should be able to call your intialisation methods in the same place.

@FNGgames But OnEnable an OnDisable is very handy. When your designers are working on particular views they can drop your listener scripts on their GameObject and connect the data to show it. Otherweise you always need a coder when a designer just want to add a new healthbar somewhere, because you need to write a system/some code, or am I getting you wrong?

I'm just suggesting that the code from OnEnable be moved to another method that's part of an interface e.g. IViewController.InitializeView(); That way, right after you call Instantiate() to create the gameobject, you can do foreach (var view in GetComponents<IViewController>) view.InitializeView();

So your listener scripts just have to implement that interface and you change the name of OnEnable() to InitView() or whatever you want. That way they are always initialized at a controlled, defined time, where you know exactly the state of every relevant object / entity. I also do the same thing with Destroy() / Disable() - there are equivalent methods in my view interface.

And how do you solve the problem when a GameObject is not instantiated by the Controller/a Coder? E.g. a ParticleSystem that needs additional data and the designer just dropped it in the scene.

If they're there on scene load perhaps an initial FindObjectsOfType?

No thats not a good idea at all. You want to iterate over all GameObjects and their childs just to call it yourself. That would be insane for a big scene.

Ok, how about using onEnable to create sth like RequestInitializationComponent and have a system take care of it?

I'm not as in-tune with the designer / programmer separation since I'm doing both for my team :)

Would it not be the same problem, but in a obfuscated way? You would rather like to see:

void OnEnable() {
   Contexts.SharedInstance.view.CreateEntity().AddRequestInitialization(this);
}
void InitializationAnswer() {
   Contexts.SharedInstance.view.CreateEntity().AddHealthListener(idToListen, this);
}
void IHealthListener.HealthChanged(int newHealth) {
   //stuff
}

than the simple solution:

void OnEnable() {
   Contexts.SharedInstance.view.CreateEntity().AddHealthListener(idToListen, this);
}
void IHealthListener.HealthChanged(int newHealth) {
   //stuff
}

In both ways you need to use a group.onEntityAdded if you want direct response and not waiting for the next Execute call of your event/initrequest features.

Forgive me if I'm misunderstanding. I think we have somewhat different architectures that make what I'm suggesting really easy for me, but really awkward for you. I'm allergic to using scenes for level design - i can't deal with the Russian roulette 'who gets initialized first' thing, so i initialize everything with code. I guess ignore me for now, I'm probably not helping! 馃槃

@FNGgames Haha I know the probem. But if you are working in a team with designers and artists you can't control everything they do - and you shouldn't. They should be creative and life their designing dreams, otherwise you are doing something wrong and don't offer the correct tools. It's also really really annoying if a designer has to come to you every 5 mins, because they want to add something. In the end they are stopping to disturb you and don't make cool stuff. But I agree with you, when you are a team only of coders or techy designer this approach could work although I thinking it's a bit overengineered for just creating view. Mostly their should be no interference if object a is created after object b or the other way around, because the listener are reactive. The relevant code is in entitas.

Sure, that's somewhat outside of my experience - my "team" is an artist and me - he doesn't even touch unity! So yeah probably shouldn't follow my advice here, I'll stfu now 馃槃

It might be good to change the false/true to something like EventScope.Context/EventScope.Entity to help with readability.

Was this page helpful?
0 / 5 - 0 ratings