Janus2

From EQUIS Lab Wiki

Revision as of 18:40, 5 June 2014 by Savery (Talk | contribs)
(diff) ← Older revision | Current revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Contents

Overview

Janus is a toolkit designed to handle consistency maintenance aspects of a multiplayer computer game. It provides a high level interface that the game programmer can use without worrying about how the consistency and data sharing are done behind the scenes.

Janus (pronounced YAH-noos) is named after Janus the Roman god of gates, doors, doorways, windows, beginnings and endings. Janus also had the ability to see into both the past and the future. Similarly, users of the Janus toolkit are able to access the game state at any point in the past or future.

For more information and pointers to publications on Janus, see our project web page.

Download

The source code for the Janus Toolkit can be downloaded here http://equis.cs.queensu.ca/~equis/Janus/Janus_2.0.0.2_20140604.zip.

Note these files work with Visual Studio 2013.

If you use Janus, please let us know. This helps us when applying for funding to continue the project.

Timeline Objects

Within Janus, the shared data are modeled as Timelines which contain all past, present and future values of the shared data. Each element in the Timeline contains an object representing the shared state variables and the time associated with that state. The shared state variable object may be as simple as a single integer, for example representing the health points of an entity in a game, or it may be any arbitrarily complex object containing multiple properties such as the position, heading and velocity of an entity. Although the time values stored in the Timeline are the actual date and time values, the programmer always accesses the data using relative time with zero (0) meaning now and +10 being 10 ms in the future and -10 being 10 ms in the past, etc.

Timelines are composed of:

  • Get/set operations that access the timeline’s value at a given time;
  • Interpolation and extrapolation functions that estimate values for times when no known value exists in the timeline; and
  • A remote update function that processes timeline updates from remote peers.

The following figure shows the elements of the timelines model: a timeline includes a set of past and future known values (v1, v2,...), along with the times at which they hold (t1, t2,...). Values between these times are computed using an interpolation function, and values after these times are estimated using an extrapolation function. A remote update function determines how updates received from peers are inserted into the timeline.

TimeLine.jpg

Timeline objects essentially have two methods available for the programmer: Set and Get.

Set Method

The Set method is used to store the state locally as well as broadcast the state to all remote clients who have subscribed to that timeline object. The Set method takes two parameters:

  • the time in seconds, and
  • the new value for the state variable.

For example to set the current health points to 25 for an integer-type Timeline representing the health of an entity named entityHealth, the code would be:

   entityHealth[0] = 25;

Similarly to set the health points to 35 at 100 ms (0.1 seconds) in the past, the code would be:

   entityHealth[-0.1f] = 35;

Normally only one client should store values into an individual timeline. If two clients attempt to write to the same timeline, the writes may conflict and result will be unpredictable.

Get Method

The Get method is used to retrieve the value of an object from the Timeline. The Get method takes just one parameter which is the time for which the value is to be retrieved. The Get method will perform one of three actions depending upon what values are stored in the Timeline:

  • Return a value from the Timeline,
  • Use interpolation to calculate a value, or
  • Use extrapolation to calculate a value

If a value exist in the Timeline at the specified time, then that value is returned. If there is no value for that time, but there is a value before that time and a value after that time, then interpolation is used to calculate and return a value. The interpolation function can be simple stepwise interpolation (i.e. hold previous value) or linear interpolation between the two points or a more complex algorithm may be implemented such as using heading and velocity data or polynomial interpolation. If all the values in the Timeline are before the requested time then extrapolation will be used to calculate and return a value. Again, any of a variety of algorithms may be used for extrapolation. When using the Get method, the programmer does not need to be aware of whether interpolation or extrapolation is being used.

Base on the previous example for entityHealth (and assuming that linear interpolation is being used), if the programmer wanted to access the health points at 50 ms (0.05 seconds) in the past,

   int healthPoints = entityHealth[-0.05f];

would return 30 and store it in the local variable healthPoints, automatically using interpolation to calculate the a value between the two known values of 25 and 35.

Similarly, if the programmer wanted to access the health points at 50 ms in the future,

   int  healthPoints = entityHealth[0.05f];

would return 20 and store it in the local variable healthPoints, automatically extrapolating past the last known value of 25.

Constructor

The constructor for a Timeline object requires one parameter, the string name of the timeline.

The simplest way to create a timeline is:

   Timeline<int> a = new Timeline<int>("player1Health");

Other syntax may be used such as:

   Timeline<int> entityHealth = TimelineManager.Default.Get<int>("player1Health");

Although longer, this is somewhat safer as a check is performed to verify that the name of the timeline is unique. This check is not performed when the new construct is used. If a Timeline already exists with the name player1Health, then a pointer is returned to that timeline.

If two clients both create instances of the “player1Health” timeline, Janus synchronizes both instances. The timeline’s remote update function specifies how updates arriving over the network are to be applied. Whenever a timeline is updated by assigning a new value at a given time, an update message is sent to any synchronized instances on other clients. By default, the remote update function adds the incoming update into the timeline at the correct location.

Implementation

The Janus toolkit is written in C# using Microsoft Visual Studio 2013. It is built on top of the Lidgren Networking Library (code.google.com/p/lidgren-network-gen3) which provides the necessary networking infrastructure.

From the developer’s point of view, Janus has a peer-to-peer architecture. That is, updates are automatically routed between peers that share the same timeline, and all data is fully replicated. If a server is required, one of the peers can be allocated a server role.

In the current implementation of Janus, we have developed a centralized message router (Timeline Server)to implement this peer-to-peer communication. The router is based on a distributed publish and subscribe architecture. Timeline synchronization is implemented by peers subscribing to updates for the timeline in question. Updates are multiplexed so that one socket can be used to synchronize any number of timelines.

A name is associated with each instance of a Timeline object. When a client creates a Timeline object with a given name, the name is passed to the Timeline server and client automatically subscribes to updates for that object. When a client stores a new value to a Timeline object using the Set method, the value and the time associated with it are sent to the Timeline server which forwards the data to all other clients who have subscribed to that Timeline object.

Sample Applications

Currently there are two sample applications that demonstrate some of the many features for Janus.

Janus Cubes: A Unity Sample Application

and

Telepointer Trails: A Windows Forms Application Using Janus


The Timeline Server Debug Window is a Unity script that allows you to seen information similar to that shown by the TimelineDedicatedServer.


Troubleshooting

Disconnections

Janus uses the Lidgren Networking library. Lidgren maintains a thread that sends heartbeat messages to check if a client has disconnected. We have set the disconnection timeout to 15 seconds. It is possible that if you exceed the network bandwidth, messages may get backed up and this could trigger disconnection. If you are experiencing this problem, see the High Latency Section for suggestions on how to reduce latency.

High Latency

High latency is often caused by trying to send too much data. The first step is to determine how much data you are actually sending. If you are using the DedicatedTimelineServer, the amount of data (in kilobits per second) and the round trip times between the timeline server and each client is shown in the bottom left panel as shown below.

TLSLatency.png

If you are using Unity, you can add the Timeline Server Debug Window to your application to see the same information.

TLSDebugWindowSmall.png

If you see the round trip time and the amount of data increasing, the problem can likely be fixed by adding send filters to your timelines. By default, the values in a Timeline are sent over the network every time a new value is set. This can quickly exceed the network bandwidth. To help with this, one or more send filters can be added to the Timeline to reduce the frequency of message transmission. The filter can be based on either time or on how much the value in the timeline has changed or both.

The following filters are currently available:

  • Rate Filter - Filters timeline value sends based on a maximum send rate
  • Inequality Filter - Filters timeline value sends based on whether or not the value has changed at all
  • Delta Filter - Filters timeline value sends based on whether or not the value has changed by a fixed amount
  • Extrapolated Delta Filter - Filters timeline value sends based on extrapolation
  • Delta Rate Filter - Filters timeline value sends based on extrapolation, but also forces a send periodically even if the value has not changed

Each of these filters is described in more detail below.

Rate Filter

The rate filter is the simplest filter, reducing the number of messages sent based only on the time since the last message for that timeline was sent.

Inequality Filter
Delta Filter
Extrapolated Delta Filter
Delta Rate Filter

The BuildDeltaRateFilter that we add here only sends data if the position has changed by more than 0.05 or if more that 0.5 seconds (1/2.0 seconds) has elapsed since the previous message. See Send Filters for more details.

   cubePositionTimeline.AddSendFilter(TimelineUtils.BuildDeltaRateFilter <Vector3>((x,y) => Vector3.Distance(x,y), ()=>0.05f, ()=>2.0f));

Jerky Movement or Animation

Additional Documentation

More details on using Janus can be found here and detailed class documentation can be found at http://research.cs.queensu.ca/~savery/JanusDoc/html/N_Janus.htm