<Download Gugga Flash Framework>

Friday, October 20, 2006

Basics of Tasks and Sequences in GuggaFF

Overview

With this new article we would like to introduce you to one of the most important aspects of the GuggaFF, the concept of tasks and sequences and their applications. We will start with one example and how it is usually handled in Flash.

Let’s examine an abstract GUI application with dynamic content implemented in Flash. One of our first tasks will be to extract the application data from our data source. It doesn't matter what of kind of data source you are using - http service, web service or simple xml file it will take a while to load and process the data. Fortunately data loading in the Flash runs in background thread, so we can implement a loading animation which will capture the user attention during these tasks. After the data is loaded we'll want to populate the controls responsible for its presentation. This example may scale to a situation where depending on the size of the data the process may need to be distributed on more than one frame to achieve the desired performance.

In more complex sites we will need to implement the ability to have more then one view (also known as sections in GuggaFF) or different states of the view. Depending on user interactions we will need to switch the current section or to change its internal state. Often this operations are composition of loading and transition animations, preloading of external assets etc.

Common problems

During the implementation of a site in Flash we will need to handle a lot of asynchronous operations. The base approach is to register an event handler for the completion of the operation and use it to continue our work after the asynchronous task is finished. If you are using the event delegation mechanism of mx framework it would be something like this:
private var mImageLoader : ImageLoader;

private function loadContent(aImageUrl : String) : Void
{
mImageLoader.addEventListener("completed", Delegate.create(this, onImageLoaderCompleted));
mImageLoader.load(aImageUrl);
}

private function onImageLoaderCompleted() : Void
{
//do something (ex. hide loading animation or start another loader)
}


Now imagine that we want to implement a functionality which is a sequence of asynchronous operations. If we scale this approach the resulting code will be:
private var mAnimation1 : IAnimation;
private var mAnimation2 : IAnimation;
private var mAnimation3 : IAnimation;

private function initUI() : Void
{
mAnimation1.addEventListener("completed", Delegate.create(this, onAnimation1Completed));
mAnimation2.addEventListener("completed", Delegate.create(this, onAnimation2Completed));
mAnimation3.addEventListener("completed", Delegate.create(this, onAnimation3Completed));
}

private function startAnimationsSequence() : Void
{
mAnimation1.start();
}

private function onAnimation1Completed() : Void
{
mAnimation2.start();
}

private function onAnimation2Completed() : Void
{
mAnimation3.start();
}

private function onAnimation3Completed() : Void
{
//do something (ex. notify for the completion of the sequence)
}


Also if we need to wait for more than one event before we can execute some function one of the possible solutions is:
private var mImageLoader1 : ImageLoader;
private var mImageLoader2 : ImageLoader;

private var mIsImageLoader1Completed : Boolean;
private var mIsImageLoader2Completed : Boolean;

private function initUI() : Void
{
mImageLoader1.addEventListener("completed", Delegate.create(this, onImageLoader1Completed));
mImageLoader2.addEventListener("completed", Delegate.create(this, onImageLoader2Completed));
}

private function loadConent(aUrl1 : String, aUrl2 : String) : Void
{
mIsImageLoader1Completed = false;
mIsImageLoader2Completed = false;

mImageLoader1.load(aUrl1);
mImageLoader2.load(aUrl2);
}

private function onImageLoadersCompleted() : Void
{
//do something (ex. start reveal animation)
}

private function onImageLoader1Completed() : Void
{
mIsImageLoader1Completed = true;
if(mIsImageLoader2Completed)
{
onImageLoadersCompleted();
}
}

private function onImageLoader2Completed() : Void
{
mIsImageLoader2Completed = true;
if(mIsImageLoader1Completed)
{
onImageLoadersCompleted();
}
}

If we combine this examples and try to scale them the result will be a big amount of spread and tangled code. So we need to find a way to generalize the management of computations which are combination of asynchronous operations.

Tasks in GuggaFF

The base unit for solving these kind of problems in GuggaFF is the task. The task is abstraction of operation which has determined begin and end. It is represented by the interface gugga.common.ITask.

In GuggaFF we have a lot of implementations of this interface – ImageLoder, XMLLoader, TimelineAnimation, PropertiesTweenAnimation. Also we provide some helper tasks which simplify and reduce the resulting code in this approach – ExecuteMethodTask, ExecuteAsynchMethodTask, SingleExecutionTask.

So once we have tasks we need a way for combining them. The basic composition of tasks is the linear sequence:





This composition can be implemented with the gugga.sequence.TaskSequence class. It uses an single-linked list for managing the task execution. As we have in the Composite design pattern TaskSequence is also task. When we start the sequence, the head of the list is stated too. Every task in the list waits for the end of his predecessor and then it is started. This iteration continues until the last task in the sequence has been completed, then the whole sequence completes.

If we rewrite the previously mentioned example of sequence of animations with the TaskSequence class it will look like:
private var mAnimation1 : IAnimation;
private var mAnimation2 : IAnimation;
private var mAnimation3 : IAnimation;

private function startAnimationsSequence() : Void
{
var animationSequence : TaskSequence = new TaskSequence();
animationSequence.addTask(mAnimation1);
animationSequence.addTask(mAnimation2);
animationSequence.addTask(mAnimation3);

Listener.createSingleTimeListener(
new EventDescriptor(animationSequence, "completed"), Delegate.create(this, onAnimationSequenceCompleted));

animationSequence.start();
}

private function onAnimationSequenceCompleted() : Void
{
//do something (ex. notify for the completion of the sequence)
}


Before describing the other composite task in GuggaFF – gugga.sequence.TaskManager we will need to introduce the abstraction for precondition.

In GuggaFF it is abstracted by the gugga.common.EventDescriptor class. This class is a pair of event name and an object which raises this event. So the EventDescriptor class exactly describes a moment in the lifetime of concrete object.

GuggaFF provides a mechanism for composing preconditions with the gugga.sequence.PreconditionsManager class. This manager observes a collection of preconditions and raises an event when all of them are met.

If we use PreconditionsManger in the example of waiting for completion of two loaders it will be something like:
private var mImageLoader1 : ImageLoader;
private var mImageLoader2 : ImageLoader;

private function setConent(aUrl1 : String, aUrl2 : String) : Void
{
var preconditionManager : PreconditionsManager = new PreconditionsManager();
preconditionManager.add(new EventDescriptor(mImageLoader1, "completed"));
preconditionManager.add(new EventDescriptor(mImageLoader2, "completed"));

Listener.createSingleTimeListener(
new EventDescriptor(preconditionManager, "preconditionsMet"), Delegate.create(this, onImageLoadersCompleted));

mImageLoader1.load(aUrl1);
mImageLoader2.load(aUrl2);
}

private function onImageLoadersCompleted() : Void
{
//do something (ex. start reveal animation)
}


When we have the idea for precondition, we can easily describe the TaskManager class as a composite task which manages the execution of its children by preconditions. We can think that the TaskManager is a web of tasks connected by preconditions.



According to Composite Pattern, the TaskManager is task itself. When the manager is started, all of its starting tasks are started too. When all the preconditions for certain task in the web are met and all of his predecessors are completed then the task is started. The manager is completed when all final tasks are completed and all final precondition are met.

As you see TaskManager lets you to solve complex situations which are combination of all previously mentioned common problems. A simple example of his usage can be illustrated by extending the example for usage of PreconditionManager by adding an animation which should be started after completion of the loaders. So the resulting code will be:
private var mImageLoader1 : ImageLoader;
private var mImageLoader2 : ImageLoader;
private var mAnimation : IAnimation;

private function setContent(aUrl1 : String, aUrl2 : String) : Void
{
var taskManager : TaskManager = new TaskManager();
taskManager.addStartingTasks([mImageLoader1, mImageLoader2]);
taskManager.addTaskWithPredecessors(mAnimation, [mImageLoader1, mImageLoader2]);
taskManager.setFinalTask(mAnimation);

Listener.createSingleTimeListener(
new EventDescriptor(taskManager, "completed"), Delegate.create(this, onTaskManagerCompleted));

taskManager.start();
}

private function onTaskManagerCompleted() : Void
{
//do something (ex. notify for the completion)
}


Stay tuned for more articles related to interruption of tasks, deeper look into web of tasks (TaskManager) and more advanced techniques related to their usage.

6 Comments:

Nikita Dudnik said...

Great! You've already done what I planned last week. =)
With you code I can skip time consuming framework programming and jump right to the fun part of doing my work fast and efficient.

9:45 AM  
laurent said...

Which Framework choosing? Gugga, Pixlib...?
I still hesitate.
With articles of this quality a choice appears obvious for somebody as me who must learn a lot!
thx

1:33 PM  
sotirov said...

We greatly appreciate the fact that you like our work. We would like to invite you to join the group on Google dedicated to the framework. Its open for registration.
http://groups.google.com/group/guggaff

2:01 AM  
Vladimir Tsvetkov said...

One of the most important things about GuggaFF is that this framework is an application framework - it does not concetrate particularly on logging, beaconing, graphics, tweening or music... The accent is on the way we structure a Flash Application - we build applications with GuggaFF on per component basis, further on we compose component into sections (the section is also a component). The section is the fundamental building block we use. Our framework gives you the means, utils and architecture for how to structure and manage a set of sections and a variety of actions in which we want to maintain these sections or interactions (transitions, navigation, etc.) between them.
Pixlib is more like a set ot libraries, or a whole set of frameworks - each framework/library concentrating on particular element in Flash application development. This justs mean that you can use parts of Pixlib with GuggaFF.
Pixlib also offers a microarchitecture (a set of related design patterns) which is very similar to the Cairngorm Flex microarcitecture and the J2EE core design patterns catalog. This microarchitecture give you another approach in RIA development - a feature driven development - in this approach you can build an application feature by feature - implementing the user gestures processing in a sequential manner - gesture by gesture.
But still I can't find an analog in Pixlib for the GuggaFF's ITask, TaskSequence, TaskManager and PreconditionManager.
These are my first impressions on Pixlib.

1:54 AM  
laurent said...

Vladimir, you are right!

After having looked at Gugga more closely, I understand the goal of Gugga…

5:52 AM  
Anonymous said...

This is great! I learned a lot from your article and the code. Thanks for your excellent work!

1:52 PM  

Post a Comment

Links to this post:

Create a Link

<< Home