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