""" 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
error = RuntimeError
[docs]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. """
[docs] def __init__(self):
self.__lock = core.Mutex('PythonLock')
self.__cvar = core.ConditionVar(self.__lock)
self.__locked = False
[docs] 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()
[docs] 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()
[docs] 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')
[docs]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()
[docs]def interrupt_main():
# TODO.
pass
[docs]def exit():
raise SystemExit
[docs]def allocate_lock():
return LockType()
[docs]def get_ident():
return core.Thread.getCurrentThread().this
[docs]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)