1 /**
2 Copyright: Copyright (c) 2016-2018 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module datadriven.entityman;
7 
8 import cbor;
9 import voxelman.log;
10 import datadriven.api;
11 import datadriven.storage;
12 import datadriven.query;
13 import voxelman.container.buffer;
14 import voxelman.container.hash.set;
15 import voxelman.serialization;
16 
17 private struct ComponentInfo
18 {
19 	IoKey ioKey;
20 	bool serializeToDb;
21 	bool serializeToNet;
22 	void delegate(EntityId) remove;
23 	void delegate() removeAll;
24 	void delegate(Buffer!ubyte*) serialize;
25 	void delegate(Buffer!ubyte*, HashSet!EntityId) serializePartial;
26 	void delegate(ubyte[]) deserialize;
27 	void* storage;
28 
29 	bool isSerialized(IoStorageType storageType) {
30 		final switch(storageType) {
31 			case IoStorageType.database: return serializeToDb;
32 			case IoStorageType.network: return serializeToNet;
33 		}
34 	}
35 
36 	ComponentStorage!C* getTypedStorage(C)() {
37 		return cast(ComponentStorage!C*)storage;
38 	}
39 }
40 
41 enum bool isTagComponent(C) = C.tupleof.length == 0;
42 
43 private struct _Totally_empty_struct {}
44 static assert(isTagComponent!_Totally_empty_struct);
45 
46 template ComponentStorage(C)
47 {
48 	static if(isTagComponent!C)
49 		alias ComponentStorage = EntitySet;
50 	else
51 		alias ComponentStorage = HashmapComponentStorage!C;
52 }
53 
54 struct EntityIdManager
55 {
56 	private EntityId lastEntityId; // 0 is reserved
57 	IoKey ioKey = IoKey("voxelman.entity.lastEntityId");
58 
59 	EntityId nextEntityId()
60 	{
61 		return ++lastEntityId;
62 	}
63 
64 	void load(ref PluginDataLoader loader)
65 	{
66 		loader.readEntryDecoded(ioKey, lastEntityId);
67 	}
68 
69 	void save(ref PluginDataSaver saver)
70 	{
71 		saver.writeEntryEncoded(ioKey, lastEntityId);
72 	}
73 }
74 
75 /// Convenience type for centralized storage and management of entity components.
76 struct EntityManager
77 {
78 	private ComponentInfo*[TypeInfo] componentInfoMap;
79 	private ComponentInfo*[] componentInfoArray;
80 	EntityIdManager* eidMan;
81 
82 	/// Before using component type in every other method, register it here.
83 	/// name is used for (de)serialization.
84 	void registerComponent(C)()
85 	{
86 		assert(typeid(C) !in componentInfoMap);
87 		auto storage = new ComponentStorage!C;
88 		auto info =	new ComponentInfo(
89 			IoKey(componentUda!C.key),
90 			cast(bool)(componentUda!C.replication & Replication.toDb),
91 			cast(bool)(componentUda!C.replication & Replication.toClient),
92 			&storage.remove,
93 			&storage.removeAll,
94 			&storage.serialize,
95 			&storage.serializePartial,
96 			&storage.deserialize,
97 			storage);
98 		componentInfoMap[typeid(C)] = info;
99 		componentInfoArray ~= info;
100 
101 		//tracef("Register component %s", *info);
102 	}
103 
104 	auto getIoKeys() {
105 		static struct IoKeyRange
106 		{
107 			ComponentInfo*[] array;
108 			int opApply(scope int delegate(ref IoKey) del) {
109 				foreach(info; array)
110 				{
111 					if (auto ret = del(info.ioKey))
112 						return ret;
113 				}
114 				return 0;
115 			}
116 		}
117 
118 		return IoKeyRange(componentInfoArray);
119 	}
120 
121 	/// Returns pointer to the storage of components C.
122 	/// Storage type depends on component type (tag or not).
123 	auto getComponentStorage(C)()
124 	{
125 		ComponentInfo* untyped = componentInfoMap[typeid(C)];
126 		return untyped.getTypedStorage!C();
127 	}
128 
129 	/// Add or set list of components for entity eid.
130 	void set(Components...)(EntityId eid, Components components)
131 	{
132 		foreach(i, C; Components)
133 		{
134 			static if(isTagComponent!C)
135 				getComponentStorage!C().set(eid);
136 			else
137 				getComponentStorage!C().set(eid, components[i]);
138 		}
139 	}
140 
141 	/// Returns pointer to the component of type C.
142 	/// Returns null if entity has no such component.
143 	/// Works only with non-tag components.
144 	C* get(C)(EntityId eid)
145 	{
146 		static assert (!isTagComponent!C, "Cannot use get for tag component, use has method");
147 		return getComponentStorage!C().get(eid);
148 	}
149 
150 	/// Returns pointer to the component of type C.
151 	/// Creates component first if entity had no such component.
152 	/// Works only with non-tag components.
153 	C* getOrCreate(C)(EntityId eid, C defVal = C.init)
154 	{
155 		static assert (!isTagComponent!C, "Cannot use getOrCreate for tag component");
156 		return getComponentStorage!C().getOrCreate(eid, defVal);
157 	}
158 
159 	/// Used to check for presence of given component or tag.
160 	bool has(C)(EntityId eid)
161 	{
162 		return cast(bool)getComponentStorage!C().get(eid);
163 	}
164 
165 	/// Removes one component for given eid.
166 	void remove(C)(EntityId eid)
167 	{
168 		getComponentStorage!C().remove(eid);
169 	}
170 
171 	/// Removes all components for given eid.
172 	void remove(EntityId eid)
173 	{
174 		foreach(info; componentInfoArray)
175 		{
176 			info.remove(eid);
177 		}
178 	}
179 
180 	/// Removes all components of all types.
181 	void removeAll()
182 	{
183 		foreach(info; componentInfoArray)
184 		{
185 			info.removeAll();
186 		}
187 	}
188 
189 	/// Returns query object for given set of component types for iteration with foreach.
190 	/// Will pass EntityId, followed by pointers to components. Flag components are omitted.
191 	/// Example:
192 	/// ---
193 	/// auto query = eman.query!(Position, Velocity, IsMovable);
194 	///	foreach(EntityId id, Position* position, Velocity* velocity; query)
195 	/// {
196 	/// 	position.vector += velocity.vector;
197 	/// }
198 	/// ---
199 	auto query(Components...)()
200 	{
201 		// generate variables for typed storages
202 		mixin(genTempComponentStorages!Components);
203 		// populate variables
204 		foreach(i, C; Components)
205 		{
206 			mixin(genComponentStorageName!(ComponentStorage!C, i)) =
207 				getComponentStorage!C();
208 		}
209 		// construct query with storages
210 		return mixin(genQueryCall!Components);
211 	}
212 
213 	/// Serializes all component storages with given saver.
214 	void save(Saver)(ref Saver saver)
215 	{
216 		foreach(info; componentInfoArray) {
217 			if(!info.isSerialized(saver.storageType)) continue;
218 			info.serialize(saver.beginWrite());
219 			saver.endWrite(info.ioKey);
220 		}
221 	}
222 
223 	void savePartial(Saver, E)(ref Saver saver, E entities)
224 	{
225 		foreach(info; componentInfoArray) {
226 			if(!info.isSerialized(saver.storageType)) continue;
227 			info.serializePartial(saver.beginWrite(), entities);
228 			saver.endWrite(info.ioKey);
229 		}
230 	}
231 
232 	/// Deserializes all component storages from given loader.
233 	void load(Loader)(ref Loader loader, bool removeBeforeRead = true)
234 	{
235 		foreach(info; componentInfoArray) {
236 			if(!info.isSerialized(loader.storageType)) continue;
237 			auto data = loader.readEntryRaw(info.ioKey);
238 			if (removeBeforeRead)
239 				info.removeAll();
240 			info.deserialize(data);
241 		}
242 	}
243 
244 	void removeSerializedComponents(IoStorageType storageType)
245 	{
246 		foreach(info; componentInfoArray) {
247 			if(info.isSerialized(storageType))
248 				info.removeAll();
249 		}
250 	}
251 }
252 
253 private string genTempComponentStorages(Components...)()
254 {
255 	import std.conv : to;
256 	string result;
257 
258 	foreach(i, C; Components)
259 	{
260 		result ~= "ComponentStorage!(Components[" ~ i.to!string ~ "])* " ~
261 			genComponentStorageName!(ComponentStorage!C, i) ~ ";\n";
262 	}
263 
264 	return result;
265 }
266 
267 private string genQueryCall(Components...)()
268 {
269 	string result = "componentQuery(";
270 
271 	foreach(i, C; Components)
272 	{
273 		result ~= "*" ~ genComponentStorageName!(ComponentStorage!C, i) ~ ",";
274 	}
275 	result ~= ")";
276 
277 	return result;
278 }