This document explains in detail how JSBinding supports Unity Coroutine in JavaScript.
First, I suggest you read this page to understand coroutine scheduling:
http://wiki.unity3d.com/index.php/CoroutineScheduler
and also Yield instructions in Mozilla SpiderMonkey engine:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield
OK, let's get started. Open this scene:
Assets/JSBinding/Samples/Coroutine/TestCoroutine.unity
This is source code of TestCoroutine.cs:
1 [JsType(JsMode.Clr,"../../../StreamingAssets/JavaScript/SharpKitGenerated/JSBinding/Samples/Coroutine/TestCoroutine.javascript")] 2 public class TestCoroutine : MonoBehaviour { 3 4 // Use this for initialization 5 void Start () 6 { 7 StartCoroutine(DoTest()); 8 } 9 10 // Update is called once per frame 11 void Update () 12 { 13 14 } 15 void LateUpdate() 16 { 17 jsimp.Coroutine.UpdateMonoBehaviourCoroutine(this); 18 } 19 IEnumerator WaitForCangJingKong() 20 { 21 yield return new WaitForSeconds(2f); 22 } 23 IEnumerator DoTest() 24 { 25 // test null 26 Debug.Log(1); 27 yield return null; 28 29 // test WaitForSeconds 30 Debug.Log(2); 31 yield return new WaitForSeconds(1f); 32 33 // test WWW 34 WWW www = new WWW("file://" + Application.dataPath + "/JSBinding/Samples/Coroutine/CoroutineReadme.txt"); 35 yield return www; 36 Debug.Log("Text from WWW: " + www.text); 37 38 // test another coroutine 39 yield return StartCoroutine(WaitForCangJingKong()); 40 Debug.Log("Wait for CangJingKong finished!"); 41 } 42 }
This is JavaScript code compiled by SharpKit:
1 if (typeof(JsTypes) == "undefined") 2 var JsTypes = []; 3 var TestCoroutine = { 4 fullname: "TestCoroutine", 5 baseTypeName: "UnityEngine.MonoBehaviour", 6 assemblyName: "SharpKitProj", 7 Kind: "Class", 8 definition: { 9 ctor: function (){ 10 UnityEngine.MonoBehaviour.ctor.call(this); 11 }, 12 Start: function (){ 13 this.StartCoroutine$$IEnumerator(this.DoTest()); 14 }, 15 Update: function (){ 16 }, 17 LateUpdate: function (){ 18 jsimp.Coroutine.UpdateMonoBehaviourCoroutine(this); 19 }, 20 WaitForCangJingKong: function (){ 21 var $yield = []; 22 $yield.push(new UnityEngine.WaitForSeconds.ctor(2)); 23 return $yield; 24 }, 25 DoTest: function (){ 26 var $yield = []; 27 UnityEngine.Debug.Log$$Object(1); 28 $yield.push(null); 29 UnityEngine.Debug.Log$$Object(2); 30 $yield.push(new UnityEngine.WaitForSeconds.ctor(1)); 31 var www = new UnityEngine.WWW.ctor$$String("file://" + UnityEngine.Application.get_dataPath() + "/JSBinding/Samples/Coroutine/CoroutineReadme.txt"); 32 $yield.push(www); 33 UnityEngine.Debug.Log$$Object("Text from WWW: " + www.get_text()); 34 $yield.push(this.StartCoroutine$$IEnumerator(this.WaitForCangJingKong())); 35 UnityEngine.Debug.Log$$Object("Wait for CangJingKong finished!"); 36 return $yield; 37 } 38 } 39 }; 40 JsTypes.push(TestCoroutine);
See "DoTest" and "WaitForCangJingKong" method, they are coroutine methods. SharpKit translates C# yield to a $yield array, and every yield instruction is pushed into that array.
BUT, this is not JavaScript yield syntax. So, I made a menu to correct this: JSB | Correct JavaScript Yield code
After excuting this menu, the 2 methods look like:
1 WaitForCangJingKong: function* (){ 2 yield (new UnityEngine.WaitForSeconds.ctor(2)); 3 }, 4 5 DoTest: function* () { 6 UnityEngine.Debug.Log$$Object(1); 7 yield (null); 8 UnityEngine.Debug.Log$$Object(2); 9 yield (new UnityEngine.WaitForSeconds.ctor(1)); 10 var www = new UnityEngine.WWW.ctor$$String("file://" + UnityEngine.Application.get_dataPath() + "/JSBinding/Samples/Coroutine/CoroutineReadme.txt"); 11 yield (www); 12 UnityEngine.Debug.Log$$Object("Text from WWW: " + www.get_text()); 13 yield (this.StartCoroutine$$IEnumerator(this.WaitForCangJingKong())); 14 UnityEngine.Debug.Log$$Object("Wait for CangJingKong finished!"); 15 }
Do you notice what has been changed?
Now the code matchs exactly JavaScript yield syntax, and is ready to run.
A C# coroutine method is compiled by C# comipler, it returns an IEnumerator when you call it.
This IEnumerator is then passed to "StartCoroutine" method. After that, it is Unity coroutine scheduler who manages them and decides when to call "MoveNext".
In the case of JavaScript, it also has compiler to compile coroutine function, but C# coroutine scheduler is not available for JavaScript's coroutine.
So I wrote a JavaScript coroutine scheduler (learn from first link in this document), it's in
StreamingAssets/JavaScript/Manual/UnityEngine_MonoBehaviour.javascript (B)
By the way, one similiar file is:
StreamingAssets/JavaScript/Generated/UnityEngine_MonoBehaviour.javascript (A)
Relationship (A) and (B): includes.javascript includes (A) first, (B) overrides some methods, and add some methods.
(B) file contents:
1 _jstype = undefined; 2 for (var i = 0; i < JsTypes.length; i++) { 3 if (JsTypes[i].fullname == "UnityEngine.MonoBehaviour") { 4 _jstype = JsTypes[i]; 5 break; 6 } 7 } 8 9 if (_jstype) { 10 _jstype.definition.StartCoroutine$$String = function(a0/*String*/) { 11 if (this[a0]) 12 { 13 var fiber = this[a0].call(this); 14 return this.$AddCoroutine(fiber); 15 } 16 } 17 _jstype.definition.StartCoroutine$$IEnumerator = function(a0/*IEnumerator*/) { 18 return this.$AddCoroutine(a0); 19 } 20 21 // 22 // Coroutine Scheduler 23 // 24 // REFERENCE FROM 25 // 26 // Coroutine Scheduler: 27 // http://wiki.unity3d.com/index.php/CoroutineScheduler 28 // 29 // JavaScript yield documents: 30 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield 31 // 32 33 // fiber 类似于 C# 的 IEnumerator 34 _jstype.definition.$AddCoroutine = function (fiber) { 35 var coroutineNode = { 36 $__CN: true, // mark this is a coroutine node 37 prev: undefined, 38 next: undefined, 39 fiber: fiber, 40 finished: false, 41 42 waitForFrames: 0, // yield null 43 waitForSeconds: undefined, // WaitForSeconds 44 www: undefined, // WWW 45 waitForCoroutine: undefined, // Coroutine 46 }; 47 48 if (this.$first) { 49 coroutineNode.next = this.$first; 50 this.$first.prev = coroutineNode; 51 }; 52 53 this.$first = coroutineNode; 54 // NOTE 55 // return coroutine node itself! 56 return coroutineNode; 57 } 58 59 // this method is called from LateUpdate 60 _jstype.definition.$UpdateAllCoroutines = function (elapsed) { 61 // cn is short for Coroutine Node 62 var cn = this.$first; 63 while (cn != undefined) { 64 // store next coroutineNode before it is removed from the list 65 var next = cn.next; 66 var update = false; 67 68 if (cn.waitForFrames > 0) { 69 cn.waitForFrames--; 70 if (cn.waitForFrames <= 0) { 71 waitForFrames = 0; 72 this.$UpdateCoroutine(cn); 73 } 74 } 75 else if (cn.waitForSeconds) { 76 if (cn.waitForSeconds.get_finished(elapsed)) { 77 cn.waitForSeconds = undefined; 78 this.$UpdateCoroutine(cn); 79 } 80 } 81 else if (cn.www) { 82 if (cn.www.get_isDone()) { 83 cn.www = undefined; 84 this.$UpdateCoroutine(cn); 85 } 86 } 87 else if (cn.waitForCoroutine) { 88 if (cn.waitForCoroutine.finished == true) { 89 cn.waitForCoroutine = undefined; 90 this.$UpdateCoroutine(cn); 91 } 92 } 93 else { 94 this.$UpdateCoroutine(cn); 95 } 96 cn = next; 97 } 98 } 99 100 _jstype.definition.$UpdateCoroutine = function (cn) { // cn is short for Coroutine Node 101 var fiber = cn.fiber; 102 var obj = fiber.next(); 103 if (!obj.done) { 104 var yieldCommand = obj.value; 105 // UnityEngine.Debug.Log$$Object(JSON.stringify(yieldCommand)); 106 if (yieldCommand == null) { 107 cn.waitForFrames = 1; 108 } 109 else { 110 if (yieldCommand instanceof UnityEngine.WaitForSeconds.ctor) { 111 cn.waitForSeconds = yieldCommand; 112 } 113 else if (yieldCommand instanceof UnityEngine.WWW.ctor) { 114 cn.www = yieldCommand; 115 } 116 else if (yieldCommand.$__CN === true/*yieldCommand.toString() == "[object Generator]"*/) { 117 cn.waitForCoroutine = yieldCommand; 118 } 119 else { 120 throw "Unexpected coroutine yield type: " + yieldCommand.GetType(); 121 } 122 } 123 } 124 else { 125 // UnityEngine.Debug.Log$$Object("cn.finished = true;"); 126 cn.finished = true; 127 this.$RemoveCoroutine(cn); 128 } 129 } 130 131 _jstype.definition.$RemoveCoroutine = function (cn) { // cn is short for Coroutine Node 132 if (this.$first == cn) { 133 this.$first = cn.next; 134 } 135 else { 136 if (cn.next != undefined) { 137 cn.prev.next = cn.next; 138 cn.next.prev = cn.prev; 139 } 140 else if (cn.prev) { 141 cn.prev.next = undefined; 142 } 143 } 144 cn.prev = undefined; 145 cn.next = undefined; 146 } 147 }
Current, in C#, things after "yield return" can be:
One more thing, in previous code:
1 void LateUpdate() 2 { 3 jsimp.Coroutine.UpdateMonoBehaviourCoroutine(this); 4 }
In C#, coroutines are updated every frame automatically. But in JavaScript, we have to update coroutine scheduler outselvies. If you use coroutine in your script, you have to call coroutine update manually.
You can do this in any xxUpdate method. It may be better to do this in "LateUpdate" according Unity scripting reference.
Note: in C#, jsimp.Coroutine.UpdateMonoBehaviourCoroutine actually does NOTHING. JavaScript version of this method is in:
StreamingAssets/JavaScript/JSImp/Coroutine.javascript
1 if (typeof(JsTypes) == "undefined") 2 var JsTypes = []; 3 var jsimp$Coroutine = { 4 fullname: "jsimp.Coroutine", 5 baseTypeName: "System.Object", 6 staticDefinition: { 7 UpdateMonoBehaviourCoroutine: function (mb){ 8 mb.$UpdateAllCoroutines(UnityEngine.Time.get_deltaTime()); 9 } 10 }, 11 assemblyName: "SharpKitProj", 12 Kind: "Class", 13 definition: { 14 ctor: function (){ 15 System.Object.ctor.call(this); 16 } 17 } 18 }; 19 20 // replace old Coroutine 21 jsb_ReplaceOrPushJsType(jsimp$Coroutine);
see Line 8.
About WaitForSeconds class, there is no public methods to tell if time finishs or not. so I wrote one my self:
1 _jstype = undefined; 2 for (var i = 0; i < JsTypes.length; i++) { 3 if (JsTypes[i].fullname == "UnityEngine.WaitForSeconds") { 4 _jstype = JsTypes[i]; 5 break; 6 } 7 } 8 9 if (_jstype) { 10 11 _jstype.definition.ctor = function(a0) { 12 this.$totalTime = a0; 13 this.$elapsedTime = 0; 14 this.$finished = false; 15 } 16 17 _jstype.definition.get_finished = function(elapsed) { 18 if (!this.$finished) { 19 this.$elapsedTime += elapsed; 20 if (this.$elapsedTime >= this.$totalTime) { 21 this.$finished = true; 22 } 23 } 24 return this.$finished; 25 } 26 }
back home: JSBinding + SharpKit / Home