From f00cef2e211b279960c87dfba0fc2a1a935c16e5 Mon Sep 17 00:00:00 2001 From: LeenkxTeam Date: Tue, 13 May 2025 19:40:30 +0000 Subject: [PATCH] Update Kha/Sources/kha/Scheduler.hx --- Kha/Sources/kha/Scheduler.hx | 1108 +++++++++++++++++----------------- 1 file changed, 554 insertions(+), 554 deletions(-) diff --git a/Kha/Sources/kha/Scheduler.hx b/Kha/Sources/kha/Scheduler.hx index a5e0470..7669b66 100644 --- a/Kha/Sources/kha/Scheduler.hx +++ b/Kha/Sources/kha/Scheduler.hx @@ -1,554 +1,554 @@ -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; - static var pausedTimeTasks: Array; - static var outdatedTimeTasks: Array; - static var timeTasksScratchpad: Array; - static inline var timeWarpSaveTime: Float = 10.0; - - static var frameTasks: Array; - static var toDeleteFrame: Array; - - 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; - - static var startTime: Float = 0; - - static var activeTimeTask: TimeTask = null; - - public static function init(): Void { - deltas = new Array(); - 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): 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, 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; - } -} +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; + static var pausedTimeTasks: Array; + static var outdatedTimeTasks: Array; + static var timeTasksScratchpad: Array; + static inline var timeWarpSaveTime: Float = 10.0; + + static var frameTasks: Array; + static var toDeleteFrame: Array; + + 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; + + static var startTime: Float = 0; + + static var activeTimeTask: TimeTask = null; + + public static function init(): Void { + deltas = new Array(); + 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 || kha_debug_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): 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, 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; + } +}