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 +171 -21
- package/dist/node/index.js +1257 -164
- package/dist/node/networking/entity-serializer.d.ts +20 -3
- package/dist/node/networking/entity-serializer.d.ts.map +1 -1
- package/dist/node/public-api.d.ts +5 -0
- package/dist/node/public-api.d.ts.map +1 -1
- package/dist/node/rendering/soa-webgl-renderer.d.ts +88 -0
- package/dist/node/rendering/soa-webgl-renderer.d.ts.map +1 -0
- package/dist/node/simulation/entity-handle.d.ts +29 -0
- package/dist/node/simulation/entity-handle.d.ts.map +1 -0
- package/dist/node/simulation/entity-manager.d.ts +34 -1
- package/dist/node/simulation/entity-manager.d.ts.map +1 -1
- package/dist/node/simulation/entity-storage.d.ts +215 -0
- package/dist/node/simulation/entity-storage.d.ts.map +1 -0
- package/dist/node/simulation/entity-type-registry.d.ts +40 -0
- package/dist/node/simulation/entity-type-registry.d.ts.map +1 -0
- package/dist/node/simulation/entity.d.ts +37 -3
- package/dist/node/simulation/entity.d.ts.map +1 -1
- package/package.json +1 -1
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
|
-
| [
|
|
100
|
-
| [
|
|
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 {
|
|
276
|
+
import { EntityManager, type EntityState } from 'bloody-engine';
|
|
265
277
|
|
|
266
|
-
// Create entity
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
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.
|
|
281
|
-
const nearby = manager.
|
|
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
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
|
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
|