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 }