Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace repeated polling in Outbox.loop() with an asyncio event (see #2482) #2867

Merged
merged 14 commits into from
Apr 16, 2024
24 changes: 20 additions & 4 deletions nicegui/outbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,35 +23,51 @@ def __init__(self, client: Client) -> None:
self.updates: Dict[ElementId, Optional[Element]] = {}
self.messages: Deque[Message] = deque()
self._should_stop = False
self._enqueue_event: Optional[asyncio.Event] = None
if core.app.is_started:
background_tasks.create(self.loop(), name=f'outbox loop {client.id}')
else:
core.app.on_startup(self.loop)

def _set_enqueue_event(self) -> None:
"""Set the enqueue event while accounting for lazy initialization."""
if self._enqueue_event:
self._enqueue_event.set()

def enqueue_update(self, element: Element) -> None:
"""Enqueue an update for the given element."""
self.updates[element.id] = element
self._set_enqueue_event()

def enqueue_delete(self, element: Element) -> None:
"""Enqueue a deletion for the given element."""
self.updates[element.id] = None
self._set_enqueue_event()

def enqueue_message(self, message_type: MessageType, data: Any, target_id: ClientId) -> None:
"""Enqueue a message for the given client."""
self.messages.append((target_id, message_type, data))
self._set_enqueue_event()

async def loop(self) -> None:
"""Send updates and messages to all clients in an endless loop."""
self._enqueue_event = asyncio.Event()
self._enqueue_event.set()

while not self._should_stop:
try:
await asyncio.sleep(0.01)

if not self.updates and not self.messages:
continue
if not self._enqueue_event.is_set():
try:
await asyncio.wait_for(self._enqueue_event.wait(), timeout=1.0)
except (TimeoutError, asyncio.TimeoutError):
continue

if not self.client.has_socket_connection:
await asyncio.sleep(0.1)
continue

self._enqueue_event.clear()

coros = []
data = {
element_id: None if element is None else element._to_dict() # pylint: disable=protected-access
Expand Down
5 changes: 3 additions & 2 deletions tests/test_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,12 @@ def test_autocompletion(screen: Screen):
assert element.get_attribute('value') == 'fx'
assert input_.value == 'fx'

input_.set_autocomplete(['one', 'two'])
input_.set_autocomplete(['once', 'twice'])
screen.wait(0.2)
element.send_keys(Keys.BACKSPACE)
element.send_keys(Keys.BACKSPACE)
element.send_keys('o')
screen.should_contain('ne')
screen.should_contain('nce')


def test_clearable_input(screen: Screen):
Expand Down
1 change: 1 addition & 0 deletions tests/test_javascript.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def page():
screen.open('/')
screen.should_contain('before js')
screen.should_contain('after js')
screen.wait(0.5)
screen.should_contain('New Title')


Expand Down