Sim
Mix.install([
{:kino, "~> 0.10.0", only: [:dev]},
{:ximula, path: Path.join(__DIR__, ".."), env: :dev}
])
Structs
defmodule Sim.Entity do
# %Sim.Vegetation{size: 5, capacity: 50, ...}
defstruct id: nil, type: Sim.Entity, value: 0
end
defmodule Sim.Field do
defstruct position: nil,
priority: :normal,
vegetation: [],
herbivores: [],
predators: [],
factories: [],
buildings: [],
transports: [],
pawns: []
end
Data
defmodule Sim.Data do
alias Ximula.{Grid, Torus}
alias Ximula.Gatekeeper.Agent, as: Gatekeeper
alias Sim.Field
def create(size) do
Torus.create(size, size, fn x, y ->
%Field{
position: {x, y},
vegetation: %Sim.Entity{type: Sim.Vegetation}
}
end)
end
def get_field(pid, {x, y}) do
Gatekeeper.get(pid, fn map -> Torus.get(map, x, y) end)
end
def get_all_fields(pid) do
Gatekeeper.get(pid, &Grid.values(&1))
end
def positions(pid) do
Gatekeeper.get(pid, &Grid.map(&1, fn x, y, _ -> {x, y} end))
end
def lock_field(pid, position) do
Gatekeeper.lock(pid, position, &Grid.get(&1, position))
end
def update_field(pid, position, field) do
Gatekeeper.update(pid, position, field, &Grid.put(&1, position, field))
end
end
{:ok, agent} = Agent.start_link(fn -> Sim.Data.create(20) end)
{:ok, gatekeeper} = Ximula.Gatekeeper.Server.start_link(context: %{agent: agent})
Sim.Data.get_field(gatekeeper, {0, 0})
Simulations
defmodule Sim.Vegetation do
alias Sim.Data
def change(position, gatekeeper) do
field = gatekeeper |> Data.lock_field(position) |> sim()
:ok = Data.update_field(gatekeeper, position, field)
position
end
def sim(field) do
if field.position == {9, 9}, do: raise("Sim Error")
update_in(field.vegetation.value, &(&1 + 1))
end
end
defmodule Sim.Herbivore do
def change(position, _world) do
position
end
end
defmodule Sim.Predator do
def change(position, _world) do
Process.sleep(10)
position
end
end
defmodule Sim.Factory do
def change(position, _world) do
Process.sleep(20)
position
end
end
defmodule Sim.Transport do
def change(position, _world) do
Process.sleep(50)
position
end
end
Simulator
defmodule FieldSimulator do
alias Ximula.Simulator
alias Ximula.Sim.Queue
alias Sim.Data
@simulations [
Sim.Vegetation,
Sim.Herbivore,
Sim.Predator,
Sim.Factory,
Sim.Transport
]
def run_queue(%Queue{} = queue, opts) do
Enum.map(@simulations, &sim_simulation(queue, &1, opts))
|> aggregate_results(queue.name)
|> notify_sum()
end
def sim_simulation(queue, simulation, opts) do
Simulator.benchmark(fn ->
get_positions(opts[:proxy], queue.name)
|> Simulator.sim({simulation, :change, [opts[:proxy]]})
|> handle_success(opts[:proxy])
|> handle_failed(opts[:proxy])
|> summarize(simulation)
|> notify()
end)
end
def get_positions(proxy, _name) do
Data.positions(proxy)
end
def handle_success(%{ok: fields} = results, _proxy) do
IO.puts("successful simulations: #{Enum.count(fields)}")
results
end
def handle_failed(%{exit: failed} = results, _proxy) do
Enum.each(failed, fn reason ->
IO.puts("failed simulations: #{Exception.format_exit(reason)}")
end)
results
end
def summarize(%{ok: success, exit: failed}, simulation) do
%{
simulation: simulation,
ok: success,
error:
Enum.map(failed, fn {id, {exception, stacktrace}} ->
{id, Exception.normalize(:exit, exception, stacktrace) |> Exception.message()}
end)
}
end
# [{1097, %{error: [], ok: [], simulation: Sim.Vegetation}}]
def aggregate_results(results, queue) do
%{
queue: queue,
results:
Enum.map(results, fn {time, %{error: error, ok: ok, simulation: simulation}} ->
%{simulation: simulation, time: time, errors: Enum.count(error), ok: Enum.count(ok)}
end)
}
end
def notify_sum(results) do
# PubSub.broadcast(topic, queue_result) | GenStage.cast(stage, {:receive, queue_result})
dbg(results)
end
def notify(%{error: _error, ok: _ok, simulation: _simulation} = result) do
# %{simulation: simulation, changed: ok} |> dbg()
# %{simulation: simulation, failed: error} |> dbg()
# PubSub.broadcast(topic, change) | GenStage.cast(stage, {:receive, change})
result
end
end
Task.Supervisor.start_link(name: Ximula.Simulator.Task.Supervisor)
Task.Supervisor.start_link(name: Ximula.Sim.Loop.Task.Supervisor)
Ximula.Sim.Loop.start_link(sim_args: [proxy: gatekeeper])
Ximula.Sim.Loop.add_queue(%Ximula.Sim.Queue{
name: :high,
func: {FieldSimulator, :run_queue, [proxy: gatekeeper]},
interval: 10_000
})
# Ximula.Sim.Loop.add_queue(%Ximula.Sim.Queue{
# name: :normal,
# func: &FieldSimulator.run_queue/2,
# interval: 2_000
# })
# Ximula.Sim.Loop.add_queue(%Ximula.Sim.Queue{
# name: :low,
# func: &FieldSimulator.run_queue/2,
# interval: 10_000
# })
require Logger
Logger.info("START!")
Ximula.Sim.Loop.start_sim()
Process.sleep(22_000)
Ximula.Sim.Loop.stop_sim()
Logger.info("END!")
Notes
Grid size 10x10
Whole queue took about 700ms
vegetation does +1 -> 5 -10 ms
herbivore does nothing -> 3- 5 ms
predator sleep 10ms -> ~88ms
factory sleep 20ms -> ~168ms
transport sleep 50ms -> ~410ms
all overhead about 30 - 100 μs per thread
Sim.Data.get_field(gatekeeper, {0, 0}) |> dbg()
Sim.Data.get_field(gatekeeper, {9, 9}) |> dbg()