
Hook events
Using hook states to manage complex integration scenarios
Introduction
A hook event is a state container that is passed through the hook execution workflow and may be used to communicate generic state information to any other subscribed hook methods invoked before or after.
Consider the following call-stack and the corresponding hook implementations..
Interface.CallHook("OnChat", player, message)
└───PluginOne.cs
└───void OnChat(IPlayer player, string message)
└───PluginTwo.cs
└───bool OnChat(IPlayer player, string message)
└───PluginThree.cs
└───bool OnChat(IPlayer player, string message)
Notice the return types vary and assuming these three plugins are not integrated, the hooks will always be called in the order they are subscribed.
Any given OnChat
hook method above ought to have the same level of responsibility as any other. This sharing of responsibility can make cross-plugin integration difficult, especially considering that hooks have return behavior which is designed to modify game behavior and as such extert authority on the outcome of the hook call.
The hook event splits hook behavior into six distincts workflow states (Before
, After
, Completed
, Canceled
, Failed
, and normal execution). After implementing hook events, the resulting call-stack may resemble the following...
Interface.CallHook("OnChat", player, message)
└───PluginOne.cs
└───[Before] void OnChat(IPlayer player, string message, HookEvent hookEvent)
└───PluginTwo.cs
└───bool OnChat(IPlayer player, string message, HookEvent hookEvent)
└───PluginThree.cs
└───[Completed] bool OnChat(IPlayer player, string message, HookEvent hookEvent)
The hook methods above will be invoked in a particular order and allow greater flexibility when integrating plugins.
Hook wrappers
The examples below wrap the OnPlayerChat
but hook wrappers may be applied to any hook. Like the hook attribute, the Before
, After
, Completed
, Canceled
, and Failed
attributes accept a name
parameter to specify the hook name if the method name does not match the hook name.
Before
Annotate a method with the Before
attribute to use it before the normal hook execution workflow.
[Before]
void OnPlayerChat(IPlayer player, string message, HookEvent e)
{
/* ... */
}
After
Annotate a method with the After
attribute to use it when the normal hook execution workflow ends, even if it ends in a canceled or failed state.
[After]
void OnPlayerChat(IPlayer player, string message, HookEvent e)
{
/* ... */
}
Completed
Annotate a method with the Completed
attribute to use it after the normal hook execution workflow is completed. The event state will be completed at the end of the hook execution workflow if the hook was not cancelled and did not fail.
[Completed]
void OnPlayerChat(IPlayer player, string message, HookEvent e)
{
/* ... */
}
Canceled
Annotate a method with the Canceled
attribute to use it when the normal hook execution workflow is canceled. Any hook may indicate that further execution should be canceled and ought to include a reason for the cancellation.
[Canceled]
void OnPlayerChat(IPlayer player, string message, HookEvent e)
{
/* ... */
}
Canceling a hook will not prevent further hook execution but it will allow other hooks to observe cancellation.
Failed
Annotate a method with the Failed
attribute to use it when the normal hook execution workflow encounters an error.
[Failed]
void OnPlayerChat(IPlayer player, string message, HookEvent e)
{
/* ... */
}
Failure will not prevent further hook execution but it will allow other hooks to observe the failure.
Hook state
The HookEvent object contains the current hook state and may be used (in addition to the hook wrappers above) to schedule callbacks to be invoked when a particular hook state is triggered.
Completed
Add a callback to listen to the hook event completion.
void OnPlayerChat(IPlayer player, string message, HookEvent e)
{
// Invoked when the hook execution workflow is completed, similar to the Completed hook wrapper
e.Context.Completed(delegate(Plugin plugin, HookEvent completedEvent)
{
player.Reply(completedEvent.StateReason);
});
}
Canceled
Check for previous cancellation and add a callback to listen for future hook cancellation.
void OnPlayerChat(IPlayer player, string message, HookEvent e)
{
// Check if a previously executed hook set the hook state to Canceled
if (e.Canceled)
{
player.Reply(e.StateReason);
}
// In case another hook post-execution attempts to cancel the hook event
e.Context.Canceled(delegate(Plugin plugin, HookEvent canceledEvent)
{
player.Reply(canceledEvent.StateReason);
});
}
The Cancel
method should provide preceeding and proceeding hooks an explanation of why the hook was canceled but it will not prevent further hook execution.
bool IsMuted(IPlayer player)
{
return player.BelongsToGroup("muted");
}
bool OnPlayerChat(IPlayer player, string message, HookEvent e)
{
if (IsMuted(player))
{
e.Cancel("Player is muted");
return true;
}
}
Failed
A Failed
state is the result of an exception in any hook in the hook execution stack. Check for previous failure and add a callback to listen for future hook failure.
void OnPlayerChat(IPlayer player, string message, HookEvent e)
{
// Check if a previously executed hook failed
if (e.Failed)
{
player.Reply(e.StateReason);
}
// In case another hook post-execution fails
e.Context.Failed(delegate(Plugin plugin, HookEvent failedEvent)
{
player.Reply(failedEvent.StateReason);
});
}