sq / JSIL

CIL to Javascript Compiler
http://jsil.org/
Other
1.73k stars 240 forks source link

Doing something wrong with proxies... #377

Closed profK closed 10 years ago

profK commented 10 years ago

I am getting a not implemented exception in execution, which strongly suggests my proxy isn't working right. I do see the proxy begin recognized on the JSIL compile line.

I define an Interface which both a dummy class and the proxy class implement. Then I define a js file to implement the proxy and place it in the folder with the rest of the generated js. I link to the dummy implementation in mono compile. My assumption was that JSIl would replace it with the proxy/js combination.

Below are my source files:

================== CanvasInterface.cs================

using System;

namespace JavascriptGraphicsProvider
{
    public interface CanvasInterface
    {
        void SetDiv (string divName);
        void SetFont (string fontName);
        void SetClip (int x, int y, int width, int height);
        void Translate (int x, int y);
        void Rotate(float radians);
        void DrawImage(object image,int sx, int sy, int swidth, int sheight,
            int x, int y, int width, int height);
        void FillText (string txt, int x, int y);
        int MeasureTextWidth (string txt);
        int GetfontHeight();
        // asset loading
        object LoadImage(string imageUrl);
        object LoadTextFile(string fileUrl);
    }
}

===================CanvasManager.cs ===================

using System;

namespace JavascriptGraphicsProvider
{
    // this is a dummy objet that will be rpealced by the proxy
    // when JSIl compile happens
    public class CanvasManager:CanvasInterface
    {
        public CanvasManager ()
        {
        }

        #region CanvasInterface implementation

        public void SetDiv (string divName)
        {
            throw new NotImplementedException ();
        }

        public void SetFont (string fontName)
        {
            throw new NotImplementedException ();
        }

        public void SetClip (int x, int y, int width, int height)
        {
            throw new NotImplementedException ();
        }

        public void Translate (int x, int y)
        {
            throw new NotImplementedException ();
        }

        public void Rotate (float radians)
        {
            throw new NotImplementedException ();
        }

        public void DrawImage (object image, int sx, int sy, int swidth, int sheight, int x, int y, int width, int height)
        {
            throw new NotImplementedException ();
        }

        public void FillText (string txt, int x, int y)
        {
            throw new NotImplementedException ();
        }

        public int MeasureTextWidth (string txt)
        {
            throw new NotImplementedException ();
        }

        public int GetfontHeight ()
        {
            throw new NotImplementedException ();
        }

        public object LoadImage (string imageUrl)
        {
            throw new NotImplementedException ();
        }

        public object LoadTextFile (string fileUrl)
        {
            throw new NotImplementedException ();
        }

        #endregion
    }
}

==================CanvasManagerProxy.cs==============

using System;
using JSIL.Meta;
using JSIL.Proxy;

namespace JavascriptGraphicsProvider
{
    [JSProxy(
        typeof(JavascriptGraphicsProvider.CanvasManager),
        JSProxyMemberPolicy.ReplaceDeclared,
        JSProxyAttributePolicy.ReplaceDeclared
    )]
    public abstract class CanvasManagerProxy:CanvasInterface
    {

        [JSMutatedArguments()]
        [JSEscapingArguments()]
        public abstract void SetDiv (string divName);

        [JSMutatedArguments()]
        [JSEscapingArguments()]
        public abstract void SetFont (string fontName);

        [JSMutatedArguments()]
        [JSEscapingArguments()]
        public abstract void SetClip (int x, int y, int width, int height);

        [JSMutatedArguments()]
        [JSEscapingArguments()]
        public abstract void Translate (int x, int y);

        [JSMutatedArguments()]
        [JSEscapingArguments()]
        public abstract void Rotate(float radians);

        [JSMutatedArguments()]
        [JSEscapingArguments()]
        public abstract void DrawImage(object image,int sx, int sy, int swidth, int sheight,
            int x, int y, int width, int height);

        [JSMutatedArguments()]
        [JSEscapingArguments()]
        public abstract void FillText (string txt, int x, int y);

        [JSMutatedArguments()]
        [JSEscapingArguments()]
        public abstract int MeasureTextWidth (string txt);

        [JSMutatedArguments()]
        [JSEscapingArguments()]
        public abstract int GetfontHeight();
        // asset loading

        [JSMutatedArguments()]
        [JSEscapingArguments()]
        public abstract object LoadImage(string imageUrl);

        [JSMutatedArguments()]
        [JSEscapingArguments()]
        public abstract object LoadTextFile(string fileUrl);

    }
}

============ CanvasManager.js====================

"use strict";

JSIL.ImplementExternals("JavascriptGraphicsProvider.CanvasManager", function ($) {
    // GLOBALS
    var ctxt;

    //Methods

    //public abstract void SetDiv (string divName);
    $.Method({Static:false, Public:true }, "SetDiv", 
    (new JSIL.MethodSignature(null, [$.String]), 
        function SetDiv (name) {
            var c = document.getElementById(name);
            ctx = c.getContext("2d");
        }
    );

    //public abstract void SetFont (string fontName);
    $.Method({Static:false, Public:true }, "SetFont", 
    (new JSIL.MethodSignature(null, [$.String]), 
        function SetFont (name) {
            ctxt.font = name;
        }
    );

    //public abstract void SetClip (int x, int y, int width, int height);
    $.Method({Static:false, Public:true }, "SetClip", 
    (new JSIL.MethodSignature(null, [$.Int32],[$.Int32],[$.Int32],[$.Int32]), 
        function SetClip (x,y,width,height) {
            ctxt.beginPath();
            ctxt.moveTo(x,y);
            ctxt.lineTo(x+width,y);
            ctxt.lineTo(x+width,y+height);
            ctxt.lineTo(x,y+height);
            ctxt.lineTo(x,y);
            ctxt.clip();
        }
    );

    //public abstract void Translate (int x, int y);
    $.Method({Static:false, Public:true }, "Translate", 
    (new JSIL.MethodSignature(null, [$.Int32],[$.Int32]), 
        function Translate (x,y) {
            ctxt.translate(x,y);
        }
    );

    //public abstract void Rotate(float radians);
    $.Method({Static:false, Public:true }, "Rotate", 
    (new JSIL.MethodSignature(null, [$.Single]), 
        function Rotate (rad) {
            ctxt.rotate(rad);
        }
    );

    //public abstract void DrawImage(object image,int sx, int sy, int swidth, int sheight,
    //      int x, int y, int width, int height);
    $.Method({Static:false, Public:true }, "DrawImage", 
    (new JSIL.MethodSignature(null, [$.Object],[$.Int32],[$.Int32],
            [$.Int32],[$.Int32],[$.Int32],[$.Int32],[$.Int32],[$.Int32]), 
        function DrawImage (image,sx,sy,swidth,sheight,x,y,width,height) {
            ctxt.drawImage(image,sx,sy,swidth,sheight,x,y,width,height);
        }
    );

    //public abstract void FillText (string txt, int x, int y);
    $.Method({Static:false, Public:true }, "FillText", 
    (new JSIL.MethodSignature(null, [$.String],[$.Int32],[$.Int32]), 
        function FillText (text,x,y) {
            ctxt.fillText(text,x,y);
        }
    );

    //public abstract int MeasureTextWidth (string txt);
    $.Method({Static:false, Public:true }, "MeasureTextWidth", 
    (new JSIL.MethodSignature([$.Int32], [$.String]), 
        function MeasureTextWidth (text) {
            return ctxt.measureText(text).width;
        }
    );

    //public abstract int GetFontHeight();
    $.Method({Static:false, Public:true }, "GetFontHeight", 
    (new JSIL.MethodSignature([$.Int32]), 
        function GetFontHeight () {
            return parseInt(ctxt.height)
        }
    );
    // asset loading

    //public abstract object LoadImage(string imageUrl);
    $.Method({Static:false, Public:true }, "LoadImage", 
    (new JSIL.MethodSignature([$.Object],[$.String]), 
        function LoadImage (name) {
            var imageObj = new Image();
            imageObj.src = name;
            imageObj.onload= function(){
                imageObj.loaded=true;
            }
            return imageObj;
        }
    );

    //public abstract string LoadXMLFile(string fileUrl);
    $.Method({Static:false, Public:true }, "LoadXMLFile", 
    (new JSIL.MethodSignature([$.Object],[$.String]), 
        function LoadXMLFile (fileUrl) {
            var oReq = new XMLHttpRequest();
            var promise = {loaded:false};
            oReq.onload = function(){
                promise.loaded=true;
                promise.text=oReq.responseText;
            }
            oReq.open("get", fileUrl, true);
            oReq.send();
        }
    );

}
iskiselev commented 10 years ago
  1. For your case, you really even don't need any proxies. You can mark methods with proper Meta Attributes right in CanvasManager.cs. At the same time, if you want isolate using of JSIL Meta attributes, than your solution is correct.
  2. You should mark your methods with (in your class or proxy) with JSExternal attribute or your class with JSStubOnly attribute.
  3. You should include your CanvasManager.js to js solution yourself. Simplest method for this will be adding this file to Libraries folder and add to your html file something like:
            var assetsToLoad = [
                ["Library", "CanvasManager.js"]
            ];
kg commented 10 years ago

@iskiselev already covered it (thanks!) but a quick summary of proxies and how they work for you:

Proxies are a layer over your existing types and objects; they let you replace method bodies, add/replace attributes, etc. So applying a proxy does not automatically mean that implementations will be pulled from JS, or anything like that - attributes cause that behavior.

The 'ReplaceDeclared' member behavior you set on your proxy just meant the NotImplementedException(s) were replaced with abstract method bodies, so that's why you get failures at runtime.

JSExternal and JSStubOnly are the two attributes you want for the way you've written things, as @iskiselev suggested. You can also replace a method body with one line of JS (the JSReplacement attribute) or replace statements inside a method body with JS (JSIL.Verbatim.Expression).

You can also just expose a regular JS object and manipulate it using 'dynamic', though as you may have noticed, dynamic support is not 100% comprehensive.

profK commented 10 years ago

Thanks Guys,

So, to be clear, do JSReplacement and JSIL.Verbatim.Expression allow for inline JS in some fashion or does it always have to be in an external file? If I could inline the JS and dispense with the external JS file that would be ideal. An example of an inline JS body would be a great help :) Also, if this inlining is possible, an example of how to create/access a field from the inline bodies of multiple methods would also be good.

Otherwise, if I mark the CanvasManager.cs class with JSStubOnly and load CanvasManager.js, will it automatically bind to the JS? (I realize I can test this myself and probably will, but just in case there is something else Im missing I figured I'd ask.)

Thanks again

kg commented 10 years ago

Nothing in JSIL.Meta requires external files.

The built-in proxies and test cases demonstrate use of both JSReplacement and Verbatim.Expression; just do a search of the source code.

profK commented 10 years ago

Thanks Ill go look at those

profK commented 10 years ago

Ok, I'm probably doing something stupid BUT...

I eliminated the proxy, and added JSStubOnly to the CanvasManager.C like so:

[JSStubOnly]
public class CanvasManager:CanvasInterface

I added a line to my HTML to load CanvasManager.js like this:

<script src="../Libraries/JSIL.js" type="text/javascript"></script>
<script src="CanvasManager.js" type="text/javascript"></script>

And after cleaning up a few syntactical errors in CanvasManager.js, got this error on execution: Loading '../Libraries/ES5.js'..." JSIL.js:72 "Loading '../Libraries/mersenne.js'..." JSIL.js:72 "Loading '../Libraries/JSIL.Core.js'..." JSIL.js:72 "Loading '../Libraries/JSIL.Host.js'..." JSIL.js:72 "Loading '../Libraries/JSIL.Browser.js'..." JSIL.js:72 "Loading '../Libraries/JSIL.Browser.Audio.js'..." JSIL.js:72 "Loading '../Libraries/JSIL.Browser.Loaders.js'..." JSIL.js:72 "Loading '../Libraries/JSIL.Browser.Touch.js'..." JSIL.js:72 "Loading '../Libraries/JSIL.Core.Types.js'..." JSIL.js:72 "Loading '../Libraries/JSIL.Core.Reflection.js'..." JSIL.js:72 "Loading '../Libraries/JSIL.References.js'..." JSIL.js:72 "Loading '../Libraries/JSIL.Unsafe.js'..." JSIL.js:72 "Loading '../Libraries/JSIL.Bootstrap.js'..." JSIL.js:72 "Loading '../Libraries/JSIL.Bootstrap.Int64.js'..." JSIL.js:72 "Loading '../Libraries/JSIL.Bootstrap.DateTime.js'..." JSIL.js:72 "Loading '../Libraries/JSIL.Bootstrap.Text.js'..." JSIL.js:72 "Loading '../Libraries/JSIL.Bootstrap.Resources.js'..." JSIL.js:72 "Loading '../Libraries/JSIL.Bootstrap.Linq.js'..." JSIL.js:72 "Loading 'JavascriptGraphicsProviderTest.exe.manifest.js'..." JSIL.js:72 The character encoding of the HTML document was not declared. The document will render with garbled text in some browser configurations if the document contains characters from outside the US-ASCII range. The character encoding of the page must be declared in the document or in the transfer protocol. index.html TypeError: signature.GetKey is not a function JSIL.Core.js:6194

Any thoughts on what i did to cause that? My complete index.html looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html> 
<html>
  <head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
  </head>
  <body onload="onLoad()">    
    <script type="text/javascript">
      var jsilConfig = {
        printStackTrace: false,
        xna: 4,

        manifests: [
          "JavascriptGraphicsProviderTest.exe"
        ],
      };
    </script>
    <script src="../Libraries/JSIL.js" type="text/javascript"></script>
     <script src="CanvasManager.js" type="text/javascript"></script>
    <canvas id="AnyGUIDiv" width="1280" height="720">
    </canvas><br>

    <div id="log"></div>

    <script type="text/javascript">
      function runMain () {
        // We can't invoke Main() since it disposes the Game immediately, breaking everything.
        var asm = JSIL.GetAssembly("JavascriptGraphicsProviderTest", true);
        var game = new asm.JavascriptGraphicsProviderTest.MainClass();
        game.Run();
      };
    </script>
  </body>
</html>```
iskiselev commented 10 years ago

You cannot load scripts that implement classes for JSIL using script tag, as JSIL itself is not loaded yet at that moment. Instead, you should instruct JSIL loader to load your custom script. For this use next script (suppose, that CanvasManager.js is in Libraries folder):

            var assetsToLoad = [
                ["Library", "CanvasManager.js"]
            ];
profK commented 10 years ago

Well no video but it runs now!

Thanks guys, Ill go to work debugging the canvas details on my own. You've been huge help.

In the long run id like to change it so the it doesn't need the external JS, but baby steps :)

kg commented 10 years ago

Whenever debugging an issue like the above, please include a full stack trace for the error. Just the message and line number is never sufficient (yay, web browsers...)

profK commented 10 years ago

heh, my bad. You don't know how many times Ive told people that when playing the Java expert.

profK commented 10 years ago

Hmm spoke a little too soon... apparently the external JS still isn't loading. Below is my html. Again Im sure its something silly I'm doing.

While I wait for your insight, I'm going to go ahead and start work on a version that doesn't need the external file...

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html>


On Tue, Apr 8, 2014 at 10:10 PM, K. Gadd notifications@github.com wrote:

Whenever debugging an issue like the above, please include a full stack trace for the error. Just the message and line number is never sufficient (yay, web browsers...)

Reply to this email directly or view it on GitHubhttps://github.com/sq/JSIL/issues/377#issuecomment-39922268 .

It's always darkest just before you are eaten by a grue.

iskiselev commented 10 years ago

Key parameter in assetsToLoad is not path, it is type of resource. Please look ad wiki: https://github.com/sq/JSIL/wiki#using-the-compiled-javascript-in-a-webpage . So, you cannot use "../Library" instead of "Library" or "Script" for loading scripts. "Library" and "Script" are loading from different paths and at different time.

kg commented 10 years ago

You also need to define assetsToLoad in global scope, not inside runMain.

profK commented 10 years ago

Ah thanks.

I've moved on to doing it inline with Verbatim.eExpression. i have that mostly figured out and it seems simpler.

One question. I've determined I can declare a variable that is local to the scope of the verbatim code with "var foo=777" and I can add one to the global scope with "foo=777" , but neither is really ideal. is there a way I can declare a variable that will be visible to multiple methods? is it as simple as adding it as field to the host class, and if so what ape do i give it? I don't need to see it in the C# code, just the JS.

On Wed, Apr 9, 2014 at 4:34 PM, K. Gadd notifications@github.com wrote:

You also need to define assetsToLoad in global scope, not inside runMain.

Reply to this email directly or view it on GitHubhttps://github.com/sq/JSIL/issues/377#issuecomment-40013000 .

It's always darkest just before you are eaten by a grue.

kg commented 10 years ago

A static field in the host class should get defined just like a normal field, without any special attributes needed. Then in your verbatim code you can access it like:

Namespace.TypeName.Field

Alternately, you can define the field, and pass it to your Verbatim expressions as an argument (I think there should be one or two test cases and examples that pass arguments to verbatim) - then it will map the name for you.

profK commented 10 years ago

Okay next quick Q... I have a couple of calls that return an int.

From the code I gather that Verbatime.Expression() will return the result of the expression (what I return from it), but is there something i need to do to cast that return back into a C# int to return to my C# code?

On Wed, Apr 9, 2014 at 4:50 PM, Jeffrey Kesselman jeffpk@gmail.com wrote:

Ah thanks.

I've moved on to doing it inline with Verbatim.eExpression. i have that mostly figured out and it seems simpler.

One question. I've determined I can declare a variable that is local to the scope of the verbatim code with "var foo=777" and I can add one to the global scope with "foo=777" , but neither is really ideal. is there a way I can declare a variable that will be visible to multiple methods? is it as simple as adding it as field to the host class, and if so what ape do i give it? I don't need to see it in the C# code, just the JS.

On Wed, Apr 9, 2014 at 4:34 PM, K. Gadd notifications@github.com wrote:

You also need to define assetsToLoad in global scope, not inside runMain.

Reply to this email directly or view it on GitHubhttps://github.com/sq/JSIL/issues/377#issuecomment-40013000 .

It's always darkest just before you are eaten by a grue.

It's always darkest just before you are eaten by a grue.

profK commented 10 years ago

Just thought you might like to see this... its a small victory, but its a victory!

using JavascriptGraphicsProvider; using JSIL;

namespace JavascriptGraphicsProviderTest { class MainClass { public static void Main (string[] args) { }

    public void Run(){
        CanvasManager cm = new CanvasManager ();
        cm.SetDiv ("AnyGUIDiv");
        //cm.SetFontSize("24");
        cm.FillText ("Some Text", 50, 50);
    }

}

}

[image: Inline image 1]

On Wed, Apr 9, 2014 at 5:05 PM, Jeffrey Kesselman jeffpk@gmail.com wrote:

Okay next quick Q... I have a couple of calls that return an int.

From the code I gather that Verbatime.Expression() will return the result of the expression (what I return from it), but is there something i need to do to cast that return back into a C# int to return to my C# code?

On Wed, Apr 9, 2014 at 4:50 PM, Jeffrey Kesselman jeffpk@gmail.comwrote:

Ah thanks.

I've moved on to doing it inline with Verbatim.eExpression. i have that mostly figured out and it seems simpler.

One question. I've determined I can declare a variable that is local to the scope of the verbatim code with "var foo=777" and I can add one to the global scope with "foo=777" , but neither is really ideal. is there a way I can declare a variable that will be visible to multiple methods? is it as simple as adding it as field to the host class, and if so what ape do i give it? I don't need to see it in the C# code, just the JS.

On Wed, Apr 9, 2014 at 4:34 PM, K. Gadd notifications@github.com wrote:

You also need to define assetsToLoad in global scope, not inside runMain.

Reply to this email directly or view it on GitHubhttps://github.com/sq/JSIL/issues/377#issuecomment-40013000 .

It's always darkest just before you are eaten by a grue.

It's always darkest just before you are eaten by a grue.

It's always darkest just before you are eaten by a grue.

profK commented 10 years ago

Hey Guys,

Just wanted to say thanks again. At this point i have all my glue written as inline code and most of it tested. Im going to work on the layer that ties it to my GUI system next.

I'll send you a URL when I have an example up, shouldn't be more then a few days more work :)

JK

On Tue, Apr 8, 2014 at 5:48 PM, K. Gadd notifications@github.com wrote:

@iskiselev https://github.com/iskiselev already covered it (thanks!) but a quick summary of proxies and how they work for you:

Proxies are a layer over your existing types and objects; they let you replace method bodies, add/replace attributes, etc. So applying a proxy does not automatically mean that implementations will be pulled from JS, or anything like that - attributes cause that behavior.

The 'ReplaceDeclared' member behavior you set on your proxy just meant the NotImplementedException(s) were replaced with abstract method bodies, so that's why you get failures at runtime.

JSExternal and JSStubOnly are the two attributes you want for the way you've written things, as @iskiselev https://github.com/iskiselevsuggested. You can also replace a method body with one line of JS (the JSReplacement attribute) or replace statements inside a method body with JS (JSIL.Verbatim.Expression).

You can also just expose a regular JS object and manipulate it using 'dynamic', though as you may have noticed, dynamic support is not 100% comprehensive.

Reply to this email directly or view it on GitHubhttps://github.com/sq/JSIL/issues/377#issuecomment-39906070 .

It's always darkest just before you are eaten by a grue.