UNPKG

@ash.ts/io

Version:

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

2 lines (1 loc) 7.81 kB
import{Signal1 as e}from"@ash.ts/signals";import{Entity as t}from"@ash.ts/core";class ArrayObjectCodec{encode(e,t){const o=[];for(const n of e){const e=t.encodeObject(n);e&&o.push(e)}return{type:"Array",value:o}}decode(e,t){const o=[];for(const n of e.value)o[o.length]=t.decodeObject(n);return o}decodeIntoObject(e,t,o){for(const n of t.value)e[e.length]=o.decodeObject(n)}decodeIntoProperty(e,t,o,n){this.decodeIntoObject(e[t],o,n)}}class ClassObjectCodec{encode(e,t){return{type:"Class",value:t.classToStringMap.get(e)}}decode(e,t){return t.stringToClassMap.get(e.value)||null}decodeIntoObject(e,t,o){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(e,t,o,n){this.decodeIntoObject(e[t],o,n)}}class NativeObjectCodec{encode(e,t){return{type:typeof e,value:e}}decode(e,t){return e.value}decodeIntoObject(e,t,o){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(e,t,o,n){e[t]=o.value}}class ObjectReflection{constructor(e,t){this._propertyTypes=new Map,this._type=t||e.constructor.name;const{_propertyTypes:o}=this,filter=e=>t=>{const o=e[t];return!!o.enumerable||!!o.get&&!!o.set},n=Object.getOwnPropertyDescriptors(e),c=Object.getOwnPropertyDescriptors(Object.getPrototypeOf(e));let s=Object.keys(n).filter(filter(n));s=s.concat(Object.keys(c).filter(filter(c)));for(const t of s){const n=e[t],c=typeof n;switch(c.toLowerCase()){case"object":null===n?o.set(t,"null"):o.set(t,n.constructor.name);break;case"undefined":case"null":break;default:o.set(t,c)}}const r=e.constructor.__ash_types__;if(r)for(const[e,t]of r){const{name:n}=t;o.has(e)||o.set(e,n)}}get propertyTypes(){return this._propertyTypes}get type(){return this._type}}class ObjectReflectionFactory{static reflection(e){const t=e.constructor.prototype.constructor,{reflections:o,classMap:n}=ObjectReflectionFactory;return!o.has(t)&&n.has(t)&&o.set(t,new ObjectReflection(e,n.get(t))),o.get(t)||null}}ObjectReflectionFactory.classMap=new Map,ObjectReflectionFactory.reflections=new Map;class ReflectionObjectCodec{encode(e,t){const o=ObjectReflectionFactory.reflection(e);if(!o)return null;const n={},c=o.propertyTypes.keys();for(const o of c)n[o]=t.encodeObject(e[o]);return{type:o.type,value:n}}decode(e,t){const o=t.stringToClassMap.get(e.type)||null;if(!o)return null;const n=new o,c=Object.keys(e.value);for(const o of c)n[o]=t.decodeObject(e.value[o]);return n}decodeIntoObject(e,t,o){const n=Object.keys(t.value);for(const c of n)e[c]?o.decodeIntoProperty(e,c,t.value[c]):e[c]=o.decodeObject(t.value[c])}decodeIntoProperty(e,t,o,n){this.decodeIntoObject(e[t],o,n)}}class CodecManager{constructor(e){e.set("number",Number),e.set("string",String),e.set("boolean",Boolean),this.stringToClassMap=e;const t=new Map;for(const[o,n]of e)t.set(n,o);ObjectReflectionFactory.classMap=t,this.classToStringMap=t,this.codecs=new Map,this.reflectionCodec=new ReflectionObjectCodec;const o=new NativeObjectCodec;this.addCustomCodec(o,Number),this.addCustomCodec(o,String),this.addCustomCodec(o,Boolean),this.addCustomCodec(new ClassObjectCodec,Function),this.addCustomCodec(new ArrayObjectCodec,Array)}getCodecForObject(e){let t={number:Number,string:String,boolean:Boolean}[typeof e];return!t&&e instanceof Array&&(t=Array),!t&&e instanceof Object&&(t=e.constructor),this.codecs.has(t)?this.codecs.get(t):null}getCodecForType(e){return this.codecs.has(e)?this.codecs.get(e):null}getCodecForComponent(e){const t=this.getCodecForObject(e);return null===t?this.reflectionCodec:t}getCodecForComponentType(e){const t=this.getCodecForType(e);return null===t?this.reflectionCodec:t}addCustomCodec(e,t){this.codecs.set(t,e)}encodeComponent(e){const t=this.getCodecForComponent(e);return t?t.encode(e,this):null}encodeObject(e){if(null===e)return{type:"null",value:null};const t=this.getCodecForObject(e);return t?t.encode(e,this):{type:"null",value:null}}decodeComponent(e){if(!e.type||null===e.value)return null;const t=this.stringToClassMap.get(e.type),o=t?this.getCodecForComponentType(t):null;return o?o.decode(e,this):null}decodeObject(e){if(!e.type||null===e.value)return null;const t=this.stringToClassMap.get(e.type),o=t?this.getCodecForType(t):null;return o?o.decode(e,this):null}decodeIntoComponent(e,t){if(!t.type||null===t.value)return;const o=this.stringToClassMap.get(t.type),n=o?this.getCodecForComponentType(o):null;n&&n.decodeIntoObject(e,t,this)}decodeIntoProperty(e,t,o){if(!o.type||null===o.value)return;const n=this.stringToClassMap.get(o.type),c=n?this.getCodecForType(n):null;c&&c.decodeIntoProperty(e,t,o,this)}}class EngineDecoder{constructor(e){this.codecManager=e,this.componentMap=[],this.encodedComponentMap=[]}reset(){this.componentMap.length=0,this.encodedComponentMap.length=0}decodeEngine(e,t){for(const t of e.components)this.decodeComponent(t);for(const o of e.entities)t.addEntity(this.decodeEntity(o))}decodeOverEngine(e,t){for(const t of e.components)this.encodedComponentMap[t.id]=t,this.decodeComponent(t);for(const o of e.entities){if(o.name){const{name:e}=o;if(e){const n=t.getEntityByName(e);if(n){this.overlayEntity(n,o);continue}}}t.addEntity(this.decodeEntity(o))}}overlayEntity(e,t){for(const o of t.components)if(this.componentMap[o]){const t=this.componentMap[o];if(t){const n=t.constructor.prototype.constructor,c=e.get(n);c?this.codecManager.decodeIntoComponent(c,this.encodedComponentMap[o]):e.add(t)}}}decodeEntity(e){const o=new t;e.name&&(o.name=e.name);for(const t of e.components)this.componentMap[t]&&o.add(this.componentMap[t]);return o}decodeComponent(e){const t=this.codecManager.stringToClassMap.get(e.type);if(!t)return;if(!this.codecManager.getCodecForComponent(t))return;const o=this.codecManager.decodeComponent(e);o&&(this.componentMap[e.id]=o)}}class EngineEncoder{constructor(e){this.codecManager=e,this.reset()}reset(){this.nextComponentId=1,this.encodedEntities=[],this.encodedComponents=[],this.componentEncodingMap=new Map,this.encoded={entities:this.encodedEntities,components:this.encodedComponents}}encodeEngine(e){for(const t of e.entities)this.encodeEntity(t);return this.encoded}encodeEntity(e){const t=e.getAll(),o=[];for(const e of t){const t=this.encodeComponent(e);t>-1&&o.push(t)}this.encodedEntities.push({name:e.name,components:o})}encodeComponent(e){if(this.componentEncodingMap.has(e))return this.componentEncodingMap.get(e).id;const t=this.codecManager.encodeComponent(e);if(t){const o=t;return this.nextComponentId+=1,o.id=this.nextComponentId,this.componentEncodingMap.set(e,o),this.encodedComponents.push(o),o.id}return-1}}class ObjectEngineCodec{constructor(t){this.encodeCompleteSignal=new e,this.decodeCompleteSignal=new e,this.codecManager=new CodecManager(t),this.encoder=new EngineEncoder(this.codecManager),this.decoder=new EngineDecoder(this.codecManager)}addCustomCodec(e,...t){for(const o of t)this.codecManager.addCustomCodec(e,o)}encodeEngine(e){this.encoder.reset();const t=this.encoder.encodeEngine(e);return this.encodeCompleteSignal.dispatch(t),t}decodeEngine(e,t){this.decoder.reset(),this.decoder.decodeEngine(e,t),this.decodeCompleteSignal.dispatch(t)}decodeOverEngine(e,t){this.decoder.reset(),this.decoder.decodeOverEngine(e,t),this.decodeCompleteSignal.dispatch(t)}get encodeComplete(){return this.encodeCompleteSignal}get decodeComplete(){return this.decodeCompleteSignal}}class JsonEngineCodec extends ObjectEngineCodec{encodeEngine(e){const t=super.encodeEngine(e);return JSON.stringify(t)}decodeEngine(e,t){const o=JSON.parse(e);super.decodeEngine(o,t)}decodeOverEngine(e,t){const o=JSON.parse(e);super.decodeOverEngine(o,t)}}export{CodecManager,JsonEngineCodec,ObjectEngineCodec};