#include "MetaCorePlatform/MetaCoreWindow.h" #include "MetaCorePhysics/MetaCorePhysics.h" #include "MetaCoreDelivery/MetaCoreBuildPipeline.h" #include "MetaCoreDelivery/MetaCoreCrashReporter.h" #include "MetaCoreFoundation/MetaCoreGeneratedReflection.h" #include "MetaCoreFoundation/MetaCoreArchive.h" #include "MetaCoreFoundation/MetaCoreAssetTypes.h" #include "MetaCoreFoundation/MetaCorePackage.h" #include "MetaCoreFoundation/MetaCoreProject.h" #include "MetaCoreRender/MetaCoreEditorViewportRenderer.h" #include "MetaCoreRender/MetaCoreRenderDevice.h" #include "MetaCoreRender/MetaCoreRenderTypes.h" #include "MetaCoreRender/MetaCoreSceneRenderSync.h" #include "MetaCoreRendering/MetaCoreRendering.h" #include "MetaCoreRuntimeData/MetaCoreRuntimeDataDispatcher.h" #include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h" #include "MetaCoreRuntimeData/MetaCoreRuntimeDataSource.h" #include "MetaCoreRuntimeUi/MetaCoreRuntimeUiSystem.h" #include "MetaCoreRuntimeInput/MetaCoreRuntimeInput.h" #include "MetaCoreRuntimeInput/MetaCoreRuntimeInteraction.h" #include "MetaCoreScene/MetaCoreScenePackage.h" #include "MetaCoreScene/MetaCoreScene.h" #include "MetaCoreScripting/MetaCoreScripting.h" #include #include #include #include #include #include #include #include namespace { struct MetaCorePackedPhysicsVertex { float px, py, pz, nx, ny, nz, u, v; }; std::unordered_map MetaCoreLoadPhysicsMeshes(const std::filesystem::path& root, const MetaCore::MetaCoreTypeRegistry& registry) { std::unordered_map result; std::error_code error; if (!std::filesystem::is_directory(root, error)) return result; const auto collisionRoot = root / "Physics"; if (std::filesystem::is_directory(collisionRoot, error)) { for (const auto& entry : std::filesystem::directory_iterator(collisionRoot, error)) { if (error || !entry.is_regular_file() || entry.path().extension() != ".mccollision") continue; const auto guid = MetaCore::MetaCoreAssetGuid::Parse(entry.path().stem().string()); if (!guid) continue; std::ifstream input(entry.path(), std::ios::binary | std::ios::ate); if (!input) continue; const auto size = input.tellg(); if (size <= 0) continue; std::vector bytes(static_cast(size)); input.seekg(0, std::ios::beg); input.read(reinterpret_cast(bytes.data()), size); if (!input) continue; if (auto collision = MetaCore::MetaCoreDecodePhysicsCollisionDerivedData(bytes)) result.insert_or_assign(*guid, std::move(*collision)); } } for (const auto& entry : std::filesystem::recursive_directory_iterator(root, error)) { if (error || !entry.is_regular_file() || entry.path().extension() != ".mcasset") continue; const auto package = MetaCore::MetaCoreReadPackageFile(entry.path(), registry); if (!package || package->PayloadSections.empty()) continue; MetaCore::MetaCoreModelAssetDocument model; if (!MetaCore::MetaCoreDeserializeFromBytes(package->PayloadSections.front(), model, registry)) continue; for (const auto& mesh : model.GeneratedMeshAssets) { if (!mesh.AssetGuid.IsValid() || mesh.PayloadIndex >= package->PayloadSections.size()) continue; const auto& payload = package->PayloadSections[static_cast(mesh.PayloadIndex)]; if (payload.size() < sizeof(std::uint32_t) * 2U) continue; std::uint32_t vertexCount = 0, indexCount = 0; std::memcpy(&vertexCount, payload.data(), sizeof(vertexCount)); std::memcpy(&indexCount, payload.data() + sizeof(vertexCount), sizeof(indexCount)); const std::size_t required = sizeof(std::uint32_t) * 2U + sizeof(MetaCorePackedPhysicsVertex) * vertexCount + sizeof(std::uint32_t) * indexCount; if (vertexCount == 0U || indexCount < 3U || required > payload.size()) continue; MetaCore::MetaCorePhysicsMeshData data; data.Positions.resize(vertexCount); data.Indices.resize(indexCount); const std::byte* cursor = payload.data() + sizeof(std::uint32_t) * 2U; for (std::uint32_t index = 0; index < vertexCount; ++index) { MetaCorePackedPhysicsVertex vertex{}; std::memcpy(&vertex, cursor, sizeof(vertex)); cursor += sizeof(vertex); data.Positions[index] = {vertex.px, vertex.pz, -vertex.py}; } std::memcpy(data.Indices.data(), cursor, sizeof(std::uint32_t) * indexCount); result.try_emplace(mesh.AssetGuid, std::move(data)); } } return result; } 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"; document.InputMapPath = std::filesystem::path("Runtime") / "Input.mcruntime"; document.PhysicsSettingsPath = std::filesystem::path("Runtime") / "Physics.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::string requestedRenderDebugView{}; 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)); } else if (std::strcmp(argv[i], "--render-debug") == 0 && i + 1 < argc) { requestedRenderDebugView = argv[++i]; } } 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; } if(!requestedRenderDebugView.empty()){ bool developmentPlayer=!deliveryDocument.has_value(); if(deliveryDocument&&std::filesystem::is_regular_file(packageRoot/"Manifest"/"MetaCore.release.json")){ MetaCore::MetaCoreBuildIssue manifestIssue;const auto manifest=MetaCore::MetaCoreReadReleaseManifest(packageRoot/"Manifest"/"MetaCore.release.json",&manifestIssue); developmentPlayer=manifest&&manifest->Configuration!="Release"; } const auto debugView=MetaCore::MetaCoreParseRenderDebugView(requestedRenderDebugView); if(developmentPlayer&&debugView)viewportRenderer.SetRenderDebugView(*debugView); else std::cerr<<"Render debug view rejected; mode="<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::MetaCoreRegisterFoundationGeneratedTypes(typeRegistry); MetaCore::MetaCoreRegisterRenderingGeneratedTypes(typeRegistry); MetaCore::MetaCoreRegisterRuntimeDataGeneratedTypes(typeRegistry); MetaCore::MetaCoreRegisterRuntimeInputGeneratedTypes(typeRegistry); MetaCore::MetaCoreRegisterPhysicsGeneratedTypes(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; const auto loadedInputMap = runtimeProjectDocument.InputMapPath.empty() ? std::optional{} : MetaCore::MetaCoreReadInputMap((projectRoot / runtimeProjectDocument.InputMapPath).lexically_normal(), typeRegistry); MetaCore::MetaCoreRuntimeInput runtimeInput; runtimeInput.SetInputMap(loadedInputMap.value_or(MetaCore::MetaCoreInputMapDocument{})); const auto projectDocument = MetaCore::MetaCoreReadProjectFile(projectPath); const std::string inputProjectName = projectDocument ? projectDocument->Name : projectRoot.filename().string(); const auto inputOverridePath = MetaCore::MetaCoreGetInputOverridePath(inputProjectName); (void)runtimeInput.LoadOverrides(inputOverridePath); for (const auto& warning : runtimeInput.GetWarnings()) errors << "MetaCorePlayer Input: " << warning << '\n'; const auto physicsSettingsPath = projectRoot / (runtimeProjectDocument.PhysicsSettingsPath.empty() ? std::filesystem::path("Runtime/Physics.mcruntime") : runtimeProjectDocument.PhysicsSettingsPath); auto physicsSettings = MetaCore::MetaCoreReadPhysicsSettings(physicsSettingsPath, typeRegistry) .value_or(MetaCore::MetaCoreBuildDefaultPhysicsSettings()); std::unordered_map physicsMaterials; std::error_code physicsAssetError; const auto physicsAssetsRoot=projectRoot/"Assets"; if(std::filesystem::is_directory(physicsAssetsRoot,physicsAssetError))for(const auto& entry:std::filesystem::recursive_directory_iterator(physicsAssetsRoot,physicsAssetError))if(entry.is_regular_file()&&entry.path().extension()==".mcphysicsmaterial")if(auto material=MetaCore::MetaCoreReadPhysicsMaterial(entry.path(),typeRegistry))physicsMaterials.insert_or_assign(material->AssetGuid,*material); auto physicsMeshes = MetaCoreLoadPhysicsMeshes(projectRoot, typeRegistry); MetaCore::MetaCorePhysicsWorld physicsWorld(scene, physicsSettings, [&physicsMeshes](MetaCore::MetaCoreAssetGuid guid)->std::optional{const auto it=physicsMeshes.find(guid);return it==physicsMeshes.end()?std::nullopt:std::optional(it->second);}, [&physicsMaterials](MetaCore::MetaCoreAssetGuid guid)->std::optional{const auto it=physicsMaterials.find(guid);return it==physicsMaterials.end()?std::nullopt:std::optional(it->second);}); 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'; }, &runtimeInput, &physicsWorld ); scriptRuntime.Start(); physicsWorld.SetEventCallback([&scriptRuntime](const MetaCore::MetaCorePhysicsContactEvent& event) { const auto topic = [&]() -> const char* { using E = MetaCore::MetaCorePhysicsEventType; switch (event.Type) { case E::CollisionEnter: return "MetaCore.Physics.CollisionEnter"; case E::CollisionStay: return "MetaCore.Physics.CollisionStay"; case E::CollisionExit: return "MetaCore.Physics.CollisionExit"; case E::TriggerEnter: return "MetaCore.Physics.TriggerEnter"; case E::TriggerStay: return "MetaCore.Physics.TriggerStay"; case E::TriggerExit: return "MetaCore.Physics.TriggerExit"; case E::ConstraintBroken: return "MetaCore.Physics.ConstraintBroken"; } return "MetaCore.Physics"; }(); const auto objectValue = [&scriptRuntime](MetaCore::MetaCoreId id) { MetaCoreScriptValueV1 value{}; value.Type = MetaCoreScriptAbiValue_GameObjectRef; value.ObjectValue = {scriptRuntime.GetWorldGeneration(), id}; return value; }; const auto vectorValue = [](glm::vec3 source) { MetaCoreScriptValueV1 value{}; value.Type = MetaCoreScriptAbiValue_Vec3; value.Vec3[0]=source.x; value.Vec3[1]=source.y; value.Vec3[2]=source.z; return value; }; MetaCoreScriptValueV1 impulse{}; impulse.Type = MetaCoreScriptAbiValue_Float; impulse.NumberValue = event.Impulse; scriptRuntime.PublishToObject(event.ObjectA, topic, {objectValue(event.ObjectB), objectValue(event.ColliderA), objectValue(event.ColliderB), vectorValue(event.Point), vectorValue(event.Normal), impulse}); scriptRuntime.PublishToObject(event.ObjectB, topic, {objectValue(event.ObjectA), objectValue(event.ColliderB), objectValue(event.ColliderA), vectorValue(event.Point), vectorValue(-event.Normal), impulse}); }); 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; MetaCore::MetaCoreRuntimeInteraction runtimeInteraction; const std::uint64_t inputSubscription = runtimeInput.Subscribe([&scriptRuntime](const MetaCore::MetaCoreInputActionEvent& event) { MetaCoreScriptValueV1 phase{}; phase.Type = MetaCoreScriptAbiValue_Int; phase.IntValue = static_cast(event.Phase); scriptRuntime.Publish("MetaCore.Input.Action/" + event.ActionId, {phase}); }); runtimeUi.SetEventCallback([&scriptRuntime](const MetaCore::MetaCoreRuntimeUiEvent& event) { const auto textValue = [](const std::string& text) { MetaCoreScriptValueV1 value{}; value.Type = MetaCoreScriptAbiValue_String; std::snprintf(value.Text, sizeof(value.Text), "%s", text.c_str()); return value; }; scriptRuntime.Publish("MetaCore.UI", { textValue(event.DocumentId), textValue(event.ElementId), textValue(event.Action), textValue(event.Type), textValue(event.Value) }); }); runtimeInteraction.SetEventCallback([&scriptRuntime](const MetaCore::MetaCorePointerEvent& event) { MetaCoreScriptValueV1 type{}; type.Type = MetaCoreScriptAbiValue_Int; type.IntValue = static_cast(event.Type); MetaCoreScriptValueV1 button{}; button.Type = MetaCoreScriptAbiValue_Int; button.IntValue = event.Button; MetaCoreScriptValueV1 screen{}; screen.Type = MetaCoreScriptAbiValue_Vec3; screen.Vec3[0] = event.ScreenPosition.x; screen.Vec3[1] = event.ScreenPosition.y; MetaCoreScriptValueV1 delta{}; delta.Type = MetaCoreScriptAbiValue_Vec3; delta.Vec3[0] = event.ScreenDelta.x; delta.Vec3[1] = event.ScreenDelta.y; MetaCoreScriptValueV1 world{}; world.Type = MetaCoreScriptAbiValue_Vec3; world.Vec3[0] = event.WorldPosition.x; world.Vec3[1] = event.WorldPosition.y; world.Vec3[2] = event.WorldPosition.z; MetaCoreScriptValueV1 target{}; target.Type = MetaCoreScriptAbiValue_GameObjectRef; target.ObjectValue = {scriptRuntime.GetWorldGeneration(), event.TargetObjectId}; scriptRuntime.PublishToObject(event.TargetObjectId, "MetaCore.Interaction.Pointer", {type, button, screen, delta, world, target}); }); std::uint64_t diagnosticsWriteFrame = 0; std::uint64_t renderedFrames = 0U; double fixedAccumulatorSeconds = 0.0; MetaCore::MetaCoreSceneRenderSync interactionCameraSync; while (!window.ShouldClose()) { window.BeginFrame(); const double frameDeltaSeconds = window.GetDeltaSeconds(); const MetaCore::MetaCoreInputConsumption uiConsumption = runtimeUi.ProcessInput(); runtimeInput.Update(window.GetInput(), uiConsumption); const auto [inputWidth, inputHeight] = window.GetWindowSize(); const MetaCore::MetaCoreViewportRect inputViewport{0.0F, 0.0F, static_cast(inputWidth), static_cast(inputHeight)}; MetaCore::MetaCoreSceneView interactionSceneView = MetaCoreBuildPlayerSceneView(); const auto interactionSnapshot = interactionCameraSync.BuildSnapshot(scene); (void)MetaCore::MetaCoreSceneRenderSync::TryBuildSceneViewFromPrimaryCamera(interactionSnapshot, interactionSceneView); runtimeInteraction.Update(scene, viewportRenderer, physicsWorld, interactionSceneView, window.GetInput(), inputViewport, uiConsumption.Mouse); fixedAccumulatorSeconds += std::clamp(frameDeltaSeconds, 0.0, physicsSettings.MaxFrameDelta); std::uint32_t fixedSteps = 0; while (fixedAccumulatorSeconds >= physicsSettings.FixedTimeStep && fixedSteps < physicsSettings.MaxFixedStepsPerFrame) { scriptRuntime.FixedUpdate(physicsSettings.FixedTimeStep); physicsWorld.Step(physicsSettings.FixedTimeStep); fixedAccumulatorSeconds -= physicsSettings.FixedTimeStep; ++fixedSteps; } if (fixedSteps == physicsSettings.MaxFixedStepsPerFrame && fixedAccumulatorSeconds >= physicsSettings.FixedTimeStep) fixedAccumulatorSeconds = 0.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; } physicsWorld.Stop(); scriptRuntime.Stop(); runtimeInput.Unsubscribe(inputSubscription); (void)runtimeInput.SaveOverrides(inputOverridePath); runtimeUi.Shutdown(); viewportRenderer.Shutdown(); renderDevice.Shutdown(); window.Shutdown(); return 0; }