Причины Событий
События великолепно подходят для добавления дополнительной логики в действия игры, однако также у них есть механизм для получения причины по которой событие случилось. Объект Cause позволяет предоставлять и получать дополнительную информацию о событии. Эта информации может быть использована для изменения поведения вашего слушателя.
К примеру, плагин для защиты мира требует для работы информацию, какой игрок вызвал ChangeBlockEvent для того чтобы решить, нужно ли отменять событие. Вместо создания большего количество дочерних событий для различных исходных данных, вся информации уже помещена в Cause
события.
Каждое событие предоставляет объект Cause
для получения информации из-за чего событие было вызвано. Объект Cause может быть получен из события вызовом метода Event#cause().
Причина и Контекст
Объект Cause
содержит два разных набора информации, стек причин и EventContext.
The cause stack of the event are the direct causes captured in order of importance. There are no names attached to the objects in the cause stack.
Контекст события содержит дополнительную информацию, связанную с событием. Контексты привязаны к ключам, но не имеют порядка, все они одинаково важны.
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.
Another example that you may need to watch out for is if you simulate a player. The simulated player may not be
in the direct cause, as the player being simulated may not have been involved in the action, however, the player
would be in the EventContext
under the EventContextKeys#PLAYER_SIMULATED
Retrieving Objects from the Direct Cause
Структурно, объект Cause
содержит последовательный список объектов. Есть несколько способов получения нужной информации из Cause которые мы здесь и обсудим, для более подробной информации смотрите javadocs.
Примечание
Объекты внутри Cause построены так, что первый объект является наиболее важной причиной события, последующие объекты отсортированы в порядке сокращения важности и/или предоставляют контекстную информацию.
Cause#root() возвращает первый объект внутри Cause. Этот объект является самой важной причиной вызова события. Поскольку Cause`не может быть пустым, это гарантирует наличие ``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) подобен методу Cause#first(Class)
за исключением того, что этот метод возвращает последнее значение соответствующего типа(Class).
Продолжая пример сверху, в случае если мы сменим метод Cause#first(Class)
на Cause#last(Class)
первый объект все так-же будет игроком, а вот второй объект уже будет той-самой сущностью что хранится второй в Cause.
Cause#containsType(Class) возвращает логическое значение которое может быть использовано для проверки, содержит ли Cause объект нужного типа.
Cause#all() возвращает все объекты из Cause для более расширенной обработки.
Event Context
Иногда порядка объектов внутри cause недостаточно, чтобы получить правильное представление о том, что объект представляет по отношению к событию. Здесь на помощь приходит EventContext. Контекст события даёт возможность ассоциировать объекты с их уникальными именами в форме EventContextKeys, позволяя легко идентифицировать и запрашивать объекты. Некоторыми примерами использования именованных причин являются Notifier в ChangeBlockEvent.All или Source в 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.
Примечание
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.
Примечание
Объекты Cause неизменны и следовательно не могут быть изменены после создания.
Using the CauseStackManager
Предупреждение
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
.
Если вы создаете событие в главном потоке, используйте CauseStackManager из соотвествующего Engine, получить его можно с помощью Engine#causeStackManager(). CauseStackManager
отслеживает потенциальные cause событий по мере работы игры, позволяя легко получать Cause
объекты. Чтобы получить текущий cause вызовите CauseStackManager#currentCause(). Можно заметить что PluginContainer вашего плагина уже находится в вернувшемся объекте Cause
, так как плагины это объекты отслеживаемые менеджером. Использование CauseStackManager
для создания cause объектов, избавляет от повторяющегося кода, где вам приходется передавать объекты вроде вашего плагин-контейнера, так вы можете сконцетрироваться на добавлении ваших собственных cause объектов.
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.
Совет
Добавление фрейма в CauseStackManager не удаляет то, что уже есть в диспетчере, поэтому все, что находится в стеке cause и контекстах до добавления фрейма стека, будет там впоследствии. Вы можете проверить это, вызвав Sponge.server().causeStackManager().currentCause() до и после пуша фрейма.
Например, если стек cause содержит PluginContainer
и ServerPlayer
при добавлении фрейма, останутся в стеке и станут частью Cause
, если она будет получена из фрейма.
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.
Совет
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.