Implementing online features

Plugins are the best way to implement high level online functionalities in games that use the Stormancer SDK. They enable the developer to add new features to the client library exposed as custom classes and to the game server in a modular fashion.

As plugins can depend to each others, this patterns allow game specific plugins to leverage functionalities implemented in other plugins.

For instance, some functionalities implemented through plugins in the SDK:

  • Authentication

  • Matchmaking

  • Parties

  • Game sessions (client server & P2P)

  • Profiles

  • Chat (text and VoIP)

  • Leaderboards

  • InApp notifications

This article will guide you through the process of creating a simple Hello world plugin that exposes a function that retrieves a string from the server.

Dependencies

Our plugin is going to depend on:

  • The API plugin that enables an easy way to create Server APIs exposed through Controllers, indluding automated deserialization and serialization of parameters and return value.

  • The serviceLocator plugin. In Stormancer, services and API are hosted in named scenes on the server. The serviceLocator plugin enables clients to find the scene that should be used to use an API without having to hardcode the scene name in the client.

Server application plugin

The server application plugin leverages the server extensibility model to add its Helloworld API to scenes created with the helloworldTemplate template.

It’s also going to automatically create an helloworld scene that uses the helloworldTemplate template on server startup if it doesn’t exist yet.

It’s recommanded for easier development and maintenance to put each plugin code in a single server directory, but not necessary.

Example structure:

> Helloworld
    >HelloworldPlugin.cs
    >App.cs
    >HelloController.cs

Creating a controller

First we will create a controller the game client can call once connected to the server. It must inherit from Stormancer.Plugins.API.ControllerBase. This abstract class provides functionalities that make integration with the routing and serialization systems much easier. It’s included in the API plugin.

using using Server.Plugins.API;
class HelloController:ControllerBase
{

}

Public methods of controllers decorated with the Server.Plugins.API.APIAttribute are automatically registered as actions that can be called from other scenes or from the game client. The attribute governs accessibility and action type.

Accessibility options are:

  • Public: The action can be called from game clients

  • S2S: (S2S : Scene 2 Scene) Clients can’t call the action, but other scenes on the server farm can.

Type options are:

  • RPC: The action returns a value and can be awaited by the caller. Messaging must be reliable.

  • FireForget: The action is executed when a message is received, but the caller is not notified of success or failure. Messaging can be both reliable and unreliable.

Deserialization of the name parameter and serialization of the return value is automatically handled by the framework using a serializer supported by the caller.

The action can be reached on the scene the controller is added with the route Hello.World. Route name can be customized by the Api attribute.

class HelloController: ControllerBase
{
    [Api(ApiAccess.Public, ApiType.Rpc)]
    public string World(string name)
    {
        return $"Hello {name}!";
    }
}

Task based asynchronous processing is supported by actions, so the following code behaves exactly the same way:

class HelloController: ControllerBase
{
    [Api(ApiAccess.Public, ApiType.Rpc)]
    public Task<string> World(string name)
    {
        return Task.FromResult($"Hello world {name}!");
    }
}

Creating the plugin class

By itself, the controller we created is not exposed by the game server to the client. To do that, we need to tell the server runtime to add it to a scene the game client is going to connect to.

This is done by creating a class that implements the interface Stormancer.Plugins.IHostPlugin. Implementations of the interface can execute code whenever the runtime perform a number of tasks (startup, shutdown, scene creation, etc…)

using Stormancer.Plugins;
class HelloworldPlugin : IHostPlugin
{
    public void Build(HostPluginBuildContext ctx)
    {

    }
}

The HostPluginBuildContext object passed to the Build method contains extensibility points for the game server lifecycle. A plugin object is intantiated once per server node in the farm, so the code declared in the Build method, as well as in any global server event handlers (HostStarting and HostShutdown for instance) will be executed once per server node running the application’s code.

Now, we are going to add the controller we created to the runtime dependency injection container. The dependency injection system provides several scopes for dependency registration:

Host Scope –> Scene scope –> action scope.

  • Host registration . Dependencies registered in scenes can be potentially instantiated in any scope, but can still be configured for specific scopes during registration (Single instance, instance per scene, instance per network request or instance per dependency).

  • Scene registration. Dependencies registered in scenes cannot be instantiated in the host scope, but in any other subscope. It can be interesting to register dependencies only in scenes to restrict in which scene specifically the dependency is available.

We are going to register our controller as a dependency only in scenes with the “helloworld” metadata.

using Stormancer.Plugins;
class HelloworldPlugin : IHostPlugin
{
    public void Build(HostPluginBuildContext ctx)
    {
        ctx.SceneDependenciesRegistration += (IDependencyBuilder builder,ISceneHost scene) =>
        {
            if(scene.Metadata.ContainsKey("helloworld"))
            {
                builder.Register<HelloController>();
            }
        }
    }
}

The controller is available as dependency in the scene, Let’s now add to a scene so that its action get registered as available operations for the game client.

using Stormancer.Plugins;
class HelloworldPlugin : IHostPlugin
{
    public void Build(HostPluginBuildContext ctx)
    {
        //Register the controller as a dependency of any scene created with
        //the HelloworldTemplate template
        ctx.SceneDependenciesRegistration += (IDependencyBuilder builder,ISceneHost scene) =>
        {
            if(scene.Metadata.ContainsKey("helloworld"))
            {
                builder.Register<HelloController>();
            }
        }
        //Add the controller to any scene created with the
        //HelloworldTemplate template
        ctx.SceneCreated += (ISceneHost scene) =>
        {
            if (scene.Metadata.ContainsKey("helloworld"))
            {
                scene.AddController<HelloController>();
            }
        };
    }
}

The code above adds our classes only if the scene has an “helloworld” metadata key. This key is added by the scene template when the scene is created at runtime. The list of available templates in a game server can be discovered using the server admin HTTP Apis.

Templates declarations are composed of

  • A template id (HelloworldTemplate)

  • A factory function that will be executed just before the SceneCreated event used above is fired. If a template is declared several time, each factory is executed when a scene using the template is created.

We are using a scene metadata to signal to the rest of the system that our API should be activated on scenes created with the HelloworldTemplate template. Metadata are available both on the game server and the game client, which enables us to use on both side for feature discovery.

using Stormancer.Plugins;
class HelloworldPlugin : IHostPlugin
{
    private const string SCENE_TEMPLATE = "HelloworldTemplate";

    public void Build(HostPluginBuildContext ctx)
    {
       ...

        //The template is added on host startup.
        ctx.HostStarting += (IHost host) =>
        {
            host.AddSceneTemplate(SCENE_TEMPLATE, (scene) => {
                //Add the helloworld metadata to scenes created with the template
                scene.Metadata["helloworld"] = "v1";
            });
        };
    }
}

Finally, we need to ensure an actual scene is created with the template we created. It can be done several ways:

  • In the admin website

  • Using the HTTP Rest admin API

  • In the server code itself, using the Management plugin to access the admin API.

The management plugin retrieves credential automatically and makes most of the process painless. We are going to use it to automatically try to create the scene on server startup. If the scene is already created, the API succeeds.

class HelloworldPlugin : IHostPlugin
{
    private const string SCENE_TEMPLATE = "HelloworldTemplate";

    private const string SCENE_ID = "HelloworldScene";

    public void Build(HostPluginBuildContext ctx)
    {
        ...

        //When host startup is complete, we create a scene using the template.
        //If the scene already exists, the operation is a success too.
        ctx.HostStarted += (IHost host) =>
        {
            var managementAccessor = host.DependencyResolver.Resolve<Management.ManagementClientAccessor>();
            if (managementAccessor != null)
            {
                managementAccessor.GetApplicationClient().ContinueWith(async t =>
                {
                    var client = await t;
                    //Create a scene HelloworldScene using the template "HelloworldTemplate"
                    // and make it persistant.
                    await client.CreateScene(SCENE_ID, SCENE_TEMPLATE, true);
                });
            }
        };
    }
}

That’s it, we now have a plugin that

  • Register the controller into the dependency injection system

  • Adds the controller to scenes created using our template

  • Declares our template

  • Creates a scene using our template on application startup.

The App class

We created a plugin, but nothing ensures the plugin is added to the runtime on startup. To do that, we create an application startup class.

On startup, the runtime compiles the server code, then looks for any method that implement the following contract.

public class App
{
    public void Run(Stormancer.IAppBuilder builder)
    {

    }
}

Then it executes all of them in the order it discovered it.

To automatically run the HelloworldPlugin, we create a startup class for it.

public class App
{
    public void Run(IAppBuilder builder)
    {
        builder.AddPlugin(new HelloworldPlugin());
    }
}

That’s all. When starting up, the server now starts a scene that expose our Hello.World action. Let’s now create a client plugin to use it.

Configuring the service locator

The scene we created is exposed in the server farm by its id. The game client could directly use the id to connect and use our API. However, we are going to leverage another plugin instead: The service locator separate the identity of a service the game client tries to reach from the identity of the scene hosting it.

  • A scene may host a set of services. The repartition of services per scene, as well as the id the scenes themselves differs from a game server to another. Not hardcoding the scene ids in the client makes it possible to adapt to changing server architectures without client changes.

  • Many scenes exist in multiple copies, for instance player groups (parties), game sessions or chat rooms. The service locators enables connecting to a scene providing a named service (gamesession with id for instance)

The service locator is a dependency of the user plugin, that provides authentication and realtime player session management to the game server.

It support extensibility points to programmatically add service/scene mappings, but in this sample we are going to use the server application configuration to provide mappings at runtime.

"serviceLocator":{
    "DefaultMapping":{
        "helloworld":"HelloworldScene" //We map the service helloworld id to the scene HelloworldScene.
    }
}

Now, whenever a game client request the service helloworld, the service locator is going to direct it to the HelloworldScene scene.

Client plugins

Client plugins extend the client library to provide strongly typed, easy to use high level APIs.

The plugin architecture is designed to remove the need of complex wrappers to handle scene connection/reconnection outside of the client & plugins, as well as better support logic to locate scenes in a complex Stormancer infrastructure.

  • It centralizes calls to the service locator to find the scene hosting the required server features.

  • It lazily triggers authentication and scene connection

  • It manages reconnection in case of disconnection

  • It provides an API endpoint scoped to the client to expose the plugin functionalities to external users, hiding the lower level logic required to interact with scenes

Architecture

../_images/architecture.png

Simple plugins register 2 classes in the SDK

  • A service class scoped to the scene that represents the server scene exposing the features the plugin should provide. This service contains logic directly related to the feature (like calling RPCs and maintaining state for the plugin directly linked to the lifecycle of the scene. For instance, the list of players in the game session or in a party, the state of a game finder, etc… if the user disconnects from the scene, this state is invalidated.)

  • An external API class scoped to the client. It contains logic associated with the lifecycle of scenes associated with the plugin features, any state maintained through reconnections and exposes the APIs the game developer is going to use.

Remark: The API class registered in the client scope MUST be a singleton (the parameter set to `true` at the end of the registration) If not done, the scene might get disconnected during operation, because the API class is going to be destroyed as soon as it goes out of scope, potentially disconnecting the underlying scene connection if no other API uses it.

In this sample, we are going to directly expose the class containing the logic to the developer, but we may want to use an abstract class as an API to hide private implementation details.

API class

The API class is the public class the game code is going to interact with. It’s registered as singleton in the client’s dependency scope. To get the class, the game code just needs to request it.

auto hello = client->dependencyResolver().resolve<Hello>();

The Hello class exposes high level, strongly typed APIs. In our case, the only method on it calls the server action we created before and returns its result.

#include "stormancer/Tasks.h"

namespace Helloworld
{
        class Hello
        {
        public:
                pplx::task<std::string> world(std::string name);
        };
}

Stormancer uses the PPLX to provide a task based non blocking API. All operations that may require blocking the caller for more than a millisecond are exposed as task based non blocking operations. In particular, rpcs are non blocking.

Remark: the PPLX handles errors using exceptions. With modern compilers, unless an exception is thrown, no run-time overhead is incurred and in the case of network operations, failure are much more costly than actual exception propagation costs in zero-cost exception implementations.

To call the Hello.World RPC, the client must be connected to the Helloworld scene we created earlier. We want the API class to:

  • Lazily locate and connect to the scene if it’s not already the case when the Hello.world(name) is called the first time.

  • Automatically reconnect in case of network failure (and wait for reconnection to happen before failing)

  • Throw errors in case of connection failure.

  • Call the RPC on the remote server through the service class.

The tasks related to scene connection are already implemented in the ClientAPI<T> base class. This class only covers connecting an API to a single class, but it’s enough in most cases (and in particular in this example)

class Hello : Stormancer::ClientAPI<Hello>

The ClientAPI constructor needs an AuthenticationService object as parameter.

Hello::Hello(std::weak_ptr<Stormancer::AuthenticationService> auth) : Stormancer::ClientAPI<Hello>(auth) {}

The class uses it to perform the following tasks if necessary:

  • Connecting to the game server cluster

  • Connecting to the Authenticator scene

  • Authenticating the client

  • Locating the scene running the features the API needs.

  • Connecting to this scene (id Helloworld in our sample)

  • Returning the HelloService class that provides a strongly typed wrapper around the low level RPC API.

The world function uses ClientAPI::getService to perform these tasks. The features we are looking for are regrouped under the id “Helloworld” on the server. This is the id the client is going to look for when trying to locate the scene.

pplx::task<std::string> Hello::world(std::string name)
{
        return this->getService<HelloService>("helloworld")
}

getService returns a tasks that provides an HelloService implementation on completion (when all the tasks described above are complete).

Once the service is available, we call the world method.

pplx::task<std::string> Hello::world(std::string name)
{
        return this->getService<HelloService>("helloworld")
                .then([name](std::shared_ptr<HelloService> hello)
        {
                return hello->world(name);
        });
}

Remark: We need to pass the name argument to the service, so it’s captured in the lambda. It must be captured by copy (and not by reference) because the function scope is going to be destroyed well before the lambda is executed (‘then’ means that the lambda callback gets executed when the task is complete, which happens asynchronously).

Service class

The scene object represents a remote scene on the server and enables code to send and receive messages from this scene. Services class expose strongly typed apis on top of the scene.

Service classes are private to the plugin code and should not be used directly by the game code, because as they are hosted into a specific scene objects, service objects get invalidated if the scene object is disconnected. Game code that directly interacts with services class need therefore to handle disconnection and reconnection properly. Using the API pattern described in this article, these issues are directly handled in the API class and not by the game code itself, which enables better concern separation and cleaner code.

class HelloService
{
public:
        HelloService(std::shared_ptr<Stormancer::RpcService> rpc);

        pplx::task<std::string> world(std::string name);

private:
        std::weak_ptr<Stormancer::RpcService> _rpcService;

};

The constructor takes the RpcService implementation resolved in the scene scope. This service provides functions to send RPCs to remote peers (server included)

HelloService(std::shared_ptr<Stormancer::RpcService> rpc) : _rpcService(rpc)
{
}

Remark As the service object is destroyed when the scene is destroyed, you MUST never store a shared pointer of the scene inside of it. Doing so would result in a shared pointer cycle and a memory leak.

The world function uses the RpcService object to call the RPC we created before on the scene.

pplx::task<std::string> HelloService::world(std::string name)
{
        auto rpc = _rpcService.lock();
        if (!rpc)
        {
            return pplx::task_from_exception(Stormancer::PointerDeletedException("Scene destroyed"));
        }
        return rpc->rpc<std::string>("Hello.World", name);
}

The plugin class

The API we just created is not yet available for use by the game code. To do that, we need to build the glue code that tells the library how to use the classes we just created. This is the role of a plugin class. By inheriting from the IPlugin class, custom code can react to different events raised by the library during execution.

Our HelloworldPlugin class is going to: - Register our API class (Hello) in the dependency scope of the Stormancer Client class. - Register our Service class in the dependency scope of any scene that exposes the controller we created.

The Helloworld plugin inherits from IPlugin, and overrides 2 functions:

class HelloworldPlugin : public Stormancer::IPlugin
{
public:

//Event fired when a new scene is building its dependency scope. This gives the plugin a chance to register scene-specific dependencies.
        void registerSceneDependencies(ContainerBuilder& sceneBuilder, std::shared_ptr<Stormancer::Scene> scene) override;
//Event fired when the client is building its dependency scope, at startup. This gives the plugin a chance to register client-wide dependencies.
        void registerClientDependencies(ContainerBuilder& clientBuilder) override;
};

In registerClientDependencies(), we register the API class (Hello) as a singleton dependency of the client. It MUST be a singleton, to make sur that the client’s dependency scope keeps it alive between calls. As inheriting from ClientAPI guarantees that the client disconnects from the scene as soon as no API class use the scene anymore, it would disconnect from it as soon as the game code discarded the API class. In some cases, it may be the expected behavior, but most of the time, because connecting to scenes is expensive, it’s prefered to keep the connection alive.

void Helloworld::HelloworldPlugin::registerClientDependencies(Stormancer::ContainerBuilder& builder)
{
        builder.registerDependency<Helloworld::Hello, Stormancer::AuthenticationService>().singleInstance();
}

The registerSceneDependencies() event is fired each time the client is going to connect to a scene, but after having retrieved the scene metadata. Our implementation checks for the helloworld metadata we declared on the server, and if present, registers the service in the scene.

void Helloworld::HelloworldPlugin::registerSceneDependencies(Stormancer::ContainerBuilder& builder, std::shared_ptr<Stormancer::Scene> scene)
{
        if (scene)
        {
                auto name = scene->getHostMetadata("helloworld");

                if (!name.empty())
                {
                        builder.registerDependency<Helloworld::HelloService, Stormancer::RpcService>().singleInstance();
                }
        }
}

Using the plugin

It’s time to use our plugin. First, we enable it by adding it to the configuration of the library.

auto config = Configuration::create("http://serverEndpoint", "gameserver-account","gameserver-app");
config->addPlugin(new Helloworld::HelloworldPlugin());

auto client = IClient::Create(config);

Now we configure the authentication system to tell it how to get client authentication credentials. In this sample, we are going to hardcode some deviceidentifier credentials. In real applications, we would have used Steam, PSN, Xbox, Playfab or Login/password credentials.

auto deviceId = "foo";
client->dependencyResolver().resolve<AuthenticationService>()->getCredentialsCallback = [deviceId](){
    AuthParameters args;
    args.type = "deviceidentifier";
    args.parameters.emplace("devideidentifier",deviceId);
    //Sets foo as the devide identifier. Obviously, using a different deviceidentifier for each client would be mandatory. This implementation is going to kick the first player connecting as soon as a second one connects.
    return pplx::task_from_result(args);
};

Optionally, it’s possible to start the authentication process immediatly.

client->dependencyResolver().resolve<AuthenticationService>()->login();

Our API is available directly on the client object.

//We use get on the task object to block the thread until task completion.
//Obviously, We don't want to do that in the game code. Prefer using .then() to supply a continuation callback.
auto result = client->dependencyResolver().resolve<Hello>()->world("Shyntae").get();

std::cout << result; //"Hello Shyntae!"

Code

helloworld.hpp (Client code)

#pragma once
#include "Core/ClientAPI.h"
#include "Authentication/AuthenticationService.h"
#include "stormancer/IPlugin.h"

namespace Stormancer
{
    namespace Helloworld
    {
        namespace details
        {
            class HelloService
            {
            public:
                HelloService(std::shared_ptr<Stormancer::RpcService> rpc) : _rpcService(rpc)
                {
                }

                pplx::task<std::string> world(std::string name)
                {
                    auto rpc = _rpcService.lock();
                    if (!rpc)
                    {
                        throw Stormancer::PointerDeletedException("Scene destroyed");
                    }
                    return rpc->rpc<std::string>("Hello.World", name);
                }

            private:
                std::weak_ptr<Stormancer::RpcService> _rpcService;

            };
        }

        class Hello : public Stormancer::ClientAPI<Hello>
        {
        public:
            Hello(std::weak_ptr<Stormancer::AuthenticationService> auth) : Stormancer::ClientAPI<Hello>(auth) {}
            pplx::task<std::string> world(std::string name)
            {
                return this->getService<details::HelloService>("helloworld")
                    .then([name](std::shared_ptr<details::HelloService> hello)
                {
                    return hello->world(name);
                });
            }

        };

        class HelloworldPlugin : public Stormancer::IPlugin
        {
        public:

            void registerSceneDependencies(Stormancer::ContainerBuilder& builder, std::shared_ptr<Stormancer::Scene> scene) override
            {
                if (scene)
                {
                    auto name = scene->getHostMetadata("helloworld");

                    if (!name.empty())
                    {
                        builder.registerDependency<details::HelloService, Stormancer::RpcService>().singleInstance();
                    }
                }
            }
            void registerClientDependencies(Stormancer::ContainerBuilder& builder) override
            {
                builder.registerDependency<Hello, Stormancer::AuthenticationService>().singleInstance();
            }
        };

    }

}

HelloworldPlugin.cs (Server code)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Stormancer.Server.Helloworld
{
    public class App
    {
        public void Run(IAppBuilder builder)
        {
            builder.AddPlugin(new HelloworldPlugin());
        }
    }


    class HelloController : ControllerBase
    {
        [Api(ApiAccess.Public, ApiType.Rpc)]
        public string World()
        {
            return "Hello world!";
        }
    }

    class HelloworldPlugin : IHostPlugin
    {
        private const string SCENE_TEMPLATE = "HelloworldTemplate";

        private const string SCENE_ID = "Helloworld";

        public void Build(HostPluginBuildContext ctx)
        {
            //Register the controller as a dependency of any scene created with
            //the HelloworldTemplate template
            ctx.SceneDependenciesRegistration += (IDependencyBuilder builder, ISceneHost scene) =>
            {
                if (scene.Template == SCENE_TEMPLATE)
                {
                    builder.Register<HelloController>();
                }
            };

            //Add the controller to any scene created with the
            //HelloworldTemplate template
            ctx.SceneCreated += (ISceneHost scene) =>
            {
                if (scene.Template == SCENE_TEMPLATE)
                {
                    scene.AddController<HelloController>();
                }
            };
            //The template is declared during host startup
            ctx.HostStarting += (IHost host) =>
            {
                host.AddSceneTemplate(SCENE_TEMPLATE, (scene) => { });
            };

            //When host startup is complete, we create a scene using the template.
            //If the scene already exists, the operation is a success too.
            ctx.HostStarted += (IHost host) =>
            {
                var managementAccessor = host.DependencyResolver.Resolve<Management.ManagementClientAccessor>();
                if (managementAccessor != null)
                {
                    managementAccessor.GetApplicationClient().ContinueWith(async t =>
                    {
                        var client = await t;
                        await client.CreateScene(SCENE_ID, SCENE_TEMPLATE, true);
                    });
                }
            };
        }
    }
}

Listening to server messages

The Helloworld sample calls an RPC on the server. However, applications may require the server to trigger event and RPC calls on the client too. We will improve the Helloworld plugin to listen to an event broadcasted to every client when the Hello.World RPC is called on the server.

Sending an event

The action currently exposed by the HelloController only returns a string. We are going to improve it to first broadcast an event to all users connected to the scene.

class HelloController : ControllerBase
{
    private readonly ISceneHost scene;

    //Import the scene instance
    public HelloController(ISceneHost scene)
    {
        this.scene = scene;
    }

    [Api(ApiAccess.Public, ApiType.Rpc)]
    public Task<string> World()
    {
        //Send the string "Hello back!" to all peers connected to the scene on the route "Hello.Back".
        //The method ensures that the string is serialized with the serializer each peer is using, if they are different.
        scene.Broadcast("Hello.Back","Hello back!");

        return "Hello world!";
    }
}

The code above calls the Broadcast extension method to simplify sending objects using a serializer that is supported by clients. If all peers use the same serializer (for instance, by default msgpack) or if you want to control serialization, you can instead call Send in conjonction with a MatchAllFilter instance.

await scene.Send(new MatchAllFilter(),"Hello.Back",s=>serializer.Serialize(s,"Hello Back!"));

Sending to a single peer, or to a collection of peer requires using a MatchPeerFilter or a MatchArrayFilter respectively. Please note that other filters are available for more complex, view based filtering, like SpatialFilter

It’s possible to send an RPC instead of a Fire&Forget event by importing the RpcService instance in a scene scope and calling RpcService.Rpc

rpcService.Rpc<string,bool>(peer, "Hello.Back.Rpc","Are you sure?");

Subscribing to a route

The service implementation clientside must subscribe to the route the server sends the event to to handle it.

class HelloService :public std::enable_shared_from_this<HelloService>
{
public:
    HelloService(std::shared_ptr<Stormancer::RpcService> rpc) : _rpcService(rpc)
    {
    }

    pplx::task<std::string> world(std::string name)
    {
        auto rpc = _rpcService.lock();
        if (!rpc)
        {
            throw Stormancer::PointerDeletedException("Scene destroyed");
        }
        return rpc->rpc<std::string>("Hello.World", name);
    }

    //Initializes the service
    void initialize(std::shared_ptr<Stormancer::Scene> scene)
    {
        //Capture a weak pointer of this in the route handler to make sure that:
        //* We don't prevent this from being destroyed (capturing a shared pointer)
        //* If destroyed, we can know it to not use it in the handler (capturing a this reference directly) This may happen if messages are received while disconnecting from the scene, as message handling is multithreaded.
        std::weak_ptr<HelloService> wService = this->shared_from_this();
        scene->addRoute("Hello.Back", [wService](Stormancer::Packetisp_ptr packet) {
            //We expect the message to contain a string
            auto message = packet->readObject<std::string>();
            auto service = wService.lock();
            //If service is valid, forward the event.
            if (service)
            {
                service->helloBackReceived(message);
            }

        });
    }

    //Event fired whenever the service client receives a server message on the Hello.Back route.
    Stormancer::Event<std::string> helloBackReceived;
private:
    std::weak_ptr<Stormancer::RpcService> _rpcService;

};

The service API exposes the event as a Stormancer::Event instance. Other eventing systems are obviously possible.

The plugin must now call the initialize function once the constructor has been called. Initializing the route cannot be done in the constructor itself, because we need to capture a weak pointer to this, which cannot be done before during object construction.

void registerSceneDependencies(Stormancer::ContainerBuilder& builder, std::shared_ptr<Stormancer::Scene> scene) override
{
    if (scene)
    {
        auto name = scene->getHostMetadata("helloworld");

        if (!name.empty())
        {
            builder.registerDependency<details::HelloService>([](const Stormancer::DependencyScope& scope) {
                //Create the service instance
                auto instance = std::make_shared<details::HelloService>(scope.resolve<Stormancer::RpcService>());
                //Initialize the route
                instance->initialize(scope.resolve<Stormancer::Scene>());
                return instance;
            }).singleInstance();
        }
    }
}

Exposing the event in the API

The event exposed by the API must be different from the one in the service, because API ans service objects have very different lifecycles. An API object stays valid during the lifetime of the client object. However, the service object is valid only while the scene it’s associated to is valid. Scenes may become invalid in case of disconnection for instance. If it happens, the API automatically triggers a reconnection, which creates a new scene instance.

When an user subscribes to an event on the API class, we don’t want his subscription to become invalid if the client loses the connection to the server. To do that, we must expose a different event to the end user than the one we just created in the service class.

Api class

class Hello : public Stormancer::ClientAPI<Hello>
{
        friend class HelloworldPlugin;
public:

        Hello(std::weak_ptr<Stormancer::AuthenticationService> auth) : Stormancer::ClientAPI<Hello>(auth) {}
        pplx::task<std::string> world(std::string name)
        {
                return this->getService<details::HelloService>("helloworld")
                        .then([name](std::shared_ptr<details::HelloService> hello)
                {
                        return hello->world(name);
                });
        }
        Stormancer::Event<std::string> helloBackReceived;
};

When the client connects to a scene that hosts the Helloworld service (we suppose in this sample that the App contains only one scene hosting the service), the API must subscribe to the event in the service instance, and unsubscribe on disconnection

Plugin code

void sceneConnecting(std::shared_ptr<Scene> scene) override
{
    auto name = scene->getHostMetadata("helloworld");

    if (!name.empty())
    {
        auto hello = scene->dependencyResolver().resolve<Hello>();
        auto service = scene->dependencyResolver().resolve<details::HelloService>();
        hello->onConnecting(service);
    }
}

void sceneDisconnecting(std::shared_ptr<Scene> scene) override
{
    auto name = scene->getHostMetadata("helloworld");

    if (!name.empty())
    {
        auto hello = scene->dependencyResolver().resolve<Hello>();
        auto service = scene->dependencyResolver().resolve<details::HelloService>();
        hello->onDisconnecting(service);
    }
}

Api code

private:
void onConnecting(std::shared_ptr <details::HelloService> service)
{
    std::weak_ptr<Hello> wThis = this->shared_from_this();
    //Always capture weak references, and NEVER 'this'. As the callback is going to be executed asynchronously,
    //who knows what may have happened to the object behind the this pointer since it was captured?
    helloBackReceivedSubscription = service->helloBackReceived.subscribe([wThis](std::string message) {
        auto that = wThis.lock();
        //If this is valid, forward the event.
        if (that)
        {
            that->helloBackReceived(message);
        }

    });
}
void onDisconnecting(std::shared_ptr <details::HelloService> service)
{
    //Unsubscribe by destroying the subscription
    helloBackReceivedSubscription = nullptr;
}
Stormancer::Event<std::string>::Subscription helloBackReceivedSubscription;

Code

Server plugin (Helloworld.cs)

using Server.Plugins.API;
using Stormancer.Core;
using Stormancer.Plugins;

namespace Stormancer.Server.Helloworld
{
    public class App
    {
        public void Run(IAppBuilder builder)
        {
            builder.AddPlugin(new HelloworldPlugin());
        }
    }


    class HelloController : ControllerBase
    {
        private readonly ISceneHost scene;

        public HelloController(ISceneHost scene)
        {
            this.scene = scene;
        }
        [Api(ApiAccess.Public, ApiType.Rpc)]
        public string World()
        {
            scene.Broadcast("Hello.Back", "Hello back!");
            return "Hello world!";
        }
    }

    class HelloworldPlugin : IHostPlugin
    {
        private const string SCENE_TEMPLATE = "HelloworldTemplate";

        private const string SCENE_ID = "Helloworld";

        public void Build(HostPluginBuildContext ctx)
        {
            //Register the controller as a dependency of any scene created with
            //the HelloworldTemplate template
            ctx.SceneDependenciesRegistration += (IDependencyBuilder builder, ISceneHost scene) =>
            {
                if (scene.Template == SCENE_TEMPLATE)
                {
                    builder.Register<HelloController>();
                }
            };

            //Add the controller to any scene created with the
            //HelloworldTemplate template
            ctx.SceneCreated += (ISceneHost scene) =>
            {
                if (scene.Template == SCENE_TEMPLATE)
                {
                    scene.AddController<HelloController>();
                }
            };
            //The template is declared during host startup
            ctx.HostStarting += (IHost host) =>
            {
                host.AddSceneTemplate(SCENE_TEMPLATE, (scene) => { });
            };

            //When host startup is complete, we create a scene using the template.
            //If the scene already exists, the operation is a success too.
            ctx.HostStarted += (IHost host) =>
            {
                var managementAccessor = host.DependencyResolver.Resolve<Management.ManagementClientAccessor>();
                if (managementAccessor != null)
                {
                    managementAccessor.GetApplicationClient().ContinueWith(async t =>
                    {
                        var client = await t;
                        await client.CreateScene(SCENE_ID, SCENE_TEMPLATE, true);
                    });
                }
            };
        }
    }
}

Client plugin (Helloworld.hpp)

#pragma once
#include "Core/ClientAPI.h"
#include "Authentication/AuthenticationService.h"
#include "stormancer/IPlugin.h"

namespace Stormancer
{
    namespace Helloworld
    {
        class HelloworldPlugin;
        namespace details
        {

            class HelloService :public std::enable_shared_from_this<HelloService>
            {
                friend class HelloworldPlugin;
            public:
                HelloService(std::shared_ptr<Stormancer::RpcService> rpc) : _rpcService(rpc)
                {
                }

                pplx::task<std::string> world(std::string name)
                {
                    auto rpc = _rpcService.lock();
                    if (!rpc)
                    {
                        throw Stormancer::PointerDeletedException("Scene destroyed");
                    }
                    return rpc->rpc<std::string>("Hello.World", name);
                }



                //Event fired whenever the service client receives a server message on the Hello.Back route.
                Stormancer::Event<std::string> helloBackReceived;
            private:
                std::weak_ptr<Stormancer::RpcService> _rpcService;

                //Initializes the service
                void initialize(std::shared_ptr<Stormancer::Scene> scene)
                {
                    //Capture a weak pointer of this in the route handler to make sure that:
                    //* We don't prevent this from being destroyed (capturing a shared pointer)
                    //* If destroyed, we don't try to use it in the handler (capturing a this reference directly)
                    std::weak_ptr<HelloService> wService = this->shared_from_this();
                    scene->addRoute("Hello.Back", [wService](Stormancer::Packetisp_ptr packet) {
                        //We expect the message to contain a string
                        auto message = packet->readObject<std::string>();
                        auto service = wService.lock();
                        //If service is valid, forward the event.
                        if (service)
                        {
                            service->helloBackReceived(message);
                        }

                    });
                }

            };
        }

        class Hello : public Stormancer::ClientAPI<Hello>
        {
            friend class HelloworldPlugin;
        public:

            Hello(std::weak_ptr<Stormancer::AuthenticationService> auth) : Stormancer::ClientAPI<Hello>(auth) {}
            pplx::task<std::string> world(std::string name)
            {
                return this->getService<details::HelloService>("helloworld")
                    .then([name](std::shared_ptr<details::HelloService> hello)
                {
                    return hello->world(name);
                });
            }
            Stormancer::Event<std::string> helloBackReceived;


        private:
            void onConnecting(std::shared_ptr <details::HelloService> service)
            {
                std::weak_ptr<Hello> wThis = this->shared_from_this();
                //Always capture weak references, and NEVER 'this'. As the callback is going to be executed asynchronously,
                //who knows what may have happened to the object behind the this pointer since it was captured?
                helloBackReceivedSubscription = service->helloBackReceived.subscribe([wThis](std::string message) {
                    auto that = wThis.lock();
                    //If this is valid, forward the event.
                    if (that)
                    {
                        that->helloBackReceived(message);
                    }

                });
            }
            void onDisconnecting(std::shared_ptr <details::HelloService> service)
            {
                //Unsubscribe by destroying the subscription
                helloBackReceivedSubscription = nullptr;
            }

            Stormancer::Event<std::string>::Subscription helloBackReceivedSubscription;
        };

        class HelloworldPlugin : public Stormancer::IPlugin
        {
        public:

            void registerSceneDependencies(Stormancer::ContainerBuilder& builder, std::shared_ptr<Stormancer::Scene> scene) override
            {

                auto name = scene->getHostMetadata("helloworld");

                if (!name.empty())
                {
                    builder.registerDependency<details::HelloService>([](const Stormancer::DependencyScope& scope) {
                        auto instance = std::make_shared<details::HelloService>(scope.resolve<Stormancer::RpcService>());
                        instance->initialize(scope.resolve<Stormancer::Scene>());
                        return instance;
                    }).singleInstance();
                }

            }
            void registerClientDependencies(Stormancer::ContainerBuilder& builder) override
            {
                builder.registerDependency<Hello, Stormancer::AuthenticationService>().singleInstance();
            }
            void sceneConnecting(std::shared_ptr<Scene> scene) override
            {
                auto name = scene->getHostMetadata("helloworld");

                if (!name.empty())
                {
                    auto hello = scene->dependencyResolver().resolve<Hello>();
                    auto service = scene->dependencyResolver().resolve<details::HelloService>();
                    hello->onConnecting(service);
                }
            }

            void sceneDisconnecting(std::shared_ptr<Scene> scene) override
            {
                auto name = scene->getHostMetadata("helloworld");

                if (!name.empty())
                {
                    auto hello = scene->dependencyResolver().resolve<Hello>();
                    auto service = scene->dependencyResolver().resolve<details::HelloService>();
                    hello->onDisconnecting(service);
                }
            }
        };

    }

}