refac(objects): CAGLoader consumes index.json cache with live fallback
CAGLoader.Load() now reads Data/objects/index.json (BuildTree) and only falls back to live type instantiation for entries missing from the cache (stale-cache warning) or when the index file itself is absent (LoadLegacy, the original categorization.json live-load, moved verbatim). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0ac505d7b6
commit
06001a6ea5
2 changed files with 193 additions and 0 deletions
|
|
@ -0,0 +1,48 @@
|
|||
using Server.Commands;
|
||||
using Server.Items;
|
||||
using Xunit;
|
||||
|
||||
namespace UOContent.Tests.Commands.Objects;
|
||||
|
||||
[Collection("Sequential UOContent Tests")]
|
||||
public class CagTreeBuilderTests
|
||||
{
|
||||
[Fact]
|
||||
public void BuildTree_creates_nested_categories_and_resolves_types()
|
||||
{
|
||||
var index = new ObjectIndexFile
|
||||
{
|
||||
Objects =
|
||||
[
|
||||
new ObjectIndexEntry
|
||||
{
|
||||
Type = "Katana", Entity = "item", Category = "Items.Weapons.Swords",
|
||||
Chunk = "items.weapons.swords", ItemID = 0x13FF, Hue = 0
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var root = CAGLoader.BuildTree(index);
|
||||
|
||||
var items = Assert.IsType<CAGCategory>(FindChild(root, "Items"));
|
||||
var weapons = Assert.IsType<CAGCategory>(FindChild(items, "Weapons"));
|
||||
var swords = Assert.IsType<CAGCategory>(FindChild(weapons, "Swords"));
|
||||
|
||||
var leaf = Assert.IsType<CAGObject>(swords.Nodes[0]);
|
||||
Assert.Equal(typeof(Katana), leaf.Type);
|
||||
Assert.Equal(0x13FF, leaf.ItemID);
|
||||
}
|
||||
|
||||
private static CAGNode FindChild(CAGCategory parent, string title)
|
||||
{
|
||||
foreach (var node in parent.Nodes)
|
||||
{
|
||||
if (node.Title == title)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Xunit.Sdk.XunitException($"No child '{title}'.");
|
||||
}
|
||||
}
|
||||
|
|
@ -29,6 +29,151 @@ public static class CAGLoader
|
|||
private static readonly ILogger logger = LogFactory.GetLogger(typeof(CAGLoader));
|
||||
|
||||
public static CAGCategory Load()
|
||||
{
|
||||
var indexPath = Path.Combine(Core.BaseDirectory, "Data/objects/index.json");
|
||||
|
||||
if (!File.Exists(indexPath))
|
||||
{
|
||||
logger.Warning("objects/index.json missing — run [GenObjects. Falling back to live categorization load.");
|
||||
return LoadLegacy();
|
||||
}
|
||||
|
||||
var index = JsonConfig.Deserialize<ObjectIndexFile>(indexPath);
|
||||
if (index?.Objects == null)
|
||||
{
|
||||
throw new JsonException($"Failed to deserialize {indexPath}.");
|
||||
}
|
||||
|
||||
var root = BuildTree(index);
|
||||
AddFallbackForStaleCache(root, index);
|
||||
return root;
|
||||
}
|
||||
|
||||
public static CAGCategory BuildTree(ObjectIndexFile index)
|
||||
{
|
||||
var root = new CAGCategory("Add Menu");
|
||||
|
||||
foreach (var entry in index.Objects)
|
||||
{
|
||||
var type = AssemblyHandler.FindTypeByName(entry.Type);
|
||||
if (type == null)
|
||||
{
|
||||
logger.Warning("Cached type {Type} no longer resolves; skipping.", entry.Type);
|
||||
continue;
|
||||
}
|
||||
|
||||
var category = NavigateToCategory(root, entry.Category);
|
||||
AppendObject(
|
||||
category,
|
||||
new CAGObject
|
||||
{
|
||||
Type = type,
|
||||
ItemID = entry.ItemID,
|
||||
Hue = entry.Hue == 0 ? null : entry.Hue,
|
||||
Parent = category
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static CAGCategory NavigateToCategory(CAGCategory root, string dotted)
|
||||
{
|
||||
var parent = root;
|
||||
foreach (var name in dotted.Split('.'))
|
||||
{
|
||||
var child = FindCategory(parent, name);
|
||||
if (child == null)
|
||||
{
|
||||
child = new CAGCategory(name, parent);
|
||||
AppendNode(parent, child);
|
||||
}
|
||||
|
||||
parent = child;
|
||||
}
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
private static CAGCategory FindCategory(CAGCategory parent, string title)
|
||||
{
|
||||
if (parent.Nodes == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var node in parent.Nodes)
|
||||
{
|
||||
if (node is CAGCategory cat && cat.Title == title)
|
||||
{
|
||||
return cat;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void AppendNode(CAGCategory parent, CAGNode node)
|
||||
{
|
||||
var nodes = parent.Nodes ?? [];
|
||||
var grown = new CAGNode[nodes.Length + 1];
|
||||
Array.Copy(nodes, grown, nodes.Length);
|
||||
grown[^1] = node;
|
||||
parent.Nodes = grown;
|
||||
}
|
||||
|
||||
private static void AppendObject(CAGCategory category, CAGObject obj) => AppendNode(category, obj);
|
||||
|
||||
private static void AddFallbackForStaleCache(CAGCategory root, ObjectIndexFile index)
|
||||
{
|
||||
var cached = new HashSet<string>();
|
||||
foreach (var entry in index.Objects)
|
||||
{
|
||||
cached.Add(entry.Type);
|
||||
}
|
||||
|
||||
var categorizationPath = Path.Combine(Core.BaseDirectory, "Data/categorization.json");
|
||||
var categorization = JsonConfig.Deserialize<List<CAGJson>>(categorizationPath);
|
||||
if (categorization == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var cag in categorization)
|
||||
{
|
||||
foreach (var obj in cag.Objects ?? [])
|
||||
{
|
||||
if (obj.Type == null || cached.Contains(obj.Type.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.Warning("objects cache stale for {Type} — run [GenObjects.", obj.Type.Name);
|
||||
try
|
||||
{
|
||||
var lean = ObjectIntrospection.ExtractLean(obj.Type);
|
||||
var category = NavigateToCategory(root, cag.Category);
|
||||
AppendObject(
|
||||
category,
|
||||
new CAGObject
|
||||
{
|
||||
Type = obj.Type,
|
||||
ItemID = lean.ItemID,
|
||||
Hue = lean.Hue == 0 ? null : lean.Hue,
|
||||
Parent = category
|
||||
}
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Warning(ex, "Failed live fallback for {Type}.", obj.Type.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static CAGCategory LoadLegacy()
|
||||
{
|
||||
var root = new CAGCategory("Add Menu");
|
||||
var path = Path.Combine(Core.BaseDirectory, "Data/categorization.json");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue