Facets

Facet is a class, whose public methods can be called remotely from your game client. Each one of these methods usually handles a player request that queries or modifies the database (e.g. player buys an item).

Creating a facet

Inside your Backend/Facets folder right click and choose Create > Unisave > Facet and name the facet HomeFacet.

using System;
using System.Collections;
using System.Collections.Generic;
using Unisave;

public class HomeFacet : Facet
{
    /// <summary>
    /// Client can call this facet method and receive a greeting
    /// Replace this with your own server-side code
    /// </summary>
    public string GreetClient()
    {
        return "Hello client! I'm the server!";
    }
}

Each public method on this class can be called over the internet.

Note: Protected, private or static methods cannot be called remotely.

Warning: Make sure you don't accidentally create a public helper method here since it could be called remotely and this might pose a security risk to your game. Make all helper methods private or protected instead.

Calling a facet method

When you want to call a facet method, use the CallFacet extension method:

using System;
using UnityEngine;
using Unisave;
using Unisave.Facets; // necessary for the extension to load

public class MyScript : MonoBehaviour
{
    void Start()
    {
        this.CallFacet(
            (HomeFacet f) => f.GreetClient()
        ).Then(FacetMethodHasBeenCalled);
    }

    void FacetMethodHasBeenCalled(string serverGreeting)
    {
        Debug.Log("Server greets you: " + serverGreeting);
    }
}

You can notice, that it's not exactly like calling an ordinary method. This is because of the time it takes for the call to complete. It may take hundreds of milliseconds, depending on the internet connection so you cannot wait, because your game would freeze. Instead, you register a callback using the .Then(...) method, that will be called once the response is received.

It's usually more convenient to use a lambda expression for the callback:

this.CallFacet(
    (HomeFacet f) => f.GreetClient()
).Then((greeting) => {
    Debug.Log("Server greets you: " + greeting);
});

Arguments

You can declare the facet method with arguments that can be passed to it via additional arguments, just like when calling an ordinary method:

string trackName = "Monaco";
string motorbikeName = "Yamaha";
int tier = 8;

this.CallFacet(
    (MatchmakerFacet f) => f.StartWaiting(
        trackName, motorbikeName, tier
    )
);

Return value

If your facet method returns a value, it will be passed as the only argument to the .Then callback.

this.CallFacet(
    (HomeFacet f) => f.GetPlayerEntity()
).Then((playerEntity) => {
    Debug.Log(playerEntity.PlayerName);
});

If it returns void, the callback should not accept any arguments:

this.CallFacet(
    (MatchmakerFacet f) => f.StartWaiting(...)
).Then(() => {
    // notice callback has no arguments
});

Exceptions

When an exception is raised inside the facet method, it can be caught using the .Catch callback:

this.CallFacet(
    (MatchmakerFacet f) => f.StartWaiting(...)
)
    .Then(() => { ... })
    .Catch((exception) => {
        if (exception is PlayerAlreadyWaitingException)
            Debug.Log("Already waiting.");
    });

Note that a classical try, catch block won't work, since the exception is not thrown as usual.

await calls

While the .Then callback approach is fine for simple calls, when you want to call multiple facets in a sequence or in a loop, you can use the async, await syntax of C#:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unisave;

public class MyScript : MonoBehaviour
{
    // notice the *async* keyword
    async void Start()
    {
        // notice the *await* keyword
        string serverGreeting = await this.CallFacet(
            (HomeFacet f) => f.GreetClient()
        );

        Debug.Log("Server greets you: " + serverGreeting);
    }
}

This approach is more readable and more C# friendly. I recommend using it over the .Then callbacks, however, make sure you fully understand the consequences of using async and await inside Unity first.

Tip: If you want to understand the differences between callbacks, async-await, and coroutines, watch this video from Unite Copenhagen 2019: https://www.youtube.com/watch?v=7eKi6NKri6I

This approach also has the advantage, that you can use try and catch the way you are used to:

try
{
    await this.CallFacet(
        (MatchmakerFacet f) => f.StartWaiting(...)
    );
}
catch (PlayerAlreadyWaitingException)
{
    Debug.Log("Already waiting.");
}

Multiple return values

Sometimes you need to return multiple values from a facet method. For example, when you need to load a garage, you need to have data about the player, data about the motorbikes they own, achievements they unlocked, ...

The cleanest way to do that is to simply define a new class to act as a data container:

using System;
using System.Collections;
using System.Collections.Generic;
using Unisave;

public class GarageFacet : Facet
{
    public class GarageData
    {
        public PlayerEntity playerEntity;
        public List<MotorbikeEntity> motorbikes;
        public AchievementsEntity achievementsEntity;
    }

    /// <summary>
    /// Player enters the garage so it has to be loaded
    /// </summary>
    public GarageData LoadGarage()
    {
        PlayerEntity player = Auth.GetPlayer<PlayerEntity>();

        return new GarageData {
            playerEntity = player,

            motorbikes = DB.TakeAll<MotorbikeEntity>()
                .Filter(m => m.Owner == player)
                .Get(),

            achievementsEntity = DB.TakeAll<AchievementsEntity>()
                .Filter(a => a.Owner == player)
                .First() ?? AchievementsEntity.Empty
        };
    }
}

Then if the new class becomes useful in other places, you can always refactor and extract it into a separate .cs file.

If on the other hand you want to be lazy, or you need a quick and simple solution, you can always use the System.Tuple<T1, T2, T3, ...> classes.

Note: You can learn more about C# tuples here: https://docs.microsoft.com/en-us/dotnet/csharp/tuples

Avoid complex argument expressions

While it might seem like we get an instance of a facet on which we call a method f.MyMethod(), it isn't actually what is happening:

this.CallFacet(
    (MyFacet f) => f.MyMethod() // this line actually NEVER RUNS!
);

The line (MyFacet f) => f.MyMethod() is only compiled as an expression tree and then dynamically analyzed during runtime to extract the name of the facet method and values for all arguments.

So while it can handle variable lookups, basic arithmetic operators, and function calls, I'd suggest keeping the arguments to this simple level. Compute any complex expressions before you call the facet and store them in a variable before passing them in.

// ✅ DO
this.CallFacet(
    (MyFacet f) => f.MyMethod(
        12, myVar, foo + "asd",
        Bar(), Baz(13, "14")
    )
);

// ❌ DON'T
this.CallFacet(
    (MyFacet f) => f.MyMethod(
        (from num in numbers
        where num % 2 == 0
        select num),
        await FooBar() ?? "none",
        3 << 5 as sbyte
    )
);

// ✅ DO INSTEAD
var a = (from num in numbers
        where num % 2 == 0
        select num);
var b = await FooBar() ?? "none";
var c = 3 << 5 as sbyte;
this.CallFacet(
    (MyFacet f) => f.MyMethod(a, b, c)
);

Calling facets outside of MonoBehaviours

Calling facets via the this.CallFacet is useful, because the facet request also knows which MonoBehaviour is making the request. This is helpful in cases where the request outlives the behvaiour i.e. the behaviour is Destroy(gameObject)-ed before the facet request finishes. In this case, Unisave discards the result of the facet call and does not call any callbacks (.Then and .Catch are never called, await never returns). Unity coroutines behave in the same way - they no longer run if the game object was destroyed.

If you want to call a facet from outside a MonoBehaviour or you want to break this behaviour association that the request has, you can call the API method directly through the FacetClient static class:

using Unisave.Facets;

public static async void MyGlobalFunction()
{
    var result = await FacetClient.CallFacet(
        (MyFacet f) => f.MyMethod()
    );
}