Deleting objects

Monkey Forums/Monkey Programming/Deleting objects

lom(Posted 2016) [#1]
Hi everyone! What is a proper way to delete objects in monkey? Right now I'm using this code:
Method update_bullets()
For bullet = Eachin bullet_list
bullet.update()
'delete bullet
If bullet.destroy=true Then bullet_list.Remove(bullet)
Next
End

Is it correct? Is it safe to constantly add and remove objects from the list?


ImmutableOctet(SKNG)(Posted 2016) [#2]
Everything in Monkey is garbage collected. This means that if there is nothing referencing the object, it will be collected automatically. The exact behavior of garbage collection is platform-specific, so it's not exactly one garbage collector you're working with.

The best way to think about the storage of objects is that if nothing cares about it, it's already free. With that said, certain precautions should be taken. For example, marking an object as "free" when you've removed it from a collection may be suitable. Alternatively, when it comes to very frequent or very large allocations, a pool could be used to manage references to objects.

Pools effectively allow you to control the lifetimes of objects. This means a user could request an object, use it, then give it back, without the need for the garbage collector to touch the area of memory. This of course means you can know ahead of time if an object should "live" or "die".

The interesting thing about this approach is that it promotes design patterns that not only keep storage details abstract, but also encourage data-only or highly managed object states. Great examples of this being release and initialization procedures which in a sense could work as ways of mapping memory. Obviously, this is more of a theoretical or perhaps a design thing, but the ground work is set with well intentioned memory management.

There's tons of details regarding memory layouts, cache coherence, and proper destruction that aren't necessarily controllable in higher level languages, but object management is one of the things we can do.

The behavior of the garbage collector honestly depends on the environment you target, so it's hard to say if pooling is required or not, but it definitely gives you a lot more head room when targeting a lot of platforms. Historically, the Android and XNA (C#) targets were the biggest offenders of "GC bloat", where despite being deterministic solutions, they could still cause inconsistent stalls during gameplay.

Today, this is at least less of an issue; that's definitely the case on higher-end systems, but I definitely feel that the mindset of reducing allocations should still be kept when targeting many platforms.

To put it simply, if you're just using moderately sized or otherwise small objects, feel free to remove them from collections and forget about them. Anything in the direction of arrays or data-buffers should have some thought put into whether a reallocation should be preferred. There's no manual deletion allowed in Monkey, nor should you try to destruct an object that could still be intently referenced somewhere else. If it's something with a lot of "weight" to it, or something like a game-object you have a lot of control over, it depends on your preferences and debugging practices.

Here's some similar threads that talk about this sort of subject:
* Question on Garbage Collection
* STDCPP GC?
* Memory Leak?


There's some other things I could go into, like how array and stacks are technically better than just using lists, but it's honestly less of a problem these days. The other posts I listed should help you if you're interested, at least.


Gerry Quinn(Posted 2016) [#3]
Just in case the above isn't clear, what you are doing is fine.

Octet is just pointing out that you can do more sophisticated stuff - essentially amounting to re-using bullet objects rather than deallocating them and creating new ones - if you need max performance with regard to memory allocation.


Paul - Taiphoz(Posted 2016) [#4]
you can also manage your object without going down the full pooling route but still gaining some of the benefit from not constantly creating new objects but giving all of your game objects a new field called something like Active which would be a boolean you set to either true or false and then only rendering or updating the objects in your list based on the value of this active field.

This lets you say for example create 100 bullets but once you need more than 100 bullets then no longer create new one's instead go and grab one that's inactive from the bullet list, this does mean its a little less efficient than full on pooling but its still fairly quick and you gain the benefit of not always crating and deleting objects which can cause some slow down.


Sicilica(Posted 2016) [#5]
Your bullet example actually would benefit HEAVILY from pooling. You don't even have to code a pool; use the brl.pool module.

The general rule of thumb is, you don't want to be calling constructors on a regular basis. If you need to New up some objects occasionally, that's fine, but with bullets you know you're going to have to make more every frame, yet at the same time there are never more than a few at a time. You could code your own solution with an "active" flag, but then you have to search through your list of bullets to find an "inactive" one, which is a huge pain. This is a textbook example of when to use a pool.

Before you get yourself in trouble, though, it's important to note that there ARE times when you have to think about memory: in Mojo as with most game frameworks, the memory for "resources" are managed differently. You'll notice that images, sounds, and databuffers have a "Discard" method or similar. You need to call these yourself, or the garbage collector won't up that memory and you'll have a leak. The memory used by those sorts of classes is outside the realm of the gc.


lom(Posted 2016) [#6]
Thanks for the tips! Now I feel more comfortable with this.
I think I really need to get used to pool.


Paul - Taiphoz(Posted 2016) [#7]
yeah iv never used monkey's default pools actually only found out about them recently I had been doing it my own way but will probably check it out at some point, just wish monkey docs were better you know, i tend to not dive through the mojo source very often :/


lom(Posted 2016) [#8]
I'm trying to implement pool in my code and here's what I did:
Import mojo
Import brl.pool

Class base Extends App

	Field rate:Int
	Field bullets:Int
	Field out:int
	Field bu:bullet
	Field bul_list:List <bullet>
	Global _pool:=New Pool<bullet>(10)
	
	Method OnCreate()
		SetDeviceWindow DeviceWidth,DeviceHeight,2
		bul_list = New List <bullet>
	End
	
	Method OnUpdate()
		rate=(rate+1) Mod 8
		
		out=0
		update_bullets()
		'create 3 bullet types
		If rate=0
			create_bullet(10,200,1)
			For Local g:Int=0 To 4
				create_bullet(10,350+Rnd(60),2)
			next
		Endif
		create_bullet(10,100,3)

		If KeyHit (KEY_ESCAPE) Then EndApp()
	End
	
	Method OnRender()
		Cls 0,0,0
		draw_bullets()
		DrawText "Bullets count "+bullets,20,30
	End
	'CREATE PLAYER BULLETS
	Method create_bullet(x:Int,y:Int,type:int)
		If out=0
			bu= New bullet
			bu.init(x,y,type)
			bul_list.AddLast bu
		Endif
		If out>0
			 _pool.Allocate().init(x,y,type)
		endif
	End
	'UPDATE PLAYER BULLETS
	Method update_bullets()
		bullets=0
		For bu = Eachin bul_list
			bullets=bullets+1
			bu.update()
			If bu.dead=true Then out=out+1
		Next
	End
	'DRAW PLAYER BULLETS
	Method draw_bullets()
		For bu = Eachin bul_list
			bu.draw()
		Next
	End

End

Class bullet
	Field x:Float,y:Float,val:Float,dead:Bool,type:Int,val2:float
	
	Method init(ix:Int,iy:Int,itype:int)
		type=itype
		x=ix
		y=iy
		dead=False
		val=0
		val2=90+Rnd(14,-14)
	end
	Method update()
		If dead=False
			Select type
				Case 1
					x=x+3
					val=val+7
					y=y+Cos(val)*2
				Case 2
					x=x+7
				Case 3
					x=x+5
					y=y+Cos(val2)*5
			End select
		endif
		If x>600 And dead=False Then dead=True base._pool.Free(Self)
	End
	Method draw()
		If dead=False
			Select type
				Case 1
					SetColor 255,155,90
					DrawRect x,y,20,20
				Case 2
					SetColor 155,255,90
					DrawRect x,y,30,10
				Case 3
					SetColor 255,35,190
					DrawRect x,y,10,10	
			End Select
				SetColor 255,255,255
		endif
	End
end

Function Main()
	New base
End

It seems like it's working fine but if you look in task manager, you see that the memory keeps increasing all the time. What am I missing?


Sicilica(Posted 2016) [#9]
What is "out"? Are you trying to track the number of bullets in the pool?

Also, in your create_bullet() method, you sometimes add the new bullet to your list and sometimes you don't. Looking through your code, I'm not convinced that all of your bullets are getting marked as "dead" properly, so you might just be creating more and more of them...

Rather than trying to track how many bullets are available in the pool, just call Allocate() every time. From the documentation:

Allocates an object from the pool.

If the pool is empty, an object created using New is returned instead.



So, you don't need to worry about keeping track.
Also, you probably need to be adding the new bullet to your active list every time you create one, or else it won't get updated and will never get freed.
Lastly, make sure that when your bullets are ready to be destroyed they both get removed from your list and freed back into the pool. In particular, I don't think they're ever being removed from bul_list, so that list is going to grow endlessly.


Jesse(Posted 2016) [#10]
The think you need to understand is that a Pool is a storage area for objects. all a pool does is allocate memory for, in your case, bullets. if you want to allocate memory for 50 bullets you do something like this:
pool = new Pool<bullet>(50).
 

that instruction creates 50 bullets for future use.
next what you want to do is get a bullet from the pool.
bu = pool.Allocate()

if there are bullets in the pool, it extracts one and assigns it to the bu variable. if there are no bullets, it creates one automatically and assigns it to bu.
if you want to use keep track of the bullet, the best think to do is save it to a list.:
It is recommended that the bullet be initialized at this stage.
list.AddLast(bu)

From there do whatever you want with the bullet until you decide to get rid of it. once you are done with the bullet you can return it to the pool:
pool.free(bu)
bu = Null  'if you so desire



here is your code working:



lom(Posted 2016) [#11]
Sicilica
Thanks for the tips!
"out" variable in my code counts "dead" bullets
Jesse
Thanks for help! Now I understand how the pool works.
However, I tested your code and the task manager shows a memory leak as well as with my code. I can't figure out what's causing it. I'm using monkey 85e


Jesse(Posted 2016) [#12]
No garbage collector is going to collect everything on the fly. usually if it doesn't go beyond a certain amount it will not be collected. so the Task Manager will almost never be accurate to a byte.
also, take in consideration that list creates and deletes objects as you add or remove objects from the list itself. also the "For enumerator creates objects..
a better way would be to create your own link list that doesn't not create new objects and go through the link list of bullets that way.