diff --git a/engine.js b/engine.js index 4ceb5a73..7bc6b3c1 100644 --- a/engine.js +++ b/engine.js @@ -97,6 +97,9 @@ export class Engine { this.worldMeshes = new Map(); this.renderRequested = false; + this.observedWorlds = new Set(); + this.updateRequested = false; + window.addEventListener('resize', () => { this.resize(); this.requestRender(); @@ -105,6 +108,19 @@ export class Engine { this.resize(); } + requestUpdate() { + if (!this.updateRequested) { + this.updateRequested = true; + + // Promise.resolve().then() executes immediately after the current synchronous + // call stack finishes, ensuring 10 simultaneous setBlock calls only trigger 1 update. + Promise.resolve().then(() => { + this.updateRequested = false; + this.updateAll(); + }); + } + } + requestRender() { if (!this.renderRequested) { this.renderRequested = true; @@ -122,6 +138,14 @@ export class Engine { addDiorama(diorama) { this.dioramas.push(diorama); + + // Subscribe to the world if we aren't already watching it + if (!this.observedWorlds.has(diorama.world)) { + this.observedWorlds.add(diorama.world); + + // Whenever the world changes, queue a single, debounced geometry rebuild + diorama.world.subscribe(() => this.requestUpdate()); + } } async updateAll() { diff --git a/index.html b/index.html index a4bc9bde..85d531ad 100644 --- a/index.html +++ b/index.html @@ -128,6 +128,40 @@ // Fetch and build everything concurrently! await engine.updateAll(); + + function set0() { + world2.setBlock('minecraft:sticky_piston', 2, 0, 2, {facing: 'east', extended: 'false'}); + world2.setBlock('minecraft:redstone_block', 3, 0, 2); + world2.removeBlock(2.5, 0, 2); + world2.removeBlock(3.5, 0, 2); + setTimeout(set1, 250) + } + + function set1() { + world2.setBlock('minecraft:sticky_piston', 2, 0, 2, {facing: 'east', extended: 'true'}); + world2.removeBlock(3, 0, 2) + world2.setBlock('minecraft:piston_head', 2.5, 0, 2, {facing: 'east', short: 'true', type: 'sticky'}); + world2.setBlock('minecraft:redstone_block', 3.5, 0, 2); + setTimeout(set2, 250) + } + + function set2() { + world2.setBlock('minecraft:piston_head', 3, 0, 2, {facing: 'east', short: 'false', type: 'sticky'}); + world2.removeBlock(2.5, 0, 2); + world2.removeBlock(3.5, 0, 2); + world2.setBlock('minecraft:redstone_block', 4, 0, 2); + setTimeout(set3, 250) + } + + function set3() { + world2.removeBlock(4, 0, 2) + world2.removeBlock(3, 0, 2) + world2.setBlock('minecraft:piston_head', 2.5, 0, 2, {facing: 'east', short: 'true', type: 'sticky'}); + world2.setBlock('minecraft:redstone_block', 3.5, 0, 2); + setTimeout(set0, 250) + } + + setTimeout(set3, 1000) } catch (err) { console.error("Renderer Initialization Failed:", err); } diff --git a/world.js b/world.js index 57fb083a..4e92c1aa 100644 --- a/world.js +++ b/world.js @@ -1,16 +1,30 @@ // world.js export class World { constructor() { - this.blocks = new Map(); // Keyed by "x,y,z" + this.blocks = new Map(); + this.listeners = new Set(); + } + + // Subscribe to changes (returns an unsubscribe function) + subscribe(callback) { + this.listeners.add(callback); + return () => this.listeners.delete(callback); + } + + notify() { + for (const listener of this.listeners) listener(); } setBlock(id, x, y, z, props = {}) { const key = `${x},${y},${z}`; this.blocks.set(key, { id, x, y, z, props }); + this.notify(); } removeBlock(x, y, z) { - this.blocks.delete(`${x},${y},${z}`); + if (this.blocks.delete(`${x},${y},${z}`)) { + this.notify(); + } } updateBlock(x, y, z, newProps) { @@ -18,6 +32,7 @@ export class World { const block = this.blocks.get(key); if (block) { block.props = { ...block.props, ...newProps }; + this.notify(); } }