Skip to content

Instantly share code, notes, and snippets.

@piscisaureus
Created August 5, 2019 23:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save piscisaureus/5f1fc257e57cb976ec446eed03fb76f5 to your computer and use it in GitHub Desktop.
Save piscisaureus/5f1fc257e57cb976ec446eed03fb76f5 to your computer and use it in GitHub Desktop.
use std::collections::HashMap;
use std::sync::Mutex;
#[macro_use]
extern crate lazy_static;
struct CoreOp {} // Placeholder.
struct FlatBuffer {} // Placeholder.
struct PinnedBuf {} // Placeholder.
impl FlatBuffer {
fn parse(_blob: &[u8]) -> Self {
unimplemented!()
}
}
// Use a separate trait to give the op a name, because a trait object
// object (dyn Opdispatcher) can't have associated constants.
trait NamedOpDispatcher: OpDispatcher {
const NAME: &'static str;
}
trait OpDispatcher: Send + Sync {
fn dispatch(&self, args: &[u8], buf: Option<PinnedBuf>) -> CoreOp;
}
trait FlatBufferOpDispatcher: Send + Sync {
fn dispatch_flatbuffer(&self, fb: &FlatBuffer, buf: Option<PinnedBuf>) -> CoreOp;
}
impl<T> OpDispatcher for T
where
T: FlatBufferOpDispatcher,
{
fn dispatch(&self, args: &[u8], buf: Option<PinnedBuf>) -> CoreOp {
let fb = FlatBuffer::parse(args); // Or something like that.
return self.dispatch_flatbuffer(&fb, buf);
}
}
fn register_op<D: NamedOpDispatcher + 'static>(d: D) {
// Rather than having a global registry, I think it should be per-isolate.
lazy_static! {
static ref OP_REGISTRY: Mutex<HashMap<&'static str, Box<dyn OpDispatcher>>> =
Mutex::new(HashMap::new());
}
OP_REGISTRY
.lock()
.unwrap()
.entry(D::NAME)
.and_modify(|_| panic!("Op already registered"))
.or_insert(Box::new(d));
}
// Example implements OpDispatcher.
struct BasicOpDispatcherExample;
impl NamedOpDispatcher for BasicOpDispatcherExample {
const NAME: &'static str = "Basic";
}
impl OpDispatcher for BasicOpDispatcherExample {
fn dispatch(&self, _args: &[u8], _buf: Option<PinnedBuf>) -> CoreOp {
unimplemented!()
}
}
// Example implements FlatBufferOpDispatcher.
struct SomeFlatBufferOpDispatcher;
impl NamedOpDispatcher for SomeFlatBufferOpDispatcher {
const NAME: &'static str = "FB";
}
impl FlatBufferOpDispatcher for SomeFlatBufferOpDispatcher {
fn dispatch_flatbuffer(&self, _fb: &FlatBuffer, _buf: Option<PinnedBuf>) -> CoreOp {
unimplemented!()
}
}
fn main() {
register_op(BasicOpDispatcherExample);
register_op(SomeFlatBufferOpDispatcher);
register_op(SomeFlatBufferOpDispatcher); // Crash.
}
@bartlomieju
Copy link

Object.freeze(Deno.core.ops);

I think that would introduce problems for dlopen.

You're right. Then probably some getter/setter structure would make sense. The idea is to prevent users from meddling with Deno.core.ops, but yeah, ability to register additional ops dynamically is required.

@afinch7
Copy link

afinch7 commented Aug 7, 2019

Namespacing maybe?

type OpsObject =  { [namespace: string]: { [name: string]: number } };

class Ops {
  
  private readonly records: OpsObject = {};
  // Cloned version of records that is frozen, so we can avoid cloning records for every access.
  private recordsFrozen: OpsObject = Object.freeze(Object.assign({}, this.records));

  constructor() {}

  get ids(): OpsObject {
    return this.recordsFrozen;
  }
  
  add(namespace: string, name: string, id: number) {
    if (this.records[namespace] === undefined) {
        this.records[namespace] = {};
    }
    this.records[namespace][name] = id;
    // Clone, freeze, and assign records to recordsFrozen.
    this.recordsFrozen = Object.freeze(Object.assign({}, this.records));
  }
}

Deno.core.ops = new Ops();

Object.seal(Deno.core.ops);

// during startup
Deno.core.registerOp = Deno.core.ops.add;

// files.ts
function read(fd: number, buf: Uint8Array): number {
  /* ... */ 
  Deno.core.dispatch(Deno.core.ops.ids.builtin.read, controlbuf, zerobuf);
  /* ... */
}

New dlopen/plugins code would be something like this:

export type PluginCallReturn = Uint8Array | undefined;

export interface PluginOp {
  dispatch(
    data: Uint8Array,
    zeroCopy?: ArrayBufferView
  ): Promise<PluginCallReturn> | PluginCallReturn;
}

class PluginOpImpl implements PluginOp {

   constructor(
     private readonly pluginRid: number,
     private readonly name: string,
  ) {
    // assert op id is present
    assert(Deno.core.ops.ids["plugin_" + this.pluginRid][name] !== undefined);
  }

   dispatch(
    data: Uint8Array,
    zeroCopy?: ArrayBufferView
  ): Promise<PluginCallReturn> | PluginCallReturn {
    const response = Deno.core.dispatch(Deno.core.ops.ids["plugin_" + this.pluginRid][name], data, zeroCopy);
    // handle response(create promise if async)
  }
}

export interface Plugin {
  loadOp(name: string): PluginOp;
}

export class PluginImpl implements Plugin {
  // unique resource identifier for the loaded dynamic lib rust side
  private readonly rid: number;
  private readonly opSet: Set<string> = new Set();

  constructor(libraryPath: string) {
    this.rid = dlOpen(libraryPath);
  }

   loadOp(name: string): PluginOp {
    if (!this.opSet.has(name)) {
      // Ensure op is registed and Deno.core.registerOp gets called with the op id.
      loadPluginOp(this.rid, name);
      this.opSet.add(name);
    }
    let op = new PluginOpImpl(this.rid, name);
    return op;
  }
}

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