[1.7.13] yet another getting started with lua thread

Discussion of Lua and LuaWML support, development, and ideas.

Moderators: Forum Moderators, Developers

Post Reply
tsr
Posts: 790
Joined: May 24th, 2006, 1:05 pm

[1.7.13] yet another getting started with lua thread

Post by tsr » February 13th, 2010, 3:52 pm

I have these files (see below) and I expect them to result in:
- on every "new turn" display a message that says "Hello world!"

Instead they result in:
- The scenario loads, I see the map and all the sides (the map-file has starting positions for 9 sides so I don't need any in the scenario-file) and then:
stderr wrote: error scripting/lua: /usr/local/share/wesnoth-dev/data/lua/helper.lua:126: bad argument #2 to 'set_variable' (WML table or scalar expected, got table)
stack traceback:
: in function 'set_variable'
/usr/local/share/wesnoth-dev/data/lua/helper.lua:126: in function set_variable_proxy'
/usr/local/share/wesnoth-dev/data/lua/helper.lua:157: in function </usr/local/share/wesnoth-dev/data/lua/helper.lua:151>
[string "wlp_utils = wesnoth.require '~add-ons/Wesno..."]:1: in main chunk

error scripting/lua: .../tsr/.wesnoth-dev/data/add-ons/WCE2/lua/prestart.lua:4: attempt to call field 'message' (a nil value)
stack traceback:
.../tsr/.wesnoth-dev/data/add-ons/WCE2/lua/prestart.lua:4: in main chunk
[C]: in function 'dofile'
[string " wesnoth.dofile "~add-ons/WCE2/lua/prestart..."]:1: in main chunk
[/quote]

Otoh if I reload the preload-lua-file from within the scenario I don't get the first error-message above, doing the same for the prestart.lua-file still gives the second error.

In my trial and error efforts I managed to get it right once, but now I can't remember what I did to make it happen. It is probably something extremely simple. Please consider not only pointing out what I did wrong but why it is wrong and why the correct solution is correct.

/tsr

ps. I will try to write up some beginners guide to lua after coding this, so hopefully the one answering this (and I'm looking at you silene ;)) wont have to anser the same question again and again...


Files:
[quote="~add-ons/WCE2/_main.cfg"]
#ifdef MULTIPLAYER
{~add-ons/WCE2/scenarios}
#endif
[/quote]
[quote="~add-ons/WCE2/scenarios/WCE_main.cfg"]
[multiplayer]

id=WCE_Main
name= _ "Wesnoth Collaborative Editor"
map_data="{~add-ons/WCE2/maps/WCE_Main.map}"
description="Edit Wesnoth maps together with other players in a multiplayer 'game'"
random_start_time="no"

{DEFAULT_SCHEDULE}
{DEFAULT_MUSIC_PLAYLIST}

[event]
name=preload
first_time_only=no
[lua]
code = << wesnoth.dofile "~add-ons/WCE2/lua/preload.lua" >>
[/lua]
[/event]

[event]
name=prestart
[lua]
code = << wesnoth.dofile "~add-ons/WCE2/lua/prestart.lua" >>
[/lua]
[/event]

[/multiplayer]
[/quote]
[quote="~add-ons/WCE2/lua/preload.lua"]
H = wesnoth.require "lua/helper.lua"
W = H.set_wml_action_metatable {}

H.set_wml_var_metatable(_G)
[/quote]
[quote="~add-ons/WCE2/lua/prestart.lua"]
T = H.set_wml_tag_metatable {}
W.event { name = "new turn", T.message { speaker = "narrator", message = "Hello world!" } }
[/quote]

silene
Posts: 1109
Joined: August 28th, 2004, 10:02 pm

Re: [1.7.13] yet another getting started with lua thread

Post by silene » February 13th, 2010, 4:19 pm

When you call set_wml_var_metatable on _G, you cause Lua to consider all the accesses to global variables as accesses to WML variables. As a consequence, when you later assign T, you are asking the engine to store into a WML variable called T something that doesn't make sense to it. Hence your issue.

There are three ways of fixing it.
  1. Don't touch _G, and access WML variables through an explicit variable (e.g. "V.something").

    Code: Select all

    V = H.set_wml_var_metatable {}
  2. Move the definition of T in the preload file before touching _G.
  3. Declare T as a variable local to the prestart file.

    Code: Select all

    local T = H.set_wml_tag_metatable {}
Note that, even if you choose solution 1, you should still apply solution 2 or 3, as it will avoid several possible bugs later on.

silene
Posts: 1109
Joined: August 28th, 2004, 10:02 pm

Re: [1.7.13] yet another getting started with lua thread

Post by silene » February 13th, 2010, 4:51 pm

Hmm... now that I think about it, writing a complete scenario in Lua will probably cause a lot of troubles. I think your way of structuring things will not scale well. First of all, you should put all your Lua code in the preload event; everything else will probably force you to write Lua code as if it was just WML, which is kind of pointless.

Code: Select all

[event]
  name=preload
  first_time_only=no
  [lua]
    code = << wesnoth.dofile '~add-ons/WCE2/lua/scenario1.lua' >>
  [/lua]
[/event]

[event]
  name=prestart
  [lua]
    code = "init()"
  [/lua]
[/event]
Then your scenario1.lua file will look like the following. (Disclaimer: completely untested.)

Code: Select all

local H = wesnoth.require "lua/helper.lua"
local W = H.set_wml_action_metatable {}
local T = H.set_wml_tag_metatable {}
H.set_wml_var_metatable(_G)

local function register_event(name, fun, filter)
  -- this function should be improved to handle other event setups
  local cfg = { name = name, first_time_only = false, T.lua { code = fun .. "(...)" } }
  if filter then cfg[2] = { "filter", filter } end
  W.event(cfg)
end

function hello_world(cfg)
  -- the cfg parameter would be useful for accessing things like x1, y1, ... if needed
  W.message { speaker = "narrator", message = "Hello world!" }
end

function init(cfg)
  register_event("new turn", "hello_world")
  -- register all the other events of the scenario...
end

Anonymissimus
Inactive Developer
Posts: 2458
Joined: August 15th, 2008, 8:46 pm
Location: Germany

Re: [1.7.13] yet another getting started with lua thread

Post by Anonymissimus » February 13th, 2010, 5:50 pm

Just in case that the code you've posted (silene) is way too much - for me at least...
@tsr
Do you have a specific reason for using this stuff

Code: Select all

W = H.set_wml_action_metatable {}
H.set_wml_var_metatable(_G)
?
Since I honestly still don't understand the difference between table, wml table, userdata, __cfg, __literal, __parsed, "dump", "proxy table" and such, I don't care about "metatable". Complex lua scripts/functions seem quite possible without "metatable"s.
If you want a "hello world" program that's as simple as possible only this should be enough (You don't need a preload event!):

Code: Select all

	[event]
		name=new turn
		first_time_only=no
		[lua]
			code= << wesnoth.message("Hello world!") >>
		[/lua]
	[/event]
...and I'd advise everyone to start with such small manageable things...
projects (BfW 1.12):
A Simple Campaign: campaign draft for wml startersPlan Your Advancements: mp mod
The Earth's Gut: sp campaignSettlers of Wesnoth: mp scenarioWesnoth Lua Pack: lua tags and utils
updated to 1.8 and handed over: A Gryphon's Tale: sp campaign

silene
Posts: 1109
Joined: August 28th, 2004, 10:02 pm

Re: [1.7.13] yet another getting started with lua thread

Post by silene » February 13th, 2010, 6:24 pm

Anonymissimus wrote:Since I honestly still don't understand the difference between table, wml table, userdata, __cfg, __literal, __parsed, "dump", "proxy table" and such,
  • A table is a native Lua type, which is a dictionary (aka a map).
  • A wml table is just a Lua table with additional restrictions, so that it can represent/describe WML objects.
  • A userdata is a native Lua type, which represents a C++ pointer. In other words, whenever you interact with a userdata, you are modifying the internal data structures of the Wesnoth engine.
__cfg is a special field provided by some userdata. Reading this field creates a copy (aka dump) of the internal data as a wml table. For instance, accessing __cfg on a unit userdata has a behavior similar to performing [store_unit] on this unit. Since the dump is a plain wml table, modifying it has no influence on the internal data.

As you know, wml objects that appear inside event handlers benefit from delayed variable substitution: formulas are evaluated only when the attributes are read, not when the objects are created. In Lua, these read-only objects are represented by userdata, which provide two special fields: __literal and __parsed. Again, these fields produce a copy of the internal data structure as a wml table. The first field does not perform variable substitution (so the attributes in the copy still contain formulas), while the second one does. If you have understood what the delayed_variable_substitution attribute of the [event] tag does, then you can assume the behavior is exactly the same for the __literal and __parsed attributes.
Anonymissimus wrote:I don't care about "metatable". Complex lua scripts/functions seem quite possible without "metatable"s.
Metatables are a property that can be set on Lua objects, in particular on tables and userdatas. They describe the behavior of these objects. For instance, it's a metatable that says that a unit has a __cfg field and that it dumps the content of the internal data.

As for the metatables in the helper file, they are just syntactic sugar. (Contrarily to the ones provided for the userdata, the ones in the helper file are written in Lua, so they don't provide any new feature.) They are just here to simplify the usage of a few things. For instance, the following three scripts are strictly equivalent:

Code: Select all

wesnoth.set_variable("counter", 1 + wesnoth.get_variable "counter")

--

local V = helper.set_wml_var_metatable {}
V.counter = 1 + V.counter

--

helper.set_wml_var_metatable(_G)
counter = 1 + counter

tsr
Posts: 790
Joined: May 24th, 2006, 1:05 pm

Re: [1.7.13] yet another getting started with lua thread

Post by tsr » February 13th, 2010, 9:13 pm

silene wrote:... force you to write Lua code as if it was just WML, which is kind of pointless.
Thanks silene, your code worked, but I want to go the pointless route ;)

(imho it has merit since it accomplishes two things. First I can edit my WML without having to go back to the main menu and F5, second it allows me to write WML-heavy scenarios without slowing down the initial loading of wesnoth since lua files are dealt with at run time - if I understand correctly)

Anyhow, this is the path I've chosen for now, and I need a little bit of help again...

Compare:

Code: Select all

	[event]
		name=turn 2
		[replace_map]
			map="{~add-ons/WCE2/user_maps/test.map}"
			expand=yes
			shrink=yes
		[/replace_map]
	[/event]
with:

Code: Select all

	wesnoth.fire (
		"event",
		{
			name="turn 3",
			{"replace_map",
				{
					map="{~add-ons/WCE2/user_maps/test.map}",
					shrink=true,
					expand=true
				}
			}
		}
	)
They should (imho) result in the same (ofc in different turns) but they don't. The reason being that [replace_map] must have a quoted string or otherwise it will choke. I've tried any combination of quotes (', ", [[, \'. \") I can think of but I don't manage to get any other result for the lua-version than:
stderr wrote: error general: An error due to possibly invalid WML occurred
The error message is :
A map without a header is not supported

When reporting the bug please include the following error message :
Condition '!(header_offset == std::string::npos || comma_offset < header_offset)' failed at src/map.cpp:163 in function 'read'.
What is it I don't understand?

(if I paste the contents of 'test.map' directly into the lua file (quoting with [[...]]) it works, but since the idea is to load maps dynamically I can't really do that)

/tsr

silene
Posts: 1109
Joined: August 28th, 2004, 10:02 pm

Re: [1.7.13] yet another getting started with lua thread

Post by silene » February 13th, 2010, 9:53 pm

tsr wrote:but I want to go the pointless route
In case it wasn't clear, it means you won't be able to use any feature of Lua. You are just using it as a preprocessor. For instance it will be impossible to write a "if then else".
tsr wrote:What is it I don't understand?
When using [replace_map] on a map file, it is the preprocessor that is responsible for loading the file. In your case, the preprocessor is not run (it wouldn't make sense to run it on a non-WML file). There are many solutions, all of them involve writing some WML. For instance, you could add the following at the beginning of your prestart event

Code: Select all

[set_variable]
  name = "test_map"
  value = "{~add-ons/WCE2/user_maps/test.map}"
[/set_variable]
and then you would do the following in your Lua code

Code: Select all

{
  map = wesnoth.get_variable "test_map",
  shrink = true,
  expand = true
}

tsr
Posts: 790
Joined: May 24th, 2006, 1:05 pm

Re: [1.7.13] yet another getting started with lua thread

Post by tsr » February 14th, 2010, 3:42 pm

Thanks for your time silene, things are becoming clearer and clearer for me.

Soon I think I'll have a basic grasp of this.

(For the record I ended up creating an index file for the maps in WML since the only way to get the WML-preprocessor to parse file is by naming them explicitly and using wesnoth.dofile in Lua required users to edit their maps (adding 'lua_map = [[' in the beginning and ']]' in the end since Lua chokes on map-files otherwise)

Other than that it is working pretty well, I'm currently using Lua to write WML that includes Lua that includes WML... fun! :P

The 'why?' can't really be answered in any other way than "cause I can!".

/tsr

Post Reply