Packages

Native desktop GUIs from Elixir, powered by iced

Retired package: Renamed - Package has been renamed to plushie: hex.pm/packages/plushie

Current section

Files

Jump to
toddy lib toddy runtime subscriptions.ex
Raw

lib/toddy/runtime/subscriptions.ex

defmodule Toddy.Runtime.Subscriptions do
@moduledoc """
Subscription lifecycle management for the Toddy runtime.
Compares the app's current `subscribe/1` output against the active
subscriptions, starting new ones and stopping removed ones. Timer-based
subscriptions (`:every`) are managed locally; renderer-based subscriptions
(`:on_key_press`, etc.) are forwarded to the bridge.
"""
require Logger
@doc """
Synchronizes subscriptions with the app's current `subscribe/1` output.
Stops subscriptions that are no longer in the spec list, starts new ones,
and preserves unchanged ones. Returns the updated state.
"""
@spec sync_subscriptions(map(), term()) :: map()
def sync_subscriptions(state, new_model) do
new_specs =
try do
case state.app.subscribe(new_model) do
specs when is_list(specs) ->
specs
other ->
Logger.error("toddy runtime: subscribe/1 must return a list, got: #{inspect(other)}")
[]
end
rescue
e ->
Logger.error("""
toddy runtime: subscribe/1 raised: #{Exception.message(e)}
#{Exception.format_stacktrace(__STACKTRACE__)}
""")
[]
end
new_by_key = Map.new(new_specs, fn spec -> {Toddy.Subscription.key(spec), spec} end)
old_key_set = state.subscriptions |> Map.keys() |> MapSet.new()
new_key_set = new_by_key |> Map.keys() |> MapSet.new()
# Stop removed subscriptions
to_stop = MapSet.difference(old_key_set, new_key_set)
Enum.each(to_stop, fn key ->
case Map.get(state.subscriptions, key) do
{:timer, ref} ->
Process.cancel_timer(ref)
{:renderer, type} ->
if state.bridge do
Toddy.Bridge.send_unsubscribe(state.bridge, Atom.to_string(type))
end
_ ->
:ok
end
end)
# Start new subscriptions
to_start = MapSet.difference(new_key_set, old_key_set)
new_entries =
Map.new(to_start, fn key ->
spec = Map.fetch!(new_by_key, key)
{key, start_subscription(spec, state.bridge)}
end)
# Keep existing (unchanged) subscriptions
kept_keys = MapSet.difference(new_key_set, to_start) |> MapSet.to_list()
kept = Map.take(state.subscriptions, kept_keys)
%{state | subscriptions: Map.merge(kept, new_entries)}
end
# -- Private helpers --------------------------------------------------------
defp start_subscription(%{type: :every, interval: interval, tag: tag}, _bridge) do
ref = Process.send_after(self(), {:subscription_tick, tag, interval}, interval)
{:timer, ref}
end
defp start_subscription(%{type: type, tag: tag}, bridge)
when type in [
:on_key_press,
:on_key_release,
:on_window_close,
:on_window_event,
:on_window_open,
:on_window_resize,
:on_window_focus,
:on_window_unfocus,
:on_window_move,
:on_mouse_move,
:on_mouse_button,
:on_mouse_scroll,
:on_touch,
:on_ime,
:on_theme_change,
:on_animation_frame,
:on_file_drop,
:on_event,
:on_modifiers_changed
] do
if bridge do
Toddy.Bridge.send_subscribe(bridge, Atom.to_string(type), Atom.to_string(tag))
end
{:renderer, type}
end
end