← Back to context

Comment by abendstolz

3 months ago

Very cool!

But I prefer the wasmtime webassembly component model approach these days.

Built a plugin system with that, which has one major upside in my book:

No stringly function invocation.

Instead of run_function("my-function-with-typo") I have a instantiated_plugin.my_function call, where I can be sure that if the plugin has been instantiated, it does have that function.

This sounds like a good approach to overcoming rusts slow compile times and lack of dynamic linking. One thing I'm concerned about with this path is what about hot reloading and fast script running? Doesn't everything in the wasm component model need to be compiled first? I imagine that would remove some of the advantages to using a scripting language like JavaScript or Python.

  • You're right. Hot reloading isn't done by default.

    I manually compile a plugin and in my system I can "refresh" a plugin and even say "activate version 1.1 of the plugin" or "activate version 1.2" of the plugin etc.

    But that's something I had to build myself and is not built into wasmtime itself.

Alternatively, whenever designing a scripting/plugin host make sure to support plugin-hosting-plugins. That way you could have the Roto plugin host complied to wasm.

  • Sounds interesting but at the same time a bit complex.

    I assume you wouldn't ship the whole plugin runtime for each plugin that wants to host another plugin?!

    • It's not that complex. You basically want to have all plugins support describing themselves (as a list of plugins, which is nearly always 1 item), and "activating" themselves according to one of their descriptors. Bonus points for deactivation for updates (this is often extremely hard to pull off because of how instrusive plugins can be). You could then have a utility header file or whatnot that does the [very minor] plumbing to make the plugin API behave like a single plugin.

      Shipping the runtime is another good option, because it means you don't have to worry about runtime versioning.

How is that not also stringly typed?

  •                 match Plugin::instantiate_async(&mut store, &component, &linker).await {
                        Ok(plugin) => {
                            match plugin
                                .plugin_guest_oncallback()
                                .call_ontimedcallback(&mut store, &callback_name)
                                .await
                            {
                                Ok(()) => debug!("Successfully called oncallback for {plugin_path:?}"),
                                Err(e) => warn!("Failed to call oncallback for {plugin_path:?}: {e}"),
                            }
                        }
                        Err(e) => {
                            error!("Failed to call oncallback for {plugin_path:?}!: {e}");
                        }
                    }
    

    See the "call_ontimedcallback"? It's not a string. The compiler ensures it exists on the Plugin type generated from the .wit file.

    If of course I put a wasm file in the plugin folder that doesn't adhere to that definition, that wasm file isn't considered a plugin.

    • Thanks for using wasmtime! I worked on the component bindings generator you’re using and it’s really nice to see it out in the wild.

      To elaborate a bit further: wasmtime ships a [bindings generator proc macro](https://docs.wasmtime.dev/api/wasmtime/component/macro.bindg...) that takes a wit and emits all the code wasmtime requires to load a component and use it through those wit interfaces. It doesn’t just check the loaded component for the string names present: it also type checks that all of the types in the component match those given by the wit. So, when you call the export functions above, you can be quite sure all of the bindings for their arguments, and any functions and types they import, all match up to Rust types. And your component can be implemented in any language!

      3 replies →

    • Ah, fair enough. So it is still stringly typed, it's just verified at compile time. Which I guess is true about all compiled functions ever.