Spaces:
Runtime error
Runtime error
File size: 4,590 Bytes
d82cf6a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
import time
import atexit
import threading
import pyglet
from pyglet.util import debug_print
_debug = debug_print('debug_media')
class MediaThread:
"""A thread that cleanly exits on interpreter shutdown, and provides
a sleep method that can be interrupted and a termination method.
:Ivariables:
`_condition` : threading.Condition
Lock _condition on all instance variables.
`_stopped` : bool
True if `stop` has been called.
"""
_threads = set()
_threads_lock = threading.Lock()
def __init__(self):
self._thread = threading.Thread(target=self._thread_run, daemon=True)
self._condition = threading.Condition()
self._stopped = False
def run(self):
raise NotImplementedError
def _thread_run(self):
if pyglet.options['debug_trace']:
pyglet._install_trace()
with self._threads_lock:
self._threads.add(self)
self.run()
with self._threads_lock:
self._threads.remove(self)
def start(self):
self._thread.start()
def stop(self):
"""Stop the thread and wait for it to terminate.
The `stop` instance variable is set to ``True`` and the condition is
notified. It is the responsibility of the `run` method to check
the value of `stop` after each sleep or wait and to return if set.
"""
assert _debug('MediaThread.stop()')
with self._condition:
self._stopped = True
self._condition.notify()
self._thread.join()
def sleep(self, timeout):
"""Wait for some amount of time, or until notified.
:Parameters:
`timeout` : float
Time to wait, in seconds.
"""
assert _debug(f'MediaThread.sleep({timeout!r})')
with self._condition:
if not self._stopped:
self._condition.wait(timeout)
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.
"""
assert _debug('MediaThread.notify()')
with self._condition:
self._condition.notify()
@classmethod
def atexit(cls):
with cls._threads_lock:
threads = list(cls._threads)
for thread in threads:
thread.stop()
atexit.register(MediaThread.atexit)
class PlayerWorkerThread(MediaThread):
"""Worker thread for refilling players."""
# Time to wait if there are players, but they're all full:
_nap_time = 0.05
def __init__(self):
super().__init__()
self.players = set()
def run(self):
while True:
# This is a big lock, but ensures a player is not deleted while
# we're processing it -- this saves on extra checks in the
# player's methods that would otherwise have to check that it's
# still alive.
with self._condition:
assert _debug('PlayerWorkerThread: woke up @{}'.format(time.time()))
if self._stopped:
break
sleep_time = -1
if self.players:
filled = False
for player in list(self.players):
filled = player.refill_buffer()
if not filled:
sleep_time = self._nap_time
else:
assert _debug('PlayerWorkerThread: No active players')
sleep_time = None # sleep until a player is added
if sleep_time != -1:
self.sleep(sleep_time)
else:
# We MUST sleep, or we will starve pyglet's main loop. It
# also looks like if we don't sleep enough, we'll starve out
# various updates that stop us from properly removing players
# that should be removed.
self.sleep(self._nap_time)
def add(self, player):
assert player is not None
assert _debug('PlayerWorkerThread: player added')
with self._condition:
self.players.add(player)
self._condition.notify()
def remove(self, player):
assert _debug('PlayerWorkerThread: player removed')
with self._condition:
if player in self.players:
self.players.remove(player)
self._condition.notify()
|