takahirox / tiny-web-metaverse

A web-based 3D virtual space lightweight framework with high flexibility, extensibility, and easy hosting, built on ECS architecture
MIT License
118 stars 13 forks source link

Introduce proper entity removal mechanism #71

Closed takahirox closed 7 months ago

takahirox commented 8 months ago

We use proxy style to handle non-number data associated with a component.

// Add component somewhere
addComponent(world, Foo, eid);
FooProxy.get(eid).allocate(new foo());

// Remove component somewhere
removeComponent(world, Foo, eid);

// Release the associated resource in a system
const exitFooSystem = exitQuery(defineQuery([Foo]));

export const fooSystem = (world: IWorld): void => {
  exitFooSystem(world).forEach(eid => {
    const proxy = FooProxy.get(eid);
    proxy.foo.close();
    proxy.free();
  });
};

A problem is that (if I'm right) exit query doesn't work if entity is removed with removeEntity(). This unreleased resource problem can cause memory leak. Also it can cause a problem when an entity id is recycled.

It's nice if we can introduce a mechanism for entity removal with releasing associated components' data properly.

takahirox commented 8 months ago

An idea

// src/components/common.ts
export const REMOVAL_INTERVAL = 3;

// src/components/removal.ts
import { defineComponent, Types } from "bitecs";

export const EntityRemoval = defineComponent(
  interval: Types.ui8
);

// src/utils/bitecs.ts
import {
  addComponent,
  getEntityComponents,
  IWorld,
  removeComponent
} from "bitecs";
import { REMOVAL_INTERVAL } from "../common";
import { EntityRemoval } from "../components/removal";

export const removeComponentsAndEntity = (world: IWorld, eid: number): void => {
  for (const c of getEntityComponents(world, eid)) {
    removeComponent(world, c, eid);
  }
  addComponent(world, EntityRemoval, eid);
  EntityRemoval.interval[eid] = REMOVAL_INTERVAL;
};

// src/systems/remove_entity.ts
import {
  defineQuery,
  getEntityComponents,
  IWorld,
  removeEntity
} from "bitecs";
import { REMOVAL_INTERVAL } from "../common";
import { EntityRemoval } from "../components/removal";

const removalQuery = defineQuery([EntityRemoval]);

export const removeEntitySystem = (world: IWorld): void => {
  removalEntity(world).forEach(eid => {
    EntityRemoval.interval[eid]--;
    if (EntityRemoval.interval[eid] === 0) {
      if (getEntityComponents(world, eid).length === 1) {
        removeEntity(world, eid);
      } else {
        EntityRemoval.interval[eid] = REMOVAL_INTERVAL;
      }
    }
  });
};

// example
import { removeComponentsAndEntity } from "../utils/bitecs";

removeComponentsAndEntity(world, eid);
takahirox commented 7 months ago

Done