迭代为数组但仍可通过键访问
Iteration as array but still accessible by a key
我正在为简单的 2D 游戏编写自己的游戏引擎并希望迭代 children,但出于某种原因我想通过一个键访问每个项目。
也许有人知道我下面遇到的问题有什么好的解决办法吗?
问题 #1
我不能使用 Object.keys and for-in
,因为简单的数组迭代有 5 倍的性能提升。性能至关重要。
问题 #2
我想通过传递 child object 来轻松地 add/remove children 函数:
scene.add(child);
scene.remove(child);
解决方案 #1?
我可以用 children array 和 object 创建数据结构。使用 add/remove 方法同时填充数组和 object 。当然在改变children
属性的情况下,你会破坏这些东西,但我的情况不是这样,你必须使用add/remove。
实例
渲染。每个着色器程序都有 children 数组。
_render(...args) {
const [gl, scene, camera] = args;
const { childrenByShaderProgram } = scene;
const dt = 0;
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
camera.updateViewMatrix();
scene.beforeUpdate(dt);
Object.keys(childrenByShaderProgram).forEach(uuid => {
const children = childrenByShaderProgram[uuid];
const sp = children[0].shaderProgram;
// Per shader program rendering.
this._useShaderProgram(gl, sp);
// Update view matrix uniform value.
sp.updateUniform('u_v', camera.viewMatrix);
for (let j = 0, len = children.length; j < len; j += 1) {
const child = children[j];
// Update attributes and uniforms values.
scene.updateEachChild(child, dt);
// Apply changes by binding uniforms and attributes.
sp.bindUniforms(gl);
sp.bindAttributes(gl);
// tbd @andytyurin texture implementation should be here.
gl.drawArrays(gl.TRIANGLE_STRIP, 0, Math.floor(child.vertices.length / 2));
}
});
scene.afterUpdate(dt);
window.requestAnimationFrame(() => this._render(...args));
}
接下来会更难...scene.js
export class Scene {
constructor() {
this.childrenByShaderProgram = {};
}
add(child) {
const { children } = child;
if (children && children.legnth) {
// Container object.
for (let i = 0, l = children.length; i < l; i += 1) {
const nestedChild = children[0];
const nestedChildren = nestedChild.children;
// Children recursion.
if (nestedChildren && nestedChildren.length) {
this.add(nestedChild);
} else {
this._addChild(nestedChild);
}
}
} else {
this._addChild(child);
}
}
remove(child) {
const { children } = child;
if (children && children.legnth) {
// Container object.
for (let i = 0, l = children.length; i < l; i += 1) {
const nestedChild = children[0];
const nestedChildren = nestedChild.children;
// Children recursion.
if (nestedChildren && nestedChildren.length) {
this.remove(nestedChild);
} else {
this._removeChild(nestedChild);
}
}
} else {
this._removeChild(child);
}
}
_addChild(child) {
const spUuid = child.shaderProgram.uuid;
if (child.renderingIdx) {
throw new Error(
'Could not add child as it is already added to the scene'
);
}
this.childrenByShaderProgram[spUuid] =
this.childrenByShaderProgram[spUuid] || [];
child.renderingIdx = this.childrenByShaderProgram[spUuid].length;
this.childrenByShaderProgram[spUuid].push(child);
}
_removeChild(child) {
const spUuid = child.shaderProgram.uuid;
const { renderingIdx } = child;
if (!renderingIdx) {
throw new Error(
'Could not remove child which has not been added to the scene'
);
}
const shaderProgramChildren = this.childrenByShaderProgram[spUuid];
const lenMinusOne = shaderProgramChildren.length - 1;
if (renderingIdx === 0) {
this.childrenByShaderProgram[spUuid] = shaderProgramChildren.slice(1);
} else if (renderingIdx === lenMinusOne) {
this.childrenByShaderProgram[spUuid] = shaderProgramChildren.slice(
0,
lenMinusOne
);
} else {
this.childrenByShaderProgram[spUuid] = [
...shaderProgramChildren.slice(0, renderingIdx),
...shaderProgramChildren.slice(renderingIdx + 1)
];
}
}
beforeUpdate(children, dt) {}
updateEachChild(child, dt) {
// Make appropriate calculations of matrices.
child.update();
}
afterUpdate(children, dt) {}
}
export default Scene;
在示例中,我使用 renderingIdx
更快地从数组中删除 child,但我不想在每个 child 中保留任何属性。因此,作为替代方案,我可以将 children 保留为两个变体,如 key-value 和 Array。它将在渲染时提供相同的性能以及添加和删除 children from/to 场景的相同性能。
谢谢!
您提出的解决方案就是可行的方法。为了跟踪密钥,最好编写一个包装器 class:
class LookupArray {
constructor(key, ...entries) {
this.key = key;
this.array = [];
this.hash = {};
this.push(...entries);
}
push(...entries) {
for(const entry of entries) {
this.hash[entry[this.key]] = entry;
this.array.push(entry);
}
return entry.length;
}
get(id) {
return this.hash[id] || this.array[id];
}
}
所以可以这样做:
const lookup = new LookupArray("length", "abcd", "defghi");
console.log(
lookup.get(0), // "abcd"
lookup.get(4), // "abcd"
);
for(const entry of lookup.array)
console.log(entry);
但我想您可以通过评论中概述的 Object.entries
以更少的内存实现类似的性能。
我正在为简单的 2D 游戏编写自己的游戏引擎并希望迭代 children,但出于某种原因我想通过一个键访问每个项目。
也许有人知道我下面遇到的问题有什么好的解决办法吗?
问题 #1
我不能使用 Object.keys and for-in
,因为简单的数组迭代有 5 倍的性能提升。性能至关重要。
问题 #2
我想通过传递 child object 来轻松地 add/remove children 函数:
scene.add(child);
scene.remove(child);
解决方案 #1?
我可以用 children array 和 object 创建数据结构。使用 add/remove 方法同时填充数组和 object 。当然在改变children
属性的情况下,你会破坏这些东西,但我的情况不是这样,你必须使用add/remove。
实例
渲染。每个着色器程序都有 children 数组。
_render(...args) {
const [gl, scene, camera] = args;
const { childrenByShaderProgram } = scene;
const dt = 0;
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
camera.updateViewMatrix();
scene.beforeUpdate(dt);
Object.keys(childrenByShaderProgram).forEach(uuid => {
const children = childrenByShaderProgram[uuid];
const sp = children[0].shaderProgram;
// Per shader program rendering.
this._useShaderProgram(gl, sp);
// Update view matrix uniform value.
sp.updateUniform('u_v', camera.viewMatrix);
for (let j = 0, len = children.length; j < len; j += 1) {
const child = children[j];
// Update attributes and uniforms values.
scene.updateEachChild(child, dt);
// Apply changes by binding uniforms and attributes.
sp.bindUniforms(gl);
sp.bindAttributes(gl);
// tbd @andytyurin texture implementation should be here.
gl.drawArrays(gl.TRIANGLE_STRIP, 0, Math.floor(child.vertices.length / 2));
}
});
scene.afterUpdate(dt);
window.requestAnimationFrame(() => this._render(...args));
}
接下来会更难...scene.js
export class Scene {
constructor() {
this.childrenByShaderProgram = {};
}
add(child) {
const { children } = child;
if (children && children.legnth) {
// Container object.
for (let i = 0, l = children.length; i < l; i += 1) {
const nestedChild = children[0];
const nestedChildren = nestedChild.children;
// Children recursion.
if (nestedChildren && nestedChildren.length) {
this.add(nestedChild);
} else {
this._addChild(nestedChild);
}
}
} else {
this._addChild(child);
}
}
remove(child) {
const { children } = child;
if (children && children.legnth) {
// Container object.
for (let i = 0, l = children.length; i < l; i += 1) {
const nestedChild = children[0];
const nestedChildren = nestedChild.children;
// Children recursion.
if (nestedChildren && nestedChildren.length) {
this.remove(nestedChild);
} else {
this._removeChild(nestedChild);
}
}
} else {
this._removeChild(child);
}
}
_addChild(child) {
const spUuid = child.shaderProgram.uuid;
if (child.renderingIdx) {
throw new Error(
'Could not add child as it is already added to the scene'
);
}
this.childrenByShaderProgram[spUuid] =
this.childrenByShaderProgram[spUuid] || [];
child.renderingIdx = this.childrenByShaderProgram[spUuid].length;
this.childrenByShaderProgram[spUuid].push(child);
}
_removeChild(child) {
const spUuid = child.shaderProgram.uuid;
const { renderingIdx } = child;
if (!renderingIdx) {
throw new Error(
'Could not remove child which has not been added to the scene'
);
}
const shaderProgramChildren = this.childrenByShaderProgram[spUuid];
const lenMinusOne = shaderProgramChildren.length - 1;
if (renderingIdx === 0) {
this.childrenByShaderProgram[spUuid] = shaderProgramChildren.slice(1);
} else if (renderingIdx === lenMinusOne) {
this.childrenByShaderProgram[spUuid] = shaderProgramChildren.slice(
0,
lenMinusOne
);
} else {
this.childrenByShaderProgram[spUuid] = [
...shaderProgramChildren.slice(0, renderingIdx),
...shaderProgramChildren.slice(renderingIdx + 1)
];
}
}
beforeUpdate(children, dt) {}
updateEachChild(child, dt) {
// Make appropriate calculations of matrices.
child.update();
}
afterUpdate(children, dt) {}
}
export default Scene;
在示例中,我使用 renderingIdx
更快地从数组中删除 child,但我不想在每个 child 中保留任何属性。因此,作为替代方案,我可以将 children 保留为两个变体,如 key-value 和 Array。它将在渲染时提供相同的性能以及添加和删除 children from/to 场景的相同性能。
谢谢!
您提出的解决方案就是可行的方法。为了跟踪密钥,最好编写一个包装器 class:
class LookupArray {
constructor(key, ...entries) {
this.key = key;
this.array = [];
this.hash = {};
this.push(...entries);
}
push(...entries) {
for(const entry of entries) {
this.hash[entry[this.key]] = entry;
this.array.push(entry);
}
return entry.length;
}
get(id) {
return this.hash[id] || this.array[id];
}
}
所以可以这样做:
const lookup = new LookupArray("length", "abcd", "defghi");
console.log(
lookup.get(0), // "abcd"
lookup.get(4), // "abcd"
);
for(const entry of lookup.array)
console.log(entry);
但我想您可以通过评论中概述的 Object.entries
以更少的内存实现类似的性能。