forked from LeenkxTeam/LNXSDK
Update Files
This commit is contained in:
554
Kha/Sources/kha/Scheduler.hx
Normal file
554
Kha/Sources/kha/Scheduler.hx
Normal file
@ -0,0 +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<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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user