diff --git a/fluxer_gateway/src/guild/guild_member_list_subs.erl b/fluxer_gateway/src/guild/guild_member_list_subs.erl index dd04a98f8..26f5a1522 100644 --- a/fluxer_gateway/src/guild/guild_member_list_subs.erl +++ b/fluxer_gateway/src/guild/guild_member_list_subs.erl @@ -14,10 +14,12 @@ retain_only_session_list/3, unsubscribe_session/2, get_list_subs/2, + has_subs/2, get_session_ranges/3, list_ids/1, fold_list_subs/4, fold_lists/3, + fold_list_ids/3, foreach_list/2, is_subscribed/3 ]). @@ -121,6 +123,13 @@ lookup_list(Tab, Key) -> get_list_subs(ListId, Tab) -> collect_list(Tab, ets:next(Tab, {ListId, <<>>}), ListId, #{}). +-spec has_subs(binary(), ets:table()) -> boolean(). +has_subs(ListId, Tab) -> + case ets:next(Tab, {ListId, <<>>}) of + {ListId, SessionId} when is_binary(SessionId) -> true; + _ -> false + end. + -spec get_session_ranges(binary(), binary(), ets:table()) -> [range()]. get_session_ranges(SessionId, ListId, Tab) -> case ets:lookup(Tab, {ListId, SessionId}) of @@ -142,6 +151,10 @@ fold_list_subs(ListId, Tab, Fun, Acc0) -> fold_lists(Fun, Acc0, Tab) -> fold_lists_loop(Fun, Acc0, Tab, ets:next(Tab, {<<>>, <<>>})). +-spec fold_list_ids(fun((binary(), Acc) -> Acc), Acc, ets:table()) -> Acc when Acc :: term(). +fold_list_ids(Fun, Acc0, Tab) -> + fold_list_ids_loop(Fun, Acc0, Tab, ets:next(Tab, {<<>>, <<>>})). + -spec foreach_list(fun((binary(), list_subs()) -> term()), ets:table()) -> ok. foreach_list(Fun, Tab) -> _ = fold_lists( @@ -201,6 +214,17 @@ fold_lists_loop(Fun, Acc, Tab, {ListId, _} = Key) when is_binary(ListId) -> NextListKey = skip_past_list(Tab, Key, ListId), fold_lists_loop(Fun, Acc2, Tab, NextListKey). +-spec fold_list_ids_loop(fun((binary(), Acc) -> Acc), Acc, ets:table(), term()) -> Acc when + Acc :: term(). +fold_list_ids_loop(_Fun, Acc, _Tab, '$end_of_table') -> + Acc; +fold_list_ids_loop(_Fun, Acc, _Tab, {idx_tab, _}) -> + Acc; +fold_list_ids_loop(Fun, Acc, Tab, {ListId, _} = Key) when is_binary(ListId) -> + Acc2 = Fun(ListId, Acc), + NextListKey = skip_past_list(Tab, Key, ListId), + fold_list_ids_loop(Fun, Acc2, Tab, NextListKey). + -spec skip_past_list(ets:table(), term(), binary()) -> term(). skip_past_list(Tab, Key, ListId) -> case ets:next(Tab, Key) of diff --git a/fluxer_gateway/src/guild/guild_member_list_write.erl b/fluxer_gateway/src/guild/guild_member_list_write.erl index cbfdeb15f..1dfdc0a46 100644 --- a/fluxer_gateway/src/guild/guild_member_list_write.erl +++ b/fluxer_gateway/src/guild/guild_member_list_write.erl @@ -180,10 +180,10 @@ broadcast_channel_engine_connection_change(UserId, State, SubsTab) -> guild_state(). fold_connection_change_lists(GuildId, UserId, State, SubsTab) -> Sessions = maps:get(sessions, State, #{}), - guild_member_list_subs:fold_lists( - fun(ListId, ListSubs, AccState) -> + guild_member_list_subs:fold_list_ids( + fun(ListId, AccState) -> sync_connection_change_for_list( - GuildId, UserId, ListId, ListSubs, Sessions, AccState + GuildId, UserId, ListId, SubsTab, Sessions, AccState ) end, State, @@ -200,10 +200,10 @@ fold_connection_change_lists(GuildId, UserId, State, SubsTab) -> dispatch_user_change_to_subscribed_lists( UserId, OldMember, NewMember, SubsTab, State ) -> - guild_member_list_subs:fold_lists( - fun(ListId, ListSubs, AccState) -> + guild_member_list_subs:fold_list_ids( + fun(ListId, AccState) -> dispatch_user_change_to_list( - UserId, OldMember, NewMember, ListId, ListSubs, AccState + UserId, OldMember, NewMember, ListId, SubsTab, AccState ) end, State, @@ -215,14 +215,14 @@ dispatch_user_change_to_subscribed_lists( map() | undefined, map() | undefined, list_id(), - guild_member_list_subs:list_subs(), + ets:table(), guild_state() ) -> guild_state(). dispatch_user_change_to_list( - UserId, OldMember, NewMember, ListId, ListSubs, State + UserId, OldMember, NewMember, ListId, SubsTab, State ) -> State1 = apply_user_change_to_channel_store(UserId, ListId, OldMember, NewMember, State), - queue_list_sync_if_subscribed(ListId, ListSubs, State1). + queue_list_sync_if_subscribed(ListId, SubsTab, State1). -spec apply_user_change_to_channel_store( user_id(), list_id(), map() | undefined, map() | undefined, guild_state() @@ -253,14 +253,14 @@ apply_channel_member_change(_UserId, _ListId, _OldMember, _NewMember, _State) -> integer(), user_id(), list_id(), - guild_member_list_subs:list_subs(), + ets:table(), map(), guild_state() ) -> guild_state(). -sync_connection_change_for_list(_GuildId, _UserId, ListId, ListSubs, _Sessions, State) -> +sync_connection_change_for_list(_GuildId, _UserId, ListId, SubsTab, _Sessions, State) -> case guild_member_list_channel_engine:is_engine_list(ListId, State) of true -> - queue_list_sync_if_subscribed(ListId, ListSubs, State); + queue_list_sync_if_subscribed(ListId, SubsTab, State); false -> State end. @@ -295,13 +295,11 @@ rebuild_channel_store(ListId, State) -> false -> State end. --spec queue_list_sync_if_subscribed( - list_id(), guild_member_list_subs:list_subs(), guild_state() -) -> guild_state(). -queue_list_sync_if_subscribed(ListId, ListSubs, State) -> - case map_size(ListSubs) of - 0 -> State; - _ -> guild_member_list_sync_batch:queue_list_sync(ListId, State) +-spec queue_list_sync_if_subscribed(list_id(), ets:table(), guild_state()) -> guild_state(). +queue_list_sync_if_subscribed(ListId, SubsTab, State) -> + case guild_member_list_subs:has_subs(ListId, SubsTab) of + false -> State; + true -> guild_member_list_sync_batch:queue_list_sync(ListId, State) end. -spec flush_pending_member_list_syncs(guild_state()) -> guild_state(). @@ -319,9 +317,11 @@ apply_user_change_to_channel_store_updates_engine_test() -> ?assert(maps:is_key(<<"123">>, maps:get(channel_member_list_engines, State1))). sync_connection_change_ignores_zero_list_test() -> + SubsTab = guild_member_list_subs:new(), ?assertEqual( #{}, - sync_connection_change_for_list(1, 2, <<"0">>, #{}, #{}, #{}) - ). + sync_connection_change_for_list(1, 2, <<"0">>, SubsTab, #{}, #{}) + ), + guild_member_list_subs:destroy(SubsTab). -endif. diff --git a/fluxer_gateway/test/guild_member_list_tests.erl b/fluxer_gateway/test/guild_member_list_tests.erl index b6fdaf85b..6f9f256da 100644 --- a/fluxer_gateway/test/guild_member_list_tests.erl +++ b/fluxer_gateway/test/guild_member_list_tests.erl @@ -406,6 +406,52 @@ presence_map(Status, Mobile, CustomStatus) -> <<"custom_status">> => CustomStatus }. +has_subs_reports_subscribers_per_list_test() -> + SubsTab = make_subs_tab([ + {<<"10">>, <<"s0">>, [{0, 99}]}, + {<<"100">>, <<"s1">>, [{0, 99}]}, + {<<"100">>, <<"s2">>, [{0, 49}]}, + {<<"300">>, <<"s3">>, [{0, 99}]} + ]), + ?assert(guild_member_list_subs:has_subs(<<"10">>, SubsTab)), + ?assert(guild_member_list_subs:has_subs(<<"100">>, SubsTab)), + ?assert(guild_member_list_subs:has_subs(<<"300">>, SubsTab)), + ?assertNot(guild_member_list_subs:has_subs(<<"1">>, SubsTab)), + ?assertNot(guild_member_list_subs:has_subs(<<"050">>, SubsTab)), + ?assertNot(guild_member_list_subs:has_subs(<<"200">>, SubsTab)), + ?assertNot(guild_member_list_subs:has_subs(<<"400">>, SubsTab)), + guild_member_list_subs:unsubscribe_session(<<"s3">>, SubsTab), + ?assertNot(guild_member_list_subs:has_subs(<<"300">>, SubsTab)), + guild_member_list_subs:unsubscribe_session(<<"s0">>, SubsTab), + ?assertNot(guild_member_list_subs:has_subs(<<"10">>, SubsTab)), + ?assert(guild_member_list_subs:has_subs(<<"100">>, SubsTab)), + guild_member_list_subs:destroy(SubsTab). + +has_subs_on_empty_table_test() -> + SubsTab = make_subs_tab(), + ?assertNot(guild_member_list_subs:has_subs(<<"500">>, SubsTab)), + ?assertEqual([], collect_list_ids(SubsTab)), + guild_member_list_subs:destroy(SubsTab). + +fold_list_ids_visits_each_list_once_test() -> + SubsTab = make_subs_tab([ + {<<"10">>, <<"s0">>, [{0, 99}]}, + {<<"100">>, <<"s1">>, [{0, 99}]}, + {<<"100">>, <<"s2">>, [{0, 49}]}, + {<<"300">>, <<"s3">>, [{0, 99}]} + ]), + ListIds = collect_list_ids(SubsTab), + ?assertEqual([<<"10">>, <<"100">>, <<"300">>], ListIds), + ?assertEqual(guild_member_list_subs:list_ids(SubsTab), ListIds), + guild_member_list_subs:destroy(SubsTab). + +collect_list_ids(SubsTab) -> + lists:reverse( + guild_member_list_subs:fold_list_ids( + fun(ListId, Acc) -> [ListId | Acc] end, [], SubsTab + ) + ). + channel_list_state(Ref, SubsTab, Members) -> (base_state(SubsTab))#{ data => #{