Unravel Engine C++ Reference
Loading...
Searching...
No Matches
asset_handle.h
Go to the documentation of this file.
1#pragma once
2
3#include <engine/engine_export.h>
4#include <hpp/filesystem.hpp>
5#include <logging/logging.h>
6
7#include <atomic>
8#include <chrono>
9#include <memory>
10#include <mutex>
11#include <string>
12#include <uuid/uuid.h>
13
15
16template<typename T>
17using task_future = tpp::job_shared_future<T>;
18
19template<typename T>
20struct asset_handle;
21
22namespace asset_detail
23{
24
40template<typename T>
42{
43public:
44 atomic_shared_ptr() = default;
45 explicit atomic_shared_ptr(std::shared_ptr<T> initial) : ptr_(std::move(initial)) {}
46
47 auto load(std::memory_order order = std::memory_order_seq_cst) const -> std::shared_ptr<T>
48 {
49#if defined(__cpp_lib_atomic_shared_ptr) && (__cpp_lib_atomic_shared_ptr >= 201711L)
50 return ptr_.load(order);
51#else
52 return std::atomic_load_explicit(&ptr_, order);
53#endif
54 }
55
56 void store(std::shared_ptr<T> desired, std::memory_order order = std::memory_order_seq_cst)
57 {
58#if defined(__cpp_lib_atomic_shared_ptr) && (__cpp_lib_atomic_shared_ptr >= 201711L)
59 ptr_.store(std::move(desired), order);
60#else
61 std::atomic_store_explicit(&ptr_, std::move(desired), order);
62#endif
63 }
64
65private:
66#if defined(__cpp_lib_atomic_shared_ptr) && (__cpp_lib_atomic_shared_ptr >= 201711L)
67 std::atomic<std::shared_ptr<T>> ptr_{};
68#else
69 std::shared_ptr<T> ptr_{};
70#endif
71};
72
73} // namespace asset_detail
74
91template<typename T>
93{
95 using weak_asset_t = std::weak_ptr<T>;
96
104 struct state_t
105 {
107 hpp::uuid uid;
109 std::string id;
112 };
113
121
126 mutable std::mutex weak_asset_mtx;
128
132 mutable std::atomic<int64_t> last_access_ns{0};
133};
134
143template<typename T>
145{
148 using state_ptr = std::shared_ptr<state_t>;
150
154 auto operator==(const asset_handle& rhs) const -> bool
155 {
156 return uid() == rhs.uid() && id() == rhs.id() && is_valid() == rhs.is_valid();
157 }
158
159 auto version() const -> uintptr_t
160 {
161 update_last_access();
162 return uintptr_t(peek().get());
163 }
164
169 operator bool() const
170 {
171 return is_valid();
172 }
173
181 auto id() const -> std::string
182 {
183 if(auto s = load_state())
184 {
185 return s->id;
186 }
187 return {};
188 }
189
197 auto uid() const -> hpp::uuid
198 {
199 if(auto s = load_state())
200 {
201 return s->uid;
202 }
203 return {};
204 }
205
209 auto name() const -> std::string
210 {
211 return fs::path(id()).stem().string();
212 }
213
214 auto extension() const -> std::string
215 {
216 return fs::path(id()).extension().string();
217 }
218
227 auto get(bool wait = true) const -> std::shared_ptr<T>
228 {
229 update_last_access();
230
231 if(auto cached_asset = peek())
232 {
233 return cached_asset;
234 }
235
236 // Take a stable snapshot. Even if another thread invalidates the link
237 // mid-call, our local `s` keeps the old state alive for the duration.
238 auto s = load_state();
239 if(!s || !s->task.valid())
240 {
241 return empty_asset();
242 }
243
244 if(!s->task.is_submitted())
245 {
246 s->task.submit();
247 }
248
249 const bool ready = s->task.is_ready();
250 const bool should_get = ready || wait;
251 if(!should_get)
252 {
253 return empty_asset();
254 }
255
256 // Copy the task locally; we may need to change priority. task.get()
257 // may block — we must hold no locks here.
258 auto task = s->task;
259 if(!ready)
260 {
261 task.change_priority(tpp::priority::high());
262 }
263
264 auto value = task.get();
265 if(value)
266 {
267 std::lock_guard<std::mutex> lock(link_->weak_asset_mtx);
268 link_->weak_asset = value;
269 return value;
270 }
271 return empty_asset();
272 }
273
277 auto is_valid() const -> bool
278 {
279 auto s = load_state();
280 return s && s->task.valid();
281 }
282
286 auto is_ready() const -> bool
287 {
288 auto s = load_state();
289 return s && s->task.valid() && s->task.is_ready();
290 }
291
295 auto is_deferred() const -> bool
296 {
297 auto s = load_state();
298 return s && s->task.valid() && !s->task.is_submitted();
299 }
300
304 auto is_loading() const -> bool
305 {
306 auto s = load_state();
307 return s && s->task.valid() && s->task.is_submitted() && !s->task.is_ready();
308 }
309
314 void submit()
315 {
316 auto s = load_state();
317 if(s && s->task.valid() && !s->task.is_submitted())
318 {
319 s->task.submit();
320 }
321 }
322
331 {
332 if(!link_)
333 {
334 return;
335 }
336
337 auto old = load_state();
338 auto fresh = std::make_shared<state_t>();
339 if(old)
340 {
341 fresh->uid = old->uid;
342 fresh->id = old->id;
343 }
344 link_->state.store(std::move(fresh), std::memory_order_release);
345
346 std::lock_guard<std::mutex> lock(link_->weak_asset_mtx);
347 link_->weak_asset.reset();
348 }
349
353 auto last_access() const -> std::chrono::steady_clock::time_point
354 {
355 if(!link_)
356 {
357 return {};
358 }
359 const auto ns = link_->last_access_ns.load(std::memory_order_relaxed);
360 return std::chrono::steady_clock::time_point(std::chrono::nanoseconds(ns));
361 }
362
366 auto task_id() const
367 {
368 if(auto s = load_state(); s && s->task.valid())
369 {
370 return s->task.id;
371 }
372 return tpp::job_id{};
373 }
374
381 void set_internal_job(const task_future_t& future)
382 {
383 ensure();
384 publish_state(
385 [&](state_t& fresh) -> void
386 {
387 fresh.task = future;
388 });
389
390 std::lock_guard<std::mutex> lock(link_->weak_asset_mtx);
391 link_->weak_asset.reset();
392 }
393
399 void set_internal_ids(const hpp::uuid& internal_uid, const std::string& internal_id = get_empty_id())
400 {
401 ensure();
402 publish_state(
403 [&](state_t& fresh) -> void
404 {
405 fresh.uid = internal_uid;
406 fresh.id = internal_id;
407 });
408 }
409
415 void set_internal_id(const std::string& internal_id = get_empty_id())
416 {
417 ensure();
418 publish_state(
419 [&](state_t& fresh) -> void
420 {
421 fresh.id = internal_id;
422 });
423 }
424
432 {
433 if(auto s = load_state(); s && s->task.valid())
434 {
435 const auto task_count = s->task.use_count();
436 if(task_count > 1)
437 {
438 APPLOG_TRACE("{} - task leak use_count {}", s->id, task_count);
439 }
440 }
441
442 if(!link_)
443 {
444 return;
445 }
446 link_->state.store(std::make_shared<state_t>(), std::memory_order_release);
447
448 std::lock_guard<std::mutex> lock(link_->weak_asset_mtx);
449 link_->weak_asset.reset();
450 }
451
455 static auto get_empty() -> const asset_handle&
456 {
457 static const asset_handle none_asset = []() -> asset_handle
458 {
459 asset_handle asset;
460 asset.set_internal_ids({});
461 return asset;
462 }();
463 return none_asset;
464 }
465
469 static auto get_empty_id() -> const std::string&
470 {
471 static const std::string empty{"None"};
472 return empty;
473 }
474
485 auto peek() const -> std::shared_ptr<T>
486 {
487 if(!link_)
488 {
489 return nullptr;
490 }
491 std::lock_guard<std::mutex> lock(link_->weak_asset_mtx);
492 return link_->weak_asset.lock();
493 }
494
498 void ensure()
499 {
500 static_assert(sizeof(asset_link_t) >= 1, "Type must be fully defined");
501 if(!link_)
502 {
503 link_ = std::make_shared<asset_link_t>();
504 }
505 }
506
507private:
511 auto load_state() const -> state_ptr
512 {
513 if(!link_)
514 {
515 return nullptr;
516 }
517 return link_->state.load(std::memory_order_acquire);
518 }
519
526 template<typename F>
527 void publish_state(F&& mutator)
528 {
529 auto old = load_state();
530 auto fresh = std::make_shared<state_t>();
531 if(old)
532 {
533 *fresh = *old;
534 }
535 std::forward<F>(mutator)(*fresh);
536 link_->state.store(std::move(fresh), std::memory_order_release);
537 }
538
543 static auto empty_asset() -> std::shared_ptr<T>
544 {
545 static const std::shared_ptr<T> empty = std::make_shared<T>();
546 return empty;
547 }
548
549 void update_last_access() const
550 {
551 if(!link_)
552 {
553 return;
554 }
555 const auto now = std::chrono::steady_clock::now().time_since_epoch();
556 const auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
557 link_->last_access_ns.store(ns, std::memory_order_relaxed);
558 }
559
561 std::shared_ptr<asset_link_t> link_;
562};
tpp::job_shared_future< T > task_future
auto load(std::memory_order order=std::memory_order_seq_cst) const -> std::shared_ptr< T >
atomic_shared_ptr(std::shared_ptr< T > initial)
void store(std::shared_ptr< T > desired, std::memory_order order=std::memory_order_seq_cst)
#define APPLOG_TRACE(...)
Definition logging.h:17
Hash specialization for batch_key to enable use in std::unordered_map.
Thread-safe handle to an asset.
static auto get_empty_id() -> const std::string &
Gets the conventional empty string identifier ("None").
auto is_valid() const -> bool
Checks if the handle references a task.
typename asset_link_t::state_t state_t
auto id() const -> std::string
Gets the string identifier of the asset.
void demote_to_deferred()
Demotes a fully loaded asset back to deferred state.
auto operator==(const asset_handle &rhs) const -> bool
Equality operator for asset handles.
void invalidate()
Invalidates the handle, resetting its state.
std::shared_ptr< state_t > state_ptr
static auto get_empty() -> const asset_handle &
Gets an empty asset handle.
void set_internal_ids(const hpp::uuid &internal_uid, const std::string &internal_id=get_empty_id())
Sets the internal IDs (uid + string identifier).
typename asset_link_t::task_future_t task_future_t
auto last_access() const -> std::chrono::steady_clock::time_point
Gets the last access timestamp.
void set_internal_job(const task_future_t &future)
Sets the internal job future.
auto peek() const -> std::shared_ptr< T >
Returns the loaded asset if it is currently in memory, or nullptr otherwise.
auto get(bool wait=true) const -> std::shared_ptr< T >
Gets the shared pointer to the asset.
asset_link< T > asset_link_t
auto extension() const -> std::string
auto name() const -> std::string
Gets the name of the asset derived from its path.
auto is_ready() const -> bool
Checks if the task is ready.
auto task_id() const
Gets the task ID (may be empty if there's no task).
auto is_deferred() const -> bool
Checks if the handle has a deferred job not yet submitted to workers.
void submit()
Submits a deferred task to the thread pool for execution. Does nothing if the task is already submitt...
void ensure()
Ensures the asset link is initialized.
auto is_loading() const -> bool
Checks if the asset is currently loading.
auto version() const -> uintptr_t
void set_internal_id(const std::string &internal_id=get_empty_id())
Sets the internal string identifier.
auto uid() const -> hpp::uuid
Gets the unique identifier of the asset.