diff --git a/packages/topdown/src/persea/controllers/room/component.jsx b/packages/topdown/src/persea/controllers/room/component.jsx index ccc7702..89f2c57 100644 --- a/packages/topdown/src/persea/controllers/room/component.jsx +++ b/packages/topdown/src/persea/controllers/room/component.jsx @@ -41,7 +41,7 @@ const RoomComponent = ({ const {uri, uuid} = useContext(Context); const dispatch = useDispatch(); const flecks = useFlecks(); - const {Room} = flecks.get('$avocado/resource.resources'); + const {Room, Tiles} = flecks.get('$avocado/resource.resources'); const previousResource = usePrevious(resource); const [events, setEvents] = useState(); const [side, setSide] = useState(0); @@ -53,31 +53,8 @@ const RoomComponent = ({ setEvents(stage?.events()); }, [setEvents]); useEffect(() => { - if (room) { - const load = async () => { - await Promise.all( - room.tiles.map(async (tiles, i) => { - await tiles.load(resource.tiles[i]); - const {area, tileSize} = tiles; - const viewport = Vector.mul(area, tileSize); - tiles.emit('update', Rectangle.compose([0, 0], viewport)); - }), - ); - if (resource.entities.length !== previousResource.entities.length) { - await room.entityList.load(resource.entities); - return; - } - await Promise.all( - Object.values(room.entities) - .map(async (entity, i) => { - await entity.load(resource.entities[i]); - entity.renderTick(Infinity); - }), - ); - }; - load(); - } - else { + // First time, render the entire room. + if (!room) { const loadRoom = async () => { const room = await Room.load({ ...resource, @@ -90,6 +67,50 @@ const RoomComponent = ({ }; loadRoom(); } + // Otherwise, we'll update the room. + else { + const updateRoom = async () => { + if (previousResource.entities !== resource.entities) { + if (previousResource.entities.length !== resource.entities.length) { + await room.entityList.load(resource.entities); + } + else { + for (let i = 0; i < resource.entities.length; i++) { + const element = resource.entities[i]; + if (element !== previousResource.entities[i]) { + const entity = room.entityList.$$flatEntities[i]; + // eslint-disable-next-line no-await-in-loop + await entity.load(element); + entity.renderTick(Infinity); + } + } + } + } + if (previousResource.tiles !== resource.tiles) { + if (previousResource.tiles.length !== resource.tiles.length) { + await room.loadTiles(resource.tiles); + roomRenderable.renderChunksForExtent(Rectangle.compose([0, 0], room.size)); + } + else { + for (let i = 0; i < resource.tiles.length; i++) { + const element = resource.tiles[i]; + if (element !== previousResource.tiles[i]) { + const currentData = Tiles.inflate(element.data); + const previousData = Tiles.inflate(previousResource.tiles[i].data); + const updates = []; + for (let j = 0; j < currentData.length; j++) { + if (currentData[j] !== previousData[j]) { + updates.push(i); + } + } + room.tiles[i].emit('update', Tiles.whereFromUpdates(updates)); + } + } + } + } + }; + updateRoom(); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [resource, uri]); if (!roomRenderable) { diff --git a/packages/topdown/src/persea/room-sides/entities-side/index.jsx b/packages/topdown/src/persea/room-sides/entities-side/index.jsx index 20e4d54..c89e1e2 100644 --- a/packages/topdown/src/persea/room-sides/entities-side/index.jsx +++ b/packages/topdown/src/persea/room-sides/entities-side/index.jsx @@ -75,18 +75,24 @@ function EntitiesPage({ return undefined; } const onValue = ({ - clientX, - clientY, deltaY, - target, + position, type, }) => { const updatePosition = () => { - const {left, top} = target.getBoundingClientRect(); patch.diff( `/entities/${selectedEntityIndex}`, entity, - merge(entity, {traits: {Positioned: {state: {x: clientX - left, y: clientY - top}}}}), + merge( + entity, + { + traits: { + Positioned: { + state: {x: Math.floor(position[0]), y: Math.floor(position[1])}, + }, + }, + }, + ), ); }; switch (type) { diff --git a/packages/topdown/src/persea/room-sides/tiles-side/index.jsx b/packages/topdown/src/persea/room-sides/tiles-side/index.jsx index 1596963..61a281b 100644 --- a/packages/topdown/src/persea/room-sides/tiles-side/index.jsx +++ b/packages/topdown/src/persea/room-sides/tiles-side/index.jsx @@ -149,23 +149,10 @@ function TilesPage({ room.tiles[currentLayer].$$data, Rectangle.compose(origin, room.tiles[currentLayer].area), ); - const [min, max] = [[Infinity, Infinity], [-Infinity, -Infinity]]; for (let i = 0; i < updates.length; i++) { const update = updates[i]; const [x, y] = [update % width, Math.floor(update / width)]; const offset = Vector.sub([x, y], origin); - if (x < min[0]) { - min[0] = x; - } - else if (x > max[0]) { - max[0] = x; - } - if (y < min[1]) { - min[1] = y; - } - else if (y > max[1]) { - max[1] = y; - } let tileLocation; switch (fillMode) { case 0: { @@ -192,7 +179,7 @@ function TilesPage({ } $$data[update] = (tileLocation[1] * (viewport[0] / tileSize[0])) + tileLocation[0]; } - const where = Rectangle.compose(min, Vector.add([1, 1], Vector.sub(max, min))); + const where = room.tiles[currentLayer].constructor.whereFromUpdates(updates); room.tiles[currentLayer].emit('update', where); }; const onValue = ( @@ -203,7 +190,7 @@ function TilesPage({ ) => { const origin = Vector.floor(Vector.div(position, tileSize)); const edit = () => { - if (0 === room.tiles.length) { + if (0 === resource.tiles.length) { return; } const {data: originalData} = room.tiles[currentLayer].toJSON(); @@ -311,7 +298,7 @@ function TilesPage({
@@ -438,14 +425,14 @@ function TilesPage({ Area {}} - value={room.tiles[currentLayer].area} + value={resource.tiles[currentLayer].area} />
diff --git a/packages/topdown/src/renderable/room.js b/packages/topdown/src/renderable/room.js index 703290c..cb3966a 100644 --- a/packages/topdown/src/renderable/room.js +++ b/packages/topdown/src/renderable/room.js @@ -12,9 +12,12 @@ export default class RoomRenderable extends Container { constructor(room, renderer) { super(); this.lastExtent = [0, 0, 0, 0]; + this.room = room; this.renderer = renderer; this.entityListView = new EntityListView(room.entityList); this.tilesRenderables = room.tiles.map((tiles) => new TilesRenderable(tiles, renderer)); + room.on('tilesAdded', this.onTilesAdded, this); + room.on('tilesRemoved', this.onTilesRemoved, this); this.addChildren(this.tilesRenderables); this.addChild(this.entityListView); this.sort(); @@ -25,11 +28,28 @@ export default class RoomRenderable extends Container { destroy() { super.destroy(); + this.room.off('tilesAdded', this.onTilesAdded); + this.room.off('tilesRemoved', this.onTilesRemoved); if (module.hot) { renderables.delete(this); } } + onTilesAdded(tiles) { + const renderable = new TilesRenderable(tiles, this.renderer); + this.tilesRenderables.push(renderable); + this.addChild(renderable); + } + + onTilesRemoved(tiles) { + const index = this.tilesRenderables.findIndex((renderable) => renderable.tiles === tiles); + if (-1 === index) { + return; + } + const [renderable] = this.tilesRenderables.splice(index, 1); + this.removeChild(renderable); + } + renderChunksForExtent(extent) { this.tilesRenderables.forEach((tilesRenderable) => { tilesRenderable.renderChunksForExtent(extent); diff --git a/packages/topdown/src/resources/room.js b/packages/topdown/src/resources/room.js index 97c522c..516b15b 100644 --- a/packages/topdown/src/resources/room.js +++ b/packages/topdown/src/resources/room.js @@ -15,10 +15,10 @@ export default (flecks) => { ); return class Room extends decorate(JsonResource) { - entityList; - static Renderable = RoomRenderable; + entityList; + tiles = []; constructor() { @@ -38,13 +38,18 @@ export default (flecks) => { return this.entityList.entities; } + addTiles(tiles) { + this.tiles.push(tiles); + this.startSynchronizing(tiles); + this.emit('tilesAdded', tiles); + } + findEntity(uuid) { return this.entityList.findEntity(uuid); } async load(json = {}) { await super.load(json); - const {Tiles} = flecks.get('$avocado/resource.resources'); const { entities, size = [0, 0], @@ -56,13 +61,19 @@ export default (flecks) => { this.startSynchronizing(this.entityList); this.size = size; if (tiles) { - this.tiles = await Promise.all(tiles.map((tiles, i) => Tiles.load({s13nId: i, ...tiles}))); - this.tiles.forEach((tiles) => { - this.startSynchronizing(tiles); - }); + await this.loadTiles(tiles); } } + async loadTiles(tiles) { + const {Tiles} = flecks.get('$avocado/resource.resources'); + this.removeAllTiles(); + (await Promise.all(tiles.map((json, i) => Tiles.load({s13nId: i, ...json})))) + .forEach((tiles) => { + this.addTiles(tiles); + }); + } + onEntityAdded(entity) { // eslint-disable-next-line no-param-reassign entity.room = this; @@ -77,10 +88,26 @@ export default (flecks) => { this.emit('entityRemoved', entity); } + removeAllTiles() { + for (let i = this.tiles.length - 1; i >= 0; --i) { + this.removeTiles(this.tiles[i]); + } + } + removeEntity(entity) { this.entityList.removeEntity(entity); } + removeTiles(tiles) { + const index = this.tiles.indexOf(tiles); + if (-1 === index) { + return; + } + const [removed] = this.tiles.splice(index, 1); + this.stopSynchronizing(removed); + this.emit('tilesRemoved', removed); + } + get s13nId() { return this.$$s13nId; } diff --git a/packages/topdown/src/resources/tiles.js b/packages/topdown/src/resources/tiles.js index 551390f..075944a 100644 --- a/packages/topdown/src/resources/tiles.js +++ b/packages/topdown/src/resources/tiles.js @@ -124,6 +124,11 @@ export default (flecks) => { return this.constructor.indexHulls(indices, this.$$data, this.area); } + static inflate(data) { + const {buffer, byteOffset, length} = inflate(Buffer.from(data, 'base64')); + return new Uint16Array(buffer, byteOffset, length / 2); + } + async load(json) { const { area, @@ -137,8 +142,7 @@ export default (flecks) => { this.area = area; } if (data) { - const {buffer, byteOffset, length} = inflate(Buffer.from(data, 'base64')); - this.$$data = new Uint16Array(buffer, byteOffset, length / 2); + this.$$data = this.constructor.inflate(data); } else if (area) { this.$$data = new Uint16Array(Vector.area(area)); @@ -298,6 +302,27 @@ export default (flecks) => { }; } + static whereFromUpdates(updates) { + const [min, max] = [[Infinity, Infinity], [-Infinity, -Infinity]]; + for (let i = 0; i < updates.length; i++) { + const update = updates[i]; + const [x, y] = [update % this.width, Math.floor(update / this.width)]; + if (x < min[0]) { + min[0] = x; + } + else if (x > max[0]) { + max[0] = x; + } + if (y < min[1]) { + min[1] = y; + } + else if (y > max[1]) { + max[1] = y; + } + } + return Rectangle.compose(min, Vector.add([1, 1], Vector.sub(max, min))); + } + get zIndex() { return this.$$zIndex; }