We want to have an avenue for extensibility in Noosphere across multiple domains, both for internal development and user configuration, including:
Decoders: We want to support rich content types in Noosphere (https://github.com/subconsciousnetwork/noosphere/issues/50) beyond text/subtext. While we want to support common types out of the box, we want to support 3P decoders, or lazily-load 1P niche decoders. This interface could include ways of rendering content as an image, or provide interaction for a 3D model, or extracting slashlinks from text.
Geists: We want to have a way of loading internal, 1P and 3P geists into a sphere.
Backwards Compat: We could dynamically load older versions of Noosphere to handle older sphere data.
These "plugins" would run inside of libnoosphere, potentially running on a desktop, in Subconscious, or in gateway servers. Broadly, this is a good fit for WASM: Well-defined interfaces for modules running in a sandbox.
Goals
Load WASM modules on iOS (Subconscious) and Web (Sphere Viewer) and Linux (Gateway).
Support running multiple plugin domains, each with multiple modules.
Modules should be able to be dynamically loaded at runtime.
Example Noosphere Interface
Illustrative FFI to support decoders as a global plugin. Geists, for example, may be better scoped per sphere.
// Load a decoder module from `bytes`. Returns
// the CID of the decoder.
char* ns_decoders_load(
ns_noosphere_t const* noosphere,
slice_ref_uint8_t bytes,
ns_error_t** error_out);
// Load a decoder module from `cid`. Returns
// the CID of the decoder
char* ns_decoders_load_from_cid(
ns_noosphere_t const* noosphere,
char* cid,
ns_error_t** error_out);
// Get an array of all currently loaded decoders as CIDs.
slice_boxed_char_ptr_t ns_decoders_list(
ns_noosphere_t const* noosphere,
ns_error_t** error_out);
// Gets the name of a decoder(?) or other meta.
char* ns_decoders_get(
ns_noosphere_t const* noosphere,
char* cid,
ns_error_t** error_out);
// Unloads a decoder.
void ns_decoders_unload(
ns_noosphere_t const* noosphere,
char* cid,
ns_error_t** error_out);
With modules being well-known at compile time, arguably running these modules does not fundamentally change features (e.g. a new decoder type), and more restricted than existing scripting apps.
Alternatively:
Restrict modules by CID/1P to reduce scope if needed for App Store.
Precompile/AOT modules for App Store
Allow gateways to load plugins on behalf of owner e.g. /api/v1/load_decoder/bafy4..
Hook directly into JavascriptCore's WASM runtime? rusty-jsc crate
Implementation
Ideally, we'd have a reusable way of hooking into a plugin definition and building rust components around in within noosphere. Something like:
In lieu of some macro work, an earlier checkpoint may just wrap an underlying wasm engine with well known interfaces:
let module = engine.load_by_cid(cid)?; // load a markdown decoder
engine.call<(&Sphere), Vec<Slashlink>>("get_slashlinks", &sphere);
Component Model
The Web Assembly Components Model proposal defines ways of modularizing WASM artifacts and enabling them to link to each other. A related project, wit-bindgen, enables guest (e.g. decoder) developers to generate bindings from a IDL-like .wit file. Only wasmtime appears to support the component model out of the box.
While components seem preferable for plugin developers (e.g. someone writing a geist or decoder), and allow us to distribute some companion tools (e.g. a wit IDL), they're only implemented currently in wasmtime, and still only a (promising) proposal.
Runtimes
Potential runtimes and their support for rust bindings, the component model, and different execution modes. Some runtimes have support for "web"/"JS", but not quite in the environment we're looking for -- we want libnoosphere as wasm natively loading another wasm artifact, rather than our plugins being ingested by JS/node directly.
IMO broadly, these runtimes are designed for edge computing, rather than embedding
In terms of integration, features, and ecosystem, wasmtime is my preference. Though due to the JIT, may not satisfy iOS restrictions.
Component model is great for our use case, but not necessary, and unfortunately rather new. Tooling is promising, but mostly limited to wasmtime for now. We aren't interested in linking multiple modules, as plugins are rather standalone, and the main appeal of components here are standardized IDL descriptions and the corresponding tooling/bindgens.
Due to underlying typed interface of wasm modules, it's non-trivial wiring up multiple runtimes, and a challenge to propagate something like foo.call<(i32, i32), i32>("foo") across multiple runtimes.
wasmtime seems to be the most feature complete runtime with a close relationship with corresponding spec writers. With iOS JIT and dynamic loading restrictions, there's still no guarantee that building out an interpreted runtime will pass Apple's muster, and some workarounds are palatable. wasmedge may provide support for all platforms we want, but no component model support (and preferred wasmtime's integration exp so far).
For now, moving forward with wasmtime as non-components so that we can attempt to support other runtimes (iOS/Web) in the future without requiring component support, and have some implementation for iOS that may be rejected by the app store, but can start a conversation, and at least have something like a decoder implementation running via CLI/gateways as we explore the extensible interface.
We want to have an avenue for extensibility in Noosphere across multiple domains, both for internal development and user configuration, including:
text/subtext
. While we want to support common types out of the box, we want to support 3P decoders, or lazily-load 1P niche decoders. This interface could include ways of rendering content as an image, or provide interaction for a 3D model, or extracting slashlinks from text.These "plugins" would run inside of libnoosphere, potentially running on a desktop, in Subconscious, or in gateway servers. Broadly, this is a good fit for WASM: Well-defined interfaces for modules running in a sandbox.
Goals
Example Noosphere Interface
Illustrative FFI to support decoders as a global plugin. Geists, for example, may be better scoped per sphere.
iOS
With modules being well-known at compile time, arguably running these modules does not fundamentally change features (e.g. a new decoder type), and more restricted than existing scripting apps.
Alternatively:
/api/v1/load_decoder/bafy4..
Implementation
Ideally, we'd have a reusable way of hooking into a plugin definition and building rust components around in within noosphere. Something like:
In lieu of some macro work, an earlier checkpoint may just wrap an underlying wasm engine with well known interfaces:
Component Model
The Web Assembly Components Model proposal defines ways of modularizing WASM artifacts and enabling them to link to each other. A related project, wit-bindgen, enables guest (e.g. decoder) developers to generate bindings from a IDL-like .wit file. Only wasmtime appears to support the component model out of the box.
While components seem preferable for plugin developers (e.g. someone writing a geist or decoder), and allow us to distribute some companion tools (e.g. a wit IDL), they're only implemented currently in wasmtime, and still only a (promising) proposal.
Runtimes
Potential runtimes and their support for rust bindings, the component model, and different execution modes. Some runtimes have support for "web"/"JS", but not quite in the environment we're looking for -- we want libnoosphere as wasm natively loading another wasm artifact, rather than our plugins being ingested by JS/node directly.
Result
foo.call<(i32, i32), i32>("foo")
across multiple runtimes.wasmtime seems to be the most feature complete runtime with a close relationship with corresponding spec writers. With iOS JIT and dynamic loading restrictions, there's still no guarantee that building out an interpreted runtime will pass Apple's muster, and some workarounds are palatable. wasmedge may provide support for all platforms we want, but no component model support (and preferred wasmtime's integration exp so far).
For now, moving forward with wasmtime as non-components so that we can attempt to support other runtimes (iOS/Web) in the future without requiring component support, and have some implementation for iOS that may be rejected by the app store, but can start a conversation, and at least have something like a decoder implementation running via CLI/gateways as we explore the extensible interface.