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]");
}
LogAllCatalogKeys(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);
}
}
///
/// Addressables 2.7 has no runtime GetLabels(); enumerate string keys from loaded locators instead.
/// Keys include both labels and asset addresses.
///
static void LogAllCatalogKeys(StringBuilder sb, string highlightLabel, int maxKeysToLog = 80)
{
var keys = new List();
var seen = new HashSet(StringComparer.Ordinal);
if (Addressables.ResourceLocators != null)
{
foreach (IResourceLocator locator in Addressables.ResourceLocators)
{
if (locator?.Keys == null)
continue;
foreach (object key in locator.Keys)
{
if (key is not string s || string.IsNullOrEmpty(s) || !seen.Add(s))
continue;
keys.Add(s);
}
}
}
keys.Sort(StringComparer.Ordinal);
int n = keys.Count;
sb.AppendLine(
$"{LogPrefix} ResourceLocator keys → {n} unique (labels + addresses; Addressables 2.7 has no GetLabels()):");
if (n == 0)
{
sb.AppendLine($"{LogPrefix} (empty — catalog build cũ hoặc chưa có remote catalog)");
return;
}
bool foundHighlight = false;
bool foundHighlightIgnoreCase = false;
int logCount = Mathf.Min(maxKeysToLog, n);
for (int i = 0; i < n; i++)
{
var k = keys[i];
bool match = string.Equals(k, highlightLabel, StringComparison.Ordinal);
bool matchIgnoreCase = string.Equals(k, highlightLabel, StringComparison.OrdinalIgnoreCase);
if (match)
foundHighlight = true;
if (matchIgnoreCase)
foundHighlightIgnoreCase = true;
if (i >= logCount)
continue;
string flag = match ? " <-- EXACT MATCH" :
matchIgnoreCase ? " <-- same letters, different case" : "";
sb.AppendLine($"{LogPrefix} [{i}] \"{k}\"{flag}");
}
if (n > logCount)
sb.AppendLine($"{LogPrefix} … {n - logCount} more key(s) omitted (maxKeysToLog={maxKeysToLog}).");
if (!foundHighlight)
{
sb.AppendLine(
$"{LogPrefix} WARNING: \"{highlightLabel}\" NOT in catalog keys — " +
"InvalidKeyException khi GetDownloadSizeAsync/DownloadDependencies là đúng.");
if (foundHighlightIgnoreCase)
{
sb.AppendLine(
$"{LogPrefix} (có key cùng chữ khác hoa/thường — label Addressables phân biệt hoa thường)");
}
}
}
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) + "...";
}
}
}