1 /++ 2 The module describing the unit of the object - Instance. 3 4 An instance is an object in a scene with behavior only for itself with the 5 interaction of other instances, through collisions, any internal events. 6 Each instance has properties of execution conditions, rendering conditions, 7 rendering properties, conditions for the execution of some events. All of these 8 properties describe an instance, however, more behavior can be achieved using 9 inheritance (see `tida.localevent`). Instance functions are not inherited. but 10 they are directly written and marked with attributes that give execution 11 conditions (under what conditions it is necessary to execute, as if it is a 12 transfer of control between scenes, rendering of a frame, processing user input). 13 14 Also, an instance has a hard mask, where, when it touches another mask, 15 a collision event can be generated and such instances can handle this event, 16 if, of course, the corresponding functions have been marked with attributes. 17 18 --- 19 class MyObject : Instance 20 { 21 this() { ... } 22 23 @Init 24 void onInitFunction() 25 { 26 firstProperty = 0.0f; 27 position = vecf(32f, 11.5f); 28 ... 29 } 30 31 @Collision("OtherInstanceName") 32 void onCollsion(Instance other) 33 { 34 other.posiiton -= vecf(1, 0); 35 } 36 } 37 --- 38 39 Macros: 40 LREF = <a href="#$1">$1</a> 41 HREF = <a href="$1">$2</a> 42 43 Authors: $(HREF https://github.com/TodNaz,TodNaz) 44 Copyright: Copyright (c) 2020 - 2021, TodNaz. 45 License: $(HREF https://github.com/TodNaz/Tida/blob/master/LICENSE,MIT) 46 +/ 47 module tida.instance; 48 49 /++ 50 Checks if an object is an instance. 51 +/ 52 template isInstance(T) 53 { 54 enum isInstance = is(T : Instance); 55 } 56 57 struct InstanceEvents 58 { 59 import tida.event; 60 import tida.render; 61 import tida.localevent; 62 63 struct FEEntry 64 { 65 void delegate() @safe func; 66 size_t argsLength; 67 68 static FEEntry create(T...)(void delegate() @safe func) @trusted 69 { 70 FEEntry entry; 71 entry.func = func; 72 entry.appendArguments!T(); 73 74 return entry; 75 } 76 77 void appendArguments(T...)() @trusted 78 { 79 static foreach (Arg; T) 80 { 81 argsLength += Arg.sizeof; 82 } 83 } 84 85 bool validArgs(T...)(T args) @trusted 86 { 87 size_t length; 88 foreach (arg; args) 89 { 90 length += arg.sizeof; 91 } 92 93 return length == argsLength; 94 } 95 96 void opCall(T...)(T args) @trusted 97 in (validArgs(args)) 98 { 99 void delegate(T) @safe callFunction = cast(void delegate(T) @safe) func; 100 callFunction(args); 101 } 102 } 103 104 alias FEInit = FEEntry; 105 alias FERestart = FEEntry; 106 107 alias FELeave = void delegate() @safe; 108 alias FEStep = void delegate() @safe; 109 alias FEGameStart = void delegate() @safe; 110 alias FEGameExit = void delegate() @safe; 111 alias FEGameRestart = void delegate() @safe; 112 alias FEEventHandle = void delegate(EventHandler) @safe; 113 alias FEDraw = void delegate(IRenderer) @safe; 114 alias FEOnError = void delegate() @safe; 115 alias FECollision = void delegate(Instance) @safe; 116 alias FETrigger = void delegate() @safe; 117 alias FEDestroy = void delegate(Instance) @safe; 118 alias FEATrigger = void delegate(string) @safe; 119 120 struct SRCollider 121 { 122 Collision ev; 123 FECollision fun; 124 } 125 126 struct SRTrigger 127 { 128 Trigger ev; 129 FETrigger fun; 130 } 131 132 FEInit[] IInitFunctions; 133 134 FEStep[] IStepFunctions; 135 FEStep[][size_t] IStepThreadFunctions; 136 FERestart[] IRestartFunctions; 137 FEEntry[] IEntryFunctions; 138 FELeave[] ILeaveFunctions; 139 FEGameStart[] IGameStartFunctions; 140 FEGameExit[] IGameExitFunctions; 141 FEGameRestart[] IGameRestartFunctions; 142 FEEventHandle[] IEventHandleFunctions; 143 FEDraw[] IDrawFunctions; 144 FEOnError[] IOnErrorFunctions; 145 SRCollider[] IColliderStructs; 146 FECollision[] ICollisionFunctions; 147 SRTrigger[] IOnTriggerFunctions; 148 FEDestroy[] IOnDestroyFunctions; 149 FEATrigger[] IOnAnyTriggerFunctions; 150 } 151 152 /++ 153 Instance object. Can be created for a render unit as well as for legacy 154 with a programmable model. 155 +/ 156 class Instance 157 { 158 import tida.vector; 159 import tida.sprite; 160 import tida.shape; 161 import tida.component; 162 163 protected: 164 /++ 165 Instance sprite. Will be output at the position of the instance. 166 +/ 167 Sprite sprite; 168 169 /++ 170 Components of an instance, complementing its functionality. 171 +/ 172 Component[] components; 173 174 /// only for call. 175 bool _destroy = false; 176 177 public: 178 InstanceEvents events; 179 180 @property auto colliders() @safe 181 { 182 return events.IColliderStructs; 183 } 184 185 @property auto collisionFunctions() @safe 186 { 187 return events.ICollisionFunctions; 188 } 189 190 /++ 191 The name of the instance, by which you can later identify 192 the collided or other events. 193 +/ 194 string name; 195 196 /++ 197 Instance tags. By this parameter, it is possible to distribute an instance 198 about groups, for example, instances that should not let the player in upon 199 collision will be marked with the "solid" tag, but not necessarily only 200 non-living creatures should be used, and others who should not squeeze 201 through are marked with such a tag. 202 (This is an example, there is no such implementation in the framework). 203 +/ 204 string[] tags; 205 206 /++ 207 The position of the instance. Serves for collision, rendering, and 208 other instance services. 209 +/ 210 Vector!float position = vec!float(0.0f, 0.0f); 211 212 /++ 213 An auxiliary variable that remembers the position in the previous 214 pass of the game loop. 215 +/ 216 Vector!float previous = vec!float(0.0f, 0.0f); 217 218 /++ 219 Collision mask. A mask is a kind of geometric shape (or several shapes) 220 that sets the collision boundary between other instances. 221 +/ 222 Shape!float mask; 223 224 /++ 225 A property that determines whether an instance can collide with 226 other instances. 227 +/ 228 bool solid = false; 229 230 /++ 231 Instance identifier (often means storage location in an instances array). 232 +/ 233 size_t id; 234 235 /++ 236 The identifier for the instance in the stream. Shows which thread the 237 instance is running on (changing the property does not affect thread selection). 238 +/ 239 size_t threadid; 240 241 /++ 242 A property indicating whether to handle all events for such an instance. 243 If false, no event will be processed for this instance, however, 244 it will exist. It is necessary if you do not need to delete the instance, 245 but you also do not need to process its events. 246 +/ 247 bool active = true; 248 249 /++ 250 A property that indicates whether to render the instance and perform 251 rendering functions. 252 +/ 253 bool visible = true; 254 255 /++ 256 A property that indicates whether it is only necessary to draw the object 257 without handling other events in it. 258 +/ 259 bool onlyDraw = false; 260 261 /++ 262 A property that indicates whether to transition to a new scene when 263 transferring control to another scene. 264 +/ 265 bool persistent = false; 266 267 /++ 268 The identifier of the layer in which the instance is placed. 269 The render queue is affected, the larger the number, the later the 270 instance will be rendered. 271 +/ 272 int depth = 0; 273 274 @safe: 275 this() 276 { 277 sprite = new Sprite(); 278 } 279 280 /++ 281 Removes an instance. However, not immediately, this will only happen on 282 the next iteration of the game loop for thread safety. 283 +/ 284 final void destroy() 285 { 286 _destroy = true; 287 } 288 289 /++ 290 A method for adding an instance to an instance to expand functionality. 291 292 Params: 293 component = Component object. 294 +/ 295 final void add(T)(T component) 296 { 297 import tida.scenemanager; 298 299 static if (is(T : Instance)) 300 static assert(null, T.stringof ~ " is not a component! Maybe you meant `sceneManager.context.add`?"); 301 else 302 static assert(isComponent!T, T.stringof ~ " is not a component!"); 303 304 components ~= component; 305 if (component.name == "") 306 component.name = T.stringof; 307 308 sceneManager.componentExplore!T(this, component); 309 } 310 311 /++ 312 A method for adding an instance to an instance to expand functionality. 313 314 Params: 315 T = Component type. 316 +/ 317 final void add(T)() 318 { 319 add(new T()); 320 } 321 322 /++ 323 A function that returns a component based on its class. 324 325 Params: 326 T = Component type. 327 +/ 328 final T cmp(T)() 329 { 330 static assert(isComponent!T, T.stringof ~ " is not a component!"); 331 332 foreach (e; components) 333 { 334 if ((cast(T) e) !is null) 335 { 336 return cast(T) e; 337 } 338 } 339 340 return null; 341 } 342 343 /++ 344 Finds a component by its name. 345 346 Params: 347 name = Component name. 348 +/ 349 final Component cmp(string name) 350 { 351 foreach (e; components) 352 { 353 if (e.name == name) 354 return e; 355 } 356 357 return null; 358 } 359 360 /++ 361 Detaches a component from an instance by finding it by class. 362 363 Params: 364 T = Component type. 365 +/ 366 final void dissconnect(T)() 367 { 368 import std.algorithm : remove; 369 import tida.scenemanager; 370 static assert(isComponent!T, "`" ~ T.stringof ~ "` is not a component!"); 371 372 Component cmp; 373 374 foreach (i; 0 .. components.length) 375 { 376 if ((cast(T) components[i]) !is null) 377 { 378 cmp = components[i]; 379 380 foreach(fun; cmp.events.CLeaveFunctions) fun(); 381 382 components = components.remove(i); 383 break; 384 } 385 } 386 } 387 388 /++ 389 Detaches a component from an instance by finding it by name. 390 391 Params: 392 name = Instance name. 393 +/ 394 final void dissconnect(string name) 395 { 396 import std.algorithm : remove; 397 import tida.scenemanager; 398 399 foreach (i; 0 .. components.length) 400 { 401 if (components[i].name == name) 402 { 403 foreach(fun; components[i].events.CLeaveFunctions) fun(); 404 405 components = components.remove(i); 406 break; 407 } 408 } 409 } 410 411 /++ 412 Detaches absolutely all components in this instance. 413 +/ 414 final void dissconnectAll() @trusted 415 { 416 import tida.scenemanager; 417 import std.algorithm : remove; 418 419 foreach (i; 0 .. components.length) 420 { 421 if (sceneManager !is null) 422 { 423 foreach(fun; components[i].events.CLeaveFunctions) 424 fun(); 425 } 426 427 components = components.remove(i); 428 } 429 } 430 431 package(tida): 432 Sprite spriteDraw() 433 { 434 return sprite; 435 } 436 437 bool isDestroy() 438 { 439 return _destroy; 440 } 441 442 Component[] getComponents() 443 { 444 return components; 445 } 446 } 447 448 unittest 449 { 450 import tida.scenemanager; 451 import tida.component; 452 453 initSceneManager(); 454 455 class CComponent : Component { } 456 457 CComponent cmp = new CComponent(); 458 cmp.name = "Cmp"; 459 460 Instance instance = new Instance(); 461 instance.add(cmp); 462 463 assert(instance.cmp("Cmp") is (cmp)); 464 assert(instance.cmp!(CComponent) is (cmp)); 465 } 466 467 debug import tida.color; 468 469 debug template debugCollisionMask(Color!ubyte color = Color!ubyte(255, 0, 0)) 470 { 471 import tida.render; 472 import std.conv : to; 473 474 void __drawShapeConture(Vecf releative, Shapef shape, IRenderer render) @safe 475 { 476 switch (shape.type) 477 { 478 case ShapeType.point: 479 render.point(shape.begin + releative, color); 480 break; 481 482 case ShapeType.line: 483 render.line([ shape.begin + releative, 484 shape.end + releative], color); 485 break; 486 487 case ShapeType.rectangle: 488 render.rectangle( shape.begin + releative, 489 shape.width.to!uint, 490 shape.height.to!uint, 491 color, 492 false); 493 break; 494 495 case ShapeType.circle: 496 render.circle(shape.begin + releative, shape.radius, color, false); 497 break; 498 499 case ShapeType.triangle: 500 render.triangle([ shape.vertex!0 + releative, 501 shape.vertex!1 + releative, 502 shape.vertex!2 + releative], color, false); 503 break; 504 505 case ShapeType.polygon: 506 render.polygon(shape.begin + releative, shape.data, color, false); 507 break; 508 509 case ShapeType.multi: 510 foreach (sh; shape.shapes) 511 { 512 __drawShapeConture(releative + shape.begin, sh, render); 513 } 514 break; 515 516 default: 517 return; 518 } 519 } 520 521 @event(Draw) 522 void __debug_drawMask(IRenderer render) @safe 523 { 524 __drawShapeConture(position, mask, render); 525 } 526 } 527 528 unittest 529 { 530 import tida.component; 531 import tida.scenemanager; 532 import tida.localevent; 533 534 initSceneManager(); 535 536 static class A : Component 537 { 538 int trace = 0; 539 540 @event(Init) 541 void onInit(Instance instance) @safe 542 { 543 trace++; 544 } 545 } 546 547 Instance instance = new Instance(); 548 A a; 549 instance.add(a = new A()); 550 551 assert(a.trace == 1); 552 }