diff --git a/src/PatchManager.Core/Assets/PatchingManager.cs b/src/PatchManager.Core/Assets/PatchingManager.cs index 4611551..b3178ae 100644 --- a/src/PatchManager.Core/Assets/PatchingManager.cs +++ b/src/PatchManager.Core/Assets/PatchingManager.cs @@ -226,17 +226,15 @@ private static AsyncOperationHandle> RebuildCache(string label) } }); - handle.Completed += results => + + void SaveArchive() { - if (results.Status != AsyncOperationStatus.Succeeded || unchanged) - { - return; - } var archive = CacheManager.CreateArchive(archiveFilename); foreach (var archiveFile in archiveFiles) { - archive.AddFile(archiveFile.Key,archiveFile.Value); + archive.AddFile(archiveFile.Key, archiveFile.Value); } + archive.Save(); CacheManager.CacheValidLabels.Add(label); @@ -244,8 +242,22 @@ private static AsyncOperationHandle> RebuildCache(string label) CacheManager.Inventory.CacheEntries.AddRangeUnique(assetsCacheEntries); CacheManager.SaveInventory(); - Addressables.Release(results); Console.WriteLine($"Cache for label '{label}' rebuilt."); + } + if (handle.Status == AsyncOperationStatus.Failed && !unchanged) + { + SaveArchive(); + } + + handle.Completed += results => + { + if (results.Status != AsyncOperationStatus.Succeeded || unchanged) + { + return; + } + + SaveArchive(); + Addressables.Release(results); }; return handle; diff --git a/src/PatchManager.Core/Cache/Archive.cs b/src/PatchManager.Core/Cache/Archive.cs index 10996bb..e926fe3 100644 --- a/src/PatchManager.Core/Cache/Archive.cs +++ b/src/PatchManager.Core/Cache/Archive.cs @@ -1,4 +1,5 @@ using System.IO.Compression; +using UnityEngine; namespace PatchManager.Core.Cache; @@ -96,7 +97,6 @@ public void AddFile(string filePath, string content) public void Save() { AssertNotDisposed(); - using var fileStream = new FileStream(_path, FileMode.Create); _stream.Seek(0, SeekOrigin.Begin); _stream.CopyTo(fileStream); diff --git a/src/PatchManager.Parts/Attributes/ModuleDataAdapterAttribute.cs b/src/PatchManager.Parts/Attributes/ModuleDataAdapterAttribute.cs new file mode 100644 index 0000000..9955605 --- /dev/null +++ b/src/PatchManager.Parts/Attributes/ModuleDataAdapterAttribute.cs @@ -0,0 +1,20 @@ +namespace PatchManager.Parts.Attributes; + +/// +/// Types that take this attribute must inherit from (ISelectable) and must have a constructor that takes the following arguments +/// JObject - ModuleData.DataObject +/// ModuleSelectable - Module +/// +[AttributeUsage(AttributeTargets.Class)] +public class ModuleDataAdapterAttribute : Attribute +{ + /// + /// The types this adapter is used for + /// + public readonly Type[] ValidTypes; + /// + /// Mark this class as a custom module data adapter + /// + /// What data types it adapts + public ModuleDataAdapterAttribute(params Type[] validTypes) => ValidTypes = validTypes; +} \ No newline at end of file diff --git a/src/PatchManager.Parts/PartsModule.cs b/src/PatchManager.Parts/PartsModule.cs index 1f8d933..ee3d4f3 100644 --- a/src/PatchManager.Parts/PartsModule.cs +++ b/src/PatchManager.Parts/PartsModule.cs @@ -9,4 +9,8 @@ namespace PatchManager.Parts; [UsedImplicitly] public class PartsModule : BaseModule { + public override void Preload() + { + PartsUtilities.GrabModuleDataAdapters(); + } } \ No newline at end of file diff --git a/src/PatchManager.Parts/PartsUtilities.cs b/src/PatchManager.Parts/PartsUtilities.cs index e8ab3e8..d64753d 100644 --- a/src/PatchManager.Parts/PartsUtilities.cs +++ b/src/PatchManager.Parts/PartsUtilities.cs @@ -1,5 +1,7 @@ using KSP.Sim.Definitions; using KSP.Sim.impl; +using PatchManager.Parts.Attributes; +using PatchManager.SassyPatching.Interfaces; namespace PatchManager.Parts; @@ -85,4 +87,31 @@ internal static IReadOnlyDictionary DataModules return _dataModules; } } + + internal static readonly Dictionary ModuleDataAdapters = new(); + + internal static void GrabModuleDataAdapters() + { + foreach (var type in AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(x => x.GetTypes()) + .Where(x => x.GetCustomAttributes(typeof(ModuleDataAdapterAttribute), + false) + .Any()) + .Select(x => (type: x, attr: (ModuleDataAdapterAttribute)x.GetCustomAttributes(typeof(ModuleDataAdapterAttribute), + false) + .FirstOrDefault()))) + { + foreach (var dataType in type.attr.ValidTypes) + { + ModuleDataAdapters[dataType] = type.type; + } + } + } + public static void RegisterModuleDataAdapter(params Type[] validTargets) where T : ISelectable + { + foreach (var type in validTargets) + { + ModuleDataAdapters[type] = typeof(T); + } + } } \ No newline at end of file diff --git a/src/PatchManager.Parts/Rulesets/PartsRuleset.cs b/src/PatchManager.Parts/Rulesets/PartsRuleset.cs index e30d1d6..a738426 100644 --- a/src/PatchManager.Parts/Rulesets/PartsRuleset.cs +++ b/src/PatchManager.Parts/Rulesets/PartsRuleset.cs @@ -1,7 +1,16 @@ -using PatchManager.Parts.Selectables; +using KSP.Game; +using KSP.IO; +using KSP.OAB; +using KSP.Sim; +using KSP.Sim.Definitions; +using KSP.Sim.ResourceSystem; +using Newtonsoft.Json.Linq; +using PatchManager.Parts.Selectables; using PatchManager.SassyPatching; using PatchManager.SassyPatching.Attributes; using PatchManager.SassyPatching.Interfaces; +using PatchManager.SassyPatching.NewAssets; +using UnityEngine; namespace PatchManager.Parts.Rulesets; @@ -25,7 +34,76 @@ public ISelectable ConvertToSelectable(string type, string name, string jsonData { return new PartSelectable(jsonData); } - /// - public INewAsset CreateNew(List dataValues) => throw new NotImplementedException(); + /// + /// Create a new part asset + /// + /// The arguments for the part asset, only one argument: name + /// The part asset creator + public INewAsset CreateNew(List dataValues) + { + var name = dataValues[0].String; + var internalPartData = new PartData + { + partName = name, + angularDrag = 0, + coMassOffset = Vector3.zero, + coPressureOffset = Vector3.zero, + mass = 0, + maximumDrag = 0, + maxTemp = 0, + author = "", + bodyLiftOnlyUnattachedLift = false, + bodyLiftOnlyAttachName = "", + buoyancy = 0, + buoyancyUseSine = false, + breakingForce = 0, + breakingTorque = 0, + category = PartCategories.none, + family = "", + coBuoyancy = Vector3.zero, + coDisplacement = Vector3.zero, + oabEditorCategory = OABEditorPartCategory.NONE, + partType = AssemblyPartTypeFilter.Rocket, + cost = 0, + crashTolerance = 0, + crewCapacity = 0, + emissiveConstant = 0, + explosionPotential = 0, + fuelCrossFeed = false, + heatConductivity = 0, + inverseStageCarryover = false, + isCompound = false, + maxLength = 0, + radiatorHeadroom = 0, + radiatorMax = 0, + physicsMode = PartPhysicsModes.None, + sizeCategory = MetaAssemblySizeFilterType.XS, + skinMassPerArea = 0, + skinMaxTemp = 0, + skinInternalConductionMult = 0, + stageOffset = 0, + stageType = AssemblyPartStageType.None, + tags = "", + stagingIconAssetAddress = "", + attachRules = AttachRules.Defaults(), + attachNodes = new List(), + resourceContainers = new List(), + resourceCosts = new List(), + serializedPartModules = new List(), + resourceSummary = new SerializedResourceInfo(), + PAMModuleSortOverride = new List(), + PAMModuleVisualsOverride = new List(), + AllowKinematicPhysicsIfIntersectTerrain = false + }; + var internalJson = IOProvider.ToJson(internalPartData); + var internalJObject = JObject.Parse(internalJson); + var externalJObject = new JObject + { + ["version"] = "0.3", + ["useExternalData"] = false, + ["data"] = internalJObject + }; + return new NewGenericAsset("parts_data", name, new PartSelectable(externalJObject.ToString())); + } } \ No newline at end of file diff --git a/src/PatchManager.Parts/Selectables/ModuleSelectable.cs b/src/PatchManager.Parts/Selectables/ModuleSelectable.cs index 97f3423..bc9d3f6 100644 --- a/src/PatchManager.Parts/Selectables/ModuleSelectable.cs +++ b/src/PatchManager.Parts/Selectables/ModuleSelectable.cs @@ -14,26 +14,39 @@ namespace PatchManager.Parts.Selectables; /// public sealed class ModuleSelectable : BaseSelectable { - private JToken _jToken; - private PartSelectable _selectable; + public readonly JToken SerializedData; + public readonly PartSelectable Selectable; /// public ModuleSelectable(JToken token, PartSelectable selectable) { - _jToken = token; - _selectable = selectable; + SerializedData = token; + Selectable = selectable; ElementType = ((string)token["Name"]).Replace("PartComponent", ""); Name = ElementType; Classes = new(); Children = new(); // Now we go down the list in the data type - var data = token["ModuleData"]; + var data = (JArray)token["ModuleData"]; foreach (var moduleData in data) { Classes.Add((string)moduleData["Name"]); // Where we are going to have to add children ree // TODO: Add a specialization for ModuleEngine - Children.Add(new JTokenSelectable(selectable.SetModified, moduleData["DataObject"], (string)moduleData["Name"])); + Children.Add(GetSelectable((JObject)moduleData)); + } + } + + private ISelectable GetSelectable(JObject moduleData) + { + var type = Type.GetType((string)moduleData["DataType"]); + if (type != null && PartsUtilities.ModuleDataAdapters.TryGetValue(type, out var adapterType)) + { + return (ISelectable)Activator.CreateInstance(type, moduleData, this); + } + else + { + return new JTokenSelectable(Selectable.SetModified, moduleData["DataObject"], (string)moduleData["Name"]); } } @@ -51,12 +64,12 @@ public ModuleSelectable(JToken token, PartSelectable selectable) /// public override bool IsSameAs(ISelectable other) => - other is ModuleSelectable moduleSelectable && moduleSelectable._jToken == _jToken; + other is ModuleSelectable moduleSelectable && moduleSelectable.SerializedData == SerializedData; /// public override IModifiable OpenModification() { - return new JTokenModifiable(_jToken, _selectable.SetModified); + return new JTokenModifiable(SerializedData, Selectable.SetModified); } /// @@ -66,7 +79,7 @@ public override ISelectable AddElement(string elementType) { throw new Exception($"Unknown data module {elementType}"); } - _selectable.SetModified(); + Selectable.SetModified(); var instance = (ModuleData)Activator.CreateInstance(dataModuleType); // var dataObject = JObject.Parse(IOProvider.ToJson(instance)); var dataObject = new JObject @@ -86,16 +99,15 @@ public override ISelectable AddElement(string elementType) ["Data"] = null, ["DataObject"] = dataObject }; - (_jToken["ModuleData"] as JArray)?.Add(trueType); + (SerializedData["ModuleData"] as JArray)?.Add(trueType); Classes.Add(dataModuleType.Name); - var selectable = new JTokenSelectable(_selectable.SetModified, trueType["DataObject"], dataModuleType.Name); + var selectable = GetSelectable(trueType); Children.Add(selectable); return selectable; } /// - public override string Serialize() => _jToken.ToString(); - + public override string Serialize() => SerializedData.ToString(); /// - public override DataValue GetValue() => DataValue.FromJToken(_jToken); + public override DataValue GetValue() => DataValue.FromJToken(SerializedData); } \ No newline at end of file diff --git a/src/PatchManager.SassyPatching/Selectables/JTokenSelectable.cs b/src/PatchManager.SassyPatching/Selectables/JTokenSelectable.cs index f10db2a..c7a073c 100644 --- a/src/PatchManager.SassyPatching/Selectables/JTokenSelectable.cs +++ b/src/PatchManager.SassyPatching/Selectables/JTokenSelectable.cs @@ -90,8 +90,21 @@ public override IModifiable OpenModification() /// public override ISelectable AddElement(string elementType) { - Token[elementType] = new JObject(); - return new JTokenSelectable(_markDirty, Token[elementType],elementType); + var obj = new JObject(); + if (Token is JArray jArray) + { + jArray[elementType] = obj; + } else if (Token is JObject jObject) + { + jObject[elementType] = obj; + } + else + { + throw new InvalidOperationException(); + } + var n = new JTokenSelectable(_markDirty, obj, elementType); + Children.Add(n); + return n; } ///