#pragma once #include "Extension/UI/Overlay/overlay.h" #include #include #include #include #include #include #include #include #include namespace dingosdk::runtime { struct LoadRequest { std::string asset, start, lm_level, lm_start; }; struct ConsoleRequest { std::string text; std::chrono::steady_clock::time_point queued_at = std::chrono::steady_clock::now(); }; struct RequestContext { bool tick_ready{}, healthy{}, loader_ready{}, offline_ready{}; }; // Runtime's mutex serializes this scheduler and the published UI model. // The engine thread takes work without holding that mutex during native calls. class RequestScheduler { public: enum class LoadPhase { idle, queued, preparing, inflight }; enum class Kind { console, debug, offline }; private: using Work = std::variant; std::deque work_; std::optional active_; std::uint32_t active_thread_{}; std::size_t continuations_{}; LoadPhase load_phase_{}; std::optional load_; template std::size_t count() const { return static_cast(std::count_if(work_.begin(), work_.end(), [](const Work& value) { return std::holds_alternative(value); })); } template void clear() { const auto end = std::next(work_.begin(), static_cast(continuations_)); continuations_ -= static_cast(std::count_if(work_.begin(), end, [](const Work& value) { return std::holds_alternative(value); })); std::erase_if(work_, [](const Work& value) { return std::holds_alternative(value); }); } template void append(T value, std::uint32_t thread) { // A console command's gameplay operation must precede work accepted // after that command. Other producers retain FIFO order. if (executing_console(thread)) { work_.emplace(std::next(work_.begin(), static_cast(continuations_)), std::move(value)); ++continuations_; } else work_.emplace_back(std::move(value)); } public: bool loading() const noexcept { return load_phase_ != LoadPhase::idle; } bool pending_load() const noexcept { return load_phase_ == LoadPhase::queued; } bool inflight() const noexcept { return load_phase_ == LoadPhase::inflight; } bool idle() const noexcept { return !loading() && !active_ && work_.empty(); } bool executing_console(std::uint32_t thread) const noexcept { return active_ == Kind::console && active_thread_ == thread; } bool can_load(const RequestContext& context, std::uint32_t thread) const noexcept { return context.tick_ready && context.healthy && context.loader_ready && !loading() && ((executing_console(thread) && continuations_ == 0) || (!active_ && work_.empty())); } bool enqueue(LoadRequest request, const RequestContext& context, std::uint32_t thread) { if (!can_load(context, thread)) return false; load_ = std::move(request); load_phase_ = LoadPhase::queued; return true; } bool enqueue(ConsoleRequest request, const RequestContext& context, std::uint32_t thread) { if (!context.tick_ready || !context.healthy || count() >= 32) return false; append(std::move(request), thread); return true; } bool enqueue(overlay::DebugRequest request, const RequestContext& context, std::uint32_t thread) { const bool restore = request.action == overlay::DebugAction::restore_debug; if (!context.tick_ready || (!context.healthy && !restore) || loading() || request.action < overlay::DebugAction::set_free_camera || request.action > overlay::DebugAction::set_drone_follow || !std::isfinite(request.value) || (!restore && count() >= 16)) return false; if (restore) clear(); append(request, thread); return true; } bool enqueue(overlay::OfflineFeatureRequest request, const RequestContext& context, std::uint32_t thread) { const bool restore = request.group == overlay::OfflineFeatureGroup::restore_all; if (!context.tick_ready || !context.offline_ready || (!context.healthy && !restore) || loading() || static_cast(request.group) > static_cast(overlay::OfflineFeatureGroup::restore_all) || (!restore && count() >= 16)) return false; if (restore) clear(); append(request, thread); return true; } template std::optional take(std::uint32_t thread) { if (loading() || active_ || work_.empty() || !std::holds_alternative(work_.front())) return {}; T value = std::move(std::get(work_.front())); work_.pop_front(); if constexpr (std::is_same_v) active_ = Kind::console; else if constexpr (std::is_same_v) active_ = Kind::debug; else active_ = Kind::offline; active_thread_ = thread; return value; } void finish_work() noexcept { active_.reset(); active_thread_ = 0; continuations_ = 0; } std::optional take_load() { if (!pending_load() || active_) return {}; auto value = std::move(load_); load_.reset(); load_phase_ = LoadPhase::preparing; // Keep the reservation through validation/restoration. return value; } void submitted_load() noexcept { load_phase_ = LoadPhase::inflight; } void finish_load() noexcept { load_.reset(); load_phase_ = LoadPhase::idle; } void fault() noexcept { work_.clear(); finish_work(); finish_load(); } }; }