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