Git Product home page Git Product logo

ruwren's Introduction

Ruwren: Wren bindings for Rust Crates.io docs.rs

Build status: Travis CI

Here is an attempt at making some Rust Wren bindings in a more Rust style. It acts at times pretty transparently (you do have to deal with the slot / foreign API), but should be

  • More typesafe than using the C API directly
  • Shouldn't get in the way of quick execution
  • Should be relatively simple to get started

Including

Just add:

ruwren = "0.4"

to your Cargo.toml.

Main API

Creating a VM

To create a VM, use the VMConfig struct. To create a basic VM that logs it's output to the console, simply call

let vm = VMConfig::new().build();

You can run code by using interpret directly:

vm.interpret("main", r##"System.print("Cool beans!")"##).unwrap();

Which returns a Ok(()) on successful execution, and Err(e) on failure (see VMError for more details).

You can also call code defined in Wren using a FunctionHandle like so:

vm.interpret("main", r##"
class GameEngine {
    static update(delta) {
        System.print(delta)
    }
}
"##).unwrap();
let handle = vm.make_call_handle(FunctionSignature::new_function("update", 1));
vm.execute(|vm| {
    vm.ensure_slots(2);
    vm.get_variable("main", "GameEngine", 0);
    vm.set_slot_double(1, 0.016);
});
vm.call_handle(&handle);

or more directly:

vm.interpret("main", r##"
class GameEngine {
    static update(delta) {
        System.print(delta)
    }
}
"##).unwrap();
vm.execute(|vm| {
    vm.ensure_slots(2);
    vm.get_variable("main", "GameEngine", 0);
    vm.set_slot_double(1, 0.016);
});
vm.call(FunctionSignature::new_function("update", 1));

Embedding Rust code in Wren

Here's a short example of how you can embed your Rust data into Wren:

use ruwren::{Class, VM, VMConfig, ModuleLibrary, get_slot_checked, create_module};
struct Foo {
    bar: f64,
}

impl Class for Foo {
    fn initialize(vm: &VM) -> Self {
        let bar = get_slot_checked!(vm => num 1);
        Foo { bar }
    }
}

impl Foo {
    fn instance(&self, vm: &VM) {
        vm.set_slot_double(0, self.bar);
    }

    fn static_fn(vm: &VM) {
        let num = get_slot_checked!(vm => num 1);
        vm.set_slot_double(0, num + 5.0)
    }
}

create_module! {
    class("Foo") crate::Foo => foo {
        instance(fn "instance", 0) instance,
        static(fn "static_fn", 1) static_fn
    }

    module => foobar
}

fn main() {
    let mut lib = ModuleLibrary::new();
    foobar::publish_module(&mut lib);

    let vm = VMConfig::new().library(&lib).build();
    vm.interpret("foobar", r##"
    foreign class Foo {
        construct new(bar) {}
        foreign instance()
        foreign static static_fn(num)
    }
    "##).unwrap();

    // You could now write Wren code like:

    vm.interpret("main", r##"
    import "foobar" for Foo
    var f = Foo.new(4)
    System.print(Foo.static_fn(f.instance()))
    "##).unwrap();

    // This should print "9".
}

V2 Foreign

V2 foreigns emulate Wren's class system on top of the original foreign API, so the above example would be:

use ruwren::{wren_impl, wren_module, ModuleLibrary, VMConfig, WrenObject};
#[derive(WrenObject, Default)]
struct Foo {
    bar: f64,
}

#[wren_impl]
impl Foo {
    /*
    you can also write out an allocator, if you
    don't want the base struct itself to implement Default

    #[wren_impl(allocator)]
    fn allocator() -> FooClass {
        FooClass {}
    }
    */

    #[wren_impl(constructor)]
    fn constructor(&self, bar: f64) -> FooInstance {
        FooInstance { bar }
    }

    #[wren_impl(instance)]
    fn instance(&self) -> f64 {
        self.bar
    }

    fn static_fn(&self, num: f64) -> f64 {
        num + 5.0
    }
}

wren_module! {
    mod foobar {
        pub crate::Foo;
    }
}

fn main() {
    let mut lib = ModuleLibrary::new();
    foobar::publish_module(&mut lib);

    let vm = VMConfig::new().library(&lib).build();
    vm.interpret("foobar", r##"
    foreign class Foo {
        construct new(bar) {}
        foreign instance()
        foreign static static_fn(num)
    }
    "##).unwrap();

    // You could now write Wren code like:

    vm.interpret("main", r##"
    import "foobar" for Foo
    var f = Foo.new(4)
    System.print(Foo.static_fn(f.instance()))
    "##).unwrap();

    // This should print "9".
}

About WASM Compilation

It technically works as long as you target WASI, and have a WASI SDK setup somewhere. look at the justfile or example-wasi.nu for the environment variables to set to get it running. There is one big caveat tho:

WASM (even with WASI) is a panic=abort platform, so catch_unwind does nothing, and panics are unhandleable.

This means that some idioms of the v1 foreign API (namely get_slot_checked!) are not very good on the platform. Basically, anything that panics doesn't work well.

With a minimal change the the v2 foreign API (namely, having the constructor be fallible) means that v2 should work relatively unchanged on web, and v1 is usable, it just shouldn't trigger a panic, or the wasm runtime will flip the table.

ruwren's People

Contributors

boringcactus avatar darmie avatar definitelynobody avatar jengamon avatar khn190 avatar pac85 avatar vangroan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

ruwren's Issues

Default module loader?

Besides the module loader always returns None, I'm thinking something like:

pub struct DefaultLoader {
    source_dir: &str,
}
impl DefaultLoader {
    pub fn new() -> DefaultLoader {
        DefaultLoader { source_dir: "." }
    }
    pub fn source_dir(&mut self, source_dir: &str) -> &mut Self {
        self.source = source_dir;
        self
    }
}
impl ModuleScriptLoader for DefaultLoader {
    fn load_script(&mut self, module: String) -> Option<String> {
        use std::fs::File;
        use std::io::Read;
        use std::path::Path;

        // Look for a file named [module].wren.
        let mut name_path = Path::new(self.source_dir).join(&module);
        name_path.set_extension("wren");

        let mut buffer = String::new();

        let result = File::open(&name_path).map(|mut f| f.read_to_string(&mut buffer));
        if result.is_ok() {
            return Some(buffer);
        } else {
            None
        }
    }
}

It loads wren scripts from source_dir in hierarchy. So you can do:

main.wren
a.wren
    - lib/
      b.wren

in main.wren:

import "a"
import "lib/b"

Except the part whether using relative current working directory as default. Or more useful, it should load from multiple source directories. Is it good to add in, or we prefer not?

How do I access a function handle in a module

The example showed how to access a function handle inside a class, while how do I access a function directly inside a module - if it's possible?

// main.wren
var start = Fn.new {}
var update = Fn.new {}

I could check if they exists using has_variable for main module; then how do I use make_call_handle to access the functions? The slot id also is confusing for me, I didn't find any instruction how they are used from wren official.

get_map_count() returns inconsistent values

Each time I call vm.get_map_count() I get random number like Some(712005920) but I am expecting 1 as value, because the value is expected to be {"sum": 3}

I have a wren class like this

class Component {
    static process(input){
        if(input["left"] != null && input["right"] != null){
            var sum = input["left"] + input["right"]
            var result = {"sum": sum}
            my_foreign_api.send(result) // send result to rust backend
        }
    }
}
 fn send(vm: &VM) {
       let count = vm.get_map_count();
       println!("{:?}", count); // <= I need to know the size of the map
}

How do I create a foreign static function for a non-foreign class in rust?

I have a class like this

class MyClass {
  foreign static api(result);
  static call(){
     api("result data") // call the foreign function
  }
}

Is there an API to do something like this?

fn api_function(vm:&VM){
   // do stuff with data
}

/// macro to generate a wren foreign function 
create_foreign_function!{
  static(fn "MyClass.api", 1) api_function
}

Remove any .unwrap() and panic!s

For example:

ffi::CString::new(string).unwrap_or_else(|_| panic!("Failed to convert source to C string for {}", module_name.to_string_lossy())).into_raw()

unwrapping or panicking in runtime.rs is UB as it crosses the C threshold. Remove these and replace with some kind of defaulting.

Rethink current static impl

Are statics as they are currently implemented useful?

They are not allowed to access the instance they are defined on, leaving them without any space to store data between calls, whiel in Wren you have the class that can take data.

Should we reimplement statics to work exactly how instance functions work? (Thus statics simply becoming a difference in how to access from the Wren side rather than anything being different on the Rust side.)

Documentation

While a lot of the API is meant to resemble that of the Wren API, it's a good idea to just do a documentation pass of the entire library, mainly because of the "magic macros" that remain currently quite undocumented and maybe a bit obtuse (im sorry i mostly just wanted to get things easy and working, so they dont look the prettiest), and to collate the knowledge that's scattered in the 'examples/' directory.

How is one supposed to return a lists of foreign objects?

set_slot_new_foreign overwrites slot 0, calling set_slot_new_list(0) then set_slot_new_foreign on a free slot(say 1) and then set_list_elment(0, idx, 1) causes a sigsev (apparently set_slot_new_foreign writes a class on slot 0 so it makes sense). Now of course I can create and populate the list in another slot but there seems to be no way of copying it to slot 0 to return it.
Handles, could solve the issue but are only available through VMWrapper, a foreign function only gets a "VM" which doesn't expose a way to get and set handles.

Add Map API

Upstream Wren has added a Map API, so once they bump their version, we should add the Map slot API too, cuz it's convenient.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.