Alternative managed wrapper around flecs-cs bindings for using the ECS framework Flecs in modern .NET.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5.3 KiB

gaemstone.ECS

.. is a medium-level managed wrapper library around the flecs-cs bindings for the amazing Entity Component System (ECS) framework Flecs. It is used as part of the gæmstone game engine, but may be used in other projects as well. To efficiently use this library, a thorough understanding of Flecs is required.

These classes have been split from the main gæmstone project. It is still a little unclear what functionality belongs where, and structural changes may occur. In its current state, I recommend to use this repository simply as a reference for building similar projects.

Features

Example

var world = new World<Program>();

var position = world
	.New("Position")    // Create a new EntityBuilder, and set its name.
	.Symbol("Position") // Set entity's symbol. (Used in query expression.)
	.Build()            // Actually create the entity in-world.
	// Finally, create a component from this entity.
	// "Position" is defined at the bottom of this example.
	.InitComponent<Position>();

// Create an "Entities" parent with two positioned entities inside.
var entities = world.New("Entities").Build();
entities.NewChild("One").Set(new Position(-5,  0)).Build();
entities.NewChild("Two").Set(new Position(10, 20)).Build();

// Changed my mind: Let's multiply each entity's position by 10.
foreach (var child in entities.GetChildren()) {
	ref var pos = ref child.GetRefOrThrow<Position>();
	pos = new(pos.X * 10, pos.Y * 10);
}

// The following systems run in the "OnUpdate"
// phase of the default pipeline provided by Flecs.
var dependsOn = world.LookupPathOrThrow("/flecs/core/DependsOn");
var onUpdate  = world.LookupPathOrThrow("/flecs/pipeline/OnUpdate");

// Create a system that will move all entities with
// the "Position" component downwards by 2 every frame.
world.New("FallSystem")
	.Add(dependsOn, onUpdate)
	.Build().InitSystem(new("Position"), iter => {
		var posColumn = iter.Field<Position>(1);
		for (var i = 0; i < iter.Count; i++) {
			ref var pos = ref posColumn[i];
			pos = new(pos.X, pos.Y - 2);
		}
	});

// Create a system that will print out entities' positions.
world.New("PrintPositionSystem").Build()
	.Add(dependsOn, onUpdate)
	.InitSystem(new("[in] Position"), iter => {
		var posColumn = iter.Field<Position>(1);
		for (var i = 0; i < iter.Count; i++) {
			var entity = iter.Entity(i);
			var pos    = posColumn[i];
			Console.WriteLine($"{entity.Name} is at {pos}");
		}
	});

// Infinite loop that runs the "game" at 30 FPS.
while (true) {
	var delta = TimeSpan.FromSeconds(1.0f / 30);
	// Progress returns false if quit was requested.
	if (!world.Progress(delta)) break;
	Thread.Sleep(delta);
}

record struct Position(int X, int Y);

Instructions

# Clone the repository. Recurse into submodules to include flecs-cs and flecs.
git clone --recurse-submodules https://git.mcft.net/copygirl/gaemstone.ECS.git

# If you want to add it to your own repository as a submodule:
git submodule add https://git.mcft.net/copygirl/gaemstone.ECS.git

# To add a reference to this library to your .NET project:
dotnet add reference gaemstone.ECS/gaemstone.ECS.csproj

# To generate flecs-cs' bindings:
./gaemstone.ECS/src/flecs-cs/library.sh

On the TContext type parameter

Entities may be looked up simply by their type, once they're registered with CreateLookup or InitComponent. Under the hood, this is made possible using a nested static generic class that hold onto an Entity field. Theoretically this could be compiled into a simple field lookup and therefore be faster than dictionary lookups.

To support scenarios where multiple worlds may be used simultaneously, each with their own unique type lookups, we specify a generic type as that context.

In cases where only a single world is used, the amount of typing can be reduced by including a file similar to the following, defining global aliases:

global using Entity   = gaemstone.ECS.Entity<Context>;
global using Id       = gaemstone.ECS.Id<Context>;
global using Iterator = gaemstone.ECS.Iterator<Context>;
global using World    = gaemstone.ECS.World<Context>;
// Add more aliases as you feel they are needed.

public struct Context {  }