/* * Copyright (C) 2015 Naios * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef _CONTINUABLE_H_ #define _CONTINUABLE_H_ // debug #include #include #include #include "Callback.h" template class Continuable; namespace detail { /// Trait to identify continuable types template struct is_continuable : std::false_type { }; template struct is_continuable> : std::true_type { }; /// Creates an empty callback. template struct create_empty_callback; template struct create_empty_callback&&)>> { static auto create() -> Callback { return [](Args&&...) { }; } }; template struct unary_chainer_t; template struct multiple_when_all_chainer_t; template class multiple_result_storage_t; template struct multiple_result_storage_invoker_t; /// Functional traits forward declaration. template struct functional_traits; } // detail /// A continuable provides useful methods to react on the result of callbacks /// and allows to chain multiple callback calls to a chain. template class Continuable { // Make all templates of Continuable to a friend. template friend class Continuable; template friend struct detail::multiple_result_storage_invoker_t; public: typedef Callback<_ATy...> CallbackFunction; typedef std::function&&)> ForwardFunction; private: /// Functional which expects a callback that is inserted from the Continuable /// to chain everything together ForwardFunction _callback_insert; /// Was the continuable released (invoked or transfered ownership) already? bool _released; template void invoke(_CTy&& callback) { if (!_released) { // Invalidate this _released = true; // Invoke this _callback_insert(std::forward<_CTy>(callback)); } } public: /// Deleted copy construct Continuable(Continuable const&) = delete; /// Move construct Continuable(Continuable&& right) : _callback_insert(std::move(right._callback_insert)), _released(right._released) { right._released = true; } // Construct through a ForwardFunction template Continuable(_FTy&& callback_insert) : _callback_insert(std::forward<_FTy>(callback_insert)), _released(false) { } template Continuable(_FTy&& callback_insert, Continuable<_RATy...>&& right) : _callback_insert(std::forward<_FTy>(callback_insert)), _released(right._released) { right._released = true; } /// Destructor which calls the dispatch chain if needed. ~Continuable() { // Dispatch everything. if (!_released) { // Set released to true to prevent multiple calls _released = true; // Invoke everything with an empty callback _callback_insert(detail::create_empty_callback::create()); } } /// Deleted copy assign Continuable& operator= (Continuable const&) = delete; /// Move construct assign Continuable& operator= (Continuable&& right) { _released = right._released; right._released = true; _callback_insert = std::move(right._callback_insert); return *this; } /// Waits for this continuable and invokes the given callback. template auto then(_CTy&& functional) -> typename detail::unary_chainer_t<_CTy, _ATy...>::continuable_t { static_assert(std::is_same, typename detail::unary_chainer_t<_CTy, _ATy...>::arguments_t>::value, "Given function signature isn't correct, for now it must match strictly!"); return typename detail::unary_chainer_t<_CTy, _ATy...>::continuable_t( [ corrected = detail::functional_traits<_ATy...>::correct(std::forward<_CTy>(functional)), callback = std::move(_callback_insert) ] (typename detail::unary_chainer_t<_CTy, _ATy...>::callback_t&& call_next) mutable { callback([corrected = std::move(corrected), call_next](_ATy&&... args) mutable { // Invoke the next callback corrected(std::forward<_ATy>(args)...).invoke(std::move(call_next)); }); }, std::move(*this)); } template auto all(_CTy&&... functionals) -> typename detail::multiple_when_all_chainer_t< fu::identity<_ATy...>, fu::identity<_CTy...> >::make_result::continuable_t { return then( detail::multiple_when_all_chainer_t< fu::identity<_ATy...>, fu::identity<_CTy...> >::make_when_all(std::forward<_CTy>(functionals)...)); } /// Placeholder template Continuable some(std::size_t const count, _CTy&&...) { return std::move(*this); } /// Placeholder template auto any(_CTy&&... functionals) -> Continuable // FIXME gcc build &-> decltype(some(1, std::declval<_CTy>()...)) { // Equivalent to invoke `some` with count 1. return some(1, std::forward<_CTy>(functionals)...); } /* /// Validates the Continuable inline Continuable Validate() { _released = false; return *this; } /// Invalidates the Continuable inline Continuable& Invalidate() { _released = true; return *this; } */ }; namespace detail { template struct ContinuableFactory; template struct ContinuableFactory<_RTy, ::fu::identity&&>> { template static auto CreateFrom(_FTy&& functional) -> Continuable<_ATy...> { return Continuable<_ATy...>( typename Continuable<_ATy...>::ForwardFunction(std::forward<_FTy>(functional))); } }; template using continuable_factory_t = ContinuableFactory< ::fu::return_type_of_t<_FTy>, ::fu::argument_type_of_t<_FTy>>; } /// Wraps a functional object which expects a r-value callback as argument into a continuable. /// The callable is invoked when the continuable shall continue. /// For example: /// make_continuable([](Callback&& callback) /// { /// /* Continue here */ /// callback(5); /// }); template inline auto make_continuable(_FTy&& functional) -> decltype(detail::continuable_factory_t<_FTy>::CreateFrom(std::declval<_FTy>())) { return detail::continuable_factory_t<_FTy>::CreateFrom(std::forward<_FTy>(functional)); } /// Creates an empty continuable. /// Can be used to start a chain with aggregate methods. /// empty_continuable() /// .all(...) /// .some(...) /// .any(...) inline auto make_continuable() -> Continuable<> { return make_continuable([](Callback<>&& callback) { callback(); }); } namespace detail { /// Helper trait for unary chains like `Continuable::then` template struct unary_chainer_t { // Corrected user given functional typedef decltype(detail::functional_traits<_ATy...>:: correct(std::declval::type>())) corrected_t; typedef fu::return_type_of_t continuable_t; typedef fu::argument_type_of_t arguments_t; typedef typename continuable_t::CallbackFunction callback_t; typedef fu::argument_type_of_t callback_arguments_t; }; template struct concat_identities; template struct concat_identities, fu::identity> { typedef fu::identity type; }; template struct identity_to_tuple; template struct identity_to_tuple> { typedef std::tuple type; }; template struct void_wrap_trait; /// Trait needed for functional_traits::remove_void_trait template struct void_wrap_trait> { template static std::function(_ATy...)> wrap(_CTy&& functional_) { return [functional = std::forward<_CTy>(functional_)](_ATy&&... args) { // Invoke the original callback functional(std::forward<_ATy>(args)...); // Return an empty continuable return make_continuable(); }; } }; /// Position wrapper class to pass ints as type template struct partial_result { static std::size_t const position = Position; typedef Tuple tuple; }; /// Continuable processing detail implementation template struct functional_traits { /// Wrap void returning functionals to returns an empty continuable. template static auto remove_void_trait(_CTy&& functional) -> typename std::enable_if< std::is_void< fu::return_type_of_t< typename std::decay<_CTy>::type > >::value, decltype( detail::void_wrap_trait< fu::argument_type_of_t< typename std::decay<_CTy>::type > >::wrap(std::declval<_CTy>()) ) >::type { return detail::void_wrap_trait< fu::argument_type_of_t< typename std::decay<_CTy>::type > >::wrap(std::forward<_CTy>(functional)); } /// Route continuable returning functionals through. template static auto remove_void_trait(_CTy&& functional) -> typename std::enable_if< !std::is_void< fu::return_type_of_t< typename std::decay<_CTy>::type > >::value, _CTy>::type { return std::forward<_CTy>(functional); } /// Wrap continuables into the continuable returning functional type. template static auto box_continuable_trait(_CTy&& continuable) -> typename std::enable_if< detail::is_continuable< typename std::decay<_CTy>::type >::value, std::function< typename std::decay<_CTy>::type(_ATy...) > >::type { static_assert(std::is_rvalue_reference<_CTy&&>::value, "Given continuable must be passed as r-value!"); // Trick C++11 lambda capture rules for non copyable but moveable continuables. // TODO Use the stack instead of heap variables. // std::shared_ptr::type> shared_continuable = // std::make_shared::type>(std::forward<_CTy>(continuable)); // Create a fake function which returns the value on invoke. return [continuable_ = std::forward<_CTy>(continuable)](_ATy&&...) mutable { return std::move(continuable_); }; } /// Route functionals through template inline static auto box_continuable_trait(_CTy&& continuable) -> typename std::enable_if< !detail::is_continuable< typename std::decay<_CTy>::type >::value, typename std::decay<_CTy>::type >::type { static_assert(std::is_rvalue_reference<_CTy&&>::value, "Given continuable must be passed as r-value!"); return std::forward<_CTy>(continuable); } /// Correct user given continuable functionals. /// Converts plan continuables to continuable retuning functions. /// Converts void return to empty continuable. template static auto correct(_CTy&& functional) -> decltype(remove_void_trait(box_continuable_trait(std::declval<_CTy>()))) { return remove_void_trait(box_continuable_trait(std::forward<_CTy>(functional))); } template struct multiple_result_maker; template struct multiple_result_maker, fu::identity> { typedef fu::identity arguments_t; typedef fu::identity partial_results_t; static std::size_t const size = Position; }; template struct multiple_result_maker : multiple_result_maker< Position + std::tuple_size< typename identity_to_tuple< typename unary_chainer_t::callback_arguments_t >::type >::value, typename concat_identities< Args, typename unary_chainer_t::callback_arguments_t >::type, typename concat_identities< Pack, fu::identity< partial_result< Position, typename identity_to_tuple< typename unary_chainer_t::callback_arguments_t >::type > > >::type, Rest... > { }; template using result_maker_of_t = multiple_result_maker<0, fu::identity<>, fu::identity<>, Args...>; }; template class multiple_result_storage_t, fu::identity<_RTy...>, fu::identity<_PTy...>> { template friend struct multiple_result_storage_invoker_t; std::size_t partitions_left; std::tuple<_RTy...> result; Callback<_RTy...> callback; std::mutex lock; public: multiple_result_storage_t(std::size_t partitions, Callback<_RTy...> callback_) : partitions_left(partitions), callback(callback_) { } void try_invoke() { // TODO Improve the lock here std::lock_guard guard(lock); { // Never call callbacks twice! // assert(partitions_left); // If all partitions have completed invoke the final callback. if (--partitions_left == 0) { fu::invoke_from_tuple(std::move(callback), std::move(result)); } } } }; template struct multiple_result_storage_invoker_t, fu::identity<_RTy...>, fu::identity<_PTy...>> { template using move_position_to = multiple_result_storage_invoker_t, fu::identity<_RTy...>, fu::identity<_PTy...>>; template inline static void partial_set(Tuple& result, Current&& current) { // Store a single callback result in the tuple std::get(result) = std::forward(current); } template inline static void partial_set(Tuple& result, Current&& current, Rest&&... rest) { // Set the result... partial_set(result, std::forward(current)); // ...and continue with the next parameter. move_position_to::partial_set(result, std::forward(rest)...); } // Do nothing when trying to store empty packs... inline static void store(std::tuple<_RTy...>& /*result*/) { } // Store the args in the result tuple template inline static void store(std::tuple<_RTy...>& result, Args&&... args) { partial_set(result, std::forward(args)...); } template static void invoke(std::shared_ptr, fu::identity<_RTy...>, fu::identity<_PTy...>>> storage, Continuable&& continuable) { // Invoke the continuable continuable.invoke([storage](Args&&... args) { // Route its result to the cache. store(storage->result, std::forward(args)...); // Try to invoke the final callback. storage->try_invoke(); }); } }; template struct multiple_when_all_chainer_t_make_result; template struct multiple_when_all_chainer_t_make_result, fu::identity<_RTy...>, fu::identity<_PTy...>> { typedef Continuable<_RTy...> continuable_t; typedef std::function return_t; typedef functional_traits<_ATy...> traits_t; typedef multiple_result_storage_t, fu::identity<_RTy...>, fu::identity<_PTy...>> ResultStorage; template using invoker_at = multiple_result_storage_invoker_t, fu::identity<_RTy...>, fu::identity<_PTy...>>; typedef std::shared_ptr shared_result_t; typedef std::tuple<_ATy...> shared_args_t; template struct distributor; template struct distributor, Stack...> { /// Real function invocation template inline static void invoke(shared_result_t storage, Arguments&& args, _CTy&& current) { // Invoke the continuable from the result storage invoker_at::invoke( storage, fu::invoke_from_tuple( traits_t::correct(std::forward<_CTy>(current)), std::forward(args))); } /// Invoke and pass recursive to itself template inline static void invoke(shared_result_t storage, Arguments&& args, _CTy&& current, Rest&&... rest) { // Invoke the current continuable... invoke(storage, std::forward(args), std::forward<_CTy>(current)); // And continue with the next distributor::invoke(storage, std::forward(args), std::forward(rest)...); } }; template struct sequenced_invoke; template struct sequenced_invoke> { template inline static void invoke(shared_result_t result, Arguments&& arguments, TupleFunctional&& functional) { // Invoke the distributor which invokes all given continuables. distributor<_PTy...>::invoke( result, std::forward(arguments), std::get(std::forward(functional))...); } }; /// Creates a faked function which invokes all sub continuables template static return_t create(_CTy&&... functionals) { // C++11 workaround for move semantics of non copyable types // TODO Use the stack instead of heap variables. auto shared_functionals = std::make_shared>( std::make_tuple(std::forward<_CTy>(functionals)...) ); return [=](_ATy&&... args) mutable { // TODO Use the stack instead of heap variables. auto shared_args = std::make_shared>( std::forward_as_tuple(std::forward<_ATy>(args)...)); // Fake continuable which wraps all continuables together return make_continuable([=](Callback<_RTy...>&& callback) mutable { sequenced_invoke< fu::sequence_of_t< sizeof...(_CTy) > >::invoke( std::make_shared(sizeof...(_CTy), callback), std::move(*shared_args), std::move(*shared_functionals) ); }); }; } }; /// Helper trait for multiple chains like `Continuable::all` template struct multiple_when_all_chainer_t, fu::identity<_CTy...>> { typedef typename functional_traits<_ATy...>::template result_maker_of_t<_CTy...> result_maker; typedef typename result_maker::arguments_t arguments_t; typedef typename result_maker::partial_results_t partial_results_t; static std::size_t const size = result_maker::size; typedef multiple_when_all_chainer_t_make_result, arguments_t, partial_results_t> make_result; // Creates one continuable from multiple ones static auto make_when_all(_CTy&&... args) -> typename make_result::return_t { return make_result::create(std::forward<_CTy>(args)...); } }; } #endif // _CONTINUABLE_H_