Combining behaviors

Our objective is to generalize the moveChildren function so that it can be called with any number of behavior generators and combine them into a complex behavior. For example, to combine rotation and color cycling on ziggurat zig:

moveChildren(zig, makeArithYRotator(rpsA, rpsB), 
                  makeColorAnimator(colorRate));

The following program shows the result. Whenever the GUI control is used to change a parameter, moveChildren gets called as above with updated arguments passed to the behavior generators.

Rainbow rotating ziggurat

Behavior generators such as makeArithYRotator and makeColorAnimator follow this pattern:

function makeBehavior(...parameters) {
    function f(child, i, children) {
        // 1. set certain properties of child to support
        //    the behavior
        // 2. construct and return a method to be bound
        //    to child's update property
    }
    return f;
}

When called with suitable arguments, makeBehavior returns a function f. In our previous version of moveChildren, f gets passed as an argument.

function moveChildren(root, f) {
    let children = root.children;
    children.forEach(function (child, i, children) {
        child.update = f(child, i, children);
        subject.register(child);
    });
}

When f gets called on child, it carries out some actions and then returns a method which gets bound to the property child.update.

In contrast, our generalized version of moveChildren will receive an array of such functions f, each produced by a different behavior generator. It is enough to call each function f on child to initialize child‘s properties to support the various behaviors. However, it’s also necessary to do something with the methods returned by these calls. What we’ll do is combine these methods into a new method which then gets bound to child.update. The process of combining a sequence of methods into a new one is called sequencing. After looking into sequencing on the next page, we’ll write a general version of moveChildren that gets called with any number of behavior functions.