Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 28 additions & 25 deletions src/component.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import { Property } from './property.js';
import { Constructor, DataComponentClass, Option, PropertiesOf } from './types';

export enum ComponentState {
None = 0,
Added = 1,
Ready = 2,
Removed = 3
}
import { Constructor, DataComponentClass, Nullable, Option, PropertiesOf } from './types';
import { World } from './world.js';

/**
* Base class for a component.
Expand All @@ -22,27 +16,28 @@ export abstract class Component {
/** `true` if the object instance derives from [[Component]] */
public readonly isComponent!: true;

/** @hidden */
public _world: Nullable<World>;

/**
* @hidden
*/
public _state: ComponentState;
public _pooled: boolean;

/**
* @hidden
*/
public _pooled: boolean;
protected _version: number;

public constructor() {
Object.defineProperty(this, 'isComponent', { value: true });
this._state = ComponentState.None;
this._world = null;
this._pooled = false;
this._version = 0;
}

/**
* This is useless for now.
*/
get state(): ComponentState {
return this._state;
public update(): this {
return this;
}

/**
Expand All @@ -52,6 +47,14 @@ export abstract class Component {
get pooled(): boolean {
return this._pooled;
}

/**
* Returns the version of the component, i.e., the number related to the
* last time it's been updated
*/
get version(): number {
return this._version;
}
}

/**
Expand Down Expand Up @@ -153,6 +156,13 @@ export class ComponentData extends Component {
return new (this.constructor as Constructor<this>)().copy(this);
}

public update(): this {
if (this._world) {
this._version = this._world.version + 1;
}
return this;
}

/**
* Initiliazes the component with its default properties, overriden by
* the `source`
Expand Down Expand Up @@ -183,16 +193,9 @@ export class ComponentData extends Component {
Class !== ComponentData
);

return this;
}
}
this._version = 0;

// @todo: up to one component per world on a dummy entity.
export class SingletonComponent extends ComponentData {
public readonly isSingletonComponent!: true;
public constructor() {
super();
Object.defineProperty(this, 'isSingletonComponent', { value: true });
return this;
}
}

Expand Down
11 changes: 9 additions & 2 deletions src/entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,11 @@ export class Entity {
* @return The component instance if found, `undefined` otherwise
*/
public write<T extends Component>(Class: ComponentClass<T>): Option<T> {
// @todo: retrieve component in write mode
return this._components.get(Class) as Option<T>;
const comp = this._components.get(Class);
if (comp) {
return comp.update() as Option<T>;
}
return comp;
}

/**
Expand All @@ -155,6 +158,10 @@ export class Entity {
return this._components.size === 0;
}

public get world(): World {
return this._world;
}

/**
* Returns an array of all component classes stored in this entity
*
Expand Down
5 changes: 2 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
export {
Component,
ComponentData,
TagComponent,
SingletonComponent
TagComponent
} from './component.js';
export { Entity } from './entity.js';
export { Query, Not } from './query.js';
export { Query, Not, Added, Updated } from './query.js';
export { System } from './system.js';
export { SystemGroup } from './system-group.js';
export { World } from './world.js';
Expand Down
5 changes: 2 additions & 3 deletions src/internals/component-manager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, ComponentState, ComponentData } from '../component.js';
import { Component, ComponentData } from '../component.js';
import { Entity } from '../entity.js';
import { ObjectPool } from '../pool.js';
import { World } from '../world.js';
Expand Down Expand Up @@ -79,7 +79,7 @@ export class ComponentManager<WorldType extends World> {
if ((comp as ComponentData).isDataComponent && opts) {
(comp as ComponentData).init(opts);
}
comp._state = ComponentState.Ready;
comp._world = this._world;
// @todo: check in dev mode for duplicate.
entity._components.set(Class, comp);
this.updateArchetype(entity, Class);
Expand Down Expand Up @@ -172,7 +172,6 @@ export class ComponentManager<WorldType extends World> {
component: Component
): void {
const Class = component.constructor as ComponentClass;
component._state = ComponentState.None;
if (component.pooled) {
this._data.get(Class)!.pool?.release(component);
}
Expand Down
61 changes: 54 additions & 7 deletions src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@ import { Archetype } from './internals/archetype.js';
import { ComponentClass, Option } from './types';

enum QueryComponentOperatorKind {
Not = 'not'
Not = 'not',
Added = 'added',
Updated = 'updated'
}

export function Not(Class: ComponentClass) {
export function Not(Class: ComponentClass): ComponentOperator {
return { Class, kind: QueryComponentOperatorKind.Not, isOperator: true };
}

export function Added(Class: ComponentClass): ComponentOperator {
return { Class, kind: QueryComponentOperatorKind.Added, isOperator: true };
}

export function Updated(Class: ComponentClass): ComponentOperator {
return { Class, kind: QueryComponentOperatorKind.Updated, isOperator: true };
}

/**
* Queries allow to retrieve entities based on the component they have.
*
Expand All @@ -35,20 +45,53 @@ export class Query<E extends Entity = Entity> {
/** @hidden */
private _notClasses: ComponentClass[];

/** @hidden */
private _addedClasses: ComponentClass[];

/** @hidden */
private _updatedClasses: ComponentClass[];

public constructor(hash: string, components: QueryComponents) {
this._hash = hash;
this._archetypes = [];
this._classes = [];
this._notClasses = [];
this._addedClasses = [];
this._updatedClasses = [];
for (const comp of components) {
if ((comp as ComponentOperator).isOperator) {
this._notClasses.push((comp as ComponentOperator).Class);
} else {
if (!(comp as ComponentOperator).isOperator) {
this._classes.push(comp as ComponentClass);
continue;
}
const Class = (comp as ComponentOperator).Class;
switch ((comp as ComponentOperator).kind) {
case QueryComponentOperatorKind.Not:
this._notClasses.push(Class);
break;
case QueryComponentOperatorKind.Added:
this._addedClasses.push(Class);
break;
case QueryComponentOperatorKind.Updated:
this._updatedClasses.push(Class);
break;
}
}
}

public isEntityUpdated(entity: E): boolean {
const modified = this._updatedClasses;
if (modified.length === 0) {
return true;
}
const version = entity.world.version;
for (const Class of modified) {
if (entity.read(Class)!.version <= version) {
return false;
}
}
return true;
}

/**
* Executes the callback on all entities matching this query
*
Expand All @@ -59,7 +102,10 @@ export class Query<E extends Entity = Entity> {
for (let archId = archetypes.length - 1; archId >= 0; --archId) {
const entities = archetypes[archId].entities;
for (let entityId = entities.length - 1; entityId >= 0; --entityId) {
cb(entities[entityId]);
const entity = entities[entityId];
if (this.isEntityUpdated(entity)) {
cb(entity);
}
}
}
}
Expand All @@ -75,7 +121,8 @@ export class Query<E extends Entity = Entity> {
for (let archId = archetypes.length - 1; archId >= 0; --archId) {
const entities = archetypes[archId].entities;
for (let entityId = entities.length - 1; entityId >= 0; --entityId) {
if (cb(entities[entityId])) {
const entity = entities[entityId];
if (this.isEntityUpdated(entity) && cb(entity)) {
return;
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/world.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ export class World<E extends Entity = Entity> {
/** @hidden */
protected _entityPool: Nullable<EntityPool<this>>;

/** @hidden */
private _version: number;

/** Public API. */

public constructor(options: Partial<WorldOptions<E>> = {}) {
Expand Down Expand Up @@ -108,12 +111,15 @@ export class World<E extends Entity = Entity> {
) as EntityPool<this>;
}

this._version = 0;

for (const component of components) {
this.registerComponent(component);
}
for (const system of systems) {
this.register(system as SystemClass<System<this>>);
}

}

/**
Expand Down Expand Up @@ -208,6 +214,7 @@ export class World<E extends Entity = Entity> {
*/
public execute(delta: number): void {
this._systems.execute(delta);
++this._version;
}

/**
Expand Down Expand Up @@ -308,6 +315,10 @@ export class World<E extends Entity = Entity> {
return this._components.maxComponentTypeCount;
}

public get version(): number {
return this._version;
}

/** Internal API. */

/**
Expand Down