Elvish_Hunter's Lua thread

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

Moderator: Forum Moderators

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

Re: Elvish_Hunter's Lua thread

Post by Anonymissimus »

8680 wrote:Regarding the problem of modifying the array while iterating over it, there could be a Boolean attribute to activate a mode that would more closely simulate the macro, including at least allowing modification of the array while iterating over it.
The lua code would need to recapture the wml array in each loop step to check whether elements were added/deleted. Or it would need to somehow forbid the wml author to modify the array outside of the lua author's control or the lua author would need some notification about how the array changed.
Due to this it doesn't make much sense to me to write such tag in lua at all.
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
User avatar
Elvish_Hunter
Posts: 1575
Joined: September 4th, 2009, 2:39 pm
Location: Lintanir Forest...

Re: Elvish_Hunter's Lua thread

Post by Elvish_Hunter »

zookeeper wrote:Because of that, I'd like to see an additional counter=variablename key in [foreach], with which you could specify a variable into which the index of the current item would be stored.
8680 wrote:I also recommend storing the current index in addition to the current value; I’m sure some users of the macro would need it.
I originally designed it to work without index, like in Python's for cycles: usually they don't return an index, unless you require it with the enumerate() function. But anyway, it's quite easy to add. :)
Anonymissimus wrote:since it doesn't support modifying the array while iterating over it.
If you're talking about modifying a value while iterating the array, you can: you just need to access $this_item. However, if you are (as I understood) talking about deleting items while iterating, well, then it's a common problem also with Lua's ipairs, Python's for ... in cycles and several other languages' for cycles. I don't remember where, but I read once that modifying an array (by adding or removing elements) while iterating it is a bad programming practice. And in fact, in the rare occasions when I had to do this, I had to iterate the array (or list, table, vector, whatever...) backwards. Maybe... adding an iterate_backwards= key?
8680 wrote:The [do] seems unnecessary, with the only subtags being the ActionWML.
It depends. Should I start adding more keys to the tag, it may become useful to keep the tag ordered. But you're right, currently [foreach] uses only the variable= key, and so for now [do] isn't really needed.
8680 wrote:Certainly. It would be necessary (or at least highly convenient) when accessing multiple levels of nested arrays.
8680 wrote:Why not?
OK, I'll add both.
Current maintainer of these add-ons, all on 1.16:
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
Anonymissimus
Inactive Developer
Posts: 2461
Joined: August 15th, 2008, 8:46 pm
Location: Germany

Re: Elvish_Hunter's Lua thread

Post by Anonymissimus »

Elvish_Hunter wrote:However, if you are (as I understood) talking about deleting items while iterating, well, then it's a common problem also with Lua's ipairs, Python's for ... in cycles and several other languages' for cycles. I don't remember where, but I read once that modifying an array (by adding or removing elements) while iterating it is a bad programming practice. And in fact, in the rare occasions when I had to do this, I had to iterate the array (or list, table, vector, whatever...) backwards. Maybe... adding an iterate_backwards= key?
I wouldn't call it bad practice, since you will end up iterating twice, first gathering data, then modifying, if you try to avoid deleting/adding elements from containers while iterating over them. But it's certainly dangerous, you can have invalid iterators, jump over elements, go out of bounds etc etc. With some containers and languages it's less dangerous; wesnoth used std::map in its unit_map for a long time. (I recall that the ListIterator with java's ArrayList is very cool with that, since it auto-updates whenever the list is modified.)

However, back to [foreach]; such tag should support everything the FOREACH macro does so that it can be used as a replacement; and as exasperation shows in the thread I've linked above, this is too hard or complicated to achieve with such a lua tag.
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
User avatar
Elvish_Hunter
Posts: 1575
Joined: September 4th, 2009, 2:39 pm
Location: Lintanir Forest...

Re: Elvish_Hunter's Lua thread

Post by Elvish_Hunter »

Anonymissimus wrote:(I recall that the ListIterator with java's ArrayList is very cool with that, since it auto-updates whenever the list is modified.)
This looks interesting; I should really try Java, sooner or later.
Anonymissimus wrote:However, back to [foreach]; such tag should support everything the FOREACH macro does so that it can be used as a replacement; and as exasperation shows in the thread I've linked above, this is too hard or complicated to achieve with such a lua tag.
Originally I had in mind to do something different, rather than a one-to-one replacement. It wasn't my plan to replace {FOREACH}, but instead to have a more powerful and maybe simpler to use alternative, even if that meant breaking retrocompatibility (on a certain degree, because I never planned to remove {FOREACH}).
Anyway, new version:
[foreach]

Code: Select all

function wml_actions.foreach( cfg )
	local array_name = cfg.variable or helper.wml_error "[foreach] missing required variable= attribute"
	local array = helper.get_variable_array( array_name )
	if #array == 0 then return end -- empty and scalars unwanted
	local item_name = cfg.item_name or "this_item"
	local this_item = start_var_scope( item_name ) -- if this_item is already set
	local i_name = cfg.index or "i"
	local i = start_var_scope( i_name ) -- if i is already set
	
	for index, value in ipairs( array ) do
		wesnoth.set_variable( item_name, value )
		-- set index variable
		wesnoth.set_variable( i_name, index-1 ) -- here -1, because of WML array
		-- get do tag. Needs to be placed here in case of nested arrays
		local do_actions = helper.get_child( cfg, "do" )
		-- perform actions
		-- replace with handle_event_commands(v) in mainline
		wml_actions.command( do_actions )
		-- set back the content, in case the author made some modifications
		if not cfg.clear_variable then
			array[index] = wesnoth.get_variable( item_name )
		end
	end
	
	-- house cleaning
	wesnoth.set_variable( item_name )
	wesnoth.set_variable( i )
	end_var_scope( item_name, this_item )
	end_var_scope( i_name, i )
	
	-- restore or clear the array
	if cfg.clear_variable then
		wesnoth.set_variable( array_name )
	else
		-- restore the array
		helper.set_variable_array( array_name, array )
	end
end
What I did this time:
  • added index key, where the index of the current element is stored (default i)
  • added clear_variable key (default no)
  • added item_name key to allow choosing the array item name (default this_item)
At least for now, I left the [do] tag. I may or may not remove it in future - it depends on how its development proceeds.
At this point, one possible problem is the "i" variable. In the FOREACH macro, it is possible to alter its value, while in the tag is not possible. For almost all situations, this shouldn't be a problem; however it'll may be an issue if the UMC author uses {FOREACH}, iterates over an array, deletes some elements and then uses {VARIABLE_OP} to make up for the missing elements.

On the other hand, it is possible to unstore units simply with this:

Code: Select all

[foreach]
	variable=units
	clear_variable=yes
	[do]
		[unstore_unit]
			variable=this_item
			find_vacant=no
		[/unstore_unit]
	[/do]
[/foreach]
Current maintainer of these add-ons, all on 1.16:
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
User avatar
pauxlo
Posts: 1047
Joined: September 19th, 2006, 8:54 pm

Re: Elvish_Hunter's Lua thread

Post by pauxlo »

Anonymissimus wrote:(I recall that the ListIterator with java's ArrayList is very cool with that, since it auto-updates whenever the list is modified.)
Even better: the list iterator itself has remove, set and insert methods. (Structurally modifying a list while iterating over it, other than with the iterator's own methods, is generally not supported, and might, depending of the list implementation, result in exceptions, reading old data or even reading data which never was in the list (the latter only in concurrent environments, though). For an ArrayList, you normally only get a ConcurrentModificationException.) But for this you can't use the foreach language construct, but have to use the explicit iterator object (and a normal for or while loop).

You could implement a similar iterator object for a Lua array (e.g. a map which uses subsequent integer keys), but the problem stays that between WML and Lua the objects are translated/copied.
User avatar
Elvish_Hunter
Posts: 1575
Joined: September 4th, 2009, 2:39 pm
Location: Lintanir Forest...

Re: Elvish_Hunter's Lua thread

Post by Elvish_Hunter »

pauxlo wrote:You could implement a similar iterator object for a Lua array (e.g. a map which uses subsequent integer keys), but the problem stays that between WML and Lua the objects are translated/copied.
That - I suppose - will be too much work, for too little benefit.
Maybe the best solution is to leave the tag as is and put a note in the documentation. Or, as I suggested, implement a iterate_backwards= key, that will solve (at least partially) the problem of deleting items while iterating them, but again won't allow altering the index variable.
Current maintainer of these add-ons, all on 1.16:
The Sojournings of Grog, Children of Dragons, A Rough Life, Wesnoth Lua Pack, The White Troll (co-author)
Post Reply