From 04e234981e849badd3dd917fb8755ecd06c39ff0 Mon Sep 17 00:00:00 2001 From: Septicake Date: Thu, 19 Mar 2026 00:46:29 -0400 Subject: [PATCH 1/6] Seemingly functional but untested implementation of instanced injections --- include/splice/detail/hook/meta_utils.hpp | 11 ++-- include/splice/detail/hook/registry.hpp | 67 +++++++++++++++++++---- tests/test_registry.cpp | 46 +++++++++++++--- 3 files changed, 100 insertions(+), 24 deletions(-) diff --git a/include/splice/detail/hook/meta_utils.hpp b/include/splice/detail/hook/meta_utils.hpp index 02f645b..0d24d79 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` /// /// @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..7d91cc4 100644 --- a/include/splice/detail/hook/registry.hpp +++ b/include/splice/detail/hook/registry.hpp @@ -237,22 +237,57 @@ 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(Source *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); + auto wrapper = [src = std::forward(ptr), &fn](Chain::CI &ci, auto &&...args) mutable + { + if (src != nullptr) + (src->*fn)(ci, (args)...); + }; + + auto ret = chain().add(a.where, typename Chain::Hook(wrapper), a.priority); + if (!ret) + return ret; + } } } } @@ -328,6 +363,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..48bfefd 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,42 @@ 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(); + Test5 *i = new Test5(); + 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); + + delete i; +} From 01a5e4ac6acb8d7f0cc2308d3409f7b707259f57 Mon Sep 17 00:00:00 2001 From: Septicake Date: Thu, 19 Mar 2026 14:22:42 -0400 Subject: [PATCH 2/6] Even more fully functional version, might still write more tests --- include/splice/detail/hook/registry.hpp | 9 ++-- tests/test_registry.cpp | 72 ++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/include/splice/detail/hook/registry.hpp b/include/splice/detail/hook/registry.hpp index 7d91cc4..d3b1521 100644 --- a/include/splice/detail/hook/registry.hpp +++ b/include/splice/detail/hook/registry.hpp @@ -263,7 +263,7 @@ namespace splice::hook } template - [[nodiscard]] std::expected inject_all_instanced(Source *ptr) + [[nodiscard]] std::expected inject_all_instanced(std::shared_ptr ptr) { template for (constexpr std::meta::info m: splice::detail::injection_methods()) { @@ -278,10 +278,11 @@ namespace splice::hook { using Chain = splice::detail::ChainFor::type; constexpr auto fn = unpackFunc>(m); - auto wrapper = [src = std::forward(ptr), &fn](Chain::CI &ci, auto &&...args) mutable + std::weak_ptr wp = ptr; + auto wrapper = [src = wp, &fn](Chain::CI &ci, auto &&...args) mutable { - if (src != nullptr) - (src->*fn)(ci, (args)...); + if (!src.expired()) + (src.lock().get()->*fn)(ci, (args)...); }; auto ret = chain().add(a.where, typename Chain::Hook(wrapper), a.priority); diff --git a/tests/test_registry.cpp b/tests/test_registry.cpp index 48bfefd..ecfd496 100644 --- a/tests/test_registry.cpp +++ b/tests/test_registry.cpp @@ -1,5 +1,6 @@ #include #include +#include #include struct DummyPlayer @@ -378,7 +379,7 @@ class Test5 TEST_CASE("Instanced injections function", "[registry][class_inject][instanced]") { auto reg = make_registry(); - Test5 *i = new Test5(); + auto i = std::make_shared(); auto result = reg->inject_all_instanced(i); REQUIRE(result.has_value()); @@ -388,6 +389,73 @@ TEST_CASE("Instanced injections function", "[registry][class_inject][instanced]" 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); - delete i; + REQUIRE(Test7::v == 1); } From 6e399fd2c8517bbdfc1d1ca177c5dd98d68f7467 Mon Sep 17 00:00:00 2001 From: Septicake Date: Thu, 19 Mar 2026 15:38:45 -0400 Subject: [PATCH 3/6] Final test works --- tests/test_registry.cpp | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_registry.cpp b/tests/test_registry.cpp index ecfd496..61ce7b8 100644 --- a/tests/test_registry.cpp +++ b/tests/test_registry.cpp @@ -459,3 +459,32 @@ TEST_CASE("Instanced injections don't run after pointer is discarded", "[registr 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); +} From 4fabbc3c88b5807b2692c5bbfe8c4df9540ff31a Mon Sep 17 00:00:00 2001 From: Septicake Date: Thu, 19 Mar 2026 15:48:51 -0400 Subject: [PATCH 4/6] Remove unnecessary header --- tests/test_registry.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_registry.cpp b/tests/test_registry.cpp index 61ce7b8..969ed6f 100644 --- a/tests/test_registry.cpp +++ b/tests/test_registry.cpp @@ -1,6 +1,5 @@ #include #include -#include #include struct DummyPlayer From eae0dbe059b17fcce29354b4894fe90506040b56 Mon Sep 17 00:00:00 2001 From: Septicake Date: Thu, 19 Mar 2026 22:21:33 -0400 Subject: [PATCH 5/6] Update docs I forgot about --- include/splice/detail/hook/meta_utils.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/splice/detail/hook/meta_utils.hpp b/include/splice/detail/hook/meta_utils.hpp index 0d24d79..2f442e9 100644 --- a/include/splice/detail/hook/meta_utils.hpp +++ b/include/splice/detail/hook/meta_utils.hpp @@ -32,7 +32,7 @@ namespace splice::hook /// `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` + /// whereas static injection methods are registered via `inject_all_static` /// /// @par Example /// @code From ed5cac0f2dd960cf3bc668fb9d64794b10338b21 Mon Sep 17 00:00:00 2001 From: Septicake Date: Fri, 20 Mar 2026 12:08:38 -0400 Subject: [PATCH 6/6] Follow suggestions made by smarter people --- include/splice/detail/hook/registry.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/splice/detail/hook/registry.hpp b/include/splice/detail/hook/registry.hpp index d3b1521..c782a81 100644 --- a/include/splice/detail/hook/registry.hpp +++ b/include/splice/detail/hook/registry.hpp @@ -279,10 +279,10 @@ namespace splice::hook using Chain = splice::detail::ChainFor::type; constexpr auto fn = unpackFunc>(m); std::weak_ptr wp = ptr; - auto wrapper = [src = wp, &fn](Chain::CI &ci, auto &&...args) mutable + auto wrapper = [src = std::move(wp), &fn](Chain::CI &ci, auto &&...args) mutable { - if (!src.expired()) - (src.lock().get()->*fn)(ci, (args)...); + if (auto ptr = src.lock(); ptr) + (ptr.get()->*fn)(ci, (args)...); }; auto ret = chain().add(a.where, typename Chain::Hook(wrapper), a.priority); @@ -366,7 +366,7 @@ namespace splice::hook } template - consteval auto __unpackFuncImpl(std::meta::info m, std::index_sequence) + consteval auto _unpackFuncImpl(std::meta::info m, std::index_sequence) { return std::meta::extract...)>(m); } @@ -374,7 +374,7 @@ namespace splice::hook template consteval auto unpackFunc(std::meta::info m) { - return __unpackFuncImpl(m, std::make_index_sequence::value>()); + return _unpackFuncImpl(m, std::make_index_sequence::value>()); } };