diff --git a/.gitignore b/.gitignore index abec243..70cb920 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # Directories build/ +recovery/ # Files -BeefSpace_User.toml \ No newline at end of file +BeefSpace_User.toml +Editor/imgui.ini \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index a693974..b5b2a1b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,10 +1,18 @@ -[submodule "Libs/ImGui"] - path = Libs/ImGui - url = https://github.com/Beef-Community-Project/imgui-beef - branch = docking -[submodule "Libs/GLFW"] +[submodule "GLFW"] path = Libs/GLFW - url = https://github.com/MineGame159/glfw-beef.git + url = https://github.com/MineGame159/glfw-beef [submodule "BGFX"] path = Libs/BGFX url = https://github.com/ExoKomodo/Bgfx-bf +[submodule "JSON_Beef"] + path = Libs/JSON_Beef + url = https://github.com/Jonathan-Racaud/JSON_Beef +[submodule "Beef-Extensions-Lib"] + path = Libs/Beef-Extensions-Lib + url = https://github.com/Jonathan-Racaud/Beef-Extensions-Lib +[submodule "ImGui"] + path = Libs/ImGui + url = https://github.com/RogueMacro/imgui-beef +[submodule "MsgPack"] + path = Libs/MsgPack + url = https://github.com/NicEastvillage/MsgPackBf diff --git a/BeefSpace.toml b/BeefSpace.toml index b3b6937..c451215 100644 --- a/BeefSpace.toml +++ b/BeefSpace.toml @@ -1,5 +1,6 @@ -FileVersion = 1 -Projects = {SteelEngine = {Path = "Engine"}, SteelEditor = {Path = "Editor"}, glfw-beef = {Path = "Libs/GLFW"}, BasicSteelGame = {Path = "Examples/BasicSteelGame"}} - -[Workspace] -StartupProject = "BasicSteelGame" +FileVersion = 1 +Projects = {SteelEngine = {Path = "Engine"}, SteelEditor = {Path = "Editor"}, BeefExtensionsLib = {Path = "Libs/Beef-Extensions-Lib"}, JSON_Beef = {Path = "Libs/JSON_Beef/lib"}, ImGuiImplGlfw = {Path = "Libs/ImGui/ImGuiImplGlfw"}, ImGuiImplOpenGL3 = {Path = "Libs/ImGui/ImGuiImplOpenGL3"}, glfw-beef = {Path = "Libs/GLFW"}, ImGui = {Path = "Libs/ImGui/ImGui"}, MsgPackBf = {Path = "Libs/MsgPack/lib"}} +Locked = ["BeefExtensionsLib", "JSON_Beef", "ImGuiImplGlfw", "ImGuiImplOpenGL3", "glfw-beef", "ImGui", "MsgPackBf"] + +[Workspace] +StartupProject = "SteelEditor" diff --git a/Editor/BeefProj.toml b/Editor/BeefProj.toml index bc3ed3c..ff2e1f2 100644 --- a/Editor/BeefProj.toml +++ b/Editor/BeefProj.toml @@ -1,6 +1,10 @@ FileVersion = 1 -Dependencies = {corlib = "*", glfw-beef = "*", SteelEngine = "*"} +Dependencies = {corlib = "*", SteelEngine = "*", BeefExtensionsLib = "*", ImGuiImplGlfw = "*", ImGuiImplOpenGL3 = "*", ImGui = "*", MsgPackBf = "*", JSON_Beef = "*"} [Project] Name = "SteelEditor" StartupObject = "SteelEditor.Program" + +[Configs.Debug.Win64] +BeefLibType = "DynamicDebug" +DebugWorkingDirectory = "$(ProjectDir)\\.." diff --git a/Editor/Samples/NewProject/BeefProj.toml b/Editor/Samples/NewProject/BeefProj.toml new file mode 100644 index 0000000..f30a41f --- /dev/null +++ b/Editor/Samples/NewProject/BeefProj.toml @@ -0,0 +1,5 @@ +FileVersion = 1 + +[Project] +Name = "$(ProjectName)" +StartupObject = "$(ProjectName).Program" diff --git a/Editor/Samples/NewProject/BeefSpace.toml b/Editor/Samples/NewProject/BeefSpace.toml new file mode 100644 index 0000000..3d622b1 --- /dev/null +++ b/Editor/Samples/NewProject/BeefSpace.toml @@ -0,0 +1,5 @@ +FileVersion = 1 +Projects = {$(ProjectName) = {Path = "."}, SteelEngine = {Path = "../../Engine"}, glfw-beef = {Path = "../../Libs/GLFW"}} + +[Workspace] +StartupProject = "$(ProjectName)" diff --git a/Editor/Samples/NewProject/SteelProj.json b/Editor/Samples/NewProject/SteelProj.json new file mode 100644 index 0000000..831e654 --- /dev/null +++ b/Editor/Samples/NewProject/SteelProj.json @@ -0,0 +1,4 @@ +{ + "Name": "$(ProjectName)", + "Entities": [] +} diff --git a/Editor/Samples/NewProject/src/$(ProjectName).bf b/Editor/Samples/NewProject/src/$(ProjectName).bf new file mode 100644 index 0000000..797dba0 --- /dev/null +++ b/Editor/Samples/NewProject/src/$(ProjectName).bf @@ -0,0 +1,25 @@ +using SteelEngine; + +namespace SteelEditor +{ + public class $(ProjectName) : Application + { + // Gets called when the application is run + public override void OnInit() + { + + } + + // Gets called every frame + protected override void OnUpdate() + { + + } + + // Gets called when the application exits + public override void OnCleanup() + { + + } + } +} diff --git a/Editor/Samples/NewProject/src/Program.bf b/Editor/Samples/NewProject/src/Program.bf new file mode 100644 index 0000000..af8532f --- /dev/null +++ b/Editor/Samples/NewProject/src/Program.bf @@ -0,0 +1,16 @@ +using System; + +namespace $(ProjectName) +{ + class Program + { + public static int Main(String[] args) + { + var app = new $(ProjectName); + app.Run(); + delete app; + + return 0; + } + } +} \ No newline at end of file diff --git a/Editor/Themes/Default.txt b/Editor/Themes/Default.txt new file mode 100644 index 0000000..483d6ce --- /dev/null +++ b/Editor/Themes/Default.txt @@ -0,0 +1,35 @@ +Alpha = 1 +WindowPadding = [8, 8] +WindowRounding = 0 +WindowBorderSize = 0 +WindowMinSize = [32, 32] +WindowTitleAlign = [0, 0.5] +WindowMenuButtonPosition = None +ChildRounding = 0 +ChildBorderSize = 1 +PopupRounding = 0 +PopupBorderSize = 0 +FramePadding = [4, 3] +FrameRounding = 0 +ItemSpacing = [8, 4] +ItemInnerSpacing = [4, 4] +TouchExtraPadding = [0, 0] +IndentSpacing = 21 +ColumnsMinSpacing = 6 +ScrollbarSize = 14 +ScrollbarRounding = 0 +GrabMinSize = 10 +GrabRounding = 0 +TabRounding = 0 +TabBorderSize = 0 +ColorButtonPosition = Right +ButtonTextAlign = [0.5, 0.5] +SelectableTextAlign = [0, 0] +DisplayWindowPadding = [19, 19] +DisplaySafeAreaPadding = [3, 3] +MouseCursorScale = 1 +AntiAliasedLines = True +AntiAliasedFill = True +CurveTessellationTol = 1.25 +CircleSegmentMaxError = 1.60000002 +Colors = [[0.899999976, 0.899999976, 0.899999976, 1], [0.600000024, 0.600000024, 0.600000024, 1], [0.0705882385, 0.0823529437, 0.0980392173, 1], [0, 0, 0, 0], [0.109999999, 0.109999999, 0.140000001, 0.920000017], [0.5, 0.5, 0.5, 0.5], [0, 0, 0, 0], [0.430000007, 0.430000007, 0.430000007, 0.389999986], [0.469999999, 0.469999999, 0.689999998, 0.400000006], [0.419999987, 0.409999996, 0.639999986, 0.689999998], [0.132840857, 0.157223493, 0.18784529, 1], [0.109803922, 0.129411772, 0.152941182, 1], [0.400000006, 0.400000006, 0.800000012, 0.200000003], [0.124446772, 0.138211429, 0.149171293, 0.800000012], [0.200000003, 0.25, 0.300000012, 0.600000024], [0.400000006, 0.400000006, 0.800000012, 0.300000012], [0.400000006, 0.400000006, 0.800000012, 0.400000006], [0.409999996, 0.389999986, 0.800000012, 0.600000024], [0.899999976, 0.899999976, 0.899999976, 0.5], [1, 1, 1, 0.300000012], [0.409999996, 0.389999986, 0.800000012, 0.600000024], [0.196078435, 0.200000003, 0.203921571, 1], [0.300753921, 0.312140822, 0.325966835, 1], [0.392967284, 0.407308578, 0.453038692, 1], [0.400000006, 0.400000006, 0.899999976, 0.449999988], [0.449999988, 0.449999988, 0.899999976, 0.800000012], [0.529999971, 0.529999971, 0.870000005, 0.800000012], [0, 0, 0, 0], [0.600000024, 0.600000024, 0.699999988, 1], [0.699999988, 0.699999988, 0.899999976, 1], [1, 1, 1, 0.159999996], [0.779999971, 0.819999993, 1, 0.600000024], [0.779999971, 0.819999993, 1, 0.899999976], [0.176470593, 0.192156866, 0.20784314, 1], [0.449999988, 0.449999988, 0.899999976, 0.800000012], [0.270588249, 0.282352954, 0.298039228, 1], [0.176470593, 0.192156866, 0.20784314, 1], [0.270588249, 0.282352954, 0.298039228, 1], [0.400000006, 0.400000006, 0.899999976, 0.314999998], [0.200000003, 0.200000003, 0.200000003, 1], [1, 1, 1, 1], [0.899999976, 0.699999988, 0, 1], [0.899999976, 0.699999988, 0, 1], [1, 0.600000024, 0, 1], [0, 0, 1, 0.349999994], [1, 1, 0, 0.899999976], [0.449999988, 0.449999988, 0.899999976, 0.800000012], [1, 1, 1, 0.699999988], [0.800000012, 0.800000012, 0.800000012, 0.200000003], [0.200000003, 0.200000003, 0.200000003, 0.349999994]] \ No newline at end of file diff --git a/Editor/src/Editor.bf b/Editor/src/Editor.bf new file mode 100644 index 0000000..ae486da --- /dev/null +++ b/Editor/src/Editor.bf @@ -0,0 +1,758 @@ +using System; +using System.Collections; +using System.IO; +using SteelEngine; +using SteelEngine.Window; +using SteelEngine.ECS; +using SteelEditor.UI; +using SteelEditor.Serialization; +using ImGui; +using JSON_Beef.Serialization; +using MsgPackBf; + +namespace SteelEditor +{ + public class Editor : Application + { + private EditorLayer _editorLayer; + + private Dictionary _entityNames = new .(); + private EditorCache _cache = new .(true) ~ delete _; + private bool _wantsSave = false; + + public EditorProject CurrentProject = EditorProject.UntitledProject() ~ delete _; + + private String _currentTheme = new .() ~ delete _; + + [Serializable] + class TestData + { + int Number = 42; + String Textt = "Lorum Ipsum"; + TestDataProp Prop = new TestDataProp() ~ delete _; + TestDataPropStruct Prop2 = TestDataPropStruct(); + } + + [Serializable] + class TestDataProp + { + int Prop1 = 1; + int Prop2 = 2; + } + + [AttributeUsage(.Class | .Struct, ReflectUser=.AllMembers | .DynamicBoxing)] + struct SerializableAttribute : Attribute + { + } + + [Serializable] + struct TestDataPropStruct + { + int Prop3 = 3; + int Prop4 = 4; + } + + public override void OnInit() + { + _editorLayer = new .(Window); + PushLayer(_editorLayer); + + RegisterWindow(); + RegisterWindow(); + RegisterWindow(); + RegisterWindow(); + RegisterWindow(); + RegisterWindow(); + RegisterWindow(); + + LoadConfig(); + LoadCache(); + + UpdateTitle(); + + Log.Trace("Editor Resource Path: {}", SteelPath.EditorInstallationPath); + + //Extensions.Load(); + + /*var buffer = new uint8[256]; + var packer = scope MsgPacker(buffer); + int totalSize = 0; + packer.WriteMapHeader(1); + totalSize = GetAndPrintSize("Map header"); + packer.Write("Prop12"); + totalSize = GetAndPrintSize("Key"); + int i = 5423543267; + packer.Write(i); + + totalSize = GetAndPrintSize("int"); + + Log.Info("Total size: {}", totalSize); + PrintBuffer(); + delete buffer; + + void PrintBuffer() + { + for (int i = 0; i < GetBufferSize(); i++) + Console.Write($"{buffer[i]} "); + Console.WriteLine(); + } + + int GetAndPrintSize(StringView sizeOfWhat) + { + var size = GetBufferSize(); + Log.Info($"{sizeOfWhat} size: {size - totalSize}"); + return size; + } + + int GetBufferSize() + { + int i = 0; + for (i = buffer.Count - 1; i > 0; i--) + { + if (buffer[i] != 0) + break; + } + + return i + 1; + }*/ + + TestOutputSizeEstimate(); + var testBuffer = new:Serializer.AllocateBuffer! uint[2]; + + var data = scope TestData(); + var serialized = new uint8[Serializer.GetOutputSize(data)]; + Serializer.Serialize(data, serialized); + Console.Write("Serialized: ["); + for (uint8 bin in serialized) + Console.Write("{}, ", bin); + Console.WriteLine("]"); + var unpacker = scope MsgUnpacker(serialized); + //var propCount = unpacker.ReadMapHeader().Get(); + //Log.Info("Properties Count: {}", propCount); + + delete serialized; + } + + private void TestOutputSizeEstimate() + { + Log.Info("-------------------------------"); + Log.Info("- Output size estimation test -"); + Log.Info("-------------------------------"); + + var data = scope TestData(); + int outputSize = Serializer.GetOutputSize(data); + Log.Info("Estimated output size: {}", outputSize); + + var buffer = new uint8[256]; + Serializer.Serialize(data, buffer); + + var actualSize = Serializer.[Friend]GetBufferSize(buffer); + + Log.Info("Actual size: {}", actualSize); + + Console.WriteLine(); + Console.Write("Buffer: ["); + for (uint8 bin in buffer) + Console.Write("{}, ", bin); + Console.WriteLine("]"); + + + Log.Info("-------------------------------"); + delete buffer; + } + + public override void OnCleanup() + { + SaveConfig(); + SaveCache(); + + for (var value in _entityNames.Values) + delete value; + delete _entityNames; + + delete _editorLayer; + } + + public static void InvalidateSave() + { + GetInstance()._wantsSave = true; + UpdateTitle(); + } + + public static void SetTheme(StringView themeName) + { + String fileName = scope .(themeName)..Append(".txt"); + String _themePath = scope .(); + SteelPath.GetEditorResourcePath(_themePath, "Themes", fileName); + + String theme = new .(); + defer delete theme; + + File.ReadAllText(_themePath, theme); + LoadStyle(theme); + } + + public static void GetEntityName(EntityId id, String buffer) + { + var editor = GetInstance(); + if (!editor._entityNames.ContainsKey(id)) + SetEntityName(id, "Entity"); + + buffer.Append(editor._entityNames[id]); + } + + public static void SetEntityName(EntityId id, StringView name) + { + var editor = GetInstance(); + if (!editor._entityNames.ContainsKey(id)) + editor._entityNames[id] = new .(name); + else + editor._entityNames[id].Set(name); + } + + public static void Refresh() + { + for (var window in GetInstance()._editorLayer.[Friend]_editorWindows) + window.OnShow(); + } + + public static void OpenProject(StringView path) + { + GameConsole.Instance.Clear(); + + if (!File.Exists(path)) + { + Log.Error("Could not open project ({}): File deleted", path); + return; + } + + var json = new String(); + defer delete json; + + if (File.ReadAllText(path, json) case .Err(let err)) + { + Log.Error("Could not open project ({}): {}", path, err); + return; + } + + var editor = GetInstance(); + delete editor.CurrentProject; + editor.CurrentProject = new .(); + + for (var entity in Entity.EntityStore.Values) + delete entity; + Entity.EntityStore.Clear(); + + if (JSONDeserializer.Deserialize(json, editor.CurrentProject) case .Err(let err)) + { + Log.Error("Could not open project ({}): {}", path, err); + return; + } + + for (var serializableEntity in editor.CurrentProject.Entities) + serializableEntity.MakeEntity(); + + var dirPath = new String(); + Path.GetDirectoryPath(path, dirPath); + editor.CurrentProject.Path = dirPath; + UpdateTitle(); + + editor._cache.AddRecentProject(dirPath); + + InspectorWindow.SetCurrentEntity(null); + SteelPath.SetContentDirectory(); + Refresh(); + } + + public static void CloseProject() + { + Log.Info("Closing project"); + + var editor = GetInstance(); + delete editor.CurrentProject; + editor.CurrentProject = EditorProject.UntitledProject(); + UpdateTitle(); + } + + public static void UpdateTitle() + { + var editor = GetInstance(); + var title = scope String(); + + if (editor.CurrentProject.Path.IsEmpty) + title.AppendF("Steel Editor - {}", editor.CurrentProject.Name); + else + title.AppendF("Steel Editor - {}", editor.CurrentProject.Path); + + if (editor._wantsSave) + title.Append('*'); + + editor.Window.SetTitle(title); + } + + public static void Save() + { + Log.Trace("Saving project"); + + var editor = GetInstance(); + + DeleteAndClearItems!(editor.CurrentProject.Entities); + for (var entity in Entity.EntityStore.Values) + { + var entityName = scope String(); + GetEntityName(entity.Id, entityName); + editor.CurrentProject.Entities.Add(new .(entityName, entity)); + } + + var result = JSONSerializer.Serialize(editor.CurrentProject); + if (result case .Err) + { + Log.Error("Could not save project: Serialization error"); + return; + } + + var json = result.Get(); + defer delete json; + + var projectFilePath = scope String(); + Path.InternalCombine(projectFilePath, editor.CurrentProject.Path, "SteelProj.json"); + + if (File.WriteAllText(projectFilePath, json) case .Err) + Log.Error("Could not save cache: File error"); + + editor._wantsSave = false; + UpdateTitle(); + + SaveCache(); + } + + public static void SaveCache() + { + Log.Trace("Saving cache"); + + var editor = GetInstance(); + editor._cache.Update(); + editor._cache.MakeSerializable(); + + var result = JSONSerializer.Serialize(editor._cache); + if (result case .Err) + { + Log.Error("Could not save cache: Serialization error"); + return; + } + + var json = result.Get(); + defer delete json; + + var cachePath = scope String(); + SteelPath.GetEditorUserPath(cachePath, "Cache.json"); + + if (File.WriteAllText(cachePath, json) case .Err) + Log.Error("Could not save cache: File error"); + } + + private static void LoadCache() + { + Log.Trace("Loading cache"); + + var cachePath = scope String(); + SteelPath.GetEditorUserPath(cachePath, "Cache.json"); + + var json = new String(); + defer delete json; + + if (File.ReadAllText(cachePath, json) case .Err(let err)) + { + Log.Error("Could not load cache: {}", err); + return; + } + + if (json.IsEmpty) + return; + + var editor = GetInstance(); + + delete editor._cache; + editor._cache = new .(); + + if (JSONDeserializer.Deserialize(json, editor._cache) case .Err(let err)) + { + Log.Error("Could not load cache: {}", err); + delete editor._cache; + editor._cache = new .(true); + } + } + + public static void ClearCache() + { + var editor = GetInstance(); + delete editor._cache; + editor._cache = new .(true); + } + + public static T GetWindow() where T : EditorWindow + { + return GetInstance()._editorLayer.GetWindow(); + } + + public static void ShowWindow() where T : EditorWindow + { + GetInstance()._editorLayer.ShowWindow(); + } + + public static void ShowWindow(StringView windowName) + { + GetInstance()._editorLayer.ShowWindow(windowName); + } + + public static void ShowWindow(EditorWindow window) + { + GetInstance()._editorLayer.ShowWindow(window); + } + + public static void RegisterWindow() where T : EditorWindow + { + GetInstance()._editorLayer.RegisterWindow(); + } + + public static void CloseWindow(EditorWindow window) + { + GetInstance()._editorLayer.CloseWindow(window); + } + + public static void SaveConfig() + { + Log.Trace("Saving config"); + + var editor = GetInstance(); + var config = new Dictionary(); + + var openWindows = new List(); + for (var window in editor._editorLayer.[Friend]_editorWindows) + { + if (window.IsActive) + openWindows.Add(window.Title); + } + config.Add("Windows", openWindows); + + var style = ImGui.GetStyle(); + + + + uint8[] buffer = scope uint8[32]; + MsgPacker packer = scope MsgPacker(buffer); + + packer.WriteMapHeader(2); + packer.Write("compact"); + packer.Write(true); + packer.Write("schema"); + packer.Write(0); + + Console.Write("BUFFER: "); + for (uint8 bin in buffer) + Console.Write("{} ", bin); + + delete config; + delete openWindows; + + /*AddSetting(config, "Alpha", style.Alpha); + AddSetting(config, "WindowPadding", style.WindowPadding); + AddSetting(config, "WindowRounding", style.WindowRounding); + AddSetting(config, "WindowBorderSize", style.WindowBorderSize); + AddSetting(config, "WindowMinSize", style.WindowMinSize); + AddSetting(config, "WindowTitleAlign", style.WindowTitleAlign); + AddSetting(config, "WindowMenuButtonPosition", style.WindowMenuButtonPosition); + AddSetting(config, "ChildRounding", style.ChildRounding); + AddSetting(config, "ChildBorderSize", style.ChildBorderSize); + AddSetting(config, "PopupRounding", style.PopupRounding); + AddSetting(config, "PopupBorderSize", style.PopupBorderSize); + AddSetting(config, "FramePadding", style.FramePadding); + AddSetting(config, "FrameRounding", style.FrameRounding); + AddSetting(config, "ItemSpacing", style.ItemSpacing); + AddSetting(config, "ItemInnerSpacing", style.ItemInnerSpacing); + AddSetting(config, "TouchExtraPadding", style.TouchExtraPadding); + AddSetting(config, "IndentSpacing", style.IndentSpacing); + AddSetting(config, "ColumnsMinSpacing", style.ColumnsMinSpacing); + AddSetting(config, "ScrollbarSize", style.ScrollbarSize); + AddSetting(config, "ScrollbarRounding", style.ScrollbarRounding); + AddSetting(config, "GrabMinSize", style.GrabMinSize); + AddSetting(config, "GrabRounding", style.GrabRounding); + AddSetting(config, "TabRounding", style.TabRounding); + AddSetting(config, "TabBorderSize", style.TabBorderSize); + AddSetting(config, "ColorButtonPosition", style.ColorButtonPosition); + AddSetting(config, "ButtonTextAlign", style.ButtonTextAlign); + AddSetting(config, "SelectableTextAlign", style.SelectableTextAlign); + AddSetting(config, "DisplayWindowPadding", style.DisplayWindowPadding); + AddSetting(config, "DisplaySafeAreaPadding", style.DisplaySafeAreaPadding); + AddSetting(config, "MouseCursorScale", style.MouseCursorScale); + AddSetting(config, "AntiAliasedLines", style.AntiAliasedLines); + AddSetting(config, "AntiAliasedFill", style.AntiAliasedFill); + AddSetting(config, "CurveTessellationTol", style.CurveTessellationTol); + AddSetting(config, "CircleSegmentMaxError", style.CircleSegmentMaxError); + AddSettingType(config, "Colors", style.Colors); + + var serialized = new String(); + + for (var prop in config) + { + if (prop.value.GetType() == typeof(ImGui.Vec2)) + { + var vec = (ImGui.Vec2) prop.value; + serialized.AppendF("{} = [{}, {}]\n", prop.key, vec.x, vec.y); + } + else if (prop.value.GetType() == typeof(List)) + { + var list = (List) prop.value; + serialized.AppendF("{} = [", prop.key); + for (var str in list) + serialized.AppendF("{}, ", str); + if (serialized.EndsWith(", ")) + serialized.RemoveFromEnd(2); + serialized.[Friend]Realloc(serialized.AllocSize); + serialized.Append("]\n"); + } + else + { + serialized.AppendF("{} = {}\n", prop.key, prop.value); + } + } + + for (var value in config.Values) + delete value; + delete config; + + var configPath = scope String(); + SteelPath.GetEditorUserPath(configPath, "Config.txt"); + + if (File.WriteAllText(configPath, serialized) case .Err) + Log.Error("Failed to save style"); + + delete serialized; + + void AddSetting(Dictionary parent, StringView name, T value) where T : struct + { + parent[name] = new box value; + } + + void AddSettingType(Dictionary parent, StringView name, ImGui.Vec4[(.) ImGui.Col.COUNT] vecArray) + { + var str = new String(); + str.Append('['); + for (var vec in vecArray) + str.AppendF("[{}, {}, {}, {}], ", vec.x, vec.y, vec.z, vec.w); + str.RemoveFromEnd(2); + str.[Friend]Realloc(str.AllocSize); + str.Append("]"); + parent[name] = str; + }*/ + } + + public static void LoadConfig() + { + var configPath = scope String(); + SteelPath.GetEditorUserPath(configPath, "Config.txt"); + var serialized = new String(); + defer delete serialized; + if (File.ReadAllText(configPath, serialized) case .Err) + return; + + ParseConfig(serialized, var config); + LoadStyle(config); + + var windows = (List) config["Windows"]; + for (var window in windows) + ShowWindow((String) window); + + DeleteConfig!(config); + } + + public static void LoadStyle(StringView str) + { + ParseConfig(str, var config); + LoadStyle(config); + DeleteConfig!(config); + } + + private static void LoadStyle(Dictionary config) + { + var style = ref ImGui.GetStyle(); + style.Alpha = (float) config["Alpha"]; + style.WindowPadding = GetVec2(config, "WindowPadding"); + style.WindowRounding = (float) config["WindowRounding"]; + style.WindowBorderSize = (float) config["WindowBorderSize"]; + style.WindowMinSize = GetVec2(config, "WindowMinSize"); + style.WindowTitleAlign = GetVec2(config, "WindowTitleAlign"); + style.WindowMenuButtonPosition = Enum.Parse((String) config["WindowMenuButtonPosition"]); + style.ChildRounding = (float) config["ChildRounding"]; + style.ChildBorderSize = (float) config["ChildBorderSize"]; + style.PopupRounding = (float) config["PopupRounding"]; + style.PopupBorderSize = (float) config["PopupBorderSize"]; + style.FramePadding = GetVec2(config, "FramePadding"); + style.FrameRounding = (float) config["FrameRounding"]; + style.ItemSpacing = GetVec2(config, "ItemSpacing"); + style.ItemInnerSpacing = GetVec2(config, "ItemInnerSpacing"); + style.TouchExtraPadding = GetVec2(config, "TouchExtraPadding"); + style.IndentSpacing = (float) config["IndentSpacing"]; + style.ColumnsMinSpacing = (float) config["ColumnsMinSpacing"]; + style.ScrollbarSize = (float) config["ScrollbarSize"]; + style.ScrollbarRounding = (float) config["ScrollbarRounding"]; + style.GrabMinSize = (float) config["GrabMinSize"]; + style.GrabRounding = (float) config["GrabRounding"]; + style.TabRounding = (float) config["TabRounding"]; + style.TabBorderSize = (float) config["TabBorderSize"]; + style.ColorButtonPosition = Enum.Parse((String) config["ColorButtonPosition"]); + style.ButtonTextAlign = GetVec2(config, "ButtonTextAlign"); + style.SelectableTextAlign = GetVec2(config, "SelectableTextAlign"); + style.DisplayWindowPadding = GetVec2(config, "DisplayWindowPadding"); + style.DisplaySafeAreaPadding = GetVec2(config, "DisplaySafeAreaPadding"); + style.MouseCursorScale = (float) config["MouseCursorScale"]; + style.AntiAliasedLines = (bool) config["AntiAliasedLines"]; + style.AntiAliasedFill = (bool) config["AntiAliasedFill"]; + style.CurveTessellationTol = (float) config["CurveTessellationTol"]; + style.CircleSegmentMaxError = (float) config["CircleSegmentMaxError"]; + GetColors(config, ref style.Colors); + + ImGui.Vec2 GetVec2(Dictionary config, String name) + { + var list = (List) config[name]; + if (list.Count != 2) + return .(); + return .((float) list[0], (float) list[1]); + } + + void GetColors(Dictionary config, ref ImGui.Vec4[(.) ImGui.Col.COUNT] colors) + { + var list = (List) config["Colors"]; + for (int i = 0; i < (.) ImGui.Col.COUNT; i++) + { + var subList = (List) list[i]; + var vec = ImGui.Vec4((float) subList[0], (float) subList[1], (float) subList[2], (float) subList[3]); + colors[i] = vec; + } + } + } + + private static void ParseConfig(StringView str, out Dictionary config) + { + config = new Dictionary(); + + for (var line in str.Split('\n')) + { + if (line.IsWhiteSpace) + continue; + + var lineEnumerator = line.GetEnumerator(); + + var key = new String(); + for (var char in lineEnumerator) + { + if (char == '=') + break; + key.Append(char); + } + + key.Trim(); + + for (var char in lineEnumerator) + if (!char.IsWhiteSpace) + break; + + var value = ParseValue(ref lineEnumerator); + if (value != null) + config[key] = value; + else + delete key; + } + + Object ParseValue(ref Span.Enumerator enumerator) + { + if (enumerator.Current == '[') + return ParseArray(ref enumerator); + else if (enumerator.Current.IsDigit) + return ParseNumber(ref enumerator); + else if (enumerator.Current.IsLetter) + return ParseString(ref enumerator); + return null; + } + + Object ParseArray(ref Span.Enumerator enumerator) + { + var array = new List(); + for (var char in enumerator) + { + if (char == ']') + break; + if (char == ',' || char == ' ') + continue; + var value = ParseValue(ref enumerator); + if (value != null) + array.Add(value); + } + + return array; + } + + Object ParseNumber(ref Span.Enumerator enumerator) + { + enumerator.[Friend]mIndex--; + var str = scope String(); + for (var char in enumerator) + { + if ((char == ']' || char == ',' || char.IsWhiteSpace) && char != '.') + break; + str.Append(char); + } + + enumerator.[Friend]mIndex--; + + return new box (float) double.Parse(str).Get(); + } + + Object ParseString(ref Span.Enumerator enumerator) + { + enumerator.[Friend]mIndex--; + var str = new String(); + for (var char in enumerator) + { + if (!char.IsLetter) + break; + str.Append(char); + } + enumerator.[Friend]mIndex--; + + bool val; + if (str == "True") + val = true; + else if (str == "False") + val = false; + else + return str; + delete str; + return new box val; + } + } + + private static mixin DeleteConfig(Dictionary config) + { + for (var value in config.Values) + DeleteObject(value); + DeleteDictionaryAndKeys!(config); + + void DeleteObject(Object object) + { + if (object.GetType() == typeof(List)) + { + for (var item in (List) object) + DeleteObject(item); + } + delete object; + } + } + + public static void ResetStyle() + { + var style = ref ImGui.GetStyle(); + style = GetInstance()._editorLayer.[Friend]_originalStyle; + } + } +} diff --git a/Editor/src/EditorGUI.bf b/Editor/src/EditorGUI.bf new file mode 100644 index 0000000..d54e7e6 --- /dev/null +++ b/Editor/src/EditorGUI.bf @@ -0,0 +1,627 @@ +using System; +using System.Collections; +using ImGui; +using SteelEngine; +using SteelEngine.Math; +using SteelEngine.Input; + +namespace SteelEditor +{ + public static class EditorGUI + { + private static InputCallback _inputCallback = .(.None); + private static Dictionary _inputTextBuffers = new .() ~ DeleteDictionaryAndKeysAndItems!(_); + + private static bool _popItemWidth = false; + private static bool _popItemColor = false; + private static bool _popItemID = false; + private static bool _useColumns = true; + private static bool _visibleSeparator = false; + + private static uint _popItemFlag = 0; + private static uint _collapsableHeaderCount = 0; + + // Window + + public static bool BeginWindow(StringView name, ref bool isActive) + { + return ImGui.Begin(name.Ptr, &isActive, .NoScrollbar); + } + + public static void EndWindow() + { + RemoveColumns(); + ImGui.End(); + } + + // Text + + public static void Text(StringView fmt, params Object[] args) + { + ImGui.Text(scope String()..AppendF(fmt, params args)); + CheckItem(); + } + + public static bool Selectable(StringView text, bool selected = false) + { + var isSelected = ImGui.Selectable(text.Ptr, selected); + CheckItem(); + return isSelected; + } + + public static bool Label(StringView label, bool sameLine = false) + { + if (label.StartsWith("##")) + return false; + + if (_useColumns) + ImGui.Columns(2); + ImGui.AlignTextToFramePadding(); + + Text(label); + CheckItem(false); + + if (sameLine) + SameLine(); + FillWidth(); + + return true; + } + + public static void LabelText(StringView label, StringView fmt, params Object[] args) + { + Label(label); + Text(scope String()..AppendF(fmt, params args)); + CheckItem(false); + } + + public static void Tooltip(StringView fmt, params Object[] args) + { + ImGui.BeginTooltip(); + Text(fmt, params args); + ImGui.EndTooltip(); + } + + // Input + + public static bool Button(StringView name, Vector2 size = default) + { + var isClicked = ImGui.Button(name.Ptr, size); + CheckItem(); + return isClicked; + } + + public static void Button(StringView name, ref bool value, Vector2 size = default) + { + value = Button(name, size); + } + + public static void ToggleButton(StringView name, ref bool value) + { + if (!value) + { + var color = ImGui.GetStyleColorVec4(.Button); + color.w = 0.4f; + ImGui.PushStyleColor(.Button, color); + } + + var isClicked = ImGui.Button(name.Ptr); + + if (!value) + ImGui.PopStyleColor(); + + CheckItem(); + if (isClicked) + value = !value; + } + + public static bool Checkbox(StringView label, bool value) + { + bool isChecked = value; + Checkbox(label, ref isChecked); + + return isChecked; + } + + public static void Checkbox(StringView label, ref bool value) + { + Label(label, true); + ImGui.Checkbox(scope UniqueLabel(label, "Checkbox"), &value); + CheckItem(); + } + + public static InputCallback Input(StringView label, String buffer, StringView hint = "", int maxSize = 256, bool readOnly = false, bool newLine = false) + { + bool hasLabel = Label(label, newLine); + if (!hasLabel) + ImGui.Columns(1); + + if (_inputCallback.[Friend]_type != .None) + _inputCallback = .(.None); + + var labelStr = scope String(label); + + if (!_inputTextBuffers.ContainsKey(labelStr)) + _inputTextBuffers[new String(label)] = new .(); + + var inputTextBuffer = _inputTextBuffers[labelStr]; + if (inputTextBuffer.Size != (uint) maxSize) + inputTextBuffer.ReAlloc((uint) maxSize); + + inputTextBuffer.Set(buffer); + + bool isEnter = false; + + ImGui.InputTextFlags flags = .EnterReturnsTrue | .CallbackHistory | .CallbackCompletion; + if (readOnly) + flags |= .ReadOnly; + + if (hint != "") + isEnter = ImGui.InputTextWithHint(scope UniqueLabel(label, "Input"), hint.Ptr, inputTextBuffer.Ptr, (uint) maxSize, flags, => InputTextCallback); + else + isEnter = ImGui.InputText(scope UniqueLabel(label, "Input"), inputTextBuffer.Ptr, (uint) maxSize, flags, => InputTextCallback); + + if (!hasLabel) + CheckItem(false); + else + CheckItem(); + + var view = inputTextBuffer.View(); + + if (view != buffer) + _inputCallback = .(.OnChange); + + if (isEnter) + _inputCallback = .(.OnEnter); + + buffer.Set(view); + + return _inputCallback; + } + + public static void InputMultiline(StringView label, String buffer, int maxSize = 256, bool readOnly = false, Vector2 size = default) + { + bool hasLabel = Label(label); + if (!hasLabel) + EditorGUI.RemoveColumns(); + + var labelStr = scope String(label); + + if (!_inputTextBuffers.ContainsKey(labelStr)) + _inputTextBuffers[new String(label)] = new .(); + + var inputTextBuffer = _inputTextBuffers[labelStr]; + if (inputTextBuffer.Size != (uint) maxSize) + inputTextBuffer.ReAlloc((uint) maxSize); + + inputTextBuffer.Set(buffer); + + ImGui.InputTextFlags flags = readOnly ? .ReadOnly : .None; + ImGui.InputTextMultiline(scope UniqueLabel(label, "InputMultiline"), inputTextBuffer.Ptr, (uint) maxSize, .(size.x, size.y), flags); + + if (!hasLabel) + CheckItem(false); + else + CheckItem(); + + buffer.Set(inputTextBuffer.View()); + } + + private static int InputTextCallback(ImGui.InputTextCallbackData* data) + { + _inputCallback = .(data); + return 0; + } + + public static int Int(StringView label, int value) + { + var input = value; + Int(label, ref input); + return input; + } + + public static void Int(StringView label, ref int value) + { + Label(label); + ImGui.DragInt(scope UniqueLabel(label, "InputInt"), (int32*) &value); + CheckItem(); + } + + public static float Float(StringView label, float value) + { + var input = value; + Float(label, ref input); + return input; + } + + public static void Float(StringView label, ref float value) + { + Label(label); + ImGui.DragFloat(scope UniqueLabel(label, "InputFloat"), &value); + CheckItem(); + } + + public static Vector2 Vector2(StringView label, Vector2 value) + { + var input = value; + Vector2(label, ref input); + return input; + } + + public static void Vector2(StringView label, ref Vector2 value) + { + Label(label); + ImGui.DragFloat2(scope UniqueLabel(label, "Vector2"), value.data); + CheckItem(); + } + + public static Vector3 Vector3(StringView label, Vector3 value) + { + var input = value; + Vector3(label, ref input); + return input; + } + + public static void Vector3(StringView label, ref Vector3 value) + { + Label(label); + ImGui.DragFloat3(scope UniqueLabel(label, "Vector3"), value.data); + CheckItem(); + } + + public static bool Combo(StringView label, ref TEnum currentItem) + where TEnum : Enum + where int : operator explicit TEnum + where TEnum : operator explicit int + { + Label(label); + + var enumItems = scope List(); + for (var item in typeof(TEnum).GetFields()) + enumItems.Add(item.Name.Ptr); + + var str = scope String(); + for (var item in enumItems) + str.AppendF("{}\0", StringView(item)); + + int tmp = (.) currentItem; + + let result = ImGui.Combo(scope UniqueLabel(label, "Combo"), (int32*) &tmp, str); + CheckItem(); + currentItem = (.) tmp; + return result; + } + + public static bool Combo(StringView label, Span items, ref int32 currentItem) + { + Label(label); + + var str = scope String(); + for (var item in items) + str.AppendF("{}\0", item); + + let result = ImGui.Combo(scope UniqueLabel(label, "Combo"), (int32*) ¤tItem, str); + CheckItem(); + return result; + } + + // Layout + + public static void Line() + { + ImGui.PopStyleColor(); + ImGui.Separator(); + ImGui.PushStyleColor(.Separator, ImGui.Vec4(0, 0, 0, 0)); + } + + public static void SameLine(float offset = 0) + { + if (offset != 0) + ImGui.SameLine(offset); + else + ImGui.SameLine(); + } + + public static void AlignFromRight(float offset) + { + ImGui.SameLine(ImGui.GetWindowWidth() - offset); + } + + public static void AlignMiddle(float itemWidth) + { + ImGui.SetCursorPosX((ImGui.GetWindowWidth() - itemWidth) / 2); + } + + public static void ItemWidth(float width) + { + ImGui.PushItemWidth(width); + _popItemWidth = true; + } + + public static void FillWidth() => ItemWidth(-1); + + public static bool BeginCollapsableHeader(StringView label) + { + ImGui.Columns(1); + var isOpen = ImGui.CollapsingHeader(label.Ptr, .AllowItemOverlap); + if (isOpen) + { + ImGui.Indent(); + _collapsableHeaderCount++; + } + else + { + CheckItem(false); + } + return isOpen; + } + + public static void EndCollapsableHeader() + { + if (_collapsableHeaderCount > 0) + { + CheckItem(false); + ImGui.Unindent(); + ImGui.Columns(2); + _collapsableHeaderCount--; + } + } + + public static bool BeginTree(StringView label) + { + return ImGui.TreeNode(label.Ptr); + } + + public static void EndTree() + { + ImGui.TreePop(); + } + + public static void BeginScrollingRegion(StringView label, float height = 0) + { + ImGui.BeginChild(label.Ptr, .(0, height), false, .HorizontalScrollbar); + } + + public static void EndScrollingRegion() + { + ImGui.EndChild(); + } + + public static float GetHeightOfItems(uint numberOfItems) + { + return ImGui.GetStyle().ItemSpacing.y + ImGui.GetFrameHeightWithSpacing() * numberOfItems; + } + + public static void RemoveColumns() + { + ImGui.Columns(1); + _useColumns = false; + } + + public static void AddColumns() + { + _useColumns = true; + } + + public static void NewLine() + { + ImGui.NewLine(); + } + + // Style + + public static void TextColor(Color color) + { + ImGui.PushStyleColor(.Text, color); + _popItemColor = true; + } + + public static void VisibleSeparator() + { + ImGui.PopStyleColor(); + _visibleSeparator = true; + } + + public static void InvisibleSeparator() + { + ImGui.PushStyleColor(.Separator, Color.Transparent.Normalized); + _visibleSeparator = false; + } + + public static void DisableItem() + { + _popItemFlag++; + ImGui.PushItemFlag(.Disabled, true); + } + + // Other + + public static void ItemID(StringView id) + { + ImGui.PushID(id.Ptr); + _popItemID = true; + } + + private static void CheckItem(bool nextColumn = true) + { + if (_popItemWidth) + { + ImGui.PopItemWidth(); + _popItemWidth = false; + } + + if (_popItemColor) + { + ImGui.PopStyleColor(); + _popItemColor = false; + } + + if (_popItemID) + { + ImGui.PopID(); + _popItemID = false; + } + + if (_popItemFlag > 0) + { + ImGui.PopItemFlag(); + _popItemFlag--; + } + + if (nextColumn && _useColumns) + ImGui.NextColumn(); + } + + public static Vector2 GetWindowSize() + { + return .(ImGui.GetContentRegionAvail().x, ImGui.GetContentRegionAvail().y); + } + + public static float GetWindowWidth() + { + return ImGui.GetContentRegionAvail().x; + } + + public static float GetWindowHeight() + { + return ImGui.GetContentRegionAvail().y; + } + + private class UniqueLabel + { + public String ID ~ delete _; + + public this(StringView label, StringView widget, params Object[] seeds) + { + ID = new String()..AppendF("##{}_{}", label, widget); + var enumerator = seeds.GetEnumerator(); + for (var seed in enumerator) + { + if (enumerator.[Friend]mIndex < enumerator.[Friend]mList.Length) + ID.AppendF("{}_", seed); + else + ID.AppendF("{}", seed); + } + } + + public static implicit operator char8*(Self uniqueLabel) + { + if (uniqueLabel == null) + return null; + + return uniqueLabel.ID; + } + } + + private class InputTextBuffer + { + private char8[] _buffer = null; + + public uint Size = 0; + + public this(uint size = 1) + { + ReAlloc(size); + } + + public ~this() + { + if (_buffer != null) + delete _buffer; + } + + public void Set(StringView str) + { + if (str.Length > (int) Size) + return; + for (int i = 0; i < str.Length; i++) + _buffer[i] = str[i]; + for (int i = str.Length; i < (int) Size; i++) + _buffer[i] = '\0'; + } + + public void ReAlloc(uint size) + { + if (size == Size) + return; + + Size = size; + + if (_buffer != null) + { + var newBuffer = new char8[size]; + _buffer.CopyTo(newBuffer); + delete _buffer; + _buffer = newBuffer; + } + else + { + _buffer = new char8[size]; + } + } + + public override void ToString(String strBuffer) + { + strBuffer.Append(StringView(Ptr)); + } + + public StringView View() => StringView(Ptr); + + public char8* Ptr => &_buffer[0]; + } + + public enum InputCallbackType + { + None, + OnHistory, + OnEnter, + OnCompletion, + OnChange + } + + public struct InputCallback + { + private InputCallbackType _type; + private VerticalDirection _historyDirection = .Up; + + public this(InputCallbackType type) + { + _type = type; + } + + public this(ImGui.InputTextCallbackData* data) + { + if (data.EventFlag == .CallbackHistory) + { + _type = .OnHistory; + if (data.EventKey == ImGui.Key.DownArrow) + _historyDirection = .Down; + } + else if (data.EventFlag == .EnterReturnsTrue) + { + _type = .OnEnter; + } + else if (data.EventFlag == .CallbackCompletion) + { + _type = .OnCompletion; + } + else + { + _type = .OnChange; + } + } + + public bool OnHistory(out VerticalDirection direction) + { + direction = _historyDirection; + if (_type == .OnHistory) + return true; + return false; + } + + public bool OnEnter => _type == .OnEnter; + public bool OnCompletion => _type == .OnCompletion; + public bool OnChange => _type == .OnChange; + } + } +} diff --git a/Editor/src/EditorLayer.bf b/Editor/src/EditorLayer.bf new file mode 100644 index 0000000..aab6941 --- /dev/null +++ b/Editor/src/EditorLayer.bf @@ -0,0 +1,331 @@ +using System; +using System.Collections; +using System.IO; +using SteelEngine; +using SteelEngine.Events; +using SteelEngine.Window; +using SteelEditor.UI; +using ImGui; +using glfw_beef; + +namespace SteelEditor +{ + public class EditorLayer : Layer + { + private Window _window; + private List _editorWindows = new .() ~ DeleteContainerAndItems!(_); + + private String _imguiIniPath = new .() ~ delete _; + private ImGui.Style _originalStyle; + + public this(Window window) : base("EditorLayer") + { + _window = window; + } + + public ~this() + { + ImGuiImplOpenGL3.Shutdown(); + ImGuiImplGlfw.Shutdown(); + ImGui.DestroyContext(); + } + + protected override void OnAttach() + { + if (!ImGui.CHECKVERSION()) + Log.Fatal("ImGui versions do not match"); + + ImGui.CreateContext(); + + SteelPath.GetEditorUserPath(_imguiIniPath, "imgui.ini"); + + var io = ref ImGui.GetIO(); + io.IniFilename = _imguiIniPath; + io.ConfigFlags |= .DockingEnable | .ViewportsEnable; + io.ConfigViewportsNoDecoration = false; + + var style = ref ImGui.GetStyle(); + style.WindowMenuButtonPosition = .None; // This disables the collapse button on windows + style.WindowRounding = 0f; + ImGui.StyleColorsClassic(&style); + ImGui.PushStyleColor(.Separator, ImGui.Vec4(0, 0, 0, 0)); + + _originalStyle = style; + + ImGuiImplGlfw.InitForOpenGL(_window.GetHandle, true); + ImGuiImplOpenGL3.Init(); + + Editor.RegisterWindow(); + } + + protected override void OnDetach() + { + for (var editorWindow in _editorWindows) + CloseWindow(editorWindow); + } + + protected override void OnUpdate() + { + var io = ref ImGui.GetIO(); + var app = Editor.Instance; + io.DisplaySize = ImGui.Vec2(app.Window.GetSize.x, app.Window.GetSize.y); + ImGuiImplOpenGL3.NewFrame(); + ImGuiImplGlfw.NewFrame(); + ImGui.NewFrame(); + + ShowMainMenuBar(); + ShowDockspace(); + + // Update ImGui windows + for (var window in _editorWindows) + { + if (window.IsClosed) + continue; + + if (window.IsActive) + window.Update(); + else + CloseWindow(window); + } + + // Background color + // ImGuiImplOpenGL3.glClearColor(0.45f, 0.55f, 0.6f, 1); + // ImGuiImplOpenGL3.glClear(ImGuiImplOpenGL3.GL_COLOR_BUFFER_BIT); + + // Rendering + ImGui.Render(); + ImGuiImplOpenGL3.RenderDrawData(&ImGui.GetDrawData()); + + if (io.ConfigFlags.HasFlag(.ViewportsEnable)) + { + GlfwWindow* glfwContextBackup = Glfw.GetCurrentContext(); +#if BF_PLATFORM_WINDOWS + ImGui.UpdatePlatformWindows(); + ImGui.RenderPlatformWindowsDefault(); +#else + #error Only windows is supported for ImGui +#endif + Glfw.MakeContextCurrent(glfwContextBackup); + } + } + + private void ShowDockspace() + { + var viewport = ImGui.GetMainViewport(); + ImGui.SetNextWindowPos(viewport.Pos); + ImGui.SetNextWindowSize(viewport.Size); + ImGui.SetNextWindowViewport(viewport.ID); + + ImGui.PushStyleVar(.WindowPadding, .(0, 0)); + ImGui.PushStyleVar(.WindowRounding, 0.0f); + ImGui.PushStyleVar(.WindowBorderSize, 0.0f); + ImGui.WindowFlags windowFlags = .MenuBar | .NoDocking | .NoTitleBar | .NoResize | .NoMove | .NoBringToFrontOnFocus | .NoNavFocus; + ImGui.Begin("EditorMainDockspaceWindow", null, windowFlags); + ImGui.PopStyleVar(3); + + ImGui.ID dockspaceId = ImGui.GetID("EditorMainDockspace"); + ImGui.DockSpace(dockspaceId); + ImGui.End(); + } + + private void ShowMainMenuBar() + { + if (ImGui.BeginMainMenuBar()) + { + ShowFileMenu(); + ShowEditMenu(); + ShowViewMenu(); + ShowCreateMenu(); + + ImGui.EndMainMenuBar(); + } + } + + private void ShowFileMenu() + { + if (ImGui.BeginMenu("File")) + { + if (ImGui.MenuItem("New")) + NewProject(); + + if (ImGui.MenuItem("Open")) + OpenProject(); + + if (ImGui.BeginMenu("Open Recent")) + { + ShowRecentProjects(); + ImGui.EndMenu(); + } + + if (ImGui.MenuItem("Save", "CTRL+S")) + Editor.Save(); + + if (ImGui.MenuItem("Close Project")) + Editor.CloseProject(); + + if (ImGui.MenuItem("Exit")) + Application.Exit(); + + ImGui.EndMenu(); + } + } + + private void NewProject() + { + Editor.ShowWindow(); + } + + private void ShowRecentProjects() + { + var cache = Application.GetInstance().[Friend]_cache; + if (cache.RecentProjects == null) + return; + + for (var projectPath in cache.RecentProjects) + { + if (ImGui.MenuItem(projectPath)) + { + var projectFilePath = scope String(); + Path.InternalCombine(projectFilePath, projectPath, "SteelProj.json"); + Editor.OpenProject(projectFilePath); + } + } + } + + private void ShowEditMenu() + { + if (ImGui.BeginMenu("Edit")) + { + if (ImGui.MenuItem("Clear Cache")) + Editor.ClearCache(); + + ImGui.EndMenu(); + } + } + + private void ShowViewMenu() + { + if (ImGui.BeginMenu("View")) + { + for (var window in _editorWindows) + { + if (ImGui.MenuItem(window.Title.Ptr)) + ShowWindow(window); + } + + EditorGUI.Line(); + if (ImGui.MenuItem("Refresh")) + Editor.Refresh(); + + ImGui.EndMenu(); + } + } + + private void ShowCreateMenu() + { + if (ImGui.BeginMenu("Create")) + { + if (ImGui.MenuItem("Entity")) + { + Application.Instance.CreateEntity(); + Editor.InvalidateSave(); + } + + ImGui.EndMenu(); + } + } + + private void OpenProject() + { + var dialog = scope OpenFileDialog(); + dialog.SetFilter("Steel Project (SteelProj.json)|SteelProj.json"); + + if (dialog.ShowDialog() case .Ok(let val)) + { + if (val == .OK) + Editor.OpenProject(dialog.FileNames[0]); + } + else + { + Log.Error("Could not show folder browser dialog"); + } + } + + public void ShowWindow() where T : EditorWindow + { + for (var window in _editorWindows) + { + if (window.GetType() == typeof(T)) + { + ShowWindow(window); + return; + } + } + + var fullTypeName = scope String()..AppendF("{}", typeof(T)); + StringView typeName = StringView("null"); + for (var str in fullTypeName.Split('.')) + typeName = str; + + Log.Error("{} does not exist in application", typeName); + } + + public void ShowWindow(StringView windowName) + { + for (var window in _editorWindows) + { + if (window.Title == windowName) + { + ShowWindow(window); + break; + } + } + } + + public void ShowWindow(EditorWindow window) + { + if (!window.IsActive) + { + window.IsActive = true; + + if (!window.[Friend]_isInitialized) + { + window.OnInit(); + window.[Friend]_isInitialized = true; + } + + window.OnShow(); + window.IsClosed = false; + } + } + + public void RegisterWindow() where T : EditorWindow + { + _editorWindows.Add(new T()); + } + + public void CloseWindow() where T : EditorWindow + { + EditorWindow window = GetWindow(); + + if (window != null) + CloseWindow(window); + } + + public void CloseWindow(EditorWindow window) + { + window.IsActive = false; + window.OnClose(); + window.IsClosed = true; + } + + public T GetWindow() where T : EditorWindow + { + for (var window in _editorWindows) + if (typeof(T) == window.GetType()) + return (T) window; + + return null; + } + } +} \ No newline at end of file diff --git a/Editor/src/EditorProject.bf b/Editor/src/EditorProject.bf new file mode 100644 index 0000000..40b710e --- /dev/null +++ b/Editor/src/EditorProject.bf @@ -0,0 +1,33 @@ +using System; +using System.Collections; +using SteelEngine.ECS; +using SteelEditor.Serialization; +using JSON_Beef.Attributes; + +namespace SteelEditor +{ + [Reflect, AlwaysInclude(AssumeInstantiated=true, IncludeAllMethods=true)] + public class EditorProject + { + public String Name ~ delete _; + public List Entities ~ DeleteContainerAndItems!(_); + + [IgnoreSerialize] + public String Path ~ delete _; + + public this() {} + + public this(StringView name, StringView path) + { + Name = new .(name); + Path = new .(path); + Entities = new .(); + } + + [NoDiscard] + public static Self UntitledProject() + { + return new .("Untitled", ""); + } + } +} diff --git a/Editor/src/EditorWindow.bf b/Editor/src/EditorWindow.bf new file mode 100644 index 0000000..e882177 --- /dev/null +++ b/Editor/src/EditorWindow.bf @@ -0,0 +1,32 @@ +using System; +using SteelEngine; +using ImGui; + +namespace SteelEditor +{ + public abstract class EditorWindow + { + public abstract StringView Title { get; } + + public bool IsActive = false; + public bool IsClosed = true; + + private bool _isInitialized = false; + + public void Update() + { + if (!IsActive) + return; + + if (EditorGUI.BeginWindow(Title, ref IsActive)) + OnRender(); + + EditorGUI.EndWindow(); + } + + public virtual void OnInit() {} + public virtual void OnShow() {} + public virtual void OnRender() {} + public virtual void OnClose() {} + } +} diff --git a/Editor/src/Program.bf b/Editor/src/Program.bf new file mode 100644 index 0000000..ffc096a --- /dev/null +++ b/Editor/src/Program.bf @@ -0,0 +1,17 @@ +using System; +using System.IO; + +namespace SteelEditor +{ + class Program + { + public static int Main(String[] args) + { + var editor = new Editor(); + editor.Run(); + delete editor; + + return 0; + } + } +} \ No newline at end of file diff --git a/Editor/src/Serialization/Attributes.bf b/Editor/src/Serialization/Attributes.bf new file mode 100644 index 0000000..99aab82 --- /dev/null +++ b/Editor/src/Serialization/Attributes.bf @@ -0,0 +1,16 @@ +using System; + +namespace SteelEditor.Serialization +{ + [AttributeUsage(.Class | .Struct, ReflectUser=.NonStaticFields | .StaticFields | .DefaultConstructor | .DynamicBoxing)] + public struct SerializableAttribute : Attribute + { + + } + + [AttributeUsage(.Property | .Field | .StaticField)] + public struct NoSerializeAttribute : Attribute + { + + } +} diff --git a/Editor/src/Serialization/EditorCache.bf b/Editor/src/Serialization/EditorCache.bf new file mode 100644 index 0000000..6524a3a --- /dev/null +++ b/Editor/src/Serialization/EditorCache.bf @@ -0,0 +1,76 @@ +using System; +using System.Collections; +using JSON_Beef.Attributes; +using ImGui; +using SteelEngine; + +namespace SteelEditor.Serialization +{ + [Reflect, AlwaysInclude(AssumeInstantiated=true, IncludeAllMethods=true)] + public class EditorCache + { + [IgnoreSerialize] + private const int MAX_RECENT_PROJECTS = 5; + + public List RecentProjects = null; + public List Windows = null; + + [IgnoreSerialize] // Temporary until structs can be serialized + public ImGui.Style Style; + + public this(bool init = false) + { + if (!init) + return; + + RecentProjects = new .(); + Windows = new .(); + } + + public ~this() + { + if (RecentProjects != null) + DeleteContainerAndItems!(RecentProjects); + + if (Windows != null) + DeleteContainerAndItems!(Windows); + } + + public void Update() + { + DeleteAndClearItems!(Windows); + + for (var window in Application.GetInstance().[Friend]_editorLayer.[Friend]_editorWindows) + { + if (window.IsActive) + Windows.Add(new String(window.Title)); + } + + Style = ImGui.GetStyle(); + } + + public void AddRecentProject(StringView path) + { + + if (Windows == null) + Windows = new .(); + if (RecentProjects.Count >= MAX_RECENT_PROJECTS) + RecentProjects.PopFront(); + + var pathString = new String(path); + if (RecentProjects.Contains(pathString)) + delete RecentProjects.GetAndRemove(pathString).Get(); + + RecentProjects.Add(pathString); + } + + public void MakeSerializable() + { + if (RecentProjects == null) + RecentProjects = new .(); + + for (var path in RecentProjects) + path.MakePath(); + } + } +} diff --git a/Editor/src/Serialization/SerializableEntity.bf b/Editor/src/Serialization/SerializableEntity.bf new file mode 100644 index 0000000..ba88056 --- /dev/null +++ b/Editor/src/Serialization/SerializableEntity.bf @@ -0,0 +1,26 @@ +using System; +using SteelEngine.ECS; + +namespace SteelEditor.Serialization +{ + [Reflect, AlwaysInclude(AssumeInstantiated=true, IncludeAllMethods=true)] + public class SerializableEntity + { + public String Name ~ delete _; + public bool IsEnabled; + + public this(StringView name, Entity entity) + { + Name = new .(name); + IsEnabled = entity.IsEnabled; + } + + public void MakeEntity() + { + var entity = new Entity(); + entity.IsEnabled = IsEnabled; + + Editor.SetEntityName(entity.Id, Name); + } + } +} diff --git a/Editor/src/Serialization/SerializationError.bf b/Editor/src/Serialization/SerializationError.bf new file mode 100644 index 0000000..72c845b --- /dev/null +++ b/Editor/src/Serialization/SerializationError.bf @@ -0,0 +1,11 @@ +namespace SteelEditor.Serialization +{ + public enum SerializationError + { + Unknown, + FieldValError, + VariantError, + BufferSizeError, + TypeMismatch + } +} diff --git a/Editor/src/Serialization/Serializer.bf b/Editor/src/Serialization/Serializer.bf new file mode 100644 index 0000000..9a4b5d1 --- /dev/null +++ b/Editor/src/Serialization/Serializer.bf @@ -0,0 +1,349 @@ +using System; +using System.Collections; +using System.IO; +using System.Reflection; +using SteelEngine; +using MsgPackBf; + +namespace SteelEditor.Serialization +{ + public static class Serializer + { + public static Result Deserialize(String source, Object object) => Deserialize(scope StringStream(source, .Reference), object); + public static Result DeserializeFile(StringView path, Object object) => Deserialize(scope FileStream()..Open(path, .Read), object); + + public static Result Deserialize(Stream source, Object object) + { + var buffer = new uint8[source.Length]; + for (int i = 0; i < source.Length; i++) + buffer[i] = source.Read(); + return Deserialize(buffer, object); + } + + public static Result Deserialize(Span source, Object object) + { + var unpacker = scope MsgUnpacker(source); + return DeserializeObject(unpacker, object); + } + + private static Result DeserializeObject(MsgUnpacker unpacker, Object object) + { + var mapCount = unpacker.ReadMapHeader().Get(); + if (object.GetType().FieldCount != (int32) mapCount) + return .Err(.TypeMismatch); + + for (var field in object.GetType().GetFields()) + { + if (field.GetType().IsPrimitive) + { + if (unpacker.ReadMapHeader() case .Ok) + return .Err(.TypeMismatch); + } + } + + return .Ok; + } + + public static int GetOutputSize(Object object) + { + int size = 1; + + if ((object as List) != null) + { + for (var item in (object as List)) + size += GetOutputSize(item); + return size; + } + else if ((object as Dictionary) != null) + { + for (var item in (object as Dictionary)) + size += GetOutputSize(item.key) + GetOutputSize(item.value); + return size; + } + + if (object is String) + { + return ((String) object).Length + 1; + } + else if (object is StringView) + { + return ((StringView) object).Length + 1; + } + + var fields = object.GetType().GetFields(); + for (var field in fields) + { + if (field.GetCustomAttribute() case .Ok) + continue; + + var valueResult = field.GetValue(object); + + if (valueResult case .Err) + return 0; + + var valueVariant = valueResult.Get(); + + var name = field.GetName(); + size += name.Length + 1; + if (field.FieldType.IsPrimitive) + { + size += GetPrimitiveSize(valueVariant); + } + else if (field.FieldType.IsStruct) + { + var fieldObject = valueVariant.GetBoxed().Get(); + size += GetOutputSize(fieldObject); + delete fieldObject; + } + else + { + size += GetOutputSize(valueVariant.Get()); + } + + valueVariant.Dispose(); + } + + return size; + } + + private static int GetBufferSize(Span buffer) + { + int i = 0; + bool isEmpty = true; + for (i = buffer.Length - 1; i >= 0; i--) + { + if (buffer[i] != 0) + { + isEmpty = false; + break; + } + } + + return i + 1; + } + + private static int GetNumberSize(T num) where bool : operator T < int + { + if (num < 0x10000) + { + if (num < 0x100) + return 1; + else + return 2; + } + else + { + if (num < 0x100000000L) + return 4; + else + return 8; + } + } + + private static int GetNumberSize(uint num) + { + if (num < 0x10000) + { + if (num < 0x100) + return 1; + else + return 2; + } + else + { + if (num < 0x100000000L) + return 4; + else + return 8; + } + } + + private static int GetPrimitiveSize(Variant variant) + { + switch (variant.VariantType) + { + case typeof(int): + return GetNumberSize(variant.Get()); + case typeof(int8): + return GetNumberSize(variant.Get()); + case typeof(int16): + return GetNumberSize(variant.Get()); + case typeof(int32): + return GetNumberSize(variant.Get()); + case typeof(int64): + return GetNumberSize(variant.Get()); + case typeof(uint): + return GetNumberSize(variant.Get()); + case typeof(uint8): + return GetNumberSize(variant.Get()); + case typeof(uint16): + return GetNumberSize(variant.Get()); + case typeof(uint32): + return GetNumberSize(variant.Get()); + case typeof(uint64): + return GetNumberSize((uint) variant.Get()); + case typeof(bool): + return 1; + case typeof(float): + return GetNumberSize(variant.Get()); + case typeof(double): + return GetNumberSize(variant.Get()); + default: + return 0; + } + } + + public static Result Serialize(Object object, uint8[] output) + { + if (GetOutputSize(object) > output.Count) + return .Err(.BufferSizeError); + return WriteObject(scope MsgPacker(output), object); + } + + public static Result Serialize(Object object, out uint8[] output) + { + output = new uint8[GetOutputSize(object)]; + return WriteObject(scope MsgPacker(output), object); + } + + private static Result WriteObject(MsgPacker packer, Object object) + { + if (object.GetType().IsPrimitive) + return WritePrimitive(packer, Variant.CreateFromBoxed(object)); + else if ((object as List) != null) + return WriteList(packer, (List) object); + else if ((object as Dictionary) != null) + return WriteMap(packer, (Dictionary) object); + + if (object is String) + { + packer.Write((String) object); + return .Ok; + } + else if (object is StringView) + { + packer.Write((StringView) object); + return .Ok; + } + + // User defined class + var fields = object.GetType().GetFields(); + uint32 fieldCount = 0; + for (var field in fields) + { + if (field.GetCustomAttribute() case .Err) + fieldCount++; + } + packer.WriteMapHeader(fieldCount); + + fields.Reset(); + for (var field in fields) + { + if (field.GetCustomAttribute() case .Ok) + continue; + + var valueResult = field.GetValue(object); + + if (valueResult case .Err) + return .Err(.FieldValError); + + var valueVariant = valueResult.Get(); + + if (!valueVariant.HasValue) + return .Err(.VariantError); + + var name = field.GetName(); + packer.Write(name); + if (field.FieldType.IsPrimitive) + { + WritePrimitive(packer, valueVariant); + } + else if (field.FieldType.IsStruct) + { + var fieldObject = valueVariant.GetBoxed().Get(); + WriteObject(packer, fieldObject); + delete fieldObject; + } + else + { + WriteObject(packer, valueVariant.Get()); + } + + valueVariant.Dispose(); + } + + return .Ok; + } + + private static Result WriteList(MsgPacker packer, List list) + { + packer.WriteArrayHeader((uint32) list.Count); + for (var item in list) + Try!(WriteObject(packer, item)); + + return .Ok; + } + + private static Result WriteMap(MsgPacker packer, Dictionary dict) + { + packer.WriteMapHeader((uint32) dict.Count); + for (var item in dict) + { + Try!(WriteObject(packer, item.key)); + Try!(WriteObject(packer, item.value)); + } + + return .Ok; + } + + private static Result WritePrimitive(MsgPacker packer, Variant value) + { + switch (value.VariantType) + { + case typeof(int): + packer.Write(value.Get()); + break; + case typeof(int8): + packer.Write(value.Get()); + break; + case typeof(int16): + packer.Write(value.Get()); + break; + case typeof(int32): + packer.Write(value.Get()); + break; + case typeof(int64): + packer.Write(value.Get()); + break; + case typeof(uint): + packer.Write(value.Get()); + break; + case typeof(uint8): + packer.Write(value.Get()); + break; + case typeof(uint16): + packer.Write(value.Get()); + break; + case typeof(uint32): + packer.Write(value.Get()); + break; + case typeof(uint64): + packer.Write(value.Get()); + break; + case typeof(bool): + packer.Write(value.Get()); + break; + case typeof(float): + packer.Write(value.Get()); + break; + case typeof(double): + packer.Write(value.Get()); + break; + default: + return .Err(.Unknown); + } + + return .Ok; + } + } +} diff --git a/Editor/src/SteelPath.bf b/Editor/src/SteelPath.bf new file mode 100644 index 0000000..55c6009 --- /dev/null +++ b/Editor/src/SteelPath.bf @@ -0,0 +1,59 @@ +using System; +using System.IO; +using SteelEditor; + +namespace SteelEngine +{ + extension SteelPath + { + public static String EditorInstallationPath = new .() ~ delete _; + + public static this() + { +#if DEBUG + Directory.GetCurrentDirectory(EditorInstallationPath); +#else + var executablePath = scope String(); + Environment.GetExecutableFilePath(executablePath); + Path.GetDirectoryPath(executablePath, EngineInstallationPath); +#endif + } + + public static void GetEditorUserPath(String target, params String[] components) + { + var newComponents = new String[components.Count + 1]; + newComponents[0] = "Editor"; + components.CopyTo(newComponents, 0, 1, components.Count); + GetUserPath(target, params newComponents); + delete newComponents; + } + + public static void GetEditorSamplePath(String target, params String[] components) + { + var newComponents = new String[components.Count + 2]; + newComponents[0] = EditorInstallationPath; + newComponents[1] = "Samples"; + components.CopyTo(newComponents, 0, 2, components.Count); + Path.InternalCombine(target, params newComponents); + delete newComponents; + } + + public static void GetEditorResourcePath(String target, params String[] components) + { + var newComponents = new String[components.Count + 2]; + newComponents[0] = EditorInstallationPath; + newComponents[1] = "Editor"; + components.CopyTo(newComponents, 0, 2, components.Count); + Path.InternalCombine(target, params newComponents); + delete newComponents; + } + + public static new void SetContentDirectory() + { + ContentDirectory.Clear(); + var projectPath = Application.GetInstance().CurrentProject.Path; + if (!projectPath.IsEmpty) + Path.InternalCombine(ContentDirectory, projectPath, "Content"); + } + } +} diff --git a/Editor/src/UI/ConsoleWindow.bf b/Editor/src/UI/ConsoleWindow.bf new file mode 100644 index 0000000..8dac0ee --- /dev/null +++ b/Editor/src/UI/ConsoleWindow.bf @@ -0,0 +1,128 @@ +using System; +using System.IO; +using System.Collections; +using SteelEngine; +using SteelEngine.Math; +using SteelEngine.Console; +using SteelEditor; +using ImGui; + +namespace SteelEditor.UI +{ + public class ConsoleWindow : EditorWindow + { + public override StringView Title => "Console"; + + public Color TraceColor = .(0.45f, 0.45f, 0.45f, 1f); + public Color WarningColor = .(0.93f, 0.82f, 0.01f, 1f); + public Color ErrorColor = .(0.952f, 0.2f, 0.011f, 1f); + + private String _commandBuffer = new .() ~ delete _; + + private bool _scrollToBottom = false; + + private int _commandStartIndex = 0; + private int _commandIndex = 0; + private int _newCommandIndex = 1; + private int _lastLogCount = 0; + + private bool _showErrors = true; + private bool _showWarnings = true; + private bool _showInfo = true; + private bool _showTrace = true; + + private const float CLEAR_BUTTON_OFFSET = 55; + + public override void OnRender() + { + EditorGUI.ToggleButton("Errors", ref _showErrors); + EditorGUI.SameLine(); + EditorGUI.ToggleButton("Warnings", ref _showWarnings); + EditorGUI.SameLine(); + EditorGUI.ToggleButton("Info", ref _showInfo); + EditorGUI.SameLine(); + EditorGUI.ToggleButton("Trace", ref _showTrace); + + EditorGUI.AlignFromRight(CLEAR_BUTTON_OFFSET); + if (EditorGUI.Button("Clear")) + GameConsole.Instance.Clear(); + + EditorGUI.Line(); + + var footerSpacing = EditorGUI.GetHeightOfItems(1); + EditorGUI.BeginScrollingRegion("CommandScrollingRegion", -footerSpacing); + + for (var line in GameConsole.Instance.[Friend]_lines) + { + if (!_showErrors && line.level == .Error || + !_showWarnings && line.level == .Warning || + !_showInfo && line.level == .Info || + !_showTrace && line.level == .Trace) + continue; + + bool hasColor = true; + Color color = .(); + + switch (line.level) + { + case .Trace: + color = TraceColor; + break; + case .Warning: + color = WarningColor; + break; + case .Error: + color = ErrorColor; + break; + default: + hasColor = false; + } + + if (hasColor) + EditorGUI.TextColor(color); + + EditorGUI.Text(line.message); + } + + if (_lastLogCount != GameConsole.Instance.[Friend]_lines.Count) + { + _lastLogCount = GameConsole.Instance.[Friend]_lines.Count; + _scrollToBottom = true; + } + + if (_scrollToBottom) + ImGui.SetScrollHereY(1.0f); + _scrollToBottom = false; + + EditorGUI.EndScrollingRegion(); + + EditorGUI.Line(); + EditorGUI.FillWidth(); + + var inputCallback = EditorGUI.Input("##CommandInputBuffer", _commandBuffer, "", 256); + if (inputCallback.OnEnter && !_commandBuffer.IsEmpty) + { + ImGui.SetKeyboardFocusHere(-1); + _scrollToBottom = true; + + GameConsole.Instance.Enqueue(_commandBuffer); + _commandBuffer.Clear(); + } + else if (inputCallback.OnHistory(let direction)) + { + OnCommandHistory(direction); + } + } + + private void OnCommandHistory(VerticalDirection dir) + { + if (dir == .Up) + _commandBuffer.Set(GameConsole.Instance.History.HistoryUp()); + else + _commandBuffer.Set(GameConsole.Instance.History.HistoryDown()); + + ImGui.SetKeyboardFocusHere(-1); + ImGui.SetActiveID(0, &ImGui.GetCurrentWindow()); + } + } +} diff --git a/Editor/src/UI/ContentWindow.bf b/Editor/src/UI/ContentWindow.bf new file mode 100644 index 0000000..cba38d2 --- /dev/null +++ b/Editor/src/UI/ContentWindow.bf @@ -0,0 +1,69 @@ +using System; +using System.IO; +using SteelEngine; +using ImGui; + +namespace SteelEditor.UI +{ + public class ContentWindow : EditorWindow + { + public override StringView Title => "Content"; + + private String _localContentPath = new .() ~ delete _; + private String _dirPathBuffer = new .() ~ delete _; + private String _dirNameBuffer = new .() ~ delete _; + + public override void OnShow() + { + _dirPathBuffer.Set(SteelPath.ContentDirectory); + _localContentPath.Set("Content / "); + } + + public override void OnRender() + { + if (SteelPath.ContentDirectory.IsEmpty) + return; + + _dirPathBuffer.Set(SteelPath.ContentDirectory); + EditorGUI.Text(_localContentPath); + ImGui.BeginChild("##ContentTreeView", .(ImGui.GetContentRegionAvail().x / 4f, ImGui.GetContentRegionAvail().y), true); + if (!SteelPath.ContentDirectory.IsEmpty) + ShowTree(); + ImGui.EndChild(); + + EditorGUI.SameLine(); + ImGui.BeginChild("##DirectoryView", .(ImGui.GetContentRegionAvail().x, ImGui.GetContentRegionAvail().y), true); + ImGui.EndChild(); + } + + private void ShowTree() + { + for (var file in Directory.EnumerateFiles(_dirPathBuffer)) + { + var fileName = scope String(); + file.GetFileName(fileName); + if (EditorGUI.Selectable(fileName)) + { + var filePath = scope String(); + file.GetFilePath(filePath); + InspectorWindow.ViewFile(filePath); + } + } + + for (var subDir in Directory.EnumerateDirectories(_dirPathBuffer)) + { + _dirPathBuffer.Clear(); + subDir.GetFilePath(_dirPathBuffer); + + _dirNameBuffer.Clear(); + subDir.GetFileName(_dirNameBuffer); + + if (EditorGUI.BeginTree(_dirNameBuffer)) + { + ShowTree(); + EditorGUI.EndTree(); + } + } + } + } +} diff --git a/Editor/src/UI/ExtensionsWindow.bf b/Editor/src/UI/ExtensionsWindow.bf new file mode 100644 index 0000000..a4c62a1 --- /dev/null +++ b/Editor/src/UI/ExtensionsWindow.bf @@ -0,0 +1,44 @@ +/*using System; +using System.IO; +using SteelEngine; +using SteelEditor.Extensions; +using ImGui; + +namespace SteelEditor.UI +{ + public class ExtensionsWindow : EditorWindow + { + public override StringView Title => "Extensions"; + + private String _searchBuffer = new .() ~ delete _; + + public override void OnRender() + { + EditorGUI.FillWidth(); + EditorGUI.Input("##ExtensionsSearch", _searchBuffer, "Search..."); + + for (var name in Extensions.[Friend]_extensionNames) + { + if (name.StartsWith(_searchBuffer)) + { + var bgColor = ImGui.GetStyleColorVec4(.WindowBg); + const float colorDecrease = 0.015f; + bgColor.x = (bgColor.x - colorDecrease) >= 0f ? bgColor.x - colorDecrease : 0; + bgColor.y = (bgColor.y - colorDecrease) >= 0f ? bgColor.y - colorDecrease : 0; + bgColor.z = (bgColor.z - colorDecrease) >= 0f ? bgColor.z - colorDecrease : 0; + ImGui.PushStyleColor(.ChildBg, bgColor); + ImGui.BeginChild(scope String()..AppendF("##{}_Frame", name), .(0, 20), false, .NoScrollbar); + + ImGui.Indent(10); + EditorGUI.Label(name); + EditorGUI.AlignFromRight(20); + EditorGUI.Checkbox(scope String()..AppendF("##{}_Checkbox", name), true); + ImGui.Unindent(); + + ImGui.EndChild(); + ImGui.PopStyleColor(); + } + } + } + } +}*/ diff --git a/Editor/src/UI/HierarchyWindow.bf b/Editor/src/UI/HierarchyWindow.bf new file mode 100644 index 0000000..8cd17a1 --- /dev/null +++ b/Editor/src/UI/HierarchyWindow.bf @@ -0,0 +1,65 @@ +using System; +using SteelEngine; +using SteelEngine.ECS; +using ImGui; + +namespace SteelEditor.UI +{ + public class HierarchyWindow : EditorWindow + { + public override StringView Title => "Hierarchy"; + + private Entity _currentEntity = null; + + public override void OnRender() + { + for (var entity in Entity.EntityStore.Values) + { + var entityName = scope String(); + Editor.GetEntityName(entity.Id, entityName); + + if (!entity.IsEnabled) + EditorGUI.TextColor(Color.Gray); + + EditorGUI.ItemID(scope String()..AppendF("{}", entity.Id)); + if (EditorGUI.Selectable(entityName, _currentEntity != null && _currentEntity == entity)) + { + InspectorWindow.SetCurrentEntity(entity); + _currentEntity = entity; + } + + if (ImGui.BeginPopupContextItem()) + { + ShowEntityPopupMenu(); + ImGui.EndPopup(); + } + } + + if (ImGui.BeginPopupContextWindow()) + { + ShowHierarchyPopupMenu(); + ImGui.EndPopup(); + } + } + + private void ShowHierarchyPopupMenu() + { + if (ImGui.BeginMenu("Create")) + { + if (ImGui.MenuItem("Empty Entity")) + Application.Instance.CreateEntity(); + + ImGui.EndMenu(); + } + } + + private void ShowEntityPopupMenu() + { + if (ImGui.MenuItem("Delete")) + { + InspectorWindow.SetCurrentEntity(null); + delete _currentEntity; + } + } + } +} diff --git a/Editor/src/UI/InspectorWindow.bf b/Editor/src/UI/InspectorWindow.bf new file mode 100644 index 0000000..5fd4f60 --- /dev/null +++ b/Editor/src/UI/InspectorWindow.bf @@ -0,0 +1,316 @@ +using System; +using System.Collections; +using System.IO; +using System.Reflection; +using SteelEngine; +using SteelEngine.ECS; +using SteelEngine.ECS.Components; +using ImGui; + +namespace SteelEditor.UI +{ + class InspectorWindow : EditorWindow + { + public override StringView Title => "Inspector"; + + private Entity _entity = null; + private String _entityName = new .() ~ delete _; + private bool _showAddComponentPopup = false; + + private bool _isFileView = false; + private String _filePath = null ~ delete _; + private String _fileBuffer = null ~ delete _; + + public static void SetCurrentEntity(Entity entity) + { + var inspector = Editor.GetWindow(); + inspector._entity = entity; + if (inspector._isFileView) + inspector.ClearFileView(); + } + + public static void ViewFile(StringView filePath) + { + var inspector = Editor.GetWindow(); + inspector._isFileView = true; + + if (inspector._fileBuffer == null) + inspector._fileBuffer = new .(); + + if (!inspector._fileBuffer.IsEmpty) + inspector._fileBuffer.Clear(); + + if (inspector._filePath == null) + inspector._filePath = new .(filePath); + else + inspector._filePath.Set(filePath); + + File.ReadAllText(filePath, inspector._fileBuffer); + } + + private void ClearFileView() + { + _isFileView = false; + _fileBuffer.Clear(); + } + + public override void OnRender() + { + if (_isFileView) + { + ShowFileView(); + return; + } + + if (_entity == null) + return; + + _entity.IsEnabled = EditorGUI.Checkbox("##EntityEnabled", _entity.IsEnabled); + + _entityName.Clear(); + Editor.GetEntityName(_entity.Id, _entityName); + + EditorGUI.SameLine(); + EditorGUI.ItemWidth(-25); + if (EditorGUI.Input("##EntityName", _entityName).OnChange) + Editor.SetEntityName(_entity.Id, _entityName); + + EditorGUI.AlignFromRight(25); + if (EditorGUI.Button("X")) + { + Application.Instance.[Friend]RemoveEntity(_entity); + SetCurrentEntity(null); + Editor.InvalidateSave(); + return; + } + + EditorGUI.Line(); + EditorGUI.AddColumns(); + + var componentsToRender = scope List(); + + for (var component in Editor.Instance.[Friend]_components.Values) + if (component.Parent == _entity) + componentsToRender.Add(component); + + for (var component in componentsToRender) + { + var componentName = scope String(); + component.GetType().GetShortName(componentName); + if (componentName.EndsWith("Component")) + { + componentName.RemoveFromEnd(9); + componentName.[Friend]Realloc(componentName.AllocSize); + } + + EditorGUI.ItemID(scope String()..AppendF("{}", component)); + var isOpen = EditorGUI.BeginCollapsableHeader(componentName); + + EditorGUI.AlignFromRight(20); + if (EditorGUI.Selectable("X")) + { + _entity.RemoveComponent(component); + continue; + } + + if (isOpen) + { + RenderObject(component); + EditorGUI.EndCollapsableHeader(); + } + } + + EditorGUI.RemoveColumns(); + EditorGUI.NewLine(); + EditorGUI.AlignMiddle(130); + if (EditorGUI.Button("Add Component", .(130, 20))) + ImGui.OpenPopup("Add Component"); + + ShowAddComponentPopup(); + } + + private void ShowFileView() + { + EditorGUI.ItemWidth(EditorGUI.GetWindowWidth() - 23); + EditorGUI.Input("##FilePath", _filePath, "", 256, true); + + EditorGUI.SameLine(); + if (EditorGUI.Button("R")) + ViewFile(_filePath); + + EditorGUI.DisableItem(); + EditorGUI.InputMultiline("##FileView", _fileBuffer, 1024, true, EditorGUI.GetWindowSize()); + } + + private void ShowAddComponentPopup() + { + Type componentType = null; + + if (ImGui.BeginPopup("Add Component")) + { + for (var type in Type.Types) + { + if (type.IsSubtypeOf(typeof(BaseComponent))) + { + var typeName = scope String(); + type.GetShortName(typeName); + if (typeName == "BaseComponent" || typeName == "BehaviourComponent") + continue; + + if (ImGui.MenuItem(typeName)) + { + componentType = type; + _showAddComponentPopup = false; + break; + } + } + } + + ImGui.EndPopup(); + } + + if (componentType != null) + { + var createResult = componentType.CreateObject(); + if (createResult case .Err) + { + var typeName = scope String(); + componentType.GetName(typeName); + Log.Error("Failed create component ({})", typeName); + return; + } + + _entity.AddComponent((BaseComponent) createResult.Get()); + } + } + + private void RenderObject(Object object, StringView name = "") => RenderObject(object, typeof(T), name); + private void RenderObject(Object object, StringView name = "") => RenderObject(object, object.GetType(), name); + + private void RenderObject(Object object, Type type, StringView preferredName = "") + { + var name = scope String(preferredName); + if (preferredName == "") + type.GetName(name); + + var fields = scope List(type.GetFields(.Instance | .Public)); + if (fields.IsEmpty) + { + if (!type.IsSubtypeOf(typeof(BaseComponent))) + RenderValue(name, object); + return; + } + + RenderFields(fields, object); + } + + private void RenderValue(StringView name, Object object) + { + if (object.GetType().IsSubtypeOf(typeof(Entity))) + return; + + EditorGUI.LabelText(name, "{}", object); + } + + private void RenderFields(List fields, Object object) + { + for (var field in fields) + { + if (field.FieldType.IsInteger) + { + RenderInt(field, object); + continue; + } + + switch (field.FieldType) + { + case typeof(Vector3): + RenderField(=> EditorGUI.Vector3, field, object); + break; + case typeof(Vector2): + RenderField(=> EditorGUI.Vector2, field, object); + break; + case typeof(bool): + RenderField(=> EditorGUI.Checkbox, field, object); + break; + case typeof(float): + RenderField(=> EditorGUI.Float, field, object); + break; + case typeof(String): + var variant = field.GetValue(object).Get(); + EditorGUI.Input(field.GetName(), variant.Get()); + variant.Dispose(); + break; + case typeof(Entity): + var variant = field.GetValue(object).Get(); + RenderValue(field.GetName(), variant.Get()); + variant.Dispose(); + break; + default: + var variant = field.GetValue(object).Get(); + if (variant.IsObject) + { + if (!EditorGUI.BeginCollapsableHeader(field.GetName())) + break; + + RenderObject(variant.Get(), field.FieldType, field.GetName()); + EditorGUI.EndCollapsableHeader(); + } + variant.Dispose(); + } + } + } + + private void RenderField(function T(StringView label, T value) callback, FieldInfo field, Object component) + { + var variant = field.GetValue(component).Get(); + field.SetValue(component, callback(field.GetName(), variant.Get())); + variant.Dispose(); + } + + private void RenderInt(FieldInfo field, Object component) + { + var fieldName = field.GetName(); + Variant variant = ?; + + switch (field.FieldType) + { + case typeof(int): + variant = field.GetValue(component).Get(); + field.SetValue(component, (int) EditorGUI.Int(fieldName, variant.Get())); + case typeof(int8): + variant = field.GetValue(component).Get(); + field.SetValue(component, (int8) EditorGUI.Int(fieldName, variant.Get())); + case typeof(int16): + variant = field.GetValue(component).Get(); + field.SetValue(component, (int16) EditorGUI.Int(fieldName, variant.Get())); + case typeof(int32): + variant = field.GetValue(component).Get(); + field.SetValue(component, (int32) EditorGUI.Int(fieldName, variant.Get())); + case typeof(int64): + variant = field.GetValue(component).Get(); + field.SetValue(component, (int64) EditorGUI.Int(fieldName, variant.Get())); + + case typeof(uint): + variant = field.GetValue(component).Get(); + field.SetValue(component, (uint) EditorGUI.Int(fieldName, (int) variant.Get())); + case typeof(uint8): + variant = field.GetValue(component).Get(); + field.SetValue(component, (uint8) EditorGUI.Int(fieldName, variant.Get())); + case typeof(uint16): + variant = field.GetValue(component).Get(); + field.SetValue(component, (uint16) EditorGUI.Int(fieldName, variant.Get())); + case typeof(uint32): + variant = field.GetValue(component).Get(); + field.SetValue(component, (uint32) EditorGUI.Int(fieldName, variant.Get())); + case typeof(uint64): + variant = field.GetValue(component).Get(); + field.SetValue(component, (uint64) EditorGUI.Int(fieldName, (int) variant.Get())); + default: + return; + } + + variant.Dispose(); + } + } +} diff --git a/Editor/src/UI/NewProjectWindow.bf b/Editor/src/UI/NewProjectWindow.bf new file mode 100644 index 0000000..c3eec82 --- /dev/null +++ b/Editor/src/UI/NewProjectWindow.bf @@ -0,0 +1,144 @@ +using System; +using System.IO; +using SteelEngine; + +namespace SteelEditor.UI +{ + public class NewProjectWindow : EditorWindow + { + public override StringView Title => "New Project"; + + private String _projectName = new .() ~ delete _; + private String _projectPath = new .() ~ delete _; + + private const int BROWSE_BUTTON_OFFSET = 59; + private const int PATH_WIDTH = -60; + private const int CREATE_BUTTON_WIDTH = 65; + private const int CREATE_BUTTON_HEIGHT = 20; + + public override void OnShow() + { + _projectName.Clear(); + _projectPath.Clear(); + } + + public override void OnRender() + { + EditorGUI.RemoveColumns(); + + EditorGUI.Label("Name", true); + EditorGUI.Input("##NewProjectName", _projectName, "", 256, false); + + EditorGUI.Label("Path", true); + + EditorGUI.ItemWidth(PATH_WIDTH); + EditorGUI.Input("##NewProjectPath", _projectPath, "", 256, true); + + EditorGUI.SameLine(); + EditorGUI.AlignFromRight(BROWSE_BUTTON_OFFSET); + if (EditorGUI.Button("Browse")) + { + var dialog = scope FolderBrowserDialog(); + var dialogResult = dialog.ShowDialog(); + + if (dialogResult case .Err) + Log.Error("Could not show folder browser dialog"); + + if (dialogResult.Get() == .OK) + { + _projectPath.Set(dialog.SelectedPath); + if (_projectName.IsEmpty) + { + var _projectDirName = scope String(); + Path.GetFileName(_projectPath, _projectDirName); + _projectName.Set(_projectDirName); + } + } + } + + EditorGUI.NewLine(); + EditorGUI.AlignMiddle(65); + if (EditorGUI.Button("Create", .(CREATE_BUTTON_WIDTH, CREATE_BUTTON_HEIGHT))) + { + CreateProject(); + IsActive = false; + } + } + + private void CreateProject() + { + Log.Info("Creating project '{}' at '{}'", _projectName, _projectPath); + + var samplePath = scope String(); + SteelPath.GetEditorSamplePath(samplePath, "NewProject"); + Directory.Copy(samplePath, _projectPath); + + var projectPath = scope String(); + Path.InternalCombine(projectPath, _projectPath, "SteelProj.json"); + Editor.OpenProject(projectPath); + } + + private void ReplaceMacros(String path) + { + for (var file in Directory.EnumerateFiles(path)) + { + var filePath = scope String(); + file.GetFilePath(filePath); + ReplaceMacrosInFile(filePath); + + var fileName = scope String(); + file.GetFileName(fileName); + if (ReplaceMacrosInString(fileName)) + { + var newFilePath = scope String(); + Path.InternalCombine(newFilePath, path, fileName); + File.Move(filePath, newFilePath); + } + } + + for (var dir in Directory.EnumerateDirectories(path)) + { + path.Clear(); + dir.GetFilePath(path); + ReplaceMacros(path); + + var fileName = scope String(); + Path.GetFileName(path, fileName); + + if (ReplaceMacrosInString(fileName)) + { + while (path.EndsWith(Path.DirectorySeparatorChar) | path.EndsWith(Path.AltDirectorySeparatorChar)) + path.RemoveFromEnd(1); + + var parentDirPath = scope String(); + Path.GetDirectoryPath(path, parentDirPath); + + var newFilePath = scope String(); + Path.InternalCombine(newFilePath, parentDirPath, fileName); + File.Move(path, newFilePath); + } + } + } + + private void ReplaceMacrosInFile(StringView path) + { + var fileContent = new String(); + File.ReadAllText(path, fileContent); + + if (ReplaceMacrosInString(fileContent)) + File.WriteAllText(path, fileContent); + + delete fileContent; + } + + private bool ReplaceMacrosInString(String string) + { + var prevString = new String(string); + defer delete prevString; + + string.Replace("$(ProjectName)", _projectName); + + return string != prevString; + } + } +} diff --git a/Editor/src/UI/PropertiesWindow.bf b/Editor/src/UI/PropertiesWindow.bf new file mode 100644 index 0000000..36ed159 --- /dev/null +++ b/Editor/src/UI/PropertiesWindow.bf @@ -0,0 +1,10 @@ +using System; +using SteelEngine; + +namespace SteelEditor.UI +{ + public class PropertiesWindow : EditorWindow + { + public override StringView Title => "Properties"; + } +} diff --git a/Editor/src/UI/StyleWindow.bf b/Editor/src/UI/StyleWindow.bf new file mode 100644 index 0000000..3d68b4b --- /dev/null +++ b/Editor/src/UI/StyleWindow.bf @@ -0,0 +1,66 @@ +using System; +using System.Collections; +using System.IO; +using SteelEngine; +using ImGui; + +namespace SteelEditor.UI +{ + public class StyleWindow : EditorWindow + { + public override StringView Title => "Style"; + + private int32 _currentTheme = -1; + private List _themes = new .() ~ DeleteContainerAndItems!(_); + + public override void OnShow() + { + LoadThemes(); + } + + public override void OnRender() + { + EditorGUI.RemoveColumns(); + if (EditorGUI.Button("Save")) + Editor.SaveConfig(); + + EditorGUI.SameLine(); + if (EditorGUI.Button("Reload")) + Editor.LoadConfig(); + + EditorGUI.SameLine(); + if (EditorGUI.Button("Reset")) + { + Editor.ResetStyle(); + _currentTheme = -1; + } + + if (EditorGUI.Combo("Theme:", _themes, ref _currentTheme)) + Editor.SetTheme(_themes[_currentTheme]); + + if (EditorGUI.BeginCollapsableHeader("Style")) + { + ImGui.ShowStyleEditor(); + EditorGUI.EndCollapsableHeader(); + } + } + + private void LoadThemes() + { + DeleteAndClearItems!(_themes); + + String _themesPath = scope .(); + SteelPath.GetEditorResourcePath(_themesPath, "Themes"); + + for (var themeFile in Directory.EnumerateFiles(_themesPath)) + { + String fileName = scope .(); + String themeName = new .(); + + themeFile.GetFileName(fileName); + Path.GetFileNameWithoutExtension(fileName, themeName); + _themes.Add(themeName); + } + } + } +} diff --git a/Editor/src/UI/TestWindow.bf b/Editor/src/UI/TestWindow.bf new file mode 100644 index 0000000..4334a07 --- /dev/null +++ b/Editor/src/UI/TestWindow.bf @@ -0,0 +1,50 @@ +using System; +using System.Collections; +using SteelEngine; + +namespace SteelEditor.UI +{ + public class TestWindow : EditorWindow + { + public override StringView Title => "Test"; + + private bool _isChecked = false; + + private String _textInput = new String() ~ delete _; + private String _hintTextInput = new String() ~ delete _; + private int _intInput = 0; + private Vector2 _vector2Input; + private Vector3 _vector3Input; + private ComboItem _comboItem = .Item1; + + public override void OnRender() + { + if (EditorGUI.Button("TestButton")) + Log.Trace("You hit the test button!"); + + EditorGUI.Text("Try hitting this checkbox"); + + _isChecked = EditorGUI.Checkbox("Checkbox", _isChecked); + if (_isChecked) + EditorGUI.Text("Checked!"); + + EditorGUI.Input("Input", _textInput); + + EditorGUI.Input("Input with hint", _hintTextInput, "Enter text here"); + + EditorGUI.Int("Int", ref _intInput); + + EditorGUI.Vector2("Vector2", ref _vector2Input); + EditorGUI.Vector3("Vector3", ref _vector3Input); + + EditorGUI.Combo("Combo", ref _comboItem); + } + + private enum ComboItem + { + Item1, + Item2, + Item3 + } + } +} diff --git a/Editor/src/Util.bf b/Editor/src/Util.bf new file mode 100644 index 0000000..fb35b50 --- /dev/null +++ b/Editor/src/Util.bf @@ -0,0 +1,33 @@ +using ImGui; + +namespace SteelEngine +{ + extension Color + { + public static implicit operator Self(ImGui.Vec4 vec) + { + return .(vec.x, vec.y, vec.z, vec.w); + } + + public static implicit operator ImGui.Vec4(Self self) + { + return .(self.r, self.g, self.b, self.a); + } + } + + extension Vector4 + { + public static implicit operator ImGui.Vec4(Self self) where float : operator explicit T + { + return .((float) self.x, (float) self.y, (float) self.z, (float) self.w); + } + } + + extension Vector2 + { + public static implicit operator ImGui.Vec2(Self self) where float : operator explicit T + { + return .((float) self.x, (float) self.y); + } + } +} diff --git a/Engine/BeefProj.toml b/Engine/BeefProj.toml index 83609bc..80bc1cd 100644 --- a/Engine/BeefProj.toml +++ b/Engine/BeefProj.toml @@ -1,5 +1,30 @@ FileVersion = 1 -Dependencies = {corlib = "*", glfw-beef = "*", corlib = "*"} +Dependencies = {corlib = "*", corlib = "*", glfw-beef = "*"} [Project] Name = "SteelEngine" + +[Configs.Debug.Win32] +PreprocessorMacros = ["STEEL_PLATFORM_WINDOWS", "DEBUG"] + +[Configs.Debug.Win64] +CLibType = "DynamicDebug" +PreprocessorMacros = ["STEEL_PLATFORM_WINDOWS", "DEBUG"] + +[Configs.Release.Win32] +PreprocessorMacros = ["STEEL_PLATFORM_WINDOWS", "RELEASE"] + +[Configs.Release.Win64] +PreprocessorMacros = ["STEEL_PLATFORM_WINDOWS", "RELEASE"] + +[Configs.Paranoid.Win32] +PreprocessorMacros = ["STEEL_PLATFORM_WINDOWS", "PARANOID"] + +[Configs.Paranoid.Win64] +PreprocessorMacros = ["STEEL_PLATFORM_WINDOWS", "PARANOID"] + +[Configs.Test.Win32] +PreprocessorMacros = ["STEEL_PLATFORM_WINDOWS"] + +[Configs.Test.Win64] +PreprocessorMacros = ["STEEL_PLATFORM_WINDOWS"] diff --git a/Engine/src/Application.bf b/Engine/src/Application.bf index 7b51940..2840933 100644 --- a/Engine/src/Application.bf +++ b/Engine/src/Application.bf @@ -1,42 +1,88 @@ using System; +using System.Collections; +using System.IO; using SteelEngine.Window; using SteelEngine.Events; using SteelEngine.Input; using SteelEngine.ECS; using SteelEngine.ECS.Systems; using SteelEngine.ECS.Components; -using System.Collections; +using SteelEngine.Console; +using glfw_beef; namespace SteelEngine { public abstract class Application : IDisposable { + public static Application Instance; + private bool _isRunning = false; - private Window _window ~ delete _; + public Window Window { get; protected set; } private Window.EventCallback _eventCallback = new => OnEvent ~ delete _; - private LayerStack _layerStack = new .() ~ delete _; + private LayerStack _layerStack = new .(); + + private Glfw.ErrorCallback _errorCallback = new => OnGlfwError; + + private List _systems ~ DeleteContainerAndItems!(_); - private List _systems ~ delete _; private Dictionary _components ~ delete _; private List _componentsToDelete ~ delete _; private List _entitiesToRemoveFromStore ~ delete _; private GLFWInputManager _inputManager = new GLFWInputManager() ~ delete _; + private GameConsole _gameConsole = new GameConsole() ~ delete _; + public this() { - OnInit(); - } + Instance = this; - public ~this() - { - Dispose(); - } + Log.AddCallback(new (str, level) => + { + ConsoleColor color; - public void Dispose() - { - OnCleanup(); + switch (level) + { + case .Trace: + color = .Gray; + break; + case .Info: + color = .White; + break; + case .Warning: + color = .Yellow; + break; + case .Error, .Fatal: + color = .Red; + break; + } + + var origin = Console.ForegroundColor; + Console.ForegroundColor = color; + Console.WriteLine(str); + Console.ForegroundColor = origin; + }); + + Log.Trace("Initializing application"); + + _gameConsole.Initialize(scope String[]("config.cfg")); + + _components = new Dictionary(); + _componentsToDelete = new List(); + _entitiesToRemoveFromStore = new List(); + + _systems = new List(); + // The order of these systems will greatly affect the behavior of the engine. + // As functionality is added, the order of these updates should become more established. + // Maybe some kind of priority filtering could be added to make sure that systems execute in a defined order established at runtime. + CreateSystem(); + CreateSystem(); + CreateSystem(); + CreateSystem(); + CreateSystem(); + CreateSystem(); + CreateSystem(); } /// @@ -45,7 +91,7 @@ namespace SteelEngine /// public BaseSystem CreateSystem() where T : BaseSystem { - let system = new T(this); + let system = new T(); _systems.Add(system); for (let item in Entity.EntityStore) @@ -67,18 +113,33 @@ namespace SteelEngine public Entity CreateEntity() { - return new Entity(this); + return new Entity(); } public void Run() { _isRunning = true; - var windowConfig = WindowConfig(1080, 720, "SteelEngine"); - _window = new Window(windowConfig, _eventCallback); + var windowConfig = WindowConfig( + 1080, // Width + 720, // Height + "SteelEngine", // Title + false, // Undecorated + true, // Resizable + false, // VSync + true, // Maximized + false // Invisible + ); + + Window = new Window(windowConfig, _eventCallback); + + OnInit(); + + Glfw.SetErrorCallback(_errorCallback, true); Time.[Friend]Initialize(); _inputManager.Initialize(); + for (let system in _systems) { switch (system.[Friend]Initialize()) @@ -93,74 +154,44 @@ namespace SteelEngine while (_isRunning) { for (var layer in _layerStack) - layer.OnUpdate(); + layer.[Friend]OnUpdate(); - _window.Update(); + Window.Update(); Update(); Draw(); } - } - - // Gets called right before the window is created - public virtual void OnInit() - { - Log.AddHandle(Console.Out); - _components = new Dictionary(); - _componentsToDelete = new List(); - _entitiesToRemoveFromStore = new List(); + _layerStack.Clear(); - _systems = new List(); - // The order of these systems will greatly affect the behavior of the engine. - // As functionality is added, the order of these updates should become more established. - // Maybe some kind of priority filtering could be added to make sure that systems execute in a defined order established at runtime. - CreateSystem(); - CreateSystem(); - CreateSystem(); - CreateSystem(); - CreateSystem(); - CreateSystem(); - CreateSystem(); + OnCleanup(); } - // Gets called when the window is destroyed - public virtual void OnCleanup() - { - _window.Destroy(); + // Gets called right after the window is created + public virtual void OnInit() {} - // Order of deletion is important. Deleting from lowest to highest abstraction is safe. - for (let item in _components) - { - delete item.value; - } - _components.Clear(); - for (let item in Entity.EntityStore) - { - delete item.value; - } - Entity.EntityStore.Clear(); - for (let system in _systems) - { - delete system; - } - _systems.Clear(); - } + // Gets called when the window is destroyed + public virtual void OnCleanup() {} // Gets called when an event occurs in the window public void OnEvent(Event event) { - _inputManager.OnEvent(event); - var dispatcher = scope EventDispatcher(event); dispatcher.Dispatch(scope => OnWindowClose); for (var layer in _layerStack) { - layer.OnEvent(event); + layer.[Friend]OnEvent(event); if (event.IsHandled) - break; + return; } + + _inputManager.OnEvent(event); + } + + private void OnGlfwError(Glfw.Error error) + { + Log.Error("[GLFW] {}", error); } private bool OnWindowClose(WindowCloseEvent event) @@ -187,9 +218,39 @@ namespace SteelEngine system.[Friend]PostUpdate(); } + for (var layer in _layerStack) + layer.[Friend]OnUpdate(); + + _gameConsole.Update(); + OnUpdate(); } + public void PushLayer() where T : Layer + { + _layerStack.PushLayer(); + } + + public void PushLayer(Layer layer) + { + _layerStack.PushLayer(layer); + } + + public void PushOverlay() where T : Layer + { + _layerStack.PushOverlay(); + } + + public void PushOverlay(Layer layer) + { + _layerStack.PushOverlay(layer); + } + + public static T GetInstance() where T : Application + { + return (T) Instance; + } + private void Draw() { for (let system in _systems) @@ -197,7 +258,7 @@ namespace SteelEngine system.[Friend]Draw(); } - _window.Update(); + Window.Update(); } @@ -299,5 +360,32 @@ namespace SteelEngine _entitiesToRemoveFromStore.Add(entity.Id); return true; } + + public static void Exit(int exitCode = 0) + { + Environment.Exit(exitCode); + } + + public ~this() + { + Dispose(); + } + + public virtual void Dispose() + { + delete _layerStack; + + Window.Destroy(); + delete Window; + + // Order of deletion is important. Deleting from lowest to highest abstraction is safe. + for (let item in _components) + delete item.value; + + _components.Clear(); + + for (let item in Entity.EntityStore) + delete item.value; + } } } diff --git a/Engine/src/Color.bf b/Engine/src/Color.bf new file mode 100644 index 0000000..10e0819 --- /dev/null +++ b/Engine/src/Color.bf @@ -0,0 +1,46 @@ +using SteelEngine.Math; + +namespace SteelEngine +{ + public struct Color + { + public float r, g, b, a; + + public this() : this(1, 1, 1, 1) {} + + + public this(float r, float g, float b) : this(r, g, b, 1) {} + + public this(float r, float g, float b, float a) + { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + public Color Normalized => .(r / 255, b / 255, g / 255, a / 255); + + public static Color Black => .(0, 0, 0, 1); + public static Color Blue => .(0, 0, 1, 1); + public static Color Cyan => .(0, 1, 1, 1); + public static Color Gray => .(0.5f, 0.5f, 0.5f, 1); + public static Color Green => .(0, 1, 0, 1); + public static Color Grey => Gray; + public static Color Magenta => .(1, 0, 1, 1); + public static Color Red => .(1, 0, 0, 1); + public static Color Transparent => .(0, 0, 0, 0); + public static Color White => .(1, 1, 1, 1); + public static Color Yellow => .(1, 0.92f, 0.016f, 1); + + public static implicit operator Vector4(Self self) + { + return .(self.r, self.g, self.b, self.a); + } + + public static implicit operator Self(Vector4 vec) + { + return .(vec.x, vec.y, vec.z, vec.w); + } + } +} diff --git a/Engine/src/Console/CVar.bf b/Engine/src/Console/CVar.bf new file mode 100644 index 0000000..75c8e37 --- /dev/null +++ b/Engine/src/Console/CVar.bf @@ -0,0 +1,158 @@ +using System; +using System.Collections; + +namespace SteelEngine.Console +{ + abstract class CVar + { + protected String _name ~ delete _; + protected String _help ~ delete _; + + public StringView Name => _name; + public StringView Help => _help; + public CVarFlags Flags { get; protected set; } + + public bool HasFlags(CVarFlags flags) => Flags.HasFlag(flags); + public void AddFlags(CVarFlags flags) => Flags |= flags; + public void RemoveFlags(CVarFlags flags) => Flags &= ~flags; + + public abstract Type Type { get; } + + public abstract int32 GetValueInt32(); + public abstract int64 GetValueInt64(); + public abstract float GetValueFloat() ; + public abstract StringView GetValueString(String buffer) ; + + public abstract Result Execute(StringView strArgs, Span args); + + public virtual bool IsCommand => false; + + protected this(StringView name, StringView help, CVarFlags flags) + { + _name = new .(name); + _help = new .(help); + Flags = flags; + } + } + + public delegate void OnCVarChange(CVar cvar); + + class ConsoleVar : CVar where T : var + { + protected T* _value; + + public ref T Value => *_value; + + public override Type Type => typeof(T); + + public override int32 GetValueInt32() { return CVarUtil.GetValueInt32(*_value); } + public override int64 GetValueInt64() { return CVarUtil.GetValueInt64(*_value); } + public override float GetValueFloat() { return CVarUtil.GetValueFloat(*_value); } + public override StringView GetValueString(String buffer) + { + let start = buffer.Length; + (*_value).ToString(buffer); + return .(buffer, start); + } + + public override Result Execute(StringView strArgs, Span args) + { + bool changed; + if (args.Length >= 1 && CVarUtil.TryParse(this, args, ref *_value, out changed)) + { + return changed; + } + + return .Err; + } + + public override void ToString(String strBuffer) + { + strBuffer.AppendF("{0} ", Name); + GetValueString(strBuffer); + } + + public this(StringView name, StringView help, T* val, CVarFlags flags) : base(name, help, flags) + { + _value = val; + } + } + + + class EnumConsoleVar : ConsoleVar where TEnum : Enum + { + public override int32 GetValueInt32() { return (int32) (*_value); } + public override int64 GetValueInt64() { return (int32) (*_value); } + public override float GetValueFloat() { return (int)(*_value); } + + + public this(StringView name, StringView help, TEnum* val, CVarFlags flags) : base(name, help, val, flags) + { + + } + } + + public delegate bool OnCmdExecute(CVar cmd, StringView line, Span args); + public delegate void OnCmdExecuteNoArgs(); + public delegate void OnCmdExecuteLineArgs(StringView line, Span args); + + class ConsoleCommand : CVar where OnExecute : Delegate + { + protected OnExecute _onExecute ~ delete _; + + public override Type Type => typeof(OnExecute); + + public override int32 GetValueInt32() => 0; + public override int64 GetValueInt64() => 0; + public override float GetValueFloat() => 0; + public override StringView GetValueString(String buffer) => default; + + public override bool IsCommand => true; + + public override void ToString(String strBuffer) + { + strBuffer.AppendF("{0}", Name); + } + + public this(StringView name, StringView help, OnExecute onExec, CVarFlags flags) : base(name, help, flags) + { + _onExecute = onExec; + } + } + + extension ConsoleCommand where OnExecute : OnCmdExecute + { + public override Result Execute(StringView strArgs, Span args) + { + if (_onExecute == null) + return .Err; + + return _onExecute(this, strArgs, args) ? .Ok(false) : .Err; + } + } + + extension ConsoleCommand where OnExecute : OnCmdExecuteNoArgs + { + public override Result Execute(StringView strArgs, Span args) + { + if (_onExecute == null) + return .Err; + + _onExecute(); + return false; + } + } + + extension ConsoleCommand where OnExecute : OnCmdExecuteLineArgs + { + public override Result Execute(StringView strArgs, Span args) + { + if (_onExecute == null) + return .Err; + + _onExecute(strArgs, args); + return false; + } + } + +} diff --git a/Engine/src/Console/CVarFlags.bf b/Engine/src/Console/CVarFlags.bf new file mode 100644 index 0000000..838f39c --- /dev/null +++ b/Engine/src/Console/CVarFlags.bf @@ -0,0 +1,21 @@ +namespace SteelEngine.Console +{ + enum CVarFlags + { + None = 0x0000, + /// Can be executed only when cheats are enabled + Cheat = 0x0001, + /// Indicates that this CVar will change value on registration to match value in configuration file + Config = 0x0002, + /// This flag is set when CVar was present in configuration file + WasInConfig = 0x0004, + /// This flags is set when CVar value is changed after configuration file was loaded + Changed = 0x0008, + /// CVar won't show in console but its value can be changed through code + Hidden = 0x0010, + /// OnChange callback will always be called even when value didn't change + AlwaysOnChange = 0x0020, + /// Disable value check for enum variables + Flags = 0x0040 + } +} diff --git a/Engine/src/Console/CVarUtil.bf b/Engine/src/Console/CVarUtil.bf new file mode 100644 index 0000000..bce9536 --- /dev/null +++ b/Engine/src/Console/CVarUtil.bf @@ -0,0 +1,192 @@ +using System; + +namespace SteelEngine.Console +{ + static class CVarUtil + { + public static bool TryParse(CVar cvar, Span args, ref bool result, out bool didChange) + { + let currentVal = result; + + String lowercase = scope String(args[0])..ToLower(); + + switch (lowercase) + { + case "true", "1": + result = true; + case "false", "0": + result = false; + default: + switch (int.Parse(lowercase)) + { + case .Err: + didChange = false; + return false; + case .Ok(let val): + result = val != 0; + } + } + didChange = result != currentVal; + return true; + } + + public static bool TryParse(CVar cvar, Span args, ref int32 result, out bool didChange) + { + let currentVal = result; + if (int32.Parse(args[0]) case .Ok(let val)) + { + result = val; + didChange = result != currentVal; + return true; + } + + didChange = false; + return false; + } + + public static bool TryParse(CVar cvar, Span args, ref int64 result, out bool didChange) + { + let currentVal = result; + if (int64.Parse(args[0]) case .Ok(let val)) + { + result = val; + didChange = result != currentVal; + return true; + } + + didChange = false; + return false; + } + + public static bool TryParse(CVar cvar, Span args, ref float result, out bool didChange) + { + let currentVal = result; + if (float.Parse(args[0]) case .Ok(let val)) + { + result = val; + didChange = result != currentVal; + return true; + } + + didChange = false; + return false; + } + + public static bool TryParse(CVar cvar, Span args, ref String result, out bool didChange) + { + didChange = result != args[0]; + result.Clear(); + result.Append(args[0]); + return true; + } + + public static bool TryParse(CVar cvar, Span args, ref TEnum result, out bool didChange) where TEnum : Enum + { + let currentVal = result; + + if (TEnum.Parse(args[0], true) case .Ok(let val)) + { + result = val; + didChange = result != currentVal; + return true; + } + if ((int64.Parse(args[0]) case .Ok(var iVal)) && (cvar.HasFlags(.Flags) || EnumUtils.HasValue(iVal))) + { + result = *(TEnum*)&iVal; + didChange = result != currentVal; + return true; + } + + didChange = false; + return false; + } + + public static int32 GetValueInt32(bool* val) + { + return *val ? 1 : 0; + } + + public static int32 GetValueInt32(int32* val) + { + return *val; + } + + public static int32 GetValueInt32(int64* val) + { + return (int32)*val; + } + + public static int32 GetValueInt32(float* val) + { + return (int32)*val; + } + + public static int32 GetValueInt32(String* val) + { + if (int32.Parse(*val) case .Ok(let res)) + { + return res; + } + return (int32)(*val).GetHashCode(); + } + + public static int32 GetValueInt64(bool* val) + { + return *val ? 1 : 0; + } + + public static int64 GetValueInt64(int32* val) + { + return (int64)*val; + } + + public static int64 GetValueInt64(int64* val) + { + return *val; + } + + public static int64 GetValueInt64(float* val) + { + return (int64)*val; + } + + public static int64 GetValueInt64(String* val) + { + if (int64.Parse(*val) case .Ok(let res)) + { + return res; + } + return (*val).GetHashCode(); + } + + public static int32 GetValueFloat(bool* val) + { + return *val ? 1 : 0; + } + + public static float GetValueFloat(int32* val) + { + return (float)*val; + } + + public static float GetValueFloat(int64* val) + { + return (float)*val; + } + + public static float GetValueFloat(float* val) + { + return *val; + } + + public static float GetValueFloat(String* val) + { + if (float.Parse(*val) case .Ok(let res)) + { + return res; + } + return 0; + } + + } +} diff --git a/Engine/src/Console/CommandsHistory.bf b/Engine/src/Console/CommandsHistory.bf new file mode 100644 index 0000000..f4b7142 --- /dev/null +++ b/Engine/src/Console/CommandsHistory.bf @@ -0,0 +1,113 @@ +using System; + +namespace SteelEngine.Console +{ + class CommandsHistory + { + private String[] _history ~ DeleteContainerAndItems!(_); + private int _count = 0; + private int _addPos = 0; + + private int _historyPos = 0; + + public this(int maxCount) + { + _history = new String[maxCount]; + for (var item in ref _history) + item = new String(); + } + + public void Resize(int newSize) + { + if (newSize <= 0 && newSize != _history.Count) + return; + + _historyPos = 0; + + String[] newHistory = new String[newSize]; + + let count = Math.Min(newHistory.Count, _count); + int i; + for (i = 0; i < count; i++) + { + newHistory[i] = StringAt(i+1); + } + + // Delete elements that did not fit into new history + for (i = i+1; i <= _count; i++) + delete StringAt(i); + + // Allocate elements that were not copied from old history + for (i = _history.Count; i < newHistory.Count; i++) + newHistory[i] = new String(); + + _count = Math.Min(_history.Count, newHistory.Count); + _addPos = _count - 1; + + delete _history; + _history = newHistory; + } + + public void Add(StringView line) + { + _historyPos = 0; + + // Don't add line if it is same as last entry + if(At(1).Equals(line)) + return; + + _addPos++; + _count++; + if (_addPos == _history.Count) + _addPos = 0; + + if (_count > _history.Count) + _count--; + + let item = _history[_addPos]; + item.Set(line); + } + + public StringView HistoryUp() + { + if (_historyPos < _count) + _historyPos++; + + return At(_historyPos); + } + + public StringView HistoryDown() + { + if (_historyPos > 0) + _historyPos--; + + return At(_historyPos); + } + + protected String StringAt(int index) + { + // index 0 = empty string + // index 1 = last added entry + // index >= _Count = first entry in history + + // @TODO(fusion) - revisit this code, have feeling this can be simplified + + if (index == 0) + return default; + + if (_count < _history.Count) + return _history[_addPos - index + 1]; + + if (index >= _count) + return _history[(_addPos+1) % _count]; + + var i = (_addPos - index + 1) % _count; + if (i < 0) + return _history[_count + i]; + + return _history[i]; + } + + public StringView At(int index) => StringAt(index); + } +} diff --git a/Engine/src/Console/ConsoleLineParser.bf b/Engine/src/Console/ConsoleLineParser.bf new file mode 100644 index 0000000..38d70ce --- /dev/null +++ b/Engine/src/Console/ConsoleLineParser.bf @@ -0,0 +1,125 @@ +using System; +using System.Collections; + +namespace SteelEngine.Console +{ + class ConsoleLineParser + { + const char8 SEPARATOR_CHAR = ';'; + const char8 QUOTE_CHAR = '"'; + const char8 COMMENT_CHAR = '#'; + + static void SkipWhitespace(StringView input, ref int i) + { + while (i < input.Length) + { + if (!input[i].IsWhiteSpace) + break; + + i++; + } + } + + static StringView? ParseNormal(StringView input, ref int i) + { + let startPos = i; + while (i < input.Length) + { + if (input[i].IsWhiteSpace || input[i] == SEPARATOR_CHAR || input[i] == COMMENT_CHAR) + break; + + i++; + } + + if (startPos < i) + return .(input, startPos, i - startPos); + + return default; + } + + static StringView? ParseQuotes(StringView input, ref int i) + { + // @TODO(fusion) - how to handle when quote is not in pair currently this is ignored + // @TODO(fusion) - escaped quotes keep the backslash char, they should be removed + + bool isPair = false; + var startPos = i; + + while (i < input.Length) + { + i++; + + if (input[i] == QUOTE_CHAR && input[i-1] != '\\') + { + isPair = true; + i++; + break; + } + } + + if (startPos < i) + { + if (isPair) + { + startPos++; + return .(input, startPos, i - 1 - startPos); + } + + return .(input, startPos, i - startPos); + } + + return default; + } + + public static bool Tokenize(StringView input, ref int i, ref int start, List tokens) + { + tokens.Clear(); + + while (i < input.Length) + { + SkipWhitespace(input, ref i); + if (i < input.Length) + { + if (tokens.IsEmpty) + start = i; + + if (input[i] == COMMENT_CHAR) + break; + + if (input[i] == SEPARATOR_CHAR && !tokens.IsEmpty) + break; + + StringView? token; + if (input[i] == '"') + token = ParseQuotes(input, ref i); + else + token = ParseNormal(input, ref i); + + if (token.HasValue) + tokens.Add(token.Value); + else + i++; + } + } + return !tokens.IsEmpty; + } + + // Sets output to contain all tokens and + // transforms tokens to be relative to output string + public static bool Tokenize(StringView input, ref int i, ref int start, List tokens, String output) + { + if (Tokenize(input, ref i, ref start, tokens)) + { + output.Set(StringView(input, start, i - start)); + for (var t in ref tokens) + { + let offset = (t.Ptr - input.Ptr) - start; + t = .(output, offset, t.Length); + } + return true; + } + + return false; + } + } +} diff --git a/Engine/src/Console/EnumUtils.bf b/Engine/src/Console/EnumUtils.bf new file mode 100644 index 0000000..0d0563d --- /dev/null +++ b/Engine/src/Console/EnumUtils.bf @@ -0,0 +1,26 @@ +using System; +using System.Collections; + +namespace SteelEngine.Console +{ + public static class EnumUtils where TEnum : Enum + { + private static HashSet _values ~ delete _; + + static this() + { + _values = new .(); + + let type = typeof(TEnum); + for (var field in type.GetFields()) + { + _values.Add(field.[Friend]mFieldData.[Friend]mData); + } + } + + public static bool HasValue(int iVal) + { + return _values.Contains(iVal); + } + } +} diff --git a/Engine/src/Console/GameConsole.bf b/Engine/src/Console/GameConsole.bf new file mode 100644 index 0000000..708de89 --- /dev/null +++ b/Engine/src/Console/GameConsole.bf @@ -0,0 +1,473 @@ +using System; +using System.Collections; +using System.IO; +using SteelEngine.Console; + +namespace SteelEngine +{ + class GameConsole + { + struct ConfigVarValue : IDisposable + { + public String line; + public StringView[] args; + + public void Dispose() + { + delete line; + delete args; + } + } + + public struct LineEntry : IDisposable + { + public LogLevel level; + public String message; + + public void Dispose() + { + delete message; + } + } + + static Self _instance; + public static Self Instance => _instance; + + Dictionary _cvars = new Dictionary() ~ delete _; + Dictionary _cvarChangedCallbacks = new Dictionary() ~ delete _; + Dictionary _configVars = new Dictionary() ~ delete _; + Queue _enqueuedCommands = new Queue() ~ delete _; + CommandsHistory _history ~ delete _; + int32 _historySize = 50; + + int _maxLines = 1000; + List _lines = new List() ~ delete _; + + public CommandsHistory History => _history; + + bool _cvarCheatsEnabled = false; + float _cvarWaitTime = 0; + int _cvarWaitFrames = 0; + + public LogLevel logLevel = .Info; + + bool _opened = false; + public bool IsOpen = _opened; + + public this() + { + _instance = this; + } + + public ~this() + { + for (let cvar in _cvars.Values) + delete cvar; + + for (let cb in _cvarChangedCallbacks.Values) + delete cb; + + for (let line in _lines) + line.Dispose(); + + for (let val in _configVars.Values) + val.Dispose(); + } + + public void Initialize(Span configFiles) + { + Log.AddCallback(new (str, level) => + { + PrintLine(level, str); + }); + + for (var file in configFiles) + { + if (LoadConfigFile(file) case .Err(let err)) + { + Log.Error("Couldn't open configuration file {0} ({1})", file, err); + } + } + + RegisterVariable("console.historysize", "Size of commands history.", ref _historySize, .Config, new (cvar) => { _historySize = Math.Max(1, _historySize); History.Resize(_historySize); } ); + _history = new CommandsHistory(_historySize); + + RegisterVariable("console.loglevel", "Minimal level message need to be to be logged into console", ref logLevel, .Config); + RegisterVariable("sv.cheats", "Enable execution of commands with Cheat flag.", ref _cvarCheatsEnabled); + RegisterVariable("wait.frames", "Wait number of frames before continuing execution of commands.", ref _cvarWaitFrames); + RegisterVariable("wait.seconds", "Wait number of real time seconds before continuing execution of commands.", ref _cvarWaitTime); + + RegisterCommand("echo", "Print message", new (line, args) => + { + PrintInfo(line); + }); + + RegisterCommand("help", "Show list of variables and commands", new (line, args) => + { + StringView filter = args.Length > 0 ? args[0] : default; + + String buffer = scope .(); + + for (let cvar in _cvars.Values) + { + if (!cvar.HasFlags(.Hidden) && (filter.IsEmpty || cvar.Name.Contains(filter))) + { + buffer.Append(" "); + buffer.Append(cvar.Name); + if (!cvar.Help.IsEmpty) + buffer.AppendF(" - {0}", cvar.Help); + buffer.Append("\n"); + } + } + + PrintInfo(buffer); + }); + + RegisterCommand("exec", "Execute file", new (line, args) => + { + if (args.Length > 0 && ExecuteFile(args[0]) case .Err(let err)) + { + PrintErrorF("Couldn't execute file {0} ({1}).", args[0], err); + } + }); + + RegisterCommand("cls", "Clear console", new () => + { + Clear(); + }); + } + + public void Open() + { + _opened = true; + } + + public void Close() + { + _opened = false; + } + + public void Toggle() + { + _opened = !_opened; + } + + public void Clear() + { + for (let l in _lines) + l.Dispose(); + + _lines.Clear(); + } + + + protected void PrintLine(LogLevel logLevel, StringView message) + { + LineEntry entry = LineEntry() { level = logLevel }; + if (_lines.Count < _maxLines) + { + entry.message = new String(message); + } + else + { + entry.message = _lines.PopFront().message..Set(message); + } + + _lines.Add(entry); + } + + protected void PrintLineF(LogLevel logLevel, StringView format, params Object[] args) + { + String buffer = scope .(); + buffer.AppendF(format, params args); + PrintLine(logLevel, buffer); + } + + public void PrintInfo(StringView message) => PrintLine(.Info, message); + public void PrintInfoF(StringView format, params Object[] args) => PrintLineF(.Info, format, params args); + public void PrintWarning(StringView message) => PrintLine(.Warning, message); + public void PrintWarningF(StringView format, params Object[] args) => PrintLineF(.Warning, format, params args); + public void PrintError(StringView message) => PrintLine(.Warning, message); + public void PrintErrorF(StringView format, params Object[] args) => PrintLineF(.Warning, format, params args); + + protected mixin CheckCVarRegistered(StringView name) + { + if (_cvars.TryGetValue(name, let cvar)) + { + Log.Warning("CVar {0} already registered!", name); + return cvar; + } + } + + protected void LoadConfigVarValue(CVar cvar) + { + if (_configVars.GetAndRemove(cvar.Name) case .Ok(var val)) + { + if (cvar.Execute(val.value.line, val.value.args) case .Ok) + { + cvar.AddFlags(.WasInConfig); + } + else + { + Log.Error("Error occurred while setting configuration variable. Can't set value of {0} to {1}", cvar.Name, val.value.args[0]); + } + val.value.Dispose(); + } + } + + protected CVar RegisterCVar(CVar cvar, OnCVarChange onChange) + { + if (cvar.HasFlags(.Config)) + LoadConfigVarValue(cvar); + + if (onChange != null) + _cvarChangedCallbacks[cvar.Name] = onChange; + + return _cvars[cvar.Name] = cvar; + } + + public CVar RegisterVariable(StringView name, StringView help, ref bool varRef, CVarFlags flags = .None, OnCVarChange onChange = null) + { + CheckCVarRegistered!(name); + return RegisterCVar(new ConsoleVar(name, help, &varRef, flags), onChange); + } + + public CVar RegisterVariable(StringView name, StringView help, ref int32 varRef, CVarFlags flags = .None, OnCVarChange onChange = null) + { + CheckCVarRegistered!(name); + return RegisterCVar(new ConsoleVar(name, help, &varRef, flags), onChange); + } + + public CVar RegisterVariable(StringView name, StringView help, ref int64 varRef, CVarFlags flags = .None, OnCVarChange onChange = null) + { + CheckCVarRegistered!(name); + return RegisterCVar(new ConsoleVar(name, help, &varRef, flags), onChange); + } + + public CVar RegisterVariable(StringView name, StringView help, ref float varRef, CVarFlags flags = .None, OnCVarChange onChange = null) + { + CheckCVarRegistered!(name); + return RegisterCVar(new ConsoleVar(name, help, &varRef, flags), onChange); + } + + public CVar RegisterVariable(StringView name, StringView help, ref String varRef, CVarFlags flags = .None, OnCVarChange onChange = null) + { + CheckCVarRegistered!(name); + return RegisterCVar(new ConsoleVar(name, help, &varRef, flags), onChange); + } + + public CVar RegisterVariable(StringView name, StringView help, ref TEnum varRef, CVarFlags flags = .None, OnCVarChange onChange = null) where TEnum : Enum + { + CheckCVarRegistered!(name); + return RegisterCVar(new EnumConsoleVar(name, help, &varRef, flags), onChange); + } + + public CVar RegisterCommand(StringView name, StringView help, OnCmdExecute onExecute, CVarFlags flags = .None) => RegisterCommand(name, help, onExecute, flags); + public CVar RegisterCommand(StringView name, StringView help, OnCmdExecuteNoArgs onExecute, CVarFlags flags = .None) => RegisterCommand(name, help, onExecute, flags); + public CVar RegisterCommand(StringView name, StringView help, OnCmdExecuteLineArgs onExecute, CVarFlags flags = .None) => RegisterCommand(name, help, onExecute, flags); + + protected CVar RegisterCommand(StringView name, StringView help, TDelegate onExecute, CVarFlags flags) where TDelegate : Delegate + { + CheckCVarRegistered!(name); + let cvar = new ConsoleCommand(name, help, onExecute, flags); + return _cvars[cvar.Name] = cvar; + } + + public bool UnregisterCVar(StringView name) + { + if (_cvars.GetAndRemove(name) case .Ok(let val)) + { + if ( _cvarChangedCallbacks.TryGetValue(name, let cb)) + delete cb; + + delete val.value; + return true; + } + return false; + } + + public bool UnregisterCVar(CVar cvar) + { + return UnregisterCVar(cvar.Name); + } + + public CVar GetCVar(StringView name) + { + return _cvars.GetValueOrDefault(name); + } + + public void Execute(StringView cmdLine) + { + int i, start; + start = i = 0; + + List tokens = scope .(); + + while (ConsoleLineParser.Tokenize(cmdLine, ref i, ref start, tokens)) + { + StringView command = .(cmdLine, start, i - start); + ExecuteLineTokens(command, .(tokens.Ptr, tokens.Count)); + } + } + + protected void ExecuteLineTokens(StringView line, Span tokens) + { + let cmdName = tokens[0]; + if (_cvars.TryGetValue(cmdName, let cvar) && !cvar.HasFlags(.Hidden)) + { + // If CVar is not command and there are no arguments specified just print its value + if (tokens.Length == 1 && !cvar.IsCommand) + { + String buffer = scope .(); + cvar.ToString(buffer); + PrintInfo(buffer); + + return; + } + + // If the command is entered with ? parameter show its description + if (tokens.Length > 1 && tokens[1] == "?") + { + String buffer = scope .(); + buffer.AppendF("{0} ", cvar.Name); + let strVal = cvar.GetValueString(buffer); + if (!strVal.IsEmpty) + buffer.Append(' '); + + if (!cvar.Help.IsEmpty) + buffer.AppendF("- {0}", cvar.Help); + + PrintInfo(buffer); + return; + } + + if (cvar.HasFlags(.Cheat) && !_cvarCheatsEnabled) + { + PrintErrorF("{0} can only be executed when cheats are enabled.", cvar.Name); + return; + } + + StringView strArgs = line; + if (tokens.Length > 1) + { + strArgs = .(line, tokens[1].Ptr - line.Ptr); + } + + let result = cvar.Execute(strArgs, .(tokens.Ptr+1, tokens.Length-1)); + if (result case .Ok(let changed)) + { + if ((changed || cvar.HasFlags(.AlwaysOnChange)) && _cvarChangedCallbacks.TryGetValue(cvar.Name, let callback)) + { + callback(cvar); + } + } + else + { + PrintErrorF("Error occurred while executing {0}.", cmdName); + } + } + else + { + PrintErrorF("Couldn't find {0} in registered cvars.", cmdName); + } + } + + protected void AddHistory(StringView cmdLine) + { + _history.Add(cmdLine); + } + + public void Enqueue(StringView cmdLine) + { + AddHistory(cmdLine); + EnqueueNoHistory(cmdLine); + } + + public void EnqueueNoHistory(StringView cmdLine) + { + _enqueuedCommands.Enqueue(new .(cmdLine)); + } + + public void Update() + { + if (_cvarWaitTime > 0) { + _cvarWaitTime -= Time.DeltaTimeUnscaled; + return; + } + + if (_cvarWaitFrames > 0) { + _cvarWaitFrames--; + return; + } + + while (!(_cvarWaitTime > 0 || _cvarWaitFrames > 0) && _enqueuedCommands.Count > 0) + { + let line = _enqueuedCommands.Dequeue(); + Execute(line); + delete line; + } + } + + public Result ExecuteFile(StringView path) + { + StreamReader reader = scope .(); + if (reader.Open(path) case .Err(let err)) + return .Err(err); + + String buffer = scope .(); + while (reader.ReadLine(buffer) case .Ok) + { + EnqueueNoHistory(buffer); + } + + return .Ok; + } + + private Result LoadConfigFile(StringView path) + { + StreamReader reader = scope .(); + if (reader.Open(path) case .Err(let err)) + return .Err(err); + + String buffer = scope .(); + List tokens = scope .(); + + String line = new .(); + + while (reader.ReadLine(buffer) case .Ok) + { + int i = 0; + int start = 0; + + while (ConsoleLineParser.Tokenize(buffer, ref i, ref start, tokens, line)) + { + if (tokens.Count < 2) + continue; + + let name = tokens[0]; + if (_configVars.GetAndRemove(name) case .Ok(let val)) + { + val.value.Dispose(); + } + + StringView[] args = new StringView[tokens.Count-1]; + tokens.CopyTo(1, args, 0, tokens.Count-1); + _configVars.Add(name, ConfigVarValue() + { + line = line, + args = args + }); + line = new .(); + + } + + buffer.Clear(); + } + + delete line; + return .Ok; + } + + public IEnumerator GetCVars() => _cvars.Values; + } +} diff --git a/Engine/src/ECS/Components/BaseComponent.bf b/Engine/src/ECS/Components/BaseComponent.bf index ef098ba..1ddd28b 100644 --- a/Engine/src/ECS/Components/BaseComponent.bf +++ b/Engine/src/ECS/Components/BaseComponent.bf @@ -1,49 +1,50 @@ -using System; - -namespace SteelEngine.ECS.Components -{ - typealias ComponentId = uint64; - /// - /// Abstract class defining all Components. - /// A class derived from Component will be managed by an appropriate . - /// - public abstract class BaseComponent - { - public this(bool isEnabled = true, Entity parent = null) - { - Id = GetNextId(); - IsEnabled = isEnabled; - Parent = parent; - } - - /// - /// Unique identifier for the Component. - /// - public ComponentId Id { get; private set; } - - /// - /// Enabled Components are managed by their corresponding , otherwise the Component is ignored. - /// - public bool IsEnabled { get; set; } - - /// - /// Uninitialized Components are initialized by their corresponding , - /// otherwise the Component is initialized on the next , , or methods. - /// - public bool IsInitialized { get; private set; } - - /// - /// Each Component belongs to a and maintains a reference to the parent . - /// - public Entity Parent { get; set; } - - public bool IsQueuedForDeletion { get; private set; } - - private static ComponentId _nextId = 0; - - private static ComponentId GetNextId() - { - return _nextId++; - } - } -} +using System; + +namespace SteelEngine.ECS.Components +{ + typealias ComponentId = uint64; + /// + /// Abstract class defining all Components. + /// A class derived from Component will be managed by an appropriate . + /// + [Reflect, AlwaysInclude(AssumeInstantiated=true, IncludeAllMethods=true)] + public abstract class BaseComponent + { + public this(bool isEnabled = true, Entity parent = null) + { + Id = GetNextId(); + IsEnabled = isEnabled; + Parent = parent; + } + + /// + /// Unique identifier for the Component. + /// + public ComponentId Id { get; private set; } + + /// + /// Enabled Components are managed by their corresponding , otherwise the Component is ignored. + /// + public bool IsEnabled { get; set; } + + /// + /// Uninitialized Components are initialized by their corresponding , + /// otherwise the Component is initialized on the next , , or methods. + /// + public bool IsInitialized { get; private set; } + + /// + /// Each Component belongs to a and maintains a reference to the parent . + /// + public Entity Parent { get; set; } + + public bool IsQueuedForDeletion { get; private set; } + + private static ComponentId _nextId = 0; + + private static ComponentId GetNextId() + { + return _nextId++; + } + } +} diff --git a/Engine/src/ECS/Components/BehaviorComponent.bf b/Engine/src/ECS/Components/BehaviorComponent.bf index 02f84be..d7b8272 100644 --- a/Engine/src/ECS/Components/BehaviorComponent.bf +++ b/Engine/src/ECS/Components/BehaviorComponent.bf @@ -1,7 +1,10 @@ -namespace SteelEngine.ECS.Components -{ - public abstract class BehaviorComponent : BaseComponent - { - protected abstract void Update(float delta); - } -} +using System; + +namespace SteelEngine.ECS.Components +{ + [Reflect] + public abstract class BehaviorComponent : BaseComponent + { + protected abstract void Update(float delta); + } +} diff --git a/Engine/src/ECS/Components/Drawable3dComponent.bf b/Engine/src/ECS/Components/Drawable3dComponent.bf index 763008d..af892e1 100644 --- a/Engine/src/ECS/Components/Drawable3dComponent.bf +++ b/Engine/src/ECS/Components/Drawable3dComponent.bf @@ -1,6 +1,9 @@ -namespace SteelEngine.ECS.Components -{ - public class Drawable3dComponent : BaseComponent - { - } -} +using System; + +namespace SteelEngine.ECS.Components +{ + [Reflect, AlwaysInclude(AssumeInstantiated=true, IncludeAllMethods=true)] + public class Drawable3dComponent : BaseComponent + { + } +} diff --git a/Engine/src/ECS/Components/Physics2dComponent.bf b/Engine/src/ECS/Components/Physics2dComponent.bf index 1e60582..4cae854 100644 --- a/Engine/src/ECS/Components/Physics2dComponent.bf +++ b/Engine/src/ECS/Components/Physics2dComponent.bf @@ -1,6 +1,9 @@ -namespace SteelEngine.ECS.Components -{ - public class Physics2dComponent : BaseComponent - { - } -} +using System; + +namespace SteelEngine.ECS.Components +{ + [Reflect, AlwaysInclude(AssumeInstantiated=true, IncludeAllMethods=true)] + public class Physics2dComponent : BaseComponent + { + } +} diff --git a/Engine/src/ECS/Components/Physics3dComponent.bf b/Engine/src/ECS/Components/Physics3dComponent.bf index aba18d3..20623b3 100644 --- a/Engine/src/ECS/Components/Physics3dComponent.bf +++ b/Engine/src/ECS/Components/Physics3dComponent.bf @@ -1,6 +1,9 @@ -namespace SteelEngine.ECS.Components -{ - public class Physics3dComponent : BaseComponent - { - } -} +using System; + +namespace SteelEngine.ECS.Components +{ + [Reflect, AlwaysInclude(AssumeInstantiated=true, IncludeAllMethods=true)] + public class Physics3dComponent : BaseComponent + { + } +} diff --git a/Engine/src/ECS/Components/SoundComponent.bf b/Engine/src/ECS/Components/SoundComponent.bf index 57cdc6d..cd62864 100644 --- a/Engine/src/ECS/Components/SoundComponent.bf +++ b/Engine/src/ECS/Components/SoundComponent.bf @@ -1,6 +1,9 @@ -namespace SteelEngine.ECS.Components -{ - public class SoundComponent : BaseComponent - { - } -} +using System; + +namespace SteelEngine.ECS.Components +{ + [Reflect, AlwaysInclude(AssumeInstantiated=true, IncludeAllMethods=true)] + public class SoundComponent : BaseComponent + { + } +} diff --git a/Engine/src/ECS/Components/SpriteComponent.bf b/Engine/src/ECS/Components/SpriteComponent.bf index e642e60..77b45fb 100644 --- a/Engine/src/ECS/Components/SpriteComponent.bf +++ b/Engine/src/ECS/Components/SpriteComponent.bf @@ -1,6 +1,9 @@ -namespace SteelEngine.ECS.Components -{ - public class SpriteComponent : BaseComponent - { - } -} +using System; + +namespace SteelEngine.ECS.Components +{ + [Reflect, AlwaysInclude(AssumeInstantiated=true, IncludeAllMethods=true)] + public class SpriteComponent : BaseComponent + { + } +} diff --git a/Engine/src/ECS/Components/TextComponent.bf b/Engine/src/ECS/Components/TextComponent.bf index 1f963d6..1b4d83d 100644 --- a/Engine/src/ECS/Components/TextComponent.bf +++ b/Engine/src/ECS/Components/TextComponent.bf @@ -1,6 +1,9 @@ -namespace SteelEngine.ECS.Components -{ - public class TextComponent : BaseComponent - { - } -} +using System; + +namespace SteelEngine.ECS.Components +{ + [Reflect, AlwaysInclude(AssumeInstantiated=true, IncludeAllMethods=true)] + public class TextComponent : BaseComponent + { + } +} diff --git a/Engine/src/ECS/Components/TransformComponent.bf b/Engine/src/ECS/Components/TransformComponent.bf index dc508bd..e62884b 100644 --- a/Engine/src/ECS/Components/TransformComponent.bf +++ b/Engine/src/ECS/Components/TransformComponent.bf @@ -1,11 +1,13 @@ -namespace SteelEngine.ECS.Components -{ - public class TransformComponent : BaseComponent - { - /* - public Vector3 Position { get; set; } - public Vector3 Rotation { get; set; } - public Vector3 Scale { get; set; } - */ - } -} +using SteelEngine; +using System; + +namespace SteelEngine.ECS.Components +{ + [Reflect, AlwaysInclude(AssumeInstantiated=true, IncludeAllMethods=true)] + public class TransformComponent : BaseComponent + { + public Vector3 Position { get; set; } + public Vector3 Rotation { get; set; } + public Vector3 Scale { get; set; } + } +} diff --git a/Engine/src/ECS/Entity.bf b/Engine/src/ECS/Entity.bf index 4d8014e..5185214 100644 --- a/Engine/src/ECS/Entity.bf +++ b/Engine/src/ECS/Entity.bf @@ -1,88 +1,86 @@ -using System; -using System.Collections; -using SteelEngine.ECS.Components; -using SteelEngine.ECS.Systems; - -namespace SteelEngine.ECS -{ - typealias EntityId = uint64; - - public class Entity - { - public this(Application app) - { - App = app; - Id = GetNextId(); - IsEnabled = true; - - Entity.EntityStore[Id] = this; - } - - static this() - { - EntityStore = new Dictionary(); - } - - public Application App { get; private set; } - - public static Dictionary EntityStore { get; private set; }; - - /// - /// Unique identifier for the Entity. - /// - public EntityId Id { get; private set; } - - /// - /// Whether or not the Entity and all child Components should be drawn or updated. - /// - public bool IsEnabled { get; set; } - - #region Member Methods - /// - /// Adds a to the relevant . will also remove the from any Entity and it was attached to before. - /// - /// to add. - public bool AddComponent(BaseComponent component) - { - let parent = component.Parent; - if (parent != null && (parent.Id == Id || !parent.RemoveComponent(component))) - { - return false; - } - component.Parent = this; - return App.[Friend]AddComponent(component); - } - - /// - /// Removes an individual . - /// - /// to remove. - /// Whether or not the was removed. Will return false if the is not registered to this Entity. - public bool RemoveComponent(BaseComponent component) - { - let parent = component?.Parent; - if (parent == null || parent.Id != Id) - { - return false; - } - return App.[Friend]RemoveComponent(component); - } - - private static EntityId _nextId = 0; - - private static EntityId GetNextId() - { - return _nextId++; - } - - public ~this() - { - App.[Friend]RemoveEntity(this); - } - - static ~this() - { - delete EntityStore; - } - } -} +using System; +using System.Collections; +using SteelEngine.ECS.Components; +using SteelEngine.ECS.Systems; + +namespace SteelEngine.ECS +{ + typealias EntityId = uint64; + + [Reflect, AlwaysInclude(AssumeInstantiated=true, IncludeAllMethods=true)] + public class Entity + { + public this() + { + Id = GetNextId(); + IsEnabled = true; + + Entity.EntityStore[Id] = this; + } + + static this() + { + EntityStore = new Dictionary(); + } + + public static Dictionary EntityStore { get; private set; }; + + /// + /// Unique identifier for the Entity. + /// + public EntityId Id { get; private set; } + + /// + /// Whether or not the Entity and all child Components should be drawn or updated. + /// + public bool IsEnabled { get; set; } + + #region Member Methods + /// + /// Adds a to the relevant . will also remove the from any Entity and it was attached to before. + /// + /// to add. + public bool AddComponent(BaseComponent component) + { + let parent = component.Parent; + if (parent != null && (parent.Id == Id || !parent.RemoveComponent(component))) + { + return false; + } + component.Parent = this; + return Application.Instance.[Friend]AddComponent(component); + } + + /// + /// Removes an individual . + /// + /// to remove. + /// Whether or not the was removed. Will return false if the is not registered to this Entity. + public bool RemoveComponent(BaseComponent component) + { + let parent = component?.Parent; + if (parent == null || parent.Id != Id) + { + return false; + } + return Application.Instance.[Friend]RemoveComponent(component); + } + + private static EntityId _nextId = 0; + + private static EntityId GetNextId() + { + return _nextId++; + } + + public ~this() + { + Application.Instance.[Friend]RemoveEntity(this); + } + + static ~this() + { + delete EntityStore; + } + } +} diff --git a/Engine/src/ECS/Systems/BaseSystem.bf b/Engine/src/ECS/Systems/BaseSystem.bf index d6ae3e8..359eb6a 100644 --- a/Engine/src/ECS/Systems/BaseSystem.bf +++ b/Engine/src/ECS/Systems/BaseSystem.bf @@ -1,323 +1,320 @@ -using System.Collections; -using SteelEngine.ECS.Components; -using System; - -namespace SteelEngine.ECS.Systems -{ - /// - /// Defines the abstract classes for all classes. - /// - public abstract class BaseSystem - { - protected this(Application app) - { - App = app; - IsEnabled = true; - EntityToComponents = new Dictionary>(); - _uninitializedComponents = new Queue(); - _entityRegistrationChecks = new List(); - - RegisterComponentTypes(); - } - - public Application App { get; protected set; } - - /// - /// All tracked components. - /// - public Dictionary> EntityToComponents ~ delete _; - - /// - /// Whether or not the System has called . - /// - public bool IsEnabled { get; set; } - - /// - /// Whether or not the System has called . - /// - public bool IsInitialized { get; protected set; } - - /// - /// Entities that need registration checks. - /// - protected List _entityRegistrationChecks ~ delete _; - - /// - /// Tracks all potentially uninitialized objects. - /// All objects will be initialized in the , , or methods. - /// - protected Queue _uninitializedComponents ~ delete _; - - /// - /// Tracks all potentially uninitialized objects. - /// All objects will be initialized in the , , or methods. - /// - protected Type[] _registeredTypes ~ delete _; - - /// - /// Adds a to relevant . If the is not initialized, it will be queued for initialization. - /// - /// to add. - /// Whether or not the was added to this BehaviorSystem's . Returns false if the already existed. - protected virtual bool AddComponent(BaseComponent component) - { - let parent = component.Parent; - List entityComponents = ?; - if (parent == null) - { - return false; - } - bool isNewEntity = false; - if (!EntityToComponents.TryGetValue(parent.Id, out entityComponents)) - { - isNewEntity = true; - entityComponents = new List(); - } - for (let entityComponent in entityComponents) - { - if (component.Id == entityComponent.Id) - { - return false; - } - } - if (!CanBeRegistered(component)) - { - if (isNewEntity) - { - delete entityComponents; - } - return false; - } - - if (!component.IsInitialized) - { - _uninitializedComponents.Enqueue(component); - } - if (isNewEntity) - { - EntityToComponents[parent.Id] = entityComponents; - } - entityComponents.Add(component); - component.[Friend]IsQueuedForDeletion = false; - _entityRegistrationChecks.Add(parent.Id); - return true; - } - - protected bool CanBeRegistered(BaseComponent component) - { - for (let type in _registeredTypes) - { - if (CanBeRegisteredAsType(component, type)) - { - return true; - } - } - return false; - } - - protected bool CanBeRegisteredAsType(BaseComponent component, Type type) - { - let componentType = component.GetType(); - if (componentType.TypeId == type.TypeId || componentType.IsSubtypeOf(type)) - { - return true; - } - return false; - } - - protected void ClearEmptyEntities() - { - let entitiesToRemove = new List(); - defer delete entitiesToRemove; - - for (let item in EntityToComponents) - { - if (item.value == null || item.value.Count == 0) - { - entitiesToRemove.Add(item.key); - } - } - for (let entity in entitiesToRemove) - { - List components = ?; - if (EntityToComponents.TryGetValue(entity, out components)) - { - delete components; - EntityToComponents.Remove(entity); - } - } - } - - protected virtual void Draw() - { - if (!IsEnabled) - { - return; - } - InitializeComponents(); - - for (let item in EntityToComponents) - { - Draw(item.key, item.value); - } - } - - protected virtual void Draw(EntityId entityId, List components) - { - - } - - protected List GetComponentsOfEntity(Entity entity) - { - List entityComponents = ?; - EntityToComponents.TryGetValue(entity.Id, out entityComponents); - return entityComponents; - } - - protected virtual Result Initialize() - { - if (!IsEnabled || IsInitialized) - { - return .Err(.AlreadyInitialized); - } - if (InitializeComponents() == .Err) - { - return .Err(.Unknown); - } - IsInitialized = true; - return .Ok; - } - - protected virtual Result Initialize(BaseComponent component) - { - component.[Friend]IsInitialized = true; - return .Ok; - } - - protected Result InitializeComponents() - { - while (_uninitializedComponents.Count != 0) - { - let component = _uninitializedComponents.Dequeue(); - if (!component.IsInitialized) - { - if (Initialize(component) == .Err) - { - return .Err; - } - } - } - return .Ok; - } - - protected virtual void PostUpdate() - { - ClearEmptyEntities(); - } - - protected virtual void PreUpdate() - { - while (_entityRegistrationChecks.Count > 0) - { - let entity = _entityRegistrationChecks.PopBack(); - RefreshEntityRegistration(entity); - } - - ClearEmptyEntities(); - } - - protected bool RefreshEntityRegistration(EntityId entityId) - { - List components = ?; - if (!EntityToComponents.TryGetValue(entityId, out components)) - { - return false; - } - - let componentsToRemove = new List(); - defer delete componentsToRemove; - var valid = true; - // Check if every required Component is present - for (let type in _registeredTypes) - { - // Check all components for the registered type - bool found = false; - for (let component in components) - { - if (CanBeRegisteredAsType(component, type)) - { - found = true; - break; - } - } - if (found) - { - continue; - } - // Registered type is not present. This invalidates all components that are registered to this entity. - for (let component in components) - { - componentsToRemove.Add(component); - } - valid = false; - } - for (let component in componentsToRemove) - { - RemoveComponent(component); - } - - return valid; - } - - protected abstract void RegisterComponentTypes(); - - protected virtual bool RemoveComponent(BaseComponent component) - { - let parent = component.Parent; - List components = ?; - if (parent == null || !EntityToComponents.TryGetValue(parent.Id, out components)) - { - return false; - } - if (components.Remove(component)) - { - _entityRegistrationChecks.Add(parent.Id); - return true; - } - return false; - } - - protected virtual void Update(float delta) - { - if (!IsEnabled) - { - return; - } - InitializeComponents(); - - for (let item in EntityToComponents) - { - Update(item.key, item.value, delta); - } - } - - protected virtual void Update(EntityId entityId, List components, float delta) - { - - } - - public ~this() - { - for (let item in EntityToComponents) - { - delete item.value; - } - } - } - - public enum InitializationError - { - AlreadyInitialized, - Unknown, - } -} +using System.Collections; +using SteelEngine.ECS.Components; +using System; + +namespace SteelEngine.ECS.Systems +{ + /// + /// Defines the abstract classes for all classes. + /// + public abstract class BaseSystem + { + protected this() + { + IsEnabled = true; + EntityToComponents = new Dictionary>(); + _uninitializedComponents = new Queue(); + _entityRegistrationChecks = new List(); + + RegisterComponentTypes(); + } + + /// + /// All tracked components. + /// + public Dictionary> EntityToComponents ~ delete _; + + /// + /// Whether or not the System has called . + /// + public bool IsEnabled { get; set; } + + /// + /// Whether or not the System has called . + /// + public bool IsInitialized { get; protected set; } + + /// + /// Entities that need registration checks. + /// + protected List _entityRegistrationChecks ~ delete _; + + /// + /// Tracks all potentially uninitialized objects. + /// All objects will be initialized in the , , or methods. + /// + protected Queue _uninitializedComponents ~ delete _; + + /// + /// Tracks all potentially uninitialized objects. + /// All objects will be initialized in the , , or methods. + /// + protected Type[] _registeredTypes ~ delete _; + + /// + /// Adds a to relevant . If the is not initialized, it will be queued for initialization. + /// + /// to add. + /// Whether or not the was added to this BehaviorSystem's . Returns false if the already existed. + protected virtual bool AddComponent(BaseComponent component) + { + let parent = component.Parent; + List entityComponents = ?; + if (parent == null) + { + return false; + } + bool isNewEntity = false; + if (!EntityToComponents.TryGetValue(parent.Id, out entityComponents)) + { + isNewEntity = true; + entityComponents = new List(); + } + for (let entityComponent in entityComponents) + { + if (component.Id == entityComponent.Id) + { + return false; + } + } + if (!CanBeRegistered(component)) + { + if (isNewEntity) + { + delete entityComponents; + } + return false; + } + + if (!component.IsInitialized) + { + _uninitializedComponents.Enqueue(component); + } + if (isNewEntity) + { + EntityToComponents[parent.Id] = entityComponents; + } + entityComponents.Add(component); + component.[Friend]IsQueuedForDeletion = false; + _entityRegistrationChecks.Add(parent.Id); + return true; + } + + protected bool CanBeRegistered(BaseComponent component) + { + for (let type in _registeredTypes) + { + if (CanBeRegisteredAsType(component, type)) + { + return true; + } + } + return false; + } + + protected bool CanBeRegisteredAsType(BaseComponent component, Type type) + { + let componentType = component.GetType(); + if (componentType.TypeId == type.TypeId || componentType.IsSubtypeOf(type)) + { + return true; + } + return false; + } + + protected void ClearEmptyEntities() + { + let entitiesToRemove = new List(); + defer delete entitiesToRemove; + + for (let item in EntityToComponents) + { + if (item.value == null || item.value.Count == 0) + { + entitiesToRemove.Add(item.key); + } + } + for (let entity in entitiesToRemove) + { + List components = ?; + if (EntityToComponents.TryGetValue(entity, out components)) + { + delete components; + EntityToComponents.Remove(entity); + } + } + } + + protected virtual void Draw() + { + if (!IsEnabled) + { + return; + } + InitializeComponents(); + + for (let item in EntityToComponents) + { + Draw(item.key, item.value); + } + } + + protected virtual void Draw(EntityId entityId, List components) + { + + } + + protected List GetComponentsOfEntity(Entity entity) + { + List entityComponents = ?; + EntityToComponents.TryGetValue(entity.Id, out entityComponents); + return entityComponents; + } + + protected virtual Result Initialize() + { + if (!IsEnabled || IsInitialized) + { + return .Err(.AlreadyInitialized); + } + if (InitializeComponents() == .Err) + { + return .Err(.Unknown); + } + IsInitialized = true; + return .Ok; + } + + protected virtual Result Initialize(BaseComponent component) + { + component.[Friend]IsInitialized = true; + return .Ok; + } + + protected Result InitializeComponents() + { + while (_uninitializedComponents.Count != 0) + { + let component = _uninitializedComponents.Dequeue(); + if (!component.IsInitialized) + { + if (Initialize(component) == .Err) + { + return .Err; + } + } + } + return .Ok; + } + + protected virtual void PostUpdate() + { + ClearEmptyEntities(); + } + + protected virtual void PreUpdate() + { + while (_entityRegistrationChecks.Count > 0) + { + let entity = _entityRegistrationChecks.PopBack(); + RefreshEntityRegistration(entity); + } + + ClearEmptyEntities(); + } + + protected bool RefreshEntityRegistration(EntityId entityId) + { + List components = ?; + if (!EntityToComponents.TryGetValue(entityId, out components)) + { + return false; + } + + let componentsToRemove = new List(); + defer delete componentsToRemove; + var valid = true; + // Check if every required Component is present + for (let type in _registeredTypes) + { + // Check all components for the registered type + bool found = false; + for (let component in components) + { + if (CanBeRegisteredAsType(component, type)) + { + found = true; + break; + } + } + if (found) + { + continue; + } + // Registered type is not present. This invalidates all components that are registered to this entity. + for (let component in components) + { + componentsToRemove.Add(component); + } + valid = false; + } + for (let component in componentsToRemove) + { + RemoveComponent(component); + } + + return valid; + } + + protected abstract void RegisterComponentTypes(); + + protected virtual bool RemoveComponent(BaseComponent component) + { + let parent = component.Parent; + List components = ?; + if (parent == null || !EntityToComponents.TryGetValue(parent.Id, out components)) + { + return false; + } + if (components.Remove(component)) + { + _entityRegistrationChecks.Add(parent.Id); + return true; + } + return false; + } + + protected virtual void Update(float delta) + { + if (!IsEnabled) + { + return; + } + InitializeComponents(); + + for (let item in EntityToComponents) + { + Update(item.key, item.value, delta); + } + } + + protected virtual void Update(EntityId entityId, List components, float delta) + { + + } + + public ~this() + { + for (let item in EntityToComponents) + { + delete item.value; + } + } + } + + public enum InitializationError + { + AlreadyInitialized, + Unknown, + } +} diff --git a/Engine/src/ECS/Systems/BehaviorSystem.bf b/Engine/src/ECS/Systems/BehaviorSystem.bf index f069096..cec88d6 100644 --- a/Engine/src/ECS/Systems/BehaviorSystem.bf +++ b/Engine/src/ECS/Systems/BehaviorSystem.bf @@ -1,34 +1,32 @@ -using SteelEngine.ECS.Components; -using System.Collections; -using System; - -namespace SteelEngine.ECS.Systems -{ - public class BehaviorSystem : BaseSystem - { - public this(Application app) : base(app) {} - - protected override void RegisterComponentTypes() - { - _registeredTypes = new Type[]{ typeof(BehaviorComponent) }; - } - - protected override void Update(EntityId entityId, List components, float delta) - { - Entity entity = ?; - if (!Entity.EntityStore.TryGetValue(entityId, out entity) || !entity.IsEnabled) - { - return; - } - - for (let component in components) - { - if (!component.IsEnabled) - { - continue; - } - (component as BehaviorComponent).[Friend]Update(delta); - } - } - } -} +using SteelEngine.ECS.Components; +using System.Collections; +using System; + +namespace SteelEngine.ECS.Systems +{ + public class BehaviorSystem : BaseSystem + { + protected override void RegisterComponentTypes() + { + _registeredTypes = new Type[]( typeof(BehaviorComponent) ); + } + + protected override void Update(EntityId entityId, List components, float delta) + { + Entity entity = ?; + if (!Entity.EntityStore.TryGetValue(entityId, out entity) || !entity.IsEnabled) + { + return; + } + + for (let component in components) + { + if (!component.IsEnabled) + { + continue; + } + ((BehaviorComponent) component).[Friend]Update(delta); + } + } + } +} diff --git a/Engine/src/ECS/Systems/Physics2dSystem.bf b/Engine/src/ECS/Systems/Physics2dSystem.bf index 0be5d46..4fa41ae 100644 --- a/Engine/src/ECS/Systems/Physics2dSystem.bf +++ b/Engine/src/ECS/Systems/Physics2dSystem.bf @@ -1,16 +1,14 @@ -using SteelEngine.ECS.Components; -using System.Collections; -using System; - -namespace SteelEngine.ECS.Systems -{ - public class Physics2dSystem : BaseSystem - { - public this(Application app) : base(app) {} - - protected override void RegisterComponentTypes() - { - _registeredTypes = new Type[]{ typeof(Physics2dComponent), typeof(TransformComponent) }; - } - } -} +using SteelEngine.ECS.Components; +using System.Collections; +using System; + +namespace SteelEngine.ECS.Systems +{ + public class Physics2dSystem : BaseSystem + { + protected override void RegisterComponentTypes() + { + _registeredTypes = new Type[]( typeof(Physics2dComponent), typeof(TransformComponent) ); + } + } +} diff --git a/Engine/src/ECS/Systems/Physics3dSystem.bf b/Engine/src/ECS/Systems/Physics3dSystem.bf index c787d3d..dd9e3f5 100644 --- a/Engine/src/ECS/Systems/Physics3dSystem.bf +++ b/Engine/src/ECS/Systems/Physics3dSystem.bf @@ -1,16 +1,14 @@ -using SteelEngine.ECS.Components; -using System.Collections; -using System; - -namespace SteelEngine.ECS.Systems -{ - public class Physics3dSystem : BaseSystem - { - public this(Application app) : base(app) {} - - protected override void RegisterComponentTypes() - { - _registeredTypes = new Type[]{ typeof(Physics3dComponent), typeof(TransformComponent) }; - } - } -} +using SteelEngine.ECS.Components; +using System.Collections; +using System; + +namespace SteelEngine.ECS.Systems +{ + public class Physics3dSystem : BaseSystem + { + protected override void RegisterComponentTypes() + { + _registeredTypes = new Type[]( typeof(Physics3dComponent), typeof(TransformComponent) ); + } + } +} diff --git a/Engine/src/ECS/Systems/Render3dSystem.bf b/Engine/src/ECS/Systems/Render3dSystem.bf index 4ce78ab..c608804 100644 --- a/Engine/src/ECS/Systems/Render3dSystem.bf +++ b/Engine/src/ECS/Systems/Render3dSystem.bf @@ -1,16 +1,14 @@ -using SteelEngine.ECS.Components; -using System.Collections; -using System; - -namespace SteelEngine.ECS.Systems -{ - public class Render3DSystem : BaseSystem - { - public this(Application app) : base(app) {} - - protected override void RegisterComponentTypes() - { - _registeredTypes = new Type[]{ typeof(Drawable3dComponent), typeof(TransformComponent) }; - } - } -} +using SteelEngine.ECS.Components; +using System.Collections; +using System; + +namespace SteelEngine.ECS.Systems +{ + public class Render3DSystem : BaseSystem + { + protected override void RegisterComponentTypes() + { + _registeredTypes = new Type[]( typeof(Drawable3dComponent), typeof(TransformComponent) ); + } + } +} diff --git a/Engine/src/ECS/Systems/RenderSpriteSystem.bf b/Engine/src/ECS/Systems/RenderSpriteSystem.bf index 5984578..55839e1 100644 --- a/Engine/src/ECS/Systems/RenderSpriteSystem.bf +++ b/Engine/src/ECS/Systems/RenderSpriteSystem.bf @@ -1,16 +1,14 @@ -using SteelEngine.ECS.Components; -using System.Collections; -using System; - -namespace SteelEngine.ECS.Systems -{ - public class RenderSpriteSystem : BaseSystem - { - public this(Application app) : base(app) {} - - protected override void RegisterComponentTypes() - { - _registeredTypes = new Type[]{ typeof(SpriteComponent), typeof(TransformComponent) }; - } - } -} +using SteelEngine.ECS.Components; +using System.Collections; +using System; + +namespace SteelEngine.ECS.Systems +{ + public class RenderSpriteSystem : BaseSystem + { + protected override void RegisterComponentTypes() + { + _registeredTypes = new Type[]( typeof(SpriteComponent), typeof(TransformComponent) ); + } + } +} diff --git a/Engine/src/ECS/Systems/RenderTextSystem.bf b/Engine/src/ECS/Systems/RenderTextSystem.bf index 4967568..08e1fae 100644 --- a/Engine/src/ECS/Systems/RenderTextSystem.bf +++ b/Engine/src/ECS/Systems/RenderTextSystem.bf @@ -1,16 +1,14 @@ -using SteelEngine.ECS.Components; -using System.Collections; -using System; - -namespace SteelEngine.ECS.Systems -{ - public class RenderTextSystem : BaseSystem - { - public this(Application app) : base(app) {} - - protected override void RegisterComponentTypes() - { - _registeredTypes = new Type[]{ typeof(TextComponent), typeof(TransformComponent) }; - } - } -} +using SteelEngine.ECS.Components; +using System.Collections; +using System; + +namespace SteelEngine.ECS.Systems +{ + public class RenderTextSystem : BaseSystem + { + protected override void RegisterComponentTypes() + { + _registeredTypes = new Type[]( typeof(TextComponent), typeof(TransformComponent) ); + } + } +} diff --git a/Engine/src/ECS/Systems/SoundSystem.bf b/Engine/src/ECS/Systems/SoundSystem.bf index 100cc3e..6823559 100644 --- a/Engine/src/ECS/Systems/SoundSystem.bf +++ b/Engine/src/ECS/Systems/SoundSystem.bf @@ -1,16 +1,14 @@ -using SteelEngine.ECS.Components; -using System.Collections; -using System; - -namespace SteelEngine.ECS.Systems -{ - public class SoundSystem : BaseSystem - { - public this(Application app) : base(app) {} - - protected override void RegisterComponentTypes() - { - _registeredTypes = new Type[]{ typeof(SoundComponent), typeof(TransformComponent) }; - } - } -} +using SteelEngine.ECS.Components; +using System.Collections; +using System; + +namespace SteelEngine.ECS.Systems +{ + public class SoundSystem : BaseSystem + { + protected override void RegisterComponentTypes() + { + _registeredTypes = new Type[]( typeof(SoundComponent), typeof(TransformComponent) ); + } + } +} diff --git a/Engine/src/Layer.bf b/Engine/src/Layer.bf index ca45c42..27da1ed 100644 --- a/Engine/src/Layer.bf +++ b/Engine/src/Layer.bf @@ -5,6 +5,8 @@ namespace SteelEngine { public class Layer { + public bool DeleteOnDetach = false; + protected String _debugName ~ delete _; public this(StringView name = "Layer") @@ -12,9 +14,9 @@ namespace SteelEngine _debugName = new String(name); } - public virtual void OnAttach() {}; - public virtual void OnDetach() {}; - public virtual void OnUpdate() {}; - public virtual void OnEvent(Event event) {}; + protected virtual void OnAttach() {}; + protected virtual void OnDetach() {}; + protected virtual void OnUpdate() {}; + protected virtual void OnEvent(Event event) {}; } } diff --git a/Engine/src/LayerStack.bf b/Engine/src/LayerStack.bf index 573cdcf..f3c86ff 100644 --- a/Engine/src/LayerStack.bf +++ b/Engine/src/LayerStack.bf @@ -5,43 +5,53 @@ namespace SteelEngine { public class LayerStack : IEnumerable { - public bool AutoDeleteLayers; - private List _layers = new .(); private int _layerInsert = 0; - public this(bool autoDeleteLayers = true) + public ~this() { - AutoDeleteLayers = autoDeleteLayers; + Clear(); + delete _layers; } - public ~this() + public void PushLayer() where T : Layer { - if (AutoDeleteLayers) - ClearAndDeleteItems(_layers); - delete _layers; + var layer = new T(); + layer.DeleteOnDetach = true; + _layers.Insert(_layerInsert++, layer); + layer.[Friend]OnAttach(); + } + + public void PushOverlay() where T : Layer + { + var overlay = new T(); + overlay.DeleteOnDetach = true; + _layers.Add(overlay); + overlay.[Friend]OnAttach(); } public void PushLayer(Layer layer) { _layers.Insert(_layerInsert++, layer); + layer.[Friend]OnAttach(); } public void PushOverlay(Layer overlay) { _layers.Add(overlay); + overlay.[Friend]OnAttach(); } public void PopLayer() { _layerInsert--; - _layers.RemoveAt(_layerInsert); + _layers.GetAndRemove(_layers[_layerInsert]).Get().[Friend]OnDetach(); } public void PopOverlay() { if (_layers.Count > _layerInsert) - _layers.PopBack(); + _layers.PopBack().[Friend]OnDetach(); } public void RemoveLayer(StringView debugName) @@ -50,7 +60,7 @@ namespace SteelEngine { if (_layers[i].[Friend]_debugName == debugName) { - _layers.RemoveAt(i); + RemoveAt(i); _layerInsert--; break; } @@ -63,7 +73,7 @@ namespace SteelEngine { if (_layers[i].[Friend]_debugName == debugName) { - _layers.RemoveAt(i); + RemoveAt(i); break; } } @@ -75,7 +85,8 @@ namespace SteelEngine { if (typeof(T) == _layers[i].GetType()) { - _layers.RemoveAt(i); + RemoveAt(i); + _layerInsert--; return; } } @@ -87,34 +98,24 @@ namespace SteelEngine { if (typeof(T) == _layers[i].GetType()) { - _layers.RemoveAt(i); + RemoveAt(i); break; } } } - - public void RemoveLayer(Layer layer) + + private void RemoveAt(int index) { - for (int i = 0; i < _layerInsert; i++) - { - if (_layers[i] == layer) - { - _layers.RemoveAt(i); - return; - } - } + var layer = _layers.GetAndRemove(_layers[index]).Get(); + layer.[Friend]OnDetach(); + if (layer.DeleteOnDetach) + delete layer; } - public void RemoveOverlay(Layer layer) + public void Clear() { - for (int i = _layerInsert; i < _layers.Count; i++) - { - if (_layers[i] == layer) - { - _layers.RemoveAt(i); - return; - } - } + for (int i = _layers.Count - 1; i >= 0; i--) + RemoveAt(i); } public List.Enumerator GetEnumerator() diff --git a/Engine/src/Log.bf b/Engine/src/Log.bf index 24c78f6..2e4c701 100644 --- a/Engine/src/Log.bf +++ b/Engine/src/Log.bf @@ -5,18 +5,29 @@ using System.Diagnostics; namespace SteelEngine { + public delegate void LogCallback(LogLevel level, StringView message); + public static class Log { public static LogLevel LogLevel = .Trace; - private static List _handles = new .() ~ delete _; + public delegate void LogCallback(StringView str, LogLevel level); + + private static List _callbacks = new .() ~ DeleteContainerAndItems!(_); - public static void AddHandle(StreamWriter handle) => _handles.Add(handle); + public static void AddCallback(LogCallback callback) => _callbacks.Add(callback); public static void Trace(StringView format, params Object[] args) => Print(.Trace, format, params args); + public static void Trace(Object arg) => Print(.Trace, "{}", arg); + public static void Info(StringView format, params Object[] args) => Print(.Info, format, params args); + public static void Info(Object arg) => Print(.Info, "{}", arg); + public static void Warning(StringView format, params Object[] args) => Print(.Warning, format, params args); + public static void Warning(Object arg) => Print(.Warning, "{}", arg); + public static void Error(StringView format, params Object[] args) => Print(.Error, format, params args); + public static void Error(Object arg) => Print(.Error, "{}", arg); public static void Fatal(StringView format, params Object[] args) { @@ -27,36 +38,12 @@ namespace SteelEngine private static void Print(LogLevel level, StringView format, params Object[] args) { - ConsoleColor color; - - switch (level) - { - case .Trace: - color = .Gray; - break; - case .Info: - color = .White; - break; - case .Warning: - color = .Yellow; - break; - case .Error, .Fatal: - color = .Red; - break; - } - - var origin = Console.ForegroundColor; // Store original color - Console.ForegroundColor = color; // Set new color - var time = scope String()..AppendF("{}:{}:{}", DateTime.Now.Minute, DateTime.Now.Second, DateTime.Now.Millisecond); // Format current time var message = scope String()..AppendF(format, params args); // Format users message - var line = scope String()..AppendF("[{}] {}: {}", time, level, message); // Format line to print - - // Print the line to all handles - for (var handle in _handles) - handle.WriteLine(line); + var line = scope String()..AppendF("[{}] [{}] {}", time, level, message); // Format line to print - Console.ForegroundColor = origin; // Set color back to original + for (var callback in _callbacks) + callback(line, level); } } } diff --git a/Engine/src/Math/Direction.bf b/Engine/src/Math/Direction.bf new file mode 100644 index 0000000..309861c --- /dev/null +++ b/Engine/src/Math/Direction.bf @@ -0,0 +1,22 @@ +namespace SteelEngine.Math +{ + enum Direction + { + Up, + Down, + Left, + Right + } + + enum VerticalDirection + { + Up, + Down + } + + enum HorizontalDirection + { + Left, + Right + } +} diff --git a/Engine/src/SteelPath.bf b/Engine/src/SteelPath.bf new file mode 100644 index 0000000..1b14685 --- /dev/null +++ b/Engine/src/SteelPath.bf @@ -0,0 +1,59 @@ +using System; +using System.Collections; +using System.IO; + +namespace SteelEngine +{ + public static class SteelPath + { + public static String UserDirectory = new .() ~ delete _; + public static String ContentDirectory = new .() ~ delete _; + + public static this() + { + Dictionary envVars = new .(); + Environment.GetEnvironmentVariables(envVars); + + if (envVars.ContainsKey("APPDATA")) + Path.InternalCombine(UserDirectory, envVars["APPDATA"], "Steel"); + + DeleteDictionaryAndKeysAndItems!(envVars); + } + + public static void SetContentDirectory() + { + /*ContentDirectory.Clear(); + +#if DEBUG + Directory.GetCurrentDirectory(ContentDirectory); +#else + var executablePath = scope String(); + Environment.GetExecutableFilePath(executablePath); + Path.GetDirectoryPath(executablePath, ContentDirectory); +#endif + + Log.Trace("Content Directory: {}", ContentDirectory);*/ + } + + public static void GetUserPath(String target, params String[] components) + { + String[] newComponents = new String[components.Count + 1]; + newComponents[0] = UserDirectory; + components.CopyTo(newComponents, 0, 1, components.Count); + Path.InternalCombine(target, params newComponents); + delete newComponents; + } + + public static void GetGamePath(String target, params String[] components) + { + String[] newComponents = new String[components.Count + 1]; + newComponents[0] = UserDirectory; + components.CopyTo(newComponents, 0, 1, components.Count); + Path.InternalCombine(target, params newComponents); + } + + [CLink] + private static extern void LoadLibraryA(char8* lpLibFileName); + public static void LoadDLL(StringView path) => LoadLibraryA(path.Ptr); + } +} diff --git a/Engine/src/Util.bf b/Engine/src/Util.bf new file mode 100644 index 0000000..3ec5bd7 --- /dev/null +++ b/Engine/src/Util.bf @@ -0,0 +1,131 @@ +using System.Collections; + +namespace System +{ + extension Type + { + public void GetShortName(String buffer) + { + var fullName = scope String(); + GetFullName(fullName); + var parts = scope List(fullName.Split('.')); + buffer.Append(parts.Back); + } + } + + extension String + { + public void MakePath() + { + Replace("\\", "/"); + } + } +} + +namespace System.Reflection +{ + extension FieldInfo + { + public void GetName(String buffer) + { + buffer.Append(GetName()); + } + + public StringView GetName() + { + if (Name.StartsWith("prop__")) + return .(Name, 6); + else + return Name; + } + } +} + +namespace System.IO +{ + extension Directory + { + public static void Copy(StringView from, StringView to) + { + if (!Directory.Exists(from)) + { + SteelEngine.Log.Error("Tried to copy non-existing directory: {}", from); + return; + } + + if (!Directory.Exists(to)) + Directory.CreateDirectory(to); + + var toStr = new String(to); + + var filePath = new String(); + var fileName = new String(); + var newFilePath = new String(); + for (var file in Directory.EnumerateFiles(from)) + { + file.GetFilePath(filePath); + file.GetFileName(fileName); + + Path.InternalCombine(newFilePath, toStr, fileName); + + File.Copy(filePath, newFilePath); + + filePath.Clear(); + fileName.Clear(); + newFilePath.Clear(); + } + + delete filePath; + delete fileName; + delete newFilePath; + + var dirPath = new String(); + var dirName = new String(); + var newDirPath = new String(); + + for (var directory in Directory.EnumerateDirectories(from)) + { + directory.GetFilePath(dirPath); + directory.GetFileName(dirName); + Path.InternalCombine(newDirPath, toStr, dirName); + + Copy(dirPath, newDirPath); + + dirPath.Clear(); + dirName.Clear(); + newDirPath.Clear(); + } + + delete dirPath; + delete dirName; + delete newDirPath; + delete toStr; + } + + public static List GetFilesRecursively(StringView path) + { + var fileList = new List(); + GetFilesRecursively(scope String(path), fileList); + return fileList; + } + + private static void GetFilesRecursively(String path, List fileList) + { + for (var file in Directory.EnumerateFiles(path)) + { + var filePath = new String(); + file.GetFilePath(filePath); + fileList.Add(filePath); + } + + var dirs = Directory.EnumerateDirectories(path); + path.Clear(); + for (var dir in dirs) + { + dir.GetFilePath(path); + GetFilesRecursively(path, fileList); + path.Clear(); + } + } + } +} \ No newline at end of file diff --git a/Engine/src/Window/Window.bf b/Engine/src/Window/Window.bf index 81d2966..08e4b1f 100644 --- a/Engine/src/Window/Window.bf +++ b/Engine/src/Window/Window.bf @@ -3,6 +3,7 @@ using glfw_beef; using SteelEngine.Events; using SteelEngine.GL; +using SteelEngine; namespace SteelEngine.Window { @@ -54,8 +55,8 @@ namespace SteelEngine.Window Log.Fatal("Could not initialize GLFW"); } - Glfw.WindowHint(GlfwWindow.Hint.ContextVersionMajor, 3); - Glfw.WindowHint(GlfwWindow.Hint.ContextVersionMinor, 3); + Glfw.WindowHint(GlfwWindow.Hint.ContextVersionMajor, 4); + Glfw.WindowHint(GlfwWindow.Hint.ContextVersionMinor, 6); Glfw.WindowHint(GlfwWindow.Hint.OpenGlProfile, .CoreProfile); Glfw.WindowHint(GlfwWindow.Hint.OpenGlForwardCompat, Glfw.TRUE); diff --git a/Examples/EditorDemo/BeefProj.toml b/Examples/EditorDemo/BeefProj.toml new file mode 100644 index 0000000..4d502ea --- /dev/null +++ b/Examples/EditorDemo/BeefProj.toml @@ -0,0 +1,5 @@ +FileVersion = 1 + +[Project] +Name = "EditorDemo" +StartupObject = "EditorDemo.Program" diff --git a/Examples/EditorDemo/BeefSpace.toml b/Examples/EditorDemo/BeefSpace.toml new file mode 100644 index 0000000..6b83822 --- /dev/null +++ b/Examples/EditorDemo/BeefSpace.toml @@ -0,0 +1,5 @@ +FileVersion = 1 +Projects = {EditorDemo = {Path = "."}, SteelEngine = {Path = "../../Engine"}, glfw-beef = {Path = "../../Libs/GLFW"}} + +[Workspace] +StartupProject = "EditorDemo" diff --git a/Examples/EditorDemo/Content/File1.txt b/Examples/EditorDemo/Content/File1.txt new file mode 100644 index 0000000..1e1de9f --- /dev/null +++ b/Examples/EditorDemo/Content/File1.txt @@ -0,0 +1 @@ +FILE 1 \ No newline at end of file diff --git a/Examples/EditorDemo/Content/File2.txt b/Examples/EditorDemo/Content/File2.txt new file mode 100644 index 0000000..ba28208 --- /dev/null +++ b/Examples/EditorDemo/Content/File2.txt @@ -0,0 +1 @@ +NO \ No newline at end of file diff --git a/Examples/EditorDemo/Content/SubFolder/File3.txt b/Examples/EditorDemo/Content/SubFolder/File3.txt new file mode 100644 index 0000000..d5219d4 --- /dev/null +++ b/Examples/EditorDemo/Content/SubFolder/File3.txt @@ -0,0 +1 @@ +MAYBE \ No newline at end of file diff --git a/Examples/EditorDemo/SteelProj.json b/Examples/EditorDemo/SteelProj.json new file mode 100644 index 0000000..94102cd --- /dev/null +++ b/Examples/EditorDemo/SteelProj.json @@ -0,0 +1 @@ +{"Name":"EditorDemo","Entities":[{"Name":"Entity1","IsEnabled":true},{"Name":"Entity2","IsEnabled":false}]} \ No newline at end of file diff --git a/Extensions/ExampleExtension.dll b/Extensions/ExampleExtension.dll new file mode 100644 index 0000000..175f8e4 Binary files /dev/null and b/Extensions/ExampleExtension.dll differ diff --git a/Libs/Beef-Extensions-Lib b/Libs/Beef-Extensions-Lib new file mode 160000 index 0000000..7bdcadb --- /dev/null +++ b/Libs/Beef-Extensions-Lib @@ -0,0 +1 @@ +Subproject commit 7bdcadbab3e976b967731ffcac5c016e80f5a1ad diff --git a/Libs/ImGui b/Libs/ImGui new file mode 160000 index 0000000..ef2019a --- /dev/null +++ b/Libs/ImGui @@ -0,0 +1 @@ +Subproject commit ef2019a36ad3b889267e00d67d188b42ad37c9b7 diff --git a/Libs/JSON_Beef b/Libs/JSON_Beef new file mode 160000 index 0000000..e2d0b5b --- /dev/null +++ b/Libs/JSON_Beef @@ -0,0 +1 @@ +Subproject commit e2d0b5bbbadb4984443c29be914e52bfc0f1a567 diff --git a/Libs/MsgPack b/Libs/MsgPack new file mode 160000 index 0000000..4c0b930 --- /dev/null +++ b/Libs/MsgPack @@ -0,0 +1 @@ +Subproject commit 4c0b930dbe0b945cf2d6468b9772f7cdf96abece