8.5. Temporary and Persistent Shared Objects
The preceding examples
in this chapter all used temporary shared objects. Each temporary
shared object is created either by a server-side script or when the
first client with write access tries to connect to the shared object.
The shared object is disposed of as soon as it is no longer in use by
the server-side script and by its clients. At the very latest, it
will be disposed of when the application instance shuts down. If you
tried out the shared ball example, you may have noticed that if every
client disconnected, the application did not remember the last
position of the ball. The ball position was simply reset by the first
client to connect. The shared ball example does not use a server-side
script, so the shared object is created when the first client
connects and then disposed of when the last client disconnects.
If you need to retain shared object data even after the application
instance quits, you can create a persistent shared object, which is
stored on the server and available the next time an instance starts
up. Use persistent shared objects whenever you need to maintain
application state between instance sessions.
When creating shared objects, you can make them temporary or
persistent (persistent ones can be made to persist only on the server
or on both the client and server). Shared objects that are persistent
only on the client are called local shared objects and are not
covered here (they are created with SharedObject.getLocal(
) as discussed in ActionScript for Flash MX: The
Definitive Guide). If you need to share data between
clients only while an application is running, use temporary shared
objects.
Persistent shared objects are stored as
.fso files in each
application's sharedobjects
directory. A shared object that persists on the server can also be
saved locally on the client. When remote shared objects are stored
locally, they have an .sor extension, which is
different from the .sol extension given to local
shared objects. Locally persistent remote shared objects enable your
application to update the shared object offline and resynchronize
with the server when the connection is reestablished.
When using the getRemote(
)
method on the client, the optional third parameter,
persistence, is a Boolean or string that
determines whether and where the object should persist:
SharedObject.getRemote(SharedObjectURI, AppInstanceURI [, persistence]);
To request a temporary shared object (one not persisted on the
server), pass false or null or
omit the persistence parameter altogether. Passing
true requests a persistent shared object that is
stored on the server only. To store a remote shared object both on
the server and locally, pass in a string that defines the path for
local persistence, as described later under "Locally
and Remotely Persistent Shared Objects." The
getRemote( ) method will return
null if it detects that any of the parameters
passed to it are invalid.
On the server, the second parameter of the
SharedObject.get( ) method specifies
persistence:
SharedObject.get (SharedObjectURI [, persistence [, netConnection]]);
Passing TRue makes the shared object persistent
while passing false or omitting the parameter
means a temporary shared object will be returned. The optional
netConnection parameter is described later under
"Proxied Shared Objects."
Persistent and temporary shared objects occupy different namespaces.
For example, the following two client-side statements return
different shared objects even though the shared
object's relative URI
("Test") and the application URI
("rtmp:/testSO") are the same:
x1_so = SharedObject.getRemote("Test", "rtmp:/testSO", true);
x2_so = SharedObject.getRemote("Test", "rtmp:/testSO", false);
 |
If you specify persistence on the client and non-persistence on the
server (or vice versa), only client-side updates are visible on the
client and only server-side updates are visible on the server. The
problem is that the two shared objects are differentthey exist
in different namespaces even though they appear to have the same
name. Make sure that you are consistent with the type of persistence
you specify on the client and on the server.
|
|
For example, to create a temporary shared object on the server, you
can use:
userList_so = SharedObject.get("public/userList");
To connect to the same temporary shared object in Flash, use:
userList_so = SharedObject.getRemote ("public/userList", nc.uri);
To persist the shared object on the server between sessions, pass
true as the second parameter:
userList_so = SharedObject.get("public/userList", true);
and in Flash, pass true as the third parameter:
userList_so = SharedObject.getRemote("public/userList", nc.uri, true);
Temporary shared objects exist within an application instance only
while a client or the application instance has a reference to it. For
example, if a single client creates a temporary shared object and
then disconnects, the shared object is disposed of. If several
clients connect to the same temporary shared object, when the last
one disconnects or closes the shared object, the shared object is
disposed of.
If a temporary shared object must last through an application
instance session, the application instance should use
SharedObject.get( ) to get a reference to the
shared object and keep it from being disposed of until the instance
itself is disposed of. The application.onAppStart(
) method is a good place to get( ) a
temporary shared object. Provided the application does not close the
shared object, the shared object and the data it contains will be
available until the application instance is disposed of.
When the last connection to a persistent shared object is closed, the
server writes the latest state to the shared
object's .fso file and disposes
the shared object from memory. Later, if a client connects to the
shared object, the contents of the .fso file are
read into memory again by the server and copied to the
client's version of the shared object.
8.5.1. Synchronizing Temporary and Persistent Shared Objects
Temporary and persistent shared
objects are not synchronized in the same way. When you write an
onSync( ) method for a shared object, you must
match your event handling approach to the persistence option you
select. Whenever a client connects to a temporary shared object, any
data already in the client's copy of the shared
object is cleared (deleted). As a result, the client must wait until
it gets the first onSync( ) call before it
updates the shared object. Persistent shared objects are not
synchronized in the same way. Their data properties might not be
deleted before the first onSync( ) is called.
Since handling synchronization is perhaps one of the most complex
aspects of FlashCom development, let's walk through
the differences between synchronizing temporary and persistent shared
objects in detail.
After a Flash movie connects to a shared object with
SharedObject.connect( ), the local copy of the
shared object is synchronized with the remote shared object on the
server. When a temporary shared object is synchronized, any
properties in the local copy are cleared and any data in the remote
shared object is downloaded to the movie's shared
object. As a result, the onSync( ) method of the
movie's shared object will always be passed a list
containing one information object with a code
value of "clear" followed by
information objects with code values of
"change" for each property in the
remote shared object.
For example, if a movie connects to a temporary shared object that
already has three properties named a,
b, and c, then the list passed
into the onSync( ) method will contain four
information objects:
{code: "clear"}
{code: "change", name: "a"}
{code: "change", name: "b"}
{code: "change", name: "c"}
Since temporary shared objects are always cleared whenever they are
synchronized, they cannot be used to read or write data until they
are synchronized. That is why it is essential to wait until the
onSync( ) method of a temporary shared object is
called the first time before using a temporary shared object in a
movie. A common way to make sure the shared object has been
synchronized before using it is to use a private flag in the shared
object to trigger an initialization function. The shared ball in
Example 8-4 uses the same technique to show when the
ball has been synchronized:
so = SharedObject.getRemote("myTemporarySharedObject", nc.uri);
// Create a private flag property named synchronized.
so.synchronized = false;
// Define the onSync( ) handler.
so.onSync = function (list) {
// If synchronized is not set, this is the first call.
if (!this.synchronized) {
this.synchronized = true;
// Call a function or object method, which can now use this shared object.
}
// Do other synchronization work here.
};
// Don't forget to actually connect to the shared object!
so.connect(nc);
Persistent shared objects are not automatically cleared when they are
synchronized. For example, if a movie creates a persistent shared
object and connects for the first time to a shared object on the
server that already has properties a,
b, and c, the list passed into
the onSync( ) method will contain only three
information objects:
{code: "change", name: "a"}
{code: "change", name: "b"}
{code: "change", name: "c"}
If there are no properties on the server's copy of
the shared object, the onSync( ) method will be
passed an empty list.
When using persistent shared objects, the movie may set properties of
the shared object before calling its connect( )
method. In this short code snippet, two properties named
a and b are created before
connecting the shared object. When connect( ) is
called, the local copy of the shared object will send any data it
already has to the server:
// Get a reference to the shared object.
// nc.connect( ) must have been called already (to create the net connection).
so = SharedObject.getRemote("myPersistentSharedObject", nc.uri, true);
// Define a simple onSync( ) method for testing.
so.onSync = function (list) {
writeln("----onSync list.length: " + list.length + "----");
for (var i = 0; i < list.length; i++) {
var info = list[i];
for (var p in info) {
writeln(i + ": " + p + ": " + info[p]);
}
}
};
// Set some initial values for the shared object.
so.data.a = "A is for alienation.";
so.data.b = "B is for the boss...";
// Now, connect the shared object.
so.connect(nc);
If the shared object on the server doesn't have any
properties already, the code produces this output. Note the
onSync( ) method is called twice. The first time
it is called with an empty list. The second time, you can see that
the server has accepted the new a and
b slot data from the local copy of the shared
object and added them to the server's copy:
----onSync list.length: 0----
----onSync list.length: 2----
0: code: success
0: name: a
1: code: success
1: name: b
If the properties a and b
already exist in the shared object on the server, onSync(
) is called only once:
----onSync list.length: 2----
0: oldValue: A is for alienation.
0: code: reject
0: name: a
1: oldValue: B is for the boss...
1: code: reject
1: name: b
If the shared object on the server already has just two properties,
x and y, then this output
occurs:
----onSync list.length: 2----
0: code: change
0: name: x
1: code: change
1: name: y
----onSync list.length: 2----
0: code: success
0: name: a
1: code: success
1: name: b
In this case, onSync( ) was called
twiceonce to notify us about what properties had been added to
the local copy by the server and the second time to tell us what
happened to the properties of the shared object that existed before
we tried to connect.
You can close the connection to a persistent shared object by calling
the shared object's close( )
method. Later, the shared object can be reconnected by calling the
shared object's connect( )
method again without necessarily deleting data in the local copy of
the shared object. This feature makes it possible to build
applications in which the client does not have to be connected to
keep working with shared object data.
Every time the client connects to a persistent shared object and
synchronization takes place, the server compares each shared object
slot's version number in the client to the version
number for each slot on the server. The server sends slot data to the
client only for slots that are not current in the
movie's shared object. For example, if the only
property of the shared object that has changed since the
movie's shared object was closed is a property named
c, then onSync( ), containing
only one information object, is called:
{code: "change", name: "c"}
If there have been no changes, onSync( ) is
passed an empty list.
The persistent shared object synchronization process also manages the
deletion of slots. If a local copy contains a slot that has since
been deleted in the remote version of the shared object, when the
local copy connects again, the local slot will be deleted. For
example, if slot c was deleted on the server and
still exists in the local copy, an information object will be
returned in the onSync( ) list:
{code: "delete", name: "c"}
Similarly, if a property of the local shared object is deleted while
the local copy of a shared object is not connected and no changes to
that property are made in the remote copy, when the shared object
reconnects, the property will be deleted on the server. If the
property was updated on the server while the client was disconnected,
the request to delete the property will be rejected.
8.5.2. Clearing and Deleting Persistent Shared Objects
Even if all
the properties of the shared object have been cleared, the
.fso file on the server, if it exists, will not
be deleted.
If you want to delete the .fso file of a
persistent shared object you must call
application.clearSharedObjects( ). It is similar
to the application.clearStreams( ) method in
that it will clear and delete many shared objects at once and is
passed a path string that can contain the * and
? wildcard characters. The ?
character matches a single character, while the *
matches any number of characters. The clearSharedObjects(
) method returns true if the shared
objects within the path were deleted and false if
they were not. If the following list of shared object URIs are used
to create persistent shared objects, several directories will be
created within the application instance's
sharedobjects directory:
userList
chat/index
chat/history_01
chat/history_02
private/blesser/streamList
private/blesser/accessLog
The actual file paths will be:
.../applications/appName/sharedobjects/instanceName/userList.fso
.../applications/appName/sharedobjects/instanceName/chat/index.fso
.../applications/appName/sharedobjects/instanceName/chat/history_01.fso
.../applications/appName/sharedobjects/instanceName/chat/history_02.fso
.../applications/appName/sharedobjects/instanceName/private/blesser/streamList.fso
.../applications/appName/sharedobjects/instanceName/private/blesser/accessLog.fso
where the path to the applications directory
depends on where FlashCom is installed, and
appName and
instanceName are placeholders for actual
application and instance names.
To delete an individual file, pass the URI for the shared object to
clearSharedObjects( ). For example, to clear and
delete only the userList shared object, use:
application.clearSharedObjects("/userList");
To clear and delete only the chat/index shared
object, use:
application.clearSharedObjects("/chat/index");
To delete shared objects beginning with
"history", use either the
* or ? wildcard character.
Deleting the entire contents of the chat path
also deletes the index shared object. See the
comments for each statement:
// Delete by matching any last two characters after "history_".
application.clearSharedObjects("/chat/history_??");
// Delete by matching any characters after "history".
application.clearSharedObjects("/chat/history*");
// Delete everything in the chat directory and its subdirectories using a "*".
application.clearSharedObjects("/chat/*");
// Delete everything in the chat directory and its subdirectories
// using a trailing forward slash after "/chat".
application.clearSharedObjects("/chat/");
To clear and delete all the shared objects in the
private directory and all its subdirectories,
use either of the following two statements:
application.clearSharedObjects("/private/");
application.clearSharedObjects("/private/*");
Either statement will delete both the private
and blesser directories as well as the shared
object files in them.
To clear all the properties and delete all the
.fso files of all the shared objects owned by an
application instance, use a path string of
"/":
application.clearSharedObjects("/")
Any shared object subdirectories that no longer have any files in
them will also be deleted.
8.5.3. Locally and Remotely Persistent Shared Objects
Persistent RSOs can also be
stored locally much like local shared objects. Local persistence
makes it possible to extend how long a user can work offline before
having to connect to the server. A user can work with a local copy of
a persistent shared object without connecting to the server, save his
work locally, and quit the application. Later, he can retrieve the
locally persistent copy and keep working. Finally, when he needs to
connect to FlashCom, his data will be synchronized with the data on
the server. Locally persistent RSOs can also reduce the bandwidth
required to synchronize to the server, as described a bit later under
"Resynchronization Depth."
When connected to the FlashCom Server, both the local and remote
copies of the shared object are updated. While disconnected from
FlashCom, only the local copy can be updated.
To request that a remotely persistent object also persist locally,
the persistence parameter of the
SharedObject.getRemote( ) method must contain a
local path string similar to the localPath string
required to create a local shared object:
SharedObject.getRemote(SharedObjectURI, AppInstanceURI [, persistence]);
The local path string must be part of the path information from the
URL to the movie's .swf file.
For example, if a .swf is downloaded from:
rtmp://host.domain.com/so/sample1.swf
then the local path string can be any one of the following:
/
/so
/so/sample1.swf
To avoid the possibility that two different Flash movies might
inadvertently overwrite each other's local copy of a
shared object, use the full path to the movie. In the preceding
example:
/so/sample1.swf
note that sample1.swf is the name of a
subdirectory, not the name of a .swf file. The
shared object is stored in a subdirectory based on the local path
string, plus the application instance's URI.
For example, if a shared object named Test were
acquired via a call to getRemote( ) this way:
SharedObject.getRemote("Test", "rtmp:/myApp/myInstance", "/so/sample1.swf");
it would show up on my Windows 2000 computer under the file path:
C:\Documents and Settings\blesser\Application Data\Macromedia\Flash Player\
host.domain.com\so\sample1.swf\myApp\myInstance\Test.sor
The first part of the path, down to the Flash
Player subdirectory, is where all local shared object data
is stored for my account on my computer. The last part of the path:
\host.domain.com\so\sample1.swf\myApp\myInstance\Test.sor
has four parts:
- host.domain.com
-
The hostname of the system from which the movie was downloaded. There
will be a directory for each host accessed.
- so\sample1.swf
-
The path to the .swf file available at the named
host. In this example, the subdirectory is named
sample1.swf after the movie that stored the
shared object in the /so/sample1.swf local path.
- myApp\myInstance
-
The FlashCom application name and instance name where the remote
shared object exists. It is always added after the local path in
order to separate locally persistent shared objects that belong to
different applications and instances.
- Test.sor
-
The file containing the local copy of the remote shared object. The
.sor extension is used for locally persistent
remote shared objects. The
.sol extension is used for purely
local shared objects.
Under some circumstances, it may be desirable that more than one
movie on a given client have access to the same remote shared object
as well as its local copy. Any movie available from the same hostname
and within the path defined by the local path string can access the
local copy of the remote shared object of a different movie.
When a Flash movie disconnects from a remote shared object or
disconnects completely from the server, it can continue to update the
local copy of the shared object. When the movie reconnects to the
server, the local and remote copies are synchronized. The server uses
slot version numbers to determine if each slot in the local copy of
the shared object should overwrite the corresponding slots in the
remote version or if the remote data should overwrite some or all of
the local data. If a slot in the local copy of the shared object has
a higher version number, it will overwrite the slot in the remote
version. In practice, this means that if a local slot in a shared
object has been updated and the remote slot has not been updated, the
local value will overwrite the remote value. However, if the remote
version has changed at all since the client disconnected, the remote
version will overwrite the local versionthe server version
takes precedence.
The client-side onSync( ) method of the remote
shared object will be called when the movie reconnects to the shared
object. Whenever a local value is used to update the remote shared
object, an info object will contain a code value
of "success." If a local change is
rejected, an info object will contain a code value
of "reject". When a local change is
rejected, the old local value is included in the info
object's oldValue property.
 |
Prior to Version 7.2 of Flash MX 2004, ActionScript
2.0's strict type checking caused an error when
using a local path string as the persistence
parameter passed to SharedObject.getRemote( ).
To solve the problem, you should upgrade to the latest release. If
for some reason you can't upgrade, change the
SharedObject definition file at
.../First Run/Classes/SharedObject.as from:
static function getRemote(name:String,
remotePath:String, persistence:Boolean):SharedObject;
to:
static function getRemote(name:String,
remotePath:String, persistence:Object):SharedObject;
to fix the problem.
|
|
8.5.4. Using flush( ) to Write to a Persistent Shared Object
Normally, when a persistent shared object is created and
data added to it, an .fso file is not
immediately written to the server's disk by the
application instance. The file may be written to disk some time later
or when the instance shuts down. In fact, if no file exists and a
persistent shared object has no properties when the instance shuts
down, no .fso file is created. You can force the
instance to write out the current state of a shared object by calling
the Server-Side ActionScript SharedObject.flush(
) method. On the client side, you can also use
SharedObject.flush( ) to write the shared object
to local storage if the remote shared object is locally persistent
(it also works for purely local shared objects). In each case, the
flush( ) method affects only the system on which
it is called. On the server, calling flush( )
forces a write to the server's disk, while on the
client, flush( ) forces a write to local disk.
In two cases, calling flush( ) on the server
does not write the RSO to the disk. Updating any slot in a shared
object increments the version property of the
shared object. If the version number has not changed since the last
time the file was written to disk, calling flush(
) has no effect (there is no point in writing to disk if
the data has not changed since the last write). The second case is
when you lock( ) a shared object. All updates to
a locked shared object are considered part of one batch, so the
version number does not increment until unlock(
) is called. Therefore, if you call flush(
) on a locked shared object, there is no guarantee
anything will be written to disk. A short code snippet illustrates:
so.setProperty("balance", 0);
so.flush( ); // The shared object has changed, so it will be written to disk
so.lock( ); // The lock freezes the version number
so.setProperty("balance", 100); // Property change does not increment version
so.flush( ); // flush( ) does nothing because the version number has not changed
so.unlock( );
To guarantee that the latest version of the shared object is written
to disk, unlock the shared object before calling flush(
):
so.lock( ); // The lock freezes the version number
so.setProperty("balance", 100); // Make a property change
so.unlock( ); // Unlocking allows FlashCom to increment the version number
so.flush( ); // Updated data is written to disk
8.5.5. Resynchronization Depth
By default, persistent shared objects are never
cleared of data when they connect or reconnect to the remote shared
object managed by the server. Version numbers are used to determine
what local properties must be updated on the remote shared object and
what slot changes in the local copy must be rejected. This scheme can
be very efficient for large shared objects because reconnections may
require fewer updates. For example, if a remote shared object has
1000 properties, the first time a client connects to it, all 1000
properties must be sent by the server to the movie. However, if the
client movie also saves a local copy of the shared object, when it
reconnects later, only the slots on the remote shared object that
have changed must be downloaded to the local copy. If only 10 slots
have changed, synchronization is much faster than if all 1000 slots
must be downloaded. However, for applications in which an object is
locally and remotely persistent and many deletions must be performed,
it may be more efficient to simply clear all the local slots and
download all the data from the remote shared object.
Server-side shared objects have a property named
resyncDepth that can be used to control how many
remote version changes must occur before reconnecting shared objects
are cleared and repopulated with data from the remote shared object.
For example, the resynchronization depth can be set this way:
my_so.resyncDepth = 5;
As a consequence, when a persistent shared object connects, its local
version number is added to the resyncDepth value.
If the total is less than the current version number of the remote
shared object on the server, all its properties will be cleared and
all the properties of the remote shared object will be downloaded.
When the data in a slot is deleted on the server, the actual slot and
its associated version number are not deleted. The
resyncDepth property also controls when properties
of a shared object are completely deleted. If the version number of a
slot plus resyncDepth is less than the shared
object's current overall version number, the slot is
completely deleted. The default value for
resyncDepth is -1, which effectively sets the
resynchronization depth to infinity so that the actual slot is never
deleted.
Setting the resynchronization depth of a shared object is not always
the best way to control the complete deletion of shared object slots.
An alternative is to purge stale slots as necessary by calling the
purge( ) method, which accepts a version
threshold for the purge. For example:
my_so.lock( );
my_so.purge(my_so.version - 5);
my_so.unlock( );
In this example, if the version number of the shared object is 100,
any slots with a version number less than 95 will be completely
deleted.
Using the purge( ) method may lead to some
surprising synchronization behaviors when locally and remotely
persistent shared objects are reconnected. For example, a property
may be saved both locally and remotely before disconnecting from an
application. Later, while disconnected, the remote property may be
deleted by another client and eventually purged. When the local copy
reconnects, there will be no record on the server that the property
was deleted, so the local version of the property will successfully
update the remote shared object.
When either the resyncDepth property or
purge( ) method is used, the complete deletion
of slots frees memory for the application. Applications that are long
livedthat is, not frequently garbage collectedand those
that create and delete large numbers of shared object slots should
completely delete them to reduce memory usage.
 |