Getting from valueTreePropertyChanged callback to Message thread

I keep hitting one dead end after another with this, and would appreciate a helpful pointer or two. This is for a standalone application project.

I’ve got a class that’s a ValueTree listener, and I’m trying to have its valueTreePropertyChanged callback be able to open a GUI window. However, trying to make that call directly results in an assert, because valueTreePropertyChanged is not being called on the Message thread. Right.

So as one way to solve that issue, I tried having valueTreePropertyChanged instead call ApplicationCommandManager::invokeDirectly, triggering a command I set up to open the GUI window (since opening the window would eventually be something I’d want to make accessible from the menubar anyways). The application command works correctly if I call invokeDirectly from a button press – but again, I’m hitting an assert when calling it from valueTreePropertyChanged:

bool ApplicationCommandManager::invoke (const ApplicationCommandTarget::InvocationInfo& inf, bool asynchronously)
{
    // This call isn't thread-safe for use from a non-UI thread without locking the message
    // manager first..
    JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
...

I’m confused by the statement in the ApplicationCommandManager::invokeDirectly docs, which state:

asynchronously if false, the command will be performed before this method returns. If true, a message will be posted so that the command will be performed later on the message thread, and this method will return immediately.

And yes, I am calling it with the asynchronously parameter set to true… but it seems it can’t even get the command into the async queue…

I find it best to only set tree properties from the message thread.

Try using MessageManager::callAsync; put the setProperty call inside of your lambda.

Matt

1 Like

Oh, so it’s best to fix it that far back up the chain of events?

Interesting… I guess I had the assumption that I should just find a way to deal with valueTreePropertyChanged being called on a background thread. You’re saying I should fix it by ensuring that it gets called on the Message thread? Thanks I’ll give that a try.

@matt I got to try your idea today but running into a conflict. This is the helper method I wrote to set tree properties from the message thread.

void setOneProperty (ValueTree tree, const Identifier& property, const var& newValue)
{
    MessageManager::callAsync (
                               [tree, property, newValue] ()
                               {
                                   tree.setProperty (property, newValue, nullptr);
    // compiler error:
    // 'this' argument to member function 'setProperty' has type
    // 'const juce::ValueTree', but function is not marked const
                               }
    );
}

I understand the error itself, but I’m not clear how my ValueTree got marked as const.

I found a workaround which compiles and works without error. But is it a kludge? Or a necessary end-run around the compiler?

void setOneProperty (ValueTree tree, const Identifier& property, const var& newValue)
{
    MessageManager::callAsync (
                               [property, newValue, tree] ()
                               {
                                   ValueTree dummy = tree;
                                   dummy.setProperty (property, newValue, nullptr);
                               }
    );
}

I believe lambdas are const by default, so anything passed in by value is const as well. I think you can mark the lambda as mutable and it will work? but I am not sure.

I’ve been thinking more about MessageManager::callAsync; it seems to me that you can’t rely on whatever is passed into the lambda still existing by the time the lambda executes.

This should be safer:

struct SetTreePropertyMessage : public CallbackMessage
{
    SetTreePropertyMessage(ValueTree tree_, const Identifier& propertyName_, const var& newValue_) :
        tree(tree_),
        propertyName(propertyName_),
        newValue(newValue_)
    {
    }
    
    void messageCallback() override
    {
        tree.setProperty(propertyName, newValue, nullptr);
    }
    
    ValueTree tree;
    const Identifier propertyName;
    const var newValue;
};

void setTreePropertyAsync(ValueTree tree, const Identifier& propertyName, const var& newValue)
{
    (new SetTreePropertyMessage{ tree, propertyName, newValue })->post();
}

That way the message object holds a reference to the underlying tree data even if the original tree object goes away before the message callback executes.

Matt

But - I could be wrong. It might be that using the lambda is safe because the lambda parameter also holds a reference to the tree.

I’m too old fashioned; still learning all this newfangled 21st century C++ stuff.

Matt

Ah, yes, there’s the ticket! Adding the mutable keyword to the lambda lets it work without having to resort to the “dummy” ValueTree trick.

It’s still new to me too… would be interested to know the answer to that, though!

A reference on itself would not be safe.
But the first argument is a ValueTree, that keeps the wrapped SharedObject inside the ValueTree alive (by reference counting), so the property name reference and the var reference are safe as well, as I see it.

1 Like

Is there a reason why your valueTreeProperyChanged callback isn’t happening on the message thread in the first place? In my experience, this is usually pretty bad news as they’re not thread-safe.

You’re usually better of making sure any interaction with the ValueTree is done on the message thread in the first place…

Yes, there is a reason - in this app, ValueTree updates were being triggered from a callback on a different thread.

Yes that has been the direction we are going in this thread, after Matt’s suggestion in the second post… moving the ValueTree::setProperty calls to the message thread, via a MessageManager::callAsync lambda. Is approach looking solid?

Thanks @daniel, it was my understanding that passing the ValueTree into the lambda would “count” towards keeping the ValueTree itself alive. Glad I had that right.

Yes, the ValueTree itself is a copy (beware, it loses the listeners), but the underlying SharedObject is the same, so all connections into the tree, to parent, children and properties stay intact.