diff --git a/src/gaemstone.Client/Systems/EntityInspector.cs b/src/gaemstone.Client/Systems/EntityInspector.cs index cb0e0a2..a0eb321 100644 --- a/src/gaemstone.Client/Systems/EntityInspector.cs +++ b/src/gaemstone.Client/Systems/EntityInspector.cs @@ -5,9 +5,9 @@ using System.Numerics; using gaemstone.Client.Utility; using gaemstone.ECS; using gaemstone.Flecs; -using gaemstone.Utility; using ImGuiNET; using static gaemstone.Client.Systems.ImGuiManager; +using static gaemstone.Flecs.Core; using Icon = gaemstone.Client.Utility.ForkAwesome; using ImGuiInternal = ImGuiNET.Internal.ImGui; @@ -75,8 +75,8 @@ public class EntityInspector SetDocInfo("/flecs/system/System" , 1 , Icon.Cog , 1.0f, 0.7f, 0.7f); SetDocInfo("/flecs/core/Observer" , 2 , Icon.Eye , 1.0f, 0.8f, 0.8f); SetDocInfo("/gaemstone/Doc/Relation" , 3 , Icon.ShareAlt , 0.7f, 1.0f, 0.8f); - SetDocInfo("/flecs/core/Tag" , 4 , Icon.Tag , 0.7f, 0.8f, 1.0f); - SetDocInfo("/flecs/core/Component" , 5 , Icon.PencilSquare , 0.6f, 0.6f, 1.0f); + SetDocInfo("/flecs/core/Component" , 4 , Icon.PencilSquare , 0.6f, 0.6f, 1.0f); + SetDocInfo("/flecs/core/Tag" , 5 , Icon.Tag , 0.7f, 0.8f, 1.0f); SetDocInfo("/flecs/core/Prefab" , 6 , Icon.Cube , 0.9f, 0.8f, 1.0f); } @@ -94,7 +94,7 @@ public class EntityInspector } [System] - public void ShowExplorerWindow(EntityRef window, InspectorWindow _, History? history) + public void ShowInspectorWindow(EntityRef window, InspectorWindow _, History? history) { var isOpen = true; var fontSize = ImGui.GetFontSize(); @@ -173,9 +173,9 @@ public class EntityInspector ImGui.TableSetupColumn("Entity", ImGuiTableColumnFlags.WidthFixed); ImGui.TableNextColumn(); - var hasExpanded = window.Has(); + var hasExpanded = window.Has(); if (IconButtonWithToolTip(Icon.Outdent, "Collapse all items in the Explorer View", hasExpanded)) - window.Remove(); + window.Remove(); if (history != null) { var hasPrev = ((selected != null) ? history.Current?.Prev : history.Current) != null; @@ -215,6 +215,7 @@ public class EntityInspector ImGui.SameLine(); if (IconButtonWithToolTip(Icon.Trash, "Delete the current entity", (selected != null))) { + // TODO: Delete history for deleted entity? SetSelected(window, history, selected!.Parent); selected.Delete(); // TODO: Confirmation dialog? } @@ -272,208 +273,174 @@ public class EntityInspector ImGui.PopStyleVar(); } - private struct EntitySummary - : IComparable + private class ExplorerEntry + : IComparable { - public Entity Entity { get; init; } - public SpecialType? Type { get; init; } - public string? Name { get; init; } - public string? DocName { get; init; } - public Color? DocColor { get; init; } - public bool HasChildren { get; init; } - public bool IsExpanded { get; init; } - public bool IsDisabled { get; init; } - - public int CompareTo(EntitySummary other) + private readonly EntityInspector _module; + + public EntityRef Entity { get; } + public int NumChildren { get; } + public bool HasChildren => (NumChildren > 0); + public bool IsExpanded { get; } + public bool IsDisabled { get; } + + private int _nameCached; private string? _name; + private int _docNameCached; private string? _docName; + public string? Name => (_nameCached++ > 0) ? _name : _name = Entity.Name; + public string? DocName => (_docNameCached++ > 0) ? _docName : _docName = Entity.GetDocName(); + + private float? _priority; + private EntityRef? _type; + public float Priority => EnsureTypeCached()._priority!.Value; + public EntityRef? Type => EnsureTypeCached()._type; + + public ExplorerEntry(EntityInspector module, EntityRef entity, + int numChildren, bool isExpanded, bool isDisabled) { + _module = module; + Entity = entity; + NumChildren = numChildren; + IsExpanded = (isExpanded && HasChildren); + IsDisabled = isDisabled; + } + + private ExplorerEntry EnsureTypeCached() + { + if (_priority != null) return this; + (_type, _priority) = _module.FindDisplayType(Entity); + return this; + } + + public int CompareTo(ExplorerEntry? other) + { + if (other == null) return -1; + return Compare(Priority, other.Priority) + ?? Compare(Name, other.Name) + ?? Compare(DocName, other.DocName) + ?? Compare(Entity.Id, other.Entity.Id) + ?? 0; static int? Compare(T x, T y) { if (x is null) { if (y is null) return null; else return 1; } else if (y is null) return -1; var result = Comparer.Default.Compare(x, y); return (result != 0) ? result : null; } - - return Compare(Type, other.Type) - ?? Compare(Name, other.Name) - ?? Compare(DocName, other.DocName) - ?? Compare(Entity.Id, other.Entity.Id) - ?? 0; } - - public string DisplayName { get { - var name = (DocName != null) ? $"\"{DocName}\"" : Name ?? Entity.Id.ToString(); - if (Type != null) name = $"{DisplayIcon} {name}"; - return name; - } } - - public string? DisplayIcon => Type switch { - SpecialType.Module => Icon.Archive, - SpecialType.System => Icon.Cog, - SpecialType.Relation => Icon.ShareAlt, - SpecialType.Component => Icon.PencilSquare, - SpecialType.Tag => Icon.Tag, - SpecialType.Prefab => Icon.Cube, - _ => null, - }; - - public Color? DisplayColor => DocColor ?? Type switch { - SpecialType.Module => Color.FromRGB(1.0f, 0.9f, 0.7f), - SpecialType.System => Color.FromRGB(1.0f, 0.7f, 0.7f), - SpecialType.Relation => Color.FromRGB(0.7f, 1.0f, 0.8f), - SpecialType.Component => Color.FromRGB(0.6f, 0.6f, 1.0f), - SpecialType.Tag => Color.FromRGB(0.7f, 0.8f, 1.0f), - SpecialType.Prefab => Color.FromRGB(0.9f, 0.8f, 1.0f), - _ => null, - }; } - public enum SpecialType - { - Module, - System, - Relation, - Component, - Tag, - Prefab, - } - - private const int MAX_CHILDREN = 64; private void ExplorerView(EntityRef window, History? history, EntityRef? selected) { - var Expanded = window.World.LookupByTypeOrThrow(); - - List GetSummaries(Entity? parent) { - var result = new List(); - var expression = $"(ChildOf, {parent?.Id ?? 0})" // Must be child of parent, or root entity. - + ",?(Identifier, Name)" // Name (in hierarchy) - + ",?(flecs.doc.Description, Name)" // DocName (human-readable) - + ",?(flecs.doc.Description, flecs.doc.Color)" // DocColor - + ",[none] ?ChildOf(_, $This)" // HasChildren - + $",?{Expanded.Id}({window.Id}, $This)" // IsExpanded - + ",?Disabled" // IsDisabled - + ",?Module,?flecs.system.System,?gaemstone.Doc.Relation,?Component,?Tag,?Prefab"; // Type - - using (var rule = new Rule(window.World, new(expression))) { - foreach (var iter in rule.Iter()) { - var names = iter.FieldOrEmpty(2); - var docNames = iter.FieldOrEmpty(3); - var docColors = iter.FieldOrEmpty(4); - - var hasChildren = iter.FieldIsSet(5); - var isExpanded = iter.FieldIsSet(6); - var isDisabled = iter.FieldIsSet(7); - - var isModule = iter.FieldIsSet(8); - var isSystem = iter.FieldIsSet(9); - var isRelation = iter.FieldIsSet(10); - var components = iter.FieldOrEmpty(11); - var isTag = iter.FieldIsSet(12); - var isPrefab = iter.FieldIsSet(13); - - for (var i = 0; i < iter.Count; i++) { - // Certain built-in components in Flecs actually have a size of 0, - // thus don't actually hold any value and behave more like tags. - // We pretend they are just tags and mark them as such. - var component = components.GetOrNull(i); - var isComponent = (component?.Size > 0); - var isTagEquiv = (component?.Size == 0) || isTag; - - var type = isModule ? SpecialType.Module - : isSystem ? SpecialType.System - : isRelation ? SpecialType.Relation - : isComponent ? SpecialType.Component - : isTagEquiv ? SpecialType.Tag - : isPrefab ? SpecialType.Prefab - : (SpecialType?)null; - result.Add(new() { - Entity = iter.Entity(i), - Type = type, - Name = names.GetOrNull(i)?.ToString(), - DocName = docNames.GetOrNull(i)?.ToString(), - DocColor = Color.TryParseHex(docColors.GetOrNull(i)?.ToString()), - HasChildren = hasChildren, - IsExpanded = isExpanded, - IsDisabled = isDisabled, - }); - if (result.Count > MAX_CHILDREN) - return result; - } + // For some reason, the analyzer thinks world can be + // nullable, so let's be explicit about the type here. + World world = window.World; + + var Wildcard = world.LookupByTypeOrThrow().Entity; + var Any = world.LookupByTypeOrThrow().Entity; + var This = world.LookupByTypeOrThrow().Entity; + var Var = world.LookupByTypeOrThrow().Entity; + bool IsSpecialEntity(Entity entity) + => (entity == Wildcard) || (entity == Any) + || (entity == This) || (entity == Var); + + var expId = world.LookupByTypeOrThrow().Id; + List GetEntries(Entity? parent) { + var result = new List(); + using var rule = new Rule(world, new( + $"(ChildOf, {parent?.Id ?? 0})" // Must be child of parent, or root entity. + + $",?{expId}({window.Id}, $This)" // Whether entity is expanded in explorer view. + + $",?Disabled" // Don't filter out disabled entities. + )); + foreach (var iter in rule.Iter()) { + var isExpanded = iter.FieldIsSet(2); + var isDisabled = iter.FieldIsSet(3); + for (var i = 0; i < iter.Count; i++) { + var entity = iter.Entity(i); + var count = IsSpecialEntity(entity) ? 0 + : IdRef.Pair(entity).Count; + result.Add(new(this, entity, count, isExpanded, isDisabled)); } } - - result.Sort(); return result; } - void EntryNode(EntitySummary summary) { - var entity = new EntityRef(window.World, summary.Entity); - var isExpanded = summary.IsExpanded; - var isSelected = (selected == entity); - - var flags = ImGuiTreeNodeFlags.OpenOnArrow - | ImGuiTreeNodeFlags.OpenOnDoubleClick - | ImGuiTreeNodeFlags.SpanAvailWidth; - if (!summary.HasChildren) flags |= ImGuiTreeNodeFlags.Leaf - | ImGuiTreeNodeFlags.Bullet - | ImGuiTreeNodeFlags.NoTreePushOnOpen; - if (isSelected) flags |= ImGuiTreeNodeFlags.Selected; - - var hasColor = false; - if (summary.DisplayColor is Color color) { ImGui.PushStyleColor(ImGuiCol.Text, color.RGBA); hasColor = true; } - if (summary.DocName != null) ImGui.PushFont(ImGui.GetIO().Fonts.Fonts[2]); - if (summary.IsDisabled) ImGui.PushStyleVar(ImGuiStyleVar.Alpha, ImGui.GetStyle().DisabledAlpha); - ImGui.SetNextItemOpen(isExpanded); - ImGui.TreeNodeEx(summary.DisplayName, flags); - if (summary.IsDisabled) ImGui.PopStyleVar(); - if (summary.DocName != null) ImGui.PopFont(); - if (hasColor) ImGui.PopStyleColor(); - - // When hovering over the node, display brief description (if available). - if (ImGui.IsItemHovered() && entity.GetDocBrief() is string brief) - ImGui.SetTooltip(brief); - - // When node is clicked (but not on the arrow), select this entity. - if (ImGui.IsItemClicked() && !ImGui.IsItemToggledOpen()) - SetSelected(window, history, entity, scrollTo: false); - - // When node is toggled, toggle (Expanded, entity) - // relation on the inspector window entity. - if (ImGui.IsItemToggledOpen()) { - if (isExpanded) { - isExpanded = false; - window.Remove(Expanded, entity); - } else if (summary.HasChildren) { - isExpanded = true; - window.Add(Expanded, entity); + var spacingX = ImGui.GetStyle().ItemInnerSpacing.X; + var spacingY = ImGui.GetStyle().ItemSpacing.Y; + var nodeHeight = ImGui.GetTextLineHeight(); + + var scrollToSelected = window.Has(); + + float GetIndent(int depth) => depth * (nodeHeight + spacingX); + + bool RenderNode(ExplorerEntry entry, int? depth) { + var entity = entry.Entity; + var startY = ImGui.GetCursorPosY(); + var isVisible = ImGui.IsRectVisible(new(nodeHeight, nodeHeight)); + var isSelected = (entity == selected); + var isRenderPass = (depth != null); + + if (isRenderPass) { + // Button for expanding the child entries. + if (entry.HasChildren && !entry.IsExpanded) { + ImGui.SetCursorPosX(GetIndent(depth!.Value)); + if (ImGui.Button($"##Expand{entity.Id}", new(nodeHeight))) + window.Add(entity); } - } - if (window.Has() && isSelected) { - ImGui.SetScrollHereY(); - window.Remove(); - } + // Render the entity text, or a dummy if not necessary. + ImGui.SetCursorPos(new(GetIndent(depth!.Value + 1), startY)); + var flags = RenderEntityFlags.IsHeaderLike + | RenderEntityFlags.SpanAvailWidth; + if (isSelected) flags |= RenderEntityFlags.IsSelected; + if (isVisible) RenderEntity(window, history, entity, flags); + else ImGui.Dummy(new(nodeHeight, nodeHeight)); + + // Scroll to this entry if selected and scrolling was requested. + if (scrollToSelected && isSelected) { + window.Remove(); + ImGui.SetScrollHereY(); + } + } else ImGui.Dummy(new(nodeHeight, nodeHeight)); + + // If node is not expanded, don't run any of code involving child entries. + if (!entry.IsExpanded) return isVisible; - if (isExpanded && summary.HasChildren) { - var children = GetSummaries(entity); - if (children.Count > MAX_CHILDREN) { - ImGui.TreePush(); - ImGui.TextWrapped($"{Icon.ExclamationTriangle} Too many children. " + - "If an entity's full path is known, it can be entered in the path input."); - ImGui.TreePop(); - } else foreach (var child in children) - EntryNode(child); - ImGui.TreePop(); + // Get the child entries of this entity. + var entries = GetEntries(entity); + + // If scrolling to selected, make sure every node is sorted. + // Otherwise the target node's position might not be right. + if (!scrollToSelected) { + foreach (var child in entries) + if (RenderNode(child, null)) + isVisible = true; + if (!isVisible) return false; } + + if (!isRenderPass) return isVisible; + + entries.Sort(); + ImGui.SetCursorPosY(startY + nodeHeight); + foreach (var child in entries) + RenderNode(child, depth + 1); + + var fullHeight = ImGui.GetCursorPosY() - startY - spacingY; + ImGui.SetCursorPos(new(GetIndent(depth!.Value), startY)); + if (ImGui.Button($"##Expand{entity.Id}", new(nodeHeight, fullHeight))) + window.Remove(entity); + + return true; } - foreach (var summary in GetSummaries(Entity.None)) - EntryNode(summary); + var entries = GetEntries(null); + entries.Sort(); + foreach (var child in entries) + RenderNode(child, 0); } private void ComponentsTab(EntityRef window, History? history, EntityRef? selected) { if (selected == null) return; - var ChildOf = window.World.LookupByTypeOrThrow(); + var ChildOf = window.World.LookupByTypeOrThrow(); foreach (var id in selected.Type) { // Hide ChildOf relations, as they are visible in the explorer. if (id.IsPair && (id.Id.RelationUnsafe == ChildOf)) continue; @@ -485,8 +452,8 @@ public class EntityInspector { if (selected == null) return; var world = window.World; - var ChildOf = world.LookupByTypeOrThrow(); - var Wildcard = world.LookupByTypeOrThrow(); + var ChildOf = world.LookupByTypeOrThrow(); + var Wildcard = world.LookupByTypeOrThrow(); if (ImGui.CollapsingHeader($"As {Icon.Tag} Entity", ImGuiTreeNodeFlags.DefaultOpen)) foreach (var iter in Iterator.FromTerm(world, new(selected))) @@ -624,7 +591,7 @@ public class EntityInspector bool scrollTo = true) // Should entity be scrolled to in the explorer view? { if (entity != null) window.Add(entity); - else window.Remove(); + else window.Remove(); for (var parent = entity?.Parent; parent != null; parent = parent.Parent) window.Add(parent); @@ -661,10 +628,10 @@ public class EntityInspector } private Rule? _findDisplayTypeRule; - private EntityRef? FindDisplayType(EntityRef entity) + private (EntityRef? DisplayType, float Priority) FindDisplayType(EntityRef entity) { var world = entity.World; - var component = world.LookupByTypeOrThrow(); + var component = world.LookupByTypeOrThrow(); var rule = _findDisplayTypeRule ??= new Rule(world, new( $"$Type, gaemstone.Doc.DisplayType($Type)")); @@ -675,13 +642,13 @@ public class EntityInspector foreach (var iter in _findDisplayTypeRule.Iter().SetVar(rule.ThisVar!, entity)) for (var i = 0; i < iter.Count; i++) { var type = iter.GetVar(typeVar); - if ((type == component) && (entity.GetOrNull(component)?.Size == 0)) - type = world.LookupByTypeOrThrow(); + if ((type == component) && (entity.GetOrNull(component)?.Size == 0)) + type = world.LookupByTypeOrThrow(); var priority = type.GetOrNull()?.Value ?? float.MaxValue; if (priority <= curPriority) { curType = type; curPriority = priority; } } - return curType; + return (curType, curPriority); } @@ -689,7 +656,7 @@ public class EntityInspector // == Utility ImGui Functions == // ============================= - private void RenderIdentifier(EntityRef window, History? history, IdentifierRef id) + private void RenderIdentifier(EntityRef window, History? history, IdRef id) { ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(2, ImGui.GetStyle().ItemSpacing.Y)); if (id.AsPair() is (EntityRef relation, EntityRef target)) { @@ -705,8 +672,14 @@ public class EntityInspector ImGui.PopStyleVar(); } - private void RenderEntity(EntityRef window, History? history, EntityRef entity) + private void RenderEntity( + EntityRef window, History? history, EntityRef entity, + RenderEntityFlags flags = default) { + var spanAvailWidth = (flags & RenderEntityFlags.SpanAvailWidth) != 0; + var isHeaderLike = (flags & RenderEntityFlags.IsHeaderLike) != 0; + var isSelected = (flags & RenderEntityFlags.IsSelected) != 0; + var pos = ImGui.GetCursorScreenPos(); // Adjust based on AlignTextToFramePadding() or similar. pos.Y += ImGuiInternal.GetCurrentWindow().DC.CurrLineTextBaseOffset; @@ -715,7 +688,7 @@ public class EntityInspector var dummySize = new Vector2(20, ImGui.GetTextLineHeight()); if (!ImGui.IsRectVisible(pos, pos + dummySize)) { ImGui.Dummy(dummySize); return; } - var displayType = FindDisplayType(entity); + var (displayType, _) = FindDisplayType(entity); var docColor = Color.TryParseHex(entity.GetDocColor()) ?? Color.TryParseHex(displayType?.GetDocColor()); var docIcon = entity.GetOrNull()?.Value.ToString() ?? displayType?.GetOrNull()?.Value.ToString(); var docName = entity.GetDocName(false); @@ -724,22 +697,42 @@ public class EntityInspector var displayName = (docName != null) ? $"\"{docName}\"" : entity.Name ?? entity.Id.ToString(); if (docIcon is string icon) displayName = $"{icon} {displayName}"; - var font = ImGui.GetIO().Fonts.Fonts[(docName != null) ? 2 : 0]; - ImGui.PushFont(font); var size = ImGui.CalcTextSize(displayName); ImGui.PopFont(); + var font = ImGui.GetIO().Fonts.Fonts[(docName != null) ? 2 : 0]; var color = docColor ?? Color.FromRGBA(ImGui.GetColorU32(ImGuiCol.Text)); if (isDisabled) color = color.WithAlpha(ImGui.GetStyle().DisabledAlpha); - var ctrl = ImGui.IsKeyDown(ImGuiKey.ModCtrl); + // Gotta push the font to calculate size properly. + // Can't just pass it to CalcTextSize for some reason. + ImGui.PushFont(font); var size = ImGui.CalcTextSize(displayName); ImGui.PopFont(); + if (isHeaderLike) size.X += ImGui.GetStyle().FramePadding.X * 2; + if (spanAvailWidth) size.X = Math.Max(size.X, ImGui.GetContentRegionAvail().X); + + if (isHeaderLike) { + ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0); + ImGui.PushStyleColor(ImGuiCol.Button, isSelected ? ImGui.GetColorU32(ImGuiCol.Header) + : Color.Transparent.RGBA); + ImGui.PushStyleColor(ImGuiCol.ButtonActive , ImGui.GetColorU32(ImGuiCol.HeaderActive)); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, ImGui.GetColorU32(ImGuiCol.HeaderHovered)); + ImGui.Button($"##{entity.Id}", size); + ImGui.PopStyleColor(3); + ImGui.PopStyleVar(); + } else ImGui.InvisibleButton($"##{entity.Id}", size); + var shift = ImGui.IsKeyDown(ImGuiKey.ModShift); - if (ImGui.InvisibleButton(entity.Id.ToString(), size) && (ctrl || shift)) { + var canClick = isHeaderLike || shift; + if (canClick && ImGui.IsItemClicked()) { if (shift) window = NewEntityInspectorWindow(window.World); - SetSelected(window, history, entity); + // Don't re-select this entity if it's already selected. + // Unless we're opening it in a new inspector window. + // This is to make sure the history doesn't get polluted. + if (!isSelected || shift) SetSelected(window, history, entity, false); } var drawList = ImGui.GetWindowDrawList(); + if (isHeaderLike) pos.X += ImGui.GetStyle().FramePadding.X; drawList.AddText(font, ImGui.GetFontSize(), pos, color.RGBA, displayName); - // Draw an underscore (like a hyperlink) if hovered and Ctrl key is held. - if (ImGui.IsItemHovered() && (ctrl || shift)) { + if (!isHeaderLike && canClick && ImGui.IsItemHovered()) { + // Draw a hyperlink-link underscore. pos.Y -= 1.75f; drawList.AddLine(pos + new Vector2(0, size.Y), pos + size, color.RGBA); ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); @@ -760,4 +753,12 @@ public class EntityInspector ImGui.EndTooltip(); } } + + [Flags] + private enum RenderEntityFlags + { + SpanAvailWidth = 0b001, // Extend the bounding box to span available width. + IsHeaderLike = 0b010, // Render the element like a header instead of a link. + IsSelected = 0b100, // Render a background showing this as selected. + } } diff --git a/src/gaemstone.ECS b/src/gaemstone.ECS index f9fa808..619e2b2 160000 --- a/src/gaemstone.ECS +++ b/src/gaemstone.ECS @@ -1 +1 @@ -Subproject commit f9fa808d6842f5b208bbdfa1925f156342ed8a60 +Subproject commit 619e2b24c6ff8797aed702a77954adfbd40f6f4d diff --git a/src/gaemstone/Flecs/Core.cs b/src/gaemstone/Flecs/Core.cs index 731973a..91d3b69 100644 --- a/src/gaemstone/Flecs/Core.cs +++ b/src/gaemstone/Flecs/Core.cs @@ -18,12 +18,14 @@ public static class Core [Tag] public struct Empty { } // Can't be in a module class with the same name. - [Path("/flecs/system/System")] - [Tag] public struct System { } + // [Path("/flecs/system/System")] + // [Tag] public struct System { } // Entities - [Entity] public struct World { } + // Conflicts with World class, and not required? + // [Entity] public struct World { } + [Path("*")] public struct Wildcard { } [Path("_")] public struct Any { } [Entity] public struct This { } diff --git a/src/gaemstone/Flecs/Doc.cs b/src/gaemstone/Flecs/Doc.cs index c3409e9..46c2a59 100644 --- a/src/gaemstone/Flecs/Doc.cs +++ b/src/gaemstone/Flecs/Doc.cs @@ -30,12 +30,10 @@ public static unsafe class DocExtensions { private static EntityRef Set(EntityRef entity, string? value) { - var world = entity.World; - var descEntity = world.LookupByTypeOrThrow(); - var docEntity = world.LookupByTypeOrThrow(); - var id = Identifier.Pair(descEntity, docEntity); + var world = entity.World; + var id = IdRef.Pair(world); - var has = entity.Has(id); + var hadId = entity.Has(id); var alloc = GlobalHeapAllocator.Instance; if (value != null) { var ptr = ecs_get_mut_id(world, entity, id); @@ -43,7 +41,7 @@ public static unsafe class DocExtensions // FIXME: Why does freeing these cause crashes? // if (has) alloc.Free((nint)desc.Value); // Free previous value. desc.Value = (void*)(nint)alloc.AllocateCString(value); - } else if (has) { + } else if (hadId) { // var @ref = ecs_ref_init_id(world, entity, id); // var ptr = ecs_ref_get_id(world, &@ref, id); // var desc = Unsafe.AsRef(ptr);