Is there a better way to add an array element?

Monkey Forums/Monkey Programming/Is there a better way to add an array element?

Grey Alien(Posted 2013) [#1]
I basically always end up doing something like this:

	Field sprites:TSprite[]
	
	Method AddSprite(s:TSprite)
		sprites = sprites.Resize(sprites.Length() +1)
		sprites[sprites.Length - 1] = s
	End Method	



I know if speed was a factor I could create the array with a certain number of slots and only make new slots all in a batch when I needed the array to grow more.


NoOdle(Posted 2013) [#2]
You could use a stack? This handles the array resizing for you and you can still access the data like a standard array. You could even extend your own to hack into the data structure to gain greater control over the data array.


Grey Alien(Posted 2013) [#3]
Hmm good plan thanks, I'll look into it. I wonder how Stack speed compares to my array resizing code up there?


Gerry Quinn(Posted 2013) [#4]
If you look at Stack module, it's pretty clear. The key lines are:

		If length=data.Length
			data=data.Resize( length*2+10 )
		Endif


So you get a default of ten elements first time you put something in a stack, then it doubles every time it fills up.


Grey Alien(Posted 2013) [#5]
Thanks Gerry. OK so it's probably a bit more efficient than my method and also I don't have to keep rewriting that resize and add code.


AdamRedwoods(Posted 2013) [#6]
brl.Pool uses a stack, but also implements Free() and Allocate() methods, which manages used/unused objects rather than allowing them to be GC'ed.


impixi(Posted 2013) [#7]
The Pool class isn't documented (V70e). How long has it existed?


EdzUp(Posted 2013) [#8]
Why not use a list?


Beaker(Posted 2013) [#9]
A list is going to be slower and potentially thrash the GC.


EdzUp(Posted 2013) [#10]
But surely removing elements would mean you have to move the elements after it down the array and then shrink the array.


Gerry Quinn(Posted 2013) [#11]
I hadn't realised Pool exists.

Seems clear enough from the code though. If you make a pool of N objects of type T, you can use pool.Allocate() instead of New T to get back a (possibly previously used) one.

Use pool.Free() to return an object you are finished with to the pool for future use.

Ignore the following:
******************************************
You seem to be limited to the initial capacity in this version, so why not use an array? I would have thought you would want to grow the stack in case of a pool being completely used up. But looking at the Stack code it's hard to see how to do that easily at present as there is no Stack.Capacity:Int(). So maybe Mark is waiting to add that method to Stack, then modify Pool.Allocate().

Or he might be considering whether to just use a simplified Stack-type code rather than an actual Stack for Pool. It doesn't need Enumerators etc. after all, so it can be short.
******************************************

Edit: Ignore the previous two paragraphs, I just realised Pool will work fine as is. If you allocate more items than the current capacity, the stack only grows when they are freed - but that is fine, it's only purpose is to preserve them from GC anyway, and GC only becomes a possibility when they are no longer in use.


Grey Alien(Posted 2013) [#12]
@EdzUp[GD] I'm just building an array to access via an index, I'm not removing elements, which is why I chose an array not a list. List does has a ToArray method but I bet that is slow. Wouldn't want to call it every frame for drawing tons of sprites for example.

Well Pool sounds interesting. I made a pool of particle objects (myself using two lists) a while ago, so it's cool if Monkey is going to add a decent class instead as I guess it will be faster. Is it iteratable?

I don't have V70 yet.


Gerry Quinn(Posted 2013) [#13]
It's in some earlier versions, look in brl modules.

Pool isn't iterable as stands, though you could easily make one that was. That would probably be a bad idea, though, as not all the objects you have allocated will necessarily be in the pool. They are only forced in the pool when they are freed.

Edit: In fact it's worse than that - objects that are in use are *never* in the pool!

The point of a pool anyway is just to avoid GC by re-using objects. You would keep them in a different data structure such as a list.

You could probably created some kind of PooledList template that combines a Pool and a List!


marksibly(Posted 2013) [#14]
Not sure where the docs went for brl.pool - adding them back now!

Pool is not 'iterable', because it doesn't know what sort of container you want the objects to be used in - sometimes you want to use a list, sometimes a stack, sometimes a map etc. It's really just a replacement for 'New' to limit GC.

Here's an example...

Import brl.pool

Class Actor

	Function Create:Actor( x:Float,y:Float )
		Return _pool.Allocate().Init( x,y )
	End
	
	Method Destroy:Void()
		_pool.Free( Self )
	End
	
	Method IsDead:Bool()
		Return _t=0	'timeout?
	End
	
	Method Update:Void()
		If _t>0 _t-=1	'update timeout
	End
	
	Private
	
	Field _x:Float
	Field _y:Float
	Field _t:Int
	
	Global _pool:=New Pool<Actor>( 1000 )
	
	Method Init:Actor( x:Float,y:Float )
		_x=x
		_y=y
		_t=Rnd(20,100)	'random timeout
		Return Self
	End
	
End

Function UpdateActors:Void( actors:Stack<Actor> )

	'Update all actors
	For Local i:=0 Until actors.Length
		actors.Get( i ).Update()
	Next
	
	'Flush dead actors
	Local alive:=0
	For Local i:=0 Until actors.Length
		Local actor:=actors.Get( i )
		
		If actor.IsDead() 
			Print "Dead!"
			actor.Destroy()
			Continue
		Endif
		
		actors.Set alive,actor
		alive+=1
	End
	
'	actors.Length=alive		'v70 only...
	While actors.Length>alive
		actors.Pop()
	Wend
End

Function Main()

	Local actors:=New Stack<Actor>
	
	For Local i:=0 Until 100
		actors.Push Actor.Create( Rnd(100),Rnd(100) )
	End
	
	While actors.Length
		UpdateActors actors
	Wend
	
	Print "Done!"

End


Note: You could actually combine the update-all/flush-dead steps in UpdateActors into a single pass. However, I've separated them because it's likely that Actor.Update may need to access the (probably global) actors stack and, if it was allowed to access destroyed actors in the process, bad things could happen...


Grey Alien(Posted 2013) [#15]
Great, thanks for the explanations Mark and Gerry!


Gerry Quinn(Posted 2013) [#16]
Just one note in case it's not obvious:

If you use Pool.Allocate() for an object instead of New(), don't expect fields to be nicely zeroed out as they would be after New(). They may contain data from a previous object.

Forgetting this could easily produce annoying bugs that only appear after your code has run for a while.


Grey Alien(Posted 2013) [#17]
Yep good point. For my particle pool I have a function that specifically zeroes out the fields before use.