-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce generic serializer for web worker transfers
Closes #5143
- Loading branch information
Anand Thakker
committed
Nov 17, 2017
1 parent
485ff9f
commit f8f65bb
Showing
2 changed files
with
200 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
// @flow | ||
|
||
const assert = require('assert'); | ||
const registry: {[string]: { klass: Class<any>, omit: Array<string> }} = {}; | ||
|
||
/** | ||
* Register the given class as serializable. | ||
* | ||
* To mark certain properties as non-serializable (such as cached/computed | ||
* properties), include an optional { omit: [] } argument. | ||
* | ||
* @private | ||
*/ | ||
function register<T: any>(klass: Class<T>, options: { omit: Array<$Keys<T>> } = { omit: [] }) { | ||
assert(klass.name); | ||
assert(!registry[klass.name]); | ||
registry[klass.name] = { klass, omit: options.omit }; | ||
} | ||
|
||
register(Object); | ||
|
||
export type Serialized = | ||
| null | ||
| void | ||
| boolean | ||
| number | ||
| string | ||
| Boolean | ||
| Number | ||
| String | ||
| Date | ||
| RegExp | ||
| ArrayBuffer | ||
| $ArrayBufferView | ||
| Array<Serialized> | ||
| {| name: string, properties: {+[string]: Serialized} |}; | ||
|
||
/** | ||
* Serialize the given object for transfer to or from a web worker. | ||
* | ||
* For non-builtin types, recursively serialize each property (possibly | ||
* omitting certain properties - see register()), and package the result along | ||
* with the constructor's `name` so that the appropriate constructor can be | ||
* looked up in `deserialize()`. | ||
* | ||
* If a `transferables` array is provided, add any transferable objects (i.e., | ||
* any ArrayBuffers or ArrayBuffer views) to the list. (If a copy is needed, | ||
* this should happen in the client code, before using serialize().) | ||
*/ | ||
function serialize(input: mixed, transferables?: Array<Transferable>): Serialized { | ||
if (input === null || | ||
input === undefined || | ||
typeof input === 'boolean' || | ||
typeof input === 'number' || | ||
typeof input === 'string' || | ||
input instanceof Boolean || | ||
input instanceof Number || | ||
input instanceof String || | ||
input instanceof Date || | ||
input instanceof RegExp) { | ||
return input; | ||
} | ||
|
||
if (input instanceof ArrayBuffer) { | ||
if (transferables) { | ||
transferables.push(input); | ||
} | ||
return input; | ||
} | ||
|
||
if (ArrayBuffer.isView(input)) { | ||
const view: $ArrayBufferView = (input: any); | ||
if (transferables) { | ||
transferables.push(view.buffer); | ||
} | ||
return view; | ||
} | ||
|
||
if (Array.isArray(input)) { | ||
return input.map((i) => serialize(i, transferables)); | ||
} | ||
|
||
if (typeof input === 'object') { | ||
const name = input.constructor.name; | ||
if (!name) { | ||
throw new Error(`can't serialize object of anonymous class`); | ||
} | ||
|
||
if (!registry[name]) { | ||
throw new Error(`can't serialize unregistered class ${name}`); | ||
} | ||
|
||
const {omit} = registry[name]; | ||
|
||
const properties: {[string]: Serialized} = {}; | ||
|
||
for (const key of Object.keys(input)) { | ||
if (omit.indexOf(key) >= 0) continue; | ||
properties[key] = serialize(input[key], transferables); | ||
} | ||
|
||
return {name, properties}; | ||
} | ||
|
||
throw new Error(`can't serialize object of type ${typeof input}`); | ||
} | ||
|
||
function deserialize(input: Serialized): mixed { | ||
if (input === null || | ||
input === undefined || | ||
typeof input === 'boolean' || | ||
typeof input === 'number' || | ||
typeof input === 'string' || | ||
input instanceof Boolean || | ||
input instanceof Number || | ||
input instanceof String || | ||
input instanceof Date || | ||
input instanceof RegExp || | ||
input instanceof ArrayBuffer || | ||
ArrayBuffer.isView(input)) { | ||
return input; | ||
} | ||
|
||
if (Array.isArray(input)) { | ||
return input.map((i) => deserialize(i)); | ||
} | ||
|
||
if (typeof input === 'object') { | ||
const {name, properties} = (input: any); | ||
if (!name) { | ||
throw new Error(`can't deserialize object of anonymous class`); | ||
} | ||
|
||
const {klass} = registry[name]; | ||
if (!klass) { | ||
throw new Error(`can't deserialize unregistered class ${name}`); | ||
} | ||
|
||
const result = Object.create(klass.prototype); | ||
|
||
for (const key of Object.keys(properties)) { | ||
result[key] = deserialize(properties[key]); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
throw new Error(`can't deserialize object of type ${typeof input}`); | ||
} | ||
|
||
module.exports = { | ||
register, | ||
serialize, | ||
deserialize | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// @flow | ||
|
||
'use strict'; | ||
|
||
const test = require('mapbox-gl-js-test').test; | ||
const {register, serialize, deserialize} = require('../../../src/util/web_worker_transfer'); | ||
|
||
test('round trip', (t) => { | ||
class Foo { | ||
n/*: number*/; | ||
buffer/*: ArrayBuffer*/; | ||
_cached/*: ?number*/; | ||
|
||
constructor(n) { | ||
this.n = n; | ||
this.buffer = new ArrayBuffer(100); | ||
this.squared(); | ||
} | ||
|
||
squared() { | ||
if (this._cached) { | ||
return this._cached; | ||
} | ||
this._cached = this.n * this.n; | ||
return this._cached; | ||
} | ||
} | ||
|
||
register(Foo, { omit: ['_cached'] }); | ||
|
||
const foo = new Foo(10); | ||
const transferables = []; | ||
const deserialized = deserialize(serialize(foo, transferables)); | ||
t.assert(deserialized instanceof Foo); | ||
const bar/*: Foo*/ = (deserialized/*: any*/); | ||
|
||
t.assert(foo !== bar); | ||
t.assert(bar.constructor === Foo); | ||
t.assert(bar.n === 10); | ||
t.assert(bar.buffer === foo.buffer); | ||
t.assert(transferables[0] === foo.buffer); | ||
t.assert(bar._cached === undefined); | ||
t.assert(bar.squared() === 100); | ||
t.end(); | ||
}); |