jucePILS - when to delete components (+ ramblings)


#1

Binding my PILS language to juce, I’m having a problem with component ownership, and I’d like to know how this is handled in pyJuce or similar systems.

First, here is a bit of PILS code that creates a DocumentWindow with a TabbedComponent with two TextEditor tabs - don’t bother with understanding the PILS syntax, most of it is juce calls anyway.

{ .test | :ok ;window j:DocumentWindow ("Testvindue", j:Colour [200 100 50], 7) j:setBounds [100 200 300 400] j:setResizable (1, 0) j:setVisible 1 ; ;tabs j:TabbedComponent 0; ;page1 j:TextEditor () j:setMultiLine 1 j:setReturnKeyStartsNewLine 1 j:setFont (j:Font ("Times New Roman", 30, 3)) j:setText "This is page 1..." ; ;page2 j:TextEditor () j:setMultiLine 1 j:setReturnKeyStartsNewLine 1 j:setText "... and this is page 2." ; tabs j:addTab("Page 1", j:Colour [100 200 250], page1, 1) j:addTab("Page 2", j:Colour [100 200 100], page2, 1) and: window j:setContentComponent (tabs, 1, 0) and: window j:when { j:closeButtonPressed: | :ok tabs j:clearTabs and: window j:removeFromDesktop } }

PILS has two kinds of wrappers for juce Components: Pilsmade and Jucemade. Those created by PILS are really of a special class that extends the juce class, redirecting its public virtual methods so that the PILS programmer may implement them (this is done with the j:when operation at the end of the sample.) Every supported juce component class is subclassed in this way by the interface generator.

A PILSmade component is automatically deleted when both of the following conditions are met: PILS has no references to it, and the component is not (directly or indirectly) on the desktop.

A Jucemade component - such as a TabBarButton created by the TabbedCompponent - is never deleted by PILS.

To trace whether a PILSmade component is on the desktop, the PILSmade wrapper implements a ComponentListener and checks the component state on componentParentHierarchyChanged and componentVisibilityChanged

Well, this works some of the time - but there are problems.

First, the tabs j:clearTabs had to be inserted. window j:removeFromDesktop causes automatic deletion of the pages and the tabs (if they aren’t already deleted). Unfortunately, the pages get deleted first, causing an assertion failure when the tabs is deleted. The tabs j:clearTabs fixes this but it makes the bindings less foolproof than I wished for.

Second, if I run the test, view both tab pages and close the window, everything works. All PILSmade components get deleted, causing the component count to reach 0 which triggers a quit() and shuts down the app with no mem leak. However if I close it without having seen the 2nd page, the app doesn’t shut down, which indicates a component didn’t get deleted. (I haven’t yet found out quite what’s going on, will investigate.)

Third, I’d like the Jucemade wrappers to act as weak pointers. Unfortunately, the ComponentListener class doesn’t have a componentDeleted() method which would be the obvious way to implement weak pointers. This isn’t a problem with the PILSmade components since they are subclassed by the binding generator, so their destructors can zero the wrapper’s pointers - but it is quite possible that a PILS program can keep a reference to a component that gets deleted because the user closes a window - operations on such a dangling reference should fail but not crash, and this is best achieved by some sort of weak pointer.

I guess the makers of bindings for Python and other languages must have faced similar problems and perhaps solved them but I don’t have a cue. Or could these problems be the reason pyJUCE is still at version 0.1?

I’d appreciate very much if you could spare time to write some reflections on this.

I have tried binding PILS to many cross platforms, I got a wxWidgets binding that actually works (I use wxPILS to write the jucePILS bindings generator) but wxWidgets is a mess compared to juce, I also experimented with GTK, VCF, QT, OpenOffice UNO - but got stuck in mud. With juce, I have been making fast progress, I find it vastly easier to grasp than the other systems, but I could use some advice on dealing with component lifetime management.

If juce doesn’t have the stuff needed for foolproof scripting, perhaps we could work together in finding a solution - I’m sure juce applications can benefit from scripting.

(Perhaps PILS could be used to graft an optional reflection interface on juce, which would make it very easy to bind other languages to it. Much of the work is already done in my binding generator.)

BTW in case you wonder, here are the “terms of use” by which PILS will be published (this will happen as soon as I get it to work satisfactorily with a framework less messy than wxWidgets.)

The PILS system as such is freeware but military institutions and the weapon’s industry should expect no cooperation from my side should they decide to use it.
If you want to use PILS for commercial applications, please consult the licensing terms of the framework used by PILS. The wxWidgets framework imposes no legal restrictions on PILS code using it, but other frameworks may.
Beware that the programming system is designed for open-source projects and relies on distributing unobfuscated source code. If you change it, please do not rampage your customers’ standard PILS installations and do not expect my help if your obfuscated code causes problems.


#2

i don’t know anything much about PILS, but I’ve built a scripting language based on Lua into my app, and the ability to create custom user interfaces is part of that scripting language.

How relevant my experiences will be depends on whether:

a) PILS does automatic garbage collection. Lua does, and that influenced my design choices heavily.
b) you are using any pointer type objects in PILS. Lua does not have pointers, though it can do references, and reference counting. All of my c++ code is represented by handles that provide a simple link between a script representation of memory data, and the actual data. This is a Lua feature that I am exploiting.
c) you’re happy to double up your code by creating thin-interface classes between the scripting language code and your wrapped c++ classes (JUCE components or otherwise).

In my case, because Lua does GC, I created an interface layer between Lua and the actual components, and added a reference counting system to the interface. Each component type is then paired with a custom sub-class of the interface to provide an abstraction between the JUCE components and the Lua library implementation. Basically what this means is that no component can go out of scope while at least one ancestor or descendent is in scope. In other words, even if the script is hanging onto only one button instance, an entire form will remain in scope.

If you’re allowing explicit deletion, things are going to be much trickier. I side step that problem by just utilizing the fact that an orphaned component that goes out of scope will be automatically collected, so if I want to delete a button (or even a panel with child controls), I just remove it from the form, and set the variable that references it to nil. At some point later, Lua will collect it for me.


#3

PILS is essentially pure-functional with a reference counted data model. Objects are freed as soon as their count reaches zero. No pointers, only object references, and these are uniquely represented by means of a hash table.

As for thin-interface classes - my interface generator specialises the Juce classes with a mixin for connecting with the wrappers, and creates metaclass objects with thunks for deleting (constructurs are treated as static methods), and I don’t think I need to specialise the wrapper classes also.

I’m not worried about the amount of wrapper classes as long I can generate them automatically, but I’d rather not have to write them by hand for each juce class. How much of your wrapper code is generic by nature and how much had to be individually crafted for each Component class?

Can your lua-juce do document windows or is it dialog-only? Can you create a DocumentWindow with a TabbedComponent of, say, TextEditor components? What happens when the window is closed? Did you manually code a wrapper for TabbedComponent that does clearTabs(), or does your scheme somehow ensure that the TabbedComponent gets deleted before its tabs (or the other way around)?


#4

:smiley: I think I just found the solution.

A PILSmade component should delete itself when orphaned or removed from the desktop, regardless of PILS references to it. This should solve the TabbedComponent problems - when the DocumentWindow is removed from the desktop, it self-destroys, orphaning its children.

This means if a PILS program needs to keep components for later use, they must be made children of an invisible component.

A malicious PILS program can still cause leaks by removing a juce-made component (such as a MaximiseButton) from its window and putting a PILSmade component on it - but this is not likely to happen by accident.