diff --git a/direct/src/interval/MetaInterval.py b/direct/src/interval/MetaInterval.py new file mode 100644 index 0000000000..b6b87a03cd --- /dev/null +++ b/direct/src/interval/MetaInterval.py @@ -0,0 +1,374 @@ +from PandaModules import * +from DirectNotifyGlobal import * +import Interval +import Task +import types + +PREVIOUS_END = CMetaInterval.RSPreviousEnd +PREVIOUS_START = CMetaInterval.RSPreviousBegin +TRACK_START = CMetaInterval.RSLevelBegin + +class MetaInterval(CMetaInterval): + + # This is a Python-C++ hybrid class. MetaInterval is a Python + # extension of the C++ class CMetaInterval, which adds some + # Python-specific features (like list management). + + # This is the base class of Sequence, Parallel, and Track. + + notify = directNotify.newCategory("Interval") + + SequenceNum = 1 + def __init__(self, *ivals, **kw): + name = None + if len(ivals) == 2 and isinstance(ivals[1], types.StringType): + # If the second parameter is a string, it's the name. + name = ivals[1] + ivals = ivals[0] + else: + # Otherwise, look for the name in the keyword params. + if kw.has_key('name'): + name = kw['name'] + del kw['name'] + + if kw: + self.notify.error("Unexpected keyword parameters: %s" % (kw.keys())) + + # We must allow the old style: Track([ ival0, ival1, ... ]) as + # well as the new style: Track(ival0, ival1, ...) + if len(ivals) == 1 and \ + (isinstance(ivals[0], types.TupleType) or \ + isinstance(ivals[0], types.ListType)): + self.ivals = ivals[0] + else: + self.ivals = ivals + + self.__ivalsDirty = 1 + + if name == None: + name = '%s-%d' % (self.__class__.__name__, self.SequenceNum) + MetaInterval.SequenceNum += 1 + + CMetaInterval.__init__(self, name) + + self.pythonIvals = [] + + + # Functions to make the MetaInterval object act just like a Python + # list of intervals: + + def append(self, ival): + # Appends a single interval to the list so far. + if isinstance(self.ivals, types.TupleType): + self.ivals = list(self.ivals) + self.ivals.append(ival) + self.__ivalsDirty = 1 + + def extend(self, ivals): + # Appends a list of intervals to the list so far. + if isinstance(self.ivals, types.TupleType): + self.ivals = list(self.ivals) + self.ivals.extend(ivals) + self.__ivalsDirty = 1 + + def __len__(self): + return len(self.ivals) + + def __getitem__(self, index): + return self.ivals[index] + + def __setitem__(self, index, value): + if isinstance(self.ivals, types.TupleType): + self.ivals = list(self.ivals) + self.ivals[index] = value + self.__ivalsDirty = 1 + + def __delitem__(self, index): + if isinstance(self.ivals, types.TupleType): + self.ivals = list(self.ivals) + del self.ivals[index] + self.__ivalsDirty = 1 + + def __getslice__(self, i, j): + if isinstance(self.ivals, types.TupleType): + self.ivals = list(self.ivals) + return self.ivals[i : j] + + def __setslice__(self, i, j, s): + if isinstance(self.ivals, types.TupleType): + self.ivals = list(self.ivals) + self.ivals[i : j] = s + self.__ivalsDirty = 1 + + def __delslice__(self, i, j): + if isinstance(self.ivals, types.TupleType): + self.ivals = list(self.ivals) + del self.ivals[i : j] + self.__ivalsDirty = 1 + + def __iadd__(self, other): + if isinstance(self.ivals, types.TupleType): + self.ivals = list(self.ivals) + if isinstance(other, MetaInterval): + self.ivals += other.ivals + else: + self.ivals += list(other.ivals) + self.__ivalsDirty = 1 + return self + + + def addSequence(self, list, relTime, relTo): + # Adds the given list of intervals to the MetaInterval to be + # played one after the other. + self.pushLevel(relTime, relTo) + for ival in list: + self.addInterval(ival, 0.0, PREVIOUS_END) + self.popLevel() + + def addParallel(self, list, relTime, relTo): + # Adds the given list of intervals to the MetaInterval to be + # played simultaneously. + self.pushLevel(relTime, relTo) + for ival in list: + self.addInterval(ival, 0.0, TRACK_START) + self.popLevel() + + def addTrack(self, list, relTime, relTo): + # Adds a "track list". This is a list of tuples of the form: + # + # ( , , + # PREVIOUS_END | PREVIOUS_START | TRACK_START ) + # + # where is a relative time, in seconds, for the + # to start, relative to either the end of the + # previous interval (PREVIOUS_END), the start of the previous + # interval (PREVIOUS_START) or the start of the track list + # (TRACK_START). If the relative code is omitted, the default + # is TRACK_START. + self.pushLevel(relTime, relTo) + for tuple in list: + if isinstance(tuple, Interval.Interval) or \ + isinstance(tuple, CInterval): + # Actually, it's not a tuple, but just an interval. + # In this case we fall back on the old default of + # assuming a sequential list of intervals. This is a + # temporary feature for backward compatibility. + self.addInterval(tuple, 0.0, PREVIOUS_END) + + elif isinstance(tuple, types.TupleType) or \ + isinstance(tuple, types.ListType): + relTime = tuple[0] + ival = tuple[1] + if len(tuple) >= 3: + relTo = tuple[2] + else: + relTo = TRACK_START + self.addInterval(ival, relTime, relTo) + + else: + self.notify.error("Not a tuple in Track: %s" % (tuple,)) + self.popLevel() + + def addInterval(self, ival, relTime, relTo): + # Adds the given interval to the MetaInterval. + + if isinstance(ival, MetaInterval): + # It's another MetaInterval, so copy in its intervals + # directly to this object. We could just store the + # MetaInterval itself, which would work, but we get a + # performance advantage by flattening out the deeply + # nested hierarchy into a linear list within the root + # CMetaInterval object. + ival.applyIvals(self, relTime, relTo) + + elif isinstance(ival, CInterval): + # It's a C++-style Interval, so add it directly. + if getattr(ival, "inPython", 0): + # Actually, it's been flagged to run in Python, even + # though it's a C++ Interval. It's probably got some + # Python functors that must be invoked at runtime to + # define some of its parameters. Treat it as a Python + # interval. + index = len(self.pythonIvals) + self.pythonIvals.append(ival) + self.addExtIndex(index, ival.getName(), ival.getDuration(), + ival.getOpenEnded(), relTime, relTo) + else: + # Nope, a perfectly ordinary C++ interval. Hooray! + self.addCInterval(ival, relTime, relTo) + + elif isinstance(ival, Interval.Interval): + # It's a Python-style Interval, so add it as an external. + index = len(self.pythonIvals) + self.pythonIvals.append(ival) + self.addExtIndex(index, ival.getName(), ival.getDuration(), + ival.getOpenEnded(), relTime, relTo) + + # Once we have any Python intervals, we must handle this + # interval from Python. + self.inPython = 1 + + else: + self.notify.error("Not an Interval: %s" % (ival,)) + + + def __updateIvals(self): + # The MetaInterval object does not create the C++ list of + # Intervals immediately; rather, it stores a Python list of + # Intervals that will be compiled into the C++ list the first + # time it is needed. + + # This design allows us to avoid creation of the C++ list for + # nested MetaInterval objects, instead copying all nested + # MetaInterval hierarchy into the root CMetaInterval object, + # for a performance benefit. + + # This function is called only on the root MetaInterval + # object, when it is time to build the C++ list for itself. + + if self.__ivalsDirty: + self.clearIntervals() + self.applyIvals(self, 0, TRACK_START) + self.__ivalsDirty = 0 + + def clearIntervals(self): + # This overrides the function defined at the C++ level to + # reset the inPython flag. Clearing out the intervals list + # allows us to run entirely in C++ again, at least until a new + # Python interval gets added. + CMetaInterval.clearIntervals(self) + self.inPython = 0 + + def applyIvals(self, meta, relTime, relTo): + # Add the intervals listed in this object to the given + # MetaInterval object at the C++ level. This will make the + # other MetaInterval object ready to play the intervals. + + # This function should be overridden in a derived class to + # change the intepretation of the intervals in this list. In + # the case of a MetaInterval directly, this is valid only if + # the list has only zero or one intervals. + + if len(self.ivals) == 0: + pass + + elif len(self.ivals) == 1: + meta.addInterval(self.ivals[0], relTime, relTo) + + else: + self.notify.error("Cannot build list from MetaInterval directly.") + + def __doPythonCallbacks(self): + # This function invokes any Python-level Intervals that need + # to be invoked at this point in time. It must be called + # after any call to setT() or setFinalT() or stepPlay(), or + # some such; basically any function that might invoke an + # interval. The C++ base class will invoke whatever C++ + # intervals it can, and then indicate the Python intervals + # that must be invoked through this interface. + + while (self.isEventReady()): + index = self.getEventIndex() + t = self.getEventT() + eventType = self.getEventType() + self.popEvent() + + ival = self.pythonIvals[index] + if eventType == CInterval.ETStep: + ival.setT(t, Interval.IVAL_NONE) + + elif eventType == CInterval.ETFinalize: + ival.setT(ival.getDuration(), Interval.IVAL_DONE) + + elif eventType == CInterval.ETReverseFinalize: + ival.setT(0, Interval.IVAL_NONE) + + elif eventType == CInterval.ETInitialize or \ + eventType == CInterval.ETReverseInitialize: + ival.setT(t, Interval.IVAL_INIT) + + elif eventType == CInterval.ETInstant: + ival.setT(ival.getDuration(), Interval.IVAL_INIT) + + elif eventType == CInterval.ETReverseInstant: + ival.setT(0, Interval.IVAL_INIT) + + else: + self.notify.error("Unhandled event type %s" % (eventType)) + + def stepPlay(self): + # This function overrides the function defined in the C++ + # CInterval code that is called every frame while the interval + # is playing, to check for Python callbacks aftwards. + result = CMetaInterval.stepPlay(self) + self.__doPythonCallbacks() + return result + + def setT(self, t, event = Interval.IVAL_NONE): + # This function overrides the C++ function to check for Python + # callbacks afterwards. + self.__updateIvals() + CMetaInterval.setT(self, t, event) + self.__doPythonCallbacks() + + def setFinalT(self): + # This function overrides the C++ function to check for Python + # callbacks afterwards. + self.__updateIvals() + CMetaInterval.setFinalT(self) + self.__doPythonCallbacks() + + def getDuration(self): + # This function overrides from the parent level to force it to + # update the interval list first, if necessary. + + self.__updateIvals() + return CMetaInterval.getDuration(self) + + def play(self, *args, **kw): + # This function overrides from the parent level to force it to + # update the interval list first, if necessary. + + self.__updateIvals() + return CMetaInterval.play(self, *args, **kw) + + def loop(self, *args, **kw): + # This function overrides from the parent level to force it to + # update the interval list first, if necessary. + + self.__updateIvals() + return CMetaInterval.loop(self, *args, **kw) + + def __repr__(self, *args, **kw): + # This function overrides from the parent level to force it to + # update the interval list first, if necessary. + + self.__updateIvals() + return CMetaInterval.__repr__(self, *args, **kw) + + def __str__(self, *args, **kw): + # This function overrides from the parent level to force it to + # update the interval list first, if necessary. + + self.__updateIvals() + return CMetaInterval.__str__(self, *args, **kw) + + + + +class Sequence(MetaInterval): + def applyIvals(self, meta, relTime, relTo): + meta.addSequence(self.ivals, relTime, relTo) + +class Parallel(MetaInterval): + def applyIvals(self, meta, relTime, relTo): + meta.addParallel(self.ivals, relTime, relTo) + +class Track(MetaInterval): + def applyIvals(self, meta, relTime, relTo): + meta.addTrack(self.ivals, relTime, relTo) + +# Temporary for backward compatibility. +class MultiTrack(MetaInterval): + def applyIvals(self, meta, relTime, relTo): + meta.addParallel(self.ivals, relTime, relTo)