From 38dcc55a9fe95762ee37a102e0b9c24938c9b39e Mon Sep 17 00:00:00 2001 From: Kevin Li Date: Tue, 11 Jun 2024 15:31:13 +0800 Subject: [PATCH 1/2] add custom creator support for ReusableManager --- src/babylon/any.hpp | 2 ++ src/babylon/reusable/manager.h | 15 +++++++++++ src/babylon/reusable/manager.hpp | 37 ++++++++++++++++--------- src/babylon/reusable/message.h | 40 +++++++++++++++++++++++++++ test/reusable/test_message.cpp | 45 +++++++++++++++++-------------- test/test_application_context.cpp | 4 ++- 6 files changed, 110 insertions(+), 33 deletions(-) diff --git a/src/babylon/any.hpp b/src/babylon/any.hpp index 0a7aa04e..7e067ce7 100644 --- a/src/babylon/any.hpp +++ b/src/babylon/any.hpp @@ -6,6 +6,8 @@ #include BABYLON_EXTERNAL(absl/base/optimization.h) // ABSL_PREDICT_FALSE // clang-format on +#include // ::assert + BABYLON_NAMESPACE_BEGIN //////////////////////////////////////////////////////////////////////////////// diff --git a/src/babylon/reusable/manager.h b/src/babylon/reusable/manager.h index f1fe23c4..9e1bc1ed 100644 --- a/src/babylon/reusable/manager.h +++ b/src/babylon/reusable/manager.h @@ -61,6 +61,18 @@ class ReusableManager { template ReusableAccessor create_object(Args&&... args) noexcept; + // 特殊情况下,实例的首次创建可能无法通过uses-allocator + // construction协议自动完成 支持传入自定义函数来实现 + // + // 例如通过反射机制使用基类统一管理Protobuf Message的情况 + // 由于T都是基类Message,需要使用反射接口才能创建实际的子类 + // 但是子类创建完成后,就可以将反射信息记录到ReusableTraits机制的AllocationMetadata中 + // 后续的重建就可以依照协议完成了 + template ::type>::value>::type> + ReusableAccessor create_object(C&& creator) noexcept; + // 对所有create_object创建的实例统计执行逻辑清空 // 并周期性进行重建,以便保持内存尽量复用且持续地连续 // @@ -95,6 +107,9 @@ class ReusableManager { Reuse::AllocationMetadata _meta; }; + template + ReusableAccessor register_object(T* instance) noexcept; + R _resource; ::std::mutex _mutex; diff --git a/src/babylon/reusable/manager.hpp b/src/babylon/reusable/manager.hpp index ae4196e4..0ff13a4e 100644 --- a/src/babylon/reusable/manager.hpp +++ b/src/babylon/reusable/manager.hpp @@ -83,18 +83,17 @@ void ReusableManager::set_recreate_interval(size_t interval) noexcept { template template ReusableAccessor ReusableManager::create_object(Args&&... args) noexcept { - MonotonicAllocator allocator {_resource}; - auto instance = - allocator.template create_object(::std::forward(args)...); - auto unit = ::std::unique_ptr>( - new TypedReusableUnit {instance}); - auto accessor = unit->accessor(); - { - // 不在关键路径,简单用锁同步 - ::std::lock_guard<::std::mutex> lock(_mutex); - _units.emplace_back(::std::move(unit)); - } - return accessor; + return create_object([&](R& resource) { + MonotonicAllocator allocator {resource}; + return allocator.create(::std::forward(args)...); + }); +} + +template +template +ReusableAccessor ReusableManager::create_object(C&& creator) noexcept { + auto instance = creator(_resource); + return register_object(instance); } template @@ -114,6 +113,20 @@ void ReusableManager::clear() noexcept { } } } + +template +template +ReusableAccessor ReusableManager::register_object(T* instance) noexcept { + auto unit = ::std::unique_ptr>( + new TypedReusableUnit {instance}); + auto accessor = unit->accessor(); + { + // 不在关键路径,简单用锁同步 + ::std::lock_guard<::std::mutex> lock(_mutex); + _units.emplace_back(::std::move(unit)); + } + return accessor; +} // ReusableManager end //////////////////////////////////////////////////////////////////////////////// diff --git a/src/babylon/reusable/message.h b/src/babylon/reusable/message.h index 35663e10..a64584b5 100644 --- a/src/babylon/reusable/message.h +++ b/src/babylon/reusable/message.h @@ -137,6 +137,46 @@ constexpr bool ReusableTraits< T>::value>::type>::REUSABLE; #endif // __cplusplus < 201703L +template <> +class ReusableTraits<::google::protobuf::Message> + : public BasicReusableTraits> { + private: + using Arena = ::google::protobuf::Arena; + using Message = ::google::protobuf::Message; + using Base = BasicReusableTraits>; + + public: + struct AllocationMetadata { + MessageAllocationMetadata metadata; + const Message* default_instance {nullptr}; + }; + + static constexpr bool REUSABLE = true; + + static void update_allocation_metadata( + const Message& message, AllocationMetadata& metadata) noexcept { + if (metadata.default_instance == nullptr) { + auto descriptor = message.GetDescriptor(); + auto reflection = message.GetReflection(); + metadata.default_instance = + reflection->GetMessageFactory()->GetPrototype(descriptor); + } + metadata.metadata.update(message); + } + + static Message* create_with_allocation_metadata( + SwissAllocator<> allocator, const AllocationMetadata& metadata) { + auto instance = + metadata.default_instance->New(&(Arena&)*allocator.resource()); + metadata.metadata.reserve(*instance); + return instance; + } + + inline static void reconstruct_instance(Message& instance, SwissAllocator<>) { + instance.Clear(); + } +}; + BABYLON_NAMESPACE_END #endif // GOOGLE_PROTOBUF_VERSION >= 3000000 diff --git a/test/reusable/test_message.cpp b/test/reusable/test_message.cpp index 6b71f790..b277ee66 100644 --- a/test/reusable/test_message.cpp +++ b/test/reusable/test_message.cpp @@ -35,6 +35,9 @@ struct MockPageAllocator : public ::babylon::NewDeletePageAllocator { // 相当于一个特殊尺寸的NewDeletePageAllocator struct ReusableMessage : public ::testing::Test { + using Arena = ::google::protobuf::Arena; + using Message = ::google::protobuf::Message; + void SetUp() override { allocator.set_page_size(256); manager.resource().set_page_allocator(allocator); @@ -173,26 +176,28 @@ TEST_F(ReusableMessage, recreate_string_on_arena) { manager.resource().contains(message->mutable_m()->mutable_s()->data())); } -/* -TEST_F(ReusableMessage, arena_string_support_resize_uninitialized) { - auto message = manager.create_object(); - message->set_s(long_string); - message->set_s("10086"); - STLStringResizeUninitialized(&*message->mutable_s(), 4); - auto* data = message->mutable_s()->data(); - ASSERT_EQ(4, message->s().size()); - ASSERT_EQ("1008", message->s()); - ASSERT_EQ(data, message->s().data()); - STLStringResizeUninitialized(&*message->mutable_s(), 2); - data = message->mutable_s()->data(); - ASSERT_EQ(2, message->s().size()); - ASSERT_EQ("10", message->s()); - ASSERT_EQ(data, message->s().data()); - STLStringResizeUninitialized(&*message->mutable_s(), 4); - data = message->mutable_s()->data(); - ASSERT_EQ(4, message->s().size()); - ASSERT_EQ(::std::string_view("10\08", 4), message->s()); +TEST_F(ReusableMessage, + usable_with_base_protobuf_message_type_when_reflection) { + auto message = manager.create_object( + [](::babylon::SwissMemoryResource& resource) { + Arena& arena = resource; + Message* result = Arena::CreateMessage(&arena); + return result; + }); + auto pmessage = static_cast(message.get()); + ASSERT_EQ(default_string_capacity, pmessage->s().capacity()); + ASSERT_EQ("10086", pmessage->ds()); + pmessage->set_s(long_string); + pmessage->set_ds(long_string); + manager.clear(); + auto pmessage2 = static_cast(message.get()); + ASSERT_NE(pmessage, pmessage2); + ASSERT_FALSE(pmessage2->has_s()); + ASSERT_TRUE(pmessage2->s().empty()); + ASSERT_LE(long_string.size(), pmessage2->s().capacity()); + ASSERT_FALSE(pmessage2->has_ds()); + ASSERT_EQ("10086", pmessage2->ds()); + ASSERT_LE(long_string.size(), pmessage2->ds().capacity()); } -*/ #endif // BABYLON_USE_PROTOBUF diff --git a/test/test_application_context.cpp b/test/test_application_context.cpp index 193b7c39..fffa506d 100644 --- a/test/test_application_context.cpp +++ b/test/test_application_context.cpp @@ -411,7 +411,9 @@ TEST_F(ApplicationContextTest, default_constructed_component_accessor_empty) { } TEST_F(ApplicationContextTest, register_empty_component_failed) { - ASSERT_NE(0, context.register_component(::std::unique_ptr> {})); + ASSERT_NE(0, + context.register_component( + ::std::unique_ptr> {})); } TEST_F(ApplicationContextTest, use_register_helper_to_register_component) { From 3672c56e92c9e63f1d7534bfbbc150e7089b179f Mon Sep 17 00:00:00 2001 From: Kevin Li Date: Tue, 11 Jun 2024 16:22:42 +0800 Subject: [PATCH 2/2] add BABYLON_REGISTER_FACTORY_COMPONENT macro for ApplicationContext --- src/babylon/application_context.h | 37 +++++++++++++++++++------------ test/test_application_context.cpp | 9 ++++++-- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/babylon/application_context.h b/src/babylon/application_context.h index 21e8ad1a..017e09db 100644 --- a/src/babylon/application_context.h +++ b/src/babylon/application_context.h @@ -48,8 +48,9 @@ class ApplicationContext { class FactoryComponentHolder; // 内部使用,永远返回失败 class EmptyComponentHolder; - template - class DefaultComponentRegister; + + template + class ComponentRegister; // 组件的使用侧接口,提供工厂和单例两种使用模式 template @@ -327,10 +328,10 @@ class ApplicationContext::EmptyComponentHolder virtual Any create_instance() noexcept override; }; -template -class ApplicationContext::DefaultComponentRegister { +template +class ApplicationContext::ComponentRegister { public: - DefaultComponentRegister(StringView name) noexcept; + ComponentRegister(StringView name) noexcept; }; template @@ -463,14 +464,12 @@ ApplicationContext::FactoryComponentHolder::create() noexcept { //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// -// ApplicationContext::DefaultComponentRegister begin -template -ApplicationContext::DefaultComponentRegister< - T, BS...>::DefaultComponentRegister(StringView name) noexcept { - ApplicationContext::instance().register_component( - DefaultComponentHolder::create(), name); +// ApplicationContext::ComponentRegister begin +template +ApplicationContext::ComponentRegister::ComponentRegister(StringView name) noexcept { + ApplicationContext::instance().register_component(T::create(), name); } -// ApplicationContext::DefaultComponentRegister end +// ApplicationContext::ComponentRegister end //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// @@ -872,6 +871,16 @@ BABYLON_NAMESPACE_END BOOST_PP_TUPLE_PUSH_BACK(args, ""), args) #define __BABYLON_REGISTER_COMPONENT(T, name, ...) \ - static ::babylon::ApplicationContext::DefaultComponentRegister< \ - T, ##__VA_ARGS__> \ + static ::babylon::ApplicationContext::ComponentRegister< \ + ::babylon::ApplicationContext::DefaultComponentHolder> \ + BOOST_PP_CAT(Babylon_Application_Context_Register, __COUNTER__) {name}; + +#define BABYLON_REGISTER_FACTORY_COMPONENT(T, ...) \ + BOOST_PP_EXPAND( \ + __BABYLON_REGISTER_FACTORY_COMPONENT __BABYLON_REGISTER_COMPONENT_FILL_ARGS( \ + (T, ##__VA_ARGS__))) + +#define __BABYLON_REGISTER_FACTORY_COMPONENT(T, name, ...) \ + static ::babylon::ApplicationContext::ComponentRegister< \ + ::babylon::ApplicationContext::FactoryComponentHolder> \ BOOST_PP_CAT(Babylon_Application_Context_Register, __COUNTER__) {name}; diff --git a/test/test_application_context.cpp b/test/test_application_context.cpp index fffa506d..a213fe54 100644 --- a/test/test_application_context.cpp +++ b/test/test_application_context.cpp @@ -429,19 +429,24 @@ TEST_F(ApplicationContextTest, use_register_helper_to_register_component) { struct S : public F, public M, public X { int vs = 4; }; + struct SS : public F, public M, public X { + int vs = 5; + }; BABYLON_REGISTER_COMPONENT(::std::string) BABYLON_REGISTER_COMPONENT(::std::vector, "name1") BABYLON_REGISTER_COMPONENT(::std::vector, "name2") BABYLON_REGISTER_COMPONENT(S, "", F, M) + BABYLON_REGISTER_FACTORY_COMPONENT(::std::vector, "name3") + BABYLON_REGISTER_FACTORY_COMPONENT(SS, "", X) ApplicationContext& context = ApplicationContext::instance(); ASSERT_NE(nullptr, context.get_or_create<::std::string>()); ASSERT_EQ(nullptr, context.get_or_create<::std::vector>()); ASSERT_NE(nullptr, context.get_or_create<::std::vector>("name1")); ASSERT_NE(nullptr, context.get_or_create<::std::vector>("name2")); - ASSERT_EQ(nullptr, context.get_or_create<::std::vector>("name3")); + ASSERT_EQ(nullptr, context.get_or_create<::std::vector>("name4")); ASSERT_NE(nullptr, context.get_or_create()); ASSERT_NE(nullptr, context.get_or_create()); ASSERT_NE(nullptr, context.get_or_create()); - ASSERT_EQ(nullptr, context.get_or_create()); + ASSERT_NE(nullptr, context.get_or_create()); }