Multiuser - Asynchronous Coding
July 20, 1999
by Doug Brown
For about a year, I have been kicking around an idea for an online game based on one of my favorite board games. Being a fan of the Starcraft games, I decided that the lobby metaphor (where users meet before a game to chat and exchange tactics) seemed an excellent way to go. I knew that XtraNet would be a good way to get different machines to talk to each other, but the server component was elusive. How would I go about managing user accounts, score tallies, ladder statistics, etc.?
I really didn't know about the multiuser server included with Director 7 before receiving my disc and manual. And I didn't really see much pre-release promotion of it, and there hasn't been much post-release talk of it either. There seems to be a small contingent of Director programmers taking a keen interest in it, but on the whole it seems kind of overlooked. I was pretty darned amazing to find out that it can do everything that I would need to create my game.
The multiuser Xtra is a means for Director applications on different computers, or even the same computer, to communicate across a TCP/IP network. Using TCP/IP does include all its pitfalls including latency, lost data, dropped connections, and a host of other potential problems. Therefore, the first thing to understand when using the multiuser Xtra doesn't involve syntax, but understanding the philosophy of asynchronous coding.
A sample chat application is available for download in Mac or PC format.
The best way to explain this concept is to contrast it with synchronous code, as you would typically see when programming in Lingo.
- User releases the mouse button
- The system generates a mouseUp event
- code in behavior, frame, and/or movie script acts on event
This is pretty straight forward and without much room for error. You know that when the user clicks the mouse, you'll immediately get the message in order to react.
Asynchronous coding is much different in that there is a substantial amount of time that passes between the generation of a message and the opportunity to react. Making a user wait indefinitely for a message to arrive before the application proceeds is never a good idea, so instead you need to let certain processes wait "in the wings". This leaves an application open to run other processes while waiting for an incoming message to arrive. Keeping those processes "in the wings" is done through the use of callbacks.
A callback can be defined as a custom handler that is earmarked for execution in response to a certain condition. Setting the keyDownScript can be considered a form of callback. By setting the value of the keyDownScript a program specifies which statement or Lingo handler to execute every time a key is depressed.
Setting a multiuser Xtra callback is done through a call to setNetMessageHandler. The setNetMessageHandler command is used to tell an instance of the multiuser Xtra what handler to use to process an incoming message. For example, if an instance of the multiuser Xtra is held in the variable connObj, all incoming messages on that connection can be handled by the code contained in on defaultHandler this Lingo.
on startMovie global connObj connObj = new(xtra "multiuser") setNetMessageHandler(connObj, #defaultHandler) end
Since each multiuser message always contains error, sender, subject, content and server time information, the code in on defaultHandler could also have special routines to handle certain messages. The down side to this is that if there are a number of different types of messages coming across the connection, this scheme will quickly become very difficult to control. A limited amount of this type of checking can be very beneficial for related types of messages, but for diverse situations it's certainly not the most efficient method of delegation.
Fortunately, the bright people at Macromedia have designed the setNetMessageHandler to allow for setting more than one callback per connection. This effectively lets setNetMessageHandler act as a traffic cop for the incoming messages. If all messages from the user "Bob" need to be sent to the on bobHandler, a callback specific to that situation can be set.
setNetMessageHandler(connObj, #bobHandler, 0, EMPTY, "Bob")
If all messages from Bob with the subject "chat" need to go to on bobChatHandler, the Xtra can automate that as well. This makes writing, debugging, and reading much simpler and far more understandable.
setNetMessageHandler(connObj, #bobChatHandler, 0, ¬ "chat", "Bob")
A third parameter that can be used in setting a callback is an object reference. This is extremely useful in any situation where something like an avatar is created. Since avatars are generally made as objects designed to be self-contained, and more than one can exist at a time, this is a real plus. Using an object reference lets the multiuser Xtra send all messages matching the callback criteria to a handler in a specific object. So if there are five avatars created from the same parent script, each will receive only the messages they are intended for. For instance, all messages from particular user, with the subject "chatMsg," can be sent to an on chatMsg handler in a particular object .
setNetMessageHandler(me.connObj, #chatMsg, me, ¬ "chatMsg", me.avatarUser)
So if the first step to understanding multiuser programming is the concept of asynchronous code, then error handling has to be the second.
If you've ever played a LAN game of Quake, you've likely seen what can happen when a network error is handled somewhat gracelessly. In a LAN Quake session, one of the players acts as the server for the session. If that player drops out of the game for any reason, the whole game ends. And in cases of the server being dropped due to an unexpected network error, the game can end rather violently even for the likes of Quake. The potential for such errors awaits even the simplest multiuser application such as a chat system. Prevention is impossible, so preparedness is key.
One error, in particular, has afflicted the chat application accompanying this article. In the event of an unexpected disconnection from the chat there would be no exit message sent to the other participants. The result being that an avatar's owner would no longer be in the chat, but the avatar would persist on all the other participants' screens. Further still, if the owner then rejoined the chat, all users who were still participating in the chat would see a second avatar appear for the user. The first avatar no longer received messages, and never went away until the application was closed.
Such errors in network communication are inevitable. The solution I've adopted has been to have each avatar check periodically to make sure its owner is still connected to the chat. If not, then the avatar self-destructs. Additionally, if the owner rejoins the chat before the avatar is destroyed, the controller creating the avatars first destroys the existing avatar before creating a new one.
It seems somewhat complicated to go through this series of events just to have a participant in a chat, but really it isn't too far different than thinking about the dynamics of normal conversation at a party. If an individual (call him "Bob") were a participant in a group conversation at a dinner party, the door to the house would effectively be the controller creating Bob. Once Bob makes his presence known, we create a cognitive placeholder for him. Should Bob unexpectedly leave without saying goodbye, we would still be under the impression he is somewhere in the party and may try to make reference to him. Thinking of how we handle such events naturally provides a good basis for building the logic into handling many errors in multiuser coding.
Given the volatile nature of each message as it is sent across the network, the very first thing that should be done when a message is received is to check for errors. Each callback handler should either handle error management itself or defer to another handler. When a message arrives, one of the properties included is an error number. A value of 0 indicates no problem and all other numbers indicate some difficulty.
msg = getNetMessage(me.connObj) errCode = msg.errorCode if errCode then errText = getNetErrorString(me.connObj, errCode) exit end if
The message is read in and checked for errors. In this case, if an error exists, then the processing of the message will end without notification. For the chat program this will only affect the display of a chat message to the user receiving the error. To ensure that all users received all messages, any message trapped for an error could be requested from the sender again. In other situations, such as a game in which information needs to be identical across systems, better handling would be necessary.
While there is much more that can be covered regarding the multiuser Xtra and server, the basics that all multi-user applications are based upon are asynchronous code and error handling. Hopefully this introduction to these topics has given you an understanding of the fundamentals you need to start exploring the many uses of the multiuser Xtra and server.
A sample chat application is available for download in Mac or PC format.
Lastly, I also want to give plugs for a couple of really good resources you can check up should an uncovered question need answering. Macromedia hosts the Usenet group macromedia.director.multiuser at forums.macromedia.com. Another resource is http://poppy.macromedia.com/~sallen/multiuser. Every Thursday evening at 5pm PST there is a public chat where various multiuser programmers get together for a couple hours. The Macromedians that created the multiuser Xtra and server also frequent both of these locations.
Copyright 1997-2024, Director Online. Article content copyright by respective authors.