From efc83b6fec3f38c45a6b2f51d5e791c30f59eb42 Mon Sep 17 00:00:00 2001 From: Minijackson Date: Wed, 4 Jan 2023 09:38:29 +0100 Subject: lua-bindings: update with straightpath, better error handling, etc. --- lua-bindings/src/lib.rs | 505 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 419 insertions(+), 86 deletions(-) diff --git a/lua-bindings/src/lib.rs b/lua-bindings/src/lib.rs index daf4b48..35cdaf8 100644 --- a/lua-bindings/src/lib.rs +++ b/lua-bindings/src/lib.rs @@ -1,3 +1,5 @@ +// TODO: split file + use std::{ any::Any, cell::RefCell, @@ -7,10 +9,14 @@ use std::{ use diaphragm_cairo_renderer::CairoRenderer; use diaphragm_core::{ - core_shapes::{CoreDrawable, CoreShape, Rectangle as CoreRectangle}, + colors::Color, + core_shapes::{ + CoreDrawable, CoreShape, Rectangle as CoreRectangle, StraightPath as CoreStraightPath, + }, solving::VariableHandle, + styles::Pattern, text::{FontDescription as CoreFontDescription, FontStyle, FontWeight, Text as CoreText}, - types::{Bool as CoreBool, CoreShapeContext, Float as CoreFloat}, + types::{Bool as CoreBool, CoreShapeContext, Float as CoreFloat, Point2D as CorePoint2D}, Runtime, }; use diaphragm_z3_solver::{z3, Z3Context}; @@ -32,7 +38,7 @@ impl LuaUserData for Float { ($name: expr, $op: ident) => { methods.add_method($name, |_lua, lhs: &Float, rhs: LuaValue| { let lhs = *lhs; - let rhs = Float::try_from(rhs).unwrap(); + let rhs = Float::try_from(rhs)?; Ok(Bool(runtime_thread_do(Box::new(move |r| { r.solver_ctx().$op(lhs.0, rhs.0) })))) @@ -46,6 +52,13 @@ impl LuaUserData for Float { float_method!("ge", float_ge); float_method!("lt", float_lt); float_method!("le", float_le); + + methods.add_method("abs", |_lua, f: &Float, _: ()| { + let f = *f; + Ok(Float(runtime_thread_do(Box::new(move |r| { + r.solver_ctx().float_abs(f.0) + })))) + }); } } @@ -58,9 +71,9 @@ impl TryFrom> for Float { LuaValue::Number(f) => Ok(Float(CoreFloat::Fixed(f))), // Create a new float from the borrow, since it might already be borrowed, with for ex. // f:eq(f) - LuaValue::UserData(u) => Ok(Float(u.borrow::().unwrap().0)), + LuaValue::UserData(u) => Ok(Float(u.borrow::()?.0)), _ => Err(LuaError::FromLuaConversionError { - from: "Value", + from: value.type_name(), to: "Float", message: Some("Only int, float, or float() values are allowed".to_string()), }), @@ -72,6 +85,20 @@ fn float(_: &Lua, _: ()) -> LuaResult { Ok(Float::new()) } +fn float_max(_: &Lua, elems: Vec) -> LuaResult { + let elems = elems.into_iter().map(|f| f.0).collect::>(); + Ok(Float(runtime_thread_do(Box::new(move |r| { + r.solver_ctx().float_max(&elems) + })))) +} + +fn float_min(_: &Lua, elems: Vec) -> LuaResult { + let elems = elems.into_iter().map(|f| f.0).collect::>(); + Ok(Float(runtime_thread_do(Box::new(move |r| { + r.solver_ctx().float_min(&elems) + })))) +} + #[derive(Clone, Copy, Debug)] struct Bool(CoreBool); @@ -81,7 +108,7 @@ impl LuaUserData for Bool { ($name: expr, $op: ident) => { methods.add_method($name, |_lua, lhs: &Bool, rhs: LuaValue| { let lhs = *lhs; - let rhs = Bool::try_from(rhs).unwrap(); + let rhs = Bool::try_from(rhs)?; Ok(Bool(runtime_thread_do(Box::new(move |r| { r.solver_ctx().$op(lhs.0, rhs.0) })))) @@ -94,7 +121,7 @@ impl LuaUserData for Bool { methods.add_method("and", |_lua, lhs: &Bool, rhs: LuaValue| { let lhs = *lhs; - let rhs = Bool::try_from(rhs).unwrap(); + let rhs = Bool::try_from(rhs)?; Ok(Bool(runtime_thread_do(Box::new(move |r| { r.solver_ctx().bool_and(&[lhs.0, rhs.0]) })))) @@ -102,7 +129,7 @@ impl LuaUserData for Bool { methods.add_method("or", |_lua, lhs: &Bool, rhs: LuaValue| { let lhs = *lhs; - let rhs = Bool::try_from(rhs).unwrap(); + let rhs = Bool::try_from(rhs)?; Ok(Bool(runtime_thread_do(Box::new(move |r| { r.solver_ctx().bool_or(&[lhs.0, rhs.0]) })))) @@ -127,7 +154,7 @@ impl TryFrom> for Bool { })))), // Create a new bool from the borrow, since it might already be borrowed, with for ex. // b:eq(b) - LuaValue::UserData(u) => Ok(Bool(u.borrow::().unwrap().0)), + LuaValue::UserData(u) => Ok(Bool(u.borrow::()?.0)), _ => Err(LuaError::FromLuaConversionError { from: "Value", to: "Bool", @@ -137,11 +164,87 @@ impl TryFrom> for Bool { } } +#[derive(Debug, Clone)] +struct Point2D(CorePoint2D); + +impl LuaUserData for Point2D { + fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("x", |_, this| Ok(Float(this.0.x()))); + fields.add_field_method_get("y", |_, this| Ok(Float(this.0.y()))); + } + + fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_method("eq", |_, this, other: LuaValue| { + let x = this.0.x(); + let y = this.0.y(); + let other: Point2D = other.try_into()?; + Ok(Bool(runtime_thread_do(Box::new(move |r| { + let solver = r.solver_ctx(); + let x_constraint = solver.float_eq(x, other.0.x()); + let y_constraint = solver.float_eq(y, other.0.y()); + solver.bool_and(&[x_constraint, y_constraint]) + })))) + }); + + methods.add_method("ne", |_, this, other: LuaValue| { + let x = this.0.x(); + let y = this.0.y(); + let other: Point2D = other.try_into()?; + Ok(Bool(runtime_thread_do(Box::new(move |r| { + let solver = r.solver_ctx(); + let x_constraint = solver.float_ne(x, other.0.x()); + let y_constraint = solver.float_ne(y, other.0.y()); + solver.bool_or(&[x_constraint, y_constraint]) + })))) + }); + + methods.add_method("man_dist", |_, this, other: LuaValue| { + let x = this.0.x(); + let y = this.0.y(); + let other: Point2D = other.try_into()?; + Ok(Float(runtime_thread_do(Box::new(move |r| { + let solver = r.solver_ctx(); + + let signed_x_dist = solver.float_sub(&[x, other.0.x()]); + let signed_y_dist = solver.float_sub(&[y, other.0.y()]); + + let x_dist = solver.float_abs(signed_x_dist); + let y_dist = solver.float_abs(signed_y_dist); + + solver.float_add(&[x_dist, y_dist]) + })))) + }); + } +} + +impl TryFrom> for Point2D { + type Error = LuaError; + + fn try_from(value: LuaValue<'_>) -> Result { + match value { + LuaValue::Table(table) => { + let x: Float = table.get::<_, LuaValue>("x")?.try_into()?; + let y: Float = table.get::<_, LuaValue>("y")?.try_into()?; + Ok(Point2D(CorePoint2D::new(x.0, y.0))) + } + // Create a new float from the borrow, since it might already be borrowed, with for ex. + // f:eq(f) + LuaValue::UserData(u) => Ok(Point2D(u.borrow::()?.0.clone())), + _ => Err(LuaError::FromLuaConversionError { + from: value.type_name(), + to: "Point2D", + message: Some("Only { x, y } tables are allowed".to_string()), + }), + } + } +} + #[derive(Clone, Debug)] struct FontDescription(CoreFontDescription); impl LuaUserData for FontDescription { fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { - fields.add_field_method_get("size", |_, this| Ok(Float(this.0.size))) + // fields.add_field_method_get("size", |_, this| Ok(Float(this.0.size))) + fields.add_field_method_get("size", |_, this| Ok(Float(dbg!(this.0.size)))) } } @@ -205,6 +308,7 @@ where trait HasContext { fn get_context(&self) -> &CoreShapeContext; + fn get_context_mut(&mut self) -> &mut CoreShapeContext; } fn add_bound_fields<'lua, T, F>(fields: &mut F) @@ -214,24 +318,139 @@ where F: LuaUserDataFields<'lua, T>, { macro_rules! bound_field { - ($name: expr, $method: ident) => { + ($name: expr, $method: ident, $type: tt) => { fields.add_field_method_get($name, |_, this| { let bounds = this.get_context().bounds().clone(); - Ok(Float(runtime_thread_do(Box::new(move |r| { + Ok($type(runtime_thread_do(Box::new(move |r| { bounds.$method(r.solver_ctx()) })))) - }) + }); + + fields.add_field_method_set($name, |_, this, other: LuaValue| { + let bounds = this.get_context().bounds().clone(); + let other: $type = other.try_into()?; + runtime_thread_do(Box::new(move |r| { + let solver = r.solver_ctx(); + let lhs = bounds.$method(solver); + bound_field!(@check, solver, lhs, other, $type); + })); + Ok(()) + }); + }; + (@check, $solver: ident, $lhs: ident, $rhs: ident, Float) => { + let constraint = $solver.float_eq($lhs, $rhs.0); + $solver.constrain(constraint); + }; + (@check, $solver: ident, $lhs: ident, $rhs: ident, Point2D) => { + let x_constraint = $solver.float_eq($lhs.x(), $rhs.0.x()); + let y_constraint = $solver.float_eq($lhs.y(), $rhs.0.y()); + let constraint = $solver.bool_and(&[x_constraint, y_constraint]); + $solver.constrain(constraint); }; } - bound_field!("top", top); - bound_field!("left", left); - bound_field!("width", width); - bound_field!("height", height); - bound_field!("right", right); - bound_field!("bottom", bottom); - bound_field!("vert_center", vert_center); - bound_field!("horiz_center", horiz_center); + bound_field!("top", top, Float); + bound_field!("left", left, Float); + bound_field!("width", width, Float); + bound_field!("height", height, Float); + bound_field!("right", right, Float); + bound_field!("bottom", bottom, Float); + bound_field!("vert_center", vert_center, Float); + bound_field!("horiz_center", horiz_center, Float); + + // TODO: two names: middle or center? + bound_field!("top_left", top_left, Point2D); + bound_field!("top_right", top_right, Point2D); + bound_field!("bottom_left", bottom_left, Point2D); + bound_field!("bottom_right", bottom_right, Point2D); + bound_field!("middle_left", middle_left, Point2D); + bound_field!("middle_right", middle_right, Point2D); + bound_field!("top_middle", top_middle, Point2D); + bound_field!("bottom_middle", bottom_middle, Point2D); + bound_field!("center", center, Point2D); +} + +fn add_context_fields<'lua, T, F>(fields: &mut F) +where + T: LuaUserData, + T: HasContext, + F: LuaUserDataFields<'lua, T>, +{ + // Everything in top-level namespace, else we need struct with interior mutability? + fields.add_field_method_get("stroke_color", |lua, this| { + Ok(match this.get_context().stroke().pattern() { + diaphragm_core::styles::Pattern::Solid(color) => { + LuaValue::String(lua.create_string(&color.to_hex())?) + } + diaphragm_core::styles::Pattern::None => LuaValue::Nil, + _ => todo!(), + }) + }); + + fields.add_field_method_set("stroke_color", |_, this, value: Option| { + let context = this.get_context_mut(); + if let Some(color) = value { + context + .stroke_mut() + .set_pattern(Pattern::Solid(Color::from_hex(&color))); + } else { + context.stroke_mut().set_pattern(Pattern::None); + } + Ok(()) + }); + + fields.add_field_method_get("stroke_width", |_, this| { + Ok(this.get_context().stroke().line_width().map(Float)) + }); + + fields.add_field_method_set("stroke_width", |_, this, value: LuaValue| { + let other: Float = match value { + LuaNil => { + this.get_context_mut().stroke_mut().set_line_width(None); + return Ok(()); + } + other => other.try_into()?, + }; + + let stroke_width = match this.get_context().stroke().line_width() { + Some(f) => f, + None => { + this.get_context_mut() + .stroke_mut() + .set_line_width(Some(other.0)); + return Ok(()); + } + }; + + runtime_thread_do(Box::new(move |r| { + let solver = r.solver_ctx(); + let constraint = solver.float_eq(stroke_width, other.0); + solver.constrain(constraint); + })); + Ok(()) + }); + + fields.add_field_method_get("fill_color", |lua, this| { + Ok(match this.get_context().fill().pattern() { + diaphragm_core::styles::Pattern::Solid(color) => { + LuaValue::String(lua.create_string(&color.to_hex())?) + } + diaphragm_core::styles::Pattern::None => LuaValue::Nil, + _ => todo!(), + }) + }); + + fields.add_field_method_set("fill_color", |_, this, value: Option| { + let context = this.get_context_mut(); + if let Some(color) = value { + context + .fill_mut() + .set_pattern(Pattern::Solid(Color::from_hex(&color))); + } else { + context.fill_mut().set_pattern(Pattern::None); + } + Ok(()) + }); } impl> From> for CoreDrawable { @@ -262,6 +481,10 @@ impl HasContext for Text { fn get_context(&self) -> &CoreShapeContext { &self.0.context } + + fn get_context_mut(&mut self) -> &mut CoreShapeContext { + &mut self.0.context + } } fn text(_: &Lua, params: LuaTable) -> LuaResult { @@ -277,6 +500,8 @@ fn text(_: &Lua, params: LuaTable) -> LuaResult { let font_2 = font.0.clone(); let context_2 = context.clone(); + // TODO: this shouldn't be here, this should be an innate property of Text. Move the + // Drawable struct in core? runtime_thread_do(Box::new(move |r| { let (text_width, text_height) = r.renderer().text_extents(&content_2, &font_2); let solver = r.solver_ctx(); @@ -317,6 +542,7 @@ impl LuaUserData for Rectangle { fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { add_bound_fields(fields); + add_context_fields(fields); } } @@ -324,6 +550,10 @@ impl HasContext for Rectangle { fn get_context(&self) -> &CoreShapeContext { &self.0.context } + + fn get_context_mut(&mut self) -> &mut CoreShapeContext { + &mut self.0.context + } } fn rectangle(_: &Lua, _params: ()) -> LuaResult { @@ -333,6 +563,91 @@ fn rectangle(_: &Lua, _params: ()) -> LuaResult { })) } +#[derive(Debug, Clone)] +struct StraightPath(Drawable); +impl LuaUserData for StraightPath { + fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_method("draw", |_, this, _params: ()| { + let drawable = this.0.clone().into(); + runtime_thread_do(Box::new(|r| { + r.add_drawable(drawable); + })); + Ok(()) + }) + } + + fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { + add_bound_fields(fields); + add_context_fields(fields); + } +} + +impl HasContext for StraightPath { + fn get_context(&self) -> &CoreShapeContext { + &self.0.context + } + + fn get_context_mut(&mut self) -> &mut CoreShapeContext { + &mut self.0.context + } +} + +impl TryFrom> for StraightPath { + type Error = LuaError; + + fn try_from(value: LuaValue) -> Result { + match value { + LuaValue::Table(t) => { + let points = t + .sequence_values::() + .map(|el| Ok(Point2D::try_from(el?)?.0)) + .collect::>>()?; + Ok(StraightPath(Drawable { + shape: CoreStraightPath::new(points), + context: new_shape_context(), + })) + } + // Create a new float from the borrow, since it might already be borrowed, with for ex. + // f:eq(f) + LuaValue::UserData(u) => Ok(StraightPath(u.borrow::()?.0.clone())), + _ => Err(LuaError::FromLuaConversionError { + from: value.type_name(), + to: "StraightPath", + message: Some("Only a list of points are allowed".to_string()), + }), + } + } +} + +// TODO: add bounds constraints before drawing +fn straight_path(_: &Lua, params: LuaTable) -> LuaResult { + let points: LuaValue = params.get("points")?; + points.try_into() +} + +// It just has a context for bounds, the rest is handled manually in Lua +#[derive(Debug, Clone)] +struct ComplexShape(CoreShapeContext); +impl LuaUserData for ComplexShape { + fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { + add_bound_fields(fields); + } +} + +impl HasContext for ComplexShape { + fn get_context(&self) -> &CoreShapeContext { + &self.0 + } + + fn get_context_mut(&mut self) -> &mut CoreShapeContext { + &mut self.0 + } +} + +fn complex_shape(_: &Lua, _params: ()) -> LuaResult { + Ok(ComplexShape(new_shape_context())) +} + thread_local! { static SENDER: RefCell>> = RefCell::new(None); static REPLY: RefCell>> = RefCell::new(None); @@ -387,7 +702,7 @@ fn draw(_: &Lua, params: LuaTable) -> LuaResult<()> { // So.... The Z3 stuff isn't Send and contains lifetimes, so we can't store them in global // variables or convert them to Lua. Solution: handle everything in a specific thread, and // communicate through a channel. - thread::scope(|s| { + thread::scope(|s| -> LuaResult<()> { let (message_sender, message_receiver) = sync_channel(1); let (reply_sender, reply_receiver) = sync_channel(1); @@ -424,13 +739,13 @@ fn draw(_: &Lua, params: LuaTable) -> LuaResult<()> { runtime.render(); }); - let content: LuaValue = params.get("content").unwrap(); - let _output: LuaTable = params.get("output").unwrap(); + let content: LuaValue = params.get("content")?; + let _output: LuaTable = params.get("output")?; match content { - LuaValue::Table(table) => { - let () = table.call_method("draw", ()).unwrap(); - } + // TODO: this doesn't stop the runtime thread? + LuaValue::Table(table) => table.call_method("draw", ()).unwrap(), + // TODO: switch to enum // LuaValue::UserData(user_data) => { // if user_data.is::() { @@ -455,9 +770,9 @@ fn draw(_: &Lua, params: LuaTable) -> LuaResult<()> { SENDER.with(|s| { *s.borrow_mut() = None; }); - }); - Ok(()) + Ok(()) + }) } #[mlua::lua_module] @@ -471,6 +786,13 @@ fn libdiaphragm(lua: &Lua) -> LuaResult { exports.set("rectangle", lua.create_function(rectangle)?)?; + exports.set("straight_path", lua.create_function(straight_path)?)?; + + exports.set("complex_shape", lua.create_function(complex_shape)?)?; + + exports.set("float_max", lua.create_function(float_max)?)?; + exports.set("float_min", lua.create_function(float_min)?)?; + exports.set("constrain", lua.create_function(constrain)?)?; exports.set("draw", lua.create_function(draw)?)?; @@ -478,25 +800,20 @@ fn libdiaphragm(lua: &Lua) -> LuaResult { // --- let float_metatable = lua - .create_userdata(Float(CoreFloat::Fixed(0.))) - .unwrap() - .get_metatable() - .unwrap(); + .create_userdata(Float(CoreFloat::Fixed(0.)))? + .get_metatable()?; macro_rules! float_metamethod { ($method: ident, $op: ident) => { - float_metatable - .set( - LuaMetaMethod::$method, - lua.create_function(|_lua, (lhs, rhs): (Float, LuaValue)| { - let rhs = Float::try_from(rhs).unwrap(); - Ok(Float(runtime_thread_do(Box::new(move |r| { - r.solver_ctx().$op(&[lhs.0, rhs.0]) - })))) - }) - .unwrap(), - ) - .unwrap(); + float_metatable.set( + LuaMetaMethod::$method, + lua.create_function(|_lua, (lhs, rhs): (Float, LuaValue)| { + let rhs = Float::try_from(rhs)?; + Ok(Float(runtime_thread_do(Box::new(move |r| { + r.solver_ctx().$op(&[lhs.0, rhs.0]) + })))) + })?, + )?; }; } @@ -504,51 +821,67 @@ fn libdiaphragm(lua: &Lua) -> LuaResult { float_metamethod!(Sub, float_sub); float_metamethod!(Mul, float_mul); - float_metatable - .set( - LuaMetaMethod::Div, - lua.create_function(|_lua, (lhs, rhs): (Float, LuaValue)| { - let rhs = Float::try_from(rhs).unwrap(); - Ok(Float(runtime_thread_do(Box::new(move |r| { - r.solver_ctx().float_div(lhs.0, rhs.0) - })))) - }) - .unwrap(), - ) - .unwrap(); - - float_metatable - .set( - LuaMetaMethod::Unm, - lua.create_function(|_lua, f: Float| { - Ok(Float(runtime_thread_do(Box::new(move |r| { - r.solver_ctx().float_neg(f.0) - })))) - }) - .unwrap(), - ) - .unwrap(); + float_metatable.set( + LuaMetaMethod::Div, + lua.create_function(|_lua, (lhs, rhs): (Float, LuaValue)| { + let rhs = Float::try_from(rhs)?; + Ok(Float(runtime_thread_do(Box::new(move |r| { + r.solver_ctx().float_div(lhs.0, rhs.0) + })))) + })?, + )?; + + float_metatable.set( + LuaMetaMethod::Unm, + lua.create_function(|_lua, f: Float| { + Ok(Float(runtime_thread_do(Box::new(move |r| { + r.solver_ctx().float_neg(f.0) + })))) + })?, + )?; // Not the operators `==`, `!=`, `<`, `<=`, `>`, and `>=` because Lua converts result to bool let bool_metatable = lua - .create_userdata(Bool(CoreBool::new(VariableHandle::new(0)))) - .unwrap() - .get_metatable() - .unwrap(); - - bool_metatable - .set( - LuaMetaMethod::Shl, - lua.create_function(|_lua, (lhs, rhs): (Bool, LuaValue)| { - let rhs = Bool::try_from(rhs).unwrap(); - Ok(Bool(runtime_thread_do(Box::new(move |r| { - r.solver_ctx().bool_implies(lhs.0, rhs.0) - })))) - }) - .unwrap(), - ) - .unwrap(); + .create_userdata(Bool(CoreBool::new(VariableHandle::new(0))))? + .get_metatable()?; + + bool_metatable.set( + LuaMetaMethod::Shl, + lua.create_function(|_lua, (lhs, rhs): (Bool, LuaValue)| { + let rhs = Bool::try_from(rhs)?; + Ok(Bool(runtime_thread_do(Box::new(move |r| { + r.solver_ctx().bool_implies(lhs.0, rhs.0) + })))) + })?, + )?; + + let point_metatable = lua + .create_userdata(Point2D(CorePoint2D::new( + CoreFloat::Fixed(0.), + CoreFloat::Fixed(0.), + )))? + .get_metatable()?; + + macro_rules! point_metamethod { + ($method: ident, $op: ident) => { + point_metatable.set( + LuaMetaMethod::$method, + lua.create_function(|_lua, (lhs, rhs): (Point2D, LuaValue)| { + let rhs = Point2D::try_from(rhs)?; + Ok(Point2D(runtime_thread_do(Box::new(move |r| { + let solver = r.solver_ctx(); + let new_x = solver.$op(&[lhs.0.x(), rhs.0.x()]); + let new_y = solver.$op(&[lhs.0.y(), rhs.0.y()]); + CorePoint2D::new(new_x, new_y) + })))) + })?, + )?; + }; + } + + point_metamethod!(Add, float_add); + point_metamethod!(Sub, float_sub); Ok(exports) } -- cgit v1.2.3