Starlight-JS / starlight

JS engine in Rust
https://teletype.in/@starlight-js
Mozilla Public License 2.0
509 stars 9 forks source link

Register custom Class #113

Open jameslahm opened 3 years ago

jameslahm commented 3 years ago

We could expose an API, for example, register_class to register a custom class in the global object.

andrieshiemstra commented 3 years ago

Would be very helpfull to me also for registering classes like Request and Response for fetch api

playXE commented 3 years ago

Yeah it would be nice. I think we need lots of APIs to make it easy to use Starlight. Like add builder like API for adding new properties to objects or easier API to declare accessors and methods on the objects.

jameslahm commented 3 years ago

Yeah it would be nice. I think we need lots of APIs to make it easy to use Starlight. Like add builder like API for adding new properties to objects or easier API to declare accessors and methods on the objects.

I am going to work on this. 🤣

jameslahm commented 3 years ago

We now support register custom class, and also snapshot for custom class

use std::mem::ManuallyDrop;

use starlight::{
    def_native_method, def_native_property, define_jsclass,
    jsrt::VM_NATIVE_REFERENCES,
    prelude::*,
    prelude::{JsClass, Options},
    vm::context::Context,
    Platform,
};

pub struct Person {
    age: i32,
}

pub fn person_constructor(ctx: GcPointer<Context>, args: &Arguments) -> Result<JsValue, JsValue> {
    let mut res = 0;
    if args.size() != 0 {
        res = args.at(0).to_int32(ctx)?;
    }
    Ok(JsValue::new(Person::new(ctx, res)))
}

impl Person {
    pub fn new(ctx: GcPointer<Context>, age: i32) -> GcPointer<JsObject> {
        let structure = ctx.global_data().get_structure("Person".intern()).unwrap();
        let obj = JsObject::new(ctx, &structure, Self::class(), ObjectTag::Ordinary);
        *obj.data::<Self>() = ManuallyDrop::new(Self { age });
        obj
    }

    pub fn say_hello(ctx: GcPointer<Context>, args: &Arguments) -> Result<JsValue, JsValue> {
        let mut obj = args.this.to_object(ctx).unwrap();
        let person = obj.as_data::<Person>();
        println!("Hello {}", person.age);
        Ok(JsValue::UNDEFINED)
    }
}

impl JsClass for Person {
    fn class() -> &'static starlight::prelude::Class {
        define_jsclass!(Person, Person)
    }

    fn init(mut ctx: GcPointer<Context>) -> Result<(), JsValue> {
        let obj_proto = ctx.global_data().get_object_prototype();
        let structure = Structure::new_unique_indexed(ctx, Some(obj_proto), false);
        let mut proto = JsObject::new(ctx, &structure, Self::class(), ObjectTag::Ordinary);

        let structure = Structure::new_indexed(ctx, Some(proto), false);
        let mut constructor = JsNativeFunction::new(ctx, "Person".intern(), person_constructor, 1);

        def_native_property!(ctx, constructor, prototype, proto)?;
        def_native_property!(ctx, proto, constructor, constructor)?;
        def_native_method!(ctx, proto, sayHello, Person::say_hello, 0)?;

        ctx.register_structure("Person".intern(), structure);

        let mut global_object = ctx.global_object();
        def_native_property!(ctx, global_object, Person, constructor)?;

        Ok(())
    }
}

fn main() {
    Platform::initialize();
    let mut runtime = Platform::new_runtime(Options::default(), None);
    let mut ctx = runtime.new_context();

    ctx.register_class::<Person>().unwrap();

    match ctx.eval("let person = new Person(10);person.sayHello()") {
        Err(e) => {
            println!("{}", e.to_string(ctx).unwrap());
        }
        _ => {}
    }

    unsafe {
        VM_NATIVE_REFERENCES.push(Person::say_hello as _);
        VM_NATIVE_REFERENCES.push(person_constructor as _);
        VM_NATIVE_REFERENCES.push(Person::class() as *const _ as _);
    }

    let buf = Snapshot::take_context(false, &mut runtime, ctx, |_, _| {}).buffer;
    let mut ctx = Deserializer::deserialize_context(&mut runtime, false, &buf);
    match ctx.eval("let person = new Person(10);person.sayHello()") {
        Err(e) => {
            println!("{}", e.to_string(ctx).unwrap());
        }
        _ => {}
    }
}
jameslahm commented 3 years ago

but it's still a little complex now. I am going to make it simpler.

andrieshiemstra commented 3 years ago

Please also consider how to remove a custom class.

E.g. when registering a custom class for a native module, e.g. import('my_native_mysql_con') i want to register custom Connection/Resultset classes but i also want to remove them when the module is no longer needed.

Keep up the great work so far!

jameslahm commented 3 years ago

Please also consider how to remove a custom class.

E.g. when registering a custom class for a native module, e.g. import('my_native_mysql_con') i want to register custom Connection/Resultset classes but i also want to remove them when the module is no longer needed.

Keep up the great work so far!

That'll be wonderful. I'll try ~

jameslahm commented 3 years ago

It's very simple to register custom class now

pub struct Person {
    age: i32,
}

impl ClassConstructor for Person {
    fn constructor(
        _ctx: GcPointer<Context>,
        args: &starlight::prelude::Arguments<'_>,
    ) -> Result<Self, JsValue> {
        Ok(Person {
            age: args.at(0).get_int32(),
        })
    }
}

impl Person {
    pub fn say_hello(ctx: GcPointer<Context>, args: &Arguments) -> Result<JsValue, JsValue> {
        let mut obj = args.this.to_object(ctx).unwrap();
        let person = obj.as_data::<Person>();
        println!("Hello {}", person.age);
        Ok(JsValue::UNDEFINED)
    }
}

impl JsClass for Person {
    fn class() -> &'static starlight::prelude::Class {
        define_jsclass!(Person, Person)
    }

    fn init(builder: &mut ClassBuilder) -> Result<(), JsValue> {
        builder.method("sayHello", Person::say_hello, 0)?;
        Ok(())
    }
}

fn main() {
    Platform::initialize();
    let mut runtime = Platform::new_runtime(Options::default(), None);
    let mut ctx = runtime.new_context();

    ctx.register_class::<Person>().unwrap();

    match ctx.eval("let person = new Person(10);person.sayHello()") {
        Err(e) => {
            println!("{}", e.to_string(ctx).unwrap());
        }
        _ => {}
    }
    unsafe {
        VM_NATIVE_REFERENCES.push(Person::say_hello as _);
    }
    let buf = Snapshot::take_context(false, &mut runtime, ctx, |_, _| {}).buffer;
    let mut ctx = Deserializer::deserialize_context(&mut runtime, false, &buf);
    match ctx.eval("let person = new Person(10);person.sayHello()") {
        Err(e) => {
            println!("{}", e.to_string(ctx).unwrap());
        }
        _ => {}
    }
}