Skip to content

Hook conflicts and the call order of hooks in plugins #103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from

Conversation

IIIaKa
Copy link
Contributor

@IIIaKa IIIaKa commented Feb 3, 2025

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:

  1. Can - In 99.99% of cases, it expects a non-null bool value. However, there are exceptions, such as the CanAcceptItem hook, which expects ItemContainer.CanAcceptResult.
  2. On(object) - These hooks don't care about the value or type of value they get, they only distinguish between null or non-null.
    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.
  3. On(non-object) - These are similar to object On(object) hooks, but for these, the specific type and value of a non-null return matter. There are fewer of these hooks than object ones, and they’re essentially exceptions among On hooks.
    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.

@IIIaKa
Copy link
Contributor Author

IIIaKa commented Feb 3, 2025

In the archive, I have provided the compiled Oxide.Core and two plugins for testing.
OxideCore103.zip

Also, a pull request for changing hook names that do not correspond to their titles and cause confusion.
OxideMod/Oxide.Rust#545

@IIIaKa
Copy link
Contributor Author

IIIaKa commented Feb 4, 2025

I spent quite a bit of time on both of these pull requests, I hope it wasn't in vain.

@xiantez
Copy link

xiantez commented Feb 8, 2025

In the archive, I have provided the compiled Oxide.Core and two plugins for testing. OxideCore103.zip

Also, a pull request for changing hook names that do not correspond to their titles and cause confusion. OxideMod/Oxide.Rust#545

This is great! Thank you for your analysis and work on the matter. I do see conflicts quite regularly on my server. Hopefully something can be done soon to avoid unnecessary console spam while handling conflicts in a more graceful manner.

@MrBlue MrBlue force-pushed the develop branch 2 times, most recently from 5b78677 to a6d7b0c Compare March 30, 2025 00:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants