refactor: Remove ForEach discovery, cache MethodInfo

Remove ForEach type argument collection from source generator.
Cache MethodInfo for RemoveComponentImpl in World.cs.
Update API docs for IRelationship and ChangeKind.
This commit is contained in:
hyper 2026-07-22 16:14:58 +08:00
parent 8a32588c54
commit 53fa3b742d
3 changed files with 15 additions and 86 deletions

View File

@ -53,10 +53,7 @@ public class ComponentDiscoveryGenerator : IIncrementalGenerator
"RemoveSingleton",
};
private static readonly HashSet<string> _forEachNames = new()
{
"ForEach",
};
public void Initialize(IncrementalGeneratorInitializationContext context)
{
@ -68,15 +65,7 @@ public class ComponentDiscoveryGenerator : IIncrementalGenerator
.Where(t => t.FullyQualifiedName != null)
.Select((t, _) => (t.FullyQualifiedName!, t.AssemblyQualifiedName!, t.IsRelationship, t.IsSingleton));
// Also collect ForEach type arguments from the World class.
var forEachCalls = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: IsForEachCandidate,
transform: ExtractForEachTypes)
.Where(list => list.Length > 0)
.SelectMany((list, _) => list);
var allTypes = invocations.Collect().Combine(forEachCalls.Collect());
var allTypes = invocations.Collect();
context.RegisterSourceOutput(allTypes, GenerateRegistry);
}
@ -97,21 +86,7 @@ public class ComponentDiscoveryGenerator : IIncrementalGenerator
return false;
}
private static bool IsForEachCandidate(SyntaxNode node, CancellationToken _)
{
if (node is not InvocationExpressionSyntax invocation)
return false;
if (invocation.Expression is MemberAccessExpressionSyntax memberAccess)
{
var name = memberAccess.Name is GenericNameSyntax gn
? gn.Identifier.Text
: memberAccess.Name.Identifier.Text;
return _forEachNames.Contains(name);
}
return false;
}
private static (string? FullyQualifiedName, string? AssemblyQualifiedName, bool IsRelationship, bool IsSingleton) ExtractComponentType(
GeneratorSyntaxContext ctx, CancellationToken _)
@ -154,44 +129,11 @@ public class ComponentDiscoveryGenerator : IIncrementalGenerator
return (fqn, aqn, isRelationship, isSingleton);
}
private static ImmutableArray<(string FullyQualifiedName, string AssemblyQualifiedName, bool IsRelationship)> ExtractForEachTypes(
GeneratorSyntaxContext ctx, CancellationToken _)
{
if (ctx.Node is not InvocationExpressionSyntax invocation)
return ImmutableArray<(string, string, bool)>.Empty;
if (invocation.Expression is not MemberAccessExpressionSyntax memberAccess)
return ImmutableArray<(string, string, bool)>.Empty;
if (memberAccess.Name is not GenericNameSyntax genericName)
return ImmutableArray<(string, string, bool)>.Empty;
var types = ImmutableArray.CreateBuilder<(string, string, bool)>();
foreach (var typeArg in genericName.TypeArgumentList.Arguments)
{
var symbolInfo = ctx.SemanticModel.GetSymbolInfo(typeArg);
if (symbolInfo.Symbol is INamedTypeSymbol typeSymbol &&
typeSymbol.IsValueType && !typeSymbol.IsAbstract &&
typeSymbol.DeclaredAccessibility == Accessibility.Public)
{
var fqn = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var aqn = typeSymbol.ContainingAssembly != null
? $"{typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted))}, {typeSymbol.ContainingAssembly.Name}"
: fqn;
bool isRelationship = typeSymbol.AllInterfaces.Any(i =>
i.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::OECS.IRelationship");
types.Add((fqn, aqn, isRelationship));
}
}
return types.ToImmutable();
}
private static void GenerateRegistry(SourceProductionContext context,
(ImmutableArray<(string FullyQualifiedName, string AssemblyQualifiedName, bool IsRelationship, bool IsSingleton)> Left,
ImmutableArray<(string FullyQualifiedName, string AssemblyQualifiedName, bool IsRelationship)> Right) data)
ImmutableArray<(string FullyQualifiedName, string AssemblyQualifiedName, bool IsRelationship, bool IsSingleton)> invocations)
{
var (invocations, forEachTypes) = data;
// Collect unique types by fully-qualified name, deduplicating.
var typeMap = new Dictionary<string, (string Fqn, string Aqn, bool IsRel, bool IsSingleton)>();
@ -202,16 +144,6 @@ public class ComponentDiscoveryGenerator : IIncrementalGenerator
else
typeMap[t.FullyQualifiedName] = (t.FullyQualifiedName, t.AssemblyQualifiedName, t.IsRelationship, t.IsSingleton);
}
foreach (var t in forEachTypes)
{
if (!typeMap.ContainsKey(t.FullyQualifiedName))
typeMap[t.FullyQualifiedName] = (t.FullyQualifiedName, t.AssemblyQualifiedName, t.IsRelationship, false);
else if (t.IsRelationship)
{
var existing = typeMap[t.FullyQualifiedName];
typeMap[t.FullyQualifiedName] = (existing.Fqn, existing.Aqn, true, existing.IsSingleton);
}
}
if (typeMap.Count == 0)
return;

View File

@ -194,6 +194,11 @@ public class World : IDisposable
nameof(AddComponentImpl),
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;
private static readonly System.Reflection.MethodInfo s_removeComponentImpl =
typeof(World).GetMethod(
nameof(RemoveComponentImpl),
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;
private void AddComponentImpl<T>(Entity entity, T component) where T : struct
{
ThrowIfNotAlive(entity);
@ -589,20 +594,13 @@ public class World : IDisposable
switch (m.Kind)
{
case PendingMutationKind.AddComponent:
// Use reflection to call AddComponentImpl<T>.
var addMethod = typeof(World).GetMethod(
nameof(AddComponentImpl),
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;
var genericAdd = addMethod.MakeGenericMethod(m.ComponentType);
genericAdd.Invoke(this, [m.Entity, m.Component]);
s_addComponentImpl.MakeGenericMethod(m.ComponentType)
.Invoke(this, [m.Entity, m.Component]);
break;
case PendingMutationKind.RemoveComponent:
var removeMethod = typeof(World).GetMethod(
nameof(RemoveComponentImpl),
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;
var genericRemove = removeMethod.MakeGenericMethod(m.ComponentType);
genericRemove.Invoke(this, [m.Entity]);
s_removeComponentImpl.MakeGenericMethod(m.ComponentType)
.Invoke(this, [m.Entity]);
break;
case PendingMutationKind.DestroyEntity:

View File

@ -379,7 +379,6 @@ namespace OECS;
public interface IRelationship
{
Entity Source { get; }
Entity Target { get; }
}
```
@ -399,8 +398,7 @@ namespace OECS;
public struct Relationship<TSelf, TTarget> : IRelationship
where TSelf : struct where TTarget : struct
{
[Key(0)] public Entity Source { get; set; }
[Key(1)] public Entity Target { get; set; }
[Key(0)] public Entity Target { get; set; }
}
```
@ -424,7 +422,8 @@ public enum ChangeKind
EntityRemoved,
ComponentAdded,
ComponentRemoved,
ComponentModified
ComponentModified,
RelationshipReordered
}
```