| 
									
										
										
										
											2025-05-13 19:40:30 +00:00
										 |  |  | 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 || kha_debug_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; | 
					
						
							| 
									
										
										
										
											2025-05-13 19:41:30 +00:00
										 |  |  | 		#if !(kha_html5 || kha_debug_html5) | 
					
						
							| 
									
										
										
										
											2025-05-13 19:40:30 +00:00
										 |  |  | 		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; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |