mirror of
https://github.com/fly-apps/live_beats.git
synced 2024-11-21 23:50:59 +00:00
Optimize presence.
Avoid fetching each user by passing in full pre-fetched presences from Presence.fetch/2 callback. Use temporary assigns in ProfileLive to avoid duping presences in memeory. Handle removes by a small hook event
This commit is contained in:
parent
a65c789748
commit
9998e06caa
6 changed files with 56 additions and 41 deletions
|
@ -195,6 +195,7 @@ window.addEventListener("phx:page-loading-stop", info => topbar.hide())
|
||||||
window.addEventListener("phx:page-loading-stop", routeUpdated)
|
window.addEventListener("phx:page-loading-stop", routeUpdated)
|
||||||
|
|
||||||
window.addEventListener("js:exec", e => e.target[e.detail.call](...e.detail.args))
|
window.addEventListener("js:exec", e => e.target[e.detail.call](...e.detail.args))
|
||||||
|
window.addEventListener("phx:remove-el", e => document.getElementById(e.detail.id).remove())
|
||||||
|
|
||||||
// connect if there are any LiveViews on the page
|
// connect if there are any LiveViews on the page
|
||||||
liveSocket.getSocket().onOpen(() => execJS("#connection-status", "js-hide"))
|
liveSocket.getSocket().onOpen(() => execJS("#connection-status", "js-hide"))
|
||||||
|
|
|
@ -2,8 +2,10 @@ defmodule Phoenix.Presence.Client do
|
||||||
use GenServer
|
use GenServer
|
||||||
|
|
||||||
@callback init(state :: term) :: {:ok, new_state :: term}
|
@callback init(state :: term) :: {:ok, new_state :: term}
|
||||||
@callback handle_join(topic :: String.t(), key :: String.t(), meta :: [map()], state :: term) :: {:ok, term}
|
@callback handle_join(topic :: String.t(), key :: String.t(), meta :: [map()], state :: term) ::
|
||||||
@callback handle_leave(topic :: String.t(), key :: String.t(), meta :: [map()], state :: term) :: {:ok, term}
|
{:ok, term}
|
||||||
|
@callback handle_leave(topic :: String.t(), key :: String.t(), meta :: [map()], state :: term) ::
|
||||||
|
{:ok, term}
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
TODO
|
TODO
|
||||||
|
@ -19,7 +21,8 @@ defmodule Phoenix.Presence.Client do
|
||||||
{:ok, name} ->
|
{:ok, name} ->
|
||||||
GenServer.start_link(__MODULE__, opts, name: name)
|
GenServer.start_link(__MODULE__, opts, name: name)
|
||||||
|
|
||||||
:error -> GenServer.start_link(__MODULE__, opts)
|
:error ->
|
||||||
|
GenServer.start_link(__MODULE__, opts)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -113,7 +116,9 @@ defmodule Phoenix.Presence.Client do
|
||||||
updated_state =
|
updated_state =
|
||||||
update_topics_state(:add_new_presence_or_metas, state, topic, joined_key, joined_meta)
|
update_topics_state(:add_new_presence_or_metas, state, topic, joined_key, joined_meta)
|
||||||
|
|
||||||
{:ok, updated_client_state} = state.client.handle_join(topic, joined_key, joined_meta, state.client_state)
|
{:ok, updated_client_state} =
|
||||||
|
state.client.handle_join(topic, joined_key, meta, state.client_state)
|
||||||
|
|
||||||
updated_state = Map.put(updated_state, :client_state, updated_client_state)
|
updated_state = Map.put(updated_state, :client_state, updated_client_state)
|
||||||
|
|
||||||
{updated_state, topic}
|
{updated_state, topic}
|
||||||
|
@ -122,7 +127,9 @@ defmodule Phoenix.Presence.Client do
|
||||||
defp handle_leave({left_key, meta}, {state, topic}) do
|
defp handle_leave({left_key, meta}, {state, topic}) do
|
||||||
updated_state = update_topics_state(:remove_presence_or_metas, state, topic, left_key, meta)
|
updated_state = update_topics_state(:remove_presence_or_metas, state, topic, left_key, meta)
|
||||||
|
|
||||||
{:ok, updated_client_state} = state.client.handle_leave(topic, left_key, meta, state.client_state)
|
{:ok, updated_client_state} =
|
||||||
|
state.client.handle_leave(topic, left_key, meta, state.client_state)
|
||||||
|
|
||||||
updated_state = Map.put(updated_state, :client_state, updated_client_state)
|
updated_state = Map.put(updated_state, :client_state, updated_client_state)
|
||||||
|
|
||||||
{updated_state, topic}
|
{updated_state, topic}
|
||||||
|
|
|
@ -15,25 +15,34 @@ defmodule LiveBeats.PresenceClient do
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl Phoenix.Presence.Client
|
@impl Phoenix.Presence.Client
|
||||||
def handle_join(topic, key, _meta, state) do
|
def handle_join(topic, _key, presence, state) do
|
||||||
active_users_topic =
|
active_users_topic =
|
||||||
topic
|
topic
|
||||||
|> profile_identifier()
|
|> profile_identifier()
|
||||||
|> active_users_topic()
|
|> active_users_topic()
|
||||||
|
|
||||||
Phoenix.PubSub.local_broadcast(@pubsub, active_users_topic, {__MODULE__, %{user_joined: key}})
|
Phoenix.PubSub.local_broadcast(
|
||||||
|
@pubsub,
|
||||||
|
active_users_topic,
|
||||||
|
{__MODULE__, %{user_joined: presence}}
|
||||||
|
)
|
||||||
|
|
||||||
{:ok, state}
|
{:ok, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl Phoenix.Presence.Client
|
@impl Phoenix.Presence.Client
|
||||||
def handle_leave(topic, key, _meta, state) do
|
def handle_leave(topic, _key, presence, state) do
|
||||||
active_users_topic =
|
active_users_topic =
|
||||||
topic
|
topic
|
||||||
|> profile_identifier()
|
|> profile_identifier()
|
||||||
|> active_users_topic()
|
|> active_users_topic()
|
||||||
|
|
||||||
Phoenix.PubSub.local_broadcast(@pubsub, active_users_topic, {__MODULE__, %{user_left: key}})
|
Phoenix.PubSub.local_broadcast(
|
||||||
|
@pubsub,
|
||||||
|
active_users_topic,
|
||||||
|
{__MODULE__, %{user_left: presence}}
|
||||||
|
)
|
||||||
|
|
||||||
{:ok, state}
|
{:ok, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,9 @@ defmodule LiveBeatsWeb.Presence do
|
||||||
See the [`Phoenix.Presence`](http://hexdocs.pm/phoenix/Phoenix.Presence.html)
|
See the [`Phoenix.Presence`](http://hexdocs.pm/phoenix/Phoenix.Presence.html)
|
||||||
docs for more details.
|
docs for more details.
|
||||||
"""
|
"""
|
||||||
use Phoenix.Presence, otp_app: :live_beats,
|
use Phoenix.Presence,
|
||||||
pubsub_server: LiveBeats.PubSub
|
otp_app: :live_beats,
|
||||||
|
pubsub_server: LiveBeats.PubSub
|
||||||
|
|
||||||
import Phoenix.LiveView.Helpers
|
import Phoenix.LiveView.Helpers
|
||||||
import LiveBeatsWeb.LiveHelpers
|
import LiveBeatsWeb.LiveHelpers
|
||||||
|
@ -18,10 +19,16 @@ defmodule LiveBeatsWeb.Presence do
|
||||||
~H"""
|
~H"""
|
||||||
<!-- users -->
|
<!-- users -->
|
||||||
<div class="px-4 mt-6 sm:px-6 lg:px-8">
|
<div class="px-4 mt-6 sm:px-6 lg:px-8">
|
||||||
<h2 class="text-gray-500 text-xs font-medium uppercase tracking-wide">Who's Listening</h2>
|
<h2 class="text-gray-500 text-xs font-medium uppercase tracking-wide">Here now</h2>
|
||||||
<ul role="list" class="grid grid-cols-1 gap-4 sm:gap-4 sm:grid-cols-2 xl:grid-cols-5 mt-3" x-max="1">
|
<ul
|
||||||
|
id="listening-now"
|
||||||
|
phx-update="prepend"
|
||||||
|
role="list"
|
||||||
|
x-max="1"
|
||||||
|
class="grid grid-cols-1 gap-4 sm:gap-4 sm:grid-cols-2 xl:grid-cols-5 mt-3"
|
||||||
|
>
|
||||||
<%= for presence <- @presences do %>
|
<%= for presence <- @presences do %>
|
||||||
<li class="relative col-span-1 flex shadow-sm rounded-md overflow-hidden">
|
<li id={"presence-#{presence.id}"} class="relative col-span-1 flex shadow-sm rounded-md overflow-hidden">
|
||||||
<.link navigate={profile_path(presence)} class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md truncate">
|
<.link navigate={profile_path(presence)} class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md truncate">
|
||||||
<img class="w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-l-md bg-purple-600" src={presence.avatar_url} alt="">
|
<img class="w-10 h-10 flex-shrink-0 flex items-center justify-center rounded-l-md bg-purple-600" src={presence.avatar_url} alt="">
|
||||||
<div class="flex-1 flex items-center justify-between text-gray-900 text-sm font-medium hover:text-gray-600 pl-3">
|
<div class="flex-1 flex items-center justify-between text-gray-900 text-sm font-medium hover:text-gray-600 pl-3">
|
||||||
|
|
|
@ -88,7 +88,9 @@ defmodule LiveBeatsWeb.ProfileLive do
|
||||||
MediaLibrary.subscribe_to_profile(profile)
|
MediaLibrary.subscribe_to_profile(profile)
|
||||||
Accounts.subscribe(current_user.id)
|
Accounts.subscribe(current_user.id)
|
||||||
LiveBeatsWeb.Presence.subscribe(profile)
|
LiveBeatsWeb.Presence.subscribe(profile)
|
||||||
Phoenix.Presence.Client.track(topic(profile.user_id),
|
|
||||||
|
Phoenix.Presence.Client.track(
|
||||||
|
topic(profile.user_id),
|
||||||
current_user.id,
|
current_user.id,
|
||||||
%{}
|
%{}
|
||||||
)
|
)
|
||||||
|
@ -111,7 +113,7 @@ defmodule LiveBeatsWeb.ProfileLive do
|
||||||
|> list_songs()
|
|> list_songs()
|
||||||
|> assign_presences()
|
|> assign_presences()
|
||||||
|
|
||||||
{:ok, socket, temporary_assigns: [songs: []]}
|
{:ok, socket, temporary_assigns: [songs: [], presences: []]}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_params(params, _url, socket) do
|
def handle_params(params, _url, socket) do
|
||||||
|
@ -147,21 +149,14 @@ defmodule LiveBeatsWeb.ProfileLive do
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({LiveBeats.PresenceClient, %{user_joined: user_id}}, socket) do
|
def handle_info({LiveBeats.PresenceClient, %{user_joined: presence}}, socket) do
|
||||||
new_user = Accounts.get_user!(user_id)
|
%{user: user} = presence
|
||||||
updated_presences =
|
{:noreply, update(socket, :presences, &[user | &1])}
|
||||||
if new_user in socket.assigns.presences do
|
|
||||||
socket.assigns.presences
|
|
||||||
else
|
|
||||||
[new_user | socket.assigns.presences]
|
|
||||||
end
|
|
||||||
{:noreply, assign(socket, :presences, updated_presences)}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({LiveBeats.PresenceClient, %{user_left: user_id}}, socket) do
|
def handle_info({LiveBeats.PresenceClient, %{user_left: presence}}, socket) do
|
||||||
updated_presences = socket.assigns.presences
|
%{user: user} = presence
|
||||||
|> Enum.reject(fn user -> user.id == String.to_integer(user_id) end)
|
{:noreply, push_event(socket, "remove-el", %{id: "presence-#{user.id}"})}
|
||||||
{:noreply, assign(socket, :presences, updated_presences)}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({Accounts, %Accounts.Events.ActiveProfileChanged{} = event}, socket) do
|
def handle_info({Accounts, %Accounts.Events.ActiveProfileChanged{} = event}, socket) do
|
||||||
|
@ -264,10 +259,11 @@ defmodule LiveBeatsWeb.ProfileLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp assign_presences(socket) do
|
defp assign_presences(socket) do
|
||||||
presences = socket.assigns.profile.user_id
|
presences =
|
||||||
|> topic()
|
socket.assigns.profile.user_id
|
||||||
|> LiveBeats.PresenceClient.list()
|
|> topic()
|
||||||
|> Enum.map(fn {_key, meta} -> meta.user end)
|
|> LiveBeats.PresenceClient.list()
|
||||||
|
|> Enum.map(fn {_key, meta} -> meta.user end)
|
||||||
|
|
||||||
assign(socket, presences: presences)
|
assign(socket, presences: presences)
|
||||||
end
|
end
|
||||||
|
|
|
@ -51,18 +51,13 @@ defmodule LiveBeatsWeb.ProfileLiveTest do
|
||||||
}
|
}
|
||||||
}) =~ "can't be blank"
|
}) =~ "can't be blank"
|
||||||
|
|
||||||
assert {:ok, new_lv, html} =
|
assert lv |> form("#song-form") |> render_submit() =~ "silence1s"
|
||||||
lv |> form("#song-form") |> render_submit() |> follow_redirect(conn)
|
assert_patch(lv, "/#{current_user.username}")
|
||||||
|
|
||||||
assert_redirected(lv, "/#{current_user.username}")
|
|
||||||
assert html =~ "1 song(s) uploaded"
|
|
||||||
|
|
||||||
assert html =~ "silence1s"
|
|
||||||
|
|
||||||
# deleting songs
|
# deleting songs
|
||||||
|
|
||||||
song = MediaLibrary.get_first_song(profile)
|
song = MediaLibrary.get_first_song(profile)
|
||||||
assert new_lv |> element("#delete-modal-#{song.id}-confirm") |> render_click()
|
assert lv |> element("#delete-modal-#{song.id}-confirm") |> render_click()
|
||||||
|
|
||||||
{:ok, refreshed_lv, _} = live(conn, LiveHelpers.profile_path(current_user))
|
{:ok, refreshed_lv, _} = live(conn, LiveHelpers.profile_path(current_user))
|
||||||
refute render(refreshed_lv) =~ "silence1s"
|
refute render(refreshed_lv) =~ "silence1s"
|
||||||
|
|
Loading…
Reference in a new issue