Patcher Traversal
Hi folks,
I'm trying to connect various mxj instances together, with some awareness of the patcher hierarchy.
I know that both Java and Javascript in Max allow for traversal of the patcher tree from parent to children.
In Java, something to this effect:
import com.cycling74.max.*;
public class MaxPeer extends MaxObject {
private void recurse(MaxPatcher p) {
MaxObject.post(p.toString());
MaxBox [] boxes = p.getAllBoxes();
for (MaxBox box : boxes) {
if (box.isPatcher()) {
recurse(box.getSubPatcher());
}
}
}
public void traverse() {
MaxPatcher patcher = this.getParentPatcher();
recurse(patcher);
}
}
JavaScript also allows for this sort of top-to-bottom patcher traversal, but also lets you go traverse up towards the root of the patcher tree.
From the docs:
function traverse_up() {
var prev = 0;
var owner = this.patcher.box;
while (owner) {
prev = owner;
owner = owner.patcher.box;
}
if (prev) {
post("top patcher is", prev.patcher.name);
}
}
Basically, I'm stumped on how to elegantly traverse from child-subpatcher to parent-patcher in Java. If I'm reading the java-docs correctly, there actually isn't any direct way to find the parent patcher of any given MaxPatcher instance. There's just nothing resembling a MaxPatcher.getMaxBox() method like there is in JavaScript.
My current kludgy attempt at rectifying this is to iterate over all of the mxj instances, as returned by MaxContext.getAllObjects(). Then I grab each MaxObject's MaxPatcher, and recurse downward through any subpatchers it might contain. Each child-parent patcher relationship I discover gets stored in a static HashMap, which in turn lets me simulate the effect of traversing upwards through patcher relationships towards the highest patcher (or patchers) I've found.
This doesn't really sit well with me - I'd rather not maintain a parallel model of the Max environment if I can help it.
Is there an easier way?
I've never looked at Java for Max so I don't know what functions are available. However, I'm not convinced that capturing the child-parent references constitutes modelling the Max environment. If I needed the functionality you describe, I'd just define a tree structure that has parent links in it, then traverse the patcher hierarchy once (or whenever you add/remove patchers) and build the tree on the fly, complete with child-parent links. I'd use the hashmap simply as a quick way to find the appropriate link in the tree and then just go up that tree.
On a separate note, I'd encourage you to wrap that recurse function with
if (p != null)
{
// your original code
}
to defend against trying to dereference a null
Regardless of the algorithm I end up using to model patcher relationships, it's still weird that Java would only offer downward traversal, while JavaScript offers both upward and downward. If that's the end of the story, then I'll just keep implementing my shadow-patcher-tree, encapsulate and stop worrying. It's just more boilerplate for the moment.
Then again, "every line of code not written is 100% bug free."
PS: Thanks on the bugfix for the Java example. Any idea under what circumstances p would be null?
This wasn't a bug fix, but rather a defensive move.
I don't know if 'this.getParentPatcher' could fail --- what happens if you invoke your script on a top level patcher?
Personally, I just like to have functions protect themselves rather than assuming that they will never be called with invalid parameters.
If you're certain it can never happen, then throw in
assert(p != null)
at the top and prepare for crashes if (when) it does happen :-)
The assertion is also a good way to notify users of your code (which includes yourself months later when you've forgotten the code) what args are legitimate. For example, if you were building a square root function, you should always put (assert(parameter) >= 0) so that you can easily detect attempts to get the square root of a negative number.
--------------
Any idea under what circumstances p would be null?
Sadly, I recently came across the same problem and you are right that this is one of the many incomplete things about the Max Java SDK. This is of coruse available in the C API and through this exposed to the JavaScript API.
This won't help you now, but I am currently working on developing a JNI library to extend mxj
and one of the things I plan to include are some missing patch navigation and scripting methods.
In the meantime, maybe you should drop an email to support@cycling74.com to point out this thread as a feature request as the current patch scripting stuff in Java is kind of crippled without this—as you've disovered.
@dhjdhjdhj
OK, totally agree about the defensive patching - thought maybe you knew something about how these mxj methods return that I might be able to work with.
MaxObject.getParentPatcher() returns a reference to the patcher containing the [mxj] box that the MaxObject Java peer lives in. I'm actually trying like hell to get it to return me a null, since that might let me notify my datastructures that the patching environment has changed, and then let me update them in a more atomic way (rather than doing a complete traversal and rebuild of my model of a potentially large patch).
No luck so far.
@Roth
That sounds pretty definitive then. I'll just keep building boilerplate. Good luck with the JNI library!
What is your definition of a large patch? Patchers are relatively static, I suspect it's rare that large numbers of patchers will be getting created dynamically, so you're not going to have to perform the traversal very often. Also, tree traversal is not very expensive so it will happen pretty quickly unless you have thousands of patchers.
I think you're getting concerned by what those of us in the software development world call "premature optimization". In other words, until your system is negativelyimpacted by the traversal, don't worry about it.
MaxObject.getParentPatcher() returns a reference to the patcher containing the [mxj] box that the MaxObject Java peer lives in. I'm actually trying like hell to get it to return me a null
It will never return null
. When you call getParentPatcher()
for the first time on your MaxObject
, a new Java MaxPatcher
object will be created as a wrapper around the mxj's parent jpatcher. There is a private field (_p_patcher
) inMaxPatcher
that stores a pointer to the corresponding jpatcher in C. If the patcher no longer exists the first time you callgetParentPatcher()
the newMaxPatcher
object will have that field set to 0. Since this aMaxObject
's parent patcher is assigned only once the first time you callgetParentPatcher()
, even after the patcher is closedgetParentPatcher()
will return a valid java object for the patcher with the_p_patcher
field set (and depening on what else has happend in Max, it is possible the pointer value in this field will be the value of another patcher that is still open.
So the bottom line is that you will not be able to use getParentPatcher()
to listen for state changes. So what exactly were you trying to accompish with that? Updating your tree every time an object is created or deleted? There are notifications you can listen to from C for that and I was thinking about messing around with including that in my library.
Also, tree traversal is not very expensive so it will happen pretty quickly unless you have thousands of patchers.
Or if you have thousands of objects that need to be examined (either for their own state or just to see if they contain a patcher). In C I agree that the traversal is not so expensive, but some work with traversing very large patches I did a few years ago in JavaScript (on Max5) was VERY slow (would take a couple minutes to scan the patch). Some small traversal speed tests I did with C compared to JavaScript a couple weeks ago showed that for this the new JavaScript engine in Max6 doesn't seem to be much faster (I'm guessing that is because all JavaScript is doing with this stuf is wrapping around native C calls which I'm guessing means there is not much from for JIT optimizations—possible the same situation for Java).
But I agree, you might wait to optimize like that until you need to. When I have done traversals of very large patches, I only needed to do so after I was done working on changes so a short wait for a rescan was no problem. If you are looking for some sort of live updating you are probably right that you don't want to do a whole traversal more than once but only update as needed.
I'll be back in Boston if a few weeks if you wanted to chat about some of this stuff and maybe help test that JNI stuff if it would be useful to you.
P.S.: Thanks for haniging out in Java land, I think Nick, Charles, and I were getting a little lonely ;)
My current kludgy attempt at rectifying this is to iterate over all of the mxj instances, as returned by MaxContext.getAllObjects(). Then I grab each MaxObject's MaxPatcher, and recurse downward through any subpatchers it might contain. Each child-parent patcher relationship I discover gets stored in a static HashMap, which in turn lets me simulate the effect of traversing upwards through patcher relationships towards the highest patcher (or patchers) I've found.
Not much better, but maybe a slightly nicer workaround would be to put your patch parser mxj all of your top level patches so you can dive into all your subpatches from there (as you said, relying on the hope that there is at least one mxj in each top level patch doesn't seem like a robust solution).
@Roth
I see now about the MaxObject.getParentPatcher() never returning a null reference. Looks like the only reliable way to test if a patcher has been deleted is not inside the MaxObject's notifyDeleted() call, but rather the next time a new MaxObject is added to the patch (by triggering a rescan).
I'm not into dropping a sentinel object into the top level patcher, although I acknowledge that that makes this problem so much simpler. Still, I'm getting pretty close to a flexible register/unregister system that avoids full traversals on every addition or deletion of [mxj] instances. I'll write back with the code once I get a little time to finish it up.
PS: I'd love to meet up to talk shop about this JNI business. Message me the dates!
Whats up with upward traversing of patcher hierarchy in the Java api?
It looks like it is still only downwards...
I just checked against the 6.1.8 Java libraries, and it looks like upward traversal is still not possible.
The MaxPatcher class still has no way to access its associated MaxBox instance, if that MaxPatcher is a subpatcher.