Last active
March 25, 2020 09:57
-
-
Save seanballais/5b8f4c99fbd0ed1d03ee1b9360e32c36 to your computer and use it in GitHub Desktop.
Relevant Files for StackOverflow Question #60835314
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
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); | |
} | |
} | |
} |
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
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; | |
}; | |
} |
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
- Entity is an alias to, at the moment, a size_t. | |
- Signature is an alias to an STL bitset. |
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
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) {} | |
} |
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
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