UNPKG

@ash.ts/io

Version:

Serialization and deserialization for Ash.ts - an entity component system framework for game development

477 lines (462 loc) 17.9 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@ash.ts/signals'), require('@ash.ts/core')) : typeof define === 'function' && define.amd ? define(['exports', '@ash.ts/signals', '@ash.ts/core'], factory) : (global = global || self, factory((global.ASH = global.ASH || {}, global.ASH.io = {}), global.ASH.signals, global.ASH.core)); }(this, (function (exports, signals, core) { 'use strict'; class ArrayObjectCodec { encode(object, codecManager) { const value = []; for (const val of object) { const encoded = codecManager.encodeObject(val); if (encoded) { value.push(encoded); } } return { type: 'Array', value }; } decode(object, codecManager) { const decoded = []; for (const obj of object.value) { decoded[decoded.length] = codecManager.decodeObject(obj); } return decoded; } decodeIntoObject(target, object, codecManager) { for (const obj of object.value) { target[target.length] = codecManager.decodeObject(obj); } } decodeIntoProperty(parent, property, object, codecManager) { this.decodeIntoObject(parent[property], object, codecManager); } } class ClassObjectCodec { encode(object, codecManager) { return { type: 'Class', value: codecManager.classToStringMap.get(object) }; } decode(object, codecManager) { return codecManager.stringToClassMap.get(object.value) || null; } decodeIntoObject(target, object, codecManager) { throw new Error('Can\'t decode into a native object because the object is passed by value, not by reference, so we\'re decoding into a local copy not the original.'); } decodeIntoProperty(parent, property, object, codecManager) { this.decodeIntoObject(parent[property], object, codecManager); } } class NativeObjectCodec { encode(object, codecManager) { return { type: typeof object, value: object }; } decode(object, codecManager) { return object.value; } decodeIntoObject(target, object, codecManager) { throw new Error('Can\'t decode into a native object because the object is passed by value, not by reference,' + 'so we\'re decoding into a local copy not the original.'); } decodeIntoProperty(parent, property, object, codecManager) { parent[property] = object.value; } } class ObjectReflection { constructor(component, type) { this._propertyTypes = new Map(); this._type = type || component.constructor.name; const { _propertyTypes } = this; const filter = (descs) => (key) => { const desc = descs[key]; return !!desc.enumerable || (!!desc.get && !!desc.set); }; const ownKeys = Object.getOwnPropertyDescriptors(component); const protoDescriptor = Object.getOwnPropertyDescriptors(Object.getPrototypeOf(component)); let keys = Object.keys(ownKeys).filter(filter(ownKeys)); keys = keys.concat(Object.keys(protoDescriptor).filter(filter(protoDescriptor))); for (const key of keys) { const cmp = component[key]; const name = typeof cmp; switch (name.toLowerCase()) { case 'object': if (cmp === null) { _propertyTypes.set(key, 'null'); } else { _propertyTypes.set(key, cmp.constructor.name); } break; case 'undefined': case 'null': break; default: _propertyTypes.set(key, name); } } const types = component.constructor.__ash_types__; if (!types) return; for (const [key, componentType] of types) { const { name } = componentType; if (!_propertyTypes.has(key)) { _propertyTypes.set(key, name); } } } get propertyTypes() { return this._propertyTypes; } get type() { return this._type; } } class ObjectReflectionFactory { static reflection(component) { const type = component.constructor.prototype.constructor; const { reflections, classMap } = ObjectReflectionFactory; if (!reflections.has(type) && classMap.has(type)) { reflections.set(type, new ObjectReflection(component, classMap.get(type))); } return reflections.get(type) || null; } } ObjectReflectionFactory.classMap = new Map(); ObjectReflectionFactory.reflections = new Map(); class ReflectionObjectCodec { encode(object, codecManager) { const reflection = ObjectReflectionFactory.reflection(object); if (!reflection) return null; const properties = {}; const keys = reflection.propertyTypes.keys(); for (const name of keys) { properties[name] = codecManager.encodeObject(object[name]); } return { type: reflection.type, value: properties }; } decode(object, codecManager) { const Type = codecManager.stringToClassMap.get(object.type) || null; if (!Type) { return null; } const decoded = new Type(); const keys = Object.keys(object.value); for (const name of keys) { decoded[name] = codecManager.decodeObject(object.value[name]); } return decoded; } decodeIntoObject(target, object, codecManager) { const keys = Object.keys(object.value); for (const name of keys) { if (target[name]) { codecManager.decodeIntoProperty(target, name, object.value[name]); } else { target[name] = codecManager.decodeObject(object.value[name]); } } } decodeIntoProperty(parent, property, object, codecManager) { this.decodeIntoObject(parent[property], object, codecManager); } } class CodecManager { constructor(classMap) { classMap.set('number', Number); classMap.set('string', String); classMap.set('boolean', Boolean); this.stringToClassMap = classMap; const classToStringMap = new Map(); for (const [className, classType] of classMap) { classToStringMap.set(classType, className); } ObjectReflectionFactory.classMap = classToStringMap; this.classToStringMap = classToStringMap; this.codecs = new Map(); this.reflectionCodec = new ReflectionObjectCodec(); const nativeCodec = new NativeObjectCodec(); this.addCustomCodec(nativeCodec, Number); this.addCustomCodec(nativeCodec, String); this.addCustomCodec(nativeCodec, Boolean); this.addCustomCodec(new ClassObjectCodec(), Function); this.addCustomCodec(new ArrayObjectCodec(), Array); } getCodecForObject(object) { const nativeTypes = { number: Number, string: String, boolean: Boolean, }; let type = nativeTypes[typeof object]; if (!type && object instanceof Array) type = Array; if (!type && object instanceof Object) type = object.constructor; if (this.codecs.has(type)) { return this.codecs.get(type); } return null; } getCodecForType(type) { if (this.codecs.has(type)) { return this.codecs.get(type); } return null; } getCodecForComponent(component) { const codec = this.getCodecForObject(component); if (codec === null) { return this.reflectionCodec; } return codec; } getCodecForComponentType(type) { const codec = this.getCodecForType(type); if (codec === null) { return this.reflectionCodec; } return codec; } addCustomCodec(codec, type) { this.codecs.set(type, codec); } encodeComponent(object) { const codec = this.getCodecForComponent(object); if (codec) { return codec.encode(object, this); } return null; } encodeObject(object) { if (object === null) { return { type: 'null', value: null }; } const codec = this.getCodecForObject(object); if (codec) { return codec.encode(object, this); } return { type: 'null', value: null }; } decodeComponent(object) { if (!object.type || object.value === null) { return null; } const type = this.stringToClassMap.get(object.type); const codec = type ? this.getCodecForComponentType(type) : null; if (codec) { return codec.decode(object, this); } return null; } decodeObject(object) { if (!object.type || object.value === null) { return null; } const type = this.stringToClassMap.get(object.type); const codec = type ? this.getCodecForType(type) : null; if (codec) { return codec.decode(object, this); } return null; } decodeIntoComponent(target, encoded) { if (!encoded.type || encoded.value === null) { return; } const type = this.stringToClassMap.get(encoded.type); const codec = type ? this.getCodecForComponentType(type) : null; if (codec) { codec.decodeIntoObject(target, encoded, this); } } decodeIntoProperty(parent, property, encoded) { if (!encoded.type || encoded.value === null) { return; } const type = this.stringToClassMap.get(encoded.type); const codec = type ? this.getCodecForType(type) : null; if (codec) { codec.decodeIntoProperty(parent, property, encoded, this); } } } class EngineDecoder { constructor(codecManager) { this.codecManager = codecManager; this.componentMap = []; this.encodedComponentMap = []; } reset() { this.componentMap.length = 0; this.encodedComponentMap.length = 0; } decodeEngine(encodedData, engine) { for (const encodedComponent of encodedData.components) { this.decodeComponent(encodedComponent); } for (const encodedEntity of encodedData.entities) { engine.addEntity(this.decodeEntity(encodedEntity)); } } decodeOverEngine(encodedData, engine) { for (const encodedComponent of encodedData.components) { this.encodedComponentMap[encodedComponent.id] = encodedComponent; this.decodeComponent(encodedComponent); } for (const encodedEntity of encodedData.entities) { if (encodedEntity.name) { const { name } = encodedEntity; if (name) { const existingEntity = engine.getEntityByName(name); if (existingEntity) { this.overlayEntity(existingEntity, encodedEntity); continue; } } } engine.addEntity(this.decodeEntity(encodedEntity)); } } overlayEntity(entity, encodedEntity) { for (const componentId of encodedEntity.components) { if (this.componentMap[componentId]) { const newComponent = this.componentMap[componentId]; if (newComponent) { const type = newComponent.constructor.prototype.constructor; const existingComponent = entity.get(type); if (existingComponent) { this.codecManager.decodeIntoComponent(existingComponent, this.encodedComponentMap[componentId]); } else { entity.add(newComponent); } } } } } decodeEntity(encodedEntity) { const entity = new core.Entity(); if (encodedEntity.name) { entity.name = encodedEntity.name; } for (const componentId of encodedEntity.components) { if (this.componentMap[componentId]) { entity.add(this.componentMap[componentId]); } } return entity; } decodeComponent(encodedComponent) { const type = this.codecManager.stringToClassMap.get(encodedComponent.type); if (!type) return; const codec = this.codecManager.getCodecForComponent(type); if (!codec) return; const decodedComponent = this.codecManager.decodeComponent(encodedComponent); if (decodedComponent) this.componentMap[encodedComponent.id] = decodedComponent; } } class EngineEncoder { constructor(codecManager) { this.codecManager = codecManager; this.reset(); } reset() { this.nextComponentId = 1; this.encodedEntities = []; this.encodedComponents = []; this.componentEncodingMap = new Map(); this.encoded = { entities: this.encodedEntities, components: this.encodedComponents }; } encodeEngine(engine) { for (const entity of engine.entities) { this.encodeEntity(entity); } return this.encoded; } encodeEntity(entity) { const components = entity.getAll(); const componentIds = []; for (const component of components) { const encodedComponentId = this.encodeComponent(component); if (encodedComponentId > -1) { componentIds.push(encodedComponentId); } } this.encodedEntities.push({ name: entity.name, components: componentIds, }); } encodeComponent(component) { if (this.componentEncodingMap.has(component)) { return this.componentEncodingMap.get(component).id; } const encodedObject = this.codecManager.encodeComponent(component); if (encodedObject) { const encoded = encodedObject; this.nextComponentId += 1; encoded.id = this.nextComponentId; this.componentEncodingMap.set(component, encoded); this.encodedComponents.push(encoded); return encoded.id; } return -1; } } class ObjectEngineCodec { constructor(classMap) { this.encodeCompleteSignal = new signals.Signal1(); this.decodeCompleteSignal = new signals.Signal1(); this.codecManager = new CodecManager(classMap); this.encoder = new EngineEncoder(this.codecManager); this.decoder = new EngineDecoder(this.codecManager); } addCustomCodec(codec, ...types) { for (const type of types) { this.codecManager.addCustomCodec(codec, type); } } encodeEngine(engine) { this.encoder.reset(); const encoded = this.encoder.encodeEngine(engine); this.encodeCompleteSignal.dispatch(encoded); return encoded; } decodeEngine(encodedData, engine) { this.decoder.reset(); this.decoder.decodeEngine(encodedData, engine); this.decodeCompleteSignal.dispatch(engine); } decodeOverEngine(encodedData, engine) { this.decoder.reset(); this.decoder.decodeOverEngine(encodedData, engine); this.decodeCompleteSignal.dispatch(engine); } get encodeComplete() { return this.encodeCompleteSignal; } get decodeComplete() { return this.decodeCompleteSignal; } } class JsonEngineCodec extends ObjectEngineCodec { encodeEngine(engine) { const object = super.encodeEngine(engine); return JSON.stringify(object); } decodeEngine(encodedData, engine) { const object = JSON.parse(encodedData); super.decodeEngine(object, engine); } decodeOverEngine(encodedData, engine) { const object = JSON.parse(encodedData); super.decodeOverEngine(object, engine); } } exports.CodecManager = CodecManager; exports.JsonEngineCodec = JsonEngineCodec; exports.ObjectEngineCodec = ObjectEngineCodec; Object.defineProperty(exports, '__esModule', { value: true }); })));