rune icon indicating copy to clipboard operation
rune copied to clipboard

Allocate Capabilities Declaratively

Open Michael-F-Bryan opened this issue 3 years ago • 0 comments

At the moment we initialize a capability imperatively by asking for a blank capability of a particular type and then setting its parameters one-by-one before asking for data.

This works, but you suffer from the problem that the runtime doesn't see the full picture and can't know when the setup phase has finished.

@Ge-te mentioned on slack that it may be better to pass a full "capability spec" to the runtime when requesting capabilities.

Proposed Change

I propose altering the Rune-runtime interface.

  // runic-types/src/wasm32/intrinsics.rs

  extern "C" {
+     /// Allocate a new capability based on its JSON spec, returning an ID which can be
+     /// used to refer to the allocated capability.
+     ///
+     /// If the Runtime is unable to create the capability, execution will halt and this
+     /// function won't return.
+     ///
+     /// The `spec` string is only guaranteed to stay valid for the duration of the function 
+     /// call.
+     fn rune_new_capability(spec: *const c_char, spec_len: u32) -> u32;

-     pub fn request_capability(capability_type: u32) -> u32;
-
-     pub fn request_capability_set_param(
-         capability_id: u32,
-         key_ptr: *const u8,
-         key_len: u32,
-         value_ptr: *const u8,
-         value_len: u32,
-         value_type: u32,
-     ) -> u32;
  }

Given this line in the Runefile...

CAPABILITY<U8[3, 640, 480]> some_capability IMAGE --pixel-format rgb --camera front

... the following "capability spec" is a JSON document will be passed to the runtime:

{
  "capability": "IMAGE",
  "output_type": "u8",
  "output_shape": [3, 640, 480],
  "args": {
    "pixel_format": "rgb",
    "camera": "front"
  }
}

Where:

  • "capability" contains one of "IMAGE", "RANDOM", "ACCELEROMETER", "SOUND", "RAW"
  • "output_shape" is the shape of the (row major) multi-dimensional array returned by the capability
  • "output_type" specifies the tensor's element type, using the Rust names (i8, u64, f32, etc.)
  • "args" is a map of key-value pairs where the value can be any arbitrary JSON value and makes sense to the capability

The rune-codegen crate will then compile this down to:

// lib.rs

extern "C" fn _manifest() -> u32 {
  let some_capability: Capability<u8> = Capability::new("{ ... }");

  ...
}

impl<T> Capability<T> {
  fn new(spec: &str) -> Self {
    let id = unsafe { intrinsics::rune_new_capability(spec.as_ptr(), spec.len()) };
    Capability { id }
  }
}

Actions

Implementing this will involve:

  • [ ] Update rune-codegen to serialize the capability spec as JSON (derived from our Runefile) and arrange for it to be passed to the runtime
  • [ ] Create a runic_types::wasm32::Capability<T> type which will call the new host function with this JSON-serialized capability when instantiated
  • [ ] Remove the now-redundant runic_types::Source trait
  • [ ] Update the host function in our runicos/base image so the Rust runtime is able to use the new rune
  • [ ] Updating the host function in our C++ runtime, rune_vm, to use the new method of initializing capabilities
  • [ ] Wire this up to the Flutter app so we can actually create capabilities that read from the camera/accelerometer/whatever
  • [ ] Recompile all Runes and re-upload them to the registry
  • [ ] (optional) Figure out some sort of versioning mechanism so the app can detect when you are using an old/unsupported Rune and emit a human-friendly error instead of failing with an opaque "function not found", possibly using WebAssembly custom sections

Michael-F-Bryan avatar May 14 '21 06:05 Michael-F-Bryan