57template<
typename T,
typename Key>
58struct AsyncResourceState {
61 CancellationSource cancel;
65 Property<bool> is_loading{
false};
66 Property<std::optional<::aria::Error>> error{std::nullopt};
67 Property<std::string> error_message{
""};
68 Property<std::optional<T>> data{std::optional<T>{}};
80 std::atomic<bool> dirty{
false};
81 std::atomic<bool> in_flight{
false};
82 std::atomic<std::uint64_t> gen{0};
84 AsyncResourceState(IExecutor& u, IExecutor& w) : ui(&u), worker(&w) {}
91 void recompute_loadable_() {
92 const bool loading_now = is_loading.peek();
93 const std::optional<T> d = data.peek();
94 const std::optional<::aria::Error> e = error.peek();
123template<
typename T,
typename Key =
int>
125 using State = detail::AsyncResourceState<T, Key>;
126 std::shared_ptr<State> state_;
136 : state_(
std::make_shared<detail::AsyncResourceState<T, Key>>(ui, worker)),
137 fetcher_(
std::move(fetcher)),
153 bool same_key = state_->has_key
154 && state_->current_key == key;
156 if (same_key && !state_->dirty.load(std::memory_order_acquire)
158 &&
data.get().has_value()) {
162 state_->gen.load(std::memory_order_relaxed)});
166 if (same_key && state_->in_flight.load(std::memory_order_acquire)) {
170 state_->gen.load(std::memory_order_relaxed)});
175 state_->current_key = key;
176 state_->has_key =
true;
181 if (!state_->has_key)
return;
183 do_fetch_(state_->current_key);
187 state_->dirty.store(
true, std::memory_order_release);
191 const auto state = state_;
193 state->has_key =
false;
194 state->dirty.store(
false, std::memory_order_release);
195 state->is_loading =
false;
196 state->error = std::nullopt;
197 state->error_message =
"";
198 state->data = std::optional<T>{};
199 state->recompute_loadable_();
221 const auto state = state_;
226 state->cancel.cancel();
234 state->gen.fetch_add(1, std::memory_order_acq_rel);
235 state->in_flight.store(
false, std::memory_order_release);
237 state->is_loading =
false;
238 state->recompute_loadable_();
242 state->gen.load(std::memory_order_relaxed)});
271 void do_fetch_(Key key) {
274 const auto state = state_;
275 auto fetcher = fetcher_;
276 const auto tok = state->cancel.token();
277 state->in_flight.store(
true, std::memory_order_release);
278 state->dirty.store(
false, std::memory_order_release);
279 const auto my_gen = state->gen.fetch_add(1, std::memory_order_acq_rel) + 1;
281 return !tok.is_cancelled() && state->gen.load(std::memory_order_acquire) == my_gen;
286 state->is_loading =
true;
287 if (!current()) {
return; }
288 state->error = std::nullopt;
289 if (!current()) {
return; }
290 state->error_message =
"";
291 if (!current()) {
return; }
292 state->recompute_loadable_();
293 if (!current()) {
return; }
298 auto runner = run_one_(state, std::move(fetcher), std::move(key), my_gen, tok);
299 std::move(runner).start_detached();
306 if (state->gen.load(std::memory_order_acquire) != my_gen) {
co_return; }
308 std::exception_ptr ex;
309 std::optional<T> result;
313 T v =
co_await fetcher(key);
314 result.emplace(std::move(v));
316 ex = std::current_exception();
329 if (state->gen.load(std::memory_order_acquire) != my_gen) {
332 ::aria::trace::Async{
"AsyncResource",
"stale_drop", my_gen});
338 try { std::rethrow_exception(ex); }
339 catch (
const OperationCancelled&) {
345 ::aria::trace::Async{
"AsyncResource",
"cancelled", my_gen},
349 catch (
const TimeoutError& e) {
351 err.message = e.what();
354 ::aria::trace::Async{
"AsyncResource",
"timeout", my_gen},
357 state->error_message = err.message;
358 state->error = std::move(err);
364 ::aria::trace::Async{
"AsyncResource",
"failure", my_gen},
367 state->error_message = err.message;
368 state->error = std::move(err);
371 state->data = result;
374 ::aria::trace::Async{
"AsyncResource",
"fetch_finish", my_gen});
377 finish_fetch(state, my_gen, tok);
380 static void finish_fetch(
const std::shared_ptr<State>& state,
381 std::uint64_t my_gen,
const CancellationToken& tok) {
383 if (tok.is_cancelled() || state->gen.load(std::memory_order_acquire) != my_gen) {
386 state->in_flight.store(
false, std::memory_order_release);
387 state->is_loading =
false;
388 state->recompute_loadable_();
static Loadable loading()
Definition loadable.hpp:81
static Loadable refreshing(T prior)
Build a Refreshing state from an existing Success payload.
Definition loadable.hpp:89
const Error * error() const noexcept
Returns a pointer to the error if has_error(), else nullptr.
Definition loadable.hpp:160
static Loadable success(T v)
Definition loadable.hpp:96
static Loadable idle() noexcept
Definition loadable.hpp:79
void clear()
Definition async_resource.hpp:190
AsyncResource(const AsyncResource &)=delete
Property< std::string > & error_message
Definition async_resource.hpp:261
Property< std::optional< T > > & data
Definition async_resource.hpp:262
AsyncResource & operator=(const AsyncResource &)=delete
Property<::aria::Loadable< T > > & loadable
Five-state loadable view-model – Idle / Loading / Refreshing / Success / Error.
Definition async_resource.hpp:268
void fetch(Key key)
Fetch for key.
Definition async_resource.hpp:152
Property< bool > & is_loading
Definition async_resource.hpp:259
AsyncResource(IExecutor &ui, IExecutor &worker, Fetcher fetcher)
Definition async_resource.hpp:135
void cancel()
Cancel any in-flight fetch and drop its pending write-back, WITHOUT destroying the resource.
Definition async_resource.hpp:220
bool has_data() const
Definition async_resource.hpp:246
~AsyncResource()
Definition async_resource.hpp:144
void invalidate() noexcept
Definition async_resource.hpp:186
void refresh()
Definition async_resource.hpp:180
std::function< Task< T >(Key)> Fetcher
Definition async_resource.hpp:129
Property< std::optional<::aria::Error > > & error
Definition async_resource.hpp:260
Definition cancellation.hpp:221
Definition cancellation.hpp:182
void throw_if_cancelled() const
Throw OperationCancelled if cancelled. Call at safe await points.
Definition cancellation.hpp:193
Abstract executor interface — schedules a callable to run "somewhere".
Definition executor.hpp:37
Definition property.hpp:103
Definition async_command.hpp:118
auto schedule_on(IExecutor &exec)
Schedule a coroutine to resume on the given executor.
Definition executor.hpp:472
void publish_trace_unchecked(const TraceEvent &event) noexcept
Publish an already-built event using one owning sink snapshot.
Definition diagnostics.hpp:293
bool has_trace_sink() noexcept
True iff a sink is currently installed.
Definition diagnostics.hpp:286
@ Async
AsyncCommand / AsyncResource lifecycle.
Definition diagnostics.hpp:68
Definition validation_key.hpp:110
static Error from_exception(std::exception_ptr ex, std::string source_tag)
Catch-all converter from a thrown exception_ptr to a typed Error.
Definition error.hpp:237
static Error cancellation(std::string source_tag="AsyncCommand")
Cancellation.
Definition error.hpp:192
static Error timeout(std::string source_tag="AsyncCommand")
with_timeout deadline expired.
Definition error.hpp:198
Async lifecycle.
Definition diagnostics.hpp:128