Scripted interface/Component lifetime management


#1

I’m trying to figure out how to manage Component object lifetime in Lua. Please see the pseudocode below. Is this the best way to manage JUCE object lifetime from a scripting language? I know I’m not the only person who’s done this.

My current approach is tracking ref counted pointers to Component objects by artificially incrementing ref counts by 1 on construction: thus, if there aren’t any Lua objects referencing it, it has a count of 1.

When a Lua object pointing to the Component gets garbage collected, it decrements as usual if it has a parent. If there is no parent and its own ref count is 2 (meaning this is the last Lua object referencing this Component), this is a chance to remove one or more objects that are no longer being referenced: it removes all subobjects that aren’t referenced as descendents by another Lua object. E.g.: A non-root Component has children of its own, and its referenced by a Lua object. When the root of this tree is destroyed, the remaining Lua object still points to the control, with its children, but has no parent.

When referencing an already created Component, it just gets ref counted as usual.

// Make Component class ref countable
class ComponentRC : public Component, public ReferenceCountedObject
{
    // ...
};

// Lua interface wrapper used by my LuaCppObject class
class ComponentWrapper
{
public:
    ComponentWrapper() : m_object(new ComponentRC())
    {
        m_object->incReferenceCount(); // keep minimum of 1 count
    }

    ComponentWrapper(ReferenceCountedObjectPointer<ComponentRC> & object)
    {
        m_object = object;
    }

    ~ComponentWrapper()
    {
        if (m_object->getParentComponent() == nullptr && m_object->getReferenceCount() == 2)
        {
            deleteAllUnreferencedChildren(m_object);
            m_object->decReferenceCount();
        }
    }

    void deleteAllUnreferencedChildren(ComponentRC * object)
    {
        // Delete all children who have a ref count == 1 and do not hav a parent with ref count > 1
    }

    static int getChild(lua_State * L)
    {
        int const index = ...; // pop the index from the Lua state
        ReferenceCountedObjectPointer<ComponentRC> rcComp(dynamic_cast<ComponentRC>(m_object->getChildComponent(index)));

        // push object on lua stack
        LuaCppObject<ComponentWrapper>::push(new ComponentWrapper(rcComp));
        return 1;
    }

    // ... other methods, registration boilerplate

private:
    ReferenceCountedObjectPointer<ComponentRC> m_object;
};

#2

There’s a couple of ways to handle this, and all are more or less equal in pros and cons.

What you’re describing makes broad sense. I’ll need to get a night’s sleep before I can think it through properly, but the concept seems sound.

I’ve been doing something somewhat similar in my own code from about six years ago. I’d probably change my design if I was starting over as my wrapper is very intrusive, partly because I needed to support mixing Lua and non Lua managed components in a tree, but mostly because there was a chance that some inexperienced coders would need to work on the code and I wanted to buffer them as much as possible from manual reference counting. The main structural difference between my approach and what you’re outlining here is that I have each level manage the lifetime of its children, so eventually when it’s time to clean up, I simply delete the highest level component wrapper (that is managed by Lua), and let that take care of all components and wrappers at or below that level. The key difference being that addChild() needs to increment the reference count of the new parent component to reflect the existence of the child.

It really all depends what you need.