feat: tile layer chunking
This commit is contained in:
parent
41baef2571
commit
3d4d29625d
|
@ -1,3 +1,5 @@
|
|||
export const CHUNK_SIZE = 32;
|
||||
|
||||
export const CLIENT_LATENCY = 0;
|
||||
|
||||
export const CLIENT_PREDICTION = true;
|
||||
|
|
|
@ -1,24 +1,35 @@
|
|||
import {CHUNK_SIZE} from '@/constants.js';
|
||||
import Component from '@/ecs/component.js';
|
||||
import {floodwalk2D, ortho, removeCollinear} from '@/util/math.js';
|
||||
|
||||
import vector2d from './helpers/vector-2d';
|
||||
|
||||
class LayerProxy {
|
||||
$$sourceJson;
|
||||
$$chunks = [];
|
||||
$$sourceJson = {};
|
||||
constructor(instance, Component, index) {
|
||||
this.instance = instance;
|
||||
this.Component = Component;
|
||||
this.index = index;
|
||||
this.$$chunks = Array(
|
||||
Math.ceil(this.area.x / CHUNK_SIZE) * Math.ceil(this.area.y / CHUNK_SIZE)
|
||||
).fill(0).map(() => ({}));
|
||||
}
|
||||
get area() {
|
||||
return this.layer.area;
|
||||
}
|
||||
clone() {
|
||||
const {$$sourceJson} = this.instance.$$layersProxies[this.index];
|
||||
const {$$chunks, $$sourceJson} = this.instance.$$layersProxies[this.index];
|
||||
const proxy = new LayerProxy(this.instance, this.Component, this.index);
|
||||
proxy.$$chunks = [...$$chunks];
|
||||
proxy.$$sourceJson = $$sourceJson;
|
||||
return proxy;
|
||||
|
||||
}
|
||||
compute(index) {
|
||||
const cx = Math.floor((index % this.area.x) / CHUNK_SIZE);
|
||||
const cy = Math.floor(Math.floor(index / this.area.x) / CHUNK_SIZE);
|
||||
const ax = Math.ceil(this.area.x / CHUNK_SIZE);
|
||||
return cy * ax + cx;
|
||||
}
|
||||
get data() {
|
||||
return this.layer.data;
|
||||
|
@ -126,22 +137,66 @@ class LayerProxy {
|
|||
}
|
||||
|
||||
export default class TileLayers extends Component {
|
||||
async insertMany(entities) {
|
||||
for (const [id, {layerChange}] of entities) {
|
||||
async createMany(entities) {
|
||||
for (const [, {layerChange, layers}] of entities) {
|
||||
if (layers) {
|
||||
for (const layer of layers) {
|
||||
const area = layer.area.x * layer.area.y;
|
||||
if (layer.data.length < area) {
|
||||
for (let i = 0; i < area; ++i) {
|
||||
if (!layer.data[i]) {
|
||||
layer.data[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (layerChange) {
|
||||
const component = this.get(id);
|
||||
const {layers} = component;
|
||||
for (const layerIndex in layerChange) {
|
||||
for (const calculated in layerChange[layerIndex]) {
|
||||
const tile = layerChange[layerIndex][calculated];
|
||||
layers[layerIndex].data[calculated] = tile;
|
||||
}
|
||||
layers[layerIndex] = {...layers[layerIndex]};
|
||||
component.$$layersProxies[layerIndex] = component.$$layersProxies[layerIndex].clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.insertMany(entities);
|
||||
return super.createMany(entities);
|
||||
}
|
||||
async insertMany(entities) {
|
||||
for (const [, {layers}] of entities) {
|
||||
if (layers) {
|
||||
for (const layer of layers) {
|
||||
const area = layer.area.x * layer.area.y;
|
||||
if (layer.data.length < area) {
|
||||
for (let i = 0; i < area; ++i) {
|
||||
if (!layer.data[i]) {
|
||||
layer.data[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await super.insertMany(entities);
|
||||
for (const [id, {layerChange}] of entities) {
|
||||
if (layerChange) {
|
||||
const component = this.get(id);
|
||||
const {layers} = component;
|
||||
for (const layerIndex in layerChange) {
|
||||
const proxy = component.$$layersProxies[layerIndex].clone();
|
||||
const chunksChanged = new Set();
|
||||
for (const tileIndex in layerChange[layerIndex]) {
|
||||
chunksChanged.add(proxy.compute(tileIndex));
|
||||
const tile = layerChange[layerIndex][tileIndex];
|
||||
layers[layerIndex].data[tileIndex] = tile;
|
||||
}
|
||||
for (const chunkChanged of chunksChanged) {
|
||||
proxy.$$chunks[chunkChanged] = {};
|
||||
}
|
||||
component.$$layersProxies[layerIndex] = proxy;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
instanceFromSchema() {
|
||||
return class TileLayersInstance extends super.instanceFromSchema() {
|
||||
|
|
106
app/engine.js
106
app/engine.js
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
CHUNK_SIZE,
|
||||
RESOLUTION,
|
||||
TPS,
|
||||
} from '@/constants.js';
|
||||
|
@ -92,7 +93,10 @@ export default class Engine {
|
|||
// remove entity link to connection to start queueing actions and pause updates
|
||||
delete connectedPlayer.entity;
|
||||
// forget previous state
|
||||
connectedPlayer.memory.clear();
|
||||
connectedPlayer.memory = {
|
||||
chunks: new Map(),
|
||||
nearby: new Set(),
|
||||
};
|
||||
// inform client of the upcoming change
|
||||
server.send(
|
||||
connection,
|
||||
|
@ -214,7 +218,10 @@ export default class Engine {
|
|||
{
|
||||
entity: ecs.get(entity),
|
||||
id,
|
||||
memory: new Set(),
|
||||
memory: {
|
||||
chunks: new Map(),
|
||||
nearby: new Set(),
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -359,12 +366,14 @@ export default class Engine {
|
|||
y1: y0 + (RESOLUTION.y * 2),
|
||||
});
|
||||
// Master entity.
|
||||
nearby.add(ecs.get(1));
|
||||
const lastMemory = new Set(memory.values());
|
||||
const master = ecs.get(1);
|
||||
nearby.add(master);
|
||||
const lastNearby = new Set(memory.nearby.values());
|
||||
const firstUpdate = 0 === lastNearby.size;
|
||||
for (const entity of nearby) {
|
||||
const {id} = entity;
|
||||
lastMemory.delete(id);
|
||||
if (!memory.has(id)) {
|
||||
lastNearby.delete(id);
|
||||
if (!memory.nearby.has(id)) {
|
||||
update[id] = entity.toJSON();
|
||||
if (mainEntityId === id) {
|
||||
update[id].MainEntity = {};
|
||||
|
@ -373,12 +382,91 @@ export default class Engine {
|
|||
else if (ecs.diff[id]) {
|
||||
update[id] = ecs.diff[id];
|
||||
}
|
||||
memory.add(id);
|
||||
memory.nearby.add(id);
|
||||
}
|
||||
for (const id of lastMemory) {
|
||||
memory.delete(id);
|
||||
for (const id of lastNearby) {
|
||||
memory.nearby.delete(id);
|
||||
update[id] = false;
|
||||
}
|
||||
// Tile layer chunking
|
||||
const {TileLayers} = master;
|
||||
const {layers} = TileLayers;
|
||||
let layerChange;
|
||||
for (const i in layers) {
|
||||
const layer = TileLayers.layer(i);
|
||||
const cx = CHUNK_SIZE * layer.tileSize.x;
|
||||
const cy = CHUNK_SIZE * layer.tileSize.y;
|
||||
const rx = 1 + Math.ceil((RESOLUTION.x * 2) / cx);
|
||||
const ry = 1 + Math.ceil((RESOLUTION.y * 2) / cy);
|
||||
const lx = Math.floor((entity.Position.x - RESOLUTION.x) / cx);
|
||||
const ly = Math.floor((entity.Position.y - RESOLUTION.y) / cy);
|
||||
const ax = Math.ceil(layer.area.x / CHUNK_SIZE);
|
||||
for (let wy = 0; wy < ry; ++wy) {
|
||||
for (let wx = 0; wx < rx; ++wx) {
|
||||
const iy = wy + ly;
|
||||
const ix = wx + lx;
|
||||
if (
|
||||
ix >= 0
|
||||
&& iy >= 0
|
||||
&& ix < Math.ceil(layer.area.x / CHUNK_SIZE)
|
||||
&& iy < Math.ceil(layer.area.y / CHUNK_SIZE)
|
||||
) {
|
||||
const chunk = iy * ax + ix;
|
||||
if (!memory.chunks.has(i)) {
|
||||
memory.chunks.set(i, new Set());
|
||||
}
|
||||
if (!memory.chunks.get(i).has(chunk)) {
|
||||
memory.chunks.get(i).add(chunk);
|
||||
if (!layerChange) {
|
||||
layerChange = {};
|
||||
}
|
||||
if (!layerChange[i]) {
|
||||
layerChange[i] = {};
|
||||
}
|
||||
for (let y = 0; y < CHUNK_SIZE; ++y) {
|
||||
for (let x = 0; x < CHUNK_SIZE; ++x) {
|
||||
const ty = (iy * CHUNK_SIZE) + y;
|
||||
const tx = (ix * CHUNK_SIZE) + x;
|
||||
if (
|
||||
tx < 0
|
||||
|| ty < 0
|
||||
|| tx >= layers[i].area.x
|
||||
|| ty >= layers[i].area.y
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const computed = ((iy * CHUNK_SIZE) + y) * layers[i].area.x + ((ix * CHUNK_SIZE) + x);
|
||||
layerChange[i][computed] = layers[i].data[computed];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (firstUpdate && update['1']) {
|
||||
const {TileLayers} = update['1'];
|
||||
if (TileLayers) {
|
||||
const layersUpdate = [];
|
||||
const {layers} = TileLayers;
|
||||
for (const l in layers) {
|
||||
layersUpdate[l] = {
|
||||
...layers[l],
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
update['1'].TileLayers = {layers: layersUpdate};
|
||||
}
|
||||
}
|
||||
if (layerChange) {
|
||||
if (!update['1']) {
|
||||
update['1'] = {};
|
||||
}
|
||||
if (!update['1'].TileLayers) {
|
||||
update['1'].TileLayers = {};
|
||||
}
|
||||
update['1'].TileLayers.layerChange = layerChange;
|
||||
}
|
||||
return update;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,42 +3,64 @@ import {PixiComponent} from '@pixi/react';
|
|||
import '@pixi/spritesheet'; // NECESSARY!
|
||||
import {CompositeTilemap} from '@pixi/tilemap';
|
||||
|
||||
import {CHUNK_SIZE} from '@/constants.js';
|
||||
import {useAsset} from '@/context/assets.js';
|
||||
|
||||
const TileLayerInternal = PixiComponent('TileLayer', {
|
||||
create: () => {
|
||||
create: ({tileLayer}) => {
|
||||
const container = new Container();
|
||||
container.addChild(new CompositeTilemap());
|
||||
const cy = Math.ceil(tileLayer.area.y / CHUNK_SIZE);
|
||||
const cx = Math.ceil(tileLayer.area.x / CHUNK_SIZE);
|
||||
for (let iy = 0; iy < cy; ++iy) {
|
||||
for (let ix = 0; ix < cx; ++ix) {
|
||||
const tilemap = new CompositeTilemap();
|
||||
tilemap.x = tileLayer.tileSize.x * CHUNK_SIZE * ix;
|
||||
tilemap.y = tileLayer.tileSize.y * CHUNK_SIZE * iy;
|
||||
container.addChild(tilemap);
|
||||
}
|
||||
}
|
||||
return container;
|
||||
},
|
||||
applyProps: (container, {mask: oldMask, tileLayer: oldTileLayer}, props) => {
|
||||
const {asset, mask, tileLayer} = props;
|
||||
applyProps: (container, {tileLayer: oldTileLayer}, props) => {
|
||||
const {asset, tileLayer} = props;
|
||||
const extless = tileLayer.source.slice('/assets/'.length, -'.json'.length);
|
||||
const {textures} = asset;
|
||||
if (tileLayer === oldTileLayer) {
|
||||
return;
|
||||
}
|
||||
if (oldMask) {
|
||||
container.removeChildAt(1);
|
||||
container.mask = undefined;
|
||||
}
|
||||
if (mask) {
|
||||
container.addChild(mask);
|
||||
container.mask = mask;
|
||||
}
|
||||
const tilemap = container.children[0];
|
||||
tilemap.clear();
|
||||
let i = 0;
|
||||
for (let y = 0; y < tileLayer.area.y; ++y) {
|
||||
for (let x = 0; x < tileLayer.area.x; ++x) {
|
||||
tilemap.tile(textures[`${extless}/${tileLayer.data[i++]}`], tileLayer.tileSize.x * x, tileLayer.tileSize.y * y);
|
||||
for (const i in tileLayer.$$chunks) {
|
||||
if (!oldTileLayer || oldTileLayer.$$chunks[i] !== tileLayer.$$chunks[i]) {
|
||||
const tilemap = container.children[i];
|
||||
tilemap.clear();
|
||||
const ax = Math.ceil(tileLayer.area.x / CHUNK_SIZE);
|
||||
const cy = Math.floor(i / ax);
|
||||
const cx = i % ax;
|
||||
for (let y = 0; y < CHUNK_SIZE; ++y) {
|
||||
for (let x = 0; x < CHUNK_SIZE; ++x) {
|
||||
const ty = (cy * CHUNK_SIZE) + y;
|
||||
const tx = (cx * CHUNK_SIZE) + x;
|
||||
if (
|
||||
tx < 0
|
||||
|| ty < 0
|
||||
|| tx >= tileLayer.area.x
|
||||
|| ty >= tileLayer.area.y
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
tilemap.tile(
|
||||
textures[`${extless}/${tileLayer.data[ty * tileLayer.area.x + tx]}`],
|
||||
tileLayer.tileSize.x * x,
|
||||
tileLayer.tileSize.y * y,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
export default function TileLayer(props) {
|
||||
const {mask, tileLayer} = props;
|
||||
const {tileLayer} = props;
|
||||
const asset = useAsset(tileLayer.source);
|
||||
if (!asset) {
|
||||
return false;
|
||||
|
@ -48,7 +70,6 @@ export default function TileLayer(props) {
|
|||
<TileLayerInternal
|
||||
{...props}
|
||||
asset={asset}
|
||||
mask={mask}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue
Block a user