This document is about: QUANTUM 1
SWITCH TO

Custom Server Plugin

Introduction

Quantum's server logic runs as an extension of Photon Server's HivePlugin SDK.

A deterministic session (the quantum simulation in the client machines connected to the plugin-controlled room) starts after one of the client sends a Quantum Join message (not to be confused with the photon join room command, which has already happened).

What follows is that all other connected clients will also follow the join procedure, which triggers the synchronized time and input exchange to start, which drives the simulation in the clients.

Important: this document explains Quantum specific server-side callbacks that are useful entry points for implementing server authoritative checks for Quantum games. For the instalation notes and troubleshooting of the Custom Plugin Sample package (which is sometimes referenced here), checks the /docs folder inside the package.

Basic Intercept Callbacks

With a custom version of the Quantum plugin, most of the steps in session the startup procedure and runtime loop can be intercepted authoritatively.

Deterministic Join Session

For every player trying to Join the Quantum session, there are basic checks in place like protocol/SDK version, available player slots, etc. You can add custom checks with this callback. The ID included into the joinData parameter is the GUID send from the client.

C#

public virtual Boolean OnDeterministicClientJoinSession(DeterministicPluginClient client, Join joinData) {
  return true;
}

DeterministicSessionConfig and RuntimeConfig

By default, the Quantum plugin will use the value for these configs as sent from the first client message that arrives at the server. By intercepting these two calls, it is possible to authoritatively force the config values on all clients.

For DeterministicSessionConfig, just replace the values in the struct passed inside configData.

C#

public virtual void OnDeterministicSessionConfig(DeterministicPluginClient client, SessionConfig configData) {
  // overriding here you can modify the session config to be used (called once per session)
}

For RuntimeConfig, the data is passed as a byte[], which can be deserialized as the (game specific) RuntimeConfig by referencing the quantum.state DLL. Modified values can be serialized back and then replace the original config passed.

C#

public virtual void OnDeterministicRuntimeConfig(DeterministicPluginClient client, RuntimeConfig configData) {
  // overriding here you can modify the runtime config (byte[]) to be used (called once per session)
}

These two can actually be already pre-set on room start (from a basic photon plugin callback) with the following methods:

C#

public void SetDeterministicSessionConfig(DeterministicSessionConfig config);
public void SetDeterministicRuntimeConfig(Byte[] configData);

RuntimePlayer Data

Similar callback and setter exist for RuntimePlayer. This callback intercepts every call done from a Unity client to QuantumGame.SetPlayerData(), so authoritative code can be used to even ignore the call, so the clients will not receive the data at all (server-side protection):

C#

public virtual Boolean OnDeterministicPlayerDataSet(DeterministicPluginClient client, SetPlayerData playerData) {
  // returning false would cause this data to be ignored and not passed to clients
  return true;
}

The setter allows the developer to inject new values for RuntimePlayer instances (for any of the players) at any moment during the session:

C#

public void SetDeterministicPlayerData(DeterministicPluginClient client, SetPlayerData playerData);

Just like RuntimeConfig, RuntimePlayer includes user-defined properties and serialization code, so the data is passed to the callbacks as a byte[]. Same approach of using quantum.state DLL to reference the class and serializer can be used.

Advanced Callbacks

The basic callbacks let the developer intercept/modify config and player data objects before they are forwarded to clients. Advanced callbacks might be used to control live input, run simulation on server, or optimize late joins, reconnects and resyncs.

Running Simulation

Running the simulation on the server requires three callbacks to be implemented:

  • public virtual void OnDeterministicStartSession() should be used to initialize the FP math LUT, the quantum DB (if not yet initialized from another room), etc;
  • public virtual void OnDeterministicInputConfirmed(DeterministicPluginClient client, Int32 tick, Int32 playerIndex, DeterministicTickInput input) needs to be used to inject the confirmed (server coordinated) input data into the server-living simulation;
  • public virtual void OnDeterministicUpdate() will call the game loop update from the server copy of the Quantum SDK Session;

The custom plugin sample includes a basic implementation with all the necessary steps for the above callbacks. Because the server-based simulation shares most of its code with the Quantum standalone .NET console runner, we recommend that to be tried first.

Snapshot-Based Reconnects

Starting with Quantum SDK 1.2.1, Quantum client SDK supports (re)starting the simulation from a pre-existing game state. If the simulation is running on the server, this can be used as a turn-key solution for snapshot-based reconnects/late joins by implementing the public virtual void OnDeterministicLateStart(DeterministicPluginClient client, SimulationStart startData).

Here's the code snippet that does it. It considers simulation is running from a session container (tool shared with .NET console runner). The full example can be found in the custom plugin sample:

C#

// [optional] snapshot-based reconnects/late joins
public override void OnDeterministicLateStart(DeterministicPluginClient client, SimulationStart startData)
{
  if (container.session.FramePredicted == null) {
    // Too early for snapshots.
    return;
  }

  // Overwrite the input by only sending relevant input (omitting inputs in the far past)
  client.ReconnectInputs = GetLateJoinInputs(container.session.FramePredicted.Number);

  // cross platform (code generated) serialization of the full frame.
  var data = container.session.FramePredicted.Serialize();
  // encoding the frame data into several FrameSnapshot protocol messages (they have an internal max size)
  var snapshotPacks = FrameSnapshot.Encode(container.session.FramePredicted.Number, data);
  foreach (var pack in snapshotPacks)
  {
    // enqueuing the snapshot data here, ensues it will arrive on the client BEFORE it receives his session start command,
    // so this frame will be used as the starting point for the sim
    client.ProtocolSendQueue.Enqueue(pack);
  }
}

Forwarding Input to Another Server

Either from OnDeterministicInputConfirmed(DeterministicPluginClient client, Int32 tick, Int32 playerIndex, DeterministicTickInput input) or from the normal determinsitic update callback, it is possible to send input to another server by HTTP.

We recommend batching several ticks worth of input into each send to avoid too many HTTP small sends (sending every 5 to 10 seconds should be enough). Check the official (not Quantum specific) Photon Server Plugin documentation for safe HTTP data sends.

Back to top