Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Breaking changes in 5.6

Discussion in 'Web' started by Marco-Trivellato, May 9, 2017.

  1. Marco-Trivellato

    Marco-Trivellato

    Unity Technologies

    Joined:
    Jul 9, 2013
    Posts:
    1,654
    As you are probably already aware, we introduced some breaking changes in 5.6, mostly related to the Embedding API, which we have been discussing in the Embedding API Whitepaper forum post.

    In summary, the areas affected by the new Embedding API are:
    • Loading and Initialization: the content needs to be instantiated via UnityLoader.instantiate()
    • JS and scripts communication: functions that used to be global (like SendMessage) are now members of the instance.
    • Deployment: we now have a single file extension (.unityweb) which you need to take into account when configuring your server
    Unfortunately, there has been a delay in updating the 5.6 docs, which caused some pain to a lot of you. Sorry about that.

    Now, the good news is that we have been working on drafting the updated manual pages which we would like to share with you before they go live:
    We should also mention that the embedding API is not the only breaking change. In 5.6, LZMA is no longer supported as asset bundle compression so you will need to rebuild them using BuildAssetBundleOptions.UncompressedAssetBundle or BuildAssetBundleOptions.ChunkBasedCompression.

    Thanks,
    Marco
     
    devluz, sirrus, yuliyF and 1 other person like this.
  2. devluz

    devluz

    Joined:
    Aug 20, 2014
    Posts:
    66
    Hi,

    I investigate a bug lately that is only triggered in Unity 5.6 WebGL. Could this be related to the Embedding API?
    e.g. the following fails in Unity 5.6 WebGL:

    Code (CSharp):
    1.         string code =
    2. @"
    3. function TESTCALL()
    4. {
    5.    return 'Test successful!';
    6. }
    7.  
    8. console.log(TESTCALL());
    9. ";
    10.         Debug.LogWarning("Eval function + first test call:");
    11.         Application.ExternalEval(code);
    12.         Debug.LogWarning("second Test Call:");
    13.         Application.ExternalEval("console.log(TESTCALL());"); //Fail TESTCALL() not found
    14.  
    Everything defined during the first eval call seems to be gone in any further calls.

    So far I didn't find an elegant solution this except manually adding the java script code.

    Edit: So if I understand it correctly ExternalEval is running in a local scope now. Which makes sense but why does every ExternalEval run in its own scope? Shouldn't they all run in a shared application wide scope?
     
    Last edited: May 15, 2017
  3. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello devluz.

    Your code works as expected. Declaring variables and functions through eval is only possible in the global scope. Note that it is not recommended to use the global JavaScript scope in your scripts, because there may be other builds already running on that page (for example, there might be another instance of the same build, which would inevitably cause conflicts in the global scope). There are several different ways to achieve what you want without causing any conflicts.


    a) The recommended way.

    Starting from Unity 5.6, Application.ExternalEval/ExternalCall is considered to be deprecated and is only supported for compatibility. It is recommended to use JavaScript plugins instead (.jslib or .jspre). Your code can be represented using plugins in the following way:

    add the following Assets/Plugins/TESTCALL.jslib plugin:
    Code (JavaScript):
    1. mergeInto(LibraryManager.library, {
    2.   TESTCALL: function() {
    3.     console.log("TESTCALL has been called successfully!");
    4.   },
    5. });
    and use the following script:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Runtime.InteropServices;
    3.  
    4. public class NewBehaviourScript : MonoBehaviour {
    5.     [DllImport("__Internal")]
    6.     private static extern void TESTCALL();
    7.  
    8.     void Start () {
    9.         TESTCALL ();
    10.     }
    11. }
    see https://docs.unity3d.com/Manual/webgl-interactingwithbrowserscripting.html for additional details (the docs for 5.6 will be updated soon)


    b) In case if you already have a lot of code depending on Application.ExternalEval/ExternalCall.

    Use an object from the local scope as a namespace to store your intermediate results between the eval calls. For example, you can always use the Module object for namespacing (i.e. use Module.TESTCALL instead of TESTCALL), as the Module object is local to the build instance and is always available:
    Code (CSharp):
    1.         string code =
    2. @"
    3. Module.TESTCALL = function ()
    4. {
    5.   return 'Test successful!';
    6. }
    7. console.log(Module.TESTCALL());
    8. ";
    9.         Debug.LogWarning("Eval function + first test call:");
    10.         Application.ExternalEval(code);
    11.         Debug.LogWarning("second Test Call:");
    12.         Application.ExternalEval("console.log(Module.TESTCALL());"); //Test successful!

    c) Not recommended way, workaround for special cases:

    Even though eval is now executed in the local scope, you can still enforce the global scope by using a wrapped indirect call to eval, for example:
    Code (CSharp):
    1.         string code = "function TESTCALL() { return 'Test successful!'; } console.log(TESTCALL());";
    2.         Debug.LogWarning("Eval function + first test call:");
    3.         Application.ExternalEval("eval.call(null,\"" + code + "\")");
    4.         Debug.LogWarning("second Test Call:");
    5.         Application.ExternalEval("console.log(TESTCALL());"); //Test successful!
    The trick here is that indirect calls to eval (such as eval.call(null, expression) or (1,eval)(expression) etc.) are always executed in the global scope (if you are interested in the details, here is a good article http://perfectionkills.com/global-eval-what-are-the-options/).

    Generally you should always assume that there are other builds running on the page at the same time, so try to avoid using global scope whenever possible.
     
    Last edited: May 15, 2017
    devluz likes this.
  4. devluz

    devluz

    Joined:
    Aug 20, 2014
    Posts:
    66
    Thank you for the detailed answer. It makes sense to avoid using the global scope. This is clearly an improvement. Sadly, for now I have to go with c) and still use the global namespace until I find a better way for my use case. I use the following now (to avoid problems with ""):

    Code (CSharp):
    1. Application.ExternalCall("(1, eval)", code);
    I will probably try avoiding using ExternalEval all together in the future. Currently, I am working on a Unity Asset that uses an existing java script file (.js) that usually has to be included into the website manually. To avoid this manual setup for the user of my Asset I use ExternalEval to emit the whole js library into the global scope if it isn't available already. I then use a .jslib to access the functions in the .js file. Sadly, I couldn't find an elegant way to package an existing js library with the jslib :/

    Is it possible to package existing .js files with unity to be included automatically into a WebGL build? The only way I found so far are WebGL Templates but they aren't ideal for Unity Assets. As the user would need to manually select the template (and probably wants to use their own template anyway).

    Edit: I find it still confusing that each ExternalCall call ends up in its own local scope. Wouldn't it be better if they all share the same application wide scope?
     
    Last edited: May 16, 2017
  5. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    Hello devluz.

    There are two types of JavaScript plugins: .jslib and .jspre. The difference between those types is the following.


    1) .jslib plugin

    This is actually a small JavaScript program, which integrates your JavaScript code with the compiled module. Specifically, when you use [DllImport("__Internal")] in your C# script, you introduce a dependency, external to the compiled module, which should be resolved at the module instantiation time. When you are using mergeInto(LibraryManager.library, {...}) in your .jslib plugin, this mergeInto function is in fact executed at build time and resolves the function names. Specifically, this puts all the functions, provided as properties of the 2nd mergeInto argument, into the JavaScript framework scope, and sets them as imports for the module. Normally it all happens at build time (except the case when you are using dynamic linking, then mergeInto functionality is emulated at runtime through function wrappers).

    Assets/Plugins/TESTCALL.jslib:
    Code (JavaScript):
    1. mergeInto(LibraryManager.library, {
    2.   TESTCALL: function() {
    3.     console.log("TESTCALL has been called successfully!");
    4.   },
    5. });
    NewBehaviourScript.cs:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Runtime.InteropServices;
    3.  
    4. public class NewBehaviourScript : MonoBehaviour {
    5.     [DllImport("__Internal")]
    6.     private static extern void TESTCALL();
    7.  
    8.     void Start () {
    9.         TESTCALL ();
    10.     }
    11. }

    2) .jspre plugin

    This is a raw JavaScript file, which is simply appended to the beginning of the JavaScript framework without any preprocessing. Such code is not integrated with the module in any way, expect of being with the module in the same scope. It might be convenient to use this type of plugin for external JavaScript libraries and other page related code.

    Assets/Plugins/TESTCALL.jspre:
    Code (JavaScript):
    1. function TESTCALL() {
    2.   console.log("TESTCALL has been called successfully!");
    3. }
    NewBehaviourScript.cs:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class NewBehaviourScript : MonoBehaviour {
    4.    void Start () {
    5.        Application.ExternalEval("TESTCALL()");
    6.    }
    7. }

    You just need to take care of the following potential issues:

    a) Your JavaScript library might be using variables already reserved in the framework scope (i.e. Module etc.). Normally this can be resolved by wrapping the whole library in a function and exporting the necessary entry points.

    b) Your JavaScript library might be using the global scope. Normally you would need to modify the library code in order to resolve it.

    You may also use a combination of .jspre and .jslib plugins: .jspre for external libraries and .jslib for integration with the build.
     
    Last edited: May 17, 2017
    devluz likes this.
  6. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    327
    This is in fact a very good question, as it is not obvious at first at all. Briefly, there is no contradiction: 2 eval's can be executed in the same outer scope, but at the same time in different local scopes.

    At any moment your JavaScript code is running inside the following nested scopes:
    Code (JavaScript):
    1. function() {
    2.   function() {
    3.     function() {
    4.       /*local scope*/
    5.     }
    6.   }
    7. }
    where all the outer scopes are accessible, but only the inner one is actually local. Each time you declare variables or functions, they are declared in the inner local scope.

    The following example should make it more clear:
    Code (JavaScript):
    1. var expressions = ["function TESTCALL() { return 'Test successful!'; }", "console.log(TESTCALL())"];
    2. for (var i = 0; i < expressions.length; i++) {
    3.   console.log("evaluating: " + expressions[i]);
    4.   eval(expressions[i]);
    5. }
    Screen Shot 2017-05-17 at 19.27.37.png
    but
    Code (JavaScript):
    1. var expressions = ["function TESTCALL() { return 'Test successful!'; }", "console.log(TESTCALL())"];
    2. expressions.forEach(function (expression) {
    3.   console.log("evaluating: " + expression);
    4.   eval(expression);
    5. });
    Screen Shot 2017-05-17 at 19.27.56.png
    So what went wrong here? The reason is that in the latter example, local scope of eval was actually the scope of the inner forEach callback function, and is therefore different for both eval calls.

    This means that the only way to use the local variable or function, declared in the previous local eval call, is not to leave the body of the function where that previous eval was called. This is of course not the case for the current setup, as ExternalEval is external to the module and therefore requires at least one intermediate function call (if you want to perform it synchronously).

    In other words, you can not declare a local variable or function when using ExternalEval, as it will be lost as soon as you return from the external function call. But you can modify already existing objects from the other outer scopes, as those outer scopes will be also accessible from subsequent ExternalEval calls (i.e. using Module.TESTCALL instead of declaring a TESTCALL).

    P.S. Actually, this is not impossible to achieve what you want, it would just be rather impractical. Consider that we are running our build in a JavaScript shell, where we implement our own message loop. Then we can in fact execute evals in the same local scope asynchronously (when running in a browser, this code would block the main thread):
    Code (JavaScript):
    1. (function myScope() {
    2.   var scheduledExpressions = ["console.log('eval simulation started')"];
    3.   function mainLoop () {
    4.     scheduledExpressions.push(prompt("schedule some expression for eval:"));
    5.     // do some rendering
    6.   }
    7.   while (true) {
    8.     while (scheduledExpressions.length) {
    9.       var expression = scheduledExpressions.shift();
    10.       if (!expression)
    11.         return;
    12.       console.log("evaluating: " + expression);
    13.       eval(expression);
    14.     }
    15.     mainLoop();
    16.   }
    17. })();
    Screen Shot 2017-05-17 at 19.28.27.png
     
    Last edited: May 17, 2017
    devluz likes this.
  7. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,157
    @alexsuvorov Sorry for asking but I am curious for such times that, what is jspre? I cannot find document about it. I know it would be javascript file about plugin but don't know what it could do differ from jslib
     
  8. Marco-Trivellato

    Marco-Trivellato

    Unity Technologies

    Joined:
    Jul 9, 2013
    Posts:
    1,654
    It looks like this is not in the manual yet.

    as @alexsuvorov mentioned, it's a feature that allows you to prepend your own JS to the generated build. All you need to do is name the JS file with a jspre extension.
     
  9. kognito1

    kognito1

    Joined:
    Apr 7, 2015
    Posts:
    331
    Super nitpicky, but under "WebGL: Deploying compressed builds" you list only Chrome and Firefox as supporting Brotli compression, but I believe since the creator's update that Edge also supports Brotli compression over https.

    http://caniuse.com/#feat=brotli
     
  10. Marco-Trivellato

    Marco-Trivellato

    Unity Technologies

    Joined:
    Jul 9, 2013
    Posts:
    1,654
    thanks for pointing that out
     
  11. tomekkie2

    tomekkie2

    Joined:
    Jul 6, 2012
    Posts:
    972
    I can not find this new template inside neither 5.6 nor 2017.1 documentation, but only old templates instead.
    Doest anyone know why and when the old templates will get replaced?
     
  12. Marco-Trivellato

    Marco-Trivellato

    Unity Technologies

    Joined:
    Jul 9, 2013
    Posts:
    1,654
    Thaina and tomekkie2 like this.
  13. tolosaoldfan

    tolosaoldfan

    Joined:
    Sep 14, 2012
    Posts:
    92
    Hi,
    we had a few Javascript code in our 5.4 web version, and we have to migrate to 5.6 integration now. It's clear, following your doc, how to manage the function called from Unity, to put it in .jslib file. But how or where to put the jscript functions called by the function called from Unity in the new .jslib file, internally ? I.e :
    Here is the jscript (simplified) used :

    Code (JavaScript):
    1.      
    2. function extractUrlParams()// internal call
    3.             {
    4.                 var t = location.search.substring(1).split('&');
    5.                 var f = [];
    6.                 for(var i=0;i<t.length;i++)
    7.                 {
    8.                     var x = t[i].split('=');
    9.                     f[x[0]] = x[1];
    10.                 }
    11.                 return f;
    12.             }
    13.          
    14.             // called by Unity app - sent by the webplayer when scene is loaded =
    15.             function ReadyToPlay()
    16.             {
    17.                 // launch this part only once (or will be endless looping)
    18.  
    19.                     // if a file was given in the url, send it to play
    20.                     var params = extractUrlParams();
    21.                    .....              
    22.             }
     
  14. Marco-Trivellato

    Marco-Trivellato

    Unity Technologies

    Joined:
    Jul 9, 2013
    Posts:
    1,654
    IIRC a similar question was asked before. Anyway, I suggest to look at the built-in jslibs in PlaybackEngines/WebGLSupport/BuildTools/lib

    in particular, FileSystem.js shows how to define a function inside an object of the library, which can then be called by library functions.

    Hope that makes sense.
     
  15. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    1,157
    Since 2017, are there any place in unity document describe jspre ?

    I tried to search for jspre and it only mention its name only 2 times in document