haku2: on the client
resource indicators are currently unimplemented
This commit is contained in:
parent
45c954cb03
commit
e667c6336a
10 changed files with 437 additions and 364 deletions
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
use core::{alloc::Layout, slice};
|
use core::{alloc::Layout, mem, slice};
|
||||||
|
|
||||||
use alloc::{boxed::Box, vec::Vec};
|
use alloc::{boxed::Box, string::String, vec::Vec};
|
||||||
use haku::{
|
use haku::{
|
||||||
ast::Ast,
|
ast::Ast,
|
||||||
bytecode::{Chunk, Defs, DefsImage, DefsLimits},
|
bytecode::{Chunk, Defs, DefsImage, DefsLimits},
|
||||||
|
@ -12,16 +12,13 @@ use haku::{
|
||||||
diagnostic::Diagnostic,
|
diagnostic::Diagnostic,
|
||||||
lexer::{lex, Lexer},
|
lexer::{lex, Lexer},
|
||||||
parser::{self, IntoAstError, Parser},
|
parser::{self, IntoAstError, Parser},
|
||||||
render::{
|
render::tiny_skia::{
|
||||||
tiny_skia::{Pixmap, PremultipliedColorU8},
|
BlendMode, Color, FillRule, LineCap, Paint, PathBuilder, Pixmap, PremultipliedColorU8,
|
||||||
RendererLimits,
|
Rect, Shader, Stroke, Transform,
|
||||||
},
|
},
|
||||||
source::SourceCode,
|
source::SourceCode,
|
||||||
system::{ChunkId, System, SystemImage},
|
system::{System, SystemImage},
|
||||||
token::Lexis,
|
token::Lexis,
|
||||||
trampoline::{Cont, Trampoline},
|
|
||||||
value::{Closure, Ref, Vec2},
|
|
||||||
vm::{Exception, Vm, VmImage, VmLimits},
|
|
||||||
};
|
};
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
|
|
||||||
|
@ -57,8 +54,7 @@ struct Limits {
|
||||||
ref_capacity: usize,
|
ref_capacity: usize,
|
||||||
fuel: usize,
|
fuel: usize,
|
||||||
memory: usize,
|
memory: usize,
|
||||||
pixmap_stack_capacity: usize,
|
render_max_depth: usize,
|
||||||
transform_stack_capacity: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Limits {
|
impl Default for Limits {
|
||||||
|
@ -77,8 +73,7 @@ impl Default for Limits {
|
||||||
ref_capacity: 2048,
|
ref_capacity: 2048,
|
||||||
fuel: 65536,
|
fuel: 65536,
|
||||||
memory: 1024 * 1024,
|
memory: 1024 * 1024,
|
||||||
pixmap_stack_capacity: 4,
|
render_max_depth: 256,
|
||||||
transform_stack_capacity: 16,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,8 +118,7 @@ limit_setter!(call_stack_capacity);
|
||||||
limit_setter!(ref_capacity);
|
limit_setter!(ref_capacity);
|
||||||
limit_setter!(fuel);
|
limit_setter!(fuel);
|
||||||
limit_setter!(memory);
|
limit_setter!(memory);
|
||||||
limit_setter!(pixmap_stack_capacity);
|
limit_setter!(render_max_depth);
|
||||||
limit_setter!(transform_stack_capacity);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct Instance {
|
struct Instance {
|
||||||
|
@ -134,23 +128,23 @@ struct Instance {
|
||||||
system_image: SystemImage,
|
system_image: SystemImage,
|
||||||
defs: Defs,
|
defs: Defs,
|
||||||
defs_image: DefsImage,
|
defs_image: DefsImage,
|
||||||
vm: Vm,
|
|
||||||
vm_image: VmImage,
|
|
||||||
|
|
||||||
trampoline: Option<Trampoline>,
|
compile_result2: Option<CompileResult>,
|
||||||
exception: Option<Exception>,
|
diagnostics2: Vec<Diagnostic>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Instance {
|
#[derive(Debug, Clone)]
|
||||||
fn set_exception(&mut self, exn: Exception) {
|
struct CompileResult {
|
||||||
debug!("setting exception = {exn:?}");
|
defs_string: String,
|
||||||
self.exception = Some(exn);
|
tags_string: String,
|
||||||
|
chunk: Chunk,
|
||||||
|
closure_spec: ClosureSpec,
|
||||||
|
stats: CompileStats,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset_exception(&mut self) {
|
#[derive(Debug, Clone)]
|
||||||
debug!("resetting exception");
|
struct CompileStats {
|
||||||
self.exception = None;
|
ast_size: usize,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
|
@ -164,20 +158,9 @@ unsafe extern "C" fn haku_instance_new(limits: *const Limits) -> *mut Instance {
|
||||||
max_defs: limits.max_defs,
|
max_defs: limits.max_defs,
|
||||||
max_tags: limits.max_tags,
|
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 system_image = system.image();
|
||||||
let defs_image = defs.image();
|
let defs_image = defs.image();
|
||||||
let vm_image = vm.image();
|
|
||||||
|
|
||||||
let instance = Box::new(Instance {
|
let instance = Box::new(Instance {
|
||||||
limits,
|
limits,
|
||||||
|
@ -185,10 +168,8 @@ unsafe extern "C" fn haku_instance_new(limits: *const Limits) -> *mut Instance {
|
||||||
system_image,
|
system_image,
|
||||||
defs,
|
defs,
|
||||||
defs_image,
|
defs_image,
|
||||||
vm,
|
compile_result2: None,
|
||||||
vm_image,
|
diagnostics2: Vec::new(),
|
||||||
trampoline: None,
|
|
||||||
exception: None,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let ptr = Box::leak(instance) as *mut _;
|
let ptr = Box::leak(instance) as *mut _;
|
||||||
|
@ -210,21 +191,6 @@ unsafe extern "C" fn haku_reset(instance: *mut Instance) {
|
||||||
instance.defs.restore_image(&instance.defs_image);
|
instance.defs.restore_image(&instance.defs_image);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
|
||||||
unsafe extern "C" fn haku_has_exception(instance: *mut Instance) -> bool {
|
|
||||||
(*instance).exception.is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
|
||||||
unsafe extern "C" fn haku_exception_message(instance: *const Instance) -> *const u8 {
|
|
||||||
(*instance).exception.as_ref().unwrap().message.as_ptr()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
|
||||||
unsafe extern "C" fn haku_exception_message_len(instance: *const Instance) -> u32 {
|
|
||||||
(*instance).exception.as_ref().unwrap().message.len() as u32
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
enum StatusCode {
|
enum StatusCode {
|
||||||
|
@ -236,10 +202,6 @@ enum StatusCode {
|
||||||
ParserUnbalancedEvents,
|
ParserUnbalancedEvents,
|
||||||
ChunkTooBig,
|
ChunkTooBig,
|
||||||
DiagnosticsEmitted,
|
DiagnosticsEmitted,
|
||||||
TooManyChunks,
|
|
||||||
OutOfRefSlots,
|
|
||||||
EvalException,
|
|
||||||
RenderException,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
|
@ -252,14 +214,6 @@ extern "C" fn haku_is_diagnostics_emitted(code: StatusCode) -> bool {
|
||||||
code == StatusCode::DiagnosticsEmitted
|
code == StatusCode::DiagnosticsEmitted
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
|
||||||
extern "C" fn haku_is_exception(code: StatusCode) -> bool {
|
|
||||||
matches!(
|
|
||||||
code,
|
|
||||||
StatusCode::EvalException | StatusCode::RenderException
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
extern "C" fn haku_status_string(code: StatusCode) -> *const i8 {
|
extern "C" fn haku_status_string(code: StatusCode) -> *const i8 {
|
||||||
match code {
|
match code {
|
||||||
|
@ -271,91 +225,162 @@ extern "C" fn haku_status_string(code: StatusCode) -> *const i8 {
|
||||||
StatusCode::ParserUnbalancedEvents => c"parser produced unbalanced events",
|
StatusCode::ParserUnbalancedEvents => c"parser produced unbalanced events",
|
||||||
StatusCode::ChunkTooBig => c"compiled bytecode is too large",
|
StatusCode::ChunkTooBig => c"compiled bytecode is too large",
|
||||||
StatusCode::DiagnosticsEmitted => c"diagnostics were emitted",
|
StatusCode::DiagnosticsEmitted => c"diagnostics were emitted",
|
||||||
StatusCode::TooManyChunks => c"too many registered bytecode chunks",
|
|
||||||
StatusCode::OutOfRefSlots => c"out of ref slots (did you forget to restore the VM image?)",
|
|
||||||
StatusCode::EvalException => c"an exception occurred while evaluating your code",
|
|
||||||
StatusCode::RenderException => c"an exception occurred while rendering your brush",
|
|
||||||
}
|
}
|
||||||
.as_ptr()
|
.as_ptr()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
struct PixmapCanvas {
|
||||||
struct CompileStats {
|
pixmap: Pixmap,
|
||||||
ast_size: usize,
|
pb: PathBuilder,
|
||||||
}
|
transform: Transform,
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct RunnableBrush {
|
|
||||||
chunk_id: ChunkId,
|
|
||||||
closure_spec: ClosureSpec,
|
|
||||||
|
|
||||||
compile_stats: CompileStats,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
enum BrushState {
|
|
||||||
#[default]
|
|
||||||
Default,
|
|
||||||
Ready(RunnableBrush),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
struct Brush {
|
|
||||||
diagnostics: Vec<Diagnostic>,
|
|
||||||
state: BrushState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
extern "C" fn haku_brush_new() -> *mut Brush {
|
extern "C" fn haku_pixmap_new(width: u32, height: u32) -> *mut PixmapCanvas {
|
||||||
let ptr = Box::leak(Box::new(Brush::default())) as *mut _;
|
let ptr = Box::leak(Box::new(PixmapCanvas {
|
||||||
debug!("created brush: {ptr:?}");
|
pixmap: Pixmap::new(width, height).expect("invalid pixmap size"),
|
||||||
|
pb: PathBuilder::new(),
|
||||||
|
transform: Transform::identity(),
|
||||||
|
})) as *mut _;
|
||||||
|
debug!("created pixmap with size {width}x{height}: {ptr:?}");
|
||||||
ptr
|
ptr
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn haku_brush_destroy(brush: *mut Brush) {
|
unsafe extern "C" fn haku_pixmap_destroy(c: *mut PixmapCanvas) {
|
||||||
debug!("destroying brush: {brush:?}");
|
debug!("destroying pixmap: {c:?}");
|
||||||
drop(Box::from_raw(brush))
|
drop(Box::from_raw(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn haku_num_diagnostics(brush: *const Brush) -> u32 {
|
unsafe extern "C" fn haku_pixmap_data(c: *mut PixmapCanvas) -> *mut u8 {
|
||||||
(*brush).diagnostics.len() as u32
|
let c = &mut *c;
|
||||||
|
c.pixmap.pixels_mut().as_mut_ptr() as *mut u8
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn haku_diagnostic_start(brush: *const Brush, index: u32) -> u32 {
|
unsafe extern "C" fn haku_pixmap_clear(c: *mut PixmapCanvas) {
|
||||||
(*brush).diagnostics[index as usize].span().start
|
let c = &mut *c;
|
||||||
|
c.pixmap
|
||||||
|
.pixels_mut()
|
||||||
|
.fill(PremultipliedColorU8::TRANSPARENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn haku_diagnostic_end(brush: *const Brush, index: u32) -> u32 {
|
unsafe extern "C" fn haku_pixmap_set_translation(c: *mut PixmapCanvas, x: f32, y: f32) {
|
||||||
(*brush).diagnostics[index as usize].span().end
|
let c = &mut *c;
|
||||||
|
c.transform = Transform::from_translate(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn haku_diagnostic_message(brush: *const Brush, index: u32) -> *const u8 {
|
unsafe extern "C" fn haku_pixmap_begin(c: *mut PixmapCanvas) -> bool {
|
||||||
(*brush).diagnostics[index as usize].message().as_ptr()
|
let c = &mut *c;
|
||||||
|
c.pb.clear();
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn haku_diagnostic_message_len(brush: *const Brush, index: u32) -> u32 {
|
unsafe extern "C" fn haku_pixmap_line(
|
||||||
(*brush).diagnostics[index as usize].message().len() as u32
|
c: *mut PixmapCanvas,
|
||||||
|
x1: f32,
|
||||||
|
y1: f32,
|
||||||
|
x2: f32,
|
||||||
|
y2: f32,
|
||||||
|
) -> bool {
|
||||||
|
let c = &mut *c;
|
||||||
|
c.pb.move_to(x1, y1);
|
||||||
|
c.pb.line_to(x2, y2);
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn haku_compile_brush(
|
unsafe extern "C" fn haku_pixmap_rectangle(
|
||||||
|
c: *mut PixmapCanvas,
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
width: f32,
|
||||||
|
height: f32,
|
||||||
|
) -> bool {
|
||||||
|
let c = &mut *c;
|
||||||
|
if let Some(rect) = Rect::from_xywh(x, y, width, height) {
|
||||||
|
c.pb.push_rect(rect);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
unsafe extern "C" fn haku_pixmap_circle(c: *mut PixmapCanvas, x: f32, y: f32, r: f32) -> bool {
|
||||||
|
let c = &mut *c;
|
||||||
|
c.pb.push_circle(x, y, r);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_paint() -> Paint<'static> {
|
||||||
|
Paint {
|
||||||
|
shader: Shader::SolidColor(Color::BLACK),
|
||||||
|
blend_mode: BlendMode::SourceOver,
|
||||||
|
anti_alias: false,
|
||||||
|
force_hq_pipeline: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
unsafe extern "C" fn haku_pixmap_fill(c: *mut PixmapCanvas, r: u8, g: u8, b: u8, a: u8) -> bool {
|
||||||
|
let c = &mut *c;
|
||||||
|
let pb = mem::take(&mut c.pb);
|
||||||
|
if let Some(path) = pb.finish() {
|
||||||
|
let paint = Paint {
|
||||||
|
shader: Shader::SolidColor(Color::from_rgba8(r, g, b, a)),
|
||||||
|
..default_paint()
|
||||||
|
};
|
||||||
|
c.pixmap
|
||||||
|
.fill_path(&path, &paint, FillRule::EvenOdd, c.transform, None);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
unsafe extern "C" fn haku_pixmap_stroke(
|
||||||
|
c: *mut PixmapCanvas,
|
||||||
|
r: u8,
|
||||||
|
g: u8,
|
||||||
|
b: u8,
|
||||||
|
a: u8,
|
||||||
|
thickness: f32,
|
||||||
|
) -> bool {
|
||||||
|
let c = &mut *c;
|
||||||
|
let pb = mem::take(&mut c.pb);
|
||||||
|
if let Some(path) = pb.finish() {
|
||||||
|
let paint = Paint {
|
||||||
|
shader: Shader::SolidColor(Color::from_rgba8(r, g, b, a)),
|
||||||
|
..default_paint()
|
||||||
|
};
|
||||||
|
c.pixmap.stroke_path(
|
||||||
|
&path,
|
||||||
|
&paint,
|
||||||
|
&Stroke {
|
||||||
|
width: thickness,
|
||||||
|
line_cap: LineCap::Round,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
c.transform,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
// v2 compile-only support
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
unsafe extern "C" fn haku_compile_brush2(
|
||||||
instance: *mut Instance,
|
instance: *mut Instance,
|
||||||
out_brush: *mut Brush,
|
|
||||||
code_len: u32,
|
code_len: u32,
|
||||||
code: *const u8,
|
code: *const u8,
|
||||||
) -> StatusCode {
|
) -> StatusCode {
|
||||||
info!("compiling brush");
|
info!("compiling brush (2)");
|
||||||
|
|
||||||
let instance = &mut *instance;
|
let instance = &mut *instance;
|
||||||
let brush = &mut *out_brush;
|
|
||||||
|
|
||||||
*brush = Brush::default();
|
|
||||||
|
|
||||||
let code = core::str::from_utf8(slice::from_raw_parts(code, code_len as usize))
|
let code = core::str::from_utf8(slice::from_raw_parts(code, code_len as usize))
|
||||||
.expect("invalid UTF-8");
|
.expect("invalid UTF-8");
|
||||||
|
@ -428,8 +453,8 @@ unsafe extern "C" fn haku_compile_brush(
|
||||||
let mut diagnostics = lexer.diagnostics;
|
let mut diagnostics = lexer.diagnostics;
|
||||||
diagnostics.append(&mut parser_diagnostics);
|
diagnostics.append(&mut parser_diagnostics);
|
||||||
diagnostics.append(&mut compiler.diagnostics);
|
diagnostics.append(&mut compiler.diagnostics);
|
||||||
if !diagnostics.is_empty() {
|
instance.diagnostics2 = diagnostics;
|
||||||
brush.diagnostics = diagnostics;
|
if !instance.diagnostics2.is_empty() {
|
||||||
debug!("compiling failed: diagnostics were emitted");
|
debug!("compiling failed: diagnostics were emitted");
|
||||||
return StatusCode::DiagnosticsEmitted;
|
return StatusCode::DiagnosticsEmitted;
|
||||||
}
|
}
|
||||||
|
@ -440,179 +465,82 @@ unsafe extern "C" fn haku_compile_brush(
|
||||||
);
|
);
|
||||||
debug!("compiling: {closure_spec:?}");
|
debug!("compiling: {closure_spec:?}");
|
||||||
|
|
||||||
let chunk_id = match instance.system.add_chunk(chunk) {
|
instance.compile_result2 = Some(CompileResult {
|
||||||
Ok(chunk_id) => chunk_id,
|
defs_string: instance.defs.serialize_defs(),
|
||||||
Err(_) => return StatusCode::TooManyChunks,
|
tags_string: instance.defs.serialize_tags(),
|
||||||
};
|
chunk,
|
||||||
brush.state = BrushState::Ready(RunnableBrush {
|
|
||||||
chunk_id,
|
|
||||||
closure_spec,
|
closure_spec,
|
||||||
compile_stats: CompileStats {
|
stats: CompileStats {
|
||||||
ast_size: ast.len(),
|
ast_size: ast.len(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
info!("brush compiled into {chunk_id:?}");
|
info!("brush code compiled into instance");
|
||||||
|
|
||||||
StatusCode::Ok
|
StatusCode::Ok
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
unsafe fn compile_result<'a>(instance: *const Instance) -> &'a CompileResult {
|
||||||
extern "C" fn haku_pixmap_new(width: u32, height: u32) -> *mut Pixmap {
|
let cr = (*instance).compile_result2.as_ref().unwrap();
|
||||||
let ptr = Box::leak(Box::new(
|
cr
|
||||||
Pixmap::new(width, height).expect("invalid pixmap size"),
|
|
||||||
)) as *mut _;
|
|
||||||
debug!("created pixmap with size {width}x{height}: {ptr:?}");
|
|
||||||
ptr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn haku_pixmap_destroy(pixmap: *mut Pixmap) {
|
unsafe extern "C" fn haku_num_diagnostics2(instance: *const Instance) -> u32 {
|
||||||
debug!("destroying pixmap: {pixmap:?}");
|
(*instance).diagnostics2.len() as u32
|
||||||
drop(Box::from_raw(pixmap))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn haku_pixmap_data(pixmap: *mut Pixmap) -> *mut u8 {
|
unsafe extern "C" fn haku_diagnostic_start2(instance: *const Instance, index: u32) -> u32 {
|
||||||
let pixmap = &mut *pixmap;
|
(*instance).diagnostics2[index as usize].span().start
|
||||||
pixmap.pixels_mut().as_mut_ptr() as *mut u8
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn haku_pixmap_clear(pixmap: *mut Pixmap) {
|
unsafe extern "C" fn haku_diagnostic_end2(instance: *const Instance, index: u32) -> u32 {
|
||||||
let pixmap = &mut *pixmap;
|
(*instance).diagnostics2[index as usize].span().end
|
||||||
pixmap.pixels_mut().fill(PremultipliedColorU8::TRANSPARENT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn haku_begin_brush(instance: *mut Instance, brush: *const Brush) -> StatusCode {
|
unsafe extern "C" fn haku_diagnostic_message2(instance: *const Instance, index: u32) -> *const u8 {
|
||||||
let instance = &mut *instance;
|
(*instance).diagnostics2[index as usize].message().as_ptr()
|
||||||
let brush = &*brush;
|
|
||||||
|
|
||||||
let BrushState::Ready(runnable) = &brush.state else {
|
|
||||||
panic!("brush is not compiled and ready to be used");
|
|
||||||
};
|
|
||||||
|
|
||||||
instance.vm.restore_image(&instance.vm_image);
|
|
||||||
instance.vm.apply_defs(&instance.defs);
|
|
||||||
instance.reset_exception();
|
|
||||||
instance.trampoline = None;
|
|
||||||
|
|
||||||
let Ok(closure_id) = instance.vm.create_ref(Ref::Closure(Closure::chunk(
|
|
||||||
runnable.chunk_id,
|
|
||||||
runnable.closure_spec,
|
|
||||||
))) else {
|
|
||||||
return StatusCode::OutOfRefSlots;
|
|
||||||
};
|
|
||||||
|
|
||||||
instance.reset_exception();
|
|
||||||
let value = match instance.vm.run(&instance.system, closure_id, &[]) {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(exn) => {
|
|
||||||
instance.set_exception(exn);
|
|
||||||
return StatusCode::EvalException;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
instance.trampoline = Some(Trampoline::new(value));
|
|
||||||
|
|
||||||
StatusCode::Ok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn haku_cont_kind(instance: *mut Instance) -> Cont {
|
unsafe extern "C" fn haku_diagnostic_message_len2(instance: *const Instance, index: u32) -> u32 {
|
||||||
let instance = &mut *instance;
|
(*instance).diagnostics2[index as usize].message().len() as u32
|
||||||
instance.trampoline.as_ref().unwrap().cont(&instance.vm)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn wrap_exception(
|
|
||||||
instance: &mut Instance,
|
|
||||||
error_code: StatusCode,
|
|
||||||
f: impl FnOnce(&mut Instance) -> Result<(), Exception>,
|
|
||||||
) -> StatusCode {
|
|
||||||
match f(instance) {
|
|
||||||
Ok(_) => StatusCode::Ok,
|
|
||||||
Err(exn) => {
|
|
||||||
instance.set_exception(exn);
|
|
||||||
error_code
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn haku_cont_scribble(
|
unsafe extern "C" fn haku_defs_len2(instance: *const Instance) -> usize {
|
||||||
instance: *mut Instance,
|
compile_result(instance).defs_string.len()
|
||||||
pixmap: *mut Pixmap,
|
|
||||||
translation_x: f32,
|
|
||||||
translation_y: f32,
|
|
||||||
) -> StatusCode {
|
|
||||||
let instance = &mut *instance;
|
|
||||||
instance.reset_exception();
|
|
||||||
|
|
||||||
debug!("cont_scribble: pixmap={pixmap:?} translation_x={translation_x:?} translation_y={translation_y:?} trampoline={:?}", instance.trampoline);
|
|
||||||
|
|
||||||
wrap_exception(instance, StatusCode::RenderException, |instance| {
|
|
||||||
instance.trampoline.as_mut().unwrap().scribble(
|
|
||||||
&instance.vm,
|
|
||||||
&mut *pixmap,
|
|
||||||
Vec2 {
|
|
||||||
x: translation_x,
|
|
||||||
y: translation_y,
|
|
||||||
},
|
|
||||||
&RendererLimits {
|
|
||||||
pixmap_stack_capacity: instance.limits.pixmap_stack_capacity,
|
|
||||||
transform_stack_capacity: instance.limits.transform_stack_capacity,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn haku_cont_dotter(
|
unsafe extern "C" fn haku_defs2(instance: *const Instance) -> *const u8 {
|
||||||
instance: *mut Instance,
|
compile_result(instance).defs_string.as_ptr()
|
||||||
from_x: f32,
|
|
||||||
from_y: f32,
|
|
||||||
to_x: f32,
|
|
||||||
to_y: f32,
|
|
||||||
num: f32,
|
|
||||||
) -> StatusCode {
|
|
||||||
let instance = &mut *instance;
|
|
||||||
instance.reset_exception();
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
"cont_dotter: from_x={from_x} from_y={from_y} to_x={to_x} to_y={to_y} trampoline={:?}",
|
|
||||||
instance.trampoline
|
|
||||||
);
|
|
||||||
|
|
||||||
wrap_exception(instance, StatusCode::RenderException, |instance| {
|
|
||||||
instance.trampoline.as_mut().unwrap().dotter(
|
|
||||||
&mut instance.vm,
|
|
||||||
&instance.system,
|
|
||||||
Vec2::new(from_x, from_y),
|
|
||||||
Vec2::new(to_x, to_y),
|
|
||||||
num,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn haku_stat_ast_size(brush: *const Brush) -> usize {
|
unsafe extern "C" fn haku_tags_len2(instance: *const Instance) -> usize {
|
||||||
match &(*brush).state {
|
compile_result(instance).tags_string.len()
|
||||||
BrushState::Default => 0,
|
|
||||||
BrushState::Ready(runnable) => runnable.compile_stats.ast_size,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn haku_stat_num_refs(instance: *const Instance) -> usize {
|
unsafe extern "C" fn haku_tags2(instance: *const Instance) -> *const u8 {
|
||||||
(*instance).vm.num_refs()
|
compile_result(instance).tags_string.as_ptr()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn haku_stat_remaining_fuel(instance: *const Instance) -> usize {
|
unsafe extern "C" fn haku_bytecode_len2(instance: *const Instance) -> usize {
|
||||||
(*instance).vm.remaining_fuel()
|
compile_result(instance).chunk.bytecode.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn haku_stat_remaining_memory(instance: *const Instance) -> usize {
|
unsafe extern "C" fn haku_bytecode2(instance: *const Instance) -> *const u8 {
|
||||||
(*instance).vm.remaining_memory()
|
compile_result(instance).chunk.bytecode.as_ptr()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
unsafe extern "C" fn haku_local_count2(instance: *const Instance) -> u8 {
|
||||||
|
compile_result(instance).closure_spec.local_count
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
.arg("--cache-dir")
|
.arg("--cache-dir")
|
||||||
.arg(out_path.join("zig-cache"))
|
.arg(out_path.join("zig-cache"))
|
||||||
.arg("--prefix")
|
.arg("--prefix")
|
||||||
.arg(out_path.join("zig-out").join(target))
|
.arg(out_path.join("zig-out"))
|
||||||
// Build settings
|
// Build settings
|
||||||
.arg(format!("-Doptimize={optimize}"))
|
.arg(format!("-Doptimize={optimize}"))
|
||||||
.arg(format!("-Dtarget={target}"))
|
.arg(format!("-Dtarget={target}"))
|
||||||
|
@ -58,7 +58,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
panic!("zig failed to build");
|
panic!("zig failed to build");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("cargo::rustc-link-search={out_dir}/zig-out/{target}/lib");
|
println!("cargo::rustc-link-search={out_dir}/zig-out/lib");
|
||||||
println!("cargo::rustc-link-lib=haku2");
|
println!("cargo::rustc-link-lib=haku2");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -10,7 +10,7 @@ const value = @import("value.zig");
|
||||||
const Vm = @import("vm.zig");
|
const Vm = @import("vm.zig");
|
||||||
const log = @import("log.zig");
|
const log = @import("log.zig");
|
||||||
|
|
||||||
const debug_logs = false;
|
const debug_logs = true;
|
||||||
|
|
||||||
pub const allocator =
|
pub const allocator =
|
||||||
if (builtin.cpu.arch == .wasm32)
|
if (builtin.cpu.arch == .wasm32)
|
||||||
|
@ -23,6 +23,16 @@ pub const std_options: std.Options = .{
|
||||||
.logFn = log.logFn,
|
.logFn = log.logFn,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Allocator
|
||||||
|
|
||||||
|
export fn haku2_alloc(size: usize, alignment: usize) ?[*]u8 {
|
||||||
|
return allocator.rawAlloc(size, mem.Alignment.fromByteUnits(alignment), @returnAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn haku2_free(alloc: [*]u8, size: usize, alignment: usize) void {
|
||||||
|
allocator.rawFree(alloc[0..size], mem.Alignment.fromByteUnits(alignment), @returnAddress());
|
||||||
|
}
|
||||||
|
|
||||||
// Scratch
|
// Scratch
|
||||||
|
|
||||||
export fn haku2_scratch_new(max: usize) ?*Scratch {
|
export fn haku2_scratch_new(max: usize) ?*Scratch {
|
||||||
|
|
|
@ -9,6 +9,8 @@ use std::{
|
||||||
|
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
|
||||||
|
pub static WASM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/zig-out/bin/haku2.wasm"));
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
unsafe extern "C" fn __haku2_alloc(size: usize, align: usize) -> *mut u8 {
|
unsafe extern "C" fn __haku2_alloc(size: usize, align: usize) -> *mut u8 {
|
||||||
if let Ok(layout) = Layout::from_size_align(size, align) {
|
if let Ok(layout) = Layout::from_size_align(size, align) {
|
||||||
|
|
|
@ -60,7 +60,7 @@ pub fn reset(vm: *Vm, fuel: u32) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn throw(vm: *Vm, comptime fmt: []const u8, args: anytype) Error {
|
pub fn throw(vm: *Vm, comptime fmt: []const u8, args: anytype) Error {
|
||||||
log.debug("throw: fmt={s}", .{fmt});
|
log.info("throw: fmt={s}", .{fmt});
|
||||||
|
|
||||||
const Args = @TypeOf(args);
|
const Args = @TypeOf(args);
|
||||||
const max_args_size = @sizeOf(@TypeOf(vm.exception.?.args));
|
const max_args_size = @sizeOf(@TypeOf(vm.exception.?.args));
|
||||||
|
|
|
@ -37,6 +37,7 @@ pub fn build(paths: &Paths<'_>, config: &BuildConfig) -> eyre::Result<()> {
|
||||||
paths.target_dir.join("static/wasm/haku.wasm"),
|
paths.target_dir.join("static/wasm/haku.wasm"),
|
||||||
)
|
)
|
||||||
.context("cannot copy haku.wasm file")?;
|
.context("cannot copy haku.wasm file")?;
|
||||||
|
write(paths.target_dir.join("static/wasm/haku2.wasm"), haku2::WASM)?;
|
||||||
|
|
||||||
let import_map = ImportMap::generate("".into(), &config.import_roots);
|
let import_map = ImportMap::generate("".into(), &config.import_roots);
|
||||||
write(
|
write(
|
||||||
|
|
|
@ -35,8 +35,7 @@ pub struct Limits {
|
||||||
pub ref_capacity: usize,
|
pub ref_capacity: usize,
|
||||||
pub fuel: usize,
|
pub fuel: usize,
|
||||||
pub memory: usize,
|
pub memory: usize,
|
||||||
pub pixmap_stack_capacity: usize,
|
pub render_max_depth: usize,
|
||||||
pub transform_stack_capacity: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Haku {
|
pub struct Haku {
|
||||||
|
|
14
rkgk.toml
14
rkgk.toml
|
@ -114,15 +114,5 @@ fuel = 65536
|
||||||
# In particular, large arrays use up this memory - such as list backing arrays.
|
# In particular, large arrays use up this memory - such as list backing arrays.
|
||||||
memory = 1048576
|
memory = 1048576
|
||||||
|
|
||||||
# Capacity of the renderer's pixmap stack.
|
# Maximum depth the renderer is allowed to recurse to.
|
||||||
# The pixmap stack is used for blending layers together within a brush.
|
render_max_depth = 256
|
||||||
# Each (composite)-type scribble requires a single entry on this pixmap stack.
|
|
||||||
# In the end, this defines how deep compositing operations may nest.
|
|
||||||
pixmap_stack_capacity = 4
|
|
||||||
|
|
||||||
# Capacity of the renderer's transformation stack.
|
|
||||||
# The transformation stack is used for operations on the transform matrix, such as (translate).
|
|
||||||
# To render each transformed operation, a single entry of the transform stack is used.
|
|
||||||
# In the end, this defines how deep matrix transform operations may nest.
|
|
||||||
transform_stack_capacity = 16
|
|
||||||
|
|
||||||
|
|
306
static/haku.js
306
static/haku.js
|
@ -1,5 +1,16 @@
|
||||||
let panicImpl;
|
let panicImpl;
|
||||||
let logImpl;
|
let logImpl, log2Impl;
|
||||||
|
let canvasBeginImpl,
|
||||||
|
canvasLineImpl,
|
||||||
|
canvasRectangleImpl,
|
||||||
|
canvasCircleImpl,
|
||||||
|
canvasFillImpl,
|
||||||
|
canvasStrokeImpl;
|
||||||
|
|
||||||
|
function allocCheck(p) {
|
||||||
|
if (p == 0) throw new Error("out of memory");
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
function makeLogFunction(level) {
|
function makeLogFunction(level) {
|
||||||
return (length, pMessage) => {
|
return (length, pMessage) => {
|
||||||
|
@ -7,9 +18,17 @@ function makeLogFunction(level) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let { instance: hakuInstance, module: hakuModule } = await WebAssembly.instantiateStreaming(
|
function makeLogFunction2(level) {
|
||||||
fetch(HAKU_WASM_PATH),
|
return (pScope, scopeLen, pMsg, len) => {
|
||||||
{
|
log2Impl(level, pScope, scopeLen, pMsg, len);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: It may seem tempting to share memory between the two modules, but that's *impossible*.
|
||||||
|
// This is because Wasm binaries are not position-independent; addresses of the stack and the heap
|
||||||
|
// are hardcoded.
|
||||||
|
let [hakuWasm, haku2Wasm] = await Promise.all([
|
||||||
|
WebAssembly.instantiateStreaming(fetch(HAKU_WASM_PATH), {
|
||||||
env: {
|
env: {
|
||||||
panic(length, pMessage) {
|
panic(length, pMessage) {
|
||||||
panicImpl(length, pMessage);
|
panicImpl(length, pMessage);
|
||||||
|
@ -20,17 +39,37 @@ let { instance: hakuInstance, module: hakuModule } = await WebAssembly.instantia
|
||||||
warn: makeLogFunction("warn"),
|
warn: makeLogFunction("warn"),
|
||||||
error: makeLogFunction("error"),
|
error: makeLogFunction("error"),
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
);
|
WebAssembly.instantiateStreaming(fetch(HAKU2_WASM_PATH), {
|
||||||
|
env: {
|
||||||
|
__haku2_log_err: makeLogFunction2("error"),
|
||||||
|
__haku2_log_warn: makeLogFunction2("warn"),
|
||||||
|
__haku2_log_info: makeLogFunction2("info"),
|
||||||
|
__haku2_log_debug: makeLogFunction2("debug"),
|
||||||
|
|
||||||
let memory = hakuInstance.exports.memory;
|
// TODO: Renderer
|
||||||
let w = hakuInstance.exports;
|
__haku2_canvas_begin: (c) => canvasBeginImpl(c),
|
||||||
|
__haku2_canvas_line: (c, x1, y1, x2, y2) => canvasLineImpl(c, x1, y1, x2, y2),
|
||||||
|
__haku2_canvas_rectangle: (c, x, y, width, height) =>
|
||||||
|
canvasRectangleImpl(c, x, y, width, height),
|
||||||
|
__haku2_canvas_circle: (c, x, y, r) => canvasCircleImpl(c, x, y, r),
|
||||||
|
__haku2_canvas_fill: (c, r, g, b, a) => canvasFillImpl(c, r, g, b, a),
|
||||||
|
__haku2_canvas_stroke: (c, r, g, b, a, thickness) =>
|
||||||
|
canvasStrokeImpl(c, r, g, b, a, thickness),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let memory = hakuWasm.instance.exports.memory;
|
||||||
|
let w = hakuWasm.instance.exports;
|
||||||
|
let memory2 = haku2Wasm.instance.exports.memory;
|
||||||
|
let w2 = haku2Wasm.instance.exports;
|
||||||
|
|
||||||
let textEncoder = new TextEncoder();
|
let textEncoder = new TextEncoder();
|
||||||
function allocString(string) {
|
function allocString(string) {
|
||||||
let size = string.length * 3;
|
let size = string.length * 3;
|
||||||
let align = 1;
|
let align = 1;
|
||||||
let pString = w.haku_alloc(size, align);
|
let pString = allocCheck(w.haku_alloc(size, align));
|
||||||
|
|
||||||
let buffer = new Uint8Array(memory.buffer, pString, size);
|
let buffer = new Uint8Array(memory.buffer, pString, size);
|
||||||
let result = textEncoder.encodeInto(string, buffer);
|
let result = textEncoder.encodeInto(string, buffer);
|
||||||
|
@ -48,7 +87,7 @@ function freeString(alloc) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let textDecoder = new TextDecoder();
|
let textDecoder = new TextDecoder();
|
||||||
function readString(size, pString) {
|
function readString(memory, size, pString) {
|
||||||
let buffer = new Uint8Array(memory.buffer, pString, size);
|
let buffer = new Uint8Array(memory.buffer, pString, size);
|
||||||
return textDecoder.decode(buffer);
|
return textDecoder.decode(buffer);
|
||||||
}
|
}
|
||||||
|
@ -62,7 +101,36 @@ function readCString(pCString) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let size = pCursor - pCString;
|
let size = pCursor - pCString;
|
||||||
return readString(size, pCString);
|
return readString(memory, size, pCString);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dup1to2(pHaku1, size, align) {
|
||||||
|
if (size <= 0) {
|
||||||
|
return {
|
||||||
|
ptr: 0,
|
||||||
|
length: 0,
|
||||||
|
size: 0,
|
||||||
|
align: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let pHaku2 = allocCheck(w2.haku2_alloc(size, align));
|
||||||
|
|
||||||
|
let src = new Uint8Array(memory.buffer, pHaku1, size);
|
||||||
|
let dst = new Uint8Array(memory2.buffer, pHaku2, size);
|
||||||
|
dst.set(src);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ptr: pHaku2,
|
||||||
|
length: size,
|
||||||
|
size,
|
||||||
|
align,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function freeString2(alloc) {
|
||||||
|
if (alloc.ptr == 0) return;
|
||||||
|
w2.haku2_free(alloc.ptr, alloc.size, alloc.align);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Panic extends Error {
|
class Panic extends Error {
|
||||||
|
@ -70,20 +138,34 @@ class Panic extends Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
panicImpl = (length, pMessage) => {
|
panicImpl = (length, pMessage) => {
|
||||||
throw new Panic(readString(length, pMessage));
|
throw new Panic(readString(memory, length, pMessage));
|
||||||
};
|
};
|
||||||
|
|
||||||
logImpl = (level, length, pMessage) => {
|
logImpl = (level, length, pMessage) => {
|
||||||
console[level](readString(length, pMessage));
|
console[level]("h1:", readString(memory, length, pMessage));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
log2Impl = (level, pScope, scopeLen, pMsg, len) => {
|
||||||
|
console[level](
|
||||||
|
"h2:",
|
||||||
|
`${readString(memory2, scopeLen, pScope)}: ${readString(memory2, len, pMsg)}`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
canvasBeginImpl = w.haku_pixmap_begin;
|
||||||
|
canvasLineImpl = w.haku_pixmap_line;
|
||||||
|
canvasRectangleImpl = w.haku_pixmap_rectangle;
|
||||||
|
canvasCircleImpl = w.haku_pixmap_circle;
|
||||||
|
canvasFillImpl = w.haku_pixmap_fill;
|
||||||
|
canvasStrokeImpl = w.haku_pixmap_stroke;
|
||||||
|
|
||||||
w.haku_init_logging();
|
w.haku_init_logging();
|
||||||
|
|
||||||
export class Pixmap {
|
export class Pixmap {
|
||||||
#pPixmap = 0;
|
#pPixmap = 0;
|
||||||
|
|
||||||
constructor(width, height) {
|
constructor(width, height) {
|
||||||
this.#pPixmap = w.haku_pixmap_new(width, height);
|
this.#pPixmap = allocCheck(w.haku_pixmap_new(width, height));
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
}
|
}
|
||||||
|
@ -113,7 +195,6 @@ export class Pixmap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: This must be kept in sync with ContKind on the haku-wasm side.
|
|
||||||
export const ContKind = {
|
export const ContKind = {
|
||||||
Scribble: 0,
|
Scribble: 0,
|
||||||
Dotter: 1,
|
Dotter: 1,
|
||||||
|
@ -121,53 +202,65 @@ export const ContKind = {
|
||||||
|
|
||||||
export class Haku {
|
export class Haku {
|
||||||
#pInstance = 0;
|
#pInstance = 0;
|
||||||
#pBrush = 0;
|
|
||||||
#brushCode = null;
|
#pLimits2 = 0;
|
||||||
|
#pScratch2 = 0;
|
||||||
|
#pVm2 = 0;
|
||||||
|
|
||||||
|
#bytecode2 = null;
|
||||||
|
#localCount = 0;
|
||||||
|
|
||||||
|
#fuel = 0;
|
||||||
|
#renderMaxDepth = 0;
|
||||||
|
|
||||||
constructor(limits) {
|
constructor(limits) {
|
||||||
console.groupCollapsed("construct Haku");
|
console.groupCollapsed("construct Haku");
|
||||||
|
{
|
||||||
let pLimits = w.haku_limits_new();
|
let pLimits = allocCheck(w.haku_limits_new());
|
||||||
for (let name of Object.keys(limits)) {
|
for (let name of Object.keys(limits)) {
|
||||||
w[`haku_limits_set_${name}`](pLimits, limits[name]);
|
w[`haku_limits_set_${name}`](pLimits, limits[name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#pInstance = w.haku_instance_new(pLimits);
|
this.#pInstance = allocCheck(w.haku_instance_new(pLimits));
|
||||||
this.#pBrush = w.haku_brush_new();
|
|
||||||
|
|
||||||
w.haku_limits_destroy(pLimits);
|
w.haku_limits_destroy(pLimits);
|
||||||
|
}
|
||||||
console.groupEnd();
|
console.groupEnd();
|
||||||
|
|
||||||
|
// NOTE(v2): VM is not constructed until there is a brush ready
|
||||||
|
this.#pLimits2 = allocCheck(w2.haku2_limits_new());
|
||||||
|
w2.haku2_limits_set_stack_capacity(this.#pLimits2, limits.stack_capacity);
|
||||||
|
w2.haku2_limits_set_call_stack_capacity(this.#pLimits2, limits.call_stack_capacity);
|
||||||
|
this.#fuel = limits.fuel;
|
||||||
|
this.#renderMaxDepth = limits.render_max_depth;
|
||||||
|
|
||||||
|
this.#pScratch2 = allocCheck(w2.haku2_scratch_new(limits.memory));
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
w.haku_brush_destroy(this.#pBrush);
|
|
||||||
w.haku_instance_destroy(this.#pInstance);
|
w.haku_instance_destroy(this.#pInstance);
|
||||||
|
|
||||||
|
w2.haku2_scratch_destroy(this.#pScratch2);
|
||||||
|
w2.haku2_vm_destroy(this.#pVm2);
|
||||||
|
w2.haku2_limits_destroy(this.#pLimits2);
|
||||||
|
w2.haku_dealloc(this.#bytecode2.ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
setBrush(code) {
|
setBrush(code) {
|
||||||
w.haku_reset(this.#pInstance);
|
let brushCode = allocString(code);
|
||||||
// NOTE: Brush is invalid at this point, because we reset removes all defs and registered chunks.
|
let statusCode = w.haku_compile_brush2(this.#pInstance, brushCode.length, brushCode.ptr);
|
||||||
|
freeString(brushCode);
|
||||||
if (this.#brushCode != null) freeString(this.#brushCode);
|
|
||||||
this.#brushCode = allocString(code);
|
|
||||||
|
|
||||||
let statusCode = w.haku_compile_brush(
|
|
||||||
this.#pInstance,
|
|
||||||
this.#pBrush,
|
|
||||||
this.#brushCode.length,
|
|
||||||
this.#brushCode.ptr,
|
|
||||||
);
|
|
||||||
if (!w.haku_is_ok(statusCode)) {
|
if (!w.haku_is_ok(statusCode)) {
|
||||||
if (w.haku_is_diagnostics_emitted(statusCode)) {
|
if (w.haku_is_diagnostics_emitted(statusCode)) {
|
||||||
let diagnostics = [];
|
let diagnostics = [];
|
||||||
for (let i = 0; i < w.haku_num_diagnostics(this.#pBrush); ++i) {
|
for (let i = 0; i < w.haku_num_diagnostics2(this.#pInstance); ++i) {
|
||||||
diagnostics.push({
|
diagnostics.push({
|
||||||
start: w.haku_diagnostic_start(this.#pBrush, i),
|
start: w.haku_diagnostic_start2(this.#pInstance, i),
|
||||||
end: w.haku_diagnostic_end(this.#pBrush, i),
|
end: w.haku_diagnostic_end2(this.#pInstance, i),
|
||||||
message: readString(
|
message: readString(
|
||||||
w.haku_diagnostic_message_len(this.#pBrush, i),
|
memory,
|
||||||
w.haku_diagnostic_message(this.#pBrush, i),
|
w.haku_diagnostic_message_len2(this.#pInstance, i),
|
||||||
|
w.haku_diagnostic_message2(this.#pInstance, i),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -185,51 +278,106 @@ export class Haku {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.#pVm2 != 0) w2.haku2_vm_destroy(this.#pVm2);
|
||||||
|
if (this.#bytecode2 != null) freeString2(this.#bytecode2);
|
||||||
|
|
||||||
|
let pDefsString = dup1to2(
|
||||||
|
w.haku_defs2(this.#pInstance),
|
||||||
|
w.haku_defs_len2(this.#pInstance),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
let pTagsString = dup1to2(
|
||||||
|
w.haku_tags2(this.#pInstance),
|
||||||
|
w.haku_tags_len2(this.#pInstance),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
let defs = allocCheck(
|
||||||
|
w2.haku2_defs_parse(
|
||||||
|
pDefsString,
|
||||||
|
w.haku_defs_len2(this.#pInstance),
|
||||||
|
pTagsString,
|
||||||
|
w.haku_tags_len2(this.#pInstance),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
w2.haku2_scratch_reset(this.#pScratch2);
|
||||||
|
this.#pVm2 = allocCheck(w2.haku2_vm_new(this.#pScratch2, defs, this.#pLimits2));
|
||||||
|
|
||||||
|
this.#bytecode2 = dup1to2(
|
||||||
|
w.haku_bytecode2(this.#pInstance),
|
||||||
|
w.haku_bytecode_len2(this.#pInstance),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
this.#localCount = w.haku_local_count2(this.#pInstance);
|
||||||
|
|
||||||
|
w2.haku2_defs_destroy(defs);
|
||||||
|
freeString2(pDefsString);
|
||||||
|
freeString2(pTagsString);
|
||||||
|
|
||||||
return { status: "ok" };
|
return { status: "ok" };
|
||||||
}
|
}
|
||||||
|
|
||||||
#statusCodeToResultObject(statusCode) {
|
#exceptionMessage() {
|
||||||
if (!w.haku_is_ok(statusCode)) {
|
let len = w2.haku2_vm_exception_len(this.#pVm2);
|
||||||
if (w.haku_is_exception(statusCode)) {
|
let pExn = allocCheck(w2.haku2_alloc(len, 1));
|
||||||
|
w2.haku2_vm_exception_render(this.#pVm2, pExn);
|
||||||
|
let exn = readString(memory2, len, pExn);
|
||||||
|
w2.haku2_free(pExn, len, 1);
|
||||||
|
return exn;
|
||||||
|
}
|
||||||
|
|
||||||
|
#exceptionResult() {
|
||||||
return {
|
return {
|
||||||
status: "error",
|
status: "error",
|
||||||
errorKind: "exception",
|
errorKind: "exception",
|
||||||
description: readCString(w.haku_status_string(statusCode)),
|
description: "Runtime error",
|
||||||
message: readString(
|
message: this.#exceptionMessage(),
|
||||||
w.haku_exception_message_len(this.#pInstance),
|
|
||||||
w.haku_exception_message(this.#pInstance),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
status: "error",
|
|
||||||
errorKind: "plain",
|
|
||||||
message: readCString(w.haku_status_string(statusCode)),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beginBrush() {
|
||||||
|
if (this.#pVm2 == 0) {
|
||||||
|
console.warn("VM instance is not available for drawing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
w2.haku2_vm_reset(this.#pVm2, this.#fuel);
|
||||||
|
let ok = w2.haku2_vm_run_main(
|
||||||
|
this.#pVm2,
|
||||||
|
this.#pScratch2,
|
||||||
|
this.#bytecode2.ptr,
|
||||||
|
this.#bytecode2.size,
|
||||||
|
this.#localCount,
|
||||||
|
);
|
||||||
|
if (!ok) {
|
||||||
|
return this.#exceptionResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
return { status: "ok" };
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedContKind() {
|
||||||
|
if (w2.haku2_vm_is_dotter(this.#pVm2)) return ContKind.Dotter;
|
||||||
|
else return ContKind.Scribble;
|
||||||
|
}
|
||||||
|
|
||||||
|
contScribble(pixmap, translationX, translationY) {
|
||||||
|
w.haku_pixmap_set_translation(pixmap.ptr, translationX, translationY);
|
||||||
|
let ok = w2.haku2_render(this.#pVm2, pixmap.ptr, this.#renderMaxDepth);
|
||||||
|
if (!ok) {
|
||||||
|
return this.#exceptionResult();
|
||||||
} else {
|
} else {
|
||||||
return { status: "ok" };
|
return { status: "ok" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
beginBrush() {
|
|
||||||
return this.#statusCodeToResultObject(w.haku_begin_brush(this.#pInstance, this.#pBrush));
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedContKind() {
|
|
||||||
return w.haku_cont_kind(this.#pInstance);
|
|
||||||
}
|
|
||||||
|
|
||||||
contScribble(pixmap, translationX, translationY) {
|
|
||||||
return this.#statusCodeToResultObject(
|
|
||||||
w.haku_cont_scribble(this.#pInstance, pixmap.ptr, translationX, translationY),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
contDotter({ fromX, fromY, toX, toY, num }) {
|
contDotter({ fromX, fromY, toX, toY, num }) {
|
||||||
return this.#statusCodeToResultObject(
|
let ok = w2.haku2_vm_run_dotter(this.#pVm2, this.#pScratch2, fromX, fromY, toX, toY, num);
|
||||||
w.haku_cont_dotter(this.#pInstance, fromX, fromY, toX, toY, num),
|
if (!ok) {
|
||||||
);
|
return this.#exceptionResult();
|
||||||
|
} else {
|
||||||
|
return { status: "ok" };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async evalBrush(options) {
|
async evalBrush(options) {
|
||||||
|
@ -239,7 +387,7 @@ export class Haku {
|
||||||
result = this.beginBrush();
|
result = this.beginBrush();
|
||||||
if (result.status != "ok") return result;
|
if (result.status != "ok") return result;
|
||||||
|
|
||||||
while (this.expectedContKind() != ContKind.Invalid) {
|
while (true) {
|
||||||
switch (this.expectedContKind()) {
|
switch (this.expectedContKind()) {
|
||||||
case ContKind.Scribble:
|
case ContKind.Scribble:
|
||||||
result = await runScribble((pixmap, translationX, translationY) => {
|
result = await runScribble((pixmap, translationX, translationY) => {
|
||||||
|
@ -254,27 +402,21 @@ export class Haku {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { status: "ok" };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get astSize() {
|
get astSize() {
|
||||||
if (this.#pBrush != 0) {
|
return 0; // TODO
|
||||||
return w.haku_stat_ast_size(this.#pBrush);
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get numRefs() {
|
get numRefs() {
|
||||||
return w.haku_stat_num_refs(this.#pInstance);
|
return 0; // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
get remainingFuel() {
|
get remainingFuel() {
|
||||||
return w.haku_stat_remaining_fuel(this.#pInstance);
|
return 0; // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
get remainingMemory() {
|
get remainingMemory() {
|
||||||
return w.haku_stat_remaining_memory(this.#pInstance);
|
return 0; // TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const HAKU_WASM_PATH = "{{{ static 'wasm/haku.wasm' }}}";
|
const HAKU_WASM_PATH = "{{{ static 'wasm/haku.wasm' }}}";
|
||||||
|
const HAKU2_WASM_PATH = "{{{ static 'wasm/haku2.wasm' }}}";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="module" async>
|
<script type="module" async>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue