Skip to content

Commit

Permalink
app: Add type hinting to base classes, fix docstrings
Browse files Browse the repository at this point in the history
input: Fix missing default value for Controller.open
  • Loading branch information
benmoran56 committed Apr 16, 2024
1 parent 557cf91 commit 007dd8f
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 70 deletions.
6 changes: 3 additions & 3 deletions pyglet/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class AppException(Exception):
"""


def run(interval=1 / 60):
def run(interval: float = 1/60) -> None:
"""Begin processing events, scheduled functions and window updates.
This is a convenience function, equivalent to::
Expand All @@ -76,12 +76,12 @@ def run(interval=1 / 60):
event_loop.run(interval)


def exit():
def exit() -> None:
"""Exit the application event loop.
Causes the application event loop to finish, if an event loop is currently
running. The application may not necessarily exit (for example, there may
be additional code following the `run` invocation).
be additional code following the ``run`` invocation).
This is a convenience function, equivalent to::
Expand Down
122 changes: 56 additions & 66 deletions pyglet/app/base.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
from __future__ import annotations

import sys
import queue
import threading

from typing import TYPE_CHECKING, Any, Callable

from pyglet import app
from pyglet import clock
from pyglet import event

if TYPE_CHECKING:
from pyglet.event import EventDispatcher
from pyglet.window import BaseWindow

_is_pyglet_doc_run = hasattr(sys, "is_pyglet_doc_run") and sys.is_pyglet_doc_run


Expand All @@ -14,19 +22,17 @@ class PlatformEventLoop:
.. versionadded:: 1.2
"""
def __init__(self):
def __init__(self) -> None:
self._event_queue = queue.Queue()
self._is_running = threading.Event()

def is_running(self):
def is_running(self) -> bool:
"""Return True if the event loop is currently processing, or False
if it is blocked or not activated.
:rtype: bool
"""
return self._is_running.is_set()

def post_event(self, dispatcher, event, *args):
def post_event(self, dispatcher: EventDispatcher, event: str, *args: Any) -> None:
"""Post an event into the main application thread.
The event is queued internally until the :py:meth:`run` method's thread
Expand All @@ -37,20 +43,11 @@ def post_event(self, dispatcher, event, *args):
example, from within an event handler), the event may be dispatched
within the same runloop iteration or the next one; the choice is
nondeterministic.
:Parameters:
`dispatcher` : EventDispatcher
Dispatcher to process the event.
`event` : str
Event name.
`args` : sequence
Arguments to pass to the event handlers.
"""
self._event_queue.put((dispatcher, event, args))
self.notify()

def dispatch_posted_events(self):
def dispatch_posted_events(self) -> None:
"""Immediately dispatch all pending events.
Normally this is called automatically by the runloop iteration.
Expand All @@ -65,25 +62,25 @@ def dispatch_posted_events(self):
# weakly-referenced object no longer exists
pass

def notify(self):
def notify(self) -> None:
"""Notify the event loop that something needs processing.
If the event loop is blocked, it will unblock and perform an iteration
immediately. If the event loop is running, another iteration is
scheduled for immediate execution afterwards.
scheduled for immediate execution afterward.
"""
raise NotImplementedError('abstract')

def start(self):
def start(self) -> None:
pass

def step(self, timeout=None):
def step(self, timeout: None | float = None):
raise NotImplementedError('abstract')

def set_timer(self, func, interval):
def set_timer(self, func: Callable, interval: float) -> None:
pass

def stop(self):
def stop(self) -> None:
pass


Expand All @@ -105,7 +102,7 @@ class EventLoop(event.EventDispatcher):
_has_exit_condition = None
_has_exit = False

def __init__(self):
def __init__(self) -> None:
self._has_exit_condition = threading.Condition()
self.clock = clock.get_default()
self.is_running = False
Expand All @@ -114,30 +111,34 @@ def __init__(self):
def _redraw_windows(dt):
# Redraw all windows
for window in app.windows:
window.switch_to()
window.dispatch_event('on_draw')
window.dispatch_event('on_refresh', dt)
window.flip()
window.draw(dt)

def run(self, interval=1/60):
def run(self, interval: None | float = 1/60):
"""Begin processing events, scheduled functions and window updates.
:Parameters:
`interval` : float or None [default: 1/60]
Windows redraw interval, in seconds (framerate).
If `interval == 0`, windows will redraw at maximum rate.
If `interval is None`, Pyglet will not call its redraw function.
The user must schedule (or call on demand) a custom redraw
function for each window, allowing a custom framerate per window.
(see example in documentation)
This method enters into the main event loop and, if the ``interval``
argument is not changed, schedules calling the :py:meth:`pyglet.window.Window.draw`
method. You can change the ``interval`` argument to suit your needs.
This method returns when :py:attr:`has_exit` is set to True.
Developers are discouraged from overriding this method, as the
Args:
interval:
Windows redraw interval, in seconds (framerate).
If ``interval == 0``, windows will redraw as fast as possible.
This can saturate a CPU core, so do not do this unless GPU bound.
If ``interval is None``, pyglet will not schedule calls to the
:py:meth:`pyglet.window.Window.draw` method. Users must schedule
this themselves for each Window (or call it on-demand). This allows
setting a custom framerate per window, or changing framerate during
runtime (see example in the documentation).
This method returns when :py:attr:`has_exit` is set to True. IE: when
:py:meth:`exit` is called.
Developers are discouraged from overriding the ``run`` method, as the
implementation is platform-specific.
"""
if interval is None:
# User application will manage a custom _redraw_windows() method
# User must call Window.draw() themselves.
pass
elif interval == 0:
self.clock.schedule(self._redraw_windows)
Expand Down Expand Up @@ -167,7 +168,7 @@ def run(self, interval=1/60):
self.dispatch_event('on_exit')
platform_event_loop.stop()

def enter_blocking(self):
def enter_blocking(self) -> None:
"""Called by pyglet internal processes when the operating system
is about to block due to a user interaction. For example, this
is common when the user begins resizing or moving a window.
Expand All @@ -185,7 +186,7 @@ def enter_blocking(self):
app.platform_event_loop.set_timer(self._blocking_timer, timeout)

@staticmethod
def exit_blocking():
def exit_blocking() -> None:
"""Called by pyglet internal processes when the blocking operation
completes. See :py:meth:`enter_blocking`.
"""
Expand All @@ -195,7 +196,7 @@ def _blocking_timer(self):
timeout = self.idle()
app.platform_event_loop.set_timer(self._blocking_timer, timeout)

def idle(self):
def idle(self) -> None | float:
"""Called during each iteration of the event loop.
The method is called immediately after any window events (i.e., after
Expand All @@ -216,9 +217,9 @@ def idle(self):
code execute at regular intervals, use the
:py:func:`pyglet.clock.schedule` methods.
:rtype: float
:return: The number of seconds before the idle method should
be called again, or `None` to block for user input.
Returns:
The number of seconds before the idle method should
be called again, or ``None`` to block for user input.
"""
dt = self.clock.update_time()
self.clock.call_scheduled_functions(dt)
Expand All @@ -227,29 +228,26 @@ def idle(self):
return self.clock.get_sleep_time(True)

@property
def has_exit(self):
def has_exit(self) -> bool:
"""Flag indicating if the event loop will exit in
the next iteration. When set, all waiting threads are interrupted (see
:py:meth:`sleep`).
Thread-safe since pyglet 1.2.
:see: `exit`
:type: bool
"""
self._has_exit_condition.acquire()
result = self._has_exit
self._has_exit_condition.release()
return result

@has_exit.setter
def has_exit(self, value):
def has_exit(self, value: bool) -> None:
self._has_exit_condition.acquire()
self._has_exit = value
self._has_exit_condition.notify()
self._has_exit_condition.release()

def exit(self):
def exit(self) -> None:
"""Safely exit the event loop at the end of the current iteration.
This method is a thread-safe equivalent for setting
Expand All @@ -259,34 +257,32 @@ def exit(self):
self.has_exit = True
app.platform_event_loop.notify()

def sleep(self, timeout):
def sleep(self, timeout: float) -> bool:
"""Wait for some amount of time, or until the :py:attr:`has_exit` flag
is set or :py:meth:`exit` is called.
This method is thread-safe.
:Parameters:
`timeout` : float
Time to wait, in seconds.
Args:
timeout: Time to sleep, in seconds.
.. versionadded:: 1.2
:rtype: bool
:return: ``True`` if the `has_exit` flag is set, otherwise ``False``.
"""
self._has_exit_condition.acquire()
self._has_exit_condition.wait(timeout)
result = self._has_exit
self._has_exit_condition.release()
return result

def on_window_close(self, window):
def on_window_close(self, window: BaseWindow):
"""Default window close handler."""
if len(app.windows) == 0:
self.exit()

if _is_pyglet_doc_run:
def on_window_close(self, window):
# Events

def on_window_close(self, window: BaseWindow):
"""A window was closed.
This event is dispatched when a window is closed. It is not
Expand All @@ -296,8 +292,6 @@ def on_window_close(self, window):
The default handler calls :py:meth:`exit` if no more windows are
open. You can override this handler to base your application exit
on some other policy.
:event:
"""

def on_enter(self):
Expand All @@ -306,8 +300,6 @@ def on_enter(self):
This is dispatched when the event loop is prepared to enter
the main run loop, and represents the last chance for an
application to initialise itself.
:event:
"""

def on_exit(self):
Expand All @@ -316,8 +308,6 @@ def on_exit(self):
After dispatching this event, the :py:meth:`run` method returns (the
application may not actually exit if you have more code
following the :py:meth:`run` invocation).
:event:
"""


Expand Down
2 changes: 1 addition & 1 deletion pyglet/input/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,7 +718,7 @@ def _initialize_controls(self) -> None:
warnings.warn(f"Could not find control '{name}' with index '{relation.index}'.")
continue

def open(self, window: None | BaseWindow, exclusive: bool = False) -> None:
def open(self, window: None | BaseWindow = None, exclusive: bool = False) -> None:
"""Open the controller. See `Device.open`. """
self.device.open(window, exclusive)

Expand Down

0 comments on commit 007dd8f

Please sign in to comment.