[oden][oden-js] Rework modules

Damn this is a lot
This commit is contained in:
John Doty 2023-06-24 08:45:39 -07:00
parent aa90cea4a3
commit db8a5f8eed
12 changed files with 280 additions and 105 deletions

View file

@ -1,24 +1,12 @@
use crate::{ use crate::{
callback::new_fn, conversion::RustFunction, Atom, ClassID, Error, Result, Runtime, Value, callback::new_fn, conversion::RustFunction, module::Module, Atom, ClassID, Error, Result,
ValueRef, ValueResult, Runtime, Value, ValueRef, ValueResult,
}; };
use bitflags::bitflags; use bitflags::bitflags;
use oden_js_sys as sys; use oden_js_sys as sys;
use std::ffi::CString; use std::ffi::CString;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
/// Different ways to evaluate JavaScript. See the various `eval` methods on
/// `ContextRef`.
pub enum EvalType {
/// Global code, i.e., just plain old JavaScript running in the most
/// boring, traditional context.
Global,
/// Module code, i.e., code running under the context of an `import`, or
/// referenced in HTML as `type=module`.
Module,
}
bitflags! { bitflags! {
pub struct EvalFlags: u32 { pub struct EvalFlags: u32 {
const NONE = 0; const NONE = 0;
@ -31,8 +19,8 @@ bitflags! {
const STRIP = sys::JS_EVAL_FLAG_STRIP; const STRIP = sys::JS_EVAL_FLAG_STRIP;
/// Compile but do not run. The result is a value with a value type /// Compile but do not run. The result is a value with a value type
/// of `ValueType::Module` or `ValueType::FunctionBytecode`, and /// of `ValueType::FunctionBytecode`, and which can be executed with
/// which can be executed with `value.eval_function`. /// `value.eval_function`.
const COMPILE_ONLY = sys::JS_EVAL_FLAG_COMPILE_ONLY; const COMPILE_ONLY = sys::JS_EVAL_FLAG_COMPILE_ONLY;
/// Don't include the stack frames before this eval in the Error() /// Don't include the stack frames before this eval in the Error()
@ -51,6 +39,10 @@ impl ContextRef {
ContextRef { ctx } ContextRef { ctx }
} }
pub fn get_runtime(&self) -> Runtime {
Runtime::from_raw(unsafe { sys::JS_GetRuntime(self.ctx) })
}
pub fn is_registered_class(&self, id: &ClassID) -> bool { pub fn is_registered_class(&self, id: &ClassID) -> bool {
let id = id.get(); let id = id.get();
let is_registered = unsafe { sys::JS_IsRegisteredClass(sys::JS_GetRuntime(self.ctx), id) }; let is_registered = unsafe { sys::JS_IsRegisteredClass(sys::JS_GetRuntime(self.ctx), id) };
@ -106,30 +98,42 @@ impl ContextRef {
unsafe { sys::JS_EnableBignumExt(self.ctx, if enable { 1 } else { 0 }) } unsafe { sys::JS_EnableBignumExt(self.ctx, if enable { 1 } else { 0 }) }
} }
/// Evaluate the specified JavaScript code as a module. /// Evaluate the specified JavaScript code.
pub fn load_module(&self, input: &str, filename: &str) -> Result<Value> { pub fn eval(&self, input: &str, filename: &str, flags: EvalFlags) -> ValueResult {
let val = self.eval(input, filename, EvalType::Module, EvalFlags::COMPILE_ONLY)?; self.eval_internal(input, filename, sys::JS_EVAL_TYPE_GLOBAL, flags)
assert!(val.is_module());
val.eval_function(self)?;
Ok(val)
} }
/// Evaluate the specified JavaScript code. /// Evaluate the specified JavaScript code as a module.
pub fn eval( pub fn eval_module(&self, input: &str, filename: &str) -> Result<Module> {
let val = self.eval_internal(
input,
filename,
sys::JS_EVAL_TYPE_MODULE,
EvalFlags::COMPILE_ONLY,
)?;
assert!(val.is_module());
unsafe {
// NOTE: This might be stupid but we're trying to
// intentionally leak the value here; since the module
// itself has a lifetime unconstrained by traditional value
// semantics. (This is a weird edge in QuickJS.)
sys::JS_DupValue(self.ctx, val.val);
Ok(Module::from_raw(
sys::JS_VALUE_GET_PTR(val.val) as *mut sys::JSModuleDef,
self.get_runtime(),
))
}
}
fn eval_internal(
&self, &self,
input: &str, input: &str,
filename: &str, filename: &str,
eval_type: EvalType, eval_type: u32,
flags: EvalFlags, flags: EvalFlags,
) -> ValueResult { ) -> ValueResult {
let c_input = CString::new(input)?; let c_input = CString::new(input)?;
let c_filename = CString::new(filename)?; let c_filename = CString::new(filename)?;
let eval_type = match eval_type {
EvalType::Global => sys::JS_EVAL_TYPE_GLOBAL,
EvalType::Module => sys::JS_EVAL_TYPE_MODULE,
};
let flags_bits: i32 = (eval_type | flags.bits).try_into().unwrap(); let flags_bits: i32 = (eval_type | flags.bits).try_into().unwrap();
unsafe { unsafe {
@ -143,6 +147,18 @@ impl ContextRef {
} }
} }
/// Import a module by name.
pub fn import_module(&self, name: &str, base: &str) -> Result<Module> {
let name = CString::new(name)?;
let base = CString::new(base)?;
let module = unsafe { sys::JS_RunModule(self.ctx, name.as_ptr(), base.as_ptr()) };
if module.is_null() {
return Err(self.exception_error());
}
Ok(Module::from_raw(module, self.get_runtime()))
}
/// Construct a new string atom. /// Construct a new string atom.
pub fn new_atom(&self, value: &str) -> Result<Atom> { pub fn new_atom(&self, value: &str) -> Result<Atom> {
let c_value = CString::new(value)?; let c_value = CString::new(value)?;
@ -377,9 +393,7 @@ mod tests {
let rt = Runtime::new(); let rt = Runtime::new();
let ctx = Context::new(rt); let ctx = Context::new(rt);
let val = ctx let val = ctx.eval("1+1", "script", EvalFlags::NONE).unwrap();
.eval("1+1", "script", EvalType::Global, EvalFlags::NONE)
.unwrap();
assert_eq!(String::from("2"), val.to_string(&ctx).unwrap()); assert_eq!(String::from("2"), val.to_string(&ctx).unwrap());
} }
@ -390,7 +404,7 @@ mod tests {
let ctx = Context::new(rt); let ctx = Context::new(rt);
let compiled = ctx let compiled = ctx
.eval("1 + 1", "script", EvalType::Global, EvalFlags::COMPILE_ONLY) .eval("1 + 1", "script", EvalFlags::COMPILE_ONLY)
.expect("Unable to compile code"); .expect("Unable to compile code");
let result = compiled let result = compiled
.eval_function(&ctx) .eval_function(&ctx)
@ -410,9 +424,7 @@ mod tests {
let val = ctx.new_i32(12).unwrap(); let val = ctx.new_i32(12).unwrap();
go.set_property(&ctx, "foo", &val).unwrap(); go.set_property(&ctx, "foo", &val).unwrap();
let result = ctx let result = ctx.eval("foo + 3", "script", EvalFlags::NONE).unwrap();
.eval("foo + 3", "script", EvalType::Global, EvalFlags::NONE)
.unwrap();
assert_eq!(String::from("15"), result.to_string(&ctx).unwrap()); assert_eq!(String::from("15"), result.to_string(&ctx).unwrap());
} }
@ -454,9 +466,7 @@ mod tests {
let val = vr.as_ref().unwrap(); let val = vr.as_ref().unwrap();
go.set_property(&ctx, "val", val).unwrap(); go.set_property(&ctx, "val", val).unwrap();
let result = ctx let result = ctx.eval(expr, "script", EvalFlags::NONE).unwrap();
.eval(expr, "script", EvalType::Global, EvalFlags::NONE)
.unwrap();
assert_eq!(String::from(*expected), result.to_string(&ctx).unwrap()); assert_eq!(String::from(*expected), result.to_string(&ctx).unwrap());
} }
} }
@ -479,12 +489,7 @@ mod tests {
} }
let result = ctx let result = ctx
.eval( .eval("foo().toUpperCase()", "script", EvalFlags::NONE)
"foo().toUpperCase()",
"script",
EvalType::Global,
EvalFlags::NONE,
)
.unwrap(); .unwrap();
assert_eq!(String::from("UNSAFE"), result.to_string(&ctx).unwrap()); assert_eq!(String::from("UNSAFE"), result.to_string(&ctx).unwrap());
} }
@ -493,17 +498,16 @@ mod tests {
fn modules_with_exports() { fn modules_with_exports() {
let ctx = Context::new(Runtime::new()); let ctx = Context::new(Runtime::new());
let module = ctx let module = ctx
.load_module("const foo = 123; export { foo };", "main.js") .eval_module("const foo = 123; export { foo };", "main.js")
.expect("Could not load!"); .expect("Could not load!");
assert_eq!(module.value_type(), ValueType::Module);
let foo = module let foo = module
.get_module_export(&ctx, "foo") .get_export(&ctx, "foo")
.expect("Could not get export"); .expect("Could not get export");
assert_eq!(String::from("123"), foo.to_string(&ctx).unwrap()); assert_eq!(String::from("123"), foo.to_string(&ctx).unwrap());
let bar = module let bar = module
.get_module_export(&ctx, "bar") .get_export(&ctx, "bar")
.expect("Could not get export"); .expect("Could not get export");
assert!(bar.is_undefined()); assert!(bar.is_undefined());
} }

View file

@ -118,6 +118,7 @@ impl TryIntoValue for Error {
Error::RustFunctionError(e) => Err(Error::RustFunctionError(e)), Error::RustFunctionError(e) => Err(Error::RustFunctionError(e)),
Error::Exception(v, d) => Err(Error::Exception(v.dup(ctx), d)), Error::Exception(v, d) => Err(Error::Exception(v.dup(ctx), d)),
Error::OutOfMemory => Err(Error::OutOfMemory), Error::OutOfMemory => Err(Error::OutOfMemory),
Error::IOError(e) => Err(Error::IOError(e)),
} }
} }
} }

View file

@ -13,7 +13,7 @@ mod value;
pub use atom::{Atom, AtomRef}; pub use atom::{Atom, AtomRef};
pub use class::{Class, ClassID}; pub use class::{Class, ClassID};
pub use context::{Context, ContextRef, EvalFlags, EvalType}; pub use context::{Context, ContextRef, EvalFlags};
pub use conversion::*; pub use conversion::*;
pub use runtime::Runtime; pub use runtime::Runtime;
pub use value::{Value, ValueRef, ValueType}; pub use value::{Value, ValueRef, ValueType};
@ -43,6 +43,8 @@ pub enum Error {
Exception(Value, String), Exception(Value, String),
#[error("out of memory")] #[error("out of memory")]
OutOfMemory, OutOfMemory,
#[error("an io error occurred: {0}")]
IOError(std::io::Error),
} }
impl From<NulError> for Error { impl From<NulError> for Error {
@ -51,10 +53,26 @@ impl From<NulError> for Error {
} }
} }
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Error::IOError(e)
}
}
pub type Result<T> = core::result::Result<T, Error>; pub type Result<T> = core::result::Result<T, Error>;
pub type ValueResult = core::result::Result<Value, Error>; pub type ValueResult = core::result::Result<Value, Error>;
pub fn throw_string(context: &ContextRef, message: String) -> sys::JSValue { pub(crate) fn throw_error(context: &ContextRef, error: Error) -> sys::JSValue {
match error {
Error::Exception(v, _) => unsafe {
sys::JS_DupValue(context.ctx, v.val);
sys::JS_Throw(context.ctx, v.val)
},
other => throw_string(context, other.to_string()),
}
}
pub(crate) fn throw_string(context: &ContextRef, message: String) -> sys::JSValue {
let ctx = context.ctx; let ctx = context.ctx;
match context.new_string(&message) { match context.new_string(&message) {
Ok(e) => unsafe { Ok(e) => unsafe {

View file

@ -0,0 +1,53 @@
use super::ModuleRef;
use crate::{throw_error, ContextRef, Error, Result};
use oden_js_sys as sys;
use std::path::Path;
pub enum ModuleSource<'a> {
Native(&'a ModuleRef),
JavaScript(String),
}
pub trait ModuleLoader {
fn load(&mut self, context: &ContextRef, name: &str) -> Result<ModuleSource>;
}
pub struct DefaultModuleLoader {}
impl DefaultModuleLoader {
pub fn new() -> DefaultModuleLoader {
DefaultModuleLoader {}
}
}
impl ModuleLoader for DefaultModuleLoader {
fn load(&mut self, _context: &ContextRef, name: &str) -> Result<ModuleSource> {
// Attempt to open the file.
let path = Path::new(name);
match std::fs::read_to_string(path) {
Ok(str) => Ok(ModuleSource::JavaScript(str)),
Err(e) => Err(Error::IOError(e)),
}
}
}
pub(crate) fn load_module(
context: &ContextRef,
name: &str,
loader: &mut Box<dyn ModuleLoader>,
) -> *mut sys::JSModuleDef {
match loader.load(context, name) {
Ok(ModuleSource::Native(native)) => native.module,
Ok(ModuleSource::JavaScript(js)) => match context.eval_module(&js, name) {
Ok(v) => v.module,
Err(e) => {
throw_error(context, e);
return std::ptr::null_mut();
}
},
Err(e) => {
throw_error(context, e);
return std::ptr::null_mut();
}
}
}

76
oden-js/src/module/mod.rs Normal file
View file

@ -0,0 +1,76 @@
use crate::{ContextRef, Result, Runtime, Value};
use oden_js_sys as sys;
use std::ffi::CString;
use std::ops::{Deref, DerefMut};
pub mod loader;
pub mod native;
#[derive(Debug, Clone)]
pub struct ModuleRef {
module: *mut sys::JSModuleDef,
}
impl ModuleRef {
pub(crate) fn from_raw(module: *mut sys::JSModuleDef) -> ModuleRef {
ModuleRef { module }
}
fn eval_self(&self, context: &ContextRef) -> Result<()> {
let result = unsafe {
let v = sys::JSValue {
u: sys::JSValueUnion {
ptr: self.module as *mut _,
},
tag: sys::JS_TAG_MODULE as i64,
};
sys::JS_DupValue(context.ctx, v);
sys::JS_EvalFunction(context.ctx, v)
};
context.check_exception(result)?;
Ok(())
}
pub fn get_export(&self, context: &ContextRef, export: &str) -> Result<Value> {
self.eval_self(context)?;
let c_value = CString::new(export)?;
unsafe {
context.check_exception(sys::JS_GetModuleExport(
context.ctx,
self.module,
c_value.as_ptr(),
))
}
}
}
pub struct Module {
m: ModuleRef,
#[allow(dead_code)]
runtime: Runtime,
}
impl Module {
pub(crate) fn from_raw(module: *mut sys::JSModuleDef, runtime: Runtime) -> Module {
Module {
m: ModuleRef::from_raw(module),
runtime,
}
}
}
impl Deref for Module {
type Target = ModuleRef;
fn deref(&self) -> &Self::Target {
&self.m
}
}
impl DerefMut for Module {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.m
}
}

View file

@ -1,3 +1,4 @@
use super::Module;
use crate::{ use crate::{
throw_string, Class, ClassID, ContextRef, Error, Result, TryIntoValue, Value, ValueRef, throw_string, Class, ClassID, ContextRef, Error, Result, TryIntoValue, Value, ValueRef,
}; };
@ -186,7 +187,7 @@ pub fn define_native_module<T: NativeModule>(
ctx: &ContextRef, ctx: &ContextRef,
name: &str, name: &str,
module: T, module: T,
) -> Result<()> { ) -> Result<Module> {
let c_name = CString::new(name)?; let c_name = CString::new(name)?;
let m = unsafe { sys::JS_NewCModule(ctx.ctx, c_name.as_ptr(), Some(init_func::<T>)) }; let m = unsafe { sys::JS_NewCModule(ctx.ctx, c_name.as_ptr(), Some(init_func::<T>)) };
if m.is_null() { if m.is_null() {
@ -202,7 +203,7 @@ pub fn define_native_module<T: NativeModule>(
let mut import_meta = ctx.check_exception(unsafe { sys::JS_GetImportMeta(ctx.ctx, m) })?; let mut import_meta = ctx.check_exception(unsafe { sys::JS_GetImportMeta(ctx.ctx, m) })?;
import_meta.set_property(ctx, "native_module", &native_value)?; import_meta.set_property(ctx, "native_module", &native_value)?;
Ok(()) Ok(Module::from_raw(m, ctx.get_runtime()))
} }
struct GenericNativeModule { struct GenericNativeModule {
@ -256,7 +257,7 @@ impl<'ctx> NativeModuleBuilder<'ctx> {
} }
/// Finish constructing the native module. /// Finish constructing the native module.
pub fn build(&self, name: &str) -> Result<()> { pub fn build(&self, name: &str) -> Result<Module> {
let context = self.exports.context; let context = self.exports.context;
let mut exports = Vec::new(); let mut exports = Vec::new();
for export in self.exports.exports.iter() { for export in self.exports.exports.iter() {
@ -298,7 +299,7 @@ mod tests {
.expect("Module load should succeed"); .expect("Module load should succeed");
let js_module = context let js_module = context
.load_module( .eval_module(
r#" r#"
import { foo } from "the_test"; import { foo } from "the_test";
export const my_foo = foo; export const my_foo = foo;
@ -308,7 +309,7 @@ export const my_foo = foo;
.expect("Evaluation of the test script should succeed"); .expect("Evaluation of the test script should succeed");
let my_foo = js_module let my_foo = js_module
.get_module_export(&context, "my_foo") .get_export(&context, "my_foo")
.expect("Retrieving JS export should succeed"); .expect("Retrieving JS export should succeed");
assert_eq!(my_foo.to_string(&context).unwrap(), String::from("23")); assert_eq!(my_foo.to_string(&context).unwrap(), String::from("23"));
@ -328,7 +329,7 @@ export const my_foo = foo;
.expect("Module build should succeed"); .expect("Module build should succeed");
let js_module = context let js_module = context
.load_module( .eval_module(
r#" r#"
import { foo, bar } from "the_test"; import { foo, bar } from "the_test";
export const my_foo = foo + bar; export const my_foo = foo + bar;
@ -338,7 +339,7 @@ export const my_foo = foo + bar;
.expect("Evaluation of the test script should succeed"); .expect("Evaluation of the test script should succeed");
let my_foo = js_module let my_foo = js_module
.get_module_export(&context, "my_foo") .get_export(&context, "my_foo")
.expect("Retrieving JS export should succeed"); .expect("Retrieving JS export should succeed");
assert_eq!(my_foo.to_string(&context).unwrap(), String::from("444")); assert_eq!(my_foo.to_string(&context).unwrap(), String::from("444"));

View file

@ -1,8 +1,29 @@
use crate::module::loader::{load_module, DefaultModuleLoader, ModuleLoader};
use crate::ContextRef;
use oden_js_sys as sys; use oden_js_sys as sys;
use std::cell::RefCell; use std::cell::RefCell;
use std::ffi::CStr;
struct PrivateState { struct PrivateState {
refs: u64, refs: u64,
loader: Box<dyn ModuleLoader>,
}
impl PrivateState {
unsafe extern "C" fn module_loader(
ctx: *mut sys::JSContext,
path: *const i8,
opaque: *mut std::os::raw::c_void,
) -> *mut sys::JSModuleDef {
let path = match CStr::from_ptr(path).to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let state = opaque as *mut PrivateState;
let context = ContextRef::from_raw(ctx);
load_module(&context, path, &mut (*state).loader)
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -12,10 +33,19 @@ pub struct Runtime {
impl Runtime { impl Runtime {
pub fn new() -> Runtime { pub fn new() -> Runtime {
let state = Box::new(RefCell::new(PrivateState { refs: 1 })); Self::with_loader(DefaultModuleLoader::new())
}
pub fn with_loader<TLoader: ModuleLoader + 'static>(loader: TLoader) -> Runtime {
let state = Box::new(RefCell::new(PrivateState {
refs: 1,
loader: Box::new(loader),
}));
let rt = unsafe { let rt = unsafe {
let rt = sys::JS_NewRuntime(); let rt = sys::JS_NewRuntime();
sys::JS_SetRuntimeOpaque(rt, Box::into_raw(state) as *mut _); let state = Box::into_raw(state) as *mut _;
sys::JS_SetRuntimeOpaque(rt, state);
sys::JS_SetModuleLoaderFunc(rt, None, Some(PrivateState::module_loader), state);
rt rt
}; };
Runtime { rt } Runtime { rt }

View file

@ -1,6 +1,6 @@
use crate::{AtomRef, ContextRef, Error, Result, Runtime, RustFunction}; use crate::{AtomRef, ContextRef, Error, Result, Runtime, RustFunction};
use oden_js_sys as sys; use oden_js_sys as sys;
use std::ffi::{CStr, CString}; use std::ffi::CStr;
use std::fmt; use std::fmt;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
@ -312,21 +312,6 @@ impl ValueRef {
Ok(result) Ok(result)
} }
pub fn get_module_export(&self, ctx: &ContextRef, export: &str) -> Result<Value> {
if self.value_type() != ValueType::Module {
return Err(Error::InvalidType {
expected: ValueType::Bool,
found: self.value_type(),
});
}
let c_value = CString::new(export)?;
unsafe {
let module = sys::JS_VALUE_GET_PTR(self.val) as *mut sys::JSModuleDef;
ctx.check_exception(sys::JS_GetModuleExport(ctx.ctx, module, c_value.as_ptr()))
}
}
pub fn call(&self, ctx: &ContextRef) -> Result<Value> { pub fn call(&self, ctx: &ContextRef) -> Result<Value> {
unsafe { unsafe {
ctx.check_exception(sys::JS_Call( ctx.check_exception(sys::JS_Call(
@ -414,7 +399,7 @@ impl fmt::Debug for Value {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{Context, EvalFlags, EvalType, Runtime}; use crate::{Context, EvalFlags, Runtime};
#[test] #[test]
fn value_type() { fn value_type() {
@ -438,9 +423,7 @@ mod tests {
]; ];
for (expr, expected) in tests.into_iter() { for (expr, expected) in tests.into_iter() {
let val = ctx let val = ctx.eval(expr, "script", EvalFlags::STRICT).unwrap();
.eval(expr, "script", EvalType::Global, EvalFlags::STRICT)
.unwrap();
assert_eq!(*expected, val.value_type(), "for {}", expr); assert_eq!(*expected, val.value_type(), "for {}", expr);
} }

15
src/graphics.js Normal file
View file

@ -0,0 +1,15 @@
import * as core from "graphics-core";
function cls(r, g, b) {
core.cls(r, g, b);
}
function print(...args) {
core.print(args.join(" "));
}
function spr(x, y, w, h, sx, sy, sw = undefined, sh = undefined) {
sw = sw | w;
sh = sh | h;
core.spr(xy, w, h, sx, sy, sw, sh);
}

View file

@ -1,4 +1,4 @@
import { cls, print, spr } from "graphics"; import { cls, print, spr } from "./src/graphics";
export function init() { export function init() {
print("Hello world!"); print("Hello world!");
@ -8,5 +8,5 @@ export function update() {}
export function draw() { export function draw() {
cls(0.1, 0.2, 0.3); cls(0.1, 0.2, 0.3);
spr(0, 0, 0.5, 0.5, 0, 0, 0.5, 0.5); spr(0, 0, 0.5, 0.5, 0, 0);
} }

View file

@ -30,17 +30,17 @@ impl ScriptContext {
let js = include_str!("main.js"); let js = include_str!("main.js");
let module = context let module = context
.load_module(js, "main.js") .eval_module(js, "main.js")
.expect("Unable to load main"); .expect("Unable to load main");
let init = module let init = module
.get_module_export(&context, "init") .get_export(&context, "init")
.expect("Unable to fetch init"); .expect("Unable to fetch init");
let update = module let update = module
.get_module_export(&context, "update") .get_export(&context, "update")
.expect("Unable to fetch update"); .expect("Unable to fetch update");
let draw = module let draw = module
.get_module_export(&context, "draw") .get_export(&context, "draw")
.expect("Unable to fetch draw"); .expect("Unable to fetch draw");
ScriptContext { ScriptContext {

View file

@ -1,4 +1,4 @@
use oden_js::{module, ContextRef, Value, ValueRef, ValueResult}; use oden_js::{module, ContextRef, Value, ValueResult};
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use std::sync::Arc; use std::sync::Arc;
@ -37,13 +37,7 @@ impl GraphicsImpl {
GraphicsImpl { sender } GraphicsImpl { sender }
} }
fn print_fn(&self, ctx: &ContextRef, args: &[&ValueRef]) -> ValueResult { fn print_fn(&self, ctx: &ContextRef, text: String) -> ValueResult {
let mut text = String::with_capacity(128);
for arg in args {
let v = arg.to_string(ctx)?;
text.push_str(&v);
}
let _ = self let _ = self
.sender .sender
.send(GraphicsCommand::Print(PrintCommand { text })); .send(GraphicsCommand::Print(PrintCommand { text }));
@ -67,8 +61,8 @@ impl GraphicsImpl {
h: f32, h: f32,
u: f32, u: f32,
v: f32, v: f32,
sw: Option<f32>, sw: f32,
sh: Option<f32>, sh: f32,
) -> ValueResult { ) -> ValueResult {
let _ = self.sender.send(GraphicsCommand::Sprite(SpriteCommand { let _ = self.sender.send(GraphicsCommand::Sprite(SpriteCommand {
x, x,
@ -77,8 +71,8 @@ impl GraphicsImpl {
h, h,
u, u,
v, v,
sw: sw.unwrap_or(w), sw,
sh: sh.unwrap_or(h), sh,
})); }));
Ok(Value::undefined(ctx)) Ok(Value::undefined(ctx))
} }
@ -91,12 +85,12 @@ pub struct GraphicsAPI {
impl GraphicsAPI { impl GraphicsAPI {
pub fn define(ctx: &ContextRef, sender: Sender<GraphicsCommand>) -> oden_js::Result<Self> { pub fn define(ctx: &ContextRef, sender: Sender<GraphicsCommand>) -> oden_js::Result<Self> {
let gfx = Arc::new(GraphicsImpl::new(sender)); let gfx = Arc::new(GraphicsImpl::new(sender));
let mut builder = module::NativeModuleBuilder::new(ctx); let mut builder = module::native::NativeModuleBuilder::new(ctx);
{ {
let gfx = gfx.clone(); let gfx = gfx.clone();
builder.export( builder.export(
"print", "print",
ctx.new_dynamic_fn(move |ctx, _, args| gfx.print_fn(ctx, args))?, ctx.new_fn(move |ctx: &ContextRef, t: String| gfx.print_fn(ctx, t))?,
)?; )?;
} }
{ {
@ -120,14 +114,14 @@ impl GraphicsAPI {
h: f32, h: f32,
u: f32, u: f32,
v: f32, v: f32,
sw: Option<f32>, sw: f32,
sh: Option<f32>| { sh: f32| {
gfx.spr_fn(ctx, x, y, w, h, u, v, sw, sh) gfx.spr_fn(ctx, x, y, w, h, u, v, sw, sh)
}, },
)?, )?,
)?; )?;
} }
builder.build("graphics")?; builder.build("graphics-core")?;
Ok(GraphicsAPI { gfx }) Ok(GraphicsAPI { gfx })
} }