无法将正确的 lambda 传递给 emscripten_set_main_loop

Unable to pass a proper lambda to emscripten_set_main_loop

我真的很难指示 emscripten_set_main_loop 执行渲染新帧的方法。我根本无法想出一个成功编译的 lambda,更不用说按预期运行了。

首先,我得到了 MyViewclass,它应该作为 Javascript 的接口。这个想法是创建一个该类型的新对象,然后调用 StartRenderingLoop 这将启动渲染新帧的循环。这应该从 Javascript 开始,这就是为什么我在底部得到 Embind 定义的原因:

class MyView
{
public:
  void StartRenderingLoop();

private:
  std::unique_ptr<Renderer> _renderer;
  void Render();
};

EMSCRIPTEN_KEEPALIVE
MyView::MyView()
{
  _renderer = std::unique_ptr<Renderer>(new Renderer());
}

void MyView::Render()
{
  _renderer->Render();
}

EMSCRIPTEN_KEEPALIVE
void MyView::StartRenderingLoop()
{
  emscripten_set_main_loop(/* should invoke MyView::Render(), but how? */, -1, 1);
}

EMSCRIPTEN_BINDINGS(MyView)
{
  class_<MyView>("MyView")
    .function("startRenderingLoop", &MyView::StartRenderingLoop)
    // everything else...
    ;
}

这是我尝试过的:

  1. 这甚至无法编译:

    emscripten_set_main_loop(&MyView::Render(), -1, 1);
    

    Emscripten 抛出以下错误:

    note: candidate function not viable: no known conversion from 'void (MyView::*)()' to 'em_callback_func' (aka 'void (*)()') for 1st argument
    
  2. 不编译:

    emscripten_set_main_loop([]() { _renderer->Render(); }, -1, 1);
    

    因为它无法访问 this 指针,很公平:

    error: 'this' cannot be implicitly captured in this context
    
  3. 捕获 this 使 lambda 与预期不兼容并抛出类似于尝试 1 的错误:

    emscripten_set_main_loop([this]() { _renderer->Render(); }, -1, 1);
    
  4. 声明一个全局std::function对象:

    static std::function<void()> renderLoopFunction;
    

    然后在 StartRenderingLoop 中做这样的事情:

    EMSCRIPTEN_KEEPALIVE
    void MyView::StartRenderingLoop()
    {
      renderLoopFunction = [=]() mutable { int test = 3; };
      emscripten_set_main_loop(renderLoopFunction, -1, 1);
    }
    

    this 等资源相反,它再次编译失败:

    note: candidate function not viable: no known conversion from 'std::function<void ()>' to 'em_callback_func' (aka 'void (*)()') for 1st argument
    

我正处于这样一个阶段,如果有一个拥有三个免费愿望的仙女出现,我很乐意将其中一个用于解决这个谜团,只是为了让编译器停止告诉我我是一个多么无能的白痴.

该接口需要类型为 void (*)() 的普通旧 c 函数指针。

假设您只打算拥有一个 Render 对象...

class MyView
{
public:
  void Render();

private:
  std::unique_ptr<Renderer> _renderer;
};

// a global or otherwise known globally
auto view = MyView();

// &main_loop has the signature void (*)(), which is what you need
void main_loop()
{
  view.Render();
}

int main()
{
   emscripten_set_main_loop(&main_loop, -1, 1);
}

似乎为了利用 emscripten_set_main_loop_arg 我真的不得不退回到使用 C。因为我更喜欢尽可能地保持代码面向对象,所以我正在寻找替代方案。事实证明,该函数的第二个版本能够处理参数:emscripten_set_main_loop_arg.

使用 emscripten_set_main_loop_arg 我可以将 StartRenderingLoop 方法保留为实例方法,只需将 this 传递给 Render 回调。修改后的代码如下:

void MyView::Render()
{
  _renderer->Render();
}


void RenderLoopCallback(void* arg)
{
  static_cast<MyView*>(arg)->Render();
}


EMSCRIPTEN_KEEPALIVE
void MyView::StartRenderingLoop()
{
  emscripten_set_main_loop_arg(&RenderLoopCallback, this, -1, 1);
}

这是我一直在做的事情:

   void do_frame(void* void_fn_ptr) {
        auto* fn_ptr = reinterpret_cast<std::function<void()>*>(void_fn_ptr);
        if (fn_ptr) {
            auto& fn = *fn_ptr;
            fn();
        }
    }


    void window::loop(std::function<void()> fn) {
        #ifdef __EMSCRIPTEN__
            emscripten_set_main_loop_arg(do_frame, &fn, 0, 1);
        #else
            while (!should_close()) {
                fn();
            }
        #endif
    }

然后:

    w.loop([&]() {
        // render stuff...
    });