Compare commits

...

10 commits

Author SHA1 Message Date
Baobeld
273a03b99b
Update README.md 2025-01-15 16:43:50 -05:00
Benjamin Palko
27a3525f3f dont need import 2024-10-10 14:04:35 -04:00
Benjamin Palko
ab187a9fa9 merge component array into manager 2024-10-10 14:00:33 -04:00
Benjamin Palko
888abc495b move to subdir 2024-10-09 13:34:54 -04:00
Benjamin Palko
41d6c28be9 missing includes 2024-10-09 12:54:15 -04:00
Benjamin Palko
5273b8017e implement ECS library 2024-10-09 11:26:03 -04:00
Benjamin Palko
4591ad623e include guard 2024-10-07 13:21:15 -04:00
Benjamin Palko
38483b4b36 hwaa??? 2024-10-07 12:31:03 -04:00
Benjamin Palko
bfcceba502 naming conventions! 2024-10-06 23:08:39 -04:00
Benjamin Palko
ce2a615ced create components dir 2024-10-06 22:29:42 -04:00
21 changed files with 502 additions and 35 deletions

View file

@ -1,4 +1,4 @@
# Wayland Application
I'm messing around with GLFW to build an OpenGL app in wayland, theoretically this is the starting point for a game engine.
### Setup

13
libs/ecs/component.hpp Normal file
View file

@ -0,0 +1,13 @@
#ifndef COMPONENT_HPP
#define COMPONENT_HPP
#include <bitset>
#include <cstdint>
using ComponentType = std::uint8_t;
const ComponentType MAX_COMPONENTS = 32;
using Signature = std::bitset<MAX_COMPONENTS>;
#endif // COMPONENT_HPP

View file

@ -0,0 +1,117 @@
#include "componentmanager.hpp"
#include <cassert>
#include <memory>
/**
* ComponentArray
**/
template <typename T>
void ComponentArray<T>::InsertData(Entity entity, T component) {
assert(mEntityToIndexMap.find(entity) == mEntityToIndexMap.end() &&
"Component added to same entity more than once.");
// Put new entry at end and update the maps
size_t newIndex = mSize;
mEntityToIndexMap[entity] = newIndex;
mIndexToEntityMap[newIndex] = entity;
mComponentArray[newIndex] = component;
++mSize;
};
template <typename T> void ComponentArray<T>::RemoveData(Entity entity) {
assert(mEntityToIndexMap.find(entity) != mEntityToIndexMap.end() &&
"Removing non-existent component.");
// Copy element at end into deleted element's place to maintain density
size_t indexOfRemovedEntity = mEntityToIndexMap[entity];
size_t indexOfLastElement = mSize - 1;
mComponentArray[indexOfRemovedEntity] = mComponentArray[indexOfLastElement];
// Update map to point to moved spot
Entity entityOfLastElement = mIndexToEntityMap[indexOfLastElement];
mEntityToIndexMap[entityOfLastElement] = indexOfRemovedEntity;
mIndexToEntityMap[indexOfRemovedEntity] = entityOfLastElement;
mEntityToIndexMap.erase(entity);
mIndexToEntityMap.erase(indexOfLastElement);
--mSize;
};
template <typename T> T &ComponentArray<T>::GetData(Entity entity) {
assert(mEntityToIndexMap.find(entity) != mEntityToIndexMap.end() &&
"Retrieving non-existent component.");
// Return a reference to the entity's component
return mComponentArray[mEntityToIndexMap[entity]];
};
template <typename T> void ComponentArray<T>::EntityDestroyed(Entity entity) {
if (mEntityToIndexMap.find(entity) != mEntityToIndexMap.end()) {
// Remove the entity's component if it existed
RemoveData(entity);
}
};
/**
* ComponentManager
**/
// Private functions
template <typename T>
std::shared_ptr<ComponentArray<T>> ComponentManager::GetComponentArray() {
const char *typeName = typeid(T).name();
assert(mComponentTypes.find(typeName) != mComponentTypes.end() &&
"Component not registered before use.");
return std::static_pointer_cast<ComponentArray<T>>(
mComponentArrays[typeName]);
}
// Public functions
template <typename T> void ComponentManager::RegisterComponent() {
const char *typeName = typeid(T).name();
assert(mComponentTypes.find(typeName) == mComponentTypes.end() &&
"Registering component type more than once.");
// Add this component type to the component type map
mComponentTypes.insert({typeName, mNextComponentType});
// Create a ComponentArray pointer and add it to the component arrays map
mComponentArrays.insert({typeName, std::make_shared<ComponentArray<T>>()});
// Increment the value so that the next component registered will be different
++mNextComponentType;
};
template <typename T> ComponentType ComponentManager::GetComponentType() {
const char *typeName = typeid(T).name();
assert(mComponentTypes.find(typeName) != mComponentTypes.end() &&
"Component not registered before use.");
// Return this component's type - used for creating signatures
return mComponentTypes[typeName];
}
template <typename T>
void ComponentManager::AddComponent(Entity entity, T component) {
// Add a component to the array for an entity
GetComponentArray<T>()->InsertData(entity, component);
}
template <typename T> void ComponentManager::RemoveComponent(Entity entity) {
// Remove a component from the array for an entity
GetComponentArray<T>()->RemoveData(entity);
}
template <typename T> T &ComponentManager::GetComponent(Entity entity) {
// Get a reference to a component from the array for an entity
return GetComponentArray<T>()->GetData(entity);
}
void ComponentManager::EntityDestroyed(Entity entity) {
// Notify each component array that an entity has been destroyed
// If it has a component for that entity, it will remove it
for (auto const &pair : mComponentArrays) {
auto const &component = pair.second;
component->EntityDestroyed(entity);
}
};

View file

@ -0,0 +1,63 @@
#ifndef COMPONENTMANAGER_HPP
#define COMPONENTMANAGER_HPP
#include <array>
#include <unordered_map>
#include <memory>
#include "entity.hpp"
class IComponentArray {
public:
virtual ~IComponentArray() = default;
virtual void EntityDestroyed(Entity entity) = 0;
};
template <typename T> class ComponentArray : public IComponentArray {
// The packed array of components (of generic type T),
// set to a specified maximum amount, matching the maximum number
// of entities allowed to exist simultaneously, so that each entity
// has a unique spot.
std::array<T, MAX_ENTITIES> mComponentArray;
// Map from an entity ID to an array index.
std::unordered_map<Entity, size_t> mEntityToIndexMap;
// Map from an array index to an entity ID.
std::unordered_map<size_t, Entity> mIndexToEntityMap;
// Total size of valid entries in the array.
size_t mSize;
public:
void InsertData(Entity entity, T component);
void RemoveData(Entity entity);
T &GetData(Entity entity);
void EntityDestroyed(Entity entity) override;
};
class ComponentManager {
// Map from type string pointer to a component type
std::unordered_map<const char *, ComponentType> mComponentTypes{};
// Map from type string pointer to a component array
std::unordered_map<const char *, std::shared_ptr<IComponentArray>>
mComponentArrays{};
// The component type to be assigned to the next registered component -
// starting at 0
ComponentType mNextComponentType{};
// Convenience function to get the statically casted pointer to the
// ComponentArray of type T.
template <typename T> std::shared_ptr<ComponentArray<T>> GetComponentArray();
public:
template <typename T> void RegisterComponent();
template <typename T> ComponentType GetComponentType();
template <typename T> void AddComponent(Entity entity, T component);
template <typename T> void RemoveComponent(Entity entity);
template <typename T> T &GetComponent(Entity entity);
void EntityDestroyed(Entity entity);
};
#endif // COMPONENTMANAGER_HPP

63
libs/ecs/coordinator.cpp Normal file
View file

@ -0,0 +1,63 @@
#include "coordinator.hpp"
void Coordinator::Init() {
// Create pointers to each manager
mComponentManager = std::make_unique<ComponentManager>();
mEntityManager = std::make_unique<EntityManager>();
mSystemManager = std::make_unique<SystemManager>();
}
// Entity methods
Entity Coordinator::CreateEntity() { return mEntityManager->CreateEntity(); }
void Coordinator::DestroyEntity(Entity entity) {
mEntityManager->DestroyEntity(entity);
mComponentManager->EntityDestroyed(entity);
mSystemManager->EntityDestroyed(entity);
}
// Component methods
template <typename T> void Coordinator::RegisterComponent() {
mComponentManager->RegisterComponent<T>();
}
template <typename T>
void Coordinator::AddComponent(Entity entity, T component) {
mComponentManager->AddComponent<T>(entity, component);
auto signature = mEntityManager->GetSignature(entity);
signature.set(mComponentManager->GetComponentType<T>(), true);
mEntityManager->SetSignature(entity, signature);
mSystemManager->EntitySignatureChanged(entity, signature);
}
template <typename T> void Coordinator::RemoveComponent(Entity entity) {
mComponentManager->RemoveComponent<T>(entity);
auto signature = mEntityManager->GetSignature(entity);
signature.set(mComponentManager->GetComponentType<T>(), false);
mEntityManager->SetSignature(entity, signature);
mSystemManager->EntitySignatureChanged(entity, signature);
}
template <typename T> T &Coordinator::GetComponent(Entity entity) {
return mComponentManager->GetComponent<T>(entity);
}
template <typename T> ComponentType Coordinator::GetComponentType() {
return mComponentManager->GetComponentType<T>();
}
// System methods
template <typename T> std::shared_ptr<T> Coordinator::RegisterSystem() {
return mSystemManager->RegisterSystem<T>();
}
template <typename T>
void Coordinator::SetSystemSignature(Signature signature) {
mSystemManager->SetSignature<T>(signature);
}

30
libs/ecs/coordinator.hpp Normal file
View file

@ -0,0 +1,30 @@
#ifndef COORDINATOR_HPP
#define COORDINATOR_HPP
#include <memory>
#include "componentmanager.hpp"
#include "entity.hpp"
#include "system.hpp"
class Coordinator {
std::unique_ptr<ComponentManager> mComponentManager;
std::unique_ptr<EntityManager> mEntityManager;
std::unique_ptr<SystemManager> mSystemManager;
public:
void Init();
// Entity methods
Entity CreateEntity();
void DestroyEntity(Entity entity);
// Component methods
template <typename T> void RegisterComponent();
template <typename T> void AddComponent(Entity entity, T component);
template <typename T> void RemoveComponent(Entity entity);
template <typename T> T &GetComponent(Entity entity);
template <typename T> ComponentType GetComponentType();
// System methods
template <typename T> std::shared_ptr<T> RegisterSystem();
template <typename T> void SetSystemSignature(Signature signature);
};
#endif // COORDINATOR_HPP

42
libs/ecs/entity.cpp Normal file
View file

@ -0,0 +1,42 @@
#include "entity.hpp"
#include <cassert>
EntityManager::EntityManager() {
// Initialize the queue with all possible entity IDs
for (Entity entity = 0; entity < MAX_ENTITIES; ++entity) {
mAvailableEntities.push(entity);
}
}
Entity EntityManager::CreateEntity() {
assert(mLivingEntityCount < MAX_ENTITIES &&
"Too many entities in existence.");
// Take an ID from the front of the queue
Entity id = mAvailableEntities.front();
mAvailableEntities.pop();
++mLivingEntityCount;
return id;
}
void EntityManager::DestroyEntity(Entity entity) {
assert(entity < MAX_ENTITIES && "Entity out of range.");
// Invalidate the destroyed entity's signature
mSignatures[entity].reset();
// Put the destroyed ID at the back of the queue
mAvailableEntities.push(entity);
--mLivingEntityCount;
}
void EntityManager::SetSignature(Entity entity, Signature signature) {
assert(entity < MAX_ENTITIES && "Entity out of range.");
// Put this entity's signature into the array
mSignatures[entity] = signature;
}
Signature EntityManager::GetSignature(Entity entity) {
assert(entity < MAX_ENTITIES && "Entity out of range.");
// Get this entity's signature from the array
return mSignatures[entity];
}

31
libs/ecs/entity.hpp Normal file
View file

@ -0,0 +1,31 @@
#ifndef ENTITY_HPP
#define ENTITY_HPP
#include <cstdint>
#include <queue>
#include <array>
#include "component.hpp"
using Entity = std::uint32_t;
const Entity MAX_ENTITIES = 5000;
class EntityManager {
// Queue of unused entity IDs
std::queue<Entity> mAvailableEntities{};
// Array of signatures where the index corresponds to the entity ID
std::array<Signature, MAX_ENTITIES> mSignatures{};
// Total living entities - used to keep limits on how many exist
uint32_t mLivingEntityCount{};
public:
EntityManager();
Entity CreateEntity();
void DestroyEntity(Entity entity);
void SetSignature(Entity entity, Signature signature);
Signature GetSignature(Entity entity);
};
#endif // ENTITY_HPP

54
libs/ecs/system.cpp Normal file
View file

@ -0,0 +1,54 @@
#include "system.hpp"
#include <cassert>
template <typename T> std::shared_ptr<T> SystemManager::RegisterSystem() {
const char *typeName = typeid(T).name();
assert(mSystems.find(typeName) == mSystems.end() &&
"Registering system more than once.");
// Create a pointer to the system and return it so it can be used externally
auto system = std::make_shared<T>();
mSystems.insert({typeName, system});
return system;
}
template <typename T> void SystemManager::SetSignature(Signature signature) {
const char *typeName = typeid(T).name();
assert(mSystems.find(typeName) != mSystems.end() &&
"System used before registered.");
// Set the signature for this system
mSignatures.insert({typeName, signature});
}
void SystemManager::EntityDestroyed(Entity entity) {
// Erase a destroyed entity from all system lists
// mEntities is a set so no check needed
for (auto const &pair : mSystems) {
auto const &system = pair.second;
system->mEntities.erase(entity);
}
}
void SystemManager::EntitySignatureChanged(Entity entity,
Signature entitySignature) {
// Notify each system that an entity's signature changed
for (auto const &pair : mSystems) {
auto const &type = pair.first;
auto const &system = pair.second;
auto const &systemSignature = mSignatures[type];
// Entity signature matches system signature - insert into set
if ((entitySignature & systemSignature) == systemSignature) {
system->mEntities.insert(entity);
}
// Entity signature does not match system signature - erase from set
else {
system->mEntities.erase(entity);
}
}
}

29
libs/ecs/system.hpp Normal file
View file

@ -0,0 +1,29 @@
#ifndef SYSTEM_HPP
#define SYSTEM_HPP
#include <set>
#include <memory>
#include <unordered_map>
#include "component.hpp"
#include "entity.hpp"
class System {
public:
std::set<Entity> mEntities;
};
class SystemManager {
// Map from system type string pointer to a signature
std::unordered_map<const char *, Signature> mSignatures{};
// Map from system type string pointer to a system pointer
std::unordered_map<const char *, std::shared_ptr<System>> mSystems{};
public:
template <typename T> std::shared_ptr<T> RegisterSystem();
template <typename T> void SetSignature(Signature signature);
void EntityDestroyed(Entity entity);
void EntitySignatureChanged(Entity entity, Signature entitySignature);
};
#endif // !SYSTEM_HPP

View file

@ -1,5 +1,7 @@
project('test', 'cpp')
sources = []
deps = []
deps += dependency('fmt')
deps += dependency('glfw3')

View file

@ -0,0 +1 @@
sources += files('shader.cpp', 'shader.hpp')

11
src/components/shader.cpp Normal file
View file

@ -0,0 +1,11 @@
#include "shader.hpp"
Shader::Shader(GLuint shader) { this->shader = shader; }
void Shader::SetSource(std::string source) { this->SetSource(source.c_str()); }
void Shader::SetSource(const char *source) {
glShaderSource(shader, 1, &source, NULL);
}
void Shader::Compile() { glCompileShader(shader); }
void Shader::Attach(GLuint program) { glAttachShader(program, shader); }
VertexShader::VertexShader() : Shader(glCreateShader(GL_VERTEX_SHADER)) {}
FragmentShader::FragmentShader() : Shader(glCreateShader(GL_FRAGMENT_SHADER)) {}

View file

@ -1,21 +1,10 @@
#ifndef SHADER_HPP
#define SHADER_HPP
#define SHADER_HPP
#include <GL/glew.h>
#include <linmath.h>
#include <string>
class ShaderSystem {
GLuint vertex_buffer;
GLuint program;
GLint mvp_location;
GLint vpos_location;
GLint vcol_location;
GLuint vertex_array;
public:
ShaderSystem();
int Draw(int width, int height, float time);
GLuint *CreateBuffer();
};
class Shader {
public:
Shader(GLuint shader);
@ -39,3 +28,5 @@ class FragmentShader : public Shader {
public:
FragmentShader();
};
#endif // SHADER_HPP

View file

@ -2,7 +2,7 @@
#include <GLFW/glfw3.h>
#include <fmt/core.h>
#include "systems/window-system.hpp"
#include "systems/windowsystem.hpp"
void error_callback(int error_code, const char *description) {
fmt::print("[ERROR - {}] {}\n", error_code, description);

View file

@ -1,2 +1,4 @@
sources = files('main.cpp')
sources += files('main.cpp')
subdir('components')
subdir('systems')

View file

@ -1,6 +1,6 @@
sources += files(
'shader-system.cpp',
'shader-system.hpp',
'window-system.cpp',
'window-system.hpp',
'shadersystem.cpp',
'shadersystem.hpp',
'windowsystem.cpp',
'windowsystem.hpp',
)

View file

@ -1,4 +1,6 @@
#include "shader-system.hpp"
#include "shadersystem.hpp"
#include <string>
#include <src/components/shader.hpp>
typedef struct Vertex {
vec2 pos;
@ -82,13 +84,3 @@ int ShaderSystem::Draw(int width, int height, float time) {
glDrawArrays(GL_TRIANGLES, 0, 3);
return 0;
}
Shader::Shader(GLuint shader) { this->shader = shader; }
void Shader::SetSource(std::string source) { this->SetSource(source.c_str()); }
void Shader::SetSource(const char *source) {
glShaderSource(shader, 1, &source, NULL);
}
void Shader::Compile() { glCompileShader(shader); }
void Shader::Attach(GLuint program) { glAttachShader(program, shader); }
VertexShader::VertexShader() : Shader(glCreateShader(GL_VERTEX_SHADER)) {}
FragmentShader::FragmentShader() : Shader(glCreateShader(GL_FRAGMENT_SHADER)) {}

View file

@ -0,0 +1,21 @@
#ifndef SHADERSYSTEM_HPP
#define SHADERSYSTEM_HPP
#include <GL/glew.h>
#include <linmath.h>
class ShaderSystem {
GLuint vertex_buffer;
GLuint program;
GLint mvp_location;
GLint vpos_location;
GLint vcol_location;
GLuint vertex_array;
public:
ShaderSystem();
int Draw(int width, int height, float time);
GLuint *CreateBuffer();
};
#endif // SHADERSYSTEM_HPP

View file

@ -1,4 +1,4 @@
#include "window-system.hpp"
#include "windowsystem.hpp"
WindowSystem::WindowSystem(GLFWwindow *window,
class ShaderSystem shaderSystem) {

View file

@ -1,6 +1,9 @@
#ifndef WINDOWSYSTEM_HPP
#define WINDOWSYSTEM_HPP
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include "shader-system.hpp"
#include "shadersystem.hpp"
class WindowSystem {
GLFWwindow *window;
@ -10,3 +13,5 @@ public:
WindowSystem(GLFWwindow *window, class ShaderSystem shaderSystem);
int Loop();
};
#endif // WINDOWSYSTEM_HPP