mudb
Version:
Real-time database for multiplayer games
322 lines (290 loc) • 11.7 kB
text/typescript
import { MuStruct } from '../schema/struct';
import { MuUnion } from '../schema/union';
import { MuRDA, MuRDAStore, MuRDATypes, MuRDAActionMeta, MuRDABindableActionMeta } from './rda';
export type MuRDAStructSpec = {
[prop:string]:MuRDA<any, any, any, any>;
};
type WrapAction<
Spec extends MuRDAStructSpec,
Id extends keyof Spec,
Meta,
Dispatch> =
Meta extends { type:'unit' }
? Dispatch extends (...args:infer ArgType) => infer RetType
? (...args:ArgType) => {
type:Id;
data:RetType;
}
: never
: Meta extends { action:MuRDAActionMeta }
? Dispatch extends (...args:infer ArgType) => infer RetType
? (...args:ArgType) => WrapAction<
Spec,
Id,
Meta['action'],
RetType>
: never
: Meta extends { table:{ [id in keyof Dispatch]:MuRDAActionMeta } }
? Dispatch extends { [id in keyof Meta['table']]:any }
? {
[id in keyof Meta['table']]:WrapAction<
Spec,
Id,
Meta['table'][id],
Dispatch[id]>;
}
: never
: never;
type StripBindMeta<Meta extends MuRDABindableActionMeta> =
Meta extends { type:'store'; action:MuRDAActionMeta; }
? Meta['action']
: Meta extends MuRDAActionMeta
? Meta
: never;
type StripBindAndWrap<
Spec extends MuRDAStructSpec,
Id extends keyof Spec> =
Spec[Id]['actionMeta'] extends { type:'store'; action:MuRDAActionMeta; }
? Spec[Id]['action'] extends (store) => infer RetAction
? WrapAction<
Spec,
Id,
Spec[Id]['actionMeta']['action'],
RetAction>
: never
: WrapAction<
Spec,
Id,
Spec[Id]['actionMeta'],
Spec[Id]['action']>;
export interface MuRDAStructTypes<Spec extends MuRDAStructSpec> {
stateSchema:MuStruct<{
[id in keyof Spec]:Spec[id]['stateSchema'];
}>;
state:this['stateSchema']['identity'];
actionSchema:MuUnion<{
[id in keyof Spec]:Spec[id]['actionSchema'];
}>;
action:this['actionSchema']['identity'];
storeSchema:MuStruct<{
[id in keyof Spec]:Spec[id]['storeSchema'];
}>;
store:this['storeSchema']['identity'];
stores:{
[id in keyof Spec]:Spec[id]['emptyStore'];
};
actionMeta:{
type:'store';
action:{
type:'table';
table:{
[id in keyof Spec]:StripBindMeta<Spec[id]['actionMeta']>;
};
};
};
}
export class MuRDAStructStore<
Spec extends MuRDAStructSpec,
RDA extends MuRDAStruct<Spec>> implements MuRDAStore<RDA> {
public stores:MuRDAStructTypes<Spec>['stores'];
constructor (stores:MuRDAStructTypes<Spec>['stores']) {
this.stores = stores;
}
public state(rda:RDA, out:MuRDATypes<RDA>['state']) {
const ids = Object.keys(this.stores);
for (let i = 0; i < ids.length; ++i) {
const id = ids[i];
(<any>out)[id] = this.stores[id].state(rda.rdas[id], out[id]);
}
return out;
}
public apply(rda:RDA, action:MuRDATypes<RDA>['action']) : boolean {
return this.stores[action.type].apply(rda.rdas[action.type], action.data);
}
public inverse(rda:RDA, action:MuRDATypes<RDA>['action']) : MuRDATypes<RDA>['action'] {
const result = rda.actionSchema.alloc();
result.type = action.type;
const x = result.data = this.stores[action.type].inverse(rda.rdas[action.type], action.data);
return <{
type:(typeof action.type);
data:(typeof x);
}>result;
}
public serialize (rda:RDA, out:MuRDATypes<RDA>['serializedStore']) {
const ids = Object.keys(this.stores);
for (let i = 0; i < ids.length; ++i) {
const id = ids[i];
(<any>out)[id] = this.stores[id].serialize(rda.rdas[id], out[id]);
}
return out;
}
public free(rda:RDA) {
const ids = Object.keys(this.stores);
for (let i = 0; i < ids.length; ++i) {
const id = ids[i];
this.stores[id].free(rda.rdas[id]);
}
}
}
export class MuRDAStruct<Spec extends { [prop:string]:MuRDA<any, any, any, any> }>
implements MuRDA<
MuRDAStructTypes<Spec>['stateSchema'],
MuRDAStructTypes<Spec>['actionSchema'],
MuRDAStructTypes<Spec>['storeSchema'],
MuRDAStructTypes<Spec>['actionMeta']> {
public readonly rdas:Spec;
public readonly stateSchema:MuRDAStructTypes<Spec>['stateSchema'];
public readonly actionSchema:MuRDAStructTypes<Spec>['actionSchema'];
public readonly storeSchema:MuRDAStructTypes<Spec>['storeSchema'];
public readonly actionMeta:MuRDAStructTypes<Spec>['actionMeta'];
public readonly action:((store:MuRDAStructStore<Spec, MuRDAStruct<Spec>>) => {
[id in keyof Spec]:StripBindAndWrap<Spec, id>;
});
public readonly emptyStore:MuRDAStructStore<Spec, this>;
private _saveStore:any = null;
private _wrapDispatch<Id extends keyof Spec>(id:Id, rda:Spec[Id]) {
const self = this;
function wrapPartial(root:MuRDAActionMeta, dispatch) {
const savedPartial = { data:<any>null };
function wrapPartialRec (meta:MuRDAActionMeta, index:string) {
if (meta.type === 'unit') {
return (new Function(
'rda',
'partial',
`/* ${id}:${index} */ return function() { var result = rda.actionSchema.alloc(); result.type = "${id}"; result.data = partial.data${index}.apply(null, arguments); return result; }`,
))(self, savedPartial);
} else if (meta.type === 'table') {
const result:any = {};
const keys = Object.keys(meta.table);
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
result[key] = wrapPartialRec(meta.table[key], `${index}["${key}"]`);
}
return result;
} else if (meta.type === 'partial') {
return wrapPartial(meta.action, (new Function(
'partial',
`/* ${id}:${index} */ return function () { return partial.data${index}.apply(null, arguments); }`,
))(savedPartial));
}
return {};
}
return (new Function(
'dispatch',
'partial',
'wrappedDispatch',
`/* ${id} */ return function () { partial.data = dispatch.apply(null, arguments); return wrappedDispatch; }`,
))(dispatch, savedPartial, wrapPartialRec(root, ''));
}
function wrapAction (meta:MuRDAActionMeta, dispatch:any) {
if (meta.type === 'unit') {
return function (...args) {
const result = self.actionSchema.alloc();
result.type = id;
result.data = dispatch.apply(null, args);
return result;
};
} else if (meta.type === 'table') {
const result:any = {};
const ids = Object.keys(meta.table);
for (let i = 0; i < ids.length; ++i) {
const key = ids[i];
result[key] = wrapAction(meta.table[key], dispatch);
}
return result;
} else if (meta.type === 'partial') {
return wrapPartial(meta.action, dispatch);
}
return {};
}
function wrapStore(meta:MuRDAActionMeta, index:string) {
if (meta.type === 'unit') {
return (new Function(
'rda',
'dispatch',
`/* ${id}:${index} */ return function() { var result = rda.actionSchema.alloc(); result.type = "${id}"; result.data = dispatch(rda._saveStore.stores["${id}"])${index}.apply(null, arguments); return result; }`,
))(self, rda.action);
} else if (meta.type === 'table') {
const result:any = {};
const ids = Object.keys(meta.table);
for (let i = 0; i < ids.length; ++i) {
const key = ids[i];
result[key] = wrapStore(meta.table[key], `${index}["${key}"]`);
}
return result;
} else if (meta.type === 'partial') {
return wrapPartial(
meta.action,
(new Function(
'rda',
'dispatch',
`/* ${id}:${index} */ return function() { return dispatch(rda._saveStore.stores["${id}"])${index}.apply(null, arguments); }`,
))(self, rda.action));
}
return {};
}
if (rda.actionMeta.type !== 'store') {
this.actionMeta.action.table[id] = rda.actionMeta;
return wrapAction(rda.actionMeta, rda.action);
} else {
this.actionMeta.action.table[id] = rda.actionMeta.action;
return wrapStore(rda.actionMeta.action, '');
}
}
constructor (spec:Spec) {
this.rdas = spec;
// construct schemas for state and actions
const stateSpec:any = {};
const actionSpec:any = {};
const storeSpec:any = {};
const emptyStores:any = {};
const props = Object.keys(spec);
for (let i = 0; i < props.length; ++i) {
const prop = props[i];
const rda = spec[prop];
stateSpec[prop] = rda.stateSchema;
actionSpec[prop] = rda.actionSchema;
storeSpec[prop] = rda.storeSchema;
emptyStores[prop] = rda.emptyStore;
}
this.stateSchema = new MuStruct(stateSpec);
this.actionSchema = new MuUnion(actionSpec);
this.storeSchema = new MuStruct(storeSpec);
this.emptyStore = new MuRDAStructStore(emptyStores);
// Generate action meta data and store stuff
this.actionMeta = <any>{
type:'store',
action:{
type:'table',
table:{},
},
};
const storeDispatch:any = {};
for (let i = 0; i < props.length; ++i) {
const prop = props[i];
storeDispatch[prop] = this._wrapDispatch(prop, <any>spec[prop]);
}
this.action = <any>((store) => {
this._saveStore = store;
return storeDispatch;
});
}
public createStore (state:MuRDAStructTypes<Spec>['state']) : MuRDAStructStore<Spec, this> {
const stores:any = {};
const ids = Object.keys(this.rdas);
for (let i = 0; i < ids.length; ++i) {
const id = ids[i];
stores[id] = this.rdas[id].createStore(state[id]);
}
return new MuRDAStructStore<Spec, this>(stores);
}
public parse (store:MuRDAStructTypes<Spec>['store']) : MuRDAStructStore<Spec, this> {
const stores:any = {};
const ids = Object.keys(this.rdas);
for (let i = 0; i < ids.length; ++i) {
const id = ids[i];
stores[id] = this.rdas[id].parse(store[id]);
}
return new MuRDAStructStore<Spec, this>(stores);
}
}