2025-01-22 16:18:30 +01:00

212 lines
4.3 KiB
Haxe

package haxe;
import haxe.EntryPoint;
#if (target.threaded && !cppia)
import sys.thread.EventLoop;
import sys.thread.Thread;
#end
class MainEvent {
var f:Void->Void;
var prev:MainEvent;
var next:MainEvent;
/**
Tells if the event can lock the process from exiting (default:true)
**/
public var isBlocking:Bool = true;
public var nextRun(default, null):Float;
public var priority(default, null):Int;
function new(f, p) {
this.f = f;
this.priority = p;
nextRun = Math.NEGATIVE_INFINITY;
}
/**
Delay the execution of the event for the given time, in seconds.
If t is null, the event will be run at tick() time.
**/
public function delay(t:Null<Float>) {
nextRun = t == null ? Math.NEGATIVE_INFINITY : haxe.Timer.stamp() + t;
}
/**
Call the event. Will do nothing if the event has been stopped.
**/
public inline function call() {
if (f != null)
f();
}
/**
Stop the event from firing anymore.
**/
public function stop() {
if (f == null)
return;
f = null;
nextRun = Math.NEGATIVE_INFINITY;
if (prev == null)
@:privateAccess MainLoop.pending = next;
else
prev.next = next;
if (next != null)
next.prev = prev;
}
}
@:access(haxe.MainEvent)
class MainLoop {
#if (target.threaded && !cppia)
static var eventLoopHandler:Null<EventHandler>;
static var mutex = new sys.thread.Mutex();
static var mainThread = Thread.current();
#end
static var pending:MainEvent;
public static var threadCount(get, never):Int;
inline static function get_threadCount()
return EntryPoint.threadCount;
public static function hasEvents() {
var p = pending;
while (p != null) {
if (p.isBlocking)
return true;
p = p.next;
}
return false;
}
public static function addThread(f:Void->Void) {
EntryPoint.addThread(f);
}
public static function runInMainThread(f:Void->Void) {
EntryPoint.runInMainThread(f);
}
/**
Add a pending event to be run into the main loop.
**/
public static function add(f:Void->Void, priority = 0):MainEvent@:privateAccess {
if (f == null)
throw "Event function is null";
var e = new MainEvent(f, priority);
var head = pending;
if (head != null)
head.prev = e;
e.next = head;
pending = e;
injectIntoEventLoop(0);
return e;
}
static function injectIntoEventLoop(waitMs:Int) {
#if (target.threaded && !cppia)
mutex.acquire();
if(eventLoopHandler != null)
mainThread.events.cancel(eventLoopHandler);
eventLoopHandler = mainThread.events.repeat(
() -> {
mainThread.events.cancel(eventLoopHandler);
var wait = tick();
if(hasEvents()) {
injectIntoEventLoop(Std.int(wait * 1000));
}
},
waitMs
);
mutex.release();
#end
}
static function sortEvents() {
// pending = haxe.ds.ListSort.sort(pending, function(e1, e2) return e1.nextRun > e2.nextRun ? -1 : 1);
// we can't use directly ListSort because it requires prev/next to be public, which we don't want here
// we do then a manual inline, this also allow use to do a Float comparison of nextRun
var list = pending;
if (list == null)
return;
var insize = 1, nmerges, psize = 0, qsize = 0;
var p, q, e, tail:MainEvent;
while (true) {
p = list;
list = null;
tail = null;
nmerges = 0;
while (p != null) {
nmerges++;
q = p;
psize = 0;
for (i in 0...insize) {
psize++;
q = q.next;
if (q == null)
break;
}
qsize = insize;
while (psize > 0 || (qsize > 0 && q != null)) {
if (psize == 0) {
e = q;
q = q.next;
qsize--;
} else if (qsize == 0
|| q == null
|| (p.priority > q.priority || (p.priority == q.priority && p.nextRun <= q.nextRun))) {
e = p;
p = p.next;
psize--;
} else {
e = q;
q = q.next;
qsize--;
}
if (tail != null)
tail.next = e;
else
list = e;
e.prev = tail;
tail = e;
}
p = q;
}
tail.next = null;
if (nmerges <= 1)
break;
insize *= 2;
}
list.prev = null; // not cycling
pending = list;
}
/**
Run the pending events. Return the time for next event.
**/
static function tick() {
sortEvents();
var e = pending;
var now = haxe.Timer.stamp();
var wait = 1e9;
while (e != null) {
var next = e.next;
var wt = e.nextRun - now;
if (wt <= 0) {
wait = 0;
e.call();
} else if (wait > wt)
wait = wt;
e = next;
}
return wait;
}
}