Search code examples
c++lambdacallbackemscripten

Unable to pass a proper lambda to emscripten_set_main_loop


I'm really struggling to instruct emscripten_set_main_loop to execute a method that renders a new frame. I'm simply unable to come up with a lambda that successfully compiles, let alone runs as expected.

First, I got the MyViewclass that should serve as the interface to Javascript. The idea is to create a new object of that type and then invoke StartRenderingLoopwhich would start the loop that renders new frames. This should be done from Javascript which is why I got the Embind definition at the bottom:

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...
    ;
}

Here's what I tried:

  1. This won't even compile:

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

    Emscripten throws the following error:

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

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

    Because it cannot access the this pointer, fair enough:

    error: 'this' cannot be implicitly captured in this context
    
  3. Capturing this makes the lambda incompatible with whatever is expected and throws an error similar to attempt 1):

    emscripten_set_main_loop([this]() { _renderer->Render(); }, -1, 1);
    
  4. Declare a global std::function object:

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

    then in StartRenderingLoop do something like this:

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

    Contrary to resources such as this it again fails to compile:

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

I'm at the stage where, if a fairy with three free wishes would show up, I'd gladly invest one of these into solving that mystery just to have the compiler stop telling me what an incompetent idiot I am.


Solution

  • It seems that in order to utilize emscripten_set_main_loop_arg I'd really have to fall back to using C. Because I prefer to keep the code object oriented as far as possible, I was looking for an alternative. As it turns out, there is a second version of that function that is able to process an argument: emscripten_set_main_loop_arg.

    Using emscripten_set_main_loop_arg I can keep StartRenderingLoop method as an instance method and just pass this to the Render callback. The modified code looks as follows:

    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);
    }