Skip to content
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

Merged
merged 35 commits into from May 26, 2015
Merged

Conversation

BevapDin
Copy link
Contributor

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 tripoints and points. Both get value semantics, their data is stored and managed by Lua. The other exported classes like monster, 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 create itype 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_casts 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):

  • Give some item the 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() )
  • Everybody is my friend: for x=0,game.num_zombies()-1 do game.zombie(x).friendly = -1; end
  • You don't belong here: game.zombie(0):setpos(tripoint(10, 10, 0), false)
  • Enter the command 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 the item_stack and its iterator).
  • Enter 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).
  • You don't like your character name? You don't have to live with! Enter player.name = "Urist McLazy" - may screw up your save!
  • You have no power? player.power_level = 1000 What about max_power_level? That can be changed, too!
  • Get rid of all the items (under your feet)! map:i_clear(player:pos())
  • Get rid of all the items (by all, I mean all): for x = 0,11*12-1 do for y = 0,11*12-1 do map:i_clear( tripoint(x,y,0) );end;end
  • You're a cannibal, but you find never find enough human corpses? This is the solution: map:add_corpse(player:pos()) - comes with free loot!
  • Or are you a Grazer? Get some shrubs: map:draw_square_ter("t_shrub", 60, 60, 80, 80)
  • Zombies are stupid, right? They even kill themselves: game.zombie(0):die(game.zombie(0) ) or you can make the dirty work: game.zombie(0):die(player) (watch the kill count)
  • Twice as interactive: game.popup(game.string_input_popup("?", 100, "") ) or player.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:

"lua" : [
    "game.popup('Lua is there to stay!')",
    "game.popup(map:name(tripoint(0,0,0)))",
    "game.dofile('<abs-path-to-some-Lua-script->')"
],

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).

  • Function overloading must be done manually (grab the arguments and dispatch the to the proper function, alternatively give each function a unique name in Lua). Currently the class definitions contain several function of the same name, only the last definition of those is actually used.
  • Allow access to the game instance via g and maybe rename the global map to m
  • Implement a Lua wrapper that supports reference and value semantic (e.g. for items).
  • Allow loading Lua scripts files from mapgen definition with some relative path

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.
@Coolthulhu
Copy link
Contributor

game.dofile doesn't work with Windows paths

@Coolthulhu
Copy link
Contributor

Breaks compatibility with Fast Zombies and similar mods.

@Coolthulhu Coolthulhu removed their assignment May 24, 2015
@Coolthulhu
Copy link
Contributor

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.

@CIB
Copy link
Contributor

CIB commented May 24, 2015

@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.

@Zireael07
Copy link
Contributor

Heey, I know Lua 😄
If this went in, I'd contribute in more ways than just adding JSON stuff.

@Coolthulhu
Copy link
Contributor

This PR will probably get in, the discussion is about making Lua a hard dependency and rewriting a lot of game in it.

@CIB
Copy link
Contributor

CIB commented May 24, 2015

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.

@BevapDin
Copy link
Contributor Author

Very good work, as expected. Your wrapper classes make me feel a lot more comfortable than the previous hack. It's a good abstraction. This also seems to make it a lot easier to wrap certain things and to add new wrapper (meta-) functionality.

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.

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.

For now, you have access to item::get_var/set_var/has_var - that data will be stored in saves. Creature has a similar map, accessed via set_value/get_value.


@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 --). Of course, that might break some Lua script.

And I'll fix the conflicts with the existing Lua mods.


One of the main things we could do to promote LUA as a viable option
for modders, would be to start porting some of the less performance
critical portions of the game logic to lua.

Why? That will only introduce bugs. If Lua needs access to that part, add a callback, there is for example already a lua_callback(lua_state, "on_day_passed"); - Never touch a running system.


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 map functions it (currently) supports, the few player functions (it's not even marked as inheriting from Character or Creature), etc. One simply can not implement much with such a small code base. A potential coder would look at it and ask "how do I access X?" and the answer is often "You can't access it from Lua."

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.
@BevapDin
Copy link
Contributor Author

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.


Well, you should probably check that it actually matches the expected prefix, then. Also probably a good idea to throw a debug message on the else branch.

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 pnt = tripoint( 0, 1, 2 ), but this is not really required for other classes like map or Creature.

I changed it to let the generator decide what name they should have (and whether they should be exported globally at all).

@BevapDin
Copy link
Contributor Author

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

@CIB
Copy link
Contributor

CIB commented May 24, 2015

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.

@DavidKeaton
Copy link
Contributor

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.

@Cat09
Copy link
Contributor

Cat09 commented May 25, 2015

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++
btw: bp.leg_r makes it less redundant, and matches the C++ better

@BevapDin
Copy link
Contributor Author

btw: bp.leg_r makes it less redundant, and matches the C++ better

You mean body_part.bp_leg_r? Because bp.leg_r does not have any resemblance to the C++ enum names.

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 body_parts with a metatable that has "__index" defined as a function that simply returns the name of the requested index. (And it may also do the checking.)

@Cat09
Copy link
Contributor

Cat09 commented May 25, 2015

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 bp_ moniker, or use that as the bodypart table name.

@Coolthulhu Coolthulhu self-assigned this May 26, 2015
@Coolthulhu
Copy link
Contributor

getlight_emit_active conflicts with Kevin's light system rewrite.
For now just removed it from the class_definitions.lua.

Coolthulhu added a commit that referenced this pull request May 26, 2015
Updating the C++ to Lua interface [CR]
@Coolthulhu Coolthulhu merged commit d902820 into CleverRaven:master May 26, 2015
@Coolthulhu
Copy link
Contributor

May have bugs, but doesn't break existing functionality.

Windows style file paths not supported by game.dofile

Tested it by spawning a mapful of corpses like:
for x=0,11*12-1 do for y=0,11*12-1 do map:add_corpse(tripoint(x,y,0)); end; end;
Clearing map, spawning single zeds etc.

@BevapDin
Copy link
Contributor Author

Windows style file paths not supported by game.dofile

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: dofile always adds a / in front of the path, that would result in a more or less invalid path on windows.

@ZhilkinSerg
Copy link
Contributor

dofile is awesome!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants