diff --git a/direct/src/distributed/DistributedObject.py b/direct/src/distributed/DistributedObject.py index 6f5866450b..dbb727d1a9 100644 --- a/direct/src/distributed/DistributedObject.py +++ b/direct/src/distributed/DistributedObject.py @@ -3,7 +3,7 @@ from pandac.PandaModules import * from direct.directnotify.DirectNotifyGlobal import directNotify from direct.distributed.DistributedObjectBase import DistributedObjectBase -from direct.showbase.PythonUtil import EnforcesCalldowns, calldownEnforced +from direct.showbase.EnforcesCalldowns import EnforcesCalldowns, calldownEnforced #from PyDatagram import PyDatagram #from PyDatagramIterator import PyDatagramIterator diff --git a/direct/src/distributed/DistributedObjectAI.py b/direct/src/distributed/DistributedObjectAI.py index c446c43cfd..83b22ddf86 100644 --- a/direct/src/distributed/DistributedObjectAI.py +++ b/direct/src/distributed/DistributedObjectAI.py @@ -3,7 +3,7 @@ from direct.directnotify.DirectNotifyGlobal import directNotify from direct.distributed.DistributedObjectBase import DistributedObjectBase from direct.showbase import PythonUtil -from direct.showbase.PythonUtil import EnforcesCalldowns, calldownEnforced +from direct.showbase.EnforcesCalldowns import EnforcesCalldowns, calldownEnforced from pandac.PandaModules import * #from PyDatagram import PyDatagram #from PyDatagramIterator import PyDatagramIterator diff --git a/direct/src/showbase/EnforcesCalldowns.py b/direct/src/showbase/EnforcesCalldowns.py new file mode 100755 index 0000000000..06999cbd45 --- /dev/null +++ b/direct/src/showbase/EnforcesCalldowns.py @@ -0,0 +1,203 @@ +__all__ = ['EnforcesCalldowns', 'calldownEnforced', 'EnforcedCalldownException', + ] + +from direct.showbase.PythonUtil import ClassTree, getBase +import new + +class EnforcedCalldownException(Exception): + def __init__(self, what): + Exception.__init__(self, what) + +class EnforcesCalldowns: + """Derive from this class if you want to ensure that specific methods + get called. See calldownEnforced decorator below""" + + # class-level data for enforcement of base class method call-down + # + # The problem is that we don't have access to the class in the + # decorator, so we need to put the decorated methods in a global + # dict. We can then insert a stub method on each class instance for + # every method that has enforced base-class methods, and the stub can + # watch for each base-class method checkpoint to be passed. + + # since the decorator can't know its own id until after it has been + # defined, we map from decorator ID to original func ID + _decoId2funcId = {} + # as calldownEnforced decorators are created, they add themselves to + # this dict. At this point we don't know what class they belong to. + _funcId2func = {} + # this is here so that we can print nice error messages + _funcId2class = {} + # as class instances are created, we populate this dictionary of + # class to func name to list of func ids. The lists of func ids + # include base-class funcs. + _class2funcName2funcIds = {} + + # this method will be inserted into instances of classes that need + # to enforce base-class method calls, as the most-derived implementation + # of the method + @staticmethod + def _enforceCalldowns(oldMethod, name, obj, *args, **kArgs): + name2funcIds = EnforcesCalldowns._class2funcName2funcIds[obj.__class__] + funcIds = name2funcIds.get(name) + + # prepare for the method call + for funcId in funcIds: + obj._EClatch(funcId) + + # call the actual method that we're stubbing + result = oldMethod(*args, **kArgs) + + # check on the results + for funcId in funcIds: + obj._ECcheck(funcId) + + return result + + @staticmethod + def notActive(): + return not (__dev__ and getBase().config.GetBool('enforce-calldowns', 1)) + + def __init__(self): + if EnforcesCalldowns.notActive(): + return + + # this map tracks how many times each func has been called + self._funcId2calls = {} + # this map tracks the 'latch' values for each func; if the call count + # for a func is greater than the latch, then the func has been called. + self._funcId2latch = {} + + if self.__class__ not in EnforcesCalldowns._class2funcName2funcIds: + # prepare stubs to enforce method call-downs + EnforcesCalldowns._class2funcName2funcIds.setdefault(self.__class__, {}) + # look through all of our base classes and find matches + classes = ClassTree(self).getAllClasses() + # collect IDs of all the enforced methods + funcId2func = {} + for cls in classes: + for name, item in cls.__dict__.items(): + if id(item) in EnforcesCalldowns._decoId2funcId: + funcId = EnforcesCalldowns._decoId2funcId[id(item)] + funcId2func[funcId] = item + EnforcesCalldowns._funcId2class[funcId] = cls + # add these funcs to the list for our class + funcName2funcIds = EnforcesCalldowns._class2funcName2funcIds[self.__class__] + for funcId, func in funcId2func.items(): + funcName2funcIds.setdefault(func.__name__, []) + funcName2funcIds[func.__name__].append(funcId) + + # now run through all the enforced funcs for this class and insert + # stub methods to do the enforcement + funcName2funcIds = EnforcesCalldowns._class2funcName2funcIds[self.__class__] + self._obscuredMethodNames = set() + for name in funcName2funcIds: + oldMethod = getattr(self, name) + self._obscuredMethodNames.add(name) + setattr(self, name, new.instancemethod( + Functor(EnforcesCalldowns._enforceCalldowns, oldMethod, name), + self, self.__class__)) + + def EC_destroy(self): + """this used to be called destroy() but it was masking destroy() functions + on other classes that were multiply-inherited after ('to the right of') + this class""" + if EnforcesCalldowns.notActive(): + return + # this must be called on destruction to prevent memory leaks + for name in self._obscuredMethodNames: + delattr(self, name) + del self._obscuredMethodNames + # this opens up more cans of worms. Let's keep it closed for the moment + #del self._funcId2calls + #del self._funcId2latch + + def skipCalldown(self, method): + if EnforcesCalldowns.notActive(): + return + # Call this function if you really don't want to call down to an + # enforced base-class method. This should hardly ever be used. + funcName2funcIds = EnforcesCalldowns._class2funcName2funcIds[self.__class__] + funcIds = funcName2funcIds[method.__name__] + for funcId in funcIds: + self._ECvisit(funcId) + + def _EClatch(self, funcId): + self._funcId2calls.setdefault(funcId, 0) + self._funcId2latch[funcId] = self._funcId2calls[funcId] + def _ECvisit(self, funcId): + self._funcId2calls.setdefault(funcId, 0) + self._funcId2calls[funcId] += 1 + def _ECcheck(self, funcId): + if self._funcId2latch[funcId] == self._funcId2calls[funcId]: + func = EnforcesCalldowns._funcId2func[funcId] + raise EnforcedCalldownException( + '%s.%s did not call down to %s.%s\n%s' % ( + self.__class__.__module__, self.__class__.__name__, + EnforcesCalldowns._funcId2class[funcId].__name__, + func.__name__, + ClassTree(self))) + +def calldownEnforced(f): + """ + Use this decorator to ensure that derived classes that override this method + call down to the base class method. + """ + if EnforcesCalldowns.notActive(): + return f + def calldownEnforcedImpl(obj, *args, **kArgs): + # track the fact that this func has been called + obj._ECvisit(id(f)) + f(obj, *args, **kArgs) + calldownEnforcedImpl.__doc__ = f.__doc__ + calldownEnforcedImpl.__name__ = f.__name__ + calldownEnforcedImpl.__module__ = f.__module__ + EnforcesCalldowns._decoId2funcId[id(calldownEnforcedImpl)] = id(f) + EnforcesCalldowns._funcId2func[id(f)] = calldownEnforcedImpl + return calldownEnforcedImpl + +if not EnforcesCalldowns.notActive(): + class CalldownEnforceTest(EnforcesCalldowns): + @calldownEnforced + def testFunc(self): + pass + class CalldownEnforceTestSubclass(CalldownEnforceTest): + def testFunc(self): + CalldownEnforceTest.testFunc(self) + class CalldownEnforceTestSubclassFail(CalldownEnforceTest): + def testFunc(self): + pass + class CalldownEnforceTestSubclassSkip(CalldownEnforceTest): + def testFunc(self): + self.skipCalldown(CalldownEnforceTest.testFunc) + cets = CalldownEnforceTestSubclass() + cetsf = CalldownEnforceTestSubclassFail() + cetss = CalldownEnforceTestSubclassSkip() + raised = False + try: + cets.testFunc() + except EnforcedCalldownException, e: + raised = True + if raised: + raise "calldownEnforced raised when it shouldn't" + raised = False + try: + cetsf.testFunc() + except EnforcedCalldownException, e: + raised = True + if not raised: + raise 'calldownEnforced failed to raise' + raised = False + try: + cetss.testFunc() + except EnforcedCalldownException, e: + raised = True + if raised: + raise "calldownEnforced.skipCalldown raised when it shouldn't" + del cetss + del cetsf + del cets + del CalldownEnforceTestSubclassSkip + del CalldownEnforceTestSubclassFail + del CalldownEnforceTestSubclass + del CalldownEnforceTest diff --git a/direct/src/showbase/PythonUtil.py b/direct/src/showbase/PythonUtil.py index 981cf9d782..f03c8fa8e7 100644 --- a/direct/src/showbase/PythonUtil.py +++ b/direct/src/showbase/PythonUtil.py @@ -23,7 +23,7 @@ __all__ = ['enumerate', 'unique', 'indent', 'nonRepeatingRandomList', '_equal', '_notEqual', '_isNone', '_notNone', '_contains', '_notIn', 'ScratchPad', 'Sync', 'RefCounter', 'itype', 'getNumberedTypedString', 'printNumberedTyped', 'DelayedCall', 'DelayedFunctor', -'FrameDelayedCallback', 'ArgumentEater'] +'FrameDelayedCallback', 'ArgumentEater', 'ClassTree', 'getBase',] import types import string @@ -2369,199 +2369,11 @@ class ClassTree: def __repr__(self): return self._getStr() -class EnforcedCalldownException(Exception): - def __init__(self, what): - Exception.__init__(self, what) - -class EnforcesCalldowns: - """Derive from this class if you want to ensure that specific methods - get called. See calldownEnforced decorator below""" - - # class-level data for enforcement of base class method call-down - # - # The problem is that we don't have access to the class in the - # decorator, so we need to put the decorated methods in a global - # dict. We can then insert a stub method on each class instance for - # every method that has enforced base-class methods, and the stub can - # watch for each base-class method checkpoint to be passed. - - # since the decorator can't know its own id until after it has been - # defined, we map from decorator ID to original func ID - _decoId2funcId = {} - # as calldownEnforced decorators are created, they add themselves to - # this dict. At this point we don't know what class they belong to. - _funcId2func = {} - # this is here so that we can print nice error messages - _funcId2class = {} - # as class instances are created, we populate this dictionary of - # class to func name to list of func ids. The lists of func ids - # include base-class funcs. - _class2funcName2funcIds = {} - - # this method will be inserted into instances of classes that need - # to enforce base-class method calls, as the most-derived implementation - # of the method - @staticmethod - def _enforceCalldowns(oldMethod, name, obj, *args, **kArgs): - name2funcIds = EnforcesCalldowns._class2funcName2funcIds[obj.__class__] - funcIds = name2funcIds.get(name) - - # prepare for the method call - for funcId in funcIds: - obj._EClatch(funcId) - - # call the actual method that we're stubbing - result = oldMethod(*args, **kArgs) - - # check on the results - for funcId in funcIds: - obj._ECcheck(funcId) - - return result - - def __init__(self): - if not __debug__: - return - - # this map tracks how many times each func has been called - self._funcId2calls = {} - # this map tracks the 'latch' values for each func; if the call count - # for a func is greater than the latch, then the func has been called. - self._funcId2latch = {} - - if self.__class__ not in EnforcesCalldowns._class2funcName2funcIds: - # prepare stubs to enforce method call-downs - EnforcesCalldowns._class2funcName2funcIds.setdefault(self.__class__, {}) - # look through all of our base classes and find matches - classes = ClassTree(self).getAllClasses() - # collect IDs of all the enforced methods - funcId2func = {} - for cls in classes: - for name, item in cls.__dict__.items(): - if id(item) in EnforcesCalldowns._decoId2funcId: - funcId = EnforcesCalldowns._decoId2funcId[id(item)] - funcId2func[funcId] = item - EnforcesCalldowns._funcId2class[funcId] = cls - # add these funcs to the list for our class - funcName2funcIds = EnforcesCalldowns._class2funcName2funcIds[self.__class__] - for funcId, func in funcId2func.items(): - funcName2funcIds.setdefault(func.__name__, []) - funcName2funcIds[func.__name__].append(funcId) - - # now run through all the enforced funcs for this class and insert - # stub methods to do the enforcement - funcName2funcIds = EnforcesCalldowns._class2funcName2funcIds[self.__class__] - self._obscuredMethodNames = set() - for name in funcName2funcIds: - oldMethod = getattr(self, name) - self._obscuredMethodNames.add(name) - setattr(self, name, new.instancemethod( - Functor(EnforcesCalldowns._enforceCalldowns, oldMethod, name), - self, self.__class__)) - - def EC_destroy(self): - """this used to be called destroy() but it was masking destroy() functions - on other classes that were multiply-inherited after ('to the right of') - this class""" - if not __debug__: - return - # this must be called on destruction to prevent memory leaks - for name in self._obscuredMethodNames: - delattr(self, name) - del self._obscuredMethodNames - # this opens up more cans of worms. Let's keep it closed for the moment - #del self._funcId2calls - #del self._funcId2latch - - def skipCalldown(self, method): - if not __debug__: - return - # Call this function if you really don't want to call down to an - # enforced base-class method. This should hardly ever be used. - funcName2funcIds = EnforcesCalldowns._class2funcName2funcIds[self.__class__] - funcIds = funcName2funcIds[method.__name__] - for funcId in funcIds: - self._ECvisit(funcId) - - def _EClatch(self, funcId): - self._funcId2calls.setdefault(funcId, 0) - self._funcId2latch[funcId] = self._funcId2calls[funcId] - def _ECvisit(self, funcId): - self._funcId2calls.setdefault(funcId, 0) - self._funcId2calls[funcId] += 1 - def _ECcheck(self, funcId): - if self._funcId2latch[funcId] == self._funcId2calls[funcId]: - func = EnforcesCalldowns._funcId2func[funcId] - raise EnforcedCalldownException( - '%s.%s did not call down to %s.%s\n%s' % ( - self.__class__.__module__, self.__class__.__name__, - EnforcesCalldowns._funcId2class[funcId].__name__, - func.__name__, - ClassTree(self))) - -def calldownEnforced(f): - """ - Use this decorator to ensure that derived classes that override this method - call down to the base class method. - """ - if not __debug__: - return f - def calldownEnforcedImpl(obj, *args, **kArgs): - # track the fact that this func has been called - obj._ECvisit(id(f)) - f(obj, *args, **kArgs) - calldownEnforcedImpl.__doc__ = f.__doc__ - calldownEnforcedImpl.__name__ = f.__name__ - calldownEnforcedImpl.__module__ = f.__module__ - EnforcesCalldowns._decoId2funcId[id(calldownEnforcedImpl)] = id(f) - EnforcesCalldowns._funcId2func[id(f)] = calldownEnforcedImpl - return calldownEnforcedImpl - -if __debug__: - class CalldownEnforceTest(EnforcesCalldowns): - @calldownEnforced - def testFunc(self): - pass - class CalldownEnforceTestSubclass(CalldownEnforceTest): - def testFunc(self): - CalldownEnforceTest.testFunc(self) - class CalldownEnforceTestSubclassFail(CalldownEnforceTest): - def testFunc(self): - pass - class CalldownEnforceTestSubclassSkip(CalldownEnforceTest): - def testFunc(self): - self.skipCalldown(CalldownEnforceTest.testFunc) - cets = CalldownEnforceTestSubclass() - cetsf = CalldownEnforceTestSubclassFail() - cetss = CalldownEnforceTestSubclassSkip() - raised = False +def getBase(): try: - cets.testFunc() - except EnforcedCalldownException, e: - raised = True - if raised: - raise "calldownEnforced raised when it shouldn't" - raised = False - try: - cetsf.testFunc() - except EnforcedCalldownException, e: - raised = True - if not raised: - raise 'calldownEnforced failed to raise' - raised = False - try: - cetss.testFunc() - except EnforcedCalldownException, e: - raised = True - if raised: - raise "calldownEnforced.skipCalldown raised when it shouldn't" - del cetss - del cetsf - del cets - del CalldownEnforceTestSubclassSkip - del CalldownEnforceTestSubclassFail - del CalldownEnforceTestSubclass - del CalldownEnforceTest + return base + except: + return simbase import __builtin__ __builtin__.Functor = Functor