PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/src/as/com/threerings/flashbang/pbe/PBEMainLoop.as

http://libdamago.googlecode.com/
ActionScript | 464 lines | 297 code | 73 blank | 94 comment | 57 complexity | db9acf8c3650142f98ae598f49636837 MD5 | raw file
Possible License(s): Apache-2.0
  1. package com.threerings.flashbang.pushbutton
  2. {
  3. import com.threerings.flashbang.audio.*;
  4. import com.threerings.flashbang.resource.*;
  5. import com.threerings.util.ArrayUtil;
  6. import com.threerings.util.Assert;
  7. import flash.display.Sprite;
  8. import flash.events.Event;
  9. import flash.events.EventDispatcher;
  10. import flash.events.IEventDispatcher;
  11. import flash.events.KeyboardEvent;
  12. public class PBEMainLoop extends EventDispatcher
  13. {
  14. public static const HAS_STOPPED :String = "HasStopped";
  15. public static const HAS_SHUTDOWN :String = "HasShutdown";
  16. public function PBEMainLoop (ctx :PBEContext, minFrameRate :Number)
  17. {
  18. _ctx = ctx;
  19. _minFrameRate = minFrameRate;
  20. }
  21. /**
  22. * Initializes structures required by the framework.
  23. */
  24. public function setup () :void
  25. {
  26. }
  27. /**
  28. * Call this function before the application shuts down to release
  29. * memory and disconnect event handlers. The MainLoop may not shut down
  30. * immediately when this function is called - if it is running, it will be
  31. * shut down at the end of the current update.
  32. *
  33. * It's an error to continue to use a MainLoop that has been shut down.
  34. *
  35. * Most applications will want to install an Event.REMOVED_FROM_STAGE
  36. * handler on the main sprite, and call shutdown from there.
  37. */
  38. public function shutdown () :void
  39. {
  40. if (_running) {
  41. _shutdownPending = true;
  42. } else {
  43. shutdownNow();
  44. }
  45. }
  46. public function addUpdatable (obj :Updatable) :void
  47. {
  48. _updatables.push(obj);
  49. }
  50. public function removeUpdatable (obj :Updatable) :void
  51. {
  52. ArrayUtil.removeFirst(_updatables, obj);
  53. }
  54. /**
  55. * Returns the top mode on the mode stack, or null
  56. * if the stack is empty.
  57. */
  58. public function get topMode () :PBEAppmode
  59. {
  60. if (_modeStack.length == 0) {
  61. return null;
  62. } else {
  63. return ((_modeStack[_modeStack.length - 1]) as PBEAppmode);
  64. }
  65. }
  66. /**
  67. * Kicks off the MainLoop. Game updates will start happening after this
  68. * function is called.
  69. */
  70. public function run (hostSprite :Sprite, keyDispatcher :IEventDispatcher = null) :void
  71. {
  72. if (_running) {
  73. throw new Error("already running");
  74. }
  75. if (null == hostSprite) {
  76. throw new ArgumentError("hostSprite must not be null");
  77. }
  78. _hostSprite = hostSprite;
  79. _keyDispatcher = (null != keyDispatcher ? keyDispatcher : _hostSprite);
  80. // ensure that proper setup has completed
  81. setup();
  82. _running = true;
  83. _stopPending = false;
  84. _hostSprite.addEventListener(Event.ENTER_FRAME, update);
  85. _keyDispatcher.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
  86. _keyDispatcher.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
  87. _lastTime = this.elapsedSeconds;
  88. // Handle initial mode transitions made since MainLoop creation
  89. handleModeTransitions();
  90. }
  91. /**
  92. * Stops the MainLoop from running. It can be restarted by calling run() again.
  93. * The MainLoop may not stop immediately when this function is called -
  94. * if it is running, it will be stopped at the end of the current update.
  95. */
  96. public function stop () :void
  97. {
  98. if (_running) {
  99. _stopPending = true;
  100. }
  101. }
  102. /**
  103. * Applies the specify mode transition to the mode stack.
  104. * (Mode changes take effect between game updates.)
  105. */
  106. public function doModeTransition (type :ModeTransition, mode :PBEAppmode = null, index :int = 0)
  107. :void
  108. {
  109. if (type.requiresMode && mode == null) {
  110. throw new Error("mode must be non-null for " + type);
  111. }
  112. var transition :PendingTransition = new PendingTransition();
  113. transition.type = type;
  114. transition.mode = mode;
  115. transition.index = index;
  116. _pendingModeTransitionQueue.push(transition);
  117. }
  118. /**
  119. * Inserts a mode into the stack at the specified index. All modes
  120. * at and above the specified index will move up in the stack.
  121. * (Mode changes take effect between game updates.)
  122. *
  123. * @param mode the PBEAppmode to add
  124. * @param index the stack position to add the mode at.
  125. * You can use a negative integer to specify a position relative
  126. * to the top of the stack (for example, -1 is the top of the stack).
  127. */
  128. public function insertMode (mode :PBEAppmode, index :int) :void
  129. {
  130. doModeTransition(ModeTransition.INSERT, mode, index);
  131. }
  132. /**
  133. * Removes a mode from the stack at the specified index. All
  134. * modes above the specified index will move down in the stack.
  135. * (Mode changes take effect between game updates.)
  136. *
  137. * @param index the stack position to add the mode at.
  138. * You can use a negative integer to specify a position relative
  139. * to the top of the stack (for example, -1 is the top of the stack).
  140. */
  141. public function removeMode (index :int) :void
  142. {
  143. doModeTransition(ModeTransition.REMOVE, null, index);
  144. }
  145. /**
  146. * Pops the top mode from the stack, if the modestack is not empty, and pushes
  147. * a new mode in its place.
  148. * (Mode changes take effect between game updates.)
  149. */
  150. public function changeMode (mode :PBEAppmode) :void
  151. {
  152. doModeTransition(ModeTransition.CHANGE, mode);
  153. }
  154. /**
  155. * Pushes a mode to the mode stack.
  156. * (Mode changes take effect between game updates.)
  157. */
  158. public function pushMode (mode :PBEAppmode) :void
  159. {
  160. doModeTransition(ModeTransition.PUSH, mode);
  161. }
  162. /**
  163. * Pops the top mode from the mode stack.
  164. * (Mode changes take effect between game updates.)
  165. */
  166. public function popMode () :void
  167. {
  168. doModeTransition(ModeTransition.REMOVE, null, -1);
  169. }
  170. /**
  171. * Pops all modes from the mode stack.
  172. * Mode changes take effect before game updates.
  173. */
  174. public function popAllModes () :void
  175. {
  176. doModeTransition(ModeTransition.UNWIND);
  177. }
  178. /**
  179. * Pops modes from the stack until the specified mode is reached.
  180. * If the specified mode is not reached, it will be pushed to the top
  181. * of the mode stack.
  182. * Mode changes take effect before game updates.
  183. */
  184. public function unwindToMode (mode :PBEAppmode) :void
  185. {
  186. doModeTransition(ModeTransition.UNWIND, mode);
  187. }
  188. /** Returns the number of seconds that have elapsed since the application started. */
  189. public function get elapsedSeconds () :Number
  190. {
  191. return (flash.utils.getTimer() / 1000); // getTimer() returns a value in milliseconds
  192. }
  193. /**
  194. * Returns the approximate frames-per-second that the application
  195. * is running at.
  196. */
  197. public function get fps () :Number
  198. {
  199. return _fps;
  200. }
  201. protected function handleModeTransitions () :void
  202. {
  203. if (_pendingModeTransitionQueue.length <= 0) {
  204. return;
  205. }
  206. var initialTopMode :PBEAppmode = this.topMode;
  207. function doPushMode (newMode :PBEAppmode) :void {
  208. if (null == newMode) {
  209. throw new Error("Can't push a null mode to the mode stack");
  210. }
  211. _modeStack.push(newMode);
  212. _hostSprite.addChild(newMode.modeSprite);
  213. newMode._ctx = _ctx;
  214. newMode.setupInternal();
  215. }
  216. function doInsertMode (newMode :PBEAppmode, index :int) :void {
  217. if (null == newMode) {
  218. throw new Error("Can't insert a null mode in the mode stack");
  219. }
  220. if (index < 0) {
  221. index = _modeStack.length + index;
  222. }
  223. index = Math.max(index, 0);
  224. index = Math.min(index, _modeStack.length);
  225. _modeStack.splice(index, 0, newMode);
  226. _hostSprite.addChildAt(newMode.modeSprite, index);
  227. newMode._ctx = _ctx;
  228. newMode.setupInternal();
  229. }
  230. function doRemoveMode (index :int) :void {
  231. if (_modeStack.length == 0) {
  232. throw new Error("Can't remove a mode from an empty stack");
  233. }
  234. if (index < 0) {
  235. index = _modeStack.length + index;
  236. }
  237. index = Math.max(index, 0);
  238. index = Math.min(index, _modeStack.length - 1);
  239. // if the top mode is removed, make sure it's exited first
  240. var mode :PBEAppmode = _modeStack[index];
  241. if (mode == initialTopMode) {
  242. initialTopMode.exitInternal();
  243. initialTopMode = null;
  244. }
  245. mode.destroyInternal();
  246. mode._ctx = null;
  247. _modeStack.splice(index, 1);
  248. _hostSprite.removeChild(mode.modeSprite);
  249. }
  250. // create a new _pendingModeTransitionQueue right now
  251. // so that we can properly handle mode transition requests
  252. // that occur during the processing of the current queue
  253. var transitionQueue :Array = _pendingModeTransitionQueue;
  254. _pendingModeTransitionQueue = [];
  255. for each (var transition :PendingTransition in transitionQueue) {
  256. var mode :PBEAppmode = transition.mode;
  257. switch (transition.type) {
  258. case ModeTransition.PUSH:
  259. doPushMode(mode);
  260. break;
  261. case ModeTransition.INSERT:
  262. doInsertMode(mode, transition.index);
  263. break;
  264. case ModeTransition.REMOVE:
  265. doRemoveMode(transition.index);
  266. break;
  267. case ModeTransition.CHANGE:
  268. // a pop followed by a push
  269. if (null != this.topMode) {
  270. doRemoveMode(-1);
  271. }
  272. doPushMode(mode);
  273. break;
  274. case ModeTransition.UNWIND:
  275. // pop modes until we find the one we're looking for
  276. while (_modeStack.length > 0 && this.topMode != mode) {
  277. doRemoveMode(-1);
  278. }
  279. Assert.isTrue(this.topMode == mode || _modeStack.length == 0);
  280. if (_modeStack.length == 0 && null != mode) {
  281. doPushMode(mode);
  282. }
  283. break;
  284. }
  285. }
  286. var topMode :PBEAppmode = this.topMode;
  287. if (topMode != initialTopMode) {
  288. if (null != initialTopMode) {
  289. initialTopMode.exitInternal();
  290. }
  291. if (null != topMode) {
  292. topMode.enterInternal();
  293. }
  294. }
  295. }
  296. protected function update (e :Event) :void
  297. {
  298. // how much time has elapsed since last frame?
  299. var newTime :Number = this.elapsedSeconds;
  300. var dt :Number = newTime - _lastTime;
  301. // If we have pending mode transitions, handle them, and discount
  302. // the time that it took to perform this processing, so that changing
  303. // modes doesn't result in stuttery behavior
  304. if (_pendingModeTransitionQueue.length > 0) {
  305. handleModeTransitions();
  306. newTime = this.elapsedSeconds;
  307. dt = 1 / 30; // Assume 30 fps is our target. Should this be configurable?
  308. }
  309. if (_minFrameRate > 0) {
  310. // Ensure that our time deltas don't get too large
  311. dt = Math.min(1 / _minFrameRate, dt);
  312. }
  313. _fps = 1 / dt;
  314. // update all our "updatables"
  315. for each (var updatable :Updatable in _updatables) {
  316. updatable.update(dt);
  317. }
  318. // update the top mode
  319. var theTopMode :PBEAppmode = this.topMode;
  320. if (null != theTopMode) {
  321. theTopMode.update(dt);
  322. }
  323. // should the MainLoop be stopped?
  324. if (_stopPending || _shutdownPending) {
  325. _hostSprite.removeEventListener(Event.ENTER_FRAME, update);
  326. _keyDispatcher.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
  327. _keyDispatcher.removeEventListener(KeyboardEvent.KEY_UP, onKeyUp);
  328. clearModeStackNow();
  329. _running = false;
  330. dispatchEvent(new Event(HAS_STOPPED));
  331. if (_shutdownPending) {
  332. shutdownNow();
  333. }
  334. }
  335. _lastTime = newTime;
  336. }
  337. protected function onKeyDown (e :KeyboardEvent) :void
  338. {
  339. var topMode :PBEAppmode = this.topMode;
  340. if (null != topMode) {
  341. topMode.onKeyDown(e.keyCode);
  342. }
  343. }
  344. protected function onKeyUp (e :KeyboardEvent) :void
  345. {
  346. var topMode :PBEAppmode = this.topMode;
  347. if (null != topMode) {
  348. topMode.onKeyUp(e.keyCode);
  349. }
  350. }
  351. protected function clearModeStackNow () :void
  352. {
  353. _pendingModeTransitionQueue = [];
  354. if (_modeStack.length > 0) {
  355. popAllModes();
  356. handleModeTransitions();
  357. }
  358. }
  359. protected function shutdownNow () :void
  360. {
  361. clearModeStackNow();
  362. _ctx = null;
  363. _hostSprite = null;
  364. _keyDispatcher = null;
  365. _modeStack = null;
  366. _pendingModeTransitionQueue = null;
  367. _updatables = null;
  368. dispatchEvent(new Event(HAS_SHUTDOWN));
  369. }
  370. protected var _ctx :PBEContext;
  371. protected var _minFrameRate :Number;
  372. protected var _hostSprite :Sprite;
  373. protected var _keyDispatcher :IEventDispatcher;
  374. protected var _hasSetup :Boolean;
  375. protected var _running :Boolean;
  376. protected var _stopPending :Boolean;
  377. protected var _shutdownPending :Boolean;
  378. protected var _lastTime :Number;
  379. protected var _modeStack :Array = [];
  380. protected var _pendingModeTransitionQueue :Array = [];
  381. protected var _updatables :Array = [];
  382. protected var _fps :Number = 0;
  383. }
  384. }
  385. import com.threerings.flashbang.ModeTransition;
  386. class PendingTransition
  387. {
  388. public var mode :PBEAppmode;
  389. public var type :ModeTransition;
  390. public var index :int;
  391. }