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) {
const drawedChunks = world.chunks
.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;
function memoize(f) {
const memo = {};
if (chunk.buffer === undefined) {
return false;
function g(...args) {
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) => {
const zz = Math.floor(z / 16);
const xx = Math.floor(x / 16);
const azz = Math.floor(a.position.z / 16) - zz;
const axx = Math.floor(a.position.x / 16) - xx;
const bzz = Math.floor(b.position.z / 16) - zz;
const bxx = Math.floor(b.position.x / 16) - xx;
const da = Math.abs(azz) + Math.abs(axx);
const db = Math.abs(bzz) + Math.abs(bxx);
return db - da;
const d = (i, j) => {
const di = i - chunki;
const dj = j - chunkj;
return Math.sqrt(di * di + dj * dj);
};
return d(...b) - d(...a);
});
const buffers = drawedChunks
.map(chunk => chunk.buffer)
.concat(drawedChunks.map(chunk => chunk.transparentBuffer));
}
const getChunkOrder = memoize(_getChunkOrder);
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 => ({
position: [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 => {
if (e.type === 'keydown') {
if (e.repeat) return;
params.keys.add(e.code);
switch (e.code) {
case 'KeyF':
params.flying = !params.flying;
break;
return false;
case 'Space':
if (!params.flying) {
if (params.jumpAmount > 0) {
@ -322,7 +335,7 @@ export function initUiListeners(params, canvas) {
params.jumpAmount -= 1;
}
}
break;
return false;
}
} else {
params.keys.delete(e.code);
@ -355,13 +368,11 @@ export function initUiListeners(params, canvas) {
};
canvas.onclick = canvasClickHandler;
document.addEventListener('keydown', e => {
if (e.repeat) return;
switch (e.code) {
case 'F11':
canvas.requestFullscreen().then(() => {
console.log('full screen!!');
});
canvas.requestFullscreen();
break;
}
});
}

234
world.js
View File

@ -218,16 +218,9 @@ function isTransparent(type) {
}
}
function makeFaceList(data, chunkz, chunkx, blockLookup) {
const lookup = (i, j, k) => {
if (i < 0 || j < 0 || i > 15 || j > 15) {
return blockLookup(i, j, k);
}
if (k < 0 || k > 255) {
return BlockType.UNDEFINED;
}
return data[256*(16*i + j) + k];
};
function makeFaceList(chunk, lookup) {
const chunkz = chunk.position.z;
const chunkx = chunk.position.x;
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] },
@ -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] },
];
const solidFaces = [];
const transparentFaces = [];
for (let i = 0; i < 16; i++) {
for (let j = 0; j < 16; j++) {
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,
});
}
function* blocks() {
for (let i = 0; i < 16; i++) {
for (let j = 0; j < 16; j++) {
for (let k = 0; k < 256; k++) {
yield [i, j, k];
}
}
}
}
return {
solidFaces,
transparentFaces,
};
chunk.faces = [];
chunk.transparentFaces = [];
return makeFaces(chunk, blocks, neighbors);
}
// - 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?
// --> could also render chunks 1 by 1
const undefinedChunk = {data: new Proxy({}, {
get: () => BlockType.UNDEFINED,
})};
class ChunkMap {
map = {};
@ -295,7 +264,8 @@ class ChunkMap {
}
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) {
@ -312,6 +282,109 @@ export function makeWorld() {
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. */
export function generateMissingChunks(world, z, x, timeLimit = 10000) {
const ic = Math.floor(z / 16);
@ -320,46 +393,27 @@ export function generateMissingChunks(world, z, x, timeLimit = 10000) {
const start = performance.now();
for (let radius = 1; radius < 8; radius++) {
for (let i = ic - radius; i < ic + radius; i++) {
for (let j = jc - radius; j < jc + radius; j++) {
if (world.chunkMap.has(i, j)) {
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) {
return;
}
const chunk = makeChunk(i * 16, j * 16);
world.chunks.push(chunk);
world.chunkMap.set(i, j, chunk);
updateMissingFaces(world, chunk);
}
}
}
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) {
const face = createChunkFace(block, dir);
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);
if (block.type === BlockType.WATER && dir === '+y') {
center[1] -= 0.15;
@ -398,23 +452,31 @@ export function updateWorldGeometry(gl, world, z, x, timeLimit = 10000) {
continue;
}
if(chunk.faces === undefined) {
const chunkz = 16 * i;
const chunkx = 16 * j;
const lookup = (i, j, k) => blockLookup(world, j + chunkx, k, i + chunkz).type;
if (radius > 2 && performance.now() - start > timeLimit) {
return;
}
const faces = makeFaceList(chunk.data, chunk.position.z, chunk.position.x, lookup);
chunk.faces = faces.solidFaces;
chunk.transparentFaces = faces.transparentFaces;
if(chunk.faces === undefined) {
const neighborChunks = [
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.transparentBuffer = makeBufferFromFaces(gl, chunk.transparentFaces.map(f => f.face));
// throttle this for fluidity
if (radius > 2 && performance.now() - start > timeLimit) {
return;
}
}
}