Anomaly NG vs Vanilla

BlitzMax Forums/BlitzMax NG/Anomaly NG vs Vanilla

Cocopino(Posted 2017) [#1]
Hi,

I've encountered something weird:

Type TDisposable
	Field link:TLink
End Type

Local lst:TList = New TList

For Local i:Int = 1 To 500
	Local dp:TDisposable = New TDisposable
	dp.link = lst.AddLast(dp)
Next

Graphics(1024, 768)


For Local d:TDisposable = EachIn lst
	Cls
	If KeyHit(KEY_ESCAPE) Exit
	RemoveLink(d.link)
	DrawText(lst.count(),10,10)
	Delay(100)
	Flip
Next


In vanilla I see the counter going down as expected, objects get removed from the list at a steady pace.
In NG the counter gets stuck after the first iteration.


Derron(Posted 2017) [#2]
It is because you do a "concurrent modification" of the list.

You iterate through a list... and then while you are inbetween, you delete something. The Iterator is something "added on top" of the list ... so it is not aware of the changes you did.

Should happen with vanilla too - Brucey added something in the last weeks which should allow concurrent modification (at least I think that the corresponding commit was done for that subject)


@ exit
You exit if "something" is > 2 ... which might happen already on the first iteration. Dunno what you mean with "stuck" exactly.

Edit: you seem to have your OP edited meanwhile so the "something"-part is gone.


Edit2: If you want to remove something from a list you could have an empty array of TLinks right before the for-loop. If you now want to remove one of the list entries in that loop, you do not delete it directly (concurrent modification) but append it to the previously empty array (arr :+ [link]). After the for loop you loop over all entries in the array and remove it from the list.
This avoids concurrent modification of the TList.


bye
Ron


Cocopino(Posted 2017) [#3]
Sorry, I edited my example because it had too much unnecessary code.

It does not happen in vanilla, nor should it IMO. TList.count() should always reflect the amount of items in the list, no matter when items were deleted. But consider this example then:


Type TDisposable
	Field link:TLink
End Type

Local lst1:TList = MakeAList()
Local lst2:TList = MakeAList()

Graphics(1024, 768)

While Not KeyHit(KEY_ESCAPE)

	Cls

	RemoveFirstItem(lst1)
	RemoveSomeItem(lst2)

	DrawText(lst1.count(), 10, 10)
	DrawText(lst2.count(), 10, 30)

	Delay(100)
	Flip
	
Wend

Function RemoveFirstItem(lst:TList)
	lst.RemoveFirst()
End Function

Function RemoveSomeItem(lst:TList)
	For Local d:TDisposable = EachIn lst
		RemoveLink(d.link)
		Exit
	Next
End Function

Function MakeAList:TList()

	Local lst:TList = New TList
	For Local i:Int = 1 To 500
		Local dp:TDisposable = New TDisposable
		dp.link = lst.AddLast(dp)
	Next
	
	Return lst

End Function


Problems might be related to RemoveLink


Derron(Posted 2017) [#4]
You are right.

"RemoveSomeItem" (in its current implementation) could be replaced with:
RemoveLink(lst.FirstLink())

And it does indeed _seem_ to not do anything... but that is not the case. The real bug is, that Brucey adjusted TList for NG - and it caches "count".


I adjusted your code to make it visible:


As you can see (hopefully) it prints multiple IDs ... so each time it removes an _other_ disposable entry.


@ count
I am not sure but in my proposal to caching count (for map or so?) I used an "invalidate count on change" approach. Brucey uses an "add/subtract on change" approach. So if he misses an "+- 1" in certain situations - then "count" becomes "wrong".

Edit: Seems the "link.remove()" command bypasses the _count-cache totally as it never uses something from the TList-object but manipulates the children directly (neighbouring TLink siblings).

Filed an issue (with a shoter example).
https://github.com/bmx-ng/brl.mod/issues/44


bye
Ron