Delete

BlitzMax Forums/BlitzMax Beginners Area/Delete

sswift(Posted 2006) [#1]
So, I've come to the point where I need to make a method to free a sprite the user has created.

The reason I need a method to do it, or rather, a function it seems, is because the sprites are added to a list so that they can be looped through.

This however seems to lead to more weirdness about Blitzmax that I don't like.

This is what I wanted to do:

	Method Free()
	
		ListRemove(List, Self)	' Remove the sprite from the list of sprites.
		Delete ThisSprite 		' Free the sprite.
		
	End Method


This would free the sprite when the user does this:

ThisSprite.Free()


Makes sense right?


But if I understand Blitzmax right, then what I ACTUALLY have to do is this:

	Function Free:Sprite()
	
		ListRemove(List, Self)	' Remove the sprite from the list of sprites.
		Return Null
		
	End Method


Which forces the user to do this instead:

ThisSprite = Sprite.Free()


Which is really really retarded.


The alternative is to make them do this:

ThisSprite.Free()
ThisSprite = 0


Which is also stupid.


Surely there's a better way to do this.

Can I do this in a method?

Self = Null

And will that make ThisSprite = Null when I do this?

ThisSprite.Free()


???


sswift(Posted 2006) [#2]
Hm... I think I know how to handle this. I don't like it though.

The key is imagining how the user might use the system. Let's say they want to have bullets, and they want to use sprites for them.

So they have a list of bullets.

When they make a bullet, they say New Bullet, or Bullet.Create or whatever to make the bullet. Then they add that bullet to the list.

They might have a temporary variable to do this, but that will get overwritten or get deleted when it goes out of scope, like when they leave a function. So the only pointer left to worry about for the bullet would be the one in the list.

Now, at the time they create the bullet, they create a sprite with my system. And they assign the pointer to this sprite to one of the fields in the bullet they created.

When the bulelt hits something, they remove it from the list. This removes the pointer to the sprite as well, so the sprite is freed. Or is it?

The sprite still has a pointer in the internal sprite list. So now, the user has lost their pointer to the sprite, but the sprite system still has a pointer, so the sprite still exists.

So I need a Free() method in the sprite which removes it from the internal linked list, but does nothing else.

The thing I don't like about this, is it's really hard to keep track of what is going on. And when the user calls Free() they can't expect the sprite to dissapear, because they're still holding onto a pointer to it. Only when they decide to let go of that pointer will the sprite finally dissapear.

I don't see how this is better than procedural programming. It is harder to understand, and while people say it results in less bugs, I say it will result in bugs which are harder to find. Instead of a crash occuring when someone forgets to free something, the program will simply misbehave. And you'll be left scratching your head why the sprite, whose pointer you set to null, is still on the screen... because you've forgotten to call Free() because why should you expect to have to call Free() on an object when you never have to do that for any of your own objects?

OOP people are crazy.


FlameDuck(Posted 2006) [#3]
Surely there's a better way to do this.
Don't use ThisSprite at all. Just remove the sprite in question from the list. There is no point in having a "Free" method (much less a function) in your sprite object.


FlameDuck(Posted 2006) [#4]
So now, the user has lost their pointer to the sprite
No he hasn't. He still has whatever pointer he used the first time he created a bullet.

I don't see how this is better than procedural programming.
How would the situation be different using procedural programming?

And you'll be left scratching your head why the sprite, whose pointer you set to null, is still on the screen...
Now you've lost me completely. Why would the sprite still be on the screen, if its been moved from the list that draws it?


sswift(Posted 2006) [#5]
"Don't use ThisSprite at all. Just remove the sprite in question from the list. There is no point in having a "Free" method (much less a function) in your sprite object."


Here is an exmaple:

The user creates a player:

ThisPlayer:Player = New Player
ThisPlayer.Image = LoadImage("ship.png")
ThisPlayer.Sprite = Sprite.Create(Player.Image)


When they call Sprite.Create, the sprite is added to a linked list in the sprite system after it is created. This is so the sprite system can loop through all the sprites to draw them.


If the player is killed, or the game ends, and the user wants to delete the player, presumably they do the following:

ThisPlayer = Null

Doing this causes the image that was loaded to be freed, because there is no longer a reference to it.

But it does not free the sprite. Even though the pointer the user had to the sprite in ThisPlayer.Sprite is now gone, the sprite is still in the internal list of the sprite system, and still being drawn.

The only way to stop it from being drawn then is to make them do this:

ThisPlayer.Sprite.Free()
ThisPlayer = Null

The sprite free method would remove the sprite from the internal linked list. And with that reference gone, erasing the pointer to the player frees the player instance, which deletes the pointer to the image, which is freed, and the last remaining pointer to the sprite, which is also freed.

The thing is, if they forget to do ThisPlayer.Sprite.Free() and just do ThisPlayer = Null, then the sprite for the player will continue to draw, and the image it uses will not be freed.

And they will probably have no idea why this is happening. No crash, means no hint as to where the problem is.


"How would the situation be different using procedural programming?"


In procedural programming, you would not be conditioned to accept that when you delete a pointer to an object it is freed automatically. If you create a sprite, then you know it needs to be freed later, and that there must be a function to do it.

But with Blizmax, you have no reason to expect that after you create a sprite, there will be hidden pointers to the sprite that are created to keep track of it that will prevent it from being deleted when you erase the pointer to it.

So it is inconsistent. Sometimes when you want to free an object, you just set the variable pointing to it to be Null. And sometimes you have to call a Free() method.

The only way to know which is to actually look at the type in question, and see if a Free() method is provided or not.


I dunno, it just seems sloppy to me. Instead of creating and deleting stuff we're suppose to create stuff and then assume it gets deleted when we forget about it. It is hard to think in those terms. Instead of just deleting it, and getting a crash if I accidentlaly deleted it elsewhere, now I have to think about where else in my code I might have a reference to the object and come to a decision about whether at this point all references have been removed. That takes a lot more thinking.


Michael Reitzenstein(Posted 2006) [#6]
It's a valid problem. I think what you are thinking of is a weak reference to the object in your library, so if the object is no longer accessable to the main program, you detect that and remove it from the list.


N(Posted 2006) [#7]
I've noticed a pattern with newcomers to BlitzMax from B3D.

First, they say "Holy crap all this is unneccessary."

Following that, they ask why there isn't a delete or something similar. They all eventually be made to look like fools with no understanding of what they're talking about.

Afterwards, they realize how they were wrong and adopt the techniques commonly used in OOP (preferably the good ones, but there's no helping it if they adopt bad ones too like I did [at first, anyways]).


FlameDuck(Posted 2006) [#8]
If you create a sprite, then you know it needs to be freed later, and that there must be a function to do it.
You're confusing object orientation with automatic memory management.

So it is inconsistent.
That's because you're using it inconsistently. And incidently you should never *need* to set any pointers (we call them references, because they're immutable) to Null. Having to null out objects is a clear indication that your logic is flawed somehow (it would translate to odd bugs in procedural languages aswell).

The proper way to address this issue (IMHO) would be to make the sprite objects the central objects, rather than just a conveniant way to render everything. So your Player object would have a Sprite object member (or extend the Sprite object, depending on your approach), rather than a disjoint seperate Sprite object.


Dreamora(Posted 2006) [#9]
On the Free method:

I have these methods as well.
What it mainly does is removing the instance it is called from from the type global list.

For example the following:

Type Sprite
  global list:TList

  method new()
    if list = null
      list = new tlist
    endif
    list.addlast(self)
  end method

  method free()
    ' Do some stuff needed at cleanup
    list.remove(self)
  end method
end type


This way it is removed from the system if you have no further references to this sprite.
Thats exactly the same way the old blitz versions are handling types behind the scene ...


sswift(Posted 2006) [#10]
"The proper way to address this issue (IMHO) would be to make the sprite objects the central objects, rather than just a conveniant way to render everything. So your Player object would have a Sprite object member"

Uh, does my code above not show a player object with a sprite object member? Player.Sprite is a field of type Sprite, which points to a sprite object.


"(or extend the Sprite object, depending on your approach), rather than a disjoint seperate Sprite object."


That won't work, because then there is no way for the sprite system to render all sprites.

Also, there are motions to consider here at well... The user will be able to spcify that a sprite should move from point A to point B over a period of time. Doing those means creating a movement type and sticking that in a list. It executes until the user tells it to stop, or it automatically stops after a period of time. But it too will have a link to the sprite, so even if the user frees the player, and frees the sprite, the sprite will still exist and be animating if they haven't freed the motion that needs it.


"Following that, they ask why there isn't a delete or something similar. They all eventually be made to look like fools with no understanding of what they're talking about."

I know why there is no Delete. I just don't agree with the concepts behind it.

If I have a stack of papers, Delete is like removing one of the papers from the stack, and then throwing it in the trash bin. I clearly don't want it any more.

The way BlitzMax works, if I have a stack of papers, I remove one, and then toss it over my shoulder. It falls somewhere on the floor, and I don't know where it fell, because there's a lot of other papers there. Eventually, a garbage man comes by my apartment and picks all the papers up off the floor for me and then puts them in the trash can.

Now, if you saw two people doing this, which one would you think was crazy?


Dreamora(Posted 2006) [#11]
This comparision is wrong and I'm sure you know that.

The correct comparision would be, that you lay the paper asside and notify the paper man, that you do not need it anymore. If no one else in your company does need the paper anymore, he will come and take it with him.

Thats exactly what the GC does.

It would be plain stupid to throw the paper away although other in the company still need the paper, but that is exactly what would happen if you could simply the delete the object.


But BM GC isn't perfect yet because it does not have root references so you can have whole cyclic structures alive although you do not have them saved in any of your structures anymore. If that gets added, the GC hopefully will become even more powerfull.


sswift(Posted 2006) [#12]
"The correct comparision would be, that you lay the paper asside and notify the paper man, that you do not need it anymore. If no one else in your company does need the paper anymore, he will come and take it with him."


But you don't notify the paper man that you don't need it anymore. You simply forget where the paper is. The paper still exists, you just don't know where it is in the filing cabinate.

But the paper man knows where it is.

The paper man, rather than having a list of all papers that need to be taken to the trash, (which you add to when you call him and tell him you need a paper to be thrown away with Delete) has a list of all the papers that are currently in use, and how many people are using them.

If you forget where a paper is, his list is mangically updated by some unknown mechanism, reducing the number of people who still need that paper by 1. You don't TELL him he needs to update his list. He just does it when you forget where the paper is.

For example:

Function Blah()
Local ThisSprite.Sprite = New Sprite
' Do stuff with sprite here.
End Function

I have done nothing to delete the sprite, nothing to "cal the paper man" or inform him I no longer need the sprite, but when the function exits, I forget the value stored in ThisSprite, and through the power of magic, the paper man knows I have forgotten and decides to do something about it.


"It would be plain stupid to throw the paper away although other in the company still need the paper, but that is exactly what would happen if you could simply the delete the object."

Okay, I can see that argument.

On the other hand, imagine you are at this company, and your filing cabinet is full, and you don't know who all these random papers belong to. The only guy who knows is the paper guy, and he's not telling!

Now imagine you are at the company and you throw a paper away that someone needs. Witin a few moment, your boss comes in and starts chewing you out because he needed that paper! That is like a crash, and it tells us we've done something wrong.

In the first instance, tracking down who all these papers belong to so we can delete some of them is a nightmare. There's no way to tell who they belong to, and who is creating this mess. In the second instance, you usually know almost immediately when someone has screwed up and thrown something away that others needed. And there's no magical paper man who like santa knows who needs a paper and who doesn't because they've simply forgotten where it is.


There is an alternative here though.

Instead of having a magical paper man who magically knows who remembers where the papers are, you could do the following:

Have each person that needs a paper it sign it out. When they don't need it any more, instead of simply forgetting where it is, they sign it back in.

So instead of doing this:

Function Blah()
Local ThisSprite:Sprite = New Sprite
End Function

You have to do this:

Function Blah()
Local ThisSprite:Sprite = New Sprite
Delete ThisSprite
End Function


But Delete() no longer deletes the sprite immediately. Instead it simply decrements the reference counter by 1. That is like signing the paper back in.

In the first example, you simply forget about it. The paper DOES get signed back in, but it is the paper man who does that behind your back when he realises you've forgotten about it.


There's another bit to this puzzle though. Signing things out.


Take this for example:

ThisSprite:Sprite = New Sprite
Blah(ThisSprite)

Function Blah(ThatSprite:Sprite)
End Function


Here, you are not signing the sprite out. That magical paper man is again up to his old tricks. He sees you (ThatSprite) looking at that paper that ThisSprite was just looking at, and knows that two people now need the paper. So he signs it out for you.


I don't know how to solve that problem in a good way. And maybe having the magic paper man sign things both in and out for you is more consistent behavior. But it still doesn't feel right. I have to tell the paper man when I want him to hand me a paper in the first place, so why shouldn't I have to tell him I want him to destroy that peice of paper as well?

If we were really going to be consistent, why does New exist?

If I say:

ThisSprite:Sprite\X = 10

Shouldn't that be enough to tell the paper man that I need a new Sprite, and that the pointer to it should be assigned to ThisSprite?

If we're going to be consistent, then if I don't have to tell the paper man to delete things, then I shouldn't have to tell him to create them either.


sswift(Posted 2006) [#13]
If you don't want to read all that crap, just read the bit at the end starting with this:


"There's another bit to this puzzle though. Signing things out."


Cause I think I might be on to something there. :-)


Dreamora(Posted 2006) [#14]
With nullifying you notify the paper man.
Thats because whenever a reference to an object is cut, the reference count is lowered by 1 so the GC knows that you layed it away.

Perhaps thats the problem.


Automatic creation of objects would be a nice thing, but not a must have.
I don't know of any language so far that is able to create referenced objects on its own. I don't know the actual reasons thought but I would assume its for consistency and correctness reasons ...


Gabriel(Posted 2006) [#15]
On second thoughts.. bad idea


LosButcher(Posted 2006) [#16]
Isnt the point of the magical paper man (or the Garbage Collector as the rest of us call it :p ) that its "magical"? So you dont have to worry about memory handling. In your scenario, what happens if you forget to delete when you dont need it anymore? Like:
Local myObject:Object
myObject:Object = new Object1()
myObject:Object = new Object2()

In BlitzMax, the memory allocated for the instance of Object1 is freed, as it has no references anymore.


Dreamora(Posted 2006) [#17]
Yeah but in my scenario, this means that you got a new paper and as you can only read one paper per time, the old paper is layed aside -> GC :)


LosButcher(Posted 2006) [#18]
OK... then I dont get what you are trying to say. Do you want to be able to get the paper back after it is layed aside?


Dreamora(Posted 2006) [#19]
No I just rewrote sswifts paper scenario to the way it actually is.

I myself have no problems with the fact that there is a GC nor how it works, as it is in a similar way as any other language I use (Eiffel, C#).
Its just a "understandable example" of what the GC does :-)


LosButcher(Posted 2006) [#20]
OMG sorry Dreamora... I got you confused with sswift.


Chris C(Posted 2006) [#21]
ROFL