JS observers stop working if another device/js with observer is removed from the Set

11OLSEN's icon

Please someone take a look. I never noticed this before. A very simple js to observe the "mute" button state of a track. Add device to multiple tracks, everything works as expected, mute state is printed to Max window. Now as soon as one of the devices is deleted, all other js observers stop working. I even tested it with 2 different devices observing different path and property, they break each other when one is deleted.
Here is a device:

mute_js_fr.amxd
application/octet-stream 3.19 KB

JS is frozen into it and looks like this:

function bang ()
{
    var obj = new LiveAPI(callback,"live_set this_device canonical_parent");
    obj.property = "mute"

    function callback(args)
    {
        post(obj.path, args, "\n")
    }
}

The same thing with Max object works as expected. Is there something wrong with the JS? How can removing one break another one in a completely different device and js?

tyler mazaika's icon

I can reproduce in both Live 11 and Live 10 (using 10.1.41, with whatever Max is bundled). I thought there might be something funny with the scoping in the callback, but then I wrote the code like this and get the same result:

function bang ()
{
    var obj = new LiveAPI(function(args) {
        post("mute2", this.path, args, "\n") 
    } ,"live_set this_device canonical_parent");
    obj.property = "mute"
}
tyler mazaika's icon

The below works fine -- the observers are appropriately isolated in their scope. But it does nothing to explain how the function-scoped variable initialization you use gets graduated into a global scope...

var obj = null
function bang ()
{
    obj = new LiveAPI(function(args) {
        post("mute2", this.path, args, "\n")
    } ,"live_set this_device canonical_parent");
    obj.property = "mute"
}

This is closer to how I do my observers (though often its in an array of observers or encapsulated in some other object.)

I guess really my intuition is that the observer initialized in your original bang() ought to cease to exist after it's function scope since it's not explicitly retained elsewhere. Then the fact that your code worked in the first place would've been a memory bug in Live.

11OLSEN's icon

Interessting, I have not seen it written like that. So instead of a function name I can write the callback directly into the LiveAPI( parameters)? I followed this doc . And

Technical note: you cannot use the LiveAPI object in JavaScript global code.

doesn't sound like I have to retain the obj in global scope. Further when you look at the example at the bottom of this page it looks like it's the recommended way to put the callback into the function scope.
btw: support just confirmed it as a bug


11OLSEN's icon

I really lost some hair today trying to find the mistake.
🦲

tyler mazaika's icon

I always took "you cannot use the LiveAPI object in JavaScript global code" to be about the initialization and function calls where writing them directly into the global code would imply you're trying to do things before [live.thisdevice] has notified that the Live API is ready. For instance in the example code at the end of the page I would expect that code could only be happy living inside another function call (as in your bang() ), and that's sort of backed up by them having a 'return' statement in there.

As for the inline syntax format: I don't normally do that, either, as it's hard to read. (It also comes with an issue where it triggers with "id nn" before you've assigned the property name you want to observe). It was just for me to test because I thought having a functional reference to 'obj' inside the callback was maybe a little questionable as I assumed the callback() would go away outside of your bang() scope. **

And now if it's confirmed as a bug -- and I assume you mean that your initial code should work and persist observers as written -- I guess I'm also surprised the implied behavior is that once you instantiate an observer it stays alive indefinitely. Seems like that would be bad for memory/performance. I normally track observer lifespan and aggressively try to unassign their observed property and null them out when they are no longer needed.

** I didn't intend to confound the two differences (inline callback AND variable outside of function) in the second example. Sorry!

11OLSEN's icon

Yes it makes sense to track the lifespan. I had a few situations today where it seems like callbacks were called from memory so that I had 3 messages from one api object. But that may also be caused by opening and closing the editor. Anyways I'm waiting for the fix. Thanks for looking at it Tyler!

11OLSEN's icon

Is there something like free(obj) in js? Setting obj.property ="" doesn't feel like obj will be garbage collected. But the global scope will help to reuse it without creating new LiveApis all the time. I can see that!

fraction's icon

there is for... only jitter objects:
myJitterObject.freepeer();

tyler mazaika's icon

I think I just borrowed that obj.property = "" practice from elsewhere in this forum. For some reason in my Favorites list is now marked as spam!:

In effect what I've been doing (maybe without satisfactorily answering your question for all JS) has been:

obj.property = ""
obj = null

And it seems to work... certainly no more redundant callbacks getting fired for me?

I would love to see an official JS LiveAPI best practices guide. There is a decent amount of not-very-intuitive stuff that goes on, IMHO.

11OLSEN's icon

To conclude here and for future reference: Putting the obj variable in global scope seems logical and fixes the problem of my first post. I'm no longer convinced it's a real bug, maybe just some confusing example code in the live js documentation.

var obj;
function bang ()
{
    obj = new LiveAPI(callback,"live_set this_device canonical_parent");
obj.property = "mute";

    function callback(args)
    {
        post(this.path, args, "\n");
    }
}

Works.

Sonoran Music Devices's icon

@11OLSEN, thanks for coming back and posting that. That - and this thread - helped me resolve a similar issue with callbacks. In short, it seemed like my API objects were not being garbage collected after reloading my Live set, although they would be garbage collected after completely reloading Live. Moving the API object declaration (not the instantiation) to the global scope resolved it. I assume that forces Live to properly dispose the API objects after reloading the Live set.

@TYLER MAZAIKA wrote:

I would love to see an official JS LiveAPI best practices guide. There is a decent amount of not-very-intuitive stuff that goes on, IMHO.

Agree. As my own best practice, I will always declare my Live API objects in the global scope.

11OLSEN's icon

They don't even consider it necessary to update the code example. So there will always be people who fall into this trap.