Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions include/splice/detail/hook/meta_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
68 changes: 58 additions & 10 deletions include/splice/detail/hook/registry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,22 +237,58 @@ namespace splice::hook
/// @tparam Source The class containing the injections
/// @returns `std::expected<void, HookError>`.
template<typename Source>
[[nodiscard]] std::expected<void, HookError> inject_all()
[[nodiscard]] std::expected<void, HookError> inject_all_static()
{
template for (constexpr std::meta::info m: splice::detail::injection_methods<Source>())
{
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<splice::hook::injection>(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<T, a.what>::type;
constexpr splice::hook::injection a = std::meta::extract<splice::hook::injection>(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<T, a.what>::type;

auto ret = chain<a.what>().add(a.where, typename Chain::Hook([:m:]), a.priority);
if (!ret)
return ret;
}
}
}
}
return { };
}

auto ret = chain<a.what>().add(a.where, typename Chain::Hook([:m:]), a.priority);
if (!ret)
return ret;
template<typename Source>
[[nodiscard]] std::expected<void, HookError> inject_all_instanced(std::shared_ptr<Source> ptr)
{
template for (constexpr std::meta::info m: splice::detail::injection_methods<Source>())
{
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<splice::hook::injection>(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<T, a.what>::type;
constexpr auto fn = unpackFunc<typename Chain::RetT, Source, splice::detail::ParamTuple<m>>(m);
std::weak_ptr<Source> 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<a.what>().add(a.where, typename Chain::Hook(wrapper), a.priority);
if (!ret)
return ret;
}
}
}
}
Expand Down Expand Up @@ -328,6 +364,18 @@ namespace splice::hook

return chain<Method>().add(InjectPoint::Head, typename Chain::Hook(std::move(wrapper)), priority);
}

template<typename Ret, typename Source, typename ArgTuple, std::size_t... Idxs>
consteval auto _unpackFuncImpl(std::meta::info m, std::index_sequence<Idxs...>)
{
return std::meta::extract<Ret (Source::*)(std::tuple_element_t<Idxs, ArgTuple>...)>(m);
}

template<typename Ret, typename Source, typename ArgTuple>
consteval auto unpackFunc(std::meta::info m)
{
return _unpackFuncImpl<Ret, Source, ArgTuple>(m, std::make_index_sequence<std::tuple_size<ArgTuple>::value>());
}
};

} // namespace splice::hook
Expand Down
142 changes: 133 additions & 9 deletions tests/test_registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Test1>();
auto result = reg->inject_all_static<Test1>();

REQUIRE(result.has_value());

Expand Down Expand Up @@ -288,10 +288,10 @@ class Test2

std::vector<int> Test2::v = std::vector<int> { };

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<Test2>();
auto result = reg->inject_all_static<Test2>();

REQUIRE(result.has_value());

Expand Down Expand Up @@ -322,10 +322,10 @@ class Test3

std::vector<int> Test3::v = std::vector<int> { };

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<Test3>();
auto result = reg->inject_all_static<Test3>();

REQUIRE(result.has_value());

Expand All @@ -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<Test4>();
auto result = reg->inject_all_static<Test4>();

REQUIRE(result.has_value());

result = g_obj->inject_all<Test4>();
result = g_obj->inject_all_static<Test4>();

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<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);
}

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<Test6>();
auto i2 = std::make_shared<Test6>();

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<Test7>();
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<Test8>();

REQUIRE(result.has_value());

DummyWorld world;
DummyPlayer player;
reg->dispatch<^^DummyWorld::onStep>(&world, &player, 0, 0);

REQUIRE(Test8::v == 2);
}
Loading