Get an Object's Ref Count?

BlitzMax Forums/BlitzMax Programming/Get an Object's Ref Count?

Gabriel(Posted 2006) [#1]
Hi,

Is it possible to get the internal reference count of an object? I've come across a number of situations where I'd love to be able to know when I'm down to 1 ( or sometimes 2 ) references to an object. I can't find anything to do it at present, but maybe there's something I've missed. If not, perhaps it could be added?


Chris C(Posted 2006) [#2]
Ya know I dont know *exactly* why I'm saying this but maybe if you need the ref count maybe you are doing somthing wrong ???

(seriously not taking the preverbial its a fundimental GC issue....)

If you can give an example of how you cant do some end result without an objects ref count I'd be interested!!


Gabriel(Posted 2006) [#3]
Well considering how many times you've been of the opinion I was doing something wrong, I'm surprised I get anything done at all ;)

It's quite simple. I want my game engine to have an internal reference to all game objects, but I want the game ( not the engine ) to be able to destroy an object without explicitly free'ing it. If the engine has a reference, and the game doesn't ( nor should it ) have access to those internal references, the object won't be collected. If the engine can say "oh, just 1 reference to that, let's destroy it" then my game engine can manage the objects itself.


gman(Posted 2006) [#4]
ive been thinking along this topic a bit and i have something that may (or may not) help you. HandleFromObject returns an integer handle. you can store this handle whereever you want and as many times as you want without affecting the internal reference counter. to release the reference you simply Release() it when you want to. there is no need to even keep the initial object variable around as you can always get to it via the handle until the handle has been released.

as long as your careful, the biggest drawback i see is that internally BMAX stores handles in a hashtable. to get the object from a handle it has to find it in the hashtable.


Gabriel(Posted 2006) [#5]
I'm not entirely sure I follow you. In another ( technically unrelated but it's around the same issues ) thread you said

the handle returned by handlefromobject is managed by the GC. internally it calls the BBRETAIN() function which increments the reference counter.



Besides, the game engine doesn't *know* when I want to "Release()" it unless it can get a reference count. So I would be back to the game having to explicitly declare that it was done with the object, so nothing gained.

Not to mention that the game engine needs a real handle, as it's using it to do lots of engine-y things involving fields snd methods.

I want the game to not have to explicitly destroy objects or declare that it's finished with them. I want the game engine to monitor the reference count, and when the only reference it has is the internal one, bin it.


gman(Posted 2006) [#6]
yes the handle is managed... but if you only create one handle with HandleFromObject() and store the handle whereever you need it instead of an object reference the reference counter will always be one. whatever has the handle can still get to the object and its props/methods via HandleToObject(). in this way you can manually manage the reference count. like is said... maybe or maybe not useful in your situation :) it would require a framework that counts where you use it. the other thread you mentioned is actually what got me thinking about this.


Gabriel(Posted 2006) [#7]
Ok, then no, I'm not talking about the same thing as the previous thread. Sorry if I was misleading. I just think being able to pull up the number of references remaining could be very useful for managing your own objects.

I mean, what's the good of having an automated garbage collector if I'm having to manually destroy everything just like I was in Blitz3d. If the engine doesn't need an internal reference of it's own, it's great, If the engine's internal reference is the only one needed, it's great. But if you need both, you can't do it automatically. Not without the refcount anyway.


Paposo(Posted 2006) [#8]
Hello.

Is posible use the new() and delete() method for control the instances ussing a global field in type.
In the new() method increment global field. In the delete() method decrement it.
delete() is called for the GC when the object is discarded.

Sorry my bad english

Bye,
Paposo


Grey Alien(Posted 2006) [#9]
that's a good point, but I guess Gabriel wondered if he can access the same ref count directly that BMax uses for the GC.


Michael Reitzenstein(Posted 2006) [#10]
Gabriel wants to hack together weak references, which is an entirely valid thing to want.


Gabriel(Posted 2006) [#11]
Paposo : No, that doesn't work. New and Delete() are not related to the reference count, just creation and destruction.

Michael : Yes, that's it. Sorry, I'm a completely untrained programmer, so I usually don't have the correct terms for things. But yes, I've just looked up weak references in a java book, and that is exactly what I want.


Dreamora(Posted 2006) [#12]
There is no BM internal way for what you want, most likely to prevent people messing with it (and because even in a managed system, Engines or SoftwareSystems are notified when something isn't needed anymore ... what you want is a GC ontop of a GC ... sort of not such a good idea as double management will bring up some kind of problem earlier or later but at least for sure)
But there is the unofficial way, thought it is, as always, risky to use and might change at any time:

Type Test
 Field c:Int

End Type

Global t:Test = New test

Print "RefCount: " + Byte Ptr(t)[-4]

Local l:Tlist = New tlist

For Local i:Int = 1 To 10
 l.addlast(t)
Next

Print "RefCount: " + Byte Ptr(t)[-4]



gman(Posted 2006) [#13]
@Dreamora - :) i was trying that last night, but was going forward when apparently i should have been going backward. nice to know i was completely loopy in my thinking.

i also managed to create a quick cpp function using the definitions from blitz.h that would return the refs property. weird though it never returned the correct value.


Grey Alien(Posted 2006) [#14]
aha so the ref count is stored 4 bytes (1 long int) behind the main pointer, cool.


Dreamora(Posted 2006) [#15]
Jupp
But I'm not the one that found that hack. there is a codearch entry when I remember correctly with that and other hacks (like get the class as well without casting)

But as mentioned: Be ready to find a new solution if the behavior and rights of pointers within BM is changed again to make it more typesafe and secure.

Until BRL decides to add a "GetReferenceCount:Int()" method there is nothing you can do. (adding it yourself most likely isn't a solution as well. There are some modules that base on the byte ordering etc within :Object and those will fail badly if you modify it by adding a new functionality. Found that out the hard way when adding clone code posted here on the module tweaks board)


Gabriel(Posted 2006) [#16]
Nice find, thanks Dreamora. As you say though, it's only a stopgap solution and may be rendered useless at any time. So a GetReferenceCount:Int() Method would be nice.


Paposo(Posted 2006) [#17]
Hi guys.

In many languajes de getReferenceCount() not exist. The control of references implies programmer have a god thecnic. If you need getReferenceCont probably you need revise your code.
The need of count references is not habituel

Not offend you! :-)

bye,
Paposo


Dreamora(Posted 2006) [#18]
Thats right.

The main problem is: Any engine / system that uses an own ref count based thing has to check for the ref count manually as it does not know when a ref is dropped. So in the end it will take care that at least double the amount of work is put into the object management.
And as mentioned above: As any engine / system will offer a creation functionality to get objects, it also should a destruction functionality. Why I say so: The user has to implement a cleanup method anyway as he needs to break up cross references etc. So calling your destructor is no work or something "unnatural" but your idea of doing double checking is something extremely unnatural within a fully managed environment


Gabriel(Posted 2006) [#19]
In many languajes de getReferenceCount() not exist. The control of references implies programmer have a god thecnic. If you need getReferenceCont probably you need revise your code.
The need of count references is not habituel

Not offend you! :-)

That's because most languages ( eg: Java, Python ) with a garbage collector ( or like C++ where you can use one as a library ) permit you to have a refefence to an object which is not counted by the garbage collector. Or weak references, as I now know they're called. If BlitzMax had this ability, I wouldn't need to hack it in myself, and hence wouldn't need the reference count. But it doesn't, so I do.

Don't worry about offending me. Loads of people enjoy telling me how I should and shouldn't program, so you're definitely entitled to a turn ;-)


Gabriel(Posted 2006) [#20]
Bump. BRL? Can we have an official reference count function/method? Or failing that, a proper implementation of weak references so we don't have to hack it in ourselves?


Jesse(Posted 2006) [#21]
I am sorry if I miss understand this but wouldn't Tlist.count() be the same thing?


Gabriel(Posted 2006) [#22]
Nope, it wouldn't. That tells you how many different objects are on a TList. I want to know how many internal references the GC has to one given object.

In other words :

Type Thing
End Type

Global A:Thing=New Thing
Global B:Thing=A
Global C:Thing=B


The GC has three references to that one object.


H&K(Posted 2006) [#23]
Really? You can see why Im asking so please explain why th GC has three and not two

As far as I can see there are only two things that can point at anything, so why would the GC say that three things are pointing to "thing"

Edit: I am so glad that it was a typo, cos if not we were entering another of those "I thought I knew this but I dont zones" ;)


Gabriel(Posted 2006) [#24]
Sorry Typo. Supposed to be C=B. ( And fixed now. )


tonyg(Posted 2006) [#25]
...and they'll be no references as those variables are declared as int when GC only manages objects.
e.g. Need A:Thing = New Thing
Sorry to be pedantic.


Gabriel(Posted 2006) [#26]
I wish I'd never bothered showing Jesse what I meant with you guys on my case :P

Example now fixed ( probably. )


gman(Posted 2006) [#27]

Really? You can see why Im asking so please explain why th GC has three and not two


the reason there is 3 is because there is always 1 for the original object. if there wasnt, the GC would kill A.


H&K(Posted 2006) [#28]
Errr no Gman the reason there wasnt 3, was because Ganriel had typed the example wrong .(Originaly there was only 2)


Gabriel(Posted 2006) [#29]
.


H&K(Posted 2006) [#30]
I still dont get the problem.

If I wanted to know how many of each object I had I would just count them. That is I wouldnt use = on object to object, I would always use Object.PassPtr and simply count many times each objects ptr is passed. (Ok I would need a Tlist of all the objects, but if I was bothered enough to want to count the refferences I would already have a tlist I suppose


Gabriel(Posted 2006) [#31]
I'm not asking you to get the problem.

I've explained more times than I should have done already, and what you propose doesn't solve the problem.


Brucey(Posted 2006) [#32]
I would need a Tlist of all the objects

Further increasing the reference count ;-)


H&K(Posted 2006) [#33]
@Gabriel,

I didnt think it would solve it otherwise you would have already done this. I was just trying to get the disscussion going again. Oh Um


Gabriel(Posted 2006) [#34]
Heh, well anything that keeps the thread bumped is fine by me, but I'm about out of energy to discuss it. Half the people don't get why I want it, a few think it's already there ( not including Dreamora's hack, which is valid, but can't be relied upon long term ) and some are absolutely certain I'm a moron for needing or wanting it.

On the plus side, I'm fairly sure one person understood what I want, knew it wasn't currently ( reliably ) available and thought it was valid to want it.

But while I wouldn't have any objection to explaining over and over if it would go somewhere, none of the people in this thread can give me what I need even if I convince them, so it's kinda wasted energy. I'm just hoping it attracts the attention of Simon or Mark and that they can see a valid purpose for it. On the plus side, it should be fairly simple for them to implement, it's just a question of how restricting it might be for them in the long term.


H&K(Posted 2006) [#35]
Ho ho,

Ive just read through again, and yes I think I do see your point.

Whilst it would be possible for you to implement a system to keep count of the refferance count. This would be a duplication of processing use as BMax has already done this internaly.

I see the code Dream posted, and I think I remember reading it as well, I think it got attacked by skid (I think this was the same one, there was a code post, and a thread here as well ). Because when all said and done its "Hack"

However. This is not like when you could change the spectrum font with a Poke. Everyone knew about it, everyone used it, and It would have gone wrong if there realesd a Spectrum version with a different address sytem for the font. Which is where it differs here. Its a valid hack, any code you produce with it will still run even when or if it is changed internaly. (Im not saying it will work if you recompile it, Im just saying and code alredy produced will work).

Now if BRL do change this system, what are they going to change it to? It can only change really in two ways, first the offset from the object could change. (Well youre going to store the ofset as a const anyway, so thats no problem). Or second, this are going to store the information consecutivly for all objects. (But in this case the still have to keep a pointer to point from the object to the number of instances (Or vise versa), and thats easyerky Hackable.

Ultimatly I agree with you, if the information is there inside the engine, and it really would only take a "Object.Refcount", I dont see why BRL dont just add it in. Come on half the command we use are undocumented already.


Gabriel(Posted 2006) [#36]
Whilst it would be possible for you to implement a system to keep count of the refferance count. This would be a duplication of processing use as BMax has already done this internaly.


Moreover, it's only possible at all if it's a self-contained program. If it's a module, intended for other people to use, you can't prevent them from making new references without going through the BlitzMax constructor of a copy constructor. IE: A type method which returns the same object but increases the reference count internally. So he just does a little a=b and now your whole reference count goes astray. The module thinks it's dead, but Joe T. User still has a reference to it. Granted Joe T. User should do what the documentation tells him to do, but it's forcing him to adhere to principles which are not generally part of the language just so that I can keep the reference count accurate.

And I agree that - as far as I can see - there should be no reason for BRL to ever need to do anything drastic with the reference count, so it shouldn't be at all difficult or restrictive to add this.


H&K(Posted 2006) [#37]
Ive just thought about this some more.

Lets say I want a List of specific objects that auto creates, (al la B3d). Which there is adivce somwhere about from mark or skid.

Now when the last instance of the object is released it still would not be eligable for GC because the Tlink member would still by there.

I would at some point want to destroy the Tlink.
So here is a rephrase of the question

How can I find out if the Ptr in a Tlist is the Last refference of an object?

I know this is slightly different from what you asked. But we can all see that the solution would be the same. Yes?

And, (appologies if I still havent understood the problem), if I have understood the problem, this is a situation that everyone can asociate with.


Gabriel(Posted 2006) [#38]
I know this is slightly different from what you asked. But we can all see that the solution would be the same. Yes?

Yes it is, and while you're right that it's not the question I asked, it's precisely the problem I'm trying to solve. I'm glad to see that someone else can see that it's a problem that will crop up pretty easily if you're managing something quite big like a game engine and you need these internal lists for proper control of the engine and good speed.

As Michael pointed out earlier ( and was news to me at the time, though I've since read a little on the subject ) other languages which have BMax-style memory management have "weak references" which essentially allows you to keep a reference ( like the internal list you refer to ) but those references are not counted by the garbage collector. So your object will still be destroyed when only "weak" references survive and all you need to do is make you that you don't access any of these references after it's been destroyed.

So if you can get the reference count, as you say, you can do this yourself. You have to parse through your list anyway, or you wouldn't need it. So when you parse the list, just check if your refcount is 1 ( or 2 if you keep it on 2 lists, whatever ) and then you know that it can be destroyed now. When you put this sort of thing into a module, the person who uses the module doesn't need to know what you're doing and they can handle their BlitzMax objects just as they normally would and they will still be automatically destroyed when they are finished with them.


Dreamora(Posted 2006) [#39]
Sad thing is that GCSuspend and GCResume do not really what one might think: stop the memory system, otherwise I would have been able to say: Yeah since 1.22 its possible to create weak links.

So here is the "no one knows how long it works" solution:

Rem
	Fake WeakReferences - BM Way :-)
End Rem

SuperStrict

Type TType
	Global list:Tlist = New tlist
	Field id:Int
	
	Method Init()
		' Lowers the internal ref count by one so that if only list reference is left, it is removed.
		list.addlast(Self)
		Byte Ptr(Self)[-4] :- 1
	End Method
	
	Method Delete()
		Print "TType ID: " + id + " destroyed."
	End Method
End Type

Global test:TType = New TType
test.id = 10


test.Init()

Print "RefCount before: " + Byte Ptr(test)[-4]

test	= Null	' no further reference left according count

GCCollect()

End



Gabriel(Posted 2006) [#40]
Ah, very clever. I hadn't thought of actually going in and changing the reference count myself ( figured it might not be safe, to be honest. Not sure how the GC operates and was afraid I might find myself modifying it as it was accessing the same data and kablooey! ) but that's a very clever way of doing it, providing nothing changes.

With that as added as an official field, so that we knew it wouldn't be removed or change, destroying many months of work, it would be perfect.


Gabriel(Posted 2006) [#41]
Apparently my reply here got lost when the site had problems so here we go again.

Since Mark and Simon don't seem to have any interest in this, I'm going to have to assume it won't ever be added. At least I can't wait around forever without any kind of encouragement at all. So I suppose I should start trying to find the least objectionable ( and slow! ) workaround.

Ok, so here's my first effort. It has a bit of overhead, so it'll be slower than it should be. A wrapper type. Something like this :

Type tInternalType
   
   Method A()
      Do Something
   End Method

   Method B()
      Do Something
   End Method

   Method C()
      Do Something
   End Method

End Type

Type tExternalType
   
   Field InternalType:tInternalType
   
   Method New()
      InternalType=New tInternalType
   End Method

   Method A()
      InternalType.A()
   End Method

   Method B()
      InternalType.B()
   End Method
   
   Method C()
      InternalType.C()
   End Method
   
   Method Delete()
      InternalType=Null ' PLUS DESTROY ANY OTHER INTERNAL REFERENCES WE MIGHT HAVE TO THE OBJECT
   End Method
End Type


Ok, it's another ( completely unnecessary ) layer of abstraction and it doubles the function call overhead ( which I'm not very happy about at all ) but I think it gives me what I'm after. Anyone have any better suggestions?


Gabriel(Posted 2006) [#42]
No thoughts on the viability of this?

And surely the "you're doing it all wrong" people can show me a much more "correct" way of achieving the same result? XD


gman(Posted 2006) [#43]
so are you trying to have only one reference to the internal object and then you have multiple references to the external object?

also, no need to wrap up all the methods of the internal object unless there is a reason to. you can just call the methods on the internal object directly since its exposed.

if you are looking for weak references or going to make a system that manually counts references i still think handlefromobject/handletoobject/release is the way to go. you can pass the integer handle around all you want without increasing the internal reference counter and is already in the BMAX API. if you are manually counting, when you hand out the handle, increase the counter. when whatever has the handle is done, decrement the counter and cleanup if necessary.

here is a simple weak reference example:
Rem
weak references via HandleFromObject()
EndRem

SuperStrict
Framework BRL.Basic

Type tObject
	Field objHandle:Int=0

	Method New()
		objHandle=HandleFromObject(Self) ' at this point there will be two refs
	EndMethod	
	
	Method A:String()
		Return "tObject.A()"
	EndMethod
	
	' a mechanism is needed to release the handle
	Method Destroy()
		Release(objHandle)
		objHandle=0
	EndMethod
	
	Method Delete()
		Print "tObject gone!"
	EndMethod
EndType

Type tOtherObject
	Field tObjectHandle:Int=0
	
	Method Set_tObjectHandle(obj:tObject)
		tObjectHandle=obj.objHandle	' this does not increase the reference
	EndMethod
	
	Method Get_tObjectAString:String()
		' probably want to know as it may be a programming error
		If Not Is_tObjectValid() Then RuntimeError "invalid handle"
		Return tObject(HandleToObject(tObjectHandle)).A()
	EndMethod
	
	Method Is_tObjectValid:Int()
		Local bIsValid:Int=(tObjectHandle>0) ' first check if the ref is 0
		' if its good, now check the object
		If bIsValid Then
			bIsValid=(HandleToObject(tObjectHandle)<>Null)			
			If Not bIsValid Then tObjectHandle=0 ' set handle to 0
		EndIf
		
		Return bIsValid
	EndMethod
EndType

GCCollect()
Print GCMemAlloced()

Local tObj1:tObject=New tObject ' we now have 2 references all contained in this instance
Local tOther1:tOtherObject=New tOtherObject

GCCollect()
Print GCMemAlloced()

' set the weak reference
tOther1.Set_tObjectHandle(tObj1)

Print(tOther1.Get_tObjectAString())

' even though tOther1 has the handle, it doesnt affect the ref count so we can
' destroy the object.
tObj1.Destroy() 
tObj1=Null

' NOTE: for some reason this allows the GC to work.  really odd behaviour.
Local objptr:Byte Ptr=Byte Ptr(tObj1)
objptr=Null

' NOTE: i think i found a bug in GC while writing this so see that post for a simple example of
' "tObject gone!" being only output when the above two lines are run
GCCollect()
Print GCMemAlloced()

' now try to call this time and boom!
Print(tOther1.Get_tObjectAString())



Gabriel(Posted 2006) [#44]
so are you trying to have only one reference to the internal object and then you have multiple references to the external object?


I'm trying to allow the engine to have multiple references ( internal ) to an object for it's own purposes, but as soon as all the references the user has ( external ) are gone, destroy the object and all of the engine's internal references ( of course, or it'll be crash city! )

also, no need to wrap up all the methods of the internal object unless there is a reason to. you can just call the methods on the internal object directly since its exposed.

Right, they're not strictly required, but I wouldn't want the user to know that the internal objects exist. I can't make them private but at the same time, I don't want them to be used if possible.

Thanks for your example. It's complex and I'm gonna have to take a long hard look at this to see if I understand it and if it will work for what I need.


Gabriel(Posted 2006) [#45]
Ok I've looked at it, and it doesn't do what I need. It requires a destroy function to be called. I can already do it easily with a destroy method without needing any reference counting or weak references. The entire reason for wanting weak references is so that objects don't *need* to be manually deleted. That defeats the purpose of a language which has automatic object management and garbage collection.

What I'm trying to achieve is - like my most recent example - a system where the object is destroyed *automatically* as soon as the last reference the game ( NOT the engine ) has is gone.


Gabriel(Posted 2006) [#46]
BUMP!

If nothing else, an official reference count command or method or whatever would be invaluable for debugging complex structures with multiple levels of inheritance and a handful of cyclic references. I'm using Dreamora's hack for that right now, but all the time it's a hack, it could be killed off at any time.


Gabriel(Posted 2006) [#47]
.