Files: 251a0f837a762cf942d5254502a65129e1a09e0b / index.js
3787 bytesRaw
1 | const ReferanceMap = require('reference-map') |
2 | const AbstractContainer = require('primea-abstract-container') |
3 | const ContainerTable = require('primea-container-table') |
4 | const RadixTree = require('dfinity-radix-tree') |
5 | |
6 | const CODEKEY = new RadixTree.ArrayConstructor([0, 1]) |
7 | |
8 | module.exports = class WasmContainer extends AbstractContainer { |
9 | /** |
10 | * The wasm container runs wasm code and provides a basic API for wasm |
11 | * interfaces for interacting with the actor |
12 | * @param {object} actor - the actor instance |
13 | * @param {object} interfaces - a map of interfaces to expose to the wasm binary |
14 | */ |
15 | constructor (actor, interfaces) { |
16 | super(actor) |
17 | this.referanceMap = new ReferanceMap() |
18 | |
19 | // hold the interfaces `initailize` functions, if any |
20 | this.initializeFuncs = [] |
21 | // Builds a import map with an array of given interfaces |
22 | this.importMap = {} |
23 | for (const name in interfaces) { |
24 | this.importMap[name] = {} |
25 | const Interface = interfaces[name] |
26 | if (Interface.initialize) { |
27 | this.initializeFuncs.push(Interface.initialize) |
28 | } |
29 | const newInterface = new Interface(this) |
30 | const props = Object.getOwnPropertyNames(Interface.prototype) |
31 | |
32 | // bind the methods to the correct 'this' |
33 | for (const prop of props) { |
34 | if (prop !== 'constructor') { |
35 | this.importMap[name][prop] = newInterface[prop].bind(newInterface) |
36 | } |
37 | } |
38 | } |
39 | } |
40 | |
41 | async onCreation (message) { |
42 | let code = message.data |
43 | if (!WebAssembly.validate(code)) { |
44 | throw new Error('invalid wasm binary') |
45 | } else { |
46 | await Promise.all(this.initializeFuncs.map(initFunc => initFunc(code))) |
47 | this.actor.state.set(CODEKEY, code) |
48 | } |
49 | return this._run(message, 'onCreation') |
50 | } |
51 | |
52 | /** |
53 | * Runs the wasm VM given a message |
54 | * @param {object} message |
55 | * @returns {Promise} a promise that resolves once the compuation is finished |
56 | */ |
57 | onMessage (message, method = 'onMessage') { |
58 | return this._run(message, method) |
59 | } |
60 | |
61 | async _run (message, method) { |
62 | const code = await this.actor.state.get(CODEKEY) |
63 | const result = await WebAssembly.instantiate(code, this.importMap) |
64 | this.instance = result.instance |
65 | if (this.instance.exports[method]) { |
66 | // add the message and ports to the refereance map |
67 | const messageRef = this.referanceMap.add(message) |
68 | |
69 | // runs the wasm code |
70 | this.instance.exports[method](messageRef) |
71 | await this.onDone() |
72 | this.referanceMap.clear() |
73 | } |
74 | } |
75 | |
76 | /** |
77 | * returns a promise that resolves when the wasm instance is done running |
78 | * @returns {Promise} |
79 | */ |
80 | async onDone () { |
81 | let prevOps |
82 | while (prevOps !== this._opsQueue) { |
83 | prevOps = this._opsQueue |
84 | await prevOps |
85 | } |
86 | } |
87 | |
88 | /** |
89 | * Pushed an async operation to the a promise queue that |
90 | * @returns {Promise} the returned promise resolves in the order the intail |
91 | * operation was pushed to the queue |
92 | */ |
93 | pushOpsQueue (promise) { |
94 | this._opsQueue = Promise.all([this._opsQueue, promise]) |
95 | return this._opsQueue |
96 | } |
97 | |
98 | /** |
99 | * executes a callback given an index in the exported callback container |
100 | * @param {integer} cb |
101 | * @param {*} val - a value to return to the callback function |
102 | */ |
103 | execute (cb) { |
104 | const args = [...arguments] |
105 | args.shift() |
106 | this.instance.exports.callbacks.get(cb)(...args) |
107 | } |
108 | |
109 | /** |
110 | * returns a section of memory from the wasm instance |
111 | * @param {integer} offset |
112 | * @param {integer} length |
113 | * @returns {Uint8Array} |
114 | */ |
115 | getMemory (offset, length) { |
116 | return new Uint8Array(this.instance.exports.memory.buffer, offset, length) |
117 | } |
118 | |
119 | setMemory (offset, val) { |
120 | const mem = this.getMemory(offset, val.length) |
121 | mem.set(val) |
122 | } |
123 | |
124 | static get typeId () { |
125 | return ContainerTable.WebAssembly |
126 | } |
127 | } |
128 |
Built with git-ssb-web