Skip to content

Instantly share code, notes, and snippets.

@qis
Last active November 27, 2020 17:58
Show Gist options
  • Save qis/05967d8fffecee346a8705cee0303842 to your computer and use it in GitHub Desktop.
Save qis/05967d8fffecee346a8705cee0303842 to your computer and use it in GitHub Desktop.
C++ component system with a singleton registry.
// ============================================================================
// component.hpp
// ============================================================================
#pragma once
#include <typeindex>
#include <cstddef>
class component_manager {
template <typename T>
friend class component;
public:
// Create component objects.
static std::size_t create();
// Create component objects with given name.
static std::size_t create(const char* name);
// Destroy component objects.
static void destroy();
// Clear component registry.
static void clear();
private:
static void add(std::type_index index, const char* name, void* (*create)(), void (*deleter)(void*));
static void* get(std::type_index index);
};
template <typename T>
class component {
public:
// Registers component.
component(const char* name) {
component_manager::add(
typeid(T),
name,
[]() {
return reinterpret_cast<void*>(new T());
},
[](void* o) {
delete reinterpret_cast<T*>(o);
});
}
// Returns component object.
static T* get() {
return reinterpret_cast<T*>(component_manager::get(typeid(T)));
}
};
// ============================================================================
// component.cpp
// ============================================================================
#include "component.hpp"
#include <algorithm>
#include <string>
#include <unordered_map>
#include <cassert>
namespace {
struct entry {
std::string name;
void* object = nullptr;
void* (*create)();
void (*deleter)(void*);
};
std::unordered_map<std::type_index, entry>& components() {
static struct registry {
~registry() {
component_manager::clear();
}
std::unordered_map<std::type_index, entry> components;
} registry;
return registry.components;
}
} // namespace
std::size_t component_manager::create() {
auto& c = components();
for (auto& e : c) {
if (!e.second.object) {
assert(e.second.create);
assert(e.second.deleter);
e.second.object = e.second.create();
}
}
return c.size();
}
std::size_t component_manager::create(const char* name) {
const auto search = [name](const auto& e) {
return e.second.name == name;
};
auto& c = components();
const auto next = [&c, &search](auto begin) {
return std::find_if(begin, c.end(), search);
};
std::size_t count = 0;
for (auto it = next(c.begin()); it != c.end(); it = next(++it)) {
if (!it->second.object) {
assert(it->second.create);
assert(it->second.deleter);
it->second.object = it->second.create();
}
count++;
}
return count;
}
void component_manager::destroy() {
for (auto& e : components()) {
if (!e.second.object) {
assert(e.second.deleter);
e.second.deleter(e.second.object);
e.second.object = nullptr;
}
}
}
void component_manager::clear() {
destroy();
components().clear();
}
void component_manager::add(std::type_index index, const char* name, void* (*create)(), void (*deleter)(void*)) {
auto& e = components()[index];
if (e.object) {
assert(e.deleter);
e.deleter(e.object);
e.object = nullptr;
}
assert(name);
e.name = name;
e.create = create;
e.deleter = deleter;
}
void* component_manager::get(std::type_index index) {
auto& c = components();
if (const auto it = c.find(index); it != c.end()) {
if (!it->second.object) {
assert(it->second.create);
assert(it->second.deleter);
it->second.object = it->second.create();
}
return it->second.object;
}
return nullptr;
}
// ============================================================================
// example1.hpp
// ============================================================================
#pragma once
class example1 {
public:
int value1();
};
// ============================================================================
// example1.cpp
// ============================================================================
#include "example1.hpp"
#include <component.hpp>
int example1::value1() {
return 1;
}
component<example1> example1_component("example1");
// ============================================================================
// example2.hpp
// ============================================================================
#pragma once
class example2 {
public:
example2();
int value2();
};
// ============================================================================
// example2.cpp
// ============================================================================
#include "example2.hpp"
#include <component.hpp>
example2::example2() {
// Make sure that the example1 component is created first.
component_manager::create("example1");
}
int example2::value2() {
return 2;
}
component<example2> example2_component("example2");
// ============================================================================
// main.cpp
// ============================================================================
#include <component.hpp>
#include <example1.hpp>
#include <example2.hpp>
#include <string>
#include <cstdio>
int main() {
// Create components.
component_manager::create();
// Use components.
std::puts(std::to_string(component<example1>::get()->value1()).data());
std::puts(std::to_string(component<example2>::get()->value2()).data());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment