Skip to content

Commit

Permalink
add custom creator support for ReusableManager (#35)
Browse files Browse the repository at this point in the history
* add custom creator support for ReusableManager

* add BABYLON_REGISTER_FACTORY_COMPONENT macro for ApplicationContext
  • Loading branch information
oathdruid authored Jun 11, 2024
1 parent e852451 commit b2b92de
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 49 deletions.
2 changes: 2 additions & 0 deletions src/babylon/any.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include BABYLON_EXTERNAL(absl/base/optimization.h) // ABSL_PREDICT_FALSE
// clang-format on

#include <cassert> // ::assert

BABYLON_NAMESPACE_BEGIN

////////////////////////////////////////////////////////////////////////////////
Expand Down
37 changes: 23 additions & 14 deletions src/babylon/application_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ class ApplicationContext {
class FactoryComponentHolder;
// 内部使用,永远返回失败
class EmptyComponentHolder;
template <typename T, typename... BS>
class DefaultComponentRegister;

template <typename T>
class ComponentRegister;

// 组件的使用侧接口,提供工厂和单例两种使用模式
template <typename T>
Expand Down Expand Up @@ -327,10 +328,10 @@ class ApplicationContext::EmptyComponentHolder
virtual Any create_instance() noexcept override;
};

template <typename T, typename... BS>
class ApplicationContext::DefaultComponentRegister {
template <typename T>
class ApplicationContext::ComponentRegister {
public:
DefaultComponentRegister(StringView name) noexcept;
ComponentRegister(StringView name) noexcept;
};

template <typename T>
Expand Down Expand Up @@ -463,14 +464,12 @@ ApplicationContext::FactoryComponentHolder<T, BS...>::create() noexcept {
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// ApplicationContext::DefaultComponentRegister begin
template <typename T, typename... BS>
ApplicationContext::DefaultComponentRegister<
T, BS...>::DefaultComponentRegister(StringView name) noexcept {
ApplicationContext::instance().register_component(
DefaultComponentHolder<T, BS...>::create(), name);
// ApplicationContext::ComponentRegister begin
template <typename T>
ApplicationContext::ComponentRegister<T>::ComponentRegister(StringView name) noexcept {
ApplicationContext::instance().register_component(T::create(), name);
}
// ApplicationContext::DefaultComponentRegister end
// ApplicationContext::ComponentRegister end
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -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<T, ##__VA_ARGS__>> \
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<T, ##__VA_ARGS__>> \
BOOST_PP_CAT(Babylon_Application_Context_Register, __COUNTER__) {name};
15 changes: 15 additions & 0 deletions src/babylon/reusable/manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ class ReusableManager {
template <typename T, typename... Args>
ReusableAccessor<T> create_object(Args&&... args) noexcept;

// 特殊情况下,实例的首次创建可能无法通过uses-allocator
// construction协议自动完成 支持传入自定义函数来实现
//
// 例如通过反射机制使用基类统一管理Protobuf Message的情况
// 由于T都是基类Message,需要使用反射接口才能创建实际的子类
// 但是子类创建完成后,就可以将反射信息记录到ReusableTraits机制的AllocationMetadata中
// 后续的重建就可以依照协议完成了
template <typename T, typename C,
typename = typename ::std::enable_if<::std::is_same<
T*, typename InvokeResult<C, R&>::type>::value>::type>
ReusableAccessor<T> create_object(C&& creator) noexcept;

// 对所有create_object创建的实例统计执行逻辑清空
// 并周期性进行重建,以便保持内存尽量复用且持续地连续
//
Expand Down Expand Up @@ -95,6 +107,9 @@ class ReusableManager {
Reuse::AllocationMetadata<T> _meta;
};

template <typename T>
ReusableAccessor<T> register_object(T* instance) noexcept;

R _resource;

::std::mutex _mutex;
Expand Down
37 changes: 25 additions & 12 deletions src/babylon/reusable/manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,17 @@ void ReusableManager<R>::set_recreate_interval(size_t interval) noexcept {
template <typename R>
template <typename T, typename... Args>
ReusableAccessor<T> ReusableManager<R>::create_object(Args&&... args) noexcept {
MonotonicAllocator<T, R> allocator {_resource};
auto instance =
allocator.template create_object<T>(::std::forward<Args>(args)...);
auto unit = ::std::unique_ptr<TypedReusableUnit<T>>(
new TypedReusableUnit<T> {instance});
auto accessor = unit->accessor();
{
// 不在关键路径,简单用锁同步
::std::lock_guard<::std::mutex> lock(_mutex);
_units.emplace_back(::std::move(unit));
}
return accessor;
return create_object<T>([&](R& resource) {
MonotonicAllocator<T, R> allocator {resource};
return allocator.create(::std::forward<Args>(args)...);
});
}

template <typename R>
template <typename T, typename C, typename>
ReusableAccessor<T> ReusableManager<R>::create_object(C&& creator) noexcept {
auto instance = creator(_resource);
return register_object(instance);
}

template <typename R>
Expand All @@ -114,6 +113,20 @@ void ReusableManager<R>::clear() noexcept {
}
}
}

template <typename R>
template <typename T>
ReusableAccessor<T> ReusableManager<R>::register_object(T* instance) noexcept {
auto unit = ::std::unique_ptr<TypedReusableUnit<T>>(
new TypedReusableUnit<T> {instance});
auto accessor = unit->accessor();
{
// 不在关键路径,简单用锁同步
::std::lock_guard<::std::mutex> lock(_mutex);
_units.emplace_back(::std::move(unit));
}
return accessor;
}
// ReusableManager end
////////////////////////////////////////////////////////////////////////////////

Expand Down
40 changes: 40 additions & 0 deletions src/babylon/reusable/message.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,46 @@ constexpr bool ReusableTraits<
T>::value>::type>::REUSABLE;
#endif // __cplusplus < 201703L

template <>
class ReusableTraits<::google::protobuf::Message>
: public BasicReusableTraits<ReusableTraits<::google::protobuf::Message>> {
private:
using Arena = ::google::protobuf::Arena;
using Message = ::google::protobuf::Message;
using Base = BasicReusableTraits<ReusableTraits<::google::protobuf::Message>>;

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
Expand Down
45 changes: 25 additions & 20 deletions test/reusable/test_message.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<ArenaExample>();
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<Message>(
[](::babylon::SwissMemoryResource& resource) {
Arena& arena = resource;
Message* result = Arena::CreateMessage<ArenaExample>(&arena);
return result;
});
auto pmessage = static_cast<ArenaExample*>(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<ArenaExample*>(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
13 changes: 10 additions & 3 deletions test/test_application_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<DefaultComponentHolder<::std::string>> {}));
ASSERT_NE(0,
context.register_component(
::std::unique_ptr<DefaultComponentHolder<::std::string>> {}));
}

TEST_F(ApplicationContextTest, use_register_helper_to_register_component) {
Expand All @@ -427,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<int>, "name1")
BABYLON_REGISTER_COMPONENT(::std::vector<int>, "name2")
BABYLON_REGISTER_COMPONENT(S, "", F, M)
BABYLON_REGISTER_FACTORY_COMPONENT(::std::vector<int>, "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<int>>());
ASSERT_NE(nullptr, context.get_or_create<::std::vector<int>>("name1"));
ASSERT_NE(nullptr, context.get_or_create<::std::vector<int>>("name2"));
ASSERT_EQ(nullptr, context.get_or_create<::std::vector<int>>("name3"));
ASSERT_EQ(nullptr, context.get_or_create<::std::vector<int>>("name4"));
ASSERT_NE(nullptr, context.get_or_create<S>());
ASSERT_NE(nullptr, context.get_or_create<F>());
ASSERT_NE(nullptr, context.get_or_create<M>());
ASSERT_EQ(nullptr, context.get_or_create<X>());
ASSERT_NE(nullptr, context.get_or_create<X>());
}

0 comments on commit b2b92de

Please sign in to comment.