Repository: genaray/Arch.Extended Branch: master Commit: 29b34ab00e5f Files: 114 Total size: 566.8 KB Directory structure: gitextract_j5eynvpp/ ├── .gitattributes ├── .github/ │ └── workflows/ │ └── main.yml ├── .gitignore ├── Arch.AOT.SourceGenerator/ │ ├── Arch.AOT.SourceGenerator.csproj │ ├── ComponentType.cs │ ├── Extensions/ │ │ └── StringBuilderExtensions.cs │ └── SourceGenerator.cs ├── Arch.EventBus/ │ ├── Arch.EventBus.csproj │ ├── EventBus.cs │ ├── Hooks.cs │ ├── MethodSymbolExtensions.cs │ └── SourceGenerator.cs ├── Arch.Extended.Sample/ │ ├── Arch.Extended.Sample.csproj │ ├── Components.cs │ ├── Extensions.cs │ ├── Game.cs │ ├── Program.cs │ ├── Serializer.cs │ └── Systems.cs ├── Arch.Extended.sln ├── Arch.Extended.sln.DotSettings ├── Arch.LowLevel/ │ ├── Arch.LowLevel.csproj │ ├── Array.cs │ ├── Enumerators.cs │ ├── Jagged/ │ │ ├── JaggedArray.cs │ │ ├── SparseJaggedArray.cs │ │ ├── UnsafeJaggedArray.cs │ │ └── UnsafeSparseJaggedArray.cs │ ├── Resources.cs │ ├── UnsafeArray.cs │ ├── UnsafeList.cs │ ├── UnsafeQueue.cs │ └── UnsafeStack.cs ├── Arch.LowLevel.Tests/ │ ├── Arch.LowLevel.Tests.csproj │ ├── ArrayTest.cs │ ├── Jagged/ │ │ └── JaggedArrayTest.cs │ ├── ResourcesTest.cs │ ├── UnsafeArrayTest.cs │ ├── UnsafeListTest.cs │ ├── UnsafeQueueTest.cs │ ├── UnsafeStackTest.cs │ └── Usings.cs ├── Arch.Persistence/ │ ├── Arch.Persistence.csproj │ ├── Binary.cs │ ├── Json.cs │ ├── Serializer.cs │ └── StreamBufferWriter.cs ├── Arch.Persistence.Tests/ │ ├── Arch.Persistence.Tests.csproj │ ├── PersistenceTest.cs │ └── Usings.cs ├── Arch.Relationships/ │ ├── Arch.Relationships.csproj │ ├── EntityRelationshipExtensions.cs │ ├── Enumerators.cs │ ├── InRelationship.cs │ ├── Relationship.cs │ └── WorldRelationshipExtensions.cs ├── Arch.Relationships.Tests/ │ ├── Arch.Relationships.Tests.csproj │ ├── RelationshipTest.cs │ └── Usings.cs ├── Arch.System/ │ ├── Arch.System.csproj │ ├── Attributes.cs │ ├── Systems.cs │ └── Templates/ │ ├── GenericAttributes.cs │ ├── GenericAttributes.tt │ └── Helpers.ttinclude ├── Arch.System.SourceGenerator/ │ ├── Arch.System.SourceGenerator.csproj │ ├── Extensions/ │ │ ├── CommonUtils.cs │ │ └── MethodSymbolExtensions.cs │ ├── Model.cs │ ├── QueryUtils.cs │ └── SourceGenerator.cs ├── Arch.System.SourceGenerator.SnapshotTests/ │ ├── .editorconfig │ ├── Arch.System.SourceGenerator.SnapshotTests.csproj │ └── SnapshotTest.cs ├── Arch.System.SourceGenerator.Tests/ │ ├── .editorconfig │ ├── .gitignore │ ├── Arch.System.SourceGenerator.Tests.csproj │ ├── AttributeQueryCompilation/ │ │ ├── AttributeQuerySystem.cs │ │ └── ExpectedGeneration/ │ │ ├── AttributeQuerySystem.IncrementA(Entity).g.cs │ │ ├── AttributeQuerySystem.IncrementAAndB(Entity).g.cs │ │ ├── AttributeQuerySystem.IncrementAAndBExclusive(Entity).g.cs │ │ ├── AttributeQuerySystem.IncrementANotB(Entity).g.cs │ │ ├── AttributeQuerySystem.IncrementAOrB(Entity).g.cs │ │ └── AttributeQuerySystem.IncrementAOrBNotC(Entity).g.cs │ ├── BasicCompilation/ │ │ ├── BasicSystem.cs │ │ └── ExpectedGeneration/ │ │ ├── BasicSystem.Basic(IntComponentA).g.cs │ │ └── BasicSystem.BasicStatic(IntComponentA).g.cs │ ├── DataParamCompilation/ │ │ ├── DataParamSystem.cs │ │ └── ExpectedGeneration/ │ │ ├── DataParamSystem.AssignEntityDataParamWithEntityRight(in Entity, in IntComponentA, ref Entity).g.cs │ │ ├── DataParamSystem.CountANoParams(ref int).g.cs │ │ ├── DataParamSystem.CountATwiceWithParams(ref int, in IntComponentA, ref int, in IntComponentB).g.cs │ │ ├── DataParamSystem.CountAWithEntityAndParamLeft(ref int, in IntComponentA, in Entity).g.cs │ │ ├── DataParamSystem.CountAWithEntityAndParamRight(in Entity, in IntComponentA, ref int).g.cs │ │ ├── DataParamSystem.CountAWithEntityLeft(ref int, in Entity).g.cs │ │ ├── DataParamSystem.CountAWithEntityRight(in Entity, ref int).g.cs │ │ ├── DataParamSystem.CountAWithParamsLeft(ref int, in IntComponentA).g.cs │ │ ├── DataParamSystem.CountAWithParamsMiddle(in IntComponentA, ref int, in IntComponentB).g.cs │ │ └── DataParamSystem.CountAWithParamsRight(in IntComponentA, ref int).g.cs │ ├── GeneratedUpdateCompilation/ │ │ ├── ExpectedGeneration/ │ │ │ ├── GeneratedUpdateSystem.AutoRunA().g.cs │ │ │ ├── GeneratedUpdateSystem.AutoRunB().g.cs │ │ │ └── GeneratedUpdateSystem.g.cs │ │ └── GeneratedUpdateSystem.cs │ ├── ParamQueryCompilation/ │ │ ├── ExpectedGeneration/ │ │ │ ├── ParamQuerySystem.IncrementA(ref IntComponentA).g.cs │ │ │ ├── ParamQuerySystem.IncrementAAndB(ref IntComponentA, ref IntComponentB).g.cs │ │ │ ├── ParamQuerySystem.IncrementANotC(ref IntComponentA).g.cs │ │ │ └── ParamQuerySystem.IncrementOnlyAWithB(ref IntComponentA, in IntComponentB).g.cs │ │ └── ParamQuerySystem.cs │ ├── Shared/ │ │ ├── BaseTestSystem.cs │ │ └── IntComponents.cs │ └── SystemsTest.cs ├── Directory.Build.targets ├── LICENSE.MD ├── README.md └── scripts/ └── UnityPublish.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ * text eol=crlf ================================================ FILE: .github/workflows/main.yml ================================================ name: CI on: push: branches: [master] pull_request: branches: [master] jobs: build: name: Test runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: build: [linux-debug, linux-release] include: - build: linux-debug os: ubuntu-latest config: debug - build: linux-release os: ubuntu-latest config: release steps: - uses: actions/checkout@v3 - uses: actions/setup-dotnet@v3 with: dotnet-version: | 6.0.x 7.0.x 8.0.x # workaround for actions/setup-dotnet#155 - name: Clear package cache run: dotnet clean Arch.Extended.sln && dotnet nuget locals all --clear - name: Restore packages run: dotnet restore Arch.Extended.sln - name: Build run: dotnet build Arch.Extended.sln -c ${{ matrix.config }} --no-restore -warnaserror - name: Test run: dotnet test Arch.Extended.sln -c ${{ matrix.config }} -l "console;verbosity=detailed" ================================================ FILE: .gitignore ================================================ dist bin/ obj/ /packages/ riderModule.iml /_ReSharper.Caches/ # Common IntelliJ Platform excludes # User specific **/.idea/**/workspace.xml **/.idea/**/tasks.xml **/.idea/shelf/* **/.idea/dictionaries **/.idea/httpRequests/ # Sensitive or high-churn files **/.idea/**/dataSources/ **/.idea/**/dataSources.ids **/.idea/**/dataSources.xml **/.idea/**/dataSources.local.xml **/.idea/**/sqlDataSources.xml **/.idea/**/dynamic.xml # Rider # Rider auto-generates .iml files, and contentModel.xml .idea **/.idea/**/*.iml **/.idea/**/contentModel.xml **/.idea/**/modules.xml *.suo *.user .vs/ [Bb]in/ [Oo]bj/ _UpgradeReport_Files/ [Pp]ackages/ Thumbs.db Desktop.ini .DS_Store Footer © 2022 GitHub, Inc. Footer navigation Terms ================================================ FILE: Arch.AOT.SourceGenerator/Arch.AOT.SourceGenerator.csproj ================================================ netstandard2.0 enable enable latest true bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml true snupkg Arch.AOT.SourceGenerator Arch.AOT.SourceGenerator 1.0.1 genaray Apache-2.0 A source generator for arch to support AOT. Updated to Arch 1.7 and up. c#;.net;.net6;.net7;ecs;game;entity;gamedev; game-development; game-engine; entity-component-system; arch; https://github.com/genaray/Arch.Extended https://github.com/genaray/Arch.Extended.git git true 11 true Apache2.0 en-US ================================================ FILE: Arch.AOT.SourceGenerator/ComponentType.cs ================================================ namespace Arch.AOT.SourceGenerator; /// /// The struct /// represents an Component (Their type with meta data) for use in the generated code. /// public struct ComponentType { /// /// The type name of the component. /// public string TypeName { get; } /// /// If the component has zero fields. /// public bool IsZeroSize { get; } /// /// If the component is a value type. /// public bool IsValueType { get; } /// /// Creates a new instance of the . /// /// The type name. /// If its zero sized. /// If its a value type. public ComponentType(string typeName, bool isZeroSize, bool isValueType) { TypeName = typeName; IsZeroSize = isZeroSize; IsValueType = isValueType; } } ================================================ FILE: Arch.AOT.SourceGenerator/Extensions/StringBuilderExtensions.cs ================================================ using System.Text; namespace Arch.AOT.SourceGenerator.Extensions; /// /// The class /// adds code-generating methods to the string-builder for outsourcing them. /// public static class StringBuilderExtensions { /// /// Appends the component types to the string builder in the form of a generated class. /// /// The target string builder. /// The types to append. /// public static StringBuilder AppendComponentTypes(this StringBuilder sb, IList componentTypes) { // Lists the component registration commands line by line. var components = new StringBuilder(); foreach (var type in componentTypes) { var componentType = type; components.AppendComponentType(ref componentType); } sb.AppendLine( $$""" using System.Runtime.CompilerServices; using Arch.Core.Utils; namespace Arch.AOT.SourceGenerator { internal static class GeneratedComponentRegistry { [ModuleInitializer] public static void Initialize() { {{components}} } } } """ ); return sb; } /// /// Appends a single component type to the string builder as a new line. /// /// The string builder. /// The component type to add. /// public static StringBuilder AppendComponentType(this StringBuilder sb, ref ComponentType componentType) { //var size = componentType.IsValueType ? $"Unsafe.SizeOf<{componentType.TypeName}>()" : "IntPtr.Size"; //sb.AppendLine($"ComponentRegistry.Add(typeof({componentType.TypeName}), new ComponentType(ComponentRegistry.Size + 1, {size}));"); sb.AppendLine($"ArrayRegistry.Add<{componentType.TypeName}>();"); return sb; } } ================================================ FILE: Arch.AOT.SourceGenerator/SourceGenerator.cs ================================================ using System.Collections.Immutable; using System.Text; using Arch.AOT.SourceGenerator.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace Arch.AOT.SourceGenerator; /// /// Incremental generator that generates a class that adds all components to the ComponentRegistry. /// [Generator(LanguageNames.CSharp)] public sealed class ComponentRegistryGenerator : IIncrementalGenerator { /// /// A of annotated components (their types) found via the source-gen. /// private readonly List _componentTypes = new(); /// /// The attribute to mark components with in order to be found by this source-gen. /// private const string AttributeTemplate = """ using System; namespace Arch.AOT.SourceGenerator { [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public sealed class ComponentAttribute : Attribute { } } """; /// public void Initialize(IncrementalGeneratorInitializationContext context) { // Register the attribute. context.RegisterPostInitializationOutput(initializationContext => { initializationContext.AddSource("Components.Attributes.g.cs", SourceText.From(AttributeTemplate, Encoding.UTF8)); }); var provider = context.SyntaxProvider.CreateSyntaxProvider( ShouldTypeBeRegistered, GetMemberDeclarationsForSourceGen).Where(t => t.attributeFound).Select((t, _) => t.Item1 ); context.RegisterSourceOutput( context.CompilationProvider.Combine(provider.Collect()), (productionContext, tuple) => GenerateCode(productionContext, tuple.Left, tuple.Right) ); } /// /// Determines if a node should be considered for code generation. /// /// /// /// private static bool ShouldTypeBeRegistered(SyntaxNode node, CancellationToken cancellationToken) { if (node is not TypeDeclarationSyntax typeDeclarationSyntax) { return false; } return typeDeclarationSyntax.AttributeLists.Count != 0; } /// /// Make sure the type is annotated with the Component attribute. /// /// /// /// private static (TypeDeclarationSyntax, bool attributeFound) GetMemberDeclarationsForSourceGen(GeneratorSyntaxContext context, CancellationToken cancellationToken) { var typeDeclarationSyntax = (TypeDeclarationSyntax) context.Node; // Stop here if we can't get the type symbol for some reason. if (ModelExtensions.GetDeclaredSymbol(context.SemanticModel, typeDeclarationSyntax) is not ITypeSymbol symbol) { return (typeDeclarationSyntax, false); } // Go through all the attributes. foreach (var attributeData in symbol.GetAttributes()) { if (attributeData.AttributeClass is null) { continue; } // If the attribute is the Component attribute, we can stop here and return true. if (string.Equals(attributeData.AttributeClass.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), "global::Arch.AOT.SourceGenerator.ComponentAttribute", StringComparison.Ordinal)) { return (typeDeclarationSyntax, true); } } // No attribute found, return false. return (typeDeclarationSyntax, false); } private void GenerateCode(SourceProductionContext productionContext, Compilation compilation, ImmutableArray typeList) { var sb = new StringBuilder(); _componentTypes.Clear(); foreach (var type in typeList) { // Get the symbol for the type. var symbol = ModelExtensions.GetDeclaredSymbol(compilation.GetSemanticModel(type.SyntaxTree), type); // If the symbol is not a type symbol, we can't do anything with it. if (symbol is not ITypeSymbol typeSymbol) { continue; } // Check if there are any fields in the type. var hasZeroFields = true; foreach (var member in typeSymbol.GetMembers()) { if (member is not IFieldSymbol) continue; hasZeroFields = false; break; } var typeName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); _componentTypes.Add(new ComponentType(typeName, hasZeroFields, typeSymbol.IsValueType)); } sb.AppendComponentTypes(_componentTypes); productionContext.AddSource("GeneratedComponentRegistry.g.cs",CSharpSyntaxTree.ParseText(sb.ToString()).GetRoot().NormalizeWhitespace().ToFullString()); } } ================================================ FILE: Arch.EventBus/Arch.EventBus.csproj ================================================ enable enable netstandard2.0 11 Arch.Bus true snupkg bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml Arch.EventBus Arch.EventBus 1.0.2 genaray Apache-2.0 A basic EventBus using source generation. Fixed some issues with source generation. c#;.net;.net6;.net7;ecs;game;entity;gamedev; game-development; game-engine; entity-component-system; arch; https://github.com/genaray/Arch.Extended https://github.com/genaray/Arch.Extended.git git true 11 true Apache2.0 en-US ================================================ FILE: Arch.EventBus/EventBus.cs ================================================ using System.Text; using Microsoft.CodeAnalysis; namespace Arch.Bus; /// /// The EventBus model /// public struct EventBus { /// /// The namespace. /// public string Namespace { get; set; } /// /// The s of the "redirecting" the event. /// public IList Methods; } /// /// A method inside the eventbus redirecting the event towards the receivers. /// public struct Method { /// /// The this method accepts as a param. /// public RefKind RefKind; /// /// The Event type as a that is being passed to the method and being redirected. /// public ITypeSymbol EventType; /// /// A list of methods which this redirects the event to. /// public IList EventReceivingMethods; } /// /// The struct /// represents a method that receives the event (and is marked by the event tag) with its order. /// public struct ReceivingMethod { /// /// If the receiving method is static. /// If not, we are targeting instances and need to handle them differently during generation. /// public bool Static; /// /// The method symbol of the static event receiver which should be called. /// public IMethodSymbol MethodSymbol; /// /// Its order. /// public int Order; } /// /// EventBusExtensions /// public static class EventBusExtensions { /// /// Convert a to its code string equivalent. /// /// The . /// The code string equivalent. public static string RefKindToString(RefKind refKind) { switch (refKind) { case RefKind.None: return ""; case RefKind.Ref: return "ref"; case RefKind.In: return "in"; case RefKind.Out: return "out"; } return null!; } /// /// Appends all methods redirecting events. /// /// The . /// The containing the s redirecting the event and calling the methods. /// public static StringBuilder AppendEventMethods(this StringBuilder sb, IList callMethods) { foreach (var eventCallMethod in callMethods) { sb.AppendEventMethod(eventCallMethod); } return sb; } /// /// Appends all methods redirecting events. /// /// The . /// The containing the s redirecting the event and calling the methods. /// public static StringBuilder AppendEventMethod(this StringBuilder sb, Method callMethod) { var callMethodsInOrder = new StringBuilder().MethodCalls(callMethod); var instanceReceiverLists = new StringBuilder().InstanceReceiverLists(callMethod); var template = $$""" {{instanceReceiverLists}} [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Send({{RefKindToString(callMethod.RefKind)}} {{callMethod.EventType.ToDisplayString()}} {{callMethod.EventType.Name.ToLower()}}){ {{callMethodsInOrder}} } """; sb.AppendLine(template); return sb; } /// /// Appends calls to all event receiving method. /// SomeMethod(...); OtherMethod(...); ... /// /// The . /// The the event call which methods will be called after one another. /// public static StringBuilder MethodCalls(this StringBuilder sb, Method callMethod) { // Loop over every found method receiver foreach (var eventReceivingMethod in callMethod.EventReceivingMethods) { var containingSymbol = eventReceivingMethod.MethodSymbol.ContainingSymbol; var methodName = eventReceivingMethod.MethodSymbol.Name; var passEvent = $"{RefKindToString(callMethod.RefKind)} {callMethod.EventType.Name.ToLower()}"; // Remove weird chars to also support value tuples flawlessly, otherwhise they are listed like (World world, int int) in code which destroys everything var eventType = callMethod.EventType.ToString(); eventType = eventType.Replace("(","").Replace(")","").Replace(".","_").Replace(",","_").Replace(" ",""); // If static, call directly... if non static, loop over the instances for this event and call them one by one. if (eventReceivingMethod.Static) { sb.AppendLine($"{containingSymbol}.{methodName}({passEvent});"); } else { var instanceList = $"{containingSymbol.Name}_{methodName}_{eventType}"; var template = $$""" for(var index = 0; index < {{instanceList}}.Count; index++) { {{instanceList}}[index].{{methodName}}({{passEvent}}); } """; sb.AppendLine(template); } } return sb; } /// /// Appends lists for a certain containing one list for each instance (non static) receiving a method. /// List<SomeInstance> SomeInstance_OnSomeEvent_EventType; ... /// /// The . /// The the event call which methods will be called after one another. /// public static StringBuilder InstanceReceiverLists(this StringBuilder sb, Method callMethod) { foreach (var eventReceivingMethod in callMethod.EventReceivingMethods) { var containingSymbol = eventReceivingMethod.MethodSymbol.ContainingSymbol; var methodName = eventReceivingMethod.MethodSymbol.Name; // Remove weird chars to also support value tuples flawlessly, otherwhise they are listed like (World world, int int) in code which destroys everything var eventType = callMethod.EventType.ToString(); eventType = eventType.Replace("(","").Replace(")","").Replace(".","_").Replace(",","_").Replace(" ",""); if (eventReceivingMethod.Static) { continue; } sb.AppendLine($"public static List<{containingSymbol}> {containingSymbol.Name}_{methodName}_{eventType} = new List<{containingSymbol}>(128);"); } return sb; } /// /// Appends a and generates it. /// /// The . /// The itself, used to generate the EventBus in code. /// public static StringBuilder AppendEventBus(this StringBuilder sb, ref EventBus eventBus) { var callerMethods = new StringBuilder().AppendEventMethods(eventBus.Methods); var template = $$""" using System.Runtime.CompilerServices; using System.Collections.Generic; namespace {{eventBus.Namespace}}{ public partial class EventBus{ {{callerMethods}} } } """; return sb.Append(template); } } ================================================ FILE: Arch.EventBus/Hooks.cs ================================================ using System.Text; using Microsoft.CodeAnalysis; namespace Arch.Bus; /// /// Hooks. /// public struct Hooks { /// /// Instances. /// public List Instances; } /// /// Class hooks. /// public struct ClassHooks { /// /// Partial class. /// public ITypeSymbol PartialClass; /// /// Event hooks. /// public IList EventHooks; } /// /// The struct /// represents a hook for an event from the eventbus inside an class instance. /// public struct EventHook { /// /// The event type. /// public ITypeSymbol EventType; /// /// The method symbol of the static event receiver which should be called. /// public IMethodSymbol MethodSymbol; } /// /// Hook extensions. /// public static class HookExtensions { /// /// Appends add operations for a set of to hook the local class instance into the EventBus instance lists for receiving events. /// EventBus.SomeClass_SomeEvent_SomeEvent.Add(this); ... /// /// The . /// The of that will be hooked in to receive instance events. /// public static StringBuilder Hook(this StringBuilder sb, IList receivingMethods) { foreach (var eventReceivingMethod in receivingMethods) { var containingSymbol = eventReceivingMethod.MethodSymbol.ContainingSymbol; var methodName = eventReceivingMethod.MethodSymbol.Name; // Remove weird chars to also support value tuples flawlessly, otherwhise they are listed like (World world, int int) in code which destroys everything var eventType = eventReceivingMethod.EventType.ToString(); eventType = eventType.Replace("(","").Replace(")","").Replace(".","_").Replace(",","_").Replace(" ",""); sb.AppendLine($"EventBus.{containingSymbol.Name}_{methodName}_{eventType}.Add(this);"); } return sb; } /// /// Appends remove operations for a set of to unhook the local class instance from the EventBus instance lists for receiving events. /// EventBus.SomeClass_SomeEvent_SomeEvent.Remove(this); ... /// /// The . /// The of that will be hooked in to receive instance events. /// public static StringBuilder Unhook(this StringBuilder sb, IList receivingMethods) { foreach (var eventReceivingMethod in receivingMethods) { var containingSymbol = eventReceivingMethod.MethodSymbol.ContainingSymbol; var methodName = eventReceivingMethod.MethodSymbol.Name; // Remove weird chars to also support value tuples flawlessly, otherwhise they are listed like (World world, int int) in code which destroys everything var eventType = eventReceivingMethod.EventType.ToString(); eventType = eventType.Replace("(","").Replace(")","").Replace(".","_").Replace(",","_").Replace(" ",""); sb.AppendLine($"EventBus.{containingSymbol.Name}_{methodName}_{eventType}.Remove(this);"); } return sb; } /// /// Appends a of and generates proper hook and unhook methods for their partial class instances. /// /// The . /// The of itself, used to generate the hooks in code. /// public static StringBuilder AppendHookList(this StringBuilder sb, List hooks) { // Loop over all hooks to create the hook and unhook functions. foreach (var hook in hooks) { var hookIntoEventbus = new StringBuilder().Hook(hook.EventHooks); var unhookFromEventBus = new StringBuilder().Unhook(hook.EventHooks); var template = $$""" namespace {{hook.PartialClass.ContainingNamespace}}{ public partial class {{hook.PartialClass.Name}}{ [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Hook() { {{hookIntoEventbus}} } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Unhook() { {{unhookFromEventBus}} } } } """; sb.AppendLine(template); } return sb; } /// /// Appends a and generates it. /// /// The . /// The itself, used to generate the hooks in code. /// public static StringBuilder AppendHooks(this StringBuilder sb, ref Hooks hooks) { var callerMethods = new StringBuilder().AppendHookList(hooks.Instances); var template = $$""" using System.Runtime.CompilerServices; using Arch.Bus; {{callerMethods}} """; return sb.Append(template); } } ================================================ FILE: Arch.EventBus/MethodSymbolExtensions.cs ================================================ using Microsoft.CodeAnalysis; namespace Arch.Bus; /// /// Method symbol extensions. /// public static class MethodSymbolExtensions { /// /// Searches attributes of a and returns the first one found. /// /// The instance. /// The attributes name. /// The attribute wrapped in an . public static AttributeData GetAttributeData(this IMethodSymbol ms, string name) { foreach (var attribute in ms.GetAttributes()) { var classSymbol = attribute.AttributeClass; if(!classSymbol!.Name.Contains(name)) continue; return attribute; } return default!; } /// /// Gets all the types of a as s and adds them to a list. /// If the attribute is generic it will add the generic parameters, if its non generic it will add the non generic types from the constructor. /// /// The . /// The where the found s are added to. public static void GetAttributeTypes(this AttributeData? data, List array) { if (data is not null && data.AttributeClass!.IsGenericType) { array.AddRange(data.AttributeClass.TypeArguments); } else if (data is not null && !data.AttributeClass!.IsGenericType) { var constructorArguments = data.ConstructorArguments[0].Values; var constructorArgumentsTypes = constructorArguments.Select(constant => constant.Value as ITypeSymbol).ToList(); array.AddRange(constructorArgumentsTypes!); } } } ================================================ FILE: Arch.EventBus/SourceGenerator.cs ================================================ using System.Collections.Immutable; using System.Diagnostics; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace Arch.Bus; /// /// Query generator. /// [Generator] public class QueryGenerator : IIncrementalGenerator { private static EventBus _eventBus; private static Hooks _hooks; private static Dictionary)> _eventTypeToReceivingMethods = null!; private static Dictionary> _containingTypeToReceivingMethods = null!; /// public void Initialize(IncrementalGeneratorInitializationContext context) { //if (!Debugger.IsAttached) Debugger.Launch(); // Register the generic attributes var attributes = $$""" namespace Arch.Bus { /// /// Marks a method to receive a certain event. /// [global::System.AttributeUsage(global::System.AttributeTargets.Method)] public class EventAttribute : global::System.Attribute { public EventAttribute(int order = 0) { Order = order; } /// /// The order of this event. /// public int Order { get; } } } """; context.RegisterPostInitializationOutput(ctx => ctx.AddSource("Attributes.g.cs", SourceText.From(attributes, Encoding.UTF8))); // Do a simple filter for methods marked with update IncrementalValuesProvider methodDeclarations = context.SyntaxProvider.CreateSyntaxProvider( static (s, _) => s is MethodDeclarationSyntax { AttributeLists.Count: > 0 }, static (ctx, _) => GetMethodSymbolIfAttributeof(ctx, "Arch.Bus.EventAttribute") ).Where(static m => m is not null)!; // filter out attributed methods that we don't care about // Combine the selected enums with the `Compilation` IncrementalValueProvider<(Compilation, ImmutableArray)> compilationAndMethods = context.CompilationProvider.Combine(methodDeclarations.WithComparer(Comparer.Instance).Collect()); context.RegisterSourceOutput(compilationAndMethods, static (spc, source) => Generate(source.Item1, source.Item2, spc)); } /// /// Returns a if its annotated with an attribute of . /// /// Its . /// The attributes name. /// private static MethodDeclarationSyntax? GetMethodSymbolIfAttributeof(GeneratorSyntaxContext context, string name) { // we know the node is a EnumDeclarationSyntax thanks to IsSyntaxTargetForGeneration var methodDeclarationSyntax = (MethodDeclarationSyntax)context.Node; // loop through all the attributes on the method foreach (var attributeListSyntax in methodDeclarationSyntax.AttributeLists) { foreach (var attributeSyntax in attributeListSyntax.Attributes) { if (ModelExtensions.GetSymbolInfo(context.SemanticModel, attributeSyntax).Symbol is not IMethodSymbol attributeSymbol) continue; var attributeContainingTypeSymbol = attributeSymbol.ContainingType; var fullName = attributeContainingTypeSymbol.ToDisplayString(); // Is the attribute the [EnumExtensions] attribute? if (fullName != name) continue; return methodDeclarationSyntax; } } // we didn't find the attribute we were looking for return null; } /// /// Maps the to its for organisation. /// /// private static void MapMethodToEventType(IMethodSymbol methodSymbol) { var eventType = methodSymbol.Parameters[0]; var param = methodSymbol.GetAttributes()[0].ConstructorArguments[0]; var receivingMethod = new ReceivingMethod{ Static = methodSymbol.IsStatic, MethodSymbol = methodSymbol, Order = (int)param.Value! }; // Either append or create a new receiving method with a new list. if (_eventTypeToReceivingMethods.TryGetValue(eventType.Type, out var tuple)) { tuple.Item2.Add(receivingMethod); } else { tuple.Item1 = eventType.RefKind; tuple.Item2 = new List{receivingMethod}; _eventTypeToReceivingMethods.Add(eventType.Type, tuple); } } /// /// Maps the to its for organisation. /// /// private static void MapMethodToContainingType(IMethodSymbol methodSymbol) { var eventType = methodSymbol.Parameters[0]; var receivingMethod = new EventHook{ EventType = eventType.Type, MethodSymbol = methodSymbol, }; // Either append or create a new receiving method with a new list. if (_containingTypeToReceivingMethods.TryGetValue(methodSymbol.ContainingType, out var tuple)) { tuple.Add(receivingMethod); } else { var list = new List{receivingMethod}; _containingTypeToReceivingMethods.Add(methodSymbol.ContainingType, list); } } /// /// Prepares the by convertings the to the eventbus model. /// private static void PrepareEventBus() { // Translate mapping to the model foreach (var kvp in _eventTypeToReceivingMethods) { var eventCallMethod = new Method { RefKind = kvp.Value.Item1, EventType = kvp.Key, EventReceivingMethods = kvp.Value.Item2 }; eventCallMethod.EventReceivingMethods = eventCallMethod.EventReceivingMethods.OrderBy(method => method.Order).ToList(); _eventBus.Methods.Add(eventCallMethod); } } /// /// Prepares the by convertings the to the hooks model. /// private static void PrepareHooks() { // Translate mapping to the model foreach (var kvp in _containingTypeToReceivingMethods) { // Skip static classes since they need no hooks if (kvp.Key.IsStatic) { continue; } var hook = new ClassHooks { PartialClass = kvp.Key, EventHooks = kvp.Value }; _hooks.Instances.Add(hook); } } /// /// Generates queries and partial classes for the found marked methods. /// /// The . /// The array, the methods which we will generate queries and classes for. /// The . private static void Generate(Compilation compilation, ImmutableArray methods, SourceProductionContext context) { if (methods.IsDefaultOrEmpty) return; // Init _eventBus.Namespace = "Arch.Bus"; _eventBus.Methods = new List(512); _eventTypeToReceivingMethods = new Dictionary)>(512, SymbolEqualityComparer.Default); _hooks.Instances = new List(512); _containingTypeToReceivingMethods = new Dictionary>(512, SymbolEqualityComparer.Default); // Generate Query methods and map them to their classes foreach (var methodSyntax in methods) { var semanticModel = compilation.GetSemanticModel(methodSyntax.SyntaxTree); var methodSymbol = ModelExtensions.GetDeclaredSymbol(semanticModel, methodSyntax) as IMethodSymbol; MapMethodToEventType(methodSymbol!); MapMethodToContainingType(methodSymbol!); } PrepareEventBus(); PrepareHooks(); var template = new StringBuilder().AppendEventBus(ref _eventBus); var hooks = new StringBuilder().AppendHooks(ref _hooks); context.AddSource($"EventBus.g.cs", CSharpSyntaxTree.ParseText(template.ToString()).GetRoot().NormalizeWhitespace().ToFullString()); context.AddSource($"Hooks.g.cs", CSharpSyntaxTree.ParseText(hooks.ToString()).GetRoot().NormalizeWhitespace().ToFullString()); } /// /// Compares s to remove duplicates. /// class Comparer : IEqualityComparer { public static readonly Comparer Instance = new Comparer(); public bool Equals(MethodDeclarationSyntax x, MethodDeclarationSyntax y) { return x.Equals(y); } public int GetHashCode(MethodDeclarationSyntax obj) { return obj.GetHashCode(); } } } ================================================ FILE: Arch.Extended.Sample/Arch.Extended.Sample.csproj ================================================ Exe net8.0 enable enable Arch.Extended 12 ================================================ FILE: Arch.Extended.Sample/Components.cs ================================================ using System.Runtime.Serialization; using Arch.AOT.SourceGenerator; using Arch.Core; using MessagePack; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Arch.Extended; /// /// The position of an entity. /// public struct Position { /// /// Its position. /// public Vector2 Vector2; /// /// Constructs a new instance. /// /// The x position. /// The y position. public Position(float x, float y) { Vector2 = new Vector2(x, y); } /// /// Constructs a new instance. /// Mostly required for . /// /// The , the position. public Position(Vector2 vector2) { Vector2 = vector2; } }; /// /// The velocity of an entity. /// public struct Velocity { /// /// Its velocity. /// public Vector2 Vector2; /// /// Constructs a new instance. /// /// The x velocity. /// The y velocity. public Velocity(float x, float y) { Vector2 = new Vector2(x, y); } /// /// Constructs a new instance. /// Mostly required for . /// /// The , the velocity. public Velocity(Vector2 vector2) { Vector2 = vector2; } } /// /// The sprite/texture of an entity. /// public struct Sprite { /// /// The used. /// [IgnoreDataMember] public Texture2D Texture2D; /// /// The id of the texture, for serialisation. /// public byte TextureId; /// /// The used. /// public Color Color; /// /// Constructs a new instance. /// /// Its . /// Its . public Sprite(Texture2D texture2D, Color color) { Texture2D = texture2D; Color = color; } } ================================================ FILE: Arch.Extended.Sample/Extensions.cs ================================================ using System.Runtime.CompilerServices; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Arch.Extended; public static class TextureExtensions { /// /// Creates a square texture and returns it. /// /// /// /// public static Texture2D CreateSquareTexture(GraphicsDevice graphicsDevice, int size) { var texture = new Texture2D(graphicsDevice, size, size); var data = new Color[size*size]; for(var i=0; i < data.Length; ++i) data[i] = Color.White; texture.SetData(data); return texture; } } public static class RandomExtensions { /// /// Creates a random inside the and returns it. /// /// The instance. /// A in which a is generated. /// The generated . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 NextVector2(this Random random, in Rectangle rectangle) { return new Vector2(random.Next(rectangle.X, rectangle.X+rectangle.Width), random.Next(rectangle.Y, rectangle.Y+rectangle.Height)); } /// /// Creates a random between two floats. /// /// The instance. /// The minimum value. /// The maximum value. /// A between those to floats. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 NextVector2(this Random random, float min, float max) { return new Vector2((float)(random.NextDouble() * (max - min) + min), (float)(random.NextDouble() * (max - min) + min)); } /// /// Creates a random . /// /// The instance. /// A . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Color NextColor(this Random random) { return new Color(random.Next(0,255),random.Next(0,255),random.Next(0,255)); } } ================================================ FILE: Arch.Extended.Sample/Game.cs ================================================ using System.IO.Compression; using System.Runtime.Serialization; using System.Text; using System.Text.Json.Serialization; using Arch.Core; using Arch.Core.Extensions; using Arch.Bus; using Arch.Core.Extensions.Dangerous; using Arch.Core.Utils; using Arch.Persistence; using Arch.Relationships; using MessagePack; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using NUnit.Framework; using Schedulers; using Utf8Json; using Utf8Json.Formatters; using Utf8Json.Resolvers; namespace Arch.Extended; /// /// The which represents the game and implements all the important monogame features. /// public class Game : Microsoft.Xna.Framework.Game { // The world and a job scheduler for multithreading private World _world = null!; private JobScheduler _jobScheduler = null!; // Our systems processing entities private System.Group _systems = null!; private DrawSystem _drawSystem = null!; // Monogame stuff private GraphicsDeviceManager _graphics; private SpriteBatch _spriteBatch = null!; private Texture2D _texture2D = null!; private Random _random = null!; public Game() { _graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } protected override void Initialize() { // Setup texture and randomness _random = new Random(); _texture2D = TextureExtensions.CreateSquareTexture(GraphicsDevice, 10); base.Initialize(); } protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. _spriteBatch = new SpriteBatch(GraphicsDevice); } protected override void BeginRun() { base.BeginRun(); // Create world & JobScheduler for multithreading _world = World.Create(); _jobScheduler = new( new JobScheduler.Config { ThreadPrefixName = "Arch.Samples", ThreadCount = 0, MaxExpectedConcurrentJobs = 64, StrictAllocationMode = false, } ); World.SharedJobScheduler = _jobScheduler; // Spawn in entities with position, velocity and sprite for (var index = 0; index < 10; index++) { _world.Create( new Position{ Vector2 = _random.NextVector2(GraphicsDevice.Viewport.Bounds) }, new Velocity{ Vector2 = _random.NextVector2(-0.25f,0.25f) }, new Sprite{ Texture2D = _texture2D, Color = _random.NextColor() } ); } //Serializer is not updated yet. // Serialize world and deserialize it back. Just for showcasing the serialization, its actually not necessary. // var archSerializer = new ArchJsonSerializer(new SpriteSerializer{GraphicsDevice = GraphicsDevice}); // var worldJson = archSerializer.ToJson(_world); // _world = archSerializer.FromJson(worldJson); var archSerializer = new ArchBinarySerializer(new SpriteSerializer{GraphicsDevice = GraphicsDevice}); var worldJson = archSerializer.Serialize(_world); _world = archSerializer.Deserialize(worldJson); // Create systems, running in order _systems = new System.Group( "Systems", new MovementSystem(_world, GraphicsDevice.Viewport.Bounds), new ColorSystem(_world), new DebugSystem(_world) ); _drawSystem = new DrawSystem(_world, _spriteBatch); // Draw system must be its own system since monogame differentiates between update and draw. // Initialize systems _systems.Initialize(); _drawSystem.Initialize(); } protected override void Update(GameTime gameTime) { // Exit game on press if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) Exit(); // Forward keyboard state as an event to another handles by using the eventbus var @event = (_world, Keyboard.GetState()); EventBus.Send(ref @event); // Continue entity movement by adding velocity to all if (Keyboard.GetState().IsKeyDown(Keys.I)) { // Query for velocity entities and remove their velocity to make them stop moving. var queryDesc = new QueryDescription().WithNone(); _world.Add(in queryDesc, new Velocity { Vector2 = _random.NextVector2(-0.25f, 0.25f) }); } // Pause entity movement by removing velocity from all if (Keyboard.GetState().IsKeyDown(Keys.O)) { // Query for velocity entities and remove their velocity to make them stop moving. var queryDesc = new QueryDescription().WithAll(); _world.Remove(in queryDesc); } // Add a random amount of new entities if (Keyboard.GetState().IsKeyDown(Keys.K)) { // Bulk create entities var amount = Random.Shared.Next(0, 500); Span entities = stackalloc Entity[amount]; _world.Create(entities,[typeof(Position), typeof(Velocity), typeof(Sprite)], amount); // Set variables foreach (var entity in entities) { #if DEBUG_PUREECS || RELEASE_PUREECS _world.Set(entity, new Position { Vector2 = _random.NextVector2(GraphicsDevice.Viewport.Bounds) }, new Velocity { Vector2 = _random.NextVector2(-0.25f, 0.25f) }, new Sprite { Texture2D = _texture2D, Color = _random.NextColor() } ); #else entity.Set( new Position { Vector2 = _random.NextVector2(GraphicsDevice.Viewport.Bounds) }, new Velocity { Vector2 = _random.NextVector2(-0.25f, 0.25f) }, new Sprite { Texture2D = _texture2D, Color = _random.NextColor() } ); #endif } } // Remove a random amount of new entities if (Keyboard.GetState().IsKeyDown(Keys.L)) { // Find all entities var entities = new Entity[_world.Size]; _world.GetEntities(new QueryDescription(), entities.AsSpan()); // Delete random entities var amount = Random.Shared.Next(0, Math.Min(500, entities.Length)); for (var index = 0; index < amount; index++) { var randomIndex = _random.Next(0, entities.Length); var randomEntity = entities[randomIndex]; #if DEBUG_PUREECS || RELEASE_PUREECS if (_world.IsAlive(randomEntity)) #else if (randomEntity.IsAlive()) #endif { _world.Destroy(randomEntity); } entities[randomIndex] = Entity.Null; } } // Update systems _systems.BeforeUpdate(in gameTime); _systems.Update(in gameTime); _systems.AfterUpdate(in gameTime); base.Update(gameTime); } protected override void Draw(GameTime gameTime) { _graphics.GraphicsDevice.Clear(Color.CornflowerBlue); // Update draw system and draw stuff _drawSystem.BeforeUpdate(in gameTime); _drawSystem.Update(in gameTime); _drawSystem.AfterUpdate(in gameTime); base.Draw(gameTime); } protected override void EndRun() { base.EndRun(); // Destroy world and shutdown the jobscheduler World.Destroy(_world); _jobScheduler.Dispose(); // Dispose systems _systems.Dispose(); } } ================================================ FILE: Arch.Extended.Sample/Program.cs ================================================ // See https://aka.ms/new-console-template for more information using Microsoft.Xna.Framework; using Game = Arch.Extended.Game; // Info : // This sample demonstrates a example usage of arch. // Especially a few different iteration techniques for entity iterations. // Its not a full demonstration of all features. // Hit "delete" to remove velocity from all entities // Disclaimer : // You can spawn in to 1 million entities, then the performance starts dropping. // The bottleneck is not the ECS framework, its actually the rendering ( Monogame Spritebatch ). Console.WriteLine("Sample App starts..."); using var game = new Game(); game.Run(); Environment.Exit(0); ================================================ FILE: Arch.Extended.Sample/Serializer.cs ================================================ using MessagePack; using MessagePack.Formatters; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Utf8Json; namespace Arch.Extended; /// /// The class /// is a for (de)serialising a . /// public class SpriteSerializer : IJsonFormatter, IMessagePackFormatter { /// /// The to create s from. /// public GraphicsDevice GraphicsDevice { get; set; } = null!; public void Serialize(ref JsonWriter writer, Sprite value, IJsonFormatterResolver formatterResolver) { writer.WriteBeginObject(); // Write color writer.WritePropertyName("color"); writer.WriteUInt32(value.Color.PackedValue); writer.WriteValueSeparator(); // Write texture id writer.WritePropertyName("textureId"); writer.WriteUInt16(value.TextureId); writer.WriteEndObject(); } public Sprite Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) { reader.ReadIsBeginObject(); // Read color reader.ReadPropertyName(); var packedColor = reader.ReadUInt32(); reader.ReadIsValueSeparator(); // Read textureid reader.ReadPropertyName(); var textureId = reader.ReadUInt16(); // Create color and texture var color = new Color { PackedValue = packedColor }; var texture = textureId switch { 1 => TextureExtensions.CreateSquareTexture(GraphicsDevice, 10), _ => TextureExtensions.CreateSquareTexture(GraphicsDevice, 10) }; reader.ReadIsEndObject(); return new Sprite(texture, color); } public void Serialize(ref MessagePackWriter writer, Sprite value, MessagePackSerializerOptions options) { // Write color writer.WriteUInt32(value.Color.PackedValue); // Write texture id writer.WriteUInt16(value.TextureId); } public Sprite Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { // Read color var packedColor = reader.ReadUInt32(); // Read textureid var textureId = reader.ReadUInt16(); // Create color and texture var color = new Color { PackedValue = packedColor }; var texture = textureId switch { 1 => TextureExtensions.CreateSquareTexture(GraphicsDevice, 10), _ => TextureExtensions.CreateSquareTexture(GraphicsDevice, 10) }; return new Sprite(texture, color); } } ================================================ FILE: Arch.Extended.Sample/Systems.cs ================================================ using System.Runtime.CompilerServices; using Arch.Bus; using Arch.Core; using Arch.Core.Extensions; using Arch.LowLevel; using Arch.System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; namespace Arch.Extended; /// /// The movement system makes the entities move and bounce properly. /// public partial class MovementSystem : BaseSystem { /// /// A rectangle which specifies the viewport. /// Needed so that the entities do not wander outside the viewport. /// private readonly Rectangle _viewport; /// /// Creates a instance. /// /// The used. /// The games . public MovementSystem(World world, Rectangle viewport) : base(world) { _viewport = viewport;} /// /// Called for each to move it. /// The calling takes place through the source generated method "MoveQuery" on . /// /// The , passed by the "MoveQuery". /// The of the . Passed by the "MoveQuery". /// The of the . Passed by the "MoveQuery". [Query(Parallel = true)] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Move([Data] GameTime time, ref Position pos, ref Velocity vel) { pos.Vector2 += time.ElapsedGameTime.Milliseconds * vel.Vector2; } /// /// Called for each to move it. /// The calling takes place through the source generated method "MoveQuery" on . /// /// The of the . Passed by the "MoveQuery". /// The of the . Passed by the "MoveQuery". [Query] [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Bounce(ref Position pos, ref Velocity vel) { if (pos.Vector2.X >= _viewport.X + _viewport.Width) vel.Vector2.X = -vel.Vector2.X; if (pos.Vector2.Y >= _viewport.Y + _viewport.Height) vel.Vector2.Y = -vel.Vector2.Y; if (pos.Vector2.X <= _viewport.X) vel.Vector2.X = -vel.Vector2.X; if (pos.Vector2.Y <= _viewport.Y) vel.Vector2.Y = -vel.Vector2.Y; } } /// /// Color system, modifies each entities color slowly. /// public partial class ColorSystem : BaseSystem { /// /// Creates an instance. /// /// public ColorSystem(World world) : base(world) {} /// /// Called for each to change its color. /// The calling takes place through the source generated method "ChangeColorQuery" on . /// /// The Passed by the "MoveQuery". /// The of the . Passed by the "ChangeColorQuery". [Query] [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ChangeColor([Data] GameTime time, ref Sprite sprite) { sprite.Color.R += (byte)(time.ElapsedGameTime.TotalMilliseconds * 0.08); sprite.Color.G += (byte)(time.ElapsedGameTime.TotalMilliseconds * 0.08); sprite.Color.B += (byte)(time.ElapsedGameTime.TotalMilliseconds * 0.08); } } /// /// The draw system, handles the drawing of sprites at their position. /// public partial class DrawSystem : BaseSystem { /// /// The used for drawing all s. /// private readonly SpriteBatch _batch; /// /// Creates a instance. /// /// The used. /// The used. public DrawSystem(World world, SpriteBatch batch) : base(world) { _batch = batch;} /// /// Is called before the to start with the recording. /// /// The . public override void BeforeUpdate(in GameTime t) { base.BeforeUpdate(in t); _batch.Begin(); } /// /// Called for each to draw it. /// The calling takes place through the source generated method "DrawQuery" on . /// /// The of the . Passed by the "DrawQuery". /// The of the . Passed by the "DrawQuery". [Query] [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Draw(ref Position position, ref Sprite sprite) { _batch.Draw(sprite.Texture2D, position.Vector2, sprite.Color); // Draw } /// /// Is called after the to stop the recording. /// /// The . public override void AfterUpdate(in GameTime t) { base.AfterUpdate(in t); _batch.End(); } } /// /// The debug system, shows how you can combine source generated queries and default ones. /// public partial class DebugSystem : BaseSystem { /// /// A custom which targets s with and without . /// private readonly QueryDescription _customQuery = new QueryDescription().WithAll().WithNone(); /// /// Creates a new instance. /// /// The used. public DebugSystem(World world) : base(world) { Hook(); } /// /// Implements to call the custom Query and the source generated one. /// /// The . public override void Update(in GameTime t) { World.Query(in _customQuery, entity => Console.WriteLine($"Custom : {entity}")); // Manual query PrintEntitiesWithoutVelocityQuery(World); // Call source generated query, which calls the PrintEntitiesWithoutVelocity method } /// /// Called for each with and without to print it. /// The calling takes place through the source generated method "PrintEntitiesWithoutVelocityQuery" on . /// /// The . Passed by the "PrintEntitiesWithoutVelocityQuery", you can also pass components or data parameters as usual. [Query] [All, None] [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PrintEntitiesWithoutVelocity(in Entity en) { Console.WriteLine(en); } /// /// Receives dispatched keyboard events and if the a key was pressed, prints it. /// Runs before any other event receiver of this kind. /// /// The event listened to. [Event(order: 0)] public void OnKeyboardEventPrint(ref (World world, KeyboardState state) tuple) { if (!tuple.state.IsKeyDown(Keys.A)) return; Console.WriteLine($"Key a was pressed."); } } /// /// A event handler class using the source generated eventbus to intercept and react to events to decouple logic. /// public static partial class EventHandler { /// /// Listens for events with a and to check if the delte key was pressed. /// If thats the case, it will remove the component from all of them. /// /// [Event(order: 1)] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void OnDeleteStopEntities(ref (World world, KeyboardState state) tuple) { if (!tuple.state.IsKeyDown(Keys.Delete)) return; // Query for velocity entities and remove their velocity to make them stop moving. var queryDesc = new QueryDescription().WithAll(); tuple.world.Query(in queryDesc, entity => entity.Remove()); } } ================================================ FILE: Arch.Extended.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.8.34408.163 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arch.System.SourceGenerator", "Arch.System.SourceGenerator\Arch.System.SourceGenerator.csproj", "{CBF9A4D4-5E31-4457-8F7B-EBC01EB86AF2}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arch.Extended.Sample", "Arch.Extended.Sample\Arch.Extended.Sample.csproj", "{ED61027D-1586-4349-A6F8-17665C786678}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arch.System", "Arch.System\Arch.System.csproj", "{39363918-BFC7-43BF-8307-F5581225FB41}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arch.EventBus", "Arch.EventBus\Arch.EventBus.csproj", "{8BBA34D1-7DAB-4410-8762-09F7CC543D1E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arch.LowLevel", "Arch.LowLevel\Arch.LowLevel.csproj", "{00B44305-AB3D-438B-9EB9-8EF1ED2E8394}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arch.LowLevel.Tests", "Arch.LowLevel.Tests\Arch.LowLevel.Tests.csproj", "{9E2F4FCC-1875-49A6-98F1-1D2DA8460984}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arch.Persistence", "Arch.Persistence\Arch.Persistence.csproj", "{4E946A7C-5AB3-4AD6-8B80-D27563CF1D6A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arch.Relationships", "Arch.Relationships\Arch.Relationships.csproj", "{533453B9-957E-401E-B639-DC81DD0265EC}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arch.Relationships.Tests", "Arch.Relationships.Tests\Arch.Relationships.Tests.csproj", "{EDAFD1B8-5FC3-4002-B2AD-5B976B809D1C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arch.System.SourceGenerator.SnapshotTests", "Arch.System.SourceGenerator.SnapshotTests\Arch.System.SourceGenerator.SnapshotTests.csproj", "{156F6B43-B5F7-48FB-BBDB-85B3968BBE56}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arch.System.SourceGenerator.Tests", "Arch.System.SourceGenerator.Tests\Arch.System.SourceGenerator.Tests.csproj", "{FDDA22B1-43DF-48E6-82CE-BC528C8349B9}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arch.AOT.SourceGenerator", "Arch.AOT.SourceGenerator\Arch.AOT.SourceGenerator.csproj", "{454AC3E0-A46A-4620-A377-3A360B8D11A3}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arch.Persistence.Tests", "Arch.Persistence.Tests\Arch.Persistence.Tests.csproj", "{15E851C2-ACD6-4626-A7E4-46AA4CCD3BD5}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {CBF9A4D4-5E31-4457-8F7B-EBC01EB86AF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CBF9A4D4-5E31-4457-8F7B-EBC01EB86AF2}.Debug|Any CPU.Build.0 = Debug|Any CPU {CBF9A4D4-5E31-4457-8F7B-EBC01EB86AF2}.Release|Any CPU.ActiveCfg = Release|Any CPU {CBF9A4D4-5E31-4457-8F7B-EBC01EB86AF2}.Release|Any CPU.Build.0 = Release|Any CPU {ED61027D-1586-4349-A6F8-17665C786678}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ED61027D-1586-4349-A6F8-17665C786678}.Debug|Any CPU.Build.0 = Debug|Any CPU {ED61027D-1586-4349-A6F8-17665C786678}.Release|Any CPU.ActiveCfg = Release|Any CPU {ED61027D-1586-4349-A6F8-17665C786678}.Release|Any CPU.Build.0 = Release|Any CPU {39363918-BFC7-43BF-8307-F5581225FB41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {39363918-BFC7-43BF-8307-F5581225FB41}.Debug|Any CPU.Build.0 = Debug|Any CPU {39363918-BFC7-43BF-8307-F5581225FB41}.Release|Any CPU.ActiveCfg = Release|Any CPU {39363918-BFC7-43BF-8307-F5581225FB41}.Release|Any CPU.Build.0 = Release|Any CPU {8BBA34D1-7DAB-4410-8762-09F7CC543D1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8BBA34D1-7DAB-4410-8762-09F7CC543D1E}.Debug|Any CPU.Build.0 = Debug|Any CPU {8BBA34D1-7DAB-4410-8762-09F7CC543D1E}.Release|Any CPU.ActiveCfg = Release|Any CPU {8BBA34D1-7DAB-4410-8762-09F7CC543D1E}.Release|Any CPU.Build.0 = Release|Any CPU {00B44305-AB3D-438B-9EB9-8EF1ED2E8394}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {00B44305-AB3D-438B-9EB9-8EF1ED2E8394}.Debug|Any CPU.Build.0 = Debug|Any CPU {00B44305-AB3D-438B-9EB9-8EF1ED2E8394}.Release|Any CPU.ActiveCfg = Release|Any CPU {00B44305-AB3D-438B-9EB9-8EF1ED2E8394}.Release|Any CPU.Build.0 = Release|Any CPU {9E2F4FCC-1875-49A6-98F1-1D2DA8460984}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9E2F4FCC-1875-49A6-98F1-1D2DA8460984}.Debug|Any CPU.Build.0 = Debug|Any CPU {9E2F4FCC-1875-49A6-98F1-1D2DA8460984}.Release|Any CPU.ActiveCfg = Release|Any CPU {9E2F4FCC-1875-49A6-98F1-1D2DA8460984}.Release|Any CPU.Build.0 = Release|Any CPU {4E946A7C-5AB3-4AD6-8B80-D27563CF1D6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4E946A7C-5AB3-4AD6-8B80-D27563CF1D6A}.Debug|Any CPU.Build.0 = Debug|Any CPU {4E946A7C-5AB3-4AD6-8B80-D27563CF1D6A}.Release|Any CPU.ActiveCfg = Release|Any CPU {4E946A7C-5AB3-4AD6-8B80-D27563CF1D6A}.Release|Any CPU.Build.0 = Release|Any CPU {533453B9-957E-401E-B639-DC81DD0265EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {533453B9-957E-401E-B639-DC81DD0265EC}.Debug|Any CPU.Build.0 = Debug|Any CPU {533453B9-957E-401E-B639-DC81DD0265EC}.Release|Any CPU.ActiveCfg = Release|Any CPU {533453B9-957E-401E-B639-DC81DD0265EC}.Release|Any CPU.Build.0 = Release|Any CPU {EDAFD1B8-5FC3-4002-B2AD-5B976B809D1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EDAFD1B8-5FC3-4002-B2AD-5B976B809D1C}.Debug|Any CPU.Build.0 = Debug|Any CPU {EDAFD1B8-5FC3-4002-B2AD-5B976B809D1C}.Release|Any CPU.ActiveCfg = Release|Any CPU {EDAFD1B8-5FC3-4002-B2AD-5B976B809D1C}.Release|Any CPU.Build.0 = Release|Any CPU {156F6B43-B5F7-48FB-BBDB-85B3968BBE56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {156F6B43-B5F7-48FB-BBDB-85B3968BBE56}.Debug|Any CPU.Build.0 = Debug|Any CPU {156F6B43-B5F7-48FB-BBDB-85B3968BBE56}.Release|Any CPU.ActiveCfg = Release|Any CPU {156F6B43-B5F7-48FB-BBDB-85B3968BBE56}.Release|Any CPU.Build.0 = Release|Any CPU {FDDA22B1-43DF-48E6-82CE-BC528C8349B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FDDA22B1-43DF-48E6-82CE-BC528C8349B9}.Debug|Any CPU.Build.0 = Debug|Any CPU {FDDA22B1-43DF-48E6-82CE-BC528C8349B9}.Release|Any CPU.ActiveCfg = Release|Any CPU {FDDA22B1-43DF-48E6-82CE-BC528C8349B9}.Release|Any CPU.Build.0 = Release|Any CPU {454AC3E0-A46A-4620-A377-3A360B8D11A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {454AC3E0-A46A-4620-A377-3A360B8D11A3}.Debug|Any CPU.Build.0 = Debug|Any CPU {454AC3E0-A46A-4620-A377-3A360B8D11A3}.Release|Any CPU.ActiveCfg = Release|Any CPU {454AC3E0-A46A-4620-A377-3A360B8D11A3}.Release|Any CPU.Build.0 = Release|Any CPU {15E851C2-ACD6-4626-A7E4-46AA4CCD3BD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {15E851C2-ACD6-4626-A7E4-46AA4CCD3BD5}.Debug|Any CPU.Build.0 = Debug|Any CPU {15E851C2-ACD6-4626-A7E4-46AA4CCD3BD5}.Release|Any CPU.ActiveCfg = Release|Any CPU {15E851C2-ACD6-4626-A7E4-46AA4CCD3BD5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal ================================================ FILE: Arch.Extended.sln.DotSettings ================================================  /usr/local/share/dotnet/sdk/8.0.101/MSBuild.dll 4294967293 ================================================ FILE: Arch.LowLevel/Arch.LowLevel.csproj ================================================ net7.0; net6.0; netstandard2.1 enable enable true true bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml true snupkg Arch.LowLevel Arch.LowLevel 1.1.5 genaray Apache-2.0 LowLevel tools for arch. Refactored JaggedArrays. Increased performance of JaggedArrays. Added Array, a new class that acts as a Wrapper around normal generic Arrays for unsafe operations. Added tests. Bug fixes. c#;.net;.net6;.net7;ecs;game;entity;gamedev; game-development; game-engine; entity-component-system; arch; https://github.com/genaray/Arch.Extended https://github.com/genaray/Arch.Extended.git git true 11 true Apache2.0 en-US true ================================================ FILE: Arch.LowLevel/Array.cs ================================================ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using CommunityToolkit.HighPerformance; namespace Arch.LowLevel; /// /// The struct /// represents an allocated array of managed items which wraps them to acess the items via an unsafe operations. /// /// The managed generic. [DebuggerTypeProxy(typeof(ArrayDebugView<>))] public readonly struct Array { /// /// The static empty . /// internal static Array Empty = new(0); /// /// The pointer, pointing towards the first element of this . /// internal readonly T[] _array; /// /// Creates an instance of the . /// Allocates the array for the passed count of items. /// /// The arrays count or capacity. public Array(int count) { _array = new T[count]; Count = count; } /// /// Creates an instance of the . /// Allocates the array for the passed count of items. /// /// The array used. public Array(T[] array) { this._array = array; Count = array.Length; } /// /// The count of this instance, its capacity. /// public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } /// /// The count of this instance, its capacity. /// public int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Count; } /// /// Returns a reference to an item at a given index. /// /// The index. public ref T this[int i] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _array.DangerousGetReferenceAt(i); } /// /// Converts this instance into a . /// /// A new instance of . [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span AsSpan() { return MemoryMarshal.CreateSpan(ref this[0], Count); } /// /// Creates an instance of a for ref acessing the array content. /// /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public Enumerator GetEnumerator() { return new Enumerator(AsSpan()); } /// /// Checks for equality. /// /// The other . /// True if equal, oterwhise false. public bool Equals(Array other) { return _array == other._array && Count == other.Count; } /// /// Checks for equality. /// /// The other . /// True if equal, oterwhise false. public override bool Equals(object? obj) { return obj is Array other && Equals(other); } /// /// Checks for equality. /// /// The first . /// The second . /// public static bool operator ==(Array left, Array right) { return left.Equals(right); } /// /// Checks for inequality. /// /// The first . /// The second . /// public static bool operator !=(Array left, Array right) { return !left.Equals(right); } /// /// Returns the hash of this . /// /// public override int GetHashCode() { return _array.GetHashCode(); } /// /// Converts an into a generic array. /// /// The instance. /// The array. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator T[](Array instance) { return instance._array; } /// /// Converts an into a generic array. /// /// The instance. /// The array. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator Array(T[] instance) { return new Array(instance); } /// /// Converts this to a string. /// /// The string. [MethodImpl(MethodImplOptions.AggressiveInlining)] public override string ToString() { var items = new StringBuilder(); foreach (ref var item in this) { items.Append($"{item},"); } items.Length--; return $"Array<{typeof(T).Name}>[{Count}]{{{items}}}"; } } /// /// Array. /// public unsafe struct Array { /// /// Returns an empty . /// /// The generic type. /// The empty . public static Array Empty() { return Array.Empty; } /// /// Copies the a part of the to the another . /// /// The source . /// The start index in the source . /// The destination . /// The start index in the destination . /// The length indicating the amount of items being copied. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Copy(ref Array source, int index, ref Array destination, int destinationIndex, int length) { System.Array.Copy(source._array, index, destination._array, destinationIndex, length); } /// /// Fills an with a given value. /// /// The instance. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Fill(ref Array source, in T value = default!) { source.AsSpan().Fill(value); } /// /// Resizes an to a new . /// /// The . /// The new capacity. /// The generic type. /// The new resized . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Array Resize(ref Array source, int newCapacity) { // Create a new array with the new capacity var destination = new Array(newCapacity); // Calculate the number of elements to copy var lengthToCopy = Math.Min(source.Length, newCapacity); Copy(ref source, 0, ref destination, 0, lengthToCopy); return destination; } } /// /// A debug view for the . /// /// The unmanaged type. internal class ArrayDebugView where T : unmanaged { [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Array _entity; [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public T[] Items { get { var items = new T[_entity.Count]; _entity.AsSpan().CopyTo(items); return items; } } public ArrayDebugView(Array entity) => _entity = entity; } ================================================ FILE: Arch.LowLevel/Enumerators.cs ================================================ using System.Collections; using System.Runtime.CompilerServices; namespace Arch.LowLevel; /// /// The is a basic implementation of an enumerator for the >. /// /// public unsafe ref struct Enumerator { private readonly Span _list; private readonly int _count; private int _index; /// /// Creates an instance of the . /// /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Enumerator(Span list) { _list = list; _count = list.Length; _index = -1; } /// /// Returns the current item. /// public readonly ref T Current => ref _list[_index]; /// /// Moves to the next item. /// /// public bool MoveNext() { return unchecked(++_index < _count); } /// /// Resets the enumerator. /// public void Reset() { _index = -1; } } /// /// The is a basic implementation of the interface for the . /// /// public unsafe struct UnsafeIEnumerator : IEnumerator where T : unmanaged { private readonly T* _list; private readonly int _count; private int _index; /// /// Creates an instance of the . /// /// The . /// Count. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal UnsafeIEnumerator(T* list, int count) { _list = list; _count = count; _index = -1; } /// /// Returns the current item. /// public T Current => _list[_index]; /// /// Returns the current item. /// object IEnumerator.Current => _list[_index]; /// /// Disposes this enumerator. /// public void Dispose() { } // nop /// /// Moves to the next item. /// /// public bool MoveNext() { return unchecked(++_index < _count); } /// /// Resets the enumerator. /// public void Reset() { _index = -1; } } /// /// The is a basic implementation of the interface for the . /// /// public unsafe ref struct UnsafeEnumerator where T : unmanaged { private readonly T* _list; private readonly int _count; private int _index; /// /// Creates an instance of the . /// /// The . /// Count. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal UnsafeEnumerator(T* list, int count) { _list = list; _count = count; _index = -1; } /// /// Returns the current item. /// public ref T Current => ref _list[_index]; /// /// Moves to the next item. /// /// public bool MoveNext() { return unchecked(++_index < _count); } /// /// Resets the enumerator. /// public void Reset() { _index = -1; } } /// /// The is a basic implementation of the interface for iterating backwards. /// /// public unsafe struct ReverseIEnumerator : IEnumerator where T : unmanaged { private readonly T* _list; private readonly int _count; private int _index; /// /// Creates an instance of the . /// /// The . /// Count. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ReverseIEnumerator(T* list, int count) { _list = list; _count = count; _index = count+1; } /// /// Returns the current item. /// public T Current => _list[_index-1]; /// /// Returns the current item. /// object IEnumerator.Current => _list[_index-1]; /// /// Disposes this enumerator. /// public void Dispose() { } // nop /// /// Moves to the next item. /// /// public bool MoveNext() { return unchecked(--_index > 0); } /// /// Resets the enumerator. /// public void Reset() { _index = _count+1; } } /// /// The is a basic implementation of the interface for iterating backwards. /// /// public unsafe ref struct UnsafeReverseEnumerator where T : unmanaged { private readonly T* _list; private readonly int _count; private int _index; /// /// Creates an instance of the . /// /// The . /// Count. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal UnsafeReverseEnumerator(T* list, int count) { _list = list; _count = count; _index = count+1; } /// /// Returns the current item. /// public ref T Current => ref _list[_index-1]; /// /// Moves to the next item. /// /// public bool MoveNext() { return unchecked(--_index > 0); } /// /// Resets the enumerator. /// public void Reset() { _index = _count+1; } } ================================================ FILE: Arch.LowLevel/Jagged/JaggedArray.cs ================================================ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using CommunityToolkit.HighPerformance; namespace Arch.LowLevel.Jagged; /// /// The /// contains several methods for math operations. /// internal static class MathExtensions { /// /// This method will round down to the nearest power of 2 number. If the supplied number is a power of 2 it will return it. /// /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static int RoundToPowerOfTwo(int num) { // If num is a power of 2, return it if (num > 0 && (num & (num - 1)) == 0) { return num; } // Find the exponent of the nearest power of 2 (rounded down) var exponent = (int)Math.Floor(Math.Log(num) / Math.Log(2)); // Calculate the nearest power of 2 var result = (int)Math.Pow(2, exponent); return result; } } /// /// The struct /// represents a bucket of the where items are stored /// /// public record struct Bucket { /// /// The items array. /// internal readonly T[] Array = System.Array.Empty(); /// /// Creates an instance of the . /// /// The capacity public Bucket(int capacity) { Array = new T[capacity]; } /// /// The amount of items in this . /// public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] internal set; } /// /// If this is empty. /// public bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Count <= 0; } /// /// Returns a reference to an item at the given index. /// /// The index. public ref T this[int i] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref Array.DangerousGetReferenceAt(i); } /// /// Clears this and sets all values to the . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear(T filler = default!) { System.Array.Fill(Array, filler); } } /// /// The class, /// represents a jagged array with s storing the items. /// /// public class JaggedArray { /// /// The size in items. /// private readonly int _bucketSize; /// /// The size in items - 1. /// private readonly int _bucketSizeMinusOne; /// /// The is always a value the power of 2, therefore we can use a bitshift for the division during the index calculation. /// private readonly int _bucketSizeShift; /// /// The allocated s. /// private Array> _buckets; /// /// The filler, the default value. /// private readonly T _filler; /// /// Creates an instance of the . /// /// The size in bytes. /// The total initial capacity, how many items should fit in. public JaggedArray(int bucketSize, int capacity = 64) { _bucketSize = MathExtensions.RoundToPowerOfTwo(bucketSize); _bucketSizeMinusOne = _bucketSize - 1; _bucketSizeShift = (int)Math.Log(_bucketSize, 2); _buckets = new Array>(capacity/_bucketSize + 1); _filler = default!; // Fill buckets for (var i = 0; i < _buckets.Length; i++) { var bucket = new Bucket(_bucketSize); SetBucket(i, in bucket); bucket.Clear(_filler); } } /// /// Creates an instance of the . /// /// The size in bytes. /// The filler value for all slots, basically a custom default-value. /// The total initial capacity, how many items should fit in. public JaggedArray(int bucketSize, T filler, int capacity = 64) : this(bucketSize, capacity) { _bucketSize = MathExtensions.RoundToPowerOfTwo(bucketSize); _bucketSizeMinusOne = _bucketSize - 1; _bucketSizeShift = (int)Math.Log(_bucketSize, 2); _buckets = new Bucket[capacity/_bucketSize + 1]; _filler = filler; // Fill buckets for (var i = 0; i < _buckets.Length; i++) { var bucket = new Bucket(_bucketSize); SetBucket(i, in bucket); bucket.Clear(_filler); } } /// /// The capacity, the total amount of items. /// public int Capacity => _buckets.Length * _bucketSize; /// /// The length, the buckets inside the . /// public int Buckets => _buckets.Length; /// /// Adds an item to the . /// /// The index. /// The item. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(int index, in T item) { IndexToSlot(index, out var bucketIndex, out var itemIndex); ref var bucket = ref GetBucket(bucketIndex); bucket[itemIndex] = item; bucket.Count++; } /// /// Removes an item from the . /// /// The index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Remove(int index) { IndexToSlot(index, out var bucketIndex, out var itemIndex); ref var bucket = ref GetBucket(bucketIndex); bucket[itemIndex] = _filler; bucket.Count--; } /// /// Trys to get an item from its index. /// /// The index. /// The returned value. /// True if sucessfull, otherwhise false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetValue(int index, out T value) { // If the id is negative if (index < 0 || index >= Capacity) { value = _filler; return false; } IndexToSlot(index, out var bucketIndex, out var itemIndex); ref var item = ref GetBucket(bucketIndex)[itemIndex]; // If the item is the default then the nobody set its value. if (EqualityComparer.Default.Equals(item, _filler)) { value = _filler; return false; } value = item; return true; } /// /// Trys to get an item from its index. /// /// The index. /// True if sucessfull, otherwhise false /// A reference or null reference to the item. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T TryGetValue(int index, out bool @bool) { // If the id is negative if (index < 0 || index >= Capacity) { @bool = false; return ref Unsafe.NullRef(); } IndexToSlot(index, out var bucketIndex, out var itemIndex); ref var item = ref GetBucket(bucketIndex)[itemIndex]; // If the item is the default then the nobody set its value. if (EqualityComparer.Default.Equals(item, _filler)) { @bool = false; return ref Unsafe.NullRef(); } @bool = true; return ref item!; } /// /// Checks if the value at the given index exists. /// /// The index. /// True if it does, false if it does not. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ContainsKey(int index) { if (index < 0 || index > Capacity) { return false; } IndexToSlot(index, out var bucketIndex, out var itemIndex); ref var item = ref GetBucket(bucketIndex)[itemIndex]; // If the item is the default then the nobody set its value. return !EqualityComparer.Default.Equals(item, _filler); } /// /// Ensures the capacity and increases it if necessary. /// /// The new capcity. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void EnsureCapacity(int newCapacity) { if (newCapacity < Capacity) { return; } var length = Buckets; var buckets = newCapacity / _bucketSize + 1; _buckets = Array.Resize(ref _buckets, buckets); for (var i = length; i < _buckets.Length; i++) { var bucket = new Bucket(_bucketSize); SetBucket(i, in bucket); bucket.Clear(_filler); } } /// /// Trims the last few empty buckets to release memory. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void TrimExcess() { // Count how many of the last buckets are empty, to trim them var count = 0; for (var i = _buckets.Length-1; i >= 0; i--) { ref var bucket = ref GetBucket(i); if (!bucket.IsEmpty) { break; } count++; } var buckets = _buckets.Length-count; _buckets = Array.Resize(ref _buckets, buckets); } /// /// Converts the passed id to its inner and outer index ( or slot ) inside the array. /// /// The id. /// The outer index. /// The inner index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void IndexToSlot(int id, out int bucketIndex, out int itemIndex) { Debug.Assert(id >= 0, "Id cannot be negative."); /* Instead of the '%' operator we can use logical '&' operator which is faster. But it requires the bucket size to be a power of 2. */ bucketIndex = id >> _bucketSizeShift; itemIndex = id & _bucketSizeMinusOne; } /// /// Returns the from the at the given index. /// /// The index. /// The at the given index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref Bucket GetBucket(int index) { return ref _buckets[index]; } /// /// Sets the of the at the given index. /// /// The index. /// The to set [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetBucket(int index, in Bucket bucket) { _buckets[index] = bucket; } /// /// Returns a reference to an item at the given index. /// /// The index. public ref T this[int i] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { IndexToSlot(i, out var bucketIndex, out var itemIndex); return ref GetBucket(bucketIndex)[itemIndex]; } } /// /// Clears this and sets all values to the . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { foreach (var bucket in _buckets) { if (bucket.IsEmpty) { continue; } bucket.Clear(_filler); } } } ================================================ FILE: Arch.LowLevel/Jagged/SparseJaggedArray.cs ================================================ using CommunityToolkit.HighPerformance; namespace Arch.LowLevel.Jagged; using System.Diagnostics; using System.Runtime.CompilerServices; /// /// The struct /// represents a bucket of the where items are stored. /// It will not allocate memory upon creation, it stays empty till the first item was added in. /// /// public record struct SparseBucket { /// /// The items array. /// internal T[] Array = System.Array.Empty(); /// /// The filler, the default value. /// private readonly T _filler; /// /// Creates an instance of the . /// /// The total capacity. /// The filler. /// If it should allocate straight forward. public SparseBucket(int capacity, T filler, bool allocate = false) { Capacity = capacity; _filler = filler; if (allocate) { EnsureCapacity(); } } /// /// The total capacity of this . /// public int Capacity { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set; } /// /// The amount of items in this . /// public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] internal set; } /// /// If this is empty. /// public bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Count <= 0; } /// /// Ensures the of this . /// Basically allocated a new array. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void EnsureCapacity() { if (Array != System.Array.Empty()) { return; } Array = new T[Capacity]; Clear(); } /// /// Trims the bucket to an empty one. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void TrimExcess() { if (Count > 0) { return; } Array = System.Array.Empty(); } /// /// Returns a reference to an item at the given index. /// /// The index. public ref T this[int i] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref Array.DangerousGetReferenceAt(i); } /// /// Clears this and sets all values to the . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { System.Array.Fill(Array, _filler); } } /// /// The class, /// represents a jagged array with s storing the items. /// Its buckets will stay empty and not allocate memory till a slot in it is being used. /// /// public class SparseJaggedArray { /// /// The size in items. /// private readonly int _bucketSize; /// /// The size in items - 1. /// private readonly int _bucketSizeMinusOne; /// /// The is always a value the power of 2, therefore we can use a bitshift for the division during the index calculation. /// private readonly int _bucketSizeShift; /// /// The allocated s. /// private Array> _buckets; /// /// The filler, the default value. /// private readonly T _filler; /// /// Creates an instance of the . /// /// The size in bytes. /// The total initial capacity, how many items should fit in. public SparseJaggedArray(int bucketSize, int capacity = 64) { _bucketSize = MathExtensions.RoundToPowerOfTwo(bucketSize); _bucketSizeMinusOne = _bucketSize - 1; _bucketSizeShift = (int)Math.Log(_bucketSize, 2); _buckets = new Array>(capacity/_bucketSize + 1); _filler = default!; // Fill buckets for (var i = 0; i < _buckets.Length; i++) { var bucket = new SparseBucket(_bucketSize, _filler); SetBucket(i, in bucket); bucket.Clear(); } } /// /// Creates an instance of the . /// /// The size in bytes. /// The filler value for all slots, basically a custom default-value. /// The total initial capacity, how many items should fit in. public SparseJaggedArray(int bucketSize, T filler, int capacity = 64) : this(bucketSize, capacity) { _bucketSize = MathExtensions.RoundToPowerOfTwo(bucketSize); _bucketSizeMinusOne = _bucketSize - 1; _bucketSizeShift = (int)Math.Log(_bucketSize, 2); _buckets = new Array>(capacity/_bucketSize + 1); _filler = filler!; // Fill buckets for (var i = 0; i < _buckets.Length; i++) { var bucket = new SparseBucket(_bucketSize, filler); SetBucket(i, in bucket); bucket.Clear(); } } /// /// If true, each bucket will stay empty and will not allocate memory until its actually being used. /// public bool Sparse { get; set; } /// /// The capacity, the total amount of items. /// public int Capacity => _buckets.Length * _bucketSize; /// /// The length, the buckets inside the . /// public int Buckets => _buckets.Length; /// /// Adds an item to the . /// /// The index. /// The item. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(int index, in T item) { IndexToSlot(index, out var bucketIndex, out var itemIndex); ref var bucket = ref GetBucket(bucketIndex); bucket.EnsureCapacity(); bucket[itemIndex] = item; bucket.Count++; } /// /// Removes an item from the . /// /// The index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Remove(int index) { IndexToSlot(index, out var bucketIndex, out var itemIndex); ref var bucket = ref GetBucket(bucketIndex); bucket[itemIndex] = _filler; bucket.Count--; bucket.TrimExcess(); } /// /// Trys to get an item from its index. /// /// The index. /// The returned value. /// True if sucessfull, otherwhise false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetValue(int index, out T value) { // If the id is negative if (index < 0 || index >= Capacity) { value = _filler; return false; } IndexToSlot(index, out var bucketIndex, out var itemIndex); // Bucket empty? return false ref var bucket = ref GetBucket(bucketIndex); if (bucket.IsEmpty) { value = _filler; return false; } // If the item is the default then the nobody set its value. ref var item = ref bucket[itemIndex]; if (EqualityComparer.Default.Equals(item, _filler)) { value = _filler; return false; } value = item; return true; } /// /// Trys to get an item from its index. /// /// The index. /// True if sucessfull, otherwhise false /// A reference or null reference to the item. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T TryGetValue(int index, out bool @bool) { // If the id is negative if (index < 0 || index >= Capacity) { @bool = false; return ref Unsafe.NullRef(); } IndexToSlot(index, out var bucketIndex, out var itemIndex); // Bucket empty? return false ref var bucket = ref GetBucket(bucketIndex); if (bucket.IsEmpty) { @bool = false; return ref Unsafe.NullRef(); } // If the item is the default then the nobody set its value. ref var item = ref bucket[itemIndex]; if (EqualityComparer.Default.Equals(item, _filler)) { @bool = false; return ref Unsafe.NullRef(); } @bool = true; return ref item!; } /// /// Checks if the value at the given index exists. /// /// The index. /// True if it does, false if it does not. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ContainsKey(int index) { if (index < 0 || index >= Capacity) { return false; } IndexToSlot(index, out var bucketIndex, out var itemIndex); // If bucket empty return false ref var bucket = ref GetBucket(bucketIndex); if (bucket.IsEmpty) { return false; } // If the item is the default then the nobody set its value. ref var item = ref bucket[itemIndex]; return !EqualityComparer.Default.Equals(item, _filler); } /// /// Ensures the capacity and increases it if necessary. /// /// The new capcity. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void EnsureCapacity(int newCapacity) { if (newCapacity < Capacity) { return; } var length = Buckets; var buckets = newCapacity / _bucketSize + 1; _buckets = Array.Resize(ref _buckets, buckets); for (var i = length; i < _buckets.Length; i++) { var bucket = new SparseBucket(_bucketSize, _filler); SetBucket(i, bucket); bucket.Clear(); } } /// /// Trims the last few empty buckets to release memory. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void TrimExcess() { // Count how many of the last buckets are empty, to trim them var count = 0; for (var i = _buckets.Length-1; i >= 0; i--) { ref var bucket = ref GetBucket(i); if (!bucket.IsEmpty) { break; } count++; } var buckets = _buckets.Length-count; _buckets = Array.Resize(ref _buckets, buckets); } /// /// Converts the passed id to its inner and outer index ( or slot ) inside the array. /// /// The id. /// The outer index. /// The inner index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void IndexToSlot(int id, out int bucketIndex, out int itemIndex) { Debug.Assert(id >= 0, "Id cannot be negative."); /* Instead of the '%' operator we can use logical '&' operator which is faster. But it requires the bucket size to be a power of 2. */ bucketIndex = id >> _bucketSizeShift; itemIndex = id & _bucketSizeMinusOne; } /// /// Returns the from the at the given index. /// /// The index. /// The at the given index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref SparseBucket GetBucket(int index) { return ref _buckets[index]; } /// /// Sets the of the at the given index. /// /// The index. /// The to set [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetBucket(int index, in SparseBucket bucket) { _buckets[index] = bucket; } /// /// Returns a reference to an item at the given index. /// /// The index. public ref T this[int i] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { IndexToSlot(i, out var bucketIndex, out var itemIndex); return ref GetBucket(bucketIndex)[itemIndex]; } } /// /// Clears this and sets all values to the . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { foreach (var bucket in _buckets) { if (bucket.IsEmpty) { continue; } bucket.Clear(); } } } ================================================ FILE: Arch.LowLevel/Jagged/UnsafeJaggedArray.cs ================================================ using System.Diagnostics; using System.Runtime.CompilerServices; namespace Arch.LowLevel.Jagged; /// /// The struct /// represents a bucket of the where items are stored /// /// public record struct UnsafeBucket : IDisposable where T : unmanaged { /// /// The items array. /// internal UnsafeArray Array = UnsafeArray.Empty(); /// /// Creates an instance of the . /// /// The capacity public UnsafeBucket(int capacity) { Array = new UnsafeArray(capacity); } /// /// The amount of items in this . /// public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] internal set; } /// /// If this is empty. /// public bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Count <= 0; } /// /// Returns a reference to an item at the given index. /// /// The index. public ref T this[int i] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref Array[i]; } /// /// Clears this and sets all values to the . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear(T filler = default) { UnsafeArray.Fill(ref Array, filler); } /// /// Disposes this . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { Array.Dispose(); } } /// /// The class, /// represents a jagged array with s storing the items. /// /// public struct UnsafeJaggedArray : IDisposable where T : unmanaged { /// /// The size in items. /// private readonly int _bucketSize; /// /// The size in items - 1. /// private readonly int _bucketSizeMinusOne; /// /// The is always a value the power of 2, therefore we can use a bitshift for the division during the index calculation. /// private readonly int _bucketSizeShift; /// /// The allocated s. /// private UnsafeArray> _bucketArray; /// /// The filler, the default value. /// private readonly T _filler; /// /// Creates an instance of the . /// /// The size in bytes. /// The total initial capacity, how many items should fit in. public UnsafeJaggedArray(int bucketSize, int capacity = 64) { _bucketSize = MathExtensions.RoundToPowerOfTwo(bucketSize); _bucketSizeMinusOne = _bucketSize - 1; _bucketSizeShift = (int)Math.Log(_bucketSize, 2); _bucketArray = new UnsafeArray>(capacity / _bucketSize + 1); _filler = default!; // Fill buckets for (var i = 0; i < _bucketArray.Length; i++) { var bucket = new UnsafeBucket(_bucketSize); SetBucket(i, in bucket); bucket.Clear(_filler); } } /// /// Creates an instance of the . /// /// The size in bytes. /// The filler value for all slots, basically a custom default-value. /// The total initial capacity, how many items should fit in. public UnsafeJaggedArray(int bucketSize, T filler, int capacity = 64) { _bucketSize = MathExtensions.RoundToPowerOfTwo(bucketSize); _bucketSizeMinusOne = _bucketSize - 1; _bucketSizeShift = (int)Math.Log(_bucketSize, 2); _bucketArray = new UnsafeArray>(capacity / _bucketSize + 1); _filler = filler; // Fill buckets for (var i = 0; i < _bucketArray.Length; i++) { var bucket = new UnsafeBucket(_bucketSize); SetBucket(i, in bucket); bucket.Clear(_filler); } } /// /// The capacity, the total amount of items. /// public int Capacity => _bucketArray.Length * _bucketSize; /// /// The length, the buckets inside the . /// public int Buckets => _bucketArray.Length; /// /// Adds an item to the . /// /// The index. /// The item. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(int index, in T item) { IndexToSlot(index, out var bucketIndex, out var itemIndex); ref var bucket = ref GetBucket(bucketIndex); bucket[itemIndex] = item; bucket.Count++; } /// /// Removes an item from the . /// /// The index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Remove(int index) { IndexToSlot(index, out var bucketIndex, out var itemIndex); ref var bucket = ref GetBucket(bucketIndex); bucket[itemIndex] = _filler; bucket.Count--; } /// /// Trys to get an item from its index. /// /// The index. /// The returned value. /// True if sucessfull, otherwhise false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetValue(int index, out T value) { // If the id is negative if (index < 0 || index >= Capacity ) { value = _filler; return false; } IndexToSlot(index, out var bucketIndex, out var itemIndex); ref var item = ref GetBucket(bucketIndex)[itemIndex]; // If the item is the default then the nobody set its value. if (EqualityComparer.Default.Equals(item, _filler)) { value = _filler; return false; } value = item; return true; } /// /// Trys to get an item from its index. /// /// The index. /// True if sucessfull, otherwhise false /// A reference or null reference to the item. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T TryGetValue(int index, out bool @bool) { // If the id is negative if (index < 0 || index >= Capacity) { @bool = false; return ref Unsafe.NullRef(); } IndexToSlot(index, out var bucketIndex, out var itemIndex); ref var item = ref GetBucket(bucketIndex)[itemIndex]; // If the item is the default then the nobody set its value. if (EqualityComparer.Default.Equals(item, _filler)) { @bool = false; return ref Unsafe.NullRef(); } @bool = true; return ref item; } /// /// Checks if the value at the given index exists. /// /// The index. /// True if it does, false if it does not. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ContainsKey(int index) { if (index < 0 || index > Capacity) { return false; } IndexToSlot(index, out var bucketIndex, out var itemIndex); ref var item = ref GetBucket(bucketIndex)[itemIndex]; // If the item is the default then the nobody set its value. return !EqualityComparer.Default.Equals(item, _filler); } /// /// Ensures the capacity and increases it if necessary. /// /// The new capcity. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void EnsureCapacity(int newCapacity) { if (newCapacity < Capacity) { return; } var length = Buckets; var buckets = newCapacity / _bucketSize + 1; _bucketArray = UnsafeArray.Resize(ref _bucketArray, buckets); for (var i = length; i < _bucketArray.Length; i++) { var bucket = new UnsafeBucket(_bucketSize); SetBucket(i, in bucket); bucket.Clear(_filler); } } /// /// Trims the last few empty buckets to release memory. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void TrimExcess() { // Count how many of the last buckets are empty, to trim them var count = 0; for (var i = _bucketArray.Length - 1; i >= 0; i--) { ref var bucket = ref _bucketArray[i]; if (!bucket.IsEmpty) { break; } count++; } var buckets = _bucketArray.Length - count; _bucketArray = UnsafeArray.Resize(ref _bucketArray, buckets); } /// /// Converts the passed id to its inner and outer index ( or slot ) inside the array. /// /// The id. /// The outer index. /// The inner index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void IndexToSlot(int id, out int bucketIndex, out int itemIndex) { Debug.Assert(id >= 0, "Id cannot be negative."); /* Instead of the '%' operator we can use logical '&' operator which is faster. But it requires the bucket size to be a power of 2. */ bucketIndex = id >> _bucketSizeShift; itemIndex = id & _bucketSizeMinusOne; } /// /// Returns the from the at the given index. /// /// The index. /// The at the given index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref UnsafeBucket GetBucket(int index) { return ref _bucketArray[index]; } /// /// Sets the from the at the given index. /// /// The index. /// Bucket. /// The at the given index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetBucket(int index, in UnsafeBucket bucket) { _bucketArray[index] = bucket; } /// /// Returns a reference to an item at the given index. /// /// The index. public ref T this[int i] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { IndexToSlot(i, out var bucketIndex, out var itemIndex); return ref GetBucket(bucketIndex)[itemIndex]; } } /// /// Clears this and sets all values to the . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { foreach (ref var bucket in _bucketArray) { if (bucket.IsEmpty) { continue; } bucket.Clear(_filler); } } /// /// Disposes this . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { foreach (ref var bucket in _bucketArray) { bucket.Dispose(); } _bucketArray.Dispose(); } } ================================================ FILE: Arch.LowLevel/Jagged/UnsafeSparseJaggedArray.cs ================================================ using System.Diagnostics; using System.Runtime.CompilerServices; namespace Arch.LowLevel.Jagged; /// /// The struct /// represents a bucket of the where items are stored. /// It will not allocate memory upon creation, it stays empty till the first item was added in. /// /// public record struct UnsafeSparseBucket : IDisposable where T : unmanaged { /// /// The items array. /// internal UnsafeArray Array = UnsafeArray.Empty(); /// /// The filler, the default value. /// private readonly T _filler; /// /// Creates an instance of the . /// /// The total capacity. /// The filler. /// If it should allocate straight forward. public UnsafeSparseBucket(int capacity, T filler, bool allocate = false) { Capacity = capacity; _filler = filler; if (allocate) { EnsureCapacity(); } } /// /// The total capacity of this . /// public int Capacity { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set; } /// /// The amount of items in this . /// public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] internal set; } /// /// If this is empty. /// public bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Count <= 0; } /// /// Ensures the of this . /// Basically allocated a new array. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void EnsureCapacity() { if (Array != UnsafeArray.Empty()) { return; } Array = new UnsafeArray(Capacity); Clear(); } /// /// Trims the bucket to an empty one. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void TrimExcess() { if (Count > 0) { return; } Array = UnsafeArray.Empty(); } /// /// Returns a reference to an item at the given index. /// /// The index. public ref T this[int i] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref Array[i]; } /// /// Clears this and sets all values to the . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { UnsafeArray.Fill(ref Array, _filler); } /// /// Disposes this /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { Array.Dispose(); } } /// /// The class, /// represents a jagged array with s storing the items. /// Its buckets will stay empty and not allocate memory till a slot in it is being used. /// /// public struct UnsafeSparseJaggedArray : IDisposable where T : unmanaged { /// /// The size in items. /// private readonly int _bucketSize; /// /// The size in items - 1. /// private readonly int _bucketSizeMinusOne; /// /// The is always a value the power of 2, therefore we can use a bitshift for the division during the index calculation. /// private readonly int _bucketSizeShift; /// /// The allocated s. /// private UnsafeArray> _bucketArray; /// /// The filler, the default value. /// private readonly T _filler; /// /// Creates an instance of the . /// /// The size in bytes. /// The total initial capacity, how many items should fit in. public UnsafeSparseJaggedArray(int bucketSize, int capacity = 64) { _bucketSize = MathExtensions.RoundToPowerOfTwo(bucketSize); _bucketSizeMinusOne = _bucketSize - 1; _bucketSizeShift = (int)Math.Log(_bucketSize, 2); _bucketArray = new UnsafeArray>(capacity / _bucketSize + 1); _filler = default!; // Fill buckets for (var i = 0; i < _bucketArray.Length; i++) { var bucket = new UnsafeSparseBucket(_bucketSize, _filler); SetBucket(i, in bucket); bucket.Clear(); } } /// /// Creates an instance of the . /// /// The size in bytes. /// The filler value for all slots, basically a custom default-value. /// The total initial capacity, how many items should fit in. public UnsafeSparseJaggedArray(int bucketSize, T filler, int capacity = 64) : this(bucketSize, capacity) { _bucketSize = MathExtensions.RoundToPowerOfTwo(bucketSize); _bucketSizeMinusOne = _bucketSize - 1; _bucketSizeShift = (int)Math.Log(_bucketSize, 2); _bucketArray = new UnsafeArray>(capacity / _bucketSize + 1); _filler = filler!; // Fill buckets for (var i = 0; i < _bucketArray.Length; i++) { var bucket = new UnsafeSparseBucket(_bucketSize, _filler); SetBucket(i, in bucket); bucket.Clear(); } } /// /// If true, each bucket will stay empty and will not allocate memory until its actually being used. /// public bool Sparse { get; set; } /// /// The capacity, the total amount of items. /// public int Capacity => _bucketArray.Length * _bucketSize; /// /// The length, the buckets inside the . /// public int Buckets => _bucketArray.Length; /// /// Adds an item to the . /// /// The index. /// The item. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(int index, in T item) { IndexToSlot(index, out var bucketIndex, out var itemIndex); ref var bucket = ref GetBucket(bucketIndex); bucket.EnsureCapacity(); bucket[itemIndex] = item; bucket.Count++; } /// /// Removes an item from the . /// /// The index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Remove(int index) { IndexToSlot(index, out var bucketIndex, out var itemIndex); ref var bucket = ref GetBucket(bucketIndex); bucket[itemIndex] = _filler; bucket.Count--; bucket.TrimExcess(); } /// /// Trys to get an item from its index. /// /// The index. /// The returned value. /// True if sucessfull, otherwhise false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetValue(int index, out T value) { // If the id is negative if (index < 0 || index >= Capacity) { value = _filler; return false; } IndexToSlot(index, out var bucketIndex, out var itemIndex); // Bucket empty? return false ref var bucket = ref GetBucket(bucketIndex); if (bucket.IsEmpty) { value = _filler; return false; } // If the item is the default then the nobody set its value. ref var item = ref bucket[itemIndex]; if (EqualityComparer.Default.Equals(item, _filler)) { value = _filler; return false; } value = item; return true; } /// /// Trys to get an item from its index. /// /// The index. /// True if sucessfull, otherwhise false /// A reference or null reference to the item. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T TryGetValue(int index, out bool @bool) { // If the id is negative if (index < 0 || index >= Capacity) { @bool = false; return ref Unsafe.NullRef(); } IndexToSlot(index, out var bucketIndex, out var itemIndex); // Bucket empty? return false ref var bucket = ref GetBucket(bucketIndex); if (bucket.IsEmpty) { @bool = false; return ref Unsafe.NullRef(); } // If the item is the default then the nobody set its value. ref var item = ref bucket[itemIndex]; if (EqualityComparer.Default.Equals(item, _filler)) { @bool = false; return ref Unsafe.NullRef(); } @bool = true; return ref item; } /// /// Checks if the value at the given index exists. /// /// The index. /// True if it does, false if it does not. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool ContainsKey(int index) { if (index < 0 || index > Capacity) { return false; } IndexToSlot(index, out var bucketIndex, out var itemIndex); // If bucket empty return false ref var bucket = ref GetBucket(bucketIndex); if (bucket.IsEmpty) { return false; } // If the item is the default then the nobody set its value. ref var item = ref bucket[itemIndex]; return !EqualityComparer.Default.Equals(item, _filler); } /// /// Ensures the capacity and increases it if necessary. /// /// The new capcity. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void EnsureCapacity(int newCapacity) { if (newCapacity < Capacity) { return; } var length = Buckets; var buckets = newCapacity / _bucketSize + 1; _bucketArray = UnsafeArray.Resize(ref _bucketArray, buckets); for (var i = length; i < _bucketArray.Length; i++) { var bucket = new UnsafeSparseBucket(_bucketSize, _filler); SetBucket(i, in bucket); bucket.Clear(); } } /// /// Trims the last few empty buckets to release memory. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void TrimExcess() { // Count how many of the last buckets are empty, to trim them var count = 0; for (var i = _bucketArray.Length - 1; i >= 0; i--) { ref var bucket = ref _bucketArray[i]; if (!bucket.IsEmpty) { break; } count++; } var buckets = _bucketArray.Length - count; _bucketArray = UnsafeArray.Resize(ref _bucketArray, buckets); } /// /// Converts the passed id to its inner and outer index ( or slot ) inside the array. /// /// The id. /// The outer index. /// The inner index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void IndexToSlot(int id, out int bucketIndex, out int itemIndex) { Debug.Assert(id >= 0, "Id cannot be negative."); /* Instead of the '%' operator we can use logical '&' operator which is faster. But it requires the bucket size to be a power of 2. */ bucketIndex = id >> _bucketSizeShift; itemIndex = id & _bucketSizeMinusOne; } /// /// Returns the from the at the given index. /// /// The index. /// The at the given index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref UnsafeSparseBucket GetBucket(int index) { return ref _bucketArray[index]; } /// /// Sets the from the at the given index. /// /// The index. /// Bucket. /// The at the given index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetBucket(int index, in UnsafeSparseBucket bucket) { _bucketArray[index] = bucket; } /// /// Returns a reference to an item at the given index. /// /// The index. public ref T this[int i] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { IndexToSlot(i, out var bucketIndex, out var itemIndex); return ref GetBucket(bucketIndex)[itemIndex]; } } /// /// Clears this and sets all values to the . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { foreach (ref var bucket in _bucketArray) { if (bucket.IsEmpty) { continue; } UnsafeArray.Fill(ref bucket.Array, _filler); } } /// /// Disposes this . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { foreach (ref var bucket in _bucketArray) { bucket.Dispose(); } _bucketArray.Dispose(); } } ================================================ FILE: Arch.LowLevel/Resources.cs ================================================ using System.Runtime.CompilerServices; using Arch.LowLevel.Jagged; [assembly: InternalsVisibleTo("Arch.LowLevel.Tests")] namespace Arch.LowLevel; /// /// The struct /// represents a reference to an managed resource. /// This is used commonly for referencing managed resources from components. /// /// The type of the managed resource. public readonly record struct Handle { /// /// A null which is invalid and used for camparison. /// public static readonly Handle NULL = new(-1); /// /// The id, its index inside a array. /// public readonly int Id = -1; /// /// Public default constructor. /// public Handle() { Id = -1; } /// /// Initializes a new instance of the class. /// /// internal Handle(int id) { Id = id; } } /// /// The class, /// represents an collection of managed resources which can be accesed by a . /// /// public sealed class Resources : IDisposable { /// /// The which stores the managed resources on the index. /// private JaggedArray _array; /// /// A list of recycled ids, used to fill in old gaps. /// internal Queue _ids; /// /// Creates an instance. /// /// The capacity of the bucket. public Resources(int capacity = 64) { _array = new JaggedArray(capacity, capacity); _ids = new Queue(capacity); } /// /// Creates an instance. /// /// The size of the generic type in bytes. /// The capcity, how many items of that type should fit into the array. public Resources(int size, int capacity = 64) { _array = new JaggedArray(160000/size, capacity); _ids = new Queue(capacity); } /// /// The amount of registered s. /// public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set; } /// /// Creates a for the given resource. /// /// The resource instance. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Handle Add(in T item) { // Create handle var recyled = _ids.TryDequeue(out var id); id = recyled ? id : Count; var handle = new Handle(id); // Resize array and fill it in _array.EnsureCapacity(id+1); _array.Add(id, item); Count++; return handle; } /// /// Checks if the is valid. /// /// The . /// True or false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsValid(in Handle handle) { return handle.Id > -1 && handle.Id <= _array.Capacity; } /// /// Returns a resource for the given . /// /// The . /// The resource. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T Get(in Handle handle) { return ref _array[handle.Id]; } /// /// Removes a and its resource. /// /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Remove(in Handle handle) { _array.Remove(handle.Id); _ids.Enqueue(handle.Id); Count--; } /// /// Trims the resources and releases unused memory if possible. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void TrimExcess() { _array.TrimExcess(); _ids.TrimExcess(); } /// /// Disposes this instance. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { _array = null!; _ids = null!; Count = 0; } } ================================================ FILE: Arch.LowLevel/UnsafeArray.cs ================================================ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; namespace Arch.LowLevel; /// /// The struct /// represents an unsafe allocated array of unmanaged items. /// /// The unmanaged generic. [DebuggerTypeProxy(typeof(UnsafeArrayDebugView<>))] public readonly unsafe struct UnsafeArray : IDisposable where T : unmanaged { /// /// The static empty . /// internal static UnsafeArray Empty = new(null, 0); /// /// The pointer, pointing towards the first element of this . /// internal readonly T* _ptr; /// /// Creates an instance of the . /// Allocates the array for the passed count of items. /// /// The arrays count or capacity. public UnsafeArray(int count) { #if NET6_0_OR_GREATER _ptr = (T*)NativeMemory.Alloc((nuint)(sizeof(T) * count)); #else _ptr = (T*)Marshal.AllocHGlobal(sizeof(T) * count); #endif Count = count; } /// /// Creates an instance of the by a pointer. /// /// The pointer. /// The count. public UnsafeArray(T* ptr, int count) { _ptr = ptr; Count = count; } /// /// The count of this instance, its capacity. /// public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } /// /// The count of this instance, its capacity. /// public int Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Count; } /// /// Returns a reference to an item at a given index. /// /// The index. public ref T this[int i] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _ptr[i]; } /// /// Disposes this instance of and releases its memory. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { #if NET6_0_OR_GREATER NativeMemory.Free(_ptr); #else Marshal.FreeHGlobal((IntPtr)_ptr); #endif } /// /// Converts this instance into a . /// /// A new instance of . [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span AsSpan() { return new Span(_ptr, Count); } /// /// Creates an instance of a for ref acessing the array content. /// /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public UnsafeEnumerator GetEnumerator() { return new UnsafeEnumerator(_ptr, Count); } /// /// Checks for equality. /// /// The other . /// True if equal, oterwhise false. public bool Equals(UnsafeArray other) { return _ptr == other._ptr && Count == other.Count; } /// /// Checks for equality. /// /// The other . /// True if equal, oterwhise false. public override bool Equals(object? obj) { return obj is UnsafeArray other && Equals(other); } /// /// Checks for equality. /// /// The first . /// The second . /// public static bool operator ==(UnsafeArray left, UnsafeArray right) { return left.Equals(right); } /// /// Checks for inequality. /// /// The first . /// The second . /// public static bool operator !=(UnsafeArray left, UnsafeArray right) { return !left.Equals(right); } /// /// Returns the hash of this . /// /// public override int GetHashCode() { unchecked { return (unchecked((int)(long)_ptr) * 397) ^ Count; } } /// /// Converts an into a void pointer. /// /// The instance. /// A void pointer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator void*(UnsafeArray instance) { return (void*)instance._ptr; } /// /// Converts an into a generic pointer. /// /// The instance. /// A void pointer. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator T*(UnsafeArray instance) { return instance._ptr; } /// /// Converts this to a string. /// /// The string. [MethodImpl(MethodImplOptions.AggressiveInlining)] public override string ToString() { var items = new StringBuilder(); foreach (ref var item in this) { items.Append($"{item},"); } items.Length--; return $"UnsafeArray<{typeof(T).Name}>[{Count}]{{{items}}}"; } } /// /// Unsafe array. /// public unsafe struct UnsafeArray { /// /// Returns an empty . /// /// The generic type. /// The empty . public static UnsafeArray Empty() where T : unmanaged { return UnsafeArray.Empty; } /// /// Copies the a part of the to the another . /// /// The source . /// The start index in the source . /// The destination . /// The start index in the destination . /// The length indicating the amount of items being copied. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Copy(ref UnsafeArray source, int index, ref UnsafeArray destination, int destinationIndex, int length) where T : unmanaged { var size = sizeof(T); var bytes = size * length; var sourcePtr = (void*)(source._ptr + index); var destinationPtr = (void*)(destination._ptr + destinationIndex); Buffer.MemoryCopy(sourcePtr, destinationPtr, bytes, bytes); } /// /// Fills an with a given value. /// /// The instance. /// The value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Fill(ref UnsafeArray source, in T value = default) where T : unmanaged { source.AsSpan().Fill(value); } /// /// Resizes an to a new . /// /// The . /// The new capacity. /// The generic type. /// The new resized . [MethodImpl(MethodImplOptions.AggressiveInlining)] public static UnsafeArray Resize(ref UnsafeArray source, int newCapacity) where T : unmanaged { var destination = new UnsafeArray(newCapacity); Copy(ref source, 0, ref destination, 0, Math.Min(source.Length, destination.Length)); source.Dispose(); return destination; } } /// /// A debug view for the . /// /// The unmanaged type. internal class UnsafeArrayDebugView where T : unmanaged { [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly UnsafeArray _entity; [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public T[] Items { get { var items = new T[_entity.Count]; _entity.AsSpan().CopyTo(items); return items; } } public UnsafeArrayDebugView(UnsafeArray entity) => _entity = entity; } ================================================ FILE: Arch.LowLevel/UnsafeList.cs ================================================ using System.Collections; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text; namespace Arch.LowLevel; /// /// The struct represents a native unmanaged list. /// Can easily be stored in unmanaged structs. /// /// The generic type stored in the list. [DebuggerTypeProxy(typeof(UnsafeListDebugView<>))] public unsafe struct UnsafeList : IList, IDisposable where T : unmanaged { /// /// The array pointer. /// private UnsafeArray _array; /// /// Creates an instance of the . /// /// The initial capacity that is being allocated. public UnsafeList(int capacity = 8) { Count = 0; Capacity = capacity; _array = new UnsafeArray(capacity); } /// /// Creates an instance of the by a pointer. /// /// The pointer. /// The initial capacity that is being allocated. public UnsafeList(T* ptr, int capacity = 8) { Count = 0; Capacity = capacity; _array = new UnsafeArray(ptr, capacity); } /// /// The amount of items in the list. /// public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set; } /// /// The total capacity of this list. /// public int Capacity { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set; } /// /// If its readonly. /// public bool IsReadOnly { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } /// /// Adds an item to the list. /// /// The item. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(T item) { if (Count == Capacity) { EnsureCapacity(Capacity * 2); } _array[Count] = item; Count++; } /// /// Inserts an item at the given index. /// /// The index. /// The item instance. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Insert(int index, T item) { // Inserting to end of the list is legal. if ((uint)index > (uint)Count) { throw new ArgumentOutOfRangeException(nameof(index)); } // Resize if the list is actually full if (Capacity == Count) { EnsureCapacity(Capacity + 1); } if (index < Count) { //var span = _array.AsSpan(); //var src = span.Slice(index, Count - index); //var dst = span.Slice(index + 1, src.Length); //src.CopyTo(dst); UnsafeArray.Copy(ref _array, index, ref _array, index + 1, Count - index); } _array[index] = item; Count++; } /// /// Removes an item from the list at a given index. /// /// The index. /// Throws when the index is out of range. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void RemoveAt(int index) { if ((uint)index > (uint)Count) { throw new ArgumentOutOfRangeException(nameof(index)); } Count--; if (index < Count) { //Buffer.MemoryCopy(_array+(index+1), _array+index,Count-index,Count-index); UnsafeArray.Copy(ref _array, index + 1, ref _array, index, Count - index); } _array[Count] = default; } /// /// Removes the item by its value and returns true or false. /// /// The item. /// True if the operation was sucessfull, false if it was not. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Remove(T item) { var index = IndexOf(item); if (index < 0) return false; RemoveAt(index); return true; } /// /// Checks if the item is containted in this instance and returns its index. /// /// The item. /// Its index. [MethodImpl(MethodImplOptions.AggressiveInlining)] public int IndexOf(T item) { for(var i = 0; i < Count; i++) { if(EqualityComparer.Default.Equals(_array[i], item)) { return i; } } return -1; } /// /// Checks if the item is containted in this instance. /// /// The item. /// True if it exists, otherwhise false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Contains(T item) { for(var i = 0; i < Count; i++) { if(EqualityComparer.Default.Equals(_array[i], item)) { return true; } } return false; } /// /// Copies all items from this to the specified array. /// /// The array to copy to. /// The index to start with. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void CopyTo(T[] array, int arrayIndex) { if (Count == 0) return; if (arrayIndex < 0 || arrayIndex >= array.Length) throw new IndexOutOfRangeException("Index must be 0 <= index <= array.Length"); if (arrayIndex + Count > array.Length) throw new ArgumentException("Destination array was not long enough. Check the destination index, length, and the array's lower bounds.", nameof(arrayIndex)); fixed(T* arrayPtr = array) { Buffer.MemoryCopy(_array, arrayPtr+arrayIndex, array.Length * sizeof(T), Count * sizeof(T)); } } /// /// Ensures the capacity of this instance and resizes it accordingly. /// /// The minimum amount of items ensured. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void EnsureCapacity(int min) { if (min <= Count) { return; } var oldArray = _array; var newArray = new UnsafeArray(min); // Copy & Free UnsafeArray.Copy(ref oldArray, 0, ref newArray,0, Count); oldArray.Dispose(); _array = newArray; Capacity = min; } /// /// Trims the capacity of this to release unused memory. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void TrimExcess() { var oldArray = _array; var newArray = new UnsafeArray(Count); // Copy & free UnsafeArray.Copy(ref oldArray, 0, ref newArray,0, Count); oldArray.Dispose(); _array = newArray; Capacity = Count; } /// /// Acesses an item at the index of the list. /// /// The index. T IList.this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _array[CheckIndex(index)]; [MethodImpl(MethodImplOptions.AggressiveInlining)] set => _array[CheckIndex(index)] = value; } /// /// Acesses an item at the index of the list. /// /// The index. public ref T this[int i] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _array[CheckIndex(i)]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private readonly int CheckIndex(int index) { #if DEBUG if (index < 0) throw new IndexOutOfRangeException("Index cannot be less than zero"); if (index >= Count) throw new IndexOutOfRangeException("Index cannot be greater than or equal to the count"); #endif return index; } /// /// Clears this instance. /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { Count = 0; } /// /// Disposes this instance and releases its memory. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { _array.Dispose(); } /// /// Converts this instance into a . /// /// A new instance of . [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span AsSpan() { return new Span(_array, Count); } /// /// Creates an instance of a for ref acessing the list content. /// /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public UnsafeEnumerator GetEnumerator() { return new UnsafeEnumerator(_array, Count); } /// /// Creates an instance of a . /// /// The new . [MethodImpl(MethodImplOptions.AggressiveInlining)] IEnumerator IEnumerable.GetEnumerator() { return new UnsafeIEnumerator(_array, Count); } /// /// Creates an instance of a . /// /// The new . [MethodImpl(MethodImplOptions.AggressiveInlining)] IEnumerator IEnumerable.GetEnumerator() { return new UnsafeIEnumerator(_array, Count); } /// /// Checks for equality. /// /// The other . /// True or false. public bool Equals(UnsafeList other) { return _array.Equals(other._array) && Count == other.Count && Capacity == other.Capacity; } /// /// Checks for equality. /// /// The other . /// True or false. public override bool Equals(object? obj) { return obj is UnsafeList other && Equals(other); } /// /// Checks for equality. /// /// The first . /// The second . /// True or false. public static bool operator ==(UnsafeList left, UnsafeList right) { return left.Equals(right); } /// /// Checks for inequality. /// /// The first . /// The second . /// True or false. public static bool operator !=(UnsafeList left, UnsafeList right) { return !left.Equals(right); } /// /// Returns the hashcode of this . /// /// public override int GetHashCode() { unchecked { var hashCode = _array.GetHashCode(); hashCode = (hashCode * 397) ^ Count; hashCode = (hashCode * 397) ^ Capacity; return hashCode; } } /// /// Converts this to a string. /// /// The string. [MethodImpl(MethodImplOptions.AggressiveInlining)] public override string ToString() { var items = new StringBuilder(); foreach (ref var item in this) { items.Append($"{item},"); } items.Length--; return $"UnsafeList<{typeof(T).Name}>[{Count}]{{{items}}}"; } } /// /// A debug view for the . /// /// The unmanaged type. internal class UnsafeListDebugView where T : unmanaged { [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly UnsafeList _entity; [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public T[] Items { get { var items = new T[_entity.Count]; _entity.CopyTo(items, 0); return items; } } public UnsafeListDebugView(UnsafeList entity) => _entity = entity; } ================================================ FILE: Arch.LowLevel/UnsafeQueue.cs ================================================ using System.Collections; using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Text; namespace Arch.LowLevel; /// /// The struct represents a native unmanaged queue. /// Can easily be stored in unmanaged structs. /// /// The generic type stored in the queue. [DebuggerTypeProxy(typeof(UnsafeQueueDebugView<>))] public unsafe struct UnsafeQueue : IEnumerable, IDisposable where T : unmanaged { private UnsafeArray _queue; private int _capacity; private int _frontIndex; private int _count; /// /// Creates an instance of the . /// /// Initial capacity of this queue. /// public UnsafeQueue(int capacity) { if (capacity <= 0) { throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than 0."); } _queue = new UnsafeArray(capacity); _capacity = capacity; _frontIndex = _count = 0; } /// /// The amount of items in the queue. /// public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _count; } /// /// The total capacity of this queue. /// public int Capacity { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _capacity; } /// /// Enqueues a item. /// /// The item [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Enqueue(T item) { if (Count == Capacity) { EnsureCapacity(_capacity * 2); } var itemOffset = (_frontIndex + _count) % _capacity; _queue[itemOffset] = item; _count++; } /// /// Dequeues an item. /// /// The item /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public T Dequeue() { if (_count == 0) { throw new InvalidOperationException("Queue is empty"); } var item = Peek(); _frontIndex = (_frontIndex + 1) % _capacity; _count--; return item; } /// /// Peeks at an item. /// /// The item. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T Peek() { if (_count == 0) { throw new InvalidOperationException("Queue is empty"); } return ref _queue[_frontIndex]; } /// /// Trims this instance and releases memory in this process. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void TrimExcess() { var newCapacity = _count; SetCapacity(newCapacity); } /// /// Ensures the capacity of this instance. /// /// The new capacity. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void EnsureCapacity(int newCapacity) { if (newCapacity <= _capacity) { return; } SetCapacity(newCapacity); } /// /// Ensures the capacity of this instance. /// /// The new capacity. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SetCapacity(int newCapacity) { if (newCapacity < _count) { throw new ArgumentOutOfRangeException(nameof(newCapacity), "newCapacity cannot be smaller than _count"); } var newBuffer = new UnsafeArray(newCapacity); if (_count > 0) { var firstChunkCount = Math.Min(_count, _capacity - _frontIndex); var secondChunkCount = _count - firstChunkCount; // Copy elements in front->rear order to the new buffer if (firstChunkCount > 0) { UnsafeArray.Copy(ref _queue, _frontIndex, ref newBuffer, 0, firstChunkCount); } if (secondChunkCount > 0) { UnsafeArray.Copy(ref _queue, 0, ref newBuffer, firstChunkCount, secondChunkCount); } } _queue.Dispose(); _queue = newBuffer; _capacity = newCapacity; _frontIndex = 0; } /// /// Clears this instance. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { _frontIndex = _count = 0; } /// /// Disposes this instance. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { _queue.Dispose(); _capacity = _frontIndex = _count = 0; } /// /// Converts this instance into a . /// /// A new instance of . [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public Span AsSpan() { return new Span(_queue, Count); } /// /// Creates an instance of a for ref acessing the list content. /// /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public UnsafeEnumerator GetEnumerator() { return new UnsafeEnumerator(_queue, Count); } /// /// Creates an instance of a . /// /// The new . [MethodImpl(MethodImplOptions.AggressiveInlining)] IEnumerator IEnumerable.GetEnumerator() { return new UnsafeIEnumerator(_queue, Count); } /// /// Creates an instance of a . /// /// The new . [MethodImpl(MethodImplOptions.AggressiveInlining)] IEnumerator IEnumerable.GetEnumerator() { return new UnsafeIEnumerator(_queue, Count); } /// /// Converts this to a string. /// /// The string. [MethodImpl(MethodImplOptions.AggressiveInlining)] public override string ToString() { var items = new StringBuilder(); foreach (ref var item in this) { items.Append($"{item},"); } items.Length--; return $"UnsafeQueue<{typeof(T).Name}>[{Count}]{{{items}}}"; } } /// /// A debug view for the . /// /// The unmanaged type. internal class UnsafeQueueDebugView where T : unmanaged { [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly UnsafeQueue _entity; [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public T[] Items { get { var items = new T[_entity.Count]; _entity.AsSpan().CopyTo(items); return items; } } public UnsafeQueueDebugView(UnsafeQueue entity) => _entity = entity; } ================================================ FILE: Arch.LowLevel/UnsafeStack.cs ================================================ using System.Collections; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text; namespace Arch.LowLevel; /// /// The struct represents a native unmanaged stack. /// Can easily be stored in unmanaged structs. /// /// The generic type stored in the stack. [DebuggerTypeProxy(typeof(UnsafeStackDebugView<>))] public unsafe struct UnsafeStack : IEnumerable, IDisposable where T : unmanaged { private const int DefaultCapacity = 4; /// /// The stack pointer. /// private UnsafeArray _stack; /// /// Its capacity. /// private int _capacity; /// /// Its count. /// private int _count; /// /// Creates an instance of the . /// /// The initial capacity that is being allocated. public UnsafeStack(int capacity = DefaultCapacity) { if (capacity <= 0) { throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than 0."); } _stack = new UnsafeArray(capacity); _capacity = capacity; _count = 0; } /// /// The amount of items in the stack. /// public readonly int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _count; } /// /// The total capacity of this stack. /// public readonly int Capacity { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _capacity; } /// /// If this stack is empty. /// public readonly bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _count == 0; } /// /// If this stack is full. /// public readonly bool IsFull { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _count >= _capacity; } /// /// Pushes an item to the . /// /// The item. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Push(T value) { if (Count == Capacity) { EnsureCapacity(_capacity * 2); } _stack[_count] = value; _count++; } /// /// Pops the first item of this and returns it. /// /// The item. /// If the stack is empty. [MethodImpl(MethodImplOptions.AggressiveInlining)] public T Pop() { if (_count == 0) { throw new InvalidOperationException("Stack is empty."); } _count--; return _stack[_count]; } /// /// Peeks at the first item of this and returns it. /// /// The item. /// If the stack is empty. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref T Peek() { if (_count == 0) { throw new InvalidOperationException("Stack is empty."); } return ref _stack[_count - 1]; } /// /// Ensures the capacity of this instance and resizes it accordingly. /// /// The minimum amount of items ensured. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void EnsureCapacity(int min) { if (min <= _capacity) { return; } var newCapacity = _capacity * 2; if (newCapacity < min) { newCapacity = min; } // Create new stack and copy elements var newStack = new UnsafeArray(newCapacity); UnsafeArray.Copy(ref _stack, 0, ref newStack,0, _count); _stack.Dispose(); _capacity = newCapacity; _stack = newStack; } /// /// Trims the capacity of this to release unused memory. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void TrimExcess() { var newCapacity = _count == 0 ? DefaultCapacity : _count; if (newCapacity >= _capacity) { return; } // Create new stack and copy elements var newStack = new UnsafeArray(newCapacity); UnsafeArray.Copy(ref _stack, 0, ref newStack,0, _count); _stack.Dispose(); _capacity = newCapacity; _stack = newStack; } /// /// Clears this instance. /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { _count = 0; } /// /// Disposes this instance and releases its memory. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() { _stack.Dispose(); } /// /// Converts this instance into a . /// /// A new instance of . [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly Span AsSpan() { return new Span(_stack, Count); } /// /// Creates an instance of a for ref acessing the list content. /// /// A new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly UnsafeReverseEnumerator GetEnumerator() { return new UnsafeReverseEnumerator(_stack, Count); } /// /// Creates an instance of a . /// /// The new . [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly IEnumerator IEnumerable.GetEnumerator() { return new ReverseIEnumerator(_stack, Count); } /// /// Creates an instance of a . /// /// The new . [MethodImpl(MethodImplOptions.AggressiveInlining)] readonly IEnumerator IEnumerable.GetEnumerator() { return new ReverseIEnumerator(_stack, Count); } /// /// Converts this to a string. /// /// The string. [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly override string ToString() { var items = new StringBuilder(); foreach (ref var item in this) { items.Append($"{item},"); } items.Length--; return $"UnsafeStack<{typeof(T).Name}>[{Count}]{{{items}}}"; } } /// /// A debug view for the . /// /// The unmanaged type. internal class UnsafeStackDebugView where T : unmanaged { [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly UnsafeStack _entity; [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public T[] Items { get { var items = new T[_entity.Count]; _entity.AsSpan().CopyTo(items); return items; } } public UnsafeStackDebugView(UnsafeStack entity) => _entity = entity; } ================================================ FILE: Arch.LowLevel.Tests/Arch.LowLevel.Tests.csproj ================================================ net7.0 enable enable false ================================================ FILE: Arch.LowLevel.Tests/ArrayTest.cs ================================================ using System.ComponentModel.DataAnnotations; namespace Arch.LowLevel.Tests; using static Assert; /// /// Checks related methods. /// [TestFixture] public class ArrayTest { /// /// Checks if is capable of allocating space and adding items. /// [Test] public void ArrayCreate() { var array = new Array(3); array[0] = 1; array[1] = 2; array[2] = 3; That(array.Count, Is.EqualTo(3)); } [Test] public void ArrayEnumerator() { var array = new Array(3); array[0] = 1; array[1] = 2; array[2] = 3; var count = 1; foreach (var item in array) That(item, Is.EqualTo(count++)); } [Test] public void ArrayEmptyIsEmpty() { var empty = Array.Empty(); That(empty, Is.Empty); } [Test] public void ArrayFill() { var array = new Array(35); Array.Fill(ref array, 8); for (var i = 0; i < array.Length; i++) That(array[i], Is.EqualTo(8)); } [Test] public void ArrayCopy() { var src = new Array(15); var dst = new Array(6); for (var i = 0; i < src.Length; i++) src[i] = i; Array.Fill(ref dst); Array.Copy(ref src, 4, ref dst, 1, 4); Multiple(() => { That(dst[0], Is.EqualTo(0)); That(dst[1], Is.EqualTo(4)); That(dst[2], Is.EqualTo(5)); That(dst[3], Is.EqualTo(6)); That(dst[4], Is.EqualTo(7)); That(dst[5], Is.EqualTo(0)); }); } [Test] public void ArrayResizeShrink() { var array = new Array(19); for (var i = 0; i < array.Length; i++) array[i] = i; var resized = Array.Resize(ref array, 8); for (var i = 0; i < resized.Length; i++) That(resized[i], Is.EqualTo(i)); } [Test] public void ArrayResizeGrow() { var array = new Array(8); for (var i = 0; i < array.Length; i++) array[i] = i; var resized = Array.Resize(ref array, 19); for (var i = 0; i < array.Length; i++) That(resized[i], Is.EqualTo(i)); } [Test] public void ArrayEquals() { var a = new Array(8); var b = a; That(a, Is.EqualTo(b)); That(a == b, Is.True); } [Test] public void ArrayNotEquals() { var a = new Array(8); var b = new Array(8); That(a, Is.Not.EqualTo(b)); That(a != b, Is.True); } } ================================================ FILE: Arch.LowLevel.Tests/Jagged/JaggedArrayTest.cs ================================================ using System.Runtime.CompilerServices; using Arch.LowLevel.Jagged; namespace Arch.LowLevel.Tests.Jagged; using static Assert; /// /// Checks related methods. /// [TestFixture] public class JaggedArrayTest { /// /// Checks if is capable of adding items correctly. /// [Test] public void Add([Values(256,512,1024,2048,4096)] int capacity) { // Check add var jaggedArray = new JaggedArray(16000/Unsafe.SizeOf(), -1, capacity); // adding for (var index = 0; index < jaggedArray.Capacity; index++) { jaggedArray.Add(index, index); } // Checking for (var index = 0; index < jaggedArray.Capacity; index++) { var item = jaggedArray[index]; That(item, Is.EqualTo(index)); } That(jaggedArray.Capacity, Is.GreaterThan(capacity)); } [Test] public void TryGetValue([Values(256,512,1024,2048,4096)] int capacity) { // Initialize the JaggedArray var jaggedArray = new JaggedArray(16000/Unsafe.SizeOf(), -1, capacity); // Add elements to the array for (var index = 0; index < jaggedArray.Capacity; index++) { jaggedArray.Add(index, index); } // Check values using TryGetValue for (var index = 0; index < jaggedArray.Capacity; index++) { var found = jaggedArray.TryGetValue(index, out int value); That(found, Is.True); That(value, Is.EqualTo(index)); } // Check for values out of bounds var outOfBoundsFound = jaggedArray.TryGetValue(jaggedArray.Capacity, out int _); That(outOfBoundsFound, Is.False); } [Test] public void TryGetValueRef([Values(256,512,1024,2048,4096)] int capacity) { // Initialize the JaggedArray var jaggedArray = new JaggedArray(16000/Unsafe.SizeOf(), -1, capacity); // Add elements to the array for (var index = 0; index < jaggedArray.Capacity; index++) { jaggedArray.Add(index, index); } // Check values using TryGetValue for (var index = 0; index < jaggedArray.Capacity; index++) { bool found; ref var value = ref jaggedArray.TryGetValue(index, out found); That(found, Is.True); That(value, Is.EqualTo(index)); } // Check for values out of bounds ref var outOfBoundsValue = ref jaggedArray.TryGetValue(jaggedArray.Capacity, out bool outOfBoundsFound); That(outOfBoundsFound, Is.False); } /// /// Checks if is capable of adding items correctly. /// [Test] public void Remove([Values(256,512,1024,2048,4096)] int capacity) { // Check add var jaggedArray = new JaggedArray(16000/Unsafe.SizeOf(), -1, capacity); // Adding for (var index = 0; index < jaggedArray.Capacity; index++) { jaggedArray.Add(index, index); } // Removing for (var index = jaggedArray.Capacity-1; index >= 0; index--) { jaggedArray.Remove(index); } // Checking for (var index = 0; index < jaggedArray.Capacity; index++) { var item = jaggedArray[index]; That(item, Is.EqualTo(-1)); } } /// /// Checks if is capable of adding items correctly. /// [Test] public void TrimExcess([Values(2560,5120,10240)] int capacity) { // Check add var jaggedArray = new JaggedArray(16000/Unsafe.SizeOf(), -1, capacity); // Adding for (var index = 0; index < jaggedArray.Capacity; index++) { jaggedArray.Add(index, index); } // Removing half of items for (var index = jaggedArray.Capacity-1; index >= jaggedArray.Capacity/2; index--) { jaggedArray.Remove(index); } var buckets = jaggedArray.Buckets; jaggedArray.TrimExcess(); That(jaggedArray.Buckets, Is.EqualTo((buckets + 2 - 1)/2)); // Checking first half still having the desired value for (var index = 0; index < jaggedArray.Capacity/2; index++) { var item = jaggedArray[index]; That(item, Is.EqualTo(index)); } } } ================================================ FILE: Arch.LowLevel.Tests/ResourcesTest.cs ================================================ using static NUnit.Framework.Assert; namespace Arch.LowLevel.Tests; /// /// Checks and HashCode related methods. /// [TestFixture] public class ResourcesTest { /// /// Checks if is capable of adding s. /// [Test] public void ResourcesAddHandle() { // Check add var resources = new Resources(IntPtr.Size, capacity: 64); var handle = resources.Add("Handle"); var nextHandle = resources.Add("NextHandle"); That(handle.Id, Is.EqualTo(0)); That(nextHandle.Id, Is.EqualTo(1)); } /// /// Checks if is capable of adding many more s than the capacity /// [Test] public void ResourcesAddManyHandles() { const int count = 10000; using var resources = new Resources(capacity: 3); var handles = new List>(); for (var i = 0; i < count; i++) handles.Add(resources.Add(i.ToString())); resources.TrimExcess(); That(resources.Count, Is.EqualTo(count)); for (var i = 0; i < handles.Count; i++) That(resources.Get(handles[i]), Is.EqualTo(i.ToString())); } /// /// Checks if is capable of getting s. /// [Test] public void ResourcesGetHandle() { // Check add var resources = new Resources(IntPtr.Size, capacity: 64); var handle = resources.Add("Handle"); var nextHandle = resources.Add("NextHandle"); // Check get var handleString = resources.Get(in handle); var nextHandleString = resources.Get(in nextHandle); That(handleString, Is.EqualTo("Handle")); That(nextHandleString, Is.EqualTo("NextHandle")); } /// /// Checks if is capable of removing s. /// [Test] public void ResourcesRemoveHandle() { // Check add var resources = new Resources(IntPtr.Size, capacity: 64); var handle = resources.Add("Handle"); var nextHandle = resources.Add("NextHandle"); // Check remove resources.Remove(in handle); resources.Remove(in nextHandle); That(resources._ids.Count, Is.EqualTo(2)); That(resources.Count, Is.EqualTo(0)); } /// /// Checks if is capable of removing s. /// [Test] public void ResourcesRecycleHandle() { // Check add var resources = new Resources(IntPtr.Size, capacity: 64); var handle = resources.Add("Handle"); var nextHandle = resources.Add("NextHandle"); // Check remove resources.Remove(in handle); resources.Remove(in nextHandle); var newHandle = resources.Add("NewString"); That(newHandle.Id, Is.EqualTo(0)); That(resources.Count, Is.EqualTo(1)); } /// /// Checks if is capable of validating a . /// [Test] public void ResourcesHandleValid() { // Check add var resources = new Resources(IntPtr.Size, capacity: 64); var handle = resources.Add("Handle"); Handle someHandle = Handle.NULL; That(resources.IsValid(handle), Is.EqualTo(true)); That(resources.IsValid(someHandle), Is.EqualTo(false)); } /// /// Checks if throws after Dispose /// [Test] public void ResourcesDispose() { // Check add var resources = new Resources(IntPtr.Size, capacity: 64); var handle = resources.Add("Handle"); // Check get That(resources.Get(in handle), Is.EqualTo("Handle")); resources.Dispose(); That(resources.Count, Is.EqualTo(0)); // Check that get fails Throws(() => { resources.Get(in handle); }); } } ================================================ FILE: Arch.LowLevel.Tests/UnsafeArrayTest.cs ================================================ using System.ComponentModel.DataAnnotations; namespace Arch.LowLevel.Tests; using static Assert; /// /// Checks related methods. /// [TestFixture] public class UnsafeArrayTest { /// /// Checks if is capable of allocating space and adding items. /// [Test] public void UnsafeArrayCreate() { using var array = new UnsafeArray(3); array[0] = 1; array[1] = 2; array[2] = 3; That(array.Count, Is.EqualTo(3)); } [Test] public void UnsafeArrayEnumerator() { using var array = new UnsafeArray(3); array[0] = 1; array[1] = 2; array[2] = 3; var count = 1; foreach (var item in array) That(item, Is.EqualTo(count++)); } [Test] public void UnsafeArrayEmptyIsEmpty() { var empty = UnsafeArray.Empty(); That(empty, Is.Empty); empty.Dispose(); That(empty, Is.Empty); } [Test] public void UnsafeArrayFill() { var array = new UnsafeArray(35); using (array) { #pragma warning disable CS0728 // Possibly incorrect assignment to local which is the argument to a using or lock statement UnsafeArray.Fill(ref array, 8); #pragma warning restore CS0728 // Possibly incorrect assignment to local which is the argument to a using or lock statement for (var i = 0; i < array.Length; i++) That(array[i], Is.EqualTo(8)); } } [Test] public void UnsafeArrayCopy() { var src = new UnsafeArray(15); var dst = new UnsafeArray(6); using (src) using (dst) { for (var i = 0; i < src.Length; i++) src[i] = i; #pragma warning disable CS0728 // Possibly incorrect assignment to local which is the argument to a using or lock statement UnsafeArray.Fill(ref dst); UnsafeArray.Copy(ref src, 4, ref dst, 1, 4); #pragma warning restore CS0728 // Possibly incorrect assignment to local which is the argument to a using or lock statement Multiple(() => { That(dst[0], Is.EqualTo(0)); That(dst[1], Is.EqualTo(4)); That(dst[2], Is.EqualTo(5)); That(dst[3], Is.EqualTo(6)); That(dst[4], Is.EqualTo(7)); That(dst[5], Is.EqualTo(0)); }); } } [Test] public void UnsafeArrayResizeShrink() { var array = new UnsafeArray(19); for (var i = 0; i < array.Length; i++) array[i] = i; var resized = UnsafeArray.Resize(ref array, 8); for (var i = 0; i < resized.Length; i++) That(resized[i], Is.EqualTo(i)); resized.Dispose(); } [Test] public void UnsafeArrayResizeGrow() { var array = new UnsafeArray(8); for (var i = 0; i < array.Length; i++) array[i] = i; var resized = UnsafeArray.Resize(ref array, 19); for (var i = 0; i < array.Length; i++) That(resized[i], Is.EqualTo(i)); resized.Dispose(); } [Test] public void UnsafeArrayEquals() { using var a = new UnsafeArray(8); var b = a; That(a, Is.EqualTo(b)); That(a == b, Is.True); } [Test] public void UnsafeArrayNotEquals() { using var a = new UnsafeArray(8); using var b = new UnsafeArray(8); That(a, Is.Not.EqualTo(b)); That(a != b, Is.True); } } ================================================ FILE: Arch.LowLevel.Tests/UnsafeListTest.cs ================================================ using System.Collections; using static NUnit.Framework.Assert; namespace Arch.LowLevel.Tests; /// /// Checks related methods. /// [TestFixture] public class UnsafeListTest { /// /// Checks if is capable of adding items. /// [Test] public void UnsafeListAdd() { using var list = new UnsafeList(8); That(list.IsReadOnly, Is.False); list.Add(1); list.Add(2); list.Add(3); That(list.Count, Is.EqualTo(3)); } /// /// Checks if GetHashCode is different for different lists /// [Test] public void UnsafeListGetHashCode() { using var list1 = new UnsafeList(8); using var list2 = new UnsafeList(8); That(list1.GetHashCode(), Is.Not.EqualTo(list2.GetHashCode())); } /// /// Checks if can access items by index /// [Test] public void UnsafeListRefIndex() { using var list = new UnsafeList(8); list.Add(7); ref var item0 = ref list[0]; That(item0, Is.EqualTo(7)); item0 = 11; That(list[0], Is.EqualTo(11)); #if DEBUG Throws(() => { var x = list[-1]; }); Throws(() => { var x = list[2]; }); #endif } /// /// Checks if can access items by index /// [Test] public void UnsafeListIndex() { using var unsafelist = new UnsafeList(8); unsafelist.Add(7); var list = (IList)unsafelist; That(list[0], Is.EqualTo(7)); list[0] = 11; That(list[0], Is.EqualTo(11)); #if DEBUG Throws(() => { var x = list[-1]; }); Throws(() => { var x = list[2]; }); #endif } /// /// Checks if is capable of being copied to an array. /// [Test] public void UnsafeListCopyTo() { using var list = new UnsafeList(8); list.Add(1); list.Add(2); list.Add(3); var arr = new int[10]; // Basic copy into the array list.CopyTo(arr, 3); CollectionAssert.AreEqual(new[] { 0, 0, 0, 1, 2, 3, 0, 0, 0, 0 }, arr); // Copy into a bad index Throws(() => { list.CopyTo(arr, -3); }); Throws(() => { list.CopyTo(arr, arr.Length + 1); }); // Copy into an index near the end, so there's not enough space Throws(() => list.CopyTo(arr, 8)); // Check that copying into an array from an empty list does nothing list.Clear(); var arr2 = arr.ToArray(); list.CopyTo(arr, 0); CollectionAssert.AreEqual(arr, arr2); } /// /// Checks if is capable of being cleared. /// [Test] public void UnsafeListClear() { using var list = new UnsafeList(8); list.Add(1); list.Add(2); list.Add(3); list.Clear(); That(list, Is.Empty); } /// /// Checks if is can be converted into a span. /// [Test] public void UnsafeListAsSpan() { using var list = new UnsafeList(8); list.Add(1); list.Add(2); list.Add(3); CollectionAssert.AreEqual(new[] { 1, 2, 3 }, list.AsSpan().ToArray()); } /// /// Checks if equality works as expected /// [Test] public void UnsafeListEquality() { using var list1 = new UnsafeList(8); list1.Add(1); list1.Add(2); list1.Add(3); using var list2 = new UnsafeList(8); list2.Add(1); list2.Add(2); list2.Add(3); That(list1 == list2, Is.False); That(list1 != list2, Is.True); var list1a = list1; That(list1 == list1a, Is.True); That(list1 != list1a, Is.False); That(list1.Equals((object)list2), Is.False); That(list1.Equals((object)list1), Is.True); } /// /// Checks if is capable of adding items at a given index. /// [Test] public void UnsafeListInsertAt() { using var list = new UnsafeList(8); list.Add(1); list.Add(3); list.Insert(1,2); That(list.Count, Is.EqualTo(3)); That(list[0], Is.EqualTo(1)); That(list[1], Is.EqualTo(2)); That(list[2], Is.EqualTo(3)); // Check that adding past the end throws Assert.Throws(() => { list.Insert(5, 5); }); // Add lots of items, to force capacity to grow var count = 10; for (var i = 0; i < count; i++) list.Insert(0, 0); CollectionAssert.AreEqual(new[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3 }, list); } /// /// Checks if is capable of removing itemss. /// [Test] public void UnsafeListRemoveAt() { using var list = new UnsafeList(8); list.Add(1); list.Add(2); list.Add(3); list.RemoveAt(0); list.RemoveAt(2); That(list.Count, Is.EqualTo(1)); That(list[0], Is.EqualTo(2)); Throws(() => { list.RemoveAt(-1); }); Throws(() => { list.RemoveAt(10); }); } /// /// Checks if is capable of removing items by value. /// [Test] public void UnsafeListRemove() { using var list = new UnsafeList(8); list.Add(1); list.Add(2); list.Add(3); That(list.Remove(2), Is.True); That(list.Remove(4), Is.False); That(list.Count, Is.EqualTo(2)); That(list[1], Is.EqualTo(3)); } /// /// Checks if is capable of checking for a contained value. /// [Test] public void UnsafeListContains() { using var list = new UnsafeList(8); list.Add(1); list.Add(2); list.Add(3); That(list.Count, Is.EqualTo(3)); That(list.Contains(2), Is.EqualTo(true)); That(list.Contains(0), Is.EqualTo(false)); } /// /// Checks if is capable of checking for a contained value. /// [Test] public void UnsafeListIndexOf() { using var list = new UnsafeList(8); list.Add(1); list.Add(2); list.Add(3); That(list.Count, Is.EqualTo(3)); That(list.IndexOf(2), Is.EqualTo(1)); That(list.IndexOf(0), Is.EqualTo(-1)); That(list.IndexOf(4), Is.EqualTo(-1)); } /// /// Checks if is capable of ensuring capacity. /// [Test] public void UnsafeListEnsureCapacity() { using var list = new UnsafeList(8); list.EnsureCapacity(16); list.Add(0); list.Add(1); That(list.Capacity, Is.EqualTo(16)); That(list.IndexOf(0), Is.EqualTo(0)); That(list.IndexOf(1), Is.EqualTo(1)); // This should do nothing list.EnsureCapacity(list.Count); That(list.Capacity, Is.EqualTo(16)); } /// /// Checks if is capable of trimming capacity. /// [Test] public void UnsafeListTrimExcess() { using var list = new UnsafeList(16); list.Add(0); list.Add(1); list.TrimExcess(); That(list.Capacity, Is.EqualTo(2)); That(list.IndexOf(0), Is.EqualTo(0)); That(list.IndexOf(1), Is.EqualTo(1)); } /// /// Checks if is capable of iterating with its enumerators. /// [Test] public void UnsafeListEnumerator() { using var list = new UnsafeList(8); list.Add(1); list.Add(2); list.Add(3); // Ref iterator var count = 0; foreach (ref var item in list) { That(++count, Is.EqualTo(item)); } That(count, Is.EqualTo(3)); // Ilist iterator count = 0; foreach (var item in list as IList) { That(++count, Is.EqualTo(item)); } That(count, Is.EqualTo(3)); // non-generic enumerator count = 0; foreach (var item in ((IEnumerable)list)) { That(++count, Is.EqualTo(item)); } That(count, Is.EqualTo(3)); } /// /// Checks if the unsafe list enumerator can be reset /// [Test] public void UnsafeListAsIListEnumeratorReset() { using var list = new UnsafeList(8); list.Add(1); list.Add(2); list.Add(3); using var enumerator = ((IList)list).GetEnumerator(); That(enumerator.MoveNext(), Is.True); That(enumerator.Current, Is.EqualTo(1)); That(enumerator.MoveNext(), Is.True); That(enumerator.Current, Is.EqualTo(2)); enumerator.Reset(); var count = 1; foreach (var item in list) { That(count, Is.EqualTo(item)); count++; } } /// /// Checks if the unsafe list enumerator can be reset /// [Test] public void UnsafeListEnumeratorReset() { using var list = new UnsafeList(8); list.Add(1); list.Add(2); list.Add(3); var enumerator = list.GetEnumerator(); That(enumerator.MoveNext(), Is.True); That(enumerator.Current, Is.EqualTo(1)); That(enumerator.MoveNext(), Is.True); That(enumerator.Current, Is.EqualTo(2)); enumerator.Reset(); var count = 1; foreach (var item in list) { That(count, Is.EqualTo(item)); count++; } } [Test] public void UnsafeListFuzz() { using var list = new UnsafeList(8); var truth = new List(); var rng = new Random(3462345); for (var i = 0; i < 1024; i++) { var index = rng.Next(0, list.Count); var value = rng.Next(); switch (rng.Next(0, 5)) { case 0: { truth.Add(value); list.Add(value); break; } case 1: { truth.Remove(value); list.Remove(value); break; } case 2 when list.Count > 0: { truth.RemoveAt(index); list.RemoveAt(index); break; } case 3: { truth.Insert(index, value); list.Insert(index, value); break; } case 4 when list.Count > 0: { value = truth[index]; That(list, Does.Contain(value)); break; } } CollectionAssert.AreEqual(truth, list); } } } ================================================ FILE: Arch.LowLevel.Tests/UnsafeQueueTest.cs ================================================ namespace Arch.LowLevel.Tests; using static NUnit.Framework.Assert; /// /// Checks related methods. /// [TestFixture] public class UnsafeQueueTest { /// /// Checks if is capable of adding items. /// [Test] public void UnsafeQueueEnqueue() { using var queue = new UnsafeQueue(8); for (var i = 0; i < 20; i++) queue.Enqueue(i); That(queue, Has.Count.EqualTo(20)); That(queue.Peek(), Is.EqualTo(0)); } /// /// Checks if is capable of being converted into a span. /// [Test] public void UnsafeQueueAsSpan() { using var queue = new UnsafeQueue(8); for (var i = 0; i < 9; i++) queue.Enqueue(i); var span = queue.AsSpan(); CollectionAssert.AreEqual(new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 }, span.ToArray()); } /// /// Checks if is capable of peeking itemss. /// [Test] public void UnsafeQueuePeek() { using var queue = new UnsafeQueue(8); queue.Enqueue(1); queue.Enqueue(2); That(queue.Peek(), Is.EqualTo(1)); queue.Enqueue(3); That(queue.Peek(), Is.EqualTo(1)); } /// /// Checks if is capable of popping itemss. /// [Test] public void UnsafeQueueDequeue() { using var queue = new UnsafeQueue(8); queue.Enqueue(1); queue.Enqueue(2); queue.Enqueue(3); That(queue.Dequeue(), Is.EqualTo(1)); That(queue.Dequeue(), Is.EqualTo(2)); That(queue.Dequeue(), Is.EqualTo(3)); Throws(() => { queue.Dequeue(); }); Throws(() => { queue.Peek(); }); } [Test] public void UnsafeQueueClear() { using var queue = new UnsafeQueue(8); for (var i = 0; i < 20; i++) queue.Enqueue(i); That(queue, Has.Count.EqualTo(20)); queue.Clear(); That(queue, Is.Empty); } /// /// Checks if is capable of iterating with its enumerators. /// [Test] public void UnsafeQueueEnumerator() { using var queue = new UnsafeQueue(8); queue.Enqueue(1); queue.Enqueue(2); queue.Enqueue(3); // Ref iterator var count = 0; foreach (ref var item in queue) { count++; } That(count, Is.EqualTo(3)); } /// /// Checks if can be constructed with invalid parameters. /// [Test] public void UnsafeQueueInvalidConstruction() { Throws(() => { new UnsafeQueue(-8); }); } /// /// Checks if EnsureCapacity functions correctly. /// [Test] public void UnsafeQueueEnsureCapacity() { using var queue = new UnsafeQueue(8); That(queue.Capacity, Is.AtLeast(8)); queue.EnsureCapacity(20); That(queue.Capacity, Is.AtLeast(20)); queue.EnsureCapacity(10); That(queue.Capacity, Is.AtLeast(20)); } /// /// Checks if TrimExcess removes all excess capacity. /// [Test] public void UnsafeQueueTrimExcess() { using var queue = new UnsafeQueue(8); for (var i = 0; i < 4; i++) queue.Enqueue(i); That(queue.Capacity, Is.AtLeast(8)); queue.EnsureCapacity(20); That(queue.Capacity, Is.AtLeast(20)); queue.TrimExcess(); That(queue.Capacity, Is.EqualTo(4)); } } ================================================ FILE: Arch.LowLevel.Tests/UnsafeStackTest.cs ================================================ using System.Collections; namespace Arch.LowLevel.Tests; using static NUnit.Framework.Assert; /// /// Checks related methods. /// [TestFixture] public class UnsafeStackTest { /// /// Checks if checks for invalid capacity on construction. /// [Test] public void UnsafeStackInvalidCapacity() { Throws(() => new UnsafeStack(-9)); } /// /// Checks if is capable of adding items. /// [Test] public void UnsafeStackAdd() { using var stack = new UnsafeStack(8); That(stack.IsFull, Is.False); That(stack.IsEmpty, Is.True); stack.Push(1); stack.Push(2); stack.Push(3); That(stack.IsFull, Is.False); That(stack.IsEmpty, Is.False); That(stack.Count, Is.EqualTo(3)); That(stack.Peek(), Is.EqualTo(3)); } /// /// Checks if is can be converted to a span /// [Test] public void UnsafeStackAsSpan() { using var stack = new UnsafeStack(8); stack.Push(1); stack.Push(2); stack.Push(3); var span = stack.AsSpan(); stack.Pop(); stack.Push(4); That(span.Length, Is.EqualTo(3)); CollectionAssert.AreEqual(span.ToArray(), new[] { 1, 2, 4 }); } /// /// Checks if is capable of adding items even past the initial capacity /// [Test] public void UnsafeStackAddBeyondCapacity() { using var stack = new UnsafeStack(4); That(stack.Capacity, Is.EqualTo(4)); stack.Push(1); stack.Push(2); stack.Push(3); stack.Push(4); That(stack.IsFull, Is.True); stack.Push(5); stack.Push(6); stack.Push(7); That(stack.Count, Is.EqualTo(7)); That(stack.Peek(), Is.EqualTo(7)); } /// /// Checks if expands capacity /// [Test] public void UnsafeStackEnsureCapacityExpands() { using var stack = new UnsafeStack(10); That(stack.Capacity, Is.EqualTo(10)); stack.EnsureCapacity(11); That(stack.Capacity, Is.GreaterThanOrEqualTo(11)); } /// /// Checks if cannot shrink the capacity /// [Test] public void UnsafeStackEnsureCapacityCannotShrink() { using var stack = new UnsafeStack(10); That(stack.Capacity, Is.EqualTo(10)); stack.EnsureCapacity(1); That(stack.Capacity, Is.EqualTo(10)); } /// /// Checks if can add a massive amount of new capacity /// [Test] public void UnsafeStackEnsureCapacityExpandsALot() { using var stack = new UnsafeStack(10); That(stack.Capacity, Is.EqualTo(10)); stack.EnsureCapacity(10000); That(stack.Capacity, Is.GreaterThanOrEqualTo(10000)); } /// /// Checks if can remove unused capacity /// [Test] public void UnsafeStackTrimExcessShrinks() { using var stack = new UnsafeStack(10); That(stack.Capacity, Is.EqualTo(10)); stack.TrimExcess(); That(stack.Capacity, Is.LessThan(10)); } /// /// Checks if does not expand /// [Test] public void UnsafeStackTrimExcessNeverExpands() { using var stack = new UnsafeStack(2); stack.TrimExcess(); That(stack.Capacity, Is.LessThanOrEqualTo(2)); } /// /// Checks if is capable of being cleared. /// [Test] public void UnsafeStackClear() { using var stack = new UnsafeStack(8); stack.Push(1); stack.Push(2); stack.Push(3); That(stack.Count, Is.EqualTo(3)); That(stack.Peek(), Is.EqualTo(3)); stack.Clear(); That(stack.Count, Is.EqualTo(0)); Throws(() => stack.Peek()); Throws(() => stack.Pop()); } /// /// Checks if is capable of peeking itemss. /// [Test] public void UnsafeStackPeek() { using var stack = new UnsafeStack(8); stack.Push(1); stack.Push(2); That(stack.Peek(), Is.EqualTo(2)); stack.Push(3); That(stack.Peek(), Is.EqualTo(3)); } /// /// Checks if is capable of popping itemss. /// [Test] public void UnsafeStackPop() { using var stack = new UnsafeStack(8); stack.Push(1); stack.Push(2); stack.Push(3); That(stack.Pop(), Is.EqualTo(3)); That(stack.Pop(), Is.EqualTo(2)); } /// /// Checks if is capable of iterating with its enumerators. /// [Test] public void UnsafeStackEnumerator() { using var stack = new UnsafeStack(8); stack.Push(1); stack.Push(2); stack.Push(3); // Ref iterator var count = 0; foreach (ref var item in stack) { count++; } That(count, Is.EqualTo(3)); } /// /// Checks if the stack enumerator can be reset /// [Test] public void UnsafeStackEnumeratorReset() { using var stack = new UnsafeStack(8); stack.Push(1); stack.Push(2); stack.Push(3); var enumerator = stack.GetEnumerator(); True(enumerator.MoveNext()); That(enumerator.Current, Is.EqualTo(3)); True(enumerator.MoveNext()); That(enumerator.Current, Is.EqualTo(2)); enumerator.Reset(); var count = 3; foreach (var item in stack) { That(count, Is.EqualTo(item)); count--; } } /// /// Checks if is capable of iterating with its enumerators. /// [Test] public void UnsafeStackIEnumerableTEnumerator() { using var stack = new UnsafeStack(8); stack.Push(1); stack.Push(2); stack.Push(3); var enumerable = (IEnumerable)stack; var count = 0; foreach (var item in enumerable) { count++; } That(count, Is.EqualTo(3)); } /// /// Checks if the stack enumerator can be reset /// [Test] public void UnsafeStackIEnumerableEnumeratorReset() { using var stack = new UnsafeStack(8); stack.Push(1); stack.Push(2); stack.Push(3); var enumerator = ((IEnumerable)stack).GetEnumerator(); True(enumerator.MoveNext()); That(enumerator.Current, Is.EqualTo(3)); True(enumerator.MoveNext()); That(enumerator.Current, Is.EqualTo(2)); enumerator.Reset(); var count = 3; foreach (var item in stack) { That(count, Is.EqualTo(item)); count--; } } } ================================================ FILE: Arch.LowLevel.Tests/Usings.cs ================================================ global using NUnit.Framework; ================================================ FILE: Arch.Persistence/Arch.Persistence.csproj ================================================ enable enable net6.0;net7.0;netstandard2.1 11 true bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml true snupkg Arch.Persistence A Persistence-Framework for Arch. Apache2.0 https://github.com/genaray/Arch.Extended https://github.com/genaray/Arch.Extended.git git c#;.net;.net6;.net7;ecs;game;entity;gamedev; game-development; game-engine; entity-component-system; arch; Updated to Arch 2.1.0 2.1.0 en-US true NU1902 ================================================ FILE: Arch.Persistence/Binary.cs ================================================ using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Extensions.Dangerous; using Arch.Core.Utils; using Arch.LowLevel.Jagged; using MessagePack; using MessagePack.Formatters; using System.Runtime.CompilerServices; using Utf8Json; namespace Arch.Persistence; /// /// The class /// is a to (de)serialize a single to or from json. /// public partial class SingleEntityFormatter : IMessagePackFormatter { /// public void Serialize(ref MessagePackWriter writer, Entity value, MessagePackSerializerOptions options) { // Write id writer.WriteInt32(value.Id); #if !PURE_ECS // Write world writer.WriteInt32(value.WorldId); #endif // Write size var componentTypes = value.GetComponentTypes(); writer.WriteInt32(componentTypes.Count); // Write components foreach (ref var type in componentTypes.Components) { // Write type MessagePackSerializer.Serialize(ref writer, type, options); // Write component var cmp = value.Get(type); MessagePackSerializer.Serialize(ref writer, cmp, options); } } /// public Entity Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { // Read id var entityId = reader.ReadInt32(); #if !PURE_ECS // Read world id var worldId = reader.ReadInt32(); #endif // Read size var size = reader.ReadInt32(); var components = new object[size]; // Read components for (var index = 0; index < size; index++) { // Read type var type = MessagePackSerializer.Deserialize(ref reader, options); var cmp = MessagePackSerializer.Deserialize(type, ref reader, options); components[index] = cmp!; } // Create the entity var entity = EntityWorld.Create(); EntityWorld.AddRange(entity, components.AsSpan()); return entity; } } /// /// The class /// is a formatter that (de)serializes structs. /// public partial class EntityFormatter : IMessagePackFormatter { /// public void Serialize(ref MessagePackWriter writer, Entity value, MessagePackSerializerOptions options) { writer.WriteInt32(value.Id); writer.WriteInt32(value.Version); } /// public Entity Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { // Read id var id = reader.ReadInt32(); var version = reader.ReadInt32(); return DangerousEntityExtensions.CreateEntityStruct(id, WorldId, version); } } /// /// The class /// is a to (de)serialize s to or from json. /// public partial class ArrayFormatter : IMessagePackFormatter { /// public void Serialize(ref MessagePackWriter writer, Array value, MessagePackSerializerOptions options) { var type = value.GetType().GetElementType(); // Write type and size MessagePackSerializer.Serialize(ref writer, type, options); writer.WriteUInt32((uint)value.Length); // Write array for (var index = 0; index < value.Length; index++) { var obj = value.GetValue(index); MessagePackSerializer.Serialize(ref writer, obj, options); } } /// public Array Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { // Write type and size var type = MessagePackSerializer.Deserialize(ref reader, options); var size = reader.ReadUInt32(); // Create array var array = Array.CreateInstance(type, size); // Read array for (var index = 0; index < size; index++) { var obj = MessagePackSerializer.Deserialize(type, ref reader, options); array.SetValue(obj, index); } return array; } } /// /// The class /// (de)serializes a . /// /// The type stored in the . public partial class JaggedArrayFormatter : IMessagePackFormatter> { private const int CpuL1CacheSize = 16_384; private readonly T _filler; /// /// Constructor. /// /// Filler. public JaggedArrayFormatter(T filler) { _filler = filler; } /// public void Serialize(ref MessagePackWriter writer, JaggedArray value, MessagePackSerializerOptions options) { // Write length/capacity and items writer.WriteInt32(value.Capacity); for (var index = 0; index < value.Capacity; index++) { var item = value[index]; MessagePackSerializer.Serialize(ref writer, item, options); } } /// public JaggedArray Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { var capacity = reader.ReadInt32(); var jaggedArray = new JaggedArray(CpuL1CacheSize / Unsafe.SizeOf(), _filler,capacity); for (var index = 0; index < capacity; index++) { var item = MessagePackSerializer.Deserialize(ref reader, options); jaggedArray.Add(index, item); } return jaggedArray; } } /// /// The class /// is a to (de)serialize s to or from json. /// public partial class ComponentTypeFormatter : IMessagePackFormatter { /// public void Serialize(ref MessagePackWriter writer, ComponentType value, MessagePackSerializerOptions options) { // Write id writer.WriteUInt32((uint)value.Id); // Write bytesize writer.WriteUInt32((uint)value.ByteSize); } /// public ComponentType Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { var id = reader.ReadUInt32(); var bytesize = reader.ReadUInt32(); return new ComponentType((int)id, (int)bytesize); } } /// /// The class /// is a to (de)serialize s to or from json. /// public partial class SignatureFormatter : IMessagePackFormatter { /// public void Serialize(ref MessagePackWriter writer, Signature value, MessagePackSerializerOptions options) { var componentTypeFormatter = options.Resolver.GetFormatter() as ComponentTypeFormatter; // Write count and types writer.WriteUInt32((uint)value.Count); foreach(var type in value.Components) { componentTypeFormatter!.Serialize(ref writer, type, options); } } /// public Signature Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { var componentTypeFormatter = options.Resolver.GetFormatter() as ComponentTypeFormatter; // Read count var count = reader.ReadUInt32(); // Read types var componentTypes = new ComponentType[count]; for (var index = 0; index < count; index++) { var componentType = componentTypeFormatter!.Deserialize(ref reader, options); componentTypes[index] = componentType; } return new Signature(componentTypes); } } /// /// The class /// is a to (de)serialize s to or from json. /// public partial class EntitySlotFormatter : IMessagePackFormatter { /// public void Serialize(ref MessagePackWriter writer, EntityData value, MessagePackSerializerOptions options) { // Write chunk index writer.WriteUInt32((uint)value.Slot.ChunkIndex); // Write entity index writer.WriteUInt32((uint)value.Slot.Index); } /// public EntityData Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { // Read chunk index and entity index var chunkIndex = reader.ReadUInt32(); var entityIndex = reader.ReadUInt32(); return new EntityData(null!, new Slot((int)entityIndex, (int)chunkIndex), 0); } } /// /// The class /// is a to (de)serialize s to or from json. /// public partial class WorldFormatter : IMessagePackFormatter { /// public void Serialize(ref MessagePackWriter writer, World value, MessagePackSerializerOptions options) { // Write important meta data writer.WriteUInt32((uint)value.BaseChunkSize); writer.WriteUInt32((uint)value.BaseChunkEntityCount); // Write slots MessagePackSerializer.Serialize(ref writer, value.GetEntityDataArray(), options); //Write recycled entity ids var recycledEntityIDs = value.GetRecycledEntityIds(); MessagePackSerializer.Serialize(ref writer, recycledEntityIDs, options); // Write archetypes writer.WriteUInt32((uint)value.Archetypes.Count); foreach (var archetype in value) { MessagePackSerializer.Serialize(ref writer, archetype, options); } } /// public World Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { // Read important metadata var baseChunkSize= reader.ReadUInt32(); var baseChunkEntityCount = reader.ReadUInt32(); // Create world and setup formatter var world = World.Create(chunkSizeInBytes: (int)baseChunkSize, minimumAmountOfEntitiesPerChunk: (int)baseChunkEntityCount); var archetypeFormatter = options.Resolver.GetFormatter() as ArchetypeFormatter; var entityFormatter = options.Resolver.GetFormatter() as EntityFormatter; entityFormatter!.WorldId = world.Id; archetypeFormatter!.World = world; // Read slots var slots = MessagePackSerializer.Deserialize>(ref reader, options); //Read recycled entity ids var recycledEntityIDs = MessagePackSerializer.Deserialize>(ref reader, options); // Forward values to the world world.SetRecycledEntityIds(recycledEntityIDs); world.SetEntityDataArray(slots); world.EnsureCapacity(slots.Capacity); // Read archetypes var size = reader.ReadInt32(); List archetypes = new(); for (var index = 0; index < size; index++) { var archetype = archetypeFormatter.Deserialize(ref reader, options); archetypes.Add(archetype); } // Set archetypes world.SetArchetypes(archetypes); return world; } } /// /// The class /// is a to (de)serialize s to or from json. /// public partial class ArchetypeFormatter : IMessagePackFormatter { /// public void Serialize(ref MessagePackWriter writer, Archetype value, MessagePackSerializerOptions options) { // Setup formatters var types = value.Signature; var chunks = value.Chunks; var chunkFormatter = options.Resolver.GetFormatter() as ChunkFormatter; chunkFormatter!.Signature = types; // Write type array MessagePackSerializer.Serialize(ref writer, types, options); // Write lookup array MessagePackSerializer.Serialize(ref writer, value.GetLookupArray(), options); // Write chunk size writer.WriteUInt32((uint)value.ChunkCount); // Write chunks for (var index = 0; index < value.ChunkCount; index++) { ref var chunk = ref chunks[index]; chunkFormatter.Serialize(ref writer, chunk, options); } } /// public Archetype Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { var chunkFormatter = options.Resolver.GetFormatter() as ChunkFormatter; // Types var types = MessagePackSerializer.Deserialize(ref reader, options); // Archetype lookup array var lookupArray = MessagePackSerializer.Deserialize(ref reader, options); // Archetype chunk size and list var chunkSize = reader.ReadUInt32(); // Create archetype var chunks = new List((int)chunkSize); var archetype = DangerousArchetypeExtensions.CreateArchetype(World.BaseChunkSize, World.BaseChunkEntityCount, types); archetype.Chunks.Clear(true); archetype.SetCount((int)chunkSize - 1); // Pass types and lookup array to the chunk formatter for saving performance and memory chunkFormatter!.World = World; chunkFormatter.Archetype = archetype; chunkFormatter.Signature = types; chunkFormatter.LookupArray = lookupArray; // Deserialise each chunk and put it into the archetype. var entities = 0; for (var index = 0; index < chunkSize; index++) { var chunk = chunkFormatter.Deserialize(ref reader, options); chunks.Add(chunk); entities += chunk.Count; } archetype.SetChunks(chunks); archetype.SetEntities(entities); return archetype; } } /// /// The class /// is a to (de)serialize s to or from json. /// public partial class ChunkFormatter : IMessagePackFormatter { /// public void Serialize(ref MessagePackWriter writer, Chunk value, MessagePackSerializerOptions options) { // Write size writer.WriteUInt32((uint)value.Count); // Write capacity writer.WriteUInt32((uint)value.Capacity); // Write entitys MessagePackSerializer.Serialize(ref writer, value.Entities, options); // Persist arrays as an array... foreach(var type in Signature.Components) { // Write array itself var array = value.GetArray(type); MessagePackSerializer.Serialize(ref writer, array, options); } } /// public Chunk Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { // Read chunk size var size = reader.ReadUInt32(); // Read chunk size var capacity = reader.ReadUInt32(); // Read entities var entities = MessagePackSerializer.Deserialize(ref reader, options); // Create chunk var chunk = DangerousChunkExtensions.CreateChunk((int)capacity, LookupArray, Signature); entities.CopyTo(chunk.Entities, 0); chunk.SetSize((int)size); // Updating World.EntityInfoStorage to their new archetype for (var index = 0; index < size; index++) { ref var entity = ref chunk.Entity(index); entity = DangerousEntityExtensions.CreateEntityStruct(entity.Id, World.Id, entity.Version); World.SetArchetype(entity, Archetype); } // Persist arrays as an array... foreach(var type in Signature.Components) { // Read array of the type var array = MessagePackSerializer.Deserialize(ref reader, options); var chunkArray = chunk.GetArray(array.GetType().GetElementType()!); Array.Copy(array, chunkArray, (int)size); } return chunk; } } ================================================ FILE: Arch.Persistence/Json.cs ================================================ using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Extensions.Dangerous; using Arch.Core.Utils; using Arch.LowLevel.Jagged; using CommunityToolkit.HighPerformance; using System.Runtime.CompilerServices; using Utf8Json; namespace Arch.Persistence; /// /// The class /// is a to (de)serialize a single to or from json. /// public partial class SingleEntityFormatter : IJsonFormatter { /// /// The the entity belongs to. /// internal World EntityWorld { get; set; } = null!; /// public void Serialize(ref JsonWriter writer, Entity value, IJsonFormatterResolver formatterResolver) { writer.WriteBeginObject(); // Write id writer.WritePropertyName("id"); writer.WriteInt32(value.Id); writer.WriteValueSeparator(); #if !PURE_ECS // Write world writer.WritePropertyName("worldId"); writer.WriteInt32(value.WorldId); writer.WriteValueSeparator(); #endif // Write size var componentTypes = value.GetComponentTypes(); writer.WritePropertyName("size"); writer.WriteInt32(componentTypes.Count); writer.WriteValueSeparator(); // Write components writer.WritePropertyName("components"); writer.WriteBeginArray(); foreach (ref var type in componentTypes.Components) { // Write type writer.WriteBeginObject(); writer.WritePropertyName("type"); JsonSerializer.Serialize(ref writer, type); writer.WriteValueSeparator(); // Write component writer.WritePropertyName("component"); var cmp = value.Get(type); JsonSerializer.NonGeneric.Serialize(ref writer, cmp, formatterResolver); writer.WriteEndObject(); writer.WriteValueSeparator(); } writer.AdvanceOffset(-1); writer.WriteEndArray(); writer.WriteEndObject(); } /// public Entity Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) { reader.ReadIsBeginObject(); // Read id reader.ReadPropertyName(); var entityId = reader.ReadInt32(); reader.ReadIsValueSeparator(); #if !PURE_ECS // Read world id reader.ReadPropertyName(); var worldId = reader.ReadInt32(); reader.ReadIsValueSeparator(); #endif // Read size reader.ReadPropertyName(); var size = reader.ReadInt32(); reader.ReadIsValueSeparator(); var components = new object[size]; var count = 0; // Read components reader.ReadPropertyName(); reader.ReadIsBeginArray(); while (!reader.ReadIsEndArrayWithSkipValueSeparator(ref count)) { reader.ReadIsBeginObject(); // Read type reader.ReadPropertyName(); var type = JsonSerializer.Deserialize(ref reader); reader.ReadIsValueSeparator(); reader.ReadPropertyName(); var cmp = JsonSerializer.NonGeneric.Deserialize(type.Type, ref reader, formatterResolver); components[count - 1] = cmp; reader.ReadIsEndObject(); } // Creat the entity var entity = EntityWorld.Create(); EntityWorld.AddRange(entity, components.AsSpan()); reader.ReadIsEndObject(); return entity; } } public partial class EntityFormatter : IJsonFormatter { /// /// The all deserialized s will belong to. /// Due to the nature of deserialisation and changing world landscape we need to assign new WorldIds to the deserialized entities. /// internal int WorldId { get; set; } /// public void Serialize(ref JsonWriter writer, Entity value, IJsonFormatterResolver formatterResolver) { writer.WriteInt32(value.Id); writer.WriteValueSeparator(); writer.WriteInt32(value.Version); } /// public Entity Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) { // Read id var id = reader.ReadInt32(); reader.ReadIsValueSeparator(); var version = reader.ReadInt32(); return DangerousEntityExtensions.CreateEntityStruct(id, WorldId, version); } } /// /// The class /// is a to (de)serialize s to or from json. /// public partial class ArrayFormatter : IJsonFormatter { /// public void Serialize(ref JsonWriter writer, Array value, IJsonFormatterResolver formatterResolver) { var type = value.GetType().GetElementType(); // Write type and size writer.WriteBeginObject(); writer.WritePropertyName("type"); JsonSerializer.Serialize(ref writer, type, formatterResolver); writer.WriteValueSeparator(); writer.WritePropertyName("length"); writer.WriteUInt32((uint)value.Length); writer.WriteValueSeparator(); // Write array writer.WritePropertyName("items"); writer.WriteBeginArray(); for (var index = 0; index < value.Length; index++) { var obj = value.GetValue(index); JsonSerializer.NonGeneric.Serialize(ref writer, obj, formatterResolver); writer.WriteValueSeparator(); } writer.AdvanceOffset(-1); writer.WriteEndArray(); writer.WriteEndObject(); } /// public Array Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) { // Write type and size reader.ReadIsBeginObject(); reader.ReadPropertyName(); var type = JsonSerializer.Deserialize(ref reader, formatterResolver); reader.ReadIsValueSeparator(); reader.ReadPropertyName(); var size = reader.ReadUInt32(); reader.ReadIsValueSeparator(); // Create array var array = Array.CreateInstance(type, size); // Read array reader.ReadPropertyName(); reader.ReadIsBeginArray(); for (var index = 0; index < size; index++) { var obj = JsonSerializer.NonGeneric.Deserialize(type, ref reader, formatterResolver); array.SetValue(obj, index); reader.ReadIsValueSeparator(); } reader.ReadIsEndArray(); reader.ReadIsEndObject(); return array; } } /// /// The class /// (de)serializes a . /// public partial class JaggedArrayFormatter : IJsonFormatter> { /// public void Serialize(ref JsonWriter writer, JaggedArray value, IJsonFormatterResolver formatterResolver) { writer.WriteBeginObject(); // Write length/capacity and items writer.WritePropertyName("capacity"); writer.WriteInt32(value.Capacity); writer.WriteValueSeparator(); // Write items writer.WritePropertyName("items"); writer.WriteBeginArray(); for (var index = 0; index < value.Capacity; index++) { var item = value[index]; JsonSerializer.Serialize(ref writer, item, formatterResolver); writer.WriteValueSeparator(); } // Cut last value seperator if (value.Capacity > 0) { writer.AdvanceOffset(-1); } writer.WriteEndArray(); writer.WriteEndObject(); } /// public JaggedArray Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) { reader.ReadIsBeginObject(); // Read capacity; reader.ReadPropertyName(); var capacity = reader.ReadInt32(); reader.ReadIsValueSeparator(); // Read items var jaggedArray = new JaggedArray(CpuL1CacheSize / Unsafe.SizeOf(), _filler,capacity); reader.ReadPropertyName(); reader.ReadIsBeginArray(); for (var index = 0; index < capacity; index++) { var item = JsonSerializer.Deserialize(ref reader, formatterResolver); jaggedArray.Add(index, item); reader.ReadIsValueSeparator(); } reader.ReadIsEndArray(); reader.ReadIsEndObject(); return jaggedArray; } } /// /// The class /// is a to (de)serialize s to or from json. /// public partial class ComponentTypeFormatter : IJsonFormatter { /// public void Serialize(ref JsonWriter writer, ComponentType value, IJsonFormatterResolver formatterResolver) { writer.WriteBeginObject(); // Write id writer.WritePropertyName("id"); writer.WriteUInt32((uint)value.Id); writer.WriteValueSeparator(); // Write bytesize writer.WritePropertyName("byteSize"); writer.WriteUInt32((uint)value.ByteSize); writer.WriteEndObject(); } /// public ComponentType Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) { reader.ReadIsBeginObject(); reader.ReadPropertyName(); var id = reader.ReadUInt32(); reader.ReadIsValueSeparator(); reader.ReadPropertyName(); var bytesize = reader.ReadUInt32(); reader.ReadIsValueSeparator(); reader.ReadIsEndObject(); return new ComponentType((int)id, (int)bytesize); } } /// /// The class /// is a to (de)serialize s to or from json. /// public partial class SignatureFormatter : IJsonFormatter { /// public void Serialize(ref JsonWriter writer, Signature value, IJsonFormatterResolver formatterResolver) { var componentTypeFormatter = formatterResolver.GetFormatter() as ComponentTypeFormatter; writer.WriteBeginObject(); // write Count writer.WritePropertyName("count"); writer.WriteUInt32((uint)value.Count); writer.WriteValueSeparator(); // Write components writer.WritePropertyName("components"); writer.WriteBeginArray(); foreach (var type in value.Components) { componentTypeFormatter!.Serialize(ref writer, type, formatterResolver); writer.WriteValueSeparator(); } // Cut last value seperator if (value.Count > 0) { writer.AdvanceOffset(-1); } writer.WriteEndArray(); writer.WriteEndObject(); } /// public Signature Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) { var componentTypeFormatter = formatterResolver.GetFormatter() as ComponentTypeFormatter; reader.ReadIsBeginObject(); reader.ReadPropertyName(); // Read count var count = (int)reader.ReadUInt32(); reader.ReadIsValueSeparator(); // Read types reader.ReadPropertyName(); reader.ReadIsBeginArray(); var componentTypes = new ComponentType[count]; count = 0; while (!reader.ReadIsEndArrayWithSkipValueSeparator(ref count)) { var archetype = componentTypeFormatter!.Deserialize(ref reader, formatterResolver); componentTypes[count - 1] = (archetype); } // Set archetypes reader.ReadIsEndObject(); return new Signature(componentTypes); } } /// /// The class /// is a to (de)serialize s to or from json. /// public partial class EntitySlotFormatter : IJsonFormatter { /// public void Serialize(ref JsonWriter writer, EntityData value, IJsonFormatterResolver options) { writer.WriteBeginObject(); // Write chunk index writer.WritePropertyName("chunkIndex"); writer.WriteUInt32((uint)value.Slot.ChunkIndex); writer.WriteValueSeparator(); // Write entity index writer.WritePropertyName("index"); writer.WriteUInt32((uint)value.Slot.Index); writer.WriteEndObject(); } /// public EntityData Deserialize(ref JsonReader reader, IJsonFormatterResolver options) { reader.ReadIsBeginObject(); // Read chunk index reader.ReadPropertyName(); var chunkIndex = reader.ReadUInt32(); reader.ReadIsValueSeparator(); // Read entity index reader.ReadPropertyName(); var entityIndex = reader.ReadUInt32(); reader.ReadIsEndObject(); return new EntityData(null!, new Slot((int)entityIndex, (int)chunkIndex), 0); } } /// /// The class /// is a to (de)serialize s to or from json. /// public partial class WorldFormatter : IJsonFormatter { /// public void Serialize(ref JsonWriter writer, World value, IJsonFormatterResolver formatterResolver) { //var archetypeFormatter = formatterResolver.GetFormatter(); //var versionsFormatter = formatterResolver.GetFormatter(); //var slotFormatter = formatterResolver.GetFormatter<(int,int)[][]>(); writer.WriteBeginObject(); // Write meta data writer.WritePropertyName("baseChunkSize"); writer.WriteUInt32((uint)value.BaseChunkSize); writer.WriteValueSeparator(); writer.WritePropertyName("baseChunkEntityCount"); writer.WriteUInt32((uint)value.BaseChunkEntityCount); writer.WriteValueSeparator(); // Write slots writer.WritePropertyName("slots"); JsonSerializer.Serialize(ref writer, value.GetEntityDataArray(), formatterResolver); writer.WriteValueSeparator(); //Write recycled entity ids writer.WritePropertyName("recycledEntityIDs"); writer.WriteBeginArray(); var recycledEntityIDs = value.GetRecycledEntityIds(); foreach (var recycledId in recycledEntityIDs) { writer.WriteBeginObject(); writer.WritePropertyName("id"); writer.WriteInt32(recycledId.Item1); writer.WriteValueSeparator(); writer.WritePropertyName("version"); writer.WriteInt32(recycledId.Item2); writer.WriteEndObject(); writer.WriteValueSeparator(); } // Cut last value seperator if (recycledEntityIDs.Count > 0) { writer.AdvanceOffset(-1); } writer.WriteEndArray(); writer.WriteValueSeparator(); //Write archetypes writer.WritePropertyName("archetypes"); writer.WriteBeginArray(); foreach (var archetype in value) { JsonSerializer.Serialize(ref writer, archetype, formatterResolver); writer.WriteValueSeparator(); } // Cut last value seperator if (value.Archetypes.Count > 0) { writer.AdvanceOffset(-1); } writer.WriteEndArray(); writer.WriteEndObject(); } /// public World Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) { // Create world and setup formatter var archetypeFormatter = formatterResolver.GetFormatter() as ArchetypeFormatter; var entityFormatter = formatterResolver.GetFormatter() as EntityFormatter; reader.ReadIsBeginObject(); // Read meta data reader.ReadPropertyName(); var baseChunkSize = reader.ReadUInt32(); reader.ReadIsValueSeparator(); reader.ReadPropertyName(); var baseChunkEntityCount = reader.ReadUInt32(); reader.ReadIsValueSeparator(); // Construct world var world = World.Create(chunkSizeInBytes: (int)baseChunkSize, minimumAmountOfEntitiesPerChunk: (int)baseChunkEntityCount); entityFormatter!.WorldId = world.Id; archetypeFormatter!.World = world; // Read slots reader.ReadPropertyName(); var slots = JsonSerializer.Deserialize>(ref reader, formatterResolver); reader.ReadIsValueSeparator(); // Read recycled ids var count = 0; List<(int, int)> recycledIds = new(); reader.ReadPropertyName(); reader.ReadIsBeginArray(); while (!reader.ReadIsEndArrayWithSkipValueSeparator(ref count)) { reader.ReadIsBeginObject(); reader.ReadPropertyName(); var id = reader.ReadInt32(); reader.ReadIsValueSeparator(); reader.ReadPropertyName(); var value = reader.ReadInt32(); reader.ReadIsEndObject(); (int, int) recycledId = new(id, value); recycledIds.Add(recycledId); } reader.ReadIsValueSeparator(); // Forward values to the world world.SetRecycledEntityIds(recycledIds); world.SetEntityDataArray(slots); world.EnsureCapacity(slots.Capacity); // Read archetypes count = 0; List archetypes = new(); reader.ReadPropertyName(); reader.ReadIsBeginArray(); while (!reader.ReadIsEndArrayWithSkipValueSeparator(ref count)) { var archetype = archetypeFormatter.Deserialize(ref reader, formatterResolver); archetypes.Add(archetype); } // Set archetypes world.SetArchetypes(archetypes); reader.ReadIsEndObject(); return world; } } /// /// The class /// is a to (de)serialize s to or from json. /// public partial class ArchetypeFormatter : IJsonFormatter { /// /// The which is being used by this formatter during serialisation/deserialisation. /// internal World World { get; set; } = null!; /// public void Serialize(ref JsonWriter writer, Archetype value, IJsonFormatterResolver formatterResolver) { // Setup formatters var types = value.Signature; var chunks = value.Chunks; var chunkFormatter = formatterResolver.GetFormatter() as ChunkFormatter; chunkFormatter!.Signature = types; writer.WriteBeginObject(); // Write type array writer.WritePropertyName("types"); JsonSerializer.Serialize(ref writer, types, formatterResolver); writer.WriteValueSeparator(); // Write lookup array writer.WritePropertyName("lookup"); JsonSerializer.Serialize(ref writer, value.GetLookupArray(), formatterResolver); writer.WriteValueSeparator(); // Write chunk size writer.WritePropertyName("chunkCount"); writer.WriteUInt32((uint)value.ChunkCount); writer.WriteValueSeparator(); // Write chunks writer.WritePropertyName("chunks"); writer.WriteBeginArray(); for (var index = 0; index < value.ChunkCount; index++) { ref var chunk = ref chunks[index]; chunkFormatter.Serialize(ref writer, chunk, formatterResolver); writer.WriteValueSeparator(); } // Trim last value separator if (value.ChunkCount > 0) { writer.AdvanceOffset(-1); } writer.WriteEndArray(); writer.WriteEndObject(); } /// public Archetype Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) { var chunkFormatter = formatterResolver.GetFormatter() as ChunkFormatter; reader.ReadIsBeginObject(); // Types reader.ReadPropertyName(); var types = JsonSerializer.Deserialize(ref reader, formatterResolver); reader.ReadIsValueSeparator(); // Archetype lookup array reader.ReadPropertyName(); var lookupArray = JsonSerializer.Deserialize(ref reader, formatterResolver); reader.ReadIsValueSeparator(); // Archetype chunk size and list reader.ReadPropertyName(); var chunkCount = reader.ReadUInt32(); reader.ReadIsValueSeparator(); // Create archetype var chunks = new List((int)chunkCount); var archetype = DangerousArchetypeExtensions.CreateArchetype(World.BaseChunkSize, World.BaseChunkEntityCount, types); archetype.Chunks.Clear(true); archetype.SetCount((int)chunkCount - 1); // Pass types and lookup array to the chunk formatter for saving performance and memory chunkFormatter!.World = World; chunkFormatter.Archetype = archetype; chunkFormatter.Signature = types; chunkFormatter.LookupArray = lookupArray; // Deserialise each chunk and put it into the archetype. reader.ReadPropertyName(); reader.ReadIsBeginArray(); var entities = 0; for (var index = 0; index < chunkCount; index++) { var chunk = chunkFormatter.Deserialize(ref reader, formatterResolver); chunks.Add(chunk); entities += chunk.Count; reader.ReadIsValueSeparator(); } archetype.SetChunks(chunks); archetype.SetEntities(entities); reader.ReadIsEndArray(); reader.ReadIsEndObject(); return archetype; } } /// /// The class /// is a to (de)serialize s to or from json. /// public partial class ChunkFormatter : IJsonFormatter { /// /// The the current (de)serialized belongs to. /// Since chunks do not know this, we need to pass this information along it. /// internal World World { get; set; } = null!; /// /// The the current (de)serialized belongs to. /// Since chunks do not know this, we need to pass this information along it. /// internal Archetype Archetype { get; set; } = null!; /// /// The types used in the in each (de)serialized by this formatter. /// Since does not have a reference to them and its controlled by its . /// internal Signature Signature { get; set; } = Signature.Null; /// /// The lookup array used by each (de)serialized by this formatter. /// Since does not have a reference to them and its controlled by its . /// internal int[] LookupArray { get; set; } = Array.Empty(); /// public void Serialize(ref JsonWriter writer, Chunk value, IJsonFormatterResolver formatterResolver) { writer.WriteBeginObject(); // Write size writer.WritePropertyName("count"); writer.WriteUInt32((uint)value.Count); writer.WriteValueSeparator(); // Write capacity writer.WritePropertyName("capacity"); writer.WriteUInt32((uint)value.Capacity); writer.WriteValueSeparator(); // Write entitys writer.WritePropertyName("entities"); JsonSerializer.NonGeneric.Serialize(ref writer, value.Entities, formatterResolver); writer.WriteValueSeparator(); // Persist arrays as an array... writer.WritePropertyName("arrays"); writer.WriteBeginArray(); foreach(var type in Signature.Components) { // Write array itself var array = value.GetArray(type); JsonSerializer.Serialize(ref writer, array, formatterResolver); writer.WriteValueSeparator(); } // Remove trailing if (Signature.Count > 0) { writer.AdvanceOffset(-1); } writer.WriteEndArray(); writer.WriteEndObject(); } /// public Chunk Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) { reader.ReadIsBeginObject(); // Read chunk size reader.ReadPropertyName(); var size = reader.ReadUInt32(); reader.ReadIsValueSeparator(); // Read chunk size reader.ReadPropertyName(); var capacity = reader.ReadUInt32(); reader.ReadIsValueSeparator(); // Read entities reader.ReadPropertyName(); var entities = JsonSerializer.Deserialize(ref reader, formatterResolver); reader.ReadIsValueSeparator(); // Create chunk var chunk = DangerousChunkExtensions.CreateChunk((int)capacity, LookupArray, Signature); entities.CopyTo(chunk.Entities, 0); chunk.SetSize((int)size); // Updating World.EntityInfoStorage to their new archetype for (var index = 0; index < size; index++) { ref var entity = ref chunk.Entity(index); entity = DangerousEntityExtensions.CreateEntityStruct(entity.Id, World.Id, entity.Version); World.SetArchetype(entity, Archetype); } // Persist arrays as an array... reader.ReadPropertyName(); reader.ReadIsBeginArray(); foreach(var type in Signature) { // Read array of the type var array = JsonSerializer.Deserialize(ref reader, formatterResolver); var chunkArray = chunk.GetArray(array.GetType().GetElementType()!); Array.Copy(array, chunkArray, (int)size); reader.ReadIsValueSeparator(); } reader.ReadIsEndArray(); reader.ReadIsEndObject(); return chunk; } } ================================================ FILE: Arch.Persistence/Serializer.cs ================================================ using Arch.Core; using MessagePack; using MessagePack.Formatters; using System.Buffers; using Utf8Json; using Utf8Json.Resolvers; using DateTimeFormatter = Utf8Json.Formatters.DateTimeFormatter; using NullableDateTimeFormatter = Utf8Json.Formatters.NullableDateTimeFormatter; namespace Arch.Persistence; /// /// The interface /// represents an interface with shared methods to (de)serialize worlds and entities. /// It might happen that the serialized object is too large to fit into a regular c# byte-array. In this case use the -API. /// public interface IArchSerializer { /// /// Serializes an to a -array. /// /// The . /// The . byte[] Serialize(World world, Entity entity); /// /// Serializes an to a e.g. a File or existing array. /// /// The . /// The . /// The . void Serialize(Stream stream, World world, Entity entity); /// /// Serializes an to a e.g. a File or existing array. /// /// The . /// The . /// The . void Serialize(IBufferWriter writer, World world, Entity entity); /// /// Deserializes an from its bytes to an real in a . /// The new and will differ. /// /// The . /// The . /// Entity Deserialize(World world, byte[] entity); /// /// Deserializes an from its bytes to an real in a . /// The new and will differ. /// /// The . /// The . /// Entity Deserialize(Stream stream, World world); /// /// Serializes a to a -array. /// /// The . byte[] Serialize(World world); /// /// Serializes a to a . /// /// The . /// The . void Serialize(Stream stream, World world); /// /// Serializes a to a . /// /// The . /// The . void Serialize(IBufferWriter writer, World world); /// /// Deserializes a byte-array into a . /// /// The as an byte-array. /// The new . World Deserialize(byte[] world); /// /// Deserializes a byte-array into a . /// /// The . /// The new . World Deserialize(Stream stream); } /// /// The class /// represents a binary serializer for arch to (de)serialize single entities and whole worlds by binary. /// public class ArchBinarySerializer : IArchSerializer { /// /// The default formatters used to (de)serialize the . /// private readonly IMessagePackFormatter[] _formatters = { new WorldFormatter(), new ArchetypeFormatter(), new ChunkFormatter(), new ArrayFormatter(), new ComponentTypeFormatter(), new SignatureFormatter(), new EntitySlotFormatter(), new EntityFormatter(), new JaggedArrayFormatter(-1), new JaggedArrayFormatter<(int,int)>((-1,-1)), new JaggedArrayFormatter(new EntityData(null!, new Slot(-1,-1), -1)) }; /// /// The default formatters used to (de)serialize a single . /// private readonly IMessagePackFormatter[] _singleEntityFormatters = { new ComponentTypeFormatter(), new SignatureFormatter(), new SingleEntityFormatter() }; /// /// The standard for world (de)serialization. /// private readonly MessagePackSerializerOptions _options; /// /// The standard for single entity (de)serialization. /// private readonly MessagePackSerializerOptions _singleEntityOptions; /// /// The static constructor gets called during compile time to setup the serializer. /// public ArchBinarySerializer(params IMessagePackFormatter[] custFormatters) { // Register all important jsonformatters _options = MessagePackSerializerOptions.Standard.WithResolver( MessagePack.Resolvers.CompositeResolver.Create( _formatters.Concat(custFormatters).ToList(), new List { MessagePack.Resolvers.BuiltinResolver.Instance, MessagePack.Resolvers.ContractlessStandardResolverAllowPrivate.Instance } ) ); _singleEntityOptions = MessagePackSerializerOptions.Standard.WithResolver( MessagePack.Resolvers.CompositeResolver.Create( _singleEntityFormatters.Concat(custFormatters).ToList(), new List { MessagePack.Resolvers.BuiltinResolver.Instance, MessagePack.Resolvers.ContractlessStandardResolverAllowPrivate.Instance } ) ); } /// public byte[] Serialize(World world, Entity entity) { (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world; return MessagePackSerializer.Serialize(entity, _singleEntityOptions); } /// public void Serialize(Stream stream, World world, Entity entity) { (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world; MessagePackSerializer.Serialize(stream, entity, _singleEntityOptions); } /// public void Serialize(IBufferWriter writer, World world, Entity entity) { (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world; MessagePackSerializer.Serialize(writer, entity, _singleEntityOptions); } /// public Entity Deserialize(World world, byte[] entity) { (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world; return MessagePackSerializer.Deserialize(entity, _singleEntityOptions); } /// public Entity Deserialize(Stream stream, World world) { (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world; return MessagePackSerializer.Deserialize(stream, _singleEntityOptions); } /// public byte[] Serialize(World world) { return MessagePackSerializer.Serialize(world, _options); ; } /// public void Serialize(Stream stream, World world) { MessagePackSerializer.Serialize(stream, world, _options); } /// public void Serialize(IBufferWriter writer, World world) { MessagePackSerializer.Serialize(writer, world, _options); } /// public World Deserialize(byte[] world) { return MessagePackSerializer.Deserialize(world, _options); } /// public World Deserialize(Stream stream) { return MessagePackSerializer.Deserialize(stream, _options); } } /// /// The class /// represents a json serializer for arch to (de)serialize single entities and whole worlds by binary. /// public class ArchJsonSerializer : IArchSerializer { /// /// The default formatters used to (de)serialize the . /// private readonly IJsonFormatter[] _formatters = { new WorldFormatter(), new ArchetypeFormatter(), new ChunkFormatter(), new ArrayFormatter(), new ComponentTypeFormatter(), new SignatureFormatter(), new EntitySlotFormatter(), new EntityFormatter(), new JaggedArrayFormatter(-1), new JaggedArrayFormatter<(int,int)>((-1,-1)), new JaggedArrayFormatter(new EntityData(null!, new Slot(-1, -1), -1)), new DateTimeFormatter("yyyy-MM-dd HH:mm:ss"), new NullableDateTimeFormatter("yyyy-MM-dd HH:mm:ss") }; /// /// The default formatters used to (de)serialize a single . /// private readonly IJsonFormatter[] _singleEntityFormatters = { new ComponentTypeFormatter(), new SignatureFormatter(), new SingleEntityFormatter(), new DateTimeFormatter("yyyy-MM-dd HH:mm:ss"), new NullableDateTimeFormatter("yyyy-MM-dd HH:mm:ss") }; // It can `not` garbage collect and create is slightly high cost. // so you should store to static field. private IJsonFormatterResolver _formatterResolver; // CompositeResolver.Create can create dynamic composite resolver. // It can `not` garbage collect and create is slightly high cost. // so you should store to static field. private IJsonFormatterResolver _singleEntityFormatterResolver; /// /// The static constructor gets called during compile time to setup the serializer. /// public ArchJsonSerializer(params IJsonFormatter[] custFormatters) { // Register all important jsonformatters _formatterResolver = CompositeResolver.Create( _formatters.Concat(custFormatters).ToArray(), new[] { EnumResolver.UnderlyingValue, StandardResolver.AllowPrivateExcludeNullSnakeCase, BuiltinResolver.Instance, DynamicGenericResolver.Instance, } ); _singleEntityFormatterResolver = CompositeResolver.Create( _singleEntityFormatters.Concat(custFormatters).ToArray(), new[] { EnumResolver.UnderlyingValue, StandardResolver.AllowPrivateExcludeNullSnakeCase, } ); } /// /// The static constructor gets called during compile time to setup the serializer. /// This variant allows custom resolvers to be passed in as well. /// public ArchJsonSerializer(IJsonFormatter[] custFormatters, IJsonFormatterResolver[] custResolvers) { // Register all important jsonformatters _formatterResolver = CompositeResolver.Create( _formatters.Concat(custFormatters).ToArray(), custResolvers.Concat(new[] { EnumResolver.UnderlyingValue, StandardResolver.AllowPrivateExcludeNullSnakeCase, BuiltinResolver.Instance, DynamicGenericResolver.Instance, }).ToArray() ); _singleEntityFormatterResolver = CompositeResolver.Create( _singleEntityFormatters.Concat(custFormatters).ToArray(), custResolvers.Concat(new[] { EnumResolver.UnderlyingValue, StandardResolver.AllowPrivateExcludeNullSnakeCase, }).ToArray() ); } /// /// Serializes the given to a json-string. /// /// The to serialize. /// Its json-string. public string ToJson(World world) { return JsonSerializer.ToJsonString(world, _formatterResolver); } /// /// Serializes the given to a json-string. /// /// The the entity belongs to.. /// The . /// Its json-string. /// A json-string of the entity with all its components. public string ToJson(World world, Entity entity) { (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world; return JsonSerializer.ToJsonString(entity, _singleEntityFormatterResolver); } /// /// Deserializes the given json to a . /// /// The json to deserialize. /// A new . public World FromJson(string jsonWorld) { return JsonSerializer.Deserialize(jsonWorld, _formatterResolver); } /// /// Deserializes the given json to a . /// The deserialized will receive a new id and a new worldId. /// /// The to deserialize the entity into. /// The json of the entity to deserialize. /// A new . public Entity FromJson(World world, string jsonEntity) { (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world; return JsonSerializer.Deserialize(jsonEntity, _singleEntityFormatterResolver); } /// public byte[] Serialize(World world, Entity entity) { (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world; return JsonSerializer.Serialize(entity, _singleEntityFormatterResolver); } /// public void Serialize(Stream stream, World world, Entity entity) { (_singleEntityFormatters[1] as SingleEntityFormatter)!.EntityWorld = world; JsonSerializer.Serialize(stream, entity, _singleEntityFormatterResolver); } /// public void Serialize(IBufferWriter writer, World world, Entity entity) { throw new NotImplementedException(); } /// public Entity Deserialize(World world, byte[] entity) { (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world; return JsonSerializer.Deserialize(entity, _singleEntityFormatterResolver); } /// public Entity Deserialize(Stream stream, World world) { (_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world; return JsonSerializer.Deserialize(stream, _singleEntityFormatterResolver); } /// public byte[] Serialize(World world) { return JsonSerializer.Serialize(world, _formatterResolver); } /// public void Serialize(Stream stream, World world) { JsonSerializer.Serialize(stream, world, _formatterResolver); } /// public void Serialize(IBufferWriter writer, World world) { throw new NotImplementedException(); } /// public World Deserialize(byte[] world) { return JsonSerializer.Deserialize(world, _formatterResolver); } /// public World Deserialize(Stream stream) { return JsonSerializer.Deserialize(stream, _formatterResolver); } } ================================================ FILE: Arch.Persistence/StreamBufferWriter.cs ================================================ using System.Buffers; namespace Arch.Persistence; /// /// The class /// is a small wrapper around a implementing a . /// It buffers incoming bytes in an internally stored array and flushes it regulary into the -. /// public sealed class StreamBufferWriter : IBufferWriter, IDisposable { /// /// The buffer. /// private byte[] _buffer; /// /// The . /// private readonly Stream _destination; /// /// If this instance owns the stream. /// private readonly bool _ownsStream; /// /// The current position and the amount of total leased bytes. /// private int _position, _leased; /// /// Creates a new instance. /// /// The . /// The buffer-size of the . /// If it owns the stream. public StreamBufferWriter(Stream destination, int bufferSize = 1024, bool ownsStream = true) { const int minBufferSize = 128; if (bufferSize < minBufferSize) { bufferSize = minBufferSize; } _buffer = ArrayPool.Shared.Rent(bufferSize); _ownsStream = ownsStream; _destination = destination; } /// /// Leases an amount of bytes from the . /// /// The total amount. /// The leased amount. private int Lease(int sizeHint) { var available = _buffer.Length - _position; if (available < sizeHint && _position != 0) { // try to get more Flush(); available = _buffer.Length - _position; } _leased = available; return available; } /// /// Flushes the buffered bytes to the . /// /// If it also should flush the . public void Flush(bool flushUnderlyingStream = false) { if (_position != 0) { _destination.Write(_buffer, 0, _position); _position = 0; } if (flushUnderlyingStream) { _destination.Flush(); } } /// /// Advances the buffer, notifies this instance that there was something new written into the memory. /// /// The amount of bytes written. /// Throws if we are out of memory. void IBufferWriter.Advance(int count) { if (count > _leased || count < 0) throw new ArgumentOutOfRangeException(nameof(count)); _position += count; _leased = 0; } /// /// Returns a partion of the as a . /// /// The total amount. /// The new instance. Memory IBufferWriter.GetMemory(int sizeHint) { var actual = Lease(sizeHint); return new Memory(_buffer, _position, actual); } /// /// Returns a partion of the as a . /// /// The total amount. /// The new instance. Span IBufferWriter.GetSpan(int sizeHint) { var actual = Lease(sizeHint); return new Span(_buffer, _position, actual); } /// /// Disposes this instance, flushes and releases all memory. /// public void Dispose() { Flush(true); var tmp = _buffer; _buffer = null!; ArrayPool.Shared.Return(tmp); if (_ownsStream) { _destination.Dispose(); } } } ================================================ FILE: Arch.Persistence.Tests/Arch.Persistence.Tests.csproj ================================================ net7.0 enable enable false ================================================ FILE: Arch.Persistence.Tests/PersistenceTest.cs ================================================ using Arch.Core; using Arch.Core.Extensions; using CommunityToolkit.HighPerformance; using static NUnit.Framework.Assert; using Throws = NUnit.Framework.Throws; namespace Arch.Persistence.Tests; /// /// The struct /// represents a test component that is being (de)serialized. /// public record struct Transform { public float X; public float Y; } /// /// The struct /// represents a test component that is being (de)serialized. /// public record struct MetaData { public string Name; } public class Tests { private ArchBinarySerializer _binarySerializer; private ArchJsonSerializer _jsonSerializer; private World _world; [SetUp] public void Setup() { _binarySerializer = new ArchBinarySerializer(); _jsonSerializer = new ArchJsonSerializer(); _world = World.Create(); for (var index = 0; index < 1000; index++) { _world.Create(new Transform { X = index, Y = index }, new MetaData{ Name = index.ToString()}); } } /// /// Checks if a world is being serialized and deserialized correctly using the . /// [Test] public void BinaryWorldSerialization() { var bytes = _binarySerializer.Serialize(_world); var newWorld = _binarySerializer.Deserialize(bytes); // Equal in structure? That(newWorld.Capacity, Is.EqualTo(_world.Capacity)); That(newWorld.Size, Is.EqualTo(_world.Size)); That(newWorld.Archetypes.Count, Is.EqualTo(_world.Archetypes.Count)); // Are archetypes equal? for (var index = 0; index < _world.Archetypes.Count; index++) { var archetype = _world.Archetypes[index]; var newArchetype = newWorld.Archetypes[index]; That(archetype.ChunkCapacity, Is.EqualTo(newArchetype.ChunkCapacity)); That(archetype.EntityCount, Is.EqualTo(newArchetype.EntityCount)); } // Are entities equal? var entities = new Entity[_world.Size]; _world.GetEntities(new QueryDescription().WithNone(), entities.AsSpan()); var newEntities = new Entity[newWorld.Size]; newWorld.GetEntities(new QueryDescription(), newEntities.AsSpan()); for (var index = 0; index < entities.Length; index++) { var entity = entities[index]; var newEntity = newEntities[index]; That(entity.Id, Is.EqualTo(newEntity.Id)); That(entity.Version, Is.EqualTo(newEntity.Version)); That(entity.Get(), Is.EqualTo(newEntity.Get())); That(entity.Get(), Is.EqualTo(newEntity.Get())); } } /// /// Checks if an entity is being serialized and deserialized correctly using the . /// [Test] public void BinaryEntitySerialization() { var entity = _world.Archetypes[0].GetChunk(0).Entity(0); var bytes = _binarySerializer.Serialize(_world, entity); var newWorld = World.Create(); var newEntity = _binarySerializer.Deserialize(newWorld, bytes); That(newEntity.Get(), Is.EqualTo(entity.Get())); That(newEntity.Get(), Is.EqualTo(entity.Get())); } /// /// Checks if a world is being serialized and deserialized correctly using the . /// [Test] public void JsonWorldSerialization() { var bytes = _jsonSerializer.Serialize(_world); var newWorld = _jsonSerializer.Deserialize(bytes); // Equal in structure? That(newWorld.Capacity, Is.EqualTo(_world.Capacity)); That(newWorld.Size, Is.EqualTo(_world.Size)); That(newWorld.Archetypes.Count, Is.EqualTo(_world.Archetypes.Count)); // Are archetypes equal? for (var index = 0; index < _world.Archetypes.Count; index++) { var archetype = _world.Archetypes[index]; var newArchetype = newWorld.Archetypes[index]; That(archetype.ChunkCapacity, Is.EqualTo(newArchetype.ChunkCapacity)); That(archetype.EntityCount, Is.EqualTo(newArchetype.EntityCount)); } // Are entities equal? var entities = new Entity[_world.Size]; _world.GetEntities(new QueryDescription().WithNone(), entities.AsSpan()); var newEntities = new Entity[newWorld.Size]; newWorld.GetEntities(new QueryDescription(), newEntities.AsSpan()); for (var index = 0; index < entities.Length; index++) { var entity = entities[index]; var newEntity = newEntities[index]; That(entity.Id, Is.EqualTo(newEntity.Id)); That(entity.Get(), Is.EqualTo(newEntity.Get())); That(entity.Get(), Is.EqualTo(newEntity.Get())); } } /// /// Checks if an entity is being serialized and deserialized correctly using the . /// [Test] public void JsonEntitySerialization() { var entity = _world.Archetypes[0].GetChunk(0).Entity(0); var bytes = _jsonSerializer.Serialize(_world, entity); var newWorld = World.Create(); var newEntity = _jsonSerializer.Deserialize(newWorld, bytes); That(newEntity.Get(), Is.EqualTo(entity.Get())); That(newEntity.Get(), Is.EqualTo(entity.Get())); } } ================================================ FILE: Arch.Persistence.Tests/Usings.cs ================================================ global using NUnit.Framework; ================================================ FILE: Arch.Relationships/Arch.Relationships.csproj ================================================ enable enable net7.0;net6.0;netstandard2.1 11 true bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml true snupkg Arch.Relationships Simple Entity-Relationships for Arch. Apache2.0 https://github.com/genaray/Arch.Extended https://github.com/genaray/Arch.Extended.git git c#;.net;.net6;.net7;ecs;game;entity;gamedev; game-development; game-engine; entity-component-system; arch; Relationships now use less memory and are serializable. genaray 2.1.0 en-US true TRACE;EVENTS ================================================ FILE: Arch.Relationships/EntityRelationshipExtensions.cs ================================================ using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using Arch.Core; namespace Arch.Relationships; #if !PURE_ECS /// /// The class /// stores several methods to forward relationship methods from the to the . /// public static class EntityRelationshipExtensions { /// /// Adds a new relationship to the . /// /// The source of the relationship. /// The target of the relationship. /// The relationship type. /// The relationship instance. public static void AddRelationship(this in Entity source, Entity target, T relationship = default!) { var world = World.Worlds[source.WorldId]; world.AddRelationship(source, target, relationship); } /// /// Sets a relationship to the by updating its relationship data. /// /// The source of the relationship. /// The target of the relationship. /// The relationship type. /// The relationship instance. public static void SetRelationship(this in Entity source, Entity target, T relationship = default!) { var world = World.Worlds[source.WorldId]; world.SetRelationship(source, target, relationship); } /// /// Checks if an has a certain relationship. /// /// The relationship type. /// The source of the relationship. /// The target of the relationship. /// True if it has the desired relationship, otherwise false. [MethodImpl(MethodImplOptions.AggressiveInlining), Pure] public static bool HasRelationship(this in Entity source, Entity target) { var world = World.Worlds[source.WorldId]; return world.HasRelationship(source, target); } /// /// Checks if an has a certain relationship. /// /// The relationship type. /// The source of the relationship. /// True if it has the desired relationship, otherwise false. [MethodImpl(MethodImplOptions.AggressiveInlining), Pure] public static bool HasRelationship(this in Entity source) { var world = World.Worlds[source.WorldId]; return world.HasRelationship(source); } /// /// Returns a relationship of an . /// /// The relationship type. /// The source of the relationship. /// The target of the relationship. /// The relationship. [MethodImpl(MethodImplOptions.AggressiveInlining), Pure] public static T GetRelationship(this in Entity source, Entity target) { var world = World.Worlds[source.WorldId]; return world.GetRelationship(source, target); } /// /// Returns a relationship of an . /// /// The relationship type. /// The source of the relationship. /// The . [MethodImpl(MethodImplOptions.AggressiveInlining), Pure] public static ref Relationship GetRelationships(this in Entity source) { var world = World.Worlds[source.WorldId]; return ref world.GetRelationships(source); } /// /// Tries to return an s relationship of the specified type. /// Will copy the relationship if its a struct. /// /// The relationship type. /// The source of the relationship. /// The target of the relationship. /// The found relationship. /// True if it exists, otherwise false. [MethodImpl(MethodImplOptions.AggressiveInlining), Pure] public static bool TryGetRelationship(this in Entity source, Entity target, out T relationship) { var world = World.Worlds[source.WorldId]; return world.TryGetRelationship(source, target, out relationship); } /// /// Removes a relationship from an . /// /// The relationship type. /// The to remove the relationship from. /// The target of the relationship. public static void RemoveRelationship(this in Entity source, Entity target) { var world = World.Worlds[source.WorldId]; world.RemoveRelationship(source, target); } } #endif ================================================ FILE: Arch.Relationships/Enumerators.cs ================================================ using System.Collections; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions.Dangerous; using CommunityToolkit.HighPerformance; namespace Arch.Relationships; /// /// The struct /// is a enumerator to enumerate a passed in an efficient way. /// /// public struct SortedListEnumerator { private SortedList sortedList; private int currentIndex; /// /// Constructor. /// /// List. public SortedListEnumerator(SortedList list) { sortedList = list; currentIndex = -1; } /// /// Current. /// public KeyValuePair Current { get { if (currentIndex == -1 || currentIndex >= sortedList.Count) throw new InvalidOperationException(); var key = sortedList.Keys[currentIndex]; var value = sortedList.Values[currentIndex]; return new KeyValuePair(key, value); } } /// /// Moves to the next element in the enumerator. /// public bool MoveNext() { currentIndex++; return currentIndex < sortedList.Count; } /// /// Resets the enumerator to its initial position. /// public void Reset() { currentIndex = -1; } } ================================================ FILE: Arch.Relationships/InRelationship.cs ================================================ using Arch.Core; using Arch.Core.Utils; namespace Arch.Relationships; /// /// The struct /// represents a reference to a . /// It sits on an to indicate in which other s it is involved in. /// internal readonly struct InRelationship { /// /// The id of the -Component that this points to. /// Basically the the is in. /// TODO: Uhmm... how the heck do we convert the Id back to the ? /// public readonly int ComponentTypeId; /// /// Creates a new instance. /// /// The that represents the relation. internal InRelationship(ComponentType targetRelation) { ComponentTypeId = targetRelation.Id; } /// /// Creates a new instance. /// Mostly for binary serialization. /// /// The . internal InRelationship(int componentTypeId) { ComponentTypeId = componentTypeId; } } ================================================ FILE: Arch.Relationships/Relationship.cs ================================================ using System.Runtime.CompilerServices; using Arch.Core; namespace Arch.Relationships; /// /// The interface /// is an interface that provides all methods required to act as a relationship. /// internal interface IRelationship { /// /// The amount of relationships currently in the buffer. /// int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } /// /// Removes the buffer as a component from the given world and entity. /// /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void Destroy(World world, Entity source); /// /// Removes the relationship targeting from this buffer. /// /// The in the relationship to remove. [MethodImpl(MethodImplOptions.AggressiveInlining)] void Remove(Entity target); } /// /// A buffer storing relationships of and . /// /// The type of the second relationship element. public class Relationship : IRelationship { /// /// Its relations. /// internal readonly SortedList Elements; /// /// Initializes a new instance of an . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Relationship() { Elements = new SortedList(); } /// /// Initializes a new instance of an . /// Mostly for binary serialization. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Relationship(SortedList elements) { Elements = elements; } /// int IRelationship.Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Elements.Count; } /// internal int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ((IRelationship) this).Count; } /// /// Adds a relationship to this buffer. /// /// The instance of the relationship. /// The target of the relationship. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void Add(in T relationship, Entity target) { Elements.Add(target, relationship); } /// /// Sets the stored for the given . /// /// The . /// The data to store. [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Set(Entity entity, T data = default!) { Elements[entity] = data; } /// /// Determines whether the given contains the passed or not. /// /// The . /// True or false. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Contains(Entity entity) { return Elements.ContainsKey(entity); } /// /// Returns the stored for the given . /// /// The . /// The stored . [MethodImpl(MethodImplOptions.AggressiveInlining)] public T Get(Entity entity) { return Elements[entity]; } /// /// Returns the stored for the given . /// /// The . /// The stored . /// The stored . [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetValue(Entity entity, out T value) { return Elements.TryGetValue(entity, out value!); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] void IRelationship.Remove(Entity target) { Elements.Remove(target); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void Remove(Entity target) { ((IRelationship) this).Remove(target); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] void IRelationship.Destroy(World world, Entity source) { world.Remove>(source); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void Destroy(World world, Entity source) { ((IRelationship) this).Destroy(world, source); } /// /// Creates a new . /// /// The new . [MethodImpl(MethodImplOptions.AggressiveInlining)] public SortedListEnumerator GetEnumerator() { return new SortedListEnumerator(Elements); } }; ================================================ FILE: Arch.Relationships/WorldRelationshipExtensions.cs ================================================ using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Extensions.Dangerous; using Arch.Core.Utils; [assembly:InternalsVisibleTo("Arch.Relationships.Tests")] namespace Arch.Relationships; /// /// The class /// stores several extension methods for relationships handling. /// public static class WorldRelationshipExtensions { #if EVENTS /// /// Subscribes to entity destruction events to cleanup their relations. /// public static void HandleRelationshipCleanup(this World world) { world.SubscribeEntityDestroyed((in Entity entity) => CleanupRelationships(world, in entity)); } // TODO: Probably someone will kill me for the dark magic that happens down below. /// /// Cleans up all relations of the passed . /// /// /// public static void CleanupRelationships(this World world, in Entity entity) { ref var relationships = ref world.TryGetRefRelationships(entity, out var exists); if (!exists) { return; } foreach (var (target, inRelationship) in relationships.Elements) { var id = inRelationship.ComponentTypeId; var componentType = new ComponentType(id, 0); // Get slots, chunk and array to prevent entity.Get(type) object allocation ref readonly var chunk = ref world.GetChunk(target); var array = chunk.GetArray(componentType); var relationshipsArray = Unsafe.As(array); var slot = world.GetSlot(target); var relationship = relationshipsArray[slot.Index]; relationship.Remove(entity); if (relationship.Count == 0) { relationship.Destroy(world, target); } ref var targetRelationships = ref world.TryGetRefRelationships(target, out exists); if (!exists) { continue; } targetRelationships.Remove(entity); } } #endif /// /// Adds a new relationship to the . /// /// World. /// The source of the relationship. /// The target of the relationship. /// The relationship type. /// The relationship instance. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AddRelationship(this World world, Entity source, Entity target, in T relationship = default!) { ref var buffer = ref world.AddOrGetRelationships(source); buffer.Add(in relationship, target); var targetComp = new InRelationship(Component>.ComponentType); ref var targetBuffer = ref world.AddOrGetRelationships(target); targetBuffer.Add(in targetComp, source); } /// /// Ensures the existence of a relationship on an . /// /// The relationship type. /// World. /// The source of the relationship. /// The target of the relationship. /// The relationship value used if its being added. /// The relationship. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T AddOrGetRelationship(this World world, Entity source, Entity target, in T relationship = default!) { ref var relationships = ref world.TryGetRefRelationships(source, out var exists); if (exists) { return relationships.Get(target); } world.AddRelationship(source, target, in relationship); return world.GetRelationship(source, target); } /// /// Ensures the existence of a buffer of relationships on an . /// /// World. /// The source of the relationships. /// The relationship type. /// The relationships. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ref Relationship AddOrGetRelationships(this World world, Entity source) { ref var component = ref world.TryGetRef>(source, out var exists); if (exists) { return ref component!; } world.Add(source, new Relationship()); return ref world.Get>(source); } /// /// Sets the existing relationship data. /// /// The relationship type. /// World. /// The source of the relationship. /// The target of the relationship. /// The new data. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetRelationship(this World world, Entity source, Entity target, in T relationship = default!) { ref var relationships = ref world.GetRelationships(source); relationships.Set(target, relationship); } /// /// Checks if an has a certain relationship. /// /// The relationship type. /// World. /// The source of the relationship. /// The target of the relationship. /// True if it has the desired relationship, otherwise false. [MethodImpl(MethodImplOptions.AggressiveInlining), Pure] public static bool HasRelationship(this World world, Entity source, Entity target) { ref var relationships = ref world.TryGetRefRelationships(source, out var exists); if (!exists) { return false; } return relationships.Contains(target); } /// /// Checks if an has a certain relationship. /// /// The relationship type. /// World. /// The source of the relationship. /// True if it has the desired relationship, otherwise false. [MethodImpl(MethodImplOptions.AggressiveInlining), Pure] public static bool HasRelationship(this World world, Entity source) { return world.Has>(source); } /// /// Returns a relationship of an . /// /// The relationship type. /// World. /// The source of the relationship. /// The target of the relationship. /// The relationship. [MethodImpl(MethodImplOptions.AggressiveInlining), Pure] public static T GetRelationship(this World world, Entity source, Entity target) { ref var relationships = ref world.GetRelationships(source); return relationships.Get(target); } /// /// Tries to return an s relationship of the specified type. /// Will copy the relationship if its a struct. /// /// The relationship type. /// World. /// The source of the relationship. /// The target of the relationship. /// The found relationship. /// True if it exists, otherwise false. [MethodImpl(MethodImplOptions.AggressiveInlining), Pure] public static bool TryGetRelationship(this World world, Entity source, Entity target, out T relationship) { ref var relationships = ref world.TryGetRefRelationships(source, out var exists); if (!exists) { relationship = default!; return false; } return relationships.TryGetValue(target, out relationship); } /// /// Returns all relationships of the given type of an . /// /// The relationship type. /// World. /// The source of the relationship. /// A reference to the relationships. [MethodImpl(MethodImplOptions.AggressiveInlining), Pure] public static ref Relationship GetRelationships(this World world, Entity source) { return ref world.Get>(source); } /// /// Tries to return an s relationships of the specified type. /// /// The relationship type. /// World. /// The . /// The found relationships. /// True if it exists, otherwise false. [MethodImpl(MethodImplOptions.AggressiveInlining), Pure] internal static bool TryGetRelationships(this World world, Entity source, out Relationship relationships) { return world.TryGet(source, out relationships!); } /// /// Tries to return a reference to an s relationships of the /// specified type. /// /// The relationship type. /// World. /// The . /// True if it exists, otherwise false. /// A reference to the relationships. [MethodImpl(MethodImplOptions.AggressiveInlining), Pure] internal static ref Relationship TryGetRefRelationships(this World world, Entity source, out bool exists) { return ref world.TryGetRef>(source, out exists); } /// /// Removes a relationship from an . /// /// The relationship type. /// World. /// The to remove the relationship from. /// The target of the relationship. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RemoveRelationship(this World world, Entity source, Entity target) { ref var buffer = ref world.GetRelationships(source); buffer.Remove(target); if (buffer.Count == 0) { world.Remove>(source); } ref var targetBuffer = ref world.GetRelationships(target); targetBuffer.Remove(source); if (targetBuffer.Count == 0) { world.Remove>(target); } } } ================================================ FILE: Arch.Relationships.Tests/Arch.Relationships.Tests.csproj ================================================ net7.0 enable enable false Arch.Relationships.Tests TRACE; ================================================ FILE: Arch.Relationships.Tests/RelationshipTest.cs ================================================ using System.Runtime.CompilerServices; using Arch.Core; using static NUnit.Framework.Assert; using Throws = NUnit.Framework.Throws; namespace Arch.Relationships.Tests; //#if EVENTS public record struct ParentOf; public record struct ChildOf; /// /// The class /// contains several tests to check if the relations work correctly. /// [TestFixture] public class RelationshipTest { private World _world = default!; [SetUp] public void SetUp() { _world = World.Create(); } /// /// Checks if no relationships are handled corretly. /// [Test] public void NoRelationships() { // Create entities without setting any relationships var parent = _world.Create(); var childOne = _world.Create(); var childTwo = _world.Create(); // Get should throw That(() => _world.GetRelationships(parent), Throws.Exception); That(() => _world.GetRelationships(childOne), Throws.Exception); That(() => _world.GetRelationships(childTwo), Throws.Exception); // TryGet should return false False(_world.TryGetRelationships(parent, out _)); False(_world.TryGetRelationships(childOne, out _)); False(_world.TryGetRelationships(childTwo, out _)); // TryGetRef should return a null ref and false That(Unsafe.IsNullRef(ref _world.TryGetRefRelationships(parent, out var exists))); False(exists); That(Unsafe.IsNullRef(ref _world.TryGetRefRelationships(childOne, out exists))); False(exists); That(Unsafe.IsNullRef(ref _world.TryGetRefRelationships(childTwo, out exists))); False(exists); } /// /// Checks if relationships were added correctly. /// [Test] public void AddRelationship() { var source = _world.Create(); var target = _world.Create(); source.AddRelationship(target); That(source.HasRelationship(), Is.True); That(source.HasRelationship(target), Is.True); } /// /// Checks if relationships were set correctly. /// [Test] public void RelationshipData() { var source = _world.Create(); var target = _world.Create(); var dummy = _world.Create(); source.AddRelationship(target, 5); source.AddRelationship(dummy, 100); var data = source.GetRelationship(target); That(data, Is.EqualTo(5)); source.SetRelationship(target, 10); data = source.GetRelationship(target); That(data, Is.EqualTo(10)); } /// /// Checks if relationships were removed correctly. /// [Test] public void RemoveRelationship() { var source = _world.Create(); var target = _world.Create(); source.AddRelationship(target); That(source.HasRelationship(), Is.True); That(source.HasRelationship(target), Is.True); source.RemoveRelationship(target); That(source.HasRelationship(), Is.False); } #if EVENTS [Test] public void RelationshipCleanup() { // Setup handling relationship cleanup _world.HandleRelationshipCleanup(); // Create entities var parent = _world.Create(); var childOne = _world.Create(); var childTwo = _world.Create(); // Add the relationships _world.AddRelationship(parent, childOne); _world.AddRelationship(parent, childTwo); _world.AddRelationship(childOne, parent); _world.AddRelationship(childTwo, parent); // Get should return a reference to the relationships That(_world.GetRelationships(parent).Elements, Does .ContainKey(childOne).And .ContainKey(childTwo)); That(_world.GetRelationships(childOne).Elements, Does.ContainKey(parent)); That(_world.GetRelationships(childTwo).Elements, Does.ContainKey(parent)); // TryGet should return true and the relationships True(_world.TryGetRelationships(parent, out var parentRelationships)); That(parentRelationships.Elements, Does.ContainKey(childOne).And.ContainKey(childTwo)); True(_world.TryGetRelationships(childOne, out var childRelationships)); That(childRelationships.Elements, Does.ContainKey(parent)); True(_world.TryGetRelationships(childTwo, out childRelationships)); That(childRelationships.Elements, Does.ContainKey(parent)); // TryGetRef should return a reference to the relationships and true ref var parentRelationshipsRef = ref _world.TryGetRefRelationships(parent, out var exists); True(exists); That(parentRelationshipsRef.Elements, Does.ContainKey(childOne).And.ContainKey(childTwo)); ref var childOneRelationshipsRef = ref _world.TryGetRefRelationships(childOne, out exists); True(exists); That(childOneRelationshipsRef.Elements, Does.ContainKey(parent)); ref var childTwoRelationshipsRef = ref _world.TryGetRefRelationships(childTwo, out exists); True(exists); That(childTwoRelationshipsRef.Elements, Does.ContainKey(parent)); // Destroy childOne, should remove any relationships containing it _world.CleanupRelationships(childOne); _world.Destroy(childOne); // Get should throw on childOne and not return it in any of the other references That(_world.GetRelationships(parent).Elements, Does.Not.ContainKey(childOne).And.ContainKey(childTwo)); That(() => _world.GetRelationships(childOne).Elements, Throws.Exception); That(_world.GetRelationships(childTwo).Elements, Does.ContainKey(parent)); // Destroy childTwo, should remove any relationships containing it _world.Destroy(childTwo); // Get, all should throw as no relationships are left That(() => _world.GetRelationships(parent), Throws.Exception); That(() => _world.GetRelationships(childOne), Throws.Exception); That(() => _world.GetRelationships(childTwo), Throws.Exception); // Destroy parent DoesNotThrow(() => _world.Destroy(parent)); } #endif [Test] public void QueryRelationship() { // Create entities var parent = _world.Create(); var childOne = _world.Create(); var childTwo = _world.Create(); // Add relationships _world.AddRelationship(parent, childOne); _world.AddRelationship(parent, childTwo); _world.AddRelationship(childOne, parent); _world.AddRelationship(childTwo, parent); // Query all ParentOf relationships var query = new QueryDescription().WithAll>(); var entities = new List(); _world.Query(query, (ref Relationship parentOf) => { entities.AddRange(parentOf.Elements.Select(p => p.Key)); }); // Should find two ParentOf relationships, from childOne and childTwo That(entities, Has.Count.EqualTo(2)); That(entities, Does.Contain(childOne)); That(entities, Does.Contain(childTwo)); } } //#endif ================================================ FILE: Arch.Relationships.Tests/Usings.cs ================================================ global using NUnit.Framework; ================================================ FILE: Arch.System/Arch.System.csproj ================================================  net7.0; net6.0; netstandard2.1 enable true bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml true snupkg Arch.System Arch.System 1.1.0 genaray Apache-2.0 A systems framework for arch. Made group extendible. c#;.net;.net6;.net7;ecs;game;entity;gamedev; game-development; game-engine; entity-component-system; arch; https://github.com/genaray/Arch.Extended https://github.com/genaray/Arch.Extended.git git true 11 true Apache2.0 en-US true TextTemplatingFileGenerator GenericAttributes.cs True True GenericAttributes.tt ================================================ FILE: Arch.System/Attributes.cs ================================================ namespace Arch.System; /// /// Marks a method to generate a high performance query for it. /// [global::System.AttributeUsage(global::System.AttributeTargets.Method)] public class QueryAttribute : global::System.Attribute { /// /// If set to true, Query will be run in parallel. /// public bool Parallel { get; set; } } /// /// Marks a parameter as "data". This will be taken into account during source generation and will still be passed as a parameter in the query method. /// Is not treated as an entity component. /// [global::System.AttributeUsage(global::System.AttributeTargets.Parameter)] public class DataAttribute : global::System.Attribute { } /// /// Defines a set of components each entity requires. /// [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] public class AllAttribute : global::System.Attribute { /// /// The types of the component. /// public Type[] ComponentTypes { get; } /// /// Constructs an All attribute with the specified component types. /// /// The types of the components that should be present. public AllAttribute(params Type[] componentTypes) { ComponentTypes = componentTypes; } } /// /// Defines a set of components each entity requires any from. /// [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] public class AnyAttribute : global::System.Attribute { /// /// The types of the component. /// public Type[] ComponentTypes { get; } /// /// Constructs an Any attribute with the specified component types. /// /// The types of the components that can be present. public AnyAttribute(params Type[] componentTypes) { ComponentTypes = componentTypes; } } /// /// Defines a set of components none of the entities should have. /// [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] public class NoneAttribute : global::System.Attribute { /// /// The types of the component. /// public Type[] ComponentTypes { get; } /// /// Constructs a None attribute with the specified component types. /// /// The types of the components that should not be present. public NoneAttribute(params Type[] componentTypes) { ComponentTypes = componentTypes; } } /// /// Defines an exclusive set of components an entity should have. /// [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] public class ExclusiveAttribute : global::System.Attribute { /// /// The types of the component. /// public Type[] ComponentTypes { get; } /// /// Constructs an exclusive attribute with the specified component types. /// /// The types of the components that should be present exclusively. public ExclusiveAttribute(params Type[] componentTypes) { ComponentTypes = componentTypes; } } ================================================ FILE: Arch.System/Systems.cs ================================================ //#if !NET5_0_OR_GREATER #define ARCH_METRICS_DISABLED //#endif using System.Collections; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Text; #if !ARCH_METRICS_DISABLED using System.Diagnostics.Metrics; #endif namespace Arch.System; /// /// An interface providing several methods for a system. /// /// The type passed to each method. For example a delta time or some other data. public interface ISystem : IDisposable { /// /// Initializes a system, before its first ever run. /// void Initialize(); /// /// Runs before . /// /// An instance passed to it. void BeforeUpdate(in T t); /// /// Updates the system. /// /// An instance passed to it. void Update(in T t); /// /// Runs after . /// /// An instance passed to it. void AfterUpdate(in T t); } /// /// A basic implementation of a . /// /// The world type. /// The type passed to the interface. public abstract class BaseSystem : ISystem { /// /// Creates an instance. /// /// The . protected BaseSystem(W world) { World = world; } /// /// The world instance. /// public W World { get; private set; } /// public virtual void Initialize(){} /// public virtual void BeforeUpdate(in T t) { } /// public virtual void Update(in T t){} /// public virtual void AfterUpdate(in T t){} /// public virtual void Dispose(){} } /// /// A group of 's to organize them. /// They will run in order. /// /// The type passed to the . public class Group : ISystem, IEnumerable> { #if !ARCH_METRICS_DISABLED private readonly Meter _meter; private readonly Stopwatch _timer = new(); #endif /// /// A unique name to identify this group /// public string Name { get; } /// /// All 's in this group. /// private readonly List _systems = new(); /// /// Creates an instance with an array of 's that will belong to this group. /// /// A unique name to identify this group /// An array. public Group(string name, params ISystem[] systems) : this(name, (IEnumerable>)systems) { } /// /// Creates an instance with an of 's that will belong to this group. /// /// A unique name to identify this group /// An of . public Group(string name, IEnumerable> systems) { Name = name; #if !ARCH_METRICS_DISABLED _meter = new Meter(name); #endif #if NET5_0_OR_GREATER // If possible expand the list before adding all the systems if (systems.TryGetNonEnumeratedCount(out var count)) _systems.Capacity = count; #endif foreach (var system in systems) Add(system); } /// /// Adds several new 's to this group. /// /// An array. /// The same . public Group Add(params ISystem[] systems) { _systems.Capacity = Math.Max(_systems.Capacity, _systems.Count + systems.Length); foreach (var system in systems) Add(system); return this; } /// /// Adds an single to this group by its generic. /// Automatically initializes it properly. Must be contructorless. /// /// Its generic type. /// The same . public Group Add() where G : ISystem, new() { return Add(new G()); } /// /// Adds an single to this group. /// /// /// public Group Add(ISystem system) { _systems.Add(new SystemEntry(system #if !ARCH_METRICS_DISABLED , _meter #endif )); return this; } /// /// Return the first which was found in the hierachy. /// /// The Type. /// public G Get() where G : ISystem { foreach (var item in _systems) { if (item.System is G sys) { return sys; } if (item.System is not Group grp) { continue; } return grp.Get(); } return default; } /// /// Finds all s which can be cast into the given type. /// /// The Type. /// public IEnumerable Find() where G : ISystem { foreach (var item in _systems) { if (item.System is G sys) { yield return sys; } if (item.System is not Group grp) { continue; } foreach (var nested in grp.Find()) { yield return nested; } } } /// /// Initializes all 's in this . /// public void Initialize() { for (var index = 0; index < _systems.Count; index++) { var entry = _systems[index]; entry.System.Initialize(); } } /// /// Runs on each of this .. /// /// An instance passed to each method. public void BeforeUpdate(in T t) { for (var index = 0; index < _systems.Count; index++) { var entry = _systems[index]; #if !ARCH_METRICS_DISABLED _timer.Restart(); { #endif entry.System.BeforeUpdate(in t); #if !ARCH_METRICS_DISABLED } _timer.Stop(); entry.BeforeUpdate.Record(_timer.Elapsed.TotalMilliseconds); #endif } } /// /// Runs on each of this .. /// /// An instance passed to each method. public void Update(in T t) { for (var index = 0; index < _systems.Count; index++) { var entry = _systems[index]; #if !ARCH_METRICS_DISABLED _timer.Restart(); { #endif entry.System.Update(in t); #if !ARCH_METRICS_DISABLED } _timer.Stop(); entry.Update.Record(_timer.Elapsed.TotalMilliseconds); #endif } } /// /// Runs on each of this .. /// /// An instance passed to each method. public void AfterUpdate(in T t) { for (var index = 0; index < _systems.Count; index++) { var entry = _systems[index]; #if !ARCH_METRICS_DISABLED _timer.Restart(); { #endif entry.System.AfterUpdate(in t); #if !ARCH_METRICS_DISABLED } _timer.Stop(); entry.AfterUpdate.Record(_timer.Elapsed.TotalMilliseconds); #endif } } /// /// Disposes this and all 's within. /// public void Dispose() { foreach (var system in _systems) { system.Dispose(); } } /// /// Converts this to a human readable string. /// /// public override string ToString() { // List all system names var stringBuilder = new StringBuilder(); foreach (var systemEntry in _systems) { stringBuilder.Append($"{systemEntry.System.GetType().Name},"); } // Cut last `,` if (_systems.Count > 0) { stringBuilder.Length--; } return $"Group = {{ {nameof(Name)} = {Name}, Systems = {{ {stringBuilder} }} }} "; } /// public IEnumerator> GetEnumerator() { foreach (var entry in _systems) { yield return entry.System; } } /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// The struct represents the given in the with all its performance statistics. /// private readonly struct SystemEntry : IDisposable { public readonly ISystem System; #if !ARCH_METRICS_DISABLED public readonly Histogram BeforeUpdate; public readonly Histogram Update; public readonly Histogram AfterUpdate; #endif public void Dispose() { System.Dispose(); } public SystemEntry(ISystem system #if !ARCH_METRICS_DISABLED , Meter meter #endif ) { var name = system.GetType().Name; System = system; #if !ARCH_METRICS_DISABLED BeforeUpdate = meter.CreateHistogram($"{name}.BeforeUpdate", unit: "millisecond"); Update = meter.CreateHistogram($"{name}.Update", unit: "millisecond"); AfterUpdate = meter.CreateHistogram($"{name}.AfterUpdate", unit: "millisecond"); #endif } } } ================================================ FILE: Arch.System/Templates/GenericAttributes.cs ================================================ namespace Arch.System; #if NET7_0_OR_GREATER /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20), typeof(T21)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20), typeof(T21), typeof(T22)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20), typeof(T21), typeof(T22), typeof(T23)) { } } /// public class AllAttribute : AllAttribute { /// public AllAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20), typeof(T21), typeof(T22), typeof(T23), typeof(T24)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20), typeof(T21)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20), typeof(T21), typeof(T22)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20), typeof(T21), typeof(T22), typeof(T23)) { } } /// public class AnyAttribute : AnyAttribute { /// public AnyAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20), typeof(T21), typeof(T22), typeof(T23), typeof(T24)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20), typeof(T21)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20), typeof(T21), typeof(T22)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20), typeof(T21), typeof(T22), typeof(T23)) { } } /// public class NoneAttribute : NoneAttribute { /// public NoneAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20), typeof(T21), typeof(T22), typeof(T23), typeof(T24)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20), typeof(T21)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20), typeof(T21), typeof(T22)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20), typeof(T21), typeof(T22), typeof(T23)) { } } /// public class ExclusiveAttribute : ExclusiveAttribute { /// public ExclusiveAttribute() : base(typeof(T0), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6), typeof(T7), typeof(T8), typeof(T9), typeof(T10), typeof(T11), typeof(T12), typeof(T13), typeof(T14), typeof(T15), typeof(T16), typeof(T17), typeof(T18), typeof(T19), typeof(T20), typeof(T21), typeof(T22), typeof(T23), typeof(T24)) { } } #endif ================================================ FILE: Arch.System/Templates/GenericAttributes.tt ================================================ namespace Arch.System; <#@ template language="C#" #> <#@ output extension=".cs" #> <#@ import namespace="System.Text" #> <#@ include file="Helpers.ttinclude" #> #if NET7_0_OR_GREATER <# var names = new string[] { "All", "Any", "None", "Exclusive" }; foreach (var name in names) { for (var index = 1; index <= Amount; index++) { var generics = AppendGenerics(index); var parameters = AppendGenericTypeParameters(index); #> /// public class <#= name #>Attribute<<#= generics #>> : <#= name #>Attribute { /// public <#= name #>Attribute() : base(<#= parameters #>) { } } <# } } #> #endif ================================================ FILE: Arch.System/Templates/Helpers.ttinclude ================================================ <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#+ public int Amount = 25; public string Indent(StringBuilder sb, int spaces) { var indent = new string(' ', spaces); return sb.ToString().Replace("\n", "\n" + indent); } string AppendGenerics(int amount) { var sb = new StringBuilder(); for (var i = 0; i < amount; i++) { if (i > 0) sb.Append(", "); sb.Append($"T{i}"); } return sb.ToString(); } /// /// Lists generic types in a row as parameters. /// /// /// typeof(T0), typeof(T1), ... /// /// /// /// /// /// public StringBuilder AppendGenericTypeParameters(int amount) { var sb = new StringBuilder(); for (var localIndex = 0; localIndex < amount; localIndex++) { sb.Append($"typeof(T{localIndex}), "); } sb.Length -= 2; return sb; } #> ================================================ FILE: Arch.System.SourceGenerator/Arch.System.SourceGenerator.csproj ================================================ netstandard2.0 enable enable latest true bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml true snupkg Arch.System.SourceGenerator Arch.System.SourceGenerator 2.1.0 genaray Apache-2.0 A source generator for arch.system. Updated to fit Arch 2.1.0-beta and upwards. c#;.net;.net6;.net7;ecs;game;entity;gamedev; game-development; game-engine; entity-component-system; arch; https://github.com/genaray/Arch.Extended https://github.com/genaray/Arch.Extended.git git true 12 true Apache2.0 en-US ================================================ FILE: Arch.System.SourceGenerator/Extensions/CommonUtils.cs ================================================ using System.Text; using Microsoft.CodeAnalysis; namespace Arch.System.SourceGenerator; /// /// Common utils. /// public static class CommonUtils { /// /// Convert a to its code string equivalent. /// /// The . /// The code string equivalent. public static string RefKindToString(RefKind refKind) { switch (refKind) { case RefKind.None: return ""; case RefKind.Ref: return "ref"; case RefKind.In: return "in"; case RefKind.Out: return "out"; } return null!; } /// /// Creates a list of generic type parameters separated by a simple comma. /// T0,T1,..TN /// /// The instance. /// The amount of generic type parameters. /// public static StringBuilder GenericsWithoutBrackets(this StringBuilder sb, int amount) { for (var i = 0; i < amount; i++) sb.Append($"T{i},"); if (sb.Length > 0) sb.Length -= 1; return sb; } /// /// Creates a list of generic type parameters types separated by a simple comma. /// typeof(T0),typeof(T1),..typeof(TN) /// /// The instance. /// The amount of generic type parameters. /// public static StringBuilder GenericsToTypeArray(this StringBuilder sb, int amount) { for (var i = 0; i < amount; i++) sb.Append($"typeof(T{i}),"); if (sb.Length > 0) sb.Length -= 1; return sb; } } ================================================ FILE: Arch.System.SourceGenerator/Extensions/MethodSymbolExtensions.cs ================================================ using Microsoft.CodeAnalysis; namespace Arch.System.SourceGenerator; /// /// Extensions for . /// public static class MethodSymbolExtensions { /// /// Searches attributes of a and returns the first one found. /// /// The instance. /// The attributes name. /// The attribute wrapped in an . public static AttributeData GetAttributeData(this IMethodSymbol ms, string name) { foreach (var attribute in ms.GetAttributes()) { var classSymbol = attribute.AttributeClass; if(!classSymbol!.Name.Contains(name)) continue; return attribute; } return default!; } } ================================================ FILE: Arch.System.SourceGenerator/Model.cs ================================================ using Microsoft.CodeAnalysis; namespace Arch.System.SourceGenerator; /// /// Represents the BaseSystem that is generated and calls its generated query methods. /// public struct BaseSystem { /// /// The namespace its generic is in. /// public string GenericTypeNamespace { get; set; } /// /// The namespace this system is in. /// public string Namespace { get; set; } /// /// Its name. /// public string Name { get; set; } /// /// The generic type. /// public ITypeSymbol GenericType { get; set; } /// /// The Query methods this base system calls one after another. /// public IList QueryMethods { get; set; } } /// /// Represents the Query method that is generated. /// public struct QueryMethod { /// /// If the class containing this Query method is within the global namespace. /// public bool IsGlobalNamespace { get; set; } /// /// The namespace of the method. /// public string Namespace { get; set; } /// /// If this method is static. /// public bool IsStatic { get; set; } /// /// If this Query method contains an Entity as a param and acesses it. /// public bool IsEntityQuery { get; set; } /// /// The name of the class containing this Query method. /// public string ClassName { get; set; } /// /// The name of the Query method. /// public string MethodName { get; set; } /// /// The entity parameter, if its an entity query. /// public IParameterSymbol EntityParameter { get; set; } /// /// All parameters within the query method, not only the components. Also Entity and Data annotated ones. /// public void Query([Data] float time, in Entity entity, ...); /// public IList Parameters { get; set; } /// /// The Components acessed within the query method. /// public void Query(ref Position pos, in Velocity vel){} /// public IList Components { get; set; } /// /// All s mentioned in the All annotation query filter. /// [All(typeof(Position), typeof(Velocity)] or its generic variant /// public IList AllFilteredTypes { get; set; } /// /// All s mentioned in the Any annotation query filter. /// [Any(typeof(Position), typeof(Velocity)] or its generic variant /// public IList AnyFilteredTypes { get; set; } /// /// All s mentioned in the None annotation query filter. /// [None(typeof(Position), typeof(Velocity)] or its generic variant /// public IList NoneFilteredTypes { get; set; } /// /// All s mentioned in the Exclusive annotation query filter. /// [Exclusive(typeof(Position), typeof(Velocity)] or its generic variant /// public IList ExclusiveFilteredTypes { get; set; } } ================================================ FILE: Arch.System.SourceGenerator/QueryUtils.cs ================================================ using System.Text; using Microsoft.CodeAnalysis; namespace Arch.System.SourceGenerator; /// /// Various query utilities. /// public static class QueryUtils { /// /// Appends the first elements of the types specified in the from the previous specified arrays. /// /// The instance. /// The list of s which we wanna append the first elements for. /// public static StringBuilder GetFirstElements(this StringBuilder sb, IEnumerable parameterSymbols) { foreach (var symbol in parameterSymbols) if(symbol.Type.Name is not "Entity" || !symbol.GetAttributes().Any(data => data.AttributeClass!.Name.Contains("Data"))) // Prevent entity being added to the type array sb.AppendLine($"ref var @{symbol.Type.Name.ToLower()}FirstElement = ref chunk.GetFirst<{symbol.Type.ToDisplayString(NullableFlowState.None, SymbolDisplayFormat.FullyQualifiedFormat)}>();"); return sb; } /// /// Appends the components of the types specified in the from the previous specified first elements. /// /// The instance. /// The list of s which we wanna append the components for. /// public static StringBuilder GetComponents(this StringBuilder sb, IEnumerable parameterSymbols) { foreach (var symbol in parameterSymbols) if(symbol.Type.Name is not "Entity") // Prevent entity being added to the type array sb.AppendLine($"ref var @{symbol.Name.ToLower()} = ref Unsafe.Add(ref {symbol.Type.Name.ToLower()}FirstElement, entityIndex);"); return sb; } /// /// Inserts the types defined in the as parameters in a method. /// ref position, out velocity,... /// /// The instance. /// The of s which we wanna insert. /// public static StringBuilder InsertParams(this StringBuilder sb, IEnumerable parameterSymbols) { foreach (var symbol in parameterSymbols) sb.Append($"{CommonUtils.RefKindToString(symbol.RefKind)} @{symbol.Name.ToLower()},"); if(sb.Length > 0) sb.Length--; return sb; } /// /// Creates a ComponentType array from the passed through. /// /// The . /// The with s which we wanna create a ComponentType array for. /// public static StringBuilder GetTypeArray(this StringBuilder sb, IList parameterSymbols) { if (parameterSymbols.Count == 0) { sb.Append("Signature.Null"); return sb; } sb.Append("new Signature("); foreach (var symbol in parameterSymbols) if(symbol.Name is not "Entity") // Prevent entity being added to the type array sb.Append($"typeof({symbol.ToDisplayString(NullableFlowState.None, SymbolDisplayFormat.FullyQualifiedFormat)}),"); if (sb.Length > 0) sb.Length -= 1; sb.Append(')'); return sb; } /// /// Appends a set of if they are marked by the data attribute. /// ref gameTime, out somePassedList,... /// /// The instance. /// The of s which will be appended if they are marked with data. /// public static StringBuilder DataParameters(this StringBuilder sb, IEnumerable parameterSymbols) { sb.Append(','); foreach (var parameter in parameterSymbols) { if (parameter.GetAttributes().Any(attributeData => attributeData.AttributeClass!.Name.Contains("Data"))) sb.Append($"{CommonUtils.RefKindToString(parameter.RefKind)} {parameter.Type} @{parameter.Name.ToLower()},"); } sb.Length--; return sb; } /// /// Appends a set of if they are marked by the data attribute. /// ref gameTime, out somePassedList,... /// /// The instance. /// The of s which will be appended if they are marked with data. /// public static StringBuilder JobParameters(this StringBuilder sb, IEnumerable parameterSymbols) { foreach (var parameter in parameterSymbols) { if (parameter.GetAttributes().Any(attributeData => attributeData.AttributeClass!.Name.Contains("Data"))) sb.AppendLine($"public {parameter.Type} @{parameter.Name.ToLower()};"); } return sb; } /// /// Appends a set of if they are marked by the data attribute. /// ref gameTime, out somePassedList,... /// /// The instance. /// The of s which will be appended if they are marked with data. /// public static StringBuilder JobParametersAssigment(this StringBuilder sb, IEnumerable parameterSymbols) { bool found = false; foreach (var parameter in parameterSymbols) { if (parameter.GetAttributes().Any(attributeData => attributeData.AttributeClass!.Name.Contains("Data"))) { found = true; sb.Append($"@{parameter.Name.ToLower()} = @{parameter.Name.ToLower()},"); } } if (found) sb.Length--; return sb; } /// /// Appends method calls made with their important data parameters. /// someQuery(World, gameTime); ... /// /// The instance. /// The of methods which we wanna call. /// public static StringBuilder CallMethods(this StringBuilder sb, IEnumerable methodNames) { foreach (var method in methodNames) { var data = new StringBuilder(); data.Append(','); foreach (var parameter in method.Parameters) { if (!parameter.GetAttributes().Any(attributeData => attributeData.AttributeClass!.Name.Contains("Data"))) continue; data.Append($"{CommonUtils.RefKindToString(parameter.RefKind)} data,"); break; } data.Length--; sb.AppendLine($"{method.Name}Query(World {data});"); } return sb; } /// /// Gets all the types of a as s and adds them to a list. /// If the attribute is generic it will add the generic parameters, if its non generic it will add the non generic types from the constructor. /// /// The . /// The where the found s are added to. public static void GetAttributeTypes(AttributeData? data, List array) { if (data is not null && data.AttributeClass!.IsGenericType) { array.AddRange(data.AttributeClass.TypeArguments); } else if (data is not null && !data.AttributeClass!.IsGenericType) { var constructorArguments = data.ConstructorArguments[0].Values; var constructorArgumentsTypes = constructorArguments.Select(constant => constant.Value as ITypeSymbol).ToList(); array.AddRange(constructorArgumentsTypes!); } } /// /// Adds a query with an entity for a given annotated method. The attributes of these methods are used to generate the query. /// /// The instance. /// The which is annotated for source generation. /// public static StringBuilder AppendQueryMethod(this StringBuilder sb, IMethodSymbol methodSymbol) { // Check for entity param var entity = methodSymbol.Parameters.Any(symbol => symbol.Type.Name.Equals("Entity")); var entityParam = entity ? methodSymbol.Parameters.First(symbol => symbol.Type.Name.Equals("Entity")) : null; var queryData = methodSymbol.GetAttributeData("Query"); bool isParallel = (bool)(queryData.NamedArguments.FirstOrDefault(d => d.Key == "Parallel").Value.Value ?? false); // Get attributes var attributeData = methodSymbol.GetAttributeData("All"); var anyAttributeData = methodSymbol.GetAttributeData("Any"); var noneAttributeData = methodSymbol.GetAttributeData("None"); var exclusiveAttributeData = methodSymbol.GetAttributeData("Exclusive"); // Get params / components except those marked with data or entities. var components = methodSymbol.Parameters.ToList(); components.RemoveAll(symbol => symbol.Type.Name.Equals("Entity")); // Remove entitys components.RemoveAll(symbol => symbol.GetAttributes().Any(data => data.AttributeClass!.Name.Contains("Data"))); // Remove data annotated params // Create all query array var allArray = components.Select(symbol => symbol.Type).ToList(); var anyArray = new List(); var noneArray = new List(); var exclusiveArray = new List(); // Get All<...> or All(...) passed types and pass them to the arrays GetAttributeTypes(attributeData, allArray); GetAttributeTypes(anyAttributeData, anyArray); GetAttributeTypes(noneAttributeData, noneArray); GetAttributeTypes(exclusiveAttributeData, exclusiveArray); // Remove doubles and entities allArray = allArray.Distinct(SymbolEqualityComparer.Default).ToList(); anyArray = anyArray.Distinct(SymbolEqualityComparer.Default).ToList(); noneArray = noneArray.Distinct(SymbolEqualityComparer.Default).ToList(); exclusiveArray = exclusiveArray.Distinct(SymbolEqualityComparer.Default).ToList(); allArray.RemoveAll(symbol => symbol.Name.Equals("Entity")); anyArray.RemoveAll(symbol => symbol.Name.Equals("Entity")); noneArray.RemoveAll(symbol => symbol.Name.Equals("Entity")); exclusiveArray.RemoveAll(symbol => symbol.Name.Equals("Entity")); // Create data modell and generate it var className = methodSymbol.ContainingSymbol.ToString(); var queryMethod = new QueryMethod { IsGlobalNamespace = methodSymbol.ContainingNamespace.IsGlobalNamespace, Namespace = methodSymbol.ContainingNamespace.ToString(), ClassName = className.Substring(className.LastIndexOf('.')+1), IsStatic = methodSymbol.IsStatic, IsEntityQuery = entity, MethodName = methodSymbol.Name, EntityParameter = entityParam!, Parameters = methodSymbol.Parameters, Components = components, AllFilteredTypes = allArray, AnyFilteredTypes = anyArray, NoneFilteredTypes = noneArray, ExclusiveFilteredTypes = exclusiveArray }; return isParallel ? sb.AppendParallelQueryMethod(ref queryMethod) : sb.AppendQueryMethod(ref queryMethod); } /// /// Adds a query with an entity for a given annotated method. The attributes of these methods are used to generate the query. /// /// The instance. /// The which is generated. /// public static StringBuilder AppendQueryMethod(this StringBuilder sb, ref QueryMethod queryMethod) { var staticModifier = queryMethod.IsStatic ? "static" : ""; // Generate code var data = new StringBuilder().DataParameters(queryMethod.Parameters); var getFirstElements = new StringBuilder().GetFirstElements(queryMethod.Components); var getComponents = new StringBuilder().GetComponents(queryMethod.Components); var insertParams = new StringBuilder().InsertParams(queryMethod.Parameters); var allTypeArray = new StringBuilder().GetTypeArray(queryMethod.AllFilteredTypes); var anyTypeArray = new StringBuilder().GetTypeArray(queryMethod.AnyFilteredTypes); var noneTypeArray = new StringBuilder().GetTypeArray(queryMethod.NoneFilteredTypes); var exclusiveTypeArray = new StringBuilder().GetTypeArray(queryMethod.ExclusiveFilteredTypes); var template = $$""" #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; {{(!queryMethod.IsGlobalNamespace ? $"namespace {queryMethod.Namespace} {{" : "")}} partial class {{queryMethod.ClassName}}{ private {{staticModifier}} QueryDescription {{queryMethod.MethodName}}_QueryDescription = new QueryDescription( all: {{allTypeArray}}, any: {{anyTypeArray}}, none: {{noneTypeArray}}, exclusive: {{exclusiveTypeArray}} ); private {{staticModifier}} World? _{{queryMethod.MethodName}}_Initialized; private {{staticModifier}} Query? _{{queryMethod.MethodName}}_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public {{staticModifier}} void {{queryMethod.MethodName}}Query(World world {{data}}){ if(!ReferenceEquals(_{{queryMethod.MethodName}}_Initialized, world)) { _{{queryMethod.MethodName}}_Query = world.Query(in {{queryMethod.MethodName}}_QueryDescription); _{{queryMethod.MethodName}}_Initialized = world; } foreach(ref var chunk in _{{queryMethod.MethodName}}_Query!){ {{(queryMethod.IsEntityQuery ? "ref var entityFirstElement = ref chunk.Entity(0);" : "")}} {{getFirstElements}} foreach(var entityIndex in chunk) { {{(queryMethod.IsEntityQuery ? $"ref readonly var {queryMethod.EntityParameter.Name.ToLower()} = ref Unsafe.Add(ref entityFirstElement, entityIndex);" : "")}} {{getComponents}} {{queryMethod.MethodName}}({{insertParams}}); } } } } {{(!queryMethod.IsGlobalNamespace ? "}" : "")}} """; sb.Append(template); return sb; } /// /// Adds a parallel query with an entity for a given annotated method. The attributes of these methods are used to generate the query. /// /// The instance. /// The which is generated. /// public static StringBuilder AppendParallelQueryMethod(this StringBuilder sb, ref QueryMethod queryMethod) { var staticModifier = queryMethod.IsStatic ? "static" : ""; // Generate code var jobParameters = new StringBuilder().JobParameters(queryMethod.Parameters); var jobParametersAssigment = new StringBuilder().JobParametersAssigment(queryMethod.Parameters); var data = new StringBuilder().DataParameters(queryMethod.Parameters); var getFirstElements = new StringBuilder().GetFirstElements(queryMethod.Components); var getComponents = new StringBuilder().GetComponents(queryMethod.Components); var insertParams = new StringBuilder().InsertParams(queryMethod.Parameters); var allTypeArray = new StringBuilder().GetTypeArray(queryMethod.AllFilteredTypes); var anyTypeArray = new StringBuilder().GetTypeArray(queryMethod.AnyFilteredTypes); var noneTypeArray = new StringBuilder().GetTypeArray(queryMethod.NoneFilteredTypes); var exclusiveTypeArray = new StringBuilder().GetTypeArray(queryMethod.ExclusiveFilteredTypes); var template = $$""" #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; {{(!queryMethod.IsGlobalNamespace ? $"namespace {queryMethod.Namespace} {{" : "")}} partial class {{queryMethod.ClassName}}{ private {{staticModifier}} QueryDescription {{queryMethod.MethodName}}_QueryDescription = new QueryDescription( all: {{allTypeArray}}, any: {{anyTypeArray}}, none: {{noneTypeArray}}, exclusive: {{exclusiveTypeArray}} ); private {{staticModifier}} World? _{{queryMethod.MethodName}}_Initialized; private {{staticModifier}} Query? _{{queryMethod.MethodName}}_Query; private struct {{queryMethod.MethodName}}QueryJobChunk : IChunkJob { {{jobParameters}} public void Execute(ref Chunk chunk) { {{(queryMethod.IsEntityQuery ? "ref var entityFirstElement = ref chunk.Entity(0);" : "")}} {{getFirstElements}} foreach(var entityIndex in chunk) { {{(queryMethod.IsEntityQuery ? $"ref readonly var {queryMethod.EntityParameter.Name.ToLower()} = ref Unsafe.Add(ref entityFirstElement, entityIndex);" : "")}} {{getComponents}} {{queryMethod.MethodName}}({{insertParams}}); } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public {{staticModifier}} void {{queryMethod.MethodName}}Query(World world {{data}}){ if(!ReferenceEquals(_{{queryMethod.MethodName}}_Initialized, world)) { _{{queryMethod.MethodName}}_Query = world.Query(in {{queryMethod.MethodName}}_QueryDescription); _{{queryMethod.MethodName}}_Initialized = world; } var job = new {{queryMethod.MethodName}}QueryJobChunk() { {{jobParametersAssigment}} }; world.InlineParallelChunkQuery(in {{queryMethod.MethodName}}_QueryDescription, job); } } {{(!queryMethod.IsGlobalNamespace ? "}" : "")}} """; sb.Append(template); return sb; } /// /// Adds a basesystem that calls a bunch of query methods. /// /// The instance. /// The which maps all query methods to a common class containing them. /// public static StringBuilder AppendBaseSystem(this StringBuilder sb, KeyValuePair> classToMethod) { // Get BaseSystem class var classSymbol = classToMethod.Key as INamedTypeSymbol; INamedTypeSymbol? parentSymbol = null; var implementsUpdate = false; var type = classSymbol; while (type != null) { // Update was implemented by user, no need to do that by source generator. if (type.GetMembers("Update").OfType().Any(member => member.IsOverride)) implementsUpdate = true; type = type.BaseType; // Ignore classes which do not derive from BaseSystem if (type?.Name == "BaseSystem") { parentSymbol = type; break; } } if (parentSymbol == null || implementsUpdate) return sb; // Get generic of BaseSystem var typeSymbol = parentSymbol.TypeArguments[1]; var className = classSymbol!.ToString(); // Generate basesystem. var baseSystem = new BaseSystem { Namespace = classSymbol.ContainingNamespace != null && !classSymbol.ContainingNamespace.IsGlobalNamespace ? classSymbol.ContainingNamespace.ToString() : string.Empty, GenericType = typeSymbol, GenericTypeNamespace = typeSymbol.ContainingNamespace.ToString(), Name = className.Substring(className.LastIndexOf('.') + 1), QueryMethods = classToMethod.Value, }; return sb.AppendBaseSystem(ref baseSystem); } /// /// Adds a basesystem that calls a bunch of query methods. /// /// The instance. /// The which is generated. /// public static StringBuilder AppendBaseSystem(this StringBuilder sb, ref BaseSystem baseSystem) { var methodCalls = new StringBuilder().CallMethods(baseSystem.QueryMethods); var template = $$""" using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using {{baseSystem.GenericTypeNamespace}}; {{(baseSystem.Namespace != string.Empty ? $"namespace {baseSystem.Namespace} {{" : "")}} partial class {{baseSystem.Name}}{ [MethodImpl(MethodImplOptions.AggressiveInlining)] public override void Update(in {{baseSystem.GenericType.ToDisplayString()}} data){ {{methodCalls}} } } {{(baseSystem.Namespace != string.Empty ? "}" : "")}} """; return sb.Append(template); } } ================================================ FILE: Arch.System.SourceGenerator/SourceGenerator.cs ================================================ using System.Collections.Immutable; using System.Diagnostics; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace Arch.System.SourceGenerator; /// /// Generates queries. /// [Generator] public class QueryGenerator : IIncrementalGenerator { private static Dictionary> _classToMethods { get; set; } = null!; /// public void Initialize(IncrementalGeneratorInitializationContext context) { //if (!Debugger.IsAttached) Debugger.Launch(); // Do a simple filter for methods marked with update IncrementalValuesProvider methodDeclarations = context.SyntaxProvider.CreateSyntaxProvider( static (s, _) => s is MethodDeclarationSyntax { AttributeLists.Count: > 0 }, static (ctx, _) => GetMethodSymbolIfAttributeof(ctx, "Arch.System.QueryAttribute") ).Where(static m => m is not null)!; // filter out attributed methods that we don't care about // Combine the selected enums with the `Compilation` IncrementalValueProvider<(Compilation, ImmutableArray)> compilationAndMethods = context.CompilationProvider.Combine(methodDeclarations.WithComparer(Comparer.Instance).Collect()); context.RegisterSourceOutput(compilationAndMethods, static (spc, source) => Generate(source.Item1, source.Item2, spc)); } /// /// Adds a to its class. /// Stores them in . /// /// The which will be added/mapped to its class. private static void AddMethodToClass(IMethodSymbol methodSymbol) { if (!_classToMethods.TryGetValue(methodSymbol.ContainingSymbol, out var list)) { list = new List(); _classToMethods[methodSymbol.ContainingSymbol] = list; } list.Add(methodSymbol); } /// /// Returns a if it's annotated with an attribute of . /// /// Its . /// The attributes name. /// private static MethodDeclarationSyntax? GetMethodSymbolIfAttributeof(GeneratorSyntaxContext context, string name) { // we know the node is a EnumDeclarationSyntax thanks to IsSyntaxTargetForGeneration var enumDeclarationSyntax = (MethodDeclarationSyntax)context.Node; // loop through all the attributes on the method foreach (var attributeListSyntax in enumDeclarationSyntax.AttributeLists) { foreach (var attributeSyntax in attributeListSyntax.Attributes) { if (ModelExtensions.GetSymbolInfo(context.SemanticModel, attributeSyntax).Symbol is not IMethodSymbol attributeSymbol) continue; var attributeContainingTypeSymbol = attributeSymbol.ContainingType; var fullName = attributeContainingTypeSymbol.ToDisplayString(); // Is the attribute the [EnumExtensions] attribute? if (fullName != name) continue; return enumDeclarationSyntax; } } // we didn't find the attribute we were looking for return null; } /// /// Generates queries and partial classes for the found marked methods. /// /// The . /// The array, the methods which we will generate queries and classes for. /// The . private static void Generate(Compilation compilation, ImmutableArray methods, SourceProductionContext context) { if (methods.IsDefaultOrEmpty) return; // Generate Query methods and map them to their classes _classToMethods = new(512, SymbolEqualityComparer.Default); foreach (var methodSyntax in methods) { IMethodSymbol? methodSymbol = null; try { var semanticModel = compilation.GetSemanticModel(methodSyntax.SyntaxTree); methodSymbol = ModelExtensions.GetDeclaredSymbol(semanticModel, methodSyntax) as IMethodSymbol; } catch { //not update,skip continue; } AddMethodToClass(methodSymbol!); var sb = new StringBuilder(); var method = sb.AppendQueryMethod(methodSymbol!); var fileName = methodSymbol!.ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat).Replace('<', '{').Replace('>', '}'); context.AddSource($"{fileName}.g.cs",CSharpSyntaxTree.ParseText(method.ToString()).GetRoot().NormalizeWhitespace().ToFullString()); } // Creating class that calls the created methods after another. foreach (var classToMethod in _classToMethods) { var template = new StringBuilder().AppendBaseSystem(classToMethod).ToString(); if (string.IsNullOrEmpty(template)) continue; var fileName = ((INamedTypeSymbol)classToMethod.Key).ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat).Replace('<', '{').Replace('>', '}'); context.AddSource($"{fileName}.g.cs", CSharpSyntaxTree.ParseText(template).GetRoot().NormalizeWhitespace().ToFullString()); } } /// /// Compares s to remove duplicates. /// class Comparer : IEqualityComparer { public static readonly Comparer Instance = new(); public bool Equals(MethodDeclarationSyntax x, MethodDeclarationSyntax y) { return x.Equals(y); } public int GetHashCode(MethodDeclarationSyntax obj) { return obj.GetHashCode(); } } } ================================================ FILE: Arch.System.SourceGenerator.SnapshotTests/.editorconfig ================================================ root = true # All files [*] # Indentation and spacing end_of_line = crlf indent_style = space trim_trailing_whitespace = true # New line preferences end_of_line = crlf insert_final_newline = true # Xml project files [*.{csproj,proj,projitems,shproj,vbproj,vcxproj,vcxproj.filters}] indent_size = 2 # Xml config files [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] indent_size = 2 # Other markup files [*.{json,xml,yml,yaml}] indent_size = 2 # Markdown files [*.md] indent_size = 1 trim_trailing_whitespace = false # C# files [*.cs] #### Core EditorConfig Options #### file_header_template = unset # Indentation and spacing indent_size = 4 #### .NET Coding Conventions #### [*.{cs,vb}] # Organize usings dotnet_separate_import_directive_groups = false dotnet_sort_system_directives_first = true # this. and Me. preferences dotnet_style_qualification_for_event = false:warning dotnet_style_qualification_for_field = false:warning dotnet_style_qualification_for_method = false:warning dotnet_style_qualification_for_property = false:warning # Language keywords vs BCL types preferences dotnet_style_predefined_type_for_locals_parameters_members = true:warning dotnet_style_predefined_type_for_member_access = true:warning # Parentheses preferences dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning dotnet_style_parentheses_in_other_operators = never_if_unnecessary:warning dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning # Modifier preferences dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning # Expression-level preferences dotnet_style_coalesce_expression = true:suggestion dotnet_style_collection_initializer = true:warning dotnet_style_explicit_tuple_names = true:warning dotnet_style_null_propagation = true:suggestion dotnet_style_object_initializer = true:warning dotnet_style_operator_placement_when_wrapping = beginning_of_line dotnet_style_prefer_auto_properties = true:warning dotnet_style_prefer_compound_assignment = true:warning dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion dotnet_style_prefer_conditional_expression_over_return = false:silent dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed dotnet_style_prefer_inferred_anonymous_type_member_names = false:warning dotnet_style_prefer_inferred_tuple_names = false:warning dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning dotnet_style_prefer_simplified_boolean_expressions = true:suggestion dotnet_style_prefer_simplified_interpolation = true:suggestion # Field preferences dotnet_style_readonly_field = true:warning # Parameter preferences dotnet_code_quality_unused_parameters = all:suggestion # Suppression preferences dotnet_remove_unnecessary_suppression_exclusions = silent # New line preferences dotnet_style_allow_multiple_blank_lines_experimental = false:warning dotnet_style_allow_statement_immediately_after_block_experimental = false:warning #### C# Coding Conventions #### [*.cs] # var preferences csharp_style_var_elsewhere = true:none csharp_style_var_for_built_in_types = true:none csharp_style_var_when_type_is_apparent = true:none # Expression-bodied members csharp_style_expression_bodied_accessors = when_on_single_line:suggestion csharp_style_expression_bodied_constructors = false:warning csharp_style_expression_bodied_indexers = false:warning csharp_style_expression_bodied_lambdas = when_on_single_line:suggestion csharp_style_expression_bodied_local_functions = false:warning csharp_style_expression_bodied_methods = false:warning csharp_style_expression_bodied_operators = false:warning csharp_style_expression_bodied_properties = false:warning # Pattern matching preferences csharp_style_pattern_matching_over_as_with_null_check = true:warning csharp_style_pattern_matching_over_is_with_cast_check = true:warning csharp_style_prefer_not_pattern = true:warning csharp_style_prefer_pattern_matching = true:suggestion csharp_style_prefer_switch_expression = true:warning # Null-checking preferences csharp_style_conditional_delegate_call = true:suggestion # Modifier preferences csharp_prefer_static_local_function = true:warning csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent # Code-block preferences csharp_prefer_braces = true:warning csharp_prefer_simple_using_statement = true:warning csharp_style_prefer_method_group_conversion = true:suggestion csharp_style_prefer_top_level_statements = true:suggestion # Expression-level preferences csharp_prefer_simple_default_expression = true:warning csharp_style_deconstructed_variable_declaration = true:warning csharp_style_implicit_object_creation_when_type_is_apparent = true:warning csharp_style_inlined_variable_declaration = true:warning csharp_style_pattern_local_over_anonymous_function = true:warning csharp_style_prefer_index_operator = true:warning csharp_style_prefer_local_over_anonymous_function = true:warning csharp_style_prefer_null_check_over_type_check = true:warning csharp_style_prefer_range_operator = true:warning csharp_style_prefer_tuple_swap = true:warning csharp_style_prefer_utf8_string_literals = false:silent csharp_style_throw_expression = false:warning csharp_style_unused_value_assignment_preference = discard_variable:none csharp_style_unused_value_expression_statement_preference = discard_variable:none # 'using' directive preferences csharp_using_directive_placement = outside_namespace:warning #### C# Formatting Rules #### # New line preferences csharp_new_line_before_catch = true csharp_new_line_before_else = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_anonymous_types = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_open_brace = all csharp_new_line_between_query_expression_clauses = true csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:warning csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:warning csharp_style_allow_embedded_statements_on_same_line_experimental = false:warning # Indentation preferences csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents = true csharp_indent_case_contents_when_block = false csharp_indent_labels = one_less_than_current csharp_indent_switch_labels = true # Space preferences csharp_space_after_cast = false csharp_space_after_colon_in_inheritance_clause = true csharp_space_after_comma = true csharp_space_after_dot = false csharp_space_after_keywords_in_control_flow_statements = true csharp_space_after_semicolon_in_for_statement = true csharp_space_around_binary_operators = before_and_after csharp_space_around_declaration_statements = false csharp_space_before_colon_in_inheritance_clause = true csharp_space_before_comma = false csharp_space_before_dot = false csharp_space_before_open_square_brackets = false csharp_space_before_semicolon_in_for_statement = false csharp_space_between_empty_square_brackets = false csharp_space_between_method_call_empty_parameter_list_parentheses = false csharp_space_between_method_call_name_and_opening_parenthesis = false csharp_space_between_method_call_parameter_list_parentheses = false csharp_space_between_method_declaration_empty_parameter_list_parentheses = false csharp_space_between_method_declaration_name_and_open_parenthesis = false csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false # Wrapping preferences csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = false # Namespace preferences csharp_style_namespace_declarations = file_scoped:warning dotnet_style_namespace_match_folder = false #### Naming styles #### [*.{cs,vb}] # Naming rules dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = warning dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = warning dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = warning dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase dotnet_naming_rule.constant_fields_should_be_pascalcase.severity = warning dotnet_naming_rule.constant_fields_should_be_pascalcase.symbols = constant_fields dotnet_naming_rule.constant_fields_should_be_pascalcase.style = pascalcase dotnet_naming_rule.public_fields_should_be_pascalcase.severity = warning dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase dotnet_naming_rule.private_fields_should_be__camelcase.severity = warning dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = warning dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase dotnet_naming_rule.parameters_should_be_camelcase.severity = warning dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase dotnet_naming_rule.local_constants_should_be_pascalcase.severity = warning dotnet_naming_rule.local_constants_should_be_pascalcase.symbols = local_constants dotnet_naming_rule.local_constants_should_be_pascalcase.style = pascalcase dotnet_naming_rule.local_variables_should_be_camelcase.severity = warning dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase dotnet_naming_rule.local_functions_should_be_camelcase.severity = warning dotnet_naming_rule.local_functions_should_be_camelcase.symbols = local_functions dotnet_naming_rule.local_functions_should_be_camelcase.style = camelcase # Symbol specifications dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.types_and_namespaces.required_modifiers = dotnet_naming_symbols.interfaces.applicable_kinds = interface dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.interfaces.required_modifiers = dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, delegate, method dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.non_field_members.required_modifiers = dotnet_naming_symbols.constant_fields.applicable_kinds = field dotnet_naming_symbols.constant_fields.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.constant_fields.required_modifiers = const dotnet_naming_symbols.public_fields.applicable_kinds = field dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal dotnet_naming_symbols.public_fields.required_modifiers = dotnet_naming_symbols.private_fields.applicable_kinds = field dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected dotnet_naming_symbols.private_fields.required_modifiers = dotnet_naming_symbols.type_parameters.applicable_kinds = type_parameter dotnet_naming_symbols.type_parameters.applicable_accessibilities = * dotnet_naming_symbols.type_parameters.required_modifiers = dotnet_naming_symbols.parameters.applicable_kinds = parameter dotnet_naming_symbols.parameters.applicable_accessibilities = * dotnet_naming_symbols.parameters.required_modifiers = dotnet_naming_symbols.local_constants.applicable_kinds = local dotnet_naming_symbols.local_constants.applicable_accessibilities = local dotnet_naming_symbols.local_constants.required_modifiers = const dotnet_naming_symbols.local_variables.applicable_kinds = local dotnet_naming_symbols.local_variables.applicable_accessibilities = local dotnet_naming_symbols.local_variables.required_modifiers = dotnet_naming_symbols.local_functions.applicable_kinds = local_function dotnet_naming_symbols.local_functions.applicable_accessibilities = local dotnet_naming_symbols.local_functions.required_modifiers = # Naming styles dotnet_naming_style.pascalcase.required_prefix = dotnet_naming_style.pascalcase.required_suffix = dotnet_naming_style.pascalcase.word_separator = dotnet_naming_style.pascalcase.capitalization = pascal_case dotnet_naming_style.ipascalcase.required_prefix = I dotnet_naming_style.ipascalcase.required_suffix = dotnet_naming_style.ipascalcase.word_separator = dotnet_naming_style.ipascalcase.capitalization = pascal_case dotnet_naming_style.tpascalcase.required_prefix = T dotnet_naming_style.tpascalcase.required_suffix = dotnet_naming_style.tpascalcase.word_separator = dotnet_naming_style.tpascalcase.capitalization = pascal_case dotnet_naming_style.camelcase.required_prefix = dotnet_naming_style.camelcase.required_suffix = dotnet_naming_style.camelcase.word_separator = dotnet_naming_style.camelcase.capitalization = camel_case dotnet_naming_style._camelcase.required_prefix = _ dotnet_naming_style._camelcase.required_suffix = dotnet_naming_style._camelcase.word_separator = dotnet_naming_style._camelcase.capitalization = camel_case ================================================ FILE: Arch.System.SourceGenerator.SnapshotTests/Arch.System.SourceGenerator.SnapshotTests.csproj ================================================  net8.0 enable enable false latest all runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive ================================================ FILE: Arch.System.SourceGenerator.SnapshotTests/SnapshotTest.cs ================================================ using System.Reflection; using System.Runtime.ExceptionServices; using Arch.Core; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using NUnit.Framework; namespace Arch.System.SourceGenerator.Tests; /// /// Tests by individually compiling and running each test system from /// Arch.System.SourceGenerator.TestCompilation. /// The tests themselves are run in the Arch.System.SourceGenerator.TestCompilation project as well, /// but here they are run a second time in complete isolation. /// These tests also ensure that the generated code matches the expected output. /// /// /// For future devs: If you're having trouble with specific test errors, first make sure /// your system does not use Primary Constructors or Collection Initializers. /// [TestFixture] internal class SnapshotTest { public static string ProjectName { get; } = $"{nameof(Arch)}.{nameof(System)}.{nameof(SourceGenerator)}.Tests"; /// /// Loads the compilation into memory and tests the specified system. /// /// The compilation to test. /// The name of the system to test. private static void TestSystem(Compilation compilation, string testSystemName) { // Load the assembly using var memory = new MemoryStream(); compilation.Emit(memory); var assembly = Assembly.Load(memory.ToArray()); // A system needs a world using var world = World.Create(); // Find the system type in the assembly var systemType = assembly.GetType($"{ProjectName}.{testSystemName}"); Assert.That(systemType, Is.Not.Null, $"System type {testSystemName} not found in assembly {assembly.FullName}."); // Create an instance of the system var system = Activator.CreateInstance(systemType, new[] { world }) as BaseSystem; Assert.That(system, Is.Not.Null, $"Ensure {testSystemName} has a constructor that takes a single World param."); // Find the Test and Setup methods in the system var testMethod = system.GetType().GetMethod("Test", BindingFlags.Public | BindingFlags.Instance); Assert.That(testMethod, Is.Not.Null, $"Method 'Test' not found in system {testSystemName}."); var setupMethod = system.GetType().GetMethod("Setup", BindingFlags.Public | BindingFlags.Instance); Assert.That(setupMethod, Is.Not.Null, $"Method 'Setup' not found in system {testSystemName}."); try { // Test the system! setupMethod.Invoke(system, []); testMethod.Invoke(system, []); } catch (TargetInvocationException ex) { // Throw the inner exception for a more useful log ExceptionDispatchInfo.Capture(ex.InnerException ?? ex).Throw(); } } /// /// Verifies the compilation of the specified folder from Arch.System.SourceGenerator.TestCompilation, /// and tests the system, if provided. /// /// The folder containing the compilation files. /// The name of the system to test, or null if the compilation doesn't point to a BaseTestSystem. private static void VerifyCompilation(string compilationFolder, string? testSystemName) { var solutionPath = Directory.GetParent(AppDomain.CurrentDomain.BaseDirectory)?.Parent?.Parent?.Parent?.Parent?.FullName; Assert.That(solutionPath, Is.Not.Null, "Could not find project path."); var projectPath = Path.Combine(solutionPath, ProjectName); Assert.That(projectPath, Is.Not.Null, "Could not find project path."); var compilationDirectory = new DirectoryInfo(Path.Combine(projectPath, compilationFolder)); Assert.That(compilationDirectory.Exists, Is.True, $"Could not find {compilationFolder} compilation directory."); // Get all .cs files in the compilation directory var csFiles = compilationDirectory.GetFiles("*.cs", SearchOption.AllDirectories); // Ensure that the expected files are NOT present, // so they don't conflict with the source-generated output. var sourceCsFiles = csFiles .Where(file => !file.FullName.Contains("ExpectedGeneration")); Assert.That(sourceCsFiles, Is.Not.Empty, "No source files found for compilation."); // Include shared files in the compilation var sharedDirectory = new DirectoryInfo(Path.Combine(projectPath, "Shared")); sourceCsFiles = sourceCsFiles.Concat(sharedDirectory.GetFiles("*.cs", SearchOption.AllDirectories)); // Parse all the source files var parseOptions = CSharpParseOptions.Default .WithLanguageVersion(LanguageVersion.Latest); var sourceTrees = sourceCsFiles .Select(file => CSharpSyntaxTree.ParseText(File.ReadAllText(file.FullName), parseOptions)); // Get the system assembly references var baseAssemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location); var systemAssemblies = Directory.GetFiles(baseAssemblyPath!) .Where(x => !x.EndsWith("Native.dll")) .Where(x => { var filename = Path.GetFileName(x); return filename.StartsWith("System") || (filename is "mscorlib.dll" or "netstandard.dll"); }); // Get any additional references needed for the compilation IEnumerable references = [ typeof(World).Assembly.Location, typeof(Assert).Assembly.Location, typeof(QueryAttribute).Assembly.Location, typeof(CommunityToolkit.HighPerformance.ArrayExtensions).Assembly.Location ]; references = references.Concat(systemAssemblies); // Run the compilation with the source trees and references var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) .WithOptimizationLevel(OptimizationLevel.Release); var inputCompilation = CSharpCompilation.Create(compilationFolder, sourceTrees, references: references.Select(r => MetadataReference.CreateFromFile(r)), options: compilationOptions); // Run the generator GeneratorDriver driver = CSharpGeneratorDriver.Create(new QueryGenerator()); driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics); // Check for compilation diagnostics var outputDiagnostics = outputCompilation.GetDiagnostics(); var errors = outputDiagnostics.Where(d => d.Severity == DiagnosticSeverity.Error); var warnings = outputDiagnostics.Where(d => d.Severity == DiagnosticSeverity.Warning); Assert.Multiple(() => { Assert.That(diagnostics, Is.Empty, "Generator diagnostics should be empty." + string.Join("\n", diagnostics)); Assert.That(errors, Is.Empty, "Output compilation should not have errors.\n" + string.Join("\n", errors)); Warn.Unless(warnings, Is.Empty, "Output compilation should not have warnings.\n" + string.Join("\n", warnings)); }); // Compare the generated files with the expected files var expectedFiles = csFiles .Where(file => file.FullName.Contains("ExpectedGeneration")) .Select(file => (Name: file.Name, Text: File.ReadAllText(file.FullName))) .OrderBy(x => x.Name).ToArray(); var generatedFiles = driver.GetRunResult().GeneratedTrees .Select(tree => (Name: Path.GetFileName(tree.FilePath), Text: tree.GetText().ToString())) .Where(x => x.Name != "Attributes.g.cs") // Skip the attributes file .OrderBy(x => x.Name).ToArray(); Assert.That(generatedFiles, Is.EqualTo(expectedFiles)); // If we're not testing a specific BaseTestSystem, we're done! if (testSystemName is not null) { TestSystem(outputCompilation, testSystemName); } } [Test] public void BasicCompilation() { VerifyCompilation(nameof(BasicCompilation), "BasicSystem"); } [Test] public void AttributeQueryCompilation() { VerifyCompilation(nameof(AttributeQueryCompilation), "AttributeQuerySystem"); } [Test] public void ParamQueryCompilation() { VerifyCompilation(nameof(ParamQueryCompilation), "ParamQuerySystem"); } [Test] public void DataParamCompilation() { VerifyCompilation(nameof(DataParamCompilation), "DataParamSystem"); } [Test] public void GeneratedUpdateCompilation() { VerifyCompilation(nameof(GeneratedUpdateCompilation), "GeneratedUpdateSystem"); } } ================================================ FILE: Arch.System.SourceGenerator.Tests/.editorconfig ================================================ root = true # All files [*] # Indentation and spacing end_of_line = crlf indent_style = space trim_trailing_whitespace = true # New line preferences end_of_line = crlf insert_final_newline = true # Xml project files [*.{csproj,proj,projitems,shproj,vbproj,vcxproj,vcxproj.filters}] indent_size = 2 # Xml config files [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] indent_size = 2 # Other markup files [*.{json,xml,yml,yaml}] indent_size = 2 # Markdown files [*.md] indent_size = 1 trim_trailing_whitespace = false # C# files [*.cs] #### Core EditorConfig Options #### file_header_template = unset # Indentation and spacing indent_size = 4 #### .NET Coding Conventions #### [*.{cs,vb}] # Organize usings dotnet_separate_import_directive_groups = false dotnet_sort_system_directives_first = true # this. and Me. preferences dotnet_style_qualification_for_event = false:warning dotnet_style_qualification_for_field = false:warning dotnet_style_qualification_for_method = false:warning dotnet_style_qualification_for_property = false:warning # Language keywords vs BCL types preferences dotnet_style_predefined_type_for_locals_parameters_members = true:warning dotnet_style_predefined_type_for_member_access = true:warning # Parentheses preferences dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning dotnet_style_parentheses_in_other_operators = never_if_unnecessary:warning dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning # Modifier preferences dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning # Expression-level preferences dotnet_style_coalesce_expression = true:suggestion dotnet_style_collection_initializer = true:warning dotnet_style_explicit_tuple_names = true:warning dotnet_style_null_propagation = true:suggestion dotnet_style_object_initializer = true:warning dotnet_style_operator_placement_when_wrapping = beginning_of_line dotnet_style_prefer_auto_properties = true:warning dotnet_style_prefer_compound_assignment = true:warning dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion dotnet_style_prefer_conditional_expression_over_return = false:silent dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed dotnet_style_prefer_inferred_anonymous_type_member_names = false:warning dotnet_style_prefer_inferred_tuple_names = false:warning dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning dotnet_style_prefer_simplified_boolean_expressions = true:suggestion dotnet_style_prefer_simplified_interpolation = true:suggestion # Field preferences dotnet_style_readonly_field = true:warning # Parameter preferences dotnet_code_quality_unused_parameters = all:suggestion # Suppression preferences dotnet_remove_unnecessary_suppression_exclusions = silent # New line preferences dotnet_style_allow_multiple_blank_lines_experimental = false:warning dotnet_style_allow_statement_immediately_after_block_experimental = false:warning #### C# Coding Conventions #### [*.cs] # var preferences csharp_style_var_elsewhere = true:none csharp_style_var_for_built_in_types = true:none csharp_style_var_when_type_is_apparent = true:none # Expression-bodied members csharp_style_expression_bodied_accessors = when_on_single_line:suggestion csharp_style_expression_bodied_constructors = false:warning csharp_style_expression_bodied_indexers = false:warning csharp_style_expression_bodied_lambdas = when_on_single_line:suggestion csharp_style_expression_bodied_local_functions = false:warning csharp_style_expression_bodied_methods = false:warning csharp_style_expression_bodied_operators = false:warning csharp_style_expression_bodied_properties = false:warning # Pattern matching preferences csharp_style_pattern_matching_over_as_with_null_check = true:warning csharp_style_pattern_matching_over_is_with_cast_check = true:warning csharp_style_prefer_not_pattern = true:warning csharp_style_prefer_pattern_matching = true:suggestion csharp_style_prefer_switch_expression = true:warning # Null-checking preferences csharp_style_conditional_delegate_call = true:suggestion # Modifier preferences csharp_prefer_static_local_function = true:warning csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent # Code-block preferences csharp_prefer_braces = true:warning csharp_prefer_simple_using_statement = true:warning csharp_style_prefer_method_group_conversion = true:suggestion csharp_style_prefer_top_level_statements = true:suggestion # Expression-level preferences csharp_prefer_simple_default_expression = true:warning csharp_style_deconstructed_variable_declaration = true:warning csharp_style_implicit_object_creation_when_type_is_apparent = true:warning csharp_style_inlined_variable_declaration = true:warning csharp_style_pattern_local_over_anonymous_function = true:warning csharp_style_prefer_index_operator = true:warning csharp_style_prefer_local_over_anonymous_function = true:warning csharp_style_prefer_null_check_over_type_check = true:warning csharp_style_prefer_range_operator = true:warning csharp_style_prefer_tuple_swap = true:warning csharp_style_prefer_utf8_string_literals = false:silent csharp_style_throw_expression = false:warning csharp_style_unused_value_assignment_preference = discard_variable:none csharp_style_unused_value_expression_statement_preference = discard_variable:none # 'using' directive preferences csharp_using_directive_placement = outside_namespace:warning #### C# Formatting Rules #### # New line preferences csharp_new_line_before_catch = true csharp_new_line_before_else = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_anonymous_types = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_open_brace = all csharp_new_line_between_query_expression_clauses = true csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:warning csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:warning csharp_style_allow_embedded_statements_on_same_line_experimental = false:warning # Indentation preferences csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents = true csharp_indent_case_contents_when_block = false csharp_indent_labels = one_less_than_current csharp_indent_switch_labels = true # Space preferences csharp_space_after_cast = false csharp_space_after_colon_in_inheritance_clause = true csharp_space_after_comma = true csharp_space_after_dot = false csharp_space_after_keywords_in_control_flow_statements = true csharp_space_after_semicolon_in_for_statement = true csharp_space_around_binary_operators = before_and_after csharp_space_around_declaration_statements = false csharp_space_before_colon_in_inheritance_clause = true csharp_space_before_comma = false csharp_space_before_dot = false csharp_space_before_open_square_brackets = false csharp_space_before_semicolon_in_for_statement = false csharp_space_between_empty_square_brackets = false csharp_space_between_method_call_empty_parameter_list_parentheses = false csharp_space_between_method_call_name_and_opening_parenthesis = false csharp_space_between_method_call_parameter_list_parentheses = false csharp_space_between_method_declaration_empty_parameter_list_parentheses = false csharp_space_between_method_declaration_name_and_open_parenthesis = false csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false # Wrapping preferences csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = false # Namespace preferences csharp_style_namespace_declarations = file_scoped:warning dotnet_style_namespace_match_folder = false #### Naming styles #### [*.{cs,vb}] # Naming rules dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = warning dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = warning dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = warning dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase dotnet_naming_rule.constant_fields_should_be_pascalcase.severity = warning dotnet_naming_rule.constant_fields_should_be_pascalcase.symbols = constant_fields dotnet_naming_rule.constant_fields_should_be_pascalcase.style = pascalcase dotnet_naming_rule.public_fields_should_be_pascalcase.severity = warning dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase dotnet_naming_rule.private_fields_should_be__camelcase.severity = warning dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = warning dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase dotnet_naming_rule.parameters_should_be_camelcase.severity = warning dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase dotnet_naming_rule.local_constants_should_be_pascalcase.severity = warning dotnet_naming_rule.local_constants_should_be_pascalcase.symbols = local_constants dotnet_naming_rule.local_constants_should_be_pascalcase.style = pascalcase dotnet_naming_rule.local_variables_should_be_camelcase.severity = warning dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase dotnet_naming_rule.local_functions_should_be_camelcase.severity = warning dotnet_naming_rule.local_functions_should_be_camelcase.symbols = local_functions dotnet_naming_rule.local_functions_should_be_camelcase.style = camelcase # Symbol specifications dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.types_and_namespaces.required_modifiers = dotnet_naming_symbols.interfaces.applicable_kinds = interface dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.interfaces.required_modifiers = dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, delegate, method dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.non_field_members.required_modifiers = dotnet_naming_symbols.constant_fields.applicable_kinds = field dotnet_naming_symbols.constant_fields.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected dotnet_naming_symbols.constant_fields.required_modifiers = const dotnet_naming_symbols.public_fields.applicable_kinds = field dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal dotnet_naming_symbols.public_fields.required_modifiers = dotnet_naming_symbols.private_fields.applicable_kinds = field dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected dotnet_naming_symbols.private_fields.required_modifiers = dotnet_naming_symbols.type_parameters.applicable_kinds = type_parameter dotnet_naming_symbols.type_parameters.applicable_accessibilities = * dotnet_naming_symbols.type_parameters.required_modifiers = dotnet_naming_symbols.parameters.applicable_kinds = parameter dotnet_naming_symbols.parameters.applicable_accessibilities = * dotnet_naming_symbols.parameters.required_modifiers = dotnet_naming_symbols.local_constants.applicable_kinds = local dotnet_naming_symbols.local_constants.applicable_accessibilities = local dotnet_naming_symbols.local_constants.required_modifiers = const dotnet_naming_symbols.local_variables.applicable_kinds = local dotnet_naming_symbols.local_variables.applicable_accessibilities = local dotnet_naming_symbols.local_variables.required_modifiers = dotnet_naming_symbols.local_functions.applicable_kinds = local_function dotnet_naming_symbols.local_functions.applicable_accessibilities = local dotnet_naming_symbols.local_functions.required_modifiers = # Naming styles dotnet_naming_style.pascalcase.required_prefix = dotnet_naming_style.pascalcase.required_suffix = dotnet_naming_style.pascalcase.word_separator = dotnet_naming_style.pascalcase.capitalization = pascal_case dotnet_naming_style.ipascalcase.required_prefix = I dotnet_naming_style.ipascalcase.required_suffix = dotnet_naming_style.ipascalcase.word_separator = dotnet_naming_style.ipascalcase.capitalization = pascal_case dotnet_naming_style.tpascalcase.required_prefix = T dotnet_naming_style.tpascalcase.required_suffix = dotnet_naming_style.tpascalcase.word_separator = dotnet_naming_style.tpascalcase.capitalization = pascal_case dotnet_naming_style.camelcase.required_prefix = dotnet_naming_style.camelcase.required_suffix = dotnet_naming_style.camelcase.word_separator = dotnet_naming_style.camelcase.capitalization = camel_case dotnet_naming_style._camelcase.required_prefix = _ dotnet_naming_style._camelcase.required_suffix = dotnet_naming_style._camelcase.word_separator = dotnet_naming_style._camelcase.capitalization = camel_case # Primary constructors don't work with manual compilation of C# csharp_style_prefer_primary_constructors = false # Collection expressions don't work with manual compilation of C# dotnet_diagnostic.IDE0301.severity = none ================================================ FILE: Arch.System.SourceGenerator.Tests/.gitignore ================================================ Generated/ ================================================ FILE: Arch.System.SourceGenerator.Tests/Arch.System.SourceGenerator.Tests.csproj ================================================  net8.0 enable true all runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers; buildtransitive true Generated ================================================ FILE: Arch.System.SourceGenerator.Tests/AttributeQueryCompilation/AttributeQuerySystem.cs ================================================ using System; using System.Collections.Generic; using Arch.Core; using NUnit.Framework; namespace Arch.System.SourceGenerator.Tests; /// /// Tests queries with different attribute combinations. /// internal partial class AttributeQuerySystem : BaseTestSystem { public AttributeQuerySystem(World world) : base(world) { } [Query] [All(typeof(IntComponentA))] public void IncrementA(Entity e) { ref var a = ref World.Get(e); a.Value++; } [Query] [Any(typeof(IntComponentA), typeof(IntComponentB))] public void IncrementAOrB(Entity e) { ref var a = ref World.TryGetRef(e, out bool aExists); ref var b = ref World.TryGetRef(e, out bool bExists); if (aExists) { a.Value++; } if (bExists) { b.Value++; } } [Query] [Any(typeof(IntComponentA), typeof(IntComponentB))] [None(typeof(IntComponentC))] public void IncrementAOrBNotC(Entity e) { ref var a = ref World.TryGetRef(e, out bool aExists); ref var b = ref World.TryGetRef(e, out bool bExists); if (aExists) { a.Value++; } if (bExists) { b.Value++; } } [Query] [All(typeof(IntComponentA), typeof(IntComponentB))] public void IncrementAAndB(Entity e) { ref var a = ref World.Get(e); a.Value++; ref var b = ref World.Get(e); b.Value++; } [Query] [All(typeof(IntComponentA))] [None(typeof(IntComponentB))] public void IncrementANotB(Entity e) { ref var a = ref World.Get(e); a.Value++; } [Query] [Exclusive(typeof(IntComponentA), typeof(IntComponentB))] public void IncrementAAndBExclusive(Entity e) { ref var a = ref World.Get(e); a.Value++; ref var b = ref World.Get(e); b.Value++; } private (Entity Entity, Dictionary ComponentValues)[] _expectedComponentValues = Array.Empty<(Entity, Dictionary)>(); public override void Setup() { _expectedComponentValues = new [] { (World.Create(new IntComponentA()), new Dictionary { { typeof(IntComponentA), 0 } }), (World.Create(new IntComponentB()), new Dictionary { { typeof(IntComponentB), 0 } }), (World.Create(new IntComponentA(), new IntComponentB()), new Dictionary { { typeof(IntComponentA), 0 }, { typeof(IntComponentB), 0 } }), (World.Create(new IntComponentA(), new IntComponentB(), new IntComponentC()), new Dictionary { { typeof(IntComponentA), 0 }, { typeof(IntComponentB), 0 }, { typeof(IntComponentC), 0 } }) }; } private void TestExpectedValues() { foreach (var (e, values) in _expectedComponentValues) { foreach (var (type, expectedValue) in values) { var component = World.Get(e, type) as IIntComponent; Assert.That(component, Is.Not.Null); Assert.That(component.Value, Is.EqualTo(expectedValue)); } } } public override void Update(in int t) { TestExpectedValues(); IncrementAQuery(World); _expectedComponentValues[0].ComponentValues[typeof(IntComponentA)]++; _expectedComponentValues[2].ComponentValues[typeof(IntComponentA)]++; _expectedComponentValues[3].ComponentValues[typeof(IntComponentA)]++; TestExpectedValues(); IncrementAOrBQuery(World); _expectedComponentValues[0].ComponentValues[typeof(IntComponentA)]++; _expectedComponentValues[1].ComponentValues[typeof(IntComponentB)]++; _expectedComponentValues[2].ComponentValues[typeof(IntComponentA)]++; _expectedComponentValues[2].ComponentValues[typeof(IntComponentB)]++; _expectedComponentValues[3].ComponentValues[typeof(IntComponentA)]++; _expectedComponentValues[3].ComponentValues[typeof(IntComponentB)]++; TestExpectedValues(); IncrementAOrBNotCQuery(World); _expectedComponentValues[0].ComponentValues[typeof(IntComponentA)]++; _expectedComponentValues[1].ComponentValues[typeof(IntComponentB)]++; _expectedComponentValues[2].ComponentValues[typeof(IntComponentA)]++; _expectedComponentValues[2].ComponentValues[typeof(IntComponentB)]++; TestExpectedValues(); IncrementAAndBQuery(World); _expectedComponentValues[2].ComponentValues[typeof(IntComponentA)]++; _expectedComponentValues[2].ComponentValues[typeof(IntComponentB)]++; _expectedComponentValues[3].ComponentValues[typeof(IntComponentA)]++; _expectedComponentValues[3].ComponentValues[typeof(IntComponentB)]++; TestExpectedValues(); IncrementANotBQuery(World); _expectedComponentValues[0].ComponentValues[typeof(IntComponentA)]++; TestExpectedValues(); IncrementAAndBExclusiveQuery(World); _expectedComponentValues[2].ComponentValues[typeof(IntComponentA)]++; _expectedComponentValues[2].ComponentValues[typeof(IntComponentB)]++; TestExpectedValues(); } } ================================================ FILE: Arch.System.SourceGenerator.Tests/AttributeQueryCompilation/ExpectedGeneration/AttributeQuerySystem.IncrementA(Entity).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class AttributeQuerySystem { private QueryDescription IncrementA_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private World? _IncrementA_Initialized; private Query? _IncrementA_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void IncrementAQuery(World world) { if (!ReferenceEquals(_IncrementA_Initialized, world)) { _IncrementA_Query = world.Query(in IncrementA_QueryDescription); _IncrementA_Initialized = world; } foreach (ref var chunk in _IncrementA_Query!) { ref var entityFirstElement = ref chunk.Entity(0); foreach (var entityIndex in chunk) { ref readonly var e = ref Unsafe.Add(ref entityFirstElement, entityIndex); IncrementA(@e); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/AttributeQueryCompilation/ExpectedGeneration/AttributeQuerySystem.IncrementAAndB(Entity).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class AttributeQuerySystem { private QueryDescription IncrementAAndB_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA), typeof(global::Arch.System.SourceGenerator.Tests.IntComponentB)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private World? _IncrementAAndB_Initialized; private Query? _IncrementAAndB_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void IncrementAAndBQuery(World world) { if (!ReferenceEquals(_IncrementAAndB_Initialized, world)) { _IncrementAAndB_Query = world.Query(in IncrementAAndB_QueryDescription); _IncrementAAndB_Initialized = world; } foreach (ref var chunk in _IncrementAAndB_Query!) { ref var entityFirstElement = ref chunk.Entity(0); foreach (var entityIndex in chunk) { ref readonly var e = ref Unsafe.Add(ref entityFirstElement, entityIndex); IncrementAAndB(@e); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/AttributeQueryCompilation/ExpectedGeneration/AttributeQuerySystem.IncrementAAndBExclusive(Entity).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class AttributeQuerySystem { private QueryDescription IncrementAAndBExclusive_QueryDescription = new QueryDescription(all: Signature.Null, any: Signature.Null, none: Signature.Null, exclusive: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA), typeof(global::Arch.System.SourceGenerator.Tests.IntComponentB))); private World? _IncrementAAndBExclusive_Initialized; private Query? _IncrementAAndBExclusive_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void IncrementAAndBExclusiveQuery(World world) { if (!ReferenceEquals(_IncrementAAndBExclusive_Initialized, world)) { _IncrementAAndBExclusive_Query = world.Query(in IncrementAAndBExclusive_QueryDescription); _IncrementAAndBExclusive_Initialized = world; } foreach (ref var chunk in _IncrementAAndBExclusive_Query!) { ref var entityFirstElement = ref chunk.Entity(0); foreach (var entityIndex in chunk) { ref readonly var e = ref Unsafe.Add(ref entityFirstElement, entityIndex); IncrementAAndBExclusive(@e); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/AttributeQueryCompilation/ExpectedGeneration/AttributeQuerySystem.IncrementANotB(Entity).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class AttributeQuerySystem { private QueryDescription IncrementANotB_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA)), any: Signature.Null, none: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentB)), exclusive: Signature.Null); private World? _IncrementANotB_Initialized; private Query? _IncrementANotB_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void IncrementANotBQuery(World world) { if (!ReferenceEquals(_IncrementANotB_Initialized, world)) { _IncrementANotB_Query = world.Query(in IncrementANotB_QueryDescription); _IncrementANotB_Initialized = world; } foreach (ref var chunk in _IncrementANotB_Query!) { ref var entityFirstElement = ref chunk.Entity(0); foreach (var entityIndex in chunk) { ref readonly var e = ref Unsafe.Add(ref entityFirstElement, entityIndex); IncrementANotB(@e); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/AttributeQueryCompilation/ExpectedGeneration/AttributeQuerySystem.IncrementAOrB(Entity).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class AttributeQuerySystem { private QueryDescription IncrementAOrB_QueryDescription = new QueryDescription(all: Signature.Null, any: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA), typeof(global::Arch.System.SourceGenerator.Tests.IntComponentB)), none: Signature.Null, exclusive: Signature.Null); private World? _IncrementAOrB_Initialized; private Query? _IncrementAOrB_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void IncrementAOrBQuery(World world) { if (!ReferenceEquals(_IncrementAOrB_Initialized, world)) { _IncrementAOrB_Query = world.Query(in IncrementAOrB_QueryDescription); _IncrementAOrB_Initialized = world; } foreach (ref var chunk in _IncrementAOrB_Query!) { ref var entityFirstElement = ref chunk.Entity(0); foreach (var entityIndex in chunk) { ref readonly var e = ref Unsafe.Add(ref entityFirstElement, entityIndex); IncrementAOrB(@e); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/AttributeQueryCompilation/ExpectedGeneration/AttributeQuerySystem.IncrementAOrBNotC(Entity).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class AttributeQuerySystem { private QueryDescription IncrementAOrBNotC_QueryDescription = new QueryDescription(all: Signature.Null, any: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA), typeof(global::Arch.System.SourceGenerator.Tests.IntComponentB)), none: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentC)), exclusive: Signature.Null); private World? _IncrementAOrBNotC_Initialized; private Query? _IncrementAOrBNotC_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void IncrementAOrBNotCQuery(World world) { if (!ReferenceEquals(_IncrementAOrBNotC_Initialized, world)) { _IncrementAOrBNotC_Query = world.Query(in IncrementAOrBNotC_QueryDescription); _IncrementAOrBNotC_Initialized = world; } foreach (ref var chunk in _IncrementAOrBNotC_Query!) { ref var entityFirstElement = ref chunk.Entity(0); foreach (var entityIndex in chunk) { ref readonly var e = ref Unsafe.Add(ref entityFirstElement, entityIndex); IncrementAOrBNotC(@e); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/BasicCompilation/BasicSystem.cs ================================================ using Arch.Core; using NUnit.Framework; namespace Arch.System.SourceGenerator.Tests; /// /// Tests basic query functionality. /// internal partial class BasicSystem : BaseTestSystem { public BasicSystem(World world) : base(world) { } private int _number = 0; static private int _numberStatic = 0; [Query] public void Basic(IntComponentA _) { _number++; _numberStatic++; } [Query] public static void BasicStatic(IntComponentA _) { _numberStatic++; } public override void Setup() { World.Create(new IntComponentA()); } public override void Update(in int t) { Assert.That(_number, Is.EqualTo(0)); Assert.That(_numberStatic, Is.EqualTo(0)); BasicQuery(World); Assert.That(_number, Is.EqualTo(1)); Assert.That(_numberStatic, Is.EqualTo(1)); BasicStaticQuery(World); Assert.That(_number, Is.EqualTo(1)); Assert.That(_numberStatic, Is.EqualTo(2)); } } ================================================ FILE: Arch.System.SourceGenerator.Tests/BasicCompilation/ExpectedGeneration/BasicSystem.Basic(IntComponentA).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class BasicSystem { private QueryDescription Basic_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private World? _Basic_Initialized; private Query? _Basic_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void BasicQuery(World world) { if (!ReferenceEquals(_Basic_Initialized, world)) { _Basic_Query = world.Query(in Basic_QueryDescription); _Basic_Initialized = world; } foreach (ref var chunk in _Basic_Query!) { ref var @intcomponentaFirstElement = ref chunk.GetFirst(); foreach (var entityIndex in chunk) { ref var @_ = ref Unsafe.Add(ref intcomponentaFirstElement, entityIndex); Basic(@_); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/BasicCompilation/ExpectedGeneration/BasicSystem.BasicStatic(IntComponentA).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class BasicSystem { private static QueryDescription BasicStatic_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private static World? _BasicStatic_Initialized; private static Query? _BasicStatic_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void BasicStaticQuery(World world) { if (!ReferenceEquals(_BasicStatic_Initialized, world)) { _BasicStatic_Query = world.Query(in BasicStatic_QueryDescription); _BasicStatic_Initialized = world; } foreach (ref var chunk in _BasicStatic_Query!) { ref var @intcomponentaFirstElement = ref chunk.GetFirst(); foreach (var entityIndex in chunk) { ref var @_ = ref Unsafe.Add(ref intcomponentaFirstElement, entityIndex); BasicStatic(@_); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/DataParamCompilation/DataParamSystem.cs ================================================ using System.Diagnostics.CodeAnalysis; using Arch.Core; using NUnit.Framework; namespace Arch.System.SourceGenerator.Tests; /// /// Tests queries using data parameters. /// [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Allow unused params for query headers")] internal partial class DataParamSystem : BaseTestSystem { public DataParamSystem(World world) : base(world) { } [Query] [All(typeof(IntComponentA))] public static void CountANoParams([Data] ref int count) { count++; } [Query] public static void CountAWithParamsLeft([Data] ref int count, in IntComponentA _) { count++; } [Query] public static void CountAWithParamsRight(in IntComponentA _, [Data] ref int count) { count++; } [Query] public static void CountAWithParamsMiddle(in IntComponentA _, [Data] ref int count, in IntComponentB __) { count++; } [Query] public static void CountATwiceWithParams([Data] ref int count1, in IntComponentA _, [Data] ref int count2, in IntComponentB __) { count1++; count2++; } [Query] [All(typeof(IntComponentA))] public static void CountAWithEntityRight(in Entity e, [Data] ref int count) { count++; } [Query] [All(typeof(IntComponentA))] public static void CountAWithEntityLeft([Data] ref int count, in Entity e) { count++; } [Query] public static void CountAWithEntityAndParamLeft([Data] ref int count, in IntComponentA a, in Entity e) { count++; } [Query] public static void CountAWithEntityAndParamRight(in Entity e, in IntComponentA a, [Data] ref int count) { count++; } // compilation fails from https://github.com/genaray/Arch.Extended/issues/89 //[Query] //public void AssignEntityDataParamRight(in IntComponentA a, [Data] ref Entity outEntity) //{ // outEntity = _sampleEntity; //} // compilation fails from https://github.com/genaray/Arch.Extended/issues/89 //[Query] //public void AssignEntityDataParamLeft([Data] ref Entity outEntity, in IntComponentA a) //{ // outEntity = _sampleEntity; //} [Query] public static void AssignEntityDataParamWithEntityRight(in Entity e, in IntComponentA a, [Data] ref Entity outEntity) { outEntity = e; } // compilation fails from https://github.com/genaray/Arch.Extended/issues/89 //[Query] //public static void AssignEntityDataParamWithEntityLeft([Data] ref Entity outEntity, in Entity e, in IntComponentA a) //{ // outEntity = e; //} // Crashes source generator due to ? in filename; see https://github.com/genaray/Arch.Extended/issues/91 //[Query] //[All(typeof(IntComponentA))] //public static void CountANullable([Data] ref int? count) //{ // count ??= 0; // count++; //} private Entity _sampleEntity; public override void Setup() { _sampleEntity = World.Create(new IntComponentA(), new IntComponentB()); World.Create(new IntComponentA(), new IntComponentB()); } public override void Update(in int t) { int i = 0; CountANoParamsQuery(World, ref i); Assert.That(i, Is.EqualTo(2)); i = 0; CountAWithParamsLeftQuery(World, ref i); Assert.That(i, Is.EqualTo(2)); i = 0; CountAWithParamsRightQuery(World, ref i); Assert.That(i, Is.EqualTo(2)); i = 0; CountAWithParamsMiddleQuery(World, ref i); Assert.That(i, Is.EqualTo(2)); i = 0; int i2 = 0; CountATwiceWithParamsQuery(World, ref i, ref i2); Assert.Multiple(() => { Assert.That(i, Is.EqualTo(2)); Assert.That(i2, Is.EqualTo(2)); }); i = 0; CountAWithEntityRightQuery(World, ref i); Assert.That(i, Is.EqualTo(2)); i = 0; CountAWithEntityLeftQuery(World, ref i); Assert.That(i, Is.EqualTo(2)); i = 0; CountAWithEntityAndParamLeftQuery(World, ref i); Assert.That(i, Is.EqualTo(2)); i = 0; CountAWithEntityAndParamRightQuery(World, ref i); Assert.That(i, Is.EqualTo(2)); Entity outEntity = Entity.Null; //AssignEntityDataParamRightQuery(World, ref outEntity); //Assert.That(outEntity, Is.EqualTo(_sampleEntity)); //outEntity = Entity.Null; //AssignEntityDataParamLeftQuery(World, ref outEntity); //Assert.That(outEntity, Is.EqualTo(_sampleEntity)); outEntity = Entity.Null; AssignEntityDataParamWithEntityRightQuery(World, ref outEntity); Assert.That(outEntity, Is.Not.EqualTo(Entity.Null)); //outEntity = Entity.Null; //AssignEntityDataParamWithEntityLeftQuery(World, ref outEntity); //Assert.That(outEntity, Is.Not.EqualTo(Entity.Null)); //int? i3 = null; //CountANullableQuery(World, ref i3); //Assert.That(i, Is.EqualTo(2)); } } ================================================ FILE: Arch.System.SourceGenerator.Tests/DataParamCompilation/ExpectedGeneration/DataParamSystem.AssignEntityDataParamWithEntityRight(in Entity, in IntComponentA, ref Entity).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class DataParamSystem { private static QueryDescription AssignEntityDataParamWithEntityRight_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private static World? _AssignEntityDataParamWithEntityRight_Initialized; private static Query? _AssignEntityDataParamWithEntityRight_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void AssignEntityDataParamWithEntityRightQuery(World world, ref Arch.Core.Entity @outentity) { if (!ReferenceEquals(_AssignEntityDataParamWithEntityRight_Initialized, world)) { _AssignEntityDataParamWithEntityRight_Query = world.Query(in AssignEntityDataParamWithEntityRight_QueryDescription); _AssignEntityDataParamWithEntityRight_Initialized = world; } foreach (ref var chunk in _AssignEntityDataParamWithEntityRight_Query!) { ref var entityFirstElement = ref chunk.Entity(0); ref var @intcomponentaFirstElement = ref chunk.GetFirst(); foreach (var entityIndex in chunk) { ref readonly var e = ref Unsafe.Add(ref entityFirstElement, entityIndex); ref var @a = ref Unsafe.Add(ref intcomponentaFirstElement, entityIndex); AssignEntityDataParamWithEntityRight(in @e, in @a, ref @outentity); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/DataParamCompilation/ExpectedGeneration/DataParamSystem.CountANoParams(ref int).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class DataParamSystem { private static QueryDescription CountANoParams_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private static World? _CountANoParams_Initialized; private static Query? _CountANoParams_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void CountANoParamsQuery(World world, ref int @count) { if (!ReferenceEquals(_CountANoParams_Initialized, world)) { _CountANoParams_Query = world.Query(in CountANoParams_QueryDescription); _CountANoParams_Initialized = world; } foreach (ref var chunk in _CountANoParams_Query!) { foreach (var entityIndex in chunk) { CountANoParams(ref @count); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/DataParamCompilation/ExpectedGeneration/DataParamSystem.CountATwiceWithParams(ref int, in IntComponentA, ref int, in IntComponentB).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class DataParamSystem { private static QueryDescription CountATwiceWithParams_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA), typeof(global::Arch.System.SourceGenerator.Tests.IntComponentB)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private static World? _CountATwiceWithParams_Initialized; private static Query? _CountATwiceWithParams_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void CountATwiceWithParamsQuery(World world, ref int @count1, ref int @count2) { if (!ReferenceEquals(_CountATwiceWithParams_Initialized, world)) { _CountATwiceWithParams_Query = world.Query(in CountATwiceWithParams_QueryDescription); _CountATwiceWithParams_Initialized = world; } foreach (ref var chunk in _CountATwiceWithParams_Query!) { ref var @intcomponentaFirstElement = ref chunk.GetFirst(); ref var @intcomponentbFirstElement = ref chunk.GetFirst(); foreach (var entityIndex in chunk) { ref var @_ = ref Unsafe.Add(ref intcomponentaFirstElement, entityIndex); ref var @__ = ref Unsafe.Add(ref intcomponentbFirstElement, entityIndex); CountATwiceWithParams(ref @count1, in @_, ref @count2, in @__); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/DataParamCompilation/ExpectedGeneration/DataParamSystem.CountAWithEntityAndParamLeft(ref int, in IntComponentA, in Entity).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class DataParamSystem { private static QueryDescription CountAWithEntityAndParamLeft_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private static World? _CountAWithEntityAndParamLeft_Initialized; private static Query? _CountAWithEntityAndParamLeft_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void CountAWithEntityAndParamLeftQuery(World world, ref int @count) { if (!ReferenceEquals(_CountAWithEntityAndParamLeft_Initialized, world)) { _CountAWithEntityAndParamLeft_Query = world.Query(in CountAWithEntityAndParamLeft_QueryDescription); _CountAWithEntityAndParamLeft_Initialized = world; } foreach (ref var chunk in _CountAWithEntityAndParamLeft_Query!) { ref var entityFirstElement = ref chunk.Entity(0); ref var @intcomponentaFirstElement = ref chunk.GetFirst(); foreach (var entityIndex in chunk) { ref readonly var e = ref Unsafe.Add(ref entityFirstElement, entityIndex); ref var @a = ref Unsafe.Add(ref intcomponentaFirstElement, entityIndex); CountAWithEntityAndParamLeft(ref @count, in @a, in @e); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/DataParamCompilation/ExpectedGeneration/DataParamSystem.CountAWithEntityAndParamRight(in Entity, in IntComponentA, ref int).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class DataParamSystem { private static QueryDescription CountAWithEntityAndParamRight_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private static World? _CountAWithEntityAndParamRight_Initialized; private static Query? _CountAWithEntityAndParamRight_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void CountAWithEntityAndParamRightQuery(World world, ref int @count) { if (!ReferenceEquals(_CountAWithEntityAndParamRight_Initialized, world)) { _CountAWithEntityAndParamRight_Query = world.Query(in CountAWithEntityAndParamRight_QueryDescription); _CountAWithEntityAndParamRight_Initialized = world; } foreach (ref var chunk in _CountAWithEntityAndParamRight_Query!) { ref var entityFirstElement = ref chunk.Entity(0); ref var @intcomponentaFirstElement = ref chunk.GetFirst(); foreach (var entityIndex in chunk) { ref readonly var e = ref Unsafe.Add(ref entityFirstElement, entityIndex); ref var @a = ref Unsafe.Add(ref intcomponentaFirstElement, entityIndex); CountAWithEntityAndParamRight(in @e, in @a, ref @count); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/DataParamCompilation/ExpectedGeneration/DataParamSystem.CountAWithEntityLeft(ref int, in Entity).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class DataParamSystem { private static QueryDescription CountAWithEntityLeft_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private static World? _CountAWithEntityLeft_Initialized; private static Query? _CountAWithEntityLeft_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void CountAWithEntityLeftQuery(World world, ref int @count) { if (!ReferenceEquals(_CountAWithEntityLeft_Initialized, world)) { _CountAWithEntityLeft_Query = world.Query(in CountAWithEntityLeft_QueryDescription); _CountAWithEntityLeft_Initialized = world; } foreach (ref var chunk in _CountAWithEntityLeft_Query!) { ref var entityFirstElement = ref chunk.Entity(0); foreach (var entityIndex in chunk) { ref readonly var e = ref Unsafe.Add(ref entityFirstElement, entityIndex); CountAWithEntityLeft(ref @count, in @e); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/DataParamCompilation/ExpectedGeneration/DataParamSystem.CountAWithEntityRight(in Entity, ref int).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class DataParamSystem { private static QueryDescription CountAWithEntityRight_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private static World? _CountAWithEntityRight_Initialized; private static Query? _CountAWithEntityRight_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void CountAWithEntityRightQuery(World world, ref int @count) { if (!ReferenceEquals(_CountAWithEntityRight_Initialized, world)) { _CountAWithEntityRight_Query = world.Query(in CountAWithEntityRight_QueryDescription); _CountAWithEntityRight_Initialized = world; } foreach (ref var chunk in _CountAWithEntityRight_Query!) { ref var entityFirstElement = ref chunk.Entity(0); foreach (var entityIndex in chunk) { ref readonly var e = ref Unsafe.Add(ref entityFirstElement, entityIndex); CountAWithEntityRight(in @e, ref @count); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/DataParamCompilation/ExpectedGeneration/DataParamSystem.CountAWithParamsLeft(ref int, in IntComponentA).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class DataParamSystem { private static QueryDescription CountAWithParamsLeft_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private static World? _CountAWithParamsLeft_Initialized; private static Query? _CountAWithParamsLeft_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void CountAWithParamsLeftQuery(World world, ref int @count) { if (!ReferenceEquals(_CountAWithParamsLeft_Initialized, world)) { _CountAWithParamsLeft_Query = world.Query(in CountAWithParamsLeft_QueryDescription); _CountAWithParamsLeft_Initialized = world; } foreach (ref var chunk in _CountAWithParamsLeft_Query!) { ref var @intcomponentaFirstElement = ref chunk.GetFirst(); foreach (var entityIndex in chunk) { ref var @_ = ref Unsafe.Add(ref intcomponentaFirstElement, entityIndex); CountAWithParamsLeft(ref @count, in @_); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/DataParamCompilation/ExpectedGeneration/DataParamSystem.CountAWithParamsMiddle(in IntComponentA, ref int, in IntComponentB).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class DataParamSystem { private static QueryDescription CountAWithParamsMiddle_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA), typeof(global::Arch.System.SourceGenerator.Tests.IntComponentB)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private static World? _CountAWithParamsMiddle_Initialized; private static Query? _CountAWithParamsMiddle_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void CountAWithParamsMiddleQuery(World world, ref int @count) { if (!ReferenceEquals(_CountAWithParamsMiddle_Initialized, world)) { _CountAWithParamsMiddle_Query = world.Query(in CountAWithParamsMiddle_QueryDescription); _CountAWithParamsMiddle_Initialized = world; } foreach (ref var chunk in _CountAWithParamsMiddle_Query!) { ref var @intcomponentaFirstElement = ref chunk.GetFirst(); ref var @intcomponentbFirstElement = ref chunk.GetFirst(); foreach (var entityIndex in chunk) { ref var @_ = ref Unsafe.Add(ref intcomponentaFirstElement, entityIndex); ref var @__ = ref Unsafe.Add(ref intcomponentbFirstElement, entityIndex); CountAWithParamsMiddle(in @_, ref @count, in @__); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/DataParamCompilation/ExpectedGeneration/DataParamSystem.CountAWithParamsRight(in IntComponentA, ref int).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class DataParamSystem { private static QueryDescription CountAWithParamsRight_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private static World? _CountAWithParamsRight_Initialized; private static Query? _CountAWithParamsRight_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void CountAWithParamsRightQuery(World world, ref int @count) { if (!ReferenceEquals(_CountAWithParamsRight_Initialized, world)) { _CountAWithParamsRight_Query = world.Query(in CountAWithParamsRight_QueryDescription); _CountAWithParamsRight_Initialized = world; } foreach (ref var chunk in _CountAWithParamsRight_Query!) { ref var @intcomponentaFirstElement = ref chunk.GetFirst(); foreach (var entityIndex in chunk) { ref var @_ = ref Unsafe.Add(ref intcomponentaFirstElement, entityIndex); CountAWithParamsRight(in @_, ref @count); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/GeneratedUpdateCompilation/ExpectedGeneration/GeneratedUpdateSystem.AutoRunA().g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class GeneratedUpdateSystem { private QueryDescription AutoRunA_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private World? _AutoRunA_Initialized; private Query? _AutoRunA_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AutoRunAQuery(World world) { if (!ReferenceEquals(_AutoRunA_Initialized, world)) { _AutoRunA_Query = world.Query(in AutoRunA_QueryDescription); _AutoRunA_Initialized = world; } foreach (ref var chunk in _AutoRunA_Query!) { foreach (var entityIndex in chunk) { AutoRunA(); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/GeneratedUpdateCompilation/ExpectedGeneration/GeneratedUpdateSystem.AutoRunB().g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class GeneratedUpdateSystem { private QueryDescription AutoRunB_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private World? _AutoRunB_Initialized; private Query? _AutoRunB_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AutoRunBQuery(World world) { if (!ReferenceEquals(_AutoRunB_Initialized, world)) { _AutoRunB_Query = world.Query(in AutoRunB_QueryDescription); _AutoRunB_Initialized = world; } foreach (ref var chunk in _AutoRunB_Query!) { foreach (var entityIndex in chunk) { AutoRunB(); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/GeneratedUpdateCompilation/ExpectedGeneration/GeneratedUpdateSystem.g.cs ================================================ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System; namespace Arch.System.SourceGenerator.Tests { partial class GeneratedUpdateSystem { [MethodImpl(MethodImplOptions.AggressiveInlining)] public override void Update(in int data) { AutoRunAQuery(World); AutoRunBQuery(World); } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/GeneratedUpdateCompilation/GeneratedUpdateSystem.cs ================================================ using Arch.Core; using NUnit.Framework; namespace Arch.System.SourceGenerator.Tests; /// /// Tests the auto-generated method. /// internal partial class GeneratedUpdateSystem : BaseTestSystem { public GeneratedUpdateSystem(World world) : base(world) { } private int _number = 0; [Query] [All(typeof(IntComponentA))] public void AutoRunA() { Assert.That(_number, Is.EqualTo(0)); _number++; } [Query] [All(typeof(IntComponentA))] public void AutoRunB() { Assert.That(_number, Is.EqualTo(1)); _number++; } public override void Setup() { World.Create(new IntComponentA()); } public override void Test() { base.Test(); Assert.That(_number, Is.EqualTo(2)); } } ================================================ FILE: Arch.System.SourceGenerator.Tests/ParamQueryCompilation/ExpectedGeneration/ParamQuerySystem.IncrementA(ref IntComponentA).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class ParamQuerySystem { private static QueryDescription IncrementA_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private static World? _IncrementA_Initialized; private static Query? _IncrementA_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IncrementAQuery(World world) { if (!ReferenceEquals(_IncrementA_Initialized, world)) { _IncrementA_Query = world.Query(in IncrementA_QueryDescription); _IncrementA_Initialized = world; } foreach (ref var chunk in _IncrementA_Query!) { ref var @intcomponentaFirstElement = ref chunk.GetFirst(); foreach (var entityIndex in chunk) { ref var @a = ref Unsafe.Add(ref intcomponentaFirstElement, entityIndex); IncrementA(ref @a); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/ParamQueryCompilation/ExpectedGeneration/ParamQuerySystem.IncrementAAndB(ref IntComponentA, ref IntComponentB).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class ParamQuerySystem { private static QueryDescription IncrementAAndB_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA), typeof(global::Arch.System.SourceGenerator.Tests.IntComponentB)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private static World? _IncrementAAndB_Initialized; private static Query? _IncrementAAndB_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IncrementAAndBQuery(World world) { if (!ReferenceEquals(_IncrementAAndB_Initialized, world)) { _IncrementAAndB_Query = world.Query(in IncrementAAndB_QueryDescription); _IncrementAAndB_Initialized = world; } foreach (ref var chunk in _IncrementAAndB_Query!) { ref var @intcomponentaFirstElement = ref chunk.GetFirst(); ref var @intcomponentbFirstElement = ref chunk.GetFirst(); foreach (var entityIndex in chunk) { ref var @a = ref Unsafe.Add(ref intcomponentaFirstElement, entityIndex); ref var @b = ref Unsafe.Add(ref intcomponentbFirstElement, entityIndex); IncrementAAndB(ref @a, ref @b); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/ParamQueryCompilation/ExpectedGeneration/ParamQuerySystem.IncrementANotC(ref IntComponentA).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class ParamQuerySystem { private static QueryDescription IncrementANotC_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA)), any: Signature.Null, none: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentC)), exclusive: Signature.Null); private static World? _IncrementANotC_Initialized; private static Query? _IncrementANotC_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IncrementANotCQuery(World world) { if (!ReferenceEquals(_IncrementANotC_Initialized, world)) { _IncrementANotC_Query = world.Query(in IncrementANotC_QueryDescription); _IncrementANotC_Initialized = world; } foreach (ref var chunk in _IncrementANotC_Query!) { ref var @intcomponentaFirstElement = ref chunk.GetFirst(); foreach (var entityIndex in chunk) { ref var @a = ref Unsafe.Add(ref intcomponentaFirstElement, entityIndex); IncrementANotC(ref @a); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/ParamQueryCompilation/ExpectedGeneration/ParamQuerySystem.IncrementOnlyAWithB(ref IntComponentA, in IntComponentB).g.cs ================================================ #nullable enable using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Arch.Core; using Arch.Core.Extensions; using Arch.Core.Utils; using ArrayExtensions = CommunityToolkit.HighPerformance.ArrayExtensions; using Component = Arch.Core.Component; namespace Arch.System.SourceGenerator.Tests { partial class ParamQuerySystem { private static QueryDescription IncrementOnlyAWithB_QueryDescription = new QueryDescription(all: new Signature(typeof(global::Arch.System.SourceGenerator.Tests.IntComponentA), typeof(global::Arch.System.SourceGenerator.Tests.IntComponentB)), any: Signature.Null, none: Signature.Null, exclusive: Signature.Null); private static World? _IncrementOnlyAWithB_Initialized; private static Query? _IncrementOnlyAWithB_Query; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void IncrementOnlyAWithBQuery(World world) { if (!ReferenceEquals(_IncrementOnlyAWithB_Initialized, world)) { _IncrementOnlyAWithB_Query = world.Query(in IncrementOnlyAWithB_QueryDescription); _IncrementOnlyAWithB_Initialized = world; } foreach (ref var chunk in _IncrementOnlyAWithB_Query!) { ref var @intcomponentaFirstElement = ref chunk.GetFirst(); ref var @intcomponentbFirstElement = ref chunk.GetFirst(); foreach (var entityIndex in chunk) { ref var @a = ref Unsafe.Add(ref intcomponentaFirstElement, entityIndex); ref var @_ = ref Unsafe.Add(ref intcomponentbFirstElement, entityIndex); IncrementOnlyAWithB(ref @a, in @_); } } } } } ================================================ FILE: Arch.System.SourceGenerator.Tests/ParamQueryCompilation/ParamQuerySystem.cs ================================================ using System; using System.Collections.Generic; using Arch.Core; using NUnit.Framework; namespace Arch.System.SourceGenerator.Tests; /// /// Tests queries using parameters. /// internal partial class ParamQuerySystem : BaseTestSystem { public ParamQuerySystem(World world) : base(world) { } [Query] public static void IncrementA(ref IntComponentA a) { a.Value++; } [Query] public static void IncrementOnlyAWithB(ref IntComponentA a, in IntComponentB _) { a.Value++; } [Query] [None(typeof(IntComponentC))] public static void IncrementANotC(ref IntComponentA a) { a.Value++; } [Query] public static void IncrementAAndB(ref IntComponentA a, ref IntComponentB b) { a.Value++; b.Value++; } private (Entity, Dictionary ComponentValues)[] _expectedComponentValues = Array.Empty<(Entity, Dictionary ComponentValues)>(); public override void Setup() { _expectedComponentValues = new[] { (World.Create(new IntComponentA()), new Dictionary { { typeof(IntComponentA), 0 } }), (World.Create(new IntComponentB()), new Dictionary { { typeof(IntComponentB), 0 } }), (World.Create(new IntComponentA(), new IntComponentB()), new Dictionary { { typeof(IntComponentA), 0 }, { typeof(IntComponentB), 0 } }), (World.Create(new IntComponentA(), new IntComponentB(), new IntComponentC()), new Dictionary { { typeof(IntComponentA), 0 }, { typeof(IntComponentB), 0 }, { typeof(IntComponentC), 0 } }) }; } private void TestExpectedValues() { foreach (var (e, values) in _expectedComponentValues) { foreach (var (type, expectedValue) in values) { var component = World.Get(e, type) as IIntComponent; Assert.That(component, Is.Not.Null); Assert.That(component.Value, Is.EqualTo(expectedValue)); } } } public override void Update(in int t) { TestExpectedValues(); IncrementAQuery(World); _expectedComponentValues[0].ComponentValues[typeof(IntComponentA)]++; _expectedComponentValues[2].ComponentValues[typeof(IntComponentA)]++; _expectedComponentValues[3].ComponentValues[typeof(IntComponentA)]++; TestExpectedValues(); IncrementOnlyAWithBQuery(World); _expectedComponentValues[2].ComponentValues[typeof(IntComponentA)]++; _expectedComponentValues[3].ComponentValues[typeof(IntComponentA)]++; TestExpectedValues(); IncrementANotCQuery(World); _expectedComponentValues[0].ComponentValues[typeof(IntComponentA)]++; _expectedComponentValues[2].ComponentValues[typeof(IntComponentA)]++; TestExpectedValues(); IncrementAAndBQuery(World); _expectedComponentValues[2].ComponentValues[typeof(IntComponentA)]++; _expectedComponentValues[2].ComponentValues[typeof(IntComponentB)]++; _expectedComponentValues[3].ComponentValues[typeof(IntComponentA)]++; _expectedComponentValues[3].ComponentValues[typeof(IntComponentB)]++; TestExpectedValues(); } } ================================================ FILE: Arch.System.SourceGenerator.Tests/Shared/BaseTestSystem.cs ================================================ using Arch.Core; namespace Arch.System.SourceGenerator.Tests; /// /// Provides a base class for test systems. This must be included in the compilation to ensure that the system is generated correctly. /// /// The world instance to which the system will be attached. internal abstract class BaseTestSystem : BaseSystem { protected BaseTestSystem(World world) : base(world) { } /// /// Sets up the system for testing. Create entities, components, and any other necessary state. /// public abstract void Setup(); /// /// Runs the test logic for the system. By default, it simply calls the update pipeline. /// public virtual void Test() { BeforeUpdate(0); Update(0); AfterUpdate(0); } } ================================================ FILE: Arch.System.SourceGenerator.Tests/Shared/IntComponents.cs ================================================ namespace Arch.System.SourceGenerator.Tests; #pragma warning disable CS0649 // Allow fields to be unassigned for testing purposes internal interface IIntComponent { int Value { get; } } internal struct IntComponentA : IIntComponent { public int Value; readonly int IIntComponent.Value { get => Value; } } internal struct IntComponentB : IIntComponent { public int Value; readonly int IIntComponent.Value { get => Value; } } internal struct IntComponentC : IIntComponent { public int Value; readonly int IIntComponent.Value { get => Value; } } internal struct IntComponentD : IIntComponent { public int Value; readonly int IIntComponent.Value { get => Value; } } #pragma warning restore CS0649 ================================================ FILE: Arch.System.SourceGenerator.Tests/SystemsTest.cs ================================================ using System; using Arch.Core; using NUnit.Framework; namespace Arch.System.SourceGenerator.Tests; /// /// Runs tests for systems in each compilation. /// Note that the compilation is shared across all tests, so the systems are not isolated. /// As a result, the tests may not be completely independent. However, they are easier to debug. /// Separately, the same tests are run in isolation in the Arch.System.SourceGenerator.Tests project. /// [TestFixture] internal sealed class SystemsTest { /// /// Tests a system by creating it and running its update method. /// /// /// The type of the system to test, which must inherit from BaseSystem and must have a constructor that takes a World parameter. /// private static void TestSystem() where T : BaseTestSystem { using var world = World.Create(); var system = Activator.CreateInstance(typeof(T), world) as T; Assert.That(system, Is.Not.Null, $"System instance {typeof(T).Name} should not be null. Ensure it has a constructor that takes a single World param."); system.Setup(); system.Test(); } [Test] public void BasicCompilation() { TestSystem(); } [Test] public void AttributeQueryCompilation() { TestSystem(); } [Test] public void ParamQueryCompilation() { TestSystem(); } [Test] public void DataParamCompilation() { TestSystem(); } [Test] public void GeneratedUpdateCompilation() { TestSystem(); } } ================================================ FILE: Directory.Build.targets ================================================ Release false en ================================================ FILE: LICENSE.MD ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [2022] [genaray / Lars Matthäus] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # Arch.Extended [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg?style=for-the-badge)](https://GitHub.com/Naereen/StrapDown.js/graphs/commit-activity) [![Nuget](https://img.shields.io/nuget/v/Arch?style=for-the-badge)](https://www.nuget.org/packages/Arch.System/) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0) ![C#](https://img.shields.io/badge/c%23-%23239120.svg?style=for-the-badge&logo=c-sharp&logoColor=white) Extensions for [Arch](https://github.com/genaray/Arch) with some useful features like Systems, Source Generator and Utils. - 🛠️ **_Productive_** > Adds some useful tools and features to the main repository! - ☕️ **_SIMPLE_** > Works easily, reliably and understandably! - 💪 _**MAINTAINED**_ > It's actively being worked on, maintained, and supported! - 🚢 _**SUPPORT**_ > Supports .NetStandard 2.1, .Net Core 6 and 7 and therefore you may use it with Unity or Godot! Download the packages and get started today! ```console dotnet add package Arch.System --version 1.1.0 dotnet add package Arch.System.SourceGenerator --version 2.1.0 dotnet add package Arch.EventBus --version 1.0.2 dotnet add package Arch.LowLevel --version 1.1.5 dotnet add package Arch.Relationships --version 1.0.0 dotnet add package Arch.Persistence --version 2.0.0 dotnet add package Arch.AOT.SourceGenerator --version 1.0.1 ``` # Features & Tools - ⚙️ **_[Systems](https://github.com/genaray/Arch.Extended/wiki/Systems-API)_** > By means of systems, it is now easy to organize, reuse and arrange queries. - ✍️ **_[Source Generator](https://github.com/genaray/Arch.Extended/wiki/Source-Generator)_** > Declarative syntax using attributes and source generator, let your queries write themselves! - ✉️ **_[EventBus](https://github.com/genaray/Arch.Extended/wiki/EventBus)_** > A source generated EventBus, send Events with high-performance! - 👾 **_[LowLevel](https://github.com/genaray/Arch.Extended/wiki/Lowlevel-&-Resource-Management)_** > Low-level utils and data structures to get rid of GC pressure! - 💑 **_[Relationships](https://github.com/genaray/Arch.Extended/wiki/Relationships)_** > Adds simple relationships between entities to arch! - 💾 **_[Persistence](https://github.com/genaray/Arch.Extended/wiki/Persistence)_** > JSON and Binary (de)serialization to persist your Worlds! - ⌛ **_[AOT Source Generator](https://github.com/genaray/Arch.Extended/wiki/AOT-Source-Generator)_** > Helps with AOT compatibility and reduces boilerplate code! > Check the links and the [Wiki](https://github.com/genaray/Arch.Extended/wiki)! # Full code sample With this package you are able to write and group queries and systems for Arch automatically. And all this with the best possible performance. The tools can be used independently of each other. ```cs // Components ( ignore the formatting, this saves space ) public struct Position{ float X, Y }; public struct Velocity{ float Dx, Dy }; // BaseSystem provides several useful methods for interacting and structuring systems public class MovementSystem : BaseSystem { public MovementSystem(World world) : base(world) {} // Generates a query and calls that one automatically on BaseSystem.Update [Query] public void Move([Data] in float time, ref Position pos, ref Velocity vel) { pos.X += time * vel.X; pos.Y += time * vel.Y; } // Generates and filters a query and calls that one automatically on BaseSystem.Update in order [Query] [All, Any, None] // Attributes also accept non generics :) public void ResetVelocity(ref Velocity vel) { vel = new Velocity{ X = 0, Y = 0 }; } } public class Game { public static void Main(string[] args) { var deltaTime = 0.05f; // This is mostly given by engines, frameworks // Create a world and a group of systems which will be controlled var world = World.Create(); var _systems = new Group( "Systems", new MovementSystem(world), // Run in order new MyOtherSystem(...), ... ); _systems.Initialize(); // Inits all registered systems _systems.BeforeUpdate(in deltaTime); // Calls .BeforeUpdate on all systems ( can be overriden ) _systems.Update(in deltaTime); // Calls .Update on all systems ( can be overriden ) _systems.AfterUpdate(in deltaTime); // Calls .AfterUpdate on all System ( can be overriden ) _systems.Dispose(); // Calls .Dispose on all systems ( can be overriden ) } } ``` ================================================ FILE: scripts/UnityPublish.sh ================================================ #!/bin/bash # Publishes Unity release to dist/Assemblies using only netstandard2.0 and netstandard2.1 ######################################################################################### dotnet restore assemblyDir="`pwd`/dist/Assemblies" rm -rf "${assemblyDir}" mkdir -p "${assemblyDir}" dotnet msbuild /t:Unity \ -p:PublishDir="${assemblyDir}" \ -p:TargetFramework=netstandard2.1 \ -p:TargetFrameworks=netstandard2.1 \ -p:TargetFrameworkVersion=v2.1