bloody-engine 1.0.11 → 1.1.0

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.
package/README.md CHANGED
@@ -4,9 +4,11 @@ A WebGL-based 2.5D graphics engine for isometric rendering on Node.js, written i
4
4
 
5
5
  ## Features
6
6
 
7
+ - **Structure of Arrays (SoA)** - Entity storage with typed arrays for zero-copy GPU transfers and cache-friendly access
7
8
  - **2.5D Rendering** - Optimized for isometric and dimetric projections with depth sorting
8
9
  - **Server-Side Rendering** - Headless WebGL rendering on Node.js using `gl` and `@kmamal/sdl`
9
10
  - **Batch Rendering** - Efficient sprite batching with GPU-accelerated transformations
11
+ - **Persistent Buffer Mapping** - WebGL2 zero-copy GPU transfers for maximum performance
10
12
  - **Resource Management** - Unified asset loading pipeline for textures and shaders
11
13
  - **Input System** - Command queue pattern supporting SDL and network input sources
12
14
  - **Networking** - Client-side prediction, server reconciliation, and state synchronization
@@ -15,6 +17,7 @@ A WebGL-based 2.5D graphics engine for isometric rendering on Node.js, written i
15
17
  - **TypeScript** - Fully typed for excellent developer experience
16
18
  - **Object Pooling** - Memory-efficient object reuse patterns
17
19
  - **Window Management** - SDL-based window creation for interactive applications
20
+ - **Custom Properties** - Opt-in extensible system for game-specific entity properties
18
21
 
19
22
  ## Installation
20
23
 
@@ -96,9 +99,12 @@ Bloody Engine uses different coordinate systems for different purposes. Mixing t
96
99
 
97
100
  | Class | Description |
98
101
  |-------|-------------|
99
- | [Ticker](src/core/ticker.ts) | Fixed timestep game loop |
100
- | [Entity](src/simulation/entity.ts) / [EntityManager](src/simulation/entity-manager.ts) | Entity component system |
102
+ | [EntityStorage](src/simulation/entity-storage.ts) | SoA storage with typed arrays for high-performance entity data |
103
+ | [EntityHandle](src/simulation/entity-handle.ts) | Opaque handles for safe entity references |
104
+ | [EntityTypeRegistry](src/simulation/entity-type-registry.ts) | Type string to ID mapping for storage efficiency |
105
+ | [Entity](src/simulation/entity.ts) / [EntityManager](src/simulation/entity-manager.ts) | Entity component system (now uses SoA storage internally) |
101
106
  | [SimulationLoop](src/simulation/simulation-loop.ts) | Deterministic game logic simulation |
107
+ | [SoaWebGLRenderer](src/rendering/soa-webgl-renderer.ts) | WebGL2 renderer with persistent buffer mapping |
102
108
  | [ClientPredictor](src/networking/client-predictor.ts) | Client-side prediction for lag compensation |
103
109
  | [ServerReconciler](src/networking/server-reconciler.ts) | Server-side reconciliation |
104
110
  | [StateSnapshot](src/networking/state-snapshot.ts) | World state serialization |
@@ -258,32 +264,46 @@ const metrics = ticker.getMetrics();
258
264
  console.log(`FPS: ${metrics.fps}, Delta Time: ${metrics.deltaTime}s`);
259
265
  ```
260
266
 
261
- ### Entity System
267
+ ### Entity System (SoA Architecture)
268
+
269
+ The engine now uses Structure of Arrays (SoA) for entity storage, providing:
270
+ - **Zero-copy GPU transfers** - Direct typed array uploads to WebGL buffers
271
+ - **Better cache locality** - Sequential memory access patterns
272
+ - **SIMD-ready** - Data layout enables future vectorization
273
+ - **Extensible properties** - Add custom typed arrays for game-specific data
262
274
 
263
275
  ```typescript
264
- import { Entity, EntityManager, type EntityState } from 'bloody-engine';
276
+ import { EntityManager, type EntityState } from 'bloody-engine';
265
277
 
266
- // Create entity
267
- const entity = new Entity({
268
- id: 'player-1',
269
- x: 0, y: 0, z: 0,
270
- rotation: 0,
271
- type: 'player',
272
- health: 100
278
+ // Create entity manager (uses SoA storage internally)
279
+ const manager = new EntityManager();
280
+
281
+ // Create entity with initial state
282
+ const player = manager.createEntity("player", {
283
+ gridPos: { xgrid: 10, ygrid: 20, zheight: 5 },
284
+ velocity: { x: 1, y: 0, z: 0 },
285
+ speed: 2.5
273
286
  });
274
287
 
275
- // Create entity manager
276
- const manager = new EntityManager();
277
- manager.add(entity);
288
+ // All existing methods work unchanged (full backward compatibility)
289
+ player.setGridPos(50, 60, 10);
290
+ player.move(5, 5, 0);
291
+ player.setVelocity(2, 1, 0);
278
292
 
279
293
  // Query entities
280
- const players = manager.query({ type: 'player' });
281
- const nearby = manager.queryInRegion(0, 0, 100);
294
+ const players = manager.getEntitiesByType("player");
295
+ const nearby = manager.getEntitiesInRange(50, 60, 100);
296
+
297
+ // Register custom properties (opt-in extension)
298
+ manager.registerCustomProperty("health", Float32Array);
299
+ manager.registerCustomProperty("stamina", Uint32Array);
282
300
 
283
- // Update entity
284
- entity.x += 10;
285
- entity.updatedAt = Date.now();
286
- manager.update(entity);
301
+ // Access SoA storage directly for advanced use
302
+ const storage = manager.getStorage();
303
+ const handle = (player as any).getHandle();
304
+
305
+ // Set custom property
306
+ storage.setCustomProperty(handle.index, "health", 100);
287
307
  ```
288
308
 
289
309
  ### Input System with Command Queue
@@ -436,6 +456,131 @@ batchRenderer.addQuad({
436
456
  });
437
457
  ```
438
458
 
459
+ ### SoA Deep Dive: Zero-Copy GPU Rendering
460
+
461
+ The Structure of Arrays (SoA) architecture enables direct GPU transfers without intermediate copying:
462
+
463
+ ```typescript
464
+ import { EntityStorage, SoaWebGLRenderer, Shader } from 'bloody-engine';
465
+
466
+ // Create SoA storage and populate with entities
467
+ const storage = new EntityStorage(10000);
468
+ // ... add entities ...
469
+
470
+ // Create WebGL2 renderer with persistent buffer mapping
471
+ const gl = device.getGLContext() as WebGL2RenderingContext;
472
+ const shader = device.createShader(vertexSource, fragmentSource);
473
+ const renderer = new SoaWebGLRenderer(gl, shader, 10000);
474
+
475
+ // Initialize persistent buffers (maps GPU memory to CPU arrays)
476
+ renderer.initialize(storage);
477
+
478
+ // In your render loop:
479
+ function render() {
480
+ // Zero-copy: Update GPU memory directly
481
+ renderer.render(storage);
482
+
483
+ // GPU sees changes immediately (coherent mapping)
484
+ device.present();
485
+ }
486
+ ```
487
+
488
+ **Performance Benefits:**
489
+ - **No bufferSubData overhead**: Direct CPU→GPU memory writes
490
+ - **Better cache locality**: Sequential access to entity data
491
+ - **SIMD-ready**: Data layout enables future vectorization
492
+ - **Memory efficient**: Typed arrays use 2-4x less memory than objects
493
+
494
+ ### Custom Properties Extension
495
+
496
+ Add game-specific properties without modifying core classes:
497
+
498
+ ```typescript
499
+ import { EntityManager, Float32Array, Uint32Array } from 'bloody-engine';
500
+
501
+ const manager = new EntityManager();
502
+
503
+ // Register custom properties (opt-in)
504
+ manager.registerCustomProperty('health', Float32Array); // Float values
505
+ manager.registerCustomProperty('mana', Uint32Array); // Integer values
506
+ manager.registerCustomProperty('xp', Float32Array); // Experience points
507
+
508
+ // Create entity
509
+ const player = manager.createEntity('player');
510
+
511
+ // Access storage to set custom properties
512
+ const storage = manager.getStorage();
513
+ const handle = (player as any).getHandle();
514
+
515
+ storage.setCustomProperty(handle.index, 'health', 100.0);
516
+ storage.setCustomProperty(handle.index, 'mana', 50);
517
+ storage.setCustomProperty(handle.index, 'xp', 0);
518
+
519
+ // Bulk update all entities (cache-efficient)
520
+ const allHealth = storage.getCustomPropertyArray('health');
521
+ for (let i = 0; i < storage.getCount(); i++) {
522
+ allHealth[i] += 10; // Regenerate health for all entities
523
+ }
524
+ ```
525
+
526
+ ### SoA Memory Layout
527
+
528
+ Understanding the SoA memory layout helps with performance optimization:
529
+
530
+ ```typescript
531
+ // Entity 0 data at indices 0-2
532
+ positions[0] = entity0.x
533
+ positions[1] = entity0.y
534
+ positions[2] = entity0.z
535
+
536
+ // Entity 1 data at indices 3-5
537
+ positions[3] = entity1.x
538
+ positions[4] = entity1.y
539
+ positions[5] = entity1.z
540
+
541
+ // Same pattern for all properties:
542
+ // - velocities: [vx0, vy0, vz0, vx1, vy1, vz1, ...]
543
+ // - colors: [r0, g0, b0, a0, r1, g1, b1, a1, ...]
544
+ // - rotations: [rot0, rot1, rot2, ...]
545
+ // - textureIds: [id0, id1, id2, ...]
546
+ ```
547
+
548
+ This layout enables:
549
+ - **Zero-copy views**: `positions.subarray(0, entityCount * 3)` → GPU
550
+ - **Bulk updates**: Loop through contiguous memory
551
+ - **Cache efficiency**: Predictable access patterns
552
+
553
+ ### Migration from AoS to SoA
554
+
555
+ If you have existing code using the old Array-of-Structures pattern:
556
+
557
+ **Before (AoS - deprecated):**
558
+ ```typescript
559
+ // This no longer works
560
+ const entity = new Entity("player1", "player", {
561
+ gridPos: { xgrid: 10, ygrid: 20, zheight: 0 }
562
+ });
563
+ ```
564
+
565
+ **After (SoA - current):**
566
+ ```typescript
567
+ // Use EntityManager factory
568
+ const manager = new EntityManager();
569
+ const entity = manager.createEntity("player", {
570
+ gridPos: { xgrid: 10, ygrid: 20, zheight: 0 }
571
+ });
572
+
573
+ // Everything else works the same!
574
+ entity.setGridPos(50, 60, 10);
575
+ entity.move(5, 5, 0);
576
+ entity.setVelocity(1, 0, 0);
577
+ ```
578
+
579
+ **Breaking Changes:**
580
+ - Direct `new Entity()` construction is no longer supported
581
+ - Use `EntityManager.createEntity()` for all entity creation
582
+ - Deserialization: Use `EntityManager.deserializeAll()` instead of `Entity.deserialize()`
583
+
439
584
  ## Advanced Examples
440
585
 
441
586
  ### Networked Game Architecture
@@ -515,7 +660,7 @@ console.log('Deterministic:', JSON.stringify(state1) === JSON.stringify(state2))
515
660
 
516
661
  ## Testing
517
662
 
518
- The engine includes comprehensive tests for determinism and visual regression:
663
+ The engine includes comprehensive tests for determinism, visual regression, and SoA functionality:
519
664
 
520
665
  ```bash
521
666
  # Run all tests
@@ -560,6 +705,7 @@ src/
560
705
  │ ├── batch-renderer.ts
561
706
  │ ├── camera.ts
562
707
  │ ├── projection.ts
708
+ │ ├── soa-webgl-renderer.ts # WebGL2 zero-copy renderer
563
709
  │ └── spatial-hash.ts
564
710
  ├── input/ # Input system (command queue)
565
711
  │ ├── command-queue.ts
@@ -568,6 +714,9 @@ src/
568
714
  ├── simulation/ # Game logic simulation
569
715
  │ ├── entity.ts
570
716
  │ ├── entity-manager.ts
717
+ │ ├── entity-storage.ts # SoA storage with typed arrays
718
+ │ ├── entity-handle.ts # Handle-based entity references
719
+ │ ├── entity-type-registry.ts # Type string to ID mapping
571
720
  │ └── simulation-loop.ts
572
721
  ├── networking/ # Networking for multiplayer
573
722
  │ ├── client-predictor.ts
@@ -584,6 +733,7 @@ src/
584
733
  ### Key Concepts
585
734
 
586
735
  - **Separation of Concerns**: Rendering, input, simulation, and networking are completely separate systems
736
+ - **Structure of Arrays (SoA)**: Entity storage uses typed arrays for zero-copy GPU transfers and cache efficiency
587
737
  - **Deterministic Simulation**: Game logic runs in fixed timestep for consistency across clients
588
738
  - **Command Pattern**: All input goes through a command queue for easy recording/replay
589
739
  - **Client-Side Prediction**: Reduces perceived lag in networked games