Minor performance refactor

- turns out the old makeFaceList was probably a bit faster
This commit is contained in:
Paul Mathieu 2021-12-26 05:00:18 -08:00
parent a38c7f7a76
commit c5370bf057
2 changed files with 189 additions and 116 deletions

71
game.js
View File

@ -123,34 +123,46 @@ function handleKeys(params) {
}); });
} }
function getObjects(world, z, x, glContext) { function memoize(f) {
const drawedChunks = world.chunks const memo = {};
.filter(chunk => {
if (chunk.position.z < z - 8 * 16) return false;
if (chunk.position.z > z + 7 * 16) return false;
if (chunk.position.x < x - 8 * 16) return false;
if (chunk.position.x > x + 7 * 16) return false;
if (chunk.buffer === undefined) { function g(...args) {
return false; if (!(args in memo)) {
memo[args] = f(...args);
}
return memo[args];
}
return g;
}
function _getChunkOrder(chunki, chunkj) {
return [...function* () {
for (let i = chunki - 8; i < chunki + 8; i++) {
for (let j = chunkj - 8; j < chunkj + 8; j++) {
yield [i, j];
} }
}
return true; }()]
})
.sort((a, b) => { .sort((a, b) => {
const zz = Math.floor(z / 16); const d = (i, j) => {
const xx = Math.floor(x / 16); const di = i - chunki;
const azz = Math.floor(a.position.z / 16) - zz; const dj = j - chunkj;
const axx = Math.floor(a.position.x / 16) - xx; return Math.sqrt(di * di + dj * dj);
const bzz = Math.floor(b.position.z / 16) - zz; };
const bxx = Math.floor(b.position.x / 16) - xx; return d(...b) - d(...a);
const da = Math.abs(azz) + Math.abs(axx);
const db = Math.abs(bzz) + Math.abs(bxx);
return db - da;
}); });
const buffers = drawedChunks }
.map(chunk => chunk.buffer) const getChunkOrder = memoize(_getChunkOrder);
.concat(drawedChunks.map(chunk => chunk.transparentBuffer));
function getObjects(world, z, x, glContext) {
const chunki = Math.floor(z / 16);
const chunkj = Math.floor(x / 16);
const chunks = getChunkOrder(chunki, chunkj)
.map(([i, j]) => world.chunkMap.get(i, j))
.filter(chunk => chunk.buffer !== undefined);
const buffers = chunks.map(chunk => chunk.buffer)
.concat(chunks.map(chunk => chunk.transparentBuffer));
return buffers.map(buffer => ({ return buffers.map(buffer => ({
position: [0.0, 0.0, 0.0], position: [0.0, 0.0, 0.0],
orientation: [0.0, 0.0, 0.0], orientation: [0.0, 0.0, 0.0],
@ -308,12 +320,13 @@ export function initUiListeners(params, canvas) {
}; };
const keyListener = e => { const keyListener = e => {
if (e.type === 'keydown') { if (e.type === 'keydown') {
if (e.repeat) return;
params.keys.add(e.code); params.keys.add(e.code);
switch (e.code) { switch (e.code) {
case 'KeyF': case 'KeyF':
params.flying = !params.flying; params.flying = !params.flying;
break; return false;
case 'Space': case 'Space':
if (!params.flying) { if (!params.flying) {
if (params.jumpAmount > 0) { if (params.jumpAmount > 0) {
@ -322,7 +335,7 @@ export function initUiListeners(params, canvas) {
params.jumpAmount -= 1; params.jumpAmount -= 1;
} }
} }
break; return false;
} }
} else { } else {
params.keys.delete(e.code); params.keys.delete(e.code);
@ -355,13 +368,11 @@ export function initUiListeners(params, canvas) {
}; };
canvas.onclick = canvasClickHandler; canvas.onclick = canvasClickHandler;
document.addEventListener('keydown', e => { document.addEventListener('keydown', e => {
if (e.repeat) return;
switch (e.code) { switch (e.code) {
case 'F11': case 'F11':
canvas.requestFullscreen().then(() => { canvas.requestFullscreen();
console.log('full screen!!');
});
break; break;
} }
}); });
} }

234
world.js
View File

@ -218,16 +218,9 @@ function isTransparent(type) {
} }
} }
function makeFaceList(data, chunkz, chunkx, blockLookup) { function makeFaceList(chunk, lookup) {
const lookup = (i, j, k) => { const chunkz = chunk.position.z;
if (i < 0 || j < 0 || i > 15 || j > 15) { const chunkx = chunk.position.x;
return blockLookup(i, j, k);
}
if (k < 0 || k > 255) {
return BlockType.UNDEFINED;
}
return data[256*(16*i + j) + k];
};
const neighbors = (i, j, k) => [ const neighbors = (i, j, k) => [
{ block: lookup(i - 1, j, k), dir: '-z', faceCenter: [chunkx + j, k, chunkz + i - 0.5] }, { block: lookup(i - 1, j, k), dir: '-z', faceCenter: [chunkx + j, k, chunkz + i - 0.5] },
{ block: lookup(i + 1, j, k), dir: '+z', faceCenter: [chunkx + j, k, chunkz + i + 0.5] }, { block: lookup(i + 1, j, k), dir: '+z', faceCenter: [chunkx + j, k, chunkz + i + 0.5] },
@ -237,48 +230,20 @@ function makeFaceList(data, chunkz, chunkx, blockLookup) {
{ block: lookup(i, j, k + 1), dir: '+y', faceCenter: [chunkx + j, k + 0.5, chunkz + i] }, { block: lookup(i, j, k + 1), dir: '+y', faceCenter: [chunkx + j, k + 0.5, chunkz + i] },
]; ];
const solidFaces = []; function* blocks() {
const transparentFaces = []; for (let i = 0; i < 16; i++) {
for (let j = 0; j < 16; j++) {
for (let i = 0; i < 16; i++) { for (let k = 0; k < 256; k++) {
for (let j = 0; j < 16; j++) { yield [i, j, k];
let bi = i * 16 * 256 + j * 256;
for (let k = 0; k < 256; k++, bi++) {
if (data[bi] === BlockType.AIR) {
continue;
}
if (isTransparent(data[bi])) {
for (const { dir, faceCenter } of neighbors(i, j, k)
.filter(({ block }) => block === BlockType.AIR)
) {
if (data[bi] === BlockType.WATER) {
faceCenter[1] -= 0.15;
}
transparentFaces.push({
dir,
face: makeFace(dir, faceTexture(data[bi], dir), faceCenter),
blockIndex: bi,
});
}
} else {
for (const { dir, faceCenter } of neighbors(i, j, k)
.filter(({ block }) => isTransparent(block))
) {
solidFaces.push({
dir,
face: makeFace(dir, faceTexture(data[bi], dir), faceCenter),
blockIndex: bi,
});
}
} }
} }
} }
} }
return { chunk.faces = [];
solidFaces, chunk.transparentFaces = [];
transparentFaces,
}; return makeFaces(chunk, blocks, neighbors);
} }
// - data <-- need to generate the first time, update when m&p // - data <-- need to generate the first time, update when m&p
@ -286,6 +251,10 @@ function makeFaceList(data, chunkz, chunkx, blockLookup) {
// - buffers <-- need to generate every time geometry changes? // - buffers <-- need to generate every time geometry changes?
// --> could also render chunks 1 by 1 // --> could also render chunks 1 by 1
const undefinedChunk = {data: new Proxy({}, {
get: () => BlockType.UNDEFINED,
})};
class ChunkMap { class ChunkMap {
map = {}; map = {};
@ -295,7 +264,8 @@ class ChunkMap {
} }
get(i, j) { get(i, j) {
return this.map[this.key(i, j)]; const out = this.map[this.key(i, j)];
return out ?? undefinedChunk;
} }
set(i, j, x) { set(i, j, x) {
@ -312,6 +282,109 @@ export function makeWorld() {
return {chunks: [], chunkMap: new ChunkMap()}; return {chunks: [], chunkMap: new ChunkMap()};
} }
function makeFaces(chunk, blocks, neighbors) {
if (chunk === undefined || chunk.faces === undefined) {
return;
}
let geometryChanged = false;
for (const [i, j, k] of blocks()) {
const bi = 256 * (16 * i + j) + k;
if (chunk.data[bi] === BlockType.AIR) {
continue;
}
if (isTransparent(chunk.data[bi])) {
for (const { dir, faceCenter } of neighbors(i, j, k)
.filter(({ block }) => block === BlockType.AIR)
) {
if (chunk.data[bi] === BlockType.WATER) {
faceCenter[1] -= 0.15;
}
chunk.transparentFaces.push({
dir,
face: makeFace(dir, faceTexture(chunk.data[bi], dir), faceCenter),
blockIndex: bi,
});
geometryChanged = true;
}
} else {
for (const { dir, faceCenter } of neighbors(i, j, k)
.filter(({ block }) => isTransparent(block))
) {
chunk.faces.push({
dir,
face: makeFace(dir, faceTexture(chunk.data[bi], dir), faceCenter),
blockIndex: bi,
});
geometryChanged = true;
}
}
}
if (geometryChanged && chunk.buffer !== undefined) {
chunk.buffer.delete();
delete chunk.buffer;
chunk.transparentBuffer.delete();
delete chunk.transparentBuffer;
}
}
function updateMissingFaces(world, chunk) {
const chunkz = chunk.position.z;
const chunkx = chunk.position.x;
const chunki = Math.floor(chunkz / 16);
const chunkj = Math.floor(chunkx / 16);
makeFaces(world.chunkMap.get(chunki - 1, chunkj),
function* () {
for (let j = 0; j < 16; j++) {
for (let k = 0; k < 256; k++) {
yield [15, j, k];
}
}
}, (i, j, k) => [{
block: chunk.data[256 * (16 * 0 + j) + k],
dir: '+z',
faceCenter: [chunkx + j, k, chunkz - 0.5],
}]);
makeFaces(world.chunkMap.get(chunki + 1, chunkj),
function* () {
for (let j = 0; j < 16; j++) {
for (let k = 0; k < 256; k++) {
yield [0, j, k];
}
}
}, (i, j, k) => [{
block: chunk.data[256 * (15 * 16 + j) + k],
dir: '-z',
faceCenter: [chunkx + j, k, chunkz + 16 - 0.5],
}]);
makeFaces(world.chunkMap.get(chunki, chunkj - 1),
function* () {
for (let i = 0; i < 16; i++) {
for (let k = 0; k < 256; k++) {
yield [i, 15, k];
}
}
}, (i, j, k) => [{
block: chunk.data[256 * (16 * i + 0) + k],
dir: '+x',
faceCenter: [chunkx - 0.5, k, chunkz + i],
}]);
makeFaces(world.chunkMap.get(chunki, chunkj + 1),
function* () {
for (let i = 0; i < 16; i++) {
for (let k = 0; k < 256; k++) {
yield [i, 0, k];
}
}
}, (i, j, k) => [{
block: chunk.data[256 * (16 * i + 15) + k],
dir: '-x',
faceCenter: [chunkx + 16 - 0.5, k, chunkz + i],
}]);
}
/** Update the world, generating missing chunks if necessary. */ /** Update the world, generating missing chunks if necessary. */
export function generateMissingChunks(world, z, x, timeLimit = 10000) { export function generateMissingChunks(world, z, x, timeLimit = 10000) {
const ic = Math.floor(z / 16); const ic = Math.floor(z / 16);
@ -320,46 +393,27 @@ export function generateMissingChunks(world, z, x, timeLimit = 10000) {
const start = performance.now(); const start = performance.now();
for (let radius = 1; radius < 8; radius++) { for (let radius = 1; radius < 8; radius++) {
for (let i = ic - radius; i < ic + radius; i++) { for (let i = ic - radius; i < ic + radius; i++) {
for (let j = jc - radius; j < jc + radius; j++) { for (let j = jc - radius; j < jc + radius; j++) {
if (world.chunkMap.has(i, j)) { if (world.chunkMap.has(i, j)) {
continue; continue;
} }
const chunk = makeChunk(i * 16, j * 16);
world.chunks.push(chunk);
world.chunkMap.set(i, j, chunk);
invalidateChunkGeometry(world, i - 1, j);
invalidateChunkGeometry(world, i + 1, j);
invalidateChunkGeometry(world, i, j - 1);
invalidateChunkGeometry(world, i, j + 1);
if (radius > 2 && performance.now() - start > timeLimit) { if (radius > 2 && performance.now() - start > timeLimit) {
return; return;
} }
const chunk = makeChunk(i * 16, j * 16);
world.chunks.push(chunk);
world.chunkMap.set(i, j, chunk);
updateMissingFaces(world, chunk);
} }
} }
} }
return world; return world;
} }
function invalidateChunkGeometry(world, i, j) {
const chunk = world.chunkMap.get(i, j);
if (chunk === undefined) {
return;
}
if (chunk.faces === undefined) {
return;
}
delete chunk.faces;
if (chunk.buffer === undefined) {
return;
}
chunk.buffer.delete();
delete chunk.buffer;
}
function addFace(chunk, block, dir) { function addFace(chunk, block, dir) {
const face = createChunkFace(block, dir); const face = createChunkFace(block, dir);
if (isTransparent(block.type)) { if (isTransparent(block.type)) {
@ -369,7 +423,7 @@ function addFace(chunk, block, dir) {
} }
} }
export function createChunkFace(block, dir, center = null) { function createChunkFace(block, dir, center = null) {
center = center ?? faceCenter(block.centerPosition, dir); center = center ?? faceCenter(block.centerPosition, dir);
if (block.type === BlockType.WATER && dir === '+y') { if (block.type === BlockType.WATER && dir === '+y') {
center[1] -= 0.15; center[1] -= 0.15;
@ -398,23 +452,31 @@ export function updateWorldGeometry(gl, world, z, x, timeLimit = 10000) {
continue; continue;
} }
if(chunk.faces === undefined) { if (radius > 2 && performance.now() - start > timeLimit) {
const chunkz = 16 * i; return;
const chunkx = 16 * j; }
const lookup = (i, j, k) => blockLookup(world, j + chunkx, k, i + chunkz).type;
const faces = makeFaceList(chunk.data, chunk.position.z, chunk.position.x, lookup); if(chunk.faces === undefined) {
chunk.faces = faces.solidFaces; const neighborChunks = [
chunk.transparentFaces = faces.transparentFaces; world.chunkMap.get(i - 1, j),
world.chunkMap.get(i + 1, j),
world.chunkMap.get(i, j - 1),
world.chunkMap.get(i, j + 1),
];
const lookup = (i, j, k) => {
if (i < 0) { return neighborChunks[0].data[256 * (16 * (i + 16) + j) + k]; }
if (i > 15) { return neighborChunks[1].data[256 * (16 * (i - 16) + j) + k]; }
if (j < 0) { return neighborChunks[2].data[256 * (16 * i + j + 16) + k]; }
if (j > 15) { return neighborChunks[3].data[256 * (16 * i + j - 16) + k]; }
if (k < 0 || k > 255) { return BlockType.UNDEFINED; }
return chunk.data[256*(16*i + j) + k];
};
makeFaceList(chunk, lookup);
} }
chunk.buffer = makeBufferFromFaces(gl, chunk.faces.map(f => f.face)); chunk.buffer = makeBufferFromFaces(gl, chunk.faces.map(f => f.face));
chunk.transparentBuffer = makeBufferFromFaces(gl, chunk.transparentFaces.map(f => f.face)); chunk.transparentBuffer = makeBufferFromFaces(gl, chunk.transparentFaces.map(f => f.face));
// throttle this for fluidity
if (radius > 2 && performance.now() - start > timeLimit) {
return;
}
} }
} }