{"action":"created","issue":{"url":"https://api.github.com/repos/junkdog/artemis-odb/issues/159","labels_url":"https://api.github.com/repos/junkdog/artemis-odb/issues/159/labels{/name}","comments_url":"https://api.github.com/repos/junkdog/artemis-odb/issues/159/comments","events_url":"https://api.github.com/repos/junkdog/artemis-odb/issues/159/events","html_url":"https://github.com/junkdog/artemis-odb/issues/159","id":43307919,"number":159,"title":"Separate systems, processing strategies and subscription lists","user":{"login":"DaanVanYperen","id":6305602,"avatar_url":"https://avatars.githubusercontent.com/u/6305602?v=3","gravatar_id":"","url":"https://api.github.com/users/DaanVanYperen","html_url":"https://github.com/DaanVanYperen","followers_url":"https://api.github.com/users/DaanVanYperen/followers","following_url":"https://api.github.com/users/DaanVanYperen/following{/other_user}","gists_url":"https://api.github.com/users/DaanVanYperen/gists{/gist_id}","starred_url":"https://api.github.com/users/DaanVanYperen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DaanVanYperen/subscriptions","organizations_url":"https://api.github.com/users/DaanVanYperen/orgs","repos_url":"https://api.github.com/users/DaanVanYperen/repos","events_url":"https://api.github.com/users/DaanVanYperen/events{/privacy}","received_events_url":"https://api.github.com/users/DaanVanYperen/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/junkdog/artemis-odb/labels/enhancement","name":"enhancement","color":"84b6eb"}],"state":"closed","locked":false,"assignee":null,"milestone":{"url":"https://api.github.com/repos/junkdog/artemis-odb/milestones/4","html_url":"https://github.com/junkdog/artemis-odb/milestones/some%20later%20release","labels_url":"https://api.github.com/repos/junkdog/artemis-odb/milestones/4/labels","id":595504,"number":4,"title":"some later release","description":null,"creator":{"login":"junkdog","id":804039,"avatar_url":"https://avatars.githubusercontent.com/u/804039?v=3","gravatar_id":"","url":"https://api.github.com/users/junkdog","html_url":"https://github.com/junkdog","followers_url":"https://api.github.com/users/junkdog/followers","following_url":"https://api.github.com/users/junkdog/following{/other_user}","gists_url":"https://api.github.com/users/junkdog/gists{/gist_id}","starred_url":"https://api.github.com/users/junkdog/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/junkdog/subscriptions","organizations_url":"https://api.github.com/users/junkdog/orgs","repos_url":"https://api.github.com/users/junkdog/repos","events_url":"https://api.github.com/users/junkdog/events{/privacy}","received_events_url":"https://api.github.com/users/junkdog/received_events","type":"User","site_admin":false},"open_issues":5,"closed_issues":4,"state":"open","created_at":"2014-03-12T21:55:08Z","updated_at":"2015-06-14T13:33:16Z","due_on":null,"closed_at":null},"comments":2,"created_at":"2014-09-20T11:08:18Z","updated_at":"2015-06-14T13:33:16Z","closed_at":"2015-06-14T13:33:16Z","body":"Moved from #149. Cleaned up a bit.\r\n\r\nYou've seen the matrices for my games; systems discrete to a fault, and everything done inside the ECS. Right now there's a bit of a functionality gap between the simplest games, and having several full fledged specialized external systems hooked into Artemis.\r\n\r\nThings like [Deferred System](https://github.com/DaanVanYperen/artemis-odb-contrib/wiki/Deferred-Systems) fill this gap, but it's a bit of a hack.\r\n\r\nI  see some possibility of breaking things up and filling this gap without breaking things for casual users. Just thinking out loud though, I'm mainly interested in your thoughts about this.  Code examples are merely illustrative.\r\n\r\n## Breaking up systems\r\n\r\n### Before breakup\r\n\r\nSo at its core EntitySystems are a discrete unit of work to keep things conceptually clean, a unary subscription list, and an iterative process, all in the same class. (sidestepping any performance benefits that I do not understand).\r\n\r\nIntervalEntityProcessingSystem, DelayedEntityProcessingSystem mainly provide an iteration strategy Odb-contrib's [Deferred Systems](https://github.com/DaanVanYperen/artemis-odb-contrib/wiki/Deferred-Systems) is an extension of this, where the iteration strategy covers multiple systems.\r\n\r\nVoidEntitySystems and Managers  take care of usecases where the unary relationship between Systems and a subscription list just doesn't make sense.\r\n\r\nVoidEntitySystem is treated as an EntitySystem, while it isn't really. it's just a generic system that needs to be fired somewhere alongside entity systems.\r\n\r\nManagers area mainly glorified event listeners, providing some utility along the way..\r\n\r\nThere's also a few use cases that do not fit in the provide System, for example a collision system where you need to offset multiple groups of entities against each other. The current unary subscription list is not much use here. I'd want to fetch multiple aspects, filtered by Aspect, and throw them at eachother.\r\n\r\n### After breakup\r\n\r\nSo how to translate all this into something flexible under the hood that still allows for the same experience above the sheets.\r\n\r\nOne thing would be pulling Aspect entity lists out of systems and into a cache to freely (or on subscription basis) pull sets out of. Preferably with support for groups and tags. So underwater the entities would track family (WINK Ashley) membership instead of system membership. Systems would then underwater be subscribed to the N families they are interested in. \r\n\r\nSecond could be stripping out the iteration strategy from EntitySystems, and treating them more as a discrete operation. This opens up the ability to reuse and nest iteration strategies, and to process systems more like a pipeline if needed. This'll also allow us to provide Nullary, Unary and Binary variants of systems.\r\n\r\nAll this could make the process methods an interface implementation as well, not sure if that matters for multimorphingpowerrangers.\r\n\r\n## Implementation details\r\n\r\nSome hoops here, mainly required so we can define systems in a single class.\r\n\r\n### Refactoring system superclasses\r\n\r\nSo on most legacy classes this could be totally invisible. The only exception is a direct subclass of EntitySystem, where people would need to now implement using our new toolkit. Makes most sense anyway, since this is usually done to introduce new  processing strategies.\r\n\r\n### Examples\r\n\r\n```java\r\npublic abstract class EntityProcessingSystem implements UnarySystem, EntityObserver { \r\n{\r\n            public EntityProcessingSystem( Aspect aspect ) { .. } \r\n            ProcessingStrategy getStrategy() { return new UnaryIterator(aspect); } \r\n            protected void process ( Entity a ) {}\r\n            protected void added ( Entity a ) {}\r\n            etc\r\n}\r\n\r\npublic abstract class VoidEntitySystem implements NullarySystem { \r\n{\r\n            protected void process () {} \r\n}\r\n\r\ninterface NullarySystem extends System { .. }\r\ninterface UnarySystem extends System { .. }\r\ninterface BinarySystem extends System { .. }\r\n```\r\n\r\n`UnarySystem` would provide the process method, `EntityObserver` all the listening methods. Upon system registration we'd do some magic to make sure our entity manager registers with all the aspects used in the iterator and the processing strategy. The ProcessingStrategy is responsible for calling the system.\r\n\r\nSo now say you'd want to use the new toys to make a superclass to deal with collisions:\r\n\r\n```java\r\n@Wire\r\npublic abstract class CollisionEntitySystem implements BinarySystem {\r\n            public CollisionEntitySystem( Aspect a, Aspect b )\r\n            ProcessingStrategy getStrategy() { new BinaryComparitiveIterator(a,b) } \r\n}\r\n```\r\n\r\nor what about deferred calls or subsystems that share a strategy, Just brew up a strategy with a common backing object. \r\n\r\n```java\r\npublic abstract class RenderSubSystem implements UnarySystem {\r\n          public RenderSubSystem( ProcessingStrategy masterRenderStrategy )  { .. }\r\n            ProcessingStrategy getStrategy() { return new SubStrategy(masterStrategy, aspect); } \r\n            protected abstract process( Entity a, Entity b) { .. }\r\n}\r\n```\r\n\r\nor maybe we want to combine both the UnaryIterator, sorted, with a cooldown?\r\n\r\n```java\r\npublic SuperSortedCooldownKermitTheFrogSystem implements EntityProcessingSystem { \r\n{\r\n            public EntitySystem( Aspect aspect ) { .. } \r\n            ProcessingStrategy getStrategy() { return new CooldownStrategy(new SortStrategy(super.getStrategy(), .. )); } \r\n             ..\r\n}\r\n```\r\n\r\nJust since I haven't driveled enough crap, small part of the puzzle,\r\n\r\n### ProcessingStrategy\r\n\r\n```java\r\ninterface ProcessingStrategy implements EntityObserver {\r\n           /** Process entities */\r\n           void process(); \r\n           /** Register system relationship with this strategy. */\r\n           void setSystem( System system );\r\n           /** Strategy will register all subscriptions with the family manager */\r\n           void initialize();\r\n}\r\n```\r\n\r\n#### Initialization\r\n\r\n`World.initialize()` would fetch all strategies from all systems, register the system with the strategy, then initialize the strategies to subscribe all needed families with the family manager for events, and to make sure the family manager is aware what families to track.\r\n\r\nAfter all this systems are initialized. ProcessingStrategy receive family events and pass them on to their registered system.  Or you could just register the systems directly for events with the family manager, if that saves cycles.\r\n\r\n### `World#process`\r\n\r\nBasically, step through each system, fetch the ProcessingStrategy, and process() it. Which calls the system. Since it matches the arity it'll know how to step through the system without needing reflection.\r\n\r\nPassive systems we can give a null strategy, or a PassiveStrategy(). ;)\r\n\r\nOverarching strategies are a bit more complex, but that'll come down to signalling a new processing cycle to the strategies and having only the first one of a set fire based on a shared backing object.\r\n\r\nwell of course a raw idea, but you get the picture.  \r\n\r\n## Pitfalls\r\n\r\n### Fragmented operations\r\n\r\nPotential pitfall in all this is causing people to fragment the operation itself. For some strategies it makes sense externalizing them, as it's more what and when you process than part of the operation itself. When you talk about highly optimized collision detection, it comes pretty close to the operation to be part of the system, externalizing it into a strategy seems less appropriate. We should provide for this usecase.\r\n\r\n### Binary operations, O(N^2)\r\n\r\n> Junkdog: Some things look like a possible O(N^2) scenario though, but specialized strategies could possibly circumvent it, achieving O(log(N)) or something.\r\n\r\nIn practice the binary operations will typically be asymmetrical enough to perform, say all Enemies versus all Friendlies (players). Though it certainly wouldn't compete with say a box2d system.\r\n\r\n### Afterthoughts\r\n- discrete operations might make systems easier to put in a testing harness?\r\n- cover singleton entity systems (example: map metadata entity)\r\n- example cases of Binary systems\r\n  - Collision.\r\n  - Ripple entities that interact with LiquidVolume.."},"comment":{"url":"https://api.github.com/repos/junkdog/artemis-odb/issues/comments/111827318","html_url":"https://github.com/junkdog/artemis-odb/issues/159#issuecomment-111827318","issue_url":"https://api.github.com/repos/junkdog/artemis-odb/issues/159","id":111827318,"user":{"login":"DaanVanYperen","id":6305602,"avatar_url":"https://avatars.githubusercontent.com/u/6305602?v=3","gravatar_id":"","url":"https://api.github.com/users/DaanVanYperen","html_url":"https://github.com/DaanVanYperen","followers_url":"https://api.github.com/users/DaanVanYperen/followers","following_url":"https://api.github.com/users/DaanVanYperen/following{/other_user}","gists_url":"https://api.github.com/users/DaanVanYperen/gists{/gist_id}","starred_url":"https://api.github.com/users/DaanVanYperen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DaanVanYperen/subscriptions","organizations_url":"https://api.github.com/users/DaanVanYperen/orgs","repos_url":"https://api.github.com/users/DaanVanYperen/repos","events_url":"https://api.github.com/users/DaanVanYperen/events{/privacy}","received_events_url":"https://api.github.com/users/DaanVanYperen/received_events","type":"User","site_admin":false},"created_at":"2015-06-14T13:33:16Z","updated_at":"2015-06-14T13:33:16Z","body":"Backlog grooming. Too broad. Need concrete tickets!"}}