Using databases with the Multiuser Server
January 19, 2000
by Doug Brown
It was at the 1999 UCON that I first began thinking about writing some multiuser articles for DOUG. Not really that long ago, and it's amazing how much changes in that amount of time. In less than twelve months from the initial release of the of the Multiuser Server, (affectionately referred to as Mars) comes Pathfinder: version 2 of the Multiuser Server. This is no small upgrade either. And best of all, the biggest changes fall right into the area this article (it's not a sequel... it's the third in a trilogy) was intended to cover: using databases with the Multiuser Server.
In the previous version of the server, database operations were fairly simple due to the format used. All data files were stored as dBase tables, meaning no data relationships. A user's information had nothing to do with their high scores in different games, what games they liked playing, or anything else. Pathfinder really opens everything up by enabling dot syntax through data objects, and moving to relational FoxPro data files.
With relational tables, everything in the multiuser environment can now be treated as a data object with related properties. A personal confusion on my part has come from the use of the term "attribute" in the documentation instead of "property" for data placed in Pathfinder data objects. Since "attribute" is the term used however, it's the term I'll use here. For the most part though, just think of it as a property. And in the long run, all it really means in truth is "data field."
There are four different types of data objects available in Pathfinder: group, user, application, and player. Group stands somewhat apart from the other objects in nature, as it's only held in RAM. The other three are very closely inter-related though, and have permanent storage on disk.
A group object is created in RAM on the server when at least one user joins the specified group. All groups continue to be named starting with the " / at / " character, such as " / at / TeamOne". When the last user leaves an active group, that group is immediately purged from RAM and ceases to exist. Group attributes are pieces of data stored in the group object. The name of the person who created the group might be stored as an attribute, for example. The possibilities are really unlimited. And this is especially so given that an attribute can hold any data that a Director cast member can, including a Flash member, text, integers, lists, and so forth. You want to create a group with a custom Flash logo? It's really simple with Pathfinder. In Figure 1 the variable flashMember is set to a cast member. Sending a message to the server with the variable being one of the passed parameters then sets the group attribute.
--Setting a Flash member in a Group Attribute flashMember = member "flashLogo" connObj.sendNetMessage("system.group.setAttribute", ¬ "setFlashLogo", [#group: " / at / TestGroup", #attribute: ¬ [#flashLogo: flashMember]])
Once the attribute value is set on the group, it remains until it is either changed or all members leave the group and the memory is purged. Any user entering the group can therefore retrieve the stored Flash member for use in their display.
--Getting a Flash member attribute errCode = ¬ connObj.sendNetMessage("system.group.getAttribute", ¬ "getFlashLogo", [#group: " / at / TestGroup", #attribute: ¬ [#flashLogo]])
Note also that when posting and retrieving attributes, the #attribute property passed is a list. Using the list makes it possible to change and request multiple attributes in a single call. Just make sure that the total resulting message size isn't too large for either the client or server net buffer, or the message will be dropped.
User objects are pretty much what one would expect. A database table on the server is maintained for holding user account information. This includes user name, password, and any other information that a developer would find necessary to define and retain. User accounts also have a user level set, defining what level of control a user can exert on the system. Set up by default, an administrator account has a user level of 80, while standard users would typically have a level of 20. The full list of operations controlled by user levels can be seen (and modified) in the multiuser.cfg file installed with the server.
There are a few different methods that can be used for creating new user accounts in a system, and all of them revolve around executing code shown below. Which method is used really depends upon how sensitive to security a developer is and how much time an administrator wants to spend creating accounts.
-- Creating a new user account errCode = ¬ connObj.sendNetMessage("system.DBAdmin.createUser", ¬ "TestCreateUser", [#name: "Jim Bob", #password: ¬ "bobspassword", #userLevel: 20])
One scenario involves having a common administrator account used by the applications connecting to the server. When a user attempts to log on for the first time, the client application connects to the server as an administrator and creates the account. The client then automatically disconnects and reconnects to the server using the new user information. The upside to this process is that it requires no intervention on the part of a human administrator to create the account. The downside is that it puts the information for a server administrator account out into the world, with the potential that it could be discovered. There are schemes that could be used to minimize this risk, but it will always be there.
A second possibility involves writing a dedicated application that runs on the server. This application connects to the server as an administrator, and waits for messages from the outside world. When a new user needs an account on the server, a request is sent to the server application, which then sends the request to create the account on the Multiuser Server. The upside to this approach is that the administrator account information is never let out into the world, increasing security for the system. The downside of course is that a dedicated Director application needs to be left running on the server system at all times. This is still an automated system though, and very effective.
Finally, there is always the manual entry method. This really isn't practical for most uses of the Multiuser Server, such as gaming rooms, but can be good for educational environments and the like where the user population is small and controlled. Security doesn't get any higher than the manual method, but then neither does the maintenance cost.
Application objects can be used to store information related to a specific application connecting to the server. The application name is the same as the movie name used when connecting a client. This is a good place for storing high scores, level maps, trivia questions, or anything else that might come in handy. Applications need to be explicitly defined on the server however, before any attributes can be set or retrieved against them. This is typically an administrator-level function, and is illustrated here:
--creating an application data object ErrCode = ¬ connObj.sendNetMessage("system.DBAdmin.createApplication", ¬ "createCheckers", [#name: "Checkers", #description: ¬ "Tournament checkers"])
Player data is a combination of user and application information. For some multiuser systems, users may want to go by different pseudonyms within different games. Or perhaps they have different avatars, win/loss records, player state information and the like from community to community within the server environment. Rather than storing all that information within multiple fields on a single user record, it makes more sense to spawn a new record for a given user related to a specific application. That's a player record.
One reason player records provide such a big advantage over storing all the information in the user record is space. Each attribute set on a group doesn't require any special storage considerations since groups are unrelated and volatile. User information on the other hand is stored indefinitely, and each user record must have the same data structure since they are stored in a common table. Storing data in a user record requires that the attribute first be declared using code similar to the example below. If all information for every application were stored in the user record, then all users would need data fields defined for every application entry, whether they used them or not. This quickly gets large, and more importantly difficult to maintain. Player records allow for storing only the information about a given user that is necessary.
--Declaring an attribute ErrCode = ¬ connObj.sendNetMessage("system.DBAdmin.declareAttribute", ¬ "createLogo", [#name: #flashLogo])
As mentioned before, one of the other new additions in this server is support for dot syntax. All the examples in this article demonstrate its use as a great benefit for data operations. In the original version of the server, letting the server know which data function to perform was handled by setting the subject line of the message to the function name. This worked, but didn't allow for setting different callbacks on varying occurrences of the same function call. Functions on the server are now defined in the recipient. A server command is always performed by "system", with the sub-system being "DBAdmin", "DBUser", "DBPlayer", "DBApplication", "Group", "Movie", or "DBF". The command for the system is then the third part of the triplicate, creating a user name of "system.subsystem.command." This frees the subject line to be unique, allowing for different handlers to process different instances of the same type of server response.
A multiuser application that wouldn't benefit from the new features of this Multiuser Server doesn't readily come to mind. Group attributes are a huge step forward, especially in the creation of moderated chat events and similar environments. In developing the Multiuser Zone the new attributes and dot syntax made possible a number of things that wouldn't have been as easily accomplished with Mars. Some of those things unfortunately weren't included in the final launch, but should be available in the downloadable source code for the interested. And if you're thinking really big scale, develop your application on the Multiuser Server and then move over to HearMe's Pop.X server. Pop.X can seamlessly replace the Multiuser Server in a client-server implementation.
And what's next for the multiuser world in Director? Only the development team at Macromedia really knows that for certain. But given the big changes seen in this short amount of time, I'd suspect we will continue to see some of the best new features in multiuser applications a development community could hope for.
Copyright 1997-2024, Director Online. Article content copyright by respective authors.