Skip to content

v4.0.3

Latest
Compare
Choose a tag to compare
@BeanCheeseBurrito BeanCheeseBurrito released this 23 Nov 15:16

Nuget Packages

Flecs.NET (Wrapper + Bindings + Native Libraries): Release | Debug
Flecs.NET.Bindings (Bindings + Native Libraries): Release | Debug
Flecs.NET.Native (Native Libraries): Release | Debug

New GitHub Package registry

For immediate access to the latest changes, NuGet packages are now pushed to the GitHub Package registry on every commit to the main branch. See the following link for more information. https://github.com/BeanCheeseBurrito/Flecs.NET?tab=readme-ov-file#github-package-registry

Static linking

Flecs can now be statically linked with Native AOT apps using the $(FlecsStaticLink) property when consuming the Flecs.NET NuGet package.

To enable static linking, add <FlecsStaticLink>true</FlecsStaticLink> to a property group. To prevent shared libraries from being copied to the output directory, add ExcludeAssets="native" to your package reference.

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <PublishAot>true</PublishAot>
        <FlecsStaticLink>true</FlecsStaticLink>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Flecs.NET.Debug" Version="*-*" ExcludeAssets="native"/>
    </ItemGroup>
</Project>

Entity and Id extensions

All entity and id methods can now be called directly on Alert , Component , Observer , Pipeline , System, TimerEntity , and UntypedComponent . Previously, accessing entity and id methods had to be done through the .Entity and .Id properties.

Old:

world.Component<Phases.Render>().Entity
    .Add(Ecs.Phase)
    .DependsOn(Ecs.OnUpdate);

New:

world.Component<Phases.Render>()
    .Add(Ecs.Phase)
    .DependsOn(Ecs.OnUpdate);

Managed type support for user contexts

The .Ctx() APIs have been changed to support storing managed types as user context for queries, systems, and observers.

Old:

int value = 10;

world.System()
    .Ctx(&value) // Pointer has to be passed.
    .Each(static (Iter it, int _) =>
    {
        ref int ctx = ref it.Ctx<int>();
        Console.WriteLine(ctx); // Prints 10
    });

New:

world.System()
    .Ctx("Context") // Context is passed by value
    .Each(static (Iter it, int _) =>
    {
        ref string ctx = ref it.Ctx<string>();
        Console.WriteLine(ctx); // Prints "String"
    });

A callback can be provided to run clean-up logic before the context object is freed.

world.System()
    .Ctx("Context", static (ref string ctx) =>
    {
        Console.WriteLine("Context is being freed");
    }) 
    .Each(...);

Managed types can now also be used for user data associated with groups.

using Query<Position> q = world.QueryBuilder<Position>()
    .GroupBy<Group>()
    // Callback invoked when a new group is created. Including the third "out T" parameter
    // allows you to provide a user context.
    .OnGroupCreate((World world, ulong id, out GroupCtx ctx) =>
    {
        Console.WriteLine($"Group {world.Entity(id)} created");

        // Set data that will be associated with the group
        ctx = new GroupCtx(groupCounter++);
    })
    // Callback invoked when a group is deleted. Including the third "ref T" parameter
    // provides access to the user context object.
    .OnGroupDelete((World world, ulong id, ref GroupCtx ctx) =>
    {
        Console.WriteLine($"Group {world.Entity(id)} deleted");
    })
    .Build();

Type-checked QueryBuilder.TermAt()

New type-checked versions of QueryBuilder.TermAt() have been added to help ensure the intended field matches the correct type.

world.QueryBuilder<Position, Velocity, Mass>()
    .TermAt<Position>(0).Singleton()
    .TermAt<Velocity>(1).Singleton()
    .TermAt<Velocity>(2).Singleton() // Assertion fails due to mismatched type
    .Each(...);

For simple queries, the field index can be omitted, in which case the first field that matches the type argument is retrieved.

world.QueryBuilder<Position, Velocity, Mass>()
    .TermAt<Velocity>().Singleton() // Matches field index 1
    .Each();

World.AtFini/World.RunPostFrame/AppBuilder.Init

World.AtFini() and World.RunPostFrame() have been updated to only take a World argument. You can no longer pass a user context alongside the callbacks. User data can instead be stored in a static field if needed.
Old:

world.RunPostFrame(static (ecs_world_t*, void*) =>
{
    Console.WriteLine("Frame finished.")
}, null);

world.AtFini(static (ecs_world_t*, void*) =>
{
    Console.WriteLine("World finish.")
}, null);

New:

world.RunPostFrame(static (World world) =>
{
    Console.WriteLine("Frame finished.")
});

world.AtFini(static (World world) =>
{
    Console.WriteLine("World finish.")
});

AppBuilder.Init() now takes a World instead of an ecs_world_t*

Old:

using World world = World.Create();

world.App()
    .Init(Setup)
    .Run();

static void Setup(ecs_world_t* worldPtr)
{
    World world = World.Create(worldPtr);
    world.Import<Module1>();
    world.Import<Module2>();
    world.Import<Module3>();
}

New:

using World world = World.Create();

world.App()
    .Init(Setup)
    .Run();

static void Setup(World world)
{
    world.Import<Module1>();
    world.Import<Module2>();
    world.Import<Module3>();
}

Other

  • Native build script can now output WASM static libraries
  • Longstanding issue with multi-threaded system crashes has been fixed
  • Using Flecs.NET in Native AOT applications works again

What's Changed

New Contributors

Full Changelog: v4.0.2...v4.0.3