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 --- cairo-renderer/src/lib.rs | 45 ++--- core/src/core_shapes.rs | 73 +++++-- core/src/lib.rs | 8 +- core/src/rendering.rs | 53 ++--- core/src/runtime.rs | 154 ++++++++------- core/src/solving.rs | 55 ++++-- core/src/types.rs | 110 +++++------ lua-bindings/src/lib.rs | 482 ++++++++++++++++++++++++++++++++++++++++++---- z3-solver/src/lib.rs | 21 +- 9 files changed, 761 insertions(+), 240 deletions(-) diff --git a/cairo-renderer/src/lib.rs b/cairo-renderer/src/lib.rs index d912afc..df6f8fd 100644 --- a/cairo-renderer/src/lib.rs +++ b/cairo-renderer/src/lib.rs @@ -1,5 +1,5 @@ use diaphragm_core::{ - styles::{Pattern, DefinedDashStyle}, + styles::{DefinedDashStyle, Pattern}, text::{DefinedFontDescription, FontDescription}, Renderer, }; @@ -67,28 +67,29 @@ impl Renderer for CairoRenderer { } fn text_extents(&self, text: &str, font: &FontDescription) -> (f64, f64) { + // Pango gives us integer back, so we lose some precision. We use this for that. + const TEST_ABSOLUTE_SIZE: f64 = 1_000_000.; + let layout = pangocairo::create_layout(&self.ctx); - let font_desc = pango::FontDescription::from_string(&font.family); + let mut font_desc = pango::FontDescription::from_string(&font.family); + font_desc.set_absolute_size(TEST_ABSOLUTE_SIZE); layout.set_font_description(Some(&font_desc)); layout.set_markup(text); - //let (extents, _) = layout.get_pixel_extents(); - //(extents.width as f64, extents.height as f64) - // TODO: get height from the baseline - let mut layout_iter = layout.iter(); - let _height = loop { - if layout_iter.at_last_line() { - break layout_iter.baseline(); - } - - layout_iter.next_line(); - }; - - // TODO: Probably should use the logical extents, but it has weird width - let (_, extents) = layout.pixel_extents(); - //let (extents, _) = layout.get_pixel_extents(); - (extents.width() as f64, extents.height() as f64) + // let mut layout_iter = layout.iter(); + // let _height = loop { + // if layout_iter.at_last_line() { + // break layout_iter.baseline(); + // } + // layout_iter.next_line(); + // }; + + let (_, extents) = dbg!(layout.pixel_extents()); + ( + extents.width() as f64 / TEST_ABSOLUTE_SIZE, + extents.height() as f64 / TEST_ABSOLUTE_SIZE, + ) //let (width, height) = layout.get_pixel_size(); //(width as f64, height as f64) @@ -98,14 +99,14 @@ impl Renderer for CairoRenderer { let layout = pangocairo::create_layout(&self.ctx); let mut font_desc = pango::FontDescription::from_string(&font.family); - // TODO: I have no fucking idea why - font_desc.set_size((font.size * 600.) as _); - //font_desc.set_size((font.size * 700.) as _); - //font_desc.set_absolute_size(font.size * 800.); + font_desc.set_absolute_size(dbg!(font.size) as _); layout.set_font_description(Some(&font_desc)); layout.set_markup(text); //self.ctx.set_font_size(dbg!(font.size)); pangocairo::show_layout(&self.ctx, &layout); + + dbg!(pangocairo::context_get_resolution(&layout.context())); + dbg!(layout.pixel_extents()); } } diff --git a/core/src/core_shapes.rs b/core/src/core_shapes.rs index 805d82e..d8017ab 100644 --- a/core/src/core_shapes.rs +++ b/core/src/core_shapes.rs @@ -1,8 +1,44 @@ -use super::complex_shapes::{ComplexShape, DrawResult}; -use super::rendering::{Render, Renderer}; -use super::solving::{Constrainable, SolverContext, SolverModel}; -use super::types::*; +pub struct CoreDrawable { + pub(crate) shape: CoreShape, + pub(crate) context: CoreShapeContext, +} + +impl CoreDrawable { + pub fn new(shape: CoreShape, context: CoreShapeContext) -> Self { + Self { shape, context } + } +} + +pub enum CoreShape { + Rectangle(Rectangle), + Text(Text), + // StraightPath(StraightPath), +} +impl From for CoreShape { + fn from(rectangle: Rectangle) -> Self { + CoreShape::Rectangle(rectangle) + } +} + +impl From for CoreShape { + fn from(text: Text) -> Self { + CoreShape::Text(text) + } +} + +pub struct DefinedCoreDrawable { + pub(crate) shape: DefinedCoreShape, + pub(crate) context: DefinedCoreShapeContext, +} + +pub enum DefinedCoreShape { + Rectangle(Rectangle), + Text(DefinedText), + // StraightPath(StraightPath), +} + +/* pub trait CoreShape { fn constrain( &self, @@ -23,19 +59,25 @@ impl ComplexShape for T { panic!("Tried to decompose core shape") } } +*/ // TODO: add default #[derive(Copy, Clone, Debug, Default)] pub struct Rectangle {} +/* impl CoreShape for Rectangle { fn to_render(&self, _model: &dyn SolverModel) -> Option> { Some(Box::new(*self)) } } +*/ + +use crate::types::{CoreShapeContext, DefinedCoreShapeContext}; pub use super::text::{DefinedText, Text}; +/* impl CoreShape for Text { fn constrain( &self, @@ -62,18 +104,20 @@ impl CoreShape for Text { .map(|path| -> Box { Box::new(path) }) } } +*/ -#[derive(Clone, Debug, Default)] -pub struct StraightPath { - pub(crate) points: Vec, -} - -impl StraightPath { - pub fn new(points: Vec) -> Self { - Self { points } - } -} +// #[derive(Clone, Debug, Default)] +// pub struct StraightPath { +// pub(crate) points: Vec, +// } +// +// impl StraightPath { +// pub fn new(points: Vec) -> Self { +// Self { points } +// } +// } +/* pub struct DefinedStraightPath { pub(crate) points: Vec, } @@ -84,3 +128,4 @@ impl CoreShape for StraightPath { .map(|path| -> Box { Box::new(path) }) } } +*/ diff --git a/core/src/lib.rs b/core/src/lib.rs index 57e45df..f949cd9 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -2,7 +2,7 @@ #![deny(unsafe_code)] pub mod colors; -mod complex_shapes; +// mod complex_shapes; pub mod core_shapes; mod rendering; mod runtime; @@ -11,9 +11,9 @@ pub mod styles; pub mod text; pub mod types; -pub use complex_shapes::{ - ComplexShape, DrawResult, Drawable, DrawableBuilder, DynClone, DynDrawable, -}; +// pub use complex_shapes::{ +// ComplexShape, DrawResult, Drawable, DrawableBuilder, DynClone, DynDrawable, +// }; pub use rendering::Renderer; pub use runtime::Runtime; pub use solving::{SolverContext, SolverModel}; diff --git a/core/src/rendering.rs b/core/src/rendering.rs index f7a0189..9c9f9e4 100644 --- a/core/src/rendering.rs +++ b/core/src/rendering.rs @@ -1,7 +1,7 @@ use super::core_shapes::*; use super::styles::{DefinedDashStyle, DefinedStrokeStyle, FillStyle, Pattern}; use super::text::{DefinedFontDescription, FontDescription}; -use super::types::DefinedShapeContext; +use super::types::DefinedCoreShapeContext; pub trait Renderer { fn move_to(&mut self, x: f64, y: f64); @@ -20,7 +20,16 @@ pub trait Renderer { } pub trait Render { - fn render(&self, context: DefinedShapeContext, renderer: &mut dyn Renderer); + fn render(&self, context: DefinedCoreShapeContext, renderer: &mut dyn Renderer); +} + +impl Render for DefinedCoreShape { + fn render(&self, context: DefinedCoreShapeContext, renderer: &mut dyn Renderer) { + match self { + Self::Rectangle(r) => r.render(context, renderer), + Self::Text(t) => t.render(context, renderer), + } + } } fn draw(fill: &FillStyle, stroke: &DefinedStrokeStyle, renderer: &mut dyn Renderer) { @@ -47,7 +56,7 @@ fn draw(fill: &FillStyle, stroke: &DefinedStrokeStyle, renderer: &mut dyn Render } impl Render for Rectangle { - fn render(&self, context: DefinedShapeContext, renderer: &mut dyn Renderer) { + fn render(&self, context: DefinedCoreShapeContext, renderer: &mut dyn Renderer) { let bounds = &context.bounds; renderer.rectangle(bounds.left, bounds.top, bounds.width, bounds.height); draw(&context.fill, &context.stroke, renderer); @@ -55,7 +64,7 @@ impl Render for Rectangle { } impl Render for DefinedText { - fn render(&self, context: DefinedShapeContext, renderer: &mut dyn Renderer) { + fn render(&self, context: DefinedCoreShapeContext, renderer: &mut dyn Renderer) { // TODO: select font, style, text shaping (renderer specific), etc. let bounds = &context.bounds; //renderer.move_to(bounds.left, bounds.top + self.font.size); @@ -66,21 +75,21 @@ impl Render for DefinedText { } } -impl Render for DefinedStraightPath { - fn render(&self, context: DefinedShapeContext, renderer: &mut dyn Renderer) { - let mut iter = self.points.iter(); - - let first_point = match iter.next() { - Some(point) => point, - None => return, - }; - - renderer.move_to(first_point.x, first_point.y); - - for point in iter { - renderer.line_to(point.x, point.y); - } - - draw(&context.fill, &context.stroke, renderer); - } -} +// impl Render for DefinedStraightPath { +// fn render(&self, context: DefinedShapeContext, renderer: &mut dyn Renderer) { +// let mut iter = self.points.iter(); +// +// let first_point = match iter.next() { +// Some(point) => point, +// None => return, +// }; +// +// renderer.move_to(first_point.x, first_point.y); +// +// for point in iter { +// renderer.line_to(point.x, point.y); +// } +// +// draw(&context.fill, &context.stroke, renderer); +// } +// } diff --git a/core/src/runtime.rs b/core/src/runtime.rs index bfb31fa..d70e4d0 100644 --- a/core/src/runtime.rs +++ b/core/src/runtime.rs @@ -1,13 +1,17 @@ -use super::complex_shapes::{ComplexShape, Drawable, DynDrawable}; +use crate::core_shapes::CoreDrawable; +use crate::rendering::Render; + +// use super::complex_shapes::{ComplexShape, Drawable}; use super::rendering::Renderer; use super::solving::{Constrainable, SolverContext}; -const RECURSION_LIMIT: u64 = 10_000; +// const RECURSION_LIMIT: u64 = 10_000; pub struct Runtime<'a> { solver_ctx: Box, renderer: Box, - drawables: Vec, + // drawables: Vec, + drawables: Vec, } impl<'a> Runtime<'a> { @@ -15,81 +19,101 @@ impl<'a> Runtime<'a> { Self { solver_ctx, renderer, + // drawables: Vec::new(), drawables: Vec::new(), } } - pub fn add_drawable(&mut self, drawable: Drawable) { - self.drawables.push(drawable.into()) + // pub fn add_drawable(&mut self, drawable: Drawable) { + // self.drawables.push(drawable.into()) + // } + + pub fn add_drawable(&mut self, drawable: CoreDrawable) { + self.drawables.push(drawable) } pub fn solver_ctx(&mut self) -> &mut (dyn SolverContext + 'a) { &mut *self.solver_ctx } - // TODO: preserve ordering of shapes - pub fn render(mut self) { - let mut drawables = self.drawables; - let mut waited_on_variables = Vec::new(); - let mut core_shapes = Vec::new(); - - /* - for drawable in &self.shapes { - let bounds = &drawable.bounds; - let shape = &drawable.shape; - - if let Some(core_shape) = shape.to_render() { - drawables.push((*drawable).clone()); - continue; - } - - let mut result = shape.draw(bounds); - drawables.append(&mut result.subshapes); - waited_on_variables.append(&mut result.waiting_on); - } - */ - - let mut recursion_count = 0; - - while !drawables.is_empty() { - recursion_count += 1; - - if recursion_count > RECURSION_LIMIT { - panic!("Recursion limit reached"); - } - - let mut tmp_drawables = Vec::new(); - - for drawable in drawables.drain(..) { - let shape_ctx = &drawable.context; - let shape = &drawable.shape; - - if let Some(core_shape) = shape.as_core_shape() { - core_shape.constrain(shape_ctx, &mut *self.solver_ctx, &*self.renderer); - core_shapes.push((shape.dyn_clone(), shape_ctx.clone())); // Better to Arc? Cow? - continue; - } - - let mut result = shape.draw(shape_ctx, &mut *self.solver_ctx); - tmp_drawables.append(&mut result.subshapes); - waited_on_variables.append(&mut result.waiting_on); - } - - drawables = tmp_drawables; - } + pub fn renderer(&mut self) -> &mut dyn Renderer { + &mut *self.renderer + } + pub fn render(mut self) { let model = self.solver_ctx.solve(); - // Delay rendering core shapes until later to have all the constraints - for (core_shape, shape_ctx) in core_shapes { - let core_shape = core_shape.as_core_shape().unwrap(); - - match (core_shape.to_render(&*model), shape_ctx.fixate(&*model)) { - (Some(defined_shape), Some(shape_ctx)) => { - defined_shape.render(shape_ctx, &mut *self.renderer) - } - _ => panic!("Failed to fixate core shape"), - } + for drawable in &self.drawables { + let defined_drawable = drawable + .fixate(&*model) + .expect("Could not fixate core shape"); + defined_drawable.shape.render(defined_drawable.context, &mut *self.renderer); } } + + // TODO: preserve ordering of shapes + // pub fn render(mut self) { + // let mut drawables = self.drawables; + // let mut waited_on_variables = Vec::new(); + // let mut core_shapes = Vec::new(); + // + // /* + // for drawable in &self.shapes { + // let bounds = &drawable.bounds; + // let shape = &drawable.shape; + // + // if let Some(core_shape) = shape.to_render() { + // drawables.push((*drawable).clone()); + // continue; + // } + // + // let mut result = shape.draw(bounds); + // drawables.append(&mut result.subshapes); + // waited_on_variables.append(&mut result.waiting_on); + // } + // */ + // + // let mut recursion_count = 0; + // + // while !drawables.is_empty() { + // recursion_count += 1; + // + // if recursion_count > RECURSION_LIMIT { + // panic!("Recursion limit reached"); + // } + // + // let mut tmp_drawables = Vec::new(); + // + // for drawable in drawables.drain(..) { + // let shape_ctx = &drawable.context; + // let shape = &drawable.shape; + // + // if let Some(core_shape) = shape.as_core_shape() { + // core_shape.constrain(shape_ctx, &mut *self.solver_ctx, &*self.renderer); + // core_shapes.push((shape.dyn_clone(), shape_ctx.clone())); // Better to Arc? Cow? + // continue; + // } + // + // let mut result = shape.draw(shape_ctx, &mut *self.solver_ctx); + // tmp_drawables.append(&mut result.subshapes); + // waited_on_variables.append(&mut result.waiting_on); + // } + // + // drawables = tmp_drawables; + // } + // + // let model = self.solver_ctx.solve(); + // + // // Delay rendering core shapes until later to have all the constraints + // for (core_shape, shape_ctx) in core_shapes { + // let core_shape = core_shape.as_core_shape().unwrap(); + // + // match (core_shape.to_render(&*model), shape_ctx.fixate(&*model)) { + // (Some(defined_shape), Some(shape_ctx)) => { + // defined_shape.render(shape_ctx, &mut *self.renderer) + // } + // _ => panic!("Failed to fixate core shape"), + // } + // } + // } } diff --git a/core/src/solving.rs b/core/src/solving.rs index 4760611..261b4d4 100644 --- a/core/src/solving.rs +++ b/core/src/solving.rs @@ -1,4 +1,5 @@ -use super::core_shapes::*; +use crate::core_shapes::{CoreDrawable, CoreShape, DefinedCoreDrawable, DefinedCoreShape}; + use super::styles::*; use super::text::*; use super::types::{Bool, Float}; @@ -147,6 +148,17 @@ pub trait Constrainable { fn fixate(&self, model: &dyn SolverModel) -> Option; } +impl Constrainable for CoreShape { + type Fixated = DefinedCoreShape; + + fn fixate(&self, model: &dyn SolverModel) -> Option { + match self { + CoreShape::Rectangle(r) => Some(DefinedCoreShape::Rectangle(*r)), + CoreShape::Text(t) => t.fixate(model).map(DefinedCoreShape::Text), + } + } +} + impl Constrainable for Float { type Fixated = f64; @@ -218,11 +230,22 @@ impl Constrainable for DashStyle { } } -impl Constrainable for ShapeContext { - type Fixated = DefinedShapeContext; +impl Constrainable for CoreDrawable { + type Fixated = DefinedCoreDrawable; + + fn fixate(&self, model: &dyn SolverModel) -> Option { + Some(DefinedCoreDrawable { + shape: self.shape.fixate(model)?, + context: self.context.fixate(model)?, + }) + } +} + +impl Constrainable for CoreShapeContext { + type Fixated = DefinedCoreShapeContext; fn fixate(&self, model: &dyn SolverModel) -> Option { - Some(DefinedShapeContext { + Some(DefinedCoreShapeContext { bounds: self.bounds.fixate(model)?, fill: self.fill.clone(), stroke: self.stroke.fixate(model)?, @@ -265,15 +288,15 @@ impl Constrainable for Text { } } -impl Constrainable for StraightPath { - type Fixated = DefinedStraightPath; - - fn fixate(&self, model: &dyn SolverModel) -> Option { - let points: Option<_> = self - .points - .iter() - .map(|point| point.fixate(model)) - .collect(); - Some(DefinedStraightPath { points: points? }) - } -} +// impl Constrainable for StraightPath { +// type Fixated = DefinedStraightPath; +// +// fn fixate(&self, model: &dyn SolverModel) -> Option { +// let points: Option<_> = self +// .points +// .iter() +// .map(|point| point.fixate(model)) +// .collect(); +// Some(DefinedStraightPath { points: points? }) +// } +// } diff --git a/core/src/types.rs b/core/src/types.rs index 1b7623a..96e059a 100644 --- a/core/src/types.rs +++ b/core/src/types.rs @@ -99,19 +99,19 @@ impl Bounds { BoundsBuilder::default() } - pub fn top(&self, _ctx: &mut dyn SolverContext) -> Float { + pub fn top(&self, _solver: &mut dyn SolverContext) -> Float { self.top } - pub fn left(&self, _ctx: &mut dyn SolverContext) -> Float { + pub fn left(&self, _solver: &mut dyn SolverContext) -> Float { self.left } - pub fn width(&self, _ctx: &mut dyn SolverContext) -> Float { + pub fn width(&self, _solver: &mut dyn SolverContext) -> Float { self.width } - pub fn height(&self, _ctx: &mut dyn SolverContext) -> Float { + pub fn height(&self, _solver: &mut dyn SolverContext) -> Float { self.height } @@ -133,7 +133,7 @@ impl Bounds { solver.float_add(&[self.left, half_width]) } - pub fn top_left(&self, _ctx: &mut dyn SolverContext) -> Point2D { + pub fn top_left(&self, _solver: &mut dyn SolverContext) -> Point2D { Point2D::new(self.left, self.top) } @@ -206,11 +206,11 @@ impl BoundsBuilder { } } -#[derive(Clone, Debug)] -pub struct Bounded { - pub bounds: Bounds, - pub shape: Shape, -} +// #[derive(Clone, Debug)] +// pub struct Bounded { +// pub bounds: Bounds, +// pub shape: Shape, +// } #[derive(Clone, PartialEq, PartialOrd, Debug)] pub struct DefinedBounds { @@ -220,20 +220,20 @@ pub struct DefinedBounds { pub height: f64, } -#[derive(Clone, PartialEq, PartialOrd, Debug)] -pub struct DefinitelyBounded { - pub bounds: DefinedBounds, - pub shape: Shape, -} +// #[derive(Clone, PartialEq, PartialOrd, Debug)] +// pub struct DefinitelyBounded { +// pub bounds: DefinedBounds, +// pub shape: Shape, +// } #[derive(Clone, Debug)] -pub struct ShapeContext { +pub struct CoreShapeContext { pub(crate) bounds: Bounds, pub(crate) fill: FillStyle, pub(crate) stroke: StrokeStyle, } -impl ShapeContext { +impl CoreShapeContext { pub fn new(solver: &mut dyn SolverContext) -> Self { Self { bounds: Bounds { @@ -247,9 +247,9 @@ impl ShapeContext { } } - pub(crate) fn builder() -> ShapeContextBuilder { - ShapeContextBuilder::default() - } + // pub(crate) fn builder() -> ShapeContextBuilder { + // ShapeContextBuilder::default() + // } pub fn bounds(&self) -> &Bounds { &self.bounds @@ -276,41 +276,41 @@ impl ShapeContext { } } -#[derive(Clone, Default)] -pub(crate) struct ShapeContextBuilder { - bounds: Option, - fill: Option, - stroke: Option, -} - -impl ShapeContextBuilder { - pub fn bounds(&mut self, bounds: Bounds) -> &mut Self { - self.bounds = Some(bounds); - self - } - - pub fn fill(&mut self, fill: FillStyle) -> &mut Self { - self.fill = Some(fill); - self - } - - pub fn stroke(&mut self, stroke: StrokeStyle) -> &mut Self { - self.stroke = Some(stroke); - self - } - - pub fn build(&self, solver: &mut dyn SolverContext) -> ShapeContext { - ShapeContext { - bounds: self.bounds.clone().unwrap_or_else(|| Bounds::builder().build(solver)), - fill: self.fill.clone().unwrap_or_default(), - stroke: self.stroke.clone().unwrap_or_default(), - } - } -} +// #[derive(Clone, Default)] +// pub(crate) struct ShapeContextBuilder { +// bounds: Option, +// fill: Option, +// stroke: Option, +// } +// +// impl ShapeContextBuilder { +// pub fn bounds(&mut self, bounds: Bounds) -> &mut Self { +// self.bounds = Some(bounds); +// self +// } +// +// pub fn fill(&mut self, fill: FillStyle) -> &mut Self { +// self.fill = Some(fill); +// self +// } +// +// pub fn stroke(&mut self, stroke: StrokeStyle) -> &mut Self { +// self.stroke = Some(stroke); +// self +// } +// +// pub fn build(&self, solver: &mut dyn SolverContext) -> ShapeContext { +// ShapeContext { +// bounds: self.bounds.clone().unwrap_or_else(|| Bounds::builder().build(solver)), +// fill: self.fill.clone().unwrap_or_default(), +// stroke: self.stroke.clone().unwrap_or_default(), +// } +// } +// } #[derive(Clone, Debug)] -pub struct DefinedShapeContext { - pub bounds: DefinedBounds, - pub fill: FillStyle, - pub stroke: DefinedStrokeStyle, +pub struct DefinedCoreShapeContext { + pub(crate) bounds: DefinedBounds, + pub(crate) fill: FillStyle, + pub(crate) stroke: DefinedStrokeStyle, } 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) } diff --git a/z3-solver/src/lib.rs b/z3-solver/src/lib.rs index 35a17a8..1c90a6f 100644 --- a/z3-solver/src/lib.rs +++ b/z3-solver/src/lib.rs @@ -30,15 +30,18 @@ impl Drop for Z3Context<'_> { } fn value_to_num_den(value: f64) -> (i32, i32) { - let fract = value.fract(); - let number_of_fract_digits = -fract.log10().floor(); - - if number_of_fract_digits >= 1. && !number_of_fract_digits.is_infinite() { - let den = 10f64.powf(number_of_fract_digits); - ((value * den) as i32, den as i32) - } else { - (value as i32, 1) - } + // TODO: FIXME: so hacky, because I'm so lazy... + ((value * 1_000_000.) as _, 1_000_000) + + // let fract = value.fract(); + // let number_of_fract_digits = -fract.log10().floor(); + // + // if number_of_fract_digits >= 1. && !number_of_fract_digits.is_infinite() { + // let den = 10f64.powf(number_of_fract_digits); + // ((value * den) as i32, den as i32) + // } else { + // (value as i32, 1) + // } } impl<'z3> Z3Context<'z3> { -- cgit v1.2.3