| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698 |
- """Undocumented Module"""
- __all__ = ['Messenger']
- from PythonUtil import *
- from direct.directnotify import DirectNotifyGlobal
- import types
- # This one line will replace the cheesy hack below, when we remove the
- # hack.
- #from direct.stdpy.threading import Lock
- class Lock:
- """ This is a cheesy delayed implementation of Lock, designed to
- support the Toontown ActiveX launch, which must import Messenger
- before it has downloaded the rest of Panda. This is a TEMPORARY
- HACK, to be removed when the ActiveX launch is retired. """
- notify = DirectNotifyGlobal.directNotify.newCategory("Messenger.Lock")
- def __init__(self):
- self.locked = 0
- def acquire(self):
- # Before we download Panda, we can't use any threading
- # interfaces. So don't, until we observe that we have some
- # actual contention on the lock.
- if self.locked:
- # We have contention.
- return self.__getLock()
-
- # This relies on the fact that any individual Python statement
- # is atomic.
- self.locked += 1
- if self.locked > 1:
- # Whoops, we have contention.
- self.locked -= 1
- return self.__getLock()
- def release(self):
- if self.locked:
- # Still using the old, cheesy lock.
- self.locked -= 1
- return
- # The new lock must have been put in place.
- self.release = self.lock.release
- return self.lock.release()
- def __getLock(self):
- # Now that we've started Panda, it's safe to import the Mutex
- # class, which becomes our actual lock.
- # From now on, this lock will be used.
- self.notify.info("Acquiring Panda lock for the first time.")
- from pandac.PandaModules import Thread, Mutex
- self.__dict__.setdefault('lock', Mutex('Messenger'))
- self.lock.acquire()
-
- self.acquire = self.lock.acquire
- # Wait for the cheesy lock to be released before we return.
- self.notify.info("Waiting for cheesy lock to be released.")
- while self.locked:
- Thread.forceYield()
- self.notify.info("Got cheesy lock.")
- # We return with the lock acquired.
-
- class Messenger:
- notify = DirectNotifyGlobal.directNotify.newCategory("Messenger")
- def __init__(self):
- """
- One is keyed off the event name. It has the following structure:
- {event1: {object1: [method, extraArgs, persistent],
- object2: [method, extraArgs, persistent]},
- event2: {object1: [method, extraArgs, persistent],
- object2: [method, extraArgs, persistent]}}
- This dictionary allow for efficient callbacks when the messenger
- hears an event.
- A second dictionary remembers which objects are accepting which
- events. This allows for efficient ignoreAll commands.
- Or, for an example with more real data:
- {'mouseDown': {avatar: [avatar.jump, [2.0], 1]}}
- """
- # eventName->objMsgrId->callbackInfo
- self.__callbacks = {}
- # objMsgrId->set(eventName)
- self.__objectEvents = {}
- self._messengerIdGen = 0
- # objMsgrId->listenerObject
- self._id2object = {}
- # A mapping of taskChain -> eventList, used for sending events
- # across task chains (and therefore across threads).
- self._eventQueuesByTaskChain = {}
- # This protects the data structures within this object from
- # multithreaded access.
- self.lock = Lock()
- if __debug__:
- self.__isWatching=0
- self.__watching={}
- # I'd like this to be in the __debug__, but I fear that someone will
- # want this in a release build. If you're sure that that will not be
- # then please remove this comment and put the quiet/verbose stuff
- # under __debug__.
- self.quieting={"NewFrame":1,
- "avatarMoving":1,
- "event-loop-done":1,
- 'collisionLoopFinished':1,
- } # see def quiet()
- def _getMessengerId(self, object):
- # TODO: allocate this id in DirectObject.__init__ and get derived
- # classes to call down (speed optimization, assuming objects
- # accept/ignore more than once over their lifetime)
- # get unique messenger id for this object
- # assumes lock is held.
- if not hasattr(object, '_MSGRmessengerId'):
- object._MSGRmessengerId = (object.__class__.__name__, self._messengerIdGen)
- self._messengerIdGen += 1
- return object._MSGRmessengerId
- def _storeObject(self, object):
- # store reference-counted reference to object in case we need to
- # retrieve it later. assumes lock is held.
- id = self._getMessengerId(object)
- if id not in self._id2object:
- self._id2object[id] = [1, object]
- else:
- self._id2object[id][0] += 1
- def _getObject(self, id):
- return self._id2object[id][1]
- def _getObjects(self):
- self.lock.acquire()
- try:
- objs = []
- for refCount, obj in self._id2object.itervalues():
- objs.append(obj)
- return objs
- finally:
- self.lock.release()
- def _getNumListeners(self, event):
- return len(self.__callbacks.get(event, {}))
- def _getEvents(self):
- return self.__callbacks.keys()
- def _releaseObject(self, object):
- # assumes lock is held.
- id = self._getMessengerId(object)
- if id in self._id2object:
- record = self._id2object[id]
- record[0] -= 1
- if record[0] <= 0:
- del self._id2object[id]
- def accept(self, event, object, method, extraArgs=[], persistent=1):
- """ accept(self, string, DirectObject, Function, List, Boolean)
- Make this object accept this event. When the event is
- sent (using Messenger.send or from C++), method will be executed,
- optionally passing in extraArgs.
- If the persistent flag is set, it will continue to respond
- to this event, otherwise it will respond only once.
- """
- notifyDebug = Messenger.notify.getDebug()
- if notifyDebug:
- Messenger.notify.debug(
- "object: %s (%s)\n accepting: %s\n method: %s\n extraArgs: %s\n persistent: %s" %
- (safeRepr(object), self._getMessengerId(object), event, safeRepr(method),
- safeRepr(extraArgs), persistent))
- # Make sure that the method is callable
- assert hasattr(method, '__call__'), (
- "method not callable in accept (ignoring): %s %s"%
- (safeRepr(method), safeRepr(extraArgs)))
- # Make sure extraArgs is a list or tuple
- if not (isinstance(extraArgs, list) or isinstance(extraArgs, tuple) or isinstance(extraArgs, set)):
- raise TypeError, "A list is required as extraArgs argument"
- self.lock.acquire()
- try:
- acceptorDict = self.__callbacks.setdefault(event, {})
- id = self._getMessengerId(object)
- # Make sure we are not inadvertently overwriting an existing event
- # on this particular object.
- if id in acceptorDict:
- # TODO: we're replacing the existing callback. should this be an error?
- if notifyDebug:
- oldMethod = acceptorDict[id][0]
- if oldMethod == method:
- self.notify.warning(
- "object: %s was already accepting: \"%s\" with same callback: %s()" %
- (object.__class__.__name__, safeRepr(event), method.__name__))
- else:
- self.notify.warning(
- "object: %s accept: \"%s\" new callback: %s() supplanting old callback: %s()" %
- (object.__class__.__name__, safeRepr(event), method.__name__, oldMethod.__name__))
- acceptorDict[id] = [method, extraArgs, persistent]
- # Remember that this object is listening for this event
- eventDict = self.__objectEvents.setdefault(id, {})
- if event not in eventDict:
- self._storeObject(object)
- eventDict[event] = None
- finally:
- self.lock.release()
- def ignore(self, event, object):
- """ ignore(self, string, DirectObject)
- Make this object no longer respond to this event.
- It is safe to call even if it was not already accepting
- """
- if Messenger.notify.getDebug():
- Messenger.notify.debug(
- safeRepr(object) + ' (%s)\n now ignoring: ' % (self._getMessengerId(object), ) + safeRepr(event))
- self.lock.acquire()
- try:
- id = self._getMessengerId(object)
- # Find the dictionary of all the objects accepting this event
- acceptorDict = self.__callbacks.get(event)
- # If this object is there, delete it from the dictionary
- if acceptorDict and id in acceptorDict:
- del acceptorDict[id]
- # If this dictionary is now empty, remove the event
- # entry from the Messenger alltogether
- if (len(acceptorDict) == 0):
- del self.__callbacks[event]
- # This object is no longer listening for this event
- eventDict = self.__objectEvents.get(id)
- if eventDict and event in eventDict:
- del eventDict[event]
- if (len(eventDict) == 0):
- del self.__objectEvents[id]
- self._releaseObject(object)
- finally:
- self.lock.release()
- def ignoreAll(self, object):
- """
- Make this object no longer respond to any events it was accepting
- Useful for cleanup
- """
- if Messenger.notify.getDebug():
- Messenger.notify.debug(
- safeRepr(object) + ' (%s)\n now ignoring all events' % (self._getMessengerId(object), ))
- self.lock.acquire()
- try:
- id = self._getMessengerId(object)
- # Get the list of events this object is listening to
- eventDict = self.__objectEvents.get(id)
- if eventDict:
- for event in eventDict.keys():
- # Find the dictionary of all the objects accepting this event
- acceptorDict = self.__callbacks.get(event)
- # If this object is there, delete it from the dictionary
- if acceptorDict and id in acceptorDict:
- del acceptorDict[id]
- # If this dictionary is now empty, remove the event
- # entry from the Messenger alltogether
- if (len(acceptorDict) == 0):
- del self.__callbacks[event]
- self._releaseObject(object)
- del self.__objectEvents[id]
- finally:
- self.lock.release()
- def getAllAccepting(self, object):
- """
- Returns the list of all events accepted by the indicated object.
- """
- self.lock.acquire()
- try:
- id = self._getMessengerId(object)
- # Get the list of events this object is listening to
- eventDict = self.__objectEvents.get(id)
- if eventDict:
- return eventDict.keys()
- return []
- finally:
- self.lock.release()
- def isAccepting(self, event, object):
- """ isAccepting(self, string, DirectOject)
- Is this object accepting this event?
- """
- self.lock.acquire()
- try:
- acceptorDict = self.__callbacks.get(event)
- id = self._getMessengerId(object)
- if acceptorDict and id in acceptorDict:
- # Found it, return true
- return 1
- # If we looked in both dictionaries and made it here
- # that object must not be accepting that event.
- return 0
- finally:
- self.lock.release()
- def whoAccepts(self, event):
- """
- Return objects accepting the given event
- """
- return self.__callbacks.get(event)
- def isIgnoring(self, event, object):
- """ isIgnorning(self, string, DirectObject)
- Is this object ignoring this event?
- """
- return (not self.isAccepting(event, object))
- def send(self, event, sentArgs=[], taskChain = None):
- """
- Send this event, optionally passing in arguments
- event is usually a string.
- sentArgs is a list of any data that you want passed along to the
- handlers listening to this event.
- If taskChain is not None, it is the name of the task chain
- which should receive the event. If taskChain is None, the
- event is handled immediately. Setting a non-None taskChain
- will defer the event (possibly till next frame or even later)
- and create a new, temporary task within the named taskChain,
- but this is the only way to send an event across threads.
- """
- if Messenger.notify.getDebug() and not self.quieting.get(event):
- assert Messenger.notify.debug(
- 'sent event: %s sentArgs = %s, taskChain = %s' % (
- event, sentArgs, taskChain))
- self.lock.acquire()
- try:
- foundWatch=0
- if __debug__:
- if self.__isWatching:
- for i in self.__watching.keys():
- if str(event).find(i) >= 0:
- foundWatch=1
- break
- acceptorDict = self.__callbacks.get(event)
- if not acceptorDict:
- if __debug__:
- if foundWatch:
- print "Messenger: \"%s\" was sent, but no function in Python listened."%(event,)
- return
- if taskChain:
- # Queue the event onto the indicated task chain.
- from direct.task.TaskManagerGlobal import taskMgr
- queue = self._eventQueuesByTaskChain.setdefault(taskChain, [])
- queue.append((acceptorDict, event, sentArgs, foundWatch))
- if len(queue) == 1:
- # If this is the first (only) item on the queue,
- # spawn the task to empty it.
- taskMgr.add(self.__taskChainDispatch, name = 'Messenger-%s' % (taskChain),
- extraArgs = [taskChain], taskChain = taskChain,
- appendTask = True)
- else:
- # Handle the event immediately.
- self.__dispatch(acceptorDict, event, sentArgs, foundWatch)
- finally:
- self.lock.release()
- def __taskChainDispatch(self, taskChain, task):
- """ This task is spawned each time an event is sent across
- task chains. Its job is to empty the task events on the queue
- for this particular task chain. This guarantees that events
- are still delivered in the same order they were sent. """
- while True:
- eventTuple = None
- self.lock.acquire()
- try:
- queue = self._eventQueuesByTaskChain.get(taskChain, None)
- if queue:
- eventTuple = queue[0]
- del queue[0]
- if not queue:
- # The queue is empty, we're done.
- if queue is not None:
- del self._eventQueuesByTaskChain[taskChain]
- if not eventTuple:
- # No event; we're done.
- return task.done
-
- self.__dispatch(*eventTuple)
- finally:
- self.lock.release()
- return task.done
- def __dispatch(self, acceptorDict, event, sentArgs, foundWatch):
- for id in acceptorDict.keys():
- # We have to make this apparently redundant check, because
- # it is possible that one object removes its own hooks
- # in response to a handler called by a previous object.
- #
- # NOTE: there is no danger of skipping over objects due to
- # modifications to acceptorDict, since the for..in above
- # iterates over a list of objects that is created once at
- # the start
- callInfo = acceptorDict.get(id)
- if callInfo:
- method, extraArgs, persistent = callInfo
- # If this object was only accepting this event once,
- # remove it from the dictionary
- if not persistent:
- # This object is no longer listening for this event
- eventDict = self.__objectEvents.get(id)
- if eventDict and event in eventDict:
- del eventDict[event]
- if (len(eventDict) == 0):
- del self.__objectEvents[id]
- self._releaseObject(self._getObject(id))
-
- del acceptorDict[id]
- # If the dictionary at this event is now empty, remove
- # the event entry from the Messenger altogether
- if (event in self.__callbacks \
- and (len(self.__callbacks[event]) == 0)):
- del self.__callbacks[event]
- if __debug__:
- if foundWatch:
- print "Messenger: \"%s\" --> %s%s"%(
- event,
- self.__methodRepr(method),
- tuple(extraArgs + sentArgs))
- #print "Messenger: \"%s\" --> %s%s"%(
- # event,
- # self.__methodRepr(method),
- # tuple(extraArgs + sentArgs))
- # It is important to make the actual call here, after
- # we have cleaned up the accept hook, because the
- # method itself might call accept() or acceptOnce()
- # again.
- assert hasattr(method, '__call__')
- # Release the lock temporarily while we call the method.
- self.lock.release()
- try:
- method (*(extraArgs + sentArgs))
- finally:
- self.lock.acquire()
- def clear(self):
- """
- Start fresh with a clear dict
- """
- self.lock.acquire()
- try:
- self.__callbacks.clear()
- self.__objectEvents.clear()
- self._id2object.clear()
- finally:
- self.lock.release()
- def isEmpty(self):
- return (len(self.__callbacks) == 0)
- def getEvents(self):
- return self.__callbacks.keys()
- def replaceMethod(self, oldMethod, newFunction):
- """
- This is only used by Finder.py - the module that lets
- you redefine functions with Control-c-Control-v
- """
- import new
- retFlag = 0
- for entry in self.__callbacks.items():
- event, objectDict = entry
- for objectEntry in objectDict.items():
- object, params = objectEntry
- method = params[0]
- if (type(method) == types.MethodType):
- function = method.im_func
- else:
- function = method
- #print ('function: ' + repr(function) + '\n' +
- # 'method: ' + repr(method) + '\n' +
- # 'oldMethod: ' + repr(oldMethod) + '\n' +
- # 'newFunction: ' + repr(newFunction) + '\n')
- if (function == oldMethod):
- newMethod = new.instancemethod(
- newFunction, method.im_self, method.im_class)
- params[0] = newMethod
- # Found it retrun true
- retFlag += 1
- # didn't find that method, return false
- return retFlag
- def toggleVerbose(self):
- isVerbose = 1 - Messenger.notify.getDebug()
- Messenger.notify.setDebug(isVerbose)
- if isVerbose:
- print "Verbose mode true. quiet list = %s"%(
- self.quieting.keys(),)
- if __debug__:
- def watch(self, needle):
- """
- return a matching event (needle) if found (in haystack).
- This is primarily a debugging tool.
- This is intended for debugging use only.
- This function is not defined if python is ran with -O (optimize).
- See Also: unwatch
- """
- if not self.__watching.get(needle):
- self.__isWatching += 1
- self.__watching[needle]=1
- def unwatch(self, needle):
- """
- return a matching event (needle) if found (in haystack).
- This is primarily a debugging tool.
- This is intended for debugging use only.
- This function is not defined if python is ran with -O (optimize).
- See Also: watch
- """
- if self.__watching.get(needle):
- self.__isWatching -= 1
- del self.__watching[needle]
- def quiet(self, message):
- """
- When verbose mode is on, don't spam the output with messages
- marked as quiet.
- This is primarily a debugging tool.
- This is intended for debugging use only.
- This function is not defined if python is ran with -O (optimize).
- See Also: unquiet
- """
- if not self.quieting.get(message):
- self.quieting[message]=1
- def unquiet(self, message):
- """
- Remove a message from the list of messages that are not reported
- in verbose mode.
- This is primarily a debugging tool.
- This is intended for debugging use only.
- This function is not defined if python is ran with -O (optimize).
- See Also: quiet
- """
- if self.quieting.get(message):
- del self.quieting[message]
- def find(self, needle):
- """
- return a matching event (needle) if found (in haystack).
- This is primarily a debugging tool.
- """
- keys = self.__callbacks.keys()
- keys.sort()
- for event in keys:
- if repr(event).find(needle) >= 0:
- print self.__eventRepr(event),
- return {event: self.__callbacks[event]}
- def findAll(self, needle, limit=None):
- """
- return a dict of events (needle) if found (in haystack).
- limit may be None or an integer (e.g. 1).
- This is primarily a debugging tool.
- """
- matches = {}
- keys = self.__callbacks.keys()
- keys.sort()
- for event in keys:
- if repr(event).find(needle) >= 0:
- print self.__eventRepr(event),
- matches[event] = self.__callbacks[event]
- # if the limit is not None, decrement and
- # check for break:
- if limit > 0:
- limit -= 1
- if limit == 0:
- break
- return matches
- def __methodRepr(self, method):
- """
- return string version of class.method or method.
- """
- if (type(method) == types.MethodType):
- functionName = method.im_class.__name__ + '.' + \
- method.im_func.__name__
- else:
- functionName = method.__name__
- return functionName
- def __eventRepr(self, event):
- """
- Compact version of event, acceptor pairs
- """
- str = event.ljust(32) + '\t'
- acceptorDict = self.__callbacks[event]
- for key, (method, extraArgs, persistent) in acceptorDict.items():
- str = str + self.__methodRepr(method) + ' '
- str = str + '\n'
- return str
- def __repr__(self):
- """
- Compact version of event, acceptor pairs
- """
- str = "The messenger is currently handling:\n" + "="*64 + "\n"
- keys = self.__callbacks.keys()
- keys.sort()
- for event in keys:
- str += self.__eventRepr(event)
- # Print out the object: event dictionary too
- str += "="*64 + "\n"
- for key, eventDict in self.__objectEvents.items():
- object = self._getObject(key)
- str += "%s:\n" % repr(object)
- for event in eventDict.keys():
- str += " %s\n" % repr(event)
- str += "="*64 + "\n" + "End of messenger info.\n"
- return str
- def detailedRepr(self):
- """
- Print out the table in a detailed readable format
- """
- import types
- str = 'Messenger\n'
- str = str + '='*50 + '\n'
- keys = self.__callbacks.keys()
- keys.sort()
- for event in keys:
- acceptorDict = self.__callbacks[event]
- str = str + 'Event: ' + event + '\n'
- for key in acceptorDict.keys():
- function, extraArgs, persistent = acceptorDict[key]
- object = self._getObject(key)
- if (type(object) == types.InstanceType):
- className = object.__class__.__name__
- else:
- className = "Not a class"
- functionName = function.__name__
- str = (str + '\t' +
- 'Acceptor: ' + className + ' instance' + '\n\t' +
- 'Function name:' + functionName + '\n\t' +
- 'Extra Args: ' + repr(extraArgs) + '\n\t' +
- 'Persistent: ' + repr(persistent) + '\n')
- # If this is a class method, get its actual function
- if (type(function) == types.MethodType):
- str = (str + '\t' +
- 'Method: ' + repr(function) + '\n\t' +
- 'Function: ' + repr(function.im_func) + '\n')
- else:
- str = (str + '\t' +
- 'Function: ' + repr(function) + '\n')
- str = str + '='*50 + '\n'
- return str
|