Created
May 25, 2018 00:11
-
-
Save wortelstoemp/fd020ec973402eecf2ba470d18e91d95 to your computer and use it in GitHub Desktop.
Data Oriented Entity Component System
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Data Oriented Entity Component System | |
| // Author: Tom Quareme | |
| // Copyright 2018 | |
| #pragma once | |
| #include <iostream> | |
| #include <cstdio> | |
| #include <ctime> | |
| #include <map> | |
| #include <vector> | |
| namespace tqECS | |
| { | |
| typedef unsigned long long Entity; | |
| typedef unsigned int Component; | |
| // Hashing: djb2 algorithm | |
| // http://www.cse.yorku.ca/~oz/hash.html | |
| // This hash has an even distribution and collisions are rare. | |
| inline unsigned long long HashDJB2(char* text) | |
| { | |
| unsigned long long hash = 5381; | |
| int c; | |
| while (c = *text++) { | |
| hash = ((hash << 5) + hash) + c; // hash * 33 + c | |
| } | |
| return hash; | |
| } | |
| class EntitySystem | |
| { | |
| private: | |
| // Map entities to a component counter | |
| std::map<Entity, int> entities; | |
| std::map<Entity, int> deadEntities; | |
| public: | |
| inline Entity CreateEntity(char* name) | |
| { | |
| const Entity e = HashDJB2(name); | |
| entities[e] = 0; | |
| return e; | |
| } | |
| // Update every frame | |
| inline void Update() | |
| { | |
| // Cleanup dead entities if they have no components | |
| std::map<Entity, int>::iterator it; | |
| for (it = deadEntities.begin(); it != deadEntities.end(); it++) { | |
| const int value = it->second; | |
| if (value <= 0) { | |
| const Entity key = it->first; | |
| deadEntities.erase(key); | |
| } | |
| } | |
| } | |
| inline void IncrementComponentCount(Entity e) | |
| { | |
| entities[e]++; | |
| } | |
| inline void DecrementComponentCount(Entity e) | |
| { | |
| entities[e]--; | |
| } | |
| inline bool IsAlive(Entity e) | |
| { | |
| return deadEntities.find(e) != deadEntities.end(); | |
| } | |
| inline void DestroyEntity(Entity e) | |
| { | |
| deadEntities[e] = entities[e]; | |
| entities.erase(e); | |
| } | |
| inline void ClearEntities() | |
| { | |
| entities.clear(); | |
| } | |
| inline void ClearDeadEntities() | |
| { | |
| deadEntities.clear(); | |
| } | |
| }; | |
| class ComponentSystem | |
| { | |
| private: | |
| std::map<Entity, Component> table; | |
| struct Memory | |
| { | |
| unsigned int instanceCount = 0; | |
| std::vector<Entity> entity; | |
| std::vector<float> x; | |
| std::vector<float> y; | |
| }; | |
| ComponentSystem::Memory data; | |
| public: | |
| struct Value | |
| { | |
| float x; | |
| float y; | |
| }; | |
| ComponentSystem() | |
| { | |
| std::srand(std::time(NULL)); | |
| } | |
| void Allocate(unsigned int allocatedCount) | |
| { | |
| // PROPERTIES | |
| data.entity.reserve(allocatedCount); | |
| data.x.reserve(allocatedCount); | |
| data.y.reserve(allocatedCount); | |
| } | |
| // Call this at the end of Update() | |
| void GarbageCollect(EntitySystem* es) | |
| { | |
| const unsigned int maxAliveInRowCount = 4; | |
| unsigned int aliveInARowCount = 0; | |
| while (data.instanceCount > 0 && aliveInARowCount < maxAliveInRowCount) { | |
| // Get random entity: index in [0, data.instanceCount - 1] | |
| unsigned int index = std::rand() % data.instanceCount; | |
| // If this entity is alive: skip it | |
| Entity e = data.entity[index]; | |
| if (es->IsAlive(e)) { | |
| aliveInARowCount++; | |
| continue; | |
| } | |
| // If this entity is not alive: destroy its component | |
| aliveInARowCount = 0; | |
| DestroyComponent(es, index); | |
| } | |
| } | |
| inline void CreateComponent(EntitySystem* es, Entity e, const ComponentSystem::Value& v) | |
| { | |
| const unsigned int i = data.instanceCount; | |
| table[e] = i; | |
| //PROPERTIES========== | |
| data.entity.push_back(e); | |
| data.x.push_back(v.x); | |
| data.y.push_back(v.y); | |
| //==================== | |
| data.instanceCount++; | |
| es->IncrementComponentCount(e); | |
| } | |
| inline void DestroyComponent(EntitySystem* es, Component index) | |
| { | |
| // Deleting a component is the same as replacing it by a copy of the last component | |
| // and leaving the original last component behind. | |
| Component lastIndex = data.instanceCount - 1; | |
| Entity e = data.entity[index]; | |
| Entity lastEntity = data.entity[lastIndex]; | |
| //PROPERTIES========================= | |
| data.entity[index] = data.entity[lastIndex]; | |
| data.x[index] = data.x[lastIndex]; | |
| data.y[index] = data.y[lastIndex]; | |
| //=================================== | |
| // In lookup table connect overwritten last entity to index of removed component | |
| table[lastEntity] = index; | |
| // Remove entity component pair from lookup table | |
| // and leave the original last component behind | |
| table.erase(e); | |
| data.instanceCount--; | |
| es->DecrementComponentCount(e); | |
| } | |
| // Updates all components | |
| void Update(EntitySystem* es, const float dt) | |
| { | |
| const float velocity = 100.0f; | |
| for (unsigned i = 0; i < this->data.instanceCount; i++) { | |
| this->data.x[i] += velocity * dt; | |
| } | |
| for (unsigned i = 0; i < this->data.instanceCount; i++) { | |
| this->data.y[i] += velocity * dt; | |
| } | |
| this->GarbageCollect(es); | |
| } | |
| // Looks up component index from the entity | |
| // Lookups are O(log n) and not instantaneous, so use with care. | |
| Component Lookup(Entity e) | |
| { | |
| return this->table[e]; | |
| } | |
| // Properties | |
| float X(Component index) const | |
| { | |
| return this->data.x[index]; | |
| } | |
| void SetX(Component index, const float x) | |
| { | |
| data.x[index] = x; | |
| } | |
| float Y(Component index) const | |
| { | |
| return this->data.y[index]; | |
| } | |
| void SetY(Component index, const float y) | |
| { | |
| data.y[index] = y; | |
| } | |
| }; | |
| // Unit testing | |
| void UnitTest() | |
| { | |
| float deltaTime = 0.01f; | |
| tqECS::EntitySystem entitySystem; | |
| tqECS::Entity shark0 = entitySystem.CreateEntity("shark0"); | |
| tqECS::Entity shark1 = entitySystem.CreateEntity("shark1"); | |
| tqECS::Entity shark2 = entitySystem.CreateEntity("shark2"); | |
| tqECS::Entity shark3 = entitySystem.CreateEntity("shark3"); | |
| tqECS::ComponentSystem componentSystem; | |
| componentSystem.Allocate(10); | |
| componentSystem.CreateComponent(&entitySystem, shark0, tqECS::ComponentSystem::Value{ 15.0f, 16.0f }); | |
| tqECS::Component shark0Comp = componentSystem.Lookup(shark0); | |
| componentSystem.SetX(shark0Comp, 17.0f); | |
| componentSystem.SetY(shark0Comp, 18.0f); | |
| //componentSystem.Update(&entitySystem, deltaTime); | |
| float x = componentSystem.X(shark0Comp); | |
| float y = componentSystem.Y(shark0Comp); | |
| std::printf("Entity: %llu, x = %f, y = %f!\n", shark0, x, y); | |
| entitySystem.DestroyEntity(shark0); | |
| //componentSystem.Update(entitySystem, deltaTime); | |
| tqECS::Entity shark4 = entitySystem.CreateEntity("shark4"); | |
| std::printf("Entity: %llu!\n", shark0); | |
| std::printf("Entity: %llu!\n", shark1); | |
| std::printf("Entity: %llu!\n", shark2); | |
| std::printf("Entity: %llu!\n", shark3); | |
| std::printf("Entity: %llu!\n", shark4); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment