迭代作为数组,但仍可通过键访问。

3
我正在为简单的2D游戏编写自己的游戏引擎,并且想要迭代子元素,但出于某种原因,我想通过键访问每个项目。
也许有人知道下面问题的任何好的解决方案吗?
问题#1 我不能使用Object.keys和for-in,因为简单的数组迭代具有5倍的性能提升。性能至关重要。
问题#2 我希望通过将子对象传递给函数轻松添加/删除子项: scene.add(child); scene.remove(child);
解决方案#1? 我可以创建同时包含子数组和子对象的数据结构。使用add/remove方法同时填充数组和对象。当然,在更改children属性的情况下,您会破坏东西,但这不是我的情况,您必须使用add/remove。
真实例子 渲染。每个着色器程序都有子数组。
_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 来更快地从数组中删除子元素,但我不想在每个子元素中保留任何属性。因此,作为替代方案,我可以将子元素保存在两个变量中,即键值对数组。这样,在渲染时以及添加和删除子元素到/从场景中的性能都是相同的。
谢谢!

我刚想建议维护一个单独的对象键数组,但看起来这是你的第一个计划,我会尝试一下并比较它在性能方面与for..in的差异。 - IrkenInvader
@PatrickRoberts 相对于简单的 for 循环,forEach 的速度较慢。我不能使用 array.entries(),因为我需要使用键而不是索引。但是使用 object.entries() 的想法看起来很有趣,但仍需要比较性能。 - user4408933
@IrkenInvader 你说得对,这只是一个想法,但老实说我不喜欢这个解决方案。我已经比较了简单的 forforEach 以及 Object.keys(),但没有和 for-in 进行比较,我会尝试一下。谢谢。 - user4408933
1
@AndyTyurin Object.prototype.entries() 实际上已经从规范中删除了,不幸的是。你可以实现一个 polyfill,但它不会像你预期的那样高效。现在只有静态的 Object.entries() 可用,它创建一个键/值对数组,并且不是惰性的。 - Patrick Roberts
1
@PatrickRoberts 嗯,看起来使用 Object.keys 迭代并不远了。顺便说一下,我会尝试在主题中放置一些代码。 - user4408933
1
@Ninjaneer 抱歉,我搞错了,为什么我们需要将数组转换为对象? - user4408933
1个回答

3
你提出的解决方案是可行的。为了跟踪键,编写一个包装类可能是不错的选择:
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 来减少内存使用并实现类似的性能。


1
顺便说一下,使用非严格类型的回退(hash[id] 其中 id 应该是字符串,array[id] 其中 id 应该是索引)可能会防止函数进行优化。也许应该将其拆分为两个函数 getKey()getIndex(),因为调用代码永远不会混合使用这两种情况。 - Patrick Roberts
好主意,但它是如何工作的? this.hash[entry[this.key]] = entry; 因为我们使用 for-of 和字符串数组,所以 entry 应该是 String 类型。 - user4408933
看起来这与解决方案#1非常相似。 - user4408933
@PatrickRoberts 这更像是一个概念验证而不是最终版本,是的,我收到了通知...谢谢。 - Jonas Wilms
@AndyTyurin 可能使用对象会更有意义,但对于字符串来说,打字量较少 :) - Jonas Wilms
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接