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