Section 14.7. Understanding the Framework


14.7. Understanding the Framework

The component framework comprises nearly 600 lines of code (not counting the components ' code), which was never documented. It's no wonder some developers decided to shy away from it and develop their own framework!

As we've seen throughout the chapter, to load one or more external .asc files, use the load( ) command in your application, such as main.asc :

 load("components.asc"); 

An .asc file can load other .asc files. For example, loading components.asc is really just a shortcut to loading framework.asc and many other components cited within it. You can load the framework and only those components you want (such as the Chat), as follows :

 load("framework.asc"); load("components/chat.asc"); 

14.7.1. The Framework's Files

Let's look at the files that make up the server-side framework. For a detailed description of each file, refer to "The Framework's Code," later in this chapter. Here's a brief description of all the files that make up the server-side framework:


components.asc

A very simple file that loads the framework.asc and every communication component that ships with FlashCom. You can add a load( ) statement to automatically load your own custom components as well. Loading components.asc is easy but is overkill if you need only one or two components.


framework.asc

This constitutes the framework itself and is where the global gFrameworkFC singletonone of two main pillars of the component frameworkis defined. The gFrameworkFC stores references to each component's instances, along with the components' clientGlobals memory space, as discussed later. This file automatically loads application.asc and facade.asc as well.


application.asc

This file is necessary for the framework. This is where the framework hijacks the application object to enhance it with some framework-specific functionality.


facade.asc

This file is in charge of method routing and lazy instantiation of server-side components. This is the second pillar of the framework.


component.asc

This is the base class for all server-side components (and should not be confused with component.asc mentioned earlier). It defines some common variables (such as prefix and callPrefix ) and handles event dispatching for intracomponent communications.

14.7.2. The Framework's Data Structures

Before diving into the framework's code, it helps to understand three fundamental aspects of the framework:

  • Everything is based on a naming scheme.

  • The gFrameworkFC.components data structure stores references to component classes, indexed by component type.

  • Every client-to-server call has to go through the server-side Client object and is therefore attached to Client.prototype or one of its subproperties .

Keeping these in mind will make the code much easier to understand.

14.7.2.1 Everything is based on a naming scheme

A key decision in the creation of the framework was to come up with a naming scheme that would uniquely identify component classes, instances, and methods . The result was this general format for naming:

   ComponentClassTypeName   .   ComponentInstanceName   .   ComponentMethodName   

So, for example, to call the sendMessage( ) method on the instance named chat1_mc of the FCChat component, the naming scheme is:

 FCChat.chat1_mc.sendMessage() 

As you will see, this naming scheme is the basis of most of the architectural decisions made during the creation of the framework.

14.7.2.2 The gFrameworkFC.components data structure

Understanding how component references are stored within the framework is key to understanding its code. As an example, let's see how a reference to an instance of the FCChat component named chat1_mc is stored in the framework. Here we navigate down the dot-delimited data structure in a progression:


gFrameworkFC

The global framework singleton.


gFrameworkFC.components

An array indexed by component type.


gFrameworkFC.components.FCChat

A reference to the FCChat class (extended from FCComponent ).


gFrameworkFC.components.FCChat.instances

An array of references to instances of the FCChat class.


gFrameworkFC.components.FCChat.instances.chat1_mc

A single instance of the FCChat class named chat1_mc . This is what you can invoke methods on.

As you can see, gFrameworkFC stores the class type name first, then the instance name, and then the method name . So, for example, if you want to call clearHistory( ) on our sample chat1_mc instance from your main.asc code, you could use:

 gFrameworkFC.components.FCChat.instances.chat1_mc.clearHistory(  ) 

Of course, typically you'll have a shorter, more convenient reference to a given instance:

 var chat1 = gFrameworkFC.components.FCChat.instances.chat1_mc; chat1.clearHistory(  ); 

This is just a general purpose way to access any instance to demonstrate how instances are stored in gFrameworkFC .

14.7.2.3 The Client.prototype data structures

Because of how FlashCom Server is built, every client-to-server call has to go through the server-side Client object. Because of this, the component framework needs to define a way to route components' calls from the Client object to the appropriate component's class instance. Such routing is established in facade.asc , based on the naming scheme described earlier. The end result is a collection of objects attached to the Client object, described as follows:


Client.prototype

The Client object's prototype, through which all client-to-server calls automatically pass. Through some fancy "_ _resolve" mechanism (we'll look at this later when we analyze facade.asc) , the following other data structures are attached to it.


Client.prototype.FCChat

An instance of FCFactory , a class that generates class instances and saves them in gFrameworkFC.components.FCChat.instances .


Client.prototype.FCChat.chat1_mc

An instance of FCFacade , a class that grabs method invocations and attaches a reference to the Client object as the first parameter to them before delivering them to the appropriate class instance.

In this case, the names of the variables attached to the Client object can be misleading. For instance, Client.prototype.FCChat is an instance of the FCFactory class (not of the FCChat class, as you might think at first). Similarly, Client.prototype.FCChat.chat1_mc , is an instance of FCFacade .

We will analyze the code that generates these data structures and wires them together when we discuss facade.asc .

14.7.3. The Framework's Memory Management

Another important part of the framework is its set of APIs that supports data structures for the components. During the lifetime of a component instance, it typically needs to manipulate and access different kinds of data, classified as follows:


Globals

Shared across all component instances, regardless of their type, and all users. Any item placed under, or made a member of, gFrameworksFC qualifies as a global.


Client globals

Distinct for each client but shared across all components. These require special support from the framework. Components extending the base FCComponent class inherit the getClientGlobalStorage( ) method through which the client global area may be accessed. An example of a property that should be a client global is userColor , which is unique for each user but is used by every component in the application.


Component globals

Shared across all component instances of a given type. Achieved by attaching the items to the component's constructor function (class) object. As such, there is no need for any special support from the framework. These are basically static variables on the component class. An example of a component global could be currentSpeaker , a variable accessed by all the instances of a component, but attached to the class directly, not to the instances of the class.


Client locals

These are unique to each component instance for each connected client. Like client globals, these require special support from the framework; all components extending the base FCComponent class inherit the getClientLocalStorage( ) method that you can use to access the client local area.


Component locals

These are unique to a given component instance and are simply the member variables of a component instance.

14.7.4. How Components Are Registered and Instantiated

Component registration occurs when the application loads. For example, the chat.asc component loads component.asc , which takes care of the registration of the component with the framework (calling registerComponent( ) on framework.asc ).

Registration consists of two parts :

  • Adding the component as a listener to the gFrameworkFC.application broadcaster

  • Saving the class name in the gFrameworkFC.components array so that the method name resolution mechanism can find it

Server-side components are usually instantiated lazily , meaning only as needed. When the client-side portion of a component calls into its server-side portion, the framework determines whether the call is valid, instantiates the server-side portion if necessary, and dispatches the call appropriately. Although this is the default component behavior, you can turn it off on a per-component basis. Bypassing lazy instantiation is useful for components that must use singletons with well-known names (such as the "GateKeeper" instance of AuthConnect described earlier in this chapter).

14.7.5. The Framework's Method Routing

The framework's built-in method routing mechanism enables the client-side portion of a component instance to call its server-side portion in a reliable manner. Conceptually, the framework intercepts remote calls arriving at the Client object (on the server), inspects the name of the call, and looks up and routes it to the appropriate server-side component instance, instantiating it if necessary. The actual implementation of this process is based on the "__resolve" mechanism (discussed later under "Analyzing facade.asc") and is optimized so that the lookup happens no more than once for any given component method. Server-to-client calls are a little simpler and are implemented by inserting an object chain on the NetConnection object. For example, if foo1 is an instance of the Foo component, nc.Foo.foo1 is set up to point to foo1 (where nc is the NetConnection object).

14.7.6. The Framework's Code

To finish our overview of the Macromedia component framework, we analyze the details of the files that make up the framework.

To be able to fully follow the next sections, either print out the framework files from your FlashCom Server's scriptlib installation folder or open them up in a text editor and look at them on your monitor.

14.7.6.1 Analyzing components.asc

The components.asc file is the simplest of the framework's files. It simply provides a shortcut to load the framework and all the components that ship with FlashCom Server. Its name is easy to remember, and it allows developers to write just one line of server-side code to build a wide variety of applications that use the framework:

 load("components.asc"); 

Its code consists of numerous load( ) commands followed by a trace( ) to indicate it is done.

14.7.6.2 Analyzing framework.asc

The framework.asc file, which is loaded automatically by components.asc , creates the global framework singleton object, named gFrameworkFC . This object stores references to all the component instances and other memory management methods for the components to use.

Let's look at the code. First, we create the gFrameworkFC object.

The next function, __toarray__( ) , is a utility function that simply converts the arguments object into an array and returns the array.

Next, the framework.asc loads two other .asc files that we'll discuss later:

 load("application.asc"); load("facade.asc"); 

The framework also provides a unique ID to each client connected to the application, by incrementing a counter ( nextClientID ) each time a client connects.

Next, the code defines gFrameworkFC.clientGlobals as a new object. It is an associative array with indexes of client.__ID __ , which is the unique ID that the framework assigns to each client. Each slot of gFrameworkFC.clientGlobals is also an object that contains client global variables. An example of a client global is the client's chosen userColor or username . These values change for each client but are global to all components. The getClientGlobals( ) function defined in framework.asc simply returns the appropriate slot of gFrameworkFC.clientGlobals given a reference to a client object.

Next, framework.asc defines gFrameworkFC.components . This property is also an associative array, with indexes represented by component class names. It contains a slot for each component type, and each slot contains a reference to that component's class.

The next function in framework.asc is registerComponent( ) , which is called by each component to tell the framework that "a new component type is in town!" (you'll see how component.asc , the base class, calls this during init( ) ).

The registerComponent( ) function takes two parameters, a string containing the name of the component class (for example, "FCChat") and a reference to the actual component class, such as FCChat without the quotes. This function's main job is to save the reference to the component class in gFrameworkFC.components .

Next, registerComponent( ) sets up the component class as a listener to gFrameworkFC.application , which is a wrapper for the application object, defined in application.asc , which we discuss later. For now, the addListener( ) call means that whenever FlashCom gets an event on the application object (such as onAppStart or onConnect ), it dispatches it to this component's class as well.

Finally, registerComponent( ) defines onAppStop( ) and onDisconnect( ) handlers on the component class itself as static methods (note the absence of .prototype ). These methods dispatch the onAppStop or onDisconnect messages to all the component's instances. Because onAppStop( ) can return false to prevent the application from stopping, the onAppStop( ) static method returns the logical AND (&&) of the results from each instance's onAppStop( ) handler. Therefore, if any onAppStop( ) handler returns false , the application is not stopped . (If all onAppStop( ) handlers return true , it means "OK, go ahead and garbage collect this application.")

This mechanism means you should not define a static onDisconnect( ) or onAppStop( ) method on your class, because it will be overridden. Instead, define them on the class's prototype , and they will get called on each instance at the appropriate times.


That concludes the discussion of framework.asc . Now let's look at how application.asc enhances the framework with gFrameworkFC.application , and what it is.

14.7.6.3 Analyzing application.asc

The application.asc file is probably the most complex piece of the framework, so let's look at it in detail. This file defines how the framework traps the default events that the application singleton receives from FlashCom and dispatches them to the different components. The application.asc file is loaded automatically by framework.asc , which is in turn loaded by components.asc .

The goal of application.asc is to hijack the application object and wrap it in another object, which is a subclass of application . We'll see what this means later.

For now, let's look at the first two- thirds of the code in application.asc , which holds no surprises .

First, application.asc creates a reference to the application object and saves it in gFrameworkFC.application . Now repeat this mantra at least three times before you go on: "When I see gFrameworkFC.application in the following code, I will just think of it as a shortcut to the good old application object that I know and love."

Done? Great. Let's move on.

The next thing application.asc does is:

 application = null; 

Don't panic: you didn't just break FlashCom Server. The application property was just a reference, a shortcut that was automatically created for us by FlashCom. We can safely delete this reference without the memory it points to being garbage collected because we stored another reference to the application object in gFrameworkFC.application , remember?

Next, application.asc simply enhances the application reference stored in gFrameworkFC.application with some methods and properties that are useful to the framework. This is no different than doing the following in your main.asc :

 application.onConnect = function ( ) {...}; application.myVariable = 3; 

except application.asc uses gFrameworkFC.application instead of application (which, again, points to the same FlashCom object).

Next, application.asc adds some event-listening mechanisms with nextListenerID , a listeners associative array (indexed by ID), and addListener( ) and removeListener( ) methods. This creates the gFrameworkFC.application.listeners associative array, which we loop through whenever we need to dispatch an event to interested listener objects.

As we have seen in framework.asc , each component class calls addListener( ) in its constructor (when it calls init( ) on the base class, which in turn calls registerComponent( ) on gFrameworkFC ). This assures that each component class receives all the events that gFrameworkFC.application dispatches to it.

Next, application.asc defines the standard onAppStart( ) and onAppStop( ) handlers, which FlashCom calls on application (now saved in gFrameworkFC.application ) automatically.

The application.asc file's implementation of onAppStart( ) loops through all the listeners and invokes onAppStart( ) on them, if they have it defined. We need to test for the existence of onAppStart( ) on the component class because Server-Side ActionScript will complain with a runtime error if you call a method on a class that doesn't define it (client-side ActionScript 2.0 can perform compile-time checking for non-dynamic classes, but I wish it had similar runtime checking).

The onAppStop( ) handler is very similar to onAppStart( ) , in that it loops through all the listeners and calls onAppStop( ) on them, tallying the results before returning. The interesting part here is that, as we have seen in framework.asc , the onAppStop( ) method attached to a component class also loops through all its component instances.

So imagine you have two instances of the Chat component, chat1_mc and chat2_mc , in your movie. On the server side, this means that both gFrameworkFC.components.FCChat.instances.chat1_mc and gFrameworkFC.components.FCChat.instances.chat2_mc are defined.

So when FlashCom calls onAppStop( ) on gFrameworkFC.application , the handler will call onAppStop( ) on FCChat , which in turn will call onAppStop( ) on chat1_mc and chat2_mc .

The next method defined on the gFrameworkFC.application object is onDisconnect( ) . This method also has a "double-loop" effect, calling onDisconnect( ) on all the component classes, with each class calling onDisconnect( ) on all of its instances. Then, onDisconnect( ) cleans up the clientGlobals for the client that just disconnected, to clean up memory.

The next method defined in application.asc , onStatus( ) , simply invokes onStatus( ) on all registered components that have it defined. The onStatus( ) method defined here is invoked when FlashCom calls onStatus( ) on the application object (this is very similar to gFrameworkFC.application.onAppStart( ) , seen earlier).

The next method defined in application.asc , gFrameworkFC.application.onConnect( ) , is more complex. FlashCom calls it automatically whenever a new client connects. Remember that FlashCom doesn't accept or reject a client until application.acceptClient( ) or application.rejectClient( ) is called. This allows developers to perform authentication asynchronously, for example by connecting to a database through Flash Remoting (as seen earlier with the AuthConnect component). Also, application.acceptClient( ) and application.rejectClient( ) can be called by any method in your application, not just onConnect( ) (it's not like onAppStop( ) , in which the return value of the function tells FlashCom what to do).

With these concepts clear in our minds, we can look at the gFrameworkFC.application.onConnect( ) handler.

First it assigns an unique ID ( client.__ID__ ) to the client that just connected. Then it creates the clientGlobals object on gFrameworkFC to hold this client's global variables ( username , userColor , and whatever component developers want to store in it).

The code also assigns a __RESPONSE__ object to the client object that just connected. This is an associative array indexed by the listener ID of each component class. Basically, this is a table of what each component has decided to do with this client: each slot can have one of four values:


true

Indicates the component has accepted the client


false

Indicates the component wants to reject the client


null

Indicates the component is still undecided, maybe because it's running some asynchronous authentication code


undefined

Indicates the component doesn't have an onConnect( ) handler

The onConnect( ) code in application.asc was not cleaned up as much as it could have been. You can safely ignore var response , as it's never used anywhere else. You should also ignore the client.__PROCESSING__ property; Server-Side ActionScript is single-threaded and onConnect( ) is synchronous, so at the end of its execution, __PROCESSING__ is always false .


Next, the code saves the arguments to onConnect( ) into the client.__ARGUMENTS__ array (using the private gFrameworkFC.__toArray __ ( ) method to convert them from an object). Then, the code loops through all the components registered with the framework and calls onConnect( ) on them, if they have it defined.

Note that gFrameworkFC does not define a static onConnect( ) method on the component class, so you'll have to define it on the class (and not on the class's prototype ) yourself if you want to have one.

If a component does not define an onConnect( ) handler, its result is assumed to be TRue , which makes sense. The way the framework does it, though, is a bit tricky. If the component doesn't have onConnect( ) defined, the loop's continue statement will leave an empty slot in the client.__RESPONSE__ table. (This works because, when dispatchConnectionStatus( ) loops through the client.__RESPONSE__ table with a for...in loop, it skips slots that have never been defined, but not slots that have been set to null .)

If a component's onConnect( ) handler returns synchronously (with TRue , false , or null ), the value is saved in the client.__RESPONSE__ table slot for that component, with this code:

 if (client.__RESPONSE__[this.listeners[i].__LID__] == null)   client.__RESPONSE__[this.listeners[i].__LID__] = null; if (status != null)   client.__RESPONSE__[this.listeners[i].__LID__] = status; 

The first test sets the default value to null (and creates the slot in the table at the same time). The second test sets the value to TRue or false if a component's onConnect( ) handler returned a value.

Finally, onConnect( ) calls dispatchConnectStatus( ) for that client, which tallies all the responses and decides what to do.

The next two methods are defined for those authenticating components that want to accept or reject a client asynchronously. Such components must define an onConnect( ) method with an empty return; statement at the end, with no return value. This will set the corresponding slot in the client.__RESPONSE__ table to null , to indicate that a result is pending (in dispatchConnectStatus( ) , described later).

When the component has decided whether to allow the connection (because the database has returned a value after attempting to authenticate the user), it can notify the framework of its decision by invoking requestAcceptConnection( ) or requestRejectConnection( ) .

Each method updates the client.__RESPONSE__ table with the required result and calls dispatchConnectStatus( ) to ask the framework to count all the responses again.

We will see later that these two methods are wrapped in acceptClient( ) and rejectClient( ) , methods that every component developer already knows how to use.

The dispatchConnectStatus( ) method is how the framework counts all the components' responses to the onConnect request for a specific client. Basically, it checks whether there are any components that are still deciding what to do by looking at the value of each slot in the client.__RESPONSE__ table. If there are still pending components, the method returns without doing anything (small thing: the code here could be optimized by returning as soon as a pending status is found, instead of counting all the other responses). When every component has made a decision, if even a single one has decided to reject the client, the connection is rejected by calling dispatchConnectReject( ) . Otherwise, the client is accepted by calling dispatchConnectAccept( ) .

The last two methods that application.asc attaches to the gFrameworkFC.application object (remember, it's still a reference to the good old application object that you know and love) are dispatchConnectAccept( ) and dispatchConnectReject( ) . They are wrappers around the application.acceptConnection( ) and application.rejectConnection( ) methods, but do a little more.

The dispatchConnectAccept( ) method calls this.acceptConnection(client) , which accepts the connection. Next, it loops through the component instances and calls onConnectAccept( ) on each one. (The onConnectAccept( ) method was introduced by the framework to allow for authenticating components, as described earlier in this chapter.)

The dispatchConnectReject( ) method is very similar but has the ability to pass an error message (whatever is stored in client.__ERROBJ__ ) back to the client. The error is passed to NetConnection.onStatus( ) as a property of the info object passed in as a parameter ( info.application.message ). Later, we will see how client.__ERROBJ__ is populated .

That concludes all the handlers that application.asc defines on gFrameworkFC.application . If you repeated the mantra as you were reading the code, you were (hopefully) able to follow so far.

The last part of application.asc is where the real magic happens. Let's look at the rest of the code from the bottom up.

The last real line of application.asc is:

 application = new FCApplication(  ); 

This is not bad. We create a reference, named application , pointing to an instance of the FCApplication class. Any method invoked on application should be a method defined for the FCApplication class. Nothing too strange here. We've done it a million times.

Now let's look at the line that precedes it:

 FCApplication.prototype = gFrameworkFC.application; 

Don't panic. This is simply prototype-based inheritance as is necessary in Server-Side ActionScript to establish a subclass. It simply establishes the FCApplication class as a subclass of gFrameworkFC.application ; as such, the subclass inherits all the methods and properties defined on the superclass.

This inheritance means that all the handlers, methods, and properties that are defined on gFrameworkFC.application are available to instances of the FCApplication class.


Let's not forget that FlashCom automatically invokes handlers on the object that's saved in gFrameworkFC.application . By performing this subclassing and instantiation, any application that defines application.onAppStart( ) and application.onAppStop( ) handlers defines them only on the instance of the FCApplication class named application , not on the object that FlashCom Server calls (which is, once again, gFrameworkFC.application ).

Why do this? We explain it with the last bit of magic, which is the constructor for the FCApplication class:

 // FCApplication class inherits and wraps the actual application object. FCApplication = function ( ) {   // Hide the following members.   this.addListener = null;   this.removeListener = null;   this.requestAcceptConnection = null;   this.requestRejectConnection = null;   // Set up ourselves as a listener on the actual application object.   gFrameworkFC.application.addListener(this);   // Clear all the events.   this.onConnect = null;   this.onConnectAccept = null;   this.onConnectReject = null;   this.onDisconnect = null;   this.onAppStart = null;   this.onAppStop = null;   this.onStatus = null;   // Define   acceptConnection( )   and   rejectConnection( )   // to request the actual application object.   this.acceptConnection = function (client) {     gFrameworkFC.application.requestAcceptConnection(client, this);   };   this.rejectConnection = function (client, errObj) {      client.__ERROBJ__ = errObj;      gFrameworkFC.application.requestRejectConnection(client, this);   }; }; 

Let's look at this gem: it hides some methods, calls addListener( ) , clears some other methods, and then defines the acceptConnection( ) and rejectConnection( ) methods.

Let's look at the addListener( ) call first:

 gFrameworkFC.application.addListener(this); 

Look familiar? It's the same exact call shown in registerComponent( ) in framework.asc ! So what are we doing here? We are adding a special kind of listener to gFrameworkFC.application one that's not a component.

This particular type of listener will receive onAppStart , onAppStop , onConnect , onDisconnect , and onAppStop events just as all the other components will. However, it is not a component class, but rather what the developer thinks is the "application" object (see the hijacking?). This allows a developer to write a main.asc in the same way regardless of whether he is using the communication component framework.


The code hides methods of the FCApplication class that we don't want developers to use (the private ones that we inherited from gFrameworkFC.application ), and we clear those other inherited events that we don't want developers to call (such as onConnect and onConnectAccept ), as developers will override them in their main.asc file.

The last thing the FCApplication constructor does is define acceptConnection( ) and rejectConnection( ) as wrappers for gFrameworkFC.application.requestAcceptConnection( ) and requestRejectConnection( ) , as described earlier.

The application.asc file ( especially the last part of it) was without a doubt the most complex part of the framework's files. If you followed this section, the rest should be fairly easy. Understanding its intricacies will allow you to build efficient components that don't break the framework's model.

14.7.6.4 Analyzing facade.asc

The second file loaded by framework.asc after application.asc is facade.asc (read it "fa §ade", a la Francais ).

This file can also seem pretty complex, unless you understand how the __ resolve( ) method works.

Server-Side ActionScript automatically invokes the __ resolve( ) method on an object whenever other code attempts to access a property or method that can't be found on that object.

It's easier to understand with an example. Suppose you have a class named MyClass , defined as follows:

 function MyClass (  ) { } MyClass.testMethod = function (  ) {   trace("testMethod called"); }; 

Now suppose you have an instance of MyClass named myInstance :

 myInstance = new MyClass(  ); 

If your code invokes testMethod( ) on myInstance , you'll see the trace( ) output as expected:

 myInstance.testMethod(  ); 

However, if you try to invoke a non-existent method:

 myInstance.someBogusMethod( ); 

the Server-Side ActionScript interpreter will throw a runtime error, telling you that "someBogusMethod" is not defined on myInstance (client-side ActionScript fails silently instead of throwing an error).

Now let's imagine that you wanted to inoculate your code against such runtime errors. Server-Side ActionScript gives you a way to trap such problems, with __ resolve( ) .

You can define a __ resolve( ) method like this:

 MyClass.__resolve = function (p_name) {   trace("ERROR. This class does not have a member or a function called " + p_name); }; 

If __ resolve( ) doesn't return a value, the interpreter still throws a runtime error, but at least you will see your trace message first.

To prevent the interpreter from throwing a runtime error, have your __ resolve( ) method return data of the type that the invoker expected.

In the earlier "someBogusMethod" example, you could write a __ resolve( ) handler like this:

 MyClass.__resolve = function (p_name) {   this[p_name] = function (  ) { trace(this.name + " was created on the fly!"); };   this[p_name].name = p_name;   return this[p_name]; }; 

Basically, the interpreter expected to run a function, and __ resolve( ) returns us a function reference, so all is fine. Running:

 myInstance.someBogusMethod( ); 

would now result in this trace:

 someBogusMethod created on the fly! 

Not the most useful function, but no runtime error!

Now let's imagine that someBogusFunction( ) is called again on myInstance .

Because in __ resolve( ) we saved the function in this.someBogusMethod (with this[p_name] ), the __ resolve( ) method doesn't even get triggered any more. Now the someBogusFunction( ) method is defined on myInstance , so there's no reason to call __ resolve( ) again. This is sometimes referred to as "caching" of the new method. Nifty, eh?

Now you know how to create functions on the fly. You could also create full-fledged objects or simple variables on the fly, as well. And that's exactly what facade.asc does, over and over again.

OK, now that we know how __ resolve( ) works, we are ready to tackle facade.asc .

The facade.asc file's job is to set up client-to-server objects that route methods from the client-side code of a component to its server-side code. This is done following the naming scheme described earlier in this chapter:

   ComponentTypeName   .   ComponentInstanceName   .   MethodName   

To route methods from the client to the server, first we define a __ resolve( ) handler on the Client.prototype object. This method is triggered every time a client-to-server call does not correspond to a function attached to the Client.prototype object. Such is the case the first time a client-to-server call on a component is made (those have the form " FCChat.chat1_mc.connect ").

The __ resolve( ) handler in the facade.asc file checks whether there exists a component in gFrameworkFC.components of type ctype (in our example "FCChat"). Notice how __ resolve( ) runs as soon as the interpreter tries to understand "FCChat", effectively splitting the string for us, and then passes only "FCChat" as a parameter of __ resolve( ) . If there is such a component, the code creates a new FCFactory object (defined later) and caches it on the Client.prototype for next time. Otherwise, the __ resolve( ) method doesn't return anything, which generates a server-side error, which in turn generates a "NetConnection.Call.Failed" onStatus event on the client side.

In our example, the data structures described so far are:


Client

Defined by FlashCom Server


Client.FCChat

An instance of the FCFactory class

Let's look at the FCFactory class defined in facade.asc . It also has a __ resolve( ) method. This is invoked right after the FCFactory is created in Client.prototype . __ resolve( ) but, this time, with the second part of the call string as a parameter ("chat1_mc" in our example).

This time around, the code checks whether an instance of the class ( FCChat , in our case) named cname ( chat1_mc ) has already been created and saved in FCChat.instances.chat1_mc . If not, the code needs to instantiate a new instance.

Before it attempts to create an instance, the code needs to check whether the component allows dynamic instantiation ( cclass.dynamic != true ). If so, the code creates a new instance of the class, which calls init( ) on itself (on the base class), which in turn calls registerComponent( ) , which we have seen earlier. The code also stores a reference to the new instance in the cclass.instances table ( FCChat.instances.chat1_mc in our example). So, the next time a call is made on this instance, this part of the code will be skipped and the class won't be instantiated again.

At the end, the code creates an FCFacade for the instance (described later), caches it in the FCFactory for next time, and returns it.

In addition to Client and Client.FCChat described earlier, the newly defined data structures are:


Client.FCChat.chat1_mc

An instance of the FCFacade class


FCChat.instances.chat1_mc

A reference to the chat1_mc instance of the FCChat class

In our example, the Client.FCChat.chat1_mc instance of the FCFacade class is addressed every time a client-side component makes a method call that starts with FCChat.chat1_mc . Let's see what the FCFacade class defined in facade.asc does.

You guessed itit has a __ resolve( ) method to trap the third part of the string, the method name (in our example "connect").

First, the FCFacade class makes sure that the component exists in gFrameworkFC.components ( gFrameworkFC.components.FCChat ). Then it makes sure that the instance exists ( FCChat.instances.chat1_mc ). If this is the case (it would be strange if it wasn't, since the FCFacade has been created by the framework itself), we go on.

This is the last of the tricky parts of the framework code. It defines a function with this code:

 var f = function ( ) {   var args = [this.client].concat(gFrameworkFC.__toarray__(arguments));   var cinst = gFrameworkFC.components[this.ctype].instances[this.cname];   return cinst[name].apply(cinst, args); }; 

The first line creates an array of arguments that has a reference to the client that called the method as the first argument.

The second line finds the instance ( gFrameworkFC.components.FCChat.instances.chat1_mc , in our example).

The third line calls the method ( connect( ) in our case), using the apply( ) call to pass in the arguments. It also returns the same value returned by the component's method, in case it had one.

Then with this instruction:

 // Cache and return the above function. return this[name] = f; 

the code caches a reference to this new "wrapper" method so that __ resolve( ) will not get called next time the method is invoked.

In addition to Client , Client.FCChat , Client.FCChat.chat1_mc , and FCChat.instances.chat1_mc described earlier, the newly defined data structure is:


Client.FCChat.chat1_mc.connect

A little wrapper function that inserts a reference to the calling client as a first parameter and then calls FCChat.instances.chat1_mc.connect( )

The beauty of all the caching is that the next time the " FCChat.chat1_mc.connect " is called by the client, none of the __ resolve( ) handlers will run! We will directly call the little wrapper function that will call the method on the component's server-side code.

Also note that we create only the server-side functions that are needed by a particular client application (we create them only if they are invoked). This makes the server-side framework very efficient, memory-wise.

So that was facade.asc . Pretty nifty, huh? Hopefully this section demystified what __ resolve( ) does and inspired you to use it for your own method-routing needs.

14.7.6.5 Analyzing component.asc

The last piece of the puzzle is component.asc , the base class for all server-side components. It lives in the scriptlib/components folder, and it provides some functionality that's common to all components (as any good base class should).

Most of its code is about event dispatching and trapping for intracomponent communication, which we described earlier in this chapter.

First, it loads the framework.asc , just in case you forgot .

Then there is the constructor. This code is run as soon as a component is loaded. As discussed previously, it uses prototype-based inheritance to make the component a subclass of FCComponent :

 FCChat.prototype = new FCComponent("FCChat", FCChat); 

The FCComponent constructor also creates a static object ( FCChat.instances ) and defines a few static methods on the derived class, such as create( ) , used to create an instance of the component on the server side.

The next section of code in component.asc allows for component-wide listeners (we described those earlier). For example, if you write the following, listObj will be notified whenever the foo property of any FCChat instance changes:

 FCChat.addListener("foo", listObj); 

The derivedType.__LISTENERS__ table and addListener( ) and removeListener( ) methods implement this listening mechanism and are pretty simple to understand. The important thing to know is that the FCChat.__LISTENERS__ is an associative array indexed by property names, with each slot containing an associative array of references to component instances indexed by the component prefix.

For example, if you want to notify chat1_mc that foo has changed in FooComponent, you would look in FooComponent.__LISTENERS__["foo"]["FCChat.chat1_mc"] and find a reference to the chat1_mc object, so that you could call onPropertyChanged( ) on it.

The sendUserEvent( ) handler that follows in component.asc is easy to understand once you know the data structure.

Finally, the FCComponent class calls gFrameworkFC.registerComponent( ) , which we described earlier in this chapter.

Note that the constructor for your component's class hasn't run yet. It runs only when facade.asc creates an instance of it, because it wants to deliver a client-to-server method call to it, and the instance doesn't exist yet. Remember that component instances are created "lazily" on the serverwhen your client-side code tries to call the server side, and not before.

Next, component.asc defines some methods on FCComponent.prototype . These methods will be inherited as-is by the component classes, which all derive from the FCComponent class.

The code defines an FCComponent.prototype.init( ) method, which is called by all well-behaved components at the beginning of their constructor's code. The init( ) method just saves some variables, such as name , prefix , and callPrefix , which can be used by component authors to namespace shared objects and net streams, or to make server-to-client calls.

Next, component.asc defines four utility methods that component developers can use:


getClientID( )

Returns the unique ID that the framework automatically assigns to each client that connects.


getClientGlobalStorage( )

Used for data management, it returns references to objects that component developers can use to store client-specific global information. A client global is different for each client but the same for each component (think of username or userColor ).


getClientLocalStorage( )

Used for data management, it returns references to objects that component developers can use to store client-specific local information. A client local is like a local variable for a component instance, but it's attached to the client object.


releaseLocalStorage( )

Cleans up the client local variables. If your component overrides the default onDisconnect( ) , your component should call this function explicitly to avoid memory leaks in your application.

The next few functions all have to do with event notifications: the static onWatch( ) function and the addListener( ) , removeListener( ) , and sendUserEvent( ) methods. They all use the __LISTENER__ data structure described earlier.

The only tricky thing is the call to:

 this.watch(prop, FCComponent.onWatch); 

This is how Server-Side ActionScript sets up a watch on a particular variable (i.e., a way to get notified when changes to its value occur). The major caveat is that there can be only one watcher for each property.



Programming Flash Communication Server
Programming Flash Communication Server
ISBN: 0596005040
EAN: 2147483647
Year: 2003
Pages: 203

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net