From b34ec985610c374ff5af07878b139ae93cc3d4a9 Mon Sep 17 00:00:00 2001 From: thegamecracks <61257169+thegamecracks@users.noreply.github.com> Date: Sun, 16 Jul 2023 18:54:24 -0400 Subject: [PATCH 1/6] debug: print player time and try detecting double runs Please revert this commit before merging --- sardine_core/handlers/player.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sardine_core/handlers/player.py b/sardine_core/handlers/player.py index 35a8f065..400729b9 100644 --- a/sardine_core/handlers/player.py +++ b/sardine_core/handlers/player.py @@ -175,6 +175,15 @@ def func( self.iterator = pattern.iterator pattern.iterator = None + import math + clock = self.runner.clock + is_repeated = math.isclose(clock.shifted_time, self.last_shifted_time, abs_tol=1e-3) + print( + f"{self.name} {clock.shifted_time = :6.3f} {clock.time = :6.3f}, " + f"{self.iterator = :3d} {' X'[is_repeated]}" + ) + self.last_shifted_time = clock.shifted_time + dur = pattern.send_method( *pattern.args, **pattern.kwargs, @@ -231,6 +240,7 @@ def push(self, pattern: Optional[PatternInformation]): self.env.scheduler.start_runner(self.runner) self.runner.reload() + self.last_shifted_time = self.runner.clock.shifted_time def again(self, *args, **kwargs): self.runner.update_state(*args, **kwargs) From a46b5aac3717f711c56dd600cdedb23c82dece7d Mon Sep 17 00:00:00 2001 From: thegamecracks <61257169+thegamecracks@users.noreply.github.com> Date: Sun, 16 Jul 2023 18:55:29 -0400 Subject: [PATCH 2/6] Add tolerance when compaaring deadlines in AsyncRunner --- sardine_core/scheduler/async_runner.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sardine_core/scheduler/async_runner.py b/sardine_core/scheduler/async_runner.py index c3fdbc66..6640dbbd 100644 --- a/sardine_core/scheduler/async_runner.py +++ b/sardine_core/scheduler/async_runner.py @@ -1,6 +1,7 @@ import asyncio import heapq import inspect +import math import traceback from collections import deque from dataclasses import dataclass @@ -556,7 +557,10 @@ async def _run_once(self): if ( self.clock.time >= entry.deadline or state is not None - and deadline >= entry.deadline + and ( + deadline > entry.deadline + or math.isclose(deadline, entry.deadline, rel_tol=0.0, abs_tol=1e-8) + ) ): heapq.heappop(self.deferred_states) From 04ca78a5c0434ae7e641a5f198bcee41f2e34342 Mon Sep 17 00:00:00 2001 From: thegamecracks <61257169+thegamecracks@users.noreply.github.com> Date: Mon, 17 Jul 2023 10:31:06 -0400 Subject: [PATCH 3/6] Simplify AsyncRunner deadline tolerance --- sardine_core/scheduler/async_runner.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/sardine_core/scheduler/async_runner.py b/sardine_core/scheduler/async_runner.py index 6640dbbd..6ee5cc76 100644 --- a/sardine_core/scheduler/async_runner.py +++ b/sardine_core/scheduler/async_runner.py @@ -1,7 +1,6 @@ import asyncio import heapq import inspect -import math import traceback from collections import deque from dataclasses import dataclass @@ -557,10 +556,7 @@ async def _run_once(self): if ( self.clock.time >= entry.deadline or state is not None - and ( - deadline > entry.deadline - or math.isclose(deadline, entry.deadline, rel_tol=0.0, abs_tol=1e-8) - ) + and deadline >= entry.deadline - 1e-8 ): heapq.heappop(self.deferred_states) From 558d1176f25776624d46f785ef92287d1ab6f903 Mon Sep 17 00:00:00 2001 From: thegamecracks <61257169+thegamecracks@users.noreply.github.com> Date: Tue, 18 Jul 2023 11:41:47 -0400 Subject: [PATCH 4/6] Replace deadline tolerance with more robust deadline calculations --- sardine_core/scheduler/async_runner.py | 40 +++++++++++++++++--------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/sardine_core/scheduler/async_runner.py b/sardine_core/scheduler/async_runner.py index 6ee5cc76..b632c87c 100644 --- a/sardine_core/scheduler/async_runner.py +++ b/sardine_core/scheduler/async_runner.py @@ -1,6 +1,7 @@ import asyncio import heapq import inspect +import math import traceback from collections import deque from dataclasses import dataclass @@ -190,7 +191,7 @@ class AsyncRunner: _can_correct_interval: bool _expected_time: float _last_interval: float - _last_iteration_called: bool + _last_expected_time: float _last_state: Optional[FunctionState] def __init__(self, name: str): @@ -213,7 +214,7 @@ def __init__(self, name: str): self._can_correct_interval = False self._expected_time = 0.0 self._last_interval = 0.0 - self._last_iteration_called = False + self._last_expected_time = -math.inf self._last_state = None def __repr__(self): @@ -471,13 +472,27 @@ def _get_next_deadline(self, period: Union[float, int]) -> float: Returns: float: The deadline for the next interval. """ - # We only want to use the expected time if the last iteration - # ran its function normally - if the runner skipped the iteration, - # that means we haven't yet reached the deadline - if self._last_iteration_called: - time = self._expected_time - else: - time = self.clock.time + # If this is called earlier than the expected time, we should use + # the current time to avoid calculating the next beat too far ahead, + # which would cause an unusually long gap between iterations. + # + # If this is called after the expected time has already passed, + # we should assume we're continuing from the last iteration and + # ignore whatever the time is. + # This allows returning an overdue deadline if we somehow exceeded + # the new interval (such as caused by a high delta), allowing missed + # iterations to fire ASAP. + # + # Given the above requirements, this would be the ideal solution: + # time = min(self.clock.time, self._expected_time) + # + # However, this is complicated by SleepHandler which does not guarantee + # that a successful iteration will never be earlier than the deadline. + # As such, we will additionally prevent the time from being sooner than + # the last successful iteration. + # If we ignored this and allowed deadlines earlier than the last iteration, + # the above solution could potentially trigger non-missed iterations too early. + time = max(self._last_expected_time, min(self.clock.time, self._expected_time)) self._check_snap(time) if self.snap is not None: @@ -518,7 +533,7 @@ async def _runner(self): print(f"[yellow][Stopped [red]{self.name}[/red]][/yellow]") def _prepare(self): - self._last_iteration_called = False + self._last_expected_time = -math.inf self._last_state = self._get_state() self._swimming = True self._stop = False @@ -556,7 +571,7 @@ async def _run_once(self): if ( self.clock.time >= entry.deadline or state is not None - and deadline >= entry.deadline - 1e-8 + and deadline >= entry.deadline ): heapq.heappop(self.deferred_states) @@ -597,7 +612,7 @@ async def _run_once(self): name=f"asyncrunner-func-{self.name}", ) finally: - self._last_iteration_called = True + self._last_expected_time = self._expected_time async def _call_func(self, func, args, kwargs): """Calls the given function and optionally applies time shift @@ -668,5 +683,4 @@ def _revert_state(self): self._has_reverted = True def _skip_iteration(self) -> None: - self._last_iteration_called = False self.swim() From 66b09c18649571145ea9c26836fe5e666b3adf04 Mon Sep 17 00:00:00 2001 From: thegamecracks <61257169+thegamecracks@users.noreply.github.com> Date: Tue, 18 Jul 2023 11:47:53 -0400 Subject: [PATCH 5/6] Clarify time bound comment in _get_next_deadline() --- sardine_core/scheduler/async_runner.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/sardine_core/scheduler/async_runner.py b/sardine_core/scheduler/async_runner.py index b632c87c..daa8124f 100644 --- a/sardine_core/scheduler/async_runner.py +++ b/sardine_core/scheduler/async_runner.py @@ -477,11 +477,9 @@ def _get_next_deadline(self, period: Union[float, int]) -> float: # which would cause an unusually long gap between iterations. # # If this is called after the expected time has already passed, - # we should assume we're continuing from the last iteration and - # ignore whatever the time is. - # This allows returning an overdue deadline if we somehow exceeded - # the new interval (such as caused by a high delta), allowing missed - # iterations to fire ASAP. + # we should continue from that iteration and ignore the current time. + # This allows returning an overdue deadline potentially caused by a + # high delta, letting missed iterations fire ASAP. # # Given the above requirements, this would be the ideal solution: # time = min(self.clock.time, self._expected_time) From 58fd9df91428fd43b91e58601514c4a761eba859 Mon Sep 17 00:00:00 2001 From: Raphael Forment Date: Tue, 18 Jul 2023 22:35:32 +0200 Subject: [PATCH 6/6] Revert "debug: print player time and try detecting double runs" This reverts commit b34ec985610c374ff5af07878b139ae93cc3d4a9. --- sardine_core/handlers/player.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/sardine_core/handlers/player.py b/sardine_core/handlers/player.py index 400729b9..35a8f065 100644 --- a/sardine_core/handlers/player.py +++ b/sardine_core/handlers/player.py @@ -175,15 +175,6 @@ def func( self.iterator = pattern.iterator pattern.iterator = None - import math - clock = self.runner.clock - is_repeated = math.isclose(clock.shifted_time, self.last_shifted_time, abs_tol=1e-3) - print( - f"{self.name} {clock.shifted_time = :6.3f} {clock.time = :6.3f}, " - f"{self.iterator = :3d} {' X'[is_repeated]}" - ) - self.last_shifted_time = clock.shifted_time - dur = pattern.send_method( *pattern.args, **pattern.kwargs, @@ -240,7 +231,6 @@ def push(self, pattern: Optional[PatternInformation]): self.env.scheduler.start_runner(self.runner) self.runner.reload() - self.last_shifted_time = self.runner.clock.shifted_time def again(self, *args, **kwargs): self.runner.update_state(*args, **kwargs)