Poodletooth-iLand/panda/direct/stdpy/thread.py
2015-03-03 17:10:12 -05:00

267 lines
7.2 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',
'forceYield', 'considerYield',
]
# Import PandaModules as pm, so we don't have any namespace collisions.
from pandac import PandaModules as pm
# These methods are defined in Panda, and are particularly useful if
# you may be running in Panda's SIMPLE_THREADS compilation mode.
forceYield = pm.Thread.forceYield
considerYield = pm.Thread.considerYield
class error(StandardError):
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 = pm.Mutex('PythonLock')
self.__cvar = pm.ConditionVar(self.__lock)
self.__locked = False
def acquire(self, waitflag = 1):
self.__lock.acquire()
try:
if self.__locked and not waitflag:
return False
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()
_threads = {}
_nextThreadId = 0
_threadsLock = pm.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 = pm.PythonThread(threadFunc, [threadId], name, name)
thread.setPythonData(threadId)
_threads[threadId] = (thread, {}, None)
thread.start(pm.TPNormal, False)
return threadId
finally:
_threadsLock.release()
def _add_thread(thread, wrapper):
""" Adds the indicated pm.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.setPythonData(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.getPythonData()
if threadId is None:
# The thread has never been assigned a threadId. Go assign one.
global _nextThreadId
_threadsLock.acquire()
try:
threadId = _nextThreadId
_nextThreadId += 1
thread.setPythonData(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.getPythonData()
if threadId is None:
# The thread has never been assigned a threadId. Go assign one.
global _nextThreadId
_threadsLock.acquire()
try:
threadId = _nextThreadId
_nextThreadId += 1
thread.setPythonData(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. """
_threadsLock.acquire()
try:
thread, locals, wrapper = _threads[threadId]
assert thread.getPythonData() == threadId
del _threads[threadId]
thread.setPythonData(None)
finally:
_threadsLock.release()
def interrupt_main():
# TODO.
pass
def exit():
raise SystemExit
def allocate_lock():
return LockType()
def get_ident():
return pm.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 _threads.values():
try:
del locals[i]
except KeyError:
pass
finally:
_threadsLock.release()
def __setattr__(self, key, value):
d = _get_thread_locals(pm.Thread.getCurrentThread(), id(self))
d[key] = value
## def __getattr__(self, key):
## d = _get_thread_locals(pm.Thread.getCurrentThread(), id(self))
## try:
## return d[key]
## except KeyError:
## raise AttributeError
def __getattribute__(self, key):
d = _get_thread_locals(pm.Thread.getCurrentThread(), id(self))
if key == '__dict__':
return d
try:
return d[key]
except KeyError:
return object.__getattribute__(self, key)