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 MyView
class that should serve as the interface to Javascript. The idea is to create a new object of that type and then invoke StartRenderingLoop
which 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:
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
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
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);
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.
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);
}