Packages
mob
0.6.24
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.26
0.6.25
0.6.24
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.2
0.6.1
0.6.0
0.5.18
0.5.17
0.5.16
0.5.15
0.5.14
0.5.11
0.5.10
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
0.1.0
BEAM-on-device mobile framework for Elixir
Current section
Files
Jump to
Current section
Files
lib/mob/component_registry.ex
defmodule Mob.ComponentRegistry do
@moduledoc false
# ETS-backed registry mapping {screen_pid, id, module} → component_pid.
# Started by Mob.App. Components register themselves on mount and deregister
# on terminate. The screen calls reconcile/2 after each render to stop
# components that have left the tree.
use GenServer
@table __MODULE__
@spec start_link(keyword()) :: GenServer.on_start()
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@doc """
Register a component process. Raises if the same {screen_pid, id, module}
is already registered (duplicate id on the same screen).
"""
@spec register(pid(), atom(), module(), pid()) :: :ok
def register(screen_pid, id, module, component_pid) do
key = {screen_pid, id, module}
case :ets.lookup(@table, key) do
[{^key, existing}] when existing != component_pid ->
raise ArgumentError,
"Mob.Component: duplicate id #{inspect(id)} for #{inspect(module)} on screen " <>
"#{inspect(screen_pid)}. Component ids must be unique per screen."
_ ->
:ets.insert(@table, {key, component_pid})
:ets.insert(@table, {component_pid, key})
:ok
end
end
@doc "Look up the component pid for a given key."
@spec lookup(pid(), atom(), module()) :: {:ok, pid()} | {:error, :not_found}
def lookup(screen_pid, id, module) do
case :ets.lookup(@table, {screen_pid, id, module}) do
[{_, pid}] -> {:ok, pid}
[] -> {:error, :not_found}
end
end
@doc "Remove a component registration (called from ComponentServer.terminate)."
@spec deregister(pid(), atom(), module()) :: :ok
def deregister(screen_pid, id, module) do
key = {screen_pid, id, module}
case :ets.lookup(@table, key) do
[{_, pid}] ->
:ets.delete(@table, key)
:ets.delete(@table, pid)
[] ->
:ok
end
:ok
end
@doc """
Stop any component processes for `screen_pid` whose {id, module} key is NOT
in `active_keys`. Called after each render to reap components that left the tree.
"""
@spec reconcile(pid(), MapSet.t()) :: :ok
def reconcile(screen_pid, active_keys) do
pattern = {{screen_pid, :_, :_}, :_}
entries = :ets.match_object(@table, pattern)
for {{^screen_pid, id, module}, pid} <- entries do
unless MapSet.member?(active_keys, {id, module}) do
:ets.delete(@table, {screen_pid, id, module})
:ets.delete(@table, pid)
Process.exit(pid, :shutdown)
end
end
:ok
end
# ── GenServer ──────────────────────────────────────────────────────────────
@impl GenServer
def init(_opts) do
:ets.new(@table, [:named_table, :public, read_concurrency: true])
{:ok, nil}
end
end