Instantly share code, notes, and snippets.
Created
September 21, 2018 15:05
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save RedBeard0531/0747a6f4fb678dee8753fae7be1b5257 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <type_traits> | |
#include <future> | |
#include <functional> | |
template <typename T> | |
struct expected { | |
expected(const T&) {} | |
}; | |
template <typename T> | |
struct Promise { | |
void set_with(std::function<T()>); | |
}; | |
template <typename T> | |
struct LazyFuture; | |
template <typename T> | |
struct [[nodiscard]] Future { | |
using value_type = T; | |
static constexpr bool isLazy = false; | |
static auto make_promise_contract() { | |
struct PromiseContract { | |
Future<T> fut; | |
Promise<T> promise; | |
}; | |
return PromiseContract(); | |
} | |
LazyFuture<T> lazify(); | |
Future<T> eagerize() { return *this; } | |
expected<T> get_expected(); | |
void finally(std::function<void(expected<T>)>); | |
}; | |
template <typename T> | |
using Receiver = std::function<void(expected<T>)>; | |
template <typename T> | |
using Sender = std::function<void(Receiver<T>)>; | |
template <typename T> | |
struct [[nodiscard]] LazyFuture { | |
static constexpr bool isLazy = true; | |
using value_type = T; | |
LazyFuture<T> lazify() { return *this; } | |
Future<T> eagerize(); | |
void finally(std::function<void(expected<T>)> func) { | |
submit(func); | |
} | |
expected<T> get_expected() { | |
return eagerize().get_expected(); | |
} | |
Sender<T> submit; | |
}; | |
template <typename T> | |
Future<T> LazyFuture<T>::eagerize() { | |
auto [fut, p] = Future<T>::make_promise_contract(); | |
submit([p = p] (expected<T>) mutable noexcept { | |
p.set_with(std::make_from_tuple<T>()); | |
}); | |
return fut; | |
} | |
template <typename T> | |
LazyFuture<T> Future<T>::lazify() { | |
#if 0 // old impl | |
struct State { | |
std::atomic<int> count{0}; | |
std::optional<expected<T>> val; | |
std::optional<Receiver<T>> receiver; | |
}; | |
auto state = std::make_shared<State>(); | |
finally([state] (expected<T> val) mutable noexcept { | |
state->val.emplace(val); | |
if (state->count++ == 1) | |
return; | |
(state->receiver)(*state->val); | |
}); | |
return LazyFuture<T>{[state] (Receiver<T> receiver) { | |
state->receiver.emplace(receiver); | |
if (state->count++ == 1) | |
return; | |
(state->receiver)(*state->val); | |
}}; | |
#else | |
return { [self = std::move(*this)] (Receiver<T> receiver) mutable noexcept { | |
self.finally(receiver); | |
}}; | |
#endif | |
} | |
template <typename T> | |
class SharedFuture { | |
public: | |
template<typename AnyFutureT> | |
SharedFuture(AnyFutureT future) { | |
future.finally([self = *this] (expected<T> result) mutable noexcept { | |
{ | |
auto lk = std::unique_lock(self.state->mx); | |
self.state->result = result; | |
self.state->is_ready.store(std::memory_order_release); | |
// After this point finally() will no longer mutate state->receivers | |
} | |
// Could also use bulk executor here. | |
for (auto&& func : self->state->receivers) { | |
func(std::as_const(self->state->result)); | |
} | |
}); | |
} | |
LazyFuture<T> lazify() { | |
return { [self = *this] (Receiver<T> receiver) mutable noexcept { | |
self.finally(receiver); | |
}}; | |
} | |
void finally(std::function<void(expected<T>)> func) { | |
if (state->is_ready.load(std::memory_order_acquire)) { | |
func(std::as_const(state->result)); | |
return; | |
} | |
auto lk = std::unique_lock(state->mx); | |
if (state->is_ready.load(std::memory_order_relaxed)) { | |
lk.unlock(); | |
func(std::as_const(state->result)); | |
return; | |
} | |
state->receivers.push_back(func); | |
} | |
private: | |
struct State { | |
std::mutex mx; | |
std::condition_variable cv; | |
std::atomic<bool> is_ready; | |
std::optional<expected<T>> result; | |
std::vector<Receiver<T>> receivers; | |
}; | |
std::shared_ptr<State> state = std::make_shared<State>(); | |
}; | |
template <typename T> SharedFuture(Future<T>) -> SharedFuture<T>; | |
template <typename T> SharedFuture(LazyFuture<T>) -> SharedFuture<T>; | |
template < | |
typename RealExec, | |
template<typename> typename Future = Future> | |
class EagerDependencyObliviousExecutorMixin { | |
public: | |
template <typename Continuation> | |
auto initially_execute(Continuation&& c) { | |
using R = decltype(c()); | |
auto [fut, p] = Future<R>::make_promise_contract(); | |
realExec()->execute([p = std::move(p), c] () mutable noexcept { | |
p.set_with(c); | |
}); | |
return fut; | |
} | |
template <typename InFut, typename Continuation> | |
auto then_execute(Continuation&& c, InFut&& inFut) { | |
using R = decltype(c(inFut.get_expected())); | |
auto [fut, p] = Future<R>::make_promise_contract(); | |
inFut.finally([this, p = std::move(p), c] (auto&& res) mutable noexcept { | |
realExec()->execute([p, c, res] () mutable noexcept { | |
p.set_with([&] { return c(res); }); | |
}); | |
}); | |
return fut; | |
} | |
template <typename InFut, typename Continuation> | |
void finally_execute(Continuation&& c, InFut&& inFut) { | |
static_assert(std::is_void<decltype(c(inFut.get_expected()))>()); | |
inFut.finally([this, c] (auto&& res) { | |
realExec()->execute([c, res] () noexcept { | |
c(res); | |
}); | |
}); | |
} | |
private: | |
RealExec* realExec() { | |
return static_cast<RealExec*>(this); | |
} | |
}; | |
template < | |
typename RealExec, | |
template<typename> typename Future = LazyFuture> | |
class LazyDependencyObliviousExecutorMixin { | |
public: | |
template <typename Continuation> | |
auto initially_execute(Continuation&& c) { | |
using R = decltype(c()); | |
return Future<R>{[this, c](Receiver<R> receiver) noexcept { | |
realExec()->execute([c, receiver] () noexcept { | |
receiver(c()); | |
}); | |
}}; | |
} | |
template <typename InFut, typename Continuation> | |
auto then_execute(Continuation&& c, InFut&& inFut) { | |
using T = typename std::remove_reference_t<InFut>::value_type; | |
using R = decltype(c(std::declval<expected<T>>())); | |
return Future<R>{[this, c, inFut](Receiver<R> receiver) mutable noexcept { | |
inFut.finally([this, c, receiver] (expected<R> res) noexcept { | |
realExec()->execute([c, receiver, res] () noexcept { | |
receiver(c(res)); | |
}); | |
}); | |
}}; | |
} | |
template <typename InFut, typename Continuation> | |
void finally_execute(Continuation&& c, InFut&& inFut) { | |
using T = typename std::remove_reference_t<InFut>::value_type; | |
static_assert(std::is_void<decltype(c(std::declval<expected<T>>()))>()); | |
inFut.finally([this, c] (expected<T> res) noexcept { | |
realExec()->execute([c, res] () noexcept{ | |
c(res); | |
}); | |
}); | |
} | |
private: | |
RealExec* realExec() { | |
return static_cast<RealExec*>(this); | |
} | |
}; | |
struct Executor : EagerDependencyObliviousExecutorMixin<Executor> { | |
void execute(std::function<void()>); | |
}; | |
struct LazyExecutor : LazyDependencyObliviousExecutorMixin<LazyExecutor> { | |
void execute(std::function<void()>); | |
}; | |
void func() { | |
Executor ex; | |
auto fut1 = ex.initially_execute([]{ return 1;}); | |
auto fut2 = ex.then_execute([](expected<int>){ return 1;}, fut1); | |
auto fut3 = ex.then_execute([](expected<int>){ return 1;}, fut2); | |
ex.finally_execute([](expected<int>){}, fut3); | |
} | |
void func2() { | |
LazyExecutor ex; | |
auto fut1 = ex.initially_execute([]{ return 1;}); | |
auto fut2 = ex.then_execute([](expected<int>){ return 1;}, fut1); | |
auto fut3 = ex.then_execute([](expected<int>){ return 1;}, fut2); | |
ex.finally_execute([](expected<int>){}, fut3); | |
} | |
void func3() { | |
Executor ex; | |
LazyExecutor lex; | |
auto fut1 = ex.initially_execute([]{ return 1;}); | |
auto fut2 = ex.then_execute([](expected<int>){ return 1;}, fut1); | |
auto fut3 = lex.then_execute([](expected<int>){ return 1;}, fut2); | |
lex.finally_execute([](expected<int>){}, fut3); | |
} | |
void func4() { | |
Executor ex; | |
LazyExecutor lex; | |
auto fut1 = lex.initially_execute([]{ return 1;}); | |
auto fut2 = lex.then_execute([](expected<int>){ return 1;}, fut1); | |
auto fut3 = ex.then_execute([](expected<int>){ return 1;}, fut2); | |
ex.finally_execute([](expected<int>){}, fut3); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment