include/unifex/any_scheduler.hpp (212 lines of code) (raw):

/* * Copyright (c) Facebook, Inc. and its affiliates. * * Licensed under the Apache License Version 2.0 with LLVM Exceptions * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * https://llvm.org/LICENSE.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include <unifex/any_unique.hpp> #include <unifex/receiver_concepts.hpp> #include <unifex/sender_concepts.hpp> #include <unifex/scheduler_concepts.hpp> #include <unifex/any_unique.hpp> #include <unifex/any_sender_of.hpp> #include <unifex/std_concepts.hpp> #include <unifex/type_index.hpp> #include <unifex/tag_invoke.hpp> #include <unifex/detail/prologue.hpp> namespace unifex { namespace _any_sched { template <typename Ret> struct _copy_as_fn { using type_erased_signature_t = Ret(const this_&); template (typename T) (requires tag_invocable<_copy_as_fn, const T&>) Ret operator()(const T& t) const { return tag_invoke(*this, t); } template (typename T) (requires (!tag_invocable<_copy_as_fn, const T&>) AND copy_constructible<T>) Ret operator()(const T& t) const { return Ret{t}; } }; template <typename Ret> inline constexpr _copy_as_fn<Ret> _copy_as{}; inline constexpr struct _get_type_index_fn { using type_erased_signature_t = type_index(const this_&) noexcept; template <typename T> type_index operator()(const T& x) const noexcept { if constexpr (tag_invocable<_get_type_index_fn, const T&>) { return tag_invoke(*this, x); } else { return type_id<T>(); } } } _get_type_index{}; inline constexpr struct _equal_to_fn { template (typename T, typename U) (requires tag_invocable<_equal_to_fn, const T&, const U&>) bool operator()(const T& t, const U& u) const noexcept { static_assert(is_nothrow_tag_invocable_v<_equal_to_fn, const T&, const U&>); return tag_invoke(*this, t, u); } template (typename T, typename U) (requires (!tag_invocable<_equal_to_fn, const T&, const U&>) AND equality_comparable<T>) bool operator()(const T& t, const U& u) const noexcept { static_assert(noexcept(t == t), "Equality comparison of schedulers ought to be noexcept"); return type_id<T>() == _get_type_index(u.impl_) && t == *static_cast<const T*>(get_object_address(u.impl_)); } } _equal_to{}; template <typename... CPOs> using _void_receiver_ref = _any::_receiver_ref<type_list<CPOs...>>; template <typename... CPOs> struct _schedule_and_connect_fn { struct type { using _rec_ref_t = _void_receiver_ref<CPOs...>; using type_erased_signature_t = _any::_operation_state(const this_&, _rec_ref_t); #ifdef _MSC_VER // MSVC (_MSC_VER == 1927) doesn't seem to like the requires // clause here. Use SFINAE instead. template <typename Scheduler> std::enable_if_t< is_tag_invocable_v<type, const Scheduler&, _rec_ref_t>, _any::_operation_state> operator()(const Scheduler& sched, _rec_ref_t rec) const { return tag_invoke(*this, sched, (_rec_ref_t&&) rec); } template <typename Scheduler> std::enable_if_t< !is_tag_invocable_v<type, const Scheduler&, _rec_ref_t>, _any::_operation_state> operator()(const Scheduler& sched, _rec_ref_t rec) const { return _any::_connect<type_list<CPOs...>>(schedule(sched), (_rec_ref_t&&) rec); } #else template (typename Scheduler) (requires tag_invocable<type, const Scheduler&, _rec_ref_t>) _any::_operation_state operator()(const Scheduler& sched, _rec_ref_t rec) const { return tag_invoke(*this, sched, (_rec_ref_t&&) rec); } template (typename Scheduler) (requires (!tag_invocable<type, const Scheduler&, _rec_ref_t>) AND scheduler<Scheduler>) _any::_operation_state operator()(const Scheduler& sched, _rec_ref_t rec) const { return _any::_connect<type_list<CPOs...>>(schedule(sched), (_rec_ref_t&&) rec); } #endif }; }; template <typename... CPOs> inline constexpr typename _schedule_and_connect_fn<CPOs...>::type _schedule_and_connect{}; template <typename... CPOs> using _any_void_sender_of = typename _any::_with<CPOs...>::template any_sender_of<>; template <typename... CPOs> using any_scheduler_impl = any_unique_t< _schedule_and_connect<CPOs...>, _copy_as<any_scheduler<CPOs...>>, _get_type_index, overload<bool(const this_&, const any_scheduler<CPOs...>&) noexcept>(_equal_to)>; template <typename... CPOs> struct _with<CPOs...>::any_scheduler { template (typename Scheduler) (requires (!same_as<Scheduler, any_scheduler>) AND scheduler<Scheduler>) /* implicit */ any_scheduler(Scheduler sched) : impl_((Scheduler&&) sched) {} any_scheduler(any_scheduler&&) noexcept = default; any_scheduler(const any_scheduler& that) : impl_(_copy_as<any_scheduler>(that.impl_).impl_) {} any_scheduler& operator=(any_scheduler&&) noexcept = default; any_scheduler& operator=(const any_scheduler& that) { impl_ = _copy_as<any_scheduler>(that.impl_).impl_; return *this; } struct _sender { template <template <class...> class Variant, template <class...> class Tuple> using value_types = Variant<Tuple<>>; template <template <class...> class Variant> using error_types = Variant<std::exception_ptr>; static constexpr bool sends_done = true; _sender(_sender&&) noexcept = default; template (typename Receiver) (requires receiver_of<Receiver> AND (invocable<CPOs, const Receiver&> &&...)) any_operation_state_for<Receiver> connect(Receiver rec) && { any_scheduler_impl<CPOs...> const& impl = sched_.impl_; return any_operation_state_for<Receiver>{ (Receiver&&) rec, [&impl](_void_receiver_ref<CPOs...> rec2) { return _schedule_and_connect<CPOs...>( impl, (_void_receiver_ref<CPOs...>&&) rec2); } }; } private: friend any_scheduler; _sender(const any_scheduler* sched) : sched_(*sched) {} // TODO This does a dynamic allocation. Fix this by: // 1. Implementing the small-object optimization in any_unique, and // 2. Provide hooks so that _sender can take a strong reference on // the impl when it's dynamically allocated. any_scheduler sched_; }; _sender schedule() const { return _sender{this}; } friend _equal_to_fn; friend bool operator==(const any_scheduler& left, const any_scheduler& right) noexcept { return _equal_to(left.impl_, right); } friend bool operator!=(const any_scheduler& left, const any_scheduler& right) noexcept { return !(left == right); } private: any_scheduler_impl<CPOs...> impl_; }; template <typename... CPOs> using any_scheduler_ref_impl = any_ref_t<_schedule_and_connect<CPOs...>>; template <typename... CPOs> struct _with<CPOs...>::any_scheduler_ref { template (typename Scheduler) (requires (!same_as<const Scheduler, const any_scheduler_ref>) AND scheduler<Scheduler>) /* implicit */ any_scheduler_ref(Scheduler& sched) noexcept : impl_(sched) {} struct _sender { template <template <class...> class Variant, template <class...> class Tuple> using value_types = Variant<Tuple<>>; template <template <class...> class Variant> using error_types = Variant<std::exception_ptr>; static constexpr bool sends_done = true; _sender(_sender&&) noexcept = default; template (typename Receiver) (requires receiver_of<Receiver> AND (invocable<CPOs, const Receiver&> &&...)) any_operation_state_for<Receiver> connect(Receiver rec) && { any_scheduler_ref_impl<CPOs...> const& impl = sched_.impl_; return any_operation_state_for<Receiver>{ (Receiver&&) rec, [&impl](_void_receiver_ref<CPOs...> rec2) { return _schedule_and_connect<CPOs...>( impl, (_void_receiver_ref<CPOs...>&&) rec2); } }; } private: friend any_scheduler_ref; _sender(const any_scheduler_ref* sched) noexcept : sched_(*sched) {} any_scheduler_ref sched_; }; _sender schedule() const noexcept { return _sender{this}; } friend bool operator==(const any_scheduler_ref& left, const any_scheduler_ref& right) noexcept { return left.impl_ == right.impl_; } friend bool operator!=(const any_scheduler_ref& left, const any_scheduler_ref& right) noexcept { return !(left == right); } private: any_scheduler_ref_impl<CPOs...> impl_; }; } // namespace _any_sched using any_scheduler = _any_sched::any_scheduler<>; using any_scheduler_ref = _any_sched::any_scheduler_ref<>; } // namespace unifex #include <unifex/detail/epilogue.hpp>