59 Commits

Author SHA1 Message Date
9bae3e647e docs(README): rewrite and expand documentation
Major rewrite of the README for Ghost.NativeWrapperGen:
- Clarifies project purpose, design, and intended usage.
- Adds concrete code examples of generated wrappers.
- Updates CLI usage and provides validated command lines.
- Replaces old config schema docs with detailed tables for all config fields, including new `remaps` and `actions`.
- Documents the new config-driven routing and remapping system.
- Explains the action-based method routing with conditions and apply steps.
- Details naming conventions and method name derivation.
- Provides a full, modern config example for nvtt.
- Updates recommended workflow for wrapper regeneration and build.
- Emphasizes the design principle of keeping policy in config.
- Removes outdated sections and limitations.
- Improves formatting and accessibility for new users.
2026-03-15 21:15:52 +09:00
6cadd8edeb feat(nativegen)!: refactor to struct-based native wrappers
Major overhaul of native wrapper generation for ufbx and nvtt.
Replaces all hand-written and class-based wrappers with auto-generated partial struct wrappers that directly expose native API methods via pointers. Introduces a new JSON-driven configuration system using "remaps" and "actions" for flexible parameter/return mapping and method routing. Removes legacy config sections and helper classes, focusing solely on method wrappers. Updates all usages and tests to use the new pointer-based API. Cleans up obsolete code and ensures resource management is handled via struct Dispose methods. The result is a thinner, more direct, and maintainable interop layer.

BREAKING CHANGE: All managed wrapper classes and helpers are removed in favor of struct-based pointer wrappers. API usage and resource management patterns have changed.
2026-03-15 20:48:54 +09:00
3e4084c42a Added ufbx warper 2026-03-15 02:19:40 +09:00
cce1cf7256 Added Ufbx 2026-03-14 18:29:18 +09:00
254b08bc81 Added doc folder 2026-03-14 12:33:12 +09:00
912b320d8f Fixed compilation errors;
Added MaterialPalette
2026-03-14 12:27:49 +09:00
8a3b40b4f8 Refactor MeshInstance 2026-03-13 15:10:25 +09:00
619720feee Render extraction system & ECS/graphics refactor
Introduced RenderExtractionSystem for entity-based render data extraction. Added MeshInstance and MeshPalette components with shadow casting support. Refactored QueryBuilder API, SharedComponentStore, and component registration for clarity and flexibility. Updated SystemManager and SystemGroup to use SystemAPI. Replaced RenderingConfig with GraphicsEngineDesc/RenderSystemDesc. RenderFrame now uses CPU/GPU fence values for sync. Removed Camera.cs in favor of ECS-based rendering. Improved Material, RenderingLayerMask, Mesh, and RenderList APIs. Updated package references and fixed naming, error handling, and disposal issues.
2026-03-08 22:51:03 +09:00
bfe8588d76 Refactor render pipeline and resource management APIs
Split IFenceSynchronizer/IRenderSystem interfaces for clarity. Refactor D3D12GraphicsEngine to use IFenceSynchronizer. Update RenderGraph and context to use explicit resource manager/database/allocator references. Add multi-buffering methods to IRGBuilder (stub). Support history access for multi-frame resources. Remove legacy RenderPipelineBase; introduce IRenderPipelineSettings and sealed GhostRenderPipeline. Clean up resource aliasing and pool logic. Improve modularity and future extensibility.
2026-03-03 20:14:22 +09:00
b8af6e8c3a Added RenderPipelineBase 2026-03-02 19:06:19 +09:00
5e42d699c3 Added RenderList and RenderReques.
Added IRenderPipeline.
2026-02-28 21:31:38 +09:00
6f802ac12b Added CopyTexture support in ICommandBuffer 2026-02-26 21:40:07 +09:00
162b71f309 Refactor render graph error handling and resource APIs
- RenderGraph.Compile/Execute now return Error for better failure detection; error handling is propagated throughout compiler and executor.
- Renamed ScheduleReleaseResource to ReleaseResource for clarity; updated all usages.
- ResourceManager now calls ReleaseResource directly on Mesh, Material, and Shader types.
- Camera exposes Actual/Virtual size properties and Render returns Error.
- RenderingContext now uses IResourceManager for mesh/resource ops.
- Replaced custom BinaryWriter with BufferWriter in RenderGraphHasher.
- Improved variable naming, interface signatures, and code formatting.
- Added Error extension for IsSuccess/IsFailure.
- Minor FMOD/native interop and test code cleanups.
- No breaking API changes except for new Error return values on some methods.
2026-02-25 19:08:54 +09:00
30090f84ab Refactor rendering projects 2026-02-24 20:08:26 +09:00
93c58fa7fb Add Ghost.Nvtt C# wrapper and integrate nvtt texture pipeline
- Introduce full managed C# wrapper for NVIDIA Texture Tools (nvtt) with safe handle classes, idiomatic APIs, and managed callback support.
- Integrate Ghost.Nvtt into Ghost.Editor.Core and Ghost.MicroTest; update TextureAssetHandler to use the new nvtt wrapper for texture compression.
- Add comprehensive end-to-end binding test (NvttBindingTest).
- Refactor D3D12 resource management: add deferred/immediate release APIs, update allocator/database usage, and ensure proper resource cleanup.
- Update project files for new native DLL layout and dependency versions.
- Minor API cleanups: EditorApplication properties, D3D12 input layout, and removal of obsolete code.
- Update shaders, tests, and documentation for new APIs and usage patterns.
2026-02-23 17:13:10 +09:00
78e3b4ef31 Merge branch 'develop' of https://github.com/misakieku/GhostEngine into develop 2026-02-18 00:52:23 +09:00
db8ca971a8 Refactor folder structure 2026-02-18 00:52:18 +09:00
638417d4f0 Refactor folder structure 2026-02-18 00:50:46 +09:00
426786397c Modify AssetService 2026-02-05 19:25:48 +09:00
9bbccfc8f8 Update ContextFlyout 2026-02-05 13:52:53 +09:00
eadd13931f Updating ProjectBrowser 2026-02-04 19:08:18 +09:00
59991f47d5 Update editor 2026-02-03 21:49:14 +09:00
9fcf06dbe4 Update icon assets 2026-02-01 01:54:04 +09:00
6505099667 Fixed the issue where the test does not cleanup the temp folder 2026-02-01 01:53:19 +09:00
d263f0c7e1 Imporving AssetDatabase 2026-01-30 21:20:18 +09:00
9f05944d81 Improve AssetDatabase performance. 2026-01-29 20:37:45 +09:00
e71851550b Update asset database 2026-01-29 14:03:24 +09:00
8a5795069f Update AssetDatabase 2026-01-27 14:39:00 +09:00
b505c7c1c0 backup 2026-01-26 22:55:14 +09:00
8d82c0a750 Update plan 2026-01-26 15:58:19 +09:00
8df0b46960 Update scene graph 2026-01-26 13:59:33 +09:00
06a150b899 Fix error 2026-01-26 11:47:00 +09:00
49f54c6b43 Add scene graph draft 2026-01-25 22:06:58 +09:00
fdf831630b feat: implement complete scene graph system with hierarchical editor support
- Add SceneNode and EntityNode classes for editor-only metadata storage
- Implement SceneGraph view-model with O(1) entity lookup via internal caching
- Create IdRemapTable for file-local to global entity ID remapping on load
- Implement SceneSerializationContext for load/save operation tracking
- Add JSON-serializable SceneAssetData, EntityData, and ComponentData models
- Implement SceneSerializer for save/load with validation and reference remapping
- Add comprehensive documentation: README.md, IMPLEMENTATION_GUIDE.md, SYSTEM_SUMMARY.md
- Update Ghost.Editor.Core.csproj to reference Ghost.Entities assembly
- Support parent-child relationships via Hierarchy component
- Enforce no cross-scene entity references
- Keep runtime minimal: only SceneID, Hierarchy, LocalToWorld components
- All editor metadata (names, UI state) stored in editor-only SceneNode/EntityNode classes

This implements the architecture from SceneGraph Plan.md with clean separation of concerns,
minimal runtime footprint, and AOT compatibility.
2026-01-25 21:42:03 +09:00
ba5dc2159e Remove old SceneGraph 2026-01-25 21:18:16 +09:00
0201f0fc33 Add simple scene graph 2026-01-25 18:37:45 +09:00
364fbf9208 Refactor error handling: use Error enum, update APIs
Replaces ErrorStatus with Error across all systems for consistency.
Renames ResourceBarrierData fields to camelCase.
Adds BindlessAccess enum and updates GetBindlessIndex API.
Updates method signatures, result types, and error checks.
Modernizes HLSL mesh shader syntax and fixes naming.
Improves code style and updates comments for clarity.
2026-01-25 16:34:28 +09:00
e11a9ebb52 Refactor render graph: modular compilation & execution
Major overhaul of render graph system for modularity and performance:
- Split compilation and execution logic into dedicated classes (Compiler, Executor, NativePassBuilder, Barriers)
- Overhauled barrier system: now uses CompiledBarrier with target state only, querying before state at execution
- Resource size/alignment now queried from D3D12 device for accurate heap allocation
- ResourceDesc now includes Type field and asserts correct union access
- Centralized D3D12 interop logic in D3D12Utility extensions
- Added RenderGraphHasher for structural graph hashing and cache invalidation
- RenderGraph class simplified to orchestrate specialized components
- ResourceAliasingManager now uses allocator for size queries
- Compilation cache now stores compiled barriers, reducing memory usage
- Improved comments, debug assertions, and removed redundant code

Result: more maintainable, efficient, and robust render graph pipeline.
2026-01-23 18:12:52 +09:00
4173ff2432 Refactor RenderGraph barrier/state tracking system
Major overhaul of resource barrier and state tracking in RenderGraph:
- Introduce ResourceBarrierData for explicit (layout, access, sync) tracking.
- Separate aliasing and transition barriers; explicit aliasing support.
- Remove BufferHint; infer buffer usage from BufferUsage flags.
- Update TextureAccess/BufferAccess to include usage requirements.
- Improve enums (BarrierSync, BarrierAccess, BarrierLayout) for D3D12 alignment.
- Update D3D12CommandBuffer to use new barrier data and error handling.
- Make D3D12DescriptorHeap a class; add ReleaseSampler to IResourceDatabase.
- Reset resource pools and aliasing managers each frame.
- Batch and flush barriers efficiently per pass.
- Update HLSL mesh shader macros to [NumThreads].
- Remove obsolete code and improve documentation.
This refactor improves correctness, extensibility, and prepares for advanced features.
2026-01-22 20:51:58 +09:00
139312d73b Enhanced barrier 2026-01-22 12:33:23 +09:00
92b966fe0d Render graph integration and resource management refactor
Introduces a full-featured render graph system with pass culling, resource aliasing, and automatic barrier generation. Refactors resource and barrier APIs, improves error handling, and unifies result types. Renderer and render passes now use the new graph-based workflow. Updates shader includes, adds a blit shader, and improves HLSL parsing. Removes dynamic descriptor heaps in favor of persistent ones. Project file now includes the render graph module. Lays the foundation for advanced rendering features and improved memory efficiency.
2026-01-21 18:32:03 +09:00
1c155f962c Render graph: native pass merging & heap-based aliasing
Major architecture upgrade:
- Add native render pass merging (hardware pass grouping, load/store op inference)
- Implement heap-based aliasing for textures & buffers (D3D12-style)
- Unify resource model: buffers and textures in one registry
- Extend builder API for buffer creation/usage, access flags, hints
- Improve barrier/state tracking (buffer hints, indirect argument state)
- Update caching, hashing, and debug output for new model
- Add enums/structs: AttachmentLoadOp, StoreOp, BufferHint, etc.
- D3D12 backend: support named resources, temp upload buffers, correct heap usage
- Update docs, benchmarks, and project files for new features

Brings render graph closer to AAA engine standards, enabling efficient memory usage, lower driver overhead, and a more flexible API.
2026-01-16 01:59:33 +09:00
ac36bbf8c7 small backup 2026-01-13 14:50:55 +09:00
02df8d7732 Update render graph 2026-01-13 13:46:50 +09:00
954e3756aa Refactor Render Graph: unified resources, benchmarking
Major overhaul of Render Graph system:
- Replaced texture handles with generic Identifier<T> for unified, type-safe resource management (textures, buffers, etc.)
- Refactored resource registry and pooling for performance and extensibility
- Added AccessFlags and TextureAccess for precise resource usage tracking
- Split passes into Raster and Compute types; introduced builder interfaces for safer pass construction
- Modernized pass setup API (SetColorAttachment, UseTexture, etc.)
- Updated command buffer and context structs to use new resource system
- Refactored barrier and aliasing logic for improved correctness
- Integrated BenchmarkDotNet for performance/memory benchmarking
- Improved blackboard type safety and removed obsolete code/extensions
- Added BenchmarkDotNet NuGet package

These changes make the Render Graph more extensible, efficient, and ready for future resource types and advanced features.
2026-01-12 23:48:56 +09:00
1fc9df1812 GhostEngine Render Graph: major refactor & Unity RG ref
- Major architectural refactor for performance, extensibility, and feature completeness: resource pooling, pass culling, aliasing, and compilation caching.
- Introduces type-safe builder and context APIs, blackboard pattern, and unified resource management.
- Adds detailed documentation and cleans up obsolete files and APIs.
- Includes (commented) Unity Render Graph source for reference; not compiled, for parity and future extension.
2026-01-11 23:43:17 +09:00
87e315a588 Refactor render graph & DSL; remove material system
- Major optimization of Ghost.RenderGraph.Concept: pooled resources, zero-allocation hot paths, explicit queue types, and batch barrier APIs.
- Migrated Ghost.DSL shader compiler to ANTLR4-based parser; removed hand-written parser, added grammar files and semantic model conversion.
- Added CollectionPool/ListPool for pooled list management.
- Updated documentation for new architecture and performance.
- Removed Ghost.Shader.Concept (material/material system) from repo and solution.
- README.md replaced with a brief project statement.
2026-01-11 13:28:17 +09:00
d71bdb3fc9 Refactor shader system: arrays, keywords, property syntax
Major refactor of shader compiler and related systems:
- Switch ShaderDescriptor/PassDescriptor to arrays; remove IPassDescriptor
- Rewrite keywords block parser/semantic analysis for flexible syntax
- Change property initializers to brace syntax `{ ... }`
- Simplify TokenStream API (remove ref index params)
- Make GetBindlessIndex return uint (~0u for not found)
- Update shader compilation and variant logic for new descriptors
- Update test shader syntax to match new property/keyword formats
- Add AGENTS.md agent development guide
- Add Antlr4 dependency to Ghost.DSL
- Miscellaneous code style and error handling improvements
2026-01-10 18:36:18 +09:00
6a041f75ba Refactor: variant-aware shader/material pipeline overhaul
Major architectural update to graphics/material/shader system:
- Introduced strongly-typed key structs (Key64/Key128) for passes, variants, and pipelines; removed legacy key types.
- Implemented robust hashing and key generation utilities for efficient variant and pipeline lookup/caching.
- Shader compiler now compiles/caches all keyword variants using new key system; includes handled as lists.
- Switched to push constant root signature for per-draw data; updated HLSL and C# codegen accordingly.
- Refactored Material, Shader, and Pass data structures for cache efficiency and variant support.
- Pipeline library and PSO management now use 128-bit keys and variant-specific caching.
- Replaced WorldNode with SceneNode in editor/scene graph; introduced ComponentManager for archetype/query management.
- Migrated math utilities to Misaki.HighPerformance.Mathematics; updated editor controls.
- Updated all HLSL and codegen for new buffer/push constant layouts and macros.
- Misc: project reference cleanup, D3D12 Work Graph support, doc updates, and code modernization.
2026-01-09 22:25:37 +09:00
c9be05fc60 Added additional config to CompilePass in IShaderCompiler 2025-12-27 15:14:06 +09:00
f988c34b3d Add high-performance material/shader system (Ghost.Shader.Concept)
Introduces a new Ghost.Shader.Concept project implementing a modern, data-oriented material and shader system with:
- Global/local keyword bitsets (fast O(1) ops, 64 bytes)
- Multi-pass shader program and per-pass render state overrides
- Thread-safe, 16-byte aligned material property blocks
- Material pooling to reduce GC pressure
- Batch renderer for efficient PSO grouping and async variant warmup
- Full demo (Program.cs) and extensive documentation (ARCHITECTURE.md, README.md, PROJECT_SUMMARY.md)
- Minor integration: new enums, doc updates, and keyword handling in existing code

No breaking changes to the existing engine; all new code is isolated. This serves as a reference implementation for high-performance, extensible material/shader architectures.
2025-12-26 19:19:30 +09:00
a89719bfc9 Refactor pipeline state and render output abstractions
- Replace old pipeline enums/structs with new strongly-typed PipelineState and enums (ZTest, ZWrite, Cull, Blend, ColorWriteMask)
- Redesign pipeline keying: introduce 128-bit GraphicsPipelineKey, MaterialPipelineKey, and PassPipelineKey for robust PSO caching
- Replace IRenderTargetStrategy with IRenderOutput; add SwapChainRenderOutput and TextureRenderOutput
- Update renderer and window code to use new render output abstraction and handle viewport/scissor updates
- Make ShaderPass a readonly struct and Shader a struct; use ID-based pass lookup for efficiency
- Materials now support per-pass pipeline overrides with new keying
- Add defensive checks in D3D12CommandBuffer; update D3D12PipelineLibrary for new keying/state
- Move test shader to test.gsdef and update for new pipeline state syntax
- Remove obsolete files/interfaces and perform general code cleanups
- Update all usages and parsing logic for new pipeline state system
2025-12-24 19:06:34 +09:00
b8ce824292 Updated Handle and Identifier that default is invalid 2025-12-23 14:22:44 +09:00
aa3d9c749b Refactor: add command allocator & render target strategies
Major refactor of graphics infrastructure:
- Introduce ICommandAllocator and D3D12CommandAllocator for explicit command buffer management.
- Change ICommandBuffer.Begin to require an allocator.
- Add IRenderTargetStrategy abstraction with swap chain and texture implementations.
- Update IRenderer to use RenderTargetStrategy instead of direct handle.
- Add DPI scaling support to swap chains (ScaleX/ScaleY, SetScale).
- RenderSystem now supports thread-safe swap chain resize requests.
- Remove persistent copy command buffer; use per-frame allocators.
- Make Logger public/static and clean up API visibility.
- Update .editorconfig and debug layer enablement.
These changes improve modularity, DPI-awareness, and future extensibility.
2025-12-23 00:35:34 +09:00
d23e701f0a refactor IRenderer 2025-12-22 15:59:02 +09:00
2881fda112 Refactor component registration, update deps, improve JSON
- Updated Misaki.HighPerformance package versions in Core and Graphics projects.
- Added IsTrimmable to Ghost.Engine.csproj for trimming support.
- Renamed GetOrRegisterComponent to GetOrRegisterComponentID and updated all usages.
- Component registration codegen now uses a static class with [ModuleInitializer], no longer requires [EngineEntry].
- Improved JSON serialization: added string support, introduced Utf8JsonObjectScope/ArrayScope, and new extension methods for cleaner JSON writing.
- Removed [SkipLocalsInit] from Hierarchy and LocalToWorld.
- Fixed Entity.Invalid to use INVALID_ID for both fields.
- Minor cleanup: clarified comments, reorganized Ghost.Generator in solution, and disabled component serialization generator.
2025-12-21 22:18:25 +09:00
840cf7dd5a Add entities SerializationTest 2025-12-21 13:07:59 +09:00
00b4e82ded ECS refactor: new ComponentSet, serialization, generators
Major ECS API overhaul: added ComponentSet, refactored ComponentRegistry, and updated all entity/component creation methods. Introduced robust custom serialization infrastructure and per-component source generators for registration and (de)serialization. Updated editor, engine, and test code to use new APIs. Improved code quality, naming, and performance throughout. Removed obsolete code and updated dependencies.
2025-12-20 20:41:40 +09:00
3118021272 Merge pull request 'feature/archetype-ecs' (#1) from feature/archetype-ecs into develop
Reviewed-on: Misaki/GhostEngine#1
2025-12-17 08:27:31 +00:00
925 changed files with 41506 additions and 15057 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.dll filter=lfs diff=lfs merge=lfs -text

1
.gitignore vendored
View File

@@ -9,6 +9,7 @@
*.user
*.userosscache
*.sln.docstates
AGENTS.md
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

View File

@@ -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
}

View File

@@ -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,
};
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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}");
}
}
}
}

View File

@@ -1,6 +0,0 @@
namespace Ghost.Data.Repository;
internal class AssetsRepository
{
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -1,8 +0,0 @@
namespace Ghost.Editor.Core.AppState;
internal enum StateKey
{
None,
Landing,
EngineEditor,
}

View File

@@ -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);
}
}
}

View File

@@ -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
});
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -1,16 +0,0 @@
namespace Ghost.Editor.Core.AssetHandle;
internal class AssetMeta
{
public Guid Guid
{
get;
internal set;
}
public ImporterSettings? Settings
{
get;
set;
}
}

View File

@@ -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();
}
}

View File

@@ -1,5 +0,0 @@
namespace Ghost.Editor.Core.AssetHandle;
public abstract class ImporterSettings
{
}

View File

@@ -1,7 +0,0 @@
namespace Ghost.Editor.Core.Contracts;
public interface INavigationAware
{
public void OnNavigatedTo(object? parameter);
public void OnNavigatedFrom();
}

View File

@@ -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" />

View File

@@ -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);
}
}

View File

@@ -1,10 +0,0 @@
namespace Ghost.Editor.Core.Inspector;
[AttributeUsage(AttributeTargets.Class)]
public class CustomEditorAttribute(Type targetType) : Attribute
{
internal Type TargetType
{
get;
} = targetType;
}

View File

@@ -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;
}
}

View File

@@ -1,12 +0,0 @@
namespace Ghost.Editor.Core.Inspector;
internal interface IInspectorService
{
public IInspectable? SelectedInspectable
{
get;
set;
}
public event Action? OnSelectionChanged;
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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";
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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];
}
}

View File

@@ -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;
}

View File

@@ -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);
});
});
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 831 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 433 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 583 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 831 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -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()
{
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
});
}
}
}

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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));
}
}

View File

@@ -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>();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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="&#xE8DA;" />
</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>

View File

@@ -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();
}
}

View File

@@ -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="&#xE712;" />
</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>

View File

@@ -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);
}
}
}

View File

@@ -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="&#xE838;" />
</SelectorBarItem.Icon>
</SelectorBarItem>
<SelectorBarItem Text="Create">
<SelectorBarItem.Icon>
<FontIcon Glyph="&#xE8F4;" />
</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>

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -1,6 +0,0 @@
using Ghost.Core.Attributes;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Ghost.Editor")]
[assembly: EngineAssembly]

View File

@@ -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)
};
}
}

View File

@@ -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();
}
}

View File

@@ -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}";
}
}

View File

@@ -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;
}
}

View File

@@ -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));
//}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -1,7 +0,0 @@
using Ghost.Entities.Test;
using Ghost.Test.Core;
using Misaki.HighPerformance.LowLevel.Buffer;
AllocationManager.EnableDebugLayer();
TestRunner.Run<SystemTest>();
AllocationManager.Dispose();

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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]

View File

@@ -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);
}

View File

@@ -1,5 +0,0 @@
namespace Ghost.Graphics.Core;
public class Camera
{
}

Some files were not shown because too many files have changed in this diff Show More