From 58ac063b0620cf95f598dce86a1feaf9d0f87a58 Mon Sep 17 00:00:00 2001 From: Chris Cummings Date: Fri, 31 Jul 2026 21:21:31 +0100 Subject: [PATCH] Fix derived function reflection hot reload --- src/sgl/refl/function.cpp | 82 ++++++++++++++++++++++++++++-- src/sgl/refl/function.h | 13 +++++ tests/sgl/refl/test_reflection.cpp | 25 +++++++++ 3 files changed, 117 insertions(+), 3 deletions(-) diff --git a/src/sgl/refl/function.cpp b/src/sgl/refl/function.cpp index 98bc7886d..cf13889b5 100644 --- a/src/sgl/refl/function.cpp +++ b/src/sgl/refl/function.cpp @@ -222,8 +222,14 @@ const std::vector>& Function::overloads() const overloads.reserve(reflected_overloads.size()); for (uint32_t i = 0; i < reflected_overloads.size(); ++i) { ref reflection = reflected_overloads[i]; - if (reflection) - overloads.push_back(make_ref(m_layout, std::move(reflection), m_this_type, m_full_name)); + if (reflection) { + ref overload = make_ref(m_layout, std::move(reflection), m_this_type, m_full_name); + std::vector type_names; + for (const ref& parameter : overload->reflection()->parameters()) + type_names.push_back(parameter->type()->full_name()); + add_derived_function(overload, DerivationKind::overload, std::move(type_names)); + overloads.push_back(overload); + } } m_cached_overloads = std::move(overloads); } @@ -238,10 +244,13 @@ bool Function::is_constructor() const ref Function::specialize_with_arg_types(const std::vector>& types) const { std::vector> reflections; + std::vector type_names; reflections.reserve(types.size()); + type_names.reserve(types.size()); for (const ref& type : types) { SGL_CHECK(type, "Cannot specialize function '{}' with null argument type", m_full_name); reflections.emplace_back(ref(const_cast(type->reflection()))); + type_names.push_back(type->full_name()); } ref reflection = m_reflection->specialize_with_arg_types(reflections); @@ -251,7 +260,73 @@ ref Function::specialize_with_arg_types(const std::vector>& // Do not cache this result by name. The input types are concrete call argument // types, not the canonical generic argument list, so deriving a full name here // can pollute Layout's named-function cache with non-canonical spellings. - return make_ref(m_layout, std::move(reflection), m_this_type); + ref function = make_ref(m_layout, std::move(reflection), m_this_type); + add_derived_function(function, DerivationKind::specialization, std::move(type_names)); + return function; +} + +void Function::add_derived_function( + ref function, + DerivationKind kind, + std::vector type_names +) const +{ + m_derived_functions.push_back( + DerivedFunction{ + .function = std::move(function), + .kind = kind, + .type_names = std::move(type_names), + } + ); +} + +void Function::refresh_derived_functions() +{ + sgl::ProgramLayout* low_level_layout = const_cast(m_layout->low_level_layout()); + for (const DerivedFunction& derived : m_derived_functions) { + ref reflection; + switch (derived.kind) { + case DerivationKind::overload: { + auto overloads = m_reflection->overloads(); + reflection = nullptr; + for (const ref& overload : overloads) { + auto parameters = overload->parameters(); + if (parameters.size() != derived.type_names.size()) + continue; + + bool matches = true; + for (uint32_t i = 0; i < parameters.size(); ++i) { + if (parameters[i]->type()->full_name() != derived.type_names[i]) { + matches = false; + break; + } + } + if (matches) { + reflection = overload; + break; + } + } + break; + } + case DerivationKind::specialization: { + std::vector> types; + types.reserve(derived.type_names.size()); + for (const std::string& type_name : derived.type_names) { + ref type = low_level_layout->find_type_by_name(type_name.c_str()); + if (!type) { + types.clear(); + break; + } + types.emplace_back(ref(const_cast(type.get()))); + } + if (types.size() == derived.type_names.size()) + reflection = m_reflection->specialize_with_arg_types(types); + break; + } + } + if (reflection) + derived.function->on_hot_reload(std::move(reflection)); + } } void Function::on_hot_reload(ref reflection) @@ -261,6 +336,7 @@ void Function::on_hot_reload(ref reflection) m_cached_return_type.reset(); m_cached_parameters.reset(); m_cached_overloads.reset(); + refresh_derived_functions(); } std::string Function::to_string() const diff --git a/src/sgl/refl/function.h b/src/sgl/refl/function.h index 670c935ed..6574fd24e 100644 --- a/src/sgl/refl/function.h +++ b/src/sgl/refl/function.h @@ -157,10 +157,23 @@ class SGL_API Function final : public Object { std::string to_string() const override; private: + enum class DerivationKind { overload, specialization }; + + struct DerivedFunction { + ref function; + DerivationKind kind; + std::vector type_names; + }; + + void add_derived_function(ref function, DerivationKind kind, std::vector type_names) const; + void refresh_derived_functions(); + ref m_layout; ref m_reflection; ref m_this_type; std::string m_full_name; + // Direct children are retained so hot reload can refresh the derivation tree recursively. + mutable std::vector m_derived_functions; mutable std::optional> m_cached_return_type; mutable std::optional>> m_cached_parameters; diff --git a/tests/sgl/refl/test_reflection.cpp b/tests/sgl/refl/test_reflection.cpp index bfd7ae209..7e51083f2 100644 --- a/tests/sgl/refl/test_reflection.cpp +++ b/tests/sgl/refl/test_reflection.cpp @@ -256,6 +256,9 @@ struct Foo { float value; }; float get_value(Foo foo) { return foo.value; } +float overloaded(float value) { return value; } +int overloaded(int value) { return value; } +float generic_value(T value) { return 0.0; } )" ); REQUIRE(module_a); @@ -267,6 +270,9 @@ struct Foo { int value; }; int get_value(Foo foo) { return foo.value; } +int overloaded(int value) { return value; } +int overloaded(float value) { return int(value); } +int generic_value(T value) { return 0; } )" ); REQUIRE(module_b); @@ -274,6 +280,22 @@ int get_value(Foo foo) { return foo.value; } ref layout = make_ref(module_a->layout()); ref old_foo_type = layout->require_type_by_name("Foo"); ref old_function = layout->require_function_by_name("get_value"); + ref float_type = layout->scalar_type(TypeReflection::ScalarType::float32); + ref overloaded = layout->require_function_by_name("overloaded"); + ref old_overload; + for (const ref& overload : overloaded->overloads()) { + if (overload->parameters().at(0)->type() == float_type) + old_overload = overload; + } + REQUIRE(old_overload); + CHECK(old_overload->return_type() == float_type); + ref old_nested_specialization = old_overload->specialize_with_arg_types({float_type}); + REQUIRE(old_nested_specialization); + CHECK(old_nested_specialization->return_type() == float_type); + ref generic = layout->require_function_by_name("generic_value"); + ref old_specialization = generic->specialize_with_arg_types({float_type}); + REQUIRE(old_specialization); + CHECK(old_specialization->return_type() == float_type); uint64_t generation = layout->generation(); layout->on_hot_reload(module_b->layout()); @@ -291,6 +313,9 @@ int get_value(Foo foo) { return foo.value; } CHECK(new_function.get() == old_function.get()); CHECK(old_function->return_type() == layout->scalar_type(TypeReflection::ScalarType::int32)); CHECK(new_function->return_type() == layout->scalar_type(TypeReflection::ScalarType::int32)); + CHECK(old_overload->return_type() == layout->scalar_type(TypeReflection::ScalarType::int32)); + CHECK(old_nested_specialization->return_type() == layout->scalar_type(TypeReflection::ScalarType::int32)); + CHECK(old_specialization->return_type() == layout->scalar_type(TypeReflection::ScalarType::int32)); } TEST_SUITE_END();