No internet connection
  1. Home
  2. How to

When to use invalidate()

Hi! I recently wrote a script where I got a 'requires UIelement' error and I remembered some discussion here about 'invalidate()', which I added to my script and it solved the issue.
That said, I don't know what it's doing or why, or if I'm using it redundantly.

example:

sf.ui.proTools.windows.invalidate().whoseTitle.is('Export Session Text').first.checkBoxes.whoseTitle.is('Include File List').first.checkboxSet({
        targetValue: "Disable",
});

sf.ui.proTools.windows.invalidate().whoseTitle.is('Export Session Text').first.checkBoxes.whoseTitle.is('Include Clip List').first.checkboxSet({
        targetValue: "Disable",
});

sf.ui.proTools.windows.invalidate().whoseTitle.is('Export Session Text').first.checkBoxes.whoseTitle.is('Include Markers').first.checkboxSet({
        targetValue: "Disable",
});

sf.ui.proTools.windows.invalidate().whoseTitle.is('Export Session Text').first.checkBoxes.whoseTitle.is('Include Plug-In List').first.checkboxSet({
        targetValue: "Disable",
});

This works, but do I need to invalidate each option or should I just invalidate the window before disabling the buttons? What is the 'best practice?', and where can we learn more about .invalidate()?

Thanks!
-D

Solved in post #2, click to view
  • 3 replies
  1. Hi Dustin.

    Thank you for this excellent question.
    I hope I can shed some light on this.

    Quick answer

    In the way that you're using invalidate specifically as quoted above, it is 100% redundant and should have no effect.

    Long answer

    Everything under sf. is a tree of nodes, that is each node has one parent node and can have many child nodes.
    Every node is an in-memory representation of some external state. The sf.ui. sub-node is a collection of applications, each of which have subnodes that represent external UI elements.
    There are also other node types, for example those that we use to describe Stream Decks, Vienna Ensemble Pro servers, Databases, Surfaces, Decks and so on.
    Each of these types of nodes have different characteristics wrt what they hold in their cache and to which degree they can invalidate the cache on their own. For example the new Vienna Ensemble Pro cache holds everything in its cache until you invalidate it.
    The UI node hierarchy developed over several years and I regret now that we never struck a "one-size-fits-all" approach to the caching. It works very very well to speed up the access to Pro Tools, but we added stuff later to the API that made caching a little more complex.

    What does invalidate do

    The invalidate() call invalidates the node right to the left of your call, and all children of that node. So in your case you'd be invalidating all windows of Pro Tools accessed this way (but not the mainWindow cache since you access that by .mainWindow), and you wouldn't be invalidating the Pro Tools app cache, since that's one more layer up.

    In short, to sum up the story for UI caching today.

    When you use an AxElementArray for querying/filtering, such as node.windows.whoseXXX.is/contains/...first - here windows is the AxElementArray property - then there is no caching under the node level.
    This is to make it simple for people to create macros with this "query language" without having to think about invalidation.
    So whenever you type something with ...windows or ...children or ...checkBoxes and stuff like that, then that will be cacheless.
    What will be cached is when you use SoundFlow's strongly associated nodes. This is by design, and should always do that. For example there's no need to find the Automation Window every time you want to toggle the preview button. This cache works intelligently in that it keeps the cache of where the preview button is, but if the window disappeared, it will re-find the window as well. This is key to why SoundFlow works so fast with UI automation.

    Caching at the UI element property level

    There are 2 hard coded property caches of UI elements that are also used to speed up things. This is title and value. These two properties are great examples of something that should be invalidated. So to access the title of a UI element called myElement you'd say myElement.title.invalidate().value - this would give you the updated title.

    General rules of invalidation

    • Invalidate as little as possible, only when needed. This speeds up everything. Your invalidations might slow down built in SoundFlow commands, since by invalidating all the time you'll be removing their ability to use the cache.
    • Invalidate as far to the right (as small a scope) as possible. For example, invalidate the UI element property instead of invalidating the whole UI element, if all you need is an updated property value.
    • When debugging an issue, add invalidate all the way to the far left (right after the app) to figure out if it's a cache issue or not. If it is, move the invalidate() one step further down the hierarchy until it stops working, then trace it one step back.
    Reply2 LikesSolution
    1. The script where you got the requires UIElement I think is most likely to be a script where you're using SoundFlow's strongly associated nodes, and not the dynamic query nodes that you are depicting above.
      It'd be great to have an example of such a "strong associated node" script, ie. one that's for example using:
      sf.ui.proTools.automationWindow.previewButton in lieu of sf.ui.proTools.windows.whoseTitle.isNullOrEmpty.first.buttons.whoseTitle.is('Preview').first
      (Note these are just syntax examples to illustrate the point, it's not the actual code one would use)

      1. First of all, thank you for such an in-depth reply! So, this is the script where I encountered the 'requires UIElement' error. It seems I need to invalidate the 'Session Export Text' window (or it's options?) before I can reliably use the script. What should I do here?

        
        sf.ui.proTools.appActivateMainWindow();
        
        if (sf.ui.proTools.selectedTrackNames[0] == undefined) {
        
           log("No tracks are selected");
           throw 0;
        
        }
        else {}
        
        sf.ui.proTools.menuClick({
            menuPath: ["File", "Export", "Session Info as Text..."],
        });
        
        sf.ui.proTools.windows.whoseTitle.is('Export Session Text').first.checkBoxes.whoseTitle.is('Include File List').first.checkboxSet({
                targetValue: "Disable",
        });
        
        sf.ui.proTools.windows.whoseTitle.is('Export Session Text').first.checkBoxes.whoseTitle.is('Include Clip List').first.checkboxSet({
                targetValue: "Disable",
        });
        
        sf.ui.proTools.windows.whoseTitle.is('Export Session Text').first.checkBoxes.whoseTitle.is('Include Markers').first.checkboxSet({
                targetValue: "Disable",
        });
        
        sf.ui.proTools.windows.whoseTitle.is('Export Session Text').first.checkBoxes.whoseTitle.is('Include Plug-In List').first.checkboxSet({
                targetValue: "Disable",
        });
        
        sf.ui.proTools.windows.whoseTitle.is('Export Session Text').first.checkBoxes.whoseTitle.is('Include Track EDL\'s').first.checkboxSet({
                targetValue: "Enable",
        });
        
        let exportSessionTextWin = sf.ui.proTools.windows.whoseTitle.is('Export Session Text').first;
        exportSessionTextWin.radioButtons.whoseTitle.startsWith('Selected Tracks Only').first.elementClick();
        exportSessionTextWin.buttons.whoseTitle.startsWith('OK').first.elementClick();