├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── compile.hxml ├── compile_c.hxml ├── haxelib.json ├── run-test-interp.hxml ├── src └── ecs │ ├── Entity.hx │ ├── System.hx │ ├── SystemList.hx │ ├── View.hx │ ├── Workflow.hx │ ├── core │ ├── AbstractView.hx │ ├── Containers.hx │ ├── ICleanableComponentContainer.hx │ ├── IPoolable.hx │ ├── ISystem.hx │ ├── Parameters.hx │ ├── RestrictedLinkedList.hx │ └── macro │ │ ├── ComponentBuilder.hx │ │ ├── Extensions.hx │ │ ├── Global.hx │ │ ├── MacroTools.hx │ │ ├── MetaTools.hx │ │ ├── PoolBuilder.hx │ │ ├── Report.hx │ │ ├── SystemBuilder.hx │ │ ├── ViewBuilder.hx │ │ ├── ViewSpec.hx │ │ └── ViewsOfComponentBuilder.hx │ └── utils │ ├── Const.hx │ ├── FastEntitySet.hx │ ├── LinkedList.hx │ ├── Root.hx │ └── Signal.hx ├── test ├── ComponentTypeTest.hx ├── EntityTest.hx ├── Run.hx ├── SignalTest.hx ├── SystemMetaTest.hx ├── SystemTest.hx ├── Test.hx ├── TestComponents.hx ├── TestSystemA.hx ├── TestSystemY.hx ├── TestSystemZ.hx ├── TestSystemsB.hx ├── TestWorlds.hx ├── ViewTest.hx └── ViewTypeTest.hx └── tests.hxml /.gitignore: -------------------------------------------------------------------------------- 1 | *.hxproj 2 | /.vscode 3 | *.map 4 | /bin 5 | dump 6 | out.txt 7 | .DS_Store 8 | .haxelib 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | 4 | language: haxe 5 | 6 | os: 7 | - linux 8 | - osx 9 | 10 | # you can specify futher versions as found at http://haxe.org/website-content/downloads/versions.json 11 | haxe: 12 | - "3.4.7" 13 | - "4.1.0" 14 | - development 15 | 16 | matrix: 17 | allow_failures: 18 | - haxe: development 19 | - haxe: "3.4.7" 20 | 21 | install: 22 | - haxelib install travix 23 | # to always use the latest version of travix comment out the previous line and uncomment the next 24 | # - haxelib git travix https://github.com/back2dos/travix && pushd . && cd $(haxelib config)travix/git && haxe build-neko.hxml && popd 25 | - haxelib run travix install 26 | 27 | script: 28 | - haxelib run travix interp 29 | - haxelib run travix node 30 | - haxelib run travix cpp 31 | - haxelib run travix neko 32 | - haxelib run travix python 33 | - haxelib run travix java 34 | 35 | - haxelib run travix interp -D ecs_array_container -D ecs_profiling 36 | - haxelib run travix node -D ecs_array_container -D ecs_profiling 37 | - haxelib run travix cpp -D ecs_array_container -D ecs_profiling 38 | - haxelib run travix neko -D ecs_array_container -D ecs_profiling 39 | - haxelib run travix python -D ecs_array_container -D ecs_profiling 40 | - haxelib run travix java -D ecs_array_container -D ecs_profiling 41 | 42 | - haxelib run travix interp -D ecs_vector_container -D ecs_profiling 43 | - haxelib run travix node -D ecs_vector_container -D ecs_profiling 44 | - haxelib run travix cpp -D ecs_vector_container -D ecs_profiling 45 | - haxelib run travix neko -D ecs_vector_container -D ecs_profiling 46 | - haxelib run travix python -D ecs_vector_container -D ecs_profiling 47 | - haxelib run travix java -D ecs_vector_container -D ecs_profiling 48 | 49 | # - haxelib run travix js 50 | # - haxelib run travix flash 51 | # - haxelib run travix cs 52 | # - haxelib run travix php 53 | #v- haxelib run travix php7 54 | # - if [[ "$(haxe -version)" =~ ^4.* ]]; then haxelib run travix hl; fi 55 | # - haxelib run travix lua 56 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ryan Cleven 4 | Copyright (c) 2017 [From deepcake/echo] 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hmecs (Haxe Macro Entity Component System) 2 | 3 | Super lightweight Entity Component System framework for Haxe. 4 | Initially created to learn the power of macros. 5 | Focused to be simple and fast. 6 | Inspired by other haxe ECS frameworks, especially [EDGE](https://github.com/fponticelli/edge), [ECX](https://github.com/eliasku/ecx), [ESKIMO](https://github.com/PDeveloper/eskimo) and [Ash-Haxe](https://github.com/nadako/Ash-Haxe) 7 | Extended to ecs - For performance improvements with struct only types 8 | 9 | #### Acknowledgement by onehundredfeet 10 | The original vision by [deepcake](https://github.com/deepcake/echo) was fantastic. A macro driven ECS that was aimed at ease of use and performance. It had a few drawbacks I wanted to fix. 11 | 12 | ## News 13 | * Version 1.0 has been tagged. 14 | * Version 1.5 is coming 15 | - Working branch you can check out if you want 16 | - Breaking changes so be careful, entities can only belong to one world 17 | - (Done) Adds World objects that contain all the entities, systems, views and components for a world 18 | - Systems can't intermingle functions that apply to different worlds 19 | - (In Progress) Threading - Automatic threading with meta data 20 | 21 | ## Details 22 | 23 | #### Challenges & Solutions 24 | - It had a single world. This is fine for most applications, but world partitions are sometimes necessary, especially in multiplayer games. (Solved with world flags feature) 25 | - Allocated objects are manually pooled (Solved pool builder feature) 26 | - Singleton components are not natively supported (Solved - Two different features) 27 | - Ability to customize the storage type per component (Solved with @:storage feature) 28 | - The performance at scale with lots of views makes adding and removing entities expensive. I plan on adding a factory system to speed the creation of entities. (1st pass done - Needs a revision) 29 | - Struct types in Haxe are still allocated individually. This makes streamlined processing difficult. For large element counts, you are constantly cache missing. 30 | - Parallelism wasn't natively supported (First pass design complete) 31 | 32 | ### Supported Platforms 33 | I have tested it on the following platforms 34 | 35 | - Hashlink 36 | - HXCPP 37 | - JS 38 | - HXCS - Warning - while it will not cause any obvious issues, using structs as components will potentially cause issues when trying to write to them as they are passed by value. 39 | 40 | ### Overview 41 | * Component is an instance of `T:Any` class. For each class `T` will be generated a global component container, where instance of `T` is a value and `Entity` is a key. 42 | * `Entity` in that case is just an abstract over the `Int`, but with the ability to work with it as with a set of components like in other regular ECS frameworks. 43 | * `View` is a collection of entities containing all components of the required types `T1, T2, TN`. Views are placed in Systems. 44 | * `System` is a place for processing a certain set of data represented by views. 45 | * To organize systems in phases can be used the `SystemList`. 46 | * `World` is a binding mechanism to allow views and systems to opperate on a subset of entities. A View (and a function in a System) can be associated with any number of Worlds. When an Entity is created, it can be associated with any number of worlds. At the moment, there is a maximum of 32 worlds. Views will only include Entities that are associated with `ANY` of the worlds it can view. `NOTE: Worlds will be deprecated in favour of a new tag system` 47 | * `Pool` a pool is a static container that can be used to speed up allocations using a rent/retire paradigm. Call a static rent to get a new instance and then retire on that instance to return it to the pool 48 | * `Workflow` a global class used to access common features such as a singleton 49 | * `Tag` is class with the @:storage(TAG) metadata that changes the behaviour from being specified as an instance to added as a type. A flag set keeps track of which entities are tagged, makeing storing many tags compact and fast. When specified in a function, a single static instance of the class will be passed in to all calls, regardless of which entity is passed in. 50 | 51 | ## WARNING & INSTRUCTIONS 52 | ### Due to the heavily macro based approach of the system, there are some nuances that require some concessions. 53 | 54 | 1. To get all the features working, you will need to create a very lean, or even proxy, main file and use it as your entry point when compiling. 55 | 56 | ```haxe 57 | import your.MainClass; 58 | 59 | class ProxyMain { 60 | public static function main() { 61 | MainClass.main(); 62 | } 63 | } 64 | ``` 65 | 66 | 2. You will need to add a call to initialize a variety of late binding mechanisms. 67 | 68 | ```haxe 69 | import your.MainClass; 70 | 71 | class ProxyMain { 72 | public static function main() { 73 | #if !macro 74 | ecs.core.macro.Global.setup(); // macro to generate all the global calls and then hook them up at runtime 75 | #end 76 | MainClass.main(); 77 | } 78 | } 79 | ``` 80 | 81 | ## Usage 82 | 83 | ### Component Storage 84 | Each component type can have its own storage specification. They are specified using the @:storage metadata on the component type. You can use abstract types to wrap basic types to apply the metadata. 85 | 86 | #### @:storage(FAST) 87 | This is the default. It is an array the length of the total number of entities. Any entities with the component will have a non-zero allocated object in the array corresponding to the entity id. Obviously this can waste a lot of memory if overused with a large number of entities. 88 | 89 | #### @:storage(COMPACT) 90 | This is the typical secondary value. It specifies an IntMap to be used for the storage backend. This will slightly increase the lookup time for get, but it will significantly reduce the amount of memory required. 91 | 92 | #### @:storage(FLAG) 93 | This is says that this type is a bit flag on the flags storage for the entity. It takes a single bit per entity, much smaller than using an array. It is slightly slower than the fast storage but not by much. 94 | 95 | A side benefit is that a single instance of the tagged class is available in views that require this flag. 96 | 97 | #### @:storage(SINGLETON) 98 | There can only be one instance of this class and only one entity can own it. It is very limited, but very fast and uses little memory. 99 | 100 | ## Examples 101 | ```haxe 102 | import ecs.SystemList; 103 | import ecs.Workflow; 104 | import ecs.Entity; 105 | 106 | 107 | // Can use vanilla classes with no annotation 108 | class MediumComponent { 109 | 110 | } 111 | 112 | // Can use vanilla abstracts to create a new component type 113 | abstract AbstractComponent(MediumComponent) from MediumComponent to MediumComponent { 114 | 115 | } 116 | 117 | // Abstracts allow adding multiple components of the same underlying type to an entity 118 | // When using abstracts, be careful not to assume the underlaying type when adding 119 | @:forward 120 | abstract Name(String) from String to String { 121 | public function new(name:String) this = name; 122 | } 123 | 124 | // 0 is assumed to mean 'empty' with Int abstracts. This may mean that with some cases it is not possible to use this approach. 125 | abstract IDComponent(Int) from Int to Int { 126 | 127 | } 128 | 129 | 130 | @:storage(FAST) // Sepcifies the flavour of storage to use. FAST is the default. Uses more memory (Array Storage) 131 | #if !macro @:build(ecs.core.macro.PoolBuilder.arrayPool()) #end // Implicitly adds rent & retire 132 | //@:no_autoretire // turns off automatically using the attached pool. Unless this is added, the ECS will automatically return the object to the pool on being removed from the entity 133 | class SmallComponent { 134 | var value = 0; 135 | 136 | function new(v : Int) { 137 | value = v; 138 | } 139 | 140 | @:pool_factory // optional: overrides the default 'new', not necessary if you provide a parameterless constructor 141 | static function factory() : SmallComponent{ 142 | return new SmallComponent(5); 143 | } 144 | 145 | @:pool_retire // optional: callback when retiring 146 | function onRetire() { 147 | 148 | } 149 | 150 | @:pool_retire // optional: callback when retiring 151 | function onRetireE(e : Entity) { 152 | 153 | } 154 | 155 | 156 | // Implicitly added by the pool builder 157 | // static function rent() : SmallComponent 158 | // function retire() 159 | 160 | } 161 | 162 | @:storage(COMPACT) // Uses less memory, but operations are slightly slower than fast (Map storage) 163 | class HeavyComponent { 164 | public function new(){} 165 | 166 | @:ecs_remove 167 | function onRemove() { 168 | // Custom logic when a component is removed from an entity 169 | } 170 | } 171 | 172 | @:storage(SINGLETON) // Simply specifies the storage capacity, does not affect the behaviour 173 | class SingletonComponent { 174 | public function new(){} 175 | } 176 | 177 | // This 'marks' entities with this component type, but will providethe same single static instance value as a parameter in updates 178 | // Specifies a type that is added as a type, not a value 179 | @:storage(TAG) // Uses very little memory and is fast to look up (Bitfield) 180 | class TagYouIt { 181 | TagYouIt() {} // requires a constructor with no parameters, public or private 182 | 183 | public var example = "it"; 184 | } 185 | 186 | @:shelvable // PLANNED: Adds extra storage and behaviour to allow this component to be shelved. Has a small performance impact on some actions. 187 | // At the moment all components are shelvable. 188 | class Position { 189 | public var x : Float; 190 | public var y : Float 191 | public function new( x : Float, y : Float) { 192 | this.x = x; 193 | this.y = y; 194 | } 195 | } 196 | // 197 | // Example program 198 | // 199 | class Example { 200 | final FIELDS = 1; 201 | final FOREST = 2; 202 | final WORLDS_FIELDS = 1 << FIELDS; 203 | final WORLDS_FOREST = 1 << FOREST; 204 | 205 | static function main() { 206 | ecsSetup(); // Called to initialize the system 207 | 208 | var physics = new SystemList() 209 | .add(new Movement()) 210 | .add(new CollisionResolver()); 211 | 212 | Workflow.addSystem(physics); 213 | Workflow.addSystem(new Render()); // or just add systems directly 214 | 215 | var john = createRabbit(0, 0, 1, 1, 'John', WORLDS_FIELDS); // Only in the forest 216 | var jack = createRabbit(5, 5, 1, 1, 'Jack', WORLDS_FIELDS | WORLDS_FOREST); // In both worlds 217 | 218 | trace(jack.exists(Position)); // true 219 | trace(jack.get(Position).x); // 5 220 | jack.remove(Position); // oh no! 221 | jack.add(new Position(1, 1)); // okay 222 | jack.add(TagYouIt); // Jack is now tagged 223 | 224 | // 225 | // Shelving - Retaining reference to component, but officially detatching it from the entity 226 | // Can only hold ONE component at a time. 227 | // Do not add a new component when there is already one shelved, the result is undefined. 228 | jack.shelve(Position); // Retains the component in storage, but removes it from being attached to the entity 229 | trace(jack.exists(Position)); // false 230 | jack.unshelve(Position): // Re-attaches the corresponding shelved component. 231 | trace(jack.exists(Position)); // true 232 | 233 | // THIS IS TWO FEATURES 234 | // - the singleton() entity on workflow is a global entity across all worlds & systems. 235 | // - the SingletonComponent is a component where only one instance will ever exist and must only ever be added to one entity 236 | Workflow.singleton().add( new SingletonComponent() ); // Only one can be added globally at any one time 237 | 238 | // also somewhere should be Workflow.update call on every tick 239 | Workflow.update(1.0); 240 | 241 | // You can manually call the systems lists if you wish to give more granular control. 242 | physics.forceUpdate(1.0); 243 | } 244 | 245 | static function createTree(x:Float, y:Float) { 246 | return new Entity() // Trees are present in all worlds 247 | .add(new Position(x, y)) 248 | .add(new Sprite('assets/tree.png')) 249 | .add(new MediumComponent(1)) 250 | .add(new HeavyComponent()); 251 | } 252 | static function createRabbit(x:Float, y:Float, vx:Float, vy:Float, name:Name, worlds:Int) { 253 | var pos = new Position(x, y); 254 | var vel = new Velocity(vx, vy); 255 | var spr = new Sprite('assets/rabbit.png'); 256 | return new Entity(worlds).add(pos, vel, spr, name, SmallComponent.rent()); // rabbits can be in world specified 257 | } 258 | } 259 | 260 | 261 | class Movement extends ecs.System { 262 | // @update-functions will be called for every entity that contains all the defined components; 263 | // All args are interpreted as components, except Float (reserved for delta time) and Int/Entity; 264 | @:update function updateBody(pos:Position, vel:Velocity, dt:Float, entity:Entity) { 265 | pos.x += vel.x * dt; 266 | pos.y += vel.y * dt; 267 | } 268 | 269 | 270 | //Can narrow the scope of the update to only entities that are present in a world set 271 | @:worlds(WORLDS_FOREST) // These are bit flags. The string is evaulate as an expression 272 | @update function inForest(name:Name) { 273 | trace('${name} is in the forest'); // Will display Jack 274 | } 275 | 276 | //Can narrow the scope of the update to only entities that have the tag TagYouIt 277 | @update function isIt(name:Name, tag:TagYouIt) { 278 | trace('${name} is ${tag.example}'); // Will display Jack is it 279 | } 280 | 281 | // Worlds can be strings or constant string expressions if using compiler only @: 282 | @:worlds(WORLDS_FIELDS) // These are bit flags. The string is evaulate as an expression 283 | @:update function inFields(name:Name) { 284 | trace('${name} is in the fields'); // Will display Jack & John 285 | } 286 | 287 | // If @update-functions are defined without components, 288 | // they are called only once per system's update; 289 | @:update function traceHello(dt:Float) { 290 | trace('Hello!'); 291 | } 292 | // The execution order of @update-functions is the same as the definition order, 293 | // so you can perform some preparations before or after iterating over entities; 294 | @:update function traceWorld() { 295 | trace('World!'); 296 | } 297 | } 298 | 299 | class NamePrinter extends ecs.System { 300 | // All of necessary for meta-functions views will be defined and initialized under the hood, 301 | // but it is also possible to define the View manually (initialization is still not required) 302 | // for additional features such as counting and sorting entities; 303 | // Note: Does not support @:worlds 304 | var named:View; 305 | 306 | @:update function sortAndPrint() { 307 | named.entities.sort((e1, e2) -> e1.get(Name) < e2.get(Name) ? -1 : 1); 308 | // using Lambda 309 | named.entities.iter(e -> trace(e.get(Name))); 310 | } 311 | } 312 | 313 | class Render extends ecs.System { 314 | var scene:DisplayObjectContainer; 315 | // There are @a, @u and @r shortcuts for @added, @update and @removed metas; 316 | // @added/@removed-functions are callbacks that are called when an entity is added/removed from the view; 317 | @:a function onEntityWithSpriteAndPositionAdded(spr:Sprite, pos:Position) { 318 | scene.addChild(spr); 319 | } 320 | // Even if callback was triggered by destroying the entity, 321 | // @removed-function will be called before this happens, 322 | // so access to the component will be still exists; 323 | @:r function onEntityWithSpriteAndPositionRemoved(spr:Sprite, pos:Position, e:Entity) { 324 | scene.removeChild(spr); // spr is still not a null 325 | trace('Oh My God! They removed ${ e.exists(Name) ? e.get(Name) : "Unknown Sprite" }!'); 326 | } 327 | 328 | //PLANNED PARALLEL API NOT IMPLEMENTED YET 329 | @:parallel(FULL) // | @:p(FULL) - Valid values FULL | DOUBLE | HALF | # - Will create threads to call this function in parallel according to the number specifed in the parameters. Will collect all threads before continuing. 330 | @:bucket(5) // | @:b(5) Specifies the parallel bucketing size valid values MAX | # - Max will take total / threads 331 | @:fork(SPRITE_UPDATE) // - Named synchronization - Will split off this update in another thread and continue processing other updates, can be combined with @:parallel 332 | @:u inline function updateSpritePosition(spr:Sprite, pos:Position) { 333 | spr.x = pos.x; 334 | spr.y = pos.y; 335 | } 336 | 337 | // PARALLEL API NOT IMPLEMENTED YET 338 | @:join(SPRITE_UPDATE) // - Will wait until the corresponding fork is completed before running this function 339 | @:u inline function afterSpritePositionsUpdated() { 340 | // rendering, etc 341 | } 342 | } 343 | 344 | function ecsSetup() { 345 | ecs.core.macro.Global.setup(); // macro to generate all the global calls and then hook them up at runtime 346 | } 347 | 348 | 349 | ``` 350 | 351 | #### Also 352 | There is also exists a few additional compiler flags: 353 | * `-D ecs_profiling` - collecting some more info in `Workflow.info()` method for debug purposes 354 | * `-D ecs_report` - traces a short report of built components and views 355 | 356 | ### Install 357 | ```haxelib git ecs https://github.com/onehundredfeet/ecs.git``` 358 | -------------------------------------------------------------------------------- /compile.hxml: -------------------------------------------------------------------------------- 1 | --library buddy 2 | --class-path src 3 | --class-path test 4 | -hl bin/test.hl 5 | --main test.Test 6 | -D ecs_max_tags=64 7 | #-D ecs_max_entities=512 8 | #-D hl_ver=1.14.0 9 | -------------------------------------------------------------------------------- /compile_c.hxml: -------------------------------------------------------------------------------- 1 | --library buddy 2 | --class-path src 3 | --class-path . 4 | -D ecs_max_tags=64 5 | #-D ecs_max_entities=512 6 | -hl bin/test/test.c 7 | --main test.Test 8 | #-D hl_ver=1.14.0 9 | -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hmecs", 3 | "url" : "https://github.com/onehundredfeet/hmecs", 4 | "license": "MIT", 5 | "tags": [ "entity", "component", "system", "ecs" ], 6 | "description": "Macro component query engine (i.e. an ECS) for Haxe. Designed for easy expression & performance.", 7 | "version": "0.3.0", 8 | "classPath": "src/", 9 | "releasenote": "Alpha 3", 10 | "contributors": [ "deepcake", "onehundredfeet" ] 11 | } 12 | -------------------------------------------------------------------------------- /run-test-interp.hxml: -------------------------------------------------------------------------------- 1 | -cp src 2 | -cp test 3 | -lib buddy 4 | -lib tink_macro 5 | -main Run.hx 6 | --interp 7 | -D ecs_profiling 8 | -D echoes_report 9 | -D ecs_array_container 10 | -D buddy-ignore-passing-specs -------------------------------------------------------------------------------- /src/ecs/Entity.hx: -------------------------------------------------------------------------------- 1 | package ecs; 2 | 3 | #if macro 4 | import haxe.macro.Expr; 5 | 6 | using ecs.core.macro.ComponentBuilder; 7 | using ecs.core.macro.ViewsOfComponentBuilder; 8 | using ecs.core.macro.MacroTools; 9 | using haxe.macro.Context; 10 | using Lambda; 11 | using StringTools; 12 | #else 13 | 14 | import haxe.CallStack; 15 | #end 16 | 17 | /** 18 | * Entity is an abstract over the `Int` key. 19 | * - Do not use the Entity as a unique id, as destroyed entities will be cached and reused! 20 | * 21 | * @author https://github.com/deepcake 22 | */ 23 | abstract Entity(Int) from Int to Int { 24 | public static inline var INVALID_ENTITY:Entity = Workflow.INVALID_ID; 25 | 26 | /** 27 | * Creates a new Entity instance 28 | * @param immediate immediately adds this entity to the workflow if `true`, otherwise `activate()` call is required 29 | */ 30 | public inline function new(world:Int = 0, immediate = true) { 31 | this = Workflow.id(immediate, world); 32 | } 33 | 34 | public inline function worlds() { 35 | return Workflow.world(this); 36 | } 37 | 38 | /** 39 | * Adds this entity to the workflow, so it can be collected by views 40 | */ 41 | public inline function activate() { 42 | Workflow.add(this); 43 | } 44 | 45 | /** 46 | * Removes this entity from the workflow (and also from all views), but saves all associated components. 47 | * Entity can be added to the workflow again by `activate()` call 48 | */ 49 | public inline function deactivate() { 50 | Workflow.remove(this); 51 | } 52 | 53 | /** 54 | * Prevents any addition callbaks until resuming 55 | */ 56 | public inline function pauseAdding() { 57 | Workflow.pauseAdding(this); 58 | } 59 | 60 | /** 61 | * Calls any addition callbacks for new views 62 | */ 63 | public inline function resumeAdding() { 64 | Workflow.resumeAdding(this); 65 | } 66 | 67 | /** 68 | * Returns the status of this entity: Active, Inactive, Cached or Invalid. Method is used mostly for debug purposes 69 | * @return Status 70 | */ 71 | public inline function status():Status { 72 | return Workflow.status(this); 73 | } 74 | 75 | /** 76 | * Returns `true` if this entity is added to the workflow, otherwise returns `false` 77 | * @return Bool 78 | */ 79 | public inline function isActive():Bool { 80 | return Workflow.status(this) == Active; 81 | } 82 | 83 | /** 84 | * Returns `true` if this entity has not been destroyed and therefore can be used safely 85 | * @return Bool 86 | */ 87 | public inline function isValid():Bool { 88 | return this != INVALID_ENTITY && Workflow.status(this) < Cached; 89 | } 90 | 91 | /** 92 | * Removes all of associated to this entity components. 93 | * __Note__ that this entity will be still exists after call this method (just without any associated components). 94 | * If entity is not required anymore - `destroy()` should be called 95 | */ 96 | public inline function removeAll() { 97 | Workflow.removeAllComponentsOf(this); 98 | } 99 | 100 | /** 101 | * Removes this entity from the workflow with removing all associated components. 102 | * The `Int` id will be cached and then will be used again in new created entities. 103 | * __Note__ that using this entity after call this method is incorrect! 104 | */ 105 | public inline function destroy() { 106 | Workflow.cache(this); 107 | } 108 | 109 | public var generation(get, never):Int; 110 | 111 | inline function get_generation() { 112 | return Workflow.getGeneration(this); 113 | } 114 | 115 | public function toSafe():EntityRef { 116 | if (this == INVALID_ENTITY) { 117 | throw('Getting safe reference from invalid entity'); 118 | } 119 | var gen = Workflow.getGeneration(this); 120 | return haxe.Int64.make(this, gen); 121 | } 122 | 123 | /** 124 | * Returns list of all associated to this entity components. 125 | * @return String 126 | */ 127 | public inline function print():String { 128 | return Workflow.printAllComponentsOf(this); 129 | } 130 | 131 | #if macro 132 | static function getComponentContainerInfo(c:haxe.macro.Expr, pos:haxe.macro.Expr.Position) { 133 | var to = c.typeof(); 134 | if (to == null) { 135 | Context.fatalError('Can not find type for ${c} ', pos); 136 | } 137 | var type = to; 138 | 139 | return switch (type) { 140 | case TType(tref, args): 141 | if (tref.get().name.contains("Class<")) { 142 | var cn = c.parseClassName(); 143 | var clt = cn.getType(); 144 | var tt = clt.follow(); 145 | var compt = tt.toComplexType(); 146 | compt.getComponentContainerInfo(pos); 147 | } else { 148 | // Typedef 149 | (type.follow().toComplexType()).getComponentContainerInfo(pos); 150 | } 151 | // class is specified instead of an expression 152 | default: 153 | (type.follow().toComplexType()).getComponentContainerInfo(pos); 154 | } 155 | } 156 | #end 157 | 158 | /** 159 | * Adds a specified components to this entity. 160 | * If a component with the same type is already added - it will be replaced 161 | * @param components comma separated list of components of `Any` type 162 | * @return `Entity` 163 | */ 164 | macro public function add(self:Expr, components:Array):ExprOf { 165 | var pos = Context.currentPos(); 166 | 167 | if (components.length == 0) { 168 | Context.error('Required one or more Components', pos); 169 | } 170 | 171 | var addComponentsToContainersExprs = components.map(function(c) { 172 | var info = getComponentContainerInfo(c, pos); 173 | 174 | return info.getAddExpr(macro __entity__, c); 175 | // var containerName = (c.typeof().follow().toComplexType()).getComponentContainerInfo().fullName; 176 | // return macro @:privateAccess $i{ containerName }.inst().add(__entity__, $c); 177 | }); 178 | 179 | var body = [].concat(addComponentsToContainersExprs).concat([ 180 | macro if (__entity__.isActive()) { 181 | for (v in ecs.Workflow.views) { 182 | @:privateAccess v.addIfMatched(__entity__); 183 | } 184 | } 185 | ]).concat([macro return __entity__]); 186 | 187 | var ret = macro #if (haxe_ver >= 4) inline #end (function(__entity__:ecs.Entity) $b{body})($self); 188 | 189 | return ret; 190 | } 191 | 192 | #if macro 193 | static function ecsActionByClass(self:Expr, types:Array>>, pos:Position, 194 | storageAction:(info:StorageInfo, entityExpr:Expr, pos:Position) -> Expr, 195 | viewAction:(viewExpr:Expr, entityExpr:Expr, pos:Position) -> Expr):ExprOf { 196 | var errorStage = ""; 197 | if (types.length == 0) { 198 | Context.error('Required one or more Component Types', pos); 199 | } 200 | errorStage = "starting"; 201 | var cts = types.map(function(type) { 202 | return type.parseClassName().getType().follow().toComplexType(); 203 | }); 204 | 205 | errorStage = "found types"; 206 | var actionExprs = cts.map(function(ct) { 207 | var info = ct.getComponentContainerInfo(pos); 208 | return storageAction(info, macro __entity__, pos); 209 | }); 210 | errorStage = "got action expression"; 211 | 212 | var viewActionExpr = cts.map(function(ct) { 213 | return ct.getViewsOfComponent(pos).followName(pos); 214 | }).map(function(viewsOfComponentClassName) { 215 | var x = viewsOfComponentClassName.asTypeIdent(Context.currentPos()); 216 | return viewAction(macro $x.inst(), macro __entity__, pos); 217 | }); 218 | errorStage = "got views of components"; 219 | 220 | var body = [ 221 | [ 222 | macro if (__entity__.isActive()) 223 | $b{viewActionExpr} 224 | ], 225 | actionExprs, 226 | [macro return __entity__] 227 | ].flatten(); 228 | 229 | /* 230 | var body = [].concat([ 231 | macro if (__entity__.isActive()) 232 | $b{removeEntityFromRelatedViewsExprs} 233 | ]).concat(removeComponentsFromContainersExprs).concat([macro return __entity__]); 234 | */ 235 | errorStage = "made body"; 236 | 237 | var ret = macro inline(function(__entity__:ecs.Entity) $b{body})($self); 238 | 239 | errorStage = "returning"; 240 | 241 | return ret; 242 | } 243 | #end 244 | 245 | // 246 | // By class functions 247 | // 248 | 249 | /** 250 | * Removes a component from this entity with specified type 251 | * @param types comma separated `Class` types of components that should be removed 252 | * @return `Entity` 253 | */ 254 | macro public function remove(self:Expr, types:Array>>):ExprOf { 255 | var storageAction = (info:StorageInfo, entityExpr:Expr, pos:Position) -> { 256 | return info.getRemoveExpr(entityExpr); 257 | } 258 | var viewAction = (viewExpr:Expr, entityExpr:Expr, pos:Position) -> { 259 | return macro @:privateAccess ${viewExpr}.removeIfExists($entityExpr); 260 | } 261 | return ecsActionByClass(self, types, Context.currentPos(), storageAction, viewAction); 262 | } 263 | 264 | macro public function shelve(self:Expr, types:Array>>):ExprOf { 265 | var storageAction = (info:StorageInfo, entityExpr:Expr, pos:Position) -> { 266 | return info.getShelveExpr(entityExpr, pos); 267 | } 268 | var viewAction = (viewExpr:Expr, entityExpr:Expr, pos:Position) -> { 269 | return macro @:privateAccess ${viewExpr}.removeIfExists($entityExpr); 270 | } 271 | return ecsActionByClass(self, types, Context.currentPos(), storageAction, viewAction); 272 | } 273 | 274 | macro public function unshelve(self:Expr, types:Array>>):ExprOf { 275 | var storageAction = (info:StorageInfo, entityExpr:Expr, pos:Position) -> { 276 | return info.getUnshelveExpr(entityExpr, pos); 277 | } 278 | var viewAction = (viewExpr:Expr, entityExpr:Expr, pos:Position) -> { 279 | return macro @:privateAccess ${viewExpr}.addIfMatched($entityExpr); 280 | } 281 | return ecsActionByClass(self, types, Context.currentPos(), storageAction, viewAction); 282 | } 283 | 284 | #if the_old_way_is_better 285 | macro public function remove(self:Expr, types:Array>>):ExprOf { 286 | var pos = Context.currentPos(); 287 | var errorStage = ""; 288 | if (types.length == 0) { 289 | Context.error('Required one or more Component Types', pos); 290 | } 291 | errorStage = "starting"; 292 | var cts = types.map(function(type) { 293 | return type.parseClassName().getType().follow().toComplexType(); 294 | }); 295 | 296 | errorStage = "found types"; 297 | var removeComponentsFromContainersExprs = cts.map(function(ct) { 298 | var info = ct.getComponentContainerInfo(pos); 299 | return info.getRemoveExpr(macro __entity__); 300 | }); 301 | errorStage = "got remove expression"; 302 | 303 | var removeEntityFromRelatedViewsExprs = cts.map(function(ct) { 304 | return ct.getViewsOfComponent(pos).followName(pos); 305 | }).map(function(viewsOfComponentClassName) { 306 | var x = viewsOfComponentClassName.asTypeIdent(Context.currentPos()); 307 | return macro @:privateAccess $x.inst().removeIfExists(__entity__); 308 | }); 309 | errorStage = "got views of components"; 310 | 311 | var body = [].concat([ 312 | macro if (__entity__.isActive()) 313 | $b{removeEntityFromRelatedViewsExprs} 314 | ]).concat(removeComponentsFromContainersExprs).concat([macro return __entity__]); 315 | 316 | errorStage = "made body"; 317 | 318 | var ret = macro inline(function(__entity__:ecs.Entity) $b{body})($self); 319 | 320 | errorStage = "returning"; 321 | 322 | return ret; 323 | } 324 | #end 325 | 326 | #if bored_and_want_to_fix 327 | /** 328 | * Returns a component of this entity of specified type. 329 | * If a component with specified type is not added to this entity, `null` will be returned 330 | * @param type `Class` type of component 331 | * @return `T:Any` component instance 332 | */ 333 | macro public function getOrAdd(self:Expr, type:ExprOf>):ExprOf { 334 | var info = (type.parseClassName().getType().follow().toComplexType()).getComponentContainerInfo(); 335 | var exists = info.getExistsExpr(self); 336 | var get = info.getGetExpr(self); 337 | 338 | return macro if ($exists) { 339 | return $get; 340 | } else { 341 | /* 342 | var addComponentsToContainersExprs = components.map(function(c) { 343 | var to = c.typeof(); 344 | if (!to.isSuccess()) { 345 | Context.error('Can not find type for ${c}', Context.currentPos()); 346 | } 347 | var info = (c.typeof().sure().follow().toComplexType()).getComponentContainerInfo(); 348 | return info.getAddExpr(macro __entity__, c); 349 | // var containerName = (c.typeof().follow().toComplexType()).getComponentContainerInfo().fullName; 350 | // return macro @:privateAccess $i{ containerName }.inst().add(__entity__, $c); 351 | }); 352 | 353 | var body = [].concat(addComponentsToContainersExprs).concat([ 354 | macro if (__entity__.isActive()) { 355 | for (v in ecs.Workflow.views) { 356 | @:privateAccess v.addIfMatched(__entity__); 357 | } 358 | } 359 | ]).concat([macro return __entity__]); 360 | 361 | var ret = macro #if (haxe_ver >= 4) inline #end (function(__entity__:ecs.Entity) $b{body})($self); 362 | 363 | */ 364 | }; 365 | } 366 | #end 367 | 368 | /** 369 | * Returns a component of this entity of specified type. 370 | * If a component with specified type is not added to this entity, `null` will be returned 371 | * @param type `Class` type of component 372 | * @return `T:Any` component instance 373 | */ 374 | macro public function get(self:Expr, type:ExprOf>):ExprOf { 375 | var pos = Context.currentPos(); 376 | var info = (type.parseClassName().getType().follow().toComplexType()).getComponentContainerInfo(pos); 377 | 378 | return info.getGetExpr(self); 379 | } 380 | 381 | /** 382 | * Returns `true` if this entity contains a component of specified type, otherwise returns `false` 383 | * @param type `Class` type of component 384 | * @return `Bool` 385 | */ 386 | macro public function exists(self:Expr, type:ExprOf>):ExprOf { 387 | var pos = Context.currentPos(); 388 | var info = (type.parseClassName().getType().follow().toComplexType()).getComponentContainerInfo(pos); 389 | return info.getExistsExpr(self); 390 | } 391 | 392 | macro public function has(self:Expr, type:ExprOf>):ExprOf { 393 | var pos = Context.currentPos(); 394 | var info = (type.parseClassName().getType().follow().toComplexType()).getComponentContainerInfo(pos); 395 | 396 | return info.getExistsExpr(self); 397 | } 398 | 399 | @:keep 400 | public function toString() { 401 | var g = Workflow.getGeneration(this); 402 | return 'Entity(id:${this}, gen:${g})'; 403 | } 404 | } 405 | 406 | enum abstract Status(Int) { 407 | var Inactive = 0; 408 | var Active = 1; 409 | var Cached = 2; 410 | var Invalid = 3; 411 | 412 | @:op(A > B) static function gt(a:Status, b:Status):Bool; 413 | 414 | @:op(A < B) static function lt(a:Status, b:Status):Bool; 415 | } 416 | 417 | abstract EntityRef(haxe.Int64) from haxe.Int64 to haxe.Int64 { 418 | public static var INVALID_ENTITY(get, never):EntityRef; 419 | 420 | inline static function get_INVALID_ENTITY() { 421 | return haxe.Int64.make(Entity.INVALID_ENTITY, 0); 422 | } 423 | 424 | public var entity(get, never):Entity; 425 | 426 | inline function get_entity() { 427 | return this.high; 428 | } 429 | 430 | public var generation(get, never):Int; 431 | 432 | inline function get_generation() { 433 | return this.low; 434 | } 435 | 436 | public inline function isValid():Bool { 437 | return entity.isValid() && generation == entity.generation; 438 | } 439 | 440 | // Assumes that the entity is valid 441 | public inline function isFresh():Bool { 442 | return generation == entity.generation; 443 | } 444 | 445 | public inline function isStale():Bool { 446 | return generation != entity.generation; 447 | } 448 | 449 | public inline function entityIsValid():Bool { 450 | return entity.isValid(); 451 | } 452 | 453 | public var entitySafe(get,never):Entity; 454 | inline function get_entitySafe() { 455 | var my_generation = this.low; 456 | var stored_generation = entity.generation; 457 | var same_generation = my_generation == stored_generation; 458 | var e = this.high; 459 | 460 | return same_generation ? e : Entity.INVALID_ENTITY; 461 | } 462 | 463 | public var entityEnsured(get,never):Entity; 464 | inline function get_entityEnsured() { 465 | var e = this.high; 466 | 467 | if (e == Entity.INVALID_ENTITY) { 468 | throw 'Entity is invalid'; 469 | } 470 | 471 | var my_generation = this.low; 472 | var stored_generation = entity.generation; 473 | var same_generation = my_generation == stored_generation; 474 | 475 | if (!same_generation) { 476 | throw 'Entity ${e} is stale ${my_generation} != ${stored_generation}'; 477 | } 478 | 479 | return e; 480 | } 481 | 482 | macro public function get(self:Expr, type:ExprOf>):ExprOf { 483 | var pos = Context.currentPos(); 484 | var info = (type.parseClassName().getType().follow().toComplexType()).getComponentContainerInfo(pos); 485 | 486 | var indirectExpr = macro $self.entity; 487 | 488 | return info.getGetExpr(indirectExpr); 489 | } 490 | 491 | macro public function has(self:Expr, type:ExprOf>):ExprOf { 492 | var pos = Context.currentPos(); 493 | var info = (type.parseClassName().getType().follow().toComplexType()).getComponentContainerInfo(pos); 494 | 495 | var indirectExpr = macro $self.entity; 496 | return info.getExistsExpr(indirectExpr); 497 | } 498 | 499 | macro public function exists(self:Expr, type:ExprOf>):ExprOf { 500 | var pos = Context.currentPos(); 501 | var info = (type.parseClassName().getType().follow().toComplexType()).getComponentContainerInfo(pos); 502 | var indirectExpr = macro $self.entity; 503 | 504 | return info.getExistsExpr(indirectExpr); 505 | } 506 | 507 | macro public function remove(self:Expr, types:Array>>):ExprOf { 508 | var storageAction = (info:StorageInfo, entityExpr:Expr, pos:Position) -> { 509 | return info.getRemoveExpr(entityExpr); 510 | } 511 | var viewAction = (viewExpr:Expr, entityExpr:Expr, pos:Position) -> { 512 | return macro @:privateAccess ${viewExpr}.removeIfExists($entityExpr); 513 | } 514 | var indirectExpr = macro $self.entity; 515 | 516 | return @:privateAccess Entity.ecsActionByClass(indirectExpr, types, Context.currentPos(), storageAction, viewAction); 517 | } 518 | 519 | @:to 520 | public inline function toEntity():Entity { 521 | return entity; 522 | } 523 | 524 | macro public function add(self:Expr, components:Array):ExprOf { 525 | var pos = Context.currentPos(); 526 | 527 | if (components.length == 0) { 528 | Context.error('Required one or more Components', pos); 529 | } 530 | 531 | var addComponentsToContainersExprs = components.map(function(c) { 532 | var info = @:privateAccess Entity.getComponentContainerInfo(c, pos); 533 | 534 | return info.getAddExpr(macro __entity__, c); 535 | }); 536 | 537 | var body = [].concat(addComponentsToContainersExprs).concat([ 538 | macro if (__entity__.isActive()) { 539 | for (v in ecs.Workflow.views) { 540 | @:privateAccess v.addIfMatched(__entity__); 541 | } 542 | } 543 | ]).concat([macro return __entity__]); 544 | 545 | var indirectExpr = macro $self.entity; 546 | 547 | var ret = macro inline (function(__entity__:ecs.Entity) $b{body})($indirectExpr); 548 | 549 | return ret; 550 | } 551 | 552 | macro public function shelve(self:Expr, types:Array>>):ExprOf { 553 | var storageAction = (info:StorageInfo, entityExpr:Expr, pos:Position) -> { 554 | return info.getShelveExpr(entityExpr, pos); 555 | } 556 | var viewAction = (viewExpr:Expr, entityExpr:Expr, pos:Position) -> { 557 | return macro @:privateAccess ${viewExpr}.removeIfExists($entityExpr); 558 | } 559 | var indirectExpr = macro $self.entity; 560 | return @:privateAccess Entity.ecsActionByClass(indirectExpr, types, Context.currentPos(), storageAction, viewAction); 561 | } 562 | 563 | macro public function unshelve(self:Expr, types:Array>>):ExprOf { 564 | var storageAction = (info:StorageInfo, entityExpr:Expr, pos:Position) -> { 565 | return info.getUnshelveExpr(entityExpr, pos); 566 | } 567 | var viewAction = (viewExpr:Expr, entityExpr:Expr, pos:Position) -> { 568 | return macro @:privateAccess ${viewExpr}.addIfMatched($entityExpr); 569 | } 570 | var indirectExpr = macro $self.entity; 571 | return @:privateAccess Entity.ecsActionByClass(indirectExpr, types, Context.currentPos(), storageAction, viewAction); 572 | } 573 | 574 | public inline function isActive():Bool { 575 | return Workflow.status(entity) == Active; 576 | } 577 | 578 | @:keep 579 | public function toString() { 580 | return 'EntityRef(id:${entity}, generation:${generation})'; 581 | } 582 | } 583 | -------------------------------------------------------------------------------- /src/ecs/System.hx: -------------------------------------------------------------------------------- 1 | package ecs; 2 | 3 | /** 4 | * System 5 | * 6 | * You must extend this class to make your own system. 7 | * 8 | * Functions with `@update` (or `@up`, or `@u`) meta are called for each entity that contains all the defined components. 9 | * So, a function like: 10 | * ``` 11 | * @u function f(a:A, b:B, entity:Entity) { } 12 | * ``` 13 | * does a two things: 14 | * - Defines and initializes a `View` (if the `View` has not been previously defined) 15 | * - Creates a loop in the system update method 16 | * ``` 17 | * for (entity in viewOfAB.entities) { 18 | * f(entity.get(A), entity.get(B), entity); 19 | * } 20 | * ``` 21 | * 22 | * Functions with `@added`, `@ad`, `@a` meta become callbacks that will be called on each entity to be assembled by the view. 23 | * Functions with `@removed`, `@rm`, `@r` does the same but when entity is removed. 24 | * 25 | * You can define the `View` manually (no initialization required) 26 | * 27 | * @author https://github.com/deepcake 28 | */ 29 | #if !macro 30 | @:autoBuild(ecs.core.macro.SystemBuilder.build()) 31 | #end 32 | @:keepSub 33 | class System implements ecs.core.ISystem { 34 | 35 | 36 | #if ecs_profiling 37 | var __updateTime__ = .0; 38 | #end 39 | 40 | 41 | var activated = false; 42 | 43 | 44 | @:noCompletion public function __activate__() { 45 | onactivate(); 46 | } 47 | 48 | @:noCompletion public function __deactivate__() { 49 | ondeactivate(); 50 | } 51 | 52 | @:noCompletion public function __update__(dt:Float) { 53 | // macro 54 | } 55 | 56 | public function isActive():Bool { 57 | return activated; 58 | } 59 | 60 | public function info(indent = ' ', level = 0):String { 61 | var span = StringTools.rpad('', indent, indent.length * level); 62 | 63 | #if ecs_profiling 64 | return '$span$this : $__updateTime__ ms'; 65 | #else 66 | return '$span$this'; 67 | #end 68 | } 69 | 70 | /** 71 | * Calls when system is added to the workflow 72 | */ 73 | public function onactivate() { } 74 | 75 | /** 76 | * Calls when system is removed from the workflow 77 | */ 78 | public function ondeactivate() { } 79 | 80 | 81 | public function toString():String return 'System'; 82 | 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/ecs/SystemList.hx: -------------------------------------------------------------------------------- 1 | package ecs; 2 | 3 | import ecs.core.ISystem; 4 | 5 | /** 6 | * SystemList 7 | * 8 | * List of Systems. Can be used for better update control: 9 | * ``` 10 | * var physics = new SystemList(); 11 | * physics.add(new MovementSystem()); 12 | * physics.add(new CollisionResolveSystem()); 13 | * Workflow.add(physics); 14 | * ``` 15 | * 16 | * @author https://github.com/deepcake 17 | */ 18 | class SystemList implements ISystem { 19 | 20 | 21 | #if ecs_profiling 22 | var __updateTime__ = .0; 23 | #end 24 | 25 | 26 | var name:String; 27 | 28 | var systems = new Array(); 29 | 30 | var activated = false; 31 | 32 | 33 | public function new(name = 'list') { 34 | this.name = name; 35 | } 36 | 37 | @:noCompletion final public function __activate__() { 38 | if (!activated) { 39 | activated = true; 40 | for (s in systems) { 41 | s.__activate__(); 42 | } 43 | } 44 | } 45 | 46 | @:noCompletion final public function __deactivate__() { 47 | if (activated) { 48 | activated = false; 49 | for (s in systems) { 50 | s.__deactivate__(); 51 | } 52 | } 53 | } 54 | 55 | final public function forceUpdate( dt : Float ) : Void { 56 | __update__(dt); 57 | } 58 | 59 | @:noCompletion final public function __update__(dt:Float) { 60 | #if ecs_profiling 61 | var __timestamp__ = haxe.Timer.stamp(); 62 | #end 63 | 64 | for (s in systems) { 65 | s.__update__(dt); 66 | } 67 | 68 | #if ecs_profiling 69 | __updateTime__ = (haxe.Timer.stamp() - __timestamp__) * 1000.; 70 | #end 71 | } 72 | 73 | public function isActive():Bool { 74 | return activated; 75 | } 76 | 77 | public function info(indent = ' ', level = 0):String { 78 | var span = StringTools.rpad('', indent, indent.length * level); 79 | 80 | var ret = '$span$name'; 81 | 82 | #if ecs_profiling 83 | ret += ' : $__updateTime__ ms'; 84 | #end 85 | 86 | if (systems.length > 0) { 87 | for (s in systems) { 88 | ret += '\n${ s.info(indent, level + 1) }'; 89 | } 90 | } 91 | 92 | return ret; 93 | } 94 | 95 | 96 | public function add(s:ISystem):SystemList { 97 | if (!exists(s)) { 98 | systems.push(s); 99 | if (activated) { 100 | s.__activate__(); 101 | } 102 | } 103 | return this; 104 | } 105 | 106 | public function remove(s:ISystem):SystemList { 107 | if (exists(s)) { 108 | systems.remove(s); 109 | if (activated) { 110 | s.__deactivate__(); 111 | } 112 | } 113 | return this; 114 | } 115 | 116 | public function removeAll():SystemList { 117 | if (activated) { 118 | for (s in systems) 119 | s.__deactivate__(); 120 | } 121 | systems = []; 122 | return this; 123 | } 124 | 125 | public function exists(s:ISystem):Bool { 126 | return systems.contains(s); 127 | } 128 | 129 | @:generic 130 | public function getOp(c : Class) : T { 131 | for (x in systems) { 132 | if (Std.isOfType(x, c)) { 133 | return cast x; 134 | } 135 | } 136 | return null; 137 | } 138 | 139 | public function toString():String return 'SystemList'; 140 | 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/ecs/View.hx: -------------------------------------------------------------------------------- 1 | package ecs; 2 | 3 | /** 4 | * View 5 | * 6 | * @author https://github.com/deepcake 7 | */ 8 | #if !macro 9 | @:genericBuild(ecs.core.macro.ViewBuilder.build()) 10 | #end 11 | class View extends ecs.core.AbstractView { } 12 | -------------------------------------------------------------------------------- /src/ecs/Workflow.hx: -------------------------------------------------------------------------------- 1 | package ecs; 2 | 3 | /** 4 | * Workflow 5 | * 6 | * @author https://github.com/deepcake 7 | * @author https://github.com/onehundredfeet 8 | */ 9 | 10 | #if macro 11 | 12 | using ecs.core.macro.ComponentBuilder; 13 | using ecs.core.macro.ViewsOfComponentBuilder; 14 | using ecs.core.macro.MacroTools; 15 | using haxe.macro.Context; 16 | using haxe.macro.ComplexTypeTools; 17 | using haxe.macro.Context; 18 | using haxe.macro.Expr; 19 | using haxe.macro.TypeTools; 20 | using ecs.core.macro.Extensions; 21 | 22 | #end 23 | 24 | import ecs.Entity.Status; 25 | import ecs.core.AbstractView; 26 | import ecs.core.ICleanableComponentContainer; 27 | import ecs.core.ISystem; 28 | import ecs.utils.FastEntitySet; 29 | import haxe.ds.ReadOnlyArray; 30 | import ecs.core.Parameters; 31 | import ecs.core.Containers; 32 | 33 | class Workflow { 34 | 35 | static inline final TAG_STRIDE : Int = Std.int(Parameters.MAX_TAGS / 32); 36 | @:allow(ecs.Entity) static inline final INVALID_ID = 0; 37 | 38 | static var nextId = INVALID_ID + 1; 39 | 40 | static var idPool = new Array(); 41 | 42 | //Per entity 43 | #if ecs_max_entities 44 | static var statuses = new EntityVector(Parameters.MAX_ENTITIES); 45 | static var tags = new EntityVector(Parameters.MAX_ENTITIES * TAG_STRIDE); 46 | static var _world = new EntityVector(Parameters.MAX_ENTITIES); 47 | static var _generations = new EntityVector(Parameters.MAX_ENTITIES); 48 | #else 49 | static var statuses = new Array(); 50 | static var tags = new Array(); 51 | static var _world = new Array(); 52 | static var _generations = new Array(); 53 | #end 54 | 55 | // all of every defined component container 56 | #if ecs_legacy_containers 57 | static var definedContainers = new Array(); 58 | #end 59 | // all of every defined view 60 | static var definedViews = new Array(); 61 | 62 | static var _singleton:Entity; 63 | 64 | static var _worldSingletons:Array = []; 65 | 66 | public static function singleton() { 67 | if (_singleton.isValid()) { 68 | return _singleton; 69 | } 70 | _singleton = new Entity(); 71 | return _singleton; 72 | } 73 | 74 | /** 75 | * All active entities 76 | */ 77 | public static var entities(get, null) : ReadOnlyFastEntitySet; 78 | static inline function get_entities() { 79 | return _entities; 80 | } 81 | static var _entities(default, null) = new FastEntitySet(); 82 | 83 | /** 84 | * All active views 85 | */ 86 | public static var views(get, null) : ReadOnlyArray; 87 | static inline function get_views() { 88 | return _views; 89 | } 90 | @:allow(ecs.core.AbstractView) static var _views(default, null) = new Array(); 91 | 92 | /** 93 | * All systems that will be called when `update()` is called 94 | */ 95 | public static var systems(get, null) : ReadOnlyArray; 96 | static inline function get_systems() { 97 | return _systems; 98 | } 99 | static var _systems(default, null) = new Array(); 100 | 101 | #if ecs_profiling 102 | static var updateTime = .0; 103 | #end 104 | 105 | /** 106 | * Returns the workflow statistics: 107 | * _( systems count ) { views count } [ entities count | entity cache size ]_ 108 | * With `ecs_profiling` flag additionaly returns: 109 | * _( system name ) : time for update ms_ 110 | * _{ view name } [ collected entities count ]_ 111 | * @return String 112 | */ 113 | public static function info():String { 114 | var ret = '# ( ${systems.length} ) { ${views.length} } [ ${_entities.length} | ${idPool.length} ]'; // TODO version or something 115 | 116 | #if ecs_profiling 117 | ret += ' : $updateTime ms'; // total 118 | for (s in systems) { 119 | ret += '\n${s.info(' ', 1)}'; 120 | } 121 | for (v in views) { 122 | ret += '\n {$v} [${v.entities.length}]'; 123 | } 124 | #end 125 | 126 | return ret; 127 | } 128 | 129 | public static function infoObj() { 130 | return { 131 | systems : systems.length, 132 | views : views.length, 133 | entities : _entities.length, 134 | ids : idPool.length 135 | 136 | } 137 | } 138 | /** 139 | * Update 140 | * @param dt deltatime 141 | */ 142 | public static function update(dt:Float) { 143 | #if ecs_profiling 144 | var timestamp = Date.now().getTime(); 145 | #end 146 | 147 | for (s in systems) { 148 | s.__update__(dt); 149 | } 150 | 151 | #if ecs_profiling 152 | updateTime = Std.int(Date.now().getTime() - timestamp); 153 | #end 154 | } 155 | 156 | /** 157 | * Removes all views, systems and entities from the workflow, and resets the id sequence 158 | */ 159 | public static function reset() { 160 | for (e in _entities) { 161 | e.destroy(); 162 | } 163 | for (s in systems) { 164 | removeSystem(s); 165 | } 166 | for (v in definedViews) { 167 | v.reset(); 168 | } 169 | #if ecs_legacy_containers 170 | for (c in definedContainers) { 171 | c.reset(); 172 | } 173 | #end 174 | 175 | // [RC] why splice and not resize? 176 | idPool.resize(0); 177 | #if !ecs_max_entities 178 | statuses.resize(0); 179 | _world.resize(0); 180 | tags.resize(0); 181 | #end 182 | 183 | nextId = INVALID_ID + 1; 184 | } 185 | 186 | // System 187 | 188 | /** 189 | * Adds the system to the workflow 190 | * @param s `System` instance 191 | */ 192 | public static function addSystem(s:ISystem) { 193 | if (!hasSystem(s)) { 194 | _systems.push(s); 195 | s.__activate__(); 196 | } 197 | } 198 | 199 | /** 200 | * Removes the system from the workflow 201 | * @param s `System` instance 202 | */ 203 | public static function removeSystem(s:ISystem) { 204 | if (hasSystem(s)) { 205 | s.__deactivate__(); 206 | _systems.remove(s); 207 | } 208 | } 209 | 210 | /** 211 | * Returns `true` if the system is added to the workflow, otherwise returns `false` 212 | * @param s `System` instance 213 | * @return `Bool` 214 | */ 215 | public static function hasSystem(s:ISystem):Bool { 216 | return _systems.contains(s); 217 | } 218 | 219 | // Entity 220 | 221 | @:allow(ecs.Entity) static function id(immediate:Bool, world:Int):Int { 222 | var id = idPool.pop(); 223 | 224 | if (id == null) { 225 | id = nextId++; 226 | _generations[id] = 0; 227 | } else { 228 | _generations[id]++; 229 | } 230 | 231 | #if ecs_max_entities 232 | if (id >= Parameters.MAX_ENTITIES) { 233 | throw 'Maximum number of entities reached'; 234 | } 235 | #end 236 | 237 | if (immediate) { 238 | statuses[id] = Active; 239 | _entities.add(id); 240 | } else { 241 | statuses[id] = Inactive; 242 | } 243 | _world[id] = world; 244 | tags[id] = 0; 245 | return id; 246 | } 247 | 248 | public inline static function world(id:Int) { 249 | if (status(id) == Active) { 250 | return _world[id]; 251 | } 252 | return -1; 253 | } 254 | 255 | public inline static function worldEntity(idx:Int) { 256 | if (!_worldSingletons[idx].isValid()) { 257 | _worldSingletons[idx] = new Entity(); 258 | } 259 | 260 | return _worldSingletons[idx]; 261 | } 262 | 263 | /* 264 | macro function getContainer( containerName : String ) { 265 | var containerName = (c.typeof().follow().toComplexType()).getComponentContainer().followName(); 266 | return macro @:privateAccess $i{ containerName }.inst(); 267 | } 268 | 269 | 270 | /** 271 | * Creates a new archetype that makes entities 272 | * @param components comma separated list of components of `Any` type 273 | * @return `Entity` 274 | */ 275 | #if macro 276 | 277 | 278 | /* 279 | static function exprOfClassToTypePath( e : ExprOf>) : TPath { 280 | var x = e.parseClassName().getType().follow().toComplexType(); 281 | trace("tpath: " + x); 282 | return x; 283 | } 284 | */ 285 | // var allocation = components.map(function(c) return {expr: ENew(exprOfClassToTypePath(c)), pos:Context.currentPos()}); 286 | #end 287 | /* 288 | macro public static function addNoViews(self:Expr, components:Array>):ExprOf { 289 | if (components.length == 0) { 290 | Context.error('Required one or more Components', Context.currentPos()); 291 | } 292 | 293 | 294 | var body = [] 295 | .concat( 296 | addComponentsToContainersExprs 297 | ) 298 | 299 | .concat([ 300 | macro return __entity__ 301 | ]); 302 | 303 | var ret = macro #if (haxe_ver >= 4) inline #end ( function(__entity__:ecs.Entity) $b{body} )($self); 304 | 305 | return ret; 306 | } 307 | */ 308 | 309 | macro public static function createFactory(worlds:ExprOf, components:Array>>) { // :ExprOf { 310 | #if macro 311 | var pos = Context.currentPos(); 312 | 313 | if (components.length == 0) { 314 | Context.error('Required one or more Components', Context.currentPos()); 315 | } 316 | 317 | // var pp = new haxe.macro.Printer(); 318 | var classNames = components.map(function(c) return {expr: c.exprOfClassToFullTypeName(null, pos).asTypeIdent(pos).expr, pos: pos}); 319 | var allocation = components.map(function(c) return {expr: ENew(c.exprOfClassToTypePath(null, pos), []), pos:pos}); 320 | 321 | var addComponentsToContainersExprs = components.map((c) -> { 322 | // trace("parsetname|" + c.parseClassName().getType().toComplexType()); 323 | var ct = c.parseClassName().getType().follow().toComplexType(); 324 | var info = ct.getComponentContainerInfo(pos); 325 | 326 | //trace('add and alloc ${c}'); 327 | var alloc = {expr: ENew(ct.toString().asTypePath(), []), pos: Context.currentPos()}; 328 | 329 | return info.getAddExpr(macro __entity__, alloc); 330 | }); 331 | 332 | // trace(pp.printExprs(allocation, "\n")); 333 | 334 | var body = [].concat([macro var _views:Array = []]) 335 | .concat([ 336 | // macro trace("Tracing against " + ecs.Workflow.views.length) 337 | ]) 338 | .concat([ 339 | macro for (v in ecs.Workflow.views) { 340 | if (@:privateAccess v.isMatchedByTypes($worlds, $a{classNames})) { 341 | _views.push(v); 342 | } 343 | } 344 | ]) 345 | .concat([ 346 | macro return function() { 347 | var __entity__ = new ecs.Entity($worlds); 348 | // ecs.Workflow.addNoViews(e, $a{allocation}); 349 | $b{addComponentsToContainersExprs}; 350 | 351 | // trace("adding to views " + _views.length); 352 | for (v in _views) { 353 | // trace("adding to view "); 354 | @:privateAccess v.addMatchedNew(__entity__); 355 | } 356 | return __entity__; 357 | } 358 | ]); 359 | 360 | var ret = macro inline(function() $b{body})(); 361 | 362 | // trace(pp.printExpr(ret)); 363 | return ret; 364 | #else 365 | return macro ""; 366 | #end 367 | 368 | #if false.concat 369 | (addComponentsToContainersExprs) var addComponentsToContainersExprs = components.map(function(c) { 370 | var info = (c.typeof().follow().toComplexType()).getComponentContainerInfo(); 371 | 372 | var containerName = (c.typeof().follow().toComplexType()).getComponentContainer().followName(); 373 | return macro @:privateAccess $i{containerName}.inst(); 374 | }); 375 | 376 | return macro ""; 377 | #end 378 | } 379 | 380 | #if factories 381 | #end 382 | 383 | @:allow(ecs.Entity) static inline function cache(id:Int) { 384 | // Active or Inactive 385 | if (status(id) < Cached) { 386 | removeAllComponentsOf(id); 387 | _entities.remove(id); 388 | 389 | // TODO: somehow we managed to double-add an id to the pool by destroying an entity multiple times... ! 390 | // idPool.remove(id); Need to figure out a better way to do this [RC] 391 | idPool.push(id); 392 | 393 | statuses[id] = Cached; 394 | } 395 | } 396 | 397 | @:allow(ecs.Entity) static inline function add(id:Int) { 398 | if (status(id) == Inactive) { 399 | statuses[id] = Active; 400 | _entities.add(id); 401 | for (v in views) 402 | v.addIfMatched(id); 403 | } 404 | } 405 | 406 | @:allow(ecs.Entity) static inline function remove(id:Int) { 407 | if (status(id) == Active) { 408 | for (v in views) 409 | v.removeIfExists(id); 410 | _entities.remove(id); 411 | statuses[id] = Inactive; 412 | } 413 | } 414 | 415 | @:allow(ecs.Entity) static inline function status(id:Int):Status { 416 | if (id <= Workflow.INVALID_ID) 417 | return Status.Invalid; 418 | return statuses[id]; 419 | } 420 | 421 | @:allow(ecs.Entity) static inline function pauseAdding(id:Int) { 422 | 423 | } 424 | 425 | @:allow(ecs.Entity) static inline function resumeAdding(id:Int) { 426 | 427 | } 428 | 429 | 430 | @:allow(ecs.Entity) static inline function getTag( id:Int, tag: Int) { 431 | final offset = tag >> 5; 432 | final bitOffset = tag - (offset << 5); 433 | final tagField = tags[id * TAG_STRIDE + offset]; 434 | return tagField & (1 << bitOffset) != 0; 435 | } 436 | 437 | 438 | @:allow(ecs.Entity) static inline function setTag( id:Int, tag: Int) { 439 | // trace('Setting tag ${tag} on ${id}'); 440 | final offset = tag >> 5; 441 | final bitOffset = tag - (offset << 5); 442 | final idx = id * TAG_STRIDE + offset; 443 | final tagField = tags[id * TAG_STRIDE + offset]; 444 | tags[id * TAG_STRIDE + offset] = tagField | (1 << bitOffset); 445 | } 446 | 447 | @:allow(ecs.Entity) static inline function clearTag( id:Int, tag: Int) { 448 | final offset = tag >> 5; 449 | final bitOffset = tag - (offset << 5); 450 | final idx = id * TAG_STRIDE + offset; 451 | final tagField = tags[id * TAG_STRIDE + offset]; 452 | tags[id * TAG_STRIDE + offset] = tagField & ~(1 << bitOffset); 453 | } 454 | 455 | static var removeAllFunction : (ecs.Entity) -> Void = null; 456 | 457 | public static dynamic function numComponentTypes() { return 0; } 458 | public static dynamic function componentNames() : Array { 459 | return []; 460 | } 461 | public static dynamic function entityComponentNames(e : ecs.Entity) : Array { 462 | return []; 463 | } 464 | public static dynamic function componentsToStrings(e : ecs.Entity) : Array { 465 | return []; 466 | } 467 | public static dynamic function componentsToDynamic(e : ecs.Entity) : Array { 468 | return []; 469 | } 470 | public static dynamic function componentNameToString(e : ecs.Entity, name : String) : String { 471 | return ""; 472 | } 473 | 474 | macro static function removeAllComponents(e : Expr) : Expr { 475 | return macro { 476 | if (removeAllFunction == null) { 477 | var c = Type.resolveClass("LateCalls"); 478 | if (c == null) throw "Internal ecs Error - no LateCalls class available in reflection. Required compilation macro: --macro ecs.core.macro.Global.setup()"; 479 | var i = Type.createInstance(c,null); 480 | if (i == null) throw "Internal ecs Error - could not instance LateCalls. Required compilation macro: --macro ecs.core.macro.Global.setup()"; 481 | removeAllFunction = i.getRemoveFunc(); 482 | } 483 | removeAllFunction($e); 484 | } 485 | } 486 | 487 | @:allow(ecs.Entity) static inline function removeAllComponentsOf(id:Int) { 488 | if (status(id) == Active) { 489 | for (v in views) { 490 | v.removeIfExists(id); 491 | } 492 | } 493 | #if ecs_legacy_containers 494 | for (c in definedContainers) { 495 | c.remove(id); 496 | } 497 | #else 498 | removeAllComponents(id); 499 | #end 500 | } 501 | 502 | 503 | 504 | @:allow(ecs.Entity) static inline function printAllComponentsOf(id:Int):String { 505 | var ret = '#$id:'; 506 | #if ecs_legacy_containers 507 | for (c in definedContainers) { 508 | if (c.exists(id)) { 509 | ret += '${c.print(id)},'; 510 | } 511 | } 512 | #end 513 | 514 | return ret.substr(0, ret.length - 1); 515 | } 516 | 517 | public inline static function getGeneration(id:Int):Int { 518 | return _generations[id]; 519 | } 520 | } 521 | -------------------------------------------------------------------------------- /src/ecs/core/AbstractView.hx: -------------------------------------------------------------------------------- 1 | package ecs.core; 2 | 3 | import ecs.Entity; 4 | import ecs.utils.FastEntitySet; 5 | /** 6 | * ... 7 | * @author https://github.com/deepcake, https://github.com/onehundredfeet 8 | */ 9 | 10 | 11 | @:ecs_view 12 | @:keepSub 13 | class AbstractView { 14 | static var _idCount = 0; 15 | var _id = 0; 16 | public function new() { 17 | _id = _idCount++; 18 | } 19 | 20 | public function hashCode():Int { 21 | return _id; 22 | } 23 | 24 | public var entities(get,null):ReadOnlyFastEntitySet; 25 | inline function get_entities() { 26 | return _entities; 27 | } 28 | 29 | /** List of matched entities */ 30 | var _entities = new FastEntitySet(); 31 | 32 | // var collected = new Array(); // Membership is already being stored 33 | 34 | var activations = 0; 35 | 36 | 37 | public function activate() { 38 | activations++; 39 | if (activations == 1) { 40 | Workflow._views.push(this); 41 | for (e in Workflow.entities) { 42 | addIfMatched(e); 43 | } 44 | } 45 | } 46 | 47 | public function deactivate() { 48 | activations--; 49 | if (activations == 0) { 50 | Workflow._views.remove(this); 51 | 52 | for (e in _entities) { 53 | dispatchRemovedCallback(e); 54 | } 55 | _entities.removeAll(); 56 | } 57 | } 58 | 59 | public inline function isActive():Bool { 60 | return activations > 0; 61 | } 62 | 63 | 64 | public inline function size():Int { 65 | return _entities.length; 66 | } 67 | 68 | 69 | function isMatched(id:Int):Bool { 70 | // each required component exists in component container with this id 71 | // macro generated 72 | return false; 73 | } 74 | 75 | function isMatchedByTypes(worlds:Int, typeNames : Array):Bool { 76 | // each required component exists in component container with this id 77 | // macro generated 78 | return false; 79 | } 80 | 81 | 82 | function dispatchAddedCallback(id:Int) { 83 | // macro generated 84 | } 85 | 86 | function dispatchRemovedCallback(id:Int) { 87 | // macro generated 88 | } 89 | 90 | 91 | @:allow(ecs.Workflow) function addIfMatched(id:Int) { 92 | // trace(this); 93 | if (isMatched(id) && !_entities.exists(id)) { 94 | // trace('ADDING $id'); 95 | _entities.add(id); 96 | dispatchAddedCallback(id); 97 | } 98 | } 99 | 100 | @:allow(ecs.Workflow) function addIfMatchedNoCheck(id:Int) { 101 | if (isMatched(id)) { 102 | _entities.add(id); 103 | dispatchAddedCallback(id); 104 | } 105 | } 106 | 107 | 108 | @:allow(ecs.Workflow) function addMatchedNew(id:Int) { 109 | _entities.add(id); 110 | dispatchAddedCallback(id); 111 | } 112 | 113 | @:allow(ecs.Workflow) function removeIfExists(id:Int) { 114 | if(_entities.remove(id)) { 115 | dispatchRemovedCallback(id); 116 | } 117 | } 118 | 119 | 120 | @:allow(ecs.Workflow) function reset() { 121 | activations = 0; 122 | Workflow._views.remove(this); 123 | for (e in _entities) { 124 | dispatchRemovedCallback(e); 125 | } 126 | _entities.removeAll(); 127 | // collected.splice(0, collected.length); 128 | } 129 | 130 | 131 | public function toString():String { 132 | return 'AbstractView'; 133 | } 134 | 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/ecs/core/Containers.hx: -------------------------------------------------------------------------------- 1 | package ecs.core; 2 | 3 | 4 | #if ecs_max_entities 5 | #if hl 6 | @:generic 7 | typedef EntityVector = hl.NativeArray; 8 | #else 9 | @:generic 10 | typedef EntityVector = haxe.ds.Vector; 11 | #end 12 | #end 13 | -------------------------------------------------------------------------------- /src/ecs/core/ICleanableComponentContainer.hx: -------------------------------------------------------------------------------- 1 | package ecs.core; 2 | 3 | interface ICleanableComponentContainer { 4 | function exists(id:Int):Bool; 5 | function remove(id:Int):Void; 6 | function reset():Void; 7 | function print(id:Int):String; 8 | } 9 | -------------------------------------------------------------------------------- /src/ecs/core/IPoolable.hx: -------------------------------------------------------------------------------- 1 | package ecs.core; 2 | 3 | #if !macro 4 | @:autoBuild(ecs.core.macro.PoolBuilder.arrayPool()) 5 | #end 6 | interface IPoolable { 7 | 8 | } -------------------------------------------------------------------------------- /src/ecs/core/ISystem.hx: -------------------------------------------------------------------------------- 1 | package ecs.core; 2 | 3 | interface ISystem { 4 | 5 | 6 | function __activate__():Void; 7 | 8 | function __deactivate__():Void; 9 | 10 | function __update__(dt:Float):Void; 11 | 12 | 13 | function isActive():Bool; 14 | 15 | function info(indent:String = ' ', level:Int = 0):String; 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/ecs/core/Parameters.hx: -------------------------------------------------------------------------------- 1 | package ecs.core; 2 | 3 | class Parameters { 4 | #if ecs_max_entities 5 | static macro function getMaxEntitiesExpr() { 6 | var x= Std.parseInt(haxe.macro.Context.definedValue("ecs_max_entities")); 7 | 8 | return macro $v{x}; 9 | } 10 | 11 | 12 | static inline function getMaxEntities() : Int { 13 | #if macro 14 | return 0; 15 | #else 16 | return getMaxEntitiesExpr(); 17 | #end 18 | } 19 | 20 | 21 | public static inline final MAX_ENTITIES = getMaxEntities(); 22 | #end 23 | 24 | 25 | #if ecs_max_flags 26 | static macro function getMaxFlagsExpr() { 27 | var x= Std.parseInt(haxe.macro.Context.definedValue("ecs_max_flags")); 28 | 29 | return macro $v{x}; 30 | } 31 | #end 32 | 33 | static inline function getMaxFlags() { 34 | #if ecs_max_flags 35 | return getMaxFlagsExpr(); 36 | #else 37 | return 32; 38 | #end 39 | } 40 | 41 | public static inline final MAX_TAGS = getMaxFlags(); 42 | 43 | } -------------------------------------------------------------------------------- /src/ecs/core/RestrictedLinkedList.hx: -------------------------------------------------------------------------------- 1 | package ecs.core; 2 | 3 | #if ECS_USE_LINKED_LIST 4 | @:allow(ecs) 5 | @:forward(head, tail, length, iterator, sort) 6 | abstract RestrictedLinkedList(ecs.utils.LinkedList) to ecs.utils.LinkedList { 7 | 8 | inline function new() this = new ecs.utils.LinkedList(); 9 | 10 | inline function add(item:T) this.add(item); 11 | inline function pop() return this.pop(); 12 | inline function remove(item:T) return this.remove(item); 13 | inline function exists(item:T) return this.exists(item); 14 | 15 | } 16 | 17 | #end -------------------------------------------------------------------------------- /src/ecs/core/macro/ComponentBuilder.hx: -------------------------------------------------------------------------------- 1 | package ecs.core.macro; 2 | 3 | import ecs.utils.Const; 4 | import ecs.core.Containers; 5 | 6 | #if macro 7 | import ecs.core.macro.MacroTools.*; 8 | import haxe.macro.Type; 9 | import haxe.macro.Printer; 10 | import haxe.macro.Expr; 11 | #if (haxe_ver >= 5.0) 12 | import haxe.macro.Compiler; 13 | #else 14 | import haxe.display.Display; 15 | #end 16 | 17 | using ecs.core.macro.MacroTools; 18 | using Lambda; 19 | 20 | using haxe.macro.ComplexTypeTools; 21 | using haxe.macro.TypeTools; 22 | using haxe.macro.Context; 23 | using haxe.ds.ArraySort; 24 | using ecs.core.macro.Extensions; 25 | 26 | typedef MetaMap = haxe.ds.Map>>; 27 | 28 | function finiteStorage() : Bool { 29 | #if ecs_max_entities 30 | return true; 31 | #else 32 | return false; 33 | #end 34 | } 35 | function maxEntities() : Int{ 36 | #if ecs_max_entities 37 | return Std.parseInt(haxe.macro.Context.definedValue("ecs_max_entities")); 38 | #else 39 | return -1; 40 | #end 41 | } 42 | 43 | enum abstract StorageType(Int) from Int to Int { 44 | var FAST = 0; // An array the length of all entities, with non-null meaning membership 45 | var COMPACT = 1; // A map from entity to members 46 | var SINGLETON = 2; // A single reference with a known entity owner 47 | var TAG = 3; // An bitfield the length of all entities, with ON | OFF meaning membership 48 | var FLAT = 4; // A pre-allocated array with values for all entities 49 | 50 | // var GLOBAL = 4; // Exists on every entity 51 | // var TRANSIENT = 5; // Automatically removed every tick 52 | // var NONE = 6; // This class is not allowed to be used as a component 53 | public static function getStorageType(mm:MetaMap, t : haxe.macro.Type) { 54 | var storageType = StorageType.FAST; 55 | 56 | var stma = mm.get(":storage"); 57 | 58 | if (stma != null) { 59 | var stm = stma[0]; 60 | if (stm[0] == null) { 61 | Context.warning('Storage specification on type ${t.toString()} is empty, using FAST', Context.currentPos()); 62 | return FAST; 63 | } 64 | return switch (stm[0].expr) { 65 | case EConst(CIdent(s)), EConst(CString(s)): 66 | switch (s.toUpperCase()) { 67 | case "FAST": FAST; 68 | case "COMPACT": 69 | #if ecs_compact_is_array 70 | FAST; 71 | #else 72 | COMPACT; 73 | #end 74 | case "SINGLETON": SINGLETON; 75 | case "TAG": TAG; 76 | case "FLAT": 77 | #if ecs_max_entities 78 | FLAT; 79 | #else 80 | FAST; 81 | #end 82 | default: 83 | Context.warning('Unknown storage type ${s}', Context.currentPos()); 84 | FAST; 85 | } 86 | default: FAST; 87 | } 88 | } 89 | return FAST; 90 | } 91 | } 92 | 93 | var _printer = new Printer(); 94 | @:persistent var createdModule = false; 95 | final modulePrefix = "__ecs__storage"; 96 | @:persistent var tagMap = new Map(); 97 | @:persistent var tagCount = 0; 98 | 99 | function getModulePath():String { 100 | if (!createdModule) { 101 | Context.defineModule(modulePrefix, []); 102 | } 103 | return modulePrefix; 104 | } 105 | 106 | class StorageInfo { 107 | public static final STORAGE_NAMESPACE = "ecs.storage"; 108 | 109 | static function getPooled(mm:MetaMap) { 110 | var bb = mm.get(":build"); 111 | 112 | if (bb != null) { 113 | for (b in bb) { 114 | switch (b[0].expr) { 115 | case ECall(e, p): 116 | switch (e.expr) { 117 | case EField(fe, field): 118 | if (fe.toString() == "ecs.core.macro.PoolBuilder") { 119 | return true; 120 | } 121 | default: 122 | } 123 | default: 124 | } 125 | } 126 | } 127 | return false; 128 | } 129 | 130 | public function new(ct:ComplexType, i:Int, pos) { 131 | // derived from parameters 132 | givenCT = ct; 133 | followedCT = ct.followComplexType(pos); 134 | name = followedCT.toString(); 135 | fullName = followedCT.followComplexType(pos).typeFullName(pos); 136 | componentIndex = i; 137 | 138 | // Derived from the type 139 | var followedT = getMacroType(followedCT); 140 | followedClass = getFollowedClass(followedT); 141 | var rt = followedT.followWithAbstracts(); 142 | 143 | emptyExpr = switch (rt) { 144 | case TInst(t, params): macro null; 145 | case TAbstract(t, params): 146 | switch(t.get().name) { 147 | case "Int": macro cast 0; 148 | case "I64" : macro haxe.Int64.make(0,0); 149 | case "Bytes": macro null; 150 | default: 151 | trace('Unknown abstract ${t.get().name}'); 152 | macro null; 153 | } 154 | 155 | case TAnonymous(t): 156 | macro null; 157 | default: 158 | macro null; 159 | } 160 | 161 | // dervied from the meta 162 | updateMeta( followedT ); 163 | updateContainer(); 164 | } 165 | 166 | function tagExpr() : Expr { 167 | if (!tagMap.exists(fullName)) { 168 | tagMap.set(fullName, tagCount++); 169 | } 170 | 171 | return EConst(CInt(Std.string(tagMap.get(fullName)))).at(); 172 | } 173 | function clearTagExpr(entityVarExpr : Expr ) : Expr { 174 | var te = tagExpr(); 175 | return macro @:privateAccess ecs.Workflow.clearTag($entityVarExpr, $te); 176 | } 177 | 178 | function setTagExpr(entityVarExpr : Expr ) : Expr { 179 | var te = tagExpr(); 180 | return macro @:privateAccess ecs.Workflow.setTag($entityVarExpr, $te); 181 | } 182 | 183 | public function getGetExprCached(entityExpr:Expr, cachedVarName:String):Expr { 184 | return switch (storageType) { 185 | case FAST: macro $i{cachedVarName}[$entityExpr]; 186 | case FLAT: macro $i{cachedVarName}[$entityExpr]; 187 | case COMPACT: macro $i{cachedVarName}.get($entityExpr); 188 | case SINGLETON: macro $i{cachedVarName}; 189 | case TAG: macro @:privateAccess $i{cachedVarName}; 190 | }; 191 | } 192 | 193 | public function getGetExpr(entityExpr:Expr, sure:Bool = false):Expr { 194 | return switch (storageType) { 195 | case FAST: macro $containerFullNameExpr.storage[$entityExpr]; 196 | case FLAT: macro $containerFullNameExpr.storage[$entityExpr]; 197 | case COMPACT: macro $containerFullNameExpr.storage.get($entityExpr); 198 | case SINGLETON: macro $containerFullNameExpr.storage; 199 | case TAG: var te = tagExpr(); 200 | sure ? 201 | macro $containerFullNameExpr.storage : 202 | macro @:privateAccess ecs.Workflow.getTag($entityExpr, $te) ? $containerFullNameExpr.storage : null; 203 | }; 204 | } 205 | 206 | public function getExistsExpr(entityVar:Expr):Expr { 207 | return switch (storageType) { 208 | case FLAT: 209 | macro $containerFullNameExpr._existsStorage[$entityVar]; 210 | case FAST: 211 | isValueStruct ? 212 | macro $containerFullNameExpr._existsStorage[$entityVar] 213 | : 214 | macro $containerFullNameExpr.storage[$entityVar] != $emptyExpr; 215 | case COMPACT: macro $containerFullNameExpr.storage.exists($entityVar); 216 | case SINGLETON: macro $containerFullNameExpr.owner == $entityVar; 217 | case TAG: 218 | var te = tagExpr(); 219 | macro @:privateAccess ecs.Workflow.getTag($entityVar, $te); 220 | }; 221 | } 222 | 223 | public function getCacheExpr(cacheVarName:String):Expr { 224 | return cacheVarName.define(macro $containerFullNameExpr.storage); 225 | } 226 | 227 | public function getAddExpr(entityVarExpr:Expr, componentExpr:Expr):Expr { 228 | return switch (storageType) { 229 | case FLAT: 230 | macro { 231 | $containerFullNameExpr.storage[$entityVarExpr].copy($componentExpr); 232 | $containerFullNameExpr._existsStorage[$entityVarExpr] = true; 233 | } 234 | case FAST: 235 | isValueStruct ? 236 | macro { 237 | $containerFullNameExpr._existsStorage[$entityVarExpr] = true; 238 | $containerFullNameExpr.storage[$entityVarExpr] = $componentExpr; 239 | } 240 | : 241 | macro $containerFullNameExpr.storage[$entityVarExpr] = $componentExpr; 242 | 243 | case COMPACT: macro $containerFullNameExpr.storage.set($entityVarExpr, $componentExpr); 244 | case SINGLETON: macro { 245 | if ($containerFullNameExpr.owner != 0) 246 | throw 'Singleton already has an owner'; 247 | $containerFullNameExpr.storage = $componentExpr; 248 | $containerFullNameExpr.owner = $entityVarExpr; 249 | }; 250 | case TAG:var te = tagExpr(); 251 | macro @:privateAccess ecs.Workflow.setTag($entityVarExpr, $te); 252 | }; 253 | } 254 | 255 | public function getRemoveExpr(entityVarExpr:Expr):Expr { 256 | if (storageType == TAG) { 257 | return clearTagExpr(entityVarExpr); 258 | } 259 | 260 | try { 261 | var retireExprs = getRetireExpr(entityVarExpr); 262 | return storageRemovePreambleExpr(entityVarExpr, retireExprs); 263 | } catch (e) { 264 | Context.fatalError('Error getting retire expr for ${entityVarExpr}', Context.currentPos()); 265 | } 266 | return null; 267 | } 268 | 269 | public function getRetireExpr(entityVarExpr:Expr):Array { 270 | var accessExpr = switch (storageType) { 271 | case FLAT: macro @:privateAccess $containerFullNameExpr.storage[$entityVarExpr]; 272 | case FAST: macro @:privateAccess $containerFullNameExpr.storage[$entityVarExpr]; 273 | case COMPACT: macro @:privateAccess $containerFullNameExpr.storage.get($entityVarExpr); 274 | case SINGLETON: macro($containerFullNameExpr.owner == $entityVarExpr ? $containerFullNameExpr.storage : null); 275 | case TAG: var te = tagExpr(); 276 | @:privateAccess macro ecs.Workflow.getTag($te, $te); 277 | }; 278 | 279 | var retireExprs = new Array(); 280 | var autoRetire = isPooled && !followedMeta.exists(":no_auto_retire"); 281 | if (autoRetire) { 282 | retireExprs.push(macro $accessExpr.retire()); 283 | } 284 | 285 | if (followedClass != null) { 286 | var cfs_statics = followedClass.statics.get().map((x) -> {cf: x, stat: true}); 287 | var cfs_non_statics = followedClass.fields.get().map((x) -> {cf: x, stat: false}); 288 | 289 | var cfs = cfs_statics.concat(cfs_non_statics); 290 | 291 | for (cfx in cfs) { 292 | var cf = cfx.cf; 293 | if (cf.meta.has(":ecs_remove")) { 294 | switch (cf.kind) { 295 | case FMethod(k): 296 | var te = cf.expr(); 297 | switch (te.expr) { 298 | case TFunction(tfunc): 299 | var fname = cf.name; 300 | var needsEntity = false; 301 | for (a in tfunc.args) { 302 | if (a.v.t.toComplexType().toString() == (macro:ecs.Entity).toString()) { 303 | needsEntity = true; 304 | break; 305 | } 306 | } 307 | if (needsEntity) { 308 | retireExprs.push(macro @:privateAccess $accessExpr.$fname($entityVarExpr)); 309 | } else { 310 | // trace('removing without entity ${cf.name} | ${tfunc.args.length} | ${ cfx.stat} in ${followedClass.name}'); 311 | retireExprs.push(macro @:privateAccess $accessExpr.$fname()); 312 | } 313 | default: 314 | } 315 | default: 316 | } 317 | } 318 | } 319 | } 320 | return retireExprs; 321 | } 322 | 323 | function storageRemoveExpr(entityVarExpr:Expr):Expr { 324 | return switch (storageType) { 325 | case FLAT, FAST, COMPACT, SINGLETON: macro @:privateAccess $containerFullNameExpr.remove($entityVarExpr); 326 | case TAG: clearTagExpr(entityVarExpr); 327 | } 328 | /* 329 | var hasExpr = getExistsExpr(entityVarExpr); 330 | return switch (storageType) { 331 | case FAST: macro @:privateAccess $containerFullNameExpr.storage[$entityVarExpr] = $emptyExpr; 332 | case COMPACT: macro @:privateAccess $containerFullNameExpr.storage.remove($entityVarExpr); 333 | case SINGLETON: macro { 334 | $containerFullNameExpr.storage = $emptyExpr; 335 | $containerFullNameExpr.owner = 0; 336 | } 337 | case TAG: 338 | var te = tagExpr(); 339 | @:privateAccess macro ecs.Workflow.clearTag($te, $te); 340 | };*/ 341 | } 342 | 343 | function storageRemovePreambleExpr(entityVarExpr:Expr, preamble:Array):Expr { 344 | var hasExpr = getExistsExpr(entityVarExpr); 345 | var removeExpr = storageRemoveExpr(entityVarExpr); 346 | return macro if ($hasExpr) { $b{preamble} $removeExpr; }; 347 | } 348 | 349 | public function getShelveExpr(entityVarExpr:Expr, pos:Position):Expr { 350 | var shelfCall = switch (storageType) { 351 | case FLAT, FAST, COMPACT, SINGLETON: macro @:privateAccess $containerFullNameExpr.shelve($entityVarExpr); 352 | case TAG: Context.fatalError("Cannot shelve a tag",pos); 353 | } 354 | 355 | shelfCall.pos = pos; 356 | return shelfCall; 357 | } 358 | 359 | public function getUnshelveExpr(entityVarExpr:Expr, pos:Position):Expr { 360 | return switch (storageType) { 361 | case FLAT, FAST, COMPACT, SINGLETON: macro @:privateAccess $containerFullNameExpr.unshelve($entityVarExpr); 362 | case TAG: Context.fatalError("Cannot unshelve a tag",pos); 363 | } 364 | } 365 | 366 | function getMacroType(ct : ComplexType) { 367 | var followedT = ct.toTypeOrNull(Context.currentPos()); 368 | if (followedT == null) { 369 | Context.error('Could not find type for ${ct}', Context.currentPos()); 370 | } 371 | return followedT; 372 | } 373 | function getFollowedClass(t : haxe.macro.Type) { 374 | var fc = null; 375 | try { 376 | fc = t.getClass(); 377 | } catch (e) { 378 | switch (t) { 379 | case TAbstract(at, params): 380 | var x = at.get().impl; 381 | if (x != null) { 382 | fc = x.get(); 383 | } else { 384 | Context.fatalError('abstract not implemented ${at} ${followedCT.toString()}', Context.currentPos()); 385 | } 386 | default: 387 | Context.fatalError('Couldn\'t find class ${followedCT.toString()}', Context.currentPos()); 388 | } 389 | } 390 | catch(s : String) { 391 | Context.fatalError('String Couldn\'t find class ${followedCT.toString()}', Context.currentPos()); 392 | } 393 | catch(d : Dynamic) { 394 | Context.fatalError('Dynamic Couldn\'t find class ${followedCT.toString()}', Context.currentPos()); 395 | } 396 | return fc; 397 | } 398 | 399 | public function update() { 400 | var t = getMacroType(followedCT); 401 | this.followedClass = getFollowedClass(t); 402 | updateMeta(t); 403 | updateContainer(); 404 | } 405 | 406 | function getTypeMetaMap(t : haxe.macro.Type) { 407 | return t.getMeta().flatMap((x) -> x.get()).toMap(); 408 | } 409 | 410 | 411 | 412 | function updateMeta(t : haxe.macro.Type) { 413 | // dervied from the meta 414 | followedMeta = getTypeMetaMap( t ); 415 | storageType = StorageType.getStorageType(followedMeta, t); 416 | // trace('ECS: ${fullName} is ${structValues}'); 417 | 418 | // isPooled = getPooled(followedMeta); 419 | isPooled = false; 420 | isImmutable = followedMeta.exists(":immutable"); 421 | var structValues = t.gatherMetaValueFromHierarchy(":struct"); 422 | var platform= haxe.macro.Compiler.getConfiguration().platform; 423 | 424 | #if (haxe_ver >= 5.0) 425 | var isStructPlatform = platform == Platform.Cpp; 426 | #else 427 | var isStructPlatform = platform == Platform.Cs || platform == Platform.Cpp; 428 | #end 429 | isValueStruct = isStructPlatform && structValues.length > 0; 430 | } 431 | 432 | function updateContainer() { 433 | #if (hl_ver >= version("1.14.0")) 434 | var platform = haxe.macro.Compiler.getConfiguration().platform; 435 | var isHL = platform == Platform.Hl; 436 | #end 437 | var tp = (switch (storageType) { 438 | #if ecs_max_entities 439 | case FAST: tpath(["ecs", "core"], "EntityVector", [TPType(followedCT)]); 440 | #else 441 | case FAST: tpath([], "Array", [TPType(followedCT)]); 442 | #end 443 | case FLAT: 444 | #if (hl_ver >= version("1.14.0")) 445 | if (!isHL) 446 | throw "Flat is unsupported outside HL"; 447 | tpath(["hl"], "CArray", [TPType(followedCT)]); 448 | #else 449 | throw "Flat is unsupported outside HL 1.14+"; 450 | #end 451 | case COMPACT: tpath(["haxe", "ds"], "IntMap", [TPType(followedCT)]); 452 | case TAG: followedCT.toString().asTypePath(); 453 | case SINGLETON: followedCT.toString().asTypePath(); 454 | default: null; 455 | }); 456 | 457 | if (tp != null) { 458 | storageCT = TPath(tp); 459 | 460 | containerTypeName = 'StorageOf' + fullName; 461 | containerFullName = STORAGE_NAMESPACE + "." + containerTypeName; 462 | 463 | containerFullNameExpr = containerFullName.asTypeIdent(Context.currentPos()); 464 | 465 | // Context.registerModuleDependency() 466 | containerCT = containerFullName.asComplexType(); 467 | var containerType = containerCT.toTypeOrNull(Context.currentPos()); 468 | 469 | if (containerType == null) { 470 | var existsExpr = getExistsExpr(macro id); 471 | var removeExpr = getRemoveExpr(macro id); 472 | 473 | var def = 474 | switch(storageType) { 475 | case TAG: macro class $containerTypeName { 476 | public static var storage:$storageCT = @:privateAccess new $tp(); 477 | 478 | } 479 | case SINGLETON: macro class $containerTypeName { 480 | public static var storage:$storageCT; 481 | public static var owner:Int = 0; 482 | public static var _shelved :$storageCT = $emptyExpr; 483 | public static inline function shelved(id:Int) { 484 | return _shelved != $emptyExpr; 485 | } 486 | public static inline function exists(id:Int) { 487 | return storage != $emptyExpr; 488 | } 489 | public inline static function shelve(id:Int) { 490 | _shelved = storage; 491 | storage = $emptyExpr; 492 | } 493 | public inline static function unshelve(id:Int) : $followedCT { 494 | storage = _shelved; 495 | _shelved = $emptyExpr; 496 | return _shelved; 497 | } 498 | public inline static function remove(id:Int) { 499 | storage = $emptyExpr; 500 | _shelved = $emptyExpr; 501 | owner = 0; 502 | } 503 | } 504 | case FLAT: 505 | var existsMarkTrueExpr = macro (_existsStorage[id] = true); 506 | var existsMarkFalseExpr = macro (_existsStorage[id] = false) ; 507 | 508 | // var x = macro var x : Class; 509 | // switch(x.expr) { 510 | // case EVars(vars): 511 | // for (v in vars) { 512 | // trace(v.name); 513 | // trace(v.type); 514 | // } 515 | // default: 516 | // } 517 | var x = name.split("."); 518 | 519 | macro class $containerTypeName { 520 | public static var storage = hl.CArray.alloc($p{x}, ecs.core.Parameters.MAX_ENTITIES); 521 | public static var _shelved = new Map(); 522 | public static var _existsStorage = new ecs.core.Containers.EntityVector(ecs.core.Parameters.MAX_ENTITIES) ; 523 | 524 | public inline function exists(id:Int) { 525 | return _existsStorage[id]; 526 | } 527 | public inline static function shelved(id:Int) { 528 | return _shelved.exists(id); 529 | } 530 | public inline static function shelve(id:Int) { 531 | _shelved.set(id, storage[id]); 532 | $existsMarkFalseExpr; 533 | } 534 | public inline static function unshelve(id:Int) : $followedCT{ 535 | var x = _shelved.get(id); 536 | storage[id].copy(x); 537 | _shelved.remove(id); 538 | $existsMarkTrueExpr; 539 | return x; 540 | } 541 | public inline static function remove(id) { 542 | $existsMarkFalseExpr; 543 | } 544 | public inline static function add(id, item : $followedCT) { 545 | // need to do a memberwise copy 546 | storage[id].copy(item); 547 | $existsMarkTrueExpr; 548 | } 549 | } 550 | case FAST: 551 | #if ecs_max_entities 552 | var existsStorage = isValueStruct ? macro new ecs.core.Containers.EntityVector(ecs.core.Parameters.MAX_ENTITIES) : macro null; 553 | #else 554 | var existsStorage = isValueStruct ? macro new Array() : macro null; 555 | #end 556 | var existsStorageExpr = isValueStruct ? macro (_existsStorage[id]) : macro (storage[id] != $emptyExpr); 557 | var existsMarkTrueExpr = isValueStruct ? macro (_existsStorage[id] = true) : macro null; 558 | var existsMarkFalseExpr = isValueStruct ? macro (_existsStorage[id] = false) : macro null; 559 | 560 | macro class $containerTypeName { 561 | #if ecs_max_entities 562 | public static var storage = new ecs.core.Containers.EntityVector<$followedCT>(ecs.core.Parameters.MAX_ENTITIES); 563 | #else 564 | public static var storage = new Array<$followedCT>(); 565 | #end 566 | public static var _shelved = new Map(); 567 | public static var _existsStorage = $existsStorage; 568 | 569 | public inline function exists(id:Int) { 570 | //return storage[id] != $emptyExpr; 571 | return $existsStorageExpr; 572 | } 573 | public inline static function shelved(id:Int) { 574 | return _shelved.exists(id); 575 | } 576 | public inline static function shelve(id:Int) { 577 | _shelved.set(id, storage[id]); 578 | storage[id] = $emptyExpr; 579 | $existsMarkFalseExpr; 580 | } 581 | public inline static function unshelve(id:Int) : $followedCT{ 582 | var x = _shelved.get(id); 583 | storage[id] = x; 584 | _shelved.remove(id); 585 | $existsMarkTrueExpr; 586 | return x; 587 | } 588 | public inline static function remove(id) { 589 | storage[id] = $emptyExpr; 590 | $existsMarkFalseExpr; 591 | } 592 | public inline static function add(id, item : $followedCT) { 593 | storage[id] = item; 594 | $existsMarkTrueExpr; 595 | } 596 | } 597 | case COMPACT: 598 | macro class $containerTypeName { 599 | //public static var storage:$storageCT = @:privateAccess new $tp(); 600 | public static var storage = new Map(); 601 | public static var _shelved = new Map(); 602 | 603 | public inline function exists(id:Int) { 604 | return storage.exists(id); 605 | } 606 | public inline static function shelved(id:Int) { 607 | return _shelved.exists(id); 608 | } 609 | public inline static function shelve(id:Int) { 610 | // This will fail if nothing is shelved 611 | var x = storage.get(id); 612 | _shelved.set(id, x); 613 | storage.remove(id); 614 | } 615 | public inline static function unshelve(id:Int) : $followedCT{ 616 | var x = _shelved.get(id); 617 | storage.set(id, x); 618 | _shelved.remove(id); 619 | return x; 620 | } 621 | public inline static function remove(id) { 622 | storage.remove(id); 623 | } 624 | public inline static function add(id, item : $followedCT) { 625 | storage.set(id,item); 626 | } 627 | } 628 | } 629 | 630 | //trace(_printer.printTypeDefinition(def)); 631 | def.defineTypeSafe(STORAGE_NAMESPACE, Const.ROOT_MODULE); 632 | } 633 | 634 | if (containerType == null) { 635 | containerType = containerCT.toTypeOrNull(Context.currentPos()); 636 | } 637 | } else { 638 | Context.fatalError('Could not find storage type for ${followedCT.toString()}', Context.currentPos()); 639 | containerCT = null; 640 | containerTypeName = null; 641 | containerFullName = null; 642 | containerFullNameExpr = null; 643 | } 644 | } 645 | 646 | public var name:String; 647 | public var givenCT:ComplexType; 648 | public var followedCT:ComplexType; 649 | public var followedMeta:MetaMap; 650 | public var followedClass:ClassType; 651 | public var storageType:StorageType; 652 | public var storageCT:ComplexType; 653 | public var componentIndex:Int; 654 | public var fullName:String; 655 | public var containerCT:ComplexType; 656 | public var containerTypeName:String; 657 | public var containerFullName:String; 658 | public var containerFullNameExpr:Expr; 659 | public var emptyExpr:Expr; 660 | public var isPooled:Bool; 661 | public var isImmutable : Bool; 662 | public var isValueStruct : Bool; 663 | } 664 | 665 | class ComponentBuilder { 666 | static var componentIndex = -1; 667 | @:persistent static var componentContainerTypeCache = new Map(); 668 | static var currentComponentContainerTypeCache = new Map(); 669 | 670 | public static function componentTypeNames() { 671 | return componentContainerTypeCache.keys(); 672 | } 673 | 674 | public static function containerInfo(s:String) { 675 | return componentContainerTypeCache[s]; 676 | } 677 | 678 | public static function getComponentContainerInfo(componentComplexType:ComplexType, pos):StorageInfo { 679 | if (componentComplexType == null) { 680 | Context.fatalError('componentComplexType type is null', pos); 681 | } 682 | var name = componentComplexType.followName(pos); 683 | 684 | var info = currentComponentContainerTypeCache.get(name); 685 | if (info != null) { 686 | return info; 687 | } 688 | 689 | info = componentContainerTypeCache.get(name); 690 | if (info != null) { 691 | #if ecs_late_debug 692 | trace('ECS: Updating type info on ${name}'); 693 | #end 694 | info.update(); 695 | currentComponentContainerTypeCache.set(name, info); 696 | // trace('Re-using type ${name}'); 697 | return info; 698 | } 699 | 700 | try { 701 | info = new StorageInfo(componentComplexType, ++componentIndex, pos); 702 | } catch(e ) { 703 | Context.fatalError('Error generating storage info for ${name} : ${e}', pos); 704 | } 705 | componentContainerTypeCache[name] = info; 706 | currentComponentContainerTypeCache.set(name, info); 707 | 708 | return info; 709 | } 710 | 711 | public static function getLookup(ct:ComplexType, entityVarName:Expr, pos):Expr { 712 | return getComponentContainerInfo(ct, pos).getGetExpr(entityVarName); 713 | } 714 | 715 | public static function getComponentId(componentComplexType:ComplexType, pos):Int { 716 | return getComponentContainerInfo(componentComplexType, pos).componentIndex; 717 | } 718 | } 719 | #end 720 | -------------------------------------------------------------------------------- /src/ecs/core/macro/Extensions.hx: -------------------------------------------------------------------------------- 1 | package ecs.core.macro; 2 | 3 | // Tiny selection of tink_macro to avoid dependency 4 | // https://github.com/haxetink/tink_macro 5 | #if macro 6 | import haxe.macro.Expr; 7 | import haxe.macro.MacroStringTools; 8 | import haxe.macro.Context; 9 | import haxe.macro.Printer; 10 | import haxe.macro.Type; 11 | import haxe.macro.TypeTools; 12 | 13 | class Extensions { 14 | static var _printer:Printer; 15 | 16 | inline static function printer():Printer { 17 | if (_printer == null) 18 | _printer = new Printer(); 19 | return _printer; 20 | } 21 | 22 | static public function toString(t:ComplexType) 23 | return printer().printComplexType(t); 24 | 25 | static public inline function sanitize(pos:Position) 26 | return if (pos == null) Context.currentPos(); else pos; 27 | 28 | static public inline function at(e:ExprDef, ?pos:Position) 29 | return { 30 | expr: e, 31 | pos: sanitize(pos) 32 | }; 33 | 34 | static public function asTypePath(s:String, ?params):TypePath { 35 | var parts = s.split('.'); 36 | var name = parts.pop(), sub = null; 37 | if (parts.length > 0 && parts[parts.length - 1].charCodeAt(0) < 0x5B) { 38 | sub = name; 39 | name = parts.pop(); 40 | if (sub == name) 41 | sub = null; 42 | } 43 | return { 44 | name: name, 45 | pack: parts, 46 | params: params == null ? [] : params, 47 | sub: sub 48 | }; 49 | } 50 | 51 | static public inline function asComplexType(s:String, ?params) 52 | return TPath(asTypePath(s, params)); 53 | 54 | static public inline function toMBlock(exprs:Array, ?pos) 55 | return at(EBlock(exprs), pos); 56 | 57 | static public inline function toBlock(exprs:Iterable, ?pos) 58 | return toMBlock(Lambda.array(exprs), pos); 59 | 60 | static public inline function binOp(e1:Expr, e2, op, ?pos) 61 | return at(EBinop(op, e1, e2), pos); 62 | 63 | static public inline function assign(target:Expr, value:Expr, ?op:Binop, ?pos:Position) 64 | return binOp(target, value, op == null ? OpAssign : OpAssignOp(op), pos); 65 | 66 | static public inline function field(x:Expr, member:String, ?pos):Expr { 67 | return at(EField(x, member), pos); 68 | } 69 | 70 | static public function isVar(field:ClassField) 71 | return switch (field.kind) { 72 | case FVar(_, _): true; 73 | default: false; 74 | } 75 | 76 | static public function getString(e:Expr) 77 | return switch (e.expr) { 78 | case EConst(c): 79 | switch (c) { 80 | case CString(string): string; 81 | default: null; 82 | } 83 | default: null; 84 | } 85 | 86 | static public inline function define(name:String, ?init:Expr, ?typ:ComplexType, ?pos:Position) 87 | return at(EVars([{name: name, type: typ, expr: init}]), pos); 88 | 89 | static public function getMeta(type:Type) 90 | return switch type { 91 | case TInst(_.get().meta => m, _): [m]; 92 | case TEnum(_.get().meta => m, _): [m]; 93 | case TAbstract(_.get().meta => m, _): [m]; 94 | case TType(_.get() => t, _): [t.meta].concat(getMeta(t.type)); 95 | case TLazy(f): getMeta(f()); 96 | default: []; 97 | } 98 | 99 | public static function isSameAbstractType( a : AbstractType, b : AbstractType ) { 100 | if (a == null || b == null) return false; 101 | if (a == b) return true; 102 | 103 | if (a.name != b.name) return false; 104 | if (a.module != b.module) return false; 105 | if (a.params.length != b.params.length) return false; 106 | 107 | for (i in 0...a.params.length) { 108 | if (a.params[i].name != b.params[i].name) return false; 109 | } 110 | 111 | return true; 112 | } 113 | 114 | public static function gatherMetaValueFromHierarchy(t:haxe.macro.Type, key:String):Array { 115 | var metaValue :Array = null; 116 | 117 | switch(t) { 118 | case TInst(cl, _): 119 | metaValue = cl.get().meta.extract(key); 120 | if (cl.get().superClass != null) { 121 | metaValue = metaValue.concat(gatherMetaValueFromHierarchy(TInst(cl.get().superClass.t, null), key)); 122 | } 123 | case TAbstract(a, _): 124 | metaValue = a.get().meta.extract( key); 125 | var next = haxe.macro.TypeTools.followWithAbstracts(t,true); 126 | if (next != null) { 127 | switch(next){ 128 | case TAbstract(aa,_): 129 | if (isSameAbstractType(a.get(), aa.get())) { 130 | metaValue = []; 131 | } else { 132 | metaValue = metaValue.concat(gatherMetaValueFromHierarchy(next, key)); 133 | } 134 | default: 135 | metaValue = metaValue.concat(gatherMetaValueFromHierarchy(next, key)); 136 | } 137 | } 138 | else { 139 | metaValue = []; 140 | } 141 | case TEnum(en, _): 142 | metaValue = en.get().meta.extract(key); 143 | case _: 144 | metaValue = []; 145 | } 146 | 147 | return metaValue; 148 | } 149 | 150 | static public function toMap(m:Metadata) { 151 | var ret = new Map>>(); 152 | if (m != null) 153 | for (meta in m) { 154 | if (!ret.exists(meta.name)) 155 | ret.set(meta.name, []); 156 | ret.get(meta.name).push(meta.params); 157 | } 158 | return ret; 159 | } 160 | static public inline function toExpr(v:Dynamic, ?pos:Position) 161 | return Context.makeExpr(v, sanitize(pos)); 162 | } 163 | 164 | class ExprExtensions { 165 | static public inline function toString(e:Expr):String 166 | return new haxe.macro.Printer().printExpr(e); 167 | } 168 | #end 169 | -------------------------------------------------------------------------------- /src/ecs/core/macro/Global.hx: -------------------------------------------------------------------------------- 1 | package ecs.core.macro; 2 | import haxe.macro.Printer; 3 | #if macro 4 | import haxe.macro.Type; 5 | import haxe.macro.Expr; 6 | import ecs.utils.Const; 7 | 8 | using ecs.core.macro.MacroTools; 9 | using ecs.core.macro.Extensions; 10 | using haxe.macro.Context; 11 | using haxe.macro.ComplexTypeTools; 12 | using haxe.macro.TypeTools; 13 | using Lambda; 14 | 15 | #end 16 | 17 | class Global { 18 | #if macro 19 | static var addedLate = false; 20 | static var lateDef:TypeDefinition; 21 | 22 | static function defineLateCalls():TypeDefinition { 23 | var containerNames = [for (c in ComponentBuilder.componentTypeNames()) c]; 24 | var removeExprs = new Array(); 25 | var nameExprs = containerNames.map( (x) -> EConst(CString(x)).at() ); 26 | 27 | var countExpr : Expr = EConst( CInt(Std.string(containerNames.length))).at(); 28 | 29 | #if ecs_late_debug 30 | trace('Container count is ${containerNames.length} expr ${countExpr}'); 31 | 32 | for (i in 0...containerNames.length) { 33 | trace('Component ${i} name: ${containerNames[i]}'); 34 | } 35 | #end 36 | 37 | var infos = containerNames.map( (x) -> ComponentBuilder.containerInfo(x) ); 38 | 39 | for (info in infos) { 40 | removeExprs.push(info.getRemoveExpr(macro e)); 41 | } 42 | 43 | var listComponentsExists = infos.map( (x) -> { 44 | var testExpr = x.getExistsExpr(macro e ); 45 | var name = EConst(CString(x.name)).at(); 46 | 47 | return macro if ($testExpr) componentNames.push( $name ); 48 | }); 49 | 50 | var toStringComponents = infos.map( (x) -> { 51 | var testExpr = x.getExistsExpr(macro e ); 52 | var getExpr = x.getGetExpr(macro e ); 53 | 54 | return macro if ($testExpr) strings.push( Std.string($getExpr) ); 55 | }); 56 | 57 | var toDynamicComponents = infos.map( (x) -> { 58 | var testExpr = x.getExistsExpr(macro e ); 59 | var getExpr = x.getGetExpr(macro e ); 60 | 61 | return macro if ($testExpr) objs.push( $getExpr ); 62 | }); 63 | 64 | var toStringByComponentCases = infos.map( (x) -> { 65 | var getExpr = x.getGetExpr(macro e); 66 | var getStr = macro Std.string($getExpr); 67 | 68 | var name = EConst(CString(x.name)).at(); 69 | var c : Case = { 70 | values:[name], 71 | expr: getStr 72 | }; 73 | return c; 74 | }); 75 | var toStringByComponentSwitch = 76 | ESwitch(macro name, toStringByComponentCases, macro null).at(); 77 | 78 | 79 | 80 | 81 | var lateClass = macro class LateCalls { 82 | public static function removeAllComponents(e:ecs.Entity) { 83 | $b{removeExprs} 84 | } 85 | 86 | public static function listComponents( e:ecs.Entity ) { 87 | var componentNames = new Array(); 88 | $b{listComponentsExists} 89 | return componentNames; 90 | } 91 | public static function numComponentTypes() { 92 | return $countExpr; 93 | } 94 | static var _componentNames = $a{nameExprs}; 95 | public static function getComponentNames() : Array { 96 | return _componentNames; 97 | } 98 | 99 | public function getRemoveFunc():(ecs.Entity) -> Void { 100 | return removeAllComponents; 101 | } 102 | 103 | public static function componentsToStrings(e:ecs.Entity) : Array { 104 | var strings = []; 105 | 106 | $b{toStringComponents} 107 | return strings; 108 | } 109 | 110 | public static function componentsToDynamic(e:ecs.Entity) : Array { 111 | var objs:Array = []; 112 | 113 | $b{toDynamicComponents} 114 | return objs; 115 | } 116 | 117 | public static function componentNameToString(e:ecs.Entity, name : String) : String { 118 | return $toStringByComponentSwitch; 119 | } 120 | }; 121 | 122 | //var p = new Printer(); 123 | //trace ('${p.printTypeDefinition(lateClass)}'); 124 | 125 | lateClass.meta.push({name: ":keep", pos: Context.currentPos()}); 126 | return lateClass; 127 | } 128 | 129 | #end 130 | 131 | public macro static function setup():Expr { 132 | //trace ('now!'); 133 | defineLateCalls().defineTypeSafe("ecs", Const.ROOT_MODULE); 134 | 135 | //ViewBuilder.createAllViewType(); 136 | var x = macro { 137 | // trace('ECS: Setting up ECS late bind functions'); 138 | @:privateAccess Workflow.removeAllFunction = ecs.LateCalls.removeAllComponents; 139 | @:privateAccess Workflow.numComponentTypes = ecs.LateCalls.numComponentTypes; 140 | @:privateAccess Workflow.componentNames = ecs.LateCalls.getComponentNames; 141 | @:privateAccess Workflow.entityComponentNames = ecs.LateCalls.listComponents; 142 | @:privateAccess Workflow.componentsToStrings = ecs.LateCalls.componentsToStrings; 143 | @:privateAccess Workflow.componentsToDynamic = ecs.LateCalls.componentsToDynamic; 144 | @:privateAccess Workflow.componentNameToString = ecs.LateCalls.componentNameToString; 145 | } 146 | return x; 147 | } 148 | 149 | 150 | } 151 | -------------------------------------------------------------------------------- /src/ecs/core/macro/MetaTools.hx: -------------------------------------------------------------------------------- 1 | package ecs.core.macro; 2 | 3 | #if macro 4 | import haxe.macro.Expr; 5 | import haxe.macro.Context; 6 | 7 | using Lambda; 8 | 9 | class MetaTools { 10 | public static final SKIP_META = ['skip']; 11 | public static final PRINT_META = ['print']; 12 | public static final AD_META = ['added', 'ad', 'a', ':added', ':ad', ':a']; 13 | public static final RM_META = ['removed', 'rm', 'r', ':removed', ':rm', ':r']; 14 | public static final UPD_META = ['update', 'up', 'u', ':update', ':up', ':u']; 15 | public static final PARALLEL_META = [':parallel', 'parallel', 'p', ':p']; 16 | public static final FORK_META = [':fork', 'fork', 'f', ':f']; 17 | public static final JOIN_META = [':join', 'join', 'j', ":j"]; 18 | public static final VIEW_FUNC_META = UPD_META.concat(AD_META).concat(RM_META); 19 | 20 | 21 | public static function containsMeta(field:Field, metas:Array) { 22 | var metaData = field.meta; 23 | if (metaData != null) { 24 | for (t in metas) { 25 | if (metaData.exists( (e) -> e.name == t)) { 26 | return true; 27 | } 28 | } 29 | } 30 | return false; 31 | } 32 | 33 | public static function notSkipped(field:Field) { 34 | return !MetaTools.containsMeta(field, MetaTools.SKIP_META); 35 | } 36 | } 37 | #end 38 | -------------------------------------------------------------------------------- /src/ecs/core/macro/PoolBuilder.hx: -------------------------------------------------------------------------------- 1 | package ecs.core.macro; 2 | 3 | #if macro 4 | import haxe.macro.Printer; 5 | import haxe.macro.Expr; 6 | 7 | 8 | 9 | import ecs.core.macro.MacroTools.*; 10 | 11 | using StringTools; 12 | using Lambda; 13 | using ecs.core.macro.Extensions; 14 | 15 | import haxe.macro.Context; 16 | 17 | using haxe.macro.TypeTools; 18 | 19 | class PoolBuilder { 20 | static var _printer:Printer = new Printer(); 21 | 22 | // Sorry this is brutally messy, it took a while to work through the edge cases 23 | // USE THE IPoolable interface please 24 | public static function arrayPool() { 25 | var fields = Context.getBuildFields(); 26 | var lt = Context.getLocalType().follow(); 27 | var ct = lt.toComplexType(); 28 | switch (lt) { 29 | case TInst(t, params): 30 | switch (t.get().kind) { 31 | case KAbstractImpl(a): 32 | if (a.get().params.length > 0) 33 | throw 'Pools do not support abstract types with parameters on ${ct.toString()}'; 34 | ct = a.get().name.asComplexType(); 35 | default: 36 | } 37 | default: 38 | } 39 | 40 | // check to see if this is the start of the pool implementation 41 | var classTypeRef = Context.getLocalClass(); 42 | var poolRoot = false; 43 | if (classTypeRef != null) { 44 | var classType = classTypeRef.get(); 45 | for (i in classType.interfaces) { 46 | var it = i.t.get(); 47 | if (it.name == "IPoolable") { 48 | poolRoot = true; 49 | break; 50 | } 51 | } 52 | if (!poolRoot) { 53 | poolRoot = true; 54 | var sc = classType.superClass; 55 | while (sc != null) { 56 | var st = sc.t.get(); 57 | for (i in st.interfaces) { 58 | var it = i.t.get(); 59 | if (it.name == "IPoolable") { 60 | poolRoot = false; 61 | break; 62 | } 63 | } 64 | if (!poolRoot) { 65 | break; 66 | } 67 | sc = st.superClass; 68 | } 69 | } 70 | } 71 | 72 | // Create pool static var 73 | { 74 | var atp = tpath([], "Array", [TPType(ct)]); 75 | var at = TPath(atp); 76 | var atalloc = constructExpr(atp); 77 | fields.push(fvar(null, [AStatic], "__pool", at, atalloc, Context.currentPos())); 78 | } 79 | // Rent 80 | { 81 | var tp = ct.toString().asTypePath(); 82 | var factoryField = fields.find((x) -> x.meta.toMap().exists(":pool_factory")); 83 | var newCall = factoryField != null ? macro $i{factoryField.name}() : macro new $tp(); 84 | var rentCalls = fields.filter((x) -> x.meta.toMap().exists(":pool_rent")).map((x) -> 85 | switch(x.kind) { 86 | case FFun(fun): 87 | var ne = x.name; 88 | var ve = macro x; 89 | macro $ve.$ne(); 90 | default: null; 91 | }).filter((x)-> x != null); 92 | 93 | var allocBody = macro { 94 | var x = (__pool.length == 0) ? $newCall : __pool.pop(); 95 | $b{rentCalls} 96 | return x; 97 | } 98 | 99 | var access = [APublic, AStatic, AInline]; 100 | fields.push(ffun(null, access, "rent", null, ct, allocBody, Context.currentPos())); 101 | } 102 | 103 | // Retire 104 | { 105 | var retireCalls = fields.filter((x) -> x.meta.toMap().exists(":pool_retire")).map((x) -> 106 | switch(x.kind) { 107 | case FFun(fun): macro $i{x.name}(); 108 | default: null; 109 | }).filter((x)-> x != null); 110 | retireCalls.push(macro __pool.push(this)); 111 | var access = [APublic]; 112 | if (!poolRoot) { 113 | access.push(AOverride); 114 | } 115 | fields.push(ffun(null, access, "retire", [], null, macro $b{retireCalls}, Context.currentPos())); 116 | } 117 | 118 | /* 119 | var cb = new ClassBuilder(fields); 120 | 121 | // In the case it doesn't already have a constructor, make a private default one 122 | if (!cb.hasConstructor() && !fields.exists((x) -> x.name == "_new")) { 123 | var constructor = cb.getConstructor(); 124 | constructor.isPublic = false; 125 | constructor.publish(); 126 | } 127 | 128 | return cb.export(); 129 | */ 130 | return fields; 131 | } 132 | 133 | public static function linkedPool(debug:Bool = false) { 134 | var fields = Context.getBuildFields(); 135 | 136 | var ct = Context.getLocalType().toComplexType(); 137 | 138 | var body = macro {}; 139 | fields.push(ffun(null, [APublic, AStatic, AInline], "rent", null, ct, body, Context.currentPos())); 140 | 141 | fields.push(ffun(null, [APublic, AInline], "retire", [{name: "p", type: ct}], null, body, Context.currentPos())); 142 | 143 | return fields; 144 | } 145 | } 146 | #end 147 | -------------------------------------------------------------------------------- /src/ecs/core/macro/Report.hx: -------------------------------------------------------------------------------- 1 | package ecs.core.macro; 2 | 3 | #if macro 4 | import ecs.core.macro.MacroTools.*; 5 | import ecs.core.macro.ViewBuilder.*; 6 | import ecs.core.macro.ComponentBuilder.*; 7 | import haxe.macro.Context; 8 | 9 | using Lambda; 10 | 11 | /** 12 | * ... 13 | * @author https://github.com/deepcake 14 | */ 15 | @:final 16 | @:dce 17 | class Report { 18 | 19 | 20 | static var reportRegistered = false; 21 | 22 | 23 | public static function gen() { 24 | #if echoes_report 25 | if (!reportRegistered) { 26 | Context.onGenerate(function(types) { 27 | function sortedlist(array:Array) { 28 | array.sort(compareStrings); 29 | return array; 30 | } 31 | 32 | var ret = 'ECHO BUILD REPORT :'; 33 | 34 | ret += '\n COMPONENTS [${componentNames.length}] :'; 35 | ret += '\n ' + sortedlist(componentNames.mapi(function(i, k) return '$k #${ componentIds.get(k) }').array()).join('\n '); 36 | ret += '\n VIEWS [${viewNames.length}] :'; 37 | ret += '\n ' + sortedlist(viewNames.mapi(function(i, k) return '$k #${ viewIds.get(k) }').array()).join('\n '); 38 | trace('\n$ret'); 39 | 40 | }); 41 | reportRegistered = true; 42 | } 43 | #end 44 | } 45 | 46 | 47 | } 48 | #end 49 | -------------------------------------------------------------------------------- /src/ecs/core/macro/SystemBuilder.hx: -------------------------------------------------------------------------------- 1 | package ecs.core.macro; 2 | 3 | #if macro 4 | import ecs.core.macro.MetaTools; 5 | import ecs.core.macro.MacroTools.*; 6 | import ecs.core.macro.ViewBuilder; 7 | import ecs.core.macro.ComponentBuilder.*; 8 | import haxe.macro.Expr; 9 | import haxe.macro.Printer; 10 | import ecs.core.macro.ViewSpec; 11 | 12 | using ecs.core.macro.Extensions; 13 | using haxe.macro.ComplexTypeTools; 14 | using haxe.macro.TypeTools; 15 | using haxe.macro.Context; 16 | using ecs.core.macro.MacroTools; 17 | 18 | using StringTools; 19 | using Lambda; 20 | 21 | typedef UpdateRec = { 22 | name:String, 23 | rawargs:Array, 24 | meta:haxe.ds.Map>>, 25 | args:Array, 26 | view:ViewRec, 27 | viewargs:Array, 28 | type:MetaFuncType 29 | }; 30 | 31 | enum ParallelType { 32 | PUnknown; 33 | PFull; 34 | PHalf; 35 | PDouble; 36 | PCount(n:Int); 37 | } 38 | 39 | enum abstract MetaFuncType(Int) { 40 | var SINGLE_CALL = 1; 41 | var VIEW_ITER = 2; 42 | var ENTITY_ITER = 3; 43 | } 44 | 45 | class SystemBuilder { 46 | public static var systemIndex = -1; 47 | 48 | static var _printer = new Printer(); 49 | 50 | static function metaFuncArgToComponentDef(a:FunctionArg, pos) { 51 | return switch (a.type.followComplexType(pos)) { 52 | case macro: StdTypes.Float 53 | :null; 54 | case macro: StdTypes.Int 55 | :null; 56 | case macro: ecs.Entity 57 | :null; 58 | default: 59 | var mm = a.meta.toMap(); 60 | mm.exists(":local") ? null : {cls: a.type.followComplexType(pos)}; 61 | } 62 | } 63 | 64 | static function notNull(e:Null) 65 | return e != null; 66 | 67 | // @meta f(a:T1, b:T2, deltatime:Float) --> a, b, __dt__ 68 | static function metaFuncArgToCallArg(a:FunctionArg, pos) { 69 | return switch (a.type.followComplexType(pos)) { 70 | case macro: StdTypes.Float 71 | :macro __dt__; 72 | case macro: StdTypes.Int 73 | :macro __entity__; 74 | case macro: ecs.Entity 75 | :macro __entity__; 76 | default: macro $i{a.name}; 77 | } 78 | } 79 | 80 | static function metaFuncArgIsEntity(a:FunctionArg,pos) { 81 | return switch (a.type.followComplexType(pos)) { 82 | case macro: StdTypes.Int 83 | , 84 | macro:ecs.Entity:true; 85 | default: 86 | false; 87 | } 88 | } 89 | 90 | static function refComponentDefToFuncArg(cls:ComplexType, args:Array,pos) { 91 | var copmonentClsName = cls.followName(pos); 92 | var a = args.find(function(a) return a.type.followName(pos) == copmonentClsName); 93 | if (a != null) { 94 | return arg(a.name, a.type); 95 | } else { 96 | return arg(cls.typeFullName(pos).toLowerCase(), cls); 97 | } 98 | } 99 | 100 | static function procMetaFunc(field:Field):UpdateRec { 101 | // Context.warning('processing meta for ${field.name}', field.pos); 102 | return switch (field.kind) { 103 | case FFun(func): { 104 | // Context.warning('Found function meta for ${field.name}', field.pos); 105 | var funcName = field.name; 106 | var funcCallArgs = func.args.map((x) -> metaFuncArgToCallArg(x,field.pos)).filter(notNull); 107 | var components = func.args.map((x) -> metaFuncArgToComponentDef(x,field.pos)).filter(notNull); 108 | var worlds = metaFieldToWorlds(field); 109 | 110 | if (components.length > 0) { 111 | // Context.warning('Found components ${components.length}', field.pos); 112 | 113 | // view iterate 114 | 115 | var vi = ViewSpec.fromField(field, func); 116 | // Context.warning('View Spec from field ${vi.name}', field.pos); 117 | var vr = ViewBuilder.getViewRec(vi, field.pos); 118 | if (vr == null) { 119 | Context.warning('View Rec is null', field.pos); 120 | return null; 121 | } 122 | // Context.warning('View Rec from field ${vr.name}', field.pos); 123 | 124 | var viewArgs = [arg('__entity__', macro:ecs.Entity)].concat(vi.includes.map((x) -> refComponentDefToFuncArg(x.ct, func.args, field.pos))); 125 | 126 | // Context.warning('View args from field ${viewArgs}', field.pos); 127 | { 128 | name: funcName, 129 | rawargs: func.args, 130 | meta: field.meta.toMap(), 131 | args: funcCallArgs, 132 | view: vr, 133 | viewargs: viewArgs, 134 | type: VIEW_ITER 135 | }; 136 | } else { 137 | // Context.warning('No components', field.pos); 138 | if (func.args.exists((x) -> metaFuncArgIsEntity(x, field.pos))) { 139 | // every entity iterate 140 | Context.warning("Are you sure you want to iterate over all the entities? If not, you should add some components or remove the Entity / Int argument", 141 | field.pos); 142 | 143 | { 144 | name: funcName, 145 | rawargs: func.args, 146 | meta: field.meta.toMap(), 147 | args: funcCallArgs, 148 | view: null, 149 | viewargs: null, 150 | type: ENTITY_ITER 151 | }; 152 | } else { 153 | // single call 154 | { 155 | name: funcName, 156 | rawargs: func.args, 157 | meta: field.meta.toMap(), 158 | args: funcCallArgs, 159 | view: null, 160 | viewargs: null, 161 | type: SINGLE_CALL 162 | }; 163 | } 164 | } 165 | } 166 | default: 167 | null; 168 | } 169 | } 170 | 171 | public static function build(debug:Bool = false) { 172 | var fields = Context.getBuildFields(); 173 | var ct = Context.getLocalType().toComplexType(); 174 | var pos = Context.currentPos(); 175 | // trace('Building ${ct.toString()}'); 176 | 177 | // define new() if not exists (just for comfort) 178 | if (!fields.exists(function(f) return f.name == 'new')) { 179 | fields.push(ffun([APublic], 'new', null, null, null, Context.currentPos())); 180 | } 181 | 182 | var index = ++systemIndex; 183 | 184 | // prevent wrong override 185 | for (field in fields) { 186 | switch (field.kind) { 187 | case FFun(func): 188 | switch (field.name) { 189 | case '__update__': 190 | Context.error('Do not override the `__update__` function! Use `@:update` meta instead! More info at README example', field.pos); 191 | case '__activate__': 192 | Context.error('Do not override the `__activate__` function! `onactivate` can be overridden instead!', field.pos); 193 | case '__deactivate__': 194 | Context.error('Do not override the `__deactivate__` function! `ondeactivate` can be overridden instead!', field.pos); 195 | default: 196 | } 197 | default: 198 | } 199 | } 200 | 201 | var definedViews = new Array<{view:ViewRec, varname:String}>(); 202 | // find and init manually defined views 203 | fields.filter(MetaTools.notSkipped).iter(function(field) { 204 | switch (field.kind) { 205 | // defined var only 206 | case FVar(clsCT, _) if (clsCT != null): 207 | { 208 | switch (clsCT) { 209 | case TPath(path): 210 | if (path.name == "View") { 211 | var components = []; 212 | 213 | for (p in path.params) { 214 | switch (p) { 215 | case TPType(tpt): { 216 | components.push(tpt.followComplexType(pos)); 217 | } 218 | case TPExpr(e): throw "unsupported"; 219 | } 220 | } 221 | 222 | var vs = ViewSpec.fromComponents(components, pos); 223 | var vr = ViewBuilder.getViewRec(vs, field.pos); 224 | 225 | if (vr != null) { 226 | if (definedViews.find(function(v) return v.view.spec.name == vr.spec.name) == null) { 227 | // init 228 | var x = vr.spec.typePath().asTypeIdent(Context.currentPos()); 229 | field.kind = FVar(vr.ct, macro $x.inst()); 230 | // trace('defining: ${field.name.toLowerCase()} from field'); 231 | definedViews.push({view: vr, varname: field.name}); 232 | } 233 | } else { 234 | Context.warning('View Rec is null', field.pos); 235 | } 236 | } 237 | default: 238 | } 239 | } 240 | default: 241 | } 242 | }); 243 | 244 | // find and init meta defined views 245 | fields.filter(MetaTools.notSkipped).filter((x) -> MetaTools.containsMeta(x, MetaTools.VIEW_FUNC_META)).iter(function(field) { 246 | switch (field.kind) { 247 | case FFun(func): 248 | { 249 | //trace ('Function ${field.name}'); 250 | var components = func.args.map((x) -> metaFuncArgToComponentDef(x,field.pos)).filter(notNull); 251 | var worlds = metaFieldToWorlds(field); 252 | 253 | if (components != null && components.length > 0) { 254 | var vs = ViewSpec.fromField(field, func); 255 | if (vs != null) { 256 | var lcName = vs.name.toLowerCase(); 257 | 258 | var view = definedViews.find(function(v) return v.varname == lcName); 259 | 260 | if (view == null || view.varname != lcName) { 261 | if (view != null) trace('? vs ${view.varname} vs ${lcName}'); 262 | var vr = ViewBuilder.getViewRec(vs, field.pos); 263 | 264 | if (vr != null) { 265 | // trace('defining: ${lcName} from function'); 266 | 267 | definedViews.push({view: vr, varname: lcName}); 268 | var tp = vs.typePath().asTypeIdent(Context.currentPos()); 269 | fields.push(fvar([], [], lcName, vr.ct, macro $tp.inst(), Context.currentPos())); 270 | } else { 271 | Context.warning('Something in denmark2 ${view}', Context.currentPos()); 272 | } 273 | } else { 274 | // trace('reusing: ${lcName} from function'); 275 | //Context.warning('Re-using view ${view.varname}', Context.currentPos()); 276 | } 277 | } 278 | } 279 | } 280 | default: 281 | } 282 | }); 283 | 284 | var ufuncs = fields.filter(MetaTools.notSkipped) 285 | .filter((x) -> return MetaTools.containsMeta(x, MetaTools.UPD_META)) 286 | .map(procMetaFunc) 287 | .filter(notNull); 288 | var afuncs = fields.filter(MetaTools.notSkipped) 289 | .filter(MetaTools.containsMeta.bind(_, MetaTools.AD_META)) 290 | .map(procMetaFunc) 291 | .filter(notNull); 292 | var rfuncs = fields.filter(MetaTools.notSkipped) 293 | .filter(MetaTools.containsMeta.bind(_, MetaTools.RM_META)) 294 | .map(procMetaFunc) 295 | .filter(notNull); 296 | var listeners = afuncs.concat(rfuncs); 297 | 298 | // define signal listener wrappers 299 | listeners.iter(function(f) { 300 | fields.push(fvar([], [], '__${f.name}_listener__', TFunction(f.viewargs.map(function(a) return a.type), macro:Void), null, Context.currentPos())); 301 | }); 302 | 303 | var uexprs = [] 304 | #if ecs_profiling 305 | .concat([macro var __timestamp__ = haxe.Timer.stamp()]) 306 | #end 307 | .concat(ufuncs.map(function(f) { 308 | return switch (f.type) { 309 | case SINGLE_CALL: { 310 | macro $i{f.name}($a{f.args}); 311 | } 312 | case VIEW_ITER: { 313 | var maxParallel = PUnknown; 314 | if (f.meta.exists(":parallel")) { 315 | // TODO - Make it run in parallel :) 316 | var pm = f.meta[":parallel"][0]; // only pay attention to the first one 317 | if (pm.length > 0) { 318 | var pstr = pm[0].getStringValue(); 319 | if (pstr != null) { 320 | maxParallel = switch (pstr.toUpperCase()) { 321 | case "FULL": PFull; 322 | case "HALF": PHalf; 323 | case "DOUBLE": PDouble; 324 | default: PUnknown; 325 | } 326 | } 327 | 328 | if (maxParallel == PUnknown) { 329 | maxParallel = PCount(pm[0].getNumericValue(1, pm[0].pos)); 330 | } 331 | } 332 | } 333 | 334 | var callTypeMap = new Map(); 335 | var callNameMap = new Map(); 336 | callTypeMap["Float".asComplexType().followComplexType(pos).typeFullName(pos)] = macro __dt__; 337 | callTypeMap["ecs.Entity".asComplexType().followComplexType(pos).typeFullName(pos)] = macro __entity__; 338 | for (c in f.view.spec.includes) { 339 | var ct = c.ct.typeFullName(pos); 340 | var info = getComponentContainerInfo(c.ct, pos); 341 | callTypeMap[ct] = info.getGetExprCached(macro __entity__, info.fullName + "_inst") ; 342 | } 343 | 344 | var cache = f.view.spec.includes.map(function(c) { 345 | var info = getComponentContainerInfo(c.ct, pos); 346 | return info.getCacheExpr(info.fullName + "_inst"); 347 | }).filter( (x) -> x != null) ; 348 | 349 | for (a in f.rawargs) { 350 | var am = a.meta.toMap(); 351 | var local = am.get(":local"); 352 | if (local != null && local.length > 0 && local[0].length > 0) { 353 | callNameMap[a.name] = macro $i{"__l_" + a.name}; 354 | cache.push(("__l_" + a.name).define(local[0][0])); 355 | } 356 | } 357 | 358 | var remappedArgs = f.rawargs.map((x) -> { 359 | var ctn = x.type.followComplexType(pos).typeFullName(pos); 360 | if (callNameMap.exists(x.name)) { 361 | return callNameMap[x.name]; 362 | } 363 | if (callTypeMap.exists(ctn)) { 364 | return callTypeMap[ctn]; 365 | } 366 | 367 | throw 'No experession for type ${ctn}'; 368 | }); 369 | 370 | var loop = macro for (__entity__ in $i{f.view.name}.entities) { 371 | $i{'${f.name}'}($a{remappedArgs}); 372 | } 373 | 374 | cache.concat([loop]).toBlock(); 375 | } 376 | case ENTITY_ITER: { 377 | macro for (__entity__ in ecs.Workflow.entities) { 378 | $i{f.name}($a{f.args}); 379 | } 380 | } 381 | } 382 | })) 383 | #if ecs_profiling 384 | .concat ([macro this.__updateTime__ = (haxe.Timer.stamp() - __timestamp__) * 1000.]) 385 | #end 386 | ; 387 | 388 | var aexpr = macro if (!activated) 389 | $b{ 390 | [].concat([macro activated = true]) 391 | .concat( // init signal listener wrappers 392 | listeners.map(function(f) { 393 | // DCE is eliminating this on 'full' 394 | // TODO: generate classfields for these 395 | var fwrapper = { 396 | expr: EFunction(FunctionKind.FAnonymous, {args: f.viewargs, ret: macro:Void, expr: macro $i{f.name}($a{f.args})}), 397 | pos: Context.currentPos() 398 | }; 399 | return macro $i{'__${f.name}_listener__'} = $fwrapper; 400 | })) 401 | .concat( // activate views 402 | definedViews.map(function(v) { 403 | return macro $i{v.varname}.activate(); 404 | })) 405 | .concat( // add added-listeners 406 | afuncs.map(function(f) { 407 | return macro $i{f.view.name}.observeAdd($i{'__${f.name}_listener__'}); 408 | })) 409 | .concat( // add removed-listeners 410 | rfuncs.map(function(f) { 411 | return macro $i{f.view.name}.observeRemove($i{'__${f.name}_listener__'}); 412 | })) 413 | .concat( // call added-listeners 414 | afuncs.map(function(f) { 415 | return macro $i{f.view.name}.iter($i{'__${f.name}_listener__'}); 416 | })) 417 | .concat([macro onactivate()])}; 418 | 419 | var dexpr = macro if (activated) 420 | $b{ 421 | [].concat([macro activated = false]) 422 | .concat( // deactivate views 423 | definedViews.map(function(v) { 424 | return macro $i{v.varname}.deactivate(); 425 | })) 426 | .concat( // remove added-listeners 427 | afuncs.map(function(f) { 428 | return macro $i{f.view.name}.ignoreAdd($i{'__${f.name}_listener__'}); 429 | })) 430 | .concat( // remove removed-listeners 431 | rfuncs.map(function(f) { 432 | return macro $i{f.view.name}.ignoreRemove($i{'__${f.name}_listener__'}); 433 | })) 434 | .concat([macro ondeactivate()]) 435 | .concat( // null signal wrappers 436 | listeners.map(function(f) { 437 | return macro $i{'__${f.name}_listener__'} = null; 438 | }))}; 439 | 440 | if (uexprs.length > 0) { 441 | fields.push(ffun([APublic, AOverride], '__update__', [arg('__dt__', macro:Float)], null, macro $b{uexprs}, Context.currentPos())); 442 | } 443 | 444 | fields.push(ffun([APublic, AOverride], '__activate__', [], null, macro {$aexpr;}, Context.currentPos())); 445 | fields.push(ffun([APublic, AOverride], '__deactivate__', [], null, macro {$dexpr;}, Context.currentPos())); 446 | 447 | // toString 448 | fields.push(ffun([AOverride, APublic], 'toString', null, macro:String, macro return $v{ct.followName(pos)}, Context.currentPos())); 449 | 450 | var clsType = Context.getLocalClass().get(); 451 | 452 | if (debug || MetaTools.PRINT_META.exists(function(m) return clsType.meta.has(m))) { 453 | switch (Context.getLocalType().toComplexType()) { 454 | case TPath(p): 455 | { 456 | var td:TypeDefinition = { 457 | pack: p.pack, 458 | name: p.name, 459 | pos: clsType.pos, 460 | kind: TDClass(tpath("ecs", "System")), 461 | fields: fields 462 | } 463 | trace(new Printer().printTypeDefinition(td)); 464 | } 465 | default: 466 | { 467 | Context.warning("Fail @print", clsType.pos); 468 | } 469 | } 470 | } 471 | 472 | #if false 473 | if (Context.getLocalType().toComplex().toString() == "TestSystemA") { 474 | trace('Type: ${Context.getLocalType().toComplex().toString()}'); 475 | for (f in fields) { 476 | trace(_printer.printField(f)); 477 | } 478 | } 479 | #end 480 | 481 | return fields; 482 | } 483 | } 484 | #end 485 | -------------------------------------------------------------------------------- /src/ecs/core/macro/ViewBuilder.hx: -------------------------------------------------------------------------------- 1 | package ecs.core.macro; 2 | 3 | #if macro 4 | import haxe.macro.Printer; 5 | import ecs.core.macro.MacroTools.*; 6 | import ecs.core.macro.ComponentBuilder.*; 7 | import ecs.core.macro.ViewsOfComponentBuilder.*; 8 | import haxe.macro.Expr; 9 | import haxe.macro.Type.ModuleType; 10 | import ecs.core.macro.ViewSpec; 11 | import ecs.utils.Const; 12 | import ecs.utils.Signal; 13 | using ecs.core.macro.Extensions; 14 | using ecs.core.macro.MacroTools; 15 | using haxe.macro.ComplexTypeTools; 16 | using haxe.macro.Context; 17 | using Lambda; 18 | using haxe.ds.ArraySort; 19 | 20 | using haxe.macro.PositionTools; 21 | 22 | typedef ViewRec = {name:String, spec:ViewSpec, ct:ComplexType}; 23 | 24 | class ViewBuilder { 25 | @:persistent static var _views = new Map(); 26 | @:persistent static var _typeDefs = new Map(); 27 | static var _callback = false; 28 | 29 | static var _resolving : String = null; 30 | 31 | /* 32 | public static function createAllViewType() { 33 | trace('building types'); 34 | for(vsi in _views.keyValueIterator()) { 35 | try { 36 | var x = Context.getType(vsi.key); 37 | } 38 | catch(e:Dynamic) { 39 | trace('could not find type ${vsi.key}'); 40 | if (Std.isOfType(e,String)) { 41 | var x = createViewTypeDef(vsi.value, Context.currentPos()); 42 | } 43 | } 44 | } 45 | } 46 | 47 | 48 | static function generateViewTypes(name:String):TypeDefinition { 49 | 50 | return null; 51 | // createViewType(vs, pos); 52 | if (name.indexOf("ecs.view.") == 0) { 53 | 54 | if (_views.exists( name)) { 55 | var vi = _views.get(name); 56 | trace('Looking for type ${name}'); 57 | 58 | var x = createViewTypeDef(vi, Context.currentPos()); 59 | 60 | //Context.defineType(x); 61 | 62 | _typeDefs.set(name, x); 63 | return x; 64 | } 65 | } 66 | 67 | return null; 68 | } 69 | */ 70 | public static function getViewRec(vs:ViewSpec, pos:Position):ViewRec { 71 | if (vs == null) { 72 | Context.error('View spec is null', pos); 73 | throw "null view spec"; 74 | } 75 | 76 | if (_callback == false) { 77 | //Context.onTypeNotFound(generateViewTypes); 78 | 79 | _callback = true; 80 | } 81 | 82 | _views.set( vs.typePath(), vs ); 83 | createViewType(vs, Context.currentPos()); 84 | return {name: vs.name.toLowerCase(), ct: vs.typePath().asComplexType(), spec: vs}; 85 | } 86 | 87 | static var callbackEstablished = false; 88 | 89 | static function afterTypingCallback(m:Array) { 90 | #if false 91 | // trace('After typing callback : ${m.length}'); 92 | 93 | trace('Total Views ${viewNames.length}'); 94 | var p = new Printer(); 95 | for (n in viewCache) { 96 | trace('View ${n.name} | ${n.spec.name}'); 97 | var t = Context.getType(n.spec.name); 98 | } 99 | #end 100 | } 101 | 102 | public static function build():haxe.macro.Type { 103 | return null; 104 | if (!callbackEstablished) { 105 | Context.onAfterTyping(afterTypingCallback); 106 | callbackEstablished = true; 107 | } 108 | var pos = Context.currentPos(); 109 | 110 | var t = Context.getLocalType().follow(); 111 | var vs = ViewSpec.fromViewType(t, pos); 112 | 113 | return createViewType(vs, Context.currentPos()); 114 | } 115 | 116 | 117 | public static function createViewTypeDef(vi:ViewSpec, pos:Position):TypeDefinition { 118 | if (vi == null) { 119 | Context.error("View spec can not be null", pos); 120 | return null; 121 | } 122 | var viewClsName = vi.name; 123 | var worlds = vi.worlds; 124 | var components = vi.includes; 125 | var ct = vi.typePath().asComplexType(); 126 | 127 | // first time call in current build 128 | 129 | // Context.warning('uncached ${viewClsName}', pos); 130 | // type was not cached in previous build 131 | // trace('creating view type ${viewClsName}'); 132 | 133 | var viewTypePath = tpath([], viewClsName, []); 134 | // var viewComplexType = TPath(viewTypePath); 135 | 136 | // signals 137 | var signalTypeParamComplexType = TFunction([macro:ecs.Entity].concat(components.map(function(c) return c.ct)), macro:Void); 138 | var signalTypePath = tpath(['ecs', 'utils'], 'Signal', [TPType(signalTypeParamComplexType)]); 139 | 140 | //trace( 'path ${signalTypePath}'); 141 | 142 | // signal args for dispatch() call 143 | var signalArgs = [macro id].concat(components.map(function(c) return getLookup(c.ct, macro id, pos))); 144 | 145 | // component related views 146 | var addViewToViewsOfComponent = components.map(function(c) { 147 | var viewsOfComponentName = getViewsOfComponent(c.ct, pos).toString(); 148 | if (viewsOfComponentName == null) { 149 | Context.error('Couldn\'t get view of component ${viewsOfComponentName}', pos); 150 | } else { 151 | // Context.warning('viewsOfComponentName ${viewsOfComponentName}', pos); 152 | } 153 | var typeIdent = viewsOfComponentName.asTypeIdent(pos); 154 | return macro @:privateAccess $typeIdent.inst().addRelatedView(this); 155 | }); 156 | 157 | //Context.warning('Defining view type ${vi.typePath()}', pos); 158 | // type def 159 | var def:TypeDefinition = macro class $viewClsName extends ecs.core.AbstractView { 160 | static var instance = new $viewTypePath(); 161 | 162 | @:keep inline public static function inst():$ct { 163 | return instance; 164 | } 165 | 166 | // instance 167 | 168 | public function observeAdd(x : $signalTypeParamComplexType) { 169 | _onAdded.push(x); 170 | } 171 | public function observeRemove(x : $signalTypeParamComplexType) { 172 | _onRemoved.push(x); 173 | } 174 | 175 | public function ignoreAdd(x : $signalTypeParamComplexType) { 176 | _onAdded.remove(x); 177 | } 178 | public function ignoreRemove(x : $signalTypeParamComplexType) { 179 | _onRemoved.remove(x); 180 | } 181 | 182 | var _onAdded = new Array<$signalTypeParamComplexType>(); 183 | var _onRemoved = new Array<$signalTypeParamComplexType>(); 184 | 185 | function new() { 186 | super(); 187 | @:privateAccess ecs.Workflow.definedViews.push(this); 188 | $b{addViewToViewsOfComponent} 189 | } 190 | 191 | override function dispatchAddedCallback(id:Int) { 192 | for (x in _onAdded) { 193 | x($a{signalArgs}); 194 | } 195 | //_onAdded.dispatch($a{signalArgs}); 196 | } 197 | 198 | override function dispatchRemovedCallback(id:Int) { 199 | for (x in _onRemoved) { 200 | x($a{signalArgs}); 201 | } 202 | //_onRemoved.dispatch($a{signalArgs}); 203 | } 204 | 205 | override function reset() { 206 | super.reset(); 207 | //_onAdded.removeAll(); 208 | //_onRemoved.removeAll(); 209 | } 210 | } 211 | 212 | // var iteratorTypePath = getViewIterator(components).tp(); 213 | // def.fields.push(ffun([], [APublic, AInline], 'iterator', null, null, macro return new $iteratorTypePath(this.echoes, this.entities.iterator()), Context.currentPos())); 214 | 215 | // iter 216 | { 217 | var funcComplexType = TFunction([macro:ecs.Entity].concat(components.map(function(c) return c.ct)), macro:Void); 218 | var funcCallArgs = [macro __entity__].concat(components.map(function(c) return getComponentContainerInfo(c.ct, pos).getGetExpr(macro __entity__, true))); 219 | var body = macro { 220 | for (__entity__ in entities) { 221 | f($a{funcCallArgs}); 222 | } 223 | } 224 | def.fields.push(ffun([APublic, AInline], 'iter', [arg('f', funcComplexType)], macro:Void, macro $body, Context.currentPos())); 225 | } 226 | 227 | // isMatched 228 | { 229 | var checksIncludes = components.map(function(c) return getComponentContainerInfo(c.ct, pos).getExistsExpr(macro id)); 230 | var checksExcludes = vi.excludes.map(function(c) return macro !${getComponentContainerInfo(c.ct, pos).getExistsExpr(macro id)}); 231 | var totalChecks = checksIncludes.concat(checksExcludes); 232 | 233 | var cond = totalChecks.slice(1).fold(function(check1, check2) return macro $check1 && $check2, totalChecks[0]); 234 | var body; 235 | if (worlds != 0xffffffff) { 236 | var worldVal:Expr = {expr: EConst(CInt('${worlds}')), pos: Context.currentPos()}; 237 | var entityWorld = macro ecs.Workflow.world(id); 238 | body = macro { 239 | return (((1 << $entityWorld) & $worldVal) == 0) ? false : $cond; 240 | }; 241 | } else { 242 | body = macro { 243 | return $cond; 244 | } 245 | } 246 | def.fields.push(ffun([AOverride], 'isMatched', [arg('id', macro:Int)], macro:Bool, body, Context.currentPos())); 247 | } 248 | 249 | // isMatchedByTypes 250 | { 251 | var checksIncludes = vi.includes.map(function(c) return macro names.contains(${c.name.toExpr()})); 252 | var checksExcludes = vi.excludes.map(function(c) return macro !names.contains(${c.name.toExpr()})); 253 | var totalChecks = checksIncludes.concat(checksExcludes); 254 | 255 | var cond = totalChecks.slice(1).fold(function(check1, check2) return macro $check1 && $check2, totalChecks[0]); 256 | var body; 257 | if (worlds != 0xffffffff) { 258 | var worldVal:Expr = {expr: EConst(CInt('${worlds}')), pos: Context.currentPos()}; 259 | var entityWorld = macro world; 260 | body = macro return (($entityWorld & $worldVal) == 0) ? false : $cond; 261 | } else { 262 | body = macro return $cond; 263 | } 264 | var show = macro trace("names " + names); 265 | body = {expr: EBlock([body]), pos: Context.currentPos()}; 266 | def.fields.push(ffun([AOverride, APublic], 'isMatchedByTypes', [arg('world', macro:Int), arg('names', macro:Array)], macro:Bool, body, 267 | Context.currentPos())); 268 | var xx = ffun([AOverride, APublic], 'isMatchedByTypes', [arg('world', macro:Int), arg('names', macro:Array)], macro:Bool, body, 269 | Context.currentPos()); 270 | 271 | // var pp = new Printer(); 272 | // trace('isMatchedByTypes : ${pp.printField(xx)}'); 273 | } 274 | 275 | // toString 276 | { 277 | var componentNames = components.map(function(c) return c.ct.typeValidShortName(pos)).join(', '); 278 | var body = macro return $v{componentNames}; 279 | def.fields.push(ffun([AOverride, APublic], 'toString', null, macro:String, body, Context.currentPos())); 280 | } 281 | 282 | def.meta.push({name: ":ecs_view", pos: Context.currentPos()}); 283 | def.pack = ViewSpec.VIEW_NAMESPACE.split("."); 284 | 285 | #if false 286 | trace('ViewType: ${def.name}'); 287 | var p = new Printer(); 288 | trace(p.printTypeDefinition(def)); 289 | #end 290 | 291 | return def; 292 | } 293 | 294 | public static function createViewType(vi:ViewSpec, pos:Position):haxe.macro.Type { 295 | if (vi == null) { 296 | Context.error("View spec can not be null", pos); 297 | return null; 298 | } 299 | var viewClsName = vi.name; 300 | var worlds = vi.worlds; 301 | var components = vi.includes; 302 | var ct = vi.typePath().asComplexType(); 303 | 304 | var viewType = ct.toTypeOrNull(pos); // This crushes the autocomplete 305 | 306 | if (viewType == null) { 307 | var def = createViewTypeDef(vi, pos); 308 | def.defineTypeSafe(ViewSpec.VIEW_NAMESPACE, Const.ROOT_MODULE); 309 | 310 | #if false 311 | trace('ViewType: ${def.name}'); 312 | var p = new Printer(); 313 | trace(p.printTypeDefinition(def)); 314 | #end 315 | viewType = ct.toTypeOrNull(pos); 316 | // trace('created view type ${viewClsName}!!'); 317 | 318 | // caching current build 319 | } 320 | 321 | return viewType; 322 | } 323 | } 324 | #end 325 | -------------------------------------------------------------------------------- /src/ecs/core/macro/ViewSpec.hx: -------------------------------------------------------------------------------- 1 | package ecs.core.macro; 2 | 3 | #if macro 4 | import ecs.core.macro.MacroTools.*; 5 | import haxe.macro.Expr; 6 | 7 | using ecs.core.macro.MacroTools; 8 | using haxe.macro.ComplexTypeTools; 9 | using haxe.macro.Context; 10 | using Lambda; 11 | using haxe.ds.ArraySort; 12 | using ecs.core.macro.Extensions; 13 | 14 | typedef ViewTypeRef = { 15 | ct:ComplexType, 16 | ex:Bool, 17 | name:String, 18 | lcname:String 19 | } 20 | 21 | class ViewSpec { 22 | public static final VIEW_NAMESPACE = "ecs.view"; 23 | 24 | function new() {} 25 | 26 | static function compareViewTypes(aRef:ViewTypeRef, bRef:ViewTypeRef):Int { 27 | return (aRef.lcname < bRef.lcname) ? -1 : (aRef.lcname > bRef.lcname) ? 1 : 0; 28 | } 29 | 30 | static function notNull(e:Null) 31 | return e != null; 32 | 33 | public var name:String; 34 | public var worlds:Int; 35 | public var includes:Array = []; 36 | public var excludes:Array = []; 37 | public var needsEntity:Bool; 38 | public var needsDT:Bool; 39 | 40 | public function typePath() { 41 | return VIEW_NAMESPACE + "." + name; 42 | } 43 | 44 | public function clone() { 45 | var c = new ViewSpec(); 46 | c.name = name; 47 | c.worlds = worlds; 48 | c.needsDT = needsDT; 49 | c.needsEntity = needsEntity; 50 | c.includes = includes.copy(); 51 | c.excludes = excludes.copy(); 52 | return c; 53 | } 54 | 55 | function addArg(a:FunctionArg, pos):ViewTypeRef { 56 | var mm = a.meta.toMap(); 57 | var ct = a.type.followComplexType(pos); 58 | 59 | if (ct == null) { 60 | Context.error('Can not find type ${a.type.toString()} for argument ${a.name} ',pos); 61 | } 62 | 63 | var x:ViewTypeRef = switch (ct) { 64 | case macro: StdTypes.Float 65 | :needsDT = true; 66 | null; 67 | case macro: StdTypes.Int 68 | :null; 69 | case macro: ecs.Entity 70 | :needsEntity = true; 71 | null; 72 | default: 73 | var localA = mm.get(":local"); 74 | if (localA == null) { 75 | var vt = { 76 | ct: ct, 77 | ex: false, 78 | name: ct.typeFullName(pos), 79 | lcname: ct.typeFullName(pos).toLowerCase() 80 | }; 81 | includes.push(vt); 82 | 83 | vt; 84 | } else null; 85 | } 86 | 87 | return x; 88 | } 89 | 90 | public static function getExcludesFromField(field:Field) { 91 | var mm = field.meta.toMap(); 92 | 93 | var excludes = []; 94 | if (mm.exists(":not")) { 95 | var exs = mm.get(":not"); 96 | for (ex in exs) { 97 | for (te in ex) { 98 | var tn = te.getStringValue(); 99 | var ct:ComplexType = TPath(exprOfClassToTypePath(cast te, null, field.pos)); 100 | ct = ct.followComplexType(field.pos); 101 | 102 | var vt = { 103 | ct: ct, 104 | ex: true, 105 | name: ct.typeFullName(field.pos), 106 | lcname: ct.typeFullName(field.pos).toLowerCase(), 107 | fun: null, 108 | local: null 109 | }; 110 | excludes.push(vt); 111 | } 112 | } 113 | } 114 | excludes.sort(compareViewTypes); 115 | 116 | return excludes; 117 | } 118 | 119 | public static function fromField(field:Field, func:Function):ViewSpec { 120 | var vi = new ViewSpec(); 121 | 122 | // Context.warning('from field ${field.name}', Context.currentPos()); 123 | var components = func.args.map((x) -> vi.addArg(x, field.pos)).filter(notNull); 124 | vi.worlds = metaFieldToWorlds(field); 125 | 126 | vi.includes.sort(compareViewTypes); 127 | vi.excludes = getExcludesFromField(field); 128 | 129 | vi.generateName(); 130 | return vi; 131 | } 132 | 133 | public static function fromVar(field:Field, ct:ComplexType):ViewSpec { 134 | trace('attemping to resolve from var ${field.name} with ${ct.toString()}'); 135 | var vs = fromViewCT(ct, field.pos); 136 | vs.worlds = metaFieldToWorlds(field); 137 | vs.excludes = getExcludesFromField(field); 138 | vs.generateName(); 139 | trace('From var ${vs.name}'); 140 | return vs; 141 | } 142 | 143 | public static function fromComponents(includes:Array, pos):ViewSpec { 144 | var vi = new ViewSpec(); 145 | vi.includes = includes.map(function(ct) { 146 | var vt = { 147 | ct: ct, 148 | ex: false, 149 | name: ct.typeFullName(pos), 150 | lcname: ct.typeFullName(pos).toLowerCase() 151 | }; 152 | return vt; 153 | }); 154 | vi.includes.sort(compareViewTypes); 155 | vi.excludes = []; 156 | vi.worlds = 0xffffffff; 157 | vi.needsEntity = false; 158 | vi.needsDT = false; 159 | vi.generateName(); 160 | return vi; 161 | } 162 | 163 | public static function fromViewCT(ct:ComplexType, pos):ViewSpec { 164 | if (ct == null) { 165 | Context.error('Can\'t turn null into view spec', Context.currentPos()); 166 | throw 'Can\'t turn null into view spec'; 167 | } 168 | var t = ct.toTypeOrNull(Context.currentPos()); 169 | if (t == null) { 170 | Context.error('Can\'t find type ${ct.toString()}', Context.currentPos()); 171 | throw 'Can\'t find type ${ct.toString()}'; 172 | } 173 | return fromViewType(t, pos); 174 | } 175 | 176 | public static function fromViewType(t:haxe.macro.Type, pos):ViewSpec { 177 | return switch (t) { 178 | case TInst(c, types): 179 | if (c.get().name != "View") { 180 | Context.warning('View type ${c.get().name} should likely be View', Context.currentPos()); 181 | } 182 | 183 | var vi = new ViewSpec(); 184 | vi.includes = types.map(function(t) { 185 | var ct = t.follow().toComplexType(); 186 | var vt = { 187 | ct: ct, 188 | ex: false, 189 | name: ct.typeFullName(pos), 190 | lcname: ct.typeFullName(pos).toLowerCase() 191 | }; 192 | return vt; 193 | }); 194 | vi.includes.sort(compareViewTypes); 195 | vi.excludes = []; 196 | vi.worlds = 0xffffffff; 197 | vi.needsEntity = false; 198 | vi.needsDT = false; 199 | vi.generateName(); 200 | vi; 201 | case TDynamic(x): 202 | throw 'Dynamic not supported on ${Context.getLocalClass().get().name} ${x} '; 203 | case TMono(rt): 204 | throw 'TMono not support ${t} ${rt}'; 205 | default: 206 | throw 'Not implemented on ${Context.getLocalClass().get().name} ${t} '; 207 | }; 208 | } 209 | 210 | #if oldy 211 | public static function parseComponents(type:haxe.macro.Type):ViewSpec { 212 | return switch (type) { 213 | case TInst(_, params = [x = TType(_, _) | TAnonymous(_) | TFun(_, _)]) if (params.length == 1): 214 | parseComponents(x); 215 | 216 | case TType(_.get() => {type: x}, []): 217 | parseComponents(x); 218 | 219 | case TAnonymous(_.get() => p): 220 | throw "not implemented"; 221 | // p.fields.map(function(f) return {cls: f.type.follow().toComplexType()}); 222 | case TFun(args, ret): 223 | throw "not implemented"; 224 | /* 225 | args.map(function(a) return a.t.follow().toComplexType()) 226 | .concat([ret.follow().toComplexType()]) 227 | .filter(function(ct) { 228 | return switch (ct) { 229 | case(macro:StdTypes.Void): false; 230 | default: true; 231 | } 232 | }) 233 | .map(function(ct) return {cls: ct}); 234 | */ 235 | case TInst(c, types): 236 | types.map(function(t) return t.follow().toComplexType()).map(function(ct) return {cls: ct}); 237 | 238 | case x: 239 | Context.error('Unexpected Type Param: $x', Context.currentPos()); 240 | } 241 | } 242 | #end 243 | 244 | /* 245 | 246 | */ 247 | public static function fromExplicit() {} 248 | 249 | public function generateName() { 250 | name = 'ViewOf_' 251 | + StringTools.hex(worlds, 8) 252 | + "_i_" 253 | + includes.map((x) -> x.name).join('_') 254 | + "_e_" 255 | + excludes.map((x) -> x.name).join('_'); 256 | return name; 257 | } 258 | } 259 | #end 260 | -------------------------------------------------------------------------------- /src/ecs/core/macro/ViewsOfComponentBuilder.hx: -------------------------------------------------------------------------------- 1 | package ecs.core.macro; 2 | 3 | #if macro 4 | import ecs.core.macro.MacroTools.*; 5 | import haxe.macro.Expr.ComplexType; 6 | import haxe.macro.Printer; 7 | import ecs.utils.Const; 8 | 9 | using ecs.core.macro.MacroTools; 10 | using haxe.macro.Context; 11 | using haxe.macro.ComplexTypeTools; 12 | using haxe.macro.TypeTools; 13 | using ecs.core.macro.Extensions; 14 | 15 | class ViewsOfComponentBuilder { 16 | public static final VIEW_OF_NAMESPACE = "ecs.viewsof"; 17 | 18 | // viewsOfComponentTypeName / viewsOfComponentType 19 | public static function createViewsOfComponentType(componentComplexType:ComplexType, pos):haxe.macro.Type { 20 | // Context.warning('Making view of component ${componentComplexType.toString()}', Context.currentPos()); 21 | var errorStage = ""; 22 | var componentTypeName = componentComplexType.followName(pos); 23 | var viewsOfComponentTypeName = 'ViewsOfComponent' + componentComplexType.typeFullName(pos); 24 | var viewsOfComponentTypePath = VIEW_OF_NAMESPACE + "." + viewsOfComponentTypeName; 25 | var viewsOfComponentCT = viewsOfComponentTypePath.asComplexType(); 26 | var viewsOfComponentType = viewsOfComponentCT.toTypeOrNull(Context.currentPos()); 27 | 28 | errorStage = "checking in cache"; 29 | if (viewsOfComponentType == null) { 30 | // first time call in current build 31 | // type was not cached in previous build 32 | errorStage = "building"; 33 | 34 | var viewsOfComponenttpath = tpath([], viewsOfComponentTypeName, []); 35 | 36 | errorStage = "defining"; 37 | 38 | var def = macro class $viewsOfComponentTypeName { 39 | static var instance = new $viewsOfComponenttpath(); 40 | 41 | @:keep public static inline function inst():$viewsOfComponentCT { 42 | return instance; 43 | } 44 | 45 | // instance 46 | 47 | var views = new Array(); 48 | 49 | function new() {} 50 | 51 | public inline function addRelatedView(v:ecs.core.AbstractView) { 52 | views.push(v); 53 | } 54 | 55 | public inline function addIfMatched(id:Int) { 56 | for (v in views) { 57 | if (v.isActive()) { // This is likely a bug - Needs to be removed even if not active 58 | // trace('addIfMatched: $v'); 59 | @:privateAccess v.addIfMatched(id); 60 | } 61 | } 62 | } 63 | 64 | public inline function removeIfExists(id:Int) { 65 | for (v in views) { 66 | if (v.isActive()) { // This is likely a bug - Needs to be removed even if not active 67 | // trace('removeIfExists: $v'); 68 | @:privateAccess v.removeIfExists(id); 69 | } 70 | } 71 | } 72 | } 73 | 74 | errorStage = "calling define"; 75 | 76 | def.defineTypeSafe(VIEW_OF_NAMESPACE, Const.ROOT_MODULE); 77 | 78 | #if false 79 | trace('ViewType: ${def.name}'); 80 | var p = new Printer(); 81 | trace(p.printTypeDefinition(def)); 82 | #end 83 | 84 | errorStage = "post define"; 85 | 86 | viewsOfComponentType = viewsOfComponentCT.toTypeOrNull(Context.currentPos()); 87 | 88 | if (viewsOfComponentType == null) { 89 | Context.error('Could not find or create view of component type', Context.currentPos()); 90 | return null; 91 | } 92 | errorStage = "caching"; 93 | } 94 | 95 | return viewsOfComponentType; 96 | } 97 | 98 | public static function getViewsOfComponent(componentComplexType:ComplexType, pos):ComplexType { 99 | return createViewsOfComponentType(componentComplexType, pos).toComplexType(); 100 | } 101 | } 102 | #end 103 | -------------------------------------------------------------------------------- /src/ecs/utils/Const.hx: -------------------------------------------------------------------------------- 1 | package ecs.utils; 2 | 3 | class Const { 4 | #if macro 5 | public static inline final ROOT_MODULE = "ecs.utils.Root"; 6 | #end 7 | } -------------------------------------------------------------------------------- /src/ecs/utils/FastEntitySet.hx: -------------------------------------------------------------------------------- 1 | package ecs.utils; 2 | 3 | class FastEntityArraySkippingIterator { 4 | var set:Array; 5 | var i:Int; 6 | 7 | public inline function new(set:Array) { 8 | this.set = set; 9 | i = 0; 10 | while (i < set.length && set[i] == -1) i++; 11 | } 12 | 13 | public inline function hasNext() { 14 | return i < set.length; 15 | } 16 | 17 | public inline function next() { 18 | var idx = i++; 19 | while (i < set.length && set[i] == -1) i++; 20 | return set[idx]; 21 | } 22 | } 23 | 24 | 25 | class FastEntitySet { 26 | public function new() { 27 | } 28 | var _setMap = new Map(); 29 | var _setArray = new Array(); 30 | var _freeList = new Array(); 31 | var _count = 0; 32 | 33 | public inline function add(value:Entity) { 34 | var idx = 0; 35 | if (_freeList.length > 0) { 36 | idx = _freeList.pop(); 37 | } else { 38 | idx = _setArray.length; 39 | _setArray.push(value); 40 | } 41 | // var idx = _freeList.length > 0 ? _freeList.pop() : _setArray.length; 42 | _setArray[idx] = value; 43 | _setMap.set(value, idx); 44 | _count++; 45 | } 46 | 47 | public inline function exists(value:Entity) : Bool { 48 | return _setMap.exists(value); 49 | } 50 | 51 | public inline function remove(value:Entity) : Bool{ 52 | var idx = _setMap.get(value); 53 | if (idx == null) return false; 54 | _setMap.remove(value); 55 | _setArray[idx] = -1; 56 | _freeList.push(idx); 57 | _count--; 58 | return true; 59 | } 60 | 61 | public inline function removeAll() { 62 | _setMap.clear(); 63 | _setArray.resize(0); 64 | _freeList.resize(0); 65 | _count = 0; 66 | } 67 | 68 | public inline function iterator():Iterator { 69 | return new FastEntityArraySkippingIterator(_setArray); 70 | } 71 | 72 | public var length(get, null):Int; 73 | 74 | public inline function get_length() : Int { 75 | return _count; 76 | } 77 | } 78 | 79 | @:forward(length, iterator, exists) 80 | abstract ReadOnlyFastEntitySet(ecs.utils.FastEntitySet) from ecs.utils.FastEntitySet{ 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/ecs/utils/LinkedList.hx: -------------------------------------------------------------------------------- 1 | package ecs.utils; 2 | 3 | // 4 | // [RC] There has to be a better data structure than this for views 5 | // Existance, removal are BRUTAL 6 | // 7 | 8 | /** 9 | * ... 10 | * @author https://github.com/deepcake 11 | */ 12 | #if ECS_USE_LINK_LIST 13 | @:generic 14 | class LinkedList { 15 | 16 | 17 | public var head(default, null):LinkedNode = null; 18 | public var tail(default, null):LinkedNode = null; 19 | 20 | public var length(default, null) = 0; 21 | 22 | 23 | public function new() { } 24 | 25 | 26 | public inline function iterator():LinkedListIterator { 27 | return new LinkedListIterator(head); 28 | } 29 | 30 | public function add(value:T) { 31 | var node = new LinkedNode(value); 32 | if (head == null) { 33 | head = node; 34 | } else { 35 | tail.next = node; 36 | } 37 | tail = node; 38 | length++; 39 | } 40 | 41 | public function pop():Null { 42 | if (head != null) { 43 | var value = head.value; 44 | head = head.next; 45 | if (head == null) { 46 | tail = null; 47 | } 48 | length--; 49 | return value; 50 | } else { 51 | return null; 52 | } 53 | } 54 | 55 | public function remove(value:T):Bool { 56 | var prev:LinkedNode = null; 57 | var node = head; 58 | while (node != null) { 59 | if (node.value == value) { 60 | if (prev == null) { 61 | head = node.next; 62 | } else { 63 | prev.next = node.next; 64 | } 65 | if (node == tail) { 66 | tail = prev; 67 | } 68 | length--; 69 | return true; 70 | } 71 | prev = node; 72 | node = node.next; 73 | } 74 | return false; 75 | } 76 | 77 | public function exists(value:T):Bool { 78 | var node = head; 79 | while (node != null) { 80 | if (node.value == value) return true; 81 | node = node.next; 82 | } 83 | return false; 84 | } 85 | 86 | /** 87 | * Sorts this LinkedList according to the comparison function `f`, where `f(x,y)` returns 0 if `x == y`, a positive Int if `x > y` and a negative Int if `x < y` 88 | * 89 | * __Based on `haxe.ds.ListSort.sortSingleLinked()` function with minor changes__. 90 | */ 91 | public function sort(f:T->T->Int) { 92 | var insize = 1, nmerges, psize = 0, qsize = 0; 93 | var p, q, e:LinkedNode; 94 | while (true) { 95 | p = head; 96 | head = null; 97 | tail = null; 98 | nmerges = 0; 99 | while (p != null) { 100 | nmerges++; 101 | q = p; 102 | psize = 0; 103 | for (i in 0...insize) { 104 | psize++; 105 | q = q.next; 106 | if (q == null) { 107 | break; 108 | } 109 | } 110 | qsize = insize; 111 | while (psize > 0 || (qsize > 0 && q != null)) { 112 | if (psize == 0) { 113 | e = q; 114 | q = q.next; 115 | qsize--; 116 | } else if (qsize == 0 || q == null || f(p.value, q.value) <= 0) { 117 | e = p; 118 | p = p.next; 119 | psize--; 120 | } else { 121 | e = q; 122 | q = q.next; 123 | qsize--; 124 | } 125 | if (tail != null) { 126 | tail.next = e; 127 | } else { 128 | head = e; 129 | } 130 | tail = e; 131 | } 132 | p = q; 133 | } 134 | tail.next = null; 135 | if (nmerges <= 1) { 136 | break; 137 | } 138 | insize *= 2; 139 | } 140 | } 141 | 142 | 143 | } 144 | 145 | @:allow(ecs.utils.LinkedList) 146 | @:generic 147 | class LinkedNode { 148 | 149 | public var next:LinkedNode; 150 | 151 | public var value(default, null):T; 152 | 153 | function new(value:T) { 154 | this.value = value; 155 | } 156 | 157 | } 158 | 159 | @:allow(ecs.utils.LinkedList) 160 | @:generic 161 | class LinkedListIterator { 162 | 163 | var node:LinkedNode; 164 | 165 | inline function new(node:LinkedNode) { 166 | this.node = node; 167 | } 168 | 169 | public inline function hasNext():Bool { 170 | return node != null; 171 | } 172 | 173 | public inline function next():T { 174 | var value = node.value; 175 | node = node.next; 176 | return value; 177 | } 178 | 179 | } 180 | #end -------------------------------------------------------------------------------- /src/ecs/utils/Root.hx: -------------------------------------------------------------------------------- 1 | package ecs.utils; 2 | 3 | // Proxy for dependencies 4 | class Root { 5 | 6 | } -------------------------------------------------------------------------------- /src/ecs/utils/Signal.hx: -------------------------------------------------------------------------------- 1 | package ecs.utils; 2 | #if false 3 | import ecs.utils.FastEntitySet; 4 | import ecs.utils.FastHashableSet; 5 | 6 | #if macro 7 | import haxe.macro.Expr; 8 | #end 9 | 10 | /** 11 | * ... 12 | * @author https://github.com/deepcake 13 | */ 14 | @:forward(length, remove, add, removeAll, iterator) 15 | @:generic 16 | abstract Signal(FastHashableSet) { 17 | 18 | 19 | public inline function new() this = new FastHashableSet(); 20 | 21 | 22 | public inline function has(listener:T):Bool { 23 | return this.exists(listener); 24 | } 25 | 26 | 27 | public inline function size() { 28 | return this.length; 29 | } 30 | 31 | macro public function dispatch(self:Expr, args:Array) { 32 | return macro { 33 | for (listener in $self) { 34 | listener($a{args}); 35 | } 36 | } 37 | } 38 | 39 | 40 | } 41 | 42 | #end -------------------------------------------------------------------------------- /test/ComponentTypeTest.hx: -------------------------------------------------------------------------------- 1 | import ecs.*; 2 | 3 | using buddy.Should; 4 | 5 | class ComponentTypeTest extends buddy.BuddySuite { 6 | public function new() { 7 | describe("Test Components of Different Types", { 8 | var e:Entity; 9 | var s:ComponentTypeSystem; 10 | 11 | beforeEach({ 12 | Workflow.reset(); 13 | }); 14 | 15 | describe("When create System with Views of Components of Different Types", { 16 | beforeEach({ 17 | e = new Entity(); 18 | s = new ComponentTypeSystem(); 19 | Workflow.addSystem(s); 20 | }); 21 | 22 | it("views should be empty", { 23 | s.objects.entities.length.should.be(0); 24 | s.abstractObjects.entities.length.should.be(0); 25 | s.abstractPrimitives.entities.length.should.be(0); 26 | s.enums.entities.length.should.be(0); 27 | s.enumAbstracts.entities.length.should.be(0); 28 | s.iobjects.entities.length.should.be(0); 29 | s.extendObjects.entities.length.should.be(0); 30 | s.typeParams.entities.length.should.be(0); 31 | s.nestedTypeParams.entities.length.should.be(0); 32 | }); 33 | 34 | describe("Then get Workflow info", { 35 | var str = "\\# \\( 1 \\) \\{ 9 \\} \\[ 1 \\| 0 \\]"; 36 | #if ecs_profiling 37 | str += " : \\d ms"; 38 | str += "\n ComponentTypeTest.ComponentTypeSystem : \\d ms"; 39 | str += "\n \\{ObjectComponent\\} \\[0\\]"; 40 | str += "\n \\{AbstractObjectComponent\\} \\[0\\]"; 41 | str += "\n \\{AbstractPrimitive\\} \\[0\\]"; 42 | str += "\n \\{EnumComponent\\} \\[0\\]"; 43 | str += "\n \\{EnumAbstractComponent\\} \\[0\\]"; 44 | str += "\n \\{IObjectComponent\\} \\[0\\]"; 45 | str += "\n \\{ExtendObjectComponent\\} \\[0\\]"; 46 | str += "\n \\{TypeParamComponent\\\\} \\[0\\]"; 47 | str += "\n \\{TypeParamComponent\\\\>\\} \\[0\\]"; 48 | #end 49 | beforeEach({ 50 | Workflow.update(0); 51 | }); 52 | it("should have correct result", Workflow.info().should.match(new EReg(str, ""))); 53 | }); 54 | 55 | describe("Then add an ObjectComponent", { 56 | var c1 = new ObjectComponent("A"); 57 | beforeEach(e.add(c1)); 58 | it("should be returned by ObjectComponent", e.get(ObjectComponent).should.be(c1)); 59 | it("should be returned by TypedefObjectComponent", e.get(TypedefObjectComponent).should.be(c1)); 60 | it("should not be returned by AbstractObjectComponent", e.get(AbstractObjectComponent).should.not.be(c1)); 61 | it("should be collected by View", s.objects.entities.length.should.be(1)); 62 | }); 63 | 64 | describe("Then add an AbstractObjectComponent", { 65 | var c2 = new AbstractObjectComponent("A"); 66 | beforeEach(e.add(c2)); 67 | it("should not be returned by ObjectComponent", e.get(ObjectComponent).should.not.be(c2)); 68 | it("should not be returned by TypedefObjectComponent", e.get(TypedefObjectComponent).should.not.be(c2)); 69 | it("should be returned by AbstractObjectComponent", e.get(AbstractObjectComponent).should.be(c2)); 70 | it("should be collected by View", s.abstractObjects.entities.length.should.be(1)); 71 | }); 72 | 73 | describe("Then add an AbstractPrimitiveComponent", { 74 | var c3 = new AbstractPrimitive(1); 75 | beforeEach(e.add(c3)); 76 | it("should be returned by AbstractPrimitive", e.get(AbstractPrimitive).should.be(c3)); 77 | it("should be collected by View", s.abstractPrimitives.entities.length.should.be(1)); 78 | }); 79 | 80 | describe("Then add an EnumComponent", { 81 | var c4 = EnumComponent.E1("A"); 82 | beforeEach(e.add(c4)); 83 | it("should be returned by EnumComponent", e.get(EnumComponent).should.equal(c4)); 84 | it("should return correct value", e.get(EnumComponent).should.equal(EnumComponent.E1("A"))); 85 | it("should be collected by View", s.enums.entities.length.should.be(1)); 86 | }); 87 | 88 | describe("Then add an EnumAbstractComponent", { 89 | var c5 = EnumAbstractComponent.EA1; 90 | beforeEach(e.add(c5)); 91 | it("should be returned by EnumAbstractComponent", e.get(EnumAbstractComponent).should.be(c5)); 92 | it("should return correct value", e.get(EnumAbstractComponent).should.be(EnumAbstractComponent.EA1)); 93 | it("should be collected by View", s.enumAbstracts.entities.length.should.be(1)); 94 | }); 95 | 96 | describe("Then add an IObjectComponent", { 97 | var c6 = (new ObjectComponent("A"):IObjectComponent); 98 | beforeEach(e.add(c6)); 99 | it("should be returned by IObjectComponent", e.get(IObjectComponent).should.be(c6)); 100 | it("should not be returned by ObjectComponent", e.get(ObjectComponent).should.not.be(c6)); 101 | it("should be collected by View", s.iobjects.entities.length.should.be(1)); 102 | }); 103 | 104 | describe("Then add an ExtendObjectComponent", { 105 | var c7 = new ExtendObjectComponent("A"); 106 | beforeEach(e.add(c7)); 107 | it("should be returned by ExtendObjectComponent", e.get(ExtendObjectComponent).should.be(c7)); 108 | it("should not be returned by ObjectComponent", e.get(ObjectComponent).should.not.be(c7)); 109 | it("should be collected by View", s.extendObjects.entities.length.should.be(1)); 110 | }); 111 | 112 | describe("Then add a TypeParamComponent", { 113 | var c8 = new TypeParamComponent(new ObjectComponent("A")); 114 | beforeEach(e.add(c8)); 115 | it("should be returned by TypeParamComponent", e.get(TypedefTypeParamComponent).should.be(c8)); 116 | it("should not be returned by another TypeParamComponent", e.get(TypedefAnotherTypeParamComponent).should.not.be(c8)); 117 | it("should be collected by View", s.typeParams.entities.length.should.be(1)); 118 | }); 119 | 120 | describe("Then add a NestedTypeParamComponent", { 121 | var c9 = new TypeParamComponent>([ new ObjectComponent("A") ]); 122 | beforeEach(e.add(c9)); 123 | it("should be returned by NestedTypeParamComponent", e.get(TypedefNestedTypeParamComponent).should.be(c9)); 124 | it("should not be returned by TypeParamComponent", e.get(TypedefTypeParamComponent).should.not.be(c9)); 125 | it("should not be returned by another TypeParamComponent", e.get(TypedefAnotherTypeParamComponent).should.not.be(c9)); 126 | it("should be collected by View", s.nestedTypeParams.entities.length.should.be(1)); 127 | }); 128 | }); 129 | }); 130 | } 131 | } 132 | 133 | 134 | class ObjectComponent implements IObjectComponent { 135 | var value:String; 136 | public function new(v:String) this.value = v; 137 | public function getValue() return value; 138 | } 139 | 140 | typedef TypedefObjectComponent = ObjectComponent; 141 | 142 | @:forward(getValue) 143 | abstract AbstractObjectComponent(ObjectComponent) { 144 | public function new(v:String) this = new ObjectComponent(v); 145 | } 146 | 147 | abstract AbstractPrimitive(Null) from Null to Null { 148 | public function new(i:Int) this = i; 149 | } 150 | 151 | enum EnumComponent { 152 | E1(value:String); 153 | E2(value:Int); 154 | } 155 | 156 | enum abstract EnumAbstractComponent(Null) from Null to Null { 157 | var EA1 = 1; 158 | var EA2 = 2; 159 | } 160 | 161 | interface IObjectComponent { 162 | function getValue():String; 163 | } 164 | 165 | class ExtendObjectComponent extends ObjectComponent { 166 | public function new(v:String) { 167 | super(v); 168 | } 169 | } 170 | 171 | class TypeParamComponent { 172 | var value:T; 173 | public function new(v:T) { 174 | this.value = v; 175 | } 176 | } 177 | 178 | typedef TypedefTypeParamComponent = TypeParamComponent; 179 | typedef TypedefAnotherTypeParamComponent = TypeParamComponent; 180 | 181 | typedef TypedefNestedTypeParamComponent = TypeParamComponent>; 182 | 183 | 184 | class ComponentTypeSystem extends System { 185 | public var objects:View; 186 | public var abstractObjects:View; 187 | public var abstractPrimitives:View; 188 | public var enums:View; 189 | public var enumAbstracts:View; 190 | public var iobjects:View; 191 | public var extendObjects:View; 192 | public var typeParams:View>; 193 | public var nestedTypeParams:View>>; 194 | } 195 | -------------------------------------------------------------------------------- /test/Run.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | class Run implements buddy.Buddy<[ 4 | 5 | new SignalTest(), 6 | 7 | new EntityTest(), 8 | new ComponentTypeTest(), 9 | new ViewTypeTest(), 10 | new ViewTest(), 11 | new SystemTest(), 12 | new SystemMetaTest() 13 | 14 | ]> {} -------------------------------------------------------------------------------- /test/SignalTest.hx: -------------------------------------------------------------------------------- 1 | import ecs.utils.Signal; 2 | 3 | using buddy.Should; 4 | 5 | class SignalTest extends buddy.BuddySuite { 6 | public function new() { 7 | describe("Signal", { 8 | var s:SignalO->Void>; 9 | var r:String; 10 | 11 | beforeEach({ 12 | s = new SignalO->Void>(); 13 | r = ''; 14 | }); 15 | 16 | describe("When add listener", { 17 | var f1 = function(i:Int, o:O) r += '1_$i$o'; 18 | beforeEach({ 19 | s.add(f1); 20 | }); 21 | it("should be added", s.has(f1).should.be(true)); 22 | it("should have correct size", s.size().should.be(1)); 23 | 24 | describe("When remove listener", { 25 | beforeEach({ 26 | s.remove(f1); 27 | }); 28 | it("should be removed", s.has(f1).should.be(false)); 29 | it("should have correct size", s.size().should.be(0)); 30 | 31 | describe("When dispatch", { 32 | beforeEach({ 33 | s.dispatch(1, new O('1')); 34 | }); 35 | it("should not be dispatched", r.should.be("")); 36 | }); 37 | }); 38 | 39 | describe("When remove all of listeners", { 40 | beforeEach({ 41 | s.removeAll(); 42 | }); 43 | it("should be removed", s.has(f1).should.be(false)); 44 | it("should have correct size", s.size().should.be(0)); 45 | 46 | describe("When dispatch", { 47 | beforeEach({ 48 | s.dispatch(1, new O('1')); 49 | }); 50 | it("should not be dispatched", r.should.be("")); 51 | }); 52 | }); 53 | 54 | describe("When dispatch", { 55 | beforeEach({ 56 | s.dispatch(1, new O('1')); 57 | }); 58 | it("should be dispatched", r.should.be("1_11")); 59 | }); 60 | 61 | describe("When add a second listener", { 62 | var f2 = function(i:Int, o:O) r += '2_$i$o'; 63 | beforeEach({ 64 | s.add(f2); 65 | }); 66 | it("should be added", s.has(f2).should.be(true)); 67 | it("should have correct size", s.size().should.be(2)); 68 | 69 | describe("When remove a second listener", { 70 | beforeEach({ 71 | s.remove(f2); 72 | }); 73 | it("should not be removed", s.has(f1).should.be(true)); 74 | it("should be removed", s.has(f2).should.be(false)); 75 | it("should have correct size", s.size().should.be(1)); 76 | 77 | describe("When dispatch", { 78 | beforeEach({ 79 | s.dispatch(1, new O('1')); 80 | }); 81 | it("should not be dispatched", r.should.be("1_11")); 82 | }); 83 | }); 84 | 85 | describe("When remove all of listeners", { 86 | beforeEach({ 87 | s.removeAll(); 88 | }); 89 | it("should be removed", s.has(f1).should.be(false)); 90 | it("should be removed", s.has(f2).should.be(false)); 91 | it("should have correct size", s.size().should.be(0)); 92 | 93 | describe("When dispatch", { 94 | beforeEach({ 95 | s.dispatch(1, new O('1')); 96 | }); 97 | it("should not be dispatched", r.should.be("")); 98 | }); 99 | }); 100 | 101 | describe("When dispatch", { 102 | beforeEach({ 103 | s.dispatch(1, new O('1')); 104 | }); 105 | it("should be dispatched", r.should.be("1_112_11")); 106 | }); 107 | }); 108 | }); 109 | }); 110 | } 111 | } 112 | 113 | class O { 114 | var val:String; 115 | public function new(val) this.val = val; 116 | public function toString() return val; 117 | } 118 | -------------------------------------------------------------------------------- /test/SystemMetaTest.hx: -------------------------------------------------------------------------------- 1 | using buddy.Should; 2 | 3 | import ecs.View; 4 | import ecs.Workflow; 5 | import ecs.Entity; 6 | 7 | class SystemMetaTest extends buddy.BuddySuite { 8 | public function new() { 9 | describe("Test System Meta", { 10 | 11 | beforeEach({ 12 | Workflow.reset(); 13 | BuildResult.value = ''; 14 | }); 15 | 16 | describe("When create System (meta @update)", { 17 | var entities:Array; 18 | var s1 = new SystemUpdateMeta(); 19 | 20 | describe("Then update", { 21 | beforeEach(Workflow.update(0)); 22 | it("should have correct result", { 23 | BuildResult.value.should.be(''); 24 | }); 25 | 26 | describe("Then add system to the flow", { 27 | beforeEach(Workflow.addSystem(s1)); 28 | it("should have correct result", { 29 | BuildResult.value.should.be('^'); 30 | }); 31 | 32 | describe("Then update", { 33 | beforeEach(Workflow.update(0)); 34 | it("should have correct result", { 35 | BuildResult.value.should.be('^[0____]'); 36 | }); 37 | 38 | describe("Then add Entities", { 39 | beforeEach({ 40 | entities = [ for (i in 0...2) new Entity() ]; 41 | }); 42 | it("should have correct result", { 43 | BuildResult.value.should.be('^[0____]'); 44 | }); 45 | 46 | describe("Then update", { 47 | beforeEach(Workflow.update(0)); 48 | it("should have correct result", { 49 | BuildResult.value.should.be('^[0____][0___0e0e_]'); 50 | }); 51 | 52 | describe("Then add Component A", { 53 | beforeEach({ 54 | for (e in entities) e.add(new CompA()); 55 | }); 56 | it("should have correct result", { 57 | BuildResult.value.should.be('^[0____][0___0e0e_]'); 58 | }); 59 | 60 | describe("Then update", { 61 | beforeEach(Workflow.update(0)); 62 | it("should have correct result", { 63 | BuildResult.value.should.be('^[0____][0___0e0e_][0_AA_0A0A_0e0e_0eA0eA]'); 64 | }); 65 | 66 | describe("Then add Component B", { 67 | beforeEach({ 68 | for (e in entities) e.add(new CompB()); 69 | }); 70 | it("should have correct result", { 71 | BuildResult.value.should.be('^[0____][0___0e0e_][0_AA_0A0A_0e0e_0eA0eA]'); 72 | }); 73 | 74 | describe("Then update", { 75 | beforeEach(Workflow.update(0)); 76 | it("should have correct result", { 77 | BuildResult.value.should.be('^[0____][0___0e0e_][0_AA_0A0A_0e0e_0eA0eA][0_AA_0A0A_0e0e_0eA0eA0eB0eB0eAB0eAB]'); 78 | }); 79 | 80 | describe("Then remove System from the flow", { 81 | beforeEach(Workflow.removeSystem(s1)); 82 | it("should have correct result", { 83 | BuildResult.value.should.be('^[0____][0___0e0e_][0_AA_0A0A_0e0e_0eA0eA][0_AA_0A0A_0e0e_0eA0eA0eB0eB0eAB0eAB]$'); 84 | }); 85 | 86 | describe("Then update", { 87 | beforeEach(Workflow.update(0)); 88 | it("should have correct result", { 89 | BuildResult.value.should.be('^[0____][0___0e0e_][0_AA_0A0A_0e0e_0eA0eA][0_AA_0A0A_0e0e_0eA0eA0eB0eB0eAB0eAB]$'); 90 | }); 91 | }); 92 | }); 93 | }); 94 | }); 95 | }); 96 | }); 97 | }); 98 | }); 99 | }); 100 | }); 101 | }); 102 | }); 103 | 104 | describe("When create System (meta @added/@removed)", { 105 | var entities:Array; 106 | var s1 = new SystemAddRemMeta(); 107 | 108 | describe("Then create Entities", { 109 | beforeEach({ 110 | entities = [ for (i in 0...2) new Entity().add(new CompA(), new CompB(), new CompC()) ]; 111 | }); 112 | it("should have correct result", { 113 | BuildResult.value.should.be(''); 114 | }); 115 | 116 | describe("Then add System to the flow", { 117 | beforeEach(Workflow.addSystem(s1)); 118 | it("should have correct result", { 119 | BuildResult.value.should.be('+A+A>A>A+Ae+Ae'); 120 | }); 121 | 122 | describe("Then remove System from the flow", { 123 | beforeEach(Workflow.removeSystem(s1)); 124 | it("should have correct result", { 125 | BuildResult.value.should.be('+A+A>A>A+Ae+AeA>A+Ae+AeA+Ae+A>A+Ae'); 163 | }); 164 | 165 | describe("Then destroy Entities", { 166 | beforeEach({ 167 | for (e in entities) e.destroy(); 168 | }); 169 | it("should have correct result", { 170 | BuildResult.value.should.be('+A>A+Ae+A>A+AeA+Ae+A>A+Ae'); 208 | }); 209 | 210 | describe("Then destroy Entities", { 211 | beforeEach({ 212 | for (e in entities) e.destroy(); 213 | }); 214 | it("should have correct result", { 215 | BuildResult.value.should.be('+A>A+Ae+A>A+AeA+Ae!+A>A+Ae!'); 254 | }); 255 | 256 | describe("Then destroy Entities", { 257 | beforeEach({ 258 | for (e in entities) e.destroy(); 259 | }); 260 | it("should have correct result", { 261 | BuildResult.value.should.be('+A>A+Ae!+A>A+Ae!; 321 | 322 | class BuildResult { 323 | public static var value = ''; 324 | } 325 | -------------------------------------------------------------------------------- /test/SystemTest.hx: -------------------------------------------------------------------------------- 1 | import ecs.*; 2 | 3 | using buddy.Should; 4 | 5 | class SystemTest extends buddy.BuddySuite { 6 | public function new() { 7 | describe("Test System", { 8 | var x:SystemX; 9 | var y:SystemY; 10 | 11 | beforeEach({ 12 | Workflow.reset(); 13 | x = new SystemX(); 14 | y = new SystemY(); 15 | }); 16 | 17 | describe("When create Systems X and Y", { 18 | 19 | it("x should not be active", x.isActive().should.be(false)); 20 | it("y should not be active", y.isActive().should.be(false)); 21 | it("should have correct count of systems", Workflow.systems.length.should.be(0)); 22 | it("should have correct count of views", Workflow.views.length.should.be(0)); 23 | 24 | describe("When add System X", { 25 | beforeEach({ 26 | Workflow.addSystem(x); 27 | }); 28 | it("x should be active", x.isActive().should.be(true)); 29 | it("y should not be active", y.isActive().should.be(false)); 30 | it("should have correct count of systems", Workflow.systems.length.should.be(1)); 31 | it("should have correct count of views", Workflow.views.length.should.be(2)); 32 | 33 | describe("Then add System X again", { 34 | beforeEach({ 35 | Workflow.addSystem(x); 36 | }); 37 | it("x should be active", x.isActive().should.be(true)); 38 | it("y should not be active", y.isActive().should.be(false)); 39 | it("should have correct count of systems", Workflow.systems.length.should.be(1)); 40 | it("should have correct count of views", Workflow.views.length.should.be(2)); 41 | }); 42 | 43 | describe("Then remove System X", { 44 | beforeEach({ 45 | Workflow.removeSystem(x); 46 | }); 47 | it("x should not be active", x.isActive().should.be(false)); 48 | it("y should not be active", y.isActive().should.be(false)); 49 | it("should have correct count of systems", Workflow.systems.length.should.be(0)); 50 | it("should have correct count of views", Workflow.views.length.should.be(0)); 51 | 52 | describe("Then remove System X again", { 53 | beforeEach({ 54 | Workflow.removeSystem(x); 55 | }); 56 | it("x should not be active", x.isActive().should.be(false)); 57 | it("y should not be active", y.isActive().should.be(false)); 58 | it("should have correct count of systems", Workflow.systems.length.should.be(0)); 59 | it("should have correct count of views", Workflow.views.length.should.be(0)); 60 | }); 61 | 62 | describe("Then add System X back", { 63 | beforeEach({ 64 | Workflow.addSystem(x); 65 | }); 66 | it("x should be active", x.isActive().should.be(true)); 67 | it("y should not be active", y.isActive().should.be(false)); 68 | it("should have correct count of systems", Workflow.systems.length.should.be(1)); 69 | it("should have correct count of views", Workflow.views.length.should.be(2)); 70 | }); 71 | }); 72 | 73 | describe("Then add System Y", { 74 | beforeEach({ 75 | Workflow.addSystem(y); 76 | }); 77 | it("x should be active", x.isActive().should.be(true)); 78 | it("y should be active", y.isActive().should.be(true)); 79 | it("should have correct count of systems", Workflow.systems.length.should.be(2)); 80 | it("should have correct count of views", Workflow.views.length.should.be(3)); 81 | 82 | describe("Then remove System Y", { 83 | beforeEach({ 84 | Workflow.removeSystem(y); 85 | }); 86 | it("x should be active", x.isActive().should.be(true)); 87 | it("y should not be active", y.isActive().should.be(false)); 88 | it("should have correct count of systems", Workflow.systems.length.should.be(1)); 89 | it("should have correct count of views", Workflow.views.length.should.be(2)); 90 | }); 91 | 92 | describe("Then remove System X", { 93 | beforeEach({ 94 | Workflow.removeSystem(x); 95 | }); 96 | it("x should not be active", x.isActive().should.be(false)); 97 | it("y should be active", y.isActive().should.be(true)); 98 | it("should have correct count of systems", Workflow.systems.length.should.be(1)); 99 | it("should have correct count of views", Workflow.views.length.should.be(2)); 100 | }); 101 | 102 | describe("Then reset", { 103 | beforeEach({ 104 | Workflow.reset(); 105 | }); 106 | it("x should not be active", x.isActive().should.be(false)); 107 | it("y should not be active", y.isActive().should.be(false)); 108 | it("should have correct count of systems", Workflow.systems.length.should.be(0)); 109 | it("should have correct count of views", Workflow.views.length.should.be(0)); 110 | }); 111 | 112 | describe("Then info", { 113 | var str = "\\# \\( 2 \\) \\{ 3 \\} \\[ 0 \\| 0 \\]"; 114 | #if ecs_profiling 115 | str += " : \\d ms"; 116 | str += "\n SystemTest.SystemX : \\d ms"; 117 | str += "\n SystemTest.SystemY : \\d ms"; 118 | str += "\n \\{X\\} \\[0\\]"; 119 | str += "\n \\{X\\, Y\\} \\[0\\]"; 120 | str += "\n \\{Y\\} \\[0\\]"; 121 | #end 122 | beforeEach({ 123 | Workflow.update(0); 124 | }); 125 | it("should have correct result", Workflow.info().should.match(new EReg(str, ""))); 126 | }); 127 | }); 128 | }); 129 | }); 130 | 131 | describe("When create SystemList", { 132 | var sl:SystemList; 133 | 134 | beforeEach({ 135 | sl = new SystemList(); 136 | }); 137 | 138 | it("x and y should not exists", { 139 | sl.exists(x).should.be(false); 140 | sl.exists(y).should.be(false); 141 | }); 142 | 143 | it("x should not be active", x.isActive().should.be(false)); 144 | it("y should not be active", y.isActive().should.be(false)); 145 | it("should not be active", sl.isActive().should.be(false)); 146 | it("should have correct count of systems", Workflow.systems.length.should.be(0)); 147 | it("should have correct count of views", Workflow.views.length.should.be(0)); 148 | 149 | describe("Then add Systems X and Y to the SystemList", { 150 | beforeEach({ 151 | sl.add(x); 152 | sl.add(y); 153 | }); 154 | it("x and y should exists", { 155 | sl.exists(x).should.be(true); 156 | sl.exists(y).should.be(true); 157 | }); 158 | it("x should not be active", x.isActive().should.be(false)); 159 | it("y should not be active", y.isActive().should.be(false)); 160 | it("should not be active", sl.isActive().should.be(false)); 161 | 162 | describe("Then remove Systems X and Y from the SystemList", { 163 | beforeEach({ 164 | sl.remove(x); 165 | sl.remove(y); 166 | }); 167 | it("x and y should not exists", { 168 | sl.exists(x).should.be(false); 169 | sl.exists(y).should.be(false); 170 | }); 171 | it("x should not be active", x.isActive().should.be(false)); 172 | it("y should not be active", y.isActive().should.be(false)); 173 | it("should not be active", sl.isActive().should.be(false)); 174 | }); 175 | 176 | describe("Then add SystemList to the flow", { 177 | beforeEach({ 178 | Workflow.addSystem(sl); 179 | }); 180 | it("x should be active", x.isActive().should.be(true)); 181 | it("y should be active", y.isActive().should.be(true)); 182 | it("should be active", sl.isActive().should.be(true)); 183 | it("should have correct count of systems", Workflow.systems.length.should.be(1)); 184 | it("should have correct count of views", Workflow.views.length.should.be(3)); 185 | 186 | describe("Then remove System X from the SystemList", { 187 | beforeEach({ 188 | sl.remove(x); 189 | }); 190 | it("x should not exists", sl.exists(x).should.be(false)); 191 | it("y should exists", sl.exists(y).should.be(true)); 192 | it("x should not be active", x.isActive().should.be(false)); 193 | it("y should be active", y.isActive().should.be(true)); 194 | it("should be active", sl.isActive().should.be(true)); 195 | it("should have correct count of systems", Workflow.systems.length.should.be(1)); 196 | it("should have correct count of views", Workflow.views.length.should.be(2)); 197 | 198 | describe("Then add System X back", { 199 | beforeEach({ 200 | sl.add(x); 201 | }); 202 | it("x and y should exists", { 203 | sl.exists(x).should.be(true); 204 | sl.exists(y).should.be(true); 205 | }); 206 | it("x should be active", x.isActive().should.be(true)); 207 | it("y should be active", y.isActive().should.be(true)); 208 | it("should be active", sl.isActive().should.be(true)); 209 | it("should have correct count of systems", Workflow.systems.length.should.be(1)); 210 | it("should have correct count of views", Workflow.views.length.should.be(3)); 211 | }); 212 | 213 | describe("Then remove System Y from the SystemList", { 214 | beforeEach({ 215 | sl.remove(y); 216 | }); 217 | it("x and y should not exists", { 218 | sl.exists(x).should.be(false); 219 | sl.exists(y).should.be(false); 220 | }); 221 | it("x should not be active", x.isActive().should.be(false)); 222 | it("y should not be active", y.isActive().should.be(false)); 223 | it("should be active", sl.isActive().should.be(true)); 224 | it("should have correct count of systems", Workflow.systems.length.should.be(1)); 225 | it("should have correct count of views", Workflow.views.length.should.be(0)); 226 | }); 227 | }); 228 | 229 | describe("Then remove SystemList from the flow", { 230 | beforeEach({ 231 | Workflow.removeSystem(sl); 232 | }); 233 | it("x and y should exists", { 234 | sl.exists(x).should.be(true); 235 | sl.exists(y).should.be(true); 236 | }); 237 | it("x should not be active", x.isActive().should.be(false)); 238 | it("y should not be active", y.isActive().should.be(false)); 239 | it("should not be active", sl.isActive().should.be(false)); 240 | it("should have correct count of systems", Workflow.systems.length.should.be(0)); 241 | it("should have correct count of views", Workflow.views.length.should.be(0)); 242 | }); 243 | 244 | describe("Then info", { 245 | var str = "\\# \\( 1 \\) \\{ 3 \\} \\[ 0 \\| 0 \\]"; 246 | #if ecs_profiling 247 | str += " : \\d ms"; 248 | str += "\n list : \\d ms"; 249 | str += "\n SystemTest.SystemX : \\d ms"; 250 | str += "\n SystemTest.SystemY : \\d ms"; 251 | str += "\n \\{X\\} \\[0\\]"; 252 | str += "\n \\{X\\, Y\\} \\[0\\]"; 253 | str += "\n \\{Y\\} \\[0\\]"; 254 | #end 255 | beforeEach({ 256 | Workflow.update(0); 257 | }); 258 | it("should have correct result", Workflow.info().should.match(new EReg(str, ""))); 259 | }); 260 | }); 261 | 262 | describe("Then add SystemList to SystemList", { 263 | var sl2:SystemList; 264 | 265 | beforeEach({ 266 | sl2 = new SystemList("parent"); 267 | sl2.add(sl); 268 | }); 269 | 270 | it("should exists", sl2.exists(sl).should.be(true)); 271 | it("x should not be active", x.isActive().should.be(false)); 272 | it("y should not be active", y.isActive().should.be(false)); 273 | it("should not be active", sl2.isActive().should.be(false)); 274 | 275 | describe("Then add SystemList to the flow", { 276 | beforeEach({ 277 | Workflow.addSystem(sl2); 278 | }); 279 | it("x should be active", x.isActive().should.be(true)); 280 | it("y should be active", y.isActive().should.be(true)); 281 | it("should be active", sl2.isActive().should.be(true)); 282 | it("should have correct count of systems", Workflow.systems.length.should.be(1)); 283 | it("should have correct count of views", Workflow.views.length.should.be(3)); 284 | 285 | describe("Then remove SystemList from the flow", { 286 | beforeEach({ 287 | Workflow.removeSystem(sl2); 288 | }); 289 | it("x should not be active", x.isActive().should.be(false)); 290 | it("y should not be active", y.isActive().should.be(false)); 291 | it("should not be active", sl2.isActive().should.be(false)); 292 | it("should have correct count of systems", Workflow.systems.length.should.be(0)); 293 | it("should have correct count of views", Workflow.views.length.should.be(0)); 294 | }); 295 | 296 | describe("Then info", { 297 | var str = "\\# \\( 1 \\) \\{ 3 \\} \\[ 0 \\| 0 \\]"; 298 | #if ecs_profiling 299 | str += " : \\d ms"; 300 | str += "\n parent : \\d ms"; 301 | str += "\n list : \\d ms"; 302 | str += "\n SystemTest.SystemX : \\d ms"; 303 | str += "\n SystemTest.SystemY : \\d ms"; 304 | str += "\n \\{X\\} \\[0\\]"; 305 | str += "\n \\{X\\, Y\\} \\[0\\]"; 306 | str += "\n \\{Y\\} \\[0\\]"; 307 | #end 308 | beforeEach({ 309 | Workflow.update(0); 310 | }); 311 | it("should have correct result", Workflow.info().should.match(new EReg(str, ""))); 312 | }); 313 | }); 314 | }); 315 | }); 316 | 317 | describe("Then add SystemList to the flow", { 318 | beforeEach({ 319 | Workflow.addSystem(sl); 320 | }); 321 | it("x should not exists", sl.exists(x).should.be(false)); 322 | it("y should not exists", sl.exists(y).should.be(false)); 323 | it("x should not be active", x.isActive().should.be(false)); 324 | it("y should not be active", y.isActive().should.be(false)); 325 | it("should be active", sl.isActive().should.be(true)); 326 | it("should have correct count of systems", Workflow.systems.length.should.be(1)); 327 | it("should have correct count of views", Workflow.views.length.should.be(0)); 328 | 329 | describe("Then add System X to the SystemList", { 330 | beforeEach({ 331 | sl.add(x); 332 | }); 333 | it("x should exists", sl.exists(x).should.be(true)); 334 | it("y should not exists", sl.exists(y).should.be(false)); 335 | it("x should be active", x.isActive().should.be(true)); 336 | it("y should not be active", y.isActive().should.be(false)); 337 | it("should be active", sl.isActive().should.be(true)); 338 | it("should have correct count of systems", Workflow.systems.length.should.be(1)); 339 | it("should have correct count of views", Workflow.views.length.should.be(2)); 340 | 341 | describe("Then add System Y to the SystemList", { 342 | beforeEach({ 343 | sl.add(y); 344 | }); 345 | it("x should exists", sl.exists(x).should.be(true)); 346 | it("y should exists", sl.exists(y).should.be(true)); 347 | it("x should be active", x.isActive().should.be(true)); 348 | it("y should be active", y.isActive().should.be(true)); 349 | it("should be active", sl.isActive().should.be(true)); 350 | it("should have correct count of systems", Workflow.systems.length.should.be(1)); 351 | it("should have correct count of views", Workflow.views.length.should.be(3)); 352 | }); 353 | }); 354 | }); 355 | }); 356 | }); 357 | } 358 | } 359 | 360 | class X { 361 | public function new() { }; 362 | } 363 | 364 | class Y { 365 | public function new() { }; 366 | } 367 | 368 | class SystemX extends ecs.System { 369 | var x:View; 370 | var xy:View; 371 | } 372 | 373 | class SystemY extends ecs.System { 374 | @u inline function update(y:Y) { } 375 | @u inline function updatexy(x:X, y:Y, dt:Float) { } 376 | } 377 | -------------------------------------------------------------------------------- /test/Test.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import ecs.core.macro.Global; 4 | import ecs.Workflow; 5 | import ecs.View; 6 | import ecs.Entity; 7 | import test.TestComponents; 8 | import test.TestWorlds; 9 | import test.TestSystemY; 10 | import test.TestSystemZ; 11 | import test.TestSystemA; 12 | 13 | 14 | class Test { 15 | public final TESTWORLD = 5; 16 | 17 | 18 | public static function main() { 19 | #if !macro ecsSetup(); #end 20 | var ysystem = new TestSystemY(); 21 | Workflow.addSystem(ysystem); 22 | Workflow.addSystem(new TestSystemZ()); 23 | Workflow.addSystem(new TestSystemA()); 24 | 25 | // only works with static views - factories don't work atm. 26 | //var factory = ecs.Workflow.createFactory(1, X, Y); 27 | //trace(factory); 28 | 29 | var e = new Entity(); 30 | var e2 = new Entity(); 31 | // e.add( new K() ); 32 | e.add( new F() ); 33 | e.add( new FS() ); 34 | e.remove( K ); 35 | 36 | var xxx = new X(); 37 | var fff = new F(); 38 | 39 | // e.add( TagA.VALID ); 40 | // e.add( TagB.VALID ); 41 | e.add( TagA ); 42 | trace( 'e.TagA is ${e.get(TagA)} has ${e.has(TagA)}'); 43 | e.add( TagB ); 44 | trace( 'e.TagA is ${e.get(TagA)} has ${e.has(TagA)}'); 45 | e.remove(TagA); 46 | trace( 'e.TagA is ${e.get(TagA)} has ${e.has(TagA)}'); 47 | e.add(TagA); 48 | // e.remove(TagB); 49 | e.add( xxx ); 50 | e.add( new Y() ); 51 | trace('y view count ${ysystem.ycount()}'); 52 | trace( 'e.TagA is ${e.get(TagA)}'); 53 | trace( 'e.TagA.test is ${e.get(TagA).test}'); 54 | e.get(TagA).test = 1; 55 | trace( 'e.TagA.test is ${e.get(TagA).test}'); 56 | e2.add( TagA ); 57 | trace( 'e2.TagA.test is ${e.get(TagA).test}'); 58 | 59 | trace ('E has tag a ${e.has(TagA)} b ${e.has(TagB)} a.test is ${e.get(TagA).test}'); 60 | trace ('E has tag Y ${e.has(Y)}'); 61 | trace('PRE SHELVE y view count ${ysystem.ycount()}'); 62 | e.shelve(Y); 63 | trace('POST SHELVE y view count ${ysystem.ycount()}'); 64 | trace ('E has tag Y ${e.has(Y)}'); 65 | e.unshelve(Y); 66 | trace('POST UNSHELVE y view count ${ysystem.ycount()}'); 67 | trace ('E has tag Y ${e.has(Y)}'); 68 | // xxx.a; 69 | 70 | Workflow.update(1.); 71 | 72 | 73 | } 74 | static function ecsSetup() { 75 | Global.setup(); 76 | } 77 | } -------------------------------------------------------------------------------- /test/TestComponents.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | @:storage(FAST) 4 | class X { 5 | public var a : Float; 6 | public inline function new() { }; 7 | 8 | } 9 | 10 | @:storage(FLAT) 11 | class F { 12 | public var a : Float = 0.0; 13 | public inline function new() { }; 14 | public inline function copy( from : F ) { 15 | this.a = from.a; 16 | } 17 | } 18 | 19 | @:storage(FLAT) 20 | @:struct 21 | class FS { 22 | public var a : Float= 0.0; 23 | public inline function new() { }; 24 | public inline function copy( from : FS ) { 25 | this.a = from.a; 26 | } 27 | } 28 | 29 | 30 | @:storage(COMPACT) 31 | class Y { 32 | public var b : Float; 33 | public function new() { }; 34 | } 35 | class Z { 36 | public function new() { }; 37 | } 38 | 39 | class K { 40 | public function new () {} 41 | } 42 | 43 | @:storage(TAG) 44 | class TagA { 45 | function new () {} 46 | 47 | public var test = 0; 48 | 49 | } 50 | 51 | @:storage(TAG) 52 | class TagB { 53 | function new () {} 54 | public var test = "hi"; 55 | } 56 | -------------------------------------------------------------------------------- /test/TestSystemA.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import test.TestComponents; 4 | 5 | class TestSystemA extends ecs.System { 6 | @:not(Y) 7 | var x:View; 8 | 9 | var xz:View; 10 | var xy:View; 11 | 12 | @:added 13 | function addedF(f : F) { 14 | trace("Added TestSystemA F"); 15 | } 16 | 17 | @:added 18 | function added(ix:X, e:ecs.Entity) { 19 | trace ("Added TestSystemA X"); 20 | } 21 | 22 | @:removed 23 | function removed(ix:X) { 24 | 25 | } 26 | 27 | @:not(Y) 28 | @:u function updateA(ix:X) { 29 | trace("SystemA|updateA"); 30 | 31 | } 32 | 33 | @:u function updateB(ix:X, iy:Y) { 34 | trace("SystemA|updateB"); 35 | } 36 | 37 | @:u function updateC(f : TagA, iy:Y) { 38 | trace('SystemA|updateC tag ${f.test}'); 39 | 40 | 41 | } 42 | 43 | function blah(ix:X) { 44 | 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /test/TestSystemY.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import test.TestComponents; 4 | 5 | class TestSystemY extends ecs.System { 6 | 7 | var viewY : ecs.View; 8 | 9 | public function ycount() { 10 | return viewY.entities.length; 11 | } 12 | @:worlds(TestWorlds.TESTWORLDA) 13 | @:u function updateA(y:Y) { 14 | trace("SystemY|update"); 15 | 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /test/TestSystemZ.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import test.TestComponents; 4 | 5 | 6 | class TestSystemZ extends ecs.System { 7 | 8 | 9 | @:pool_rent 10 | function onRent() { 11 | 12 | } 13 | 14 | @:worlds(TestWorlds.TESTWORLDA) 15 | @:u function updateA(y:Y) { 16 | trace("SystemY|update"); 17 | 18 | 19 | } 20 | 21 | 22 | } 23 | 24 | -------------------------------------------------------------------------------- /test/TestSystemsB.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import test.TestComponents; 4 | 5 | class TestSystemsB extends ecs.System { 6 | // @:not(Y) 7 | // var x:View; 8 | 9 | // var xz:View; 10 | //var xy:View; 11 | 12 | @:not(Y) 13 | @:u function updateA(ix:X) { 14 | trace("SystemX|update"); 15 | 16 | } 17 | 18 | @:u function updateB(ix:X, iy:Y) { 19 | trace("SystemX|update"); 20 | 21 | 22 | 23 | 24 | } 25 | 26 | @:u function updateC(iy:Y) { 27 | trace("SystemX|update"); 28 | 29 | 30 | } 31 | 32 | function blah(ix:X) { 33 | 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /test/TestWorlds.hx: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | class TestWorlds { 4 | public static inline final TESTWORLDA : Int = 5; 5 | } -------------------------------------------------------------------------------- /test/ViewTypeTest.hx: -------------------------------------------------------------------------------- 1 | using buddy.Should; 2 | 3 | import ecs.View; 4 | import ecs.Workflow; 5 | import ecs.Entity; 6 | 7 | class ViewTypeTest extends buddy.BuddySuite { 8 | public function new() { 9 | describe("Using View with Different Type Params", { 10 | 11 | beforeEach({ 12 | Workflow.reset(); 13 | }); 14 | 15 | describe("When define a View with Different Type Params", { 16 | var sys:ViewTypeSystem; 17 | 18 | beforeEach(sys = new ViewTypeSystem()); 19 | 20 | it("should be equals", { 21 | sys.func.should.be(StandaloneVAVBSystem.ab); 22 | sys.funcReversed.should.be(StandaloneVAVBSystem.ab); 23 | sys.funcShort.should.be(StandaloneVAVBSystem.ab); 24 | sys.anon.should.be(StandaloneVAVBSystem.ab); 25 | sys.anonTypedef.should.be(StandaloneVAVBSystem.ab); 26 | sys.viewTypedef.should.be(StandaloneVAVBSystem.ab); 27 | sys.rest.should.be(StandaloneVAVBSystem.ab); 28 | sys.restReversed.should.be(StandaloneVAVBSystem.ab); 29 | }); 30 | 31 | describe("When add System to the flow", { 32 | beforeEach(Workflow.addSystem(sys)); 33 | 34 | it("should have correct count of views", { 35 | Workflow.views.length.should.be(1); 36 | }); 37 | 38 | describe("When add standalone System to the flow", { 39 | beforeEach(Workflow.addSystem(new StandaloneVAVBSystem())); 40 | 41 | it("should have correct count of views", { 42 | Workflow.views.length.should.be(1); 43 | }); 44 | }); 45 | 46 | describe("When remove System from the flow", { 47 | beforeEach(Workflow.removeSystem(sys)); 48 | 49 | it("should have correct count of views", { 50 | Workflow.views.length.should.be(0); 51 | }); 52 | }); 53 | }); 54 | }); 55 | 56 | }); 57 | } 58 | } 59 | 60 | abstract VA(String) { 61 | public function new() this = 'A'; 62 | } 63 | 64 | abstract VB(String) { 65 | public function new() this = 'B'; 66 | } 67 | 68 | abstract VC(String) { 69 | public function new() this = 'C'; 70 | } 71 | 72 | typedef VAVBTypedef = { a:VA, b:VB }; 73 | 74 | typedef ViewVAVBTypedef = View<{ a:VA, b:VB }>; 75 | 76 | class ViewTypeSystem extends ecs.System { 77 | 78 | public var func:ViewVB->Void>; 79 | 80 | public var funcReversed:ViewVA->Void>; 81 | 82 | public var funcShort:ViewVB>; 83 | 84 | public var anon:View<{ a:VA, b:VB }>; 85 | 86 | public var anonTypedef:View; 87 | 88 | public var viewTypedef:ViewVAVBTypedef; 89 | 90 | public var rest:View; 91 | 92 | public var restReversed:View; 93 | 94 | @u function ab(a:VA, b:VB) { } 95 | 96 | @u function ba(b:VB, a:VA) { } 97 | 98 | @u function cd(c:VB, d:VA) { } 99 | 100 | @u function fab(f:Float, a:VA, b:VB) { } 101 | 102 | @u function eab(e:Entity, a:VA, b:VB) { } 103 | 104 | @u function iab(i:Int, a:VA, b:VB) { } 105 | 106 | @u function feab(f:Float, e:Entity, a:VA, b:VB) { } 107 | 108 | } 109 | 110 | class StandaloneVAVBSystem extends ecs.System { 111 | public static var ab:View; 112 | } 113 | -------------------------------------------------------------------------------- /tests.hxml: -------------------------------------------------------------------------------- 1 | -cp src 2 | -cp test 3 | -lib buddy 4 | -lib tink_macro 5 | -main Run 6 | -dce full 7 | -D analyzer-optimize 8 | -D buddy-ignore-passing-specs 9 | --------------------------------------------------------------------------------