Alternative for weak references?

BlitzMax Forums/BlitzMax Programming/Alternative for weak references?

Aek(Posted 2009) [#1]
I've been using weak references extensively with C++ and Java. Since BlitzMax does not support weak references, I have to find alternatives and I'm facing the following:

1) Create two generic proxy types, one for strong references and another for weak references. The approach works (no resource leaks) but it is pretty ugly due to the packing and unpacking required to get the actual objects from their proxies.

2) Manually call dispose functions. This approach is clean for simple objects like GUI, but is a nightmare for AI callbacks.

3) Use lua, which has both strong and weak references. I have used this approach before in C and has been happy with it. However, I have never use this approach in BlitzMax.

If anybody here has implemented these approaches before and would share me experiences, I would feel very grateful.

Of course, if you know another alternative and would enlighten me, I'll feel grateful too.


Czar Flavius(Posted 2009) [#2]
Could you give some examples of Blitz code that is giving you nightmares?


Aek(Posted 2009) [#3]
Dear Czar,

I am trying to port AI code written in C++ and Java to BlitzMax so I don't have Blitz code yet. However, I'll try to explain the situation.

The program has many actors. Actors subscribe to events from multiple sources. The sources may be other actors or global objects like the system event queue.

To prevent leaks, actors must be unsubscribed from the event sources when they are no longer in use. However, an actor can be in many places in the program so it is not trivial to determine if an actor is still being used or not.

In the original code, the event sources hold weak references to the actors so they can automatically unsubscribe actors once their weak references become invalid.

Without weak references, one has to put a reference count in the actor. But that means one also has to remember to increment/decrement the count. Another way is to use proxy types like I said in the original post but they are very ugly.

Actors are not the only elements in the program with this issue. The GUI elements also have this problem. However, GUI elements are cheap to recreate so they have more workarounds.


Czar Flavius(Posted 2009) [#4]
You could try turning off the garbage collector, or maybe using pointers instead of references to objects.


Aek(Posted 2009) [#5]
Dear Czar,

I don't think I understand what you're trying to suggest. Event handling runs throughout the entire life of the program. Does this mean I will have to turn off the garbage collector for the entire life of the program? Wouldn't that mean I will have to manually manage not just actors but everything else too?

Besides, I think using VarPtr with a user defined type is no longer supported.


Czar Flavius(Posted 2009) [#6]
To be honest, I don't really understand what you are asking. Maybe you should look at why you are porting the code in the first place. It might make more sense to import the C++ into a Blitz program directly (so then you can control the memory however you want C++ side), rather than porting it and risking it not working.


Aek(Posted 2009) [#7]
Dear Czar,

It's getting quite off topic but, the reason for porting is long term maintenance. It is hard to create an AI library that will work for every program. Most of the time, there must be some tweaking and I believe languages like BlitzMax are easier to tweak than C++, especially for new folks on the team.

Anyway, thanks for reminding me that BlitzMax can use C++. I'll check how BlitzMax works with C++ exceptions.


Jesse(Posted 2009) [#8]
I'm not sure if I should be posting here, I am getting into unknown waters here. But Can't you just use the BlitzMax's Lua integrated module to do that? just a curious thought.


Aek(Posted 2009) [#9]
Dear Jesse,

I know about the Lua module. I just wanted to know whether there's anybody here who used Lua to get around the lack of weak references in BlitzMax.

I can do some experiments myself but it is possible that problems won't show up in small experiments. Of course, making large experiments are very time consuming...

Weak references have helped me reduce a lot of code in the past so I don't want to let it go until I'm sure I have no choice. However, it seems like mentioning AI confused many people to think that this is an AI topic. I'm sorry if I did confuse you.


dmaz(Posted 2009) [#10]
why do you need the proxy for strong refs? how does one proxy for weak not solve the problem?


Aek(Posted 2009) [#11]
Dear dmaz,

In my implementation, weak references need to be informed when their object is no longer being used. This is done by the Delete() method of the strong reference holding the same object.

You can not use the Delete() method of the object to notify the weak references because the weak references themselves have a copy of the object.

This implementation will only work if all managed objects are wrapped into references. The same object can not be in two strong references. You also can not store objects directly in any data structure. It is hell ugly.

If you know of a better way that would not require a proxy for strong references, I would be grateful if you can tell me. I can't afford those that rely on undocumented features though.


Arowx(Posted 2009) [#12]
How about using HandleToObject() and HandleFromObject()?


Aek(Posted 2009) [#13]
Dear Arowx,

According to my understanding, HandleFromObject() increments the reference count of the object. It takes ownership of the object, preventing the garbage collector from disposing it.

A weak reference checks whether the object has been disposed by the garbage collector. Therefore, it must not stop the garbage collector from disposing the object.

If my understanding of HandleFromObject() is wrong, please enlighten me.


GW(Posted 2009) [#14]
It sounds like your working with john holland style adaptive agents.
Could you not bypass object references and use a lookup ID/table?
I think like others i'm having difficulty understanding the scope of the coupling.


Arowx(Posted 2009) [#15]
Aek, no need for enlightenment the handle is still reference counted until Release(handle) is called then it is Nulled!

Sounds like GW's idea for id'd objects would work well!


Aek(Posted 2009) [#16]
GW,

If I store objects in a table, it will be alive forever until I remove it from the table manually. However, it is not always trivial to determine when I can safely remove objects from the table.

I guess it boils down to whether the object life time is obvious. With GUI programming, you know you can delete all child windows when user closes the parent window. With most games, you know you can destroy all characters when the player finishes the level. With my stuff, some objects will live much longer than the rest and there's no break point.


Aek(Posted 2009) [#17]
Arowx,

That means the object will be alive until I call Release right? The problem is I don't have an easy way to determine when it is safe to call Release...

I might have a workaround if I can determine the reference count of an object. If the reference count is one, I'll call Release. However, there's no official way to determine the reference count...

OK! Considering all the confusion because of my stupidity of mentioning AI, I'll change my question. How can I know if an object is shared?


Arowx(Posted 2009) [#18]
Aek,

Extending GW's idea ... How about a simple id to map approach?

When an Actor is removed from the map it's reference is nulled but it's id can remain...

To notify the Actor the event system uses the id as a reference to find the actor via the map, Nulled actor id's can then be released from the system.

In fact it doesn't even have to be a map an array of object pointers and indexes could work and be faster, although allocation sizes might be an issue in a dynamic system.

I think this would be a good way to approach it!


GW(Posted 2009) [#19]
Remember that even if an object is still alive from the gc's perspective, the local reference can be (set to) null.
So if an agent is no longer a consumer of a resource (set to null from the perspective of the resource) then that check works out as normal.
If an agent, under natural operation, drops every connection to its (resource)producers, then the gc will clean up the object in the background for you.

If you go with the array based method i mentioned above, you would basically have an array of id's of consumers in each resource.
You loop through these arrays from the perspective of the agent looking for 0 references of its own id. If its not found, then its an orphan.


Aek(Posted 2009) [#20]
Arowx,

Using your example, my problem would be knowing when it is appropriate to remove the actor from the map. You remove the actor from the map when the actor is no longer in use. But, how do you know that the actor is no longer in use?

An actor could be many things, a widget, a part of a game character's brain, etc. There are many places in the program that you will have to search. And you have to do correctly it at run-time. If you remove the actor from the map prematurely, the actor will miss events. If you forget to remove the actor, there will be a resource leak.

If weak references are available, the actor will be null-ed out automatically by the garbage collector when it is no longer in use. If weak references are not available but the reference count can be queried, I can manually remove all actors from the map when its count is one because that means the actor exists only in the map. But none of the two is available which is why I'm asking whether there's alternatives.


Aek(Posted 2009) [#21]
GW,

I think I get what you're saying. It is similar to what the garbage collector does. However, wouldn't that have trouble when two agents are consumers of each other?


Aek(Posted 2009) [#22]
Oh, shoot. Even if I can get the reference count, I'll still have trouble with circular references.


GW(Posted 2009) [#23]
wouldn't that have trouble when two agents are consumers of each other?

I don't see how.
One of them has to expire somehow. This is a circular reference that every GC has trouble with.

There are 2 parts here, that i think are causing some confusion (for me).
#1. is agents naturally falling out of scope in the simulation. how are other consumers and producers to be updated?
#2. is the actual memory of the object getting reclaimed by the gc.

I have a feeling that you can solve your problem, without regard for Bmax GC at all. it will take care of itself.

Are object links one way or two way?

How about this?
If each agent had a flag called 'alive'. this is what gets set when an object is set 'dead'. The object itself null's out all of its own references, consumers of itself ect..
Then on next update of all the other objects, the alive flag is check for each reference it contains, if its false then the local reference is nulled and that object is skipped.
If every object does this check, then hidden references cant occur.


Aek(Posted 2009) [#24]
GW,

Agents can be in more than one simulation at the same time. Each simulation tests different aspects of the agent. The criterion to remove an agent from a simulation varies. Once an agent is removed from all simulations, it is considered unreachable by the program.

In Java, the garbage collector searches for unreachable agents and notifies all weak references to the agents automatically. Before an event source attempts to send an event to the agent, it will query the weak reference of the agent. If the agent has already been claimed, null will be returned and the event source will remove the dead agent so it will not be checked again.

C++ is similar. However, there's no garbage collector to handle circular references so one must also use weak references to prevent circular references.

It is not my intention to trouble this many people... I just wanted to know whether anybody here has gone through the same problem I had and tried the same workarounds I listed in my original post. I also wanted to know how it went for them...

My time is running out so I guess I have to close this topic and start hacking code. I thank everybody here for helping me.