diff --git a/src/Arch.Benchmarks/QueryBenchmark.cs b/src/Arch.Benchmarks/QueryBenchmark.cs index 206e10cd..eb9a588c 100644 --- a/src/Arch.Benchmarks/QueryBenchmark.cs +++ b/src/Arch.Benchmarks/QueryBenchmark.cs @@ -37,8 +37,8 @@ public void WorldEntityQuery() { var refs = _world.Get(entity); - refs.t0.X += refs.t1.X; - refs.t0.Y += refs.t1.Y; + refs.Component_T0.X += refs.Component_T1.X; + refs.Component_T0.Y += refs.Component_T1.Y; }); } @@ -49,8 +49,8 @@ public void EntityExtensionQuery() _world.Query(in _queryDescription, (Entity entity) => { var refs = entity.Get(); - refs.t0.X += refs.t1.X; - refs.t0.Y += refs.t1.Y; + refs.Component_T0.X += refs.Component_T1.X; + refs.Component_T0.Y += refs.Component_T1.Y; }); } #endif @@ -84,8 +84,8 @@ public void Iterator() var refs = chunk.GetFirst(); foreach (var entity in chunk) { - ref var pos = ref Unsafe.Add(ref refs.t0, entity); - ref var vel = ref Unsafe.Add(ref refs.t1, entity); + ref var pos = ref Unsafe.Add(ref refs.Component_T0, entity); + ref var vel = ref Unsafe.Add(ref refs.Component_T1, entity); pos.X += vel.X; pos.Y += vel.Y; diff --git a/src/Arch.SourceGen/Arch.SourceGen.csproj b/src/Arch.SourceGen/Arch.SourceGen.csproj index 5a76d2dd..b35f22de 100644 --- a/src/Arch.SourceGen/Arch.SourceGen.csproj +++ b/src/Arch.SourceGen/Arch.SourceGen.csproj @@ -6,6 +6,7 @@ true + true enable Debug;Release;Release-Pure;Release-PureECS;Release-Events;Debug-PureECS;Debug-Events @@ -25,7 +26,12 @@ - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/src/Arch.SourceGen/CopyArgsAlgorithm.cs b/src/Arch.SourceGen/CopyArgsAlgorithm.cs new file mode 100644 index 00000000..65b0fb51 --- /dev/null +++ b/src/Arch.SourceGen/CopyArgsAlgorithm.cs @@ -0,0 +1,36 @@ +using System.Text.RegularExpressions; + +namespace Arch.SourceGen; + +/// +/// repeats a given argument of structure {name}_{variadicType}, separated by commas. It properly copies any ref, in, out, etc modifiers. +/// It will additionally copy generic type arguments matching the variadic type, only if the variadic type is the last in the type parameter list. +/// +/// +/// With type parameter T0: +/// +/// MyMethod<T0>(in component_T0) +/// +/// ... will convert to: +/// +/// MyMethod<T0, T1>(in component_T0, in component_T1) +/// +/// +[VariadicAlgorithm] +internal class CopyArgsAlgorithm : LineAlgorithm +{ + public override string Name { get => "CopyArgs"; } + public override int ExpectedParameterCount { get => 1; } + + public override string Transform(string line, string type, int lastVariadic, string[] parameters) + { + // Expand the args + line = Utils.ExpandUntypedArguments(line, type, lastVariadic, parameters[0]); + + // Expand any remaining generics + // Note that this'll break if the user uses Span instead of var or something.... + line = Utils.ExpandGenericTypes(line, type, lastVariadic); + + return line; + } +} diff --git a/src/Arch.SourceGen/CopyLinesAlgorithm.cs b/src/Arch.SourceGen/CopyLinesAlgorithm.cs new file mode 100644 index 00000000..da3a02df --- /dev/null +++ b/src/Arch.SourceGen/CopyLinesAlgorithm.cs @@ -0,0 +1,39 @@ +namespace Arch.SourceGen; + +/// +/// is a very simple algorithm that will simply repeat the line, replacing any occurances of the variadic type with its variant for each iteration. +/// +/// +/// With type parameter T0: +/// +/// /// [Variadic: CopyLines] +/// somethingSomething_T0 = new T0(); // look! T0! +/// +/// ... will expand to: +/// +/// somethingSomething_T0 = new T0(); // look! T0! +/// somethingSomething_T1 = new T1(); // look! T1! +/// ... +/// +/// +[VariadicAlgorithm] +internal class CopyLinesAlgorithm : LineAlgorithm +{ + public override string Name { get => "CopyLines"; } + public override int ExpectedParameterCount { get => 0; } + + public override string Transform(string line, string type, int lastVariadic, string[] parameters) + { + var (typeName, typeNum) = Utils.ExtractTypeInfo(type); + + var transformed = new StringBuilder(); + transformed.AppendLine(line); + + for (int i = typeNum + 1; i <= lastVariadic; i++) + { + transformed.AppendLine(Utils.ReplaceType(line, type, i)); + } + + return transformed.ToString(); + } +} diff --git a/src/Arch.SourceGen/CopyParamsAlgorithm.cs b/src/Arch.SourceGen/CopyParamsAlgorithm.cs new file mode 100644 index 00000000..8bda8961 --- /dev/null +++ b/src/Arch.SourceGen/CopyParamsAlgorithm.cs @@ -0,0 +1,46 @@ +using System.Text.RegularExpressions; + +namespace Arch.SourceGen; + +/// +/// +/// The is a special algorithm for more complicated method headers that can't be handled by the basic . +/// In particular, it handles cases of a type other than directly the variadic type, that needs to be transformed and copied. It also handles variable initializers. +/// For example, it will expand ref Span<T0>? component_T0 = default to +/// ref Span<T0>? component_T0 = default, ref Span<T1>? component_T1 = default .... +/// +/// +/// Basically, use this algorithm if your method has any of these: Initializers (= default), nullables (T0?), or wrapped type arguments +/// (MyWrapper<T, T0>). +/// +/// +/// Of note, it won't expand generic parameters within arguments like the default algorithm will, which is often not desired. +/// +/// +[VariadicAlgorithm] +internal class CopyParamsAlgorithm : LineAlgorithm +{ + public override int ExpectedParameterCount { get => 1; } + public override string Name { get => "CopyParams"; } + + public override string Transform(string line, string type, int lastVariadic, string[] parameters) + { + // Expand the "where" constraints (like where T0 : ISomething) + line = Utils.ExpandConstraints(line, type, lastVariadic); + + // Expand params in header (i.e. Span component_T0 -> Span component_T0, Span component_T1... + line = Utils.ExpandTypedParameters(line, type, parameters[0], lastVariadic, out int transformedStart, out int transformedEnd); + + // Expand any generic type groups (like ). We need to explicitly ONLY do this on the part of the header outside of the params, or Span + // will turn into Span which we don't want. + // So, we exclude the transformed range from the typed parameter extension. + + var beforeParams = line.Substring(0, transformedStart); + var @params = line.Substring(transformedStart, transformedEnd - transformedStart); + var afterParams = line.Substring(transformedEnd, line.Length - transformedEnd); + + // Composite the parts back together + return Utils.ExpandGenericTypes(beforeParams, type, lastVariadic) + @params + + Utils.ExpandGenericTypes(afterParams, type, lastVariadic); + } +} diff --git a/src/Arch.SourceGen/DefaultAlgorithm.cs b/src/Arch.SourceGen/DefaultAlgorithm.cs new file mode 100644 index 00000000..ed739ada --- /dev/null +++ b/src/Arch.SourceGen/DefaultAlgorithm.cs @@ -0,0 +1,51 @@ +using System.Text.RegularExpressions; + +namespace Arch.SourceGen; + +/// +/// +/// The runs by default on any lines where a variadic comment like "// [Variadic: MethodName(param1...)]" is NOT present. +/// +/// +/// The behavior of the algorithm is as such: +/// +/// +/// Anywhere in the string, if there is a type sequence ending in the specified variadic type, it fills in the variadics. For example, <T, T0> will +/// expand to <T, T0, T1...>. The generic sequence must end in the specified variadic type, otherwise nothing will be changed. +/// +/// +/// If any type constraints are found with the specified variadic type, it copies the constraints to each new type. For example, with type T0, +/// where T0 : struct, new() will expand to where T0 : struct, new() where T1 : struct, new() .... Of note, nested constraints of the variadic +/// types are not currently processed correctly, i.e. where T0 : ISomething<T0>. If that behavior is needed, either edit this algorithm or introduce a new one. +/// +/// +/// For method headers, it copies simple type parameters of the given variadic type, with the form {paramName}_{type}. For example, in T0 component_T0 +/// would expand to in T0 component_T0, in T1 component_T1. Note that this algorithm isn't smart enough to recognize wrapped parameters, such as +/// in Span<T0> component_T0; it will expand it to an invalid Span<T0, T1, ...>. To use the former behavior, or if initializers like +/// component_T0 = default is needed, or nullables are needed, specify instead with an explicit type to copy. +/// +/// +/// Nothing else is changed. +/// +/// +[VariadicAlgorithm] +internal class DefaultAlgorithm : LineAlgorithm +{ + public override int ExpectedParameterCount { get => 0; } + public override string Name { get => string.Empty; } + public override string Transform(string line, string type, int lastVariadic, string[] parameters) + { + // Expand the "where" constraints (like where T0 : ISomething) + line = Utils.ExpandConstraints(line, type, lastVariadic); + + // Expand any generic type groups (like ) + line = Utils.ExpandGenericTypes(line, type, lastVariadic); + + // Expand params in header (i.e. T0 component_T0 -> T0 component_T0, T1 component_T1... + // This is the 90% case. Occasionally there needs to be special handling for a method header with wrapped types, like Span. + // Those cases are handled by CopyParamsAlgorithm; this algorithm incorrectly produces Span instead of copying the typed params. + line = Utils.ExpandTypedParameters(line, type, type, lastVariadic, out var _, out var _); + + return line; + } +} diff --git a/src/Arch.SourceGen/Fundamentals/Components.cs b/src/Arch.SourceGen/Fundamentals/Components.cs deleted file mode 100644 index 17df4b90..00000000 --- a/src/Arch.SourceGen/Fundamentals/Components.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System.Text; -using Arch.SourceGen; - -namespace ArchSourceGenerator; - -public static class ReferencesExtensions -{ - public static StringBuilder AppendComponents(this StringBuilder sb, int amount) - { - for (var index = 0; index < amount; index++) - sb.AppendComponent(index); - - return sb; - } - - public static StringBuilder AppendComponent(this StringBuilder sb, int amount) - { - - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var parameters = new StringBuilder().GenericRefParams(amount); - - var refStructs = new StringBuilder(); - for (var index = 0; index <= amount; index++) - refStructs.AppendLine($"public Ref t{index};"); - - var references = new StringBuilder(); - for (var index = 0; index <= amount; index++) - references.AppendLine($"public ref T{index} t{index};"); - - var assignRefStructs = new StringBuilder(); - for (var index = 0; index <= amount; index++) - assignRefStructs.AppendLine($"t{index} = new Ref(ref t{index}Component);"); - - var assignRefs = new StringBuilder(); - for (var index = 0; index <= amount; index++) - assignRefs.AppendLine($"t{index} = ref t{index}Component;"); - - - var template = - $$""" - [SkipLocalsInit] - public ref struct Components<{{generics}}> - { - - #if NETSTANDARD2_1 || NET6_0 - {{refStructs}} - #else - {{references}} - #endif - - [SkipLocalsInit] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Components({{parameters}}){ - - #if NETSTANDARD2_1 || NET6_0 - {{assignRefStructs}} - #else - {{assignRefs}} - #endif - - } - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendEntityComponents(this StringBuilder sb, int amount) - { - for (var index = 0; index < amount; index++) - sb.AppendEntityComponent(index); - - return sb; - } - - public static StringBuilder AppendEntityComponent(this StringBuilder sb, int amount) - { - - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var parameters = new StringBuilder().GenericRefParams(amount); - - var refStructs = new StringBuilder(); - for (var index = 0; index <= amount; index++) - refStructs.AppendLine($"public Ref t{index};"); - - var references = new StringBuilder(); - for (var index = 0; index <= amount; index++) - references.AppendLine($"public ref T{index} t{index};"); - - var assignRefStructs = new StringBuilder(); - for (var index = 0; index <= amount; index++) - assignRefStructs.AppendLine($"t{index} = new Ref(ref t{index}Component);"); - - var assignRefs = new StringBuilder(); - for (var index = 0; index <= amount; index++) - assignRefs.AppendLine($"t{index} = ref t{index}Component;"); - - - var template = - $$""" - [SkipLocalsInit] - public ref struct EntityComponents<{{generics}}> - { - - #if NETSTANDARD2_1 || NET6_0 - public ReadOnlyRef Entity; - {{refStructs}} - #else - public ref readonly Entity Entity; - {{references}} - #endif - - [SkipLocalsInit] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public EntityComponents(ref Entity entity, {{parameters}}){ - - #if NETSTANDARD2_1 || NET6_0 - Entity = new ReadOnlyRef(in entity); - {{assignRefStructs}} - #else - Entity = ref entity; - {{assignRefs}} - #endif - - } - } - """; - - return sb.AppendLine(template); - } -} diff --git a/src/Arch.SourceGen/Fundamentals/Create.cs b/src/Arch.SourceGen/Fundamentals/Create.cs deleted file mode 100644 index 54198b38..00000000 --- a/src/Arch.SourceGen/Fundamentals/Create.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Arch.SourceGen; - -public static class CreateExtensions -{ - public static StringBuilder AppendCreates(this StringBuilder sb, int amount) - { - for (var index = 0; index < amount; index++) - { - sb.AppendCreate(index); - } - - return sb; - } - - public static StringBuilder AppendCreate(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var parameters = new StringBuilder().GenericInDefaultParams(amount); - var inParameters = new StringBuilder().InsertGenericInParams(amount); - - var addEvents = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - addEvents.AppendLine($"OnComponentAdded(entity);"); - } - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [StructuralChange] - public Entity Create<{{generics}}>({{parameters}}) - { - var types = Group<{{generics}}>.Types; - - // Recycle id or increase - var recycle = RecycledIds.TryDequeue(out var recycledId); - var recycled = recycle ? recycledId : new RecycledEntity(Size, 1); - - // Create new entity and put it to the back of the array - var entity = new Entity(recycled.Id, Id); - - // Add to archetype & mapping - var archetype = GetOrCreate(types); - var createdChunk = archetype.Add(entity, out var slot); - - archetype.Set<{{generics}}>(ref slot, {{inParameters}}); - - // Resize map & Array to fit all potential new entities - if (createdChunk) - { - Capacity += archetype.EntitiesPerChunk; - EntityInfo.EnsureCapacity(Capacity); - } - - // Map - EntityInfo.Add(entity.Id, recycled.Version, archetype, slot); - - Size++; - OnEntityCreated(entity); - - {{addEvents}} - return entity; - } - """; - - return sb.AppendLine(template); - } -} diff --git a/src/Arch.SourceGen/Fundamentals/Get.cs b/src/Arch.SourceGen/Fundamentals/Get.cs deleted file mode 100644 index b5b11cf2..00000000 --- a/src/Arch.SourceGen/Fundamentals/Get.cs +++ /dev/null @@ -1,299 +0,0 @@ -namespace Arch.SourceGen; - -public static class GetExtensions -{ - - public static StringBuilder AppendChunkGetArrays(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendChunkGetArray(index); - } - - return sb; - } - - public static StringBuilder AppendChunkGetArray(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var outs = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - outs.Append($"out T{index}[] t{index}Array,"); - } - outs.Length--; - - var indexes = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - indexes.Append($"out var t{index}Index,"); - } - indexes.Length--; - - var assignComponents = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - assignComponents.AppendLine($"t{index}Array = Unsafe.As(Unsafe.Add(ref arrays, t{index}Index));"); - } - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public void GetArray<{{generics}}>({{outs}}) - { - Index<{{generics}}>({{indexes}}); - ref var arrays = ref Components.DangerousGetReference(); - {{assignComponents}} - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendChunkGetSpans(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendChunkGetSpan(index); - } - - return sb; - } - - public static StringBuilder AppendChunkGetSpan(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - - var outs = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - outs.Append($"out Span t{index}Span,"); - } - outs.Length--; - - var arrays = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - arrays.Append($"out var t{index}Array,"); - } - arrays.Length--; - - var assignComponents = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - assignComponents.AppendLine($"t{index}Span = new Span(t{index}Array);"); - } - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public void GetSpan<{{generics}}>({{outs}}) - { - GetArray<{{generics}}>({{arrays}}); - {{assignComponents}} - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendChunkGetFirsts(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendChunkGetFirst(index); - } - - return sb; - } - - public static StringBuilder AppendChunkGetFirst(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var arrays = new StringBuilder().GetChunkArrays(amount); - - var insertParams = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - insertParams.Append($"ref t{index}Array.DangerousGetReference(),"); - } - insertParams.Length--; - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public Components<{{generics}}> GetFirst<{{generics}}>() - { - {{arrays}} - return new Components<{{generics}}>({{insertParams}}); - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendChunkIndexGets(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendChunkIndexGet(index); - } - - return sb; - } - - public static StringBuilder AppendChunkIndexGet(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var inParams = new StringBuilder().InsertGenericParams(amount); - var arrays = new StringBuilder().GetChunkArrays(amount); - - var gets = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - gets.AppendLine($"ref var t{index}Component = ref t{index}Array[index];"); - } - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public Components<{{generics}}> Get<{{generics}}>(int index) - { - {{arrays}} - {{gets}} - - return new Components<{{generics}}>({{inParams}}); - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendChunkIndexGetRows(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendChunkIndexGetRow(index); - } - - return sb; - } - - public static StringBuilder AppendChunkIndexGetRow(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var getArrays = new StringBuilder().GetChunkArrays(amount); - var inParams = new StringBuilder().InsertGenericParams(amount); - - var gets = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - gets.AppendLine($"ref var t{index}Component = ref t{index}Array[index];"); - } - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public EntityComponents<{{generics}}> GetRow<{{generics}}>(int index) - { - {{getArrays}} - - ref var entity = ref Entities[index]; - {{gets}} - - return new EntityComponents<{{generics}}>(ref entity, {{inParams}}); - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendArchetypeGets(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendArchetypeGet(index); - } - - return sb; - } - - public static StringBuilder AppendArchetypeGet(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal unsafe Components<{{generics}}> Get<{{generics}}>(scoped ref Slot slot) - { - ref var chunk = ref GetChunk(slot.ChunkIndex); - return chunk.Get<{{generics}}>(slot.Index); - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendWorldGets(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendWorldGet(index); - } - - return sb; - } - - public static StringBuilder AppendWorldGet(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public Components<{{generics}}> Get<{{generics}}>(Entity entity) - { - var slot = EntityInfo.GetSlot(entity.Id); - var archetype = EntityInfo.GetArchetype(entity.Id); - return archetype.Get<{{generics}}>(ref slot); - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendEntityGets(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendEntityGet(index); - } - - return sb; - } - - public static StringBuilder AppendEntityGet(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public static Components<{{generics}}> Get<{{generics}}>(this Entity entity) - { - var world = World.Worlds[entity.WorldId]; - return world.Get<{{generics}}>(entity); - } - """; - - return sb.AppendLine(template); - } -} diff --git a/src/Arch.SourceGen/Fundamentals/Group.cs b/src/Arch.SourceGen/Fundamentals/Group.cs deleted file mode 100644 index 52bd6e1d..00000000 --- a/src/Arch.SourceGen/Fundamentals/Group.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace Arch.SourceGen; - -public static class GroupExtensions -{ - public static StringBuilder AppendGroups(this StringBuilder sb, int amount) - { - for (var index = 0; index < amount; index++) - { - sb.AppendGroup(index); - } - - return sb; - } - - public static StringBuilder AppendGroup(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var types = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - types.Append($"Component.ComponentType,"); - } - - var template = - $$""" - /// - public static class Group<{{generics}}> - { - internal static readonly int Id; - - /// - /// The global array of for this given type group. Must not be modified in any way. - /// - public static readonly ComponentType[] Types; - - /// - /// The hash code for this given type group. - /// - public static readonly int Hash; - - static Group() - { - Id = Interlocked.Increment(ref Group.Id); - Types = new ComponentType[] { {{types}} }; - Hash = Component.GetHashCode(Types); - } - } - """; - - return sb.AppendLine(template); - } -} diff --git a/src/Arch.SourceGen/Fundamentals/Has.cs b/src/Arch.SourceGen/Fundamentals/Has.cs deleted file mode 100644 index 8f7a1327..00000000 --- a/src/Arch.SourceGen/Fundamentals/Has.cs +++ /dev/null @@ -1,150 +0,0 @@ -namespace Arch.SourceGen; - -public static class HasExtensions -{ - public static StringBuilder AppendChunkHases(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendChunkHas(index); - } - - return sb; - } - - public static StringBuilder AppendChunkHas(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - - var getIds = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - getIds.AppendLine($"var t{index}ComponentId = Component.ComponentType.Id;"); - } - - var boundChecks = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - boundChecks.AppendLine($"if (t{index}ComponentId >= ComponentIdToArrayIndex.Length) return false;"); - } - - var ifs = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - ifs.AppendLine($"if (ComponentIdToArrayIndex[t{index}ComponentId] == -1) return false;"); - } - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public bool Has<{{generics}}>() - { - {{getIds}} - {{boundChecks}} - {{ifs}} - - return true; - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendArchetypeHases(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendArchetypeHas(index); - } - - return sb; - } - - public static StringBuilder AppendArchetypeHas(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - - var getIds = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - getIds.AppendLine($"var t{index}ComponentId = Component.ComponentType.Id;"); - } - - var isSet = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - isSet.AppendLine($"BitSet.IsSet(t{index}ComponentId) &&"); - } - - isSet.Length -= 4; - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Has<{{generics}}>() - { - {{getIds}} - return {{isSet}}; - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendWorldHases(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendWorldHas(index); - } - - return sb; - } - - public static StringBuilder AppendWorldHas(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public bool Has<{{generics}}>(Entity entity) - { - var archetype = EntityInfo.GetArchetype(entity.Id); - return archetype.Has<{{generics}}>(); - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendEntityHases(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendEntityHas(index); - } - - return sb; - } - - public static StringBuilder AppendEntityHas(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - public static bool Has<{{generics}}>(this Entity entity) - { - var world = World.Worlds[entity.WorldId]; - return world.Has<{{generics}}>(entity); - } - """; - - return sb.AppendLine(template); - } -} diff --git a/src/Arch.SourceGen/Fundamentals/Index.cs b/src/Arch.SourceGen/Fundamentals/Index.cs deleted file mode 100644 index 3f58ebbf..00000000 --- a/src/Arch.SourceGen/Fundamentals/Index.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace Arch.SourceGen.Fundamentals; - -public static class IndexExtensions -{ - public static StringBuilder AppendChunkIndexes(this StringBuilder sb, int amount) - { - for (int index = 1; index <= amount; index++) - { - sb.AppendChunkIndex(index); - } - - return sb; - } - - public static StringBuilder AppendChunkIndex(this StringBuilder sb, int amount) - { - - var generics = new StringBuilder().GenericWithoutBrackets(amount); - - var outs = new StringBuilder(); - for (int i = 0; i <= amount; i++) - { - outs.Append($"out int t{i}Index,"); - } - outs.Length--; - - var assignIds = new StringBuilder(); - for (int i = 0; i <= amount; i++) - { - assignIds.AppendLine($"t{i}Index = Unsafe.Add(ref componentIdToArrayFirstElement, Component.ComponentType.Id);"); - } - - var template = $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Pure] - private void Index<{{generics}}>({{outs}}) - { - ref var componentIdToArrayFirstElement = ref ComponentIdToArrayIndex.DangerousGetReference(); - {{assignIds}} - } - """; - - sb.Append(template); - return sb; - } -} diff --git a/src/Arch.SourceGen/Fundamentals/Set.cs b/src/Arch.SourceGen/Fundamentals/Set.cs deleted file mode 100644 index ed480134..00000000 --- a/src/Arch.SourceGen/Fundamentals/Set.cs +++ /dev/null @@ -1,187 +0,0 @@ -namespace Arch.SourceGen; - -public static class SetExtensions -{ - public static StringBuilder AppendChunkIndexSets(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendChunkIndexSet(index); - } - - return sb; - } - - public static StringBuilder AppendChunkIndexSet(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var parameters = new StringBuilder().GenericInParams(amount); - var arrays = new StringBuilder().GetChunkArrays(amount); - - var sets = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - sets.AppendLine($"t{index}Array[index] = t{index}Component;"); - } - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Set<{{generics}}>(int index, {{parameters}}) - { - {{arrays}} - {{sets}} - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendArchetypeSets(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendArchetypeSet(index); - } - - return sb; - } - - public static StringBuilder AppendArchetypeSet(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var parameters = new StringBuilder().GenericInParams(amount); - var insertParameters = new StringBuilder().InsertGenericInParams(amount); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void Set<{{generics}}>(ref Slot slot, {{parameters}}) - { - ref var chunk = ref GetChunk(slot.ChunkIndex); - chunk.Set<{{generics}}>(slot.Index, {{insertParameters}}); - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendArchetypeSetRanges(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendArchetypeSetRange(index); - } - - return sb; - } - - public static StringBuilder AppendArchetypeSetRange(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var parameters = new StringBuilder().GenericInDefaultParams(amount,"ComponentValue"); - var getFirstElements = new StringBuilder().GetFirstGenericElements(amount); - var getComponents = new StringBuilder().GetGenericComponents(amount); - - var assignComponents = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - assignComponents.AppendLine($"t{index}Component = t{index}ComponentValue;"); - } - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void SetRange<{{generics}}>(in Slot from, in Slot to, {{parameters}}) - { - // Set the added component, start from the last slot and move down - for (var chunkIndex = from.ChunkIndex; chunkIndex >= to.ChunkIndex; --chunkIndex) - { - ref var chunk = ref GetChunk(chunkIndex); - {{getFirstElements}} - - // Only move within the range, depening on which chunk we are at. - var isStart = chunkIndex == from.ChunkIndex; - var isEnd = chunkIndex == to.ChunkIndex; - - var upper = isStart ? from.Index : chunk.Size-1; - var lower = isEnd ? to.Index : 0; - - for (var entityIndex = upper; entityIndex >= lower; --entityIndex) - { - {{getComponents}} - {{assignComponents}} - } - } - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendWorldSets(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendWorldSet(index); - } - - return sb; - } - - public static StringBuilder AppendWorldSet(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var parameters = new StringBuilder().GenericInParams(amount); - var insertParams = new StringBuilder().InsertGenericInParams(amount); - - var events = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - events.AppendLine($"OnComponentSet(entity);"); - } - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Set<{{generics}}>(Entity entity, {{parameters}}) - { - var slot = EntityInfo.GetSlot(entity.Id); - var archetype = EntityInfo.GetArchetype(entity.Id); - archetype.Set<{{generics}}>(ref slot, {{insertParams}}); - {{events}} - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendEntitySets(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendEntitySet(index); - } - - return sb; - } - - public static StringBuilder AppendEntitySet(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var parameters = new StringBuilder().GenericInParams(amount); - var insertParams = new StringBuilder().InsertGenericInParams(amount); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Set<{{generics}}>(this Entity entity, {{parameters}}) - { - var world = World.Worlds[entity.WorldId]; - world.Set<{{generics}}>(entity, {{insertParams}}); - } - """; - - return sb.AppendLine(template); - } -} diff --git a/src/Arch.SourceGen/Fundamentals/StructuralChanges.cs b/src/Arch.SourceGen/Fundamentals/StructuralChanges.cs deleted file mode 100644 index a5a5553a..00000000 --- a/src/Arch.SourceGen/Fundamentals/StructuralChanges.cs +++ /dev/null @@ -1,165 +0,0 @@ -namespace Arch.SourceGen; - -public static class StructuralChangesExtensions -{ - public static StringBuilder AppendWorldAdds(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendWorldAdd(index); - } - - return sb; - } - - public static StringBuilder AppendWorldAdd(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var parameters = new StringBuilder().GenericInDefaultParams(amount); - var inParameters = new StringBuilder().InsertGenericInParams(amount); - var types = new StringBuilder().GenericTypeParams(amount); - - var setIds = new StringBuilder(); - var addEvents = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - setIds.AppendLine($"spanBitSet.SetBit(Component.ComponentType.Id);"); - addEvents.AppendLine($"OnComponentAdded(entity);"); - } - - var template = - $$""" - [SkipLocalsInit] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [StructuralChange] - public void Add<{{generics}}>(Entity entity, {{parameters}}) - { - var oldArchetype = EntityInfo.GetArchetype(entity.Id); - - // BitSet to stack/span bitset, size big enough to contain ALL registered components. - Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size)]; - oldArchetype.BitSet.AsSpan(stack); - - // Create a span bitset, doing it local saves us headache and gargabe - var spanBitSet = new SpanBitSet(stack); - {{setIds}} - - if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) - newArchetype = GetOrCreate(oldArchetype.Types.Add({{types}})); - - Move(entity, oldArchetype, newArchetype, out var newSlot); - newArchetype.Set<{{generics}}>(ref newSlot, {{inParameters}}); - {{addEvents}} - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendWorldRemoves(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendWorldRemove(index); - } - - return sb; - } - - public static StringBuilder AppendWorldRemove(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var types = new StringBuilder().GenericTypeParams(amount); - - var removes = new StringBuilder(); - var events = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - removes.AppendLine($"spanBitSet.ClearBit(Component.ComponentType.Id);"); - events.AppendLine($"OnComponentRemoved(entity);"); - } - - var template = - $$""" - [SkipLocalsInit] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [StructuralChange] - public void Remove<{{generics}}>(Entity entity) - { - var oldArchetype = EntityInfo.GetArchetype(entity.Id); - - // BitSet to stack/span bitset, size big enough to contain ALL registered components. - Span stack = stackalloc uint[oldArchetype.BitSet.Length]; - oldArchetype.BitSet.AsSpan(stack); - - // Create a span bitset, doing it local saves us headache and gargabe - var spanBitSet = new SpanBitSet(stack); - {{removes}} - - if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) - newArchetype = GetOrCreate(oldArchetype.Types.Remove({{types}})); - - {{events}} - Move(entity, oldArchetype, newArchetype, out _); - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendEntityAdds(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendEntityAdd(index); - } - - return sb; - } - - public static StringBuilder AppendEntityAdd(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var parameters = new StringBuilder().GenericInDefaultParams(amount); - var inParameters = new StringBuilder().InsertGenericInParams(amount); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Add<{{generics}}>(this Entity entity, {{parameters}}) - { - var world = World.Worlds[entity.WorldId]; - world.Add<{{generics}}>(entity, {{inParameters}}); - } - """; - - return sb.AppendLine(template); - } - - public static StringBuilder AppendEntityRemoves(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendEntityRemove(index); - } - - return sb; - } - - public static StringBuilder AppendEntityRemove(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Remove<{{generics}}>(this Entity entity) - { - var world = World.Worlds[entity.WorldId]; - world.Remove<{{generics}}>(entity); - } - """; - - return sb.AppendLine(template); - } -} diff --git a/src/Arch.SourceGen/IgnoreAlgorithm.cs b/src/Arch.SourceGen/IgnoreAlgorithm.cs new file mode 100644 index 00000000..c7e96e66 --- /dev/null +++ b/src/Arch.SourceGen/IgnoreAlgorithm.cs @@ -0,0 +1,16 @@ +namespace Arch.SourceGen; + +/// +/// Processes nothing on a line. +/// +[VariadicAlgorithm] +internal class IgnoreAlgorithm : LineAlgorithm +{ + public override string Name { get => "Ignore"; } + public override int ExpectedParameterCount { get => 0; } + + public override string Transform(string line, string type, int lastVariadic, string[] parameters) + { + return line; + } +} diff --git a/src/Arch.SourceGen/LineAlgorithm.cs b/src/Arch.SourceGen/LineAlgorithm.cs new file mode 100644 index 00000000..631d975f --- /dev/null +++ b/src/Arch.SourceGen/LineAlgorithm.cs @@ -0,0 +1,30 @@ +using System.Text.RegularExpressions; + +namespace Arch.SourceGen; + +/// +/// Base class for an algorithm that processes a given line. +/// Tag subclasses with to automatically add them to the generator. +/// +internal abstract class LineAlgorithm +{ + /// + /// The name of the algorithm, as specified in the marking comment. + /// + public abstract string Name { get; } + + /// + /// The exact count of parameters to require for . + /// + public abstract int ExpectedParameterCount { get; } + + /// + /// Transform a string based on the algorithm's behavior. + /// + /// The input line. + /// The variadic type provided in the variadic attribute, e.g. T0 + /// The last variadic to generate. If 1, for example, would generate T0, T1. + /// The parameters provided to the variadic comment, if any. + /// The transformed string according to the algorithm. + public abstract string Transform(string line, string type, int lastVariadic, string[] parameters); +} diff --git a/src/Arch.SourceGen/Queries/AddWithQueryDescription.cs b/src/Arch.SourceGen/Queries/AddWithQueryDescription.cs deleted file mode 100644 index 22f1aa18..00000000 --- a/src/Arch.SourceGen/Queries/AddWithQueryDescription.cs +++ /dev/null @@ -1,93 +0,0 @@ -namespace Arch.SourceGen; - -/// -/// Adds extension methods for generating `World.Add(in query, T0...TN);` methods. -/// -public static class AddWithQueryDescription -{ - /// - /// Appends `World.Add(in query, T0...TN)` methods. - /// - /// The instance. - /// The amount. - /// - public static StringBuilder AppendAddWithQueryDescriptions(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendAddWithQueryDescription(index); - } - - return sb; - } - - /// - /// Appends a `World.Add(in query, T0...TN)` method. - /// - /// The instance. - /// The amount of generic parameters. - public static void AppendAddWithQueryDescription(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var parameters = new StringBuilder().GenericInDefaultParams(amount); - var inParameters = new StringBuilder().InsertGenericInParams(amount); - var types = new StringBuilder().GenericTypeParams(amount); - - var setIds = new StringBuilder(); - var addEvents = new StringBuilder(); - var setEvents = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - setIds.AppendLine($"spanBitSet.SetBit(Component.ComponentType.Id);"); - addEvents.AppendLine($"OnComponentAdded(archetype);"); - } - - var template = - $$""" - [SkipLocalsInit] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [StructuralChange] - public void Add<{{generics}}>(in QueryDescription queryDescription, {{parameters}}) - { - // BitSet to stack/span bitset, size big enough to contain ALL registered components. - Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size)]; - - var query = Query(in queryDescription); - foreach (var archetype in query.GetArchetypeIterator()) - { - // Archetype with T shouldnt be skipped to prevent undefined behaviour. - if(archetype.Entities == 0 || archetype.Has<{{generics}}>()) - { - continue; - } - - // Create local bitset on the stack and set bits to get a new fitting bitset of the new archetype. - archetype.BitSet.AsSpan(stack); - var spanBitSet = new SpanBitSet(stack); - {{setIds}} - - // Get or create new archetype. - if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) - { - newArchetype = GetOrCreate(archetype.Types.Add({{types}})); - } - - // Get last slots before copy, for updating entityinfo later - var archetypeSlot = archetype.LastSlot; - var newArchetypeLastSlot = newArchetype.LastSlot; - Slot.Shift(ref newArchetypeLastSlot, newArchetype.EntitiesPerChunk); - EntityInfo.Shift(archetype, archetypeSlot, newArchetype, newArchetypeLastSlot); - - // Copy, set and clear - Archetype.Copy(archetype, newArchetype); - var lastSlot = newArchetype.LastSlot; - newArchetype.SetRange(in lastSlot, in newArchetypeLastSlot, {{inParameters}}); - {{addEvents}} - archetype.Clear(); - } - } - """; - - sb.AppendLine(template); - } -} diff --git a/src/Arch.SourceGen/Queries/InlineParallelQuery.cs b/src/Arch.SourceGen/Queries/InlineParallelQuery.cs deleted file mode 100644 index 6c10f567..00000000 --- a/src/Arch.SourceGen/Queries/InlineParallelQuery.cs +++ /dev/null @@ -1,122 +0,0 @@ -namespace Arch.SourceGen; - -public static class StringBuilderHpParallelQueryExtensions -{ - public static StringBuilder AppendHpParallelQuerys(this StringBuilder builder, int amount) - { - for (var index = 0; index < amount; index++) - { - builder.AppendHpParallelQuery(index); - } - - return builder; - } - - public static void AppendHpParallelQuery(this StringBuilder builder, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void InlineParallelQuery(in QueryDescription description, ref T iForEach) where T : struct, IForEach<{{generics}}> - { - var innerJob = new IForEachJob(); - innerJob.ForEach = iForEach; - - var pool = JobMeta>>.Pool; - var query = Query(in description); - foreach (var archetype in query.GetArchetypeIterator()) - { - var archetypeSize = archetype.Size; - var part = new RangePartitioner(Environment.ProcessorCount, archetypeSize); - foreach (var range in part) - { - var job = pool.Get(); - job.Start = range.Start; - job.Size = range.Length; - job.Chunks = archetype.Chunks; - job.Instance = innerJob; - JobsCache.Add(job); - } - - IJob.Schedule(JobsCache, JobHandles); - JobScheduler.JobScheduler.Instance.Flush(); - JobHandle.Complete(JobHandles); - JobHandle.Return(JobHandles); - - // Return jobs to pool - for (var jobIndex = 0; jobIndex < JobsCache.Count; jobIndex++) - { - var job = Unsafe.As>>(JobsCache[jobIndex]); - pool.Return(job); - } - - JobHandles.Clear(); - JobsCache.Clear(); - } - } - """; - - builder.AppendLine(template); - } - - public static StringBuilder AppendHpeParallelQuerys(this StringBuilder builder, int amount) - { - for (var index = 0; index < amount; index++) - { - builder.AppendHpeParallelQuery(index); - } - - return builder; - } - - public static void AppendHpeParallelQuery(this StringBuilder builder, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void InlineParallelEntityQuery(in QueryDescription description, ref T iForEach) where T : struct, IForEachWithEntity<{{generics}}> - { - var innerJob = new IForEachWithEntityJob(); - innerJob.ForEach = iForEach; - - var pool = JobMeta>>.Pool; - var query = Query(in description); - foreach (var archetype in query.GetArchetypeIterator()) { - - var archetypeSize = archetype.Size; - var part = new RangePartitioner(Environment.ProcessorCount, archetypeSize); - foreach (var range in part) - { - var job = pool.Get(); - job.Start = range.Start; - job.Size = range.Length; - job.Chunks = archetype.Chunks; - job.Instance = innerJob; - JobsCache.Add(job); - } - - IJob.Schedule(JobsCache, JobHandles); - JobScheduler.JobScheduler.Instance.Flush(); - JobHandle.Complete(JobHandles); - JobHandle.Return(JobHandles); - - // Return jobs to pool - for (var jobIndex = 0; jobIndex < JobsCache.Count; jobIndex++) - { - var job = Unsafe.As>>(JobsCache[jobIndex]); - pool.Return(job); - } - - JobHandles.Clear(); - JobsCache.Clear(); - } - } - """; - - builder.AppendLine(template); - } -} diff --git a/src/Arch.SourceGen/Queries/InlineQuery.cs b/src/Arch.SourceGen/Queries/InlineQuery.cs deleted file mode 100644 index 7227074c..00000000 --- a/src/Arch.SourceGen/Queries/InlineQuery.cs +++ /dev/null @@ -1,217 +0,0 @@ -namespace Arch.SourceGen; - -public struct InterfaceInfo -{ - public string Name { get; set; } - public List Generics { get; set; } - public List Params { get; set; } -} - -public static class StringBuilderHpQueryExtensions -{ - public static StringBuilder Append(this StringBuilder sb, ref InterfaceInfo interfaceInfo) - { - var genericSb = new StringBuilder(); - foreach (var generic in interfaceInfo.Generics) - { - genericSb.Append(generic).Append(","); - } - - genericSb.Length--; - - var paramSb = new StringBuilder(); - foreach (var param in interfaceInfo.Params) - { - paramSb.Append(param).Append(","); - } - - paramSb.Length--; - - var template = - $$""" - public interface {{interfaceInfo.Name}}<{{genericSb}}> - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - void Update({{paramSb}}); - } - """; - - sb.Append(template); - return sb; - } - - public static StringBuilder AppendInterfaces(this StringBuilder sb, int amount) - { - var generics = new List(); - var parameters = new List(); - - for (var index = 0; index <= amount; index++) - { - generics.Add($"T{index}"); - parameters.Add($"ref T{index} t{index}"); - var interfaceInfo = new InterfaceInfo { Name = "IForEach", Generics = generics, Params = parameters }; - sb.Append(ref interfaceInfo); - } - - return sb; - } - - public static StringBuilder AppendEntityInterfaces(this StringBuilder sb, int amount) - { - var generics = new List(); - var parameters = new List - { - "Entity entity" - }; - - for (var index = 0; index <= amount; index++) - { - generics.Add($"T{index}"); - parameters.Add($"ref T{index} t{index}"); - - var interfaceInfo = new InterfaceInfo { Name = "IForEachWithEntity", Generics = generics, Params = parameters }; - sb.Append(ref interfaceInfo); - } - - return sb; - } - - public static StringBuilder AppendQueryInterfaceMethods(this StringBuilder builder, int amount) - { - for (var index = 0; index <= amount; index++) - { - var generics = new StringBuilder().GenericWithoutBrackets(index); - var getFirstElement = new StringBuilder().GetFirstGenericElements(index); - var getComponents = new StringBuilder().GetGenericComponents(index); - var insertParams = new StringBuilder().InsertGenericParams(index); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void InlineQuery(in QueryDescription description, ref T iForEach) where T : struct, IForEach<{{generics}}> - { - var query = Query(in description); - foreach (ref var chunk in query) - { - {{getFirstElement}} - - foreach(var entityIndex in chunk) - { - {{getComponents}} - iForEach.Update({{insertParams}}); - } - } - } - """; - - builder.AppendLine(template); - } - - // Methods with default T - for (var index = 0; index <= amount; index++) - { - var generics = new StringBuilder().GenericWithoutBrackets(index); - var getFirstElement = new StringBuilder().GetFirstGenericElements(index); - var getComponents = new StringBuilder().GetGenericComponents(index); - var insertParams = new StringBuilder().InsertGenericParams(index); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void InlineQuery(in QueryDescription description) where T : struct, IForEach<{{generics}}> - { - var t = new T(); - - var query = Query(in description); - foreach (ref var chunk in query) - { - var chunkSize = chunk.Size; - {{getFirstElement}} - - foreach(var entityIndex in chunk) - { - {{getComponents}} - t.Update({{insertParams}}); - } - } - } - """; - - builder.AppendLine(template); - } - - return builder; - } - - public static StringBuilder AppendEntityQueryInterfaceMethods(this StringBuilder builder, int amount) - { - for (var index = 0; index <= amount; index++) - { - var generics = new StringBuilder().GenericWithoutBrackets(index); - var getFirstElement = new StringBuilder().GetFirstGenericElements(index); - var getComponents = new StringBuilder().GetGenericComponents(index); - var insertParams = new StringBuilder().InsertGenericParams(index); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void InlineEntityQuery(in QueryDescription description, ref T iForEach) where T : struct, IForEachWithEntity<{{generics}}> - { - var query = Query(in description); - foreach (ref var chunk in query) - { - var chunkSize = chunk.Size; - ref var entityFirstElement = ref chunk.Entity(0); - {{getFirstElement}} - - foreach(var entityIndex in chunk) - { - var entity = Unsafe.Add(ref entityFirstElement, entityIndex); - {{getComponents}} - iForEach.Update(entity, {{insertParams}}); - } - } - } - """; - - builder.AppendLine(template); - } - - // Methods with default T - for (var index = 0; index <= amount; index++) - { - var generics = new StringBuilder().GenericWithoutBrackets(index); - var getFirstElement = new StringBuilder().GetFirstGenericElements(index); - var getComponents = new StringBuilder().GetGenericComponents(index); - var insertParams = new StringBuilder().InsertGenericParams(index); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void InlineEntityQuery(in QueryDescription description) where T : struct, IForEachWithEntity<{{generics}}> - { - var t = new T(); - - var query = Query(in description); - foreach (ref var chunk in query) - { - var chunkSize = chunk.Size; - ref var entityFirstElement = ref chunk.Entity(0); - {{getFirstElement}} - - foreach (var entityIndex in chunk) - { - var entity = Unsafe.Add(ref entityFirstElement, entityIndex); - {{getComponents}} - t.Update(entity, {{insertParams}}); - } - } - } - """; - - builder.AppendLine(template); - } - - return builder; - } -} diff --git a/src/Arch.SourceGen/Queries/Job.cs b/src/Arch.SourceGen/Queries/Job.cs deleted file mode 100644 index 8d259711..00000000 --- a/src/Arch.SourceGen/Queries/Job.cs +++ /dev/null @@ -1,164 +0,0 @@ -namespace Arch.SourceGen; - -public static class StringBuilderChunkJobExtensions -{ - public static void AppendForEachJobs(this StringBuilder sb, int amount) - { - for (var index = 0; index < amount; index++) - { - sb.AppendForEachJob(index); - } - } - - public static void AppendForEachJob(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var getFirstElement = new StringBuilder().GetFirstGenericElements(amount); - var getComponents = new StringBuilder().GetGenericComponents(amount); - var insertParams = new StringBuilder().InsertGenericParams(amount); - - var template = - $$""" - public struct ForEachJob<{{generics}}> : IChunkJob - { - public ForEach<{{generics}}> ForEach; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Execute(int index, ref Chunk chunk) - { - var chunkSize = chunk.Size; - {{getFirstElement}} - - for (var entityIndex = chunkSize - 1; entityIndex >= 0; --entityIndex) - { - {{getComponents}} - ForEach({{insertParams}}); - } - } - } - """; - - sb.AppendLine(template); - } - - public static void AppendEntityForEachJobs(this StringBuilder sb, int amount) - { - for (var index = 0; index < amount; index++) - { - sb.AppendEntityForEachJob(index); - } - } - - public static void AppendEntityForEachJob(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var getFirstElement = new StringBuilder().GetFirstGenericElements(amount); - var getComponents = new StringBuilder().GetGenericComponents(amount); - var insertParams = new StringBuilder().InsertGenericParams(amount); - - var template = - $$""" - public struct ForEachWithEntityJob<{{generics}}> : IChunkJob - { - public ForEachWithEntity<{{generics}}> ForEach; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Execute(int index, ref Chunk chunk) - { - ref var entityFirstElement = ref chunk.Entity(0); - {{getFirstElement}} - - foreach(var entityIndex in chunk) - { - var entity = Unsafe.Add(ref entityFirstElement, entityIndex); - {{getComponents}} - - ForEach(entity, {{insertParams}}); - } - } - } - """; - - sb.AppendLine(template); - } - - public static void AppendIForEachJobs(this StringBuilder sb, int amount) - { - for (var index = 0; index < amount; index++) - { - sb.AppendIForEachJob(index); - } - } - - public static void AppendIForEachJob(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var getFirstElement = new StringBuilder().GetFirstGenericElements(amount); - var getComponents = new StringBuilder().GetGenericComponents(amount); - var insertParams = new StringBuilder().InsertGenericParams(amount); - - var template = - $$""" - public struct IForEachJob : IChunkJob where T : struct, IForEach<{{generics}}> - { - public T ForEach; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Execute(int index, ref Chunk chunk) - { - var chunkSize = chunk.Size; - {{getFirstElement}} - - for (var entityIndex = chunkSize - 1; entityIndex >= 0; --entityIndex) - { - {{getComponents}} - ForEach.Update({{insertParams}}); - } - } - } - """; - - sb.AppendLine(template); - } - - public static void AppendIForEachWithEntityJobs(this StringBuilder sb, int amount) - { - for (var index = 0; index < amount; index++) - { - sb.AppendIForEachWithEntityJob(index); - } - } - - public static void AppendIForEachWithEntityJob(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var getFirstElement = new StringBuilder().GetFirstGenericElements(amount); - var getComponents = new StringBuilder().GetGenericComponents(amount); - var insertParams = new StringBuilder().InsertGenericParams(amount); - - var template = - $$""" - public struct IForEachWithEntityJob : IChunkJob where T : struct, IForEachWithEntity<{{generics}}> - { - public T ForEach; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Execute(int index, ref Chunk chunk) - { - var chunkSize = chunk.Size; - ref var entityFirstElement = ref chunk.Entity(0); - {{getFirstElement}} - - for (var entityIndex = chunkSize - 1; entityIndex >= 0; --entityIndex) - { - var entity = Unsafe.Add(ref entityFirstElement, entityIndex); - {{getComponents}} - ForEach.Update(entity, {{insertParams}}); - } - } - } - """; - - sb.AppendLine(template); - } -} diff --git a/src/Arch.SourceGen/Queries/ParallelQuery.cs b/src/Arch.SourceGen/Queries/ParallelQuery.cs deleted file mode 100644 index df73725a..00000000 --- a/src/Arch.SourceGen/Queries/ParallelQuery.cs +++ /dev/null @@ -1,124 +0,0 @@ -namespace Arch.SourceGen; - -public static class StringBuilderParallelQueryExtensions -{ - public static StringBuilder AppendParallelQuerys(this StringBuilder sb, int amount) - { - for (var index = 0; index < amount; index++) - { - sb.AppendParallelQuery(index); - } - - return sb; - } - - public static StringBuilder AppendParallelQuery(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ParallelQuery<{{generics}}>(in QueryDescription description, ForEach<{{generics}}> forEach) - { - var innerJob = new ForEachJob<{{generics}}>(); - innerJob.ForEach = forEach; - - var pool = JobMeta>>.Pool; - var query = Query(in description); - foreach (var archetype in query.GetArchetypeIterator()) { - - var archetypeSize = archetype.Size; - var part = new RangePartitioner(Environment.ProcessorCount, archetypeSize); - foreach (var range in part) - { - var job = pool.Get(); - job.Start = range.Start; - job.Size = range.Length; - job.Chunks = archetype.Chunks; - job.Instance = innerJob; - JobsCache.Add(job); - } - - IJob.Schedule(JobsCache, JobHandles); - JobScheduler.JobScheduler.Instance.Flush(); - JobHandle.Complete(JobHandles); - JobHandle.Return(JobHandles); - - // Return jobs to pool - for (var jobIndex = 0; jobIndex < JobsCache.Count; jobIndex++) - { - var job = Unsafe.As>>(JobsCache[jobIndex]); - pool.Return(job); - } - - JobHandles.Clear(); - JobsCache.Clear(); - } - } - """; - - sb.AppendLine(template); - return sb; - } - - public static StringBuilder AppendParallelEntityQuerys(this StringBuilder sb, int amount) - { - for (var index = 0; index < amount; index++) - { - sb.AppendParallelEntityQuery(index); - } - - return sb; - } - - public static StringBuilder AppendParallelEntityQuery(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ParallelQuery<{{generics}}>(in QueryDescription description, ForEachWithEntity<{{generics}}> forEach) - { - var innerJob = new ForEachWithEntityJob<{{generics}}>(); - innerJob.ForEach = forEach; - - var pool = JobMeta>>.Pool; - var query = Query(in description); - foreach (var archetype in query.GetArchetypeIterator()) - { - var archetypeSize = archetype.Size; - var part = new RangePartitioner(Environment.ProcessorCount, archetypeSize); - foreach (var range in part) - { - var job = pool.Get(); - job.Start = range.Start; - job.Size = range.Length; - job.Chunks = archetype.Chunks; - job.Instance = innerJob; - JobsCache.Add(job); - } - - IJob.Schedule(JobsCache, JobHandles); - JobScheduler.JobScheduler.Instance.Flush(); - JobHandle.Complete(JobHandles); - JobHandle.Return(JobHandles); - - // Return jobs to pool - for (var jobIndex = 0; jobIndex < JobsCache.Count; jobIndex++) - { - var job = Unsafe.As>>(JobsCache[jobIndex]); - pool.Return(job); - } - - JobHandles.Clear(); - JobsCache.Clear(); - } - } - """; - - sb.AppendLine(template); - return sb; - } -} diff --git a/src/Arch.SourceGen/Queries/Query.cs b/src/Arch.SourceGen/Queries/Query.cs deleted file mode 100644 index d77cff0d..00000000 --- a/src/Arch.SourceGen/Queries/Query.cs +++ /dev/null @@ -1,134 +0,0 @@ -namespace Arch.SourceGen; - -public static class StringBuilderQueryExtensions -{ - public static StringBuilder AppendForEachDelegates(this StringBuilder sb, int amount) - { - for (var index = 0; index < amount; index++) - { - sb.AppendForEachDelegate(index); - } - - return sb; - } - - public static StringBuilder AppendForEachDelegate(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var parameters = new StringBuilder().GenericRefParams(amount); - - var template = - $$""" - public delegate void ForEach<{{generics}}>({{parameters}}); - """; - - sb.Append(template); - return sb; - } - - public static StringBuilder AppendForEachEntityDelegates(this StringBuilder sb, int amount) - { - for (var index = 0; index < amount; index++) - { - sb.AppendForEachEntityDelegate(index); - } - - return sb; - } - - public static StringBuilder AppendForEachEntityDelegate(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var parameters = new StringBuilder().GenericRefParams(amount); - - var template = - $$""" - public delegate void ForEachWithEntity<{{generics}}>(Entity entity, {{parameters}}); - """; - - sb.Append(template); - return sb; - } - - public static StringBuilder AppendQueryMethods(this StringBuilder sb, int amount) - { - for (var index = 0; index < amount; index++) - { - sb.AppendQueryMethod(index); - } - - return sb; - } - - public static StringBuilder AppendQueryMethod(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var getFirstElement = new StringBuilder().GetFirstGenericElements(amount); - var getComponents = new StringBuilder().GetGenericComponents(amount); - var insertParams = new StringBuilder().InsertGenericParams(amount); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Query<{{generics}}>(in QueryDescription description, ForEach<{{generics}}> forEach) - { - var query = Query(in description); - foreach (ref var chunk in query) - { - {{getFirstElement}} - - foreach(var entityIndex in chunk) - { - {{getComponents}} - forEach({{insertParams}}); - } - } - } - """; - - sb.AppendLine(template); - return sb; - } - - public static StringBuilder AppendEntityQueryMethods(this StringBuilder sb, int amount) - { - for (var index = 0; index < amount; index++) - { - sb.AppendEntityQueryMethod(index); - } - - return sb; - } - - public static StringBuilder AppendEntityQueryMethod(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var getFirstElement = new StringBuilder().GetFirstGenericElements(amount); - var getComponents = new StringBuilder().GetGenericComponents(amount); - var insertParams = new StringBuilder().InsertGenericParams(amount); - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Query<{{generics}}>(in QueryDescription description, ForEachWithEntity<{{generics}}> forEach) - { - var query = Query(in description); - foreach (ref var chunk in query) - { - ref var entityFirstElement = ref chunk.Entity(0); - {{getFirstElement}} - - foreach(var entityIndex in chunk) - { - var entity = Unsafe.Add(ref entityFirstElement, entityIndex); - {{getComponents}} - forEach(entity, {{insertParams}}); - } - } - } - """; - - sb.AppendLine(template); - return sb; - } -} diff --git a/src/Arch.SourceGen/Queries/QueryDescription.cs b/src/Arch.SourceGen/Queries/QueryDescription.cs deleted file mode 100644 index 1a485029..00000000 --- a/src/Arch.SourceGen/Queries/QueryDescription.cs +++ /dev/null @@ -1,120 +0,0 @@ -namespace Arch.SourceGen; - -public static class QueryDescriptionExtensions -{ - public static StringBuilder AppendQueryDescriptionWithAlls(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendQueryDescriptionWithAll(index); - } - - return sb; - } - - public static StringBuilder AppendQueryDescriptionWithAll(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - - var template = - $$""" - [UnscopedRef] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref QueryDescription WithAll<{{generics}}>() - { - All = Group<{{generics}}>.Types; - return ref this; - } - """; - - sb.AppendLine(template); - return sb; - } - - public static StringBuilder AppendQueryDescriptionWithAnys(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendQueryDescriptionWithAny(index); - } - - return sb; - } - - public static StringBuilder AppendQueryDescriptionWithAny(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - - var template = - $$""" - [UnscopedRef] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref QueryDescription WithAny<{{generics}}>() - { - Any = Group<{{generics}}>.Types; - return ref this; - } - """; - - sb.AppendLine(template); - return sb; - } - - public static StringBuilder AppendQueryDescriptionWithNones(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendQueryDescriptionWithNone(index); - } - - return sb; - } - - public static StringBuilder AppendQueryDescriptionWithNone(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - - var template = - $$""" - [UnscopedRef] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref QueryDescription WithNone<{{generics}}>() - { - None = Group<{{generics}}>.Types; - return ref this; - } - """; - - sb.AppendLine(template); - return sb; - } - - public static StringBuilder AppendQueryDescriptionWithExclusives(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendQueryDescriptionWithExclusive(index); - } - - return sb; - } - - public static StringBuilder AppendQueryDescriptionWithExclusive(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - - var template = - $$""" - [UnscopedRef] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref QueryDescription WithExclusive<{{generics}}>() - { - Exclusive = Group<{{generics}}>.Types; - return ref this; - } - """; - - sb.AppendLine(template); - return sb; - } -} diff --git a/src/Arch.SourceGen/Queries/RemoveWithQueryDescription.cs b/src/Arch.SourceGen/Queries/RemoveWithQueryDescription.cs deleted file mode 100644 index ea0712ec..00000000 --- a/src/Arch.SourceGen/Queries/RemoveWithQueryDescription.cs +++ /dev/null @@ -1,89 +0,0 @@ -namespace Arch.SourceGen; - -/// -/// Adds extension methods for generating `World.Remove(in query);` methods. -/// -public static class RemoveWithQueryDesription -{ - /// - /// Appends `World.Remove(in query)` methods. - /// - /// The instance. - /// The amount. - /// - public static StringBuilder AppendRemoveWithQueryDescriptions(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendRemoveWithQueryDescription(index); - } - - return sb; - } - - /// - /// Appends a `World.Remove(in query)` method. - /// - /// The instance. - /// The amount of generic parameters. - public static void AppendRemoveWithQueryDescription(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var types = new StringBuilder().GenericTypeParams(amount); - - var clearIds = new StringBuilder(); - var removeEvents = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - clearIds.AppendLine($"spanBitSet.ClearBit(Component.ComponentType.Id);"); - removeEvents.AppendLine($"OnComponentRemoved(archetype);"); - } - - var template = - $$""" - [SkipLocalsInit] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [StructuralChange] - public void Remove<{{generics}}>(in QueryDescription queryDescription) - { - // BitSet to stack/span bitset, size big enough to contain ALL registered components. - Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size)]; - - var query = Query(in queryDescription); - foreach (var archetype in query.GetArchetypeIterator()) - { - // Archetype without T shouldnt be skipped to prevent undefined behaviour. - if(archetype.Entities <= 0 || !archetype.Has<{{generics}}>()) - { - continue; - } - - // Create local bitset on the stack and set bits to get a new fitting bitset of the new archetype. - var bitSet = archetype.BitSet; - var spanBitSet = new SpanBitSet(bitSet.AsSpan(stack)); - {{clearIds}} - - // Get or create new archetype. - if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) - { - newArchetype = GetOrCreate(archetype.Types.Remove({{types}})); - } - - {{removeEvents}} - - // Get last slots before copy, for updating entityinfo later - var archetypeSlot = archetype.LastSlot; - var newArchetypeLastSlot = newArchetype.LastSlot; - Slot.Shift(ref newArchetypeLastSlot, newArchetype.EntitiesPerChunk); - EntityInfo.Shift(archetype, archetypeSlot, newArchetype, newArchetypeLastSlot); - - Archetype.Copy(archetype, newArchetype); - archetype.Clear(); - } - } - """; - - sb.AppendLine(template); - } -} - diff --git a/src/Arch.SourceGen/Queries/SetWithQueryDescription.cs b/src/Arch.SourceGen/Queries/SetWithQueryDescription.cs deleted file mode 100644 index c021dab2..00000000 --- a/src/Arch.SourceGen/Queries/SetWithQueryDescription.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Arch.SourceGen; - -/// -/// Adds extension methods for generating `World.Remove(in query);` methods. -/// -public static class SetWithQueryDesription -{ - /// - /// Appends `World.Set(in query)` methods. - /// - /// The instance. - /// The amount. - /// - public static StringBuilder AppendSetWithQueryDescriptions(this StringBuilder sb, int amount) - { - for (var index = 1; index < amount; index++) - { - sb.AppendSetWithQueryDescription(index); - } - - return sb; - } - - /// - /// Appends a `World.Set(in query)` method. - /// - /// The instance. - /// The amount of generic parameters. - public static void AppendSetWithQueryDescription(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var getFirsts = new StringBuilder().GetFirstGenericElements(amount); - var getComponents = new StringBuilder().GetGenericComponents(amount); - var parameters = new StringBuilder().GenericInDefaultParams(amount,"ComponentValue"); - - var assignValues = new StringBuilder(); - var assignValuesEvents = new StringBuilder(); - for (var index = 0; index <= amount; index++) - { - assignValues.AppendLine($"t{index}Component = t{index}ComponentValue;"); - assignValuesEvents.AppendLine($"OnComponentSet(entity);"); - } - - var template = - $$""" - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Set<{{generics}}>(in QueryDescription queryDescription, {{parameters}}) - { - var query = Query(in queryDescription); - foreach (ref var chunk in query) - { - {{getFirsts}} - foreach (var entityIndex in chunk) - { - {{getComponents}} - {{assignValues}} - #if EVENTS - var entity = chunk.Entity(entityIndex); - {{assignValuesEvents}} - #endif - } - } - } - """; - - sb.AppendLine(template); - } -} diff --git a/src/Arch.SourceGen/QueryGenerator.cs b/src/Arch.SourceGen/QueryGenerator.cs deleted file mode 100644 index 23b8f9e6..00000000 --- a/src/Arch.SourceGen/QueryGenerator.cs +++ /dev/null @@ -1,163 +0,0 @@ -using Arch.SourceGen.Fundamentals; -using ArchSourceGenerator; - -namespace Arch.SourceGen; - -[Generator] -public class QueryGenerator : IIncrementalGenerator -{ - public void Initialize(IncrementalGeneratorInitializationContext context) - { - if (!Debugger.IsAttached) - { - //Debugger.Launch(); - } - - context.RegisterPostInitializationOutput(initializationContext => - { - - var compileTimeStatics = new StringBuilder(); - compileTimeStatics.AppendLine("using System;"); - compileTimeStatics.AppendLine("using System.Threading;"); - compileTimeStatics.AppendLine("namespace Arch.Core.Utils;"); - compileTimeStatics.AppendGroups(25); - - var delegates = new StringBuilder(); - delegates.AppendLine("using System;"); - delegates.AppendLine("namespace Arch.Core;"); - delegates.AppendForEachDelegates(25); - delegates.AppendForEachEntityDelegates(25); - - var interfaces = new StringBuilder(); - interfaces.AppendLine("using System;"); - interfaces.AppendLine("using System.Runtime.CompilerServices;"); - interfaces.AppendLine("namespace Arch.Core;"); - interfaces.AppendInterfaces(25); - interfaces.AppendEntityInterfaces(25); - - var references = new StringBuilder(); - references.AppendLine("using System;"); - references.AppendLine("using System.Runtime.CompilerServices;"); - references.AppendLine("using CommunityToolkit.HighPerformance;"); - references.AppendLine("namespace Arch.Core;"); - references.AppendComponents(25); - references.AppendEntityComponents(25); - - var jobs = new StringBuilder(); - jobs.AppendLine("using System;"); - jobs.AppendLine("using System.Runtime.CompilerServices;"); - jobs.AppendLine("using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions;"); - jobs.AppendLine("namespace Arch.Core;"); - jobs.AppendForEachJobs(25); - jobs.AppendEntityForEachJobs(25); - jobs.AppendIForEachJobs(25); - jobs.AppendIForEachWithEntityJobs(25); - - var accessors = new StringBuilder(); - accessors.AppendLine("using System;"); - accessors.AppendLine("using System.Runtime.CompilerServices;"); - accessors.AppendLine("using JobScheduler;"); - accessors.AppendLine("using Arch.Core.Utils;"); - accessors.AppendLine("using System.Diagnostics.Contracts;"); - accessors.AppendLine("using Arch.Core.Extensions;"); - accessors.AppendLine("using Arch.Core.Extensions.Internal;"); - accessors.AppendLine("using System.Diagnostics.CodeAnalysis;"); - accessors.AppendLine("using CommunityToolkit.HighPerformance;"); - accessors.AppendLine("using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions;"); - accessors.AppendLine("using System.Buffers;"); - accessors.AppendLine( - $$""" - namespace Arch.Core - { - public partial struct Chunk - { - {{new StringBuilder().AppendChunkIndexes(25)}} - {{new StringBuilder().AppendChunkHases(25)}} - {{new StringBuilder().AppendChunkIndexGets(25)}} - {{new StringBuilder().AppendChunkIndexGetRows(25)}} - {{new StringBuilder().AppendChunkIndexSets(25)}} - - {{new StringBuilder().AppendChunkGetArrays(25)}} - {{new StringBuilder().AppendChunkGetSpans(25)}} - {{new StringBuilder().AppendChunkGetFirsts(25)}} - } - - public partial class Archetype - { - {{new StringBuilder().AppendArchetypeHases(25)}} - {{new StringBuilder().AppendArchetypeGets(25)}} - {{new StringBuilder().AppendArchetypeSets(25)}} - {{new StringBuilder().AppendArchetypeSetRanges(25)}} - } - - public partial class World - { - {{new StringBuilder().AppendCreates(25)}} - {{new StringBuilder().AppendWorldHases(25)}} - {{new StringBuilder().AppendWorldGets(25)}} - {{new StringBuilder().AppendWorldSets(25)}} - {{new StringBuilder().AppendWorldAdds(25)}} - {{new StringBuilder().AppendWorldRemoves(25)}} - - {{new StringBuilder().AppendQueryMethods(25)}} - {{new StringBuilder().AppendEntityQueryMethods(25)}} - {{new StringBuilder().AppendParallelQuerys(25)}} - {{new StringBuilder().AppendParallelEntityQuerys(25)}} - - {{new StringBuilder().AppendQueryInterfaceMethods(25)}} - {{new StringBuilder().AppendEntityQueryInterfaceMethods(25)}} - {{new StringBuilder().AppendHpParallelQuerys(25)}} - {{new StringBuilder().AppendHpeParallelQuerys(25)}} - - {{new StringBuilder().AppendSetWithQueryDescriptions(25)}} - {{new StringBuilder().AppendAddWithQueryDescriptions(25)}} - {{new StringBuilder().AppendRemoveWithQueryDescriptions(25)}} - } - - public partial struct QueryDescription - { - {{new StringBuilder().AppendQueryDescriptionWithAlls(25)}} - {{new StringBuilder().AppendQueryDescriptionWithAnys(25)}} - {{new StringBuilder().AppendQueryDescriptionWithNones(25)}} - {{new StringBuilder().AppendQueryDescriptionWithExclusives(25)}} - } - - } - - namespace Arch.Core.Extensions - { - public static partial class EntityExtensions - { - #if !PURE_ECS - {{new StringBuilder().AppendEntityHases(25)}} - {{new StringBuilder().AppendEntitySets(25)}} - {{new StringBuilder().AppendEntityGets(25)}} - {{new StringBuilder().AppendEntityAdds(25)}} - {{new StringBuilder().AppendEntityRemoves(25)}} - #endif - } - - } - """ - ); - - initializationContext.AddSource("CompileTimeStatics.g.cs", - CSharpSyntaxTree.ParseText(compileTimeStatics.ToString()).GetRoot().NormalizeWhitespace().ToFullString()); - - initializationContext.AddSource("Delegates.g.cs", - CSharpSyntaxTree.ParseText(delegates.ToString()).GetRoot().NormalizeWhitespace().ToFullString()); - - initializationContext.AddSource("Interfaces.g.cs", - CSharpSyntaxTree.ParseText(interfaces.ToString()).GetRoot().NormalizeWhitespace().ToFullString()); - - initializationContext.AddSource("References.g.cs", - CSharpSyntaxTree.ParseText(references.ToString()).GetRoot().NormalizeWhitespace().ToFullString()); - - initializationContext.AddSource("Jobs.g.cs", - CSharpSyntaxTree.ParseText(jobs.ToString()).GetRoot().NormalizeWhitespace().ToFullString()); - - initializationContext.AddSource("Accessors.g.cs", - CSharpSyntaxTree.ParseText(accessors.ToString()).GetRoot().NormalizeWhitespace().ToFullString()); - }); - } -} diff --git a/src/Arch.SourceGen/StringBuilderExtensions.cs b/src/Arch.SourceGen/StringBuilderExtensions.cs deleted file mode 100644 index fae466fd..00000000 --- a/src/Arch.SourceGen/StringBuilderExtensions.cs +++ /dev/null @@ -1,259 +0,0 @@ -namespace Arch.SourceGen; - -/// -/// The class -/// contains several static extensions for the to ease code generation. -/// -public static class StringBuilderExtensions -{ - - - /// - /// Appends a comma seperated list of generics like in between diamong operators mostly. But without the diamonds. - /// - /// - /// T0,T1,T2... - /// - /// - /// - /// - /// - /// - public static StringBuilder GenericWithoutBrackets(this StringBuilder sb, int amount) - { - for (var localIndex = 0; localIndex <= amount; localIndex++) - { - sb.Append($"T{localIndex},"); - } - - sb.Length--; - - return sb; - } - - - // Queries, set, has & get - - /// TODO : Probably use chunk.GetFirst<...>(); overloads instead? - /// - /// Gets all generic first elements from an chunk and lists them up under each other. - /// - /// - /// ref var t0FirstElement = ref chunk.GetFirst<T0>(); - /// ref var t1FirstElement = ref chunk.GetFirst<T1>(); - /// ... - /// - /// - /// - /// - /// - /// - public static StringBuilder GetFirstGenericElements(this StringBuilder sb, int amount) - { - for (var localIndex = 0; localIndex <= amount; localIndex++) - { - sb.AppendLine($"ref var t{localIndex}FirstElement = ref chunk.GetFirst();"); - } - return sb; - } - - /// - /// Gets generics components from the first element and lists them under each other. - /// - /// - /// ref var t0Component = ref Unsafe.Add(ref t0FirstElement, entityIndex); - /// ref var t1Component = ref Unsafe.Add(ref t1FirstElement, entityIndex); - /// ... - /// - /// - /// - /// - /// - /// - public static StringBuilder GetGenericComponents(this StringBuilder sb, int amount) - { - for (var localIndex = 0; localIndex <= amount; localIndex++) - { - sb.AppendLine($"ref var t{localIndex}Component = ref Unsafe.Add(ref t{localIndex}FirstElement, entityIndex);"); - } - - return sb; - } - - /// - /// Lists ref params in a row as parameters. - /// - /// - /// ref T0 t0Component, ref T1 t1Component,... - /// - /// - /// - /// - /// - /// - public static StringBuilder GenericRefParams(this StringBuilder sb, int amount) - { - for (var localIndex = 0; localIndex <= amount; localIndex++) - { - sb.Append($"ref T{localIndex} t{localIndex}Component,"); - } - - sb.Length--; - return sb; - } - - /// - /// Lists in params in a row as parameters. - /// - /// - /// in T0 t0Component, in T1 t1Component,... - /// - /// - /// - /// - /// - /// - public static StringBuilder GenericInDefaultParams(this StringBuilder sb, int amount, string name = "Component") - { - for (var localIndex = 0; localIndex <= amount; localIndex++) - { - sb.Append($"in T{localIndex} t{localIndex}{name} = default,"); - } - - sb.Length--; - return sb; - } - - /// - /// Lists ref params in a row as parameters with in. - /// - /// - /// in T0 t0Component, in T1 t1Component,... - /// - /// - /// - /// - /// - /// - public static StringBuilder GenericInParams(this StringBuilder sb, int amount) - { - for (var localIndex = 0; localIndex <= amount; localIndex++) - { - sb.Append($"in T{localIndex} t{localIndex}Component,"); - } - - sb.Length--; - return sb; - } - - /// - /// Inserts ref params in a row as parameters. - /// - /// - /// ref t0Component, ref t1Component,... - /// - /// - /// - /// - /// - /// - public static StringBuilder InsertGenericParams(this StringBuilder sb, int amount) - { - for (var localIndex = 0; localIndex <= amount; localIndex++) - { - sb.Append($"ref t{localIndex}Component,"); - } - - sb.Length--; - return sb; - } - - /// - /// Inserts ref params in a row as parameters. - /// - /// - /// in t0Component, in t1Component,... - /// - /// - /// - /// - /// - /// - public static StringBuilder InsertGenericInParams(this StringBuilder sb, int amount) - { - for (var localIndex = 0; localIndex <= amount; localIndex++) - { - sb.Append($"in t{localIndex}Component,"); - } - - sb.Length--; - return sb; - } - - - /// - /// Gets out params and appends them after each other - /// - /// - /// out var t1Array, out var t2Array - /// - /// - /// - /// The appendix. - /// - /// - /// - public static StringBuilder InsertGenericOutParams(this StringBuilder sb, string appendix, int amount) - { - var arrays = new StringBuilder(); - for (var localIndex = 0; localIndex <= amount; localIndex++) - { - arrays.Append($"out var t{localIndex}{appendix},"); - } - arrays.Length--; - - return arrays; - } - - /// - /// Lists the types of generics in a row. - /// - /// - /// typeof(T0), typeof(T1),... - /// - /// - /// - /// - /// - /// - public static StringBuilder GenericTypeParams(this StringBuilder sb, int amount) - { - for (var index = 0; index <= amount; index++) - { - sb.Append($"typeof(T{index}),"); - } - sb.Length--; - return sb; - } - - - /// - /// Gets the chunk arrays in one line and appends them. - /// - /// - /// GetArray<T, T1, ...>(out var t0Array, out var t1Array,...); - /// - /// - /// - /// - /// - /// - public static StringBuilder GetChunkArrays(this StringBuilder sb, int amount) - { - var generics = new StringBuilder().GenericWithoutBrackets(amount); - var arrays = new StringBuilder().InsertGenericOutParams("Array", amount); - - sb.Append($"GetArray<{generics}>({arrays});"); - return sb; - } -} diff --git a/src/Arch.SourceGen/Utils.cs b/src/Arch.SourceGen/Utils.cs new file mode 100644 index 00000000..d1940707 --- /dev/null +++ b/src/Arch.SourceGen/Utils.cs @@ -0,0 +1,194 @@ +using System.Text.RegularExpressions; + +namespace Arch.SourceGen; +internal static class Utils +{ + /// + /// Within a method call, expand untyped arguments into their generic version. For example, MyMethod(foo, out T0 component_T0) + /// would expand to MyMethod(foo, out T0 component_T0, out T1 component_T1) given a of T0, + /// a of 1, and a of "component". + /// + /// The line to transform. + /// The basic type to expand, for example T0. + /// The last variadic to generate, incremented from the number of . + /// The variable to expand, with a _0 suffix. For example, "component" would expand "component_T0" into its types. + /// The transformed line. + public static string ExpandUntypedArguments(string line, string type, int endIndex, string variable) + { + var (typeName, typeNumber) = ExtractTypeInfo(type); + + // Modifiers capture group contains ref out, out var, etc + var modifiersMatch = Regex.Match(line, $@"[(,]\s*(?(ref|out ref|out var|out {type}\??|in|ref {type}\??))?\s*{variable}_{type}"); + if (!modifiersMatch.Success) + { + throw new InvalidOperationException($"Can't find variable {variable}_{type} in a parameter list."); + } + + var modifiers = modifiersMatch.Groups["Modifiers"].Value; + + // Collect the new arguments + var arguments = new List(); + for (int i = typeNumber; i <= endIndex; i++) + { + arguments.Add($"{modifiers} {variable}_{typeName}{i}"); + } + + // Remove the original arguments, and replace them with our variadic version. + // +1 to remove the preceding , or ( + var transformed = new StringBuilder(); + transformed.AppendLine(line); + transformed.Remove(modifiersMatch.Index + 1, modifiersMatch.Length - 1); + transformed.Insert(modifiersMatch.Index + 1, string.Join(", ", arguments)); + + return transformed.ToString(); + } + + /// The method header line. + /// The basic type to expand, for example T0. + /// The full type to copy, for example Span. + /// The last variadic to generate, incremented from the number of . + /// The inclusive start index of the transformed region of the original string. + /// The exclusive end index of the transformed region of the original string. + /// + public static string ExpandTypedParameters(string line, string type, string fullType, int endIndex, out int transformedStart, out int transformedEnd) + { + var (typeName, typeNumber) = ExtractTypeInfo(type); + + // This matches, specifically, a method header. + // This grabs the first param that matches the passed in type (in Variables[0]), like "Span? paramName_T0 = default" + // it extracts paramName, the initializer if it exists, any ref/out/in modifiers, and repeats it according to the type params. + // Captured groups: + // - Modifiers: holds ref, out, ref readonly, etc + // - ParamName: holds the parameter name that matches fullType and type. For example, if fullType is Span and type is T0, and the parameters includes + // "component_T0", this will match "component". + // - Assignment: holds the = assignment if present. Currently only supports default assignment. + var headerMatch = Regex.Match(line, $@"[(,]\s*(?(ref|out|ref readonly|in)\s+)?{Regex.Escape(fullType)}\s+(?\w+)_{type}\s*(?=\s*default)?"); + + // If we didn't find anything to transform, we give up. + if (!headerMatch.Success) + { + transformedStart = 0; + transformedEnd = 0; + return line; + } + + bool hasDefault = headerMatch.Groups["Assignment"].Success; + string modifiers = headerMatch.Groups["Modifiers"].Success ? headerMatch.Groups["Modifiers"].Value : string.Empty; + string param = headerMatch.Groups["ParamName"].Value; + + // Here, we build up the parameter string. So T0 component_T0 would become "T0 component_T0, T1 component_T1..." + var @params = new List(); + for (int i = typeNumber; i <= endIndex; i++) + { + // One issue with this approach... if we had a wrapper type of SomethingT1 (which is bad name but whatever) then this would break. + // but so far we don't have anything like that. + var variedFullType = fullType.Replace(type, $"{typeName}{i}"); + @params.Add($"{modifiers} {variedFullType} {param}_{typeName}{i} {(hasDefault ? "= default" : "")}"); + } + + var transformed = new StringBuilder(); + transformed.Append(line); + + // Remove the parameters we found + transformed.Remove(headerMatch.Index + 1, headerMatch.Length - 1); + + // Add in our newly constructed params + var paramsString = string.Join(", ", @params); + transformed.Insert(headerMatch.Index + 1, string.Join(", ", paramsString)); + + transformedStart = headerMatch.Index + 1; + transformedEnd = headerMatch.Index + 1 + paramsString.Length; + return transformed.ToString(); + } + + /// + /// Given a line and a type string, convert all instances of type constraints to the variadic version. + /// + /// The line of code to transform. + /// The type to expand, in format TypeName{N}. For example, T0. + /// The last type to generate. For example, to expand T2 to T2, T3, T4, this parameter should be 4. + /// The transformed line. + public static string ExpandConstraints(string line, string type, int endIndex) + { + var (typeName, typeNumber) = ExtractTypeInfo(type); + + // copy type constraints for our selected type + var transformed = new StringBuilder(); + var constraints = Regex.Match(line, $@"where\s+{type}\s*:\s*(?.*?)(?:where|{{|$)"); + if (!constraints.Success) + { + // no constraints, just return the line + return line; + } + + // append anything prior to the original constraint + transformed.Append(line.Substring(0, constraints.Index)); + + // append extra constraints as needed + for (int i = typeNumber; i <= endIndex; i++) + { + transformed.Append($" where {typeName}{i} : {constraints.Groups["Constraints"].Value} "); + } + + // add in the rest of the line, including the original constraint + transformed.Append(line.Substring(constraints.Index, line.Length - constraints.Index)); + return transformed.ToString(); + } + + /// + /// Given a line and a type string, convert all instances of angle brackets in the line to variadic version, + /// where the last type is the given type. + /// For example, <A, T0> would expand to <A, T0, T1, T2>. + /// + /// The line of code to transform. + /// The type to expand, in format TypeName{N}. For example, T0. + /// The last type to generate. For example, to expand T2 to T2, T3, T4, this parameter should be 4. + /// The transformed line. + public static string ExpandGenericTypes(string line, string type, int endIndex) + { + var (typeName, typeNumber) = ExtractTypeInfo(type); + + // build a string like "T0, T1, ...>" + var variadics = new List(); + for (int i = typeNumber; i <= endIndex; i++) + { + variadics.Add($"{typeName}{i}"); + } + + // Replace any occurances of T0> with our generated T0, ...> + return line.Replace($"{type}>", $"{string.Join(", ", variadics)}>"); + } + + /// + /// Replace a type with one of its variadics in a line. + /// + /// The line to transform + /// A type. For example, T0. + /// The variadic to replace the type with. For example, 1 for T1. + /// The transformed line + public static string ReplaceType(string line, string type, int variadic) + { + var (typeName, _) = ExtractTypeInfo(type); + + var transformed = new StringBuilder(); + transformed.AppendLine(line); + transformed.Replace(type, $"{typeName}{variadic}"); + return transformed.ToString(); + } + + /// + /// Given a type string, extracts the type name and number. For example, T0 would return ("T", 0). + /// + /// The type string. + /// A tuple with the type name and number + public static (string TypeName, int TypeNumber) ExtractTypeInfo(string type) + { + var match = Regex.Match(type, @"(?\w+?)(?[0-9]+)"); + if (!match.Success) + { + throw new InvalidOperationException($"{type} doesn't match format TypeName{{N}} (for example T0)"); + } + + return (match.Groups["Name"].Value, int.Parse(match.Groups["Number"].Value)); + } +} diff --git a/src/Arch.SourceGen/VariadicAlgorithmAttribute.cs b/src/Arch.SourceGen/VariadicAlgorithmAttribute.cs new file mode 100644 index 00000000..37d720d2 --- /dev/null +++ b/src/Arch.SourceGen/VariadicAlgorithmAttribute.cs @@ -0,0 +1,6 @@ +namespace Arch.SourceGen; + +/// +/// Denotes a as being an applicable algorithm, and will be applied by +/// +internal class VariadicAlgorithmAttribute : Attribute { } diff --git a/src/Arch.SourceGen/VariadicGenerator.cs b/src/Arch.SourceGen/VariadicGenerator.cs new file mode 100644 index 00000000..60df390a --- /dev/null +++ b/src/Arch.SourceGen/VariadicGenerator.cs @@ -0,0 +1,366 @@ +using System.Collections.Immutable; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Arch.SourceGen; + +/// +/// The finds assembly members with , and generates variadic types. +/// See the attribute's documentation for information on usage. +/// +[Generator] +public class VariadicGenerator : IIncrementalGenerator +{ + // A note on implementation: + // This class is implemented on a line-by-line basis making heavy use of regular expressions to parse C# code. + // Clearly, this isn't ideal. The "correct" way of implementing something like this would be a CSharpSyntaxRewriter. + // However, CSharpSyntaxRewriter is poorly documented and can lead to a lot of very very difficult code. + // For now, since this is limited to Arch, it's enough to use this. + + /// + /// Stores useful info about a member marked with . + /// + private class VariadicInfo + { + /// + /// The generic type to look for, as passed to . For example, T0. + /// + public string Type { get; set; } = string.Empty; + + /// + /// The last variadic index to generate, passed into . + /// + public int LastVariadic { get; set; } = 0; + + /// + /// The full code of the member the attribute is on, including any preceding documentation and attributes. + /// + public string Code { get; set; } = string.Empty; + + /// + /// The name of the member the attribute is on. + /// + public string Name { get; set; } = string.Empty; + + /// + /// The type declaration that the member is nested in, or an empty string if not nested. For example, public partial class MyClass + /// + public string EnclosingType { get; set; } = string.Empty; + + /// + /// The name of the type declaration that the member is nested in, or an empty string if not nested. For example, MyClass + /// + public string EnclosingTypeName { get; set; } = string.Empty; + + /// + /// The namespace the type is in. + /// + public string Namespace { get; set; } = string.Empty; + + /// + /// Any usings from the file of the attribute definition. + /// + public string Usings { get; set; } = string.Empty; + } + + /// + /// Initialize the incremental generator. + /// + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // Uncomment to help debug with breakpoints: + // Debugger.Launch(); + + var infos = context.SyntaxProvider.ForAttributeWithMetadataName("Arch.Core.VariadicAttribute", + // We want to grab everything with the attribute, always! + (node, token) => true, + // Convert into an array of VariadicInfo objects, for each attribute + (ctx, token) => ParseVariadicInfo(ctx)) + // Collect into a single array so we can process the whole thing in one go + .Collect(); + + context.RegisterSourceOutput(infos, GenerateVariadics); + } + + /// + /// Generate all of the files from a of . + /// + /// + /// + private void GenerateVariadics(SourceProductionContext ctx, ImmutableArray infos) + { + Dictionary filenames = new(); + foreach (var info in infos) + { + // Generate the code + var convertedCode = MakeVariadic(info); + + // Format properly + var text = CSharpSyntaxTree.ParseText(convertedCode).GetRoot().NormalizeWhitespace().ToFullString(); + + // Calculate the filename + var filename = info.EnclosingType != string.Empty ? $"{info.EnclosingTypeName}.{info.Name}.Variadic" : $"{info.Name}.Variadic"; + + // If we accidentally created a duplicate filename, we need to append an integer. + int filenameIndex; + if (!filenames.ContainsKey(filename)) + { + filenames[filename] = 0; + filenameIndex = 0; + } + else + { + filenames[filename]++; + filenameIndex = filenames[filename]; + } + + // We add a .1, .2 etc to repeat filenames, but leave .0 off. + string index = filenameIndex != 0 ? $".{filenameIndex}" : string.Empty; + + // Finally, add the source as well as the index + ctx.AddSource($"{filename}{index}.g.cs", text); + } + } + + /// + /// Given a , produces a object describing the method to be generated. + /// + /// The context. + /// A new instance. + private VariadicInfo ParseVariadicInfo(GeneratorAttributeSyntaxContext ctx) + { + var info = new VariadicInfo(); + + // Note: doesn't support mulilevel type nesting, like struct Something { struct AnotherThing { struct AThirdThing {} } }. Only 1 nesting layer is supported. + if (ctx.TargetSymbol.ContainingType is not null) + { + info.EnclosingTypeName = ctx.TargetSymbol.ContainingType.Name; + var accessibility = ctx.TargetSymbol.ContainingType.DeclaredAccessibility switch + { + Accessibility.Public => "public", + _ => "internal", + }; + + var reference = ctx.TargetSymbol.ContainingType.IsReferenceType ? "class" : "struct"; + + var record = ctx.TargetSymbol.ContainingType.IsRecord ? "record" : ""; + + var typeName = ctx.TargetSymbol.ContainingType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); + + info.EnclosingType = $"{accessibility} partial {record} {reference} {typeName}"; + } + + // Loop through the namespaces to get the full namespace path + string ns = string.Empty; + var containingNamespace = ctx.TargetSymbol.ContainingNamespace; + while (containingNamespace is not null && !string.IsNullOrEmpty(containingNamespace.Name)) + { + ns = ns != string.Empty ? containingNamespace.Name + "." + ns : containingNamespace.Name; + containingNamespace = containingNamespace.ContainingNamespace; + } + + info.Namespace = ns; + info.Name = ctx.TargetSymbol.Name; + + // Leading documentation (trivia) + code + info.Code = ctx.TargetNode.GetLeadingTrivia().ToString() + ctx.TargetNode.ToString(); + foreach (var use in (ctx.TargetNode.SyntaxTree.GetRoot() as CompilationUnitSyntax)!.Usings) + { + info.Usings += use.ToString() + "\n"; + } + + // Grab the arguments of the variadic attribute + foreach (var attr in ctx.TargetSymbol.GetAttributes()) + { + if (attr.AttributeClass?.Name == "VariadicAttribute") + { + info.Type = attr.ConstructorArguments[0].Value as string ?? throw new InvalidOperationException(); + info.LastVariadic = Convert.ToInt32(attr.ConstructorArguments[1].Value); + break; + } + } + + if (info.Type == string.Empty) + { + throw new InvalidOperationException(); + } + + return info; + } + + // stores the algorithms from reflection + private readonly static Dictionary _algorithms = new(); + + /// + /// Collect any methods tagged with to register them as potential algorithms for the generation. + /// process. + /// + static VariadicGenerator() + { + foreach (var type in typeof(VariadicGenerator).Assembly.GetTypes()) + { + if (type.GetCustomAttributes(typeof(VariadicAlgorithmAttribute), true).Length > 0) + { + var algorithm = (LineAlgorithm)Activator.CreateInstance(type); + if (_algorithms.ContainsKey(algorithm.Name)) + { + throw new InvalidOperationException($"Two {nameof(LineAlgorithm)}s cannot have the same name!"); + } + + _algorithms[algorithm.Name] = algorithm; + } + } + } + + /// + /// Generate a full string of generated variadic methods given a . + /// + /// The instance with information on how to generate the code. + /// A C# string containing all the generated methods. + private string MakeVariadic(VariadicInfo info) + { + // Grab a list of code tokens to help us generate + var lines = ProcessLines(info.Code).ToList(); + + var combined = new StringBuilder(); + + // Run for each variadic requested, starting after typeNum (so if the type passed is T0, it means we already defined 1 variadic , + // and must start by generating ). + var (_, typeNum) = Utils.ExtractTypeInfo(info.Type); + for (var i = typeNum + 1; i <= info.LastVariadic; i++) + { + // Process line by line, and run the algorithm on each + foreach (var line in lines) + { + if (!_algorithms.ContainsKey(line.Algorithm)) + { + throw new InvalidOperationException($"Algorithm {line.Algorithm} is unknown."); + } + + // Grab our algorithm class from the reflection dictionary. If line.Algorithm is empty, it gets the default algorithm. + var algo = _algorithms[line.Algorithm]; + + // Validate algorithm params + if (algo.ExpectedParameterCount != line.Parameters.Length) + { + throw new InvalidOperationException($"Algorithm {line.Algorithm} supports only exactly {algo.ExpectedParameterCount} parameters, " + + $"but {line.Parameters.Length} were provided."); + } + + // Run the transformation algorithm on the line + combined.AppendLine(algo.Transform(line.Line, info.Type, i, line.Parameters)); + } + } + + // If we're inside a type (like a method, for example, or an inner class), we have to put it in its parent. + if (info.EnclosingType != string.Empty) + { + combined.Insert(0, info.EnclosingType + "{"); + combined.AppendLine("}"); + } + + return $$""" + #nullable enable + {{info.Usings}} + + namespace {{info.Namespace}}; + + {{combined}} + """; + } + + /// + /// Represents a line "token" from the original code, + /// + private struct LineInfo + { + /// + /// The user-specified algorithm to use. For example, [Variadic: CopyLines] would use . + /// If blank, is assumed. + /// + public string Algorithm; + + /// + /// The parameters passed into the algorrithm. For example, [Variadic: CopyArgs(component)] would pass in component as + /// a singular param to . + /// + public string[] Parameters; + + /// + /// The unprocessed C# line, ready to go into an algorithm. + /// + public string Line; + } + + /// + /// "Tokenize" the input code into a series of lines, assigning them processing algorithms where necessary. + /// Processing algorithms are determined by a comment on the preceding lines, like // [Variadic: MyAlgorithm(param1...)] + /// + /// The info to process the code of + /// + private IEnumerable ProcessLines(string code) + { + var lines = code.Split('\n').Select(line => line.Trim()); + + string nextAlgorithm = string.Empty; + List nextParameters = new(); + foreach (var line in lines) + { + if (line.StartsWith("[Variadic(")) + { + // Don't include variadic attributes; skip over them + continue; + } + + // Never process documentation, just leave it as-is + bool isDocumentation = line.StartsWith("///"); + + // If it's a normal comment, check for variadic. + // If not, discard it. If so, include it in the line token. + if (!isDocumentation && line.StartsWith("//")) + { + // Match an algorithm specifier, like "// [Variadic: AlgorithmName(param1, param2...)]" + var match = Regex.Match(line, @"\[Variadic:\s*(?\w+)(?:\((?:(?[?\w\[\]<>]+),?\s*)+\)\])?"); + if (!match.Success) + { + // discard the comment + continue; + } + + // The next line will have this algorithm! + nextAlgorithm = match.Groups["Operation"].Value; + + // Add parameters as specified + if (match.Groups["Variable"].Success) + { + foreach (Capture capture in match.Groups["Variable"].Captures) + { + nextParameters.Add(capture.Value); + } + } + + // Since it's a comment, we want to discard anyways. + continue; + } + + // At this point, we know that the line is a meaningful line, either documentation or code. + var lineInfo = new LineInfo() + { + Line = line, + // The algorithm, as previously specified, or if it's documentation a directive to completely ignore it! + Algorithm = !isDocumentation ? nextAlgorithm : "Ignore", + Parameters = !isDocumentation ? nextParameters.ToArray() : Array.Empty() + }; + + // If this was meaningful code, we used up the algorithm and reset to default. + if (!isDocumentation) + { + nextAlgorithm = string.Empty; + nextParameters.Clear(); + } + + yield return lineInfo; + } + } +} diff --git a/src/Arch.Tests/EntityTest.cs b/src/Arch.Tests/EntityTest.cs index adbccff8..f629d2a9 100644 --- a/src/Arch.Tests/EntityTest.cs +++ b/src/Arch.Tests/EntityTest.cs @@ -155,10 +155,10 @@ public void GeneratedSetAndGet() _entity.Set(new Transform { X = 10, Y = 10 }, new Rotation { X = 10, Y = 10 }); var refs = _entity.Get(); - AreEqual(10, refs.t0.X); - AreEqual(10, refs.t0.Y); - AreEqual(10, refs.t1.X); - AreEqual(10, refs.t1.Y); + AreEqual(10, refs.Component_T0.X); + AreEqual(10, refs.Component_T0.Y); + AreEqual(10, refs.Component_T1.X); + AreEqual(10, refs.Component_T1.Y); } /// diff --git a/src/Arch.Tests/WorldTest.cs b/src/Arch.Tests/WorldTest.cs index 6b46709a..f2dfa425 100644 --- a/src/Arch.Tests/WorldTest.cs +++ b/src/Arch.Tests/WorldTest.cs @@ -622,10 +622,10 @@ public void GeneratedSetGetAndHas() _world.Set(entity, new Transform { X = 20, Y = 20 }, new Rotation { X = 20, Y = 20 }); var references = _world.Get(entity); - That(references.t0.X, Is.EqualTo(20)); - That(references.t0.Y, Is.EqualTo(20)); - That(references.t1.X, Is.EqualTo(20)); - That(references.t1.Y, Is.EqualTo(20)); + That(references.Component_T0.X, Is.EqualTo(20)); + That(references.Component_T0.Y, Is.EqualTo(20)); + That(references.Component_T1.X, Is.EqualTo(20)); + That(references.Component_T1.Y, Is.EqualTo(20)); } /// diff --git a/src/Arch/Core/Variadics/Archetype.Variadics.cs b/src/Arch/Core/Variadics/Archetype.Variadics.cs new file mode 100644 index 00000000..f352665c --- /dev/null +++ b/src/Arch/Core/Variadics/Archetype.Variadics.cs @@ -0,0 +1,72 @@ +using Arch.Core.Utils; + +namespace Arch.Core; +public partial class Archetype +{ + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T1), 24)] + public bool Has() + { + var componentId_T0 = Component.ComponentType.Id; + // [Variadic: CopyLines] + var componentId_T1 = Component.ComponentType.Id; + return BitSet.IsSet(componentId_T0) && + // [Variadic: CopyLines] + BitSet.IsSet(componentId_T1) && + true; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T1), 24)] + internal unsafe Components Get(scoped ref Slot slot) + { + ref var chunk = ref GetChunk(slot.ChunkIndex); + return chunk.Get(slot.Index); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T1), 24)] + // [Variadic: CopyParams(T1?)] + internal void Set(ref Slot slot, in T0? component_T0, in T1? component_T1) + { + ref var chunk = ref GetChunk(slot.ChunkIndex); + // [Variadic: CopyArgs(component)] + chunk.Set(slot.Index, in component_T0, in component_T1); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T1), 24)] + // [Variadic: CopyParams(T1?)] + internal void SetRange(in Slot from, in Slot to, in T0? componentValue_T0 = default, in T1? componentValue_T1 = default) + { + // Set the added component, start from the last slot and move down + for (var chunkIndex = from.ChunkIndex; chunkIndex >= to.ChunkIndex; --chunkIndex) + { + ref var chunk = ref GetChunk(chunkIndex); + ref var firstElement_T0 = ref chunk.GetFirst(); + // [Variadic: CopyLines] + ref var firstElement_T1 = ref chunk.GetFirst(); + + // Only move within the range, depening on which chunk we are at. + var isStart = chunkIndex == from.ChunkIndex; + var isEnd = chunkIndex == to.ChunkIndex; + + var upper = isStart ? from.Index : chunk.Size - 1; + var lower = isEnd ? to.Index : 0; + + for (var entityIndex = upper; entityIndex >= lower; --entityIndex) + { + ref var component_T0 = ref Unsafe.Add(ref firstElement_T0, entityIndex); + // [Variadic: CopyLines] + ref var component_T1 = ref Unsafe.Add(ref firstElement_T1, entityIndex); + component_T0 = componentValue_T0; + // [Variadic: CopyLines] + component_T1 = componentValue_T1; + } + } + } +} diff --git a/src/Arch/Core/Variadics/Chunk.Variadics.cs b/src/Arch/Core/Variadics/Chunk.Variadics.cs new file mode 100644 index 00000000..f967f8ec --- /dev/null +++ b/src/Arch/Core/Variadics/Chunk.Variadics.cs @@ -0,0 +1,140 @@ +using System.Diagnostics.Contracts; +using Arch.Core.Utils; +using CommunityToolkit.HighPerformance; + +namespace Arch.Core; + +public partial struct Chunk +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T1), 24)] + [Pure] + // [Variadic: CopyParams(int)] + private readonly void Index(out int index_T0, out int index_T1) + { + ref var componentIdToArrayFirstElement = ref ComponentIdToArrayIndex.DangerousGetReference(); + index_T0 = Unsafe.Add(ref componentIdToArrayFirstElement, Component.ComponentType.Id); + // [Variadic: CopyLines] + index_T1 = Unsafe.Add(ref componentIdToArrayFirstElement, Component.ComponentType.Id); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + [Variadic(nameof(T1), 24)] + [SuppressMessage("Style", "IDE0011:Add braces", Justification = "Enables single-line statements")] + [SuppressMessage("Style", "IDE2001:Embedded statements must be on their own line", Justification = "Enables single-line statements")] + public readonly bool Has() + { + var componentId_T0 = Component.ComponentType.Id; + // [Variadic: CopyLines] + var componentId_T1 = Component.ComponentType.Id; + + if (componentId_T0 >= ComponentIdToArrayIndex.Length) return false; + // [Variadic: CopyLines] + if (componentId_T1 >= ComponentIdToArrayIndex.Length) return false; + + if (ComponentIdToArrayIndex[componentId_T0] == -1) return false; + // [Variadic: CopyLines] + if (ComponentIdToArrayIndex[componentId_T1] == -1) return false; + + return true; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + [Variadic(nameof(T1), 24)] + public Components Get(int index) + { + // [Variadic: CopyArgs(array)] + GetArray(out var array_T0, out var array_T1); + ref var component_T0 = ref array_T0[index]; + // [Variadic: CopyLines] + ref var component_T1 = ref array_T1[index]; + + // [Variadic: CopyArgs(component)] + return new Components(ref component_T0, ref component_T1); + } + + /// + [Variadic(nameof(T1), 24)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + public EntityComponents GetRow(int index) + { + // [Variadic: CopyArgs(array)] + GetArray(out var array_T0, out var array_T1); + + ref var entity = ref Entities[index]; + ref var component_T0 = ref array_T0[index]; + // [Variadic: CopyLines] + ref var component_T1 = ref array_T1[index]; + + // [Variadic: CopyArgs(component)] + return new EntityComponents(ref entity, ref component_T0, ref component_T1); + } + + /// + [Variadic(nameof(T1), 24)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // [Variadic: CopyParams(T1?)] + public void Set(int index, in T0? component_T0, in T1? component_T1) + { + // [Variadic: CopyArgs(array)] + GetArray(out var array_T0, out var array_T1); + + array_T0[index] = component_T0!; + // [Variadic: CopyLines] + array_T1[index] = component_T1!; + } + + /// + [Variadic(nameof(T1), 24)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + [SuppressMessage("Style", "IDE0251:Make member 'readonly'", Justification = "Not actually readonly due to unsafe get")] + // [Variadic: CopyParams(T1[])] + public void GetArray(out T0[] array_T0, out T1[] array_T1) + { + // [Variadic: CopyArgs(index)] + Index(out var index_T0, out var index_T1); + ref var arrays = ref Components.DangerousGetReference(); + array_T0 = Unsafe.As(Unsafe.Add(ref arrays, index_T0)); + + // [Variadic: CopyLines] + array_T1 = Unsafe.As(Unsafe.Add(ref arrays, index_T1)); + } + + /// + [Variadic(nameof(T1), 24)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + // [Variadic: CopyParams(Span)] + public void GetSpan(out Span span_T0, out Span span_T1) + { + // [Variadic: CopyArgs(array)] + GetArray(out var array_T0, out var array_T1); + span_T0 = new Span(array_T0); + + // [Variadic: CopyLines] + span_T1 = new Span(array_T1); + } + + /// + [Variadic(nameof(T1), 24)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + public Components GetFirst() + { + // [Variadic: CopyArgs(array)] + GetArray(out var array_T0, out var array_T1); + + ref var arrayRef_T0 = ref array_T0.DangerousGetReference(); + // [Variadic: CopyLines]; + ref var arrayRef_T1 = ref array_T1.DangerousGetReference(); + + // [Variadic: CopyArgs(arrayRef)] + return new Components(ref arrayRef_T0, ref arrayRef_T1); + } +} diff --git a/src/Arch/Core/Variadics/Components.Variadics.cs b/src/Arch/Core/Variadics/Components.Variadics.cs new file mode 100644 index 00000000..e2c47d9e --- /dev/null +++ b/src/Arch/Core/Variadics/Components.Variadics.cs @@ -0,0 +1,75 @@ +using CommunityToolkit.HighPerformance; + +namespace Arch.Core; + +/// +/// Stores a reference group of up to 25 components. +/// +[SkipLocalsInit] +[Variadic(nameof(T0), 24)] +public ref struct Components +{ + /// + /// A component. + /// +#if NETSTANDARD2_1 || NET6_0 + // [Variadic: CopyLines] + public Ref Component_T0; +#else + // [Variadic: CopyLines] + public ref T0 Component_T0; +#endif + + /// + /// Creates a new instance of the struct. + /// + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Components(ref T0 component_T0) + { +#if NETSTANDARD2_1 || NET6_0 + // [Variadic: CopyLines] + Component_T0 = new Ref(ref component_T0); +#else + // [Variadic: CopyLines] + Component_T0 = ref component_T0; +#endif + } +} + +/// +/// Stores a reference group of up to 25 components, as well as an entity. +/// +[SkipLocalsInit] +[Variadic(nameof(T0), 24)] +public ref struct EntityComponents +{ + +#if NETSTANDARD2_1 || NET6_0 + public ReadOnlyRef Entity; + // [Variadic: CopyLines] + public Ref Component_T0; +#else + public ref readonly Entity Entity; + // [Variadic: CopyLines] + public ref T0 Component_T0; +#endif + + /// + /// Creates a new instance of the struct. + /// + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EntityComponents(ref Entity entity, ref T0 component_T0) + { +#if NETSTANDARD2_1 || NET6_0 + Entity = new ReadOnlyRef(in entity); + // [Variadic: CopyLines] + Component_T0 = new Ref(ref component_T0); +#else + Entity = ref entity; + // [Variadic: CopyLines] + Component_T0 = ref component_T0; +#endif + } +} diff --git a/src/Arch/Core/Variadics/EntityExtensions.Variadics.cs b/src/Arch/Core/Variadics/EntityExtensions.Variadics.cs new file mode 100644 index 00000000..6aa76ff7 --- /dev/null +++ b/src/Arch/Core/Variadics/EntityExtensions.Variadics.cs @@ -0,0 +1,57 @@ +using System.Diagnostics.Contracts; + +namespace Arch.Core.Extensions; +#if !PURE_ECS +public static partial class EntityExtensions +{ + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + [Variadic(nameof(T1), 24)] + public static bool Has(this Entity entity) + { + var world = World.Worlds[entity.WorldId]; + return world.Has(entity); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T1), 24)] + public static void Set(this Entity entity, in T0 component_T0, in T1 component_T1) + { + var world = World.Worlds[entity.WorldId]; + // [Variadic: CopyArgs(component)] + world.Set(entity, in component_T0, in component_T1); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + [Variadic(nameof(T1), 24)] + public static Components Get(this Entity entity) + { + var world = World.Worlds[entity.WorldId]; + return world.Get(entity); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T1), 24)] + public static void Add(this Entity entity, in T0 component_T0, in T1 component_T1) + { + var world = World.Worlds[entity.WorldId]; + // [Variadic: CopyArgs(component)] + world.Add(entity, in component_T0, in component_T1); + } + + /// + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T1), 24)] + public static void Remove(this Entity entity) + { + var world = World.Worlds[entity.WorldId]; + world.Remove(entity); + } +} +#endif diff --git a/src/Arch/Core/Variadics/ForEach.Variadics.cs b/src/Arch/Core/Variadics/ForEach.Variadics.cs new file mode 100644 index 00000000..4120e3a1 --- /dev/null +++ b/src/Arch/Core/Variadics/ForEach.Variadics.cs @@ -0,0 +1,15 @@ +namespace Arch.Core; + +/// +/// The delegate +/// provides a callback to execute logic on up to 25 component types. +/// +[Variadic(nameof(T0), 24)] +public delegate void ForEach(ref T0 component_T0); + +/// +/// The delegate +/// provides a callback to execute logic on an and up to 25 component types. +/// +[Variadic(nameof(T0), 24)] +public delegate void ForEachWithEntity(Entity entity, ref T0 component_T0); diff --git a/src/Arch/Core/Variadics/Group.Variadics.cs b/src/Arch/Core/Variadics/Group.Variadics.cs new file mode 100644 index 00000000..3766a76a --- /dev/null +++ b/src/Arch/Core/Variadics/Group.Variadics.cs @@ -0,0 +1,30 @@ +using System.Threading; +using Arch.Core.Utils; + +namespace Arch.Core; + +/// +[Variadic(nameof(T0), 24)] +public static class Group +{ + internal static readonly int Id; + /// + /// The global array of for this given type group. Must not be modified in any way. + /// + public static readonly ComponentType[] Types; + /// + /// The hash code for this given type group. + /// + public static readonly int Hash; + + static Group() + { + Id = Interlocked.Increment(ref Id); + Types = new ComponentType[] + { + // [Variadic: CopyLines] + Component.ComponentType, + }; + Hash = Component.GetHashCode(Types); + } +} diff --git a/src/Arch/Core/Variadics/Interfaces.Variadics.cs b/src/Arch/Core/Variadics/Interfaces.Variadics.cs new file mode 100644 index 00000000..18f444b4 --- /dev/null +++ b/src/Arch/Core/Variadics/Interfaces.Variadics.cs @@ -0,0 +1,17 @@ +namespace Arch.Core; + +/// +[Variadic(nameof(T0), 24)] +public interface IForEach +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void Update(ref T0 component_T0); +} + +/// +[Variadic(nameof(T0), 24)] +public interface IForEachWithEntity +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void Update(Entity entity, ref T0 component_T0); +} diff --git a/src/Arch/Core/Variadics/Jobs.Variadics.cs b/src/Arch/Core/Variadics/Jobs.Variadics.cs new file mode 100644 index 00000000..45317976 --- /dev/null +++ b/src/Arch/Core/Variadics/Jobs.Variadics.cs @@ -0,0 +1,103 @@ +namespace Arch.Core; + +/// +[Variadic(nameof(T0), 24)] +public struct ForEachJob : IChunkJob +{ + /// + public ForEach ForEach; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void Execute(int index, ref Chunk chunk) + { + var chunkSize = chunk.Size; + // [Variadic: CopyLines] + ref var firstElement_T0 = ref chunk.GetFirst(); + + for (var entityIndex = chunkSize - 1; entityIndex >= 0; --entityIndex) + { + // [Variadic: CopyLines] + ref var component_T0 = ref Unsafe.Add(ref firstElement_T0, entityIndex); + // [Variadic: CopyArgs(component)] + ForEach(ref component_T0); + } + } +} + +/// +[Variadic(nameof(T0), 24)] +public struct ForEachWithEntityJob : IChunkJob +{ + /// + public ForEachWithEntity ForEach; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly void Execute(int index, ref Chunk chunk) + { + ref var entityFirstElement = ref chunk.Entity(0); + // [Variadic: CopyLines] + ref var firstElement_T0 = ref chunk.GetFirst(); + + foreach (var entityIndex in chunk) + { + var entity = Unsafe.Add(ref entityFirstElement, entityIndex); + // [Variadic: CopyLines] + ref var component_T0 = ref Unsafe.Add(ref firstElement_T0, entityIndex); + // [Variadic: CopyArgs(component)] + ForEach(entity, ref component_T0); + } + } +} + +/// +[Variadic(nameof(T0), 24)] +public struct IForEachJob : IChunkJob where T : struct, IForEach +{ + /// + public T ForEach; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Execute(int index, ref Chunk chunk) + { + var chunkSize = chunk.Size; + // [Variadic: CopyLines] + ref var firstElement_T0 = ref chunk.GetFirst(); + + for (var entityIndex = chunkSize - 1; entityIndex >= 0; --entityIndex) + { + // [Variadic: CopyLines] + ref var component_T0 = ref Unsafe.Add(ref firstElement_T0, entityIndex); + // [Variadic: CopyArgs(component)] + ForEach.Update(ref component_T0); + } + } +} + +[Variadic(nameof(T0), 24)] +public struct IForEachWithEntityJob : IChunkJob where T : struct, IForEachWithEntity +{ + /// + public T ForEach; + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Execute(int index, ref Chunk chunk) + { + var chunkSize = chunk.Size; + ref var entityFirstElement = ref chunk.Entity(0); + // [Variadic: CopyLines] + ref var firstElement_T0 = ref chunk.GetFirst(); + + for (var entityIndex = chunkSize - 1; entityIndex >= 0; --entityIndex) + { + var entity = Unsafe.Add(ref entityFirstElement, entityIndex); + // [Variadic: CopyLines] + ref var component_T0 = ref Unsafe.Add(ref firstElement_T0, entityIndex); + // [Variadic: CopyArgs(component)] + ForEach.Update(entity, ref component_T0); + } + } +} diff --git a/src/Arch/Core/Variadics/QueryDescription.Variadics.cs b/src/Arch/Core/Variadics/QueryDescription.Variadics.cs new file mode 100644 index 00000000..1506c17a --- /dev/null +++ b/src/Arch/Core/Variadics/QueryDescription.Variadics.cs @@ -0,0 +1,43 @@ +namespace Arch.Core; +public partial struct QueryDescription +{ + /// + [UnscopedRef] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T1), 24)] + public ref QueryDescription WithAll() + { + All = Group.Types; + return ref this; + } + + /// + [UnscopedRef] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T1), 24)] + public ref QueryDescription WithAny() + { + Any = Group.Types; + return ref this; + } + + /// + [UnscopedRef] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T1), 24)] + public ref QueryDescription WithNone() + { + None = Group.Types; + return ref this; + } + + /// + [UnscopedRef] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T1), 24)] + public ref QueryDescription WithExclusive() + { + Exclusive = Group.Types; + return ref this; + } +} diff --git a/src/Arch/Core/Variadics/World.Variadics.cs b/src/Arch/Core/Variadics/World.Variadics.cs new file mode 100644 index 00000000..7fad4b87 --- /dev/null +++ b/src/Arch/Core/Variadics/World.Variadics.cs @@ -0,0 +1,608 @@ +using System.Diagnostics.Contracts; +using Arch.Core.Extensions.Internal; +using Arch.Core.Utils; +using JobScheduler; + +namespace Arch.Core; +public partial class World +{ + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StructuralChange] + [Variadic(nameof(T0), 24)] + // [Variadic: CopyParams(T0?)] + public Entity Create(in T0? componentValue_T0 = default) + { + var types = Group.Types; + + // Recycle id or increase + var recycle = RecycledIds.TryDequeue(out var recycledId); + var recycled = recycle ? recycledId : new RecycledEntity(Size, 1); + + // Create new entity and put it to the back of the array + var entity = new Entity(recycled.Id, Id); + + // Add to archetype & mapping + var archetype = GetOrCreate(types); + var createdChunk = archetype.Add(entity, out var slot); + + // [Variadic: CopyArgs(componentValue)] + archetype.Set(ref slot, in componentValue_T0); + + // Resize map & Array to fit all potential new entities + if (createdChunk) + { + Capacity += archetype.EntitiesPerChunk; + EntityInfo.EnsureCapacity(Capacity); + } + + // Map + EntityInfo.Add(entity.Id, recycled.Version, archetype, slot); + + Size++; + OnEntityCreated(entity); + + // [Variadic: CopyLines] + OnComponentAdded(entity); + return entity; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + [Variadic(nameof(T1), 24)] + public bool Has(Entity entity) + { + var archetype = EntityInfo.GetArchetype(entity.Id); + return archetype.Has(); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + [Variadic(nameof(T1), 24)] + public Components Get(Entity entity) + { + var slot = EntityInfo.GetSlot(entity.Id); + var archetype = EntityInfo.GetArchetype(entity.Id); + return archetype.Get(ref slot); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Pure] + [Variadic(nameof(T1), 24)] + public void Set(Entity entity, in T0 component_T0, in T1 component_T1) + { + var slot = EntityInfo.GetSlot(entity.Id); + var archetype = EntityInfo.GetArchetype(entity.Id); + // [Variadic: CopyArgs(component)] + archetype.Set(ref slot, in component_T0, in component_T1); + OnComponentSet(entity); + // [Variadic: CopyLines] + OnComponentSet(entity); + } + + /// + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StructuralChange] + [Variadic(nameof(T1), 24)] + // [Variadic: CopyParams(T1?)] + public void Add(Entity entity, in T0? component_T0 = default, in T1? component_T1 = default) + { + var oldArchetype = EntityInfo.GetArchetype(entity.Id); + + // BitSet to stack/span bitset, size big enough to contain ALL registered components. + Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size)]; + oldArchetype.BitSet.AsSpan(stack); + + // Create a span bitset, doing it local saves us headache and gargabe + var spanBitSet = new SpanBitSet(stack); + spanBitSet.SetBit(Component.ComponentType.Id); + // [Variadic: CopyLines] + spanBitSet.SetBit(Component.ComponentType.Id); + + if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) + { + var type_T0 = typeof(T0); + // [Variadic: CopyLines] + var type_T1 = typeof(T1); + // [Variadic: CopyArgs(type)] + newArchetype = GetOrCreate(oldArchetype.Types.Add(type_T0, type_T1)); + } + + Move(entity, oldArchetype, newArchetype, out var newSlot); + + // [Variadic: CopyArgs(component)] + newArchetype.Set(ref newSlot, in component_T0, in component_T1); + + OnComponentAdded(entity); + // [Variadic: CopyLines] + OnComponentAdded(entity); + } + + /// + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StructuralChange] + [Variadic(nameof(T1), 24)] + public void Remove(Entity entity) + { + var oldArchetype = EntityInfo.GetArchetype(entity.Id); + + // BitSet to stack/span bitset, size big enough to contain ALL registered components. + Span stack = stackalloc uint[oldArchetype.BitSet.Length]; + oldArchetype.BitSet.AsSpan(stack); + + // Create a span bitset, doing it local saves us headache and gargabe + var spanBitSet = new SpanBitSet(stack); + spanBitSet.ClearBit(Component.ComponentType.Id); + // [Variadic: CopyLines] + spanBitSet.ClearBit(Component.ComponentType.Id); + + if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) + { + var type_T0 = typeof(T0); + // [Variadic: CopyLines] + var type_T1 = typeof(T1); + // [Variadic: CopyArgs(type)] + newArchetype = GetOrCreate(oldArchetype.Types.Remove(type_T0, type_T1)); + } + + OnComponentRemoved(entity); + // [Variadic: CopyLines] + OnComponentRemoved(entity); + Move(entity, oldArchetype, newArchetype, out _); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T0), 24)] + public void Query(in QueryDescription description, ForEach forEach) + { + var query = Query(in description); + foreach (ref var chunk in query) + { + // [Variadic: CopyLines] + ref var firstElement_T0 = ref chunk.GetFirst(); + + foreach (var entityIndex in chunk) + { + // [Variadic: CopyLines] + ref var component_T0 = ref Unsafe.Add(ref firstElement_T0, entityIndex); + // [Variadic: CopyArgs(component)] + forEach(ref component_T0); + } + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T0), 24)] + public void Query(in QueryDescription description, ForEachWithEntity forEach) + { + var query = Query(in description); + foreach (ref var chunk in query) + { + ref var entityFirstElement = ref chunk.Entity(0); + // [Variadic: CopyLines] + ref var firstElement_T0 = ref chunk.GetFirst(); + + foreach (var entityIndex in chunk) + { + var entity = Unsafe.Add(ref entityFirstElement, entityIndex); + // [Variadic: CopyLines] + ref var component_T0 = ref Unsafe.Add(ref firstElement_T0, entityIndex); + // [Variadic: CopyArgs(component)] + forEach(entity, ref component_T0); + } + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T0), 24)] + public void ParallelQuery(in QueryDescription description, ForEach forEach) + { + var innerJob = new ForEachJob + { + ForEach = forEach + }; + + var pool = JobMeta>>.Pool; + var query = Query(in description); + foreach (var archetype in query.GetArchetypeIterator()) + { + + var archetypeSize = archetype.Size; + var part = new RangePartitioner(Environment.ProcessorCount, archetypeSize); + foreach (var range in part) + { + var job = pool.Get(); + job.Start = range.Start; + job.Size = range.Length; + job.Chunks = archetype.Chunks; + job.Instance = innerJob; + JobsCache.Add(job); + } + + IJob.Schedule(JobsCache, JobHandles); + JobScheduler.JobScheduler.Instance.Flush(); + JobHandle.Complete(JobHandles); + JobHandle.Return(JobHandles); + + // Return jobs to pool + for (var jobIndex = 0; jobIndex < JobsCache.Count; jobIndex++) + { + var job = Unsafe.As>>(JobsCache[jobIndex]); + pool.Return(job); + } + + JobHandles.Clear(); + JobsCache.Clear(); + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T0), 24)] + public void ParallelQuery(in QueryDescription description, ForEachWithEntity forEach) + { + var innerJob = new ForEachWithEntityJob + { + ForEach = forEach + }; + + var pool = JobMeta>>.Pool; + var query = Query(in description); + foreach (var archetype in query.GetArchetypeIterator()) + { + var archetypeSize = archetype.Size; + var part = new RangePartitioner(Environment.ProcessorCount, archetypeSize); + foreach (var range in part) + { + var job = pool.Get(); + job.Start = range.Start; + job.Size = range.Length; + job.Chunks = archetype.Chunks; + job.Instance = innerJob; + JobsCache.Add(job); + } + + IJob.Schedule(JobsCache, JobHandles); + JobScheduler.JobScheduler.Instance.Flush(); + JobHandle.Complete(JobHandles); + JobHandle.Return(JobHandles); + + // Return jobs to pool + for (var jobIndex = 0; jobIndex < JobsCache.Count; jobIndex++) + { + var job = Unsafe.As>>(JobsCache[jobIndex]); + pool.Return(job); + } + + JobHandles.Clear(); + JobsCache.Clear(); + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T0), 24)] + public void InlineQuery(in QueryDescription description, ref T iForEach) where T : struct, IForEach + { + var query = Query(in description); + foreach (ref var chunk in query) + { + // [Variadic: CopyLines] + ref var firstElement_T0 = ref chunk.GetFirst(); + + foreach (var entityIndex in chunk) + { + // [Variadic: CopyLines] + ref var component_T0 = ref Unsafe.Add(ref firstElement_T0, entityIndex); + // [Variadic: CopyArgs(component)] + iForEach.Update(ref component_T0); + } + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T0), 24)] + public void InlineQuery(in QueryDescription description) where T : struct, IForEach + { + var t = new T(); + + var query = Query(in description); + foreach (ref var chunk in query) + { + var chunkSize = chunk.Size; + // [Variadic: CopyLines] + ref var firstElement_T0 = ref chunk.GetFirst(); + + foreach (var entityIndex in chunk) + { + // [Variadic: CopyLines] + ref var component_T0 = ref Unsafe.Add(ref firstElement_T0, entityIndex); + // [Variadic: CopyArgs(component)] + t.Update(ref component_T0); + } + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T0), 24)] + public void InlineEntityQuery(in QueryDescription description, ref T iForEach) where T : struct, IForEachWithEntity + { + var query = Query(in description); + foreach (ref var chunk in query) + { + var chunkSize = chunk.Size; + ref var entityFirstElement = ref chunk.Entity(0); + // [Variadic: CopyLines] + ref var firstElement_T0 = ref chunk.GetFirst(); + + foreach (var entityIndex in chunk) + { + var entity = Unsafe.Add(ref entityFirstElement, entityIndex); + // [Variadic: CopyLines] + ref var component_T0 = ref Unsafe.Add(ref firstElement_T0, entityIndex); + // [Variadic: CopyArgs(component)] + iForEach.Update(entity, ref component_T0); + } + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T0), 24)] + public void InlineEntityQuery(in QueryDescription description) where T : struct, IForEachWithEntity + { + var t = new T(); + + var query = Query(in description); + foreach (ref var chunk in query) + { + var chunkSize = chunk.Size; + ref var entityFirstElement = ref chunk.Entity(0); + // [Variadic: CopyLines] + ref var firstElement_T0 = ref chunk.GetFirst(); + + foreach (var entityIndex in chunk) + { + var entity = Unsafe.Add(ref entityFirstElement, entityIndex); + // [Variadic: CopyLines] + ref var component_T0 = ref Unsafe.Add(ref firstElement_T0, entityIndex); + // [Variadic: CopyArgs(component)] + t.Update(entity, ref component_T0); + } + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T0), 24)] + public void InlineParallelQuery(in QueryDescription description, ref T iForEach) where T : struct, IForEach + { + var innerJob = new IForEachJob + { + ForEach = iForEach + }; + + var pool = JobMeta>>.Pool; + var query = Query(in description); + foreach (var archetype in query.GetArchetypeIterator()) + { + var archetypeSize = archetype.Size; + var part = new RangePartitioner(Environment.ProcessorCount, archetypeSize); + foreach (var range in part) + { + var job = pool.Get(); + job.Start = range.Start; + job.Size = range.Length; + job.Chunks = archetype.Chunks; + job.Instance = innerJob; + JobsCache.Add(job); + } + + IJob.Schedule(JobsCache, JobHandles); + JobScheduler.JobScheduler.Instance.Flush(); + JobHandle.Complete(JobHandles); + JobHandle.Return(JobHandles); + + // Return jobs to pool + for (var jobIndex = 0; jobIndex < JobsCache.Count; jobIndex++) + { + var job = Unsafe.As>>(JobsCache[jobIndex]); + pool.Return(job); + } + + JobHandles.Clear(); + JobsCache.Clear(); + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T0), 24)] + public void InlineParallelEntityQuery(in QueryDescription description, ref T iForEach) where T : struct, IForEachWithEntity + { + var innerJob = new IForEachWithEntityJob + { + ForEach = iForEach + }; + + var pool = JobMeta>>.Pool; + var query = Query(in description); + foreach (var archetype in query.GetArchetypeIterator()) + { + + var archetypeSize = archetype.Size; + var part = new RangePartitioner(Environment.ProcessorCount, archetypeSize); + foreach (var range in part) + { + var job = pool.Get(); + job.Start = range.Start; + job.Size = range.Length; + job.Chunks = archetype.Chunks; + job.Instance = innerJob; + JobsCache.Add(job); + } + + IJob.Schedule(JobsCache, JobHandles); + JobScheduler.JobScheduler.Instance.Flush(); + JobHandle.Complete(JobHandles); + JobHandle.Return(JobHandles); + + // Return jobs to pool + for (var jobIndex = 0; jobIndex < JobsCache.Count; jobIndex++) + { + var job = Unsafe.As>>(JobsCache[jobIndex]); + pool.Return(job); + } + + JobHandles.Clear(); + JobsCache.Clear(); + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Variadic(nameof(T1), 24)] + // [Variadic: CopyParams(T1?)] + public void Set(in QueryDescription queryDescription, in T0? componentValue_T0 = default, in T1? componentValue_T1 = default) + { + var query = Query(in queryDescription); + foreach (ref var chunk in query) + { + ref var firstElement_T0 = ref chunk.GetFirst(); + // [Variadic: CopyLines] + ref var firstElement_T1 = ref chunk.GetFirst(); + + foreach (var entityIndex in chunk) + { + ref var component_T0 = ref Unsafe.Add(ref firstElement_T0, entityIndex); + // [Variadic: CopyLines] + ref var component_T1 = ref Unsafe.Add(ref firstElement_T1, entityIndex); + component_T0 = componentValue_T0; + // [Variadic: CopyLines] + component_T1 = componentValue_T1; +#if EVENTS + var entity = chunk.Entity(entityIndex); + OnComponentSet(entity); + // [Variadic: CopyLines] + OnComponentSet(entity); +#endif + } + } + } + + /// + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StructuralChange] + [Variadic(nameof(T1), 24)] + // [Variadic: CopyParams(T1?)] + public void Add(in QueryDescription queryDescription, in T0? component_T0 = default, in T1? component_T1 = default) + { + // BitSet to stack/span bitset, size big enough to contain ALL registered components. + Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size)]; + + var query = Query(in queryDescription); + foreach (var archetype in query.GetArchetypeIterator()) + { + // Archetype with T shouldnt be skipped to prevent undefined behaviour. + if (archetype.Entities == 0 || archetype.Has()) + { + continue; + } + + // Create local bitset on the stack and set bits to get a new fitting bitset of the new archetype. + archetype.BitSet.AsSpan(stack); + var spanBitSet = new SpanBitSet(stack); + spanBitSet.SetBit(Component.ComponentType.Id); + // [Variadic: CopyLines] + spanBitSet.SetBit(Component.ComponentType.Id); + + // Get or create new archetype. + if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) + { + var type_T0 = typeof(T0); + // [Variadic: CopyLines] + var type_T1 = typeof(T1); + // [Variadic: CopyArgs(type)] + newArchetype = GetOrCreate(archetype.Types.Add(type_T0, type_T1)); + } + + // Get last slots before copy, for updating entityinfo later + var archetypeSlot = archetype.LastSlot; + var newArchetypeLastSlot = newArchetype.LastSlot; + Slot.Shift(ref newArchetypeLastSlot, newArchetype.EntitiesPerChunk); + EntityInfo.Shift(archetype, archetypeSlot, newArchetype, newArchetypeLastSlot); + + // Copy, set and clear + Archetype.Copy(archetype, newArchetype); + var lastSlot = newArchetype.LastSlot; + // [Variadic: CopyArgs(component)] + newArchetype.SetRange(in lastSlot, in newArchetypeLastSlot, in component_T0, in component_T1); + OnComponentAdded(archetype); + // [Variadic: CopyLines] + OnComponentAdded(archetype); + archetype.Clear(); + } + } + + /// + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [StructuralChange] + [Variadic(nameof(T1), 24)] + public void Remove(in QueryDescription queryDescription) + { + // BitSet to stack/span bitset, size big enough to contain ALL registered components. + Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size)]; + + var query = Query(in queryDescription); + foreach (var archetype in query.GetArchetypeIterator()) + { + // Archetype without T shouldnt be skipped to prevent undefined behaviour. + if (archetype.Entities <= 0 || !archetype.Has()) + { + continue; + } + + // Create local bitset on the stack and set bits to get a new fitting bitset of the new archetype. + var bitSet = archetype.BitSet; + var spanBitSet = new SpanBitSet(bitSet.AsSpan(stack)); + spanBitSet.ClearBit(Component.ComponentType.Id); + // [Variadic: CopyLines] + spanBitSet.ClearBit(Component.ComponentType.Id); + + // Get or create new archetype. + if (!TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) + { + var type_T0 = typeof(T0); + // [Variadic: CopyLines] + var type_T1 = typeof(T1); + // [Variadic: CopyArgs(type)] + newArchetype = GetOrCreate(archetype.Types.Remove(type_T0, type_T1)); + } + + OnComponentRemoved(archetype); + // [Variadic: CopyLines] + OnComponentRemoved(archetype); + + // Get last slots before copy, for updating entityinfo later + var archetypeSlot = archetype.LastSlot; + var newArchetypeLastSlot = newArchetype.LastSlot; + Slot.Shift(ref newArchetypeLastSlot, newArchetype.EntitiesPerChunk); + EntityInfo.Shift(archetype, archetypeSlot, newArchetype, newArchetypeLastSlot); + + Archetype.Copy(archetype, newArchetype); + archetype.Clear(); + } + } +} diff --git a/src/Arch/VariadicAttribute.cs b/src/Arch/VariadicAttribute.cs new file mode 100644 index 00000000..6d154de5 --- /dev/null +++ b/src/Arch/VariadicAttribute.cs @@ -0,0 +1,36 @@ +namespace Arch.Core; + +#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved +/// +/// Tags a method or type as being variadic; i.e. generating many versions with any number of generic parameters. +/// +/// +/// +/// See for the default behavior. +/// +/// +/// Frequently, the default behavior of the generator will be inadequate. In those cases, you can precede an offending line with +/// "// [Variadic: AlgorithmName(...algorithmParams)]". Currently, the available algorithms are (for SOME method headers +/// with special params), (to simply copy a line for as many variadics are generating), and +/// (to insert a variable into a method call's arguments.). See their respective documentation for their usage information. +/// +/// +/// For examples, look at the existing implementations. +/// +/// +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Delegate | AttributeTargets.Interface)] +#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved +internal class VariadicAttribute : Attribute +{ + /// + /// Tag a method or type as being variadic; i.e. generating many generic parameters. + /// + /// + /// The name of the type to expand. For example, if your method is Add<T0, T1>, it would be nameof(T1). + /// + /// + /// The last variadic to generate. If your type is T0, use 24 to get 25 total methods (T0 to T24). + /// + [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Instance params not necessary for sourcegen.")] + public VariadicAttribute(string name, int lastVariadic) { } +}