172 lines
6.1 KiB
C#
172 lines
6.1 KiB
C#
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Server.Galaxy;
|
|
|
|
public sealed class GalaxyHierarchyIndex
|
|
{
|
|
private GalaxyHierarchyIndex(
|
|
IReadOnlyList<GalaxyObjectView> objectViews,
|
|
IReadOnlyDictionary<int, GalaxyObjectView> objectViewsById,
|
|
IReadOnlyDictionary<string, GalaxyTagLookup> tagsByAddress,
|
|
IReadOnlyDictionary<int, IReadOnlyList<GalaxyObjectView>> childrenByParent)
|
|
{
|
|
ObjectViews = objectViews;
|
|
ObjectViewsById = objectViewsById;
|
|
TagsByAddress = tagsByAddress;
|
|
ChildrenByParent = childrenByParent;
|
|
}
|
|
|
|
/// <summary>Gets an empty Galaxy hierarchy index.</summary>
|
|
public static GalaxyHierarchyIndex Empty { get; } = new(
|
|
Array.Empty<GalaxyObjectView>(),
|
|
new Dictionary<int, GalaxyObjectView>(),
|
|
new Dictionary<string, GalaxyTagLookup>(StringComparer.OrdinalIgnoreCase),
|
|
new Dictionary<int, IReadOnlyList<GalaxyObjectView>>());
|
|
|
|
/// <summary>Gets the object views.</summary>
|
|
public IReadOnlyList<GalaxyObjectView> ObjectViews { get; }
|
|
|
|
/// <summary>Gets the object views indexed by GUID.</summary>
|
|
public IReadOnlyDictionary<int, GalaxyObjectView> ObjectViewsById { get; }
|
|
|
|
/// <summary>Gets tags indexed by address.</summary>
|
|
public IReadOnlyDictionary<string, GalaxyTagLookup> TagsByAddress { get; }
|
|
|
|
/// <summary>Gets direct children grouped by parent gobject id. Root objects (no parent, or self-parented) live under key 0. Each list is sorted areas-first, then by display name (OrdinalIgnoreCase).</summary>
|
|
public IReadOnlyDictionary<int, IReadOnlyList<GalaxyObjectView>> ChildrenByParent { get; }
|
|
|
|
/// <summary>Builds a Galaxy hierarchy index from the given objects.</summary>
|
|
/// <param name="objects">The Galaxy objects to index.</param>
|
|
/// <returns>A new Galaxy hierarchy index.</returns>
|
|
public static GalaxyHierarchyIndex Build(IReadOnlyList<GalaxyObject> objects)
|
|
{
|
|
if (objects.Count == 0)
|
|
{
|
|
return Empty;
|
|
}
|
|
|
|
Dictionary<int, GalaxyObject> objectsById = new();
|
|
foreach (GalaxyObject obj in objects)
|
|
{
|
|
objectsById.TryAdd(obj.GobjectId, obj);
|
|
}
|
|
|
|
List<GalaxyObjectView> views = new(objects.Count);
|
|
Dictionary<int, GalaxyObjectView> viewsById = new();
|
|
Dictionary<string, GalaxyTagLookup> tagsByAddress = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
foreach (GalaxyObject obj in objects)
|
|
{
|
|
string path = BuildContainedPath(obj, objectsById);
|
|
int depth = string.IsNullOrWhiteSpace(path) ? 0 : path.Count(character => character == '/');
|
|
GalaxyObjectView view = new(obj, path, depth);
|
|
views.Add(view);
|
|
viewsById.TryAdd(obj.GobjectId, view);
|
|
|
|
if (!string.IsNullOrWhiteSpace(obj.TagName))
|
|
{
|
|
tagsByAddress.TryAdd(obj.TagName, new GalaxyTagLookup(obj, Attribute: null, path));
|
|
}
|
|
|
|
foreach (GalaxyAttribute attribute in obj.Attributes)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(attribute.FullTagReference))
|
|
{
|
|
tagsByAddress.TryAdd(attribute.FullTagReference, new GalaxyTagLookup(obj, attribute, path));
|
|
}
|
|
}
|
|
}
|
|
|
|
Dictionary<int, List<GalaxyObjectView>> childrenByParent = new();
|
|
foreach (GalaxyObjectView view in views)
|
|
{
|
|
int parentKey = view.Object.ParentGobjectId;
|
|
// Treat self-parented (corrupt) rows as roots; matches DashboardBrowseTreeBuilder.
|
|
if (parentKey == view.Object.GobjectId)
|
|
{
|
|
parentKey = 0;
|
|
}
|
|
if (!childrenByParent.TryGetValue(parentKey, out List<GalaxyObjectView>? bucket))
|
|
{
|
|
bucket = [];
|
|
childrenByParent[parentKey] = bucket;
|
|
}
|
|
bucket.Add(view);
|
|
}
|
|
|
|
foreach (List<GalaxyObjectView> bucket in childrenByParent.Values)
|
|
{
|
|
bucket.Sort(CompareByAreaThenDisplayName);
|
|
}
|
|
|
|
Dictionary<int, IReadOnlyList<GalaxyObjectView>> readOnlyChildren = new(childrenByParent.Count);
|
|
foreach (KeyValuePair<int, List<GalaxyObjectView>> kvp in childrenByParent)
|
|
{
|
|
readOnlyChildren[kvp.Key] = kvp.Value;
|
|
}
|
|
|
|
return new GalaxyHierarchyIndex(
|
|
views,
|
|
viewsById,
|
|
tagsByAddress,
|
|
readOnlyChildren);
|
|
}
|
|
|
|
private static string BuildContainedPath(
|
|
GalaxyObject obj,
|
|
IReadOnlyDictionary<int, GalaxyObject> objectsById)
|
|
{
|
|
Stack<string> names = new();
|
|
HashSet<int> seen = [];
|
|
GalaxyObject? current = obj;
|
|
while (current is not null && seen.Add(current.GobjectId))
|
|
{
|
|
names.Push(ResolvePathSegment(current));
|
|
current = current.ParentGobjectId != 0
|
|
&& objectsById.TryGetValue(current.ParentGobjectId, out GalaxyObject? parent)
|
|
? parent
|
|
: null;
|
|
}
|
|
|
|
return string.Join('/', names.Where(name => !string.IsNullOrWhiteSpace(name)));
|
|
}
|
|
|
|
private static string ResolvePathSegment(GalaxyObject obj)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(obj.ContainedName))
|
|
{
|
|
return obj.ContainedName;
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(obj.BrowseName))
|
|
{
|
|
return obj.BrowseName;
|
|
}
|
|
|
|
return obj.TagName;
|
|
}
|
|
|
|
private static int CompareByAreaThenDisplayName(GalaxyObjectView left, GalaxyObjectView right)
|
|
{
|
|
if (left.Object.IsArea != right.Object.IsArea)
|
|
{
|
|
return left.Object.IsArea ? -1 : 1;
|
|
}
|
|
return string.Compare(DisplayNameOf(left), DisplayNameOf(right), StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private static string DisplayNameOf(GalaxyObjectView view)
|
|
{
|
|
GalaxyObject obj = view.Object;
|
|
if (!string.IsNullOrWhiteSpace(obj.BrowseName))
|
|
{
|
|
return obj.BrowseName;
|
|
}
|
|
if (!string.IsNullOrWhiteSpace(obj.ContainedName))
|
|
{
|
|
return obj.ContainedName;
|
|
}
|
|
return obj.TagName;
|
|
}
|
|
}
|