Skip to content

Instantly share code, notes, and snippets.

@novacrazy
Last active August 17, 2016 13:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save novacrazy/3e17f7e3c65ee83abf7224b703062975 to your computer and use it in GitHub Desktop.
Save novacrazy/3e17f7e3c65ee83abf7224b703062975 to your computer and use it in GitHub Desktop.
Recursive then implementation
#define UV_OVERLOAD_OSTREAM
#define UV_USE_BOOST_LOCKFREE
#include "uv++.hpp"
#include <iostream>
using namespace std;
int main() {
//Just use the default loop
auto loop = uv::default_loop();
/*
* The detached flag at the end means it will resolve all these in a separate thread, so the main thread can continute after
* the callbacks have been queued up.
* */
loop->fs()->stat( "../../src/main.cpp" )
.then( []( uv::fs::Stat result ) {
cout << result << endl;
} )
.then( [] {
cout << "Hello, World!" << endl;
return 10;
} )
.then( []( int i ) {
assert( i == 10 );
cout << "this is fun." << endl;
} )
.then( [] {
cout << "What should I do next?" << endl;
}, uv::uv_launch::detached );
//Start the event loop
loop->start();
return 0;
}
//
// Created by Aaron on 8/17/2016.
//
#ifndef UV_THEN_DETAIL_HPP
#define UV_THEN_DETAIL_HPP
#include "../defines.hpp"
#include "function_traits.hpp"
#include <future>
#include <memory>
/*
* The `then` function implemented below is designed to be very similar to JavaScript's Promise.then in functionality.
*
* You provide it with a promise or future/shared_future object, and it will invoke a callback with the result of that promise or future.
*
* Additionally, it will resolve recursive futures. As in, if the original future resolves to another future, and so forth.
*
* It will also resolve futures/promises returned by the callback, so the future returned by `then` will always resolve to a non-future type.
*
* Basically, you can layer up whatever you want and it'll resolve them all.
* */
namespace uv {
namespace detail {
using namespace std;
/*
* The default launch policy of std::async is a combination of the std::launch flags,
* allowing it to choose whatever policy it wants depending on the system.
* */
constexpr launch default_policy = launch::deferred | launch::async;
/*
* This is a special launch type where it is guarenteed to spawn a new thread to run the task
* asynchronously. It will not wait on any calls to .get() on the resulting future.
*
* 4 was chosen because deferred and async are usually 1 and 2, so 4 is the next power of two,
* though it doesn't matter since this is a type-safe enum class anyway.
* */
enum class uv_launch {
detached = 4
};
//////////
/*
* Forward declarations and default arguments for standard then overloads
* */
template <typename T, typename Functor>
UV_DECLTYPE_AUTO then( future<T> &, Functor, launch = default_policy );
template <typename T, typename Functor>
UV_DECLTYPE_AUTO then( shared_future<T>, Functor, launch = default_policy );
template <typename T, typename Functor>
UV_DECLTYPE_AUTO then( future<T> &&, Functor, launch = default_policy );
template <typename T, typename Functor>
UV_DECLTYPE_AUTO then( promise<T> &, Functor, launch = default_policy );
//////////
/*
* Forward declarations (without default arguments) for detached then overloads.
*
* There are no default overloads because these will ONLY be called if the uv_launch::detached
* flag is given, ensuring these overloads are called instead of the above.
* */
//TODO: Implement these
template <typename T, typename Functor>
UV_DECLTYPE_AUTO then( future<T> &, Functor, uv_launch );
template <typename T, typename Functor>
UV_DECLTYPE_AUTO then( shared_future<T>, Functor, uv_launch );
template <typename T, typename Functor>
UV_DECLTYPE_AUTO then( future<T> &&, Functor, uv_launch );
template <typename T, typename Functor>
UV_DECLTYPE_AUTO then( promise<T> &, Functor, uv_launch );
//////////
template <typename... Args>
UV_DECLTYPE_AUTO then2( Args... );
//////////
/*
* recursive_get:
*
* This structure will continually call "get" on any future object returned by another future,
* recursively, basically resolving the entire chain at once
* */
/*
* If K is not a future type, just return k.get()
* */
template <typename K>
struct recursive_get {
inline static UV_DECLTYPE_AUTO get( future<K> &&k ) {
return k.get();
}
/*
* Overload for shared_future, which can be passed by value if needed
* */
inline static UV_DECLTYPE_AUTO get( shared_future<K> k ) {
return k.get();
}
/*
* Overload for promises
* */
inline static UV_DECLTYPE_AUTO get( promise<K> &&k ) {
return recursive_get<K>::get( k.get_future());
}
};
/*
* If K was a future type, call recursive_get again on the result of k.get()
* */
template <typename P>
struct recursive_get<future<P>> {
/*
* Overload for a future resolving to a future
* */
inline static UV_DECLTYPE_AUTO get( future<future<P>> &&k ) {
return recursive_get<P>::get( k.get());
}
/*
* Overload for a shared_future resolving to a future
* */
inline static UV_DECLTYPE_AUTO get( shared_future<future<P>> k ) {
return recursive_get<P>::get( k.get());
}
};
/*
* If K was a shared_future type, call recursive_get again on the result of k.get()
* */
template <typename P>
struct recursive_get<shared_future<P>> {
/*
* Overload for future resolving to a shared_future
* */
inline static UV_DECLTYPE_AUTO get( future<shared_future<P>> &&k ) {
return recursive_get<P>::get( k.get());
}
/*
* Overload for shared_future resolving to another shared_future
* */
inline static UV_DECLTYPE_AUTO get( shared_future<shared_future<P>> k ) {
return recursive_get<P>::get( k.get());
}
};
//////////
/*
* optional_get
*
* This structure will recursively call get() only if the type is a future,
* otherwise it just forwards the value through like it wasn't there
* */
/*
* The is the raw version that just passes the value through like it wasn't there
* */
template <typename T>
struct optional_get {
/*
* No special forwarding or moves are done by this, because the compiler should
* optimize away any copies, given the sheer simplicity of this. I mean, it's basically the
* epitome of the identity function, especially with the decltype(auto) allowing it to choose
* the best return type for perfectly forwarding it.
* */
inline static UV_DECLTYPE_AUTO get( T t ) {
return t;
}
};
/*
* Specialization for future
* */
template <typename R>
struct optional_get<future<R>> {
inline static UV_DECLTYPE_AUTO get( future<R> &&r ) {
return recursive_get<R>::get( move( r ));
}
};
/*
* Specialization for shared_future
* */
template <typename R>
struct optional_get<shared_future<R>> {
inline static UV_DECLTYPE_AUTO get( shared_future<R> r ) {
return recursive_get<R>::get( r );
}
};
//////////
/*
* then_invoke_helper structure
*
* This just takes care of the invocation of the callback, forwarding arguments and so forth and
* taking care of any returned futures
* */
template <typename Functor, typename R = fn_result_of <Functor>>
struct then_invoke_helper {
template <typename... Args>
inline static UV_DECLTYPE_AUTO invoke( Functor f, Args... args ) {
return optional_get<R>::get( f( forward<Args>( args )... ));
}
};
/*
* Specialization of then_invoke_helper for void callbacks.
* */
template <typename Functor>
struct then_invoke_helper<Functor, void> {
template <typename... Args>
inline static void invoke( Functor f, Args... args ) {
f( forward<Args>( args )... );
}
};
//////////
/*
* then_helper structure
*
* This takes care of resolving the first given future, and optionally passing the
* resulting value to the callback function.
* */
template <typename T, typename Functor>
struct then_helper {
inline static UV_DECLTYPE_AUTO dispatch( future<T> &&s, Functor f ) {
return then_invoke_helper<Functor>::invoke( f, recursive_get<T>::get( move( s )));
}
inline static UV_DECLTYPE_AUTO dispatch( shared_future<T> s, Functor f ) {
return then_invoke_helper<Functor>::invoke( f, recursive_get<T>::get( s ));
}
};
template <typename Functor>
struct then_helper<void, Functor> {
inline static UV_DECLTYPE_AUTO dispatch( future<void> &&s, Functor f ) {
s.get();
return then_invoke_helper<Functor>::invoke( f );
}
inline static UV_DECLTYPE_AUTO dispatch( shared_future<void> s, Functor f ) {
s.get();
return then_invoke_helper<Functor>::invoke( f );
}
};
//////////
/*
* then function
*
* This is the overload of then that resolves futures and promises and invokes a callback with the
* resulting value. It uses the standard std::async for asynchronous invocation.
* */
/*
* Overload for simple futures
* */
template <typename T, typename Functor>
inline UV_DECLTYPE_AUTO then( future<T> &&s, Functor f, launch policy ) {
return async( policy, [policy, f]( future <T> &&s2 ) {
return then_helper<T, Functor>::dispatch( move( s2 ), f );
}, move( s ));
};
/*
* Overload for futures by references. It will take ownership and move the future,
* calling the above overload.
* */
template <typename T, typename Functor>
inline UV_DECLTYPE_AUTO then( future<T> &s, Functor f, launch policy ) {
return then( move( s ), f, policy );
};
/*
* Overload for shared_futures, passed by value because they can be copied.
* It's similar to the first overload, but can capture the shared_future in the lambda.
* */
template <typename T, typename Functor>
inline UV_DECLTYPE_AUTO then( shared_future<T> s, Functor f, launch policy ) {
return async( policy, [policy, s, f]() {
return then_helper<T, Functor>::dispatch( s, f );
} );
};
/*
* Overload for promise references. The promise is NOT moved, but rather the future is acquired
* from it and the promise is left intact, so it can be used elsewhere.
* */
template <typename T, typename Functor>
inline UV_DECLTYPE_AUTO then( promise<T> &s, Functor f, launch policy ) {
return then( s.get_future(), f, policy );
};
//////////
/*
* detached_then_helper structure
*
* These provide a function for catching exceptions and forwarding the result of the callbacks to the promise.
*
* It also accounts for void callbacks which don't return anything.
* */
template <typename T>
struct detached_then_helper {
template <typename Functor, typename K>
static inline void dispatch( promise<T> &p, future<K> &&s, Functor f ) {
try {
p.set_value( then_helper<K, Functor>::dispatch( move( s ), f ));
} catch( ... ) {
p.set_exception( current_exception());
}
}
template <typename Functor, typename K>
static inline void dispatch( promise<T> &p, shared_future<K> s, Functor f ) {
try {
p.set_value( then_helper<K, Functor>::dispatch( s, f ));
} catch( ... ) {
p.set_exception( current_exception());
}
}
};
/*
* Specialization for void Functors
* */
template <>
struct detached_then_helper<void> {
template <typename Functor, typename K>
static inline void dispatch( promise<void> &p, future<K> &&s, Functor f ) {
try {
then_helper<K, Functor>::dispatch( move( s ), f );
p.set_value();
} catch( ... ) {
p.set_exception( current_exception());
}
}
template <typename Functor, typename K>
static inline void dispatch( promise<void> &p, shared_future<K> s, Functor f ) {
try {
then_helper<K, Functor>::dispatch( s, f );
p.set_value();
} catch( ... ) {
p.set_exception( current_exception());
}
}
};
//////////
/*
* then function with detached launch flag.
*
* These are the same as the normal then functions, but launch the future resolution in a new thread
* to immediately wait on them and execute the callbacks. That way the future destructors don't block the main thread.
* */
/*
* Overload for simple futures
* */
template <typename T, typename Functor>
inline UV_DECLTYPE_AUTO then( future<T> &&s, Functor f, uv_launch policy ) {
//I don't really like having to do this, but I don't feel like rewriting almost all the recursive template logic above
typedef decltype( then_helper<T, Functor>::dispatch( move( s ), f )) P;
//This is here because these templates are so complicated it's confused my IDE, so I kind of have to hint it's a type
typedef promise<P> promiseP;
assert( policy == uv_launch::detached );
/*
* A shared pointer is used to keep the shared state of the future alive in both threads until it's resolved
* */
auto p = make_shared<promiseP>();
thread( [p, f]( future<T> &&s2 ) {
detached_then_helper<P>::dispatch( *p, move( s2 ), f );
}, move( s )).detach();
return p->get_future();
};
/*
* Overload for futures by references. It will take ownership and move the future,
* calling the above overload.
* */
template <typename T, typename Functor>
inline UV_DECLTYPE_AUTO then( future<T> &s, Functor f, uv_launch policy ) {
return then( move( s ), f, policy );
};
/*
* Overload for shared_futures, passed by value because they can be copied.
* It's similar to the first overload, but can capture the shared_future in the lambda.
* */
template <typename T, typename Functor>
inline UV_DECLTYPE_AUTO then( shared_future<T> s, Functor f, uv_launch policy ) {
//I don't really like having to do this, but I don't feel like rewriting almost all the recursive template logic above
typedef decltype( then_helper<T, Functor>::dispatch( s, f )) P;
//This is here because these templates are so complicated it's confused my IDE, so I kind of have to hint it's a type
typedef promise<P> promiseP;
assert( policy == uv_launch::detached );
/*
* A shared pointer is used to keep the shared state of the future alive in both threads until it's resolved
* */
auto p = make_shared<promiseP>();
thread( [s, p, f] {
detached_then_helper<P>::dispatch( *p, s, f );
} ).detach();
return p->get_future();
};
/*
* Overload for promise references. The promise is NOT moved, but rather the future is acquired
* from it and the promise is left intact, so it can be used elsewhere.
* */
template <typename T, typename Functor>
inline UV_DECLTYPE_AUTO then( promise<T> &s, Functor f, uv_launch policy ) {
return then( s.get_future(), f, policy );
};
//////////
/*
* Just a little trick to get the type of a future when it's not known beforehand.
*
* Used in then2 below.
* */
template <typename K>
struct get_future_type {
//No type is given here
};
template <typename T>
struct get_future_type<future<T>> {
typedef T type;
};
//////////
/*
* This is a future object functionally equivalent to std::future,
* but with a .then function to chain together many futures.
* */
template <typename T>
class ThenableFuture : public future<T> {
public:
constexpr ThenableFuture() noexcept : future<T>() {}
inline ThenableFuture( future<T> &&f ) noexcept : future<T>( move( f )) {}
inline ThenableFuture( ThenableFuture &&f ) noexcept : future<T>( move( f )) {}
ThenableFuture( const ThenableFuture & ) = delete;
ThenableFuture &operator=( const ThenableFuture & ) = delete;
/*
* These operator overloads allow ThenableFuture to be used as a normal future rather easily
* */
inline operator future<T> &() {
return *static_cast<future <T> *>(this);
}
inline operator future<T> &&() {
return move( *static_cast<future <T> *>(this));
}
template <typename... Args>
inline UV_DECLTYPE_AUTO then( Args... args ) {
//NOTE: if `then` is used instead of `then2`, the namespace should be explicitely given, or this function will recurse
return then2( move( *this ), std::forward<Args>( args )... );
}
};
//////////
/*
* then2 is a variation of then that returns a ThenableFuture instead of a normal future
* */
template <typename... Args>
inline UV_DECLTYPE_AUTO then2( Args... args ) {
auto &&k = then( std::forward<Args>( args )... );
return ThenableFuture<typename get_future_type<typename remove_reference<decltype( k )>::type>::type>( move( k ));
}
}
using detail::then;
using detail::then2;
using detail::uv_launch;
}
#endif //UV_THEN_DETAIL_HPP
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment