murow 0.0.31 → 0.0.41

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.
@@ -0,0 +1,79 @@
1
+ import { Component } from "./component";
2
+ /**
3
+ * Stores component data for entities using typed arrays.
4
+ * Provides efficient packed storage with O(1) access by entity ID.
5
+ *
6
+ * Key optimizations:
7
+ * - ArrayBuffer storage (exact byte size, no waste)
8
+ * - Single reusable DataView (no allocations)
9
+ * - Single reusable object for get() (zero allocations)
10
+ * - Pre-computed field offsets (no runtime calculations)
11
+ */
12
+ export declare class ComponentStore<T extends object> {
13
+ private buffer;
14
+ private view;
15
+ private stride;
16
+ private component;
17
+ private maxEntities;
18
+ private reusableObject;
19
+ private fieldOffsets;
20
+ private fields;
21
+ private fieldKeys;
22
+ private fieldIndexMap;
23
+ constructor(component: Component<T>, maxEntities: number);
24
+ /**
25
+ * Get component data for an entity.
26
+ *
27
+ * ⚠️ IMPORTANT: Returns a REUSED object that is overwritten on the next get() call.
28
+ * Use immediately or copy the data. For safe access, use getMutable() or copyTo().
29
+ *
30
+ * @example
31
+ * // ✅ CORRECT: Use immediately
32
+ * const t = store.get(entity);
33
+ * console.log(t.x, t.y);
34
+ *
35
+ * // ❌ WRONG: Storing reference
36
+ * const t1 = store.get(entity1);
37
+ * const t2 = store.get(entity2); // t1 is now corrupted!
38
+ *
39
+ * // ✅ CORRECT: Copy if you need multiple
40
+ * const t1 = { ...store.get(entity1) };
41
+ * const t2 = { ...store.get(entity2) };
42
+ */
43
+ get(entityId: number): Readonly<T>;
44
+ /**
45
+ * Get a mutable copy of component data.
46
+ * Use this when you need to modify and keep the data.
47
+ *
48
+ * Note: This allocates a new object. Use sparingly in hot paths.
49
+ */
50
+ getMutable(entityId: number): T;
51
+ /**
52
+ * Copy component data into a provided object.
53
+ * Use this when you need to keep multiple components at once.
54
+ */
55
+ copyTo(entityId: number, target: T): void;
56
+ /**
57
+ * Set component data for an entity.
58
+ * Writes the data directly into the typed array.
59
+ */
60
+ set(entityId: number, data: T): void;
61
+ /**
62
+ * Update specific fields of a component without reading the whole component first.
63
+ * Optimized to only iterate over the fields being updated.
64
+ */
65
+ update(entityId: number, partial: Partial<T>): void;
66
+ /**
67
+ * Clear component data for an entity (set to default values)
68
+ */
69
+ clear(entityId: number): void;
70
+ /**
71
+ * Get direct access to the underlying buffer.
72
+ * Advanced use only - for SIMD operations, GPU uploads, zero-copy networking, etc.
73
+ */
74
+ getRawBuffer(): ArrayBuffer;
75
+ /**
76
+ * Get the stride in bytes.
77
+ */
78
+ getStride(): number;
79
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Stores component data for entities using typed arrays.
3
+ * Provides efficient packed storage with O(1) access by entity ID.
4
+ *
5
+ * Key optimizations:
6
+ * - ArrayBuffer storage (exact byte size, no waste)
7
+ * - Single reusable DataView (no allocations)
8
+ * - Single reusable object for get() (zero allocations)
9
+ * - Pre-computed field offsets (no runtime calculations)
10
+ */
11
+ export class ComponentStore {
12
+ constructor(component, maxEntities) {
13
+ this.component = component;
14
+ this.maxEntities = maxEntities;
15
+ // Use exact byte size (no waste like Float32Array)
16
+ this.stride = component.size;
17
+ // Allocate exact memory needed
18
+ this.buffer = new ArrayBuffer(maxEntities * this.stride);
19
+ this.view = new DataView(this.buffer);
20
+ // Pre-compute field metadata once
21
+ this.fieldKeys = component.fieldNames;
22
+ this.fieldOffsets = [];
23
+ this.fields = [];
24
+ this.fieldIndexMap = new Map();
25
+ let offset = 0;
26
+ for (let i = 0; i < this.fieldKeys.length; i++) {
27
+ const key = this.fieldKeys[i];
28
+ const field = component.schema[key];
29
+ this.fieldOffsets.push(offset);
30
+ this.fields.push(field);
31
+ this.fieldIndexMap.set(key, i);
32
+ offset += field.size;
33
+ }
34
+ // Create single reusable object
35
+ this.reusableObject = {};
36
+ for (let i = 0; i < this.fieldKeys.length; i++) {
37
+ this.reusableObject[this.fieldKeys[i]] = this.fields[i].toNil();
38
+ }
39
+ }
40
+ /**
41
+ * Get component data for an entity.
42
+ *
43
+ * ⚠️ IMPORTANT: Returns a REUSED object that is overwritten on the next get() call.
44
+ * Use immediately or copy the data. For safe access, use getMutable() or copyTo().
45
+ *
46
+ * @example
47
+ * // ✅ CORRECT: Use immediately
48
+ * const t = store.get(entity);
49
+ * console.log(t.x, t.y);
50
+ *
51
+ * // ❌ WRONG: Storing reference
52
+ * const t1 = store.get(entity1);
53
+ * const t2 = store.get(entity2); // t1 is now corrupted!
54
+ *
55
+ * // ✅ CORRECT: Copy if you need multiple
56
+ * const t1 = { ...store.get(entity1) };
57
+ * const t2 = { ...store.get(entity2) };
58
+ */
59
+ get(entityId) {
60
+ const baseOffset = entityId * this.stride;
61
+ const length = this.fields.length;
62
+ // Unrolled loop for common cases
63
+ if (length === 2) {
64
+ this.reusableObject[this.fieldKeys[0]] = this.fields[0].read(this.view, baseOffset + this.fieldOffsets[0]);
65
+ this.reusableObject[this.fieldKeys[1]] = this.fields[1].read(this.view, baseOffset + this.fieldOffsets[1]);
66
+ }
67
+ else if (length === 3) {
68
+ this.reusableObject[this.fieldKeys[0]] = this.fields[0].read(this.view, baseOffset + this.fieldOffsets[0]);
69
+ this.reusableObject[this.fieldKeys[1]] = this.fields[1].read(this.view, baseOffset + this.fieldOffsets[1]);
70
+ this.reusableObject[this.fieldKeys[2]] = this.fields[2].read(this.view, baseOffset + this.fieldOffsets[2]);
71
+ }
72
+ else {
73
+ // Generic loop for other sizes
74
+ for (let i = 0; i < length; i++) {
75
+ this.reusableObject[this.fieldKeys[i]] = this.fields[i].read(this.view, baseOffset + this.fieldOffsets[i]);
76
+ }
77
+ }
78
+ return this.reusableObject;
79
+ }
80
+ /**
81
+ * Get a mutable copy of component data.
82
+ * Use this when you need to modify and keep the data.
83
+ *
84
+ * Note: This allocates a new object. Use sparingly in hot paths.
85
+ */
86
+ getMutable(entityId) {
87
+ const copy = {};
88
+ this.copyTo(entityId, copy);
89
+ return copy;
90
+ }
91
+ /**
92
+ * Copy component data into a provided object.
93
+ * Use this when you need to keep multiple components at once.
94
+ */
95
+ copyTo(entityId, target) {
96
+ const baseOffset = entityId * this.stride;
97
+ for (let i = 0; i < this.fields.length; i++) {
98
+ target[this.fieldKeys[i]] = this.fields[i].read(this.view, baseOffset + this.fieldOffsets[i]);
99
+ }
100
+ }
101
+ /**
102
+ * Set component data for an entity.
103
+ * Writes the data directly into the typed array.
104
+ */
105
+ set(entityId, data) {
106
+ const baseOffset = entityId * this.stride;
107
+ const length = this.fields.length;
108
+ // Unrolled loop for common cases
109
+ if (length === 2) {
110
+ this.fields[0].write(this.view, baseOffset + this.fieldOffsets[0], data[this.fieldKeys[0]]);
111
+ this.fields[1].write(this.view, baseOffset + this.fieldOffsets[1], data[this.fieldKeys[1]]);
112
+ }
113
+ else if (length === 3) {
114
+ this.fields[0].write(this.view, baseOffset + this.fieldOffsets[0], data[this.fieldKeys[0]]);
115
+ this.fields[1].write(this.view, baseOffset + this.fieldOffsets[1], data[this.fieldKeys[1]]);
116
+ this.fields[2].write(this.view, baseOffset + this.fieldOffsets[2], data[this.fieldKeys[2]]);
117
+ }
118
+ else {
119
+ // Generic loop for other sizes
120
+ for (let i = 0; i < length; i++) {
121
+ this.fields[i].write(this.view, baseOffset + this.fieldOffsets[i], data[this.fieldKeys[i]]);
122
+ }
123
+ }
124
+ }
125
+ /**
126
+ * Update specific fields of a component without reading the whole component first.
127
+ * Optimized to only iterate over the fields being updated.
128
+ */
129
+ update(entityId, partial) {
130
+ const baseOffset = entityId * this.stride;
131
+ // Use Object.keys for faster iteration than for...in
132
+ const keys = Object.keys(partial);
133
+ for (let j = 0; j < keys.length; j++) {
134
+ const key = keys[j];
135
+ const i = this.fieldIndexMap.get(key);
136
+ this.fields[i].write(this.view, baseOffset + this.fieldOffsets[i], partial[key]);
137
+ }
138
+ }
139
+ /**
140
+ * Clear component data for an entity (set to default values)
141
+ */
142
+ clear(entityId) {
143
+ const baseOffset = entityId * this.stride;
144
+ for (let i = 0; i < this.fields.length; i++) {
145
+ this.fields[i].write(this.view, baseOffset + this.fieldOffsets[i], this.fields[i].toNil());
146
+ }
147
+ }
148
+ /**
149
+ * Get direct access to the underlying buffer.
150
+ * Advanced use only - for SIMD operations, GPU uploads, zero-copy networking, etc.
151
+ */
152
+ getRawBuffer() {
153
+ return this.buffer;
154
+ }
155
+ /**
156
+ * Get the stride in bytes.
157
+ */
158
+ getStride() {
159
+ return this.stride;
160
+ }
161
+ }
@@ -0,0 +1,48 @@
1
+ import { Schema } from "../core/binary-codec";
2
+ import { ArrayField } from "../core/pooled-codec";
3
+ /**
4
+ * Metadata for a component definition
5
+ */
6
+ export interface ComponentMeta<T extends object> {
7
+ /** Schema defining the component's binary layout */
8
+ schema: Schema<T>;
9
+ /** Unique name for this component type */
10
+ name: string;
11
+ /** Size of the component in bytes */
12
+ size: number;
13
+ /** Number of fields in the schema */
14
+ fieldCount: number;
15
+ /** Field names in order */
16
+ fieldNames: (keyof T)[];
17
+ /** Codec for array serialization */
18
+ arrayCodec: ArrayField<T>;
19
+ }
20
+ /**
21
+ * Component type returned by defineComponent
22
+ */
23
+ export type Component<T extends object = any> = ComponentMeta<T> & {
24
+ /** Type marker for TypeScript inference */
25
+ __type?: T;
26
+ };
27
+ /**
28
+ * Infer the data type from a Component
29
+ */
30
+ export type InferComponentType<C> = C extends Component<infer T> ? T : never;
31
+ /**
32
+ * Define a component type with its binary schema.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * const Transform = defineComponent('Transform', {
37
+ * x: BinaryCodec.f32,
38
+ * y: BinaryCodec.f32,
39
+ * rotation: BinaryCodec.f32,
40
+ * });
41
+ *
42
+ * const Health = defineComponent('Health', {
43
+ * current: BinaryCodec.u16,
44
+ * max: BinaryCodec.u16,
45
+ * });
46
+ * ```
47
+ */
48
+ export declare function defineComponent<T extends object>(name: string, schema: Schema<T>): Component<T>;
@@ -0,0 +1,43 @@
1
+ import { PooledCodec } from "../core/pooled-codec";
2
+ /**
3
+ * Calculate the byte size of a schema
4
+ */
5
+ function calculateSchemaSize(schema) {
6
+ let size = 0;
7
+ for (const key of Object.keys(schema)) {
8
+ size += schema[key].size;
9
+ }
10
+ return size;
11
+ }
12
+ /**
13
+ * Define a component type with its binary schema.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const Transform = defineComponent('Transform', {
18
+ * x: BinaryCodec.f32,
19
+ * y: BinaryCodec.f32,
20
+ * rotation: BinaryCodec.f32,
21
+ * });
22
+ *
23
+ * const Health = defineComponent('Health', {
24
+ * current: BinaryCodec.u16,
25
+ * max: BinaryCodec.u16,
26
+ * });
27
+ * ```
28
+ */
29
+ export function defineComponent(name, schema) {
30
+ const size = calculateSchemaSize(schema);
31
+ const fieldNames = Object.keys(schema);
32
+ const fieldCount = fieldNames.length;
33
+ // Create PooledCodec for array serialization
34
+ const arrayCodec = PooledCodec.array(schema);
35
+ return {
36
+ name,
37
+ schema,
38
+ size,
39
+ fieldCount,
40
+ fieldNames,
41
+ arrayCodec,
42
+ };
43
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * ECS Usage Example
3
+ *
4
+ * This example demonstrates how to use the ECS for a simple game.
5
+ */
6
+ export {};
@@ -0,0 +1,125 @@
1
+ /**
2
+ * ECS Usage Example
3
+ *
4
+ * This example demonstrates how to use the ECS for a simple game.
5
+ */
6
+ import { defineComponent, World } from "./index";
7
+ import { BinaryCodec } from "../core/binary-codec";
8
+ // 1. Define components using BinaryCodec schemas
9
+ const Transform = defineComponent("Transform", {
10
+ x: BinaryCodec.f32,
11
+ y: BinaryCodec.f32,
12
+ rotation: BinaryCodec.f32,
13
+ });
14
+ const Velocity = defineComponent("Velocity", {
15
+ vx: BinaryCodec.f32,
16
+ vy: BinaryCodec.f32,
17
+ });
18
+ const Health = defineComponent("Health", {
19
+ current: BinaryCodec.u16,
20
+ max: BinaryCodec.u16,
21
+ });
22
+ const Damage = defineComponent("Damage", {
23
+ amount: BinaryCodec.u16,
24
+ });
25
+ // 2. Create systems
26
+ class MovementSystem {
27
+ update(world, deltaTime) {
28
+ for (const entity of world.query(Transform, Velocity)) {
29
+ const transform = world.get(entity, Transform); // Readonly!
30
+ const velocity = world.get(entity, Velocity); // Readonly!
31
+ // Update position using update() (efficient partial update)
32
+ world.update(entity, Transform, {
33
+ x: transform.x + velocity.vx * deltaTime,
34
+ y: transform.y + velocity.vy * deltaTime,
35
+ });
36
+ }
37
+ }
38
+ }
39
+ class HealthSystem {
40
+ update(world) {
41
+ for (const entity of world.query(Health)) {
42
+ const health = world.get(entity, Health);
43
+ // Despawn dead entities
44
+ if (health.current <= 0) {
45
+ console.log(`Entity ${entity} died`);
46
+ world.despawn(entity);
47
+ }
48
+ }
49
+ }
50
+ }
51
+ class CombatSystem {
52
+ update(world) {
53
+ // Simple collision-based damage (in real game, use spatial partitioning)
54
+ const combatants = Array.from(world.query(Transform, Health, Damage));
55
+ for (let i = 0; i < combatants.length; i++) {
56
+ for (let j = i + 1; j < combatants.length; j++) {
57
+ const e1 = combatants[i];
58
+ const e2 = combatants[j];
59
+ const t1 = world.get(e1, Transform);
60
+ const t2 = world.get(e2, Transform);
61
+ // Check collision (simple distance check)
62
+ const dx = t1.x - t2.x;
63
+ const dy = t1.y - t2.y;
64
+ const dist = Math.sqrt(dx * dx + dy * dy);
65
+ if (dist < 10) {
66
+ // Apply damage
67
+ const d1 = world.get(e1, Damage);
68
+ const d2 = world.get(e2, Damage);
69
+ world.update(e1, Health, { current: world.get(e1, Health).current - d2.amount });
70
+ world.update(e2, Health, { current: world.get(e2, Health).current - d1.amount });
71
+ console.log(`Entities ${e1} and ${e2} collided! Applying damage.`);
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
77
+ // 3. Create world and spawn entities
78
+ function runExample() {
79
+ const world = new World({
80
+ maxEntities: 1000,
81
+ components: [Transform, Velocity, Health, Damage],
82
+ });
83
+ // Spawn player
84
+ const player = world.spawn();
85
+ world.add(player, Transform, { x: 0, y: 0, rotation: 0 });
86
+ world.add(player, Velocity, { vx: 100, vy: 0 });
87
+ world.add(player, Health, { current: 100, max: 100 });
88
+ world.add(player, Damage, { amount: 10 });
89
+ // Spawn enemies
90
+ for (let i = 0; i < 5; i++) {
91
+ const enemy = world.spawn();
92
+ world.add(enemy, Transform, { x: 200 + i * 50, y: 0, rotation: 0 });
93
+ world.add(enemy, Velocity, { vx: -50, vy: 0 });
94
+ world.add(enemy, Health, { current: 50, max: 50 });
95
+ world.add(enemy, Damage, { amount: 5 });
96
+ }
97
+ console.log(`Spawned ${world.getEntityCount()} entities`);
98
+ // 4. Create systems
99
+ const movementSystem = new MovementSystem();
100
+ const combatSystem = new CombatSystem();
101
+ const healthSystem = new HealthSystem();
102
+ // 5. Game loop
103
+ const deltaTime = 0.016; // 60 FPS
104
+ let tick = 0;
105
+ const interval = setInterval(() => {
106
+ tick++;
107
+ // Update systems
108
+ movementSystem.update(world, deltaTime);
109
+ combatSystem.update(world);
110
+ healthSystem.update(world);
111
+ // Log stats every 10 ticks
112
+ if (tick % 10 === 0) {
113
+ console.log(`Tick ${tick}: ${world.getEntityCount()} entities alive`);
114
+ }
115
+ // Stop after 100 ticks or when all entities are dead
116
+ if (tick >= 100 || world.getEntityCount() === 0) {
117
+ clearInterval(interval);
118
+ console.log("Simulation ended");
119
+ }
120
+ }, deltaTime * 1000);
121
+ }
122
+ // Run the example
123
+ if (import.meta.main) {
124
+ runExample();
125
+ }
@@ -0,0 +1,3 @@
1
+ export { defineComponent, type Component, type InferComponentType, type ComponentMeta } from "./component";
2
+ export { ComponentStore } from "./component-store";
3
+ export { World, type Entity, type WorldConfig } from "./world";
@@ -0,0 +1,3 @@
1
+ export { defineComponent } from "./component";
2
+ export { ComponentStore } from "./component-store";
3
+ export { World } from "./world";
@@ -0,0 +1,208 @@
1
+ import { Component } from "./component";
2
+ /**
3
+ * Configuration for creating a World
4
+ */
5
+ export interface WorldConfig {
6
+ /** Maximum number of entities that can exist simultaneously */
7
+ maxEntities?: number;
8
+ /** Component types to register */
9
+ components: Component<any>[];
10
+ }
11
+ /**
12
+ * Entity ID type (just a number, indexing into component arrays)
13
+ */
14
+ export type Entity = number;
15
+ /**
16
+ * World manages entities and their components.
17
+ * Provides efficient ECS storage using typed arrays.
18
+ *
19
+ * Performance optimizations:
20
+ * - Array iteration instead of Set for 2-5x faster queries
21
+ * - Query bitmask caching for repeated queries
22
+ * - Array-indexed component stores for O(1) access
23
+ * - Pre-allocated ring buffer for entity ID reuse
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const world = new World({
28
+ * maxEntities: 10000,
29
+ * components: [Transform, Health, Velocity]
30
+ * });
31
+ *
32
+ * const entity = world.spawn();
33
+ * world.add(entity, Transform, { x: 100, y: 200, rotation: 0 });
34
+ * world.add(entity, Health, { current: 100, max: 100 });
35
+ *
36
+ * // Query entities
37
+ * for (const entity of world.query(Transform, Velocity)) {
38
+ * const transform = world.get(entity, Transform);
39
+ * const velocity = world.get(entity, Velocity);
40
+ * // transform is readonly, use update() to modify
41
+ * world.update(entity, Transform, {
42
+ * x: transform.x + velocity.vx,
43
+ * y: transform.y + velocity.vy
44
+ * });
45
+ * }
46
+ * ```
47
+ */
48
+ export declare class World {
49
+ private maxEntities;
50
+ private nextEntityId;
51
+ private freeEntityIds;
52
+ private freeEntityHead;
53
+ private freeEntityTail;
54
+ private freeEntityCount;
55
+ private freeEntityMask;
56
+ private aliveEntitiesArray;
57
+ private aliveEntitiesIndices;
58
+ private aliveEntityFlags;
59
+ private componentStoresArray;
60
+ private componentMasks;
61
+ private componentMap;
62
+ private components;
63
+ private queryResultBuffers;
64
+ private worldId;
65
+ constructor(config: WorldConfig);
66
+ /**
67
+ * Get component index (with caching via Map)
68
+ */
69
+ private getComponentIndex;
70
+ /**
71
+ * Get or compute query bitmask (optimized - computes mask without caching)
72
+ */
73
+ private getQueryMask;
74
+ /**
75
+ * Spawn a new entity.
76
+ * Returns the entity ID.
77
+ */
78
+ spawn(): Entity;
79
+ /**
80
+ * Despawn an entity, removing all its components.
81
+ * The entity ID will be reused.
82
+ */
83
+ despawn(entity: Entity): void;
84
+ /**
85
+ * Check if an entity is alive
86
+ */
87
+ isAlive(entity: Entity): boolean;
88
+ /**
89
+ * Add a component to an entity with initial data.
90
+ */
91
+ add<T extends object>(entity: Entity, component: Component<T>, data: T): void;
92
+ /**
93
+ * Remove a component from an entity.
94
+ */
95
+ remove<T extends object>(entity: Entity, component: Component<T>): void;
96
+ /**
97
+ * Check if an entity has a component.
98
+ */
99
+ has<T extends object>(entity: Entity, component: Component<T>): boolean;
100
+ /**
101
+ * Get a component's data for an entity.
102
+ * Returns a READONLY reusable object (zero allocations).
103
+ *
104
+ * ⚠️ IMPORTANT: The returned object is reused and will be overwritten on the next get().
105
+ * To modify, use set() or update() instead.
106
+ * To keep multiple components, use getMutable() or spread operator.
107
+ *
108
+ * @example
109
+ * // ✅ CORRECT: Use immediately
110
+ * const t = world.get(entity, Transform);
111
+ * console.log(t.x, t.y);
112
+ *
113
+ * // ❌ WRONG: Storing reference
114
+ * const t1 = world.get(entity1, Transform);
115
+ * const t2 = world.get(entity2, Transform); // t1 is now corrupted!
116
+ *
117
+ * // ✅ CORRECT: Copy if you need to keep
118
+ * const t1 = { ...world.get(entity1, Transform) };
119
+ * const t2 = { ...world.get(entity2, Transform) };
120
+ */
121
+ get<T extends object>(entity: Entity, component: Component<T>): Readonly<T>;
122
+ /**
123
+ * Get a mutable copy of component data.
124
+ * Use this when you need to modify and keep the data.
125
+ *
126
+ * Note: This allocates a new object. Use sparingly in hot paths.
127
+ */
128
+ getMutable<T extends object>(entity: Entity, component: Component<T>): T;
129
+ /**
130
+ * Set a component's data for an entity.
131
+ * Overwrites all fields.
132
+ */
133
+ set<T extends object>(entity: Entity, component: Component<T>, data: T): void;
134
+ /**
135
+ * Update specific fields of a component.
136
+ * More efficient than get + modify + set.
137
+ *
138
+ * @example
139
+ * // ✅ GOOD: Partial update
140
+ * world.update(entity, Transform, { x: 150 });
141
+ *
142
+ * // ❌ BAD: Full get/set for single field
143
+ * const t = world.getMutable(entity, Transform);
144
+ * t.x = 150;
145
+ * world.set(entity, Transform, t);
146
+ */
147
+ update<T extends object>(entity: Entity, component: Component<T>, partial: Partial<T>): void;
148
+ /**
149
+ * Query entities that have all specified components.
150
+ * Returns a readonly array for zero-allocation iteration.
151
+ *
152
+ * Uses reusable buffers and direct bitmask checks for maximum performance.
153
+ * The returned array is reused on subsequent queries with the same mask.
154
+ *
155
+ * @example
156
+ * ```typescript
157
+ * for (const entity of world.query(Transform, Velocity)) {
158
+ * const t = world.get(entity, Transform);
159
+ * const v = world.get(entity, Velocity);
160
+ * world.update(entity, Transform, {
161
+ * x: t.x + v.vx * dt,
162
+ * y: t.y + v.vy * dt
163
+ * });
164
+ * }
165
+ * ```
166
+ */
167
+ query(...components: Component<any>[]): readonly Entity[];
168
+ /**
169
+ * Get all alive entity IDs.
170
+ *
171
+ * ⚠️ WARNING: The returned array is a direct reference and should not be modified.
172
+ * For a safe copy, use [...world.getEntities()].
173
+ */
174
+ getEntities(): readonly Entity[];
175
+ /**
176
+ * Get the number of alive entities.
177
+ */
178
+ getEntityCount(): number;
179
+ /**
180
+ * Get the maximum number of entities.
181
+ */
182
+ getMaxEntities(): number;
183
+ /**
184
+ * Get all registered components.
185
+ */
186
+ getComponents(): readonly Component<any>[];
187
+ /**
188
+ * Get component names for an entity (for debugging)
189
+ */
190
+ private getEntityComponentNames;
191
+ /**
192
+ * Serialize entities with specific components to binary.
193
+ * Uses PooledCodec internally for efficient encoding.
194
+ *
195
+ * @param components Components to include in the snapshot
196
+ * @param entities Optional list of entities to serialize (defaults to all)
197
+ * @returns Binary buffer with serialized data
198
+ */
199
+ serialize(components: Component<any>[], entities?: Entity[]): Uint8Array;
200
+ /**
201
+ * Deserialize binary data into entities.
202
+ * Uses PooledCodec internally for efficient decoding.
203
+ *
204
+ * Note: This is a basic implementation. For production use,
205
+ * you'd want a more sophisticated format with component IDs, etc.
206
+ */
207
+ deserialize(components: Component<any>[], buffer: Uint8Array): void;
208
+ }