// MIT License - Copyright (c) 2025 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE #pragma warning disable CS0162 // Unreachable code detected namespace WallstopStudios.UnityHelpers.Editor.Settings { using System; using System.Collections.Generic; using System.Text; using UnityEditor; using UnityEngine; using Object = UnityEngine.Object; /// /// Centralizes palette serialization logging so drawers and settings UI can emit consistent diagnostics. /// internal static class PaletteSerializationDiagnostics { private const string LogPrefix = "[UnityHelpers][PaletteSerialization]"; private const string CommitLogPrefix = "[UnityHelpers][CommitEntry]"; #if UNITY_HELPERS_PALETTE_DIAGNOSTICS // Define UNITY_HELPERS_PALETTE_DIAGNOSTICS to re-enable verbose palette logging during investigations. private const bool DiagnosticsEnabled = true; #else private const bool DiagnosticsEnabled = false; #endif #if UNITY_HELPERS_COMMIT_DIAGNOSTICS // Define UNITY_HELPERS_COMMIT_DIAGNOSTICS to enable verbose logging for dictionary/set add operations. private const bool CommitDiagnosticsEnabled = true; #else private const bool CommitDiagnosticsEnabled = false; #endif /// /// Reports the start of a dictionary CommitEntry operation. /// internal static void ReportCommitEntryStart( SerializedObject serializedObject, string propertyPath, int keysArraySizeBefore, object keyValue, object valueValue, int existingIndex ) { if (!CommitDiagnosticsEnabled) { return; } bool isSettings = IsUnityHelpersSettings(serializedObject); string message = $"{CommitLogPrefix} START property={propertyPath} isSettings={isSettings} keysBefore={keysArraySizeBefore} existingIndex={existingIndex} key={keyValue ?? ""} value={valueValue ?? ""}"; Debug.Log(message, serializedObject?.targetObject); } /// /// Reports array insertion during CommitEntry. /// internal static void ReportCommitEntryArrayInsert( SerializedObject serializedObject, string propertyPath, int insertIndex, int keysArraySizeAfter, int valuesArraySizeAfter ) { if (!CommitDiagnosticsEnabled) { return; } string message = $"{CommitLogPrefix} ARRAY_INSERT property={propertyPath} insertIndex={insertIndex} keysAfter={keysArraySizeAfter} valuesAfter={valuesArraySizeAfter}"; Debug.Log(message, serializedObject?.targetObject); } /// /// Reports the ApplyModifiedProperties result during CommitEntry. /// internal static void ReportCommitEntryApplyResult( SerializedObject serializedObject, string propertyPath, bool applyResult, bool hadModifiedPropertiesBefore, bool hasModifiedPropertiesAfter ) { if (!CommitDiagnosticsEnabled) { return; } string message = $"{CommitLogPrefix} APPLY property={propertyPath} result={applyResult} dirtyBefore={hadModifiedPropertiesBefore} dirtyAfter={hasModifiedPropertiesAfter}"; Debug.Log(message, serializedObject?.targetObject); } /// /// Reports the SyncRuntimeDictionary operation. /// internal static void ReportSyncRuntimeDictionary( SerializedObject serializedObject, string propertyPath, object dictionaryInstance, bool isSerializableDictionaryBase, bool calledSaveSettings ) { if (!CommitDiagnosticsEnabled) { return; } string instanceType = dictionaryInstance?.GetType().Name ?? ""; string message = $"{CommitLogPrefix} SYNC_RUNTIME property={propertyPath} instanceType={instanceType} isSerializableDictionaryBase={isSerializableDictionaryBase} calledSaveSettings={calledSaveSettings}"; Debug.Log(message, serializedObject?.targetObject); } /// /// Reports the completion of CommitEntry. /// internal static void ReportCommitEntryComplete( SerializedObject serializedObject, string propertyPath, bool added, int affectedIndex, int finalKeysArraySize ) { if (!CommitDiagnosticsEnabled) { return; } string message = $"{CommitLogPrefix} COMPLETE property={propertyPath} added={added} index={affectedIndex} finalKeysSize={finalKeysArraySize}"; Debug.Log(message, serializedObject?.targetObject); } /// /// Reports the start of a set TryCommitPendingEntry operation. /// internal static void ReportSetCommitStart( SerializedObject serializedObject, string propertyPath, int itemsArraySizeBefore, object value ) { if (!CommitDiagnosticsEnabled) { return; } bool isSettings = IsUnityHelpersSettings(serializedObject); string message = $"{CommitLogPrefix} SET_START property={propertyPath} isSettings={isSettings} itemsBefore={itemsArraySizeBefore} value={value ?? ""}"; Debug.Log(message, serializedObject?.targetObject); } /// /// Reports set add operation result. /// internal static void ReportSetAddResult( SerializedObject serializedObject, string propertyPath, bool success, string errorMessage ) { if (!CommitDiagnosticsEnabled) { return; } string message = $"{CommitLogPrefix} SET_ADD property={propertyPath} success={success} error={errorMessage ?? ""}"; Debug.Log(message, serializedObject?.targetObject); } /// /// Reports the SyncRuntimeSet operation. /// internal static void ReportSyncRuntimeSet( SerializedObject serializedObject, string propertyPath, object setInstance, bool isSerializableSetInspector, bool calledSaveSettings ) { if (!CommitDiagnosticsEnabled) { return; } string instanceType = setInstance?.GetType().Name ?? ""; string message = $"{CommitLogPrefix} SET_SYNC property={propertyPath} instanceType={instanceType} isInspector={isSerializableSetInspector} calledSaveSettings={calledSaveSettings}"; Debug.Log(message, serializedObject?.targetObject); } /// /// Reports set commit completion. /// internal static void ReportSetCommitComplete( SerializedObject serializedObject, string propertyPath, int finalItemsArraySize ) { if (!CommitDiagnosticsEnabled) { return; } string message = $"{CommitLogPrefix} SET_COMPLETE property={propertyPath} finalItemsSize={finalItemsArraySize}"; Debug.Log(message, serializedObject?.targetObject); } private static bool IsUnityHelpersSettings(SerializedObject serializedObject) { if (serializedObject?.targetObject is UnityHelpersSettings) { return true; } Object[] targets = serializedObject?.targetObjects; if (targets == null) { return false; } for (int index = 0; index < targets.Length; index++) { if (targets[index] is UnityHelpersSettings) { return true; } } return false; } private static readonly string[] PalettePropertyRoots = { UnityHelpersSettings.SerializedPropertyNames.WButtonCustomColors, UnityHelpersSettings.SerializedPropertyNames.WEnumToggleButtonsCustomColors, }; internal enum DrawerApplyResult { UndoPathSucceeded, FallbackPathSucceeded, Failed, } internal static bool IsPaletteProperty(string propertyPath) { if (string.IsNullOrEmpty(propertyPath)) { return false; } for (int index = 0; index < PalettePropertyRoots.Length; index++) { if (propertyPath.StartsWith(PalettePropertyRoots[index], StringComparison.Ordinal)) { return true; } } return false; } internal static void ReportDrawerApplyResult( SerializedObject serializedObject, string propertyPath, string operation, DrawerApplyResult result, bool hadChangesBefore, bool hasChangesAfter ) { if ( !DiagnosticsEnabled || !ShouldLog(serializedObject, propertyPath, hadChangesBefore, result) ) { return; } StringBuilder builder = new(); builder .Append(LogPrefix) .Append(' ') .Append(operation ?? "DrawerApply") .Append(" property=") .Append(propertyPath ?? "") .Append(" result=") .Append(result) .Append(" hadChangesBefore=") .Append(hadChangesBefore) .Append(" remainingDirty=") .Append(hasChangesAfter) .Append(" targets=") .Append(serializedObject.targetObjects?.Length ?? 0); Object context = serializedObject.targetObject; if (result == DrawerApplyResult.Failed) { Debug.LogWarning(builder.ToString(), context); return; } if (result == DrawerApplyResult.FallbackPathSucceeded) { Debug.Log(builder.ToString(), context); return; } Debug.Log(builder.ToString(), context); } internal static void ReportInspectorApplyResult( SerializedObject serializedObject, bool paletteChanged, bool dataChanged, bool guiChanged, bool applyResult ) { if (!DiagnosticsEnabled || !paletteChanged || !ShouldLogForTargets(serializedObject)) { return; } bool stillDirty = serializedObject != null && serializedObject.hasModifiedProperties; string message = $"{LogPrefix} InspectorApply result={(applyResult ? "Success" : "Failed")} dataChanged={dataChanged} guiChanged={guiChanged} remainingDirty={stillDirty}"; Object context = serializedObject?.targetObject; if (applyResult) { Debug.Log(message, context); } else { Debug.LogWarning(message, context); } } internal static bool ShouldLogPaletteSort( SerializedProperty dictionaryProperty, Type keyType ) { if ( !DiagnosticsEnabled || dictionaryProperty == null || dictionaryProperty.serializedObject == null || keyType != typeof(string) ) { return false; } if (!IsPaletteProperty(dictionaryProperty.propertyPath)) { return false; } return ShouldLogForTargets(dictionaryProperty.serializedObject); } internal static void ReportDictionarySort( SerializedProperty dictionaryProperty, IReadOnlyList serializedBefore, IReadOnlyList snapshotAfterSort, IReadOnlyList serializedAfter ) { if ( !DiagnosticsEnabled || dictionaryProperty == null || !IsPaletteProperty(dictionaryProperty.propertyPath) ) { return; } SerializedObject serializedObject = dictionaryProperty.serializedObject; if (!ShouldLogForTargets(serializedObject)) { return; } string beforeSequence = FormatKeySequence(serializedBefore); string snapshotSequence = FormatKeySequence(snapshotAfterSort); string afterSequence = FormatKeySequence(serializedAfter); string message = $"{LogPrefix} Sort property={dictionaryProperty.propertyPath} before=[{beforeSequence}] snapshot=[{snapshotSequence}] after=[{afterSequence}]"; Debug.Log(message, serializedObject?.targetObject); } private static bool ShouldLog( SerializedObject serializedObject, string propertyPath, bool hadChangesBefore, DrawerApplyResult result ) { if ( !DiagnosticsEnabled || !ShouldLogForTargets(serializedObject) || !IsPaletteProperty(propertyPath) ) { return false; } if (!hadChangesBefore && result != DrawerApplyResult.Failed) { return false; } return true; } private static bool ShouldLogForTargets(SerializedObject serializedObject) { if (!DiagnosticsEnabled || serializedObject == null) { return false; } if (serializedObject.targetObject is UnityHelpersSettings) { return true; } Object[] targets = serializedObject.targetObjects; if (targets == null || targets.Length == 0) { return false; } for (int index = 0; index < targets.Length; index++) { if (targets[index] is UnityHelpersSettings) { return true; } } return false; } private static string FormatKeySequence(IReadOnlyList keys) { if (keys == null || keys.Count == 0) { return string.Empty; } StringBuilder builder = new(); for (int index = 0; index < keys.Count; index++) { if (index > 0) { builder.Append(", "); } builder.Append(keys[index] ?? ""); } return builder.ToString(); } } }