diff --git a/src/Godot.SourceGeneration/EntryPointGenerator.cs b/src/Godot.SourceGeneration/EntryPointGenerator.cs index ef6df70..abd2230 100644 --- a/src/Godot.SourceGeneration/EntryPointGenerator.cs +++ b/src/Godot.SourceGeneration/EntryPointGenerator.cs @@ -35,36 +35,32 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Where(spec => spec is not null) .Select((spec, ct) => spec!.Value) .Collect() - // Sort for base classes to to be registered before derived classes. + // Sort for base classes to be registered before derived classes. .Select((specs, ct) => { Dictionary specByTypeName = []; - Dictionary derivedTypeCountByBaseTypeName = []; - foreach (var spec in specs) { - specByTypeName.Add(spec.FullyQualifiedSymbolName, spec); - - if (!derivedTypeCountByBaseTypeName.TryGetValue(spec.FullyQualifiedSymbolName, out int derivedTypeCount)) - { - derivedTypeCountByBaseTypeName.Add(spec.FullyQualifiedSymbolName, 0); - derivedTypeCount++; - } + specByTypeName[spec.FullyQualifiedSymbolName] = spec; + } + // Number of ancestors (transitively) that are also registered in this assembly. + // A base type therefore has a strictly lower depth than any type deriving from it, + // so ordering by depth guarantees base classes register before derived classes. + int GetRegisteredAncestorDepth(GodotRegistrationSpec spec) + { + int depth = 0; string baseTypeName = spec.FullyQualifiedBaseSymbolName; - while (specByTypeName.TryGetValue(baseTypeName, out var baseTypeNameType)) + while (specByTypeName.TryGetValue(baseTypeName, out var baseSpec)) { - derivedTypeCountByBaseTypeName[baseTypeName] += derivedTypeCount; - baseTypeName = baseTypeNameType.FullyQualifiedBaseSymbolName; + depth++; + baseTypeName = baseSpec.FullyQualifiedBaseSymbolName; } - - derivedTypeCountByBaseTypeName.TryGetValue(baseTypeName, out int oldDerivedTypeCount); - derivedTypeCountByBaseTypeName[baseTypeName] = oldDerivedTypeCount + derivedTypeCount; + return depth; } - return specByTypeName - .OrderByDescending(kvp => derivedTypeCountByBaseTypeName[kvp.Key]) - .Select(kvp => kvp.Value) + return specs + .OrderBy(GetRegisteredAncestorDepth) .ToImmutableArray(); }); diff --git a/tests/Godot.SourceGeneration.Tests/EntryPointGeneratorTests.cs b/tests/Godot.SourceGeneration.Tests/EntryPointGeneratorTests.cs index 1c3f392..991a7e6 100644 --- a/tests/Godot.SourceGeneration.Tests/EntryPointGeneratorTests.cs +++ b/tests/Godot.SourceGeneration.Tests/EntryPointGeneratorTests.cs @@ -32,4 +32,13 @@ await Verifier.Verify( [("Main.generated.cs", "MainWithInheritance.generated.cs")] ); } + + [Fact] + public async Task BaseTypesRegisteredBeforeDerivedTypesWhenDeclaredDerivedFirst() + { + await Verifier.Verify( + ["NodesWithInheritanceReversed.cs"], + [("Main.generated.cs", "MainWithInheritanceReversed.generated.cs")] + ); + } } diff --git a/tests/Godot.SourceGeneration.Tests/TestData/GeneratedSources/MainWithInheritance.generated.cs b/tests/Godot.SourceGeneration.Tests/TestData/GeneratedSources/MainWithInheritance.generated.cs index f4e76b3..83a4637 100644 --- a/tests/Godot.SourceGeneration.Tests/TestData/GeneratedSources/MainWithInheritance.generated.cs +++ b/tests/Godot.SourceGeneration.Tests/TestData/GeneratedSources/MainWithInheritance.generated.cs @@ -35,9 +35,9 @@ internal static void InitializeUserTypes(global::Godot.Bridge.InitializationLeve return; } global::Godot.Bridge.GodotRegistry.RegisterRuntimeClass(global::NS.BaseType.BindMembers); - global::Godot.Bridge.GodotRegistry.RegisterRuntimeClass(global::NS.DerivedType3.BindMembers); global::Godot.Bridge.GodotRegistry.RegisterRuntimeClass(global::NS.DerivedType.BindMembers); global::Godot.Bridge.GodotRegistry.RegisterRuntimeClass(global::NS.DerivedType2.BindMembers); + global::Godot.Bridge.GodotRegistry.RegisterRuntimeClass(global::NS.DerivedType3.BindMembers); global::Godot.Bridge.GodotRegistry.RegisterRuntimeClass(global::NS.HighlyDerivedType.BindMembers); } internal static void DeinitializeUserTypes(global::Godot.Bridge.InitializationLevel level) diff --git a/tests/Godot.SourceGeneration.Tests/TestData/GeneratedSources/MainWithInheritanceReversed.generated.cs b/tests/Godot.SourceGeneration.Tests/TestData/GeneratedSources/MainWithInheritanceReversed.generated.cs new file mode 100644 index 0000000..e157bea --- /dev/null +++ b/tests/Godot.SourceGeneration.Tests/TestData/GeneratedSources/MainWithInheritanceReversed.generated.cs @@ -0,0 +1,48 @@ +[assembly: global::System.Runtime.CompilerServices.DisableRuntimeMarshalling] + +#nullable enable + +namespace @TestProject; + +static partial class Main +{ + internal static void InitializeTypes(global::Godot.Bridge.InitializationLevel level) + { + ClassDBExtensions.InitializeUserTypes(level); + } + internal static void DeinitializeTypes(global::Godot.Bridge.InitializationLevel level) + { + ClassDBExtensions.DeinitializeUserTypes(level); + } + [global::System.Runtime.InteropServices.UnmanagedCallersOnly(EntryPoint = "init")] + internal static bool Init(nint getProcAddress, nint library, nint initialization) + { + global::Godot.Bridge.GodotBridge.Initialize(getProcAddress, library, initialization, config => + { + config.SetMinimumLibraryInitializationLevel(global::Godot.Bridge.InitializationLevel.Scene); + config.RegisterInitializer(InitializeTypes); + config.RegisterTerminator(DeinitializeTypes); + }); + return true; + } +} +internal static class ClassDBExtensions +{ + internal static void InitializeUserTypes(global::Godot.Bridge.InitializationLevel level) + { + if (level != global::Godot.Bridge.InitializationLevel.Scene) + { + return; + } + global::Godot.Bridge.GodotRegistry.RegisterRuntimeClass(global::NS.Mob.BindMembers); + global::Godot.Bridge.GodotRegistry.RegisterRuntimeClass(global::NS.Enemy.BindMembers); + global::Godot.Bridge.GodotRegistry.RegisterRuntimeClass(global::NS.Bat.BindMembers); + } + internal static void DeinitializeUserTypes(global::Godot.Bridge.InitializationLevel level) + { + if (level != global::Godot.Bridge.InitializationLevel.Scene) + { + return; + } + } +} diff --git a/tests/Godot.SourceGeneration.Tests/TestData/Sources/NodesWithInheritanceReversed.cs b/tests/Godot.SourceGeneration.Tests/TestData/Sources/NodesWithInheritanceReversed.cs new file mode 100644 index 0000000..af8a911 --- /dev/null +++ b/tests/Godot.SourceGeneration.Tests/TestData/Sources/NodesWithInheritanceReversed.cs @@ -0,0 +1,22 @@ +using Godot; +using Godot.Bridge; + +namespace NS; + +[GodotClass] +public partial class Bat : Enemy +{ + public new static void BindMembers(ClassRegistrationContext context) { } +} + +[GodotClass] +public partial class Enemy : Mob +{ + public new static void BindMembers(ClassRegistrationContext context) { } +} + +[GodotClass] +public partial class Mob : Node +{ + public static void BindMembers(ClassRegistrationContext context) { } +}