275 lines
7.1 KiB
Haxe
275 lines
7.1 KiB
Haxe
|
package sys.thread;
|
||
|
|
||
|
/**
|
||
|
When an event loop has an available event to execute.
|
||
|
**/
|
||
|
enum NextEventTime {
|
||
|
/** There's already an event waiting to be executed */
|
||
|
Now;
|
||
|
/** No new events are expected. */
|
||
|
Never;
|
||
|
/**
|
||
|
An event is expected to arrive at any time.
|
||
|
If `time` is specified, then the event will be ready at that time for sure.
|
||
|
*/
|
||
|
AnyTime(time:Null<Float>);
|
||
|
/** An event is expected to be ready for execution at `time`. */
|
||
|
At(time:Float);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
An event loop implementation used for `sys.thread.Thread`
|
||
|
**/
|
||
|
@:coreApi
|
||
|
class EventLoop {
|
||
|
final mutex = new Mutex();
|
||
|
final oneTimeEvents = new Array<Null<()->Void>>();
|
||
|
var oneTimeEventsIdx = 0;
|
||
|
final waitLock = new Lock();
|
||
|
var promisedEventsCount = 0;
|
||
|
var regularEvents:Null<RegularEvent>;
|
||
|
|
||
|
public function new():Void {}
|
||
|
|
||
|
/**
|
||
|
Schedule event for execution every `intervalMs` milliseconds in current loop.
|
||
|
**/
|
||
|
public function repeat(event:()->Void, intervalMs:Int):EventHandler {
|
||
|
mutex.acquire();
|
||
|
var interval = 0.001 * intervalMs;
|
||
|
var event = new RegularEvent(event, Sys.time() + interval, interval);
|
||
|
inline insertEventByTime(event);
|
||
|
waitLock.release();
|
||
|
mutex.release();
|
||
|
return event;
|
||
|
}
|
||
|
|
||
|
function insertEventByTime(event:RegularEvent):Void {
|
||
|
switch regularEvents {
|
||
|
case null:
|
||
|
regularEvents = event;
|
||
|
case current:
|
||
|
var previous = null;
|
||
|
while(true) {
|
||
|
if(current == null) {
|
||
|
previous.next = event;
|
||
|
event.previous = previous;
|
||
|
break;
|
||
|
} else if(event.nextRunTime < current.nextRunTime) {
|
||
|
event.next = current;
|
||
|
current.previous = event;
|
||
|
switch previous {
|
||
|
case null:
|
||
|
regularEvents = event;
|
||
|
case _:
|
||
|
event.previous = previous;
|
||
|
previous.next = event;
|
||
|
current.previous = event;
|
||
|
}
|
||
|
break;
|
||
|
} else {
|
||
|
previous = current;
|
||
|
current = current.next;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Prevent execution of a previously scheduled event in current loop.
|
||
|
**/
|
||
|
public function cancel(eventHandler:EventHandler):Void {
|
||
|
mutex.acquire();
|
||
|
var event:RegularEvent = eventHandler;
|
||
|
event.cancelled = true;
|
||
|
if(regularEvents == event) {
|
||
|
regularEvents = event.next;
|
||
|
}
|
||
|
switch event.next {
|
||
|
case null:
|
||
|
case e: e.previous = event.previous;
|
||
|
}
|
||
|
switch event.previous {
|
||
|
case null:
|
||
|
case e: e.next = event.next;
|
||
|
}
|
||
|
mutex.release();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Notify this loop about an upcoming event.
|
||
|
This makes the thread to stay alive and wait for as many events as many times
|
||
|
`.promise()` was called. These events should be added via `.runPromised()`
|
||
|
**/
|
||
|
public function promise():Void {
|
||
|
mutex.acquire();
|
||
|
++promisedEventsCount;
|
||
|
mutex.release();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Execute `event` as soon as possible.
|
||
|
**/
|
||
|
public function run(event:()->Void):Void {
|
||
|
mutex.acquire();
|
||
|
oneTimeEvents[oneTimeEventsIdx++] = event;
|
||
|
waitLock.release();
|
||
|
mutex.release();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Add previously promised `event` for execution.
|
||
|
**/
|
||
|
public function runPromised(event:()->Void):Void {
|
||
|
mutex.acquire();
|
||
|
oneTimeEvents[oneTimeEventsIdx++] = event;
|
||
|
--promisedEventsCount;
|
||
|
waitLock.release();
|
||
|
mutex.release();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Executes all pending events.
|
||
|
|
||
|
The returned time stamps can be used with `Sys.time()` for calculations.
|
||
|
|
||
|
Depending on a target platform this method may be non-reentrant. It must
|
||
|
not be called from event callbacks.
|
||
|
**/
|
||
|
public function progress():NextEventTime {
|
||
|
return switch __progress(Sys.time(), [], []) {
|
||
|
case {nextEventAt:-2}: Now;
|
||
|
case {nextEventAt:-1, anyTime:false}: Never;
|
||
|
case {nextEventAt:-1, anyTime:true}: AnyTime(null);
|
||
|
case {nextEventAt:time, anyTime:true}: AnyTime(time);
|
||
|
case {nextEventAt:time, anyTime:false}: At(time);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Blocks until a new event is added or `timeout` (in seconds) to expires.
|
||
|
|
||
|
Depending on a target platform this method may also automatically execute arriving
|
||
|
events while waiting. However if any event is executed it will stop waiting.
|
||
|
|
||
|
Returns `true` if more events are expected.
|
||
|
Returns `false` if no more events expected.
|
||
|
|
||
|
Depending on a target platform this method may be non-reentrant. It must
|
||
|
not be called from event callbacks.
|
||
|
**/
|
||
|
public function wait(?timeout:Float):Bool {
|
||
|
return waitLock.wait(timeout);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Execute all pending events.
|
||
|
Wait and execute as many events as many times `promiseEvent()` was called.
|
||
|
Runs until all repeating events are cancelled and no more events is expected.
|
||
|
|
||
|
Depending on a target platform this method may be non-reentrant. It must
|
||
|
not be called from event callbacks.
|
||
|
**/
|
||
|
public function loop():Void {
|
||
|
var recycleRegular = [];
|
||
|
var recycleOneTimers = [];
|
||
|
while(true) {
|
||
|
var r = __progress(Sys.time(), recycleRegular, recycleOneTimers);
|
||
|
switch r {
|
||
|
case {nextEventAt:-2}:
|
||
|
case {nextEventAt:-1, anyTime:false}:
|
||
|
break;
|
||
|
case {nextEventAt:-1, anyTime:true}:
|
||
|
waitLock.wait();
|
||
|
case {nextEventAt:time}:
|
||
|
var timeout = time - Sys.time();
|
||
|
waitLock.wait(Math.max(0, timeout));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
`.progress` implementation with a reusable array for internal usage.
|
||
|
The `nextEventAt` field of the return value denotes when the next event
|
||
|
is expected to run:
|
||
|
* -1 - never
|
||
|
* -2 - now
|
||
|
* other values - at specified time
|
||
|
**/
|
||
|
inline function __progress(now:Float, recycleRegular:Array<RegularEvent>, recycleOneTimers:Array<()->Void>):{nextEventAt:Float, anyTime:Bool} {
|
||
|
var regularsToRun = recycleRegular;
|
||
|
var eventsToRunIdx = 0;
|
||
|
// When the next event is expected to run
|
||
|
var nextEventAt:Float = -1;
|
||
|
|
||
|
mutex.acquire();
|
||
|
//reset waitLock
|
||
|
while(waitLock.wait(0.0)) {}
|
||
|
// Collect regular events to run
|
||
|
var current = regularEvents;
|
||
|
while(current != null) {
|
||
|
if(current.nextRunTime <= now) {
|
||
|
regularsToRun[eventsToRunIdx++] = current;
|
||
|
current.nextRunTime += current.interval;
|
||
|
nextEventAt = -2;
|
||
|
} else if(nextEventAt == -1 || current.nextRunTime < nextEventAt) {
|
||
|
nextEventAt = current.nextRunTime;
|
||
|
}
|
||
|
current = current.next;
|
||
|
}
|
||
|
mutex.release();
|
||
|
|
||
|
// Run regular events
|
||
|
for(i in 0...eventsToRunIdx) {
|
||
|
if(!regularsToRun[i].cancelled)
|
||
|
regularsToRun[i].run();
|
||
|
regularsToRun[i] = null;
|
||
|
}
|
||
|
eventsToRunIdx = 0;
|
||
|
|
||
|
var oneTimersToRun = recycleOneTimers;
|
||
|
// Collect pending one-time events
|
||
|
mutex.acquire();
|
||
|
for(i => event in oneTimeEvents) {
|
||
|
switch event {
|
||
|
case null:
|
||
|
break;
|
||
|
case _:
|
||
|
oneTimersToRun[eventsToRunIdx++] = event;
|
||
|
oneTimeEvents[i] = null;
|
||
|
}
|
||
|
}
|
||
|
oneTimeEventsIdx = 0;
|
||
|
var hasPromisedEvents = promisedEventsCount > 0;
|
||
|
mutex.release();
|
||
|
|
||
|
//run events
|
||
|
for(i in 0...eventsToRunIdx) {
|
||
|
oneTimersToRun[i]();
|
||
|
oneTimersToRun[i] = null;
|
||
|
}
|
||
|
|
||
|
// Some events were executed. They could add new events to run.
|
||
|
if(eventsToRunIdx > 0) {
|
||
|
nextEventAt = -2;
|
||
|
}
|
||
|
return {nextEventAt:nextEventAt, anyTime:hasPromisedEvents}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
abstract EventHandler(RegularEvent) from RegularEvent to RegularEvent {}
|
||
|
|
||
|
private class RegularEvent {
|
||
|
public var nextRunTime:Float;
|
||
|
public final interval:Float;
|
||
|
public final run:()->Void;
|
||
|
public var next:Null<RegularEvent>;
|
||
|
public var previous:Null<RegularEvent>;
|
||
|
public var cancelled:Bool = false;
|
||
|
|
||
|
public function new(run:()->Void, nextRunTime:Float, interval:Float) {
|
||
|
this.run = run;
|
||
|
this.nextRunTime = nextRunTime;
|
||
|
this.interval = interval;
|
||
|
}
|
||
|
}
|