Skip to content

Mutating the same TypedArray on either side of the JS or WASM fence #850

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
michaeltheory opened this issue Sep 19, 2019 · 10 comments
Closed
Labels

Comments

@michaeltheory
Copy link

michaeltheory commented Sep 19, 2019

Hi,

Thanks for all the work on AssemblyScript -- the tooling is fantastic.

I'm aware there are other issues that ask a similar question, but unfortunately I couldn't find a specific answer, so I'm hoping for some clarification.

My search started in this thread but what I'm finding hard to follow is that OP seems to be trying to take two steps at once, both sharing memory and packing different data sets into it.

I did notice this comment which appears to be exactly what I want to do. An array that can be mutated and read from on either side WASM/JS.

I followed some links to this repo which sort of worked after I used what appeared to be the new API in assemblyscript/lib/loader with __allocArray and __get(X)Array instead. The issue here though is that __get appears to be creating a new typed array, or atleast view of it. I also have to call __get in JS every time the data has been mutated in AS, which isn't possible in my use case.

The most promising avenue I found was this pull request but I wanted to ask this before trying to piece that together, because it seems like the API might have changed in the last year since those questions were asked.

My use case is I have a 3D engine and I want to do the expensive work in WASM. For example, A Vector3 class has a Float32Array with 3 floats. I would like to mutate it in JS, but then allow AS to have a reference to and be able to use those 3 floats in computation.

Could someone guide me to the best solution?

@dcodeIO
Copy link
Member

dcodeIO commented Sep 19, 2019

  • __getArrayView
  • __getFloat32Array etc.

returns a typed array view on the Wasm memory that can be mutated on both sides as long as the respective managed object is alive and has not been garbage collected (i.e. simply make sure it remains referenced somewhere on the Wasm side), while

  • __getArray

does copy from Wasm memory to a new JS array. The reason for having both versions is that a normal Array<T> on the Wasm side can grow its backing buffer automatically, for example when .pushing to it, and any previously obtained view would become invalidated in this case (hence copying makes sense if that can happen).

Using __getFloat32Array is slightly more efficient because, if the type of the array is known, it can skip the runtime checks __getArrayView would otherwise do to return a view of the respective type.

@michaeltheory
Copy link
Author

Got it, thank you. Just to post my findings for anyone else that stumbles on this. It's really simple:

AS:

export function createFloat32Array(length: i32): Float32Array {
    let array = new Float32Array(length);
    //store a reference to array somewhere
    return array;
}

JS:
let array = lib.__getFloat32Array(lib.createFloat32Array(3));

@dcodeIO
Copy link
Member

dcodeIO commented Sep 19, 2019

For completeness, instead of storing a reference, one can also trick the GC:

export function createFloat32Array(length: i32): Float32Array {
    let array = new Float32Array(length);
    __retain(changetype<usize>(array));
    return array;
}

so the object has always one excess reference recorded. On the JS side one could then __release(theArray) once it is not needed anymore, allowing it to become garbage collected.

@dcodeIO
Copy link
Member

dcodeIO commented Sep 19, 2019

Similarly, arrays can also be allocated on the JS side from an existing array of values:

// wasm
export const Float32Array_ID = idof<Float32Array>();
// js
let array = module.__retain(module.__allocArray(module.Float32Array_ID, [1, 2, 3, 4]));
// do something with array
...
// once not needed anymore:
module.__release(array);

@michaeltheory michaeltheory reopened this Sep 20, 2019
@michaeltheory
Copy link
Author

I may have spoken too soon. Here's where I'm at:

export function createFloat32Array(length: i32): Float32Array {
    let array = new Float32Array(length);
    return array;
}

export function modifyArray(array: Float32Array):void {
    array[2] = 7;
}

JS Side:

let array = _lib.__getFloat32Array(_lib.createFloat32Array(3)); //returns [0, 0, 0]
_lib.modifyArray(array); //should mutate array to [0, 0, 7], but remains [0, 0, 0] in JS

Is what I'm trying to do possible?

@dcodeIO
Copy link
Member

dcodeIO commented Sep 20, 2019

This is calling _lib.modifiyArray with a JS object (the view) instead of the pointer. It should be called with the return of _lib.createFloat32Array.

@michaeltheory
Copy link
Author

michaeltheory commented Sep 20, 2019

I tried that as well, but same result

let ptr = _lib.createFloat32Array(3);
let array = _lib.__getFloat32Array(ptr);
_lib.modifyArray(ptr);

If I move __getFloat32Array after modifyArray, it works of course, but that means any time the array is mutated in AS JS needs to be notified and to reset it's view, which isn't practical for my 3D engine scenario

@dcodeIO
Copy link
Member

dcodeIO commented Sep 20, 2019

Strange, this should work. Maybe a bug, need to investigate.

@dcodeIO
Copy link
Member

dcodeIO commented Sep 20, 2019

Fix in my PR above :)

@michaeltheory
Copy link
Author

Fantastic, that worked. Thanks!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants