PageRenderTime 48ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/flashbang/main/as/com/threerings/flashbang/MainLoop.as

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