/*----------------------------------------------------------------------------*/ /* Copyright (c) 2018-2019 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 #include #include #include #include #include #include #include #include "wpi/condition_variable.h" #include "wpi/mutex.h" namespace wpi { template class PromiseFactory; template class future; template class promise; template <> class promise; 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& lock) { m_resultCv.wait(lock); } // returns false if timeout reached template bool WaitUntil(std::unique_lock& lock, const std::chrono::time_point& 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 m_requests; }; template struct FutureThen { template static future Create(PromiseFactory& fromFactory, uint64_t request, PromiseFactory& factory, F&& func); }; template struct FutureThen { template static future Create(PromiseFactory& fromFactory, uint64_t request, PromiseFactory& factory, F&& func); }; template struct FutureThen { template static future Create(PromiseFactory& fromFactory, uint64_t request, PromiseFactory& factory, F&& func); }; template <> struct FutureThen { template static future Create(PromiseFactory& fromFactory, uint64_t request, PromiseFactory& 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 class PromiseFactory final : public detail::PromiseFactoryBase { friend class future; public: using detail::PromiseFactoryBase::Notify; using ThenFunction = std::function; /** * Creates a future. * * @param request the request id returned by CreateRequest() * @return the future */ future CreateFuture(uint64_t request); /** * Creates a future and makes it immediately ready. * * @return the future */ future MakeReadyFuture(T&& value); /** * Creates a promise. * * @param request the request id returned by CreateRequest() * @return the promise */ promise 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 bool WaitResultUntil( uint64_t request, const std::chrono::time_point& 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 m_thens; std::vector> m_results; }; /** * Explicit specialization for PromiseFactory. */ template <> class PromiseFactory final : public detail::PromiseFactoryBase { friend class future; public: using detail::PromiseFactoryBase::Notify; using ThenFunction = std::function; /** * Creates a future. * * @param request the request id returned by CreateRequest() * @return std::pair of the future and the request id */ future CreateFuture(uint64_t request); /** * Creates a future and makes it immediately ready. * * @return the future */ future MakeReadyFuture(); /** * Creates a promise. * * @param request the request id returned by CreateRequest() * @return the promise */ promise 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 bool WaitResultUntil( uint64_t request, const std::chrono::time_point& 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 m_thens; std::vector m_results; }; /** * A lightweight version of std::future. * * Use either promise::get_future() or PromiseFactory::CreateFuture() to create. * * @tparam T the "return" type */ template class future final { friend class PromiseFactory; friend class promise; 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 future(future&& 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 future then(PromiseFactory& factory, F&& func) { if (m_promises) { auto promises = m_promises; m_promises = nullptr; return detail::FutureThen::Create(*promises, m_request, factory, func); } else { return future(); } } template ::type> future then(F&& func) { return then(PromiseFactory::GetInstance(), std::forward(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 bool wait_until( const std::chrono::time_point& 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 bool wait_for( const std::chrono::duration& timeout_duration) const { return wait_until(std::chrono::steady_clock::now() + timeout_duration); } private: future(PromiseFactory* promises, uint64_t request) noexcept : m_request(request), m_promises(promises) {} uint64_t m_request = 0; PromiseFactory* m_promises = nullptr; }; /** * Explicit specialization for future. */ template <> class future final { friend class PromiseFactory; friend class promise; 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 future then(PromiseFactory& factory, F&& func) { if (m_promises) { auto promises = m_promises; m_promises = nullptr; return detail::FutureThen::Create(*promises, m_request, factory, func); } else { return future(); } } template ::type> future then(F&& func) { return then(PromiseFactory::GetInstance(), std::forward(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 bool wait_until( const std::chrono::time_point& 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 bool wait_for( const std::chrono::duration& timeout_duration) const { return wait_until(std::chrono::steady_clock::now() + timeout_duration); } private: future(PromiseFactory* promises, uint64_t request) noexcept : m_request(request), m_promises(promises) {} uint64_t m_request = 0; PromiseFactory* m_promises = nullptr; }; /** * A lightweight version of std::promise. * * Use PromiseFactory::CreatePromise() to create. * * @tparam T the "return" type */ template class promise final { friend class PromiseFactory; public: /** * Constructs an empty promise. */ promise() : m_promises(&PromiseFactory::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 get_future() noexcept { return future(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* promises, uint64_t request) noexcept : m_request(request), m_promises(promises) {} uint64_t m_request = 0; PromiseFactory* m_promises = nullptr; }; /** * Explicit specialization for promise. */ template <> class promise final { friend class PromiseFactory; public: /** * Constructs an empty promise. */ promise() : m_promises(&PromiseFactory::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 get_future() noexcept { return future(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* promises, uint64_t request) noexcept : m_request(request), m_promises(promises) {} uint64_t m_request = 0; PromiseFactory* m_promises = nullptr; }; /** * Constructs a valid future with the value set. */ template inline future make_ready_future(T&& value) { return PromiseFactory::GetInstance().MakeReadyFuture( std::forward(value)); } /** * Constructs a valid future with the value set. */ inline future make_ready_future() { return PromiseFactory::GetInstance().MakeReadyFuture(); } template inline future PromiseFactory::CreateFuture(uint64_t request) { return future{this, request}; } template future PromiseFactory::MakeReadyFuture(T&& value) { std::unique_lock 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{this, req}; } template inline promise PromiseFactory::CreatePromise(uint64_t request) { return promise{this, request}; } template void PromiseFactory::SetValue(uint64_t request, const T& value) { std::unique_lock 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 void PromiseFactory::SetValue(uint64_t request, T&& value) { std::unique_lock 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 void PromiseFactory::SetThen(uint64_t request, uint64_t outRequest, ThenFunction func) { std::unique_lock 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 bool PromiseFactory::IsReady(uint64_t request) noexcept { std::unique_lock 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 T PromiseFactory::GetResult(uint64_t request) { // wait for response std::unique_lock 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 void PromiseFactory::WaitResult(uint64_t request) { // wait for response std::unique_lock 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 template bool PromiseFactory::WaitResultUntil( uint64_t request, const std::chrono::time_point& timeout_time) { std::unique_lock 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 PromiseFactory& PromiseFactory::GetInstance() { static PromiseFactory inst; return inst; } inline future PromiseFactory::CreateFuture(uint64_t request) { return future{this, request}; } inline promise PromiseFactory::CreatePromise(uint64_t request) { return promise{this, request}; } template bool PromiseFactory::WaitResultUntil( uint64_t request, const std::chrono::time_point& timeout_time) { std::unique_lock 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 template future detail::FutureThen::Create( PromiseFactory& fromFactory, uint64_t request, PromiseFactory& 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 template future detail::FutureThen::Create( PromiseFactory& fromFactory, uint64_t request, PromiseFactory& 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 template future detail::FutureThen::Create( PromiseFactory& fromFactory, uint64_t request, PromiseFactory& 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 future detail::FutureThen::Create( PromiseFactory& fromFactory, uint64_t request, PromiseFactory& 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_