Closed GasimGasimzada closed 5 months ago
As I see it in your code, the important part from lua seems to be that you need to only be able to bind arbitrary lua function to a signal regardless of what that signal is as long as the signal is exposed to lua.
A problem is that you cannot access templated classes without knowing the template parameters, which are the types that you cannot get from lua. You are basically required to do any binding for templated classes beforehand or you must know/have a predetermined logic to come up with the template signature in the function you bind. There are different kinds of things you can do depending on what you need. There might be some way to get around to what you need using templates, but off the top of my head I didnt come up with any that did not seem impractical (for example make a map of template instanciations that are then available to lua through a map of sorts).
Instead, here is one simple example that utilizes a inheritance to implement a non-templated base class for lua while maintaining the typed C++ counterpart in the templated subclass. It allows you to bind the functionality required by lua through the base class only once, enabling you to connect functions to any signal exposed into lua regardless of its types. Maybe its what you need, or maybe it gives you inspiration for something cooler :)
I ended up doing a a weird implementation with lambdas. One of the things that I wanted to also do was to make the lua signal "proxy" to consume the internal Signal (the example below is simplified but I pass more than just the original signal to the SolSignal class):
template<class... TArgs>
class Signal {
using Handler = std::function<void(TArgs &...)>;
public:
void connect(Handler &&handler) {
auto id = handlers.size();
handlers.push_back(handler);
}
void notify(TArgs... args) {
for (auto handler: handlers) {
handler(args...);
}
}
private:
std::vector<Handler> handlers;
};
class SolSignal {
public:
template<class... TArgs>
SolSignal(Signal<TArgs...> &signal) {
mConnector = [&signal](sol::protected_function fn) {
return signal.connect([fn](TArgs &...args) {
fn(args...);
});
};
}
SignalSlot connect(sol::protected_function fn) {
return mConnector(fn);
}
private:
std::function<SignalSlot(sol::protected_function)> mConnector;
};
int main() {
sol::state state;
state.open_libraries();
auto usertype = state.new_usertype<SolSignal>("Signal", sol::no_constructor);
usertype["connect"] = &SolSignal::connect;
Signal<float, uint32_t> signal;
state["mySignal"] = SolSignal(signal);
signal.connect([](float a, uint32_t b) {
std::cout << "App: " << a << " " << b << "\n";
});
state.script(R"(
mySignal:connect(function(a, b)
print('Lua: ', a, b)
end)
)");
signal.notify(10.0f, 20);
return 0;
}
But I might try inheritance as well. Maybe I can make it work with it since I have kind of made my own dynamic dispatch here and if I can, I would want to use the language feature itself :)
I want to implement a Signal-Slot pattern that I can use within C++ and in Sol:
(Godbolt link: https://godbolt.org/z/a7Mqn4c9v)
This works well but I have a lot of signals in my application and I do not want to create a separate usertype for each type. Is there any way that I can create a usertype in Lua only once and use a template class instance to generate the value?