mirror of
https://github.com/ThePhD/sol2.git
synced 2024-03-22 13:10:44 +08:00
Improve documentation and add examples for the things requested by @Nava2, @billw2012 and friends from #71 and other places
Closes the loop and informs people of the changes coming to the newest version thanks to the #116 changes Closes #100
This commit is contained in:
parent
cf5919b705
commit
147aff1915
|
@ -43,3 +43,112 @@ You can use this in conjunction with :doc:`sol::table<table>` to set/get a metat
|
||||||
int result2 = b_call(b, 1);
|
int result2 = b_call(b, 1);
|
||||||
// result1 == result2 == 1
|
// result1 == result2 == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
It's further possible to have a "Dynamic Getter" (`thanks to billw2012 and Nava2 for this example`_!):
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
:caption: One way to make dynamic properties (there are others!)
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
#include <sol.hpp>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
struct PropertySet {
|
||||||
|
sol::object get_property_lua(const char* name, sol::this_state s)
|
||||||
|
{
|
||||||
|
auto& var = props[name];
|
||||||
|
return sol::make_object(s, var);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_property_lua(const char* name, sol::stack_object object)
|
||||||
|
{
|
||||||
|
props[name] = object.as<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::string> props;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DynamicObject {
|
||||||
|
PropertySet& get_dynamic_props() {
|
||||||
|
return dynamic_props;
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertySet dynamic_props;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
int main () {
|
||||||
|
sol::state lua;
|
||||||
|
lua.open_libraries(sol::lib::base);
|
||||||
|
|
||||||
|
lua.new_usertype<PropertySet>("PropertySet",
|
||||||
|
sol::meta_function::new_index, &PropertySet::set_property_lua,
|
||||||
|
sol::meta_function::index, &PropertySet::get_property_lua
|
||||||
|
);
|
||||||
|
|
||||||
|
lua.new_usertype<DynamicObject>("DynamicObject",
|
||||||
|
"props", sol::property(&DynamicObject::get_dynamic_props)
|
||||||
|
);
|
||||||
|
|
||||||
|
lua.script(R"(
|
||||||
|
obj = DynamicObject:new()
|
||||||
|
obj.props.name = 'test name'
|
||||||
|
print('name = ' .. obj.props.name)
|
||||||
|
)");
|
||||||
|
|
||||||
|
std::string name = lua["obj"]["props"]["name"];
|
||||||
|
// name == "test name";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
You can even manipulate the ability to set and get items using metatable objects on a usertype or similar:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
:caption: messing with metatables - vector type
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
#include <sol.hpp>
|
||||||
|
|
||||||
|
class vector {
|
||||||
|
public:
|
||||||
|
double data[3];
|
||||||
|
|
||||||
|
vector() : data{ 0,0,0 } {}
|
||||||
|
|
||||||
|
double& operator[](int i)
|
||||||
|
{
|
||||||
|
return data[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static double my_index(vector& v, int i)
|
||||||
|
{
|
||||||
|
return v[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void my_new_index(vector& v, int i, double x)
|
||||||
|
{
|
||||||
|
v[i] = x;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main () {
|
||||||
|
sol::state lua;
|
||||||
|
lua.open_libraries(sol::lib::base);
|
||||||
|
lua.new_usertype<vector>("vector", sol::constructors<sol::types<>>(),
|
||||||
|
sol::meta_function::index, &vector::my_index,
|
||||||
|
sol::meta_function::new_index, &vector::my_new_index);
|
||||||
|
lua.script("v = vector.new()\n"
|
||||||
|
"print(v[1])\n"
|
||||||
|
"v[2] = 3\n"
|
||||||
|
"print(v[2])\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
vector& v = lua["v"];
|
||||||
|
// v[0] == 0.0;
|
||||||
|
// v[1] == 0.0;
|
||||||
|
// v[2] == 3.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.. _thanks to billw2012 and Nava2 for this example: https://github.com/ThePhD/sol2/issues/71#issuecomment-225402055
|
|
@ -60,7 +60,7 @@ The following C++ code will call this function from this file and retrieve the r
|
||||||
auto secondwoof = problematic_woof(19);
|
auto secondwoof = problematic_woof(19);
|
||||||
if (secondwoof.valid()) {
|
if (secondwoof.valid()) {
|
||||||
// Call succeeded
|
// Call succeeded
|
||||||
double numwoof = firstwoof;
|
double numwoof = secondwoof;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Call failed
|
// Call failed
|
||||||
|
@ -128,6 +128,7 @@ Get and set the Lua entity that is used as the default error handler. The defaul
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
:caption: variable: handler
|
:caption: variable: handler
|
||||||
|
:name: protected-function-error-handler
|
||||||
|
|
||||||
reference error_handler;
|
reference error_handler;
|
||||||
|
|
||||||
|
|
|
@ -189,9 +189,13 @@ inheritance
|
||||||
|
|
||||||
Sol can adjust pointers from derived classes to base classes at runtime, but it has some caveats based on what you compile with:
|
Sol can adjust pointers from derived classes to base classes at runtime, but it has some caveats based on what you compile with:
|
||||||
|
|
||||||
If your class has no complicated™ virtual inheritance or multiple inheritance, than you can try to sneak away with the performance boost from not specifying any base classes and doing any casting checks. (What does "complicated™" mean? Ask your compiler's documentation, if you're in that deep.)
|
If your class has no complicated™ virtual inheritance or multiple inheritance, than you can try to sneak away with a performance boost from not specifying any base classes and doing any casting checks. (What does "complicated™" mean? Ask your compiler's documentation, if you're in that deep.)
|
||||||
|
|
||||||
For the rest of us safe individuals out there: You must specify the ``sol::base_classes`` tag with the ``sol::bases<Types...>()`` argument, where ``Types...`` are all the base classes of the single type ``T`` that you are making a usertype out of. If you turn exceptions off and are also completely mad and turn off :doc:`run-time type information<../rtti>` as well, we fallback to a id-based systemthat still requires you to specifically list the base classes as well. For example:
|
For the rest of us safe individuals out there: You must specify the ``sol::base_classes`` tag with the ``sol::bases<Types...>()`` argument, where ``Types...`` are all the base classes of the single type ``T`` that you are making a usertype out of. If you turn exceptions off and are also completely mad and turn off :doc:`run-time type information<../rtti>` as well, we fallback to a id-based system that still requires you to specifically list the base classes as well. For example:
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Please be advised that this was a breaking change from before: previously, with the power of exceptions we did not need to list base classes. This unfortunately came at the cost of quite a bit of performance, and therefore it is not adivsable / required to specify all of your base classes, exceptions or no exceptions, RTTI or not: always specify your bases.
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
:linenos:
|
:linenos:
|
||||||
|
@ -219,7 +223,9 @@ Then, to register the base classes explicitly:
|
||||||
sol::base_classes, sol::bases<A>()
|
sol::base_classes, sol::bases<A>()
|
||||||
);
|
);
|
||||||
|
|
||||||
Note that Sol does not support down-casting from a base class to a derived class at runtime.
|
.. note::
|
||||||
|
|
||||||
|
Sol does not support down-casting from a base class to a derived class at runtime.
|
||||||
|
|
||||||
inheritance + overloading
|
inheritance + overloading
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
|
@ -370,7 +370,7 @@ multiple returns to lua
|
||||||
|
|
||||||
lua["f"] = [](int a, int b, sol::object c) {
|
lua["f"] = [](int a, int b, sol::object c) {
|
||||||
// sol::object can be anything here: just pass it through
|
// sol::object can be anything here: just pass it through
|
||||||
return std::make_tuple( 100, 200, c );
|
return std::make_tuple( a, b, c );
|
||||||
};
|
};
|
||||||
|
|
||||||
std::tuple<int, int, int> result = lua["f"](100, 200, 300);
|
std::tuple<int, int, int> result = lua["f"](100, 200, 300);
|
||||||
|
@ -501,6 +501,7 @@ advanced
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Some more advanced things you can do/read about:
|
Some more advanced things you can do/read about:
|
||||||
|
* :doc:`metatable manipulations<../api/metatable_key>` allow a user to change how indexing, function calls, and other things work on a single type.
|
||||||
* :doc:`ownership semantics<ownership>` are described for how lua deals with (raw) pointers.
|
* :doc:`ownership semantics<ownership>` are described for how lua deals with (raw) pointers.
|
||||||
* :doc:`stack manipulation<../api/stack>` to safely play with the stack. You can also define customization points for ``stack::get``/``stack::check``/``stack::push`` for your type.
|
* :doc:`stack manipulation<../api/stack>` to safely play with the stack. You can also define customization points for ``stack::get``/``stack::check``/``stack::push`` for your type.
|
||||||
* :doc:`stack references<../api/stack_reference>` to have zero-overhead Sol abstractions while not copying to the Lua registry.
|
* :doc:`stack references<../api/stack_reference>` to have zero-overhead Sol abstractions while not copying to the Lua registry.
|
||||||
|
|
|
@ -6,13 +6,15 @@ Sol can register all kinds of functions. Many are shown in the :doc:`quick 'n' d
|
||||||
Setting a new function
|
Setting a new function
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
Given a C++ function, you can drop it into Sol in several equivalent ways, working similar to how :ref:`setting variables<writing-main-cpp>`
|
Given a C++ function, you can drop it into Sol in several equivalent ways, working similar to how :ref:`setting variables<writing-main-cpp>` works:
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
:linenos:
|
:linenos:
|
||||||
:caption: Registering C++ functions
|
:caption: Registering C++ functions
|
||||||
:name: writing-functions
|
:name: writing-functions
|
||||||
|
|
||||||
|
#include <sol.hpp>
|
||||||
|
|
||||||
std::string my_function( int a, std::string b ) {
|
std::string my_function( int a, std::string b ) {
|
||||||
// Create a string with the letter 'D' "a" times,
|
// Create a string with the letter 'D' "a" times,
|
||||||
// append it to 'b'
|
// append it to 'b'
|
||||||
|
@ -23,6 +25,273 @@ Given a C++ function, you can drop it into Sol in several equivalent ways, worki
|
||||||
|
|
||||||
sol::state lua;
|
sol::state lua;
|
||||||
|
|
||||||
lua["my_func"] = my_function;
|
lua["my_func"] = my_function; // way 1
|
||||||
|
lua.set("my_func", my_function); // way 2
|
||||||
|
lua.set_function("my_func", my_function); // way 3
|
||||||
|
|
||||||
|
// This function is now accessible as 'my_func' in
|
||||||
|
// lua scripts / code run on this state:
|
||||||
|
lua.script("some_str = my_func(1, "Da");
|
||||||
|
|
||||||
|
// Read out the global variable we stored in 'some_str' in the
|
||||||
|
// quick lua code we just executed
|
||||||
|
std::string some_str = lua["some_str"];
|
||||||
|
// some_str == "DaD"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
The same code works with all sorts of functions, from member function/variable pointers you have on a class as well as lambdas:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
:linenos:
|
||||||
|
:caption: Registering C++ member functions
|
||||||
|
:name: writing-member-functions
|
||||||
|
|
||||||
|
struct my_class {
|
||||||
|
int a = 0;
|
||||||
|
|
||||||
|
my_class(int x) : a(x) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int func() {
|
||||||
|
++a; // increment a by 1
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main () {
|
||||||
|
|
||||||
|
sol::state lua;
|
||||||
|
|
||||||
|
// Here, we are binding the member function and a class instance: it will call the function on
|
||||||
|
// the given class instance
|
||||||
|
lua.set_function("my_class_func", &my_class::func, my_class());
|
||||||
|
|
||||||
|
// We do not pass a class instance here:
|
||||||
|
// the function will need you to pass an instance of "my_class" to it
|
||||||
|
// in lua to work, as shown below
|
||||||
|
lua.set_function("my_class_func_2", &my_class::func);
|
||||||
|
|
||||||
|
// With a pre-bound instance:
|
||||||
|
lua.script(R"(
|
||||||
|
first_value = my_class_func()
|
||||||
|
second_value = my_class_func()
|
||||||
|
)");
|
||||||
|
// first_value == 1
|
||||||
|
// second_value == 2
|
||||||
|
|
||||||
|
// With no bound instance:
|
||||||
|
lua.set("obj", my_class(24));
|
||||||
|
// Calls "func" on the class instance
|
||||||
|
// referenced by "obj" in Lua
|
||||||
|
lua.script(R"(
|
||||||
|
third_value = my_class_func_2(obj)
|
||||||
|
fourth_value = my_class_func_2(obj)
|
||||||
|
)");
|
||||||
|
// first_value == 25
|
||||||
|
// second_value == 26
|
||||||
|
}
|
||||||
|
|
||||||
|
Member class functions and member class variables will both be turned into functions when set in this manner. You can get intuitive variable with the ``obj.a = value`` access after this section when you learn about :doc:`usertypes to have C++ in Lua<cxx-in-lua>`, but for now we're just dealing with functions!
|
||||||
|
|
||||||
|
|
||||||
|
Another question a lot of people have is about function templates. Function templates -- member functions or free functions -- cannot be registered because they do not exist until you instantiate them in C++. Therefore, given a templated function such as:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
:linenos:
|
||||||
|
:caption: A C++ templated function
|
||||||
|
:name: writing-templated-functions-the-func
|
||||||
|
|
||||||
|
template <typename A, typename B>
|
||||||
|
auto my_add( A a, B b ) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
You must specify all the template arguments in order to bind and use it, like so:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
:linenos:
|
||||||
|
:caption: Registering function template instantiations
|
||||||
|
:name: writing-templated-functions
|
||||||
|
|
||||||
|
|
||||||
|
int main () {
|
||||||
|
|
||||||
|
sol::state lua;
|
||||||
|
|
||||||
|
// adds 2 integers
|
||||||
|
lua["my_int_add"] = my_add<int, int>;
|
||||||
|
|
||||||
|
// concatenates 2 strings
|
||||||
|
lua["my_string_combine"] = my_add<std::string, std::string>;
|
||||||
|
|
||||||
|
lua.script("my_num = my_int_add(1, 2)");
|
||||||
|
int my_num = lua["my_num"];
|
||||||
|
// my_num == 3
|
||||||
|
|
||||||
|
lua.script("my_str = my_string_combine('bark bark', ' woof woof')");
|
||||||
|
std::string my_str = lua["my_str"];
|
||||||
|
// my_str == "bark bark woof woof"
|
||||||
|
}
|
||||||
|
|
||||||
|
Notice here that we bind two separate functions. What if we wanted to bind only one function, but have it behave differently based on what arguments it is called with? This is called Overloading, and it can be done with :doc:`sol::overload<../api/overload>` like so:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
:linenos:
|
||||||
|
:caption: Registering C++ function template instantiations
|
||||||
|
:name: writing-templated-functions-overloaded
|
||||||
|
|
||||||
|
|
||||||
|
int main () {
|
||||||
|
|
||||||
|
sol::state lua;
|
||||||
|
|
||||||
|
// adds 2 integers
|
||||||
|
lua["my_combine"] = sol::overload( my_add<int, int>, my_add<std::string, std::string> );
|
||||||
|
|
||||||
|
lua.script("my_num = my_combine(1, 2)");
|
||||||
|
lua.script("my_str = my_combine('bark bark', ' woof woof')");
|
||||||
|
int my_num = lua["my_num"];
|
||||||
|
std::string my_str = lua["my_str"];
|
||||||
|
// my_num == 3
|
||||||
|
// my_str == "bark bark woof woof"
|
||||||
|
}
|
||||||
|
|
||||||
|
This is useful for functions which can take multiple types and need to behave differently based on those types. You can set as many overloads as you want, and they can be of many different types.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Function object ``obj`` -- a struct with a ``return_type operator()( ... )`` member defined on them, like all C++ lambdas -- are not interpreted as functions when you use ``set`` for ``mytable.set( key, value )``. This only happens automagically with ``mytable[key] = obj``. To be explicit about wanting a struct to be interpreted as a function, use ``mytable.set_function( key, func_value );``.
|
||||||
|
|
||||||
|
|
||||||
|
Getting a function from Lua
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
There are 2 ways to get a function from Lua. One is with :doc:`sol::function<../api/function>` and the other is a more advanced wrapper with :doc:`sol::protected_function<../api/protected_function>`. Use them to retrieve callables from Lua and call the underlying function, in two ways:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
:linenos:
|
||||||
|
:caption: Retrieving a sol::function
|
||||||
|
:name: reading-functions
|
||||||
|
|
||||||
|
int main () {
|
||||||
|
|
||||||
|
sol::state lua;
|
||||||
|
|
||||||
|
lua.script(R"(
|
||||||
|
function f (a)
|
||||||
|
return a + 5
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
// Get and immediately call
|
||||||
|
int x = lua["f"](30);
|
||||||
|
// x == 35
|
||||||
|
|
||||||
|
// Store it into a variable first, then call
|
||||||
|
sol::function f = lua["f"];
|
||||||
|
int y = f(20);
|
||||||
|
// y == 25
|
||||||
|
}
|
||||||
|
|
||||||
|
You can get anything that's a callable in Lua, including C++ functions you bind using ``set_function`` or similar. ``sol::protected_function`` behaves similarly to ``sol::function``, but has a :ref:`error_handler<protected-function-error-handler>` variable you can set to a Lua function. This catches all errors and runs them through the error-handling function:
|
||||||
|
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
:linenos:
|
||||||
|
:caption: Retrieving a sol::protected_function
|
||||||
|
:name: reading-protected-functions
|
||||||
|
|
||||||
|
int main () {
|
||||||
|
sol::state lua;
|
||||||
|
|
||||||
|
lua.script(R"(
|
||||||
|
function handler (message)
|
||||||
|
return "Handled this message: " .. message
|
||||||
|
end
|
||||||
|
|
||||||
|
function f (a)
|
||||||
|
if a < 0 then
|
||||||
|
error("negative number detected")
|
||||||
|
return a + 5
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
sol::protected_function f = lua["f"];
|
||||||
|
f.error_handler = lua["handler"];
|
||||||
|
|
||||||
|
sol::protected_function_result result = f(-500);
|
||||||
|
if (result.valid()) {
|
||||||
|
// Call succeeded
|
||||||
|
int x = r1esult;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Call failed
|
||||||
|
sol::error err = result;
|
||||||
|
std::string what = err.what();
|
||||||
|
// 'what' Should read
|
||||||
|
// "Handled this message: negative number detected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Multiple returns to and from Lua
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
You can return multiple items to and from Lua using ``std::tuple``/``std::pair`` classes provided by C++. These enable you to also use :doc:`sol::tie<../api/tie>` to set return values into pre-declared items. To recieve multiple returns, just ask for a ``std::tuple`` type from the result of a function's computation, or ``sol::tie`` a bunch of pre-declared variables together and set the result equal to that:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
:linenos:
|
||||||
|
:caption: Multiple returns from Lua
|
||||||
|
:name: multi-return-lua-functions
|
||||||
|
|
||||||
|
int main () {
|
||||||
|
sol::state lua;
|
||||||
|
|
||||||
|
lua.script("function f (a, b, c) return a, b, c end");
|
||||||
|
|
||||||
|
std::tuple<int, int, int> result;
|
||||||
|
result = lua["f"](1, 2, 3);
|
||||||
|
// result == { 1, 2, 3 }
|
||||||
|
int a, int b;
|
||||||
|
std::string c;
|
||||||
|
sol::tie( a, b, c ) = lua["f"](1, 2, "bark");
|
||||||
|
// a == 1
|
||||||
|
// b == 2
|
||||||
|
// c == "bark"
|
||||||
|
}
|
||||||
|
|
||||||
|
You can also return mutiple items yourself from a C++-bound function. Here, we're going to bind a C++ lambda into Lua, and then call it through Lua and get a ``std::tuple`` out on the other side:
|
||||||
|
|
||||||
|
.. code-block:: cpp
|
||||||
|
:linenos:
|
||||||
|
:caption: Multiple returns into Lua
|
||||||
|
:name: multi-return-cxx-functions
|
||||||
|
|
||||||
|
sol::state lua;
|
||||||
|
|
||||||
|
lua["f"] = [](int a, int b, sol::object c) {
|
||||||
|
// sol::object can be anything here: just pass it through
|
||||||
|
return std::make_tuple( a, b, c );
|
||||||
|
};
|
||||||
|
|
||||||
|
std::tuple<int, int, int> result = lua["f"](1, 2, 3);
|
||||||
|
// result == { 1, 2, 3 }
|
||||||
|
|
||||||
|
std::tuple<int, int, std::string> result2;
|
||||||
|
result2 = lua["f"](1, 2, "Arf?")
|
||||||
|
// result2 == { 1, 2, "Arf?" }
|
||||||
|
|
||||||
|
int a, int b;
|
||||||
|
std::string c;
|
||||||
|
sol::tie( a, b, c ) = lua["f"](1, 2, "meow");
|
||||||
|
// a == 1
|
||||||
|
// b == 2
|
||||||
|
// c == "meow"
|
||||||
|
|
||||||
|
|
||||||
|
Note here that we use :doc:`sol::object<../api/object>` to transport through "any value" that can come from Lua. You can also use ``sol::make_object`` to create an object from some value, so that it can be returned into Lua as well.
|
||||||
|
|
||||||
|
|
||||||
|
This covers almost everything you need to know about Functions and how they interact with Sol. For some advanced tricks and neat things, check out :doc:`sol::this_state<../api/this_state>` and :doc:`sol::variadic_args<../api/variadic_args>`. The last stop in this tutorial is about :doc:`C++ types (usertypes) in Lua<cxx-in-lua>`!
|
Loading…
Reference in New Issue
Block a user