├── .gitignore ├── ECS.h ├── EntityComponentSystem.sln ├── EntityComponentSystem.vcxproj ├── EntityComponentSystem.vcxproj.filters ├── LICENSE ├── README.md └── sample.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # Visual Studio code coverage results 114 | *.coverage 115 | *.coveragexml 116 | 117 | # NCrunch 118 | _NCrunch_* 119 | .*crunch*.local.xml 120 | nCrunchTemp_* 121 | 122 | # MightyMoose 123 | *.mm.* 124 | AutoTest.Net/ 125 | 126 | # Web workbench (sass) 127 | .sass-cache/ 128 | 129 | # Installshield output folder 130 | [Ee]xpress/ 131 | 132 | # DocProject is a documentation generator add-in 133 | DocProject/buildhelp/ 134 | DocProject/Help/*.HxT 135 | DocProject/Help/*.HxC 136 | DocProject/Help/*.hhc 137 | DocProject/Help/*.hhk 138 | DocProject/Help/*.hhp 139 | DocProject/Help/Html2 140 | DocProject/Help/html 141 | 142 | # Click-Once directory 143 | publish/ 144 | 145 | # Publish Web Output 146 | *.[Pp]ublish.xml 147 | *.azurePubxml 148 | # TODO: Comment the next line if you want to checkin your web deploy settings 149 | # but database connection strings (with potential passwords) will be unencrypted 150 | *.pubxml 151 | *.publishproj 152 | 153 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 154 | # checkin your Azure Web App publish settings, but sensitive information contained 155 | # in these scripts will be unencrypted 156 | PublishScripts/ 157 | 158 | # NuGet Packages 159 | *.nupkg 160 | # The packages folder can be ignored because of Package Restore 161 | **/packages/* 162 | # except build/, which is used as an MSBuild target. 163 | !**/packages/build/ 164 | # Uncomment if necessary however generally it will be regenerated when needed 165 | #!**/packages/repositories.config 166 | # NuGet v3's project.json files produces more ignoreable files 167 | *.nuget.props 168 | *.nuget.targets 169 | 170 | # Microsoft Azure Build Output 171 | csx/ 172 | *.build.csdef 173 | 174 | # Microsoft Azure Emulator 175 | ecf/ 176 | rcf/ 177 | 178 | # Windows Store app package directories and files 179 | AppPackages/ 180 | BundleArtifacts/ 181 | Package.StoreAssociation.xml 182 | _pkginfo.txt 183 | 184 | # Visual Studio cache files 185 | # files ending in .cache can be ignored 186 | *.[Cc]ache 187 | # but keep track of directories ending in .cache 188 | !*.[Cc]ache/ 189 | 190 | # Others 191 | ClientBin/ 192 | ~$* 193 | *~ 194 | *.dbmdl 195 | *.dbproj.schemaview 196 | *.jfm 197 | *.pfx 198 | *.publishsettings 199 | node_modules/ 200 | orleans.codegen.cs 201 | 202 | # Since there are multiple workflows, uncomment next line to ignore bower_components 203 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 204 | #bower_components/ 205 | 206 | # RIA/Silverlight projects 207 | Generated_Code/ 208 | 209 | # Backup & report files from converting an old project file 210 | # to a newer Visual Studio version. Backup files are not needed, 211 | # because we have git ;-) 212 | _UpgradeReport_Files/ 213 | Backup*/ 214 | UpgradeLog*.XML 215 | UpgradeLog*.htm 216 | 217 | # SQL Server files 218 | *.mdf 219 | *.ldf 220 | 221 | # Business Intelligence projects 222 | *.rdl.data 223 | *.bim.layout 224 | *.bim_*.settings 225 | 226 | # Microsoft Fakes 227 | FakesAssemblies/ 228 | 229 | # GhostDoc plugin setting file 230 | *.GhostDoc.xml 231 | 232 | # Node.js Tools for Visual Studio 233 | .ntvs_analysis.dat 234 | 235 | # Visual Studio 6 build log 236 | *.plg 237 | 238 | # Visual Studio 6 workspace options file 239 | *.opt 240 | 241 | # Visual Studio LightSwitch build output 242 | **/*.HTMLClient/GeneratedArtifacts 243 | **/*.DesktopClient/GeneratedArtifacts 244 | **/*.DesktopClient/ModelManifest.xml 245 | **/*.Server/GeneratedArtifacts 246 | **/*.Server/ModelManifest.xml 247 | _Pvt_Extensions 248 | 249 | # Paket dependency manager 250 | .paket/paket.exe 251 | paket-files/ 252 | 253 | # FAKE - F# Make 254 | .fake/ 255 | 256 | # JetBrains Rider 257 | .idea/ 258 | *.sln.iml 259 | 260 | # CodeRush 261 | .cr/ 262 | 263 | # Python Tools for Visual Studio (PTVS) 264 | __pycache__/ 265 | *.pyc 266 | 267 | # Cake - Uncomment if you are using it 268 | # tools/ 269 | 270 | # executables 271 | *.exe 272 | 273 | # vs code settings and cache folder 274 | .vscode 275 | -------------------------------------------------------------------------------- /ECS.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | Copyright (c) 2016 Sam Bloomberg 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 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | ////////////////////////////////////////////////////////////////////////// 33 | // SETTINGS // 34 | ////////////////////////////////////////////////////////////////////////// 35 | 36 | 37 | // Define what you want to pass to the tick() function by defining ECS_TICK_TYPE before including this header, 38 | // or leave it as default (float). 39 | // This is really messy to do but the alternative is some sort of slow custom event setup for ticks, which is silly. 40 | 41 | // Add this before including this header if you don't want to pass anything to tick() 42 | //#define ECS_TICK_TYPE_VOID 43 | #ifndef ECS_TICK_TYPE 44 | #define ECS_TICK_TYPE ECS::DefaultTickData 45 | #endif 46 | 47 | // Define what kind of allocator you want the world to use. It should have a default constructor. 48 | #ifndef ECS_ALLOCATOR_TYPE 49 | #define ECS_ALLOCATOR_TYPE std::allocator 50 | #endif 51 | 52 | // Define ECS_TICK_NO_CLEANUP if you don't want the world to automatically cleanup dead entities 53 | // at the beginning of each tick. This will require you to call cleanup() manually to prevent memory 54 | // leaks. 55 | //#define ECS_TICK_NO_CLEANUP 56 | 57 | // Define ECS_NO_RTTI to turn off RTTI. This requires using the ECS_DEFINE_TYPE and ECS_DECLARE_TYPE macros on all types 58 | // that you wish to use as components or events. If you use ECS_NO_RTTI, also place ECS_TYPE_IMPLEMENTATION in a single cpp file. 59 | //#define ECS_NO_RTTI 60 | 61 | #ifndef ECS_NO_RTTI 62 | 63 | #include 64 | #include 65 | #define ECS_TYPE_IMPLEMENTATION 66 | 67 | #else 68 | 69 | #define ECS_TYPE_IMPLEMENTATION \ 70 | ECS::TypeIndex ECS::Internal::TypeRegistry::nextIndex = 1; \ 71 | \ 72 | ECS_DEFINE_TYPE(ECS::Events::OnEntityCreated);\ 73 | ECS_DEFINE_TYPE(ECS::Events::OnEntityDestroyed); \ 74 | 75 | #endif 76 | 77 | ////////////////////////////////////////////////////////////////////////// 78 | // CODE // 79 | ////////////////////////////////////////////////////////////////////////// 80 | 81 | namespace ECS 82 | { 83 | #ifndef ECS_NO_RTTI 84 | typedef std::type_index TypeIndex; 85 | 86 | #define ECS_DECLARE_TYPE 87 | #define ECS_DEFINE_TYPE(name) 88 | 89 | template 90 | TypeIndex getTypeIndex() 91 | { 92 | return std::type_index(typeid(T)); 93 | } 94 | 95 | #else 96 | typedef uint32_t TypeIndex; 97 | 98 | namespace Internal 99 | { 100 | class TypeRegistry 101 | { 102 | public: 103 | TypeRegistry() 104 | { 105 | index = nextIndex; 106 | ++nextIndex; 107 | } 108 | 109 | TypeIndex getIndex() const 110 | { 111 | return index; 112 | } 113 | 114 | private: 115 | static TypeIndex nextIndex; 116 | TypeIndex index; 117 | }; 118 | } 119 | 120 | #define ECS_DECLARE_TYPE public: static ECS::Internal::TypeRegistry __ecs_type_reg 121 | #define ECS_DEFINE_TYPE(name) ECS::Internal::TypeRegistry name::__ecs_type_reg 122 | 123 | template 124 | TypeIndex getTypeIndex() 125 | { 126 | return T::__ecs_type_reg.getIndex(); 127 | } 128 | #endif 129 | 130 | class World; 131 | class Entity; 132 | 133 | typedef float DefaultTickData; 134 | typedef ECS_ALLOCATOR_TYPE Allocator; 135 | 136 | // Do not use anything in the Internal namespace yourself. 137 | namespace Internal 138 | { 139 | template 140 | class EntityComponentView; 141 | 142 | class EntityView; 143 | 144 | struct BaseComponentContainer 145 | { 146 | public: 147 | virtual ~BaseComponentContainer() { } 148 | 149 | // This should only ever be called by the entity itself. 150 | virtual void destroy(World* world) = 0; 151 | 152 | // This will be called by the entity itself 153 | virtual void removed(Entity* ent) = 0; 154 | }; 155 | 156 | class BaseEventSubscriber 157 | { 158 | public: 159 | virtual ~BaseEventSubscriber() {}; 160 | }; 161 | 162 | template 163 | class EntityComponentIterator 164 | { 165 | public: 166 | EntityComponentIterator(World* world, size_t index, bool bIsEnd, bool bIncludePendingDestroy); 167 | 168 | size_t getIndex() const 169 | { 170 | return index; 171 | } 172 | 173 | bool isEnd() const; 174 | 175 | bool includePendingDestroy() const 176 | { 177 | return bIncludePendingDestroy; 178 | } 179 | 180 | World* getWorld() const 181 | { 182 | return world; 183 | } 184 | 185 | Entity* get() const; 186 | 187 | Entity* operator*() const 188 | { 189 | return get(); 190 | } 191 | 192 | bool operator==(const EntityComponentIterator& other) const 193 | { 194 | if (world != other.world) 195 | return false; 196 | 197 | if (isEnd()) 198 | return other.isEnd(); 199 | 200 | return index == other.index; 201 | } 202 | 203 | bool operator!=(const EntityComponentIterator& other) const 204 | { 205 | if (world != other.world) 206 | return true; 207 | 208 | if (isEnd()) 209 | return !other.isEnd(); 210 | 211 | return index != other.index; 212 | } 213 | 214 | EntityComponentIterator& operator++(); 215 | 216 | private: 217 | bool bIsEnd = false; 218 | size_t index; 219 | class ECS::World* world; 220 | bool bIncludePendingDestroy; 221 | }; 222 | 223 | template 224 | class EntityComponentView 225 | { 226 | public: 227 | EntityComponentView(const EntityComponentIterator& first, const EntityComponentIterator& last); 228 | 229 | const EntityComponentIterator& begin() const 230 | { 231 | return firstItr; 232 | } 233 | 234 | const EntityComponentIterator& end() const 235 | { 236 | return lastItr; 237 | } 238 | 239 | private: 240 | EntityComponentIterator firstItr; 241 | EntityComponentIterator lastItr; 242 | }; 243 | } 244 | 245 | /** 246 | * Think of this as a pointer to a component. Whenever you get a component from the world or an entity, 247 | * it'll be wrapped in a ComponentHandle. 248 | */ 249 | template 250 | class ComponentHandle 251 | { 252 | public: 253 | ComponentHandle() 254 | : component(nullptr) 255 | { 256 | } 257 | 258 | ComponentHandle(T* component) 259 | : component(component) 260 | { 261 | } 262 | 263 | T* operator->() const 264 | { 265 | return component; 266 | } 267 | 268 | operator bool() const 269 | { 270 | return isValid(); 271 | } 272 | 273 | T& get() 274 | { 275 | return *component; 276 | } 277 | 278 | bool isValid() const 279 | { 280 | return component != nullptr; 281 | } 282 | 283 | private: 284 | T* component; 285 | }; 286 | 287 | /** 288 | * A system that acts on entities. Generally, this will act on a subset of entities using World::each(). 289 | * 290 | * Systems often will respond to events by subclassing EventSubscriber. You may use configure() to subscribe to events, 291 | * but remember to unsubscribe in unconfigure(). 292 | */ 293 | class EntitySystem 294 | { 295 | public: 296 | virtual ~EntitySystem() {} 297 | 298 | /** 299 | * Called when this system is added to a world. 300 | */ 301 | virtual void configure(World* world) 302 | { 303 | } 304 | 305 | /** 306 | * Called when this system is being removed from a world. 307 | */ 308 | virtual void unconfigure(World* world) 309 | { 310 | } 311 | 312 | /** 313 | * Called when World::tick() is called. See ECS_TICK_TYPE at the top of this file for more 314 | * information about passing data to tick. 315 | */ 316 | #ifdef ECS_TICK_TYPE_VOID 317 | virtual void tick(World* world) 318 | #else 319 | virtual void tick(World* world, ECS_TICK_TYPE data) 320 | #endif 321 | { 322 | } 323 | }; 324 | 325 | /** 326 | * Subclass this as EventSubscriber and then call World::subscribe() in order to subscribe to events. Make sure 327 | * to call World::unsubscribe() or World::unsubscribeAll() when your subscriber is deleted! 328 | */ 329 | template 330 | class EventSubscriber : public Internal::BaseEventSubscriber 331 | { 332 | public: 333 | virtual ~EventSubscriber() {} 334 | 335 | /** 336 | * Called when an event is emitted by the world. 337 | */ 338 | virtual void receive(World* world, const T& event) = 0; 339 | }; 340 | 341 | namespace Events 342 | { 343 | // Called when a new entity is created. 344 | struct OnEntityCreated 345 | { 346 | ECS_DECLARE_TYPE; 347 | 348 | Entity* entity; 349 | }; 350 | 351 | // Called when an entity is about to be destroyed. 352 | struct OnEntityDestroyed 353 | { 354 | ECS_DECLARE_TYPE; 355 | 356 | Entity* entity; 357 | }; 358 | 359 | // Called when a component is assigned (not necessarily created). 360 | template 361 | struct OnComponentAssigned 362 | { 363 | ECS_DECLARE_TYPE; 364 | 365 | Entity* entity; 366 | ComponentHandle component; 367 | }; 368 | 369 | // Called when a component is removed 370 | template 371 | struct OnComponentRemoved 372 | { 373 | ECS_DECLARE_TYPE; 374 | 375 | Entity* entity; 376 | ComponentHandle component; 377 | }; 378 | 379 | #ifdef ECS_NO_RTTI 380 | template 381 | ECS_DEFINE_TYPE(ECS::Events::OnComponentAssigned); 382 | template 383 | ECS_DEFINE_TYPE(ECS::Events::OnComponentRemoved); 384 | #endif 385 | } 386 | 387 | 388 | /** 389 | * A container for components. Entities do not have any logic of their own, except of that which to manage 390 | * components. Components themselves are generally structs that contain data with which EntitySystems can 391 | * act upon, but technically any data type may be used as a component, though only one of each data type 392 | * may be on a single Entity at a time. 393 | */ 394 | class Entity 395 | { 396 | public: 397 | friend class World; 398 | 399 | const static size_t InvalidEntityId = 0; 400 | 401 | // Do not create entities yourself, use World::create(). 402 | Entity(World* world, size_t id) 403 | : world(world), id(id) 404 | { 405 | } 406 | 407 | // Do not delete entities yourself, use World::destroy(). 408 | ~Entity() 409 | { 410 | removeAll(); 411 | } 412 | 413 | /** 414 | * Get the world associated with this entity. 415 | */ 416 | World* getWorld() const 417 | { 418 | return world; 419 | } 420 | 421 | /** 422 | * Does this entity have a component? 423 | */ 424 | template 425 | bool has() const 426 | { 427 | auto index = getTypeIndex(); 428 | return components.find(index) != components.end(); 429 | } 430 | 431 | /** 432 | * Does this entity have this list of components? The order of components does not matter. 433 | */ 434 | template 435 | bool has() const 436 | { 437 | return has() && has(); 438 | } 439 | 440 | /** 441 | * Assign a new component (or replace an old one). All components must have a default constructor, though they 442 | * may have additional constructors. You may pass arguments to this function the same way you would to a constructor. 443 | * 444 | * It is recommended that components be simple types (not const, not references, not pointers). If you need to store 445 | * any of the above, wrap it in a struct. 446 | */ 447 | template 448 | ComponentHandle assign(Args&&... args); 449 | 450 | /** 451 | * Remove a component of a specific type. Returns whether a component was removed. 452 | */ 453 | template 454 | bool remove() 455 | { 456 | auto found = components.find(getTypeIndex()); 457 | if (found != components.end()) 458 | { 459 | found->second->removed(this); 460 | found->second->destroy(world); 461 | 462 | components.erase(found); 463 | 464 | return true; 465 | } 466 | 467 | return false; 468 | } 469 | 470 | /** 471 | * Remove all components from this entity. 472 | */ 473 | void removeAll() 474 | { 475 | for (auto pair : components) 476 | { 477 | pair.second->removed(this); 478 | pair.second->destroy(world); 479 | } 480 | 481 | components.clear(); 482 | } 483 | 484 | /** 485 | * Get a component from this entity. 486 | */ 487 | template 488 | ComponentHandle get(); 489 | 490 | /** 491 | * Call a function with components from this entity as arguments. This will return true if this entity has 492 | * all specified components attached, and false if otherwise. 493 | */ 494 | template 495 | bool with(typename std::common_type...)>>::type view) 496 | { 497 | if (!has()) 498 | return false; 499 | 500 | view(get()...); // variadic template expansion is fun 501 | return true; 502 | } 503 | 504 | /** 505 | * Get this entity's id. Entity ids aren't too useful at the moment, but can be used to tell the difference between entities when debugging. 506 | */ 507 | size_t getEntityId() const 508 | { 509 | return id; 510 | } 511 | 512 | bool isPendingDestroy() const 513 | { 514 | return bPendingDestroy; 515 | } 516 | 517 | private: 518 | std::unordered_map components; 519 | World* world; 520 | 521 | size_t id; 522 | bool bPendingDestroy = false; 523 | }; 524 | 525 | /** 526 | * The world creates, destroys, and manages entities. The lifetime of entities and _registered_ systems are handled by the world 527 | * (don't delete a system without unregistering it from the world first!), while event subscribers have their own lifetimes 528 | * (the world doesn't delete them automatically when the world is deleted). 529 | */ 530 | class World 531 | { 532 | public: 533 | using WorldAllocator = std::allocator_traits::template rebind_alloc; 534 | using EntityAllocator = std::allocator_traits::template rebind_alloc; 535 | using SystemAllocator = std::allocator_traits::template rebind_alloc; 536 | using EntityPtrAllocator = std::allocator_traits::template rebind_alloc; 537 | using SystemPtrAllocator = std::allocator_traits::template rebind_alloc; 538 | using SubscriberPtrAllocator = std::allocator_traits::template rebind_alloc; 539 | using SubscriberPairAllocator = std::allocator_traits::template rebind_alloc>>; 540 | 541 | /** 542 | * Use this function to construct the world with a custom allocator. 543 | */ 544 | static World* createWorld(Allocator alloc) 545 | { 546 | WorldAllocator worldAlloc(alloc); 547 | World* world = std::allocator_traits::allocate(worldAlloc, 1); 548 | std::allocator_traits::construct(worldAlloc, world, alloc); 549 | 550 | return world; 551 | } 552 | 553 | /** 554 | * Use this function to construct the world with the default allocator. 555 | */ 556 | static World* createWorld() 557 | { 558 | return createWorld(Allocator()); 559 | } 560 | 561 | // Use this to destroy the world instead of calling delete. 562 | // This will emit OnEntityDestroyed events and call EntitySystem::unconfigure as appropriate. 563 | void destroyWorld() 564 | { 565 | WorldAllocator alloc(entAlloc); 566 | std::allocator_traits::destroy(alloc, this); 567 | std::allocator_traits::deallocate(alloc, this, 1); 568 | } 569 | 570 | World(Allocator alloc) 571 | : entAlloc(alloc), systemAlloc(alloc), 572 | entities({}, EntityPtrAllocator(alloc)), 573 | systems({}, SystemPtrAllocator(alloc)), 574 | subscribers({}, 0, std::hash(), std::equal_to(), SubscriberPtrAllocator(alloc)) 575 | { 576 | } 577 | 578 | /** 579 | * Destroying the world will emit OnEntityDestroyed events and call EntitySystem::unconfigure() as appropriate. 580 | * 581 | * Use World::destroyWorld to destroy and deallocate the world, do not manually delete the world! 582 | */ 583 | ~World(); 584 | 585 | /** 586 | * Create a new entity. This will emit the OnEntityCreated event. 587 | */ 588 | Entity* create() 589 | { 590 | ++lastEntityId; 591 | Entity* ent = std::allocator_traits::allocate(entAlloc, 1); 592 | std::allocator_traits::construct(entAlloc, ent, this, lastEntityId); 593 | entities.push_back(ent); 594 | 595 | emit({ ent }); 596 | 597 | return ent; 598 | } 599 | 600 | /** 601 | * Destroy an entity. This will emit the OnEntityDestroy event. 602 | * 603 | * If immediate is false (recommended), then the entity won't be immediately 604 | * deleted but instead will be removed at the beginning of the next tick() or 605 | * when cleanup() is called. OnEntityDestroyed will still be called immediately. 606 | * 607 | * This function is safe to call multiple times on a single entity. Note that calling 608 | * this once with immediate = false and then calling it with immediate = true will 609 | * remove the entity from the pending destroy queue and will immediately destroy it 610 | * _without_ emitting a second OnEntityDestroyed event. 611 | * 612 | * A warning: Do not set immediate to true if you are currently iterating through entities! 613 | */ 614 | void destroy(Entity* ent, bool immediate = false); 615 | 616 | /** 617 | * Delete all entities in the pending destroy queue. Returns true if any entities were cleaned up, 618 | * false if there were no entities to clean up. 619 | */ 620 | bool cleanup(); 621 | 622 | /** 623 | * Reset the world, destroying all entities. Entity ids will be reset as well. 624 | */ 625 | void reset(); 626 | 627 | /** 628 | * Register a system. The world will manage the memory of the system unless you unregister the system. 629 | */ 630 | EntitySystem* registerSystem(EntitySystem* system) 631 | { 632 | systems.push_back(system); 633 | system->configure(this); 634 | 635 | return system; 636 | } 637 | 638 | /** 639 | * Unregister a system. 640 | */ 641 | void unregisterSystem(EntitySystem* system) 642 | { 643 | systems.erase(std::remove(systems.begin(), systems.end(), system), systems.end()); 644 | system->unconfigure(this); 645 | } 646 | 647 | void enableSystem(EntitySystem* system) 648 | { 649 | auto it = std::find(disabledSystems.begin(), disabledSystems.end(), system); 650 | if (it != disabledSystems.end()) 651 | { 652 | disabledSystems.erase(it); 653 | systems.push_back(system); 654 | } 655 | } 656 | 657 | void disableSystem(EntitySystem* system) 658 | { 659 | auto it = std::find(systems.begin(), systems.end(), system); 660 | if (it != systems.end()) 661 | { 662 | systems.erase(it); 663 | disabledSystems.push_back(system); 664 | } 665 | } 666 | 667 | /** 668 | * Subscribe to an event. 669 | */ 670 | template 671 | void subscribe(EventSubscriber* subscriber) 672 | { 673 | auto index = getTypeIndex(); 674 | auto found = subscribers.find(index); 675 | if (found == subscribers.end()) 676 | { 677 | std::vector subList(entAlloc); 678 | subList.push_back(subscriber); 679 | 680 | subscribers.insert({ index, subList }); 681 | } 682 | else 683 | { 684 | found->second.push_back(subscriber); 685 | } 686 | } 687 | 688 | /** 689 | * Unsubscribe from an event. 690 | */ 691 | template 692 | void unsubscribe(EventSubscriber* subscriber) 693 | { 694 | auto index = getTypeIndex(); 695 | auto found = subscribers.find(index); 696 | if (found != subscribers.end()) 697 | { 698 | found->second.erase(std::remove(found->second.begin(), found->second.end(), subscriber), found->second.end()); 699 | if (found->second.size() == 0) 700 | { 701 | subscribers.erase(found); 702 | } 703 | } 704 | } 705 | 706 | /** 707 | * Unsubscribe from all events. Don't be afraid of the void pointer, just pass in your subscriber as normal. 708 | */ 709 | void unsubscribeAll(void* subscriber) 710 | { 711 | for (auto kv : subscribers) 712 | { 713 | kv.second.erase(std::remove(kv.second.begin(), kv.second.end(), subscriber), kv.second.end()); 714 | if (kv.second.size() == 0) 715 | { 716 | subscribers.erase(kv.first); 717 | } 718 | } 719 | } 720 | 721 | /** 722 | * Emit an event. This will do nothing if there are no subscribers for the event type. 723 | */ 724 | template 725 | void emit(const T& event) 726 | { 727 | auto found = subscribers.find(getTypeIndex()); 728 | if (found != subscribers.end()) 729 | { 730 | for (auto* base : found->second) 731 | { 732 | auto* sub = reinterpret_cast*>(base); 733 | sub->receive(this, event); 734 | } 735 | } 736 | } 737 | 738 | /** 739 | * Run a function on each entity with a specific set of components. This is useful for implementing an EntitySystem. 740 | * 741 | * If you want to include entities that are pending destruction, set includePendingDestroy to true. 742 | */ 743 | template 744 | void each(typename std::common_type...)>>::type viewFunc, bool bIncludePendingDestroy = false); 745 | 746 | /** 747 | * Run a function on all entities. 748 | */ 749 | void all(std::function viewFunc, bool bIncludePendingDestroy = false); 750 | 751 | /** 752 | * Get a view for entities with a specific set of components. The list of entities is calculated on the fly, so this method itself 753 | * has little overhead. This is mostly useful with a range based for loop. 754 | */ 755 | template 756 | Internal::EntityComponentView each(bool bIncludePendingDestroy = false) 757 | { 758 | Internal::EntityComponentIterator first(this, 0, false, bIncludePendingDestroy); 759 | Internal::EntityComponentIterator last(this, getCount(), true, bIncludePendingDestroy); 760 | return Internal::EntityComponentView(first, last); 761 | } 762 | 763 | Internal::EntityView all(bool bIncludePendingDestroy = false); 764 | 765 | size_t getCount() const 766 | { 767 | return entities.size(); 768 | } 769 | 770 | Entity* getByIndex(size_t idx) 771 | { 772 | if (idx >= getCount()) 773 | return nullptr; 774 | 775 | return entities[idx]; 776 | } 777 | 778 | /** 779 | * Get an entity by an id. This is a slow process. 780 | */ 781 | Entity* getById(size_t id) const; 782 | 783 | /** 784 | * Tick the world. See the definition for ECS_TICK_TYPE at the top of this file for more information on 785 | * passing data through tick(). 786 | */ 787 | #ifdef ECS_TICK_TYPE_VOID 788 | void tick() 789 | #else 790 | void tick(ECS_TICK_TYPE data) 791 | #endif 792 | { 793 | #ifndef ECS_TICK_NO_CLEANUP 794 | cleanup(); 795 | #endif 796 | for (auto* system : systems) 797 | { 798 | #ifdef ECS_TICK_TYPE_VOID 799 | system->tick(this); 800 | #else 801 | system->tick(this, data); 802 | #endif 803 | } 804 | } 805 | 806 | EntityAllocator& getPrimaryAllocator() 807 | { 808 | return entAlloc; 809 | } 810 | 811 | private: 812 | EntityAllocator entAlloc; 813 | SystemAllocator systemAlloc; 814 | 815 | std::vector entities; 816 | std::vector systems; 817 | std::vector disabledSystems; 818 | std::unordered_map, 820 | std::hash, 821 | std::equal_to, 822 | SubscriberPairAllocator> subscribers; 823 | 824 | size_t lastEntityId = 0; 825 | }; 826 | 827 | namespace Internal 828 | { 829 | class EntityIterator 830 | { 831 | public: 832 | EntityIterator(World* world, size_t index, bool bIsEnd, bool bIncludePendingDestroy); 833 | 834 | size_t getIndex() const 835 | { 836 | return index; 837 | } 838 | 839 | bool isEnd() const; 840 | 841 | bool includePendingDestroy() const 842 | { 843 | return bIncludePendingDestroy; 844 | } 845 | 846 | World* getWorld() const 847 | { 848 | return world; 849 | } 850 | 851 | Entity* get() const; 852 | 853 | Entity* operator*() const 854 | { 855 | return get(); 856 | } 857 | 858 | bool operator==(const EntityIterator& other) const 859 | { 860 | if (world != other.world) 861 | return false; 862 | 863 | if (isEnd()) 864 | return other.isEnd(); 865 | 866 | return index == other.index; 867 | } 868 | 869 | bool operator!=(const EntityIterator& other) const 870 | { 871 | if (world != other.world) 872 | return true; 873 | 874 | if (isEnd()) 875 | return !other.isEnd(); 876 | 877 | return index != other.index; 878 | } 879 | 880 | EntityIterator& operator++(); 881 | 882 | private: 883 | bool bIsEnd = false; 884 | size_t index; 885 | class ECS::World* world; 886 | bool bIncludePendingDestroy; 887 | }; 888 | 889 | class EntityView 890 | { 891 | public: 892 | EntityView(const EntityIterator& first, const EntityIterator& last) 893 | : firstItr(first), lastItr(last) 894 | { 895 | if (firstItr.get() == nullptr || (firstItr.get()->isPendingDestroy() && !firstItr.includePendingDestroy())) 896 | { 897 | ++firstItr; 898 | } 899 | } 900 | 901 | const EntityIterator& begin() const 902 | { 903 | return firstItr; 904 | } 905 | 906 | const EntityIterator& end() const 907 | { 908 | return lastItr; 909 | } 910 | 911 | private: 912 | EntityIterator firstItr; 913 | EntityIterator lastItr; 914 | }; 915 | 916 | template 917 | struct ComponentContainer : public BaseComponentContainer 918 | { 919 | ComponentContainer() {} 920 | ComponentContainer(const T& data) : data(data) {} 921 | 922 | T data; 923 | 924 | protected: 925 | virtual void destroy(World* world) 926 | { 927 | using ComponentAllocator = std::allocator_traits::template rebind_alloc>; 928 | 929 | ComponentAllocator alloc(world->getPrimaryAllocator()); 930 | std::allocator_traits::destroy(alloc, this); 931 | std::allocator_traits::deallocate(alloc, this, 1); 932 | } 933 | 934 | virtual void removed(Entity* ent) 935 | { 936 | auto handle = ComponentHandle(&data); 937 | ent->getWorld()->emit>({ ent, handle }); 938 | } 939 | }; 940 | } 941 | 942 | inline World::~World() 943 | { 944 | for (auto* system : systems) 945 | { 946 | system->unconfigure(this); 947 | } 948 | 949 | for (auto* ent : entities) 950 | { 951 | if (!ent->isPendingDestroy()) 952 | { 953 | ent->bPendingDestroy = true; 954 | emit({ ent }); 955 | } 956 | 957 | std::allocator_traits::destroy(entAlloc, ent); 958 | std::allocator_traits::deallocate(entAlloc, ent, 1); 959 | } 960 | 961 | for (auto* system : systems) 962 | { 963 | std::allocator_traits::destroy(systemAlloc, system); 964 | std::allocator_traits::deallocate(systemAlloc, system, 1); 965 | } 966 | } 967 | 968 | inline void World::destroy(Entity* ent, bool immediate) 969 | { 970 | if (ent == nullptr) 971 | return; 972 | 973 | if (ent->isPendingDestroy()) 974 | { 975 | if (immediate) 976 | { 977 | entities.erase(std::remove(entities.begin(), entities.end(), ent), entities.end()); 978 | std::allocator_traits::destroy(entAlloc, ent); 979 | std::allocator_traits::deallocate(entAlloc, ent, 1); 980 | } 981 | 982 | return; 983 | } 984 | 985 | ent->bPendingDestroy = true; 986 | 987 | emit({ ent }); 988 | 989 | if (immediate) 990 | { 991 | entities.erase(std::remove(entities.begin(), entities.end(), ent), entities.end()); 992 | std::allocator_traits::destroy(entAlloc, ent); 993 | std::allocator_traits::deallocate(entAlloc, ent, 1); 994 | } 995 | } 996 | 997 | inline bool World::cleanup() 998 | { 999 | size_t count = 0; 1000 | entities.erase(std::remove_if(entities.begin(), entities.end(), [&, this](Entity* ent) { 1001 | if (ent->isPendingDestroy()) 1002 | { 1003 | std::allocator_traits::destroy(entAlloc, ent); 1004 | std::allocator_traits::deallocate(entAlloc, ent, 1); 1005 | ++count; 1006 | return true; 1007 | } 1008 | 1009 | return false; 1010 | }), entities.end()); 1011 | 1012 | return count > 0; 1013 | } 1014 | 1015 | inline void World::reset() 1016 | { 1017 | for (auto* ent : entities) 1018 | { 1019 | if (!ent->isPendingDestroy()) 1020 | { 1021 | ent->bPendingDestroy = true; 1022 | emit({ ent }); 1023 | } 1024 | std::allocator_traits::destroy(entAlloc, ent); 1025 | std::allocator_traits::deallocate(entAlloc, ent, 1); 1026 | } 1027 | 1028 | entities.clear(); 1029 | lastEntityId = 0; 1030 | } 1031 | 1032 | inline void World::all(std::function viewFunc, bool bIncludePendingDestroy) 1033 | { 1034 | for (auto* ent : all(bIncludePendingDestroy)) 1035 | { 1036 | viewFunc(ent); 1037 | } 1038 | } 1039 | 1040 | inline Internal::EntityView World::all(bool bIncludePendingDestroy) 1041 | { 1042 | Internal::EntityIterator first(this, 0, false, bIncludePendingDestroy); 1043 | Internal::EntityIterator last(this, getCount(), true, bIncludePendingDestroy); 1044 | return Internal::EntityView(first, last); 1045 | } 1046 | 1047 | inline Entity* World::getById(size_t id) const 1048 | { 1049 | if (id == Entity::InvalidEntityId || id > lastEntityId) 1050 | return nullptr; 1051 | 1052 | // We should likely store entities in a map of id -> entity so that this is faster. 1053 | for (auto* ent : entities) 1054 | { 1055 | if (ent->getEntityId() == id) 1056 | return ent; 1057 | } 1058 | 1059 | return nullptr; 1060 | } 1061 | 1062 | template 1063 | void World::each(typename std::common_type...)>>::type viewFunc, bool bIncludePendingDestroy) 1064 | { 1065 | for (auto* ent : each(bIncludePendingDestroy)) 1066 | { 1067 | viewFunc(ent, ent->template get()...); 1068 | } 1069 | } 1070 | 1071 | template 1072 | ComponentHandle Entity::assign(Args&&... args) 1073 | { 1074 | using ComponentAllocator = std::allocator_traits::template rebind_alloc>; 1075 | 1076 | auto found = components.find(getTypeIndex()); 1077 | if (found != components.end()) 1078 | { 1079 | Internal::ComponentContainer* container = reinterpret_cast*>(found->second); 1080 | container->data = T(args...); 1081 | 1082 | auto handle = ComponentHandle(&container->data); 1083 | world->emit>({ this, handle }); 1084 | return handle; 1085 | } 1086 | else 1087 | { 1088 | ComponentAllocator alloc(world->getPrimaryAllocator()); 1089 | 1090 | Internal::ComponentContainer* container = std::allocator_traits::allocate(alloc, 1); 1091 | std::allocator_traits::construct(alloc, container, T(args...)); 1092 | 1093 | components.insert({ getTypeIndex(), container }); 1094 | 1095 | auto handle = ComponentHandle(&container->data); 1096 | world->emit>({ this, handle }); 1097 | return handle; 1098 | } 1099 | } 1100 | 1101 | template 1102 | ComponentHandle Entity::get() 1103 | { 1104 | auto found = components.find(getTypeIndex()); 1105 | if (found != components.end()) 1106 | { 1107 | return ComponentHandle(&reinterpret_cast*>(found->second)->data); 1108 | } 1109 | 1110 | return ComponentHandle(); 1111 | } 1112 | 1113 | namespace Internal 1114 | { 1115 | inline EntityIterator::EntityIterator(class World* world, size_t index, bool bIsEnd, bool bIncludePendingDestroy) 1116 | : bIsEnd(bIsEnd), index(index), world(world), bIncludePendingDestroy(bIncludePendingDestroy) 1117 | { 1118 | if (index >= world->getCount()) 1119 | this->bIsEnd = true; 1120 | } 1121 | 1122 | inline bool EntityIterator::isEnd() const 1123 | { 1124 | return bIsEnd || index >= world->getCount(); 1125 | } 1126 | 1127 | inline Entity* EntityIterator::get() const 1128 | { 1129 | if (isEnd()) 1130 | return nullptr; 1131 | 1132 | return world->getByIndex(index); 1133 | } 1134 | 1135 | inline EntityIterator& EntityIterator::operator++() 1136 | { 1137 | ++index; 1138 | while (index < world->getCount() && (get() == nullptr || (get()->isPendingDestroy() && !bIncludePendingDestroy))) 1139 | { 1140 | ++index; 1141 | } 1142 | 1143 | if (index >= world->getCount()) 1144 | bIsEnd = true; 1145 | 1146 | return *this; 1147 | } 1148 | 1149 | template 1150 | EntityComponentIterator::EntityComponentIterator(World* world, size_t index, bool bIsEnd, bool bIncludePendingDestroy) 1151 | : bIsEnd(bIsEnd), index(index), world(world), bIncludePendingDestroy(bIncludePendingDestroy) 1152 | { 1153 | if (index >= world->getCount()) 1154 | this->bIsEnd = true; 1155 | } 1156 | 1157 | template 1158 | bool EntityComponentIterator::isEnd() const 1159 | { 1160 | return bIsEnd || index >= world->getCount(); 1161 | } 1162 | 1163 | template 1164 | Entity* EntityComponentIterator::get() const 1165 | { 1166 | if (isEnd()) 1167 | return nullptr; 1168 | 1169 | return world->getByIndex(index); 1170 | } 1171 | 1172 | template 1173 | EntityComponentIterator& EntityComponentIterator::operator++() 1174 | { 1175 | ++index; 1176 | while (index < world->getCount() && (get() == nullptr || !get()->template has() || (get()->isPendingDestroy() && !bIncludePendingDestroy))) 1177 | { 1178 | ++index; 1179 | } 1180 | 1181 | if (index >= world->getCount()) 1182 | bIsEnd = true; 1183 | 1184 | return *this; 1185 | } 1186 | 1187 | template 1188 | EntityComponentView::EntityComponentView(const EntityComponentIterator& first, const EntityComponentIterator& last) 1189 | : firstItr(first), lastItr(last) 1190 | { 1191 | if (firstItr.get() == nullptr || (firstItr.get()->isPendingDestroy() && !firstItr.includePendingDestroy()) 1192 | || !firstItr.get()->template has()) 1193 | { 1194 | ++firstItr; 1195 | } 1196 | } 1197 | } 1198 | } -------------------------------------------------------------------------------- /EntityComponentSystem.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EntityComponentSystem", "EntityComponentSystem.vcxproj", "{8153F43D-5D7B-4961-BE55-CCB2FE46C322}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {8153F43D-5D7B-4961-BE55-CCB2FE46C322}.Debug|x64.ActiveCfg = Debug|x64 17 | {8153F43D-5D7B-4961-BE55-CCB2FE46C322}.Debug|x64.Build.0 = Debug|x64 18 | {8153F43D-5D7B-4961-BE55-CCB2FE46C322}.Debug|x86.ActiveCfg = Debug|Win32 19 | {8153F43D-5D7B-4961-BE55-CCB2FE46C322}.Debug|x86.Build.0 = Debug|Win32 20 | {8153F43D-5D7B-4961-BE55-CCB2FE46C322}.Release|x64.ActiveCfg = Release|x64 21 | {8153F43D-5D7B-4961-BE55-CCB2FE46C322}.Release|x64.Build.0 = Release|x64 22 | {8153F43D-5D7B-4961-BE55-CCB2FE46C322}.Release|x86.ActiveCfg = Release|Win32 23 | {8153F43D-5D7B-4961-BE55-CCB2FE46C322}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /EntityComponentSystem.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {8153F43D-5D7B-4961-BE55-CCB2FE46C322} 23 | Win32Proj 24 | EntityComponentSystem 25 | 8.1 26 | 27 | 28 | 29 | Application 30 | true 31 | v140 32 | Unicode 33 | 34 | 35 | Application 36 | false 37 | v140 38 | true 39 | Unicode 40 | 41 | 42 | Application 43 | true 44 | v140 45 | Unicode 46 | 47 | 48 | Application 49 | false 50 | v140 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | true 74 | 75 | 76 | true 77 | 78 | 79 | false 80 | 81 | 82 | false 83 | 84 | 85 | 86 | 87 | 88 | Level3 89 | Disabled 90 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 91 | true 92 | 93 | 94 | Console 95 | true 96 | 97 | 98 | 99 | 100 | 101 | 102 | Level3 103 | Disabled 104 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 105 | true 106 | 107 | 108 | Console 109 | true 110 | 111 | 112 | 113 | 114 | Level3 115 | 116 | 117 | MaxSpeed 118 | true 119 | true 120 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 121 | true 122 | 123 | 124 | Console 125 | true 126 | true 127 | true 128 | 129 | 130 | 131 | 132 | Level3 133 | 134 | 135 | MaxSpeed 136 | true 137 | true 138 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 139 | true 140 | 141 | 142 | Console 143 | true 144 | true 145 | true 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /EntityComponentSystem.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | 23 | 24 | Source Files 25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Sam Bloomberg 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ECS 2 | 3 | This is a simple C++ header-only type-safe entity component system library. It makes heavy use of C++11 4 | constructs, so make sure you have an up to date compiler. It isn't meant to do absolutely everything, 5 | so please feel free to modify it when using. There's a VS2015 solution provided, but it should 6 | compile on any standard compiler with C++11 support (C++14 is more ideal, however, as it lets you use `auto` 7 | parameters in lambdas). 8 | 9 | Again, this is meant for quick prototyping or as a starting point for a more advanced ECS toolkit. It works and it works well, but it isn't optimized for speed or anything. 10 | 11 | This has been tested on the following compilers: 12 | 13 | * Visual Studio 2015 on Windows 10 (x64) 14 | * G++ 5.4.1 (using -std=c++11 and -std=c++14) on Ubuntu 14.04 (x64) 15 | 16 | Contributions are welcome! Submit a pull request, or if you find a problem (or have a feature request) make a new issue! 17 | 18 | ## Tutorial 19 | 20 | This ECS library is based on the [Evolve Your Hierarchy](http://cowboyprogramming.com/2007/01/05/evolve-your-heirachy/) article. If you haven't read it, please do or else things won't make much sense. This is a data-driven entity component system library, and to know how to work with it you need to know what that entails (this is _not_ the same as Unity's components so don't expect it to be). 21 | 22 | ### Your first components 23 | 24 | Components in ECS can be any data type, but generally they'll be a struct containing some plain old data. 25 | For now, let's define two components: 26 | 27 | struct Position 28 | { 29 | Position(float x, float y) : x(x), y(y) {} 30 | Position() : x(0.f), y(0.f) {} 31 | 32 | float x; 33 | float y; 34 | } 35 | 36 | struct Rotation 37 | { 38 | Rotation(float angle) : angle(angle) {} 39 | Rotation() : angle(0) {} 40 | float angle; 41 | } 42 | 43 | This isn't the most realistic example - normally you'd just have a single transform component for a game but this should 44 | help illustrate some functionality later. Also note that we don't have to do anything special for these structs to 45 | act as components, though there is the requirement for at least a default constructor. 46 | 47 | ### Create a system 48 | 49 | Now we need some logic to act on that data. Let's make a simple gravity system: 50 | 51 | class GravitySystem : public EntitySystem 52 | { 53 | public: 54 | GravitySystem(float amount) 55 | { 56 | gravityAmount = amount; 57 | } 58 | 59 | virtual ~GravitySystem() {} 60 | 61 | virtual void tick(World* world, float deltaTime) override 62 | { 63 | world->each([&](Entity* ent, ComponentHandle position) { 64 | position->y += gravityAmount * deltaTime; 65 | }); 66 | } 67 | 68 | private: 69 | float gravityAmount; 70 | } 71 | 72 | This is a pretty standard class definition. We subclass `EntitySystem` and implement the `tick()` method. The world 73 | provides the `each` method, which takes a list of component types and runs a given function (in this case a 74 | lambda) on every entity that has those components. Note that the lambda is passed a `ComponentHandle`, and not the 75 | component itself. 76 | 77 | #### Alternate iteration methods 78 | 79 | In addition to the lambda-based each, there's also an iterator-based each, made to be used with the range based for loop. 80 | Lambda-based each isn't a true loop, and as such you can't break from it. Instead, you can use a range based for loop. The 81 | downside is that it will not directly expose components as arguments, but you can combine it with `Entity::with` for a 82 | similar result: 83 | 84 | for (Entity* ent : world->each()) 85 | { 86 | ent->with([&](ComponentHandle position) { 87 | position->y += gravityAmount * deltaTime; 88 | }); 89 | } 90 | 91 | Alternatively, you may retrieve a single component at a time with `Entity::get`, though this will return an invalid component 92 | handle (see `ComponentHandle::isValid` and `ComponentHandle::operator bool()`) if there isn't a component of that type attached: 93 | 94 | ComponentHandle position = ent->get(); 95 | position->y += gravityAmount * deltaTime; // this will crash if there is no position component on the entity 96 | 97 | `with()` only runs the given function if the entity has the listed components. It also returns true if all components were 98 | found, or false if not all components were on the entity. 99 | 100 | Finally, if you want to run a function on all entities, regardless of components, then use the `all` function in the same way 101 | as `each`: 102 | 103 | world->all([](Entity* ent) { 104 | // do something with ent 105 | }); 106 | 107 | You may also use `all` in a range based for loop in a similar fashion to `each`. 108 | 109 | ### Create the world 110 | 111 | Next, inside a `main()` function somewhere, you can add the following code to create the world, setup the system, and 112 | create an entity: 113 | 114 | World* world = World::createWorld(); 115 | world->registerSystem(new GravitySystem(-9.8f)); 116 | 117 | Entity* ent = world->create(); 118 | ent->assign(0.f, 0.f); // assign() takes arguments and passes them to the constructor 119 | ent->assign(35.f); 120 | 121 | Now you can call the tick function on the world in order to tick all systems that have been registered with the world: 122 | 123 | world->tick(deltaTime); 124 | 125 | Once you are done with the world, make sure to destroy it (this will also deallocate the world). 126 | 127 | world->destroyWorld(); 128 | 129 | #### Custom Allocators 130 | 131 | You may use any standards-compliant custom allocator. The world handles all allocations and deallocations for entities and components. 132 | 133 | In order to use a custom allocator, define `ECS_ALLOCATOR_TYPE` before including `ECS.h`: 134 | 135 | #define ECS_ALLOCATOR_TYPE MyAllocator 136 | #include "ECS.h" 137 | 138 | Allocators must have a default constructor. When creating the world with `World::createWorld`, you may pass in your custom 139 | allocator if you need to initialize it first. Additionally, custom allocators must be rebindable via `std::allocator_traits`. 140 | 141 | The default implementation uses `std::allocator`. Note that the world will rebind allocators for different types. 142 | 143 | ### Working with components 144 | 145 | You may retrieve a component handle (for example, to print out the position of your entity) with `get`: 146 | 147 | ComponentHandle pos = ent->get(); 148 | std::cout << "My position is " << pos->x << ", " << pos->y << std::endl; 149 | 150 | If an entity doesn't have a component and you try to retrieve that type from it, `get` will return an invalid 151 | component handle: 152 | 153 | ComponentHandle pos = otherEnt->get(); // assume otherEnt doesn't have a Position component 154 | pos.isValid(); // returns false, note the . instead of the -> 155 | 156 | Alternatively, you may use a handle's bool conversion operator instead of `isValid`: 157 | 158 | if (pos) 159 | { 160 | // pos is valid 161 | } 162 | else 163 | { 164 | // pos is not valid 165 | } 166 | 167 | ### Events 168 | 169 | For communication between systems (and with other objects outside of ECS) there is an event system. Events can be any 170 | type of object, and you can subscribe to specific types of events by subclassing `EventSubscriber` and calling 171 | `subscribe` on the world: 172 | 173 | struct MyEvent 174 | { 175 | int foo; 176 | float bar; 177 | } 178 | 179 | class MyEventSubscriber : public EventSubscriber 180 | { 181 | public: 182 | virtual ~MyEventSubscriber() {} 183 | 184 | virtual void receive(World* world, const MyEvent& event) override 185 | { 186 | std::cout << "MyEvent was emitted!" << std::endl; 187 | } 188 | } 189 | 190 | // ... 191 | 192 | MyEventSubscriber* mySubscriber = new MyEventSubscriber(); 193 | world->subscribe(mySubscriber); 194 | 195 | Then, to emit an event: 196 | 197 | world->emit({ 123, 45.67f }); // you can use initializer syntax if you want, this sets foo = 123 and bar = 45.67f 198 | 199 | Make sure you call `unsubscribe` or `unsubscribeAll` on your subscriber before deleting it, or else emitting the event 200 | may cause a crash or other undesired behavior. 201 | 202 | ### Systems and events 203 | 204 | Often, your event subscribers will also be systems. Systems have `configure` and `unconfigure` functions that are called 205 | when they are added to/removed from the world and which you may use to subscribe and unsubscribe from events: 206 | 207 | class MySystem : public EntitySystem, public EventSubscriber 208 | { 209 | // ... 210 | 211 | virtual void configure(World* world) override 212 | { 213 | world->subscribe(this); 214 | } 215 | 216 | virtual void unconfigure(World* world) override 217 | { 218 | world->unsubscribeAll(this); 219 | // You may also unsubscribe from specific events with world->unsubscribe(this), but 220 | // when unconfigure is called you usually want to unsubscribe from all events. 221 | } 222 | 223 | // ... 224 | } 225 | 226 | ### Built-in events 227 | 228 | There are a handful of built-in events. Here is the list: 229 | 230 | * `OnEntityCreated` - called when an entity has been created. 231 | * `OnEntityDestroyed` - called when an entity is being destroyed (including when a world is beind deleted). 232 | * `OnComponentAssigned` - called when a component is assigned to an entity. This might mean the component is new to the entity, or there's just a new assignment of the component to that entity overwriting an old one. 233 | * `OnComponentRemoved` - called when a component is removed from an entity. This happens upon manual removal (via `Entity::remove()` and `Entity::removeAll()`) or upon entity destruction (which can also happen as a result of the world being destroyed). 234 | 235 | ## Avoiding RTTI 236 | 237 | If you wish to avoid using RTTI for any reason, you may define the ECS_NO_RTTI macro before including 238 | ECS.h. When doing so, you must also add a couple of macros to your component and event types: 239 | 240 | // in a header somewhere 241 | struct MyComponent 242 | { 243 | ECS_DECLARE_TYPE; // add this at the top of the structure, make sure to include the semicolon! 244 | 245 | // ... 246 | }; 247 | 248 | // in a cpp somewhere 249 | ECS_DEFINE_TYPE(MyComponent); 250 | 251 | Again, make sure you do this with events as well. 252 | 253 | Additionally, you will have to put the following in a cpp file: 254 | 255 | #include "ECS.h" 256 | ECS_TYPE_IMPLEMENTATION; 257 | 258 | If you have any templated events, you may do the following: 259 | 260 | template 261 | struct MyEvent 262 | { 263 | ECS_DECLARE_TYPE; 264 | 265 | T someField; 266 | 267 | // ... 268 | } 269 | 270 | template 271 | ECS_DEFINE_TYPE(MyEvent); 272 | -------------------------------------------------------------------------------- /sample.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016 Sam Bloomberg 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | #include 24 | 25 | #include "ECS.h" 26 | 27 | ECS_TYPE_IMPLEMENTATION; 28 | 29 | using namespace ECS; 30 | 31 | struct Position 32 | { 33 | ECS_DECLARE_TYPE; 34 | 35 | Position(float x, float y) : x(x), y(y) {} 36 | Position() {} 37 | 38 | float x; 39 | float y; 40 | }; 41 | 42 | ECS_DEFINE_TYPE(Position); 43 | 44 | struct Rotation 45 | { 46 | ECS_DECLARE_TYPE; 47 | 48 | Rotation(float angle) : angle(angle) {} 49 | Rotation() {} 50 | 51 | float angle; 52 | }; 53 | 54 | ECS_DEFINE_TYPE(Rotation); 55 | 56 | struct SomeComponent 57 | { 58 | ECS_DECLARE_TYPE; 59 | 60 | SomeComponent() {} 61 | }; 62 | 63 | ECS_DEFINE_TYPE(SomeComponent); 64 | 65 | struct SomeEvent 66 | { 67 | ECS_DECLARE_TYPE; 68 | 69 | int num; 70 | }; 71 | 72 | ECS_DEFINE_TYPE(SomeEvent); 73 | 74 | class TestSystem : public EntitySystem, 75 | public EventSubscriber, 76 | public EventSubscriber, 77 | public EventSubscriber>, 78 | public EventSubscriber>, 79 | public EventSubscriber 80 | { 81 | public: 82 | virtual ~TestSystem() {} 83 | 84 | virtual void configure(class World* world) override 85 | { 86 | world->subscribe(this); 87 | world->subscribe(this); 88 | world->subscribe>(this); 89 | world->subscribe>(this); 90 | world->subscribe(this); 91 | } 92 | 93 | virtual void unconfigure(class World* world) override 94 | { 95 | world->unsubscribeAll(this); 96 | } 97 | 98 | virtual void tick(class World* world, float deltaTime) override 99 | { 100 | world->each([&](Entity* ent, ComponentHandle pos, ComponentHandle rot) -> void { 101 | pos->x += deltaTime; 102 | pos->y += deltaTime; 103 | rot->angle += deltaTime * 2; 104 | }); 105 | } 106 | 107 | virtual void receive(class World* world, const Events::OnEntityCreated& event) override 108 | { 109 | std::cout << "An entity was created!" << std::endl; 110 | } 111 | 112 | virtual void receive(class World* world, const Events::OnEntityDestroyed& event) override 113 | { 114 | std::cout << "An entity was destroyed!" << std::endl; 115 | } 116 | 117 | virtual void receive(class World* world, const Events::OnComponentRemoved& event) override 118 | { 119 | std::cout << "A position component was removed!" << std::endl; 120 | } 121 | 122 | virtual void receive(class World* world, const Events::OnComponentRemoved& event) override 123 | { 124 | std::cout << "A rotation component was removed!" << std::endl; 125 | } 126 | 127 | virtual void receive(class World* world, const SomeEvent& event) override 128 | { 129 | std::cout << "I received SomeEvent with value " << event.num << "!" << std::endl; 130 | 131 | // Let's delete an entity while iterating because why not? 132 | world->all([&](Entity* ent) { 133 | if (ent->getEntityId() + 1 == event.num) 134 | world->destroy(world->getById(event.num)); 135 | 136 | if (ent->getEntityId() == event.num) 137 | std::cout << "Woah, we shouldn't get here!" << std::endl; 138 | }); 139 | } 140 | }; 141 | 142 | int main(int argc, char** argv) 143 | { 144 | std::cout << "EntityComponentSystem Test" << std::endl 145 | << "==========================" << std::endl; 146 | 147 | World* world = World::createWorld(); 148 | 149 | EntitySystem* testSystem = world->registerSystem(new TestSystem()); 150 | 151 | Entity* ent = world->create(); 152 | auto pos = ent->assign(0.f, 0.f); 153 | auto rot = ent->assign(0.f); 154 | 155 | std::cout << "Initial values: position(" << pos->x << ", " << pos->y << "), rotation(" << rot->angle << ")" << std::endl; 156 | 157 | world->tick(10.f); 158 | 159 | std::cout << "After tick(10): position(" << pos->x << ", " << pos->y << "), rotation(" << rot->angle << ")" << std::endl; 160 | 161 | world->disableSystem(testSystem); 162 | 163 | world->tick(10.f); 164 | 165 | std::cout << "After tick(10) and DisableSystem(testSystem): position(" << pos->x << ", " << pos->y << "), rotation(" << rot->angle << ")" << std::endl; 166 | 167 | world->enableSystem(testSystem); 168 | 169 | world->tick(10.f); 170 | 171 | std::cout << "After tick(10) and EnableSystem(testSystem): position(" << pos->x << ", " << pos->y << "), rotation(" << rot->angle << ")" << std::endl; 172 | 173 | ent->remove(); 174 | ent->remove(); 175 | 176 | std::cout << "Creating more entities..." << std::endl; 177 | 178 | for (int i = 0; i < 10; ++i) 179 | { 180 | ent = world->create(); 181 | ent->assign(); 182 | } 183 | 184 | int count = 0; 185 | std::cout << "Counting entities with SomeComponent..." << std::endl; 186 | // range based for loop 187 | for (auto ent : world->each()) 188 | { 189 | ++count; 190 | std::cout << "Found entity #" << ent->getEntityId() << std::endl; 191 | } 192 | std::cout << count << " entities have SomeComponent!" << std::endl; 193 | 194 | // Emitting events 195 | world->emit({ 4 }); 196 | 197 | std::cout << "We have " << world->getCount() << " entities right now." << std::endl; 198 | world->cleanup(); 199 | std::cout << "After a cleanup, we have " << world->getCount() << " entities." << std::endl; 200 | 201 | std::cout << "Destroying the world..." << std::endl; 202 | 203 | world->destroyWorld(); 204 | 205 | std::cout << "Press any key to exit..." << std::endl; 206 | std::getchar(); 207 | 208 | return 0; 209 | } --------------------------------------------------------------------------------