From 2313471cb50ca6de19c165735eefc4b448bc966f Mon Sep 17 00:00:00 2001 From: emelrad12 Date: Sun, 15 Jun 2025 11:55:16 +0200 Subject: [PATCH 1/4] Make parallel queries not require static Add async version to Query Prevent need for migration, by creating Query and QueryAsync methods Make parallel queries return handle Update Query generation to use parallel over chunk Silence warnings Update Query.cs Update Query.cs --- Arch.System.SourceGenerator/Query.cs | 115 ++++++++++++++++++++------- 1 file changed, 86 insertions(+), 29 deletions(-) diff --git a/Arch.System.SourceGenerator/Query.cs b/Arch.System.SourceGenerator/Query.cs index 85bb5b2..9aa2083 100644 --- a/Arch.System.SourceGenerator/Query.cs +++ b/Arch.System.SourceGenerator/Query.cs @@ -118,17 +118,22 @@ public static StringBuilder JobParameters(this StringBuilder sb, IEnumerable /// Appends a set of if they are marked by the data attribute. /// ref gameTime, out somePassedList,... /// /// The instance. /// The of s which will be appended if they are marked with data. + /// Whether the method is static and should not add the self parameter /// - public static StringBuilder JobParametersAssigment(this StringBuilder sb, IEnumerable parameterSymbols) + public static StringBuilder JobParametersAssigment(this StringBuilder sb, IEnumerable parameterSymbols, bool isStatic) { bool found = false; + if (!isStatic) + { + sb.Append($"self = this,"); + } foreach (var parameter in parameterSymbols) { if (parameter.GetAttributes().Any(attributeData => attributeData.AttributeClass.Name.Contains("Data"))) @@ -276,7 +281,9 @@ public static StringBuilder AppendQueryMethod(this StringBuilder sb, ref QueryMe var getFirstElements = new StringBuilder().GetFirstElements(queryMethod.Components); var getComponents = new StringBuilder().GetComponents(queryMethod.Components); var insertParams = new StringBuilder().InsertParams(queryMethod.Parameters); - + var jobParameters = new StringBuilder().JobParameters(queryMethod.Parameters); + var jobParametersAssigment = new StringBuilder().JobParametersAssigment(queryMethod.Parameters, queryMethod.IsStatic); + var allTypeArray = new StringBuilder().GetTypeArray(queryMethod.AllFilteredTypes); var anyTypeArray = new StringBuilder().GetTypeArray(queryMethod.AnyFilteredTypes); var noneTypeArray = new StringBuilder().GetTypeArray(queryMethod.NoneFilteredTypes); @@ -284,6 +291,7 @@ public static StringBuilder AppendQueryMethod(this StringBuilder sb, ref QueryMe var template = $$""" + // #nullable enable using System; using System.Runtime.CompilerServices; @@ -291,6 +299,7 @@ public static StringBuilder AppendQueryMethod(this StringBuilder sb, ref QueryMe using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; + using Schedulers; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; {{(!queryMethod.IsGlobalNamespace ? $"namespace {queryMethod.Namespace} {{" : "")}} @@ -305,27 +314,46 @@ partial class {{queryMethod.ClassName}}{ private {{staticModifier}} World? _{{queryMethod.MethodName}}_Initialized; private {{staticModifier}} Query? _{{queryMethod.MethodName}}_Query; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public {{staticModifier}} void {{queryMethod.MethodName}}Query(World world {{data}}){ - - if(!ReferenceEquals(_{{queryMethod.MethodName}}_Initialized, world)) { - _{{queryMethod.MethodName}}_Query = world.Query(in {{queryMethod.MethodName}}_QueryDescription); - _{{queryMethod.MethodName}}_Initialized = world; - } - - foreach(ref var chunk in _{{queryMethod.MethodName}}_Query){ - + private struct {{queryMethod.MethodName}}QueryJobChunk : IJob + { + {{jobParameters}} + public {{queryMethod.ClassName}} self; + public void Execute() { + foreach(ref var chunk in {{(queryMethod.IsStatic?$"_{queryMethod.MethodName}_Query" : $"self._{queryMethod.MethodName}_Query")}}){ {{(queryMethod.IsEntityQuery ? "ref var entityFirstElement = ref chunk.Entity(0);" : "")}} {{getFirstElements}} - + foreach(var entityIndex in chunk) { {{(queryMethod.IsEntityQuery ? $"ref readonly var {queryMethod.EntityParameter.Name.ToLower()} = ref Unsafe.Add(ref entityFirstElement, entityIndex);" : "")}} {{getComponents}} - {{queryMethod.MethodName}}({{insertParams}}); + {{(queryMethod.IsStatic ? "" : "self.")}}{{queryMethod.MethodName}}({{insertParams}}); } } + } + } + + public {{staticModifier}} void {{queryMethod.MethodName}}Query(World world {{data}}){ + + if(!ReferenceEquals(_{{queryMethod.MethodName}}_Initialized, world)) { + _{{queryMethod.MethodName}}_Query = world.Query(in {{queryMethod.MethodName}}_QueryDescription); + _{{queryMethod.MethodName}}_Initialized = world; + } + var job = new {{queryMethod.MethodName}}QueryJobChunk() { {{jobParametersAssigment}} }; + job.Execute(); + } + + public {{staticModifier}} JobHandle {{queryMethod.MethodName}}QueryAsync(World world, JobHandle parent, JobHandle source {{data}}){ + + if(!ReferenceEquals(_{{queryMethod.MethodName}}_Initialized, world)) { + _{{queryMethod.MethodName}}_Query = world.Query(in {{queryMethod.MethodName}}_QueryDescription); + _{{queryMethod.MethodName}}_Initialized = world; + } + var job = new {{queryMethod.MethodName}}QueryJobChunk() { {{jobParametersAssigment}} }; + var handle = World.SharedJobScheduler.Schedule(job, parent); + handle.SetDependsOn(source); + World.SharedJobScheduler.Flush(handle); + return handle; } } {{(!queryMethod.IsGlobalNamespace ? "}" : "")}} @@ -347,7 +375,7 @@ public static StringBuilder AppendParallelQueryMethod(this StringBuilder sb, ref // Generate code var jobParameters = new StringBuilder().JobParameters(queryMethod.Parameters); - var jobParametersAssigment = new StringBuilder().JobParametersAssigment(queryMethod.Parameters); + var jobParametersAssigment = new StringBuilder().JobParametersAssigment(queryMethod.Parameters, queryMethod.IsStatic); var data = new StringBuilder().DataParameters(queryMethod.Parameters); var getFirstElements = new StringBuilder().GetFirstElements(queryMethod.Components); var getComponents = new StringBuilder().GetComponents(queryMethod.Components); @@ -360,6 +388,7 @@ public static StringBuilder AppendParallelQueryMethod(this StringBuilder sb, ref var template = $$""" + // #nullable enable using System; using System.Runtime.CompilerServices; @@ -368,6 +397,7 @@ public static StringBuilder AppendParallelQueryMethod(this StringBuilder sb, ref using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; + using Schedulers; using Component = Arch.Core.Component; {{(!queryMethod.IsGlobalNamespace ? $"namespace {queryMethod.Namespace} {{" : "")}} partial class {{queryMethod.ClassName}}{ @@ -382,34 +412,61 @@ partial class {{queryMethod.ClassName}}{ private {{staticModifier}} World? _{{queryMethod.MethodName}}_Initialized; private {{staticModifier}} Query? _{{queryMethod.MethodName}}_Query; - private struct {{queryMethod.MethodName}}QueryJobChunk : IChunkJob + private struct {{queryMethod.MethodName}}QueryJobChunk : IParallelChunkJobProducer { {{jobParameters}} - - public void Execute(ref Chunk chunk) { + public {{queryMethod.ClassName}} self; + private Chunk chunk; + public void RunSingle(int entityIndex) { {{(queryMethod.IsEntityQuery ? "ref var entityFirstElement = ref chunk.Entity(0);" : "")}} {{getFirstElements}} - foreach(var entityIndex in chunk) - { - {{(queryMethod.IsEntityQuery ? $"ref readonly var {queryMethod.EntityParameter.Name.ToLower()} = ref Unsafe.Add(ref entityFirstElement, entityIndex);" : "")}} - {{getComponents}} - {{queryMethod.MethodName}}({{insertParams}}); - } + {{(queryMethod.IsEntityQuery ? $"ref readonly var {queryMethod.EntityParameter.Name.ToLower()} = ref Unsafe.Add(ref entityFirstElement, entityIndex);" : "")}} + {{getComponents}} + {{(queryMethod.IsStatic ? "" : "self.")}}{{queryMethod.MethodName}}({{insertParams}}); + } + + public void SetChunk(Chunk chunk) + { + this.chunk = chunk; } } + /// + /// Runs the query asynchronously. + /// Meaning you must wait for the handle returned by this method somewhere with />. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public {{staticModifier}} void {{queryMethod.MethodName}}Query(World world {{data}}){ - + public {{staticModifier}} JobHandle {{queryMethod.MethodName}}QueryAsync(World world, JobHandle parent, JobHandle source {{data}}){ + if(!ReferenceEquals(_{{queryMethod.MethodName}}_Initialized, world)) { + _{{queryMethod.MethodName}}_Query = world.Query(in {{queryMethod.MethodName}}_QueryDescription); + _{{queryMethod.MethodName}}_Initialized = world; + } + + var job = new {{queryMethod.MethodName}}QueryJobChunk() { {{jobParametersAssigment}} }; + return world.AdvancedInlineParallelChunkQuery(in {{queryMethod.MethodName}}_QueryDescription, job, parent, source); + } + + /// + /// Runs the query synchronously. + /// This is not recommended, as it will block the main thread until the query is finished. + /// This is how all queries worked in previous arch versions. + /// Use instead. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public {{staticModifier}} JobHandle {{queryMethod.MethodName}}Query(World world {{data}}){ if(!ReferenceEquals(_{{queryMethod.MethodName}}_Initialized, world)) { _{{queryMethod.MethodName}}_Query = world.Query(in {{queryMethod.MethodName}}_QueryDescription); _{{queryMethod.MethodName}}_Initialized = world; } var job = new {{queryMethod.MethodName}}QueryJobChunk() { {{jobParametersAssigment}} }; - world.InlineParallelChunkQuery(in {{queryMethod.MethodName}}_QueryDescription, job); + var parentHandle = World.SharedJobScheduler.Schedule(); + var handle = world.AdvancedInlineParallelChunkQuery(in {{queryMethod.MethodName}}_QueryDescription, job, parentHandle, default); + World.SharedJobScheduler.Flush(parentHandle); + handle.Wait(); + return handle; } } {{(!queryMethod.IsGlobalNamespace ? "}" : "")}} From 2caff96a32296e1299f9a2d1a5ebc0db914f5cc2 Mon Sep 17 00:00:00 2001 From: emelrad12 Date: Mon, 21 Jul 2025 19:29:40 +0200 Subject: [PATCH 2/4] Update Query.cs --- Arch.System.SourceGenerator/Query.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Arch.System.SourceGenerator/Query.cs b/Arch.System.SourceGenerator/Query.cs index 9aa2083..7a15a8b 100644 --- a/Arch.System.SourceGenerator/Query.cs +++ b/Arch.System.SourceGenerator/Query.cs @@ -351,7 +351,10 @@ public void Execute() { } var job = new {{queryMethod.MethodName}}QueryJobChunk() { {{jobParametersAssigment}} }; var handle = World.SharedJobScheduler.Schedule(job, parent); - handle.SetDependsOn(source); + if(!source.IsNull) + { + handle.SetDependsOn(source); + } World.SharedJobScheduler.Flush(handle); return handle; } From bf08116ffdb95547fd55d0b8dd8e14773e0e8aef Mon Sep 17 00:00:00 2001 From: emelrad12 Date: Thu, 7 Aug 2025 16:00:47 +0200 Subject: [PATCH 3/4] Update Query.cs --- Arch.System.SourceGenerator/Query.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Arch.System.SourceGenerator/Query.cs b/Arch.System.SourceGenerator/Query.cs index 7a15a8b..cb95bf4 100644 --- a/Arch.System.SourceGenerator/Query.cs +++ b/Arch.System.SourceGenerator/Query.cs @@ -465,7 +465,7 @@ public void SetChunk(Chunk chunk) } var job = new {{queryMethod.MethodName}}QueryJobChunk() { {{jobParametersAssigment}} }; - var parentHandle = World.SharedJobScheduler.Schedule(); + var parentHandle = World.SharedJobScheduler!.Schedule(); var handle = world.AdvancedInlineParallelChunkQuery(in {{queryMethod.MethodName}}_QueryDescription, job, parentHandle, default); World.SharedJobScheduler.Flush(parentHandle); handle.Wait(); From 2ff5ea1797ae02102d33f96306ec55b994c5ecd5 Mon Sep 17 00:00:00 2001 From: emelrad12 <16389331+emelrad12@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:02:44 +0100 Subject: [PATCH 4/4] Update Query.cs --- Arch.System.SourceGenerator/Query.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Arch.System.SourceGenerator/Query.cs b/Arch.System.SourceGenerator/Query.cs index cb95bf4..b612513 100644 --- a/Arch.System.SourceGenerator/Query.cs +++ b/Arch.System.SourceGenerator/Query.cs @@ -430,6 +430,17 @@ public void RunSingle(int entityIndex) { {{(queryMethod.IsStatic ? "" : "self.")}}{{queryMethod.MethodName}}({{insertParams}}); } + public void RunVectorized(int fromEntityIndex, int toEntityIndex) { + {{(queryMethod.IsEntityQuery ? "ref var entityFirstElement = ref chunk.Entity(0);" : "")}} + {{getFirstElements}} + for (var entityIndex = fromEntityIndex; entityIndex < toEntityIndex; entityIndex++) + { + {{(queryMethod.IsEntityQuery ? $"ref readonly var {queryMethod.EntityParameter.Name.ToLower()} = ref Unsafe.Add(ref entityFirstElement, entityIndex);" : "")}} + {{getComponents}} + {{(queryMethod.IsStatic ? "" : "self.")}}{{queryMethod.MethodName}}({{insertParams}}); + } + } + public void SetChunk(Chunk chunk) { this.chunk = chunk;