Refactor activation handling and introduce entity system

Added new `ActivationHandler` class for folder initialization.
Added `ProjectService` class for project-related operations.
Added `Ghost.Entities` project with entity management classes.
Added `EngineEditorWindow` for enhanced user interface.

Changed project files to restructure dependencies and remove unused references.
Changed `ProjectRepository` to use asynchronous methods for improved performance.
Changed data binding in `CreateProjectPage.xaml` and `OpenProjectPage.xaml` to use new data models.
Changed `App.xaml.cs` to utilize the new `ActivationHandler` and include additional services.

Removed `IActivationHandler` interface and integrated its functionality into `ActivationHandler`.
Removed `EditorActivationHandler` as its functionality was merged into `ActivationHandler`.

Updated `AssemblyInfo.cs` to include global using directives for entity types.
Updated image assets to reflect visual resource changes.
This commit is contained in:
2025-03-27 00:52:07 +09:00
parent 02b3edcd7a
commit 62fe30ff2b
47 changed files with 711 additions and 231 deletions

View File

@@ -0,0 +1,4 @@
global using EntityID = System.UInt32;
global using GenerationID = System.UInt16;
global using WorldID = System.UInt16;

View File

@@ -0,0 +1,88 @@
using System.Runtime.CompilerServices;
namespace Ghost.Entities.Core;
[SkipLocalsInit]
public struct Entity : IEquatable<Entity>, IComparable<Entity>
{
private const EntityID _WORLD_INDEX_BITS = 4u;
private const EntityID _GENERATION_BITS = 8u;
private const EntityID _INDEX_BITS = sizeof(EntityID) * 8 - _WORLD_INDEX_BITS - _GENERATION_BITS;
private const EntityID _WORLD_INDEX_MASK = (1u << (int)_WORLD_INDEX_BITS) - 1;
private const EntityID _GENERATION_MASK = (1u << (int)_GENERATION_BITS) - 1;
private const EntityID _INDEX_MASK = (1u << (int)_INDEX_BITS) - 1;
private const EntityID _ID_MASK = EntityID.MaxValue;
private uint _id;
public readonly bool IsValid
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _id != _ID_MASK;
}
public readonly EntityID Index
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _id & _INDEX_MASK;
}
public readonly GenerationID Generation
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (GenerationID)((_id >> (int)_INDEX_BITS) & _GENERATION_MASK);
}
public readonly WorldID WorldIndex
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (WorldID)((_id >> (int)(_INDEX_BITS + _GENERATION_BITS)) & _WORLD_INDEX_MASK);
}
public void IncrementGeneration()
{
var generation = Generation + 1u;
if (generation >= _GENERATION_MASK)
{
throw new InvalidOperationException("Generation overflow");
}
_id = (_id & ~(_GENERATION_MASK << (int)_INDEX_BITS)) | (generation << (int)_INDEX_BITS);
}
internal Entity(EntityID index, EntityID generation, EntityID worldIndex)
{
_id = (worldIndex << (int)(_INDEX_BITS + _GENERATION_BITS)) | (generation << (int)_INDEX_BITS) | index;
}
public readonly bool Equals(Entity other)
{
return _id == other._id;
}
public readonly int CompareTo(Entity other)
{
return _id.CompareTo(other._id);
}
public override readonly bool Equals(object? obj)
{
return obj is Entity other && Equals(other);
}
public override readonly int GetHashCode()
{
return _id.GetHashCode();
}
public static bool operator ==(Entity left, Entity right)
{
return left.Equals(right);
}
public static bool operator !=(Entity left, Entity right)
{
return !(left == right);
}
}

View File

@@ -0,0 +1,5 @@
namespace Ghost.Entities.Core;
public readonly struct EntityInfo
{
}

View File

@@ -0,0 +1,141 @@
using Ghost.Entities.Helpers;
using Misaki.HighPerformance.Unsafe.Collections;
namespace Ghost.Entities.Core;
public partial struct World
{
public static UnsafeArray<World> Worlds
{
get;
private set;
} = new(4, AllocationType.UnInitialized);
public static UnsafeQueue<WorldID> FreeIndices
{
get;
private set;
} = new(4, AllocationType.UnInitialized);
public static ushort Count
{
get;
private set;
}
public static World Create(int chunkSizeInBytes = 16384, int minimumAmountOfEntitiesPerChunk = 100, int archetypeCapacity = 2, int entityCapacity = 64)
{
lock (ThreadLocker.WorldLock)
{
var recycle = FreeIndices.TryDequeue(out var id);
var recycledId = recycle ? id : Count;
var world = new World(recycledId, chunkSizeInBytes, minimumAmountOfEntitiesPerChunk, archetypeCapacity, entityCapacity);
if (recycledId >= Worlds.Size)
{
var newCapacity = Worlds.Size * 2;
Worlds.ReAlloc(newCapacity);
}
Worlds[recycledId] = world;
Count++;
return world;
}
}
}
public partial struct World
{
/// <summary>
/// The unique <see cref="World"/> ID.
/// </summary>
public int Id
{
get;
}
/// <summary>
/// The amount of <see cref="Entity"/>s currently stored by this <see cref="World"/>.
/// </summary>
public int Size
{
get; internal set;
}
/// <summary>
/// The available <see cref="Entity"/> capacity of this <see cref="World"/>.
/// </summary>
public int Capacity
{
get; internal set;
}
///// <summary>
///// All <see cref="Archetype"/>s that exist in this <see cref="World"/>.
///// </summary>
//public Archetypes Archetypes
//{
// get;
//}
///// <summary>
///// Maps an <see cref="Entity"/> to its <see cref="EntityInfo"/> for quick lookup.
///// </summary>
//internal EntityInfoStorage EntityInfo
//{
// get;
//}
///// <summary>
///// Stores recycled <see cref="Entity"/> IDs and their last version.
///// </summary>
//internal PooledQueue<RecycledEntity> RecycledIds
//{
// get; set;
//}
///// <summary>
///// A cache to map <see cref="QueryDescription"/> to their <see cref="Core.Query"/>, to avoid allocs.
///// </summary>
//internal PooledDictionary<QueryDescription, Query> QueryCache
//{
// get; set;
//}
/// <summary>
/// The <see cref="Chunk"/> size of each <see cref="Archetype"/> in bytes.
/// <remarks>For the best cache optimisation use values that are divisible by 16Kb.</remarks>
/// </summary>
public int BaseChunkSize { get; private set; } = 16_384;
/// <summary>
/// The minimum number of <see cref="Arch.Core.Entity"/>'s that should fit into a <see cref="Chunk"/> within all <see cref="Archetype"/>s.
/// On the basis of this, the <see cref="Archetypes"/>s chunk size may increase.
/// </summary>
public int BaseChunkEntityCount { get; private set; } = 100;
private World(int id, int baseChunkSize, int baseChunkEntityCount, int archetypeCapacity, int entityCapacity)
{
Id = id;
// Mapping.
//GroupToArchetype = new PooledDictionary<int, Archetype>(archetypeCapacity);
// Entity stuff.
//Archetypes = new Archetypes(archetypeCapacity);
//EntityInfo = new EntityInfoStorage(baseChunkSize, entityCapacity);
//RecycledIds = new PooledQueue<RecycledEntity>(entityCapacity);
// Query.
//QueryCache = new PooledDictionary<QueryDescription, Query>(archetypeCapacity);
// Multithreading/Jobs.
//JobHandles = new PooledList<JobHandle>(Environment.ProcessorCount);
//JobsCache = new List<IJob>(Environment.ProcessorCount);
// Config
BaseChunkSize = baseChunkSize;
BaseChunkEntityCount = baseChunkEntityCount;
}
}

View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<Reference Include="Misaki.HighPerformance.Unsafe">
<HintPath>..\..\source\Class\Misaki.HighPerformance\Misaki.HighPerformance.Unsafe\bin\Release\net9.0\Misaki.HighPerformance.Unsafe.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,7 @@
namespace Ghost.Entities.Helpers;
internal static class ThreadLocker
{
private static Lock? _worldLock;
public static Lock WorldLock => _worldLock ??= new();
}