// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "ProjectInfo.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Platform/File.h" #include "Engine/Core/Log.h" #include "Engine/Engine/Globals.h" #include "Engine/Core/Math/Quaternion.h" #include "Engine/Serialization/JsonWriters.h" #include "Engine/Serialization/JsonTools.h" #include using namespace pugi; Array ProjectInfo::ProjectsCache; void ShowProjectLoadError(const Char* errorMsg, const String& projectRootFolder) { Platform::Error(String::Format(TEXT("Failed to load project. {0}\nPath: '{1}'"), errorMsg, projectRootFolder)); } Vector3 GetVector3FromXml(const xml_node& parent, const PUGIXML_CHAR* name, const Vector3& defaultValue) { const auto node = parent.child(name); if (!node.empty()) { const auto x = node.child_value(PUGIXML_TEXT("X")); const auto y = node.child_value(PUGIXML_TEXT("Y")); const auto z = node.child_value(PUGIXML_TEXT("Z")); if (x && y && z) { Vector3 v; if (!StringUtils::Parse(x, &v.X) && !StringUtils::Parse(y, &v.Y) && !StringUtils::Parse(z, &v.Z)) { return v; } } } return defaultValue; } int32 GetIntFromXml(const xml_node& parent, const PUGIXML_CHAR* name, const int32 defaultValue) { const auto node = parent.child_value(name); if (node) { int32 v; if (!StringUtils::Parse(node, &v)) { return v; } } return defaultValue; } bool ProjectInfo::SaveProject() { // Serialize object to Json rapidjson_flax::StringBuffer buffer; PrettyJsonWriter writerObj(buffer); auto& stream = *(JsonWriter*)&writerObj; stream.StartObject(); { stream.JKEY("Name"); stream.String(Name); stream.JKEY("Version"); stream.String(Version.ToString()); stream.JKEY("Company"); stream.String(Company); stream.JKEY("Copyright"); stream.String(Copyright); stream.JKEY("GameTarget"); stream.String(GameTarget); stream.JKEY("EditorTarget"); stream.String(EditorTarget); stream.JKEY("References"); stream.StartArray(); for (auto& reference : References) { stream.StartObject(); stream.JKEY("Name"); stream.String(reference.Name); stream.EndObject(); } stream.EndArray(); if (DefaultScene.IsValid()) { stream.JKEY("DefaultScene"); stream.Guid(DefaultScene); } if (DefaultSceneSpawn != Ray(Vector3::Zero, Vector3::Forward)) { stream.JKEY("DefaultSceneSpawn"); stream.Ray(DefaultSceneSpawn); } stream.JKEY("MinEngineVersion"); stream.String(MinEngineVersion.ToString()); if (EngineNickname.HasChars()) { stream.JKEY("EngineNickname"); stream.String(EngineNickname); } } stream.EndObject(); // Write to file return File::WriteAllBytes(ProjectPath, (const byte*)buffer.GetString(), (int32)buffer.GetSize()); } bool ProjectInfo::LoadProject(const String& projectPath) { // Load Json file StringAnsi fileData; if (File::ReadAllText(projectPath, fileData)) { ShowProjectLoadError(TEXT("Failed to read file contents."), projectPath); return true; } // Parse Json data rapidjson_flax::Document document; document.Parse(fileData.Get(), fileData.Length()); if (document.HasParseError()) { ShowProjectLoadError(TEXT("Failed to parse project contents. Ensure to have valid Json format."), projectPath); return true; } // Parse properties Name = JsonTools::GetString(document, "Name", String::Empty); ProjectPath = projectPath; ProjectFolderPath = StringUtils::GetDirectoryName(projectPath); const auto versionMember = document.FindMember("Version"); if (versionMember != document.MemberEnd()) { auto& version = versionMember->value; if (version.IsString()) { Version::Parse(version.GetText(), &Version); } else if (version.IsObject()) { Version = ::Version( JsonTools::GetInt(version, "Major", 0), JsonTools::GetInt(version, "Minor", 0), JsonTools::GetInt(version, "Build", 0)); } } if (Version.Revision() == 0) Version = ::Version(Version.Major(), Version.Minor(), Version.Build()); if (Version.Build() == 0 && Version.Revision() == -1) Version = ::Version(Version.Major(), Version.Minor()); Company = JsonTools::GetString(document, "Company", String::Empty); Copyright = JsonTools::GetString(document, "Copyright", String::Empty); GameTarget = JsonTools::GetString(document, "GameTarget", String::Empty); EditorTarget = JsonTools::GetString(document, "EditorTarget", String::Empty); EngineNickname = JsonTools::GetString(document, "EngineNickname", String::Empty); const auto referencesMember = document.FindMember("References"); if (referencesMember != document.MemberEnd()) { const auto& references = referencesMember->value.GetArray(); References.Resize(references.Size()); for (int32 i = 0; i < References.Count(); i++) { auto& reference = References[i]; auto& value = references[i]; reference.Name = JsonTools::GetString(value, "Name", String::Empty); String referencePath; if (reference.Name.StartsWith(TEXT("$(EnginePath)"))) { // Relative to engine root referencePath = Globals::StartupFolder / reference.Name.Substring(14); } else if (reference.Name.StartsWith(TEXT("$(ProjectPath)"))) { // Relative to project root referencePath = ProjectFolderPath / reference.Name.Substring(15); } else if (FileSystem::IsRelative(reference.Name)) { // Relative to workspace referencePath = Globals::StartupFolder / reference.Name; } else { // Absolute referencePath = reference.Name; } StringUtils::PathRemoveRelativeParts(referencePath); // Load referenced project reference.Project = Load(referencePath); if (reference.Project == nullptr) { LOG(Error, "Failed to load referenced project ({0}, from {1})", reference.Name, referencePath); return true; } } } DefaultScene = JsonTools::GetGuid(document, "DefaultScene"); DefaultSceneSpawn = JsonTools::GetRay(document, "DefaultSceneSpawn", Ray(Vector3::Zero, Vector3::Forward)); const auto minEngineVersionMember = document.FindMember("MinEngineVersion"); if (minEngineVersionMember != document.MemberEnd()) { auto& minEngineVersion = minEngineVersionMember->value; if (minEngineVersion.IsString()) { Version::Parse(minEngineVersion.GetText(), &MinEngineVersion); } else if (minEngineVersionMember->value.IsObject()) { MinEngineVersion = ::Version( JsonTools::GetInt(minEngineVersion, "Major", 0), JsonTools::GetInt(minEngineVersion, "Minor", 0), JsonTools::GetInt(minEngineVersion, "Build", 0)); } } // Validate properties if (Name.Length() == 0) { ShowProjectLoadError(TEXT("Missing project name."), projectPath); return true; } return false; } bool ProjectInfo::LoadOldProject(const String& projectPath) { // Open Xml file xml_document doc; const xml_parse_result result = doc.load_file((const PUGIXML_CHAR*)*projectPath); if (result.status) { ShowProjectLoadError(TEXT("Xml file parsing error."), projectPath); return true; } // Get root node const xml_node root = doc.child(PUGIXML_TEXT("Project")); if (!root) { ShowProjectLoadError(TEXT("Missing Project root node in xml file."), projectPath); return true; } // Load data Name = (const Char*)root.child_value(PUGIXML_TEXT("Name")); ProjectPath = projectPath; ProjectFolderPath = StringUtils::GetDirectoryName(projectPath); DefaultScene = Guid::Empty; const auto defaultScene = root.child_value(PUGIXML_TEXT("DefaultSceneId")); if (defaultScene) { Guid::Parse((const Char*)defaultScene, DefaultScene); } DefaultSceneSpawn.Position = GetVector3FromXml(root, PUGIXML_TEXT("DefaultSceneSpawnPos"), Vector3::Zero); DefaultSceneSpawn.Direction = Quaternion::Euler(GetVector3FromXml(root, PUGIXML_TEXT("DefaultSceneSpawnDir"), Vector3::Zero)) * Vector3::Forward; MinEngineVersion = ::Version(0, 0, GetIntFromXml(root, PUGIXML_TEXT("MinVersionSupported"), 0)); // Always reference engine project auto& flaxReference = References.AddOne(); flaxReference.Name = TEXT("$(EnginePath)/Flax.flaxproj"); flaxReference.Project = Load(Globals::StartupFolder / TEXT("Flax.flaxproj")); if (!flaxReference.Project) { ShowProjectLoadError(TEXT("Failed to load Flax Engine project."), projectPath); return true; } return false; } ProjectInfo* ProjectInfo::Load(const String& path) { // Try to reuse loaded file for (int32 i = 0; i < ProjectsCache.Count(); i++) { if (ProjectsCache[i]->ProjectPath == path) return ProjectsCache[i]; } // Load auto project = New(); if (project->LoadProject(path)) { Delete(project); return nullptr; } // Cache project ProjectsCache.Add(project); return project; }