unity ![]() |
push client demo: overview
In the unity push client demo, we'll look at two related Flash clients that use XMLSocket to communicate with each other in real time. The first--pushAdmin--will send text messages and .swf file urls to the second--pushViewer. pushViewer, in turn, will receive commands from pushAdmin, and display transmitted text or load specified .swf files. Before we examine pushAdmin and pushViewer in detail, you may want to view the demo in action. Try connecting to pushAdmin and pushViewer in separate windows, then use pushAdmin to send commands to pushViewer.
All unity clients are fundamentally concerned with one thing: sending XML to unity and receiving XML from unity. In order to ease this process, we'll use the unityCore
classes to handle the labour of sending and receiving data over sockets. unityCore
is not required to build unity clients, it just provides convenient access to XMLSocket communication and reduces the labour required to write new unity clients in Flash.
download
The files discussed in this tutorial are available here. That archive may not contain (and may not be compatible with) the latest release of unityCore.
xml conventions
Though unity itself does not require the use of any specific XML nomenclature, the unityCore
client module defines a simple XML convention to use when communicating with unity. Each tag sent to and received from unity takes the general form:
<MESSAGE TYPE="someMethodName" FROM="someOrigin">
<SUPPORT_TAG(s)>tags_and_data</SUPPORT_TAG(s)>
</MESSAGE>
Borrowing some of the concepts of XML-RPC, the MESSAGE tag is normally used to trigger a remote method specified by the TYPE attribute. The FROM attribute indicates where the message originated. Inside the MESSAGE tag are any number of arbitrary support tags; these are passed to the specified method as a parameter.
Here is a specific example of a MESSAGE tag sent by a client to unity:
<MESSAGE TYPE="requestRoom" FROM="pushAdmin">
<ROOMID>pushAdmin</ROOMID>
</MESSAGE>
When a client connects to unity, it is placed in a generic dispatcher room and expected to ask to enter a more meaningful room using the above requestRoom
message. The requestRoom
message is the only tag actually required by the base unity server. All other messages are application defined. To create the push demo clients, we'll continue to use the MESSAGE tag convention, but it's important to note that any XML can be used, including more fully developed schemas like RPC. Custom applications, of course, require custom XML implementations on the server side (new unity rooms).
unityCore classes
We'll use the classes in the unityCore
module to get our clients up and running quickly. The three unityCore
classes hide lower level socket communication and provide logging services, as outlined below. (For the curious, documentation describing the inner functioning of the core classes is available via code comments in the classes' source.)
Class | Description | Public Methods | ||||||||||||||||||
SocketManager() | Abstract class (must be subclassed). Provides socket connection, disconnection, and data transfer services. |
|
||||||||||||||||||
MessageProcessor() | Abstract class (must be subclassed). Invokes methods based on incoming XML MESSAGE objects and passes outgoing MESSAGEs to the socket manager for transfer. |
|
||||||||||||||||||
Log() | Sends log entries to log movie clip and Flash Output window. |
|
pushViewer walkthrough
Now that we're familiar with unityCore
and the XML conventions used in unity clients, let's see how the pushViewer client works. Recall the pushViewer's functions:
pushViewer MESSAGE tags - sending
The pushViewer only sends one MESSAGE tag to the server--the requestRoom
tag we discussed earlier:
<MESSAGE TYPE="requestRoom" FROM="admin">
<ROOMID>PushViewer</ROOMID>
</MESSAGE>
The ROOMID
tag is set to "PushViewer", so unity will place our client in the PushViewer room when we send this requestRoom
message. The ROOMID
is an arbitrary string that simply identifies the room to the server.
pushViewer MESSAGE tags - receiving
The pushViewer can receive and respond to four MESSAGE tags, as follows:
<MESSAGE TYPE="numUsers" FROM="unity">
<NUMBER>n</NUMBER>
</MESSAGE>
The numUsers
message tells our client how many other PushViewer clients are connected (specified by the NUMBER
tag).
<MESSAGE TYPE="broadcastText" FROM="pushAdmin">
<TEXT>Hello world...</TEXT>
</MESSAGE>
The broadcastText
message causes PushViewer to display text sent from PushAdmin. The text to display is contained by the TEXT
tag.
<MESSAGE TYPE="loadMovie" FROM="pushAdmin">
<FILEPATH>myMovie.swf</FILEPATH>
</MESSAGE>
The loadMovie message causes PushViewer to display the .swf file specified by the FILEPATH tag in a container movie clip.
<MESSAGE TYPE="eventNotice" FROM="pushAdmin">
<EVENT>reconnect</EVENT>
</MESSAGE>
Finally, the eventNotice
message simply triggers the method specified in the EVENT
tag. eventNotice
provides a generic means of executing any method (without parameters) in a unity client without requiring server-side intervention. In this case, we use eventNotice
to call reconnect()
, which causes the client to disconnect and connect again (used for load testing).
extending the unityCore classes
With our MESSAGE tags defined, we now have to extend the SocketManager and MessageProcessor classes so we can connect to unity and send and receive XML. Our subclasses will be called PushViewerSocketManager and PushViewerMessageProcessor.
the PushViewerSocketManager class
Here's the source of PushViewerSocketManager (stored as a separate .as file in the pushDemo.zip archive):
/************************************************************/
/*
* PushViewerSocketManager Class. Extends SocketManager.
* Version: 0.3.0
* Desc: Implements specific repsonses to generic socket
* connection events.
*/
// ===============================
// Set superclass to SocketManager
// ===============================
PushViewerSocketManager.prototype = new Object.pvMain.SocketManager();
// =================
// Class constructor
// =================
function PushViewerSocketManager (host, port, log) {
// Invoke super's constructor.
this.superclass = Object.pvMain.SocketManager;
this.superclass(host, port, log);
}
// ================
// Instance Methods
// ================
PushViewerSocketManager.prototype.onConnectFailure = function () {
Object.pvMain.gotoAndStop("connectionFailed");
}
PushViewerSocketManager.prototype.onConnectSuccess = function () {
this.requestRoom("pushViewer", "pushViewer");
Object.pvMain.gotoAndStop("clientInterface");
}
PushViewerSocketManager.prototype.onServerKillConnect = function () {
Object.pvMain.gotoAndStop("connectionFailed");
}
PushViewerSocketManager.prototype.onClientKillConnect = function () {
Object.pvMain.gotoAndStop("connectionFailed");
}
/************************************************************/
The PushViewerSocketManager
class's first task is to set up its superclass, SocketManager
, like this:
PushViewerSocketManager.prototype = new Object.pvMain.SocketManager();
The SocketManager
class lives on the _root of the pushViewer.swf file, but we refer to the _root as Object.pvMain
. Each client sets a property of Object corresponding to its _root timeline. This keeps our code portable so the entire pushViewer client could be loaded into another movie.
The PushViewerSocketManager
constructor takes three arguments (which it passes to its superclass's constructor):
host
-a string specifying the default host to which to connect.port
- an integer specifying which port on host
to use.log
- a reference to a Log object for debugging.PushViewerSocketManager
methods provide our client's response to various connection events triggered by the SocketManager superclass. These callbacks define our our application's base network functionality: what to do when a new connection attempt succeeds or fails, and how to handle losing an established connection.onConnectFailure()
, onServerKillConnect()
, and onClientKillConnect()
) contain a single line of code:Object.pvMain.gotoAndStop("connectionFailed");
Ojbect.pvMain
) to goto the "connectionFailed" frame (see /pushViewer/pushViewer.fla in the pushDemo.zip file).onConnectSuccess()
method is fired when a connection attempt with the server completes sucessfully. We use it to:
requestRoom()
method, which takes two arguments, the roomID, and the origin of the request)SocketManager
class.onConnectFailure()
, onConnectSuccess()
, onServerKillConnect()
, and onClientKillConnect()
.the PushViewerMessageProcessor class
The PushViewerMessageProcessor
class provides methods that are called in response to incoming XML MESSAGE tags. Its superclass, MessageProcessor
, handles the XML itself; PushViewerMessageProcessor
need only implement the appropriate methods. Earlier we learned that pushViewer could respond to four MESSAGE tag types: numUsers, broadcastText, loadMovie, and eventNotice. In the source for PushViewerMessageProcessor
below, we see these methods implemented:
The methods of
/************************************************************/
/*
* PushViewerMessageProcessor Class. Extends MessageProcessor.
* Version: 0.3.0
* Desc: Responds to the receipt of various message types sent
* by the server to the client. Each method deals with one
* type of message.
*/
// ==================================
// Set superclass to MessageProcessor
// ==================================
PushViewerMessageProcessor.prototype = new Object.pvMain.MessageProcessor();
// =================
// Class constructor
// =================
function PushViewerMessageProcessor (log) {
// Pass our arguments on to the super class constructor.
this.superclass = Object.pvMain.MessageProcessor;
this.superclass(log);
}
// ================
// Instance Methods
// ================
/*
* Handles ALL generic eventNotice MESSAGESs broadcast by server.
* Invokes the method on the PushViewerMesssageProcessor that corresponds
* to the EVENT tag in the MESSAGE.
*/
PushViewerMessageProcessor.prototype.eventNotice = function (msg) {
// If the method exists...
if (typeof this[msg[0].firstChild.nodeValue] == "function") {
// ...invoke it.
this[msg[0].firstChild.nodeValue]();
} else {
// ...otherwise, generate an error message
this.log.addEntry("Event could not fire. No such method: "
+ msg[0].firstChild.nodeValue);
}
}
/*
* Handles load movie commands sent by server.
*/
PushViewerMessageProcessor.prototype.loadMovie = function (msg) {
Object.pvMain.movieViewer.viewClip.loadMovie(msg[0].firstChild.nodeValue);
Object.pvMain.broadcastTextOutput = "loading new movie...";
}
/*
* Handles text messages sent by server.
*/
PushViewerMessageProcessor.prototype.broadcastText = function (msg) {
Object.pvMain.broadcastTextOutput = msg[0].firstChild.nodeValue;
}
/*
* Handles reconnect eventNotices.
*/
PushViewerMessageProcessor.prototype.reconnect = function (msg) {
Object.pvMain.gotoAndPlay("reconnect");
}
/*
* Displays number of connected users.
*/
PushViewerMessageProcessor.prototype.numUsers = function (msg) {
// Retrieve and format the number.
var numUsers = msg[0].firstChild.nodeValue;
var numChars = numUsers.length;
var extraZeros = 4 - numChars > 0 ? 4 - numChars : 0;
var zeroString = "";
for (var i = 0; i < extraZeros; i++) {
zeroString += "0";
}
// Output the number to screen.
numUsersOutput = zeroString + numUsers;
}
/************************************************************/
PushViewerMessageProcessor
are generally simple. The broadcastText()
method, for example, does only one thing: sets the value of an onscreen text field. The loadMovie()
method loads a movie into a movie clip container on the pushViewer's main timeline. The methods dictate what should happen when the corresponding MESSAGE tag arrives.
Each method is passed a single parameter, msg
, which contains the array of child nodes (tags) descending from the MESSAGE tag. Each method uses the information in these child nodes to perform its task.
One method bears special attention: eventNotice()
. It extracts the text contained by the EVENT tag and calls the method of that name, without any parameters. In the line:
this[msg[0].firstChild.nodeValue]();
the this
refers to the PushViewerMessageProcessor
object and msg[0].firstChild.nodeValue
is the EVENT tag's text. We use the []
operator to access the property specified by the EVENT tag's text, and we invoke it using ()
.
So, in general, any new client can start responding to MESSAGEs in two steps:
MessageProcessor
class.sendMessage()
method is inherited by our PushViewerMessageProcessor
class, so we could send messages to unity like this:msgProcessor.sendMessage(XMLmessage);
PushViewerMessageProcessor
doesn't actually send any messages to unity. The only message sent to unity from pushViewer is requestRoom
, and that's sent by the SocketManager class directly. We'll learn more about sending messages when we study pushAdmin.
using the pushViewer classes (pushViewer.fla)
Now that we know how the pushViewer classes work, let's see how to use them in our flash .fla file, pushViewer.fla. After some preloading, pushViewer.fla runs the code on the frame labelled "main", the first half of which is listed below. Read through the comments to learn how the classes are imported and initialized.
Once our objects are created and initialized, our next task is normally to set a server and port (if one wasn't set by the constructor) and then connect, as follows:
/************************************************************/
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// unity pushViewer
// version 0.9.0
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// First, we set a property of Object to the main timeline. All
// our classes use this instead of _root so we can safely load the whole
// pushViewer.swf file into another movie.
Object.pvMain = this;
// Now we load the general socket communication classes of unityCore.
// This just brings in the source code...we haven't instantiated
// anything yet.
#include "../unityCore/SocketManager.as"
#include "../unityCore/MessageProcessor.as"
// Here we load the push viewer subclasses we studied earlier. Again,
// this is just a load, no objects have been made yet.
#include "PushViewerSocketManager.as"
#include "PushViewerMessageProcessor.as"
// Now we actually instantiate our socket communication tools,
// PushViewerSocketManager and PushViewerMessageProcessor and store
// the objects in sockMan and msgProcessor. Initially, we set
// the sockMan server and port to "" and 0 respectively. We'll set
// that information later once we load it in from the config file.
// The null value passed to both constructors is the log...we aren't
// logging in the client, so there's no Log object to pass.
sockMan = new PushViewerSocketManager("", 0, null);
msgProcessor = new PushViewerMessageProcessor(null);
// We have made our two socket communication objects, but they don't
// know about each other. Our last task is to provide each object with
// a reference to the other. They use the references when passing
// information back and forth.
sockMan.setMsgProcessor(msgProcessor);
msgProcessor.setSockMan(sockMan);
/************************************************************/
The results of
// Tell the socket manager which server we want to use.
sockMan.setServer("someserver.com", 2001);
// Attempt to open socket...
sockMan.doConnect();
doConnect()
are all handled by the callbacks of sockMan
(an instance of PushViewerSocketManager
). No other code is required to connect when the unityCore subclasses are in place. In the specific case of pushViewer, the server to which to connect is actually loaded from an external XML configuration file (pushViewerConfig.xml). The doConnect()
call comes after the config file loads (see the pushViewer.fla file).
If the connection attempt is successful, sockMan.onConnectSuccess()
fires and the movie displays the "clientInterface" frame. From that point forward, all messages received by sockMan
are passed to msgProcessor
, which in turn executes methods specified by the XML MESSAGE tag's TYPE attribute.
pushAdmin walkthrough
Recall that pushAdmin sends commands to pushViewer, causing pushViewer to display text and movies specified by pushAdmin. The pushAdmin client works almost exactly like the pushViewer--it:
PushAdminMessageProcessor
implements only a single method.the PushAdminMessageBuilder class
Because pushAdmin sends a variety of commands to pushViewer, it requires a class pushViewer did not: PushAdminMessageBuilder
. This class assembles XML MESSAGE tags to be transferred to unity.
Here's the source for PushAdminMessageBuilder
:
For each type of MESSAGE tag that pushAdmin sends to unity, the
/************************************************************/
/*
* PushAdminMessageBuilder Class
* Version: 0.2.0
* Desc: Creates XML fragment messages to be sent to the server.
* Each method builds one type of message.
*/
// =================
// Class constructor
// =================
function PushAdminMessageBuilder () {
// Default constructor. No initialization required.
}
// ================
// Instance Methods
// ================
// All methods create tags to be sent to the server from the admin.
// See xml structure files for documentation on each tag.
/*
* Broadcast methods
*/
// Makes a broadcastText tag
PushAdminMessageBuilder.prototype.createBroadcastText = function (text) {
var msg = '
PushAdminMessageBuilder
class provides a corresponding method. For example, to generate a MESSAGE of TYPE "broadcastText", we invoke createBroadcastText()
, and pass it the text we wish to broadcast. The createBroadcastText()
method wraps the text in an appropriate XML MESSAGE tag and returns that tag as a string. Note that there's no need to parse the MESSAGE string into an XML object--by passing XMLSocket.send() a string we avoid the cost of generating an XML object.
sending messages to unity from pushAdmin
Once the PushAdminMessageProcessor
and PushAdminMessageBuilder
objects are instantiated, sending messages to unity is trivial. We call the sendMessage() method of the PushAdminMessageProcessor
object (msgProcessor), and pass it the MESSAGE tag to send:
msgProcessor.sendMessage(MESSAGE_TAG_GOES_HERE);
Of course, we're building our MESSAGEs with our handy PushAdminMessageBuilder
object, so here's how we actually send three types of messages to unity from pushAdmin:
msgProcessor.sendMessage(msgBuilder.createBroadcastText(broadcastTextInput));
msgProcessor.sendMessage(msgBuilder.createLoadMovie(swfFileInput));
msgProcessor.sendMessage(msgBuilder.createEventNotice("reconnect"));
pushAdmin logging
For the sake of simplicity, pushViewer did not include any logging. But we're now familiar with the general unity client system, so let's see how to add a logging feature to pushAdmin.
The Log class comes as part of unityCore. We use it to send messages both to the Flash Output window (Test Movie mode) and to a text field in a custom movie clip. Here's how we create a functioning log:
logWindow
that contains a text field named logOutput
.#include "../unityCore/Log.as"
sysLog = new Log(logWindow, 300);
sockMan = new PushAdminSocketManager("", 0, sysLog);
msgProcessor = new PushAdminMessageProcessor(sysLog);
addEntry()
method. We can also use addEntry()
whenever we want to send a message to the log. For example:sysLog.addEntry("this is a test");
hide()
and show()
methods.
conclusion
The pushDemo has showed how a specific implementation of a unity client works. To view an abstracted list of steps required to produce a client using unityCore, see: the shortest route to a new unity client