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({
{
- room.tiles.map((tiles, i) => (
+ resource.tiles.map((tiles, i) => (
// eslint-disable-next-line react/no-array-index-key
-
@@ -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;
}