-
Notifications
You must be signed in to change notification settings - Fork 0
/
dsecs.hpp
177 lines (137 loc) · 5.96 KB
/
dsecs.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#pragma once
#include <memory>
#include <unordered_map>
#include <ranges>
#include <vector>
#include <string>
#include <sstream>
#include <concepts>
// dead simple ecs
namespace dsecs {
/* concepts forward declare */
template <typename T>
concept Streamable = requires(std::ostream &os, T value) {
{ os << value } -> std::convertible_to<std::ostream &>;
};
/* entity trinity */
using Entity = uint64_t;
constexpr Entity NoEntity = 0;
/* component trinity */
struct ComponentManagerBase {
std::string name;
ComponentManagerBase(std::string_view name)
: name(name) { }
virtual ~ComponentManagerBase() = default;
virtual auto has(Entity e) const -> bool = 0;
virtual void del(Entity e) = 0;
virtual auto str(Entity e) const -> std::string = 0;
};
template<typename TComp>
struct ComponentManager final : ComponentManagerBase {
std::unordered_map<Entity, TComp> values; // the actual array
ComponentManager(std::string_view name)
: ComponentManagerBase(name), values() { }
virtual ~ComponentManager() = default;
virtual auto has(Entity e) const -> bool override final { return values.contains(e); }
virtual void del(Entity e) override final { values.erase(e); }
auto get(Entity e) const -> TComp const& { return values.at(e); }
auto mut(Entity e) -> TComp& { return values.at(e); }
void set(Entity e, TComp&& v) { values.insert_or_assign(e, v); }
void with(Entity e, std::invocable<TComp&> auto chain) {
if (auto it = values.find(e); it != values.end())
chain(it->second); // reuse the found iterator/lookup
}
virtual auto str(Entity e) const -> std::string override {
if (auto it = values.find(e); it != values.end())
if constexpr (Streamable<TComp>) {
std::stringstream ss;
ss << it->second;
return ss.str();
} else
return "<UNSTREAMABLE>";
else
return "<NULL>";
}
};
/* system trinity */
struct SystemBase {
std::string name;
bool enable = true;
SystemBase(std::string_view name)
: name(name) { }
virtual ~SystemBase() = default;
virtual void update(class World* w) = 0;
};
template<std::invocable<class World*> FExec>
struct SystemAnonymous : SystemBase {
FExec execution;
SystemAnonymous(std::string_view name, FExec execution)
: SystemBase(name), execution(execution) { }
virtual ~SystemAnonymous() = default;
virtual void update(class World* w) override { execution(w); } // the actual dispatch
};
/* name ergonomics */
struct Name {
std::string name;
};
auto& operator<<(std::ostream& os, Name n) {
return os << n.name;
}
/* final world type */
class World {
Entity _nextEntity = 1;
std::unordered_map<size_t, std::shared_ptr<ComponentManagerBase>> _components; // the dynamic structure
std::vector<std::shared_ptr<SystemBase>> _systems; // the dynamic update list
std::unordered_map<std::string_view, Entity> _entityNames;
public:
/* trinity */
auto newEntity() -> Entity { return _nextEntity++; }
template<typename TComp>
auto requireComponent() -> std::shared_ptr<ComponentManager<TComp>> {
auto key = typeid(TComp).hash_code();
if (auto it = _components.find(key); it != _components.end())
// this static cast is safe because we index by typeid
return std::static_pointer_cast<ComponentManager<TComp>>(it->second);
auto res = std::make_shared<ComponentManager<TComp>>(typeid(TComp).name());
_components[key] = res;
return res;
}
auto allEntities() { return std::ranges::iota_view(1u, _nextEntity); }
void update() {
for (auto sys : _systems | std::views::filter(&SystemBase::enable)) {
sys->update(this);
}
}
template<std::invocable<World*> FExec>
auto makeSystem(std::string_view name, FExec exec) -> std::shared_ptr<SystemAnonymous<FExec>> {
auto res = std::make_shared<SystemAnonymous<FExec>>(name, exec);
_systems.emplace_back(res);
return res;
}
/* ergonomics I */
auto findEntity(std::string_view name) -> Entity {
auto it = _entityNames.find(name);
return (it != _entityNames.end()) ? it->second : NoEntity;
}
auto requireEntity(std::string_view name) -> Entity {
// early exit if the name already exists
// we don't attempt to reuse this lookup in this case because the string view in the index must come from the component for a safe lifetime.
if (auto e = findEntity(name); e != NoEntity)
return e;
auto e = newEntity();
std::string_view name_str = (requireComponent<Name>()->values[e] = { std::string(name) }).name;
return _entityNames[name_str] = e;
}
auto allComponents() { return _components | std::views::values; }
auto allSystems() { return _systems | std::views::all; }
auto findSystem(std::string_view name) -> std::shared_ptr<SystemBase> {
auto it = std::ranges::find_if(_systems, [&](auto s){ return s->name == name; });
return (it != _systems.end()) ? *it : nullptr;
}
void kill(Entity e) {
for (auto c : allComponents())
if (c->has(e))
c->del(e);
}
};
}