summaryrefslogtreecommitdiffstats
path: root/lua-bindings
diff options
context:
space:
mode:
authorMinijackson <minijackson@riseup.net>2022-12-29 01:40:08 +0100
committerMinijackson <minijackson@riseup.net>2022-12-29 01:40:08 +0100
commit9c15b76c5a6355902b2a105a7c6ee93f6b5016dc (patch)
tree44cacfe756169f7b3d3cc23f875864683ba2af74 /lua-bindings
parent30f7d39ca2ed4590b5d356b1a4c024d11156a383 (diff)
downloaddiaphragm-9c15b76c5a6355902b2a105a7c6ee93f6b5016dc.tar.gz
diaphragm-9c15b76c5a6355902b2a105a7c6ee93f6b5016dc.zip
WIP v2: text works, primitives works in Lua
Diffstat (limited to 'lua-bindings')
-rw-r--r--lua-bindings/src/lib.rs482
1 files changed, 449 insertions, 33 deletions
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 @@
1use std::{sync::atomic::{AtomicUsize, Ordering}, cell::RefCell}; 1use std::{
2 any::Any,
3 cell::RefCell,
4 sync::mpsc::{sync_channel, Receiver, SyncSender},
5 thread,
6};
2 7
3use diaphragm_cairo_renderer::CairoRenderer; 8use diaphragm_cairo_renderer::CairoRenderer;
4use diaphragm_core::{ 9use diaphragm_core::{
10 core_shapes::{CoreDrawable, CoreShape, Rectangle as CoreRectangle},
5 solving::VariableHandle, 11 solving::VariableHandle,
6 text::{FontDescription as CoreFontDescription, FontStyle, FontWeight, Text as CoreText}, 12 text::{FontDescription as CoreFontDescription, FontStyle, FontWeight, Text as CoreText},
7 types::Float as CoreFloat, 13 types::{Bool as CoreBool, CoreShapeContext, Float as CoreFloat},
8 Runtime, 14 Runtime,
9}; 15};
10use diaphragm_z3_solver::{z3, Z3Context}; 16use diaphragm_z3_solver::{z3, Z3Context};
11use mlua::prelude::*; 17use mlua::prelude::*;
12 18
13static MAX_ID: AtomicUsize = AtomicUsize::new(0);
14
15thread_local! {
16 static SENDER: RefCell<Option<Runtime<'static>>> = RefCell::new(None);
17}
18
19#[derive(Clone, Copy, Debug)] 19#[derive(Clone, Copy, Debug)]
20struct Float(CoreFloat); 20struct Float(CoreFloat);
21 21
22impl Float { 22impl Float {
23 fn new() -> Float { 23 fn new() -> Float {
24 Float(CoreFloat::Variable(VariableHandle::new( 24 Float(runtime_thread_do(Box::new(|r| {
25 MAX_ID.fetch_add(1, Ordering::SeqCst), 25 r.solver_ctx().new_free_float()
26 ))) 26 })))
27 }
28}
29impl LuaUserData for Float {
30 fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
31 macro_rules! float_method {
32 ($name: expr, $op: ident) => {
33 methods.add_method($name, |_lua, lhs: &Float, rhs: LuaValue| {
34 let lhs = *lhs;
35 let rhs = Float::try_from(rhs).unwrap();
36 Ok(Bool(runtime_thread_do(Box::new(move |r| {
37 r.solver_ctx().$op(lhs.0, rhs.0)
38 }))))
39 });
40 };
41 }
42
43 float_method!("eq", float_eq);
44 float_method!("ne", float_ne);
45 float_method!("gt", float_gt);
46 float_method!("ge", float_ge);
47 float_method!("lt", float_lt);
48 float_method!("le", float_le);
49 }
50}
51
52impl TryFrom<LuaValue<'_>> for Float {
53 type Error = LuaError;
54
55 fn try_from(value: LuaValue) -> Result<Self, Self::Error> {
56 match value {
57 LuaValue::Integer(i) => Ok(Float(CoreFloat::Fixed(i as _))),
58 LuaValue::Number(f) => Ok(Float(CoreFloat::Fixed(f))),
59 // Create a new float from the borrow, since it might already be borrowed, with for ex.
60 // f:eq(f)
61 LuaValue::UserData(u) => Ok(Float(u.borrow::<Float>().unwrap().0)),
62 _ => Err(LuaError::FromLuaConversionError {
63 from: "Value",
64 to: "Float",
65 message: Some("Only int, float, or float() values are allowed".to_string()),
66 }),
67 }
27 } 68 }
28} 69}
29impl LuaUserData for Float {}
30 70
31fn float(_: &Lua, _: ()) -> LuaResult<Float> { 71fn float(_: &Lua, _: ()) -> LuaResult<Float> {
32 Ok(Float::new()) 72 Ok(Float::new())
33} 73}
34 74
75#[derive(Clone, Copy, Debug)]
76struct Bool(CoreBool);
77
78impl LuaUserData for Bool {
79 fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
80 macro_rules! bool_method {
81 ($name: expr, $op: ident) => {
82 methods.add_method($name, |_lua, lhs: &Bool, rhs: LuaValue| {
83 let lhs = *lhs;
84 let rhs = Bool::try_from(rhs).unwrap();
85 Ok(Bool(runtime_thread_do(Box::new(move |r| {
86 r.solver_ctx().$op(lhs.0, rhs.0)
87 }))))
88 });
89 };
90 }
91
92 bool_method!("eq", bool_eq);
93 bool_method!("implies", bool_implies);
94
95 methods.add_method("and", |_lua, lhs: &Bool, rhs: LuaValue| {
96 let lhs = *lhs;
97 let rhs = Bool::try_from(rhs).unwrap();
98 Ok(Bool(runtime_thread_do(Box::new(move |r| {
99 r.solver_ctx().bool_and(&[lhs.0, rhs.0])
100 }))))
101 });
102
103 methods.add_method("or", |_lua, lhs: &Bool, rhs: LuaValue| {
104 let lhs = *lhs;
105 let rhs = Bool::try_from(rhs).unwrap();
106 Ok(Bool(runtime_thread_do(Box::new(move |r| {
107 r.solver_ctx().bool_or(&[lhs.0, rhs.0])
108 }))))
109 });
110
111 methods.add_method("no", |_lua, b: &Bool, _: ()| {
112 let b = *b;
113 Ok(Bool(runtime_thread_do(Box::new(move |r| {
114 r.solver_ctx().bool_not(b.0)
115 }))))
116 });
117 }
118}
119
120impl TryFrom<LuaValue<'_>> for Bool {
121 type Error = LuaError;
122
123 fn try_from(value: LuaValue) -> Result<Self, Self::Error> {
124 match value {
125 LuaValue::Boolean(b) => Ok(Bool(runtime_thread_do(Box::new(move |r| {
126 r.solver_ctx().new_fixed_bool(b)
127 })))),
128 // Create a new bool from the borrow, since it might already be borrowed, with for ex.
129 // b:eq(b)
130 LuaValue::UserData(u) => Ok(Bool(u.borrow::<Bool>().unwrap().0)),
131 _ => Err(LuaError::FromLuaConversionError {
132 from: "Value",
133 to: "Bool",
134 message: Some("Only bool, or bool() values are allowed".to_string()),
135 }),
136 }
137 }
138}
139
35#[derive(Clone, Debug)] 140#[derive(Clone, Debug)]
36struct FontDescription(CoreFontDescription); 141struct FontDescription(CoreFontDescription);
37impl LuaUserData for FontDescription {} 142impl LuaUserData for FontDescription {
143 fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
144 fields.add_field_method_get("size", |_, this| Ok(Float(this.0.size)))
145 }
146}
38 147
39const DEFAULT_FONT_FAMILY: &str = "serif"; 148const DEFAULT_FONT_FAMILY: &str = "serif";
40 149
@@ -68,9 +177,10 @@ fn font(_: &Lua, params: LuaTable) -> LuaResult<FontDescription> {
68 Some(_) => return Err(LuaError::RuntimeError("Unknown weight".to_string())), 177 Some(_) => return Err(LuaError::RuntimeError("Unknown weight".to_string())),
69 }; 178 };
70 179
71 let size = params 180 let size = match params.get::<_, Option<LuaValue>>("size")? {
72 .get::<_, Option<_>>("size")? 181 Some(f) => Float::try_from(f)?,
73 .unwrap_or_else(Float::new); 182 None => Float::new(),
183 };
74 184
75 Ok(FontDescription(CoreFontDescription { 185 Ok(FontDescription(CoreFontDescription {
76 family, 186 family,
@@ -80,46 +190,272 @@ fn font(_: &Lua, params: LuaTable) -> LuaResult<FontDescription> {
80 })) 190 }))
81} 191}
82 192
193fn new_shape_context() -> CoreShapeContext {
194 runtime_thread_do(Box::new(|r| CoreShapeContext::new(r.solver_ctx())))
195}
196
197#[derive(Debug, Clone)]
198struct Drawable<T>
199where
200 T: Into<CoreShape>,
201{
202 shape: T,
203 context: CoreShapeContext,
204}
205
206trait HasContext {
207 fn get_context(&self) -> &CoreShapeContext;
208}
209
210fn add_bound_fields<'lua, T, F>(fields: &mut F)
211where
212 T: LuaUserData,
213 T: HasContext,
214 F: LuaUserDataFields<'lua, T>,
215{
216 macro_rules! bound_field {
217 ($name: expr, $method: ident) => {
218 fields.add_field_method_get($name, |_, this| {
219 let bounds = this.get_context().bounds().clone();
220 Ok(Float(runtime_thread_do(Box::new(move |r| {
221 bounds.$method(r.solver_ctx())
222 }))))
223 })
224 };
225 }
226
227 bound_field!("top", top);
228 bound_field!("left", left);
229 bound_field!("width", width);
230 bound_field!("height", height);
231 bound_field!("right", right);
232 bound_field!("bottom", bottom);
233 bound_field!("vert_center", vert_center);
234 bound_field!("horiz_center", horiz_center);
235}
236
237impl<T: Into<CoreShape>> From<Drawable<T>> for CoreDrawable {
238 fn from(value: Drawable<T>) -> Self {
239 CoreDrawable::new(value.shape.into(), value.context)
240 }
241}
242
83#[derive(Clone, Debug)] 243#[derive(Clone, Debug)]
84struct Text(CoreText); 244struct Text(Drawable<CoreText>);
85impl LuaUserData for Text { 245impl LuaUserData for Text {
86 fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { 246 fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
87 methods.add_method("draw", |_, this, _params: ()| { 247 methods.add_method("draw", |_, this, _params: ()| {
88 println!("I'm drawing: {}", this.0.content); 248 let drawable = this.0.clone().into();
249 runtime_thread_do(Box::new(|r| {
250 r.add_drawable(drawable);
251 }));
89 Ok(()) 252 Ok(())
90 }) 253 })
91 } 254 }
255
256 fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
257 add_bound_fields(fields);
258 }
259}
260
261impl HasContext for Text {
262 fn get_context(&self) -> &CoreShapeContext {
263 &self.0.context
264 }
92} 265}
93 266
94fn text(_: &Lua, params: LuaTable) -> LuaResult<Text> { 267fn text(_: &Lua, params: LuaTable) -> LuaResult<Text> {
95 let content = params.get("content")?; 268 let content: String = params.get("content")?;
96 269
97 let font = params 270 let font = params
98 .get::<_, Option<FontDescription>>("font")? 271 .get::<_, Option<FontDescription>>("font")?
99 .unwrap_or_default(); 272 .unwrap_or_default();
100 273
101 Ok(Text(CoreText { 274 let context = new_shape_context();
102 content, 275
103 font: font.0, 276 let content_2 = content.clone();
277 let font_2 = font.0.clone();
278 let context_2 = context.clone();
279
280 runtime_thread_do(Box::new(move |r| {
281 let (text_width, text_height) = r.renderer().text_extents(&content_2, &font_2);
282 let solver = r.solver_ctx();
283 // let scale = solver.float_div(font.0.size, CoreFloat::Fixed(height));
284
285 let calculated_width = solver.float_mul(&[CoreFloat::Fixed(text_width), font.0.size]);
286 let bounds_width = context_2.bounds().width(solver);
287 let width_constraint = solver.float_eq(bounds_width, calculated_width);
288 solver.constrain(width_constraint);
289
290 let calculated_height = solver.float_mul(&[CoreFloat::Fixed(text_height), font.0.size]);
291 let bounds_height = context_2.bounds().height(solver);
292 let height_constraint = solver.float_eq(bounds_height, calculated_height);
293 solver.constrain(height_constraint);
294 }));
295
296 Ok(Text(Drawable {
297 shape: CoreText {
298 content,
299 font: font.0,
300 },
301 context,
302 }))
303}
304
305#[derive(Clone, Debug)]
306struct Rectangle(Drawable<CoreRectangle>);
307impl LuaUserData for Rectangle {
308 fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
309 methods.add_method("draw", |_, this, _params: ()| {
310 let drawable = this.0.clone().into();
311 runtime_thread_do(Box::new(|r| {
312 r.add_drawable(drawable);
313 }));
314 Ok(())
315 })
316 }
317
318 fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
319 add_bound_fields(fields);
320 }
321}
322
323impl HasContext for Rectangle {
324 fn get_context(&self) -> &CoreShapeContext {
325 &self.0.context
326 }
327}
328
329fn rectangle(_: &Lua, _params: ()) -> LuaResult<Rectangle> {
330 Ok(Rectangle(Drawable {
331 shape: CoreRectangle {},
332 context: new_shape_context(),
104 })) 333 }))
105} 334}
106 335
336thread_local! {
337 static SENDER: RefCell<Option<SyncSender<Message>>> = RefCell::new(None);
338 static REPLY: RefCell<Option<Receiver<Reply>>> = RefCell::new(None);
339 static REPLIER: RefCell<Option<SyncSender<Reply>>> = RefCell::new(None);
340}
341
342enum Message {
343 Do(Box<dyn FnOnce(&mut Runtime) + Send>),
344}
345
346fn runtime_thread_do<T: Any + Send>(fun: Box<dyn FnOnce(&mut Runtime) -> T + Send>) -> T {
347 SENDER.with(|sender| {
348 sender
349 .borrow_mut()
350 .as_mut()
351 .expect("Not currently drawing")
352 .send(Message::Do(Box::new(|r| {
353 let ret = fun(r);
354 REPLIER.with(|replier| {
355 replier
356 .borrow_mut()
357 .as_mut()
358 .unwrap()
359 .send(Reply::Any(Box::new(ret)))
360 .unwrap();
361 });
362 })))
363 .unwrap();
364 });
365
366 REPLY.with(|reply| {
367 let Reply::Any(any) = reply
368 .borrow_mut()
369 .as_mut()
370 .expect("Not currently drawing")
371 .recv()
372 .unwrap();
373 *any.downcast().unwrap()
374 })
375}
376
377enum Reply {
378 Any(Box<dyn Any + Send>),
379}
380
381fn constrain(_: &Lua, bool: Bool) -> LuaResult<()> {
382 runtime_thread_do(Box::new(move |r| r.solver_ctx().constrain(bool.0)));
383 Ok(())
384}
385
107fn draw(_: &Lua, params: LuaTable) -> LuaResult<()> { 386fn draw(_: &Lua, params: LuaTable) -> LuaResult<()> {
108 let content: LuaTable = params.get("content")?; 387 // So.... The Z3 stuff isn't Send and contains lifetimes, so we can't store them in global
109 let output: LuaTable = params.get("output")?; 388 // variables or convert them to Lua. Solution: handle everything in a specific thread, and
389 // communicate through a channel.
390 thread::scope(|s| {
391 let (message_sender, message_receiver) = sync_channel(1);
392 let (reply_sender, reply_receiver) = sync_channel(1);
393
394 SENDER.with(|f| {
395 *f.borrow_mut() = Some(message_sender);
396 });
397
398 REPLY.with(|f| {
399 *f.borrow_mut() = Some(reply_receiver);
400 });
110 401
111 let z3_cfg = z3::Config::new(); 402 s.spawn(move || {
112 let z3_ctx = z3::Context::new(&z3_cfg); 403 REPLIER.with(|f| {
113 let ctx = Z3Context::new(&z3_ctx); 404 *f.borrow_mut() = Some(reply_sender.clone());
405 });
114 406
115 let cairo_renderer = CairoRenderer::new(); 407 let z3_cfg = z3::Config::new();
408 let z3_ctx = z3::Context::new(&z3_cfg);
409 let ctx = Z3Context::new(&z3_ctx);
116 410
117 // TODO: we shouldn't need the renderer until the end 411 let cairo_renderer = CairoRenderer::new();
118 let mut runtime = Runtime::new(Box::new(ctx), Box::new(cairo_renderer));
119 412
120 let _solver = runtime.solver_ctx(); 413 // TODO: we shouldn't need the renderer until the end
414 let mut runtime = Runtime::new(Box::new(ctx), Box::new(cairo_renderer));
121 415
122 dbg!(content, output); 416 for message in message_receiver {
417 match message {
418 Message::Do(fun) => {
419 fun(&mut runtime);
420 }
421 }
422 }
423
424 runtime.render();
425 });
426
427 let content: LuaValue = params.get("content").unwrap();
428 let _output: LuaTable = params.get("output").unwrap();
429
430 match content {
431 LuaValue::Table(table) => {
432 let () = table.call_method("draw", ()).unwrap();
433 }
434 // TODO: switch to enum
435 // LuaValue::UserData(user_data) => {
436 // if user_data.is::<Text>() {
437 // let text = user_data
438 // .borrow::<Text>()
439 // .expect("Couldn't borrow Text user data");
440 //
441 // SENDER.with(|f| {
442 // f.borrow_mut()
443 // .as_ref()
444 // .expect("Not currently drawing")
445 // .send(Message::AddDrawable(CoreShape::Text(text.0.clone())))
446 // .expect("Could not send shape");
447 // })
448 // } else {
449 // panic!("Non-drawable passed to draw");
450 // }
451 // }
452 _ => panic!("Non-drawable passed to draw"),
453 }
454
455 SENDER.with(|s| {
456 *s.borrow_mut() = None;
457 });
458 });
123 459
124 Ok(()) 460 Ok(())
125} 461}
@@ -128,11 +464,91 @@ fn draw(_: &Lua, params: LuaTable) -> LuaResult<()> {
128fn libdiaphragm(lua: &Lua) -> LuaResult<LuaTable> { 464fn libdiaphragm(lua: &Lua) -> LuaResult<LuaTable> {
129 // TODO: the solver as a mutable global solves so much problem (pun not intended) 465 // TODO: the solver as a mutable global solves so much problem (pun not intended)
130 let exports = lua.create_table()?; 466 let exports = lua.create_table()?;
467 exports.set("float", lua.create_function(float)?)?;
468
131 exports.set("text", lua.create_function(text)?)?; 469 exports.set("text", lua.create_function(text)?)?;
132 exports.set("font", lua.create_function(font)?)?; 470 exports.set("font", lua.create_function(font)?)?;
133 exports.set("float", lua.create_function(float)?)?;
134 471
472 exports.set("rectangle", lua.create_function(rectangle)?)?;
473
474 exports.set("constrain", lua.create_function(constrain)?)?;
135 exports.set("draw", lua.create_function(draw)?)?; 475 exports.set("draw", lua.create_function(draw)?)?;
136 476
477 // Setting up metatables
478 // ---
479
480 let float_metatable = lua
481 .create_userdata(Float(CoreFloat::Fixed(0.)))
482 .unwrap()
483 .get_metatable()
484 .unwrap();
485
486 macro_rules! float_metamethod {
487 ($method: ident, $op: ident) => {
488 float_metatable
489 .set(
490 LuaMetaMethod::$method,
491 lua.create_function(|_lua, (lhs, rhs): (Float, LuaValue)| {
492 let rhs = Float::try_from(rhs).unwrap();
493 Ok(Float(runtime_thread_do(Box::new(move |r| {
494 r.solver_ctx().$op(&[lhs.0, rhs.0])
495 }))))
496 })
497 .unwrap(),
498 )
499 .unwrap();
500 };
501 }
502
503 float_metamethod!(Add, float_add);
504 float_metamethod!(Sub, float_sub);
505 float_metamethod!(Mul, float_mul);
506
507 float_metatable
508 .set(
509 LuaMetaMethod::Div,
510 lua.create_function(|_lua, (lhs, rhs): (Float, LuaValue)| {
511 let rhs = Float::try_from(rhs).unwrap();
512 Ok(Float(runtime_thread_do(Box::new(move |r| {
513 r.solver_ctx().float_div(lhs.0, rhs.0)
514 }))))
515 })
516 .unwrap(),
517 )
518 .unwrap();
519
520 float_metatable
521 .set(
522 LuaMetaMethod::Unm,
523 lua.create_function(|_lua, f: Float| {
524 Ok(Float(runtime_thread_do(Box::new(move |r| {
525 r.solver_ctx().float_neg(f.0)
526 }))))
527 })
528 .unwrap(),
529 )
530 .unwrap();
531
532 // Not the operators `==`, `!=`, `<`, `<=`, `>`, and `>=` because Lua converts result to bool
533
534 let bool_metatable = lua
535 .create_userdata(Bool(CoreBool::new(VariableHandle::new(0))))
536 .unwrap()
537 .get_metatable()
538 .unwrap();
539
540 bool_metatable
541 .set(
542 LuaMetaMethod::Shl,
543 lua.create_function(|_lua, (lhs, rhs): (Bool, LuaValue)| {
544 let rhs = Bool::try_from(rhs).unwrap();
545 Ok(Bool(runtime_thread_do(Box::new(move |r| {
546 r.solver_ctx().bool_implies(lhs.0, rhs.0)
547 }))))
548 })
549 .unwrap(),
550 )
551 .unwrap();
552
137 Ok(exports) 553 Ok(exports)
138} 554}