Refactor application structure and add unit tests
Added: - New `ProgressService` class for managing progress indicators. - New `AssetDatabase`, `AssetOpenHandlerAttribute`, and `AsyncAssetOpenHandlerAttribute` classes for asset handling. - `Ghost.UnitTest` project for unit testing with associated files and configurations. Changed: - `ActivationHandler` class to ensure correct handling of `LaunchActivatedEventArgs`. - `App.xaml.cs` to register `INotificationService` and `IProgressService`, replacing `StackedNotificationService`. - `OnLaunched` method in `App.xaml.cs` to correctly call `ActivationHandler.Handle(args)` and start the host. - `INavigationAware` interface from internal to public for broader access. - `EditorState.cs` to activate `EditorApplication` with the current service provider. - Property names in `AssetItem` and `ExplorerItem` structs to `Name` and `FullName`. - `NotificationService` class to implement `INotificationService` and refactor notification handling. - `AssetPathToGlyphConverter` to handle file extensions consistently. - Bindings in `ProjectPage.xaml` and `ProjectPage.xaml.cs` to use `FullName` instead of `Path`. - `EngineEditorWindow` and `LandingWindow` classes to utilize new notification and progress services. - `Logger` class to include a new method for logging errors with exceptions. Updated: - Manifest files and project files to reflect new structure and dependencies. - Solution file `GhostEngine.sln` to include the new unit test project. - Added several new test classes and methods in `UnitTests.cs`.
@@ -25,4 +25,4 @@ internal static class ActivationHandler
|
|||||||
FolderInitialization();
|
FolderInitialization();
|
||||||
ProjectService.EnsureDefaultTemplate();
|
ProjectService.EnsureDefaultTemplate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
using Ghost.App.Infrastructures.AppState;
|
using Ghost.App.Infrastructures.AppState;
|
||||||
using Ghost.App.Services;
|
using Ghost.App.Services;
|
||||||
using Ghost.App.Utilities;
|
using Ghost.App.Utilities;
|
||||||
|
using Ghost.Editor.Services.Contracts;
|
||||||
|
using Ghost.Engine.Services;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
@@ -52,7 +54,8 @@ public partial class GhostApplication : Application
|
|||||||
HostHelper.AddEngineScope(context, services);
|
HostHelper.AddEngineScope(context, services);
|
||||||
|
|
||||||
services.AddSingleton<AppStateMachine>();
|
services.AddSingleton<AppStateMachine>();
|
||||||
services.AddSingleton<StackedNotificationService>();
|
services.AddSingleton<INotificationService, NotificationService>();
|
||||||
|
services.AddSingleton<IProgressService, ProgressService>();
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
@@ -82,9 +85,8 @@ public partial class GhostApplication : Application
|
|||||||
{
|
{
|
||||||
base.OnLaunched(args);
|
base.OnLaunched(args);
|
||||||
|
|
||||||
ActivationHandler.Handle(args);
|
|
||||||
|
|
||||||
Host.Start();
|
Host.Start();
|
||||||
|
ActivationHandler.Handle(args);
|
||||||
|
|
||||||
var stateMachine = GetService<AppStateMachine>();
|
var stateMachine = GetService<AppStateMachine>();
|
||||||
stateMachine.RegisterState(StateKey.Landing, () => new LandingState());
|
stateMachine.RegisterState(StateKey.Landing, () => new LandingState());
|
||||||
@@ -95,7 +97,6 @@ public partial class GhostApplication : Application
|
|||||||
|
|
||||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
// TODO: Log and handle exceptions as appropriate.
|
Logger.LogError(e.Exception);
|
||||||
// https://docs.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.application.unhandledexception.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 599 B After Width: | Height: | Size: 599 B |
|
Before Width: | Height: | Size: 831 B After Width: | Height: | Size: 831 B |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 580 B After Width: | Height: | Size: 580 B |
|
Before Width: | Height: | Size: 825 B After Width: | Height: | Size: 825 B |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 433 B After Width: | Height: | Size: 433 B |
|
Before Width: | Height: | Size: 599 B After Width: | Height: | Size: 599 B |
|
Before Width: | Height: | Size: 580 B After Width: | Height: | Size: 580 B |
|
Before Width: | Height: | Size: 583 B After Width: | Height: | Size: 583 B |
|
Before Width: | Height: | Size: 831 B After Width: | Height: | Size: 831 B |
|
Before Width: | Height: | Size: 825 B After Width: | Height: | Size: 825 B |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 852 B After Width: | Height: | Size: 852 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 432 B After Width: | Height: | Size: 432 B |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 456 B After Width: | Height: | Size: 456 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
@@ -1,6 +1,6 @@
|
|||||||
namespace Ghost.App.Contracts;
|
namespace Ghost.App.Contracts;
|
||||||
|
|
||||||
internal interface INavigationAware
|
public interface INavigationAware
|
||||||
{
|
{
|
||||||
public void OnNavigatedTo(object? parameter);
|
public void OnNavigatedTo(object? parameter);
|
||||||
public void OnNavigatedFrom();
|
public void OnNavigatedFrom();
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
using Ghost.App.View.Windows;
|
using Ghost.App.View.Windows;
|
||||||
using Ghost.Data.Models;
|
using Ghost.Data.Models;
|
||||||
using Ghost.Data.Services;
|
using Ghost.Data.Services;
|
||||||
|
using Ghost.Editor;
|
||||||
using Ghost.Engine;
|
using Ghost.Engine;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -57,6 +59,7 @@ internal class EditorState : IAppState
|
|||||||
|
|
||||||
public Task OnEnteredAsync(object? parameter)
|
public Task OnEnteredAsync(object? parameter)
|
||||||
{
|
{
|
||||||
|
EditorApplication.Activate(parameter, ((GhostApplication)(Application.Current)).Host.Services);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
internal struct AssetItem()
|
internal struct AssetItem()
|
||||||
{
|
{
|
||||||
public string AssetPath
|
public string Name
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
} = string.Empty;
|
} = string.Empty;
|
||||||
|
|
||||||
public string AssetName
|
public string FullNam
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
} = string.Empty;
|
} = string.Empty;
|
||||||
@@ -9,7 +9,7 @@ internal class ExplorerItem(string name, string path, bool isDirectory)
|
|||||||
get;
|
get;
|
||||||
} = name;
|
} = name;
|
||||||
|
|
||||||
public string Path
|
public string FullName
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
} = path;
|
} = path;
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
using CommunityToolkit.WinUI.Behaviors;
|
using CommunityToolkit.WinUI.Behaviors;
|
||||||
|
using Ghost.Editor.Models;
|
||||||
|
using Ghost.Editor.Services.Contracts;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ghost.App.Services;
|
namespace Ghost.App.Services;
|
||||||
|
|
||||||
public class StackedNotificationService
|
public class NotificationService : INotificationService
|
||||||
{
|
{
|
||||||
private InfoBar? _infoBar;
|
private InfoBar? _infoBar;
|
||||||
private StackedNotificationsBehavior? _notificationQueue;
|
private StackedNotificationsBehavior? _notificationQueue;
|
||||||
@@ -15,17 +17,7 @@ public class StackedNotificationService
|
|||||||
_notificationQueue = notificationQueue;
|
_notificationQueue = notificationQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void ClearReference()
|
public void ShowNotification(string? message, MessageType type, int duration = 5, string? title = null)
|
||||||
{
|
|
||||||
if (_infoBar != null)
|
|
||||||
{
|
|
||||||
_infoBar.IsOpen = false;
|
|
||||||
}
|
|
||||||
_infoBar = null;
|
|
||||||
_notificationQueue = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowNotification(string? message, InfoBarSeverity severity, int duration = 5, string? title = null)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(message))
|
if (string.IsNullOrWhiteSpace(message))
|
||||||
{
|
{
|
||||||
@@ -35,7 +27,7 @@ public class StackedNotificationService
|
|||||||
var notification = new Notification
|
var notification = new Notification
|
||||||
{
|
{
|
||||||
Message = message,
|
Message = message,
|
||||||
Severity = severity,
|
Severity = (InfoBarSeverity)type,
|
||||||
Duration = TimeSpan.FromSeconds(duration),
|
Duration = TimeSpan.FromSeconds(duration),
|
||||||
Title = title
|
Title = title
|
||||||
};
|
};
|
||||||
@@ -47,4 +39,14 @@ public class StackedNotificationService
|
|||||||
{
|
{
|
||||||
_notificationQueue?.Show(notification);
|
_notificationQueue?.Show(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void ClearReference()
|
||||||
|
{
|
||||||
|
if (_infoBar != null)
|
||||||
|
{
|
||||||
|
_infoBar.IsOpen = false;
|
||||||
|
}
|
||||||
|
_infoBar = null;
|
||||||
|
_notificationQueue = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
75
Ghost.App/Services/ProgressService.cs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
using CommunityToolkit.WinUI;
|
||||||
|
using Ghost.Editor.Services.Contracts;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Ghost.App.Services;
|
||||||
|
|
||||||
|
public class ProgressService : IProgressService
|
||||||
|
{
|
||||||
|
private Grid? _progressBarContainer;
|
||||||
|
private TextBlock? _progressMessage;
|
||||||
|
private ProgressBar? _progressBar;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private bool IsInitialized()
|
||||||
|
{
|
||||||
|
return _progressBarContainer != null && _progressMessage != null && _progressBar != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetReference(Grid progressBarContainer)
|
||||||
|
{
|
||||||
|
_progressBarContainer = progressBarContainer;
|
||||||
|
_progressMessage = _progressBarContainer.FindChild<TextBlock>();
|
||||||
|
_progressBar = _progressBarContainer.FindChild<ProgressBar>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowProgress(string message, double progress = 0.0)
|
||||||
|
{
|
||||||
|
if (!IsInitialized())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_progressBarContainer!.Visibility = Visibility.Visible;
|
||||||
|
_progressMessage!.Text = message;
|
||||||
|
_progressBar!.Value = progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowIndeterminateProgress(string message)
|
||||||
|
{
|
||||||
|
if (!IsInitialized())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_progressBarContainer!.Visibility = Visibility.Visible;
|
||||||
|
_progressMessage!.Text = message;
|
||||||
|
_progressBar!.IsIndeterminate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetProgress(double progress)
|
||||||
|
{
|
||||||
|
_progressBar!.Value = progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HideProgress()
|
||||||
|
{
|
||||||
|
if (!IsInitialized())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_progressBarContainer!.Visibility = Visibility.Collapsed;
|
||||||
|
_progressMessage!.Text = string.Empty;
|
||||||
|
_progressBar!.Value = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ClearReference()
|
||||||
|
{
|
||||||
|
_progressBarContainer = null;
|
||||||
|
_progressMessage = null;
|
||||||
|
_progressBar = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,13 +23,14 @@ public partial class AssetPathToGlyphConverter : IValueConverter
|
|||||||
// TODO: Use resource dictionary for icons.
|
// TODO: Use resource dictionary for icons.
|
||||||
return extension switch
|
return extension switch
|
||||||
{
|
{
|
||||||
|
".ghostscene" => "\uF159",
|
||||||
".fbx" or ".obj" => "\uF158",
|
".fbx" or ".obj" => "\uF158",
|
||||||
".png" or ".jpg" or ".jpeg" or ".gif" or ".bmp" => "\uE91B", // Image icon
|
".png" or ".jpg" or ".jpeg" or ".gif" or ".bmp" => "\uE91B",
|
||||||
".mp3" or ".wav" or ".ogg" => "\uE767", // Audio icon
|
".mp3" or ".wav" or ".ogg" => "\uE767",
|
||||||
".mp4" or ".avi" or ".mkv" => "\uE714", // Video icon
|
".mp4" or ".avi" or ".mkv" => "\uE714",
|
||||||
".txt" or ".md" => "\uF000", // Text file icon
|
".txt" or ".md" => "\uF000",
|
||||||
".cs" or ".hlsl" => "\uE943", // Code file icon
|
".cs" or ".hlsl" => "\uE943",
|
||||||
_ => "\uE8A5", // Default file icon
|
_ => "\uE8A5",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="0.25*" />
|
<RowDefinition Height="0.25*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<FontIcon FontSize="42" Glyph="{x:Bind Path, Converter={StaticResource AssetPathToGlyphConverter}}" />
|
<FontIcon FontSize="42" Glyph="{x:Bind FullName, Converter={StaticResource AssetPathToGlyphConverter}}" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Margin="8,0"
|
Margin="8,0"
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
HorizontalTextAlignment="Left"
|
HorizontalTextAlignment="Left"
|
||||||
Style="{StaticResource CaptionTextBlockStyle}"
|
Style="{StaticResource CaptionTextBlockStyle}"
|
||||||
Text="{x:Bind ViewModel.SelectedAsset.Path, Mode=OneWay}"
|
Text="{x:Bind ViewModel.SelectedAsset.FullName, Mode=OneWay}"
|
||||||
TextTrimming="CharacterEllipsis" />
|
TextTrimming="CharacterEllipsis" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -18,8 +18,8 @@ internal sealed partial class ProjectPage : Page
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GridViewItem_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
|
private async void GridViewItem_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ViewModel.NavigateToSelected();
|
await ViewModel.OpenSelected();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,13 +3,16 @@
|
|||||||
x:Class="Ghost.App.View.Windows.EngineEditorWindow"
|
x:Class="Ghost.App.View.Windows.EngineEditorWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors"
|
||||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:ee="using:Ghost.App.View.Pages.EngineEditor"
|
xmlns:ee="using:Ghost.App.View.Pages.EngineEditor"
|
||||||
|
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||||
xmlns:local="using:Ghost.App.View.Windows"
|
xmlns:local="using:Ghost.App.View.Windows"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:winex="using:WinUIEx"
|
xmlns:winex="using:WinUIEx"
|
||||||
Activated="WindowEx_Activated"
|
Activated="WindowEx_Activated"
|
||||||
|
Closed="WindowEx_Closed"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<Window.SystemBackdrop>
|
<Window.SystemBackdrop>
|
||||||
@@ -192,5 +195,50 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Grid.Row="0" Grid.RowSpan="4">
|
||||||
|
<InfoBar
|
||||||
|
x:Name="InfoBar"
|
||||||
|
Margin="16"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Bottom">
|
||||||
|
<interactivity:Interaction.Behaviors>
|
||||||
|
<behaviors:StackedNotificationsBehavior x:Name="NotificationQueue" />
|
||||||
|
</interactivity:Interaction.Behaviors>
|
||||||
|
</InfoBar>
|
||||||
|
|
||||||
|
<Grid
|
||||||
|
x:Name="ProgressBarContainer"
|
||||||
|
Background="{ThemeResource SmokeFillColorDefaultBrush}"
|
||||||
|
Visibility="Collapsed">
|
||||||
|
<Grid
|
||||||
|
Height="100"
|
||||||
|
Padding="36,24"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Background="{ThemeResource SolidBackgroundFillColorBaseBrush}"
|
||||||
|
CornerRadius="{StaticResource OverlayCornerRadius}">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
x:Name="ProgressMessage"
|
||||||
|
Grid.Row="0"
|
||||||
|
Margin="0,0,0,12"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Style="{StaticResource TitleTextBlockStyle}"
|
||||||
|
Text="Loading..." />
|
||||||
|
<ProgressBar
|
||||||
|
x:Name="ProgressBar"
|
||||||
|
Grid.Row="1"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
IsIndeterminate="True" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</winex:WindowEx>
|
</winex:WindowEx>
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
using Ghost.Data.Resources;
|
using Ghost.App.Services;
|
||||||
|
using Ghost.Data.Resources;
|
||||||
|
using Ghost.Editor.Services.Contracts;
|
||||||
using Ghost.Editor.ViewModels.Windows;
|
using Ghost.Editor.ViewModels.Windows;
|
||||||
using Ghost.Engine.Resources;
|
using Ghost.Engine.Resources;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using WinUIEx;
|
using WinUIEx;
|
||||||
|
|
||||||
// To learn more about WinUI, the WinUI project structure,
|
// To learn more about WinUI, the WinUI project structure,
|
||||||
@@ -12,6 +15,11 @@ namespace Ghost.App.View.Windows;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed partial class EngineEditorWindow : WindowEx
|
internal sealed partial class EngineEditorWindow : WindowEx
|
||||||
{
|
{
|
||||||
|
private IServiceScope? _editorScope;
|
||||||
|
|
||||||
|
private readonly NotificationService _notificationService;
|
||||||
|
private readonly ProgressService _progressService;
|
||||||
|
|
||||||
public EngineEditorViewModel ViewModel
|
public EngineEditorViewModel ViewModel
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
@@ -21,6 +29,9 @@ internal sealed partial class EngineEditorWindow : WindowEx
|
|||||||
{
|
{
|
||||||
ViewModel = GhostApplication.GetService<EngineEditorViewModel>();
|
ViewModel = GhostApplication.GetService<EngineEditorViewModel>();
|
||||||
|
|
||||||
|
_notificationService = (NotificationService)GhostApplication.GetService<INotificationService>();
|
||||||
|
_progressService = (ProgressService)GhostApplication.GetService<IProgressService>();
|
||||||
|
|
||||||
AppWindow.SetIcon(AssetsPath.s_appIconPath);
|
AppWindow.SetIcon(AssetsPath.s_appIconPath);
|
||||||
Title = EngineData.ENGINE_NAME;
|
Title = EngineData.ENGINE_NAME;
|
||||||
ExtendsContentIntoTitleBar = true;
|
ExtendsContentIntoTitleBar = true;
|
||||||
@@ -33,5 +44,19 @@ internal sealed partial class EngineEditorWindow : WindowEx
|
|||||||
private void WindowEx_Activated(object sender, Microsoft.UI.Xaml.WindowActivatedEventArgs args)
|
private void WindowEx_Activated(object sender, Microsoft.UI.Xaml.WindowActivatedEventArgs args)
|
||||||
{
|
{
|
||||||
Bindings.Update();
|
Bindings.Update();
|
||||||
|
|
||||||
|
_editorScope?.Dispose();
|
||||||
|
_editorScope = GhostApplication.CreateScope();
|
||||||
|
|
||||||
|
_notificationService.SetReference(InfoBar, NotificationQueue);
|
||||||
|
_progressService.SetReference(ProgressBarContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WindowEx_Closed(object sender, Microsoft.UI.Xaml.WindowEventArgs args)
|
||||||
|
{
|
||||||
|
_editorScope?.Dispose();
|
||||||
|
|
||||||
|
_notificationService.ClearReference();
|
||||||
|
_progressService.ClearReference();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,6 @@
|
|||||||
<behaviors:StackedNotificationsBehavior x:Name="NotificationQueue" />
|
<behaviors:StackedNotificationsBehavior x:Name="NotificationQueue" />
|
||||||
</interactivity:Interaction.Behaviors>
|
</interactivity:Interaction.Behaviors>
|
||||||
</InfoBar>
|
</InfoBar>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</winex:WindowEx>
|
</winex:WindowEx>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Ghost.App.Services;
|
using Ghost.App.Services;
|
||||||
using Ghost.App.View.Pages.Landing;
|
using Ghost.App.View.Pages.Landing;
|
||||||
using Ghost.Data.Resources;
|
using Ghost.Data.Resources;
|
||||||
|
using Ghost.Editor.Services.Contracts;
|
||||||
using Ghost.Engine.Resources;
|
using Ghost.Engine.Resources;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
@@ -13,10 +14,14 @@ internal sealed partial class LandingWindow : WindowEx
|
|||||||
{
|
{
|
||||||
private IServiceScope? _landingScope;
|
private IServiceScope? _landingScope;
|
||||||
|
|
||||||
|
private readonly NotificationService _notificationService;
|
||||||
|
|
||||||
private int _previousSelectedIndex;
|
private int _previousSelectedIndex;
|
||||||
|
|
||||||
public LandingWindow()
|
public LandingWindow()
|
||||||
{
|
{
|
||||||
|
_notificationService = (NotificationService)GhostApplication.GetService<INotificationService>();
|
||||||
|
|
||||||
AppWindow.SetIcon(AssetsPath.s_appIconPath);
|
AppWindow.SetIcon(AssetsPath.s_appIconPath);
|
||||||
Title = EngineData.ENGINE_NAME;
|
Title = EngineData.ENGINE_NAME;
|
||||||
|
|
||||||
@@ -32,13 +37,13 @@ internal sealed partial class LandingWindow : WindowEx
|
|||||||
{
|
{
|
||||||
_landingScope?.Dispose();
|
_landingScope?.Dispose();
|
||||||
_landingScope = GhostApplication.CreateScope();
|
_landingScope = GhostApplication.CreateScope();
|
||||||
GhostApplication.GetService<StackedNotificationService>().SetReference(InfoBar, NotificationQueue);
|
_notificationService.SetReference(InfoBar, NotificationQueue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WindowEx_Closed(object sender, Microsoft.UI.Xaml.WindowEventArgs args)
|
private void WindowEx_Closed(object sender, Microsoft.UI.Xaml.WindowEventArgs args)
|
||||||
{
|
{
|
||||||
_landingScope?.Dispose();
|
_landingScope?.Dispose();
|
||||||
GhostApplication.GetService<StackedNotificationService>().ClearReference();
|
_notificationService.ClearReference();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SelectorBar_SelectionChanged(SelectorBar sender, SelectorBarSelectionChangedEventArgs e)
|
private void SelectorBar_SelectionChanged(SelectorBar sender, SelectorBarSelectionChangedEventArgs e)
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Ghost.Editor.SceneGraph;
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace Ghost.Editor.ViewModels.Pages.EngineEditor;
|
||||||
|
|
||||||
|
internal partial class HierarchyViewModel : ObservableObject, IDisposable
|
||||||
|
{
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial ObservableCollection<WorldNode> SceneList
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
} = new(EditorWorldManager.LoadedWorlds);
|
||||||
|
|
||||||
|
public HierarchyViewModel()
|
||||||
|
{
|
||||||
|
EditorWorldManager.OnWorldLoaded += OnWorldLoaded;
|
||||||
|
EditorWorldManager.OnWorldUnloaded += OnWorldUnloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWorldLoaded(WorldNode node)
|
||||||
|
{
|
||||||
|
SceneList.Add(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWorldUnloaded(WorldNode node)
|
||||||
|
{
|
||||||
|
SceneList.Remove(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
EditorWorldManager.OnWorldLoaded -= OnWorldLoaded;
|
||||||
|
EditorWorldManager.OnWorldUnloaded -= OnWorldUnloaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
using Ghost.App;
|
using Ghost.App;
|
||||||
using Ghost.App.Models;
|
using Ghost.App.Models;
|
||||||
using Ghost.Data.Services;
|
using Ghost.Data.Services;
|
||||||
|
using Ghost.Editor.AssetHandle;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
@@ -51,9 +52,9 @@ internal partial class ProjectViewModel : ObservableObject
|
|||||||
SubDirectories.Add(assetsRootItem);
|
SubDirectories.Add(assetsRootItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadSubFolderRecursive(ref ExplorerItem parentItem)
|
private static void LoadSubFolderRecursive(ref ExplorerItem parentItem)
|
||||||
{
|
{
|
||||||
foreach (var directory in Directory.EnumerateDirectories(parentItem.Path))
|
foreach (var directory in Directory.EnumerateDirectories(parentItem.FullName))
|
||||||
{
|
{
|
||||||
var item = new ExplorerItem(Path.GetFileName(directory), directory, true);
|
var item = new ExplorerItem(Path.GetFileName(directory), directory, true);
|
||||||
LoadSubFolderRecursive(ref item);
|
LoadSubFolderRecursive(ref item);
|
||||||
@@ -116,28 +117,35 @@ internal partial class ProjectViewModel : ObservableObject
|
|||||||
DirectoryAssets.Add(fileItem);
|
DirectoryAssets.Add(fileItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectedDirectory = await FindNodeIterative(SubDirectories[0], x => x.Path == path);
|
SelectedDirectory = await FindNodeIterative(SubDirectories[0], x => x.FullName == path);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void NavigateToSelected()
|
public async Task OpenSelected()
|
||||||
{
|
{
|
||||||
if (SelectedAsset == null || !SelectedAsset.IsDirectory)
|
if (SelectedAsset == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigateToDirectory(SelectedAsset.Path);
|
if (SelectedAsset.IsDirectory)
|
||||||
|
{
|
||||||
|
NavigateToDirectory(SelectedAsset.FullName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await AssetDatabase.OpenAsset(SelectedAsset.FullName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnSelectedDirectoryChanged(ExplorerItem? value)
|
partial void OnSelectedDirectoryChanged(ExplorerItem? value)
|
||||||
{
|
{
|
||||||
DirectoryAssets.Clear();
|
|
||||||
if (value == null)
|
if (value == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigateToDirectory(value.Path);
|
DirectoryAssets.Clear();
|
||||||
|
NavigateToDirectory(value.FullName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,8 +6,8 @@ using Ghost.App.Services;
|
|||||||
using Ghost.App.Utilities;
|
using Ghost.App.Utilities;
|
||||||
using Ghost.Data.Models;
|
using Ghost.Data.Models;
|
||||||
using Ghost.Data.Services;
|
using Ghost.Data.Services;
|
||||||
|
using Ghost.Editor.Models;
|
||||||
using Ghost.Engine.Resources;
|
using Ghost.Engine.Resources;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -15,7 +15,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Ghost.Editor.ViewModels.Pages.Landing;
|
namespace Ghost.Editor.ViewModels.Pages.Landing;
|
||||||
|
|
||||||
internal partial class CreateProjectViewModel(StackedNotificationService notificationService, ProjectService projectService, AppStateMachine stateService) : ObservableObject, INavigationAware
|
internal partial class CreateProjectViewModel(NotificationService notificationService, ProjectService projectService, AppStateMachine stateService) : ObservableObject, INavigationAware
|
||||||
{
|
{
|
||||||
public ObservableCollection<TemplateData> templates = new();
|
public ObservableCollection<TemplateData> templates = new();
|
||||||
|
|
||||||
@@ -72,14 +72,14 @@ internal partial class CreateProjectViewModel(StackedNotificationService notific
|
|||||||
|| !Directory.Exists(ProjectLocation)
|
|| !Directory.Exists(ProjectLocation)
|
||||||
|| !SelectedTemplate.HasValue)
|
|| !SelectedTemplate.HasValue)
|
||||||
{
|
{
|
||||||
notificationService.ShowNotification("Incorrect project info", InfoBarSeverity.Error);
|
notificationService.ShowNotification("Incorrect project info", MessageType.Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await projectService.CreateProjectAsync(ProjectName, ProjectLocation, EngineData.s_engineVersion, SelectedTemplate.Value.directory);
|
var result = await projectService.CreateProjectAsync(ProjectName, ProjectLocation, EngineData.s_engineVersion, SelectedTemplate.Value.directory);
|
||||||
if (!result.success)
|
if (!result.success)
|
||||||
{
|
{
|
||||||
notificationService.ShowNotification(result.message, InfoBarSeverity.Error);
|
notificationService.ShowNotification(result.message, MessageType.Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ internal partial class CreateProjectViewModel(StackedNotificationService notific
|
|||||||
}
|
}
|
||||||
catch (System.Exception e)
|
catch (System.Exception e)
|
||||||
{
|
{
|
||||||
notificationService.ShowNotification($"Failed to load project: {e.Message}", InfoBarSeverity.Error);
|
notificationService.ShowNotification($"Failed to load project: {e.Message}", MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using Ghost.App.Contracts;
|
using Ghost.App.Contracts;
|
||||||
using Ghost.App.Infrastructures.AppState;
|
using Ghost.App.Infrastructures.AppState;
|
||||||
using Ghost.App.Services;
|
|
||||||
using Ghost.Data.Models;
|
using Ghost.Data.Models;
|
||||||
using Ghost.Data.Services;
|
using Ghost.Data.Services;
|
||||||
|
using Ghost.Editor.Models;
|
||||||
|
using Ghost.Editor.Services.Contracts;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -15,7 +15,7 @@ using Windows.Storage;
|
|||||||
|
|
||||||
namespace Ghost.Editor.ViewModels.Pages.Landing;
|
namespace Ghost.Editor.ViewModels.Pages.Landing;
|
||||||
|
|
||||||
internal partial class OpenProjectViewModel(ProjectService projectService, StackedNotificationService _notificationService, AppStateMachine _stateService) : ObservableObject, INavigationAware
|
internal partial class OpenProjectViewModel(ProjectService projectService, INotificationService _notificationService, AppStateMachine _stateService) : ObservableObject, INavigationAware
|
||||||
{
|
{
|
||||||
public readonly ObservableCollection<ProjectMetadataInfo> projects = new();
|
public readonly ObservableCollection<ProjectMetadataInfo> projects = new();
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ internal partial class OpenProjectViewModel(ProjectService projectService, Stack
|
|||||||
errorMessage = "Unsupported data format. Please drop a folder containing a project.";
|
errorMessage = "Unsupported data format. Please drop a folder containing a project.";
|
||||||
}
|
}
|
||||||
|
|
||||||
_notificationService.ShowNotification(errorMessage, InfoBarSeverity.Error);
|
_notificationService.ShowNotification(errorMessage, MessageType.Error);
|
||||||
|
|
||||||
CloseDropPanel:
|
CloseDropPanel:
|
||||||
DragVisibility = Visibility.Collapsed;
|
DragVisibility = Visibility.Collapsed;
|
||||||
@@ -104,7 +104,7 @@ internal partial class OpenProjectViewModel(ProjectService projectService, Stack
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_notificationService.ShowNotification($"Failed to load project: {e.Message}", InfoBarSeverity.Error);
|
_notificationService.ShowNotification($"Failed to load project: {e.Message}", MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
global using EntityID = System.UInt32;
|
|
||||||
global using GenerationID = System.UInt16;
|
|
||||||
global using WorldID = System.UInt16;
|
|
||||||
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("Ghost.Engine")]
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
using Misaki.HighPerformance.Unsafe.Collections;
|
|
||||||
using Misaki.HighPerformance.Unsafe.Helpers;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
|
||||||
|
|
||||||
internal unsafe struct ChunkCollection : IDisposable
|
|
||||||
{
|
|
||||||
private UnsafeArray<Chunk> _chunkStorage;
|
|
||||||
private int _count;
|
|
||||||
private int _capacity;
|
|
||||||
|
|
||||||
public readonly int Count => _count;
|
|
||||||
public readonly int Capacity => _capacity;
|
|
||||||
|
|
||||||
public readonly ref Chunk this[int index] => ref _chunkStorage[index];
|
|
||||||
|
|
||||||
public ChunkCollection(int capacity)
|
|
||||||
{
|
|
||||||
_chunkStorage = new(capacity, Allocator.Persistent);
|
|
||||||
_count = 0;
|
|
||||||
_capacity = capacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(Chunk chunk)
|
|
||||||
{
|
|
||||||
_chunkStorage[_count] = chunk;
|
|
||||||
_count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EnsureCapacity(int newCapacity)
|
|
||||||
{
|
|
||||||
if (newCapacity <= _capacity)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_chunkStorage.Resize(newCapacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void TrimExcess()
|
|
||||||
{
|
|
||||||
if (_count == _capacity)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_chunkStorage.Resize(_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
for (var i = 0; i < _count; i++)
|
|
||||||
{
|
|
||||||
_chunkStorage[i].Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
_count = 0;
|
|
||||||
_capacity = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public readonly Span<Chunk> AsSpan()
|
|
||||||
{
|
|
||||||
return _chunkStorage.AsSpan();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
for (var i = 0; i < _count; i++)
|
|
||||||
{
|
|
||||||
_chunkStorage[i].Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_chunkStorage.Dispose();
|
|
||||||
_count = 0;
|
|
||||||
_capacity = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal struct Chunk : IDisposable
|
|
||||||
{
|
|
||||||
public UnsafeArray<Entity> entities;
|
|
||||||
public UnsafeArray<UnsafeArray<byte>> components;
|
|
||||||
|
|
||||||
// The component lookup array is used to quickly find the index of a component in the components array.
|
|
||||||
// Mapping component ID to component index in the components array.
|
|
||||||
private UnsafeArray<int> _componentLookup;
|
|
||||||
|
|
||||||
private int _count;
|
|
||||||
private readonly int _capacity;
|
|
||||||
private bool _isDisposed;
|
|
||||||
|
|
||||||
public readonly int Count => _count;
|
|
||||||
public readonly int Capacity => _capacity;
|
|
||||||
|
|
||||||
public Chunk(int capacity, Span<ComponentData> data) : this(capacity, data, Component.ToLookupArray(data, Allocator.Persistent))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public Chunk(int capacity, Span<ComponentData> data, UnsafeArray<int> lookup)
|
|
||||||
{
|
|
||||||
_count = 0;
|
|
||||||
_capacity = capacity;
|
|
||||||
|
|
||||||
entities = new((int)capacity, Allocator.Persistent);
|
|
||||||
components = new(data.Length, Allocator.Persistent);
|
|
||||||
|
|
||||||
_componentLookup = lookup;
|
|
||||||
|
|
||||||
for (var i = 0; i < data.Length; i++)
|
|
||||||
{
|
|
||||||
var component = data[i];
|
|
||||||
components[component.id] = new UnsafeArray<byte>(capacity * (int)component.sizeInByte, Allocator.Persistent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Add(Entity entity)
|
|
||||||
{
|
|
||||||
var index = _count;
|
|
||||||
entities[index] = entity;
|
|
||||||
_count++;
|
|
||||||
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe bool Remove(int index)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= _count)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastIndex = _count--;
|
|
||||||
entities[index] = entities[lastIndex];
|
|
||||||
|
|
||||||
for (var i = 0; i < components.Count; i++)
|
|
||||||
{
|
|
||||||
var componentArray = UnsafeUtilities.ReadArrayElementUnsafe<UnsafeArray<byte>>(components.GetUnsafePtr(), i);
|
|
||||||
var componentSize = componentArray->Count / _capacity;
|
|
||||||
var removedComponent = UnsafeUtilities.ReadArrayElementUnsafe<byte>(componentArray->GetUnsafePtr(), index * componentSize);
|
|
||||||
var lastComponent = UnsafeUtilities.ReadArrayElementUnsafe<byte>(componentArray->GetUnsafePtr(), lastIndex * componentSize);
|
|
||||||
MemoryUtilities.MemCpy(removedComponent, lastComponent, (nuint)componentSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private readonly int IndexOf<T>()
|
|
||||||
where T : unmanaged, IComponent
|
|
||||||
{
|
|
||||||
var id = Component<T>.data.id;
|
|
||||||
Debug.Assert(id != -1 && id < _componentLookup.Count, $"Index is out of bounds, component {typeof(T)} with id {id} does not exist in this chunk.");
|
|
||||||
return _componentLookup[id];
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public readonly bool Has<T>()
|
|
||||||
where T : unmanaged, IComponent
|
|
||||||
{
|
|
||||||
var id = Component<T>.data.id;
|
|
||||||
return id < _componentLookup.Count && _componentLookup[id] != -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public readonly unsafe UnsafeArray<T> GetArrayOf<T>()
|
|
||||||
where T : unmanaged, IComponent
|
|
||||||
{
|
|
||||||
var index = IndexOf<T>();
|
|
||||||
var componentArray = components[index];
|
|
||||||
return UnsafeUtilities.CastArray<byte, T>(componentArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public readonly unsafe ref T GetComponent<T>(int index)
|
|
||||||
where T : unmanaged, IComponent
|
|
||||||
{
|
|
||||||
var componentArray = GetArrayOf<T>();
|
|
||||||
return ref componentArray[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public readonly Entity GetEntity(int index)
|
|
||||||
{
|
|
||||||
return entities[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
_count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_isDisposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
entities.Dispose();
|
|
||||||
_componentLookup.Dispose();
|
|
||||||
|
|
||||||
for (var i = 0; i < components.Count; i++)
|
|
||||||
{
|
|
||||||
components[i].Dispose();
|
|
||||||
}
|
|
||||||
components.Dispose();
|
|
||||||
|
|
||||||
_isDisposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
using Ghost.Entities.Helpers;
|
|
||||||
using Ghost.Entities.Registries;
|
|
||||||
using Misaki.HighPerformance.Unsafe.Collections;
|
|
||||||
using Misaki.HighPerformance.Unsafe.Helpers;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
|
||||||
|
|
||||||
public interface IComponent
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[SkipLocalsInit]
|
|
||||||
internal readonly record struct ComponentData
|
|
||||||
{
|
|
||||||
public readonly int id;
|
|
||||||
public readonly int sizeInByte;
|
|
||||||
|
|
||||||
public ComponentData(int id, int sizeInByte)
|
|
||||||
{
|
|
||||||
this.id = id;
|
|
||||||
this.sizeInByte = sizeInByte;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static class Component
|
|
||||||
{
|
|
||||||
internal static int GetTotalSize(in Span<ComponentData> datas)
|
|
||||||
{
|
|
||||||
var size = 0;
|
|
||||||
foreach (var type in datas)
|
|
||||||
{
|
|
||||||
var typeSize = type.sizeInByte;
|
|
||||||
typeSize = typeSize != 1 ? typeSize : 0; // Ignore tag components
|
|
||||||
size += typeSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static unsafe UnsafeArray<int> ToLookupArray(in Span<ComponentData> datas, Allocator allocator)
|
|
||||||
{
|
|
||||||
var max = 0;
|
|
||||||
foreach (var data in datas)
|
|
||||||
{
|
|
||||||
var componentId = data.id;
|
|
||||||
if (componentId >= max)
|
|
||||||
{
|
|
||||||
max = componentId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create lookup table where the component ID points to the component index.
|
|
||||||
var array = new UnsafeArray<int>(max + 1, allocator);
|
|
||||||
array.AsSpan().Fill(-1);
|
|
||||||
|
|
||||||
for (var index = 0; index < datas.Length; index++)
|
|
||||||
{
|
|
||||||
ref var type = ref datas[index];
|
|
||||||
var componentId = type.id;
|
|
||||||
array[componentId] = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static int GetHashCode(Span<ComponentData> components)
|
|
||||||
{
|
|
||||||
// Search for the highest id to determine how much uints we need for the stack.
|
|
||||||
var highestId = 0;
|
|
||||||
foreach (ref var cmp in components)
|
|
||||||
{
|
|
||||||
if (cmp.id > highestId)
|
|
||||||
{
|
|
||||||
highestId = cmp.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate the stack and set bits to replicate a bitset
|
|
||||||
var length = BitSet.RequiredLength(highestId + 1);
|
|
||||||
Span<uint> stack = stackalloc uint[length];
|
|
||||||
var spanBitSet = new SpanBitSet(stack);
|
|
||||||
|
|
||||||
foreach (ref var type in components)
|
|
||||||
{
|
|
||||||
var x = type.id;
|
|
||||||
spanBitSet.SetBit(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetHashCode(stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int GetHashCode(Span<uint> span)
|
|
||||||
{
|
|
||||||
var hashCode = new HashCode();
|
|
||||||
hashCode.AddBytes(MemoryMarshal.AsBytes(span));
|
|
||||||
|
|
||||||
return hashCode.ToHashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static class Component<T>
|
|
||||||
where T : unmanaged, IComponent
|
|
||||||
{
|
|
||||||
public static readonly ComponentData data;
|
|
||||||
|
|
||||||
public static readonly Signature signature;
|
|
||||||
|
|
||||||
static Component()
|
|
||||||
{
|
|
||||||
data = ComponentRegistry.GetOrAdd<T>();
|
|
||||||
signature = new Signature(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using Misaki.HighPerformance.Unsafe.Collections;
|
|
||||||
|
|
||||||
namespace Ghost.Entities.Core;
|
|
||||||
internal partial struct Archetype : IEquatable<Archetype>
|
|
||||||
{
|
|
||||||
public void AddInsertionEdge(Archetype archetype)
|
|
||||||
{
|
|
||||||
if (_insertionEdges.IsCreated)
|
|
||||||
{
|
|
||||||
_insertionEdges = new UnsafeHashSet<Archetype>(_BUCKET_SIZE, Allocator.Persistent);
|
|
||||||
}
|
|
||||||
_insertionEdges.Add(archetype);
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly bool Equals(Archetype other)
|
|
||||||
{
|
|
||||||
return signature.Equals(other.signature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
using Ghost.Entities.Helpers;
|
|
||||||
using Misaki.HighPerformance.Unsafe.Collections;
|
|
||||||
using Misaki.HighPerformance.Unsafe.Helpers;
|
|
||||||
|
|
||||||
namespace Ghost.Entities.Core;
|
|
||||||
|
|
||||||
internal partial struct Archetype : IDisposable
|
|
||||||
{
|
|
||||||
private const int _BUCKET_SIZE = 16;
|
|
||||||
|
|
||||||
// The component ID to array index lookup array.
|
|
||||||
private UnsafeArray<int> _lookupArray;
|
|
||||||
|
|
||||||
public readonly int sizeOfBaseChunk;
|
|
||||||
public readonly int sizeOfChunk;
|
|
||||||
|
|
||||||
public readonly int entitiesPerChunk;
|
|
||||||
public int totalEntityCount;
|
|
||||||
|
|
||||||
public Signature signature;
|
|
||||||
// For fast lookups
|
|
||||||
public BitSet bitSet;
|
|
||||||
public ChunkCollection chunks;
|
|
||||||
|
|
||||||
|
|
||||||
private UnsafeHashSet<Archetype> _insertionEdges;
|
|
||||||
private UnsafeHashSet<Archetype> _deletionEdges;
|
|
||||||
|
|
||||||
public readonly UnsafeArray<int> LookupArray => _lookupArray;
|
|
||||||
|
|
||||||
internal Archetype(Signature signature, int sizeOfBaseChunk, int minimalEntityCountPerChunk)
|
|
||||||
{
|
|
||||||
this.signature = signature;
|
|
||||||
this.sizeOfBaseChunk = sizeOfBaseChunk;
|
|
||||||
|
|
||||||
var sizeOfTotalData = Component.GetTotalSize(signature.componentDatas.AsSpan());
|
|
||||||
sizeOfChunk = GetSizeOfChunk(sizeOfBaseChunk, minimalEntityCountPerChunk, sizeOfTotalData);
|
|
||||||
entitiesPerChunk = GetEntityCount(sizeOfChunk, sizeOfTotalData);
|
|
||||||
|
|
||||||
_lookupArray = Component.ToLookupArray(signature.componentDatas.AsSpan(), Allocator.Persistent);
|
|
||||||
bitSet = new BitSet();
|
|
||||||
for (var i = 0; i < signature.componentDatas.Count; i++)
|
|
||||||
{
|
|
||||||
bitSet.SetBit(signature.componentDatas[i].id);
|
|
||||||
}
|
|
||||||
|
|
||||||
chunks = new ChunkCollection(1);
|
|
||||||
AddNewChunk();
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe static int GetEntityCount(int sizeOfChunk, int sizeOfTotalData)
|
|
||||||
{
|
|
||||||
return sizeOfChunk / (sizeof(Entity) + sizeOfTotalData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static unsafe int GetSizeOfChunk(int sizeOfBaseChunk, int entityCount, int sizeOfTotalData)
|
|
||||||
{
|
|
||||||
var entityBytes = (sizeof(Entity) + sizeOfTotalData) * entityCount;
|
|
||||||
return (int)Math.Ceiling((float)entityBytes / sizeOfBaseChunk) * sizeOfBaseChunk; // Calculates and rounds to a multiple of BaseSize to store the number of entities
|
|
||||||
}
|
|
||||||
|
|
||||||
public ref Chunk AddNewChunk()
|
|
||||||
{
|
|
||||||
chunks.EnsureCapacity(chunks.Count + 1);
|
|
||||||
chunks.Add(new Chunk(entitiesPerChunk, signature.componentDatas.AsSpan(), _lookupArray));
|
|
||||||
return ref chunks[^1];
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_lookupArray.Dispose();
|
|
||||||
signature.Dispose();
|
|
||||||
bitSet.Dispose();
|
|
||||||
chunks.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace Ghost.Entities.Core;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="Slot"/> struct references an <see cref="Entity"/> entry within an <see cref="Archetype"/> using a reference to its <see cref="Chunk"/> and its index.
|
|
||||||
/// </summary>
|
|
||||||
[SkipLocalsInit]
|
|
||||||
internal record struct Slot
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The index of the <see cref="Entity"/> in the <see cref="Chunk"/>.
|
|
||||||
/// </summary>
|
|
||||||
public int index;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The index of the <see cref="Chunk"/> in which the <see cref="Entity"/> is located.
|
|
||||||
/// </summary>
|
|
||||||
public int chunkIndex;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="Slot"/> struct.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">The index of the <see cref="Entity"/> in the <see cref="Chunk"/>.</param>
|
|
||||||
/// <param name="chunkIndex">The index of the <see cref="Chunk"/> in which the <see cref="Entity"/> is located.</param>
|
|
||||||
public Slot(int index, int chunkIndex)
|
|
||||||
{
|
|
||||||
this.index = index;
|
|
||||||
this.chunkIndex = chunkIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a plus operator for easy calculation of new <see cref="Slot"/>. Adds the positions of both <see cref="Slot"/>s.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="first">The first <see cref="Slot"/>.</param>
|
|
||||||
/// <param name="second">The second <see cref="Slot"/>.</param>
|
|
||||||
/// <returns>The result <see cref="Slot"/>.</returns>
|
|
||||||
public static Slot operator +(Slot first, Slot second)
|
|
||||||
{
|
|
||||||
return new Slot(first.index + second.index, first.chunkIndex + second.chunkIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a plus plus operator for easy calculation of new <see cref="Slot"/>. Increases the index by one.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="slot">The <see cref="Slot"/>.</param>
|
|
||||||
/// <returns>The <see cref="Slot"/> with index increased by one..</returns>
|
|
||||||
public static Slot operator ++(Slot slot)
|
|
||||||
{
|
|
||||||
slot.index++;
|
|
||||||
return slot;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Validates the <see cref="Slot"/>, moves the <see cref="Slot"/> if it is outside a <see cref="Chunk.Capacity"/> to match it.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public void Wrap(int capacity)
|
|
||||||
{
|
|
||||||
// Result outside valid chunk, wrap into next one
|
|
||||||
if (index < capacity)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Index outside of its chunk, so we calculate how many times a chunk fit into the index for adjusting the chunkindex to that position.
|
|
||||||
// Floor since we do not need a rounded value since the index is within that chunk and not the next one.
|
|
||||||
chunkIndex += (int)Math.Floor(index / (float)capacity);
|
|
||||||
|
|
||||||
// After moving the chunk index we can simply take the rest and assign it as a index.
|
|
||||||
index %= capacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Moves or shifts this <see cref="Slot"/> by one slot forward.
|
|
||||||
/// Ensures that the slots chunkindex updated properly once the end was reached.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">The <see cref="Slot"/> to shift by one.</param>
|
|
||||||
/// <param name="sourceCapacity">The capacity of the chunk the slot is in.</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static Slot Shift(ref Slot source, int sourceCapacity)
|
|
||||||
{
|
|
||||||
source.index++;
|
|
||||||
source.Wrap(sourceCapacity);
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Moves or shifts the source <see cref="Slot"/> based on the destination <see cref="Slot"/> and calculates its new position.
|
|
||||||
/// Used for copy operations to predict where the source <see cref="Slot"/> will end up.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">The source <see cref="Slot"/>, from which we want to calculate where it lands..</param>
|
|
||||||
/// <param name="destination">The destination <see cref="Slot"/>, a reference point at which the copy or shift operation starts.</param>
|
|
||||||
/// <param name="sourceCapacity">The source <see cref="Chunk.Capacity"/>.</param>
|
|
||||||
/// <param name="destinationCapacity">The destination <see cref="Chunk.Capacity"/></param>
|
|
||||||
public static Slot Shift(in Slot source, int sourceCapacity, in Slot destination, int destinationCapacity)
|
|
||||||
{
|
|
||||||
var freeSpot = destination;
|
|
||||||
var resultSlot = source + freeSpot;
|
|
||||||
resultSlot.index += source.chunkIndex * (sourceCapacity - destinationCapacity);
|
|
||||||
resultSlot.Wrap(destinationCapacity);
|
|
||||||
|
|
||||||
return resultSlot;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
|
||||||
|
|
||||||
[SkipLocalsInit]
|
|
||||||
public struct Entity : IEquatable<Entity>, IComparable<Entity>
|
|
||||||
{
|
|
||||||
private const EntityID _WORLD_INDEX_BITS = 4u;
|
|
||||||
private const EntityID _GENERATION_BITS = 8u;
|
|
||||||
private const EntityID _INDEX_BITS = sizeof(EntityID) * 8 - _WORLD_INDEX_BITS - _GENERATION_BITS;
|
|
||||||
|
|
||||||
private const EntityID _WORLD_INDEX_MASK = (1u << (int)_WORLD_INDEX_BITS) - 1;
|
|
||||||
private const EntityID _GENERATION_MASK = (1u << (int)_GENERATION_BITS) - 1;
|
|
||||||
private const EntityID _INDEX_MASK = (1u << (int)_INDEX_BITS) - 1;
|
|
||||||
private const EntityID _ID_MASK = EntityID.MaxValue;
|
|
||||||
|
|
||||||
private EntityID _id;
|
|
||||||
|
|
||||||
public readonly bool IsValid
|
|
||||||
{
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
get => _id != _ID_MASK;
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly EntityID Index
|
|
||||||
{
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
get => _id & _INDEX_MASK;
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly GenerationID Generation
|
|
||||||
{
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
get => (GenerationID)(_id >> (int)_INDEX_BITS & _GENERATION_MASK);
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly WorldID WorldIndex
|
|
||||||
{
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
get => (WorldID)(_id >> (int)(_INDEX_BITS + _GENERATION_BITS) & _WORLD_INDEX_MASK);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void IncrementGeneration()
|
|
||||||
{
|
|
||||||
var generation = Generation + 1u;
|
|
||||||
if (generation >= _GENERATION_MASK)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Generation overflow");
|
|
||||||
}
|
|
||||||
|
|
||||||
_id = _id & ~(_GENERATION_MASK << (int)_INDEX_BITS) | generation << (int)_INDEX_BITS;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal Entity(EntityID index, EntityID generation, EntityID worldIndex)
|
|
||||||
{
|
|
||||||
_id = worldIndex << (int)(_INDEX_BITS + _GENERATION_BITS) | generation << (int)_INDEX_BITS | index;
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly bool Equals(Entity other)
|
|
||||||
{
|
|
||||||
return _id == other._id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly int CompareTo(Entity other)
|
|
||||||
{
|
|
||||||
return _id.CompareTo(other._id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override readonly bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
return obj is Entity other && Equals(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override readonly int GetHashCode()
|
|
||||||
{
|
|
||||||
return _id.GetHashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator ==(Entity left, Entity right)
|
|
||||||
{
|
|
||||||
return left.Equals(right);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator !=(Entity left, Entity right)
|
|
||||||
{
|
|
||||||
return !(left == right);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override readonly string ToString()
|
|
||||||
{
|
|
||||||
return $"Entity {{ Index: {Index}, Generation: {Generation}, WorldIndex: {WorldIndex} }}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="Misaki.HighPerformance.Unsafe">
|
|
||||||
<HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,604 +0,0 @@
|
|||||||
// Code from https://github.com/genaray/Arch/blob/master/src/Arch/Core/Utils/BitSet.cs
|
|
||||||
|
|
||||||
using Misaki.HighPerformance.Unsafe.Collections;
|
|
||||||
using Misaki.HighPerformance.Unsafe.Helpers;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Ghost.Entities.Helpers;
|
|
||||||
|
|
||||||
// NOTE: Can this be replaced with `System.Collections.BitArray`?
|
|
||||||
// NOTE: If not, can it at least mirror that type's API?
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="BitSet"/> class
|
|
||||||
/// represents a resizable collection of bits.
|
|
||||||
/// </summary>
|
|
||||||
public struct BitSet : IDisposable
|
|
||||||
{
|
|
||||||
private const int _BIT_SIZE = (sizeof(uint) * 8) - 1; // 31
|
|
||||||
private const int _INDEX_SIZE = 5; // log_2(BitSize + 1)
|
|
||||||
|
|
||||||
private static readonly int _padding = Vector<uint>.Count; // The padding used for vectorisation, the amount of uints required for being vectorized basically
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines the required length of an <see cref="BitSet"/> to hold the passed id or bit.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">The id or bit.</param>
|
|
||||||
/// <returns>A size of required <see cref="uint"/>s for the bitset.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static int RequiredLength(int id)
|
|
||||||
{
|
|
||||||
return (id >> 5) + int.Sign(id & _BIT_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The bits from the bitset.
|
|
||||||
/// </summary>
|
|
||||||
private UnsafeArray<uint> _bits;
|
|
||||||
|
|
||||||
/// TODO: Update on ClearBit, however clearbit is only used in tests so its fine for now.
|
|
||||||
/// <summary>
|
|
||||||
/// The highest bit set.
|
|
||||||
/// </summary>
|
|
||||||
private int _highestBit;
|
|
||||||
|
|
||||||
/// TODO: Update on ClearBit, probably remove <see cref="_highestBit"/> in favor?
|
|
||||||
/// <summary>
|
|
||||||
/// The maximum <see cref="_bits"/>-index current in use.
|
|
||||||
/// </summary>
|
|
||||||
private int _max;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="BitSet" /> class.
|
|
||||||
/// </summary>
|
|
||||||
public BitSet()
|
|
||||||
{
|
|
||||||
_bits = new UnsafeArray<uint>(_padding, Allocator.Persistent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="BitSet" /> class.
|
|
||||||
/// </summary>
|
|
||||||
public BitSet(params Span<uint> bits)
|
|
||||||
{
|
|
||||||
_bits = new UnsafeArray<uint>(bits.Length, Allocator.Persistent);
|
|
||||||
_bits.CopyFrom(bits);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The highest uint index in use inside the <see cref="_bits"/>-array.
|
|
||||||
/// </summary>
|
|
||||||
public readonly int HighestIndex
|
|
||||||
{
|
|
||||||
get => _max;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The highest bit set.
|
|
||||||
/// </summary>
|
|
||||||
public readonly int HighestBit
|
|
||||||
{
|
|
||||||
get => _highestBit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the length of the bitset, how many int's it consists of.
|
|
||||||
/// </summary>
|
|
||||||
public readonly int Count
|
|
||||||
{
|
|
||||||
get => _bits.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks whether a bit is set at the index.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">The index.</param>
|
|
||||||
/// <returns>True if it is, otherwise false</returns>
|
|
||||||
public readonly bool IsSet(int index)
|
|
||||||
{
|
|
||||||
var b = index >> _INDEX_SIZE;
|
|
||||||
if (b >= _bits.Count)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (_bits[b] & (1 << (index & _BIT_SIZE))) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets a bit at the given index.
|
|
||||||
/// Resizes its internal array if necessary.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">The index.</param>
|
|
||||||
public void SetBit(int index)
|
|
||||||
{
|
|
||||||
var b = index >> _INDEX_SIZE;
|
|
||||||
if (b >= _bits.Count)
|
|
||||||
{
|
|
||||||
_bits.Resize((b + _padding) / _padding * _padding); // Round up to a multiply of Padding
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track highest set bit
|
|
||||||
_highestBit = Math.Max(_highestBit, index);
|
|
||||||
_max = (_highestBit / (_BIT_SIZE + 1)) + 1;
|
|
||||||
_bits[b] |= 1u << (index & _BIT_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears the bit at the given index.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">The index.</param>
|
|
||||||
public readonly void ClearBit(int index)
|
|
||||||
{
|
|
||||||
var b = index >> _INDEX_SIZE;
|
|
||||||
if (b >= _bits.Count)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_bits[b] &= ~(1u << (index & _BIT_SIZE));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets all bits.
|
|
||||||
/// </summary>
|
|
||||||
public void SetAll()
|
|
||||||
{
|
|
||||||
var count = _bits.Count;
|
|
||||||
for (var i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
_bits[i] = 0xffffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
_highestBit = (_bits.Count * (_BIT_SIZE + 1)) - 1;
|
|
||||||
_max = (_highestBit / (_BIT_SIZE + 1)) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears all set bits.
|
|
||||||
/// </summary>
|
|
||||||
public readonly void ClearAll()
|
|
||||||
{
|
|
||||||
_bits.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if all bits from this instance match those of the other instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="other">The other <see cref="BitSet"/>.</param>
|
|
||||||
/// <returns>True if they match, false if not.</returns>
|
|
||||||
[SkipLocalsInit]
|
|
||||||
public readonly bool All(BitSet other)
|
|
||||||
{
|
|
||||||
var min = Math.Min(Math.Min(Count, other.Count), _max);
|
|
||||||
if (!Vector.IsHardwareAccelerated || min < _padding)
|
|
||||||
{
|
|
||||||
// Bitwise and
|
|
||||||
for (var i = 0; i < min; i++)
|
|
||||||
{
|
|
||||||
var bit = _bits[i];
|
|
||||||
if ((bit & other._bits[i]) != bit)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle extra bits on our side that might just be all zero.
|
|
||||||
for (var i = min; i < _max; i++)
|
|
||||||
{
|
|
||||||
if (_bits[i] != 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Vectorized bitwise and
|
|
||||||
for (var i = 0; i < min; i += _padding)
|
|
||||||
{
|
|
||||||
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
|
|
||||||
var otherVector = new Vector<uint>(other._bits.AsSpan()[i..]);
|
|
||||||
|
|
||||||
var resultVector = Vector.BitwiseAnd(vector, otherVector);
|
|
||||||
if (!Vector.EqualsAll(resultVector, vector))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle extra bits on our side that might just be all zero.
|
|
||||||
for (var i = min; i < _max; i += _padding)
|
|
||||||
{
|
|
||||||
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
|
|
||||||
if (!Vector.EqualsAll(vector, Vector<uint>.Zero)) // Vectors are not zero bits[0] != 0 basically
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if any bits from this instance match those of the other instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="other">The other <see cref="BitSet"/>.</param>
|
|
||||||
/// <returns>True if they match, false if not.</returns>
|
|
||||||
public readonly bool Any(BitSet other)
|
|
||||||
{
|
|
||||||
var min = Math.Min(Math.Min(Count, other.Count), _max);
|
|
||||||
if (!Vector.IsHardwareAccelerated || min < _padding)
|
|
||||||
{
|
|
||||||
var bits = _bits.AsSpan();
|
|
||||||
var otherBits = other._bits.AsSpan();
|
|
||||||
|
|
||||||
// Bitwise and, return true since any is met
|
|
||||||
for (var i = 0; i < min; i++)
|
|
||||||
{
|
|
||||||
var bit = bits[i];
|
|
||||||
if ((bit & otherBits[i]) > 0)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle extra bits on our side that might just be all zero.
|
|
||||||
for (var i = min; i < _max; i++)
|
|
||||||
{
|
|
||||||
if (bits[i] > 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Vectorized bitwise and, return true since any is met
|
|
||||||
for (var i = 0; i < min; i += _padding)
|
|
||||||
{
|
|
||||||
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
|
|
||||||
var otherVector = new Vector<uint>(other._bits.AsSpan()[i..]);
|
|
||||||
|
|
||||||
var resultVector = Vector.BitwiseAnd(vector, otherVector);
|
|
||||||
if (!Vector.EqualsAll(resultVector, Vector<uint>.Zero))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle extra bits on our side that might just be all zero.
|
|
||||||
for (var i = min; i < _max; i += _padding)
|
|
||||||
{
|
|
||||||
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
|
|
||||||
if (!Vector.EqualsAll(vector, Vector<uint>.Zero)) // Vectors are not zero bits[0] != 0 basically
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return _highestBit <= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if none bits from this instance match those of the other instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="other">The other <see cref="BitSet"/>.</param>
|
|
||||||
/// <returns>True if none match, false if not.</returns>
|
|
||||||
public readonly bool None(BitSet other)
|
|
||||||
{
|
|
||||||
var min = Math.Min(Math.Min(Count, other.Count), _max);
|
|
||||||
if (!Vector.IsHardwareAccelerated || min < _padding)
|
|
||||||
{
|
|
||||||
var bits = _bits.AsSpan();
|
|
||||||
var otherBits = other._bits.AsSpan();
|
|
||||||
|
|
||||||
// Bitwise and, return true since any is met
|
|
||||||
for (var i = 0; i < min; i++)
|
|
||||||
{
|
|
||||||
var bit = bits[i];
|
|
||||||
if ((bit & otherBits[i]) != 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Vectorized bitwise and, return true since any is met
|
|
||||||
for (var i = 0; i < min; i += _padding)
|
|
||||||
{
|
|
||||||
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
|
|
||||||
var otherVector = new Vector<uint>(other._bits.AsSpan()[i..]);
|
|
||||||
|
|
||||||
var resultVector = Vector.BitwiseAnd(vector, otherVector);
|
|
||||||
if (!Vector.EqualsAll(resultVector, Vector<uint>.Zero))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if exactly all bits from this instance match those of the other instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="other">The other <see cref="BitSet"/>.</param>
|
|
||||||
/// <returns>True if they match, false if not.</returns>
|
|
||||||
public readonly bool Exclusive(BitSet other)
|
|
||||||
{
|
|
||||||
var min = Math.Min(Math.Min(Count, other.Count), _max);
|
|
||||||
|
|
||||||
if (!Vector.IsHardwareAccelerated || min < _padding)
|
|
||||||
{
|
|
||||||
var bits = _bits.AsSpan();
|
|
||||||
var otherBits = other._bits.AsSpan();
|
|
||||||
|
|
||||||
// Bitwise xor, if both are not totally equal, return false
|
|
||||||
for (var i = 0; i < min; i++)
|
|
||||||
{
|
|
||||||
var bit = bits[i];
|
|
||||||
if ((bit ^ otherBits[i]) != 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle extra bits on our side that might just be all zero
|
|
||||||
for (var i = min; i < _max; i++)
|
|
||||||
{
|
|
||||||
if (bits[i] != 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Vectorized bitwise xor, return true since any is met
|
|
||||||
for (var i = 0; i < min; i += _padding)
|
|
||||||
{
|
|
||||||
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
|
|
||||||
var otherVector = new Vector<uint>(other._bits.AsSpan()[i..]);
|
|
||||||
|
|
||||||
var resultVector = Vector.Xor(vector, otherVector);
|
|
||||||
if (!Vector.EqualsAll(resultVector, Vector<uint>.Zero))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle extra bits on our side that might just be all zero.
|
|
||||||
for (var i = min; i < _max; i += _padding)
|
|
||||||
{
|
|
||||||
var vector = new Vector<uint>(_bits.AsSpan()[i..]);
|
|
||||||
if (!Vector.EqualsAll(vector, Vector<uint>.Zero)) // Vectors are not zero bits[0] != 0 basically
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a <see cref="Span{T}"/> to access the <see cref="_bits"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The hash.</returns>
|
|
||||||
public readonly Span<uint> AsSpan()
|
|
||||||
{
|
|
||||||
var max = (_highestBit / (_BIT_SIZE + 1)) + 1;
|
|
||||||
return _bits.AsSpan()[0..max];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Copies the bits into a <see cref="Span{T}"/> and returns a slice containing the copied <see cref="_bits"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="span">The <see cref="Span{T}"/> to copy into.</param>
|
|
||||||
/// <param name="zero">If true, it will zero the unused space from the <see cref="span"/>.</param>
|
|
||||||
/// <returns>The <see cref="Span{T}"/>.</returns>
|
|
||||||
public readonly Span<uint> AsSpan(Span<uint> span, bool zero = true)
|
|
||||||
{
|
|
||||||
// Copy everything that's possible from one to another
|
|
||||||
var length = Math.Min(Count, span.Length);
|
|
||||||
for (var index = 0; index < length; index++)
|
|
||||||
{
|
|
||||||
span[index] = _bits[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zero the rest space which was not overriden due to the copy.
|
|
||||||
for (var index = length; zero && index < span.Length; index++)
|
|
||||||
{
|
|
||||||
span[index] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return span[0..length];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates the hash, this is unique for the set bits. Two <see cref="BitSet"/> with the same set bits, result in the same hash.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The hash.</returns>
|
|
||||||
public readonly override int GetHashCode()
|
|
||||||
{
|
|
||||||
return Component.GetHashCode(AsSpan());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Prints the content of this instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The string.</returns>
|
|
||||||
public readonly override string ToString()
|
|
||||||
{
|
|
||||||
// Convert uint to binary form for pretty printing
|
|
||||||
var binaryBuilder = new StringBuilder();
|
|
||||||
foreach (var bit in _bits)
|
|
||||||
{
|
|
||||||
binaryBuilder.Append(Convert.ToString((uint)bit, 2).PadLeft(32, '0')).Append(',');
|
|
||||||
}
|
|
||||||
binaryBuilder.Length--;
|
|
||||||
|
|
||||||
return $"{nameof(_bits)}: {binaryBuilder}, {nameof(Count)}: {Count}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_bits.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="SpanBitSet"/> struct
|
|
||||||
/// represents a non resizable collection of bits.
|
|
||||||
/// Used to set, check and clear bits on a allocated <see cref="BitSet"/> or on the stack.
|
|
||||||
/// </summary>
|
|
||||||
public readonly ref struct SpanBitSet
|
|
||||||
{
|
|
||||||
private const int _BIT_SIZE = (sizeof(uint) * 8) - 1; // 31
|
|
||||||
// NOTE: Is a byte not 8 bits?
|
|
||||||
private const int _BYTE_SIZE = 5; // log_2(BitSize + 1)
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The bits from the bitset.
|
|
||||||
/// </summary>
|
|
||||||
private readonly Span<uint> _bits;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="BitSet" /> class.
|
|
||||||
/// </summary>
|
|
||||||
public SpanBitSet(Span<uint> bits)
|
|
||||||
{
|
|
||||||
_bits = bits;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks whether a bit is set at the index.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">The index.</param>
|
|
||||||
/// <returns>True if it is, otherwise false</returns>
|
|
||||||
public bool IsSet(int index)
|
|
||||||
{
|
|
||||||
var b = index >> _BYTE_SIZE;
|
|
||||||
if (b >= _bits.Length)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (_bits[b] & (1 << (index & _BIT_SIZE))) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets a bit at the given index.
|
|
||||||
/// Resizes its internal array if necessary.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">The index.</param>
|
|
||||||
public void SetBit(int index)
|
|
||||||
{
|
|
||||||
var b = index >> _BYTE_SIZE;
|
|
||||||
if (b >= _bits.Length)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_bits[b] |= 1u << (index & _BIT_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears the bit at the given index.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">The index.</param>
|
|
||||||
public void ClearBit(int index)
|
|
||||||
{
|
|
||||||
var b = index >> _BYTE_SIZE;
|
|
||||||
if (b >= _bits.Length)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_bits[b] &= ~(1u << (index & _BIT_SIZE));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets all bits.
|
|
||||||
/// </summary>
|
|
||||||
public void SetAll()
|
|
||||||
{
|
|
||||||
var count = _bits.Length;
|
|
||||||
for (var i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
_bits[i] = 0xffffffff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears all set bits.
|
|
||||||
/// </summary>
|
|
||||||
public void ClearAll()
|
|
||||||
{
|
|
||||||
_bits.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a <see cref="Span{T}"/> to access the <see cref="_bits"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The hash.</returns>
|
|
||||||
public Span<uint> AsSpan()
|
|
||||||
{
|
|
||||||
return _bits;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Copies the bits into a <see cref="Span{T}"/> and returns a slice containing the copied <see cref="_bits"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name=""></param>
|
|
||||||
/// <returns>The hash.</returns>
|
|
||||||
public Span<uint> AsSpan(Span<uint> span, bool zero = true)
|
|
||||||
{
|
|
||||||
// Prevent exception because target array is to small for copy operation
|
|
||||||
var length = Math.Min(this._bits.Length, span.Length);
|
|
||||||
for (var index = 0; index < length; index++)
|
|
||||||
{
|
|
||||||
span[index] = _bits[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zero the rest space which was not overriden due to the copy.
|
|
||||||
for (var index = length; zero && index < span.Length; index++)
|
|
||||||
{
|
|
||||||
span[index] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return span[.._bits.Length];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates the hash, this is unique for the set bits. Two <see cref="BitSet"/> with the same set bits, result in the same hash.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The hash.</returns>
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return Component.GetHashCode(AsSpan());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Prints the content of this instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The string.</returns>
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
// Convert uint to binary form for pretty printing
|
|
||||||
var binaryBuilder = new StringBuilder();
|
|
||||||
foreach (var bit in _bits)
|
|
||||||
{
|
|
||||||
binaryBuilder.Append(Convert.ToString((uint)bit, 2).PadLeft(32, '0')).Append(',');
|
|
||||||
}
|
|
||||||
binaryBuilder.Length--;
|
|
||||||
|
|
||||||
return $"{nameof(_bits)}: {string.Join(",", binaryBuilder)}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace Ghost.Entities.Helpers;
|
|
||||||
|
|
||||||
internal static class ThreadLocker
|
|
||||||
{
|
|
||||||
private static Lock? _worldLock;
|
|
||||||
public static Lock WorldLock => _worldLock ??= new();
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
namespace Ghost.Entities.Registries;
|
|
||||||
|
|
||||||
internal static class ComponentRegistry
|
|
||||||
{
|
|
||||||
private static readonly Dictionary<Type, ComponentData> _hashCodeToComponentMap = new(64);
|
|
||||||
|
|
||||||
public static unsafe ComponentData GetOrAdd<T>()
|
|
||||||
where T : unmanaged, IComponent
|
|
||||||
{
|
|
||||||
var type = typeof(T);
|
|
||||||
if (_hashCodeToComponentMap.TryGetValue(type, out var data))
|
|
||||||
{
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
var id = (ushort)_hashCodeToComponentMap.Count;
|
|
||||||
data = new ComponentData(id, sizeof(T));
|
|
||||||
_hashCodeToComponentMap.Add(type, data);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Ghost.Entities.Services;
|
|
||||||
|
|
||||||
internal class EntityChangeQueue
|
|
||||||
{
|
|
||||||
// TODO: This class is not implemented yet.
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
using Misaki.HighPerformance.Unsafe.Collections;
|
|
||||||
using Misaki.HighPerformance.Unsafe.Helpers;
|
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
|
||||||
|
|
||||||
internal struct Signature : IDisposable, IEquatable<Signature>
|
|
||||||
{
|
|
||||||
public UnsafeArray<ComponentData> componentDatas;
|
|
||||||
private int _hashCode;
|
|
||||||
|
|
||||||
public Signature(params Span<ComponentData> components)
|
|
||||||
{
|
|
||||||
componentDatas = new UnsafeArray<ComponentData>(components.Length, Allocator.Persistent);
|
|
||||||
componentDatas.CopyFrom(components);
|
|
||||||
|
|
||||||
_hashCode = -1;
|
|
||||||
_hashCode = GetHashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(Signature other)
|
|
||||||
{
|
|
||||||
return GetHashCode() == other.GetHashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
if (obj is Signature other)
|
|
||||||
{
|
|
||||||
return Equals(other);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
if (_hashCode != -1)
|
|
||||||
{
|
|
||||||
return _hashCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
unchecked
|
|
||||||
{
|
|
||||||
_hashCode = Component.GetHashCode(componentDatas.AsSpan());
|
|
||||||
return _hashCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
componentDatas.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||