yuna-engine 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. yuna/README.md +1017 -0
  2. yuna/__init__.py +30 -0
  3. yuna/ai/__init__.py +0 -0
  4. yuna/ai/behavior_tree/__init__.py +0 -0
  5. yuna/ai/behavior_tree/component.py +57 -0
  6. yuna/ai/behavior_tree/composite.py +161 -0
  7. yuna/ai/behavior_tree/decorator.py +202 -0
  8. yuna/ai/behavior_tree/node.py +44 -0
  9. yuna/ai/behavior_tree/system.py +106 -0
  10. yuna/ai/behavior_tree/types.py +68 -0
  11. yuna/ai/blackboard.py +162 -0
  12. yuna/ai/events.py +175 -0
  13. yuna/ai/optimization.py +75 -0
  14. yuna/ai/pathfinding.py +412 -0
  15. yuna/ai/perception.py +461 -0
  16. yuna/ai/profiling.py +36 -0
  17. yuna/ai/query.py +184 -0
  18. yuna/ai/steering.py +480 -0
  19. yuna/api-docs/ERROR_HANDLING.md +429 -0
  20. yuna/commands/__init__.py +0 -0
  21. yuna/commands/applicator.py +153 -0
  22. yuna/commands/command.py +118 -0
  23. yuna/commands/effect_modifiers.py +201 -0
  24. yuna/commands/invoker.py +137 -0
  25. yuna/commands/macro.py +165 -0
  26. yuna/commands/permissions.py +159 -0
  27. yuna/commands/result.py +29 -0
  28. yuna/commands/validator.py +335 -0
  29. yuna/config/__init__.py +0 -0
  30. yuna/config/component.py +69 -0
  31. yuna/config/modifiers.py +144 -0
  32. yuna/config/patterns.py +139 -0
  33. yuna/config/schema.py +310 -0
  34. yuna/config/store.py +203 -0
  35. yuna/config/types.py +179 -0
  36. yuna/config/validators.py +33 -0
  37. yuna/deprecation.py +153 -0
  38. yuna/ecs/__init__.py +0 -0
  39. yuna/ecs/archetype.py +241 -0
  40. yuna/ecs/archetype_store.py +374 -0
  41. yuna/ecs/buffer.py +131 -0
  42. yuna/ecs/component.py +29 -0
  43. yuna/ecs/dirty.py +84 -0
  44. yuna/ecs/entity.py +152 -0
  45. yuna/ecs/hierarchy.py +208 -0
  46. yuna/ecs/lifecycle.py +232 -0
  47. yuna/ecs/markers.py +27 -0
  48. yuna/ecs/priority.py +69 -0
  49. yuna/ecs/prototype.py +179 -0
  50. yuna/ecs/query.py +265 -0
  51. yuna/ecs/relationships.py +170 -0
  52. yuna/ecs/store.py +235 -0
  53. yuna/ecs/system.py +130 -0
  54. yuna/ecs/world.py +771 -0
  55. yuna/events/__init__.py +0 -0
  56. yuna/events/bus.py +149 -0
  57. yuna/events/consumption.py +38 -0
  58. yuna/events/dispatcher.py +201 -0
  59. yuna/events/event.py +42 -0
  60. yuna/events/filtering.py +168 -0
  61. yuna/events/queue.py +123 -0
  62. yuna/exceptions.py +450 -0
  63. yuna/fsm/__init__.py +0 -0
  64. yuna/fsm/component.py +54 -0
  65. yuna/fsm/machine.py +168 -0
  66. yuna/fsm/state.py +81 -0
  67. yuna/fsm/system.py +81 -0
  68. yuna/fsm/transition.py +51 -0
  69. yuna/loop/__init__.py +0 -0
  70. yuna/loop/dependencies.py +229 -0
  71. yuna/loop/game_loop.py +247 -0
  72. yuna/loop/job.py +126 -0
  73. yuna/loop/parallel.py +144 -0
  74. yuna/loop/scheduler.py +207 -0
  75. yuna/loop/time.py +79 -0
  76. yuna/modifiers/__init__.py +0 -0
  77. yuna/modifiers/applicator.py +42 -0
  78. yuna/modifiers/batch.py +94 -0
  79. yuna/modifiers/cache.py +192 -0
  80. yuna/modifiers/conditions.py +327 -0
  81. yuna/modifiers/config.py +324 -0
  82. yuna/modifiers/context.py +41 -0
  83. yuna/modifiers/conventions.py +171 -0
  84. yuna/modifiers/conversions.py +198 -0
  85. yuna/modifiers/expiration.py +58 -0
  86. yuna/modifiers/interceptor.py +42 -0
  87. yuna/modifiers/modifier.py +125 -0
  88. yuna/modifiers/operations.py +154 -0
  89. yuna/modifiers/pipeline.py +234 -0
  90. yuna/modifiers/query.py +194 -0
  91. yuna/modifiers/reader.py +40 -0
  92. yuna/modifiers/relationships.py +402 -0
  93. yuna/modifiers/scaling.py +253 -0
  94. yuna/modifiers/stages.py +1046 -0
  95. yuna/modifiers/tracker.py +180 -0
  96. yuna/modifiers/types.py +80 -0
  97. yuna/network/__init__.py +0 -0
  98. yuna/network/authority.py +27 -0
  99. yuna/network/budget.py +180 -0
  100. yuna/network/component_tags.py +29 -0
  101. yuna/network/compression.py +153 -0
  102. yuna/network/delta.py +171 -0
  103. yuna/network/interest.py +293 -0
  104. yuna/network/priority.py +265 -0
  105. yuna/network/quantization.py +179 -0
  106. yuna/network/relevancy.py +319 -0
  107. yuna/network/replication.py +288 -0
  108. yuna/particles/__init__.py +0 -0
  109. yuna/particles/field.py +79 -0
  110. yuna/particles/simulation.py +271 -0
  111. yuna/physics/__init__.py +0 -0
  112. yuna/physics/body.py +93 -0
  113. yuna/physics/engine.py +356 -0
  114. yuna/physics/shapes.py +266 -0
  115. yuna/physics/system.py +150 -0
  116. yuna/pipeline/__init__.py +0 -0
  117. yuna/pipeline/context.py +17 -0
  118. yuna/pipeline/pipeline.py +187 -0
  119. yuna/pipeline/registry.py +95 -0
  120. yuna/pipeline/stage.py +47 -0
  121. yuna/prefabs/__init__.py +0 -0
  122. yuna/prefabs/manager.py +277 -0
  123. yuna/prefabs/prefab.py +50 -0
  124. yuna/profiling/__init__.py +0 -0
  125. yuna/profiling/decorators.py +85 -0
  126. yuna/profiling/monitor.py +370 -0
  127. yuna/profiling/stats.py +68 -0
  128. yuna/py.typed +0 -0
  129. yuna/replay/__init__.py +0 -0
  130. yuna/replay/config.py +70 -0
  131. yuna/replay/controls.py +59 -0
  132. yuna/replay/incremental.py +571 -0
  133. yuna/replay/player.py +293 -0
  134. yuna/replay/recorder.py +320 -0
  135. yuna/replay/storage.py +223 -0
  136. yuna/resources/__init__.py +0 -0
  137. yuna/resources/flyweight.py +74 -0
  138. yuna/resources/pool.py +93 -0
  139. yuna/resources/pools.py +288 -0
  140. yuna/scene/__init__.py +0 -0
  141. yuna/scene/chunk.py +221 -0
  142. yuna/scene/manager.py +156 -0
  143. yuna/scene/scene.py +91 -0
  144. yuna/scene/streaming.py +301 -0
  145. yuna/scene/transition.py +59 -0
  146. yuna/script/__init__.py +0 -0
  147. yuna/script/compiler.py +198 -0
  148. yuna/script/instructions.py +77 -0
  149. yuna/script/vm.py +232 -0
  150. yuna/services/__init__.py +0 -0
  151. yuna/services/lifetime.py +16 -0
  152. yuna/services/locator.py +117 -0
  153. yuna/services/provider.py +32 -0
  154. yuna/spatial/__init__.py +0 -0
  155. yuna/spatial/bvh.py +380 -0
  156. yuna/spatial/collision.py +15 -0
  157. yuna/spatial/factory.py +134 -0
  158. yuna/spatial/grid.py +819 -0
  159. yuna/spatial/integration.py +202 -0
  160. yuna/spatial/quadtree.py +433 -0
  161. yuna/spatial/queries.py +84 -0
  162. yuna/state/__init__.py +0 -0
  163. yuna/state/component_registry.py +218 -0
  164. yuna/state/manager.py +169 -0
  165. yuna/state/migrations.py +302 -0
  166. yuna/state/raw_snapshot.py +40 -0
  167. yuna/state/serializer.py +730 -0
  168. yuna/state/serializers.py +122 -0
  169. yuna/state/snapshot.py +33 -0
  170. yuna/state/versioning.py +121 -0
  171. yuna/types/__init__.py +0 -0
  172. yuna/types/bounds.py +99 -0
  173. yuna/types/identifiers.py +7 -0
  174. yuna/types/registry.py +233 -0
  175. yuna/types/type_object.py +63 -0
  176. yuna/types/vector.py +99 -0
  177. yuna_engine-0.1.0.dist-info/METADATA +124 -0
  178. yuna_engine-0.1.0.dist-info/RECORD +180 -0
  179. yuna_engine-0.1.0.dist-info/WHEEL +4 -0
  180. yuna_engine-0.1.0.dist-info/licenses/LICENSE +21 -0
yuna/README.md ADDED
@@ -0,0 +1,1017 @@
1
+ # Engine Core v3
2
+
3
+ A high-performance Entity Component System (ECS) game engine with advanced patterns for game development.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Quick Start](#quick-start)
8
+ - [Architecture Overview](#architecture-overview)
9
+ - [Core Patterns](#core-patterns)
10
+ - [API Reference](#api-reference)
11
+ - [Integration Examples](#integration-examples)
12
+ - [Performance Characteristics](#performance-characteristics)
13
+
14
+ ## Quick Start
15
+
16
+ ### Creating Your First Game Loop
17
+
18
+ ```python
19
+ from yuna.ecs.world import ECSWorld
20
+ from yuna.loop.game_loop import GameLoop
21
+ from yuna.loop.scheduler import SystemScheduler
22
+ from yuna.loop.time import TimeManager
23
+
24
+ time_manager = TimeManager(fixed_delta=1 / 60)
25
+ scheduler = SystemScheduler()
26
+ world = ECSWorld()
27
+
28
+ game_loop = GameLoop(
29
+ time_manager=time_manager,
30
+ scheduler=scheduler,
31
+ world=world,
32
+ )
33
+
34
+ while running:
35
+ elapsed = get_frame_time()
36
+ game_loop.update(elapsed=elapsed)
37
+ ```
38
+
39
+ ### Creating Entities and Components
40
+
41
+ ```python
42
+ from dataclasses import dataclass
43
+ from yuna.ecs.component import Component
44
+
45
+
46
+ @dataclass
47
+ class Position(Component):
48
+ x: float
49
+ y: float
50
+
51
+
52
+ @dataclass
53
+ class Velocity(Component):
54
+ dx: float
55
+ dy: float
56
+
57
+
58
+ entity = world.create_entity()
59
+ world.add_component(entity_id=entity, component=Position(x=0.0, y=0.0))
60
+ world.add_component(entity_id=entity, component=Velocity(dx=1.0, dy=0.0))
61
+ ```
62
+
63
+ ### Creating Systems
64
+
65
+ ```python
66
+ from yuna.ecs.system import System
67
+
68
+
69
+ class MovementSystem(System):
70
+ @property
71
+ def priority(self) -> int:
72
+ return 100
73
+
74
+ def update(self, world: ECSWorld, delta_time: float) -> None:
75
+ for entity_id, (pos, vel) in (
76
+ world.query().with_components(Position, Velocity).iterator()
77
+ ):
78
+ pos.x += vel.dx * delta_time
79
+ pos.y += vel.dy * delta_time
80
+
81
+
82
+ scheduler.register(system=MovementSystem())
83
+ ```
84
+
85
+ ## Architecture Overview
86
+
87
+ The engine core v3 is built on a modular architecture with 11 core patterns:
88
+
89
+ 1. **Foundation**: Core types (Vector2, EntityID) and utilities
90
+ 2. **Service Locator**: Dependency injection with singleton/transient lifetimes
91
+ 3. **Entity Component System**: Data-oriented design for game entities
92
+ 4. **Event System**: Double-buffered event queue with priority ordering
93
+ 5. **Command Pattern**: Undo/redo support with validation
94
+ 6. **Pipeline Pattern**: Composable processing stages
95
+ 7. **Modifier Pipeline**: Stat modification with stacking rules
96
+ 8. **Spatial Indexing**: O(1) spatial queries with grid-based hashing
97
+ 9. **Resource Management**: Object pooling and flyweight pattern
98
+ 10. **Game Loop**: Fixed timestep with accumulator pattern
99
+ 11. **Integration**: All patterns working together
100
+
101
+ ## Core Patterns
102
+
103
+ ### Entity Component System (ECS)
104
+
105
+ The ECS pattern separates data (Components) from logic (Systems) for better performance and maintainability.
106
+
107
+ **Key Classes:**
108
+ - `ECSWorld`: Central coordinator for entities and components
109
+ - `Component`: Base class for data containers
110
+ - `System`: Base class for game logic
111
+ - `Query`: Efficient entity filtering by component composition
112
+
113
+ **Benefits:**
114
+ - Data locality for cache-friendly iteration
115
+ - Easy to add/remove functionality
116
+ - Natural parallelization opportunities
117
+
118
+ ### Event System
119
+
120
+ Double-buffered event queue with priority-based ordering ensures deterministic event processing.
121
+
122
+ **Key Classes:**
123
+ - `Event`: Immutable event data with timestamp and tick
124
+ - `EventBus`: Unified event queue and dispatcher
125
+ - `EventQueue`: Double-buffered priority queue
126
+ - `EventDispatcher`: Type-based event routing
127
+
128
+ **Features:**
129
+ - Frame-based event delays
130
+ - Priority ordering within frames
131
+ - Type-safe event subscriptions
132
+
133
+ ### Command Pattern
134
+
135
+ Encapsulates actions with validation, execution, and optional undo support.
136
+
137
+ **Key Classes:**
138
+ - `Command`: Abstract base for all commands
139
+ - `CommandInvoker`: Execution with history tracking
140
+ - `CommandMacro`: Batch multiple commands
141
+
142
+ **Use Cases:**
143
+ - User input handling
144
+ - AI decision execution
145
+ - Network command replication
146
+
147
+ ### Modifier Pipeline
148
+
149
+ Processes stat modifications through configurable pipeline stages.
150
+
151
+ **Key Classes:**
152
+ - `ModifierPipeline`: Orchestrates all pipeline stages
153
+ - `Modifier`: Immutable modification data
154
+ - `ModifierConfig`: Stat definitions and constraints
155
+
156
+ **Pipeline Stages:**
157
+ 1. Collect: Gather all queued modifiers
158
+ 2. Filter: Remove invalid modifiers
159
+ 3. Sort: Order by priority
160
+ 4. Group: Group by target stat
161
+ 5. Stack: Apply stacking rules (add/multiply/max/min)
162
+ 6. Clamp: Enforce min/max constraints
163
+ 7. Apply: Write final values
164
+
165
+ ### Spatial Indexing
166
+
167
+ Grid-based spatial indexing provides O(1) point queries and efficient radius searches.
168
+
169
+ **Key Classes:**
170
+ - `SpatialGrid`: Cell-based entity positioning
171
+ - `SpatialQuery`: Protocol for spatial queries
172
+
173
+ **Performance:**
174
+ - O(1) point queries
175
+ - O(k) radius queries where k = entities in nearby cells
176
+ - Efficient updates when entities move
177
+
178
+ ### Resource Management
179
+
180
+ Object pooling and flyweight patterns reduce allocations.
181
+
182
+ **Key Classes:**
183
+ - `ObjectPool`: Reusable object instances
184
+ - `FlyweightFactory`: Shared instances by key
185
+
186
+ **Benefits:**
187
+ - Reduced garbage collection pressure
188
+ - Consistent allocation patterns
189
+ - Memory efficiency through sharing
190
+
191
+ ### Game Loop
192
+
193
+ Fixed timestep game loop with accumulator pattern ensures deterministic simulation.
194
+
195
+ **Key Classes:**
196
+ - `GameLoop`: Main loop orchestrator
197
+ - `TimeManager`: Fixed timestep with accumulator
198
+ - `SystemScheduler`: Priority-based system ordering
199
+
200
+ **Execution Order:**
201
+ 1. Modifier pipeline processing
202
+ 2. Event processing
203
+ 3. System updates (by priority)
204
+ 4. Event bus tick finalization
205
+
206
+ ## API Reference
207
+
208
+ ### ECS World
209
+
210
+ #### ECSWorld
211
+
212
+ Central coordinator for the Entity Component System.
213
+
214
+ ```python
215
+ world = ECSWorld()
216
+ ```
217
+
218
+ **Methods:**
219
+
220
+ ```python
221
+ def create_entity() -> EntityID:
222
+ """Create a new entity."""
223
+
224
+
225
+ def destroy_entity(entity_id: EntityID) -> None:
226
+ """Mark entity for destruction at end of update cycle."""
227
+
228
+
229
+ def add_component(entity_id: EntityID, component: Component) -> None:
230
+ """Add component to entity."""
231
+
232
+
233
+ def get_component(
234
+ entity_id: EntityID, component_type: type[Component]
235
+ ) -> Component | None:
236
+ """Get component from entity."""
237
+
238
+
239
+ def has_component(entity_id: EntityID, component_type: type[Component]) -> bool:
240
+ """Check if entity has component."""
241
+
242
+
243
+ def remove_component(entity_id: EntityID, component_type: type[Component]) -> None:
244
+ """Remove component from entity."""
245
+
246
+
247
+ def query() -> Query:
248
+ """Create a new component query."""
249
+
250
+
251
+ def register_system(system: System) -> None:
252
+ """Register system for execution."""
253
+
254
+
255
+ def update(delta_time: float) -> None:
256
+ """Update all systems and flush destroyed entities."""
257
+ ```
258
+
259
+ #### Query
260
+
261
+ Filters entities based on component composition.
262
+
263
+ ```python
264
+ query = world.query()
265
+ ```
266
+
267
+ **Methods:**
268
+
269
+ ```python
270
+ def with_components(*component_types: type[Component]) -> Query:
271
+ """Filter for entities that have all specified components."""
272
+
273
+
274
+ def without_components(*component_types: type[Component]) -> Query:
275
+ """Filter for entities that don't have any specified components."""
276
+
277
+
278
+ def iterator() -> Iterator[tuple[EntityID, tuple[Component, ...]]]:
279
+ """Iterate over entities matching the query with their components."""
280
+
281
+
282
+ def get_entities() -> set[EntityID]:
283
+ """Get all entity IDs matching the query."""
284
+
285
+
286
+ def count() -> int:
287
+ """Count number of entities matching the query."""
288
+ ```
289
+
290
+ **Examples:**
291
+
292
+ ```python
293
+ entities = world.query().with_components(Position).get_entities()
294
+
295
+ entities = (
296
+ world
297
+ .query()
298
+ .with_components(Position, Velocity)
299
+ .without_components(Dead)
300
+ .get_entities()
301
+ )
302
+
303
+ for entity_id, (pos, vel) in (
304
+ world.query().with_components(Position, Velocity).iterator()
305
+ ):
306
+ pos.x += vel.dx
307
+ pos.y += vel.dy
308
+ ```
309
+
310
+ ### Event System
311
+
312
+ #### EventBus
313
+
314
+ Unified event bus combining queue and dispatcher.
315
+
316
+ ```python
317
+ bus = EventBus()
318
+ ```
319
+
320
+ **Methods:**
321
+
322
+ ```python
323
+ def emit(event: Event, delay_frames: int = 0) -> None:
324
+ """Emit event with optional frame delay."""
325
+
326
+
327
+ def emit_priority(event: Event, priority: int, delay_frames: int = 0) -> None:
328
+ """Emit event with specific priority."""
329
+
330
+
331
+ def subscribe(event_type: str, handler: Callable[[Event], None]) -> None:
332
+ """Subscribe handler to specific event type."""
333
+
334
+
335
+ def subscribe_all(handler: Callable[[Event], None]) -> None:
336
+ """Subscribe handler to all event types."""
337
+
338
+
339
+ def unsubscribe(event_type: str, handler: Callable[[Event], None]) -> None:
340
+ """Unsubscribe handler from specific event type."""
341
+
342
+
343
+ def process_events() -> None:
344
+ """Process all events in current frame."""
345
+
346
+
347
+ def end_tick() -> None:
348
+ """End current tick and move delayed events to current frame."""
349
+ ```
350
+
351
+ **Example:**
352
+
353
+ ```python
354
+ from dataclasses import dataclass
355
+ from yuna.events.event import Event
356
+
357
+
358
+ @dataclass(frozen=True)
359
+ class PlayerDiedEvent(Event):
360
+ player_id: EntityID
361
+
362
+
363
+ def on_player_died(event: Event) -> None:
364
+ print(f"Player {event.player_id} died!")
365
+
366
+
367
+ bus.subscribe(event_type="PlayerDiedEvent", handler=on_player_died)
368
+ bus.emit(event=PlayerDiedEvent(timestamp=0.0, tick=100, player_id=player_id))
369
+ bus.process_events()
370
+ ```
371
+
372
+ ### Modifier Pipeline
373
+
374
+ #### ModifierPipeline
375
+
376
+ Pipeline for processing stat modifications.
377
+
378
+ ```python
379
+ from yuna.modifiers.config import ModifierConfig, StackingRule
380
+ from yuna.modifiers.pipeline import ModifierPipeline
381
+
382
+ config = ModifierConfig()
383
+ config.register_stat(
384
+ name="health",
385
+ min_value=0.0,
386
+ max_value=100.0,
387
+ stacking_rule=StackingRule.ADD,
388
+ )
389
+
390
+ pipeline = ModifierPipeline(config=config)
391
+ ```
392
+
393
+ **Methods:**
394
+
395
+ ```python
396
+ def queue_modifier(modifier: Modifier) -> None:
397
+ """Add modifier to processing queue."""
398
+
399
+
400
+ def process(world: Any | None = None) -> dict[tuple, float]:
401
+ """Process all queued modifiers through pipeline."""
402
+
403
+
404
+ def clear_queue() -> None:
405
+ """Clear all queued modifiers without processing."""
406
+
407
+
408
+ def get_queue_size() -> int:
409
+ """Get number of queued modifiers."""
410
+ ```
411
+
412
+ **Example:**
413
+
414
+ ```python
415
+ from yuna.modifiers.modifier import Modifier
416
+ from yuna.modifiers.types import ModificationType, ModifierPriority
417
+
418
+ modifier = Modifier(
419
+ entity_id=player_id,
420
+ stat="health",
421
+ modification_type=ModificationType.FLAT,
422
+ value=10.0,
423
+ priority=ModifierPriority.NORMAL,
424
+ source="healing_potion",
425
+ )
426
+
427
+ pipeline.queue_modifier(modifier=modifier)
428
+ result = pipeline.process()
429
+ final_health = result[(player_id, "health")]
430
+ ```
431
+
432
+ ### Spatial Indexing
433
+
434
+ #### SpatialGrid
435
+
436
+ Grid-based spatial index for fast entity position queries.
437
+
438
+ ```python
439
+ from yuna.spatial.grid import SpatialGrid
440
+
441
+ grid = SpatialGrid(cell_size=10)
442
+ ```
443
+
444
+ **Methods:**
445
+
446
+ ```python
447
+ def add(entity_id: EntityID, position: Vector2) -> None:
448
+ """Add entity to spatial grid at position."""
449
+
450
+
451
+ def remove(entity_id: EntityID) -> None:
452
+ """Remove entity from spatial grid."""
453
+
454
+
455
+ def move(entity_id: EntityID, new_position: Vector2) -> None:
456
+ """Update entity position in spatial grid."""
457
+
458
+
459
+ def get_at(position: Vector2) -> set[EntityID]:
460
+ """Get all entities at a specific position (O(1) lookup)."""
461
+
462
+
463
+ def get_in_radius(position: Vector2, radius: float) -> set[EntityID]:
464
+ """Get all entities within radius of position."""
465
+ ```
466
+
467
+ **Example:**
468
+
469
+ ```python
470
+ grid.add(entity_id=enemy_id, position=Vector2(x=50.0, y=50.0))
471
+
472
+ nearby = grid.get_in_radius(position=player_pos, radius=10.0)
473
+ for enemy_id in nearby:
474
+ attack(enemy_id)
475
+
476
+ grid.move(entity_id=enemy_id, new_position=Vector2(x=55.0, y=55.0))
477
+ ```
478
+
479
+ ### Resource Management
480
+
481
+ #### ObjectPool
482
+
483
+ Reusable object pool to reduce allocations.
484
+
485
+ ```python
486
+ from yuna.resources.pool import ObjectPool
487
+
488
+
489
+ def factory() -> dict:
490
+ return {"x": 0.0, "y": 0.0}
491
+
492
+
493
+ def reset(obj: dict) -> None:
494
+ obj["x"] = 0.0
495
+ obj["y"] = 0.0
496
+
497
+
498
+ pool = ObjectPool[dict](factory=factory, reset=reset, max_size=100)
499
+ ```
500
+
501
+ **Methods:**
502
+
503
+ ```python
504
+ def acquire() -> T:
505
+ """Get object from pool or create new one."""
506
+
507
+
508
+ def release(obj: T) -> None:
509
+ """Return object to pool after reset."""
510
+
511
+
512
+ def clear() -> None:
513
+ """Remove all objects from pool."""
514
+
515
+
516
+ def count() -> int:
517
+ """Get number of objects in pool."""
518
+ ```
519
+
520
+ **Example:**
521
+
522
+ ```python
523
+ obj = pool.acquire()
524
+ obj["x"] = 10.0
525
+ obj["y"] = 20.0
526
+ pool.release(obj=obj)
527
+ ```
528
+
529
+ #### FlyweightFactory
530
+
531
+ Shared instance caching by key.
532
+
533
+ ```python
534
+ from yuna.resources.flyweight import FlyweightFactory
535
+
536
+ factory = FlyweightFactory[str, dict]()
537
+ ```
538
+
539
+ **Methods:**
540
+
541
+ ```python
542
+ def get(key: K, factory: Callable[[], T]) -> T:
543
+ """Get shared instance for key, creating if needed."""
544
+
545
+
546
+ def clear() -> None:
547
+ """Remove all cached instances."""
548
+
549
+
550
+ def count() -> int:
551
+ """Get number of cached instances."""
552
+ ```
553
+
554
+ **Example:**
555
+
556
+ ```python
557
+ config_a = factory.get(key="level_1", factory=lambda: load_level_config("level_1"))
558
+ config_b = factory.get(key="level_1", factory=lambda: load_level_config("level_1"))
559
+ assert config_a is config_b
560
+ ```
561
+
562
+ ### Game Loop
563
+
564
+ #### GameLoop
565
+
566
+ Orchestrates game loop with fixed timestep execution.
567
+
568
+ ```python
569
+ from yuna.loop.game_loop import GameLoop
570
+
571
+ game_loop = GameLoop(
572
+ time_manager=time_manager,
573
+ scheduler=scheduler,
574
+ world=world,
575
+ event_bus=event_bus,
576
+ modifier_pipeline=modifier_pipeline,
577
+ )
578
+ ```
579
+
580
+ **Methods:**
581
+
582
+ ```python
583
+ def tick() -> None:
584
+ """Execute one game tick."""
585
+
586
+
587
+ def update(elapsed: float) -> int:
588
+ """Update game loop with elapsed time, returns number of ticks executed."""
589
+
590
+
591
+ def reset_time() -> None:
592
+ """Reset time manager accumulator to zero."""
593
+ ```
594
+
595
+ **Properties:**
596
+
597
+ ```python
598
+ @property
599
+ def fixed_delta() -> float:
600
+ """Get fixed timestep value."""
601
+
602
+
603
+ @property
604
+ def accumulator() -> float:
605
+ """Get current time accumulator."""
606
+ ```
607
+
608
+ #### TimeManager
609
+
610
+ Manages fixed timestep game loop timing.
611
+
612
+ ```python
613
+ from yuna.loop.time import TimeManager
614
+
615
+ time_manager = TimeManager(fixed_delta=1 / 60)
616
+ ```
617
+
618
+ **Methods:**
619
+
620
+ ```python
621
+ def update(elapsed: float) -> int:
622
+ """Update accumulator and calculate ticks to execute."""
623
+
624
+
625
+ def reset() -> None:
626
+ """Reset accumulator to zero."""
627
+ ```
628
+
629
+ **Properties:**
630
+
631
+ ```python
632
+ @property
633
+ def fixed_delta() -> float:
634
+ """Get fixed timestep value."""
635
+
636
+
637
+ @property
638
+ def accumulator() -> float:
639
+ """Get current accumulator value."""
640
+ ```
641
+
642
+ #### SystemScheduler
643
+
644
+ Manages system registration and execution ordering.
645
+
646
+ ```python
647
+ from yuna.loop.scheduler import SystemScheduler
648
+
649
+ scheduler = SystemScheduler()
650
+ ```
651
+
652
+ **Methods:**
653
+
654
+ ```python
655
+ def register(system: System) -> None:
656
+ """Register system for execution."""
657
+
658
+
659
+ def get_ordered_systems() -> list[System]:
660
+ """Get systems in priority order."""
661
+
662
+
663
+ def clear() -> None:
664
+ """Remove all systems from scheduler."""
665
+ ```
666
+
667
+ **Properties:**
668
+
669
+ ```python
670
+ @property
671
+ def count() -> int:
672
+ """Get number of registered systems."""
673
+ ```
674
+
675
+ ### Service Locator
676
+
677
+ #### ServiceLocator
678
+
679
+ Central registry for dependency injection.
680
+
681
+ ```python
682
+ from yuna.services.locator import ServiceLocator
683
+
684
+ locator = ServiceLocator()
685
+ ```
686
+
687
+ **Methods:**
688
+
689
+ ```python
690
+ def register(
691
+ interface: type[T],
692
+ factory: Callable[[], T],
693
+ lifetime: ServiceLifetime = ServiceLifetime.TRANSIENT,
694
+ ) -> None:
695
+ """Register a service with its factory and lifetime."""
696
+
697
+
698
+ def resolve(interface: type[T]) -> T:
699
+ """Resolve service instance by interface type."""
700
+
701
+
702
+ def clear() -> None:
703
+ """Clear all registrations and singletons."""
704
+ ```
705
+
706
+ **Example:**
707
+
708
+ ```python
709
+ from yuna.services.lifetime import ServiceLifetime
710
+
711
+
712
+ class Logger:
713
+ def log(self, message: str) -> None:
714
+ print(message)
715
+
716
+
717
+ locator.register(
718
+ interface=Logger,
719
+ factory=lambda: Logger(),
720
+ lifetime=ServiceLifetime.SINGLETON,
721
+ )
722
+
723
+ logger = locator.resolve(interface=Logger)
724
+ logger.log("Hello, world!")
725
+ ```
726
+
727
+ ## Integration Examples
728
+
729
+ ### Complete Game Loop with All Systems
730
+
731
+ ```python
732
+ from yuna.ecs.world import ECSWorld
733
+ from yuna.events.bus import EventBus
734
+ from yuna.loop.game_loop import GameLoop
735
+ from yuna.loop.scheduler import SystemScheduler
736
+ from yuna.loop.time import TimeManager
737
+ from yuna.modifiers.config import ModifierConfig, StackingRule
738
+ from yuna.modifiers.pipeline import ModifierPipeline
739
+ from yuna.spatial.grid import SpatialGrid
740
+ from yuna.services.locator import ServiceLocator
741
+
742
+ world = ECSWorld()
743
+ event_bus = EventBus()
744
+ time_manager = TimeManager(fixed_delta=1 / 60)
745
+ scheduler = SystemScheduler()
746
+
747
+ config = ModifierConfig()
748
+ config.register_stat(
749
+ name="health",
750
+ min_value=0.0,
751
+ max_value=100.0,
752
+ stacking_rule=StackingRule.ADD,
753
+ )
754
+ modifier_pipeline = ModifierPipeline(config=config)
755
+
756
+ grid = SpatialGrid(cell_size=10)
757
+
758
+ locator = ServiceLocator()
759
+ locator.register(interface=SpatialGrid, factory=lambda: grid)
760
+
761
+ game_loop = GameLoop(
762
+ time_manager=time_manager,
763
+ scheduler=scheduler,
764
+ world=world,
765
+ event_bus=event_bus,
766
+ modifier_pipeline=modifier_pipeline,
767
+ )
768
+
769
+ scheduler.register(system=MovementSystem())
770
+ scheduler.register(system=CombatSystem())
771
+ scheduler.register(system=RenderSystem())
772
+
773
+ while running:
774
+ elapsed = get_frame_time()
775
+ game_loop.update(elapsed=elapsed)
776
+ ```
777
+
778
+ ### Movement System with Spatial Grid
779
+
780
+ ```python
781
+ class MovementSystem(System):
782
+ @property
783
+ def priority(self) -> int:
784
+ return 100
785
+
786
+ def update(self, world: ECSWorld, delta_time: float) -> None:
787
+ spatial_grid = locator.resolve(interface=SpatialGrid)
788
+
789
+ for entity_id, (pos, vel) in (
790
+ world.query().with_components(Position, Velocity).iterator()
791
+ ):
792
+ old_pos = Vector2(x=pos.x, y=pos.y)
793
+ pos.x += vel.dx * delta_time
794
+ pos.y += vel.dy * delta_time
795
+
796
+ spatial_grid.move(
797
+ entity_id=entity_id,
798
+ new_position=Vector2(x=pos.x, y=pos.y),
799
+ )
800
+
801
+ event_bus.emit(
802
+ event=EntityMovedEvent(
803
+ timestamp=time.time(),
804
+ tick=current_tick,
805
+ entity_id=entity_id,
806
+ old_position=old_pos,
807
+ new_position=Vector2(x=pos.x, y=pos.y),
808
+ )
809
+ )
810
+ ```
811
+
812
+ ### Combat System with Modifiers
813
+
814
+ ```python
815
+ class CombatSystem(System):
816
+ @property
817
+ def priority(self) -> int:
818
+ return 200
819
+
820
+ def update(self, world: ECSWorld, delta_time: float) -> None:
821
+ spatial_grid = locator.resolve(interface=SpatialGrid)
822
+
823
+ for entity_id, (pos, attack) in (
824
+ world.query().with_components(Position, Attack).iterator()
825
+ ):
826
+ nearby = spatial_grid.get_in_radius(
827
+ position=Vector2(x=pos.x, y=pos.y),
828
+ radius=attack.range,
829
+ )
830
+
831
+ for target_id in nearby:
832
+ if target_id == entity_id:
833
+ continue
834
+
835
+ if world.has_component(entity_id=target_id, component_type=Health):
836
+ modifier = Modifier(
837
+ entity_id=target_id,
838
+ stat="health",
839
+ modification_type=ModificationType.FLAT,
840
+ value=-attack.damage,
841
+ priority=ModifierPriority.NORMAL,
842
+ source=f"attack_{entity_id}",
843
+ )
844
+ modifier_pipeline.queue_modifier(modifier=modifier)
845
+ ```
846
+
847
+ ### Event-Driven System
848
+
849
+ ```python
850
+ class DeathSystem(System):
851
+ @property
852
+ def priority(self) -> int:
853
+ return 300
854
+
855
+ def update(self, world: ECSWorld, delta_time: float) -> None:
856
+ for entity_id, health in world.query().with_components(Health).iterator():
857
+ if health.value <= 0:
858
+ event_bus.emit(
859
+ event=EntityDiedEvent(
860
+ timestamp=time.time(),
861
+ tick=current_tick,
862
+ entity_id=entity_id,
863
+ )
864
+ )
865
+ world.destroy_entity(entity_id=entity_id)
866
+
867
+
868
+ def on_entity_died(event: Event) -> None:
869
+ print(f"Entity {event.entity_id} died!")
870
+ spawn_death_particles(event.entity_id)
871
+ play_death_sound()
872
+
873
+
874
+ event_bus.subscribe(event_type="EntityDiedEvent", handler=on_entity_died)
875
+ ```
876
+
877
+ ## Performance Characteristics
878
+
879
+ ### ECS World
880
+
881
+ - **Entity Creation**: O(1)
882
+ - **Component Add/Remove**: O(1)
883
+ - **Component Get**: O(1)
884
+ - **Query (single component)**: O(n) where n = entities with that component
885
+ - **Query (multiple components)**: O(k) where k = entities with least common component
886
+
887
+ ### Spatial Grid
888
+
889
+ - **Point Query**: O(1) - constant time cell lookup
890
+ - **Radius Query**: O(k) where k = entities in nearby cells
891
+ - **Insert**: O(1)
892
+ - **Move**: O(1) if same cell, O(2) if different cell
893
+ - **Remove**: O(1)
894
+
895
+ ### Event System
896
+
897
+ - **Emit**: O(log n) for priority queue insertion
898
+ - **Process**: O(n) where n = events in current frame
899
+ - **Subscribe**: O(1)
900
+
901
+ ### Modifier Pipeline
902
+
903
+ - **Queue**: O(1)
904
+ - **Process**: O(n log n) where n = queued modifiers (dominated by sorting)
905
+
906
+ ### Resource Management
907
+
908
+ - **ObjectPool Acquire**: O(1) from pool, O(k) if creating new (k = factory time)
909
+ - **ObjectPool Release**: O(1)
910
+ - **Flyweight Get**: O(1) hash lookup
911
+
912
+ ### Benchmark Results
913
+
914
+ Tested on 1000 entities with multiple components:
915
+
916
+ - Entity creation with 3 components: < 1.0s
917
+ - Query iteration: < 0.1s
918
+ - Spatial grid point queries (10,000 iterations): < 1.0s
919
+ - Spatial grid radius queries vs linear search: 2-5x faster
920
+ - Component add/remove (10,000 iterations): < 1.0s
921
+ - Entity destruction (1000 entities): < 1.0s
922
+
923
+ ## Best Practices
924
+
925
+ ### Component Design
926
+
927
+ - Keep components as pure data containers
928
+ - Use frozen dataclasses for immutability where possible
929
+ - Avoid circular references between components
930
+
931
+ ```python
932
+ @dataclass
933
+ class Position(Component):
934
+ x: float
935
+ y: float
936
+ ```
937
+
938
+ ### System Design
939
+
940
+ - Systems should be stateless where possible
941
+ - Use service locator for shared resources
942
+ - Keep systems focused on single responsibility
943
+ - Use priority to control execution order
944
+
945
+ ```python
946
+ class MovementSystem(System):
947
+ @property
948
+ def priority(self) -> int:
949
+ return 100
950
+
951
+ def update(self, world: ECSWorld, delta_time: float) -> None:
952
+ pass
953
+ ```
954
+
955
+ ### Event Handling
956
+
957
+ - Events should be immutable (frozen dataclasses)
958
+ - Include timestamp and tick for debugging
959
+ - Use specific event types rather than generic events
960
+
961
+ ```python
962
+ @dataclass(frozen=True)
963
+ class CollisionEvent(Event):
964
+ entity_a: EntityID
965
+ entity_b: EntityID
966
+ position: Vector2
967
+ ```
968
+
969
+ ### Modifier Usage
970
+
971
+ - Register all stats in config before using pipeline
972
+ - Use appropriate stacking rules for each stat
973
+ - Set min/max constraints to prevent invalid values
974
+
975
+ ```python
976
+ config.register_stat(
977
+ name="speed",
978
+ min_value=0.0,
979
+ max_value=100.0,
980
+ stacking_rule=StackingRule.MULTIPLY,
981
+ )
982
+ ```
983
+
984
+ ### Spatial Grid Configuration
985
+
986
+ - Choose cell size based on average query radius
987
+ - Generally: cell_size = 2 * average_query_radius
988
+ - Larger cells reduce overhead but less precision
989
+ - Smaller cells improve precision but more overhead
990
+
991
+ ```python
992
+ grid = SpatialGrid(cell_size=10)
993
+ ```
994
+
995
+ ## Testing
996
+
997
+ The engine core v3 includes comprehensive test coverage:
998
+
999
+ - **Unit Tests**: Individual module functionality
1000
+ - **Integration Tests**: All patterns working together
1001
+ - **Performance Tests**: Benchmark critical operations
1002
+
1003
+ Run tests:
1004
+
1005
+ ```bash
1006
+ pytest tests/services/engine/core/
1007
+ ```
1008
+
1009
+ Run with coverage:
1010
+
1011
+ ```bash
1012
+ pytest tests/services/engine/core/ --cov=yuna --cov-report=term-missing
1013
+ ```
1014
+
1015
+ ## License
1016
+
1017
+ Hello Yuna LLC