555 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
		
		
			
		
	
	
			555 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
| 
								 | 
							
								package kha;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class TimeTask {
							 | 
						||
| 
								 | 
							
									public var task: Void->Bool;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public var start: Float;
							 | 
						||
| 
								 | 
							
									public var period: Float;
							 | 
						||
| 
								 | 
							
									public var duration: Float;
							 | 
						||
| 
								 | 
							
									public var next: Float;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public var id: Int;
							 | 
						||
| 
								 | 
							
									public var groupId: Int;
							 | 
						||
| 
								 | 
							
									public var active: Bool;
							 | 
						||
| 
								 | 
							
									public var paused: Bool;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public function new() {}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class FrameTask {
							 | 
						||
| 
								 | 
							
									public var task: Void->Bool;
							 | 
						||
| 
								 | 
							
									public var priority: Int;
							 | 
						||
| 
								 | 
							
									public var id: Int;
							 | 
						||
| 
								 | 
							
									public var active: Bool;
							 | 
						||
| 
								 | 
							
									public var paused: Bool;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public function new(task: Void->Bool, priority: Int, id: Int) {
							 | 
						||
| 
								 | 
							
										this.task = task;
							 | 
						||
| 
								 | 
							
										this.priority = priority;
							 | 
						||
| 
								 | 
							
										this.id = id;
							 | 
						||
| 
								 | 
							
										active = true;
							 | 
						||
| 
								 | 
							
										paused = false;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Scheduler {
							 | 
						||
| 
								 | 
							
									static var timeTasks: Array<TimeTask>;
							 | 
						||
| 
								 | 
							
									static var pausedTimeTasks: Array<TimeTask>;
							 | 
						||
| 
								 | 
							
									static var outdatedTimeTasks: Array<TimeTask>;
							 | 
						||
| 
								 | 
							
									static var timeTasksScratchpad: Array<TimeTask>;
							 | 
						||
| 
								 | 
							
									static inline var timeWarpSaveTime: Float = 10.0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static var frameTasks: Array<FrameTask>;
							 | 
						||
| 
								 | 
							
									static var toDeleteFrame: Array<FrameTask>;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static var current: Float;
							 | 
						||
| 
								 | 
							
									static var lastTime: Float;
							 | 
						||
| 
								 | 
							
									static var lastFrameEnd: Float;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static var frame_tasks_sorted: Bool;
							 | 
						||
| 
								 | 
							
									static var stopped: Bool;
							 | 
						||
| 
								 | 
							
									static var vsync: Bool;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Html5 target can update display frequency after some delay
							 | 
						||
| 
								 | 
							
									#if kha_html5
							 | 
						||
| 
								 | 
							
									static var onedifhz(get, never): Float;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static inline function get_onedifhz(): Float {
							 | 
						||
| 
								 | 
							
										return 1.0 / Display.primary.frequency;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									#else
							 | 
						||
| 
								 | 
							
									static var onedifhz: Float;
							 | 
						||
| 
								 | 
							
									#end
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static var currentFrameTaskId: Int;
							 | 
						||
| 
								 | 
							
									static var currentTimeTaskId: Int;
							 | 
						||
| 
								 | 
							
									static var currentGroupId: Int;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static var DIF_COUNT = 3;
							 | 
						||
| 
								 | 
							
									static var maxframetime = 0.5;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static var deltas: Array<Float>;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static var startTime: Float = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static var activeTimeTask: TimeTask = null;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function init(): Void {
							 | 
						||
| 
								 | 
							
										deltas = new Array<Float>();
							 | 
						||
| 
								 | 
							
										for (i in 0...DIF_COUNT)
							 | 
						||
| 
								 | 
							
											deltas[i] = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										stopped = true;
							 | 
						||
| 
								 | 
							
										frame_tasks_sorted = true;
							 | 
						||
| 
								 | 
							
										current = lastTime = lastFrameEnd = realTime();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										currentFrameTaskId = 0;
							 | 
						||
| 
								 | 
							
										currentTimeTaskId = 0;
							 | 
						||
| 
								 | 
							
										currentGroupId = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										timeTasks = [];
							 | 
						||
| 
								 | 
							
										pausedTimeTasks = [];
							 | 
						||
| 
								 | 
							
										outdatedTimeTasks = [];
							 | 
						||
| 
								 | 
							
										timeTasksScratchpad = [];
							 | 
						||
| 
								 | 
							
										frameTasks = [];
							 | 
						||
| 
								 | 
							
										toDeleteFrame = [];
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function start(restartTimers: Bool = false): Void {
							 | 
						||
| 
								 | 
							
										vsync = Window.get(0).vSynced;
							 | 
						||
| 
								 | 
							
										#if !kha_html5
							 | 
						||
| 
								 | 
							
										var hz = Display.primary != null ? Display.primary.frequency : 60;
							 | 
						||
| 
								 | 
							
										if (hz >= 57 && hz <= 63)
							 | 
						||
| 
								 | 
							
											hz = 60;
							 | 
						||
| 
								 | 
							
										onedifhz = 1.0 / hz;
							 | 
						||
| 
								 | 
							
										#end
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										stopped = false;
							 | 
						||
| 
								 | 
							
										resetTime();
							 | 
						||
| 
								 | 
							
										lastTime = realTime() - startTime;
							 | 
						||
| 
								 | 
							
										for (i in 0...DIF_COUNT)
							 | 
						||
| 
								 | 
							
											deltas[i] = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if (restartTimers) {
							 | 
						||
| 
								 | 
							
											for (timeTask in timeTasks) {
							 | 
						||
| 
								 | 
							
												timeTask.paused = false;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											for (frameTask in frameTasks) {
							 | 
						||
| 
								 | 
							
												frameTask.paused = false;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function stop(): Void {
							 | 
						||
| 
								 | 
							
										stopped = true;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function isStopped(): Bool {
							 | 
						||
| 
								 | 
							
										return stopped;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static function warpTimeTasksBack(time: Float, tasks: Array<TimeTask>): Void {
							 | 
						||
| 
								 | 
							
										for (timeTask in tasks) {
							 | 
						||
| 
								 | 
							
											if (timeTask.start >= time) {
							 | 
						||
| 
								 | 
							
												timeTask.next = timeTask.start;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											else if (timeTask.period > 0) {
							 | 
						||
| 
								 | 
							
												var sinceStart = time - timeTask.start;
							 | 
						||
| 
								 | 
							
												var times = Math.ceil(sinceStart / timeTask.period);
							 | 
						||
| 
								 | 
							
												timeTask.next = timeTask.start + times * timeTask.period;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function warp(time: Float): Void {
							 | 
						||
| 
								 | 
							
										if (time < lastTime) {
							 | 
						||
| 
								 | 
							
											current = time;
							 | 
						||
| 
								 | 
							
											lastTime = time;
							 | 
						||
| 
								 | 
							
											lastFrameEnd = time;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											warpTimeTasksBack(time, outdatedTimeTasks);
							 | 
						||
| 
								 | 
							
											warpTimeTasksBack(time, timeTasks);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											for (task in outdatedTimeTasks) {
							 | 
						||
| 
								 | 
							
												if (task.next >= time) {
							 | 
						||
| 
								 | 
							
													timeTasksScratchpad.push(task);
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											for (task in timeTasksScratchpad) {
							 | 
						||
| 
								 | 
							
												outdatedTimeTasks.remove(task);
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											for (task in timeTasksScratchpad) {
							 | 
						||
| 
								 | 
							
												insertSorted(timeTasks, task);
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											while (timeTasksScratchpad.length > 0) {
							 | 
						||
| 
								 | 
							
												timeTasksScratchpad.remove(timeTasksScratchpad[0]);
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										else if (time > lastTime) {
							 | 
						||
| 
								 | 
							
											// TODO: Changing startTime line prevents clients from falling into a
							 | 
						||
| 
								 | 
							
											// warp-forward-then-wait-for-systemtime-to-catch-up-loop that causes
							 | 
						||
| 
								 | 
							
											// choppy movement (e.g. every 3rd frame forward 3 times).
							 | 
						||
| 
								 | 
							
											// But it causes backwards jumps in originally constant movements.
							 | 
						||
| 
								 | 
							
											// And on HTML5 packets are received while no frames are executed,
							 | 
						||
| 
								 | 
							
											// which causes the client to overtakes the server and then move
							 | 
						||
| 
								 | 
							
											// farther away with each packet while being unable to synch back
							 | 
						||
| 
								 | 
							
											// (backwards warping is not allowed to change startTime).
							 | 
						||
| 
								 | 
							
											startTime -= (time - lastTime);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											current = time;
							 | 
						||
| 
								 | 
							
											lastTime = time;
							 | 
						||
| 
								 | 
							
											lastFrameEnd = time;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											executeTimeTasks(time);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function executeFrame(): Void {
							 | 
						||
| 
								 | 
							
										var real = realTime();
							 | 
						||
| 
								 | 
							
										var now: Float = real - startTime;
							 | 
						||
| 
								 | 
							
										var delta = now - lastTime;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										var frameEnd: Float = lastFrameEnd;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if (delta >= 0) {
							 | 
						||
| 
								 | 
							
											if (kha.netsync.Session.the() == null) {
							 | 
						||
| 
								 | 
							
												// tdif = 1.0 / 60.0; //force fixed frame rate
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												if (delta > maxframetime) {
							 | 
						||
| 
								 | 
							
													startTime += delta - maxframetime;
							 | 
						||
| 
								 | 
							
													now = real - startTime;
							 | 
						||
| 
								 | 
							
													delta = maxframetime;
							 | 
						||
| 
								 | 
							
													frameEnd += delta;
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												else {
							 | 
						||
| 
								 | 
							
													if (vsync) {
							 | 
						||
| 
								 | 
							
														// var measured = delta;
							 | 
						||
| 
								 | 
							
														// this is optimized not to run at exact speed
							 | 
						||
| 
								 | 
							
														// but to run as fluid as possible
							 | 
						||
| 
								 | 
							
														var frames = Math.round(delta / onedifhz);
							 | 
						||
| 
								 | 
							
														if (frames < 1) {
							 | 
						||
| 
								 | 
							
															return;
							 | 
						||
| 
								 | 
							
														}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														var realdif = frames * onedifhz;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														delta = realdif;
							 | 
						||
| 
								 | 
							
														for (i in 0...DIF_COUNT - 2) {
							 | 
						||
| 
								 | 
							
															delta += deltas[i];
							 | 
						||
| 
								 | 
							
															deltas[i] = deltas[i + 1];
							 | 
						||
| 
								 | 
							
														}
							 | 
						||
| 
								 | 
							
														delta += deltas[DIF_COUNT - 2];
							 | 
						||
| 
								 | 
							
														delta /= DIF_COUNT;
							 | 
						||
| 
								 | 
							
														deltas[DIF_COUNT - 2] = realdif;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														frameEnd += delta;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														// trace("Measured: " + measured + " Frames: " + frames +  " Delta: " + delta + " ");
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
													else {
							 | 
						||
| 
								 | 
							
														for (i in 0...DIF_COUNT - 1) {
							 | 
						||
| 
								 | 
							
															deltas[i] = deltas[i + 1];
							 | 
						||
| 
								 | 
							
														}
							 | 
						||
| 
								 | 
							
														deltas[DIF_COUNT - 1] = delta;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														var next: Float = 0;
							 | 
						||
| 
								 | 
							
														for (i in 0...DIF_COUNT) {
							 | 
						||
| 
								 | 
							
															next += deltas[i];
							 | 
						||
| 
								 | 
							
														}
							 | 
						||
| 
								 | 
							
														next /= DIF_COUNT;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														// delta = interpolated_delta; // average the frame end estimation
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
														// lastTime = now;
							 | 
						||
| 
								 | 
							
														frameEnd += next;
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											else {
							 | 
						||
| 
								 | 
							
												frameEnd += delta;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											lastTime = now;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if (!stopped) { // Stop simulation time
							 | 
						||
| 
								 | 
							
												lastFrameEnd = frameEnd;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											// Extend endpoint by paused time (individually paused tasks)
							 | 
						||
| 
								 | 
							
											for (pausedTask in pausedTimeTasks) {
							 | 
						||
| 
								 | 
							
												pausedTask.next += delta;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if (stopped) {
							 | 
						||
| 
								 | 
							
												// Extend endpoint by paused time (running tasks)
							 | 
						||
| 
								 | 
							
												for (timeTask in timeTasks) {
							 | 
						||
| 
								 | 
							
													timeTask.next += delta;
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											executeTimeTasks(frameEnd);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											// Maintain outdated task list
							 | 
						||
| 
								 | 
							
											for (task in outdatedTimeTasks) {
							 | 
						||
| 
								 | 
							
												if (task.next < frameEnd - timeWarpSaveTime) {
							 | 
						||
| 
								 | 
							
													timeTasksScratchpad.push(task);
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											for (task in timeTasksScratchpad) {
							 | 
						||
| 
								 | 
							
												outdatedTimeTasks.remove(task);
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											while (timeTasksScratchpad.length > 0) {
							 | 
						||
| 
								 | 
							
												timeTasksScratchpad.remove(timeTasksScratchpad[0]);
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										current = frameEnd;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										sortFrameTasks();
							 | 
						||
| 
								 | 
							
										for (frameTask in frameTasks) {
							 | 
						||
| 
								 | 
							
											if (!stopped && !frameTask.paused && frameTask.active) {
							 | 
						||
| 
								 | 
							
												if (!frameTask.task())
							 | 
						||
| 
								 | 
							
													frameTask.active = false;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										for (frameTask in frameTasks) {
							 | 
						||
| 
								 | 
							
											if (!frameTask.active) {
							 | 
						||
| 
								 | 
							
												toDeleteFrame.push(frameTask);
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										while (toDeleteFrame.length > 0) {
							 | 
						||
| 
								 | 
							
											frameTasks.remove(toDeleteFrame.pop());
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static function executeTimeTasks(until: Float) {
							 | 
						||
| 
								 | 
							
										while (timeTasks.length > 0) {
							 | 
						||
| 
								 | 
							
											activeTimeTask = timeTasks[0];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											if (activeTimeTask.next <= until) {
							 | 
						||
| 
								 | 
							
												current = activeTimeTask.next;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												activeTimeTask.next += activeTimeTask.period;
							 | 
						||
| 
								 | 
							
												timeTasks.remove(activeTimeTask);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
												if (activeTimeTask.active && activeTimeTask.task()) {
							 | 
						||
| 
								 | 
							
													if (activeTimeTask.period > 0
							 | 
						||
| 
								 | 
							
														&& (activeTimeTask.duration == 0 || activeTimeTask.duration >= activeTimeTask.start + activeTimeTask.next)) {
							 | 
						||
| 
								 | 
							
														insertSorted(timeTasks, activeTimeTask);
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
													else {
							 | 
						||
| 
								 | 
							
														archiveTimeTask(activeTimeTask, until);
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												else {
							 | 
						||
| 
								 | 
							
													activeTimeTask.active = false;
							 | 
						||
| 
								 | 
							
													archiveTimeTask(activeTimeTask, until);
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											else {
							 | 
						||
| 
								 | 
							
												break;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										activeTimeTask = null;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static function archiveTimeTask(timeTask: TimeTask, frameEnd: Float) {
							 | 
						||
| 
								 | 
							
										#if sys_server
							 | 
						||
| 
								 | 
							
										if (timeTask.next > frameEnd - timeWarpSaveTime) {
							 | 
						||
| 
								 | 
							
											outdatedTimeTasks.push(timeTask);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										#end
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
									 * An approximation of the amount of time (in fractional seconds) that elapsed while the game was active.
							 | 
						||
| 
								 | 
							
									 * This value is optimized for achieving smooth framerates.
							 | 
						||
| 
								 | 
							
									 */
							 | 
						||
| 
								 | 
							
									public static function time(): Float {
							 | 
						||
| 
								 | 
							
										return current;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									/**
							 | 
						||
| 
								 | 
							
									 * The amount of time (in fractional seconds) that elapsed since the game started.
							 | 
						||
| 
								 | 
							
									 */
							 | 
						||
| 
								 | 
							
									public static function realTime(): Float {
							 | 
						||
| 
								 | 
							
										return System.time;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function resetTime(): Void {
							 | 
						||
| 
								 | 
							
										var now = System.time;
							 | 
						||
| 
								 | 
							
										var dif = now - startTime;
							 | 
						||
| 
								 | 
							
										startTime = now;
							 | 
						||
| 
								 | 
							
										for (timeTask in timeTasks) {
							 | 
						||
| 
								 | 
							
											timeTask.start -= dif;
							 | 
						||
| 
								 | 
							
											timeTask.next -= dif;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										for (i in 0...DIF_COUNT)
							 | 
						||
| 
								 | 
							
											deltas[i] = 0;
							 | 
						||
| 
								 | 
							
										current = 0;
							 | 
						||
| 
								 | 
							
										lastTime = 0;
							 | 
						||
| 
								 | 
							
										lastFrameEnd = 0;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function addBreakableFrameTask(task: Void->Bool, priority: Int): Int {
							 | 
						||
| 
								 | 
							
										frameTasks.push(new FrameTask(task, priority, ++currentFrameTaskId));
							 | 
						||
| 
								 | 
							
										frame_tasks_sorted = false;
							 | 
						||
| 
								 | 
							
										return currentFrameTaskId;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function addFrameTask(task: Void->Void, priority: Int): Int {
							 | 
						||
| 
								 | 
							
										return addBreakableFrameTask(function() {
							 | 
						||
| 
								 | 
							
											task();
							 | 
						||
| 
								 | 
							
											return true;
							 | 
						||
| 
								 | 
							
										}, priority);
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function pauseFrameTask(id: Int, paused: Bool): Void {
							 | 
						||
| 
								 | 
							
										for (frameTask in frameTasks) {
							 | 
						||
| 
								 | 
							
											if (frameTask.id == id) {
							 | 
						||
| 
								 | 
							
												frameTask.paused = paused;
							 | 
						||
| 
								 | 
							
												break;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function removeFrameTask(id: Int): Void {
							 | 
						||
| 
								 | 
							
										for (frameTask in frameTasks) {
							 | 
						||
| 
								 | 
							
											if (frameTask.id == id) {
							 | 
						||
| 
								 | 
							
												frameTask.active = false;
							 | 
						||
| 
								 | 
							
												break;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function generateGroupId(): Int {
							 | 
						||
| 
								 | 
							
										return ++currentGroupId;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function addBreakableTimeTaskToGroup(groupId: Int, task: Void->Bool, start: Float, period: Float = 0, duration: Float = 0): Int {
							 | 
						||
| 
								 | 
							
										var t = new TimeTask();
							 | 
						||
| 
								 | 
							
										t.active = true;
							 | 
						||
| 
								 | 
							
										t.task = task;
							 | 
						||
| 
								 | 
							
										t.id = ++currentTimeTaskId;
							 | 
						||
| 
								 | 
							
										t.groupId = groupId;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										t.start = current + start;
							 | 
						||
| 
								 | 
							
										t.period = 0;
							 | 
						||
| 
								 | 
							
										if (period != 0)
							 | 
						||
| 
								 | 
							
											t.period = period;
							 | 
						||
| 
								 | 
							
										t.duration = 0; // infinite
							 | 
						||
| 
								 | 
							
										if (duration != 0)
							 | 
						||
| 
								 | 
							
											t.duration = t.start + duration;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										t.next = t.start;
							 | 
						||
| 
								 | 
							
										insertSorted(timeTasks, t);
							 | 
						||
| 
								 | 
							
										return t.id;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function addTimeTaskToGroup(groupId: Int, task: Void->Void, start: Float, period: Float = 0, duration: Float = 0): Int {
							 | 
						||
| 
								 | 
							
										return addBreakableTimeTaskToGroup(groupId, function() {
							 | 
						||
| 
								 | 
							
											task();
							 | 
						||
| 
								 | 
							
											return true;
							 | 
						||
| 
								 | 
							
										}, start, period, duration);
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function addBreakableTimeTask(task: Void->Bool, start: Float, period: Float = 0, duration: Float = 0): Int {
							 | 
						||
| 
								 | 
							
										return addBreakableTimeTaskToGroup(0, task, start, period, duration);
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function addTimeTask(task: Void->Void, start: Float, period: Float = 0, duration: Float = 0): Int {
							 | 
						||
| 
								 | 
							
										return addTimeTaskToGroup(0, task, start, period, duration);
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static function getTimeTask(id: Int): TimeTask {
							 | 
						||
| 
								 | 
							
										if (activeTimeTask != null && activeTimeTask.id == id)
							 | 
						||
| 
								 | 
							
											return activeTimeTask;
							 | 
						||
| 
								 | 
							
										for (timeTask in timeTasks) {
							 | 
						||
| 
								 | 
							
											if (timeTask.id == id) {
							 | 
						||
| 
								 | 
							
												return timeTask;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										for (timeTask in pausedTimeTasks) {
							 | 
						||
| 
								 | 
							
											if (timeTask.id == id) {
							 | 
						||
| 
								 | 
							
												return timeTask;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										return null;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function pauseTimeTask(id: Int, paused: Bool): Void {
							 | 
						||
| 
								 | 
							
										var timeTask = getTimeTask(id);
							 | 
						||
| 
								 | 
							
										if (timeTask != null) {
							 | 
						||
| 
								 | 
							
											pauseRunningTimeTask(timeTask, paused);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										if (activeTimeTask != null && activeTimeTask.id == id) {
							 | 
						||
| 
								 | 
							
											activeTimeTask.paused = paused;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static function pauseRunningTimeTask(timeTask: TimeTask, paused: Bool): Void {
							 | 
						||
| 
								 | 
							
										timeTask.paused = paused;
							 | 
						||
| 
								 | 
							
										if (paused) {
							 | 
						||
| 
								 | 
							
											timeTasks.remove(timeTask);
							 | 
						||
| 
								 | 
							
											pausedTimeTasks.push(timeTask);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										else {
							 | 
						||
| 
								 | 
							
											insertSorted(timeTasks, timeTask);
							 | 
						||
| 
								 | 
							
											pausedTimeTasks.remove(timeTask);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function pauseTimeTasks(groupId: Int, paused: Bool): Void {
							 | 
						||
| 
								 | 
							
										if (paused) {
							 | 
						||
| 
								 | 
							
											for (timeTask in timeTasks) {
							 | 
						||
| 
								 | 
							
												if (timeTask.groupId == groupId) {
							 | 
						||
| 
								 | 
							
													pauseRunningTimeTask(timeTask, paused);
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										else {
							 | 
						||
| 
								 | 
							
											for (timeTask in pausedTimeTasks) {
							 | 
						||
| 
								 | 
							
												if (timeTask.groupId == groupId) {
							 | 
						||
| 
								 | 
							
													pauseRunningTimeTask(timeTask, paused);
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										if (activeTimeTask != null && activeTimeTask.groupId == groupId) {
							 | 
						||
| 
								 | 
							
											activeTimeTask.paused = paused;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function removeTimeTask(id: Int): Void {
							 | 
						||
| 
								 | 
							
										var timeTask = getTimeTask(id);
							 | 
						||
| 
								 | 
							
										if (timeTask != null) {
							 | 
						||
| 
								 | 
							
											timeTask.active = false;
							 | 
						||
| 
								 | 
							
											timeTasks.remove(timeTask);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function removeTimeTasks(groupId: Int): Void {
							 | 
						||
| 
								 | 
							
										for (timeTask in timeTasks) {
							 | 
						||
| 
								 | 
							
											if (timeTask.groupId == groupId) {
							 | 
						||
| 
								 | 
							
												timeTask.active = false;
							 | 
						||
| 
								 | 
							
												timeTasksScratchpad.push(timeTask);
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										for (timeTask in timeTasksScratchpad) {
							 | 
						||
| 
								 | 
							
											timeTasks.remove(timeTask);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										while (timeTasksScratchpad.length > 0) {
							 | 
						||
| 
								 | 
							
											timeTasksScratchpad.remove(timeTasksScratchpad[0]);
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										if (activeTimeTask != null && activeTimeTask.groupId == groupId) {
							 | 
						||
| 
								 | 
							
											activeTimeTask.active = false;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									public static function numTasksInSchedule(): Int {
							 | 
						||
| 
								 | 
							
										return timeTasks.length + frameTasks.length;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static function insertSorted(list: Array<TimeTask>, task: TimeTask) {
							 | 
						||
| 
								 | 
							
										for (i in 0...list.length) {
							 | 
						||
| 
								 | 
							
											if (list[i].next > task.next) {
							 | 
						||
| 
								 | 
							
												list.insert(i, task);
							 | 
						||
| 
								 | 
							
												return;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										list.push(task);
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									static function sortFrameTasks(): Void {
							 | 
						||
| 
								 | 
							
										if (frame_tasks_sorted)
							 | 
						||
| 
								 | 
							
											return;
							 | 
						||
| 
								 | 
							
										frameTasks.sort(function(a: FrameTask, b: FrameTask): Int {
							 | 
						||
| 
								 | 
							
											return a.priority > b.priority ? 1 : ((a.priority < b.priority) ? -1 : 0);
							 | 
						||
| 
								 | 
							
										});
							 | 
						||
| 
								 | 
							
										frame_tasks_sorted = true;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 |