fix for timer

This commit is contained in:
Will McGugan
2021-12-28 16:09:27 +00:00
parent 431f43fb4b
commit 690df0518b
9 changed files with 86 additions and 46 deletions

View File

@@ -19,4 +19,4 @@ class BasicApp(App):
)
BasicApp.run(css_file="basic.css", watch_css=True)
BasicApp.run(css_file="basic.css", watch_css=True, log="textual.log")

10
examples/colours.txt Normal file
View File

@@ -0,0 +1,10 @@
header blue white on #173f5f
sidebar #09312e on #3caea3
sidebar border #09312e
content blue white #20639b
content border #0f2b41
footer border #0f2b41
footer yellow #3a3009 on #f6d55c;

View File

@@ -10,6 +10,7 @@ from dataclasses import dataclass
from . import log
from ._easing import DEFAULT_EASING, EASING
from ._profile import timer
from ._timer import Timer
from ._types import MessageTarget
@@ -124,17 +125,12 @@ class Animator:
callback=self,
pause=True,
)
self._timer_task: asyncio.Task | None = None
async def start(self) -> None:
if self._timer_task is None:
self._timer_task = asyncio.get_event_loop().create_task(self._timer.run())
self._timer.start()
async def stop(self) -> None:
self._timer.stop()
if self._timer_task:
await self._timer_task
self._timer_task = None
await self._timer.stop()
def bind(self, obj: object) -> BoundAnimator:
return BoundAnimator(self, obj)

View File

@@ -7,7 +7,6 @@ import selectors
import signal
import sys
import termios
from time import time
import tty
from typing import Any, TYPE_CHECKING
from threading import Event, Thread
@@ -15,12 +14,14 @@ from threading import Event, Thread
if TYPE_CHECKING:
from rich.console import Console
from . import log
from . import events
from .driver import Driver
from .geometry import Size
from ._types import MessageTarget
from ._xterm_parser import XTermParser
from ._profile import timer
class LinuxDriver(Driver):
@@ -149,23 +150,26 @@ class LinuxDriver(Driver):
self.exit_event.set()
if self._key_thread is not None:
self._key_thread.join()
except Exception:
except Exception as error:
# TODO: log this
pass
def stop_application_mode(self) -> None:
self.disable_input()
with timer("disable_input"):
self.disable_input()
if self.attrs_before is not None:
try:
termios.tcsetattr(self.fileno, termios.TCSANOW, self.attrs_before)
except termios.error:
pass
with timer("tcsetattr"):
if self.attrs_before is not None:
try:
termios.tcsetattr(self.fileno, termios.TCSANOW, self.attrs_before)
except termios.error:
pass
self.console.set_alt_screen(False)
self.console.show_cursor(True)
self.console.file.flush()
with timer("set_alt_screen False, show cursor"):
with self.console:
self.console.set_alt_screen(False)
self.console.show_cursor(True)
def run_input_thread(self, loop) -> None:
try:
@@ -182,7 +186,7 @@ class LinuxDriver(Driver):
def more_data() -> bool:
"""Check if there is more data to parse."""
for key, events in selector.select(0.1):
for key, events in selector.select(0.01):
if events:
return True
return False
@@ -201,11 +205,11 @@ class LinuxDriver(Driver):
unicode_data = decode(read(fileno, 1024))
for event in parser.feed(unicode_data):
self.process_event(event)
except Exception:
pass
# TODO: log
except Exception as error:
log(error)
finally:
selector.close()
with timer("selector.close"):
selector.close()
if __name__ == "__main__":

View File

@@ -1,12 +1,23 @@
from __future__ import annotations
import weakref
from asyncio import CancelledError, Event, TimeoutError, wait_for
from asyncio import (
get_event_loop,
CancelledError,
Event,
TimeoutError,
sleep,
wait,
wait_for,
Task,
)
from time import monotonic
from typing import Awaitable, Callable, Union
from rich.repr import Result, rich_repr
from ._profile import timer
from . import log
from . import events
from ._types import MessageTarget
@@ -42,7 +53,6 @@ class Timer:
self._callback = callback
self._repeat = repeat
self._skip = skip
self._stop_event = Event()
self._active = Event()
if not pause:
self._active.set()
@@ -59,9 +69,16 @@ class Timer:
raise EventTargetGone()
return target
def stop(self) -> None:
self._active.set()
self._stop_event.set()
def start(self) -> Task:
self._task = get_event_loop().create_task(self.run())
return self._task
async def stop(self) -> None:
self._task.cancel()
await self._task
async def wait(self) -> None:
await self._task
def pause(self) -> None:
self._active.clear()
@@ -73,20 +90,19 @@ class Timer:
count = 0
_repeat = self._repeat
_interval = self._interval
_wait = self._stop_event.wait
_wait_active = self._active.wait
start = monotonic()
try:
while _repeat is None or count <= _repeat:
next_timer = start + ((count + 1) * _interval)
if self._skip and next_timer < monotonic():
count += 1
continue
try:
if await wait_for(_wait(), max(0, next_timer - monotonic())):
break
except TimeoutError:
pass
wait_time = max(0, next_timer - monotonic())
if wait_time:
await sleep(wait_time)
event = events.Timer(
self.sender, timer=self, count=count, callback=self._callback
)
@@ -95,7 +111,8 @@ class Timer:
await self.target.post_message(event)
except EventTargetGone:
break
await self._active.wait()
await _wait_active()
except CancelledError:
pass
log(timer_id=id(self))

View File

@@ -1,9 +1,11 @@
from __future__ import annotations
import os
import re
from typing import Callable, Generator
from typing import Any, Callable, Generator
from . import log
from . import events
from ._types import MessageTarget
from ._parser import Awaitable, Parser, TokenCallback
@@ -22,8 +24,17 @@ class XTermParser(Parser[events.Event]):
self.more_data = more_data
self.last_x = 0
self.last_y = 0
self._debug_log_file = (
open("keys.log", "wt") if "TEXTUAL_DEBUG" in os.environ else None
)
super().__init__()
def debug_log(self, *args: Any) -> None:
if self._debug_log_file is not None:
self._debug_log_file.write(" ".join(args) + "\n")
def parse_mouse_code(self, code: str, sender: MessageTarget) -> events.Event | None:
sgr_match = self._re_sgr_mouse.match(code)
if sgr_match:
@@ -71,12 +82,14 @@ class XTermParser(Parser[events.Event]):
while not self.is_eof:
character = yield read1()
# log.debug("character=%r", character)
self.debug_log(f"character={character!r}")
# The more_data is to allow the parse to distinguish between an escape sequence
# and the escape key pressed
if character == ESC and ((yield self.peek_buffer()) or more_data()):
sequence: str = character
while True:
sequence += yield read1()
# log.debug(f"sequence=%r", sequence)
self.debug_log(f"sequence={sequence!r}")
keys = get_ansi_sequence(sequence, None)
if keys is not None:
for key in keys:

View File

@@ -364,8 +364,10 @@ class App(DOMNode):
await self.animator.start()
await super().process_messages()
log("PROCESS END")
await self.animator.stop()
await self.close_all()
with timer("animator.stop()"):
await self.animator.stop()
with timer("self.close_all()"):
await self.close_all()
finally:
driver.stop_application_mode()
except:

View File

@@ -125,8 +125,7 @@ class MessagePump:
name: str | None = None,
) -> Timer:
timer = Timer(self, delay, self, name=name, callback=callback, repeat=0)
timer_task = asyncio.get_event_loop().create_task(timer.run())
self._child_tasks.add(timer_task)
self._child_tasks.add(timer.start())
return timer
def set_interval(
@@ -140,7 +139,7 @@ class MessagePump:
timer = Timer(
self, interval, self, name=name, callback=callback, repeat=repeat or None
)
self._child_tasks.add(asyncio.get_event_loop().create_task(timer.run()))
self._child_tasks.add(timer.start())
return timer
async def call_later(self, callback: Callable, *args, **kwargs) -> None:

View File

@@ -273,7 +273,6 @@ class Widget(DOMNode):
elif repaint:
self.clear_render_cache()
self._repaint_required = True
self.log("refresh", repaint, layout)
self.post_message_no_wait(events.Null(self))
def render(self) -> RenderableType: