miniplex
Version:
A developer-friendly entity management system for games and similarly demanding applications, based on ECS architecture.
225 lines (224 loc) • 9.35 kB
TypeScript
import { Bucket } from "@miniplex/bucket";
export * from "@miniplex/bucket";
export type Predicate<E, D extends E> = ((v: E) => v is D) | ((entity: E) => boolean);
/**
* A utility type that marks the specified properties as required.
*/
export type With<E, P extends keyof E> = E & Required<Pick<E, P>>;
export type Without<E, P extends keyof E> = Omit<E, P>;
/**
* A utility type that removes all optional properties.
*/
export type Strict<T> = WithoutOptional<T>;
type OptionalKeys<T> = {
[K in keyof T]-?: undefined extends T[K] ? K : never;
};
type WithoutOptional<T> = Pick<T, Exclude<keyof T, OptionalKeys<T>[keyof T]>>;
type QueryConfiguration = {
with: any[];
without: any[];
predicates: Function[];
};
interface IQueryableBucket<E> {
/**
* Queries for entities that have all of the given components. If this is called on
* an existing query, the query will be extended to include this new criterion.
*
* @param components The components to query for.
*/
with<C extends keyof E>(...components: C[]): Query<With<E, C>>;
/**
* Queries for entities that have none of the given components. If this is called on
* an existing query, the query will be extended to include this new criterion.
*
* @param components The components to query for.
*/
without<C extends keyof E>(...components: C[]): Query<Without<E, C>>;
/**
* Queries for entities that match the given predicate. If this is called on
* an existing query, the query will be extended to include this new criterion.
*
* @param predicate The predicate to query for.
*/
where<D extends E>(predicate: Predicate<E, D>): Query<D>;
}
export declare class World<E extends {} = any> extends Bucket<E> implements IQueryableBucket<E> {
constructor(entities?: E[]);
update(entity: E): E;
update<C extends keyof E>(entity: E, component: C, value: E[C]): E;
update(entity: E, update: Partial<E>): E;
update(entity: E, fun: (entity: E) => Partial<E> | void): E;
/**
* Adds a component to an entity. If the entity already has the component, the
* existing component will not be overwritten.
*
* After the component was added, the entity will be reindexed, causing it to be
* added to or removed from any queries depending on their criteria.
*
* @param entity The entity to modify.
* @param component The name of the component to add.
* @param value The value of the component to add.
*/
addComponent<C extends keyof E>(entity: E, component: C, value: E[C]): void;
/**
* Removes a component from an entity. If the entity does not have the component,
* this function does nothing.
*
* After the component was removed, the entity will be reindexed, causing it to be
* added to or removed from any queries depending on their criteria.
*
* @param entity The entity to modify.
* @param component The name of the component to remove.
*/
removeComponent(entity: E, component: keyof E): void;
protected queries: Set<Query<any>>;
/**
* Creates (or reuses) a query that matches the given configuration.
*
* @param config The query configuration.
* @returns A query that matches the given configuration.
*/
query<D>(config: QueryConfiguration): Query<D>;
/**
* Creates (or reuses) a query that holds entities that have all of the specified
* components.
*
* @param components One or more component names to query for.
* @returns A query that holds entities that have all of the given components.
*/
with<C extends keyof E>(...components: C[]): Query<With<E, C>>;
/**
* Creates (or reuses) a query that holds entities that do not have any of the
* specified components.
*
* @param components One or more component names to query for.
* @returns A query that holds entities that do not have any of the given components.
*/
without<C extends keyof E>(...components: C[]): Query<Without<E, C>>;
/**
* Creates (or reuses) a query that holds entities that match the given predicate.
* Please note that as soon as you are building queries that use predicates, you
* will need to explicitly reindex entities when their properties change.
*
* @param predicate The predicate that entities must match.
* @returns A query that holds entities that match the given predicate.
*/
where<D extends E>(predicate: Predicate<E, D>): Query<D>;
/**
* Reindexes the specified entity. This will iteratere over all registered queries
* and ask them to reevaluate the entity.
*
* If the `future` parameter is specified,
* it will be used in the evaluation instead of the entity itself. This is useful
* if you are about to perform a destructive change on the entity (like removing
* a component), but want emitted events to still have access to the unmodified entity
* before the change.
*
* @param entity The entity to reindex.
* @param future The entity that the entity will become in the future.
*/
reindex(entity: E, future?: E): void;
private entityToId;
private idToEntity;
private nextId;
/**
* Generate and return a numerical identifier for the given entity. The ID can later
* be used to retrieve the entity again through the `entity(id)` method.
*
* @param entity The entity to get the ID for.
* @returns An ID for the entity, or undefined if the entity is not in the world.
*/
id(entity: E): number | undefined;
/**
* Given an entity ID that was previously generated through the `id(entity)` function,
* returns the entity matching that ID, or undefined if no such entity exists.
*
* @param id The ID of the entity to retrieve.
* @returns The entity with the given ID, or undefined if no such entity exists.
*/
entity(id: number): E | undefined;
}
export declare class Query<E> extends Bucket<E> implements IQueryableBucket<E> {
world: World;
config: QueryConfiguration;
protected _isConnected: boolean;
/**
* True if this query is connected to the world, and will automatically
* re-evaluate when entities are added or removed.
*/
get isConnected(): boolean;
/**
* A unique, string-based key for this query, based on its configuration.
*/
readonly key: string;
constructor(world: World, config: QueryConfiguration);
/**
* An array containing all entities that match this query. For iteration, it
* is recommended to use the `for (const entity of query) {}` syntax instead.
*/
get entities(): E[];
[Symbol.iterator](): {
next: () => {
value: E;
done: boolean;
};
};
/**
* Connects this query to the world. While connected, it will automatically
* re-evaluate when entities are added or removed, and store those that match
* its query configuration.
*
* @returns The query object.
*/
connect(): this;
/**
* Disconnects this query from the world. This essentially stops the query from
* automatically receiving entities.
*/
disconnect(): this;
/**
* Returns a new query that extends this query and also matches entities that
* have all of the components specified.
*
* @param components The components that entities must have.
* @returns A new query representing the extended query configuration.
*/
with<C extends keyof E>(...components: C[]): Query<With<E, C>>;
/**
* Returns a new query that extends this query and also matches entities that
* have none of the components specified.
*
* @param components The components that entities must not have.
* @returns A new query representing the extended query configuration.
*/
without<C extends keyof E>(...components: C[]): Query<Without<E, C>>;
/**
* Returns a new query that extends this query and also matches entities that
* match the given predicate.
*
* @param predicate The predicate that entities must match.
* @returns A new query representing the extended query configuration.
*/
where<D extends E>(predicate: Predicate<E, D>): Query<D>;
/**
* Checks the given entity against this query's configuration, and returns
* true if the entity matches the query, false otherwise.
*
* @param entity The entity to check.
* @returns True if the entity matches this query, false otherwise.
*/
want(entity: E): boolean;
/**
* Evaluate the given entity against this query's configuration, and add or
* remove it from the query if necessary.
*
* If `future` is specified, the entity will be evaluated against that entity
* instead. This is useful for checking if an entity will match the query
* after some potentially destructive change has been made to it, before
* actually applying that change to the entity itself.
*
* @param entity The entity to evaluate.
* @param future The entity to evaluate against. If not specified, the entity will be evaluated against itself.
*/
evaluate(entity: any, future?: any): void;
}