Arch-ECS
💬 Join the discord!☕ Buy us a coffee!
  • 🌄Why Arch?
  • 📖Documentation
    • Concepts
    • World
    • Entity
    • Query
    • Archetypes & Chunks
    • Optimizations
      • Query-Techniques
      • Pass on data
      • Batch and Bulk
      • PURE_ECS
      • Multithreading
      • EntityData
    • Utilities
      • Component Registration
      • Non-generic API
      • CommandBuffer
      • Events
      • Dangerous Extensions
  • 🧩Extensions
    • Arch.Extended
      • Arch.System
      • Arch.System.SourceGenerator
      • Arch.EventBus
      • Arch.AOT.SourceGenerator
      • Arch.LowLevel
      • Arch.Persistence
      • Arch.Relationships
  • 💡Examples & Guidelines
    • Arch.Samples
    • Entities in Query
    • Structural changes
  • Unity
  • 🎮Projects using Arch
    • Skylandkingdoms
    • Cubetory
    • SS14
    • EquilibriumEngine-CSharp
    • Rougelite-Survivor
  • ✏️Misc
    • Roadmap
    • FAQ
Powered by GitBook
On this page
  • Example
  • Overriding Update
  • Generating Queries in custom classes
  • Multithreading

Was this helpful?

Edit on GitHub
  1. Extensions
  2. Arch.Extended

Arch.System.SourceGenerator

Arch.System.SourceGenerator, automatically generates queries for you.

PreviousArch.SystemNextArch.EventBus

Last updated 2 months ago

Was this helpful?

The Arch.System.SourceGenerator package provides some code generation utils. With them, queries can be written virtually by themselves which saves some boilerplate code.

Query methods can be generated in as long as they are partial. However it makes most sense in the . The attributes can be used to meaningfully describe what query to generate, and the query will always call the annotated method. And the best, they can even be called manually!

Example

Let's take a look at the whole thing and what is possible with it.

public partial class MovementSystem : BaseSystem<World, >
{

    public MovementSystem(World world) : base(world) {}
    
    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void MoveEntity( in float time, )
    {
        pos.X += time * vel.X;
        pos.Y += time * vel.Y;
    }
    
    [Query]
    
    public void StopDeadEntities(ref Velocity vel)
    {
        vel.X = 0;
        vel.Y = 0;
    }
}

Again, there are attributes without generics that accept types. In addition, you can also pass an entity in the method signature of a [Query] to refer to the current entity. In addition to ref, you can also use in or out or omit the modifier altogether.

Each method marked with [Query] is extended to a Query by the source generation and called once for each Entity that matches it. The method signature and additional annotations act as a filter. This means that the MoveEntity method from above is called for each Entity with Position and Velocity. We only write the method with which we process each Entity and what we need... and Arch.System.SourceGenerator takes care of the rest.

A method with the same name and query is generated for each of these selected methods. This query method then calls our selected method for each Entity. E.g. MoveEntity which is getting called for each fitting Entity and MoveEntityQuery, the generated method that runs the query and calls MoveEntity for each Entity. This happens for every method within a class that has been marked with Query. So also for StopDeadEntities.

In addition, if BaseSystem.Update has not been implemented itself, a BaseSystem.Update is generated which calls all Queries according to their sequence in the system. So all you have to do is call BaseSystem.Update() or the generated query directly.

var world = World.Create();
var movementSystem = new MovementSystem(world);

myMovementSystem.Update(deltaTime);            // Calls both Querys in order automatically
myMovementSystem.MoveEntityQuery(world);       // Manually calling the querys
myMovementSystem.StopDeadEntitiesQuery(world);

It can be that easy, I bet that blew your mind, didn't it?

Overriding Update

If you use the generator as above then an Update method is automatically generated for you which calls all queries one after the other. However, if you want to have control over the update method yourself, you can do it this way:

public partial class MovementSystem : BaseSystem<World, GameTime>
{
    private readonly QueryDescription _customQuery = new QueryDescription().WithAll<Position, Velocity>();
    public DebugSystem(World world) : base(world) { }

    // Adding this will give you manual control
    public override void Update(in GameTime t)
    {
        World.Query(in _customQuery, (in Entity entity) => /** LOGIC **/ ));
        MoveEntityQuery(World);  // Call source generated query, which calls the MoveEntity method
    }

    [Query]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void MoveEntity([Data] in float time, ref Position pos, ref Velocity vel)
    {
        pos.X += time * vel.X;
        pos.Y += time * vel.Y;
    }
}

If you provide the update method yourself, you must also ensure that your generated queries (in the update method) are called!

Generating Queries in custom classes

However, you do not necessarily have to use the BaseSystem with the SourceGenerator. Instead, you can have queries generated wherever you want!

{

    [Query]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void MoveEntities([Data] in float time, ref Position pos, ref Velocity vel){
        pos.X += time * vel.X;
        pos.Y += time * vel.Y;
    }
    
    [Query]
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public  void DamageEntities(ref Hitted hit, ref Health health){
        health.value -= hit.value;
    }
}

The same works for static classes, whose performance is better. The advantage is that no class instance is needed.

Multithreading

Arch supports parallel queries directly out of the box. The source generator also does, with a simple attribute you can ensure that the query is executed on multiple threads:

[Query(Parallel = true)]  // Method is being called by Archs multithreading. 
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void MoveEntities([Data] in float time, ref Position pos, ref Velocity vel){
    pos.X += time * vel.X;
    pos.Y += time * vel.Y;
}
🧩
BaseSystem