New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Events aka Reactive-UI #591
Comments
I would say you always want the entity from |
Our team check this way and event handling like this is unprofitable in huge projects, while you can't subscribe for group of components.
Also few features will be useful here:
|
Updated code |
Iteration 1 will contain generated
|
Test results can already be seen here |
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) |
I suggest can call it one-to-one event and one-to-many event. |
Current state: 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 When [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 |
|
Can it use [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 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:
|
Agree, I'm not 100% happy with [Event(bool)]. Suggestions for names? 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? |
yes |
but not sure if that's a good idea as it might encourage modifying the entity |
I'd use entity argument in both Event and EntityEvent, sometimes there are additional components on entities. |
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. MemberData[] memberData = data.GetMemberData();
string newValue1 = memberData.Length == 0 ? "AddedOrRemoved" : "Added"; Do you request separate Added, Removed systems or separate subscription? In case of |
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 |
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. |
I will probably add a check to the filter, so I ensure that it really isGameOver by the time we notify all listeners |
|
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 |
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:
|
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 |
@sschmid I think just keep the
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 |
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. |
@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:
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 |
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 |
@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 |
@FNGgames But |
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 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:
than the simple solution:
In both ways you need to use a |
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. |
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)
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)]
This is the idea, the implementation might be different once I see it in action.
Discussion and feedback is welcome
The text was updated successfully, but these errors were encountered: