- Split core ECS wrapper into gaemstone.ECS project - Module loading part of gaemstone, still - Remove ComponentHooks (for now) - Fix IL for nullable references in IterActionGenerator - Move from Silk.NET.Maths to System.Numerics - Add Flecs.Core.Component and .Identifier - Add Flecs.Doc module - Add gaemstone.Doc module - Add [Symbol] to register entities with symbols This decision will probably be reversed. - World generator picks random blocks, more color! Changes relating to ImGui: - Use custom ImGUI.NET version with local changes to get access to internal functions. Currently waiting for upstream to implement this functionality. - Add font support, and the fonts OpenSans and ForkAwesome - Add an entity inspector window to browse entities, see their contents and find references to other entities.wip/source-generators
parent
0c6d63af21
commit
5268d97828
84 changed files with 2818 additions and 2256 deletions
@ -1,6 +1,6 @@ |
|||||||
[submodule "src/flecs-cs"] |
|
||||||
path = src/flecs-cs |
|
||||||
url = https://github.com/flecs-hub/flecs-cs |
|
||||||
[submodule "src/FastNoiseLite"] |
[submodule "src/FastNoiseLite"] |
||||||
path = src/FastNoiseLite |
path = src/FastNoiseLite |
||||||
url = https://github.com/Auburn/FastNoiseLite.git |
url = https://github.com/Auburn/FastNoiseLite.git |
||||||
|
[submodule "src/gaemstone.ECS"] |
||||||
|
path = src/gaemstone.ECS |
||||||
|
url = https://git.mcft.net/copygirl/gaemstone.ECS.git |
||||||
|
@ -1 +0,0 @@ |
|||||||
Subproject commit a2047983917aa462a8c2f34d5315aea48502f4d8 |
|
Binary file not shown.
@ -0,0 +1,95 @@ |
|||||||
|
Copyright (c) 2018, Fork Awesome (https://forkawesome.github.io), |
||||||
|
with Reserved Font Name Fork Awesome. |
||||||
|
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1. |
||||||
|
This license is copied below, and is also available with a FAQ at: |
||||||
|
http://scripts.sil.org/OFL |
||||||
|
|
||||||
|
|
||||||
|
----------------------------------------------------------- |
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 |
||||||
|
----------------------------------------------------------- |
||||||
|
|
||||||
|
PREAMBLE |
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide |
||||||
|
development of collaborative font projects, to support the font creation |
||||||
|
efforts of academic and linguistic communities, and to provide a free and |
||||||
|
open framework in which fonts may be shared and improved in partnership |
||||||
|
with others. |
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and |
||||||
|
redistributed freely as long as they are not sold by themselves. The |
||||||
|
fonts, including any derivative works, can be bundled, embedded, |
||||||
|
redistributed and/or sold with any software provided that any reserved |
||||||
|
names are not used by derivative works. The fonts and derivatives, |
||||||
|
however, cannot be released under any other type of license. The |
||||||
|
requirement for fonts to remain under this license does not apply |
||||||
|
to any document created using the fonts or their derivatives. |
||||||
|
|
||||||
|
DEFINITIONS |
||||||
|
"Font Software" refers to the set of files released by the Copyright |
||||||
|
Holder(s) under this license and clearly marked as such. This may |
||||||
|
include source files, build scripts and documentation. |
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the |
||||||
|
copyright statement(s). |
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as |
||||||
|
distributed by the Copyright Holder(s). |
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting, |
||||||
|
or substituting -- in part or in whole -- any of the components of the |
||||||
|
Original Version, by changing formats or by porting the Font Software to a |
||||||
|
new environment. |
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical |
||||||
|
writer or other person who contributed to the Font Software. |
||||||
|
|
||||||
|
PERMISSION & CONDITIONS |
||||||
|
Permission is hereby granted, free of charge, to any person obtaining |
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify, |
||||||
|
redistribute, and sell modified and unmodified copies of the Font |
||||||
|
Software, subject to the following conditions: |
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components, |
||||||
|
in Original or Modified Versions, may be sold by itself. |
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled, |
||||||
|
redistributed and/or sold with any software, provided that each copy |
||||||
|
contains the above copyright notice and this license. These can be |
||||||
|
included either as stand-alone text files, human-readable headers or |
||||||
|
in the appropriate machine-readable metadata fields within text or |
||||||
|
binary files as long as those fields can be easily viewed by the user. |
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font |
||||||
|
Name(s) unless explicit written permission is granted by the corresponding |
||||||
|
Copyright Holder. This restriction only applies to the primary font name as |
||||||
|
presented to the users. |
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font |
||||||
|
Software shall not be used to promote, endorse or advertise any |
||||||
|
Modified Version, except to acknowledge the contribution(s) of the |
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written |
||||||
|
permission. |
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole, |
||||||
|
must be distributed entirely under this license, and must not be |
||||||
|
distributed under any other license. The requirement for fonts to |
||||||
|
remain under this license does not apply to any document created |
||||||
|
using the Font Software. |
||||||
|
|
||||||
|
TERMINATION |
||||||
|
This license becomes null and void if any of the above conditions are |
||||||
|
not met. |
||||||
|
|
||||||
|
DISCLAIMER |
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF |
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT |
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE |
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL |
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM |
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE. |
@ -0,0 +1,93 @@ |
|||||||
|
Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans) |
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1. |
||||||
|
This license is copied below, and is also available with a FAQ at: |
||||||
|
http://scripts.sil.org/OFL |
||||||
|
|
||||||
|
|
||||||
|
----------------------------------------------------------- |
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 |
||||||
|
----------------------------------------------------------- |
||||||
|
|
||||||
|
PREAMBLE |
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide |
||||||
|
development of collaborative font projects, to support the font creation |
||||||
|
efforts of academic and linguistic communities, and to provide a free and |
||||||
|
open framework in which fonts may be shared and improved in partnership |
||||||
|
with others. |
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and |
||||||
|
redistributed freely as long as they are not sold by themselves. The |
||||||
|
fonts, including any derivative works, can be bundled, embedded, |
||||||
|
redistributed and/or sold with any software provided that any reserved |
||||||
|
names are not used by derivative works. The fonts and derivatives, |
||||||
|
however, cannot be released under any other type of license. The |
||||||
|
requirement for fonts to remain under this license does not apply |
||||||
|
to any document created using the fonts or their derivatives. |
||||||
|
|
||||||
|
DEFINITIONS |
||||||
|
"Font Software" refers to the set of files released by the Copyright |
||||||
|
Holder(s) under this license and clearly marked as such. This may |
||||||
|
include source files, build scripts and documentation. |
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the |
||||||
|
copyright statement(s). |
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as |
||||||
|
distributed by the Copyright Holder(s). |
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting, |
||||||
|
or substituting -- in part or in whole -- any of the components of the |
||||||
|
Original Version, by changing formats or by porting the Font Software to a |
||||||
|
new environment. |
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical |
||||||
|
writer or other person who contributed to the Font Software. |
||||||
|
|
||||||
|
PERMISSION & CONDITIONS |
||||||
|
Permission is hereby granted, free of charge, to any person obtaining |
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify, |
||||||
|
redistribute, and sell modified and unmodified copies of the Font |
||||||
|
Software, subject to the following conditions: |
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components, |
||||||
|
in Original or Modified Versions, may be sold by itself. |
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled, |
||||||
|
redistributed and/or sold with any software, provided that each copy |
||||||
|
contains the above copyright notice and this license. These can be |
||||||
|
included either as stand-alone text files, human-readable headers or |
||||||
|
in the appropriate machine-readable metadata fields within text or |
||||||
|
binary files as long as those fields can be easily viewed by the user. |
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font |
||||||
|
Name(s) unless explicit written permission is granted by the corresponding |
||||||
|
Copyright Holder. This restriction only applies to the primary font name as |
||||||
|
presented to the users. |
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font |
||||||
|
Software shall not be used to promote, endorse or advertise any |
||||||
|
Modified Version, except to acknowledge the contribution(s) of the |
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written |
||||||
|
permission. |
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole, |
||||||
|
must be distributed entirely under this license, and must not be |
||||||
|
distributed under any other license. The requirement for fonts to |
||||||
|
remain under this license does not apply to any document created |
||||||
|
using the Font Software. |
||||||
|
|
||||||
|
TERMINATION |
||||||
|
This license becomes null and void if any of the above conditions are |
||||||
|
not met. |
||||||
|
|
||||||
|
DISCLAIMER |
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF |
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT |
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE |
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL |
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM |
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE. |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,763 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Linq; |
||||||
|
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 Icon = gaemstone.Client.Utility.ForkAwesome; |
||||||
|
using ImGuiInternal = ImGuiNET.Internal.ImGui; |
||||||
|
|
||||||
|
namespace gaemstone.Client.Systems; |
||||||
|
|
||||||
|
[Module] |
||||||
|
[DependsOn<gaemstone.Client.Systems.ImGuiManager>] |
||||||
|
public class EntityInspector |
||||||
|
: IModuleInitializer |
||||||
|
{ |
||||||
|
[Tag] |
||||||
|
public struct InspectorWindow { } |
||||||
|
|
||||||
|
[Relation, Exclusive] |
||||||
|
[Add<DeletionEvent.OnDeleteTarget, DeletionBehavior.Delete>] |
||||||
|
public struct Selected { } |
||||||
|
|
||||||
|
[Tag] |
||||||
|
public struct ScrollToSelected { } |
||||||
|
|
||||||
|
[Relation] |
||||||
|
public struct Expanded { } |
||||||
|
|
||||||
|
[Component] |
||||||
|
public class History |
||||||
|
{ |
||||||
|
public Entry? Current { get; set; } = null; |
||||||
|
|
||||||
|
public class Entry |
||||||
|
{ |
||||||
|
public Entity Entity { get; } |
||||||
|
public EntityPath? Path { get; } |
||||||
|
|
||||||
|
public Entry? Prev { get; set; } |
||||||
|
public Entry? Next { get; set; } |
||||||
|
|
||||||
|
public Entry(EntityRef entity, Entry? prev, Entry? next) |
||||||
|
{ |
||||||
|
Entity = entity; |
||||||
|
Path = entity.GetFullPath(); |
||||||
|
if ((Prev = prev) != null) Prev.Next = this; |
||||||
|
if ((Next = next) != null) Next.Prev = this; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
[Component] |
||||||
|
public struct DocPriority { public float Value; } |
||||||
|
|
||||||
|
[Component] |
||||||
|
public struct DocIcon { public char Value; } |
||||||
|
|
||||||
|
|
||||||
|
public void Initialize(EntityRef module) |
||||||
|
{ |
||||||
|
void SetDocInfo(string path, float priority, string icon, float r, float g, float b) |
||||||
|
=> module.World.LookupByPathOrThrow(path) |
||||||
|
.Add<Doc.DisplayType>() |
||||||
|
.Set(new DocPriority { Value = priority }) |
||||||
|
.Set(new DocIcon { Value = icon[0] }) |
||||||
|
.SetDocColor(Color.FromRGB(r, g, b).ToHexString()); |
||||||
|
|
||||||
|
SetDocInfo("/flecs/core/Module" , 0 , Icon.Archive , 1.0f, 0.9f, 0.7f); |
||||||
|
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/Prefab" , 6 , Icon.Cube , 0.9f, 0.8f, 1.0f); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
[System] |
||||||
|
public void ShowUIButton(World world, ImGuiData _) |
||||||
|
{ |
||||||
|
var hasAnyInspector = false; |
||||||
|
var inspectorWindow = world.LookupByTypeOrThrow<InspectorWindow>(); |
||||||
|
foreach (var entity in Iterator.FromTerm(world, new(inspectorWindow))) |
||||||
|
{ hasAnyInspector = true; break; } |
||||||
|
|
||||||
|
if (ImGuiUtility.UIButton(0, Icon.Search, "Entity Inspector", hasAnyInspector)) |
||||||
|
NewEntityInspectorWindow(world); |
||||||
|
} |
||||||
|
|
||||||
|
[System] |
||||||
|
public void ShowExplorerWindow(EntityRef window, InspectorWindow _, History? history) |
||||||
|
{ |
||||||
|
var isOpen = true; |
||||||
|
var fontSize = ImGui.GetFontSize(); |
||||||
|
var viewCenter = ImGui.GetMainViewport().GetCenter(); |
||||||
|
ImGui.SetNextWindowPos(viewCenter, ImGuiCond.Appearing, new(0.5f, 0.5f)); |
||||||
|
ImGui.SetNextWindowSize(new(fontSize * 40, fontSize * 25), ImGuiCond.Appearing); |
||||||
|
ImGui.PushFont(ImGui.GetIO().Fonts.Fonts[1]); |
||||||
|
if (ImGui.Begin($"{Icon.Search} Entity Inspector##{window.Id}", |
||||||
|
ref isOpen, ImGuiWindowFlags.NoScrollbar)) { |
||||||
|
ImGui.PushFont(ImGui.GetIO().Fonts.Fonts[0]); |
||||||
|
|
||||||
|
var selected = window.GetTargets<Selected>().FirstOrDefault(); |
||||||
|
ActionBarAndPath(window, history, selected); |
||||||
|
|
||||||
|
ImGui.BeginTable("Views", 2, ImGuiTableFlags.Resizable); |
||||||
|
ImGui.TableSetupColumn("Explorer", ImGuiTableColumnFlags.WidthFixed, fontSize * 12); |
||||||
|
ImGui.TableSetupColumn("Entity", ImGuiTableColumnFlags.WidthStretch); |
||||||
|
|
||||||
|
ImGui.TableNextColumn(); |
||||||
|
ImGui.BeginChild("ExplorerView", new(-float.Epsilon, -float.Epsilon)); |
||||||
|
ExplorerView(window, history, selected); |
||||||
|
ImGui.EndChild(); |
||||||
|
|
||||||
|
void Tab(string name, Action<EntityRef, History?, EntityRef?> contentMethod) |
||||||
|
{ |
||||||
|
if (!ImGui.BeginTabItem(name)) return; |
||||||
|
ImGui.BeginChild($"{name}Tab", new(-float.Epsilon, -float.Epsilon)); |
||||||
|
contentMethod(window, history, selected); |
||||||
|
ImGui.EndChild(); |
||||||
|
ImGui.EndTabItem(); |
||||||
|
} |
||||||
|
|
||||||
|
ImGui.TableNextColumn(); |
||||||
|
ImGui.BeginChild("EntityView", new(-float.Epsilon, -float.Epsilon)); |
||||||
|
if (!ImGui.BeginTabBar("Tabs")) return; |
||||||
|
Tab($"{Icon.PencilSquare} Components", ComponentsTab); |
||||||
|
Tab($"{Icon.ShareAlt} References", ReferencesTab); |
||||||
|
Tab($"{Icon.InfoCircle} Documentation", DocumentationTab); |
||||||
|
ImGui.EndTabBar(); |
||||||
|
ImGui.EndChild(); |
||||||
|
|
||||||
|
ImGui.EndTable(); |
||||||
|
|
||||||
|
ImGui.PopFont(); |
||||||
|
} |
||||||
|
ImGui.PopFont(); |
||||||
|
ImGui.End(); |
||||||
|
|
||||||
|
// If window is closed, delete the entity. |
||||||
|
if (!isOpen) window.Delete(); |
||||||
|
} |
||||||
|
|
||||||
|
[Observer<ObserverEvent.OnRemove>] |
||||||
|
public void ClearStorageOnRemove(EntityRef _1, InspectorWindow _2) |
||||||
|
{ |
||||||
|
// TODO: Clear out settings store for the window. |
||||||
|
} |
||||||
|
|
||||||
|
private void ActionBarAndPath(EntityRef window, History? history, EntityRef? selected) |
||||||
|
{ |
||||||
|
var world = window.World; |
||||||
|
|
||||||
|
static bool IconButtonWithToolTip(string icon, string tooltip, bool enabled = true) { |
||||||
|
if (!enabled) ImGui.BeginDisabled(); |
||||||
|
var clicked = ImGui.Button(icon); |
||||||
|
if (!enabled) ImGui.EndDisabled(); |
||||||
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) |
||||||
|
ImGui.SetTooltip(tooltip); |
||||||
|
return clicked; |
||||||
|
} |
||||||
|
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(2, ImGui.GetStyle().ItemSpacing.Y)); |
||||||
|
ImGui.BeginTable("ActionBar", 3); |
||||||
|
ImGui.TableSetupColumn("Explorer", ImGuiTableColumnFlags.WidthFixed); |
||||||
|
ImGui.TableSetupColumn("Path", ImGuiTableColumnFlags.WidthStretch); |
||||||
|
ImGui.TableSetupColumn("Entity", ImGuiTableColumnFlags.WidthFixed); |
||||||
|
|
||||||
|
ImGui.TableNextColumn(); |
||||||
|
var hasExpanded = window.Has<Expanded, Core.Wildcard>(); |
||||||
|
if (IconButtonWithToolTip(Icon.Outdent, "Collapse all items in the Explorer View", hasExpanded)) |
||||||
|
window.Remove<Expanded, Core.Wildcard>(); |
||||||
|
|
||||||
|
if (history != null) { |
||||||
|
var hasPrev = ((selected != null) ? history.Current?.Prev : history.Current) != null; |
||||||
|
var hasNext = history.Current?.Next != null; |
||||||
|
ImGui.SameLine(); |
||||||
|
if (IconButtonWithToolTip(Icon.ArrowLeft, "Go to the previously viewed entity", hasPrev)) |
||||||
|
GoToPrevious(window, history, selected); |
||||||
|
ImGui.SameLine(); |
||||||
|
if (IconButtonWithToolTip(Icon.ArrowRight, "Go to the next viewed entity", hasNext)) |
||||||
|
GoToNext(window, history); |
||||||
|
} |
||||||
|
|
||||||
|
ImGui.SameLine(); |
||||||
|
if (IconButtonWithToolTip(Icon.Crosshairs, "Scroll to the current entity in the Explorer View", (selected != null))) |
||||||
|
window.Add<ScrollToSelected>(); |
||||||
|
|
||||||
|
ImGui.TableNextColumn(); |
||||||
|
var availableWidth = ImGui.GetColumnWidth() - ImGui.GetStyle().CellPadding.X * 2; |
||||||
|
PathInput(window, history, selected, availableWidth); |
||||||
|
|
||||||
|
ImGui.TableNextColumn(); |
||||||
|
if (IconButtonWithToolTip(Icon.PlusCircle, "Create a new child entity", (selected != null))) |
||||||
|
// FIXME: Replace this once Flecs has been fixed. |
||||||
|
SetSelected(window, history, world.New().Build().ChildOf(selected)); |
||||||
|
// SelectAndScrollTo(windowEntity, windowData, selected!.NewChild().Build(), selected); |
||||||
|
|
||||||
|
ImGui.SameLine(); |
||||||
|
if (IconButtonWithToolTip(Icon.Pencil, "Rename the current entity", false && (selected != null))) |
||||||
|
{ } // TODO: Implement this! |
||||||
|
|
||||||
|
ImGui.SameLine(); |
||||||
|
var isDisabled = (selected?.IsDisabled == true); |
||||||
|
var icon = !isDisabled ? Icon.BellSlash : Icon.Bell; |
||||||
|
var tooltip = $"{(!isDisabled ? "Disable" : "Enable")} the current entity"; |
||||||
|
if (IconButtonWithToolTip(icon, tooltip, (selected != null))) |
||||||
|
{ if (isDisabled) selected!.Enable(); else selected!.Disable(); } |
||||||
|
|
||||||
|
ImGui.SameLine(); |
||||||
|
if (IconButtonWithToolTip(Icon.Trash, "Delete the current entity", (selected != null))) { |
||||||
|
SetSelected(window, history, selected!.Parent); |
||||||
|
selected.Delete(); // TODO: Confirmation dialog? |
||||||
|
} |
||||||
|
|
||||||
|
ImGui.EndTable(); |
||||||
|
ImGui.PopStyleVar(); |
||||||
|
} |
||||||
|
|
||||||
|
private void PathInput(EntityRef window, History? history, EntityRef? selected, float availableWidth) |
||||||
|
{ |
||||||
|
var style = ImGui.GetStyle(); |
||||||
|
ImGui.AlignTextToFramePadding(); |
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(1, style.ItemSpacing.Y)); |
||||||
|
|
||||||
|
var path = selected?.GetFullPath() ?? null; |
||||||
|
|
||||||
|
if (path != null) { |
||||||
|
var visiblePath = path.GetParts().ToList(); |
||||||
|
var visiblePathTextSize = ImGui.CalcTextSize(path.ToString()).X |
||||||
|
+ style.ItemSpacing.X * 2 * (path.Count - 0.5f) |
||||||
|
+ style.FramePadding.X * 2 * path.Count; |
||||||
|
while ((visiblePath.Count > 3) && (visiblePathTextSize > availableWidth)) { |
||||||
|
if (visiblePath[1] != "...") { |
||||||
|
visiblePathTextSize -= ImGui.CalcTextSize(visiblePath[1]).X - ImGui.CalcTextSize("...").X; |
||||||
|
visiblePath[1] = "..."; |
||||||
|
} else { |
||||||
|
visiblePathTextSize -= ImGui.CalcTextSize(visiblePath[2] + "/").X |
||||||
|
+ (style.ItemSpacing.X + style.FramePadding.X) * 2; |
||||||
|
visiblePath.RemoveAt(2); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var numHiddenItems = path.Count - visiblePath.Count; |
||||||
|
for (var i = 0; i < visiblePath.Count - 1; i++) { |
||||||
|
var actualIndex = (i == 0) ? 0 : i + numHiddenItems; |
||||||
|
ImGui.Text("/"); |
||||||
|
ImGui.SameLine(); |
||||||
|
if (visiblePath[i] == "...") { |
||||||
|
ImGui.BeginDisabled(); |
||||||
|
ImGui.Button("..."); |
||||||
|
ImGui.EndDisabled(); |
||||||
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) |
||||||
|
ImGui.SetTooltip(path[1..(numHiddenItems+2)].ToString()); |
||||||
|
} else if (ImGui.Button(visiblePath[i])) |
||||||
|
SetSelected(window, history, window.World.LookupByPath(path[..(actualIndex+1)])); |
||||||
|
ImGui.SameLine(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ImGui.Text("/"); ImGui.SameLine(); |
||||||
|
var name = path?.Name.ToString() ?? ""; |
||||||
|
ImGui.SetNextItemWidth(-float.Epsilon); |
||||||
|
ImGui.InputText("##Path", ref name, 256); |
||||||
|
|
||||||
|
ImGui.PopStyleVar(); |
||||||
|
} |
||||||
|
|
||||||
|
private struct EntitySummary |
||||||
|
: IComparable<EntitySummary> |
||||||
|
{ |
||||||
|
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) |
||||||
|
{ |
||||||
|
static int? Compare<T>(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<T>.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<Expanded>(); |
||||||
|
|
||||||
|
List<EntitySummary> GetSummaries(Entity? parent) { |
||||||
|
var result = new List<EntitySummary>(); |
||||||
|
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<Core.Identifier>(2); |
||||||
|
var docNames = iter.FieldOrEmpty<Flecs.Doc.Description>(3); |
||||||
|
var docColors = iter.FieldOrEmpty<Flecs.Doc.Description>(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<Core.Component>(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; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (window.Has<ScrollToSelected>() && isSelected) { |
||||||
|
ImGui.SetScrollHereY(); |
||||||
|
window.Remove<ScrollToSelected>(); |
||||||
|
} |
||||||
|
|
||||||
|
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(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
foreach (var summary in GetSummaries(Entity.None)) |
||||||
|
EntryNode(summary); |
||||||
|
} |
||||||
|
|
||||||
|
private void ComponentsTab(EntityRef window, History? history, EntityRef? selected) |
||||||
|
{ |
||||||
|
if (selected == null) return; |
||||||
|
var ChildOf = window.World.LookupByTypeOrThrow<Core.ChildOf>(); |
||||||
|
foreach (var id in selected.Type) { |
||||||
|
// Hide ChildOf relations, as they are visible in the explorer. |
||||||
|
if (id.IsPair && (id.Id.RelationUnsafe == ChildOf)) continue; |
||||||
|
RenderIdentifier(window, history, id); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void ReferencesTab(EntityRef window, History? history, EntityRef? selected) |
||||||
|
{ |
||||||
|
if (selected == null) return; |
||||||
|
var world = window.World; |
||||||
|
var ChildOf = world.LookupByTypeOrThrow<Core.ChildOf>(); |
||||||
|
var Wildcard = world.LookupByTypeOrThrow<Core.Wildcard>(); |
||||||
|
|
||||||
|
if (ImGui.CollapsingHeader($"As {Icon.Tag} Entity", ImGuiTreeNodeFlags.DefaultOpen)) |
||||||
|
foreach (var iter in Iterator.FromTerm(world, new(selected))) |
||||||
|
for (var i = 0; i < iter.Count; i++) |
||||||
|
RenderEntity(window, history, iter.Entity(i)); |
||||||
|
|
||||||
|
if (ImGui.CollapsingHeader($"As {Icon.ShareAlt} Relation", ImGuiTreeNodeFlags.DefaultOpen)) |
||||||
|
foreach (var iter in Iterator.FromTerm(world, new(selected, Wildcard))) { |
||||||
|
var id = iter.FieldId(1); |
||||||
|
if (id.AsPair() is not (EntityRef relation, EntityRef target)) throw new InvalidOperationException(); |
||||||
|
if (relation == ChildOf) continue; // Hide ChildOf relations. |
||||||
|
|
||||||
|
for (var i = 0; i < iter.Count; i++) { |
||||||
|
RenderEntity(window, history, iter.Entity(i)); |
||||||
|
ImGui.SameLine(); ImGui.TextUnformatted("has"); ImGui.SameLine(); |
||||||
|
RenderIdentifier(window, history, id); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (ImGui.CollapsingHeader($"As {Icon.Bullseye} Target", ImGuiTreeNodeFlags.DefaultOpen)) |
||||||
|
foreach (var iter in Iterator.FromTerm(world, new(Wildcard, selected))) { |
||||||
|
var id = iter.FieldId(1); |
||||||
|
if (id.AsPair() is not (EntityRef relation, EntityRef target)) throw new InvalidOperationException(); |
||||||
|
if (relation == ChildOf) continue; // Hide ChildOf relations. |
||||||
|
|
||||||
|
for (var i = 0; i < iter.Count; i++) { |
||||||
|
RenderEntity(window, history, iter.Entity(i)); |
||||||
|
ImGui.SameLine(); ImGui.TextUnformatted("has"); ImGui.SameLine(); |
||||||
|
RenderIdentifier(window, history, id); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void DocumentationTab(EntityRef _1, History? _2, EntityRef? selected) |
||||||
|
{ |
||||||
|
var hasSelected = (selected != null); |
||||||
|
|
||||||
|
ImGui.BeginTable("Documentation", 2); |
||||||
|
ImGui.TableSetupColumn("Label", ImGuiTableColumnFlags.WidthFixed); |
||||||
|
ImGui.TableSetupColumn("Value", ImGuiTableColumnFlags.WidthStretch); |
||||||
|
|
||||||
|
static void Column(string label, string? tooltip, bool fill = true) { |
||||||
|
ImGui.TableNextColumn(); |
||||||
|
ImGui.AlignTextToFramePadding(); |
||||||
|
ImGui.TextUnformatted(label); |
||||||
|
if (ImGui.IsItemHovered() && (tooltip != null)) |
||||||
|
ImGui.SetTooltip(tooltip); |
||||||
|
ImGui.TableNextColumn(); |
||||||
|
if (fill) ImGui.SetNextItemWidth(-float.Epsilon); |
||||||
|
} |
||||||
|
|
||||||
|
Column($"{Icon.Tag} Display Name", """
|
||||||
|
A display name for this entity. |
||||||
|
Names in the entity hierarchy must be unique within the parent entity, |
||||||
|
This doesn't apply to display names - they are mostly informational. |
||||||
|
""");
|
||||||
|
if (!hasSelected) ImGui.BeginDisabled(); |
||||||
|
var name = selected?.GetDocName(false) ?? ""; |
||||||
|
if (ImGui.InputText("##Name", ref name, 256)) |
||||||
|
selected!.SetDocName((name.Length > 0) ? name : null); |
||||||
|
if (!hasSelected) ImGui.EndDisabled(); |
||||||
|
|
||||||
|
Column($"{Icon.Comment} Description", |
||||||
|
"A brief description of this entity."); |
||||||
|
if (!hasSelected) ImGui.BeginDisabled(); |
||||||
|
var brief = selected?.GetDocBrief() ?? ""; |
||||||
|
if (ImGui.InputText("##Brief", ref brief, 256)) |
||||||
|
selected!.SetDocBrief((brief.Length > 0) ? brief : null); |
||||||
|
if (!hasSelected) ImGui.EndDisabled(); |
||||||
|
|
||||||
|
Column($"{Icon.FileText} Documentation", """
|
||||||
|
A detailed description, or full documentation, of this entity's purpose and behaviors. |
||||||
|
It's encouraged to use multiple paragraphs and markdown formatting if necessary. |
||||||
|
""");
|
||||||
|
var cellPadding = ImGui.GetStyle().CellPadding.Y; |
||||||
|
var minHeight = ImGui.GetTextLineHeightWithSpacing() * 4; |
||||||
|
var availHeight = ImGui.GetContentRegionAvail().Y |
||||||
|
- (ImGui.GetFrameHeight() + cellPadding * 2) * 2 - cellPadding; |
||||||
|
if (!hasSelected) ImGui.BeginDisabled(); |
||||||
|
var detail = selected?.GetDocDetail() ?? ""; |
||||||
|
// TODO: Needs wordwrap. |
||||||
|
if (ImGui.InputTextMultiline("##Detail", ref detail, 2048, |
||||||
|
new(-float.Epsilon, Math.Max(minHeight, availHeight)))) |
||||||
|
selected!.SetDocDetail((detail.Length > 0) ? detail : null); |
||||||
|
if (!hasSelected) ImGui.EndDisabled(); |
||||||
|
|
||||||
|
Column($"{Icon.Link} Link", """
|
||||||
|
A link to a website relating to this entity, such as |
||||||
|
a module's repository, or further documentation. |
||||||
|
""");
|
||||||
|
if (!hasSelected) ImGui.BeginDisabled(); |
||||||
|
var link = selected?.GetDocLink() ?? ""; |
||||||
|
if (ImGui.InputText("##Link", ref link, 256)) |
||||||
|
selected!.SetDocLink((link.Length > 0) ? link : null); |
||||||
|
if (!hasSelected) ImGui.EndDisabled(); |
||||||
|
|
||||||
|
Column($"{Icon.PaintBrush} Color", """
|
||||||
|
A custom color to represent this entity. |
||||||
|
Used in the entity inspector's explorer view. |
||||||
|
""", false);
|
||||||
|
if (!hasSelected) ImGui.BeginDisabled(); |
||||||
|
var maybeColor = Color.TryParseHex(selected?.GetDocColor()); |
||||||
|
var hasColor = (maybeColor != null); |
||||||
|
var color = maybeColor ?? Color.White; |
||||||
|
if (ImGui.Checkbox("##HasColor", ref hasColor)) { |
||||||
|
if (hasColor) selected!.SetDocColor(color.ToHexString()); |
||||||
|
else selected!.SetDocColor(null); |
||||||
|
} |
||||||
|
ImGui.SameLine(); |
||||||
|
if (!hasColor) ImGui.BeginDisabled(); |
||||||
|
ImGui.SetNextItemWidth(-float.Epsilon); |
||||||
|
var colorVec = color.ToVector3(); |
||||||
|
if (ImGui.ColorEdit3("##Color", ref colorVec)) |
||||||
|
selected!.SetDocColor(Color.FromRGB(colorVec).ToHexString()); |
||||||
|
if (!hasColor) ImGui.EndDisabled(); |
||||||
|
if (!hasSelected) ImGui.EndDisabled(); |
||||||
|
|
||||||
|
ImGui.EndTable(); |
||||||
|
ImGui.EndTabItem(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// ======================= |
||||||
|
// == Utility Functions == |
||||||
|
// ======================= |
||||||
|
|
||||||
|
private EntityRef NewEntityInspectorWindow(World world) |
||||||
|
=> world.New().Add<InspectorWindow>().Set(new History()) |
||||||
|
.Build().SetDocName("Entity Inspector"); |
||||||
|
|
||||||
|
private void SetSelected( |
||||||
|
EntityRef window, // The InspectorWindow entity. |
||||||
|
History? history, // InspectorWindow's History component, null if it shouldn't be changed. |
||||||
|
EntityRef? entity, // Entity to set as selected or null to unset. |
||||||
|
bool scrollTo = true) // Should entity be scrolled to in the explorer view? |
||||||
|
{ |
||||||
|
if (entity != null) window.Add<Selected>(entity); |
||||||
|
else window.Remove<Selected, Core.Wildcard>(); |
||||||
|
|
||||||
|
for (var parent = entity?.Parent; parent != null; parent = parent.Parent) |
||||||
|
window.Add<Expanded>(parent); |
||||||
|
|
||||||
|
if ((entity != null) && scrollTo) |
||||||
|
window.Add<ScrollToSelected>(); |
||||||
|
|
||||||
|
if (history != null) { |
||||||
|
if (entity != null) history.Current = new History.Entry(entity, history.Current, null); |
||||||
|
else if (history.Current is History.Entry entry) entry.Next = null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void GoToPrevious(EntityRef window, History history, EntityRef? selected) |
||||||
|
{ |
||||||
|
if (selected != null) { |
||||||
|
if (history.Current?.Prev == null) return; |
||||||
|
history.Current = history.Current.Prev; |
||||||
|
} else if (history.Current == null) return; |
||||||
|
|
||||||
|
var entity = EntityRef.CreateOrNull(window.World, history.Current.Entity); |
||||||
|
SetSelected(window, null, entity); |
||||||
|
// TODO: Set path if entity could not be found. |
||||||
|
} |
||||||
|
|
||||||
|
private void GoToNext(EntityRef window, History history) |
||||||
|
{ |
||||||
|
if (history.Current?.Next == null) return; |
||||||
|
history.Current = history.Current.Next; |
||||||
|
|
||||||
|
var entity = EntityRef.CreateOrNull(window.World, history.Current.Entity); |
||||||
|
SetSelected(window, null, entity); |
||||||
|
// TODO: Set path if entity could not be found. |
||||||
|
} |
||||||
|
|
||||||
|
private Rule? _findDisplayTypeRule; |
||||||
|
private EntityRef? FindDisplayType(EntityRef entity) |
||||||
|
{ |
||||||
|
var world = entity.World; |
||||||
|
var component = world.LookupByTypeOrThrow<Core.Component>(); |
||||||
|
|
||||||
|
var rule = _findDisplayTypeRule ??= new Rule(world, new( |
||||||
|
$"$Type, gaemstone.Doc.DisplayType($Type)")); |
||||||
|
var typeVar = rule.Variables["Type"]!; |
||||||
|
|
||||||
|
var curType = (EntityRef?)null; |
||||||
|
var curPriority = float.MaxValue; |
||||||
|
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<Core.Component>(component)?.Size == 0)) |
||||||
|
type = world.LookupByTypeOrThrow<Core.Tag>(); |
||||||
|
var priority = type.GetOrNull<DocPriority>()?.Value ?? float.MaxValue; |
||||||
|
if (priority <= curPriority) { curType = type; curPriority = priority; } |
||||||
|
} |
||||||
|
|
||||||
|
return curType; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// ============================= |
||||||
|
// == Utility ImGui Functions == |
||||||
|
// ============================= |
||||||
|
|
||||||
|
private void RenderIdentifier(EntityRef window, History? history, IdentifierRef id) |
||||||
|
{ |
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(2, ImGui.GetStyle().ItemSpacing.Y)); |
||||||
|
if (id.AsPair() is (EntityRef relation, EntityRef target)) { |
||||||
|
ImGui.TextUnformatted("("); ImGui.SameLine(); |
||||||
|
RenderEntity(window, history, relation); |
||||||
|
ImGui.SameLine(); ImGui.TextUnformatted(","); ImGui.SameLine(); |
||||||
|
RenderEntity(window, history, target); |
||||||
|
ImGui.SameLine(); ImGui.TextUnformatted(")"); |
||||||
|
} else if (id.AsEntity() is EntityRef entity) |
||||||
|
RenderEntity(window, history, entity); |
||||||
|
else |
||||||
|
ImGui.TextUnformatted(id.ToString()); |
||||||
|
ImGui.PopStyleVar(); |
||||||
|
} |
||||||
|
|
||||||
|
private void RenderEntity(EntityRef window, History? history, EntityRef entity) |
||||||
|
{ |
||||||
|
var pos = ImGui.GetCursorScreenPos(); |
||||||
|
// Adjust based on AlignTextToFramePadding() or similar. |
||||||
|
pos.Y += ImGuiInternal.GetCurrentWindow().DC.CurrLineTextBaseOffset; |
||||||
|
|
||||||
|
// TODO: Calculate the size properly. |
||||||
|
var dummySize = new Vector2(20, ImGui.GetTextLineHeight()); |
||||||
|
if (!ImGui.IsRectVisible(pos, pos + dummySize)) { ImGui.Dummy(dummySize); return; } |
||||||
|
|
||||||
|
var displayType = FindDisplayType(entity); |
||||||
|
var docColor = Color.TryParseHex(entity.GetDocColor()) ?? Color.TryParseHex(displayType?.GetDocColor()); |
||||||
|
var docIcon = entity.GetOrNull<DocIcon>()?.Value.ToString() ?? displayType?.GetOrNull<DocIcon>()?.Value.ToString(); |
||||||
|
var docName = entity.GetDocName(false); |
||||||
|
|
||||||
|
var isDisabled = entity.IsDisabled; |
||||||
|
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 color = docColor ?? Color.FromRGBA(ImGui.GetColorU32(ImGuiCol.Text)); |
||||||
|
if (isDisabled) color = color.WithAlpha(ImGui.GetStyle().DisabledAlpha); |
||||||
|
|
||||||
|
var ctrl = ImGui.IsKeyDown(ImGuiKey.ModCtrl); |
||||||
|
var shift = ImGui.IsKeyDown(ImGuiKey.ModShift); |
||||||
|
if (ImGui.InvisibleButton(entity.Id.ToString(), size) && (ctrl || shift)) { |
||||||
|
if (shift) window = NewEntityInspectorWindow(window.World); |
||||||
|
SetSelected(window, history, entity); |
||||||
|
} |
||||||
|
|
||||||
|
var drawList = ImGui.GetWindowDrawList(); |
||||||
|
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)) { |
||||||
|
pos.Y -= 1.75f; |
||||||
|
drawList.AddLine(pos + new Vector2(0, size.Y), pos + size, color.RGBA); |
||||||
|
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); |
||||||
|
} |
||||||
|
|
||||||
|
if (ImGui.IsItemHovered()) { |
||||||
|
ImGui.BeginTooltip(); |
||||||
|
ImGui.PushFont(ImGui.GetIO().Fonts.Fonts[1]); |
||||||
|
ImGui.TextUnformatted(entity.GetFullPath().ToString()); |
||||||
|
ImGui.PopFont(); |
||||||
|
if (isDisabled) { |
||||||
|
ImGui.SameLine(); |
||||||
|
ImGui.BeginDisabled(); |
||||||
|
ImGui.TextUnformatted(" (disabled)"); |
||||||
|
ImGui.EndDisabled(); |
||||||
|
} |
||||||
|
if (entity.GetDocBrief() is string brief) ImGui.Text(brief); |
||||||
|
ImGui.EndTooltip(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
using gaemstone.Client.Utility; |
||||||
|
using gaemstone.ECS; |
||||||
|
using ImGuiNET; |
||||||
|
using static gaemstone.Client.Systems.ImGuiManager; |
||||||
|
|
||||||
|
namespace gaemstone.Client.Systems; |
||||||
|
|
||||||
|
[Module] |
||||||
|
[DependsOn<gaemstone.Client.Systems.ImGuiManager>] |
||||||
|
public class ImGuiDemoWindow |
||||||
|
{ |
||||||
|
private bool _isOpen = false; |
||||||
|
|
||||||
|
[System] |
||||||
|
public void Show(ImGuiData _) |
||||||
|
{ |
||||||
|
if (ImGuiUtility.UIButtonToggle(2, ForkAwesome.WindowMaximize, "ImGui Demo Window", ref _isOpen)) |
||||||
|
ImGui.ShowDemoWindow(ref _isOpen); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,819 @@ |
|||||||
|
namespace gaemstone.Client.Utility; |
||||||
|
|
||||||
|
public class ForkAwesome |
||||||
|
{ |
||||||
|
public const int Min = 0xf000; |
||||||
|
public const int Max = 0xf372; |
||||||
|
|
||||||
|
public const string Glass = "\uF000"; |
||||||
|
public const string Music = "\uF001"; |
||||||
|
public const string Search = "\uF002"; |
||||||
|
public const string EnvelopeO = "\uF003"; |
||||||
|
public const string Heart = "\uF004"; |
||||||
|
public const string Star = "\uF005"; |
||||||
|
public const string StarO = "\uF006"; |
||||||
|
public const string User = "\uF007"; |
||||||
|
public const string Film = "\uF008"; |
||||||
|
public const string ThLarge = "\uF009"; |
||||||
|
public const string Th = "\uF00A"; |
||||||
|
public const string ThList = "\uF00B"; |
||||||
|
public const string Check = "\uF00C"; |
||||||
|
public const string Times = "\uF00D"; |
||||||
|
public const string SearchPlus = "\uF00E"; |
||||||
|
public const string SearchMinus = "\uF010"; |
||||||
|
public const string PowerOff = "\uF011"; |
||||||
|
public const string Signal = "\uF012"; |
||||||
|
public const string Cog = "\uF013"; |
||||||
|
public const string TrashO = "\uF014"; |
||||||
|
public const string Home = "\uF015"; |
||||||
|
public const string FileO = "\uF016"; |
||||||
|
public const string ClockO = "\uF017"; |
||||||
|
public const string Road = "\uF018"; |
||||||
|
public const string Download = "\uF019"; |
||||||
|
public const string ArrowCircleODown = "\uF01A"; |
||||||
|
public const string ArrowCircleOUp = "\uF01B"; |
||||||
|
public const string Inbox = "\uF01C"; |
||||||
|
public const string PlayCircleO = "\uF01D"; |
||||||
|
public const string Repeat = "\uF01E"; |
||||||
|
public const string Refresh = "\uF021"; |
||||||
|
public const string ListAlt = "\uF022"; |
||||||
|
public const string Lock = "\uF023"; |
||||||
|
public const string Flag = "\uF024"; |
||||||
|
public const string Headphones = "\uF025"; |
||||||
|
public const string VolumeOff = "\uF026"; |
||||||
|
public const string VolumeDown = "\uF027"; |
||||||
|
public const string VolumeUp = "\uF028"; |
||||||
|
public const string Qrcode = "\uF029"; |
||||||
|
public const string Barcode = "\uF02A"; |
||||||
|
public const string Tag = "\uF02B"; |
||||||
|
public const string Tags = "\uF02C"; |
||||||
|
public const string Book = "\uF02D"; |
||||||
|
public const string Bookmark = "\uF02E"; |
||||||
|
public const string Print = "\uF02F"; |
||||||
|
public const string Camera = "\uF030"; |
||||||
|
public const string Font = "\uF031"; |
||||||
|
public const string Bold = "\uF032"; |
||||||
|
public const string Italic = "\uF033"; |
||||||
|
public const string TextHeight = "\uF034"; |
||||||
|
public const string TextWidth = "\uF035"; |
||||||
|
public const string AlignLeft = "\uF036"; |
||||||
|
public const string AlignCenter = "\uF037"; |
||||||
|
public const string AlignRight = "\uF038"; |
||||||
|
public const string AlignJustify = "\uF039"; |
||||||
|
public const string List = "\uF03A"; |
||||||
|
public const string Outdent = "\uF03B"; |
||||||
|
public const string Indent = "\uF03C"; |
||||||
|
public const string VideoCamera = "\uF03D"; |
||||||
|
public const string PictureO = "\uF03E"; |
||||||
|
public const string Pencil = "\uF040"; |
||||||
|
public const string MapMarker = "\uF041"; |
||||||
|
public const string Adjust = "\uF042"; |
||||||
|
public const string Tint = "\uF043"; |
||||||
|
public const string PencilSquareO = "\uF044"; |
||||||
|
public const string ShareSquareO = "\uF045"; |
||||||
|
public const string CheckSquareO = "\uF046"; |
||||||
|
public const string Arrows = "\uF047"; |
||||||
|
public const string StepBackward = "\uF048"; |
||||||
|
public const string FastBackward = "\uF049"; |
||||||
|
public const string Backward = "\uF04A"; |
||||||
|
public const string Play = "\uF04B"; |
||||||
|
public const string Pause = "\uF04C"; |
||||||
|
public const string Stop = "\uF04D"; |
||||||
|
public const string Forward = "\uF04E"; |
||||||
|
public const string FastForward = "\uF050"; |
||||||
|
public const string StepForward = "\uF051"; |
||||||
|
public const string Eject = "\uF052"; |
||||||
|
public const string ChevronLeft = "\uF053"; |
||||||
|
public const string ChevronRight = "\uF054"; |
||||||
|
public const string PlusCircle = "\uF055"; |
||||||
|
public const string MinusCircle = "\uF056"; |
||||||
|
public const string TimesCircle = "\uF057"; |
||||||
|
public const string CheckCircle = "\uF058"; |
||||||
|
public const string QuestionCircle = "\uF059"; |
||||||
|
public const string InfoCircle = "\uF05A"; |
||||||
|
public const string Crosshairs = "\uF05B"; |
||||||
|
public const string TimesCircleO = "\uF05C"; |
||||||
|
public const string CheckCircleO = "\uF05D"; |
||||||
|
public const string Ban = "\uF05E"; |
||||||
|
public const string ArrowLeft = "\uF060"; |
||||||
|
public const string ArrowRight = "\uF061"; |
||||||
|
public const string ArrowUp = "\uF062"; |
||||||
|
public const string ArrowDown = "\uF063"; |
||||||
|
public const string Share = "\uF064"; |
||||||
|
public const string Expand = "\uF065"; |
||||||
|
public const string Compress = "\uF066"; |
||||||
|
public const string Plus = "\uF067"; |
||||||
|
public const string Minus = "\uF068"; |
||||||
|
public const string Asterisk = "\uF069"; |
||||||
|
public const string ExclamationCircle = "\uF06A"; |
||||||
|
public const string Gift = "\uF06B"; |
||||||
|
public const string Leaf = "\uF06C"; |
||||||
|
public const string Fire = "\uF06D"; |
||||||
|
public const string Eye = "\uF06E"; |
||||||
|
public const string EyeSlash = "\uF070"; |
||||||
|
public const string ExclamationTriangle = "\uF071"; |
||||||
|
public const string Plane = "\uF072"; |
||||||
|
public const string Calendar = "\uF073"; |
||||||
|
public const string Random = "\uF074"; |
||||||
|
public const string Comment = "\uF075"; |
||||||
|
public const string Magnet = "\uF076"; |
||||||
|
public const string ChevronUp = "\uF077"; |
||||||
|
public const string ChevronDown = "\uF078"; |
||||||
|
public const string Retweet = "\uF079"; |
||||||
|
public const string ShoppingCart = "\uF07A"; |
||||||
|
public const string Folder = "\uF07B"; |
||||||
|
public const string FolderOpen = "\uF07C"; |
||||||
|
public const string ArrowsV = "\uF07D"; |
||||||
|
public const string ArrowsH = "\uF07E"; |
||||||
|
public const string BarChart = "\uF080"; |
||||||
|
public const string TwitterSquare = "\uF081"; |
||||||
|
public const string FacebookSquare = "\uF082"; |
||||||
|
public const string CameraRetro = "\uF083"; |
||||||
|
public const string Key = "\uF084"; |
||||||
|
public const string Cogs = "\uF085"; |
||||||
|
public const string Comments = "\uF086"; |
||||||
|
public const string ThumbsOUp = "\uF087"; |
||||||
|
public const string ThumbsODown = "\uF088"; |
||||||
|
public const string StarHalf = "\uF089"; |
||||||
|
public const string HeartO = "\uF08A"; |
||||||
|
public const string SignOut = "\uF08B"; |
||||||
|
public const string LinkedinSquare = "\uF08C"; |
||||||
|
public const string ThumbTack = "\uF08D"; |
||||||
|
public const string ExternalLink = "\uF08E"; |
||||||
|
public const string SignIn = "\uF090"; |
||||||
|
public const string Trophy = "\uF091"; |
||||||
|
public const string GithubSquare = "\uF092"; |
||||||
|
public const string Upload = "\uF093"; |
||||||
|
public const string LemonO = "\uF094"; |
||||||
|
public const string Phone = "\uF095"; |
||||||
|
public const string SquareO = "\uF096"; |
||||||
|
public const string BookmarkO = "\uF097"; |
||||||
|
public const string PhoneSquare = "\uF098"; |
||||||
|
public const string Twitter = "\uF099"; |
||||||
|
public const string Facebook = "\uF09A"; |
||||||
|
public const string Github = "\uF09B"; |
||||||
|
public const string Unlock = "\uF09C"; |
||||||
|
public const string CreditCard = "\uF09D"; |
||||||
|
public const string Rss = "\uF09E"; |
||||||
|
public const string HddO = "\uF0A0"; |
||||||
|
public const string Bullhorn = "\uF0A1"; |
||||||
|
public const string BellO = "\uF0F3"; |
||||||
|
public const string Certificate = "\uF0A3"; |
||||||
|
public const string HandORight = "\uF0A4"; |
||||||
|
public const string HandOLeft = "\uF0A5"; |
||||||
|
public const string HandOUp = "\uF0A6"; |
||||||
|
public const string HandODown = "\uF0A7"; |
||||||
|
public const string ArrowCircleLeft = "\uF0A8"; |
||||||
|
public const string ArrowCircleRight = "\uF0A9"; |
||||||
|
public const string ArrowCircleUp = "\uF0AA"; |
||||||
|
public const string ArrowCircleDown = "\uF0AB"; |
||||||
|
public const string Globe = "\uF0AC"; |
||||||
|
public const string GlobeE = "\uF304"; |
||||||
|
public const string GlobeW = "\uF305"; |
||||||
|
public const string Wrench = "\uF0AD"; |
||||||
|
public const string Tasks = "\uF0AE"; |
||||||
|
public const string Filter = "\uF0B0"; |
||||||
|
public const string Briefcase = "\uF0B1"; |
||||||
|
public const string ArrowsAlt = "\uF0B2"; |
||||||
|
public const string Users = "\uF0C0"; |
||||||
|
public const string Link = "\uF0C1"; |
||||||
|
public const string Cloud = "\uF0C2"; |
||||||
|
public const string Flask = "\uF0C3"; |
||||||
|
public const string Scissors = "\uF0C4"; |
||||||
|
public const string FilesO = "\uF0C5"; |
||||||
|
public const string Paperclip = "\uF0C6"; |
||||||
|
public const string FloppyO = "\uF0C7"; |
||||||
|
public const string Square = "\uF0C8"; |
||||||
|
public const string Bars = "\uF0C9"; |
||||||
|
public const string ListUl = "\uF0CA"; |
||||||
|
public const string ListOl = "\uF0CB"; |
||||||
|
public const string Strikethrough = "\uF0CC"; |
||||||
|
public const string Underline = "\uF0CD"; |
||||||
|
public const string Table = "\uF0CE"; |
||||||
|
public const string Magic = "\uF0D0"; |
||||||
|
public const string Truck = "\uF0D1"; |
||||||
|
public const string Pinterest = "\uF0D2"; |
||||||
|
public const string PinterestSquare = "\uF0D3"; |
||||||
|
public const string GooglePlusSquare = "\uF0D4"; |
||||||
|
public const string GooglePlus = "\uF0D5"; |
||||||
|
public const string Money = "\uF0D6"; |
||||||
|
public const string CaretDown = "\uF0D7"; |
||||||
|
public const string CaretUp = "\uF0D8"; |
||||||
|
public const string CaretLeft = "\uF0D9"; |
||||||
|
public const string CaretRight = "\uF0DA"; |
||||||
|
public const string Columns = "\uF0DB"; |
||||||
|
public const string Sort = "\uF0DC"; |
||||||
|
public const string SortDesc = "\uF0DD"; |
||||||
|
public const string SortAsc = "\uF0DE"; |
||||||
|
public const string Envelope = "\uF0E0"; |
||||||
|
public const string Linkedin = "\uF0E1"; |
||||||
|
public const string Undo = "\uF0E2"; |
||||||
|
public const string Gavel = "\uF0E3"; |
||||||
|
public const string Tachometer = "\uF0E4"; |
||||||
|
public const string CommentO = "\uF0E5"; |
||||||
|
public const string CommentsO = "\uF0E6"; |
||||||
|
public const string Bolt = "\uF0E7"; |
||||||
|
public const string Sitemap = "\uF0E8"; |
||||||
|
public const string Umbrella = "\uF0E9"; |
||||||
|
public const string Clipboard = "\uF0EA"; |
||||||
|
public const string LightbulbO = "\uF0EB"; |
||||||
|
public const string Exchange = "\uF0EC"; |
||||||
|
public const string CloudDownload = "\uF0ED"; |
||||||
|
public const string CloudUpload = "\uF0EE"; |
||||||
|
public const string UserMd = "\uF0F0"; |
||||||
|
public const string Stethoscope = "\uF0F1"; |
||||||
|
public const string Suitcase = "\uF0F2"; |
||||||
|
public const string Bell = "\uF0A2"; |
||||||
|
public const string Coffee = "\uF0F4"; |
||||||
|
public const string Cutlery = "\uF0F5"; |
||||||
|
public const string FileTextO = "\uF0F6"; |
||||||
|
public const string BuildingO = "\uF0F7"; |
||||||
|
public const string HospitalO = "\uF0F8"; |
||||||
|
public const string Ambulance = "\uF0F9"; |
||||||
|
public const string Medkit = "\uF0FA"; |
||||||
|
public const string FighterJet = "\uF0FB"; |
||||||
|
public const string Beer = "\uF0FC"; |
||||||
|
public const string HSquare = "\uF0FD"; |
||||||
|
public const string PlusSquare = "\uF0FE"; |
||||||
|
public const string AngleDoubleLeft = "\uF100"; |
||||||
|
public const string AngleDoubleRight = "\uF101"; |
||||||
|
public const string AngleDoubleUp = "\uF102"; |
||||||
|
public const string AngleDoubleDown = "\uF103"; |
||||||
|
public const string AngleLeft = "\uF104"; |
||||||
|
public const string AngleRight = "\uF105"; |
||||||
|
public const string AngleUp = "\uF106"; |
||||||
|
public const string AngleDown = "\uF107"; |
||||||
|
public const string Desktop = "\uF108"; |
||||||
|
public const string Laptop = "\uF109"; |
||||||
|
public const string Tablet = "\uF10A"; |
||||||
|
public const string Mobile = "\uF10B"; |
||||||
|
public const string CircleO = "\uF10C"; |
||||||
|
public const string QuoteLeft = "\uF10D"; |
||||||
|
public const string QuoteRight = "\uF10E"; |
||||||
|
public const string Spinner = "\uF110"; |
||||||
|
public const string Circle = "\uF111"; |
||||||
|
public const string Reply = "\uF112"; |
||||||
|
public const string GithubAlt = "\uF113"; |
||||||
|
public const string FolderO = "\uF114"; |
||||||
|
public const string FolderOpenO = "\uF115"; |
||||||
|
public const string SmileO = "\uF118"; |
||||||
|
public const string FrownO = "\uF119"; |
||||||
|
public const string MehO = "\uF11A"; |
||||||
|
public const string Gamepad = "\uF11B"; |
||||||
|
public const string KeyboardO = "\uF11C"; |
||||||
|
public const string FlagO = "\uF11D"; |
||||||
|
public const string FlagCheckered = "\uF11E"; |
||||||
|
public const string Terminal = "\uF120"; |
||||||
|
public const string Code = "\uF121"; |
||||||
|
public const string ReplyAll = "\uF122"; |
||||||
|
public const string StarHalfO = "\uF123"; |
||||||
|
public const string LocationArrow = "\uF124"; |
||||||
|
public const string Crop = "\uF125"; |
||||||
|
public const string CodeFork = "\uF126"; |
||||||
|
public const string ChainBroken = "\uF127"; |
||||||
|
public const string Question = "\uF128"; |
||||||
|
public const string Info = "\uF129"; |
||||||
|
public const string Exclamation = "\uF12A"; |
||||||
|
public const string Superscript = "\uF12B"; |
||||||
|
public const string Subscript = "\uF12C"; |
||||||
|
public const string Eraser = "\uF12D"; |
||||||
|
public const string PuzzlePiece = "\uF12E"; |
||||||
|
public const string Microphone = "\uF130"; |
||||||
|
public const string MicrophoneSlash = "\uF131"; |
||||||
|
public const string Shield = "\uF132"; |
||||||
|
public const string CalendarO = "\uF133"; |
||||||
|
public const string FireExtinguisher = "\uF134"; |
||||||
|
public const string Rocket = "\uF135"; |
||||||
|
public const string Maxcdn = "\uF136"; |
||||||
|
public const string ChevronCircleLeft = "\uF137"; |
||||||
|
public const string ChevronCircleRight = "\uF138"; |
||||||
|
public const string ChevronCircleUp = "\uF139"; |
||||||
|
public const string ChevronCircleDown = "\uF13A"; |
||||||
|
public const string Html5 = "\uF13B"; |
||||||
|
public const string Css3 = "\uF13C"; |
||||||
|
public const string Anchor = "\uF13D"; |
||||||
|
public const string UnlockAlt = "\uF13E"; |
||||||
|
public const string Bullseye = "\uF140"; |
||||||
|
public const string EllipsisH = "\uF141"; |
||||||
|
public const string EllipsisV = "\uF142"; |
||||||
|
public const string RssSquare = "\uF143"; |
||||||
|
public const string PlayCircle = "\uF144"; |
||||||
|
public const string Ticket = "\uF145"; |
||||||
|
public const string MinusSquare = "\uF146"; |
||||||
|
public const string MinusSquareO = "\uF147"; |
||||||
|
public const string LevelUp = "\uF148"; |
||||||
|
public const string LevelDown = "\uF149"; |
||||||
|
public const string CheckSquare = "\uF14A"; |
||||||
|
public const string PencilSquare = "\uF14B"; |
||||||
|
public const string ExternalLinkSquare = "\uF14C"; |
||||||
|
public const string ShareSquare = "\uF14D"; |
||||||
|
public const string Compass = "\uF14E"; |
||||||
|
public const string CaretSquareODown = "\uF150"; |
||||||
|
public const string CaretSquareOUp = "\uF151"; |
||||||
|
public const string CaretSquareORight = "\uF152"; |
||||||
|
public const string Eur = "\uF153"; |
||||||
|
public const string Gbp = "\uF154"; |
||||||
|
public const string Usd = "\uF155"; |
||||||
|
public const string Inr = "\uF156"; |
||||||
|
public const string Jpy = "\uF157"; |
||||||
|
public const string Rub = "\uF158"; |
||||||
|
public const string Krw = "\uF159"; |
||||||
|
public const string Btc = "\uF15A"; |
||||||
|
public const string File = "\uF15B"; |
||||||
|
public const string FileText = "\uF15C"; |
||||||
|
public const string SortAlphaAsc = "\uF15D"; |
||||||
|
public const string SortAlphaDesc = "\uF15E"; |
||||||
|
public const string SortAmountAsc = "\uF160"; |
||||||
|
public const string SortAmountDesc = "\uF161"; |
||||||
|
public const string SortNumericAsc = "\uF162"; |
||||||
|
public const string SortNumericDesc = "\uF163"; |
||||||
|
public const string ThumbsUp = "\uF164"; |
||||||
|
public const string ThumbsDown = "\uF165"; |
||||||
|
public const string YoutubeSquare = "\uF166"; |
||||||
|
public const string Youtube = "\uF167"; |
||||||
|
public const string Xing = "\uF168"; |
||||||
|
public const string XingSquare = "\uF169"; |
||||||
|
public const string YoutubePlay = "\uF16A"; |
||||||
|
public const string Dropbox = "\uF16B"; |
||||||
|
public const string StackOverflow = "\uF16C"; |
||||||
|
public const string Instagram = "\uF16D"; |
||||||
|
public const string Flickr = "\uF16E"; |
||||||
|
public const string Adn = "\uF170"; |
||||||
|
public const string Bitbucket = "\uF171"; |
||||||
|
public const string BitbucketSquare = "\uF172"; |
||||||
|
public const string Tumblr = "\uF173"; |
||||||
|
public const string TumblrSquare = "\uF174"; |
||||||
|
public const string LongArrowDown = "\uF175"; |
||||||
|
public const string LongArrowUp = "\uF176"; |
||||||
|
public const string LongArrowLeft = "\uF177"; |
||||||
|
public const string LongArrowRight = "\uF178"; |
||||||
|
public const string Apple = "\uF179"; |
||||||
|
public const string Windows = "\uF17A"; |
||||||
|
public const string Android = "\uF17B"; |
||||||
|
public const string Linux = "\uF17C"; |
||||||
|
public const string Dribbble = "\uF17D"; |
||||||
|
public const string Skype = "\uF17E"; |
||||||
|
public const string Foursquare = "\uF180"; |
||||||
|
public const string Trello = "\uF181"; |
||||||
|
public const string Female = "\uF182"; |
||||||
|
public const string Male = "\uF183"; |
||||||
|
public const string Gratipay = "\uF184"; |
||||||
|
public const string SunO = "\uF185"; |
||||||
|
public const string MoonO = "\uF186"; |
||||||
|
public const string Archive = "\uF187"; |
||||||
|
public const string Bug = "\uF188"; |
||||||
|
public const string Vk = "\uF189"; |
||||||
|
public const string Weibo = "\uF18A"; |
||||||
|
public const string Renren = "\uF18B"; |
||||||
|
public const string Pagelines = "\uF18C"; |
||||||
|
public const string StackExchange = "\uF18D"; |
||||||
|
public const string ArrowCircleORight = "\uF18E"; |
||||||
|
public const string ArrowCircleOLeft = "\uF190"; |
||||||
|
public const string CaretSquareOLeft = "\uF191"; |
||||||
|
public const string DotCircleO = "\uF192"; |
||||||
|
public const string Wheelchair = "\uF193"; |
||||||
|
public const string VimeoSquare = "\uF194"; |
||||||
|
public const string Try = "\uF195"; |
||||||
|
public const string PlusSquareO = "\uF196"; |
||||||
|
public const string SpaceShuttle = "\uF197"; |
||||||
|
public const string Slack = "\uF198"; |
||||||
|
public const string EnvelopeSquare = "\uF199"; |
||||||
|
public const string Wordpress = "\uF19A"; |
||||||
|
public const string Openid = "\uF19B"; |
||||||
|
public const string University = "\uF19C"; |
||||||
|
public const string GraduationCap = "\uF19D"; |
||||||
|
public const string Yahoo = "\uF19E"; |
||||||
|
public const string Google = "\uF1A0"; |
||||||
|
public const string Reddit = "\uF1A1"; |
||||||
|
public const string RedditSquare = "\uF1A2"; |
||||||
|
public const string StumbleuponCircle = "\uF1A3"; |
||||||
|
public const string Stumbleupon = "\uF1A4"; |
||||||
|
public const string Delicious = "\uF1A5"; |
||||||
|
public const string Digg = "\uF1A6"; |
||||||
|
public const string Drupal = "\uF1A9"; |
||||||
|
public const string Joomla = "\uF1AA"; |
||||||
|
public const string Language = "\uF1AB"; |
||||||
|
public const string Fax = "\uF1AC"; |
||||||
|
public const string Building = "\uF1AD"; |
||||||
|
public const string Child = "\uF1AE"; |
||||||
|
public const string Paw = "\uF1B0"; |
||||||
|
public const string Spoon = "\uF1B1"; |
||||||
|
public const string Cube = "\uF1B2"; |
||||||
|
public const string Cubes = "\uF1B3"; |
||||||
|
public const string Behance = "\uF1B4"; |
||||||
|
public const string BehanceSquare = "\uF1B5"; |
||||||
|
public const string Steam = "\uF1B6"; |
||||||
|
public const string SteamSquare = "\uF1B7"; |
||||||
|
public const string Recycle = "\uF1B8"; |
||||||
|
public const string Car = "\uF1B9"; |
||||||
|
public const string Taxi = "\uF1BA"; |
||||||
|
public const string Tree = "\uF1BB"; |
||||||
|
public const string Spotify = "\uF1BC"; |
||||||
|
public const string Deviantart = "\uF1BD"; |
||||||
|
public const string Soundcloud = "\uF1BE"; |
||||||
|
public const string Database = "\uF1C0"; |
||||||
|
public const string FilePdfO = "\uF1C1"; |
||||||
|
public const string FileWordO = "\uF1C2"; |
||||||
|
public const string FileExcelO = "\uF1C3"; |
||||||
|
public const string FilePowerpointO = "\uF1C4"; |
||||||
|
public const string FileImageO = "\uF1C5"; |
||||||
|
public const string FileArchiveO = "\uF1C6"; |
||||||
|
public const string FileAudioO = "\uF1C7"; |
||||||
|
public const string FileVideoO = "\uF1C8"; |
||||||
|
public const string FileCodeO = "\uF1C9"; |
||||||
|
public const string Vine = "\uF1CA"; |
||||||
|
public const string Codepen = "\uF1CB"; |
||||||
|
public const string Jsfiddle = "\uF1CC"; |
||||||
|
public const string LifeRing = "\uF1CD"; |
||||||
|
public const string CircleONotch = "\uF1CE"; |
||||||
|
public const string Rebel = "\uF1D0"; |
||||||
|
public const string Empire = "\uF1D1"; |
||||||
|
public const string GitSquare = "\uF1D2"; |
||||||
|
public const string Git = "\uF1D3"; |
||||||
|
public const string HackerNews = "\uF1D4"; |
||||||
|
public const string TencentWeibo = "\uF1D5"; |
||||||
|
public const string Qq = "\uF1D6"; |
||||||
|
public const string Weixin = "\uF1D7"; |
||||||
|
public const string PaperPlane = "\uF1D8"; |
||||||
|
public const string PaperPlaneO = "\uF1D9"; |
||||||
|
public const string History = "\uF1DA"; |
||||||
|
public const string CircleThin = "\uF1DB"; |
||||||
|
public const string Header = "\uF1DC"; |
||||||
|
public const string Paragraph = "\uF1DD"; |
||||||
|
public const string Sliders = "\uF1DE"; |
||||||
|
public const string ShareAlt = "\uF1E0"; |
||||||
|
public const string ShareAltSquare = "\uF1E1"; |
||||||
|
public const string Bomb = "\uF1E2"; |
||||||
|
public const string FutbolO = "\uF1E3"; |
||||||
|
public const string Tty = "\uF1E4"; |
||||||
|
public const string Binoculars = "\uF1E5"; |
||||||
|
public const string Plug = "\uF1E6"; |
||||||
|
public const string Slideshare = "\uF1E7"; |
||||||
|
public const string Twitch = "\uF1E8"; |
||||||
|
public const string Yelp = "\uF1E9"; |
||||||
|
public const string NewspaperO = "\uF1EA"; |
||||||
|
public const string Wifi = "\uF1EB"; |
||||||
|
public const string Calculator = "\uF1EC"; |
||||||
|
public const string Paypal = "\uF1ED"; |
||||||
|
public const string GoogleWallet = "\uF1EE"; |
||||||
|
public const string CcVisa = "\uF1F0"; |
||||||
|
public const string CcMastercard = "\uF1F1"; |
||||||
|
public const string CcDiscover = "\uF1F2"; |
||||||
|
public const string CcAmex = "\uF1F3"; |
||||||
|
public const string CcPaypal = "\uF1F4"; |
||||||
|
public const string CcStripe = "\uF1F5"; |
||||||
|
public const string BellSlash = "\uF1F6"; |
||||||
|
public const string BellSlashO = "\uF1F7"; |
||||||
|
public const string Trash = "\uF1F8"; |
||||||
|
public const string Copyright = "\uF1F9"; |
||||||
|
public const string At = "\uF1FA"; |
||||||
|
public const string Eyedropper = "\uF1FB"; |
||||||
|
public const string PaintBrush = "\uF1FC"; |
||||||
|
public const string BirthdayCake = "\uF1FD"; |
||||||
|
public const string AreaChart = "\uF1FE"; |
||||||
|
public const string PieChart = "\uF200"; |
||||||
|
public const string LineChart = "\uF201"; |
||||||
|
public const string Lastfm = "\uF202"; |
||||||
|
public const string LastfmSquare = "\uF203"; |
||||||
|
public const string ToggleOff = "\uF204"; |
||||||
|
public const string ToggleOn = "\uF205"; |
||||||
|
public const string Bicycle = "\uF206"; |
||||||
|
public const string Bus = "\uF207"; |
||||||
|
public const string Ioxhost = "\uF208"; |
||||||
|
public const string Angellist = "\uF209"; |
||||||
|
public const string Cc = "\uF20A"; |
||||||
|
public const string Ils = "\uF20B"; |
||||||
|
public const string Meanpath = "\uF20C"; |
||||||
|
public const string Buysellads = "\uF20D"; |
||||||
|
public const string Connectdevelop = "\uF20E"; |
||||||
|
public const string Dashcube = "\uF210"; |
||||||
|
public const string Forumbee = "\uF211"; |
||||||
|
public const string Leanpub = "\uF212"; |
||||||
|
public const string Sellsy = "\uF213"; |
||||||
|
public const string Shirtsinbulk = "\uF214"; |
||||||
|
public const string Simplybuilt = "\uF215"; |
||||||
|
public const string Skyatlas = "\uF216"; |
||||||
|
public const string CartPlus = "\uF217"; |
||||||
|
public const string CartArrowDown = "\uF218"; |
||||||
|
public const string Diamond = "\uF219"; |
||||||
|
public const string Ship = "\uF21A"; |
||||||
|
public const string UserSecret = "\uF21B"; |
||||||
|
public const string Motorcycle = "\uF21C"; |
||||||
|
public const string StreetView = "\uF21D"; |
||||||
|
public const string Heartbeat = "\uF21E"; |
||||||
|
public const string Venus = "\uF221"; |
||||||
|
public const string Mars = "\uF222"; |
||||||
|
public const string Mercury = "\uF223"; |
||||||
|
public const string Transgender = "\uF224"; |
||||||
|
public const string TransgenderAlt = "\uF225"; |
||||||
|
public const string VenusDouble = "\uF226"; |
||||||
|
public const string MarsDouble = "\uF227"; |
||||||
|
public const string VenusMars = "\uF228"; |
||||||
|
public const string MarsStroke = "\uF229"; |
||||||
|
public const string MarsStrokeV = "\uF22A"; |
||||||
|
public const string MarsStrokeH = "\uF22B"; |
||||||
|
public const string Neuter = "\uF22C"; |
||||||
|
public const string Genderless = "\uF22D"; |
||||||
|
public const string FacebookOfficial = "\uF230"; |
||||||
|
public const string PinterestP = "\uF231"; |
||||||
|
public const string Whatsapp = "\uF232"; |
||||||
|
public const string Server = "\uF233"; |
||||||
|
public const string UserPlus = "\uF234"; |
||||||
|
public const string UserTimes = "\uF235"; |
||||||
|
public const string Bed = "\uF236"; |
||||||
|
public const string Viacoin = "\uF237"; |
||||||
|
public const string Train = "\uF238"; |
||||||
|
public const string Subway = "\uF239"; |
||||||
|
public const string Medium = "\uF23A"; |
||||||
|
public const string MediumSquare = "\uF2F8"; |
||||||
|
public const string YCombinator = "\uF23B"; |
||||||
|
public const string OptinMonster = "\uF23C"; |
||||||
|
public const string Opencart = "\uF23D"; |
||||||
|
public const string Expeditedssl = "\uF23E"; |
||||||
|
public const string BatteryFull = "\uF240"; |
||||||
|
public const string BatteryThreeQuarters = "\uF241"; |
||||||
|
public const string BatteryHalf = "\uF242"; |
||||||
|
public const string BatteryQuarter = "\uF243"; |
||||||
|
public const string BatteryEmpty = "\uF244"; |
||||||
|
public const string MousePointer = "\uF245"; |
||||||
|
public const string ICursor = "\uF246"; |
||||||
|
public const string ObjectGroup = "\uF247"; |
||||||
|
public const string ObjectUngroup = "\uF248"; |
||||||
|
public const string StickyNote = "\uF249"; |
||||||
|
public const string StickyNoteO = "\uF24A"; |
||||||
|
public const string CcJcb = "\uF24B"; |
||||||
|
public const string CcDinersClub = "\uF24C"; |
||||||
|
public const string Clone = "\uF24D"; |
||||||
|
public const string BalanceScale = "\uF24E"; |
||||||
|
public const string HourglassO = "\uF250"; |
||||||
|
public const string HourglassStart = "\uF251"; |
||||||
|
public const string HourglassHalf = "\uF252"; |
||||||
|
public const string HourglassEnd = "\uF253"; |
||||||
|
public const string Hourglass = "\uF254"; |
||||||
|
public const string HandRockO = "\uF255"; |
||||||
|
public const string HandPaperO = "\uF256"; |
||||||
|
public const string HandScissorsO = "\uF257"; |
||||||
|
public const string HandLizardO = "\uF258"; |
||||||
|
public const string HandSpockO = "\uF259"; |
||||||
|
public const string HandPointerO = "\uF25A"; |
||||||
|
public const string HandPeaceO = "\uF25B"; |
||||||
|
public const string Trademark = "\uF25C"; |
||||||
|
public const string Registered = "\uF25D"; |
||||||
|
public const string CreativeCommons = "\uF25E"; |
||||||
|
public const string Gg = "\uF260"; |
||||||
|
public const string GgCircle = "\uF261"; |
||||||
|
public const string Tripadvisor = "\uF262"; |
||||||
|
public const string Odnoklassniki = "\uF263"; |
||||||
|
public const string OdnoklassnikiSquare = "\uF264"; |
||||||
|
public const string GetPocket = "\uF265"; |
||||||
|
public const string WikipediaW = "\uF266"; |
||||||
|
public const string Safari = "\uF267"; |
||||||
|
public const string Chrome = "\uF268"; |
||||||
|
public const string Firefox = "\uF269"; |
||||||
|
public const string Opera = "\uF26A"; |
||||||
|
public const string InternetExplorer = "\uF26B"; |
||||||
|
public const string Television = "\uF26C"; |
||||||
|
public const string Contao = "\uF26D"; |
||||||
|
public const string Num500px = "\uF26E"; |
||||||
|
public const string Amazon = "\uF270"; |
||||||
|
public const string CalendarPlusO = "\uF271"; |
||||||
|
public const string CalendarMinusO = "\uF272"; |
||||||
|
public const string CalendarTimesO = "\uF273"; |
||||||
|
public const string CalendarCheckO = "\uF274"; |
||||||
|
public const string Industry = "\uF275"; |
||||||
|
public const string MapPin = "\uF276"; |
||||||
|
public const string MapSigns = "\uF277"; |
||||||
|
public const string MapO = "\uF278"; |
||||||
|
public const string Map = "\uF279"; |
||||||
|
public const string Commenting = "\uF27A"; |
||||||
|
public const string CommentingO = "\uF27B"; |
||||||
|
public const string Houzz = "\uF27C"; |
||||||
|
public const string Vimeo = "\uF27D"; |
||||||
|
public const string BlackTie = "\uF27E"; |
||||||
|
public const string Fonticons = "\uF280"; |
||||||
|
public const string RedditAlien = "\uF281"; |
||||||
|
public const string Edge = "\uF282"; |
||||||
|
public const string CreditCardAlt = "\uF283"; |
||||||
|
public const string Codiepie = "\uF284"; |
||||||
|
public const string Modx = "\uF285"; |
||||||
|
public const string FortAwesome = "\uF286"; |
||||||
|
public const string Usb = "\uF287"; |
||||||
|
public const string ProductHunt = "\uF288"; |
||||||
|
public const string Mixcloud = "\uF289"; |
||||||
|
public const string Scribd = "\uF28A"; |
||||||
|
public const string PauseCircle = "\uF28B"; |
||||||
|
public const string PauseCircleO = "\uF28C"; |
||||||
|
public const string StopCircle = "\uF28D"; |
||||||
|
public const string StopCircleO = "\uF28E"; |
||||||
|
public const string ShoppingBag = "\uF290"; |
||||||
|
public const string ShoppingBasket = "\uF291"; |
||||||
|
public const string Hashtag = "\uF292"; |
||||||
|
public const string Bluetooth = "\uF293"; |
||||||
|
public const string BluetoothB = "\uF294"; |
||||||
|
public const string Percent = "\uF295"; |
||||||
|
public const string Gitlab = "\uF296"; |
||||||
|
public const string Wpbeginner = "\uF297"; |
||||||
|
public const string Wpforms = "\uF298"; |
||||||
|
public const string Envira = "\uF299"; |
||||||
|
public const string UniversalAccess = "\uF29A"; |
||||||
|
public const string WheelchairAlt = "\uF29B"; |
||||||
|
public const string QuestionCircleO = "\uF29C"; |
||||||
|
public const string Blind = "\uF29D"; |
||||||
|
public const string AudioDescription = "\uF29E"; |
||||||
|
public const string VolumeControlPhone = "\uF2A0"; |
||||||
|
public const string Braille = "\uF2A1"; |
||||||
|
public const string AssistiveListeningSystems = "\uF2A2"; |
||||||
|
public const string AmericanSignLanguageInterpreting = "\uF2A3"; |
||||||
|
public const string Deaf = "\uF2A4"; |
||||||
|
public const string Glide = "\uF2A5"; |
||||||
|
public const string GlideG = "\uF2A6"; |
||||||
|
public const string SignLanguage = "\uF2A7"; |
||||||
|
public const string LowVision = "\uF2A8"; |
||||||
|
public const string Viadeo = "\uF2A9"; |
||||||
|
public const string ViadeoSquare = "\uF2AA"; |
||||||
|
public const string Snapchat = "\uF2AB"; |
||||||
|
public const string SnapchatGhost = "\uF2AC"; |
||||||
|
public const string SnapchatSquare = "\uF2AD"; |
||||||
|
public const string FirstOrder = "\uF2B0"; |
||||||
|
public const string Yoast = "\uF2B1"; |
||||||
|
public const string Themeisle = "\uF2B2"; |
||||||
|
public const string GooglePlusOfficial = "\uF2B3"; |
||||||
|
public const string FontAwesome = "\uF2B4"; |
||||||
|
public const string HandshakeO = "\uF2B5"; |
||||||
|
public const string EnvelopeOpen = "\uF2B6"; |
||||||
|
public const string EnvelopeOpenO = "\uF2B7"; |
||||||
|
public const string Linode = "\uF2B8"; |
||||||
|
public const string AddressBook = "\uF2B9"; |
||||||
|
public const string AddressBookO = "\uF2BA"; |
||||||
|
public const string AddressCard = "\uF2BB"; |
||||||
|
public const string AddressCardO = "\uF2BC"; |
||||||
|
public const string UserCircle = "\uF2BD"; |
||||||
|
public const string UserCircleO = "\uF2BE"; |
||||||
|
public const string UserO = "\uF2C0"; |
||||||
|
public const string IdBadge = "\uF2C1"; |
||||||
|
public const string IdCard = "\uF2C2"; |
||||||
|
public const string IdCardO = "\uF2C3"; |
||||||
|
public const string Quora = "\uF2C4"; |
||||||
|
public const string FreeCodeCamp = "\uF2C5"; |
||||||
|
public const string Telegram = "\uF2C6"; |
||||||
|
public const string ThermometerFull = "\uF2C7"; |
||||||
|
public const string ThermometerThreeQuarters = "\uF2C8"; |
||||||
|
public const string ThermometerHalf = "\uF2C9"; |
||||||
|
public const string ThermometerQuarter = "\uF2CA"; |
||||||
|
public const string ThermometerEmpty = "\uF2CB"; |
||||||
|
public const string Shower = "\uF2CC"; |
||||||
|
public const string Bath = "\uF2CD"; |
||||||
|
public const string Podcast = "\uF2CE"; |
||||||
|
public const string WindowMaximize = "\uF2D0"; |
||||||
|
public const string WindowMinimize = "\uF2D1"; |
||||||
|
public const string WindowRestore = "\uF2D2"; |
||||||
|
public const string WindowClose = "\uF2D3"; |
||||||
|
public const string WindowCloseO = "\uF2D4"; |
||||||
|
public const string Bandcamp = "\uF2D5"; |
||||||
|
public const string Grav = "\uF2D6"; |
||||||
|
public const string Etsy = "\uF2D7"; |
||||||
|
public const string Imdb = "\uF2D8"; |
||||||
|
public const string Ravelry = "\uF2D9"; |
||||||
|
public const string Eercast = "\uF2DA"; |
||||||
|
public const string Microchip = "\uF2DB"; |
||||||
|
public const string SnowflakeO = "\uF2DC"; |
||||||
|
public const string Superpowers = "\uF2DD"; |
||||||
|
public const string Wpexplorer = "\uF2DE"; |
||||||
|
public const string Meetup = "\uF2E0"; |
||||||
|
public const string Mastodon = "\uF2E1"; |
||||||
|
public const string MastodonAlt = "\uF2E2"; |
||||||
|
public const string ForkAwesomeIcon = "\uF2E3"; |
||||||
|
public const string Peertube = "\uF2E4"; |
||||||
|
public const string Diaspora = "\uF2E5"; |
||||||
|
public const string Friendica = "\uF2E6"; |
||||||
|
public const string GnuSocial = "\uF2E7"; |
||||||
|
public const string LiberapaySquare = "\uF2E8"; |
||||||
|
public const string Liberapay = "\uF2E9"; |
||||||
|
public const string Scuttlebutt = "\uF2EA"; |
||||||
|
public const string Hubzilla = "\uF2EB"; |
||||||
|
public const string SocialHome = "\uF2EC"; |
||||||
|
public const string Artstation = "\uF2ED"; |
||||||
|
public const string Discord = "\uF2EE"; |
||||||
|
public const string DiscordAlt = "\uF2EF"; |
||||||
|
public const string Patreon = "\uF2F0"; |
||||||
|
public const string Snowdrift = "\uF2F1"; |
||||||
|
public const string Activitypub = "\uF2F2"; |
||||||
|
public const string Ethereum = "\uF2F3"; |
||||||
|
public const string Keybase = "\uF2F4"; |
||||||
|
public const string Shaarli = "\uF2F5"; |
||||||
|
public const string ShaarliO = "\uF2F6"; |
||||||
|
public const string KeyModern = "\uF2F7"; |
||||||
|
public const string Xmpp = "\uF2F9"; |
||||||
|
public const string ArchiveOrg = "\uF2FC"; |
||||||
|
public const string Freedombox = "\uF2FD"; |
||||||
|
public const string FacebookMessenger = "\uF2FE"; |
||||||
|
public const string Debian = "\uF2FF"; |
||||||
|
public const string MastodonSquare = "\uF300"; |
||||||
|
public const string Tipeee = "\uF301"; |
||||||
|
public const string React = "\uF302"; |
||||||
|
public const string Dogmazic = "\uF303"; |
||||||
|
public const string Zotero = "\uF309"; |
||||||
|
public const string Nodejs = "\uF308"; |
||||||
|
public const string Nextcloud = "\uF306"; |
||||||
|
public const string NextcloudSquare = "\uF307"; |
||||||
|
public const string Hackaday = "\uF30A"; |
||||||
|
public const string Laravel = "\uF30B"; |
||||||
|
public const string Signalapp = "\uF30C"; |
||||||
|
public const string Gnupg = "\uF30D"; |
||||||
|
public const string Php = "\uF30E"; |
||||||
|
public const string Ffmpeg = "\uF30F"; |
||||||
|
public const string Joplin = "\uF310"; |
||||||
|
public const string Syncthing = "\uF311"; |
||||||
|
public const string Inkscape = "\uF312"; |
||||||
|
public const string MatrixOrg = "\uF313"; |
||||||
|
public const string Pixelfed = "\uF314"; |
||||||
|
public const string Bootstrap = "\uF315"; |
||||||
|
public const string DevTo = "\uF316"; |
||||||
|
public const string Hashnode = "\uF317"; |
||||||
|
public const string Jirafeau = "\uF318"; |
||||||
|
public const string Emby = "\uF319"; |
||||||
|
public const string Wikidata = "\uF31A"; |
||||||
|
public const string Gimp = "\uF31B"; |
||||||
|
public const string C = "\uF31C"; |
||||||
|
public const string Digitalocean = "\uF31D"; |
||||||
|
public const string Att = "\uF31E"; |
||||||
|
public const string Gitea = "\uF31F"; |
||||||
|
public const string FileEpub = "\uF321"; |
||||||
|
public const string Python = "\uF322"; |
||||||
|
public const string Archlinux = "\uF323"; |
||||||
|
public const string Pleroma = "\uF324"; |
||||||
|
public const string Unsplash = "\uF325"; |
||||||
|
public const string Hackster = "\uF326"; |
||||||
|
public const string SpellCheck = "\uF327"; |
||||||
|
public const string Moon = "\uF328"; |
||||||
|
public const string Sun = "\uF329"; |
||||||
|
public const string FDroid = "\uF32A"; |
||||||
|
public const string Biometric = "\uF32B"; |
||||||
|
public const string Wire = "\uF32C"; |
||||||
|
public const string TorOnion = "\uF32E"; |
||||||
|
public const string VolumeMute = "\uF32F"; |
||||||
|
public const string BellRinging = "\uF32D"; |
||||||
|
public const string BellRingingO = "\uF330"; |
||||||
|
public const string Hal = "\uF333"; |
||||||
|
public const string Jupyter = "\uF335"; |
||||||
|
public const string Julia = "\uF334"; |
||||||
|
public const string Classicpress = "\uF331"; |
||||||
|
public const string ClassicpressCircle = "\uF332"; |
||||||
|
public const string OpenCollective = "\uF336"; |
||||||
|
public const string Orcid = "\uF337"; |
||||||
|
public const string Researchgate = "\uF338"; |
||||||
|
public const string Funkwhale = "\uF339"; |
||||||
|
public const string Askfm = "\uF33A"; |
||||||
|
public const string Blockstack = "\uF33B"; |
||||||
|
public const string Boardgamegeek = "\uF33C"; |
||||||
|
public const string Bunny = "\uF35F"; |
||||||
|
public const string Buymeacoffee = "\uF33D"; |
||||||
|
public const string CcBy = "\uF33E"; |
||||||
|
public const string CcCc = "\uF33F"; |
||||||
|
public const string CcNcEu = "\uF341"; |
||||||
|
public const string CcNcJp = "\uF342"; |
||||||
|
public const string CcNc = "\uF340"; |
||||||
|
public const string CcNd = "\uF343"; |
||||||
|
public const string CcPd = "\uF344"; |
||||||
|
public const string CcRemix = "\uF345"; |
||||||
|
public const string CcSa = "\uF346"; |
||||||
|
public const string CcShare = "\uF347"; |
||||||
|
public const string CcZero = "\uF348"; |
||||||
|
public const string ConwayGlider = "\uF349"; |
||||||
|
public const string Csharp = "\uF34A"; |
||||||
|
public const string EmailBulk = "\uF34B"; |
||||||
|
public const string EmailBulkO = "\uF34C"; |
||||||
|
public const string Gnu = "\uF34D"; |
||||||
|
public const string GooglePlay = "\uF34E"; |
||||||
|
public const string Heroku = "\uF34F"; |
||||||
|
public const string HomeAssistant = "\uF350"; |
||||||
|
public const string Java = "\uF351"; |
||||||
|
public const string Mariadb = "\uF352"; |
||||||
|
public const string Markdown = "\uF353"; |
||||||
|
public const string Mysql = "\uF354"; |
||||||
|
public const string Nordcast = "\uF355"; |
||||||
|
public const string Plume = "\uF356"; |
||||||
|
public const string Postgresql = "\uF357"; |
||||||
|
public const string SassAlt = "\uF359"; |
||||||
|
public const string Sass = "\uF358"; |
||||||
|
public const string Skate = "\uF35A"; |
||||||
|
public const string Sketchfab = "\uF35B"; |
||||||
|
public const string Tex = "\uF35C"; |
||||||
|
public const string Textpattern = "\uF35D"; |
||||||
|
public const string Unity = "\uF35E"; |
||||||
|
public const string Hedgedoc = "\uF360"; |
||||||
|
public const string Fediverse = "\uF361"; |
||||||
|
public const string Proftpd = "\uF362"; |
||||||
|
public const string Osi = "\uF363"; |
||||||
|
public const string Eyeem = "\uF364"; |
||||||
|
public const string EyeemO = "\uF365"; |
||||||
|
public const string Codeberg = "\uF366"; |
||||||
|
public const string Discourse = "\uF367"; |
||||||
|
public const string Mumble = "\uF368"; |
||||||
|
public const string Freedesktop = "\uF369"; |
||||||
|
public const string Javascript = "\uF370"; |
||||||
|
public const string Lemmy = "\uF371"; |
||||||
|
public const string Ipfs = "\uF372"; |
||||||
|
public const string Canonical = "\uF36A"; |
||||||
|
public const string Ubuntu = "\uF36B"; |
||||||
|
} |
@ -0,0 +1,55 @@ |
|||||||
|
using System.Numerics; |
||||||
|
using ImGuiNET; |
||||||
|
using ImGuiInternal = ImGuiNET.Internal.ImGui; |
||||||
|
|
||||||
|
namespace gaemstone.Client.Utility; |
||||||
|
|
||||||
|
public static class ImGuiUtility |
||||||
|
{ |
||||||
|
public static bool UIButtonToggle(int index, string label, string tooltip, ref bool enabled) |
||||||
|
{ if (UIButton(index, label, tooltip, enabled)) enabled = !enabled; return enabled; } |
||||||
|
public static bool UIButton(int index, string label, string tooltip, bool active) |
||||||
|
{ |
||||||
|
var start = new Vector2(4, 4); |
||||||
|
var buttonSize = new Vector2(32, 32); |
||||||
|
var pos = start + new Vector2(buttonSize.X + 4, 0) * index; |
||||||
|
|
||||||
|
ImGui.SetNextWindowPos(pos, ImGuiCond.Always); |
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); |
||||||
|
ImGui.Begin("UIToggle" + index, |
||||||
|
ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoBackground | |
||||||
|
ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize | |
||||||
|
ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoDocking); |
||||||
|
ImGui.PopStyleVar(); |
||||||
|
|
||||||
|
if (!active) ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.6f); |
||||||
|
var clicked = ImGui.Button(label, buttonSize); |
||||||
|
if (!active) ImGui.PopStyleVar(); |
||||||
|
if (ImGui.IsItemHovered()) ImGui.SetTooltip(tooltip); |
||||||
|
|
||||||
|
ImGui.End(); |
||||||
|
return clicked; |
||||||
|
} |
||||||
|
|
||||||
|
// public static void AdvancedTooltip(string brief, string detail) |
||||||
|
// { |
||||||
|
// if (!ImGui.IsItemHovered()) return; |
||||||
|
// ImGui.BeginTooltip(); |
||||||
|
// ImGui.PushFont(ImGui.GetIO().Fonts.Fonts[1]); |
||||||
|
// ImGui.TextUnformatted(brief); |
||||||
|
// ImGui.PopFont(); |
||||||
|
// ImGui.Text(detail); |
||||||
|
// ImGui.EndTooltip(); |
||||||
|
// } |
||||||
|
|
||||||
|
public static void TextCentered(string text, Vector2 size) |
||||||
|
{ |
||||||
|
var pos = ImGui.GetCursorScreenPos(); |
||||||
|
ImGuiInternal.ItemSize(size, 0); |
||||||
|
if (!ImGuiInternal.ItemAdd(new() { Min = pos, Max = pos + size }, 0)) return; |
||||||
|
|
||||||
|
var textSize = ImGui.CalcTextSize(text); |
||||||
|
var textPos = pos + (size - textSize) / 2; |
||||||
|
ImGuiInternal.RenderText(textPos, text); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
Subproject commit f9fa808d6842f5b208bbdfa1925f156342ed8a60 |
@ -1,17 +1,17 @@ |
|||||||
|
using System.Numerics; |
||||||
using gaemstone.ECS; |
using gaemstone.ECS; |
||||||
using Silk.NET.Maths; |
|
||||||
|
|
||||||
namespace gaemstone.Components; |
namespace gaemstone.Components; |
||||||
|
|
||||||
[Module] |
[Module] |
||||||
public class TransformComponents |
public class TransformComponents |
||||||
{ |
{ |
||||||
[Component] |
[Symbol, Component] |
||||||
public struct GlobalTransform |
public struct GlobalTransform |
||||||
{ |
{ |
||||||
public Matrix4X4<float> Value; |
public Matrix4x4 Value; |
||||||
public GlobalTransform(Matrix4X4<float> value) => Value = value; |
public GlobalTransform(Matrix4x4 value) => Value = value; |
||||||
public static implicit operator GlobalTransform(in Matrix4X4<float> value) => new(value); |
public static implicit operator GlobalTransform(in Matrix4x4 value) => new(value); |
||||||
public static implicit operator Matrix4X4<float>(in GlobalTransform index) => index.Value; |
public static implicit operator Matrix4x4(in GlobalTransform index) => index.Value; |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,86 @@ |
|||||||
|
using System; |
||||||
|
using gaemstone.ECS; |
||||||
|
using static gaemstone.Flecs.Core; |
||||||
|
|
||||||
|
namespace gaemstone; |
||||||
|
|
||||||
|
[Module] |
||||||
|
public class Doc |
||||||
|
{ |
||||||
|
[Tag] |
||||||
|
public struct DisplayType { } |
||||||
|
|
||||||
|
[Tag] |
||||||
|
public struct Relation { } |
||||||
|
|
||||||
|
|
||||||
|
// TODO: These need to actually be read at some point. |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// A display name for this entity. |
||||||
|
/// Names in the entity hierarchy must be unique within the parent entity, |
||||||
|
/// This doesn't apply to display names - they are mostly informational. |
||||||
|
/// Displayed in the Entity Inspector. |
||||||
|
/// </summary> |
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] |
||||||
|
public class Name : Attribute |
||||||
|
{ |
||||||
|
public string Value { get; } |
||||||
|
public Name(string value) => Value = value; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// A brief description of this entity. |
||||||
|
/// Displayed in the Entity Inspector. |
||||||
|
/// </summary> |
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] |
||||||
|
public class Brief : Attribute |
||||||
|
{ |
||||||
|
public string Value { get; } |
||||||
|
public Brief(string value) => Value = value; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// A detailed description, or full documentation, of this entity's purpose and behaviors. |
||||||
|
/// It's encouraged to use multiple paragraphs and markdown formatting if necessary. |
||||||
|
/// Displayed in the Entity Inspector. |
||||||
|
/// </summary> |
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] |
||||||
|
public class Detail : Attribute |
||||||
|
{ |
||||||
|
public string Value { get; } |
||||||
|
public Detail(string value) => Value = value; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// A link to a website relating to this entity, such as |
||||||
|
/// a module's repository, or further documentation. |
||||||
|
/// Displayed in the Entity Inspector. |
||||||
|
/// </summary> |
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] |
||||||
|
public class Link : Attribute |
||||||
|
{ |
||||||
|
public string Value { get; } |
||||||
|
public Link(string value) => Value = value; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary> |
||||||
|
/// A custom color to represent this entity. |
||||||
|
/// Displayed in the Entity Inspector. |
||||||
|
/// </summary> |
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] |
||||||
|
public class Color : Attribute |
||||||
|
{ |
||||||
|
public float Red { get; } |
||||||
|
public float Green { get; } |
||||||
|
public float Blue { get; } |
||||||
|
|
||||||
|
public Color(float red, float green, float blue) |
||||||
|
{ |
||||||
|
if ((red < 0.0f) || (red > 1.0f)) throw new ArgumentOutOfRangeException(nameof(red )); |
||||||
|
if ((green < 0.0f) || (green > 1.0f)) throw new ArgumentOutOfRangeException(nameof(green)); |
||||||
|
if ((blue < 0.0f) || (blue > 1.0f)) throw new ArgumentOutOfRangeException(nameof(blue )); |
||||||
|
Red = red; Green = green; Blue = blue; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,36 +0,0 @@ |
|||||||
using System; |
|
||||||
using gaemstone.Utility; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] |
|
||||||
public class ComponentAttribute : EntityAttribute |
|
||||||
{ |
|
||||||
public ComponentAttribute() { } |
|
||||||
public ComponentAttribute(params string[] path) : base(path) { } |
|
||||||
} |
|
||||||
|
|
||||||
public static class ComponentExtensions |
|
||||||
{ |
|
||||||
public unsafe static EntityRef CreateComponent<T>(this EntityRef entity) |
|
||||||
=> entity.CreateComponent(typeof(T)); |
|
||||||
public unsafe static EntityRef CreateComponent(this EntityRef entity, Type type) |
|
||||||
{ |
|
||||||
// TODO: Do some additional sanity checking for this type. |
|
||||||
|
|
||||||
var typeInfo = default(ecs_type_info_t); |
|
||||||
if (type.IsPrimitive) throw new ArgumentException( |
|
||||||
"Must not be primitive", nameof(type)); |
|
||||||
var wrapper = TypeWrapper.For(type); |
|
||||||
if (type.IsValueType && !wrapper.IsUnmanaged) throw new Exception( |
|
||||||
"Struct component must satisfy the unmanaged constraint. " + |
|
||||||
"Consider making it a class if you need to store references."); |
|
||||||
typeInfo.size = wrapper.Size; |
|
||||||
typeInfo.alignment = wrapper.Size; |
|
||||||
|
|
||||||
var desc = new ecs_component_desc_t { entity = entity, type = typeInfo }; |
|
||||||
ecs_component_init(entity.Universe, &desc); |
|
||||||
return entity.CreateLookup(type); |
|
||||||
} |
|
||||||
} |
|
@ -1,90 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Runtime.InteropServices; |
|
||||||
using gaemstone.Utility; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
// FIXME: This API is flawed. |
|
||||||
public unsafe class ComponentHooks |
|
||||||
{ |
|
||||||
public EntityRef Entity { get; } |
|
||||||
public ecs_type_hooks_t* Handle { get; } |
|
||||||
|
|
||||||
private Action<Iterator>? _onAdd; |
|
||||||
public event Action<Iterator>? OnAdd { |
|
||||||
add { if ((_onAdd += value) != null) Handle->on_add.Data.Pointer = &CallbackOnAdd; } |
|
||||||
remove { if ((_onAdd -= value) == null) Handle->on_add.Data.Pointer = null; } |
|
||||||
} |
|
||||||
private Action<Iterator>? _onSet; |
|
||||||
public event Action<Iterator>? OnSet { |
|
||||||
add { if ((_onSet += value) != null) Handle->on_set.Data.Pointer = &CallbackOnSet; } |
|
||||||
remove { if ((_onSet -= value) == null) Handle->on_set.Data.Pointer = null; } |
|
||||||
} |
|
||||||
private Action<Iterator>? _onRemove; |
|
||||||
public event Action<Iterator>? OnRemove { |
|
||||||
add { if ((_onRemove += value) != null) Handle->on_remove.Data.Pointer = &CallbackOnRemove; } |
|
||||||
remove { if ((_onRemove -= value) == null) Handle->on_remove.Data.Pointer = null; } |
|
||||||
} |
|
||||||
|
|
||||||
private Action<nint, int>? _constructor; |
|
||||||
public event Action<nint, int>? Construct { |
|
||||||
add { if ((_constructor += value) != null) Handle->ctor.Data.Pointer = &CallbackConstructor; } |
|
||||||
remove { if ((_constructor -= value) == null) Handle->ctor.Data.Pointer = null; } |
|
||||||
} |
|
||||||
private Action<nint, int>? _destructor; |
|
||||||
public event Action<nint, int>? Destruct { |
|
||||||
add { if ((_destructor += value) != null) Handle->dtor.Data.Pointer = &CallbackDestructor; } |
|
||||||
remove { if ((_destructor -= value) == null) Handle->dtor.Data.Pointer = null; } |
|
||||||
} |
|
||||||
private Action<nint, nint, int>? _copy; |
|
||||||
public event Action<nint, nint, int>? Copy { |
|
||||||
add { if ((_copy += value) != null) Handle->copy.Data.Pointer = &CallbackCopy; } |
|
||||||
remove { if ((_copy -= value) == null) Handle->copy.Data.Pointer = null; } |
|
||||||
} |
|
||||||
private Action<nint, nint, int>? _move; |
|
||||||
public event Action<nint, nint, int>? Move { |
|
||||||
add { if ((_move += value) != null) Handle->move.Data.Pointer = &CallbackMove; } |
|
||||||
remove { if ((_move -= value) == null) Handle->move.Data.Pointer = null; } |
|
||||||
} |
|
||||||
|
|
||||||
internal ComponentHooks(EntityRef entity, ecs_type_hooks_t* handle) |
|
||||||
{ Entity = entity; Handle = handle; } |
|
||||||
|
|
||||||
|
|
||||||
private static ComponentHooks Get(void* ptr) |
|
||||||
=> CallbackContextHelper.Get<ComponentHooks>((nint)ptr); |
|
||||||
|
|
||||||
[UnmanagedCallersOnly] private static void CallbackOnAdd(ecs_iter_t* it) |
|
||||||
{ var hooks = Get(it->binding_ctx); hooks._onAdd?.Invoke(new(hooks.Entity.Universe, null, *it)); } |
|
||||||
[UnmanagedCallersOnly] private static void CallbackOnSet(ecs_iter_t* it) |
|
||||||
{ var hooks = Get(it->binding_ctx); hooks._onSet?.Invoke(new(hooks.Entity.Universe, null, *it)); } |
|
||||||
[UnmanagedCallersOnly] private static void CallbackOnRemove(ecs_iter_t* it) |
|
||||||
{ var hooks = Get(it->binding_ctx); hooks._onRemove?.Invoke(new(hooks.Entity.Universe, null, *it)); } |
|
||||||
|
|
||||||
[UnmanagedCallersOnly] private static void CallbackConstructor(void* ptr, int count, ecs_type_info_t* type) |
|
||||||
=> Get(type->hooks.binding_ctx)._constructor?.Invoke((nint)ptr, count); |
|
||||||
[UnmanagedCallersOnly] private static void CallbackDestructor(void* ptr, int count, ecs_type_info_t* type) |
|
||||||
=> Get(type->hooks.binding_ctx)._destructor?.Invoke((nint)ptr, count); |
|
||||||
[UnmanagedCallersOnly] private static void CallbackCopy(void* dest, void* src, int count, ecs_type_info_t* type) |
|
||||||
=> Get(type->hooks.binding_ctx)._copy?.Invoke((nint)dest, (nint)src, count); |
|
||||||
[UnmanagedCallersOnly] private static void CallbackMove(void* dest, void* src, int count, ecs_type_info_t* type) |
|
||||||
=> Get(type->hooks.binding_ctx)._move?.Invoke((nint)dest, (nint)src, count); |
|
||||||
} |
|
||||||
|
|
||||||
public unsafe static class ComponentHooksExtensions |
|
||||||
{ |
|
||||||
public static ComponentHooks GetComponentHooks(this EntityRef entity) |
|
||||||
{ |
|
||||||
var handle = ecs_get_hooks_id(entity.Universe, entity); |
|
||||||
if (handle == null) throw new ArgumentException($"No type info found for {entity}"); |
|
||||||
|
|
||||||
if (handle->binding_ctx == null) { |
|
||||||
var hooks = new ComponentHooks(entity, handle); |
|
||||||
handle->binding_ctx = (void*)CallbackContextHelper.Create(hooks); |
|
||||||
return hooks; |
|
||||||
} else { |
|
||||||
return CallbackContextHelper.Get<ComponentHooks>((nint)handle->binding_ctx); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,48 +0,0 @@ |
|||||||
using System; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Struct)] |
|
||||||
public class EntityAttribute : Attribute, ICreateEntityAttribute |
|
||||||
{ |
|
||||||
/// <summary> If specified, uses this path instead of the default name. </summary> |
|
||||||
public string[]? Path { get; } |
|
||||||
|
|
||||||
/// <summary> If true, the path will be absolute instead of relative. </summary> |
|
||||||
public bool Global { get; init; } |
|
||||||
|
|
||||||
public EntityAttribute() { } |
|
||||||
public EntityAttribute(params string[] path) |
|
||||||
{ |
|
||||||
if (path.Length == 0) throw new ArgumentException( |
|
||||||
"Path must not be empty", nameof(path)); |
|
||||||
Path = path; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public readonly struct Entity |
|
||||||
: IEquatable<Entity> |
|
||||||
{ |
|
||||||
public static readonly Entity None = default; |
|
||||||
|
|
||||||
public readonly ecs_entity_t Value; |
|
||||||
public uint ID => (uint)Value.Data; |
|
||||||
|
|
||||||
public bool IsSome => Value.Data != 0; |
|
||||||
public bool IsNone => Value.Data == 0; |
|
||||||
|
|
||||||
public Entity(ecs_entity_t value) => Value = value; |
|
||||||
|
|
||||||
public bool Equals(Entity other) => Value.Data == other.Value.Data; |
|
||||||
public override bool Equals(object? obj) => (obj is Entity other) && Equals(other); |
|
||||||
public override int GetHashCode() => Value.Data.GetHashCode(); |
|
||||||
public override string? ToString() => $"Entity(0x{Value.Data.Data:X})"; |
|
||||||
|
|
||||||
public static bool operator ==(Entity left, Entity right) => left.Equals(right); |
|
||||||
public static bool operator !=(Entity left, Entity right) => !left.Equals(right); |
|
||||||
|
|
||||||
public static implicit operator ecs_entity_t(Entity e) => e.Value; |
|
||||||
public static implicit operator Identifier(Entity e) => new(e.Value.Data); |
|
||||||
public static implicit operator ecs_id_t(Entity e) => e.Value.Data; |
|
||||||
} |
|
@ -1,48 +0,0 @@ |
|||||||
using static gaemstone.Flecs.Core; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public abstract class EntityBase<TReturn> |
|
||||||
{ |
|
||||||
public abstract Universe Universe { get; } |
|
||||||
|
|
||||||
public abstract TReturn Add(Identifier id); |
|
||||||
public abstract TReturn Remove(Identifier id); |
|
||||||
public abstract bool Has(Identifier id); |
|
||||||
|
|
||||||
public abstract T Get<T>(); |
|
||||||
public abstract T? MaybeGet<T>() where T : unmanaged; |
|
||||||
public abstract T? MaybeGet<T>(T _ = null!) where T : class; |
|
||||||
public abstract ref T GetMut<T>() where T : unmanaged; |
|
||||||
public abstract ref T GetRefOrNull<T>() where T : unmanaged; |
|
||||||
public abstract ref T GetRefOrThrow<T>() where T : unmanaged; |
|
||||||
public abstract void Modified<T>(); |
|
||||||
|
|
||||||
public abstract TReturn Set<T>(in T value) where T : unmanaged; |
|
||||||
public abstract TReturn Set<T>(T obj) where T : class; |
|
||||||
|
|
||||||
public TReturn Add(string symbol) => Add(Universe.LookupSymbolOrThrow(symbol)); |
|
||||||
public TReturn Add<T>() => Add(Universe.LookupOrThrow(typeof(T))); |
|
||||||
public TReturn Add(Entity relation, Entity target) => Add(Identifier.Pair(relation, target)); |
|
||||||
public TReturn Add<TRelation>(Entity target) => Add(Universe.LookupOrThrow<TRelation>(), target); |
|
||||||
public TReturn Add<TRelation, TTarget>() => Add(Universe.LookupOrThrow<TRelation>(), Universe.LookupOrThrow<TTarget>()); |
|
||||||
|
|
||||||
public TReturn Remove(string symbol) => Remove(Universe.LookupSymbolOrThrow(symbol)); |
|
||||||
public TReturn Remove<T>() => Remove(Universe.LookupOrThrow(typeof(T))); |
|
||||||
public TReturn Remove(Entity relation, Entity target) => Remove(Identifier.Pair(relation, target)); |
|
||||||
public TReturn Remove<TRelation>(Entity target) => Remove(Universe.LookupOrThrow<TRelation>(), target); |
|
||||||
public TReturn Remove<TRelation, TTarget>() => Remove(Universe.LookupOrThrow<TRelation>(), Universe.LookupOrThrow<TTarget>()); |
|
||||||
|
|
||||||
public bool Has(string symbol) => Has(Universe.LookupSymbolOrThrow(symbol)); |
|
||||||
public bool Has<T>() => Has(Universe.LookupOrThrow(typeof(T))); |
|
||||||
public bool Has(Entity relation, Entity target) => Has(Identifier.Pair(relation, target)); |
|
||||||
public bool Has<TRelation>(Entity target) => Has(Universe.LookupOrThrow<TRelation>(), target); |
|
||||||
public bool Has<TRelation, TTarget>() => Has(Universe.LookupOrThrow<TRelation>(), Universe.LookupOrThrow<TTarget>()); |
|
||||||
|
|
||||||
public TReturn ChildOf(Entity parent) => Add<ChildOf>(parent); |
|
||||||
public TReturn ChildOf<TParent>() => Add<ChildOf, TParent>(); |
|
||||||
|
|
||||||
public TReturn Disable() => Add<Disabled>(); |
|
||||||
public TReturn Enable() => Remove<Disabled>(); |
|
||||||
public bool IsDisabled => Has<Disabled>(); |
|
||||||
} |
|
@ -1,116 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using gaemstone.Utility; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public class EntityBuilder |
|
||||||
: EntityBase<EntityBuilder> |
|
||||||
{ |
|
||||||
public override Universe Universe { get; } |
|
||||||
|
|
||||||
/// <summary> Set to modify existing entity (optional). </summary> |
|
||||||
public Entity ID { get; set; } |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// Path of the entity. If no entity is provided, an entity with this path |
|
||||||
/// will be looked up first. When an entity is provided, the path will be |
|
||||||
/// verified with the existing entity. |
|
||||||
/// </summary> |
|
||||||
public EntityPath? Path { get; set; } |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// Optional entity symbol. A symbol is an unscoped identifier that can |
|
||||||
/// be used to lookup an entity. The primary use case for this is to |
|
||||||
/// associate the entity with a language identifier, such as a type or |
|
||||||
/// function name, where these identifiers differ from the name they are |
|
||||||
/// registered with in flecs. |
|
||||||
/// </summary> |
|
||||||
public EntityBuilder Symbol(string symbol) { _symbol = symbol; return this; } |
|
||||||
private string? _symbol = null; |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// When set to true, a low id (typically reserved for components) |
|
||||||
/// will be used to create the entity, if no id is specified. |
|
||||||
/// </summary> |
|
||||||
public bool UseLowID { get; set; } |
|
||||||
|
|
||||||
/// <summary> IDs to add to the new or existing entity. </summary> |
|
||||||
private readonly HashSet<Identifier> _toAdd = new(); |
|
||||||
private Entity _parent = Entity.None; |
|
||||||
|
|
||||||
/// <summary> String expression with components to add. </summary> |
|
||||||
public string? Expression { get; } |
|
||||||
|
|
||||||
/// <summary> Actions to run once the entity has been created. </summary> |
|
||||||
private readonly List<Action<EntityRef>> _toSet = new(); |
|
||||||
|
|
||||||
public EntityBuilder(Universe universe, EntityPath? path = null) |
|
||||||
{ Universe = universe; Path = path; } |
|
||||||
|
|
||||||
public override EntityBuilder Add(Identifier id) |
|
||||||
{ |
|
||||||
// If adding a ChildOf relation, store the parent separately. |
|
||||||
if (id.AsPair(Universe) is (EntityRef relation, EntityRef target) && |
|
||||||
(relation == Universe.ChildOf)) { _parent = target; return this; } |
|
||||||
|
|
||||||
if (_toAdd.Count == 31) throw new NotSupportedException( |
|
||||||
"Must not add more than 31 IDs at once with EntityBuilder"); |
|
||||||
_toAdd.Add(id); |
|
||||||
|
|
||||||
return this; |
|
||||||
} |
|
||||||
public override EntityBuilder Remove(Identifier id) |
|
||||||
=> throw new NotSupportedException(); |
|
||||||
public override bool Has(Identifier id) |
|
||||||
=> !ecs_id_is_wildcard(id) ? _toAdd.Contains(id) |
|
||||||
: throw new NotSupportedException(); // TODO: Support wildcard. |
|
||||||
|
|
||||||
public override T Get<T>() => throw new NotSupportedException(); |
|
||||||
public override T? MaybeGet<T>() => throw new NotSupportedException(); |
|
||||||
public override T? MaybeGet<T>(T _ = null!) where T : class => throw new NotSupportedException(); |
|
||||||
public override ref T GetMut<T>() => throw new NotSupportedException(); |
|
||||||
public override ref T GetRefOrNull<T>() => throw new NotSupportedException(); |
|
||||||
public override ref T GetRefOrThrow<T>() => throw new NotSupportedException(); |
|
||||||
public override void Modified<T>() => throw new NotImplementedException(); |
|
||||||
|
|
||||||
public override EntityBuilder Set<T>(in T value) |
|
||||||
// "in" can't be used with lambdas, so we make a local copy. |
|
||||||
{ var copy = value; _toSet.Add(e => e.Set(copy)); return this; } |
|
||||||
|
|
||||||
public override EntityBuilder Set<T>(T obj) |
|
||||||
{ _toSet.Add(e => e.Set(obj)); return this; } |
|
||||||
|
|
||||||
public unsafe EntityRef Build() |
|
||||||
{ |
|
||||||
var parent = _parent; |
|
||||||
|
|
||||||
if (Path != null) { |
|
||||||
if (parent.IsSome && Path.IsAbsolute) throw new InvalidOperationException( |
|
||||||
"Entity already has parent set (via ChildOf), so path must not be absolute"); |
|
||||||
// If path specifies more than just a name, ensure the parent entity exists. |
|
||||||
if (Path.Count > 1) parent = EntityPath.EnsureEntityExists(Universe, parent, Path.Parent!); |
|
||||||
} |
|
||||||
|
|
||||||
using var alloc = TempAllocator.Use(); |
|
||||||
var desc = new ecs_entity_desc_t { |
|
||||||
id = ID, |
|
||||||
name = (Path != null) ? alloc.AllocateCString(Path.Name.AsSpan()) : default, |
|
||||||
symbol = alloc.AllocateCString(_symbol), |
|
||||||
add_expr = alloc.AllocateCString(Expression), |
|
||||||
use_low_id = UseLowID, |
|
||||||
sep = CStringExtensions.ETX, |
|
||||||
}; |
|
||||||
|
|
||||||
var add = desc.add; var index = 0; |
|
||||||
if (parent.IsSome) add[index++] = Identifier.Pair(Universe.ChildOf, parent); |
|
||||||
foreach (var id in _toAdd) add[index++] = id; |
|
||||||
|
|
||||||
var entityID = ecs_entity_init(Universe, &desc); |
|
||||||
var entity = new EntityRef(Universe, new(entityID)); |
|
||||||
foreach (var action in _toSet) action(entity); |
|
||||||
|
|
||||||
return entity; |
|
||||||
} |
|
||||||
} |
|
@ -1,233 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Buffers; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Diagnostics.CodeAnalysis; |
|
||||||
using System.Linq; |
|
||||||
using System.Text; |
|
||||||
using gaemstone.Utility; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public class EntityPath |
|
||||||
{ |
|
||||||
private readonly byte[][] _parts; |
|
||||||
|
|
||||||
public bool IsAbsolute { get; } |
|
||||||
public bool IsRelative => !IsAbsolute; |
|
||||||
public int Count => _parts.Length; |
|
||||||
|
|
||||||
public UTF8View Name => this[^1]; |
|
||||||
public EntityPath? Parent => (Count > 1) ? new(IsAbsolute, _parts[..^1]) : null; |
|
||||||
|
|
||||||
public UTF8View this[int index] |
|
||||||
=> (index >= 0 && index < Count) ? new(_parts[index].AsSpan()[..^1]) |
|
||||||
: throw new ArgumentOutOfRangeException(nameof(index)); |
|
||||||
|
|
||||||
public EntityPath this[Range range] |
|
||||||
=> new(IsAbsolute && (range.GetOffsetAndLength(Count).Offset == 0), _parts[range]); |
|
||||||
|
|
||||||
internal EntityPath(bool absolute, params byte[][] parts) |
|
||||||
{ |
|
||||||
if (parts.Length == 0) throw new ArgumentException( |
|
||||||
"Must have at least one part", nameof(parts)); |
|
||||||
IsAbsolute = absolute; |
|
||||||
_parts = parts; |
|
||||||
} |
|
||||||
|
|
||||||
public EntityPath(params string[] parts) |
|
||||||
: this(false, parts) { } |
|
||||||
public EntityPath(bool absolute, params string[] parts) |
|
||||||
: this(absolute, parts.Select(part => { |
|
||||||
if (GetNameValidationError(part) is string error) |
|
||||||
throw new ArgumentException(error); |
|
||||||
var byteCount = Encoding.UTF8.GetByteCount(part); |
|
||||||
// Includes NUL character at the end of bytes. |
|
||||||
var bytes = new byte[byteCount + 1]; |
|
||||||
Encoding.UTF8.GetBytes(part, bytes); |
|
||||||
return bytes; |
|
||||||
}).ToArray()) { } |
|
||||||
|
|
||||||
public static bool TryParse(string str, [NotNullWhen(true)] out EntityPath? result) |
|
||||||
{ |
|
||||||
result = null; |
|
||||||
if (str.Length == 0) return false; |
|
||||||
|
|
||||||
var strSpan = str.AsSpan(); |
|
||||||
var isAbsolute = (str[0] == '/'); |
|
||||||
if (isAbsolute) strSpan = strSpan[1..]; |
|
||||||
|
|
||||||
var numSeparators = 0; |
|
||||||
foreach (var chr in strSpan) if (chr == '/') numSeparators++; |
|
||||||
|
|
||||||
var index = 0; |
|
||||||
var parts = new byte[numSeparators + 1][]; |
|
||||||
foreach (var part in strSpan.Split('/')) { |
|
||||||
if (GetNameValidationError(part) != null) return false; |
|
||||||
var byteCount = Encoding.UTF8.GetByteCount(part); |
|
||||||
// Includes NUL character at the end of bytes. |
|
||||||
var bytes = new byte[byteCount + 1]; |
|
||||||
Encoding.UTF8.GetBytes(part, bytes); |
|
||||||
parts[index++] = bytes; |
|
||||||
} |
|
||||||
|
|
||||||
result = new(isAbsolute, parts); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
public static EntityPath Parse(string str) |
|
||||||
{ |
|
||||||
if (str.Length == 0) throw new ArgumentException( |
|
||||||
"String must not be empty", nameof(str)); |
|
||||||
var parts = str.Split('/'); |
|
||||||
// If string starts with a slash, first part will be empty, so create an absolute path. |
|
||||||
return (parts[0].Length == 0) ? new(true, parts[1..]) : new(parts); |
|
||||||
} |
|
||||||
|
|
||||||
public static string? GetNameValidationError(ReadOnlySpan<char> name) |
|
||||||
{ |
|
||||||
if (name.Length == 0) return "Must not be empty"; |
|
||||||
// NOTE: This is a hopefully straightforward way to also prevent "." |
|
||||||
// and ".." to be part of paths which may access the file system. |
|
||||||
if (name[0] == '.') return "Must not begin with a dot"; |
|
||||||
foreach (var chr in name) if (char.IsControl(chr)) |
|
||||||
return "Must not contain contol characters"; |
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
// private static readonly Rune[] _validRunes = { (Rune)'-', (Rune)'.', (Rune)'_' }; |
|
||||||
// private static readonly UnicodeCategory[] _validCategories = { |
|
||||||
// UnicodeCategory.LowercaseLetter, UnicodeCategory.UppercaseLetter, |
|
||||||
// UnicodeCategory.OtherLetter, UnicodeCategory.DecimalDigitNumber }; |
|
||||||
|
|
||||||
// private static void ValidateRune(Rune rune) |
|
||||||
// { |
|
||||||
// if (!_validRunes.Contains(rune) && !_validCategories.Contains(Rune.GetUnicodeCategory(rune))) |
|
||||||
// throw new ArgumentException($"Must not contain {Rune.GetUnicodeCategory(rune)} character"); |
|
||||||
// } |
|
||||||
|
|
||||||
public string[] GetParts() |
|
||||||
{ |
|
||||||
var result = new string[Count]; |
|
||||||
for (var i = 0; i < Count; i++) result[i] = this[i]; |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
public override string ToString() |
|
||||||
{ |
|
||||||
var builder = new StringBuilder(); |
|
||||||
if (IsAbsolute) builder.Append('/'); |
|
||||||
foreach (var part in this) builder.Append(part).Append('/'); |
|
||||||
return builder.ToString(0, builder.Length - 1); |
|
||||||
} |
|
||||||
|
|
||||||
public static implicit operator EntityPath(string path) => Parse(path); |
|
||||||
|
|
||||||
public Enumerator GetEnumerator() => new(this); |
|
||||||
public ref struct Enumerator |
|
||||||
{ |
|
||||||
private readonly EntityPath _path; |
|
||||||
private int index = -1; |
|
||||||
public UTF8View Current => _path[index]; |
|
||||||
internal Enumerator(EntityPath path) => _path = path; |
|
||||||
public bool MoveNext() => (++index < _path.Count); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
internal static unsafe Entity Lookup(Universe universe, Entity parent, EntityPath path) |
|
||||||
{ |
|
||||||
// If path is absolute, ignore parent and start at root. |
|
||||||
if (path.IsAbsolute) parent = default; |
|
||||||
// Otherwise, if no parent is specified, use the current scope. |
|
||||||
else if (parent.IsNone) parent = new(ecs_get_scope(universe)); |
|
||||||
|
|
||||||
foreach (var part in path) |
|
||||||
fixed (byte* ptr = part.AsSpan()) { |
|
||||||
// FIXME: This breaks when using large entity IDs. |
|
||||||
parent = new(ecs_lookup_child(universe, parent, ptr)); |
|
||||||
if (parent.IsNone || !ecs_is_alive(universe, parent)) |
|
||||||
return Entity.None; |
|
||||||
} |
|
||||||
|
|
||||||
return parent; |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary> Used by <see cref="EntityBuilder.Build"/>. </summary> |
|
||||||
internal static unsafe Entity EnsureEntityExists( |
|
||||||
Universe universe, Entity parent, EntityPath path) |
|
||||||
{ |
|
||||||
// If no parent is specified and path is relative, use the current scope. |
|
||||||
if (parent.IsNone && path.IsRelative) parent = new(ecs_get_scope(universe)); |
|
||||||
|
|
||||||
var skipLookup = parent.IsNone; |
|
||||||
foreach (var part in path) |
|
||||||
fixed (byte* ptr = part.AsSpan()) |
|
||||||
if (skipLookup || (parent = new(ecs_lookup_child(universe, parent, ptr))).IsNone) { |
|
||||||
var desc = new ecs_entity_desc_t { name = ptr, sep = CStringExtensions.ETX }; |
|
||||||
if (parent.IsSome) desc.add[0] = Identifier.Pair(universe.ChildOf, parent); |
|
||||||
parent = new(ecs_entity_init(universe, &desc)); |
|
||||||
skipLookup = true; |
|
||||||
} |
|
||||||
|
|
||||||
return parent; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class EntityPathExtensions |
|
||||||
{ |
|
||||||
public static unsafe EntityPath GetFullPath(this EntityRef entity) |
|
||||||
{ |
|
||||||
var current = (Entity)entity; |
|
||||||
var parts = new List<byte[]>(32); |
|
||||||
|
|
||||||
do { |
|
||||||
var name = ecs_get_name(entity.Universe, current).FlecsToBytes(); |
|
||||||
if (name != null) parts.Add(name); |
|
||||||
else { |
|
||||||
// If name is not set, use the numeric ID, instead. |
|
||||||
var id = current.ID.ToString(); |
|
||||||
var bytes = new byte[Encoding.UTF8.GetByteCount(id) + 1]; |
|
||||||
Encoding.UTF8.GetBytes(id, bytes); |
|
||||||
parts.Add(bytes); |
|
||||||
} |
|
||||||
} while ((current = new(ecs_get_target(entity.Universe, current, EcsChildOf, 0))).IsSome); |
|
||||||
|
|
||||||
parts.Reverse(); |
|
||||||
return new(true, parts.ToArray()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public readonly ref struct UTF8View |
|
||||||
{ |
|
||||||
private readonly ReadOnlySpan<byte> _bytes; |
|
||||||
public UTF8View(ReadOnlySpan<byte> bytes) |
|
||||||
=> _bytes = bytes; |
|
||||||
|
|
||||||
public int Length => _bytes.Length; |
|
||||||
public byte this[int index] => _bytes[index]; |
|
||||||
|
|
||||||
public ReadOnlySpan<byte> AsSpan() => _bytes; |
|
||||||
public override string ToString() => Encoding.UTF8.GetString(_bytes); |
|
||||||
public static implicit operator string(UTF8View view) => view.ToString(); |
|
||||||
|
|
||||||
public Enumerator GetEnumerator() => new(_bytes); |
|
||||||
public ref struct Enumerator |
|
||||||
{ |
|
||||||
private readonly ReadOnlySpan<byte> _bytes; |
|
||||||
private int index = 0; |
|
||||||
private Rune _current = default; |
|
||||||
public Rune Current => _current; |
|
||||||
|
|
||||||
internal Enumerator(ReadOnlySpan<byte> bytes) |
|
||||||
=> _bytes = bytes; |
|
||||||
|
|
||||||
public bool MoveNext() |
|
||||||
{ |
|
||||||
if (index >= _bytes.Length) return false; |
|
||||||
if (Rune.DecodeFromUtf8(_bytes[index..], out _current, out var consumed) != OperationStatus.Done) |
|
||||||
throw new InvalidOperationException("Contains invalid UTF8"); |
|
||||||
index += consumed; |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,169 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Runtime.CompilerServices; |
|
||||||
using System.Runtime.InteropServices; |
|
||||||
using gaemstone.Utility; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public unsafe sealed class EntityRef |
|
||||||
: EntityBase<EntityRef> |
|
||||||
, IEquatable<EntityRef> |
|
||||||
{ |
|
||||||
public override Universe Universe { get; } |
|
||||||
public Entity Entity { get; } |
|
||||||
public uint ID => Entity.ID; |
|
||||||
|
|
||||||
public bool IsAlive => ecs_is_alive(Universe, this); |
|
||||||
public EntityType Type => new(Universe, ecs_get_type(Universe, this)); |
|
||||||
|
|
||||||
public string? Name { |
|
||||||
get => ecs_get_name(Universe, this).FlecsToString()!; |
|
||||||
set { using var alloc = TempAllocator.Use(); |
|
||||||
ecs_set_name(Universe, this, alloc.AllocateCString(value)); } |
|
||||||
} |
|
||||||
public string? Symbol { |
|
||||||
get => ecs_get_symbol(Universe, this).FlecsToString()!; |
|
||||||
set { using var alloc = TempAllocator.Use(); |
|
||||||
ecs_set_symbol(Universe, this, alloc.AllocateCString(value)); } |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
private EntityRef(Universe universe, Entity entity, bool throwOnInvalid) |
|
||||||
{ |
|
||||||
if (throwOnInvalid && !ecs_is_valid(universe, entity)) |
|
||||||
throw new InvalidOperationException($"The entity {entity} is not valid"); |
|
||||||
Universe = universe; |
|
||||||
Entity = entity; |
|
||||||
} |
|
||||||
|
|
||||||
public EntityRef(Universe universe, Entity entity) |
|
||||||
: this(universe, entity, true) { } |
|
||||||
|
|
||||||
public static EntityRef? CreateOrNull(Universe universe, Entity entity) |
|
||||||
=> ecs_is_valid(universe, entity) ? new(universe, entity) : null; |
|
||||||
|
|
||||||
|
|
||||||
public void Delete() |
|
||||||
=> ecs_delete(Universe, this); |
|
||||||
|
|
||||||
public EntityBuilder NewChild(EntityPath? path = null) |
|
||||||
=> Universe.New(EnsureRelativePath(path)).ChildOf(this); |
|
||||||
public EntityRef? Lookup(EntityPath path) |
|
||||||
=> Universe.Lookup(this, EnsureRelativePath(path)!); |
|
||||||
public EntityRef LookupOrThrow(EntityPath path) |
|
||||||
=> Universe.LookupOrThrow(this, EnsureRelativePath(path)!); |
|
||||||
|
|
||||||
private static EntityPath? EnsureRelativePath(EntityPath? path) |
|
||||||
{ if (path?.IsAbsolute == true) throw new ArgumentException("path must not be absolute", nameof(path)); return path; } |
|
||||||
|
|
||||||
|
|
||||||
public EntityRef? Parent |
|
||||||
=> GetTarget(Universe.ChildOf); |
|
||||||
|
|
||||||
public IEnumerable<EntityRef> GetChildren() |
|
||||||
{ |
|
||||||
foreach (var iter in Iterator.FromTerm(Universe, new(Universe.ChildOf, this))) |
|
||||||
for (var i = 0; i < iter.Count; i++) |
|
||||||
yield return iter.Entity(i); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
public override EntityRef Add(Identifier id) { ecs_add_id(Universe, this, id); return this; } |
|
||||||
public override EntityRef Remove(Identifier id) { ecs_remove_id(Universe, this, id); return this; } |
|
||||||
public override bool Has(Identifier id) => ecs_has_id(Universe, this, id); |
|
||||||
|
|
||||||
public override T Get<T>() |
|
||||||
{ |
|
||||||
var comp = Universe.LookupOrThrow<T>(); |
|
||||||
var ptr = ecs_get_id(Universe, this, comp); |
|
||||||
if (ptr == null) throw new Exception($"Component {typeof(T)} not found on {this}"); |
|
||||||
return typeof(T).IsValueType ? Unsafe.Read<T>(ptr) |
|
||||||
: (T)((GCHandle)Unsafe.Read<nint>(ptr)).Target!; |
|
||||||
} |
|
||||||
|
|
||||||
public override T? MaybeGet<T>() |
|
||||||
{ |
|
||||||
var comp = Universe.LookupOrThrow<T>(); |
|
||||||
var ptr = ecs_get_id(Universe, this, comp); |
|
||||||
return (ptr != null) ? Unsafe.Read<T>(ptr) : null; |
|
||||||
} |
|
||||||
|
|
||||||
public override T? MaybeGet<T>(T _ = null!) |
|
||||||
where T : class
|
|
||||||
{ |
|
||||||
var comp = Universe.LookupOrThrow<T>(); |
|
||||||
var ptr = ecs_get_id(Universe, this, comp); |
|
||||||
return (ptr != null) ? (T)((GCHandle)Unsafe.Read<nint>(ptr)).Target! : null; |
|
||||||
} |
|
||||||
|
|
||||||
public override ref T GetRefOrNull<T>() |
|
||||||
{ |
|
||||||
var comp = Universe.LookupOrThrow<T>(); |
|
||||||
var @ref = ecs_ref_init_id(Universe, this, comp); |
|
||||||
var ptr = ecs_ref_get_id(Universe, &@ref, comp); |
|
||||||
return ref (ptr != null) ? ref Unsafe.AsRef<T>(ptr) : ref Unsafe.NullRef<T>(); |
|
||||||
} |
|
||||||
|
|
||||||
public override ref T GetRefOrThrow<T>() |
|
||||||
{ |
|
||||||
ref var ptr = ref GetRefOrNull<T>(); |
|
||||||
if (Unsafe.IsNullRef(ref ptr)) throw new Exception( |
|
||||||
$"Component {typeof(T)} not found on {this}"); |
|
||||||
return ref ptr; |
|
||||||
} |
|
||||||
|
|
||||||
public override ref T GetMut<T>() |
|
||||||
{ |
|
||||||
var comp = Universe.LookupOrThrow<T>(); |
|
||||||
var ptr = ecs_get_mut_id(Universe, this, comp); |
|
||||||
// NOTE: Value is added if it doesn't exist on the entity. |
|
||||||
return ref Unsafe.AsRef<T>(ptr); |
|
||||||
} |
|
||||||
|
|
||||||
public override void Modified<T>() |
|
||||||
=> ecs_modified_id(Universe, this, Universe.LookupOrThrow<T>()); |
|
||||||
|
|
||||||
public override EntityRef Set<T>(in T value) |
|
||||||
{ |
|
||||||
var comp = Universe.LookupOrThrow<T>(); |
|
||||||
var size = (ulong)Unsafe.SizeOf<T>(); |
|
||||||
fixed (T* ptr = &value) |
|
||||||
if (ecs_set_id(Universe, this, comp, size, ptr).Data == 0) |
|
||||||
throw new InvalidOperationException(); |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
public override EntityRef Set<T>(T obj) where T : class
|
|
||||||
{ |
|
||||||
var comp = Universe.LookupOrThrow<T>(); |
|
||||||
var handle = (nint)GCHandle.Alloc(obj); |
|
||||||
// FIXME: Previous handle needs to be freed. |
|
||||||
if (ecs_set_id(Universe, this, comp, (ulong)sizeof(nint), &handle).Data == 0) |
|
||||||
throw new InvalidOperationException(); |
|
||||||
// FIXME: Handle needs to be freed when component is removed! |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
public EntityRef? GetTarget(Entity relation, int index = 0) |
|
||||||
=> CreateOrNull(Universe, new(ecs_get_target(Universe, this, relation, index))); |
|
||||||
public EntityRef? GetTarget(string symbol, int index = 0) |
|
||||||
=> GetTarget(Universe.LookupSymbolOrThrow(symbol), index); |
|
||||||
public EntityRef? GetTarget<T>(int index = 0) |
|
||||||
=> GetTarget(Universe.LookupOrThrow(typeof(T)), index); |
|
||||||
|
|
||||||
public bool Equals(EntityRef? other) => (other is not null) && (Universe == other.Universe) && (Entity == other.Entity); |
|
||||||
public override bool Equals(object? obj) => Equals(obj as EntityRef); |
|
||||||
public override int GetHashCode() => HashCode.Combine(Universe, Entity); |
|
||||||
public override string? ToString() => ecs_entity_str(Universe, this).FlecsToStringAndFree()!; |
|
||||||
|
|
||||||
public static bool operator ==(EntityRef? left, EntityRef? right) => ReferenceEquals(left, right) || (left?.Equals(right) ?? false); |
|
||||||
public static bool operator !=(EntityRef? left, EntityRef? right) => !(left == right); |
|
||||||
|
|
||||||
public static implicit operator Entity(EntityRef? e) => e?.Entity ?? default; |
|
||||||
public static implicit operator ecs_entity_t(EntityRef? e) => e?.Entity.Value ?? default; |
|
||||||
|
|
||||||
public static implicit operator Identifier(EntityRef? e) => new(e?.Entity.Value.Data ?? default); |
|
||||||
public static implicit operator ecs_id_t(EntityRef? e) => e?.Entity.Value.Data ?? default; |
|
||||||
} |
|
@ -1,25 +0,0 @@ |
|||||||
using System.Collections; |
|
||||||
using System.Collections.Generic; |
|
||||||
using gaemstone.Utility; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public unsafe readonly struct EntityType |
|
||||||
: IReadOnlyList<IdentifierRef> |
|
||||||
{ |
|
||||||
public Universe Universe { get; } |
|
||||||
public ecs_type_t* Handle { get; } |
|
||||||
|
|
||||||
public EntityType(Universe universe, ecs_type_t* handle) |
|
||||||
{ Universe = universe; Handle = handle; } |
|
||||||
|
|
||||||
public override string ToString() |
|
||||||
=> ecs_type_str(Universe, Handle).FlecsToStringAndFree()!; |
|
||||||
|
|
||||||
// IReadOnlyList implementation |
|
||||||
public int Count => Handle->count; |
|
||||||
public IdentifierRef this[int index] => new(Universe, new(Handle->array[index])); |
|
||||||
public IEnumerator<IdentifierRef> GetEnumerator() { for (var i = 0; i < Count; i++) yield return this[i]; } |
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
|
||||||
} |
|
@ -1,87 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Linq; |
|
||||||
using System.Runtime.CompilerServices; |
|
||||||
using gaemstone.Utility; |
|
||||||
using gaemstone.Utility.IL; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public unsafe sealed class Filter |
|
||||||
: IDisposable |
|
||||||
{ |
|
||||||
public Universe Universe { get; } |
|
||||||
public ecs_filter_t* Handle { get; } |
|
||||||
|
|
||||||
public Filter(Universe universe, FilterDesc desc) |
|
||||||
{ |
|
||||||
using var alloc = TempAllocator.Use(); |
|
||||||
var flecsDesc = desc.ToFlecs(alloc); |
|
||||||
Universe = universe; |
|
||||||
Handle = ecs_filter_init(universe, &flecsDesc); |
|
||||||
} |
|
||||||
|
|
||||||
public static void RunOnce(Universe universe, Delegate action) |
|
||||||
{ |
|
||||||
var gen = IterActionGenerator.GetOrBuild(universe, action.Method); |
|
||||||
var desc = new FilterDesc(gen.Terms.ToArray()); |
|
||||||
using var filter = new Filter(universe, desc); |
|
||||||
foreach (var iter in filter.Iter()) gen.RunWithTryCatch(action.Target, iter); |
|
||||||
} |
|
||||||
|
|
||||||
public void Dispose() |
|
||||||
=> ecs_filter_fini(Handle); |
|
||||||
|
|
||||||
public Iterator Iter() |
|
||||||
=> new(Universe, IteratorType.Filter, ecs_filter_iter(Universe, this)); |
|
||||||
|
|
||||||
public override string ToString() |
|
||||||
=> ecs_filter_str(Universe, Handle).FlecsToStringAndFree()!; |
|
||||||
|
|
||||||
public static implicit operator ecs_filter_t*(Filter q) => q.Handle; |
|
||||||
} |
|
||||||
|
|
||||||
public class FilterDesc |
|
||||||
{ |
|
||||||
public IReadOnlyList<Term> Terms { get; } |
|
||||||
|
|
||||||
public string? Expression { get; } |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// When true, terms returned by an iterator may either contain 1 or N |
|
||||||
/// elements, where terms with N elements are owned, and terms with 1 |
|
||||||
/// element are shared, for example from a parent or base entity. When |
|
||||||
/// false, the iterator will at most return 1 element when the result |
|
||||||
/// contains both owned and shared terms. |
|
||||||
/// </summary> |
|
||||||
public bool Instanced { get; set; } |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// Entity associated with query (optional). |
|
||||||
/// </summary> |
|
||||||
public Entity Entity { get; set; } |
|
||||||
|
|
||||||
public FilterDesc(params Term[] terms) |
|
||||||
=> Terms = terms; |
|
||||||
public FilterDesc(string expression) : this() |
|
||||||
=> Expression = expression; |
|
||||||
|
|
||||||
public unsafe ecs_filter_desc_t ToFlecs(IAllocator allocator) |
|
||||||
{ |
|
||||||
var desc = new ecs_filter_desc_t { |
|
||||||
expr = allocator.AllocateCString(Expression), |
|
||||||
instanced = Instanced, |
|
||||||
entity = Entity, |
|
||||||
}; |
|
||||||
var span = desc.terms; |
|
||||||
if (Terms.Count > span.Length) { |
|
||||||
span = allocator.Allocate<ecs_term_t>(Terms.Count); |
|
||||||
desc.terms_buffer = (ecs_term_t*)Unsafe.AsPointer(ref span[0]); |
|
||||||
desc.terms_buffer_count = Terms.Count; |
|
||||||
} |
|
||||||
for (var i = 0; i < Terms.Count; i++) |
|
||||||
span[i] = Terms[i].ToFlecs(allocator); |
|
||||||
return desc; |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,16 @@ |
|||||||
|
using System; |
||||||
|
using System.Linq; |
||||||
|
using gaemstone.Utility.IL; |
||||||
|
|
||||||
|
namespace gaemstone.ECS; |
||||||
|
|
||||||
|
public static class FilterExtensions |
||||||
|
{ |
||||||
|
public static void RunOnce(World world, Delegate action) |
||||||
|
{ |
||||||
|
var gen = IterActionGenerator.GetOrBuild(world, action.Method); |
||||||
|
var desc = new FilterDesc(gen.Terms.ToArray()); |
||||||
|
using var filter = new Filter(world, desc); |
||||||
|
foreach (var iter in filter.Iter()) gen.RunWithTryCatch(action.Target, iter); |
||||||
|
} |
||||||
|
} |
@ -1,54 +0,0 @@ |
|||||||
using System; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public readonly struct Identifier |
|
||||||
: IEquatable<Identifier> |
|
||||||
{ |
|
||||||
public readonly ecs_id_t Value; |
|
||||||
|
|
||||||
public bool IsPair => ecs_id_is_pair(Value); |
|
||||||
public bool IsWildcard => ecs_id_is_wildcard(Value); |
|
||||||
|
|
||||||
public IdentifierFlags Flags => (IdentifierFlags)(Value & ECS_ID_FLAGS_MASK); |
|
||||||
|
|
||||||
public Entity RelationUnsafe => new(new() { Data = (Value & ECS_COMPONENT_MASK) >> 32 }); |
|
||||||
public Entity TargetUnsafe => new(new() { Data = Value & ECS_ENTITY_MASK }); |
|
||||||
|
|
||||||
public Identifier(ecs_id_t value) => Value = value; |
|
||||||
|
|
||||||
public static Identifier Combine(IdentifierFlags flags, Identifier id) |
|
||||||
=> new((ulong)flags | id.Value); |
|
||||||
|
|
||||||
public static Identifier Pair(Entity relation, Entity target) |
|
||||||
=> Combine(IdentifierFlags.Pair, new( |
|
||||||
((relation.Value.Data << 32) & ECS_COMPONENT_MASK) | |
|
||||||
( target.Value.Data & ECS_ENTITY_MASK ))); |
|
||||||
|
|
||||||
public (EntityRef Relation, EntityRef Target)? AsPair(Universe universe) |
|
||||||
=> new IdentifierRef(universe, this).AsPair(); |
|
||||||
|
|
||||||
public bool Equals(Identifier other) => Value.Data == other.Value.Data; |
|
||||||
public override bool Equals(object? obj) => (obj is Identifier other) && Equals(other); |
|
||||||
public override int GetHashCode() => Value.Data.GetHashCode(); |
|
||||||
public override string? ToString() |
|
||||||
=> (Flags != default) ? $"Identifier(0x{Value.Data:X}, Flags={Flags})" |
|
||||||
: $"Identifier(0x{Value.Data:X})"; |
|
||||||
|
|
||||||
public static bool operator ==(Identifier left, Identifier right) => left.Equals(right); |
|
||||||
public static bool operator !=(Identifier left, Identifier right) => !left.Equals(right); |
|
||||||
|
|
||||||
public static implicit operator ecs_id_t(Identifier i) => i.Value; |
|
||||||
} |
|
||||||
|
|
||||||
[Flags] |
|
||||||
public enum IdentifierFlags : ulong |
|
||||||
{ |
|
||||||
Pair = 1ul << 63, |
|
||||||
Override = 1ul << 62, |
|
||||||
Toggle = 1ul << 61, |
|
||||||
Or = 1ul << 60, |
|
||||||
And = 1ul << 59, |
|
||||||
Not = 1ul << 58, |
|
||||||
} |
|
@ -1,41 +0,0 @@ |
|||||||
using System; |
|
||||||
using gaemstone.Utility; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public unsafe class IdentifierRef |
|
||||||
: IEquatable<IdentifierRef> |
|
||||||
{ |
|
||||||
public Universe Universe { get; } |
|
||||||
public Identifier ID { get; } |
|
||||||
|
|
||||||
public IdentifierFlags Flags => ID.Flags; |
|
||||||
public bool IsPair => ID.IsPair; |
|
||||||
public bool IsWildcard => ID.IsWildcard; |
|
||||||
public bool IsValid => ecs_id_is_valid(Universe, ID); |
|
||||||
|
|
||||||
public IdentifierRef(Universe universe, Identifier id) |
|
||||||
{ Universe = universe; ID = id; } |
|
||||||
|
|
||||||
public static IdentifierRef Combine(IdentifierFlags flags, IdentifierRef id) |
|
||||||
=> new(id.Universe, Identifier.Combine(flags, id)); |
|
||||||
public static IdentifierRef Pair(EntityRef relation, Entity target) |
|
||||||
=> new(relation.Universe, Identifier.Pair(relation, target)); |
|
||||||
public static IdentifierRef Pair(Entity relation, EntityRef target) |
|
||||||
=> new(target.Universe, Identifier.Pair(relation, target)); |
|
||||||
|
|
||||||
public (EntityRef Relation, EntityRef Target)? AsPair() |
|
||||||
=> IsPair ? (Universe.LookupOrThrow(ID.RelationUnsafe), Universe.LookupOrThrow(ID.TargetUnsafe)) : null; |
|
||||||
|
|
||||||
public bool Equals(IdentifierRef? other) => (other is not null) && (Universe == other.Universe) && (ID == other.ID); |
|
||||||
public override bool Equals(object? obj) => Equals(obj as IdentifierRef); |
|
||||||
public override int GetHashCode() => HashCode.Combine(Universe, ID); |
|
||||||
public override string? ToString() => ecs_id_str(Universe, this).FlecsToStringAndFree()!; |
|
||||||
|
|
||||||
public static bool operator ==(IdentifierRef? left, IdentifierRef? right) => ReferenceEquals(left, right) || (left?.Equals(right) ?? false); |
|
||||||
public static bool operator !=(IdentifierRef? left, IdentifierRef? right) => !(left == right); |
|
||||||
|
|
||||||
public static implicit operator Identifier(IdentifierRef i) => i.ID; |
|
||||||
public static implicit operator ecs_id_t(IdentifierRef i) => i.ID.Value; |
|
||||||
} |
|
@ -1,116 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Collections; |
|
||||||
using System.Collections.Generic; |
|
||||||
using System.Runtime.CompilerServices; |
|
||||||
using gaemstone.Utility; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public unsafe class Iterator |
|
||||||
: IEnumerable<Iterator> |
|
||||||
, IDisposable |
|
||||||
{ |
|
||||||
public Universe Universe { get; } |
|
||||||
public IteratorType? Type { get; } |
|
||||||
public ecs_iter_t Value; |
|
||||||
|
|
||||||
public bool Completed { get; private set; } |
|
||||||
public int Count => Value.count; |
|
||||||
public TimeSpan DeltaTime => TimeSpan.FromSeconds(Value.delta_time); |
|
||||||
public TimeSpan DeltaSystemTime => TimeSpan.FromSeconds(Value.delta_system_time); |
|
||||||
|
|
||||||
public Iterator(Universe universe, IteratorType? type, ecs_iter_t value) |
|
||||||
{ Universe = universe; Type = type; Value = value; } |
|
||||||
|
|
||||||
public static Iterator FromTerm(Universe universe, Term term) |
|
||||||
{ |
|
||||||
using var alloc = TempAllocator.Use(); |
|
||||||
var flecsTerm = term.ToFlecs(alloc); |
|
||||||
var flecsIter = ecs_term_iter(universe, &flecsTerm); |
|
||||||
return new(universe, IteratorType.Term, flecsIter); |
|
||||||
} |
|
||||||
|
|
||||||
public void Dispose() |
|
||||||
{ |
|
||||||
GC.SuppressFinalize(this); |
|
||||||
// NOTE: When an iterator is iterated until completion, resources are automatically freed. |
|
||||||
if (!Completed) |
|
||||||
fixed (ecs_iter_t* ptr = &Value) |
|
||||||
ecs_iter_fini(ptr); |
|
||||||
} |
|
||||||
|
|
||||||
public void SetThis(Entity entity) |
|
||||||
{ |
|
||||||
fixed (ecs_iter_t* ptr = &Value) |
|
||||||
ecs_iter_set_var(ptr, 0, entity); |
|
||||||
} |
|
||||||
|
|
||||||
public bool Next() |
|
||||||
{ |
|
||||||
fixed (ecs_iter_t* ptr = &Value) { |
|
||||||
var result = Type switch { |
|
||||||
IteratorType.Term => ecs_term_next(ptr), |
|
||||||
IteratorType.Filter => ecs_filter_next(ptr), |
|
||||||
IteratorType.Query => ecs_query_next(ptr), |
|
||||||
IteratorType.Rule => ecs_rule_next(ptr), |
|
||||||
_ => ecs_iter_next(ptr), |
|
||||||
}; |
|
||||||
Completed = !result; |
|
||||||
return result; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public EntityRef Entity(int index) |
|
||||||
=> new(Universe, new(Value.entities[index])); |
|
||||||
|
|
||||||
public Span<T> Field<T>(int index) |
|
||||||
where T : unmanaged |
|
||||||
{ |
|
||||||
fixed (ecs_iter_t* ptr = &Value) { |
|
||||||
var size = (ulong)Unsafe.SizeOf<T>(); |
|
||||||
var isSelf = ecs_field_is_self(ptr, index); |
|
||||||
var pointer = ecs_field_w_size(ptr, size, index); |
|
||||||
return new(pointer, isSelf ? Count : 1); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public Span<T> MaybeField<T>(int index) |
|
||||||
where T : unmanaged => FieldIsSet(index) ? Field<T>(index) : default; |
|
||||||
|
|
||||||
public SpanToRef<T> FieldRef<T>(int index) |
|
||||||
where T : class => new(Field<nint>(index)); |
|
||||||
|
|
||||||
public bool FieldIsSet(int index) |
|
||||||
{ |
|
||||||
fixed (ecs_iter_t* ptr = &Value) |
|
||||||
return ecs_field_is_set(ptr, index); |
|
||||||
} |
|
||||||
|
|
||||||
public bool FieldIs<T>(int index) |
|
||||||
{ |
|
||||||
fixed (ecs_iter_t* ptr = &Value) { |
|
||||||
var id = ecs_field_id(ptr, index); |
|
||||||
var comp = Universe.LookupOrThrow<T>(); |
|
||||||
return id == comp.Entity.Value.Data; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public override string ToString() |
|
||||||
{ |
|
||||||
fixed (ecs_iter_t* ptr = &Value) |
|
||||||
return ecs_iter_str(ptr).FlecsToStringAndFree()!; |
|
||||||
} |
|
||||||
|
|
||||||
// IEnumerable implementation |
|
||||||
public IEnumerator<Iterator> GetEnumerator() { while (Next()) yield return this; } |
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
|
||||||
} |
|
||||||
|
|
||||||
public enum IteratorType |
|
||||||
{ |
|
||||||
Term, |
|
||||||
Filter, |
|
||||||
Query, |
|
||||||
Rule, |
|
||||||
} |
|
@ -1,46 +0,0 @@ |
|||||||
using System; |
|
||||||
using gaemstone.Utility; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public unsafe sealed class Query |
|
||||||
: IDisposable |
|
||||||
{ |
|
||||||
public Universe Universe { get; } |
|
||||||
public ecs_query_t* Handle { get; } |
|
||||||
|
|
||||||
public Query(Universe universe, QueryDesc desc) |
|
||||||
{ |
|
||||||
using var alloc = TempAllocator.Use(); |
|
||||||
var flecsDesc = desc.ToFlecs(alloc); |
|
||||||
Universe = universe; |
|
||||||
Handle = ecs_query_init(universe, &flecsDesc); |
|
||||||
} |
|
||||||
|
|
||||||
public void Dispose() |
|
||||||
=> ecs_query_fini(this); |
|
||||||
|
|
||||||
public Iterator Iter() |
|
||||||
=> new(Universe, IteratorType.Query, ecs_query_iter(Universe, this)); |
|
||||||
|
|
||||||
public override string ToString() |
|
||||||
=> ecs_query_str(Handle).FlecsToStringAndFree()!; |
|
||||||
|
|
||||||
public static implicit operator ecs_query_t*(Query q) => q.Handle; |
|
||||||
} |
|
||||||
|
|
||||||
public class QueryDesc : FilterDesc |
|
||||||
{ |
|
||||||
public QueryDesc(string expression) : base(expression) { } |
|
||||||
public QueryDesc(params Term[] terms) : base(terms) { } |
|
||||||
|
|
||||||
public new unsafe ecs_query_desc_t ToFlecs(IAllocator allocator) |
|
||||||
{ |
|
||||||
var desc = new ecs_query_desc_t { |
|
||||||
filter = base.ToFlecs(allocator), |
|
||||||
// TODO: Implement more Query features. |
|
||||||
}; |
|
||||||
return desc; |
|
||||||
} |
|
||||||
} |
|
@ -1,22 +0,0 @@ |
|||||||
using System; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
[Tag] |
|
||||||
public struct Relation { } |
|
||||||
|
|
||||||
/// <summary> |
|
||||||
/// Marked entity represents a relationship type. |
|
||||||
/// It may be used as the "relation" in a pair. |
|
||||||
/// </summary> |
|
||||||
/// <remarks> |
|
||||||
/// The relationship may have component data associated with |
|
||||||
/// it when added to an entity under these circumstances: |
|
||||||
/// <list type="bullet"> |
|
||||||
/// <item>If marked as a <see cref="TagAttribute"/>, does not carry data.</item> |
|
||||||
/// <item>If marked as a <see cref="ComponentAttribute"/>, carries the relation's data.</item> |
|
||||||
/// <item>If marked with neither, will carry the target's data, if it's a component.</item> |
|
||||||
/// </list> |
|
||||||
/// </remarks> |
|
||||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)] |
|
||||||
public class RelationAttribute : Attribute, ICreateEntityAttribute { } |
|
@ -1,31 +0,0 @@ |
|||||||
using System; |
|
||||||
using gaemstone.Utility; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public unsafe sealed class Rule |
|
||||||
: IDisposable |
|
||||||
{ |
|
||||||
public Universe Universe { get; } |
|
||||||
public ecs_rule_t* Handle { get; } |
|
||||||
|
|
||||||
public Rule(Universe universe, FilterDesc desc) |
|
||||||
{ |
|
||||||
using var alloc = TempAllocator.Use(); |
|
||||||
var flecsDesc = desc.ToFlecs(alloc); |
|
||||||
Universe = universe; |
|
||||||
Handle = ecs_rule_init(universe, &flecsDesc); |
|
||||||
} |
|
||||||
|
|
||||||
public void Dispose() |
|
||||||
=> ecs_rule_fini(this); |
|
||||||
|
|
||||||
public Iterator Iter() |
|
||||||
=> new(Universe, IteratorType.Rule, ecs_rule_iter(Universe, this)); |
|
||||||
|
|
||||||
public override string ToString() |
|
||||||
=> ecs_rule_str(Handle).FlecsToStringAndFree()!; |
|
||||||
|
|
||||||
public static implicit operator ecs_rule_t*(Rule q) => q.Handle; |
|
||||||
} |
|
@ -1,116 +0,0 @@ |
|||||||
using System; |
|
||||||
using gaemstone.Utility; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public class Term |
|
||||||
{ |
|
||||||
public Identifier ID { get; set; } |
|
||||||
public TermID? Source { get; set; } |
|
||||||
public TermID? Relation { get; set; } |
|
||||||
public TermID? Target { get; set; } |
|
||||||
public TermInOutKind InOut { get; set; } |
|
||||||
public TermOperKind Oper { get; set; } |
|
||||||
public IdentifierFlags Flags { get; set; } |
|
||||||
|
|
||||||
public Term() { } |
|
||||||
public Term(Identifier id) => ID = id; |
|
||||||
public Term(TermID relation, TermID target) |
|
||||||
{ Relation = relation; Target = target; } |
|
||||||
|
|
||||||
public static implicit operator Term(EntityRef entity) => new(entity); |
|
||||||
public static implicit operator Term(Entity entity) => new(entity); |
|
||||||
public static implicit operator Term(IdentifierRef id) => new(id); |
|
||||||
public static implicit operator Term(Identifier id) => new(id); |
|
||||||
|
|
||||||
public Term None { get { InOut = TermInOutKind.None; return this; } } |
|
||||||
public Term In { get { InOut = TermInOutKind.In; return this; } } |
|
||||||
public Term Out { get { InOut = TermInOutKind.Out; return this; } } |
|
||||||
|
|
||||||
public Term Or { get { Oper = TermOperKind.Or; return this; } } |
|
||||||
public Term Not { get { Oper = TermOperKind.Not; return this; } } |
|
||||||
public Term Optional { get { Oper = TermOperKind.Optional; return this; } } |
|
||||||
|
|
||||||
public ecs_term_t ToFlecs(IAllocator allocator) => new() { |
|
||||||
id = ID, |
|
||||||
src = Source?.ToFlecs(allocator) ?? default, |
|
||||||
first = Relation?.ToFlecs(allocator) ?? default, |
|
||||||
second = Target?.ToFlecs(allocator) ?? default, |
|
||||||
inout = (ecs_inout_kind_t)InOut, |
|
||||||
oper = (ecs_oper_kind_t)Oper, |
|
||||||
id_flags = (ecs_id_t)(ulong)Flags, |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
public enum TermInOutKind |
|
||||||
{ |
|
||||||
Default = ecs_inout_kind_t.EcsInOutDefault, |
|
||||||
None = ecs_inout_kind_t.EcsInOutNone, |
|
||||||
InOut = ecs_inout_kind_t.EcsInOut, |
|
||||||
In = ecs_inout_kind_t.EcsIn, |
|
||||||
Out = ecs_inout_kind_t.EcsOut, |
|
||||||
} |
|
||||||
|
|
||||||
public enum TermOperKind |
|
||||||
{ |
|
||||||
And = ecs_oper_kind_t.EcsAnd, |
|
||||||
Or = ecs_oper_kind_t.EcsOr, |
|
||||||
Not = ecs_oper_kind_t.EcsNot, |
|
||||||
Optional = ecs_oper_kind_t.EcsOptional, |
|
||||||
AndFrom = ecs_oper_kind_t.EcsAndFrom, |
|
||||||
OrFrom = ecs_oper_kind_t.EcsOrFrom, |
|
||||||
NotFrom = ecs_oper_kind_t.EcsNotFrom, |
|
||||||
} |
|
||||||
|
|
||||||
public class TermID |
|
||||||
{ |
|
||||||
public static TermID This { get; } = new("$This"); |
|
||||||
|
|
||||||
public Entity ID { get; } |
|
||||||
public string? Name { get; } |
|
||||||
public Entity Traverse { get; set; } |
|
||||||
public TermTraversalFlags Flags { get; set; } |
|
||||||
|
|
||||||
public TermID(Entity id) |
|
||||||
=> ID = id; |
|
||||||
public TermID(string name) |
|
||||||
{ |
|
||||||
if (name[0] == '$') { |
|
||||||
Name = name[1..]; |
|
||||||
Flags = TermTraversalFlags.IsVariable; |
|
||||||
} else Name = name; |
|
||||||
} |
|
||||||
|
|
||||||
public static implicit operator TermID(EntityRef entity) => new(entity); |
|
||||||
public static implicit operator TermID(Entity entity) => new(entity); |
|
||||||
public static implicit operator TermID(string name) => new(name); |
|
||||||
|
|
||||||
public ecs_term_id_t ToFlecs(IAllocator allocator) => new() { |
|
||||||
id = ID, |
|
||||||
name = allocator.AllocateCString(Name), |
|
||||||
trav = Traverse, |
|
||||||
flags = (ecs_flags32_t)(uint)Flags |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
[Flags] |
|
||||||
public enum TermTraversalFlags : uint |
|
||||||
{ |
|
||||||
/// <summary> Match on self. </summary> |
|
||||||
Self = EcsSelf, |
|
||||||
/// <summary> Match by traversing upwards. </summary> |
|
||||||
Up = EcsUp, |
|
||||||
/// <summary> Match by traversing downwards (derived, cannot be set). </summary> |
|
||||||
Down = EcsDown, |
|
||||||
/// <summary> Sort results breadth first. </summary> |
|
||||||
Cascade = EcsCascade, |
|
||||||
/// <summary> Short for up(ChildOf). </summary> |
|
||||||
Parent = EcsParent, |
|
||||||
/// <summary> Term id is a variable. </summary> |
|
||||||
IsVariable = EcsIsVariable, |
|
||||||
/// <summary> Term id is an entity. </summary> |
|
||||||
IsEntity = EcsIsEntity, |
|
||||||
/// <summary> Prevent observer from triggering on term. </summary> |
|
||||||
Filter = EcsFilter, |
|
||||||
} |
|
@ -1,63 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Collections.Generic; |
|
||||||
using gaemstone.Utility; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public unsafe partial class Universe |
|
||||||
{ |
|
||||||
private readonly Dictionary<Type, Entity> _lookupByType = new(); |
|
||||||
|
|
||||||
public void AddLookupByType(Type type, Entity entity) |
|
||||||
=> _lookupByType.Add(type, entity); |
|
||||||
public void RemoveLookupByType(Type type) |
|
||||||
{ if (!_lookupByType.Remove(type)) throw new InvalidOperationException( |
|
||||||
$"Lookup for {type} does not exist"); } |
|
||||||
|
|
||||||
|
|
||||||
private EntityRef? CreateOrNull(Entity entity) |
|
||||||
=> EntityRef.CreateOrNull(this, entity); |
|
||||||
|
|
||||||
public EntityRef? Lookup<T>() |
|
||||||
=> Lookup(typeof(T)); |
|
||||||
public EntityRef? Lookup(Type type) |
|
||||||
=> Lookup(_lookupByType.GetValueOrDefault(type)); |
|
||||||
|
|
||||||
public EntityRef? Lookup(Entity value) |
|
||||||
=> CreateOrNull(new(ecs_get_alive(this, value))); |
|
||||||
|
|
||||||
public EntityRef? Lookup(EntityPath path) |
|
||||||
=> Lookup(default, path); |
|
||||||
public EntityRef? Lookup(Entity parent, EntityPath path) |
|
||||||
=> CreateOrNull(EntityPath.Lookup(this, parent, path)); |
|
||||||
public EntityRef? LookupSymbol(string symbol) |
|
||||||
{ |
|
||||||
using var alloc = TempAllocator.Use(); |
|
||||||
return CreateOrNull(new(ecs_lookup_symbol(this, alloc.AllocateCString(symbol), false))); |
|
||||||
} |
|
||||||
|
|
||||||
public EntityRef LookupOrThrow<T>() => LookupOrThrow(typeof(T)); |
|
||||||
public EntityRef LookupOrThrow(Type type) => Lookup(type) |
|
||||||
?? throw new EntityNotFoundException($"Entity of type {type} not found"); |
|
||||||
public EntityRef LookupOrThrow(Entity entity) => Lookup(entity) |
|
||||||
?? throw new EntityNotFoundException($"Entity {entity} not alive"); |
|
||||||
public EntityRef LookupOrThrow(EntityPath path) => Lookup(default, path) |
|
||||||
?? throw new EntityNotFoundException($"Entity '{path}' not found"); |
|
||||||
public EntityRef LookupOrThrow(Entity parent, EntityPath path) => Lookup(parent, path) |
|
||||||
?? throw new EntityNotFoundException($"Child entity of {parent} '{path}' not found"); |
|
||||||
public EntityRef LookupSymbolOrThrow(string symbol) => LookupSymbol(symbol) |
|
||||||
?? throw new EntityNotFoundException($"Entity with symbol '{symbol}' not found"); |
|
||||||
|
|
||||||
|
|
||||||
public class EntityNotFoundException : Exception |
|
||||||
{ public EntityNotFoundException(string message) : base(message) { } } |
|
||||||
} |
|
||||||
|
|
||||||
public static class LookupExtensions |
|
||||||
{ |
|
||||||
public static EntityRef CreateLookup<T>(this EntityRef entity) |
|
||||||
=> entity.CreateLookup(typeof(T)); |
|
||||||
public static EntityRef CreateLookup(this EntityRef entity, Type type) |
|
||||||
{ entity.Universe.AddLookupByType(type, entity); return entity; } |
|
||||||
} |
|
@ -1,51 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Linq; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.ECS; |
|
||||||
|
|
||||||
public unsafe partial class Universe |
|
||||||
{ |
|
||||||
public ecs_world_t* Handle { get; } |
|
||||||
public ModuleManager Modules { get; } |
|
||||||
|
|
||||||
public EntityRef Wildcard { get; } |
|
||||||
public EntityRef Any { get; } |
|
||||||
public EntityRef ChildOf { get; } |
|
||||||
|
|
||||||
public bool IsDeferred => ecs_is_deferred(this); |
|
||||||
|
|
||||||
public Universe(params string[] args) |
|
||||||
{ |
|
||||||
Handle = ecs_init_w_args(args.Length, null); |
|
||||||
Modules = new(this); |
|
||||||
|
|
||||||
// Bootstrap flecs built-ins that are important to internals. |
|
||||||
Wildcard = LookupOrThrow("/flecs/core/*"); |
|
||||||
Any = LookupOrThrow("/flecs/core/_"); |
|
||||||
ChildOf = LookupOrThrow("/flecs/core/ChildOf"); |
|
||||||
|
|
||||||
// Bootstrap custom "Relation" tag. |
|
||||||
// This will be retrofitted to certain flecs built-ins. |
|
||||||
New("/gaemstone/Relation") |
|
||||||
.Add(LookupOrThrow("/flecs/core/Tag")) |
|
||||||
.Build().CreateLookup<Relation>(); |
|
||||||
|
|
||||||
// Register built-in (static) modules, which |
|
||||||
// are defined in the "gaemstone.Flecs" namespace. |
|
||||||
Modules.RegisterAll(GetType().Assembly.GetTypes() |
|
||||||
.Where(t => t.IsAbstract && t.IsSealed)); |
|
||||||
|
|
||||||
// Create "Game" entity to store global state. |
|
||||||
New("/Game").Symbol("Game").Build() |
|
||||||
.CreateLookup<Game>().Add<Game>(); |
|
||||||
} |
|
||||||
|
|
||||||
public EntityBuilder New(EntityPath? path = null) |
|
||||||
=> new(this, path); |
|
||||||
|
|
||||||
public bool Progress(TimeSpan delta) |
|
||||||
=> ecs_progress(this, (float)delta.TotalSeconds); |
|
||||||
|
|
||||||
public static implicit operator ecs_world_t*(Universe w) => w.Handle; |
|
||||||
} |
|
@ -0,0 +1,81 @@ |
|||||||
|
using System.Runtime.CompilerServices; |
||||||
|
using System.Runtime.InteropServices; |
||||||
|
using gaemstone.ECS; |
||||||
|
using gaemstone.Utility; |
||||||
|
using static flecs_hub.flecs; |
||||||
|
|
||||||
|
namespace gaemstone.Flecs; |
||||||
|
|
||||||
|
[Module, Path("/flecs/doc")] |
||||||
|
public static class Doc |
||||||
|
{ |
||||||
|
[Tag] public struct Brief { } |
||||||
|
[Tag] public struct Detail { } |
||||||
|
[Tag] public struct Link { } |
||||||
|
[Tag] public struct Color { } |
||||||
|
|
||||||
|
[Relation, Component] |
||||||
|
public struct Description |
||||||
|
{ |
||||||
|
internal unsafe void* Value; |
||||||
|
|
||||||
|
public override string? ToString() |
||||||
|
{ unsafe { return Marshal.PtrToStringUTF8((nint)Value); } } |
||||||
|
public static implicit operator string?(Description desc) |
||||||
|
=> desc.ToString(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static unsafe class DocExtensions |
||||||
|
{ |
||||||
|
private static EntityRef Set<T>(EntityRef entity, string? value) |
||||||
|
{ |
||||||
|
var world = entity.World; |
||||||
|
var descEntity = world.LookupByTypeOrThrow<Doc.Description>(); |
||||||
|
var docEntity = world.LookupByTypeOrThrow<T>(); |
||||||
|
var id = Identifier.Pair(descEntity, docEntity); |
||||||
|
|
||||||
|
var has = entity.Has(id); |
||||||
|
var alloc = GlobalHeapAllocator.Instance; |
||||||
|
if (value != null) { |
||||||
|
var ptr = ecs_get_mut_id(world, entity, id); |
||||||
|
ref var desc = ref Unsafe.AsRef<Doc.Description>(ptr); |
||||||
|
// 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) { |
||||||
|
// var @ref = ecs_ref_init_id(world, entity, id); |
||||||
|
// var ptr = ecs_ref_get_id(world, &@ref, id); |
||||||
|
// var desc = Unsafe.AsRef<Doc.Description>(ptr); |
||||||
|
// alloc.Free((nint)desc.Value); // Free previous value. |
||||||
|
entity.Remove(id); |
||||||
|
} |
||||||
|
return entity; |
||||||
|
} |
||||||
|
|
||||||
|
public static string? GetDocName(this EntityRef entity, bool fallbackToEntityName = true) |
||||||
|
=> fallbackToEntityName || entity.Has<Doc.Description, Core.Name>() |
||||||
|
? ecs_doc_get_name(entity.World, entity).FlecsToString() : null; |
||||||
|
public static EntityRef SetDocName(this EntityRef entity, string? value) |
||||||
|
=> Set<Core.Name>(entity, value); |
||||||
|
|
||||||
|
public static string? GetDocBrief(this EntityRef entity) |
||||||
|
=> ecs_doc_get_brief(entity.World, entity).FlecsToString()!; |
||||||
|
public static EntityRef SetDocBrief(this EntityRef entity, string? value) |
||||||
|
=> Set<Doc.Brief>(entity, value); |
||||||
|
|
||||||
|
public static string? GetDocDetail(this EntityRef entity) |
||||||
|
=> ecs_doc_get_detail(entity.World, entity).FlecsToString()!; |
||||||
|
public static EntityRef SetDocDetail(this EntityRef entity, string? value) |
||||||
|
=> Set<Doc.Detail>(entity, value); |
||||||
|
|
||||||
|
public static string? GetDocLink(this EntityRef entity) |
||||||
|
=> ecs_doc_get_link(entity.World, entity).FlecsToString()!; |
||||||
|
public static EntityRef SetDocLink(this EntityRef entity, string? value) |
||||||
|
=> Set<Doc.Link>(entity, value); |
||||||
|
|
||||||
|
public static string? GetDocColor(this EntityRef entity) |
||||||
|
=> ecs_doc_get_color(entity.World, entity).FlecsToString()!; |
||||||
|
public static EntityRef SetDocColor(this EntityRef entity, string? value) |
||||||
|
=> Set<Doc.Color>(entity, value); |
||||||
|
} |
@ -1,36 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Diagnostics; |
|
||||||
using System.Runtime.InteropServices; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
|
|
||||||
namespace gaemstone.Flecs; |
|
||||||
|
|
||||||
public class FlecsException |
|
||||||
: Exception |
|
||||||
{ |
|
||||||
public FlecsException() : base() { } |
|
||||||
public FlecsException(string message) : base(message) { } |
|
||||||
} |
|
||||||
|
|
||||||
public class FlecsAbortException |
|
||||||
: FlecsException |
|
||||||
{ |
|
||||||
private readonly string _stackTrace = new StackTrace(2, true).ToString(); |
|
||||||
public override string? StackTrace => _stackTrace; |
|
||||||
|
|
||||||
private FlecsAbortException() |
|
||||||
: base("Abort was called by flecs") { } |
|
||||||
|
|
||||||
// TODO: This might not be ideal if we ever want to set other OS API settings. |
|
||||||
public unsafe static void SetupHook() |
|
||||||
{ |
|
||||||
ecs_os_set_api_defaults(); |
|
||||||
var api = ecs_os_get_api(); |
|
||||||
api.abort_ = new FnPtr_Void { Pointer = &Abort }; |
|
||||||
ecs_os_set_api(&api); |
|
||||||
} |
|
||||||
|
|
||||||
[UnmanagedCallersOnly] |
|
||||||
private static void Abort() |
|
||||||
=> throw new FlecsAbortException(); |
|
||||||
} |
|
@ -0,0 +1,35 @@ |
|||||||
|
using System.Linq; |
||||||
|
using gaemstone.ECS; |
||||||
|
|
||||||
|
namespace gaemstone; |
||||||
|
|
||||||
|
public class Universe : World |
||||||
|
{ |
||||||
|
public ModuleManager Modules { get; } |
||||||
|
|
||||||
|
public Universe(params string[] args) |
||||||
|
: base(args) |
||||||
|
{ |
||||||
|
Modules = new(this); |
||||||
|
|
||||||
|
// Bootstrap some stuff, so we can register flecs properly. |
||||||
|
New("/gaemstone/Doc/DisplayType") |
||||||
|
.Add(LookupByPathOrThrow("/flecs/core/Exclusive")) |
||||||
|
.Build().CreateLookup<Doc.DisplayType>(); |
||||||
|
New("/gaemstone/Doc/Relation").Build().CreateLookup<Doc.Relation>(); |
||||||
|
LookupByPathOrThrow("/flecs/core/Module" ).CreateLookup<Flecs.Core.Module>(); |
||||||
|
LookupByPathOrThrow("/flecs/core/Component").CreateLookup<Flecs.Core.Component>(); |
||||||
|
LookupByPathOrThrow("/flecs/core/Tag" ).CreateLookup<Flecs.Core.Tag>(); |
||||||
|
|
||||||
|
// Register built-in (static) modules, which |
||||||
|
// are defined in the "gaemstone.Flecs" namespace. |
||||||
|
Modules.RegisterAll(GetType().Assembly.GetTypes() |
||||||
|
.Where(t => t.IsAbstract && t.IsSealed)); |
||||||
|
|
||||||
|
Modules.Register<Doc>(); |
||||||
|
|
||||||
|
// Create "Game" entity to store global state. |
||||||
|
New("/Game").Symbol("Game").Build() |
||||||
|
.CreateLookup<Game>().Add<Game>(); |
||||||
|
} |
||||||
|
} |
@ -1,167 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Runtime.CompilerServices; |
|
||||||
using System.Runtime.InteropServices; |
|
||||||
using System.Text; |
|
||||||
using System.Threading; |
|
||||||
using static flecs_hub.flecs.Runtime; |
|
||||||
|
|
||||||
namespace gaemstone.Utility; |
|
||||||
|
|
||||||
public interface IAllocator |
|
||||||
{ |
|
||||||
nint Allocate(int byteCount); |
|
||||||
void Free(nint pointer); |
|
||||||
} |
|
||||||
|
|
||||||
public unsafe static class AllocatorExtensions |
|
||||||
{ |
|
||||||
public static Span<T> Allocate<T>(this IAllocator allocator, int count) where T : unmanaged |
|
||||||
=> new((void*)allocator.Allocate(sizeof(T) * count), count); |
|
||||||
public static void Free<T>(this IAllocator allocator, Span<T> span) where T : unmanaged |
|
||||||
=> allocator.Free((nint)Unsafe.AsPointer(ref span[0])); |
|
||||||
|
|
||||||
public static Span<T> AllocateCopy<T>(this IAllocator allocator, ReadOnlySpan<T> orig) where T : unmanaged |
|
||||||
{ var copy = allocator.Allocate<T>(orig.Length); orig.CopyTo(copy); return copy; } |
|
||||||
|
|
||||||
public static ref T Allocate<T>(this IAllocator allocator) where T : unmanaged |
|
||||||
=> ref Unsafe.AsRef<T>((void*)allocator.Allocate(sizeof(T))); |
|
||||||
public static void Free<T>(this IAllocator allocator, ref T value) where T : unmanaged |
|
||||||
=> allocator.Free((nint)Unsafe.AsPointer(ref value)); |
|
||||||
|
|
||||||
public static CString AllocateCString(this IAllocator allocator, string? value) |
|
||||||
{ |
|
||||||
if (value == null) return default; |
|
||||||
var bytes = Encoding.UTF8.GetByteCount(value); |
|
||||||
var span = allocator.Allocate<byte>(bytes + 1); |
|
||||||
Encoding.UTF8.GetBytes(value, span); |
|
||||||
span[^1] = 0; // In case the allocated span is not cleared. |
|
||||||
return new((nint)Unsafe.AsPointer(ref span[0])); |
|
||||||
} |
|
||||||
|
|
||||||
public static CString AllocateCString(this IAllocator allocator, ReadOnlySpan<byte> utf8) |
|
||||||
{ |
|
||||||
var copy = allocator.Allocate<byte>(utf8.Length + 1); |
|
||||||
utf8.CopyTo(copy); |
|
||||||
copy[^1] = 0; // In case the allocated span is not cleared. |
|
||||||
return new((nint)Unsafe.AsPointer(ref copy[0])); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class TempAllocator |
|
||||||
{ |
|
||||||
public const int Capacity = 1024 * 1024; // 1 MB |
|
||||||
private static readonly ThreadLocal<ArenaAllocator> _allocator |
|
||||||
= new(() => new(Capacity)); |
|
||||||
|
|
||||||
public static ResetOnDispose Use() |
|
||||||
{ |
|
||||||
var allocator = _allocator.Value!; |
|
||||||
return new(allocator, allocator.Used); |
|
||||||
} |
|
||||||
|
|
||||||
public sealed class ResetOnDispose |
|
||||||
: IAllocator |
|
||||||
, IDisposable |
|
||||||
{ |
|
||||||
private readonly ArenaAllocator _allocator; |
|
||||||
private readonly int _start; |
|
||||||
|
|
||||||
public ResetOnDispose(ArenaAllocator allocator, int start) |
|
||||||
{ _allocator = allocator; _start = start; } |
|
||||||
|
|
||||||
// IAllocator implementation |
|
||||||
public nint Allocate(int byteCount) => _allocator.Allocate(byteCount); |
|
||||||
public void Free(nint pointer) { /* Do nothing. */ } |
|
||||||
|
|
||||||
// IDisposable implementation |
|
||||||
public void Dispose() => _allocator.Reset(_start); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public class GlobalHeapAllocator |
|
||||||
: IAllocator |
|
||||||
{ |
|
||||||
public nint Allocate(int byteCount) |
|
||||||
=> Marshal.AllocHGlobal(byteCount); |
|
||||||
public void Free(nint pointer) |
|
||||||
=> Marshal.FreeHGlobal(pointer); |
|
||||||
} |
|
||||||
|
|
||||||
public sealed class ArenaAllocator |
|
||||||
: IAllocator |
|
||||||
, IDisposable |
|
||||||
{ |
|
||||||
private nint _buffer; |
|
||||||
public int Capacity { get; private set; } |
|
||||||
public int Used { get; private set; } = 0; |
|
||||||
|
|
||||||
public ArenaAllocator(int capacity) |
|
||||||
{ |
|
||||||
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity)); |
|
||||||
_buffer = Marshal.AllocHGlobal(capacity); |
|
||||||
Capacity = capacity; |
|
||||||
} |
|
||||||
|
|
||||||
public void Dispose() |
|
||||||
{ |
|
||||||
Marshal.FreeHGlobal(_buffer); |
|
||||||
_buffer = default; |
|
||||||
Capacity = 0; |
|
||||||
} |
|
||||||
|
|
||||||
public nint Allocate(int byteCount) |
|
||||||
{ |
|
||||||
if (_buffer == default) throw new ObjectDisposedException(nameof(ArenaAllocator)); |
|
||||||
if (Used + byteCount > Capacity) throw new InvalidOperationException( |
|
||||||
$"Cannot allocate more than {Capacity} bytes with this {nameof(ArenaAllocator)}"); |
|
||||||
var ptr = _buffer + Used; |
|
||||||
Used += byteCount; |
|
||||||
return ptr; |
|
||||||
} |
|
||||||
|
|
||||||
public void Free(nint pointer) |
|
||||||
{ /* Do nothing. */ } |
|
||||||
|
|
||||||
public unsafe void Reset(int start = 0) |
|
||||||
{ |
|
||||||
if (start > Used) throw new ArgumentOutOfRangeException(nameof(start)); |
|
||||||
new Span<byte>((void*)(_buffer + start), Used - start).Clear(); |
|
||||||
Used = start; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public sealed class RingAllocator |
|
||||||
: IAllocator |
|
||||||
, IDisposable |
|
||||||
{ |
|
||||||
private nint _buffer; |
|
||||||
private int _current = 0; |
|
||||||
public int Capacity { get; private set; } |
|
||||||
|
|
||||||
public RingAllocator(int capacity) |
|
||||||
{ |
|
||||||
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity)); |
|
||||||
_buffer = Marshal.AllocHGlobal(capacity); |
|
||||||
Capacity = capacity; |
|
||||||
} |
|
||||||
|
|
||||||
public void Dispose() |
|
||||||
{ |
|
||||||
Marshal.FreeHGlobal(_buffer); |
|
||||||
_buffer = default; |
|
||||||
Capacity = 0; |
|
||||||
} |
|
||||||
|
|
||||||
public nint Allocate(int byteCount) |
|
||||||
{ |
|
||||||
if (_buffer == default) throw new ObjectDisposedException(nameof(RingAllocator)); |
|
||||||
if (byteCount > Capacity) throw new ArgumentOutOfRangeException(nameof(byteCount)); |
|
||||||
if (_current + byteCount > Capacity) _current = 0; |
|
||||||
var ptr = _buffer + _current; |
|
||||||
_current += byteCount; |
|
||||||
return ptr; |
|
||||||
} |
|
||||||
|
|
||||||
public void Free(nint pointer) |
|
||||||
{ /* IGNORE */ } |
|
||||||
} |
|
@ -1,34 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Runtime.InteropServices; |
|
||||||
using static flecs_hub.flecs; |
|
||||||
using static flecs_hub.flecs.Runtime; |
|
||||||
|
|
||||||
namespace gaemstone.Utility; |
|
||||||
|
|
||||||
public unsafe static class CStringExtensions |
|
||||||
{ |
|
||||||
public static CString Empty { get; } = (CString)""; |
|
||||||
public static CString ETX { get; } = (CString)"\x3"; // TODO: Temporary, until flecs supports Empty. |
|
||||||
|
|
||||||
public static unsafe byte[]? FlecsToBytes(this CString str) |
|
||||||
{ |
|
||||||
if (str.IsNull) return null; |
|
||||||
var pointer = (byte*)(nint)str; |
|
||||||
// Find length of the string by locating the NUL character. |
|
||||||
var length = 0; while (true) if (pointer[length++] == 0) break; |
|
||||||
// Create span over the region, NUL included, copy it into an array. |
|
||||||
return new Span<byte>(pointer, length).ToArray(); |
|
||||||
} |
|
||||||
|
|
||||||
public static byte[]? FlecsToBytesAndFree(this CString str) |
|
||||||
{ var result = str.FlecsToBytes(); str.FlecsFree(); return result; } |
|
||||||
|
|
||||||
public static string? FlecsToString(this CString str) |
|
||||||
=> Marshal.PtrToStringUTF8(str); |
|
||||||
|
|
||||||
public static string? FlecsToStringAndFree(this CString str) |
|
||||||
{ var result = str.FlecsToString(); str.FlecsFree(); return result; } |
|
||||||
|
|
||||||
public static void FlecsFree(this CString str) |
|
||||||
{ if (!str.IsNull) ecs_os_get_api().free_.Data.Pointer((void*)(nint)str); } |
|
||||||
} |
|
@ -1,14 +0,0 @@ |
|||||||
using System.Collections.Generic; |
|
||||||
|
|
||||||
namespace gaemstone.Utility; |
|
||||||
|
|
||||||
public static class CallbackContextHelper |
|
||||||
{ |
|
||||||
private static readonly List<object> _contexts = new(); |
|
||||||
|
|
||||||
public static nint Create<T>(T context) where T : notnull |
|
||||||
{ _contexts.Add(context); return _contexts.Count; } |
|
||||||
|
|
||||||
public static T Get<T>(nint context) |
|
||||||
=> (T)_contexts[(int)context - 1]; |
|
||||||
} |
|
@ -1,19 +0,0 @@ |
|||||||
using System; |
|
||||||
using System.Runtime.InteropServices; |
|
||||||
|
|
||||||
namespace gaemstone.Utility; |
|
||||||
|
|
||||||
public readonly ref struct SpanToRef<T> |
|
||||||
where T : class
|
|
||||||
{ |
|
||||||
private readonly Span<nint> _span; |
|
||||||
|
|
||||||
public int Length => _span.Length; |
|
||||||
public T? this[int index] => (_span[index] != 0) |
|
||||||
? (T)((GCHandle)_span[index]).Target! : null; |
|
||||||
|
|
||||||
internal SpanToRef(Span<nint> span) => _span = span; |
|
||||||
|
|
||||||
public void Clear() => _span.Clear(); |
|
||||||
public void CopyTo(SpanToRef<T> dest) => _span.CopyTo(dest._span); |
|
||||||
} |
|
Loading…
Reference in new issue