Reputation: 15365
I have some JavaScript data which I am serializing so that Unity can consume it as a byte[] array, as generally described here. I want to expose this data to Unity via a jslib interface like this:
[DllImport("__Internal")]
private static extern byte[] GetByteArray();
I attempted to bridge my values from JavaScript using the built-in methods in emscripten:
GetByteArray: function () {
var myTypedArray = new Uint8Array([10, 20, 30, 40, 50]);
var buf = _malloc(myTypedArray.length * myTypedArray.BYTES_PER_ELEMENT);
writeArrayToMemory(myTypedArray, buf);
return buf;
},
Unfortunately, this does not work -- the webassembly C# just sees a 0-length array as the return type. I also tried stuff like HEAPU8.set(myTypedArray, buf)
.
Questions:
Upvotes: 2
Views: 1856
Reputation: 704
One approach that I've used when I want as little trouble as possible is to save the data as a file in the emscripten in-memory file system and then read it back on C# side. Haven't measure the performance impact but because it's in-memory in theory it's quite performant.
Something like this:
FS.writeFile('/tmp/myfile', data);
Note that FS
is a global variable only reachable from within the JSLib file (which ends up i webgl.framework.js
when built in Unity). Whether it's in-memory filesystem depends on the path, see details here
var data = File.ReadAllBytes("/tmp/myfile");
Upvotes: 0
Reputation: 580
The secret is to have a method to instantiate the byte array and call it from the js context. The method that instantiates the array then calls the js method that actually fills the byte array. It's a 2 step process.
C# side
[DllImport("__Internal", EntryPoint = "getByteArrayTmp")]
private static extern void getByteArrayTmp(Action<int> instantiateByteArrayCallback);
[DllImport("__Internal", EntryPoint = "getByteArray")]
private static extern void getByteArray(Action callback, byte[] byteArray);
private static event Action callbackEvent;
public static byte[] byteArray;
[MonoPInvokeCallback(typeof(Action))]
private static void callback() {
callbackEvent?.Invoke();
callbackEvent = null;
}
[MonoPInvokeCallback(typeof(Action<int>))]
private static void instantiateByteArrayCallback(int size) {
byteArray = new byte[size];
getByteArray(callback, byteArray);
}
The method that is actually called by the user is
public static void getByteArray(Action callback) {
#if UNITY_WEBGL && !UNITY_EDITOR
callbackEvent = null;
callbackEvent += callback;
getByteArrayTmp(instantiateByteArrayCallback);
#endif
}
On the js side of things
getByteArrayTmp: function(instantiateByteArrayCallback) {
const byteArray = new Uint8Array([21, 31]);//example
Module["yourplugin"].byteArray = byteArray;
Module.dynCall_vi(instantiateByteArrayCallback, byteArray.length);
},
getByteArray: function(callback, byteArray) {
Module.HEAPU8.set(Module["yourplugin"].byteArray, byteArray);
Module.dynCall_v(callback);
},
This is several times faster than using a string as an intermediate. You can transfer over 20MB from js to Unity in less than a second like this, while using the string method it takes several seconds.
Upvotes: 0
Reputation: 2442
I hope you found a solution to your issue. Here's my (really hacky solution) but it works :
jslib
GetByteArray: function(){
var array = new Uint8Array([10, 20, 30, 40, 50]);
var str = array.toString();
str = '[' + str + ']';
unityInstance.sendMessage('gameObject', 'ByteArrayAvailable', str);
}
c#
#if UNITY_WEBGL && !UNITY_EDITOR
[DllImport("__Internal")]
private static extern void GetByteArray();
#endif
...
void CallWebGL()
{
GetByteArray();
}
void ByteArrayAvailable(string byteArrayStr)
{
var byteArray = JsonConvert.DeserializeObject<byte[]>(byteArrayStr);
}
You need to add Newtonsoft.Json.dll to you plugins folder to make it work but that's the only easy way I found to make it work :)
Upvotes: 1