#include "MetaCorePlatform/MetaCoreWindow.h" #include "MetaCoreFoundation/MetaCoreGeneratedReflection.h" #include "MetaCoreFoundation/MetaCorePackage.h" #include "MetaCoreFoundation/MetaCoreProject.h" #include "MetaCoreRender/MetaCorePlayerRenderer.h" #include "MetaCoreRender/MetaCoreRenderDevice.h" #include "MetaCoreRender/MetaCoreRuntimeUiRenderer.h" #include "MetaCoreRender/MetaCoreRenderTypes.h" #include "MetaCoreRuntimeData/MetaCoreRuntimeDataDispatcher.h" #include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h" #include "MetaCoreRuntimeData/MetaCoreRuntimeDataSource.h" #include "MetaCoreScene/MetaCoreRuntimeLifecycle.h" #include "MetaCoreScene/MetaCoreScenePackage.h" #include "MetaCoreScene/MetaCoreSceneSerializer.h" #include "MetaCoreScene/MetaCoreScene.h" #include "MetaCoreScene/MetaCoreUiRmlCompiler.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { 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; } struct MetaCoreRuntimeDataSourceInstance { MetaCore::MetaCoreDataSourceDefinition Definition{}; std::unique_ptr Adapter{}; MetaCore::MetaCoreRuntimeDataSourceState LastReportedState = MetaCore::MetaCoreRuntimeDataSourceState::Disconnected; }; [[nodiscard]] std::optional MetaCoreResolveRuntimeDataReplayFilePaths( MetaCore::MetaCoreRuntimeDataSourcesDocument& sourcesDocument, const std::filesystem::path& projectRoot ) { for (MetaCore::MetaCoreDataSourceDefinition& sourceDefinition : sourcesDocument.Sources) { if (sourceDefinition.AdapterType != "file_replay") { continue; } for (MetaCore::MetaCoreDataSourceSetting& setting : sourceDefinition.ConnectionSettings) { if (setting.Key != "file_path" || setting.Value.empty()) { continue; } const std::filesystem::path replayPath(setting.Value); if (replayPath.is_absolute()) { continue; } if (MetaCore::MetaCoreIsUnsafeRuntimeProjectPath(replayPath)) { return "RuntimeData source " + sourceDefinition.Id + " file_path must be project-relative without parent traversal: " + replayPath.generic_string(); } setting.Value = (projectRoot / replayPath.lexically_normal()).string(); } } return std::nullopt; } [[nodiscard]] std::optional MetaCoreReadCookManifestDocument( const std::filesystem::path& manifestPath, const MetaCore::MetaCoreTypeRegistry& registry ) { std::ifstream input(manifestPath, std::ios::binary); if (!input.is_open()) { return std::nullopt; } input.seekg(0, std::ios::end); const auto size = static_cast(input.tellg()); input.seekg(0, std::ios::beg); std::vector buffer(size); if (size > 0 && !input.read(reinterpret_cast(buffer.data()), static_cast(size))) { return std::nullopt; } MetaCore::MetaCoreCookManifestDocument document; if (!MetaCore::MetaCoreDeserializeFromBytes( std::span(buffer.data(), buffer.size()), document, registry)) { return std::nullopt; } return document; } [[nodiscard]] std::optional MetaCoreReadSceneDocumentFromPath( const std::filesystem::path& path, const MetaCore::MetaCoreTypeRegistry& registry ) { if (path.extension() == ".json") { return MetaCore::MetaCoreSceneSerializer::LoadSceneFromJson(path, registry); } return MetaCore::MetaCoreReadScenePackage(path); } [[nodiscard]] std::string MetaCoreBuildPortablePathKey(const std::filesystem::path& path) { return path.lexically_normal().generic_string(); } [[nodiscard]] const MetaCore::MetaCoreCookManifestEntry* MetaCoreFindCookedManifestEntry( const MetaCore::MetaCoreCookManifestDocument& manifest, const std::filesystem::path& relativeSourcePath ) { const std::string requestedPath = MetaCoreBuildPortablePathKey(relativeSourcePath); const auto iterator = std::find_if( manifest.Entries.begin(), manifest.Entries.end(), [&](const MetaCore::MetaCoreCookManifestEntry& entry) { return MetaCoreBuildPortablePathKey(entry.SourcePackagePath) == requestedPath; } ); return iterator == manifest.Entries.end() ? nullptr : &*iterator; } [[nodiscard]] const MetaCore::MetaCoreCookManifestEntry* MetaCoreFindCookedManifestEntry( const MetaCore::MetaCoreCookManifestDocument& manifest, const MetaCore::MetaCoreAssetGuid& assetGuid ) { if (!assetGuid.IsValid()) { return nullptr; } const auto iterator = std::find_if( manifest.Entries.begin(), manifest.Entries.end(), [&](const MetaCore::MetaCoreCookManifestEntry& entry) { return entry.AssetGuid == assetGuid; } ); return iterator == manifest.Entries.end() ? nullptr : &*iterator; } [[nodiscard]] bool MetaCoreIsCookedRuntimeGltfModel(const std::filesystem::path& relativeSourcePath) { std::string extension = relativeSourcePath.extension().generic_string(); std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); return extension == ".glb" || extension == ".gltf"; } void MetaCoreRewriteSceneGltfModelPathsToCooked( MetaCore::MetaCoreSceneDocument& sceneDocument, const MetaCore::MetaCoreCookManifestDocument& manifest ) { for (MetaCore::MetaCoreGameObjectData& gameObject : sceneDocument.GameObjects) { if (!gameObject.MeshRenderer.has_value()) { continue; } if (gameObject.MeshRenderer->SourceModelPath.empty() && !gameObject.MeshRenderer->SourceModelAssetGuid.IsValid()) { continue; } const std::filesystem::path sourceModelPath(gameObject.MeshRenderer->SourceModelPath); const MetaCore::MetaCoreCookManifestEntry* entry = nullptr; if (!sourceModelPath.empty() && MetaCoreIsCookedRuntimeGltfModel(sourceModelPath)) { entry = MetaCoreFindCookedManifestEntry(manifest, sourceModelPath); } if (entry == nullptr && gameObject.MeshRenderer->SourceModelAssetGuid.IsValid()) { entry = MetaCoreFindCookedManifestEntry(manifest, gameObject.MeshRenderer->SourceModelAssetGuid); } if (entry == nullptr || entry->CookedPath.empty()) { continue; } if (!MetaCoreIsCookedRuntimeGltfModel(entry->SourcePackagePath)) { continue; } const std::string cookedModelPath = entry->CookedPath.generic_string(); gameObject.MeshRenderer->SourceModelPath = cookedModelPath; gameObject.MeshRenderer->SourceModelAssetGuid = {}; if (gameObject.ModelRootTag.has_value()) { gameObject.ModelRootTag->SourceModelPath = cookedModelPath; } } } template [[nodiscard]] std::optional MetaCoreReadTypedPackagePayload( const MetaCore::MetaCorePackageDocument& package, const MetaCore::MetaCoreTypeRegistry& registry, std::string_view expectedTypeName ) { const MetaCore::MetaCoreTypeId expectedTypeId = MetaCore::MetaCoreMakeTypeId(expectedTypeName); for (const MetaCore::MetaCoreExportEntry& exportEntry : package.Exports) { if (exportEntry.TypeId != expectedTypeId || exportEntry.PayloadIndex >= package.PayloadSections.size()) { continue; } T payload{}; if (MetaCore::MetaCoreDeserializeFromBytes( package.PayloadSections[exportEntry.PayloadIndex], payload, registry)) { return payload; } } return std::nullopt; } template [[nodiscard]] std::optional MetaCoreReadCookedDocument( const std::filesystem::path& projectRoot, const MetaCore::MetaCoreCookManifestDocument& manifest, const std::filesystem::path& relativeSourcePath, const MetaCore::MetaCoreTypeRegistry& registry, std::string_view expectedTypeName ) { const MetaCore::MetaCoreCookManifestEntry* entry = MetaCoreFindCookedManifestEntry(manifest, relativeSourcePath); if (entry == nullptr || entry->CookedPath.empty()) { return std::nullopt; } const auto cookedPackage = MetaCore::MetaCoreReadPackageFile(projectRoot / entry->CookedPath, registry); if (!cookedPackage.has_value()) { return std::nullopt; } return MetaCoreReadTypedPackagePayload(*cookedPackage, registry, expectedTypeName); } [[nodiscard]] MetaCore::MetaCoreRuntimeDataSourcesDocument MetaCoreBuildDefaultRuntimeDataSourcesDocument() { MetaCore::MetaCoreRuntimeDataSourcesDocument document; document.Sources.push_back(MetaCore::MetaCoreDataSourceDefinition{ "replay-source", "file_replay", "Replay Source", {MetaCore::MetaCoreDataSourceSetting{"file_path", "TestProject/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]] 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; } [[nodiscard]] std::string MetaCoreBuildRuntimeDataStatusText( const MetaCore::MetaCoreRuntimeDiagnosticsSnapshot& diagnostics ) { const auto connectedSources = std::count_if( diagnostics.SourceStatuses.begin(), diagnostics.SourceStatuses.end(), [](const MetaCore::MetaCoreRuntimeDataSourceStatus& status) { return status.State == MetaCore::MetaCoreRuntimeDataSourceState::Connected; } ); const auto sourceIssues = std::count_if( diagnostics.SourceStatuses.begin(), diagnostics.SourceStatuses.end(), [](const MetaCore::MetaCoreRuntimeDataSourceStatus& status) { return status.State == MetaCore::MetaCoreRuntimeDataSourceState::Degraded || status.State == MetaCore::MetaCoreRuntimeDataSourceState::Faulted; } ); const auto bindingIssues = std::count_if( diagnostics.BindingStatuses.begin(), diagnostics.BindingStatuses.end(), [](const MetaCore::MetaCoreRuntimeBindingStatus& status) { return !status.Healthy || status.Stale; } ); return "RuntimeData sources=" + std::to_string(diagnostics.SourceStatuses.size()) + " connected=" + std::to_string(static_cast(connectedSources)) + " source_issues=" + std::to_string(static_cast(sourceIssues)) + " bindings=" + std::to_string(diagnostics.BindingStatuses.size()) + " binding_issues=" + std::to_string(static_cast(bindingIssues)); } [[nodiscard]] std::string MetaCoreFormatRuntimeDataValue( const MetaCore::MetaCoreRuntimeDataValue& value ) { std::ostringstream stream; switch (value.Type) { case MetaCore::MetaCoreRuntimeValueType::Bool: return value.BoolValue ? "true" : "false"; case MetaCore::MetaCoreRuntimeValueType::Int64: return std::to_string(value.Int64Value); case MetaCore::MetaCoreRuntimeValueType::Double: stream << value.DoubleValue; return stream.str(); case MetaCore::MetaCoreRuntimeValueType::String: return value.StringValue; case MetaCore::MetaCoreRuntimeValueType::Vec3: stream << value.Vec3Value.x << ", " << value.Vec3Value.y << ", " << value.Vec3Value.z; return stream.str(); } return {}; } [[nodiscard]] const MetaCore::MetaCoreDataPointDefinition* MetaCoreFindRuntimeDataPoint( const std::vector& dataPoints, const std::string& dataPointId ) { const auto iterator = std::find_if( dataPoints.begin(), dataPoints.end(), [&](const MetaCore::MetaCoreDataPointDefinition& dataPoint) { return dataPoint.Id == dataPointId; } ); return iterator == dataPoints.end() ? nullptr : &*iterator; } void MetaCoreMarkRuntimeBindingFault( std::vector& statuses, const std::string& bindingId, const std::string& error ) { const auto iterator = std::find_if( statuses.begin(), statuses.end(), [&](const MetaCore::MetaCoreRuntimeBindingStatus& status) { return status.BindingId == bindingId; } ); if (iterator == statuses.end()) { return; } iterator->Healthy = false; iterator->Stale = false; iterator->LastError = error; } void MetaCoreMarkRuntimeBindingHealthy( std::vector& statuses, const std::string& bindingId, std::uint64_t appliedAt ) { const auto iterator = std::find_if( statuses.begin(), statuses.end(), [&](const MetaCore::MetaCoreRuntimeBindingStatus& status) { return status.BindingId == bindingId; } ); if (iterator == statuses.end()) { return; } iterator->Healthy = true; iterator->Stale = false; iterator->LastAppliedAt = appliedAt; iterator->LastError.clear(); } [[nodiscard]] std::vector MetaCoreBuildRuntimeUiBindingStatuses( const std::vector& uiBindings ) { std::vector statuses; statuses.reserve(uiBindings.size()); for (const MetaCore::MetaCoreUiBindingDefinition& binding : uiBindings) { statuses.push_back(MetaCore::MetaCoreRuntimeBindingStatus{ binding.BindingId, true, false, 0, 1000, {} }); } return statuses; } [[nodiscard]] bool MetaCoreHasRuntimeUiTextBindingForNode( const std::vector& uiBindings, std::string_view nodeId ) { return std::any_of( uiBindings.begin(), uiBindings.end(), [nodeId](const MetaCore::MetaCoreUiBindingDefinition& binding) { return binding.Target == MetaCore::MetaCoreRuntimeUiBindingTarget::Text && binding.TargetNodeId == nodeId; } ); } void MetaCoreValidateRuntimeUiBindingTargets( const std::vector& uiBindings, const MetaCore::MetaCoreRuntimeUiRenderer& runtimeUiRenderer, std::vector& uiBindingStatuses ) { for (const MetaCore::MetaCoreUiBindingDefinition& binding : uiBindings) { if (binding.Target != MetaCore::MetaCoreRuntimeUiBindingTarget::Text) { continue; } if (!runtimeUiRenderer.GetStats().Loaded) { MetaCoreMarkRuntimeBindingFault( uiBindingStatuses, binding.BindingId, "Runtime UI document is not loaded" ); continue; } if (!runtimeUiRenderer.HasNode(binding.TargetNodeId)) { MetaCoreMarkRuntimeBindingFault( uiBindingStatuses, binding.BindingId, "Runtime UI node was not found: " + binding.TargetNodeId ); } } } void MetaCoreApplyRuntimeUiBindings( const std::vector& updates, const std::vector& dataPoints, const std::vector& uiBindings, MetaCore::MetaCoreRuntimeUiRenderer& runtimeUiRenderer, std::vector& uiBindingStatuses ) { for (const MetaCore::MetaCoreRuntimeDataUpdate& update : updates) { const MetaCore::MetaCoreDataPointDefinition* dataPoint = MetaCoreFindRuntimeDataPoint(dataPoints, update.DataPointId); if (dataPoint == nullptr) { continue; } for (const MetaCore::MetaCoreUiBindingDefinition& binding : uiBindings) { if (binding.DataPointId != update.DataPointId) { continue; } if (update.Value.Type != dataPoint->ValueType) { MetaCoreMarkRuntimeBindingFault( uiBindingStatuses, binding.BindingId, "Update type does not match data point definition" ); continue; } if (update.Value.Quality == MetaCore::MetaCoreRuntimeDataQuality::Bad) { MetaCoreMarkRuntimeBindingFault(uiBindingStatuses, binding.BindingId, "Update quality is bad"); continue; } if (binding.Target == MetaCore::MetaCoreRuntimeUiBindingTarget::Text) { if (!runtimeUiRenderer.SetNodeText(binding.TargetNodeId, MetaCoreFormatRuntimeDataValue(update.Value))) { MetaCoreMarkRuntimeBindingFault( uiBindingStatuses, binding.BindingId, runtimeUiRenderer.GetStats().LastError ); continue; } } MetaCoreMarkRuntimeBindingHealthy(uiBindingStatuses, binding.BindingId, update.Value.SourceTimestamp); } } } void MetaCoreTickRuntimeBindingStaleness( std::vector& statuses, std::uint64_t currentTimestamp ) { for (MetaCore::MetaCoreRuntimeBindingStatus& status : statuses) { if (status.LastAppliedAt == 0) { status.Stale = true; continue; } status.Stale = currentTimestamp > status.LastAppliedAt && (currentTimestamp - status.LastAppliedAt) > status.StaleAfterMs; } } void MetaCoreAppendRuntimeUiBindingDiagnostics( MetaCore::MetaCoreRuntimeDiagnosticsSnapshot& diagnostics, const std::vector& uiBindingStatuses ) { diagnostics.BindingStatuses.insert( diagnostics.BindingStatuses.end(), uiBindingStatuses.begin(), uiBindingStatuses.end() ); diagnostics.HasFaults = diagnostics.HasFaults || std::any_of( uiBindingStatuses.begin(), uiBindingStatuses.end(), [](const MetaCore::MetaCoreRuntimeBindingStatus& status) { return !status.Healthy || status.Stale; } ); } [[nodiscard]] std::vector MetaCoreCollectRuntimeSourceStatuses( const std::vector& runtimeSources ) { std::vector sourceStatuses; sourceStatuses.reserve(runtimeSources.size()); for (const MetaCoreRuntimeDataSourceInstance& sourceInstance : runtimeSources) { sourceStatuses.push_back(sourceInstance.Adapter->GetStatus()); } return sourceStatuses; } void MetaCoreWriteRuntimeDiagnosticsSnapshot( const std::filesystem::path& diagnosticsPath, const MetaCore::MetaCoreTypeRegistry& typeRegistry, const MetaCore::MetaCoreRuntimeDataDispatcher& runtimeDataDispatcher, const std::vector& sourceStatuses, const std::vector& uiBindingStatuses ) { MetaCore::MetaCoreRuntimeDiagnosticsSnapshot diagnostics = runtimeDataDispatcher.BuildDiagnosticsSnapshot(sourceStatuses); MetaCoreAppendRuntimeUiBindingDiagnostics(diagnostics, uiBindingStatuses); (void)MetaCore::MetaCoreWriteRuntimeDiagnosticsSnapshot(diagnosticsPath, diagnostics, typeRegistry); } [[nodiscard]] std::string MetaCoreRuntimeBindingIssueSignature( const MetaCore::MetaCoreRuntimeBindingStatus& bindingStatus ) { std::ostringstream stream; stream << (bindingStatus.Healthy ? "healthy" : "fault") << "|stale=" << (bindingStatus.Stale ? "true" : "false") << "|error=" << bindingStatus.LastError; return stream.str(); } void MetaCoreLogRuntimeBindingIssues( const MetaCore::MetaCoreRuntimeDiagnosticsSnapshot& diagnostics, std::unordered_map& reportedBindingIssues ) { for (const MetaCore::MetaCoreRuntimeBindingStatus& bindingStatus : diagnostics.BindingStatuses) { if (bindingStatus.Healthy && !bindingStatus.Stale) { reportedBindingIssues.erase(bindingStatus.BindingId); continue; } const std::string issueSignature = MetaCoreRuntimeBindingIssueSignature(bindingStatus); const auto reportedIterator = reportedBindingIssues.find(bindingStatus.BindingId); if (reportedIterator != reportedBindingIssues.end() && reportedIterator->second == issueSignature) { continue; } std::cout << "MetaCorePlayer: binding issue id=" << bindingStatus.BindingId << " stale=" << (bindingStatus.Stale ? "true" : "false") << " error=" << bindingStatus.LastError << '\n'; reportedBindingIssues[bindingStatus.BindingId] = issueSignature; } } struct MetaCorePlayerLaunchConfig { std::string Platform{"Windows"}; std::string GraphicsBackend{"OpenGL"}; std::string Architecture{"x64"}; std::string BuildProfile{"Development"}; }; [[nodiscard]] MetaCorePlayerLaunchConfig MetaCoreReadPlayerLaunchConfig( const std::filesystem::path& configPath ) { MetaCorePlayerLaunchConfig config; std::ifstream input(configPath, std::ios::binary); if (!input.is_open()) { return config; } nlohmann::json document; try { input >> document; } catch (const nlohmann::json::exception&) { return config; } const auto targetIterator = document.find("target"); if (targetIterator != document.end() && targetIterator->is_object()) { config.Platform = targetIterator->value("platform", config.Platform); config.GraphicsBackend = targetIterator->value("graphics_backend", config.GraphicsBackend); config.Architecture = targetIterator->value("architecture", config.Architecture); } const auto runtimeIterator = document.find("runtime"); if (runtimeIterator != document.end() && runtimeIterator->is_object()) { config.BuildProfile = runtimeIterator->value("build_profile", config.BuildProfile); } return config; } [[nodiscard]] MetaCore::MetaCoreRenderBackend MetaCoreSelectRenderBackend( const MetaCorePlayerLaunchConfig& launchConfig ) { if (launchConfig.GraphicsBackend == "Vulkan") { return MetaCore::MetaCoreRenderBackend::Vulkan; } if (launchConfig.GraphicsBackend == "WebGPU") { return MetaCore::MetaCoreRenderBackend::WebGPU; } if (launchConfig.GraphicsBackend == "WebGL") { std::cout << "MetaCorePlayer: WebGL is not available in native Player; using OpenGL backend\n"; } return MetaCore::MetaCoreRenderBackend::OpenGL; } } // namespace int main(int argc, char* argv[]) { std::filesystem::path customProjectRoot{}; std::filesystem::path customScenePath{}; 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; } } if (argc > 0) { std::filesystem::current_path(std::filesystem::path(argv[0]).parent_path()); } const MetaCorePlayerLaunchConfig launchConfig = MetaCoreReadPlayerLaunchConfig(std::filesystem::current_path() / "MetaCorePlayer.config.json"); std::cout << "MetaCorePlayer: target platform=" << launchConfig.Platform << " graphics=" << launchConfig.GraphicsBackend << " arch=" << launchConfig.Architecture << " config_profile=" << launchConfig.BuildProfile << '\n'; MetaCore::MetaCoreWindow window; if (!window.Initialize(1280, 720, "MetaCore Player")) { std::cerr << "MetaCorePlayer: window initialize failed\n"; return 1; } MetaCore::MetaCoreRenderDevice renderDevice; MetaCore::MetaCoreRenderDeviceConfig renderConfig{}; renderConfig.Backend = MetaCoreSelectRenderBackend(launchConfig); if (!renderDevice.Initialize(window, renderConfig)) { std::cerr << "MetaCorePlayer: render device initialize failed\n"; return 1; } MetaCore::MetaCorePlayerRenderer playerRenderer; if (!playerRenderer.Initialize(renderDevice, window)) { std::cerr << "MetaCorePlayer: renderer initialize failed\n"; return 1; } MetaCore::MetaCoreRuntimeUiRenderer runtimeUiRenderer; if (!runtimeUiRenderer.Initialize()) { std::cerr << "MetaCorePlayer: runtime UI renderer initialize failed\n"; return 1; } std::filesystem::path projectRoot; if (!customProjectRoot.empty()) { projectRoot = std::filesystem::absolute(customProjectRoot); } else { const auto discovered = MetaCore::MetaCoreDiscoverProjectRoot(); if (!discovered.has_value()) { std::cerr << "MetaCorePlayer: failed to discover project root\n"; return 1; } projectRoot = *discovered; } const std::filesystem::path projectPath = MetaCore::MetaCoreGetProjectFilePath(projectRoot); playerRenderer.SetProjectRootPath(projectRoot); MetaCore::MetaCoreTypeRegistry typeRegistry; MetaCore::MetaCoreRegisterFoundationGeneratedTypes(typeRegistry); MetaCore::MetaCoreRegisterSceneGeneratedTypes(typeRegistry); MetaCore::MetaCoreRegisterRuntimeDataGeneratedTypes(typeRegistry); const auto projectDocument = MetaCore::MetaCoreReadProjectFile(projectPath); std::filesystem::path runtimeDirectoryRelative = projectDocument.has_value() ? projectDocument->RuntimeDirectory : std::filesystem::path("Runtime"); if (runtimeDirectoryRelative.empty()) { runtimeDirectoryRelative = "Runtime"; } if (MetaCore::MetaCoreIsUnsafeRuntimeProjectPath(runtimeDirectoryRelative)) { std::cerr << "MetaCorePlayer: project runtime_directory must be project-relative without parent traversal: " << runtimeDirectoryRelative.generic_string() << '\n'; return 1; } const std::filesystem::path runtimeDirectory = projectRoot / runtimeDirectoryRelative.lexically_normal(); const auto loadedRuntimeProjectDocument = MetaCore::MetaCoreReadRuntimeProjectDocument( runtimeDirectory / "ProjectRuntime.mcruntimecfg", typeRegistry ); auto runtimeProjectDocument = loadedRuntimeProjectDocument.value_or(MetaCore::MetaCoreBuildDefaultRuntimeProjectDocument(runtimeDirectoryRelative)); MetaCore::MetaCoreApplyRuntimeProjectDefaults(runtimeProjectDocument, runtimeDirectoryRelative); const std::vector runtimeProjectPathIssues = MetaCore::MetaCoreValidateRuntimeProjectPaths(runtimeProjectDocument); if (!runtimeProjectPathIssues.empty()) { std::cerr << "MetaCorePlayer: Runtime project path validation failed: " << runtimeProjectPathIssues.front().Message << '\n'; return 1; } std::cout << "MetaCorePlayer: build profile=" << runtimeProjectDocument.BuildProfileName << " platform=" << runtimeProjectDocument.TargetPlatform << " output=" << runtimeProjectDocument.OutputDirectory.generic_string() << " cooked_assets=" << (runtimeProjectDocument.UseCookedAssets ? "true" : "false") << '\n'; std::optional cookedManifest; if (runtimeProjectDocument.UseCookedAssets) { const std::filesystem::path manifestPath = projectRoot / runtimeProjectDocument.CookedAssetsDirectory / "CookManifest.bin"; cookedManifest = MetaCoreReadCookManifestDocument(manifestPath, typeRegistry); if (cookedManifest.has_value()) { std::cout << "MetaCorePlayer: loaded cook manifest " << manifestPath.string() << " entries=" << cookedManifest->Entries.size() << '\n'; } else { std::cerr << "MetaCorePlayer: cooked assets enabled but cook manifest is missing or unreadable: " << manifestPath.string() << '\n'; } } std::optional startupSceneDocument; if (!customScenePath.empty()) { const auto absoluteScene = customScenePath.is_absolute() ? customScenePath : (projectRoot / customScenePath); startupSceneDocument = MetaCoreReadSceneDocumentFromPath(absoluteScene, typeRegistry); } if (!startupSceneDocument.has_value() && cookedManifest.has_value() && !runtimeProjectDocument.StartupScenePath.empty()) { startupSceneDocument = MetaCoreReadCookedDocument( projectRoot, *cookedManifest, runtimeProjectDocument.StartupScenePath, typeRegistry, "MetaCoreSceneDocument" ); if (startupSceneDocument.has_value()) { std::cout << "MetaCorePlayer: loaded cooked startup scene " << runtimeProjectDocument.StartupScenePath.generic_string() << '\n'; } } if (!startupSceneDocument.has_value() && !runtimeProjectDocument.StartupScenePath.empty()) { startupSceneDocument = MetaCoreReadSceneDocumentFromPath(projectRoot / runtimeProjectDocument.StartupScenePath, typeRegistry); } if (!startupSceneDocument.has_value()) { startupSceneDocument = MetaCore::MetaCoreLoadStartupSceneDocument(projectPath); } MetaCore::MetaCoreScene scene; if (startupSceneDocument.has_value()) { if (cookedManifest.has_value()) { MetaCoreRewriteSceneGltfModelPathsToCooked(*startupSceneDocument, *cookedManifest); } MetaCore::MetaCoreSceneSnapshot snapshot; snapshot.GameObjects = startupSceneDocument->GameObjects; scene.RestoreSnapshot(snapshot); std::cout << "MetaCorePlayer: loaded startup scene from project " << projectPath.string() << '\n'; } else { scene = MetaCore::MetaCoreCreateDefaultScene(); std::cout << "MetaCorePlayer: startup scene unavailable, using built-in default scene\n"; } MetaCore::MetaCoreRuntimeLifecycleExecutor runtimeLifecycleExecutor; const std::size_t runtimeComponentCount = runtimeLifecycleExecutor.RegisterDescriptors( MetaCore::MetaCoreGetRuntimeLifecycleRegistry().GetDescriptors() ); if (runtimeComponentCount > 0) { std::cout << "MetaCorePlayer: registered " << runtimeComponentCount << " runtime lifecycle component(s)\n"; } const auto runtimeLifecycleErrorSink = [](const MetaCore::MetaCoreRuntimeLifecycleError& error) { std::cerr << "MetaCorePlayer: lifecycle " << error.Phase << " failed component=" << error.ComponentTypeId << " object=" << error.ObjectId << " error=" << error.Message << '\n'; }; runtimeLifecycleExecutor.EnterScene(scene, runtimeLifecycleErrorSink); std::optional startupUiDocument; if (!runtimeProjectDocument.StartupUiPath.empty()) { bool startupUiLoadedFromCooked = false; if (cookedManifest.has_value()) { startupUiDocument = MetaCoreReadCookedDocument( projectRoot, *cookedManifest, runtimeProjectDocument.StartupUiPath, typeRegistry, "MetaCoreUiDocument" ); startupUiLoadedFromCooked = startupUiDocument.has_value(); } if (!startupUiDocument.has_value()) { const std::filesystem::path absoluteUiPath = runtimeProjectDocument.StartupUiPath.is_absolute() ? runtimeProjectDocument.StartupUiPath : (projectRoot / runtimeProjectDocument.StartupUiPath); if (std::filesystem::exists(absoluteUiPath)) { startupUiDocument = MetaCore::MetaCoreSceneSerializer::LoadUiFromJson(absoluteUiPath, typeRegistry); if (!startupUiDocument.has_value()) { std::cerr << "MetaCorePlayer: startup UI exists but is unreadable: " << runtimeProjectDocument.StartupUiPath.generic_string() << '\n'; } } else { std::cout << "MetaCorePlayer: startup UI not found: " << runtimeProjectDocument.StartupUiPath.generic_string() << '\n'; } } if (startupUiDocument.has_value()) { const auto compiledUi = MetaCore::MetaCoreCompileUiDocumentToRml( *startupUiDocument, runtimeProjectDocument.StartupUiPath.filename().string() + ".rcss" ); if (!runtimeUiRenderer.LoadCompiledDocument(compiledUi)) { std::cerr << "MetaCorePlayer: startup UI failed to load into runtime renderer error=" << runtimeUiRenderer.GetStats().LastError << '\n'; } std::cout << "MetaCorePlayer: loaded " << (startupUiLoadedFromCooked ? "cooked " : "") << "startup UI " << runtimeProjectDocument.StartupUiPath.generic_string() << " nodes=" << startupUiDocument->Nodes.size() << " roots=" << startupUiDocument->RootNodeIds.size() << " rml_bytes=" << compiledUi.Rml.size() << " rcss_bytes=" << compiledUi.Rcss.size() << '\n'; } } MetaCore::MetaCoreRuntimeDataDispatcher runtimeDataDispatcher(scene); 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(MetaCore::MetaCoreRuntimeDataSourcesDocument{}); const auto bindingsDocument = loadedBindingsDocument.value_or(MetaCore::MetaCoreRuntimeBindingsDocument{}); if (loadedSourcesDocument.has_value() && loadedBindingsDocument.has_value()) { std::cout << "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())) { std::cerr << "MetaCorePlayer: runtime config exists but is unreadable or corrupted; continuing without runtime data bindings\n"; } else { std::cout << "MetaCorePlayer: runtime config missing; continuing without runtime data bindings\n"; } } if (const auto replayPathError = MetaCoreResolveRuntimeDataReplayFilePaths(sourcesDocument, projectRoot); replayPathError.has_value()) { std::cerr << "MetaCorePlayer: " << *replayPathError << '\n'; return 1; } runtimeDataDispatcher.SetDataPointDefinitions(sourcesDocument.DataPoints); runtimeDataDispatcher.SetBindingDefinitions(bindingsDocument.Bindings); std::vector runtimeUiBindingStatuses = MetaCoreBuildRuntimeUiBindingStatuses(bindingsDocument.UiBindings); MetaCoreValidateRuntimeUiBindingTargets(bindingsDocument.UiBindings, runtimeUiRenderer, runtimeUiBindingStatuses); std::vector runtimeSources; runtimeSources.reserve(sourcesDocument.Sources.size()); for (const MetaCore::MetaCoreDataSourceDefinition& sourceDefinition : sourcesDocument.Sources) { auto runtimeAdapter = MetaCore::MetaCoreCreateRuntimeDataSourceAdapter(sourceDefinition.AdapterType); if (runtimeAdapter == nullptr) { std::cerr << "MetaCorePlayer: unsupported adapter type " << sourceDefinition.AdapterType << " source=" << sourceDefinition.Id << '\n'; auto sourceStatuses = MetaCoreCollectRuntimeSourceStatuses(runtimeSources); MetaCore::MetaCoreRuntimeDataSourceStatus unsupportedStatus; unsupportedStatus.SourceId = sourceDefinition.Id; unsupportedStatus.State = MetaCore::MetaCoreRuntimeDataSourceState::Faulted; unsupportedStatus.LastError = "Unsupported adapter type: " + sourceDefinition.AdapterType; sourceStatuses.push_back(std::move(unsupportedStatus)); MetaCoreWriteRuntimeDiagnosticsSnapshot( diagnosticsPath, typeRegistry, runtimeDataDispatcher, sourceStatuses, runtimeUiBindingStatuses ); return 1; } if (!runtimeAdapter->Configure(sourceDefinition)) { std::cerr << "MetaCorePlayer: failed to configure runtime adapter error=" << runtimeAdapter->GetStatus().LastError << '\n'; auto sourceStatuses = MetaCoreCollectRuntimeSourceStatuses(runtimeSources); sourceStatuses.push_back(runtimeAdapter->GetStatus()); MetaCoreWriteRuntimeDiagnosticsSnapshot( diagnosticsPath, typeRegistry, runtimeDataDispatcher, sourceStatuses, runtimeUiBindingStatuses ); return 1; } MetaCoreRuntimeDataSourceInstance sourceInstance; sourceInstance.Definition = sourceDefinition; sourceInstance.LastReportedState = runtimeAdapter->GetStatus().State; sourceInstance.Adapter = std::move(runtimeAdapter); if (sourceDefinition.AutoConnect && !sourceInstance.Adapter->Connect()) { std::cerr << "MetaCorePlayer: failed to connect runtime adapter error=" << sourceInstance.Adapter->GetStatus().LastError << " source=" << sourceDefinition.Id << '\n'; auto sourceStatuses = MetaCoreCollectRuntimeSourceStatuses(runtimeSources); sourceStatuses.push_back(sourceInstance.Adapter->GetStatus()); MetaCoreWriteRuntimeDiagnosticsSnapshot( diagnosticsPath, typeRegistry, runtimeDataDispatcher, sourceStatuses, runtimeUiBindingStatuses ); return 1; } sourceInstance.LastReportedState = sourceInstance.Adapter->GetStatus().State; runtimeSources.push_back(std::move(sourceInstance)); } std::uint64_t diagnosticsWriteFrame = 0; std::unordered_map reportedBindingIssues; bool runtimeStatusUiAvailable = !MetaCoreHasRuntimeUiTextBindingForNode(bindingsDocument.UiBindings, "runtime.status"); if (!runtimeStatusUiAvailable) { std::cout << "MetaCorePlayer: runtime.status is controlled by a RuntimeData UI binding\n"; } std::string lastRuntimeStatusText{}; while (!window.ShouldClose()) { window.BeginFrame(); const auto [windowWidth, windowHeight] = window.GetWindowSize(); playerRenderer.SetViewportRect(MetaCore::MetaCoreViewportRect{ 0.0F, 0.0F, static_cast(windowWidth), static_cast(windowHeight) }); runtimeUiRenderer.Resize(windowWidth, windowHeight); runtimeUiRenderer.BeginFrame(1.0F / 60.0F); runtimeLifecycleExecutor.TickScene(scene, 1.0F / 60.0F, runtimeLifecycleErrorSink); if (!runtimeSources.empty()) { std::uint64_t latestUpdateAt = 0; for (MetaCoreRuntimeDataSourceInstance& sourceInstance : runtimeSources) { sourceInstance.Adapter->Tick(1.0 / 60.0); const std::vector updates = sourceInstance.Adapter->PollUpdates(); runtimeDataDispatcher.ApplyUpdates(updates); MetaCoreApplyRuntimeUiBindings( updates, sourcesDocument.DataPoints, bindingsDocument.UiBindings, runtimeUiRenderer, runtimeUiBindingStatuses ); latestUpdateAt = std::max(latestUpdateAt, sourceInstance.Adapter->GetStatus().LastUpdateAt); } runtimeDataDispatcher.TickStaleness(latestUpdateAt); MetaCoreTickRuntimeBindingStaleness(runtimeUiBindingStatuses, latestUpdateAt); } std::vector sourceStatuses = MetaCoreCollectRuntimeSourceStatuses(runtimeSources); auto diagnostics = runtimeDataDispatcher.BuildDiagnosticsSnapshot(sourceStatuses); MetaCoreAppendRuntimeUiBindingDiagnostics(diagnostics, runtimeUiBindingStatuses); if (runtimeStatusUiAvailable && runtimeUiRenderer.GetStats().Loaded) { const std::string runtimeStatusText = MetaCoreBuildRuntimeDataStatusText(diagnostics); if (runtimeStatusText != lastRuntimeStatusText) { if (runtimeUiRenderer.SetNodeText("runtime.status", runtimeStatusText)) { lastRuntimeStatusText = runtimeStatusText; } else { runtimeStatusUiAvailable = false; } } } if ((diagnosticsWriteFrame % 15ULL) == 0ULL) { (void)MetaCore::MetaCoreWriteRuntimeDiagnosticsSnapshot(diagnosticsPath, diagnostics, typeRegistry); } ++diagnosticsWriteFrame; for (MetaCoreRuntimeDataSourceInstance& sourceInstance : runtimeSources) { if (sourceInstance.Adapter->GetStatus().State != sourceInstance.LastReportedState) { std::cout << "MetaCorePlayer: data source state changed source=" << sourceInstance.Definition.Id << " adapter=" << sourceInstance.Definition.AdapterType << " state=" << static_cast(sourceInstance.Adapter->GetStatus().State) << " error=" << sourceInstance.Adapter->GetStatus().LastError << '\n'; sourceInstance.LastReportedState = sourceInstance.Adapter->GetStatus().State; } } MetaCoreLogRuntimeBindingIssues(diagnostics, reportedBindingIssues); playerRenderer.RenderScene(scene, MetaCoreBuildPlayerSceneView()); runtimeUiRenderer.Render(); playerRenderer.SetRuntimeUiOverlayFrame(runtimeUiRenderer.GetLastFrame(), windowWidth, windowHeight); playerRenderer.RenderAll(); renderDevice.RenderFrame(); renderDevice.PresentFrame(); window.EndFrame(); } runtimeLifecycleExecutor.ExitScene(scene, runtimeLifecycleErrorSink); runtimeUiRenderer.Shutdown(); playerRenderer.Shutdown(); renderDevice.Shutdown(); window.Shutdown(); return 0; }