diff --git a/src/MaterialEditor.Base/MaterialEditor.Base.projitems b/src/MaterialEditor.Base/MaterialEditor.Base.projitems index 1c573edd..0fa00bc4 100644 --- a/src/MaterialEditor.Base/MaterialEditor.Base.projitems +++ b/src/MaterialEditor.Base/MaterialEditor.Base.projitems @@ -11,7 +11,6 @@ - diff --git a/src/MaterialEditor.Base/Operations/MaterialCubemapImportTransaction.cs b/src/MaterialEditor.Base/Operations/MaterialCubemapImportTransaction.cs deleted file mode 100644 index 3a3627ae..00000000 --- a/src/MaterialEditor.Base/Operations/MaterialCubemapImportTransaction.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEngine; - -namespace MaterialEditorAPI -{ - /// Controller-owned byte storage and leases; no serialization types cross this boundary. - internal sealed class MaterialCubemapImportStorage - { - internal Func Count; - internal Func StoreData; - internal Action StoreLease; - internal Action RemoveCreated; - internal Action PurgeLeases; - } - - internal sealed class MaterialCubemapRecordAccess - { - internal Func GetId; - internal Action SetId; - internal Func Original; - } - - /// One transaction for Chara and Studio; candidates stay detached until application succeeds. - internal static class MaterialCubemapImportTransaction - { - private static void Cleanup(Action action) - { - try { action(); } - catch (Exception ex) { MaterialEditorPluginBase.Logger?.LogWarning("Cubemap cleanup: " + ex.Message); } - } - - internal static MaterialEditResult Execute(byte[] data, MaterialEditorCubemapContentKey key, - GameObject root, string materialName, string property, bool logNormalization, - MaterialCubemapImportStorage storage, IList records, T existing, - Func create, MaterialCubemapRecordAccess access, Func apply) where T : class - { - if (data == null || root == null) - return new MaterialEditResult(MaterialEditStatus.Failed, "Validate"); - - MaterialEditorCubemapLease lease = null; - MaterialTextureSnapshot runtime = null; - T candidate = null; - var created = false; - var committed = false; - var texId = 0; - var stage = "Acquire"; - var previousId = existing == null ? null : access.GetId(existing); - var previousState = existing == null ? null : access.Original(existing).CaptureCheckpoint(); - try - { - string warning; - string error; - var acquired = key == null - ? MaterialEditorCubemapCache.TryAcquire(data, out lease, out warning, out error) - : MaterialEditorCubemapCache.TryAcquire(data, key, out lease, out warning, out error); - if (!acquired) return new MaterialEditResult(MaterialEditStatus.Failed, stage, error); - if (logNormalization && !string.IsNullOrEmpty(warning)) - MaterialEditorPluginBase.Logger?.LogWarning(warning); - stage = "Store"; - var count = storage.Count(); - texId = storage.StoreData(data); - created = storage.Count() > count; - storage.StoreLease(texId, lease); - lease = null; - - stage = "Snapshot"; - runtime = new MaterialTextureSnapshot(root, materialName, property); - candidate = create(texId); - // Checkpoints are copy-on-replace state; Synchronize builds new collections. - if (existing != null) access.Original(candidate).RestoreCheckpoint(previousState, false); - stage = "Apply"; - if (!apply(candidate)) - return new MaterialEditResult(MaterialEditStatus.Failed, stage, "Previous override preserved."); - stage = "Commit"; - if (existing == null) records.Add(candidate); - else - { - access.SetId(existing, texId); - access.Original(existing).RestoreCheckpoint(access.Original(candidate).CaptureCheckpoint(), false); - } - committed = true; - return new MaterialEditResult(MaterialEditStatus.Succeeded, stage); - } - catch (Exception ex) { return new MaterialEditResult(MaterialEditStatus.Failed, stage, ex.Message); } - finally - { - if (!committed) - { - runtime?.Restore(); - if (existing != null) - { - access.SetId(existing, previousId); - access.Original(existing).RestoreCheckpoint(previousState, true); - } - else if (candidate != null) records.Remove(candidate); - if (candidate != null) access.Original(candidate).Clear(); - if (created) Cleanup(() => storage.RemoveCreated(texId)); - } - if (lease != null) Cleanup(lease.Dispose); - Cleanup(storage.PurgeLeases); - } - } - } -} diff --git a/src/MaterialEditor.Base/Operations/MaterialTextureImportTransaction.cs b/src/MaterialEditor.Base/Operations/MaterialTextureImportTransaction.cs index 200838d4..43de0a01 100644 --- a/src/MaterialEditor.Base/Operations/MaterialTextureImportTransaction.cs +++ b/src/MaterialEditor.Base/Operations/MaterialTextureImportTransaction.cs @@ -42,34 +42,23 @@ internal static MaterialEditResult Execute(GameObject root, string materialNa } } - // Keep the existing DTO identity: animation bindings may still reference it. - internal static void Commit(IList records, IDictionary animations, - T existing, T candidate, Func getId, Action setId, - Func getAnimation, Action setAnimation) where T : class + // Move the binding before the caller assigns the two DTO fields. If the + // map update fails, the existing record and its binding remain unchanged. + internal static void CommitAnimationBinding( + IDictionary animations, T existing, T candidate) where T : class { - if (existing == null) - { - records.Add(candidate); - return; - } - var previousId = getId(existing); - var previousAnimation = getAnimation(existing); TController previousController; TController candidateController; var hadPrevious = animations.TryGetValue(existing, out previousController); var hasCandidate = animations.TryGetValue(candidate, out candidateController); try { - setId(existing, getId(candidate)); - setAnimation(existing, getAnimation(candidate)); animations.Remove(candidate); if (hasCandidate) animations[existing] = candidateController; else animations.Remove(existing); } catch { - setId(existing, previousId); - setAnimation(existing, previousAnimation); animations.Remove(candidate); if (hadPrevious) animations[existing] = previousController; else animations.Remove(existing); diff --git a/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.Edits.Cubemap.cs b/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.Edits.Cubemap.cs index 6ed28e29..21a62ada 100644 --- a/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.Edits.Cubemap.cs +++ b/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.Edits.Cubemap.cs @@ -82,38 +82,45 @@ private bool SetMaterialCubemap( var existing = MaterialCubemapPropertyList.FirstOrDefault(x => x.ID == id && x.Property == propertyName && x.MaterialName == material.NameFormatted()); var result = MaterialCubemapImportTransaction.Execute(data, contentKey, go, material.NameFormatted(), propertyName, logNormalizationWarning, - new MaterialCubemapImportStorage - { - Count = () => TextureDictionary.Count, - StoreData = SetAndGetTextureID, - StoreLease = CubemapLeases.Store, - RemoveCreated = RemoveFailedCubemapData, - PurgeLeases = PurgeUnusedCubemapLeases - }, MaterialCubemapPropertyList, existing, - texId => new MaterialCubemapProperty(id, material.NameFormatted(), propertyName, texId), - CubemapRecordAccess, candidate => SetCubemapWithProperty(go, candidate)); + TextureDictionary, CubemapLeases, ApplyAndCommit, PurgeUnusedCubemapLeases); if (!result.Succeeded) MaterialEditorPluginBase.Logger?.LogWarning("Cubemap import: " + result.Stage + ": " + result.Diagnostic); return result.Succeeded; - } - private static readonly MaterialCubemapRecordAccess CubemapRecordAccess = - new MaterialCubemapRecordAccess + bool ApplyAndCommit(int texId) { - GetId = x => x.TexID, - SetId = (x, id) => x.TexID = id, - Original = x => x.CubemapOriginalState - }; - - private void RemoveFailedCubemapData(int texId) - { - CubemapLeases.Release(texId); - TextureContainer container; - if (!TextureDictionary.TryGetValue(texId, out container)) return; - try { container?.Dispose(); } - finally { TextureDictionary.Remove(texId); } + var candidate = new MaterialCubemapProperty(id, material.NameFormatted(), propertyName, texId); + var committed = false; + try + { + if (existing != null) + candidate.CubemapOriginalState.RestoreCheckpoint( + existing.CubemapOriginalState.CaptureCheckpoint(), false); + if (!SetCubemapWithProperty(go, candidate)) + return false; + + if (existing == null) + MaterialCubemapPropertyList.Add(candidate); + else + { + // Finish all potentially throwing preparation before updating the + // existing record. Checkpoint restoration only assigns state fields. + var state = candidate.CubemapOriginalState.CaptureCheckpoint(); + existing.CubemapOriginalState.RestoreCheckpoint(state, false); + existing.TexID = texId; + } + committed = true; + return true; + } + finally + { + if (!committed) + candidate.CubemapOriginalState.Clear(); + } + } } + private bool SetCubemapWithProperty( GameObject gameObject, MaterialCubemapProperty cubemapProperty) diff --git a/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.Edits.TextureShader.cs b/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.Edits.TextureShader.cs index d467e3c0..4cd42fb0 100644 --- a/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.Edits.TextureShader.cs +++ b/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.Edits.TextureShader.cs @@ -136,18 +136,18 @@ private bool TrySetMaterialTexture(int id, Material material, string propertyNam var result = MaterialTextureImportTransaction.Execute( go, materialName, propertyName, () => { - var texID = SetAndGetTextureID(data); - var animationDefinition = MEAnimationUtil.LoadAnimationDefFromBytes(texID, data, SetAndGetTextureID); - return new MaterialTextureProperty( - id, - materialName, - propertyName, - texID, - existingProperty == null ? null : existingProperty.Offset, - existingProperty == null ? null : existingProperty.OffsetOriginal, - existingProperty == null ? null : existingProperty.Scale, - existingProperty == null ? null : existingProperty.ScaleOriginal, - animationDefinition); + var texID = SetAndGetTextureID(data); + var animationDefinition = MEAnimationUtil.LoadAnimationDefFromBytes(texID, data, SetAndGetTextureID); + return new MaterialTextureProperty( + id, + materialName, + propertyName, + texID, + existingProperty == null ? null : existingProperty.Offset, + existingProperty == null ? null : existingProperty.OffsetOriginal, + existingProperty == null ? null : existingProperty.Scale, + existingProperty == null ? null : existingProperty.ScaleOriginal, + animationDefinition); }, candidate => SetTextureWithProperty(go, candidate), candidate => CommitTextureImport(existingProperty, candidate), @@ -161,10 +161,20 @@ private bool TrySetMaterialTexture(int id, Material material, string propertyNam return result.Succeeded; } - private void CommitTextureImport(MaterialTextureProperty existingProperty, MaterialTextureProperty candidateProperty) => - MaterialTextureImportTransaction.Commit(MaterialTexturePropertyList, AnimationControllerMap, - existingProperty, candidateProperty, x => x.TexID, (x, id) => x.TexID = id, - x => x.TexAnimationDef, (x, animation) => x.TexAnimationDef = animation); + private void CommitTextureImport(MaterialTextureProperty existingProperty, MaterialTextureProperty candidateProperty) + { + if (existingProperty == null) + { + MaterialTexturePropertyList.Add(candidateProperty); + return; + } + + MaterialTextureImportTransaction.CommitAnimationBinding( + AnimationControllerMap, existingProperty, candidateProperty); + // These field assignments cannot fail; retain the existing record identity. + existingProperty.TexID = candidateProperty.TexID; + existingProperty.TexAnimationDef = candidateProperty.TexAnimationDef; + } /// /// Get the saved material property value or null if none is saved diff --git a/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.Persistence.Properties.cs b/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.Persistence.Properties.cs index a6ff57af..fc57be58 100644 --- a/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.Persistence.Properties.cs +++ b/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.Persistence.Properties.cs @@ -147,5 +147,63 @@ private void LoadSceneMaterialVectorPropertyList(SceneLoadContext context) }); } + private void LoadSceneMaterialTexturePropertyList(SceneLoadContext context) + { + context.Data.Read(nameof(MaterialTexturePropertyList), loadedProperty => + { + GameObject go = ExtractGameObject(context.Items, loadedProperty.ID, out var objID); + if (go != null) + { + int? texID = null; + if (context.Operation == SceneOperationKind.Import) + { + if (loadedProperty.TexID != null) + texID = context.Data.RemapTexture(loadedProperty.TexID); + MEAnimationUtil.RemapTexID(loadedProperty.TexAnimationDef, context.Data.TextureIds); + } + else + texID = loadedProperty.TexID; + + MaterialTextureProperty newTextureProperty = new MaterialTextureProperty(objID, loadedProperty.MaterialName, loadedProperty.Property, texID, loadedProperty.Offset, loadedProperty.OffsetOriginal, loadedProperty.Scale, loadedProperty.ScaleOriginal, loadedProperty.TexAnimationDef); + + bool setTex = false; + if (newTextureProperty.TexID != null) + setTex = SetTextureWithProperty(go, newTextureProperty); + + bool setOffset = SetTextureOffset(go, newTextureProperty.MaterialName, newTextureProperty.Property, newTextureProperty.Offset); + bool setScale = SetTextureScale(go, newTextureProperty.MaterialName, newTextureProperty.Property, newTextureProperty.Scale); + + if (setTex || setOffset || setScale) + MaterialTexturePropertyList.Add(newTextureProperty); + } + }); + } + + private void LoadSceneMaterialCubemapPropertyList(SceneLoadContext context) + { + context.Data.Read(nameof(MaterialCubemapPropertyList), loadedProperty => + { + GameObject go = ExtractGameObject( + context.Items, + loadedProperty.ID, + out var objID); + if (go == null) + return; + + int? texID = loadedProperty.TexID; + if (context.Operation == SceneOperationKind.Import + && loadedProperty.TexID.HasValue) + texID = context.Data.RemapTexture(loadedProperty.TexID); + + var newCubemapProperty = new MaterialCubemapProperty( + objID, + loadedProperty.MaterialName, + loadedProperty.Property, + texID); + if (newCubemapProperty.TexID.HasValue + && SetCubemapWithProperty(go, newCubemapProperty)) + MaterialCubemapPropertyList.Add(newCubemapProperty); + }); + } } } diff --git a/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.Persistence.Textures.cs b/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.Persistence.Textures.cs deleted file mode 100644 index 8f586f1f..00000000 --- a/src/MaterialEditor.Core.Studio/Core.MaterialEditor.SceneController.Persistence.Textures.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Collections.Generic; -using KKAPI.Utilities; -using System.Linq; -using KKAPI.Studio.SaveLoad; -using MaterialEditorAPI; -using Studio; -using UnityEngine; -using static MaterialEditorAPI.MaterialAPI; - -namespace KK_Plugins.MaterialEditor -{ - public partial class SceneController - { - private void LoadSceneMaterialTexturePropertyList(SceneLoadContext context) - { - context.Data.Read(nameof(MaterialTexturePropertyList), loadedProperty => - { - GameObject go = ExtractGameObject(context.Items, loadedProperty.ID, out var objID); - if (go != null) - { - int? texID = null; - if (context.Operation == SceneOperationKind.Import) - { - if (loadedProperty.TexID != null) - texID = context.Data.RemapTexture(loadedProperty.TexID); - MEAnimationUtil.RemapTexID(loadedProperty.TexAnimationDef, context.Data.TextureIds); - } - else - texID = loadedProperty.TexID; - - MaterialTextureProperty newTextureProperty = new MaterialTextureProperty(objID, loadedProperty.MaterialName, loadedProperty.Property, texID, loadedProperty.Offset, loadedProperty.OffsetOriginal, loadedProperty.Scale, loadedProperty.ScaleOriginal, loadedProperty.TexAnimationDef); - - bool setTex = false; - if (newTextureProperty.TexID != null) - setTex = SetTextureWithProperty(go, newTextureProperty); - - bool setOffset = SetTextureOffset(go, newTextureProperty.MaterialName, newTextureProperty.Property, newTextureProperty.Offset); - bool setScale = SetTextureScale(go, newTextureProperty.MaterialName, newTextureProperty.Property, newTextureProperty.Scale); - - if (setTex || setOffset || setScale) - MaterialTexturePropertyList.Add(newTextureProperty); - } - }); - } - - private void LoadSceneMaterialCubemapPropertyList(SceneLoadContext context) - { - context.Data.Read(nameof(MaterialCubemapPropertyList), loadedProperty => - { - GameObject go = ExtractGameObject( - context.Items, - loadedProperty.ID, - out var objID); - if (go == null) - return; - - int? texID = loadedProperty.TexID; - if (context.Operation == SceneOperationKind.Import - && loadedProperty.TexID.HasValue) - texID = context.Data.RemapTexture(loadedProperty.TexID); - - var newCubemapProperty = new MaterialCubemapProperty( - objID, - loadedProperty.MaterialName, - loadedProperty.Property, - texID); - if (newCubemapProperty.TexID.HasValue - && SetCubemapWithProperty(go, newCubemapProperty)) - MaterialCubemapPropertyList.Add(newCubemapProperty); - }); - } - - } -} diff --git a/src/MaterialEditor.Core.Studio/Core.MaterialEditor.Studio.projitems b/src/MaterialEditor.Core.Studio/Core.MaterialEditor.Studio.projitems index c6af0795..f7f03f5f 100644 --- a/src/MaterialEditor.Core.Studio/Core.MaterialEditor.Studio.projitems +++ b/src/MaterialEditor.Core.Studio/Core.MaterialEditor.Studio.projitems @@ -11,7 +11,6 @@ - diff --git a/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.Edits.Cubemap.cs b/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.Edits.Cubemap.cs index 808986b8..65719abc 100644 --- a/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.Edits.Cubemap.cs +++ b/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.Edits.Cubemap.cs @@ -105,36 +105,42 @@ private bool SetMaterialCubemap( var existing = FindMaterialCubemapProperty(slot, objectType, material, propertyName); var result = MaterialCubemapImportTransaction.Execute(data, contentKey, go, material.NameFormatted(), propertyName, logNormalizationWarning, - new MaterialCubemapImportStorage - { - Count = () => TextureDictionary.Count, - StoreData = SetAndGetTextureID, - StoreLease = CubemapLeases.Store, - RemoveCreated = RemoveFailedCubemapData, - PurgeLeases = PurgeUnusedCubemapLeases - }, MaterialCubemapPropertyList, existing, - texId => new MaterialCubemapProperty(objectType, GetCoordinateIndex(objectType), slot, material.NameFormatted(), propertyName, texId), - CubemapRecordAccess, candidate => SetCubemapWithProperty(go, candidate)); + TextureDictionary, CubemapLeases, ApplyAndCommit, PurgeUnusedCubemapLeases); if (!result.Succeeded) MaterialEditorPluginBase.Logger?.LogWarning("Cubemap import: " + result.Stage + ": " + result.Diagnostic); return result.Succeeded; - } - private static readonly MaterialCubemapRecordAccess CubemapRecordAccess = - new MaterialCubemapRecordAccess + bool ApplyAndCommit(int texId) { - GetId = x => x.TexID, - SetId = (x, id) => x.TexID = id, - Original = x => x.CubemapOriginalState - }; + var candidate = new MaterialCubemapProperty(objectType, GetCoordinateIndex(objectType), slot, material.NameFormatted(), propertyName, texId); + var committed = false; + try + { + if (existing != null) + candidate.CubemapOriginalState.RestoreCheckpoint( + existing.CubemapOriginalState.CaptureCheckpoint(), false); + if (!SetCubemapWithProperty(go, candidate)) + return false; - private void RemoveFailedCubemapData(int texId) - { - CubemapLeases.Release(texId); - TextureContainer container; - if (!TextureDictionary.TryGetValue(texId, out container)) return; - try { container?.Dispose(); } - finally { TextureDictionary.Remove(texId); } + if (existing == null) + MaterialCubemapPropertyList.Add(candidate); + else + { + // Finish all potentially throwing preparation before updating the + // existing record. Checkpoint restoration only assigns state fields. + var state = candidate.CubemapOriginalState.CaptureCheckpoint(); + existing.CubemapOriginalState.RestoreCheckpoint(state, false); + existing.TexID = texId; + } + committed = true; + return true; + } + finally + { + if (!committed) + candidate.CubemapOriginalState.Clear(); + } + } } diff --git a/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.Edits.TextureShader.cs b/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.Edits.TextureShader.cs index e208e04c..d4b190b2 100644 --- a/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.Edits.TextureShader.cs +++ b/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.Edits.TextureShader.cs @@ -117,20 +117,20 @@ private bool TrySetMaterialTexture(int slot, ObjectType objectType, Material mat var result = MaterialTextureImportTransaction.Execute( go, materialName, propertyName, () => { - var texID = SetAndGetTextureID(data); - var animationDefinition = MEAnimationUtil.LoadAnimationDefFromBytes(texID, data, SetAndGetTextureID); - return new MaterialTextureProperty( - objectType, - coordinateIndex, - slot, - materialName, - propertyName, - texID, - existingProperty == null ? null : existingProperty.Offset, - existingProperty == null ? null : existingProperty.OffsetOriginal, - existingProperty == null ? null : existingProperty.Scale, - existingProperty == null ? null : existingProperty.ScaleOriginal, - animationDefinition); + var texID = SetAndGetTextureID(data); + var animationDefinition = MEAnimationUtil.LoadAnimationDefFromBytes(texID, data, SetAndGetTextureID); + return new MaterialTextureProperty( + objectType, + coordinateIndex, + slot, + materialName, + propertyName, + texID, + existingProperty == null ? null : existingProperty.Offset, + existingProperty == null ? null : existingProperty.OffsetOriginal, + existingProperty == null ? null : existingProperty.Scale, + existingProperty == null ? null : existingProperty.ScaleOriginal, + animationDefinition); }, candidate => SetTextureWithProperty(go, candidate), candidate => CommitTextureImport(existingProperty, candidate), @@ -144,10 +144,20 @@ private bool TrySetMaterialTexture(int slot, ObjectType objectType, Material mat return result.Succeeded; } - private void CommitTextureImport(MaterialTextureProperty existingProperty, MaterialTextureProperty candidateProperty) => - MaterialTextureImportTransaction.Commit(MaterialTexturePropertyList, AnimationControllerMap, - existingProperty, candidateProperty, x => x.TexID, (x, id) => x.TexID = id, - x => x.TexAnimationDef, (x, animation) => x.TexAnimationDef = animation); + private void CommitTextureImport(MaterialTextureProperty existingProperty, MaterialTextureProperty candidateProperty) + { + if (existingProperty == null) + { + MaterialTexturePropertyList.Add(candidateProperty); + return; + } + + MaterialTextureImportTransaction.CommitAnimationBinding( + AnimationControllerMap, existingProperty, candidateProperty); + // These field assignments cannot fail; retain the existing record identity. + existingProperty.TexID = candidateProperty.TexID; + existingProperty.TexAnimationDef = candidateProperty.TexAnimationDef; + } /// /// Get the saved material property value or null if none is saved diff --git a/src/MaterialEditor.Core/Core.MaterialEditor.projitems b/src/MaterialEditor.Core/Core.MaterialEditor.projitems index 8a5b0f47..08d346c7 100644 --- a/src/MaterialEditor.Core/Core.MaterialEditor.projitems +++ b/src/MaterialEditor.Core/Core.MaterialEditor.projitems @@ -9,6 +9,7 @@ Core.MaterialEditor + diff --git a/src/MaterialEditor.Core/Persistence/MaterialCubemapImportTransaction.cs b/src/MaterialEditor.Core/Persistence/MaterialCubemapImportTransaction.cs new file mode 100644 index 00000000..ea8c8ce5 --- /dev/null +++ b/src/MaterialEditor.Core/Persistence/MaterialCubemapImportTransaction.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using MaterialEditorAPI; +using UnityEngine; + +namespace KK_Plugins.MaterialEditor +{ + /// Shared resource transaction; controller-specific records stay with their controller. + internal static class MaterialCubemapImportTransaction + { + internal static MaterialEditResult Execute(byte[] data, MaterialEditorCubemapContentKey key, + GameObject root, string materialName, string property, bool logNormalization, + Dictionary textures, MaterialEditorCubemapLeaseStore leases, + Func applyAndCommit, Action purgeLeases) + { + if (data == null || root == null) + return new MaterialEditResult(MaterialEditStatus.Failed, "Validate"); + + MaterialEditorCubemapLease lease = null; + MaterialTextureSnapshot runtime = null; + var created = false; + var committed = false; + var texId = 0; + var stage = "Acquire"; + try + { + string warning; + string error; + var acquired = key == null + ? MaterialEditorCubemapCache.TryAcquire(data, out lease, out warning, out error) + : MaterialEditorCubemapCache.TryAcquire(data, key, out lease, out warning, out error); + if (!acquired) return new MaterialEditResult(MaterialEditStatus.Failed, stage, error); + if (logNormalization && !string.IsNullOrEmpty(warning)) + MaterialEditorPluginBase.Logger?.LogWarning(warning); + + stage = "Store"; + var count = textures.Count; + texId = TextureSaveHandler.GetOrAddTexture(textures, data); + created = textures.Count > count; + leases.Store(texId, lease); + lease = null; + + stage = "Snapshot"; + runtime = new MaterialTextureSnapshot(root, materialName, property); + stage = "Apply/commit"; + // The callback leaves the existing record unchanged on failure and + // publishes the candidate only after successful runtime application. + if (!applyAndCommit(texId)) + return new MaterialEditResult(MaterialEditStatus.Failed, stage, "Previous override preserved."); + committed = true; + return new MaterialEditResult(MaterialEditStatus.Succeeded, stage); + } + catch (Exception ex) { return new MaterialEditResult(MaterialEditStatus.Failed, stage, ex.Message); } + finally + { + if (!committed) + { + runtime?.Restore(); + if (created) Cleanup(() => + { + leases.Release(texId); + TextureContainer container; + if (!textures.TryGetValue(texId, out container)) return; + try { container?.Dispose(); } + finally { textures.Remove(texId); } + }); + } + if (lease != null) Cleanup(lease.Dispose); + Cleanup(purgeLeases); + } + } + + private static void Cleanup(Action action) + { + try { action(); } + catch (Exception ex) { MaterialEditorPluginBase.Logger?.LogWarning("Cubemap cleanup: " + ex.Message); } + } + } +}