using System; using System.Collections.Generic; using System.Text; using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.AddressableAssets.ResourceLocators; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceLocations; using UnityEngine.ResourceManagement.ResourceProviders; namespace BrewMonster.Scripts { /// /// Runtime diagnostics for Addressables labels (e.g. RemoteContent) — log with [Cuong] prefix. /// public static class AddressablesLabelDebug { const string LogPrefix = "[Cuong] AddressablesLabelDebug:"; /// /// After , logs locators, label lookup (exact + case variants), /// and sample remote InternalIds. Labels are case-sensitive in Addressables. /// public static async UniTask LogLabelDiagnosticsAsync(string primaryLabel, int maxSampleLocations = 5) { if (string.IsNullOrWhiteSpace(primaryLabel)) { Debug.LogWarning($"{LogPrefix} primaryLabel is empty — skip."); return; } var label = primaryLabel.Trim(); var sb = new StringBuilder(2048); sb.AppendLine($"{LogPrefix} === Label diagnostics (runtime catalog) ==="); sb.AppendLine($"{LogPrefix} Primary label (exact, case-sensitive): \"{label}\""); int locatorCount = 0; if (Addressables.ResourceLocators != null) { foreach (IResourceLocator locator in Addressables.ResourceLocators) { locatorCount++; var id = locator?.LocatorId ?? "(null)"; var keyCount = 0; if (locator?.Keys != null) { foreach (var _ in locator.Keys) keyCount++; } sb.AppendLine($"{LogPrefix} Locator #{locatorCount}: id={id}, keys~={keyCount}"); } } if (locatorCount == 0) sb.AppendLine($"{LogPrefix} WARNING: No ResourceLocators — catalog chưa load hoặc init thất bại."); await LogLabelLookupAsync(sb, label, maxSampleLocations); var wrongCase = char.IsUpper(label[0]) ? char.ToLowerInvariant(label[0]) + label.Substring(1) : char.ToUpperInvariant(label[0]) + label.Substring(1); if (!string.Equals(wrongCase, label, StringComparison.Ordinal)) { sb.AppendLine($"{LogPrefix} --- Case mismatch test (should be 0 locations if case matters) ---"); await LogLabelLookupAsync(sb, wrongCase, 2, suffix: " [wrong case]"); } await LogAllCatalogLabelsAsync(sb, label); sb.AppendLine($"{LogPrefix} Ghi chú: Label trong Addressables Groups ≠ file trên CDN. " + "CDN có catalog_*.json + bundle; label nằm TRONG catalog JSON sau khi build. " + "Group Include In Build = false → entry chỉ có sau khi remote catalog tải thành công."); Debug.Log(sb.ToString()); } static async UniTask LogLabelLookupAsync( StringBuilder sb, string label, int maxSampleLocations, string suffix = "") { AsyncOperationHandle> handle = default; try { handle = Addressables.LoadResourceLocationsAsync(label, typeof(UnityEngine.Object)); await handle.ToUniTask(); if (handle.Status != AsyncOperationStatus.Succeeded) { sb.AppendLine( $"{LogPrefix} LoadResourceLocationsAsync(\"{label}\"){suffix} FAILED: {handle.OperationException?.Message}"); return; } var locations = handle.Result; int count = locations?.Count ?? 0; sb.AppendLine($"{LogPrefix} Label \"{label}\"{suffix} → {count} location(s)."); if (count == 0) return; int remoteCount = 0; int sample = Mathf.Min(maxSampleLocations, count); for (int i = 0; i < count; i++) { var loc = locations[i]; if (loc == null) continue; bool isRemote = IsRemoteLocation(loc); if (isRemote) remoteCount++; if (i < sample) { sb.AppendLine( $"{LogPrefix} [{i}] remote={isRemote} provider={loc.ProviderId} id={Truncate(loc.InternalId, 120)}"); } } sb.AppendLine($"{LogPrefix} … remote locations: {remoteCount}/{count} (rest omitted)."); } catch (Exception e) { sb.AppendLine($"{LogPrefix} Label \"{label}\"{suffix} exception: {e.GetType().Name}: {e.Message}"); } finally { if (handle.IsValid()) Addressables.Release(handle); } } static async UniTask LogAllCatalogLabelsAsync(StringBuilder sb, string highlightLabel) { AsyncOperationHandle> handle = default; try { handle = Addressables.GetLabels(); await handle.ToUniTask(); if (handle.Status != AsyncOperationStatus.Succeeded) { sb.AppendLine($"{LogPrefix} GetLabels() failed: {handle.OperationException?.Message}"); return; } var labels = handle.Result; int n = labels?.Count ?? 0; sb.AppendLine($"{LogPrefix} GetLabels() → {n} label(s) in loaded catalog(s):"); if (n == 0) { sb.AppendLine($"{LogPrefix} (empty — catalog build cũ hoặc chưa có remote catalog)"); return; } bool foundHighlight = false; for (int i = 0; i < n; i++) { var l = labels[i]; if (string.IsNullOrEmpty(l)) continue; bool match = string.Equals(l, highlightLabel, StringComparison.Ordinal); bool matchIgnoreCase = string.Equals(l, highlightLabel, StringComparison.OrdinalIgnoreCase); if (match) foundHighlight = true; string flag = match ? " <-- EXACT MATCH" : matchIgnoreCase ? " <-- same letters, different case" : ""; sb.AppendLine($"{LogPrefix} [{i}] \"{l}\"{flag}"); } if (!foundHighlight) { sb.AppendLine( $"{LogPrefix} WARNING: \"{highlightLabel}\" NOT in GetLabels() list — " + "InvalidKeyException khi GetDownloadSizeAsync/DownloadDependencies là đúng."); } } catch (Exception e) { sb.AppendLine($"{LogPrefix} GetLabels() exception: {e.GetType().Name}: {e.Message}"); } finally { if (handle.IsValid()) Addressables.Release(handle); } } static bool IsRemoteLocation(IResourceLocation loc) { if (loc == null || string.IsNullOrEmpty(loc.InternalId)) return false; var id = loc.InternalId; if (id.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || id.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) return true; return loc.ResourceType == typeof(IAssetBundleResource); } static string Truncate(string s, int maxLen) { if (string.IsNullOrEmpty(s) || s.Length <= maxLen) return s ?? ""; return s.Substring(0, maxLen) + "..."; } } }