Causes des Events
Les événements sont utiles pour attacher une logique supplémentaire aux actions du jeu, mais ils ont l’inconvénient de ne fournir quasiment aucun contexte quant à ce qui a provoqué cet événement. L’objet Cause permet de fournir et de recevoir des informations contextuelles à propos de l’événement. Ces informations peuvent être utilisées pour modifier le comportement de votre event listener.
Par exemple, un plugin de protection de monde a besoin d’informations sur quel joueur a causé l’appel du ChangeBlockEvent avant de pouvoir décider si l’événement doit être annulé ou pas. Plutôt que de passer par la voie traditionnelle avec la création d’une multitude de sous-événements pour les différentes conditions de source, cette information est fournie dans la Cause
de l’événement.
Every event provides a Cause
object which can be interrogated for the information pertaining to why the event was
fired. The Cause object can be retrieved from an event by simply calling Event#cause().
Cause et Contexte
L’objet Cause
contient deux ensembles distincts d’informations, la pile de causes et l” EventContext.
La pile de causes de l’event est la cause directe capturée par importance. Il n’y a pas de noms attachés aux objets constituant cette pile.
Le contexte de l’event contient des informations supplémentaires sur cet event. Les contextes sont attachés à des clés, et n’ont pas d’ordre, ils ont tous la même importance.
As an example, if a sheep owned by a player eats some grass, the most direct cause of this is the sheep. The
player would be in the EventContext
as the EventContextKeys#PLAYER, giving event consumers
that additional information about how the event has come about, but would not necessarily be within the
direct cause itself.
Un autre exemple serait de vouloir savoir si vous simulez un joueur.Le joueur simulé ne sera peut être pas la cause directe puisqu’il ne sera pas impliqué dans l’action, mais il sera tout de même dans l” EventContext
sous EventContextKeys#PLAYER_SIMULATED
Récupérer des objets de la cause directe
Structurellement, un objet Cause
contient une liste séquentielle d’objets. Il y a de nombreuses méthodes de récupération des informations d’un objet Cause dont nous discuterons ici, pour obtenir une liste plus complète veuillez consulter les Javadocs.
Note
Les objets à l’intérieur d’une cause sont classés tels que le premier objet est la cause la plus immédiate de l’événement, et les objets suivants ont une importance décroissante et/ou peuvent seulement fournir des informations contextuelles.
Cause#root() retourne le premier objet à l’intérieur d’une cause. Cet objet est la cause la plus immédiate ou directe de l’événement. Puisqu’une Cause
ne peut pas être vide, elle possède forcément une racine (root
).
Cause#first(Class) retourne le premier objet dans la chaîne de la cause dont le type est soit identique, soit un sous-type de la classe donnée. Par exemple, pour une cause qui contient un joueur suivi par une entité : [Player, Entity, ...]
@Listener
public void onEvent(ExampleCauseEvent event) {
Cause cause = event.cause(); // [Player, Entity]
Optional<Player> firstPlayer = cause.first(Player.class); // 1
Optional<Entity> firstEntity = cause.first(Entity.class); // 2
}
Les deux Optionals contiendraient l’objet joueur puisque son type correspond directement à la demande pour un objet de type Player, et il correspond aussi à la demande pour un objet de type Entity puisque Player est un sous-type d’Entity.
Cause#last(Class) est semblable à Cause#first(Class)
, sauf qu’il retourne la dernière valeur dans la chaîne de cause correspondant au type.
Pour continuer dans l’exemple ci-dessus, si au lieu de ça nous l’avions changé pour appeler Cause#last(Class)
, le premier Optional contiendrait encore l’objet Player, mais le second Optional contiendrait désormais l’entité que nous avons passé en seconde position de la cause.
Cause#containsType(Class) retourne un booléen et peut être utilisé pour vérifier si une chaîne de cause contient un objet correspondant au type fournit.
Cause#all() retourne simplement tous les objets à l’intérieur de la cause pour permettre une gestion plus avancée.
Contexte de l’event
Sometimes the ordering of objects within the cause isn’t enough to get the proper idea of what an object represents in
relation to the event. This is where EventContext comes in. The event context allows objects to be
associated with unique names, in the form of EventContextKeys, allowing them to be easily identified and
requested. Some examples of use cases for named causes is the Notifier of a ChangeBlockEvent.All or the
Source
of a DamageEntityEvent.
Contrairement à la pile de causes, qui ne garantit pas le type de l’objet contenu, la valeur d’un EventContextKey sera toujours du type spécifié par la clé.
Récupérer une entrée du contexte de la cause
@Listener
public void onGrow(ChangeBlockEvent.All event) {
Optional<UUID> notifier = event.cause().context().get(EventContextKeys.NOTIFIER);
}
Cet exemple utilise EventContext#get(EventContextKey) qui peut être utilisé pour récupérer l’objet associé à un nom s’il est présent dans le contexte. De plus, EventContext#asMap() fournit une Map<EventContextKey<?>, Object>
qui peut être utilisée pour obtenir tous les EventContextKey
s et leurs objets.
Note
Les EventContextKey
communs sont présents dans des fields statiques dans la classe EventContextKeys
.
Créer une cause personnalisée
Créer une cause est simple, mais dépend de si sa création se fait sur le thread principal ou en asynchrone.
Note
Les objets Cause sont immuables donc il ne peuvent pas être modifiés une fois créés.
Utiliser le CauseStackManager
Avertissement
Le CauseStackManager
ne fonctionne que sur le thread principal du serveur, si ce n’est pas le cas, un IllegalStateException
sera lancé. Assurez-vous d’être sur le thread principal avant d’appeler les méthodes du CauseStackManager
.
If you are creating your event on the main thread, then use the CauseStackManager from the
appropriate Engine, which can be found at Engine#causeStackManager().
The CauseStackManager
tracks the potential causes of events as the game runs, allowing for easy
retrieval of the current Cause
without effort. To see the current cause, call
CauseStackManager#currentCause(). You may notice that your plugin’s PluginContainer
is already in the returned Cause
, as plugins are one of the objects tracked by the manager. Using the
CauseStackManager
for creating causes removes the need for boilerplate-like code where you supply
objects like your plugin container, so that you can concentrate on adding your own causes.
Avant d’ajouter vos propres causes, vous devriez pousser une cause stack frame dans le manager. Ajouter une frame agot comme un état sauvegardé, quand vous en avez fini avec vos causes, la suppression de la frame rend au manager sont contenu original.
Astuce
Adding a frame to the CauseStackManager does not remove what is already in the manager, so anything
that is in the cause stack and contexts before a stack frame is added will be there afterwards. You
can verify this by calling Sponge.server().causeStackManager().currentCause()
before and after the
frame is pushed.
For example, if the cause stack contains a PluginContainer
and a ServerPlayer
when a frame
is pushed, they will remain on the stack and will form part of the Cause
if one is obtained from
the frame.
Par exemple, si vous lancez un event qui simule un joueur dans une commande comme sudo, vous voudriez vouloir ajouter le joueur agissant comme cause et le GameProfile
de ce joueur qua vous simulez dans le contexte (comme le joueur simulé n’est pas la cause responsable de l’event).
Créer une Cause personnalisée avec le CauseStackManager
Dans cet exemple, les variables seront remplies, la cause contiendrait le playerToSimulate
comme racine de la cause, sourceRunningSudo
sera le second objet dans la cause et le GameProfile dans EventContextKeys#PLAYER_SIMULATED à l’intérieur du contexte, en plus de tout ce que le CauseStackManager
contient déjà. Votre code d’événement sera en bas de la méthode.
CommandSource sourceRunningSudo = ...;
Player playerToSimulate = ...;
try (CauseStackManager.StackFrame frame = Sponge.server().causeStackManager().pushCauseFrame()) {
frame.pushCause(sourceRunningSudo);
frame.pushCause(playerToSimulate);
frame.addContext(EventContextKeys.PLAYER_SIMULATED, playerToSimulate.getProfile());
Cause cause = frame.currentCause();
}
Notez que le dernier objet poussé dans la pile de causes sera la racine de la Cause
comme les stacks suivent la structure « last in, first out » (LIFO).
Astuce
Pour plus d’informations à propos des types de données des piles et du pourquoi l’ordre est important, voir les javadocs de Stack ou cet article Wikipedia.
Utiliser le builder de Cause
Si vous créez un event qui n’est pas lancé depuis le thread principal, vous ne pouvez pas utiliser le CauseStackManager
, à la place, vous devrez créer une Cause
manuellement.
Créer une cause est simple en utilisant le Cause.Builder, vous pouvez obtenir un builder en appelant Cause.builder()
. Pour ajouter une cause au builder, utilisez Cause.Builder#append(Object), mais notez que contrairement au CauseStackManager
, le premier élément que vous ajoutez sera la racine, par le dernier.
Si vous voulez ajouter des éléments au contexte, il y a un builder séparé, le EventContext.Builder, disponible via EventContext#builder()
. L” EventContext
peut êyte ajouté en utilisant Cause.Builder#build(EventContext)
qyand vous avez finit de configurer votre Cause
.
Pour remprendre l’exemple précédent, voilà comment nous construirons la cause en utilisant son builder.
Créer une Cause personnalisé avec les builders de Cause et l’EventContext
Notez que dans cet exemple, les variables sont assignées, et que la première entrée mise dans la cause sera la racine.
CommandSource sourceRunningSudo = ...;
Player playerToSimulate = ...;
PluginContainer plugin = ...;
EventContext context = EventContext.builder()
.add(EventContextKeys.PLAYER_SIMULATED, playerToSimulate.profile())
.add(EventContextKeys.PLUGIN, plugin)
.build();
Cause cause = Cause.builder()
.append(playerToSimulate)
.append(sourceRunningSudo)
.append(plugin)
.build(context);
Pensez bien à quelles informations inclure dans la cause, si vous lancez un event depuis votre plugin, qui est généralement lancé depuis d’autres moyens, c’est une bonne idée de mettre votre PluginContainer
dans la cause, pour que les autres plugins sachent que l’event vient de votre plugin. Si vous lancez l’event après l’action d’un joueur, c’est généralement une bonne idée d’inclure ce joueur dans la cause.