perf: optimize map LiveView hot paths

Remove unused 30s timer, skip heatmap recluster on every single packet, trail dedup O(n^2)->O(n)

Ultraworked with Sisyphus

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Graham McIntire 2026-06-01 16:49:08 -05:00
parent ae1194ebb0
commit e02fafc8da
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
3 changed files with 6 additions and 31 deletions

View file

@ -161,7 +161,10 @@ defmodule AprsmeWeb.MapLive.DisplayManager do
defp trail_packets_for_callsign(visible_packets, historical_packets, callsign, threshold) do
(Map.values(visible_packets) ++ Map.values(historical_packets))
|> Enum.uniq_by(&trail_packet_key/1)
|> Enum.reduce(%{}, fn p, acc ->
Map.put_new(acc, trail_packet_key(p), p)
end)
|> Map.values()
|> Enum.filter(&tracked_packet?(&1, callsign, threshold))
|> Enum.sort_by(&packet_timestamp/1, :asc)
end

View file

@ -141,8 +141,6 @@ defmodule AprsmeWeb.MapLive.Index do
_ = Aprsme.StreamingPacketsPubSub.subscribe_to_bounds(self(), default_bounds)
_ = Process.send_after(self(), :cleanup_old_packets, 60_000)
# Schedule UI update timer to refresh "time ago" display every 30 seconds
_ = Process.send_after(self(), :update_time_display, 30_000)
socket
|> assign(:spatial_client_id, client_id)
@ -830,8 +828,6 @@ defmodule AprsmeWeb.MapLive.Index do
def handle_info(:cleanup_old_packets, socket), do: handle_cleanup_old_packets(socket)
def handle_info(:update_time_display, socket), do: handle_update_time_display(socket)
def handle_info(:reload_historical_packets, socket), do: handle_reload_historical_packets(socket)
def handle_info({:postgres_packet, packet}, socket) do
@ -1743,13 +1739,6 @@ defmodule AprsmeWeb.MapLive.Index do
{:noreply, assign(socket, :visible_packets, kept)}
end
defp handle_update_time_display(socket) do
# Schedule next update
Process.send_after(self(), :update_time_display, 30_000)
{:noreply, assign(socket, :time_display_tick, System.monotonic_time())}
end
defp handle_reload_historical_packets(socket) do
require Logger
@ -1808,15 +1797,6 @@ defmodule AprsmeWeb.MapLive.Index do
# Helper functions for marker operations
@spec remove_markers_batch(Socket.t(), list()) :: Socket.t()
defp remove_markers_batch(socket, []), do: socket
defp remove_markers_batch(socket, marker_ids) do
Enum.reduce(marker_ids, socket, fn id, acc ->
push_event(acc, "remove_marker", %{id: id})
end)
end
@spec add_markers_if_any(Socket.t(), list()) :: Socket.t()
defp add_markers_if_any(socket, []), do: socket
@ -2014,7 +1994,7 @@ defmodule AprsmeWeb.MapLive.Index do
socket =
socket
|> remove_markers_batch(packets_to_remove)
|> DisplayManager.remove_markers_batch(packets_to_remove)
|> maybe_clear_historical_packets(bounds_state)
|> push_event("filter_markers_by_bounds", %{bounds: map_bounds})
|> assign(map_bounds: map_bounds, visible_packets: new_visible_packets)

View file

@ -10,7 +10,6 @@ defmodule AprsmeWeb.MapLive.PacketProcessor do
alias AprsmeWeb.Live.Shared.CoordinateUtils
alias AprsmeWeb.Live.Shared.PacketUtils, as: SharedPacketUtils
alias AprsmeWeb.MapLive.DataBuilder
alias AprsmeWeb.MapLive.DisplayManager
alias Phoenix.LiveView
alias Phoenix.LiveView.Socket
@ -108,10 +107,7 @@ defmodule AprsmeWeb.MapLive.PacketProcessor do
render_for_zoom(socket.assigns.map_zoom, marker_data, socket)
end
# Zoom 8 and below use the heatmap instead of individual markers.
defp render_for_zoom(zoom, _marker_data, socket) when zoom <= 8 do
send_heat_map_for_current_bounds(socket)
end
defp render_for_zoom(zoom, _marker_data, socket) when zoom <= 8, do: socket
defp render_for_zoom(_zoom, nil, socket), do: socket
@ -224,10 +220,6 @@ defmodule AprsmeWeb.MapLive.PacketProcessor do
Map.get(socket.assigns, :locale, "en")
end
defp send_heat_map_for_current_bounds(socket) do
DisplayManager.send_heat_map_for_current_bounds(socket)
end
defp send_marker_with_popup_check(socket, marker_data) do
# Strip convert_from — the single-packet JS handler uses its own scan logic.
marker_data = Map.delete(marker_data, "convert_from")