Update Editor from Processor without reference

Hi everyone!

My AudioProcessor has an Unlocker class that handles the license checking and plugin activation. It basically checks if the license file is present in a specific directory and, if not, it pops up a file selector for the user to choose the license file.

My AudioProcessorEditor has an InfoPanel class, a separate Component that can be accessed when clicking on the company logo, that displays some info. Included in the displayed info, there is some user data extracted from the license file and passed to the InfoPanel in its constructor.

When the plugin is already activated everything works as expected: the AudioProcessorEditor is constructed with the user data already known that can be displayed in the InfoPanel.
When the the plugin is activated for the first time, by choosing the license file through the file selector, the AudioProcessorEditor needs to be re-constructed (hence the UI window needs to be closed and re-opened) before the correct user data is displayed.

This is of course not optimal, so I’m trying to find a way to set the correct user info in the InfoPanel without needing to restart the plugin UI.

What’s the best approach to do this?

For now, I managed by having a pointer to the InfoPanel stored in the Unlocker class that gets passed when calling createEditor() :

juce::AudioProcessorEditor* PluginAudioProcessor::createEditor()
{
    auto editor = new WrappedPluginAudioProcessorEditor (*this, m_unlocker.getUserInfo());
    m_unlocker.setInfoPanel(editor->getInfoPanel());
    return editor;
}

and, when the correct license file is selected, I call something like

if(m_infoPanel)
    m_infoPanel->setUserInfo(m_userInfo);

from the Unlocker class, to set the correct user info to be displayed.

I haven’t managed to crash the plugin yet (I guess because the file selector gets called when the editor already exists), but this is of course wrong since references to the editor’s Component shouldn’t be stored in the processor.
The if(m_infoPanel) protects from the pointer to the InfoPanel being null, but not from it being dangling.

What are the alternatives here?

The only other solutions I can think of are:

  1. Call the parent’s getActivedEditor() from the Unlocker class when the correct license file is selected and, from that, set the correct info in the InfoPanel if any editor is active.

  2. Use a Timer in the InfoPanel that periodically fetches the correct info from the AudioProcessor’s Unlocker.

Thanks!

Option 1 (use getActivedEditor()) isn’t a good idea, you never want to call the editor directly from the processor.

Using a Timer, however, is a totally valid approach.

Another option is to store a callback (std::function) in the processor or unlocker object.

Then the editor can fill that function in its constructor, and change it to an empty function again when it gets destroyed. The unlocker can then use that callback to notify the editor without calling it directly.

This is a perfect case for the ChangeBroadcaster / ChangeListener.
Your Unlocker would be a ChangeBroadcaster and the editor the ChangeListener.
Now you simply upon creation of the Editor you subscribe to the Unlocker (addChangeListener) and unsubscribe in the constructor.

When the license changes, call sendChangeMessage and in the changeListenerCallback() you make the changes to the GUI.

It is 100% safe, even in the rare case that there were more than one editors, which is technically allowed.

1 Like

@eyalamir

Option 1 (use getActivedEditor()) isn’t a good idea, you never want to call the editor directly from the processor.

Why it’s not a good idea? I won’t be storing any reference to the Editor in the Unlocker, but do something like

if (auto* editor = dynamic_cast<eftilo::gui::EditorWithUserInfo*>(m_parentProcessor.getActiveEditor())) 
    editor->setUserInfo(m_userInfo);

where EditorWithUserInfo is an interface class with a virtual setUserInfo().

I’ll give the callback suggestion a try, thanks!

@daniel

This is a perfect case for the ChangeBroadcaster / ChangeListener.

Seems great! I completely missed that. I’ll give it a try for sure, thanks!

It’s not about storing. The Editor is meant to be separate from the processor, and there could be more than one editor, or no editors.

Since this is C++ you can call anything from anywhere, but you usually want to keep this editor->processor decoupling in place to avoid nasty bugs and only have the editor know about the processor, as its guaranteed to outlive it.

mind the threading. If this happens on the message thread it is safe, though ugly. The destruction is guaranteed to happen on the message thread as well. But if you call this from a background thread (and setStateInformation might happen that way, e.g. protools), then the editor might get destroyed while you are accessing it.

1 Like

Got it, thank you! I thought wrapping the setUserInfo() in the if() was enough, but I guess stuff can go wrong with multiple threads going on.

Yeah I must admit my knowledge about threading stuff is very limited. Do you have any reference/book you can suggest on the matter?

This code is in the prepareToPlay() call stack, but it’s part of a lambda launched asynchronously from a FileChooser (DialogWindow* DialogWindow::LaunchOptions::launchAsync()), so I’m not sure which thread is dealing with it. I can check with a debugger, but I’m really not familiar with all this.

I have my licenser class working in the processor thread, while the equivalent of your infobox on the editor’s one. Our “infobox” stores a reference to the licenser in its constructor. All the methods to activate, check and retrieve license data are in the licenser class, while the “infobox” just call these methods when needed. There’s no need to have a reference to the plugin editor class in our case.

Hope this helps.

All the methods to activate, check and retrieve license data are in the licenser class, while the “infobox” just call these methods when needed.

Got it. So basically you perform all your license checks in your licenser class, but everything gets triggered from the editor. By doing so you only need the processor reference in the editor, and not vice-versa.

In my case, everything is triggered from the prepareToPlay(). I don’t remember why I did it that way, I guess because I wanted to be able to deal with the license checking even without an editor (eg. when opening an existing project) and splitting the checks in both the editor and processor seems to be a bit dangerous.

you can have a label with all the license text and reference it to a output text of the processor, any changes in the text will be updated immediately in the label. In the constructor of the editor you have access to the processor, so it is enough to set the reference with infoLicenseLabel.getTextValue().referTo(processor.InfoLicenseText);

updates in the referenced text should not be made in the audio thread directly but through a broadcaster

so it is enough to set the reference with infoLicenseLabel.getTextValue().referTo(processor.InfoLicenseText);

Thanks, I’ll give that a try too. Unfortunately I also set the label boundaries and justification upon text change, so I would have to time that somehow.

That’s a weak design, but that’s up to you.
Instead of connecting the labels text, you can use a juce::Value local in your editor, which you call referTo. Then you can listen to the jcue::Value.
But it is getting quirky, better stick to the ChangeListener.

I guess you can adapt the text as soon as it is updated with a Label::Listener::LabelTextChanged. for example you could separate each field with a token, and then display it in an organized way. I don’t know if there will be any conflict, then you should use 2 separate labels or some other workaround. In any case I guess this way is better than a timer.

1 Like

I agree the solution you suggested seems to be the best one for my case.

I wonder though, where can I get some knowledge about how different DAWs deal with threads for specific tasks? Is there some documentation I could look into, or is it knowledge you got through painful debugging sessions?

As you suggested, my solution, although ugly, is safe if the bit of code I provided runs on the message thread. Since it runs from an asynchronous call done by the FileChooser, I guess that might not be the case (to not lock the rest of the UI while waiting for the file to be selected).