diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d861dc4..e38ea43b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -195,6 +195,7 @@ endif() set(_XAML_TARGETS xaml_global xaml_meta xaml_markup + xaml_async xaml_ui xaml_ui_appmain ) @@ -226,6 +227,7 @@ xaml_fix_char8_t_flags() add_subdirectory(global) add_subdirectory(helpers) add_subdirectory(meta) +add_subdirectory(async) add_subdirectory(markup) add_subdirectory(ui) if(${BUILD_PARSER}) diff --git a/async/CMakeLists.txt b/async/CMakeLists.txt new file mode 100644 index 00000000..d3199b80 --- /dev/null +++ b/async/CMakeLists.txt @@ -0,0 +1,27 @@ +project(XamlAsync CXX) + +file(GLOB ASYNC_HEADERS "include/xaml/async/*.h") +file(GLOB ASYNC_SOURCE "src/*.cpp") +add_library(xaml_async ${ASYNC_SOURCE}) + +target_include_directories(xaml_async + PUBLIC + $ + $ + PRIVATE + $ +) + +target_link_libraries(xaml_async PUBLIC xaml_global xaml_meta) + +if(${BUILD_SHARED_LIBS}) + target_compile_definitions(xaml_async PRIVATE "XAML_ASYNC_API=__XAML_EXPORT") +endif() + +if(${XAML_INSTALL}) + install(FILES ${ASYNC_HEADERS} DESTINATION include/xaml) +endif() + +if(${BUILD_TESTS}) + add_subdirectory(test) +endif() diff --git a/async/include/xaml/async/action.h b/async/include/xaml/async/action.h new file mode 100644 index 00000000..202e6012 --- /dev/null +++ b/async/include/xaml/async/action.h @@ -0,0 +1,188 @@ +#ifndef XAML_ASYNC_ACTION_H +#define XAML_ASYNC_ACTION_H + +#include +#include +#include +#include + +#ifdef __cplusplus + #include + #include + #include +#endif // __cplusplus + +XAML_CLASS(xaml_async_action, { 0xe31c7f48, 0x8af4, 0x46af, { 0xac, 0x54, 0x01, 0xa1, 0xc1, 0xea, 0xea, 0x1e } }) + +#ifndef xaml_delegate_2__xaml_async_action__xaml_async_status_defined + #define xaml_delegate_2__xaml_async_action__xaml_async_status_defined +XAML_DELEGATE_2_TYPE(XAML_T_O(xaml_async_action), XAML_T_V(xaml_async_status)) +#endif // !xaml_delegate_2__xaml_async_action__xaml_async_status_defined + +#define XAML_ASYNC_ACTION_VTBL(type) \ + XAML_VTBL_INHERIT(XAML_ASYNC_INFO_VTBL(type)); \ + XAML_PROP(completed, type, XAML_DELEGATE_2_NAME(xaml_async_action, xaml_async_status)**, XAML_DELEGATE_2_NAME(xaml_async_action, xaml_async_status)*) + +XAML_DECL_INTERFACE_(xaml_async_action, xaml_async_info) +{ + XAML_DECL_VTBL(xaml_async_action, XAML_ASYNC_ACTION_VTBL); +}; + +#ifndef xaml_enumerator_1__xaml_async_action_defined + #define xaml_enumerator_1__xaml_async_action_defined +XAML_ENUMERATOR_1_TYPE(XAML_T_O(xaml_async_action)) +#endif // !xaml_enumerator_1__xaml_async_action_defined + +#ifndef xaml_vector_view_1__xaml_async_action_defined + #define xaml_vector_view_1__xaml_async_action_defined +XAML_VECTOR_VIEW_1_TYPE(XAML_T_O(xaml_async_action)) +#endif // !xaml_vector_view_1__xaml_async_action_defined + +EXTERN_C XAML_ASYNC_API xaml_result XAML_CALL xaml_async_action_wait_all(XAML_VECTOR_VIEW_1_NAME(xaml_async_action) *) XAML_NOEXCEPT; + +#ifdef __cplusplus +template +struct xaml_async_promise_base : xaml_implement +{ + std::exception_ptr m_exception{}; + std::atomic m_status{}; + xaml_ptr> m_handler{}; + + std::coroutine_handle get_handle() { return std::coroutine_handle::from_promise(*static_cast(this)); } + + std::uint32_t XAML_CALL release() noexcept override + { + std::int32_t res = --this->m_ref_count; + if (res == 0) + { + get_handle().destroy(); + } + return res; + } + + xaml_result XAML_CALL get_completed(xaml_delegate** ptr) noexcept override + { + return m_handler.query(ptr); + } + + xaml_result XAML_CALL set_completed(xaml_delegate* h) noexcept override + { + m_handler = h; + if (m_status != xaml_async_started) + { + return invoke_completed(); + } + return XAML_S_OK; + } + + xaml_result invoke_completed() noexcept + { + if (m_handler) + { + return m_handler->invoke(this, m_status); + } + return XAML_S_OK; + } + + xaml_ptr get_return_object() noexcept { return this; } + std::suspend_always initial_suspend() const noexcept { return {}; } + auto final_suspend() noexcept + { + struct awaitable + { + xaml_async_promise_base* m_promise; + + bool await_ready() const noexcept { return false; } + void await_resume() const noexcept {} + + bool await_suspend(std::coroutine_handle<>) const noexcept + { + m_promise->invoke_completed(); + return m_promise->release() > 0; + } + }; + if (m_status == xaml_async_started) m_status = xaml_async_completed; + return awaitable{ this }; + } + + void unhandled_exception() noexcept + { + m_exception = std::current_exception(); + m_status = xaml_async_error; + } + + void rethrow_if_failed() const + { + if (m_status == xaml_async_error) + { + std::rethrow_exception(m_exception); + } + } + + xaml_result XAML_CALL get_error() noexcept override + try + { + auto handle = get_handle(); + while (!handle.done()) handle.resume(); + rethrow_if_failed(); + return XAML_S_OK; + } + XAML_CATCH_RETURN() + + xaml_result XAML_CALL get_status(xaml_async_status* ptr) noexcept override + { + *ptr = m_status; + return XAML_S_OK; + } +}; + +auto operator co_await(xaml_ptr action) +{ + struct awaitable + { + xaml_ptr m_action; + + bool await_ready() const + { + xaml_async_status status; + XAML_THROW_IF_FAILED(m_action->get_status(&status)); + return status != xaml_async_started; + } + + void await_resume() const noexcept {} + + void await_suspend(std::coroutine_handle<> h) const + { + auto action = m_action; + xaml_ptr> handler; + XAML_THROW_IF_FAILED(xaml_delegate_new( + [h](xaml_ptr const&, xaml_async_status) noexcept -> xaml_result { + try + { + h.resume(); + return XAML_S_OK; + } + XAML_CATCH_RETURN() + }, + &handler)); + XAML_THROW_IF_FAILED(action->set_completed(handler)); + XAML_THROW_IF_FAILED(action->get_error()); + } + }; + return awaitable{ action }; +} + +namespace std +{ + template + struct coroutine_traits, Args...> + { + struct promise_type final : xaml_async_promise_base + { + void return_void() const noexcept {} + }; + }; +} // namespace std +#endif // __cplusplus + +#endif // !XAML_ASYNC_ACTION_H diff --git a/async/include/xaml/async/info.h b/async/include/xaml/async/info.h new file mode 100644 index 00000000..a5e5fee2 --- /dev/null +++ b/async/include/xaml/async/info.h @@ -0,0 +1,27 @@ +#ifndef XAML_ASYNC_INFO_H +#define XAML_ASYNC_INFO_H + +#include + +typedef enum xaml_async_status +{ + xaml_async_started, + xaml_async_completed, + xaml_async_error +} xaml_async_status; + +XAML_TYPE(xaml_async_status, { 0x2e1ff26a, 0x5de9, 0x4d44, { 0x8d, 0xbd, 0xcf, 0xf6, 0xd2, 0xe8, 0x12, 0xf7 } }) + +XAML_CLASS(xaml_async_info, { 0x0d9e61f6, 0x5eb4, 0x4d82, { 0x9e, 0xb0, 0x98, 0x2f, 0xf8, 0x20, 0x82, 0x23 } }) + +#define XAML_ASYNC_INFO_VTBL(type) \ + XAML_VTBL_INHERIT(XAML_OBJECT_VTBL(type)); \ + XAML_METHOD(get_error, type); \ + XAML_METHOD(get_status, type, xaml_async_status*) + +XAML_DECL_INTERFACE_(xaml_async_info, xaml_object) +{ + XAML_DECL_VTBL(xaml_async_info, XAML_ASYNC_INFO_VTBL); +}; + +#endif // !XAML_ASYNC_INFO_H diff --git a/async/src/action.cpp b/async/src/action.cpp new file mode 100644 index 00000000..9e5356b6 --- /dev/null +++ b/async/src/action.cpp @@ -0,0 +1,8 @@ +#include + +using namespace std; + +xaml_result XAML_CALL xaml_async_action_wait_all(xaml_vector_view* actions) noexcept +{ + return XAML_E_NOTIMPL; +} diff --git a/async/test/CMakeLists.txt b/async/test/CMakeLists.txt new file mode 100644 index 00000000..0571586b --- /dev/null +++ b/async/test/CMakeLists.txt @@ -0,0 +1,6 @@ +project(AsyncTest CXX) + +file(GLOB TEST_SOURCE "src/*.cpp") + +add_executable(async_test ${TEST_SOURCE}) +target_link_libraries(async_test xaml_async stream_format nowide) diff --git a/async/test/src/main.cpp b/async/test/src/main.cpp new file mode 100644 index 00000000..ded3719a --- /dev/null +++ b/async/test/src/main.cpp @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include + +auto operator co_await(std::chrono::system_clock::duration duration) +{ + struct awaitable + { + std::chrono::system_clock::duration m_duration; + std::thread m_thread; + + explicit awaitable(std::chrono::system_clock::duration d) + : m_duration(d), + m_thread( + [d] { + if (d.count() > 0) std::this_thread::sleep_for(d); + }) + { + } + + bool await_ready() const + { + return m_duration.count() <= 0; + } + + auto await_suspend(std::coroutine_handle<> h) + { + m_thread.join(); + return h; + } + + void await_resume() {} + }; + return awaitable{ duration }; +} + +using namespace sf; +using nowide::cout; +using namespace std::chrono_literals; + +xaml_ptr foo() +{ + println(cout, "Here is in foo."); + co_await 1s; + println(cout, "Here is also in foo."); +} + +xaml_ptr bar() +{ + println(cout, "Here is in bar."); + auto task = foo(); + println(cout, "Here is also in bar."); + co_await task; + println(cout, "Now back to bar once again."); + throw std::bad_alloc{}; +} + +int main() +{ + auto action = bar(); + try + { + XAML_THROW_IF_FAILED(action->get_error()); + } + catch (xaml_result_error const& e) + { + println(cout, xaml_result_get_message(e.get_result())); + } + println(cout, "Back to main."); +} diff --git a/global/include/xaml/observable_vector.h b/global/include/xaml/observable_vector.h index 256d3f21..4ec417e1 100644 --- a/global/include/xaml/observable_vector.h +++ b/global/include/xaml/observable_vector.h @@ -89,7 +89,9 @@ xaml_result XAML_CALL xaml_vector_changed_args_new(xaml_vector_changed_action ac XAML_TYPE_BASE(xaml_observable_vector_1, { 0xc84cb35f, 0x0a1c, 0x40e2, { 0x8e, 0x1c, 0x2c, 0x43, 0x0b, 0x1b, 0xb6, 0xcf } }) -#define __XAML_DELEGATE_2_NAME(a, b) XAML_DELEGATE_2_NAME(a, b) +#ifndef __XAML_DELEGATE_2_NAME + #define __XAML_DELEGATE_2_NAME(a, b) XAML_DELEGATE_2_NAME(a, b) +#endif // !__XAML_DELEGATE_2_NAME #define XAML_OBSERVABLE_VECTOR_1_VTBL(type, TN, TI) \ XAML_VTBL_INHERIT(XAML_VECTOR_1_VTBL(type, TN, TI)); \ diff --git a/global/include/xaml/utility.h b/global/include/xaml/utility.h index e399fe12..7ca0ef41 100644 --- a/global/include/xaml/utility.h +++ b/global/include/xaml/utility.h @@ -49,14 +49,14 @@ #define XAML_META_API __XAML_IMPORT #endif // !XAML_META_API +#ifndef XAML_ASYNC_API + #define XAML_ASYNC_API __XAML_IMPORT +#endif // !XAML_ASYNC_API + #ifndef XAML_UI_API #define XAML_UI_API __XAML_IMPORT #endif // !XAML_UI_API -#ifndef XAML_UI_META_API - #define XAML_UI_META_API __XAML_META_IMPORT -#endif // !XAML_UI_META_API - #ifndef XAML_UI_CONTROLS_API #define XAML_UI_CONTROLS_API __XAML_IMPORT #endif // !XAML_UI_CONTROLS_API