diff --git a/direct/src/showbase/PythonUtil.py b/direct/src/showbase/PythonUtil.py index 8cc402f592..eccfddbff5 100644 --- a/direct/src/showbase/PythonUtil.py +++ b/direct/src/showbase/PythonUtil.py @@ -731,6 +731,177 @@ class Functor: _kargs.update(kargs) return apply(self._function,_args,_kargs) +class ParamSet: + # abstract base class for container of parameter values for a ParamObj + # (see below) + # specifies default values for every parameter + + # dict of params and their default values + # derived classes should define their own additional params and default + # values in the same way + Params = { + # base class does not define any parameters, but they would appear as + # 'name': value, + } + + def __init__(self, *args, **kwArgs): + ParamSet._compileDefaultParams() + if len(args) == 1 and len(kwArgs) == 0: + # extract our params from an existing ParamObj instance + obj = args[0] + self.paramVals = {} + for param in self.getParams(): + self.paramVals[param] = getSetter(obj, param, 'get')() + else: + assert len(args) == 0 + if __debug__: + for arg in kwArgs.keys(): + assert arg in self.getParams() + self.paramVals = dict(kwArgs) + def getValue(self, param): + if param in self.paramVals: + return self.paramVals[param] + return self._Params[param] + def applyTo(self, obj): + # Apply our entire set of params to a ParamObj + obj.lockParams() + for param in self.getParams(): + getSetter(obj, param)(self.getValue(param)) + obj.unlockParams() + # CLASS METHODS + def getParams(cls): + # returns safely-mutable list of param names + cls._compileDefaultParams() + return cls._Params.keys() + getParams = classmethod(getParams) + def getDefaultValue(cls, param): + cls._compileDefaultParams() + return cls._Params[param] + getDefaultValue = classmethod(getDefaultValue) + def _compileDefaultParams(cls): + if cls.__dict__.has_key('_Params'): + # we've already compiled the defaults for this class + return + bases = list(cls.__bases__) + # bring less-derived classes to the front + mostDerivedLast(bases) + cls._Params = {} + for c in (bases + [cls]): + # make sure this base has its dict of param defaults + c._compileDefaultParams() + if c.__dict__.has_key('Params'): + # apply this class' default param values to our dict + cls._Params.update(c.Params) + _compileDefaultParams = classmethod(_compileDefaultParams) + +class ParamObj: + # abstract base for classes that want to support a formal parameter + # set whose values may be queried, changed, 'bulk' changed, and + # extracted/stored/applied all at once (see ParamSet above) + + # for each param, ParamObj must define getter, setter, and applyer + # for each parameter + # (replace 'Param' with the name of the parameter): + # + # getParam() returns current value, + # setParam(value) sets current value, + # applyParam() (OPTIONAL) tells object to react to newly-set value + # inside applyParam, previous value of param is avaliable + # as self.getPriorValue() + + # to do a bulk change: + # obj.lockParams() + # obj.setX('foo') + # obj.setY(34) + # ... + # obj.unlockParams() + + # derived class must override this to be the appropriate ParamSet subclass + ParamClass = ParamSet + + def __init__(self): + self._paramLockRefCount = 0 + + def setterStub(param, value, self=self): + # should we apply the value now or should we wait? + # if this obj's params are locked, we track which values have + # been set, and on unlock, we'll call the applyers for those + # values + if self._paramLockRefCount > 0: + # set the new value; make sure we're not calling ourselves + # recursively + getSetter(self.__class__, param)(self, value) + if param not in self._priorValues: + try: + priorValue = getSetter(self, param, 'get')() + except: + priorValue = None + self._priorValues[param] = priorValue + self._paramsSet[param] = None + else: + # prepare for call to getPriorValue + self._oneShotPriorVal = getSetter(self, param, 'get')() + # set the new value; make sure we're not calling ourselves + # recursively + getSetter(self.__class__, param)(self, value) + # call the applier, if there is one + applier = getattr(self, getSetterName(param, 'apply'), None) + if applier is not None: + applier() + del self._oneShotPriorVal + + # insert stub funcs for param setters + for param in self.ParamClass.getParams(): + # if the setter is a direct member of self, move the setter + # aside + setterName = getSetterName(param) + if setterName in self.__dict__: + self.__dict__[setterName + '_MOVED'] = self.__dict__[setterName] + # and replace it with a stub that will a) call the setter and + # then the applier, or b) call the setter and queue the applier, + # depending on whether our params are locked + self.__dict__[setterName] = Functor(setterStub, param) + + def setDefaultParams(self): + # set all the default parameters on ourself + self.ParamClass().applyTo(self) + + def lockParams(self): + self._paramLockRefCount += 1 + if self._paramLockRefCount == 1: + self._handleLockParams() + def unlockParams(self): + if self._paramLockRefCount > 0: + self._paramLockRefCount -= 1 + if self._paramLockRefCount == 0: + self._handleUnlockParams() + def _handleLockParams(self): + # this will store the names of the parameters that are modified + self._paramsSet = {} + # this will store the values of modified params (from prior to + # the lock). + self._priorValues = {} + def _handleUnlockParams(self): + self.__curParam = None + for param in self._paramsSet: + # call the applier, if there is one + applier = getattr(self, getSetterName(param, 'apply'), None) + if applier is not None: + self.__curParam = param + applier() + del self.__curParam + del self._priorValues + del self._paramsSet + def paramsLocked(self): + return self._paramLockRefCount > 0 + def getPriorValue(self): + # call this within an apply function to find out what the prior value + # of a param was before the set call(s) corresponding to the call + # to apply + if hasattr(self, '_oneShotPriorVal'): + return self._oneShotPriorVal + return self._priorValues[self.__curParam] + def bound(value, bound1, bound2): """ returns value if value is between bound1 and bound2