mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-26 01:51:41 +00:00
wpiutil: Change uv::AsyncFunction to use promise/future.
This allows the called function to pass along the promise to another asynchronous callback. To avoid memory allocations, add a home-rolled, simplified, non-allocating version of std::promise and std::future as wpi::promise and wpi::future.
This commit is contained in:
907
wpiutil/src/main/native/include/wpi/future.h
Normal file
907
wpiutil/src/main/native/include/wpi/future.h
Normal file
@@ -0,0 +1,907 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef WPIUTIL_WPI_FUTURE_H_
|
||||
#define WPIUTIL_WPI_FUTURE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "wpi/condition_variable.h"
|
||||
#include "wpi/mutex.h"
|
||||
|
||||
namespace wpi {
|
||||
|
||||
template <typename T>
|
||||
class PromiseFactory;
|
||||
|
||||
template <typename T>
|
||||
class future;
|
||||
|
||||
template <typename T>
|
||||
class promise;
|
||||
template <>
|
||||
class promise<void>;
|
||||
|
||||
namespace detail {
|
||||
|
||||
class PromiseFactoryBase {
|
||||
public:
|
||||
~PromiseFactoryBase();
|
||||
|
||||
bool IsActive() const { return m_active; }
|
||||
|
||||
wpi::mutex& GetResultMutex() { return m_resultMutex; }
|
||||
|
||||
void Notify() { m_resultCv.notify_all(); }
|
||||
|
||||
// must be called with locked lock == ResultMutex
|
||||
void Wait(std::unique_lock<wpi::mutex>& lock) { m_resultCv.wait(lock); }
|
||||
|
||||
// returns false if timeout reached
|
||||
template <class Clock, class Duration>
|
||||
bool WaitUntil(std::unique_lock<wpi::mutex>& lock,
|
||||
const std::chrono::time_point<Clock, Duration>& timeout_time) {
|
||||
return m_resultCv.wait_until(lock, timeout_time) ==
|
||||
std::cv_status::no_timeout;
|
||||
}
|
||||
|
||||
void IgnoreResult(uint64_t request);
|
||||
|
||||
uint64_t CreateRequest();
|
||||
|
||||
// returns true if request was pending
|
||||
// must be called with ResultMutex held
|
||||
bool EraseRequest(uint64_t request);
|
||||
|
||||
// same as doing CreateRequest() followed by EraseRequest()
|
||||
// must be called with ResultMutex held
|
||||
uint64_t CreateErasedRequest() { return ++m_uid; }
|
||||
|
||||
private:
|
||||
wpi::mutex m_resultMutex;
|
||||
std::atomic_bool m_active{true};
|
||||
wpi::condition_variable m_resultCv;
|
||||
|
||||
uint64_t m_uid = 0;
|
||||
std::vector<uint64_t> m_requests;
|
||||
};
|
||||
|
||||
template <typename To, typename From>
|
||||
struct FutureThen {
|
||||
template <typename F>
|
||||
static future<To> Create(PromiseFactory<From>& fromFactory, uint64_t request,
|
||||
PromiseFactory<To>& factory, F&& func);
|
||||
};
|
||||
|
||||
template <typename From>
|
||||
struct FutureThen<void, From> {
|
||||
template <typename F>
|
||||
static future<void> Create(PromiseFactory<From>& fromFactory,
|
||||
uint64_t request, PromiseFactory<void>& factory,
|
||||
F&& func);
|
||||
};
|
||||
|
||||
template <typename To>
|
||||
struct FutureThen<To, void> {
|
||||
template <typename F>
|
||||
static future<To> Create(PromiseFactory<void>& fromFactory, uint64_t request,
|
||||
PromiseFactory<To>& factory, F&& func);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct FutureThen<void, void> {
|
||||
template <typename F>
|
||||
static future<void> Create(PromiseFactory<void>& fromFactory,
|
||||
uint64_t request, PromiseFactory<void>& factory,
|
||||
F&& func);
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
* A promise factory for lightweight futures.
|
||||
*
|
||||
* The lifetime of the factory must be ensured to be longer than any futures
|
||||
* it creates.
|
||||
*
|
||||
* Use CreateRequest() to create the future request id, and then CreateFuture()
|
||||
* and CreatePromise() to create future and promise objects. A promise should
|
||||
* only be created once for any given request id.
|
||||
*
|
||||
* @tparam T the "return" type of the promise/future
|
||||
*/
|
||||
template <typename T>
|
||||
class PromiseFactory final : public detail::PromiseFactoryBase {
|
||||
friend class future<T>;
|
||||
|
||||
public:
|
||||
using detail::PromiseFactoryBase::Notify;
|
||||
using ThenFunction = std::function<void(uint64_t, T)>;
|
||||
|
||||
/**
|
||||
* Creates a future.
|
||||
*
|
||||
* @param request the request id returned by CreateRequest()
|
||||
* @return the future
|
||||
*/
|
||||
future<T> CreateFuture(uint64_t request);
|
||||
|
||||
/**
|
||||
* Creates a future and makes it immediately ready.
|
||||
*
|
||||
* @return the future
|
||||
*/
|
||||
future<T> MakeReadyFuture(T&& value);
|
||||
|
||||
/**
|
||||
* Creates a promise.
|
||||
*
|
||||
* @param request the request id returned by CreateRequest()
|
||||
* @return the promise
|
||||
*/
|
||||
promise<T> CreatePromise(uint64_t request);
|
||||
|
||||
/**
|
||||
* Sets a value directly for a future without creating a promise object.
|
||||
* Identical to `CreatePromise(request).set_value(value)`.
|
||||
*
|
||||
* @param request request id, as returned by CreateRequest()
|
||||
* @param value lvalue
|
||||
*/
|
||||
void SetValue(uint64_t request, const T& value);
|
||||
|
||||
/**
|
||||
* Sets a value directly for a future without creating a promise object.
|
||||
* Identical to `CreatePromise(request).set_value(value)`.
|
||||
*
|
||||
* @param request request id, as returned by CreateRequest()
|
||||
* @param value rvalue
|
||||
*/
|
||||
void SetValue(uint64_t request, T&& value);
|
||||
|
||||
void SetThen(uint64_t request, uint64_t outRequest, ThenFunction func);
|
||||
|
||||
bool IsReady(uint64_t request) noexcept;
|
||||
T GetResult(uint64_t request);
|
||||
void WaitResult(uint64_t request);
|
||||
template <class Clock, class Duration>
|
||||
bool WaitResultUntil(
|
||||
uint64_t request,
|
||||
const std::chrono::time_point<Clock, Duration>& timeout_time);
|
||||
|
||||
static PromiseFactory& GetInstance();
|
||||
|
||||
private:
|
||||
struct Then {
|
||||
Then(uint64_t request_, uint64_t outRequest_, ThenFunction func_)
|
||||
: request(request_), outRequest(outRequest_), func(std::move(func_)) {}
|
||||
uint64_t request;
|
||||
uint64_t outRequest;
|
||||
ThenFunction func;
|
||||
};
|
||||
|
||||
std::vector<Then> m_thens;
|
||||
std::vector<std::pair<uint64_t, T>> m_results;
|
||||
};
|
||||
|
||||
/**
|
||||
* Explicit specialization for PromiseFactory<void>.
|
||||
*/
|
||||
template <>
|
||||
class PromiseFactory<void> final : public detail::PromiseFactoryBase {
|
||||
friend class future<void>;
|
||||
|
||||
public:
|
||||
using detail::PromiseFactoryBase::Notify;
|
||||
using ThenFunction = std::function<void(uint64_t)>;
|
||||
|
||||
/**
|
||||
* Creates a future.
|
||||
*
|
||||
* @param request the request id returned by CreateRequest()
|
||||
* @return std::pair of the future and the request id
|
||||
*/
|
||||
future<void> CreateFuture(uint64_t request);
|
||||
|
||||
/**
|
||||
* Creates a future and makes it immediately ready.
|
||||
*
|
||||
* @return the future
|
||||
*/
|
||||
future<void> MakeReadyFuture();
|
||||
|
||||
/**
|
||||
* Creates a promise.
|
||||
*
|
||||
* @param request the request id returned by CreateRequest()
|
||||
* @return the promise
|
||||
*/
|
||||
promise<void> CreatePromise(uint64_t request);
|
||||
|
||||
/**
|
||||
* Sets a value directly for a future without creating a promise object.
|
||||
* Identical to `promise(factory, request).set_value()`.
|
||||
*
|
||||
* @param request request id, as returned by CreateRequest()
|
||||
*/
|
||||
void SetValue(uint64_t request);
|
||||
|
||||
void SetThen(uint64_t request, uint64_t outRequest, ThenFunction func);
|
||||
|
||||
bool IsReady(uint64_t request) noexcept;
|
||||
void GetResult(uint64_t request);
|
||||
void WaitResult(uint64_t request);
|
||||
template <class Clock, class Duration>
|
||||
bool WaitResultUntil(
|
||||
uint64_t request,
|
||||
const std::chrono::time_point<Clock, Duration>& timeout_time);
|
||||
|
||||
static PromiseFactory& GetInstance();
|
||||
|
||||
private:
|
||||
struct Then {
|
||||
Then(uint64_t request_, uint64_t outRequest_, ThenFunction func_)
|
||||
: request(request_), outRequest(outRequest_), func(std::move(func_)) {}
|
||||
uint64_t request;
|
||||
uint64_t outRequest;
|
||||
ThenFunction func;
|
||||
};
|
||||
|
||||
std::vector<Then> m_thens;
|
||||
std::vector<uint64_t> m_results;
|
||||
};
|
||||
|
||||
/**
|
||||
* A lightweight version of std::future.
|
||||
*
|
||||
* Use either promise::get_future() or PromiseFactory::CreateFuture() to create.
|
||||
*
|
||||
* @tparam T the "return" type
|
||||
*/
|
||||
template <typename T>
|
||||
class future final {
|
||||
friend class PromiseFactory<T>;
|
||||
friend class promise<T>;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructs an empty (invalid) future.
|
||||
*/
|
||||
future() noexcept = default;
|
||||
|
||||
future(future&& oth) noexcept {
|
||||
this->m_request = oth.m_request;
|
||||
this->m_promises = oth.m_promises;
|
||||
oth.m_request = 0;
|
||||
oth.m_promises = nullptr;
|
||||
}
|
||||
future(const future&) = delete;
|
||||
|
||||
template <typename R>
|
||||
future(future<R>&& oth) noexcept
|
||||
: future(oth.then([](R&& val) -> T { return val; })) {}
|
||||
|
||||
/**
|
||||
* Ignores the result of the future if it has not been retrieved.
|
||||
*/
|
||||
~future() {
|
||||
if (m_promises) m_promises->IgnoreResult(m_request);
|
||||
}
|
||||
|
||||
future& operator=(future&& oth) noexcept {
|
||||
this->m_request = oth.m_request;
|
||||
this->m_promises = oth.m_promises;
|
||||
oth.m_request = 0;
|
||||
oth.m_promises = nullptr;
|
||||
return *this;
|
||||
}
|
||||
future& operator=(const future&) = delete;
|
||||
|
||||
/**
|
||||
* Gets the value. Calls wait() if the value is not yet available.
|
||||
* Can only be called once. The future will be marked invalid after the call.
|
||||
*
|
||||
* @return The value provided by the corresponding promise.set_value().
|
||||
*/
|
||||
T get() {
|
||||
if (m_promises)
|
||||
return m_promises->GetResult(m_request);
|
||||
else
|
||||
return T();
|
||||
}
|
||||
|
||||
template <typename R, typename F>
|
||||
future<R> then(PromiseFactory<R>& factory, F&& func) {
|
||||
if (m_promises) {
|
||||
auto promises = m_promises;
|
||||
m_promises = nullptr;
|
||||
return detail::FutureThen<R, T>::Create(*promises, m_request, factory,
|
||||
func);
|
||||
} else {
|
||||
return future<R>();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F, typename R = typename std::result_of<F && (T &&)>::type>
|
||||
future<R> then(F&& func) {
|
||||
return then(PromiseFactory<R>::GetInstance(), std::forward<F>(func));
|
||||
}
|
||||
|
||||
bool is_ready() const noexcept {
|
||||
return m_promises && m_promises->IsReady(m_request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the future is valid.
|
||||
* A default-constructed future or one where get() has been called is invalid.
|
||||
*
|
||||
* @return True if valid
|
||||
*/
|
||||
bool valid() const noexcept { return m_promises; }
|
||||
|
||||
/**
|
||||
* Waits for the promise to provide a value.
|
||||
* Does not return until the value is available or the promise is destroyed
|
||||
* (in which case a default-constructed value is "returned").
|
||||
* If the value has already been provided, returns immediately.
|
||||
*/
|
||||
void wait() const {
|
||||
if (m_promises) m_promises->WaitResult(m_request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the promise to provide a value, or the specified time has been
|
||||
* reached.
|
||||
*
|
||||
* @return True if the promise provided a value, false if timed out.
|
||||
*/
|
||||
template <class Clock, class Duration>
|
||||
bool wait_until(
|
||||
const std::chrono::time_point<Clock, Duration>& timeout_time) const {
|
||||
return m_promises && m_promises->WaitResultUntil(m_request, timeout_time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the promise to provide a value, or the specified amount of time
|
||||
* has elapsed.
|
||||
*
|
||||
* @return True if the promise provided a value, false if timed out.
|
||||
*/
|
||||
template <class Rep, class Period>
|
||||
bool wait_for(
|
||||
const std::chrono::duration<Rep, Period>& timeout_duration) const {
|
||||
return wait_until(std::chrono::steady_clock::now() + timeout_duration);
|
||||
}
|
||||
|
||||
private:
|
||||
future(PromiseFactory<T>* promises, uint64_t request) noexcept
|
||||
: m_request(request), m_promises(promises) {}
|
||||
|
||||
uint64_t m_request = 0;
|
||||
PromiseFactory<T>* m_promises = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Explicit specialization for future<void>.
|
||||
*/
|
||||
template <>
|
||||
class future<void> final {
|
||||
friend class PromiseFactory<void>;
|
||||
friend class promise<void>;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructs an empty (invalid) future.
|
||||
*/
|
||||
future() noexcept = default;
|
||||
|
||||
future(future&& oth) noexcept {
|
||||
m_request = oth.m_request;
|
||||
m_promises = oth.m_promises;
|
||||
oth.m_request = 0;
|
||||
oth.m_promises = nullptr;
|
||||
}
|
||||
future(const future&) = delete;
|
||||
|
||||
/**
|
||||
* Ignores the result of the future if it has not been retrieved.
|
||||
*/
|
||||
~future() {
|
||||
if (m_promises) m_promises->IgnoreResult(m_request);
|
||||
}
|
||||
|
||||
future& operator=(future&& oth) noexcept {
|
||||
m_request = oth.m_request;
|
||||
m_promises = oth.m_promises;
|
||||
oth.m_request = 0;
|
||||
oth.m_promises = nullptr;
|
||||
return *this;
|
||||
}
|
||||
future& operator=(const future&) = delete;
|
||||
|
||||
/**
|
||||
* Gets the value. Calls wait() if the value is not yet available.
|
||||
* Can only be called once. The future will be marked invalid after the call.
|
||||
*/
|
||||
void get() {
|
||||
if (m_promises) m_promises->GetResult(m_request);
|
||||
}
|
||||
|
||||
template <typename R, typename F>
|
||||
future<R> then(PromiseFactory<R>& factory, F&& func) {
|
||||
if (m_promises) {
|
||||
auto promises = m_promises;
|
||||
m_promises = nullptr;
|
||||
return detail::FutureThen<R, void>::Create(*promises, m_request, factory,
|
||||
func);
|
||||
} else {
|
||||
return future<R>();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F, typename R = typename std::result_of<F && ()>::type>
|
||||
future<R> then(F&& func) {
|
||||
return then(PromiseFactory<R>::GetInstance(), std::forward<F>(func));
|
||||
}
|
||||
|
||||
bool is_ready() const noexcept {
|
||||
return m_promises && m_promises->IsReady(m_request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the future is valid.
|
||||
* A default-constructed future or one where get() has been called is invalid.
|
||||
*
|
||||
* @return True if valid
|
||||
*/
|
||||
bool valid() const noexcept { return m_promises; }
|
||||
|
||||
/**
|
||||
* Waits for the promise to provide a value.
|
||||
* Does not return until the value is available or the promise is destroyed
|
||||
* If the value has already been provided, returns immediately.
|
||||
*/
|
||||
void wait() const {
|
||||
if (m_promises) m_promises->WaitResult(m_request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the promise to provide a value, or the specified time has been
|
||||
* reached.
|
||||
*
|
||||
* @return True if the promise provided a value, false if timed out.
|
||||
*/
|
||||
template <class Clock, class Duration>
|
||||
bool wait_until(
|
||||
const std::chrono::time_point<Clock, Duration>& timeout_time) const {
|
||||
return m_promises && m_promises->WaitResultUntil(m_request, timeout_time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the promise to provide a value, or the specified amount of time
|
||||
* has elapsed.
|
||||
*
|
||||
* @return True if the promise provided a value, false if timed out.
|
||||
*/
|
||||
template <class Rep, class Period>
|
||||
bool wait_for(
|
||||
const std::chrono::duration<Rep, Period>& timeout_duration) const {
|
||||
return wait_until(std::chrono::steady_clock::now() + timeout_duration);
|
||||
}
|
||||
|
||||
private:
|
||||
future(PromiseFactory<void>* promises, uint64_t request) noexcept
|
||||
: m_request(request), m_promises(promises) {}
|
||||
|
||||
uint64_t m_request = 0;
|
||||
PromiseFactory<void>* m_promises = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* A lightweight version of std::promise.
|
||||
*
|
||||
* Use PromiseFactory::CreatePromise() to create.
|
||||
*
|
||||
* @tparam T the "return" type
|
||||
*/
|
||||
template <typename T>
|
||||
class promise final {
|
||||
friend class PromiseFactory<T>;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructs an empty promise.
|
||||
*/
|
||||
promise() : m_promises(&PromiseFactory<T>::GetInstance()) {
|
||||
m_request = m_promises->CreateRequest();
|
||||
}
|
||||
|
||||
promise(promise&& oth) noexcept
|
||||
: m_request(oth.m_request), m_promises(oth.m_promises) {
|
||||
oth.m_request = 0;
|
||||
oth.m_promises = nullptr;
|
||||
}
|
||||
|
||||
promise(const promise&) = delete;
|
||||
|
||||
/**
|
||||
* Sets the promised value to a default-constructed T if not already set.
|
||||
*/
|
||||
~promise() {
|
||||
if (m_promises) m_promises->SetValue(m_request, T());
|
||||
}
|
||||
|
||||
promise& operator=(promise&& oth) noexcept {
|
||||
m_request = oth.m_request;
|
||||
m_promises = oth.m_promises;
|
||||
oth.m_request = 0;
|
||||
oth.m_promises = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
promise& operator=(const promise&) = delete;
|
||||
|
||||
/**
|
||||
* Swaps this promise with another one.
|
||||
*/
|
||||
void swap(promise& oth) noexcept {
|
||||
std::swap(m_request, oth.m_request);
|
||||
std::swap(m_promises, oth.m_promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a future for this promise.
|
||||
*
|
||||
* @return The future
|
||||
*/
|
||||
future<T> get_future() noexcept { return future<T>(m_promises, m_request); }
|
||||
|
||||
/**
|
||||
* Sets the promised value.
|
||||
* Only effective once (subsequent calls will be ignored).
|
||||
*
|
||||
* @param value The value to provide to the waiting future
|
||||
*/
|
||||
void set_value(const T& value) {
|
||||
if (m_promises) m_promises->SetValue(m_request, value);
|
||||
m_promises = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the promised value.
|
||||
* Only effective once (subsequent calls will be ignored).
|
||||
*
|
||||
* @param value The value to provide to the waiting future
|
||||
*/
|
||||
void set_value(T&& value) {
|
||||
if (m_promises) m_promises->SetValue(m_request, std::move(value));
|
||||
m_promises = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
promise(PromiseFactory<T>* promises, uint64_t request) noexcept
|
||||
: m_request(request), m_promises(promises) {}
|
||||
|
||||
uint64_t m_request = 0;
|
||||
PromiseFactory<T>* m_promises = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Explicit specialization for promise<void>.
|
||||
*/
|
||||
template <>
|
||||
class promise<void> final {
|
||||
friend class PromiseFactory<void>;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructs an empty promise.
|
||||
*/
|
||||
promise() : m_promises(&PromiseFactory<void>::GetInstance()) {
|
||||
m_request = m_promises->CreateRequest();
|
||||
}
|
||||
|
||||
promise(promise&& oth) noexcept
|
||||
: m_request(oth.m_request), m_promises(oth.m_promises) {
|
||||
oth.m_request = 0;
|
||||
oth.m_promises = nullptr;
|
||||
}
|
||||
|
||||
promise(const promise&) = delete;
|
||||
|
||||
/**
|
||||
* Sets the promised value if not already set.
|
||||
*/
|
||||
~promise() {
|
||||
if (m_promises) m_promises->SetValue(m_request);
|
||||
}
|
||||
|
||||
promise& operator=(promise&& oth) noexcept {
|
||||
m_request = oth.m_request;
|
||||
m_promises = oth.m_promises;
|
||||
oth.m_request = 0;
|
||||
oth.m_promises = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
promise& operator=(const promise&) = delete;
|
||||
|
||||
/**
|
||||
* Swaps this promise with another one.
|
||||
*/
|
||||
void swap(promise& oth) noexcept {
|
||||
std::swap(m_request, oth.m_request);
|
||||
std::swap(m_promises, oth.m_promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a future for this promise.
|
||||
*
|
||||
* @return The future
|
||||
*/
|
||||
future<void> get_future() noexcept {
|
||||
return future<void>(m_promises, m_request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the promised value.
|
||||
* Only effective once (subsequent calls will be ignored).
|
||||
*/
|
||||
void set_value() {
|
||||
if (m_promises) m_promises->SetValue(m_request);
|
||||
m_promises = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
promise(PromiseFactory<void>* promises, uint64_t request) noexcept
|
||||
: m_request(request), m_promises(promises) {}
|
||||
|
||||
uint64_t m_request = 0;
|
||||
PromiseFactory<void>* m_promises = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs a valid future with the value set.
|
||||
*/
|
||||
template <typename T>
|
||||
inline future<T> make_ready_future(T&& value) {
|
||||
return PromiseFactory<T>::GetInstance().MakeReadyFuture(
|
||||
std::forward<T>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a valid future with the value set.
|
||||
*/
|
||||
inline future<void> make_ready_future() {
|
||||
return PromiseFactory<void>::GetInstance().MakeReadyFuture();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline future<T> PromiseFactory<T>::CreateFuture(uint64_t request) {
|
||||
return future<T>{this, request};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
future<T> PromiseFactory<T>::MakeReadyFuture(T&& value) {
|
||||
std::unique_lock<wpi::mutex> lock(GetResultMutex());
|
||||
uint64_t req = CreateErasedRequest();
|
||||
m_results.emplace_back(std::piecewise_construct, std::forward_as_tuple(req),
|
||||
std::forward_as_tuple(std::move(value)));
|
||||
return future<T>{this, req};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline promise<T> PromiseFactory<T>::CreatePromise(uint64_t request) {
|
||||
return promise<T>{this, request};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PromiseFactory<T>::SetValue(uint64_t request, const T& value) {
|
||||
std::unique_lock<wpi::mutex> lock(GetResultMutex());
|
||||
if (!EraseRequest(request)) return;
|
||||
auto it = std::find_if(m_thens.begin(), m_thens.end(),
|
||||
[=](const auto& x) { return x.request == request; });
|
||||
if (it != m_thens.end()) {
|
||||
uint64_t outRequest = it->outRequest;
|
||||
ThenFunction func = std::move(it->func);
|
||||
m_thens.erase(it);
|
||||
lock.unlock();
|
||||
return func(outRequest, value);
|
||||
}
|
||||
m_results.emplace_back(std::piecewise_construct,
|
||||
std::forward_as_tuple(request),
|
||||
std::forward_as_tuple(value));
|
||||
Notify();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PromiseFactory<T>::SetValue(uint64_t request, T&& value) {
|
||||
std::unique_lock<wpi::mutex> lock(GetResultMutex());
|
||||
if (!EraseRequest(request)) return;
|
||||
auto it = std::find_if(m_thens.begin(), m_thens.end(),
|
||||
[=](const auto& x) { return x.request == request; });
|
||||
if (it != m_thens.end()) {
|
||||
uint64_t outRequest = it->outRequest;
|
||||
ThenFunction func = std::move(it->func);
|
||||
m_thens.erase(it);
|
||||
lock.unlock();
|
||||
return func(outRequest, std::move(value));
|
||||
}
|
||||
m_results.emplace_back(std::piecewise_construct,
|
||||
std::forward_as_tuple(request),
|
||||
std::forward_as_tuple(std::move(value)));
|
||||
Notify();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PromiseFactory<T>::SetThen(uint64_t request, uint64_t outRequest,
|
||||
ThenFunction func) {
|
||||
std::unique_lock<wpi::mutex> lock(GetResultMutex());
|
||||
auto it = std::find_if(m_results.begin(), m_results.end(),
|
||||
[=](const auto& r) { return r.first == request; });
|
||||
if (it != m_results.end()) {
|
||||
auto val = std::move(it->second);
|
||||
m_results.erase(it);
|
||||
lock.unlock();
|
||||
return func(outRequest, std::move(val));
|
||||
}
|
||||
m_thens.emplace_back(request, outRequest, func);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool PromiseFactory<T>::IsReady(uint64_t request) noexcept {
|
||||
std::unique_lock<wpi::mutex> lock(GetResultMutex());
|
||||
auto it = std::find_if(m_results.begin(), m_results.end(),
|
||||
[=](const auto& r) { return r.first == request; });
|
||||
return it != m_results.end();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T PromiseFactory<T>::GetResult(uint64_t request) {
|
||||
// wait for response
|
||||
std::unique_lock<wpi::mutex> lock(GetResultMutex());
|
||||
while (IsActive()) {
|
||||
// Did we get a response to *our* request?
|
||||
auto it = std::find_if(m_results.begin(), m_results.end(),
|
||||
[=](const auto& r) { return r.first == request; });
|
||||
if (it != m_results.end()) {
|
||||
// Yes, remove it from the vector and we're done.
|
||||
auto rv = std::move(it->second);
|
||||
m_results.erase(it);
|
||||
return rv;
|
||||
}
|
||||
// No, keep waiting for a response
|
||||
Wait(lock);
|
||||
}
|
||||
return T();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PromiseFactory<T>::WaitResult(uint64_t request) {
|
||||
// wait for response
|
||||
std::unique_lock<wpi::mutex> lock(GetResultMutex());
|
||||
while (IsActive()) {
|
||||
// Did we get a response to *our* request?
|
||||
auto it = std::find_if(m_results.begin(), m_results.end(),
|
||||
[=](const auto& r) { return r.first == request; });
|
||||
if (it != m_results.end()) return;
|
||||
// No, keep waiting for a response
|
||||
Wait(lock);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <class Clock, class Duration>
|
||||
bool PromiseFactory<T>::WaitResultUntil(
|
||||
uint64_t request,
|
||||
const std::chrono::time_point<Clock, Duration>& timeout_time) {
|
||||
std::unique_lock<wpi::mutex> lock(GetResultMutex());
|
||||
bool timeout = false;
|
||||
while (IsActive()) {
|
||||
// Did we get a response to *our* request?
|
||||
auto it = std::find_if(m_results.begin(), m_results.end(),
|
||||
[=](const auto& r) { return r.first == request; });
|
||||
if (it != m_results.end()) return true;
|
||||
if (timeout) break;
|
||||
// No, keep waiting for a response
|
||||
if (!WaitUntil(lock, timeout_time)) timeout = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
PromiseFactory<T>& PromiseFactory<T>::GetInstance() {
|
||||
static PromiseFactory inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
inline future<void> PromiseFactory<void>::CreateFuture(uint64_t request) {
|
||||
return future<void>{this, request};
|
||||
}
|
||||
|
||||
inline promise<void> PromiseFactory<void>::CreatePromise(uint64_t request) {
|
||||
return promise<void>{this, request};
|
||||
}
|
||||
|
||||
template <class Clock, class Duration>
|
||||
bool PromiseFactory<void>::WaitResultUntil(
|
||||
uint64_t request,
|
||||
const std::chrono::time_point<Clock, Duration>& timeout_time) {
|
||||
std::unique_lock<wpi::mutex> lock(GetResultMutex());
|
||||
bool timeout = false;
|
||||
while (IsActive()) {
|
||||
// Did we get a response to *our* request?
|
||||
auto it = std::find_if(m_results.begin(), m_results.end(),
|
||||
[=](const auto& r) { return r == request; });
|
||||
if (it != m_results.end()) return true;
|
||||
if (timeout) break;
|
||||
// No, keep waiting for a response
|
||||
if (!WaitUntil(lock, timeout_time)) timeout = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename To, typename From>
|
||||
template <typename F>
|
||||
future<To> detail::FutureThen<To, From>::Create(
|
||||
PromiseFactory<From>& fromFactory, uint64_t request,
|
||||
PromiseFactory<To>& factory, F&& func) {
|
||||
uint64_t req = factory.CreateRequest();
|
||||
fromFactory.SetThen(request, req, [&factory, func](uint64_t r, From value) {
|
||||
factory.SetValue(r, func(std::move(value)));
|
||||
});
|
||||
return factory.CreateFuture(req);
|
||||
}
|
||||
|
||||
template <typename From>
|
||||
template <typename F>
|
||||
future<void> detail::FutureThen<void, From>::Create(
|
||||
PromiseFactory<From>& fromFactory, uint64_t request,
|
||||
PromiseFactory<void>& factory, F&& func) {
|
||||
uint64_t req = factory.CreateRequest();
|
||||
fromFactory.SetThen(request, req, [&factory, func](uint64_t r, From value) {
|
||||
func(std::move(value));
|
||||
factory.SetValue(r);
|
||||
});
|
||||
return factory.CreateFuture(req);
|
||||
}
|
||||
|
||||
template <typename To>
|
||||
template <typename F>
|
||||
future<To> detail::FutureThen<To, void>::Create(
|
||||
PromiseFactory<void>& fromFactory, uint64_t request,
|
||||
PromiseFactory<To>& factory, F&& func) {
|
||||
uint64_t req = factory.CreateRequest();
|
||||
fromFactory.SetThen(request, req, [&factory, func](uint64_t r) {
|
||||
factory.SetValue(r, func());
|
||||
});
|
||||
return factory.CreateFuture(req);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
future<void> detail::FutureThen<void, void>::Create(
|
||||
PromiseFactory<void>& fromFactory, uint64_t request,
|
||||
PromiseFactory<void>& factory, F&& func) {
|
||||
uint64_t req = factory.CreateRequest();
|
||||
fromFactory.SetThen(request, req, [&factory, func](uint64_t r) {
|
||||
func();
|
||||
factory.SetValue(r);
|
||||
});
|
||||
return factory.CreateFuture(req);
|
||||
}
|
||||
|
||||
} // namespace wpi
|
||||
|
||||
#endif // WPIUTIL_WPI_FUTURE_H_
|
||||
@@ -8,18 +8,17 @@
|
||||
#ifndef WPIUTIL_WPI_UV_ASYNCFUNCTION_H_
|
||||
#define WPIUTIL_WPI_UV_ASYNCFUNCTION_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <uv.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "wpi/STLExtras.h"
|
||||
#include "wpi/condition_variable.h"
|
||||
#include "wpi/future.h"
|
||||
#include "wpi/mutex.h"
|
||||
#include "wpi/uv/Handle.h"
|
||||
#include "wpi/uv/Loop.h"
|
||||
@@ -27,85 +26,6 @@
|
||||
namespace wpi {
|
||||
namespace uv {
|
||||
|
||||
namespace detail {
|
||||
|
||||
class AsyncFunctionBase {
|
||||
public:
|
||||
virtual ~AsyncFunctionBase() {
|
||||
m_active = false;
|
||||
m_resultCv.notify_all(); // wake up any waiters
|
||||
}
|
||||
|
||||
protected:
|
||||
wpi::mutex m_mutex;
|
||||
std::atomic_bool m_active{true};
|
||||
wpi::condition_variable m_resultCv;
|
||||
};
|
||||
|
||||
template <typename R, typename... T>
|
||||
struct AsyncFunctionHelper : public AsyncFunctionBase {
|
||||
inline void RunCall(const std::function<R(T...)>& func,
|
||||
std::pair<std::thread::id, std::tuple<T...>>& v) {
|
||||
m_results.emplace_back(
|
||||
std::piecewise_construct, std::forward_as_tuple(v.first),
|
||||
std::forward_as_tuple(apply_tuple(func, std::move(v.second))));
|
||||
}
|
||||
|
||||
inline R GetCallResult(std::thread::id from) {
|
||||
// wait for response
|
||||
std::unique_lock<wpi::mutex> lock(m_mutex);
|
||||
while (m_active) {
|
||||
// Did we get a response to *our* request?
|
||||
auto it = std::find_if(m_results.begin(), m_results.end(),
|
||||
[=](const auto& r) { return r.first == from; });
|
||||
if (it != m_results.end()) {
|
||||
// Yes, remove it from the vector and we're done.
|
||||
auto rv = std::move(it->second);
|
||||
m_results.erase(it);
|
||||
return rv;
|
||||
}
|
||||
// No, keep waiting for a response
|
||||
m_resultCv.wait(lock);
|
||||
}
|
||||
|
||||
return R();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::pair<std::thread::id, R>> m_results;
|
||||
};
|
||||
|
||||
// void return value partial specialization
|
||||
template <typename... T>
|
||||
struct AsyncFunctionHelper<void, T...> : public AsyncFunctionBase {
|
||||
inline void RunCall(const std::function<void(T...)>& func,
|
||||
std::pair<std::thread::id, std::tuple<T...>>& v) {
|
||||
apply_tuple(func, std::move(v.second));
|
||||
m_results.emplace_back(v.first);
|
||||
}
|
||||
|
||||
inline void GetCallResult(std::thread::id from) {
|
||||
// wait for response
|
||||
std::unique_lock<wpi::mutex> lock(m_mutex);
|
||||
while (m_active) {
|
||||
// Did we get a response to *our* request?
|
||||
auto it = std::find(m_results.begin(), m_results.end(), from);
|
||||
if (it != m_results.end()) {
|
||||
// Yes, remove it from the vector and we're done.
|
||||
m_results.erase(it);
|
||||
return;
|
||||
}
|
||||
// No, keep waiting for a response
|
||||
m_resultCv.wait(lock);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::thread::id> m_results;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T>
|
||||
class AsyncFunction;
|
||||
|
||||
@@ -116,12 +36,11 @@ class AsyncFunction;
|
||||
*/
|
||||
template <typename R, typename... T>
|
||||
class AsyncFunction<R(T...)> final
|
||||
: public HandleImpl<AsyncFunction<R(T...)>, uv_async_t>,
|
||||
private detail::AsyncFunctionHelper<R, T...> {
|
||||
: public HandleImpl<AsyncFunction<R(T...)>, uv_async_t> {
|
||||
struct private_init {};
|
||||
|
||||
public:
|
||||
AsyncFunction(std::function<R(T...)> func, const private_init&)
|
||||
AsyncFunction(std::function<void(promise<R>, T...)> func, const private_init&)
|
||||
: wakeup{func} {}
|
||||
~AsyncFunction() noexcept override = default;
|
||||
|
||||
@@ -129,28 +48,32 @@ class AsyncFunction<R(T...)> final
|
||||
* Create an async handle.
|
||||
*
|
||||
* @param loop Loop object where this handle runs.
|
||||
* @param func wakeup function to be called (sets wakeup value)
|
||||
* @param func wakeup function to be called (sets wakeup value); the function
|
||||
* needs to return void, and its first parameter is the promise
|
||||
* for the result. If no value is set on the promise by the
|
||||
* wakeup function, a default-constructed value is "returned".
|
||||
*/
|
||||
static std::shared_ptr<AsyncFunction> Create(
|
||||
Loop& loop, std::function<R(T...)> func = nullptr) {
|
||||
Loop& loop, std::function<void(promise<R>, T...)> func = nullptr) {
|
||||
auto h = std::make_shared<AsyncFunction>(std::move(func), private_init{});
|
||||
int err = uv_async_init(loop.GetRaw(), h->GetRaw(), [](uv_async_t* handle) {
|
||||
auto& h = *static_cast<AsyncFunction*>(handle->data);
|
||||
std::lock_guard<wpi::mutex> lock(h.m_mutex);
|
||||
std::unique_lock<wpi::mutex> lock(h.m_mutex);
|
||||
|
||||
if (!h.m_params.empty()) {
|
||||
// for each set of parameters in the input queue
|
||||
// for each set of parameters in the input queue, call the wakeup
|
||||
// function and put the result in the output queue if the caller is
|
||||
// waiting for it
|
||||
for (auto&& v : h.m_params) {
|
||||
// call the wakeup function and put the result in the output queue
|
||||
// if the caller is waiting for it
|
||||
if (v.first == std::thread::id{})
|
||||
apply_tuple(h.wakeup, std::move(v.second));
|
||||
else
|
||||
h.RunCall(h.wakeup, v);
|
||||
auto p = h.m_promises.CreatePromise(v.first);
|
||||
if (h.wakeup)
|
||||
apply_tuple(h.wakeup, std::tuple_cat(std::make_tuple(std::move(p)),
|
||||
std::move(v.second)));
|
||||
}
|
||||
h.m_params.clear();
|
||||
// wake up any threads that might be waiting for the result
|
||||
h.m_resultCv.notify_all();
|
||||
lock.unlock();
|
||||
h.m_promises.Notify();
|
||||
}
|
||||
});
|
||||
if (err < 0) {
|
||||
@@ -165,71 +88,61 @@ class AsyncFunction<R(T...)> final
|
||||
* Create an async handle.
|
||||
*
|
||||
* @param loop Loop object where this handle runs.
|
||||
* @param func wakeup function to be called (sets wakeup value)
|
||||
* @param func wakeup function to be called (sets wakeup value); the function
|
||||
* needs to return void, and its first parameter is the promise
|
||||
* for the result. If no value is set on the promise by the
|
||||
* wakeup function, a default-constructed value is "returned".
|
||||
*/
|
||||
static std::shared_ptr<AsyncFunction> Create(
|
||||
const std::shared_ptr<Loop>& loop,
|
||||
std::function<R(T...)> func = nullptr) {
|
||||
std::function<void(promise<R>, T...)> func = nullptr) {
|
||||
return Create(*loop, std::move(func));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wakeup the event loop, call the async function, and ignore any result.
|
||||
* This is non-blocking and does NOT wait until the async function returns.
|
||||
* Wakeup the event loop, call the async function, and return a future for
|
||||
* the result.
|
||||
*
|
||||
* It’s safe to call this function from any thread EXCEPT the loop thread.
|
||||
* The async function will be called on the loop thread.
|
||||
*
|
||||
* The future will return a default-constructed result if this handle is
|
||||
* destroyed while waiting for a result.
|
||||
*/
|
||||
template <typename... U>
|
||||
void Send(U&&... u) {
|
||||
future<R> Call(U&&... u) {
|
||||
// create the future
|
||||
uint64_t req = m_promises.CreateRequest();
|
||||
|
||||
// add the parameters to the input queue
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(this->m_mutex);
|
||||
std::lock_guard<wpi::mutex> lock(m_mutex);
|
||||
m_params.emplace_back(std::piecewise_construct,
|
||||
std::forward_as_tuple(std::thread::id{}),
|
||||
std::forward_as_tuple(req),
|
||||
std::forward_as_tuple(std::forward<U>(u)...));
|
||||
}
|
||||
|
||||
// signal the loop
|
||||
this->Invoke(&uv_async_send, this->GetRaw());
|
||||
|
||||
// return future
|
||||
return m_promises.CreateFuture(req);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wakeup the event loop, call the async function, and return the result.
|
||||
* This blocks the calling thread until the async function returns.
|
||||
*
|
||||
* It’s safe to call this function from any thread EXCEPT the loop thread.
|
||||
* The async function will be called on the loop thread.
|
||||
*
|
||||
* Returns a default-constructed result if this handle is destroyed while
|
||||
* waiting for a result.
|
||||
*/
|
||||
template <typename... U>
|
||||
R Call(U&&... u) {
|
||||
std::thread::id from = std::this_thread::get_id();
|
||||
|
||||
// add the parameters to the input queue
|
||||
{
|
||||
std::lock_guard<wpi::mutex> lock(this->m_mutex);
|
||||
m_params.emplace_back(std::piecewise_construct,
|
||||
std::forward_as_tuple(from),
|
||||
std::forward_as_tuple(std::forward<U>(u)...));
|
||||
}
|
||||
|
||||
// signal the loop
|
||||
this->Invoke(&uv_async_send, this->GetRaw());
|
||||
|
||||
// wait for response
|
||||
return this->GetCallResult(from);
|
||||
future<R> operator()(U&&... u) {
|
||||
return Call(std::forward<U>(u)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called (on event loop thread) when the async is called.
|
||||
*/
|
||||
std::function<R(T...)> wakeup;
|
||||
std::function<void(promise<R>, T...)> wakeup;
|
||||
|
||||
private:
|
||||
std::vector<std::pair<std::thread::id, std::tuple<T...>>> m_params;
|
||||
wpi::mutex m_mutex;
|
||||
std::vector<std::pair<uint64_t, std::tuple<T...>>> m_params;
|
||||
PromiseFactory<R> m_promises;
|
||||
};
|
||||
|
||||
} // namespace uv
|
||||
|
||||
Reference in New Issue
Block a user