elics
Version:
lightweight, flexible ECS for web games
935 lines (920 loc) • 37.7 kB
JavaScript
var Types;
(function (Types) {
Types["Int8"] = "Int8";
Types["Int16"] = "Int16";
Types["Entity"] = "Entity";
Types["Float32"] = "Float32";
Types["Float64"] = "Float64";
Types["Boolean"] = "Boolean";
Types["String"] = "String";
Types["Object"] = "Object";
Types["Vec2"] = "Vec2";
Types["Vec3"] = "Vec3";
Types["Vec4"] = "Vec4";
Types["Enum"] = "Enum";
})(Types || (Types = {}));
const TypedArrayMap = {
Int8: { arrayConstructor: Int8Array, length: 1 },
Int16: { arrayConstructor: Int16Array, length: 1 },
Entity: { arrayConstructor: Int32Array, length: 1 },
Float32: { arrayConstructor: Float32Array, length: 1 },
Float64: { arrayConstructor: Float64Array, length: 1 },
Boolean: { arrayConstructor: Uint8Array, length: 1 },
String: { arrayConstructor: Array, length: 1 },
Object: { arrayConstructor: Array, length: 1 },
Vec2: { arrayConstructor: Float32Array, length: 2 },
Vec3: { arrayConstructor: Float32Array, length: 3 },
Vec4: { arrayConstructor: Float32Array, length: 4 },
Enum: { arrayConstructor: Array, length: 1 },
};
let CHECKS_ON = true;
function toggleChecks(value) {
CHECKS_ON = value;
}
var ErrorMessages;
(function (ErrorMessages) {
ErrorMessages["TypeNotSupported"] = "Type not supported";
ErrorMessages["InvalidDefaultValue"] = "Invalid default value";
ErrorMessages["InvalidEnumValue"] = "Invalid enum value";
ErrorMessages["InvalidRangeValue"] = "Value out of range";
})(ErrorMessages || (ErrorMessages = {}));
function assertCondition(condition, message, object) {
if (CHECKS_ON && !condition) {
throw new Error(`${message}: ${object}`);
}
}
function assertValidEnumValue(value, enumObject, fieldName) {
if (!CHECKS_ON) {
return;
}
const enumValues = Object.values(enumObject);
if (!enumValues.includes(value)) {
throw new Error(`${ErrorMessages.InvalidEnumValue}: ${value} is not a valid value for enum ${fieldName}`);
}
}
function assertValidRangeValue(value, min, max, fieldName) {
if (!CHECKS_ON) {
return;
}
if (min !== undefined && value < min) {
throw new Error(`${ErrorMessages.InvalidRangeValue}: ${value} is below minimum ${min} for field ${fieldName}`);
}
if (max !== undefined && value > max) {
throw new Error(`${ErrorMessages.InvalidRangeValue}: ${value} is above maximum ${max} for field ${fieldName}`);
}
}
class BitSet {
constructor(init = 0) {
if (typeof init === 'number') {
this.words = new Uint32Array(1);
// treat number as initial lower 32-bit mask value
this.words[0] = init >>> 0;
}
else {
this.words = new Uint32Array(init.length);
this.words.set(init);
}
}
// Back-compat for tests referencing .bits
get bits() {
return this.words[0] >>> 0;
}
ensure(wordIndex) {
if (wordIndex < this.words.length)
return;
const nextLen = Math.max(this.words.length << 1, wordIndex + 1);
const n = new Uint32Array(nextLen);
n.set(this.words);
this.words = n;
}
set(bitIndex, value) {
const w = (bitIndex / 32) | 0;
const b = bitIndex & 31;
this.ensure(w);
const mask = 1 << b;
if (value)
this.words[w] |= mask;
else
this.words[w] &= ~mask;
}
or(other) {
var _a, _b;
const out = new BitSet(new Uint32Array(Math.max(this.words.length, other.words.length)));
const a = this.words, b = other.words, o = out.words;
const max = o.length;
for (let i = 0; i < max; i++) {
o[i] = ((_a = a[i]) !== null && _a !== void 0 ? _a : 0) | ((_b = b[i]) !== null && _b !== void 0 ? _b : 0);
}
return out;
}
and(other) {
var _a, _b;
const out = new BitSet(new Uint32Array(Math.max(this.words.length, other.words.length)));
const a = this.words, b = other.words, o = out.words;
const max = o.length;
for (let i = 0; i < max; i++) {
o[i] = ((_a = a[i]) !== null && _a !== void 0 ? _a : 0) & ((_b = b[i]) !== null && _b !== void 0 ? _b : 0);
}
return out;
}
andNot(other) {
var _a, _b;
const out = new BitSet(new Uint32Array(Math.max(this.words.length, other.words.length)));
const a = this.words, b = other.words, o = out.words;
const max = o.length;
for (let i = 0; i < max; i++) {
o[i] = ((_a = a[i]) !== null && _a !== void 0 ? _a : 0) & ~((_b = b[i]) !== null && _b !== void 0 ? _b : 0);
}
return out;
}
orInPlace(other) {
this.ensure(other.words.length - 1);
const a = this.words, b = other.words;
for (let i = 0; i < b.length; i++)
a[i] |= b[i];
}
andNotInPlace(other) {
const a = this.words, b = other.words;
const max = Math.min(a.length, b.length);
for (let i = 0; i < max; i++)
a[i] &= ~b[i];
}
equals(other) {
var _a, _b;
const max = Math.max(this.words.length, other.words.length);
for (let i = 0; i < max; i++) {
const a = (_a = this.words[i]) !== null && _a !== void 0 ? _a : 0;
const b = (_b = other.words[i]) !== null && _b !== void 0 ? _b : 0;
if (a !== b)
return false;
}
return true;
}
isEmpty() {
const w = this.words;
for (let i = 0; i < w.length; i++)
if (w[i] !== 0)
return false;
return true;
}
clear() {
this.words.fill(0);
}
toArray() {
const out = [];
const w = this.words;
for (let wi = 0; wi < w.length; wi++) {
let word = w[wi];
while (word) {
const t = word & -word; // lowbit
const bit = 31 - Math.clz32(t);
out.push((wi << 5) + bit);
word &= word - 1;
}
}
return out;
}
toString() {
if (this.words.length === 1)
return this.words[0].toString();
// Hex words from high->low to produce stable mask ids
let s = '';
for (let i = this.words.length - 1; i >= 0; i--) {
const hex = this.words[i].toString(16);
s += i === this.words.length - 1 ? hex : '-' + hex;
}
return s;
}
contains(other) {
var _a;
const a = this.words, b = other.words;
const n = b.length;
for (let i = 0; i < n; i++) {
const aw = ((_a = a[i]) !== null && _a !== void 0 ? _a : 0) >>> 0;
const bw = b[i] >>> 0;
if ((aw & bw) >>> 0 !== bw)
return false;
}
return true;
}
intersects(other) {
const a = this.words, b = other.words;
const n = Math.min(a.length, b.length);
for (let i = 0; i < n; i++)
if ((a[i] & b[i]) >>> 0 !== 0)
return true;
return false;
}
}
class Entity {
constructor(entityManager, queryManager, componentManager, index) {
this.entityManager = entityManager;
this.queryManager = queryManager;
this.componentManager = componentManager;
this.index = index;
this.bitmask = new BitSet();
this.active = true;
this.vectorViews = new Map();
}
addComponent(component, initialData = {}) {
if (!this.active) {
console.warn(`Entity ${this.index} is destroyed, cannot add component ${component.schema}`);
}
else {
if (component.bitmask === null) {
this.componentManager.registerComponent(component);
}
this.bitmask.orInPlace(component.bitmask);
this.componentManager.attachComponentToEntity(this.index, component, initialData);
this.queryManager.updateEntity(this, component);
}
return this;
}
removeComponent(component) {
if (!this.active) {
console.warn(`Entity ${this.index} is destroyed, cannot remove component ${component.schema}`);
}
else if (component.bitmask) {
this.bitmask.andNotInPlace(component.bitmask);
this.vectorViews.delete(component);
this.queryManager.updateEntity(this, component);
}
return this;
}
hasComponent(component) {
return this.bitmask.intersects(component.bitmask);
}
getComponents() {
const bitArray = this.bitmask.toArray();
return bitArray.map((typeId) => this.componentManager.getComponentByTypeId(typeId));
}
getValue(component, key) {
var _a;
// allow runtime access with invalid keys, return null
const schemaEntry = component.schema[key];
if (!schemaEntry) {
return null;
}
const data = (_a = component.data[key]) === null || _a === void 0 ? void 0 : _a[this.index];
const type = schemaEntry.type;
switch (type) {
case Types.Boolean:
return Boolean(data);
case Types.Entity:
return data === -1
? null
: this.entityManager.getEntityByIndex(data);
default:
return data;
}
}
setValue(component, key, value) {
const componentData = component.data[key];
const schemaField = component.schema[key];
const type = schemaField.type;
switch (type) {
case Types.Enum:
// enum property is guaranteed to exist due to initialization validation
assertValidEnumValue(value, schemaField.enum, key);
componentData[this.index] = value;
break;
case Types.Int8:
case Types.Int16:
case Types.Float32:
case Types.Float64:
// For numeric types, validate range constraints if present
if ('min' in schemaField || 'max' in schemaField) {
assertValidRangeValue(value, schemaField.min, schemaField.max, key);
}
componentData[this.index] = value;
break;
case Types.Entity:
componentData[this.index] =
value === null ? -1 : value.index;
break;
default:
componentData[this.index] = value;
break;
}
// Notify only queries that depend on this component's values
this.queryManager.updateEntityValue(this, component);
}
getVectorView(component, key) {
var _a;
const keyStr = key;
const cachedVectorView = (_a = this.vectorViews.get(component)) === null || _a === void 0 ? void 0 : _a.get(keyStr);
if (cachedVectorView) {
return cachedVectorView;
}
else {
const componentData = component.data[key];
const type = component.schema[key].type;
const length = TypedArrayMap[type].length;
const offset = this.index * length;
const vectorView = componentData.subarray(offset, offset + length);
if (!this.vectorViews.has(component)) {
this.vectorViews.set(component, new Map());
}
this.vectorViews.get(component).set(keyStr, vectorView);
return vectorView;
}
}
destroy() {
if (this.active) {
this.active = false;
this.bitmask.clear();
this.vectorViews.clear();
this.queryManager.resetEntity(this);
this.entityManager.releaseEntityInstance(this);
}
}
}
class ComponentRegistry {
static record(component) {
if (this.components.has(component.id)) {
throw new Error(`Component with id '${component.id}' already exists. Each component must have a unique identifier.`);
}
this.components.set(component.id, component);
}
static getById(id) {
return this.components.get(id);
}
static getAllComponents() {
return Array.from(this.components.values());
}
static has(id) {
return this.components.has(id);
}
static clear() {
this.components.clear();
}
}
ComponentRegistry.components = new Map();
function createComponent(id, schema, description) {
const component = {
id,
description,
schema,
data: {},
bitmask: null,
typeId: -1,
};
ComponentRegistry.record(component);
return component;
}
function initializeComponentStorage(component, entityCapacity) {
const s = component.schema;
component.data = {};
for (const key in s) {
const schemaField = s[key];
const { type, default: defaultValue } = schemaField;
let { arrayConstructor, length } = TypedArrayMap[type];
// For Enum types, validate enum property exists
if (type === Types.Enum) {
assertCondition('enum' in schemaField, ErrorMessages.InvalidDefaultValue, `Enum type requires 'enum' property for field ${key}`);
}
assertCondition(!!arrayConstructor, ErrorMessages.TypeNotSupported, type);
component.data[key] = new arrayConstructor(entityCapacity * length);
assertCondition(length === 1 ||
(Array.isArray(defaultValue) && defaultValue.length === length), ErrorMessages.InvalidDefaultValue, key);
}
}
function assignInitialComponentData(component, index, initialData) {
var _a;
const s = component.schema;
for (const key in s) {
const schemaField = s[key];
const { type, default: defaultValue } = schemaField;
const length = TypedArrayMap[type].length;
const dataRef = component.data[key];
const input = (_a = initialData[key]) !== null && _a !== void 0 ? _a : defaultValue;
switch (type) {
case Types.Entity:
dataRef[index] = input ? input.index : -1;
break;
case Types.Enum:
assertValidEnumValue(input, schemaField.enum, key);
dataRef[index] = input;
break;
case Types.Int8:
case Types.Int16:
case Types.Float32:
case Types.Float64:
// For numeric types, validate range constraints if present
if ('min' in schemaField || 'max' in schemaField) {
assertValidRangeValue(input, schemaField.min, schemaField.max, key);
}
dataRef[index] = input;
break;
case Types.String:
case Types.Object:
dataRef[index] = input;
break;
default:
if (length === 1) {
dataRef[index] = input;
}
else {
dataRef.set(input, index * length);
}
break;
}
}
}
class ComponentManager {
constructor(entityCapacity) {
this.entityCapacity = entityCapacity;
this.nextComponentTypeId = 0;
this.componentsByTypeId = [];
}
hasComponent(component) {
return (component.typeId !== -1 &&
this.componentsByTypeId[component.typeId] === component);
}
registerComponent(component) {
if (this.hasComponent(component)) {
return;
}
const typeId = this.nextComponentTypeId++;
component.bitmask = new BitSet();
component.bitmask.set(typeId, 1);
component.typeId = typeId;
initializeComponentStorage(component, this.entityCapacity);
this.componentsByTypeId[typeId] = component;
}
attachComponentToEntity(entityIndex, component, initialData) {
assignInitialComponentData(component, entityIndex, initialData);
}
getComponentByTypeId(typeId) {
var _a;
return (_a = this.componentsByTypeId[typeId]) !== null && _a !== void 0 ? _a : null;
}
}
class EntityManager {
constructor(queryManager, componentManager, entityReleaseCallback) {
this.queryManager = queryManager;
this.componentManager = componentManager;
this.entityReleaseCallback = entityReleaseCallback;
this.pool = [];
this.entityIndex = 0;
this.indexLookup = [];
this.poolSize = 0;
}
requestEntityInstance() {
let entity;
if (this.poolSize > 0) {
entity = this.pool[--this.poolSize];
entity.active = true;
}
else {
entity = new Entity(this, this.queryManager, this.componentManager, this.entityIndex++);
}
this.indexLookup[entity.index] = entity;
return entity;
}
releaseEntityInstance(entity) {
var _a;
(_a = this.entityReleaseCallback) === null || _a === void 0 ? void 0 : _a.call(this, entity);
this.indexLookup[entity.index] = null;
this.pool[this.poolSize++] = entity;
}
getEntityByIndex(index) {
var _a;
if (index === -1) {
return null;
}
return (_a = this.indexLookup[index]) !== null && _a !== void 0 ? _a : null;
}
}
class Query {
constructor(requiredMask, excludedMask, queryId, valuePredicates = []) {
this.requiredMask = requiredMask;
this.excludedMask = excludedMask;
this.queryId = queryId;
this.subscribers = {
qualify: new Set(),
disqualify: new Set(),
};
this.entities = new Set();
this.valuePredicates = [];
this.valuePredicates = valuePredicates;
}
matches(entity) {
// Excluded: if any excluded bit is present on entity -> no match
if (!this.excludedMask.isEmpty() &&
this.excludedMask.intersects(entity.bitmask)) {
return false;
}
// Required: entity must contain all required bits
if (!entity.bitmask.contains(this.requiredMask))
return false;
// Value predicates: verify values
if (this.valuePredicates.length > 0) {
for (const p of this.valuePredicates) {
const v = entity.getValue(p.component, p.key);
switch (p.op) {
case 'eq':
if (v !== p.value)
return false;
break;
case 'ne':
if (v === p.value)
return false;
break;
case 'lt':
if (!(typeof v === 'number' &&
typeof p.value === 'number' &&
v < p.value))
return false;
break;
case 'le':
if (!(typeof v === 'number' &&
typeof p.value === 'number' &&
v <= p.value))
return false;
break;
case 'gt':
if (!(typeof v === 'number' &&
typeof p.value === 'number' &&
v > p.value))
return false;
break;
case 'ge':
if (!(typeof v === 'number' &&
typeof p.value === 'number' &&
v >= p.value))
return false;
break;
case 'in':
if (!p.valueSet || !p.valueSet.has(v))
return false;
break;
case 'nin':
if (p.valueSet && p.valueSet.has(v))
return false;
break;
}
}
}
return true;
}
subscribe(event, callback) {
this.subscribers[event].add(callback);
return () => {
this.subscribers[event].delete(callback);
};
}
static generateQueryInfo(queryConfig) {
var _a;
const requiredMask = new BitSet();
const excludedMask = new BitSet();
for (const c of queryConfig.required) {
requiredMask.orInPlace(c.bitmask);
}
if (queryConfig.excluded) {
for (const c of queryConfig.excluded) {
excludedMask.orInPlace(c.bitmask);
}
}
// Normalize and validate predicates
const rawPreds = (_a = queryConfig.where) !== null && _a !== void 0 ? _a : [];
const preds = rawPreds.map((p) => {
const schema = p.component.schema[p.key];
if (!schema) {
throw new Error(`Predicate key '${p.key}' not found on component ${p.component.id}`);
}
const t = schema.type;
// Validate operator applicability
switch (p.op) {
case 'lt':
case 'le':
case 'gt':
case 'ge':
if (!(t === Types.Int8 ||
t === Types.Int16 ||
t === Types.Float32 ||
t === Types.Float64)) {
throw new Error(`Operator '${p.op}' only valid for numeric fields: ${p.component.id}.${p.key}`);
}
break;
case 'in':
case 'nin':
if (!Array.isArray(p.value)) {
throw new Error(`Operator '${p.op}' requires array value for ${p.component.id}.${p.key}`);
}
break;
}
const np = {
...p,
};
if (p.op === 'in' || p.op === 'nin') {
np.valueSet = new Set(p.value);
}
return np;
});
for (const p of preds)
requiredMask.orInPlace(p.component.bitmask);
const whereStr = preds
.map((p) => `${p.component.typeId}:${p.key}:${p.op}=${Array.isArray(p.value) ? 'arr' : String(p.value)}`)
.join(',');
return {
requiredMask,
excludedMask,
queryId: `required:${requiredMask.toString()}|excluded:${excludedMask.toString()}|where:${whereStr}`,
valuePredicates: preds,
};
}
}
class QueryManager {
constructor(componentManager) {
this.componentManager = componentManager;
this.queries = new Map();
this.trackedEntities = new Set();
this.queriesByComponent = new Map();
this.queriesByValueComponent = new Map();
}
registerQuery(query) {
var _a, _b, _c;
for (const component of query.required) {
if (component.bitmask === null) {
this.componentManager.registerComponent(component);
}
}
if (query.excluded) {
for (const component of query.excluded) {
if (component.bitmask === null) {
this.componentManager.registerComponent(component);
}
}
}
if (query.where) {
for (const p of query.where) {
if (p.component.bitmask === null) {
this.componentManager.registerComponent(p.component);
}
}
}
const { requiredMask, excludedMask, queryId, valuePredicates } = Query.generateQueryInfo(query);
if (!this.queries.has(queryId)) {
const newQuery = new Query(requiredMask, excludedMask, queryId, valuePredicates);
for (const entity of this.trackedEntities) {
if (newQuery.matches(entity)) {
newQuery.entities.add(entity);
}
}
const comps = [
...query.required,
...((_a = query.excluded) !== null && _a !== void 0 ? _a : []),
...((_b = query.where) !== null && _b !== void 0 ? _b : []).map((p) => p.component),
];
for (const c of comps) {
let set = this.queriesByComponent.get(c);
if (!set) {
set = new Set();
this.queriesByComponent.set(c, set);
}
set.add(newQuery);
}
// Track value-predicate components separately for efficient value updates
for (const p of (_c = query.where) !== null && _c !== void 0 ? _c : []) {
let set = this.queriesByValueComponent.get(p.component);
if (!set) {
set = new Set();
this.queriesByValueComponent.set(p.component, set);
}
set.add(newQuery);
}
this.queries.set(queryId, newQuery);
}
return this.queries.get(queryId);
}
updateEntity(entity, changedComponent) {
var _a;
this.trackedEntities.add(entity);
if (entity.bitmask.isEmpty()) {
for (const query of this.queries.values()) {
if (query.entities.delete(entity)) {
for (const cb of query.subscribers.disqualify) {
cb(entity);
}
}
}
return;
}
const queries = changedComponent
? ((_a = this.queriesByComponent.get(changedComponent)) !== null && _a !== void 0 ? _a : [])
: this.queries.values();
for (const query of queries) {
const matches = query.matches(entity);
const inSet = query.entities.has(entity);
if (matches && !inSet) {
query.entities.add(entity);
for (const cb of query.subscribers.qualify) {
cb(entity);
}
}
else if (!matches && inSet) {
query.entities.delete(entity);
for (const cb of query.subscribers.disqualify) {
cb(entity);
}
}
}
}
// Optimized path for value changes: only check queries that have value predicates on this component
updateEntityValue(entity, component) {
this.trackedEntities.add(entity);
const queries = this.queriesByValueComponent.get(component);
if (!queries || queries.size === 0) {
return;
}
for (const query of queries) {
const matches = query.matches(entity);
const inSet = query.entities.has(entity);
if (matches && !inSet) {
query.entities.add(entity);
for (const cb of query.subscribers.qualify) {
cb(entity);
}
}
else if (!matches && inSet) {
query.entities.delete(entity);
for (const cb of query.subscribers.disqualify) {
cb(entity);
}
}
}
}
resetEntity(entity) {
this.trackedEntities.delete(entity);
if (entity.bitmask.isEmpty()) {
for (const query of this.queries.values()) {
if (query.entities.delete(entity)) {
for (const cb of query.subscribers.disqualify) {
cb(entity);
}
}
}
return;
}
const processed = new Set();
for (const i of entity.bitmask.toArray()) {
const component = this.componentManager.getComponentByTypeId(i);
const qs = this.queriesByComponent.get(component);
if (!qs) {
continue;
}
for (const q of qs) {
if (processed.has(q)) {
continue;
}
if (q.entities.delete(entity))
for (const cb of q.subscribers.disqualify)
cb(entity);
processed.add(q);
}
}
}
}
class World {
constructor({ entityCapacity = 1000, checksOn = true, entityReleaseCallback, } = {}) {
this.systems = [];
this.globals = {};
this.componentManager = new ComponentManager(entityCapacity);
this.queryManager = new QueryManager(this.componentManager);
this.entityManager = new EntityManager(this.queryManager, this.componentManager, entityReleaseCallback);
toggleChecks(checksOn);
}
registerComponent(component) {
this.componentManager.registerComponent(component);
return this;
}
hasComponent(component) {
return this.componentManager.hasComponent(component);
}
createEntity() {
return this.entityManager.requestEntityInstance();
}
registerSystem(systemClass, options = {}) {
if (this.hasSystem(systemClass)) {
console.warn(`System ${systemClass.name} is already registered, skipping registration.`);
return this;
}
const { configData = {}, priority = 0, } = options;
const queries = {};
Object.entries(systemClass.queries).forEach(([queryName, queryConfig]) => {
queries[queryName] =
this.queryManager.registerQuery(queryConfig);
});
const systemInstance = new systemClass(this, this.queryManager, priority);
systemInstance.queries = queries;
Object.keys(configData).forEach((key) => {
if (key in systemInstance.config) {
systemInstance.config[key].value = configData[key];
}
});
systemInstance.init();
// Determine the correct position for the new system based on priority
const insertIndex = this.systems.findIndex((s) => s.priority > systemInstance.priority);
if (insertIndex === -1) {
this.systems.push(systemInstance);
}
else {
this.systems.splice(insertIndex, 0, systemInstance);
}
return this;
}
unregisterSystem(systemClass) {
const systemInstance = this.getSystem(systemClass);
if (systemInstance) {
systemInstance.destroy();
this.systems = this.systems.filter((system) => !(system instanceof systemClass));
}
}
registerQuery(queryConfig) {
this.queryManager.registerQuery(queryConfig);
return this;
}
update(delta, time) {
for (const system of this.systems) {
if (!system.isPaused) {
system.update(delta, time);
}
}
}
getSystem(systemClass) {
for (const system of this.systems) {
if (system instanceof systemClass) {
return system;
}
}
return undefined;
}
getSystems() {
return this.systems;
}
hasSystem(systemClass) {
return this.systems.some((system) => system instanceof systemClass);
}
}
const i=Symbol.for("preact-signals");function t(){if(r>1){r--;return}let i,t=false;while(void 0!==s){let o=s;s=void 0;f++;while(void 0!==o){const n=o.o;o.o=void 0;o.f&=-3;if(!(8&o.f)&&v(o))try{o.c();}catch(o){if(!t){i=o;t=true;}}o=n;}}f=0;r--;if(t)throw i}let n,s;function h(i){const t=n;n=void 0;try{return i()}finally{n=t;}}let r=0,f=0,e=0;function c(i){if(void 0===n)return;let t=i.n;if(void 0===t||t.t!==n){t={i:0,S:i,p:n.s,n:void 0,t:n,e:void 0,x:void 0,r:t};if(void 0!==n.s)n.s.n=t;n.s=t;i.n=t;if(32&n.f)i.S(t);return t}else if(-1===t.i){t.i=0;if(void 0!==t.n){t.n.p=t.p;if(void 0!==t.p)t.p.n=t.n;t.p=n.s;t.n=void 0;n.s.n=t;n.s=t;}return t}}function u(i,t){this.v=i;this.i=0;this.n=void 0;this.t=void 0;this.W=null==t?void 0:t.watched;this.Z=null==t?void 0:t.unwatched;}u.prototype.brand=i;u.prototype.h=function(){return true};u.prototype.S=function(i){const t=this.t;if(t!==i&&void 0===i.e){i.x=t;this.t=i;if(void 0!==t)t.e=i;else h(()=>{var i;null==(i=this.W)||i.call(this);});}};u.prototype.U=function(i){if(void 0!==this.t){const t=i.e,o=i.x;if(void 0!==t){t.x=o;i.e=void 0;}if(void 0!==o){o.e=t;i.x=void 0;}if(i===this.t){this.t=o;if(void 0===o)h(()=>{var i;null==(i=this.Z)||i.call(this);});}}};u.prototype.subscribe=function(i){return E(()=>{const t=this.value,o=n;n=void 0;try{i(t);}finally{n=o;}})};u.prototype.valueOf=function(){return this.value};u.prototype.toString=function(){return this.value+""};u.prototype.toJSON=function(){return this.value};u.prototype.peek=function(){const i=n;n=void 0;try{return this.value}finally{n=i;}};Object.defineProperty(u.prototype,"value",{get(){const i=c(this);if(void 0!==i)i.i=this.i;return this.v},set(i){if(i!==this.v){if(f>100)throw new Error("Cycle detected");this.v=i;this.i++;e++;r++;try{for(let i=this.t;void 0!==i;i=i.x)i.t.N();}finally{t();}}}});function d(i,t){return new u(i,t)}function v(i){for(let t=i.s;void 0!==t;t=t.n)if(t.S.i!==t.i||!t.S.h()||t.S.i!==t.i)return true;return false}function l(i){for(let t=i.s;void 0!==t;t=t.n){const o=t.S.n;if(void 0!==o)t.r=o;t.S.n=t;t.i=-1;if(void 0===t.n){i.s=t;break}}}function y(i){let t,o=i.s;while(void 0!==o){const i=o.p;if(-1===o.i){o.S.U(o);if(void 0!==i)i.n=o.n;if(void 0!==o.n)o.n.p=i;}else t=o;o.S.n=o.r;if(void 0!==o.r)o.r=void 0;o=i;}i.s=t;}function a(i,t){u.call(this,void 0);this.x=i;this.s=void 0;this.g=e-1;this.f=4;this.W=null==t?void 0:t.watched;this.Z=null==t?void 0:t.unwatched;}a.prototype=new u;a.prototype.h=function(){this.f&=-3;if(1&this.f)return false;if(32==(36&this.f))return true;this.f&=-5;if(this.g===e)return true;this.g=e;this.f|=1;if(this.i>0&&!v(this)){this.f&=-2;return true}const i=n;try{l(this);n=this;const i=this.x();if(16&this.f||this.v!==i||0===this.i){this.v=i;this.f&=-17;this.i++;}}catch(i){this.v=i;this.f|=16;this.i++;}n=i;y(this);this.f&=-2;return true};a.prototype.S=function(i){if(void 0===this.t){this.f|=36;for(let i=this.s;void 0!==i;i=i.n)i.S.S(i);}u.prototype.S.call(this,i);};a.prototype.U=function(i){if(void 0!==this.t){u.prototype.U.call(this,i);if(void 0===this.t){this.f&=-33;for(let i=this.s;void 0!==i;i=i.n)i.S.U(i);}}};a.prototype.N=function(){if(!(2&this.f)){this.f|=6;for(let i=this.t;void 0!==i;i=i.x)i.t.N();}};Object.defineProperty(a.prototype,"value",{get(){if(1&this.f)throw new Error("Cycle detected");const i=c(this);this.h();if(void 0!==i)i.i=this.i;if(16&this.f)throw this.v;return this.v}});function _(i){const o=i.u;i.u=void 0;if("function"==typeof o){r++;const s=n;n=void 0;try{o();}catch(t){i.f&=-2;i.f|=8;g(i);throw t}finally{n=s;t();}}}function g(i){for(let t=i.s;void 0!==t;t=t.n)t.S.U(t);i.x=void 0;i.s=void 0;_(i);}function p(i){if(n!==this)throw new Error("Out-of-order effect");y(this);n=i;this.f&=-2;if(8&this.f)g(this);t();}function b(i){this.x=i;this.u=void 0;this.s=void 0;this.o=void 0;this.f=32;}b.prototype.c=function(){const i=this.S();try{if(8&this.f)return;if(void 0===this.x)return;const t=this.x();if("function"==typeof t)this.u=t;}finally{i();}};b.prototype.S=function(){if(1&this.f)throw new Error("Cycle detected");this.f|=1;this.f&=-9;_(this);l(this);r++;const i=n;n=this;return p.bind(this,i)};b.prototype.N=function(){if(!(2&this.f)){this.f|=2;this.o=s;s=this;}};b.prototype.d=function(){this.f|=8;if(!(1&this.f))g(this);};function E(i){const t=new b(i);try{t.c();}catch(i){t.d();throw i}return t.d.bind(t)}
function createSystem(queries = {}, schema = {}) {
var _a;
return _a = class {
constructor(world, queryManager, priority) {
this.world = world;
this.queryManager = queryManager;
this.priority = priority;
this.isPaused = false;
this.config = {};
for (const key in schema) {
this.config[key] = d(schema[key].default);
}
}
get globals() {
return this.world.globals;
}
createEntity() {
return this.world.createEntity();
}
init() { }
update(_delta, _time) { }
play() {
this.isPaused = false;
}
stop() {
this.isPaused = true;
}
destroy() { }
},
_a.schema = schema,
_a.isSystem = true,
_a.queries = queries,
_a;
}
const VERSION = '3.2.0';
function eq(component, key, value) {
return { component, key: key, op: 'eq', value };
}
function ne(component, key, value) {
return { component, key: key, op: 'ne', value };
}
function lt(component, key, value) {
return { component, key: key, op: 'lt', value };
}
function le(component, key, value) {
return { component, key: key, op: 'le', value };
}
function gt(component, key, value) {
return { component, key: key, op: 'gt', value };
}
function ge(component, key, value) {
return { component, key: key, op: 'ge', value };
}
function isin(component, key, values) {
return { component, key: key, op: 'in', value: values };
}
function nin(component, key, values) {
return { component, key: key, op: 'nin', value: values };
}
export { ComponentRegistry, Entity, Query, TypedArrayMap, Types, VERSION, World, assignInitialComponentData, createComponent, createSystem, eq, ge, gt, initializeComponentStorage, isin, le, lt, ne, nin };