Welcome to the World of Avatars

Virtual Worlds

Subscribe to Virtual Worlds: eMailAlertsEmail Alerts newslettersWeekly Newsletters
Get Virtual Worlds: homepageHomepage mobileMobile rssRSS facebookFacebook twitterTwitter linkedinLinkedIn


Virtual Worlds Authors: Kevin Benedict, Jyoti Bansal, David Dodd, Pat Romanski, Corey Roth

Related Topics: Java Developer Magazine, Virtual Worlds

Java Developer : Article

Developing Collaborative Games Using Active Objects

Developing Collaborative Games Using Active Objects

Abstract
This is the first of a two-part series presenting a Java implementation of a real-time multi-user blackjack game based on a collaborative, active object framework. In this article, we will walk through the design of an active object framework for developing collaborative client/server applications. Important concepts, such as synchronous collaboration, active objects, multicasting, sessions and events are defined and discussed.

Introduction
In the world of collaborative multi-user software, or groupware, there are two main collaboration models: synchronous and asynchronous. The asynchronous model allows multiple users to exchange and share information through asynchronous messages, sent at different times to a central location. Plain e-mail, bulletin boards and Lotus Notes are examples of asynchronous groupware. The synchronous model, on the other hand, allows information sharing on a real-time basis; that is, all participants interact at the same time. Chat systems and multi-user virtual worlds are examples of synchronous groupware. Both models of collaboration are necessary in certain situations, yet most so-called groupware today ignores the synchronous collaboration model. If the goal of collaborative software is to let people work together concurrently across the boundaries of time and space, then they should be able to do so in real-time, in order to allow immediate feedback and achieve faster group consensus.

The applications of groupware are far-reaching, especially those of synchronous groupware, and will revolutionize computing in the next decade. Java has come at the right time to help fuel this revolution.

Java: An Enabling Technology for Real-time Collaboration
The development of Java has made it easier than ever to create synchronous groupware over intranets and the Internet. Java is the perfect platform to build groupware due to its built-in cross-platform networking features, and its ease of access through popular browsers. In this article, we will demonstrate one approach to building synchronous collaborative software in Java, by designing around an active object model. This approach considerably simplifies implementation of collaborative client/server protocols. A collaborative client/server protocol specifies control flow among one server and a set of clients. To illustrate the active object approach, we present a simple framework for an active object model. A sample implementation of a multi-user blackjack game using the framework will be presented in Part 2.

What is an Active Object?
We distinguish active and normal (passive) objects by the following: Active objects possess both a proactive and a reactive event handler, while a passive object uses only reactive event handling. In other words, active objects are characterized by having a thread executing a state-based event protocol, in addition to normal callback-based (reactive) event handling. Most applications today are written only using callback-based event handling. This is where an application registers callback methods with the runtime system, which calls one of the application's callback methods when an event occurs. Java's AWT event handling system (both 1.02 and 1.1) is an example of a callback-based model. A threaded active object, on the other hand, actively waits for a set of events to occur, suspending its thread until one of the desired events occurs. The active object model permits straightforward implementations of client/server control flow, where events may be causally dependent upon one another. Implementing control flow logic is vastly complicated by having to spread the protocol's implementation across many callback methods.

As shown in Figure 1, an active object contains two main components, an active event handler used to implement client/server control flow, and a passive event handler used to implement status updates. The active event handler is intended to process events with causal dependencies between each other, while the passive event handler is intended to process idempotent, self-contained events (i.e., events that have no causal dependencies between each other). There should also be no causal dependencies between events used by the passive handler and events used by the active handler. For example, in a multi-player blackjack game, the active hander would control the game flow with respect to the client player's game. All other status events relating to other players on the same table would be handled by the passive handler.

There are alternative approaches to building a collaborative system, such as using RMI. However, RMI is practically equivalent to a pure callback model, and RMI is not fully supported in current browsers, making it unusable for deployment of collaborative applications over the Internet today.

Let's define an abstract class ActiveObject to represent our concept of an active object.

public abstract class ActiveObject extends Thread
{
public abstract void run();
public abstract void handleEvent(Object evt);
}

Inter-object events are implemented as standard Java objects. Each event type is identified with a unique integer.

The run() method is where the active control flow is implemented, whereas handleEvent() is used to process callback events.

Note that handleEvent()'s thread should never be suspended (i.e., no blocking statements should ever be executed inside handleEvent()) because its thread is the thread that the system uses to process all incoming callback events.

The control flow thread in the run() method should interact with other objects, either by waiting for events or sending an event to another object. Let's define two basic methods in class ActiveObject to serve these purposes.

protected synchronized Object waitFor(int[] evts) throws ProtocolException; protected void send(Object evt, int destID);

waitFor() takes an int array containing "hash codes" of events that the control flow thread inside run() is currently expecting. It either returns with an arriving expected event, or throws a ProtocolException to signify that an event was received which was not expected. This makes the control flow processing deterministic, and results in the automatic checking of client/server protocol correctness.

Send() transmits an event to the destination object, specified by destID, which is a numeric identifier assigned to each member of a session (described later).

Both of these methods are protected to make sure that only the owning active object uses these methods. (Only run() should use the waitFor() method since it is a blocking call. send() is non-blocking and may be used by both handleEvent() and run().)

We also want support for callback-based event handling, so we define

protected void registerCallback(int[] evts);

to allow the active object to specify which events should be handled using the callback model in the handleEvent() method.

Implementing Event Queuing
Active event handling requires that the object possess an event queue, which collects events from other active objects and distributes them in first-in, first-out (FIFO) order to the object's active and passive event handlers. For simplicity, we will implement a FIFO queue using a Vector, although more efficient implementations using linked lists or piped streams may also be used. For an implementation using piped streams, see Tips & Techniques in Java Developer's Journal (Vol. 2, Issue 8) by Brian Maso.

A queue obviously requires a way of putting arriving events into the queue. We define

public synchronized void pushEvent(Object evt);

as the means for putting an arriving event into the queue. This method will first check if the arriving event is intended for processing by a callback method. If so, it calls handleEvent() immediately; otherwise, the arriving event is pushed into the queue. Moreover, if the run() method is actively waiting for this arriving event, it immediately calls notify() to wake up the thread so that the arriving event may be processed.

Implementing the waitFor() method is straightforward. First, we save the array of expected events so that pushEvent() may be aware of them when an event arrives. Then, if the queue is empty, the thread waits (using wait()) until an event arrives, which is when pushEvent() wakes up the thread using notify(). If the queue is not empty or the thread is awakened, it proceeds to check that the first event in the queue is an expected event, returning it if it is an expected event, or throwing a ProtocolException if it is not.

Adding Support for Collaboration
To support collaborative client/server protocols, the active object framework must support event communication among a set of objects interacting within a group. Any object wishing to notify all other objects in the group should be able to multicast an event to the rest of the group with one call, without having to track the other group members themselves.

We introduce the concept of a session to represent a group of collaborating active objects. A session is identified by its session name and its session instance number. Since it may be necessary to run multiple sessions concurrently, the session instance number (or sessionID) distinguishes between multiple instances of the same session protocol.

In the Java/Internet environment, making collaboration work among applets requires a server program to coordinate all participants in the session. This is because 1) an applet may only connect to its originating server, and may not communicate with other applets directly, and 2) a server is needed to start a daemon for each active session to provide a central location to which clients can connect. Thus, our Java collaborative model resembles Figure 2.

Let's define a class called ActiveSession to represent our concept of a session. The "Active" prefix is just to remind us that it represents a group of active objects and has nothing to do with Microsoft's ActiveX.

public class ActiveSession extends Object
{
public ActiveSession(String sessionName, int sessionID);
public static void join(String sessionName, int sessionID)
}

A session is started by the server-side active object using

new ActiveSession(sessionName, sessionID);

In our framework, a server is always identified with a numeric id of 0. Thus, clients always send an event to the current server using with a destID of 0, i.e., send(evt,0);

Then, clients join the session by calling

ActiveSession.join(sessionName, sessionID).

A particular session type and instance (sessionID) must be started by a server before any client can join it using the same session name and sessionID. Any number of parallel sessions, each with a unique sessionID, may be started by the server.

Clients are assigned a numeric id starting from 1, up to the maximum number of clients allowed in the session. This numeric id may be chosen by the client, or it may be dynamically assigned by the session entity by finding, for example, the first available slot in the session. The sample implementation given in the code listings lets the clients choose their own numeric id.

Of course, we also need to add a multicast command to our API to send an event to everyone in the group.

public void mcast(Object evt, int omitID)

The omitID allows the multicast sender to omit sending a copy to someone in the group. Usually, the sender will not need a copy of the multicast event, so omitID usually specifies the sender itself.

Implementing Network Communication
Full networking support is not given in the code listings due to space limitations. Instead, we do provide a simulated network in Listing 1b so that the collaborative client/server protocol may be demonstrated and tested using one program, by instantiating a server-side active object and several client-side active objects all in the same program. The clients and server will communicate strictly through event-based communication, as if they were talking across the network. However, we will discuss here, issues in implementing network support for our active object framework.

  • ActiveSession constructor: Creating an ActiveSession object is equivalent to starting a daemon to listen for incoming client connections. However, we need to consider the usage of port numbers. Either all session types use the same port number, or each session type uses a separate port number. If all session types use the same port number, only one server socket (java.net.ServerSocket) needs to be created. Otherwise a server socket needs to be created for each session type in use. Note that there may also be multiple sessions instances for a single session type. Communications for multiple session instances of the same session type may be multiplexed through the same socket, but of course the server needs to sort out events as it receives them and forward them to the appropriate sessions. This implies that each event must carry a session name and sessionID.
  • ActiveSession.join(): A request to join a session from the client translates to a connection request to the server on a certain port---the port associated with the desired session. The server, upon a connection request, should check whether the requested session name and session instance are active (i.e., have already been started by the server).
  • ActiveObject.send(): Since we've chosen to use class java.lang.Object as the base class for our events, we need to figure out a way to serialize the data members, or instance variables, of any arbitrary subclass of Object. In Java 1.1, serialization is available to aid in this task. Unfortunately, in Java 1.0.2 which resides in most browsers in use today, strictly speaking this is impossible. However, we may simulate serialization by manually converting each of the values of the object's instance variables into a serialized form in the toString() method. We also need to provide a means of deserializing the values of the instance variables and recreating the event object. This will require adding a required fromString() method (not part of java.lang.Object), so another derived abstract class or interface is required.
  • ActiveObject.mcast(): An ideal implementation of group multicast would use multicast sockets (java.net.MulticastSocket) to communicate efficiently with other group members. However, applets are not allowed to open multicast sockets, and even if they were, operation of a multicast socket through the Internet requires the underlying support of the Multicast Backbone (MBONE), which is not yet widely deployed. Moreover, multicast sockets only support unreliable datagrams, and would thus require designing a reliable protocol. An interim solution, though inefficient, is to use iterative unicasting through multiple TCP sockets to achieve the same effect; that is, the server sends a copy of the event to each connected client in the session.

    Listing 1b implements a simulated network within the ActiveSession class. Implementing full networking support is left as an exercise to the reader.

    Putting it to Work
    Now, to build a collaborative system using active objects, we may subclass ActiveObject to define a server active object, and define another subclass for client active objects. Required event types are defined by subclassing Object and implementing hashCode() to return a unique integer identifier for each event type. We must carefully design the protocol such that causally dependent events are processed in the run() method, while the self-contained, independent events are processed in handleEvent().

    To illustrate in detail active objects at work, the second part of this series will present a multi-user blackjack game built using our collaborative active object framework.

    Conclusion
    Active objects are a useful paradigm for implementing collaborative client/server protocols. They allow for intuitive coding of client/server control flow, rather than being forced to use awkward callback methods to process causally dependent events. Active objects are similar to actors and agent-oriented programming, well-known in the distributed systems research community. The active object paradigm has been used to successfully implement a full-scale multi-user virtual world populated with avatars and collaborative games. This virtual world is named Funtopia, at http://www.funtopia.com.

    A complete example for the framework presented is available at http://www.avanteer.com.

  • More Stories By Todd A. Busby

    Todd Busby is a project manager at Avanteer, Inc. Todd holds an MS in Computer Science from the California State University, Fullerton.

    More Stories By Larry T. Chen

    Larry T. Chen is a co-founder of Avanteer, Inc., a company focusing on developing Java-based collaborative software. He is also pursuing a Ph.D. degree in the area of distributed systems at the University of California, Irvine.

    Comments (0)

    Share your thoughts on this story.

    Add your comment
    You must be signed in to add a comment. Sign-in | Register

    In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.