ecsy
Version:
Entity Component System in JS
255 lines (221 loc) • 7.66 kB
JavaScript
import Query from "./Query.js";
import { componentRegistered } from "./Utils.js";
export class System {
canExecute() {
if (this._mandatoryQueries.length === 0) return true;
for (let i = 0; i < this._mandatoryQueries.length; i++) {
var query = this._mandatoryQueries[i];
if (query.entities.length === 0) {
return false;
}
}
return true;
}
getName() {
return this.constructor.getName();
}
constructor(world, attributes) {
this.world = world;
this.enabled = true;
// @todo Better naming :)
this._queries = {};
this.queries = {};
this.priority = 0;
// Used for stats
this.executeTime = 0;
if (attributes && attributes.priority) {
this.priority = attributes.priority;
}
this._mandatoryQueries = [];
this.initialized = true;
if (this.constructor.queries) {
for (var queryName in this.constructor.queries) {
var queryConfig = this.constructor.queries[queryName];
var Components = queryConfig.components;
if (!Components || Components.length === 0) {
throw new Error("'components' attribute can't be empty in a query");
}
// Detect if the components have already been registered
let unregisteredComponents = Components.filter(
(Component) => !componentRegistered(Component)
);
if (unregisteredComponents.length > 0) {
throw new Error(
`Tried to create a query '${
this.constructor.name
}.${queryName}' with unregistered components: [${unregisteredComponents
.map((c) => c.getName())
.join(", ")}]`
);
}
var query = this.world.entityManager.queryComponents(Components);
this._queries[queryName] = query;
if (queryConfig.mandatory === true) {
this._mandatoryQueries.push(query);
}
this.queries[queryName] = {
results: query.entities,
};
// Reactive configuration added/removed/changed
var validEvents = ["added", "removed", "changed"];
const eventMapping = {
added: Query.prototype.ENTITY_ADDED,
removed: Query.prototype.ENTITY_REMOVED,
changed: Query.prototype.COMPONENT_CHANGED, // Query.prototype.ENTITY_CHANGED
};
if (queryConfig.listen) {
validEvents.forEach((eventName) => {
if (!this.execute) {
console.warn(
`System '${this.getName()}' has defined listen events (${validEvents.join(
", "
)}) for query '${queryName}' but it does not implement the 'execute' method.`
);
}
// Is the event enabled on this system's query?
if (queryConfig.listen[eventName]) {
let event = queryConfig.listen[eventName];
if (eventName === "changed") {
query.reactive = true;
if (event === true) {
// Any change on the entity from the components in the query
let eventList = (this.queries[queryName][eventName] = []);
query.eventDispatcher.addEventListener(
Query.prototype.COMPONENT_CHANGED,
(entity) => {
// Avoid duplicates
if (eventList.indexOf(entity) === -1) {
eventList.push(entity);
}
}
);
} else if (Array.isArray(event)) {
let eventList = (this.queries[queryName][eventName] = []);
query.eventDispatcher.addEventListener(
Query.prototype.COMPONENT_CHANGED,
(entity, changedComponent) => {
// Avoid duplicates
if (
event.indexOf(changedComponent.constructor) !== -1 &&
eventList.indexOf(entity) === -1
) {
eventList.push(entity);
}
}
);
} else {
/*
// Checking just specific components
let changedList = (this.queries[queryName][eventName] = {});
event.forEach(component => {
let eventList = (changedList[
componentPropertyName(component)
] = []);
query.eventDispatcher.addEventListener(
Query.prototype.COMPONENT_CHANGED,
(entity, changedComponent) => {
if (
changedComponent.constructor === component &&
eventList.indexOf(entity) === -1
) {
eventList.push(entity);
}
}
);
});
*/
}
} else {
let eventList = (this.queries[queryName][eventName] = []);
query.eventDispatcher.addEventListener(
eventMapping[eventName],
(entity) => {
// @fixme overhead?
if (eventList.indexOf(entity) === -1)
eventList.push(entity);
}
);
}
}
});
}
}
}
}
stop() {
this.executeTime = 0;
this.enabled = false;
}
play() {
this.enabled = true;
}
// @question rename to clear queues?
clearEvents() {
for (let queryName in this.queries) {
var query = this.queries[queryName];
if (query.added) {
query.added.length = 0;
}
if (query.removed) {
query.removed.length = 0;
}
if (query.changed) {
if (Array.isArray(query.changed)) {
query.changed.length = 0;
} else {
for (let name in query.changed) {
query.changed[name].length = 0;
}
}
}
}
}
toJSON() {
var json = {
name: this.getName(),
enabled: this.enabled,
executeTime: this.executeTime,
priority: this.priority,
queries: {},
};
if (this.constructor.queries) {
var queries = this.constructor.queries;
for (let queryName in queries) {
let query = this.queries[queryName];
let queryDefinition = queries[queryName];
let jsonQuery = (json.queries[queryName] = {
key: this._queries[queryName].key,
});
jsonQuery.mandatory = queryDefinition.mandatory === true;
jsonQuery.reactive =
queryDefinition.listen &&
(queryDefinition.listen.added === true ||
queryDefinition.listen.removed === true ||
queryDefinition.listen.changed === true ||
Array.isArray(queryDefinition.listen.changed));
if (jsonQuery.reactive) {
jsonQuery.listen = {};
const methods = ["added", "removed", "changed"];
methods.forEach((method) => {
if (query[method]) {
jsonQuery.listen[method] = {
entities: query[method].length,
};
}
});
}
}
}
return json;
}
}
System.isSystem = true;
System.getName = function () {
return this.displayName || this.name;
};
export function Not(Component) {
return {
operator: "not",
Component: Component,
};
}