Game Integration
C# Side
Please refer to the C# documentation.
C++ Side
Bifrost uses nlohmann JSON as its default serializer for the C++ server and as such, these instructions assume JSON as the serializer for the initial setup.
Setup Instructions:
There are a few ways to integrate Bifrost integrated in the game project:
Via static libraries and header files available in a share (provided by your Test/Tech lead)
By adding the Bifrost source directly to your game and building it locally
Via the UnrealToolkit (Unreal 4 or 5 games)
To integrate Bifrost as source and build it as part of the project, contact the SQ Tech team.
1. Linking bifrost
Skip this step if integrating Bifrost as source or as part of the UnrealToolkit. Get a drop of all Bifrost’s headers, static libs and all its required dependencies as mentioned on the installation page.
Add the path to the Bifrost include headers into the include directories property of the game project
Add the path to the Bifrost libraries into the library directories property
Link against the following libraries
Infrastructure.lib
Serializer.JSON.lib
Transport.lib
Add the name of any required platform specific libraries
Windows:
wsock32.lib
ws2_32.lib
Xbox (GameCore):
xgameruntime.lib
It is common for title developers to compile out Bifrost configuration in shipping configuration. How that is accomplished is dependent on the title. A couple of methods include:
A compiler preprocessor definition that is defined in the configurations that Bifrost should be enabled.
Build tools avoids adding Bifrost when generating the solution for shipping configuration.
2. Implement game specific error handling
Add additional error codes specific to the game project by creating a ReturnCode enum class with the underlying type int16_t
.
Then create a class inheriting the templated GenericResult
to return custom error codes in hooks.
Create any return codes specific to the game project:
#include <stdint.h>
enum class GameReturnCode : int16_t
{
/** The operation was a success. */
Success,
/** An error occurred trying to get the player. */
PlayerError,
};
Create a GameResult header class:
#include <Bifrost/GenericResult.hpp>
#include "GameReturnCode.h"
class GameResult : public Microsoft::XboxStudios::Bifrost::GenericResult<GameReturnCode>
{
public:
GameResult(GameReturnCode returnCode);
};
An optional BoundedString
can be added with the GameResult to include an error message with the return code.
Implement the GameResult class:
#include <Bifrost/ErrorHandling/BResultCategory.h>
#include "GameResult.h"
using namespace Microsoft::XboxStudios::Bifrost;
GameResult::GameResult(GameReturnCode returnCode) : GenericResult<GameReturnCode>(BResultCategory::Game, returnCode) {}
3. Create messages
The following messages support JSON serialization using nlohmann library.
Sample messages:
Assume the following is in a TestGameHooks.h
file.
#include "Serializer/json.hpp"
struct PlayerLocation {
int X;
int Y;
int Z;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(PlayerLocation, X, Y, Z)
}
struct PlayerLocationRequest {
int PlayerIndex;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(PlayerLocationRequest, PlayerIndex)
}
struct PlayerLocationResponse {
std::string PlayerName;
PlayerLocation Location;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(PlayerLocationResponse, PlayerName, Location)
}
Note that the above macro will serialize property keys as named in the structure. The key names are case sensitive and must match exactly what the client expects. To customize the property key names or property value, implement to_json(json& j, const PlayerLocation& p)
and from_json(const json& j, PlayerLocation& p)
methods instead of the macro. More details can be found here.
4. Creating game hooks
Bifrost hooks may have any of the following method signatures:
// A hook that expects a request and has a response.
BResult(const TRequest&, std::unique_ptr<TResponse>&);
// A hook that expects no request and has a response.
BResult(std::unique_ptr<TResponse>&);
// A hook that expects a request and has no response.
BResult(const TRequest&);
// A hook that expects no request and has no response.
BResult();
Where
BResult
represents the result of the hook. Note: The GameResult explained in step 2 inherits fromBResult
.TRequest/TResponse represents any type that implements
to_json(json& j, const T& t)
andfrom_json(const json& j, T& t)
methods. These methods are automatically generated if using theNLOHMANN_DEFINE_TYPE_INTRUSIVE
macro shown in step 3.
Create GameHooks header class:
#include "TestGameHooks.h"
#include "GameResult.h"
#include "HookTableBuilderJSON.h"
using BResult = Microsoft::XboxStudios::Bifrost::BResult;
using HookTableBuilderJSON = Microsoft::XboxStudios::Bifrost::Serializer::HookTableBuilderJSON;
class GameHooks
{
public:
static BResult GetPlayerLocation(const PlayerLocationRequest& request, std::unique_ptr<PlayerLocationResponse>& response);
// If hooks are static only, consider using Microsoft::XboxStudios::Bifrost::Serializer::StaticHookTableBuilderJSON instead.
void RegisterHooks(HookTableBuilderJSON& builder);
};
Note that the hook class contains a RegisterHooks
method that will register hooks in a hook table builder that will be used to construct the server later.
Implement GameHooks:
#include "GameHooks.h"
void GameHooks::RegisterHooks(HookTableBuilderJSON& builder)
{
builder.AddHook<PlayerLocationRequest, PlayerLocationResponse>("GetPlayerLocation", GetPlayerLocation);
}
BResult GameHooks::GetPlayerLocation(const PlayerLocationRequest& request, std::unique_ptr<PlayerLocationResponse>& response)
{
// Use the player index in the request to call a game API to get the player position. Example:
Vector3f playerPosition = GameState::GetPlayerPosition(request.playerIndex());
response = std::make_unique<PlayerLocationResponse>();
response->X = playerPosition.x;
response->Y = playerPosition.y;
response->Z = playerPosition.z;
response->PlayerName = GameState::GetPlayerName(request.playerIndex());
return GameResult(GameReturnCode::Success);
}
Note that if using non-static hooks, you must create a lambda that captures this
pointer to register a hook. For example:
builder.AddHook<PlayerLocationRequest, PlayerLocationResponse>("GetPlayerLocation",
[this](const PlayerLocationRequest& request, std::unique_ptr<PlayerLocationResponse>& response) { return this->GetPlayerLocation(request, response); });
One may prefer creating macros to auto generate the above code block.
5. Create and use the Bifrost server
Next, define what transport and serializer protocol to use with the ServerWithReferences
.
The following is an example of a server that uses the windows transport protocol and JSON serializer protocol:
#include "Bifrost/Server.hpp"
#include "JSONSerializer.h"
#include "HookTableBuilderJSON.h"
#include "Win_TcpTransport.h
#include "GameHooks.h"
// Select the JSON serializer
using Serializer = Microsoft::XboxStudios::Bifrost::Serializer::JSONSerializer;
// Select the transport protocol based on platform
using Transport = Microsoft::XboxStudios::Bifrost::Transport::Win_TcpTransport;
// The hook table builder to use to register JSON hooks.
using HookTableBuilderJSON = Microsoft::XboxStudios::Bifrost::Serializer::HookTableBuilderJSON;
// Define the serializer and transport protocol the Bifrost server will use
using BifrostServer = Microsoft::XboxStudios::Bifrost::ServerWithReferences<Serializer, Transport>;
Register hooks with the hook table builder and construct a server:
HookTableBuilderJSON builder;
GameHooks hooks;
hooks.RegisterHooks(builder);
BifrostServer server(
builder.Build(),
Transport(4601), // Select port number here. Default is 4600.
1000000); // Configure maximum chunk size here. Default is 512 KB.
Start the server in a startup/initialization function:
server.Start();
Process incoming requests somewhere in the game loop:
server.ProcessRequests();
When requests are received, they will be placed into a queue and processed on demand. Call ProcessRequests()
somewhere in the game loop to process the queue.
On game shutdown/exit, make sure to stop the server:
server.Stop();
Compile and run the game title. To learn how to invoke the game hook, proceed to the tool integration page.