#include "MetaCorePlatform/MetaCoreWindow.h" #include "MetaCoreDelivery/MetaCoreBuildPipeline.h" #include "MetaCoreDelivery/MetaCoreCrashReporter.h" #include "MetaCoreFoundation/MetaCoreGeneratedReflection.h" #include "MetaCoreFoundation/MetaCoreProject.h" #include "MetaCoreRender/MetaCoreEditorViewportRenderer.h" #include "MetaCoreRender/MetaCoreRenderDevice.h" #include "MetaCoreRender/MetaCoreRenderTypes.h" #include "MetaCoreRuntimeData/MetaCoreRuntimeDataDispatcher.h" #include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h" #include "MetaCoreRuntimeData/MetaCoreRuntimeDataSource.h" #include "MetaCoreRuntimeUi/MetaCoreRuntimeUiSystem.h" #include "MetaCoreScene/MetaCoreScenePackage.h" #include "MetaCoreScene/MetaCoreScene.h" #include "MetaCoreScripting/MetaCoreScripting.h" #include #include #include #include #include #include namespace { class MetaCoreTeeBuffer final : public std::streambuf { public: MetaCoreTeeBuffer(std::streambuf* first, std::streambuf* second) : First_(first), Second_(second) {} protected: int overflow(int character) override { if (character == traits_type::eof()) return traits_type::not_eof(character); const bool firstOk = First_ == nullptr || First_->sputc(static_cast(character)) != traits_type::eof(); const bool secondOk = Second_ == nullptr || Second_->sputc(static_cast(character)) != traits_type::eof(); return firstOk && secondOk ? character : traits_type::eof(); } int sync() override { return (First_ == nullptr || First_->pubsync() == 0) && (Second_ == nullptr || Second_->pubsync() == 0) ? 0 : -1; } private: std::streambuf* First_ = nullptr; std::streambuf* Second_ = nullptr; }; [[nodiscard]] std::filesystem::path MetaCoreGetExecutableRoot(int argc, char* argv[]) { #if defined(__linux__) std::error_code error; const auto executable = std::filesystem::canonical("/proc/self/exe", error); if (!error) return executable.parent_path(); #endif if (argc > 0) { std::error_code error; return std::filesystem::absolute(argv[0], error).parent_path(); } return std::filesystem::current_path(); } void MetaCoreRotatePlayerLog(const std::filesystem::path& path) { std::error_code error; if (std::filesystem::is_regular_file(path, error) && std::filesystem::file_size(path, error) >= 5U * 1024U * 1024U) { std::filesystem::rename(path, path.string() + ".1", error); } } MetaCore::MetaCoreSceneView MetaCoreBuildPlayerSceneView() { MetaCore::MetaCoreSceneView sceneView; sceneView.CameraPosition = {0.0F, 2.5F, 6.5F}; sceneView.CameraTarget = {0.0F, 0.7F, 0.0F}; sceneView.CameraUp = {0.0F, 1.0F, 0.0F}; sceneView.VerticalFieldOfViewDegrees = 60.0F; return sceneView; } [[nodiscard]] MetaCore::MetaCoreRuntimeProjectDocument MetaCoreBuildDefaultRuntimeProjectDocument() { MetaCore::MetaCoreRuntimeProjectDocument document; document.StartupScenePath = std::filesystem::path("Scenes") / "Main.mcscene"; document.DataSourcesPath = std::filesystem::path("Runtime") / "DataSources.mcruntime"; document.BindingsPath = std::filesystem::path("Runtime") / "Bindings.mcruntime"; document.DiagnosticsPath = std::filesystem::path("Runtime") / "Diagnostics.mcruntimestate"; document.UiManifestPath = std::filesystem::path("Runtime") / "Ui.mcruntime"; return document; } [[nodiscard]] MetaCore::MetaCoreRuntimeDataSourcesDocument MetaCoreBuildDefaultRuntimeDataSourcesDocument() { MetaCore::MetaCoreRuntimeDataSourcesDocument document; document.Sources.push_back(MetaCore::MetaCoreDataSourceDefinition{ "replay-source", "file_replay", "Replay Source", {MetaCore::MetaCoreDataSourceSetting{"file_path", "Runtime/RuntimeReplay.mcstream"}}, true, 1000 }); document.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{ "cube.position", "replay-source", "cube.position", MetaCore::MetaCoreRuntimeValueType::Vec3 }); document.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{ "cube.visible", "replay-source", "cube.visible", MetaCore::MetaCoreRuntimeValueType::Bool }); document.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{ "cube.base_color", "replay-source", "cube.base_color", MetaCore::MetaCoreRuntimeValueType::Vec3 }); document.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{ "valve.visible", "replay-source", "valve.visible", MetaCore::MetaCoreRuntimeValueType::Bool }); document.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{ "tank.base_color", "replay-source", "tank.base_color", MetaCore::MetaCoreRuntimeValueType::Vec3 }); document.DataPoints.push_back(MetaCore::MetaCoreDataPointDefinition{ "alarm.intensity", "replay-source", "alarm.intensity", MetaCore::MetaCoreRuntimeValueType::Double }); return document; } [[nodiscard]] std::filesystem::path MetaCoreStripFirstPathComponent(const std::filesystem::path& path) { std::filesystem::path stripped; auto iterator = path.begin(); if (iterator == path.end()) { return stripped; } ++iterator; for (; iterator != path.end(); ++iterator) { stripped /= *iterator; } return stripped; } void MetaCoreResolveRuntimeSourcePathsForProject( MetaCore::MetaCoreRuntimeDataSourcesDocument& sourcesDocument, const std::filesystem::path& projectRoot ) { for (MetaCore::MetaCoreDataSourceDefinition& source : sourcesDocument.Sources) { if (source.AdapterType != "file_replay") { continue; } for (MetaCore::MetaCoreDataSourceSetting& setting : source.ConnectionSettings) { if (setting.Key != "file_path" || setting.Value.empty()) { continue; } const std::filesystem::path configuredPath(setting.Value); if (configuredPath.is_absolute()) { continue; } std::vector candidates; candidates.push_back(projectRoot / configuredPath); candidates.push_back(projectRoot.parent_path() / configuredPath); candidates.push_back(projectRoot / "Runtime" / configuredPath.filename()); const std::filesystem::path strippedPath = MetaCoreStripFirstPathComponent(configuredPath); if (!strippedPath.empty()) { candidates.push_back(projectRoot / strippedPath); } for (const std::filesystem::path& candidate : candidates) { std::error_code errorCode; if (!std::filesystem::exists(candidate, errorCode)) { continue; } const std::filesystem::path canonicalCandidate = std::filesystem::weakly_canonical(candidate, errorCode); setting.Value = errorCode ? candidate.lexically_normal().string() : canonicalCandidate.string(); break; } } } } [[nodiscard]] MetaCore::MetaCoreRuntimeBindingsDocument MetaCoreBuildDefaultRuntimeBindingsDocument() { MetaCore::MetaCoreRuntimeBindingsDocument document; document.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{ "binding.cube.position", "cube.position", 3, MetaCore::MetaCoreRuntimeBindingTarget::TransformPosition, MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue }); document.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{ "binding.cube.visible", "cube.visible", 3, MetaCore::MetaCoreRuntimeBindingTarget::MeshRendererVisible, MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue }); document.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{ "binding.cube.base_color", "cube.base_color", 3, MetaCore::MetaCoreRuntimeBindingTarget::MeshRendererBaseColor, MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue }); document.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{ "binding.valve.visible", "valve.visible", 4, MetaCore::MetaCoreRuntimeBindingTarget::MeshRendererVisible, MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue }); document.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{ "binding.tank.base_color", "tank.base_color", 5, MetaCore::MetaCoreRuntimeBindingTarget::MeshRendererBaseColor, MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue }); document.Bindings.push_back(MetaCore::MetaCoreSceneBindingDefinition{ "binding.alarm.intensity", "alarm.intensity", 6, MetaCore::MetaCoreRuntimeBindingTarget::LightIntensity, MetaCore::MetaCoreRuntimeMissingDataPolicy::KeepLastValue }); return document; } } // namespace int main(int argc, char* argv[]) { std::filesystem::path customProjectRoot{}; std::filesystem::path customScenePath{}; std::filesystem::path runtimeProjectPath{}; bool validateOnly = false; std::uint64_t smokeTestFrames = 0U; for (int i = 1; i < argc; ++i) { if (std::strcmp(argv[i], "--project") == 0 && i + 1 < argc) { customProjectRoot = argv[i + 1]; ++i; } else if (std::strcmp(argv[i], "--scene") == 0 && i + 1 < argc) { customScenePath = argv[i + 1]; ++i; } else if (std::strcmp(argv[i], "--runtime-project") == 0 && i + 1 < argc) { runtimeProjectPath = argv[++i]; } else if (std::strcmp(argv[i], "--validate-only") == 0) { validateOnly = true; } else if (std::strcmp(argv[i], "--smoke-test-frames") == 0 && i + 1 < argc) { smokeTestFrames = static_cast(std::strtoull(argv[++i], nullptr, 10)); } } const std::filesystem::path executableRoot = MetaCoreGetExecutableRoot(argc, argv); if (runtimeProjectPath.empty() && customProjectRoot.empty() && std::filesystem::is_regular_file(executableRoot / "Project" / "MetaCore.runtimeproject")) { runtimeProjectPath = executableRoot / "Project" / "MetaCore.runtimeproject"; } std::optional deliveryDocument; std::filesystem::path packageRoot; if (!runtimeProjectPath.empty()) { runtimeProjectPath = runtimeProjectPath.is_absolute() ? runtimeProjectPath : std::filesystem::absolute(runtimeProjectPath); packageRoot = runtimeProjectPath.parent_path().parent_path(); MetaCore::MetaCoreBuildIssue issue; deliveryDocument = MetaCore::MetaCoreReadRuntimeDeliveryDocument(runtimeProjectPath, &issue); if (!deliveryDocument) { std::cerr << MetaCore::MetaCoreBuildErrorCodeName(issue.Code) << ": " << issue.Message << " path=" << issue.Path << '\n'; return 2; } const auto validationIssues = MetaCore::MetaCoreValidateReleasePackage(packageRoot); if (!validationIssues.empty()) { for (const auto& validationIssue : validationIssues) { std::cerr << MetaCore::MetaCoreBuildErrorCodeName(validationIssue.Code) << ": " << validationIssue.Message << " path=" << validationIssue.Path << '\n'; } return 2; } if (validateOnly) { std::cout << "MetaCorePlayer: delivery package validation passed build_id=" << deliveryDocument->BuildId << '\n'; return 0; } } else if (validateOnly) { std::cerr << "MCB1001: --validate-only requires a packaged runtime project\n"; return 2; } std::filesystem::current_path(deliveryDocument ? packageRoot : executableRoot); const auto diagnosticsRoot = deliveryDocument ? packageRoot / "Diagnostics" : executableRoot / "Diagnostics"; std::error_code logError; std::filesystem::create_directories(diagnosticsRoot / "Logs", logError); const auto logPath = diagnosticsRoot / "Logs" / "MetaCorePlayer.log"; MetaCoreRotatePlayerLog(logPath); std::ofstream logFile(logPath, std::ios::app); MetaCoreTeeBuffer coutBuffer(std::cout.rdbuf(), logFile.rdbuf()); MetaCoreTeeBuffer cerrBuffer(std::cerr.rdbuf(), logFile.rdbuf()); std::ostream output(&coutBuffer); std::ostream errors(&cerrBuffer); output << "MetaCorePlayer: start version=1.0.0 platform=Linux-x86_64 build_id=" << (deliveryDocument ? deliveryDocument->BuildId : "development") << '\n'; if (!MetaCore::MetaCoreInitializeCrashReporter( executableRoot, diagnosticsRoot, deliveryDocument ? deliveryDocument->BuildId : "development")) { output << "MetaCorePlayer: Crashpad handler unavailable; Minidump capture disabled\n"; } MetaCore::MetaCoreWindow window; if (!window.Initialize(1280, 720, "MetaCore Player")) { errors << "MetaCorePlayer: window initialize failed\n"; return 1; } MetaCore::MetaCoreRenderDevice renderDevice; if (!renderDevice.Initialize(window)) { errors << "MetaCorePlayer: render device initialize failed\n"; return 1; } MetaCore::MetaCoreEditorViewportRenderer viewportRenderer; if (!viewportRenderer.Initialize(renderDevice, window, false)) { errors << "MetaCorePlayer: viewport renderer initialize failed\n"; return 1; } std::filesystem::path projectRoot; if (deliveryDocument) { projectRoot = packageRoot / deliveryDocument->ContentRoot; } else if (!customProjectRoot.empty()) { projectRoot = std::filesystem::absolute(customProjectRoot); } else { const auto discovered = MetaCore::MetaCoreDiscoverProjectRoot(); if (!discovered.has_value()) { errors << "MetaCorePlayer: failed to discover project root\n"; return 1; } projectRoot = *discovered; } const std::filesystem::path projectPath = MetaCore::MetaCoreGetProjectFilePath(projectRoot); viewportRenderer.SetProjectRootPath(projectRoot); MetaCore::MetaCoreTypeRegistry typeRegistry; MetaCore::MetaCoreRegisterRuntimeDataGeneratedTypes(typeRegistry); const auto loadedRuntimeProjectDocument = MetaCore::MetaCoreReadRuntimeProjectDocument( deliveryDocument ? packageRoot / deliveryDocument->RuntimeConfig : projectRoot / "Runtime" / "ProjectRuntime.mcruntimecfg", typeRegistry ); const auto runtimeProjectDocument = loadedRuntimeProjectDocument.value_or(MetaCoreBuildDefaultRuntimeProjectDocument()); std::optional startupSceneDocument; if (!customScenePath.empty()) { const auto absoluteScene = customScenePath.is_absolute() ? customScenePath : (projectRoot / customScenePath); startupSceneDocument = MetaCore::MetaCoreReadScenePackage(absoluteScene); } if (!startupSceneDocument.has_value() && deliveryDocument) { startupSceneDocument = MetaCore::MetaCoreReadScenePackage(packageRoot / deliveryDocument->StartupScene); } if (!startupSceneDocument.has_value() && !deliveryDocument && !runtimeProjectDocument.StartupScenePath.empty()) { startupSceneDocument = MetaCore::MetaCoreReadScenePackage(projectRoot / runtimeProjectDocument.StartupScenePath); } if (!startupSceneDocument.has_value() && !deliveryDocument) { startupSceneDocument = MetaCore::MetaCoreLoadStartupSceneDocument(projectPath); } MetaCore::MetaCoreScene scene; if (startupSceneDocument.has_value()) { MetaCore::MetaCoreSceneSnapshot snapshot; snapshot.GameObjects = startupSceneDocument->GameObjects; scene.RestoreSnapshot(snapshot); output << "MetaCorePlayer: loaded startup scene from project " << projectPath.string() << '\n'; } else if (!deliveryDocument) { scene = MetaCore::MetaCoreCreateDefaultScene(); output << "MetaCorePlayer: startup scene unavailable, using built-in default scene\n"; } else { errors << "MCB6004: packaged startup scene is missing or unreadable path=" << deliveryDocument->StartupScene << '\n'; return 2; } MetaCore::MetaCoreRuntimeDataDispatcher runtimeDataDispatcher(scene); MetaCore::MetaCoreScriptRegistry scriptRegistry; MetaCore::MetaCoreRegisterBuiltinScripts(scriptRegistry); MetaCore::MetaCoreScriptModuleLoader scriptModuleLoader; std::vector loadedScriptModules; std::vector scriptModulePaths; if (deliveryDocument) { for (const auto& relative : deliveryDocument->ScriptModules) scriptModulePaths.push_back(packageRoot / relative); } else if (const auto latest = MetaCore::MetaCoreFindLatestScriptModule(projectRoot); latest.has_value()) { scriptModulePaths.push_back(*latest); } for (const auto& modulePath : scriptModulePaths) { std::string scriptError; auto module = scriptModuleLoader.LoadCandidate(modulePath, &scriptError); if (!module.has_value() || !scriptRegistry.ReplaceModule(module->ModuleId, module->Definitions, &scriptError)) { errors << "MetaCorePlayer: script module load failed path=" << modulePath << " error=" << scriptError << '\n'; if (deliveryDocument) return 2; continue; } output << "MetaCorePlayer: loaded script module id=" << module->ModuleId << " build_id=" << module->BuildId << '\n'; loadedScriptModules.push_back(std::move(*module)); } bool missingRequiredScript = false; for (auto object : scene.GetGameObjects()) { if (!object.HasComponent()) continue; for (const auto& instance : object.GetComponent().Instances) { if (!instance.ScriptTypeId.empty() && scriptRegistry.FindScript(instance.ScriptTypeId) == nullptr) { errors << "MetaCorePlayer: missing script type=" << instance.ScriptTypeId << " object=" << object.GetId() << '\n'; missingRequiredScript = true; } } } if (deliveryDocument && missingRequiredScript) return 2; MetaCore::MetaCoreScriptRuntime scriptRuntime( scene, scriptRegistry, [&output, &errors](std::uint32_t level, std::string_view category, std::string_view message) { std::ostream& stream = level >= 2U ? errors : output; stream << "MetaCorePlayer Script[" << category << "]: " << message << '\n'; } ); scriptRuntime.Start(); const auto sourcesPath = (projectRoot / runtimeProjectDocument.DataSourcesPath).lexically_normal(); const auto bindingsPath = (projectRoot / runtimeProjectDocument.BindingsPath).lexically_normal(); const auto diagnosticsPath = (projectRoot / runtimeProjectDocument.DiagnosticsPath).lexically_normal(); const auto loadedSourcesDocument = MetaCore::MetaCoreReadRuntimeDataSourcesDocument(sourcesPath, typeRegistry); const auto loadedBindingsDocument = MetaCore::MetaCoreReadRuntimeBindingsDocument(bindingsPath, typeRegistry); auto sourcesDocument = loadedSourcesDocument.value_or(MetaCoreBuildDefaultRuntimeDataSourcesDocument()); auto bindingsDocument = loadedBindingsDocument.value_or(MetaCoreBuildDefaultRuntimeBindingsDocument()); MetaCoreResolveRuntimeSourcePathsForProject(sourcesDocument, projectRoot); const std::filesystem::path uiManifestPath = (projectRoot / runtimeProjectDocument.UiManifestPath).lexically_normal(); const auto loadedUiManifest = runtimeProjectDocument.UiManifestPath.empty() ? std::optional{} : MetaCore::MetaCoreReadRuntimeUiManifest(uiManifestPath, typeRegistry); MetaCore::MetaCoreRuntimeUiSystem runtimeUi; if (loadedUiManifest.has_value()) { if (!runtimeUi.Initialize(window, projectRoot, projectRoot / "Ui", *loadedUiManifest)) { errors << "MetaCorePlayer: failed to initialize runtime UI\n"; } else { runtimeUi.AttachToViewportRenderer(viewportRenderer); } for (const std::string& error : runtimeUi.GetErrors()) { errors << "MetaCorePlayer UI: " << error << '\n'; } } if (loadedSourcesDocument.has_value() && loadedBindingsDocument.has_value()) { output << "MetaCorePlayer: loaded runtime config from binary .mcruntime documents\n"; } else { const bool sourcesExists = std::filesystem::exists(sourcesPath); const bool bindingsExists = std::filesystem::exists(bindingsPath); if ((sourcesExists && !loadedSourcesDocument.has_value()) || (bindingsExists && !loadedBindingsDocument.has_value())) { errors << "MetaCorePlayer: runtime config exists but is unreadable or corrupted" << (deliveryDocument ? "\n" : ", using built-in fallback config\n"); if (deliveryDocument) return 2; } else { if (deliveryDocument) { errors << "MCB6004: packaged runtime configuration is missing\n"; return 2; } output << "MetaCorePlayer: runtime config missing, using built-in fallback config\n"; } } runtimeDataDispatcher.SetDataPointDefinitions(sourcesDocument.DataPoints); runtimeDataDispatcher.SetBindingDefinitions(bindingsDocument.Bindings); std::unique_ptr runtimeAdapter; if (!sourcesDocument.Sources.empty()) { runtimeAdapter = MetaCore::MetaCoreCreateRuntimeDataSourceAdapter(sourcesDocument.Sources.front().AdapterType); if (runtimeAdapter == nullptr) { errors << "MetaCorePlayer: unsupported adapter type " << sourcesDocument.Sources.front().AdapterType << '\n'; return 1; } if (!runtimeAdapter->Configure(sourcesDocument.Sources.front())) { errors << "MetaCorePlayer: failed to configure runtime adapter error=" << runtimeAdapter->GetStatus().LastError << '\n'; return 1; } if (!runtimeAdapter->Connect()) { errors << "MetaCorePlayer: failed to connect runtime adapter error=" << runtimeAdapter->GetStatus().LastError << '\n'; return 1; } } MetaCore::MetaCoreRuntimeDataSourceState lastReportedSourceState = runtimeAdapter != nullptr ? runtimeAdapter->GetStatus().State : MetaCore::MetaCoreRuntimeDataSourceState::Disconnected; std::unordered_map lastReportedBindingIssueKeys; std::uint64_t diagnosticsWriteFrame = 0; std::uint64_t renderedFrames = 0U; while (!window.ShouldClose()) { window.BeginFrame(); const double frameDeltaSeconds = window.GetDeltaSeconds(); scriptRuntime.FixedUpdate(1.0 / 60.0); scriptRuntime.Update(frameDeltaSeconds); const auto [windowWidth, windowHeight] = window.GetWindowSize(); viewportRenderer.SetViewportRect(MetaCore::MetaCoreViewportRect{ 0.0F, 0.0F, static_cast(windowWidth), static_cast(windowHeight) }); if (runtimeAdapter != nullptr) { runtimeAdapter->Tick(1.0 / 60.0); const std::vector updates = runtimeAdapter->PollUpdates(); runtimeDataDispatcher.ApplyUpdates(updates); runtimeUi.ApplyDataUpdates(updates); runtimeDataDispatcher.TickStaleness(runtimeAdapter->GetStatus().LastUpdateAt); } const auto diagnostics = runtimeDataDispatcher.BuildDiagnosticsSnapshot( runtimeAdapter != nullptr ? std::vector{runtimeAdapter->GetStatus()} : std::vector{} ); if ((diagnosticsWriteFrame % 15ULL) == 0ULL) { (void)MetaCore::MetaCoreWriteRuntimeDiagnosticsSnapshot(diagnosticsPath, diagnostics, typeRegistry); } ++diagnosticsWriteFrame; if (runtimeAdapter != nullptr && runtimeAdapter->GetStatus().State != lastReportedSourceState) { output << "MetaCorePlayer: data source state changed to " << static_cast(runtimeAdapter->GetStatus().State) << " error=" << runtimeAdapter->GetStatus().LastError << '\n'; lastReportedSourceState = runtimeAdapter->GetStatus().State; } if (diagnostics.HasFaults) { for (const MetaCore::MetaCoreRuntimeBindingStatus& bindingStatus : diagnostics.BindingStatuses) { if (!bindingStatus.Healthy || bindingStatus.Stale) { const std::string issueKey = std::string(bindingStatus.Healthy ? "healthy" : "fault") + "|" + (bindingStatus.Stale ? "stale" : "fresh") + "|" + bindingStatus.LastError; const auto previousIssue = lastReportedBindingIssueKeys.find(bindingStatus.BindingId); if (previousIssue == lastReportedBindingIssueKeys.end() || previousIssue->second != issueKey) { output << "MetaCorePlayer: binding issue id=" << bindingStatus.BindingId << " stale=" << (bindingStatus.Stale ? "true" : "false") << " error=" << bindingStatus.LastError << '\n'; lastReportedBindingIssueKeys[bindingStatus.BindingId] = issueKey; } } else { const auto previousIssue = lastReportedBindingIssueKeys.find(bindingStatus.BindingId); if (previousIssue != lastReportedBindingIssueKeys.end()) { output << "MetaCorePlayer: binding recovered id=" << bindingStatus.BindingId << '\n'; lastReportedBindingIssueKeys.erase(previousIssue); } } } } runtimeUi.Update(window.GetDeltaSeconds()); scriptRuntime.LateUpdate(frameDeltaSeconds); viewportRenderer.RenderSceneToViewport(scene, MetaCoreBuildPlayerSceneView(), true); viewportRenderer.RenderAll(); renderDevice.RenderFrame(); renderDevice.PresentFrame(); window.EndFrame(); ++renderedFrames; if (smokeTestFrames > 0U && renderedFrames >= smokeTestFrames) break; } scriptRuntime.Stop(); runtimeUi.Shutdown(); viewportRenderer.Shutdown(); renderDevice.Shutdown(); window.Shutdown(); return 0; }