GithubHelp home page GithubHelp logo

genaray / arch.extended Goto Github PK

View Code? Open in Web Editor NEW
148.0 148.0 34.0 302 KB

Extensions for Arch with some useful features like Systems, Source Generator and Utils.

License: Apache License 2.0

C# 99.87% Shell 0.13%
csharp dotnet dotnet-core ecs entity-component-system entity-framework fast framework game game-development gamedev godot monogame monogame-framework unity utilities

arch.extended's Introduction

Hi, I'm genaray ๐Ÿ’ป

I am a 23-year-old computer science student and technology enthusiast living in NRW, Germany.

I recently finished my bachelor thesis at a small local IT company, and I am looking forward to doing my master as well.

I mostly use C# and Java on a daily basis. However, I love learning new languages and technologies. In my spare time, I mostly play with Unity and develop small games.

Hobbies

  • Videogames ๐ŸŽฎ
  • Gym ๐Ÿ‹๏ธ
  • Photography ๐Ÿ“ท
  • Learning new languages ๐Ÿ‡น๐Ÿ‡ท
  • Game-Development ๐Ÿค–

arch.extended's People

Contributors

andreakarasho avatar difficulty-in-naming avatar drsmugleaf avatar epicguru avatar genaray avatar hertzole avatar iainmckay avatar jzapdot avatar lesinski avatar martindevans avatar reeseschultz avatar richdog avatar royconskylands avatar stanoddly avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

arch.extended's Issues

Query source generation compile error

Issue
I'm getting a compile "error" with the query source generation. It strangely doesn't prevent me from building and running the project, but it does prevent hot reloading. (See comment below)
image

I'm using Arch.System.SourceGenerator v1.1.2:

<PackageReference Include="Arch.System.SourceGenerator" Version="1.1.2" />

Potential cause
This appears to be because the generated code does not include the correct using statement nor does it use the global:: keyword to fully specify which type to use:
image
image

Workaround
Fully qualifying the name in the source code fixes the error:
image
image

This implies that the source generator is directly using the type name from the source code.

Potential fix
I'm interested in taking a look at the source generator code and changing it so that the global:: qualified type name is always used. This should prevent any similar issues in the future and prevent type name collisions.

However, I'm not familiar with C#'s source generation feature and what it's limitations are.

Getting Started tips?

Hey, awesome work! I'm slowly migrating to monogame, I have little experience with modern c#.

I can successfully setup a Monogame project and run it, and also integrate with base Arch. However I'm not being able to get Arch.Extended's EventBus to play well. I just saw #9 but OP didn't mention how exactly did he solve it. Can the EventBus code be generated by running a command, or by specifying it somehow in the csproj? Thank you in advance. I believe this is also a good opportunity to add brief yet effective documentation on how to setup a project with these tools.

System source generation not qualifying types properly.

When there is a type that matches a namespace the source generation doesn't properly write the type out.

Here's an example.

using Arch.Core;
using Arch.System;
using Microsoft.Xna.Framework;

namespace MyGame;

public static class Program
{
    static void Main(string[] args)
    {
        using var game = new MyGame();
        game.Run();
    }
}

public class MyGame : Game { }
public struct MyComponent { }
public partial class MySystem : BaseSystem<World, GameTime>
{
    public MySystem(World world) : base(world) { }
    [Query]
    public void Do(ref MyComponent myComponent) { }
}

Here's the generated code.

namespace MyGame
{
    public partial class MySystem
    {
        private QueryDescription Do_QueryDescription = new QueryDescription
        {
            All = new ComponentType[]
            {
                typeof(MyGame.MyComponent)
            },
            Any = Array.Empty<ComponentType>(),
            None = Array.Empty<ComponentType>(),
            Exclusive = Array.Empty<ComponentType>()
        };
        private bool _Do_Initialized;
        private Query _Do_Query;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void DoQuery(World world)
        {
            if (!_Do_Initialized)
            {
                _Do_Query = world.Query(in Do_QueryDescription);
                _Do_Initialized = true;
            }

            foreach (ref var chunk in _Do_Query.GetChunkIterator())
            {
                var chunkSize = chunk.Size;
                ref var mycomponentFirstElement = ref chunk.GetFirst<MyGame.MyComponent>();
                foreach (var entityIndex in chunk)
                {
                    ref var mycomponent = ref Unsafe.Add(ref mycomponentFirstElement, entityIndex);
                    Do(ref mycomponent);
                }
            }
        }
    }
}

The issue here is for example typeof(MyGame.MyComponent), where the compiler is intepreting MyGame as the type not the namespace because this code is already in the MyGame namespace.

Instead that line should be typeof(global::MyGame.MyComponent)

I'm happy to submit a PR to fix this.

Static Generated Queries

My game creates and destroys multiple world (running lots of mini-games in sequence). I've found that for the first World everything works fine, but for subsequent games any static queries will never be called.

Stepping through the generated source it seems as though the query is simply not finding any relevant entities.

Just to be clear, this is with queries generated like this:

[Query]
[None<PendingDestroy>]
private static void MySpecialQuery(in Thing whatever)
{
    // Do stuff
}

Note that it is a static query. Changing this to an instance method seems to fix the problem.

Add support for source generated parallel queries

Unless I am missing something, there is currently no way to source generate a query that runs in parallel. This would be a nice enhancement, as otherwise writing parallel queries requires using the more verbose syntax.

Source Generation Fails For Generic System

Given a system like this:

public partial class CodeGenBugDemo<T>
    : BaseSystem<World, float>
{
    public CodeGenBugDemo(World world)
        : base(world)
    {
    }

    public override void Update(in float clock)
    {
        base.Update(in clock);

        AQueryQuery(World, clock);
    }

    [Query]
    public static void AQuery([Data] float clock, T component)
    {
    }
}

The source generator will generate this:

partial class CodeGenBugDemo
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public override void Update(in float data)
    {
        AQueryQuery(World, Data);
    }
}

which is missing the generic parameters and causes a build error.

There is value on using relationships instead of defining relationship components?

I'm asking this on a performance/usability standpoint, for genuine curiosity and interest, as I'm kinda new to ECS (and I'm loving the flexibility of it).

Does it make sense to use something like parent.AddRelationship(childOne)
instead of something like: child.Add(new ParentOf(parent.Reference())

I'm using a 'custom' thing to drop components, a mix of a 'Destroy' component and a 'DropFrom' component (for entities that can be 'dropped' from a parent entity when it is destroyed, the drop from system runs before the destroy system so we can 'catch' when a parent reference will be destroyed before it is) and I'm curious if using something like relationships would be more suitable and/or performant.

How about modeling "relationship" as an entity instead of as a component?

  1. Whenever a new type of relationship component is added to an entity, the entity's archetype changes. This is also mentioned in the wiki. If there are multiple types of relationships, say three, then there may be as many as eight different archetypes generated during the world updating. On the other hand, being added to a relationship should not be considered a 'structural change', literally.
  2. A relationship can be complex, and can be accessed from more than one system. If a relationship is an entity, it could contain as many components as it needs.
  3. A relationship may be related to more than two entities. The current modeling approach allows each relationship to be related to only two entities.
  4. Instead of firstly iterate through entities and then iterate through other entities that related to the former, I think it's more intuitive to directly iterate through all relationships and then process all entities in each relationship.

Relationship addition breaks if 30 or 31 different component types have been added to the World first

This is a really finicky one, but I finally got a usable repro. As far as I can tell, the number of entities is irrelevant, and the number of components is irrelevant; it's entirely based on 30-31 different components first existing in the World.

With Sourcegen, those components can be instead simply located in a [Query], either in the attribute or as a parameter to the query method. Then, the construction of the enclosing class triggers the component types to be added to the world.

One aspect to perhaps also test is to see if it breaks if the number of components added is 62 or 63. But I didn't try that.

Here is a test that demonstrates the issue:
RelationshipManyComponents.zip

Two important notes:

  • You need to right click and run each test case individually, like so:
    image
    I don't know why the behavior changes when all the tests are run (they either all succeed or all fail depending on if you're debugging), but that issue should be fixed before this test is added to the suite. I didn't have any luck splitting it up into other methods, either, even with Nunit's [NonParallelizable] on each method.

  • This is ONLY present in Arch 1.2.6.8-alpha (not 1.2.6.7); Arch.Extended isn't updated to require that package version yet. If you're running the test, you need to change the version in Arch.Relationships.csproj to fetch the correct version from Nuget.

Any ideas? I assume it has something to do with an allocation failure on some 33rd bit somewhere...?

UnsafeList.Add does not resize the list automatically, unlike Insert

There is no call to EnsureCapacity in Add but there is one in Insert. If this is intended it should probably be added to the xmldoc of the method.

This code will crash the process when i = 11:

var list = new UnsafeList<int>(10);
for (int i = 0; i < 100; i++)
{
    list.Add(i);
}

This will not:

var list = new UnsafeList<int>(10);
for (int i = 0; i < 100; i++)
{
    list.Insert(i, i);
}

Cannot create entities after deserializing the world

I am trying to use Persistence in order to send the world state over to another client and continue simulation there. However, I am getting this error when trying to create entities after deserialization.

ArrayTypeMismatchException: Attempted to access an element as a type incompatible with the array.
System.Array.Copy (System.Array sourceArray, System.Int32 sourceIndex, System.Array destinationArray, System.Int32 destinationIndex, System.Int32 length) (at <59a524f39437477884870e130f2eaa4f>:0)
System.Array.Resize[T] (T[]& array, System.Int32 newSize) (at <59a524f39437477884870e130f2eaa4f>:0)
Arch.Core.JaggedArray`1[T].EnsureCapacity (System.Int32 capacity) (at <f04907b0740a41dcb23e49fd4f7f024b>:0)
Arch.Core.EntityInfoStorage.EnsureCapacity (System.Int32 capacity) (at <f04907b0740a41dcb23e49fd4f7f024b>:0)
Arch.Core.World.GetOrCreate (System.Span`1[T] types) (at <f04907b0740a41dcb23e49fd4f7f024b>:0)
Arch.Core.World.Create[T0] (T0& t0Component) (at <f04907b0740a41dcb23e49fd4f7f024b>:0)`

This is enough to reproduce. Just create the world, serialize it and deserialize it, and when trying to create, it errors.

public struct Test
{
    public int value;
}        

ArchBinarySerializer serializer = new ArchBinarySerializer();
World = World.Create();
var bytes = serializer.Serialize(World);
World = serializer.Deserialize(bytes);
World.Create(new Test { value = 2 });

I also used ComponentRegistry but to no avail.
One interesting behaviour, if I add the entity before serializing, the error is fired from the .Deserialize line instead. Here is that stacktrace, if it helps.

ArrayTypeMismatchException: Attempted to access an element as a type incompatible with the array.
System.Array.Copy (System.Array sourceArray, System.Int32 sourceIndex, System.Array destinationArray, System.Int32 destinationIndex, System.Int32 length) (at <59a524f39437477884870e130f2eaa4f>:0)
System.Array.Resize[T] (T[]& array, System.Int32 newSize) (at <59a524f39437477884870e130f2eaa4f>:0)
Arch.Core.JaggedArray`1[T].EnsureCapacity (System.Int32 capacity) (at <f04907b0740a41dcb23e49fd4f7f024b>:0)
Arch.Core.EntityInfoStorage.EnsureCapacity (System.Int32 capacity) (at <f04907b0740a41dcb23e49fd4f7f024b>:0)
Arch.Core.Extensions.Dangerous.DangerousWorldExtensions.EnsureCapacity (Arch.Core.World world, System.Int32 capacity) (at <f04907b0740a41dcb23e49fd4f7f024b>:0)
Arch.Persistence.WorldFormatter.Deserialize (MessagePack.MessagePackReader& reader, MessagePack.MessagePackSerializerOptions options) (at <af3ea2856d7b42a2bd70a4676dc1a254>:0)
MessagePack.MessagePackSerializer.Deserialize[T] (MessagePack.MessagePackReader& reader, MessagePack.MessagePackSerializerOptions options) (at <e0125fc189a34138b45ca77ebe60d374>:0)
Rethrow as MessagePackSerializationException: Failed to deserialize Arch.Core.World value.
MessagePack.MessagePackSerializer.Deserialize[T] (MessagePack.MessagePackReader& reader, MessagePack.MessagePackSerializerOptions options) (at <e0125fc189a34138b45ca77ebe60d374>:0)
MessagePack.MessagePackSerializer.Deserialize[T] (System.ReadOnlyMemory`1[T] buffer, MessagePack.MessagePackSerializerOptions options, System.Threading.CancellationToken cancellationToken) (at <e0125fc189a34138b45ca77ebe60d374>:0)
Arch.Persistence.ArchBinarySerializer.Deserialize (System.Byte[] world) (at <af3ea2856d7b42a2bd70a4676dc1a254>:0)

I got the Persistence package from https://www.nuget.org/packages/Arch.Persistence, is it maybe outdated? I see there's tag 1.0.8 on github, but I'm using Unity, and this is how I got it to compile.

The source generator does not respect nullability being enabled/disabled.

As the title states, the source generator always assumes that the Nullable Reference Types feature is enabled and will generate the following line:
private World? _xx_Initialized;
image

If the user does not have NRT enabled, this causes a warning, and if (like myself) you have warnings as errors enabled, it will make the project unable to compile. Currently the only workaround is to either disable the warning CS8669 (for the entire project) or enable NRT.

source generator for unity like memorypack?

hi,first I need to say arch is very very performance and convenient to use!
memorypack from cysharp support source generator for unity,so may arch also support it. and it is also good for sterilization!

Attributes for generating query delegetes?

Is it possible we can get a way to generate [n] number of generic delegates on our side?

For example a game engine might want to wrap 3rd party libraries and hand writing [n] number of signatures to simply wrap something is really awful.

This is kinda why C# needs variadic generics, and typedef but sadly it does not.

Test Failure?

At the moment the JsonWorldSerialization test seems to fail with this error:

  • System.InvalidOperationException : Register must call on startup(before use GetFormatter).

I haven't investigated this much, but it looks like it's coming from inside Utf8Json.

Is this something I'm doing wrong, or is this feature broken at the moment?

Support for entity references in command buffers

image

The above code shows the desired functionality, which is to be able to have an entity created via command buffer be set as a reference on another component on another entity via the same command buffer, before that entity has been created properly and has an ID assigned to it.

This is supported in Unity's ECS EntityCommandBuffer, but via some forbidden magic.
If anyone is willing to do something similar to support this functionality, here's some of what I gathered from going through the Unity code. Note that I'm referencing code in Entities 1.0.10.

I'm going to start with the managed version of this code, since they have different implementations for managed vs unmanaged components.

image
In line 4141 of EntityCommandBuffer of ECS is the AddManagedComponentData method (which is part of the PlaybackProcessor), which gets called when playing back the command buffer.
This method uses the TypeManager (a Unity Entities tool that keeps track of all the types) to check if the type has any references to entities.

image
If it does, it calls another method "FixUpComponent" (line 4499) to somehow modify the values of that type that reference the entity, using some otherworldly sorcery.

image

Unmanaged fixup takes a different approach, somehow keeping track of the component byte size (also using the TypeManager), an using that to find the components to "fix up" and put the correct entity in.

This does all feel like a very complicated solution to a very simple problem, but it would be a nice feature to have if there's a simpler solution.

Why and what are the generic passed into BaseSystem for?

BaseSystem takes two generic parameters which seem to be reduntant

In the example W is a World but a why do we need to pass it if the library already knows what a World is?

public WindowSystem  : BaseSystem
{
    private QueryDescription queryDescription = new QueryDescription().WithAll<Rect2D<uint>>();
    public WindowSystem(World world) : base(world)
    {
        
    }
}

also T is a float in the example which looks like it corresponds to the float X and Y in Velocity and Position but what if I have a component with multiple different types?

struct MyComponent
{
  float X;
  float Y;
  bool Success;
}

Deserializing world only works within the same process as the serialization

I'm trying to serialize and deserialize a World, using Arch.Persistence. If I do it in the same process, it works fine. But if I send that byte array over to a different client and try to deserialize there, all hell breaks loose. In order to not use some pipe or networking I just write the byte array to a file and read it again after restarting the app, as a simple repro scenario.

In my FlaxEngine project, this causes the client to crash with a
the thread tried to read from or write to a virtual address for which it does not have the appropriate access
error inside the minidump.

In the repro C# console Net7 project I get

MessagePack.MessagePackSerializationException: Failed to deserialize Arch.Core.World value.
 ---> System.ArgumentNullException: Value cannot be null. (Parameter 'elementType')
   at System.Array.CreateInstance(Type elementType, Int32 length)
   at Arch.Core.Utils.ArrayRegistry.GetArray(ComponentType type, Int32 capacity)
   at Arch.Core.Chunk..ctor(Int32 capacity, Int32[] componentIdToArrayIndex, ComponentType[] types)
   at Arch.Core.Archetype..ctor(ComponentType[] types)
   at Arch.Core.Extensions.Dangerous.DangerousArchetypeExtensions.CreateArchetype(ComponentType[] types)
   at Arch.Persistence.ArchetypeFormatter.Deserialize(MessagePackReader& reader, MessagePackSerializerOptions options)
   at Arch.Persistence.WorldFormatter.Deserialize(MessagePackReader& reader, MessagePackSerializerOptions options)
   at MessagePack.MessagePackSerializer.Deserialize[T](MessagePackReader& reader, MessagePackSerializerOptions options)
   --- End of inner exception stack trace ---
   at MessagePack.MessagePackSerializer.Deserialize[T](MessagePackReader& reader, MessagePackSerializerOptions options)
   at MessagePack.MessagePackSerializer.Deserialize[T](ReadOnlyMemory`1 buffer, MessagePackSerializerOptions options, CancellationToken cancellationToken)
   at Arch.Persistence.ArchBinarySerializer.Deserialize(Byte[] world)
   at Program.Main(String[] args) in C:\Users\hdgam\source\repos\ConsoleApp5\ConsoleApp5\Program.cs:line 31

Repro steps:

  1. Make new console project
  2. Add Arch 1.2.7 & Arch.Persistence 1.0.3 via NuGet
  3. Use this code:
using Arch.Core;
using Arch.Persistence;

internal class Program
{
    public struct Test
    {
        public int value;
    }
    static void Main(string[] args)
    {
        var input = Console.ReadLine();
        var path = AppDomain.CurrentDomain.BaseDirectory + ".data";
        ArchBinarySerializer serializer = new ArchBinarySerializer();

        if (input.Equals("Server"))
        {
            var world = World.Create(); // new world
            world.Create(new Test { value = 1 }); // only 1 basic entity
            var bytes = serializer.Serialize(world);
            Console.WriteLine(bytes.Select(b => (long)b).Sum()); // this is just to check we read the same array on the client
            File.WriteAllBytes(path, bytes);
        }
        else if (input.Equals("Client"))
        {
            try
            {
                var bytes = File.ReadAllBytes(path);
                Console.WriteLine(bytes.Select(b => (long)b).Sum()); // check we have the same array
                var world = serializer.Deserialize(bytes); // error
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        Console.ReadKey();
    }
}
  1. Run once, writing "Server"
  2. Run again, writing "Client", observe that the number printed is the same, and it should error

System.SourceGenerator: How the data param is passed to queries in the generated Update method is unexpected

Being that Update takes a data param, I would expect that that param is passed directly to the generated queries.
Instead, I see that the Data property is passed instead.

For example:

partial class MovementSystem
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public override void Update(in float data)
    {
        MoveQuery(World, in Data);
    }
}

What I expect:

partial class MovementSystem
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public override void Update(in float data)
    {
        MoveQuery(World, in data);
    }
}

I'm not sure why you would want to pass data to update, but instead use the data set by BeforeUpdate.
Honestly I think Data property shouldn't exist in BaseSystem. If someone wants to derive their own base that has a Data property they can easily do so, but I think it's just unnecessary to complicate the workings of BaseSystem.

If the Data property is to be kept, I would at least expect Update and AfterUpdate to also set it.

Regardless, it doesn't make sense for Data to be passed for [Data] because if the query wanted to use Data it could just access it through this.

`[Data]` parameter always have the default value on generated queries

Not sure if this is the expected behavior, but the following code will not have the same output, as the second one will ignore the data parameter.

I'm calling MovementSystem.Update(deltaTime) and this works fine:

public partial class MovementSystem : BaseSystem<World, float> {

    public MovementSystem(World world) : base(world) {
    }

    public override void Update(in float t) {
        UpdateSystemQuery(World, t);
    }

    [Query]
    [All(typeof(VelocityComponent), typeof(TransformComponent))]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void UpdateSystem([Data] in float dt, ref VelocityComponent velocity, ref TransformComponent transform) {
        transform.Position += velocity.Velocity * dt;
    }
}

However, if I omit the Update function (so that it's automatically generated) UpdateSystem will always have a zero dt:

public partial class MovementSystem : BaseSystem<World, float> {

    public MovementSystem(World world) : base(world) {
    }

    [Query]
    [All(typeof(VelocityComponent), typeof(TransformComponent))]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void UpdateSystem([Data] in float dt, ref VelocityComponent velocity, ref TransformComponent transform) {
        transform.Position += velocity.Velocity * dt; // dt is always zero?
    }
}

Want to integrate this source generator into your project as a 'experimental feature'?

A week ago, I developed this source generator in some hours for a experiment I thought was interesting, it uses the new .NET feature called 'Interceptors' to intercept the 'prototype-style queries' and turn them into the more 'boilerplate and fast' version as a method.

As an example, it will take this:

public class TestSystem : ISystem
{
    public static string SomeExternalSourceSearch(string searchFor)
        => searchFor + " found";

    [Optimize]
    public void Update(SystemProps props)
    {
        var (world, _) = props;

        var query = new QueryDescription()
            .WithAll<TestComponent, OtherComponent>();

        world.Query(query, (Entity entity, ref TestComponent test, ref OtherComponent other) =>
        {
            string searched = Optimizer.OutOfScope(() => TestSystem.SomeExternalSourceSearch("test")); // For any reason

            test.Value += 1;
        });
    }
    public void Draw(SystemProps props, SpriteBatch spriteBatch) { }
}
public struct TestComponent
{
    public float Value;
}
public struct OtherComponent;

(this ISystem is a part of a game I'm making with arch, it is a custom thing for my needs; the 'Optimize' attribute can be applied on any kind of function with as many queries as you would like)
And will turn into this:

public static class ProjectName_ComponentsUpdate_0_Interceptor
{
    [InterceptsLocation(@"C:\(...hiding my directories...)\Components\TestSystem.cs", 26, 15)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void Intercept(this World world, in QueryDescription description, Arch.Core.ForEachWithEntity<ProjectName.Components.TestComponent, ProjectName.Components.OtherComponent> _)
    {
{
var searched=TestSystem.SomeExternalSourceSearch("test");
    foreach(var chunk in world.Query(description).GetChunkIterator())
    {
        var arr = chunk.GetFirst<ProjectName.Components.TestComponent, ProjectName.Components.OtherComponent>();
        foreach(var index in chunk)
        {
            ref var entity = ref chunk.Entity(index);
            ref var test = ref Unsafe.Add(ref arr.t0, index);
ref var other = ref Unsafe.Add(ref arr.t1, index);
        {

            test.Value += 1;
        }        }
    }
}
    }
}

(it's not being generated formatted currently, but it works and kinda the generated code is the same for all instances, so it should not produce any bugs)

So basically, what this does is take the closure AST and turn it into an actual boilerplate-like query, using the closure body as a simple C# block in the generated output. It also replaces any Optimzer.OutOfScope call with an actual out of scope variable, inlining the closure expression as the variable initializer (and the actual variable declaration as the, well, variable declaration).

I made some benchmarks, and it appear to have similar (or better) performance than the source generated queries, and in relation to the closure prototype non-optimized version they have marginal gains, and substantial gains when accessing objects out of the closure (I suppose because of the inlining).

These where some of my tests for it:

BenchmarkDotNet v0.13.12, Windows 11 (10.0.22621.3007/22H2/2022Update/SunValley2)
Intel Core i5-10600KF CPU 4.10GHz, 1 CPU, 12 logical and 6 physical cores
.NET SDK 8.0.101
  [Host]     : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2
  DefaultJob : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2


Method Mean Error StdDev Code Size
DealDamageQuery 12.141 ms 0.0529 ms 0.0494 ms 7,815 B
DealDamageQueryOptimized 9.261 ms 0.0355 ms 0.0296 ms 9,577 B
DealDamageManuallyOptimized 9.391 ms 0.1259 ms 0.1178 ms 9,180 B
DealDamageManuallyOptimizedSimpler 10.483 ms 0.0896 ms 0.0794 ms 8,957 B
DealDamageQueryFromArchExtended 10.946 ms 0.0801 ms 0.0749 ms 9,415 B

The 'Query' version is the normal prototype one, the optimized is the prototype one with code generation and my interceptors, the manually optimized one is the version I wrote manually in place, the simpler is not using unsafe and instead querying manually from the entity each iteration, and the last is your version. I think the benchmarks can be enhanced, but it sounds promising from my tests.

I still want to do some improvements here and there, and cover some edge cases (as well as adding compiler errors for non static queries and automatically infering the static method calls to the source type without needing to specify it manually, and add some testing as well), but I thought this would interest you and I'm curious if you would want to export this project if I publish it or is willing to put it inside the extended repository.

Persistence bug. Serializing and deserializing 2 times in a row results in exception

Serializing and deserializing to json 2 times in a row (without even doing any changes to the world) results in different JSON texts, and throws exception during second deserialization.

Arch version 1.2.7
Arch.Persistence version 1.0.3

Full code to reproduce:

using Arch.Core;
using Arch.Persistence;

var _world = World.Create();
var a = _world.Create(
    new Component { exp = 1, name = "c_1" }
);
var archSerializer = new ArchJsonSerializer();
var worldJson = archSerializer.ToJson(_world);
_world = archSerializer.FromJson(worldJson);
worldJson = archSerializer.ToJson(_world);
_world = archSerializer.FromJson(worldJson);


public struct Component
{
    public long exp;
    public string name;
}

Error message:

System.IndexOutOfRangeException
  HResult=0x80131508
  Message=Index was outside the bounds of the array.
  Source=Arch.Persistence
  StackTrace:
   at Arch.Persistence.JaggedArrayFormatter`1.Deserialize(JsonReader& reader, IJsonFormatterResolver formatterResolver)
   at Arch.Persistence.WorldFormatter.Deserialize(JsonReader& reader, IJsonFormatterResolver formatterResolver)
   at Arch.Persistence.ArchJsonSerializer.FromJson(String jsonWorld)
   at Program.<Main>$(String[] args) in C:\[...]\Program.cs:line 12

Warning: EventBus conflicts with imported type EventBus

I have a fresh Monogame project with Arch and all Arch.Extended plugins:

<ItemGroup>
    <PackageReference Include="Arch" Version="1.2.7" />
    <PackageReference Include="Arch.AOT.SourceGenerator" Version="1.0.0" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
    <PackageReference Include="Arch.EventBus" Version="1.0.2" />
    <PackageReference Include="Arch.LowLevel" Version="1.1.0" />
    <PackageReference Include="Arch.Persistence" Version="1.0.3" />
    <PackageReference Include="Arch.Relationships" Version="1.0.1" />
    <PackageReference Include="Arch.System" Version="1.0.2" />
    <PackageReference Include="Arch.System.SourceGenerator" Version="1.1.2" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
    <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" />
    <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.1.303" />
  </ItemGroup>

I try to dispatch an event exactly like in the sample project:

var @event = (Main.World, key);
EventBus.Send(ref @event);

I get a warning on compilation:

warning CS0436: The type 'EventBus' in 'Arch.EventBus\Arch.Bus.QueryGenerator\EventBus.g.cs' conflicts with the imported type 'EventBus' in 'Arch.EventBus, Version=1.0.2.0, Culture=neutral, PublicKeyToken=null'. Using the type defined in 'Arch.EventBus\Arch.Bus.QueryGenerator\EventBus.g.cs'.

It compiles and works fine but I like it when there is no warning. I've tinkered with the .csproj without success.

Did I fail to configure something ?

World grows after deserialization

Every time I save the same world after loading, the world grows. This happens even if I save an empty world. This also happens with both Binary and JSON serialization.

I've attached two copies of the world JSON, once from the first save, and again after loading and saving again. note that it keeps growing every time I save and load, not just the first time.

world-empty-1.json
world-empty-2.json

I tracked it to this point:
image
image

I dug a bit deeper after this and it seems to do with the way the capacity is calculated, and serialized. I attempted serializing/deserializing the capacity of just a single bucket instead of the total capacity, which seemed to partially fix the issue, but it would still grow in some cases so I moved on to other things.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.