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