287 lines
7.6 KiB
Python
287 lines
7.6 KiB
Python
""" This module reimplements Python's native thread module using Panda
|
|
threading constructs. It's designed as a drop-in replacement for the
|
|
thread module for code that works with Panda; it is necessary because
|
|
in some compilation models, Panda's threading constructs are
|
|
incompatible with the OS-provided threads used by Python's thread
|
|
module. """
|
|
|
|
__all__ = [
|
|
'error', 'LockType',
|
|
'start_new_thread',
|
|
'interrupt_main',
|
|
'exit', 'allocate_lock', 'get_ident',
|
|
'stack_size',
|
|
'force_yield', 'consider_yield',
|
|
'forceYield', 'considerYield',
|
|
'TIMEOUT_MAX'
|
|
]
|
|
|
|
from panda3d import core
|
|
import sys
|
|
|
|
if sys.platform == "win32":
|
|
TIMEOUT_MAX = float(0xffffffff // 1000)
|
|
else:
|
|
TIMEOUT_MAX = float(0x7fffffffffffffff // 1000000000)
|
|
|
|
# These methods are defined in Panda, and are particularly useful if
|
|
# you may be running in Panda's SIMPLE_THREADS compilation mode.
|
|
force_yield = core.Thread.force_yield
|
|
consider_yield = core.Thread.consider_yield
|
|
|
|
forceYield = force_yield
|
|
considerYield = consider_yield
|
|
|
|
if sys.version_info >= (3, 3):
|
|
error = RuntimeError
|
|
else:
|
|
class error(Exception):
|
|
pass
|
|
|
|
class LockType:
|
|
""" Implements a mutex lock. Instead of directly subclassing
|
|
PandaModules.Mutex, we reimplement the lock here, to allow us to
|
|
provide the described Python lock semantics. In particular, this
|
|
allows a different thread to release the lock than the one that
|
|
acquired it. """
|
|
|
|
def __init__(self):
|
|
self.__lock = core.Mutex('PythonLock')
|
|
self.__cvar = core.ConditionVar(self.__lock)
|
|
self.__locked = False
|
|
|
|
def acquire(self, waitflag = 1, timeout = -1):
|
|
self.__lock.acquire()
|
|
try:
|
|
if self.__locked and not waitflag:
|
|
return False
|
|
|
|
if timeout >= 0:
|
|
while self.__locked:
|
|
self.__cvar.wait(timeout)
|
|
else:
|
|
while self.__locked:
|
|
self.__cvar.wait()
|
|
|
|
self.__locked = True
|
|
return True
|
|
|
|
finally:
|
|
self.__lock.release()
|
|
|
|
def release(self):
|
|
self.__lock.acquire()
|
|
try:
|
|
if not self.__locked:
|
|
raise error('Releasing unheld lock.')
|
|
|
|
self.__locked = False
|
|
self.__cvar.notify()
|
|
|
|
finally:
|
|
self.__lock.release()
|
|
|
|
def locked(self):
|
|
return self.__locked
|
|
|
|
__enter__ = acquire
|
|
|
|
def __exit__(self, t, v, tb):
|
|
self.release()
|
|
|
|
# Helper to generate new thread names
|
|
_counter = 0
|
|
def _newname(template="Thread-%d"):
|
|
global _counter
|
|
_counter = _counter + 1
|
|
return template % _counter
|
|
|
|
_threads = {}
|
|
_nextThreadId = 0
|
|
_threadsLock = core.Mutex('thread._threadsLock')
|
|
|
|
def start_new_thread(function, args, kwargs = {}, name = None):
|
|
def threadFunc(threadId, function = function, args = args, kwargs = kwargs):
|
|
try:
|
|
try:
|
|
function(*args, **kwargs)
|
|
except SystemExit:
|
|
pass
|
|
|
|
finally:
|
|
_remove_thread_id(threadId)
|
|
|
|
global _nextThreadId
|
|
_threadsLock.acquire()
|
|
try:
|
|
threadId = _nextThreadId
|
|
_nextThreadId += 1
|
|
|
|
if name is None:
|
|
name = 'PythonThread-%s' % (threadId)
|
|
|
|
thread = core.PythonThread(threadFunc, [threadId], name, name)
|
|
thread.setPythonIndex(threadId)
|
|
_threads[threadId] = (thread, {}, None)
|
|
|
|
thread.start(core.TPNormal, False)
|
|
return threadId
|
|
|
|
finally:
|
|
_threadsLock.release()
|
|
|
|
def _add_thread(thread, wrapper):
|
|
""" Adds the indicated core.Thread object, with the indicated Python
|
|
wrapper, to the thread list. Returns the new thread ID. """
|
|
|
|
global _nextThreadId
|
|
_threadsLock.acquire()
|
|
try:
|
|
threadId = _nextThreadId
|
|
_nextThreadId += 1
|
|
|
|
thread.setPythonIndex(threadId)
|
|
_threads[threadId] = (thread, {}, wrapper)
|
|
return threadId
|
|
|
|
finally:
|
|
_threadsLock.release()
|
|
|
|
def _get_thread_wrapper(thread, wrapperClass):
|
|
""" Returns the thread wrapper for the indicated thread. If there
|
|
is not one, creates an instance of the indicated wrapperClass
|
|
instead. """
|
|
|
|
threadId = thread.getPythonIndex()
|
|
if threadId == -1:
|
|
# The thread has never been assigned a threadId. Go assign one.
|
|
|
|
global _nextThreadId
|
|
_threadsLock.acquire()
|
|
try:
|
|
threadId = _nextThreadId
|
|
_nextThreadId += 1
|
|
|
|
thread.setPythonIndex(threadId)
|
|
wrapper = wrapperClass(thread, threadId)
|
|
_threads[threadId] = (thread, {}, wrapper)
|
|
return wrapper
|
|
|
|
finally:
|
|
_threadsLock.release()
|
|
|
|
else:
|
|
# The thread has been assigned a threadId. Look for the wrapper.
|
|
_threadsLock.acquire()
|
|
try:
|
|
t, locals, wrapper = _threads[threadId]
|
|
assert t == thread
|
|
if wrapper is None:
|
|
wrapper = wrapperClass(thread, threadId)
|
|
_threads[threadId] = (thread, locals, wrapper)
|
|
return wrapper
|
|
|
|
finally:
|
|
_threadsLock.release()
|
|
|
|
def _get_thread_locals(thread, i):
|
|
""" Returns the locals dictionary for the indicated thread. If
|
|
there is not one, creates an empty dictionary. """
|
|
|
|
threadId = thread.getPythonIndex()
|
|
if threadId == -1:
|
|
# The thread has never been assigned a threadId. Go assign one.
|
|
|
|
global _nextThreadId
|
|
_threadsLock.acquire()
|
|
try:
|
|
threadId = _nextThreadId
|
|
_nextThreadId += 1
|
|
|
|
thread.setPythonIndex(threadId)
|
|
locals = {}
|
|
_threads[threadId] = (thread, locals, None)
|
|
return locals.setdefault(i, {})
|
|
|
|
finally:
|
|
_threadsLock.release()
|
|
|
|
else:
|
|
# The thread has been assigned a threadId. Get the locals.
|
|
_threadsLock.acquire()
|
|
try:
|
|
t, locals, wrapper = _threads[threadId]
|
|
assert t == thread
|
|
return locals.setdefault(i, {})
|
|
|
|
finally:
|
|
_threadsLock.release()
|
|
|
|
|
|
def _remove_thread_id(threadId):
|
|
""" Removes the thread with the indicated ID from the thread list. """
|
|
|
|
# On interpreter shutdown, Python may set module globals to None.
|
|
if _threadsLock is None or _threads is None:
|
|
return
|
|
|
|
_threadsLock.acquire()
|
|
try:
|
|
if threadId in _threads:
|
|
thread, locals, wrapper = _threads[threadId]
|
|
assert thread.getPythonIndex() == threadId
|
|
del _threads[threadId]
|
|
thread.setPythonIndex(-1)
|
|
|
|
finally:
|
|
_threadsLock.release()
|
|
|
|
|
|
def interrupt_main():
|
|
# TODO.
|
|
pass
|
|
|
|
def exit():
|
|
raise SystemExit
|
|
|
|
def allocate_lock():
|
|
return LockType()
|
|
|
|
def get_ident():
|
|
return core.Thread.getCurrentThread().this
|
|
|
|
def stack_size(size = 0):
|
|
raise error
|
|
|
|
|
|
class _local(object):
|
|
""" This class provides local thread storage using Panda's
|
|
threading system. """
|
|
|
|
def __del__(self):
|
|
i = id(self)
|
|
|
|
# Delete this key from all threads.
|
|
_threadsLock.acquire()
|
|
try:
|
|
for thread, locals, wrapper in list(_threads.values()):
|
|
try:
|
|
del locals[i]
|
|
except KeyError:
|
|
pass
|
|
|
|
finally:
|
|
_threadsLock.release()
|
|
|
|
def __setattr__(self, key, value):
|
|
d = _get_thread_locals(core.Thread.getCurrentThread(), id(self))
|
|
d[key] = value
|
|
|
|
def __getattribute__(self, key):
|
|
d = _get_thread_locals(core.Thread.getCurrentThread(), id(self))
|
|
if key == '__dict__':
|
|
return d
|
|
try:
|
|
return d[key]
|
|
except KeyError:
|
|
return object.__getattribute__(self, key)
|