// 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})";
}
}
}