// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. #pragma once #if !defined(RXCPP_RX_SCHEDULER_HPP) #define RXCPP_RX_SCHEDULER_HPP #include "rx-includes.hpp" namespace rxcpp { namespace schedulers { class worker_interface; class scheduler_interface; namespace detail { class action_type; typedef std::shared_ptr action_ptr; typedef std::shared_ptr worker_interface_ptr; typedef std::shared_ptr const_worker_interface_ptr; typedef std::weak_ptr worker_interface_weak_ptr; typedef std::weak_ptr const_worker_interface_weak_ptr; typedef std::shared_ptr scheduler_interface_ptr; typedef std::shared_ptr const_scheduler_interface_ptr; inline action_ptr shared_empty() { static action_ptr shared_empty = std::make_shared(); return shared_empty; } } // It is essential to keep virtual function calls out of an inner loop. // To make tail-recursion work efficiently the recursion objects create // a space on the stack inside the virtual function call in the actor that // allows the callback and the scheduler to share stack space that records // the request and the allowance without any virtual calls in the loop. /// recursed is set on a schedulable by the action to allow the called /// function to request to be rescheduled. class recursed { bool& isrequested; recursed operator=(const recursed&); public: explicit recursed(bool& r) : isrequested(r) { } /// request to be rescheduled inline void operator()() const { isrequested = true; } }; /// recurse is passed to the action by the scheduler. /// the action uses recurse to coordinate the scheduler and the function. class recurse { std::atomic& isallowed; mutable bool isrequested; recursed requestor; recurse operator=(const recurse&); public: explicit recurse(std::atomic& a) : isallowed(a) , isrequested(true) , requestor(isrequested) { } /// does the scheduler allow tail-recursion now? inline bool is_allowed() const { return isallowed; } /// did the function request to be recursed? inline bool is_requested() const { return isrequested; } /// reset the function request. call before each call to the function. inline void reset() const { isrequested = false; } /// get the recursed to set into the schedulable for the function to use to request recursion inline const recursed& get_recursed() const { return requestor; } }; /// recursion is used by the scheduler to signal to each action whether tail recursion is allowed. class recursion { mutable std::atomic isallowed; recurse recursor; recursion operator=(const recursion&); public: recursion() : isallowed(true) , recursor(isallowed) { } explicit recursion(bool b) : isallowed(b) , recursor(isallowed) { } /// set whether tail-recursion is allowed inline void reset(bool b = true) const { isallowed = b; } /// get the recurse to pass into each action being called inline const recurse& get_recurse() const { return recursor; } }; struct action_base { typedef tag_action action_tag; }; class schedulable; /// action provides type-forgetting for a potentially recursive set of calls to a function that takes a schedulable class action : public action_base { typedef action this_type; detail::action_ptr inner; public: action() { } explicit action(detail::action_ptr i) : inner(std::move(i)) { } /// return the empty action inline static action empty() { return action(detail::shared_empty()); } /// call the function inline void operator()(const schedulable& s, const recurse& r) const; }; struct scheduler_base { typedef std::chrono::steady_clock clock_type; typedef tag_scheduler scheduler_tag; }; struct worker_base : public subscription_base { typedef tag_worker worker_tag; }; class worker_interface : public std::enable_shared_from_this { typedef worker_interface this_type; public: typedef scheduler_base::clock_type clock_type; virtual ~worker_interface() {} virtual clock_type::time_point now() const = 0; virtual void schedule(const schedulable& scbl) const = 0; virtual void schedule(clock_type::time_point when, const schedulable& scbl) const = 0; }; namespace detail { template struct is_action_function { struct not_void {}; template static auto check(int) -> decltype((*(CF*)nullptr)(*(schedulable*)nullptr)); template static not_void check(...); static const bool value = std::is_same>(0)), void>::value; }; } class weak_worker; /// a worker ensures that all scheduled actions on the same instance are executed in-order with no overlap /// a worker ensures that all scheduled actions are unsubscribed when it is unsubscribed /// some inner implementations will impose additional constraints on the execution of items. class worker : public worker_base { typedef worker this_type; detail::worker_interface_ptr inner; composite_subscription lifetime; friend bool operator==(const worker&, const worker&); friend class weak_worker; public: typedef scheduler_base::clock_type clock_type; typedef composite_subscription::weak_subscription weak_subscription; worker() { } worker(composite_subscription cs, detail::const_worker_interface_ptr i) : inner(std::const_pointer_cast(i)) , lifetime(std::move(cs)) { } worker(composite_subscription cs, worker o) : inner(o.inner) , lifetime(std::move(cs)) { } inline const composite_subscription& get_subscription() const { return lifetime; } inline composite_subscription& get_subscription() { return lifetime; } // composite_subscription // inline bool is_subscribed() const { return lifetime.is_subscribed(); } inline weak_subscription add(subscription s) const { return lifetime.add(std::move(s)); } inline void remove(weak_subscription w) const { return lifetime.remove(std::move(w)); } inline void clear() const { return lifetime.clear(); } inline void unsubscribe() const { return lifetime.unsubscribe(); } // worker_interface // /// return the current time for this worker inline clock_type::time_point now() const { return inner->now(); } /// insert the supplied schedulable to be run as soon as possible inline void schedule(const schedulable& scbl) const { // force rebinding scbl to this worker schedule_rebind(scbl); } /// insert the supplied schedulable to be run at the time specified inline void schedule(clock_type::time_point when, const schedulable& scbl) const { // force rebinding scbl to this worker schedule_rebind(when, scbl); } // helpers // /// insert the supplied schedulable to be run at now() + the delay specified inline void schedule(clock_type::duration when, const schedulable& scbl) const { // force rebinding scbl to this worker schedule_rebind(now() + when, scbl); } /// insert the supplied schedulable to be run at the initial time specified and then again at initial + (N * period) /// this will continue until the worker or schedulable is unsubscribed. inline void schedule_periodically(clock_type::time_point initial, clock_type::duration period, const schedulable& scbl) const { // force rebinding scbl to this worker schedule_periodically_rebind(initial, period, scbl); } /// insert the supplied schedulable to be run at now() + the initial delay specified and then again at now() + initial + (N * period) /// this will continue until the worker or schedulable is unsubscribed. inline void schedule_periodically(clock_type::duration initial, clock_type::duration period, const schedulable& scbl) const { // force rebinding scbl to this worker schedule_periodically_rebind(now() + initial, period, scbl); } /// use the supplied arguments to make a schedulable and then insert it to be run template auto schedule(Arg0&& a0, ArgN&&... an) const -> typename std::enable_if< (detail::is_action_function::value || is_subscription::value) && !is_schedulable::value>::type; template /// use the supplied arguments to make a schedulable and then insert it to be run void schedule_rebind(const schedulable& scbl, ArgN&&... an) const; /// use the supplied arguments to make a schedulable and then insert it to be run template auto schedule(clock_type::time_point when, Arg0&& a0, ArgN&&... an) const -> typename std::enable_if< (detail::is_action_function::value || is_subscription::value) && !is_schedulable::value>::type; /// use the supplied arguments to make a schedulable and then insert it to be run template void schedule_rebind(clock_type::time_point when, const schedulable& scbl, ArgN&&... an) const; /// use the supplied arguments to make a schedulable and then insert it to be run template auto schedule_periodically(clock_type::time_point initial, clock_type::duration period, Arg0&& a0, ArgN&&... an) const -> typename std::enable_if< (detail::is_action_function::value || is_subscription::value) && !is_schedulable::value>::type; /// use the supplied arguments to make a schedulable and then insert it to be run template void schedule_periodically_rebind(clock_type::time_point initial, clock_type::duration period, const schedulable& scbl, ArgN&&... an) const; }; inline bool operator==(const worker& lhs, const worker& rhs) { return lhs.inner == rhs.inner && lhs.lifetime == rhs.lifetime; } inline bool operator!=(const worker& lhs, const worker& rhs) { return !(lhs == rhs); } class weak_worker { detail::worker_interface_weak_ptr inner; composite_subscription lifetime; public: weak_worker() { } explicit weak_worker(worker& owner) : inner(owner.inner) , lifetime(owner.lifetime) { } worker lock() const { return worker(lifetime, inner.lock()); } }; class scheduler_interface : public std::enable_shared_from_this { typedef scheduler_interface this_type; public: typedef scheduler_base::clock_type clock_type; virtual ~scheduler_interface() {} virtual clock_type::time_point now() const = 0; virtual worker create_worker(composite_subscription cs) const = 0; }; struct schedulable_base : // public subscription_base, <- already in worker base public worker_base, public action_base { typedef tag_schedulable schedulable_tag; }; /*! \brief allows functions to be called at specified times and possibly in other contexts. \ingroup group-core */ class scheduler : public scheduler_base { typedef scheduler this_type; detail::scheduler_interface_ptr inner; friend bool operator==(const scheduler&, const scheduler&); public: typedef scheduler_base::clock_type clock_type; scheduler() { } explicit scheduler(detail::scheduler_interface_ptr i) : inner(std::move(i)) { } explicit scheduler(detail::const_scheduler_interface_ptr i) : inner(std::const_pointer_cast(i)) { } /// return the current time for this scheduler inline clock_type::time_point now() const { return inner->now(); } /// create a worker with a lifetime. /// when the worker is unsubscribed all scheduled items will be unsubscribed. /// items scheduled to a worker will be run one at a time. /// scheduling order is preserved: when more than one item is scheduled for /// time T then at time T they will be run in the order that they were scheduled. inline worker create_worker(composite_subscription cs = composite_subscription()) const { return inner->create_worker(cs); } }; template inline scheduler make_scheduler(ArgN&&... an) { return scheduler(std::static_pointer_cast(std::make_shared(std::forward(an)...))); } inline scheduler make_scheduler(std::shared_ptr si) { return scheduler(si); } class schedulable : public schedulable_base { typedef schedulable this_type; composite_subscription lifetime; weak_worker controller; action activity; bool scoped; composite_subscription::weak_subscription action_scope; struct detacher { ~detacher() { if (that) { that->unsubscribe(); } } detacher(const this_type* that) : that(that) { } const this_type* that; }; class recursed_scope_type { mutable const recursed* requestor; class exit_recursed_scope_type { const recursed_scope_type* that; public: ~exit_recursed_scope_type() { if (that != nullptr) { that->requestor = nullptr; } } exit_recursed_scope_type(const recursed_scope_type* that) : that(that) { } exit_recursed_scope_type(exit_recursed_scope_type && other) RXCPP_NOEXCEPT : that(other.that) { other.that = nullptr; } }; public: recursed_scope_type() : requestor(nullptr) { } recursed_scope_type(const recursed_scope_type&) : requestor(nullptr) { // does not aquire recursion scope } recursed_scope_type& operator=(const recursed_scope_type& ) { // no change in recursion scope return *this; } exit_recursed_scope_type reset(const recurse& r) const { requestor = std::addressof(r.get_recursed()); return exit_recursed_scope_type(this); } bool is_recursed() const { return !!requestor; } void operator()() const { (*requestor)(); } }; recursed_scope_type recursed_scope; public: typedef composite_subscription::weak_subscription weak_subscription; typedef scheduler_base::clock_type clock_type; ~schedulable() { if (scoped) { controller.lock().remove(action_scope); } } schedulable() : scoped(false) { } /// action and worker share lifetime schedulable(worker q, action a) : lifetime(q.get_subscription()) , controller(q) , activity(std::move(a)) , scoped(false) { } /// action and worker have independent lifetimes schedulable(composite_subscription cs, worker q, action a) : lifetime(std::move(cs)) , controller(q) , activity(std::move(a)) , scoped(true) , action_scope(controller.lock().add(lifetime)) { } /// inherit lifetimes schedulable(schedulable scbl, worker q, action a) : lifetime(scbl.get_subscription()) , controller(q) , activity(std::move(a)) , scoped(scbl.scoped) , action_scope(scbl.scoped ? controller.lock().add(lifetime) : weak_subscription()) { } inline const composite_subscription& get_subscription() const { return lifetime; } inline composite_subscription& get_subscription() { return lifetime; } inline const worker get_worker() const { return controller.lock(); } inline worker get_worker() { return controller.lock(); } inline const action& get_action() const { return activity; } inline action& get_action() { return activity; } inline static schedulable empty(worker sc) { return schedulable(composite_subscription::empty(), sc, action::empty()); } inline auto set_recursed(const recurse& r) const -> decltype(recursed_scope.reset(r)) { return recursed_scope.reset(r); } // recursed // bool is_recursed() const { return recursed_scope.is_recursed(); } /// requests tail-recursion of the same action /// this will exit the process if called when /// is_recursed() is false. /// Note: to improve perf it is not required /// to call is_recursed() before calling this /// operator. Context is sufficient. The schedulable /// passed to the action by the scheduler will return /// true from is_recursed() inline void operator()() const { recursed_scope(); } // composite_subscription // inline bool is_subscribed() const { return lifetime.is_subscribed(); } inline weak_subscription add(subscription s) const { return lifetime.add(std::move(s)); } template auto add(F f) const -> typename std::enable_if::value, weak_subscription>::type { return lifetime.add(make_subscription(std::move(f))); } inline void remove(weak_subscription w) const { return lifetime.remove(std::move(w)); } inline void clear() const { return lifetime.clear(); } inline void unsubscribe() const { return lifetime.unsubscribe(); } // scheduler // inline clock_type::time_point now() const { return controller.lock().now(); } /// put this on the queue of the stored scheduler to run asap inline void schedule() const { if (is_subscribed()) { get_worker().schedule(*this); } } /// put this on the queue of the stored scheduler to run at the specified time inline void schedule(clock_type::time_point when) const { if (is_subscribed()) { get_worker().schedule(when, *this); } } /// put this on the queue of the stored scheduler to run after a delay from now inline void schedule(clock_type::duration when) const { if (is_subscribed()) { get_worker().schedule(when, *this); } } // action // /// invokes the action inline void operator()(const recurse& r) const { if (!is_subscribed()) { return; } detacher protect(this); activity(*this, r); protect.that = nullptr; } }; struct current_thread; namespace detail { class action_type : public std::enable_shared_from_this { typedef action_type this_type; public: typedef std::function function_type; private: function_type f; public: action_type() { } action_type(function_type f) : f(std::move(f)) { } inline void operator()(const schedulable& s, const recurse& r) { if (!f) { std::terminate(); } f(s, r); } }; class action_tailrecurser : public std::enable_shared_from_this { typedef action_type this_type; public: typedef std::function function_type; private: function_type f; public: action_tailrecurser() { } action_tailrecurser(function_type f) : f(std::move(f)) { } inline void operator()(const schedulable& s, const recurse& r) { if (!f) { std::terminate(); } trace_activity().action_enter(s); auto scope = s.set_recursed(r); while (s.is_subscribed()) { r.reset(); f(s); if (!r.is_allowed() || !r.is_requested()) { if (r.is_requested()) { s.schedule(); } break; } trace_activity().action_recurse(s); } trace_activity().action_return(s); } }; } inline void action::operator()(const schedulable& s, const recurse& r) const { (*inner)(s, r); } inline action make_action_empty() { return action::empty(); } template inline action make_action(F&& f) { static_assert(detail::is_action_function::value, "action function must be void(schedulable)"); auto fn = std::forward(f); return action(std::make_shared(detail::action_tailrecurser(fn))); } // copy inline auto make_schedulable( const schedulable& scbl) -> schedulable { return schedulable(scbl); } // move inline auto make_schedulable( schedulable&& scbl) -> schedulable { return schedulable(std::move(scbl)); } inline schedulable make_schedulable(worker sc, action a) { return schedulable(sc, a); } inline schedulable make_schedulable(worker sc, composite_subscription cs, action a) { return schedulable(cs, sc, a); } template auto make_schedulable(worker sc, F&& f) -> typename std::enable_if::value, schedulable>::type { return schedulable(sc, make_action(std::forward(f))); } template auto make_schedulable(worker sc, composite_subscription cs, F&& f) -> typename std::enable_if::value, schedulable>::type { return schedulable(cs, sc, make_action(std::forward(f))); } template auto make_schedulable(schedulable scbl, composite_subscription cs, F&& f) -> typename std::enable_if::value, schedulable>::type { return schedulable(cs, scbl.get_worker(), make_action(std::forward(f))); } template auto make_schedulable(schedulable scbl, worker sc, F&& f) -> typename std::enable_if::value, schedulable>::type { return schedulable(scbl, sc, make_action(std::forward(f))); } template auto make_schedulable(schedulable scbl, F&& f) -> typename std::enable_if::value, schedulable>::type { return schedulable(scbl, scbl.get_worker(), make_action(std::forward(f))); } inline auto make_schedulable(schedulable scbl, composite_subscription cs) -> schedulable { return schedulable(cs, scbl.get_worker(), scbl.get_action()); } inline auto make_schedulable(schedulable scbl, worker sc, composite_subscription cs) -> schedulable { return schedulable(cs, sc, scbl.get_action()); } inline auto make_schedulable(schedulable scbl, worker sc) -> schedulable { return schedulable(scbl, sc, scbl.get_action()); } template auto worker::schedule(Arg0&& a0, ArgN&&... an) const -> typename std::enable_if< (detail::is_action_function::value || is_subscription::value) && !is_schedulable::value>::type { auto scbl = make_schedulable(*this, std::forward(a0), std::forward(an)...); trace_activity().schedule_enter(*inner.get(), scbl); inner->schedule(std::move(scbl)); trace_activity().schedule_return(*inner.get()); } template void worker::schedule_rebind(const schedulable& scbl, ArgN&&... an) const { auto rescbl = make_schedulable(scbl, *this, std::forward(an)...); trace_activity().schedule_enter(*inner.get(), rescbl); inner->schedule(std::move(rescbl)); trace_activity().schedule_return(*inner.get()); } template auto worker::schedule(clock_type::time_point when, Arg0&& a0, ArgN&&... an) const -> typename std::enable_if< (detail::is_action_function::value || is_subscription::value) && !is_schedulable::value>::type { auto scbl = make_schedulable(*this, std::forward(a0), std::forward(an)...); trace_activity().schedule_when_enter(*inner.get(), when, scbl); inner->schedule(when, std::move(scbl)); trace_activity().schedule_when_return(*inner.get()); } template void worker::schedule_rebind(clock_type::time_point when, const schedulable& scbl, ArgN&&... an) const { auto rescbl = make_schedulable(scbl, *this, std::forward(an)...); trace_activity().schedule_when_enter(*inner.get(), when, rescbl); inner->schedule(when, std::move(rescbl)); trace_activity().schedule_when_return(*inner.get()); } template auto worker::schedule_periodically(clock_type::time_point initial, clock_type::duration period, Arg0&& a0, ArgN&&... an) const -> typename std::enable_if< (detail::is_action_function::value || is_subscription::value) && !is_schedulable::value>::type { schedule_periodically_rebind(initial, period, make_schedulable(*this, std::forward(a0), std::forward(an)...)); } template void worker::schedule_periodically_rebind(clock_type::time_point initial, clock_type::duration period, const schedulable& scbl, ArgN&&... an) const { auto keepAlive = *this; auto target = std::make_shared(initial); auto activity = make_schedulable(scbl, keepAlive, std::forward(an)...); auto periodic = make_schedulable( activity, [keepAlive, target, period, activity](schedulable self) { // any recursion requests will be pushed to the scheduler queue recursion r(false); // call action activity(r.get_recurse()); // schedule next occurance (if the action took longer than 'period' target will be in the past) *target += period; self.schedule(*target); }); trace_activity().schedule_when_enter(*inner.get(), *target, periodic); inner->schedule(*target, periodic); trace_activity().schedule_when_return(*inner.get()); } namespace detail { template struct time_schedulable { typedef TimePoint time_point_type; time_schedulable(TimePoint when, schedulable a) : when(when) , what(std::move(a)) { } TimePoint when; schedulable what; }; // Sorts time_schedulable items in priority order sorted // on value of time_schedulable.when. Items with equal // values for when are sorted in fifo order. template class schedulable_queue { public: typedef time_schedulable item_type; typedef std::pair elem_type; typedef std::vector container_type; typedef const item_type& const_reference; private: struct compare_elem { bool operator()(const elem_type& lhs, const elem_type& rhs) const { if (lhs.first.when == rhs.first.when) { return lhs.second > rhs.second; } else { return lhs.first.when > rhs.first.when; } } }; typedef std::priority_queue< elem_type, container_type, compare_elem > queue_type; queue_type q; int64_t ordinal; public: schedulable_queue() : ordinal(0) { } const_reference top() const { return q.top().first; } void pop() { q.pop(); } bool empty() const { return q.empty(); } void push(const item_type& value) { q.push(elem_type(value, ordinal++)); } void push(item_type&& value) { q.push(elem_type(std::move(value), ordinal++)); } }; } } namespace rxsc=schedulers; } #include "schedulers/rx-currentthread.hpp" #include "schedulers/rx-runloop.hpp" #include "schedulers/rx-newthread.hpp" #include "schedulers/rx-eventloop.hpp" #include "schedulers/rx-immediate.hpp" #include "schedulers/rx-virtualtime.hpp" #include "schedulers/rx-sameworker.hpp" #endif