Using the Scalable Server¶
Scalable Server documentation¶
Please refer to the documentation available at http://ssdoc.asmodee.net/
Dependencies¶
Protobuf C#¶
Google provides Protobuf via GitHub: https://github.com/protocolbuffers/protobuf.
We developed SDK 5.0 using Protobuf v3.6.1:
- Go to https://github.com/protocolbuffers/protobuf/releases/tag/v3.6.1
- Download protobuf-csharp-3.6.1.zip
- Extract sub directory csharp/src/Google.Protobuf and import it in your Unity project
Protobuf Unity Compiler¶
Protobuf lets you describe the format of your data in .proto files, then you generate the corresponding C# classes by calling protoc. ProtobufUnityCompiler available at https://github.com/5argon/protobuf-unity/blob/master/Editor/ProtobufUnityCompiler.cs eases this process by automatically calling protoc every time you modify a .proto file.
Setup¶
NetworkParameters¶
Verify your connection parameters in Network Parameters under Scalable Server section.
CoreApplication¶
In your CoreApplication component, you should set the Use Scalable Server parameter to true.
Usage¶
ScalableServerManager¶
ScalableServerManager
is the entry point for all the services linked with the Scalable Server. You can access it through the CoreApplication
:
var scalableServerManager = CoreApplication.Instance.ScalableServerManager;
The ScalableServerManager
contains a ServerConnection
wich is the low level API to send and receive requests. It also provides workflows following the
Scalable Server Documentation.
CommunityHub¶
A simple way to connect to the Scalable Server is to require an Online Status with:
InternetConnectionWithScalableServerConnectionOptionalStatus
InternetConnectionWithScalableServerConnectionRequiredStatus
will prompt the SSO
ServerConnection¶
ServerConnection
is the base layer where everything happen. Your main entry point is Connect
allowing you to establish a connection with the Scalable Server and triggering the authentication of the player. When leaving an online section of the game, you may want to Disconnect
or DisconnectAfterDelay
.
When connected to the Scalable Server, the client sends and receives requests. The complete Request Reference is available as C# helper methods (C -> S) and events (S -> C).
public enum ConnectionState { FailedToConnect, NotConnected, Connecting, Connected }
public ConnectionState ConnectionState;
public void Connect();
public void Reconnect();
public void Disconnect(bool disconnectedOnNetworkLoss = false);
public void DisconnectAfterDelay();
public void PreventFromDelayedDisconnection();
// Server -> Client
public event Action<GameStatusReportRequest, long> GameStatusReportRequestReceived;
...
...
// Client -> Server
public long SendWhatsNewPussycatRequest(long? gameId = null, bool summaryDataOnly = false) {...}
...
...
Authentication Workflow¶
Reference: Scalable Server Documentation.
Authenticating the user is done automatically when a connection is established. You can subscribe to AuthenticationState
modifications, and access data about the AuthenticatedPlayer
.
public enum AuthenticationState { NotAuthenticated, Authenticating, Authenticated }
public AuthenticationState AuthenticationState;
public event Action Authenticated;
public event Action DeAuthenticated;
public event Action FailedToAuthenticate;
public Player AuthenticatedPlayer { get; private set; }
Chat Workflow¶
References: in lobby chat workflow documentation, in game chat workflow documentation
The API is simple, but we also provide a Chat Community Hub Content for a simpler integration.
public event Action<ChatEntryRaw> ChatReceived;
public event Action<List<ChatEntryRaw>> ChatHistoryReceived;
public void RefreshHistory(long? gameID = null) {...}
public void SendMessage(string chat, long? gameId, List<int> recipientIds = null, int code = 0) {...}
Game Workflow¶
Reference: Scalable Server Documentation.
Receiving game events:
public delegate void GameCreatedListener(int invitedBy, int localPlayerId, GameDetails onComplete);
public event GameCreatedListener gameCreatedListener;
// Standard Game Turn
public delegate void ActionRequiredListener(long gameId, byte[] state, List<Player> players, List<int> nextPlayerIds, int turnIndex, int invitedBy, GameClock playerClock, List<PlayerPregameData> userData);
public event ActionRequiredListener actionRequiredListener;
// - Broadcast
public delegate void GameStateUpdatedListener(long gameId, int playerId, int turnIndex, byte[] state, List<StatusReport.Types.PlayerClock> clock);
public event GameStateUpdatedListener gameStateUpdatedListener;
// Robots
public delegate void PlayerTimeoutListener(long gameId, byte[] state, int offenderId, List<Player> players, List<int> nextPlayerIds, PlayerTimeoutRequest.Types.PlayerStatus status, int turnIndex, GameStatus gameStatus, List<PlayerPregameData> userData);
public event PlayerTimeoutListener playerTimeoutListener;
public delegate void PlayerReplacedListener(long gameId, int playerId, bool becomeRobot, PlayerTimeoutRequest.Types.PlayerStatus status);
public event PlayerReplacedListener playerReplacedListener;
// Simultaneous Game Turn
public delegate void UserDataUpdateRequiredListener(long gameId, int turnIndex, List<int> participant, byte[] state);
public event UserDataUpdateRequiredListener userDataUpdateRequiredListener;
// - Broadcast
public delegate void GameUserDataUpdatedListener(long gameId, int playerId, int turnIndex, List<PlayerPregameData> userData);
public event GameUserDataUpdatedListener gameUserDataUpdatedListener;
// Multicast
public delegate void ClientDataListener(long gameId, byte[] data, Player sender);
public event ClientDataListener clientDataListener;
// Abort
public delegate void GameAbortedListener(long gameId, List<int> abortingPlayerIds, List<PlayerRankingUpdate> rankingUpdate, List<KarmaUpdate> karmaUpdate, byte[] data);
public event GameAbortedListener gameAbortedListener;
public void PlayerClocksStatus(Action<ErrorCode?, List<StatusReport.Types.PlayerClock>> completion, long gameId) { ... }
Sending game data:
public void CommitAction(Action<ErrorCode?> completion, long gameId, int localPlayerId, byte[] nextState, List<int> nextPlayerIds, byte[] nextSummaryData = null, int turnIndex = 0, bool nextSimultaneous = false, List<int> simultaneousPlayers = null, bool broadcast = false) { ... }
public void UpdateUserData(Action<ErrorCode?> completion, long gameId, byte[] userData, int localPlayerId, bool expectMoreData, bool broadcast) { ... }
public void MulticastData(Action<ErrorCode?> completion, long gameId, byte[] data, List<int> recipientIds, bool presentPlayersOnly) { ... }
public void GameForfeit(Action<ErrorCode?, bool?> completion, long gameId) { ... }
public void GameOver(Action<ErrorCode?, List<PlayerRankingUpdate>, List<KarmaUpdate>> completion, long gameId, List<FinalScore> finalScore, List<PlayerAchievementUpdate> achievementsAwarded = null, byte[] data = null) { ... }
public void GameOutcomeConfirmation(long gameId, int localPlayerId) { ... }
public void GameAbortedConfirmation(long gameId, int localPlayerId) { ... }
In Game Presence:
public enum Presence { unknown, absent, present, inGame }
public event Action<Dictionary<int, Presence>> PresenceDidChange;
public Presence PlayerInGamePresence(int playerId) { ... }
public void SwitchToGame(long gameId) { ... }
public void LeaveGame(long gameId) { ... }
General Workflow¶
Reference: Scalable Server Documentation.
All non-specific services are provided through the General Workflow.
public void GetGameInformation(Action<ErrorCode?, StatusReport> completion, long gameId, bool summaryDataOnly = false) { ... }
public void GetCurrentGamesInformation(Action<ErrorCode?, List<StatusReport>> completion, bool summaryDataOnly = false) { ... }
// Buddy List / Ignore List
public void GetBuddyList(Action<ErrorCode?, List<Buddy>> completion, List<string> webCardFilter = null) { ... }
public void AddBuddy(Action<ErrorCode?> completion, int playerId) { ... }
public void RemoveBuddy(Action<ErrorCode?> completion, int playerId) { ... }
public void GetIgnoreList(Action<ErrorCode?, List<Player>> completion, List<string> webCardFiter = null) { ... }
public void AddIgnore(Action<ErrorCode?> completion, int playerId) { ... }
public void RemoveIgnore(Action<ErrorCode?> completion, int playerId) { ... }
// Server Statistics
public ServerStatistics? LatestServerStatistics;
public event Action<ServerStatistics?> ServerStatisticsDidChange;
public void AskServerStatistics(bool subscribe) { ... }
// Presence
public enum Presence { unknown, absent, present }
public event Action<IDictionary<int, Presence>> PresenceDidChange;
public Presence PlayerPresence(int playerId) { ... }
public void RegisterPresence(List<int> playerIds) { ... }
public void UnregisterPresence(List<int> playerIds) { ... }
public void SubscribePresenceService() { ... }
public void UnsubscribePresenceService() { ... }
// Invitation
public delegate void InvitationAnsweredListener(long gameId, Player invitee, bool accept);
public event InvitationAnsweredListener invitationAnsweredListener;
public void EngageGameWithFriends(Action<ErrorCode?, GameDetails> completion, GameConfiguration gameConfiguration, List<int> friends, byte[] userData = null, byte[] initialState = null, byte[] initialStateSummary = null) { ... }
public void AnswerInvitation(Action<ErrorCode?> completion, long gameId, bool accept, byte[] userData = null) { ... }
Heartbeat Workflow¶
Reference: Scalable Server Documentation.
In order to keep a connection live, the Scalable Server requires to send requests regularly. The role of HeartbeatWorkflow
is to send the Ping request and to alert if it takes too long to receive a response.
public enum HeartbeatState { Failure, Disabled, Unstable, Stable }
public long RequestRoundTripTime { get; private set; }
public event Action<HeartbeatState> HeartbeatStateDidChange;
public HeartbeatState HeartbeatState;
Lobby Workflow¶
Lobby State:
public enum LobbyState { NotInLobby, EnteringLobby, ExitingLobby, InLobby }
public LobbyState LobbyState;
public event Action EnteredLobby;
public event Action ExitedLobby;
public bool AutomaticallyEnterLobbyAgainAfterReAuthentication { get; set; } = true;
public void EnterLobby(Action<bool> completion = null, bool forceSendingRequest = false) { ... }
public void ExitLobby(Action<bool> completion = null, bool forceSendingRequest = false) { ... }
Lobby Players:
public event Action<IList<Player>> LobbyPlayersUpdated;
public IList<Player> LobbyPlayers;
public void GetPlayerInformation(Action<ErrorCode?, Player> completion, int playerId) { ... }
Open Games:
public event Action<IList<GameDetails>> LobbyGamesUpdated;
public event Action<IList<long>> LobbyJoinedGameIdsUpdated;
public event Action<GameDetails, int> NewPlayer;
public event Action<GameDetails, Player> PlayerLeftGame;
public IList<GameDetails> LobbyGames;
public IList<long> LobbyJoinedGameIds;
public void CreateGame(Action<ErrorCode?, GameDetails> completion, GameConfiguration gameConfiguration, string password = "", byte[] userData = null, byte[] initialState = null, byte[] initialStateSummary = null) { ... }
public void JoinGame(Action<ErrorCode?, LobbyJoinDeniedRequest.Types.JoinError?, GameDetails> completion, long gameId, string password = "", byte[] userData = null) { ... }
public void LeaveGame(Action<ErrorCode?, GameDetails> completion, long gameId) { ... }
public void StartGame(Action<ErrorCode?, LobbyStartGameDeniedRequest.Types.StartError?, GameDetails> completion, long gameId, bool fillWithRobots = false, byte[] data = null) { ... }
Observable Games:
public event Action<IList<GameDetails>> ObservableGamesUpdated;
public IList<GameDetails> ObservableGames;
public void SubscribeToObservableGames(Action<ErrorCode?, bool> completion, bool subscribe = true) { ... }
public void UnSubscribeToObservableGames(Action<ErrorCode?, bool> completion) { ... }
public void StartObserveGame(Action<ErrorCode?, GameObservedRequest.Types.Status?, StatusReport> completion, long gameId, bool observe = true) { ... }
public void StopObserveGame(Action<ErrorCode?, GameObservedRequest.Types.Status?> completion, long gameId) { ... }
LobbyPresenceManager¶
After having entered the Lobby, we can monitor the presence of specific players. This is useful to present the availability of the player’s buddies.
public interface IPresenceSubscriber
{
void UpdatePresence(int playerWWWId, PresenceStatus newStatus);
}
public void Subscribe(int playerWWWId, IPresenceSubscriber subscriber) { ... }
public void Subscribe(List<int> playerWWWIds, IPresenceSubscriber subscriber) { ... }
public void Unsubscribe(int playerWWWId, IPresenceSubscriber subscriber) { ... }
public void Unsubscribe(List<int> playerWWWIds, IPresenceSubscriber subscriber) { ... }
public enum PresenceStatus { Unknown, Absent, Away, Present }
public PresenceStatus GetPresenceStatus(int playerWWWId) { ... }