I've been using SFML for a project, and I'm attempting to make a pause state while still using the SFML game loop. Long story short, I have been attempting to use 2 Lambda expressions within the same function to toggle between the pause and run states I have running. Here's some example code:
int main() {
//some stuff here...
auto pauseloop = [&] {
//do some stuff here...
runloop();
}
auto runloop = [&] {
//some more code, include SFML game loop here...
if (paused)
pauseloop();
}
runloop();
}
This works fine at first, but runs into an issue almost immediately: upon calling pauseloop()
from within runloop()
, I cannot call runloop()
from within pauseloop()
. I tried to add something to the effect of this above my main()
function:
auto pauseloop = [&] {}; //initializer
auto runloop = [&] {}; //initializer
However, after some extensive testing and debugging, this causes the runloop()
Lambda expression above the main()
function to be called when being called from pauseloop()
, however the intended, fully declared, runloop()
is called when called at the end of the main()
function.
First, is there any way to get two Lambda expressions from within the same function to call each other? Second, why does the pauseloop()
expression call the initializer version of runloop()
, while the runloop()
at the end of main()
calls the intended one? From my best guess, I would have assumed that because runloop()
and pauseloop()
were both initialized defined before either one was called, the pauseloop()
would call the declared version, as it had been read in its entirety and correctly overwritten the initializer, yet this seems completely off compared to what is actually happening. Thank you in advance!
Edit 1: To be concise, the above attempts don't work for the intended solution (having Lambda expression "a" call Lambda expression "b" and vice versa). How would I write two Lambda expressions that can?
CodePudding user response:
As long as your lambdas have captures, and it seems like yours do, you can't have them call each other directly because there is a circular dependency when you declare them. To work around this, you can assign them to pointers.
If you did not have captures, the compiler would simply generate anonymous functions for your lambdas and you could assign them to function pointers like this:
void (*pauseloop)() = nullptr;
void (*runloop)() = nullptr;
pauseloop = []() {
//...
runloop();
//...
};
runloop = []() {
//...
pauseloop();
//...
};
Since you do have captures, the solution is very similar but you would use std::function
instead. Actually, since you can use std::function
with functions as well, this is my recommendation in any case:
std::function<void()> pauseloop, runloop;
pauseloop = [&]() {
// ...
runloop();
// ...
};
runloop = [&]() {
// ...
pauseloop();
// ...
};
Note that, in both cases, it is not going to be enough to capture the function pointer / std::function
by value. At least one of the lambdas will have to capture it by reference or use it as a global. Otherwise it will only capture the initial nullptr
value.
CodePudding user response:
As you have discovered, simply using each of them through capture will create a circular dependency. An alternative to it is to pass them as arguments.
Let's start with the runloop
. Rather than getting the pauseloop
through capture, you can pass it as an argument:
auto runloop = [&](auto const& pauseloop) {
if (pause) {
pauseloop();
}
};
And later you can call it like: runloop(pauseloop);
Similarly, you would give pauseloop
an argument:
auto pauseloop = [&](auto const& runloop) {
runloop();
};
However, now in the pauseloop
, you were calling runloop()
, despite runloop
actually requires a parameter. Similarly, in runloop
, you are calling pauseloop()
despite pauseloop
also requires a parameter. To fix it, you can also pass the parameter that should be called in them to the outer function:
auto runloop = [&](auto const& runloop, auto const& pauseloop) {
if (pause) {
pauseloop(runloop, pauseloop);
}
};
auto pauseloop = [&](auto const& runloop, auto const& pauseloop) {
runloop(runloop, pauseloop);
};
Now, you would call runloop
and pauseloop
like:
runloop(runloop, pauseloop);
pauseloop(runloop, pauseloop);
However, that final call interface is pretty verbose. To make it simpler, you can separate the interface and the implementation for each of them:
auto _runloop_impl = [&](auto const& runloop, auto const& pauseloop) {
if (pause) {
pauseloop(runloop, pauseloop);
}
};
auto _pauseloop_impl = [&](auto const& runloop, auto const& pauseloop) {
runloop(runloop, pauseloop);
};
auto runloop = [&] {
_runloop_impl(_runloop_impl, _pauseloop_impl);
};
auto pauseloop = [&] {
_pauseloop_impl(_runloop_impl, _pauseloop_impl);
};
Now, you can simply call them like:
runloop();
pauseloop();
- Note, you only need to define the interface lambda if you intend to call them directly.
And if you have many different types of loop to add into the logic, you can potentially use parameter pack to easily pass all arguments around:
auto _runloop_impl = [&](auto const& ... loops) {
auto loops_tuple = std::tuple{std::cref(loops)...};
switch(state) {
case State::PAUSE:
std::get<1>(loops_tuple)(loops...);
break;
case State::MENU:
std::get<2>(loops_tuple)(loops...);
break;
case State::SHOP:
std::get<3>(loops_tuple)(loops...);
break;
}
};
auto _pauseloop_impl = [&](auto const& ... loops) {
auto loops_tuple = std::tuple{std::cref(loops)...};
std::get<0>(loops_tuple)(loops...);
};
////////////////////////////////////////////////////
//
// ... similar implementations for other loops
//
////////////////////////////////////////////////////
auto runloop = [&] {
_runloop_impl(
_runloop_impl,
_pauseloop_impl,
_menuloop_impl,
_shoploop_impl
)
};
int main() {
runloop();
}