[Intended] Using P_RemoveMobj on objects from thinkers.iterate("mobj") stops the loop

Status
Not open for further replies.

Monster Iestyn

Fangtastic
[Intended] Using P_RemoveMobj on objects from thinkers.iterate("mobj") stops the loop

What I mean is doing something like this:

Code:
local function Test(player)
	local numremoved = 0
	for mobj in thinkers.iterate("mobj")
		if mobj.type == MT_RING
			P_RemoveMobj(mobj)
			numremoved = $ + 1
		end
	end
	print("removed "+numremoved+" objects")
end

COM_AddCommand("luatest", Test)

(Lua script also available as attachment below)

Normally, you'd expect *all* rings to be removed from the map when "luatest" is used in the console, but as it happens this only removes one ring. Why? The loop is halted once the removed object is fed back into the next iteration of the contents, beause it can't continue!

This *could* explain some of the problems a few may have been having with Lua. But I don't know, I've only seen a handful of the scripts being produced.

One *could* work around this bug by making a blank deathstate or so (lasting at least 1 tic) instead of removing the object immediately - that way the loop isn't interfered with and you still get your object removals (although delayed by 1 tic). That works for now at least.
 

Attachments

  • Luatest-ringremove.lua
    257 bytes · Views: 143
Last edited by a moderator:
Is this really a bug, though? This is a classic list iteration/deletion problem, and in these cases you're supposed to handle the iteration yourself instead of falling back on the foreach construct. (Whether or not SRB2's Lua actually allows you to do this or not is something I can't answer, though.)
 
Dunno, it's not a bug with Lua handling itself certainly - when an object is removed, it's thinker is removed and Lua thereon deems it invalid and therefore nil, which stops the loop.

...Yet I have the niggling feeling there's *other* ways to do this; object thinkers usually delay their full removal when they're still referenced as a target/tracer of something. Maybe they shouldn't be made to be deemed as nil by Lua when fed back into the iterator, but I'm stumped as to how this would work.

As for other alternatives, one could create a table listing all objects of a particular type which you want to remove? Then yes, you could do the iteration yourself. But how many Lua beginners know how to organise *that* sort of thing in their scripts?
 
Certainly a strange issue. You could work around it by just adding a P_RemoveMobj to the thinkers of those objects specifically with a MobjThinker hook though, right?
 
Maybe the lua is just too lazy to delete all the rings and says "Ill do it another time." It may just be doing that because it is going too fast, as proven by a "work around" you stated. I honestly couldn't tell you though, as lua absolutely LOVES me. But hey, that's my input on this situation, just don't quote me on it.
 
This isn't a bug, it's actually intended behavior. Not exactly intuitive, but intended. The same issue happens with using iterators in other languages (in Java, you get a ConcurrentModificationException). One of the caveats of a language that allows side effects naively.

The way to work with this is to filter the thinkers list with a foreach, put matching filtered thinkers into a new local table, and then iterate over that new table calling P_RemoveMobj on each element. Remember that variable assignment in Lua is by reference.

It should probably be noted somewhere that you shouldn't modify a collection while iterating over it. That's bad practice in any language, because it's hard to infer what's supposed to happen in that context; many just consider it foul play and error out.
 
Last edited:
Kinda late to the party... but instead of exposing the real P_RemoveMobj to scripters, why not have the Lua version instead just call P_SetMobjState(mobj, S_DISS) ?? That's what S_DISS was intended for.
 
As was P_SetMobjState for Lua scripters, I believe! The mechanism which you can change the state is "mo.state = S_NULL".

IIRC, S_NULL's effect on an existing mobj is just that of the former S_DISS? As it removes the object after either 1 tic or immediately (I'm uncertain on the amount of time, but it DOES remove the object.)
 
Actually, S_NULL immediately removes an object as soon as it switches to that state, while the former S_DISS kept the object around for another tic before removing it.

Also, as far as I can guess SSNTails wasn't referring to using P_SetMobjState directly in Lua scripts, but making P_RemoveMobj in Lua scripts secretly switch an object's state to S_DISS instead. After all, mo.state = state itself in a Lua script is secretly performing P_SetMobjState(mo, state) in the source code whenever that action is done.
 
Last edited:
Status
Not open for further replies.

Who is viewing this thread (Total: 1, Members: 0, Guests: 1)

Back
Top