JSBinding + SharpKit / Supporting Coroutine

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

 

Part 1. Usage

 

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?

  1. Use "function*" instead of "function" to declare a coroutine function
  2. "$yield" array has been deleted
  3. "$yield.push" has been replaced with simple "yield"

Now the code matchs exactly JavaScript yield syntax, and is ready to run.

 

Part 2. Things inside

 
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.

  • Overrides: StartCoroutine$$IEnumerator, StartCoroutine$$String,
  • New methods: $UpdateAllCoroutines,$updateCoroutine, etc. They are coroutine scheduler.

(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:

  1. yield return null; // call MoveNext() next frame
  2. yield return new WWW(...); // WWW
  3. yield return new WaitForSeconds(...); // wait for some time
  4. yield return new StartCoroutine(...); // wait for another coroutine

 

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

你可能感兴趣的:(coroutine)