{"action":"created","issue":{"url":"https://api.github.com/repos/sschmid/Entitas-CSharp/issues/211","repository_url":"https://api.github.com/repos/sschmid/Entitas-CSharp","labels_url":"https://api.github.com/repos/sschmid/Entitas-CSharp/issues/211/labels{/name}","comments_url":"https://api.github.com/repos/sschmid/Entitas-CSharp/issues/211/comments","events_url":"https://api.github.com/repos/sschmid/Entitas-CSharp/issues/211/events","html_url":"https://github.com/sschmid/Entitas-CSharp/issues/211","id":184740012,"number":211,"title":"Event-Listeners Systems","user":{"login":"aqaddoumi","id":22945563,"avatar_url":"https://avatars.githubusercontent.com/u/22945563?v=3","gravatar_id":"","url":"https://api.github.com/users/aqaddoumi","html_url":"https://github.com/aqaddoumi","followers_url":"https://api.github.com/users/aqaddoumi/followers","following_url":"https://api.github.com/users/aqaddoumi/following{/other_user}","gists_url":"https://api.github.com/users/aqaddoumi/gists{/gist_id}","starred_url":"https://api.github.com/users/aqaddoumi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aqaddoumi/subscriptions","organizations_url":"https://api.github.com/users/aqaddoumi/orgs","repos_url":"https://api.github.com/users/aqaddoumi/repos","events_url":"https://api.github.com/users/aqaddoumi/events{/privacy}","received_events_url":"https://api.github.com/users/aqaddoumi/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":3,"created_at":"2016-10-24T03:10:57Z","updated_at":"2016-10-24T14:09:21Z","closed_at":null,"body":"I'm starting a new project and would like to fix some systems I've used for Clowny Rain, my previous project. One of the major pain points was the event-listener system I've built. I would like to explain how it works and see if we can make it better or maybe replace it all together but provide the same functionality.\r\n\r\nThis is a long question so bear with me but I think it's a very beneficial system to have in Entitas. It helped me solve a lot my UI problems and I think it can be done more elegantly.\r\n\r\nThe scenario:\r\n- You have two buttons one for options menu and one for shop menu.\r\n- You press the options button, an OptionsTrigger event is triggered, the two buttons move outside the screen and the options menu appears.\r\n- You press the shop button, a ShopTrigger event is triggered and the two buttons move outside the screen and the Shop menu appears\r\n- Imagine you have dozens of these events and a lot more stuff happening for each event.\r\n\r\nWhat I'm trying to do is create an Enum and assign it to a component, so that when it's added to the pool, some entities which can listen to this event and be retrieved in a system and make each entity do something different.\r\n\r\nHere's how I approached it.\r\n\r\nComponents:\r\n(1) ListenersDictionaryComponent:\r\nA SingleEntity component which is dictionary of dictionaries that can be accessed from anywhere.\r\n```C#\r\n[SingleEntity]\r\npublic class ListenersDictionaryComponent : IComponent {\r\n\r\n\tpublic Dictionary <TriggerEnum, Dictionary <string, Entity>> ListenersDictionary =\r\n\tnew Dictionary <TriggerEnum, Dictionary<string, Entity>>();\r\n}\r\n```\r\n\r\n(2) ListenToComponent:\r\nAn array added to the entity which has all the triggers the entity wants to listen to.\r\n```C#\r\npublic class ListenToComponent : IComponent {\r\n\r\n\tpublic TriggerEnum[] listenTo;\r\n}\r\n```\r\n\r\n(3) TriggerComponent:\r\nAn enum that's added to a created entity choose the right dictionary from the parent dictionary.\r\n```C#\r\npublic class TriggerComponent : IComponent {\r\n\r\n\tpublic TriggerEnum trigger;\r\n}\r\n```\r\n\r\n(4) ActiveListenersComponent:\r\nA listeners dictionary chosen from the parent dictionary and added to an entity to trigger different systems.\r\n```C#\r\npublic class ActiveListenersComponent : IComponent {\r\n\r\n\tpublic TriggerEnum triggerKey;\r\n\tpublic Dictionary<string, Entity> activeListeners = new Dictionary<string, Entity>();\r\n}\r\n```\r\n\r\nThe Systems:\r\n(1) CreateListenersDictionarySystem:\r\nThis Creates a global single entity with the ListenersDictionary component which is a dictionary of dictionaries.\r\n```C#\r\npublic class CreateListenersDictionarySystem : IInitializeSystem, ISetPool {\r\n\r\n\tpublic void Initialize () {\r\n\t\tDictionary <TriggerEnum, Dictionary <string, Entity>> ListenersDictionary =\r\n\t\tnew Dictionary <TriggerEnum, Dictionary<string, Entity>>();\r\n\r\n\t\t_pool.CreateEntity()\r\n\t\t\t.AddListenersDictionary(ListenersDictionary);\r\n\t}\r\n}\r\n```\r\n\r\n(2) AddListenersToDictionarySystem:\r\nThis system loops through each entity with the ListenToComponent array and creates a child dictionary if the TriggerKey is not found in the parent dictionary and then add the entity to the each child dictionary.\r\n```C#\r\npublic class AddListenersToDictionarySystem : IReactiveSystem, ISetPool {\r\n\r\n\tpublic TriggerOnEvent trigger { get { return Matcher.AllOf(Matcher.ListenTo).OnEntityAdded(); } }\r\n\r\n\tpublic void Execute (List<Entity> entities) {\r\n\r\n\t\tforeach (var e in entities) {\r\n\r\n\t\t\tfor (int i = 0; i < e.listenTo.listenTo.Length; i++) {\r\n\r\n\t\t\t\tif (!_pool.listenersDictionary.ListenersDictionary.ContainsKey(e.listenTo.listenTo[i])) {\r\n\r\n\t\t\t\t\tDictionary <string, Entity> dictionary = new Dictionary<string, Entity>();\r\n\t\t\t\t\t_pool.listenersDictionary.ListenersDictionary.Add(e.listenTo.listenTo[i], dictionary);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tDictionary <string, Entity> listener = _pool.listenersDictionary.ListenersDictionary[e.listenTo.listenTo[i]];\r\n\t\t\t\tlistener.Add(e.name.name, e);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n```\r\n\r\n(3) TriggerActiveListenersSystem:\r\nThis system is triggered when a TriggerComponent is added. Then it looks through the Parent dictionary for the key and chooses the right listeners dictionary and adds it into the ActiveListeners component of a new entity.\r\n```C#\r\npublic class TriggerActiveListenersSystem : IReactiveSystem, ISetPool\r\n{\r\n\tpublic TriggerOnEvent trigger { get { return Matcher.AllOf(Matcher.Trigger).OnEntityAdded(); } }\r\n\r\n\tpublic void Execute (List<Entity> entities) {\r\n\r\n\t\tforeach (var e in entities) {\r\n\r\n\t\t\tDictionary <string, Entity> activeDic =\r\n\t\t\t\t_pool.listenersDictionary.ListenersDictionary[e.trigger.trigger];\r\n\r\n\t\t\t_pool.CreateEntity()\r\n\t\t\t\t.AddActiveListeners(e.trigger.trigger, activeDic)\r\n\t\t\t\t.IsDestroy(true);\r\n\t\t}\r\n\t}\r\n}\r\n```\r\n\r\n(4) TriggerOptionsSystem, TriggerShopSystem, etc\r\nI have dozens of systems like this one and they are triggered when the ActiveListeners is added and if it has the same TriggerEnum they want, the code runs.\r\n```C#\r\npublic class TriggerOptionsUiSystem : IReactiveSystem \r\n{\r\n\tpublic TriggerOnEvent trigger { get { return Matcher.AllOf(Matcher.ActiveListeners, Matcher.Destroy).OnEntityAdded(); } }\r\n\r\n\tpublic void Execute (List<Entity> entities) {\r\n\r\n\t\tforeach (var e in entities) {\r\n\r\n\t\t\tif (e.hasActiveListeners) {\r\n\r\n\t\t\t\tif (e.activeListeners.triggerKey == TriggerEnum.OptionsButton) {\r\n\r\n\t\t\t\t\tEntity optionsPanel = e.activeListeners.activeListeners[DataNames.OptionsPanelName];\r\n\t\t\t\t\toptionsPanel.ReplaceExampleComponent(1)\r\n\r\n\t\t\t\t\tEntity shopButton = e.activeListeners.activeListeners[DataName.ShopButtonName];\r\n\t\t\t\t\tshopButton.ReplaceExampleComponenet(2)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n```\r\n\r\nThese systems work fine but I think there is better way to approach this than I did.\r\nPros:\r\n- I don't have to create a new component for each event firing.\r\n- It provides a structured way to trigger all events.\r\n- I can access entities directly to make each one listening do something different.\r\n- I think if I did each behaviour in a different system I would end up with even more systems and components.\r\n\r\nCons:\r\n- All system like (4) TriggerOptionsSystem, TriggerShopSystem, etc. fire when an ActiveListener is created, but I only need one so I don't run them through an if statement.\r\n- You have a huge dictionary of dictionary which can be accessed globally.\r\n- Entities are referenced by strings in their name component which I don't know if it's a good idea.\r\n- Any new trigger has to be added to TriggerEnum file.\r\n- Sometimes you forget to add TriggerEmum in each entity ListenTo Component.\r\n\r\nThanks you for reading this far :)\r\n\r\nEntitas is great and very straightforward for game logic but I'm still looking for a better approach for the UI. I'm just looking for ideas to make it better and maybe even a totally different way but still achieve the same functionality.\r\n\r\nSo my questions really is, is this a good way to handle UI events? How can I make it better? or is there a better way that I'm not aware of?\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"},"comment":{"url":"https://api.github.com/repos/sschmid/Entitas-CSharp/issues/comments/255750974","html_url":"https://github.com/sschmid/Entitas-CSharp/issues/211#issuecomment-255750974","issue_url":"https://api.github.com/repos/sschmid/Entitas-CSharp/issues/211","id":255750974,"user":{"login":"aqaddoumi","id":22945563,"avatar_url":"https://avatars.githubusercontent.com/u/22945563?v=3","gravatar_id":"","url":"https://api.github.com/users/aqaddoumi","html_url":"https://github.com/aqaddoumi","followers_url":"https://api.github.com/users/aqaddoumi/followers","following_url":"https://api.github.com/users/aqaddoumi/following{/other_user}","gists_url":"https://api.github.com/users/aqaddoumi/gists{/gist_id}","starred_url":"https://api.github.com/users/aqaddoumi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aqaddoumi/subscriptions","organizations_url":"https://api.github.com/users/aqaddoumi/orgs","repos_url":"https://api.github.com/users/aqaddoumi/repos","events_url":"https://api.github.com/users/aqaddoumi/events{/privacy}","received_events_url":"https://api.github.com/users/aqaddoumi/received_events","type":"User","site_admin":false},"created_at":"2016-10-24T14:09:21Z","updated_at":"2016-10-24T14:09:21Z","body":"So the UI elements behaviour would be in the UI manager and not in Entitas systems?"}}
{"actor":{"display_login":"aqaddoumi"}}