Explorar o código

Added EntryPoint/MainLoop support (close #3075) (#5017)

* Added EntryPoint/MainLoop support (close #3075)
* use MainLoop to implement haxe.Timer on platforms that don't have native support for it.
Nicolas Cannasse %!s(int64=9) %!d(string=hai) anos
pai
achega
77cd95b520

+ 7 - 0
.gitignore

@@ -79,3 +79,10 @@ tests/misc/projects/Issue3756/cpp/
 tests/misc/projects/Issue4070/cpp/
 
 /*.manifest
+/tests/misc/eventLoop/cpp
+/tests/misc/eventLoop/eventLoop.js
+/tests/misc/eventLoop/eventLoop.n
+/tests/misc/eventLoop/eventLoop.swf
+/tests/misc/eventLoop/dump
+/tests/misc/eventLoop/eventLoop.py
+/tests/misc/eventLoop/php

+ 1 - 1
src/optimization/dce.ml

@@ -572,7 +572,7 @@ let run com main full =
 		curclass = null_class;
 	} in
 	begin match main with
-		| Some {eexpr = TCall({eexpr = TField(e,(FStatic(c,cf)))},_)} ->
+		| Some {eexpr = TCall({eexpr = TField(e,(FStatic(c,cf)))},_)} | Some {eexpr = TBlock ({ eexpr = TCall({eexpr = TField(e,(FStatic(c,cf)))},_)} :: _)} ->
 			cf.cf_meta <- (Meta.Keep,[],cf.cf_pos) :: cf.cf_meta
 		| _ ->
 			()

+ 16 - 3
src/typing/typer.ml

@@ -4252,7 +4252,7 @@ and build_call ctx acc el (with_type:with_type) p =
 (* ---------------------------------------------------------------------- *)
 (* FINALIZATION *)
 
-let get_main ctx =
+let get_main ctx types =
 	match ctx.com.main_class with
 	| None -> None
 	| Some cl ->
@@ -4271,7 +4271,20 @@ let get_main ctx =
 				Not_found -> error ("Invalid -main : " ^ s_type_path cl ^ " does not have static function main") c.cl_pos
 		) in
 		let emain = type_type ctx cl null_pos in
-		Some (mk (TCall (mk (TField (emain,fmode)) ft null_pos,[])) r null_pos)
+		let main = mk (TCall (mk (TField (emain,fmode)) ft null_pos,[])) r null_pos in
+		(* add haxe.EntryPoint.run() call *)
+		let main = (try
+			let et = List.find (fun t -> t_path t = (["haxe"],"EntryPoint")) types in
+			let ec = (match et with TClassDecl c -> c | _ -> assert false) in
+			let ef = PMap.find "run" ec.cl_statics in
+			let p = null_pos in
+			let et = mk (TTypeExpr et) (TAnon { a_fields = PMap.empty; a_status = ref (Statics ec) }) p in
+			let call = mk (TCall (mk (TField (et,FStatic (ec,ef))) ef.cf_type p,[])) ctx.t.tvoid p in
+			mk (TBlock [main;call]) ctx.t.tvoid p
+		with Not_found ->
+			main
+		) in
+		Some main
 
 let finalize ctx =
 	flush_pass ctx PFinal "final";
@@ -4389,7 +4402,7 @@ let generate ctx =
 	in
 	let sorted_modules = List.sort (fun m1 m2 -> compare m1.m_path m2.m_path) (Hashtbl.fold (fun _ m acc -> m :: acc) ctx.g.modules []) in
 	List.iter (fun m -> List.iter loop m.m_types) sorted_modules;
-	get_main ctx, List.rev !types, sorted_modules
+	get_main ctx !types, List.rev !types, sorted_modules
 
 (* ---------------------------------------------------------------------- *)
 (* MACROS *)

+ 140 - 0
std/haxe/EntryPoint.hx

@@ -0,0 +1,140 @@
+package haxe;
+
+#if (neko && !macro)
+import neko.vm.Lock;
+import neko.vm.Mutex;
+import neko.vm.Thread;
+#elseif cpp
+import cpp.vm.Lock;
+import cpp.vm.Mutex;
+import cpp.vm.Thread;
+#elseif java
+import java.vm.Lock;
+import java.vm.Mutex;
+import java.vm.Thread;
+#elseif sys
+private class Lock {
+	public function new() {
+	}
+	public inline function release() {
+	}
+	public inline function wait( ?t : Float ) {
+	}
+}
+private class Mutex {
+	public function new() {
+	}
+	public inline function acquire() {
+	}
+	public inline function release() {
+	}
+}
+private class Thread {
+	public static function create( f : Void -> Void ) {
+		f();
+	}
+}
+#end
+
+/**
+	If haxe.MainLoop is kept from DCE, then we will insert an haxe.EntryPoint.run() call just at then end of main().
+	This class can be redefined by custom frameworks so they can handle their own main loop logic.
+**/
+class EntryPoint {
+
+	#if sys
+	static var sleepLock = new Lock();
+	static var mutex = new Mutex();
+	#end
+	static var pending = new Array<Void->Void>();
+
+	public static var threadCount(default,null) : Int = 0;
+
+	/**
+		Wakeup a sleeping run()
+	**/
+	public static function wakeup() {
+		#if sys
+		sleepLock.release();
+		#end
+	}
+
+	public static function runInMainThread( f : Void -> Void ) {
+		#if sys
+		mutex.acquire();
+		pending.push(f);
+		mutex.release();
+		wakeup();
+		#else
+		pending.push(f);
+		#end
+	}
+
+	public static function addThread( f : Void -> Void ) {
+		#if sys
+		mutex.acquire();
+		threadCount++;
+		mutex.release();
+		Thread.create(function() {
+			f();
+			mutex.acquire();
+			threadCount--;
+			if( threadCount == 0 ) wakeup();
+			mutex.release();
+		});
+		#else
+		threadCount++;
+		pending.push(function() { f(); threadCount--; } );
+		#end
+	}
+
+	static function processEvents() : Float {
+		// flush all pending calls
+		while( true ) {
+			#if sys
+			mutex.acquire();
+			var f = pending.shift();
+			mutex.release();
+			#else
+			var f = pending.shift();
+			#end
+			if( f == null ) break;
+			f();
+		}
+		if( !MainLoop.hasEvents() && threadCount == 0 )
+			return -1;
+		return @:privateAccess MainLoop.tick();
+	}
+
+	/**
+		Start the main loop. Depending on the platform, this can return immediately or will only return when the application exits.
+	**/
+	@:keep public static function run() @:privateAccess {
+		#if js
+
+		processEvents();
+		var window : Dynamic = js.Browser.window;
+		var rqf : Dynamic = window.requestAnimationFrame ||
+			window.webkitRequestAnimationFrame ||
+			window.mozRequestAnimationFrame;
+		rqf(run);
+
+		#elseif flash
+
+		flash.Lib.current.stage.addEventListener(flash.events.Event.ENTER_FRAME, function(_) processEvents());
+
+		#elseif sys
+		while( true ) {
+			var nextTick = processEvents();
+			if( nextTick < 0 )
+				break;
+			if( nextTick > 0 )
+				sleepLock.wait(nextTick); // wait until nextTick or wakeup() call
+		}
+		#else
+
+		// no implementation available, let's exit immediately
+
+		#end
+	}
+}

+ 164 - 0
std/haxe/MainLoop.hx

@@ -0,0 +1,164 @@
+package haxe;
+import haxe.EntryPoint;
+
+class MainEvent {
+
+	var f : Void -> Void;
+	var prev : MainEvent;
+	var next : MainEvent;
+	public var nextRun(default,null) : Float;
+	public var priority(default,null) : Int;
+
+	function new(f, p) {
+		this.f = f;
+		this.priority = p;
+		nextRun = -1;
+	}
+
+	/**
+		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 ? -1 : haxe.Timer.stamp() + t;
+	}
+
+	/**
+		Call the event. Will do nothing is 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 = -1;
+		if( prev == null )
+			@:privateAccess MainLoop.pending = next;
+		else
+			prev.next = next;
+		if( next != null )
+			next.prev = prev;
+	}
+
+}
+
+@:access(haxe.MainEvent)
+class MainLoop {
+
+	static var pending : MainEvent = null;
+
+	public static var threadCount(get, never) : Int;
+
+	inline static function get_threadCount() return EntryPoint.threadCount;
+
+	public static function hasEvents() {
+		return pending != null;
+	}
+
+	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;
+		return e;
+	}
+
+	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( e.nextRun < 0 || wt <= 0 ) {
+				wait = 0;
+				e.call();
+			} else if( wait > wt )
+				wait = wt;
+			e = next;
+		}
+		return wait;
+	}
+
+}

+ 14 - 3
std/haxe/Timer.hx

@@ -36,13 +36,14 @@ package haxe;
 	the child class.
 **/
 class Timer {
-	#if (flash || js || java || python)
 
 	#if (flash || js)
 		private var id : Null<Int>;
 	#elseif java
 		private var timer : java.util.Timer;
 		private var task : java.util.TimerTask;
+	#else
+		private var event : MainLoop.MainEvent;
 	#end
 
 	/**
@@ -66,6 +67,13 @@ class Timer {
 		#elseif java
 			timer = new java.util.Timer();
 			timer.scheduleAtFixedRate(task = new TimerTask(this), haxe.Int64.ofInt(time_ms), haxe.Int64.ofInt(time_ms));
+		#else
+			var dt = time_ms / 1000;
+			event = MainLoop.add(function() {
+				@:privateAccess event.nextRun += dt;
+				run();
+			});
+			event.delay(dt);
 		#end
 	}
 
@@ -93,6 +101,11 @@ class Timer {
 				timer = null;
 			}
 			task = null;
+		#else
+			if( event != null ) {
+				event.stop();
+				event = null;
+			}
 		#end
 	}
 
@@ -129,8 +142,6 @@ class Timer {
 		return t;
 	}
 
-	#end
-
 	/**
 		Measures the time it takes to execute `f`, in seconds with fractions.
 

+ 57 - 0
tests/misc/eventLoop/Main.hx

@@ -0,0 +1,57 @@
+class Main {
+
+	static function main() {
+		var event : haxe.MainLoop.MainEvent = null;
+		var count = 0;
+		event = haxe.MainLoop.add(function() {
+			trace(count++);
+			if( count == 10 ) {
+				event.stop();
+				trace(haxe.MainLoop.hasEvents());
+			}
+		});
+
+		#if sys
+		// if we don't use native timer, this should execute before
+		var t = new haxe.Timer(100);
+		t.run = function() {
+			trace("BEFORE1");
+			t.stop();
+		};
+		#end
+
+		var t = new haxe.Timer(200);
+		var count = 0;
+		t.run = function() {
+			trace("T" + count++);
+			if( count == 5 )
+				t.stop();
+		};
+
+		#if sys
+		// if we don't use native timer, this should execute before
+		var t = new haxe.Timer(100);
+		t.run = function() {
+			trace("BEFORE2");
+			t.stop();
+		};
+		#end
+
+		#if sys
+		Sys.sleep(0.3);
+		#end
+
+		haxe.MainLoop.addThread(function() {
+			var event : haxe.MainLoop.MainEvent = null;
+			var count = 0;
+			event = haxe.MainLoop.add(function() {
+				trace(String.fromCharCode("A".code + count++));
+				if( count == 5 ) event.stop();
+			});
+			#if sys
+			Sys.sleep(3);
+			#end
+		});
+	}
+
+}

+ 19 - 0
tests/misc/eventLoop/all.hxml

@@ -0,0 +1,19 @@
+-main Main
+-dce full
+--each
+-swf eventLoop.swf
+-swf-version 14
+--next
+-neko eventLoop.n
+--next
+-js eventLoop.js
+--next
+-python eventLoop.py
+--next
+-php php
+--next
+-java java
+--next
+-cs cs
+--next
+-cpp cpp

+ 57 - 0
tests/misc/eventLoop/eventLoop.hxproj

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<project version="2">
+  <!-- Output SWF options -->
+  <output>
+    <movie outputType="CustomBuild" />
+    <movie input="" />
+    <movie path="eventLoop.swf" />
+    <movie fps="30" />
+    <movie width="800" />
+    <movie height="600" />
+    <movie version="14" />
+    <movie minorVersion="0" />
+    <movie platform="Flash Player" />
+    <movie background="#FFFFFF" />
+  </output>
+  <!-- Other classes to be compiled into your SWF -->
+  <classpaths>
+    <!-- example: <class path="..." /> -->
+  </classpaths>
+  <!-- Build options -->
+  <build>
+    <option directives="" />
+    <option flashStrict="False" />
+    <option noInlineOnDebug="False" />
+    <option mainClass="Main" />
+    <option enabledebug="False" />
+    <option additional="" />
+  </build>
+  <!-- haxelib libraries -->
+  <haxelib>
+    <!-- example: <library name="..." /> -->
+  </haxelib>
+  <!-- Class files to compile (other referenced classes will automatically be included) -->
+  <compileTargets>
+    <!-- example: <compile path="..." /> -->
+  </compileTargets>
+  <!-- Assets to embed into the output SWF -->
+  <library>
+    <!-- example: <asset path="..." id="..." update="..." glyphs="..." mode="..." place="..." sharepoint="..." /> -->
+  </library>
+  <!-- Paths to exclude from the Project Explorer tree -->
+  <hiddenPaths>
+    <hidden path="obj" />
+  </hiddenPaths>
+  <!-- Executed before build -->
+  <preBuildCommand>haxe all.hxml</preBuildCommand>
+  <!-- Executed after build -->
+  <postBuildCommand alwaysRun="False" />
+  <!-- Other project options -->
+  <options>
+    <option showHiddenPaths="False" />
+    <option testMovie="Default" />
+    <option testMovieCommand="" />
+  </options>
+  <!-- Plugin storage -->
+  <storage />
+</project>

+ 1 - 0
tests/misc/eventLoop/test.html

@@ -0,0 +1 @@
+<script src="eventLoop.js"></script>