Przyczyny zdarzenia
Eventy (wydarzenia) są świetne do dołączania dodatkowej logiki do akcji gry, ale mają wadę posiadania braku kontekstu co spowodowało dany event (wydarzenie). Obiekt Cause pozwala dostarczać i otrzymywać dodatkowe informacje na temat kontekstu eventu. Ta informacja może zostać później użyta do modyfikacji zachowania Twojego event listenera (nasłuchiwacza wydarzenia).
Dla przykładu, plugin ochraniający świat potrzebuje informacji o tym, jaki gracz wywołał wydarzenie ChangeBlockEvent zanim zdecyduje czy wydarzenie powinno zostać anulowane czy nie. Zamiast iść tradycyjną ścieżką tworzenia wielu subeventów (pod-wydarzeń) dla różnych warunków źródłowych, ta informacja jest przedstawiona w obiekcie Cause
w evencie (wydarzeniu).
Każdy event posiada obiekt powodu Cause
, który może zostać wykorzystany w celu uzyskania informacji dotyczącej, dlaczego event został wywołany. Obiekt Cause
może być wydobyty z eventu przy pomocy Event#cause().
Przyczyna i Kontekst
Obiekt Cause
zawiera dwa różne zestawy informacji, stos przyczyn i EventContext.
Stosem przyczyn zdarzenia są przyczyny przechwycone w kolejności ważności. Obiekty w stosie przyczyn (cause stack) nie posiadają dołączonych do nich nazw.
Kontekst eventu (wydarzenia) zawiera dodatkowe informacje dotyczące wydarzenia. Konteksty są dołączone do kluczy, ale nie są w kolejności, są równie ważne.
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.
Innym przykładem, na który musisz uważać, jest sytuacja w której symulujesz gracza. Symulowany gracz nie może znajdować się w bezpośredniej przyczynie, ponieważ taki gracz nie mógł być zaangażowany w akcje, jednakże taki gracz może pojawić się w EventContext
pod EventContextKeys#PLAYER_SIMULATED
Pobieranie Obiektów z Bezpośredniej Przyczyny
Strukturalnie obiekt Cause
zawiera sekwencyjną listę obiektów. Istnieje kilka metod, które można wykorzystać do pobrania informacji z obiektu Przyczyny, które omówimy tutaj. Aby uzyskać kompletną listę zobacz Javadocs <https://jd.spongepowered.org>.
Informacja
The objects within a cause are ordered such that the first object is the most immediate cause of the event, and subsequent objects are of decreasing importance and/or may only provide contextual information.
Cause#root() returns the first object within the cause. This object is the most immediate or direct cause of
the event. Since a Cause
may not be empty, it is guaranteed to have a root
.
Cause#first(Class) returns the first object in the cause chain whose type is either the same as or is a
subtype of the given class. For example, given a cause which contained a player followed by an entity
[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
}
Both optionals would contain the player object as its type directly matched request for a Player type and it matched the request for an Entity type as Player is a subtype of Entity.
Cause#last(Class) is similar to Cause#first(Class)
except it returns the last value in the cause chain
matching the type.
Continuing from the example above, if we instead changed it to call Cause#last(Class)
the first
optional would contain the player object still, but the second optional would now contain
the entity that we passed in the second position of the cause.
Cause#containsType(Class) returns a boolean value and can be used to check if a cause chain contains any object matching the provided type.
Cause#all() simply returns all objects within the cause allowing more advanced handling.
Event Context
Czasem kolejność obiektów wewnątrz powodu nie jest wystarczająca aby dowiedzieć się co reprezentuje dany obiekt w odniesieniu do eventu. W tutaj z pomocą przychodzi EventContext. Kontekst eventu pozwala obiektom być przypisanym pod unikatowe klucze z EventContextKeys, tym samym pozwalają im być łatwo identyfikowalnym. Niektórymi przykładami użyć nazywanych powodów są Notifier dla ChangeBlockEvent.All lub Source
dla DamageEntityEvent.
Unlike the cause stack, which makes no guarantees as to the objects contained witin it, an object associated with a EventContextKey is guaranteed to be of the type specified by the key.
Retrieving a entry from the context of a cause
@Listener
public void onGrow(ChangeBlockEvent.All event) {
Optional<UUID> notifier = event.cause().context().get(EventContextKeys.NOTIFIER);
}
This example makes use of EventContext#get(EventContextKey) which can be used to retrieve the expected object
associated with a name if it is present within the context. Additionally EventContext#asMap() provides
a Map<EventContextKey<?>, Object>
which can be used to find all present EventContextKey
s and their associated
objects.
Informacja
Some common identifying names for EventContextKey
s are present as static fields in the
EventContextKeys
class.
Creating Custom Causes
Creating a cause is easy, but depends on whether you are creating your cause on the main server thread or async.
Informacja
Cause objects are immutable therefore cannot be modified once created.
Using the CauseStackManager
Ostrzeżenie
The CauseStackManager
only works on the main server thread. If you call it from a
different thread, an IllegalStateException
will be thrown. Ensure you are on the main
server thread before calling methods on the CauseStackManager
.
Jeśli tworzysz swój event w głównym wątku, użyj CauseStackManager z właściwego Engine, który może zostać znaleziony w Engine#causeStackManager(). CauseStackManager
śledzi potencjalne przyczyny eventów w trakcie gry, pozwalając na łatwe wydobycie obecnej przyczyny Cause
bez większych trudności. Aby sprawdzić obecną przyczynę, użyj CauseStackManager#currentCause(). Możesz zauważyć że PluginContainer Twojego pluginu znajduje się już w zwróconej Cause
, dlatego że pluginy są jednymi z wielu obiektów śledzonymi przez menedżer. Używanie CauseStackManager
do tworzenia przyczyn, usuwa potrzebę zbędnego kodu, w którym dostarcza się obiekty jak kontener pluginu. Dzięki temu mżesz skoncentrować się na dodawaniu swoich własnych przyczyn.
Before adding your own causes, you should push a cause stack frame to the manager. Adding a frame acts as a saved state, when you are done with your causes, the removal of the frame returns the manager to its original state.
Wskazówka
Dodając ramę do CauseStackManagera nie usuwa tego co już się w nim znajduje. Wszystko co istnieje już w kontekście i stosie przyczyn przed dodawaniem ramy, będzie tam dalej po jej dodaniu. Możesz to zweryfikować wywołując Sponge.server().causeStackManager().currentCause()
przed i po wychnięciu swojej ramki.
Dla przykładu, jeśli stos przyczyn zawiera PluginContainer
i ServerPlayer
kiedy ramka zostanie wypchnięta, pozostaną one na stosie i będą zawarte w części Cause
kiedy zostanie ona pobrana.
For example, if you were to fire an event that was simulating another player in a sudo like command,
you may want to add the player you are acting as in the cause and the GameProfile
of the player that you are
simulating in the context (as the simulated player is not directly responsible for the event being fired.)
Creating a custom Cause with the CauseStackManager
In this example, the variables would be populated, the cause would contain the playerToSimulate
as
the root cause, the sourceRunningSudo
as the second object in the cause and the GameProfile
as the EventContextKeys#PLAYER_SIMULATED context, in addition to anything already in the
CauseStackManager
. Your event code would be at the bottom of the method.
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();
}
Note that the last item you push to the cause stack will be the root of the Cause
as
stacks are „last in, first out” (LIFO) structures.
Wskazówka
For more information about the stack data type and why the order matters, see the Stack javadocs or this Wikipedia article.
Using the Cause Builder
If you are creating an event that does not fire on the main thread, you cannot use the
CauseStackManager
. Instead, you will need to create a Cause
object manually.
Creating a cause object is easy using the Cause.Builder. You can obtain a
builder by calling Cause.builder()
. To add a cause to the builder, use the
Cause.Builder#append(Object) method, but note that unlike the CauseStackManager
,
the first element you add will be the root, not the last.
If you wish to add contexts, there is a separate builder for those, the
EventContext.Builder, accessed by calling EventContext#builder()
.
The EventContext
can then be added using the Cause.Builder#build(EventContext)
when
you have finished building the Cause
up.
Taking the previous example, this is how we would build it using the cause builder.
Creating a custom Cause with the Cause and EventContext builders
Note that in this example, the variables would be populated, and that the first entry appended to the cause would be the root cause.
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);
Think carefully about what information to include in your cause.
If you’re firing an event from your plugin which is usually triggered through other means,
it is a good idea to include your PluginContainer
in the cause so other plugins know
that the event comes from your plugin. If you are firing the event on behalf of a player
due to some action it’s usually a good idea to include that player in the cause.