diff --git a/packages/miniplex-bucket/src/Bucket.ts b/packages/miniplex-bucket/src/Bucket.ts index 39d3dcd5..ed7f101e 100644 --- a/packages/miniplex-bucket/src/Bucket.ts +++ b/packages/miniplex-bucket/src/Bucket.ts @@ -61,4 +61,22 @@ export class Bucket extends SimpleBucket { return bucket } + + /* DIRTY TRACKING */ + + private dirty = new Set() + + mark(entity: E) { + this.dirty.add(entity) + } + + flushMarked() { + for (const entity of this.dirty) this.test(entity) + this.dirty.clear() + } + + update(entity: D, fun: (entity: D) => void) { + fun(entity) + this.mark(entity) + } } diff --git a/packages/miniplex-bucket/test/Bucket.test.ts b/packages/miniplex-bucket/test/Bucket.test.ts index c62b29ee..87101a6b 100644 --- a/packages/miniplex-bucket/test/Bucket.test.ts +++ b/packages/miniplex-bucket/test/Bucket.test.ts @@ -1,3 +1,4 @@ +import { World } from "../../miniplex/src" import { Bucket } from "../src" describe(Bucket, () => { @@ -53,5 +54,28 @@ describe(Bucket, () => { const derived2 = bucket.where(predicate) expect(derived1).toBe(derived2) }) + + it("creates a bucket that is updated automatically for entities that are explicitly marked as dirty", () => { + type Entity = { health: number } + + const bucket = new Bucket() + const player = bucket.add({ health: 30 }) + const lowHealth = bucket.where((p) => p.health < 25) + + function applyDamage(entity: Entity, amount = 10) { + entity.health -= amount + bucket.mark(entity) + } + + expect(lowHealth.entities).toEqual([]) + + /* Ouch, something hit the player */ + applyDamage(player) + + /* This would typically be done once per tick, or similar */ + bucket.flushMarked() + + expect(lowHealth.entities).toEqual([player]) + }) }) }) diff --git a/packages/miniplex-core/benchmark.ts b/packages/miniplex-core/benchmark.ts index 59a0c323..dcf320c5 100644 --- a/packages/miniplex-core/benchmark.ts +++ b/packages/miniplex-core/benchmark.ts @@ -196,6 +196,63 @@ profile("simulate (iterator, archetypes)", () => { } }) +profile("simulate (mutate & mark)", () => { + const world = new World() + const withVelocity = world.where(archetype("velocity")) + + for (let i = 0; i < entityCount; i++) + world.add({ + position: { x: 0, y: i, z: 0 }, + velocity: { x: 1, y: 2, z: 3 } + }) + + return () => { + let i = 0 + + for (const entity of withVelocity) { + i++ + + const { position, velocity } = entity + position.x += velocity.x + position.y += velocity.y + position.z += velocity.z + world.mark(entity) + } + + world.flushMarked() + + return () => i === entityCount + } +}) + +profile("simulate (update & function)", () => { + const world = new World() + const withVelocity = world.where(archetype("velocity")) + + for (let i = 0; i < entityCount; i++) + world.add({ + position: { x: 0, y: i, z: 0 }, + velocity: { x: 1, y: 2, z: 3 } + }) + + return () => { + let i = 0 + + for (const entity of withVelocity) { + i++ + world.update(entity, ({ position, velocity }) => { + position.x += velocity.x + position.y += velocity.y + position.z += velocity.z + }) + } + + world.flushMarked() + + return () => i === entityCount + } +}) + profile("simulate (array)", () => { const world = new World()