// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using System; using System.IO; using System.Collections.Generic; using FlaxEngine; using Newtonsoft.Json; namespace FlaxEditor { /// /// /// public class FlaxVersionConverter : JsonConverter { // Original implementation is based on Newtonsoft.Json VersionConverter /// /// Writes the JSON representation of the object. /// /// The to write to. /// The value. /// The calling serializer. public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value == null) { writer.WriteNull(); } else if (value is Version) { writer.WriteValue(value.ToString()); } else { throw new JsonSerializationException("Expected Version object value"); } } /// /// Reads the JSON representation of the object. /// /// The to read from. /// Type of the object. /// The existing property value of the JSON that is being converted. /// The calling serializer. /// The object value. public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) { return null; } else { if (reader.TokenType == JsonToken.StartObject) { try { reader.Read(); Dictionary values = new Dictionary(); while (reader.TokenType == JsonToken.PropertyName) { var key = reader.Value as string; reader.Read(); var val = (long)reader.Value; reader.Read(); values.Add(key, (int)val); } int major = 0, minor = 0, build = 0; values.TryGetValue("Major", out major); values.TryGetValue("Minor", out minor); values.TryGetValue("Build", out build); Version v = new Version(major, minor, build); return v; } catch (Exception ex) { throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex); } } else if (reader.TokenType == JsonToken.String) { try { Version v = new Version((string)reader.Value!); return v; } catch (Exception ex) { throw new Exception(String.Format("Error parsing version string: {0}", reader.Value), ex); } } else { throw new Exception(String.Format("Unexpected token or value when parsing version. Token: {0}, Value: {1}", reader.TokenType, reader.Value)); } } } /// /// Determines whether this instance can convert the specified object type. /// /// Type of the object. /// /// true if this instance can convert the specified object type; otherwise, false. /// public override bool CanConvert(Type objectType) { return objectType == typeof(Version); } } /// /// Contains information about Flax project. /// public sealed class ProjectInfo { private static List _projectsCache; /// /// The project reference. /// public class Reference { /// /// The referenced project name. /// public string Name; /// /// The referenced project. /// [NonSerialized] public ProjectInfo Project; /// public override string ToString() { return Name; } } /// /// The project name. /// public string Name; /// /// The project file path. /// [NonSerialized] public string ProjectPath; /// /// The project root folder path. /// [NonSerialized] public string ProjectFolderPath; /// /// The project version. /// public Version Version; /// /// The project publisher company. /// public string Company = string.Empty; /// /// The project copyright note. /// public string Copyright = string.Empty; /// /// The name of the build target to use for the game building (final, cooked game code). /// public string GameTarget; /// /// The name of the build target to use for the game in editor building (editor game code). /// public string EditorTarget; /// /// The project references. /// public Reference[] References = new Reference[0]; /// /// The default scene asset identifier to open on project startup. /// public string DefaultScene; /// /// The default scene spawn point (position and view direction). /// public Ray DefaultSceneSpawn; /// /// The minimum version supported by this project. /// public Version MinEngineVersion; /// /// The user-friendly nickname of the engine installation to use when opening the project. Can be used to open game project with a custom engine distributed for team members. This value must be the same in engine and game projects to be paired. /// public string EngineNickname; /// /// Gets all projects including this project, it's references and their references (any deep level of references). /// /// The collection of projects. public HashSet GetAllProjects() { var result = new HashSet(); GetAllProjects(result); return result; } private void GetAllProjects(HashSet result) { result.Add(this); foreach (var reference in References) reference.Project.GetAllProjects(result); } /// /// Saves the project file. /// public void Save() { var contents = FlaxEngine.Json.JsonSerializer.Serialize(this); File.WriteAllText(ProjectPath, contents); } /// /// Loads the project from the specified file. /// /// The path. /// The loaded project. public static ProjectInfo Load(string path) { // Try to reuse loaded file path = StringUtils.RemovePathRelativeParts(path); if (_projectsCache == null) _projectsCache = new List(); for (int i = 0; i < _projectsCache.Count; i++) { if (_projectsCache[i].ProjectPath == path) return _projectsCache[i]; } Profiler.BeginEvent(path); try { // Load var contents = File.ReadAllText(path); var project = JsonConvert.DeserializeObject(contents, new JsonSerializerSettings() { Converters = new[] { new FlaxVersionConverter() } }); project.ProjectPath = path; project.ProjectFolderPath = StringUtils.NormalizePath(Path.GetDirectoryName(path)); // Process project data if (string.IsNullOrEmpty(project.Name)) throw new Exception("Missing project name."); if (project.Version == null) project.Version = new Version(1, 0); if (project.Version.Revision == 0) project.Version = new Version(project.Version.Major, project.Version.Minor, project.Version.Build); if (project.Version.Build == 0 && project.Version.Revision == -1) project.Version = new Version(project.Version.Major, project.Version.Minor); foreach (var reference in project.References) { string referencePath; if (reference.Name.StartsWith("$(EnginePath)")) { // Relative to engine root referencePath = Path.Combine(Globals.StartupFolder, reference.Name.Substring(14)); } else if (reference.Name.StartsWith("$(ProjectPath)")) { // Relative to project root referencePath = Path.Combine(project.ProjectFolderPath, reference.Name.Substring(15)); } else if (Path.IsPathRooted(reference.Name)) { // Relative to workspace referencePath = Path.Combine(Environment.CurrentDirectory, reference.Name); } else { // Absolute referencePath = reference.Name; } // Load referenced project reference.Project = Load(referencePath); } // Project loaded Editor.Log($"Loaded project {project.Name}, version {project.Version}"); _projectsCache.Add(project); return project; } catch { // Failed to load project Editor.LogError("Failed to load project \"" + path + "\"."); throw; } finally { Profiler.EndEvent(); } } /// public override string ToString() { return $"{Name} ({ProjectPath})"; } } }