MetaInterval.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. """
  2. This module defines the various "meta intervals", which execute other
  3. intervals either in parallel or in a specified sequential order.
  4. """
  5. __all__ = ['MetaInterval', 'Sequence', 'Parallel', 'ParallelEndTogether', 'Track']
  6. from panda3d.core import PStatCollector, ostream
  7. from panda3d.direct import CInterval, CMetaInterval
  8. from direct.directnotify.DirectNotifyGlobal import directNotify
  9. from .IntervalManager import ivalMgr
  10. from . import Interval
  11. from direct.task.Task import TaskManager
  12. #if __debug__:
  13. # import direct.showbase.PythonUtil as PythonUtil
  14. PREVIOUS_END = CMetaInterval.RSPreviousEnd
  15. PREVIOUS_START = CMetaInterval.RSPreviousBegin
  16. TRACK_START = CMetaInterval.RSLevelBegin
  17. class MetaInterval(CMetaInterval):
  18. # This is a Python-C++ hybrid class. MetaInterval is a Python
  19. # extension of the C++ class CMetaInterval, which adds some
  20. # Python-specific features (like list management).
  21. # This is the base class of Sequence, Parallel, and Track.
  22. notify = directNotify.newCategory("MetaInterval")
  23. SequenceNum = 1
  24. def __init__(self, *ivals, **kw):
  25. #if __debug__:
  26. # self.debugInitTraceback = PythonUtil.StackTrace(
  27. # "create interval", 1, 10)
  28. name = None
  29. #if len(ivals) == 2 and isinstance(ivals[1], str):
  30. # # If the second parameter is a string, it's the name.
  31. # name = ivals[1]
  32. # ivals = ivals[0]
  33. #else:
  34. # Look for the name in the keyword params.
  35. if 'name' in kw:
  36. name = kw['name']
  37. del kw['name']
  38. # If the keyword "autoPause" or "autoFinish" is defined to
  39. # non-zero, it means the interval may be automatically paused
  40. # or finished when CIntervalManager::interrupt() is called.
  41. # This is generally called only on a catastrophic situation
  42. # (for instance, the connection to the server being lost) when
  43. # we have to exit right away; these keywords indicate
  44. # intervals that might not be cleaned up by their owners.
  45. autoPause = 0
  46. autoFinish = 0
  47. if 'autoPause' in kw:
  48. autoPause = kw['autoPause']
  49. del kw['autoPause']
  50. if 'autoFinish' in kw:
  51. autoFinish = kw['autoFinish']
  52. del kw['autoFinish']
  53. # A duration keyword specifies the duration the interval will
  54. # appear to have for the purposes of computing the start time
  55. # for subsequent intervals in a sequence or track.
  56. self.phonyDuration = -1
  57. if 'duration' in kw:
  58. self.phonyDuration = kw['duration']
  59. del kw['duration']
  60. if kw:
  61. self.notify.error("Unexpected keyword parameters: %s" % (list(kw.keys())))
  62. # We must allow the old style: Track([ival0, ival1, ...]) as
  63. # well as the new style: Track(ival0, ival1, ...)
  64. # Note: this breaks in the case of a Track with one tuple:
  65. # Track((0, ival0),). We could go through some effort to fix
  66. # this case, but for now I prefer just to document it as a
  67. # bug, since it will go away when we eventually remove support
  68. # for the old interface.
  69. #if len(ivals) == 1 and \
  70. # (isinstance(ivals[0], tuple) or \
  71. # isinstance(ivals[0], list)):
  72. # self.ivals = ivals[0]
  73. #else:
  74. self.ivals = ivals
  75. self.__ivalsDirty = 1
  76. if name is None:
  77. name = self.__class__.__name__ + '-%d'
  78. if '%' in name:
  79. name = name % (self.SequenceNum)
  80. MetaInterval.SequenceNum += 1
  81. CMetaInterval.__init__(self, name)
  82. self.__manager = ivalMgr
  83. self.setAutoPause(autoPause)
  84. self.setAutoFinish(autoFinish)
  85. self.pstats = None
  86. if __debug__ and TaskManager.taskTimerVerbose:
  87. self.pname = name.split('-', 1)[0]
  88. self.pstats = PStatCollector("App:Tasks:ivalLoop:%s" % (self.pname))
  89. self.pythonIvals = []
  90. # If we are running in debug mode, we validate the intervals
  91. # in the list right away. There's no good reason to do this,
  92. # except that it makes it easier for the programmer to detect
  93. # when a MetaInterval is misdefined at creation time.
  94. assert self.validateComponents(self.ivals)
  95. # Functions to make the MetaInterval object act just like a Python
  96. # list of intervals:
  97. def append(self, ival):
  98. # Appends a single interval to the list so far.
  99. if isinstance(self.ivals, tuple):
  100. self.ivals = list(self.ivals)
  101. self.ivals.append(ival)
  102. self.__ivalsDirty = 1
  103. assert self.validateComponent(ival)
  104. def extend(self, ivals):
  105. # Appends a list of intervals to the list so far.
  106. self += ivals
  107. def count(self, ival):
  108. # Returns the number of occurrences of the indicated interval.
  109. return self.ivals.count(ival)
  110. def index(self, ival):
  111. # Returns the position of the indicated interval within the list.
  112. return self.ivals.index(ival)
  113. def insert(self, index, ival):
  114. # Inserts the given interval into the middle of the list.
  115. if isinstance(self.ivals, tuple):
  116. self.ivals = list(self.ivals)
  117. self.ivals.insert(index, ival)
  118. self.__ivalsDirty = 1
  119. assert self.validateComponent(ival)
  120. def pop(self, index = None):
  121. # Returns element index (or the last element) and removes it
  122. # from the list.
  123. if isinstance(self.ivals, tuple):
  124. self.ivals = list(self.ivals)
  125. self.__ivalsDirty = 1
  126. if index is None:
  127. return self.ivals.pop()
  128. else:
  129. return self.ivals.pop(index)
  130. def remove(self, ival):
  131. # Removes the indicated interval from the list.
  132. if isinstance(self.ivals, tuple):
  133. self.ivals = list(self.ivals)
  134. self.ivals.remove(ival)
  135. self.__ivalsDirty = 1
  136. def reverse(self):
  137. # Reverses the order of the intervals.
  138. if isinstance(self.ivals, tuple):
  139. self.ivals = list(self.ivals)
  140. self.ivals.reverse()
  141. self.__ivalsDirty = 1
  142. def sort(self, cmpfunc = None):
  143. # Sorts the intervals. (?)
  144. if isinstance(self.ivals, tuple):
  145. self.ivals = list(self.ivals)
  146. self.__ivalsDirty = 1
  147. if cmpfunc is None:
  148. self.ivals.sort()
  149. else:
  150. self.ivals.sort(cmpfunc)
  151. def __len__(self):
  152. return len(self.ivals)
  153. def __getitem__(self, index):
  154. return self.ivals[index]
  155. def __setitem__(self, index, value):
  156. if isinstance(self.ivals, tuple):
  157. self.ivals = list(self.ivals)
  158. self.ivals[index] = value
  159. self.__ivalsDirty = 1
  160. assert self.validateComponent(value)
  161. def __delitem__(self, index):
  162. if isinstance(self.ivals, tuple):
  163. self.ivals = list(self.ivals)
  164. del self.ivals[index]
  165. self.__ivalsDirty = 1
  166. def __getslice__(self, i, j):
  167. if isinstance(self.ivals, tuple):
  168. self.ivals = list(self.ivals)
  169. return self.__class__(self.ivals[i: j])
  170. def __setslice__(self, i, j, s):
  171. if isinstance(self.ivals, tuple):
  172. self.ivals = list(self.ivals)
  173. self.ivals[i: j] = s
  174. self.__ivalsDirty = 1
  175. assert self.validateComponents(s)
  176. def __delslice__(self, i, j):
  177. if isinstance(self.ivals, tuple):
  178. self.ivals = list(self.ivals)
  179. del self.ivals[i: j]
  180. self.__ivalsDirty = 1
  181. def __iadd__(self, other):
  182. if isinstance(self.ivals, tuple):
  183. self.ivals = list(self.ivals)
  184. if isinstance(other, MetaInterval):
  185. assert self.__class__ == other.__class__
  186. ivals = other.ivals
  187. else:
  188. ivals = list(other)
  189. self.ivals += ivals
  190. self.__ivalsDirty = 1
  191. assert self.validateComponents(ivals)
  192. return self
  193. def __add__(self, other):
  194. copy = self[:]
  195. copy += other
  196. return copy
  197. # Functions to define sequence, parallel, and track behaviors:
  198. def addSequence(self, list, name, relTime, relTo, duration):
  199. # Adds the given list of intervals to the MetaInterval to be
  200. # played one after the other.
  201. self.pushLevel(name, relTime, relTo)
  202. for ival in list:
  203. self.addInterval(ival, 0.0, PREVIOUS_END)
  204. self.popLevel(duration)
  205. def addParallel(self, list, name, relTime, relTo, duration):
  206. # Adds the given list of intervals to the MetaInterval to be
  207. # played simultaneously; all will start at the same time.
  208. self.pushLevel(name, relTime, relTo)
  209. for ival in list:
  210. self.addInterval(ival, 0.0, TRACK_START)
  211. self.popLevel(duration)
  212. def addParallelEndTogether(self, list, name, relTime, relTo, duration):
  213. # Adds the given list of intervals to the MetaInterval to be
  214. # played simultaneously; all will end at the same time, but
  215. # the longest interval will be started first to achieve this.
  216. maxDuration = 0
  217. for ival in list:
  218. maxDuration = max(maxDuration, ival.getDuration())
  219. self.pushLevel(name, relTime, relTo)
  220. for ival in list:
  221. self.addInterval(ival, maxDuration - ival.getDuration(), TRACK_START)
  222. self.popLevel(duration)
  223. def addTrack(self, trackList, name, relTime, relTo, duration):
  224. # Adds a "track list". This is a list of tuples of the form:
  225. #
  226. # (<delay>, <Interval>,
  227. # PREVIOUS_END | PREVIOUS_START | TRACK_START)
  228. #
  229. # where <delay> is a relative time, in seconds, for the
  230. # <Interval> to start, relative to either the end of the
  231. # previous interval (PREVIOUS_END), the start of the previous
  232. # interval (PREVIOUS_START) or the start of the track list
  233. # (TRACK_START). If the relative code is omitted, the default
  234. # is TRACK_START.
  235. self.pushLevel(name, relTime, relTo)
  236. for tupleObj in trackList:
  237. if isinstance(tupleObj, tuple) or \
  238. isinstance(tupleObj, list):
  239. relTime = tupleObj[0]
  240. ival = tupleObj[1]
  241. if len(tupleObj) >= 3:
  242. relTo = tupleObj[2]
  243. else:
  244. relTo = TRACK_START
  245. self.addInterval(ival, relTime, relTo)
  246. else:
  247. self.notify.error("Not a tuple in Track: %s" % (tupleObj,))
  248. self.popLevel(duration)
  249. def addInterval(self, ival, relTime, relTo):
  250. # Adds the given interval to the MetaInterval.
  251. if isinstance(ival, CInterval):
  252. # It's a C++-style Interval, so add it directly.
  253. if getattr(ival, "inPython", 0):
  254. # Actually, it's been flagged to run in Python, even
  255. # though it's a C++ Interval. It's probably got some
  256. # Python functors that must be invoked at runtime to
  257. # define some of its parameters. Treat it as a Python
  258. # interval.
  259. index = len(self.pythonIvals)
  260. self.pythonIvals.append(ival)
  261. self.addExtIndex(index, ival.getName(), ival.getDuration(),
  262. ival.getOpenEnded(), relTime, relTo)
  263. elif isinstance(ival, MetaInterval):
  264. # It's another MetaInterval, so copy in its intervals
  265. # directly to this object. We could just store the
  266. # MetaInterval itself, which would work, but we get a
  267. # performance advantage by flattening out the deeply
  268. # nested hierarchy into a linear list within the root
  269. # CMetaInterval object.
  270. ival.applyIvals(self, relTime, relTo)
  271. else:
  272. # Nope, a perfectly ordinary C++ interval. Hooray!
  273. self.addCInterval(ival, relTime, relTo)
  274. elif isinstance(ival, Interval.Interval):
  275. # It's a Python-style Interval, so add it as an external.
  276. index = len(self.pythonIvals)
  277. self.pythonIvals.append(ival)
  278. if self.pstats:
  279. ival.pstats = PStatCollector(self.pstats, ival.pname)
  280. self.addExtIndex(index, ival.getName(), ival.getDuration(),
  281. ival.getOpenEnded(), relTime, relTo)
  282. else:
  283. self.notify.error("Not an Interval: %s" % (ival,))
  284. # Functions to support automatic playback of MetaIntervals along
  285. # with all of their associated Python callbacks:
  286. def setManager(self, manager):
  287. self.__manager = manager
  288. CMetaInterval.setManager(self, manager)
  289. def getManager(self):
  290. return self.__manager
  291. manager = property(getManager, setManager)
  292. def setT(self, t):
  293. self.__updateIvals()
  294. CMetaInterval.setT(self, t)
  295. t = property(CMetaInterval.getT, setT)
  296. def start(self, startT = 0.0, endT = -1.0, playRate = 1.0):
  297. self.__updateIvals()
  298. self.setupPlay(startT, endT, playRate, 0)
  299. self.__manager.addInterval(self)
  300. def loop(self, startT = 0.0, endT = -1.0, playRate = 1.0):
  301. self.__updateIvals()
  302. self.setupPlay(startT, endT, playRate, 1)
  303. self.__manager.addInterval(self)
  304. def pause(self):
  305. if self.getState() == CInterval.SStarted:
  306. self.privInterrupt()
  307. self.__manager.removeInterval(self)
  308. self.privPostEvent()
  309. return self.getT()
  310. def resume(self, startT = None):
  311. self.__updateIvals()
  312. if startT is not None:
  313. self.setT(startT)
  314. self.setupResume()
  315. self.__manager.addInterval(self)
  316. def resumeUntil(self, endT):
  317. self.__updateIvals()
  318. self.setupResumeUntil(endT)
  319. self.__manager.addInterval(self)
  320. def finish(self):
  321. self.__updateIvals()
  322. state = self.getState()
  323. if state == CInterval.SInitial:
  324. self.privInstant()
  325. elif state != CInterval.SFinal:
  326. self.privFinalize()
  327. self.__manager.removeInterval(self)
  328. self.privPostEvent()
  329. def clearToInitial(self):
  330. # This is overloaded at the Python level to properly call
  331. # pause() at the Python level, then upcall to finish the job
  332. # at the C++ level.
  333. self.pause()
  334. CMetaInterval.clearToInitial(self)
  335. # Internal functions:
  336. def validateComponent(self, component):
  337. # This is called only in debug mode to verify that the
  338. # indicated component added to the MetaInterval is appropriate
  339. # to this type of MetaInterval. In most cases except Track,
  340. # this is the same as asking that the component is itself an
  341. # Interval.
  342. return isinstance(component, CInterval) or \
  343. isinstance(component, Interval.Interval)
  344. def validateComponents(self, components):
  345. # This is called only in debug mode to verify that all the
  346. # components on the indicated list are appropriate to this
  347. # type of MetaInterval.
  348. for component in components:
  349. if not self.validateComponent(component):
  350. return 0
  351. return 1
  352. def __updateIvals(self):
  353. # The MetaInterval object does not create the C++ list of
  354. # Intervals immediately; rather, it stores a Python list of
  355. # Intervals that will be compiled into the C++ list the first
  356. # time it is needed.
  357. # This design allows us to avoid creation of the C++ list for
  358. # nested MetaInterval objects, instead copying all nested
  359. # MetaInterval hierarchy into the root CMetaInterval object,
  360. # for a performance benefit.
  361. # This function is called only on the root MetaInterval
  362. # object, when it is time to build the C++ list for itself.
  363. if self.__ivalsDirty:
  364. self.clearIntervals()
  365. self.applyIvals(self, 0, TRACK_START)
  366. self.__ivalsDirty = 0
  367. def clearIntervals(self):
  368. # This overrides the function defined at the C++ level to
  369. # reset the inPython flag. Clearing out the intervals list
  370. # allows us to run entirely in C++ again, at least until a new
  371. # Python interval gets added.
  372. CMetaInterval.clearIntervals(self)
  373. self.inPython = 0
  374. def applyIvals(self, meta, relTime, relTo):
  375. # Add the intervals listed in this object to the given
  376. # MetaInterval object at the C++ level. This will make the
  377. # other MetaInterval object ready to play the intervals.
  378. # This function should be overridden in a derived class to
  379. # change the intepretation of the intervals in this list. In
  380. # the case of a MetaInterval directly, this is valid only if
  381. # the list has only zero or one intervals.
  382. if len(self.ivals) == 0:
  383. pass
  384. elif len(self.ivals) == 1:
  385. meta.addInterval(self.ivals[0], relTime, relTo)
  386. else:
  387. self.notify.error("Cannot build list from MetaInterval directly.")
  388. def setPlayRate(self, playRate):
  389. """ Changes the play rate of the interval. If the interval is
  390. already started, this changes its speed on-the-fly. Note that
  391. since playRate is a parameter to start() and loop(), the next
  392. call to start() or loop() will reset this parameter. """
  393. if self.isPlaying():
  394. self.pause()
  395. CMetaInterval.setPlayRate(self, playRate)
  396. self.resume()
  397. else:
  398. CMetaInterval.setPlayRate(self, playRate)
  399. play_rate = property(CMetaInterval.getPlayRate, setPlayRate)
  400. def __doPythonCallbacks(self):
  401. # This function invokes any Python-level Intervals that need
  402. # to be invoked at this point in time. It must be called
  403. # after any call to setT() or setFinalT() or stepPlay(), or
  404. # some such; basically any function that might invoke an
  405. # interval. The C++ base class will invoke whatever C++
  406. # intervals it can, and then indicate the Python intervals
  407. # that must be invoked through this interface.
  408. ival = None
  409. try:
  410. while self.isEventReady():
  411. index = self.getEventIndex()
  412. t = self.getEventT()
  413. eventType = self.getEventType()
  414. self.popEvent()
  415. ival = self.pythonIvals[index]
  416. ival.privDoEvent(t, eventType)
  417. ival.privPostEvent()
  418. ival = None
  419. except:
  420. if ival is not None:
  421. print("Exception occurred while processing %s of %s:" % (ival.getName(), self.getName()))
  422. else:
  423. print("Exception occurred while processing %s:" % (self.getName()))
  424. print(self)
  425. raise
  426. def privDoEvent(self, t, event):
  427. # This function overrides the C++ function to initialize the
  428. # intervals first if necessary.
  429. if self.pstats:
  430. self.pstats.start()
  431. self.__updateIvals()
  432. CMetaInterval.privDoEvent(self, t, event)
  433. if self.pstats:
  434. self.pstats.stop()
  435. def privPostEvent(self):
  436. if self.pstats:
  437. self.pstats.start()
  438. self.__doPythonCallbacks()
  439. CMetaInterval.privPostEvent(self)
  440. if self.pstats:
  441. self.pstats.stop()
  442. def setIntervalStartTime(self, *args, **kw):
  443. # This function overrides from the parent level to force it to
  444. # update the interval list first, if necessary.
  445. self.__updateIvals()
  446. # Once we have monkeyed with the interval timings, we'd better
  447. # run the whole thing as a monolithic Python interval, since
  448. # we can't extract the ivals list back out and append them
  449. # into a parent MetaInterval.
  450. self.inPython = 1
  451. return CMetaInterval.setIntervalStartTime(self, *args, **kw)
  452. def getIntervalStartTime(self, *args, **kw):
  453. # This function overrides from the parent level to force it to
  454. # update the interval list first, if necessary.
  455. self.__updateIvals()
  456. return CMetaInterval.getIntervalStartTime(self, *args, **kw)
  457. def getDuration(self):
  458. # This function overrides from the parent level to force it to
  459. # update the interval list first, if necessary.
  460. self.__updateIvals()
  461. return CMetaInterval.getDuration(self)
  462. duration = property(getDuration)
  463. def __repr__(self, *args, **kw):
  464. # This function overrides from the parent level to force it to
  465. # update the interval list first, if necessary.
  466. self.__updateIvals()
  467. return CMetaInterval.__repr__(self, *args, **kw)
  468. def __str__(self, *args, **kw):
  469. # This function overrides from the parent level to force it to
  470. # update the interval list first, if necessary.
  471. self.__updateIvals()
  472. return CMetaInterval.__str__(self, *args, **kw)
  473. def timeline(self, out = None):
  474. # This function overrides from the parent level to force it to
  475. # update the interval list first, if necessary.
  476. self.__updateIvals()
  477. if out is None:
  478. out = ostream
  479. CMetaInterval.timeline(self, out)
  480. add_sequence = addSequence
  481. add_parallel = addParallel
  482. add_parallel_end_together = addParallelEndTogether
  483. add_track = addTrack
  484. add_interval = addInterval
  485. set_manager = setManager
  486. get_manager = getManager
  487. set_t = setT
  488. resume_until = resumeUntil
  489. clear_to_initial = clearToInitial
  490. clear_intervals = clearIntervals
  491. set_play_rate = setPlayRate
  492. priv_do_event = privDoEvent
  493. priv_post_event = privPostEvent
  494. set_interval_start_time = setIntervalStartTime
  495. get_interval_start_time = getIntervalStartTime
  496. get_duration = getDuration
  497. class Sequence(MetaInterval):
  498. def applyIvals(self, meta, relTime, relTo):
  499. meta.addSequence(self.ivals, self.getName(),
  500. relTime, relTo, self.phonyDuration)
  501. class Parallel(MetaInterval):
  502. def applyIvals(self, meta, relTime, relTo):
  503. meta.addParallel(self.ivals, self.getName(),
  504. relTime, relTo, self.phonyDuration)
  505. class ParallelEndTogether(MetaInterval):
  506. def applyIvals(self, meta, relTime, relTo):
  507. meta.addParallelEndTogether(self.ivals, self.getName(),
  508. relTime, relTo, self.phonyDuration)
  509. class Track(MetaInterval):
  510. def applyIvals(self, meta, relTime, relTo):
  511. meta.addTrack(self.ivals, self.getName(),
  512. relTime, relTo, self.phonyDuration)
  513. def validateComponent(self, tupleObj):
  514. # This is called only in debug mode to verify that the
  515. # indicated component added to the MetaInterval is appropriate
  516. # to this type of MetaInterval. In most cases except Track,
  517. # this is the same as asking that the component is itself an
  518. # Interval.
  519. if not (isinstance(tupleObj, tuple) or \
  520. isinstance(tupleObj, list)):
  521. # It's not a tuple.
  522. return 0
  523. relTime = tupleObj[0]
  524. ival = tupleObj[1]
  525. if len(tupleObj) >= 3:
  526. relTo = tupleObj[2]
  527. else:
  528. relTo = TRACK_START
  529. if not (isinstance(relTime, float) or \
  530. isinstance(relTime, int)):
  531. # First parameter is not a number.
  532. return 0
  533. if not MetaInterval.validateComponent(self, ival):
  534. # Second parameter is not an interval.
  535. return 0
  536. if relTo != PREVIOUS_END and \
  537. relTo != PREVIOUS_START and \
  538. relTo != TRACK_START:
  539. # Third parameter is an invalid value.
  540. return 0
  541. # Looks good.
  542. return 1