1 /*
2                                     __
3                                    / _|
4   __ _ _   _ _ __ ___  _ __ __ _  | |_ ___  ___ ___
5  / _` | | | | '__/ _ \| '__/ _` | |  _/ _ \/ __/ __|
6 | (_| | |_| | | | (_) | | | (_| | | || (_) \__ \__ \
7  \__,_|\__,_|_|  \___/|_|  \__,_| |_| \___/|___/___/
8 
9 Copyright (C) 2019-2020 Aurora Free Open Source Software.
10 Copyright (C) 2019 João Lourenço <joao@aurorafoss.org>
11 
12 This file is part of the Aurora Free Open Source Software. This
13 organization promote free and open source software that you can
14 redistribute and/or modify under the terms of the GNU Lesser General
15 Public License Version 3 as published by the Free Software Foundation or
16 (at your option) any later version approved by the Aurora Free Open Source
17 Software Organization. The license is available in the package root path
18 as 'LICENSE' file. Please review the following information to ensure the
19 GNU Lesser General Public License version 3 requirements will be met:
20 https://www.gnu.org/licenses/lgpl.html .
21 
22 Alternatively, this file may be used under the terms of the GNU General
23 Public License version 3 or later as published by the Free Software
24 Foundation. Please review the following information to ensure the GNU
25 General Public License requirements will be met:
26 https://www.gnu.org/licenses/gpl-3.0.html.
27 
28 NOTE: All products, services or anything associated to trademarks and
29 service marks used or referenced on this file are the property of their
30 respective companies/owners or its subsidiaries. Other names and brands
31 may be claimed as the property of others.
32 
33 For more info about intellectual property visit: aurorafoss.org or
34 directly send an email to: contact (at) aurorafoss.org .
35 */
36 
37 module aurorafw.entity.entity;
38 
39 import aurorafw.entity.icomponent;
40 import aurorafw.entity.componentmanager;
41 import aurorafw.entity.entitymanager;
42 
43 version (unittest) import aurorafw.unit.assertion;
44 
45 import std.exception;
46 import std.traits : fullyQualifiedName, Fields, FieldNameTuple;
47 import std.meta : AliasSeq;
48 
49 class EntityComponentHandlingException : Exception
50 {
51 	mixin basicExceptionCtors;
52 }
53 
54 class Entity
55 {
56 	@safe pure
57 	public this(EntityManager manager, size_t id)
58 	{
59 		this.id = id;
60 		this.manager = manager;
61 		this.enabled = true;
62 	}
63 
64 	/**
65 	 * Add a component
66 	 *
67 	 * Every time you inittialize a new component, an unique id is generated
68 	 * Use this function only if you want to generate the component with values
69 	 *
70 	 * Examples:
71 	 * --------------------
72 	 * e.AddComponent(new Foo(value1, value2, ...));
73 	 * --------------------
74 	 *
75 	 * See_Also: T add(T : IComponent)()
76 	 */
77 	@safe pure
78 	public T add(T : IComponent)(T t)
79 	{
80 		enum id = ComponentManager.idOf!T;
81 
82 		if (id in this.components)
83 			throw new EntityComponentHandlingException(
84 					"Cannot add component. Entity already contains the same type."
85 			);
86 
87 		this.components[id] = t;
88 		return t;
89 	}
90 
91 	/**
92 	 * Add a component
93 	 *
94 	 * Use this only if you want to generate the component with init values
95 	 *
96 	 * Examples:
97 	 * --------------------
98 	 * e.AddComponent!Foo;
99 	 * --------------------
100 	 */
101 	@safe pure
102 	public T add(T : IComponent)()
103 	{
104 		return add(new T());
105 	}
106 
107 	/**
108 	 * Remove a component
109 	 *
110 	 * It's called by passing the component's id
111 	 * Every time you inittialize a new component, an unique id is generated
112 	 * If you don't know which id you should pass, use the other variant of this
113 	 *   function, which is the recomended one, as it will get the correct id
114 	 *   for you
115 	 *
116 	 * Examples:
117 	 * --------------------
118 	 * e.remove(world.component.idOf!Foo);
119 	 * --------------------
120 	 *
121 	 * See_Also: remove(C : IComponent)()
122 	 */
123 	@safe pure
124 	public void remove(in string id)
125 	{
126 		if (id in this.components)
127 			this.components.remove(id);
128 
129 		else
130 			throw new EntityComponentHandlingException(
131 					"Cannot remove component. Entity doesn't contain the type you're trying to remove."
132 			);
133 	}
134 
135 	/**
136 	 * Removes a component
137 	 *
138 	 * It's recomended to always use this function, as it will do the 'hard
139 	 *   work' for you
140 	 *
141 	 * Examples:
142 	 * --------------------
143 	 * e.remove!Foo;
144 	 * --------------------
145 	 */
146 	@safe pure
147 	public void remove(C : IComponent)()
148 	{
149 		remove(ComponentManager.idOf!C);
150 	}
151 
152 	/**
153 	 * Modify
154 	 *
155 	 * Updates the data of a component
156 	 *
157 	 * Examples:
158 	 * --------------------
159 	 * e.moodify!Foo(ctor values...);
160 	 * --------------------
161 	 * e.add(new Foo(3));
162 	 * e.modify!Foo(5);
163 	 * --------------------
164 	 */
165 	@safe pure
166 	public C modify(C : IComponent)(AliasSeq!(Fields!C) args)
167 	{
168 		enum id = ComponentManager.idOf!C;
169 		IComponent* p;
170 		p = id in components;
171 
172 		if (p is null)
173 			throw new EntityComponentHandlingException(
174 					"Cannot modify component. Entity doesn't contain "
175 					~ __traits(identifier, C) ~ "."
176 			);
177 
178 		C c = cast(C) components[id];
179 		import std.conv : to;
180 
181 		static foreach (i, f; [FieldNameTuple!C])
182 		{
183 			mixin("c." ~ f ~ "=" ~ "args[" ~ i.to!string ~ "];");
184 		}
185 
186 		return c;
187 	}
188 
189 	/**
190 	 * Clear
191 	 *
192 	 * Removes every component from this entity
193 	 *
194 	 * Examples:
195 	 * --------------------
196 	 * e.clear();
197 	 * --------------------
198 	 */
199 	@safe pure
200 	public void clear()
201 	{
202 		import std.algorithm.iteration : each;
203 
204 		components.byKey.each!(_ => components.remove(_));
205 	}
206 
207 	/**
208 	 * Get a component
209 	 *
210 	 * Returns:
211 	 *     Same type if it exists
212 	 *     Null otherwise
213 	 *
214 	 * Examples:
215 	 * --------------------
216 	 * e.get!Foo
217 	 *--------------------
218 	 */
219 	@safe pure
220 	public C get(C : IComponent)()
221 	{
222 		enum id = ComponentManager.idOf!C;
223 		IComponent* p;
224 		p = id in components;
225 
226 		return p !is null ? cast(C)(*p) : null;
227 	}
228 
229 	/**
230 	 * Get components
231 	 *
232 	 * Returns:
233 	 *     array of components which are contained in an entity
234 	 *     However if you want to actualy access any of this, this method isn't
235 	 *       recomended
236 	 *     Use the template 'get' as it will return the type of that component
237 	 *     Or use 'contains', 'containsAny', 'containsAll' if you want to know
238 	 *       which components an entity is holding
239 	 */
240 	@safe pure
241 	public IComponent[] getAll()
242 	{
243 		import std.array : array;
244 
245 		return components.byValue.array;
246 	}
247 
248 	/**
249 	 * Contains a component
250 	 *
251 	 * You call this function by passing the component's id
252 	 * Every time you create a component, it'll generate an unique id
253 	 * If you don't know which id you should pass, use the other variant of this
254 	 *   function, which is the recomended one, as it will get the correct id
255 	 *   for you
256 	 *
257 	 * Returns:
258 	 *     True if the entity contains the component
259 	 *     False otherwise
260 	 *
261 	 * Examples:
262 	 * --------------------
263 	 * e.contains(world.component.idOf!Foo);
264 	 * --------------------
265 	 *
266 	 * See_Also: contains(C : IComponent)()
267 	 */
268 	@safe pure
269 	public bool contains(in string id) const
270 	{
271 		return (id in components) !is null;
272 	}
273 
274 	/**
275 	 * Contains a component
276 	 *
277 	 * It's recomended to always use this function, as it will do the 'hard
278 	 *   work' for you
279 	 *
280 	 * Returns:
281 	 *     True if the entity contains the component
282 	 *     False otherwise
283 	 *
284 	 * Examples:
285 	 * --------------------
286 	 * e.contains!Foo;
287 	 * --------------------
288 	 */
289 	public bool contains(C : IComponent)() const
290 	{
291 		return contains(ComponentManager.idOf!C);
292 	}
293 
294 	/**
295 	 * Contains components
296 	 *
297 	 * You call this function by passing an array of the component ids
298 	 * Every time you create a component, it'll generate an unique id
299 	 * If you don't know which ids you should pass, use the other variant of
300 	 *   this function, which is the recomended one, as it will get the correct
301 	 *   id for you
302 	 *
303 	 * Returns:
304 	 *     True if all the components exist
305 	 *     False otherwise
306 	 *
307 	 * Examples:
308 	 * --------------------
309 	 * e.containsAll([world.component.idOf!Foo, world.component.idOf!Bar]);
310 	 * --------------------
311 	 *
312 	 * See_Also: containsAll(C...)()
313 	 */
314 	@safe pure
315 	public bool containsAll(in string[] ids) const
316 	{
317 		foreach (id; ids)
318 			if (!contains(id))
319 				return false;
320 
321 		return true;
322 	}
323 
324 	/**
325 	 * Contains components
326 	 *
327 	 * It's recomended to always use this function, as it will do the 'hard
328 	 *   work' for you
329 	 *
330 	 * Returns:
331 	 *     True if the entity contains all of the components
332 	 *     False otherwise
333 	 *
334 	 * Examples:
335 	 * --------------------
336 	 * e.containsAll!(Foo, Goo);
337 	 * --------------------
338 	 */
339 	@safe pure
340 	public bool containsAll(C...)() const
341 	{
342 		return containsAll(ids!C);
343 	}
344 
345 	/**
346 	 * Contains any component
347 	 *
348 	 * You call this function by passing an array of the component ids
349 	 * Every time you create a component, it'll generate an unique id
350 	 * If you don't know which ids you should pass, use the other variant of
351 	 *   this function, which is the recomended one, as it will get the correct
352 	 *   id for you
353 	 *
354 	 * Returns:
355 	 *     True if the entity contains at least one of the components
356 	 *     False otherwise
357 	 *
358 	 * Examples:
359 	 * --------------------
360 	 * e.containsAny([world.component.idOf!Foo, world.component.idOf!Bar]);
361 	 * --------------------
362 	 *
363 	 * See_Also: containsAny(C...)()
364 	 */
365 	@safe pure
366 	public bool containsAny(in string[] ids) const
367 	{
368 		foreach (id; ids)
369 			if (contains(id))
370 				return true;
371 
372 		return false;
373 	}
374 
375 	/**
376 	 * Contains any component
377 	 *
378 	 * It's recomended to always use this function, as it will do the 'hard
379 	 *   work' for you
380 	 *
381 	 * Returns:
382 	 *     True if the entity contains at least one of the components
383 	 *     False otherwise
384 	 *
385 	 * Examples:
386 	 * --------------------
387 	 * e.hasAnyComponents!(Foo, Goo);
388 	 * --------------------
389 	 */
390 	@safe pure
391 	public bool containsAny(C...)() const
392 	{
393 		return containsAny(ids!C);
394 	}
395 
396 	/**
397 	 * Ids
398 	 *
399 	 * Used internaly only
400 	 * Returns: ids of every component contained by this entity
401 	 */
402 	@safe pure
403 	private auto ids(C...)() const
404 	{
405 		import std.meta : staticMap;
406 
407 		return [staticMap!(fullyQualifiedName, C)];
408 	}
409 
410 	/**
411 	 * Detach
412 	 *
413 	 * Remove an entity from the world's scope
414 	 * Use this when you no longer need the entity
415 	 */
416 	@safe pure
417 	public void detach()
418 	{
419 		manager.detach(this);
420 	}
421 
422 	public immutable size_t id;
423 	public string name;
424 	public string description;
425 	public bool enabled;
426 	private IComponent[string] components;
427 	private EntityManager manager;
428 }
429 
430 version (unittest)
431 {
432 	final class unittest_FooComponent : IComponent
433 	{
434 		int a;
435 	}
436 
437 	final class unittest_BarComponent : IComponent
438 	{
439 		int a;
440 	}
441 }
442 
443 ///
444 @safe pure
445 @("Entity: Adding and removing a component twice")
446 unittest
447 {
448 	Entity e = new Entity(null, 0);
449 
450 	unittest_FooComponent f = new unittest_FooComponent();
451 	e.add(f); // First time the component is added, no error
452 	assertThrown!EntityComponentHandlingException(e.add(f), "Second time the same type is added");
453 
454 	e.remove!unittest_FooComponent; // First time the component is removed, no error
455 	assertThrown!EntityComponentHandlingException(e.remove!unittest_FooComponent,
456 			"Second time the type is being accessed");
457 }
458 
459 ///
460 @safe pure
461 @("Entity: Getting and contained components")
462 unittest
463 {
464 	Entity e = new Entity(null, 0);
465 	unittest_FooComponent foo = new unittest_FooComponent();
466 	e.add(foo); // Entity components == [foo]
467 
468 	assertTrue(e.contains!unittest_FooComponent);
469 	assertTrue(e.containsAll!unittest_FooComponent);
470 	assertTrue(e.containsAny!(unittest_FooComponent, unittest_BarComponent), "Entity contains an unittets_FooComponent");
471 
472 	assertFalse(e.containsAll!(unittest_FooComponent, unittest_BarComponent),
473 			"Entity doesn't contain an unittest_BarComponent");
474 	assertFalse(e.containsAny!(unittest_BarComponent));
475 
476 	import std.range.primitives;
477 
478 	auto arr = e.getAll;
479 
480 	import std.traits : ReturnType;
481 
482 	assertTrue(is(ReturnType!(e.get!unittest_FooComponent) == unittest_FooComponent), "Returns the original type");
483 	assertTrue(foo is e.get!unittest_FooComponent);
484 	assertTrue(e.get!unittest_BarComponent is null, "Entity doesn't contain a type unittest_BarComponent component");
485 	assertEquals(1, arr.length, "Entity should contain only 1 component");
486 	assertTrue(is(typeof(arr.front) == IComponent));
487 }
488 
489 ///
490 @safe pure
491 @("Entity: Entity clear")
492 unittest
493 {
494 	Entity e = new Entity(null, 0);
495 
496 	e.add(new unittest_FooComponent());
497 	e.clear();
498 
499 	assertEquals(0, e.getAll().length);
500 }
501 
502 ///
503 @safe pure
504 @("Entity: Modify component contents")
505 unittest
506 {
507 	Entity e = new Entity(null, 0);
508 	e.add!unittest_FooComponent;
509 
510 	assertEquals(int.init, e.get!unittest_FooComponent.a);
511 
512 	e.modify!unittest_FooComponent(4);
513 
514 	assertEquals(4, e.get!unittest_FooComponent.a);
515 	assertThrown!EntityComponentHandlingException(e.modify!unittest_BarComponent(5));
516 }