Skip to content

RFC: support emscripten #34

@yuchanns

Description

@yuchanns

用来记录一下进度。使用分支 https://github.com/yuchanns/soluna/tree/feat/support-wasm

相关参考资料:


已完成事项

  • open_url
  • 使用 font.zip 方案
  • 输入法支持

  • 编译通过: 添加 luamke 编译支持, 添加 GHA CI 支持
    • 编译命令 luamake precompile && luamake -compiler emcc precompile && luamake -compiler emcc
    • 得到产物 bin/emcc/release/soluna{.wasm,.js}
    • 在该目录下创建 index.html, 并使用 emrun --no_browser --serve_root . --port 8000 index.html 启动调试
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Soluna Web (Lua test)</title>
  <style>
    body { margin: 0; background: #000; }
    canvas { display: block; width: 100vw; height: 100vh; }
  </style>
</head>
<body>
  <canvas id="canvas" oncontextmenu="event.preventDefault()"></canvas>

  <script>
  var Module = {
    arguments: ["entry=/data/main.lua"],
    canvas: (function () {
      const canvas = document.getElementById('canvas');
      canvas.addEventListener('webglcontextlost', function (e) {
        e.preventDefault();
      }, false);
      return canvas;
    })(),
    preRun: [function () {
      const luaSource = `
local soluna = require "soluna"

soluna.set_window_title "Soluna Sample"

local callback = {}

function callback.frame(count)
      print("Frame: " .. count)
end

return callback
`;

      const originalLookup = MEMFS.lookup;
      MEMFS.lookup = function (parent, name) {
        try {
          return originalLookup.call(this, parent, name);
        } catch (e) {
          console.error('[MEMFS lookup failed]', 
                        'parent:', Module.FS.getPath(parent), 
                        'name:', name, e);
          throw e;
        }
      };

      Module.FS_createPath('/', 'data', true, true);
      Module.FS.writeFile('/data/main.lua', luaSource);
      Module.FS.readdir('/');
      Module.FS.readdir('/data');
      Module.FS.readFile('/data/main.lua', { encoding: 'utf8' });
    }],
    onRuntimeInitialized: function () {
      console.log('Soluna runtime ready');
    },
    onExit: function (status) {
      console.log('Program exited with status', status);
    },
    onAbort: function (what) {
      console.error('Program aborted:', what);
    },
    print: console.log,
    printErr: console.error
  };
  </script>

  <script src="soluna.js"></script>
</body>
</html>
  • 尝试运行 test/window.lua
  • 实现 emscripten 缺失的功能
  • 尝试运行 deepfuture

目前编译已通过,但是在运行程序方面遇到了一些问题。

  • ltask socket pair 问题: 浏览器环境不支持 socket pair
    • timer 调用 eventinit api 会失败
    • 解决方案: 设置条件编译, 在 emscripten 环境下不初始化 socket pair. TODO: ltask 提交 PR
  • soluna 内置服务启动失败:
    • loader 服务启动失败, 在 require "soluna.spritebundle" 时抛出异常 RuntimeError: null function or function signature mismatch (下图1)
    • 原因暂时没查到, 通过在该行代码前后 print 确定出错位置, 且注释该行代码可以运行后续代码(下图2), 虽然会报找不到 spritebundle
    • 应该不是 spritbundle 有什么特殊问题, 而是某种共性错误, 注释掉 loader 服务, start 服务也会抛出同样的错误
    • Image
    • Image
    • 服务启动失败后, sokol 还可以继续回调 frame 等函数, 但是由于没有 lua 对应的函数所以会报错。
    • 下一步应该想办法找到 source map 的开启, 让错误栈打印出代码位置
    • 开启 debug 模式后看到了具体的错误原因: TODO 调整一下 luamake 脚本方便 debug
    • Image
    • 看起来 lmessage_send 内部出现了错误
    • 定位到是 send_message 类型不匹配。从 ltask.external_sender 拿到的是 int (*send_message)(void *ud, void *p) 而在 lmessage_send 里转成了 void (*send_message)(void *ud, void *p), 导致 wasm 校验异常。修改后正常,但是主线程卡住了, 新问题, 看起来是主线程没有返还给浏览器导致的.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions