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) { 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; 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; } }