Hook conflicts and the call order of hooks in plugins #103
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
The call order of hooks in plugins and improvements in hook conflicts.
In the archive, I have provided the compiled Oxide.Core and two plugins for testing.
1. The call order of hooks in plugins.
Previously, when plugins subscribed to a hook, they were simply added to the end of the hook's plugin list. For most plugins, this wasn't a problem, but there are API plugins(AdvancedStatus, ZoneManager, MonumentsWatcher, ImageLibrary, etc.) where the hook should be called before other plugins.
For example, with my AdvancedStatus and WipeStatus plugins: in the OnPlayerConnected hook, the AdvancedStatus plugin initializes the player so that the necessary status bars can be added. On the other hand, the WipeStatus plugin calls the API method to add a status bar for the player showing the time until the wipe.
But in 99% of cases, WipeStatus is positioned earlier in the queue, meaning that WipeStatus receives the OnPlayerConnected hook call before AdvancedStatus, so by the time the API is called, AdvancedStatus hasn't yet initialized the player.
With the ability to specify the order of subscriptions, the AdvancedStatus plugin(and others like it) can resubscribe with the necessary index, for example Subscribe("OnPlayerConnected", 0);, ensuring that API plugins are the first to receive hook calls.
This change does not affect plugins that don't care about the order in the queue, for them, everything remains as it was.
2. Improvements in hook conflicts.
A delay has been added between hook conflict checks when a conflict occurs.
This was implemented for a simple reason: a hook conflict won’t resolve instantly and constantly checking for conflicts between hooks that are known to conflict adds unnecessary operations and causes console spam.
This is especially noticeable in hooks like CanBeTargeted and similar hooks.
Now, regarding hook conflicts between plugins(the changes only affect standard hooks, On and Can)
In the proposed version. There can be multiple expected types. For all Can hooks, a bool type is automatically added as the expected type. For On hooks, no expected type is set, allowing any return type without causing a conflict.
For existing On(non-object) hooks, default expected types have been added. However, it is also possible to add custom expected types for hooks.
With this implementation, a hook conflict occurs only when there are differing expected type values.
Informally, there are 3 types of hooks, all of which expect null or non-null values:
Conflicts most often occur with these hooks. In the current version, it is not taken into account that these hooks do not care about the type or the non-null value returned to them.
Since the type doesn't matter for non-null values, different developers may return true or false, and this causes server console spam when conflicting plugins are installed. For many plugins, there’s no real difference between returning false or true for non-null values, but in my plugins, there are cases where the returned value is used elsewhere, and returning true as negation confuses me. Therefore, for object hooks, I prefer returning false for non-null values.
In essence, for these hooks, returning non-null values of any other type is treated the same as if they had returned null. In the current version, if plugin ONE returns any non-null value and plugin TWO returns a non-null expected type, a conflict message will appear, and the last value requested by the hook will be returned(this can be either an expected type or a non-expected type), even though plugin TWO’s value is the expected and correct one.
In the proposed version, the last non-null value will be returned, or the last non-null value of the correct type, without causing a conflict, unless different expected types are present.