Giter Club home page Giter Club logo

Comments (8)

rxliuli avatar rxliuli commented on May 30, 2024 3

I try to simplify the call of dispose() in another more general way @crysislinux

import { QuickJSHandle, QuickJSVm } from 'quickjs-emscripten'

const QuickJSVmScopeSymbol = Symbol('QuickJSVmScope')

/**
 * 为 QuickJSVm 添加局部作用域,局部作用域的所有方法调用不再需要手动释放内存
 * @param vm
 * @param handle
 */
export function withScope<F extends (vm: QuickJSVm) => any>(
  vm: QuickJSVm,
  handle: F,
): {
  value: ReturnType<F>
  dispose(): void
} {
  let disposes: (() => void)[] = []

  function wrap(handle: QuickJSHandle) {
    disposes.push(() => handle.alive && handle.dispose())
    return handle
  }

  //避免多层代理
  const isProxy = !!Reflect.get(vm, QuickJSVmScopeSymbol)
  function dispose() {
    if (isProxy) {
      Reflect.get(vm, QuickJSVmScopeSymbol)()
      return
    }
    disposes.forEach((dispose) => dispose())
    //手动释放闭包变量的内存
    disposes.length = 0
  }
  const value = handle(
    isProxy
      ? vm
      : new Proxy(vm, {
          get(
            target: QuickJSVm,
            p: keyof QuickJSVm | typeof QuickJSVmScopeSymbol,
          ): any {
            if (p === QuickJSVmScopeSymbol) {
              return dispose
            }
            //锁定所有方法的 this 值为 QuickJSVm 对象而非 Proxy 对象
            const res = Reflect.get(target, p, target)
            if (
              p.startsWith('new') ||
              ['getProp', 'unwrapResult'].includes(p)
            ) {
              return (...args: any[]): QuickJSHandle => {
                return wrap(Reflect.apply(res, target, args))
              }
            }
            if (['evalCode', 'callFunction'].includes(p)) {
              return (...args: any[]) => {
                const res = (target[p] as any)(...args)
                disposes.push(() => {
                  const handle = res.error ?? res.value
                  handle.alive && handle.dispose()
                })
                return res
              }
            }
            if (typeof res === 'function') {
              return (...args: any[]) => {
                return Reflect.apply(res, target, args)
              }
            }
            return res
          },
        }),
  )

  return { value, dispose }
}
withScope(vm, (vm) => {
  const _hello = vm.newFunction('hello', () => {})
  const _object = vm.newObject()
  vm.setProp(_object, 'hello', _hello)
  vm.setProp(_object, 'name', vm.newString('liuli'))
  expect(vm.dump(vm.getProp(_object, 'hello'))).not.toBeNull()
  vm.setProp(vm.global, 'VM_GLOBAL', _object)
}).dispose()

from quickjs-emscripten.

eliot-akira avatar eliot-akira commented on May 30, 2024

Here's a quick (untested) example, based on js-sandbox-demo.

const disposables = [];

const trueHandle = vm.unwrapResult(vm.evalCode('true'));
const falseHandle = vm.unwrapResult(vm.evalCode('false'));
const nullHandle = vm.unwrapResult(vm.evalCode('null'));

function marshal(target) {
  switch (typeof target) {
    case 'number': {
      const handle = vm.newNumber(target);
      disposables.push(handle);
      return handle;
    }
    case 'string': {
      const handle = vm.newString(target);
      disposables.push(handle);
      return handle;
    }
    case 'undefined': {
      return vm.undefined;
    }
    case 'boolean': {
      return target ? trueHandle : falseHandle;
    }
    case 'object': {
      if (target === null) {
        return nullHandle;
      }

      if (Array.isArray(target)) {
        const array = vm.unwrapResult(vm.evalCode('([])'));
        disposables.push(array);

        target.forEach((item) => {
          const marshaledItem = marshal(item);
          disposables.push(marshaledItem);

          const push = vm.getProp(array, 'push');
          vm.callFunction(push, array, marshaledItem);
        });

        return array;
      }

      const obj = vm.newObject();
      disposables.push(obj);

      Object.keys(target).forEach((key) => {
        const value = target[key];

        const marshaledKey = marshal(key);
        const marshaledValue = marshal(value);

        vm.setProp(obj, marshaledKey, marshaledValue);
      });

      return obj;
    }
    default: {
      throw new Error(`${typeof target} marshaling is not supported`);
    }
  }
}

// Call this when done, to dispose all handles
function dispose() {
  disposables.forEach((disposable) => disposable.alive && disposable.dispose())
}

// Define globals in vm

const globals = {
  // Can pass nested values
};

Object.keys(globals).forEach(key => {
  vm.setProp(
    vm.global,
    key,
    marshal(globals[key])
  );
});

from quickjs-emscripten.

justjake avatar justjake commented on May 30, 2024

@crysislinux I'll add VM-wide constants vm.true, vm.false, and vm.null the way we currrently have vm.undefined.
I can also expose a vm.newArray() that creates empty arrays.

Personally I use JSON.stringify to copy data into the VM for testing purposes. I haven't evaluated if recursive marshalling like @eliot-akira's example is faster or slower than JSON.stringify - it could be that the GC pressure of creating all the Lifetime wrapper objects is more expensive than the JSON.stringify / code parsing stuff. Probably moot for small object, but something to keep in mind for your testing if data copy speed is a bottleneck for your application.

from quickjs-emscripten.

crysislinux avatar crysislinux commented on May 30, 2024

@eliot-akira @justjake

Thank you both very much. It's good to know those tricks are common solutions to the problems 😀

from quickjs-emscripten.

crysislinux avatar crysislinux commented on May 30, 2024

@justjake I have another question unrelated this one, I guess call vm.dispose() at the end is enough(without calling dispose for every handle) if the VM is a short-live VM, right?

from quickjs-emscripten.

justjake avatar justjake commented on May 30, 2024

@crysislinux unfortunately no - you still need to free every handle. We could possibly track every owned handle inside the vm object, and do the handle.dispose() calls inside vm.dispose(), but as the API is currently written, you must dispose all the handles. The quickjs C code will assert the handles are correctly disposed.

from quickjs-emscripten.

justjake avatar justjake commented on May 30, 2024

Just published 0.4.0 which includes vm.newArray() and support for working with array indices as native numbers.

from quickjs-emscripten.

crysislinux avatar crysislinux commented on May 30, 2024

@justjake Thanks😋

from quickjs-emscripten.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.