use crate::scripting::shared::ScriptingSchematic;
use rquickjs::{
class::Trace, CatchResultExt, Class, Context, Ctx, Function, JsLifetime, Result as JsResult,
Runtime,
};
#[rquickjs::class]
pub struct JsSchematic {
inner: ScriptingSchematic,
}
impl<'js> Trace<'js> for JsSchematic {
fn trace<'a>(&self, _tracer: rquickjs::class::Tracer<'a, 'js>) {}
}
unsafe impl<'js> JsLifetime<'js> for JsSchematic {
type Changed<'to> = JsSchematic;
}
fn make_js_err(msg: &str) -> rquickjs::Error {
rquickjs::Error::IntoJs {
from: "Rust",
to: "JS",
message: Some(msg.to_string()),
}
}
#[rquickjs::methods]
impl JsSchematic {
#[qjs(constructor)]
pub fn new(name: rquickjs::function::Opt<String>) -> Self {
Self {
inner: ScriptingSchematic::new(name.0),
}
}
#[qjs(get, rename = "name")]
pub fn get_name(&self) -> String {
self.inner.get_name()
}
#[qjs(set, rename = "name")]
pub fn set_name(&mut self, name: String) {
self.inner.set_name(&name);
}
#[qjs(get, rename = "author")]
pub fn get_author(&self) -> String {
self.inner.get_author()
}
#[qjs(set, rename = "author")]
pub fn set_author(&mut self, author: String) {
self.inner.set_author(&author);
}
#[qjs(get, rename = "description")]
pub fn get_description(&self) -> String {
self.inner.get_description()
}
#[qjs(set, rename = "description")]
pub fn set_description(&mut self, desc: String) {
self.inner.set_description(&desc);
}
pub fn set_block(&mut self, x: i32, y: i32, z: i32, name: String) {
self.inner.set_block(x, y, z, &name);
}
pub fn get_block(&self, x: i32, y: i32, z: i32) -> Option<String> {
self.inner.get_block(x, y, z)
}
pub fn fill_cuboid(
&mut self,
min_x: i32,
min_y: i32,
min_z: i32,
max_x: i32,
max_y: i32,
max_z: i32,
block: String,
) {
self.inner
.fill_cuboid((min_x, min_y, min_z), (max_x, max_y, max_z), &block);
}
pub fn fill_sphere(&mut self, cx: i32, cy: i32, cz: i32, radius: f64, block: String) {
self.inner.fill_sphere((cx, cy, cz), radius, &block);
}
pub fn get_dimensions<'js>(&self, ctx: Ctx<'js>) -> JsResult<rquickjs::Object<'js>> {
let (w, h, d) = self.inner.get_dimensions();
let obj = rquickjs::Object::new(ctx)?;
obj.set("width", w)?;
obj.set("height", h)?;
obj.set("depth", d)?;
Ok(obj)
}
pub fn get_block_count(&self) -> i32 {
self.inner.get_block_count()
}
pub fn get_volume(&self) -> i32 {
self.inner.get_volume()
}
pub fn flip_x(&mut self) {
self.inner.flip_x();
}
pub fn flip_y(&mut self) {
self.inner.flip_y();
}
pub fn flip_z(&mut self) {
self.inner.flip_z();
}
pub fn rotate_x(&mut self, degrees: i32) -> rquickjs::Result<()> {
self.inner.rotate_x(degrees).map_err(|e| make_js_err(&e))
}
pub fn rotate_y(&mut self, degrees: i32) -> rquickjs::Result<()> {
self.inner.rotate_y(degrees).map_err(|e| make_js_err(&e))
}
pub fn rotate_z(&mut self, degrees: i32) -> rquickjs::Result<()> {
self.inner.rotate_z(degrees).map_err(|e| make_js_err(&e))
}
pub fn to_schematic(&self) -> rquickjs::Result<Vec<u8>> {
self.inner.to_schematic().map_err(|e| make_js_err(&e))
}
pub fn to_litematic(&self) -> rquickjs::Result<Vec<u8>> {
self.inner.to_litematic().map_err(|e| make_js_err(&e))
}
pub fn save_as(&self, format: String) -> rquickjs::Result<Vec<u8>> {
self.inner.save_as(&format).map_err(|e| make_js_err(&e))
}
pub fn save_to_file(&self, path: String) -> rquickjs::Result<()> {
self.inner.save_to_file(&path).map_err(|e| make_js_err(&e))
}
pub fn get_all_blocks<'js>(&self, ctx: Ctx<'js>) -> JsResult<rquickjs::Array<'js>> {
let blocks = self.inner.get_all_blocks();
let arr = rquickjs::Array::new(ctx.clone())?;
for (i, (x, y, z, name)) in blocks.iter().enumerate() {
let obj = rquickjs::Object::new(ctx.clone())?;
obj.set("x", *x)?;
obj.set("y", *y)?;
obj.set("z", *z)?;
obj.set("name", name.as_str())?;
arr.set(i, obj)?;
}
Ok(arr)
}
pub fn get_region_names<'js>(&self, ctx: Ctx<'js>) -> JsResult<rquickjs::Array<'js>> {
let names = self.inner.get_region_names();
let arr = rquickjs::Array::new(ctx)?;
for (i, name) in names.iter().enumerate() {
arr.set(i, name.as_str())?;
}
Ok(arr)
}
}
fn setup_js(ctx: &Ctx<'_>) -> JsResult<()> {
Class::<JsSchematic>::define(&ctx.globals())?;
let schematic_ctor: Function = ctx.globals().get("JsSchematic")?;
schematic_ctor.set(
"load",
Function::new(
ctx.clone(),
|path: String| -> rquickjs::Result<JsSchematic> {
let ss = ScriptingSchematic::from_file(&path).map_err(|e| make_js_err(&e))?;
Ok(JsSchematic { inner: ss })
},
)?,
)?;
let ctor: rquickjs::Value = ctx.globals().get("JsSchematic")?;
ctx.globals().set("Schematic", ctor)?;
ctx.globals().set(
"palette_gradient_ids",
Function::new(
ctx.clone(),
|name: String, args: rquickjs::function::Rest<i32>| -> rquickjs::Result<Vec<String>> {
let a = &args.0;
if a.len() != 7 {
return Err(make_js_err(
"palette_gradient_ids expects (name, r1, g1, b1, r2, g2, b2, steps)",
));
}
let channel = |v: i32| -> rquickjs::Result<u8> {
u8::try_from(v).map_err(|_| make_js_err("color channels must be 0-255"))
};
let steps =
usize::try_from(a[6]).map_err(|_| make_js_err("steps must be non-negative"))?;
crate::scripting::shared::palette_gradient_ids(
&name,
(channel(a[0])?, channel(a[1])?, channel(a[2])?),
(channel(a[3])?, channel(a[4])?, channel(a[5])?),
steps,
)
.map_err(|e| make_js_err(&e))
},
)?,
)?;
ctx.globals().set(
"palette_block_ids",
Function::new(
ctx.clone(),
|name: String| -> rquickjs::Result<Vec<String>> {
crate::scripting::shared::palette_block_ids(&name).map_err(|e| make_js_err(&e))
},
)?,
)?;
ctx.globals().set(
"palette_closest_block",
Function::new(
ctx.clone(),
|name: String, r: u8, g: u8, b: u8| -> rquickjs::Result<String> {
crate::scripting::shared::palette_closest_block(&name, r, g, b)
.map_err(|e| make_js_err(&e))
},
)?,
)?;
Ok(())
}
pub fn run_js_script(path: &str) -> Result<Option<ScriptingSchematic>, String> {
let code =
std::fs::read_to_string(path).map_err(|e| format!("Failed to read JS script: {}", e))?;
run_js_code(&code)
}
pub fn run_js_code(code: &str) -> Result<Option<ScriptingSchematic>, String> {
let rt = Runtime::new().map_err(|e| format!("JS runtime error: {}", e))?;
let ctx = Context::full(&rt).map_err(|e| format!("JS context error: {}", e))?;
ctx.with(|ctx| {
setup_js(&ctx).map_err(|e| format!("JS setup error: {}", e))?;
ctx.globals()
.set("result", rquickjs::Value::new_undefined(ctx.clone()))
.map_err(|e| format!("JS setup error: {}", e))?;
ctx.eval::<(), _>(code)
.catch(&ctx)
.map_err(|e| format!("JS execution error: {}", e))?;
let result: Option<Class<JsSchematic>> = ctx.globals().get("result").ok();
match result {
Some(cls) => {
let borrow = cls.borrow();
let cloned = borrow.inner.inner.clone();
drop(borrow);
Ok(Some(ScriptingSchematic { inner: cloned }))
}
None => Ok(None),
}
})
}