Refactor folder structure

This commit is contained in:
2026-02-18 00:50:46 +09:00
parent 426786397c
commit db8ca971a8
413 changed files with 2885 additions and 3634 deletions

View File

@@ -0,0 +1,89 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading;
namespace Ghost.Generator
{
// TODO: this should be per assembly, not global
[Generator]
public class ComponentRegistrationGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 1. Pipeline: Find all structs implementing IComponent
var componentCandidates = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (s, _) => s is StructDeclarationSyntax,
transform: GetComponentSymbol)
.Where(symbol => symbol != null)
.Collect();
// 4. Output: Generate the source using both pieces of data at once
context.RegisterSourceOutput(componentCandidates, GenerateRegistrationCode);
}
// Extraction Logic for Components
private static INamedTypeSymbol GetComponentSymbol(GeneratorSyntaxContext ctx, CancellationToken _)
{
var structSyntax = (StructDeclarationSyntax)ctx.Node;
if (!(ctx.SemanticModel.GetDeclaredSymbol(structSyntax) is INamedTypeSymbol symbol))
{
return null;
}
var iComponentSymbol = ctx.SemanticModel.Compilation.GetTypeByMetadataName("Ghost.Entities.IComponent");
if (iComponentSymbol == null)
{
return null;
}
foreach (var iface in symbol.AllInterfaces)
{
if (SymbolEqualityComparer.Default.Equals(iface, iComponentSymbol))
{
return symbol;
}
}
return null;
}
// The Generation Logic (Stateless)
private static void GenerateRegistrationCode(SourceProductionContext context, ImmutableArray<INamedTypeSymbol> components)
{
if (components.IsDefaultOrEmpty)
{
return;
}
var name = $"g_component_registration";
var sb = new StringBuilder();
sb.Append($@"
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
internal static class {name}
{{
[global::System.Runtime.CompilerServices.ModuleInitializer]
public static void RegisterIComponentTypes()
{{");
foreach (var symbol in components.Distinct(SymbolEqualityComparer.Default))
{
if (symbol is null) continue;
sb.Append($@"
global::Ghost.Entities.ComponentRegistry.GetOrRegisterComponentID<{symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>();");
}
sb.Append(@"
}
}");
context.AddSource($"{name}.gen.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
}
}
}

View File

@@ -0,0 +1,229 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
namespace Ghost.Generator
{
[Generator]
public class ComponentSerializationGenerator : IIncrementalGenerator
{
private string GetJsonWriteCall(ITypeSymbol type, string fieldName)
{
switch (type.SpecialType)
{
case SpecialType.System_Byte:
case SpecialType.System_SByte:
case SpecialType.System_Int16:
case SpecialType.System_Int32:
case SpecialType.System_Int64:
case SpecialType.System_Single:
case SpecialType.System_Double:
case SpecialType.System_Decimal:
case SpecialType.System_UInt64:
case SpecialType.System_UInt16:
case SpecialType.System_UInt32:
case SpecialType.System_IntPtr:
case SpecialType.System_UIntPtr:
return $@"writer.WriteNumber(""{fieldName}"", value.{fieldName});";
case SpecialType.System_Boolean:
return $@"writer.WriteBoolean(""{fieldName}"", value.{fieldName});";
case SpecialType.System_Char:
return $@"writer.WriteString(""{fieldName}"", [value.{fieldName}]);";
case SpecialType.System_String:
return $@"writer.WriteString(""{fieldName}"", value.{fieldName});";
}
return $@"writer.WritePropertyName(""{fieldName}""); global::System.Text.Json.JsonSerializer.Serialize(writer, value.{fieldName}, options);";
}
private string GetJsonReadCall(ITypeSymbol type)
{
switch (type.SpecialType)
{
case SpecialType.System_Byte: return "reader.GetByte()";
case SpecialType.System_SByte: return "reader.GetSByte()";
case SpecialType.System_Int16: return "reader.GetInt16()";
case SpecialType.System_Int32: return "reader.GetInt32()";
case SpecialType.System_Int64: return "reader.GetInt64()";
case SpecialType.System_Single: return "reader.GetSingle()";
case SpecialType.System_Double: return "reader.GetDouble()";
case SpecialType.System_Decimal: return "reader.GetDecimal()";
case SpecialType.System_UInt16: return "reader.GetUInt16()";
case SpecialType.System_UInt32: return "reader.GetUInt32()";
case SpecialType.System_UInt64: return "reader.GetUInt64()";
// Note: the size of IntPtr and UIntPtr varies by platform, we use Int64/UInt64 to ensure compatibility
case SpecialType.System_IntPtr: return "reader.GetInt64()";
case SpecialType.System_UIntPtr: return "reader.GetUInt64()";
case SpecialType.System_Boolean: return "reader.GetBoolean()";
case SpecialType.System_Char: return "reader.GetString()[0]";
case SpecialType.System_String: return "reader.GetString()";
}
return $"global::System.Text.Json.JsonSerializer.Deserialize<{type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>(ref reader, options)";
}
private void GenerateJsonSerializer(SourceProductionContext context, ImmutableArray<INamedTypeSymbol> symbols)
{
var sbWrites = new StringBuilder();
var sbReads = new StringBuilder();
foreach (var symbol in symbols)
{
var namespaceName = symbol.ContainingNamespace.ToDisplayString();
var structName = symbol.Name;
var fullTypeName = symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
// 1. Build Field Logic (Same as before)
sbWrites.Clear();
sbReads.Clear();
var fields = symbol.GetMembers()
.OfType<IFieldSymbol>()
.Where(f => !f.IsStatic && f.DeclaredAccessibility == Accessibility.Public);
foreach (var field in fields)
{
// Note: GetJsonWriteCall returns a string ending with ";"
var writeCall = GetJsonWriteCall(field.Type, field.Name);
if (writeCall != null)
{
sbWrites.Append(" "); // Indentation
sbWrites.AppendLine(writeCall);
}
var readCall = GetJsonReadCall(field.Type);
if (readCall != null)
{
// Note the double quotes ""{field.Name}"" for the case string
sbReads.Append(" "); // Indentation
sbReads.AppendLine($@"case ""{field.Name}"": result.{field.Name} = {readCall}; break;");
}
}
// 2. The Main Template using $@
// Watch the double braces {{ }} and double quotes "" ""
var sourceCode = $@"// <auto-generated/>
#nullable enable
namespace {namespaceName}
{{
public unsafe static class {structName}_Serializer
{{
[global::System.Runtime.CompilerServices.ModuleInitializer]
internal static void Init()
{{
var id = Ghost.Entities.ComponentTypeID<{fullTypeName}>.Value;
Ghost.Engine.IO.ComponentSerializerRegistry.Register(id, SerializeBinaryUnsafe, SerializeJsonUnsafe);
}}
// ---------------------------------------------------------
// BINARY (Fast Path)
// ---------------------------------------------------------
[global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static unsafe void SerializeBinaryUnsafe(global::System.IO.BinaryWriter writer, void* value)
{{
writer.Write(new global::System.ReadOnlySpan<byte>(value, sizeof({fullTypeName})));
}}
[global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static void SerializeBinary(this global::System.IO.BinaryWriter writer, ref {fullTypeName} value)
{{
unsafe {{ writer.Write(new global::System.ReadOnlySpan<byte>(global::System.Runtime.CompilerServices.Unsafe.AsPointer(ref value), sizeof({fullTypeName}))); }}
}}
[global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public static void DeserializeBinary(this global::System.IO.BinaryReader reader, ref {fullTypeName} value)
{{
unsafe {{ reader.Read(new global::System.Span<byte>(global::System.Runtime.CompilerServices.Unsafe.AsPointer(ref value), sizeof({fullTypeName}))); }}
}}
// ---------------------------------------------------------
// JSON WRITE
// ---------------------------------------------------------
public static unsafe void SerializeJsonUnsafe(System.Text.Json.Utf8JsonWriter writer, void* ptr, System.Text.Json.JsonSerializerOptions? options)
{{
SerializeJson(writer, ref *( {fullTypeName}*)ptr, options);
}}
public static void SerializeJson(this global::System.Text.Json.Utf8JsonWriter writer, ref {fullTypeName} value, global::System.Text.Json.JsonSerializerOptions? options)
{{
writer.WriteStartObject();
{sbWrites}
writer.WriteEndObject();
}}
// ---------------------------------------------------------
// JSON READ
// ---------------------------------------------------------
public static {fullTypeName} DeserializeJson(ref global::System.Text.Json.Utf8JsonReader reader, global::System.Text.Json.JsonSerializerOptions? options)
{{
var result = default({fullTypeName});
if (reader.TokenType != global::System.Text.Json.JsonTokenType.StartObject) throw new global::System.Text.Json.JsonException();
while (reader.Read())
{{
if (reader.TokenType == global::System.Text.Json.JsonTokenType.EndObject) return result;
if (reader.TokenType != global::System.Text.Json.JsonTokenType.PropertyName) throw new global::System.Text.Json.JsonException();
var propName = reader.GetString();
reader.Read();
switch (propName)
{{
{sbReads}
default: reader.Skip(); break;
}}
}}
return result;
}}
}}
}}";
context.AddSource($"{structName}.Serializer.gen.cs", sourceCode);
}
}
public void Initialize(IncrementalGeneratorInitializationContext context)
{
return;
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 iComponentSymbol = compilation.GetTypeByMetadataName("Ghost.Entities.IComponent");
if (iComponentSymbol == null)
{
return null;
}
foreach (var iface in symbol.AllInterfaces)
{
if (SymbolEqualityComparer.Default.Equals(iface, iComponentSymbol))
{
return symbol;
}
}
return null;
})
.Where(symbol => symbol != null)
.Collect();
context.RegisterSourceOutput(componentCandidates, GenerateJsonSerializer);
}
}
}

View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Shader\**" />
<EmbeddedResource Remove="Shader\**" />
<None Remove="Shader\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
</ItemGroup>
</Project>