use std::{ any::Any, cell::RefCell, sync::mpsc::{sync_channel, Receiver, SyncSender}, thread, }; use diaphragm_cairo_renderer::CairoRenderer; use diaphragm_core::{ core_shapes::{CoreDrawable, CoreShape, Rectangle as CoreRectangle}, solving::VariableHandle, text::{FontDescription as CoreFontDescription, FontStyle, FontWeight, Text as CoreText}, types::{Bool as CoreBool, CoreShapeContext, Float as CoreFloat}, Runtime, }; use diaphragm_z3_solver::{z3, Z3Context}; use mlua::prelude::*; #[derive(Clone, Copy, Debug)] struct Float(CoreFloat); impl Float { fn new() -> Float { Float(runtime_thread_do(Box::new(|r| { r.solver_ctx().new_free_float() }))) } } impl LuaUserData for Float { fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { macro_rules! float_method { ($name: expr, $op: ident) => { methods.add_method($name, |_lua, lhs: &Float, rhs: LuaValue| { let lhs = *lhs; let rhs = Float::try_from(rhs).unwrap(); Ok(Bool(runtime_thread_do(Box::new(move |r| { r.solver_ctx().$op(lhs.0, rhs.0) })))) }); }; } float_method!("eq", float_eq); float_method!("ne", float_ne); float_method!("gt", float_gt); float_method!("ge", float_ge); float_method!("lt", float_lt); float_method!("le", float_le); } } impl TryFrom> for Float { type Error = LuaError; fn try_from(value: LuaValue) -> Result { match value { LuaValue::Integer(i) => Ok(Float(CoreFloat::Fixed(i as _))), 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)), _ => Err(LuaError::FromLuaConversionError { from: "Value", to: "Float", message: Some("Only int, float, or float() values are allowed".to_string()), }), } } } fn float(_: &Lua, _: ()) -> LuaResult { Ok(Float::new()) } #[derive(Clone, Copy, Debug)] struct Bool(CoreBool); impl LuaUserData for Bool { fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { macro_rules! bool_method { ($name: expr, $op: ident) => { methods.add_method($name, |_lua, lhs: &Bool, rhs: LuaValue| { let lhs = *lhs; let rhs = Bool::try_from(rhs).unwrap(); Ok(Bool(runtime_thread_do(Box::new(move |r| { r.solver_ctx().$op(lhs.0, rhs.0) })))) }); }; } bool_method!("eq", bool_eq); bool_method!("implies", bool_implies); methods.add_method("and", |_lua, lhs: &Bool, rhs: LuaValue| { let lhs = *lhs; let rhs = Bool::try_from(rhs).unwrap(); Ok(Bool(runtime_thread_do(Box::new(move |r| { r.solver_ctx().bool_and(&[lhs.0, rhs.0]) })))) }); methods.add_method("or", |_lua, lhs: &Bool, rhs: LuaValue| { let lhs = *lhs; let rhs = Bool::try_from(rhs).unwrap(); Ok(Bool(runtime_thread_do(Box::new(move |r| { r.solver_ctx().bool_or(&[lhs.0, rhs.0]) })))) }); methods.add_method("no", |_lua, b: &Bool, _: ()| { let b = *b; Ok(Bool(runtime_thread_do(Box::new(move |r| { r.solver_ctx().bool_not(b.0) })))) }); } } impl TryFrom> for Bool { type Error = LuaError; fn try_from(value: LuaValue) -> Result { match value { LuaValue::Boolean(b) => Ok(Bool(runtime_thread_do(Box::new(move |r| { r.solver_ctx().new_fixed_bool(b) })))), // 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)), _ => Err(LuaError::FromLuaConversionError { from: "Value", to: "Bool", message: Some("Only bool, or bool() values 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))) } } const DEFAULT_FONT_FAMILY: &str = "serif"; impl Default for FontDescription { fn default() -> Self { Self(CoreFontDescription { family: DEFAULT_FONT_FAMILY.to_string(), style: FontStyle::Normal, weight: FontWeight::Normal, size: Float::new().0, }) } } fn font(_: &Lua, params: LuaTable) -> LuaResult { // TODO: better validation of the table // What happens when I mistype a param? // TODO: better error handling let family = params .get::<_, Option<_>>("family")? .unwrap_or_else(|| DEFAULT_FONT_FAMILY.to_string()); let style = match params.get::<_, Option>("style")?.as_deref() { Some("normal") | None => FontStyle::Normal, Some(_) => return Err(LuaError::RuntimeError("Unknown style".to_string())), }; let weight = match params.get::<_, Option>("weight")?.as_deref() { Some("normal") | None => FontWeight::Normal, Some(_) => return Err(LuaError::RuntimeError("Unknown weight".to_string())), }; let size = match params.get::<_, Option>("size")? { Some(f) => Float::try_from(f)?, None => Float::new(), }; Ok(FontDescription(CoreFontDescription { family, style, weight, size: size.0, })) } fn new_shape_context() -> CoreShapeContext { runtime_thread_do(Box::new(|r| CoreShapeContext::new(r.solver_ctx()))) } #[derive(Debug, Clone)] struct Drawable where T: Into, { shape: T, context: CoreShapeContext, } trait HasContext { fn get_context(&self) -> &CoreShapeContext; } fn add_bound_fields<'lua, T, F>(fields: &mut F) where T: LuaUserData, T: HasContext, F: LuaUserDataFields<'lua, T>, { macro_rules! bound_field { ($name: expr, $method: ident) => { fields.add_field_method_get($name, |_, this| { let bounds = this.get_context().bounds().clone(); Ok(Float(runtime_thread_do(Box::new(move |r| { bounds.$method(r.solver_ctx()) })))) }) }; } 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); } impl> From> for CoreDrawable { fn from(value: Drawable) -> Self { CoreDrawable::new(value.shape.into(), value.context) } } #[derive(Clone, Debug)] struct Text(Drawable); impl LuaUserData for Text { 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); } } impl HasContext for Text { fn get_context(&self) -> &CoreShapeContext { &self.0.context } } fn text(_: &Lua, params: LuaTable) -> LuaResult { let content: String = params.get("content")?; let font = params .get::<_, Option>("font")? .unwrap_or_default(); let context = new_shape_context(); let content_2 = content.clone(); let font_2 = font.0.clone(); let context_2 = context.clone(); 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(); // let scale = solver.float_div(font.0.size, CoreFloat::Fixed(height)); let calculated_width = solver.float_mul(&[CoreFloat::Fixed(text_width), font.0.size]); let bounds_width = context_2.bounds().width(solver); let width_constraint = solver.float_eq(bounds_width, calculated_width); solver.constrain(width_constraint); let calculated_height = solver.float_mul(&[CoreFloat::Fixed(text_height), font.0.size]); let bounds_height = context_2.bounds().height(solver); let height_constraint = solver.float_eq(bounds_height, calculated_height); solver.constrain(height_constraint); })); Ok(Text(Drawable { shape: CoreText { content, font: font.0, }, context, })) } #[derive(Clone, Debug)] struct Rectangle(Drawable); impl LuaUserData for Rectangle { 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); } } impl HasContext for Rectangle { fn get_context(&self) -> &CoreShapeContext { &self.0.context } } fn rectangle(_: &Lua, _params: ()) -> LuaResult { Ok(Rectangle(Drawable { shape: CoreRectangle {}, context: new_shape_context(), })) } thread_local! { static SENDER: RefCell>> = RefCell::new(None); static REPLY: RefCell>> = RefCell::new(None); static REPLIER: RefCell>> = RefCell::new(None); } enum Message { Do(Box), } fn runtime_thread_do(fun: Box T + Send>) -> T { SENDER.with(|sender| { sender .borrow_mut() .as_mut() .expect("Not currently drawing") .send(Message::Do(Box::new(|r| { let ret = fun(r); REPLIER.with(|replier| { replier .borrow_mut() .as_mut() .unwrap() .send(Reply::Any(Box::new(ret))) .unwrap(); }); }))) .unwrap(); }); REPLY.with(|reply| { let Reply::Any(any) = reply .borrow_mut() .as_mut() .expect("Not currently drawing") .recv() .unwrap(); *any.downcast().unwrap() }) } enum Reply { Any(Box), } fn constrain(_: &Lua, bool: Bool) -> LuaResult<()> { runtime_thread_do(Box::new(move |r| r.solver_ctx().constrain(bool.0))); Ok(()) } 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| { let (message_sender, message_receiver) = sync_channel(1); let (reply_sender, reply_receiver) = sync_channel(1); SENDER.with(|f| { *f.borrow_mut() = Some(message_sender); }); REPLY.with(|f| { *f.borrow_mut() = Some(reply_receiver); }); s.spawn(move || { REPLIER.with(|f| { *f.borrow_mut() = Some(reply_sender.clone()); }); let z3_cfg = z3::Config::new(); let z3_ctx = z3::Context::new(&z3_cfg); let ctx = Z3Context::new(&z3_ctx); let cairo_renderer = CairoRenderer::new(); // TODO: we shouldn't need the renderer until the end let mut runtime = Runtime::new(Box::new(ctx), Box::new(cairo_renderer)); for message in message_receiver { match message { Message::Do(fun) => { fun(&mut runtime); } } } runtime.render(); }); let content: LuaValue = params.get("content").unwrap(); let _output: LuaTable = params.get("output").unwrap(); match content { LuaValue::Table(table) => { let () = table.call_method("draw", ()).unwrap(); } // TODO: switch to enum // LuaValue::UserData(user_data) => { // if user_data.is::() { // let text = user_data // .borrow::() // .expect("Couldn't borrow Text user data"); // // SENDER.with(|f| { // f.borrow_mut() // .as_ref() // .expect("Not currently drawing") // .send(Message::AddDrawable(CoreShape::Text(text.0.clone()))) // .expect("Could not send shape"); // }) // } else { // panic!("Non-drawable passed to draw"); // } // } _ => panic!("Non-drawable passed to draw"), } SENDER.with(|s| { *s.borrow_mut() = None; }); }); Ok(()) } #[mlua::lua_module] fn libdiaphragm(lua: &Lua) -> LuaResult { // TODO: the solver as a mutable global solves so much problem (pun not intended) let exports = lua.create_table()?; exports.set("float", lua.create_function(float)?)?; exports.set("text", lua.create_function(text)?)?; exports.set("font", lua.create_function(font)?)?; exports.set("rectangle", lua.create_function(rectangle)?)?; exports.set("constrain", lua.create_function(constrain)?)?; exports.set("draw", lua.create_function(draw)?)?; // Setting up metatables // --- let float_metatable = lua .create_userdata(Float(CoreFloat::Fixed(0.))) .unwrap() .get_metatable() .unwrap(); 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_metamethod!(Add, float_add); 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(); // 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(); Ok(exports) }