UNPKG

mudb

Version:

Real-time database for multiplayer games

374 lines (336 loc) 16.7 kB
import * as tape from 'tape'; import { MuFloat64, MuStruct, MuUint32, MuUTF8, MuInt8, MuASCII, MuVarint, MuBoolean, MuDate } from '../../schema'; import { MuRDA, MuRDAStore, MuRDAConstant, MuRDARegister, MuRDAList, MuRDAMap, MuRDAStruct } from '../index'; function createTest<T extends MuRDA<any, any, any, any>> (t:tape.Test, store:MuRDAStore<T>, rda:T) { return function (action:T['actionSchema']['identity'], stateAfter:T['stateSchema']['identity']) { t.equal(store.apply(rda, action), true, JSON.stringify(action)); t.deepEqual(store.state(rda, rda.stateSchema.alloc()), stateAfter); }; } tape('constant', (t) => { const store = new MuRDAConstant(new MuFloat64()).createStore(0); t.false(store.apply(), 'always false'); t.end(); }); tape('register', (t) => { const Durability = new MuRDARegister(new MuFloat64()); const durabilityStore = Durability.createStore(100); t.true(durabilityStore.apply(Durability, 99.97), 'reduce durability'); t.equal(durabilityStore.state(Durability, 100), 99.97, 'get new durability'); const User = new MuRDARegister(new MuStruct({ id: new MuUint32(), name: new MuUTF8(), })); const userStore = User.createStore(User.stateSchema.alloc()); const u = {id: 12345, name: 'Mikola'}; t.true(userStore.apply(User, u), 'initiate user'); t.deepEqual(userStore.state(User, User.stateSchema.alloc()), u, 'get user info'); t.isNot(userStore.state(User, User.stateSchema.alloc()), u, 'should be a copy'); t.end(); }); tape('constrain', (t) => { const R = new MuRDARegister(new MuFloat64(), (x) => Math.max(0, Math.min(1, +x || 0))); const store = R.createStore(0); store.apply(R, 0.1); t.equal(store.state(R, 0), 0.1); store.apply(R, -0.1); t.equal(store.state(R, 0), 0); store.apply(R, 1.1); t.equal(store.state(R, 0), 1); store.apply(R, NaN); t.equal(store.state(R, 0), 0); t.end(); }); tape('list of lists', (t) => { const L = new MuRDAList(new MuRDAList(new MuRDARegister(new MuInt8()))); const store = L.createStore([]); const dispatchers = L.action(store); const test = createTest(t, store, L); test(dispatchers.push([]), [[]]); test(dispatchers.update(0).push(), [[]]); test(dispatchers.update(0).push(1), [[1]]); test(dispatchers.update(0).push(2, 3), [[1, 2, 3]]); test(dispatchers.update(0).pop(), [[1, 2]]); test(dispatchers.update(0).pop(2), [[]]); test(dispatchers.update(0).pop(3), [[]]); test(dispatchers.update(0).unshift(5), [[5]]); test(dispatchers.update(0).unshift(1, 3), [[1, 3, 5]]); test(dispatchers.update(0).update(2)(6), [[1, 3, 6]]); test(dispatchers.update(0).splice(0, 0, 0), [[0, 1, 3, 6]]); test(dispatchers.update(0).splice(2, 0, 2), [[0, 1, 2, 3, 6]]); test(dispatchers.update(0).splice(4, 0, 4, 5), [[0, 1, 2, 3, 4, 5, 6]]); test(dispatchers.update(0).splice(7, 1), [[0, 1, 2, 3, 4, 5, 6]]); test(dispatchers.update(0).splice(2, 2), [[0, 1, 4, 5, 6]]); test(dispatchers.update(0).splice(1, 3, 5, 4, 1), [[0, 5, 4, 1, 6]]); test(dispatchers.update(0).swap(1, 3), [[0, 1, 4, 5, 6]]); test(dispatchers.update(0).swap(4, 0), [[6, 1, 4, 5, 0]]); test(dispatchers.update(0).swap(4, 4), [[6, 1, 4, 5, 0]]); test(dispatchers.update(0).swap(4, 5), [[6, 1, 4, 5, 0]]); test(dispatchers.update(0).sort((a, b) => a - b), [[0, 1, 4, 5, 6]]); test(dispatchers.update(0).reverse(), [[6, 5, 4, 1, 0]]); test(dispatchers.update(0).reverse(), [[0, 1, 4, 5, 6]]); test(dispatchers.update(0).shift(), [[1, 4, 5, 6]]); test(dispatchers.update(0).shift(2), [[5, 6]]); test(dispatchers.update(0).shift(3), [[]]); test(dispatchers.update(0).reset([1, 2, 3]), [[1, 2, 3]]); test(dispatchers.update(0).reset([4, 5, 6]), [[4, 5, 6]]); test(dispatchers.update(0).clear(), [[]]); t.end(); }); tape('list of structs of list of structs', (t) => { const L = new MuRDAList(new MuRDAStruct({ s: new MuRDAStruct({ f: new MuRDARegister(new MuFloat64()), }), l: new MuRDAList( new MuRDAStruct({ u: new MuRDARegister(new MuUTF8()), }), ), })); const store = L.createStore([]); const dispatchers = L.action(store); const test = createTest(t, store, L); test(dispatchers.pop(), []); test(dispatchers.shift(), []); test(dispatchers.push({s: {f: 11.11}, l: []}, {s: {f: 22.11}, l: [{u: '22.11'}]}), [{s: {f: 11.11}, l: []}, {s: {f: 22.11}, l: [{u: '22.11'}]}]); test(dispatchers.update(0).s.f(11.22), [{s: {f: 11.22}, l: []}, {s: {f: 22.11}, l: [{u: '22.11'}]}]); test(dispatchers.update(1).s.f(22.22), [{s: {f: 11.22}, l: []}, {s: {f: 22.22}, l: [{u: '22.11'}]}]); test(dispatchers.update(1).l.update(0).u('22.22'), [{s: {f: 11.22}, l: []}, {s: {f: 22.22}, l: [{u: '22.22'}]}]); test(dispatchers.pop(), [{s: {f: 11.22}, l: []}]); test(dispatchers.update(0).l.pop(), [{s: {f: 11.22}, l: []}]); test(dispatchers.update(0).l.shift(), [{s: {f: 11.22}, l: []}]); test(dispatchers.update(0).l.push({u: '11.11'}, {u: '11.22'}, {u: '11.33'}), [{s: {f: 11.22}, l: [{u: '11.11'}, {u: '11.22'}, {u: '11.33'}]}]); test(dispatchers.update(0).l.pop(), [{s: {f: 11.22}, l: [{u: '11.11'}, {u: '11.22'}]}]); test(dispatchers.update(0).l.shift(), [{s: {f: 11.22}, l: [{u: '11.22'}]}]); test(dispatchers.update(0).l.unshift({u: '11.00'}, {u: '11.11'}), [{s: {f: 11.22}, l: [{u: '11.00'}, {u: '11.11'}, {u: '11.22'}]}]); test(dispatchers.update(0).l.splice(1, 0, {u: '11.33'}), [{s: {f: 11.22}, l: [{u: '11.00'}, {u: '11.33'}, {u: '11.11'}, {u: '11.22'}]}]); test(dispatchers.update(0).l.splice(1, 0, {u: '11.11'}, {u: '11.22'}), [{s: {f: 11.22}, l: [{u: '11.00'}, {u: '11.11'}, {u: '11.22'}, {u: '11.33'}, {u: '11.11'}, {u: '11.22'}]}]); test(dispatchers.update(0).l.splice(1, 1), [{s: {f: 11.22}, l: [{u: '11.00'}, {u: '11.22'}, {u: '11.33'}, {u: '11.11'}, {u: '11.22'}]}]); test(dispatchers.update(0).l.splice(1, 2), [{s: {f: 11.22}, l: [{u: '11.00'}, {u: '11.11'}, {u: '11.22'}]}]); test(dispatchers.update(0).l.clear(), [{s: {f: 11.22}, l: []}]); test(dispatchers.update(0).l.reset([{u: '11.33'}, {u: '11.44'}, {u: '11.55'}]), [{s: {f: 11.22}, l: [{u: '11.33'}, {u: '11.44'}, {u: '11.55'}]}]); t.end(); }); tape('map of constants', (t) => { const M = new MuRDAMap(new MuVarint(), new MuRDAConstant(new MuDate())); const store = M.createStore(M.stateSchema.identity); const dispatchers = M.action(store); const test = createTest(t, store, M); test(dispatchers.clear(), {}); test(dispatchers.move(0, 1), {}); test(dispatchers.remove(0), {}); test(dispatchers.reset({}), {}); test(dispatchers.set(0, new Date(0)), {0: new Date(0)}); test(dispatchers.set(1, new Date(1000)), {0: new Date(0), 1: new Date(1000)}); test(dispatchers.move(1, 0), {0: new Date(1000)}); test(dispatchers.move(0, 1), {1: new Date(1000)}); test(dispatchers.set(2, new Date(2000)), {1: new Date(1000), 2: new Date(2000)}); test(dispatchers.move(2, 1), {1: new Date(2000)}); test(dispatchers.move(2, 0), {1: new Date(2000)}); test(dispatchers.remove(1), {}); test(dispatchers.move(1, 0), {}); test(dispatchers.reset({0: new Date(0), 1: new Date(1000)}), {0: new Date(0), 1: new Date(1000)}); test(dispatchers.reset({2: new Date(2000)}), {2: new Date(2000)}); test(dispatchers.clear(), {}); // contrived invalid actions test(dispatchers.reset({0: new Date(0)}), {0: new Date(0)}); let action:any = dispatchers.remove(0); action.data.id = -1; t.equal(store.apply(M, action), false, JSON.stringify(action)); t.deepEqual(store.state(M, M.stateSchema.alloc()), {0: new Date(0)}); action = dispatchers.move(0, 1); action.data.id = -1; t.equal(store.apply(M, action), false, JSON.stringify(action)); t.deepEqual(store.state(M, M.stateSchema.alloc()), {0: new Date(0)}); action = dispatchers.set(1, new Date(1)); action.type = 'foo'; t.equal(store.apply(M, action), false, JSON.stringify(action)); t.deepEqual(store.state(M, M.stateSchema.alloc()), {0: new Date(0)}); t.end(); }); tape('map of maps of registers', (t) => { const M = new MuRDAMap( new MuASCII(), new MuRDAMap(new MuASCII(), new MuRDARegister(new MuVarint())), ); const store = M.createStore(M.stateSchema.identity); const dispatchers = M.action(store); const test = createTest(t, store, M); test(dispatchers.update('a').clear(), {}); test(dispatchers.update('a').set('a', 0), {}); test(dispatchers.update('a').update('a')(1), {}); test(dispatchers.update('a').move('a', 'b'), {}); test(dispatchers.update('a').remove('b'), {}); test(dispatchers.update('a').reset({'a': 0}), {}); test(dispatchers.set('foo', {}), {foo: {}}); test(dispatchers.update('foo').set('a', 0), {foo: {a: 0}}); test(dispatchers.update('foo').set('a', 1), {foo: {a: 1}}); test(dispatchers.update('foo').move('a', 'b'), {foo: {b: 1}}); test(dispatchers.update('foo').move('b', 'b'), {foo: {b: 1}}); test(dispatchers.update('foo').set('a', 0), {foo: {a: 0, b: 1}}); test(dispatchers.update('foo').move('b', 'a'), {foo: {a: 1}}); test(dispatchers.update('foo').move('b', 'c'), {foo: {a: 1}}); test(dispatchers.update('foo').remove('a'), {foo: {}}); test(dispatchers.update('foo').move('a', 'b'), {foo: {}}); test(dispatchers.update('foo').reset({a: 0, b: 1}), {foo: {a: 0, b: 1}}); test(dispatchers.update('foo').reset({c: 2}), {foo: {c: 2}}); test(dispatchers.update('foo').update('c')(3), {foo: {c: 3}}); test(dispatchers.update('foo').clear(), {foo: {}}); test(dispatchers.clear(), {}); test(dispatchers.reset({foo: {bar: 0}}), {foo: {bar: 0}}); // contrived invalid actions let action:any = dispatchers.update('foo').update('bar')(1); action.data.id = -1; t.equal(store.apply(M, action), false, JSON.stringify(action)); t.deepEqual(store.state(M, M.stateSchema.alloc()), {foo: {bar: 0}}); action = dispatchers.update('foo').update('bar')(1); action.data.action.data.id = -1; t.equal(store.apply(M, action), false, JSON.stringify(action)); t.deepEqual(store.state(M, M.stateSchema.alloc()), {foo: {bar: 0}}); t.end(); }); tape('map of structs', (t) => { const M = new MuRDAMap(new MuVarint(), new MuRDAStruct({ b: new MuRDARegister(new MuBoolean()), v: new MuRDARegister(new MuVarint()), u: new MuRDARegister(new MuUTF8()), d: new MuRDAConstant(new MuDate()), })); const store = M.createStore(M.stateSchema.identity); const dispatchers = M.action(store); const test = createTest(t, store, M); test(dispatchers.set(127, {b: false, v: 0, u: '', d: new Date(0)}), {127: {b: false, v: 0, u: '', d: new Date(0)}}); test(dispatchers.move(127, 128), {128: {b: false, v: 0, u: '', d: new Date(0)}}); test(dispatchers.update(128).b(true), {128: {b: true, v: 0, u: '', d: new Date(0)}}); test(dispatchers.update(128).v(1), {128: {b: true, v: 1, u: '', d: new Date(0)}}); test(dispatchers.update(128).u('Iñtërnâtiônàlizætiøn☃💩'), {128: {b: true, v: 1, u: 'Iñtërnâtiônàlizætiøn☃💩', d: new Date(0)}}); test(dispatchers.remove(128), {}); test(dispatchers.reset({128: {b: true, v: 1, u: 'Iñtërnâtiônàlizætiøn☃💩', d: new Date(0)}}), {128: {b: true, v: 1, u: 'Iñtërnâtiônàlizætiøn☃💩', d: new Date(0)}}); test(dispatchers.clear(), {}); t.end(); }); tape('map of structs of structs of structs', (t) => { const M = new MuRDAMap( new MuASCII(), new MuRDAStruct({ s: new MuRDAStruct({ s: new MuRDAStruct({ v: new MuRDARegister(new MuVarint()), }), }), }), ); const store = M.createStore(M.stateSchema.identity); const dispatchers = M.action(store); const test = createTest(t, store, M); test(dispatchers.set('foo', {s: {s: {v: 0}}}), {foo: {s: {s: {v: 0}}}}); test(dispatchers.move('foo', 'bar'), {bar: {s: {s: {v: 0}}}}); test(dispatchers.update('bar').s.s.v(128), {bar: {s: {s: {v: 128}}}}); test(dispatchers.move('bar', 'baz'), {baz: {s: {s: {v: 128}}}}); test(dispatchers.update('bar').s.s.v(129), {baz: {s: {s: {v: 128}}}}); test(dispatchers.remove('baz'), {}); test(dispatchers.reset({ bar: {s: {s: {v: 128}}}, baz: {s: {s: {v: 0}}}, }), { bar: {s: {s: {v: 128}}}, baz: {s: {s: {v: 0}}}, }); test(dispatchers.move('bar', 'baz'), {baz: {s: {s: {v: 128}}}}); test(dispatchers.clear(), {}); t.end(); }); tape('map of structs of maps of structs', (t) => { const M = new MuRDAMap(new MuVarint(), new MuRDAStruct({ m: new MuRDAMap(new MuASCII(), new MuRDAStruct({ v: new MuRDARegister(new MuVarint()), })), })); const store = M.createStore(M.stateSchema.identity); const dispatchers = M.action(store); const test = createTest(t, store, M); test(dispatchers.set(16383, { m: {}}), {16383: {m: {}}}); test(dispatchers.move(16383, 16384), {16384: {m: {}}}); test(dispatchers.update(16384).m.set('foo', {v: 127}), {16384: {m: {foo: {v: 127}}}}); test(dispatchers.update(16384).m.move('foo', 'bar'), {16384: {m: {bar: {v: 127}}}}); test(dispatchers.update(16384).m.update('bar').v(128), {16384: {m: {bar: {v: 128}}}}); test(dispatchers.update(16384).m.update('foo').v(128), {16384: {m: {bar: {v: 128}}}}); test(dispatchers.update(16384).m.remove('bar'), {16384: {m: {}}}); test(dispatchers.update(16384).m.reset({ foo: {v: 127}, bar: {v: 128}, }), {16384: {m: { foo: {v: 127}, bar: {v: 128}, }}}); test(dispatchers.update(16384).m.move('foo', 'bar'), {16384: {m: {bar: {v: 127}}}}); test(dispatchers.update(16384).m.clear(), {16384: {m: {}}}); t.end(); }); tape('non-empty output map', (t) => { const M = new MuRDAMap(new MuVarint(), new MuRDAConstant(new MuDate())); const store = M.createStore(M.stateSchema.identity); const dispatchers = M.action(store); t.deepEqual(store.state(M, {}), {}); t.deepEqual(store.state(M, {0: new Date(0)}), {}); store.apply(M, dispatchers.reset({127: new Date(127), 128: new Date(128)})); t.deepEqual(store.state(M, {0: new Date(0)}), {127: new Date(127), 128: new Date(128)}); t.deepEqual(store.state(M, {127: new Date(0)}), {127: new Date(127), 128: new Date(128)}); t.deepEqual(store.state(M, {127: new Date(127), 128: new Date(128)}), {127: new Date(127), 128: new Date(128)}); store.apply(M, dispatchers.remove(127)); t.deepEqual(store.state(M, {127: new Date(127), 128: new Date(128)}), {128: new Date(128)}); t.end(); }); tape('struct', (t) => { const S = new MuRDAStruct({ c: new MuRDAConstant(new MuInt8()), rf: new MuRDARegister(new MuFloat64()), rs: new MuRDARegister(new MuStruct({ f: new MuFloat64(), u: new MuUTF8(), })), s: new MuRDAStruct({ rf: new MuRDARegister(new MuFloat64()), rs: new MuRDARegister(new MuStruct({ f: new MuFloat64(), u: new MuUTF8(), })), s: new MuRDAStruct({ rf: new MuRDARegister(new MuFloat64()), rs: new MuRDARegister(new MuStruct({ f: new MuFloat64(), u: new MuUTF8(), })), }), }), }); const store = S.createStore(S.stateSchema.identity); const dispatcher = S.action(store); const actions:any[] = []; actions.push(dispatcher.rf(11.11)); actions.push(dispatcher.rs({f: 22.22, u: 'a'})); actions.push(dispatcher.s.rf(33.33)); actions.push(dispatcher.s.rs({f: 44.44, u: 'b'})); actions.push(dispatcher.s.s.rf(55.55)); actions.push(dispatcher.s.s.rs({f: 66.66, u: 'Iñtërnâtiônàlizætiøn☃💩'})); for (let i = 0; i < actions.length; ++i) { t.true(store.apply(S, actions[i]), JSON.stringify(actions[i])); } t.deepEqual( store.state(S, S.stateSchema.alloc()), { c: 0, rf: 11.11, rs: {f: 22.22, u: 'a'}, s: { rf: 33.33, rs: {f: 44.44, u: 'b'}, s: { rf: 55.55, rs: {f: 66.66, u: 'Iñtërnâtiônàlizætiøn☃💩'}, }, }, }, ); t.end(); });