UNPKG

geotic

Version:
1,037 lines (815 loc) 26.2 kB
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true, }); } else { obj[key] = value; } return obj; } const preserveCamelCase = (string) => { let isLastCharLower = false; let isLastCharUpper = false; let isLastLastCharUpper = false; for (let i = 0; i < string.length; i++) { const character = string[i]; if (isLastCharLower && /[\p{Lu}]/u.test(character)) { string = string.slice(0, i) + '-' + string.slice(i); isLastCharLower = false; isLastLastCharUpper = isLastCharUpper; isLastCharUpper = true; i++; } else if ( isLastCharUpper && isLastLastCharUpper && /[\p{Ll}]/u.test(character) ) { string = string.slice(0, i - 1) + '-' + string.slice(i - 1); isLastLastCharUpper = isLastCharUpper; isLastCharUpper = false; isLastCharLower = true; } else { isLastCharLower = character.toLocaleLowerCase() === character && character.toLocaleUpperCase() !== character; isLastLastCharUpper = isLastCharUpper; isLastCharUpper = character.toLocaleUpperCase() === character && character.toLocaleLowerCase() !== character; } } return string; }; const camelCase = (input, options) => { if (!(typeof input === 'string' || Array.isArray(input))) { throw new TypeError('Expected the input to be `string | string[]`'); } options = { ...{ pascalCase: false, }, ...options, }; const postProcess = (x) => options.pascalCase ? x.charAt(0).toLocaleUpperCase() + x.slice(1) : x; if (Array.isArray(input)) { input = input .map((x) => x.trim()) .filter((x) => x.length) .join('-'); } else { input = input.trim(); } if (input.length === 0) { return ''; } if (input.length === 1) { return options.pascalCase ? input.toLocaleUpperCase() : input.toLocaleLowerCase(); } const hasUpperCase = input !== input.toLocaleLowerCase(); if (hasUpperCase) { input = preserveCamelCase(input); } input = input .replace(/^[_.\- ]+/, '') .toLocaleLowerCase() .replace(/[_.\- ]+([\p{Alpha}\p{N}_]|$)/gu, (_, p1) => p1.toLocaleUpperCase() ) .replace(/\d+([\p{Alpha}\p{N}_]|$)/gu, (m) => m.toLocaleUpperCase()); return postProcess(input); }; var camelcase = camelCase; // TODO: Remove this for the next major release var _default = camelCase; camelcase.default = _default; const camelCache = {}; const camelString = (value) => { const result = camelCache[value]; if (!result) { camelCache[value] = camelcase(value); return camelCache[value]; } return result; }; class ComponentRegistry { constructor() { _defineProperty(this, '_cbit', 0); _defineProperty(this, '_map', {}); } register(clazz) { const key = camelString(clazz.name); clazz.prototype._ckey = key; clazz.prototype._cbit = BigInt(++this._cbit); this._map[key] = clazz; } get(key) { return this._map[key]; } } var isMergeableObject = function isMergeableObject(value) { return isNonNullObject(value) && !isSpecial(value); }; function isNonNullObject(value) { return !!value && typeof value === 'object'; } function isSpecial(value) { var stringValue = Object.prototype.toString.call(value); return ( stringValue === '[object RegExp]' || stringValue === '[object Date]' || isReactElement(value) ); } // see https://github.com/facebook/react/blob/b5ac963fb791d1298e7f396236383bc955f916c1/src/isomorphic/classic/element/ReactElement.js#L21-L25 var canUseSymbol = typeof Symbol === 'function' && Symbol.for; var REACT_ELEMENT_TYPE = canUseSymbol ? Symbol.for('react.element') : 0xeac7; function isReactElement(value) { return value.$$typeof === REACT_ELEMENT_TYPE; } function emptyTarget(val) { return Array.isArray(val) ? [] : {}; } function cloneUnlessOtherwiseSpecified(value, options) { return options.clone !== false && options.isMergeableObject(value) ? deepmerge(emptyTarget(value), value, options) : value; } function defaultArrayMerge(target, source, options) { return target.concat(source).map(function (element) { return cloneUnlessOtherwiseSpecified(element, options); }); } function getMergeFunction(key, options) { if (!options.customMerge) { return deepmerge; } var customMerge = options.customMerge(key); return typeof customMerge === 'function' ? customMerge : deepmerge; } function getEnumerableOwnPropertySymbols(target) { return Object.getOwnPropertySymbols ? Object.getOwnPropertySymbols(target).filter(function (symbol) { return target.propertyIsEnumerable(symbol); }) : []; } function getKeys(target) { return Object.keys(target).concat(getEnumerableOwnPropertySymbols(target)); } function propertyIsOnObject(object, property) { try { return property in object; } catch (_) { return false; } } // Protects from prototype poisoning and unexpected merging up the prototype chain. function propertyIsUnsafe(target, key) { return ( propertyIsOnObject(target, key) && // Properties are safe to merge if they don't exist in the target yet, !( Object.hasOwnProperty.call(target, key) && // unsafe if they exist up the prototype chain, Object.propertyIsEnumerable.call(target, key) ) ); // and also unsafe if they're nonenumerable. } function mergeObject(target, source, options) { var destination = {}; if (options.isMergeableObject(target)) { getKeys(target).forEach(function (key) { destination[key] = cloneUnlessOtherwiseSpecified( target[key], options ); }); } getKeys(source).forEach(function (key) { if (propertyIsUnsafe(target, key)) { return; } if ( propertyIsOnObject(target, key) && options.isMergeableObject(source[key]) ) { destination[key] = getMergeFunction(key, options)( target[key], source[key], options ); } else { destination[key] = cloneUnlessOtherwiseSpecified( source[key], options ); } }); return destination; } function deepmerge(target, source, options) { options = options || {}; options.arrayMerge = options.arrayMerge || defaultArrayMerge; options.isMergeableObject = options.isMergeableObject || isMergeableObject; // cloneUnlessOtherwiseSpecified is added to `options` so that custom arrayMerge() // implementations can use it. The caller may not replace it. options.cloneUnlessOtherwiseSpecified = cloneUnlessOtherwiseSpecified; var sourceIsArray = Array.isArray(source); var targetIsArray = Array.isArray(target); var sourceAndTargetTypesMatch = sourceIsArray === targetIsArray; if (!sourceAndTargetTypesMatch) { return cloneUnlessOtherwiseSpecified(source, options); } else if (sourceIsArray) { return options.arrayMerge(target, source, options); } else { return mergeObject(target, source, options); } } deepmerge.all = function deepmergeAll(array, options) { if (!Array.isArray(array)) { throw new Error('first argument should be an array'); } return array.reduce(function (prev, next) { return deepmerge(prev, next, options); }, {}); }; var deepmerge_1 = deepmerge; var cjs = deepmerge_1; class PrefabComponent { constructor(clazz, properties = {}, overwrite = true) { this.clazz = clazz; this.properties = properties; this.overwrite = overwrite; } applyToEntity(entity, initialProps = {}) { if (!this.clazz.allowMultiple && entity.has(this.clazz)) { if (!this.overwrite) { return; } const comp = entity[this.clazz.prototype._ckey]; entity.remove(comp); } const props = cjs(this.properties, initialProps); entity.add(this.clazz, props); } } class Prefab { constructor(name) { _defineProperty(this, 'name', ''); _defineProperty(this, 'inherit', []); _defineProperty(this, 'components', []); this.name = name; } addComponent(prefabComponent) { this.components.push(prefabComponent); } applyToEntity(entity, prefabProps = {}) { this.inherit.forEach((parent) => { parent.applyToEntity(entity, prefabProps); }); const arrComps = {}; this.components.forEach((component) => { const clazz = component.clazz; const ckey = clazz.prototype._ckey; let initialCompProps = {}; if (clazz.allowMultiple) { if (clazz.keyProperty) { const key = component.properties[clazz.keyProperty]; if (prefabProps[ckey] && prefabProps[ckey][key]) { initialCompProps = prefabProps[ckey][key]; } } else { if (!arrComps[ckey]) { arrComps[ckey] = 0; } if ( prefabProps[ckey] && prefabProps[ckey][arrComps[ckey]] ) { initialCompProps = prefabProps[ckey][arrComps[ckey]]; } arrComps[ckey]++; } } else { initialCompProps = prefabProps[ckey]; } component.applyToEntity(entity, initialCompProps); }); return entity; } } class PrefabRegistry { constructor(engine) { _defineProperty(this, '_prefabs', {}); _defineProperty(this, '_engine', null); this._engine = engine; } deserialize(data) { const registered = this.get(data.name); if (registered) { return registered; } const prefab = new Prefab(data.name); let inherit; if (Array.isArray(data.inherit)) { inherit = data.inherit; } else if (typeof data.inherit === 'string') { inherit = [data.inherit]; } else { inherit = []; } prefab.inherit = inherit.map((parent) => { const ref = this.get(parent); if (!ref) { console.warn( `Prefab "${data.name}" cannot inherit from Prefab "${parent}" because is not registered yet! Prefabs must be registered in the right order.` ); return parent; } return ref; }); const comps = data.components || []; comps.forEach((componentData) => { if (typeof componentData === 'string') { const ckey = camelString(componentData); const clazz = this._engine._components.get(ckey); if (clazz) { prefab.addComponent(new PrefabComponent(clazz)); return; } } if (typeof componentData === 'object') { const ckey = camelString(componentData.type); const clazz = this._engine._components.get(ckey); if (clazz) { prefab.addComponent( new PrefabComponent( clazz, componentData.properties, componentData.overwrite ) ); return; } } console.warn( `Unrecognized component reference "${componentData}" in prefab "${data.name}". Ensure the component is registered before the prefab.` ); }); return prefab; } register(data) { const prefab = this.deserialize(data); this._prefabs[prefab.name] = prefab; } get(name) { return this._prefabs[name]; } create(world, name, properties = {}) { const prefab = this.get(name); if (!prefab) { console.warn( `Could not instantiate prefab "${name}" since it is not registered` ); return; } const entity = world.createEntity(); entity._qeligible = false; prefab.applyToEntity(entity, properties); entity._qeligible = true; entity._candidacy(); return entity; } } class Component { get world() { return this.entity.world; } get allowMultiple() { return this.constructor.allowMultiple; } get keyProperty() { return this.constructor.keyProperty; } constructor(properties = {}) { Object.assign(this, this.constructor.properties, properties); } destroy() { this.entity.remove(this); } _onDestroyed() { this.onDestroyed(); delete this.entity; } _onEvent(evt) { this.onEvent(evt); if (typeof this[evt.handlerName] === 'function') { this[evt.handlerName](evt); } } _onAttached(entity) { this.entity = entity; this.onAttached(entity); } serialize() { const ob = {}; for (const key in this.constructor.properties) { ob[key] = this[key]; } return ob; } onAttached(entity) {} onDestroyed() {} onEvent(evt) {} } _defineProperty(Component, 'allowMultiple', false); _defineProperty(Component, 'keyProperty', null); _defineProperty(Component, 'properties', {}); class EntityEvent { constructor(name, data = {}) { _defineProperty(this, 'data', {}); _defineProperty(this, 'prevented', false); _defineProperty(this, 'handled', false); this.name = name; this.data = data; this.handlerName = camelString(`on ${this.name}`); } is(name) { return this.name === name; } handle() { this.handled = true; this.prevented = true; } prevent() { this.prevented = true; } } const ONE = 1n; const subtractBit = (num, bit) => { return num & ~(1n << bit); }; const addBit = (num, bit) => { return num | (ONE << bit); }; const hasBit = (num, bit) => { return (num >> bit) % 2n !== 0n; }; const bitIntersection = (n1, n2) => { return n1 & n2; }; const attachComponent = (entity, component) => { const key = component._ckey; entity[key] = component; entity.components[key] = component; }; const attachComponentKeyed = (entity, component) => { const key = component._ckey; if (!entity.components[key]) { entity[key] = {}; entity.components[key] = {}; } entity[key][component[component.keyProperty]] = component; entity.components[key][component[component.keyProperty]] = component; }; const attachComponentArray = (entity, component) => { const key = component._ckey; if (!entity.components[key]) { entity[key] = []; entity.components[key] = []; } entity[key].push(component); entity.components[key].push(component); }; const removeComponent = (entity, component) => { const key = component._ckey; delete entity[key]; delete entity.components[key]; entity._cbits = subtractBit(entity._cbits, component._cbit); entity._candidacy(); }; const removeComponentKeyed = (entity, component) => { const key = component._ckey; const keyProp = component[component.keyProperty]; delete entity[key][keyProp]; delete entity.components[key][keyProp]; if (Object.keys(entity[key]).length <= 0) { delete entity[key]; delete entity.components[key]; entity._cbits = subtractBit(entity._cbits, component._cbit); entity._candidacy(); } }; const removeComponentArray = (entity, component) => { const key = component._ckey; const idx = entity[key].indexOf(component); entity[key].splice(idx, 1); entity.components[key].splice(idx, 1); if (entity[key].length <= 0) { delete entity[key]; delete entity.components[key]; entity._cbits = subtractBit(entity._cbits, component._cbit); entity._candidacy(); } }; const serializeComponent = (component) => { return component.serialize(); }; const serializeComponentArray = (arr) => { return arr.map(serializeComponent); }; const serializeComponentKeyed = (ob) => { const ser = {}; for (const k in ob) { ser[k] = serializeComponent(ob[k]); } return ser; }; class Entity { constructor(world, id) { _defineProperty(this, '_cbits', 0n); _defineProperty(this, '_qeligible', true); this.world = world; this.id = id; this.components = {}; this.isDestroyed = false; } _candidacy() { if (this._qeligible) { this.world._candidate(this); } } add(clazz, properties) { const component = new clazz(properties); if (component.keyProperty) { attachComponentKeyed(this, component); } else if (component.allowMultiple) { attachComponentArray(this, component); } else { attachComponent(this, component); } this._cbits = addBit(this._cbits, component._cbit); component._onAttached(this); this._candidacy(); } has(clazz) { return hasBit(this._cbits, clazz.prototype._cbit); } remove(component) { if (component.keyProperty) { removeComponentKeyed(this, component); } else if (component.allowMultiple) { removeComponentArray(this, component); } else { removeComponent(this, component); } component._onDestroyed(); } destroy() { for (const k in this.components) { const v = this.components[k]; if (v instanceof Component) { this._cbits = subtractBit(this._cbits, v._cbit); v._onDestroyed(); } else if (v instanceof Array) { for (const component of v) { this._cbits = subtractBit(this._cbits, component._cbit); component._onDestroyed(); } } else { for (const component of Object.values(v)) { this._cbits = subtractBit(this._cbits, component._cbit); component._onDestroyed(); } } delete this[k]; delete this.components[k]; } this._candidacy(); this.world._destroyed(this.id); this.components = {}; this.isDestroyed = true; } serialize() { const components = {}; for (const k in this.components) { const v = this.components[k]; if (v instanceof Component) { components[k] = serializeComponent(v); } else if (v instanceof Array) { components[k] = serializeComponentArray(v); } else { components[k] = serializeComponentKeyed(v); } } return { id: this.id, ...components, }; } fireEvent(name, data) { const evt = new EntityEvent(name, data); for (const key in this.components) { const v = this.components[key]; if (v instanceof Component) { v._onEvent(evt); if (evt.prevented) { return evt; } } else if (v instanceof Array) { for (let i = 0; i < v.length; i++) { v[i]._onEvent(evt); if (evt.prevented) { return evt; } } } else { for (const component of Object.values(v)) { component._onEvent(evt); if (evt.prevented) { return evt; } } } } return evt; } } class Query { constructor(world, filters) { _defineProperty(this, '_cache', []); _defineProperty(this, '_onAddListeners', []); _defineProperty(this, '_onRemoveListeners', []); this._world = world; const any = filters.any || []; const all = filters.all || []; const none = filters.none || []; this._any = any.reduce((s, c) => { return addBit(s, c.prototype._cbit); }, 0n); this._all = all.reduce((s, c) => { return addBit(s, c.prototype._cbit); }, 0n); this._none = none.reduce((s, c) => { return addBit(s, c.prototype._cbit); }, 0n); this.refresh(); } onEntityAdded(fn) { this._onAddListeners.push(fn); } onEntityRemoved(fn) { this._onRemoveListeners.push(fn); } has(entity) { return this.idx(entity) >= 0; } idx(entity) { return this._cache.indexOf(entity); } matches(entity) { const bits = entity._cbits; const any = this._any === 0n || bitIntersection(bits, this._any) > 0; const all = bitIntersection(bits, this._all) === this._all; const none = bitIntersection(bits, this._none) === 0n; return any && all && none; } candidate(entity) { const idx = this.idx(entity); const isTracking = idx >= 0; if (!entity.isDestroyed && this.matches(entity)) { if (!isTracking) { this._cache.push(entity); this._onAddListeners.forEach((cb) => cb(entity)); } return true; } if (isTracking) { this._cache.splice(idx, 1); this._onRemoveListeners.forEach((cb) => cb(entity)); } return false; } refresh() { this._cache = []; this._world._entities.forEach((entity) => { this.candidate(entity); }); } get() { return this._cache; } } class World { constructor(engine) { _defineProperty(this, '_id', 0); _defineProperty(this, '_queries', []); _defineProperty(this, '_entities', new Map()); this.engine = engine; } createId() { return ++this._id + Math.random().toString(36).substr(2, 9); } getEntity(id) { return this._entities.get(id); } getEntities() { return this._entities.values(); } createEntity(id = this.createId()) { const entity = new Entity(this, id); this._entities.set(id, entity); return entity; } destroyEntity(id) { const entity = this.getEntity(id); if (entity) { entity.destroy(); } } destroyEntities() { this._entities.forEach((entity) => { entity.destroy(); }); } destroy() { this.destroyEntities(); this._id = 0; this._queries = []; this._entities = new Map(); } createQuery(filters) { const query = new Query(this, filters); this._queries.push(query); return query; } createPrefab(name, properties = {}) { return this.engine._prefabs.create(this, name, properties); } serialize(entities) { const json = []; const list = entities || this._entities; list.forEach((e) => { json.push(e.serialize()); }); return { entities: json, }; } deserialize(data) { for (const entityData of data.entities) { this._createOrGetEntityById(entityData.id); } for (const entityData of data.entities) { this._deserializeEntity(entityData); } } _createOrGetEntityById(id) { return this.getEntity(id) || this.createEntity(id); } _deserializeEntity(data) { const { id, ...components } = data; const entity = this._createOrGetEntityById(id); entity._qeligible = false; Object.entries(components).forEach(([key, value]) => { const type = camelString(key); const def = this.engine._components.get(type); if (def.allowMultiple) { Object.values(value).forEach((d) => { entity.add(def, d); }); } else { entity.add(def, value); } }); entity._qeligible = true; entity._candidacy(); } _candidate(entity) { this._queries.forEach((q) => q.candidate(entity)); } _destroyed(id) { return this._entities.delete(id); } } class Engine { constructor() { _defineProperty(this, '_components', new ComponentRegistry()); _defineProperty(this, '_prefabs', new PrefabRegistry(this)); } registerComponent(clazz) { this._components.register(clazz); } registerPrefab(data) { this._prefabs.register(data); } createWorld() { return new World(this); } destroyWorld(world) { world.destroy(); } } export { Component, Engine }; //# sourceMappingURL=index.js.map