ceifa / wasmoon

A real lua 5.4 VM with JS bindings made with webassembly
MIT License
495 stars 32 forks source link

unable to read table returned by js async function #96

Closed hariseldon78 closed 1 year ago

hariseldon78 commented 1 year ago

If i declare an async function from javascript, that returns a table, when I run it inside the lua script i obtain a 'js_promise' object instead. If instead the async function returns a simple value i correctly receive that value.

With the following unit test:

import { LuaFactory, LuaEngine } from 'wasmoon';

describe('Wasmoon lua library', () => {
    let factory: LuaFactory;
    let lua: LuaEngine;
    beforeAll(async () => {
        factory = new LuaFactory();
    });
    beforeEach(async () => {
        lua=await factory.createEngine();
        lua.global.set('testResults', {a: 1, b: 2, c: 3});
    });
    it('should run a lua script', async () => {
        const result = await lua.doString('return testResults.a');
        expect(result).toBe(1);
    });
    it('should run a lua script 2', async () => {
        const result = await lua.doString('return testResults.b + testResults.c');
        expect(result).toBe(5);
    });
    // sync functions
    it('shold run a function', async () => {
        function f() {
            return 42;
        }
        lua.global.set('f', f);
        const result = await lua.doString('return f()');
        expect(result).toBe(42);
    });
    it('should run a function with arguments', async () => {
        function f(a: number, b: number) {
            return a + b;
        }
        lua.global.set('f', f);
        const result = await lua.doString('return f(1, 2)');
        expect(result).toBe(3);
    });
    it('should run a function with arguments and return a table', async () => {
        function f(a: number, b: number) {
            return { a, b };
        }
        lua.global.set('f', f);
        const result = await lua.doString(`
local res=f(1, 2)
return res.b+res.a
`);
        expect(result).toEqual(3);
    });
    it('should run a function with arguments and return a closure', async () => {
        function f(a: number, b: number) {
            return { a, b, c: () => a + b };
        }
        lua.global.set('f', f);
        const result = await lua.doString('return f(1, 2)');
        expect(result.c()).toEqual(3);
    });

    // async functions
    it('should run an async function', async () => {
        async function f() {
            await new Promise((resolve) => setTimeout(resolve, 100));
            return 42;
        }
        lua.global.set('f', f);
        const result = await lua.doString('return f()');
        expect(result).toBe(42);
    });
    it('should run an async function with arguments', async () => {
        async function f(a: number, b: number) {
            await new Promise((resolve) => setTimeout(resolve, 100));
            return a + b;
        }
        lua.global.set('f', f);
        const result = await lua.doString('return f(1, 2)');
        expect(result).toBe(3);
    });
    it.only('should run an async function with arguments and use the returned table', async () => {
        async function f(a: number, b: number) {
            await new Promise((resolve) => setTimeout(resolve, 100));
            return { a, b };
        }
        lua.global.set('f', f);
        const result = await lua.doString(`
local res=f(1, 2)
print(res)
return res.b+res.a
`);
        expect(result).toEqual(3);
    });
    it('should run an async function with arguments and return a table', async () => {
        async function f(a: number, b: number) {
            await new Promise((resolve) => setTimeout(resolve, 100));
            return { a, b };
        }
        lua.global.set('f', f);
        const result = await lua.doString('return f(1, 2)');
        expect(result).toEqual({ a: 1, b: 2 });
    });
});

the result is

  console.log
    js_promise: 0x1337c8

      at Object.Jd (../../node_modules/wasmoon/dist/index.js:1194:132)

 FAIL  test/lua-wasmoon.spec.ts
  Wasmoon lua library
    ✓ should run a lua script (49 ms)
    ✓ should run a lua script 2 (2 ms)
    ✓ shold run a function (2 ms)
    ✓ should run a function with arguments (2 ms)
    ✓ should run a function with arguments and return a table (2 ms)
    ✓ should run a function with arguments and return a closure (3 ms)
    ✓ should run an async function (106 ms)
    ✓ should run an async function with arguments (106 ms)
    ✕ should run an async function with arguments and use the returned table (13 ms)
    ✓ should run an async function with arguments and return a table (102 ms)

  ● Wasmoon lua library › should run an async function with arguments and use the returned table

    [string "..."]:4: attempt to perform arithmetic on a nil value (field 'b')

      82 |         }
      83 |         lua.global.set('f', f);
    > 84 |         const result = await lua.doString(`
         |                                  ^
      85 | local res=f(1, 2)
      86 | print(res)
      87 | return res.b+res.a

      at Thread.assertOk (../../node_modules/wasmoon/dist/index.js:365:31)
      at Thread.run (../../node_modules/wasmoon/dist/index.js:172:22)
      at LuaEngine.callByteCode (../../node_modules/wasmoon/dist/index.js:1155:45)
      at LuaEngine.doString (../../node_modules/wasmoon/dist/index.js:1135:25)
      at test/lua-wasmoon.spec.ts:84:34
      at ../../node_modules/tslib/tslib.js:115:75
      at Object.__awaiter (../../node_modules/tslib/tslib.js:111:16)
      at Object.<anonymous> (test/lua-wasmoon.spec.ts:78:93)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 9 passed, 10 total
Snapshots:   0 total
Time:        3.359 s
Ran all test suites matching /wasmoon/i.

As you can see, the returned table is not read correctly on lua side; the two previous tests instead are reading the values correctly and passing. Am I missing something obvious? Should I call the async function in a special way? (thread? could you give me an example?)

hariseldon78 commented 1 year ago

Ok i found the answer, i'll close this issue but leave it for somebody that could have the same problem: the solution is to add :await() inside lua to the returned js_promise. Btw: the other tests were working because i didn't actually used the values, just returned them, so they were awaited on js side on returning. The fixed unit test is:

import { LuaFactory, LuaEngine } from 'wasmoon';

describe('Wasmoon lua library', () => {
    let factory: LuaFactory;
    let lua: LuaEngine;
    beforeAll(async () => {
        factory = new LuaFactory();
    });
    beforeEach(async () => {
        lua=await factory.createEngine();
        lua.global.set('testResults', {a: 1, b: 2, c: 3});
    });
    it('should run a lua script', async () => {
        const result = await lua.doString('return testResults.a');
        expect(result).toBe(1);
    });
    it('should run a lua script 2', async () => {
        const result = await lua.doString('return testResults.b + testResults.c');
        expect(result).toBe(5);
    });
    // sync functions
    it('shold run a function', async () => {
        function f() {
            return 42;
        }
        lua.global.set('f', f);
        const result = await lua.doString('return f()');
        expect(result).toBe(42);
    });
    it('should run a function with arguments', async () => {
        function f(a: number, b: number) {
            return a + b;
        }
        lua.global.set('f', f);
        const result = await lua.doString('return f(1, 2)');
        expect(result).toBe(3);
    });
    it('should run a function with arguments and return a table', async () => {
        function f(a: number, b: number) {
            return { a, b };
        }
        lua.global.set('f', f);
        const result = await lua.doString(`
local res=f(1, 2)
return res.b+res.a
`);
        expect(result).toEqual(3);
    });
    it('should run a function with arguments and return a closure', async () => {
        function f(a: number, b: number) {
            return { a, b, c: () => a + b };
        }
        lua.global.set('f', f);
        const result = await lua.doString('return f(1, 2)');
        expect(result.c()).toEqual(3);
    });

    // async functions
    it('should run an async function', async () => {
        async function f() {
            await new Promise((resolve) => setTimeout(resolve, 100));
            return 42;
        }
        lua.global.set('f', f);
        const result = await lua.doString('return f()');
        expect(result).toBe(42);
    });
    it('should run an async function 2', async () => {
        async function f() {
            await new Promise((resolve) => setTimeout(resolve, 100));
            return 42;
        }
        lua.global.set('f', f);
        const result = await lua.doString('return f():await()+f():await()');
        expect(result).toBe(84);
    });
    it('should run an async function with arguments', async () => {
        async function f(a: number, b: number) {
            await new Promise((resolve) => setTimeout(resolve, 100));
            return a + b;
        }
        lua.global.set('f', f);
        const result = await lua.doString('return f(1, 2)');
        expect(result).toBe(3);
    });
    it('should run an async function with arguments and use the returned table', async () => {
        async function f(a: number, b: number) {
            await new Promise((resolve) => setTimeout(resolve, 100));
            return { a, b };
        }
        lua.global.set('f', f);
        const result = await lua.doString(`
local res=f(1, 2):await()
print(res)
return res.b+res.a
`);
        expect(result).toEqual(3);
    });
    it('should run an async function with arguments and return a table', async () => {
        async function f(a: number, b: number) {
            await new Promise((resolve) => setTimeout(resolve, 100));
            return { a, b };
        }
        lua.global.set('f', f);
        const result = await lua.doString('return f(1, 2)');
        expect(result).toEqual({ a: 1, b: 2 });
    });
});