Skip to main content

mumukanvas/
lib.rs

1// FILE: kanvas/src/lib.rs
2
3use std::cell::RefCell;
4use std::collections::HashMap;
5use std::rc::Rc;
6use std::sync::{Arc, Mutex, OnceLock};
7use std::ffi::c_void;
8use std::time::Duration;
9
10use core_mumu::parser::interpreter::{Interpreter, DynamicFn, DynamicFnInfo};
11use core_mumu::parser::types::{FunctionValue, Value};
12
13use minifb::{Window, WindowOptions, Scale, ScaleMode};
14
15/// An enum describing the type of shape (pixel, rectangle, triangle, line).
16/// We store coordinates needed for drawing.
17#[derive(Clone)]
18enum Shape {
19    Pixel {
20        x: i32,
21        y: i32,
22    },
23    Rectangle {
24        x1: i32,
25        y1: i32,
26        x2: i32,
27        y2: i32,
28    },
29    Triangle {
30        x1: i32,
31        y1: i32,
32        x2: i32,
33        y2: i32,
34        x3: i32,
35        y3: i32,
36    },
37    // NEW: Support for line drawing
38    Line {
39        x1: i32,
40        y1: i32,
41        x2: i32,
42        y2: i32,
43    },
44}
45
46/// Each shape item gets a unique handle. We store color + shape info.
47#[derive(Clone)]
48pub struct ShapeItem {
49    handle_id: i32,
50    shape: Shape,
51    color: u32,
52}
53
54/// The main window handle: we store minifb Window, pixel buffer,
55/// plus a display-list of shape items.
56pub struct WindowHandle {
57    pub window: Option<Window>,
58    pub width: usize,
59    pub height: usize,
60    pub buffer: Vec<u32>,
61    pub items: Vec<ShapeItem>,
62}
63
64thread_local! {
65    static REGISTRY: RefCell<HashMap<i32, Rc<RefCell<WindowHandle>>>> =
66        RefCell::new(HashMap::new());
67}
68
69static POLLER_INSTALLED: OnceLock<bool> = OnceLock::new();
70
71////////////////////////////////////////////////////////////////////////////////////////////////////
72// Polling & Registry
73////////////////////////////////////////////////////////////////////////////////////////////////////
74
75fn poll_kanvas(interp: &mut Interpreter) -> usize {
76    // Sleep a bit => ~15ms => ~66fps
77    std::thread::sleep(Duration::from_millis(15));
78
79    let mut remove_list = Vec::new();
80    REGISTRY.with(|r| {
81        let map = r.borrow();
82        for (&win_id, rc_wh) in map.iter() {
83            let mut wh = rc_wh.borrow_mut();
84            if let Some(win) = wh.window.as_mut() {
85                win.update();
86                if !win.is_open() {
87                    remove_list.push(win_id);
88                }
89            }
90        }
91    });
92
93    if !remove_list.is_empty() {
94        REGISTRY.with(|r| {
95            let mut map = r.borrow_mut();
96            for rid in remove_list {
97                if interp.is_verbose() {
98                    eprintln!("[kanvas poll_kanvas] removing window {}", rid);
99                }
100                map.remove(&rid);
101            }
102        });
103    }
104
105    // Keep main loop alive if we have at least one open window
106    let remaining = REGISTRY.with(|r| r.borrow().len());
107    if remaining > 0 { 1 } else { 0 }
108}
109
110fn install_kanvas_poller_if_needed(interp: &mut Interpreter) {
111    if POLLER_INSTALLED.get().is_none() {
112        if interp.is_verbose() {
113            eprintln!("[kanvas] Installing poller => poll_kanvas");
114        }
115        interp.add_poller(Arc::new(Mutex::new(move |intrp: &mut Interpreter| {
116            poll_kanvas(intrp)
117        })));
118        POLLER_INSTALLED.set(true).ok();
119    }
120}
121
122////////////////////////////////////////////////////////////////////////////////////////////////////
123// Color / Blending Helpers
124////////////////////////////////////////////////////////////////////////////////////////////////////
125
126/// Parse "#RRGGBB" or "#AARRGGBB" => 32-bit ARGB color (0xAARRGGBB).
127fn parse_color_hex(s: &str) -> Result<u32, String> {
128    let trimmed = s.trim();
129    if !trimmed.starts_with('#') {
130        return Err(format!("Color must start with '#', got '{}'", s));
131    }
132    let hexpart = &trimmed[1..];
133    let val = u32::from_str_radix(hexpart, 16)
134        .map_err(|e| format!("Invalid color '{}': {}", s, e))?;
135    // If 6 digits, assume 0xFF as alpha
136    match hexpart.len() {
137        6 => Ok(0xFF000000 | val),
138        8 => Ok(val),
139        _ => Err(format!("Hex color must have 6 or 8 digits, got '{}'", s)),
140    }
141}
142
143/// Alpha-blend a source color over a destination color. Each color is ARGB (0xAARRGGBB).
144fn alpha_blend(dest: u32, src: u32) -> u32 {
145    let sa = ((src >> 24) & 0xFF) as f32;
146    let sr = ((src >> 16) & 0xFF) as f32;
147    let sg = ((src >> 8) & 0xFF) as f32;
148    let sb = (src & 0xFF) as f32;
149
150    let da = ((dest >> 24) & 0xFF) as f32;
151    let dr = ((dest >> 16) & 0xFF) as f32;
152    let dg = ((dest >> 8) & 0xFF) as f32;
153    let db = (dest & 0xFF) as f32;
154
155    let sf = sa / 255.0;  // source alpha in [0..1]
156    let df = da / 255.0;  // dest alpha in [0..1]
157
158    let out_a = sf + df * (1.0 - sf);
159    if out_a < 1e-6 {
160        // effectively fully transparent => return 0
161        return 0;
162    }
163    let out_r = (sr * sf + dr * df * (1.0 - sf)) / out_a;
164    let out_g = (sg * sf + dg * df * (1.0 - sf)) / out_a;
165    let out_b = (sb * sf + db * df * (1.0 - sf)) / out_a;
166
167    let final_a = (out_a * 255.0).round() as u32;
168    let final_r = out_r.round() as u32;
169    let final_g = out_g.round() as u32;
170    let final_b = out_b.round() as u32;
171
172    (final_a << 24) | (final_r << 16) | (final_g << 8) | final_b
173}
174
175////////////////////////////////////////////////////////////////////////////////////////////////////
176// Basic Geometry
177////////////////////////////////////////////////////////////////////////////////////////////////////
178
179/// Return true if (px,py) is inside the triangle given by (x1,y1),(x2,y2),(x3,y3).
180fn point_in_triangle(px: i32, py: i32, x1: i32, y1: i32, x2: i32, y2: i32, x3: i32, y3: i32) -> bool {
181    let d1 = (px - x2) * (y1 - y2) - (py - y2) * (x1 - x2);
182    let d2 = (px - x3) * (y2 - y3) - (py - y3) * (x2 - x3);
183    let d3 = (px - x1) * (y3 - y1) - (py - y1) * (x3 - x1);
184
185    let has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
186    let has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);
187    !(has_neg && has_pos)
188}
189
190////////////////////////////////////////////////////////////////////////////////////////////////////
191// Buffer Management
192////////////////////////////////////////////////////////////////////////////////////////////////////
193
194/// Overwrite the entire buffer to black
195fn clear_buffer_to_black(buf: &mut [u32]) {
196    for px in buf {
197        *px = 0xFF000000; // opaque black
198    }
199}
200
201////////////////////////////////////////////////////////////////////////////////////////////////////
202// Line Drawing Helper
203////////////////////////////////////////////////////////////////////////////////////////////////////
204
205/// A simple integer-based Bresenham approach for line drawing.
206fn draw_line_in_buffer(
207    buf: &mut [u32],
208    w: usize,
209    h: usize,
210    x1: i32, y1: i32,
211    x2: i32, y2: i32,
212    color: u32
213) {
214    let dx = (x2 - x1).abs();
215    let sx = if x1 < x2 { 1 } else { -1 };
216    let dy = -(y2 - y1).abs();
217    let sy = if y1 < y2 { 1 } else { -1 };
218    let mut err = dx + dy;
219
220    let (mut x, mut y) = (x1, y1);
221    loop {
222        if x >= 0 && x < w as i32 && y >= 0 && y < h as i32 {
223            let idx = (y as usize) * w + (x as usize);
224            buf[idx] = alpha_blend(buf[idx], color);
225        }
226        if x == x2 && y == y2 {
227            break;
228        }
229        let e2 = 2 * err;
230        if e2 >= dy {
231            err += dy;
232            x += sx;
233        }
234        if e2 <= dx {
235            err += dx;
236            y += sy;
237        }
238    }
239}
240
241////////////////////////////////////////////////////////////////////////////////////////////////////
242// Redraw
243////////////////////////////////////////////////////////////////////////////////////////////////////
244
245/// Draw the entire display list into the buffer, alpha-blending each shape.
246fn redraw_all(wh: &mut WindowHandle) {
247    clear_buffer_to_black(&mut wh.buffer);
248
249    for item in wh.items.iter() {
250        match item.shape {
251            Shape::Pixel { x, y } => {
252                if x >= 0 && y >= 0 && (x as usize) < wh.width && (y as usize) < wh.height {
253                    let idx = (y as usize) * wh.width + (x as usize);
254                    let dest = wh.buffer[idx];
255                    wh.buffer[idx] = alpha_blend(dest, item.color);
256                }
257            }
258            Shape::Rectangle { x1, y1, x2, y2 } => {
259                let xs = x1.min(x2).max(0);
260                let xe = x1.max(x2).max(0);
261                let ys = y1.min(y2).max(0);
262                let ye = y1.max(y2).max(0);
263
264                for yy in ys..=ye {
265                    if yy < 0 || (yy as usize) >= wh.height { continue; }
266                    for xx in xs..=xe {
267                        if xx < 0 || (xx as usize) >= wh.width { continue; }
268                        let idx = (yy as usize) * wh.width + (xx as usize);
269                        let dest = wh.buffer[idx];
270                        wh.buffer[idx] = alpha_blend(dest, item.color);
271                    }
272                }
273            }
274            Shape::Triangle { x1, y1, x2, y2, x3, y3 } => {
275                let xx_min = x1.min(x2).min(x3).max(0);
276                let xx_max = x1.max(x2).max(x3).max(0);
277                let yy_min = y1.min(y2).min(y3).max(0);
278                let yy_max = y1.max(y2).max(y3).max(0);
279
280                for yy in yy_min..=yy_max {
281                    if yy < 0 || (yy as usize) >= wh.height { continue; }
282                    for xx in xx_min..=xx_max {
283                        if xx < 0 || (xx as usize) >= wh.width { continue; }
284                        if point_in_triangle(xx, yy, x1, y1, x2, y2, x3, y3) {
285                            let idx = (yy as usize) * wh.width + (xx as usize);
286                            let dest = wh.buffer[idx];
287                            wh.buffer[idx] = alpha_blend(dest, item.color);
288                        }
289                    }
290                }
291            }
292            // NEW: line drawing
293            Shape::Line { x1, y1, x2, y2 } => {
294                draw_line_in_buffer(
295                    &mut wh.buffer,
296                    wh.width,
297                    wh.height,
298                    x1, y1,
299                    x2, y2,
300                    item.color
301                );
302            }
303        }
304    }
305
306    if let Some(win) = wh.window.as_mut() {
307        let _ = win.update_with_buffer(&wh.buffer, wh.width, wh.height);
308    }
309}
310
311////////////////////////////////////////////////////////////////////////////////////////////////////
312// Bridging Functions
313////////////////////////////////////////////////////////////////////////////////////////////////////
314
315static mut GLOBAL_ITEM_ID: i32 = 1;
316
317/// create => create a new window
318fn kanvas_create_bridge(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
319    if args.len() != 1 {
320        return Err("kanvas:create => expected 1 argument => KeyedArray".to_string());
321    }
322    let keyed = match &args[0] {
323        Value::KeyedArray(map) => map,
324        _ => return Err("kanvas:create => argument must be a KeyedArray".to_string()),
325    };
326
327    let mut w: i32 = 640;
328    let mut h: i32 = 480;
329    let mut title = "Kanvas Window".to_string();
330
331    if let Some(Value::Int(x)) = keyed.get("width") {
332        w = *x;
333    }
334    if let Some(Value::Int(y)) = keyed.get("height") {
335        h = *y;
336    }
337    if let Some(Value::SingleString(tt)) = keyed.get("title") {
338        title = tt.clone();
339    }
340
341    if w <= 0 || h <= 0 {
342        return Err(format!("Invalid width/height => {}x{}", w, h));
343    }
344
345    if interp.is_verbose() {
346        eprintln!("[kanvas:create] size={}x{}, title='{}'", w, h, title);
347    }
348
349    let w_usize = w as usize;
350    let h_usize = h as usize;
351
352    let mut window = Window::new(
353        &title,
354        w_usize,
355        h_usize,
356        WindowOptions {
357            scale: Scale::X1,
358            scale_mode: ScaleMode::Stretch,
359            ..WindowOptions::default()
360        }
361    ).map_err(|e| format!("Error creating Window => {}", e))?;
362
363    let buffer = vec![0xFF000000; w_usize * h_usize];
364    let _ = window.update_with_buffer(&buffer, w_usize, h_usize);
365
366    static mut NEXT_WIN_ID: i32 = 1000;
367    let window_id: i32;
368    unsafe {
369        window_id = NEXT_WIN_ID;
370        NEXT_WIN_ID += 1;
371    }
372
373    let wh = WindowHandle {
374        window: Some(window),
375        width: w_usize,
376        height: h_usize,
377        buffer,
378        items: Vec::new(),
379    };
380
381    REGISTRY.with(|r| {
382        r.borrow_mut().insert(window_id, Rc::new(RefCell::new(wh)));
383    });
384
385    Ok(Value::Int(window_id))
386}
387
388/// display => block briefly so the window can show
389fn kanvas_display_bridge(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
390    if args.len() != 1 {
391        return Err("kanvas:display => expected 1 argument => handle".to_string());
392    }
393
394    let handle_id = match &args[0] {
395        Value::Int(i) => *i,
396        _ => return Err("kanvas:display => first arg must be int handle".to_string()),
397    };
398
399    install_kanvas_poller_if_needed(interp);
400
401    for _ in 0..3 {
402        poll_kanvas(interp);
403        let still_there = REGISTRY.with(|r| r.borrow().contains_key(&handle_id));
404        if !still_there {
405            return Ok(Value::Bool(false));
406        }
407        std::thread::sleep(std::time::Duration::from_millis(16));
408    }
409
410    Ok(Value::Bool(true))
411}
412
413/// remove => remove an item from the display list
414fn kanvas_remove_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
415    if args.len() != 2 {
416        return Err("kanvas:remove => expected 2 arguments => (windowHandle, itemHandle)".to_string());
417    }
418    let win_id = match &args[0] {
419        Value::Int(i) => *i,
420        _ => return Err("kanvas:remove => first arg must be window handle int".to_string()),
421    };
422    let item_id = match &args[1] {
423        Value::Int(i) => *i,
424        _ => return Err("kanvas:remove => second arg must be item handle int".to_string()),
425    };
426
427    let mut found = false;
428    REGISTRY.with(|r| {
429        if let Some(rc_wh) = r.borrow().get(&win_id) {
430            let mut wh = rc_wh.borrow_mut();
431            let old_len = wh.items.len();
432            wh.items.retain(|it| it.handle_id != item_id);
433            if wh.items.len() < old_len {
434                found = true;
435            }
436        }
437    });
438
439    Ok(Value::Bool(found))
440}
441
442/// refresh => clear buffer + re-draw all items with alpha blending
443fn kanvas_refresh_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
444    if args.len() != 1 {
445        return Err("kanvas:refresh => expected 1 argument => windowHandle".to_string());
446    }
447    let win_id = match &args[0] {
448        Value::Int(i) => *i,
449        _ => return Err("kanvas:refresh => first arg must be window handle int".to_string()),
450    };
451
452    REGISTRY.with(|r| {
453        if let Some(rc_wh) = r.borrow().get(&win_id) {
454            let mut wh = rc_wh.borrow_mut();
455            redraw_all(&mut wh);
456        }
457    });
458    Ok(Value::Bool(true))
459}
460
461/// pixel => add a Pixel item to the display list, returns item handle
462fn kanvas_pixel_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
463    if args.len() != 3 {
464        return Err("kanvas:pixel => (windowHandle, [x,y], colorString)".to_string());
465    }
466    let win_id = match &args[0] {
467        Value::Int(i) => *i,
468        _ => return Err("kanvas:pixel => first arg must be window handle int".to_string()),
469    };
470    let (px, py) = match &args[1] {
471        Value::IntArray(v) if v.len() == 2 => (v[0], v[1]),
472        _ => return Err("kanvas:pixel => second arg must be [x,y]".to_string()),
473    };
474    let color_str = match &args[2] {
475        Value::SingleString(s) => s.clone(),
476        _ => return Err("kanvas:pixel => third arg must be color string".to_string()),
477    };
478    let color_val = parse_color_hex(&color_str)?;
479
480    let item_id = unsafe {
481        GLOBAL_ITEM_ID += 1;
482        GLOBAL_ITEM_ID
483    };
484
485    REGISTRY.with(|r| {
486        if let Some(rc_wh) = r.borrow().get(&win_id) {
487            let mut wh = rc_wh.borrow_mut();
488            let shape = Shape::Pixel { x: px, y: py };
489            let new_item = ShapeItem {
490                handle_id: item_id,
491                shape,
492                color: color_val,
493            };
494            wh.items.push(new_item);
495        }
496    });
497
498    Ok(Value::Int(item_id))
499}
500
501/// rectangle => add a Rectangle item to the display list, returns item handle
502fn kanvas_rectangle_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
503    if args.len() != 3 {
504        return Err("kanvas:rectangle => (windowHandle, [[x1,y1],[x2,y2]], colorString)".to_string());
505    }
506    let win_id = match &args[0] {
507        Value::Int(i) => *i,
508        _ => return Err("kanvas:rectangle => first arg must be window handle int".to_string()),
509    };
510    let coords = match &args[1] {
511        Value::Int2DArray(rows) if rows.len() == 2 && rows[0].len() == 2 && rows[1].len() == 2 => {
512            (rows[0][0], rows[0][1], rows[1][0], rows[1][1])
513        }
514        _ => {
515            return Err("kanvas:rectangle => second arg must be [[x1,y1],[x2,y2]]".to_string());
516        }
517    };
518    let color_str = match &args[2] {
519        Value::SingleString(s) => s.clone(),
520        _ => return Err("kanvas:rectangle => third arg must be color string".to_string()),
521    };
522    let color_val = parse_color_hex(&color_str)?;
523
524    let (x1, y1, x2, y2) = coords;
525    let item_id = unsafe {
526        GLOBAL_ITEM_ID += 1;
527        GLOBAL_ITEM_ID
528    };
529
530    REGISTRY.with(|r| {
531        if let Some(rc_wh) = r.borrow().get(&win_id) {
532            let mut wh = rc_wh.borrow_mut();
533            let shape = Shape::Rectangle { x1, y1, x2, y2 };
534            let new_item = ShapeItem {
535                handle_id: item_id,
536                shape,
537                color: color_val,
538            };
539            wh.items.push(new_item);
540        }
541    });
542
543    Ok(Value::Int(item_id))
544}
545
546/// triangle => add a Triangle item to the display list, returns item handle
547///
548/// **Updated**: second argument may now be either an **integer** or
549/// **floating-point** 2-D array.  Floating values are rounded to the nearest
550/// pixel before drawing.
551fn kanvas_triangle_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
552    if args.len() != 3 {
553        return Err("kanvas:triangle => (windowHandle, [[x1,y1],[x2,y2],[x3,y3]], colorString)".to_string());
554    }
555    let win_id = match &args[0] {
556        Value::Int(i) => *i,
557        _ => return Err("kanvas:triangle => first arg must be window handle int".to_string()),
558    };
559
560    // --- Parse coordinate array (ints **or** floats) -----------------------
561    let (x1, y1, x2, y2, x3, y3) = match &args[1] {
562        // Original, integer path
563        Value::Int2DArray(rows)
564            if rows.len() == 3 &&
565               rows[0].len() == 2 &&
566               rows[1].len() == 2 &&
567               rows[2].len() == 2 => {
568            (rows[0][0], rows[0][1], rows[1][0], rows[1][1], rows[2][0], rows[2][1])
569        }
570        // NEW: floating-point vertices
571        Value::Float2DArray(rows)
572            if rows.len() == 3 &&
573               rows[0].len() == 2 &&
574               rows[1].len() == 2 &&
575               rows[2].len() == 2 => {
576            (
577                rows[0][0].round() as i32, rows[0][1].round() as i32,
578                rows[1][0].round() as i32, rows[1][1].round() as i32,
579                rows[2][0].round() as i32, rows[2][1].round() as i32,
580            )
581        }
582        _ => {
583            return Err("kanvas:triangle => second arg must be [[x1,y1],[x2,y2],[x3,y3]] (ints or floats)".to_string());
584        }
585    };
586
587    let color_str = match &args[2] {
588        Value::SingleString(s) => s.clone(),
589        _ => return Err("kanvas:triangle => third arg must be color string".to_string()),
590    };
591    let color_val = parse_color_hex(&color_str)?;
592
593    let item_id = unsafe {
594        GLOBAL_ITEM_ID += 1;
595        GLOBAL_ITEM_ID
596    };
597
598    REGISTRY.with(|r| {
599        if let Some(rc_wh) = r.borrow().get(&win_id) {
600            let mut wh = rc_wh.borrow_mut();
601            let shape = Shape::Triangle { x1, y1, x2, y2, x3, y3 };
602            let new_item = ShapeItem {
603                handle_id: item_id,
604                shape,
605                color: color_val,
606            };
607            wh.items.push(new_item);
608        }
609    });
610
611    Ok(Value::Int(item_id))
612}
613
614/// NEW: line => add a Line item to the display list, returns item handle
615fn kanvas_line_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
616    if args.len() != 3 {
617        return Err("kanvas:line => (windowHandle, [[x1,y1],[x2,y2]], colorString)".to_string());
618    }
619    let win_id = match &args[0] {
620        Value::Int(i) => *i,
621        _ => return Err("kanvas:line => first arg must be window handle int".to_string()),
622    };
623    let coords = match &args[1] {
624        Value::Int2DArray(rows) if rows.len() == 2 => {
625            let row0 = &rows[0];
626            let row1 = &rows[1];
627            if row0.len() != 2 || row1.len() != 2 {
628                return Err("kanvas:line => second arg must be [[x1,y1],[x2,y2]]".to_string());
629            }
630            (row0[0], row0[1], row1[0], row1[1])
631        }
632        _ => {
633            return Err("kanvas:line => second arg must be [[x1,y1],[x2,y2]]".to_string());
634        }
635    };
636    let color_str = match &args[2] {
637        Value::SingleString(s) => s.clone(),
638        _ => return Err("kanvas:line => third arg must be color string".to_string()),
639    };
640    let color_val = parse_color_hex(&color_str)?;
641
642    let (x1, y1, x2, y2) = coords;
643    let item_id = unsafe {
644        GLOBAL_ITEM_ID += 1;
645        GLOBAL_ITEM_ID
646    };
647
648    REGISTRY.with(|r| {
649        if let Some(rc_wh) = r.borrow().get(&win_id) {
650            let mut wh = rc_wh.borrow_mut();
651            let shape = Shape::Line { x1, y1, x2, y2 };
652            let new_item = ShapeItem {
653                handle_id: item_id,
654                shape,
655                color: color_val,
656            };
657            wh.items.push(new_item);
658        }
659    });
660
661    Ok(Value::Int(item_id))
662}
663
664////////////////////////////////////////////////////////////////////////////////////////////////////
665// Cargo.lock => plugin entry
666////////////////////////////////////////////////////////////////////////////////////////////////////
667
668#[export_name = "Cargo_lock"]
669pub unsafe extern "C" fn cargo_lock(interp_ptr: *mut c_void, _extra: *const c_void) -> i32 {
670    if interp_ptr.is_null() {
671        return 1;
672    }
673    let interp = &mut *(interp_ptr as *mut Interpreter);
674
675    if interp.is_verbose() {
676        eprintln!("[kanvas Cargo.lock] => registering + installing poller if needed");
677    }
678
679    // create
680    {
681        let f: DynamicFn = Arc::new(Mutex::new(kanvas_create_bridge));
682        let info = DynamicFnInfo::new(f, false);
683        interp.register_dynamic_function_ex("kanvas:create", info);
684        interp.set_variable(
685            "kanvas:create",
686            Value::Function(Box::new(FunctionValue::Named("kanvas:create".to_string())))
687        );
688    }
689    // display
690    {
691        let f: DynamicFn = Arc::new(Mutex::new(kanvas_display_bridge));
692        let info = DynamicFnInfo::new(f, false);
693        interp.register_dynamic_function_ex("kanvas:display", info);
694        interp.set_variable(
695            "kanvas:display",
696            Value::Function(Box::new(FunctionValue::Named("kanvas:display".to_string())))
697        );
698    }
699    // remove
700    {
701        let f: DynamicFn = Arc::new(Mutex::new(kanvas_remove_bridge));
702        let info = DynamicFnInfo::new(f, false);
703        interp.register_dynamic_function_ex("kanvas:remove", info);
704        interp.set_variable(
705            "kanvas:remove",
706            Value::Function(Box::new(FunctionValue::Named("kanvas:remove".to_string())))
707        );
708    }
709    // refresh
710    {
711        let f: DynamicFn = Arc::new(Mutex::new(kanvas_refresh_bridge));
712        let info = DynamicFnInfo::new(f, false);
713        interp.register_dynamic_function_ex("kanvas:refresh", info);
714        interp.set_variable(
715            "kanvas:refresh",
716            Value::Function(Box::new(FunctionValue::Named("kanvas:refresh".to_string())))
717        );
718    }
719    // pixel
720    {
721        let f: DynamicFn = Arc::new(Mutex::new(kanvas_pixel_bridge));
722        let info = DynamicFnInfo::new(f, false);
723        interp.register_dynamic_function_ex("kanvas:pixel", info);
724        interp.set_variable(
725            "kanvas:pixel",
726            Value::Function(Box::new(FunctionValue::Named("kanvas:pixel".to_string())))
727        );
728    }
729    // rectangle
730    {
731        let f: DynamicFn = Arc::new(Mutex::new(kanvas_rectangle_bridge));
732        let info = DynamicFnInfo::new(f, false);
733        interp.register_dynamic_function_ex("kanvas:rectangle", info);
734        interp.set_variable(
735            "kanvas:rectangle",
736            Value::Function(Box::new(FunctionValue::Named("kanvas:rectangle".to_string())))
737        );
738    }
739    // triangle
740    {
741        let f: DynamicFn = Arc::new(Mutex::new(kanvas_triangle_bridge));
742        let info = DynamicFnInfo::new(f, false);
743        interp.register_dynamic_function_ex("kanvas:triangle", info);
744        interp.set_variable(
745            "kanvas:triangle",
746            Value::Function(Box::new(FunctionValue::Named("kanvas:triangle".to_string())))
747        );
748    }
749    // NEW: line
750    {
751        let f: DynamicFn = Arc::new(Mutex::new(kanvas_line_bridge));
752        let info = DynamicFnInfo::new(f, false);
753        interp.register_dynamic_function_ex("kanvas:line", info);
754        interp.set_variable(
755            "kanvas:line",
756            Value::Function(Box::new(FunctionValue::Named("kanvas:line".to_string())))
757        );
758    }
759
760    install_kanvas_poller_if_needed(interp);
761    0
762}