forked from LeenkxTeam/LNXSDK
		
	
		
			
	
	
		
			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;
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 |