Ghettoizing special tween features

Here we are returning to the question of how to decorate functionality into the optional tween base class LinearGo, while keeping the primary update method legible & efficient.

public override function update(currentTime:Number) : void
{
    if (_doComplexUpdate) {
        complexUpdate(currentTime);
        return;
    }
    ...
}

/**
 * Works like update but supports 2 extra features.
 */
protected function complexUpdate(currentTime:Number) : void
{
    ...
}

The features in question are cycles (or loops) & useFrames. These are standard core features for tween packages. Cycles lets you yo-yo a tween back and forth any number of times. useFrames accepts a framecount instead of a seconds-based duration, as with Flash 9’s Paste Motion as AS3.

We do need to make these common needs available at a base-class level – not doing so would be a flaw in the Go standard. But, their code is harder to read and runs much slower. Hence the push to find a way to exclude them from update.

Opinions and ideas please!


Discussion points:

The code may seem tacky at first glance, however it has a number of things going for it:

  • The update method remains elegant: It is streamlined, efficient, and legible.
  • The complexUpdate method elegantly compartmentalizes special features.
  • By breaking special features out, we can document them as extras.
  • Although code is duplicated between the two functions, this may not pose a problem. Most developers will work in subclasses, with no need to mess with LinearGo. If they do, they can just add their new core features to complexUpdate.

What about the Decorator design pattern? Or adding a namespace so we can have 2 methods named update?

Good ideas, but unfortunately neither pans out very well in practice:

  • Decorator is less performant – it adds processes.
  • Decorator clutters the Go package with additional updater classes, thicker docs.
  • Decorator also duplicates update code, but then spreads it around different files.
  • A namespace is also messier: a namespace file & docs are required, and the code in LinearGo becomes less approachable.

8 Responses to “Ghettoizing special tween features”

  1. From a COLLADA point of view I would prefer time-based. I understand the need for ‘cycles’ and ‘useFrames’, but wouldn’t it be simpler to simply have those two as public properties?

    Or something like:
    public override function update( time:Number, cycles:uint = 0, useFrames:Boolean = false ){}

    Or even:
    public override function update( time:Number, … ) {}

    Just my 2 cents.

  2. thanks for the comment tim :)

    yes, rest assured that time-based is the norm. useFrames is just a special feature, that’s why I want it to be sidelined so it doesn’t muck up or slow down the normal time-based updates.

    BTW, I haven’t sent out any code that contains those special features yet. If you run a TestGo or Lesson1Go tween you can see normal time-based updates in action, and you can currently open LinearGo and check the update() method. Wanted to first get feedback on the best way to break those features out.

  3. I think either method Tim mentioned is great. Here are some that come to mind:

    Option 1
    —-
    var g:Go = new Go();
    g.onComplete = “yoyo”;

    OR

    Option 2
    —-
    var g:CycleGo = new CycleGo(3);
    // CycleGo extends Go or rather, LinearGo

    OR

    Option 3
    —-
    var g:Go = new Go();
    g.cycle = 3;

    Or I guess since you’d want to be able cycle it at any given point, you could do something like:

    var g:Go = new Go();
    g.cycleAtSeconds = 12;
    // or g.cycleAtFrame = 24;
    // or g.cycleAtSeconds = “middle”;
    // or some other word to signify the half-way point of the animation

    Perhaps the best way would be to use the Rest operator (…) and make the properties public so you could pass it through the update method and/or change it later dynamically (i.e., g.cycles).

    I don’t know, maybe all those suggestions are stupid. I’m just thinking out loud (or, as I type, whichever).

  4. I think you are on the right path with the ‘complexUpdate’ method. Rather than having a single complex method that handles various types of complex updates, I think the core update method should delegate to a couple of methods specific to the tasks at hand. For example:

    override public function update(currentTime:Number)
    {
    if(cycles)
    updateForCycles(currentTime);

    if(useFrames)
    updateAsFrames(currentTime);
    }

  5. Right because the goal is to keep Go completely stripped-down in all contexts, so you’re only hitting features you actually use – vs. including the kit & kaboodle just to get those few things you use in a project.

    Ryan, thanks for the post and as to the idea of having various update methods, I like it but wonder what happens when you want to combine useFrames + cycles… hopefully it wouldn’t need to run separate updates.

    That said, there might be some way to “layer” functionality into complexUpdate, so that it’s actually an update() call plus other secondary update calls like the ones you’ve suggested — less code gets duplicated that way too. Nice idea in theory but would need to be proven — maybe one of you testers would be able to write something like that?

  6. Well, there are really two options here in my opinion. First, let’s talk a little bit more about the approach you are suggesting (the complexUpdate method). I think it’s an acceptable solution to do that or call the methods separately as I suggested. The real issue for me isn’t how to split everything up, but more of a question of over-designing the LinearGo class. Let me explain.

    So, let’s just say that for the sake of argument that we go with my suggestion and call task-specific methods directly from the update method. It would end up looking something like this:

    override public function update(currentTime:Number):void
    {
    if(useFrames)
    {
    updateAsFrames(currentTime);
    }
    else
    {
    updateAsTime(currentTime);
    }
    }

    That’s not too bad, as we are keeping the core update method nice and clean and encapsulating the specific type of update we are in need of making to it’s own function rather than having the update method get over-bloated. From there, after we calculate a time based on the time or frame, we can call a updateCycle method if applicable and do what we need to do to offset the time even further. Finally, we can call updatePosition to apply easing to the time and apply it to the position.

    The problem is, I don’t feel as if the LinearGo class should be responsible for handling both time and frames. I think LinearGo should be converted into a base class for two other separate types, LinearTimeGo and LinearFrameGo or something. The base LinearGo class could house the updateCycle and updatePosition methods, and the two subclasses could calculate time based on time or frames respectively. By sharing a common interface, you could easily interchange and support both types in the GoEngine.

    The bottom line is, by doing this you will only be running the code needed for that object, not checking if-statements on each update for handling time or frames, etc. Hopefully all of this is making some sense.

    Thoughts?

  7. Cool, thanks for putting some real thought into this Ryan…

    There’s a real problem with having different base classes just to support features: it wouldn’t be easy to manage in a project. Remember that when using Go you’ll be using a lot of items that probably subclass LinearGo. Standard, expected features of tween kits need to be available without having to change all your items “extends” parameters, and they need to be available on a usecase, not a class basis.

    I agree that these features are a nuisance to have in LinearGo but, they’re standards for sure, and if we omit them people will have a very hard time figuring out how to add them for a project.

    You seem to be accepting the direction I’m proposing, splitting update, and maybe there’s an even better way, although I haven’t come up with it yet. I’d say go for it! Start writing your directions right into the Go codebase, and we’ll test & discuss! :)

    I’ve partially mocked up the version I show at the top of the post and will share that with you (off blog). Thanks again for getting involved. :)

    I’m also getting a nice little benchmarking utility together that will make it easy for us to test different ideas for efficiency.

  8. i like Go Animation System too…it’s simple and works…

Leave a Reply

You must be logged in to post a comment.