diff --git a/CMakeLists.txt b/CMakeLists.txt index 45f75ded..154d7313 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -230,6 +230,10 @@ set(dxFeedGraalCxxApi_OnDemand_Sources src/ondemand/OnDemandService.cpp ) +set(dxFeedGraalCxxApi_Promise_Sources + src/promise/Promise.cpp +) + set(dxFeedGraalCxxApi_Symbols_Sources src/symbols/StringSymbol.cpp src/symbols/SymbolWrapper.cpp @@ -317,6 +321,7 @@ set(dxFeedGraalCxxApi_Sources ${dxFeedGraalCxxApi_ApiOsub_Sources} ${dxFeedGraalCxxApi_Ipf_Sources} ${dxFeedGraalCxxApi_OnDemand_Sources} + ${dxFeedGraalCxxApi_Promise_Sources} ${dxFeedGraalCxxApi_Symbols_Sources} ${dxFeedGraalCxxApi_System_Sources} ${dxFeedGraalCxxApi_Event_Sources} diff --git a/README.md b/README.md index 7e65d43a..e8e09ffd 100644 --- a/README.md +++ b/README.md @@ -355,6 +355,8 @@ versions): is a simple demonstration of how to get various scheduling information for instruments - [x] [OnDemandSample](https://github.com/dxFeed/dxfeed-graal-cxx-api/blob/main/samples/cpp/OnDemandSample/src/main.cpp) a sample that demonstrates how to use the dxFeed on-demand history data replay service API +- [x] [FetchDailyCandles](https://github.com/dxFeed/dxfeed-graal-cxx-api/blob/main/samples/cpp/FetchDailyCandles/src/main.cpp) + a sample that demonstrates how to fetch last 20 days of candles for a specified symbol and print them. ## Current State diff --git a/include/dxfeed_graal_cpp_api/api.hpp b/include/dxfeed_graal_cpp_api/api.hpp index 34366040..26a9222f 100644 --- a/include/dxfeed_graal_cpp_api/api.hpp +++ b/include/dxfeed_graal_cpp_api/api.hpp @@ -58,6 +58,7 @@ DXFCXX_DISABLE_MSC_WARNINGS_PUSH(4251 4996) #include "isolated/api/IsolatedDXPublisher.hpp" #include "isolated/api/IsolatedDXPublisherObservableSubscription.hpp" #include "isolated/api/osub/IsolatedObservableSubscriptionChangeListener.hpp" +#include "isolated/promise/IsolatedPromise.hpp" #include "isolated/internal/IsolatedString.hpp" #include "isolated/internal/IsolatedTimeFormat.hpp" #include "isolated/internal/IsolatedTools.hpp" diff --git a/include/dxfeed_graal_cpp_api/api/DXFeed.hpp b/include/dxfeed_graal_cpp_api/api/DXFeed.hpp index a1765062..acce0eae 100644 --- a/include/dxfeed_graal_cpp_api/api/DXFeed.hpp +++ b/include/dxfeed_graal_cpp_api/api/DXFeed.hpp @@ -125,6 +125,9 @@ struct DXFCPP_EXPORT DXFeed : SharedEntity { JavaObjectHandle handle_; static std::shared_ptr create(void *feedHandle); + void *getTimeSeriesPromiseImpl(const EventTypeEnum &eventType, const SymbolWrapper &symbol, std::int64_t fromTime, + std::int64_t toTime) const; + protected: DXFeed() noexcept : handle_{} { if constexpr (Debugger::isDebug) { @@ -295,9 +298,21 @@ struct DXFCPP_EXPORT DXFeed : SharedEntity { return sub; } - Promise>> getTimeSeriesPromise(const EventTypeEnum &eventType, - const SymbolWrapper &symbol, std::int64_t fromTime, - std::int64_t toTime); + /** + * Requests time series of events for the specified event type, symbol, and a range of time. + * @tparam E The type of event. + * @param symbol The symbol. + * @param fromTime The time, inclusive, to request events from (see TimeSeriesEvent::getTime()). + * @param toTime The time, inclusive, to request events to (see TimeSeriesEvent::getTime()). + * Use `std::numeric_limits::max()` or `LLONG_MAX` macro to retrieve events without an + * upper limit on time. + * @return The promise for the result of the request. + */ + template E> + Promise>> getTimeSeriesPromise(const SymbolWrapper &symbol, std::int64_t fromTime, + std::int64_t toTime) const { + return Promise>>(getTimeSeriesPromiseImpl(E::TYPE, symbol, fromTime, toTime)); + } std::string toString() const noexcept override; }; diff --git a/include/dxfeed_graal_cpp_api/promise/Promise.hpp b/include/dxfeed_graal_cpp_api/promise/Promise.hpp index 738480fb..5bfc5c80 100644 --- a/include/dxfeed_graal_cpp_api/promise/Promise.hpp +++ b/include/dxfeed_graal_cpp_api/promise/Promise.hpp @@ -3,60 +3,349 @@ #pragma once +#include "../exceptions/JavaException.hpp" #include "../internal/Conf.hpp" DXFCXX_DISABLE_MSC_WARNINGS_PUSH(4251) +#include #include #include DXFCPP_BEGIN_NAMESPACE -struct TimeSeriesEvent; -struct LastingEvent; +struct EventType; +struct JavaException; + +template +concept Derived = std::is_base_of_v; + +template EDerived> +std::shared_ptr convertEvent(const std::shared_ptr &source) { + return source->template sharedAs(); +} + +template EDerived> +std::vector> convertEvents(const std::vector> &source) { + std::vector> result{}; + + result.reserve(source.size()); + + for (const auto &e : source) { + result.emplace_back(e->template sharedAs()); + } + + return result; +} struct PromiseImpl { + protected: + void *handle = nullptr; + + public: + explicit PromiseImpl(void *handle); + bool isDone() const; + bool hasResult() const; + bool hasException() const; + bool isCancelled() const; + JavaException getException() const; + void await() const; + void await(std::int32_t timeoutInMilliseconds) const; + bool awaitWithoutException(std::int32_t timeoutInMilliseconds) const; + void cancel() const; }; -template struct Promise { +struct EventPromiseImpl : PromiseImpl { + void *handle = nullptr; + + explicit EventPromiseImpl(void *handle); + ~EventPromiseImpl(); + std::shared_ptr getResult() const; }; -template -concept Derived = std::is_base_of_v; +struct EventsPromiseImpl : PromiseImpl { + void *handle = nullptr; + + explicit EventsPromiseImpl(void *handle); + ~EventsPromiseImpl(); + std::vector> getResult() const; +}; + +/** + * Mixin for wrapping calls to common promise methods. + * + * @tparam P The promise type + */ +template struct CommonPromiseMixin { + /** + * Returns `true` when computation has completed normally, or exceptionally, or was cancelled. + * + * @return `true` when computation has completed. + */ + bool isDone() const { + return static_cast(this)->impl.isDone(); + } + + /** + * Returns `true` when computation has completed normally. + * Use ::getResult() method to get the result of the computation. + * @return `true` when computation has completed normally. + * @see ::getResult() + */ + bool hasResult() const { + return static_cast(this)->impl.hasResult(); + } + + /** + * Returns `true` when computation has completed exceptionally or was cancelled. + * Use ::getException() method to get the exceptional outcome of the computation. + * @return `true` when computation has completed exceptionally or was cancelled. + */ + bool hasException() const { + return static_cast(this)->impl.hasException(); + } + + /** + * Returns `true` when computation was cancelled. + * Use ::getException() method to get the corresponding CancellationException. + * @return `true` when computation was cancelled. + * @see ::getException() + */ + bool isCancelled() const { + return static_cast(this)->impl.isCancelled(); + } + + /** + * Returns exceptional outcome of computation. If computation has no ::hasException() exception, + * then this method returns an exception with a message "null". If computation has completed exceptionally or was + * cancelled, then the result of this method is not an exception with a message "null". If computation was @ref + * ::isCancelled() "cancelled", then this method returns "an instance of CancellationException". + * + * @return exceptional outcome of computation. + * @see ::hasException() + */ + JavaException getException() const { + return static_cast(this)->impl.getException(); + } + + /** + * Wait for computation to complete or timeout or throw an exception in case of exceptional completion. + * If the wait is interrupted, then the computation is @ref ::cancel() "cancelled", + * the interruption flag on the current thread is set, and "CancellationException" is thrown. + * + *

If the wait times out, then the computation is @ref ::cancel() "cancelled" and this method returns `false`. + * Use this method in the code that shall continue normal execution in case of timeout. + * + * @param timeoutInMilliseconds The timeout. + * @return `true` if the computation has completed normally; `false` when wait timed out. + * @throws CancellationException if computation was cancelled. + * @throws PromiseException if computation has completed exceptionally. + */ + bool awaitWithoutException(std::int32_t timeoutInMilliseconds) const { + return static_cast(this)->impl.awaitWithoutException(timeoutInMilliseconds); + } + + /** + * Wait for computation to complete or timeout or throw an exception in case of exceptional completion. + * If the wait is interrupted, then the computation is @ref ::cancel() "cancelled", + * the interruption flag on the current thread is set, and "CancellationException" is thrown. + * + *

If the wait times out, then the computation is @ref ::cancel() "cancelled" and this method returns `false`. + * Use this method in the code that shall continue normal execution in case of timeout. + * + * @param timeoutInMilliseconds The timeout. + * @return `true` if the computation has completed normally; `false` when wait timed out. + * @throws CancellationException if computation was cancelled. + * @throws PromiseException if computation has completed exceptionally. + */ + bool awaitWithoutException(const std::chrono::milliseconds &timeoutInMilliseconds) const { + auto timeout = timeoutInMilliseconds.count(); + + if (timeout > std::numeric_limits::max()) { + timeout = std::numeric_limits::max(); + } -template T> struct Promise> { + return static_cast(this)->impl.awaitWithoutException(timeout); + } + /** + * Cancels computation. This method does nothing if computation has already @ref ::isDone() "completed". + * + *

If cancelled, then ::getException() will return "CancellationException", + * @ref ::isDone() "isDone", @ref ::isCancelled() "isCancelled", and @ref ::hasException() "hasException" will + * return `true`, all handlers that were installed with `whenDone` method are notified by invoking their + * `promiseDone` method, and all waiters on @ref ::await() "join" method throw "CancellationException". + */ + void cancel() const { + static_cast(this)->impl.cancel(); + } }; -template T> struct Promise> { +/** + * Mixin for wrapping Promise method calls for a single event. + * @tparam E The event type. + * @tparam P The promise type. + */ +template struct EventPromiseMixin { + /** + * Returns result of computation. If computation has no @ref CommonPromiseMixin::hasResult() "result", then + * this method returns `std::shared_ptr(nullptr)`. + * + * @return The result of computation. + * @see CommonPromiseMixin::hasResult() + */ + std::shared_ptr getResult() const { + return convertEvent(static_cast(this)->impl.getResult()); + } + /** + * Wait for computation to complete and return its result or throw an exception in case of exceptional completion. + * @return result of computation. + * @throws CancellationException if computation was cancelled. + * @throws PromiseException if computation has completed exceptionally. + */ + std::shared_ptr await() const { + static_cast(this)->impl.await(); + + return getResult(); + } + + /** + * Wait for computation to complete or timeout and return its result or throw an exception in case of exceptional + * completion or timeout. + * + * @param timeoutInMilliseconds The timeout. + * @return The result of computation. + * @throws CancellationException if computation was cancelled or timed out. + * @throws PromiseException if computation has completed exceptionally. + */ + std::shared_ptr await(std::int32_t timeoutInMilliseconds) const & { + static_cast(this)->impl.await(timeoutInMilliseconds); + + return getResult(); + } + + /** + * Wait for computation to complete or timeout and return its result or throw an exception in case of exceptional + * completion or timeout. + * + * @param timeoutInMilliseconds The timeout. + * @return The result of computation. + * @throws CancellationException if computation was cancelled or timed out. + * @throws PromiseException if computation has completed exceptionally. + */ + std::shared_ptr await(const std::chrono::milliseconds &timeoutInMilliseconds) const & { + auto timeout = timeoutInMilliseconds.count(); + + if (timeout > std::numeric_limits::max()) { + timeout = std::numeric_limits::max(); + } + + static_cast(this)->impl.await(timeout); + + return getResult(); + } }; -template T> struct Promise>> { +template struct EventsPromiseMixin { + /** + * Returns result of computation. If computation has no @ref CommonPromiseMixin::hasResult() "result", then + * this method returns an empty ollection. + * + * @return The result of computation. + * @see CommonPromiseMixin::hasResult() + */ + std::vector> getResult() const { + return convertEvents(static_cast(this)->impl.getResult()); + } + + /** + * Wait for computation to complete and return its result or throw an exception in case of exceptional completion. + * @return result of computation. + * @throws CancellationException if computation was cancelled. + * @throws PromiseException if computation has completed exceptionally. + */ + std::vector> await() const { + static_cast(this)->impl.await(); + + return getResult(); + } + + /** + * Wait for computation to complete or timeout and return its result or throw an exception in case of exceptional + * completion or timeout. + * + * @param timeoutInMilliseconds The timeout. + * @return The result of computation. + * @throws CancellationException if computation was cancelled or timed out. + * @throws PromiseException if computation has completed exceptionally. + */ + std::vector> await(std::int32_t timeoutInMilliseconds) const & { + static_cast(this)->impl.await(timeoutInMilliseconds); + + return getResult(); + } + /** + * Wait for computation to complete or timeout and return its result or throw an exception in case of exceptional + * completion or timeout. + * + * @param timeoutInMilliseconds The timeout. + * @return The result of computation. + * @throws CancellationException if computation was cancelled or timed out. + * @throws PromiseException if computation has completed exceptionally. + */ + std::vector> await(const std::chrono::milliseconds &timeoutInMilliseconds) const & { + auto timeout = timeoutInMilliseconds.count(); + + if (timeout > std::numeric_limits::max()) { + timeout = std::numeric_limits::max(); + } + + static_cast(this)->impl.await(timeout); + + return getResult(); + } +}; + +/** + * Result of a computation that will be completed normally or exceptionally in the future. + * @tparam T The result type. + */ +template struct Promise {}; + +/** + * Result of an event receiving that will be completed normally or exceptionally in the future. + * @tparam E The event type. + */ +template +struct Promise> : CommonPromiseMixin>>, + EventPromiseMixin>> { + friend struct CommonPromiseMixin; + friend struct EventPromiseMixin; + + EventsPromiseImpl impl; + + explicit Promise(void *handle) : impl(handle) { + } }; +/** + * Result of an collection of events receiving that will be completed normally or exceptionally in the future. + * @tparam E The event type. + */ +template +struct Promise>> : CommonPromiseMixin>>>, + EventsPromiseMixin>>> { + friend struct CommonPromiseMixin; + friend struct EventsPromiseMixin; -// -// Promise> x{}; -// auto _ = x.z(); -// -// template struct PromiseSFINAE { -// int x() { -// return {}; -// } -// }; -// -// template -// struct PromiseSFINAE::value>::type> { -// int z() { -// return 0; -// } -// }; -// -// PromiseSFINAE> xx{}; -// auto __ = xx.z(); + EventsPromiseImpl impl; + + explicit Promise(void *handle) : impl(handle) { + } +}; DXFCPP_END_NAMESPACE diff --git a/samples/cpp/FetchDailyCandles/src/main.cpp b/samples/cpp/FetchDailyCandles/src/main.cpp index 9ef1f672..a2d65392 100644 --- a/samples/cpp/FetchDailyCandles/src/main.cpp +++ b/samples/cpp/FetchDailyCandles/src/main.cpp @@ -8,13 +8,13 @@ void fetchAndPrint(const dxfcpp::CandleSymbol &candleSymbol, std::int64_t toTime, std::int64_t fromTime) { // Use default DXFeed instance for that data feed address is defined by dxfeed.properties file - // auto result = dxfcpp::DXFeed::getInstance() - // ->getTimeSeriesPromise(candleSymbol, fromTime, toTime) - // ->await(std::chrono::seconds(5)); - // - // for (auto candle : result) { - // std::cout << candle->toString(); - // } + auto result = dxfcpp::DXFeed::getInstance() + ->getTimeSeriesPromise(candleSymbol, fromTime, toTime) + .await(std::chrono::seconds(5)); + + for (const auto &candle : result) { + std::cout << candle->toString() << "\n"; + } } // Fetches last 20 days of candles for a specified symbol, prints them, and exits. diff --git a/src/api/DXFeed.cpp b/src/api/DXFeed.cpp index 170f95c3..1925bd97 100644 --- a/src/api/DXFeed.cpp +++ b/src/api/DXFeed.cpp @@ -121,14 +121,9 @@ std::shared_ptr DXFeed::create(void *feedHandle) { return feed; } -Promise>> DXFeed::getTimeSeriesPromise(const EventTypeEnum &eventType, - const SymbolWrapper &symbol, - std::int64_t fromTime, - std::int64_t toTime) { - - // TODO: impelement - - return {}; +void *DXFeed::getTimeSeriesPromiseImpl(const EventTypeEnum &eventType, const SymbolWrapper &symbol, + std::int64_t fromTime, std::int64_t toTime) const { + return isolated::api::IsolatedDXFeed::getTimeSeriesPromise(handle_, eventType, symbol, fromTime, toTime); } std::string DXFeed::toString() const noexcept { diff --git a/src/exceptions/JavaException.cpp b/src/exceptions/JavaException.cpp index 7ee697f3..b3ee9a5a 100644 --- a/src/exceptions/JavaException.cpp +++ b/src/exceptions/JavaException.cpp @@ -98,7 +98,7 @@ JavaException::JavaException(const std::string &message, const std::string &clas JavaException JavaException::create(void *exceptionHandle) { if (exceptionHandle == nullptr) { - return {"", "", ""}; + return {"null", "", ""}; } auto *exception = dxfcpp::bit_cast(exceptionHandle); diff --git a/src/internal/JavaObjectHandle.cpp b/src/internal/JavaObjectHandle.cpp index 87e84284..d45ac45a 100644 --- a/src/internal/JavaObjectHandle.cpp +++ b/src/internal/JavaObjectHandle.cpp @@ -83,4 +83,8 @@ template struct JavaObjectHandle; template struct JavaObjectHandle; template struct JavaObjectHandle; +template struct JavaObjectHandle; +template struct JavaObjectHandle; +template struct JavaObjectHandle; + DXFCPP_END_NAMESPACE \ No newline at end of file diff --git a/src/promise/Promise.cpp b/src/promise/Promise.cpp new file mode 100644 index 00000000..f36d0ef7 --- /dev/null +++ b/src/promise/Promise.cpp @@ -0,0 +1,80 @@ +// Copyright (c) 2024 Devexperts LLC. +// SPDX-License-Identifier: MPL-2.0 + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +DXFCPP_BEGIN_NAMESPACE + +PromiseImpl::PromiseImpl(void *handle) : handle(handle) { +} + +bool PromiseImpl::isDone() const { + return isolated::promise::IsolatedPromise::isDone(handle); +} + +bool PromiseImpl::hasResult() const { + return isolated::promise::IsolatedPromise::hasResult(handle); +} + +bool PromiseImpl::hasException() const { + return isolated::promise::IsolatedPromise::hasException(handle); +} + +bool PromiseImpl::isCancelled() const { + return isolated::promise::IsolatedPromise::isCancelled(handle); +} + +JavaException PromiseImpl::getException() const { + return isolated::promise::IsolatedPromise::getException(handle); +} + +void PromiseImpl::await() const { + isolated::promise::IsolatedPromise::await(handle); +} + +void PromiseImpl::await(std::int32_t timeoutInMilliseconds) const { + isolated::promise::IsolatedPromise::await(handle, timeoutInMilliseconds); +} + +bool PromiseImpl::awaitWithoutException(std::int32_t timeoutInMilliseconds) const { + return isolated::promise::IsolatedPromise::awaitWithoutException(handle, timeoutInMilliseconds); +} + +void PromiseImpl::cancel() const { + isolated::promise::IsolatedPromise::cancel(handle); +} + +EventPromiseImpl::EventPromiseImpl(void *handle) : PromiseImpl(handle), handle(handle) { +} + +EventPromiseImpl::~EventPromiseImpl() { + JavaObjectHandle::deleter(handle); +} + +std::shared_ptr EventPromiseImpl::getResult() const { + return isolated::promise::IsolatedPromise::getResult(handle); +} + +EventsPromiseImpl::EventsPromiseImpl(void *handle) : PromiseImpl(handle), handle(handle) { +} + +EventsPromiseImpl::~EventsPromiseImpl() { + JavaObjectHandle::deleter(handle); +} + +std::vector> EventsPromiseImpl::getResult() const { + return isolated::promise::IsolatedPromise::getResults(handle); +} + +DXFCPP_END_NAMESPACE \ No newline at end of file