Eyup - it's that Mark Smith!

ECSx Kanban Simulator: Elixir + ECSx + Kanban = taiicihi

Jira, Trello, that boards thing in Teams. Lots of people use some form of Kanban board for managing the flow of work for their team. Generally, they are pretty intuitive at first - 'to do', 'doing', 'done' is pretty self-evident.

In Software teams, the subtleties of the work at hand means you often see additional columns and so much work in progress that it becomes hard to see what's actually happening.

In this series I'm going to build a Kanban board simulator that will allow the exploration of work-in-progress, quality and capacity management on the thing that matters - actually delivering finished work.

I'll be writing the code in Elixir using the ECSx Entity-Component-System framework for Elixir, with a Phoenix front end of some form. Let's get started with a new project. Run the below and choose Yes when prompted to install the dependencies, and then do the suggested steps to build and run the application.

mix phx.new taiichi

What is an Entity-Component-System?

More commonly used in game development, Entity Component Systems (ECSs) take a different approach to Object-Oriented applications.

ECS: Systems

An application might be composed of many systems, that work to implement the behaviour of the application. Let's add a system called kanban to our application.

First, let's add the dependency on the ECSx Elixir package, above the default phoenix package dependency:

defp deps do
    [
      {:ecsx, "~> 0.5"},
      {:phoenix, "~> 1.7.18"},
      {:phoenix_ecto, "~> 4.5"},
      {:ecto_sql, "~> 3.10"},

then run mix deps.get to fetch the ECSx package. I also had to update the version of phoenix to 1.8.1 to remove some warnings.

Now we can run mix ecsx.setup to add the ECSx magic that drives our simulation. The first is a new config section in config.esx:

config :ecsx,
  tick_rate: 20,
  manager: Taiichi.Manager

We can see a couple of things - tick_rate is how many iterations we are going to see, per second. We'll be reducing this to 1; and manager, pointing to a new module named Taiichi.Manager, shown below.

defmodule Taiichi.Manager do
  @moduledoc """
  ECSx manager.
  """
  use ECSx.Manager

  def setup do
    # Seed persistent components only for the first server start
    # (This will not be run on subsequent app restarts)
    :ok
  end

  def startup do
    # Load ephemeral components during first server start and again
    # on every subsequent app restart
    :ok
  end

  # Declare all valid Component types
  def components do
    [
      # MyApp.Components.SampleComponent
    ]
  end

  # Declare all Systems to run
  def systems do
    [
      # MyApp.Systems.SampleSystem
    ]
  end
end

It's interesting to see that the Taiichi.Manager module has functions to return lists of components and systems, as well as a couple of methods for starting the application. No Entities so far, though!

I guess we now need a System, so let's add one called Kanban using the mix task mix ecsx.gen.system Kanban. This generates

defmodule Taiichi.Systems.Kanban do
  @moduledoc """
  Documentation for Kanban system.
  """
  @behaviour ECSx.System

  @impl ECSx.System
  def run do
    # System logic
    :ok
  end
end

The run/0 function is where our system logic goes and according to the ECSx docs is called every game tick. We'll see that in action momentarily, but let's have a look at the change in the manager.esx first:

...
 # Declare all Systems to run
  def systems do
    [
      Taiichi.Systems.Kanban
    ]
  end
...

Our Kanban module has been registered in the list of systems. We can run our application, but we don't see much in the logs:

% mix phx.server
[info] Component tables initialized
[info] Retrieved Components
[info] `startup/0` complete
[info] Running TaiichiWeb.Endpoint with Bandit 1.8.0 at 127.0.0.1:4000 (http)
[info] Access TaiichiWeb.Endpoint at http://localhost:4000
[watch] build finished, watching for changes...

Rebuilding...

Done in 205ms.
[info] GET /
[debug] Processing with TaiichiWeb.PageController.home/2
  Parameters: %{}
  Pipelines: [:browser]
[info] Sent 200 in 67ms

Not very exciting, though the first three [info] lines tell us that the ECSx system is running. Let's put some logging into it and see what's happening.

...
  def setup do
    # Seed persistent components only for the first server start
    # (This will not be run on subsequent app restarts)
    Logger.info("ECSx.Manager setup")
    :ok
  end

  def startup do
    # Load ephemeral components during first server start and again
    # on every subsequent app restart
    Logger.info("ECSx.Manager startup")
    :ok
  end
...

(I also deleted a file called components.persistence to get the setup/0 method to be called - ECSx sets up a file to save state). Now we get a little bit more:

(base) marksmith@Marks-macbook taiichi % mix phx.server
[info] Component tables initialized
[info] Fresh server detected
[info] ECSx.Manager setup
[info] `setup/0` complete
[info] ECSx.Manager startup
[info] `startup/0` complete
[info] Running TaiichiWeb.Endpoint with Bandit 1.8.0 at 127.0.0.1:4000 (http)
[info] Access TaiichiWeb.Endpoint at http://localhost:4000
[watch] build finished, watching for changes...

The logs show our ECSx.Manager callbacks are being called. Elxir does not override methods - you implement the callback by writing a new version of a function.

Let's put some logging into our Taiichi.Systems.Kanban.run/0 method:

  require Logger

  @impl ECSx.System
  def run do
    # System logic
    {:ok, datetime} = DateTime.now("Etc/UTC")
    Logger.info("Taiichi.Systems.Kanban.run/0 #{datetime}")
    :ok
  end

And now we get

[info] Taiichi.Systems.Kanban.run/0 2025-11-18 21:51:02.600605Z
[info] Taiichi.Systems.Kanban.run/0 2025-11-18 21:51:02.649910Z
[info] Taiichi.Systems.Kanban.run/0 2025-11-18 21:51:02.699905Z
[info] Taiichi.Systems.Kanban.run/0 2025-11-18 21:51:02.749907Z
[info] Taiichi.Systems.Kanban.run/0 2025-11-18 21:51:02.799925Z
[info] Taiichi.Systems.Kanban.run/0 2025-11-18 21:51:02.849927Z
[info] Taiichi.Systems.Kanban.run/0 2025-11-18 21:51:02.899938Z
[info] Taiichi.Systems.Kanban.run/0 2025-11-18 21:51:02.949928Z
[info] Taiichi.Systems.Kanban.run/0 2025-11-18 21:51:02.999811Z
[info] Taiichi.Systems.Kanban.run/0 2025-11-18 21:51:03.049867Z
...etc.

So our system is being called - twenty times a second which is probably fine for a game, but maybe a bit too frequently for a simulation. Let's revisit our `config.esx' and scale it down:

config :ecsx,
  tick_rate: 1,
  manager: Taiichi.Manager

Now when we run, we see:

[info] Taiichi.Systems.Kanban.run/0 2025-11-18 23:30:07.054903Z
[info] Taiichi.Systems.Kanban.run/0 2025-11-18 23:30:08.054884Z
[info] Taiichi.Systems.Kanban.run/0 2025-11-18 23:30:09.054862Z

Once per second. Our application is running and our first system (kanban) is running once per second.

Seems like a good place to stop. The code thus far is here: v0.1.0.

Other posts in this series:

Links:

#ECSx #Elixir #Kanban #Lean #taiichi