Aller au contenu principal

Persistance

L'API Sinespace dispose d'une série de fonctions API qui nous permettent de créer différents types de persistance. En tant que scripteurs, nous devons choisir le type le plus adapté à la tâche à accomplir, ou même les combiner. Dans ce guide, nous passerons en revue toutes les différentes classes et fonctions et expliquerons comment les utiliser.

Référence

Ces deux tableaux expliquent brièvement la différence entre les différents types de classes et de membres disposant de fonctionnalités de persistance, et nous développerons davantage par la suite.

ClasseMembreType de persistance
SNetwork
(scripting client)
SetRegionProperty
GetRegionProperty
SetShardProperty
GetShardProperty
HasShardProperties
Région semi-permanente
Région semi-permanente
Shard semi-permanent
Shard semi-permanent
Shard/Région semi-permanent
SPersistence
(scripting client)
UpdateInfo
RetrieveValue
SetValue
UpdateRegionInfo
RetrieveRegionValue
SetRegionValue
Joueur permanent
Joueur permanent
Joueur permanent
Région permanente
Région permanente
Région permanente
SShared
(scripting client)
SetSuperGlobal
GetSuperGlobal
Viewer semi-permanent
Viewer semi-permanent
SDatabase (scripting serveur)GetPlayerValue
SetPlayerValue
GetRegionValue
SetRegionValue
Joueur permanent
Joueur permanent
Région permanente
Région permanente
Types de persistanceDescription
Région semi-permanente

La persistance durera jusqu'à la fermeture de la Région (après que tous les joueurs aient quitté la Région).

Persistance appliquée à l'ensemble de la région (y compris tous les shards, s'il y en a d'actifs).

Shard semi-permanent

La persistance durera jusqu'à la fermeture du Shard (après que tous les joueurs aient quitté le Shard).

Persistance appliquée uniquement à un Shard spécifique.

Viewer semi-permanentLa persistance durera jusqu'à la fermeture du Viewer.
Persistance appliquée au Viewer.
Joueur permanentLa persistance durera de manière permanente.
Persistance appliquée à un Joueur spécifique.
Région permanenteLa persistance durera de manière permanente.
Persistance appliquée à la Région.

Qu'est-ce que la persistance ?

L'envoi de messages réseau est pratique pour des événements ponctuels, par exemple : un joueur qui fait un signe de la main pour dire bonjour, ce qui n'est pertinent que pendant quelques secondes. Cependant, parfois nous souhaitons effectuer des modifications pertinentes pendant plus que quelques secondes, par exemple : l'ouverture d'une fenêtre.

Si nous envoyons un message réseau pour diffuser à tous les joueurs que la Fenêtre A est désormais ouverte, lorsqu'un nouveau joueur entrera dans notre région, il aura manqué ce message réseau, et la Fenêtre A restera fermée pour lui dans son viewer.

C'est pourquoi nous avons besoin de la capacité de rendre certaines modifications de la scène persistantes, soit de manière permanente, soit au moins jusqu'à ce que tout le monde ait quitté la région.

Classe SNetwork

La classe SNetwork dispose de diverses fonctions de réseau, mais possède 4 fonctions et 1 propriété liées à la persistance.

  • SetRegionProperty/GetRegionProperty
  • SetShardProperty/GetShardProperty
  • HasShardProperties

Shard vs Région

Vous remarquerez que certaines fonctions s'appliquent aux Shards et d'autres aux Régions. Qu'est-ce qu'un "shard" au juste ?

Un Shard est une fonctionnalité de Sinespace où une Région peut avoir plusieurs Shards (instances), très semblable à des univers parallèles, où les joueurs d'un Shard ne peuvent pas voir les joueurs d'un autre Shard.

Dans le cadre de ce guide pour débutants, nous n'aborderons pas le scripting d'expériences multi-Shards, mais il est suffisant de comprendre les différences.

Que sont les propriétés Shard/Région ?

Pour chaque Shard/Région ouverte (il y a des joueurs à l'intérieur), un stockage semi-permanent est créé sur le serveur qui contient des propriétés. Tous les scripts utilisant ces fonctions accéderont à ce même stockage pour lire et écrire des propriétés. Ce stockage est une collection de clés et de valeurs. Ce stockage sera fermé après la fermeture du Shard/Région (après que tous les joueurs aient quitté).

Quelle "clé" devez-vous utiliser ?

Vous souhaitez principalement que votre clé soit privée, afin que d'autres scripts ne puissent pas y accéder. Vous voudrez également très probablement que votre clé soit spécifique à un GameObject.

Nous pouvons réaliser les deux points ci-dessus de la manière suivante :

thisGameObject = Space.Host.ExecutingObject
KEY = 'SomethingSecret' .. thisGameObject.ID

Nous avons maintenant créé une clé qui est à la fois privée et spécifique à un GameObject. Nous l'utiliserons plus tard pour accéder au stockage des propriétés et lire/écrire une valeur spécifique.

HasShardProperties

Lorsqu'un joueur rejoint la région, les scripts qui souhaitent accéder immédiatement à la base de données des propriétés peuvent s'exécuter pendant une très courte période où nous ne sommes pas encore connectés au stockage des propriétés. C'est pourquoi nous utilisons HasShardProperties pour vérifier si la base de données de stockage est connectée et prête pour la lecture/écriture. (HasShardProperties est utilisé pour les propriétés Shard et les propriétés Région)

Étoffons notre code précédent en ajoutant une coroutine (exécuter notre code dans un thread parallèle) pour gérer cela :

thisGameObject = Space.Host.ExecutingObject
KEY = 'SomethingSecret' .. thisGameObject.ID

function TheCoroutineFunction()
while not Space.Network.HasShardProperties do --this while loop keeps looping until HasShardProperties returns true
coroutine.yield(0) --this ensures our loop runs once per frame, otherwise it will be crashed for running too long on a single frame
end

--At this point we have the confirmation we need to begin read/write

end

Space.Host.StartCoroutine(TheCoroutineFunction)

Définir et obtenir des propriétés

Les propriétés Shard et les propriétés Région sont exécutées de manière identique. Ainsi, dans le cadre de ce guide, nous continuerons à utiliser uniquement les propriétés Région.

Remarque : Les deux fonctions ci-dessous sont limitées à 10 appels par seconde. (20 appels par seconde sur Breakroom)

Pour obtenir une propriété Région : value = Space.Network.GetRegionProperty(KEY)

Remarque : Parfois, une propriété Région peut ne pas avoir été définie auparavant, elle peut donc retourner Nil. Nous devrons ajouter une vérification pour cela.

Pour définir une propriété Région : Space.Network.SetRegionProperty(KEY, 'A Value')

Remarque : Les valeurs des propriétés Région sont de type chaîne de caractères, vous devrez donc convertir vos nombres ou tableaux en chaîne pour les stocker.

thisGameObject = Space.Host.ExecutingObject
KEY = 'SomethingSecret' .. thisGameObject.ID

function TheCoroutineFunction()
while not Space.Network.HasShardProperties do --this while loop keeps looping until HasShardProperties returns true
coroutine.yield(0) --this ensures our loop runs once per frame, otherwise it will be crashed for running too long on a single frame
end
--below part only runs after weve got our confirmation
value = Space.Network.GetRegionProperty(KEY) --how to read
Space.Network.SetRegionProperty(KEY, 'A Value') --how to write
end

Space.Host.StartCoroutine(TheCoroutineFunction)

L'exemple de la fenêtre

Écrivons un script pour une fenêtre qui, lorsqu'elle est cliquée, s'ouvre/ferme et met à jour sa propriété Région, et qui lit également en permanence la propriété Région au cas où un autre joueur ouvrirait/fermerait la fenêtre.

Nous allons ajouter à notre script deux parties principales. La première partie lit constamment la propriété Région pour surveiller et réagir aux changements. La deuxième partie réagit aux clics et met à jour la propriété Région qui contient l'état de la fenêtre (ouverte/fermée).

La première partie

thisGameObject = Space.Host.ExecutingObject
KEY = 'SomethingSecret' .. thisGameObject.ID
refWindow = Space.Host.GetReference("window") -- this should be linked in the Scripting Runtime "references" section


function TheCoroutineForFirstPart()
while true do
local value = Space.Network.GetRegionProperty(KEY) --how to read

if value = nil then
--we're going to default the window to closed if Nil
refWindow.Active = true
elseif value == "Open" then
--if it's "Open" we're going to make the window disappear
refWindow.Active = false
elseif value == "Closed" then
--if it's "Closed" we're going to make the window appear
refWindow.Active = true
end
coroutine.yield(0.2) --We'll make this run every 0.2 seconds. That's should be quick enough and we also have a limit to mind.
end
end


function TheCoroutineFunction()
while not Space.Network.HasShardProperties do --this while loop keeps looping until HasShardProperties returns true
coroutine.yield(0) --this ensures our loop runs once per frame, otherwise it will be crashed for running too long on a single frame
end
--below part only runs after weve got our confirmation
Space.Host.StartCoroutine(TheCoroutineForFirstPart) -- we can no start our coroutine which consist of the first part

Space.Network.SetRegionProperty(KEY, 'A Value') --how to write
end


Space.Host.StartCoroutine(TheCoroutineFunction)

La deuxième partie (et code final)

thisGameObject = Space.Host.ExecutingObject
KEY = 'SomethingSecret' .. thisGameObject.ID
refWindow = Space.Host.GetReference("window") -- this should be linked in the Scripting Runtime "references" section


function TheCoroutineForFirstPart()
while true do
local value = Space.Network.GetRegionProperty(KEY) --how to read

if value = nil then
--we're going to default the window to closed if Nil
refWindow.Active = true
elseif value == "Open" then
--if it's "Open" we're going to make the window disappear
refWindow.Active = false
elseif value == "Closed" then
--if it's "Closed" we're going to make the window appear
refWindow.Active = true
end
coroutine.yield(0.2) --We'll make this run every 0.2 seconds. That's should be quick enough and we also have a limit to mind.
end
end

function TheOnClickFunctionForSecondPart() --Second Part code which will update region properties when object is clicked
local checkFirst = Space.Network.GetRegionProperty(KEY)

if checkFirst == nil then --if it's nil, this window was never used, so we update property to open
Space.Network.SetRegionProperty(KEY, "Open")
elseif checkFirst == "Open" --if its "Open" we update it to "Closed"
Space.Network.SetRegionProperty(KEY, "Closed")
elseif checkFirst == "Closed" --if its "Closed" we update it to "Open"
end

end

function TheCoroutineFunction()
while not Space.Network.HasShardProperties do --this while loop keeps looping until HasShardProperties returns true
coroutine.yield(0) --this ensures our loop runs once per frame, otherwise it will be crashed for running too long on a single frame
end
--below part only runs after weve got our confirmation
Space.Host.StartCoroutine(TheCoroutineForFirstPart) -- we can no start our coroutine which consist of the first part

Space.Network.SetRegionProperty(KEY, 'A Value') --how to write
end


thisGameObject.AddClickable() --we make this object a clickable
thisGameObject.Clickable.OnClick(TheOnClickFunctionForSecondPart) --we add our Second Part new code to be executed On cLick
Space.Host.StartCoroutine(TheCoroutineFunction)


Classe SShared

La classe SShared (scripting client) dispose de diverses fonctions de communication inter-scripts et de stockage local du viewer, mais possède 2 fonctions liées à la persistance.

  • SetSuperGlobal
  • GetSuperGlobal

Qu'est-ce qu'un Super Global ?

Un "Global" dans la classe SShared est un stockage dans le viewer du joueur local accessible à tous les scripts.

Un "Super Global" est le même stockage mais semi-permanent ; il continuera d'exister même lors du changement de région, jusqu'à la fermeture du viewer.

Pour cet exemple, nous créerons un objet cliquable qui nous téléporte vers une région, et dans cette région, nous aurons un script qui réagit en fonction de notre provenance.

Quel "namespace" et quelle "clé" devez-vous utiliser ?

Contrairement à notre cas précédent, ces fonctions stockent les données dans le viewer d'un joueur spécifique, et elles pourraient être destinées à être accessibles par plusieurs scripts (ce pourraient être nos autres scripts ou ceux d'autres scripteurs). Elles sont intrinsèquement spécifiques à un joueur, et probablement à un projet ou une opération/objectif spécifique.

C'est pourquoi elles sont accessibles à l'aide d'un namespace, d'une clé et d'une valeur, par opposition à seulement une clé et une valeur. Le namespace pourrait être un type d'accesseur qui n'a pas besoin d'être privé, tout en ayant un accesseur clé que nous pouvons rendre privé.

--SCRIPT A
NAMESPACE = 'space.sine.apidocs.persistenceguide' --this format is just a naming convention
KEY = 'Teleported From'

Définir et obtenir des Super Globals

Lors de la définition d'un Super Global, la valeur n'a pas besoin d'être une chaîne. Elle accepte une valeur de type DynValue, ce qui signifie qu'elle peut être une chaîne, un nombre ou un tableau, etc.

Nous définissons un Super Global comme suit :

Space.Shared.SetSuperGlobal('namespace', 'key', AVariable)

Nous obtenons un Super Global comme suit :

value = Space.Shared.GetSuperGlobal('namespace','key')

L'exemple du téléporteur

Nous créerons 2 scripts pour cet exemple. Le premier script sera celui qui nous téléporte et définit un Super Global, et le second sera à la destination et accédera à notre Super Global pour réagir à notre point de départ.


Nous commencerons par créer notre premier script en développant notre code ci-dessus en ajoutant un élément cliquable et en faisant définir un Super Global avec les informations dont nous avons besoin pour passer à la deuxième région. Dans le cadre de ce guide, nous transmettrons nos informations dans un Tableau plutôt que dans une seule variable.

--Script A
NAMESPACE = 'space.sine.apidocs.persistenceguide' --this format is just a naming convention
KEY = 'Teleported From'
thisGameObject = Space.Host.ExecutingObject

function OnClickFunction()

local tableValue = {id = Space.Scene.RegionID, name = Space.Scene.Name} --we create a Table that holds both RegionID and Region Name
Space.Shared.SetSuperGlobal(NAMESPACE,KEY, tableValue) --we Set our Super Global
Space.Scene.PlayerAvatar.Teleport(0000000) -- We teleport. You'll have to replace the zeros with your region ID

end

thisGameObject.AddClickable() --we make the object clickable
thisGameObject.Clickable.OnClick(OnClickFunction) --we hook our OnClickFunction to the clickable's OnClick event

Nous avons maintenant un objet qui, lorsqu'il est cliqué, définit un Super Global puis nous téléporte. Dans la région de destination, nous créerons maintenant le script qui traite nos informations transportées (nom et ID de la région).

--Script B
NAMESPACE = 'space.sine.apidocs.persistenceguide' --this format is just a naming convention
KEY = 'Teleported From'

getTableValue = Space.Shared.GetSuperGlobal(NAMESPACE, KEY)

Space.Log("You have arrived from region ID: ".. getTableValue.id .. " with Name: " .. getTableValue.name)

Classe SPersistence

La classe SPersistence possède 6 fonctions toutes utilisées pour créer une persistance permanente.

  • UpdateInfo/UpdateRegionInfo
  • RetrieveValue/RetrieveRegionValue
  • SetValue/SetRegionValue

Que sont les Valeurs de Région et les Valeurs ?

La moitié des fonctions de cette classe créent une persistance de Région permanente, et l'autre moitié créent une persistance de Joueur permanente.

Si la fonction contient le mot region, cela signifie qu'elle crée une persistance de Région. Si ce n'est pas le cas, cela signifie qu'elle crée une persistance de Joueur.

Les valeurs Région/Joueur sont des valeurs dans un stockage permanent. Les valeurs Région sont dans un stockage permanent lié à la région, et les Valeurs (Joueur) sont dans un stockage permanent lié au joueur exécutant le script.

En termes d'utilisation, les Valeurs Région sont l'équivalent de la version permanente de la classe SNetwork. Tandis que les Valeurs Joueur sont l'équivalent de la classe SShared.

Quelle "clé" devez-vous utiliser ?

Dans le cas des valeurs Région, nous créerons des clés de la même manière que nous l'avons fait pour la classe SNetwork.

Dans le cas des valeurs (Joueur), nous créerons des clés de la même manière que nous l'avons fait pour la classe SShared.

Pourquoi devons-nous "Mettre à jour les infos de la Région" ?

La caractéristique unique de cette classe est qu'il s'agit d'une classe de scripting client, mais qui nous donne la capacité de créer une persistance sans utiliser de script serveur.

Le stockage utilisé pour y parvenir est externe, ce qui signifie que nous devons commencer par utiliser la fonction UpdateRegionInfo() pour demander au stockage de nous envoyer une copie de ses valeurs les plus récentes (avant d'essayer d'accéder à ces valeurs).

Nous le faisons de cette manière :

function WhatToDoWhenInfoIsReady()
--We access the data we need from inside here
end
Space.Persistence.UpdateRegionInfo() --or UpdateInfo() for Player version

Contrairement aux propriétés de région semi-permanentes, cet appel de fonction n'est pas instantané. Comme il accède à un stockage permanent externe, nous devons accrocher une fonction qui sera appelée une fois que les informations sont arrivées.

thisGameObject = Space.Host.ExecutingObject
KEY = "Secret" .. thisGameObject.GlobalID

function WhatToDoWhenInfoIsReady()
value = Space.Persistence.RetrieveRegionValue(KEY) --Getting a region value
Space.Log(value) --should print "Something"
end

Space.Persistence.SetRegionValue(KEY, "Something") --Setting a region value
Space.Persistence.UpdateRegionInfo()



La vitesse de réponse plus lente ou variable de cette fonctionnalité signifie qu'il n'est pas recommandé de l'appeler rapidement comme nous l'avons fait avec les propriétés de région SNetwork. Par conséquent, nous pourrions devoir mélanger notre code avec d'autres types de persistance.

Exemple des Lumières Intelligentes

Visitez notre page de projet d'exemple Lumières Intelligentes.

Classe SDatabase

La classe SDatabase est l'équivalent de la classe SPersistence, la seule différence étant qu'elle est accessible uniquement via les Scripts Serveur**. Les scripts serveur n'ont pas accès à la classe SPersistence.

Un script serveur est également intrinsèquement semi-permanent. Cela signifie que si vous faites ceci aVariable = 5 dans un script serveur. Cette variable sera persistante jusqu'à ce que tous les joueurs quittent la région. Cela rend l'utilisation de la classe SDatabase beaucoup plus facile que la classe SPersistent, car il n'est pas nécessaire de mélanger les méthodes persistantes.

Un inconvénient des scripts serveur est qu'ils ne peuvent être placés que dans des objets de mobilier et doivent être chargés sur une région pour être utilisés (contrairement aux scripts client).

Puisque cette classe est presque une copie exacte de la classe SPersistence. Nous ne décrirons que les différences ci-dessous (pour éviter les répétitions).

Différences entre SDatabase et SPersistence

Les deux seules petites différences entre ces deux classes sont :

  1. Les fonctions SetValue/GetValue dans SPersistence sont SetPlayerValue/GetPlayerValue dans SDatabase
  2. GetRegionValue et GetPlayerValue ont un paramètre supplémentaire onSave qui est un crochet nous informant lorsque la valeur a été définie avec succès.
--Setting a region value using SPersistence in a client script
Space.Persistence.SetRegionValue(KEY, "Something")

--vs

--Setting a region value using SDatabase in a server script
function onSave()
--do something when set is succesful
end
Space.Database.SetRegionValue(KEY, "Something", onSave)

Merci

Merci d'avoir terminé ce guide. Si vous rencontrez des problèmes ou souhaitez recommander des améliorations, veuillez le faire sur cette page.