feat: dynamically manage floors/rooms/tags

This commit is contained in:
2026-04-16 16:57:45 +02:00
parent 34ddbe669e
commit c3fd8b950c
11 changed files with 238 additions and 22 deletions
@@ -0,0 +1,43 @@
defmodule Localiser.Localisation.Floor.Manager do
@moduledoc """
Global GenServer that reacts to floor lifecycle events and drives Floor.Supervisor.
- {:floor_created, floor} → start a Floor.Server for the new floor
- {:floor_deleted, floor_id} → terminate the Floor.Server subtree
"""
use GenServer
alias Localiser.Localisation.Floor
def start_link(_args) do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
@impl true
def init(:ok) do
Phoenix.PubSub.subscribe(Localiser.PubSub, "floors")
{:ok, :ok}
end
@impl true
def handle_info({:floor_created, floor}, state) do
case Registry.lookup(Localiser.Registry, {:floor_server, floor.id}) do
[] -> Floor.Supervisor.start_floor_server(floor)
_ -> :ok
end
{:noreply, state}
end
def handle_info({:floor_deleted, floor_id}, state) do
case Registry.lookup(Localiser.Registry, {:floor_server, floor_id}) do
[{pid, _}] -> DynamicSupervisor.terminate_child(Floor.Supervisor, pid)
[] -> :ok
end
{:noreply, state}
end
def handle_info(_msg, state), do: {:noreply, state}
end
@@ -3,6 +3,7 @@ defmodule Localiser.Localisation.Floor.Server do
alias Localiser.Localisation.Room
alias Localiser.Localisation.Sensor
def start_link(floor) do
Supervisor.start_link(__MODULE__, floor, name: via(floor.id))
end
@@ -15,6 +16,7 @@ defmodule Localiser.Localisation.Floor.Server do
def init(floor) do
children = [
{Room.Supervisor, floor},
{Room.Manager, floor},
{Sensor.Supervisor, floor},
{Sensor.Manager, floor}
]
@@ -0,0 +1,47 @@
defmodule Localiser.Localisation.Room.Manager do
@moduledoc """
Per-floor GenServer that reacts to room lifecycle events and drives Room.Supervisor.
- {:room_created, room} → start a Room.Server (if room belongs to this floor)
- {:room_deleted, room_id, floor_id} → terminate the Room.Server (if on this floor)
"""
use GenServer
alias Localiser.Localisation.Room
def start_link(floor) do
GenServer.start_link(__MODULE__, floor.id, name: via(floor.id))
end
def via(floor_id) do
{:via, Registry, {Localiser.Registry, {:room_manager, floor_id}}}
end
@impl true
def init(floor_id) do
Phoenix.PubSub.subscribe(Localiser.PubSub, "rooms")
{:ok, %{floor_id: floor_id}}
end
@impl true
def handle_info({:room_created, %{floor_id: floor_id} = room}, %{floor_id: floor_id} = state) do
case Registry.lookup(Localiser.Registry, {:room, room.id}) do
[] -> Room.Supervisor.start_room_server(floor_id, room)
_ -> :ok
end
{:noreply, state}
end
def handle_info({:room_deleted, room_id, floor_id}, %{floor_id: floor_id} = state) do
case Registry.lookup(Localiser.Registry, {:room, room_id}) do
[{pid, _}] -> DynamicSupervisor.terminate_child(Room.Supervisor.via(floor_id), pid)
[] -> :ok
end
{:noreply, state}
end
def handle_info(_msg, state), do: {:noreply, state}
end
+15
View File
@@ -27,6 +27,8 @@ defmodule Localiser.Localisation.Room.Server do
@impl true
def init(room) do
Phoenix.PubSub.subscribe(@pubsub, "rooms")
state = %__MODULE__{
id: room.id,
name: room.name,
@@ -65,6 +67,19 @@ defmodule Localiser.Localisation.Room.Server do
{:reply, state.occupants, state}
end
@impl true
def handle_info({:room_updated, %{id: id} = room}, %{id: id} = state) do
{:noreply, %{state |
name: room.name,
offset_x: room.offset_x || 0.0,
offset_y: room.offset_y || 0.0,
width: room.width || 0.0,
height: room.height || 0.0
}}
end
def handle_info(_msg, state), do: {:noreply, state}
defp broadcast(room_id, occupants) do
Phoenix.PubSub.broadcast(
@pubsub,
+14
View File
@@ -39,6 +39,8 @@ defmodule Localiser.Localisation.Tag.Filter do
rooms = load_rooms()
{:ok, filter_state} = filter_module.init([], [])
Phoenix.PubSub.subscribe(Localiser.PubSub, "rooms")
state = %__MODULE__{
tag_id: tag.tag_id,
filter_module: filter_module,
@@ -77,6 +79,18 @@ defmodule Localiser.Localisation.Tag.Filter do
end
end
# Room geometry changed — reload the rooms cache so containment checks stay accurate.
@impl true
def handle_info({event, _}, state) when event in [:room_created, :room_updated] do
{:noreply, %{state | rooms: load_rooms()}}
end
def handle_info({:room_deleted, _room_id, _floor_id}, state) do
{:noreply, %{state | rooms: load_rooms()}}
end
def handle_info(_msg, state), do: {:noreply, state}
defp load_rooms do
Floors.list_floors_with_rooms()
|> Enum.flat_map(& &1.rooms)
+43
View File
@@ -0,0 +1,43 @@
defmodule Localiser.Localisation.Tag.Manager do
@moduledoc """
Global GenServer that reacts to tag lifecycle events and drives Filter.Supervisor.
- {:tag_enrolled, tag} → start a Tag.Filter for the new tag
- {:tag_removed, tag_id} → terminate the Tag.Filter
"""
use GenServer
alias Localiser.Localisation.Filter
def start_link(_args) do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
end
@impl true
def init(:ok) do
Phoenix.PubSub.subscribe(Localiser.PubSub, "tags")
{:ok, :ok}
end
@impl true
def handle_info({:tag_enrolled, tag}, state) do
case Registry.lookup(Localiser.Registry, {:filter, tag.tag_id}) do
[] -> Filter.Supervisor.start_tag_filter(tag)
_ -> :ok
end
{:noreply, state}
end
def handle_info({:tag_removed, tag_id}, state) do
case Registry.lookup(Localiser.Registry, {:filter, tag_id}) do
[{pid, _}] -> DynamicSupervisor.terminate_child(Filter.Supervisor, pid)
[] -> :ok
end
{:noreply, state}
end
def handle_info(_msg, state), do: {:noreply, state}
end