diff --git a/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.Edits.TextureShader.cs b/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.Edits.TextureShader.cs
index d4b190b2..97d96da3 100644
--- a/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.Edits.TextureShader.cs
+++ b/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.Edits.TextureShader.cs
@@ -62,13 +62,18 @@ internal Action QueueMaterialTextureFromFile(
// Track slot replacement without narrowing the caller's apply scope.
var location = FindGameObject(objectType, slot);
var target = new MaterialEditTarget(go, material, propertyName);
- return _textureImports.Enqueue(target,
- () => this != null && GetCoordinateIndex(objectType) == coordinate
+ var cancel = _textureImports.Enqueue(target,
+ () => this != null && isActiveAndEnabled && GetCoordinateIndex(objectType) == coordinate
&& FindGameObject(objectType, slot) == location
&& !CoordinateChanging && File.Exists(filePath),
done => { done(MaterialEditResult.FromApplied(
TrySetMaterialTextureFromFile(slot, objectType, material, propertyName, filePath, go))); return null; },
completed);
+ // A file-dialog callback can arrive after OnDisable stopped Update.
+ // Complete through the queue's normal cancellation path immediately.
+ if (!isActiveAndEnabled)
+ cancel?.Invoke();
+ return cancel;
}
private bool TrySetMaterialTextureFromFile(
diff --git a/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.Events.cs b/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.Events.cs
index 6274675f..8eb0f237 100644
--- a/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.Events.cs
+++ b/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.Events.cs
@@ -677,36 +677,31 @@ internal void HandleMaterialNameChange(int slot, ObjectType objectType, Renderer
}
}
+ private Coroutine _clothesMainTexRefresh;
+ private Coroutine _bodyMainTexRefresh;
+ private int _mainTexRefreshGeneration;
+
///
/// Refresh the clothes MainTex, typically called after editing colors in the character maker
///
- private EndOfFrameRefreshGate _clothesMainTexRefreshGate;
- private EndOfFrameRefreshGate _bodyMainTexRefreshGate;
-
public void RefreshClothesMainTex()
{
- if (!_clothesMainTexRefreshGate.TryRequest())
- {
+ if (!isActiveAndEnabled || _clothesMainTexRefresh != null)
return;
- }
- try
- {
- if (StartCoroutine(RefreshClothesMainTexCoroutine()) != null)
- return;
- }
- catch
- {
- _clothesMainTexRefreshGate.Complete();
- throw;
- }
- _clothesMainTexRefreshGate.Complete();
+
+ // A failed start leaves the handle null, so a later request can retry.
+ _clothesMainTexRefresh = StartCoroutine(
+ RefreshClothesMainTexCoroutine(_mainTexRefreshGeneration));
}
- private IEnumerator RefreshClothesMainTexCoroutine()
+ private IEnumerator RefreshClothesMainTexCoroutine(int generation)
{
- yield return new WaitForEndOfFrame();
try
{
+ yield return new WaitForEndOfFrame();
+ if (!isActiveAndEnabled || generation != _mainTexRefreshGeneration)
+ yield break;
+
for (var i = 0; i < MaterialTexturePropertyList.Count; i++)
{
var property = MaterialTexturePropertyList[i];
@@ -726,7 +721,8 @@ private IEnumerator RefreshClothesMainTexCoroutine()
}
finally
{
- _clothesMainTexRefreshGate.Complete();
+ if (generation == _mainTexRefreshGeneration)
+ _clothesMainTexRefresh = null;
}
}
@@ -778,28 +774,21 @@ private bool SetTextureWithProperty(GameObject go, MaterialTextureProperty textu
///
public void RefreshBodyMainTex()
{
- if (!_bodyMainTexRefreshGate.TryRequest())
- {
+ if (!isActiveAndEnabled || _bodyMainTexRefresh != null)
return;
- }
- try
- {
- if (StartCoroutine(RefreshBodyMainTexCoroutine()) != null)
- return;
- }
- catch
- {
- _bodyMainTexRefreshGate.Complete();
- throw;
- }
- _bodyMainTexRefreshGate.Complete();
+
+ _bodyMainTexRefresh = StartCoroutine(
+ RefreshBodyMainTexCoroutine(_mainTexRefreshGeneration));
}
- private IEnumerator RefreshBodyMainTexCoroutine()
+ private IEnumerator RefreshBodyMainTexCoroutine(int generation)
{
- yield return new WaitForEndOfFrame();
try
{
+ yield return new WaitForEndOfFrame();
+ if (!isActiveAndEnabled || generation != _mainTexRefreshGeneration)
+ yield break;
+
for (var i = 0; i < MaterialTexturePropertyList.Count; i++)
{
var property = MaterialTexturePropertyList[i];
@@ -812,9 +801,26 @@ private IEnumerator RefreshBodyMainTexCoroutine()
}
finally
{
- _bodyMainTexRefreshGate.Complete();
+ if (generation == _mainTexRefreshGeneration)
+ _bodyMainTexRefresh = null;
}
}
+
+ private void CancelMainTexRefreshes()
+ {
+ // Invalidate before stopping: an old iterator's finally must not
+ // clear a refresh requested after this character is enabled again.
+ _mainTexRefreshGeneration++;
+ var clothesRefresh = _clothesMainTexRefresh;
+ var bodyRefresh = _bodyMainTexRefresh;
+ _clothesMainTexRefresh = null;
+ _bodyMainTexRefresh = null;
+ if (clothesRefresh != null)
+ StopCoroutine(clothesRefresh);
+ if (bodyRefresh != null)
+ StopCoroutine(bodyRefresh);
+ }
+
///
/// Reapply all edits to the body and face
///
@@ -823,22 +829,5 @@ public void RefreshBodyEdits()
if (CharacterLoading) return;
StartCoroutine(LoadData(false, false, false));
}
- private struct EndOfFrameRefreshGate
- {
- private bool _pending;
-
- internal bool TryRequest()
- {
- if (_pending)
- return false;
- _pending = true;
- return true;
- }
-
- internal void Complete()
- {
- _pending = false;
- }
- }
}
}
diff --git a/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.cs b/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.cs
index fe26e17a..22d506c5 100644
--- a/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.cs
+++ b/src/MaterialEditor.Core/Core.MaterialEditor.CharaController.cs
@@ -160,6 +160,7 @@ protected override void OnReload(GameMode currentGameMode, bool maintainState)
///
protected override void OnDestroy()
{
+ CancelMainTexRefreshes();
_textureImports.Dispose();
ChaControl targetControl = null;
GameObject targetRoot = null;
@@ -192,6 +193,12 @@ protected override void OnDestroy()
}
}
+ private void OnDisable()
+ {
+ CancelMainTexRefreshes();
+ _textureImports.CancelAll();
+ }
+
private bool CurrentUiTargetBelongsToThisController()
{
var retainedTarget = MaterialEditorUI.RetainedTargetGameObject;