JavaScript API Developer Guide
This section is for developers who want to add or modify the JavaScript API. You can find information on:
- Methods for exchanging information between C# and JavaScript (both directions)
- Implementing events and methods that let the two languages work together.
C# Assembly
The C# assembly has three classes: WebGLAPI.cs, JsBridge.cs, and JsonClasses.cs
WebGLAPI.cs
This class handles all communication to and from JavaScript. It also communicates with the FormaUIBase Assembly (a layer that aggregates all the data and operations necessary for implementing UIs).
Send information from C# to JavaScript
To send information from C# to JavaScript, Forma can either trigger events or set data in known memory locations.
Events
Events let you detect specific situations that end users can trigger in Unity Forma. To create events that can be used in JavaScript, you must do the following:
- Declare a static extern void method in the C# code using the following syntax:
#if UNITY_WEBGL && !UNITY_EDITOR
[DllImport("__Internal")]
static extern void MethodName(paramType1 paramName1, paramType2 paramName2, ...);
#else
static void MethodName(paramType1 paramName1, paramType2 paramName2, ...);
#end
- Define the corresponding method in the JavaScript plugin and add it to the library (see below).
Set data in known memory locations
You can use pointers to share data between C# and JavaScript. The browser automatically handles this to a point. When an extern method, such as the one above, is called with integers, floats, and Booleans, the JavaScript implementation can use those values directly. When the argument is a string or an array (complex types such as lists aren't directly supported), the JavaScript implementation receives a memory pointer.
To convert back a string, you need a call to Pointer_stringify(stringPointer)
, which returns the original string.
Note: Pointer_stringify
is only accessible in the JavaScript plugins. You can't use it on the HTML page that contains the WebGL build.
For arrays, you also need to pass its length and be aware of the data type (signed, unsigned, 8 bits, 16 bits, etc.). It's more efficient to use arrays to pass data from C# to JavaScript, but it's less flexible because any change to the data type requires you to change the JavaScript plugins. Supporting more complex data structures becomes difficult.
Unity Forma uses JSON objects to pass arrays and complex objects from C# to JavaScript. The method is efficient because it needs to share a string pointer between the two languages. The conversion operations can make the process slower than simple arrays, but it's more manageable to change the data structures.
To exchange JSON objects, the C# code must use the JsonUtility.ToJson()
with an instance of a serializable class to turn the complex data structure into a string.
[Serializable]
internal/public class ClassName
{
public valueType memberName;
//...
}
var jsonObj = new ClassName
{
memberName = memberValue
};
JsonUtility.ToJson(jsonObj);
This string can then be retrieved in the JavaScript plugins, converted back to a readable string, and then parsed.
const jsonData = JSON.parse(Pointer_stringify(pointerValue));
Send information from JavaScript to C#
There are two ways to pass information from JavaScript to C#: Using SendMessage()
or
using dynamic calls.
Use dynamic calls
To use dynamic calls, the JavaScript must have a pointer to the C# function to call. Unity Forma does this by using one of the JsBridge.SetDynamicCall_x()
methods available in the JsBridge
class. The SetDynamicCall_x()
methods are declared in C# with the correct attributes and defined in a JavaScript plugin.
In the JavaScript plugin, a call to the Unity Forma instance is made to insert the function pointer in a JavaScript object so that you can reference it easily later.
The following call:
Module.forma._addDynamicCall(Pointer_stringify(functionName), functionPointer);
adds an entry in the this.#dynamicCalls
array in the form of a function pointer, accessed using this.#dynamicCalls.functionName
.
For example, in C#:
JsBridge.SetDynamicCall_x("functionName", functionName);
In JavaScript, to call that function:
Module.dynCall_y(this.#dynamicCalls.functionName);
There are different flavors of Module.dynCall
, depending on the parameter types and return value. The first letter after the underscore (_
) is the return type. After it are the parameter types. Available values include:
v
: voidi
: intf
: floatd
: double
Strings passed between C# and JavaScript are in the form of int
pointers.
Examples
Module.dynCall_v(this.#dynamicCalls.functionName); //void return type, no parameters
Module.dynCall_i(this.#dynamicCalls.functionName); //int return type, no parameters. This is the return type of the corresponding C# method (see JsBridge.cs section below). If the method returns a string, the JS will receive an int pointer that can be converted back to a string using Pointer_stringify(intPointer)
Module.dynCall_vii(this.#dynamicCalls.functionName, intParam1, intParam2); //void return type, two int parameters
Module.dynCall_ii(this.#dynamicCalls.functionName, Helpers.stringToPointer(strValue)); //int return type, string parameter (converted to an int pointer using a method available in the Helpers class).
There are different flavors of SetDynamicCall
. They follow the same naming convention as Module.dynCall_x
, except in C# you must use the letter s
to signify a string parameter or return value.
JsBridge.cs
The JsBridge.cs
class contains a set of methods called SetDynamicCall_x
. they accomodate different callbacks that you can then use in JavaScript.
For this class, the convention is that the first letter represents the return value type. Other letters represent parameter types. The return and parameter types need to be specified like you would a plain C# method.
Example: If the method expects a string parameter and returns void, the signature is as follows:
#if UNITY_WEBGL && !UNITY_EDITOR
[DllImport("__Internal")]
public static extern void SetDynamicCall_vs(string callbackName, Action<string> callback);
#else
public static void SetDynamicCall_vs(string callbackName, Action<string> callback) { }
#end
If the method returns a value, you must use Func <paramType1, paramType2,...,returnType>
instead of Action
. Then, you must define the method in C#:
[MonoPInvokeCallback(typeof(Action))] //or Action<paramType1,...> or Func<paramType1,...,returnType>
public static void MethodName() // a return type can also be specified
{
//...
}
Another example, with an int
parameter, is as follows:
[MonoPInvokeCallback(typeof(Action<int>))]
public static void methodName(int intParam)
{
//...
}
Finally, an example with a string return type and a string parameter appears as follows:
[MonoPInvokeCallback(typeof(Func<int,int>))]
public static string methodName(string strParam)
{
//...
}
//this will be invoked in JS via:
const returnedIntPointer = Module.dynCall_ii(this.#dynamicCalls.functionName, Helpers.stringToPointer(strValue));
const actualString = Pointer_stringify(returnedIntPointer);
Note: For strings, the actual method parameters have a string parameter, but the Action
or Func
part of the attribute must list the string type as int
.
JsonClasses.cs
The JsonClasses.cs
file contains a series of serializable classes that you can use for JSON serialization. It also has a special template class, called JsonList
that functions as a wrapper class for JSON lists. The wrapper is necessary because JsonUtility.ToJson
doesn't support directly serializing lists. Unity Forma serializes lists using the wrapper object and accesses them on the JavaScript side via the list
property on the JSON object.
JavaScript Plugins
Unity Forma uses two types of JavaScript plugins: those with the .jslib
extension and those with the .jspre
extension. The .jslib
extension is for plugins that are processed into the WebGL build framework. The .jspre
extension is for regular JavaScript code that is copied as is before the framework, but still needs to access framework functionality such as Pointer_stringify()
. The JavaScript classes in the guide for Forma
and Display info
are in .jspre
files.
The .jslib
files contain code that can be accessed from the C#. You must add the code to the library explicitly. The syntax is the one you use for literal JavaScript objects.
mergeInto(LibraryManager.library, {
MethodName: function(param1, param2, ...) {
//...
}
}
To help separate boilerplate code from Unity Forma events, Unity Forma has two .jslib
files. The .jslib
files define the methods declared in the C#. You use them for communication from C# to JavaScript (not JavaScript to C#). For JavaScript to C# communication, see above.