#include "runtime_internal.h" #include "Engine/Core/Console/console_core.h" #include "Engine/Core/Log/logging.h" #include "Engine/Core/Platform/launcher_support.h" #include "Extension/Profile/local_profile_runtime.h" #include #include namespace dingosdk::runtime::detail { namespace { const char* command_parse_error(dingosdk::CommandParseError error) { using Error = dingosdk::CommandParseError; switch (error) { case Error::input_too_long: return "Command is too long."; case Error::invalid_character: return "Command contains an unsupported character."; case Error::too_many_arguments: return "Command has too many arguments."; case Error::unterminated_quote: return "Command has an unterminated quote."; case Error::dangling_escape: return "Command ends with an incomplete escape."; default: return "Command syntax is invalid."; } } } void read_model(void*, dingosdk::overlay::Model& output) { auto& r = runtime(); // The menu or console is up: keep engine settings fresh on the game thread. r.named_settings_wanted_until.store(GetTickCount64() + 1000, std::memory_order_relaxed); // `output` is the copy the overlay already holds (or a new one). Copy only // the parts that changed since it was taken: the game thread takes this // lock several times every frame and must not wait on a full model copy. const auto held = output.revisions; auto debug = std::move(output.debug); auto offline = std::move(output.offline); auto settings = std::move(output.engine_settings); std::shared_ptr fresh_settings; { std::lock_guard lock(r.mutex); if (held.model != r.model_revision) { output = r.model; } else { // The held copy has the adjustments below applied: start again from the source. output.can_queue_load = r.model.can_queue_load; output.load_block_reason = r.model.load_block_reason; } if (held.debug != r.debug_revision) debug = r.debug_model; if (held.offline != r.offline_revision) offline = r.offline_model; if (held.engine_settings != r.named_settings_revision) fresh_settings = r.named_settings; output.revisions = {r.model_revision, r.debug_revision, r.offline_revision, r.named_settings_revision}; if (!r.requests.idle()) { output.can_queue_load = false; output.load_block_reason = "Waiting for queued game work or a level transition."; } } output.debug = std::move(debug); output.offline = std::move(offline); // Thousands of rows: copied outside the lock, and only after they changed. if (held.engine_settings != output.revisions.engine_settings) settings = fresh_settings ? *fresh_settings : NamedSettings{}; output.engine_settings = std::move(settings); output.console_log.clear(); output.menu_scale = dingosdk::profile_runtime::local_menu_scale(); output.steam_offline = dingosdk::launcher::offline_mode(); output.multiplayer=dingosdk::multiplayer::model(); if (dingosdk::multiplayer_controls_level(output.multiplayer)) { output.can_queue_load = false; output.load_block_reason = "Only the lobby host can change levels."; } // The overlay retains its own bounded history across hide/reopen and // graphics recreation. Deliver only new lines instead of copying all 512 // retained strings every rendered menu frame. Gaps retain their sequence // numbers so the existing UI can report evicted lines when reopened. if (dingosdk::logging::latest_sequence() != r.console_delivered_sequence) { output.console_log = dingosdk::logging::snapshot_after(r.console_delivered_sequence); if (!output.console_log.empty()) r.console_delivered_sequence = output.console_log.back().sequence; } dingosdk::overlay::Status status; DingoSDKOverlayGetStatus(&status); if (status.ready && status.rendered_frames && !r.overlay_confirmed.exchange(true)) { const auto keys = dingosdk::launcher::overlay_keys(); activity_line(dingosdk::ConsoleSource::runtime, "Overlay ready. Press " + dingosdk::launcher::key_name(keys.console) + " for the console or " + dingosdk::launcher::key_name(keys.menu) + " for the menu."); record("{\"event\":\"overlay_rendering\",\"rendered_frames\":" + std::to_string(status.rendered_frames) + "}"); } } void read_native_menu_model(void*, dingosdk::overlay::Model& output) { auto& r = runtime(); { std::lock_guard lock(r.mutex); output = r.model; output.debug = r.debug_model; output.offline = r.offline_model; // Board wear and other feature toggles. if (!r.requests.idle()) { output.can_queue_load = false; output.load_block_reason = "Waiting for queued game work or a level transition."; } } output.multiplayer = dingosdk::multiplayer::model(); if (dingosdk::multiplayer_controls_level(output.multiplayer)) { output.can_queue_load = false; output.load_block_reason = "Only the lobby host can change levels."; } // Native UI has no console reader. Do not advance the overlay log cursor. output.console_log.clear(); } bool queue_debug(void*, const dingosdk::overlay::DebugRequest& request, char* result, std::size_t size) { auto& r = runtime(); std::lock_guard lock(r.mutex); if (!r.requests.enqueue(request, request_context(r), GetCurrentThreadId())) { if (size) std::snprintf(result, size, "Debug request unavailable while loading or queue is full."); return false; } if (size) result[0] = 0; // Accepted: the change speaks for itself. return true; } bool queue_offline_feature(void*, const dingosdk::overlay::OfflineFeatureRequest& request, char* result, std::size_t size) { auto& r = runtime(); std::lock_guard lock(r.mutex); const auto context = request_context(r); if (!r.requests.enqueue(request, context, GetCurrentThreadId())) { dingosdk::logging::printf(dingosdk::logging::Level::info, dingosdk::logging::Channel::runtime, "Offline feature %d -> %d rejected (tick %d, healthy %d, offline %d, loading %d).", static_cast(request.group), request.enabled, context.tick_ready, context.healthy, context.offline_ready, r.requests.loading()); if (size) std::snprintf(result, size, "Offline setting unavailable while loading, disconnected, or the queue is full."); return false; } dingosdk::logging::printf(dingosdk::logging::Level::info, dingosdk::logging::Channel::runtime, "Offline feature %d -> %d queued.", static_cast(request.group), request.enabled); if (size) result[0] = 0; // Accepted: the change speaks for itself. return true; } bool queue_multiplayer_command(const char* action, const char* argument, const char* password, char* result, std::size_t size) { const bool ok = action && argument && password && dingosdk::multiplayer::queue_command(action, argument, password); if (size) std::snprintf(result, size, "%s", ok ? "" : "Multiplayer request unavailable or too long."); return ok; } bool queue_console_command(void*, const char* command, char* result, std::size_t size) { if (!command) { if (size) std::snprintf(result, size, "Command is missing."); return false; } std::size_t length{}; while (length <= dingosdk::console_max_command_bytes && command[length]) ++length; if (length > dingosdk::console_max_command_bytes) { if (size) std::snprintf(result, size, "Command is too long."); return false; } const auto parsed = dingosdk::parse_console_command(std::string_view(command, length)); if (!parsed) { if (size) std::snprintf(result, size, "%s", command_parse_error(parsed.error)); return false; } if (parsed.arguments.empty()) { if (size) std::snprintf(result, size, "Enter a command. Try 'help'."); return false; } auto& r = runtime(); // Commands can show engine settings: keep their values fresh for a while. r.named_settings_wanted_until.store(GetTickCount64() + 10000, std::memory_order_relaxed); std::lock_guard lock(r.mutex); if (!r.requests.enqueue(ConsoleRequest{std::string(command, length)}, request_context(r), GetCurrentThreadId())) { if (size) std::snprintf(result, size, "Console command unavailable until the game update thread is ready."); return false; } if (size) result[0] = 0; // Accepted: the change speaks for itself. return true; } }