From 5460f7e626e4c54266d6d2a7e7ab73cb6354f496 Mon Sep 17 00:00:00 2001 From: ThePhD Date: Wed, 10 Aug 2016 20:39:30 -0400 Subject: [PATCH] update examples, support inherited usertype metatables, and beef up wording and fix other crap in docs Addresses #157 temporarily --- bootstrap.py | 38 +++++--- docs/source/api/var.rst | 6 +- docs/source/api/variadic_args.rst | 61 +++++++------ docs/source/tutorial/customization.rst | 2 +- docs/source/tutorial/cxx-in-lua.rst | 62 +++++++------ docs/source/tutorial/functions.rst | 67 +++++++------- examples/any_return.cpp | 41 +++++++++ examples/basic.cpp | 7 +- examples/config.cpp | 14 ++- examples/customization.cpp | 80 +++++++++++++++++ examples/functions.cpp | 20 +++-- examples/player_script.lua | 29 ++++++ examples/protected_functions.cpp | 47 ++++++++++ examples/static_variables.cpp | 54 +++++++++++ examples/tables.cpp | 4 + examples/usertype.cpp | 5 ++ examples/usertype_advanced.cpp | 119 +++++++++++++++++++++++++ examples/variables.cpp | 3 + examples/variadic_args.cpp | 37 ++++++++ test_usertypes.cpp | 53 +++++++++++ 20 files changed, 637 insertions(+), 112 deletions(-) create mode 100644 examples/any_return.cpp create mode 100644 examples/customization.cpp create mode 100644 examples/player_script.lua create mode 100644 examples/protected_functions.cpp create mode 100644 examples/static_variables.cpp create mode 100644 examples/usertype_advanced.cpp create mode 100644 examples/variadic_args.cpp diff --git a/bootstrap.py b/bootstrap.py index c2f4ebe3..7bb9304a 100755 --- a/bootstrap.py +++ b/bootstrap.py @@ -132,6 +132,24 @@ if 'win32' in sys.platform: else: tests = os.path.join(builddir, 'tests') +tests_inputs = [] +tests_object_files = [] +for f in glob.glob('test*.cpp'): + obj = object_file(f) + tests_inputs.append(f) + tests_object_files.append(obj) + +examples = [] +examples_input = [] +for f in glob.glob('examples/*.cpp'): + if 'win32' in sys.platform: + example = os.path.join(builddir, replace_extension(f, '.exe')) + else: + example = os.path.join(builddir, replace_extension(f, '')) + examples_input.append(f) + examples.append(example) + + # ninja file ninja = ninja_syntax.Writer(open('build.ninja', 'w')) @@ -149,7 +167,8 @@ ninja.rule('compile', command = '$cxx -MMD -MF $out.d -c $cxxflags -Werror $in - deps = 'gcc', depfile = '$out.d', description = 'Compiling $in to $out') ninja.rule('link', command = '$cxx $cxxflags $in -o $out $ldflags', description = 'Creating $out') -ninja.rule('runner', command = tests) +ninja.rule('tests_runner', command = tests) +ninja.rule('examples_runner', command = 'cmd /c ' + (' && '.join(examples)) if 'win32' in sys.platform else ' && '.join(examples) ) ninja.rule('example', command = '$cxx $cxxflags $in -o $out $ldflags') ninja.rule('installer', command = copy_command) ninja.rule('uninstaller', command = remove_command) @@ -158,22 +177,17 @@ ninja.newline() # builds ninja.build('build.ninja', 'bootstrap', implicit = sys.argv[0]) -tests_object_files = [] -for f in glob.glob('test*.cpp'): - obj = object_file(f) - tests_object_files.append(obj) +for obj, f in zip(tests_object_files, tests_inputs): ninja.build(obj, 'compile', inputs = f) -examples = [] -for f in glob.glob('examples/*.cpp'): - example = os.path.join(builddir, replace_extension(f, '')) - examples.append(example) +for example, f in zip(examples, examples_input): ninja.build(example, 'example', inputs = f) ninja.build(tests, 'link', inputs = tests_object_files) ninja.build('tests', 'phony', inputs = tests) +ninja.build('examples', 'phony', inputs = examples) ninja.build('install', 'installer', inputs = args.install_dir) ninja.build('uninstall', 'uninstaller') -ninja.build('examples', 'phony', inputs = examples) -ninja.build('run', 'runner', implicit = 'tests') -ninja.default('run') +ninja.build('run', 'tests_runner', implicit = 'tests') +ninja.build('run_examples', 'examples_runner', implicit = 'examples') +ninja.default('run run_examples') diff --git a/docs/source/api/var.rst b/docs/source/api/var.rst index 10956522..9e9bcec5 100644 --- a/docs/source/api/var.rst +++ b/docs/source/api/var.rst @@ -7,6 +7,8 @@ The sole purpose of this tagging type is to work with :doc:`usertypes` .. code-block:: cpp + #include + struct test { static int muh_variable; }; @@ -19,10 +21,10 @@ The sole purpose of this tagging type is to work with :doc:`usertypes` lua.new_usertype("test", "direct", sol::var(2), "global", sol::var(test::muh_variable), - "ref_global", sol::var(std::ref(test::muh_variable)), + "ref_global", sol::var(std::ref(test::muh_variable)) ); - int direct_value = lua["test"]["straight"]; + int direct_value = lua["test"]["direct"]; // direct_value == 2 int global = lua["test"]["global"]; diff --git a/docs/source/api/variadic_args.rst b/docs/source/api/variadic_args.rst index 7982f2ec..5d3e0c10 100644 --- a/docs/source/api/variadic_args.rst +++ b/docs/source/api/variadic_args.rst @@ -8,37 +8,42 @@ transparent argument to deal with multiple parameters to a function struct variadic_args; -This class is meant to represent every single argument at its current index and beyond in a function list. It does not increment the argument count and is thus transparent. You can place it anyway in the argument list, and it will represent all of the objects in a function call that come after it, whether they are listed explicitly or not. +This class is meant to represent every single argument at its current index and beyond in a function list. It does not increment the argument count and is thus transparent. You can place it anywhere in the argument list, and it will represent all of the objects in a function call that come after it, whether they are listed explicitly or not. ``variadic_args`` also has ``begin()`` and ``end()`` functions that return (almost) random-acess iterators. These return a proxy type that can be implicitly converted, much like the :doc:`table proxy type`. .. code-block:: cpp :linenos: - sol::state lua; - - // Function requires 2 arguments - // rest can be variadic, but: - // va will include everything after "a" argument, - // which means "b" will be part of the varaidic_args list too - // at position 0 - lua.set_function("v", [](sol::this_state, int a, sol::variadic_args va, int b) { - int r = 0; - for (auto v : va) { - int value = v; // get argument out (implicit conversion) - // can also do int v = va.get(i); with index i - r += value; - } - // Only have to add a, b was included - return r + a; - }); - - lua.script("x = v(25, 25)"); - lua.script("x2 = v(25, 25, 100, 50, 250, 150)"); - lua.script("x3 = v(1, 2, 3, 4, 5, 6)"); - // will error: not enough arguments - //lua.script("x4 = v(1)"); - - lua.script("print(x)"); // 50 - lua.script("print(x2)"); // 600 - lua.script("print(x3)"); // 21 + #include + + int main () { + + sol::state lua; + + // Function requires 2 arguments + // rest can be variadic, but: + // va will include everything after "a" argument, + // which means "b" will be part of the varaidic_args list too + // at position 0 + lua.set_function("v", [](int a, sol::variadic_args va, int b) { + int r = 0; + for (auto v : va) { + int value = v; // get argument out (implicit conversion) + // can also do int v = va.get(i); with index i + r += value; + } + // Only have to add a, b was included + return r + a; + }); + + lua.script("x = v(25, 25)"); + lua.script("x2 = v(25, 25, 100, 50, 250, 150)"); + lua.script("x3 = v(1, 2, 3, 4, 5, 6)"); + // will error: not enough arguments + //lua.script("x4 = v(1)"); + + lua.script("print(x)"); // 50 + lua.script("print(x2)"); // 600 + lua.script("print(x3)"); // 21 + } diff --git a/docs/source/tutorial/customization.rst b/docs/source/tutorial/customization.rst index 7f15cef6..032e4d60 100644 --- a/docs/source/tutorial/customization.rst +++ b/docs/source/tutorial/customization.rst @@ -77,7 +77,7 @@ This is the base formula that you can follow to extend to your own classes. Usin #include #include - int main (int argc, char* argv[]) { + int main () { sol::state lua; diff --git a/docs/source/tutorial/cxx-in-lua.rst b/docs/source/tutorial/cxx-in-lua.rst index 9ce6123b..be64e102 100644 --- a/docs/source/tutorial/cxx-in-lua.rst +++ b/docs/source/tutorial/cxx-in-lua.rst @@ -39,7 +39,7 @@ Take this ``player`` struct in C++ in a header file: return true; } - int set_hp(int value) { + void set_hp(int value) { hp = value; } @@ -49,7 +49,7 @@ Take this ``player`` struct in C++ in a header file: private: int hp; - } + }; It's a fairly minimal class, but we don't want to have to rewrite this with metatables in Lua. We want this to be part of Lua easily. The following is the Lua code that we'd like to have work properly: @@ -92,37 +92,41 @@ To do this, you bind things using the ``new_usertype`` and ``set_usertype`` meth .. code-block:: cpp :caption: player_script.cpp - sol::state lua; + #include - // note that you can set a - // userdata before you register a usertype, - // and it will still carry - // the right metatable if you register it later - - // set a variable "p2" of type "player" with 0 ammo - lua["p2"] = player(0); + int main () { + sol::state lua; - // make usertype metatable - lua.new_usertype( "player", + // note that you can set a + // userdata before you register a usertype, + // and it will still carry + // the right metatable if you register it later - // 3 constructors - sol::constructors, sol::types, sol::types>(), - - // typical member function that returns a variable - "shoot", &player::shoot, - // typical member function - "boost", &player::boost, - - // gets or set the value using member variable syntax - "hp", sol::property(&player::get_hp, &player::set_hp), - - // read and write variable - "speed", &player::speed, - // can only read from, not write to - "bullets", sol::readonly( &player::bullets ) - ); + // set a variable "p2" of type "player" with 0 ammo + lua["p2"] = player(0); - lua.script_file("player_script.lua"); + // make usertype metatable + lua.new_usertype( "player", + + // 3 constructors + sol::constructors, sol::types, sol::types>(), + + // typical member function that returns a variable + "shoot", &player::shoot, + // typical member function + "boost", &player::boost, + + // gets or set the value using member variable syntax + "hp", sol::property(&player::get_hp, &player::set_hp), + + // read and write variable + "speed", &player::speed, + // can only read from, not write to + "bullets", sol::readonly( &player::bullets ) + ); + + lua.script_file("player_script.lua"); + } That script should run fine now, and you can observe and play around with the values. Even more stuff :doc:`you can do<../api/usertype>` is described elsewhere, like initializer functions (private constructors / destructors support), "static" functions callable with ``name.my_function( ... )``, and overloaded member functions. You can even bind global variables (even by reference with ``std::ref``) with ``sol::var``. There's a lot to try out! diff --git a/docs/source/tutorial/functions.rst b/docs/source/tutorial/functions.rst index 6635a050..2b2f0e3b 100644 --- a/docs/source/tutorial/functions.rst +++ b/docs/source/tutorial/functions.rst @@ -214,6 +214,7 @@ You can get anything that's a callable in Lua, including C++ functions you bind function f (a) if a < 0 then error("negative number detected") + end return a + 5 end )"); @@ -224,7 +225,7 @@ You can get anything that's a callable in Lua, including C++ functions you bind sol::protected_function_result result = f(-500); if (result.valid()) { // Call succeeded - int x = r1esult; + int x = result; } else { // Call failed @@ -269,26 +270,28 @@ You can also return mutiple items yourself from a C++-bound function. Here, we'r :caption: Multiple returns into Lua :name: multi-return-cxx-functions - sol::state lua; + int main () { + 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 result = lua["f"](1, 2, 3); - // result == { 1, 2, 3 } - - std::tuple result2; - result2 = lua["f"](1, 2, "Arf?") - // result2 == { 1, 2, "Arf?" } + 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 result = lua["f"](1, 2, 3); + // result == { 1, 2, 3 } + + std::tuple 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" + 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. @@ -307,7 +310,7 @@ It can be used like so, inconjunction with ``sol::this_state``: :name: object-return-cxx-functions sol::object fancy_func (sol::object a, sol::object b, sol::this_state s) { - sol::state_view lua = s; + sol::state_view lua(s); if (a.is() && b.is()) { return sol::make_object(lua, a.as() + b.as()); } @@ -318,19 +321,21 @@ It can be used like so, inconjunction with ``sol::this_state``: return sol::make_object(lua, sol::nil); } - sol::state lua; + int main () { + sol::state lua; - lua["f"] = fancy_func; - - int result = lua["f"](1, 2); - // result == 3 - double result2 = lua["f"](false, 2.5); - // result2 == 2.5 + lua["f"] = fancy_func; + + int result = lua["f"](1, 2); + // result == 3 + double result2 = lua["f"](false, 2.5); + // result2 == 2.5 - // call in Lua, get result - lua.script("result3 = f(true, 5.5)"); - double result3 = lua["result3"]; - // result3 == 16.5 + // call in Lua, get result + lua.script("result3 = f(true, 5.5)"); + double result3 = lua["result3"]; + // result3 == 16.5 + } 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 next stop in this tutorial is about :doc:`C++ types (usertypes) in Lua`! \ No newline at end of file diff --git a/examples/any_return.cpp b/examples/any_return.cpp new file mode 100644 index 00000000..a5aa97e6 --- /dev/null +++ b/examples/any_return.cpp @@ -0,0 +1,41 @@ +#include + +#include + +// Uses some of the fancier bits of sol2, including the "transparent argument", +// sol::this_state, which gets the current state and does not increment +// function arguments +sol::object fancy_func(sol::object a, sol::object b, sol::this_state s) { + sol::state_view lua(s); + if (a.is() && b.is()) { + return sol::make_object(lua, a.as() + b.as()); + } + else if (a.is()) { + bool do_triple = a.as(); + return sol::make_object(lua, b.as() * (do_triple ? 3 : 1)); + } + return sol::make_object(lua, sol::nil); +} + +int main() { + sol::state lua; + + lua["f"] = fancy_func; + + int result = lua["f"](1, 2); + // result == 3 + double result2 = lua["f"](false, 2.5); + // result2 == 2.5 + + // call in Lua, get result + // notice we only need 2 arguments here, not 3 (sol::this_state is transparent) + lua.script("result3 = f(true, 5.5)"); + double result3 = lua["result3"]; + // result3 == 16.5 + + std::cout << "=== any_return example ===" << std::endl; + std::cout << "result : " << result << std::endl; + std::cout << "result2: " << result2 << std::endl; + std::cout << "result3: " << result3 << std::endl; + std::cout << std::endl; +} \ No newline at end of file diff --git a/examples/basic.cpp b/examples/basic.cpp index 4852aa8e..8d9d62c3 100644 --- a/examples/basic.cpp +++ b/examples/basic.cpp @@ -1,5 +1,7 @@ #include +#include + int main() { // create an empty lua state sol::state lua; @@ -10,5 +12,8 @@ int main() { lua.open_libraries(sol::lib::base); // call lua code directly - lua.script("print('hello world')"); + std::cout << "=== basic example ===" << std::endl; + lua.script("print('hello world')"); + + std::cout << std::endl; } \ No newline at end of file diff --git a/examples/config.cpp b/examples/config.cpp index 6b9929ac..70734332 100644 --- a/examples/config.cpp +++ b/examples/config.cpp @@ -18,10 +18,18 @@ struct config { int main() { sol::state lua; - config screen; - lua.script_file("config.lua"); + config screen; + // To use the file, uncomment here and make sure it is in local dir + //lua.script_file("config.lua"); + lua.script(R"( +name = "Asus" +width = 1920 +height = 1080 +)"); screen.name = lua.get("name"); screen.width = lua.get("width"); screen.height = lua.get("height"); - screen.print(); + std::cout << "=== config example ===" << std::endl; + screen.print(); + std::cout << std::endl; } diff --git a/examples/customization.cpp b/examples/customization.cpp new file mode 100644 index 00000000..19b143bd --- /dev/null +++ b/examples/customization.cpp @@ -0,0 +1,80 @@ +#include + +#include +#include + +struct two_things { + int a; + bool b; +}; + +namespace sol { + + // First, the expected size + // Specialization of a struct + template <> + struct lua_size : std::integral_constant {}; + + // Now, specialize various stack structures + namespace stack { + + template <> + struct checker { + template + static bool check(lua_State* L, int index, Handler&& handler, record& tracking) { + // Check first and second second index for being the proper types + bool success = stack::check(L, index, handler) + && stack::check(L, index + 1, handler); + tracking.use(2); + return success; + } + }; + + template <> + struct getter { + static two_things get(lua_State* L, int index, record& tracking) { + // Get the first element + int a = stack::get(L, index); + // Get the second element, + // in the +1 position from the first + bool b = stack::get(L, index + 1); + // we use 2 slots, each of the previous takes 1 + tracking.use(2); + return two_things{ a, b }; + } + }; + + template <> + struct pusher { + static int push(lua_State* L, const two_things& things) { + int amount = stack::push(L, things.a); + amount += stack::push(L, things.b); + // Return 2 things + return amount; + } + }; + + } +} + +int main() { + sol::state lua; + + // Create a pass-through style of function + lua.script("function f ( a, b ) return a, b end"); + + // get the function out of Lua + sol::function f = lua["f"]; + + two_things things = f(two_things{ 24, true }); + // things.a == 24 + // things.b == true + + std::cout << "=== customization example ===" << std::endl; + std::cout << std::boolalpha; + std::cout << "things.a: " << things.a << std::endl; + std::cout << "things.b: " << things.b << std::endl; + std::cout << std::endl; + + return 0; +} diff --git a/examples/functions.cpp b/examples/functions.cpp index d06a92f6..129671b2 100644 --- a/examples/functions.cpp +++ b/examples/functions.cpp @@ -21,6 +21,8 @@ inline std::string make_string( std::string input ) { } int main() { + std::cout << "=== functions example ===" << std::endl; + sol::state lua; lua.open_libraries(sol::lib::base); @@ -40,7 +42,7 @@ int main() { lua.script("assert(mult_by_ten(50) == 500)"); lua.script("assert(mult_by_five(10) == 50)"); - // using lambdas, functions could have state. + // using lambdas, functions can have state. int x = 0; lua.set_function("inc", [&x]() { x += 10; }); @@ -53,7 +55,11 @@ int main() { } // this can be done as many times as you want - lua.script("inc()\ninc()\ninc()"); + lua.script(R"( +inc() +inc() +inc() +)"); assert(x == 40); if (x == 40) { // Do something based on this information @@ -63,9 +69,11 @@ int main() { // to other variables, using sol::function sol::function add = lua["my_add"]; int value = add(10, 11); - assert(add.call(10, 11) == 21); + // second way to call the function + int value2 = add.call(10, 11); assert(value == 21); - if (value == 21) { + assert(value2 == 21); + if (value == 21 && value2 == 21) { std::cout << "Woo, it's 21!" << std::endl; } @@ -78,7 +86,7 @@ int main() { auto multi = lua.get("multi"); int first; std::string second; - std::tie(first, second) = multi.call(); + sol::tie(first, second) = multi(); // use the values assert(first == 10); @@ -97,4 +105,6 @@ print(func(1)) print(func("bark")) print(func(1,2)) )"); + + std::cout << std::endl; } \ No newline at end of file diff --git a/examples/player_script.lua b/examples/player_script.lua new file mode 100644 index 00000000..432983e0 --- /dev/null +++ b/examples/player_script.lua @@ -0,0 +1,29 @@ +-- call single argument integer constructor +p1 = player.new(2) + +-- p2 is still here from being +-- set with lua["p2"] = player(0); below +local p2shoots = p2:shoot() +assert(not p2shoots) +-- had 0 ammo + +-- set variable property setter +p1.hp = 545; +-- get variable through property getter +print(p1.hp); + +local did_shoot_1 = p1:shoot() +print(did_shoot_1) +print(p1.bullets) +local did_shoot_2 = p1:shoot() +print(did_shoot_2) +print(p1.bullets) +local did_shoot_3 = p1:shoot() +print(did_shoot_3) + +-- can read +print(p1.bullets) +-- would error: is a readonly variable, cannot write +-- p1.bullets = 20 + +p1:boost() \ No newline at end of file diff --git a/examples/protected_functions.cpp b/examples/protected_functions.cpp new file mode 100644 index 00000000..a60e3826 --- /dev/null +++ b/examples/protected_functions.cpp @@ -0,0 +1,47 @@ +#include + +#include + +int main() { + std::cout << "=== protected_functions example ===" << std::endl; + + sol::state lua; + + // A complicated function which can error out + // We define both in terms of Lua code + + lua.script(R"( + function handler (message) + return "Handled this message: " .. message + end + + function f (a) + if a < 0 then + error("negative number detected") + end + return a + 5 + end + )"); + + // Get a protected function out of Lua + sol::protected_function f = lua["f"]; + // Set a non-default error handler + f.error_handler = lua["handler"]; + + sol::protected_function_result result = f(-500); + if (result.valid()) { + // Call succeeded + int x = result; + std::cout << x << std::endl; + } + else { + // Call failed + sol::error err = result; + std::string what = err.what(); + std::cout << what << std::endl; + // 'what' Should read + // "Handled this message: negative number detected" + } + + std::cout << std::endl; +} diff --git a/examples/static_variables.cpp b/examples/static_variables.cpp new file mode 100644 index 00000000..2b8d00d2 --- /dev/null +++ b/examples/static_variables.cpp @@ -0,0 +1,54 @@ +#include + +#include + +struct test { + static int muh_variable; +}; +int test::muh_variable = 25; + + +int main() { + std::cout << "=== static_variables example ===" << std::endl; + + sol::state lua; + lua.open_libraries(); + lua.new_usertype("test", + "direct", sol::var(2), + "global", sol::var(test::muh_variable), + "ref_global", sol::var(std::ref(test::muh_variable)) + ); + + int direct_value = lua["test"]["direct"]; + std::cout << "direct_value: " << direct_value << std::endl; + // direct_value == 2 + + int global = lua["test"]["global"]; + // global == 25 + int global2 = lua["test"]["ref_global"]; + // global2 == 25 + + std::cout << "First round of values --" << std::endl; + std::cout << global << std::endl; + std::cout << global2 << std::endl; + + test::muh_variable = 542; + + global = lua["test"]["global"]; + // global == 25 + // global is its own memory: was passed by value + + global2 = lua["test"]["ref_global"]; + // global2 == 542 + // global2 was passed through std::ref + // global2 holds a reference to muh_variable + // if muh_variable goes out of scope or is deleted + // problems could arise, so be careful! + + std::cout << "Second round of values --" << std::endl; + std::cout << "global : " << global << std::endl; + std::cout << "global2: " << global2 << std::endl; + std::cout << std::endl; + + return 0; +} diff --git a/examples/tables.cpp b/examples/tables.cpp index 2f9b6080..41798d3b 100644 --- a/examples/tables.cpp +++ b/examples/tables.cpp @@ -5,6 +5,8 @@ // this example shows how to read data in from a lua table int main() { + std::cout << "=== tables example ===" << std::endl; + sol::state lua; // table used as an array lua.script("table1 = {\"hello\", \"table\"}"); @@ -52,4 +54,6 @@ int main() { << lua.get("table2").get("nestedTable").get("key2") << '\n'; std::cout << "name of t2: " << t2.get("name") << '\n'; + + std::cout << std::endl; } diff --git a/examples/usertype.cpp b/examples/usertype.cpp index e5f934c1..b29f9e8a 100644 --- a/examples/usertype.cpp +++ b/examples/usertype.cpp @@ -39,6 +39,8 @@ struct variables { }; int main() { + std::cout << "=== usertype example ===" << std::endl; + sol::state lua; lua.open_libraries(sol::lib::base, sol::lib::math); @@ -101,4 +103,7 @@ int main() { "vars.low_gravity = true\n" "local x = vars.low_gravity\n" "assert(x)"); + + std::cout << std::endl; + } diff --git a/examples/usertype_advanced.cpp b/examples/usertype_advanced.cpp new file mode 100644 index 00000000..0e358acb --- /dev/null +++ b/examples/usertype_advanced.cpp @@ -0,0 +1,119 @@ +#include + +#include + +struct player { +public: + int bullets; + int speed; + + player() + : player(3, 100) { + + } + + player(int ammo) + : player(ammo, 100) { + + } + + player(int ammo, int hitpoints) + : bullets(ammo), hp(hitpoints) { + + } + + void boost() { + speed += 10; + } + + bool shoot() { + if (bullets < 1) + return false; + --bullets; + return true; + } + + void set_hp(int value) { + hp = value; + } + + int get_hp() const { + return hp; + } + +private: + int hp; +}; + +int main() { + sol::state lua; + + lua.open_libraries(sol::lib::base); + + // note that you can set a + // userdata before you register a usertype, + // and it will still carry + // the right metatable if you register it later + + // set a variable "p2" of type "player" with 0 ammo + lua["p2"] = player(0); + + // make usertype metatable + lua.new_usertype("player", + + // 3 constructors + sol::constructors, sol::types, sol::types>(), + + // typical member function that returns a variable + "shoot", &player::shoot, + // typical member function + "boost", &player::boost, + + // gets or set the value using member variable syntax + "hp", sol::property(&player::get_hp, &player::set_hp), + + // read and write variable + "speed", &player::speed, + // can only read from, not write to + "bullets", sol::readonly(&player::bullets) + ); + + std::string player_script = R"( +-- call single argument integer constructor +p1 = player.new(2) + +-- p2 is still here from being +-- set with lua["p2"] = player(0); below +local p2shoots = p2:shoot() +assert(not p2shoots) +-- had 0 ammo + +-- set variable property setter +p1.hp = 545; +-- get variable through property getter +print(p1.hp); + +local did_shoot_1 = p1:shoot() +print(did_shoot_1) +print(p1.bullets) +local did_shoot_2 = p1:shoot() +print(did_shoot_2) +print(p1.bullets) +local did_shoot_3 = p1:shoot() +print(did_shoot_3) + +-- can read +print(p1.bullets) +-- would error: is a readonly variable, cannot write +-- p1.bullets = 20 + +p1:boost() +)"; + + // Uncomment and use the file to try that out, too! + // Make sure it's in the local directory of the executable after you build, or adjust the filename path + // Or whatever else you like! + //lua.script_file("player_script.lua"); + lua.script(player_script); + std::cout << std::endl; +} diff --git a/examples/variables.cpp b/examples/variables.cpp index 38af0d39..ec20921b 100644 --- a/examples/variables.cpp +++ b/examples/variables.cpp @@ -2,6 +2,8 @@ #include int main() { + std::cout << "=== variables example ===" << std::endl; + sol::state lua; // need the base library for assertions @@ -31,4 +33,5 @@ int main() { std::cout << y << std::endl; std::cout << x2 << std::endl; std::cout << y2 << std::endl; + std::cout << std::endl; } \ No newline at end of file diff --git a/examples/variadic_args.cpp b/examples/variadic_args.cpp new file mode 100644 index 00000000..93a575f5 --- /dev/null +++ b/examples/variadic_args.cpp @@ -0,0 +1,37 @@ +#include + +#include + +int main() { + std::cout << "=== variadic_args example ===" << std::endl; + + sol::state lua; + lua.open_libraries(sol::lib::base); + + // Function requires 2 arguments + // rest can be variadic, but: + // va will include everything after "a" argument, + // which means "b" will be part of the varaidic_args list too + // at position 0 + lua.set_function("v", [](int a, sol::variadic_args va, int /*b*/) { + int r = 0; + for (auto v : va) { + int value = v; // get argument out (implicit conversion) + // can also do int v = va.get(i); with index i + r += value; + } + // Only have to add a, b was included from variadic_args and beyond + return r + a; + }); + + lua.script("x = v(25, 25)"); + lua.script("x2 = v(25, 25, 100, 50, 250, 150)"); + lua.script("x3 = v(1, 2, 3, 4, 5, 6)"); + // will error: not enough arguments + //lua.script("x4 = v(1)"); + + lua.script("print(x)"); // 50 + lua.script("print(x2)"); // 600 + lua.script("print(x3)"); // 21 + std::cout << std::endl; +} \ No newline at end of file diff --git a/test_usertypes.cpp b/test_usertypes.cpp index a7e783c8..771d7cf8 100644 --- a/test_usertypes.cpp +++ b/test_usertypes.cpp @@ -1340,3 +1340,56 @@ t.global = 20 print(t.global) )")); } + +TEST_CASE("usertype/inheritance", "test that metatables are properly inherited") { + struct A { + int a = 5; + }; + + struct B { + int b() { + return 10; + } + }; + + struct C : B, A { + double c = 2.4; + }; + + struct D : C { + bool d() const { + return true; + } + }; + + sol::state lua; + lua.new_usertype("A", + "a", &A::a + ); + lua.new_usertype("B", + "b", &B::b + ); + lua.new_usertype("C", + "c", &C::c, + sol::base_classes, sol::bases() + ); + lua.new_usertype("D", + "d", &D::d, + sol::base_classes, sol::bases() + ); + + lua.script("obj = D.new()"); + lua.script("d = obj:d()"); + bool d = lua["d"]; + lua.script("c = obj.c"); + double c = lua["c"]; + lua.script("b = obj:b()"); + int b = lua["b"]; + lua.script("a = obj.a"); + int a = lua["a"]; + + REQUIRE(d); + REQUIRE(c == 2.4); + REQUIRE(b == 10); + REQUIRE(a == 5); +}