README.md

# ECSComb

`ECSComb` is an Entity-Component-System framework for Elixir built on BEAM and ETS.
It focuses on a small runtime surface:

- `ECSComb.World` owns entity lifecycle and component storage.
- `ECSComb.Query` provides runtime `all` / `any` / `none` queries.
- `ECSComb.System` defines the scheduler contract.
- `ECSComb.Scheduler` groups non-conflicting systems into parallel layers.
- `ECSComb.TickLoop` drives the world at a fixed tick rate.

## Design Highlights

- ETS-backed storage with public named tables and direct read access
- Entity IDs use `{index, generation}` to reject stale references
- Components are plain structs with no macros or registration
- System ordering is inferred from declared read/write sets
- Runtime add/remove of systems is supported between ticks

## Quick Example

```elixir
defmodule Demo.Position do
  defstruct [:x, :y]
end

defmodule Demo.Velocity do
  defstruct [:dx, :dy]
end

defmodule Demo.MoveSystem do
  @behaviour ECSComb.System

  alias ECSComb.Query
  alias ECSComb.World
  alias Demo.Position
  alias Demo.Velocity

  @impl true
  def access do
    %{reads: [Position, Velocity], writes: [Position]}
  end

  @impl true
  def run(world) do
    Query.select(world, all: [Position, Velocity])
    |> Enum.each(fn entity_id ->
      {:ok, %Position{x: x, y: y}} = World.get(world, entity_id, Position)
      {:ok, %Velocity{dx: dx, dy: dy}} = World.get(world, entity_id, Velocity)
      :ok = World.put(world, entity_id, %Position{x: x + dx, y: y + dy})
    end)

    :ok
  end
end

{:ok, world} = ECSComb.World.start_link()
{:ok, entity_id} = ECSComb.World.spawn_entity(world)

:ok = ECSComb.World.put(world, entity_id, %Demo.Position{x: 0, y: 0})
:ok = ECSComb.World.put(world, entity_id, %Demo.Velocity{dx: 1, dy: 1})

scheduler =
  ECSComb.Scheduler.new()
  |> ECSComb.Scheduler.add_system(Demo.MoveSystem)
  |> ECSComb.Scheduler.build_graph()

{:ok, tick_loop} =
  ECSComb.TickLoop.start_link(
    world: world,
    scheduler: scheduler,
    tick_rate: 20
  )

Process.sleep(120)
{:ok, %Demo.Position{x: x, y: y}} = ECSComb.World.get(world, entity_id, Demo.Position)
IO.inspect({x, y}, label: "updated position")

:ok = ECSComb.TickLoop.stop(tick_loop)
```

## Test Coverage

The project includes tests for:

- Entity lifecycle and component CRUD
- Transactions, name indexes, and concurrent reads/writes
- Query filtering and indexed lookup correctness
- Behaviour validation for systems
- Scheduler conflict detection, graph layering, and parallel execution
- Tick timing, cron callbacks, and end-to-end runtime integration

## Status

Implemented and covered by `mix test`.