Skip to content

Instantly share code, notes, and snippets.

@seanballais
Last active March 25, 2020 09:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save seanballais/5b8f4c99fbd0ed1d03ee1b9360e32c36 to your computer and use it in GitHub Desktop.
Save seanballais/5b8f4c99fbd0ed1d03ee1b9360e32c36 to your computer and use it in GitHub Desktop.
Relevant Files for StackOverflow Question #60835314
namespace planes::engine::ecs
{
NoComponentForEntityError::NoComponentForEntityError(const char* what_arg)
: std::runtime_error(what_arg) {}
UnregisteredComponentTypeError::UnregisteredComponentTypeError(
const char* what_arg)
: std::runtime_error(what_arg) {}
void ComponentManager::notifyEntityDeleted(const Entity e)
{
for (const auto& item : this->typeNameToArrayMap) {
IComponentArray& componentArray = *(item.second.get());
componentArray.notifyEntityDeleted(e);
}
}
}
namespace planes::engine::ecs {
class NoComponentForEntityError : std::runtime_error
{
public:
NoComponentForEntityError(const char* what_arg);
};
class IComponentArray
{
public:
virtual ~IComponentArray() = default;
virtual void notifyEntityDeleted(Entity entity) = 0;
};
template <class T>
class ComponentArray : public IComponentArray
{
public:
ComponentArray() {}
void addComponent(const Entity entity, const T component)
{
const int entityIndex = this->components.size();
this->components.push_back(component);
this->entityToComponentMap.insert({entity, entityIndex});
}
T& getComponent(const Entity entity)
{
auto item = this->getComponentFromEntity(entity);
const int entityIndex = item->second;
return this->components[entityIndex];
}
void deleteComponent(const Entity entity)
{
auto item = this->getComponentFromEntity(entity);
const int deletedEntityIndex = item->second;
T lastComponent = this->components.back();
this->components[deletedEntityIndex] = lastComponent;
this->components.pop_back();
this->entityToComponentMap.erase(item);
}
void notifyEntityDeleted(const Entity entity) override
{
try {
auto item = this->getComponentFromEntity(entity);
} catch (NoComponentForEntityError e) {
return;
}
this->deleteComponent(entity);
}
private:
std::unordered_map<Entity, uint32_t>::iterator
getComponentFromEntity(const Entity entity)
{
auto item = this->entityToComponentMap.find(entity);
if (item == this->entityToComponentMap.end()) {
std::stringstream errorMsgStream;
errorMsgStream << "Entity" << entity
<< " does not have a component we hold.";
const std::string errorMsg = errorMsgStream.str();
throw NoComponentForEntityError(errorMsg.c_str());
}
return item;
}
std::vector<T> components;
std::unordered_map<Entity, uint32_t> entityToComponentMap;
};
class UnregisteredComponentTypeError : public std::runtime_error
{
public:
UnregisteredComponentTypeError(const char* what_arg);
};
class ComponentManager
{
public:
ComponentManager()
: nextComponentTypeIndex(0) {}
template <typename T>
void registerComponentType()
{
// There should be an error when a component type has been registered
// twice.
const std::string typeName = typeid(T).name();
this->typeNameToArrayMap.insert({
typeName,
std::make_unique<ComponentArray<T>>()});
this->typeNameToIndexMap.insert({typeName, this->nextComponentTypeIndex});
this->nextComponentTypeIndex++;
}
template <typename T>
unsigned int getComponentTypeIndex()
{
// This should really only be done in debug mode.
this->checkComponentTypeRegistration<T>();
const std::string typeName = typeid(T).name();
return this->typeNameToIndexMap[typeName];
}
template <typename T>
T& getComponent(const Entity e)
{
// This should really only be done in debug mode.
this->checkComponentTypeRegistration<T>();
return this->getComponentTypeArray<T>()
.getComponent(e);
}
template <typename T>
void addComponentType(const Entity e)
{
// This should really only be done in debug mode.
this->checkComponentTypeRegistration<T>();
this->getComponentTypeArray<T>()
.addComponent(e, T{});
}
template <typename T>
void deleteComponentType(const Entity e)
{
// This should really only be done in debug mode.
this->checkComponentTypeRegistration<T>();
this->getComponentTypeArray<T>()
.deleteComponent(e);
}
void notifyEntityDeleted(const Entity e);
private:
template <typename T>
void checkComponentTypeRegistration() const
{
const std::string typeName = typeid(T).name();
const auto item = this->typeNameToArrayMap.find(typeName);
if (item == this->typeNameToArrayMap.end()) {
std::stringstream errorMsgStream;
errorMsgStream << "Attempted to access an unregistered component type, "
<< typeName << ".";
const std::string errorMsg = errorMsgStream.str();
throw UnregisteredComponentTypeError(errorMsg.c_str());
}
}
template <typename T>
ComponentArray<T>& getComponentTypeArray()
{
this->checkComponentTypeRegistration<T>();
const std::string typeName = typeid(T).name();
IComponentArray* const array = this->typeNameToArrayMap[typeName].get();
return *(dynamic_cast<ComponentArray<T>*>(array));
}
std::unordered_map<std::string, std::unique_ptr<IComponentArray>>
typeNameToArrayMap;
std::unordered_map<std::string, unsigned int> typeNameToIndexMap;
unsigned int nextComponentTypeIndex;
};
}
- Entity is an alias to, at the moment, a size_t.
- Signature is an alias to an STL bitset.
namespace planes::engine::ecs
{
System::System(const Signature signature, ComponentManager& componentManager)
: signature(signature)
, componentManager(componentManager)
, entityToIndexMap({})
, numEntities(0) {}
void System::addEntity(const Entity e)
{
auto item = this->entityToIndexMap.find(e);
if (item == this->entityToIndexMap.end()) {
this->entities.push_back(e);
this->entityToIndexMap.insert({e, this->numEntities});
this->numEntities++;
} else {
std::stringstream errorMsgStream;
errorMsgStream << "Attempted to add already added entity, " << e;
std::string errorMsg = errorMsgStream.str();
throw EntityAlreadyExistsError(errorMsg);
}
}
void System::removeEntity(const Entity e)
{
auto item = this->entityToIndexMap.find(e);
if (item == this->entityToIndexMap.end()) {
std::stringstream errorMsgStream;
errorMsgStream << "Attempted to remove a non-registered entity, " << e;
std::string errorMsg = errorMsgStream.str();
throw UnregisteredEntityError(errorMsg);
} else {
size_t entityIndex = item->second;
const Entity& lastEntity = this->entities.back();
this->entities[entityIndex] = lastEntity;
this->entities.pop_back();
this->entityToIndexMap.erase(item);
}
}
EntityAlreadyExistsError::EntityAlreadyExistsError(const char* what_arg)
: std::runtime_error(what_arg) {}
EntityAlreadyExistsError::EntityAlreadyExistsError(const std::string what_arg)
: std::runtime_error(what_arg) {}
UnregisteredEntityError::UnregisteredEntityError(const char* what_arg)
: std::runtime_error(what_arg) {}
UnregisteredEntityError::UnregisteredEntityError(const std::string what_arg)
: std::runtime_error(what_arg) {}
}
namespace planes::engine::ecs
{
class EntityAlreadyExistsError;
class UnregisteredEntityError;
class System
{
public:
System(const Signature signature, ComponentManager& componentManager);
void addEntity(const Entity e);
void removeEntity(const Entity e);
virtual void update() = 0;
protected:
const Signature signature;
ComponentManager& componentManager;
// We're using a vector here for cache locality during system updates.
std::vector<Entity> entities;
private:
// This unordered set is typically used during entity addition and removal
// for a fast checking if an entity is registered in the system or not.
std::unordered_map<Entity, size_t> entityToIndexMap;
size_t numEntities;
};
class EntityAlreadyExistsError : public std::runtime_error
{
public:
EntityAlreadyExistsError(const char* what_arg);
EntityAlreadyExistsError(const std::string what_arg);
};
class UnregisteredEntityError : public std::runtime_error
{
public:
UnregisteredEntityError(const char* what_arg);
UnregisteredEntityError(const std::string what_arg);
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment