• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

Custom Refcounting

Status
Not open for further replies.
Level 26
Joined
Aug 18, 2009
Messages
4,097
Agent types in wc3 log the remaining references on the object, such as variables or hashtable entries. The agent's id cannot be released until the object is no longer referenced. This grants the advantage it cannot happen that a new object obtains the old id and is still attributed the remaining data/actions.

Now when we have techniques like the return bug, the principle is undercut, causing vulnerability. Same is the case with struct types. They are represented by integers. So if, for example, you make a wrapper type for the native "unit" called "Unit":

JASS:
function abc
    local Unit someUnit

JASS:
function def
    local unit someUnit

The wrapper, top, is good to immediately access the custom information inside Unit without having to load the object from the native unit first everytime. It lacks the refcounting, however. And since the whole struct management is not very type-safe, but also because tracking every reference in jass seems very ineffective, I do not see a hundred percent replacement for this.

Instead I only manually insert addRef/removeRef lines in critical places, where the object could potentially get destroyed while being operated on. But I want to have some uniformity, therefore inject the system in all allocation methods and make a propose:

The object starts with one reference and a flag "destroyed" set to false on allocation. Destroy methods may do some individual stuff like calling events but are to set the "destroyed" flag to false, subtract the reference and check the current ref count if the object id can be deallocated already.

The "destroyed" flag serves as a lock so no further destroy method can be called on the object and may as well be used to prevent other method calls. What you would really want to block are the procedures that actively change something on the object.

JASS:
struct abc
    //integer refs
    //boolean destroyed

    //method deallocate
        //if (refs > 0)
            //return
        //endif

        //...
    //endmethod

    //method subRef
        //refs = refs - 1
        //deallocate()
    //endmethod

    //static method allocate
        //...

        //destroyed = false
        //refs = 1

        //return this
    //endmethod

    destroyMethod Destroy
        //if this.destroyed then
            //return
        //endif
        //this.destroyed = true

        ...custom lines

        //subRef()
    endmethod

The lines with // are auto-injected. Of course the destroy method would have to be marked as such.


A classic example where I use refs is the multi-missile:

A single spell instance spawns multiple missiles, maybe in periods, maybe with a flexible amount. All missiles reference a parent data holder instead of copying it all. So the parent is not allowed to disperse until all missiles have been spawned and all spawned missiles have been destroyed. They need to prolong the parent's life.
 
I don't really get the purpose of this, aside from memory management aspect and garbage collection. And the latter doesn't matter for anyone that has enough knowledge of JASS to use vJass structs, as they will most likely know about leaks and how to avoid them.

Convenience maybe? But then I could just use the onDestroy method to clear all leaks with structs when terminating the struct anyway.


Don't get me wrong; it's probably the only "correct" way of doing garbage collection, but it only works on the JASS level and does not help GUIers (unless we implement this into JASShelper), so it's pretty much pointless.
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
Yesterday, I rewrote triggered unit translations (sliding, leaps etc) and kinda disconnected knockbacks from it because knockbacks do some additional stuff like deactivating the normal unit movement and I wanted them to abort on wall impacts.

You could say Knockback is a child type of Translation but what I actually aim at should work for aggregations as well.

Now the problem that unfolded was that Knockbacks manage a Translation each:

JASS:
class Knockback
    Translation trans

    onDestroy
        destroy trans

    onImpact
        destroy this

    onCreate
        trans = new Translation

So a Knockback instance gets a new Translation on board and destroys it along with itself.

As mentioned, the Knockback dies onImpact. At the same time, Translation possesses a destroy condition of its own. When the unit in question meets its fate, the Translation should be gone as well. This means that there are two possibilities to end a Translation, which should not interfere.

One option to solve this would be to run events in the destroy method of Translation and add listeners for it within Knockback -> additional framework and performance overhead.

2nd option the direct call: Translation onDestroy -> call Knockback. Parents should not know their individual children (sounds gruesome I agree). Well I just introduced a form of hooking, so a child could inject itself into the destroy method of its parent. Still it requires some framework on the Knockback side and means performance loss. In frequent functions you may spend a thought which technique to apply.

In particularly, what annoys me is that you have to differ within the Knockback whether the Translation is still kicking -> kill it, or is already killed -> do not touch. Also, unless you react immediately using one of those options, the id could be newly assigned meanwhile.



I was wrong about starting with one ref and making destroy decrease it btw because if you have multiple places where you not just want to subtract a ref but trigger destroy with its custom functionalities, then you would not know of the other places, if that allocate action with its initial ref was really limited to your scope.

In the above example, Knockback creates a new Translation, wants to hold it. The allocation inside Translation (which of Knockback does not really know btw) would set the refcount to 1, the destroy method of Translation would decrease it to zero, therefore deallocate the instance -> mistake since Knockback still expecting. On the other side, if Knockback adds a ref and subtracts it in its own destroy method, plus destroying the Translation instance, the counter would again totalize to zero although Translation is still seeing the object. Both sides would have to add refs independent from the allocation. I doubt you would want to write that -> insert an extra check in the deallocating method if the object is destroyed.
 
Status
Not open for further replies.
Top