New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Updating the C++ to Lua interface [CR] #12451
Conversation
Generated code looked like: it_tool* rval = (it_tool*) get_tool_type(parameter1); The cast is clearly not needed, if it is ever needed, the (C++) function should be change to verify the result and use the proper cast.
in the Lua wrappers form them. This is generic and save as it automatically avoids copying when the result is a references and forwards that reference.
Instead let the caller declare the variable via a perfect forwarding reference: auto && parameter1 = <generated>;
The caller pretty much depends upon the generated instance, and this way the script will announce if some type is not properly wrapped.
It handles binary strings with embedded \0 and nullptr.
The result of lua_touserdata must be cast to T**, but the code looked like it would only cast to T* - very confusing. Note that this does not change the result. The * has simply moved from member_type_to_cpp_type into the only function that used it (retrieve_lua_value) and the local variable that stored the result of that function call has been removed, the result is used directly.
The code is essentially the same as it was in the bindings generator, but it's now in a cpp file, the bindings generator only calls a functions (just one line), the remaining stuff is done completely in C++. This should make it easier to read.
Output from LuaValue is now a references to the pointer. This is a pretty much pointless change because the caller never assigns to that value. But it allows us to get rid of one level of de-referencing in the generator.
Currently it's exactly the same as the base LuaValue, the only difference is it automatically makes the template parameter a pointer. So instead of LuaValue<T*> one can now write: LuaReference<T>
This allows for values created and managed solely by Lua. For a proof of concept, it adds a wrapper for player::pos() so one can get a *copy* of the value. The Lua function testvalues can be use to see the value semantic. Calling player:pos() does *not* store a reference to the C++ object player::position, but a copy of it. The nullptr check is now in LuaReference because LuaValue can be used with non-pointers, comparing those to nullptr won't work. Note: the __gc entry in the metatable is not required for trivially destructed objects (like POD types). Also, this now uses the placement operator new to construct the object instead of assignment to uninitialized memory. For now, the bindings generator simply has several special cases that get a value from Lua (LuaValue returns a reference, but the generated code converts it into a pointer, so it's compatible with the value from LuaReference).
LuaReference now returns a reference when queried for the data from Lua. It can still be used to store a pointer (which will still be stored as pointer), but the main interface also understands references (they are converted to pointers internally). Main enhancement is in the generated bindings: they store the references as proper references, not as pointers (in temporary variables obtained via auto&&). The wrapper classes have a new function get_proxy which returns a proxy object or (in the case of non-pointers) a reference to the value as only that is required. This generates code like: item_instance.type = LuaReference<itype>::get_proxy(L, 2); The code does the correct think if whether `item_instance.type` is a pointer or a reference value. Similar for functions: auto && parameter1 = LuaReference<item>::get_proxy(L, 3); game_remove_item(parameter1); This will work whether game_remove_item requires an item pointer or an item reference. It will automatically be converted to the required type. Note that it is still type safe. The proxy is not convertible to any other thing.
Less redundant clutter in the bindings generator, more safety.
Let member_type_to_cpp_type decide whether to use LuaValue or LuaReference.
This is only done for types that are wrapped via the LuaValue class. - Removes the creation of the metatable in autoexec.lua (as it's done in C++ now). - Removes generating the global functions that were used by the Lua-generated metatable. Instead: - Generate separate containers with wrapper functions to be used in LuaValue: a simple array for normal class functions, two maps that map the member name to the setter/getter function. LuaValue::get_metatable now generates the metatable on its own (or simply returns the existing one). To make every object in Lua consistent, all the push-object-on-stack code uses LuaValue/LuaReference to push values (including setting the metatable). Use the Lua function tripoint_mt_test to test it. The serialize function was added to have a by-value object with a member function (not only member access).
Add a generator for equality function to enable the use of the std::list::iterator. Add a iterator generator in Lua to walk over all items on the map (at a given point). Export further functions from C++ to Lua.
Note about registry: the generic push (without registry) leaves the new value on the stack, that's the place where luah_store_in_registry expect it. In other words, the changed order of function calls (newuserdata, store_in_registry, setmetatable) to newuserdata, setmetatable, store_in_registry is fine.
|
Breaks compatibility with Fast Zombies and similar mods. |
Other than those it looks good: loops, adding/removing items etc. seem to work. There may be some subtle bugs, but probably nothing that would affect everyone. Windows paths don't need to work yet, but Fast Zombies and similar mods have to, because many people have saves with those. |
@kevingranade I'm not sure that using swig or something of the sort would actually improve the situation. I just don't think there's any such framework mature enough that it'd be more effective than doing things "manually". At least all the games I know that use lua don't use swig or similar and I guess there's a reason for that. But maybe using lua is the issue. Not a lot of people know lua, and let's be honest, the language has a terrible syntax. Maybe python or javascript would be more encouraging for people to actually use the scripting language. Modifying the wrapper generator to generate bindings for those languages would probably be the lesser problem. The bigger problem would be that having those languages as build dependencies would be (AFAIK) much more complex. |
Heey, I know Lua 😄 |
This PR will probably get in, the discussion is about making Lua a hard dependency and rewriting a lot of game in it. |
By the way, one thing missing for serious lua modding capability is still creating custom save data. Mods that add new game logic will also need to store data related to that game logic somewhere. |
Thank you. I originally though of some really generic code, the getter and setter would have been heavily templated, it was even more beautiful. But it didn't work out. Never mind.
For now, you have access to @Coolthulhu regarding breaking the Lua interface: the compiler (and Jenkins) will tell you if you do so. Fixing this is usually simple: change the function/member declaration in lua/classdeclarations.lua (if you can't make it work, comment it out by prefixing the line with And I'll fix the conflicts with the existing Lua mods.
Why? That will only introduce bugs. If Lua needs access to that part, add a callback, there is for example already a I suspect, the biggest problem with the current state of the Lua part is the very small subset of functions it supports. Look at the few Part of this is the tendency to simply remove functions from the Lua interface when their C++ version has changed or was removed. That is one of the problems I wanted to address with this PR. There is still the issue with the documentation, but because the Lua functions are mostly 1-to-1 wrappers of the C++ functions, I would forward this issue to the C++/doxygen documentation. |
Fix invalid member "id", "name" by using the proper tname function Export the effect function of the Creature class, but without the body_part parameters for now.
Sorting the member lists fixed the problem with the mods, it now exports the right overload. But the real fix would of course be to support overloading from Lua function calls.
Does the metatable need to exported as a global thing at all? I know it's required for tripoint/point to allow using it as constructor like I changed it to let the generator decide what name they should have (and whether they should be exported globally at all). |
Question for the Lua people: How should the C++ enums be exported to Lua? http://lua-users.org/wiki/BindingEnumsToLua recommends to use string, wouldn't be a problem. mon:add_effect( "sleeping", 100, "bp_leg_r" ) Somebody used to C++ (read: me) could expect it as entry in a global table: mon:add_effect( "sleeping", 100, body_part.bp_leg_r ) Swig agrees: http://www.swig.org/Doc1.3/Lua.html#Lua_nn11 |
The problem is that there likely isn't an advantage to using the global table. There is no static type checking like in C++, so either way you'll only realize that you mis-spelled the identifier when the code is executed. Though your version might be better in the case that the editor supports auto-completion. |
strings would be better, as you'd not need to tie it to a table. it is by glance more similar to C++ via strings. |
I know a little bit of LUA (been a bit, but it'd dust off easy) and my biggest barrier to entry is the tangle of programs and their dependencies that compiling is (the gcc and code::blocks stuff sends you all over, and documentation updating is temporally challenged) so more Lua support would be nice, also LUA is easier to learn than C++ |
You mean But I'll probably use strings in a (future) implementation. The whole point of identifier checking does not happen in Lua. Through once the C++ side is done, one could make a global table |
I probably just don't know what I'm doing. the C++ looks like it is completely unnested with regards to that function, and the lua has the right leg entry under a table. seems like you'd have it leave out the |
|
Updating the C++ to Lua interface [CR]
May have bugs, but doesn't break existing functionality. Windows style file paths not supported by Tested it by spawning a mapful of corpses like: |
Have you tried "C:/what/ever/dude.lua"? Backslashes may need to be escaped in Lua strings. But that is not a problem of Lua or C++ - a path to a file is just a string, the programming language should not care about that. Edit: my falt, I just remembered: |
|
So the Lua part is pretty much dead, isn't it? That's a shame. Is there somebody interested in doing some Lua scripting?
This updates it to work with
tripoint
s andpoint
s. Both get value semantics, their data is stored and managed by Lua. The other exported classes likemonster
,itype
etc. are only exported as references. A Lua script can access them, but can't create new ones on their own. Lua can spawn monsters and get a reference to the newly spawned one, but it can't createitype
instances. Through this is probably not a problem as everyone writing Lua can just as easy add the new object to the json data.A bunch of C++ function are now available by Lua, mostly member functions of the already exported types and wrapper to get/set their members. The whole thing is not const correct in the sightliest bit, one can freely write data to C++ members that should not really be accessible (however, there are no
const_cast
s involved, so it's technically fine).I toke the liberty to remove some function declaration that are actually nowhere defined. Some of the code that was generated by the Lua script is now stuffed into a class (accessed via static functions). It's a bit more type safe and way easier to read than C++ code in a Lua string.
What you can do (enter the commands in the Lua "console" from the debug menu):
use_action
"CUSTOM_IUSE_TEST", spawn it and apply it. The Lua part should show a popup saying where the item is (tripoint), what item it is and whether it's active. This can of course be extend as you like. If you're lazy, you may call the use function directly:custom_iuse_test(player:i_at(0), false, player:pos() )
for x=0,game.num_zombies()-1 do game.zombie(x).friendly = -1; end
game.zombie(0):setpos(tripoint(10, 10, 0), false)
item_test()
while the character stands on top of some items. The Lua function will print a popup with the item name of each item on the characters position (tests the wrapper for theitem_stack
and its iterator).necro()
while standing on a (revivable) corpse. The corpse will revive, the monster appears on a adjacent square (try in an open area, it needs space).player.name = "Urist McLazy"
- may screw up your save!player.power_level = 1000
What aboutmax_power_level
? That can be changed, too!map:i_clear(player:pos())
for x = 0,11*12-1 do for y = 0,11*12-1 do map:i_clear( tripoint(x,y,0) );end;end
map:add_corpse(player:pos())
- comes with free loot!map:draw_square_ter("t_shrub", 60, 60, 80, 80)
game.zombie(0):die(game.zombie(0) )
or you can make the dirty work:game.zombie(0):die(player)
(watch the kill count)game.popup(game.string_input_popup("?", 100, "") )
orplayer.name = game.string_input_popup( "Name?", 100, player.name)
Stack as much as you like.The exported function behave exactly as the C++ function (including all the bugs and preconditions), for mapgen relevant are probably the mapgen functions. This piece works:
It shows two popups and tries to load the listed file (must be an absolute path, for whatever reason).
Don't forget to compile with
LUA=1
And I gave the Lua debug input box a history, like the filter in the "List items around" has. And it can read files, I found this string to be very useful:
game.dofile("/tmp/x.lua")
(it must be an absolute path). Once entered, you can get it back from the history with only a few keystrokes. The file is read each time the function is called, so you can edit the file with a proper editor.The bugs are probably strong in this one (-:
TODO (for later, not here, not now, just for reference).
g
and maybe rename the globalmap
tom