├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── config.yml
└── workflows
│ └── main.yml
├── .gitignore
├── .npmignore
├── .prettierrc
├── .vscode
└── extensions.json
├── LICENSE
├── README.md
├── bundle
├── typed-array-buffer-schema.js
└── typed-array-buffer-schema.js.LICENSE.txt
├── package.json
├── readme
├── logo.png
└── logo.svg
├── src
├── bundle.ts
├── deep-sort-object.ts
├── dev.ts
├── index.ts
├── lib.ts
├── model.ts
├── schema.ts
├── serialize.ts
└── views.ts
├── test
├── benchmark.test.js
├── dataViews.test.js
├── emptyData.test.js
├── schemaId.test.js
├── serializeDeserialize.test.js
├── simple.test.js
└── specialTypes.test.js
├── tsconfig.json
├── webpack.bundle.js
└── webpack.bundle.tmp.js
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: yandeu
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **Have a question?**
13 | Join the [discussions](https://github.com/geckosio/geckos.io/discussions) instead.
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 | contact_links:
3 | - name: Ask a question
4 | url: https://github.com/geckosio/geckos.io/discussions
5 | about: Ask questions and discuss with other community members
6 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | node-version: [18.x, 16.x]
12 |
13 | steps:
14 | - name: Checkout repository
15 | uses: actions/checkout@v3
16 |
17 | - name: Use Node.js ${{ matrix.node-version }}
18 | uses: actions/setup-node@v3
19 | with:
20 | node-version: ${{ matrix.node-version }}
21 |
22 | - name: Install and Build
23 | run: |
24 | npm install
25 | npm run build
26 |
27 | - name: Test
28 | run: npm test
29 |
30 | - name: Webpack Bundle
31 | run: npm run bundle:tmp
32 |
33 | - name: Upload code coverage
34 | uses: codecov/codecov-action@v2
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /coverage
2 | /lib
3 | /node_modules
4 | /package-lock.json
5 | /bundle/typed-array-buffer-schema.tmp.js
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | !/.npmrc
4 | !/README.md
5 | !/bundle
6 | !/lib
7 | !/package.json
8 | !/readme
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | "@yandeu/prettier-config"
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["esbenp.prettier-vscode"]
3 | }
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2020, Yannick Deubel (https://github.com/yandeu); Project Url: https://github.com/geckosio/typed-array-buffer-schema
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 | # Typed Array Buffer Schema
6 |
7 | ## A Schema based Object to Buffer converter
8 |
9 | #### Easily convert/compress your JavaScript Objects to Binary Data using a simple to use Schema.
10 |
11 | [](https://www.npmjs.com/package/@geckos.io/typed-array-buffer-schema)
12 | [](https://github.com/geckosio/typed-array-buffer-schema/actions?query=workflow%3ACI)
13 | [](https://github.com/geckosio/typed-array-buffer-schema/commits/master)
14 | [](https://www.npmjs.com/package/@geckos.io/typed-array-buffer-schema)
15 | [](https://codecov.io/gh/geckosio/typed-array-buffer-schema)
16 | [](https://www.typescriptlang.org/)
17 |
18 |
19 |
20 | ---
21 |
22 | ## Introduction
23 |
24 | Checkout this short introduction video on YouTube!
25 |
26 | https://youtu.be/TBd1miOrLPQ
27 |
28 | ## Install
29 |
30 | Install from npm.
31 |
32 | ```console
33 | npm install @geckos.io/typed-array-buffer-schema
34 | ```
35 |
36 | Or use the bundled version.
37 |
38 | ```html
39 |
40 |
44 | ```
45 |
46 | ## Snapshot Interpolation
47 |
48 | You can easily combine this library with the Snapshot Interpolation library [@geckos.io/snapshot-interpolation](https://www.npmjs.com/package/@geckos.io/snapshot-interpolation).
49 |
50 | ## Usage
51 |
52 | #### model.js
53 |
54 | ```js
55 | import { BufferSchema, Model } from '@geckos.io/typed-array-buffer-schema'
56 | import { uint8, int16, uint16, int64, string8 } from '@geckos.io/typed-array-buffer-schema'
57 |
58 | const playerSchema = BufferSchema.schema('player', {
59 | id: uint8,
60 | // The length parameter controls the length of a String8 or String16
61 | name: { type: string8, length: 6 },
62 | // The digits parameter controls where the decimal point is placed
63 | // Therefore, it divides the maximum and minimum values by 10^n
64 | x: { type: int16, digits: 2 },
65 | y: { type: int16, digits: 2 }
66 | })
67 |
68 | const towerSchema = BufferSchema.schema('tower', {
69 | id: uint8,
70 | health: uint8,
71 | team: uint8
72 | })
73 |
74 | const mainSchema = BufferSchema.schema('main', {
75 | time: int64,
76 | tick: uint16,
77 | players: [playerSchema],
78 | towers: [towerSchema]
79 | })
80 |
81 | export const mainModel = new Model(mainSchema)
82 |
83 | // if you get the error "RangeError: Offset is outside the bounds of the DataView", increase the max. bufferSize.
84 | // default is 8 (8KB).
85 | export const mainModel = new Model(mainSchema, 8)
86 | ```
87 |
88 | #### server.js
89 |
90 | ```js
91 | import { mainModel } from './model'
92 |
93 | const gameState = {
94 | time: new Date().getTime(),
95 | tick: 32580,
96 | players: [
97 | { id: 0, name: 'Mistin', x: -14.43, y: 47.78 },
98 | { id: 1, name: 'Coobim', x: 21.85, y: -78.48 }
99 | ],
100 | towers: [
101 | { id: 0, health: 100, team: 0 },
102 | { id: 1, health: 89, team: 0 },
103 | { id: 2, health: 45, team: 1 }
104 | ]
105 | }
106 |
107 | const buffer = mainModel.toBuffer(gameState)
108 |
109 | // toBuffer() shrunk the byte size from 241 to only 56
110 | // that is -77% compression!
111 | console.log(JSON.stringify(gameState).length) // 241
112 | console.log(buffer.byteLength) // 56
113 |
114 | // send the buffer to the client (using geckos.io or any other library)
115 | sendMessage(buffer)
116 | ```
117 |
118 | #### client.js
119 |
120 | ```js
121 | import { mainModel } from './model'
122 |
123 | onMessage(buffer => {
124 | // access your game state
125 | const gameState = mainModel.fromBuffer(buffer)
126 | })
127 | ```
128 |
129 | ## Schema ID
130 |
131 | Each Schema has an unique ID. To get the Schema ID from the Schema, Model or Buffer, use the helper functions listed below:
132 |
133 | ```ts
134 | // get the schema id
135 | const schemaId = BufferSchema.getIdFromSchema(schema)
136 |
137 | // get the id of the top level schema (added via new Schema())
138 | const modelId = BufferSchema.getIdFromModel(model)
139 |
140 | // get the id of the top level schema
141 | const bufferId = BufferSchema.getIdFromBuffer(buffer)
142 | ```
143 |
144 | ## DataViews
145 |
146 | A list of all supported dataViews
147 |
148 | ```js
149 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays
150 |
151 | /** -128 to 127 (1 byte) */
152 | export const int8 = { _type: 'Int8Array', _bytes: 1 }
153 | /** 0 to 255 (1 byte) */
154 | export const uint8 = { _type: 'Uint8Array', _bytes: 1 }
155 |
156 | /** -32768 to 32767 (2 bytes) */
157 | export const int16 = { _type: 'Int16Array', _bytes: 2 }
158 | /** 0 to 65535 (2 bytes) */
159 | export const uint16 = { _type: 'Uint16Array', _bytes: 2 }
160 |
161 | /** -2147483648 to 2147483647 (4 bytes) */
162 | export const int32 = { _type: 'Int32Array', _bytes: 4 }
163 | /** 0 to 4294967295 (4 bytes) */
164 | export const uint32 = { _type: 'Uint32Array', _bytes: 4 }
165 |
166 | /** -2^63 to 2^63-1 (8 bytes) */
167 | export const int64 = { _type: 'BigInt64Array', _bytes: 8 }
168 | /** 0 to 2^64-1 (8 bytes) */
169 | export const uint64 = { _type: 'BigUint64Array', _bytes: 8 }
170 |
171 | /** 1.2×10-38 to 3.4×1038 (7 significant digits e.g., 1.123456) (4 bytes) */
172 | export const float32 = { _type: 'Float32Array', _bytes: 4 }
173 |
174 | /** 5.0×10-324 to 1.8×10308 (16 significant digits e.g., 1.123...15) (8 bytes) */
175 | export const float64 = { _type: 'Float64Array', _bytes: 8 }
176 |
177 | /** 1 byte per character */
178 | export const string8 = { _type: 'String8', _bytes: 1 }
179 | /** 2 bytes per character */
180 | export const string16 = { _type: 'String16', _bytes: 2 }
181 |
182 | /** An array of 7 booleans */
183 | export const bool8 = { _type: 'BitArray8', _bytes: 1 }
184 | /** An array of 15 booleans */
185 | export const bool16 = { _type: 'BitArray16', _bytes: 2 }
186 | ```
187 |
--------------------------------------------------------------------------------
/bundle/typed-array-buffer-schema.js:
--------------------------------------------------------------------------------
1 | /*! For license information please see typed-array-buffer-schema.js.LICENSE.txt */
2 | var Schema;(()=>{var t={6057:(t,e)=>{"use strict";function r(t){return"[object Object]"===Object.prototype.toString.call(t)}Object.defineProperty(e,"__esModule",{value:!0}),e.isPlainObject=function(t){var e,i;return!1!==r(t)&&(void 0===(e=t.constructor)||!1!==r(i=e.prototype)&&!1!==i.hasOwnProperty("isPrototypeOf"))}},1989:(t,e,r)=>{var i=r(1789),n=r(401),o=r(7667),s=r(1327),a=r(1866);function l(t){var e=-1,r=null==t?0:t.length;for(this.clear();++e{var i=r(7040),n=r(4125),o=r(2117),s=r(7518),a=r(4705);function l(t){var e=-1,r=null==t?0:t.length;for(this.clear();++e{var i=r(852)(r(5639),"Map");t.exports=i},3369:(t,e,r)=>{var i=r(4785),n=r(1285),o=r(6e3),s=r(9916),a=r(5265);function l(t){var e=-1,r=null==t?0:t.length;for(this.clear();++e{var i=r(5639).Symbol;t.exports=i},9932:t=>{t.exports=function(t,e){for(var r=-1,i=null==t?0:t.length,n=Array(i);++r{var i=r(9465),n=r(7813),o=Object.prototype.hasOwnProperty;t.exports=function(t,e,r){var s=t[e];o.call(t,e)&&n(s,r)&&(void 0!==r||e in t)||i(t,e,r)}},8470:(t,e,r)=>{var i=r(7813);t.exports=function(t,e){for(var r=t.length;r--;)if(i(t[r][0],e))return r;return-1}},9465:(t,e,r)=>{var i=r(8777);t.exports=function(t,e,r){"__proto__"==e&&i?i(t,e,{configurable:!0,enumerable:!0,value:r,writable:!0}):t[e]=r}},4239:(t,e,r)=>{var i=r(2705),n=r(9607),o=r(2333),s=i?i.toStringTag:void 0;t.exports=function(t){return null==t?void 0===t?"[object Undefined]":"[object Null]":s&&s in Object(t)?n(t):o(t)}},8458:(t,e,r)=>{var i=r(3560),n=r(5346),o=r(3218),s=r(346),a=/^\[object .+?Constructor\]$/,l=Function.prototype,u=Object.prototype,c=l.toString,p=u.hasOwnProperty,y=RegExp("^"+c.call(p).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");t.exports=function(t){return!(!o(t)||n(t))&&(i(t)?y:a).test(s(t))}},611:(t,e,r)=>{var i=r(4865),n=r(1811),o=r(5776),s=r(3218),a=r(327);t.exports=function(t,e,r,l){if(!s(t))return t;for(var u=-1,c=(e=n(e,t)).length,p=c-1,y=t;null!=y&&++u{var i=r(2705),n=r(9932),o=r(1469),s=r(3448),a=i?i.prototype:void 0,l=a?a.toString:void 0;t.exports=function t(e){if("string"==typeof e)return e;if(o(e))return n(e,t)+"";if(s(e))return l?l.call(e):"";var r=e+"";return"0"==r&&1/e==-1/0?"-0":r}},1811:(t,e,r)=>{var i=r(1469),n=r(5403),o=r(5514),s=r(9833);t.exports=function(t,e){return i(t)?t:n(t,e)?[t]:o(s(t))}},4429:(t,e,r)=>{var i=r(5639)["__core-js_shared__"];t.exports=i},8777:(t,e,r)=>{var i=r(852),n=function(){try{var t=i(Object,"defineProperty");return t({},"",{}),t}catch(t){}}();t.exports=n},1957:(t,e,r)=>{var i="object"==typeof r.g&&r.g&&r.g.Object===Object&&r.g;t.exports=i},5050:(t,e,r)=>{var i=r(7019);t.exports=function(t,e){var r=t.__data__;return i(e)?r["string"==typeof e?"string":"hash"]:r.map}},852:(t,e,r)=>{var i=r(8458),n=r(7801);t.exports=function(t,e){var r=n(t,e);return i(r)?r:void 0}},9607:(t,e,r)=>{var i=r(2705),n=Object.prototype,o=n.hasOwnProperty,s=n.toString,a=i?i.toStringTag:void 0;t.exports=function(t){var e=o.call(t,a),r=t[a];try{t[a]=void 0;var i=!0}catch(t){}var n=s.call(t);return i&&(e?t[a]=r:delete t[a]),n}},7801:t=>{t.exports=function(t,e){return null==t?void 0:t[e]}},1789:(t,e,r)=>{var i=r(4536);t.exports=function(){this.__data__=i?i(null):{},this.size=0}},401:t=>{t.exports=function(t){var e=this.has(t)&&delete this.__data__[t];return this.size-=e?1:0,e}},7667:(t,e,r)=>{var i=r(4536),n=Object.prototype.hasOwnProperty;t.exports=function(t){var e=this.__data__;if(i){var r=e[t];return"__lodash_hash_undefined__"===r?void 0:r}return n.call(e,t)?e[t]:void 0}},1327:(t,e,r)=>{var i=r(4536),n=Object.prototype.hasOwnProperty;t.exports=function(t){var e=this.__data__;return i?void 0!==e[t]:n.call(e,t)}},1866:(t,e,r)=>{var i=r(4536);t.exports=function(t,e){var r=this.__data__;return this.size+=this.has(t)?0:1,r[t]=i&&void 0===e?"__lodash_hash_undefined__":e,this}},5776:t=>{var e=/^(?:0|[1-9]\d*)$/;t.exports=function(t,r){var i=typeof t;return!!(r=null==r?9007199254740991:r)&&("number"==i||"symbol"!=i&&e.test(t))&&t>-1&&t%1==0&&t{var i=r(1469),n=r(3448),o=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,s=/^\w*$/;t.exports=function(t,e){if(i(t))return!1;var r=typeof t;return!("number"!=r&&"symbol"!=r&&"boolean"!=r&&null!=t&&!n(t))||s.test(t)||!o.test(t)||null!=e&&t in Object(e)}},7019:t=>{t.exports=function(t){var e=typeof t;return"string"==e||"number"==e||"symbol"==e||"boolean"==e?"__proto__"!==t:null===t}},5346:(t,e,r)=>{var i,n=r(4429),o=(i=/[^.]+$/.exec(n&&n.keys&&n.keys.IE_PROTO||""))?"Symbol(src)_1."+i:"";t.exports=function(t){return!!o&&o in t}},7040:t=>{t.exports=function(){this.__data__=[],this.size=0}},4125:(t,e,r)=>{var i=r(8470),n=Array.prototype.splice;t.exports=function(t){var e=this.__data__,r=i(e,t);return!(r<0||(r==e.length-1?e.pop():n.call(e,r,1),--this.size,0))}},2117:(t,e,r)=>{var i=r(8470);t.exports=function(t){var e=this.__data__,r=i(e,t);return r<0?void 0:e[r][1]}},7518:(t,e,r)=>{var i=r(8470);t.exports=function(t){return i(this.__data__,t)>-1}},4705:(t,e,r)=>{var i=r(8470);t.exports=function(t,e){var r=this.__data__,n=i(r,t);return n<0?(++this.size,r.push([t,e])):r[n][1]=e,this}},4785:(t,e,r)=>{var i=r(1989),n=r(8407),o=r(7071);t.exports=function(){this.size=0,this.__data__={hash:new i,map:new(o||n),string:new i}}},1285:(t,e,r)=>{var i=r(5050);t.exports=function(t){var e=i(this,t).delete(t);return this.size-=e?1:0,e}},6e3:(t,e,r)=>{var i=r(5050);t.exports=function(t){return i(this,t).get(t)}},9916:(t,e,r)=>{var i=r(5050);t.exports=function(t){return i(this,t).has(t)}},5265:(t,e,r)=>{var i=r(5050);t.exports=function(t,e){var r=i(this,t),n=r.size;return r.set(t,e),this.size+=r.size==n?0:1,this}},4523:(t,e,r)=>{var i=r(8306);t.exports=function(t){var e=i(t,(function(t){return 500===r.size&&r.clear(),t})),r=e.cache;return e}},4536:(t,e,r)=>{var i=r(852)(Object,"create");t.exports=i},2333:t=>{var e=Object.prototype.toString;t.exports=function(t){return e.call(t)}},5639:(t,e,r)=>{var i=r(1957),n="object"==typeof self&&self&&self.Object===Object&&self,o=i||n||Function("return this")();t.exports=o},5514:(t,e,r)=>{var i=r(4523),n=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,o=/\\(\\)?/g,s=i((function(t){var e=[];return 46===t.charCodeAt(0)&&e.push(""),t.replace(n,(function(t,r,i,n){e.push(i?n.replace(o,"$1"):r||t)})),e}));t.exports=s},327:(t,e,r)=>{var i=r(3448);t.exports=function(t){if("string"==typeof t||i(t))return t;var e=t+"";return"0"==e&&1/t==-1/0?"-0":e}},346:t=>{var e=Function.prototype.toString;t.exports=function(t){if(null!=t){try{return e.call(t)}catch(t){}try{return t+""}catch(t){}}return""}},7813:t=>{t.exports=function(t,e){return t===e||t!=t&&e!=e}},1469:t=>{var e=Array.isArray;t.exports=e},3560:(t,e,r)=>{var i=r(4239),n=r(3218);t.exports=function(t){if(!n(t))return!1;var e=i(t);return"[object Function]"==e||"[object GeneratorFunction]"==e||"[object AsyncFunction]"==e||"[object Proxy]"==e}},3218:t=>{t.exports=function(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}},7005:t=>{t.exports=function(t){return null!=t&&"object"==typeof t}},3448:(t,e,r)=>{var i=r(4239),n=r(7005);t.exports=function(t){return"symbol"==typeof t||n(t)&&"[object Symbol]"==i(t)}},8306:(t,e,r)=>{var i=r(3369);function n(t,e){if("function"!=typeof t||null!=e&&"function"!=typeof e)throw new TypeError("Expected a function");var r=function(){var i=arguments,n=e?e.apply(this,i):i[0],o=r.cache;if(o.has(n))return o.get(n);var s=t.apply(this,i);return r.cache=o.set(n,s)||o,s};return r.cache=new(n.Cache||i),r}n.Cache=i,t.exports=n},6968:(t,e,r)=>{var i=r(611);t.exports=function(t,e,r){return null==t?t:i(t,e,r)}},9833:(t,e,r)=>{var i=r(531);t.exports=function(t){return null==t?"":i(t)}},6818:(t,e,r)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.deepSortObject=void 0;const i=r(6057),n=t=>{return!((0,i.isPlainObject)(t)||(e=t,Array.isArray(e)&&(!(e.length>0)||"object"==typeof e[0])));var e},o=([t,e],[r,i])=>n(e)&&n(i)?t.localeCompare(r):n(e)?-1:n(i)?1:t.localeCompare(r),s=(t,e)=>{let r;return Array.isArray(t)?t.map((function(t){return s(t,e)})):(0,i.isPlainObject)(t)?(r={},Object.entries(t).sort(e||o).forEach((function([t,i]){r[t]=s(i,e)})),r):t};e.deepSortObject=s},5563:(t,e,r)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.Lib=void 0;const i=r(6818),n=r(1734);class o{static newHash(t,e){let r=(t=>{let e=0;for(let r=0;r{const e=new DataView(t);let r="";for(let t=0;t<5;t++){const i=e.getUint8(t);r+=String.fromCharCode(i)}return r},o.getIdFromSchema=t=>t.id,o.getIdFromModel=t=>t.schema.id},3134:(t,e,r)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.Model=void 0;const i=r(488);class n extends i.Serialize{constructor(t,e=8){super(t,e),this.schema=t}}e.Model=n},1734:(t,e)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.Schema=void 0;class r{constructor(t,e,i){this._id=t,this._name=e,this._struct=i,this._bytes=0,r.Validation(i),this.calcBytes()}static Validation(t){}get id(){return this._id}get name(){return this._name}isSpecialType(t){return!Object.keys(t).filter((t=>"type"!=t&&"digits"!=t&&"length"!=t)).length}calcBytes(){const t=e=>{var r,i;for(let n in e){const o=(null==e?void 0:e._type)||!!this.isSpecialType(e)&&(null===(r=null==e?void 0:e.type)||void 0===r?void 0:r._type),s=(null==e?void 0:e._bytes)||!!this.isSpecialType(e)&&(null===(i=null==e?void 0:e.type)||void 0===i?void 0:i._bytes);if(!o&&e.hasOwnProperty(n))"object"==typeof e[n]&&t(e[n]);else{if("_type"!==n&&"type"!==n)continue;if(!s)continue;if("String8"===o||"String16"===o){const t=e.length||12;this._bytes+=s*t}else this._bytes+=s}}};t(this._struct)}get struct(){return this._struct}get bytes(){return this._bytes}}e.Schema=r},488:function(t,e,r){"use strict";var i=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.Serialize=void 0;const n=r(5563),o=i(r(6968)),s=r(6818);e.Serialize=class{constructor(t,e){this.schema=t,this.bufferSize=e,this._buffer=new ArrayBuffer(0),this._dataView=new DataView(this._buffer),this._bytes=0}refresh(){this._buffer=new ArrayBuffer(1024*this.bufferSize),this._dataView=new DataView(this._buffer),this._bytes=0}cropString(t,e){return t.padEnd(e," ").slice(0,e)}isSpecialType(t){return!Object.keys(t).filter((t=>"type"!=t&&"digits"!=t&&"length"!=t)).length}boolArrayToInt(t){let e="1";for(var r=0;r>>0).toString(2)].map((t=>"0"!=t)).slice(1)}flatten(t,e){let r=[];const i=(t,e)=>{var n,o,s,a,l,u,c,p,y,h,_,f;let d;for(d in(null==t?void 0:t._id)?r.push({d:t._id,t:"String8"}):(null===(n=null==t?void 0:t[0])||void 0===n?void 0:n._id)&&r.push({d:t[0]._id,t:"String8"}),(null==t?void 0:t._struct)?t=t._struct:(null===(o=null==t?void 0:t[0])||void 0===o?void 0:o._struct)&&(t=t[0]._struct),e)if(e.hasOwnProperty(d))if("object"==typeof e[d])Array.isArray(e)?i(t,e[parseInt(d)]):"BitArray8"===(null===(s=t[d])||void 0===s?void 0:s._type)||"BitArray16"===(null===(a=t[d])||void 0===a?void 0:a._type)?r.push({d:this.boolArrayToInt(e[d]),t:t[d]._type}):i(t[d],e[d]);else if((null===(u=null===(l=t[d])||void 0===l?void 0:l.type)||void 0===u?void 0:u._type)&&this.isSpecialType(t[d])){if((null===(c=t[d])||void 0===c?void 0:c.digits)&&(e[d]*=Math.pow(10,t[d].digits),e[d]=parseInt(e[d].toFixed(0))),null===(p=t[d])||void 0===p?void 0:p.length){const r=null===(y=t[d])||void 0===y?void 0:y.length;e[d]=this.cropString(e[d],r)}r.push({d:e[d],t:t[d].type._type})}else(null===(h=t[d])||void 0===h?void 0:h._type)&&("String8"!==(null===(_=t[d])||void 0===_?void 0:_._type)&&"String16"!==(null===(f=t[d])||void 0===f?void 0:f._type)||(e[d]=this.cropString(e[d],12)),r.push({d:e[d],t:t[d]._type}))};return i(t,e),r}toBuffer(t){let e=(0,s.deepSortObject)(t);const r=JSON.parse(JSON.stringify(e));this.refresh(),this.flatten(this.schema,r).forEach(((t,e)=>{if("String8"===t.t)for(let e=0;e-1;)e=s.indexOf(35,e),-1!==e&&(r.push(e),e++);let a=[];r.forEach((t=>{let e="";for(let r=0;r<5;r++)e+=String.fromCharCode(s[t+r]);a.push(e)}));let l=[];a.forEach(((t,e)=>{n.Lib._schemas.get(t)&&l.push({id:t,schema:n.Lib._schemas.get(t),startsAt:r[e]+5})}));let u={},c=0,p={};const y=t=>{var e,r;let n={};if("object"==typeof t)for(let o in t)if(t.hasOwnProperty(o)){const s=t[o];let a;if(((null===(e=null==s?void 0:s.type)||void 0===e?void 0:e._type)||(null===(r=null==s?void 0:s.type)||void 0===r?void 0:r._bytes)||this.isSpecialType(s))&&(a=s,s._type=s.type._type,s._bytes=s.type._bytes),s&&s._type&&s._bytes){const t=s._type,e=s._bytes;let r;if("String8"===t){r="";const t=s.length||12;for(let e=0;e{var i,n,o;let s=null===(i=e.schema)||void 0===i?void 0:i.struct,a=e.startsAt,u=t.byteLength,h=(null===(n=e.schema)||void 0===n?void 0:n.id)||"XX";"XX"===h&&console.error("ERROR: Something went horribly wrong!");try{u=l[r+1].startsAt-5}catch{}const _=(null===(o=e.schema)||void 0===o?void 0:o.bytes)||1,f=(u-a)/_;for(let t=0;t{if(t&&t._id&&t._id===e){let t=i.replace(/_struct\./,"").replace(/\.$/,"");n&&!Array.isArray(r)&&(r=[r]),""===t?u={...u,...r}:(0,o.default)(u,t,r)}else for(const n in t)if(t.hasOwnProperty(n)&&"object"==typeof t[n]){let o=Array.isArray(t)?"":`${n}.`;h(t[n],e,r,i+o,Array.isArray(t))}};for(let t=0;t{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.bool16=e.bool8=e.string16=e.string8=e.float64=e.float32=e.uint64=e.int64=e.uint32=e.int32=e.uint16=e.int16=e.uint8=e.int8=void 0,e.int8={_type:"Int8Array",_bytes:1},e.uint8={_type:"Uint8Array",_bytes:1},e.int16={_type:"Int16Array",_bytes:2},e.uint16={_type:"Uint16Array",_bytes:2},e.int32={_type:"Int32Array",_bytes:4},e.uint32={_type:"Uint32Array",_bytes:4},e.int64={_type:"BigInt64Array",_bytes:8},e.uint64={_type:"BigUint64Array",_bytes:8},e.float32={_type:"Float32Array",_bytes:4},e.float64={_type:"Float64Array",_bytes:8},e.string8={_type:"String8",_bytes:1},e.string16={_type:"String16",_bytes:2},e.bool8={_type:"BitArray8",_bytes:1},e.bool16={_type:"BitArray16",_bytes:2}}},e={};function r(i){var n=e[i];if(void 0!==n)return n.exports;var o=e[i]={exports:{}};return t[i].call(o.exports,o,o.exports,r),o.exports}r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}();var i={};(()=>{"use strict";var t=i;const e=r(5563),n=r(3134),o=r(7826);t.default={BufferSchema:e.Lib,Model:n.Model,int8:o.int8,uint8:o.uint8,int16:o.int16,uint16:o.uint16,int32:o.int32,uint32:o.uint32,int64:o.int64,uint64:o.uint64,float32:o.float32,float64:o.float64,string8:o.string8,string16:o.string16,bool8:o.bool8,bool16:o.bool16}})(),Schema=i.default})();
--------------------------------------------------------------------------------
/bundle/typed-array-buffer-schema.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*!
2 | * is-plain-object
3 | *
4 | * Copyright (c) 2014-2017, Jon Schlinkert.
5 | * Released under the MIT License.
6 | */
7 |
8 | /**
9 | * @copyright
10 | * Copyright (c) 2014 IndigoUnited (https://github.com/IndigoUnited)
11 | * Copyright (c) 2021 Yannick Deubel (https://github.com/yandeu)
12 | *
13 | * @license {@link https://github.com/geckosio/geckos.io/blob/master/LICENSE BSD-3-Clause}
14 | *
15 | * @description
16 | * copied and modified from deep-sort-object@1.0.2 (https://github.com/IndigoUnited/js-deep-sort-object/blob/master/index.js)
17 | * previously licensed under MIT (https://github.com/IndigoUnited/js-deep-sort-object/blob/master/LICENSE)
18 | */
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@geckos.io/typed-array-buffer-schema",
3 | "version": "1.2.1",
4 | "description": "A Schema based Object to Buffer converter",
5 | "main": "./lib/index.js",
6 | "types": "./lib/index.d.ts",
7 | "type": "commonjs",
8 | "scripts": {
9 | "test:ci": "npm i && npm run build && npm test && npm run bundle:tmp",
10 | "start": "npm run dev",
11 | "dev": "npm run build && npm-run-all --parallel dev:*",
12 | "dev:tsc": "tsc --watch",
13 | "dev:nodemon": "nodemon lib/dev.js --watch lib",
14 | "build": "npm run clean && tsc",
15 | "bundle": "webpack --config webpack.bundle.js",
16 | "bundle:tmp": "webpack --config webpack.bundle.tmp.js",
17 | "test": "jest --collectCoverage",
18 | "clean": "rimraf lib",
19 | "format": "prettier --write src/**/*.ts && prettier --write test/**/*.js",
20 | "preReleaseHook": "prepublishOnly",
21 | "prepublishOnly": "npm i && npm run build && npm test && npm run bundle"
22 | },
23 | "keywords": [
24 | "typed",
25 | "array",
26 | "buffer",
27 | "typedArray",
28 | "arrayBuffer",
29 | "serialize",
30 | "serialization",
31 | "schema",
32 | "binary"
33 | ],
34 | "author": "Yannick Deubel (https://github.com/yandeu)",
35 | "license": "BSD-3-Clause",
36 | "repository": {
37 | "type": "git",
38 | "url": "git://github.com/geckosio/typed-array-buffer-schema.git"
39 | },
40 | "bugs": {
41 | "url": "https://github.com/geckosio/typed-array-buffer-schema/issues"
42 | },
43 | "homepage": "http://geckos.io",
44 | "dependencies": {
45 | "is-plain-object": "^5.0.0",
46 | "lodash": "^4.17.21"
47 | },
48 | "devDependencies": {
49 | "@types/lodash": "^4.14.177",
50 | "@yandeu/prettier-config": "^0.0.3",
51 | "jest": "^27.4.0",
52 | "nodemon": "^2.0.15",
53 | "npm-run-all": "^4.1.5",
54 | "rimraf": "^3.0.2",
55 | "ts-loader": "^9.2.6",
56 | "typescript": "^4.5.2",
57 | "underscore": "^1.13.4",
58 | "webpack": "^5.64.4",
59 | "webpack-cli": "^4.9.1"
60 | },
61 | "funding": {
62 | "url": "https://github.com/sponsors/yandeu"
63 | }
64 | }
--------------------------------------------------------------------------------
/readme/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geckosio/typed-array-buffer-schema/9827e7a2a72304a17202b5d393b277db082f1434/readme/logo.png
--------------------------------------------------------------------------------
/src/bundle.ts:
--------------------------------------------------------------------------------
1 | import { Lib as BufferSchema } from './lib'
2 | import { Model } from './model'
3 | import {
4 | int8,
5 | uint8,
6 | int16,
7 | uint16,
8 | int32,
9 | uint32,
10 | int64,
11 | uint64,
12 | float32,
13 | float64,
14 | string8,
15 | string16,
16 | bool8,
17 | bool16
18 | } from './views'
19 |
20 | export default {
21 | BufferSchema,
22 | Model,
23 | int8,
24 | uint8,
25 | int16,
26 | uint16,
27 | int32,
28 | uint32,
29 | int64,
30 | uint64,
31 | float32,
32 | float64,
33 | string8,
34 | string16,
35 | bool8,
36 | bool16
37 | }
38 |
--------------------------------------------------------------------------------
/src/deep-sort-object.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @copyright
3 | * Copyright (c) 2014 IndigoUnited (https://github.com/IndigoUnited)
4 | * Copyright (c) 2021 Yannick Deubel (https://github.com/yandeu)
5 | *
6 | * @license {@link https://github.com/geckosio/geckos.io/blob/master/LICENSE BSD-3-Clause}
7 | *
8 | * @description
9 | * copied and modified from deep-sort-object@1.0.2 (https://github.com/IndigoUnited/js-deep-sort-object/blob/master/index.js)
10 | * previously licensed under MIT (https://github.com/IndigoUnited/js-deep-sort-object/blob/master/LICENSE)
11 | */
12 |
13 | import { isPlainObject } from 'is-plain-object'
14 |
15 | const isPlainArray = (arr: any) => Array.isArray(arr) && (arr.length > 0 ? typeof arr[0] == "object" : true)
16 |
17 | const isValue = (val: any) => !isPlainObject(val) && !isPlainArray(val)
18 |
19 | /** Sort objects by key; sort properties that are not itself an object on top. */
20 | // @ts-ignore
21 | const defaultSortFn = ([keyA, valueA], [keyB, valueB]) => {
22 | if (isValue(valueA) && isValue(valueB)) return keyA.localeCompare(keyB)
23 | if (isValue(valueA)) return -1
24 | if (isValue(valueB)) return 1
25 |
26 | return keyA.localeCompare(keyB)
27 | }
28 |
29 | const sort = (src: any, comparator?: any): any => {
30 | let out: any
31 |
32 | if (Array.isArray(src)) {
33 | return src.map(function (item) {
34 | return sort(item, comparator)
35 | })
36 | }
37 |
38 | if (isPlainObject(src)) {
39 | out = {}
40 |
41 | Object.entries(src)
42 | .sort(comparator || defaultSortFn)
43 | .forEach(function ([key, value]) {
44 | out[key] = sort(value, comparator)
45 | })
46 |
47 | return out
48 | }
49 |
50 | return src
51 | }
52 |
53 | export { sort as deepSortObject }
54 |
--------------------------------------------------------------------------------
/src/dev.ts:
--------------------------------------------------------------------------------
1 | import { BufferSchema, Model, uint8, int16, uint16 } from './index'
2 | import { Schema } from './schema'
3 | import { string8, int64 } from './views'
4 |
5 | const playerSchema = BufferSchema.schema('player', {
6 | id: uint8,
7 | name: { type: string8, length: 6 },
8 | x: { type: int16, digits: 2 },
9 | y: { type: int16, digits: 2 }
10 | })
11 |
12 | const towerSchema = BufferSchema.schema('tower', {
13 | id: uint8,
14 | health: uint8,
15 | team: uint8
16 | })
17 |
18 | const mainSchema = BufferSchema.schema('snapshot', {
19 | time: int64,
20 | tick: uint16,
21 | players: [playerSchema],
22 | towers: [towerSchema]
23 | })
24 |
25 | const gameState = {
26 | time: new Date().getTime(),
27 | tick: 32580,
28 | players: [
29 | { id: 0, name: 'Mistin', x: -14.43, y: 47.78 },
30 | { id: 1, name: 'Coobim', x: 21.85, y: -78.48 }
31 | ],
32 | towers: [
33 | { id: 0, health: 100, team: 0 },
34 | { id: 1, health: 89, team: 0 },
35 | { id: 2, health: 45, team: 1 }
36 | ]
37 | }
38 |
39 | const mainModel = new Model(mainSchema)
40 | const buffer = mainModel.toBuffer(gameState)
41 | const data = mainModel.fromBuffer(buffer)
42 |
43 | // toBuffer() shrunk the byte size from 241 to only 56
44 | // that is -77% compression!
45 | console.log(JSON.stringify(gameState).length) // 241
46 | console.log(buffer.byteLength) // 56
47 | console.log(JSON.stringify(data).length) // 241
48 |
49 | //------------------------------------------------------------------
50 | // Get the Schema IDs
51 | //------------------------------------------------------------------
52 | const bufferId = BufferSchema.getIdFromBuffer(buffer)
53 | const schemaId = BufferSchema.getIdFromSchema(mainSchema)
54 | const modelId = BufferSchema.getIdFromModel(mainModel)
55 |
56 | console.log(`bufferId: ${bufferId}`)
57 | console.log(`schemaId: ${schemaId}`)
58 | console.log(`modelId: ${modelId}`)
59 |
60 | if (bufferId === schemaId && schemaId === modelId) console.log(`Schema name is "${mainSchema.name}"`)
61 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { Lib as BufferSchema } from './lib'
2 | export { Model } from './model'
3 | export { int8, uint8, int16, uint16, int32, uint32, int64, uint64, float32, float64, string8, string16, bool8, bool16 } from './views'
4 |
--------------------------------------------------------------------------------
/src/lib.ts:
--------------------------------------------------------------------------------
1 | import { deepSortObject } from './deep-sort-object'
2 | import { Model } from './model'
3 | import { Schema } from './schema'
4 |
5 | export class Lib {
6 | public static _schemas: Map = new Map()
7 |
8 | public static newHash(name: string, _struct: any) {
9 | // https://stackoverflow.com/a/7616484/12656855
10 | const strToHash = (s: string) => {
11 | let hash = 0
12 |
13 | for (let i = 0; i < s.length; i++) {
14 | const chr = s.charCodeAt(i)
15 | hash = (hash << 5) - hash + chr
16 | hash |= 0 // Convert to 32bit integer
17 | }
18 | hash *= 254785 // times a random number
19 | return Math.abs(hash).toString(32).slice(2, 6)
20 | }
21 |
22 | let hash = strToHash(JSON.stringify(_struct) + name)
23 | if (hash.length !== 4) throw new Error('Hash has not length of 4')
24 | return `#${hash}`
25 | }
26 |
27 | public static schema(name: string, _struct: object) {
28 | _struct = deepSortObject(_struct as any)
29 | const id = Lib.newHash(name, _struct)
30 | const s = new Schema(id, name, _struct)
31 | this._schemas.set(id, s)
32 | return s
33 | }
34 |
35 | public static getIdFromBuffer = (buffer: ArrayBuffer) => {
36 | const dataView = new DataView(buffer)
37 | let id = ''
38 |
39 | for (let i = 0; i < 5; i++) {
40 | const uInt8 = dataView.getUint8(i)
41 | id += String.fromCharCode(uInt8)
42 | }
43 |
44 | return id
45 | }
46 |
47 | public static getIdFromSchema = (schema: Schema) => schema.id
48 |
49 | public static getIdFromModel = (model: Model) => model.schema.id
50 | }
51 |
--------------------------------------------------------------------------------
/src/model.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from './schema'
2 | import { Serialize } from './serialize'
3 |
4 | export class Model extends Serialize {
5 | /**
6 | * @param schema Your schema
7 | * @param bufferSize The max bufferSize in KB (default: 8)
8 | */
9 | constructor(public schema: Schema, bufferSize = 8) {
10 | super(schema, bufferSize)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/schema.ts:
--------------------------------------------------------------------------------
1 | //var property
2 |
3 | export class Schema {
4 | private _bytes: number = 0
5 |
6 | constructor(private _id: string, private _name: string, private _struct: Object) {
7 | Schema.Validation(_struct)
8 | this.calcBytes()
9 | }
10 |
11 | public static Validation(struct: Object) {
12 | // do all the validation here (as static me)
13 | }
14 |
15 | public get id() {
16 | return this._id
17 | }
18 |
19 | public get name() {
20 | return this._name
21 | }
22 |
23 | private isSpecialType(prop: object) {
24 | let propKeys = Object.keys(prop).filter(k => k != 'type' && k != 'digits' && k != 'length')
25 |
26 | return !propKeys.length
27 | }
28 |
29 | private calcBytes() {
30 | const iterate = (obj: any) => {
31 | for (let property in obj) {
32 | const type = obj?._type || (this.isSpecialType(obj) ? obj?.type?._type : false)
33 | const bytes = obj?._bytes || (this.isSpecialType(obj) ? obj?.type?._bytes : false)
34 |
35 | if (!type && obj.hasOwnProperty(property)) {
36 | if (typeof obj[property] === 'object') {
37 | iterate(obj[property])
38 | }
39 | }
40 | //---
41 | else {
42 | if (property !== '_type' && property !== 'type') continue
43 | if (!bytes) continue
44 |
45 | // we multiply the bytes by the String8 / String16 length.
46 | if (type === 'String8' || type === 'String16') {
47 | const length = obj.length || 12
48 | this._bytes += bytes * length
49 | } else {
50 | this._bytes += bytes
51 | }
52 | }
53 | }
54 | }
55 | iterate(this._struct)
56 | }
57 |
58 | public get struct() {
59 | return this._struct
60 | }
61 |
62 | public get bytes() {
63 | return this._bytes
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/serialize.ts:
--------------------------------------------------------------------------------
1 | import { Schema } from './schema'
2 | import { Lib } from './lib'
3 | import set from 'lodash/set'
4 | import { deepSortObject } from './deep-sort-object'
5 |
6 | export class Serialize {
7 | protected _buffer: ArrayBuffer = new ArrayBuffer(0)
8 | protected _dataView: DataView = new DataView(this._buffer)
9 | protected _bytes: number = 0
10 |
11 | constructor(protected schema: Schema, private bufferSize: number) {}
12 |
13 | public refresh() {
14 | this._buffer = new ArrayBuffer(this.bufferSize * 1024)
15 | this._dataView = new DataView(this._buffer)
16 | this._bytes = 0
17 | }
18 |
19 | private cropString(str: string, length: number) {
20 | return str.padEnd(length, ' ').slice(0, length)
21 | }
22 |
23 | private isSpecialType(prop: object) {
24 | let propKeys = Object.keys(prop).filter(k => k != 'type' && k != 'digits' && k != 'length')
25 |
26 | return !propKeys.length
27 | }
28 |
29 | private boolArrayToInt(array: any) {
30 | // start with 1 to avoid errors such as 010 -> 10
31 | // now it will be 1010 which will not simplify
32 | let string = '1'
33 | for (var i = 0; i < array.length; i++) {
34 | string += +!!array[i]
35 | }
36 | return parseInt(string, 2)
37 | }
38 |
39 | private intToBoolArray(int: number) {
40 | // convert string to array, map the numbers to bools,
41 | // and remove the initial 1
42 | return [...(int >>> 0).toString(2)].map(e => (e == '0' ? false : true)).slice(1)
43 | }
44 |
45 | public flatten(schema: any, data: any) {
46 | let flat: any[] = []
47 |
48 | // https://stackoverflow.com/a/15589677/12656855
49 | const flatten = (schema: any, data: any) => {
50 | // add the schema id to flat[] (its a String8 with 5 characters, the first char is #)
51 | if (schema?._id) flat.push({ d: schema._id, t: 'String8' })
52 | else if (schema?.[0]?._id) flat.push({ d: schema[0]._id, t: 'String8' })
53 |
54 | // if it is a schema
55 | if (schema?._struct) schema = schema._struct
56 | // if it is a schema[]
57 | else if (schema?.[0]?._struct) schema = schema[0]._struct
58 |
59 | // console.log('-------')
60 | // console.log('data', typeof data, data)
61 |
62 | let property: any
63 | for (property in data) {
64 | if (data.hasOwnProperty(property)) {
65 | if (typeof data[property] === 'object') {
66 | // if data is array, but schemas is flat, use index 0 on the next iteration
67 | if (Array.isArray(data)) {
68 | flatten(schema, data[parseInt(property)])
69 | } else if (schema[property]?._type === 'BitArray8' || schema[property]?._type === 'BitArray16') {
70 | flat.push({
71 | d: this.boolArrayToInt(data[property]),
72 | t: schema[property]._type
73 | })
74 | } else flatten(schema[property], data[property])
75 | }
76 | //---
77 | else {
78 | // handle specialTypes e.g.: "x: { type: int16, digits: 2 }"
79 | if (schema[property]?.type?._type && this.isSpecialType(schema[property])) {
80 | if (schema[property]?.digits) {
81 | data[property] *= Math.pow(10, schema[property].digits)
82 | data[property] = parseInt(data[property].toFixed(0))
83 | }
84 | if (schema[property]?.length) {
85 | const length = schema[property]?.length
86 | data[property] = this.cropString(data[property], length)
87 | }
88 | flat.push({ d: data[property], t: schema[property].type._type })
89 | } else {
90 | if (schema[property]?._type) {
91 | // crop strings to default length of 12 characters if nothing else is specified
92 | if (schema[property]?._type === 'String8' || schema[property]?._type === 'String16') {
93 | data[property] = this.cropString(data[property], 12)
94 | }
95 | flat.push({ d: data[property], t: schema[property]._type })
96 | }
97 | }
98 | }
99 | } else {
100 | }
101 | }
102 | }
103 |
104 | flatten(schema, data)
105 |
106 | return flat
107 | }
108 |
109 | public toBuffer(state: any) {
110 | let worldState = deepSortObject(state)
111 |
112 | // deep clone the worldState
113 | const data = JSON.parse(JSON.stringify(worldState))
114 |
115 | this.refresh()
116 |
117 | const flat = this.flatten(this.schema, data)
118 |
119 | // to buffer
120 | flat.forEach((f: any, i: number) => {
121 | if (f.t === 'String8') {
122 | for (let j = 0; j < f.d.length; j++) {
123 | this._dataView.setUint8(this._bytes, f.d[j].charCodeAt(0))
124 | this._bytes++
125 | }
126 | } else if (f.t === 'String16') {
127 | for (let j = 0; j < f.d.length; j++) {
128 | this._dataView.setUint16(this._bytes, f.d[j].charCodeAt(0))
129 | this._bytes += 2
130 | }
131 | } else if (f.t === 'Int8Array') {
132 | this._dataView.setInt8(this._bytes, f.d)
133 | this._bytes++
134 | } else if (f.t === 'Uint8Array') {
135 | this._dataView.setUint8(this._bytes, f.d)
136 | this._bytes++
137 | } else if (f.t === 'Int16Array') {
138 | this._dataView.setInt16(this._bytes, f.d)
139 | this._bytes += 2
140 | } else if (f.t === 'Uint16Array') {
141 | this._dataView.setUint16(this._bytes, f.d)
142 | this._bytes += 2
143 | } else if (f.t === 'Int32Array') {
144 | this._dataView.setInt32(this._bytes, f.d)
145 | this._bytes += 4
146 | } else if (f.t === 'Uint32Array') {
147 | this._dataView.setUint32(this._bytes, f.d)
148 | this._bytes += 4
149 | } else if (f.t === 'BigInt64Array') {
150 | this._dataView.setBigInt64(this._bytes, BigInt(f.d))
151 | this._bytes += 8
152 | } else if (f.t === 'BigUint64Array') {
153 | this._dataView.setBigUint64(this._bytes, BigInt(f.d))
154 | this._bytes += 8
155 | } else if (f.t === 'Float32Array') {
156 | this._dataView.setFloat32(this._bytes, f.d)
157 | this._bytes += 4
158 | } else if (f.t === 'Float64Array') {
159 | this._dataView.setFloat64(this._bytes, f.d)
160 | this._bytes += 8
161 | } else if (f.t === 'BitArray8') {
162 | this._dataView.setUint8(this._bytes, f.d)
163 | this._bytes++
164 | } else if (f.t === 'BitArray16') {
165 | this._dataView.setUint16(this._bytes, f.d)
166 | this._bytes += 2
167 | } else {
168 | console.log('ERROR: Something unexpected happened!')
169 | }
170 | })
171 |
172 | const newBuffer = new ArrayBuffer(this._bytes)
173 | const view = new DataView(newBuffer)
174 |
175 | // copy all data to a new (resized) ArrayBuffer
176 | for (let i = 0; i < this._bytes; i++) {
177 | view.setUint8(i, this._dataView.getUint8(i))
178 | }
179 |
180 | return newBuffer
181 | }
182 |
183 | public fromBuffer(buffer: ArrayBuffer) {
184 | // 35 is #
185 |
186 | // check where, in the buffer, the schemas are
187 | let index = 0
188 | let indexes: number[] = []
189 |
190 | const view = new DataView(buffer)
191 | const int8 = Array.from(new Int8Array(buffer))
192 |
193 | while (index > -1) {
194 | index = int8.indexOf(35, index)
195 | if (index !== -1) {
196 | indexes.push(index)
197 | index++
198 | }
199 | }
200 |
201 | // get the schema ids
202 | let schemaIds: string[] = []
203 | indexes.forEach(index => {
204 | let id = ''
205 | for (let i = 0; i < 5; i++) {
206 | let char = String.fromCharCode(int8[index + i])
207 | id += char
208 | }
209 | schemaIds.push(id)
210 | })
211 |
212 | // assemble all info about the schemas we need
213 | let schemas: { id: string; schema: any; startsAt: number }[] = []
214 | schemaIds.forEach((id, i) => {
215 | // check if the schemaId exists
216 | // (this can be, for example, if charCode 35 is not really a #)
217 | const schemaId = Lib._schemas.get(id)
218 | if (schemaId) schemas.push({ id, schema: Lib._schemas.get(id), startsAt: indexes[i] + 5 })
219 | })
220 | // schemas[] contains now all the schemas we need to fromBuffer the bufferArray
221 |
222 | // lets begin the serialization
223 | let data: any = {} // holds all the data we want to give back
224 | let bytes: number = 0 // the current bytes of arrayBuffer iteration
225 | let dataPerSchema: any = {}
226 |
227 | const deserializeSchema = (struct: any) => {
228 | let data = {}
229 | if (typeof struct === 'object') {
230 | for (let property in struct) {
231 | if (struct.hasOwnProperty(property)) {
232 | const prop = struct[property]
233 |
234 | // handle specialTypes e.g.: "x: { type: int16, digits: 2 }"
235 | let specialTypes
236 | if (prop?.type?._type || prop?.type?._bytes || this.isSpecialType(prop)) {
237 | specialTypes = prop
238 | prop._type = prop.type._type
239 | prop._bytes = prop.type._bytes
240 | }
241 |
242 | if (prop && prop['_type'] && prop['_bytes']) {
243 | const _type = prop['_type']
244 | const _bytes = prop['_bytes']
245 | let value
246 |
247 | if (_type === 'String8') {
248 | value = ''
249 | const length = prop.length || 12
250 | for (let i = 0; i < length; i++) {
251 | const char = String.fromCharCode(view.getUint8(bytes))
252 | value += char
253 | bytes++
254 | }
255 | }
256 | if (_type === 'String16') {
257 | value = ''
258 | const length = prop.length || 12
259 | for (let i = 0; i < length; i++) {
260 | const char = String.fromCharCode(view.getUint16(bytes))
261 | value += char
262 | bytes += 2
263 | }
264 | }
265 | if (_type === 'Int8Array') {
266 | value = view.getInt8(bytes)
267 | bytes += _bytes
268 | }
269 | if (_type === 'Uint8Array') {
270 | value = view.getUint8(bytes)
271 | bytes += _bytes
272 | }
273 | if (_type === 'Int16Array') {
274 | value = view.getInt16(bytes)
275 | bytes += _bytes
276 | }
277 | if (_type === 'Uint16Array') {
278 | value = view.getUint16(bytes)
279 | bytes += _bytes
280 | }
281 | if (_type === 'Int32Array') {
282 | value = view.getInt32(bytes)
283 | bytes += _bytes
284 | }
285 | if (_type === 'Uint32Array') {
286 | value = view.getUint32(bytes)
287 | bytes += _bytes
288 | }
289 | if (_type === 'BigInt64Array') {
290 | value = parseInt(view.getBigInt64(bytes).toString())
291 | bytes += _bytes
292 | }
293 | if (_type === 'BigUint64Array') {
294 | value = parseInt(view.getBigUint64(bytes).toString())
295 | bytes += _bytes
296 | }
297 | if (_type === 'Float32Array') {
298 | value = view.getFloat32(bytes)
299 | bytes += _bytes
300 | }
301 | if (_type === 'Float64Array') {
302 | value = view.getFloat64(bytes)
303 | bytes += _bytes
304 | }
305 | if (_type === 'BitArray8') {
306 | value = this.intToBoolArray(view.getUint8(bytes))
307 | bytes += _bytes
308 | }
309 | if (_type === 'BitArray16') {
310 | value = this.intToBoolArray(view.getUint16(bytes))
311 | bytes += _bytes
312 | }
313 |
314 | // apply special types options
315 | if (typeof value === 'number' && specialTypes?.digits) {
316 | value *= Math.pow(10, -specialTypes.digits)
317 | value = parseFloat(value.toFixed(specialTypes.digits))
318 | }
319 |
320 | data = { ...data, [property]: value }
321 | }
322 | }
323 | }
324 | }
325 | return data
326 | }
327 |
328 | schemas.forEach((s, i) => {
329 | let struct = s.schema?.struct
330 | let start = s.startsAt
331 | let end = buffer.byteLength
332 | let id = s.schema?.id || 'XX'
333 |
334 | if (id === 'XX') console.error('ERROR: Something went horribly wrong!')
335 |
336 | try {
337 | end = schemas[i + 1].startsAt - 5
338 | } catch {}
339 |
340 | // TOOD(yandeu) bytes is not accurate since it includes child schemas
341 | const length = s.schema?.bytes || 1
342 | // determine how many iteration we have to make in this schema
343 | // the players array maybe contains 5 player, so we have to make 5 iterations
344 | const iterations = (end - start) / length
345 |
346 | for (let i = 0; i < iterations; i++) {
347 | bytes = start + i * length
348 | // gets the data from this schema
349 | let schemaData = deserializeSchema(struct)
350 |
351 | if (iterations <= 1) dataPerSchema[id] = { ...schemaData }
352 | else {
353 | if (typeof dataPerSchema[id] === 'undefined') dataPerSchema[id] = []
354 | dataPerSchema[id].push(schemaData)
355 | }
356 | }
357 | })
358 |
359 | // add dataPerScheme to data
360 | data = {}
361 |
362 | const populateData = (obj: any, key: any, value: any, path: string = '', isArray = false) => {
363 | if (obj && obj._id && obj._id === key) {
364 | let p = path.replace(/_struct\./, '').replace(/\.$/, '')
365 | // if it is a schema[], but only has one set, we manually have to make sure it transforms to an array
366 | if (isArray && !Array.isArray(value)) value = [value]
367 | // '' is the top level
368 | if (p === '') data = { ...data, ...value }
369 | else set(data, p, value)
370 | } else {
371 | for (const props in obj) {
372 | if (obj.hasOwnProperty(props)) {
373 | if (typeof obj[props] === 'object') {
374 | let p = Array.isArray(obj) ? '' : `${props}.`
375 | populateData(obj[props], key, value, path + p, Array.isArray(obj))
376 | }
377 | //obj
378 | }
379 | }
380 | }
381 | }
382 |
383 | // to it backwards (don't remember why this is needed, but it works without it)
384 | // for (let i = Object.keys(dataPerSchema).length - 1; i >= 0; i--) {
385 | // const key = Object.keys(dataPerSchema)[i]
386 | // const value = dataPerSchema[key]
387 | // populateData(this.schema, key, value, '')
388 | // }
389 |
390 | for (let i = 0; i < Object.keys(dataPerSchema).length; i++) {
391 | const key = Object.keys(dataPerSchema)[i]
392 | const value = dataPerSchema[key]
393 | populateData(this.schema, key, value, '')
394 | }
395 |
396 | return data
397 | }
398 | }
399 |
--------------------------------------------------------------------------------
/src/views.ts:
--------------------------------------------------------------------------------
1 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays
2 |
3 | /** -128 to 127 (1 byte) */
4 | export const int8 = { _type: 'Int8Array', _bytes: 1 }
5 | /** 0 to 255 (1 byte) */
6 | export const uint8 = { _type: 'Uint8Array', _bytes: 1 }
7 |
8 | /** -32768 to 32767 (2 bytes) */
9 | export const int16 = { _type: 'Int16Array', _bytes: 2 }
10 | /** 0 to 65535 (2 bytes) */
11 | export const uint16 = { _type: 'Uint16Array', _bytes: 2 }
12 |
13 | /** -2147483648 to 2147483647 (4 bytes) */
14 | export const int32 = { _type: 'Int32Array', _bytes: 4 }
15 | /** 0 to 4294967295 (4 bytes) */
16 | export const uint32 = { _type: 'Uint32Array', _bytes: 4 }
17 |
18 | /** -2^63 to 2^63-1 (8 bytes) */
19 | export const int64 = { _type: 'BigInt64Array', _bytes: 8 }
20 | /** 0 to 2^64-1 (8 bytes) */
21 | export const uint64 = { _type: 'BigUint64Array', _bytes: 8 }
22 |
23 | /** 1.2×10-38 to 3.4×1038 (7 significant digits e.g., 1.123456) (4 bytes) */
24 | export const float32 = { _type: 'Float32Array', _bytes: 4 }
25 |
26 | /** 5.0×10-324 to 1.8×10308 (16 significant digits e.g., 1.123...15) (8 bytes) */
27 | export const float64 = { _type: 'Float64Array', _bytes: 8 }
28 |
29 | /** 1 byte per character */
30 | export const string8 = { _type: 'String8', _bytes: 1 }
31 | /** 2 bytes per character */
32 | export const string16 = { _type: 'String16', _bytes: 2 }
33 |
34 | /** An array of 7 booleans */
35 | export const bool8 = { _type: 'BitArray8', _bytes: 1 }
36 | /** An array of 15 booleans */
37 | export const bool16 = { _type: 'BitArray16', _bytes: 2 }
38 |
--------------------------------------------------------------------------------
/test/benchmark.test.js:
--------------------------------------------------------------------------------
1 | const { BufferSchema, Model, uint8, int8, int16, uint16 } = require('../lib/index.js')
2 |
3 | describe('simple test', () => {
4 | const playerSchema = BufferSchema.schema('player', {
5 | id: int8,
6 | x: int16,
7 | y: int16
8 | })
9 |
10 | const snapshotSchema = BufferSchema.schema('snapshot', {
11 | time: uint16,
12 | data: {
13 | players: [playerSchema]
14 | }
15 | })
16 |
17 | const SnapshotModel = new Model(snapshotSchema)
18 |
19 | const snap = {
20 | time: 1234,
21 | data: {
22 | players: [
23 | { id: 0, x: 22, y: 38 },
24 | { id: 1, x: -54, y: 7 }
25 | ]
26 | }
27 | }
28 |
29 | let buffer
30 | let data = snap
31 |
32 | test('should convert as many time as possible', () => {
33 | const hrstart = process.hrtime()
34 |
35 | for (let i = 0; i < 100; i++) {
36 | buffer = SnapshotModel.toBuffer(data)
37 | data = SnapshotModel.fromBuffer(buffer)
38 | }
39 |
40 | const hrend = process.hrtime(hrstart)
41 |
42 | console.info('Execution time (hr): %ds %dms', hrend[0], hrend[1] / 1000000)
43 |
44 | const dataL = JSON.stringify(data).length
45 | const snapL = JSON.stringify(snap).length
46 |
47 | expect(dataL).toBe(snapL)
48 | })
49 | })
50 |
--------------------------------------------------------------------------------
/test/dataViews.test.js:
--------------------------------------------------------------------------------
1 | const {
2 | BufferSchema,
3 | Model,
4 | int8,
5 | uint8,
6 | int16,
7 | uint16,
8 | int32,
9 | uint32,
10 | int64,
11 | uint64,
12 | float32,
13 | float64,
14 | string8,
15 | string16,
16 | bool8,
17 | bool16
18 | } = require('../lib/index.js')
19 |
20 | describe('dataViews test', () => {
21 | const playerSchema = BufferSchema.schema('player', {
22 | a: int8,
23 | b: uint8,
24 | c: int16,
25 | d: uint16,
26 | e: int32,
27 | f: uint32,
28 | g: int64,
29 | h: uint64,
30 | i: float32,
31 | j: float64,
32 | k: string8,
33 | kk: { type: string8, length: 24 },
34 | l: string16,
35 | m: bool8,
36 | n: bool16
37 | })
38 |
39 | const snapshotSchema = BufferSchema.schema('snapshot', {
40 | players: [playerSchema]
41 | })
42 |
43 | const SnapshotModel = new Model(snapshotSchema)
44 |
45 | const now = new Date().getTime()
46 |
47 | const snap = {
48 | players: [
49 | {
50 | a: 10,
51 | b: 10,
52 | c: 50,
53 | d: 50,
54 | e: 100,
55 | f: 100,
56 | g: now,
57 | h: now,
58 | i: 1.123456,
59 | j: 1.123456789,
60 | k: 'This line is too long.',
61 | kk: 'This line is too long.',
62 | l: 'Эта строка слишком длинная.',
63 | m: [true, false, false],
64 | n: [true, true, false, true, true, true, false, false, false, true]
65 | }
66 | ]
67 | }
68 |
69 | let buffer
70 | let data = snap
71 |
72 | test('should convert successfully', () => {
73 | buffer = SnapshotModel.toBuffer(data)
74 | data = SnapshotModel.fromBuffer(buffer)
75 |
76 | expect(data.players[0].m[2]).toBe(false)
77 | expect(data.players[0].n[7]).toBe(false)
78 | expect(data.players[0].g).toBe(now)
79 | expect(data.players[0].h).toBe(now)
80 | expect(data.players[0].k).toBe('This line is')
81 | expect(data.players[0].kk.trim()).toBe('This line is too long.')
82 | expect(data.players[0].l).toBe('Эта строка с')
83 | })
84 | })
85 |
--------------------------------------------------------------------------------
/test/emptyData.test.js:
--------------------------------------------------------------------------------
1 | const { BufferSchema, Model, uint8, int16, uint16 } = require('../lib/index.js')
2 |
3 | describe('simple test', () => {
4 | const playerSchema = BufferSchema.schema('player', {
5 | id: uint8
6 | })
7 |
8 | const botSchema = BufferSchema.schema('bot', {
9 | id: uint8
10 | })
11 |
12 | const carSchema = BufferSchema.schema('car', {
13 | id: uint8
14 | })
15 |
16 | const snapshotSchema = BufferSchema.schema('snapshot', {
17 | time: uint16,
18 | data: {
19 | emptyArr: [playerSchema],
20 | emptyObj: botSchema,
21 | superCar: carSchema
22 | }
23 | })
24 |
25 | const SnapshotModel = new Model(snapshotSchema)
26 |
27 | const snap = {
28 | data: {
29 | emptyArr: [],
30 | emptyObj: {},
31 | superCar: {
32 | id: 911
33 | }
34 | }
35 | }
36 |
37 | test('empty arrays and empty object are omitted', () => {
38 | const buffer = SnapshotModel.toBuffer(snap)
39 |
40 | const dataL = JSON.stringify(SnapshotModel.fromBuffer(buffer)).length
41 | const snapL = JSON.stringify(snap).length
42 | const emptiesL = '"emptyArr":[],"emptyObj":{},'.length
43 |
44 | expect(dataL).toBe(snapL - emptiesL)
45 | })
46 | })
47 |
--------------------------------------------------------------------------------
/test/schemaId.test.js:
--------------------------------------------------------------------------------
1 | const { BufferSchema, Model, uint8, int16, uint16 } = require('../lib/index.js')
2 | const { Schema } = require('../lib/schema.js')
3 |
4 | describe('get schema id test', () => {
5 | const schema = BufferSchema.schema('mySchema', {
6 | id: uint8,
7 | x: { type: uint16, digits: 4 }
8 | })
9 |
10 | const model = new Model(schema)
11 | const state = { id: 0, x: 1.2345 }
12 | const buffer = model.toBuffer(state)
13 |
14 | test('should get the same ids', () => {
15 | const bufferId = BufferSchema.getIdFromBuffer(buffer)
16 | const schemaId = BufferSchema.getIdFromSchema(schema)
17 | const modelId = BufferSchema.getIdFromModel(model)
18 |
19 | expect(bufferId).toBe(schemaId)
20 | expect(schemaId).toBe(modelId)
21 | })
22 | })
23 |
--------------------------------------------------------------------------------
/test/serializeDeserialize.test.js:
--------------------------------------------------------------------------------
1 | const { BufferSchema, Model, uint8, uint32, string8, int16, bool8 } = require('../lib/index.js')
2 |
3 | // see: https://github.com/geckosio/typed-array-buffer-schema/issues/7
4 | describe('serialize deserialize', () => {
5 | const movementSchema = BufferSchema.schema('movement', {
6 | sequenceNumber: uint32,
7 | horizontal: uint8,
8 | vertical: uint8,
9 | options: bool8
10 | })
11 | const movementModel = new Model(movementSchema)
12 |
13 | const inOrder = {
14 | sequenceNumber: 2,
15 | horizontal: 4,
16 | vertical: 4,
17 | options: [false, true, false]
18 | }
19 | const notInOrder = {
20 | horizontal: 4,
21 | options: [false, true, false],
22 | vertical: 4,
23 | sequenceNumber: 2
24 | }
25 |
26 | it('should work if defined in order', () => {
27 | const serialized = movementModel.toBuffer(inOrder)
28 | const deserialized = movementModel.fromBuffer(serialized)
29 |
30 | expect(deserialized.sequenceNumber).toBe(2)
31 | expect(deserialized.horizontal).toBe(4)
32 | expect(deserialized.vertical).toBe(4)
33 | expect(deserialized.options[1]).toBe(true)
34 | })
35 |
36 | it('should work if NOT defined in order', () => {
37 | const serialized = movementModel.toBuffer(notInOrder)
38 | const deserialized = movementModel.fromBuffer(serialized)
39 |
40 | expect(deserialized.sequenceNumber).toBe(2)
41 | expect(deserialized.horizontal).toBe(4)
42 | expect(deserialized.vertical).toBe(4)
43 | expect(deserialized.options[1]).toBe(true)
44 | })
45 | })
46 |
47 | describe('serialize deserialize (complex)', () => {
48 | const TimerSchema = BufferSchema.schema('timer', {
49 | time: uint32
50 | })
51 |
52 | const castleSchema = BufferSchema.schema('castle', {
53 | name: string8,
54 | health: uint8
55 | })
56 |
57 | const playerSchema = BufferSchema.schema('player', {
58 | id: uint8,
59 | y: int16,
60 | x: int16
61 | })
62 |
63 | const gameSchema = BufferSchema.schema('snapshot', {
64 | name: string8,
65 | players: [playerSchema],
66 | time: uint32,
67 | stats: TimerSchema,
68 | castles: [castleSchema],
69 | config: bool8
70 | })
71 |
72 | const gameModel = new Model(gameSchema)
73 |
74 | const timeInSeconds = Math.floor(new Date().getTime() / 1000)
75 |
76 | const randomOrder = {
77 | stats: { time: timeInSeconds },
78 | time: timeInSeconds,
79 | castles: [
80 | { name: 'beauty', health: 100 },
81 | { health: 78, name: 'beauty2' },
82 | { health: 88, name: 'very_long_name' }
83 | ],
84 | name: 'myGame',
85 | players: [
86 | { id: 25, x: 788, y: -14 },
87 | { x: 1, y: 2, id: 87 }
88 | ],
89 | config: [true, false, true, true, true]
90 | }
91 |
92 | it('should work if defined randomly', () => {
93 | const serialized = gameModel.toBuffer(randomOrder)
94 | const deserialized = gameModel.fromBuffer(serialized)
95 |
96 | expect(deserialized.players[0].id).toBe(25)
97 | expect(deserialized.castles[1].name.trim()).toBe('beauty2')
98 | expect(deserialized.castles[2].name.trim()).toBe('very_long_name'.slice(0, 12))
99 | expect(deserialized.name.trim()).toBe('myGame')
100 | expect(deserialized.time).toBe(timeInSeconds)
101 | expect(deserialized.stats.time).toBe(timeInSeconds)
102 | expect(deserialized.config[1]).toBe(false)
103 | })
104 | })
105 |
--------------------------------------------------------------------------------
/test/simple.test.js:
--------------------------------------------------------------------------------
1 | const { BufferSchema, Model, uint8, int16, uint16, string8, bool8 } = require('../lib/index.js')
2 | const _ = require('underscore')
3 |
4 | describe('simple test', () => {
5 | const castleSchema = BufferSchema.schema('castle', {
6 | id: uint8,
7 | type: { type: string8, length: 3 },
8 | health: uint8
9 | })
10 |
11 | const playerSchema = BufferSchema.schema('player', {
12 | id: uint8,
13 | a: { type: int16, digits: 1 },
14 | b: { type: int16, digits: 1 },
15 | x: int16,
16 | y: int16
17 | })
18 |
19 | const listSchema = BufferSchema.schema('list', {
20 | value: uint8
21 | })
22 |
23 | const snapshotSchema = BufferSchema.schema('snapshot', {
24 | time: uint16,
25 | single: uint8,
26 | data: { list: [listSchema], players: [playerSchema], castles: [castleSchema] },
27 | serverConfig: bool8
28 | })
29 |
30 | const SnapshotModel = new Model(snapshotSchema)
31 |
32 | const snap = {
33 | time: 1234,
34 | single: 0,
35 | data: {
36 | list: [{ value: 1 }, { value: 2 }],
37 | castles: [
38 | {
39 | id: 2,
40 | type: 'big',
41 | health: 81
42 | }
43 | ],
44 | players: [
45 | {
46 | id: 14,
47 | a: 10,
48 | b: 5,
49 | x: 145,
50 | y: 98
51 | },
52 | {
53 | id: 15,
54 | a: 7,
55 | b: -55,
56 | x: 218,
57 | y: -14
58 | }
59 | ]
60 | },
61 | serverConfig: [true, false, true, false, false]
62 | }
63 |
64 | let buffer
65 | let data
66 |
67 | test('get schema name', () => {
68 | expect(castleSchema.name).toBe('castle')
69 | })
70 |
71 | test('should return a buffer', () => {
72 | buffer = SnapshotModel.toBuffer(snap)
73 | const uint8 = new Uint8Array(buffer)
74 |
75 | expect(typeof buffer).toBe('object')
76 | expect(uint8.buffer.byteLength).toBe(49)
77 | })
78 |
79 | test('should fromBuffer', () => {
80 | data = SnapshotModel.fromBuffer(buffer)
81 |
82 | expect(data.time).toBe(1234)
83 | expect(data.data.players[0].x).toBe(145)
84 | expect(data.data.players[0].a).toBe(10)
85 | expect(data.data.players[1].b).toBe(-55)
86 | })
87 |
88 | test('stringified version should have same length', () => {
89 | expect(_.isEqual(snap, data)).toBeTruthy()
90 | })
91 | })
92 |
--------------------------------------------------------------------------------
/test/specialTypes.test.js:
--------------------------------------------------------------------------------
1 | const { BufferSchema, Model, uint8, int16, uint16 } = require('../lib/index.js')
2 |
3 | describe('special types test', () => {
4 | const playerSchema = BufferSchema.schema('player', {
5 | id: uint8,
6 | x: { type: uint16, digits: 4 }
7 | })
8 |
9 | const PlayerModel = new Model(playerSchema)
10 |
11 | const state = { id: 0, x: 5.211427545 }
12 |
13 | test('should be able to manage and crop digits', () => {
14 | const buffer = PlayerModel.toBuffer(state)
15 | const data = PlayerModel.fromBuffer(buffer)
16 |
17 | expect(data.x).toBe(5.2114)
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2019",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 |
7 | "rootDir": "src",
8 | "outDir": "lib",
9 |
10 | "strict": true,
11 | "esModuleInterop": true,
12 | "newLine": "lf",
13 |
14 | "sourceMap": true,
15 | "declaration": true,
16 | "declarationMap": true
17 | },
18 | "include": ["src/**/*"],
19 | "exclude": ["node_modules", "**/*.spec.ts"]
20 | }
21 |
--------------------------------------------------------------------------------
/webpack.bundle.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | mode: 'production',
5 | entry: './src/bundle.ts',
6 | output: {
7 | filename: 'typed-array-buffer-schema.js',
8 | path: path.resolve(__dirname, 'bundle'),
9 | library: 'Schema',
10 | libraryExport: 'default'
11 | },
12 | resolve: {
13 | extensions: ['.ts', '.tsx', '.js']
14 | },
15 | module: {
16 | rules: [{ test: /\.tsx?$/, loader: 'ts-loader' }]
17 | }
18 | }
--------------------------------------------------------------------------------
/webpack.bundle.tmp.js:
--------------------------------------------------------------------------------
1 | const config = require('./webpack.bundle.js')
2 |
3 | module.exports = { ...config, output: { ...config.output, filename: 'typed-array-buffer-schema.tmp.js' } }
4 |
--------------------------------------------------------------------------------