Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge ImmediateClosure and DelayedClosure to Closure #2363

Open
yanminhui opened this issue Mar 22, 2023 · 4 comments
Open

Merge ImmediateClosure and DelayedClosure to Closure #2363

yanminhui opened this issue Mar 22, 2023 · 4 comments

Comments

@yanminhui
Copy link

yanminhui commented Mar 22, 2023

namespace td {
template <class ActorT, class FunctionT, class... ArgsT>
class DelayedClosure;
template <class ActorT, class FunctionT, class... ArgsT>
class ImmediateClosure {
public:
using Delayed = DelayedClosure<ActorT, FunctionT, ArgsT...>;
friend Delayed;
using ActorType = ActorT;
// no &&. just save references as references.
explicit ImmediateClosure(FunctionT func, ArgsT... args) : args(func, std::forward<ArgsT>(args)...) {
}
private:
std::tuple<FunctionT, ArgsT...> args;
public:
auto run(ActorT *actor) -> decltype(mem_call_tuple(actor, std::move(args))) {
return mem_call_tuple(actor, std::move(args));
}
};
template <class ActorT, class ResultT, class... DestArgsT, class... SrcArgsT>
ImmediateClosure<ActorT, ResultT (ActorT::*)(DestArgsT...), SrcArgsT &&...> create_immediate_closure(
ResultT (ActorT::*func)(DestArgsT...), SrcArgsT &&...args) {
return ImmediateClosure<ActorT, ResultT (ActorT::*)(DestArgsT...), SrcArgsT &&...>(func,
std::forward<SrcArgsT>(args)...);
}
template <class ActorT, class FunctionT, class... ArgsT>
class DelayedClosure {
public:
using ActorType = ActorT;
explicit DelayedClosure(ImmediateClosure<ActorT, FunctionT, ArgsT...> &&other) : args(std::move(other.args)) {
}
explicit DelayedClosure(FunctionT func, ArgsT... args) : args(func, std::forward<ArgsT>(args)...) {
}
template <class F>
void for_each(const F &f) {
tuple_for_each(args, f);
}
private:
std::tuple<FunctionT, typename std::decay<ArgsT>::type...> args;
public:
auto run(ActorT *actor) -> decltype(mem_call_tuple(actor, std::move(args))) {
return mem_call_tuple(actor, std::move(args));
}
};
template <class ActorT, class ResultT, class... DestArgsT, class... SrcArgsT>
auto create_delayed_closure(ResultT (ActorT::*func)(DestArgsT...), SrcArgsT &&...args) {
return DelayedClosure<ActorT, ResultT (ActorT::*)(DestArgsT...), SrcArgsT &&...>(func,
std::forward<SrcArgsT>(args)...);
}

It seems ImmediateClosure and DelayedClosure can merge to one class, for example (see Insights):

#include <tuple>
#include <type_traits>

template <class T>
struct MemberPointerClass {};

template<class T, class U>
struct MemberPointerClass<T U::*> {
    using type = U;
};

template <class F, class... Args>
class Closure {
    template <class FT, class... ArgsT>
    friend auto MakeImmediateClosure(FT f, ArgsT&&... args);

    template <class FT, class... ArgsT>
    friend auto MakeDelayedClosure(FT f, ArgsT... args);

    explicit Closure(F f, Args... args)
    : f_{f}, args_{std::forward<Args>(args)...}
    {}

public:
    template <class Object>
    [[maybe_unused]] auto run([[maybe_unused]] Object* obj) const {
        if constexpr (std::is_member_function_pointer_v<F>) {
            using C = std::conditional_t<std::is_const<Object>::value,
                                        std::add_const_t<typename MemberPointerClass<F>::type>,
                                        typename MemberPointerClass<F>::type>;
            static_assert(std::is_base_of_v<Object, C>);
            return std::apply(f_, std::tuple_cat(std::make_tuple(static_cast<C*>(obj)), std::move(args_)));
        } else {
            return run();
        }
    }

    [[maybe_unused]] auto run() const {
        return std::apply(f_, args_);
    }

private:
    F f_;
    std::tuple<Args...> args_;
};

template <class F, class... Args>
auto MakeImmediateClosure(F f, Args&&... args) {
    return Closure<F, Args...>(f, std::forward<Args>(args)...);
}

template <class F, class... Args>
auto MakeDelayedClosure(F f, Args... args) {
    return Closure<F, std::decay_t<Args>...>(f, std::move(args)...);
}
@levlam
Copy link
Contributor

levlam commented Mar 22, 2023

Likely, yes, but TDLib is written in C++14, hence we can't use std::apply and other minor features used in the PoC code.

Also, what benefits do you try to achieve by merging the classes?

@yanminhui
Copy link
Author

template <class ClosureT>
class ClosureEvent final : public CustomEvent {
public:
void run(Actor *actor) final {
closure_.run(static_cast<typename ClosureT::ActorType *>(actor));
}
template <class... ArgsT>
explicit ClosureEvent(ArgsT &&...args) : closure_(std::forward<ArgsT>(args)...) {
}
void start_migrate(int32 sched_id) final {
closure_.for_each([sched_id](auto &obj) {
using ::td::start_migrate;
start_migrate(obj, sched_id);
});
}
void finish_migrate() final {
closure_.for_each([](auto &obj) {
using ::td::finish_migrate;
finish_migrate(obj);
});
}
private:
ClosureT closure_;
};
template <class LambdaT>
class LambdaEvent final : public CustomEvent {
public:
void run(Actor *actor) final {
f_();
}
template <class FromLambdaT, std::enable_if_t<!std::is_same<std::decay_t<FromLambdaT>, LambdaEvent>::value, int> = 0>
explicit LambdaEvent(FromLambdaT &&lambda) : f_(std::forward<FromLambdaT>(lambda)) {
}
private:
LambdaT f_;
};

LambdaEvent can be replaced with ClosureEvent as well. Using lambda and member function pointer the same way.

int a = 1;
int b = 2;

const Base* obj = new Derive{};
auto c = MakeImmediateClosure(&Derive::add, a, b);
std::cout << "run memptr: " << c.run(obj) << std::endl;
delete obj;

auto c2 = MakeImmediateClosure([](int a, int b) { return a + b + 4; }, a, b);
std::cout << "run lambda: " << c2.run(obj) << std::endl;  // or c2.run(), obj is ignored.

@levlam
Copy link
Contributor

levlam commented Mar 26, 2023

It is definitely possible to achieve the same behavior in many ways. But is there reason to change the current implementation?

@yanminhui
Copy link
Author

It is for simplify logic code, and usage consistency for lambda and member pointer function only.

In addition, the events that send to actor is mostly custom events (i.e. member pointer functor), it can improve performance like std::function by allocate object on the stack.

_FunAlloc __af(__a);
if (__use_small_storage<_Fun>())
{
    ::new ((void*)&__buf_.__small)
        _Fun(_VSTD::move(__f), _Alloc(__af));
}
else
{
    typedef __allocator_destructor<_FunAlloc> _Dp;
    unique_ptr<_Fun, _Dp> __hold(__af.allocate(1), _Dp(__af, 1));
    ::new ((void*)__hold.get())
        _Fun(_VSTD::move(__f), _Alloc(__af));
    __buf_.__large = __hold.release();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants