File size: 2,477 Bytes
bc20498
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
'use strict';
// The goal of this file is to normalize SAB
// at least in main -> worker() use cases.
// This still cannot possibly solve the sync
// worker -> main() use case if SharedArrayBuffer
// is not available or usable.

const {CHANNEL} = require('./channel.js');

const {isArray} = Array;

let {SharedArrayBuffer, window} = globalThis;
let {notify, wait, waitAsync} = Atomics;
let postPatched = null;

// This is needed for some version of Firefox
if (!waitAsync) {
  waitAsync = buffer => ({
    value: new Promise(onmessage => {
      // encodeURIComponent('onmessage=({data:b})=>(Atomics.wait(b,0),postMessage(0))')
      let w = new Worker('data:application/javascript,onmessage%3D(%7Bdata%3Ab%7D)%3D%3E(Atomics.wait(b%2C0)%2CpostMessage(0))');
      w.onmessage = onmessage;
      w.postMessage(buffer);
    })
  });
}

// Monkey-patch SharedArrayBuffer if needed
try {
  new SharedArrayBuffer(4);
}
catch (_) {
  SharedArrayBuffer = ArrayBuffer;

  const ids = new WeakMap;
  // patch only main -> worker():async use case
  if (window) {
    const resolvers = new Map;
    const {prototype: {postMessage}} = Worker;

    const listener = event => {
      const details = event.data?.[CHANNEL];
      if (!isArray(details)) {
        event.stopImmediatePropagation();
        const { id, sb } = details;
        resolvers.get(id)(sb);
      }
    };

    postPatched = function (data, ...rest) {
      const details = data?.[CHANNEL];
      if (isArray(details)) {
        const [id, sb] = details;
        ids.set(sb, id);
        this.addEventListener('message', listener);
      }
      return postMessage.call(this, data, ...rest);
    };

    waitAsync = sb => ({
      value: new Promise(resolve => {
        resolvers.set(ids.get(sb), resolve);
      }).then(buff => {
        resolvers.delete(ids.get(sb));
        ids.delete(sb);
        for (let i = 0; i < buff.length; i++) sb[i] = buff[i];
        return 'ok';
      })
    });
  }
  else {
    const as = (id, sb) => ({[CHANNEL]: { id, sb }});

    notify = sb => {
      postMessage(as(ids.get(sb), sb));
    };

    addEventListener('message', event => {
      const details = event.data?.[CHANNEL];
      if (isArray(details)) {
        const [id, sb] = details;
        ids.set(sb, id);
      }
    });
  }
}

exports.SharedArrayBuffer = SharedArrayBuffer;
exports.isArray = isArray;
exports.notify = notify;
exports.postPatched = postPatched;
exports.wait = wait;
exports.waitAsync = waitAsync;