Skip to content

Commit

Permalink
Add tests for engine.
Browse files Browse the repository at this point in the history
Also supply engine with an initial animation.
  • Loading branch information
amclain committed Jul 2, 2020
1 parent 16e3fe7 commit 0eb786a
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 10 deletions.
5 changes: 4 additions & 1 deletion lib/xebow/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ defmodule Xebow.Application do

use Application

@animation_type Xebow.Animation.types() |> List.first()
@animation Xebow.Animation.new(type: @animation_type)

def start(_type, _args) do
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
Expand Down Expand Up @@ -36,7 +39,7 @@ defmodule Xebow.Application do
# {Xebow.Worker, arg},
Xebow.HIDGadget,
Xebow.RGBMatrix,
{Xebow.Engine, [Xebow.RGBMatrix]},
{Xebow.Engine, {@animation, [Xebow.RGBMatrix]}},
Xebow.Keys
]
end
Expand Down
23 changes: 14 additions & 9 deletions lib/xebow/engine.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,19 @@ defmodule Xebow.Engine do
@doc """
Start the engine.
This function accepts the following arguments:
- `paintables` - A list of modules that implement the `Xebow.Paintable` behaviour.
This module registers its process globally and is expected to be started by
a supervisor.
This function accepts the following arguments as a tuple:
- `initial_animation` - The animation that plays when the engine starts.
- `paintables` - A list of modules to output `Xebow.Frame` to that implement
the `Xebow.Paintable` behavior. If you want to register your paintables
dynamically, set this to an empty list `[]`.
"""
@spec start_link(paintables :: list(module)) :: GenServer.on_start()
def start_link(paintables \\ []) do
GenServer.start_link(__MODULE__, {paintables}, name: __MODULE__)
@spec start_link({initial_animation :: Animation.t(), paintables :: list(module)}) ::
GenServer.on_start()
def start_link({initial_animation, paintables}) do
GenServer.start_link(__MODULE__, {initial_animation, paintables}, name: __MODULE__)
end

@doc """
Expand Down Expand Up @@ -83,18 +90,16 @@ defmodule Xebow.Engine do
# Server

@impl GenServer
def init({paintables}) do
def init({initial_animation, paintables}) do
send(self(), :get_next_frame)

[initial_animation_type | _] = Animation.types()
animation = Animation.new(type: initial_animation_type)
initial_state = %State{paintables: %{}}

state =
Enum.reduce(paintables, initial_state, fn paintable, state ->
add_paintable(paintable, state)
end)
|> set_animation(animation)
|> set_animation(initial_animation)

{:ok, state}
end
Expand Down
109 changes: 109 additions & 0 deletions test/engine_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
defmodule Xebow.EngineTest do
use ExUnit.Case

alias Xebow.{Animation, Engine, Frame}

# Creates a Xebow.Paintable module that emits frames to the test suite process.
defp paintable(%{test: test_name}) do
process = self()
module_name = String.to_atom("#{test_name}-paintable")

Module.create(
module_name,
quote do
def get_paint_fn do
fn frame ->
send(unquote(process), {:frame, frame})
end
end
end,
Macro.Env.location(__ENV__)
)

%{paintable: module_name}
end

# Creates a single pixel, single frame animation.
defp solid_animation(red \\ 255, green \\ 128, blue \\ 0) do
pixels = [{0, 0}]
color = Chameleon.RGB.new(red, green, blue)
frame = Frame.solid_color(pixels, color)

animation =
Animation.new(
type: Animation.Static,
frames: [frame],
delay_ms: 10,
loop: 1
)

{animation, frame}
end

setup [:paintable]

test "renders a solid animation", %{paintable: paintable} do
{animation, frame} = solid_animation()

start_supervised!({Engine, {animation, [paintable]}})

assert_receive {:frame, ^frame}
end

test "renders a multi-frame, multi-pixel animation", %{paintable: paintable} do
pixels = [
{0, 0},
{0, 1},
{1, 0},
{1, 1}
]

frames = [
Frame.solid_color(pixels, Chameleon.Keyword.new("red")),
Frame.solid_color(pixels, Chameleon.Keyword.new("green")),
Frame.solid_color(pixels, Chameleon.Keyword.new("blue")),
Frame.solid_color(pixels, Chameleon.Keyword.new("white"))
]

animation =
Animation.new(
type: Animation.Static,
frames: frames,
delay_ms: 10,
loop: 1
)

start_supervised!({Engine, {animation, [paintable]}})

Enum.each(frames, fn frame ->
assert_receive {:frame, ^frame}
end)
end

test "can play a different animation", %{paintable: paintable} do
{animation, _frame} = solid_animation()
{animation2, frame2} = solid_animation(127, 127, 127)

start_supervised!({Engine, {animation, [paintable]}})

:ok = Engine.play_animation(animation2)

assert_receive {:frame, ^frame2}
end

test "can register and unregister paintables", %{paintable: paintable} do
{animation, frame} = solid_animation()
{animation2, frame2} = solid_animation(127, 127, 127)

start_supervised!({Engine, {animation, []}})

:ok = Engine.register_paintable(paintable)

assert_receive {:frame, ^frame}

:ok = Engine.unregister_paintable(paintable)
:ok = Engine.play_animation(animation2)

refute_receive {:frame, ^frame2}
end
end

0 comments on commit 0eb786a

Please sign in to comment.