From 441438b93e3925158da4c799be96c0d840fa6bb7 Mon Sep 17 00:00:00 2001 From: Teodor Vecerdi Date: Fri, 27 Nov 2020 16:57:41 +0200 Subject: [PATCH] Add backwards compatibility support (graph conversion) --- Editor/AssetCallbacks/CreateDlogGraph.cs | 1 + Editor/Graph/Data/DlogGraphData.cs | 4 +- Editor/Graph/Views/DlogEditorWindow.cs | 47 ++++++++++-- Editor/Util/DlogUtility.cs | 16 ++++- Editor/Util/Versioning/Conversion.meta | 3 + .../Conversion/ConvertMethodAttribute.cs | 16 +++++ .../Conversion/ConvertMethodAttribute.cs.meta | 3 + .../Versioning/Conversion/VersionConverter.cs | 72 +++++++++++++++++++ .../Conversion/VersionConverter.cs.meta | 3 + Editor/Util/Versioning/SemVer.cs | 18 +++-- 10 files changed, 169 insertions(+), 14 deletions(-) create mode 100644 Editor/Util/Versioning/Conversion.meta create mode 100644 Editor/Util/Versioning/Conversion/ConvertMethodAttribute.cs create mode 100644 Editor/Util/Versioning/Conversion/ConvertMethodAttribute.cs.meta create mode 100644 Editor/Util/Versioning/Conversion/VersionConverter.cs create mode 100644 Editor/Util/Versioning/Conversion/VersionConverter.cs.meta diff --git a/Editor/AssetCallbacks/CreateDlogGraph.cs b/Editor/AssetCallbacks/CreateDlogGraph.cs index 08beb82..1eb6bd6 100644 --- a/Editor/AssetCallbacks/CreateDlogGraph.cs +++ b/Editor/AssetCallbacks/CreateDlogGraph.cs @@ -16,6 +16,7 @@ public override void Action(int instanceId, string pathName, string resourceFile var dlogObject = CreateInstance(); dlogObject.Initialize(dlogGraph); dlogObject.DlogGraph.AssetGuid = AssetDatabase.GetAssetPath(instanceId); + dlogObject.DlogGraph.DialogueGraphVersion = DlogVersion.Version.GetValue(); dlogObject.AssetGuid = dlogObject.DlogGraph.AssetGuid; DlogUtility.CreateFile(pathName, dlogObject, false); AssetDatabase.ImportAsset(pathName); diff --git a/Editor/Graph/Data/DlogGraphData.cs b/Editor/Graph/Data/DlogGraphData.cs index 8acc975..2614846 100644 --- a/Editor/Graph/Data/DlogGraphData.cs +++ b/Editor/Graph/Data/DlogGraphData.cs @@ -46,7 +46,7 @@ public class DlogGraphData : ISerializationCallbackReceiver { [NonSerialized] private List edgeSelectionQueue = new List(); public List NodeSelectionQueue => nodeSelectionQueue; public List EdgeSelectionQueue => edgeSelectionQueue; - + public void OnBeforeSerialize() { if (Owner != null) IsBlackboardVisible = Owner.IsBlackboardVisible; @@ -55,8 +55,6 @@ public void OnBeforeSerialize() { foreach (var property in properties) { serializedProperties.Add(new SerializedProperty(property)); } - - DialogueGraphVersion = DlogVersion.Version.GetValue(); } public void OnAfterDeserialize() { diff --git a/Editor/Graph/Views/DlogEditorWindow.cs b/Editor/Graph/Views/DlogEditorWindow.cs index ab43ec8..15fc212 100644 --- a/Editor/Graph/Views/DlogEditorWindow.cs +++ b/Editor/Graph/Views/DlogEditorWindow.cs @@ -19,6 +19,7 @@ public class DlogEditorWindow : EditorWindow { private EditorView editorView; private bool deleted; + private bool skipOnDestroyCheck; public string SelectedAssetGuid { get => selectedAssetGuid; @@ -46,8 +47,9 @@ public void BuildWindow() { IsBlackboardVisible = dlogObject.IsBlackboardVisible }; rootVisualElement.Add(editorView); - - Refresh(); + if (VersionCheck()) { + Refresh(); + } } private void Update() { @@ -95,8 +97,7 @@ private void Update() { private void DisplayDeletedFromDiskDialog() { bool shouldClose = true; // Close unless if the same file was replaced - if (EditorUtility.DisplayDialog("Dialogue Graph Missing", AssetDatabase.GUIDToAssetPath(selectedAssetGuid) - + " has been deleted or moved outside of Unity.\n\nWould you like to save your Graph Asset?", "Save As", "Close Window")) { + if (EditorUtility.DisplayDialog("Dialogue Graph Missing", $"{AssetDatabase.GUIDToAssetPath(selectedAssetGuid)} has been deleted or moved outside of Unity.\n\nWould you like to save your Graph Asset?", "Save As", "Close Window")) { shouldClose = !SaveAs(); } @@ -129,14 +130,50 @@ private void OnEnable() { this.SetAntiAliasing(4); } + private bool VersionCheck() { + var fileVersion = (SemVer)dlogObject.DlogGraph.DialogueGraphVersion; + var comparison = fileVersion.CompareTo(DlogVersion.Version.GetValue()); + if (comparison < 0) { + if (EditorUtility.DisplayDialog("Version mismatch", $"The graph you are trying to load was saved with an older version of Dialogue Graph.\nIf you proceed with loading it will be converted to the current version. (A backup will be created)\n\nDo you wish to continue?", "Yes", "No")) { + var assetPath = AssetDatabase.GUIDToAssetPath(dlogObject.AssetGuid); + var assetNameSubEndIndex = assetPath.LastIndexOf('.'); + var backupAssetPath = assetPath.Substring(0, assetNameSubEndIndex); + DlogUtility.CreateFileNoUpdate($"{backupAssetPath}.backup_{fileVersion}.dlog", dlogObject); + DlogUtility.VersionConvert(fileVersion, dlogObject); + Refresh(); + } else { + skipOnDestroyCheck = true; + Close(); + } + return false; + } + + if (comparison > 0) { + if (EditorUtility.DisplayDialog("Version mismatch", $"The graph you are trying to load was saved with a newer version of Dialogue Graph.\nLoading the file might cause unexpected behaviour or errors. (A backup will be created)\n\nDo you wish to continue?", "Yes", "No")) { + var assetPath = AssetDatabase.GUIDToAssetPath(dlogObject.AssetGuid); + var assetNameSubEndIndex = assetPath.LastIndexOf('.'); + var backupAssetPath = assetPath.Substring(0, assetNameSubEndIndex); + DlogUtility.CreateFileNoUpdate($"{backupAssetPath}.backup_{fileVersion}.dlog", dlogObject); + Refresh(); + } else { + skipOnDestroyCheck = true; + Close(); + } + + return false; + } + return true; + } + private void OnDestroy() { - if (IsDirty && EditorUtility.DisplayDialog("Dlog Graph has been modified", "Do you want to save the changes you made in the Dialogue Graph?\nYour changes will be lost if you don't save them.", "Save", "Don't Save")) { + if (!skipOnDestroyCheck && IsDirty && EditorUtility.DisplayDialog("Dlog Graph has been modified", "Do you want to save the changes you made in the Dialogue Graph?\nYour changes will be lost if you don't save them.", "Save", "Don't Save")) { SaveAsset(); } } #region Window Events private void SaveAsset() { + dlogObject.DlogGraph.DialogueGraphVersion = DlogVersion.Version.GetValue(); DlogUtility.SaveGraph(dlogObject); UpdateTitle(); } diff --git a/Editor/Util/DlogUtility.cs b/Editor/Util/DlogUtility.cs index 1477d07..1028694 100644 --- a/Editor/Util/DlogUtility.cs +++ b/Editor/Util/DlogUtility.cs @@ -20,11 +20,14 @@ public static bool CreateFile(string path, DlogGraphObject dlogObject, bool refr var assetGuid = AssetDatabase.AssetPathToGUID(path); dlogObject.DlogGraph.AssetGuid = assetGuid; + CreateFileNoUpdate(path, dlogObject, refreshAsset); + return true; + } + + public static void CreateFileNoUpdate(string path, DlogGraphObject dlogObject, bool refreshAsset = true) { var jsonString = JsonUtility.ToJson(dlogObject.DlogGraph, true); File.WriteAllText(path, jsonString); if (refreshAsset) AssetDatabase.ImportAsset(path); - - return true; } public static bool SaveGraph(DlogGraphObject dlogObject, bool refreshAsset = true) { @@ -66,6 +69,15 @@ public static DlogGraphObject LoadGraphAtGuid(string assetGuid) { } #endregion + /// + /// Converts (back-ports or forward-ports) dlogObject from to the current version. + /// + /// Dlog object version + /// Dlog object to be converted + public static void VersionConvert(SemVer fromVersion, DlogGraphObject dlogObject) { + VersionConverter.ConvertVersion(fromVersion, DlogVersion.Version.GetValue(), dlogObject); + } + /** * Found this nifty method inside the codebase of ShaderGraph while reverse engineering some functionality. * I needed something like this so it didn't make sense to reinvent the wheel, so I took this and slightly modified it. diff --git a/Editor/Util/Versioning/Conversion.meta b/Editor/Util/Versioning/Conversion.meta new file mode 100644 index 0000000..ffbee2d --- /dev/null +++ b/Editor/Util/Versioning/Conversion.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c13a35f5c76d4a138eba82586a4166e9 +timeCreated: 1606485778 \ No newline at end of file diff --git a/Editor/Util/Versioning/Conversion/ConvertMethodAttribute.cs b/Editor/Util/Versioning/Conversion/ConvertMethodAttribute.cs new file mode 100644 index 0000000..f2d262a --- /dev/null +++ b/Editor/Util/Versioning/Conversion/ConvertMethodAttribute.cs @@ -0,0 +1,16 @@ +using System; + +namespace Dlog { + [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false), ] + public class ConvertMethodAttribute : Attribute { + public readonly SemVer TargetVersion; + + /// + /// Specifies that tha attached method is a converting method (from one version of Dialogue Graph to another) + /// + /// The target version the method converts to. + public ConvertMethodAttribute(string targetVersion) { + TargetVersion = (SemVer)targetVersion; + } + } +} \ No newline at end of file diff --git a/Editor/Util/Versioning/Conversion/ConvertMethodAttribute.cs.meta b/Editor/Util/Versioning/Conversion/ConvertMethodAttribute.cs.meta new file mode 100644 index 0000000..3c6ac18 --- /dev/null +++ b/Editor/Util/Versioning/Conversion/ConvertMethodAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f2622420eb2242bf9412f6b96cfeca0b +timeCreated: 1606485813 \ No newline at end of file diff --git a/Editor/Util/Versioning/Conversion/VersionConverter.cs b/Editor/Util/Versioning/Conversion/VersionConverter.cs new file mode 100644 index 0000000..9acf1ab --- /dev/null +++ b/Editor/Util/Versioning/Conversion/VersionConverter.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using UnityEngine; + +namespace Dlog { + public static class VersionConverter { + private static readonly SemVer v111 = (SemVer) "1.1.1"; + private static readonly SemVer v112 = (SemVer) "1.1.2"; + + private static SemVer[] sortedVersions = {v111, v112}; + + private static bool builtMethodCache; + private static Dictionary> upgradeMethodCache; + + private static SemVer GetNextVersion(SemVer from) { + for (var i = 1; i < sortedVersions.Length; i++) { + var comparePrev = from.CompareTo(sortedVersions[i - 1]); + var compareNext = from.CompareTo(sortedVersions[i]); + if (comparePrev >= 0 && compareNext < 0) return sortedVersions[i]; + } + + return SemVer.Invalid; + } + + public static void ConvertVersion(SemVer from, SemVer to, DlogGraphObject dlogObject) { + if (from == to) return; + var next = GetNextVersion(from); + if (next == SemVer.Invalid) { + Debug.Log($"Could not find upgrading method [{from} -> {to}]"); + return; + } + + UpgradeTo(next, dlogObject); + ConvertVersion(next, to, dlogObject); + } + + + [ConvertMethod("1.1.2")] + private static void U_112(DlogGraphObject dlogObject) { + dlogObject.DlogGraph.DialogueGraphVersion = v112; + } + + private static void UpgradeTo(SemVer version, DlogGraphObject dlogGraphObject) { + if (!builtMethodCache) { + BuildMethodCache(); + } + if(upgradeMethodCache.ContainsKey(version)) + upgradeMethodCache[version](dlogGraphObject); + else Debug.LogWarning($"Upgrade conversion with [target={version}] is not supported."); + } + + + private static void BuildMethodCache() { + Debug.Log("Building cache"); + builtMethodCache = true; + upgradeMethodCache = new Dictionary>(); + + var methods = typeof(VersionConverter).GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); + foreach (var method in methods) { + var attributes = method.GetCustomAttributes(false).ToList(); + if (attributes.Count <= 0) continue; + var attribute = attributes[0]; + + var methodCall = method.CreateDelegate(typeof(Action)) as Action; + upgradeMethodCache.Add(attribute.TargetVersion, methodCall); + } + } + } +} \ No newline at end of file diff --git a/Editor/Util/Versioning/Conversion/VersionConverter.cs.meta b/Editor/Util/Versioning/Conversion/VersionConverter.cs.meta new file mode 100644 index 0000000..45f617c --- /dev/null +++ b/Editor/Util/Versioning/Conversion/VersionConverter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1ef41f3bff6a4a5a83e68257e061444d +timeCreated: 1606481644 \ No newline at end of file diff --git a/Editor/Util/Versioning/SemVer.cs b/Editor/Util/Versioning/SemVer.cs index ccc368d..7f1e380 100644 --- a/Editor/Util/Versioning/SemVer.cs +++ b/Editor/Util/Versioning/SemVer.cs @@ -3,9 +3,9 @@ namespace Dlog { [Serializable] - public struct SemVer : IEquatable { - public static SemVer Invalid = new SemVer {MAJOR = -1, MINOR = -1, PATCH = -1}; - + public struct SemVer : IEquatable, IComparable { + public static readonly SemVer Invalid = new SemVer {MAJOR = -1, MINOR = -1, PATCH = -1}; + // ReSharper disable once InconsistentNaming public int MAJOR; @@ -15,6 +15,7 @@ public struct SemVer : IEquatable { // ReSharper disable once InconsistentNaming public int PATCH; + public SemVer(string versionString) { if (!IsValid(versionString, out var major, out var minor, out var patch)) { Debug.LogError($"Could not parse SemVer string {versionString} into format MAJOR.MINOR.PATCH."); @@ -52,7 +53,6 @@ public static explicit operator SemVer(string versionString) { return FromVersionString(versionString); } - public static SemVer FromVersionString(string versionString) { return new SemVer(versionString); } @@ -101,5 +101,15 @@ public override int GetHashCode() { public static bool operator !=(SemVer left, SemVer right) { return !left.Equals(right); } + + public int CompareTo(SemVer other) { + var majorComparison = MAJOR.CompareTo(other.MAJOR); + if (majorComparison != 0) + return majorComparison; + var minorComparison = MINOR.CompareTo(other.MINOR); + if (minorComparison != 0) + return minorComparison; + return PATCH.CompareTo(other.PATCH); + } } } \ No newline at end of file