Fix frame rate limiter (#2318)

* simplify

* fix for frame rate limiter

* fix update

* fix update

* update comment

* No need for lock

* remove comment

* fix for glitched test

* force update

* implement dim fix

* docstrings

* foreground fix

* cached filters

* cache default

* fix for filter tests

* docstring

* optimization

* Update src/textual/filter.py

Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>

* Update src/textual/constants.py

Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>

* fix cache

* remove comment [skip ci]

---------

Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
This commit is contained in:
Will McGugan
2023-04-19 16:55:42 +01:00
committed by GitHub
parent 0e8e232a08
commit d7f5fb9107
5 changed files with 48 additions and 47 deletions

View File

@@ -245,19 +245,15 @@ class ChopsUpdate(CompositorUpdate):
append(strip.render(console)) append(strip.render(console))
continue continue
if x < x1: strip = strip.crop(0, min(end, x2) - x)
strip = strip.crop(x1 - x, x2 - x) append(move_to(x, y).segment.text)
append(move_to(x1, y).segment.text)
else:
strip = strip.crop(0, x2 - x)
append(move_to(x, y).segment.text)
append(strip.render(console)) append(strip.render(console))
if y != last_y: if y != last_y:
append("\n") append("\n")
return "".join(sequences) terminal_sequences = "".join(sequences)
return terminal_sequences
def __rich_repr__(self) -> rich.repr.Result: def __rich_repr__(self) -> rich.repr.Result:
yield from () yield from ()

View File

@@ -117,12 +117,8 @@ def dim_style(style: Style, background: Color, factor: float) -> Style:
style style
+ Style.from_color( + Style.from_color(
dim_color( dim_color(
( (background.rich_color if style.bgcolor.is_default else style.bgcolor),
background.rich_color style.color,
if style.bgcolor.is_default
else (style.bgcolor or DEFAULT_COLOR)
),
style.color or DEFAULT_COLOR,
factor, factor,
), ),
None, None,

View File

@@ -141,6 +141,7 @@ class Pilot(Generic[ReturnType]):
delay: Seconds to pause, or None to wait for cpu idle. delay: Seconds to pause, or None to wait for cpu idle.
""" """
# These sleep zeros, are to force asyncio to give up a time-slice, # These sleep zeros, are to force asyncio to give up a time-slice,
self.app.screen._on_timer_update() # Force one last repaint
if delay is None: if delay is None:
await wait_for_idle(0) await wait_for_idle(0)
else: else:

View File

@@ -42,8 +42,8 @@ from .widget import Widget
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import Final from typing_extensions import Final
# Screen updates will be batched so that they don't happen more often than 120 times per second: # Screen updates will be batched so that they don't happen more often than 60 times per second:
UPDATE_PERIOD: Final[float] = 1 / 120 UPDATE_PERIOD: Final[float] = 1 / 60
ScreenResultType = TypeVar("ScreenResultType") ScreenResultType = TypeVar("ScreenResultType")
"""The result type of a screen.""" """The result type of a screen."""
@@ -437,42 +437,49 @@ class Screen(Generic[ScreenResultType], Widget):
event.prevent_default() event.prevent_default()
if not self.app._batch_count and self.is_current: if not self.app._batch_count and self.is_current:
async with self.app._dom_lock: if (
if self.is_current: self._layout_required
if self._layout_required: or self._scroll_required
self._refresh_layout() or self._repaint_required
self._layout_required = False or self._dirty_widgets
self._scroll_required = False ):
self._dirty_widgets.clear() self._update_timer.resume()
elif self._scroll_required:
self._refresh_layout(scroll=True)
self._scroll_required = False
if self._repaint_required:
self._dirty_widgets.clear()
self._dirty_widgets.add(self)
self._repaint_required = False
if self._dirty_widgets:
self._update_timer.resume()
# The Screen is idle - a good opportunity to invoke the scheduled callbacks # The Screen is idle - a good opportunity to invoke the scheduled callbacks
await self._invoke_and_clear_callbacks()
if self._callbacks:
self._on_timer_update()
def _on_timer_update(self) -> None: def _on_timer_update(self) -> None:
"""Called by the _update_timer.""" """Called by the _update_timer."""
# Render widgets together
if self._dirty_widgets and self.is_current:
self._compositor.update_widgets(self._dirty_widgets)
update = self._compositor.render_update(
screen_stack=self.app._background_screens
)
self.app._display(self, update)
self._dirty_widgets.clear()
if self._callbacks:
self.post_message(events.InvokeCallbacks())
self._update_timer.pause() self._update_timer.pause()
if self.is_current:
if self._layout_required:
self._refresh_layout()
self._layout_required = False
self._scroll_required = False
self._dirty_widgets.clear()
elif self._scroll_required:
self._refresh_layout(scroll=True)
self._scroll_required = False
if self._repaint_required:
self._update_timer.resume()
self._dirty_widgets.clear()
self._dirty_widgets.add(self)
self._repaint_required = False
if self._dirty_widgets and self.is_current:
self._compositor.update_widgets(self._dirty_widgets)
update = self._compositor.render_update(
screen_stack=self.app._background_screens
)
self.app._display(self, update)
self._dirty_widgets.clear()
if self._callbacks:
self.post_message(events.InvokeCallbacks())
async def _on_invoke_callbacks(self, event: events.InvokeCallbacks) -> None: async def _on_invoke_callbacks(self, event: events.InvokeCallbacks) -> None:
"""Handle PostScreenUpdate events, which are sent after the screen is updated""" """Handle PostScreenUpdate events, which are sent after the screen is updated"""

View File

@@ -282,6 +282,7 @@ class Strip:
cached_strip = Strip( cached_strip = Strip(
filter.apply(self._segments, background), self._cell_length filter.apply(self._segments, background), self._cell_length
) )
self._filter_cache[(filter, background)] = cached_strip
return cached_strip return cached_strip
def style_links(self, link_id: str, link_style: Style) -> Strip: def style_links(self, link_id: str, link_style: Style) -> Strip:
@@ -312,7 +313,7 @@ class Strip:
] ]
return Strip(segments, self._cell_length) return Strip(segments, self._cell_length)
def crop(self, start: int, end: int) -> Strip: def crop(self, start: int, end: int | None = None) -> Strip:
"""Crop a strip between two cell positions. """Crop a strip between two cell positions.
Args: Args:
@@ -323,7 +324,7 @@ class Strip:
A new Strip. A new Strip.
""" """
start = max(0, start) start = max(0, start)
end = min(self.cell_length, end) end = self.cell_length if end is None else min(self.cell_length, end)
if start == 0 and end == self.cell_length: if start == 0 and end == self.cell_length:
return self return self
cache_key = (start, end) cache_key = (start, end)