Skip to content

Commit

Permalink
split the animation interface out from the BaseAnimation
Browse files Browse the repository at this point in the history
Renamed the BaseAnimation to simply Base
Renamed the LedAnimationManager to simply Manager
Renamed the LedAnimator to simply Animator
Added module documentation to the LedANimationManager
  • Loading branch information
a-maze-d committed Dec 4, 2023
1 parent 66f5654 commit 30285b5
Show file tree
Hide file tree
Showing 23 changed files with 367 additions and 275 deletions.
22 changes: 14 additions & 8 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ Even though this library is published, there are things I still want to do befor
- [ ] Fledex
- [x] Fledex.Leds
- [x] Fledex.Color.Names
- [ ] Fledex.Animation.BaseAnimator
- [x] Fledex.Animation.BaseAnimator
- [x] Fledex.Animation.LedAnimator
- [ ] Fledex.Animation.LedAnimationManager
- [x] Fledex.Animation.LedAnimationManager
- [x] Fledex.LedsDriver
- [ ] Add type specs (at least for the most important modules) (v0.3)
- [ ] Fledex
Expand All @@ -32,16 +32,22 @@ Even though this library is published, there are things I still want to do befor
- [x] Change from `@behaviour` to `use` and make BaseAnimation the base for the animators
moving the implementations down to the base but allow overides (v0.3)
- [x] Remove dialyzer warnings
- [ ] Improve the LedsDriver config
- [ ] Fix flaxy tests (see TODOs) (v0.3)
- [ ] Perform an extra round of testing on hardware (v0.3)
- [x] Big renaming (v0.3)
- [ ] Rename `LedsDriver` --> `LedStrip` (v0.3)
- [x] Split BaseAnimation into the interface part and the base part (v0.3)
- [x] Rename the BaseAnimation to take advantage of the namespace. (v0.3)
- [x] Rename the LedAnimationManager to take advantage of the namespace (v0.3)
- [x] Rename the LedAnimator to just Animator
- [ ] Improve the LedsDriver config (v0.4)
- [ ] Fix flaxy tests (see TODOs) (v0.4)
- [ ] Perform an extra round of testing on hardware (v0.4)
- [ ] Enable Telemetry? (v0.5)
- [ ] Upgrade to a hex released version of circuits_sim as soon as available (v0.?)
- [ ] Missing functionality
- [ ] see the project plan that was planned out with my son, we are not quite there yet
- [ ] see the project plan that was planned out with my son, we are not quite there yet (v0.4?)
- [ ] Connect everything into a supervision tree (to make it more robust) (v0.4)
- [ ] Use protocols ?
- [ ] Drivers
- [x] Drivers <-- No, doesn't make sense in this case
- [x] Conversions `to_rgb`, `to_colorint` <-- decided against it to allow having simple structures (tuple) instead of (module)structs. Protocols don't seem to work with those.
- [ ] ??? animations & components?
- [ ] Create a dsl (domain specific language) to (finally) easily program strips
Expand All @@ -50,7 +56,7 @@ Even though this library is published, there are things I still want to do befor
- [ ] Provide examples on how to cluster (v0.3)
- [ ] Add an example where several nodes are connected to transfer pubsub messages accross nodes (v0.3)
- [x] Create a driver that outputs through pubsub (on one node) (v0.3) and
- [ ] an animation that consumes those (this allows to connect remote livebooks to a physical led strip) (v0.4)
- [ ] an animation that consumes those (this allows to connect remote livebooks to a physical led strip. CAUTION: protect against loops!) (v0.4)
- [ ] LED-component library
- [ ] Create foundation for a led-component-library that enables defining reusable led components. For example both the clock as well as the weather example have a scale it would be easy to define those as components that would make it easier to defining certain aspects (v0.5)
- [ ] Increase consumption
Expand Down
12 changes: 6 additions & 6 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,24 @@ The `Leds` is an easy way to define a set of Leds. This module provides the poss
The `Leds` are not bound to a specific led strip (and namespace), but can be send to one by calling the `send()` function with the appropriate parameters. For convenience it's possible to bind it already during creation time to a specific led strip and namespace.

# The Animations
To define individual `Leds` is nice, but it would be even nicer, if it would be possible to animate the `Leds`. This is where the LedAnimator comes in. It allows to define `Leds` and vary them through some triggers (like a timer/counter).
To define individual `Leds` is nice, but it would be even nicer, if it would be possible to animate the `Leds`. This is where the `Animator` comes in. It allows to define `Leds` and vary them through some triggers (like a timer/counter).
It's possible to define this simply through a sequence of `Leds` definitions followed by a `send()` and some `Process.sleep()`, but that approach would have several limitations.

The `LedAnimator` is a `GenServer` that loops through the animation function and allows to modify the animation between loops.
The `Animator` is a `GenServer` that loops through the animation function and allows to modify the animation between loops.

The various `LedAnimator`s can be managed through a `LedAnimationManager`. It allows to create, redefine, and destroy animations (i.e.`LedAnimator`s) through a single configuration.
The various `Fledex.Animation.Animator`s can be managed through a `Fledex.Animation.Manager`. It allows to create, redefine, and destroy animations (i.e.`Fledex.Animation.Animator`s) through a single configuration.

# The Fledex DSL
The ultimate goal is to define a `Fledex` DSL that can configure the `LedAnimationManager` and update annimations on the fly.
The ultimate goal is to define a `Fledex` DSL that can configure the `Fledex.Animation.Manager` and update annimations on the fly.

In the above diagram an example is shown on how such a DSL might look like.

# The Triggers

The `LedsDriver` is publishing via `PubSub` an event (a trigger) that gets picked up by the `LedAnimator`s to change their animation. The trigger can not only be created by the driver, but also by external events. The difference is that only the triggers from the driver are used by the `LedAnimator`s to redefine the animation. All other triggers are collected over time and can be used when updating the animation.
The `LedsDriver` is publishing via `PubSub` an event (a trigger) that gets picked up by the `Fledex.Animation.Animator`s to change their animation. The trigger can not only be created by the driver, but also by external events. The difference is that only the triggers from the driver are used by the `Fledex.Animation.Animator`s to redefine the animation. All other triggers are collected over time and can be used when updating the animation.

For example: we want to be able to check the current temperature in regular interval and trigger a re-animation to adjust the "display" depending on the temperature.

# Final Notes
## Naming
The led naming, the namespace naming, and the `LedsDriver` server naming should all be driven by the `Fledex` DSL. Even though it would be possible to have different names it helps to align them. The `led_strip` name becomes the `LedsDriver` server name. The `animation` name becomes the namespace name and combined with the `led_strip` name the `LedAnimator` server name.
The led naming, the namespace naming, and the `LedsDriver` server naming should all be driven by the `Fledex` DSL. Even though it would be possible to have different names it helps to align them. The `led_strip` name becomes the `LedsDriver` server name. The `animation` name becomes the namespace name and combined with the `led_strip` name the `Fledex.Animation.Animator` server name.
2 changes: 1 addition & 1 deletion lib/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ defmodule Fledex.Application do
{Phoenix.PubSub, [name: :fledex, adapter_name: :pg2]}
]
{:ok, _pid} = Supervisor.start_link(children, strategy: :one_for_one)
# Fledex.Animation.LedAnimationManager.run
# Fledex.Animation.Manager.run
{:ok, self()}
end
end
24 changes: 12 additions & 12 deletions lib/fledex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ defmodule Fledex do
```
"""

alias Fledex.Animation.BaseAnimation
alias Fledex.Animation.LedAnimationManager
alias Fledex.Animation.LedAnimator
alias Fledex.Animation.Animator
alias Fledex.Animation.Base
alias Fledex.Animation.Manager

# configuration for the different macros/functions that can be used to configure our strip
# this is also used to configure our LedAnimationManager to resolve the type to a module
# this is also used to configure our Manager to resolve the type to a module
@config %{
animation: LedAnimator,
static: LedAnimator,
component: LedAnimator # This is not the correct one yet
animation: Animator,
static: Animator,
component: Animator # This is not the correct one yet
}
@config_keys Map.keys @config

Expand All @@ -47,7 +47,7 @@ defmodule Fledex do
import Fledex.Color.Names
# let's start our animation manager. The manager makes sure only one will be started
if not Keyword.get(opts, :dont_start, false) do
LedAnimationManager.start_link(fledex_config())
Manager.start_link(fledex_config())
end
end
end
Expand All @@ -59,7 +59,7 @@ defmodule Fledex do
"""
defmacro animation(name, options \\ [], do: block) do
def_func_ast = {:fn, [], block}
send_config = options[:send_config] || &BaseAnimation.default_send_config_func/1
send_config = options[:send_config] || &Base.default_send_config_func/1
# Logger.warning(inspect block)
quote do
{
Expand All @@ -81,7 +81,7 @@ defmodule Fledex do
"""
defmacro static(name, options \\ [], do: block) do
def_func_ast = {:fn, [], [{:->, [], [[{:_triggers, [], Elixir}], block]}]}
send_config = options[:send_config] || &BaseAnimation.default_send_config_func/1
send_config = options[:send_config] || &Base.default_send_config_func/1
quote do
{
unquote(name),
Expand Down Expand Up @@ -113,8 +113,8 @@ defmodule Fledex do
quote do
strip_name = unquote(strip_name)
strip_options = unquote(strip_options)
LedAnimationManager.register_strip(strip_name, strip_options)
LedAnimationManager.register_animations(strip_name, Map.new(unquote(configs_ast)))
Manager.register_strip(strip_name, strip_options)
Manager.register_animations(strip_name, Map.new(unquote(configs_ast)))
end
# |> tap(& IO.puts Code.format_string! Macro.to_string &1)
end
Expand Down
65 changes: 65 additions & 0 deletions lib/fledex/animation/base.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
defmodule Fledex.Animation.Base do

alias Fledex.Animation.Interface
alias Fledex.Leds

defmacro __using__(opts) do
quote location: :keep, bind_quoted: [opts: opts] do
use GenServer, opts
@behaviour Fledex.Animation.Interface

alias Fledex.Animation.Base
alias Fledex.Animation.Interface

# client side
@doc false
@spec start_link(config :: config_t, strip_name::atom, animation_name::atom) :: GenServer.on_start()
def start_link(config, strip_name, animation_name) do
{:ok, _pid} = GenServer.start_link(__MODULE__, {config, strip_name, animation_name},
name: Interface.build_animator_name(strip_name, animation_name))
end

@doc false
@spec config(atom, atom, config_t) :: :ok
def config(strip_name, animation_name, config) do
GenServer.cast(Interface.build_animator_name(strip_name, animation_name), {:config, config})
end

@doc false
@spec get_info(strip_name :: atom, animation_name :: atom) :: {:ok, any}
def get_info(strip_name, animation_name) do
GenServer.call(Interface.build_animator_name(strip_name, animation_name), :info)
end

@doc false
@spec shutdown(atom, atom) :: :ok
def shutdown(strip_name, animation_name) do
GenServer.stop(Interface.build_animator_name(strip_name, animation_name), :normal)
end

defoverridable start_link: 3, config: 3, get_info: 2, shutdown: 2

# server side
@impl GenServer
@spec handle_call(:info, {pid, any}, state_t) :: {:reply, {:ok, map}, state_t}
def handle_call(:info, _from, state) do
{:reply, {:ok, state}, state}
end

defoverridable handle_call: 3
end
end

@doc false
@default_leds Leds.leds()
@spec default_def_func(map) :: Leds.t()
def default_def_func(_triggers) do
@default_leds
end

@doc false
@spec default_send_config_func(map) :: %{}
def default_send_config_func(_triggers) do
%{}
end
end
114 changes: 0 additions & 114 deletions lib/fledex/animation/base_animation.ex

This file was deleted.

55 changes: 55 additions & 0 deletions lib/fledex/animation/interface.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
defmodule Fledex.Animation.Interface do
@moduledoc """
The behaviour for animations.
This behaviour is the interface expected by the `Fledex.Animation.Manager`
and should be implemented as a GenServer.
If you implement an animation you will have to implement those functions
but you can use `Fledex.Animation.Base` to assist you.
"""

@doc """
Create a new animation (with a given name and configuration) for the led strip
with the specified name.
"""
@callback start_link(config :: any, strip_name :: atom, animation_name :: atom) :: GenServer.on_start()
@doc """
(Re-)Configure this animation. You will have to implement this function on server side.
This will look something like the following:
```elixir
@spec handle_cast({:config, config_t}, state_t) :: {:noreply, state_t}
def handle_cast({:config, config}, state) do
# do something here
{:noreply, state}
end
```
"""
@callback config(strip_name :: atom, animation_name :: atom, config :: map) :: :ok
@doc """
Retrieve infromation about this animation. This is mainly useful when running some
tests. By default this function will return the animation state. This is one of the
few functions that does not need to be implemented
"""
@callback get_info(strip_name :: atom, animation_name :: atom) :: any
@doc """
When the animation is no long required, this function should be called. This will
call (by default) GenServer.stop. The animation can implement the `terminate/2`
function if necessary.
"""
@callback shutdown(strip_name :: atom, animation_name :: atom) :: :ok

@doc """
This utility function will create an atomic name for the combination of strip name and
animation name. This is used to name the animator. It is important that we do
have a naming convention, because we would otherwise have a hard time to shutdown
animators that have been removed. We do not keep a reference, but only a config
Therefore the animator needs to adhere to this naming convention to properly be shut down.
It is the responsibility of the Animator to set the servername correctly. The
`Fledex.Animation.Base` is doing this by default.
"""
@spec build_animator_name(atom, atom) :: atom
def build_animator_name(strip_name, animation_name)
when is_atom(strip_name) and is_atom(animation_name) do
String.to_atom("#{strip_name}_#{animation_name}")
end
end
Loading

0 comments on commit 30285b5

Please sign in to comment.