|
| 1 | +#include <atomic> |
| 2 | + |
1 | 3 | #include "application.h" |
| 4 | +#include "application_quit_dispatch.h" |
| 5 | +#include "foundation/dispatcher.h" |
| 6 | +#include "foundation/event_request_dispatch.h" |
2 | 7 |
|
3 | 8 | namespace nativeapi { |
4 | 9 |
|
| 10 | +struct detail::ApplicationQuitState { |
| 11 | + std::shared_ptr<EventRequest> request; |
| 12 | + std::atomic<bool> dispatch_failed{false}; |
| 13 | + bool retain_for_host = false; |
| 14 | + bool has_loop_owner = false; |
| 15 | + std::weak_ptr<void> loop_owner; |
| 16 | +}; |
| 17 | + |
5 | 18 | Application& Application::GetInstance() { |
6 | 19 | static Application instance; |
7 | 20 | return instance; |
8 | 21 | } |
9 | 22 |
|
| 23 | +void Application::RequestQuit(int exit_code, |
| 24 | + std::function<void(int)> stop_loop, |
| 25 | + std::shared_ptr<void> loop_owner, |
| 26 | + std::function<void(bool)> native_confirmation) try { |
| 27 | + const bool has_loop_owner = !!loop_owner; |
| 28 | + auto dispatch = CreateGuardedCallback<>( |
| 29 | + std::function<void()>([this, exit_code, stop_loop = std::move(stop_loop), has_loop_owner, |
| 30 | + weak_owner = std::weak_ptr<void>(loop_owner), |
| 31 | + native_confirmation = std::move(native_confirmation)] { |
| 32 | + try { |
| 33 | + if (has_loop_owner && weak_owner.expired()) |
| 34 | + return; |
| 35 | + exit_code_ = exit_code; |
| 36 | + if (quit_state_) { |
| 37 | + if (!quit_state_->dispatch_failed.load() && |
| 38 | + !(quit_state_->has_loop_owner && quit_state_->loop_owner.expired())) |
| 39 | + return; |
| 40 | + InvalidateQuitRequest(); |
| 41 | + } |
| 42 | + |
| 43 | + auto state = std::make_shared<detail::ApplicationQuitState>(); |
| 44 | + state->retain_for_host = !!native_confirmation; |
| 45 | + state->has_loop_owner = has_loop_owner; |
| 46 | + state->loop_owner = weak_owner; |
| 47 | + std::weak_ptr<detail::ApplicationQuitState> weak_state = state; |
| 48 | + |
| 49 | + auto finish = CreateGuardedCallback<bool>(std::function<void(bool)>( |
| 50 | + [this, stop_loop, weak_state, native_confirmation](bool accepted) { |
| 51 | + // Keep the request installed until its completion reaches the UI thread. |
| 52 | + // Reentrant and worker requests cannot start a second confirmation. |
| 53 | + auto state = weak_state.lock(); |
| 54 | + if (!state || quit_state_ != state) |
| 55 | + return; |
| 56 | + if (!accepted || (state->has_loop_owner && state->loop_owner.expired())) { |
| 57 | + quit_state_.reset(); |
| 58 | + if (native_confirmation) |
| 59 | + native_confirmation(false); |
| 60 | + return; |
| 61 | + } |
| 62 | + // Keep this attempt installed through its action too. An exiting |
| 63 | + // listener can call Quit(), but must not start recursive termination. |
| 64 | + const int code = exit_code_; |
| 65 | + try { |
| 66 | + if (native_confirmation) { |
| 67 | + native_confirmation(true); |
| 68 | + } else if (stop_loop) { |
| 69 | + // A notification failure cannot undo an already approved quit. |
| 70 | + try { |
| 71 | + Emit<ApplicationExitingEvent>(code); |
| 72 | + } catch (...) { |
| 73 | + } |
| 74 | + stop_loop(code); |
| 75 | + } else { |
| 76 | + PerformQuit(code); |
| 77 | + } |
| 78 | + } catch (...) { |
| 79 | + } |
| 80 | + if (quit_state_ == state && !state->retain_for_host) |
| 81 | + quit_state_.reset(); |
| 82 | + })); |
| 83 | + std::shared_ptr<EventRequest> request; |
| 84 | + try { |
| 85 | + request = |
| 86 | + detail::EventRequestDispatch::Create(true, [finish, weak_state](bool accepted) { |
| 87 | + auto complete = [finish, accepted] { finish(accepted); }; |
| 88 | + if (IsMainThread()) |
| 89 | + complete(); |
| 90 | + else { |
| 91 | + try { |
| 92 | + if (RunOnMainThread(std::move(complete))) |
| 93 | + return; |
| 94 | + } catch (...) { |
| 95 | + } |
| 96 | + if (auto state = weak_state.lock()) |
| 97 | + state->dispatch_failed = true; |
| 98 | + } |
| 99 | + }); |
| 100 | + } catch (...) { |
| 101 | + // Allocation failure must not silently approve termination. |
| 102 | + return; |
| 103 | + } |
| 104 | + state->request = request; |
| 105 | + quit_state_ = state; |
| 106 | + try { |
| 107 | + Emit<ApplicationQuitRequestedEvent>(request); |
| 108 | + } catch (...) { |
| 109 | + request->Cancel(); |
| 110 | + } |
| 111 | + detail::EventRequestDispatch::Finish(request); |
| 112 | + } catch (...) { |
| 113 | + // The UI scheduler invokes this after a worker Quit() has returned. |
| 114 | + // Failure here must also leave the application running. |
| 115 | + } |
| 116 | + })); |
| 117 | + if (IsMainThread()) |
| 118 | + dispatch(); |
| 119 | + else |
| 120 | + (void)RunOnMainThread(std::move(dispatch)); |
| 121 | +} catch (...) { |
| 122 | + // Public Quit() never throws, including failures to allocate/schedule work. |
| 123 | +} |
| 124 | + |
| 125 | +void Application::InvalidateQuitRequest() { |
| 126 | + if (quit_state_) |
| 127 | + detail::EventRequestDispatch::Invalidate(quit_state_->request); |
| 128 | + quit_state_.reset(); |
| 129 | +} |
| 130 | + |
| 131 | +void detail::ApplicationQuitDispatch::RequestForLoop(int exit_code, |
| 132 | + std::function<void(int)> stop_loop, |
| 133 | + std::shared_ptr<void> loop_owner) { |
| 134 | + if (stop_loop) |
| 135 | + Application::GetInstance().RequestQuit(exit_code, std::move(stop_loop), std::move(loop_owner)); |
| 136 | +} |
| 137 | + |
| 138 | +void detail::ApplicationQuitDispatch::CancelForLoop(std::weak_ptr<void> loop_owner) try { |
| 139 | + auto& app = Application::GetInstance(); |
| 140 | + auto cancel = app.CreateGuardedCallback<>(std::function<void()>([&app, loop_owner] { |
| 141 | + const auto& state = app.quit_state_; |
| 142 | + if (state && state->has_loop_owner && !state->loop_owner.owner_before(loop_owner) && |
| 143 | + !loop_owner.owner_before(state->loop_owner)) |
| 144 | + app.InvalidateQuitRequest(); |
| 145 | + })); |
| 146 | + if (IsMainThread()) |
| 147 | + cancel(); |
| 148 | + else |
| 149 | + (void)RunOnMainThread(std::move(cancel)); |
| 150 | +} catch (...) { |
| 151 | + // Expiring the binding's owner also fences later requests and completions, |
| 152 | + // including when scheduling this eager invalidation fails. |
| 153 | +} |
| 154 | + |
| 155 | +std::shared_ptr<EventRequest> detail::ApplicationQuitDispatch::CurrentRequest() { |
| 156 | + auto& app = Application::GetInstance(); |
| 157 | + if (!IsMainThread()) |
| 158 | + return nullptr; |
| 159 | + const auto& state = app.quit_state_; |
| 160 | + if (state && |
| 161 | + (state->dispatch_failed.load() || (state->has_loop_owner && state->loop_owner.expired()))) |
| 162 | + app.InvalidateQuitRequest(); |
| 163 | + return app.quit_state_ ? app.quit_state_->request : nullptr; |
| 164 | +} |
| 165 | +void detail::ApplicationQuitDispatch::RequestFromNative(std::function<void(bool)> confirmation) { |
| 166 | + if (confirmation && IsMainThread()) |
| 167 | + Application::GetInstance().RequestQuit(0, nullptr, nullptr, std::move(confirmation)); |
| 168 | +} |
| 169 | +void detail::ApplicationQuitDispatch::HoldForHost(const std::shared_ptr<EventRequest>& request) { |
| 170 | + auto& app = Application::GetInstance(); |
| 171 | + if (IsMainThread() && app.quit_state_ && app.quit_state_->request == request) |
| 172 | + app.quit_state_->retain_for_host = true; |
| 173 | +} |
| 174 | +void detail::ApplicationQuitDispatch::ReleaseFromHost( |
| 175 | + const std::shared_ptr<EventRequest>& request) { |
| 176 | + auto& app = Application::GetInstance(); |
| 177 | + if (IsMainThread() && app.quit_state_ && app.quit_state_->request == request) |
| 178 | + app.InvalidateQuitRequest(); |
| 179 | +} |
| 180 | +int detail::ApplicationQuitDispatch::ExitCode() { |
| 181 | + return Application::GetInstance().exit_code_; |
| 182 | +} |
| 183 | +void detail::ApplicationQuitDispatch::NotifyRequired() try { |
| 184 | + auto& app = Application::GetInstance(); |
| 185 | + if (!IsMainThread()) |
| 186 | + return; |
| 187 | + app.InvalidateQuitRequest(); |
| 188 | + auto request = EventRequestDispatch::Create(false, nullptr); |
| 189 | + try { |
| 190 | + app.Emit<ApplicationQuitRequestedEvent>(request); |
| 191 | + } catch (...) { |
| 192 | + } |
| 193 | + EventRequestDispatch::Finish(request); |
| 194 | +} catch (...) { |
| 195 | + // Mandatory native termination cannot be vetoed by a failed notification. |
| 196 | +} |
| 197 | + |
10 | 198 | int RunApp(std::shared_ptr<Window> window) { |
11 | 199 | return Application::GetInstance().Run(window); |
12 | 200 | } |
|
0 commit comments