HSL Oracle Systems

Jump to: navigation, search


This page is about the HSL-created system for managing game definitions known as Spec Oracles. For the database application, see Category:Oracle Database.

The Oracle Pattern


The game client knows almost nothing about server data and even less about data created and consumed by serverside HSL systems. This limitation is at war with the need for HSL systems ( in particular GUIs ) to display information to the player. The Oracle Pattern describes a way of implementing our systems to provide clientside access to data that is held on the server.

This pattern is named the "Oracle Pattern" because it describes a method of structuring data for storage on the client to mirror server data, without requiring that the server re-transmit data. In essence, it is the creation of an in-memory datastore that the client can query for information that it is supposed to know about. It "knows" things by being fed information from the server in some manner, and caches this knowledge for future requests.

Oracles provide interfaces for working with and listening to the data models they support.

What problem does the Oracle Pattern solve?

What problems does the Oracle Pattern not solve?


Oracles are Nodes

On the client, an oracle is represented by a node ( noderef of class SomeClass ). The class from which the oracle node is instantiated in turn commonly inherits from the collection classes ( most often CollectionUnorderedSet ) so that it is able to add/remove/list the data nodes it stores/caches.

Data stored by Oracles are nodes

Oracles store their data as nodes.

Oracles expose an interface to work with their data

Oracles provide a means of working with their nodes through the functions they expose.

Oracles have ClientSide Classes and may have ServerSide Classes that support them

An oracle is manifested as a node instantiated from the specific oracle class, the class methods script for that class then exposes methods to work with the oracle's data. Some oracles will simply ask the server the first time data is required, other oracles may have complex serverside classes supporting their functionality. These serverside expressions of oracles handle the work required to make the data accessible to the client oracle, through Inter-Process Communication.


Implementation of the Oracle Pattern is typically used to minimize the bandwidth costs and increase the responsiveness of the GUI on the client. By caching a local copy of serverside data, and updating it with changes as is appropriate, there is no need to send repeated transmissions to the server requesting data for display in a GUI. Additionally, by exposing the cached data to general queries, other GUIs can reuse the data, reducing or eliminating the need for them to directly ask the server for data.

Consumers of the data may either make direct requests/queries through the Oracle's API or may listen for updates to the data using an implementation of the Observer Pattern. Consistent implementation of this pattern will eventually enable players to create their own GUIs as consumers of one or more Oracles' data, to display the information in a way that is pleasing to them.

The Oracle Pattern

Let's say you are tasked to design an inventory system including both the serverside and clientside representations of an inventory. For this particular implementation ,the server-side oracle is represented by the character's Inventory node ( and hierarchy of items therein ). For the client-side representation of Inventory it turns out to be convenient to mimic the structure on the server, so we can take advantage of the built-in transmission capabilities of HSL collections. Once a copy of Inventory is received on the client it needs to be anchored to some well-known node so it can be found by all consumers of its data.

Design considerations

Populate initial data set

Client oracle's data set needs to be initialized with the current server oracle's data set at the start of a session, thereafter updates should be sufficent to synchronize the client view of the data to that of the server. Which leads to the question, how do we populate the initial data set for the client? Most commonly during the login process the server's oracle node or a procedure in a system script is notified that the client needs a copy of the data. The data is then packaged in a format the client understands and is sent to the client in the args field of a remotecall. Upon receipt by the client, the data is unpacked into one or more nodes which are then anchored to a well known interface.

Optionally, the first time a client oracle is asked for data it does not yet have it may make a request for the server to send a copy of the current data set. Any subsequent requests are put into a queue pending the arrival of the initial data set.

Associate data to a Root Node

The local data cache (represented by 1...n nodes) must to be associated in some known fashion to facilitate the use of that data by its consumers. The root node on the client from which you may anchor client data is the world anchor, on the server the root node is either the arearoot, the account node, or the character node. For data that is account specific, the account node should be used for the anchor. For data that is character specific, the character node should be used as the anchor.

Typically, a system node is created with methods for querying and subscribing to the system. The system node is anchored to one of the root nodes, and all data nodes for the system are in turn anchored to the system node.

Associating System Data to a Root

Get the area root

// Server side root node for areas
var areaRoot = GetRootNode()

Get the world anchor

// Client side root node

Data is so commonly anchored to the world anchor on the client that a utility script was created specifically to deal with anchoring system data in using a set of common utilities, this script is the ClientDataSystem.

Required Utility: ClientDataSystem

The ClientDataSystem script provides a way of anchoring node hierarchies of data in a well known way on the client. Oracles will commonly take advantage of the ClientDataSystem script to handle the anchoring of a system root node in a manner that it can be easily found. Oracles may optionally implement methods in the World_AnchorClassMethods script enabling easy access to a system root node using method calls on the world_anchor node which is eaisly located using the system variable SYSTEM.INFO.WORLDANCHOR

Get the Anchor for your system

// This will get and/or create a node in the clientDataSystem hierarchy to which you may
//   anchor the nodes for your system.
ClientDataSystem:GetSystemRoot( "YourSystemName" )

Add a node/hierarchy to your system node

// Anchor a node to your sytem root, since it is a node you in effect
//   may anchor a node hierarchy by adding the top level node to your system
ClientDataSystem:AddDataToSystem( "YourSystemName", yourNode )

Get a list of nodes in your system

yourData as list of id = ClientDataSystem:GetDataForSystem( "YourSystemName" )

Send notifications as server data changes

Once you have the initial data set on the client, keeping it synchronized with the server requires the notifications be sent to the client when the data changes.

There are several ways you can implement the notification mechanic;

No matter which mechanic you choose to identify that the server data has changed, in the end it all boils down to using Inter-Process Communication to send serialized data to the client oracles to update their local caches.

// Notify the client inventory that an item has been destroyed
rout as Class RemoteCallClientOut
rout.toPlayer = inventoryPossessor.GetMyAccount()
rout.toScript = "InventoryClassMethods"
rout.toFunction = "RemoteDestroyInventoryItem"
rout.failScript = debugUtils
rout.failFunction = "ClientRMCFail"
rout.args["item"] = item

See also

Personal tools