Skip to content

Instantly share code, notes, and snippets.

@cessor
Created June 19, 2021 07:04
Show Gist options
  • Save cessor/044620b10be11b73c0459a9505b2caef to your computer and use it in GitHub Desktop.
Save cessor/044620b10be11b73c0459a9505b2caef to your computer and use it in GitHub Desktop.
class Duration(object):
'''
Calculate duration with 60 FPS (16.66 ms per frame)
Make it so that the duration can be compared using >=,
so that the NEXT Frame will actually display a stimulus.
Example:
Suppose you want to display a stimulus after 225 ms. This is technically impossible on a 60hz screen with a synchronized display because each frame takes 0.0167 seconds (16.7ms) to render, and so the stimulus can either draw for 216.7ms or 233.3ms. I discussed with the product owner (Jan Rummel) that we should opt for the earlier stimulus, because otherwise the milliseconds add up.
During each frame, an update is sent to the system whether the next
stimulus should be displayed during the next frame.
If so, the stimulus will draw itself onto the window.
The next flip command will then display the stimulus,
therefore we need to decide to draw one frame ahead.
In other words:
225ms is between frame 13 (216.7ms) and frame 14 (233.3ms).
Therefore, we draw during frame 13 (216.7ms), so that
frame 14 (233.3ms) contains the stimulus.
Frame Time ms
-----------------
...
FRAME 10, t=0.1667
DRAW
FRAME 11, t=0.1833
DRAW
FRAME 12, t=0.2000
DRAW
FRAME 13, t=0.2167 # 1. Timeout?
DRAW # 2. Draw Here!
FRAME 14, t=0.2333 # 3. Frame contains stimulus
DRAW
FRAME 15, t=0.2500 # 4. Too Late
DRAW
FRAME 16, t=0.2667
FRAME 17, t=0.2833
1. We need to check whether this frame is hit
2. If so, we need to draw the stimulus...
3. ...so that the next frame displays it
4. This frame would be too late
'''
FREQUENCY = 1 / 60 * 1000
def __init__(self, duration_ms: int):
self._duration_ms = duration_ms
self._frames = int(self._duration_ms // self.FREQUENCY)
def ms(self):
return self._duration_ms
def frames(self):
return self._frames
def frames_elapsed(self, frames: int):
return frames >= self._frames
def __repr__(self):
return f'<Duration: {self._duration_ms} ms ({self._frames} f)>'
class Forever(Duration):
def __init__(self):
super().__init__(0)
def frames_elapsed(self, frames):
return False
def __repr__(self):
return '<Forever>'
# Add static factory
Duration.Forever = Forever
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment