From 48d03699bda0dfa7f134487bf399517a9a0b957f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=AA=E3=82=AD=E8=90=8C?= Date: Fri, 13 Jun 2025 20:11:52 +0200 Subject: [PATCH] haku2: make server use haku2 (and make it work!) --- Cargo.lock | 79 +++++++++++++++++++-- crates/haku-wasm/src/lib.rs | 1 - crates/haku2/src/haku2.zig | 20 +++--- crates/haku2/src/lib.rs | 44 +++++++----- crates/haku2/src/system.zig | 19 ++++-- crates/haku2/src/value.zig | 22 ++++++ crates/haku2/src/vm.zig | 11 ++- crates/rkgk/Cargo.toml | 1 + crates/rkgk/src/api/wall.rs | 74 +++++--------------- crates/rkgk/src/haku.rs | 84 ++++------------------- crates/rkgk/src/id.rs | 9 +++ crates/rkgk/src/main.rs | 13 +++- crates/rkgk/src/wall.rs | 9 ++- crates/rkgk/src/wall/broker.rs | 2 +- crates/rkgk/src/wall/database.rs | 2 +- crates/rkgk/src/wall/render.rs | 113 +++++++++++++++++++++++++++++++ 16 files changed, 329 insertions(+), 174 deletions(-) create mode 100644 crates/rkgk/src/wall/render.rs diff --git a/Cargo.lock b/Cargo.lock index a705f92..52e9f8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.8.11" @@ -175,7 +181,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object", "rustc-demangle", ] @@ -198,6 +204,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" @@ -353,6 +365,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -484,6 +505,25 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.4", +] + [[package]] name = "fnv" version = "1.0.7" @@ -955,6 +995,16 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.0.1" @@ -1142,6 +1192,19 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.4", +] + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1225,7 +1288,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags", + "bitflags 2.6.0", ] [[package]] @@ -1298,6 +1361,7 @@ dependencies = [ "rusqlite", "serde", "serde_json", + "tiny-skia", "tokio", "toml", "tower-http", @@ -1318,7 +1382,7 @@ version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" dependencies = [ - "bitflags", + "bitflags 2.6.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -1474,6 +1538,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "slab" version = "0.4.9" @@ -1575,6 +1645,7 @@ dependencies = [ "bytemuck", "cfg-if", "log", + "png", "tiny-skia-path", ] @@ -1715,7 +1786,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ - "bitflags", + "bitflags 2.6.0", "bytes", "futures-util", "http", diff --git a/crates/haku-wasm/src/lib.rs b/crates/haku-wasm/src/lib.rs index 1878026..416092f 100644 --- a/crates/haku-wasm/src/lib.rs +++ b/crates/haku-wasm/src/lib.rs @@ -493,7 +493,6 @@ unsafe extern "C" fn haku_begin_brush(instance: *mut Instance, brush: *const Bru panic!("brush is not compiled and ready to be used"); }; - info!("old image: {:?}", instance.vm.image()); instance.vm.restore_image(&instance.vm_image); instance.vm.apply_defs(&instance.defs); instance.reset_exception(); diff --git a/crates/haku2/src/haku2.zig b/crates/haku2/src/haku2.zig index bf298ca..c4bd960 100644 --- a/crates/haku2/src/haku2.zig +++ b/crates/haku2/src/haku2.zig @@ -10,7 +10,7 @@ const value = @import("value.zig"); const Vm = @import("vm.zig"); const log = @import("log.zig"); -const vm_trace = true; +const debug_logs = false; pub const allocator = if (builtin.cpu.arch == .wasm32) @@ -19,7 +19,7 @@ pub const allocator = @import("allocator.zig").hostAllocator; pub const std_options: std.Options = .{ - .log_level = if (vm_trace) .debug else .info, + .log_level = if (debug_logs) .debug else .info, .logFn = log.logFn, }; @@ -57,10 +57,6 @@ export fn haku2_limits_set_call_stack_capacity(limits: *Vm.Limits, new: usize) v limits.call_stack_capacity = new; } -export fn haku2_limits_set_fuel(limits: *Vm.Limits, new: u32) void { - limits.fuel = new; -} - // Defs export fn haku2_defs_parse( @@ -95,6 +91,10 @@ export fn haku2_vm_destroy(vm: *Vm) void { allocator.destroy(vm); } +export fn haku2_vm_reset(vm: *Vm, fuel: u32) void { + vm.reset(fuel); +} + export fn haku2_vm_run_main( vm: *Vm, scratch: *Scratch, @@ -102,11 +102,15 @@ export fn haku2_vm_run_main( code_len: usize, local_count: u8, ) bool { - const chunk = bytecode.Chunk{ + const chunk = scratch.allocator().create(bytecode.Chunk) catch { + vm.outOfMemory() catch {}; + return false; + }; + chunk.* = bytecode.Chunk{ .bytecode = code[0..code_len], }; const closure = value.Closure{ - .chunk = &chunk, + .chunk = chunk, .start = 0, .param_count = 0, .local_count = local_count, diff --git a/crates/haku2/src/lib.rs b/crates/haku2/src/lib.rs index 60f6978..c0b80c9 100644 --- a/crates/haku2/src/lib.rs +++ b/crates/haku2/src/lib.rs @@ -7,6 +7,8 @@ use std::{ slice, }; +use log::{debug, trace}; + #[unsafe(no_mangle)] unsafe extern "C" fn __haku2_alloc(size: usize, align: usize) -> *mut u8 { if let Ok(layout) = Layout::from_size_align(size, align) { @@ -121,7 +123,6 @@ extern "C" { fn haku2_limits_destroy(limits: *mut LimitsC); fn haku2_limits_set_stack_capacity(limits: *mut LimitsC, new: usize); fn haku2_limits_set_call_stack_capacity(limits: *mut LimitsC, new: usize); - fn haku2_limits_set_fuel(limits: *mut LimitsC, new: u32); fn haku2_defs_parse( defs_string: *const u8, @@ -133,6 +134,7 @@ extern "C" { fn haku2_vm_new(s: *mut ScratchC, defs: *const DefsC, limits: *const LimitsC) -> *mut VmC; fn haku2_vm_destroy(vm: *mut VmC); + fn haku2_vm_reset(vm: *mut VmC, fuel: u32); fn haku2_vm_run_main( vm: *mut VmC, scratch: *mut ScratchC, @@ -167,13 +169,14 @@ pub struct Scratch { impl Scratch { pub fn new(max: usize) -> Scratch { - Scratch { - // SAFETY: haku2_scratch_new does not have any safety invariants. - raw: NonNull::new(unsafe { haku2_scratch_new(max) }).expect("out of memory"), - } + // SAFETY: haku2_scratch_new does not have any safety invariants. + let raw = NonNull::new(unsafe { haku2_scratch_new(max) }).expect("out of memory"); + trace!("Scratch::new -> {raw:?}"); + Scratch { raw } } pub fn reset(&mut self) { + trace!("Scratch::reset({:?})", self.raw); // SAFETY: The pointer passed is non-null. unsafe { haku2_scratch_reset(self.raw.as_ptr()); @@ -183,6 +186,7 @@ impl Scratch { impl Drop for Scratch { fn drop(&mut self) { + trace!("Scratch::drop({:?})", self.raw); // SAFETY: The pointer passed is non-null. unsafe { haku2_scratch_destroy(self.raw.as_ptr()); @@ -194,7 +198,6 @@ impl Drop for Scratch { pub struct LimitsSpec { pub stack_capacity: usize, pub call_stack_capacity: usize, - pub fuel: u32, } #[derive(Debug)] @@ -216,7 +219,6 @@ impl Limits { unsafe { haku2_limits_set_stack_capacity(limits.as_ptr(), spec.stack_capacity); haku2_limits_set_call_stack_capacity(limits.as_ptr(), spec.call_stack_capacity); - haku2_limits_set_fuel(limits.as_ptr(), spec.fuel); } Self { raw: limits } @@ -315,21 +317,21 @@ pub struct Dotter { impl Vm { pub fn new(scratch: Scratch, code: Code, limits: &Limits) -> Self { + let raw = NonNull::new(unsafe { + haku2_vm_new( + scratch.raw.as_ptr(), + code.defs.raw.as_ptr(), + limits.raw.as_ptr(), + ) + }) + .expect("out of memory"); + trace!("Vm::new({scratch:?}, {code:?}, {limits:?}) -> {raw:?}"); Self { // SAFETY: // - Ownership of scratch is passed to the VM, so the VM cannot outlive the scratch space. // - The VM never gives you any references back, so this is safe to do. // - The other arguments are only borrowed immutably for construction. - inner: VmInner { - raw: NonNull::new(unsafe { - haku2_vm_new( - scratch.raw.as_ptr(), - code.defs.raw.as_ptr(), - limits.raw.as_ptr(), - ) - }) - .expect("out of memory"), - }, + inner: VmInner { raw }, scratch, code, } @@ -340,8 +342,10 @@ impl Vm { /// /// Calling `begin` again during this process will work correctly, and result in another /// continuation being stack on top of the old one---at the expense of a stack slot. - pub fn begin(&mut self) -> Result<(), Exception> { + pub fn begin(&mut self, fuel: u32) -> Result<(), Exception> { + trace!("Vm::begin({self:?}, {fuel})"); let ok = unsafe { + haku2_vm_reset(self.inner.raw.as_ptr(), fuel); haku2_vm_run_main( self.inner.raw.as_ptr(), self.scratch.raw.as_ptr(), @@ -411,6 +415,7 @@ impl Vm { /// Take the `Scratch` out of the VM for reuse in another one. /// The scratch memory will be reset (no bytes will be consumed.) pub fn into_scratch(self) -> Scratch { + trace!("Vm::into_scratch({self:?})"); let Vm { mut scratch, code: _, @@ -423,6 +428,8 @@ impl Vm { impl ContDotter<'_> { pub fn run(self, dotter: &Dotter) -> Result<(), Exception> { + trace!("ContDotter::run({self:?}, {dotter:?})"); + let Dotter { from: (from_x, from_y), to: (to_x, to_y), @@ -455,6 +462,7 @@ struct VmInner { impl Drop for VmInner { fn drop(&mut self) { + trace!("VmInner::drop({:?})", self.raw); // SAFETY: The pointer passed is non-null. unsafe { haku2_vm_destroy(self.raw.as_ptr()); diff --git a/crates/haku2/src/system.zig b/crates/haku2/src/system.zig index 7cb0446..9071720 100644 --- a/crates/haku2/src/system.zig +++ b/crates/haku2/src/system.zig @@ -2,6 +2,7 @@ const std = @import("std"); const mem = std.mem; const meta = std.meta; const math = std.math; +const log = std.log.scoped(.system); const bytecode = @import("bytecode.zig"); const Opcode = bytecode.Opcode; @@ -112,10 +113,13 @@ fn fromArgument(cx: Context, comptime T: type, i: usize) Vm.Error!T { if (val != .ref or val.ref.* != .list) return typeError(cx.vm, val, i, "list"); return val.ref.list; }, - *const value.Shape => { + value.Shape => { const val = cx.args[i]; - if (val != .ref or val.ref.* != .shape) return typeError(cx.vm, val, i, "shape"); - return &val.ref.shape; + if (toShape(val)) |shape| { + return shape; + } else { + return typeError(cx.vm, val, i, "shape"); + } }, *const value.Closure => { const val = cx.args[i]; @@ -756,9 +760,9 @@ fn circle(center: Vec4, radius: f32) value.Ref { } } }; } -fn stroke(thickness: f32, color: Rgba, shape: *const value.Shape) value.Ref { +fn stroke(thickness: f32, color: Rgba, shape: value.Shape) value.Ref { return .{ .scribble = .{ - .shape = shape.*, + .shape = shape, .action = .{ .stroke = .{ .thickness = thickness, .color = color.value, @@ -766,9 +770,9 @@ fn stroke(thickness: f32, color: Rgba, shape: *const value.Shape) value.Ref { } }; } -fn fill(color: Rgba, shape: *const value.Shape) value.Ref { +fn fill(color: Rgba, shape: value.Shape) value.Ref { return .{ .scribble = .{ - .shape = shape.*, + .shape = shape, .action = .{ .fill = .{ .color = color.value, } }, @@ -776,6 +780,7 @@ fn fill(color: Rgba, shape: *const value.Shape) value.Ref { } fn withDotter(cont: *const value.Closure, cx: Context) Vm.Error!value.Ref { + log.debug("withDotter({})", .{cont}); if (cont.param_count != 1) { return cx.vm.throw("function passed to withDotter must have a single parameter (\\d -> _), but it has {}", .{cont.param_count}); } diff --git a/crates/haku2/src/value.zig b/crates/haku2/src/value.zig index 51a6e76..27ed963 100644 --- a/crates/haku2/src/value.zig +++ b/crates/haku2/src/value.zig @@ -68,6 +68,28 @@ pub const Value = union(enum) { }, }; } + + pub fn format(value: Value, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { + switch (value) { + .nil => try std.fmt.formatBuf("Nil", options, writer), + .false => try std.fmt.formatBuf("False", options, writer), + .true => try std.fmt.formatBuf("True", options, writer), + inline .tag, .number => |x| try std.fmt.format(writer, "{d}", .{x}), + inline .vec4, .rgba => |x| try std.fmt.format(writer, "{s}{d}", .{ @tagName(value), x }), + .ref => |ref| switch (ref.*) { + .closure => |c| try std.fmt.format(writer, "function({})", .{c.param_count}), + .list => |l| { + try std.fmt.formatBuf("[", options, writer); + for (l, 0..) |elem, i| { + if (i != 0) try std.fmt.formatBuf(", ", options, writer); + try elem.format(fmt, options, writer); + } + try std.fmt.formatBuf("]", options, writer); + }, + inline .shape, .scribble, .reticle => |x| try std.fmt.format(writer, "{}", .{x}), + }, + } + } }; pub const TagId = enum(u16) { diff --git a/crates/haku2/src/vm.zig b/crates/haku2/src/vm.zig index 943e217..ad0939e 100644 --- a/crates/haku2/src/vm.zig +++ b/crates/haku2/src/vm.zig @@ -17,13 +17,12 @@ stack_top: u32 = 0, call_stack: []CallFrame, call_stack_top: u32 = 0, defs: []Value, -fuel: u32, +fuel: u32 = 0, // NOTE: VM must be refueled via reset() before running code exception: ?Exception = null, pub const Limits = struct { stack_capacity: usize = 256, call_stack_capacity: usize = 256, - fuel: u32 = 63336, }; pub const CallFrame = struct { @@ -50,10 +49,16 @@ pub fn init(a: mem.Allocator, defs: *const bytecode.Defs, limits: *const Limits) .stack = try a.alloc(Value, limits.stack_capacity), .call_stack = try a.alloc(CallFrame, limits.call_stack_capacity), .defs = try a.alloc(Value, defs.num_defs), - .fuel = limits.fuel, }; } +pub fn reset(vm: *Vm, fuel: u32) void { + vm.stack_top = 0; + vm.call_stack_top = 0; + vm.fuel = fuel; + vm.exception = null; +} + pub fn throw(vm: *Vm, comptime fmt: []const u8, args: anytype) Error { log.debug("throw: fmt={s}", .{fmt}); diff --git a/crates/rkgk/Cargo.toml b/crates/rkgk/Cargo.toml index 829cb6d..a4b9d73 100644 --- a/crates/rkgk/Cargo.toml +++ b/crates/rkgk/Cargo.toml @@ -26,6 +26,7 @@ rayon = "1.10.0" rusqlite = { version = "0.32.1", features = ["bundled"] } serde = { version = "1.0.206", features = ["derive"] } serde_json = "1.0.124" +tiny-skia = "0.11.4" tokio = { version = "1.39.2", features = ["full"] } toml = "0.8.19" tower-http = { version = "0.5.2", features = ["fs"] } diff --git a/crates/rkgk/src/api/wall.rs b/crates/rkgk/src/api/wall.rs index 94d71b4..daad763 100644 --- a/crates/rkgk/src/api/wall.rs +++ b/crates/rkgk/src/api/wall.rs @@ -12,10 +12,7 @@ use axum::{ }; use base64::Engine; use eyre::{bail, Context, OptionExt}; -use haku::{ - trampoline::{Cont, Trampoline}, - value::Value, -}; +use haku::value::Value; use schema::{ ChunkInfo, Error, LoginRequest, LoginResponse, Notify, Online, Request, Version, WallInfo, }; @@ -32,8 +29,8 @@ use crate::{ schema::Vec2, wall::{ self, auto_save::AutoSave, chunk_images::ChunkImages, chunk_iterator::ChunkIterator, - database::ChunkDataPair, ChunkPosition, Interaction, JoinError, SessionHandle, UserInit, - Wall, WallId, + database::ChunkDataPair, render::ChunkCanvas, ChunkPosition, Interaction, JoinError, + SessionHandle, UserInit, Wall, WallId, }, }; @@ -265,7 +262,7 @@ impl SessionLoop { let wall = Arc::clone(&wall); move || { let _span = - info_span!("render_thread", %wall_id, session_id = ?handle.session_id) + info_span!("render_thread", ?wall_id, session_id = ?handle.session_id) .entered(); Self::render_thread(wall, limits, render_commands_rx) } @@ -287,7 +284,7 @@ impl SessionLoop { }) } - #[instrument(skip(self, ws), fields(wall_id = %self.wall_id, session_id = ?self.handle.session_id))] + #[instrument(skip(self, ws), fields(wall_id = ?self.wall_id, session_id = ?self.handle.session_id))] async fn event_loop(&mut self, ws: &mut WebSocket) -> eyre::Result<()> { loop { select! { @@ -456,7 +453,6 @@ impl SessionLoop { fn render_thread(wall: Arc, limits: Limits, mut commands: mpsc::Receiver) { let mut haku = Haku::new(limits); - let mut trampoline = None; let mut brush_ok = false; let mut current_render_area = RenderArea::default(); @@ -488,37 +484,15 @@ impl SessionLoop { }, other => error!("received Dotter interaction when a {other:?} continuation was next") } - - // old v1 code - - if let Some(tramp) = jumpstart_trampoline(&mut haku, &mut trampoline) { - let cont = haku.cont(tramp); - if cont == Cont::Dotter { - _ = haku.dotter(tramp, from, to, num); - } else { - error!("received Dotter interaction when a {cont:?} continuation was next"); - } - } else { - info!("failed to jumpstart trampoline for Dotter interaction"); - } } } Interaction::Scribble => { - // Regarding the take(): this is to self-synchronize in case of error. - // Once a scribble is rendered, we reset back to not having a trampoline, - // and further interactions must be sent to kickstart a new one. - // - // Regarding not jumpstarting a trampoline here: it would be useless, - // because a scribble is always the last thing in the chain, and it never - // modifies the render area. Therefore what would end up happening is we'd - // end up rendering to no chunks, therefore not doing anything. - // The server doesn't need to report anything to the user and so can save - // a bit of work by _not_ evaluating the brush initially here. - if let Some(trampoline) = trampoline.take() { - _ = draw_to_chunks(&wall, &haku, current_render_area, trampoline.value); - } else { - info!("tried to scribble without an active trampoline"); + match haku.cont2() { + haku2::Cont::None => { + draw_to_chunks2(&wall, current_render_area, &mut haku); + } + _ => error!("tried to draw a scribble with an active continuation"), } current_render_area = RenderArea::default(); @@ -574,31 +548,16 @@ fn chunks_to_modify( chunks } -fn jumpstart_trampoline<'a>( - haku: &mut Haku, - trampoline: &'a mut Option, -) -> Option<&'a mut Trampoline> { - if trampoline.is_none() { - *trampoline = haku.eval_brush().ok().map(Trampoline::new); - } - trampoline.as_mut() -} - fn jumpstart_trampoline2(haku: &mut Haku) { if !haku.has_cont2() { if let Err(e) = haku.eval_brush2() { - error!("eval_brush2: {e:?}"); + error!("eval_brush2 exception: {e:?}"); } } } -#[instrument(skip(wall, haku, value))] -fn draw_to_chunks( - wall: &Wall, - haku: &Haku, - render_area: RenderArea, - value: Value, -) -> eyre::Result<()> { +#[instrument(skip(wall, vm))] +fn draw_to_chunks2(wall: &Wall, render_area: RenderArea, vm: &mut Haku) { let settings = wall.settings(); let chunk_size = settings.chunk_size as f32; @@ -612,9 +571,10 @@ fn draw_to_chunks( let y = f32::floor(-chunk_y as f32 * chunk_size); let chunk_ref = wall.get_or_create_chunk(ChunkPosition::new(chunk_x, chunk_y)); let mut chunk = chunk_ref.blocking_lock(); - haku.render_value(&mut chunk.pixmap, value, Vec2 { x, y })?; + let mut canvas = ChunkCanvas::new(&mut chunk).translated(x, y); + if let Err(e) = vm.render2(&mut canvas, 256) { + error!(chunk_x, chunk_y, "draw_to_chunks2 exception: {e:?}"); + } } } - - Ok(()) } diff --git a/crates/rkgk/src/haku.rs b/crates/rkgk/src/haku.rs index 02599e5..0e178d7 100644 --- a/crates/rkgk/src/haku.rs +++ b/crates/rkgk/src/haku.rs @@ -52,8 +52,6 @@ pub struct Haku { system_image: SystemImage, defs: Defs, defs_image: DefsImage, - vm: Vm, - vm_image: VmImage, vm2: Option, @@ -67,20 +65,9 @@ impl Haku { max_defs: limits.max_defs, max_tags: limits.max_tags, }); - let vm = Vm::new( - &defs, - &VmLimits { - stack_capacity: limits.stack_capacity, - call_stack_capacity: limits.call_stack_capacity, - ref_capacity: limits.ref_capacity, - fuel: limits.fuel, - memory: limits.memory, - }, - ); let system_image = system.image(); let defs_image = defs.image(); - let vm_image = vm.image(); Self { limits, @@ -88,8 +75,6 @@ impl Haku { system_image, defs, defs_image, - vm, - vm_image, vm2: None, brush: None, } @@ -163,7 +148,6 @@ impl Haku { let limits = haku2::Limits::new(haku2::LimitsSpec { stack_capacity: self.limits.stack_capacity, call_stack_capacity: self.limits.call_stack_capacity, - fuel: self.limits.fuel as u32, }); self.vm2 = Some(haku2::Vm::new(scratch, code, &limits)) } @@ -173,60 +157,30 @@ impl Haku { Ok(()) } - #[instrument(skip(self), err(level = Level::INFO))] - pub fn eval_brush(&mut self) -> eyre::Result { - let (chunk_id, closure_spec) = self - .brush - .ok_or_eyre("brush is not compiled and ready to be used")?; - - self.vm.restore_image(&self.vm_image); - self.vm.apply_defs(&self.defs); - - let closure_id = self - .vm - .create_ref(Ref::Closure(Closure::chunk(chunk_id, closure_spec))) - .context("not enough ref slots to create initial closure")?; - - let scribble = self - .vm - .run(&self.system, closure_id, &[]) - .context("an exception occurred while evaluating the scribble")?; - - Ok(scribble) - } - #[instrument(skip(self), err(level = Level::INFO))] pub fn eval_brush2(&mut self) -> eyre::Result<()> { let vm = self .vm2 .as_mut() .ok_or_eyre("brush is not compiled and ready to be used")?; - vm.begin().context("an exception occurred during begin()")?; + vm.begin(self.limits.fuel as u32) + .context("an exception occurred during begin()")?; Ok(()) } - #[instrument(skip(self, pixmap, value, translation), err(level = Level::INFO))] - pub fn render_value( - &self, - pixmap: &mut Pixmap, - value: Value, - translation: Vec2, + #[instrument(skip(self, canvas, max_depth), err(level = Level::INFO))] + pub fn render2( + &mut self, + canvas: &mut dyn haku2::Canvas, + max_depth: usize, ) -> eyre::Result<()> { - let mut renderer = Renderer::new( - pixmap, - &RendererLimits { - pixmap_stack_capacity: self.limits.pixmap_stack_capacity, - transform_stack_capacity: self.limits.transform_stack_capacity, - }, - ); - renderer.translate(translation.x, translation.y); - let result = renderer.render(&self.vm, value); - - result.context("an exception occurred while rendering the scribble") - } - - pub fn cont(&self, trampoline: &Trampoline) -> Cont { - trampoline.cont(&self.vm) + let vm = self + .vm2 + .as_mut() + .ok_or_eyre("VM is not ready for rendering")?; + vm.render(canvas, max_depth) + .context("exception while rendering")?; + Ok(()) } pub fn has_cont2(&mut self) -> bool { @@ -236,14 +190,4 @@ impl Haku { pub fn cont2(&mut self) -> haku2::Cont<'_> { self.vm2.as_mut().expect("VM is not started").cont() } - - pub fn dotter( - &mut self, - trampoline: &mut Trampoline, - from: Vec2, - to: Vec2, - num: f32, - ) -> eyre::Result<()> { - Ok(trampoline.dotter(&mut self.vm, &self.system, from.into(), to.into(), num)?) - } } diff --git a/crates/rkgk/src/id.rs b/crates/rkgk/src/id.rs index 9a054ff..22c2100 100644 --- a/crates/rkgk/src/id.rs +++ b/crates/rkgk/src/id.rs @@ -12,6 +12,15 @@ pub fn serialize(f: &mut fmt::Formatter<'_>, prefix: &str, bytes: &[u8; 32]) -> Ok(()) } +pub fn serialize_debug(f: &mut fmt::Formatter<'_>, bytes: &[u8; 32]) -> fmt::Result { + let mut buffer = [b'0'; 43]; + base64::engine::general_purpose::URL_SAFE_NO_PAD + .encode_slice(bytes, &mut buffer) + .unwrap(); + f.write_str(std::str::from_utf8(&buffer[0..6]).unwrap())?; + Ok(()) +} + #[derive(Debug)] pub struct InvalidId; diff --git a/crates/rkgk/src/main.rs b/crates/rkgk/src/main.rs index f402982..b8d0e75 100644 --- a/crates/rkgk/src/main.rs +++ b/crates/rkgk/src/main.rs @@ -5,7 +5,7 @@ use config::Config; use eyre::Context; use router::router; use tokio::{fs, net::TcpListener}; -use tracing::info; +use tracing::{info, level_filters::LevelFilter}; use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt as _, EnvFilter}; mod api; @@ -95,8 +95,15 @@ async fn main() { color_eyre::install().unwrap(); tracing_subscriber::registry() - .with(tracing_subscriber::fmt::layer()) - .with(EnvFilter::from_default_env()) + .with( + tracing_subscriber::fmt::layer() + .event_format(tracing_subscriber::fmt::format().without_time()), + ) + .with( + EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + .from_env_lossy(), + ) .init(); tracing::debug!("debug logs are enabled"); diff --git a/crates/rkgk/src/wall.rs b/crates/rkgk/src/wall.rs index 5dca597..a638938 100644 --- a/crates/rkgk/src/wall.rs +++ b/crates/rkgk/src/wall.rs @@ -22,11 +22,12 @@ pub mod broker; pub mod chunk_images; pub mod chunk_iterator; pub mod database; +pub mod render; pub use broker::Broker; pub use database::Database; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct WallId([u8; 32]); impl WallId { @@ -37,6 +38,12 @@ impl WallId { } } +impl fmt::Debug for WallId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + id::serialize_debug(f, &self.0) + } +} + impl fmt::Display for WallId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { id::serialize(f, "wall_", &self.0) diff --git a/crates/rkgk/src/wall/broker.rs b/crates/rkgk/src/wall/broker.rs index 23c5ddc..dae3a92 100644 --- a/crates/rkgk/src/wall/broker.rs +++ b/crates/rkgk/src/wall/broker.rs @@ -56,7 +56,7 @@ impl Broker { WallId::new(&mut *rng) } - #[instrument(skip(self), fields(%wall_id))] + #[instrument(skip(self), fields(?wall_id))] pub async fn open(&self, wall_id: WallId) -> eyre::Result { let open_wall = self.open_walls.entry(wall_id); diff --git a/crates/rkgk/src/wall/database.rs b/crates/rkgk/src/wall/database.rs index ee7a650..6b70b93 100644 --- a/crates/rkgk/src/wall/database.rs +++ b/crates/rkgk/src/wall/database.rs @@ -77,7 +77,7 @@ impl Database { } } -#[instrument(name = "wall::database::start", skip(settings), fields(wall_id = %settings.wall_id))] +#[instrument(name = "wall::database::start", skip(settings), fields(wall_id = ?settings.wall_id))] pub fn start(settings: Settings) -> eyre::Result { let db = Connection::open(settings.path).context("cannot open wall database")?; diff --git a/crates/rkgk/src/wall/render.rs b/crates/rkgk/src/wall/render.rs new file mode 100644 index 0000000..0299fb7 --- /dev/null +++ b/crates/rkgk/src/wall/render.rs @@ -0,0 +1,113 @@ +//! Implementation of a haku2 canvas based on tiny-skia. + +use std::mem; + +use tiny_skia::{ + BlendMode, Color, FillRule, LineCap, Paint, PathBuilder, Shader, Stroke, Transform, +}; + +use super::Chunk; + +pub struct ChunkCanvas<'c> { + chunk: &'c mut Chunk, + transform: Transform, + pb: PathBuilder, +} + +impl<'c> ChunkCanvas<'c> { + pub fn new(chunk: &'c mut Chunk) -> Self { + Self { + chunk, + transform: Transform::identity(), + pb: PathBuilder::new(), + } + } + + pub fn translated(mut self, x: f32, y: f32) -> Self { + self.transform = self.transform.post_translate(x, y); + self + } +} + +impl haku2::Canvas for ChunkCanvas<'_> { + fn begin(&mut self) -> Result<(), haku2::RenderError> { + self.pb.clear(); + Ok(()) + } + + fn line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) -> Result<(), haku2::RenderError> { + self.pb.move_to(x1, y1); + self.pb.line_to(x2, y2); + Ok(()) + } + + fn rectangle( + &mut self, + x: f32, + y: f32, + width: f32, + height: f32, + ) -> Result<(), haku2::RenderError> { + if let Some(rect) = tiny_skia::Rect::from_xywh(x, y, width, height) { + self.pb.push_rect(rect); + } + Ok(()) + } + + fn circle(&mut self, x: f32, y: f32, r: f32) -> Result<(), haku2::RenderError> { + self.pb.push_circle(x, y, r); + Ok(()) + } + + fn fill(&mut self, r: u8, g: u8, b: u8, a: u8) -> Result<(), haku2::RenderError> { + let pb = mem::take(&mut self.pb); + if let Some(path) = pb.finish() { + let paint = Paint { + shader: Shader::SolidColor(Color::from_rgba8(r, g, b, a)), + ..default_paint() + }; + self.chunk + .pixmap + .fill_path(&path, &paint, FillRule::EvenOdd, self.transform, None); + } + Ok(()) + } + + fn stroke( + &mut self, + r: u8, + g: u8, + b: u8, + a: u8, + thickness: f32, + ) -> Result<(), haku2::RenderError> { + let pb = mem::take(&mut self.pb); + if let Some(path) = pb.finish() { + let paint = Paint { + shader: Shader::SolidColor(Color::from_rgba8(r, g, b, a)), + ..default_paint() + }; + self.chunk.pixmap.stroke_path( + &path, + &paint, + &Stroke { + width: thickness, + line_cap: LineCap::Round, + ..Default::default() + }, + self.transform, + None, + ); + } + Ok(()) + } +} + +fn default_paint() -> Paint<'static> { + Paint { + shader: Shader::SolidColor(Color::BLACK), + blend_mode: BlendMode::SourceOver, + anti_alias: false, + force_hq_pipeline: false, + } +}