Difficult-Rocket/libs/pyglet/media/mediathreads.py

130 lines
3.9 KiB
Python
Raw Normal View History

import time
import atexit
import threading
import pyglet
from pyglet.util import debug_print
_debug = debug_print('debug_media')
2023-06-27 01:05:51 +08:00
class PlayerWorkerThread(threading.Thread):
"""Worker thread for refilling players. Exits on interpreter shutdown,
provides a notify method to interrupt it as well as a termination method.
"""
2023-06-27 01:05:51 +08:00
_threads = set()
2023-06-27 01:05:51 +08:00
# Time to wait if there are players, but they're all full:
_nap_time = 0.05
def __init__(self):
2023-06-27 01:05:51 +08:00
super().__init__(daemon=True)
self._rest_event = threading.Event()
# A lock that should be held as long as consistency of `self.players` is required.
self._operation_lock = threading.Lock()
self._stopped = False
2023-06-27 01:05:51 +08:00
self.players = set()
def run(self):
if pyglet.options['debug_trace']:
pyglet._install_trace()
2023-06-27 01:05:51 +08:00
self._threads.add(self)
sleep_time = None
while True:
assert _debug(
'PlayerWorkerThread: Going to sleep ' +
('indefinitely; no active players' if sleep_time is None else f'for {sleep_time}')
)
self._rest_event.wait(sleep_time)
self._rest_event.clear()
assert _debug(f'PlayerWorkerThread: woke up @{time.time()}')
if self._stopped:
break
with self._operation_lock:
if self.players:
sleep_time = self._nap_time
for player in self.players:
player.refill_buffer()
else:
# sleep until a player is added
sleep_time = None
2023-06-27 01:05:51 +08:00
self._threads.remove(self)
def stop(self):
"""Stop the thread and wait for it to terminate.
2023-06-27 01:05:51 +08:00
The `stop` instance variable is set to ``True`` and the rest event
is set. It is the responsibility of the `run` method to check
the value of `_stopped` after each sleep or wait and to return if
set.
"""
2023-06-27 01:05:51 +08:00
assert _debug('PlayerWorkerThread.stop()')
self._stopped = True
self._rest_event.set()
2023-06-09 14:48:51 +08:00
try:
2023-06-27 01:05:51 +08:00
self.join()
2023-06-09 14:48:51 +08:00
except RuntimeError:
# Ignore on unclean shutdown
pass
def notify(self):
"""Interrupt the current sleep operation.
If the thread is currently sleeping, it will be woken immediately,
instead of waiting the full duration of the timeout.
2023-06-27 01:05:51 +08:00
If the thread is not sleeping, it will run again as soon as it is
done with its operation.
"""
2023-06-27 01:05:51 +08:00
assert _debug('PlayerWorkerThread.notify()')
self._rest_event.set()
2023-06-27 01:05:51 +08:00
def add(self, player):
"""
Add a player to the PlayerWorkerThread; which will call
`refill_buffer` on it regularly. Notify the thread as well.
2023-06-27 01:05:51 +08:00
Do not call this method from within the thread, as it will deadlock.
"""
assert player is not None
assert _debug('PlayerWorkerThread: player added')
2023-06-27 01:05:51 +08:00
with self._operation_lock:
self.players.add(player)
2023-06-27 01:05:51 +08:00
self.notify()
2023-06-27 01:05:51 +08:00
def remove(self, player):
"""
Remove a player from the PlayerWorkerThread, or ignore if it does
not exist.
2023-06-27 01:05:51 +08:00
Do not call this method from within the thread, as it may deadlock.
"""
assert _debug('PlayerWorkerThread: player removed')
2023-06-27 01:05:51 +08:00
if player in self.players:
with self._operation_lock:
self.players.remove(player)
2023-06-27 01:05:51 +08:00
# self.notify()
2023-06-27 01:05:51 +08:00
@classmethod
def atexit(cls):
for thread in list(cls._threads):
thread.stop()
# Can't be 100% sure that all threads are stopped here as it is technically possible that
# a thread may just have removed itself from cls._threads as the last action in `run()`
# and then was unscheduled; But it will definitely finish very soon after anyways
2023-06-27 01:05:51 +08:00
atexit.register(PlayerWorkerThread.atexit)