Hook Priority

Hook priority allows for a developer to control when their hook will be called in relation to other hooks on the same function. When done well, hook priority can improve a mod’s compatibility. However, poorly applied priorities can also completely break compatibility with other mods.

⚠️ This is advanced functionality! Most developers will not need to touch this system.

The syntax, as previously mentioned in the Hooking tutorial, looks like this:

class $modify(cocos2d::CCLabelBMFont) {
    static void onModify(auto& self) {
        if (!self.setHookPriorityPost("cocos2d::CCLabelBMFont::init", Priority::First)) {
            geode::log::warn("Failed to set hook priority.");
        }
        if (!self.setHookPriorityPre("cocos2d::CCLabelBMFont::setString", Priority::Late)) {
            geode::log::warn("Failed to set hook priority.");
        }
        if (!self.setHookPriorityAfterPost("cocos2d::CCLabelBMFont::limitLabelWidth", "geode.node-ids")) {
            geode::log::warn("Failed to set hook priority.");
        }
    }
};

A function with no explicit priority is given the priority of Priority::Normal.

There are 7 pre-assigned priority values recommended for use: First, VeryEarly, Early, Normal, Late, VeryLate, Last. Arithmetic on these values are also possible, although not recommended: Priority::Early + 2, meaning 2 internal units later than Early.

There are also 2 prefixes related with priorities: Pre, Post. Pre priorities sort based on code called before the original call, and Post priorities sort based on code called after the original call.

Here’s an example of the pre/post differentiation:

void functionEarlyPre() {
    log::info("statement 1");
    function();
}
void functionLatePre() {
    log::info("statement 3");
    function();
}
void functionNormal() {
    log::info("statement 2");
    function();
    log::info("statement 5");
}
void functionEarlyPost() {
    function();
    log::info("statement 4");
}
void functionLatePost() {
    function();
    log::info("statement 6");
}

Guidelines

Always use the before/after functions instead of doing arithmetic on priorities! Unless circular priorities happen, it is guaranteed for them to work consistently even if the other mods change their priorities.

Using raw numbers as priorities is strongly discouraged for mod compatibility. There are tools for mod compatibilities! Please take advantage of them.

Obviously, these are not strict rules. Feel free to choose the hook priority that best fits your situation.

Notes

This section details some additional tricks to the hook priority system.

Internal Implementation

The initial call to the function starts with the hook with lowest priority (internally INT_MIN). Each call to the original function increases the current priority, with the original function being at the highest priority (internally INT_MAX). As each function in the chain returns, priority is decreased until the function with lowest priority returns. At this point, the function call is finished and execution returns to the original caller.

The 7 pre-assigned priorities have the values of -3000, -2000, -1000, 0, 1000, 2000, 3000 respectively.

An example is shown by this diagram and its accompanying code:

Diagram detailing the flow of execution for a hooked function

// priority of -1'000'000
void fn_lowest() {
    log::info("statement 1");
    fn();
    log::info("statement 6");
}

// priority of -100
void fn_neg100() {
    // code here happens prior to statement 2 and after statement 1
    fn();
    log::info("statement 5");
}

// priority of 0
void fn_default() {
    log::info("statement 2");
    fn();
    log::info("statement 4");
}

// priority of 1'000'000
void fn_highest() {
    log::info("statement 3");
    fn();
    // code here will happen prior to statement 4 and after the original function call
}

The statements will be printed in numerical order, from “statement 1” to “statement 6”.

A developer may also choose to not call the original, which means hooks that are a higher priority than the developer’s hooks (including the original function) will not be executed.

Internal notes