add support for exported classes
I currently look at how we can support classes.
Example:
Assemblyscript
export class example {
constructor(private s: string) {}
public getString() { return this.s }
}
In JS:
const e = new exports.example('answer: 42')
console.log(e.getString()) // "answer: 42"
This in combination with #64 would allow some cool things!
To do that I would wrap the full class in a JS-Class that calls the correspondig function by runtime and loader.
FinalizationRegistry
I want to do the following to get full garbage collection (in a wrapper function for the classes):
const classPtr = new exports.Foo()
exports.__pin(classPtr)
const foo = Foo.wrap(classPtr)
// We only need one of these for all classes
const registry = new FinalizationRegistry(ptr => {
exports.__unpin(ptr)
});
registry.register(foo, classPtr);
This would allow full GarbageCollection for Browsers that support FinalizationRegistry (see https://caniuse.com/mdn-javascript_builtins_finalizationregistry). Without FinalizationRegistry I see no way to add this wrapping without memory leaks (any ideas?).
But in that case the developer can allways call __unpin so I think there is nothing to worry about.
Or instead of wrapping useing a proxy.
Got a small working prototype (all manual but can be automated):
Assemblyscript
export class Foo {
constructor(public str: string) {}
getString(): string {
return this.str
}
}
Host:
import * as AsBind from "/node_modules/as-bind/dist/as-bind.esm.js";
const asyncTask = async () => {
const wasm = await fetch("/build/optimized.wasm").then(v => v.arrayBuffer());
const asBindInstance = await AsBind.instantiate(wasm);
let e = asBindInstance.exports
// When not supported noop
const pointerRegistry = FinalizationRegistry ? new FinalizationRegistry(ptr => {
e.__unpin(ptr)
}) : {register(){}};
function createProxyClass(klass, definition) {
const newConstructor = new Proxy(klass, {
construct(target, args) {
// TODO: wrap args - get from definition
const ptr = new target(...args)
return newConstructor.wrap(ptr)
},
get(_, prop) {
if (prop === 'wrap') {
return (ptr) => {
e.__pin(ptr)
const instance = klass.wrap(ptr)
const a = new Proxy({}, {
ownKeys() {
return Reflect.ownKeys(instance);
},
defineProperty() {
throw new Error("Not allowed!")
},
deleteProperty() {
throw new Error("Not allowed!")
},
get(_, ...args) {
if (prop === '__collect') {
return () => e.__unpin(ptr)
}
const value = Reflect.get(instance, ...args)
// TODO: wrap function - or wrap value
return value
},
set(_, ...args) {
// TODO: check if is settable; wrap value if needed
return Reflect.set(instance, ...args)
},
setPrototypeOf() {
throw new Error("Not allowed!")
},
preventExtensions() {
throw new Error("Not allowed!")
},
isExtensible() {
return false;
},
has(_, p) {
return Reflect.has(instance, p)
},
getPrototypeOf() {
throw new Error("Not allowed!")
}
})
// handle GC
pointerRegistry.register(a, ptr)
return a
}
}
return undefined
},
set() {
throw new Error("Not allowed!")
}
})
return newConstructor
}
window.e = e
window.Foo = createProxyClass(e.Foo, {
constructor: {
memberType: "constructor",
parameterTypes: ["~lib/string/String"],
returnType: null
},
str: {
memberType: "field",
set: true,
get: true,
type: "~lib/string/String"
},
getString: {
memberType: "function",
parameterTypes: [],
returnType: "~lib/string/String"
}
})
// example usage
const sptr = e.__newString("test")
const f = new Foo(sptr)
console.log(e.__getString(f.getString())) // "test"
const sptr2 = e.__newString("test 42")
f.str = sptr2
console.log(e.__getString(f.getString())) // "test 42"
};
asyncTask();
@torch2424 If this looks good to you I will integrate it. :D
@mathe42 Thank you very much for drafting up this implementation! 😄 Yeah! I like this approach and it makes sense to me, so let's go for it! 😄