Skip to content

Instantly share code, notes, and snippets.

@wortelstoemp
Created May 25, 2018 00:11
Show Gist options
  • Select an option

  • Save wortelstoemp/fd020ec973402eecf2ba470d18e91d95 to your computer and use it in GitHub Desktop.

Select an option

Save wortelstoemp/fd020ec973402eecf2ba470d18e91d95 to your computer and use it in GitHub Desktop.
Data Oriented Entity Component System
// 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