mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-02-07 16:13:23 +08:00
Improved caching and performance of the public party browser
This commit is contained in:
parent
82c739f727
commit
e2126fa0f7
@ -1,9 +1,11 @@
|
|||||||
# Released under the MIT License. See LICENSE for details.
|
# Released under the MIT License. See LICENSE for details.
|
||||||
#
|
#
|
||||||
|
# pylint: disable=too-many-lines
|
||||||
"""Defines the public tab in the gather UI."""
|
"""Defines the public tab in the gather UI."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import copy
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
@ -15,9 +17,11 @@ import ba
|
|||||||
from bastd.ui.gather.bases import GatherTab
|
from bastd.ui.gather.bases import GatherTab
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Callable, Any, Optional, Dict, Union, Tuple
|
from typing import Callable, Any, Optional, Dict, Union, Tuple, List
|
||||||
from bastd.ui.gather import GatherWindow
|
from bastd.ui.gather import GatherWindow
|
||||||
|
|
||||||
|
DEBUG_SERVER_COMMUNICATION = False
|
||||||
|
|
||||||
|
|
||||||
class SubTabType(Enum):
|
class SubTabType(Enum):
|
||||||
"""Available sub-tabs."""
|
"""Available sub-tabs."""
|
||||||
@ -25,18 +29,10 @@ class SubTabType(Enum):
|
|||||||
HOST = 'host'
|
HOST = 'host'
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class TabState:
|
|
||||||
"""State saved/restored only while the app is running."""
|
|
||||||
sub_tab: SubTabType = SubTabType.JOIN
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PartyEntry:
|
class PartyEntry:
|
||||||
"""Info about a party."""
|
"""Info about a public party."""
|
||||||
address: str
|
address: str
|
||||||
next_ping_time: float
|
|
||||||
ping: Optional[int]
|
|
||||||
index: int
|
index: int
|
||||||
queue: Optional[str] = None
|
queue: Optional[str] = None
|
||||||
port: int = -1
|
port: int = -1
|
||||||
@ -44,7 +40,11 @@ class PartyEntry:
|
|||||||
size: int = -1
|
size: int = -1
|
||||||
size_max: int = -1
|
size_max: int = -1
|
||||||
claimed: bool = False
|
claimed: bool = False
|
||||||
|
ping: Optional[int] = None
|
||||||
ping_interval: float = -1.0
|
ping_interval: float = -1.0
|
||||||
|
next_ping_time: float = -1.0
|
||||||
|
ping_attempts: int = 0
|
||||||
|
ping_responses: int = 0
|
||||||
stats_addr: Optional[str] = None
|
stats_addr: Optional[str] = None
|
||||||
name_widget: Optional[ba.Widget] = None
|
name_widget: Optional[ba.Widget] = None
|
||||||
ping_widget: Optional[ba.Widget] = None
|
ping_widget: Optional[ba.Widget] = None
|
||||||
@ -52,6 +52,14 @@ class PartyEntry:
|
|||||||
size_widget: Optional[ba.Widget] = None
|
size_widget: Optional[ba.Widget] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class State:
|
||||||
|
"""State saved/restored only while the app is running."""
|
||||||
|
sub_tab: SubTabType = SubTabType.JOIN
|
||||||
|
parties: Optional[List[PartyEntry]] = None
|
||||||
|
next_entry_index: int = 0
|
||||||
|
|
||||||
|
|
||||||
class SelectionComponent(Enum):
|
class SelectionComponent(Enum):
|
||||||
"""Describes what part of an entry is selected."""
|
"""Describes what part of an entry is selected."""
|
||||||
NAME = 'name'
|
NAME = 'name'
|
||||||
@ -187,17 +195,19 @@ class PublicGatherTab(GatherTab):
|
|||||||
self._host_scrollwidget: Optional[ba.Widget] = None
|
self._host_scrollwidget: Optional[ba.Widget] = None
|
||||||
self._host_name_text: Optional[ba.Widget] = None
|
self._host_name_text: Optional[ba.Widget] = None
|
||||||
self._host_toggle_button: Optional[ba.Widget] = None
|
self._host_toggle_button: Optional[ba.Widget] = None
|
||||||
self._join_last_refresh_time = -99999.0
|
self._last_server_list_query_time: Optional[float] = None
|
||||||
self._host_columnwidget: Optional[ba.Widget] = None
|
self._join_list_column: Optional[ba.Widget] = None
|
||||||
self._join_status_text: Optional[ba.Widget] = None
|
self._join_status_text: Optional[ba.Widget] = None
|
||||||
self._host_max_party_size_value: Optional[ba.Widget] = None
|
self._host_max_party_size_value: Optional[ba.Widget] = None
|
||||||
self._host_max_party_size_minus_button: (Optional[ba.Widget]) = None
|
self._host_max_party_size_minus_button: (Optional[ba.Widget]) = None
|
||||||
self._host_max_party_size_plus_button: (Optional[ba.Widget]) = None
|
self._host_max_party_size_plus_button: (Optional[ba.Widget]) = None
|
||||||
self._host_status_text: Optional[ba.Widget] = None
|
self._host_status_text: Optional[ba.Widget] = None
|
||||||
self._public_parties: Dict[str, PartyEntry] = {}
|
self._parties: Dict[str, PartyEntry] = {}
|
||||||
self._last_list_rebuild_time: Optional[float] = None
|
self._last_server_list_update_time: Optional[float] = None
|
||||||
self._first_list_rebuild_time: Optional[float] = None
|
self._first_server_list_rebuild_time: Optional[float] = None
|
||||||
self._next_entry_index = 0
|
self._next_entry_index = 0
|
||||||
|
self._have_valid_server_list = False
|
||||||
|
self._built_join_list = False
|
||||||
|
|
||||||
def on_activate(
|
def on_activate(
|
||||||
self,
|
self,
|
||||||
@ -234,8 +244,6 @@ class PublicGatherTab(GatherTab):
|
|||||||
SubTabType.JOIN,
|
SubTabType.JOIN,
|
||||||
region_width,
|
region_width,
|
||||||
region_height,
|
region_height,
|
||||||
region_left,
|
|
||||||
region_bottom,
|
|
||||||
playsound=True,
|
playsound=True,
|
||||||
),
|
),
|
||||||
text=ba.Lstr(resource='gatherWindow.'
|
text=ba.Lstr(resource='gatherWindow.'
|
||||||
@ -256,8 +264,6 @@ class PublicGatherTab(GatherTab):
|
|||||||
SubTabType.HOST,
|
SubTabType.HOST,
|
||||||
region_width,
|
region_width,
|
||||||
region_height,
|
region_height,
|
||||||
region_left,
|
|
||||||
region_bottom,
|
|
||||||
playsound=True,
|
playsound=True,
|
||||||
),
|
),
|
||||||
text=ba.Lstr(resource='gatherWindow.'
|
text=ba.Lstr(resource='gatherWindow.'
|
||||||
@ -268,48 +274,54 @@ class PublicGatherTab(GatherTab):
|
|||||||
up_widget=tab_button)
|
up_widget=tab_button)
|
||||||
ba.widget(edit=self._join_text, right_widget=self._host_text)
|
ba.widget(edit=self._join_text, right_widget=self._host_text)
|
||||||
|
|
||||||
# Attempt to fetch our local address so we have it for
|
# Attempt to fetch our local address so we have it for error messages.
|
||||||
# error messages.
|
if self._local_address is None:
|
||||||
self._local_address = None
|
AddrFetchThread(ba.WeakCall(self._fetch_local_addr_cb)).start()
|
||||||
|
|
||||||
AddrFetchThread(ba.WeakCall(self._fetch_local_addr_cb)).start()
|
|
||||||
|
|
||||||
assert self._sub_tab is not None
|
assert self._sub_tab is not None
|
||||||
self._set_sub_tab(self._sub_tab, region_width, region_height,
|
self._set_sub_tab(self._sub_tab, region_width, region_height)
|
||||||
region_left, region_bottom)
|
|
||||||
self._update_timer = ba.Timer(0.2,
|
self._update_timer = ba.Timer(0.2,
|
||||||
ba.WeakCall(self._update_sub_tab),
|
ba.WeakCall(self._update),
|
||||||
repeat=True,
|
repeat=True,
|
||||||
timetype=ba.TimeType.REAL)
|
timetype=ba.TimeType.REAL)
|
||||||
|
self._update()
|
||||||
# Also update it immediately so we don't have to wait for the
|
|
||||||
# initial query.
|
|
||||||
self._update_sub_tab()
|
|
||||||
return self._container
|
return self._container
|
||||||
|
|
||||||
def on_deactivate(self) -> None:
|
def on_deactivate(self) -> None:
|
||||||
self._update_timer = None
|
self._update_timer = None
|
||||||
|
|
||||||
def save_state(self) -> None:
|
def save_state(self) -> None:
|
||||||
ba.app.ui.window_states[self.__class__.__name__] = TabState(
|
|
||||||
sub_tab=self._sub_tab)
|
# Save off a small number of parties with the lowest ping; this
|
||||||
|
# should be most of the ones that matter and will keep things
|
||||||
|
# a reasonable size.
|
||||||
|
ba.app.ui.window_states[self.__class__.__name__] = State(
|
||||||
|
sub_tab=self._sub_tab,
|
||||||
|
parties=[copy.copy(p) for p in self._get_ordered_parties()[:20]],
|
||||||
|
next_entry_index=self._next_entry_index)
|
||||||
|
|
||||||
def restore_state(self) -> None:
|
def restore_state(self) -> None:
|
||||||
state = ba.app.ui.window_states.get(self.__class__.__name__)
|
state = ba.app.ui.window_states.get(self.__class__.__name__)
|
||||||
if state is None:
|
if state is None:
|
||||||
state = TabState()
|
state = State()
|
||||||
assert isinstance(state, TabState)
|
assert isinstance(state, State)
|
||||||
self._sub_tab = state.sub_tab
|
self._sub_tab = state.sub_tab
|
||||||
|
|
||||||
|
# Restore the parties we stored...
|
||||||
|
if state.parties:
|
||||||
|
self._parties = {
|
||||||
|
f'{p.address}_{p.port}': copy.copy(p)
|
||||||
|
for p in state.parties
|
||||||
|
}
|
||||||
|
self._next_entry_index = state.next_entry_index
|
||||||
|
self._have_valid_server_list = True
|
||||||
|
|
||||||
def _set_sub_tab(self,
|
def _set_sub_tab(self,
|
||||||
value: SubTabType,
|
value: SubTabType,
|
||||||
region_width: float,
|
region_width: float,
|
||||||
region_height: float,
|
region_height: float,
|
||||||
region_left: float,
|
|
||||||
region_bottom: float,
|
|
||||||
playsound: bool = False) -> None:
|
playsound: bool = False) -> None:
|
||||||
assert self._container
|
assert self._container
|
||||||
del region_left, region_bottom # Unused
|
|
||||||
if playsound:
|
if playsound:
|
||||||
ba.playsound(ba.getsound('click01'))
|
ba.playsound(ba.getsound('click01'))
|
||||||
|
|
||||||
@ -343,6 +355,21 @@ class PublicGatherTab(GatherTab):
|
|||||||
if value is SubTabType.JOIN:
|
if value is SubTabType.JOIN:
|
||||||
self._build_join_tab(v, sub_scroll_width, sub_scroll_height,
|
self._build_join_tab(v, sub_scroll_width, sub_scroll_height,
|
||||||
c_width, c_height)
|
c_width, c_height)
|
||||||
|
self._built_join_list = False
|
||||||
|
|
||||||
|
# If we've not yet successfully fetched a server list,
|
||||||
|
# force an attempt now and show the user a 'loading...' status.
|
||||||
|
if not self._have_valid_server_list:
|
||||||
|
self._last_server_list_query_time = None
|
||||||
|
join_status_str = ba.Lstr(
|
||||||
|
value='${A}...',
|
||||||
|
subs=[('${A}', ba.Lstr(resource='store.loadingText'))],
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Otherwise we've got valid data already. Show it.
|
||||||
|
join_status_str = ba.Lstr(value='')
|
||||||
|
self._update_server_list()
|
||||||
|
ba.textwidget(edit=self._join_status_text, text=join_status_str)
|
||||||
|
|
||||||
if value is SubTabType.HOST:
|
if value is SubTabType.HOST:
|
||||||
self._build_host_tab(v, sub_scroll_width, sub_scroll_height,
|
self._build_host_tab(v, sub_scroll_width, sub_scroll_height,
|
||||||
@ -351,13 +378,6 @@ class PublicGatherTab(GatherTab):
|
|||||||
def _build_join_tab(self, v: float, sub_scroll_width: float,
|
def _build_join_tab(self, v: float, sub_scroll_width: float,
|
||||||
sub_scroll_height: float, c_width: float,
|
sub_scroll_height: float, c_width: float,
|
||||||
c_height: float) -> None:
|
c_height: float) -> None:
|
||||||
# Reset this so we do an immediate refresh query.
|
|
||||||
self._join_last_refresh_time = -99999.0
|
|
||||||
|
|
||||||
# Reset our list of public parties.
|
|
||||||
self._public_parties = {}
|
|
||||||
self._last_list_rebuild_time = 0
|
|
||||||
self._first_list_rebuild_time = None
|
|
||||||
ba.textwidget(text=ba.Lstr(resource='nameText'),
|
ba.textwidget(text=ba.Lstr(resource='nameText'),
|
||||||
parent=self._container,
|
parent=self._container,
|
||||||
size=(0, 0),
|
size=(0, 0),
|
||||||
@ -400,26 +420,23 @@ class PublicGatherTab(GatherTab):
|
|||||||
size=(sub_scroll_width, sub_scroll_height),
|
size=(sub_scroll_width, sub_scroll_height),
|
||||||
claims_left_right=True,
|
claims_left_right=True,
|
||||||
autoselect=True)
|
autoselect=True)
|
||||||
self._host_columnwidget = ba.containerwidget(parent=scrollw,
|
self._join_list_column = ba.containerwidget(parent=scrollw,
|
||||||
background=False,
|
background=False,
|
||||||
size=(400, 400),
|
size=(400, 400),
|
||||||
claims_left_right=True)
|
claims_left_right=True)
|
||||||
|
|
||||||
self._join_status_text = ba.textwidget(
|
self._join_status_text = ba.textwidget(parent=self._container,
|
||||||
parent=self._container,
|
text='',
|
||||||
text=ba.Lstr(
|
size=(0, 0),
|
||||||
value='${A}...',
|
scale=0.9,
|
||||||
subs=[('${A}', ba.Lstr(resource='store.loadingText'))],
|
flatness=1.0,
|
||||||
),
|
shadow=0.0,
|
||||||
size=(0, 0),
|
h_align='center',
|
||||||
scale=0.9,
|
v_align='top',
|
||||||
flatness=1.0,
|
maxwidth=c_width,
|
||||||
shadow=0.0,
|
color=(0.6, 0.6, 0.6),
|
||||||
h_align='center',
|
position=(c_width * 0.5,
|
||||||
v_align='top',
|
c_height * 0.5))
|
||||||
maxwidth=c_width,
|
|
||||||
color=(0.6, 0.6, 0.6),
|
|
||||||
position=(c_width * 0.5, c_height * 0.5))
|
|
||||||
|
|
||||||
def _build_host_tab(self, v: float, sub_scroll_width: float,
|
def _build_host_tab(self, v: float, sub_scroll_width: float,
|
||||||
sub_scroll_height: float, c_width: float,
|
sub_scroll_height: float, c_width: float,
|
||||||
@ -545,45 +562,48 @@ class PublicGatherTab(GatherTab):
|
|||||||
if _ba.get_public_party_enabled():
|
if _ba.get_public_party_enabled():
|
||||||
self._do_status_check()
|
self._do_status_check()
|
||||||
|
|
||||||
def _rebuild_public_party_list(self) -> None:
|
def _get_ordered_parties(self) -> List[PartyEntry]:
|
||||||
# pylint: disable=too-many-branches
|
# Sort - show queue-enabled ones first and sort by lowest ping.
|
||||||
# pylint: disable=too-many-locals
|
ordered_parties = sorted(
|
||||||
# pylint: disable=too-many-statements
|
self._parties.values(),
|
||||||
cur_time = ba.time(ba.TimeType.REAL)
|
key=lambda p: (
|
||||||
if self._first_list_rebuild_time is None:
|
p.queue is None, # Show non-queued last.
|
||||||
self._first_list_rebuild_time = cur_time
|
p.ping if p.ping is not None else 999999,
|
||||||
|
p.index))
|
||||||
|
return ordered_parties
|
||||||
|
|
||||||
# Update faster for the first few seconds;
|
def _update_server_list(self) -> None:
|
||||||
|
cur_time = ba.time(ba.TimeType.REAL)
|
||||||
|
if self._first_server_list_rebuild_time is None:
|
||||||
|
self._first_server_list_rebuild_time = cur_time
|
||||||
|
|
||||||
|
# We get called quite often (for each ping response, etc) so we want
|
||||||
|
# to limit our rebuilds to keep the UI responsive.
|
||||||
|
# Let's update faster for the first few seconds,
|
||||||
# then ease off to keep the list from jumping around.
|
# then ease off to keep the list from jumping around.
|
||||||
since_first = cur_time - self._first_list_rebuild_time
|
since_first = cur_time - self._first_server_list_rebuild_time
|
||||||
wait_time = (1.0 if since_first < 2.0 else
|
wait_time = (1.0 if since_first < 2.0 else
|
||||||
2.5 if since_first < 10.0 else 5.0)
|
2.5 if since_first < 10.0 else 5.0)
|
||||||
assert self._last_list_rebuild_time is not None
|
if (self._built_join_list
|
||||||
if cur_time - self._last_list_rebuild_time < wait_time:
|
and self._last_server_list_update_time is not None
|
||||||
|
and cur_time - self._last_server_list_update_time < wait_time):
|
||||||
return
|
return
|
||||||
self._last_list_rebuild_time = cur_time
|
|
||||||
|
|
||||||
# First off, check for the existence of our column widget;
|
# If we somehow got here without the required UI being in place...
|
||||||
# if we don't have this, we're done.
|
columnwidget = self._join_list_column
|
||||||
columnwidget = self._host_columnwidget
|
|
||||||
if not columnwidget:
|
if not columnwidget:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self._last_server_list_update_time = cur_time
|
||||||
|
self._built_join_list = True
|
||||||
|
|
||||||
with ba.Context('ui'):
|
with ba.Context('ui'):
|
||||||
|
|
||||||
# Now kill and recreate all widgets.
|
# Now kill and recreate all widgets.
|
||||||
for widget in columnwidget.get_children():
|
for widget in columnwidget.get_children():
|
||||||
widget.delete()
|
widget.delete()
|
||||||
|
|
||||||
# Sort - show queue-enabled ones first and sort by lowest ping.
|
ordered_parties = self._get_ordered_parties()
|
||||||
ordered_parties = sorted(
|
|
||||||
list(self._public_parties.values()),
|
|
||||||
key=lambda p: (
|
|
||||||
p.queue is None, # Show non-queued last.
|
|
||||||
p.ping if p.ping is not None else 999999,
|
|
||||||
p.index))
|
|
||||||
existing_selection = self._selection
|
|
||||||
first = True
|
|
||||||
|
|
||||||
sub_scroll_width = 830
|
sub_scroll_width = 830
|
||||||
lineheight = 42
|
lineheight = 42
|
||||||
@ -602,102 +622,8 @@ class PublicGatherTab(GatherTab):
|
|||||||
ba.containerwidget(edit=self._host_scrollwidget,
|
ba.containerwidget(edit=self._host_scrollwidget,
|
||||||
claims_up_down=(len(ordered_parties) > 0))
|
claims_up_down=(len(ordered_parties) > 0))
|
||||||
|
|
||||||
for i, party in enumerate(ordered_parties):
|
self._build_server_entry_lines(lineheight, ordered_parties,
|
||||||
hpos = 20
|
sub_scroll_height, sub_scroll_width)
|
||||||
vpos = sub_scroll_height - lineheight * i - 50
|
|
||||||
party.name_widget = ba.textwidget(
|
|
||||||
text=ba.Lstr(value=party.name),
|
|
||||||
parent=columnwidget,
|
|
||||||
size=(sub_scroll_width * 0.63, 20),
|
|
||||||
position=(0 + hpos, 4 + vpos),
|
|
||||||
selectable=True,
|
|
||||||
on_select_call=ba.WeakCall(
|
|
||||||
self._set_public_party_selection,
|
|
||||||
Selection(party.index, SelectionComponent.NAME)),
|
|
||||||
on_activate_call=ba.WeakCall(
|
|
||||||
self._on_public_party_activate, party),
|
|
||||||
click_activate=True,
|
|
||||||
maxwidth=sub_scroll_width * 0.45,
|
|
||||||
corner_scale=1.4,
|
|
||||||
autoselect=True,
|
|
||||||
color=(1, 1, 1, 0.3 if party.ping is None else 1.0),
|
|
||||||
h_align='left',
|
|
||||||
v_align='center')
|
|
||||||
ba.widget(edit=party.name_widget,
|
|
||||||
left_widget=self._join_text,
|
|
||||||
show_buffer_top=64.0,
|
|
||||||
show_buffer_bottom=64.0)
|
|
||||||
# if existing_selection == (party.address, 'name'):
|
|
||||||
if existing_selection == Selection(party.index,
|
|
||||||
SelectionComponent.NAME):
|
|
||||||
ba.containerwidget(edit=columnwidget,
|
|
||||||
selected_child=party.name_widget)
|
|
||||||
if party.stats_addr:
|
|
||||||
url = party.stats_addr.replace(
|
|
||||||
'${ACCOUNT}',
|
|
||||||
_ba.get_account_misc_read_val_2(
|
|
||||||
'resolvedAccountID', 'UNKNOWN'))
|
|
||||||
party.stats_button = ba.buttonwidget(
|
|
||||||
color=(0.3, 0.6, 0.94),
|
|
||||||
textcolor=(1.0, 1.0, 1.0),
|
|
||||||
label=ba.Lstr(resource='statsText'),
|
|
||||||
parent=columnwidget,
|
|
||||||
autoselect=True,
|
|
||||||
on_activate_call=ba.Call(ba.open_url, url),
|
|
||||||
on_select_call=ba.WeakCall(
|
|
||||||
self._set_public_party_selection,
|
|
||||||
Selection(party.index,
|
|
||||||
SelectionComponent.STATS_BUTTON)),
|
|
||||||
size=(120, 40),
|
|
||||||
position=(sub_scroll_width * 0.66 + hpos, 1 + vpos),
|
|
||||||
scale=0.9)
|
|
||||||
if existing_selection == Selection(
|
|
||||||
party.index, SelectionComponent.STATS_BUTTON):
|
|
||||||
ba.containerwidget(edit=columnwidget,
|
|
||||||
selected_child=party.stats_button)
|
|
||||||
else:
|
|
||||||
if party.stats_button:
|
|
||||||
party.stats_button.delete()
|
|
||||||
party.stats_button = None
|
|
||||||
|
|
||||||
if first:
|
|
||||||
if party.stats_button:
|
|
||||||
ba.widget(edit=party.stats_button,
|
|
||||||
up_widget=self._join_text)
|
|
||||||
if party.name_widget:
|
|
||||||
ba.widget(edit=party.name_widget,
|
|
||||||
up_widget=self._join_text)
|
|
||||||
first = False
|
|
||||||
|
|
||||||
party.size_widget = ba.textwidget(
|
|
||||||
text=str(party.size) + '/' + str(party.size_max),
|
|
||||||
parent=columnwidget,
|
|
||||||
size=(0, 0),
|
|
||||||
position=(sub_scroll_width * 0.86 + hpos, 20 + vpos),
|
|
||||||
scale=0.7,
|
|
||||||
color=(0.8, 0.8, 0.8),
|
|
||||||
h_align='right',
|
|
||||||
v_align='center')
|
|
||||||
party.ping_widget = ba.textwidget(
|
|
||||||
parent=columnwidget,
|
|
||||||
size=(0, 0),
|
|
||||||
position=(sub_scroll_width * 0.94 + hpos, 20 + vpos),
|
|
||||||
scale=0.7,
|
|
||||||
h_align='right',
|
|
||||||
v_align='center')
|
|
||||||
if party.ping is None:
|
|
||||||
ba.textwidget(edit=party.ping_widget,
|
|
||||||
text='-',
|
|
||||||
color=(0.5, 0.5, 0.5))
|
|
||||||
else:
|
|
||||||
ping_good = _ba.get_account_misc_read_val('pingGood', 100)
|
|
||||||
ping_med = _ba.get_account_misc_read_val('pingMed', 500)
|
|
||||||
ba.textwidget(edit=party.ping_widget,
|
|
||||||
text=str(party.ping),
|
|
||||||
color=(0, 1,
|
|
||||||
0) if party.ping <= ping_good else
|
|
||||||
(1, 1, 0) if party.ping <= ping_med else
|
|
||||||
(1, 0, 0))
|
|
||||||
|
|
||||||
# So our selection callbacks can start firing..
|
# So our selection callbacks can start firing..
|
||||||
def refresh_off() -> None:
|
def refresh_off() -> None:
|
||||||
@ -705,6 +631,109 @@ class PublicGatherTab(GatherTab):
|
|||||||
|
|
||||||
ba.pushcall(refresh_off)
|
ba.pushcall(refresh_off)
|
||||||
|
|
||||||
|
def _build_server_entry_lines(self, lineheight: float,
|
||||||
|
ordered_parties: List[PartyEntry],
|
||||||
|
sub_scroll_height: float,
|
||||||
|
sub_scroll_width: float) -> None:
|
||||||
|
existing_selection = self._selection
|
||||||
|
columnwidget = self._join_list_column
|
||||||
|
first = True
|
||||||
|
assert columnwidget
|
||||||
|
for i, party in enumerate(ordered_parties):
|
||||||
|
hpos = 20
|
||||||
|
vpos = sub_scroll_height - lineheight * i - 50
|
||||||
|
party.name_widget = ba.textwidget(
|
||||||
|
text=ba.Lstr(value=party.name),
|
||||||
|
parent=columnwidget,
|
||||||
|
size=(sub_scroll_width * 0.63, 20),
|
||||||
|
position=(0 + hpos, 4 + vpos),
|
||||||
|
selectable=True,
|
||||||
|
on_select_call=ba.WeakCall(
|
||||||
|
self._set_public_party_selection,
|
||||||
|
Selection(party.index, SelectionComponent.NAME)),
|
||||||
|
on_activate_call=ba.WeakCall(self._on_public_party_activate,
|
||||||
|
party),
|
||||||
|
click_activate=True,
|
||||||
|
maxwidth=sub_scroll_width * 0.45,
|
||||||
|
corner_scale=1.4,
|
||||||
|
autoselect=True,
|
||||||
|
color=(1, 1, 1, 0.3 if party.ping is None else 1.0),
|
||||||
|
h_align='left',
|
||||||
|
v_align='center')
|
||||||
|
ba.widget(edit=party.name_widget,
|
||||||
|
left_widget=self._join_text,
|
||||||
|
show_buffer_top=64.0,
|
||||||
|
show_buffer_bottom=64.0)
|
||||||
|
if existing_selection == Selection(party.index,
|
||||||
|
SelectionComponent.NAME):
|
||||||
|
ba.containerwidget(edit=columnwidget,
|
||||||
|
selected_child=party.name_widget)
|
||||||
|
if party.stats_addr:
|
||||||
|
url = party.stats_addr.replace(
|
||||||
|
'${ACCOUNT}',
|
||||||
|
_ba.get_account_misc_read_val_2('resolvedAccountID',
|
||||||
|
'UNKNOWN'))
|
||||||
|
party.stats_button = ba.buttonwidget(
|
||||||
|
color=(0.3, 0.6, 0.94),
|
||||||
|
textcolor=(1.0, 1.0, 1.0),
|
||||||
|
label=ba.Lstr(resource='statsText'),
|
||||||
|
parent=columnwidget,
|
||||||
|
autoselect=True,
|
||||||
|
on_activate_call=ba.Call(ba.open_url, url),
|
||||||
|
on_select_call=ba.WeakCall(
|
||||||
|
self._set_public_party_selection,
|
||||||
|
Selection(party.index,
|
||||||
|
SelectionComponent.STATS_BUTTON)),
|
||||||
|
size=(120, 40),
|
||||||
|
position=(sub_scroll_width * 0.66 + hpos, 1 + vpos),
|
||||||
|
scale=0.9)
|
||||||
|
if existing_selection == Selection(
|
||||||
|
party.index, SelectionComponent.STATS_BUTTON):
|
||||||
|
ba.containerwidget(edit=columnwidget,
|
||||||
|
selected_child=party.stats_button)
|
||||||
|
else:
|
||||||
|
if party.stats_button:
|
||||||
|
party.stats_button.delete()
|
||||||
|
party.stats_button = None
|
||||||
|
|
||||||
|
if first:
|
||||||
|
if party.stats_button:
|
||||||
|
ba.widget(edit=party.stats_button,
|
||||||
|
up_widget=self._join_text)
|
||||||
|
if party.name_widget:
|
||||||
|
ba.widget(edit=party.name_widget,
|
||||||
|
up_widget=self._join_text)
|
||||||
|
first = False
|
||||||
|
|
||||||
|
party.size_widget = ba.textwidget(
|
||||||
|
text=str(party.size) + '/' + str(party.size_max),
|
||||||
|
parent=columnwidget,
|
||||||
|
size=(0, 0),
|
||||||
|
position=(sub_scroll_width * 0.86 + hpos, 20 + vpos),
|
||||||
|
scale=0.7,
|
||||||
|
color=(0.8, 0.8, 0.8),
|
||||||
|
h_align='right',
|
||||||
|
v_align='center')
|
||||||
|
party.ping_widget = ba.textwidget(
|
||||||
|
parent=columnwidget,
|
||||||
|
size=(0, 0),
|
||||||
|
position=(sub_scroll_width * 0.94 + hpos, 20 + vpos),
|
||||||
|
scale=0.7,
|
||||||
|
h_align='right',
|
||||||
|
v_align='center')
|
||||||
|
if party.ping is None:
|
||||||
|
ba.textwidget(edit=party.ping_widget,
|
||||||
|
text='-',
|
||||||
|
color=(0.5, 0.5, 0.5))
|
||||||
|
else:
|
||||||
|
ping_good = _ba.get_account_misc_read_val('pingGood', 100)
|
||||||
|
ping_med = _ba.get_account_misc_read_val('pingMed', 500)
|
||||||
|
ba.textwidget(edit=party.ping_widget,
|
||||||
|
text=str(party.ping),
|
||||||
|
color=(0, 1, 0) if party.ping <= ping_good else
|
||||||
|
(1, 1, 0) if party.ping <= ping_med else
|
||||||
|
(1, 0, 0))
|
||||||
|
|
||||||
def _on_public_party_query_result(
|
def _on_public_party_query_result(
|
||||||
self, result: Optional[Dict[str, Any]]) -> None:
|
self, result: Optional[Dict[str, Any]]) -> None:
|
||||||
with ba.Context('ui'):
|
with ba.Context('ui'):
|
||||||
@ -724,11 +753,13 @@ class PublicGatherTab(GatherTab):
|
|||||||
ba.textwidget(edit=status_text, text='')
|
ba.textwidget(edit=status_text, text='')
|
||||||
|
|
||||||
if result is not None:
|
if result is not None:
|
||||||
|
self._have_valid_server_list = True
|
||||||
parties_in = result['l']
|
parties_in = result['l']
|
||||||
else:
|
else:
|
||||||
|
self._have_valid_server_list = False
|
||||||
parties_in = []
|
parties_in = []
|
||||||
|
|
||||||
for partyval in list(self._public_parties.values()):
|
for partyval in list(self._parties.values()):
|
||||||
partyval.claimed = False
|
partyval.claimed = False
|
||||||
|
|
||||||
for party_in in parties_in:
|
for party_in in parties_in:
|
||||||
@ -737,14 +768,13 @@ class PublicGatherTab(GatherTab):
|
|||||||
port = party_in['p']
|
port = party_in['p']
|
||||||
assert isinstance(port, int)
|
assert isinstance(port, int)
|
||||||
party_key = f'{addr}_{port}'
|
party_key = f'{addr}_{port}'
|
||||||
party = self._public_parties.get(party_key)
|
party = self._parties.get(party_key)
|
||||||
if party is None:
|
if party is None:
|
||||||
# If this party is new to us, init it.
|
# If this party is new to us, init it.
|
||||||
party = self._public_parties[party_key] = PartyEntry(
|
party = self._parties[party_key] = PartyEntry(
|
||||||
address=addr,
|
address=addr,
|
||||||
next_ping_time=ba.time(ba.TimeType.REAL) +
|
next_ping_time=ba.time(ba.TimeType.REAL) +
|
||||||
0.001 * party_in['pd'],
|
0.001 * party_in['pd'],
|
||||||
ping=None,
|
|
||||||
index=self._next_entry_index)
|
index=self._next_entry_index)
|
||||||
self._next_entry_index += 1
|
self._next_entry_index += 1
|
||||||
assert isinstance(party.address, str)
|
assert isinstance(party.address, str)
|
||||||
@ -768,14 +798,14 @@ class PublicGatherTab(GatherTab):
|
|||||||
assert isinstance(party.stats_addr, (str, type(None)))
|
assert isinstance(party.stats_addr, (str, type(None)))
|
||||||
|
|
||||||
# Prune unclaimed party entries.
|
# Prune unclaimed party entries.
|
||||||
self._public_parties = {
|
self._parties = {
|
||||||
key: val
|
key: val
|
||||||
for key, val in list(self._public_parties.items())
|
for key, val in list(self._parties.items()) if val.claimed
|
||||||
if val.claimed
|
|
||||||
}
|
}
|
||||||
self._rebuild_public_party_list()
|
self._update_server_list()
|
||||||
|
|
||||||
def _update_sub_tab(self) -> None:
|
def _update(self) -> None:
|
||||||
|
"""Periodic updating."""
|
||||||
|
|
||||||
# Special case: if a party-queue window is up, don't do any of this
|
# Special case: if a party-queue window is up, don't do any of this
|
||||||
# (keeps things smoother).
|
# (keeps things smoother).
|
||||||
@ -791,35 +821,50 @@ class PublicGatherTab(GatherTab):
|
|||||||
|
|
||||||
if self._sub_tab is SubTabType.JOIN:
|
if self._sub_tab is SubTabType.JOIN:
|
||||||
now = ba.time(ba.TimeType.REAL)
|
now = ba.time(ba.TimeType.REAL)
|
||||||
if (now - self._join_last_refresh_time > 0.001 *
|
|
||||||
|
# Fire off a new public-party query periodically.
|
||||||
|
if (self._last_server_list_query_time is None
|
||||||
|
or now - self._last_server_list_query_time > 0.001 *
|
||||||
_ba.get_account_misc_read_val('pubPartyRefreshMS', 10000)):
|
_ba.get_account_misc_read_val('pubPartyRefreshMS', 10000)):
|
||||||
self._join_last_refresh_time = now
|
self._last_server_list_query_time = now
|
||||||
app = ba.app
|
if DEBUG_SERVER_COMMUNICATION:
|
||||||
|
print('REQUESTING SERVER LIST')
|
||||||
_ba.add_transaction(
|
_ba.add_transaction(
|
||||||
{
|
{
|
||||||
'type': 'PUBLIC_PARTY_QUERY',
|
'type': 'PUBLIC_PARTY_QUERY',
|
||||||
'proto': app.protocol_version,
|
'proto': ba.app.protocol_version,
|
||||||
'lang': app.lang.language
|
'lang': ba.app.lang.language
|
||||||
},
|
},
|
||||||
callback=ba.WeakCall(self._on_public_party_query_result))
|
callback=ba.WeakCall(self._on_public_party_query_result))
|
||||||
_ba.run_transactions()
|
_ba.run_transactions()
|
||||||
|
|
||||||
# Go through our existing public party entries firing off pings
|
# Go through our existing public party entries firing off pings
|
||||||
# for any that have timed out.
|
# for any that have timed out.
|
||||||
for party in list(self._public_parties.values()):
|
for party in list(self._parties.values()):
|
||||||
if (party.next_ping_time <= now
|
if (party.next_ping_time <= now
|
||||||
and ba.app.ping_thread_count < 15):
|
and ba.app.ping_thread_count < 15):
|
||||||
|
|
||||||
# Make sure to fully catch up and not to multi-ping if
|
# Crank the interval up for high-latency or non-responding
|
||||||
# we're way behind somehow.
|
# parties to save us some useless work.
|
||||||
while party.next_ping_time <= now:
|
mult = 1
|
||||||
# Crank the interval up for high-latency parties to
|
if party.ping_responses == 0:
|
||||||
# save us some work.
|
if party.ping_attempts > 4:
|
||||||
mult = 1
|
mult = 10
|
||||||
if party.ping is not None:
|
elif party.ping_attempts > 2:
|
||||||
mult = (10 if party.ping > 300 else
|
mult = 5
|
||||||
5 if party.ping > 150 else 2)
|
if party.ping is not None:
|
||||||
party.next_ping_time += party.ping_interval * mult
|
mult = (10 if party.ping > 300 else
|
||||||
|
5 if party.ping > 150 else 2)
|
||||||
|
|
||||||
|
interval = party.ping_interval * mult
|
||||||
|
if DEBUG_SERVER_COMMUNICATION:
|
||||||
|
print(
|
||||||
|
f'pinging #{party.index} cur={party.ping} '
|
||||||
|
f'interval={interval} '
|
||||||
|
f'({party.ping_responses}/{party.ping_attempts})')
|
||||||
|
|
||||||
|
party.next_ping_time = now + party.ping_interval * mult
|
||||||
|
party.ping_attempts += 1
|
||||||
|
|
||||||
PingThread(party.address, party.port,
|
PingThread(party.address, party.port,
|
||||||
ba.WeakCall(self._ping_callback)).start()
|
ba.WeakCall(self._ping_callback)).start()
|
||||||
@ -828,8 +873,12 @@ class PublicGatherTab(GatherTab):
|
|||||||
result: Optional[int]) -> None:
|
result: Optional[int]) -> None:
|
||||||
# Look for a widget corresponding to this target.
|
# Look for a widget corresponding to this target.
|
||||||
# If we find one, update our list.
|
# If we find one, update our list.
|
||||||
party = self._public_parties.get(address + '_' + str(port))
|
party_key = f'{address}_{port}'
|
||||||
|
party = self._parties.get(party_key)
|
||||||
if party is not None:
|
if party is not None:
|
||||||
|
if result is not None:
|
||||||
|
party.ping_responses += 1
|
||||||
|
|
||||||
# We now smooth ping a bit to reduce jumping around in the list
|
# We now smooth ping a bit to reduce jumping around in the list
|
||||||
# (only where pings are relatively good).
|
# (only where pings are relatively good).
|
||||||
current_ping = party.ping
|
current_ping = party.ping
|
||||||
@ -840,11 +889,7 @@ class PublicGatherTab(GatherTab):
|
|||||||
(1.0 - smoothing) * result)
|
(1.0 - smoothing) * result)
|
||||||
else:
|
else:
|
||||||
party.ping = result
|
party.ping = result
|
||||||
|
self._update_server_list()
|
||||||
# This can happen if we switch away and then back to the
|
|
||||||
# client tab while pings are in flight.
|
|
||||||
if party.ping_widget:
|
|
||||||
self._rebuild_public_party_list()
|
|
||||||
|
|
||||||
def _fetch_local_addr_cb(self, val: str) -> None:
|
def _fetch_local_addr_cb(self, val: str) -> None:
|
||||||
self._local_address = str(val)
|
self._local_address = str(val)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user