Client programmer's guide

Abstraction layers
The agent layer
Item referencing
The client abstraction layer
LocalMainAgent
Tile
TileDictionary
ClientContext
Multiple clients
Bootstrapping the client

This document describes the system design from a client-side perspective, i.e what needs to be understood in order to program White Orb clients.

Abstraction layers

 

This is an overall picture of which layers are involved in the client/server design.

The Agent layer

As I mentioned above, the client implementation layer does not need to speak directly to the agent layer.  But I'll describe it anyway - it's good to know how the API interface looks like.  The MainAgent is the main interface for clients to use over the internet.

A MainAgent always has an actor and usually has a connected client.  The actor is the item in the server that the MainAgent represents, sort of like the puppet that the client is actually acting on behalf of.  This is usually an item in the world, for example an Account or a Soul.  The actor is used as a basis for security - determining what actions are allowed, and what information can be accessed.  The client (see the javadoc for MainClient) is what the agent uses for callback purposes, for example reporting property changes, server messages, and stuff like that.

The MainAgent interface provides a number of different types of methods (see the MainAgent javadoc):

So basically a MainAgent represents one client connection, whether it be a player's account, a character, a builder, or an administrator. Each one needs to be able to access the world and retrieve information about it.

There are a few other, more specialized agents - MapAgent and AreaAgent.  These two may be merged together later, I'm not sure.  AreaAgent provide access to a specific area, and is designed to be connected to an AreaClient. The AreaClient will receive updates when the contents of the area changes.  The MapAgent is similar to AreaAgent, but represents a mobile subset of an area, for example a creature's field of view.  Thus it includes things like sight range.  The corresponding MapClient is similar to AreaClient, but also includes facilities for scrolling, i.e the MapAgent will tell the MapClient when new squares are visible, and when old squares move out off the edge of the map.

Item referencing

Items are constantly being referred to across all layers.  The different layers refer to items in the following ways when communicating with each other:

If you study the diagram you can see that the main purpose of the client abstraction layer and Agent layer is to translate the server's Item-based world to the client's Tile-based world.

Let's take a concrete example.  The server tells the client that it sees a creature, and now the client (being a builder) wants to delete that creature.  The server layer tells the agent layer using a normal Item (or, more specifically, a Creature).  Since Items cannot be sent over the net (they are too heavily tied into the server world) the agent creates a corresponding ItemReference and stores the Item's integer id for further reference.  The ItemReference encapsulates this integer id, plus some cached properties (for example the item's icon and location).  This ItemReference is sent to the client.

The client abstraction layer receives this, and creates a corresponding Tile object.  The client can communicate with Tile objects in a very easy fashion, without having to worry about client/server communication and property caching.  Now the client only wants to delete it, so it tells the client abstraction layer to delete the Tile.  The client abstraction layer will, in turn, extract the item ID integer and tell the agent layer to delete the item with that ID (since that is all the agent needs to be able to look up the original Item).

The agent receive the integer ID, uses it to look up the corresponding Item, and then deletes it.  Thus internet traffic is minimized, without complicating the implementation of the client or the server.

The client abstraction layer

The agent layer provides all the functionality needed by any client, but the problem is that each single call to the agent layer must be sent through the internet.  Of course the actual mechanisms for remote method invocation is not so difficult, using for example Voyager, but we still need to minimize the internet traffic.  The client implementation will be retrieving very much information from the server, but in many cases this information can be cached locally (if the client will retrieve the information more often than the information is changed in the server).  Thus, the client abstraction layer provides a bunch of high-level classes that hide all gory networking details from the client implementation.

This diagram shows the public classes within the client abstraction layer, and how they relate to each other.  The arrows represent dependencies (i.e references).  Red or dotted arrow means that the reference is private, and cannot be traversed from outside that class.  Thus the client abstraction layer really hides the MainAgent from the client implementation.

LocalMainAgent

The API interface of LocalMainAgent looks very similar to MainAgent - but the level of abstraction is raised.  The main differences are:

Tile

A Tile is a high-level representation of an item in the server.  It contains the following general types of methods: There is never (or should never be) more than one Tile representing a single item.

TileDictionary

This class stores and indexes all Tiles.  It provides the following types of methods:

ClientContext

The ClientContext is what holds it all together, i.e it represents the client abstraction layer as a whole.  It contains a reference to the LocalMainAgent as well as client-specific things like the status bar, the currently selected tile, and such things.  There are subclasses of ClientContext that keep track of more information for specific types of clients.  PlayerClientContext, for example, contains info about which player the client represents, BuilderClientContext contains info about which item type is selected in the toolbox.

Any functionality that all clients are expected to provide should be placed here, in a non GUI-specific way.  General methods for error handling and logging will probably be added here.

Multiple clients

In many cases a single person will be using multiple clients.  There are currently four basic client types: account, player, builder, and admin.  These reflect the different "roles" with which you can use the system. When more than one client is open, each client is completely independent. This is to ensure that the roles and security restrictions don't interfere with each other.  Here is an example:

The client abstraction layers cannot be shared because they contain state information (such as cached properties) that may vary between the clients.  In the Player client, for example, the character might be drunk and have a corrupted view of the world.

Bootstrapping the client

So how does it all start?  Well, the first thing the client must do is connect to the server.  This is done using Voyager's RPC implementation.  The Server provides the following three methods: Once you manage to log in successfully, you will receive a MainAgent representing your account (i.e your account object in the server will be the actor).  Now you can create a ClientContext (the constructor requires a MainAgent), which in turn will create the LocalMainAgent, TileDictionary, and all that.  Once you have a ClientContext the client abstraction layer is complete.

Now you need to create a client, for example an account client (implemented as AccountPanel).  The AccountPanel takes the ClientContext as a parameter and does the rest.

All this behaviour is already implemented - in OrbClient.  I just wanted to describe the general process...



Henrik Kniberg

Last updated: