Compare commits
59 Commits
feature/ar
...
9bae3e647e
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bae3e647e | |||
| 6cadd8edeb | |||
| 3e4084c42a | |||
| cce1cf7256 | |||
| 254b08bc81 | |||
| 912b320d8f | |||
| 8a3b40b4f8 | |||
| 619720feee | |||
| bfe8588d76 | |||
| b8af6e8c3a | |||
| 5e42d699c3 | |||
| 6f802ac12b | |||
| 162b71f309 | |||
| 30090f84ab | |||
| 93c58fa7fb | |||
| 78e3b4ef31 | |||
| db8ca971a8 | |||
| 638417d4f0 | |||
| 426786397c | |||
| 9bbccfc8f8 | |||
| eadd13931f | |||
| 59991f47d5 | |||
| 9fcf06dbe4 | |||
| 6505099667 | |||
| d263f0c7e1 | |||
| 9f05944d81 | |||
| e71851550b | |||
| 8a5795069f | |||
| b505c7c1c0 | |||
| 8d82c0a750 | |||
| 8df0b46960 | |||
| 06a150b899 | |||
| 49f54c6b43 | |||
| fdf831630b | |||
| ba5dc2159e | |||
| 0201f0fc33 | |||
| 364fbf9208 | |||
| e11a9ebb52 | |||
| 4173ff2432 | |||
| 139312d73b | |||
| 92b966fe0d | |||
| 1c155f962c | |||
| ac36bbf8c7 | |||
| 02df8d7732 | |||
| 954e3756aa | |||
| 1fc9df1812 | |||
| 87e315a588 | |||
| d71bdb3fc9 | |||
| 6a041f75ba | |||
| c9be05fc60 | |||
| f988c34b3d | |||
| a89719bfc9 | |||
| b8ce824292 | |||
| aa3d9c749b | |||
| d23e701f0a | |||
| 2881fda112 | |||
| 840cf7dd5a | |||
| 00b4e82ded | |||
| 3118021272 |
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.dll filter=lfs diff=lfs merge=lfs -text
|
||||
1
.gitignore
vendored
@@ -9,6 +9,7 @@
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
AGENTS.md
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
namespace Ghost.Core.Graphics;
|
||||
|
||||
public enum ZTestOptions
|
||||
{
|
||||
Disabled,
|
||||
Less,
|
||||
LessEqual,
|
||||
Equal,
|
||||
GreaterEqual,
|
||||
Greater,
|
||||
NotEqual,
|
||||
Always
|
||||
}
|
||||
|
||||
public enum ZWriteOptions
|
||||
{
|
||||
Off,
|
||||
On
|
||||
}
|
||||
|
||||
public enum CullOptions
|
||||
{
|
||||
Off,
|
||||
Front,
|
||||
Back
|
||||
}
|
||||
|
||||
public enum BlendOptions
|
||||
{
|
||||
Opaque,
|
||||
Alpha,
|
||||
Additive,
|
||||
Multiply,
|
||||
PremultipliedAlpha
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
namespace Ghost.Core.Graphics;
|
||||
|
||||
public enum KeywordType
|
||||
{
|
||||
Static,
|
||||
Dynamic,
|
||||
}
|
||||
|
||||
public enum ShaderPropertyType
|
||||
{
|
||||
None,
|
||||
Float, Float2, Float3, Float4,
|
||||
Float4x4,
|
||||
Int, Int2, Int3, Int4,
|
||||
UInt, UInt2, UInt3, UInt4,
|
||||
Bool, Bool2, Bool3, Bool4,
|
||||
Texture2D, Texture3D, TextureCube,
|
||||
Texture2DArray, TextureCubeArray,
|
||||
Sampler
|
||||
}
|
||||
|
||||
public struct ShaderEntryPoint
|
||||
{
|
||||
public string entry;
|
||||
public string shader;
|
||||
|
||||
public readonly bool IsCreated => !string.IsNullOrEmpty(entry) && !string.IsNullOrEmpty(shader);
|
||||
}
|
||||
|
||||
public struct KeywordsGroup
|
||||
{
|
||||
public KeywordType type;
|
||||
public List<string>? keywords;
|
||||
}
|
||||
|
||||
public struct PipelineDescriptor
|
||||
{
|
||||
public ZTestOptions zTest;
|
||||
public ZWriteOptions zWrite;
|
||||
public CullOptions cull;
|
||||
public BlendOptions blend;
|
||||
public uint colorMask;
|
||||
|
||||
public static PipelineDescriptor Default = new PipelineDescriptor
|
||||
{
|
||||
zTest = ZTestOptions.LessEqual,
|
||||
zWrite = ZWriteOptions.On,
|
||||
cull = CullOptions.Back,
|
||||
blend = BlendOptions.Opaque,
|
||||
colorMask = 0
|
||||
};
|
||||
}
|
||||
|
||||
public interface IPassDescriptor
|
||||
{
|
||||
public string Identifier
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
|
||||
public struct PropertyDescriptor
|
||||
{
|
||||
public ShaderPropertyType type;
|
||||
public string name;
|
||||
public object? defaultValue;
|
||||
}
|
||||
|
||||
public class FullPassDescriptor : IPassDescriptor
|
||||
{
|
||||
public string uniqueIdentifier = string.Empty;
|
||||
public string name = string.Empty;
|
||||
|
||||
public ShaderEntryPoint taskShader;
|
||||
public ShaderEntryPoint meshShader;
|
||||
public ShaderEntryPoint pixelShader;
|
||||
public List<string>? defines;
|
||||
public List<KeywordsGroup>? keywords;
|
||||
public PipelineDescriptor localPipeline;
|
||||
|
||||
public string Identifier => uniqueIdentifier;
|
||||
public string Name => name;
|
||||
}
|
||||
|
||||
public class FallbackPassDescriptor : IPassDescriptor
|
||||
{
|
||||
public string fallbackPassIdentifier = string.Empty;
|
||||
public string name = string.Empty;
|
||||
|
||||
public string Identifier => fallbackPassIdentifier;
|
||||
public string Name => name;
|
||||
}
|
||||
|
||||
public class ShaderDescriptor
|
||||
{
|
||||
public string name = string.Empty;
|
||||
public string? generatedCodePath;
|
||||
public uint cbufferSize;
|
||||
public List<PropertyDescriptor> globalProperties = new();
|
||||
public List<PropertyDescriptor> properties = new();
|
||||
public List<IPassDescriptor> passes = new();
|
||||
}
|
||||
|
||||
public static class ShaderDescriptorExtensions
|
||||
{
|
||||
public static uint GetSize(this ShaderPropertyType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
ShaderPropertyType.Float => 4,
|
||||
ShaderPropertyType.Float2 => 8,
|
||||
ShaderPropertyType.Float3 => 12,
|
||||
ShaderPropertyType.Float4 => 16,
|
||||
ShaderPropertyType.Float4x4 => 64,
|
||||
ShaderPropertyType.Int => 4,
|
||||
ShaderPropertyType.Int2 => 8,
|
||||
ShaderPropertyType.Int3 => 12,
|
||||
ShaderPropertyType.Int4 => 16,
|
||||
ShaderPropertyType.UInt => 4,
|
||||
ShaderPropertyType.UInt2 => 8,
|
||||
ShaderPropertyType.UInt3 => 12,
|
||||
ShaderPropertyType.UInt4 => 16,
|
||||
ShaderPropertyType.Bool => 4,
|
||||
ShaderPropertyType.Bool2 => 8,
|
||||
ShaderPropertyType.Bool3 => 12,
|
||||
ShaderPropertyType.Bool4 => 16,
|
||||
ShaderPropertyType.Texture2D => 4, // Bindless resource use uint32
|
||||
ShaderPropertyType.Texture3D => 4,
|
||||
ShaderPropertyType.TextureCube => 4,
|
||||
ShaderPropertyType.Texture2DArray => 4,
|
||||
ShaderPropertyType.TextureCubeArray => 4,
|
||||
ShaderPropertyType.Sampler => 4,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
namespace Ghost.Core;
|
||||
|
||||
public interface IHandleType;
|
||||
public interface IIdentifierType;
|
||||
public interface IKeyType;
|
||||
|
||||
public readonly struct Handle<T> : IEquatable<Handle<T>>
|
||||
{
|
||||
public readonly int id;
|
||||
public readonly int generation;
|
||||
|
||||
public Handle(int id, int generation)
|
||||
{
|
||||
this.id = id;
|
||||
this.generation = generation;
|
||||
}
|
||||
|
||||
public static Handle<T> Invalid => new(-1, -1);
|
||||
|
||||
public readonly bool IsValid => this != Invalid;
|
||||
public readonly bool IsNotValid => this == Invalid;
|
||||
|
||||
public readonly override int GetHashCode()
|
||||
{
|
||||
return id + (generation << 16);
|
||||
}
|
||||
|
||||
public readonly override bool Equals(object? obj)
|
||||
{
|
||||
return obj is Handle<T> id && Equals(id);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Handle<{typeof(T).Name}>({id}, {generation})";
|
||||
}
|
||||
|
||||
public readonly bool Equals(Handle<T> other)
|
||||
{
|
||||
return id == other.id;
|
||||
}
|
||||
|
||||
public readonly int CompareTo(Handle<T> other)
|
||||
{
|
||||
return id.CompareTo(other.id);
|
||||
}
|
||||
|
||||
public static bool operator ==(Handle<T> a, Handle<T> b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Handle<T> a, Handle<T> b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct Identifier<T> : IEquatable<Identifier<T>>
|
||||
{
|
||||
public readonly int value;
|
||||
|
||||
public Identifier(int value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static Identifier<T> Invalid => new(-1);
|
||||
|
||||
public readonly bool IsValid => this != Invalid;
|
||||
public readonly bool IsNotValid => this == Invalid;
|
||||
|
||||
public readonly override int GetHashCode()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
public readonly override bool Equals(object? obj)
|
||||
{
|
||||
return obj is Identifier<T> id && Equals(id);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Identifier<{typeof(T).Name}>({value})";
|
||||
}
|
||||
|
||||
public readonly bool Equals(Identifier<T> other)
|
||||
{
|
||||
return value == other.value;
|
||||
}
|
||||
|
||||
public readonly int CompareTo(Identifier<T> other)
|
||||
{
|
||||
return value.CompareTo(other.value);
|
||||
}
|
||||
|
||||
public static bool operator ==(Identifier<T> a, Identifier<T> b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Identifier<T> a, Identifier<T> b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator <(Identifier<T> a, Identifier<T> b)
|
||||
{
|
||||
return a.value < b.value;
|
||||
}
|
||||
|
||||
public static bool operator >(Identifier<T> a, Identifier<T> b)
|
||||
{
|
||||
return a.value > b.value;
|
||||
}
|
||||
|
||||
public static bool operator <=(Identifier<T> a, Identifier<T> b)
|
||||
{
|
||||
return a.value <= b.value;
|
||||
}
|
||||
|
||||
public static bool operator >=(Identifier<T> a, Identifier<T> b)
|
||||
{
|
||||
return a.value >= b.value;
|
||||
}
|
||||
|
||||
public static implicit operator int(Identifier<T> id) => id.value;
|
||||
public static implicit operator Identifier<T>(int value) => new Identifier<T>(value);
|
||||
}
|
||||
|
||||
public readonly struct Key<T>
|
||||
{
|
||||
public readonly ulong value;
|
||||
|
||||
public Key(ulong value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static Key<T> Invalid => new(0);
|
||||
|
||||
public bool IsValid => this != Invalid;
|
||||
|
||||
public readonly override int GetHashCode()
|
||||
{
|
||||
return value.GetHashCode();
|
||||
}
|
||||
|
||||
public readonly override bool Equals(object? obj)
|
||||
{
|
||||
return obj is Key<T> id && Equals(id);
|
||||
}
|
||||
|
||||
public readonly bool Equals(Key<T> other)
|
||||
{
|
||||
return value == other.value;
|
||||
}
|
||||
|
||||
public readonly int CompareTo(Key<T> other)
|
||||
{
|
||||
return value.CompareTo(other.value);
|
||||
}
|
||||
|
||||
public static bool operator ==(Key<T> a, Key<T> b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(Key<T> a, Key<T> b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ghost.Core;
|
||||
|
||||
public enum LogLevel
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
|
||||
internal readonly struct LogMessage
|
||||
{
|
||||
public LogLevel Level
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public string Message
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public string? StackTrace
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public DateTime Timestamp
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public LogMessage(LogLevel level, string message, string? stackTrace = null)
|
||||
{
|
||||
Level = level;
|
||||
Message = message;
|
||||
StackTrace = stackTrace;
|
||||
Timestamp = DateTime.Now;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (StackTrace != null)
|
||||
{
|
||||
return $"{Timestamp:HH:mm:ss} [{Level}] {Message}\n{StackTrace}";
|
||||
}
|
||||
|
||||
return $"{Timestamp:HH:mm:ss} [{Level}] {Message}";
|
||||
}
|
||||
}
|
||||
|
||||
internal interface ILogger
|
||||
{
|
||||
public ReadOnlyObservableCollection<LogMessage> Logs
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public void Log(string message, LogLevel level);
|
||||
public void Log(Exception exception);
|
||||
public void Assert(bool condition, string message);
|
||||
public void Clear();
|
||||
}
|
||||
|
||||
// TODO: Add file logging.
|
||||
internal class LoggerImplementation : ILogger
|
||||
{
|
||||
private readonly ObservableCollection<LogMessage> _logs = new();
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
public ReadOnlyObservableCollection<LogMessage> Logs => new(_logs);
|
||||
|
||||
public void Log(string message, LogLevel level)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_logs.Add(new LogMessage(level, message));
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(Exception exception)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_logs.Add(new LogMessage(LogLevel.Error, exception.Message, exception.StackTrace));
|
||||
}
|
||||
}
|
||||
|
||||
public void Assert(bool condition, string message)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
Log(message, LogLevel.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_logs.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Logger
|
||||
{
|
||||
private static readonly ILogger s_logger = new LoggerImplementation();
|
||||
internal static ReadOnlyObservableCollection<LogMessage> Logs => s_logger.Logs;
|
||||
|
||||
public static void Log(LogLevel level, object? message)
|
||||
{
|
||||
s_logger.Log(message?.ToString() ?? "null", level);
|
||||
}
|
||||
|
||||
public static void Log(LogLevel level, string message)
|
||||
{
|
||||
s_logger.Log(message, level);
|
||||
}
|
||||
|
||||
public static void LogInfo(object? message)
|
||||
{
|
||||
s_logger.Log(message?.ToString() ?? "null", LogLevel.Info);
|
||||
}
|
||||
|
||||
public static void LogInfo(string message)
|
||||
{
|
||||
s_logger.Log(message, LogLevel.Info);
|
||||
}
|
||||
|
||||
public static void LogWarning(object? message)
|
||||
{
|
||||
s_logger.Log(message?.ToString() ?? "null", LogLevel.Warning);
|
||||
}
|
||||
|
||||
public static void LogWarning(string message)
|
||||
{
|
||||
s_logger.Log(message, LogLevel.Warning);
|
||||
}
|
||||
|
||||
public static void LogError(object? message)
|
||||
{
|
||||
s_logger.Log(message?.ToString() ?? "null", LogLevel.Error);
|
||||
}
|
||||
|
||||
public static void LogError(string message)
|
||||
{
|
||||
s_logger.Log(message, LogLevel.Error);
|
||||
}
|
||||
|
||||
public static void LogError(Exception ex)
|
||||
{
|
||||
s_logger.Log(ex);
|
||||
}
|
||||
|
||||
public static void Assert(bool condition, string message)
|
||||
{
|
||||
s_logger.Assert(condition, message);
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
s_logger.Clear();
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Core;
|
||||
|
||||
public readonly struct TypeHandle
|
||||
{
|
||||
public readonly IntPtr Value
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
private TypeHandle(IntPtr value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type handle for the specified type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to get the handle for.</param>
|
||||
/// <returns>The type handle as a nint.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TypeHandle Get(Type type) => new TypeHandle(type.TypeHandle.Value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type handle for the specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to get the handle for.</typeparam>
|
||||
/// <returns>The type handle as a nint.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TypeHandle Get<T>() => Get(typeof(T));
|
||||
|
||||
/// <summary>
|
||||
/// Converts a TypeHandle to a Type.
|
||||
/// </summary>
|
||||
/// <param name="handle">The TypeHandle to convert.</param>
|
||||
/// <returns>The corresponding Type.</returns>
|
||||
public Type? ToType()
|
||||
{
|
||||
return Type.GetTypeFromHandle(RuntimeTypeHandle.FromIntPtr(Value));
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Value.GetHashCode();
|
||||
}
|
||||
|
||||
public static implicit operator TypeHandle(IntPtr value)
|
||||
{
|
||||
return new TypeHandle(value);
|
||||
}
|
||||
|
||||
public static implicit operator IntPtr(TypeHandle handle)
|
||||
{
|
||||
return handle.Value;
|
||||
}
|
||||
|
||||
public static implicit operator TypeHandle(Type type)
|
||||
{
|
||||
return Get(type);
|
||||
}
|
||||
|
||||
public static implicit operator Type?(TypeHandle handle)
|
||||
{
|
||||
return handle.ToType();
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
using Misaki.HighPerformance.LowLevel;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Versioning;
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
namespace Ghost.Core.Utilities;
|
||||
|
||||
[SupportedOSPlatform("windows10.0.19041.0")]
|
||||
internal static unsafe partial class Win32Utility
|
||||
{
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public readonly ref struct IID_PPV
|
||||
{
|
||||
public readonly Guid* iid;
|
||||
public readonly void** ppv;
|
||||
|
||||
public IID_PPV(Guid* iid, void** ppv)
|
||||
{
|
||||
this.iid = iid;
|
||||
this.ppv = ppv;
|
||||
}
|
||||
|
||||
public void Deconstruct(out Guid* iid, out void** ppv)
|
||||
{
|
||||
iid = this.iid;
|
||||
ppv = this.ppv;
|
||||
}
|
||||
}
|
||||
|
||||
public static Guid* IID_NULL
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in IID.IID_NULL));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IID_PPV IID_PPV_ARGS<T>(ComPtr<T>* comPtr)
|
||||
where T : unmanaged, IUnknown.Interface
|
||||
{
|
||||
return new IID_PPV(Windows.__uuidof<T>(), (void**)comPtr);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Attach<T>(ref this UniquePtr<T> uPtr, T* other)
|
||||
where T : unmanaged, IUnknown.Interface
|
||||
{
|
||||
var ptr = uPtr.Get();
|
||||
if (ptr != null)
|
||||
{
|
||||
var refCount = ptr->Release();
|
||||
Debug.Assert((refCount != 0) || (ptr != other));
|
||||
}
|
||||
|
||||
uPtr = new UniquePtr<T>(other);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Dispose<T>(ref this UniquePtr<T> uPtr)
|
||||
where T : unmanaged, IUnknown.Interface
|
||||
{
|
||||
T* ptr = uPtr.Get();
|
||||
if (ptr != null)
|
||||
{
|
||||
uPtr = default;
|
||||
ptr->Release();
|
||||
//MemoryLeakException.ThrowIfRefCountNonZero(ptr->Release());
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Result ToResult(this HRESULT hr, [CallerArgumentExpression(nameof(hr))] string? op = null)
|
||||
{
|
||||
if (hr.SUCCEEDED)
|
||||
{
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
return Result.Failure($"{op} failed with code {hr}");
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void** ReleaseAndGetVoidAddressOf<T>(ref this ComPtr<T> comPtr)
|
||||
where T : unmanaged, IUnknown.Interface
|
||||
{
|
||||
return (void**)comPtr.ReleaseAndGetAddressOf();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ComPtr<T> Move<T>(ref this ComPtr<T> comPtr)
|
||||
where T : unmanaged, IUnknown.Interface
|
||||
{
|
||||
var copy = default(ComPtr<T>);
|
||||
comPtr.Swap(ref copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool HasFlag<T>(this uint flags, T flag)
|
||||
where T : Enum
|
||||
{
|
||||
return (flags & Unsafe.As<T, uint>(ref flag)) != 0;
|
||||
}
|
||||
|
||||
extension(MemoryLeakException)
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ThrowIfRefCountNonZero(uint count)
|
||||
{
|
||||
if (count != 0)
|
||||
{
|
||||
throw new MemoryLeakException($"Reference count is not zero: {count}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace Ghost.Data.Repository;
|
||||
|
||||
internal class AssetsRepository
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
using Ghost.Core;
|
||||
|
||||
namespace Ghost.Editor.Core.AppState;
|
||||
|
||||
internal partial class AppStateMachine : IDisposable, IAsyncDisposable
|
||||
{
|
||||
private Dictionary<StateKey, Lazy<IAppState>> _states = new();
|
||||
private IAppState? _current;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
public void RegisterState(StateKey key, Func<IAppState> stateFactory)
|
||||
{
|
||||
_states[key] = new(stateFactory);
|
||||
}
|
||||
|
||||
public async Task<Result> TransitionToAsync(StateKey stateKey, object? parameter = null)
|
||||
{
|
||||
var previous = _current;
|
||||
if (!_states.TryGetValue(stateKey, out var next))
|
||||
{
|
||||
return Result.Failure($"State '{stateKey}' not found.");
|
||||
}
|
||||
|
||||
Result result;
|
||||
if (previous != null)
|
||||
{
|
||||
result = await previous.OnExitingAsync();
|
||||
if (result.IsFailure)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result = await next.Value.OnEnteringAsync(parameter);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
if (previous != null)
|
||||
{
|
||||
await previous.OnEnteredAsync(parameter);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (previous != null)
|
||||
{
|
||||
result = await previous.OnExitedAsync();
|
||||
if (result.IsFailure)
|
||||
{
|
||||
await next.Value.OnExitedAsync();
|
||||
await previous.OnEnteredAsync(parameter);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result = await next.Value.OnEnteredAsync(parameter);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
await next.Value.OnExitedAsync();
|
||||
|
||||
if (previous != null)
|
||||
{
|
||||
await previous.OnEnteredAsync(parameter);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
_current = next.Value;
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeAsync().AsTask().Wait();
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_states.Clear();
|
||||
if (_current != null)
|
||||
{
|
||||
await _current.OnExitingAsync();
|
||||
await _current.OnExitedAsync();
|
||||
}
|
||||
|
||||
_current = null;
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using Ghost.Core;
|
||||
|
||||
namespace Ghost.Editor.Core.AppState;
|
||||
|
||||
internal interface IAppState
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when exiting the state.
|
||||
/// </summary>
|
||||
public Task<Result> OnExitingAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Called when entering the state, right after OnEnteringAsync.
|
||||
/// <paramref name="parameter">can be used to pass data into the state, such as a project to load.</summary>
|
||||
/// </summary>
|
||||
public Task<Result> OnEnteringAsync(object? parameter);
|
||||
|
||||
/// <summary>
|
||||
/// Called when exiting the state, specifically for pose transitions.
|
||||
/// </summary>
|
||||
public Task<Result> OnExitedAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Called when entered the state, specifically after the state has been fully initialized and is ready for interaction.
|
||||
/// </summary>
|
||||
/// <param name="parameter">can be used to pass data into the state, such as a project to load.</param>
|
||||
public Task<Result> OnEnteredAsync(object? parameter);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace Ghost.Editor.Core.AppState;
|
||||
|
||||
internal enum StateKey
|
||||
{
|
||||
None,
|
||||
Landing,
|
||||
EngineEditor,
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Editor.Core.Utilities;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Ghost.Editor.Core.AssetHandle;
|
||||
|
||||
public static partial class AssetDatabase
|
||||
{
|
||||
private static readonly Dictionary<string, Type> s_importerTypeLookup = new();
|
||||
|
||||
private static void InitializeMetaData()
|
||||
{
|
||||
if (_watcher == null)
|
||||
{
|
||||
throw new InvalidOperationException("AssetDatabase is not initialized. Ensure that Initialize() is called before registering asset importers.");
|
||||
}
|
||||
|
||||
var importerTypes = TypeCache.GetTypes().Where(t => t.GetCustomAttribute<AssetImporterAttribute>() != null);
|
||||
foreach (var type in importerTypes)
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<AssetImporterAttribute>()!;
|
||||
foreach (var extension in attribute.SupportedExtensions)
|
||||
{
|
||||
s_importerTypeLookup[extension] = type;
|
||||
}
|
||||
}
|
||||
|
||||
_watcher.Created += OnAssetCreated;
|
||||
_watcher.Deleted += OnAssetDeleted;
|
||||
_watcher.Renamed += OnAssetRenamed;
|
||||
}
|
||||
|
||||
private static Result<string> GetMetaFilePath(string assetPath)
|
||||
{
|
||||
if (Directory.Exists(assetPath))
|
||||
{
|
||||
return Result<string>.Failure("Folder does not have meta data");
|
||||
}
|
||||
|
||||
if (Path.GetExtension(assetPath).Equals(".meta", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Result<string>.Failure("Asset path cannot be a meta file");
|
||||
}
|
||||
|
||||
return Result<string>.Success(assetPath + ".meta");
|
||||
}
|
||||
|
||||
private static ImporterSettings? GetDefaultSettingsForAsset(string assetPath)
|
||||
{
|
||||
var extension = Path.GetExtension(assetPath);
|
||||
|
||||
if (s_importerTypeLookup.TryGetValue(extension, out var importerType))
|
||||
{
|
||||
var settingsType = importerType.BaseType?.GetGenericArguments()[0];
|
||||
if (settingsType == null || !typeof(ImporterSettings).IsAssignableFrom(settingsType))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (ImporterSettings?)Activator.CreateInstance(settingsType);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void WriteMetaFile(string metaFilePath, AssetMeta metaData)
|
||||
{
|
||||
using var fileStream = File.Create(metaFilePath);
|
||||
|
||||
try
|
||||
{
|
||||
JsonSerializer.Serialize(fileStream, metaData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
internal static Result GenerateMetaFile(string assetPath)
|
||||
{
|
||||
var metaFileResult = GetMetaFilePath(assetPath);
|
||||
if (!metaFileResult.IsSuccess)
|
||||
{
|
||||
return Result.Failure(metaFileResult.Message);
|
||||
}
|
||||
|
||||
if (File.Exists(metaFileResult.Value))
|
||||
{
|
||||
var existingMeta = JsonSerializer.Deserialize<AssetMeta>(File.ReadAllText(metaFileResult.Value));
|
||||
if (existingMeta != null && s_assetPathLookup.TryGetValue(existingMeta.Guid, out var path))
|
||||
{
|
||||
if (assetPath != path)
|
||||
{
|
||||
existingMeta.Guid = Guid.NewGuid();
|
||||
WriteMetaFile(metaFileResult.Value, existingMeta);
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
var defaultSettings = GetDefaultSettingsForAsset(assetPath);
|
||||
var metaData = new AssetMeta
|
||||
{
|
||||
Guid = Guid.NewGuid(),
|
||||
Settings = defaultSettings
|
||||
};
|
||||
|
||||
WriteMetaFile(metaFileResult.Value, metaData);
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
private static void OnAssetCreated(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
GenerateMetaFile(e.FullPath);
|
||||
}
|
||||
|
||||
private static void OnAssetDeleted(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
var metaFileResult = GetMetaFilePath(e.FullPath);
|
||||
if (metaFileResult.IsSuccess && File.Exists(metaFileResult.Value))
|
||||
{
|
||||
try
|
||||
{
|
||||
var meta = JsonSerializer.Deserialize<AssetMeta>(File.ReadAllText(metaFileResult.Value));
|
||||
if (meta != null
|
||||
&& s_assetPathLookup.TryGetValue(meta.Guid, out var path)
|
||||
&& path == e.FullPath)
|
||||
{
|
||||
s_assetPathLookup.Remove(meta.Guid);
|
||||
}
|
||||
|
||||
File.Delete(metaFileResult.Value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnAssetRenamed(object sender, RenamedEventArgs e)
|
||||
{
|
||||
var oldMetaPath = e.OldFullPath + ".meta";
|
||||
var newMetaPath = e.FullPath + ".meta";
|
||||
|
||||
if (File.Exists(oldMetaPath))
|
||||
{
|
||||
File.Move(oldMetaPath, newMetaPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
GenerateMetaFile(e.FullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ghost.Editor.Core.AssetHandle;
|
||||
|
||||
public static partial class AssetDatabase
|
||||
{
|
||||
private static readonly Dictionary<string, Action<string>> _assetOpenHandlers = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private static void InitializeAssetHandle()
|
||||
{
|
||||
var methods = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(a => a.GetTypes())
|
||||
.SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
.Where(m => m.GetCustomAttribute<AssetOpenHandlerAttribute>() != null &&
|
||||
m.GetParameters().Length == 1 &&
|
||||
m.GetParameters()[0].ParameterType == typeof(string));
|
||||
|
||||
foreach (var method in methods)
|
||||
{
|
||||
var attr = method.GetCustomAttribute<AssetOpenHandlerAttribute>()!;
|
||||
var del = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), method);
|
||||
foreach (var ext in attr.Extensions)
|
||||
{
|
||||
if (_assetOpenHandlers.ContainsKey(ext))
|
||||
{
|
||||
throw new InvalidOperationException($"Duplicate handler for extension '{ext}'");
|
||||
}
|
||||
|
||||
_assetOpenHandlers[ext] = del;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void OpenAsset(string path)
|
||||
{
|
||||
var extension = Path.GetExtension(path);
|
||||
if (_assetOpenHandlers.TryGetValue(extension, out var handler))
|
||||
{
|
||||
handler(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(path)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using Ghost.Data.Services;
|
||||
|
||||
namespace Ghost.Editor.Core.AssetHandle;
|
||||
|
||||
public static partial class AssetDatabase
|
||||
{
|
||||
private static FileSystemWatcher? _watcher;
|
||||
|
||||
private static readonly Dictionary<Guid, string> s_assetPathLookup = new();
|
||||
|
||||
public static DirectoryInfo? AssetsDirectory
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
internal static void Initialize()
|
||||
{
|
||||
if (ProjectService.CurrentProject.Metadata == null)
|
||||
{
|
||||
throw new InvalidOperationException("Project metadata is not initialized. Ensure that the project is loaded before accessing the AssetDatabase.");
|
||||
}
|
||||
|
||||
AssetsDirectory = new DirectoryInfo(Path.Combine(Path.GetDirectoryName(ProjectService.CurrentProject.Path)!, ProjectService.ASSETS_FOLDER));
|
||||
_watcher = new FileSystemWatcher
|
||||
{
|
||||
Path = AssetsDirectory.FullName,
|
||||
IncludeSubdirectories = true,
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
|
||||
InitializeAssetHandle();
|
||||
InitializeMetaData();
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace Ghost.Editor.Core.AssetHandle;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
public class AssetImporterAttribute : Attribute
|
||||
{
|
||||
public string[] SupportedExtensions
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public AssetImporterAttribute(params string[] supportedExtensions)
|
||||
{
|
||||
SupportedExtensions = supportedExtensions;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace Ghost.Editor.Core.AssetHandle;
|
||||
|
||||
internal class AssetMeta
|
||||
{
|
||||
public Guid Guid
|
||||
{
|
||||
get;
|
||||
internal set;
|
||||
}
|
||||
|
||||
public ImporterSettings? Settings
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace Ghost.Editor.Core.AssetHandle;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class AssetOpenHandlerAttribute : Attribute
|
||||
{
|
||||
public string[] Extensions
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public AssetOpenHandlerAttribute(params string[] extensions)
|
||||
{
|
||||
Extensions = extensions.Select(e => e.StartsWith('.') ? e.ToLowerInvariant() : '.' + e.ToLowerInvariant()).ToArray();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
namespace Ghost.Editor.Core.AssetHandle;
|
||||
|
||||
public abstract class ImporterSettings
|
||||
{
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace Ghost.Editor.Core.Contracts;
|
||||
|
||||
public interface INavigationAware
|
||||
{
|
||||
public void OnNavigatedTo(object? parameter);
|
||||
public void OnNavigatedFrom();
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Ghost.Editor.Controls.Internal" />
|
||||
@@ -1,29 +0,0 @@
|
||||
using Ghost.SparseEntities;
|
||||
using Ghost.SparseEntities.Components;
|
||||
using Ghost.SparseEntities.Query;
|
||||
|
||||
namespace Ghost.Editor.Core.Inspector;
|
||||
|
||||
public unsafe readonly struct ComponentObject
|
||||
{
|
||||
private readonly World _world;
|
||||
private readonly Entity _entity;
|
||||
|
||||
internal ComponentObject(World world, Entity entity)
|
||||
{
|
||||
_world = world;
|
||||
_entity = entity;
|
||||
}
|
||||
|
||||
public CompRef<T> GetData<T>()
|
||||
where T : unmanaged, IComponentData
|
||||
{
|
||||
return _world.EntityManager.GetComponent<T>(_entity);
|
||||
}
|
||||
|
||||
public void SetData<T>(in T data)
|
||||
where T : unmanaged, IComponentData
|
||||
{
|
||||
_world.EntityManager.SetComponent(_entity, in data);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
namespace Ghost.Editor.Core.Inspector;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class CustomEditorAttribute(Type targetType) : Attribute
|
||||
{
|
||||
internal Type TargetType
|
||||
{
|
||||
get;
|
||||
} = targetType;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Ghost.Editor.Core.Inspector;
|
||||
|
||||
public interface IInspectable
|
||||
{
|
||||
public IconSource? Icon
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public UIElement? HeaderContent
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public UIElement? InspectorContent
|
||||
{
|
||||
get;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace Ghost.Editor.Core.Inspector;
|
||||
|
||||
internal interface IInspectorService
|
||||
{
|
||||
public IInspectable? SelectedInspectable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public event Action? OnSelectionChanged;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
namespace Ghost.Editor.Core.Inspector;
|
||||
|
||||
public class InspectorService : IInspectorService
|
||||
{
|
||||
public IInspectable? SelectedInspectable
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
if (field != value)
|
||||
{
|
||||
field = value;
|
||||
OnSelectionChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event Action? OnSelectionChanged;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using CommunityToolkit.WinUI.Behaviors;
|
||||
|
||||
namespace Ghost.Editor.Core.Notifications;
|
||||
|
||||
public interface INotificationService
|
||||
{
|
||||
public void ShowNotification(string? message, MessageType type, int duration = 5, string? title = null);
|
||||
public void ShowNotification(Notification notification);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace Ghost.Editor.Core.Progress;
|
||||
|
||||
public interface IProgressService
|
||||
{
|
||||
public void ShowProgress(string message, double progress = 0.0);
|
||||
public void ShowIndeterminateProgress(string message);
|
||||
public void SetProgress(double progress);
|
||||
public void HideProgress();
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
namespace Ghost.Editor.Core.Resources;
|
||||
|
||||
internal static class FileExtensions
|
||||
{
|
||||
public const string PROJECT_FILE_EXTENSION = ".ghostproj";
|
||||
public const string TEMPLATE_FILE_EXTENSION = ".ghosttemplate";
|
||||
public const string SCENE_FILE_EXTENSION = ".ghostscene";
|
||||
public const string ASSET_FILE_EXTENSION = ".ghostasset";
|
||||
public const string SHADER_FILE_EXTENSION = ".ghostshader";
|
||||
public const string MATERIAL_FILE_EXTENSION = ".ghostmaterial";
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using Ghost.Editor.Core.Progress;
|
||||
using Ghost.Editor.Core.Resources;
|
||||
using Ghost.Editor.Core.Utilities;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Ghost.Editor.Core.SceneGraph;
|
||||
|
||||
public enum OpenWorldMode
|
||||
{
|
||||
Single,
|
||||
Additive,
|
||||
AdditiveWithoutLoading
|
||||
}
|
||||
|
||||
public static class EditorWorldManager
|
||||
{
|
||||
// TODO: Use guid keys instead of string paths for better performance and uniqueness
|
||||
private static readonly Dictionary<string, WorldNode> _loadedWorlds = new();
|
||||
public static IEnumerable<WorldNode> LoadedWorlds => _loadedWorlds.Values;
|
||||
|
||||
public static event Action<WorldNode>? OnWorldLoaded;
|
||||
public static event Action<WorldNode>? OnWorldUnloaded;
|
||||
|
||||
public static async Task LoadWorld(string worldPath)
|
||||
{
|
||||
if (_loadedWorlds.ContainsKey(worldPath)
|
||||
|| !File.Exists(worldPath)
|
||||
|| Path.GetExtension(worldPath) != FileExtensions.SCENE_FILE_EXTENSION)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var progressService = EditorApplication.GetService<IProgressService>();
|
||||
progressService.ShowIndeterminateProgress("Loading world...");
|
||||
|
||||
foreach (var world in _loadedWorlds)
|
||||
{
|
||||
world.Value.Unload();
|
||||
OnWorldUnloaded?.Invoke(world.Value);
|
||||
}
|
||||
|
||||
await using var readStream = new FileStream(worldPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
var deserializedScene = await JsonSerializer.DeserializeAsync<WorldNode>(readStream, Engine.Resources.StaticResource.defaultSerializerOptions) ?? throw new Exception("Deserialization failed.");
|
||||
|
||||
_loadedWorlds.Clear();
|
||||
|
||||
_loadedWorlds[worldPath] = deserializedScene;
|
||||
await deserializedScene.LoadAsync();
|
||||
|
||||
progressService.HideProgress();
|
||||
OnWorldLoaded?.Invoke(deserializedScene);
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
using Ghost.Editor.Core.Controls.Internal;
|
||||
using Ghost.Editor.Core.Inspector;
|
||||
using Ghost.Editor.Core.Resources;
|
||||
using Ghost.Engine.Editor;
|
||||
using Ghost.SparseEntities;
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ghost.Editor.Core.SceneGraph;
|
||||
|
||||
public partial class EntityNode : SceneGraphNode
|
||||
{
|
||||
public WorldNode Owner
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Entity Entity
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public override SceneGraphNodeType NodeType => SceneGraphNodeType.Entity;
|
||||
|
||||
public EntityNode(WorldNode owner, Entity entity, string name)
|
||||
{
|
||||
Owner = owner;
|
||||
Entity = entity;
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class EntityNode : IInspectable
|
||||
{
|
||||
public IconSource? Icon => EditorIconSource.entity_24;
|
||||
|
||||
public UIElement? HeaderContent
|
||||
{
|
||||
get
|
||||
{
|
||||
var root = new StackPanel()
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
|
||||
var nameText = new TextBox
|
||||
{
|
||||
Text = Name,
|
||||
FontWeight = FontWeights.Bold,
|
||||
};
|
||||
var idText = new TextBlock
|
||||
{
|
||||
Text = $"ID: {Entity.ID} Generation: {Entity.Generation}",
|
||||
Margin = new Thickness(5, 7, 0, 0),
|
||||
Opacity = 0.75,
|
||||
Style = Application.Current.Resources["CaptionTextBlockStyle"] as Style
|
||||
};
|
||||
|
||||
nameText.SetBinding(TextBox.TextProperty, new Binding
|
||||
{
|
||||
Source = this,
|
||||
Path = new PropertyPath(nameof(Name)),
|
||||
Mode = BindingMode.TwoWay,
|
||||
UpdateSourceTrigger = UpdateSourceTrigger.LostFocus,
|
||||
});
|
||||
|
||||
root.Children.Add(nameText);
|
||||
root.Children.Add(idText);
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
public UIElement? InspectorContent
|
||||
{
|
||||
get
|
||||
{
|
||||
var root = new StackPanel()
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
VerticalAlignment = VerticalAlignment.Top
|
||||
};
|
||||
|
||||
foreach (var (typeHandle, componentPtr) in Owner.World.EntityManager.GetComponentsUnsafe(Entity))
|
||||
{
|
||||
if (componentPtr == IntPtr.Zero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var type = typeHandle.ToType();
|
||||
if (type == null || type.GetCustomAttribute<HideEditorAttribute>() != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var dataView = new ComponentDataView(type.Name, Owner.World, Entity, type);
|
||||
root.Children.Add(dataView);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
using Ghost.Engine.Components;
|
||||
using Ghost.SparseEntities;
|
||||
|
||||
namespace Ghost.Editor.Core.SceneGraph;
|
||||
|
||||
public class SceneGraphHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="EntityNode"/> entity with default components.
|
||||
/// </summary>
|
||||
/// <param name="world">The world context where the entity will be created.</param>
|
||||
/// <param name="entity">The entity to be wrapped in the <see cref="EntityNode"/>.</param>
|
||||
public static EntityNode CreateEntityNode(WorldNode owner, Entity entity, string name)
|
||||
{
|
||||
owner.World.EntityManager.AddComponent(entity, LocalToWorld.Identity);
|
||||
owner.World.EntityManager.AddComponent(entity, Hierarchy.Root);
|
||||
return new EntityNode(owner, entity, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Entity"/> and <see cref="EntityNode"/> entity with default components.
|
||||
/// </summary>
|
||||
/// <param name="owner">The world context where the entity will be created.</param>
|
||||
public static EntityNode CreateEntityNode(WorldNode owner, string name)
|
||||
{
|
||||
var entity = owner.World.EntityManager.CreateEntity();
|
||||
return CreateEntityNode(owner, entity, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches childEntity to parentEntity in the scene graph.
|
||||
/// </summary>
|
||||
/// <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="childNode">The child entity to be attached.</param>
|
||||
public static void AttachChild(WorldNode scene, EntityNode parentNode, EntityNode childNode)
|
||||
{
|
||||
// 1) If the child already has a parent, detach it first
|
||||
var childHierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(childNode.Entity);
|
||||
if (childHierarchy.ValueRO.parent != Entity.Invalid)
|
||||
{
|
||||
DetachFromParent(scene, childNode);
|
||||
}
|
||||
|
||||
// 2) Link child to new parent
|
||||
childHierarchy.ValueRW.parent = parentNode.Entity;
|
||||
|
||||
// 3) Insert child at the head of parent's child list
|
||||
var parentHierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(parentNode.Entity);
|
||||
|
||||
childHierarchy.ValueRW.nextSibling = parentHierarchy.ValueRO.firstChild;
|
||||
parentHierarchy.ValueRW.firstChild = childNode.Entity;
|
||||
|
||||
// 4) Write back
|
||||
scene.World.EntityManager.SetComponent(parentNode.Entity, in parentHierarchy.ValueRO);
|
||||
scene.World.EntityManager.SetComponent(childNode.Entity, in childHierarchy.ValueRO);
|
||||
|
||||
// 5) Update children list in parent node
|
||||
parentNode.AddChild(childNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detaches the specified entity from its parent in the scene graph.
|
||||
/// </summary>
|
||||
/// <param name="world">The world context where the entities exist.</param>
|
||||
/// <param name="node">The entity to detach from its parent.</param>
|
||||
public static void DetachFromParent(WorldNode scene, EntityNode node)
|
||||
{
|
||||
var hierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(node.Entity);
|
||||
var parent = hierarchy.ValueRO.parent;
|
||||
if (parent == Entity.Invalid)
|
||||
{
|
||||
return; // already root
|
||||
}
|
||||
|
||||
var parentHierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(parent);
|
||||
|
||||
// If entity is the first child, simply move head
|
||||
if (parentHierarchy.ValueRO.firstChild == node.Entity)
|
||||
{
|
||||
parentHierarchy.ValueRW.firstChild = hierarchy.ValueRO.nextSibling;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, find the previous sibling in the linked list
|
||||
var prevSibling = parentHierarchy.ValueRO.firstChild;
|
||||
while (prevSibling != Entity.Invalid)
|
||||
{
|
||||
var prevHierarchy = scene.World.EntityManager.GetComponent<Hierarchy>(prevSibling);
|
||||
if (prevHierarchy.ValueRW.nextSibling == node.Entity)
|
||||
{
|
||||
prevHierarchy.ValueRW.nextSibling = hierarchy.ValueRO.nextSibling;
|
||||
scene.World.EntityManager.SetComponent(prevSibling, in prevHierarchy.ValueRO);
|
||||
break;
|
||||
}
|
||||
|
||||
prevSibling = prevHierarchy.ValueRO.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear child's references
|
||||
hierarchy.ValueRW.parent = Entity.Invalid;
|
||||
hierarchy.ValueRW.nextSibling = Entity.Invalid;
|
||||
|
||||
// Write back
|
||||
scene.World.EntityManager.SetComponent(parent, in parentHierarchy.ValueRO);
|
||||
scene.World.EntityManager.SetComponent(node.Entity, in hierarchy.ValueRO);
|
||||
|
||||
// Remove from parent's children list
|
||||
scene.EntityNodeLookup[parent].RemoveChild(node);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Ghost.Editor.Core.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];
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
using Ghost.Editor.Core.AssetHandle;
|
||||
using Ghost.Editor.Core.Inspector;
|
||||
using Ghost.Editor.Core.Resources;
|
||||
using Ghost.Editor.Core.Serializer;
|
||||
using Ghost.Engine.Components;
|
||||
using Ghost.SparseEntities;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ghost.Editor.Core.SceneGraph;
|
||||
|
||||
[JsonConverter(typeof(WorldNodeSerializer))]
|
||||
public partial class WorldNode : SceneGraphNode, IEquatable<WorldNode>
|
||||
{
|
||||
private 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)
|
||||
{
|
||||
if (!_entityNodeLookup.TryGetValue(entity, out var node))
|
||||
{
|
||||
node = new EntityNode(this, 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));
|
||||
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);
|
||||
AddChild(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task LoadAsync()
|
||||
{
|
||||
return Task.Run(BuildGraph);
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
_world.Dispose();
|
||||
_world = null!;
|
||||
|
||||
Children?.Clear();
|
||||
_entityNodeLookup.Clear();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"WorldNode: {Name} (World ID: {_world.ID})";
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(_world, Name);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is WorldNode other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(WorldNode? other)
|
||||
{
|
||||
if (other is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(this, other))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return _world.Equals(other._world) && Name == other.Name;
|
||||
}
|
||||
|
||||
public static bool operator ==(WorldNode? left, WorldNode? right)
|
||||
{
|
||||
if (left is null)
|
||||
{
|
||||
return right is null;
|
||||
}
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(WorldNode? left, WorldNode? right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class WorldNode : IInspectable
|
||||
{
|
||||
public IconSource? Icon => EditorIconSource.scene_24;
|
||||
|
||||
[AssetOpenHandler(FileExtensions.SCENE_FILE_EXTENSION)]
|
||||
public static async void Open(string path)
|
||||
{
|
||||
await EditorWorldManager.LoadWorld(path);
|
||||
}
|
||||
|
||||
public UIElement? HeaderContent => null;
|
||||
|
||||
public UIElement? InspectorContent => null;
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
using Ghost.Editor.Core.SceneGraph;
|
||||
using Ghost.Engine.Utilities;
|
||||
using Ghost.SparseEntities;
|
||||
using Ghost.SparseEntities.Components;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ghost.Editor.Core.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(result, 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, () =>
|
||||
{
|
||||
for (var i = 0; i < value.World.ComponentStorage.ComponentPools.Count; i++)
|
||||
{
|
||||
var pool = value.World.ComponentStorage.ComponentPools[i];
|
||||
if (pool == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var type = value.World.ComponentStorage.GetComponentPoolType(i).GetType();
|
||||
var typeName = type.AssemblyQualifiedName ?? type.Name;
|
||||
|
||||
writer.WriteArray(typeName, pool.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,26 +0,0 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Ghost.Editor.Core.Utilities;
|
||||
|
||||
public static class EditorApplication
|
||||
{
|
||||
private static IServiceProvider? _serviceProvider;
|
||||
|
||||
public static Application Current => Application.Current;
|
||||
|
||||
internal static void Initialize(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public static T GetService<T>()
|
||||
where T : class
|
||||
{
|
||||
if (_serviceProvider?.GetService(typeof(T)) is not T service)
|
||||
{
|
||||
throw new ArgumentException($"{typeof(T)} needs to be registered in ConfigureServices within App.xaml.cs.");
|
||||
}
|
||||
|
||||
return service;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using Ghost.Core.Attributes;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ghost.Editor.Core.Utilities;
|
||||
|
||||
public static class TypeCache
|
||||
{
|
||||
private static readonly TypeInfo[] _types;
|
||||
|
||||
static TypeCache()
|
||||
{
|
||||
var loadableTypes = new List<Type>();
|
||||
var assembliesToScan = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.Where(a => a.GetCustomAttribute<EngineAssemblyAttribute>() != null);
|
||||
|
||||
foreach (var assembly in assembliesToScan)
|
||||
{
|
||||
try
|
||||
{
|
||||
loadableTypes.AddRange(assembly.GetTypes());
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
var types = ex.Types.Where(t => t != null);
|
||||
loadableTypes.AddRange(types!);
|
||||
}
|
||||
}
|
||||
|
||||
_types = loadableTypes.Select(t => t.GetTypeInfo()).ToArray();
|
||||
}
|
||||
|
||||
public static Type[] GetTypes()
|
||||
{
|
||||
return _types;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using Ghost.Data.Resources;
|
||||
using Ghost.Data.Services;
|
||||
using Ghost.Editor.Core.Utilities;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Ghost.Editor;
|
||||
|
||||
internal static class ActivationHandler
|
||||
{
|
||||
private static void FolderInitialization()
|
||||
{
|
||||
if (!Directory.Exists(DataPath.s_applicationDataFolder))
|
||||
{
|
||||
Directory.CreateDirectory(DataPath.s_applicationDataFolder);
|
||||
}
|
||||
|
||||
if (!Directory.Exists(DataPath.s_projectTemplateFolder))
|
||||
{
|
||||
Directory.CreateDirectory(DataPath.s_projectTemplateFolder);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Handle(LaunchActivatedEventArgs args)
|
||||
{
|
||||
FolderInitialization();
|
||||
ProjectService.EnsureDefaultTemplate();
|
||||
|
||||
EditorApplication.Initialize(((App)(Application.Current)).Host.Services);
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Editor.Core.AppState;
|
||||
using Ghost.Editor.Core.Inspector;
|
||||
using Ghost.Editor.Core.Notifications;
|
||||
using Ghost.Editor.Core.Progress;
|
||||
using Ghost.Editor.Utilities;
|
||||
using Ghost.Engine.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
// 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)
|
||||
{
|
||||
// HACK: As far as I can tell, there is no proper application shutdown event in WinUI 3.
|
||||
app._window?.Closed -= app.OnClosed;
|
||||
app._window = value;
|
||||
app._window?.Closed += app.OnClosed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<INotificationService, NotificationService>();
|
||||
services.AddSingleton<IProgressService, ProgressService>();
|
||||
services.AddSingleton<IInspectorService, InspectorService>();
|
||||
})
|
||||
.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);
|
||||
|
||||
await Host.StartAsync();
|
||||
ActivationHandler.Handle(args);
|
||||
|
||||
var stateMachine = GetService<AppStateMachine>();
|
||||
stateMachine.RegisterState(StateKey.Landing, () => new LandingState());
|
||||
stateMachine.RegisterState(StateKey.EngineEditor, () => new EditorState());
|
||||
|
||||
await stateMachine.TransitionToAsync(StateKey.Landing);
|
||||
}
|
||||
|
||||
private void OnClosed(object? sender, WindowEventArgs args)
|
||||
{
|
||||
Host.StopAsync().GetAwaiter().GetResult();
|
||||
Host.Dispose();
|
||||
}
|
||||
|
||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
Logger.LogError(e.Exception);
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 599 B |
|
Before Width: | Height: | Size: 831 B |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 433 B |
|
Before Width: | Height: | Size: 599 B |
|
Before Width: | Height: | Size: 583 B |
|
Before Width: | Height: | Size: 831 B |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 852 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
@@ -1,61 +0,0 @@
|
||||
using Ghost.Editor.Core.Controls;
|
||||
using Ghost.Editor.Core.Inspector;
|
||||
using Ghost.Engine.Components;
|
||||
using Ghost.Engine.Utilities;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Ghost.Editor.Components;
|
||||
|
||||
[CustomEditor(typeof(LocalToWorld))]
|
||||
internal class LocalToWorldEditor : ComponentEditor
|
||||
{
|
||||
private Vector3Field _translationField = null!;
|
||||
private Vector3Field _rotationField = null!;
|
||||
private Vector3Field _scaleField = null!;
|
||||
|
||||
public override void Create(StackPanel container)
|
||||
{
|
||||
_translationField = new Vector3Field();
|
||||
_rotationField = new Vector3Field();
|
||||
_scaleField = new Vector3Field();
|
||||
|
||||
_translationField.OnValueChanged += (s, e) =>
|
||||
{
|
||||
var data = ComponentObject.GetData<LocalToWorld>();
|
||||
MatrixUtility.GetTRS(data.ValueRO.matrix, out var _, out var oldRotation, out var oldScale);
|
||||
data.ValueRW.matrix = MatrixUtility.CreateTRS(e.NewValue, oldRotation, oldScale);
|
||||
};
|
||||
|
||||
_rotationField.OnValueChanged += (s, e) =>
|
||||
{
|
||||
var data = ComponentObject.GetData<LocalToWorld>();
|
||||
MatrixUtility.GetTRS(data.ValueRO.matrix, out var oldTranslation, out var _, out var oldScale);
|
||||
data.ValueRW.matrix = MatrixUtility.CreateTRS(oldTranslation, e.NewValue.ToQuaternion(), oldScale);
|
||||
};
|
||||
|
||||
_scaleField.OnValueChanged += (s, e) =>
|
||||
{
|
||||
var data = ComponentObject.GetData<LocalToWorld>();
|
||||
MatrixUtility.GetTRS(data.ValueRO.matrix, out var oldTranslation, out var oldRotation, out var _);
|
||||
data.ValueRW.matrix = MatrixUtility.CreateTRS(oldTranslation, oldRotation, e.NewValue);
|
||||
};
|
||||
|
||||
container.Children.Add(new PropertyField() { Label = "Position", Content = _translationField });
|
||||
container.Children.Add(new PropertyField() { Label = "Rotation", Content = _rotationField });
|
||||
container.Children.Add(new PropertyField() { Label = "Scale", Content = _scaleField });
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
var data = ComponentObject.GetData<LocalToWorld>();
|
||||
MatrixUtility.GetTRS(data.ValueRO.matrix, out var translation, out var rotation, out var scale);
|
||||
|
||||
_translationField.Value = translation;
|
||||
_rotationField.Value = VectorUtility.CreateFromQuaternion(rotation);
|
||||
_scaleField.Value = scale;
|
||||
}
|
||||
|
||||
public override void Destroy()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Ghost.Editor.Core.Contracts;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Ghost.Editor.Controls;
|
||||
|
||||
public abstract partial class ViewModelPage<VM> : Page
|
||||
where VM : ObservableObject
|
||||
{
|
||||
public VM ViewModel
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
protected ViewModelPage(VM viewModel)
|
||||
{
|
||||
ViewModel = viewModel;
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
if (ViewModel is INavigationAware navigationAware)
|
||||
{
|
||||
navigationAware.OnNavigatedTo(e.Parameter);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedFrom(e);
|
||||
if (ViewModel is INavigationAware navigationAware)
|
||||
{
|
||||
navigationAware.OnNavigatedFrom();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
using Ghost.Data.Models;
|
||||
using Ghost.Data.Services;
|
||||
using Ghost.Editor.Core.AssetHandle;
|
||||
using Ghost.Editor.View.Windows;
|
||||
using Ghost.Engine;
|
||||
using Ghost.Engine.Services;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace Ghost.Editor.Core.AppState;
|
||||
|
||||
internal class EditorState : IAppState
|
||||
{
|
||||
private EngineEditorWindow? _window;
|
||||
private EngineCore? _engineCore;
|
||||
|
||||
public Task OnExitingAsync()
|
||||
{
|
||||
if (App.Window == _window)
|
||||
{
|
||||
App.Window = null;
|
||||
}
|
||||
|
||||
_engineCore?.ShutDown();
|
||||
CompositionTarget.Rendering -= OnRendering;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task OnEnteringAsync(object? parameter)
|
||||
{
|
||||
if (parameter is not ProjectMetadataInfo metadataInfo)
|
||||
{
|
||||
throw new ArgumentException("Parameter must be of type ProjectMetadata.", nameof(parameter));
|
||||
}
|
||||
|
||||
ProjectService.CurrentProject = metadataInfo;
|
||||
|
||||
_engineCore = App.GetService<EngineCore>();
|
||||
_engineCore.Start(new Engine.Models.LaunchArgument());
|
||||
CompositionTarget.Rendering += OnRendering;
|
||||
|
||||
_window = App.GetService<EngineEditorWindow>();
|
||||
_window.Activate();
|
||||
|
||||
App.Window = _window;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task OnExitedAsync()
|
||||
{
|
||||
_window?.Close();
|
||||
_window = null;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task OnEnteredAsync(object? parameter)
|
||||
{
|
||||
AssetDatabase.Initialize();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void OnRendering(object? sender, object e)
|
||||
{
|
||||
if (GraphicsPipeline.WaitForGPUReady(0))
|
||||
{
|
||||
_window?.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.High, () =>
|
||||
{
|
||||
PlayerLoopService.Update();
|
||||
GraphicsPipeline.SignalCPUReady();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using Ghost.Editor.View.Windows;
|
||||
|
||||
namespace Ghost.Editor.Core.AppState;
|
||||
|
||||
internal class LandingState : IAppState
|
||||
{
|
||||
private LandingWindow? _window;
|
||||
|
||||
public Task OnExitingAsync()
|
||||
{
|
||||
if (App.Window == _window)
|
||||
{
|
||||
App.Window = null;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task OnEnteringAsync(object? parameter)
|
||||
{
|
||||
_window = App.GetService<LandingWindow>();
|
||||
_window.Activate();
|
||||
|
||||
App.Window = _window;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task OnExitedAsync()
|
||||
{
|
||||
_window?.Close();
|
||||
_window = null;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task OnEnteredAsync(object? parameter)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net10.0-windows10.0.22621.0</TargetFramework>
|
||||
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
|
||||
<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="View\Pages\EngineEditor\ConsolePage.xaml" />
|
||||
<None Remove="View\Pages\EngineEditor\HierarchyPage.xaml" />
|
||||
<None Remove="View\Pages\EngineEditor\InspectorPage.xaml" />
|
||||
<None Remove="View\Pages\EngineEditor\ProjectPage.xaml" />
|
||||
<None Remove="View\Pages\EngineEditor\ScenePage.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.WinUI.Controls.Primitives" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.TabbedCommandBar" Version="8.2.250402" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.8.251106002" />
|
||||
<PackageReference Include="WinUIEx" Version="2.9.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ghost.Editor.Core\Ghost.Editor.Core.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>
|
||||
<Page Update="View\Pages\EngineEditor\InspectorPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</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="View\Windows\EngineEditorWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Pages\EngineEditor\ScenePage.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>
|
||||
<RootNamespace>Ghost.Editor</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Microsoft.UI.Xaml.Controls"
|
||||
xmlns:internal="using:Ghost.Editor.Controls.Internal">
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<StaticResource x:Key="TabViewItemHeaderBackgroundSelected" ResourceKey="ControlFillColorSecondaryBrush" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<StaticResource x:Key="TabViewItemHeaderBackgroundSelected" ResourceKey="ControlFillColorSecondaryBrush" />
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<Style TargetType="internal:NavigationTabView">
|
||||
<Setter Property="TabWidthMode" Value="Compact" />
|
||||
</Style>
|
||||
<Style TargetType="NumberBox" />
|
||||
</ResourceDictionary>
|
||||
@@ -1,27 +0,0 @@
|
||||
using Ghost.Engine.Utilities;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ghost.Editor.Utilities.Converters;
|
||||
|
||||
public partial class Vector3ToQuaternionConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is Vector3 vector)
|
||||
{
|
||||
return Quaternion.CreateFromYawPitchRoll(vector.Y, vector.X, vector.Z);
|
||||
}
|
||||
|
||||
throw new ArgumentException("Value must be of type System.Numerics.Vector3.", nameof(value));
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is Quaternion quaternion)
|
||||
{
|
||||
return VectorUtility.CreateFromQuaternion(quaternion);
|
||||
}
|
||||
throw new ArgumentException("Value must be of type System.Numerics.Quaternion.", nameof(value));
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using Ghost.Data.Services;
|
||||
using Ghost.Editor.View.Pages.EngineEditor;
|
||||
using Ghost.Editor.View.Pages.Landing;
|
||||
using Ghost.Editor.View.Windows;
|
||||
using Ghost.Editor.ViewModels.Pages.EngineEditor;
|
||||
using Ghost.Editor.ViewModels.Pages.Landing;
|
||||
using Ghost.Editor.ViewModels.Windows;
|
||||
using Ghost.Engine;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Ghost.Editor.Utilities;
|
||||
|
||||
internal static partial class HostHelper
|
||||
{
|
||||
public static void AddLandingScope(HostBuilderContext context, IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<LandingWindow>();
|
||||
|
||||
services.AddTransient<CreateProjectPage>();
|
||||
services.AddTransient<CreateProjectViewModel>();
|
||||
|
||||
services.AddTransient<OpenProjectPage>();
|
||||
services.AddTransient<OpenProjectViewModel>();
|
||||
|
||||
services.AddTransient<ProjectService>();
|
||||
}
|
||||
|
||||
public static void AddEngineScope(HostBuilderContext context, IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<EngineCore>();
|
||||
|
||||
services.AddSingleton<EngineEditorWindow>();
|
||||
services.AddSingleton<EngineEditorViewModel>();
|
||||
|
||||
services.AddTransient<ScenePage>();
|
||||
|
||||
services.AddTransient<HierarchyPage>();
|
||||
services.AddTransient<HierarchyViewModel>();
|
||||
|
||||
services.AddTransient<ProjectPage>();
|
||||
services.AddTransient<ProjectViewModel>();
|
||||
|
||||
services.AddTransient<ConsolePage>();
|
||||
services.AddTransient<ConsoleViewModel>();
|
||||
|
||||
services.AddTransient<InspectorPage>();
|
||||
services.AddTransient<InspectorViewModel>();
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using Ghost.Editor.Controls.Internal;
|
||||
using Ghost.Graphics.Contracts;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using WinRT;
|
||||
|
||||
namespace Ghost.Editor.View.Pages.EngineEditor;
|
||||
|
||||
internal sealed partial class ScenePage : NavigationTabPage
|
||||
{
|
||||
private Renderer? _renderView;
|
||||
private ISwapChainPanelNative _swapChainPanelNative;
|
||||
|
||||
public ScenePage()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
SwapChainPanel.Loaded += SwapChainPanel_Loaded;
|
||||
SwapChainPanel.Unloaded += SwapChainPanel_Unloaded;
|
||||
SwapChainPanel.SizeChanged += SwapChainPanel_SizeChanged;
|
||||
}
|
||||
|
||||
private void SwapChainPanel_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var guid = typeof(ISwapChainPanelNative.Interface).GUID;
|
||||
((IWinRTObject)SwapChainPanel).NativeObject.TryAs(guid, out var swapChainPanelNativeHandle);
|
||||
_swapChainPanelNative = new ISwapChainPanelNative(swapChainPanelNativeHandle);
|
||||
|
||||
_renderView = GraphicsPipeline.GraphicsDevice.CreateRenderer(new(_swapChainPanelNative, (uint)SwapChainPanel.ActualWidth, (uint)SwapChainPanel.ActualHeight));
|
||||
}
|
||||
|
||||
private void SwapChainPanel_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_swapChainPanelNative.Dispose();
|
||||
_renderView?.Dispose();
|
||||
}
|
||||
|
||||
private void SwapChainPanel_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
if (e.NewSize.Width > 8.0 && e.NewSize.Height > 8.0)
|
||||
{
|
||||
_renderView?.RequestResize((uint)e.NewSize.Width, (uint)e.NewSize.Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="Ghost.Editor.View.Pages.Landing.CreateProjectPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:data="using:Ghost.Data.Models"
|
||||
xmlns:editor="using:Ghost.Editor.Core.Controls"
|
||||
xmlns:local="using:Ghost.Editor.View.Pages.Landing"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
NavigationCacheMode="Enabled"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Template Info -->
|
||||
<Grid Grid.Column="0" Width="300">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Margin="0,0,0,24"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="Template" />
|
||||
|
||||
<ListView
|
||||
Grid.Row="1"
|
||||
ItemsSource="{x:Bind ViewModel.templates}"
|
||||
SelectedItem="{x:Bind ViewModel.SelectedTemplate, Mode=TwoWay}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="data:TemplateData">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ImageIcon
|
||||
Grid.Column="0"
|
||||
Width="24"
|
||||
Height="24">
|
||||
<ImageIcon.Source>
|
||||
<BitmapImage UriSource="{x:Bind GetIconURI()}" />
|
||||
</ImageIcon.Source>
|
||||
</ImageIcon>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="8,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Info.Name}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
|
||||
<!-- Project Info -->
|
||||
<Grid
|
||||
Grid.Column="1"
|
||||
Margin="16,0,0,0"
|
||||
Padding="16"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="{StaticResource OverlayCornerRadius}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="300" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" CornerRadius="4">
|
||||
<Image VerticalAlignment="Center" Stretch="UniformToFill">
|
||||
<Image.Source>
|
||||
<BitmapImage UriSource="{x:Bind ViewModel.SelectedTemplate.Value.GetPreviewURI(), Mode=OneWay}" />
|
||||
</Image.Source>
|
||||
</Image>
|
||||
<Grid
|
||||
MaxHeight="100"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="{ThemeResource ControlOnImageFillColorDefaultBrush}">
|
||||
<TextBlock
|
||||
Margin="16"
|
||||
VerticalAlignment="Bottom"
|
||||
Foreground="{ThemeResource TextFillColorTertiaryBrush}"
|
||||
Text="{x:Bind ViewModel.SelectedTemplate.Value.Info.Description, Mode=OneWay}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Row="1" Margin="8,0">
|
||||
<TextBlock
|
||||
Margin="0,16,0,8"
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
Text="{x:Bind ViewModel.SelectedTemplate.Value.Info.Name, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
Margin="0,8,0,16"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="Project Settings" />
|
||||
|
||||
<editor:PropertyField Label="Name">
|
||||
<TextBox Text="{x:Bind ViewModel.ProjectName, Mode=TwoWay}" />
|
||||
</editor:PropertyField>
|
||||
<editor:PropertyField Label="Location">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBox
|
||||
Grid.Column="0"
|
||||
IsReadOnly="True"
|
||||
Text="{x:Bind ViewModel.ProjectLocation, Mode=TwoWay}" />
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
Margin="4,0,0,0"
|
||||
VerticalAlignment="Stretch"
|
||||
Command="{x:Bind ViewModel.SelectionProjectLocationCommand}">
|
||||
<FontIcon FontSize="16" Glyph="" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</editor:PropertyField>
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="2">
|
||||
<Button
|
||||
Width="150"
|
||||
HorizontalAlignment="Right"
|
||||
Command="{x:Bind ViewModel.CreateProjectCommand}"
|
||||
Content="Create"
|
||||
Style="{ThemeResource AccentButtonStyle}" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -1,32 +0,0 @@
|
||||
using Ghost.Editor.ViewModels.Pages.Landing;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Ghost.Editor.View.Pages.Landing;
|
||||
|
||||
internal sealed partial class CreateProjectPage : Page
|
||||
{
|
||||
public CreateProjectViewModel ViewModel
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public CreateProjectPage()
|
||||
{
|
||||
ViewModel = App.GetService<CreateProjectViewModel>();
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
ViewModel.OnNavigatedTo(e.Parameter);
|
||||
}
|
||||
|
||||
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedFrom(e);
|
||||
ViewModel.OnNavigatedFrom();
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="Ghost.Editor.View.Pages.Landing.OpenProjectPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="using:Ghost.Editor.Utilities.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:data="using:Ghost.Data.Models"
|
||||
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:local="using:Ghost.Editor.View.Pages.Landing"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
NavigationCacheMode="Enabled"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
<converters:GetDirectoryNameConverter x:Key="DirNameConverter" />
|
||||
</Page.Resources>
|
||||
|
||||
<Grid x:Name="MainContainer">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" Margin="16,4">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="Projects" />
|
||||
<AutoSuggestBox
|
||||
Width="300"
|
||||
HorizontalAlignment="Right"
|
||||
PlaceholderText="Search project by name"
|
||||
QueryIcon="Find" />
|
||||
</Grid>
|
||||
|
||||
<!-- Header for the ListView -->
|
||||
<Grid Grid.Row="1" Margin="28,16,45,8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="200" />
|
||||
<ColumnDefinition Width="165" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="NAME" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="LAST OPEN" />
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="ENGINE VERSION" />
|
||||
</Grid>
|
||||
|
||||
<!-- Project ListView -->
|
||||
<Grid
|
||||
Grid.Row="2"
|
||||
Padding="8"
|
||||
AllowDrop="True"
|
||||
DragEnter="ProjectContainer_DragEnter"
|
||||
DragLeave="ProjectContainer_DragLeave"
|
||||
DragOver="ProjectContainer_DragOver"
|
||||
Drop="ProjectContainer_Drop">
|
||||
<ListView
|
||||
Padding="4,8"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
CornerRadius="{StaticResource OverlayCornerRadius}"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="ListView_ItemClick"
|
||||
ItemsSource="{x:Bind ViewModel.projects}"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="data:ProjectMetadataInfo">
|
||||
<Grid Height="64" Padding="4,8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="200" />
|
||||
<ColumnDefinition Width="100" />
|
||||
<ColumnDefinition Width="65" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid Grid.Column="0" VerticalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="{x:Bind Metadata.Name}" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="0,4,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind Path, Converter={StaticResource DirNameConverter}}" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="16,4"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Metadata.LastOpened}" />
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
Margin="16,4"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="{x:Bind Metadata.EngineVersion}" />
|
||||
<Button
|
||||
Grid.Column="3"
|
||||
HorizontalAlignment="Right"
|
||||
Background="Transparent"
|
||||
BorderThickness="0">
|
||||
<FontIcon Glyph="" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<!-- Drag Visual -->
|
||||
<Grid
|
||||
x:Name="DragVisual"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource ControlStrongStrokeColorDefaultBrush}"
|
||||
BorderThickness="2"
|
||||
CornerRadius="{StaticResource OverlayCornerRadius}"
|
||||
Visibility="{x:Bind ViewModel.DragVisibility, Mode=OneWay}">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
Text="Drage Project Folder Here" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- Empty Place Holder -->
|
||||
<Grid
|
||||
x:Name="EmptyPlaceHolder"
|
||||
Grid.Row="2"
|
||||
Visibility="{x:Bind ViewModel.EmptyVisibility, Mode=OneWay}">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
Text="No projects found" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -1,72 +0,0 @@
|
||||
using Ghost.Data.Models;
|
||||
using Ghost.Editor.ViewModels.Pages.Landing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
|
||||
namespace Ghost.Editor.View.Pages.Landing;
|
||||
|
||||
internal sealed partial class OpenProjectPage : Page
|
||||
{
|
||||
public OpenProjectViewModel ViewModel
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
public OpenProjectPage()
|
||||
{
|
||||
ViewModel = App.GetService<OpenProjectViewModel>();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
ViewModel.OnNavigatedTo(e.Parameter);
|
||||
}
|
||||
|
||||
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedFrom(e);
|
||||
ViewModel.OnNavigatedFrom();
|
||||
}
|
||||
|
||||
private void ProjectContainer_DragEnter(object sender, DragEventArgs e)
|
||||
{
|
||||
ViewModel.DragVisibility = Visibility.Visible;
|
||||
ViewModel.EmptyVisibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void ProjectContainer_DragLeave(object sender, DragEventArgs e)
|
||||
{
|
||||
ViewModel.DragVisibility = Visibility.Collapsed;
|
||||
ViewModel.UpdateEmptyPlaceHolderVisibility();
|
||||
}
|
||||
|
||||
private void ProjectContainer_DragOver(object sender, DragEventArgs e)
|
||||
{
|
||||
if (e.DataView.Contains(StandardDataFormats.StorageItems))
|
||||
{
|
||||
e.AcceptedOperation = DataPackageOperation.Link;
|
||||
}
|
||||
else
|
||||
{
|
||||
e.AcceptedOperation = DataPackageOperation.None;
|
||||
}
|
||||
}
|
||||
|
||||
private async void ProjectContainer_Drop(object sender, DragEventArgs e)
|
||||
{
|
||||
await ViewModel.ContentDrop(e.DataView);
|
||||
}
|
||||
|
||||
private async void ListView_ItemClick(object sender, ItemClickEventArgs e)
|
||||
{
|
||||
if (e.ClickedItem is ProjectMetadataInfo project)
|
||||
{
|
||||
await Task.Yield();
|
||||
await ViewModel.OpenProjectAsync(project);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<winex:WindowEx
|
||||
x:Class="Ghost.Editor.View.Windows.LandingWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:local="using:Ghost.Editor.View.Windows"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:winex="using:WinUIEx"
|
||||
Activated="WindowEx_Activated"
|
||||
Closed="WindowEx_Closed"
|
||||
IsResizable="False"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Window.SystemBackdrop>
|
||||
<MicaBackdrop />
|
||||
</Window.SystemBackdrop>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="32" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0">
|
||||
<TextBlock
|
||||
Margin="24,0,0,0"
|
||||
VerticalAlignment="Bottom"
|
||||
Style="{StaticResource BodyTextBlockStyle}"
|
||||
Text="Ghost Engine" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" Padding="24,0,24,18">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<SelectorBar
|
||||
Grid.Row="0"
|
||||
HorizontalAlignment="Right"
|
||||
SelectionChanged="SelectorBar_SelectionChanged">
|
||||
<SelectorBarItem IsSelected="True" Text="Open">
|
||||
<SelectorBarItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</SelectorBarItem.Icon>
|
||||
</SelectorBarItem>
|
||||
<SelectorBarItem Text="Create">
|
||||
<SelectorBarItem.Icon>
|
||||
<FontIcon Glyph="" />
|
||||
</SelectorBarItem.Icon>
|
||||
</SelectorBarItem>
|
||||
</SelectorBar>
|
||||
|
||||
<Frame
|
||||
x:Name="ContentFrame"
|
||||
Grid.Row="1"
|
||||
Padding="8"
|
||||
CacheMode="BitmapCache"
|
||||
CacheSize="10" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" Padding="16">
|
||||
<InfoBar
|
||||
x:Name="InfoBar"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<behaviors:StackedNotificationsBehavior x:Name="NotificationQueue" />
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</InfoBar>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</winex:WindowEx>
|
||||
@@ -1,59 +0,0 @@
|
||||
using Ghost.Data.Resources;
|
||||
using Ghost.Editor.Core.Notifications;
|
||||
using Ghost.Editor.View.Pages.Landing;
|
||||
using Ghost.Engine.Resources;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using WinUIEx;
|
||||
|
||||
namespace Ghost.Editor.View.Windows;
|
||||
|
||||
internal sealed partial class LandingWindow : WindowEx
|
||||
{
|
||||
private readonly NotificationService _notificationService;
|
||||
|
||||
private int _previousSelectedIndex;
|
||||
|
||||
public LandingWindow()
|
||||
{
|
||||
_notificationService = (NotificationService)App.GetService<INotificationService>();
|
||||
|
||||
AppWindow.SetIcon(AssetsPath.s_appIconPath);
|
||||
Title = EngineData.ENGINE_NAME;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
this.SetWindowSize(1000, 750);
|
||||
this.CenterOnScreen();
|
||||
|
||||
ExtendsContentIntoTitleBar = true;
|
||||
}
|
||||
|
||||
private void WindowEx_Activated(object sender, Microsoft.UI.Xaml.WindowActivatedEventArgs args)
|
||||
{
|
||||
_notificationService.SetReference(InfoBar, NotificationQueue);
|
||||
}
|
||||
|
||||
private void WindowEx_Closed(object sender, Microsoft.UI.Xaml.WindowEventArgs args)
|
||||
{
|
||||
_notificationService.ClearReference();
|
||||
}
|
||||
|
||||
private void SelectorBar_SelectionChanged(SelectorBar sender, SelectorBarSelectionChangedEventArgs e)
|
||||
{
|
||||
var selectedItem = sender.SelectedItem;
|
||||
var currentSelectedIndex = sender.Items.IndexOf(selectedItem);
|
||||
var pageType = currentSelectedIndex switch
|
||||
{
|
||||
1 => typeof(CreateProjectPage),
|
||||
_ => typeof(OpenProjectPage),
|
||||
};
|
||||
|
||||
var slideNavigationTransitionEffect = currentSelectedIndex - _previousSelectedIndex > 0 ?
|
||||
SlideNavigationTransitionEffect.FromRight : SlideNavigationTransitionEffect.FromLeft;
|
||||
|
||||
ContentFrame.Navigate(pageType, null, new SlideNavigationTransitionInfo() { Effect = slideNavigationTransitionEffect });
|
||||
|
||||
_previousSelectedIndex = currentSelectedIndex;
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Ghost.Engine.Models;
|
||||
using Ghost.Engine.Services;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Ghost.Editor.ViewModels.Pages.EngineEditor;
|
||||
|
||||
internal partial class ConsoleViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<LogMessage> Logs
|
||||
{
|
||||
get; set;
|
||||
} = new();
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool ShowInfo
|
||||
{
|
||||
get; set;
|
||||
} = true;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool ShowWarning
|
||||
{
|
||||
get; set;
|
||||
} = true;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool ShowError
|
||||
{
|
||||
get; set;
|
||||
} = true;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool ShowStackTrace
|
||||
{
|
||||
get; set;
|
||||
} = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial LogMessage? SelectedLog
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ConsoleViewModel()
|
||||
{
|
||||
foreach (var log in Logger.Logs)
|
||||
{
|
||||
Logs.Add(log);
|
||||
}
|
||||
|
||||
Logger.OnLogsUpdate += UpdateLogs;
|
||||
}
|
||||
|
||||
~ConsoleViewModel()
|
||||
{
|
||||
Logger.OnLogsUpdate -= UpdateLogs;
|
||||
}
|
||||
|
||||
private void UpdateLogs(LogChangeContext ctx)
|
||||
{
|
||||
switch (ctx.changeType)
|
||||
{
|
||||
case LogChangeType.LogAdded:
|
||||
Logs.Add(Logger.Logs[ctx.index]);
|
||||
break;
|
||||
case LogChangeType.LogRemoved:
|
||||
if (Logs.Count > 0)
|
||||
{
|
||||
Logs.RemoveAt(ctx.index);
|
||||
}
|
||||
break;
|
||||
case LogChangeType.LogsCleared:
|
||||
Logs.Clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnShowStackTraceChanged(bool value)
|
||||
{
|
||||
Logger.HasStackTrace = value;
|
||||
Logger.LogInfo($"Stack trace visibility set to {value}.");
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ClearLogs()
|
||||
{
|
||||
Logger.Clear();
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Ghost.Editor.Core.Contracts;
|
||||
using Ghost.Editor.Core.SceneGraph;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Ghost.Editor.ViewModels.Pages.EngineEditor;
|
||||
|
||||
internal partial class HierarchyViewModel : ObservableObject, INavigationAware
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<WorldNode> SceneList
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new(EditorWorldManager.LoadedWorlds);
|
||||
|
||||
private void OnWorldLoaded(WorldNode node)
|
||||
{
|
||||
SceneList.Add(node);
|
||||
}
|
||||
|
||||
private void OnWorldUnloaded(WorldNode node)
|
||||
{
|
||||
SceneList.Remove(node);
|
||||
}
|
||||
|
||||
public void OnNavigatedTo(object? parameter)
|
||||
{
|
||||
EditorWorldManager.OnWorldLoaded += OnWorldLoaded;
|
||||
EditorWorldManager.OnWorldUnloaded += OnWorldUnloaded;
|
||||
}
|
||||
|
||||
public void OnNavigatedFrom()
|
||||
{
|
||||
EditorWorldManager.OnWorldLoaded -= OnWorldLoaded;
|
||||
EditorWorldManager.OnWorldUnloaded -= OnWorldUnloaded;
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Ghost.Data.Models;
|
||||
using Ghost.Data.Services;
|
||||
using Ghost.Editor.Core.AppState;
|
||||
using Ghost.Editor.Core.Contracts;
|
||||
using Ghost.Editor.Core.Notifications;
|
||||
using Ghost.Editor.Utilities;
|
||||
using Ghost.Engine.Resources;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Ghost.Editor.ViewModels.Pages.Landing;
|
||||
|
||||
internal partial class CreateProjectViewModel(INotificationService notificationService, ProjectService projectService, AppStateMachine stateService) : ObservableObject, INavigationAware
|
||||
{
|
||||
public ObservableCollection<TemplateData> templates = new();
|
||||
|
||||
[ObservableProperty]
|
||||
public partial TemplateData? SelectedTemplate
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string? ProjectName
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string? ProjectLocation
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public async void OnNavigatedTo(object? parameter)
|
||||
{
|
||||
templates.Clear();
|
||||
await foreach (var (path, info) in ProjectService.GetProjectTemplatesAsync())
|
||||
{
|
||||
templates.Add(new(path, info));
|
||||
}
|
||||
|
||||
SelectedTemplate = templates.FirstOrDefault();
|
||||
}
|
||||
|
||||
public void OnNavigatedFrom()
|
||||
{
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task SelectionProjectLocation()
|
||||
{
|
||||
var folder = await SystemUtilities.OpenFolderPickerAsync();
|
||||
if (folder != null)
|
||||
{
|
||||
ProjectLocation = folder.Path;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task CreateProject()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ProjectName)
|
||||
|| !Directory.Exists(ProjectLocation)
|
||||
|| !SelectedTemplate.HasValue)
|
||||
{
|
||||
notificationService.ShowNotification("Incorrect project info", MessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await projectService.CreateProjectAsync(ProjectName, ProjectLocation, EngineData.s_engineVersion, SelectedTemplate.Value.directory);
|
||||
if (result.IsFailure)
|
||||
{
|
||||
notificationService.ShowNotification(result.Message, MessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await stateService.TransitionToAsync(StateKey.EngineEditor, result.Value);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
notificationService.ShowNotification($"Failed to load project: {e.Message}", MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Ghost.Data.Models;
|
||||
using Ghost.Data.Services;
|
||||
using Ghost.Editor.Core.AppState;
|
||||
using Ghost.Editor.Core.Contracts;
|
||||
using Ghost.Editor.Core.Notifications;
|
||||
using Microsoft.UI.Xaml;
|
||||
using System.Collections.ObjectModel;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace Ghost.Editor.ViewModels.Pages.Landing;
|
||||
|
||||
internal partial class OpenProjectViewModel(ProjectService projectService, INotificationService _notificationService, AppStateMachine _stateService) : ObservableObject, INavigationAware
|
||||
{
|
||||
public readonly ObservableCollection<ProjectMetadataInfo> projects = new();
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Visibility EmptyVisibility
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Visibility DragVisibility
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public void UpdateEmptyPlaceHolderVisibility()
|
||||
{
|
||||
EmptyVisibility = projects.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public async void OnNavigatedTo(object? parameter)
|
||||
{
|
||||
await foreach (var projectInfo in projectService.GetAllProjectAsync())
|
||||
{
|
||||
var metadata = await ProjectService.LoadMetadataAsync(projectInfo.MetadataPath);
|
||||
if (metadata == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
projects.Add(new(projectInfo.MetadataPath, metadata));
|
||||
}
|
||||
|
||||
UpdateEmptyPlaceHolderVisibility();
|
||||
DragVisibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public void OnNavigatedFrom()
|
||||
{
|
||||
projects.Clear();
|
||||
}
|
||||
|
||||
public async Task ContentDrop(DataPackageView dataView)
|
||||
{
|
||||
var errorMessage = string.Empty;
|
||||
if (dataView.Contains(StandardDataFormats.StorageItems))
|
||||
{
|
||||
var items = await dataView.GetStorageItemsAsync();
|
||||
var rootFolder = items.OfType<StorageFolder>().FirstOrDefault();
|
||||
if (rootFolder != null)
|
||||
{
|
||||
var result = await projectService.AddProjectFromDirectoryAsync(rootFolder.Path);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
projects.Add(result.Value);
|
||||
goto CloseDropPanel;
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = result.Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = "Unsupported data format. Please drop a folder containing a project.";
|
||||
}
|
||||
|
||||
_notificationService.ShowNotification(errorMessage, MessageType.Error);
|
||||
|
||||
CloseDropPanel:
|
||||
DragVisibility = Visibility.Collapsed;
|
||||
UpdateEmptyPlaceHolderVisibility();
|
||||
}
|
||||
|
||||
public async Task OpenProjectAsync(ProjectMetadataInfo project)
|
||||
{
|
||||
try
|
||||
{
|
||||
project.Metadata.LastOpened = DateTime.Now;
|
||||
await ProjectService.CreateMetadataFileAsync(project.Path, project.Metadata);
|
||||
|
||||
await _stateService.TransitionToAsync(StateKey.EngineEditor, project);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_notificationService.ShowNotification($"Failed to load project: {e.Message}", MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Ghost.Data.Models;
|
||||
using Ghost.Data.Services;
|
||||
using Ghost.Engine.Resources;
|
||||
|
||||
namespace Ghost.Editor.ViewModels.Windows;
|
||||
|
||||
internal partial class EngineEditorViewModel : ObservableRecipient
|
||||
{
|
||||
public string engineVersionDescriptor = $"{EngineData.ENGINE_NAME} - {EngineData.s_engineVersion}";
|
||||
|
||||
public ProjectMetadataInfo CurrentProject => ProjectService.CurrentProject;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
using Ghost.Core.Attributes;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Ghost.Editor")]
|
||||
|
||||
[assembly: EngineAssembly]
|
||||
@@ -1,21 +0,0 @@
|
||||
using Ghost.Engine.Utilities;
|
||||
using Ghost.SparseEntities.Components;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Engine.Components;
|
||||
|
||||
[SkipLocalsInit]
|
||||
public struct LocalToWorld : IComponentData
|
||||
{
|
||||
public Matrix4x4 matrix;
|
||||
|
||||
public static LocalToWorld Identity
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => new()
|
||||
{
|
||||
matrix = MatrixUtility.CreateTRS(Vector3.Zero, Quaternion.Identity, Vector3.One)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using Ghost.Core;
|
||||
using Ghost.Engine.Models;
|
||||
|
||||
namespace Ghost.Engine;
|
||||
|
||||
internal class EngineCore
|
||||
{
|
||||
public void Start(LaunchArgument args)
|
||||
{
|
||||
ActivationHandler.Handle(args);
|
||||
|
||||
//GraphicsPipeline.Initialize();
|
||||
//GraphicsPipeline.Start();
|
||||
|
||||
Logger.LogInfo("Engine started successfully.");
|
||||
}
|
||||
|
||||
public void IncrementCPUFenceValue()
|
||||
{
|
||||
//GraphicsPipeline.SignalCPUReady();
|
||||
}
|
||||
|
||||
public void ShutDown()
|
||||
{
|
||||
//GraphicsPipeline.SignalCPUReady();
|
||||
//GraphicsPipeline.Shutdown();
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
namespace Ghost.Engine.Models;
|
||||
|
||||
public enum LogLevel
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
}
|
||||
|
||||
internal class LogMessage
|
||||
{
|
||||
public LogLevel Level
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string? Message
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public string? StackTrace
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public DateTime Timestamp
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public LogMessage(LogLevel level, string? message, string? stackTrace = null)
|
||||
{
|
||||
Level = level;
|
||||
Message = message;
|
||||
StackTrace = stackTrace;
|
||||
Timestamp = DateTime.Now;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Timestamp:HH:mm:ss} [{Level}] {Message}";
|
||||
}
|
||||
|
||||
public string ToStringWithStackTrace()
|
||||
{
|
||||
if (StackTrace == null)
|
||||
{
|
||||
return ToString();
|
||||
}
|
||||
|
||||
return $"{ToString()}\n{StackTrace}";
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
using Ghost.SparseEntities;
|
||||
|
||||
namespace Ghost.Engine.Services;
|
||||
|
||||
internal static class PlayerLoopService
|
||||
{
|
||||
private static bool _isRunning = false;
|
||||
|
||||
// TODO: Implement the actual time system
|
||||
public static float fixedDeltaTime = 0.02f;
|
||||
|
||||
public static void Start()
|
||||
{
|
||||
if (_isRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < World.WorldCount; i++)
|
||||
{
|
||||
var world = World.GetWorld(i);
|
||||
|
||||
foreach (var script in world.QueryScript())
|
||||
{
|
||||
script.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
for (var i = 0; i < World.WorldCount; i++)
|
||||
{
|
||||
var world = World.GetWorld(i);
|
||||
world.SystemStorage.UpdateSystems();
|
||||
|
||||
foreach (var script in world.QueryScript())
|
||||
{
|
||||
script.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Shutdown()
|
||||
{
|
||||
for (var i = 0; i < World.WorldCount; i++)
|
||||
{
|
||||
var world = World.GetWorld(i);
|
||||
foreach (var script in world.QueryScript())
|
||||
{
|
||||
script.OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
_isRunning = false;
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
namespace Ghost.Engine.Services;
|
||||
|
||||
public enum SceneLoadMode
|
||||
{
|
||||
Single,
|
||||
Additive
|
||||
}
|
||||
|
||||
public static class SceneManager
|
||||
{
|
||||
//private readonly static HashSet<Scene> _activeScenes = new();
|
||||
|
||||
//internal static IEnumerable<GameObject> QueryRootGameObjects()
|
||||
//{
|
||||
// foreach (var scene in _activeScenes)
|
||||
// {
|
||||
// foreach (var gameObject in scene.RootObjects)
|
||||
// {
|
||||
// if (!gameObject.IsActive)
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// yield return gameObject;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
//public static void LoadScene(Scene scene, SceneLoadMode loadMode)
|
||||
//{
|
||||
// if (loadMode == SceneLoadMode.Single)
|
||||
// {
|
||||
// foreach (var activeScene in _activeScenes)
|
||||
// {
|
||||
// activeScene.Unload();
|
||||
// }
|
||||
// _activeScenes.Clear();
|
||||
// }
|
||||
|
||||
// _activeScenes.Add(scene);
|
||||
// scene.Load();
|
||||
//}
|
||||
|
||||
//public static Task LoadSceneAsync(Scene scene, SceneLoadMode loadMode)
|
||||
//{
|
||||
// return Task.Run(() => LoadScene(scene, loadMode));
|
||||
//}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Engine.Utilities;
|
||||
|
||||
public static class MathUtility
|
||||
{
|
||||
public const float RAD_TO_DEG = 180f / MathF.PI;
|
||||
public const float DEG_TO_RAD = MathF.PI / 180f;
|
||||
|
||||
/// <summary>
|
||||
/// Converts radians to degrees.
|
||||
/// </summary>
|
||||
/// <param name="radians">The angle in radians to convert.</param>
|
||||
/// <returns>The angle in degrees.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float RadToDeg(float radians)
|
||||
{
|
||||
return radians * RAD_TO_DEG;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts degrees to radians.
|
||||
/// </summary>
|
||||
/// <param name="degrees">The angle in degrees to convert.</param>
|
||||
/// <returns>The angle in radians.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float DegToRad(float degrees)
|
||||
{
|
||||
return degrees * DEG_TO_RAD;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Engine.Utilities;
|
||||
|
||||
public static class MatrixUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a transformation matrix from position, rotation, and scale vectors.
|
||||
/// </summary>
|
||||
/// <param name="position">Defines the translation component of the transformation matrix.</param>
|
||||
/// <param name="rotation">Specifies the orientation of the object in 3D space.</param>
|
||||
/// <param name="scale">Determines the size of the object along each axis.</param>
|
||||
/// <returns>Returns a transformation matrix that combines the specified position, rotation, and scale.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Matrix4x4 CreateTRS(Vector3 position, Quaternion rotation, Vector3 scale)
|
||||
{
|
||||
return Matrix4x4.CreateScale(scale) * Matrix4x4.CreateFromQuaternion(rotation) * Matrix4x4.CreateTranslation(position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decomposes a transformation matrix into its position, rotation, and scale components.
|
||||
/// </summary>
|
||||
/// <remarks>This method assumes the input matrix represents a valid affine transformation, including
|
||||
/// translation, rotation, and scaling. If the matrix contains skew or other non-standard transformations, the
|
||||
/// results may be undefined.</remarks>
|
||||
/// <param name="matrix">The <see cref="Matrix4x4"/> to decompose. Must represent a valid transformation matrix.</param>
|
||||
/// <param name="position">When the method returns, contains the position component extracted from the matrix.</param>
|
||||
/// <param name="rotation">When the method returns, contains the rotation component extracted from the matrix as a <see
|
||||
/// cref="Quaternion"/>.</param>
|
||||
/// <param name="scale">When the method returns, contains the scale component extracted from the matrix.</param>
|
||||
public static void GetTRS(Matrix4x4 matrix, out Vector3 position, out Quaternion rotation, out Vector3 scale)
|
||||
{
|
||||
position = new(matrix.M41, matrix.M42, matrix.M43);
|
||||
|
||||
var scaleX = new Vector3(matrix.M11, matrix.M12, matrix.M13).Length();
|
||||
var scaleY = new Vector3(matrix.M21, matrix.M22, matrix.M23).Length();
|
||||
var scaleZ = new Vector3(matrix.M31, matrix.M32, matrix.M33).Length();
|
||||
scale = new(scaleX, scaleY, scaleZ);
|
||||
|
||||
Matrix4x4 rotationMatrix = new(
|
||||
matrix.M11 / scale.X, matrix.M12 / scale.X, matrix.M13 / scale.X, 0,
|
||||
matrix.M21 / scale.Y, matrix.M22 / scale.Y, matrix.M23 / scale.Y, 0,
|
||||
matrix.M31 / scale.Z, matrix.M32 / scale.Z, matrix.M33 / scale.Z, 0,
|
||||
0, 0, 0, 1);
|
||||
|
||||
rotation = Quaternion.CreateFromRotationMatrix(rotationMatrix);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Engine.Utilities;
|
||||
|
||||
public static class VectorUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a Vector3 representing Euler angles (in degrees) to a Quaternion.
|
||||
/// </summary>
|
||||
/// <param name="v">The Vector3 containing Euler angles (X: Pitch, Y: Yaw, Z: Roll) in degrees.</param>
|
||||
/// <returns>A Quaternion representing the rotation defined by the Euler angles.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion ToQuaternion(this Vector3 v)
|
||||
{
|
||||
return Quaternion.CreateFromYawPitchRoll(MathUtility.DegToRad(v.Y), MathUtility.DegToRad(v.X), MathUtility.DegToRad(v.Z));
|
||||
}
|
||||
|
||||
public static Vector3 CreateFromQuaternion(Quaternion quaternion)
|
||||
{
|
||||
// Convert quaternion to Euler angles (Yaw, Pitch, Roll)
|
||||
quaternion = Quaternion.Normalize(quaternion);
|
||||
|
||||
// Extract pitch (X), yaw (Y), roll (Z)
|
||||
var ysqr = quaternion.Y * quaternion.Y;
|
||||
|
||||
// Pitch (X-axis rotation)
|
||||
var t0 = +2.0 * (quaternion.W * quaternion.X + quaternion.Y * quaternion.Z);
|
||||
var t1 = +1.0 - 2.0 * (quaternion.X * quaternion.X + ysqr);
|
||||
var pitch = Math.Atan2(t0, t1);
|
||||
|
||||
// Yaw (Y-axis rotation)
|
||||
var t2 = +2.0 * (quaternion.W * quaternion.Y - quaternion.Z * quaternion.X);
|
||||
t2 = Math.Clamp(t2, -1.0, 1.0);
|
||||
var yaw = Math.Asin(t2);
|
||||
|
||||
// Roll (Z-axis rotation)
|
||||
var t3 = +2.0 * (quaternion.W * quaternion.Z + quaternion.X * quaternion.Y);
|
||||
var t4 = +1.0 - 2.0 * (ysqr + quaternion.Z * quaternion.Z);
|
||||
var roll = Math.Atan2(t3, t4);
|
||||
|
||||
const float radToDeg = 180f / MathF.PI;
|
||||
return new Vector3((float)pitch, (float)yaw, (float)roll) * radToDeg;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ghost.Entities\Ghost.Entities.csproj" />
|
||||
<ProjectReference Include="..\Ghost.Test.Core\Ghost.Test.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,7 +0,0 @@
|
||||
using Ghost.Entities.Test;
|
||||
using Ghost.Test.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
|
||||
AllocationManager.EnableDebugLayer();
|
||||
TestRunner.Run<SystemTest>();
|
||||
AllocationManager.Dispose();
|
||||
@@ -1,125 +0,0 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public interface IComponent
|
||||
{
|
||||
}
|
||||
|
||||
public interface IEnableableComponent : IComponent
|
||||
{
|
||||
}
|
||||
|
||||
internal struct ComponentInfo
|
||||
{
|
||||
// public string stableName; // Do we actually need this?
|
||||
public int id;
|
||||
public int size;
|
||||
public int alignment;
|
||||
public bool isEnableable;
|
||||
}
|
||||
|
||||
public static class ComponentTypeID<T>
|
||||
where T : unmanaged, IComponent
|
||||
{
|
||||
public static readonly Identifier<IComponent> value = ComponentRegister.GetOrRegisterComponent<T>();
|
||||
}
|
||||
|
||||
internal static class ComponentRegister
|
||||
{
|
||||
private static readonly List<ComponentInfo> s_registeredComponents = new();
|
||||
private static readonly Dictionary<IntPtr, int> s_typeHandleToID = new();
|
||||
private static readonly Dictionary<string, int> s_nameToRuntimeID = new();
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
internal static readonly Dictionary<int, Type> s_runtimeIDToType = new();
|
||||
#endif
|
||||
|
||||
public static unsafe Identifier<IComponent> GetOrRegisterComponent<T>()
|
||||
where T : unmanaged, IComponent
|
||||
{
|
||||
var type = typeof(T);
|
||||
var typeHandle = type.TypeHandle.Value;
|
||||
|
||||
lock (s_registeredComponents)
|
||||
{
|
||||
if (s_typeHandleToID.TryGetValue(typeHandle, out var existingID))
|
||||
{
|
||||
return existingID;
|
||||
}
|
||||
|
||||
var newID = new Identifier<IComponent>(s_registeredComponents.Count);
|
||||
var stableName = typeof(T).FullName ?? typeof(T).Name;
|
||||
var info = new ComponentInfo
|
||||
{
|
||||
// stableName = new FixedText64(stableName),
|
||||
id = newID,
|
||||
size = sizeof(T),
|
||||
alignment = (int)MemoryUtility.AlignOf<T>(),
|
||||
isEnableable = typeof(IEnableableComponent).IsAssignableFrom(type),
|
||||
// isManaged = typeof(IManagedWrapper).IsAssignableFrom(type),
|
||||
};
|
||||
|
||||
s_registeredComponents.Add(info);
|
||||
|
||||
s_typeHandleToID[typeHandle] = newID;
|
||||
s_nameToRuntimeID[stableName] = newID;
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
s_runtimeIDToType[newID.value] = typeof(T);
|
||||
#endif
|
||||
|
||||
return newID;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Identifier<IComponent> GetComponentID(Type type)
|
||||
{
|
||||
var typeHandle = type.TypeHandle.Value;
|
||||
lock (s_registeredComponents)
|
||||
{
|
||||
if (s_typeHandleToID.TryGetValue(typeHandle, out var existingID))
|
||||
{
|
||||
return existingID;
|
||||
}
|
||||
}
|
||||
|
||||
return Identifier<IComponent>.Invalid;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ComponentInfo GetComponentInfo(Identifier<IComponent> typeId)
|
||||
{
|
||||
lock (s_registeredComponents)
|
||||
{
|
||||
return s_registeredComponents[typeId];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: A ComponentSet structure to cache the hashcode for better performance.
|
||||
public static int GetHashCode(params ReadOnlySpan<Identifier<IComponent>> componentTypeIDs)
|
||||
{
|
||||
var largestID = 0;
|
||||
foreach (var id in componentTypeIDs)
|
||||
{
|
||||
if (id.value > largestID)
|
||||
{
|
||||
largestID = id.value;
|
||||
}
|
||||
}
|
||||
|
||||
var length = UnsafeBitSet.RequiredLength(largestID + 1);
|
||||
var bits = (Span<uint>)stackalloc uint[length];
|
||||
bits.Clear();
|
||||
|
||||
var bitSet = new SpanBitSet(bits);
|
||||
foreach (var id in componentTypeIDs)
|
||||
{
|
||||
bitSet.SetBit(id.value);
|
||||
}
|
||||
|
||||
return bitSet.GetHashCode();
|
||||
}
|
||||
}
|
||||
@@ -1,322 +0,0 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.Jobs;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.Entities;
|
||||
|
||||
public partial class World
|
||||
{
|
||||
private static readonly List<World?> s_worlds = new(4);
|
||||
private static readonly Queue<Identifier<World>> s_freeWorldSlots = new();
|
||||
|
||||
internal static Identifier<Archetype> EmptyArchetypeID => new (0);
|
||||
|
||||
public static int WorldCount => s_worlds.Count - s_freeWorldSlots.Count;
|
||||
|
||||
public static World Create(JobScheduler jobScheduler, int entityCapacity = 16)
|
||||
{
|
||||
lock (s_worlds)
|
||||
{
|
||||
if (s_freeWorldSlots.TryDequeue(out var index))
|
||||
{
|
||||
s_worlds[index.value] = new World(index, entityCapacity, jobScheduler);
|
||||
}
|
||||
else
|
||||
{
|
||||
index = new Identifier<World>(s_worlds.Count);
|
||||
s_worlds.Add(new World(index, entityCapacity, jobScheduler));
|
||||
}
|
||||
|
||||
return s_worlds[index.value]!;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Destroy(Identifier<World> id)
|
||||
{
|
||||
lock (s_worlds)
|
||||
{
|
||||
if (id.value < 0 || id.value >= s_worlds.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var world = s_worlds[id.value];
|
||||
world?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static World GetWorldUncheck(Identifier<World> id)
|
||||
{
|
||||
#if DEBUG || GHOST_EDITOR
|
||||
if (id.value < 0 || id.value >= s_worlds.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(id), "World ID is out of range.");
|
||||
}
|
||||
|
||||
var world = s_worlds[id.value];
|
||||
return world is null ? throw new InvalidOperationException("World not found.") : world;
|
||||
#else
|
||||
return s_worlds[id.value]!;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Result<World, ErrorStatus> GetWorld(Identifier<World> id)
|
||||
{
|
||||
if (id.value < 0 || id.value >= s_worlds.Count)
|
||||
{
|
||||
return ErrorStatus.InvalidArgument;
|
||||
}
|
||||
|
||||
var world = s_worlds[id.value];
|
||||
return world is null ? ErrorStatus.NotFound : world;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class World : IDisposable, IEquatable<World>
|
||||
{
|
||||
private readonly Identifier<World> _id;
|
||||
private readonly JobScheduler _jobScheduler;
|
||||
|
||||
private readonly EntityManager _entityManager;
|
||||
private readonly EntityCommandBuffer _entityCommandBuffer;
|
||||
private readonly EntityCommandBuffer[] _threadLocalECBs;
|
||||
|
||||
private readonly SystemManager _systemManager;
|
||||
|
||||
private UnsafeList<Archetype> _archetypes;
|
||||
private UnsafeList<EntityQuery> _entityQueries;
|
||||
|
||||
private UnsafeHashMap<int, Identifier<Archetype>> _archetypeLookup; // Signature Hash to Archetype ID
|
||||
private UnsafeHashMap<int, Identifier<EntityQuery>> _querieLookup; // Query Mask Hash to Query ID
|
||||
|
||||
private int _version;
|
||||
private bool _disposed = false;
|
||||
|
||||
internal int ArchetypeCount => _archetypes.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of this world.
|
||||
/// </summary>
|
||||
public Identifier<World> ID => _id;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the job scheduler associated with this world.
|
||||
/// </summary>
|
||||
public JobScheduler JobScheduler => _jobScheduler;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the publicntity manager for this world.
|
||||
/// </summary>
|
||||
public EntityManager EntityManager => _entityManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system manager for this world.
|
||||
/// </summary>
|
||||
public SystemManager SystemManager => _systemManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current version number of the world.
|
||||
/// </summary>
|
||||
public int Version => Interlocked.CompareExchange(ref _version, 0, 0);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the main entity command buffer for this world.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use <see cref="GetThreadLocalEntityCommandBuffer(int)"/> to get thread-local command buffers for multi-threaded jobs.
|
||||
/// </remarks>
|
||||
public EntityCommandBuffer EntityCommandBuffer => _entityCommandBuffer;
|
||||
|
||||
private World(Identifier<World> id, int entityCapacity, JobScheduler jobScheduler)
|
||||
{
|
||||
_id = id;
|
||||
_jobScheduler = jobScheduler;
|
||||
|
||||
_entityManager = new EntityManager(this, entityCapacity);
|
||||
_entityCommandBuffer = new EntityCommandBuffer(_entityManager);
|
||||
_threadLocalECBs = new EntityCommandBuffer[jobScheduler.WorkerCount];
|
||||
|
||||
_systemManager = new SystemManager(this);
|
||||
|
||||
_archetypes = new UnsafeList<Archetype>(16, Allocator.Persistent);
|
||||
_entityQueries = new UnsafeList<EntityQuery>(16, Allocator.Persistent);
|
||||
|
||||
_archetypeLookup = new UnsafeHashMap<int, Identifier<Archetype>>(16, Allocator.Persistent);
|
||||
_querieLookup = new UnsafeHashMap<int, Identifier<EntityQuery>>(16, Allocator.Persistent);
|
||||
|
||||
for (var i = 0; i < jobScheduler.WorkerCount; i++)
|
||||
{
|
||||
_threadLocalECBs[i] = new EntityCommandBuffer(_entityManager);
|
||||
}
|
||||
|
||||
// Create the empty archetype
|
||||
CreateArchetype(ReadOnlySpan<Identifier<IComponent>>.Empty, 0);
|
||||
}
|
||||
|
||||
~World()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal Identifier<Archetype> CreateArchetype(ReadOnlySpan<Identifier<IComponent>> componentTypeIDs, int signatureHash)
|
||||
{
|
||||
var arcID = new Identifier<Archetype>(_archetypes.Count);
|
||||
_archetypes.Add(new Archetype(arcID, _id, componentTypeIDs));
|
||||
_archetypeLookup.Add(signatureHash, arcID);
|
||||
|
||||
for (int i = 0; i < _entityQueries.Count; i++)
|
||||
{
|
||||
ref var query = ref _entityQueries[i];
|
||||
query.AddArchetypeIfMatch(in _archetypes[arcID.value]);
|
||||
}
|
||||
|
||||
return arcID;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal Identifier<Archetype> GetArchetypeIDBySignatureHash(int signatureHash)
|
||||
{
|
||||
if (_archetypeLookup.TryGetValue(signatureHash, out var arcID))
|
||||
{
|
||||
return arcID;
|
||||
}
|
||||
|
||||
return Identifier<Archetype>.Invalid;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal ref Archetype GetArchetypeReference(Identifier<Archetype> id)
|
||||
{
|
||||
return ref _archetypes[id.value];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal Identifier<EntityQuery> CreateEntityQuery(EntityQueryMask mask, int maskHash)
|
||||
{
|
||||
var queryID = new Identifier<EntityQuery>(_entityQueries.Count);
|
||||
_entityQueries.Add(new EntityQuery(queryID, _id, mask));
|
||||
_querieLookup.Add(maskHash, queryID);
|
||||
|
||||
ref var query = ref _entityQueries[queryID.value];
|
||||
for (var i = 0; i < _archetypes.Count; i++)
|
||||
{
|
||||
query.AddArchetypeIfMatch(in _archetypes[i]);
|
||||
}
|
||||
|
||||
return queryID;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal Identifier<EntityQuery> GetEntityQueryIDByMaskHash(int maskHash)
|
||||
{
|
||||
if (_querieLookup.TryGetValue(maskHash, out var queryID))
|
||||
{
|
||||
return queryID;
|
||||
}
|
||||
|
||||
return Identifier<EntityQuery>.Invalid;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void PlaybackEntityCommandBuffers()
|
||||
{
|
||||
_entityCommandBuffer.Playback();
|
||||
|
||||
for (var i = 0; i < _threadLocalECBs.Length; i++)
|
||||
{
|
||||
_threadLocalECBs[i].Playback();
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal int AdvanceVersion()
|
||||
{
|
||||
return Interlocked.Increment(ref _version);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to the entity query with the specified identifier.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ref EntityQuery GetEntityQueryReference(Identifier<EntityQuery> id)
|
||||
{
|
||||
return ref _entityQueries[id.value];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the thread-local entity command buffer for the specified thread index.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public EntityCommandBuffer GetThreadLocalEntityCommandBuffer(int threadIndex)
|
||||
{
|
||||
return _threadLocalECBs[threadIndex];
|
||||
}
|
||||
|
||||
public bool Equals(World? other)
|
||||
{
|
||||
return other is not null && _id == other._id;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return _id.GetHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is World other && Equals(other);
|
||||
}
|
||||
|
||||
public static bool operator ==(World? left, World? right)
|
||||
{
|
||||
return left?.Equals(right) ?? right is null;
|
||||
}
|
||||
|
||||
public static bool operator !=(World? left, World? right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (ref var archetype in _archetypes)
|
||||
{
|
||||
archetype.Dispose();
|
||||
}
|
||||
|
||||
foreach (ref var query in _entityQueries)
|
||||
{
|
||||
query.Dispose();
|
||||
}
|
||||
|
||||
_entityManager.Dispose();
|
||||
_entityCommandBuffer.Dispose();
|
||||
foreach (var v in _threadLocalECBs)
|
||||
{
|
||||
v.Dispose();
|
||||
}
|
||||
|
||||
_archetypes.Dispose();
|
||||
_entityQueries.Dispose();
|
||||
_archetypeLookup.Dispose();
|
||||
_querieLookup.Dispose();
|
||||
|
||||
s_freeWorldSlots.Enqueue(_id);
|
||||
s_worlds[_id] = null;
|
||||
|
||||
_disposed = true;
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
global using static TerraFX.Interop.DirectX.D3D12;
|
||||
global using static TerraFX.Interop.DirectX.DirectX;
|
||||
global using static TerraFX.Interop.DirectX.DXGI;
|
||||
global using static TerraFX.Interop.Windows.Windows;
|
||||
|
||||
using Ghost.Core.Attributes;
|
||||
using Ghost.Core.Utilities;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
[assembly: InternalsVisibleTo("Ghost.Engine")]
|
||||
[assembly: InternalsVisibleTo("Ghost.Editor")]
|
||||
[assembly: InternalsVisibleTo("Ghost.Editor.Core")]
|
||||
[assembly: InternalsVisibleTo("Ghost.Graphics.Test")]
|
||||
[assembly: SupportedOSPlatform("windows10.0.19041.0")]
|
||||
|
||||
|
||||
[assembly: EngineAssembly]
|
||||
@@ -1,11 +0,0 @@
|
||||
using Ghost.Graphics.Core;
|
||||
using Ghost.Graphics.RHI;
|
||||
|
||||
namespace Ghost.Graphics.Contracts;
|
||||
|
||||
public interface IRenderPass
|
||||
{
|
||||
public void Initialize(ref readonly RenderingContext ctx);
|
||||
public void Execute(ref readonly RenderingContext ctx);
|
||||
public void Cleanup(IResourceDatabase resourceDatabase);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
namespace Ghost.Graphics.Core;
|
||||
|
||||
public class Camera
|
||||
{
|
||||
}
|
||||