-
Notifications
You must be signed in to change notification settings - Fork 242
JsObject API #532
Comments
This issue is connected with #531. |
JsFunction.Call should be named JsFunction.Invoke. Otherwise this is ambiguous: JsObject o; JsFunction f;
o.Call("x", 1);
o.Call("y");
f.Call();
f.Call(1);
f.Call("y"); // ???????? |
JsObject.Cast's intent is ambiguous; it is unclear whether it is a conversion or an 'I promise this value is of type T' operation. For JsObject I would instead do something like: public abstract T AssumeType<T>();
public abstract T As<T> () where T : class;
public abstract T Cast<T> ();
public abstract bool Is<T> (); Sadly we can't support as/is/(T) on JsObject since the compiler will yell that the conversions are impossible :-( |
Also worthwhile to add: public abstract JsObject this[int index] { get; set; } |
Also it may be good idea add to
|
Ah, yes. Though I would probably name that one Keys. Good point either way. |
Could you define, how should |
It would generate a checked static cast at runtime - |
Hey guys, I've started on the above, specifically on the libraries rather than the compiler side; my goal at this point is to essentially produce the set of core lib DLLs based on the Mono ones using the above API. Quick overview:
The end result would be a set of "standalone" DLLs which could then be passed through the JSIL compiler. |
@Bablakeluke, I'm not sure that fully understand your goals. Which Mono libraries you want to translate? mscorlib/System.Core/System? What do you want to achieve with it? Probably I just not fully understand your desires. If you describe them more (probably with examples) I may suggest you some better options :) |
Sure, I'll try and describe it a little better: In short, Reflection.Emit support. This single feature requires a completely different direction and, in my case at least (a moddable game currently in the Unity web player with its own custom scripting languages), is vital to have. Ideally, I want to leverage as much existing code as possible. That's where the Mono libraries come in - All of them! The point of view is that the runtime must be able to load a bytecode translated from emit calls anyway, so it might as well load the libraries in this way too. That allows it to keep the compactness of streaming IL vs streaming potentially enormous amounts of pre-translated .NET IL in Javascript. So, a few things about this approach. Keeping in line with reusing as much code as possible, the actual bytecode and files being streamed in would actually be WebAssembly but with a small GC extension (which is coming in the distant future of WebAssembly anyway, and the WebIL project will essentially be making proposals on how it should operate). This avoids needing to implement a complete .NET runtime in Javascript and it'll be accelerated by all major browsers shortly too. A quick summary at this point, then, is like this: Mono DLLs -> WebAssembly files (+GC calls) -> stream to browser -> Load on a runtime Next, ideally, that runtime is as small as it can possibly be. Totally free from any particular languages/ frameworks like .NET, if possible - allowing it to leverage the existing JS engines. That means all the bootstrap-like functionality needs to be contained within those special WebAssembly files, which are being derived from ordinary DLLs. This is where JSObject comes in. Getting JS calls into the Mono DLLs The Mono DLLs of course contain a bunch of extern methods - the stubs which point at the functionality implemented in the bootstrap/ runtime. However, I need to implement them inside the library itself - get JS calls into the body of the methods. That process works like this: A Mono DLL -> Get all the extern/ InternalCall methods -> Generate a bunch of C# files with those methods in them -> Implement the methods in C# with JSObject calls -> Compile them into a DLL -> Merge this DLL with the original Mono one, overwriting the original extern call. From there, the resulting DLL is the one which then goes ahead and gets converted into a WebAssembly file. (It's worth pointing out here that the Mono DLL is used rather than the sources as it provides a nice wall between Mono/WebIL) So, time for a bit of an example! At the moment, the generation/ compilation/ merge of the C# files is working, and Socket seems like a nice place to start with an example as feature wise, it's about as awkward as they get (ironically, inside a web browser). In this case, the implementation would be the original protocol over websocket or WebRTC, and it's just going to be a very basic overview for now. Looking in the C# files generated from the Mono DLL, there's this method: private static void Connect_internal_real(IntPtr sock, SocketAddress sa, out int error){
} First thing to note is JSObject and IntPtr are essentially the same thing. So, the body would look something like this: private static void Connect_internal_real(IntPtr sock, SocketAddress sa, out int error){
} The code above is of course rough, so only take it as an overview of the API. Importantly, it's all (probably!) valid C#, aka it will compile into a DLL. This method inside that DLL can then be used to overwrite the original Mono extern method with the same namespace and method name, creating a method which is now only dependent on the JSObject API. In short, the conversion to WebAssembly implements the relatively tiny API, resulting in a "standalone" WebAssembly file, without the need for built in methods. From there, the compiler can do all the hard work, stripping IL methods that are known to be not in use, or the pipeline could be used for other languages like Java too. |
At least for initial implementation, I'd like to keep JS interop API as simple, as possible.
Verbatium.Expression, JSGlobal, JSLocal will return JsObject. Do we really need any other methods for JsObject/JsFunction? And now small test example:
|
I would suggest that even that may be too detailed. So far the following has been very successful for coverage albeit still in very early stages (thus it's not been committed anywhere just yet): using System;
using System.Collections;
using System.Collections.Generic;
namespace WebAPI{
/// <summary>
/// This represents an object from the DOM. This class is mostly a no-op; the runtime or compiler that
/// uses this API implements the methods directly, emitting the equivelant Javascript.
/// </summary>
public class JsObject{
public JsObject(string name){
}
/*
public Type GetType(){
// Here as a reminder only; runtime/compiler must implement this one (e.g. get the constructor prototype).
}
*/
/// <summary>For example, new Array() is implemented as Js.Global["Array"].New().</summary>
public JsObject New(params object[] args){
// No-op in this stub. Runtime/ compiler must implement this.
throw new NotImplementedException();
}
/// <summary>True if the given key exists on this object.</summary>
public bool Exists(string key){
// No-op in this stub. Runtime/ compiler must implement this.
throw new NotImplementedException();
}
/// <summary>Equivelant of the JS delete keyword.
/// Deletes the given property from the JS object this represents.</summary>
public void Delete(string key){
// No-op in this stub. Runtime/ compiler must implement this.
throw new NotImplementedException();
}
/// <summary>All the keys in this object.</summary>
public IEnumerable<string> Keys{
get{
// No-op in this stub. Runtime/ compiler must implement this.
throw new NotImplementedException();
}
}
/// <summary>Gets or sets a particular value on this object.</summary>
public object this[string key]{
get{
// No-op in this stub. Runtime/ compiler must implement this.
throw new NotImplementedException();
}
set{
// No-op in this stub. Runtime/ compiler must implement this.
throw new NotImplementedException();
}
}
/// <summary>Gets or sets a particular value at the given index on this object.</summary>
public object this[int index]{
get{
// No-op in this stub. Runtime/ compiler must implement this.
throw new NotImplementedException();
}
set{
// No-op in this stub. Runtime/ compiler must implement this.
throw new NotImplementedException();
}
}
/// <summary>Runs this DOM object as a method.
/// For example, Js.Global["console"]["log"].Invoke("Hello world!");</summary>
public object Invoke(params object[] args){
// No-op in this stub. Runtime/ compiler must implement this.
throw new NotImplementedException();
}
}
} Everything else is provided by the C# language itself the above is essentially a stub (along with another tiny file) which just makes the C# compiler happy. Usage is as follows: JSObject websocket=Js.Global("websocket").New("wss://...");
websocket["onopen"]=delegate(JSObject e){
// Open event
};
(websocket["send"] as JSObject).Invoke("hello!"); Useful side effects are things like strings and numbers are directly usable, i.e: For JSIL, once the above is compiled and the IL is being processed, the calls can fairly trivially be swapped for their equivalent Javascript. JSFunction seemingly isn't necessary either, as the usage here is to only satisfy the C# compiler; everything else can be derived (i.e. the user is using Invoke, therefore "send" can be expected to be a function). We're in the process of swapping all extern/ internal calls in the Mono DLLs with normal C# code written using the above API, with the end goal being to make a DLL which has very few dependencies (i.e. this API, basic types and something which understands IL) |
I have no objectives against
I have strict objectives against Invoke/New with Same I have objectives against Probably we don't need I'm not sure if we want extract Invoke/New to subclass. It a little bit more type-safe, but may end with lot more typing. On other hand it will be safe way to check if something is really function on JS side. |
As I'd like remove |
Also, I thought about
If UPDATE: Adapt method will be not changed on compile time, instead it will be fully JS-based implemented. |
Next question, does anybody have any objectives if all JsAPI methods will throw NotSupportedException if they executed in .Net? |
Looks like it is impossible to create JS-based implementation for Verbatium.Expression, that will work in dynamic context same, as it will work in static context when translated. That's why I'd like just add some explicit error message in translation, if somebody will try use it in dynamic context. Also, for better understand of type system and dynamic interop compatibility, all method of JSObject/JSFunction will be extension methods. We will lost indexer and need use Get/Set method instead. |
Updated API proposal:
All functions here will have duplicated JS implementations, so that they will work through Reflection and in dynamic context. UPDATE: Attempt to get type ref to |
@kg and @Bablakeluke, please review proposal changes in sq/JSIL.Meta#3. |
Though more about |
I suggest to introduce JsObject API, that will allow easier interop with JS, then using Verbutium without usage of dynamic. Suggested API:
Additionally we can overload some operators in JsObject. Initial implementation could be done with
JsReplace
attribute, but for proper implementation we should implement it inside JSIL transfromations logic - as we should detect creating new array inparams
calls and replace it with simple method call. Also we may discuss, ifJsFunction
should catch originalthis
context or not.EDITED: Suggested API changed based on @kg feedback
The text was updated successfully, but these errors were encountered: