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:
4
Ghost.Entities/AssemblyInfo.cs
Normal file
4
Ghost.Entities/AssemblyInfo.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
global using EntityID = System.UInt32;
|
||||
|
||||
global using GenerationID = System.UInt16;
|
||||
global using WorldID = System.UInt16;
|
||||
88
Ghost.Entities/Core/Entity.cs
Normal file
88
Ghost.Entities/Core/Entity.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
5
Ghost.Entities/Core/EntityInfo.cs
Normal file
5
Ghost.Entities/Core/EntityInfo.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace Ghost.Entities.Core;
|
||||
|
||||
public readonly struct EntityInfo
|
||||
{
|
||||
}
|
||||
141
Ghost.Entities/Core/World.cs
Normal file
141
Ghost.Entities/Core/World.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
24
Ghost.Entities/Ghost.Entities.csproj
Normal file
24
Ghost.Entities/Ghost.Entities.csproj
Normal 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>
|
||||
7
Ghost.Entities/Helpers/ThreadLocker.cs
Normal file
7
Ghost.Entities/Helpers/ThreadLocker.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Ghost.Entities.Helpers;
|
||||
|
||||
internal static class ThreadLocker
|
||||
{
|
||||
private static Lock? _worldLock;
|
||||
public static Lock WorldLock => _worldLock ??= new();
|
||||
}
|
||||
Reference in New Issue
Block a user