It's my ComponentContainer code:
#pragma once
#include <cassert>
#include <vector>
#include <unordered_map>
#include "tool/config.h"
#include "component.h"
struct ComponentContainerData {
ComponentType type;
size_t sizeOfComponent;
size_t capacity;
size_t freeIndex;
};
template<typename ComponentClass>
struct Components {
size_t size;
ComponentClass* components;
ComponentClass& operator[](size_t index) {
return components[index];
}
};
class ComponentContainer {
public:
bool componentExist(ComponentHandle component) {
assert(component != 0 && "Null component request (null component is always invalid)");
return componentHandles.find(component) != componentHandles.end();
}
template<typename ComponentClass>
ComponentClass* getComponent(ComponentHandle component) {
assert(componentExist(component));
return (ComponentClass*) componentHandles[component];
}
template<typename ComponentClass>
ComponentClass* getComponent(Entity owner) {
assert(entityExist(owner));
for (unsigned int i = 0; i < containers.size(); i++) {
if (containers[i].second.type == ComponentClass::type) {
for (unsigned int j = 0; j < containers[i].second.freeIndex; j++) { //TODO: possible optimalization - owner is sorted
ComponentClass* component = (ComponentClass*) containers[i].first + j;
if (component->owner == owner)
return component;
}
return nullptr;
}
}
return nullptr;
}
template<typename ComponentClass>
Components<ComponentClass> getComponents() { //when optimalization mentioned in comment below(containers field) will be done, it will be simple return {containers[ComponentClass::type.....
for (unsigned int i = 0; i < containers.size(); i++) {
if (containers[i].second.type == ComponentClass::type) {
return {containers[i].second.freeIndex, (ComponentClass*) containers[i].first};
}
}
return {0, nullptr};
}
template<typename ComponentClass, typename ...CmpArgs>
ComponentClass* createComponent(Entity owner, CmpArgs&& ...cmpArgs) {
assert(entityExist(owner));
auto container = locateAndPrepareComponentContainer<ComponentClass>();
if (!container)
return nullptr;
ComponentClass* createdComponent = new (container->first + sizeof(ComponentClass) * container->second.freeIndex)
ComponentClass(owner, nextComponentHandle, std::forward<CmpArgs>(cmpArgs)...);
componentHandles[nextComponentHandle] = (Component*) &container->first[sizeof(ComponentClass) * container->second.freeIndex];
ComponentHandle currentHandle = nextComponentHandle++;
container->second.freeIndex++;
logger.info("Created new component. Owner: %u, Handle: %llu, (int)Type: %d", (unsigned int) owner, currentHandle, (int) ComponentClass::type);
return createdComponent;
}
template<typename ComponentClass>
void deleteComponent(Entity owner) {
assert(entityExist(owner));
for (auto& container : containers) {
if (container.second.type == ComponentClass::type) {
ComponentClass* currentComponent = (ComponentClass*) container.first;
for (unsigned int i = 0; i < container.second.freeIndex; i++, currentComponent++) {
if (currentComponent->owner == owner) {
logger.info("Deleted component %llu. Type: %d, Owner: %u", currentComponent->handle, (int) ComponentClass::type, (unsigned int) owner);
componentHandles.erase(currentComponent->handle);
currentComponent->~ComponentClass();
ComponentClass* lastComponent = (ComponentClass*) container.first + --container.second.freeIndex;
if (currentComponent != lastComponent) {
memcpy(currentComponent, lastComponent, sizeof(ComponentClass));
}
return;
}
}
logger.warn("Cannot delete component type %d with owner %u: component not in container", (int) ComponentClass::type, (unsigned int) owner);
return;
}
}
logger.warn("Cannot delete component type %d with owner %u: component not found, even container don't exist", (int) ComponentClass::type, (unsigned int) owner);
}
bool entityExist(Entity entityID) {
assert(entityID < entityExistingTable.size() && "Entity with this id cannot exist (and we're reading garbage)");
return entityID < entityExistingTable.size() && entityExistingTable[entityID];
}
Entity createEntity() {
entityExistingTable.push_back(1);
return Entity(entityExistingTable.size() - 1);
}
void deleteEntity(Entity owner) {
assert(entityExist(owner));
for (auto& container : containers) {
Component* currentComponent = (Component*) container.first;
for (unsigned int i = 0; i < container.second.freeIndex; i++) {
if (currentComponent->owner == owner) {
logger.info("deleteEntity: Deleted component %llu. Type: %d, Owner: %u", currentComponent->handle, container.second.type, (unsigned int) owner);
componentHandles.erase(currentComponent->handle);
currentComponent->~Component();
Component* lastComponent = (Component*) container.first + --container.second.freeIndex;
if (currentComponent != lastComponent) {
memcpy(currentComponent, lastComponent, container.second.sizeOfComponent);
}
continue;
}
char* rawCurrent = (char*) currentComponent;
currentComponent = (Component*) (rawCurrent + container.second.sizeOfComponent);
}
}
}
~ComponentContainer() {
for (auto& container : containers) {
free(container.first); //FIXME: this only deallocates memory, won't call destructors. (components shouldn't have destructors but...)
}
}
private:
std::vector<std::pair<char*, ComponentContainerData> > containers; //TODO: possible optimalization: it should be containers[i].second.type == i. So a)change it to map, b)when creating container fill fields with 0 while
std::unordered_map<ComponentHandle, Component*> componentHandles; //untill desired type is if containers.size < type, else just change this 0 to appropiate type. 0 can be indicated by pair {nullptr, {}} or sthk
ComponentHandle nextComponentHandle = 1; //maybe even component handle stuff will be unnecessary, since it will be fast.
std::vector<char> entityExistingTable;
template<typename ComponentClass>
std::pair<char*, ComponentContainerData>* locateAndPrepareComponentContainer() {
for (auto& container : containers) {
if (container.second.type == ComponentClass::type) {
if (container.second.freeIndex >= container.second.capacity) {
if (!resizeContainer(container, sizeof(ComponentClass), (int) ComponentClass::type)) {
return nullptr;
}
for (unsigned int i = 0; i < container.second.freeIndex; i++) {
ComponentClass* elem = (ComponentClass*) container.first;
componentHandles[elem[i].handle] = &elem[i];
}
}
return &container;
}
}
if (!createNewContainer<ComponentClass>()) {
return nullptr;
}
return &containers.back();
}
bool resizeContainer(std::pair<char*, ComponentContainerData>& container, size_t sizeOfElement, int typeID) {
logger.info("Resizing component container for type %d. Current Capacity: %u", typeID, container.second.capacity);
size_t capacity = container.second.capacity * config.get("ECS.growFactor", 2);
char* result = (char*) realloc(container.first, capacity * sizeOfElement);
if (!result) {
//try to get at least necessary amount of memory
capacity = container.second.capacity + 1;
result = (char*) realloc(container.first, capacity * sizeOfElement);
if (!result) {
logger.fatal("Cannot resize component container, even by 1 element. Desired capacity: %d, Sizeof(Type): %d, type: %d", capacity, sizeOfElement, typeID);
assert(!"resizeContainer: cannot allocate memory for new element");
return false;
}
}
container.second.capacity = capacity;
container.first = result;
return true;
}
template<typename ComponentClass>
bool createNewContainer() {
ComponentContainerData metadata;
metadata.type = ComponentClass::type;
metadata.capacity = config.get("ECS.initialCapacity", 128);
metadata.sizeOfComponent = sizeof(ComponentClass);
metadata.freeIndex = 0;
char* rawComps = (char*) malloc(sizeof(ComponentClass) * metadata.capacity);
if (!rawComps) {
//try to get at least necessary amount of memory
metadata.capacity = 1;
rawComps = (char*) malloc(sizeof(ComponentClass));
if (!rawComps) {
logger.fatal("Cannot create new container, even for 1 element. Sizeof(Type): %d, Type: %d", sizeof(ComponentClass), (int) ComponentClass::type);
assert(!"Create component: cannot allocate memory for new container; even for 1 element");
return false;
}
}
containers.push_back({rawComps, metadata});
return true;
}
};
It has all components of the same type in array, and all arrays are in one array, so there is no need to update ComponentClass if new components are added. It also forwards arguments to components.
component.h:
#pragma once
#include <boost/serialization/strong_typedef.hpp>
enum class ComponentType {
SampleComponent, //for unit test
PositionComponent,
SpriteComponent,
RectComponent,
MovementComponent,
CollisionComponent,
SizeComponent
};
using ComponentHandle = unsigned long long;
BOOST_STRONG_TYPEDEF(unsigned
int, Entity);
struct Component {
Component(Entity owner, ComponentHandle handle) : owner(owner), handle(handle) {
}
virtual ~Component() {
}
Entity owner;
ComponentHandle handle;
};
tool/config.h is unneded, it's only config.get calls, their second value is default.
All derived Components must have static variable of type ComponentType and name type