8.4. Scripting Shared Objects on the Server
Working with remote shared objects in Server-Side
ActionScript is a little different than working with them in the
Flash client. Example 8-5 shows a short
main.asc file that forces users to sign into an
application with a unique username and keeps
track of each user in a remote shared object.
Example 8-5. main.asc file for a simple video chat application
// trim( ) removes whitespace from the beginning and end of a string.
function trim (str) {
if (typeof str != "string") return ""; // Make sure str is a string.
str = str.replace(/^\s*/, ""); // Trim leading spaces.
str = str.replace(/\s*$/, ""); // Trim trailing spaces.
str = str.replace(/\n/g, ""); // Remove new lines.
str = str.replace(/\r/g, ""); // Remove carriage returns.
str = str.replace(/\t/g, ""); // Remove tabs.
return str;
}
// Get a temporary (non-persistent) RSO for a read-only list of usernames.
application.onAppStart = function ( ) {
userList_so = SharedObject.get("public/userList");
};
/* Accept a client whose username is a unique, non-empty string
* and update the userList shared object.
* Each slot value in userList is the path to which each client
* has write access and where other clients should look for
* a stream to which to subscribe.
*/
application.onConnect = function (client, userName) {
userName = trim(userName);
if (userName.length == 0) {
application.rejectConnection(client, {msg: "Blank or missing user name."});
return;
}
var userPath = userList_so.getProperty(userName);
if (userPath) {
application.rejectConnection(client,
{msg: "The user name '" + userName + "' is already in use."});
return;
}
client.readAccess = "public";
client.writeAccess = "public/chat";
client.writeAccess = "public/textchat";
client.userName = userName;
userList_so.setProperty(userName, "public/chat/" + userName);
return true;
};
// Delete records of clients when they disconnect.
application.onDisconnect = function (client) {
userList_so.setProperty(client.userName, null);
};
The server-side SharedObject.get( ) method is
used in place of the client-side SharedObject.getRemote(
) method in Flash and does not require a URI to the
application instance (because it is executed from within the FlashCom
application):
userList_so = SharedObject.get("public/userList");
Like streams, shared objects are identified by partial URIs that are
relative to the application instance. In Example 8-5, the userList shared object
is created within the public path. In the
application.onConnect( ) method, all clients are
given read-only access to the public path so
that they can read but not update the userList
shared object.
Since the server controls the shared object, there is no need in
server-side scripts to call a server-side connect(
) method (there isn't one) or wait for an
onSync( ) event.
Another difference from client-side ActionScript is that server-side
shared objects don't have a
data property. Assigning and
retrieving values of server-side shared objects is done via the
getProperty( ) and setProperty(
) methods. The following statement sets a property of the
shared object. The first parameter in setProperty(
) is always a string property name, while the second
parameter can be data of any built-in ActionScript
datatypewith the same exceptions and caveats mentioned
earlier:
userList_so.setProperty(userName, "public/chat/" + userName);
If an object, array, or other non-primitive data is placed in a
shared object's slot and an element in the object or
array is updated later, setProperty( ) must be
called again to update the shared object. For example:
// Create an object.
var loc = {x: 10, y: 30};
// Copy it into the shared object property named location.
userList_so.setProperty("location", loc);
// Update the object.
loc.x = 89;
// You must copy the object back into the shared object to update it.
userList_so.setProperty("location", loc);
To delete a slot of a shared object in Server-Side ActionScript, call
setProperty( ) and pass null
as the second parameter. In Example 8-5, this
deletes the entry for a user when her client disconnects:
userList_so.setProperty(client.userName, null);
To retrieve the value of a shared object's property
in SSAS, use getProperty( ):
var userPath = userList_so.getProperty(userName);
If a property does not exist, getProperty( )
returns null.
Working with server-side shared objects is not as convenient as
working with them in client-side Flash ActionScript. The
data object of client-side shared objects is not
available on the server, and you have to call setProperty(
) every time you want to update any part of an object or
array within a slot. The data object and automatic
updates available in Flash were left out of the server implementation
for performance reasons. Detecting and managing changes to elements
of the data object is both a CPU- and
memory-intensive process. However, you should not shy away from
scripting shared objects on the server. It is often the right place
to control updates, as described in Chapter 15 and Chapter 18.
Let's look at a quick example. In Example 8-5, clients are given read-only access to a
shared object. The shared object is created before any client
connections are accepted, and all the updates are done on the server
side. It is not uncommon for applications to simply deny write access
to every client, as follows:
application.onConnect = function (client) {
client.writeAccess = ""; // No write access
client.readAccess = "/"; // Read anything
};
Now consider what happens if the server doesn't
create the shared object before the first client is allowed to
connect. If a shared object does not exist when a client with
read-only access attempts to connect to it, the connection attempt
will fail. To avoid the problem, shared objects are often created and
initialized in the application.onAppStart( )
method as in Example 8-5.
8.4.1. Server-Side onSync( )
In Flash, a shared object's
onSync( ) handler can be defined either on
SharedObject.prototype or on an individual shared
object instance. On the server, you cannot use the prototype to
define an onSync( ) handler for all shared
objects; for performance reasons, onSync( )
handlers must be defined for an individual instance. One way to
define an onSync( ) handler for a
SharedObject instance is by using a function
expression, also called a function literal:
ball_so = SharedObject.get("BallPosition");
ball_so.onSync = function (list) {
trace("ball_so.onSync> list.length: " + list.length);
for (var i in list) {
trace("---------- list item number: " + i + " ------------");
var info = list[i];
for (var p in info) {
trace(p + ": " + info[p]);
}
}
};
Another option is to use a function declaration (also called a
function statement) to define the function and then assign it to an
individual instance as necessary:
function genericOnSyncHandler (list) {
trace("ball_so.onSync> list.length: " + list.length);
for (var i in list) {
trace("---------- list item number: " + i + " ------------");
var info = list[i];
for (var p in info) {
trace(p + ": " + info[p]);
}
}
}
ball_so = SharedObject.get("BallPosition");
ball_so.onSync = genericOnSyncHandler;
The shared ball example does not require a
main.asc script. However, if we wrote one and
added the code from either of the previous examples to it, when you
drag the ball around, the output in the App Inspector movie would be
similar to:
Loading of app instance: chapter8/sharedBall/version_1 successful
ball_so.onSync> list.length: 2
---------- list item number: 0 ------------
code: change
name: ball_1_mc_x
oldValue: undefined
---------- list item number: 1 ------------
code: change
name: ball_1_mc_y
oldValue: undefined
ball_so.onSync> list.length: 1
---------- list item number: 0 ------------
code: change
name: ball_1_mc_y
oldValue: 82
Whenever a change occurs as a result of an update from a Flash movie,
the list array passed into the server-side
onSync( ) method includes information objects
with a code value of
"change" or
"delete" and will always contain an
oldValue propertyeven if its value is
undefined. When a change is made using the
server-side setProperty( ) method,
onSync( ) is not called on the server; however,
the client-side onSync( ) method is called for
each copy of the shared object in connected Flash movies, subject to
the constraints described in the next section.
8.4.2. Locking a Shared Object
Client-generated updates of shared
objects do not run in the same thread on the server as SSAS, so
shared object updates can occur in between server-side
setProperty( ) method calls. To guarantee that a
shared object cannot be updated by a client until a unit of work is
complete on the server, use the shared object's
lock( ) and unlock( )
methods. A locked shared object will not notify clients of updates
until it is unlocked, so locking a shared object on the server is
also a good way to group updates together so they are all sent in one
batch to clients. The client-side shared object's
onSync( ) method will be called only after all
the data has been incorporated into the local copy of the shared
object. The following server-side code demonstrates using
lock( ) and unlock( ) properly:
// initPuzzlePieces( ) creates a shared object for each puzzle piece
// containing the initial coordinates, x and y velocity, and state.
function initPuzzlePieces (nClips, xMin, xMax, yMin, yMax) {
var so;
puzzlePieces = [];
for (var i = 0; i < nClips; i++) {
so = SharedObject.get("PuzzlePiece_" + i, true);
// Stop client update requests and stop sending updates to clients.
so.lock( );
// Select a random position within the travel limits.
so.setProperty("x", Math.floor(Math.random( )*(xMax-xMin) + xMin));
so.setProperty("y", Math.floor(Math.random( )*(yMax-yMin) + yMin));
// Select a random direction in which to start moving this clip.
so.setProperty("xVel", Math.floor(Math.random( )*12) - 5);
so.setProperty("yVel", Math.floor(Math.random( )*12) - 5);
// Mark the puzzle piece as being available.
so.setProperty("state", "free");
// Send the new properties for this shared object now.
so.unlock( );
// Keep a reference to each shared object.
puzzlePieces.push(so);
}
}
Locks can be nested, so be sure you have a matching unlock(
) call for every lock( ) you issue.
FlashCom maintains a version number for the shared object as a whole
and for each shared object slot. When lock( ) is
called on a shared object, the version number is not incremented
until the shared object is unlocked. The version number for the
shared object is available in its version property
and is normally incremented after every setProperty(
) call. The version number of each individual slot is not
available to ActionScript.
8.4.3. Clearing a Shared Object
Three server-side approaches
can be used to reliably clear all the properties of a shared object,
so that none are left. One approach is to lock the shared object, get
all its properties' names using
getPropertyNames( ), and then delete each
property using setProperty( ), as follows:
so = SharedObject.get("SOName");
so.lock( );
var names = so.getPropertyNames( );
for (i in names) {
so.setProperty(names[i], null);
}
so.unlock( );
Executing a loop like this will also result in the client shared
object's onSync( ) method being
passed a list of information objects. Each item's
code property will have a value of
"delete." However, it is much
simpler to use the server-side clear( ) method:
so = SharedObject.get("SOName");
so.clear( );
In this case, each client's shared object
onSync( ) handler will receive an array
containing one information object with a code
value of "clear."
The application.clearSharedObjects( ) method can
also be used to clear shared objects as discussed in
"Clearing and Deleting Persistent Shared
Objects" later in this chapter.
8.4.4. Checking the Size of a Shared Object
The size( ) method, available only on the
server, returns the number of properties in a shared object and is a
convenient way to check whether a shared object is already in use:
if (so.size( ) == 0) {
// Initialize some shared object properties.
}
 |