bloody-engine 1.0.10 → 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 +203 -21
- package/dist/node/index.js +1300 -156
- 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 +86 -5
- package/dist/node/simulation/entity.d.ts.map +1 -1
- package/package.json +2 -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
|
|
|
@@ -22,6 +25,38 @@ A WebGL-based 2.5D graphics engine for isometric rendering on Node.js, written i
|
|
|
22
25
|
npm install bloody-engine
|
|
23
26
|
```
|
|
24
27
|
|
|
28
|
+
## Understanding Coordinate Systems
|
|
29
|
+
|
|
30
|
+
**⚠️ IMPORTANT**: Before building your game, understand the coordinate systems to avoid inverted controls!
|
|
31
|
+
|
|
32
|
+
Bloody Engine uses different coordinate systems for different purposes. Mixing these up is the #1 cause of inverted controls.
|
|
33
|
+
|
|
34
|
+
### Quick Summary
|
|
35
|
+
|
|
36
|
+
| System | Used For | Y-Axis | Example |
|
|
37
|
+
|--------|----------|--------|---------|
|
|
38
|
+
| **Grid Space** | Game logic, entity positions | Y-UP (↓ Y = North/Up) | `entity.move(0, -1, 0)` moves up on screen |
|
|
39
|
+
| **Screen Space** | Rendering, camera, mouse | Y-DOWN (↓ Y = Down) | `camera.y += 10` moves camera down |
|
|
40
|
+
|
|
41
|
+
**Golden Rule**: Use grid space for game logic, transform to screen space only for rendering.
|
|
42
|
+
|
|
43
|
+
### Common Mistake
|
|
44
|
+
|
|
45
|
+
❌ **Wrong**: `camera.y += 1` for "up" movement (moves down on screen!)
|
|
46
|
+
✅ **Right**: Use direction deltas: `entity.move(0, -1, 0)` for North
|
|
47
|
+
|
|
48
|
+
### WASD Controls
|
|
49
|
+
|
|
50
|
+
| Key | Direction | Delta | Screen Effect |
|
|
51
|
+
|-----|-----------|-------|---------------|
|
|
52
|
+
| **W** / ↑ | North | `{dx: 0, dy: -1}` | ✅ Up |
|
|
53
|
+
| **S** / ↓ | South | `{dx: 0, dy: 1}` | ✅ Down |
|
|
54
|
+
| **A** / ← | West | `{dx: -1, dy: 0}` | ✅ Left |
|
|
55
|
+
| **D** / → | East | `{dx: 1, dy: 0}` | ✅ Right |
|
|
56
|
+
|
|
57
|
+
📖 **Full Guide**: [docs/COORDINATE_SYSTEMS.md](docs/COORDINATE_SYSTEMS.md)
|
|
58
|
+
🚀 **Interactive Demo**: Run `npm run demo:coordinates` after building
|
|
59
|
+
|
|
25
60
|
## API Overview
|
|
26
61
|
|
|
27
62
|
### Core Graphics
|
|
@@ -64,9 +99,12 @@ npm install bloody-engine
|
|
|
64
99
|
|
|
65
100
|
| Class | Description |
|
|
66
101
|
|-------|-------------|
|
|
67
|
-
| [
|
|
68
|
-
| [
|
|
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) |
|
|
69
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 |
|
|
70
108
|
| [ClientPredictor](src/networking/client-predictor.ts) | Client-side prediction for lag compensation |
|
|
71
109
|
| [ServerReconciler](src/networking/server-reconciler.ts) | Server-side reconciliation |
|
|
72
110
|
| [StateSnapshot](src/networking/state-snapshot.ts) | World state serialization |
|
|
@@ -226,32 +264,46 @@ const metrics = ticker.getMetrics();
|
|
|
226
264
|
console.log(`FPS: ${metrics.fps}, Delta Time: ${metrics.deltaTime}s`);
|
|
227
265
|
```
|
|
228
266
|
|
|
229
|
-
### 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
|
|
230
274
|
|
|
231
275
|
```typescript
|
|
232
|
-
import {
|
|
276
|
+
import { EntityManager, type EntityState } from 'bloody-engine';
|
|
233
277
|
|
|
234
|
-
// Create entity
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
|
241
286
|
});
|
|
242
287
|
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
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);
|
|
246
292
|
|
|
247
293
|
// Query entities
|
|
248
|
-
const players = manager.
|
|
249
|
-
const nearby = manager.
|
|
294
|
+
const players = manager.getEntitiesByType("player");
|
|
295
|
+
const nearby = manager.getEntitiesInRange(50, 60, 100);
|
|
250
296
|
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
297
|
+
// Register custom properties (opt-in extension)
|
|
298
|
+
manager.registerCustomProperty("health", Float32Array);
|
|
299
|
+
manager.registerCustomProperty("stamina", Uint32Array);
|
|
300
|
+
|
|
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);
|
|
255
307
|
```
|
|
256
308
|
|
|
257
309
|
### Input System with Command Queue
|
|
@@ -404,6 +456,131 @@ batchRenderer.addQuad({
|
|
|
404
456
|
});
|
|
405
457
|
```
|
|
406
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
|
+
|
|
407
584
|
## Advanced Examples
|
|
408
585
|
|
|
409
586
|
### Networked Game Architecture
|
|
@@ -483,7 +660,7 @@ console.log('Deterministic:', JSON.stringify(state1) === JSON.stringify(state2))
|
|
|
483
660
|
|
|
484
661
|
## Testing
|
|
485
662
|
|
|
486
|
-
The engine includes comprehensive tests for determinism and
|
|
663
|
+
The engine includes comprehensive tests for determinism, visual regression, and SoA functionality:
|
|
487
664
|
|
|
488
665
|
```bash
|
|
489
666
|
# Run all tests
|
|
@@ -528,6 +705,7 @@ src/
|
|
|
528
705
|
│ ├── batch-renderer.ts
|
|
529
706
|
│ ├── camera.ts
|
|
530
707
|
│ ├── projection.ts
|
|
708
|
+
│ ├── soa-webgl-renderer.ts # WebGL2 zero-copy renderer
|
|
531
709
|
│ └── spatial-hash.ts
|
|
532
710
|
├── input/ # Input system (command queue)
|
|
533
711
|
│ ├── command-queue.ts
|
|
@@ -536,6 +714,9 @@ src/
|
|
|
536
714
|
├── simulation/ # Game logic simulation
|
|
537
715
|
│ ├── entity.ts
|
|
538
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
|
|
539
720
|
│ └── simulation-loop.ts
|
|
540
721
|
├── networking/ # Networking for multiplayer
|
|
541
722
|
│ ├── client-predictor.ts
|
|
@@ -552,6 +733,7 @@ src/
|
|
|
552
733
|
### Key Concepts
|
|
553
734
|
|
|
554
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
|
|
555
737
|
- **Deterministic Simulation**: Game logic runs in fixed timestep for consistency across clients
|
|
556
738
|
- **Command Pattern**: All input goes through a command queue for easy recording/replay
|
|
557
739
|
- **Client-Side Prediction**: Reduces perceived lag in networked games
|