From 9c15b76c5a6355902b2a105a7c6ee93f6b5016dc Mon Sep 17 00:00:00 2001 From: Minijackson Date: Thu, 29 Dec 2022 01:40:08 +0100 Subject: WIP v2: text works, primitives works in Lua --- lua-bindings/src/lib.rs | 482 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 449 insertions(+), 33 deletions(-) (limited to 'lua-bindings/src') diff --git a/lua-bindings/src/lib.rs b/lua-bindings/src/lib.rs index fcf307b..daf4b48 100644 --- a/lua-bindings/src/lib.rs +++ b/lua-bindings/src/lib.rs @@ -1,40 +1,149 @@ -use std::{sync::atomic::{AtomicUsize, Ordering}, cell::RefCell}; +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::Float as CoreFloat, + types::{Bool as CoreBool, CoreShapeContext, Float as CoreFloat}, Runtime, }; use diaphragm_z3_solver::{z3, Z3Context}; use mlua::prelude::*; -static MAX_ID: AtomicUsize = AtomicUsize::new(0); - -thread_local! { - static SENDER: RefCell>> = RefCell::new(None); -} - #[derive(Clone, Copy, Debug)] struct Float(CoreFloat); impl Float { fn new() -> Float { - Float(CoreFloat::Variable(VariableHandle::new( - MAX_ID.fetch_add(1, Ordering::SeqCst), - ))) + 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()), + }), + } } } -impl LuaUserData for Float {} 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 {} +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"; @@ -68,9 +177,10 @@ fn font(_: &Lua, params: LuaTable) -> LuaResult { Some(_) => return Err(LuaError::RuntimeError("Unknown weight".to_string())), }; - let size = params - .get::<_, Option<_>>("size")? - .unwrap_or_else(Float::new); + let size = match params.get::<_, Option>("size")? { + Some(f) => Float::try_from(f)?, + None => Float::new(), + }; Ok(FontDescription(CoreFontDescription { family, @@ -80,46 +190,272 @@ fn font(_: &Lua, params: LuaTable) -> LuaResult { })) } +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(CoreText); +struct Text(Drawable); impl LuaUserData for Text { fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { methods.add_method("draw", |_, this, _params: ()| { - println!("I'm drawing: {}", this.0.content); + 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 = params.get("content")?; + let content: String = params.get("content")?; let font = params .get::<_, Option>("font")? .unwrap_or_default(); - Ok(Text(CoreText { - content, - font: font.0, + 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<()> { - let content: LuaTable = params.get("content")?; - let output: LuaTable = params.get("output")?; + // 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); + }); - let z3_cfg = z3::Config::new(); - let z3_ctx = z3::Context::new(&z3_cfg); - let ctx = Z3Context::new(&z3_ctx); + s.spawn(move || { + REPLIER.with(|f| { + *f.borrow_mut() = Some(reply_sender.clone()); + }); - let cairo_renderer = CairoRenderer::new(); + let z3_cfg = z3::Config::new(); + let z3_ctx = z3::Context::new(&z3_cfg); + let ctx = Z3Context::new(&z3_ctx); - // TODO: we shouldn't need the renderer until the end - let mut runtime = Runtime::new(Box::new(ctx), Box::new(cairo_renderer)); + let cairo_renderer = CairoRenderer::new(); - let _solver = runtime.solver_ctx(); + // TODO: we shouldn't need the renderer until the end + let mut runtime = Runtime::new(Box::new(ctx), Box::new(cairo_renderer)); - dbg!(content, output); + 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(()) } @@ -128,11 +464,91 @@ fn draw(_: &Lua, params: LuaTable) -> LuaResult<()> { 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("float", lua.create_function(float)?)?; + 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) } -- cgit v1.2.3