A simple C# ECS library.
global using World = PipeKit.World<Flags, string, double>;
global using Query = PipeKit.World<Flags, string, double>.Query;
global using QueryBuilder = PipeKit.World<Flags, string, double>.QueryBuilder;
global using ISystem = PipeKit.World<Flags, string, double>.ISystem;
using FluentResponse;
var world = World.CreateBuilder()
.WithComponent<Position>()
.WithComponent<Velocity>()
.WithComponent<Size>()
.WithComponent<Mass>()
.WithSystem(world => new MovementSystem(world))
.WithSystem(world => new CollisionSystem(world))
.WithEventBus<Position>()
.WithEventBus<CollisionEvent>()
.Build();
uint collisionCount = 0;
const uint PI_DIGITS = 6;
const double PHYSICS_FIXED_DELTA_TIME = 0.00001;
world.SubscribeEvent<CollisionEvent>(_ => collisionCount++);
// Heavy block
var blockA = world.CreateEntityBuilder()
.WithComponent(new Position(5))
.WithComponent(new Velocity(-1))
.WithComponent(new Size(1))
.WithComponent(new Mass(Math.Pow(100, PI_DIGITS - 1)))
.Build();
// Light block
var blockB = world.CreateEntityBuilder()
.WithComponent(new Position(2.5))
.WithComponent(new Velocity(0))
.WithComponent(new Size(1))
.WithComponent(new Mass(1))
.Build();
// Wall entity
var wall = world.CreateEntityBuilder()
.WithComponent(new Position(0))
.WithComponent(new Size(0))
.Build();
while (true) {
world.RunSystems(PHYSICS_FIXED_DELTA_TIME);
var velocityA = world.TryGetComponent<Velocity>(blockA).Unwrap().X;
var velocityB = world.TryGetComponent<Velocity>(blockB).Unwrap().X;
if (velocityA is > 0 && velocityB > velocityA)
break;
}
Console.WriteLine($"Collision Count = {collisionCount}");
Console.WriteLine($"Estimated digits of Pi = {PI_DIGITS}");
Console.WriteLine($"Estimated value of Pi = {(collisionCount << 1) / Math.Pow(10, PI_DIGITS - 1)}");using PipeKit;
enum Flags {};
record struct CollisionEvent(Entity A, Entity B);
record struct Position(double X);
record struct Velocity(double X);
record struct Size(double Width);
record struct Mass(double Value);
class MovementSystem(World world) : ISystem {
public QueryBuilder QueryBuilder { get; } = world.CreateQueryBuilder()
.WithComponent<Position>()
.WithComponent<Velocity>(velocity => velocity.X is > 0);
public void Run(Query query, double deltaTime) {
var positions = query.GetComponents<Position>();
var velocities = query.GetComponents<Velocity>();
foreach (var entity in query.Entities) {
var position = positions[entity];
var velocity = velocities[entity];
position.X += velocity.X * deltaTime;
world.TrySetComponent(entity, position);
world.PublishEvent(entity, position);
}
}
}
class CollisionSystem(World world) : ISystem {
public QueryBuilder QueryBuilder { get; } = world.CreateQueryBuilder()
.WithComponent<Position>()
.WithComponent<Velocity>()
.WithComponent<Size>()
.WithComponent<Mass>();
public void Run(Query query, double deltaTime) {
var entities = query.Entities;
var positions = query.GetComponents<Position>();
var velocities = query.GetComponents<Velocity>();
var sizes = query.GetComponents<Size>();
var masses = query.GetComponents<Mass>();
foreach (var a in query.Entities)
foreach (var b in query.Entities)
if (a != b) {
var positionA = positions[a];
var positionB = positions[b];
var sizeA = sizes[a];
var sizeB = sizes[b];
if (Math.Abs(positionA.X - positionB.X) < (sizeA.Width + sizeB.Width) * 0.5) {
// 1D elastic collision
var massA = masses[a].Value;
var massB = masses[b].Value;
var velocityA = velocities[a];
var velocityB = velocities[b];
world.TrySetComponent(a, new Velocity((massA - massB) / (massA + massB) * velocityA.X + 2 * massB / (massA + massB) * velocityB.X));
world.TrySetComponent(b, new Velocity(2 * massA / (massA + massB) * velocityA.X + (massB - massA) / (massA + massB) * velocityB.X));
world.PublishEvent(new CollisionEvent(a, b));
}
}
var wallQuery = world.CreateQueryBuilder()
.WithComponent<Position>()
.WithComponent<Size>()
.WithoutComponent<Velocity>()
.Build();
var wallEntities = wallQuery.Entities;
var wallPositions = wallQuery.GetComponents<Position>();
var wallSizes = wallQuery.GetComponents<Size>();
foreach (var entity in entities) {
var position = positions[entity];
var velocity = velocities[entity];
var size = sizes[entity];
foreach (var wallEntity in wallEntities) {
var wallPosition = wallPositions[wallEntity];
var wallSize = wallSizes[wallEntity];
if (Math.Abs(position.X - wallPosition.X) < (size.Width + wallSize.Width) * 0.5)
world.TrySetComponent(entity, new Velocity(-velocity.X));
}
}
}
}