flow: room editing

This commit is contained in:
cha0s 2022-04-11 18:05:15 -05:00
parent 65d5430ab0
commit 06b1a6ec66
6 changed files with 149 additions and 63 deletions

View File

@ -41,7 +41,7 @@ const RoomComponent = ({
const {uri, uuid} = useContext(Context); const {uri, uuid} = useContext(Context);
const dispatch = useDispatch(); const dispatch = useDispatch();
const flecks = useFlecks(); const flecks = useFlecks();
const {Room} = flecks.get('$avocado/resource.resources'); const {Room, Tiles} = flecks.get('$avocado/resource.resources');
const previousResource = usePrevious(resource); const previousResource = usePrevious(resource);
const [events, setEvents] = useState(); const [events, setEvents] = useState();
const [side, setSide] = useState(0); const [side, setSide] = useState(0);
@ -53,31 +53,8 @@ const RoomComponent = ({
setEvents(stage?.events()); setEvents(stage?.events());
}, [setEvents]); }, [setEvents]);
useEffect(() => { useEffect(() => {
if (room) { // First time, render the entire room.
const load = async () => { if (!room) {
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 {
const loadRoom = async () => { const loadRoom = async () => {
const room = await Room.load({ const room = await Room.load({
...resource, ...resource,
@ -90,6 +67,50 @@ const RoomComponent = ({
}; };
loadRoom(); 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 // eslint-disable-next-line react-hooks/exhaustive-deps
}, [resource, uri]); }, [resource, uri]);
if (!roomRenderable) { if (!roomRenderable) {

View File

@ -75,18 +75,24 @@ function EntitiesPage({
return undefined; return undefined;
} }
const onValue = ({ const onValue = ({
clientX,
clientY,
deltaY, deltaY,
target, position,
type, type,
}) => { }) => {
const updatePosition = () => { const updatePosition = () => {
const {left, top} = target.getBoundingClientRect();
patch.diff( patch.diff(
`/entities/${selectedEntityIndex}`, `/entities/${selectedEntityIndex}`,
entity, 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) { switch (type) {

View File

@ -149,23 +149,10 @@ function TilesPage({
room.tiles[currentLayer].$$data, room.tiles[currentLayer].$$data,
Rectangle.compose(origin, room.tiles[currentLayer].area), Rectangle.compose(origin, room.tiles[currentLayer].area),
); );
const [min, max] = [[Infinity, Infinity], [-Infinity, -Infinity]];
for (let i = 0; i < updates.length; i++) { for (let i = 0; i < updates.length; i++) {
const update = updates[i]; const update = updates[i];
const [x, y] = [update % width, Math.floor(update / width)]; const [x, y] = [update % width, Math.floor(update / width)];
const offset = Vector.sub([x, y], origin); 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; let tileLocation;
switch (fillMode) { switch (fillMode) {
case 0: { case 0: {
@ -192,7 +179,7 @@ function TilesPage({
} }
$$data[update] = (tileLocation[1] * (viewport[0] / tileSize[0])) + tileLocation[0]; $$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); room.tiles[currentLayer].emit('update', where);
}; };
const onValue = ( const onValue = (
@ -203,7 +190,7 @@ function TilesPage({
) => { ) => {
const origin = Vector.floor(Vector.div(position, tileSize)); const origin = Vector.floor(Vector.div(position, tileSize));
const edit = () => { const edit = () => {
if (0 === room.tiles.length) { if (0 === resource.tiles.length) {
return; return;
} }
const {data: originalData} = room.tiles[currentLayer].toJSON(); const {data: originalData} = room.tiles[currentLayer].toJSON();
@ -311,7 +298,7 @@ function TilesPage({
<div className={styles.layers}> <div className={styles.layers}>
<ul> <ul>
{ {
room.tiles.map((tiles, i) => ( resource.tiles.map((tiles, i) => (
// eslint-disable-next-line react/no-array-index-key // eslint-disable-next-line react/no-array-index-key
<li key={i}> <li key={i}>
<button <button
@ -329,9 +316,9 @@ function TilesPage({
<span>Area</span> <span>Area</span>
<span> <span>
[ [
{tiles.width} {tiles.area[0]}
{' x '} {' x '}
{tiles.height} {tiles.area[1]}
] ]
</span> </span>
</label> </label>
@ -349,15 +336,15 @@ function TilesPage({
<span>Full size</span> <span>Full size</span>
<span> <span>
[ [
{tiles.tileSize[0] * tiles.width} {tiles.tileSize[0] * tiles.area[0]}
{' x '} {' x '}
{tiles.tileSize[1] * tiles.height} {tiles.tileSize[1] * tiles.area[1]}
] ]
</span> </span>
</label> </label>
<label> <label>
<span>Tileset URI</span> <span>Tileset URI</span>
<span>{tiles.atlas.image.uri}</span> <span>{tiles.tileImageUri}</span>
</label> </label>
</div> </div>
<div className={styles.visibility}> <div className={styles.visibility}>
@ -438,14 +425,14 @@ function TilesPage({
<span>Area</span> <span>Area</span>
<VectorComponent <VectorComponent
onChange={() => {}} onChange={() => {}}
value={room.tiles[currentLayer].area} value={resource.tiles[currentLayer].area}
/> />
</label> </label>
<label className={styles.tileSize}> <label className={styles.tileSize}>
<span>Tile size</span> <span>Tile size</span>
<VectorComponent <VectorComponent
onChange={() => {}} onChange={() => {}}
value={room.tiles[currentLayer].tileSize} value={resource.tiles[currentLayer].tileSize}
/> />
</label> </label>
<div className={styles.grower} /> <div className={styles.grower} />

View File

@ -12,9 +12,12 @@ export default class RoomRenderable extends Container {
constructor(room, renderer) { constructor(room, renderer) {
super(); super();
this.lastExtent = [0, 0, 0, 0]; this.lastExtent = [0, 0, 0, 0];
this.room = room;
this.renderer = renderer; this.renderer = renderer;
this.entityListView = new EntityListView(room.entityList); this.entityListView = new EntityListView(room.entityList);
this.tilesRenderables = room.tiles.map((tiles) => new TilesRenderable(tiles, renderer)); 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.addChildren(this.tilesRenderables);
this.addChild(this.entityListView); this.addChild(this.entityListView);
this.sort(); this.sort();
@ -25,11 +28,28 @@ export default class RoomRenderable extends Container {
destroy() { destroy() {
super.destroy(); super.destroy();
this.room.off('tilesAdded', this.onTilesAdded);
this.room.off('tilesRemoved', this.onTilesRemoved);
if (module.hot) { if (module.hot) {
renderables.delete(this); 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) { renderChunksForExtent(extent) {
this.tilesRenderables.forEach((tilesRenderable) => { this.tilesRenderables.forEach((tilesRenderable) => {
tilesRenderable.renderChunksForExtent(extent); tilesRenderable.renderChunksForExtent(extent);

View File

@ -15,10 +15,10 @@ export default (flecks) => {
); );
return class Room extends decorate(JsonResource) { return class Room extends decorate(JsonResource) {
entityList;
static Renderable = RoomRenderable; static Renderable = RoomRenderable;
entityList;
tiles = []; tiles = [];
constructor() { constructor() {
@ -38,13 +38,18 @@ export default (flecks) => {
return this.entityList.entities; return this.entityList.entities;
} }
addTiles(tiles) {
this.tiles.push(tiles);
this.startSynchronizing(tiles);
this.emit('tilesAdded', tiles);
}
findEntity(uuid) { findEntity(uuid) {
return this.entityList.findEntity(uuid); return this.entityList.findEntity(uuid);
} }
async load(json = {}) { async load(json = {}) {
await super.load(json); await super.load(json);
const {Tiles} = flecks.get('$avocado/resource.resources');
const { const {
entities, entities,
size = [0, 0], size = [0, 0],
@ -56,13 +61,19 @@ export default (flecks) => {
this.startSynchronizing(this.entityList); this.startSynchronizing(this.entityList);
this.size = size; this.size = size;
if (tiles) { if (tiles) {
this.tiles = await Promise.all(tiles.map((tiles, i) => Tiles.load({s13nId: i, ...tiles}))); await this.loadTiles(tiles);
this.tiles.forEach((tiles) => {
this.startSynchronizing(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) { onEntityAdded(entity) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
entity.room = this; entity.room = this;
@ -77,10 +88,26 @@ export default (flecks) => {
this.emit('entityRemoved', entity); this.emit('entityRemoved', entity);
} }
removeAllTiles() {
for (let i = this.tiles.length - 1; i >= 0; --i) {
this.removeTiles(this.tiles[i]);
}
}
removeEntity(entity) { removeEntity(entity) {
this.entityList.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() { get s13nId() {
return this.$$s13nId; return this.$$s13nId;
} }

View File

@ -124,6 +124,11 @@ export default (flecks) => {
return this.constructor.indexHulls(indices, this.$$data, this.area); 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) { async load(json) {
const { const {
area, area,
@ -137,8 +142,7 @@ export default (flecks) => {
this.area = area; this.area = area;
} }
if (data) { if (data) {
const {buffer, byteOffset, length} = inflate(Buffer.from(data, 'base64')); this.$$data = this.constructor.inflate(data);
this.$$data = new Uint16Array(buffer, byteOffset, length / 2);
} }
else if (area) { else if (area) {
this.$$data = new Uint16Array(Vector.area(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() { get zIndex() {
return this.$$zIndex; return this.$$zIndex;
} }