diff --git a/include/splice/detail/hook/meta_utils.hpp b/include/splice/detail/hook/meta_utils.hpp index 02f645b..2f442e9 100644 --- a/include/splice/detail/hook/meta_utils.hpp +++ b/include/splice/detail/hook/meta_utils.hpp @@ -29,7 +29,10 @@ namespace splice::hook /// `inject_all()` /// /// Apply with `[[= splice::hook::injection{/* ... */}]]` or with the - /// `SPLICE[_PRIO]_INJECT_*` macros on any non-special, static member function. + /// `SPLICE[_PRIO]_INJECT_*` macros on any non-special member function. + /// + /// Non-static injection methods are registered via `inject_all_instanced` + /// whereas static injection methods are registered via `inject_all_static` /// /// @par Example /// @code @@ -182,16 +185,14 @@ namespace splice::detail return !std::meta::annotations_of_with_type(m, ^^splice::hook::injection).empty(); } - /// @brief Returns `true` if @p m is a non-special,static member function - /// annotated with + /// @brief Returns `true` if @p m is a non-special member function annotated with /// `[[= splice::hook::injection{/* ... */}]]`. /// /// Excludes constructors, destructors, and operators. consteval bool is_injection_method(std::meta::info m) { return std::meta::is_function(m) && std::meta::has_identifier(m) && !std::meta::is_constructor(m) - && std::meta::is_static_member(m) && !std::meta::is_destructor(m) && !std::meta::is_operator_function(m) - && has_injection(m); + && !std::meta::is_destructor(m) && !std::meta::is_operator_function(m) && has_injection(m); } /// @brief Returns a `std::array` of reflected methods on @p T annotated with diff --git a/include/splice/detail/hook/registry.hpp b/include/splice/detail/hook/registry.hpp index 2ab0b54..c782a81 100644 --- a/include/splice/detail/hook/registry.hpp +++ b/include/splice/detail/hook/registry.hpp @@ -237,22 +237,58 @@ namespace splice::hook /// @tparam Source The class containing the injections /// @returns `std::expected`. template - [[nodiscard]] std::expected inject_all() + [[nodiscard]] std::expected inject_all_static() { template for (constexpr std::meta::info m: splice::detail::injection_methods()) { - template for (constexpr std::meta::info a_m: [:std::meta::reflect_constant_array( - std::meta::annotations_of_with_type( - m, ^^splice::hook::injection)):]) + if constexpr (std::meta::is_static_member(m)) { - constexpr splice::hook::injection a = std::meta::extract(a_m); - if constexpr (std::meta::parent_of(a.what) == ^^T) // only try to register hooks for the registry's type + template for (constexpr std::meta::info a_m: [:std::meta::reflect_constant_array( + std::meta::annotations_of_with_type( + m, ^^splice::hook::injection)):]) { - using Chain = splice::detail::ChainFor::type; + constexpr splice::hook::injection a = std::meta::extract(a_m); + if constexpr (std::meta::parent_of(a.what) == ^^T) // only try to register hooks for the registry's type + { + using Chain = splice::detail::ChainFor::type; + + auto ret = chain().add(a.where, typename Chain::Hook([:m:]), a.priority); + if (!ret) + return ret; + } + } + } + } + return { }; + } - auto ret = chain().add(a.where, typename Chain::Hook([:m:]), a.priority); - if (!ret) - return ret; + template + [[nodiscard]] std::expected inject_all_instanced(std::shared_ptr ptr) + { + template for (constexpr std::meta::info m: splice::detail::injection_methods()) + { + if constexpr (!std::meta::is_static_member(m)) + { + template for (constexpr std::meta::info a_m: [:std::meta::reflect_constant_array( + std::meta::annotations_of_with_type( + m, ^^splice::hook::injection)):]) + { + constexpr splice::hook::injection a = std::meta::extract(a_m); + if constexpr (std::meta::parent_of(a.what) == ^^T) // only try to register hooks for the registry's type + { + using Chain = splice::detail::ChainFor::type; + constexpr auto fn = unpackFunc>(m); + std::weak_ptr wp = ptr; + auto wrapper = [src = std::move(wp), &fn](Chain::CI &ci, auto &&...args) mutable + { + if (auto ptr = src.lock(); ptr) + (ptr.get()->*fn)(ci, (args)...); + }; + + auto ret = chain().add(a.where, typename Chain::Hook(wrapper), a.priority); + if (!ret) + return ret; + } } } } @@ -328,6 +364,18 @@ namespace splice::hook return chain().add(InjectPoint::Head, typename Chain::Hook(std::move(wrapper)), priority); } + + template + consteval auto _unpackFuncImpl(std::meta::info m, std::index_sequence) + { + return std::meta::extract...)>(m); + } + + template + consteval auto unpackFunc(std::meta::info m) + { + return _unpackFuncImpl(m, std::make_index_sequence::value>()); + } }; } // namespace splice::hook diff --git a/tests/test_registry.cpp b/tests/test_registry.cpp index 1054259..969ed6f 100644 --- a/tests/test_registry.cpp +++ b/tests/test_registry.cpp @@ -246,11 +246,11 @@ class Test1 int Test1::val = 0; -TEST_CASE("Ensure functions actually get injected", "[registry][class_inject]") +TEST_CASE("Ensure functions actually get injected", "[registry][class_inject][static]") { auto reg = make_registry(); - auto result = reg->inject_all(); + auto result = reg->inject_all_static(); REQUIRE(result.has_value()); @@ -288,10 +288,10 @@ class Test2 std::vector Test2::v = std::vector { }; -TEST_CASE("Ensure hooks respect priority", "[registry][class_inject]") +TEST_CASE("Ensure hooks respect priority", "[registry][class_inject][static]") { auto reg = make_registry(); - auto result = reg->inject_all(); + auto result = reg->inject_all_static(); REQUIRE(result.has_value()); @@ -322,10 +322,10 @@ class Test3 std::vector Test3::v = std::vector { }; -TEST_CASE("Hooks without priority are registered in reverse declaration order", "[registry][class_inject]") +TEST_CASE("Hooks without priority are registered in reverse declaration order", "[registry][class_inject][static]") { auto reg = make_registry(); - auto result = reg->inject_all(); + auto result = reg->inject_all_static(); REQUIRE(result.has_value()); @@ -352,14 +352,138 @@ class Test4 } }; -TEST_CASE("Only register hooks for the specified class", "[registry][class_inject]") +TEST_CASE("Only try to register hooks for the specified class", "[registry][class_inject][static]") { auto reg = make_registry(); - auto result = reg->inject_all(); + auto result = reg->inject_all_static(); REQUIRE(result.has_value()); - result = g_obj->inject_all(); + result = g_obj->inject_all_static(); REQUIRE(result.has_value()); } + +class Test5 +{ +public: + int val = 0; + [[= splice::hook::injection { .what = ^^DummyWorld::onStep, .where = splice::hook::InjectPoint::Head }]] void inject( + splice::detail::CallbackInfo &, DummyWorld *, DummyPlayer *, int, int) + { + val = 1; + } +}; + +TEST_CASE("Instanced injections function", "[registry][class_inject][instanced]") +{ + auto reg = make_registry(); + auto i = std::make_shared(); + auto result = reg->inject_all_instanced(i); + + REQUIRE(result.has_value()); + + DummyWorld world; + DummyPlayer player; + reg->dispatch<^^DummyWorld::onStep>(&world, &player, 0, 0); + + REQUIRE(i->val == 1); +} + +class Test6 +{ +public: + static int v; + int val = 0; + [[= splice::hook::injection { .what = ^^DummyWorld::onStep, .where = splice::hook::InjectPoint::Head }]] void inject( + splice::detail::CallbackInfo &, DummyWorld *, DummyPlayer *, int, int) + { + val++; + v++; + } +}; + +int Test6::v = 0; + +TEST_CASE("Instanced injection only affects its instance", "[registry][class_inject][instanced]") +{ + auto reg = make_registry(); + auto i1 = std::make_shared(); + auto i2 = std::make_shared(); + + auto result = reg->inject_all_instanced(i1); + REQUIRE(result.has_value()); + result = reg->inject_all_instanced(i2); + REQUIRE(result.has_value()); + + DummyWorld world; + DummyPlayer player; + reg->dispatch<^^DummyWorld::onStep>(&world, &player, 0, 0); + + REQUIRE(i1->val == 1); + REQUIRE(i2->val == 1); + REQUIRE(Test6::v == 2); +} + +class Test7 +{ +public: + static int v; + [[= splice::hook::injection { .what = ^^DummyWorld::onStep, .where = splice::hook::InjectPoint::Head }]] void inject( + splice::detail::CallbackInfo &, DummyWorld *, DummyPlayer *, int, int) + { + v++; + } +}; + +int Test7::v = 0; + +TEST_CASE("Instanced injections don't run after pointer is discarded", "[registry][class_inject][instanced]") +{ + auto reg = make_registry(); + auto i = std::make_shared(); + auto result = reg->inject_all_instanced(i); + + REQUIRE(result.has_value()); + + DummyWorld world; + DummyPlayer player; + reg->dispatch<^^DummyWorld::onStep>(&world, &player, 0, 0); + + REQUIRE(Test7::v == 1); + + i.reset(); + + reg->dispatch<^^DummyWorld::onStep>(&world, &player, 0, 0); + + REQUIRE(Test7::v == 1); +} + +class Test8 +{ +public: + static int v; + [[= splice::hook::injection { .what = ^^DummyWorld::onStep, + .where = splice::hook::InjectPoint::Head }]][[= splice::hook::injection { .what = ^^DummyWorld::onStep, + .where = splice::hook::InjectPoint::Head }]] static void inject(splice::detail::CallbackInfo &, DummyWorld *, + DummyPlayer *, int, int) + { + v++; + } +}; + +int Test8::v = 0; + +TEST_CASE("Repeat annotations function", "[registry][class_inject]") +{ + auto reg = make_registry(); + auto result = reg->inject_all_static(); + + REQUIRE(result.has_value()); + + DummyWorld world; + DummyPlayer player; + reg->dispatch<^^DummyWorld::onStep>(&world, &player, 0, 0); + + REQUIRE(Test8::v == 2); +}