Refactor project structure and enhance functionality
Changed the project namespace from `Ghost.Editor` to `Ghost.App` across multiple files. Changed the `InternalsVisibleTo` attribute in `AssemblyInfo.cs` to include `Ghost.App`. Changed the `ProjectRepository` class to add new asynchronous methods for retrieving projects by ID, name, and metadata path. Changed the `ProjectService` class to utilize the new asynchronous project loading methods. Changed the `SceneGraph` classes to improve node management and serialization. Changed the `EntityManager` class to enhance entity management with new component handling methods. Added new test classes, `EntityTest` and `SerializationTest`, to ensure reliability in entity and serialization systems. Added the `Ghost.App` project file to establish a modular project structure. Added the `Ghost.Generator` project for automated component serialization code generation. Updated UI components to reflect the new namespace for proper functionality.
@@ -1,3 +1,3 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("Ghost.Editor")]
|
[assembly: InternalsVisibleTo("Ghost.App")]
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ internal static class ProjectRepository
|
|||||||
await createCommand.ExecuteNonQueryAsync();
|
await createCommand.ExecuteNonQueryAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async IAsyncEnumerable<ProjectInfo> LoadProjectsAsync()
|
public static async IAsyncEnumerable<ProjectInfo> GetAllProjectsAsync()
|
||||||
{
|
{
|
||||||
using var connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, DataPath.s_applicationDataFolder));
|
using var connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, DataPath.s_applicationDataFolder));
|
||||||
connection.Open();
|
connection.Open();
|
||||||
@@ -47,6 +47,85 @@ internal static class ProjectRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<ProjectInfo?> GetProjectByIdAsync(int id)
|
||||||
|
{
|
||||||
|
using var connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, DataPath.s_applicationDataFolder));
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
await EnsureTableCreatedAsync(connection);
|
||||||
|
|
||||||
|
using var command = connection.CreateCommand();
|
||||||
|
command.CommandText = Command.SELECT_PROJECT_STRING + " WHERE ID = @ID;";
|
||||||
|
|
||||||
|
command.Parameters.AddWithValue("@ID", id);
|
||||||
|
|
||||||
|
using var reader = await command.ExecuteReaderAsync();
|
||||||
|
|
||||||
|
if (await reader.ReadAsync())
|
||||||
|
{
|
||||||
|
return new ProjectInfo
|
||||||
|
{
|
||||||
|
ID = reader.GetInt32(0),
|
||||||
|
Name = reader.GetString(1),
|
||||||
|
MetadataPath = reader.GetString(2),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<ProjectInfo?> GetProjectByNameAsync(string name)
|
||||||
|
{
|
||||||
|
using var connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, DataPath.s_applicationDataFolder));
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
await EnsureTableCreatedAsync(connection);
|
||||||
|
|
||||||
|
using var command = connection.CreateCommand();
|
||||||
|
command.CommandText = Command.SELECT_PROJECT_STRING + " WHERE Name = @Name;";
|
||||||
|
|
||||||
|
command.Parameters.AddWithValue("@Name", name);
|
||||||
|
|
||||||
|
using var reader = await command.ExecuteReaderAsync();
|
||||||
|
if (await reader.ReadAsync())
|
||||||
|
{
|
||||||
|
return new ProjectInfo
|
||||||
|
{
|
||||||
|
ID = reader.GetInt32(0),
|
||||||
|
Name = reader.GetString(1),
|
||||||
|
MetadataPath = reader.GetString(2),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<ProjectInfo?> GetProjectByMetadataPathAsync(string metadataPath)
|
||||||
|
{
|
||||||
|
using var connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, DataPath.s_applicationDataFolder));
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
await EnsureTableCreatedAsync(connection);
|
||||||
|
|
||||||
|
using var command = connection.CreateCommand();
|
||||||
|
command.CommandText = Command.SELECT_PROJECT_STRING + " WHERE MetadataPath = @MetadataPath;";
|
||||||
|
|
||||||
|
command.Parameters.AddWithValue("@MetadataPath", metadataPath);
|
||||||
|
|
||||||
|
using var reader = await command.ExecuteReaderAsync();
|
||||||
|
if (await reader.ReadAsync())
|
||||||
|
{
|
||||||
|
return new ProjectInfo
|
||||||
|
{
|
||||||
|
ID = reader.GetInt32(0),
|
||||||
|
Name = reader.GetString(1),
|
||||||
|
MetadataPath = reader.GetString(2),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task AddProjectAsync(ProjectInfo project)
|
public static async Task AddProjectAsync(ProjectInfo project)
|
||||||
{
|
{
|
||||||
using var connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, DataPath.s_applicationDataFolder));
|
using var connection = new SQLiteConnection(string.Format(Command.CONNECTION_STRING, DataPath.s_applicationDataFolder));
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ internal partial class ProjectService
|
|||||||
public const string ASSETS_FOLDER = "Assets";
|
public const string ASSETS_FOLDER = "Assets";
|
||||||
public const string CONFIG_FOLDER = "ProjectConfig";
|
public const string CONFIG_FOLDER = "ProjectConfig";
|
||||||
|
|
||||||
|
public static ProjectMetadataInfo CurrentProject
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
public static void EnsureDefaultTemplate()
|
public static void EnsureDefaultTemplate()
|
||||||
{
|
{
|
||||||
var templates = Directory.GetFiles(DataPath.s_projectTemplateFolder, "template.json", SearchOption.AllDirectories);
|
var templates = Directory.GetFiles(DataPath.s_projectTemplateFolder, "template.json", SearchOption.AllDirectories);
|
||||||
@@ -114,12 +120,6 @@ internal partial class ProjectService
|
|||||||
|
|
||||||
internal partial class ProjectService
|
internal partial class ProjectService
|
||||||
{
|
{
|
||||||
public static ProjectMetadataInfo CurrentProject
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task AddProjectAsync(ProjectInfo project)
|
public Task AddProjectAsync(ProjectInfo project)
|
||||||
{
|
{
|
||||||
return ProjectRepository.AddProjectAsync(project);
|
return ProjectRepository.AddProjectAsync(project);
|
||||||
@@ -147,9 +147,14 @@ internal partial class ProjectService
|
|||||||
return ProjectRepository.UpdateProjectAsync(project);
|
return ProjectRepository.UpdateProjectAsync(project);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAsyncEnumerable<ProjectInfo> LoadAllProjectAsync()
|
public async Task<bool> HasProjectAsync(string path)
|
||||||
{
|
{
|
||||||
return ProjectRepository.LoadProjectsAsync();
|
return await ProjectRepository.GetProjectByMetadataPathAsync(path) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IAsyncEnumerable<ProjectInfo> GetAllProjectAsync()
|
||||||
|
{
|
||||||
|
return ProjectRepository.GetAllProjectsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<ProjectMetadataInfo>> CreateProjectAsync(string projectName, string projectDirectory, Version engineVersion, string templatePath)
|
public async Task<Result<ProjectMetadataInfo>> CreateProjectAsync(string projectName, string projectDirectory, Version engineVersion, string templatePath)
|
||||||
@@ -187,11 +192,18 @@ internal partial class ProjectService
|
|||||||
public async Task<Result<ProjectMetadataInfo>> AddProjectFromDirectoryAsync(string projectDirectory)
|
public async Task<Result<ProjectMetadataInfo>> AddProjectFromDirectoryAsync(string projectDirectory)
|
||||||
{
|
{
|
||||||
var result = await ValidateProjectDirectoryAsync(projectDirectory);
|
var result = await ValidateProjectDirectoryAsync(projectDirectory);
|
||||||
if (result.success)
|
if (!result.success)
|
||||||
{
|
{
|
||||||
await AddProjectAsync(result.data.Metadata.Name, result.data.Path);
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (await HasProjectAsync(result.data.Path))
|
||||||
|
{
|
||||||
|
return Result<ProjectMetadataInfo>.Error("Project already exists.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await AddProjectAsync(result.data.Metadata.Name, result.data.Path);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
using Ghost.Editor.Helpers;
|
|
||||||
using Ghost.Editor.Infrastructures.AppState;
|
|
||||||
using Ghost.Editor.Services;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.UI.Xaml;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
// 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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Provides application-specific behavior to supplement the default Application class.
|
|
||||||
/// </summary>
|
|
||||||
public partial class App : Application
|
|
||||||
{
|
|
||||||
private Window? _window;
|
|
||||||
|
|
||||||
internal static Window? Window
|
|
||||||
{
|
|
||||||
get => (Current as App)!._window;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (Current is App app)
|
|
||||||
{
|
|
||||||
app._window = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal IHost Host
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes the singleton application object. This is the first line of authored code
|
|
||||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
|
||||||
/// </summary>
|
|
||||||
internal App()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
Host = Microsoft.Extensions.Hosting.Host.
|
|
||||||
CreateDefaultBuilder().
|
|
||||||
UseContentRoot(AppContext.BaseDirectory).
|
|
||||||
ConfigureServices((context, services) =>
|
|
||||||
{
|
|
||||||
HostHelper.AddLandingScope(context, services);
|
|
||||||
HostHelper.AddEngineScope(context, services);
|
|
||||||
|
|
||||||
services.AddSingleton<AppStateMachine>();
|
|
||||||
services.AddSingleton<StackedNotificationService>();
|
|
||||||
})
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
UnhandledException += App_UnhandledException;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static IServiceScope CreateScope()
|
|
||||||
{
|
|
||||||
return (Current as App)!.Host.Services.CreateScope();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static T GetService<T>() where T : class
|
|
||||||
{
|
|
||||||
if ((Current as App)!.Host.Services.GetService(typeof(T)) is not T service)
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"{typeof(T)} needs to be registered in ConfigureServices within App.xaml.cs.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return service;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoked when the application is launched.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="args">Details about the launch request and process.</param>
|
|
||||||
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
|
||||||
{
|
|
||||||
base.OnLaunched(args);
|
|
||||||
|
|
||||||
ActivationHandler.Handle(args);
|
|
||||||
|
|
||||||
Host.Start();
|
|
||||||
|
|
||||||
var stateMachine = GetService<AppStateMachine>();
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
// TODO: Log and handle exceptions as appropriate.
|
|
||||||
// https://docs.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.application.unhandledexception.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3
Ghost.Editor/AssemblyInfo.cs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Ghost.App")]
|
||||||
@@ -1,182 +1,24 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
<Nullable>enable</Nullable>
|
||||||
<RootNamespace>Ghost.Editor</RootNamespace>
|
|
||||||
<Platforms>x86;x64;ARM64</Platforms>
|
|
||||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
|
||||||
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
|
||||||
<UseWinUI>true</UseWinUI>
|
|
||||||
<EnableMsixTooling>true</EnableMsixTooling>
|
|
||||||
<LangVersion>preview</LangVersion>
|
<LangVersion>preview</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
|
||||||
<Content Remove="Assets\icon-256.png" />
|
|
||||||
<Content Remove="Assets\Icon.altform-lightunplated_targetsize-16.png" />
|
|
||||||
<Content Remove="Assets\Icon.altform-lightunplated_targetsize-24.png" />
|
|
||||||
<Content Remove="Assets\Icon.altform-lightunplated_targetsize-256.png" />
|
|
||||||
<Content Remove="Assets\Icon.altform-lightunplated_targetsize-32.png" />
|
|
||||||
<Content Remove="Assets\Icon.altform-lightunplated_targetsize-48.png" />
|
|
||||||
<Content Remove="Assets\Icon.altform-unplated_targetsize-16.png" />
|
|
||||||
<Content Remove="Assets\Icon.altform-unplated_targetsize-24.png" />
|
|
||||||
<Content Remove="Assets\Icon.altform-unplated_targetsize-256.png" />
|
|
||||||
<Content Remove="Assets\Icon.altform-unplated_targetsize-32.png" />
|
|
||||||
<Content Remove="Assets\Icon.altform-unplated_targetsize-48.png" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<None Remove="Assets\Icon.scale-100.png" />
|
|
||||||
<None Remove="Assets\Icon.scale-125.png" />
|
|
||||||
<None Remove="Assets\Icon.scale-150.png" />
|
|
||||||
<None Remove="Assets\Icon.scale-200.png" />
|
|
||||||
<None Remove="Assets\Icon.scale-400.png" />
|
|
||||||
<None Remove="Assets\Icon.targetsize-16.png" />
|
|
||||||
<None Remove="Assets\Icon.targetsize-16_altform-unplated.png" />
|
|
||||||
<None Remove="Assets\Icon.targetsize-24.png" />
|
|
||||||
<None Remove="Assets\Icon.targetsize-24_altform-lightunplated.png" />
|
|
||||||
<None Remove="Assets\Icon.targetsize-256.png" />
|
|
||||||
<None Remove="Assets\Icon.targetsize-256_altform-unplated.png" />
|
|
||||||
<None Remove="Assets\Icon.targetsize-32.png" />
|
|
||||||
<None Remove="Assets\Icon.targetsize-32_altform-lightunplated.png" />
|
|
||||||
<None Remove="Assets\Icon.targetsize-48.png" />
|
|
||||||
<None Remove="Assets\Icon.targetsize-48_altform-unplated.png" />
|
|
||||||
<None Remove="Controls\BasicInput\PropertyField.xaml" />
|
|
||||||
<None Remove="Controls\EditorControls.xaml" />
|
|
||||||
<None Remove="Controls\Internal\InspectorView.xaml" />
|
|
||||||
<None Remove="Controls\Internal\InternalControls.xaml" />
|
|
||||||
<None Remove="View\Pages\EngineEditor\ConsolePage.xaml" />
|
|
||||||
<None Remove="View\Pages\EngineEditor\HierarchyPage.xaml" />
|
|
||||||
<None Remove="View\Pages\EngineEditor\ProjectPage.xaml" />
|
|
||||||
<None Remove="View\Pages\Landing\CreateProjectPage.xaml" />
|
|
||||||
<None Remove="View\Pages\Landing\OpenProjectPage.xaml" />
|
|
||||||
<None Remove="View\Windows\EngineEditorWindow.xaml" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Content Include="Assets\SplashScreen.scale-200.png" />
|
|
||||||
<Content Include="Assets\LockScreenLogo.scale-200.png" />
|
|
||||||
<Content Include="Assets\Square150x150Logo.scale-200.png" />
|
|
||||||
<Content Include="Assets\StoreLogo.png" />
|
|
||||||
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Manifest Include="$(ApplicationManifest)" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
|
||||||
Tools extension to be activated for this project even if the Windows App SDK Nuget
|
|
||||||
package has not yet been restored.
|
|
||||||
-->
|
|
||||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
|
||||||
<ProjectCapability Include="Msix" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
|
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
|
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.TabbedCommandBar" Version="8.2.250402" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.5" />
|
|
||||||
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
|
|
||||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
|
|
||||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
|
|
||||||
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
|
|
||||||
<PackageReference Include="WinUIEx" Version="2.5.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ghost.Data\Ghost.Data.csproj" />
|
|
||||||
<ProjectReference Include="..\Ghost.Engine\Ghost.Engine.csproj" />
|
<ProjectReference Include="..\Ghost.Engine\Ghost.Engine.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<Page Update="View\Pages\Landing\CreateProjectPage.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Page Update="View\Window\Landing.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Page Update="View\Pages\Landing\OpenProjectPage.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="AppStates\" />
|
|
||||||
<Folder Include="Resources\" />
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="Misaki.HighPerformance.Unsafe">
|
<Reference Include="Misaki.HighPerformance.Unsafe">
|
||||||
<HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll</HintPath>
|
<HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<Page Update="View\Pages\EngineEditor\HierarchyPage.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Page Update="View\Pages\EngineEditor\ProjectPage.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Page Update="View\Pages\EngineEditor\ConsolePage.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Page Update="Themes\Override.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Page Update="Controls\Internal\InternalControls.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Page Update="Controls\Internal\InspectorView.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Page Update="View\Windows\EngineEditorWindow.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Page Update="Controls\BasicInput\PropertyField.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<Page Update="Controls\EditorControls.xaml">
|
|
||||||
<Generator>MSBuild:Compile</Generator>
|
|
||||||
</Page>
|
|
||||||
</ItemGroup>
|
|
||||||
<PropertyGroup Label="Globals" />
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
|
|
||||||
Explorer "Package and Publish" context menu entry to be enabled for this project even if
|
|
||||||
the Windows App SDK Nuget package has not yet been restored.
|
|
||||||
-->
|
|
||||||
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
|
||||||
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<!-- Publish Properties -->
|
|
||||||
<PropertyGroup>
|
|
||||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
|
||||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
|
||||||
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<SupportedOSPlatformVersion>10.0.20348.0</SupportedOSPlatformVersion>
|
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
|
||||||
<PublishAot>True</PublishAot>
|
|
||||||
<PublishTrimmed>True</PublishTrimmed>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
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<EntityNode>? _children;
|
|
||||||
public ObservableCollection<EntityNode> Children
|
|
||||||
{
|
|
||||||
get => _children ??= new();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
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<Entity, EntityNode> _entityNodeLookup = new();
|
|
||||||
|
|
||||||
public World World => _world;
|
|
||||||
public Dictionary<Entity, EntityNode> 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<Hierarchy>(entity);
|
|
||||||
var child = hc.ValueRO.firstChild;
|
|
||||||
|
|
||||||
while (child != Entity.Invalid)
|
|
||||||
{
|
|
||||||
node.Children.Add(BuildNodeRecursive(child, world));
|
|
||||||
var childHC = world.EntityManager.GetComponent<Hierarchy>(child);
|
|
||||||
child = childHC.ValueRO.nextSibling;
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void BuildGraph()
|
|
||||||
{
|
|
||||||
foreach (var (entity, hierarchy) in _world.Query<Hierarchy>())
|
|
||||||
{
|
|
||||||
if (hierarchy.ValueRO.parent == Entity.Invalid)
|
|
||||||
{
|
|
||||||
var node = BuildNodeRecursive(entity, _world);
|
|
||||||
Children.Add(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Load()
|
|
||||||
{
|
|
||||||
BuildGraph();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,21 @@
|
|||||||
using Ghost.Entities;
|
using Ghost.Entities;
|
||||||
|
|
||||||
namespace Ghost.Editor.Infrastructures.SceneGraph;
|
namespace Ghost.Editor.SceneGraph;
|
||||||
|
|
||||||
public partial class EntityNode : SceneGraphNode
|
public partial class EntityNode : SceneGraphNode
|
||||||
{
|
{
|
||||||
private readonly Entity _entity;
|
private readonly Entity _entity;
|
||||||
|
|
||||||
public Entity Entity => _entity;
|
public Entity Entity => _entity;
|
||||||
public override NodeType Type => NodeType.Entity;
|
public override SceneGraphNodeType NodeType => SceneGraphNodeType.Entity;
|
||||||
|
|
||||||
public EntityNode(Entity entity, string name)
|
public EntityNode(Entity entity, string name)
|
||||||
{
|
{
|
||||||
_entity = entity;
|
_entity = entity;
|
||||||
Name = name;
|
Name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal EntityNode()
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
using Ghost.Engine.Components;
|
using Ghost.Engine.Components;
|
||||||
using Ghost.Entities;
|
using Ghost.Entities;
|
||||||
|
|
||||||
namespace Ghost.Editor.Infrastructures.SceneGraph;
|
namespace Ghost.Editor.SceneGraph;
|
||||||
|
|
||||||
internal class SceneGraphHelpers
|
public class SceneGraphHelpers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="EntityNode"/> entity with default components.
|
/// Creates a new <see cref="EntityNode"/> entity with default components.
|
||||||
@@ -33,7 +33,7 @@ internal class SceneGraphHelpers
|
|||||||
/// <param name="world">The world context where the entities exist.</param>
|
/// <param name="world">The world context where the entities exist.</param>
|
||||||
/// <param name="parentNode">The parent entity to which the child will be attached.</param>
|
/// <param name="parentNode">The parent entity to which the child will be attached.</param>
|
||||||
/// <param name="childNode">The child entity to be attached.</param>
|
/// <param name="childNode">The child entity to be attached.</param>
|
||||||
public static void AttachChild(SceneNode scene, EntityNode parentNode, EntityNode childNode)
|
public static void AttachChild(WorldNode scene, EntityNode parentNode, EntityNode childNode)
|
||||||
{
|
{
|
||||||
// 1) If the child already has a parent, detach it first
|
// 1) If the child already has a parent, detach it first
|
||||||
var childHierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(childNode.Entity);
|
var childHierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(childNode.Entity);
|
||||||
@@ -56,7 +56,7 @@ internal class SceneGraphHelpers
|
|||||||
scene.World.EntityManager.SetComponent(childNode.Entity, in childHierarchy.ValueRO);
|
scene.World.EntityManager.SetComponent(childNode.Entity, in childHierarchy.ValueRO);
|
||||||
|
|
||||||
// 5) Update children list in parent node
|
// 5) Update children list in parent node
|
||||||
parentNode.Children.Add(childNode);
|
parentNode.AddChild(childNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -64,7 +64,7 @@ internal class SceneGraphHelpers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="world">The world context where the entities exist.</param>
|
/// <param name="world">The world context where the entities exist.</param>
|
||||||
/// <param name="node">The entity to detach from its parent.</param>
|
/// <param name="node">The entity to detach from its parent.</param>
|
||||||
public static void DetachFromParent(SceneNode scene, EntityNode node)
|
public static void DetachFromParent(WorldNode scene, EntityNode node)
|
||||||
{
|
{
|
||||||
var hierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(node.Entity);
|
var hierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(node.Entity);
|
||||||
var parent = hierarchy.ValueRO.parent;
|
var parent = hierarchy.ValueRO.parent;
|
||||||
@@ -107,6 +107,6 @@ internal class SceneGraphHelpers
|
|||||||
scene.World.EntityManager.SetComponent(node.Entity, in hierarchy.ValueRO);
|
scene.World.EntityManager.SetComponent(node.Entity, in hierarchy.ValueRO);
|
||||||
|
|
||||||
// Remove from parent's children list
|
// Remove from parent's children list
|
||||||
scene.EntityNodeLookup[parent].Children.Remove(node);
|
scene.EntityNodeLookup[parent].RemoveChild(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
54
Ghost.Editor/SceneGraph/SceneGraphNode.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace Ghost.Editor.SceneGraph;
|
||||||
|
|
||||||
|
public enum SceneGraphNodeType
|
||||||
|
{
|
||||||
|
Scene,
|
||||||
|
Entity,
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract partial class SceneGraphNode : ObservableObject
|
||||||
|
{
|
||||||
|
public ObservableCollection<SceneGraphNode>? Children
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
public partial string Name
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract SceneGraphNodeType NodeType
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ChildCount => Children?.Count ?? 0;
|
||||||
|
|
||||||
|
public virtual void AddChild(SceneGraphNode child)
|
||||||
|
{
|
||||||
|
Children ??= new();
|
||||||
|
Children.Add(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual bool RemoveChild(SceneGraphNode child)
|
||||||
|
{
|
||||||
|
return Children?.Remove(child) ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SceneGraphNode GetChild(int index)
|
||||||
|
{
|
||||||
|
if (Children == null || index < 0 || index >= Children.Count)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Children[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
112
Ghost.Editor/SceneGraph/WorldNode.cs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
using Ghost.Editor.Serializer;
|
||||||
|
using Ghost.Engine.Components;
|
||||||
|
using Ghost.Entities;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ghost.Editor.SceneGraph;
|
||||||
|
|
||||||
|
[JsonConverter(typeof(WorldNodeSerializer))]
|
||||||
|
public partial class WorldNode : SceneGraphNode
|
||||||
|
{
|
||||||
|
private readonly World _world;
|
||||||
|
private Dictionary<Entity, EntityNode> _entityNodeLookup = new();
|
||||||
|
|
||||||
|
public World World => _world;
|
||||||
|
public Dictionary<Entity, EntityNode> EntityNodeLookup => _entityNodeLookup;
|
||||||
|
|
||||||
|
public override SceneGraphNodeType NodeType => SceneGraphNodeType.Scene;
|
||||||
|
|
||||||
|
public WorldNode(World world, string name)
|
||||||
|
{
|
||||||
|
_world = world;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal WorldNode()
|
||||||
|
{
|
||||||
|
_world = World.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateLookup(Entity key, EntityNode value)
|
||||||
|
{
|
||||||
|
_entityNodeLookup[key] = value;
|
||||||
|
if (value.Children == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var child in value.Children)
|
||||||
|
{
|
||||||
|
if (child is EntityNode entityChild)
|
||||||
|
{
|
||||||
|
UpdateLookup(entityChild.Entity, entityChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AddChild(SceneGraphNode child)
|
||||||
|
{
|
||||||
|
if (child is not EntityNode entityNode)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Child must be of type EntityNode.", nameof(child));
|
||||||
|
}
|
||||||
|
|
||||||
|
base.AddChild(entityNode);
|
||||||
|
UpdateLookup(entityNode.Entity, entityNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool RemoveChild(SceneGraphNode child)
|
||||||
|
{
|
||||||
|
if (child is not EntityNode entityNode)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Child must be of type EntityNode.", nameof(child));
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = base.RemoveChild(child);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
_entityNodeLookup.Remove(entityNode.Entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EntityNode BuildNodeRecursive(Entity entity, World world)
|
||||||
|
{
|
||||||
|
// TODO: Node serialization.
|
||||||
|
if (!_entityNodeLookup.TryGetValue(entity, out var node))
|
||||||
|
{
|
||||||
|
node = new EntityNode(entity, "New Entity");
|
||||||
|
_entityNodeLookup[entity] = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hc = world.EntityManager.GetComponent<Hierarchy>(entity);
|
||||||
|
var child = hc.ValueRO.firstChild;
|
||||||
|
|
||||||
|
while (child != Entity.Invalid)
|
||||||
|
{
|
||||||
|
node.AddChild(BuildNodeRecursive(child, world));
|
||||||
|
var childHC = world.EntityManager.GetComponent<Hierarchy>(child);
|
||||||
|
child = childHC.ValueRO.nextSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildGraph()
|
||||||
|
{
|
||||||
|
foreach (var (entity, hierarchy) in _world.Query<Hierarchy>())
|
||||||
|
{
|
||||||
|
if (hierarchy.ValueRO.parent == Entity.Invalid)
|
||||||
|
{
|
||||||
|
var node = BuildNodeRecursive(entity, _world);
|
||||||
|
AddChild(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Load()
|
||||||
|
{
|
||||||
|
BuildGraph();
|
||||||
|
}
|
||||||
|
}
|
||||||
131
Ghost.Editor/Serializer/WorldNodeSerializer.cs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
using Ghost.Editor.SceneGraph;
|
||||||
|
using Ghost.Engine.Utilities;
|
||||||
|
using Ghost.Entities;
|
||||||
|
using Ghost.Entities.Components;
|
||||||
|
using Ghost.Entities.Utilities;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ghost.Editor.Serializer;
|
||||||
|
|
||||||
|
internal class WorldNodeSerializer : JsonConverter<WorldNode>
|
||||||
|
{
|
||||||
|
private static class Property
|
||||||
|
{
|
||||||
|
public const string NAME = "Name";
|
||||||
|
public const string ENTITIES = "Entities";
|
||||||
|
public const string ID = "ID";
|
||||||
|
public const string ENTITY_ID = "EntityID";
|
||||||
|
public const string COMPONENTS = "Components";
|
||||||
|
public const string DATA = "Data";
|
||||||
|
public const string SYSTEMS = "Systems";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanConvert(Type typeToConvert)
|
||||||
|
{
|
||||||
|
return typeToConvert == typeof(WorldNode) || typeToConvert.IsSubclassOf(typeof(WorldNode));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override WorldNode? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
var element = JsonDocument.ParseValue(ref reader).RootElement;
|
||||||
|
var name = element.GetProperty(Property.NAME).GetString() ?? "New World";
|
||||||
|
|
||||||
|
var world = World.Create();
|
||||||
|
var result = new WorldNode(world, name);
|
||||||
|
|
||||||
|
foreach (var entityElement in element.GetProperty(Property.ENTITIES).EnumerateArray())
|
||||||
|
{
|
||||||
|
var entityName = entityElement.GetProperty(Property.NAME).GetString() ?? "New Entity";
|
||||||
|
var entityID = entityElement.GetProperty(Property.ID).GetInt32();
|
||||||
|
var entity = new Entity(entityID, 0);
|
||||||
|
var node = new EntityNode(entity, entityName);
|
||||||
|
|
||||||
|
world.EntityManager.AddEntityInternal(entity);
|
||||||
|
result.EntityNodeLookup[entity] = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var componentElement in element.GetProperty(Property.COMPONENTS).EnumerateObject())
|
||||||
|
{
|
||||||
|
var typeName = componentElement.Name;
|
||||||
|
var type = Type.GetType(typeName) ?? throw new Exception($"Type {typeName} not found.");
|
||||||
|
|
||||||
|
foreach (var dataElement in componentElement.Value.EnumerateArray())
|
||||||
|
{
|
||||||
|
var entityID = dataElement.GetProperty(Property.ENTITY_ID).GetInt32();
|
||||||
|
var entity = new Entity(entityID, 0);
|
||||||
|
|
||||||
|
var dataProperty = dataElement.GetProperty(Property.DATA);
|
||||||
|
var component = JsonSerializer.Deserialize(dataProperty.GetRawText(), type, options);
|
||||||
|
if (component is IComponentData data)
|
||||||
|
{
|
||||||
|
world.EntityManager.AddComponent(entity, data, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var systemElement in element.GetProperty(Property.SYSTEMS).EnumerateArray())
|
||||||
|
{
|
||||||
|
var typeString = systemElement.GetString();
|
||||||
|
if (string.IsNullOrEmpty(typeString))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var systemType = Type.GetType(typeString);
|
||||||
|
if (systemType == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
world.SystemStorage.AddSystem(systemType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, WorldNode value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
writer.WriteObject(() =>
|
||||||
|
{
|
||||||
|
writer.WriteString(Property.NAME, value.Name);
|
||||||
|
writer.WriteArray(Property.ENTITIES, value.World.EntityManager.Entities, entity =>
|
||||||
|
{
|
||||||
|
if (!entity.IsValid)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteObject(() =>
|
||||||
|
{
|
||||||
|
writer.WriteString(Property.NAME, value.EntityNodeLookup[entity].Name);
|
||||||
|
writer.WriteNumber(Property.ID, entity.ID);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
writer.WriteObject(Property.COMPONENTS, () =>
|
||||||
|
{
|
||||||
|
foreach (var kvp in value.World.ComponentStorage.ComponentPools)
|
||||||
|
{
|
||||||
|
var type = TypeHandle.ToType(kvp.Key) ?? throw new Exception($"Type {kvp.Key} not found.");
|
||||||
|
var typeName = type.AssemblyQualifiedName ?? type.Name;
|
||||||
|
|
||||||
|
writer.WriteArray(typeName, kvp.Value.Enumerate(), data =>
|
||||||
|
{
|
||||||
|
writer.WriteObject(() =>
|
||||||
|
{
|
||||||
|
writer.WriteNumber(Property.ENTITY_ID, data.entity.ID);
|
||||||
|
writer.WritePropertyName(Property.DATA);
|
||||||
|
JsonSerializer.Serialize(writer, data.component, type, options);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
writer.WriteArray(Property.SYSTEMS, value.World.SystemStorage.Systems, systemType =>
|
||||||
|
{
|
||||||
|
writer.WriteStringValue(systemType.AssemblyQualifiedName ?? systemType.Name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
[assembly: InternalsVisibleTo("Ghost.Editor")]
|
[assembly: InternalsVisibleTo("Ghost.App")]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Ghost.Engine.Helpers;
|
using Ghost.Engine.Utilities;
|
||||||
using Ghost.Entities.Components;
|
using Ghost.Entities.Components;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@@ -15,7 +15,7 @@ public struct LocalToWorld : IComponentData
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get => new()
|
get => new()
|
||||||
{
|
{
|
||||||
matrix = MatrixHelpers.CreateTRS(Vector3.Zero, Quaternion.Identity, Vector3.One)
|
matrix = MatrixUtilities.CreateTRS(Vector3.Zero, Quaternion.Identity, Vector3.One)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ghost.Entities\Ghost.Entities.csproj" />
|
<ProjectReference Include="..\Ghost.Entities\Ghost.Entities.csproj" />
|
||||||
|
<!--<ProjectReference Include="..\Ghost.Generator\Ghost.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />-->
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ghost.Engine.Helpers;
|
namespace Ghost.Engine.Utilities;
|
||||||
|
|
||||||
public static class MatrixHelpers
|
public static class MatrixUtilities
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a transformation matrix from position, rotation, and scale vectors.
|
/// Generates a transformation matrix from position, rotation, and scale vectors.
|
||||||
40
Ghost.Engine/Utilities/Utf8JsonWriterExtensions.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Ghost.Engine.Utilities;
|
||||||
|
|
||||||
|
public static class Utf8JsonWriterExtensions
|
||||||
|
{
|
||||||
|
public static void WriteArray<T>(this Utf8JsonWriter writer, ReadOnlySpan<char> name, IEnumerable<T> source, Action<T> writeAction)
|
||||||
|
{
|
||||||
|
writer.WriteStartArray(name);
|
||||||
|
foreach (var item in source)
|
||||||
|
{
|
||||||
|
writeAction(item);
|
||||||
|
}
|
||||||
|
writer.WriteEndArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WriteArray<T>(this Utf8JsonWriter writer, ReadOnlySpan<char> name, ReadOnlySpan<T> source, Action<T> writeAction)
|
||||||
|
{
|
||||||
|
writer.WriteStartArray(name);
|
||||||
|
foreach (var item in source)
|
||||||
|
{
|
||||||
|
writeAction(item);
|
||||||
|
}
|
||||||
|
writer.WriteEndArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WriteObject(this Utf8JsonWriter writer, Action writeAction)
|
||||||
|
{
|
||||||
|
writer.WriteStartObject();
|
||||||
|
writeAction();
|
||||||
|
writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WriteObject(this Utf8JsonWriter writer, ReadOnlySpan<char> name, Action writeAction)
|
||||||
|
{
|
||||||
|
writer.WriteStartObject(name);
|
||||||
|
writeAction();
|
||||||
|
writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
global using EntityID = System.Int32;
|
global using EntityID = System.Int32;
|
||||||
global using GenerationID = System.Byte;
|
global using GenerationID = System.UInt16;
|
||||||
global using WorldID = System.UInt16;
|
global using WorldID = System.UInt16;
|
||||||
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Ghost.App")]
|
||||||
[assembly: InternalsVisibleTo("Ghost.Engine")]
|
[assembly: InternalsVisibleTo("Ghost.Engine")]
|
||||||
[assembly: InternalsVisibleTo("Ghost.Editor")]
|
[assembly: InternalsVisibleTo("Ghost.Editor")]
|
||||||
[assembly: InternalsVisibleTo("Ghost.Test")]
|
[assembly: InternalsVisibleTo("Ghost.Test")]
|
||||||
@@ -18,8 +18,12 @@ internal interface IComponentPool : IDisposable
|
|||||||
get;
|
get;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Add(Entity entity, IComponentData component);
|
||||||
public bool Remove(Entity entity);
|
public bool Remove(Entity entity);
|
||||||
public bool Has(Entity entity);
|
public bool Has(Entity entity);
|
||||||
|
public IComponentData Get(Entity entity);
|
||||||
|
|
||||||
|
public IEnumerable<(Entity entity, IComponentData component)> Enumerate();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface IComponentPool<T> : IComponentPool
|
internal interface IComponentPool<T> : IComponentPool
|
||||||
@@ -56,6 +60,10 @@ internal class ComponentPool<T> : IComponentPool<T>
|
|||||||
_lookup.AsSpan().Fill(Entity.INVALID_ID);
|
_lookup.AsSpan().Fill(Entity.INVALID_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ComponentPool() : this(16)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static EntityID GetLookupIndex(Entity entity)
|
private static EntityID GetLookupIndex(Entity entity)
|
||||||
{
|
{
|
||||||
@@ -68,6 +76,16 @@ internal class ComponentPool<T> : IComponentPool<T>
|
|||||||
return _lookup[GetLookupIndex(entity)];
|
return _lookup[GetLookupIndex(entity)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Add(Entity entity, IComponentData component)
|
||||||
|
{
|
||||||
|
if (component is not T typedComponent)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Component type mismatch. Expected {typeof(T)}, but got {component.GetType()}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Add(entity, typedComponent);
|
||||||
|
}
|
||||||
|
|
||||||
public void Add(Entity entity, T component)
|
public void Add(Entity entity, T component)
|
||||||
{
|
{
|
||||||
if (!entity.IsValid)
|
if (!entity.IsValid)
|
||||||
@@ -117,6 +135,11 @@ internal class ComponentPool<T> : IComponentPool<T>
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IComponentData Get(Entity entity)
|
||||||
|
{
|
||||||
|
return GetRef(entity);
|
||||||
|
}
|
||||||
|
|
||||||
public ref T GetRef(Entity entity)
|
public ref T GetRef(Entity entity)
|
||||||
{
|
{
|
||||||
if (!entity.IsValid)
|
if (!entity.IsValid)
|
||||||
@@ -128,6 +151,17 @@ internal class ComponentPool<T> : IComponentPool<T>
|
|||||||
return ref _components[index].data;
|
return ref _components[index].data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<(Entity entity, IComponentData component)> Enumerate()
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _nextId; i++)
|
||||||
|
{
|
||||||
|
if (_components[i].owner.IsValid)
|
||||||
|
{
|
||||||
|
yield return (_components[i].owner, _components[i].data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool Has(Entity entity)
|
public bool Has(Entity entity)
|
||||||
{
|
{
|
||||||
if (entity.ID >= _lookup.Length)
|
if (entity.ID >= _lookup.Length)
|
||||||
@@ -194,6 +228,16 @@ internal class ScriptComponentPool : IComponentPool<ScriptComponent>
|
|||||||
_executionList.Sort((a, b) => a.ExecutionOrder.CompareTo(b.ExecutionOrder));
|
_executionList.Sort((a, b) => a.ExecutionOrder.CompareTo(b.ExecutionOrder));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Add(Entity entity, IComponentData component)
|
||||||
|
{
|
||||||
|
if (component is not ScriptComponent scriptComponent)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Component type mismatch. Expected {typeof(ScriptComponent)}, but got {component.GetType()}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Add(entity, scriptComponent);
|
||||||
|
}
|
||||||
|
|
||||||
public void Add(Entity entity, ScriptComponent component)
|
public void Add(Entity entity, ScriptComponent component)
|
||||||
{
|
{
|
||||||
if (!IsInitialized)
|
if (!IsInitialized)
|
||||||
@@ -283,12 +327,33 @@ internal class ScriptComponentPool : IComponentPool<ScriptComponent>
|
|||||||
return _scriptComponents?.ContainsKey(entity) ?? false;
|
return _scriptComponents?.ContainsKey(entity) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ScriptComponent>? Get(Entity entity)
|
public IComponentData Get(Entity entity)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Use GetAll instead of Get for ScriptComponentPool.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<(Entity entity, IComponentData component)> Enumerate()
|
||||||
|
{
|
||||||
|
if (_scriptComponents == null)
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var kvp in _scriptComponents)
|
||||||
|
{
|
||||||
|
foreach (var script in kvp.Value)
|
||||||
|
{
|
||||||
|
yield return (kvp.Key, script);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IComponentData> GetAll(Entity entity)
|
||||||
{
|
{
|
||||||
if (_scriptComponents == null
|
if (_scriptComponents == null
|
||||||
|| !_scriptComponents.TryGetValue(entity, out var scriptList))
|
|| !_scriptComponents.TryGetValue(entity, out var scriptList))
|
||||||
{
|
{
|
||||||
return null;
|
return Enumerable.Empty<ScriptComponent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
return scriptList;
|
return scriptList;
|
||||||
|
|||||||
@@ -1,20 +1,28 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
namespace Ghost.Entities;
|
||||||
|
|
||||||
[SkipLocalsInit]
|
[SkipLocalsInit]
|
||||||
public struct Entity : IEquatable<Entity>, IComparable<Entity>
|
public struct Entity : IEquatable<Entity>, IComparable<Entity>
|
||||||
{
|
{
|
||||||
// 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 _GENERATION_MASK = (1 << GENERATION_BITS) - 1;
|
|
||||||
private const int _INDEX_MASK = (1 << INDEX_BITS) - 1;
|
|
||||||
|
|
||||||
public const EntityID INVALID_ID = -1;
|
public const EntityID INVALID_ID = -1;
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
private EntityID _id;
|
private EntityID _id;
|
||||||
|
private GenerationID _generation;
|
||||||
|
|
||||||
|
public readonly EntityID ID
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly GenerationID Generation
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _generation;
|
||||||
|
}
|
||||||
|
|
||||||
public readonly bool IsValid
|
public readonly bool IsValid
|
||||||
{
|
{
|
||||||
@@ -22,39 +30,20 @@ public struct Entity : IEquatable<Entity>, IComparable<Entity>
|
|||||||
get => ID != INVALID_ID;
|
get => ID != INVALID_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly EntityID ID
|
|
||||||
{
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
get => _id & _INDEX_MASK;
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly GenerationID Generation
|
|
||||||
{
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
get => (GenerationID)(_id >> INDEX_BITS & _GENERATION_MASK);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Entity Invalid
|
public static Entity Invalid
|
||||||
{
|
{
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get => new(INVALID_ID, 0);
|
get => new(INVALID_ID, GenerationID.MaxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Entity(EntityID id, GenerationID generation)
|
internal Entity(EntityID id, GenerationID generation)
|
||||||
{
|
{
|
||||||
_id = generation << INDEX_BITS | id;
|
_id = id;
|
||||||
|
_generation = generation;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void IncrementGeneration()
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
{
|
internal void IncrementGeneration() => _generation++;
|
||||||
var generation = Generation + 1;
|
|
||||||
if (generation >= _GENERATION_MASK)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Generation overflow");
|
|
||||||
}
|
|
||||||
|
|
||||||
_id = _id & ~(_GENERATION_MASK << INDEX_BITS) | generation << INDEX_BITS;
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly bool Equals(Entity other)
|
public readonly bool Equals(Entity other)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using System.Runtime.CompilerServices;
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
namespace Ghost.Entities;
|
||||||
public struct EntityManager : IDisposable
|
public readonly struct EntityManager : IDisposable
|
||||||
{
|
{
|
||||||
private readonly List<Entity> _entities;
|
private readonly List<Entity> _entities;
|
||||||
private readonly Queue<EntityID> _freeEntitySlots;
|
private readonly Queue<EntityID> _freeEntitySlots;
|
||||||
@@ -15,11 +15,6 @@ public struct EntityManager : IDisposable
|
|||||||
public readonly int EntityCount => _entities.Count;
|
public readonly int EntityCount => _entities.Count;
|
||||||
public readonly ReadOnlySpan<Entity> Entities => CollectionsMarshal.AsSpan(_entities);
|
public readonly ReadOnlySpan<Entity> Entities => CollectionsMarshal.AsSpan(_entities);
|
||||||
|
|
||||||
public event Action<Entity>? OnEntityCreated;
|
|
||||||
public event Action<EntityID>? OnEntityRemoved;
|
|
||||||
public event Action<Entity, Type>? OnComponentAdded;
|
|
||||||
public event Action<Entity, Type>? OnComponentRemoved;
|
|
||||||
|
|
||||||
internal EntityManager(World world, int initialCapacity)
|
internal EntityManager(World world, int initialCapacity)
|
||||||
{
|
{
|
||||||
_entities = new(initialCapacity);
|
_entities = new(initialCapacity);
|
||||||
@@ -45,10 +40,14 @@ public struct EntityManager : IDisposable
|
|||||||
_entities.Add(entity);
|
_entities.Add(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
OnEntityCreated?.Invoke(entity);
|
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal readonly void AddEntityInternal(Entity entity)
|
||||||
|
{
|
||||||
|
_entities.Add(entity);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes the specified <see cref="Entity"/> from the world.
|
/// Removes the specified <see cref="Entity"/> from the world.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -67,7 +66,6 @@ public struct EntityManager : IDisposable
|
|||||||
_entities[entity.ID] = slot;
|
_entities[entity.ID] = slot;
|
||||||
_freeEntitySlots.Enqueue(entity.ID);
|
_freeEntitySlots.Enqueue(entity.ID);
|
||||||
|
|
||||||
OnEntityRemoved?.Invoke(entity.ID);
|
|
||||||
entity = Entity.Invalid;
|
entity = Entity.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +86,20 @@ public struct EntityManager : IDisposable
|
|||||||
return _entities[entity.ID].Generation == entity.Generation;
|
return _entities[entity.ID].Generation == entity.Generation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public readonly void AddComponent(Entity entity, IComponentData component, Type type)
|
||||||
|
{
|
||||||
|
var typeHandle = TypeHandle.Get(type);
|
||||||
|
ref var pool = ref CollectionsMarshal.GetValueRefOrAddDefault(_world.ComponentStorage.ComponentPools, typeHandle, out var exists);
|
||||||
|
if (!exists)
|
||||||
|
{
|
||||||
|
var poolType = typeof(ComponentPool<>).MakeGenericType(type);
|
||||||
|
pool = (IComponentPool)(Activator.CreateInstance(poolType) ?? throw new InvalidOperationException($"Failed to create component pool for type {type}."));
|
||||||
|
}
|
||||||
|
|
||||||
|
pool!.Add(entity, component);
|
||||||
|
_world.ComponentStorage.GetOrCreateMask(typeHandle).SetBit(entity.ID);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a component of type <typeparamref name="T"/> to the given <see cref="Entity"/>.
|
/// Adds a component of type <typeparamref name="T"/> to the given <see cref="Entity"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -100,7 +112,6 @@ public struct EntityManager : IDisposable
|
|||||||
{
|
{
|
||||||
_world.ComponentStorage.GetOrCreateComponentPool<T>().Add(entity, component);
|
_world.ComponentStorage.GetOrCreateComponentPool<T>().Add(entity, component);
|
||||||
_world.ComponentStorage.GetOrCreateMask(TypeHandle.Get<T>()).SetBit(entity.ID);
|
_world.ComponentStorage.GetOrCreateMask(TypeHandle.Get<T>()).SetBit(entity.ID);
|
||||||
OnComponentAdded?.Invoke(entity, typeof(T));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -123,7 +134,6 @@ public struct EntityManager : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
_world.ComponentStorage.GetOrCreateMask(TypeHandle.Get<T>()).ClearBit(entity.ID);
|
_world.ComponentStorage.GetOrCreateMask(TypeHandle.Get<T>()).ClearBit(entity.ID);
|
||||||
OnComponentRemoved?.Invoke(entity, typeof(T));
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -183,7 +193,6 @@ public struct EntityManager : IDisposable
|
|||||||
where T : ScriptComponent, new()
|
where T : ScriptComponent, new()
|
||||||
{
|
{
|
||||||
_world.ComponentStorage.ScriptComponentPool.Add(entity, new T());
|
_world.ComponentStorage.ScriptComponentPool.Add(entity, new T());
|
||||||
OnComponentAdded?.Invoke(entity, typeof(ScriptComponent));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -203,9 +212,14 @@ public struct EntityManager : IDisposable
|
|||||||
|
|
||||||
var instance = (ScriptComponent?)Activator.CreateInstance(type) ?? throw new InvalidOperationException($"Failed to create instance of {type}.");
|
var instance = (ScriptComponent?)Activator.CreateInstance(type) ?? throw new InvalidOperationException($"Failed to create instance of {type}.");
|
||||||
_world.ComponentStorage.ScriptComponentPool.Add(entity, instance);
|
_world.ComponentStorage.ScriptComponentPool.Add(entity, instance);
|
||||||
OnComponentAdded?.Invoke(entity, typeof(ScriptComponent));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a script of type <typeparamref name="T"/> from the given <see cref="Entity"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the script to remove.</typeparam>
|
||||||
|
/// <param name="entity">The entity from which the script is to be removed.</param>
|
||||||
|
/// <returns>True if the script was successfully removed; otherwise, false.</returns>
|
||||||
public readonly bool RemoveScript<T>(Entity entity)
|
public readonly bool RemoveScript<T>(Entity entity)
|
||||||
where T : ScriptComponent
|
where T : ScriptComponent
|
||||||
{
|
{
|
||||||
@@ -214,10 +228,15 @@ public struct EntityManager : IDisposable
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
OnComponentRemoved?.Invoke(entity, typeof(ScriptComponent));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a script at the specified index from the given <see cref="Entity"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">The entity from which the script is to be removed.</param>
|
||||||
|
/// <param name="index">The index of the script to remove.</param>
|
||||||
|
/// <returns>True if the script was successfully removed; otherwise, false.</returns>
|
||||||
public readonly bool RemoveScriptAt(Entity entity, int index)
|
public readonly bool RemoveScriptAt(Entity entity, int index)
|
||||||
{
|
{
|
||||||
if (!_world.ComponentStorage.ScriptComponentPool.RemoveAt(entity, index))
|
if (!_world.ComponentStorage.ScriptComponentPool.RemoveAt(entity, index))
|
||||||
@@ -225,7 +244,6 @@ public struct EntityManager : IDisposable
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
OnComponentRemoved?.Invoke(entity, typeof(ScriptComponent));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +256,7 @@ public struct EntityManager : IDisposable
|
|||||||
public readonly T? GetScript<T>(Entity entity)
|
public readonly T? GetScript<T>(Entity entity)
|
||||||
where T : ScriptComponent
|
where T : ScriptComponent
|
||||||
{
|
{
|
||||||
return (T?)_world.ComponentStorage.ScriptComponentPool.Get(entity)?
|
return (T?)_world.ComponentStorage.ScriptComponentPool.GetAll(entity)?
|
||||||
.FirstOrDefault(script => script is T tScript);
|
.FirstOrDefault(script => script is T tScript);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,7 +269,7 @@ public struct EntityManager : IDisposable
|
|||||||
public readonly IEnumerable<T> GetScripts<T>(Entity entity)
|
public readonly IEnumerable<T> GetScripts<T>(Entity entity)
|
||||||
where T : ScriptComponent
|
where T : ScriptComponent
|
||||||
{
|
{
|
||||||
return (IEnumerable<T>?)_world.ComponentStorage.ScriptComponentPool.Get(entity)?.Where(script => script is T tScript) ?? Enumerable.Empty<T>();
|
return (IEnumerable<T>?)_world.ComponentStorage.ScriptComponentPool.GetAll(entity)?.Where(script => script is T tScript) ?? Enumerable.Empty<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly void Dispose()
|
public readonly void Dispose()
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ internal struct QueryFilter()
|
|||||||
internal List<nint> _absent = new(6);
|
internal List<nint> _absent = new(6);
|
||||||
internal List<nint> _disabled = new(6);
|
internal List<nint> _disabled = new(6);
|
||||||
|
|
||||||
public readonly void ComputeFilterBitMask(World world, ref BitSet result)
|
public readonly void ComputeFilterBitMask(World world, BitSet result)
|
||||||
{
|
{
|
||||||
BitSet allMask = default;
|
BitSet allMask = new();
|
||||||
BitSet anyMask = default;
|
BitSet anyMask = new();
|
||||||
BitSet absentMask = default;
|
BitSet absentMask = new();
|
||||||
|
|
||||||
var hasAll = false;
|
var hasAll = false;
|
||||||
var hasAny = false;
|
var hasAny = false;
|
||||||
@@ -77,7 +77,6 @@ internal struct QueryFilter()
|
|||||||
absentMask |= mask;
|
absentMask |= mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = new BitSet(world.EntityManager.EntityCount);
|
|
||||||
result.SetAll();
|
result.SetAll();
|
||||||
|
|
||||||
if (hasAll)
|
if (hasAll)
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ghost.Entities.Systems;
|
namespace Ghost.Entities.Systems;
|
||||||
|
|
||||||
[SkipLocalsInit]
|
[SkipLocalsInit]
|
||||||
public struct SystemStorage
|
public readonly struct SystemStorage
|
||||||
{
|
{
|
||||||
private readonly List<Type> _systems = new();
|
private readonly List<Type> _systems = new();
|
||||||
private readonly List<ISystem> _executionList = new();
|
private readonly List<ISystem> _executionList = new();
|
||||||
|
|
||||||
private readonly World _world;
|
private readonly World _world;
|
||||||
|
|
||||||
public event Action<Type>? SystemAdded;
|
internal ReadOnlySpan<Type> Systems => CollectionsMarshal.AsSpan(_systems);
|
||||||
public event Action<Type>? SystemRemoved;
|
|
||||||
|
|
||||||
internal SystemStorage(World world)
|
internal SystemStorage(World world)
|
||||||
{
|
{
|
||||||
@@ -21,7 +21,6 @@ public struct SystemStorage
|
|||||||
public readonly void AddSystem(Type systemType)
|
public readonly void AddSystem(Type systemType)
|
||||||
{
|
{
|
||||||
_systems.Add(systemType);
|
_systems.Add(systemType);
|
||||||
SystemAdded?.Invoke(systemType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -34,7 +33,6 @@ public struct SystemStorage
|
|||||||
public readonly void RemoveSystem(Type systemType)
|
public readonly void RemoveSystem(Type systemType)
|
||||||
{
|
{
|
||||||
_systems.Remove(systemType);
|
_systems.Remove(systemType);
|
||||||
SystemRemoved?.Invoke(systemType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ public struct QueryEnumerable<T0>
|
|||||||
|
|
||||||
_count = count;
|
_count = count;
|
||||||
_index = -1;
|
_index = -1;
|
||||||
filters.ComputeFilterBitMask(_world, ref _filterMask);
|
_filterMask = new BitSet(_world.EntityManager.EntityCount);
|
||||||
|
filters.ComputeFilterBitMask(_world, _filterMask);
|
||||||
|
|
||||||
Current = default;
|
Current = default;
|
||||||
}
|
}
|
||||||
@@ -231,7 +232,8 @@ public struct QueryEnumerable<T0, T1>
|
|||||||
|
|
||||||
_count = count;
|
_count = count;
|
||||||
_index = -1;
|
_index = -1;
|
||||||
filters.ComputeFilterBitMask(_world, ref _filterMask);
|
_filterMask = new BitSet(_world.EntityManager.EntityCount);
|
||||||
|
filters.ComputeFilterBitMask(_world, _filterMask);
|
||||||
|
|
||||||
Current = default;
|
Current = default;
|
||||||
}
|
}
|
||||||
@@ -408,7 +410,8 @@ public struct QueryEnumerable<T0, T1, T2>
|
|||||||
|
|
||||||
_count = count;
|
_count = count;
|
||||||
_index = -1;
|
_index = -1;
|
||||||
filters.ComputeFilterBitMask(_world, ref _filterMask);
|
_filterMask = new BitSet(_world.EntityManager.EntityCount);
|
||||||
|
filters.ComputeFilterBitMask(_world, _filterMask);
|
||||||
|
|
||||||
Current = default;
|
Current = default;
|
||||||
}
|
}
|
||||||
@@ -590,7 +593,8 @@ public struct QueryEnumerable<T0, T1, T2, T3>
|
|||||||
|
|
||||||
_count = count;
|
_count = count;
|
||||||
_index = -1;
|
_index = -1;
|
||||||
filters.ComputeFilterBitMask(_world, ref _filterMask);
|
_filterMask = new BitSet(_world.EntityManager.EntityCount);
|
||||||
|
filters.ComputeFilterBitMask(_world, _filterMask);
|
||||||
|
|
||||||
Current = default;
|
Current = default;
|
||||||
}
|
}
|
||||||
@@ -777,7 +781,8 @@ public struct QueryEnumerable<T0, T1, T2, T3, T4>
|
|||||||
|
|
||||||
_count = count;
|
_count = count;
|
||||||
_index = -1;
|
_index = -1;
|
||||||
filters.ComputeFilterBitMask(_world, ref _filterMask);
|
_filterMask = new BitSet(_world.EntityManager.EntityCount);
|
||||||
|
filters.ComputeFilterBitMask(_world, _filterMask);
|
||||||
|
|
||||||
Current = default;
|
Current = default;
|
||||||
}
|
}
|
||||||
@@ -969,7 +974,8 @@ public struct QueryEnumerable<T0, T1, T2, T3, T4, T5>
|
|||||||
|
|
||||||
_count = count;
|
_count = count;
|
||||||
_index = -1;
|
_index = -1;
|
||||||
filters.ComputeFilterBitMask(_world, ref _filterMask);
|
_filterMask = new BitSet(_world.EntityManager.EntityCount);
|
||||||
|
filters.ComputeFilterBitMask(_world, _filterMask);
|
||||||
|
|
||||||
Current = default;
|
Current = default;
|
||||||
}
|
}
|
||||||
@@ -1166,7 +1172,8 @@ public struct QueryEnumerable<T0, T1, T2, T3, T4, T5, T6>
|
|||||||
|
|
||||||
_count = count;
|
_count = count;
|
||||||
_index = -1;
|
_index = -1;
|
||||||
filters.ComputeFilterBitMask(_world, ref _filterMask);
|
_filterMask = new BitSet(_world.EntityManager.EntityCount);
|
||||||
|
filters.ComputeFilterBitMask(_world, _filterMask);
|
||||||
|
|
||||||
Current = default;
|
Current = default;
|
||||||
}
|
}
|
||||||
@@ -1368,7 +1375,8 @@ public struct QueryEnumerable<T0, T1, T2, T3, T4, T5, T6, T7>
|
|||||||
|
|
||||||
_count = count;
|
_count = count;
|
||||||
_index = -1;
|
_index = -1;
|
||||||
filters.ComputeFilterBitMask(_world, ref _filterMask);
|
_filterMask = new BitSet(_world.EntityManager.EntityCount);
|
||||||
|
filters.ComputeFilterBitMask(_world, _filterMask);
|
||||||
|
|
||||||
Current = default;
|
Current = default;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ public struct QueryEnumerable<<#= generics #>>
|
|||||||
|
|
||||||
_count = count;
|
_count = count;
|
||||||
_index = -1;
|
_index = -1;
|
||||||
filters.ComputeFilterBitMask(_world, ref _filterMask);
|
_filterMask = new BitSet(_world.EntityManager.EntityCount);
|
||||||
|
filters.ComputeFilterBitMask(_world, _filterMask);
|
||||||
|
|
||||||
Current = default;
|
Current = default;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,4 +25,9 @@ internal static class TypeHandle
|
|||||||
{
|
{
|
||||||
return type.TypeHandle.Value;
|
return type.TypeHandle.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Type? ToType(nint handle)
|
||||||
|
{
|
||||||
|
return Type.GetTypeFromHandle(RuntimeTypeHandle.FromIntPtr(handle));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
114
Ghost.Generator/ComponentSerializationGenerator.cs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using Microsoft.CodeAnalysis.Text;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Ghost.Generator
|
||||||
|
{
|
||||||
|
[Generator]
|
||||||
|
public class ComponentSerializationGenerator : IIncrementalGenerator
|
||||||
|
{
|
||||||
|
private void GenerateJsonContext(SourceProductionContext context, ImmutableArray<INamedTypeSymbol> symbols)
|
||||||
|
{
|
||||||
|
if (symbols.IsDefaultOrEmpty)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append(@"
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Text.Json.Serialization.Metadata;
|
||||||
|
|
||||||
|
namespace Ghost.Engine.Components.Serialization
|
||||||
|
{
|
||||||
|
[JsonSourceGenerationOptions(WriteIndented = true, IncludeFields = true)]");
|
||||||
|
|
||||||
|
foreach (var symbol in symbols.Distinct(SymbolEqualityComparer.Default))
|
||||||
|
{
|
||||||
|
if (symbol is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fqtn = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||||
|
sb.Append($@"
|
||||||
|
[JsonSerializable(typeof({fqtn}))]");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append(@"
|
||||||
|
public partial class ComponentJsonContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<Type, JsonTypeInfo> _typeLookup = new()
|
||||||
|
{");
|
||||||
|
|
||||||
|
foreach (var symbol in symbols.Distinct(SymbolEqualityComparer.Default))
|
||||||
|
{
|
||||||
|
if (symbol is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fqtn = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
|
||||||
|
sb.Append($@"
|
||||||
|
{{ typeof({fqtn}), ComponentJsonContext.{symbol.Name} }},");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append(@"
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to retrieve the generated JsonTypeInfo for a given component type.
|
||||||
|
/// </summary>
|
||||||
|
public static bool TryGetTypeInfo(Type componentType, out JsonTypeInfo jsonTypeInfo) =>
|
||||||
|
_typeLookup.TryGetValue(componentType, out jsonTypeInfo);
|
||||||
|
}
|
||||||
|
}");
|
||||||
|
|
||||||
|
context.AddSource("ComponentJsonContext.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||||
|
{
|
||||||
|
var componentCandidates = context.SyntaxProvider
|
||||||
|
.CreateSyntaxProvider(
|
||||||
|
predicate: (syntaxNode, _) => syntaxNode is StructDeclarationSyntax,
|
||||||
|
transform: (ctx, _) =>
|
||||||
|
{
|
||||||
|
var structSyntax = (StructDeclarationSyntax)ctx.Node;
|
||||||
|
|
||||||
|
if (!(ctx.SemanticModel.GetDeclaredSymbol(structSyntax) is INamedTypeSymbol symbol))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var compilation = ctx.SemanticModel.Compilation;
|
||||||
|
var iComponentDataSymbol = compilation.GetTypeByMetadataName("Ghost.Entities.Components.IComponentData");
|
||||||
|
|
||||||
|
if (iComponentDataSymbol == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var iface in symbol.AllInterfaces)
|
||||||
|
{
|
||||||
|
if (SymbolEqualityComparer.Default.Equals(iface, iComponentDataSymbol))
|
||||||
|
{
|
||||||
|
return symbol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.Where(symbol => symbol != null)
|
||||||
|
.Collect();
|
||||||
|
|
||||||
|
context.RegisterSourceOutput(componentCandidates, GenerateJsonContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
Ghost.Generator/Ghost.Generator.csproj
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -3,7 +3,7 @@ using Ghost.Data.Services;
|
|||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace Ghost.Editor;
|
namespace Ghost.App;
|
||||||
|
|
||||||
internal static class ActivationHandler
|
internal static class ActivationHandler
|
||||||
{
|
{
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<Application
|
<Application
|
||||||
x:Class="Ghost.Editor.App"
|
x:Class="Ghost.App.GhostApplication"
|
||||||
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:local="using:Ghost.Editor">
|
xmlns:local="using:Ghost.App">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
101
Ghost.InternalEditor/App.xaml.cs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
using Ghost.App.Infrastructures.AppState;
|
||||||
|
using Ghost.App.Services;
|
||||||
|
using Ghost.App.Utilities;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
// To learn more about WinUI, the WinUI project structure,
|
||||||
|
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||||
|
|
||||||
|
namespace Ghost.App;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides application-specific behavior to supplement the default Application class.
|
||||||
|
/// </summary>
|
||||||
|
public partial class GhostApplication : Application
|
||||||
|
{
|
||||||
|
private Window? _window;
|
||||||
|
|
||||||
|
internal static Window? Window
|
||||||
|
{
|
||||||
|
get => (Current as GhostApplication)!._window;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Current is GhostApplication app)
|
||||||
|
{
|
||||||
|
app._window = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal IHost Host
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the singleton application object. This is the first line of authored code
|
||||||
|
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||||
|
/// </summary>
|
||||||
|
internal GhostApplication()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
Host = Microsoft.Extensions.Hosting.Host.
|
||||||
|
CreateDefaultBuilder().
|
||||||
|
UseContentRoot(AppContext.BaseDirectory).
|
||||||
|
ConfigureServices((context, services) =>
|
||||||
|
{
|
||||||
|
HostHelper.AddLandingScope(context, services);
|
||||||
|
HostHelper.AddEngineScope(context, services);
|
||||||
|
|
||||||
|
services.AddSingleton<AppStateMachine>();
|
||||||
|
services.AddSingleton<StackedNotificationService>();
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
UnhandledException += App_UnhandledException;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static IServiceScope CreateScope()
|
||||||
|
{
|
||||||
|
return (Current as GhostApplication)!.Host.Services.CreateScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T GetService<T>() where T : class
|
||||||
|
{
|
||||||
|
if ((Current as GhostApplication)!.Host.Services.GetService(typeof(T)) is not T service)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"{typeof(T)} needs to be registered in ConfigureServices within App.xaml.cs.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return service;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when the application is launched.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">Details about the launch request and process.</param>
|
||||||
|
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
||||||
|
{
|
||||||
|
base.OnLaunched(args);
|
||||||
|
|
||||||
|
ActivationHandler.Handle(args);
|
||||||
|
|
||||||
|
Host.Start();
|
||||||
|
|
||||||
|
var stateMachine = GetService<AppStateMachine>();
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
// TODO: Log and handle exceptions as appropriate.
|
||||||
|
// 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,4 +1,4 @@
|
|||||||
namespace Ghost.Editor.Contracts;
|
namespace Ghost.App.Contracts;
|
||||||
|
|
||||||
internal interface INavigationAware
|
internal interface INavigationAware
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
|
||||||
namespace Ghost.Editor.Contracts;
|
namespace Ghost.App.Contracts;
|
||||||
|
|
||||||
internal interface INotificationService
|
internal interface INotificationService
|
||||||
{
|
{
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using Ghost.Editor.Contracts;
|
using Ghost.App.Contracts;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using Microsoft.UI.Xaml.Navigation;
|
using Microsoft.UI.Xaml.Navigation;
|
||||||
|
|
||||||
namespace Ghost.Editor.Controls;
|
namespace Ghost.App.Controls;
|
||||||
|
|
||||||
public abstract partial class ViewModelPage<VM> : Page
|
public abstract partial class ViewModelPage<VM> : Page
|
||||||
where VM : ObservableObject
|
where VM : ObservableObject
|
||||||
185
Ghost.InternalEditor/Ghost.App.csproj
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework>
|
||||||
|
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||||
|
<RootNamespace>Ghost.App</RootNamespace>
|
||||||
|
<Platforms>x86;x64;ARM64</Platforms>
|
||||||
|
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||||
|
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
||||||
|
<UseWinUI>true</UseWinUI>
|
||||||
|
<EnableMsixTooling>true</EnableMsixTooling>
|
||||||
|
<LangVersion>preview</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Remove="Assets\icon-256.png" />
|
||||||
|
<Content Remove="Assets\Icon.altform-lightunplated_targetsize-16.png" />
|
||||||
|
<Content Remove="Assets\Icon.altform-lightunplated_targetsize-24.png" />
|
||||||
|
<Content Remove="Assets\Icon.altform-lightunplated_targetsize-256.png" />
|
||||||
|
<Content Remove="Assets\Icon.altform-lightunplated_targetsize-32.png" />
|
||||||
|
<Content Remove="Assets\Icon.altform-lightunplated_targetsize-48.png" />
|
||||||
|
<Content Remove="Assets\Icon.altform-unplated_targetsize-16.png" />
|
||||||
|
<Content Remove="Assets\Icon.altform-unplated_targetsize-24.png" />
|
||||||
|
<Content Remove="Assets\Icon.altform-unplated_targetsize-256.png" />
|
||||||
|
<Content Remove="Assets\Icon.altform-unplated_targetsize-32.png" />
|
||||||
|
<Content Remove="Assets\Icon.altform-unplated_targetsize-48.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Assets\Icon.scale-100.png" />
|
||||||
|
<None Remove="Assets\Icon.scale-125.png" />
|
||||||
|
<None Remove="Assets\Icon.scale-150.png" />
|
||||||
|
<None Remove="Assets\Icon.scale-200.png" />
|
||||||
|
<None Remove="Assets\Icon.scale-400.png" />
|
||||||
|
<None Remove="Assets\Icon.targetsize-16.png" />
|
||||||
|
<None Remove="Assets\Icon.targetsize-16_altform-unplated.png" />
|
||||||
|
<None Remove="Assets\Icon.targetsize-24.png" />
|
||||||
|
<None Remove="Assets\Icon.targetsize-24_altform-lightunplated.png" />
|
||||||
|
<None Remove="Assets\Icon.targetsize-256.png" />
|
||||||
|
<None Remove="Assets\Icon.targetsize-256_altform-unplated.png" />
|
||||||
|
<None Remove="Assets\Icon.targetsize-32.png" />
|
||||||
|
<None Remove="Assets\Icon.targetsize-32_altform-lightunplated.png" />
|
||||||
|
<None Remove="Assets\Icon.targetsize-48.png" />
|
||||||
|
<None Remove="Assets\Icon.targetsize-48_altform-unplated.png" />
|
||||||
|
<None Remove="Controls\BasicInput\PropertyField.xaml" />
|
||||||
|
<None Remove="Controls\EditorControls.xaml" />
|
||||||
|
<None Remove="Controls\Internal\InspectorView.xaml" />
|
||||||
|
<None Remove="Controls\Internal\InternalControls.xaml" />
|
||||||
|
<None Remove="View\Pages\EngineEditor\ConsolePage.xaml" />
|
||||||
|
<None Remove="View\Pages\EngineEditor\HierarchyPage.xaml" />
|
||||||
|
<None Remove="View\Pages\EngineEditor\ProjectPage.xaml" />
|
||||||
|
<None Remove="View\Pages\Landing\CreateProjectPage.xaml" />
|
||||||
|
<None Remove="View\Pages\Landing\OpenProjectPage.xaml" />
|
||||||
|
<None Remove="View\Windows\EngineEditorWindow.xaml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Remove="App.xaml" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Assets\SplashScreen.scale-200.png" />
|
||||||
|
<Content Include="Assets\LockScreenLogo.scale-200.png" />
|
||||||
|
<Content Include="Assets\Square150x150Logo.scale-200.png" />
|
||||||
|
<Content Include="Assets\StoreLogo.png" />
|
||||||
|
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Manifest Include="$(ApplicationManifest)" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
||||||
|
Tools extension to be activated for this project even if the Windows App SDK Nuget
|
||||||
|
package has not yet been restored.
|
||||||
|
-->
|
||||||
|
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||||
|
<ProjectCapability Include="Msix" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
|
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
|
||||||
|
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.250402" />
|
||||||
|
<PackageReference Include="CommunityToolkit.WinUI.Controls.TabbedCommandBar" Version="8.2.250402" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.5" />
|
||||||
|
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
|
||||||
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
|
||||||
|
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
|
||||||
|
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
|
||||||
|
<PackageReference Include="WinUIEx" Version="2.5.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Ghost.Data\Ghost.Data.csproj" />
|
||||||
|
<ProjectReference Include="..\Ghost.Editor\Ghost.Editor.csproj" />
|
||||||
|
<ProjectReference Include="..\Ghost.Engine\Ghost.Engine.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="View\Pages\Landing\CreateProjectPage.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="View\Window\Landing.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="View\Pages\Landing\OpenProjectPage.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Resources\" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Misaki.HighPerformance.Unsafe">
|
||||||
|
<HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="View\Pages\EngineEditor\HierarchyPage.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="View\Pages\EngineEditor\ProjectPage.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="View\Pages\EngineEditor\ConsolePage.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Themes\Override.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Controls\Internal\InternalControls.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Controls\Internal\InspectorView.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="View\Windows\EngineEditorWindow.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Controls\BasicInput\PropertyField.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Controls\EditorControls.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup Label="Globals" />
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
|
||||||
|
Explorer "Package and Publish" context menu entry to be enabled for this project even if
|
||||||
|
the Windows App SDK Nuget package has not yet been restored.
|
||||||
|
-->
|
||||||
|
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||||
|
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- Publish Properties -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||||
|
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||||
|
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<SupportedOSPlatformVersion>10.0.20348.0</SupportedOSPlatformVersion>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
<PublishAot>False</PublishAot>
|
||||||
|
<PublishTrimmed>False</PublishTrimmed>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ghost.Editor.Infrastructures.AppState;
|
namespace Ghost.App.Infrastructures.AppState;
|
||||||
|
|
||||||
internal class AppStateMachine
|
internal class AppStateMachine
|
||||||
{
|
{
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
using Ghost.Data.Models;
|
using Ghost.App.View.Windows;
|
||||||
|
using Ghost.Data.Models;
|
||||||
using Ghost.Data.Services;
|
using Ghost.Data.Services;
|
||||||
using Ghost.Editor.View.Windows;
|
|
||||||
using Ghost.Engine;
|
using Ghost.Engine;
|
||||||
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ghost.Editor.Infrastructures.AppState;
|
namespace Ghost.App.Infrastructures.AppState;
|
||||||
|
|
||||||
internal class EditorState : IAppState
|
internal class EditorState : IAppState
|
||||||
{
|
{
|
||||||
@@ -13,9 +14,9 @@ internal class EditorState : IAppState
|
|||||||
|
|
||||||
public Task OnExitingAsync()
|
public Task OnExitingAsync()
|
||||||
{
|
{
|
||||||
if (App.Window == _window)
|
if (GhostApplication.Window == _window)
|
||||||
{
|
{
|
||||||
App.Window = null;
|
GhostApplication.Window = null;
|
||||||
}
|
}
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -24,18 +25,18 @@ internal class EditorState : IAppState
|
|||||||
{
|
{
|
||||||
if (parameter is not ProjectMetadataInfo metadataInfo)
|
if (parameter is not ProjectMetadataInfo metadataInfo)
|
||||||
{
|
{
|
||||||
throw new System.ArgumentException("Parameter must be of type ProjectMetadata.", nameof(parameter));
|
throw new ArgumentException("Parameter must be of type ProjectMetadata.", nameof(parameter));
|
||||||
}
|
}
|
||||||
|
|
||||||
ProjectService.CurrentProject = metadataInfo;
|
ProjectService.CurrentProject = metadataInfo;
|
||||||
|
|
||||||
_engineCore = App.GetService<EngineCore>();
|
_engineCore = GhostApplication.GetService<EngineCore>();
|
||||||
await _engineCore.StartAsync(new Engine.Models.LaunchArgument());
|
await _engineCore.StartAsync(new Engine.Models.LaunchArgument());
|
||||||
|
|
||||||
_window = App.GetService<EngineEditorWindow>();
|
_window = GhostApplication.GetService<EngineEditorWindow>();
|
||||||
_window.Activate();
|
_window.Activate();
|
||||||
|
|
||||||
App.Window = _window;
|
GhostApplication.Window = _window;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnExitedAsync()
|
public async Task OnExitedAsync()
|
||||||
@@ -45,9 +46,9 @@ internal class EditorState : IAppState
|
|||||||
await _engineCore.ShutDownAsync();
|
await _engineCore.ShutDownAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (App.Window == _window)
|
if (GhostApplication.Window == _window)
|
||||||
{
|
{
|
||||||
App.Window = null;
|
GhostApplication.Window = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_window?.Close();
|
_window?.Close();
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ghost.Editor.Infrastructures.AppState;
|
namespace Ghost.App.Infrastructures.AppState;
|
||||||
|
|
||||||
internal interface IAppState
|
internal interface IAppState
|
||||||
{
|
{
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using Ghost.Editor.View.Windows;
|
using Ghost.App.View.Windows;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ghost.Editor.Infrastructures.AppState;
|
namespace Ghost.App.Infrastructures.AppState;
|
||||||
|
|
||||||
internal class LandingState : IAppState
|
internal class LandingState : IAppState
|
||||||
{
|
{
|
||||||
@@ -9,17 +9,17 @@ internal class LandingState : IAppState
|
|||||||
|
|
||||||
public Task OnExitingAsync()
|
public Task OnExitingAsync()
|
||||||
{
|
{
|
||||||
if (App.Window == _window)
|
if (GhostApplication.Window == _window)
|
||||||
{
|
{
|
||||||
App.Window = null;
|
GhostApplication.Window = null;
|
||||||
}
|
}
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task OnEnteringAsync(object? parameter)
|
public Task OnEnteringAsync(object? parameter)
|
||||||
{
|
{
|
||||||
_window = App.GetService<LandingWindow>();
|
_window = GhostApplication.GetService<LandingWindow>();
|
||||||
App.Window = _window;
|
GhostApplication.Window = _window;
|
||||||
|
|
||||||
_window.Activate();
|
_window.Activate();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@@ -27,9 +27,9 @@ internal class LandingState : IAppState
|
|||||||
|
|
||||||
public Task OnExitedAsync()
|
public Task OnExitedAsync()
|
||||||
{
|
{
|
||||||
if (App.Window == _window)
|
if (GhostApplication.Window == _window)
|
||||||
{
|
{
|
||||||
App.Window = null;
|
GhostApplication.Window = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_window?.Close();
|
_window?.Close();
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Ghost.Editor.Infrastructures.AppState;
|
namespace Ghost.App.Infrastructures.AppState;
|
||||||
|
|
||||||
internal enum StateKey
|
internal enum StateKey
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Ghost.Editor.Models;
|
namespace Ghost.App.Models;
|
||||||
|
|
||||||
internal struct AssetItem()
|
internal struct AssetItem()
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
namespace Ghost.Editor.Models;
|
namespace Ghost.App.Models;
|
||||||
|
|
||||||
internal class ExplorerItem(string name, string path, bool isDirectory)
|
internal class ExplorerItem(string name, string path, bool isDirectory)
|
||||||
{
|
{
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ghost.Editor.Services;
|
namespace Ghost.App.Services;
|
||||||
|
|
||||||
public class StackedNotificationService
|
public class StackedNotificationService
|
||||||
{
|
{
|
||||||
@@ -11,4 +11,7 @@
|
|||||||
<StaticResource x:Key="TabViewItemHeaderBackgroundSelected" ResourceKey="ControlFillColorSecondaryBrush" />
|
<StaticResource x:Key="TabViewItemHeaderBackgroundSelected" ResourceKey="ControlFillColorSecondaryBrush" />
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</ResourceDictionary.ThemeDictionaries>
|
</ResourceDictionary.ThemeDictionaries>
|
||||||
|
<Style TargetType="TabView">
|
||||||
|
<Setter Property="TabWidthMode" Value="Compact" />
|
||||||
|
</Style>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Ghost.Editor.Helpers;
|
namespace Ghost.App.Utilities;
|
||||||
|
|
||||||
public static class ComponentTypeCache
|
public static class ComponentTypeCache
|
||||||
{
|
{
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace Ghost.Editor.Helpers.Converters;
|
namespace Ghost.Editor.Utilities.Converters;
|
||||||
|
|
||||||
public partial class AssetPathToGlyphConverter : IValueConverter
|
public partial class AssetPathToGlyphConverter : IValueConverter
|
||||||
{
|
{
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using Microsoft.UI.Xaml.Data;
|
using Microsoft.UI.Xaml.Data;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ghost.Editor.Helpers.Converters;
|
namespace Ghost.Editor.Utilities.Converters;
|
||||||
|
|
||||||
public partial class GetDirectoryNameConverter : IValueConverter
|
public partial class GetDirectoryNameConverter : IValueConverter
|
||||||
{
|
{
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using Ghost.Data.Services;
|
using Ghost.App.View.Pages.EngineEditor;
|
||||||
using Ghost.Editor.View.Pages.EngineEditor;
|
using Ghost.App.View.Pages.Landing;
|
||||||
using Ghost.Editor.View.Pages.Landing;
|
using Ghost.App.View.Windows;
|
||||||
using Ghost.Editor.View.Windows;
|
using Ghost.Data.Services;
|
||||||
using Ghost.Editor.ViewModels.Pages.EngineEditor;
|
using Ghost.Editor.ViewModels.Pages.EngineEditor;
|
||||||
using Ghost.Editor.ViewModels.Pages.Landing;
|
using Ghost.Editor.ViewModels.Pages.Landing;
|
||||||
using Ghost.Editor.ViewModels.Windows;
|
using Ghost.Editor.ViewModels.Windows;
|
||||||
@@ -9,7 +9,7 @@ using Ghost.Engine;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
namespace Ghost.Editor.Helpers;
|
namespace Ghost.App.Utilities;
|
||||||
|
|
||||||
internal static partial class HostHelper
|
internal static partial class HostHelper
|
||||||
{
|
{
|
||||||
@@ -5,14 +5,14 @@ using Windows.Storage;
|
|||||||
using Windows.Storage.Pickers;
|
using Windows.Storage.Pickers;
|
||||||
using WinRT.Interop;
|
using WinRT.Interop;
|
||||||
|
|
||||||
namespace Ghost.Editor.Helpers;
|
namespace Ghost.App.Utilities;
|
||||||
|
|
||||||
public static class SystemUtilities
|
public static class SystemUtilities
|
||||||
{
|
{
|
||||||
public static async Task<StorageFolder?> OpenFolderPickerAsync(PickerLocationId startLocation = PickerLocationId.DocumentsLibrary, string settingsIdentifier = "")
|
public static async Task<StorageFolder?> OpenFolderPickerAsync(PickerLocationId startLocation = PickerLocationId.DocumentsLibrary, string settingsIdentifier = "")
|
||||||
{
|
{
|
||||||
var openPicker = new FolderPicker();
|
var openPicker = new FolderPicker();
|
||||||
var hWnd = WindowNative.GetWindowHandle(App.Window);
|
var hWnd = WindowNative.GetWindowHandle(GhostApplication.Window);
|
||||||
InitializeWithWindow.Initialize(openPicker, hWnd);
|
InitializeWithWindow.Initialize(openPicker, hWnd);
|
||||||
|
|
||||||
openPicker.SuggestedStartLocation = startLocation;
|
openPicker.SuggestedStartLocation = startLocation;
|
||||||
@@ -26,7 +26,7 @@ public static class SystemUtilities
|
|||||||
public static async Task<StorageFile?> OpenFilePickerAsync(PickerLocationId startLocation = PickerLocationId.DocumentsLibrary, string settingsIdentifier = "", params IEnumerable<string> filter)
|
public static async Task<StorageFile?> OpenFilePickerAsync(PickerLocationId startLocation = PickerLocationId.DocumentsLibrary, string settingsIdentifier = "", params IEnumerable<string> filter)
|
||||||
{
|
{
|
||||||
var openPicker = new FileOpenPicker();
|
var openPicker = new FileOpenPicker();
|
||||||
var hWnd = WindowNative.GetWindowHandle(App.Window);
|
var hWnd = WindowNative.GetWindowHandle(GhostApplication.Window);
|
||||||
InitializeWithWindow.Initialize(openPicker, hWnd);
|
InitializeWithWindow.Initialize(openPicker, hWnd);
|
||||||
|
|
||||||
openPicker.SuggestedStartLocation = startLocation;
|
openPicker.SuggestedStartLocation = startLocation;
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<Page
|
<Page
|
||||||
x:Class="Ghost.Editor.View.Pages.EngineEditor.ConsolePage"
|
x:Class="Ghost.App.View.Pages.EngineEditor.ConsolePage"
|
||||||
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:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:local="using:Ghost.Editor.View.Pages.EngineEditor"
|
xmlns:local="using:Ghost.App.View.Pages.EngineEditor"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using Ghost.Editor.ViewModels.Pages.EngineEditor;
|
using Ghost.Editor.ViewModels.Pages.EngineEditor;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
|
||||||
namespace Ghost.Editor.View.Pages.EngineEditor;
|
namespace Ghost.App.View.Pages.EngineEditor;
|
||||||
|
|
||||||
internal sealed partial class ConsolePage : Page
|
internal sealed partial class ConsolePage : Page
|
||||||
{
|
{
|
||||||
@@ -12,7 +12,7 @@ internal sealed partial class ConsolePage : Page
|
|||||||
|
|
||||||
public ConsolePage()
|
public ConsolePage()
|
||||||
{
|
{
|
||||||
ViewModel = App.GetService<ConsoleViewModel>();
|
ViewModel = GhostApplication.GetService<ConsoleViewModel>();
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<Page
|
<Page
|
||||||
x:Class="Ghost.Editor.View.Pages.EngineEditor.HierarchyPage"
|
x:Class="Ghost.App.View.Pages.EngineEditor.HierarchyPage"
|
||||||
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:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:local="using:Ghost.Editor.View.Pages.EngineEditor"
|
xmlns:local="using:Ghost.App.View.Pages.EngineEditor"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:sg="using:Ghost.Editor.Infrastructures.SceneGraph"
|
xmlns:sg="using:Ghost.Editor.SceneGraph"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<Page.Resources>
|
<Page.Resources>
|
||||||
<DataTemplate x:Key="SceneTemplate" x:DataType="sg:SceneGraphNode">
|
<DataTemplate x:Key="SceneTemplate" x:DataType="sg:SceneGraphNode">
|
||||||
<TreeViewItem
|
<TreeViewItem
|
||||||
AutomationProperties.Name="{x:Bind Name}"
|
AutomationProperties.Name="{x:Bind Name}"
|
||||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
Background="{ThemeResource ControlSolidFillColorDefaultBrush}"
|
||||||
IsExpanded="True"
|
IsExpanded="True"
|
||||||
ItemsSource="{x:Bind Children}">
|
ItemsSource="{x:Bind Children}">
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
@@ -33,10 +33,10 @@
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</Page.Resources>
|
</Page.Resources>
|
||||||
|
|
||||||
<Grid Background="{ThemeResource LayerFillColorDefaultBrush}">
|
<Grid Padding="4,6" Background="{ThemeResource LayerFillColorDefaultBrush}">
|
||||||
<TreeView ItemsSource="{x:Bind ViewModel.SceneList}">
|
<TreeView ItemsSource="{x:Bind ViewModel.SceneList}">
|
||||||
<TreeView.ItemTemplateSelector>
|
<TreeView.ItemTemplateSelector>
|
||||||
<local:HierarchyTemplateSector EntityTemplate="{StaticResource EntityTemplate}" SceneTemplate="{StaticResource SceneTemplate}" />
|
<local:HierarchyTemplateSector EntityTemplate="{StaticResource EntityTemplate}" WorldTemplate="{StaticResource SceneTemplate}" />
|
||||||
</TreeView.ItemTemplateSelector>
|
</TreeView.ItemTemplateSelector>
|
||||||
</TreeView>
|
</TreeView>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Ghost.Editor.Infrastructures.SceneGraph;
|
using Ghost.Editor.SceneGraph;
|
||||||
using Ghost.Editor.ViewModels.Pages.EngineEditor;
|
using Ghost.Editor.ViewModels.Pages.EngineEditor;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
@@ -6,7 +6,7 @@ using Microsoft.UI.Xaml.Controls;
|
|||||||
// To learn more about WinUI, the WinUI project structure,
|
// To learn more about WinUI, the WinUI project structure,
|
||||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||||
|
|
||||||
namespace Ghost.Editor.View.Pages.EngineEditor;
|
namespace Ghost.App.View.Pages.EngineEditor;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -19,7 +19,7 @@ internal sealed partial class HierarchyPage : Page
|
|||||||
|
|
||||||
public HierarchyPage()
|
public HierarchyPage()
|
||||||
{
|
{
|
||||||
ViewModel = App.GetService<HierarchyViewModel>();
|
ViewModel = GhostApplication.GetService<HierarchyViewModel>();
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ internal sealed partial class HierarchyPage : Page
|
|||||||
|
|
||||||
internal partial class HierarchyTemplateSector : DataTemplateSelector
|
internal partial class HierarchyTemplateSector : DataTemplateSelector
|
||||||
{
|
{
|
||||||
public DataTemplate? SceneTemplate
|
public DataTemplate? WorldTemplate
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
@@ -41,16 +41,16 @@ internal partial class HierarchyTemplateSector : DataTemplateSelector
|
|||||||
|
|
||||||
protected override DataTemplate SelectTemplateCore(object item)
|
protected override DataTemplate SelectTemplateCore(object item)
|
||||||
{
|
{
|
||||||
if (SceneTemplate == null || EntityTemplate == null)
|
if (WorldTemplate == null || EntityTemplate == null)
|
||||||
{
|
{
|
||||||
return base.SelectTemplateCore(item);
|
return base.SelectTemplateCore(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
var node = (SceneGraphNode)item;
|
var node = (SceneGraphNode)item;
|
||||||
return node.Type switch
|
return node.NodeType switch
|
||||||
{
|
{
|
||||||
SceneGraphNode.NodeType.Scene => SceneTemplate,
|
SceneGraphNodeType.Scene => WorldTemplate,
|
||||||
SceneGraphNode.NodeType.Entity => EntityTemplate,
|
SceneGraphNodeType.Entity => EntityTemplate,
|
||||||
_ => base.SelectTemplateCore(item)
|
_ => base.SelectTemplateCore(item)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<Page
|
<Page
|
||||||
x:Class="Ghost.Editor.View.Pages.EngineEditor.ProjectPage"
|
x:Class="Ghost.App.View.Pages.EngineEditor.ProjectPage"
|
||||||
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:converter="using:Ghost.Editor.Helpers.Converters"
|
xmlns:converter="using:Ghost.Editor.Utilities.Converters"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:local="using:Ghost.Editor.View.Pages.EngineEditor"
|
xmlns:local="using:Ghost.App.View.Pages.EngineEditor"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:model="using:Ghost.Editor.Models"
|
xmlns:model="using:Ghost.App.Models"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<Page.Resources>
|
<Page.Resources>
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
BorderBrush="{ThemeResource CardStrokeColorDefaultSolid}"
|
BorderBrush="{ThemeResource CardStrokeColorDefaultSolid}"
|
||||||
BorderThickness="0,0,1,0">
|
BorderThickness="0,0,1,0">
|
||||||
<TreeView
|
<TreeView
|
||||||
|
x:Name="DirectoryTreeView"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
ItemsSource="{x:Bind ViewModel.SubDirectories}"
|
ItemsSource="{x:Bind ViewModel.SubDirectories}"
|
||||||
@@ -81,6 +82,7 @@
|
|||||||
HorizontalScrollBarVisibility="Auto"
|
HorizontalScrollBarVisibility="Auto"
|
||||||
VerticalScrollBarVisibility="Auto">
|
VerticalScrollBarVisibility="Auto">
|
||||||
<GridView
|
<GridView
|
||||||
|
x:Name="AssetsGridView"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
ItemsSource="{x:Bind ViewModel.DirectoryAssets, Mode=OneWay}"
|
ItemsSource="{x:Bind ViewModel.DirectoryAssets, Mode=OneWay}"
|
||||||