Skip to content

Script protocols using rhai #946

Description

@qdot

Overview

We currently write all protocols is native rust. While fast and keeping with the rest of the server crate, it also means any protocol update requires full rebuilds and releases of the library. As most of our protocols are fairly simple and do not require outside modules or inclusions, they could be replaced with scripts that we can update outside of the library, similar to the device configuration file we already distributed outside of full releases.

https://rhai.rs/ seems like the natural choice for this, in that it:

  • Is already built for working with rust
  • Works with wasm-unknown-unknown architectures
  • Has built in sandboxing and security

Sample Protocol implementation comparison

Rust protocol

use uuid::Uuid;

use crate::device::{
  hardware::{HardwareCommand, HardwareWriteCmd},
  protocol::{ProtocolHandler, generic_protocol_setup},
};
use buttplug_core::errors::ButtplugDeviceError;
use buttplug_server_device_config::Endpoint;

generic_protocol_setup!(Aneros, "aneros");

#[derive(Default)]
pub struct Aneros {}

impl ProtocolHandler for Aneros {
  fn handle_output_vibrate_cmd(
    &self,
    feature_index: u32,
    feature_id: Uuid,
    speed: u32,
  ) -> Result<Vec<HardwareCommand>, ButtplugDeviceError> {
    Ok(vec![
      HardwareWriteCmd::new(
        &[feature_id],
        Endpoint::Tx,
        vec![0xF1 + (feature_index as u8), speed as u8],
        false,
      )
      .into(),
    ])
  }
}

rhai equivalent

// Aneros protocol script.
//
// Port of crates/buttplug_server/src/device/protocol_impl/aneros.rs:
// stateless vibration, one packet per motor, command byte 0xF1 + motor index.

fn metadata() {
  #{ "protocol": "aneros", "api_version": 1 }
}

fn handle_vibrate(index, speed) {
  [
    #{
      "endpoint": "tx",
      "data": [0xF1 + index, speed & 0xff],
    },
  ]
}

Concerns

Handling Protocol Initialization

Several protocols require specialization or possibly hardware communication. Some of these may require native implementation, at least for initialization.

Protocols that Cannot be Converted

Some protocols, including those with complex cryptography functions or requiring bluetooth subscriptions, may not be completely convertible. In these instances, we will leave the protocol implemented in native rust. These are rarely updated anyways, and the gained ease of asynchronous releases are well worth the work otherwise.

Inclusion of Scripts in the Library

Much like we distribute the library with an embedded version of the our device config file, we should be able to fold in and distribute versions of rhai protocol scripts in the base library. Scripts should also be loadable outside of the library, most likely by using versioning definitions within a script file.

Loading at Runtime

We may change our configuration downloads to include both configuration files and protocols. This keeps the protocols and configurations in step with each other, since device configurations depend on protocol implementations.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions