Skip to content
This repository has been archived by the owner on Jun 1, 2022. It is now read-only.

Object Pooling and Component Managers #23

Closed
robertlong opened this issue Jun 24, 2019 · 1 comment
Closed

Object Pooling and Component Managers #23

robertlong opened this issue Jun 24, 2019 · 1 comment

Comments

@robertlong
Copy link
Member

During our internal hackathon I noticed that most people were running into some confusion around the entity.addComponent() API.

The current signature is this:

class World {
  registerComponent<C extends Component>(component: ComponentConstructor<C>): this
}

interface Component {
  copy(source: Component): this // Optional
}

class Entity {
  addComponent(component: ComponentConstructor, parameters: Component | {}): this
}

Where the parameters are copied to the instance of the component acquired from the object pool. In the case that it is an instance of Component with a copy() method it uses that otherwise it just does a shallow copy of the object.

I'd like to propose moving to this:

class World {
  registerComponent<C extends Component>(component: ComponentConstructor<C>, componentManager?: ComponentManager<C>): this
}

class ComponentManager<C extends Component> {
  components: C[]
  get(entityId: number): C
  add(entityId: number): C
  has(entityId: number): boolean
  remove(entityId: number): void
}

class Component<T extends Component> {
  clone(): T {
    return new this.constructor().copy(this);
  }
  copy(source: T): this {
    for (const key in source) {
      if (this.hasOwnProperty(key)) {
        const destValue = this[key];
        const srcValue = source[key];
        if (destValue && destValue.copy && srcValue && srcValue.copy) {
          destValue.copy(srcValue);
        } else {
          this[key] = source[key];
        }
      }
    }
  }
}

class Entity {
  addComponent<C extends Component>(component: ComponentConstructor<C>, instance?: C): C
}

The differences are the following:

  1. addComponent() does not implement pooling by default. I think most of us had issues with the default pooling behavior. The shallow copy doesn't work for non-value types like Matrix4, Vector3, etc. which produces undesirable results. I'll let others comment on their experiences with object pooling, but I think the Hubs team was generally in favor of making object pooling a higher level concept and not turned on by default.
  2. addComponent() returns the component instance. In my experience this is more common than adding multiple components to an entity which is why we currently return the instance of the entity rather than the component.
  3. registerComponent() takes an optional ComponentManager which allows you to implement pooling yourself on a per component basis. You can also store your components with different backing data structures such as Map or a BitSet or TypedArray. The default should probably just use a Map or Array I did some benchmarks in HECS that you can look at to determine the best default.
  4. Add clone() to Component. I think it's good to allow people to add arguments to the component constructor. However, this makes it tough to clone components in editors like Spoke. Adding the clone() method lets you specify those constructor parameters or override how the component is instantiated when cloned.
  5. Move Component from an interface to a class with default implementations of clone and copy. These implementations support ThreeJS classes such as Vector3, Matrix4, etc.
@fernandojsg fernandojsg added this to the v1 milestone Aug 7, 2019
@fernandojsg fernandojsg modified the milestones: v0, v1 Sep 17, 2019
@fernandojsg
Copy link
Member

Not relevant anymore after our last conversations

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants