diff --git a/Ghost.Data/Repository/ProjectRepository.cs b/Ghost.Data/Repository/ProjectRepository.cs index a45755e..5ef6954 100644 --- a/Ghost.Data/Repository/ProjectRepository.cs +++ b/Ghost.Data/Repository/ProjectRepository.cs @@ -1,19 +1,11 @@ using Ghost.Data.Models; -using System.Data; +using Ghost.Data.Resources; using System.Data.SQLite; namespace Ghost.Data.Repository; -internal class ProjectRepository : IDisposable +internal static class ProjectRepository { - private readonly SQLiteConnection _connection; - - public ProjectRepository(string sourceDirectory) - { - _connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, sourceDirectory)); - _connection.Open(); - } - private static class Command { public const string CONNECTION_STRING = "Data Source={0}\\projects.db;Version=3;"; @@ -24,18 +16,21 @@ internal class ProjectRepository : IDisposable public const string UPDATE_PROJECT_STRING = "UPDATE Projects SET Name = @Name, MetadataPath = @MetadataPath WHERE ID = @ID;"; } - private async Task EnsureTableCreatedAsync() + private static async Task EnsureTableCreatedAsync(SQLiteConnection connection) { - using var createCommand = _connection.CreateCommand(); + using var createCommand = connection.CreateCommand(); createCommand.CommandText = Command.CREATE_PROJECT_TABLE_STRING; await createCommand.ExecuteNonQueryAsync(); } - public async IAsyncEnumerable LoadProjectsAsync() + public static async IAsyncEnumerable LoadProjectsAsync() { - await EnsureTableCreatedAsync(); + using var connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, DataPath.s_applicationDataFolder)); + connection.Open(); - using var command = _connection.CreateCommand(); + await EnsureTableCreatedAsync(connection); + + using var command = connection.CreateCommand(); command.CommandText = Command.SELECT_PROJECT_STRING; using var reader = command.ExecuteReader(); @@ -52,11 +47,14 @@ internal class ProjectRepository : IDisposable } } - public async Task AddProjectAsync(ProjectInfo project) + public static async Task AddProjectAsync(ProjectInfo project) { - await EnsureTableCreatedAsync(); + using var connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, DataPath.s_applicationDataFolder)); + connection.Open(); - using var command = _connection.CreateCommand(); + await EnsureTableCreatedAsync(connection); + + using var command = connection.CreateCommand(); command.CommandText = Command.INSERT_PROJECT_STRING; command.Parameters.AddWithValue("@Name", project.Name); @@ -65,9 +63,12 @@ internal class ProjectRepository : IDisposable await command.ExecuteNonQueryAsync(); } - public async Task RemoveProjectAsync(ProjectInfo project) + public static async Task RemoveProjectAsync(ProjectInfo project) { - using var command = _connection.CreateCommand(); + using var connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, DataPath.s_applicationDataFolder)); + connection.Open(); + + using var command = connection.CreateCommand(); command.CommandText = Command.REMOVE_PROJECT_STRING; command.Parameters.AddWithValue("@ID", project.ID); @@ -75,9 +76,12 @@ internal class ProjectRepository : IDisposable await command.ExecuteNonQueryAsync(); } - public async Task UpdateProjectAsync(ProjectInfo project) + public static async Task UpdateProjectAsync(ProjectInfo project) { - using var command = _connection.CreateCommand(); + using var connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, DataPath.s_applicationDataFolder)); + connection.Open(); + + using var command = connection.CreateCommand(); command.CommandText = Command.UPDATE_PROJECT_STRING; command.Parameters.AddWithValue("@Name", project.Name); @@ -86,14 +90,4 @@ internal class ProjectRepository : IDisposable await command.ExecuteNonQueryAsync(); } - - public void Dispose() - { - if (_connection.State == ConnectionState.Open) - { - _connection.Close(); - } - - _connection.Dispose(); - } } \ No newline at end of file diff --git a/Ghost.Data/Services/ProjectService.cs b/Ghost.Data/Services/ProjectService.cs index 1a07763..486ee80 100644 --- a/Ghost.Data/Services/ProjectService.cs +++ b/Ghost.Data/Services/ProjectService.cs @@ -8,10 +8,11 @@ namespace Ghost.Data.Services; internal partial class ProjectService { - private const string _ASSETS_FOLDER = "Assets"; - private const string _CONFIG_FOLDER = "ProjectConfig"; private const string _TEMPLATE_CONTENT_FILE = "content.zip"; + public const string ASSETS_FOLDER = "Assets"; + public const string CONFIG_FOLDER = "ProjectConfig"; + public static void EnsureDefaultTemplate() { var templates = Directory.GetFiles(DataPath.s_projectTemplateFolder, "template.json", SearchOption.AllDirectories); @@ -70,8 +71,8 @@ internal partial class ProjectService return Result.Error("Project directory is invalid or does not exist."); } - var projectAssetsPath = Path.Combine(projectDirectory, _ASSETS_FOLDER); - var projectConfigPath = Path.Combine(projectDirectory, _CONFIG_FOLDER); + var projectAssetsPath = Path.Combine(projectDirectory, ASSETS_FOLDER); + var projectConfigPath = Path.Combine(projectDirectory, CONFIG_FOLDER); if (!Directory.Exists(projectAssetsPath) || !Directory.Exists(projectConfigPath)) { return Result.Error("Project folder structure is invalid."); @@ -94,8 +95,8 @@ internal partial class ProjectService private static async ValueTask SetupRequestFolderAsync(string projectDirectory, string templateDirectory) { - var projectAssetsPath = Path.Combine(projectDirectory, _ASSETS_FOLDER); - var projectConfigPath = Path.Combine(projectDirectory, _CONFIG_FOLDER); + var projectAssetsPath = Path.Combine(projectDirectory, ASSETS_FOLDER); + var projectConfigPath = Path.Combine(projectDirectory, CONFIG_FOLDER); var templateContentPath = Path.Combine(templateDirectory, _TEMPLATE_CONTENT_FILE); Directory.CreateDirectory(projectAssetsPath); @@ -111,13 +112,17 @@ internal partial class ProjectService } } -internal partial class ProjectService : IDisposable +internal partial class ProjectService { - private readonly ProjectRepository _repository = new(DataPath.s_applicationDataFolder); + public static ProjectMetadataInfo CurrentProject + { + get; + set; + } public Task AddProjectAsync(ProjectInfo project) { - return _repository.AddProjectAsync(project); + return ProjectRepository.AddProjectAsync(project); } public async Task AddProjectAsync(string name, string path) @@ -127,27 +132,27 @@ internal partial class ProjectService : IDisposable Name = name, MetadataPath = path, }; - await _repository.AddProjectAsync(project); + await ProjectRepository.AddProjectAsync(project); return project; } public Task RemoveProjectAsync(ProjectInfo project) { - return _repository.RemoveProjectAsync(project); + return ProjectRepository.RemoveProjectAsync(project); } public Task UpdateProjectAsync(ProjectInfo project) { - return _repository.UpdateProjectAsync(project); + return ProjectRepository.UpdateProjectAsync(project); } public IAsyncEnumerable LoadAllProjectAsync() { - return _repository.LoadProjectsAsync(); + return ProjectRepository.LoadProjectsAsync(); } - public async Task> CreateProjectAsync(string projectName, string projectDirectory, Version engineVersion, string templatePath) + public async Task> CreateProjectAsync(string projectName, string projectDirectory, Version engineVersion, string templatePath) { try { @@ -161,7 +166,7 @@ internal partial class ProjectService : IDisposable // Check if folder is empty if (Directory.EnumerateFiles(projectPath, "*", SearchOption.AllDirectories).Any()) { - return new(false, null, "Directory is not empty"); + return new(false, default, "Directory is not empty"); } } @@ -171,11 +176,11 @@ internal partial class ProjectService : IDisposable await SetupRequestFolderAsync(projectPath, templatePath); var info = await AddProjectAsync(projectName, metadataPath); - return new(true, info); + return new(true, new(metadataPath, metadata)); } catch (Exception e) { - return Result.Error($"Failed to create project: {e.Message}"); + return Result.Error($"Failed to create project: {e.Message}"); } } @@ -189,9 +194,4 @@ internal partial class ProjectService : IDisposable return result; } - - public void Dispose() - { - _repository.Dispose(); - } } \ No newline at end of file diff --git a/Ghost.Editor/App.xaml b/Ghost.Editor/App.xaml index b414503..723642d 100644 --- a/Ghost.Editor/App.xaml +++ b/Ghost.Editor/App.xaml @@ -9,10 +9,8 @@ - - + - diff --git a/Ghost.Editor/App.xaml.cs b/Ghost.Editor/App.xaml.cs index 434e791..e3db0e1 100644 --- a/Ghost.Editor/App.xaml.cs +++ b/Ghost.Editor/App.xaml.cs @@ -1,5 +1,5 @@ -using Ghost.Editor.AppStates; -using Ghost.Editor.Helpers; +using Ghost.Editor.Helpers; +using Ghost.Editor.Infrastructures.AppState; using Ghost.Editor.Services; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -20,7 +20,7 @@ namespace Ghost.Editor internal static Window? Window { - get => (Current as App)?._window; + get => (Current as App)!._window; set { if (Current is App app) @@ -48,16 +48,10 @@ namespace Ghost.Editor UseContentRoot(AppContext.BaseDirectory). ConfigureServices((context, services) => { - services.AddSingleton(sp => - { - return new AppStateService( - new LandingState(), - new EditorState()); - }); - HostHelper.AddLandingScope(context, services); HostHelper.AddEngineScope(context, services); + services.AddSingleton(); services.AddSingleton(); }) .Build(); @@ -92,7 +86,11 @@ namespace Ghost.Editor Host.Start(); - await GetService().TransitionToAsync(StateKey.Landing); + var stateMachine = GetService(); + stateMachine.RegisterState(StateKey.Landing, () => new LandingState()); + stateMachine.RegisterState(StateKey.EngineEditor, () => new EditorState()); + + await stateMachine.TransitionToAsync(StateKey.Landing); } private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) diff --git a/Ghost.Editor/Controls/ViewModelPage.cs b/Ghost.Editor/Controls/ViewModelPage.cs new file mode 100644 index 0000000..06f3870 --- /dev/null +++ b/Ghost.Editor/Controls/ViewModelPage.cs @@ -0,0 +1,38 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Ghost.Editor.Contracts; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; + +namespace Ghost.Editor.Controls; + +public abstract partial class ViewModelPage : Page + where VM : ObservableObject +{ + public VM ViewModel + { + get; + } + + protected ViewModelPage(VM viewModel) + { + ViewModel = viewModel; + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + base.OnNavigatedTo(e); + if (ViewModel is INavigationAware navigationAware) + { + navigationAware.OnNavigatedTo(e.Parameter); + } + } + + protected override void OnNavigatedFrom(NavigationEventArgs e) + { + base.OnNavigatedFrom(e); + if (ViewModel is INavigationAware navigationAware) + { + navigationAware.OnNavigatedFrom(); + } + } +} \ No newline at end of file diff --git a/Ghost.Editor/Ghost.Editor.csproj b/Ghost.Editor/Ghost.Editor.csproj index 4f27c93..8931038 100644 --- a/Ghost.Editor/Ghost.Editor.csproj +++ b/Ghost.Editor/Ghost.Editor.csproj @@ -44,9 +44,8 @@ - - + @@ -105,7 +104,7 @@ - + @@ -113,6 +112,11 @@ ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll + + + MSBuild:Compile + + MSBuild:Compile @@ -124,12 +128,7 @@ - - MSBuild:Compile - - - - + MSBuild:Compile diff --git a/Ghost.Editor/Helpers/Converters/AssetPathToGlyphConverter.cs b/Ghost.Editor/Helpers/Converters/AssetPathToGlyphConverter.cs new file mode 100644 index 0000000..b7e0594 --- /dev/null +++ b/Ghost.Editor/Helpers/Converters/AssetPathToGlyphConverter.cs @@ -0,0 +1,40 @@ +using Microsoft.UI.Xaml.Data; +using System; +using System.IO; + +namespace Ghost.Editor.Helpers.Converters; + +public partial class AssetPathToGlyphConverter : IValueConverter +{ + public object? Convert(object value, Type targetType, object parameter, string language) + { + if (value is not string path) + { + return null; + } + + if (Directory.Exists(path)) + { + return "\uE8B7"; + } + + var extension = Path.GetExtension(path).ToLowerInvariant(); + + // TODO: Use resource dictionary for icons. + return extension switch + { + ".fbx" or ".obj" => "\uF158", + ".png" or ".jpg" or ".jpeg" or ".gif" or ".bmp" => "\uE91B", // Image icon + ".mp3" or ".wav" or ".ogg" => "\uE767", // Audio icon + ".mp4" or ".avi" or ".mkv" => "\uE714", // Video icon + ".txt" or ".md" => "\uF000", // Text file icon + ".cs" or ".hlsl" => "\uE943", // Code file icon + _ => "\uE8A5", // Default file icon + }; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } +} diff --git a/Ghost.Editor/Helpers/HostHelpers.Page.cs b/Ghost.Editor/Helpers/HostHelpers.Page.cs index 0f3e9ab..b89652f 100644 --- a/Ghost.Editor/Helpers/HostHelpers.Page.cs +++ b/Ghost.Editor/Helpers/HostHelpers.Page.cs @@ -1,8 +1,11 @@ using Ghost.Data.Services; +using Ghost.Editor.View.Pages.EngineEditor; using Ghost.Editor.View.Pages.Landing; using Ghost.Editor.View.Windows; -using Ghost.Editor.ViewModel.Pages.Landing; -using Ghost.Editor.ViewModel.Windows; +using Ghost.Editor.ViewModels.Pages.EngineEditor; +using Ghost.Editor.ViewModels.Pages.Landing; +using Ghost.Editor.ViewModels.Windows; +using Ghost.Engine; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -12,19 +15,31 @@ internal static partial class HostHelper { public static void AddLandingScope(HostBuilderContext context, IServiceCollection services) { - services.AddSingleton(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); } public static void AddEngineScope(HostBuilderContext context, IServiceCollection services) { - services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); + + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); } } \ No newline at end of file diff --git a/Ghost.Editor/Services/AppStateService.cs b/Ghost.Editor/Infrastructures/AppState/AppStateMachine.cs similarity index 50% rename from Ghost.Editor/Services/AppStateService.cs rename to Ghost.Editor/Infrastructures/AppState/AppStateMachine.cs index 6ac3760..5f9dc0c 100644 --- a/Ghost.Editor/Services/AppStateService.cs +++ b/Ghost.Editor/Infrastructures/AppState/AppStateMachine.cs @@ -1,20 +1,23 @@ -using Ghost.Editor.AppStates; -using Ghost.Editor.Contracts; +using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; -namespace Ghost.Editor.Services; +namespace Ghost.Editor.Infrastructures.AppState; -internal class AppStateService(params IEnumerable> states) +internal class AppStateMachine { - private readonly Dictionary> _states = states.ToDictionary(s => s.StateKy, s => s); - private IAppState? _current; + private Dictionary> s_states = new(); + private IAppState? s_current; + + public void RegisterState(StateKey key, Func stateFactory) + { + s_states[key] = new(stateFactory); + } public async Task TransitionToAsync(StateKey stateKey, object? parameter = null) { - var previous = _current; - var next = _states[stateKey]; + var previous = s_current; + var next = s_states[stateKey].Value; if (previous != null) { @@ -30,6 +33,6 @@ internal class AppStateService(params IEnumerable> states) await next.OnEnteredAsync(parameter); - _current = next; + s_current = next; } } \ No newline at end of file diff --git a/Ghost.Editor/AppStates/EditorState.cs b/Ghost.Editor/Infrastructures/AppState/EditorState.cs similarity index 57% rename from Ghost.Editor/AppStates/EditorState.cs rename to Ghost.Editor/Infrastructures/AppState/EditorState.cs index 46e3055..7ad8641 100644 --- a/Ghost.Editor/AppStates/EditorState.cs +++ b/Ghost.Editor/Infrastructures/AppState/EditorState.cs @@ -1,15 +1,15 @@ using Ghost.Data.Models; -using Ghost.Editor.Contracts; +using Ghost.Data.Services; using Ghost.Editor.View.Windows; +using Ghost.Engine; using System.Threading.Tasks; -namespace Ghost.Editor.AppStates; +namespace Ghost.Editor.Infrastructures.AppState; -internal class EditorState : IAppState +internal class EditorState : IAppState { private EngineEditorWindow? _window; - - public StateKey StateKy => StateKey.EngineEditor; + private EngineCore? _engineCore; public Task OnExitingAsync() { @@ -20,23 +20,31 @@ internal class EditorState : IAppState return Task.CompletedTask; } - public Task OnEnteringAsync(object? parameter) + public async Task OnEnteringAsync(object? parameter) { - if (parameter is not ProjectMetadata metadata) + if (parameter is not ProjectMetadataInfo metadataInfo) { throw new System.ArgumentException("Parameter must be of type ProjectMetadata.", nameof(parameter)); } + ProjectService.CurrentProject = metadataInfo; + + _engineCore = App.GetService(); + await _engineCore.StartAsync(new Engine.Models.LaunchArgument()); + _window = App.GetService(); - _window.ViewModel.CurrentProject = metadata; _window.Activate(); App.Window = _window; - return Task.CompletedTask; } - public Task OnExitedAsync() + public async Task OnExitedAsync() { + if (_engineCore != null) + { + await _engineCore.ShutDownAsync(); + } + if (App.Window == _window) { App.Window = null; @@ -44,7 +52,6 @@ internal class EditorState : IAppState _window?.Close(); _window = null; - return Task.CompletedTask; } public Task OnEnteredAsync(object? parameter) diff --git a/Ghost.Editor/Contracts/IAppState.cs b/Ghost.Editor/Infrastructures/AppState/IAppState.cs similarity index 88% rename from Ghost.Editor/Contracts/IAppState.cs rename to Ghost.Editor/Infrastructures/AppState/IAppState.cs index 511ec2f..0012257 100644 --- a/Ghost.Editor/Contracts/IAppState.cs +++ b/Ghost.Editor/Infrastructures/AppState/IAppState.cs @@ -1,14 +1,9 @@ using System.Threading.Tasks; -namespace Ghost.Editor.Contracts; +namespace Ghost.Editor.Infrastructures.AppState; -internal interface IAppState +internal interface IAppState { - public Key StateKy - { - get; - } - /// /// Called when exiting the state. /// diff --git a/Ghost.Editor/AppStates/LandingState.cs b/Ghost.Editor/Infrastructures/AppState/LandingState.cs similarity index 79% rename from Ghost.Editor/AppStates/LandingState.cs rename to Ghost.Editor/Infrastructures/AppState/LandingState.cs index fd0de78..d0769aa 100644 --- a/Ghost.Editor/AppStates/LandingState.cs +++ b/Ghost.Editor/Infrastructures/AppState/LandingState.cs @@ -1,15 +1,12 @@ -using Ghost.Editor.Contracts; -using Ghost.Editor.View.Windows; +using Ghost.Editor.View.Windows; using System.Threading.Tasks; -namespace Ghost.Editor.AppStates; +namespace Ghost.Editor.Infrastructures.AppState; -internal class LandingState : IAppState +internal class LandingState : IAppState { private LandingWindow? _window; - public StateKey StateKy => StateKey.Landing; - public Task OnExitingAsync() { if (App.Window == _window) diff --git a/Ghost.Editor/AppStates/StateKey.cs b/Ghost.Editor/Infrastructures/AppState/StateKey.cs similarity index 55% rename from Ghost.Editor/AppStates/StateKey.cs rename to Ghost.Editor/Infrastructures/AppState/StateKey.cs index c4eb4b2..7a9d385 100644 --- a/Ghost.Editor/AppStates/StateKey.cs +++ b/Ghost.Editor/Infrastructures/AppState/StateKey.cs @@ -1,4 +1,4 @@ -namespace Ghost.Editor.AppStates; +namespace Ghost.Editor.Infrastructures.AppState; internal enum StateKey { diff --git a/Ghost.Editor/Infrastructures/SceneGraph/EntityNode.cs b/Ghost.Editor/Infrastructures/SceneGraph/EntityNode.cs new file mode 100644 index 0000000..0928b18 --- /dev/null +++ b/Ghost.Editor/Infrastructures/SceneGraph/EntityNode.cs @@ -0,0 +1,17 @@ +using Ghost.Entities; + +namespace Ghost.Editor.Infrastructures.SceneGraph; + +public partial class EntityNode : SceneGraphNode +{ + private readonly Entity _entity; + + public Entity Entity => _entity; + public override NodeType Type => NodeType.Entity; + + public EntityNode(Entity entity, string name) + { + _entity = entity; + Name = name; + } +} \ No newline at end of file diff --git a/Ghost.Editor/Infrastructures/SceneGraph/SceneGraphHelpers.cs b/Ghost.Editor/Infrastructures/SceneGraph/SceneGraphHelpers.cs new file mode 100644 index 0000000..5f2f1de --- /dev/null +++ b/Ghost.Editor/Infrastructures/SceneGraph/SceneGraphHelpers.cs @@ -0,0 +1,112 @@ +using Ghost.Engine.Components; +using Ghost.Entities; + +namespace Ghost.Editor.Infrastructures.SceneGraph; + +internal class SceneGraphHelpers +{ + /// + /// Creates a new entity with default components. + /// + /// The world context where the entity will be created. + /// The entity to be wrapped in the . + public static EntityNode CreateEntityNode(World world, Entity entity, string name) + { + world.EntityManager.AddComponent(entity, LocalToWorld.Identity); + world.EntityManager.AddComponent(entity, Hierarchy.Root); + return new EntityNode(entity, name); + } + + /// + /// Creates a new and entity with default components. + /// + /// The world context where the entity will be created. + public static EntityNode CreateEntityNode(World world, string name) + { + var entity = world.EntityManager.CreateEntity(); + return CreateEntityNode(world, entity, name); + } + + /// + /// Attaches childEntity to parentEntity in the scene graph. + /// + /// The world context where the entities exist. + /// The parent entity to which the child will be attached. + /// The child entity to be attached. + public static void AttachChild(SceneNode scene, EntityNode parentNode, EntityNode childNode) + { + // 1) If the child already has a parent, detach it first + var childHierarchy = scene.World.EntityManager.GetComponent(childNode.Entity); + if (childHierarchy.ValueRO.parent != Entity.Invalid) + { + DetachFromParent(scene, childNode); + } + + // 2) Link child to new parent + childHierarchy.ValueRW.parent = parentNode.Entity; + + // 3) Insert child at the head of parent's child list + var parentHierarchy = scene.World.EntityManager.GetComponent(parentNode.Entity); + + childHierarchy.ValueRW.nextSibling = parentHierarchy.ValueRO.firstChild; + parentHierarchy.ValueRW.firstChild = childNode.Entity; + + // 4) Write back + scene.World.EntityManager.SetComponent(parentNode.Entity, in parentHierarchy.ValueRO); + scene.World.EntityManager.SetComponent(childNode.Entity, in childHierarchy.ValueRO); + + // 5) Update children list in parent node + parentNode.Children.Add(childNode); + } + + /// + /// Detaches the specified entity from its parent in the scene graph. + /// + /// The world context where the entities exist. + /// The entity to detach from its parent. + public static void DetachFromParent(SceneNode scene, EntityNode node) + { + var hierarchy = scene.World.EntityManager.GetComponent(node.Entity); + var parent = hierarchy.ValueRO.parent; + if (parent == Entity.Invalid) + { + return; // already root + } + + var parentHierarchy = scene.World.EntityManager.GetComponent(parent); + + // If entity is the first child, simply move head + if (parentHierarchy.ValueRO.firstChild == node.Entity) + { + parentHierarchy.ValueRW.firstChild = hierarchy.ValueRO.nextSibling; + } + else + { + // Otherwise, find the previous sibling in the linked list + var prevSibling = parentHierarchy.ValueRO.firstChild; + while (prevSibling != Entity.Invalid) + { + var prevHierarchy = scene.World.EntityManager.GetComponent(prevSibling); + if (prevHierarchy.ValueRW.nextSibling == node.Entity) + { + prevHierarchy.ValueRW.nextSibling = hierarchy.ValueRO.nextSibling; + scene.World.EntityManager.SetComponent(prevSibling, in prevHierarchy.ValueRO); + break; + } + + prevSibling = prevHierarchy.ValueRO.nextSibling; + } + } + + // Clear child's references + hierarchy.ValueRW.parent = Entity.Invalid; + hierarchy.ValueRW.nextSibling = Entity.Invalid; + + // Write back + scene.World.EntityManager.SetComponent(parent, in parentHierarchy.ValueRO); + scene.World.EntityManager.SetComponent(node.Entity, in hierarchy.ValueRO); + + // Remove from parent's children list + scene.EntityNodeLookup[parent].Children.Remove(node); + } +} \ No newline at end of file diff --git a/Ghost.Editor/Infrastructures/SceneGraph/SceneGraphNode.cs b/Ghost.Editor/Infrastructures/SceneGraph/SceneGraphNode.cs new file mode 100644 index 0000000..bcea333 --- /dev/null +++ b/Ghost.Editor/Infrastructures/SceneGraph/SceneGraphNode.cs @@ -0,0 +1,32 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using System.Collections.ObjectModel; + +namespace Ghost.Editor.Infrastructures.SceneGraph; + +public abstract partial class SceneGraphNode : ObservableObject +{ + public enum NodeType + { + Scene, + Entity, + } + + public abstract NodeType Type + { + get; + } + + [ObservableProperty] + public partial string Name + { + get; + set; + } + + // Will the new collection allocated if ui bind to this property? + private ObservableCollection? _children; + public ObservableCollection Children + { + get => _children ??= new(); + } +} \ No newline at end of file diff --git a/Ghost.Editor/Infrastructures/SceneGraph/SceneNode.cs b/Ghost.Editor/Infrastructures/SceneGraph/SceneNode.cs new file mode 100644 index 0000000..df2ae45 --- /dev/null +++ b/Ghost.Editor/Infrastructures/SceneGraph/SceneNode.cs @@ -0,0 +1,58 @@ +using Ghost.Engine.Components; +using Ghost.Entities; +using System.Collections.Generic; + +namespace Ghost.Editor.Infrastructures.SceneGraph; + +public partial class SceneNode : SceneGraphNode +{ + private readonly World _world; + private Dictionary _entityNodeLookup = new(); + + public World World => _world; + public Dictionary EntityNodeLookup => _entityNodeLookup; + + public override NodeType Type => NodeType.Scene; + + public SceneNode(World world, string name) + { + _world = world; + Name = name; + } + + private EntityNode BuildNodeRecursive(Entity entity, World world) + { + // TODO: Node serialization. + var node = new EntityNode(entity, "New Entity"); + _entityNodeLookup[entity] = node; + + var hc = world.EntityManager.GetComponent(entity); + var child = hc.ValueRO.firstChild; + + while (child != Entity.Invalid) + { + node.Children.Add(BuildNodeRecursive(child, world)); + var childHC = world.EntityManager.GetComponent(child); + child = childHC.ValueRO.nextSibling; + } + + return node; + } + + private void BuildGraph() + { + foreach (var (entity, hierarchy) in _world.Query()) + { + if (hierarchy.ValueRO.parent == Entity.Invalid) + { + var node = BuildNodeRecursive(entity, _world); + Children.Add(node); + } + } + } + + public void Load() + { + BuildGraph(); + } +} \ No newline at end of file diff --git a/Ghost.Editor/Models/AssetItem.cs b/Ghost.Editor/Models/AssetItem.cs new file mode 100644 index 0000000..3b30563 --- /dev/null +++ b/Ghost.Editor/Models/AssetItem.cs @@ -0,0 +1,19 @@ +namespace Ghost.Editor.Models; + +internal struct AssetItem() +{ + public string AssetPath + { + get; set; + } = string.Empty; + + public string AssetName + { + get; set; + } = string.Empty; + + public string IconGlyph + { + get; set; + } = string.Empty; +} diff --git a/Ghost.Editor/Models/ExplorerItem.cs b/Ghost.Editor/Models/ExplorerItem.cs new file mode 100644 index 0000000..f148185 --- /dev/null +++ b/Ghost.Editor/Models/ExplorerItem.cs @@ -0,0 +1,27 @@ +using System.Collections.ObjectModel; + +namespace Ghost.Editor.Models; + +internal class ExplorerItem(string name, string path, bool isDirectory) +{ + public string Name + { + get; + } = name; + + public string Path + { + get; + } = path; + + public bool IsDirectory + { + get; + } = isDirectory; + + public ObservableCollection? Children + { + get; + set; + } +} \ No newline at end of file diff --git a/Ghost.Editor/Models/GameObject.cs b/Ghost.Editor/Models/GameObject.cs deleted file mode 100644 index 56d077a..0000000 --- a/Ghost.Editor/Models/GameObject.cs +++ /dev/null @@ -1,269 +0,0 @@ -using CommunityToolkit.Mvvm.ComponentModel; -using Ghost.Entities; -using Ghost.Entities.Helpers; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; - -namespace Ghost.Editor.Models; - -public partial class GameObject : ObservableObject -{ - [ObservableProperty] - public partial bool IsActive - { - get; - set; - } - - [ObservableProperty] - public partial bool IsActiveHierarchy - { - get; - set; - } - - public Entity Entity - { - get; - } - - public Scene Scene - { - get; - internal set; - } - - public GameObject? Parent - { - get; - internal set; - } - - public string Name - { - get; - set; - } - - [ObservableProperty] - public partial ObservableCollection? Components - { - get; - private set; - } - - [ObservableProperty] - public partial IEnumerable? ScriptComponents - { - get; - private set; - } - - [ObservableProperty] - public partial ObservableCollection? Children - { - get; - private set; - } - - public GameObject(Scene scene, string name) - { - Entity = scene.World.EntityManager.CreateEntity(); - Scene = scene; - Name = name; - IsActive = true; - } - - partial void OnIsActiveChanged(bool value) - { - IsActiveHierarchy = value && (Parent?.IsActiveHierarchy ?? true); - HandleActiveStateChanged(); - - if (Children != null) - { - foreach (var child in Children) - { - child.IsActiveHierarchy = value && IsActiveHierarchy; - } - } - } - - partial void OnIsActiveHierarchyChanged(bool value) - { - HandleActiveStateChanged(); - } - - private void HandleActiveStateChanged() - { - if (IsActive && IsActiveHierarchy) - { - OnEnable(); - } - else - { - OnDisable(); - } - } - - internal void OnEnable() - { - if (ScriptComponents != null) - { - foreach (var script in ScriptComponents) - { - if (!script.Enable) - { - continue; - } - script.OnEnable(); - } - } - } - - internal void OnDisable() - { - if (ScriptComponents != null) - { - foreach (var script in ScriptComponents) - { - if (!script.Enable) - { - continue; - } - script.OnDisable(); - } - } - } - - public void AddChild(GameObject child) - { - if (child.Scene != Scene) - { - throw new InvalidOperationException("Child GameObject must belong to the same Scene."); - } - - Children ??= new(); - Children.Add(child); - child.Parent = this; - } - - public bool RemoveChild(GameObject child) - { - if (Children is null) - { - return false; - } - - if (!Children.Remove(child)) - { - return false; - } - - child.Parent = null; - return true; - } - - public void Destroy() - { - if (ScriptComponents != null) - { - foreach (var component in ScriptComponents) - { - if (!component.Enable) - { - continue; - } - component.OnDestroy(); - } - } - - if (Children != null) - { - foreach (var child in Children) - { - child.Destroy(); - } - - Children.Clear(); - } - - Parent?.Children?.Remove(this); - Entity.Destroy(); - } -} - -public partial class GameObject -{ - // TODO: Implement a more efficient synchronization mechanism for components - internal void SyncComponents() - { - foreach (var (typeHandle, mask) in Scene.World.ComponentStorage.ComponentEntityMasks) - { - if (!mask.IsSet(Entity.ID)) - { - continue; - } - - var pool = Scene.World.ComponentStorage.ComponentPools[typeHandle]; - } - } - - internal void SyncScripts() - { - var scriptsPool = Scene.World.ComponentStorage.ScriptComponentPool.ScriptComponents; - if (scriptsPool == null) - { - return; - } - - scriptsPool.TryGetValue(Entity, out var scripts); - ScriptComponents = scripts; - } - - public void AddComponent(T component) - where T : struct, IComponentData - { - Entity.AddComponent(component); - SyncComponents(); - } - - public bool RemoveComponent() - where T : struct, IComponentData - { - var result = Entity.RemoveComponent(); - SyncComponents(); - - return result; - } - - public void AddScript() - where T : ScriptComponent, new() - { - Entity.AddScript(); - SyncScripts(); - } - - public void AddScript(Type type) - { - Entity.AddScript(type); - SyncScripts(); - } - - public bool RemoveScript() - where T : ScriptComponent - { - var result = Scene.World.EntityManager.RemoveScript(Entity); - SyncScripts(); - - return result; - } - - public bool RemoveScriptAt(int index) - { - var result = Scene.World.EntityManager.RemoveScriptAt(Entity, index); - SyncScripts(); - - return result; - } -} \ No newline at end of file diff --git a/Ghost.Editor/Models/Scene.cs b/Ghost.Editor/Models/Scene.cs deleted file mode 100644 index 3e38716..0000000 --- a/Ghost.Editor/Models/Scene.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Ghost.Entities; -using System.Collections.Generic; - -namespace Ghost.Editor.Models; - -public class Scene -{ - private readonly HashSet _rootObjects = new(); - private readonly World _world = World.Create(); - - public IEnumerable RootObjects => _rootObjects; - public World World => _world; - - internal Scene() - { - } - - internal void Load() - { - foreach (var gameObject in _rootObjects) - { - gameObject.OnEnable(); - } - } - - internal void Unload() - { - foreach (var gameObject in _rootObjects) - { - gameObject.OnDisable(); - gameObject.Destroy(); - } - - _rootObjects.Clear(); - } -} \ No newline at end of file diff --git a/Ghost.Editor/Themes/Dark.xaml b/Ghost.Editor/Themes/Dark.xaml deleted file mode 100644 index 8f22c98..0000000 --- a/Ghost.Editor/Themes/Dark.xaml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/Ghost.Editor/Themes/Light.xaml b/Ghost.Editor/Themes/Light.xaml deleted file mode 100644 index 8527c05..0000000 --- a/Ghost.Editor/Themes/Light.xaml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/Ghost.Editor/Themes/Override.xaml b/Ghost.Editor/Themes/Override.xaml new file mode 100644 index 0000000..57152cc --- /dev/null +++ b/Ghost.Editor/Themes/Override.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Ghost.Editor/View/Pages/EngineEditor/ConsolePage.xaml b/Ghost.Editor/View/Pages/EngineEditor/ConsolePage.xaml index 06dde5f..4def1d2 100644 --- a/Ghost.Editor/View/Pages/EngineEditor/ConsolePage.xaml +++ b/Ghost.Editor/View/Pages/EngineEditor/ConsolePage.xaml @@ -21,19 +21,19 @@ BorderThickness="0,0,0,1"> - + - + - + - + @@ -42,7 +42,10 @@ - + @@ -54,7 +57,11 @@ - + diff --git a/Ghost.Editor/View/Pages/EngineEditor/ConsolePage.xaml.cs b/Ghost.Editor/View/Pages/EngineEditor/ConsolePage.xaml.cs index c690558..52bb554 100644 --- a/Ghost.Editor/View/Pages/EngineEditor/ConsolePage.xaml.cs +++ b/Ghost.Editor/View/Pages/EngineEditor/ConsolePage.xaml.cs @@ -1,11 +1,19 @@ +using Ghost.Editor.ViewModels.Pages.EngineEditor; using Microsoft.UI.Xaml.Controls; namespace Ghost.Editor.View.Pages.EngineEditor; -public sealed partial class ConsolePage : Page +internal sealed partial class ConsolePage : Page { + public ConsoleViewModel ViewModel + { + get; + } + public ConsolePage() { + ViewModel = App.GetService(); + InitializeComponent(); } } diff --git a/Ghost.Editor/View/Pages/EngineEditor/HierarchyPage.xaml b/Ghost.Editor/View/Pages/EngineEditor/HierarchyPage.xaml new file mode 100644 index 0000000..5fc3128 --- /dev/null +++ b/Ghost.Editor/View/Pages/EngineEditor/HierarchyPage.xaml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ghost.Editor/View/Pages/EngineEditor/HierarchyPage.xaml.cs b/Ghost.Editor/View/Pages/EngineEditor/HierarchyPage.xaml.cs new file mode 100644 index 0000000..811bf44 --- /dev/null +++ b/Ghost.Editor/View/Pages/EngineEditor/HierarchyPage.xaml.cs @@ -0,0 +1,57 @@ +using Ghost.Editor.Infrastructures.SceneGraph; +using Ghost.Editor.ViewModels.Pages.EngineEditor; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace Ghost.Editor.View.Pages.EngineEditor; +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +internal sealed partial class HierarchyPage : Page +{ + public HierarchyViewModel ViewModel + { + get; + } + + public HierarchyPage() + { + ViewModel = App.GetService(); + + InitializeComponent(); + } +} + +internal partial class HierarchyTemplateSector : DataTemplateSelector +{ + public DataTemplate? SceneTemplate + { + get; + set; + } + + public DataTemplate? EntityTemplate + { + get; + set; + } + + protected override DataTemplate SelectTemplateCore(object item) + { + if (SceneTemplate == null || EntityTemplate == null) + { + return base.SelectTemplateCore(item); + } + + var node = (SceneGraphNode)item; + return node.Type switch + { + SceneGraphNode.NodeType.Scene => SceneTemplate, + SceneGraphNode.NodeType.Entity => EntityTemplate, + _ => base.SelectTemplateCore(item) + }; + } +} \ No newline at end of file diff --git a/Ghost.Editor/View/Pages/EngineEditor/ProjectPage.xaml b/Ghost.Editor/View/Pages/EngineEditor/ProjectPage.xaml index daa13cf..bd3adfe 100644 --- a/Ghost.Editor/View/Pages/EngineEditor/ProjectPage.xaml +++ b/Ghost.Editor/View/Pages/EngineEditor/ProjectPage.xaml @@ -3,11 +3,17 @@ x:Class="Ghost.Editor.View.Pages.EngineEditor.ProjectPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:converter="using:Ghost.Editor.Helpers.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:Ghost.Editor.View.Pages.EngineEditor" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:model="using:Ghost.Editor.Models" mc:Ignorable="d"> + + + + @@ -17,55 +23,116 @@ - - - + ItemsSource="{x:Bind ViewModel.SubDirectories}" + ScrollViewer.HorizontalScrollBarVisibility="Hidden" + ScrollViewer.VerticalScrollBarVisibility="Auto" + SelectedItem="{x:Bind ViewModel.SelectedDirectory, Mode=TwoWay}"> + + + + + + + + + + - - - - - + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ghost.Editor/View/Pages/EngineEditor/ProjectPage.xaml.cs b/Ghost.Editor/View/Pages/EngineEditor/ProjectPage.xaml.cs index cd80545..fc8a3c7 100644 --- a/Ghost.Editor/View/Pages/EngineEditor/ProjectPage.xaml.cs +++ b/Ghost.Editor/View/Pages/EngineEditor/ProjectPage.xaml.cs @@ -1,30 +1,25 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; -using Windows.Foundation; -using Windows.Foundation.Collections; -using Microsoft.UI.Xaml; +using Ghost.Editor.ViewModels.Pages.EngineEditor; using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Controls.Primitives; -using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Input; -using Microsoft.UI.Xaml.Media; -using Microsoft.UI.Xaml.Navigation; - -// To learn more about WinUI, the WinUI project structure, -// and more about our project templates, see: http://aka.ms/winui-project-info. namespace Ghost.Editor.View.Pages.EngineEditor; -/// -/// An empty page that can be used on its own or navigated to within a Frame. -/// -public sealed partial class ProjectPage : Page +internal sealed partial class ProjectPage : Page { + public ProjectViewModel ViewModel + { + get; + } + public ProjectPage() { + ViewModel = App.GetService(); + InitializeComponent(); } + + private void GridViewItem_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e) + { + ViewModel.NavigateToSelected(); + } } diff --git a/Ghost.Editor/View/Pages/Landing/CreateProjectPage.xaml.cs b/Ghost.Editor/View/Pages/Landing/CreateProjectPage.xaml.cs index 4c6e359..1acca94 100644 --- a/Ghost.Editor/View/Pages/Landing/CreateProjectPage.xaml.cs +++ b/Ghost.Editor/View/Pages/Landing/CreateProjectPage.xaml.cs @@ -1,4 +1,4 @@ -using Ghost.Editor.ViewModel.Pages.Landing; +using Ghost.Editor.ViewModels.Pages.Landing; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Navigation; diff --git a/Ghost.Editor/View/Pages/Landing/OpenProjectPage.xaml b/Ghost.Editor/View/Pages/Landing/OpenProjectPage.xaml index 68f5f61..b77d897 100644 --- a/Ghost.Editor/View/Pages/Landing/OpenProjectPage.xaml +++ b/Ghost.Editor/View/Pages/Landing/OpenProjectPage.xaml @@ -6,6 +6,7 @@ xmlns:converters="using:Ghost.Editor.Helpers.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:data="using:Ghost.Data.Models" + xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:local="using:Ghost.Editor.View.Pages.Landing" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" NavigationCacheMode="Enabled" @@ -73,7 +74,7 @@ CornerRadius="{StaticResource OverlayCornerRadius}" IsItemClickEnabled="True" ItemClick="ListView_ItemClick" - ItemsSource="{x:Bind projects}" + ItemsSource="{x:Bind ViewModel.projects}" SelectionMode="None"> diff --git a/Ghost.Editor/View/Pages/Landing/OpenProjectPage.xaml.cs b/Ghost.Editor/View/Pages/Landing/OpenProjectPage.xaml.cs index 89f0b62..accc060 100644 --- a/Ghost.Editor/View/Pages/Landing/OpenProjectPage.xaml.cs +++ b/Ghost.Editor/View/Pages/Landing/OpenProjectPage.xaml.cs @@ -1,68 +1,47 @@ using Ghost.Data.Models; -using Ghost.Data.Services; -using Ghost.Editor.AppStates; -using Ghost.Editor.Services; +using Ghost.Editor.ViewModels.Pages.Landing; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Navigation; -using System; -using System.Collections.ObjectModel; -using System.Linq; using Windows.ApplicationModel.DataTransfer; -using Windows.Storage; namespace Ghost.Editor.View.Pages.Landing; internal sealed partial class OpenProjectPage : Page { - private readonly ProjectService _projectService; - private readonly StackedNotificationService _notificationService; - private readonly AppStateService _stateService; - - public readonly ObservableCollection projects = new(); + public OpenProjectViewModel ViewModel + { + get; + } public OpenProjectPage() { - _notificationService = App.GetService(); - _projectService = App.GetService(); - _stateService = App.GetService(); + ViewModel = App.GetService(); InitializeComponent(); } - private void UpdateEmptyPlaceHolderVisibility() - { - EmptyPlaceHolder.Visibility = projects.Count == 0 ? Visibility.Visible : Visibility.Collapsed; - } - - protected override async void OnNavigatedTo(NavigationEventArgs e) + protected override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); + ViewModel.OnNavigatedTo(e.Parameter); + } - projects.Clear(); - await foreach (var projectInfo in _projectService.LoadAllProjectAsync()) - { - var metadata = await ProjectService.LoadMetadataAsync(projectInfo.MetadataPath); - if (metadata == null) - { - continue; - } - - projects.Add(new(projectInfo.MetadataPath, metadata)); - } - - UpdateEmptyPlaceHolderVisibility(); + override protected void OnNavigatedFrom(NavigationEventArgs e) + { + base.OnNavigatedFrom(e); + ViewModel.OnNavigatedFrom(); } private void ProjectContainer_DragEnter(object sender, DragEventArgs e) { - DragVisual.Visibility = Visibility.Visible; - EmptyPlaceHolder.Visibility = Visibility.Collapsed; + ViewModel.DragVisibility = Visibility.Visible; + ViewModel.EmptyVisibility = Visibility.Collapsed; } private void ProjectContainer_DragLeave(object sender, DragEventArgs e) { - DragVisual.Visibility = Visibility.Collapsed; - UpdateEmptyPlaceHolderVisibility(); + ViewModel.DragVisibility = Visibility.Collapsed; + ViewModel.UpdateEmptyPlaceHolderVisibility(); } private void ProjectContainer_DragOver(object sender, DragEventArgs e) @@ -79,55 +58,14 @@ internal sealed partial class OpenProjectPage : Page private async void ProjectContainer_Drop(object sender, DragEventArgs e) { - var errorMessage = string.Empty; - if (e.DataView.Contains(StandardDataFormats.StorageItems)) - { - var items = await e.DataView.GetStorageItemsAsync(); - var rootFolder = items.OfType().FirstOrDefault(); - if (rootFolder != null) - { - var result = await _projectService.AddProjectFromDirectoryAsync(rootFolder.Path); - if (result.success) - { - projects.Add(result.data); - DragVisual.Visibility = Visibility.Collapsed; - goto CloseDropPanel; - } - else - { - errorMessage = result.message; - } - } - } - else - { - errorMessage = "Unsupported data format. Please drop a folder containing a project."; - } - - _notificationService.ShowNotification(errorMessage, InfoBarSeverity.Error); - - CloseDropPanel: - DragVisual.Visibility = Visibility.Collapsed; - UpdateEmptyPlaceHolderVisibility(); + await ViewModel.ContentDrop(e.DataView); } private async void ListView_ItemClick(object sender, ItemClickEventArgs e) { - if (e.ClickedItem is not ProjectMetadataInfo project) + if (e.ClickedItem is ProjectMetadataInfo project) { - return; - } - - try - { - project.Metadata.LastOpened = DateTime.Now; - await ProjectService.CreateMetadataFileAsync(project.Path, project.Metadata); - - await _stateService.TransitionToAsync(StateKey.EngineEditor, project.Metadata); - } - catch (Exception exp) - { - _notificationService.ShowNotification($"Failed to load project: {exp.Message}", InfoBarSeverity.Error); + await ViewModel.LoadProject(project); } } } \ No newline at end of file diff --git a/Ghost.Editor/View/Windows/EngineEditorWindow.xaml b/Ghost.Editor/View/Windows/EngineEditorWindow.xaml index 0928334..b7414c0 100644 --- a/Ghost.Editor/View/Windows/EngineEditorWindow.xaml +++ b/Ghost.Editor/View/Windows/EngineEditorWindow.xaml @@ -43,7 +43,7 @@ Margin="8,0,0,0" VerticalAlignment="Center" Style="{StaticResource CaptionTextBlockStyle}" - Text="{x:Bind ViewModel.CurrentProject.Name, Mode=OneWay}" /> + Text="{x:Bind ViewModel.CurrentProject.Metadata.Name, Mode=OneWay}" /> @@ -83,13 +83,26 @@ - + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch" + TabWidthMode="Compact"> + + + + + + + + + + + - + diff --git a/Ghost.Editor/View/Windows/EngineEditorWindow.xaml.cs b/Ghost.Editor/View/Windows/EngineEditorWindow.xaml.cs index e03e83f..be2157c 100644 --- a/Ghost.Editor/View/Windows/EngineEditorWindow.xaml.cs +++ b/Ghost.Editor/View/Windows/EngineEditorWindow.xaml.cs @@ -1,5 +1,5 @@ using Ghost.Data.Resources; -using Ghost.Editor.ViewModel.Windows; +using Ghost.Editor.ViewModels.Windows; using Ghost.Engine.Resources; using WinUIEx; diff --git a/Ghost.Editor/ViewModel/Pages/EngineEditor/ProjectViewModel.cs b/Ghost.Editor/ViewModel/Pages/EngineEditor/ProjectViewModel.cs deleted file mode 100644 index 33d74c3..0000000 --- a/Ghost.Editor/ViewModel/Pages/EngineEditor/ProjectViewModel.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Ghost.Editor.ViewModel.Pages.EngineEditor; - -internal class ProjectViewModel -{ -} diff --git a/Ghost.Editor/ViewModels/Pages/EngineEditor/ConsoleViewModel.cs b/Ghost.Editor/ViewModels/Pages/EngineEditor/ConsoleViewModel.cs new file mode 100644 index 0000000..2bb3942 --- /dev/null +++ b/Ghost.Editor/ViewModels/Pages/EngineEditor/ConsoleViewModel.cs @@ -0,0 +1,92 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Ghost.Engine.Models; +using Ghost.Engine.Services; +using System.Collections.ObjectModel; + +namespace Ghost.Editor.ViewModels.Pages.EngineEditor; + +internal partial class ConsoleViewModel : ObservableObject +{ + [ObservableProperty] + public partial ObservableCollection Logs + { + get; set; + } = new(); + + [ObservableProperty] + public partial bool ShowInfo + { + get; set; + } = true; + + [ObservableProperty] + public partial bool ShowWarning + { + get; set; + } = true; + + [ObservableProperty] + public partial bool ShowError + { + get; set; + } = true; + + [ObservableProperty] + public partial bool ShowStackTrace + { + get; set; + } = false; + + [ObservableProperty] + public partial LogMessage? SelectedLog + { + get; set; + } + + public ConsoleViewModel() + { + foreach (var log in Logger.Logs) + { + Logs.Add(log); + } + + Logger.OnLogsUpdate += UpdateLogs; + } + + ~ConsoleViewModel() + { + Logger.OnLogsUpdate -= UpdateLogs; + } + + private void UpdateLogs(LogChangeType updateType) + { + switch (updateType) + { + case LogChangeType.LogAdded: + Logs.Add(Logger.Logs[^1]); + break; + case LogChangeType.LogRemoved: + if (Logs.Count > 0) + { + Logs.RemoveAt(0); + } + break; + case LogChangeType.LogsCleared: + Logs.Clear(); + break; + } + } + + partial void OnShowStackTraceChanged(bool value) + { + Logger.HasStackTrace = value; + Logger.LogInfo($"Stack trace visibility set to {value}."); + } + + [RelayCommand] + private void ClearLogs() + { + Logger.Clear(); + } +} diff --git a/Ghost.Editor/ViewModels/Pages/EngineEditor/HierarchyViewModel.cs b/Ghost.Editor/ViewModels/Pages/EngineEditor/HierarchyViewModel.cs new file mode 100644 index 0000000..6ad1e64 --- /dev/null +++ b/Ghost.Editor/ViewModels/Pages/EngineEditor/HierarchyViewModel.cs @@ -0,0 +1,38 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Ghost.Editor.Infrastructures.SceneGraph; +using Ghost.Entities; +using System.Collections.ObjectModel; + +namespace Ghost.Editor.ViewModels.Pages.EngineEditor; + +internal partial class HierarchyViewModel : ObservableObject +{ + [ObservableProperty] + public partial ObservableCollection SceneList + { + get; + private set; + } = new(); + + public HierarchyViewModel() + { + // Test only + var testWorld = World.Create(); + var entity1 = SceneGraphHelpers.CreateEntityNode(testWorld, "entity 1"); + var entity2 = SceneGraphHelpers.CreateEntityNode(testWorld, "entity 3"); + var entity3 = SceneGraphHelpers.CreateEntityNode(testWorld, "entity 4"); + var entity4 = SceneGraphHelpers.CreateEntityNode(testWorld, "entity 5"); + var entity5 = SceneGraphHelpers.CreateEntityNode(testWorld, "entity 2"); + + var testScene = new SceneNode(testWorld, "Test Scene"); + + SceneGraphHelpers.AttachChild(testScene, entity1, entity2); + SceneGraphHelpers.AttachChild(testScene, entity1, entity3); + SceneGraphHelpers.AttachChild(testScene, entity2, entity4); + + testScene.Children.Add(entity1); + testScene.Children.Add(entity5); + + SceneList.Add(testScene); + } +} \ No newline at end of file diff --git a/Ghost.Editor/ViewModels/Pages/EngineEditor/ProjectViewModel.cs b/Ghost.Editor/ViewModels/Pages/EngineEditor/ProjectViewModel.cs new file mode 100644 index 0000000..1b1f001 --- /dev/null +++ b/Ghost.Editor/ViewModels/Pages/EngineEditor/ProjectViewModel.cs @@ -0,0 +1,142 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Ghost.Data.Services; +using Ghost.Editor.Models; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Threading.Tasks; + +namespace Ghost.Editor.ViewModels.Pages.EngineEditor; + +internal partial class ProjectViewModel : ObservableObject +{ + public ObservableCollection SubDirectories + { + get; + } = new(); + + [ObservableProperty] + public partial ObservableCollection DirectoryAssets + { + get; + set; + } = new(); + + [ObservableProperty] + public partial ExplorerItem? SelectedDirectory + { + get; + set; + } + + [ObservableProperty] + public partial ExplorerItem? SelectedAsset + { + get; + set; + } + + public ProjectViewModel() + { + if (ProjectService.CurrentProject.Metadata == null) + { + throw new InvalidOperationException("Current project is not set."); + } + + var assetsItem = new ExplorerItem("Assets", Path.Combine(Path.GetDirectoryName(ProjectService.CurrentProject.Path)!, ProjectService.ASSETS_FOLDER), true); + LoadSubFolderRecursive(ref assetsItem); + + SubDirectories.Add(assetsItem); + } + + private void LoadSubFolderRecursive(ref ExplorerItem parentItem) + { + foreach (var directory in Directory.EnumerateDirectories(parentItem.Path)) + { + var item = new ExplorerItem(Path.GetFileName(directory), directory, true); + LoadSubFolderRecursive(ref item); + + parentItem.Children ??= new(); + parentItem.Children.Add(item); + } + } + + public static Task FindNodeIterative(ExplorerItem root, Func predicate) + { + var stack = new Stack(); + stack.Push(root); + + return Task.Run(() => + { + while (stack.Count > 0) + { + var node = stack.Pop(); + if (predicate(node)) + { + return node; + } + + if (node.Children == null || node.Children.Count == 0) + { + continue; + } + + for (var i = node.Children.Count - 1; i >= 0; i--) + { + stack.Push(node.Children[i]); + } + } + + return null; + }); + } + + private void NavigateToDirectory(string? path) + { + App.Window?.DispatcherQueue.TryEnqueue(async () => + { + DirectoryAssets.Clear(); + + if (!Directory.Exists(path)) + { + return; + } + + foreach (var directory in Directory.EnumerateDirectories(path)) + { + var directoryItem = new ExplorerItem(Path.GetFileName(directory), directory, true); + DirectoryAssets.Add(directoryItem); + } + + foreach (var file in Directory.EnumerateFiles(path)) + { + var fileItem = new ExplorerItem(Path.GetFileName(file), file, false); + DirectoryAssets.Add(fileItem); + } + + SelectedDirectory = await FindNodeIterative(SubDirectories[0], x => x.Path == path); + }); + } + + public void NavigateToSelected() + { + if (SelectedAsset == null || !SelectedAsset.IsDirectory) + { + return; + } + + NavigateToDirectory(SelectedAsset.Path); + } + + partial void OnSelectedDirectoryChanged(ExplorerItem? value) + { + DirectoryAssets.Clear(); + if (value == null) + { + return; + } + + NavigateToDirectory(value.Path); + } +} \ No newline at end of file diff --git a/Ghost.Editor/ViewModel/Pages/Landing/CreateProjectViewModel.cs b/Ghost.Editor/ViewModels/Pages/Landing/CreateProjectViewModel.cs similarity index 86% rename from Ghost.Editor/ViewModel/Pages/Landing/CreateProjectViewModel.cs rename to Ghost.Editor/ViewModels/Pages/Landing/CreateProjectViewModel.cs index 9a0cce6..119af9c 100644 --- a/Ghost.Editor/ViewModel/Pages/Landing/CreateProjectViewModel.cs +++ b/Ghost.Editor/ViewModels/Pages/Landing/CreateProjectViewModel.cs @@ -2,9 +2,9 @@ using CommunityToolkit.Mvvm.Input; using Ghost.Data.Models; using Ghost.Data.Services; -using Ghost.Editor.AppStates; using Ghost.Editor.Contracts; using Ghost.Editor.Helpers; +using Ghost.Editor.Infrastructures.AppState; using Ghost.Editor.Services; using Ghost.Engine.Resources; using Microsoft.UI.Xaml.Controls; @@ -13,9 +13,9 @@ using System.IO; using System.Linq; using System.Threading.Tasks; -namespace Ghost.Editor.ViewModel.Pages.Landing; +namespace Ghost.Editor.ViewModels.Pages.Landing; -internal partial class CreateProjectViewModel(StackedNotificationService notificationService, ProjectService projectService, AppStateService stateService) : ObservableRecipient, INavigationAware +internal partial class CreateProjectViewModel(StackedNotificationService notificationService, ProjectService projectService, AppStateMachine stateService) : ObservableObject, INavigationAware { public ObservableCollection templates = new(); @@ -77,16 +77,15 @@ internal partial class CreateProjectViewModel(StackedNotificationService notific } var result = await projectService.CreateProjectAsync(ProjectName, ProjectLocation, EngineData.s_engineVersion, SelectedTemplate.Value.directory); - if (!result.success || result.data == null) + if (!result.success) { notificationService.ShowNotification(result.message, InfoBarSeverity.Error); return; } - var metadata = await ProjectService.LoadMetadataAsync(result.data.MetadataPath); // Metadata should not be null here if create project succeeded try { - await stateService.TransitionToAsync(StateKey.EngineEditor, metadata); + await stateService.TransitionToAsync(StateKey.EngineEditor, result.data); } catch (System.Exception e) { diff --git a/Ghost.Editor/ViewModels/Pages/Landing/OpenProjectViewModel.cs b/Ghost.Editor/ViewModels/Pages/Landing/OpenProjectViewModel.cs new file mode 100644 index 0000000..c56d980 --- /dev/null +++ b/Ghost.Editor/ViewModels/Pages/Landing/OpenProjectViewModel.cs @@ -0,0 +1,109 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Ghost.Data.Models; +using Ghost.Data.Services; +using Ghost.Editor.Contracts; +using Ghost.Editor.Infrastructures.AppState; +using Ghost.Editor.Services; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using Windows.ApplicationModel.DataTransfer; +using Windows.Storage; + +namespace Ghost.Editor.ViewModels.Pages.Landing; + +internal partial class OpenProjectViewModel(ProjectService projectService, StackedNotificationService _notificationService, AppStateMachine _stateService) : ObservableObject, INavigationAware +{ + public readonly ObservableCollection projects = new(); + + [ObservableProperty] + public partial Visibility EmptyVisibility + { + get; + set; + } + + [ObservableProperty] + public partial Visibility DragVisibility + { + get; + set; + } + + public void UpdateEmptyPlaceHolderVisibility() + { + EmptyVisibility = projects.Count == 0 ? Visibility.Visible : Visibility.Collapsed; + } + + public async void OnNavigatedTo(object? parameter) + { + projects.Clear(); + await foreach (var projectInfo in projectService.LoadAllProjectAsync()) + { + var metadata = await ProjectService.LoadMetadataAsync(projectInfo.MetadataPath); + if (metadata == null) + { + continue; + } + + projects.Add(new(projectInfo.MetadataPath, metadata)); + } + + UpdateEmptyPlaceHolderVisibility(); + } + + public void OnNavigatedFrom() + { + } + + public async Task ContentDrop(DataPackageView dataView) + { + var errorMessage = string.Empty; + if (dataView.Contains(StandardDataFormats.StorageItems)) + { + var items = await dataView.GetStorageItemsAsync(); + var rootFolder = items.OfType().FirstOrDefault(); + if (rootFolder != null) + { + var result = await projectService.AddProjectFromDirectoryAsync(rootFolder.Path); + if (result.success) + { + projects.Add(result.data); + goto CloseDropPanel; + } + else + { + errorMessage = result.message; + } + } + } + else + { + errorMessage = "Unsupported data format. Please drop a folder containing a project."; + } + + _notificationService.ShowNotification(errorMessage, InfoBarSeverity.Error); + + CloseDropPanel: + DragVisibility = Visibility.Collapsed; + UpdateEmptyPlaceHolderVisibility(); + } + + public async Task LoadProject(ProjectMetadataInfo project) + { + try + { + project.Metadata.LastOpened = DateTime.Now; + await ProjectService.CreateMetadataFileAsync(project.Path, project.Metadata); + + await _stateService.TransitionToAsync(StateKey.EngineEditor, project); + } + catch (Exception e) + { + _notificationService.ShowNotification($"Failed to load project: {e.Message}", InfoBarSeverity.Error); + } + } +} \ No newline at end of file diff --git a/Ghost.Editor/ViewModel/Windows/EngineEditorViewModel.cs b/Ghost.Editor/ViewModels/Windows/EngineEditorViewModel.cs similarity index 64% rename from Ghost.Editor/ViewModel/Windows/EngineEditorViewModel.cs rename to Ghost.Editor/ViewModels/Windows/EngineEditorViewModel.cs index 91b50eb..4d85172 100644 --- a/Ghost.Editor/ViewModel/Windows/EngineEditorViewModel.cs +++ b/Ghost.Editor/ViewModels/Windows/EngineEditorViewModel.cs @@ -1,17 +1,13 @@ using CommunityToolkit.Mvvm.ComponentModel; using Ghost.Data.Models; +using Ghost.Data.Services; using Ghost.Engine.Resources; -namespace Ghost.Editor.ViewModel.Windows; +namespace Ghost.Editor.ViewModels.Windows; internal partial class EngineEditorViewModel : ObservableRecipient { public string engineVersionDescriptor = $"{EngineData.ENGINE_NAME} - {EngineData.s_engineVersion}"; - [ObservableProperty] - public partial ProjectMetadata CurrentProject - { - get; - set; - } + public ProjectMetadataInfo CurrentProject => ProjectService.CurrentProject; } \ No newline at end of file diff --git a/Ghost.Engine/Components/Hierarchy.cs b/Ghost.Engine/Components/Hierarchy.cs new file mode 100644 index 0000000..5254664 --- /dev/null +++ b/Ghost.Engine/Components/Hierarchy.cs @@ -0,0 +1,24 @@ +using Ghost.Entities; +using Ghost.Entities.Components; +using System.Runtime.CompilerServices; + +namespace Ghost.Engine.Components; + +[SkipLocalsInit] +public struct Hierarchy : IComponentData +{ + public Entity parent; + public Entity firstChild; + public Entity nextSibling; + + public static Hierarchy Root + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new() + { + parent = Entity.Invalid, + firstChild = Entity.Invalid, + nextSibling = Entity.Invalid + }; + } +} \ No newline at end of file diff --git a/Ghost.Engine/Components/LocalToWorld.cs b/Ghost.Engine/Components/LocalToWorld.cs new file mode 100644 index 0000000..9c980aa --- /dev/null +++ b/Ghost.Engine/Components/LocalToWorld.cs @@ -0,0 +1,21 @@ +using Ghost.Engine.Helpers; +using Ghost.Entities.Components; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ghost.Engine.Components; + +[SkipLocalsInit] +public struct LocalToWorld : IComponentData +{ + public Matrix4x4 matrix; + + public static LocalToWorld Identity + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new() + { + matrix = MatrixHelpers.CreateTRS(Vector3.Zero, Quaternion.Identity, Vector3.One) + }; + } +} \ No newline at end of file diff --git a/Ghost.Engine/Components/Transform.cs b/Ghost.Engine/Components/Transform.cs deleted file mode 100644 index 647950a..0000000 --- a/Ghost.Engine/Components/Transform.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Ghost.Engine.Helpers; -using Ghost.Entities; -using System.Numerics; -using System.Runtime.CompilerServices; - -namespace Ghost.Engine.Components; - -public struct Transform : IComponentData -{ - private Vector3 _position = Vector3.Zero; - public Vector3 Position - { - get => _position; - set - { - _position = value; - hasChanged = true; - UpdateMatrices(); - } - } - - private Quaternion _rotation = Quaternion.Identity; - public Quaternion Rotation - { - get => _rotation; - set - { - _rotation = value; - hasChanged = true; - UpdateMatrices(); - } - } - - private Vector3 _scale = Vector3.One; - public Vector3 Scale - { - get => _scale; - set - { - _scale = value; - hasChanged = true; - UpdateMatrices(); - } - } - - public bool hasChanged; - - private Matrix4x4 _localToWorldMatrix; - private Matrix4x4 _worldToLocalMatrix; - - public readonly Matrix4x4 LocalToWorldMatrix => _localToWorldMatrix; - public readonly Matrix4x4 WorldToLocalMatrix => _worldToLocalMatrix; - - public static Transform Default - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(Vector3.Zero, Quaternion.Identity, Vector3.One); - } - - public Transform(Vector3 position, Quaternion rotation, Vector3 scale) - { - _position = position; - _rotation = rotation; - _scale = scale; - hasChanged = false; - _localToWorldMatrix = Matrix4x4.Identity; - _worldToLocalMatrix = Matrix4x4.Identity; - - UpdateMatrices(); - } - - private void UpdateMatrices() - { - _localToWorldMatrix = MatrixHelpers.CreateTRS(_position, _rotation, _scale); - Matrix4x4.Invert(_localToWorldMatrix, out _worldToLocalMatrix); - } -} \ No newline at end of file diff --git a/Ghost.Engine/EngineCore.cs b/Ghost.Engine/EngineCore.cs index 3970f28..f8f74fe 100644 --- a/Ghost.Engine/EngineCore.cs +++ b/Ghost.Engine/EngineCore.cs @@ -1,25 +1,16 @@ using Ghost.Engine.Models; +using Ghost.Engine.Services; namespace Ghost.Engine; -internal class EngineCore +internal class EngineCore : IDisposable, IAsyncDisposable { - public static EngineCore? Current + public async Task StartAsync(LaunchArgument args) { - get; - private set; - } - - public static async Task StartAsync(LaunchArgument args) - { - if (Current != null) - { - return; - } - - Current = new EngineCore(); - ActivationHandler.Handle(args); + + Logger.LogInfo("Engine started successfully."); + await Task.CompletedTask; } @@ -27,4 +18,14 @@ internal class EngineCore { await Task.CompletedTask; } + + public void Dispose() + { + ShutDownAsync().GetAwaiter().GetResult(); + } + + public async ValueTask DisposeAsync() + { + await ShutDownAsync(); + } } \ No newline at end of file diff --git a/Ghost.Engine/Models/LogMessage.cs b/Ghost.Engine/Models/LogMessage.cs new file mode 100644 index 0000000..0892764 --- /dev/null +++ b/Ghost.Engine/Models/LogMessage.cs @@ -0,0 +1,56 @@ +using System.Diagnostics; + +namespace Ghost.Engine.Models; + +public enum LogLevel +{ + Info, + Warning, + Error +} + +internal class LogMessage +{ + public LogLevel Level + { + get; set; + } + + public string Message + { + get; set; + } + + public StackTrace? StackTrace + { + get; set; + } + + public DateTime Timestamp + { + get; set; + } + + public LogMessage(LogLevel level, string message, StackTrace? stackTrace = null) + { + Level = level; + Message = message; + StackTrace = stackTrace; + Timestamp = DateTime.Now; + } + + public override string ToString() + { + return $"{Timestamp:HH:mm:ss} [{Level}] {Message}"; + } + + public string ToStringWithStackTrace() + { + if (StackTrace == null) + { + return ToString(); + } + + return $"{ToString()}\n{StackTrace}"; + } +} \ No newline at end of file diff --git a/Ghost.Engine/Services/Logger.cs b/Ghost.Engine/Services/Logger.cs new file mode 100644 index 0000000..58fe559 --- /dev/null +++ b/Ghost.Engine/Services/Logger.cs @@ -0,0 +1,73 @@ +using Ghost.Engine.Models; +using System.Diagnostics; + +namespace Ghost.Engine.Services; + +internal enum LogChangeType +{ + LogAdded, + LogRemoved, + LogsCleared +} + +public static class Logger +{ + + private const int _MAX_LOGS = 4096; + + private static readonly List _logs = new(); + internal static List Logs => _logs; + + internal static event Action? OnLogsUpdate; + + internal static bool HasStackTrace + { + get; set; + } + + private static void LogInternal(LogLevel level, string message, int skipFrame) + { + if (_logs.Count >= _MAX_LOGS) + { + _logs.RemoveAt(0); + OnLogsUpdate?.Invoke(LogChangeType.LogRemoved); + } + + StackTrace? stackTrace = null; + if (HasStackTrace) + { + stackTrace = new StackTrace(skipFrame, true); + } + + var logMessage = new LogMessage(level, message, stackTrace); + _logs.Add(logMessage); + + OnLogsUpdate?.Invoke(LogChangeType.LogAdded); + } + + public static void Log(LogLevel level, string message) + { + LogInternal(level, message, 2); + } + + public static void LogInfo(string message) + { + LogInternal(LogLevel.Info, message, 3); + } + + public static void LogWarning(string message) + { + LogInternal(LogLevel.Warning, message, 3); + } + + public static void LogError(string message) + { + LogInternal(LogLevel.Error, message, 3); + } + + internal static void Clear() + { + _logs.Clear(); + OnLogsUpdate?.Invoke(LogChangeType.LogsCleared); + } +} \ No newline at end of file diff --git a/Ghost.Entities/AssemblyInfo.cs b/Ghost.Entities/AssemblyInfo.cs index f4854ec..8926405 100644 --- a/Ghost.Entities/AssemblyInfo.cs +++ b/Ghost.Entities/AssemblyInfo.cs @@ -1,5 +1,5 @@ global using EntityID = System.Int32; -global using GenerationID = System.UInt16; +global using GenerationID = System.Byte; global using WorldID = System.UInt16; using System.Runtime.CompilerServices; diff --git a/Ghost.Entities/Component.cs b/Ghost.Entities/Components/ComponentStorage.cs similarity index 95% rename from Ghost.Entities/Component.cs rename to Ghost.Entities/Components/ComponentStorage.cs index d0a2c74..acfec2f 100644 --- a/Ghost.Entities/Component.cs +++ b/Ghost.Entities/Components/ComponentStorage.cs @@ -3,12 +3,7 @@ using Misaki.HighPerformance.Unsafe.Collections; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -namespace Ghost.Entities; - -public interface IComponentData -{ - -} +namespace Ghost.Entities.Components; internal static class SingletonContainer where T : struct, IComponentData @@ -50,7 +45,7 @@ internal class ComponentPool : IComponentPool public EntityID Count => _nextId; - public ComponentPool(int initialSize = 16) + public ComponentPool(int initialSize) { _nextId = 0; _capacity = initialSize; @@ -144,7 +139,7 @@ internal class ComponentPool : IComponentPool return index != Entity.INVALID_ID && _components[index].owner.Generation == entity.Generation; } - public void Set(Entity entity, T component) + public void Set(Entity entity, in T component) { if (!entity.IsValid || entity.ID >= _lookup.Length || GetComponentIndex(entity) == Entity.INVALID_ID) { @@ -334,7 +329,8 @@ internal class ScriptComponentPool : IComponentPool } } -internal class ComponentStorage : IDisposable +[SkipLocalsInit] +internal readonly struct ComponentStorage : IDisposable { private readonly Dictionary _componentPools = new(); private readonly Dictionary _componentEntityMasks = new(); @@ -360,25 +356,25 @@ internal class ComponentStorage : IDisposable [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetPool(Type type, [MaybeNullWhen(false)] out IComponentPool pool) { - return TryGetPool(type.TypeHandle.Value, out pool); + return TryGetPool(TypeHandle.Get(type), out pool); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetPool([MaybeNullWhen(false)] out ComponentPool pool) where T : struct, IComponentData { - var result = TryGetPool(TypeHandle.Value, out var obj); - pool = (ComponentPool?)obj; + var result = TryGetPool(TypeHandle.Get(), out var obj); + pool = (ComponentPool?)obj ?? default; return result; } public ComponentPool GetOrCreateComponentPool() where T : struct, IComponentData { - var key = TypeHandle.Value; + var key = TypeHandle.Get(); if (!_componentPools.TryGetValue(key, out var obj)) { - var pool = new ComponentPool(); + var pool = new ComponentPool(16); _componentPools[key] = pool; return pool; } @@ -402,7 +398,7 @@ internal class ComponentStorage : IDisposable public bool TryGetMask([MaybeNullWhen(false)] out BitSet bitSet) where T : struct, IComponentData { - return TryGetMask(TypeHandle.Value, out bitSet); + return TryGetMask(TypeHandle.Get(), out bitSet); } public BitSet GetOrCreateMask(nint typeHandle) diff --git a/Ghost.Entities/Components/IComponentData.cs b/Ghost.Entities/Components/IComponentData.cs new file mode 100644 index 0000000..779363b --- /dev/null +++ b/Ghost.Entities/Components/IComponentData.cs @@ -0,0 +1,4 @@ +namespace Ghost.Entities.Components; +public interface IComponentData +{ +} \ No newline at end of file diff --git a/Ghost.Entities/ScriptComponent.cs b/Ghost.Entities/Components/ScriptComponent.cs similarity index 97% rename from Ghost.Entities/ScriptComponent.cs rename to Ghost.Entities/Components/ScriptComponent.cs index 3aae5b0..33f0788 100644 --- a/Ghost.Entities/ScriptComponent.cs +++ b/Ghost.Entities/Components/ScriptComponent.cs @@ -1,4 +1,4 @@ -namespace Ghost.Entities; +namespace Ghost.Entities.Components; public abstract class ScriptComponent : IComponentData { diff --git a/Ghost.Entities/Entity.cs b/Ghost.Entities/Entity.cs index 539ada3..2b9fdef 100644 --- a/Ghost.Entities/Entity.cs +++ b/Ghost.Entities/Entity.cs @@ -1,18 +1,14 @@ -using Ghost.Entities.Query; -using Ghost.Entities.Utilities; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; namespace Ghost.Entities; [SkipLocalsInit] public struct Entity : IEquatable, IComparable { - public const int WORLD_INDEX_BITS = 4; - public const int GENERATION_BITS = 8; - public const int INDEX_BITS = sizeof(EntityID) * 8 - WORLD_INDEX_BITS - GENERATION_BITS; + // Is 256 generations enough? If not, increase the size of GenerationID or make generation as a separate int field. + public const int GENERATION_BITS = sizeof(GenerationID) * 8; + public const int INDEX_BITS = sizeof(EntityID) * 8 - GENERATION_BITS; - private const int _WORLD_INDEX_MASK = (1 << WORLD_INDEX_BITS) - 1; private const int _GENERATION_MASK = (1 << GENERATION_BITS) - 1; private const int _INDEX_MASK = (1 << INDEX_BITS) - 1; @@ -38,24 +34,18 @@ public struct Entity : IEquatable, IComparable get => (GenerationID)(_id >> INDEX_BITS & _GENERATION_MASK); } - public readonly WorldID WorldID - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => (WorldID)(_id >> (INDEX_BITS + GENERATION_BITS) & _WORLD_INDEX_MASK); - } - public static Entity Invalid { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(INVALID_ID, 0, 0); + get => new(INVALID_ID, 0); } - internal Entity(EntityID id, GenerationID generation, WorldID worldID) + internal Entity(EntityID id, GenerationID generation) { - _id = worldID << (INDEX_BITS + GENERATION_BITS) | generation << INDEX_BITS | id; + _id = generation << INDEX_BITS | id; } - public void IncrementGeneration() + internal void IncrementGeneration() { var generation = Generation + 1; if (generation >= _GENERATION_MASK) @@ -98,263 +88,6 @@ public struct Entity : IEquatable, IComparable public override readonly string ToString() { - return $"Entity {{ Index: {ID}, Generation: {Generation}, WorldIndex: {WorldID} }}"; - } -} - -public class EntityManager : IDisposable -{ - private readonly List _entities; - private readonly Queue _freeEntitySlots; - - private readonly World _world; - - public int EntityCount => _entities.Count; - public ReadOnlySpan Entities => CollectionsMarshal.AsSpan(_entities); - - public event Action? OnComponentAdded; - public event Action? OnComponentRemoved; - public event Action? OnEntityCreated; - public event Action? OnEntityRemoved; - - internal EntityManager(World world, int initialCapacity) - { - _entities = new(initialCapacity); - _freeEntitySlots = new(initialCapacity); - _world = world; - } - - /// - /// Adds a new to the world. - /// - /// The created . - public Entity CreateEntity() - { - Entity entity; - if (_freeEntitySlots.TryDequeue(out var id)) - { - entity = _entities[id]; - } - else - { - id = _entities.Count; - entity = new Entity(id, 0, _world.ID); - _entities.Add(entity); - } - - OnEntityCreated?.Invoke(entity); - return entity; - } - - /// - /// Removes the specified from the world. - /// - /// - public void RemoveEntity(ref Entity entity) - { - if (entity.ID >= _entities.Count || _entities[entity.ID].Generation != entity.Generation) - { - return; - } - - _world.ComponentStorage.Remove(entity); - - var slot = _entities[entity.ID]; - slot.IncrementGeneration(); - _entities[entity.ID] = slot; - _freeEntitySlots.Enqueue(entity.ID); - - OnEntityRemoved?.Invoke(entity.ID); - entity = Entity.Invalid; - } - - /// - /// Checks if the given is valid and belongs to this . - /// - /// The entity to check. - /// True if the entity is valid and belongs to this world; otherwise, false. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool HasEntity(Entity entity) - { - if (!entity.IsValid - || entity.WorldID != _world.ID - || entity.ID >= _entities.Count) - { - return false; - } - - return _entities[entity.ID].Generation == entity.Generation; - } - - /// - /// Adds a component of type to the given . - /// - /// The type of the component to set. - /// The entity for which the component is to be add. - /// The component value to add. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddComponent(Entity entity, T component) - where T : struct, IComponentData - { - _world.ComponentStorage.GetOrCreateComponentPool().Add(entity, component); - _world.ComponentStorage.GetOrCreateMask(TypeHandle.Value).SetBit(entity.ID); - OnComponentAdded?.Invoke(entity, typeof(T)); - } - - /// - /// Removes a component of type from the given . - /// - /// The type of the component to remove. - /// The entity for which the component is to be remove. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool RemoveComponent(Entity entity) - where T : struct, IComponentData - { - if (!_world.ComponentStorage.TryGetPool(out var pool) || !pool.Has(entity)) - { - return false; - } - - if (!pool.Remove(entity)) - { - return false; - } - - _world.ComponentStorage.GetOrCreateMask(TypeHandle.Value).ClearBit(entity.ID); - OnComponentRemoved?.Invoke(entity, typeof(T)); - - return true; - } - - /// - /// Sets a component of type for the given . - /// - /// The type of the component to set. - /// The entity for which the component is to be set. - /// The component value to set. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetComponent(Entity entity, T component) - where T : struct, IComponentData - { - _world.ComponentStorage.GetOrCreateComponentPool().Set(entity, component); - } - - /// - /// Checks if the given has a component of the specified type. - /// - /// The entity to check. - /// The handle of the component type. - /// True if the entity has the component; otherwise, false. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool HasComponent(Entity entity, nint typeHandle) - { - return _world.ComponentStorage.TryGetMask(typeHandle, out var bitSet) && bitSet.IsSet(entity.ID); - } - - /// - /// Retrieves a reference to a component of type associated with the given . - /// - /// The type of the component to retrieve. - /// The entity whose component is to be retrieved. - /// A to the component, or a null reference if the component does not exist. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Ref GetComponent(Entity entity) - where T : struct, IComponentData - { - if (_world.ComponentStorage.TryGetPool(out var pool) && pool.Has(entity)) - { - return new Ref(ref pool.GetRef(entity)); - } - else - { - return new Ref(ref Unsafe.NullRef(), false); - } - } - - /// - /// Adds a script of type to the given . - /// - /// The type of the script to add. - /// The entity to which the script is to be added. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddScript(Entity entity) - where T : ScriptComponent, new() - { - _world.ComponentStorage.ScriptComponentPool.Add(entity, new T()); - OnComponentAdded?.Invoke(entity, typeof(ScriptComponent)); - } - - /// - /// Adds a script of the specified type to the given . - /// - /// The entity to which the script is to be added. - /// The type of the script to add. - /// Thrown if the specified type does not inherit from . - /// Thrown if the script instance could not be created. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddScript(Entity entity, Type type) - { - if (!typeof(ScriptComponent).IsAssignableFrom(type)) - { - throw new ArgumentException($"Type {type} must inherit from ScriptComponent.", nameof(type)); - } - - var instance = (ScriptComponent?)Activator.CreateInstance(type) ?? throw new InvalidOperationException($"Failed to create instance of {type}."); - _world.ComponentStorage.ScriptComponentPool.Add(entity, instance); - OnComponentAdded?.Invoke(entity, typeof(ScriptComponent)); - } - - public bool RemoveScript(Entity entity) - where T : ScriptComponent - { - if (!_world.ComponentStorage.ScriptComponentPool.Remove(entity)) - { - return false; - } - - OnComponentRemoved?.Invoke(entity, typeof(ScriptComponent)); - return true; - } - - public bool RemoveScriptAt(Entity entity, int index) - { - if (!_world.ComponentStorage.ScriptComponentPool.RemoveAt(entity, index)) - { - return false; - } - - OnComponentRemoved?.Invoke(entity, typeof(ScriptComponent)); - return true; - } - - /// - /// Retrieves the first script of type associated with the given . - /// - /// The type of the script to retrieve. - /// The entity whose script is to be retrieved. - /// The script of type , or null if no such script exists. - public T? GetScript(Entity entity) - where T : ScriptComponent - { - return (T?)_world.ComponentStorage.ScriptComponentPool.Get(entity)? - .FirstOrDefault(script => script is T tScript); - } - - /// - /// Retrieves all scripts of type associated with the given . - /// - /// The type of the scripts to retrieve. - /// The entity whose scripts are to be retrieved. - /// An enumerable of scripts of type . - public IEnumerable GetScripts(Entity entity) - where T : ScriptComponent - { - return (IEnumerable?)_world.ComponentStorage.ScriptComponentPool.Get(entity)?.Where(script => script is T tScript) ?? Enumerable.Empty(); - } - - public void Dispose() - { - _entities.Clear(); - _freeEntitySlots.Clear(); + return $"Entity {{ Index: {ID}, Generation: {Generation} }}"; } } \ No newline at end of file diff --git a/Ghost.Entities/EntityManager.cs b/Ghost.Entities/EntityManager.cs new file mode 100644 index 0000000..74b9483 --- /dev/null +++ b/Ghost.Entities/EntityManager.cs @@ -0,0 +1,262 @@ +using Ghost.Entities.Components; +using Ghost.Entities.Query; +using Ghost.Entities.Utilities; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ghost.Entities; +public struct EntityManager : IDisposable +{ + private readonly List _entities; + private readonly Queue _freeEntitySlots; + + private readonly World _world; + + public readonly int EntityCount => _entities.Count; + public readonly ReadOnlySpan Entities => CollectionsMarshal.AsSpan(_entities); + + public event Action? OnEntityCreated; + public event Action? OnEntityRemoved; + public event Action? OnComponentAdded; + public event Action? OnComponentRemoved; + + internal EntityManager(World world, int initialCapacity) + { + _entities = new(initialCapacity); + _freeEntitySlots = new(initialCapacity); + _world = world; + } + + /// + /// Adds a new to the world. + /// + /// The created . + public readonly Entity CreateEntity() + { + Entity entity; + if (_freeEntitySlots.TryDequeue(out var id)) + { + entity = _entities[id]; + } + else + { + id = _entities.Count; + entity = new Entity(id, 0); + _entities.Add(entity); + } + + OnEntityCreated?.Invoke(entity); + return entity; + } + + /// + /// Removes the specified from the world. + /// + /// + public readonly void RemoveEntity(ref Entity entity) + { + if (entity.ID >= _entities.Count || _entities[entity.ID].Generation != entity.Generation) + { + return; + } + + _world.ComponentStorage.Remove(entity); + + var slot = _entities[entity.ID]; + slot.IncrementGeneration(); + _entities[entity.ID] = slot; + _freeEntitySlots.Enqueue(entity.ID); + + OnEntityRemoved?.Invoke(entity.ID); + entity = Entity.Invalid; + } + + /// + /// Checks if the given is valid and belongs to this . + /// + /// The entity to check. + /// True if the entity is valid and belongs to this world; otherwise, false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool HasEntity(Entity entity) + { + if (!entity.IsValid + || entity.ID >= _entities.Count) + { + return false; + } + + return _entities[entity.ID].Generation == entity.Generation; + } + + /// + /// Adds a component of type to the given . + /// + /// The type of the component to set. + /// The entity for which the component is to be add. + /// The component value to add. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void AddComponent(Entity entity, T component) + where T : struct, IComponentData + { + _world.ComponentStorage.GetOrCreateComponentPool().Add(entity, component); + _world.ComponentStorage.GetOrCreateMask(TypeHandle.Get()).SetBit(entity.ID); + OnComponentAdded?.Invoke(entity, typeof(T)); + } + + /// + /// Removes a component of type from the given . + /// + /// The type of the component to remove. + /// The entity for which the component is to be remove. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool RemoveComponent(Entity entity) + where T : struct, IComponentData + { + if (!_world.ComponentStorage.TryGetPool(out var pool) || !pool.Has(entity)) + { + return false; + } + + if (!pool.Remove(entity)) + { + return false; + } + + _world.ComponentStorage.GetOrCreateMask(TypeHandle.Get()).ClearBit(entity.ID); + OnComponentRemoved?.Invoke(entity, typeof(T)); + + return true; + } + + /// + /// Sets a component of type for the given . + /// + /// The type of the component to set. + /// The entity for which the component is to be set. + /// The component value to set. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void SetComponent(Entity entity, in T component) + where T : struct, IComponentData + { + _world.ComponentStorage.GetOrCreateComponentPool().Set(entity, in component); + } + + /// + /// Checks if the given has a component of the specified type. + /// + /// The entity to check. + /// The handle of the component type. + /// True if the entity has the component; otherwise, false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool HasComponent(Entity entity, nint typeHandle) + { + return _world.ComponentStorage.TryGetMask(typeHandle, out var bitSet) && bitSet.IsSet(entity.ID); + } + + /// + /// Retrieves a reference to a component of type associated with the given . + /// + /// The type of the component to retrieve. + /// The entity whose component is to be retrieved. + /// A to the component, or a null reference if the component does not exist. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly Ref GetComponent(Entity entity) + where T : struct, IComponentData + { + if (_world.ComponentStorage.TryGetPool(out var pool) && pool.Has(entity)) + { + return new Ref(ref pool.GetRef(entity)); + } + else + { + return new Ref(ref Unsafe.NullRef(), false); + } + } + + /// + /// Adds a script of type to the given . + /// + /// The type of the script to add. + /// The entity to which the script is to be added. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void AddScript(Entity entity) + where T : ScriptComponent, new() + { + _world.ComponentStorage.ScriptComponentPool.Add(entity, new T()); + OnComponentAdded?.Invoke(entity, typeof(ScriptComponent)); + } + + /// + /// Adds a script of the specified type to the given . + /// + /// The entity to which the script is to be added. + /// The type of the script to add. + /// Thrown if the specified type does not inherit from . + /// Thrown if the script instance could not be created. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void AddScript(Entity entity, Type type) + { + if (!typeof(ScriptComponent).IsAssignableFrom(type)) + { + throw new ArgumentException($"Type {type} must inherit from ScriptComponent.", nameof(type)); + } + + var instance = (ScriptComponent?)Activator.CreateInstance(type) ?? throw new InvalidOperationException($"Failed to create instance of {type}."); + _world.ComponentStorage.ScriptComponentPool.Add(entity, instance); + OnComponentAdded?.Invoke(entity, typeof(ScriptComponent)); + } + + public readonly bool RemoveScript(Entity entity) + where T : ScriptComponent + { + if (!_world.ComponentStorage.ScriptComponentPool.Remove(entity)) + { + return false; + } + + OnComponentRemoved?.Invoke(entity, typeof(ScriptComponent)); + return true; + } + + public readonly bool RemoveScriptAt(Entity entity, int index) + { + if (!_world.ComponentStorage.ScriptComponentPool.RemoveAt(entity, index)) + { + return false; + } + + OnComponentRemoved?.Invoke(entity, typeof(ScriptComponent)); + return true; + } + + /// + /// Retrieves the first script of type associated with the given . + /// + /// The type of the script to retrieve. + /// The entity whose script is to be retrieved. + /// The script of type , or null if no such script exists. + public readonly T? GetScript(Entity entity) + where T : ScriptComponent + { + return (T?)_world.ComponentStorage.ScriptComponentPool.Get(entity)? + .FirstOrDefault(script => script is T tScript); + } + + /// + /// Retrieves all scripts of type associated with the given . + /// + /// The type of the scripts to retrieve. + /// The entity whose scripts are to be retrieved. + /// An enumerable of scripts of type . + public readonly IEnumerable GetScripts(Entity entity) + where T : ScriptComponent + { + return (IEnumerable?)_world.ComponentStorage.ScriptComponentPool.Get(entity)?.Where(script => script is T tScript) ?? Enumerable.Empty(); + } + + public readonly void Dispose() + { + _entities.Clear(); + _freeEntitySlots.Clear(); + } +} \ No newline at end of file diff --git a/Ghost.Entities/Helpers/EntityHelpers.cs b/Ghost.Entities/Helpers/EntityHelpers.cs deleted file mode 100644 index 01f348a..0000000 --- a/Ghost.Entities/Helpers/EntityHelpers.cs +++ /dev/null @@ -1,147 +0,0 @@ -using Ghost.Entities.Query; -using System.Runtime.CompilerServices; - -namespace Ghost.Entities.Helpers; - -/// -/// Provides extension methods for working with entities in the Ghost framework. -/// -public static class EntityHelpers -{ - public static World GetWorld(this Entity entity) - { - return World.GetWorld(entity.WorldID); - } - - /// - /// Adds a component of type to the given . - /// - /// The type of the component to set. - /// The entity for which the component is to be add. - /// The component value to add. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void AddComponent(this Entity entity, T component) - where T : struct, IComponentData - { - var world = entity.GetWorld(); - world.EntityManager.AddComponent(entity, component); - } - - /// - /// Removes a component of type from the given . - /// - /// The type of the component to remove. - /// The entity for which the component is to be remove. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool RemoveComponent(this Entity entity) - where T : struct, IComponentData - { - var world = entity.GetWorld(); - return world.EntityManager.RemoveComponent(entity); - } - - /// - /// Sets a component of type for the given . - /// - /// The type of the component to set. - /// The entity for which the component is to be set. - /// The component value to set. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetComponent(this Entity entity, T component) - where T : struct, IComponentData - { - var world = entity.GetWorld(); - world.EntityManager.SetComponent(entity, component); - } - - /// - /// Checks if the given has a component of the specified type. - /// - /// The entity to check. - /// The handle of the component type. - /// True if the entity has the component; otherwise, false. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool HasComponent(this Entity entity, nint typeHandle) - { - var world = entity.GetWorld(); - return world.EntityManager.HasComponent(entity, typeHandle); - } - - /// - /// Retrieves a reference to a component of type associated with the given . - /// - /// The type of the component to retrieve. - /// The entity whose component is to be retrieved. - /// A to the component, or a null reference if the component does not exist. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Ref GetComponent(this Entity entity) - where T : struct, IComponentData - { - var world = entity.GetWorld(); - return world.EntityManager.GetComponent(entity); - } - - /// - /// Adds a script of type to the given . - /// - /// The type of the script to add. - /// The entity to which the script is to be added. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void AddScript(this Entity entity) - where T : ScriptComponent, new() - { - var world = entity.GetWorld(); - world.EntityManager.AddScript(entity); - } - - /// - /// Adds a script of the specified type to the given . - /// - /// The entity to which the script is to be added. - /// The type of the script to add. - /// Thrown if the specified type does not inherit from . - /// Thrown if the script instance could not be created. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void AddScript(this Entity entity, Type type) - { - var world = entity.GetWorld(); - world.EntityManager.AddScript(entity, type); - } - - /// - /// Retrieves the first script of type associated with the given . - /// - /// The type of the script to retrieve. - /// The entity whose script is to be retrieved. - /// The script of type , or null if no such script exists. - public static T? GetScript(this Entity entity) - where T : ScriptComponent - { - var world = entity.GetWorld(); - return world.EntityManager.GetScript(entity); - } - - /// - /// Retrieves all scripts of type associated with the given . - /// - /// The type of the scripts to retrieve. - /// The entity whose scripts are to be retrieved. - /// An enumerable of scripts of type . - public static IEnumerable GetScripts(this Entity entity) - where T : ScriptComponent - { - var world = entity.GetWorld(); - return world.EntityManager.GetScripts(entity); - } - - /// - /// Destroys the given by removing it from its associated . - /// - /// The entity to destroy. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Destroy(this Entity entity) - { - var world = entity.GetWorld(); - world.EntityManager.RemoveEntity(ref entity); - } -} \ No newline at end of file diff --git a/Ghost.Entities/Query/QueryTypeParameter.cs b/Ghost.Entities/Query/QueryTypeParameter.cs index 182bf44..0f2ea30 100644 --- a/Ghost.Entities/Query/QueryTypeParameter.cs +++ b/Ghost.Entities/Query/QueryTypeParameter.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using Ghost.Entities.Components; +using System.Runtime.CompilerServices; namespace Ghost.Entities.Query; diff --git a/Ghost.Entities/System.cs b/Ghost.Entities/System.cs deleted file mode 100644 index 2261dba..0000000 --- a/Ghost.Entities/System.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace Ghost.Entities; - -public abstract class SystemBase -{ - /// - /// Gets the execution order of the current operation or component. - /// - public virtual int ExecutionOrder => 0; - - /// - /// Gets or sets a value indicating whether the feature is enabled. - /// - public virtual bool Enable - { - get; - set; - } = true; - - /// - /// The world that this system belongs to. - /// - public World World - { - get; - internal set; - } = null!; - - public virtual void OnCreate() - { - } - - public virtual void OnUpdate() - { - } - - public virtual void OnDestroy() - { - } -} - -public class SystemStorage : IDisposable -{ - private readonly List _systems = new(); - private readonly List _executionList = new(); - - private readonly World _world; - - public event Action? SystemAdded; - public event Action? SystemRemoved; - - internal SystemStorage(World world) - { - _world = world; - } - - public void AddSystem(T system) - where T : SystemBase - { - _systems.Add(system); - system.World = _world; - if (system.Enable) - { - system.OnCreate(); - } - - SystemAdded?.Invoke(system); - } - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddSystem() - where T : SystemBase, new() - { - AddSystem(new T()); - } - - public void RemoveSystem(T system) - where T : SystemBase - { - system.World = null!; - _systems.Remove(system); - if (system.Enable) - { - system.OnDestroy(); - } - - SystemRemoved?.Invoke(system); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void RemoveSystem() - where T : SystemBase, new() - { - var system = _systems.FirstOrDefault(s => s is T); - if (system != null) - { - RemoveSystem(system); - } - } - - internal void RebuildExecutionList() - { - _executionList.Clear(); - _executionList.AddRange(_systems.OrderBy(s => s.ExecutionOrder)); - } - - internal void UpdateSystems() - { - foreach (var system in _systems) - { - if (!system.Enable) - { - continue; - } - system.OnUpdate(); - } - } - - public void Dispose() - { - foreach (var system in _systems) - { - if (!system.Enable) - { - continue; - } - system.OnDestroy(); - } - - _systems.Clear(); - _executionList.Clear(); - } -} \ No newline at end of file diff --git a/Ghost.Entities/Systems/ISystem.cs b/Ghost.Entities/Systems/ISystem.cs new file mode 100644 index 0000000..be2a3af --- /dev/null +++ b/Ghost.Entities/Systems/ISystem.cs @@ -0,0 +1,27 @@ +namespace Ghost.Entities.Systems; + +/// +/// Attribute to declare that a system depends on one or more other systems. +/// +[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = false)] +public class DependsOnAttribute : Attribute +{ + public Type[] Prerequisites + { + get; + } + + public DependsOnAttribute(params Type[] prerequisites) + { + Prerequisites = prerequisites; + } +} + +public interface ISystem +{ + public void OnCreate(in SystemState state); + + public void OnUpdate(in SystemState state); + + public void OnDestroy(in SystemState state); +} \ No newline at end of file diff --git a/Ghost.Entities/Systems/SystemDependencyBuilder.cs b/Ghost.Entities/Systems/SystemDependencyBuilder.cs new file mode 100644 index 0000000..07a0687 --- /dev/null +++ b/Ghost.Entities/Systems/SystemDependencyBuilder.cs @@ -0,0 +1,92 @@ +using System.Reflection; + +namespace Ghost.Entities.Systems; + +internal class SystemDependencyBuilder(List allSystemTypes) +{ + private Dictionary> _dependencies = new(); + + public void BuildDependencyGraph() + { + foreach (var systemType in allSystemTypes) + { + if (!typeof(ISystem).IsAssignableFrom(systemType) || systemType.IsAbstract || systemType.IsInterface) + { + throw new ArgumentException($"{systemType.Name} is not a concrete ISystem type."); + } + + var directDependencies = new List(); + var dependsOnAttributes = systemType.GetCustomAttributes(false); + foreach (var attr in dependsOnAttributes) + { + directDependencies.AddRange(attr.Prerequisites); + } + + _dependencies[systemType] = directDependencies; + } + } + + private void Visit(Type systemType, HashSet visited, HashSet permanentMark, List executionOrder) + { + if (permanentMark.Contains(systemType)) + { + return; + } + + if (visited.Contains(systemType)) + { + throw new InvalidOperationException($"Circular dependency detected involving system: {systemType.Name}"); + } + + visited.Add(systemType); // Mark as currently visiting + + if (_dependencies.TryGetValue(systemType, out var directDependencies)) + { + foreach (var dependencyType in directDependencies) + { + // Ensure the dependency is a registered system type + if (!allSystemTypes.Contains(dependencyType)) + { + throw new InvalidOperationException($"System {systemType.Name} depends on unregistered system {dependencyType.Name}."); + } + + Visit(dependencyType, visited, permanentMark, executionOrder); + } + } + + visited.Remove(systemType); // Done visiting this node in the current path + permanentMark.Add(systemType); // Mark as permanently processed + executionOrder.Add(systemType); // Add to the sorted list (this will be reversed later for correct order) + } + + /// + /// Builds the topological order of systems. + /// + /// A list of system types in the order they should be executed. + /// Thrown if a circular dependency is detected." + public List BuildExecutionOrder() + { + var executionOrder = new List(allSystemTypes.Count); + var visited = new HashSet(); // Tracks visited nodes in the current DFS path (for cycle detection) + var permanentMark = new HashSet(); // Tracks nodes whose dependencies have been fully resolved + + // Initialize dependencies for all registered systems, even those without explicit attributes + foreach (var sysType in allSystemTypes) + { + if (!_dependencies.ContainsKey(sysType)) + { + _dependencies[sysType] = new(); + } + } + + foreach (var systemType in allSystemTypes) + { + if (!permanentMark.Contains(systemType)) + { + Visit(systemType, visited, permanentMark, executionOrder); + } + } + + return executionOrder; + } +} \ No newline at end of file diff --git a/Ghost.Entities/Systems/SystemState.cs b/Ghost.Entities/Systems/SystemState.cs new file mode 100644 index 0000000..5e15c7e --- /dev/null +++ b/Ghost.Entities/Systems/SystemState.cs @@ -0,0 +1,10 @@ +namespace Ghost.Entities.Systems; + +public struct SystemState +{ + public World World + { + get; + init; + } +} \ No newline at end of file diff --git a/Ghost.Entities/Systems/SystemStorage.cs b/Ghost.Entities/Systems/SystemStorage.cs new file mode 100644 index 0000000..3a91735 --- /dev/null +++ b/Ghost.Entities/Systems/SystemStorage.cs @@ -0,0 +1,95 @@ +using System.Runtime.CompilerServices; + +namespace Ghost.Entities.Systems; + +[SkipLocalsInit] +public struct SystemStorage +{ + private readonly List _systems = new(); + private readonly List _executionList = new(); + + private readonly World _world; + + public event Action? SystemAdded; + public event Action? SystemRemoved; + + internal SystemStorage(World world) + { + _world = world; + } + + public readonly void AddSystem(Type systemType) + { + _systems.Add(systemType); + SystemAdded?.Invoke(systemType); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void AddSystem() + where T : ISystem, new() + { + AddSystem(typeof(T)); + } + + public readonly void RemoveSystem(Type systemType) + { + _systems.Remove(systemType); + SystemRemoved?.Invoke(systemType); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void RemoveSystem() + where T : ISystem, new() + { + RemoveSystem(typeof(T)); + } + + internal void CreateSystems() + { + var builder = new SystemDependencyBuilder(_systems); + builder.BuildDependencyGraph(); + var executionOrder = builder.BuildExecutionOrder(); + + var state = new SystemState() + { + World = _world, + }; + + foreach (var systemType in executionOrder) + { + var system = (ISystem?)Activator.CreateInstance(systemType) ?? throw new InvalidOperationException($"Failed to create instance of system type {systemType.Name}."); + + _executionList.Add(system); + system.OnCreate(in state); + } + } + + internal void UpdateSystems() + { + var state = new SystemState() + { + World = _world, + }; + + foreach (var system in _executionList) + { + system.OnUpdate(in state); + } + } + + internal void Dispose() + { + var state = new SystemState() + { + World = _world, + }; + + foreach (var system in _executionList) + { + system.OnDestroy(in state); + } + + _systems.Clear(); + _executionList.Clear(); + } +} \ No newline at end of file diff --git a/Ghost.Entities/Template/QueryEnumerable.cs b/Ghost.Entities/Template/QueryEnumerable.cs index 247406d..e2b4845 100644 --- a/Ghost.Entities/Template/QueryEnumerable.cs +++ b/Ghost.Entities/Template/QueryEnumerable.cs @@ -1,5 +1,6 @@  +using Ghost.Entities.Components; using Ghost.Entities.Query; using Ghost.Entities.Utilities; using Misaki.HighPerformance.Unsafe.Collections; @@ -26,10 +27,10 @@ public struct QueryEnumerable _count = count; _filters = new(); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); } - public Enumerator GetEnumerator() => new Enumerator(_world, _pool0, _count, _filters); + public readonly Enumerator GetEnumerator() => new (_world, _pool0, _count, _filters); public ref struct Enumerator { @@ -79,96 +80,96 @@ public struct QueryEnumerable public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); return this; } } @@ -195,11 +196,11 @@ public struct QueryEnumerable _count = count; _filters = new(); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); } - public Enumerator GetEnumerator() => new Enumerator(_world, _pool0, _pool1, _count, _filters); + public readonly Enumerator GetEnumerator() => new (_world, _pool0, _pool1, _count, _filters); public ref struct Enumerator { @@ -251,96 +252,96 @@ public struct QueryEnumerable public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); return this; } } @@ -369,12 +370,12 @@ public struct QueryEnumerable _count = count; _filters = new(); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); } - public Enumerator GetEnumerator() => new Enumerator(_world, _pool0, _pool1, _pool2, _count, _filters); + public readonly Enumerator GetEnumerator() => new (_world, _pool0, _pool1, _pool2, _count, _filters); public ref struct Enumerator { @@ -428,96 +429,96 @@ public struct QueryEnumerable public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); return this; } } @@ -548,13 +549,13 @@ public struct QueryEnumerable _count = count; _filters = new(); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); } - public Enumerator GetEnumerator() => new Enumerator(_world, _pool0, _pool1, _pool2, _pool3, _count, _filters); + public readonly Enumerator GetEnumerator() => new (_world, _pool0, _pool1, _pool2, _pool3, _count, _filters); public ref struct Enumerator { @@ -610,96 +611,96 @@ public struct QueryEnumerable public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); return this; } } @@ -732,14 +733,14 @@ public struct QueryEnumerable _count = count; _filters = new(); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); } - public Enumerator GetEnumerator() => new Enumerator(_world, _pool0, _pool1, _pool2, _pool3, _pool4, _count, _filters); + public readonly Enumerator GetEnumerator() => new (_world, _pool0, _pool1, _pool2, _pool3, _pool4, _count, _filters); public ref struct Enumerator { @@ -797,96 +798,96 @@ public struct QueryEnumerable public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); return this; } } @@ -921,15 +922,15 @@ public struct QueryEnumerable _count = count; _filters = new(); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); } - public Enumerator GetEnumerator() => new Enumerator(_world, _pool0, _pool1, _pool2, _pool3, _pool4, _pool5, _count, _filters); + public readonly Enumerator GetEnumerator() => new (_world, _pool0, _pool1, _pool2, _pool3, _pool4, _pool5, _count, _filters); public ref struct Enumerator { @@ -989,96 +990,96 @@ public struct QueryEnumerable public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); return this; } } @@ -1115,16 +1116,16 @@ public struct QueryEnumerable _count = count; _filters = new(); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); } - public Enumerator GetEnumerator() => new Enumerator(_world, _pool0, _pool1, _pool2, _pool3, _pool4, _pool5, _pool6, _count, _filters); + public readonly Enumerator GetEnumerator() => new (_world, _pool0, _pool1, _pool2, _pool3, _pool4, _pool5, _pool6, _count, _filters); public ref struct Enumerator { @@ -1186,96 +1187,96 @@ public struct QueryEnumerable public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); return this; } } @@ -1314,17 +1315,17 @@ public struct QueryEnumerable _count = count; _filters = new(); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); } - public Enumerator GetEnumerator() => new Enumerator(_world, _pool0, _pool1, _pool2, _pool3, _pool4, _pool5, _pool6, _pool7, _count, _filters); + public readonly Enumerator GetEnumerator() => new (_world, _pool0, _pool1, _pool2, _pool3, _pool4, _pool5, _pool6, _pool7, _count, _filters); public ref struct Enumerator { @@ -1388,96 +1389,96 @@ public struct QueryEnumerable public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAll() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); - _filters._all.Add(TypeHandle.Value); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); + _filters._all.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAny() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); - _filters._any.Add(TypeHandle.Value); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); + _filters._any.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithAbsent() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); - _filters._absent.Add(TypeHandle.Value); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); + _filters._absent.Add(TypeHandle.Get()); return this; } public readonly QueryEnumerable WithDisabled() where TComponent0 : struct, IComponentData where TComponent1 : struct, IComponentData where TComponent2 : struct, IComponentData { - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); - _filters._disabled.Add(TypeHandle.Value); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); + _filters._disabled.Add(TypeHandle.Get()); return this; } } diff --git a/Ghost.Entities/Template/QueryEnumerable.tt b/Ghost.Entities/Template/QueryEnumerable.tt index 26913ad..eab3fd2 100644 --- a/Ghost.Entities/Template/QueryEnumerable.tt +++ b/Ghost.Entities/Template/QueryEnumerable.tt @@ -5,6 +5,7 @@ <#@ import namespace="System.Linq" #> <#@ include file="Helpers.ttinclude" #> +using Ghost.Entities.Components; using Ghost.Entities.Query; using Ghost.Entities.Utilities; using Misaki.HighPerformance.Unsafe.Collections; @@ -47,11 +48,11 @@ public struct QueryEnumerable<<#= generics #>> _filters = new(); <# for (int i = 0; i < arity; i++) {#> - _filters._all.Add(TypeHandle>.Value); + _filters._all.Add(TypeHandle.Get>()); <# } #> } - public Enumerator GetEnumerator() => new Enumerator(_world, <#= constructorParams #>, _count, _filters); + public readonly Enumerator GetEnumerator() => new (_world, <#= constructorParams #>, _count, _filters); public ref struct Enumerator { @@ -110,7 +111,7 @@ var compRestrictions = AppendGenericRestrictions(i, "TComponent", "struct, IComp <#= compRestrictions #> { <# for (int j = 0; j < i; j++) {#> - _filters._all.Add(TypeHandle>.Value); + _filters._all.Add(TypeHandle.Get>()); <# } #> return this; } @@ -119,7 +120,7 @@ var compRestrictions = AppendGenericRestrictions(i, "TComponent", "struct, IComp <#= compRestrictions #> { <# for (int j = 0; j < i; j++) {#> - _filters._any.Add(TypeHandle>.Value); + _filters._any.Add(TypeHandle.Get>()); <# } #> return this; } @@ -128,7 +129,7 @@ var compRestrictions = AppendGenericRestrictions(i, "TComponent", "struct, IComp <#= compRestrictions #> { <# for (int j = 0; j < i; j++) {#> - _filters._absent.Add(TypeHandle>.Value); + _filters._absent.Add(TypeHandle.Get>()); <# } #> return this; } @@ -137,7 +138,7 @@ var compRestrictions = AppendGenericRestrictions(i, "TComponent", "struct, IComp <#= compRestrictions #> { <# for (int j = 0; j < i; j++) {#> - _filters._disabled.Add(TypeHandle>.Value); + _filters._disabled.Add(TypeHandle.Get>()); <# } #> return this; } diff --git a/Ghost.Entities/Template/QueryItem.cs b/Ghost.Entities/Template/QueryItem.cs index 87b0053..c7a32e2 100644 --- a/Ghost.Entities/Template/QueryItem.cs +++ b/Ghost.Entities/Template/QueryItem.cs @@ -1,5 +1,6 @@  +using Ghost.Entities.Components; using Ghost.Entities.Query; namespace Ghost.Entities; diff --git a/Ghost.Entities/Template/QueryRefComponent.cs b/Ghost.Entities/Template/QueryRefComponent.cs index 397c77f..2d50819 100644 --- a/Ghost.Entities/Template/QueryRefComponent.cs +++ b/Ghost.Entities/Template/QueryRefComponent.cs @@ -1,5 +1,7 @@  +using Ghost.Entities.Components; + namespace Ghost.Entities; public delegate void QueryRefComponent(Entity entity, ref T0 t0Component) diff --git a/Ghost.Entities/Template/World.Query.cs b/Ghost.Entities/Template/World.Query.cs index ea7a192..89adc3c 100644 --- a/Ghost.Entities/Template/World.Query.cs +++ b/Ghost.Entities/Template/World.Query.cs @@ -1,5 +1,7 @@  +using Ghost.Entities.Components; + namespace Ghost.Entities; public partial class World diff --git a/Ghost.Entities/Utilities/TypeHandle.cs b/Ghost.Entities/Utilities/TypeHandle.cs index 6544b4c..fae5516 100644 --- a/Ghost.Entities/Utilities/TypeHandle.cs +++ b/Ghost.Entities/Utilities/TypeHandle.cs @@ -1,6 +1,28 @@ -namespace Ghost.Entities.Utilities; +using System.Runtime.CompilerServices; -internal static class TypeHandle +namespace Ghost.Entities.Utilities; + +internal static class TypeHandle { - public static nint Value => typeof(T).TypeHandle.Value; + /// + /// Gets the type handle for the specified type. + /// + /// The type to get the handle for. + /// The type handle as a nint. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nint Get() + { + return typeof(T).TypeHandle.Value; + } + + /// + /// Gets the type handle for the specified type. + /// + /// The type to get the handle for. + /// The type handle as a nint. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nint Get(Type type) + { + return type.TypeHandle.Value; + } } \ No newline at end of file diff --git a/Ghost.Entities/World.cs b/Ghost.Entities/World.cs index e634975..84fdd24 100644 --- a/Ghost.Entities/World.cs +++ b/Ghost.Entities/World.cs @@ -1,4 +1,6 @@ -using Ghost.Entities.Query; +using Ghost.Entities.Components; +using Ghost.Entities.Query; +using Ghost.Entities.Systems; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -7,11 +9,9 @@ namespace Ghost.Entities; // TODO: Archetype system for better performance public partial class World { - private static List s_worlds = new(2); + private static List s_worlds = new(4); private static Queue s_freeWorldSlots = new(); - private static int s_maxWorldCount = (int)MathF.Pow(2, Entity.WORLD_INDEX_BITS); - public static int WorldCount => s_worlds.Count - s_freeWorldSlots.Count; public static World Create(int entityCapacity = 16) @@ -24,7 +24,7 @@ public partial class World } else { - if (s_worlds.Count >= s_maxWorldCount) + if (s_worlds.Count >= WorldID.MaxValue) { throw new InvalidOperationException("Maximum number of worlds reached"); } diff --git a/Ghost.Test/Program.cs b/Ghost.Test/Program.cs index c470c53..4f39864 100644 --- a/Ghost.Test/Program.cs +++ b/Ghost.Test/Program.cs @@ -1,5 +1,6 @@ using Ghost.Entities; -using Ghost.Entities.Helpers; +using Ghost.Entities.Components; +using Ghost.Entities.Systems; using System.Numerics; var t = new Test(); @@ -15,17 +16,17 @@ public partial class Test var entity2 = world.EntityManager.CreateEntity(); var entity3 = world.EntityManager.CreateEntity(); - entity1.AddComponent(new Transform { position = new Vector3(1, 2, 3) }); - entity1.AddComponent(new Mesh { index = 42 }); - entity1.AddScript(); - entity1.AddScript(); + world.EntityManager.AddComponent(entity1, new Transform { position = new Vector3(1, 2, 3) }); + world.EntityManager.AddComponent(entity1, new Mesh { index = 42 }); + world.EntityManager.AddScript(entity1); + world.EntityManager.AddScript(entity1); - entity2.AddComponent(new Transform { position = new Vector3(4, 5, 6) }); - entity2.AddComponent(new Mesh { index = 43 }); - entity2.AddScript(); + world.EntityManager.AddComponent(entity2, new Transform { position = new Vector3(4, 5, 6) }); + world.EntityManager.AddComponent(entity2, new Mesh { index = 43 }); + world.EntityManager.AddScript(entity2); - entity3.AddComponent(new Transform { position = new Vector3(7, 8, 9) }); - entity3.AddScript(); + world.EntityManager.AddComponent(entity3, new Transform { position = new Vector3(7, 8, 9) }); + world.EntityManager.AddScript(entity3); foreach (var (_, transform) in world.Query()) { @@ -40,62 +41,56 @@ public partial class Test world.EntityManager.RemoveEntity(ref entity2); var entity4 = world.EntityManager.CreateEntity(); - entity4.AddComponent(new Transform { position = new Vector3(10, 11, 12) }); - entity4.AddComponent(new Mesh { index = 44 }); - entity4.AddScript(); + world.EntityManager.AddComponent(entity4, new Transform { position = new Vector3(10, 11, 12) }); + world.EntityManager.AddComponent(entity4, new Mesh { index = 44 }); + world.EntityManager.AddScript(entity4); + world.SystemStorage.AddSystem(); world.SystemStorage.AddSystem(); + world.SystemStorage.CreateSystems(); world.SystemStorage.UpdateSystems(); - //world.SystemStorage.RebuildExecutionList(); - //world.ComponentStorage.RebuildExecutionList(); - - //Console.WriteLine(); - //Console.WriteLine("Starting scripts..."); - //foreach (var component in world.QueryScript()) - //{ - // if (!component.Enable) - // { - // continue; - // } - // component.Start(); - //} - - //Console.WriteLine(); - //Console.WriteLine("Updating scripts..."); - //foreach (var component in world.QueryScript()) - //{ - // if (!component.Enable) - // { - // continue; - // } - // component.Update(); - //} - - //Console.WriteLine(); - //Console.WriteLine("LateUpdating scripts..."); - //foreach (var component in world.QueryScript()) - //{ - // if (!component.Enable) - // { - // continue; - // } - // component.LateUpdate(); - //} - world.Dispose(); } } -public class TestSystem : SystemBase +public class TestSystem : ISystem { - public override void OnUpdate() + public void OnCreate(in SystemState state) { - foreach (var (entity, transform) in World.Query().WithAny()) + } + + public void OnUpdate(in SystemState state) + { + foreach (var (entity, transform) in state.World.Query()) { Console.WriteLine($"Entity {entity.ID}: Transform Position = {transform.ValueRO.position}"); } } + + public void OnDestroy(in SystemState state) + { + } +} + +[DependsOn(typeof(TestSystem))] +public class TestSystem2 : ISystem +{ + public void OnCreate(in SystemState state) + { + } + + public void OnUpdate(in SystemState state) + { + foreach (var (entity, mesh) in state.World.Query()) + { + Console.WriteLine($"Entity {entity.ID}: Mesh Index = {mesh.ValueRO.index}"); + } + } + + public void OnDestroy(in SystemState state) + { + } } public struct Transform : IComponentData