如何在网络工作者上使用matter.js?

问题描述 投票:0回答:1

我想在带有离屏画布的 Web Worker 上使用 Matter.js,但问题是它似乎会抛出有关

requestAnimationFrame
的错误。它尝试使用
window.requestAnimationFrame
来获取它,但在网络工作窗口中未定义。另外,如果我尝试设置一个精灵,它会失败,因为 Matter.js 使用
new Image()
但在网络工作人员中没有定义。它还尝试设置画布的样式,但这不起作用,因为它是屏幕外的画布。

有人让它工作吗?

javascript canvas web-worker matter.js
1个回答
0
投票

在库的作者提供真正的

Worker
版本之前,您必须对上下文进行猴子修补,以便可以访问他们使用的所有方法。

这里有一些我能想到的常见重写,它们对于其他库也可能有用,但是对于 UI 事件之类的事情,您实际上还需要从所有者的上下文(主线程)中处理它们,而我写的时候前段时间的一个

EventPort
会有帮助,我会将其作为读者的练习,因为每个库的需求可能会有很大差异。

无论如何,在这里你会找到一个覆盖:

  • Image
    构造函数,其底层使用
    ImageBitmap
    ,
  • document
    createElement("canvas")
    ,返回
    OffscreenCanvas
  • document
    createElement("image")
    ,它返回我们的
    Image
  • OffscreenCanvas
    上的一些方法和属性,以便它更好地映射到
    <canvas>
  • 一些
    HTMLImageElement
    的消费者(在 2D 环境中),以便我们的
    Image
    暴露其
    ImageBitmap

const scriptContent = document.querySelector("[type=worker-script]").textContent;
const scriptURL = URL.createObjectURL(new Blob([scriptContent]));
const worker = new Worker(scriptURL);
worker.onerror = console.log
const placeholder = document.querySelector("canvas");
const offCanvas = placeholder.transferControlToOffscreen();
worker.postMessage(offCanvas, [offCanvas]);
<canvas></canvas>
<script type="worker-script">
self.window = self;
self.document = { // Not really needed for this example
  createElement(val) {
    if (val === "img") {
      return new Image();
    }
    if (val === "canvas") {
      return new OffscreenCanvas(300, 150);
    }
  }
};
// They try to set a background
// You could catch it and let the placeholder know, if wanted
Object.defineProperty(OffscreenCanvas.prototype, "style", { value: {} });
// Make it act more like an element
OffscreenCanvas.prototype.getAttribute = function (attr) {
  return this._attributes?.[attr];
};
OffscreenCanvas.prototype.setAttribute = function (attr, value) {
  if (attr === "width" || attr === "height") {
    this[attr] = parseInt(value);
  }
  return (this._attributes ??= {})[attr] = value.toString();
};

// Our new Image() class
const bitmapSymbol = Symbol("bitmap");
const imageMap = new Map();
class Image extends EventTarget {
  [bitmapSymbol] = null;
  get width() {
    return this[bitmapSymbol]?.width || 0;
  }
  get height() {
    return this[bitmapSymbol]?.height || 0;
  }
  get naturalWidth() {
    return this.width;
  }
  get naturalHeight() {
    return this.height;
  }
  #src = null;
  get src() { return this.#src; }
  set src(value) {
    this.#src = value.toString();
    (async () => {
      // The request has already been performed before
      // We try to make it behave synchrnously like the actual
      // Image does when the resource has already been loaded
      if (imageMap.has(this.#src)) {
        // Still ongoing, await
        if (imageMap.get(this.#src) instanceof Promise) {
          await imageMap.get(this.#src);
        }
        // Set it sync if possible
        this[bitmapSymbol] = imageMap.get(this.#src);
        this.dispatchEvent(new Event("load"));
      }
      else {
        const { promise, resolve } = Promise.withResolvers();
        imageMap.set(this.#src, promise);
        const resp = await fetch(this.#src);
        const blob = resp.ok && await resp.blob();
        const bmp = this[bitmapSymbol] = await createImageBitmap(blob);
        resolve(bmp);
        imageMap.set(this.#src, bmp);
        this.dispatchEvent(new Event("load"));
      }
    })().catch((err) => {
      this.dispatchEvent(new Event("error"));
    });
  }
  async decode() {
    if (!imageMap.has(this.src)) {
      throw new DOMException("Invalid image request.");
    }
    await imageMap.get(this.src);
  }
  #onload = null;
  set onload(handler) {
    this.removeEventListener("load", this.#onload);
    this.#onload = handler
    this.addEventListener("load", this.#onload);    
  }
  #onerror = null;
  set onerror(handler) {
    this.removeEventListener("error", this.#onerror);
    this.#onerror = handler;
    this.addEventListener("error", this.#onerror);
  }
}
// Image() consumers need to be overridden
const overrideProto = (proto, funcName) => {
  const orig = proto[funcName];
  proto[funcName] = function(source, ...args) {
    const fixedSource = source[bitmapSymbol] || source;
    return orig.call(this, fixedSource, ...args);
  };
};
overrideProto(OffscreenCanvasRenderingContext2D.prototype, "drawImage");
overrideProto(OffscreenCanvasRenderingContext2D.prototype, "createPattern");
overrideProto(globalThis, "createImageBitmap");
// WebGL has another signature, if needed, shouldn't be too hard to write it yourself.
// END OVERRIDES
/////////////////////////////

importScripts("https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js");

onmessage = async ({ data: canvas }) => {
  // matter.js doesn't wait for the texture have loaded...
  // So we must preload them, also tru for when in the main thread
  const preloadImage = (url) => {
    const img = new Image();
    img.src = url;
    return img.decode();
  };
  await preloadImage("https://cdn.jsdelivr.net/gh/liabru/matter-js/demo/img/box.png");
  await preloadImage("https://cdn.jsdelivr.net/gh/liabru/matter-js/demo/img/ball.png");
  // https://github.com/liabru/matter-js/blob/master/examples/sprites.js
  // Edited to point to set the Render's canvas to our OffscreenCanvas
  // (and the URLs absolute)
  var Engine = Matter.Engine,
        Render = Matter.Render,
        Runner = Matter.Runner,
        Composites = Matter.Composites,
        Common = Matter.Common,
        MouseConstraint = Matter.MouseConstraint,
        Mouse = Matter.Mouse,
        Composite = Matter.Composite,
        Bodies = Matter.Bodies;

    // create engine
    var engine = Engine.create(),
        world = engine.world;

    // create renderer
    var render = Render.create({
        canvas, // [EDITED]
        engine: engine,
        options: {
            width: 800,
            height: 600,
            showAngleIndicator: false,
            wireframes: false
        }
    });

    Render.run(render);

    // create runner
    var runner = Runner.create();
    Runner.run(runner, engine);

    // add bodies
    var offset = 10,
        options = { 
            isStatic: true
        };

    world.bodies = [];

    // these static walls will not be rendered in this sprites example, see options
    Composite.add(world, [
        Bodies.rectangle(400, -offset, 800.5 + 2 * offset, 50.5, options),
        Bodies.rectangle(400, 600 + offset, 800.5 + 2 * offset, 50.5, options),
        Bodies.rectangle(800 + offset, 300, 50.5, 600.5 + 2 * offset, options),
        Bodies.rectangle(-offset, 300, 50.5, 600.5 + 2 * offset, options)
    ]);

    var stack = Composites.stack(20, 20, 10, 4, 0, 0, function(x, y) {
        if (Common.random() > 0.35) {
            return Bodies.rectangle(x, y, 64, 64, {
                render: {
                    strokeStyle: '#ffffff',
                    sprite: {
                        texture: 'https://cdn.jsdelivr.net/gh/liabru/matter-js/demo/img/box.png'
                    }
                }
            });
        } else {
            return Bodies.circle(x, y, 46, {
                density: 0.0005,
                frictionAir: 0.06,
                restitution: 0.3,
                friction: 0.01,
                render: {
                    sprite: {
                        texture: 'https://cdn.jsdelivr.net/gh/liabru/matter-js/demo/img/ball.png'
                    }
                }
            });
        }
    });

    Composite.add(world, stack);

    // add mouse control
    var mouse = Mouse.create(render.canvas),
        mouseConstraint = MouseConstraint.create(engine, {
            mouse: mouse,
            constraint: {
                stiffness: 0.2,
                render: {
                    visible: false
                }
            }
        });

    Composite.add(world, mouseConstraint);

    // keep the mouse in sync with rendering
    render.mouse = mouse;

    // fit the render viewport to the scene
    Render.lookAt(render, {
        min: { x: 0, y: 0 },
        max: { x: 800, y: 600 }
    });
}
</script>

© www.soinside.com 2019 - 2024. All rights reserved.