├── .gitignore ├── source └── star │ └── entity │ ├── package.d │ ├── engine.d │ ├── system.d │ ├── event.d │ └── entity.d ├── dub.json ├── NOTICE ├── MIT ├── LICENSE ├── README.md └── APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | .dub/ 2 | bin/ 3 | docs/ 4 | dub.selections.json -------------------------------------------------------------------------------- /source/star/entity/package.d: -------------------------------------------------------------------------------- 1 | module star.entity; 2 | 3 | public import star.entity.engine; 4 | public import star.entity.entity; 5 | public import star.entity.system; 6 | public import star.entity.event; 7 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "star-entity", 3 | "description": "An open-source D entity-component-system.", 4 | "homepage": "https://github.com/jzhu98/star-entity", 5 | "authors": ["jzhu98"], 6 | "copyright": "Copyright © 2014 James Zhu", 7 | "license": "MIT", 8 | 9 | "targetPath": "bin" 10 | } 11 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | EntityX 2 | --- 3 | Copyright (C) 2012 Alec Thomas 4 | Used under the MIT License (Expat). See the file MIT for the full text. 5 | 6 | ashley 7 | --- 8 | Copyright (c) 2013 Stefan Bachmann 9 | This software contains code derived from Ash (http://www.ashframework.org/) and Artemis (http://gamadu.com/artemis/). 10 | 11 | Used under the Apache 2.0 License. See the file APACHE for the full text. 12 | -------------------------------------------------------------------------------- /source/star/entity/engine.d: -------------------------------------------------------------------------------- 1 | /// 2 | /// Convenience class wrapping the entire ECS framework. 3 | /// 4 | /// Copyright: Copyright (c) 2014 James Zhu. 5 | /// 6 | /// License: MIT License (Expat). See accompanying file LICENSE. 7 | /// 8 | /// Authors: James Zhu 9 | /// 10 | 11 | module star.entity.engine; 12 | 13 | import star.entity.entity; 14 | import star.entity.system; 15 | import star.entity.event; 16 | 17 | /// Encapsulates components, systems, entities, and events. 18 | class Engine 19 | { 20 | this() 21 | { 22 | events = new EventManager; 23 | entities = new EntityManager(events); 24 | systems = new SystemManager(entities, events); 25 | } 26 | 27 | EventManager events; 28 | EntityManager entities; 29 | SystemManager systems; 30 | } 31 | -------------------------------------------------------------------------------- /MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 James Zhu. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /source/star/entity/system.d: -------------------------------------------------------------------------------- 1 | /// 2 | /// Defines an architecture to manage systems. 3 | /// Systems must implement the System interface. 4 | /// 5 | /// Copyright: Copyright (c) 2014 James Zhu. 6 | /// 7 | /// License: MIT License (Expat). See accompanying file LICENSE. 8 | /// 9 | /// Authors: James Zhu 10 | /// 11 | 12 | module star.entity.system; 13 | 14 | import std.traits : fullyQualifiedName; 15 | 16 | import star.entity.entity; 17 | import star.entity.event; 18 | 19 | /// A generic system, encapsulating game logic. 20 | interface System 21 | { 22 | /// Used to register events. 23 | void configure(EventManager events); 24 | 25 | /// Used to update entities. 26 | void update(EntityManager entities, EventManager events, double dt); 27 | } 28 | 29 | /// Manages systems and their execution. 30 | class SystemManager 31 | { 32 | public: 33 | /// Constructor taking events and entities. 34 | this(EntityManager entityManager, EventManager eventManager) pure nothrow @safe 35 | { 36 | _entityManager = entityManager; 37 | _eventManager = eventManager; 38 | } 39 | 40 | /// Add a system to the manager. 41 | void add(S)(S system) pure nothrow @trusted if (is (S : System)) 42 | in 43 | { 44 | assert(!(system.classinfo.name in _systems)); 45 | } 46 | body 47 | { 48 | _systems[system.classinfo.name] = system; 49 | _systems.rehash(); 50 | } 51 | 52 | /// Remove a system from the manager. 53 | void remove(S)(S system) pure nothrow @safe 54 | { 55 | _systems.remove(system.classinfo.name); 56 | } 57 | 58 | /// Return the specified system. 59 | inout(S) system(S)() inout pure nothrow @safe 60 | { 61 | if (S.classinfo.name in _systems) 62 | { 63 | return cast(inout(S)) _systems[S.classinfo.name]; 64 | } 65 | else 66 | { 67 | return null; 68 | } 69 | } 70 | 71 | /// Configure every system added to the manager. 72 | void configure() 73 | { 74 | foreach(system; _systems) 75 | { 76 | system.configure(_eventManager); 77 | } 78 | _configured = true; 79 | } 80 | 81 | /// Update entities with the specified system. 82 | void update(S)(double dt) 83 | in 84 | { 85 | assert(_configured); 86 | } 87 | body 88 | { 89 | _systems[S.classinfo.name].update(_entityManager, _eventManager, dt); 90 | } 91 | 92 | private: 93 | EntityManager _entityManager; 94 | EventManager _eventManager; 95 | System[string] _systems; 96 | bool _configured = false; 97 | } 98 | -------------------------------------------------------------------------------- /source/star/entity/event.d: -------------------------------------------------------------------------------- 1 | /// 2 | /// Defines an architecture for event receiving and subscribing. 3 | /// Events may be structs or components. 4 | /// Event receivers must implement the Receiver(E) interface. 5 | /// 6 | /// Copyright: Copyright (c) 2014 James Zhu. 7 | /// 8 | /// License: MIT License (Expat). See accompanying file LICENSE. 9 | /// 10 | /// Authors: James Zhu 11 | /// 12 | 13 | module star.entity.event; 14 | 15 | import std.traits; 16 | 17 | private interface BaseReceiver 18 | { 19 | } 20 | 21 | template typename(C) 22 | { 23 | alias typename = mangledName!C; 24 | } 25 | 26 | /// Recieves events of type E. 27 | interface Receiver(E) : BaseReceiver 28 | { 29 | /// Event callback for an event E. 30 | void receive(E event) pure nothrow; 31 | } 32 | 33 | /// Manages event subscription and emission. 34 | class EventManager 35 | { 36 | public: 37 | /// Subscribe reciever to a certain event E. 38 | void subscribe(E)(Receiver!E receiver) pure nothrow @safe 39 | { 40 | auto name = typename!E; 41 | if (name in _receivers) 42 | { 43 | _receivers[name] ~= receiver; 44 | } 45 | else 46 | { 47 | _receivers[name] = [receiver]; 48 | } 49 | } 50 | 51 | /// Notify all receivers of an event E. Calls their receive callback. 52 | void emit(E)(E event) pure nothrow @trusted 53 | { 54 | if (typename!E in _receivers) 55 | { 56 | foreach(r; _receivers[typename!E]) 57 | { 58 | auto receiver = cast(Receiver!E) r; 59 | receiver.receive(event); 60 | } 61 | } 62 | } 63 | private: 64 | BaseReceiver[][string] _receivers; 65 | } 66 | 67 | unittest 68 | { 69 | struct Explosion { } 70 | 71 | class Block : Receiver!Explosion 72 | { 73 | bool destroyed = false; 74 | void receive(Explosion event) 75 | { 76 | destroyed = true; 77 | } 78 | } 79 | 80 | auto manager = new EventManager; 81 | auto block = new Block; 82 | assert(block.destroyed == false); 83 | 84 | manager.subscribe!Explosion(block); 85 | assert(manager._receivers.length == 1); 86 | assert(typename!Explosion in manager._receivers); 87 | assert(manager._receivers[typename!Explosion].length == 1); 88 | 89 | bool hasReceiver(E)(EventManager manager, Receiver!E receiver) 90 | { 91 | bool result = false; 92 | foreach(r; manager._receivers[typename!E]) 93 | { 94 | if (r == receiver) 95 | { 96 | result = true; 97 | } 98 | } 99 | return result; 100 | } 101 | 102 | assert(hasReceiver!Explosion(manager, block)); 103 | 104 | manager.emit(Explosion()); 105 | assert(block.destroyed == true); 106 | } 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | star-entity 2 | =========== 3 | 4 | An open-source entity-component-system, written in D. 5 | **star-entity** offers component management, entity creation, event delivery, 6 | and system management. 7 | 8 | This framework is essentially a D port of 9 | [EntityX](https://github.com/alecthomas/entityx) by Alec Thomas, with some features 10 | from [Ashley](https://github.com/libgdx/ashley), managed by Badlogic Games. 11 | 12 | ## Overview 13 | The framework is modeled after the Entity-Component-System (ECS) architecture, a form 14 | of decomposition that decouples logic and data, and using composition instead 15 | of inheritance to allow greater flexibility and modular functionality. 16 | 17 | Essentially, data is condensed into a component, a simple data class, and 18 | an `Entity` is simply an aggregate of these components. 19 | `Systems` encapsulates logic and operates upon a specific subset of entities, 20 | namely those with specific components. 21 | `Events` allow for system interaction without tight coupling. 22 | 23 | As an example: a game might have players with *health*, *sword*, *speed*, 24 | *position*, *bounds* and *sprite*, and walls would have *position*, *bounds*, 25 | and *sprite*. 26 | 27 | The graphics system would need only the *position* and *sprite* components, 28 | whereas the physics might require the *position* and *bounds*. 29 | 30 | If the player collided with the wall, the physics system might emit a 31 | *collision* event. 32 | 33 | The article 34 | [Evolve your Hierarchy](https://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/) 35 | offers a great introduction and overview of ECS frameworks and how they can 36 | make your code more modular, more extensible, and simpler. 37 | 38 | ## Building 39 | This project uses the DUB build system, found [here](https://code.dlang.org/download). 40 | 41 | To build the project, simply run in the top-level directory 42 | 43 | ```sh 44 | dub build --build=release 45 | ``` 46 | 47 | To use this project as a dependency, add the latest version (see [Releases](https://github.com/jzhu98/star-entity/releases)) to your **dub.json**: 48 | 49 | ```json 50 | "dependencies": { 51 | "star-entity": "~>1.0.8" 52 | } 53 | ``` 54 | 55 | ## Usage 56 | 57 | Some example code to implement the aforementioned physics system: 58 | 59 | ### Entities 60 | `star.entity.Entity` wraps on opaque index (uint) that is used to 61 | add, remove, or retrieve components in its corresponding 62 | `star.entity.EntityManager`. 63 | 64 | Creating an entity is done by 65 | 66 | ```c++ 67 | import star.entity; 68 | 69 | auto engine = new Engine; 70 | auto entity = engine.entities.create(); 71 | ``` 72 | 73 | The entity is destroyed by 74 | ``` 75 | entity.destroy(); 76 | ``` 77 | 78 | #### Implementation details: 79 | - The entity wraps an index (uint) and a tag (uint). 80 | - `Entity` acts as a *handle*, meaning that multiple `Entities` may refer to 81 | the same entity. 82 | - `Entity.invalidate()` is used to invalidate the handle, meaning it can 83 | no longer be used. The data, however, is still intact and is still accessible. 84 | - `Entity.destroy()` is used to invalidate all handles and deallocate the data, 85 | freeing the index for reuse by a new entity. 86 | - `Entity.valid()` should always be used to check validity before usage. 87 | - Destruction is done by incrementing the tag; thus making all current 88 | `Entities` tags unequal and invalid. 89 | 90 | ### Components 91 | Components should be designed to hold data, and have few methods (if any). 92 | At the moment, they must be implemented as **classes** (for internal storage), but 93 | in the future I hope to implement templates properly to enable using POD 94 | structs. 95 | 96 | #### Creation 97 | Continuing our previous example of a physics system: 98 | ```c++ 99 | class Position 100 | { 101 | this(double x, double y) { this.x = x; this.y = y; } 102 | double x, y; 103 | } 104 | 105 | class Velocity 106 | { 107 | this(double x, double y) { this.x = x; this.y = y; } 108 | double x, y; 109 | } 110 | 111 | class Gravity 112 | { 113 | this(double accel) { this.accel = accel; } 114 | double accel; 115 | } 116 | ``` 117 | 118 | #### Assignment 119 | To associate these components with an entity, call `Entity.add(C)(C component)`: 120 | 121 | ```c++ 122 | entity.add(new Position(1.0, 2.0)); 123 | entity.add(new Velocity(15.0, -2.0)); 124 | entity.add(new Gravity(-9.8)); 125 | ``` 126 | 127 | #### Querying 128 | To access all entities with specific components, use 129 | `EntityManager.entities!(Components...)()`: 130 | 131 | ```cs 132 | foreach(entity; engine.entities.entities!(Position, Velocity)) 133 | { 134 | // Do work with entities containing Position and Velocity components 135 | } 136 | ``` 137 | 138 | To access a specific entity's component, use 139 | `Entity.component!(C)()`: 140 | 141 | ```c++ 142 | auto velocity = entity.component!Velocity(); 143 | ``` 144 | 145 | ### Systems 146 | Systems implement logic and behavior. 147 | They must implement the `star.system.System` interface 148 | (`configure()` and `update()`) 149 | 150 | Continuing our physics example, let's implement a movement and gravity system: 151 | 152 | ```cs 153 | class MovementSystem : System 154 | { 155 | void configure(EventManager events) { } 156 | void update(EntityManager entities, EventManager events, double dt) 157 | { 158 | foreach(entity; entities.entities!(Position, Velocity)()) 159 | { 160 | auto position = entity.component!Position(); 161 | auto velocity = entity.component!Velocity(); 162 | position.x += velocity.x * dt; 163 | position.y += velocity.y * dt; 164 | } 165 | } 166 | } 167 | 168 | class GravitySystem : System 169 | { 170 | void configure(EventManager events) { } 171 | void update(EntityManager entities, EventManager events, double dt) 172 | { 173 | foreach(entity; entities.entities!(Velocity, Gravity)()) 174 | { 175 | auto gravity = entity.component!gravity(); 176 | auto velocity = entity.component!Velocity(); 177 | auto accel = gravity.accel * dt; 178 | if (antigravity) 179 | { 180 | accel = -accel; 181 | } 182 | velocity.y += accel; 183 | } 184 | } 185 | private: 186 | bool antigravity = false; 187 | } 188 | ``` 189 | 190 | Adding them to the system manager is quite simple: 191 | 192 | ```c++ 193 | engine.systems.add(new MovementSystem); 194 | engine.systems.add(new GravitySystem); 195 | ``` 196 | 197 | ### Events 198 | Events are objects (structs or classes) that indicate something has occured, 199 | e.g. a collision, button press, mouse event, etc. 200 | Instead of setting component flags, events offer a simple way of notifying 201 | other classes of infrequent data, using callbacks. 202 | 203 | #### Event types 204 | 205 | Events can be either structs or classes. 206 | No interfaces or class extension necessary. 207 | 208 | ```css 209 | struct Collision 210 | { 211 | Entity first, second; 212 | } 213 | ``` 214 | 215 | #### Event emission 216 | 217 | Our collision system will emit a Collision object if two objects collide. 218 | (Ignore the slow algorithm below without any of that fancy "spatial 219 | partitioning". This is just an example.) 220 | 221 | ```cs 222 | class CollisionSystem : System 223 | { 224 | void configure(EventManager events) { } 225 | void update(EntityManager entities, EventManager events, double dt) 226 | { 227 | foreach(first; entities.entities!(Position)) 228 | { 229 | foreach(second; entities.entites!(Position)()) 230 | { 231 | if (collides(first, second)) 232 | { 233 | events.emit(Collision {first, second}); 234 | } 235 | } 236 | } 237 | } 238 | } 239 | ``` 240 | 241 | #### Event subscription 242 | 243 | Classes intending to receive specific events should implement the 244 | `Receiver(E)` interface, for events of type E. 245 | 246 | ```cs 247 | class DebugSystem : System, Receiver!Collision 248 | { 249 | void configure(EventManager events) 250 | { 251 | events.subscribe!Collision(this); 252 | } 253 | 254 | void update(EntityManager entities, EventManager events, double dt) { } 255 | 256 | void receive(E)(E event) pure nothrow if (is(E : Collision)) 257 | { 258 | try 259 | { 260 | debug writefln("Entities collided: %s, %s", event.first.id, event.second.id); 261 | } 262 | catch (Throwable o) 263 | { 264 | } 265 | } 266 | } 267 | ``` 268 | 269 | `` 270 | For those of you who've made it so far: should `pure nothrow` be enforced upon 271 | the `receive` callback? 272 | `` 273 | 274 | A few events are emitted by the **star-entity** library: 275 | - `EntityCreatedEvent` 276 | - `EntityDestroyedEvent` 277 | - `ComponentAddedEvent(C)` 278 | - `ComponentRemovedEvent(C)` 279 | 280 | ### Engine 281 | The engine ties everything together. It allows you to perform everything listed 282 | above, and manage your own game / input loop. 283 | 284 | ```c++ 285 | while (true) 286 | { 287 | engine.update(0.02); 288 | } 289 | ``` 290 | 291 | ## License 292 | This code is licensed under the MIT License. See LICENSE for the full text. 293 | -------------------------------------------------------------------------------- /APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | -------------------------------------------------------------------------------- /source/star/entity/entity.d: -------------------------------------------------------------------------------- 1 | /// 2 | /// Defines an architecture to manage entities, and several entity-related 3 | /// events. Components must be defined as classes (for internal storage). 4 | /// 5 | /// Copyright: Copyright (c) 2014 James Zhu. 6 | /// 7 | /// License: MIT License (Expat). See accompanying file LICENSE. 8 | /// 9 | /// Authors: James Zhu 10 | /// 11 | 12 | module star.entity.entity; 13 | 14 | import std.container; 15 | import std.conv; 16 | import std.algorithm : filter; 17 | import std.math : abs; 18 | 19 | import star.entity.event; 20 | 21 | /// An id encapsulates an index (unique ulong in an entity manager) 22 | /// and a tag (to check if the entity is in sync (valid) with the manager). 23 | struct ID 24 | { 25 | public: 26 | /// An invalid id, used for invalidating entities. 27 | static immutable ID INVALID = ID(0, 0); 28 | 29 | /// Construct an id from a 64-bit integer: 30 | /// A concatenated 32-bit index and 32-bit tag. 31 | this(ulong id) pure nothrow @safe 32 | { 33 | _id = id; 34 | } 35 | 36 | /// Construct an id from a 32-bit index and a 32-bit tag. 37 | this(uint index, uint tag) pure nothrow @safe 38 | { 39 | this(cast(ulong) index << 32UL | (cast(ulong) tag)); 40 | } 41 | 42 | /// Return the index of the ID. 43 | inout(uint) index() inout pure nothrow @property @safe 44 | { 45 | return cast(uint)(_id >> 32UL); 46 | } 47 | 48 | unittest 49 | { 50 | auto id1 = ID(0x0000000100000002UL); 51 | auto id2 = ID(3U, 4U); 52 | assert(id1.index == 1U); 53 | assert(id2.index == 3U); 54 | } 55 | 56 | /// Return the tag of the ID. 57 | inout(uint) tag() inout pure nothrow @property @safe 58 | { 59 | return cast(uint) (_id); 60 | } 61 | 62 | unittest 63 | { 64 | auto id1 = ID((12UL << 32) + 13UL); 65 | auto id2 = ID(210U, 5U); 66 | assert(id1.tag == 13U); 67 | assert(id2.tag == 5U); 68 | } 69 | 70 | string toString() const pure @safe 71 | { 72 | return "ID(" ~ to!string(this.index) ~ ", " ~ to!string(this.tag) ~ ")"; 73 | } 74 | 75 | /// Equals operator (check for equality). 76 | bool opEquals()(auto ref const ID other) const pure nothrow @safe 77 | { 78 | return _id == other._id; 79 | } 80 | 81 | unittest 82 | { 83 | auto id1 = ID(12U, 32U); 84 | auto id2 = ID(12U, 32U); 85 | auto id3 = ID(13U, 32U); 86 | assert(id1 == id2); 87 | assert(id1 != id3); 88 | assert(id2 != id3); 89 | } 90 | 91 | /// Comparison operators (check for greater / less than). 92 | int opCmp(ref const ID other) const pure nothrow @safe 93 | { 94 | if (_id > other._id) 95 | { 96 | return 1; 97 | } 98 | else if (_id == other._id) 99 | { 100 | return 0; 101 | } 102 | else 103 | { 104 | return -1; 105 | } 106 | } 107 | 108 | unittest 109 | { 110 | auto id1 = ID(1U, 10U); 111 | auto id2 = ID(1U, 11U); 112 | auto id3 = ID(2U, 1U); 113 | assert(id1 < id2); 114 | assert(id1 <= id3); 115 | assert(id3 >= id2); 116 | assert(id3 > id1); 117 | } 118 | 119 | private: 120 | ulong _id; 121 | } 122 | 123 | /// An entity an aggregate of components (pure data), accessible with an id. 124 | class Entity 125 | { 126 | public: 127 | /// Construct an entity with a manager reference and an ID. 128 | this(EntityManager manager, ID id) pure nothrow @safe 129 | { 130 | _manager = manager; 131 | _id = id; 132 | } 133 | 134 | /// Return the entity id. 135 | inout(ID) id() inout pure nothrow @property @safe 136 | { 137 | return _id; 138 | } 139 | 140 | unittest 141 | { 142 | auto entity = new Entity(null, ID(1, 2)); 143 | assert(entity.id.index == 1); 144 | assert(entity.id.tag == 2); 145 | } 146 | 147 | /// Return the component added to this entity. 148 | inout(C) component(C)() inout pure nothrow @safe 149 | { 150 | return _manager.component!C(_id); 151 | } 152 | 153 | /// Check if the entity has a specific component. 154 | bool hasComponent(C)() pure nothrow @safe 155 | { 156 | return _manager.hasComponent!C(_id); 157 | } 158 | 159 | /// Add a component to the entity. 160 | void add(C)(C component) pure nothrow @safe 161 | { 162 | _manager.addComponent!C(_id, component); 163 | } 164 | 165 | /// Remove the component if the entity has it. 166 | void remove(C)() pure nothrow @safe 167 | { 168 | _manager.remove!C(_id); 169 | } 170 | 171 | /// Destroy this entity and invalidate all handles to this entity. 172 | void destroy() pure nothrow @safe 173 | { 174 | _manager.destroy(_id); 175 | invalidate(); 176 | } 177 | 178 | /// Check if this handle is valid (points to the entity with the same tag). 179 | bool valid() pure nothrow @safe 180 | { 181 | if (_manager is null) 182 | { 183 | return false; 184 | } 185 | else 186 | { 187 | return _manager.valid(_id); 188 | } 189 | } 190 | 191 | /// Invalidate this entity handle (but not other handles). 192 | void invalidate() pure nothrow @safe 193 | { 194 | _manager = null; 195 | _id = ID.INVALID; 196 | } 197 | 198 | unittest 199 | { 200 | auto entity = new Entity(null, ID.INVALID); 201 | assert(!entity.valid()); 202 | } 203 | 204 | /// Equals operator (check for equality). 205 | override bool opEquals(Object o) const 206 | { 207 | auto other = cast(Entity) o; 208 | return _id == other._id && _manager == other._manager; 209 | } 210 | 211 | unittest 212 | { 213 | auto entity1 = new Entity(null, ID(1, 1)); 214 | auto entity2 = new Entity(null, ID(1, 1)); 215 | auto entity3 = new Entity(null, ID(1, 2)); 216 | auto entity4 = new Entity(null, ID(2, 1)); 217 | 218 | assert(entity1 == entity1); 219 | assert(entity1 == entity2); 220 | assert(entity1 != entity3); 221 | assert(entity1 != entity4); 222 | 223 | assert(entity2 == entity1); 224 | assert(entity2 == entity2); 225 | assert(entity2 != entity3); 226 | assert(entity2 != entity4); 227 | 228 | assert(entity3 != entity1); 229 | assert(entity3 != entity2); 230 | assert(entity3 == entity3); 231 | assert(entity3 != entity4); 232 | 233 | assert(entity4 != entity1); 234 | assert(entity4 != entity2); 235 | assert(entity4 != entity3); 236 | assert(entity4 == entity4); 237 | } 238 | 239 | /// Comparison operator. 240 | override int opCmp(Object o) const @safe 241 | { 242 | auto other = cast(Entity) o; 243 | return _id.opCmp(other._id); 244 | } 245 | 246 | unittest 247 | { 248 | auto entity1 = new Entity(null, ID(0, 1)); 249 | auto entity2 = new Entity(null, ID(10, 230)); 250 | auto entity3 = new Entity(null, ID(11, 200)); 251 | 252 | assert(entity1 < entity2); 253 | assert(entity1 <= entity3); 254 | assert(entity3 > entity2); 255 | assert(entity2 >= entity1); 256 | } 257 | 258 | private: 259 | EntityManager _manager; 260 | ID _id; 261 | } 262 | 263 | mixin template EntityEvent() 264 | { 265 | this(Entity entity) 266 | { 267 | this.entity = entity; 268 | } 269 | Entity entity; 270 | } 271 | 272 | mixin template ComponentEvent(C) 273 | { 274 | this(Entity entity, C component) 275 | { 276 | this.entity = entity; 277 | this.component = component; 278 | } 279 | Entity entity; 280 | C component; 281 | } 282 | 283 | struct EntityCreatedEvent 284 | { 285 | mixin EntityEvent; 286 | } 287 | 288 | struct EntityDestroyedEvent 289 | { 290 | mixin EntityEvent; 291 | } 292 | 293 | struct ComponentAddedEvent(C) 294 | { 295 | mixin ComponentEvent!C; 296 | } 297 | 298 | struct ComponentRemovedEvent(C) 299 | { 300 | mixin ComponentEvent!C; 301 | } 302 | 303 | /// Manages entities and their associated components. 304 | class EntityManager 305 | { 306 | public: 307 | /// Construct an empty entity manager. 308 | this(EventManager events) 309 | { 310 | _indexCounter = 0U; 311 | _numEntities = 0U; 312 | _events = events; 313 | } 314 | 315 | /// A range over all entities in the manager. 316 | struct Range 317 | { 318 | /// Construct a range over this manager. 319 | private this(EntityManager manager, uint index = 0) pure nothrow @safe 320 | { 321 | _manager = manager; 322 | _index = index; 323 | } 324 | 325 | /// Return the number of entities to iterate over (including empty ones). 326 | size_t length() const pure nothrow @property @safe 327 | { 328 | return _manager.capacity; 329 | } 330 | 331 | /// Return if an only if the range cannot access any more entities. 332 | bool empty() const pure nothrow @property @safe 333 | { 334 | return _index >= _manager.capacity; 335 | } 336 | 337 | /// Return the current entity. 338 | Entity front() pure nothrow @property @safe 339 | { 340 | return _manager.entity(_index); 341 | } 342 | 343 | /// Access the next entity. 344 | void popFront() pure nothrow @safe 345 | { 346 | _index++; 347 | } 348 | 349 | /// Return a copy of this range. 350 | Range save() pure nothrow @safe 351 | { 352 | return Range(_manager, _index); 353 | } 354 | 355 | private EntityManager _manager; 356 | private uint _index; 357 | } 358 | 359 | /// Return a range over the entities. 360 | Range opSlice() pure nothrow @safe 361 | { 362 | return Range(this); 363 | } 364 | 365 | unittest 366 | { 367 | class Test 368 | { 369 | this(int x) 370 | { 371 | y = x; 372 | } 373 | int y; 374 | } 375 | 376 | auto manager = new EntityManager(new EventManager); 377 | auto entity1 = manager.create(); 378 | auto entity2 = manager.create(); 379 | 380 | entity1.add(new Test(3061)); 381 | entity2.add(new Test(2015)); 382 | 383 | foreach(entity; manager[]) 384 | { 385 | if (entity == entity1) 386 | { 387 | assert(entity1.component!Test().y == 3061); 388 | } 389 | else if (entity == entity2) 390 | { 391 | assert(entity2.component!Test().y == 2015); 392 | } 393 | else 394 | { 395 | assert(0); 396 | } 397 | } 398 | } 399 | 400 | /// Create a range with only the entities with the specified components. 401 | auto entities(Components...)() pure nothrow @safe 402 | { 403 | foreach(C; Components) 404 | { 405 | if (!hasType!C()) 406 | { 407 | accomodateComponent!C(); 408 | } 409 | } 410 | 411 | auto mask = componentMask!Components(); 412 | 413 | bool hasComponents(Entity entity) 414 | { 415 | bool[] combinedMask = new bool[mask.length]; 416 | combinedMask[] = componentMask(entity.id)[] & mask[]; 417 | return combinedMask[] == mask[]; 418 | } 419 | 420 | return this[].filter!(hasComponents)(); 421 | } 422 | 423 | unittest 424 | { 425 | class Position 426 | { 427 | this(int x, int y) 428 | { 429 | this.x = x; 430 | this.y = y; 431 | } 432 | int x, y; 433 | } 434 | 435 | class Velocity 436 | { 437 | this(int x, int y) 438 | { 439 | this.x = x; 440 | this.y = y; 441 | } 442 | int x, y; 443 | } 444 | 445 | class Gravity 446 | { 447 | this(double acc) 448 | { 449 | accel = acc; 450 | } 451 | double accel; 452 | } 453 | 454 | auto manager = new EntityManager(new EventManager); 455 | 456 | auto entity1 = manager.create(); 457 | entity1.add(new Position(2, 1)); 458 | entity1.add(new Velocity(15, 4)); 459 | 460 | auto entity2 = manager.create(); 461 | entity2.add(new Velocity(-1, -3)); 462 | entity2.add(new Gravity(10)); 463 | 464 | auto entity3 = manager.create(); 465 | entity3.add(new Gravity(-9.8)); 466 | entity3.add(new Position(14, -9)); 467 | 468 | auto positionEntities = manager.entities!Position(); 469 | auto velocityEntities = manager.entities!Velocity(); 470 | auto gravityEntities = manager.entities!Gravity(); 471 | auto physicsEntities = manager.entities!(Position, Velocity, Gravity)(); 472 | 473 | foreach (pos; positionEntities) 474 | { 475 | assert(pos == entity1 || pos == entity3); 476 | assert(pos != entity2); 477 | } 478 | 479 | foreach(vel; velocityEntities) 480 | { 481 | assert(vel == entity1 || vel == entity2); 482 | assert(vel != entity3); 483 | } 484 | 485 | foreach(grav; gravityEntities) 486 | { 487 | assert(grav == entity2 || grav == entity3); 488 | assert(grav != entity1); 489 | } 490 | 491 | assert(physicsEntities.empty()); 492 | } 493 | 494 | /// Return the number of entities. 495 | size_t count() const pure nothrow @property @safe 496 | { 497 | return _numEntities; 498 | } 499 | 500 | /// Return the number of free entity indices. 501 | size_t free() const pure nothrow @property @safe 502 | { 503 | return capacity - count; 504 | } 505 | 506 | /// Return the maximum capacity of entities before needing reallocation. 507 | size_t capacity() const pure nothrow @property @safe 508 | { 509 | return _indexCounter; 510 | } 511 | 512 | /// Return if and only if there are no entities. 513 | bool empty() const pure nothrow @property @safe 514 | { 515 | return _numEntities == 0; 516 | } 517 | 518 | /// Return the entity with the specified index. 519 | Entity entity(uint index) pure nothrow @safe 520 | { 521 | return entity(id(index)); 522 | } 523 | 524 | unittest 525 | { 526 | auto manager = new EntityManager(new EventManager); 527 | auto entity = manager.create(); 528 | assert(entity == manager.entity(entity.id.index)); 529 | } 530 | 531 | /// Return the entity with the specified (and valid) id. 532 | /// Returns null if the id is invalid. 533 | Entity entity(ID id) pure nothrow @safe 534 | out (result) 535 | { 536 | if (result !is null) 537 | { 538 | assert(valid(result.id)); 539 | } 540 | } 541 | body 542 | { 543 | if (valid(id)) 544 | { 545 | return new Entity(this, id); 546 | } 547 | else 548 | { 549 | return null; 550 | } 551 | } 552 | 553 | unittest 554 | { 555 | auto manager = new EntityManager(new EventManager); 556 | auto entity = manager.create(); 557 | assert(entity == manager.entity(entity.id)); 558 | } 559 | 560 | /// Return the id with the specified index. 561 | ID id(uint index) pure nothrow @safe 562 | { 563 | if (index < _indexCounter) 564 | { 565 | return ID(index, _entityTags[index]); 566 | } 567 | else 568 | { 569 | return ID(index, 0); 570 | } 571 | } 572 | 573 | unittest 574 | { 575 | auto manager = new EntityManager(new EventManager); 576 | auto entity = manager.create(); 577 | assert(entity.id == manager.id(entity.id.index)); 578 | } 579 | 580 | /// Check if this entity handle is valid - is not invalidated or outdated 581 | bool valid(ID id) const pure nothrow @safe 582 | { 583 | return (id.index < _indexCounter && _entityTags[id.index] == id.tag); 584 | } 585 | 586 | unittest 587 | { 588 | auto manager = new EntityManager(new EventManager); 589 | assert(!manager.valid(ID.INVALID)); 590 | assert(manager.valid(manager.create().id)); 591 | } 592 | 593 | /// Create an entity in a free slot. 594 | Entity create() pure nothrow @safe 595 | out (result) 596 | { 597 | assert(valid(result.id)); 598 | } 599 | body 600 | { 601 | uint index; 602 | 603 | // Expand containers to accomodate new index 604 | if (_freeIndices.empty()) 605 | { 606 | index = _indexCounter; 607 | accomodateEntity(_indexCounter); 608 | 609 | // Uninitialized value is 0, so any entities with tag 0 are invalid 610 | _entityTags[index] = 1; 611 | } 612 | // Fill unused index, no resizing necessary 613 | else 614 | { 615 | // Remove index from free indices list 616 | index = _freeIndices.front(); 617 | _freeIndices.removeFront(); 618 | } 619 | 620 | _numEntities++; 621 | _events.emit(EntityCreatedEvent()); 622 | return new Entity(this, ID(index, _entityTags[index])); 623 | } 624 | 625 | unittest 626 | { 627 | auto manager = new EntityManager(new EventManager); 628 | auto entity1 = manager.create(); 629 | auto entity2 = manager.create(); 630 | 631 | assert(entity1.valid()); 632 | assert(entity2.valid()); 633 | assert(entity1 != entity2); 634 | 635 | entity1.invalidate(); 636 | assert(!entity1.valid()); 637 | } 638 | 639 | /// Destroy the specified entity and invalidate all handles to it. 640 | void destroy(ID id) pure nothrow @safe 641 | out 642 | { 643 | assert(!valid(id)); 644 | } 645 | body 646 | { 647 | if (valid(id)) 648 | { 649 | auto index = id.index; 650 | // Invalidate all handles by incrementing tag 651 | _entityTags[index]++; 652 | 653 | // Add index to free list 654 | _freeIndices.insert(index); 655 | 656 | // Remove all components 657 | foreach(component; _components) 658 | { 659 | component[index] = null; 660 | } 661 | 662 | // Clear the component bitmask 663 | for (int i = 0; i < _componentMasks[index].length; i++) 664 | { 665 | _componentMasks[index][i] = false; 666 | } 667 | 668 | _numEntities--; 669 | _events.emit(EntityDestroyedEvent()); 670 | } 671 | } 672 | 673 | unittest 674 | { 675 | auto manager = new EntityManager(new EventManager); 676 | auto entity1 = manager.create(); 677 | auto entity2 = manager.create(); 678 | auto entity3 = manager.create(); 679 | 680 | assert(entity1.id == ID(0, 1)); 681 | assert(entity2.id == ID(1, 1)); 682 | assert(entity3.id == ID(2, 1)); 683 | 684 | // Two methods of destroying entities 685 | manager.destroy(entity1.id); 686 | entity2.destroy(); 687 | 688 | assert(!entity1.valid()); 689 | assert(!entity2.valid()); 690 | assert(entity3.valid()); 691 | 692 | auto entity4 = manager.create(); 693 | auto entity5 = manager.create(); 694 | 695 | assert(entity3.valid()); 696 | assert(entity4.valid()); 697 | assert(entity5.valid()); 698 | assert(entity4.id == ID(1, 2)); 699 | assert(entity5.id == ID(0, 2)); 700 | } 701 | 702 | /// Add a component to the specified entity. 703 | void addComponent(C)(ID id, C component) pure nothrow @safe 704 | in 705 | { 706 | assert(valid(id)); 707 | } 708 | out 709 | { 710 | assert(hasComponent!C(id)); 711 | } 712 | body 713 | { 714 | accomodateComponent!C(); 715 | setComponent!C(id, component); 716 | setMask!C(id, true); 717 | _events.emit(ComponentAddedEvent!C()); 718 | } 719 | 720 | /// Remove a component from the entity (no effects if it is not present). 721 | void removeComponent(C)(ID id) pure nothrow @safe 722 | out 723 | { 724 | assert(!hasComponent!C(id)); 725 | } 726 | body 727 | { 728 | if (hasComponent!C(id)) 729 | { 730 | setComponent(id, null); 731 | setMask!C(id, false); 732 | _events.emit(ComponentRemovedEvent!C()); 733 | } 734 | } 735 | 736 | /// Check if the entity has the specified component. 737 | bool hasComponent(C)(const ID id) const pure nothrow @safe 738 | in 739 | { 740 | assert(valid(id)); 741 | } 742 | body 743 | { 744 | return (hasType!C() && component!C(id) !is null); 745 | } 746 | 747 | /// Return the component associated with this entity. 748 | inout(C) component(C)(ID id) inout pure nothrow @safe 749 | in 750 | { 751 | assert(valid(id)); 752 | } 753 | body 754 | { 755 | return cast(inout(C)) _components[type!C()][id.index]; 756 | } 757 | 758 | unittest 759 | { 760 | class Position 761 | { 762 | this(int x, int y) 763 | { 764 | this.x = x; 765 | this.y = y; 766 | } 767 | int x, y; 768 | } 769 | 770 | class Jump 771 | { 772 | bool onGround = true; 773 | } 774 | 775 | auto manager = new EntityManager(new EventManager); 776 | auto entity = manager.create(); 777 | auto position = new Position(1001, -19); 778 | auto jump = new Jump(); 779 | 780 | entity.add(position); 781 | manager.addComponent(entity.id, jump); 782 | assert(entity.hasComponent!Position()); 783 | assert(entity.hasComponent!Jump()); 784 | assert(position == entity.component!Position()); 785 | assert(jump == manager.component!Jump(entity.id)); 786 | } 787 | 788 | /// Delete all entities and components. 789 | void clear() pure nothrow @safe 790 | { 791 | _indexCounter = 0U; 792 | _numEntities = 0U; 793 | _freeIndices.clear(); 794 | _entityTags = null; 795 | _components = null; 796 | _componentTypes = null; 797 | _componentMasks = null; 798 | } 799 | 800 | unittest 801 | { 802 | class Position 803 | { 804 | this(int x, int y) 805 | { 806 | this.x = x; 807 | this.y = y; 808 | } 809 | int x, y; 810 | } 811 | 812 | class Velocity 813 | { 814 | this(int x, int y) 815 | { 816 | this.x = x; 817 | this.y = y; 818 | } 819 | int x, y; 820 | } 821 | 822 | class Gravity 823 | { 824 | this(double acc) 825 | { 826 | accel = acc; 827 | } 828 | double accel; 829 | } 830 | 831 | auto manager = new EntityManager(new EventManager); 832 | 833 | auto entity1 = manager.create(); 834 | entity1.add(new Position(2, 1)); 835 | 836 | auto entity2 = manager.create(); 837 | entity2.add(new Velocity(-1, -3)); 838 | 839 | auto entity3 = manager.create(); 840 | entity3.add(new Gravity(-9.8)); 841 | 842 | auto position = entity1.component!Position(); 843 | auto velocity = entity2.component!Velocity(); 844 | auto gravity = entity3.component!Gravity(); 845 | 846 | assert(position.x == 2 && position.y == 1); 847 | assert(velocity.x == -1 && velocity.y == -3); 848 | assert(abs(-9.8 - gravity.accel) < 1e-9); 849 | 850 | manager.clear(); 851 | assert(!entity1.valid()); 852 | assert(!entity2.valid()); 853 | assert(!entity3.valid()); 854 | assert(manager._indexCounter == 0); 855 | assert(manager._freeIndices.empty); 856 | assert(manager._entityTags.length == 0); 857 | assert(manager._components.length == 0); 858 | assert(manager._componentMasks.length == 0); 859 | 860 | auto entity4 = manager.create(); 861 | assert(entity4.valid()); 862 | assert(entity4.id.index == 0); 863 | } 864 | 865 | private: 866 | // Convenience function to set components. 867 | void setComponent(C)(ID id, C component) pure nothrow @safe 868 | { 869 | _components[type!C()][id.index] = component; 870 | } 871 | 872 | // Convenience function to set mask bits. 873 | void setMask(C)(ID id, bool value) pure nothrow @safe 874 | { 875 | _componentMasks[id.index][type!C()] = value; 876 | } 877 | 878 | // Reallocate space for a new component. 879 | void accomodateComponent(C)() pure nothrow @safe 880 | { 881 | if (!hasType!C()) 882 | { 883 | addType!C(); 884 | auto type = type!C(); 885 | 886 | // Expand component array (new component - first dimension widens). 887 | if (_components.length < type + 1) 888 | { 889 | _components ~= new Object[_indexCounter]; 890 | } 891 | 892 | // Expand all component masks to include new component. 893 | if (_componentMasks.length > 0 && _componentMasks[0].length < type + 1) 894 | { 895 | foreach (ref componentMask; _componentMasks) 896 | { 897 | componentMask.length = type + 1; 898 | } 899 | } 900 | } 901 | } 902 | 903 | // Reallocate space for a new entity. 904 | void accomodateEntity(uint index) pure nothrow @safe 905 | { 906 | if (index >= _indexCounter) 907 | { 908 | // Expand entity tags. 909 | if (_entityTags.length < index + 1) 910 | { 911 | _entityTags.length = index + 1; 912 | } 913 | 914 | // Expand component mask array. 915 | if (_componentMasks.length < index + 1) 916 | { 917 | _componentMasks ~= new bool[_components.length]; 918 | } 919 | 920 | // Expand all component arrays (new entity - second dimension widens). 921 | if (_components.length > 0 && _components[0].length < index + 1) 922 | { 923 | foreach (ref component; _components) 924 | { 925 | component.length = index + 1; 926 | } 927 | } 928 | } 929 | _indexCounter = index + 1; 930 | } 931 | 932 | // Return a unique integer for every component type. 933 | size_t type(C)() inout pure nothrow @safe 934 | in 935 | { 936 | assert(hasType!C()); 937 | } 938 | body 939 | { 940 | return _componentTypes[C.classinfo]; 941 | } 942 | 943 | // Create a unique id for a new component type. 944 | void addType(C)() pure nothrow @trusted 945 | { 946 | if (!hasType!C()) 947 | { 948 | _componentTypes[C.classinfo] = _componentTypes.length; 949 | _componentTypes.rehash(); 950 | } 951 | } 952 | 953 | // Return if this component type has already been assigned a unique id. 954 | bool hasType(C)() const pure nothrow @safe 955 | { 956 | return (C.classinfo in _componentTypes) !is null; 957 | } 958 | 959 | // Return the component mask (bool array) of this entity. 960 | bool[] componentMask(ID id) pure nothrow @safe 961 | in 962 | { 963 | assert(valid(id)); 964 | } 965 | body 966 | { 967 | return _componentMasks[id.index]; 968 | } 969 | 970 | // Return the component mask with the specified components marked as true. 971 | bool[] componentMask(Components...)() pure nothrow @safe 972 | in 973 | { 974 | foreach(C; Components) 975 | { 976 | assert(type!C() < _components.length); 977 | } 978 | } 979 | body 980 | { 981 | bool[] mask = new bool[_components.length]; 982 | foreach (C; Components) 983 | { 984 | mask[type!C()] = true; 985 | } 986 | return mask; 987 | } 988 | 989 | unittest 990 | { 991 | class Position { } 992 | class Velocity { } 993 | class Gravity { } 994 | 995 | auto manager = new EntityManager(new EventManager); 996 | auto entity = manager.create(); 997 | entity.add(new Position()); 998 | entity.add(new Velocity()); 999 | entity.add(new Gravity()); 1000 | 1001 | assert(manager.componentMask!(Position)() == [true, false, false]); 1002 | assert(manager.componentMask!(Position, Velocity, Gravity)() == [true, true, true]); 1003 | } 1004 | 1005 | // Debugging checks to ensure valid space for new entities and components. 1006 | invariant() 1007 | { 1008 | assert(_numEntities <= _indexCounter); 1009 | assert(_entityTags.length == _indexCounter); 1010 | assert(_componentMasks.length == _indexCounter); 1011 | assert(_components.length == _componentTypes.length); 1012 | 1013 | foreach(componentMask; _componentMasks) 1014 | { 1015 | assert(componentMask.length == _components.length); 1016 | } 1017 | 1018 | foreach(ref componentArray; _components) 1019 | { 1020 | assert(componentArray.length == _indexCounter); 1021 | } 1022 | } 1023 | 1024 | // Tracks the actual number of entities. 1025 | uint _numEntities; 1026 | 1027 | // Tracks the next unused entity index (i.e. capacity).. 1028 | uint _indexCounter; 1029 | 1030 | // Tracks entity indices recently freed. 1031 | SList!uint _freeIndices; 1032 | 1033 | // Tracks entity versions (incremented when entity is destroyed) for validity checking. 1034 | uint[] _entityTags; 1035 | 1036 | // A nested array of entity components, ordered by component and then entity index. 1037 | Object[][] _components; 1038 | 1039 | // A map associating each component class with a unique unsigned integer. 1040 | size_t[ClassInfo] _componentTypes; 1041 | 1042 | // Bitmasks of each entity's components, ordered by entity and then by component bit. 1043 | bool[][] _componentMasks; 1044 | 1045 | // Event manager. 1046 | EventManager _events; 1047 | } 1048 | --------------------------------------------------------------------------------