378 lines
15 KiB
C++
378 lines
15 KiB
C++
#include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h"
|
|
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
|
|
namespace MetaCore {
|
|
|
|
namespace {
|
|
|
|
template <typename T>
|
|
[[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<const char*>(bytes->data()), static_cast<std::streamsize>(bytes->size()));
|
|
}
|
|
return output.good();
|
|
}
|
|
|
|
template <typename T>
|
|
[[nodiscard]] std::optional<T> 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<std::size_t>(input.tellg());
|
|
input.seekg(0, std::ios::beg);
|
|
|
|
std::vector<std::byte> bytes(size);
|
|
if (size > 0 && !input.read(reinterpret_cast<char*>(bytes.data()), static_cast<std::streamsize>(size))) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
T document{};
|
|
if (!MetaCoreDeserializeFromBytes(std::span<const std::byte>(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<MetaCoreRuntimeDataSourcesDocument> MetaCoreReadRuntimeDataSourcesDocument(
|
|
const std::filesystem::path& path,
|
|
const MetaCoreTypeRegistry& registry
|
|
) {
|
|
return MetaCoreReadBinaryDocument<MetaCoreRuntimeDataSourcesDocument>(path, registry);
|
|
}
|
|
|
|
std::optional<MetaCoreRuntimeBindingsDocument> MetaCoreReadRuntimeBindingsDocument(
|
|
const std::filesystem::path& path,
|
|
const MetaCoreTypeRegistry& registry
|
|
) {
|
|
return MetaCoreReadBinaryDocument<MetaCoreRuntimeBindingsDocument>(path, registry);
|
|
}
|
|
|
|
bool MetaCoreWriteRuntimeProjectDocument(
|
|
const std::filesystem::path& path,
|
|
const MetaCoreRuntimeProjectDocument& document,
|
|
const MetaCoreTypeRegistry& registry
|
|
) {
|
|
return MetaCoreWriteBinaryDocument(path, document, registry);
|
|
}
|
|
|
|
std::optional<MetaCoreRuntimeProjectDocument> MetaCoreReadRuntimeProjectDocument(
|
|
const std::filesystem::path& path,
|
|
const MetaCoreTypeRegistry& registry
|
|
) {
|
|
return MetaCoreReadBinaryDocument<MetaCoreRuntimeProjectDocument>(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<MetaCoreRuntimeConfigIssue> MetaCoreValidateRuntimeProjectPaths(
|
|
const MetaCoreRuntimeProjectDocument& document
|
|
) {
|
|
std::vector<MetaCoreRuntimeConfigIssue> 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<MetaCoreRuntimeDiagnosticsSnapshot> MetaCoreReadRuntimeDiagnosticsSnapshot(
|
|
const std::filesystem::path& path,
|
|
const MetaCoreTypeRegistry& registry
|
|
) {
|
|
return MetaCoreReadBinaryDocument<MetaCoreRuntimeDiagnosticsSnapshot>(path, registry);
|
|
}
|
|
|
|
std::vector<MetaCoreRuntimeConfigIssue> MetaCoreValidateRuntimeDataDocuments(
|
|
const MetaCoreRuntimeDataSourcesDocument& sourcesDocument,
|
|
const MetaCoreRuntimeBindingsDocument& bindingsDocument
|
|
) {
|
|
std::vector<MetaCoreRuntimeConfigIssue> issues;
|
|
|
|
std::unordered_set<std::string> 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<std::string> dataPointIds;
|
|
std::unordered_map<std::string, MetaCoreRuntimeValueType> 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<std::string> 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
|