-
Notifications
You must be signed in to change notification settings - Fork 6
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
Browser Runtime Implementation #11
Browser Runtime Implementation #11
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally I would like to share the .js
files between all runtimes.
That would mean that the .js
files use a backend-agnostic "op" mechanism, so instead of
return Deno.core.opSync("op_world_components", this.rid);
they would
return bevyModJsScriptingOpSync("op_world_components", this.rid);
and each backend/runtime would define its own bevyModJsScriptingOpSync
. Do you think this is possible even on wasm, or would we need a different common API betwenn the runtimes?
The common API I'm thinking of entails
- having a way to map "resource ids" to arbitrary host data
- having a way to call synchronous "ops" with arbitrary serialized parameters
- currently values are not represented by resource IDs, but by a
v8::Local::<v8::External>
in an internal field of an object because I wasn't sure whether u32 resource ids were enough. Can this be done in wasm bindgen as well?
- currently values are not represented by resource IDs, but by a
I had vaguely thought about doing something like that. That's definitely preferable to duplicating the JavaScript for both platforms with annoying little tweaks between the two of them.
That should be totally possible. On the browser we'd probably just make use of a global object on the
Yep. wasm_bindgen has it's equivalent pattern for wrapping private data in JavaScript facing, opaque objects. Sounds like we're going in the right direction. I'll keep moving on this then! :) |
72f2f1b
to
74b8ac2
Compare
@jakobhellermann I noticed that we get errors in VSCode because our files aren't recognized as modules, so we can't redeclare the existing variables that are defined in other typescript files. This isn't a problem at runtime because we scope the modules with closures, but VSCode doesn't know that. The only way to fix the error is to add an The issue is that we don't actually support modules. To fix this I went back to using SWC to transpile the JS/TS and added an extra step that transforms the And I like this syntax much more than having to make a function specifically called But I'd like your feedback on that workflow and whether or not you think that's a good solution. On a side-note, this also proves out the concept of rewriting |
74b8ac2
to
6ac75fc
Compare
a3d3da0
to
6ee88cc
Compare
Revert "use deno_ast for typescript transpilation" This reverts commit ae334f5. Use Module Syntax For Scripts WIP Browser Runtime
6ee88cc
to
362bdcb
Compare
I got the browser runtime working with bindings for logging, now all I have left is the ECS binding implementation! Progress Update:
|
Alternatively, what if we replace |
That was my original thought, and in fact I've already gone this route and made it work on native, with an async loader and everything, but the issue was browser support. The browser can't import typescript files, and we can't create a custom module loader like we can with Deno. So I think it ends up being easiest to do whatever will work in the browser, because it's the least common denominator, and anything that we can get to work in the browser we should be able to make work on native, too. Technically we could use a custom loader on native and transpiling hacks on the browser, but it's probably much simpler just to use the same technique for both of them. Also, interestingly, I believe rewriting the So while it seems a little hacky, it may actually be standards compliant. 😄 |
- Share types with native and WASM runtimes. - Avoid errors in console by waiting until script is loaded before running it. - Use more idiomatic approach for calling WASM script functions.
Implements all the remaining ops for the browser runtime, other than op_value_ref_call.
I got I've got a serious code cleanup to do and then this will be ready for review. |
83c9b87
to
aa70660
Compare
aa70660
to
305425d
Compare
OK, this is ready for review. To help you review, here are the high-level changes I made:
It's worth noting that there's a fair bit of code duplication in the implementation of the ops for the browser and the native runtimes, but there are also difference sprinkled throughout so I don't think it's something that can be abstracted away. |
I just realized that I handled value ref lifetimes differently in the browser runtime than you did in the native runtime. If I understand correctly, you garbage collected value refs on the native runtime by hooking into the runtime's finalization handler. Unfortunately, on the browser, we don't have that ability because JS doesn't have destructors yet. :/ To handle this, I just deleted every value ref at the end of every script run on the Rust side, but this doesn't delete the references on the JavaScript side. This mean that you couldn't store a value ref for later. You would have to re-obtain it every frame through querying. This feels somewhat natural because it's not like you're allowed to store references to components across frames in Bevy in Rust anyway, you always have to query to get references to the ECS data. Edit: Oh, but this means that you couldn't store the freestanding results of function calls across frames either ( unless it's a primitive ). :/ We may just have to require that scripts store any component data that must be accessed across frames in a resource or a component. Which again, isn't much different than Bevy, so it might be that bad. Right now, storing a value reference in a global variable that can be access across frames on the JavaScript side will work on native, but it will fail with a descriptive error on WASM, because the references are deleted at the end of every script run. So we'll need to figure out how we want to move forward here. Maybe that is actually fine. And people who only care about native will get a little bit better of an experience, but people who want to work on WASM will have to understand they have to work around that limitation? If we document it well enough, that might be fine. |
Yes, I did that because it was the only way I could find to free the allocated external data after a while and not let it accumulate indefinitely. |
If you’re fine with merging this, we can proceed with our Scripting MVP and begin using it in production 🤘 |
I'll give this a proper review by the end of the week (or maybe sooner if I get around to doing it), and then probably merge it.
|
Cheers. To be clear, I’m stretching the term ‘in production’ here to mean ‘put to use in the ongoing development of a real-world pre-alpha application’, hehe. |
Yeah, "production" here means we're using it in a game, but the game itself is by no means "production ready" anyway. :)
In the web backend I used keys into a Then even arbitrarily generated keys on the JavaScript side will just safely come back |
I gave this PR another review, pushed some small changes (formatting and removing the wasm scripts in favor of |
Yay! 🎉 I'm glad I can help, and thanks so much for your work, too! I'm excited to see how we can progress scripting in Bevy. :D |
This is somewhat work-in-progress, since it's missing a type declaration on the expected return type of the
init()
function, but I wanted to run this by you to see what you think.This moves the current implementation of the JS runtime into it's own module named
native
to distinguish it from thewasm
module which I'll be adding in a follow up PR if you like the look of this.It switches to using an interface for scripts that will be possible to produce the same behavior in the browser, when we don't have access to independent runtimes or
JsRealms
orContext
s.We expect each script to have an
init()
function that runs once when the asset is loaded, and again for each hot-reload. It needs to return an object with a function for every stage that it wants to execute code during.This is allows one script to easily run code in multiple stages.
We now maintain only one runtime, allowing scripts to share a
globalThis
, but I don't think that's really an issue, and it makes it consistent with the browser. It also probably saves us some memory, because we don't create a full-blown runtime for every script.Let me know if this looks like a reasonable direction to keep moving in.
I have the wasm runtime implemented already, I just have to migrate it from the game I was working on, and then I have to implement the ECS bindings, which will be the most of the remaining work to get first-class browser support.