haku2: make server use haku2 (and make it work!)
This commit is contained in:
parent
c5e2892def
commit
48d03699bd
16 changed files with 329 additions and 174 deletions
79
Cargo.lock
generated
79
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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});
|
||||
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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<Wall>, limits: Limits, mut commands: mpsc::Receiver<RenderCommand>) {
|
||||
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<Trampoline>,
|
||||
) -> 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(())
|
||||
}
|
||||
|
|
|
@ -52,8 +52,6 @@ pub struct Haku {
|
|||
system_image: SystemImage,
|
||||
defs: Defs,
|
||||
defs_image: DefsImage,
|
||||
vm: Vm,
|
||||
vm_image: VmImage,
|
||||
|
||||
vm2: Option<haku2::Vm>,
|
||||
|
||||
|
@ -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<Value> {
|
||||
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)?)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<OpenWall> {
|
||||
let open_wall = self.open_walls.entry(wall_id);
|
||||
|
||||
|
|
|
@ -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<Database> {
|
||||
let db = Connection::open(settings.path).context("cannot open wall database")?;
|
||||
|
||||
|
|
113
crates/rkgk/src/wall/render.rs
Normal file
113
crates/rkgk/src/wall/render.rs
Normal file
|
@ -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,
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue