#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h" #include #include #include #include namespace MetaCore { namespace { template [[nodiscard]] bool MetaCoreWriteBinaryDocument( const std::filesystem::path& path, const T& document, const MetaCoreTypeRegistry& registry ) { const auto bytes = MetaCoreSerializeToBytes(document, registry); if (!bytes.has_value()) { return false; } std::filesystem::create_directories(path.parent_path()); std::ofstream output(path, std::ios::binary | std::ios::trunc); if (!output.is_open()) { return false; } if (!bytes->empty()) { output.write(reinterpret_cast(bytes->data()), static_cast(bytes->size())); } return output.good(); } template [[nodiscard]] std::optional MetaCoreReadBinaryDocument( const std::filesystem::path& path, const MetaCoreTypeRegistry& registry ) { std::ifstream input(path, 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 bytes(size); if (size > 0 && !input.read(reinterpret_cast(bytes.data()), static_cast(size))) { return std::nullopt; } T document{}; if (!MetaCoreDeserializeFromBytes(std::span(bytes.data(), bytes.size()), document, registry)) { return std::nullopt; } return document; } [[nodiscard]] MetaCoreRuntimeValueType MetaCoreExpectedValueTypeForBindingTarget( MetaCoreRuntimeBindingTarget target ) { switch (target) { case MetaCoreRuntimeBindingTarget::MeshRendererVisible: return MetaCoreRuntimeValueType::Bool; case MetaCoreRuntimeBindingTarget::LightIntensity: return MetaCoreRuntimeValueType::Double; case MetaCoreRuntimeBindingTarget::TransformPosition: case MetaCoreRuntimeBindingTarget::MeshRendererBaseColor: case MetaCoreRuntimeBindingTarget::LightColor: default: return MetaCoreRuntimeValueType::Vec3; } } [[nodiscard]] const char* MetaCoreRuntimeValueTypeName(MetaCoreRuntimeValueType type) { switch (type) { case MetaCoreRuntimeValueType::Bool: return "Bool"; case MetaCoreRuntimeValueType::Int64: return "Int64"; case MetaCoreRuntimeValueType::Double: return "Double"; case MetaCoreRuntimeValueType::String: return "String"; case MetaCoreRuntimeValueType::Vec3: return "Vec3"; } return "Unknown"; } } // namespace bool MetaCoreWriteRuntimeDataSourcesDocument( const std::filesystem::path& path, const MetaCoreRuntimeDataSourcesDocument& document, const MetaCoreTypeRegistry& registry ) { return MetaCoreWriteBinaryDocument(path, document, registry); } bool MetaCoreWriteRuntimeBindingsDocument( const std::filesystem::path& path, const MetaCoreRuntimeBindingsDocument& document, const MetaCoreTypeRegistry& registry ) { return MetaCoreWriteBinaryDocument(path, document, registry); } std::optional MetaCoreReadRuntimeDataSourcesDocument( const std::filesystem::path& path, const MetaCoreTypeRegistry& registry ) { return MetaCoreReadBinaryDocument(path, registry); } std::optional MetaCoreReadRuntimeBindingsDocument( const std::filesystem::path& path, const MetaCoreTypeRegistry& registry ) { return MetaCoreReadBinaryDocument(path, registry); } bool MetaCoreWriteRuntimeProjectDocument( const std::filesystem::path& path, const MetaCoreRuntimeProjectDocument& document, const MetaCoreTypeRegistry& registry ) { return MetaCoreWriteBinaryDocument(path, document, registry); } std::optional MetaCoreReadRuntimeProjectDocument( const std::filesystem::path& path, const MetaCoreTypeRegistry& registry ) { return MetaCoreReadBinaryDocument(path, registry); } bool MetaCoreIsUnsafeRuntimeProjectPath(const std::filesystem::path& path) { if (path.is_absolute()) { return true; } for (const auto& part : path.lexically_normal()) { if (part == "..") { return true; } } return false; } std::filesystem::path MetaCoreBuildRuntimeDirectoryRelativePath( const std::filesystem::path& projectRoot, const std::filesystem::path& runtimeDirectory ) { std::filesystem::path relativeRuntimeDirectory = !runtimeDirectory.empty() ? runtimeDirectory.lexically_relative(projectRoot) : std::filesystem::path("Runtime"); if (relativeRuntimeDirectory.empty() || relativeRuntimeDirectory == "." || MetaCoreIsUnsafeRuntimeProjectPath(relativeRuntimeDirectory)) { relativeRuntimeDirectory = "Runtime"; } return relativeRuntimeDirectory.lexically_normal(); } MetaCoreRuntimeProjectDocument MetaCoreBuildDefaultRuntimeProjectDocument( const std::filesystem::path& runtimeDirectoryRelative ) { MetaCoreRuntimeProjectDocument document; document.StartupScenePath = std::filesystem::path("Scenes") / "Main.mcscene.json"; document.StartupUiPath = std::filesystem::path("Assets") / "UI" / "Hud.mcui.json"; document.BuildProfileName = "Development"; document.TargetPlatform = "Windows"; document.GraphicsBackend = "OpenGL"; document.Architecture = "x64"; document.OutputDirectory = std::filesystem::path("Build") / "Windows"; document.CookedAssetsDirectory = std::filesystem::path("Library") / "Cooked" / "Windows"; document.UseCookedAssets = false; document.DataSourcesPath = runtimeDirectoryRelative / "DataSources.mcruntime"; document.BindingsPath = runtimeDirectoryRelative / "Bindings.mcruntime"; document.DiagnosticsPath = runtimeDirectoryRelative / "Diagnostics.mcruntimestate"; return document; } void MetaCoreApplyRuntimeProjectDefaults( MetaCoreRuntimeProjectDocument& document, const std::filesystem::path& runtimeDirectoryRelative ) { const MetaCoreRuntimeProjectDocument defaults = MetaCoreBuildDefaultRuntimeProjectDocument(runtimeDirectoryRelative); if (document.StartupScenePath.empty()) { document.StartupScenePath = defaults.StartupScenePath; } if (document.StartupUiPath.empty()) { document.StartupUiPath = defaults.StartupUiPath; } if (document.BuildProfileName.empty()) { document.BuildProfileName = defaults.BuildProfileName; } if (document.TargetPlatform.empty()) { document.TargetPlatform = defaults.TargetPlatform; } if (document.GraphicsBackend.empty()) { document.GraphicsBackend = defaults.GraphicsBackend; } if (document.Architecture.empty()) { document.Architecture = defaults.Architecture; } if (document.OutputDirectory.empty()) { document.OutputDirectory = defaults.OutputDirectory; } if (document.CookedAssetsDirectory.empty()) { document.CookedAssetsDirectory = defaults.CookedAssetsDirectory; } if (document.DataSourcesPath.empty()) { document.DataSourcesPath = defaults.DataSourcesPath; } if (document.BindingsPath.empty()) { document.BindingsPath = defaults.BindingsPath; } if (document.DiagnosticsPath.empty()) { document.DiagnosticsPath = defaults.DiagnosticsPath; } } std::vector MetaCoreValidateRuntimeProjectPaths( const MetaCoreRuntimeProjectDocument& document ) { std::vector issues; const auto validatePath = [&](const std::filesystem::path& path, const char* label) { if (!path.empty() && MetaCoreIsUnsafeRuntimeProjectPath(path)) { issues.push_back(MetaCoreRuntimeConfigIssue{ MetaCoreRuntimeConfigIssueSeverity::Error, "RuntimeProject", std::string(label) + " must be a project-relative path without parent traversal" }); } }; validatePath(document.StartupScenePath, "StartupScenePath"); validatePath(document.StartupUiPath, "StartupUiPath"); validatePath(document.CookedAssetsDirectory, "CookedAssetsDirectory"); validatePath(document.DataSourcesPath, "DataSourcesPath"); validatePath(document.BindingsPath, "BindingsPath"); validatePath(document.DiagnosticsPath, "DiagnosticsPath"); return issues; } bool MetaCoreWriteRuntimeDiagnosticsSnapshot( const std::filesystem::path& path, const MetaCoreRuntimeDiagnosticsSnapshot& snapshot, const MetaCoreTypeRegistry& registry ) { return MetaCoreWriteBinaryDocument(path, snapshot, registry); } std::optional MetaCoreReadRuntimeDiagnosticsSnapshot( const std::filesystem::path& path, const MetaCoreTypeRegistry& registry ) { return MetaCoreReadBinaryDocument(path, registry); } std::vector MetaCoreValidateRuntimeDataDocuments( const MetaCoreRuntimeDataSourcesDocument& sourcesDocument, const MetaCoreRuntimeBindingsDocument& bindingsDocument ) { std::vector issues; std::unordered_set sourceIds; for (const auto& source : sourcesDocument.Sources) { if (source.Id.empty()) { issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, "Source", "存在空的 SourceId"}); continue; } if (!sourceIds.insert(source.Id).second) { issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, source.Id, "SourceId 重复"}); } if (source.AdapterType.empty()) { issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, source.Id, "AdapterType 不能为空"}); } if (source.AdapterType == "file_replay") { const auto filePathSetting = std::find_if( source.ConnectionSettings.begin(), source.ConnectionSettings.end(), [](const MetaCoreDataSourceSetting& setting) { return setting.Key == "file_path"; } ); if (filePathSetting == source.ConnectionSettings.end() || filePathSetting->Value.empty()) { issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, source.Id, "file_replay 缺少 file_path"}); } } if (source.AdapterType == "tcp") { const auto portSetting = std::find_if( source.ConnectionSettings.begin(), source.ConnectionSettings.end(), [](const MetaCoreDataSourceSetting& setting) { return setting.Key == "port"; } ); if (portSetting == source.ConnectionSettings.end() || portSetting->Value.empty()) { issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, source.Id, "tcp 缺少 port"}); } } } std::unordered_set dataPointIds; std::unordered_map dataPointValueTypes; for (const auto& dataPoint : sourcesDocument.DataPoints) { if (dataPoint.Id.empty()) { issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, "DataPoint", "存在空的 DataPointId"}); continue; } if (!dataPointIds.insert(dataPoint.Id).second) { issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, dataPoint.Id, "DataPointId 重复"}); } if (dataPoint.SourceId.empty() || !sourceIds.contains(dataPoint.SourceId)) { issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, dataPoint.Id, "DataPoint 引用了不存在的 SourceId"}); } dataPointValueTypes[dataPoint.Id] = dataPoint.ValueType; if (dataPoint.ExternalAddress.empty()) { issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Warning, dataPoint.Id, "ExternalAddress 为空"}); } } std::unordered_set bindingIds; for (const auto& binding : bindingsDocument.Bindings) { if (binding.BindingId.empty()) { issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, "Binding", "存在空的 BindingId"}); continue; } if (!bindingIds.insert(binding.BindingId).second) { issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, binding.BindingId, "BindingId 重复"}); } if (binding.DataPointId.empty() || !dataPointIds.contains(binding.DataPointId)) { issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, binding.BindingId, "Binding 引用了不存在的 DataPointId"}); } else if (const auto dataPointType = dataPointValueTypes.find(binding.DataPointId); dataPointType != dataPointValueTypes.end()) { const MetaCoreRuntimeValueType expectedType = MetaCoreExpectedValueTypeForBindingTarget(binding.Target); if (dataPointType->second != expectedType) { issues.push_back({ MetaCoreRuntimeConfigIssueSeverity::Error, binding.BindingId, "Binding target expects " + std::string(MetaCoreRuntimeValueTypeName(expectedType)) + " but DataPoint is " + MetaCoreRuntimeValueTypeName(dataPointType->second) }); } } if (binding.TargetObjectId == 0) { issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, binding.BindingId, "Binding 缺少 TargetObjectId"}); } } for (const auto& binding : bindingsDocument.UiBindings) { if (binding.BindingId.empty()) { issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, "UiBinding", "UI BindingId is empty"}); continue; } if (!bindingIds.insert(binding.BindingId).second) { issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, binding.BindingId, "BindingId duplicate"}); } if (binding.DataPointId.empty() || !dataPointIds.contains(binding.DataPointId)) { issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, binding.BindingId, "UI binding references a missing DataPointId"}); } if (binding.TargetNodeId.empty()) { issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Error, binding.BindingId, "UI binding missing TargetNodeId"}); } } if (sourcesDocument.Sources.empty()) { issues.push_back({MetaCoreRuntimeConfigIssueSeverity::Warning, "Runtime", "当前没有任何 Source"}); } return issues; } } // namespace MetaCore