feat: trying to remove wasm-bindgen

This commit is contained in:
Max Richter
2026-01-19 01:29:12 +01:00
parent 987ece2a4b
commit be97387252
75 changed files with 513 additions and 1150 deletions

View File

@@ -11,7 +11,7 @@ repository = "https://github.com/jim-fx/nodes"
proc-macro = true
[dependencies]
syn = { version = "1.0", features = ["full"] }
syn = { version = "2.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
quote = "1.0"

View File

@@ -5,32 +5,7 @@ use quote::quote;
use std::env;
use std::fs;
use std::path::Path;
use syn::{parse_macro_input, LitStr};
#[proc_macro]
pub fn node_definition(input: TokenStream) -> TokenStream {
let input_string = parse_macro_input!(input as LitStr).value();
// Validate JSON format
let json: NodeDefinition = match serde_json::from_str(&input_string) {
Ok(json) => json,
Err(e) => panic!("Invalid JSON input: {}", e),
};
// Convert the validated JSON back to a pretty-printed string
let formatted_json = serde_json::to_string_pretty(&json).expect("Failed to serialize JSON");
// Generate the output function
let expanded = quote! {
#[wasm_bindgen]
pub fn get_definition() -> String {
String::from(#formatted_json)
}
};
// Convert the generated code back to a TokenStream
TokenStream::from(expanded)
}
use syn::parse_macro_input;
fn add_line_numbers(input: String) -> String {
return input
@@ -41,39 +16,120 @@ fn add_line_numbers(input: String) -> String {
.join("\n");
}
#[proc_macro]
pub fn include_definition_file(input: TokenStream) -> TokenStream {
let file_path = syn::parse_macro_input!(input as syn::LitStr).value();
#[proc_macro_attribute]
pub fn nodarium_execute(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(item as syn::ItemFn);
let _fn_name = &input_fn.sig.ident;
let _fn_vis = &input_fn.vis;
let fn_body = &input_fn.block;
// Retrieve the directory containing the Cargo.toml file
let project_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let full_path = Path::new(&project_dir).join(&file_path);
let first_arg_ident = if let Some(syn::FnArg::Typed(pat_type)) = input_fn.sig.inputs.first() {
if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
&pat_ident.ident
} else {
panic!("Expected a simple identifier for the first argument");
}
} else {
panic!("The execute function must have at least one argument (the input slice)");
};
// Read the JSON file content
let json_content = fs::read_to_string(full_path).unwrap_or_else(|err| {
panic!(
"Failed to read JSON file at '{}/{}': {}",
project_dir, file_path, err
)
});
// Optionally, validate that the content is valid JSON
let _: NodeDefinition = serde_json::from_str(&json_content).unwrap_or_else(|err| {
panic!(
"JSON file contains invalid JSON: \n{} \n{}",
err,
add_line_numbers(json_content.clone())
)
});
// Generate the function that returns the JSON string
// We create a wrapper that handles the C ABI and pointer math
let expanded = quote! {
#[wasm_bindgen]
pub fn get_definition() -> String {
String::from(#json_content)
extern "C" {
fn host_log_panic(ptr: *const u8, len: usize);
fn host_log(ptr: *const u8, len: usize);
}
fn setup_panic_hook() {
static SET_HOOK: std::sync::Once = std::sync::Once::new();
SET_HOOK.call_once(|| {
std::panic::set_hook(Box::new(|info| {
let msg = info.to_string();
unsafe { host_log_panic(msg.as_ptr(), msg.len()); }
}));
});
}
#[no_mangle]
pub extern "C" fn __alloc(len: usize) -> *mut i32 {
let mut buf = Vec::with_capacity(len);
let ptr = buf.as_mut_ptr();
std::mem::forget(buf);
ptr
}
#[no_mangle]
pub extern "C" fn __free(ptr: *mut i32, len: usize) {
unsafe {
let _ = Vec::from_raw_parts(ptr, 0, len);
}
}
static mut OUTPUT_BUFFER: Vec<i32> = Vec::new();
#[no_mangle]
pub extern "C" fn execute(ptr: *const i32, len: usize) -> *mut i32 {
setup_panic_hook();
// 1. Convert raw pointer to slice
let input = unsafe { core::slice::from_raw_parts(ptr, len) };
// 2. Call the logic (which we define below)
let result_data: Vec<i32> = internal_logic(input);
// 3. Use the static buffer for the result
let result_len = result_data.len();
unsafe {
OUTPUT_BUFFER.clear();
OUTPUT_BUFFER.reserve(result_len + 1);
OUTPUT_BUFFER.push(result_len as i32);
OUTPUT_BUFFER.extend(result_data);
OUTPUT_BUFFER.as_mut_ptr()
}
}
fn internal_logic(#first_arg_ident: &[i32]) -> Vec<i32> {
#fn_body
}
};
TokenStream::from(expanded)
}
#[proc_macro]
pub fn nodarium_definition_file(input: TokenStream) -> TokenStream {
let path_lit = syn::parse_macro_input!(input as syn::LitStr);
let file_path = path_lit.value();
let project_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let full_path = Path::new(&project_dir).join(&file_path);
let json_content = fs::read_to_string(&full_path).unwrap_or_else(|err| {
panic!("Failed to read JSON file at '{}/{}': {}", project_dir, file_path, err)
});
let _: NodeDefinition = serde_json::from_str(&json_content).unwrap_or_else(|err| {
panic!("JSON file contains invalid JSON: \n{} \n{}", err, add_line_numbers(json_content.clone()))
});
// We use the span from the input path literal
let bytes = syn::LitByteStr::new(json_content.as_bytes(), path_lit.span());
let len = json_content.len();
let expanded = quote! {
#[link_section = "nodarium_definition"]
static DEFINITION_DATA: [u8; #len] = *#bytes;
#[no_mangle]
pub extern "C" fn get_definition_ptr() -> *const u8 {
DEFINITION_DATA.as_ptr()
}
#[no_mangle]
pub extern "C" fn get_definition_len() -> usize {
DEFINITION_DATA.len()
}
};
// Convert the generated code back to a TokenStream
TokenStream::from(expanded)
}

View File

@@ -13,7 +13,6 @@ export class RemoteNodeRegistry implements NodeRegistry {
status: "loading" | "ready" | "error" = "loading";
private nodes: Map<string, NodeDefinition> = new Map();
constructor(
private url: string,
private cache?: AsyncCache<ArrayBuffer | string>,

View File

@@ -4,7 +4,6 @@ version = "0.1.0"
edition = "2021"
license = "MIT"
description = "Types for Nodarium"
website = "https://nodes.max-richter.dev"
repository = "https://github.com/jim-fx/nodes"
[dependencies]

View File

@@ -40,6 +40,30 @@
}
@theme {
--color-neutral-100: #E7E7E7;
--color-neutral-200: #CECECE;
--color-neutral-300: #7C7C7C;
--color-neutral-400: #2D2D2D;
--color-neutral-500: #171717;
--color-neutral-800: #111111;
--color-neutral-900: #060606;
--color-layer-0: var(--neutral-900);
--color-layer-1: var(--neutral-500);
--color-layer-2: var(--neutral-400);
--color-layer-3: var(--neutral-200);
--color-active: #ffffff;
--color-selected: #c65a19;
--color-outline: var(--neutral-400);
--color-connection: #333333;
--color-edge: var(--connection, var(--outline));
--color-text-color: var(--neutral-200);
}
html {
--neutral-100: #E7E7E7;
--neutral-200: #CECECE;

View File

@@ -6,14 +6,11 @@ description = "A collection of utilities for Nodarium"
license = "MIT"
repository = "https://github.com/jim-fx/nodes"
[features]
default = ["console_error_panic_hook"]
[lib]
crate-type = ["rlib"]
[dependencies]
wasm-bindgen = "0.2.92"
web-sys = { version = "0.3.69", features = ["console"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
console_error_panic_hook = { version = "0.1.7", optional = true }
glam = "0.27.0"
glam = "0.30.10"
noise = "0.9.0"

View File

@@ -109,10 +109,12 @@ export function decodeNestedArray(dense: number[] | Int32Array) {
}
export function splitNestedArray(input: Int32Array) {
let index = 0;
const length = input.length;
let res: Int32Array[] = [];
let nextBracketIndex = 0;
let argStartIndex = 0;
let depth = -1;

View File

@@ -61,7 +61,7 @@ pub fn create_geometry_data(vertex_amount: usize, face_amount: usize) -> Vec<i32
geo
}
pub fn wrap_geometry_data(geometry: &mut [i32]) -> GeometryData {
pub fn wrap_geometry_data(geometry: &mut [i32]) -> GeometryData<'_> {
// Basic validity checks
assert!(
geometry.len() > GEOMETRY_HEADER_SIZE,

View File

@@ -73,7 +73,7 @@ pub fn create_instance_data(
geo
}
pub fn wrap_instance_data(instances: &mut [i32]) -> InstanceData {
pub fn wrap_instance_data(instances: &mut [i32]) -> InstanceData<'_> {
assert!(
instances.len() > INSTANCE_HEADER_SIZE,
"Instance vector does not contain enough data for a header."

View File

@@ -130,7 +130,7 @@ pub fn create_path(point_amount: usize, depth: i32) -> Vec<i32> {
path
}
pub fn wrap_path(input: &[i32]) -> PathData {
pub fn wrap_path(input: &[i32]) -> PathData<'_> {
// Basic validity checks
assert!(
input.len() > PATH_HEADER_SIZE,

View File

@@ -6,12 +6,22 @@ pub use nodes::reset_call_count;
pub use tree::*;
pub mod geometry;
extern "C" {
#[cfg(target_arch = "wasm32")]
pub fn host_log(ptr: *const u8, len: usize);
}
#[cfg(debug_assertions)]
#[macro_export]
macro_rules! log {
($($arg:tt)*) => {{
use web_sys::console;
console::log_1(&format!($($arg)*).into());
($($t:tt)*) => {{
let msg = std::format!($($t)*);
#[cfg(target_arch = "wasm32")]
unsafe {
$crate::host_log(msg.as_ptr(), msg.len());
}
#[cfg(not(target_arch = "wasm32"))]
println!("{}", msg);
}}
}
@@ -23,13 +33,3 @@ macro_rules! log {
}};
}
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}

View File

@@ -1,256 +1,55 @@
//@ts-nocheck
import { NodeDefinition } from "@nodarium/types";
interface NodariumExports extends WebAssembly.Exports {
memory: WebAssembly.Memory;
execute: (ptr: number, len: number) => number;
__free: (ptr: number, len: number) => void;
__alloc: (len: number) => number;
}
const cachedTextDecoder = new TextDecoder("utf-8", {
ignoreBOM: true,
fatal: true,
});
const cachedTextEncoder = new TextEncoder();
export function createWasmWrapper(buffer: ArrayBuffer) {
let exports: NodariumExports;
const encodeString =
typeof cachedTextEncoder.encodeInto === "function"
? function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
const importObject = {
env: {
host_log_panic: (ptr: number, len: number) => {
if (!exports) return;
const view = new Uint8Array(exports.memory.buffer, ptr, len);
console.error("RUST PANIC:", new TextDecoder().decode(view));
},
host_log: (ptr: number, len: number) => {
if (!exports) return;
const view = new Uint8Array(exports.memory.buffer, ptr, len);
console.log("RUST:", new TextDecoder().decode(view));
}
}
: function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length,
};
};
};
function createWrapper() {
let wasm: any;
let cachedUint8Memory0: Uint8Array | null = null;
let cachedInt32Memory0: Int32Array | null = null;
let cachedUint32Memory0: Uint32Array | null = null;
const heap = new Array(128).fill(undefined);
heap.push(undefined, null, true, false);
let heap_next = heap.length;
function getUint8Memory0() {
if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachedUint8Memory0;
}
function getInt32Memory0() {
if (cachedInt32Memory0 === null || cachedInt32Memory0.byteLength === 0) {
cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
}
return cachedInt32Memory0;
}
function getUint32Memory0() {
if (cachedUint32Memory0 === null || cachedUint32Memory0.byteLength === 0) {
cachedUint32Memory0 = new Uint32Array(wasm.memory.buffer);
}
return cachedUint32Memory0;
}
function getStringFromWasm0(ptr: number, len: number) {
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
function getObject(idx: number) {
return heap[idx];
}
function addHeapObject(obj: any) {
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
heap_next = heap[idx];
heap[idx] = obj;
return idx;
}
let WASM_VECTOR_LEN = 0;
function passArray32ToWasm0(
arg: ArrayLike<number>,
malloc: (arg0: number, arg1: number) => number,
) {
const ptr = malloc(arg.length * 4, 4) >>> 0;
getUint32Memory0().set(arg, ptr / 4);
WASM_VECTOR_LEN = arg.length;
return ptr;
}
function getArrayI32FromWasm0(ptr: number, len: number) {
ptr = ptr >>> 0;
return getInt32Memory0().subarray(ptr / 4, ptr / 4 + len);
}
function dropObject(idx: number) {
if (idx < 132) return;
heap[idx] = heap_next;
heap_next = idx;
}
function takeObject(idx: number) {
const ret = getObject(idx);
dropObject(idx);
return ret;
}
function __wbindgen_string_new(arg0: number, arg1: number) {
const ret = getStringFromWasm0(arg0, arg1);
return addHeapObject(ret);
}
// Additional methods and their internal helpers can also be refactored in a similar manner.
function get_definition() {
let deferred1_0: number;
let deferred1_1: number;
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
wasm.get_definition(retptr);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
deferred1_0 = r0;
deferred1_1 = r1;
const rawDefinition = getStringFromWasm0(r0, r1);
return JSON.parse(rawDefinition) as NodeDefinition;
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
}
}
const module = new WebAssembly.Module(buffer);
const instance = new WebAssembly.Instance(module, importObject);
exports = instance.exports as NodariumExports;
function execute(args: Int32Array) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
const ptr0 = passArray32ToWasm0(args, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
wasm.execute(retptr, ptr0, len0);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
var v2 = getArrayI32FromWasm0(r0, r1).slice();
wasm.__wbindgen_free(r0, r1 * 4, 4);
return v2;
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
const inPtr = exports.__alloc(args.length);
new Int32Array(exports.memory.buffer).set(args, inPtr / 4);
const outPtr = exports.execute(inPtr, args.length);
const i32Result = new Int32Array(exports.memory.buffer);
const outLen = i32Result[outPtr / 4];
const out = i32Result.slice(outPtr / 4 + 1, outPtr / 4 + 1 + outLen);
exports.__free(inPtr, args.length);
return out;
}
function get_definition() {
const sections = WebAssembly.Module.customSections(module, "nodarium_definition");
if (sections.length > 0) {
const decoder = new TextDecoder();
const jsonString = decoder.decode(sections[0]);
return JSON.parse(jsonString);
}
}
function passStringToWasm0(
arg: string,
malloc: (arg0: any, arg1: number) => number,
realloc:
| ((arg0: number, arg1: any, arg2: number, arg3: number) => number)
| undefined,
) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length, 1) >>> 0;
getUint8Memory0()
.subarray(ptr, ptr + buf.length)
.set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len, 1) >>> 0;
const mem = getUint8Memory0();
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7f) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0;
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
offset += ret.written;
ptr = realloc(ptr, len, offset, 1) >>> 0;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
function __wbg_new_abda76e883ba8a5f() {
const ret = new Error();
return addHeapObject(ret);
}
function __wbg_stack_658279fe44541cf6(arg0, arg1) {
const ret = getObject(arg1).stack;
const ptr1 = passStringToWasm0(
ret,
wasm.__wbindgen_malloc,
wasm.__wbindgen_realloc,
);
const len1 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len1;
getInt32Memory0()[arg0 / 4 + 0] = ptr1;
}
function __wbg_error_f851667af71bcfc6(arg0, arg1) {
let deferred0_0;
let deferred0_1;
try {
deferred0_0 = arg0;
deferred0_1 = arg1;
console.error(getStringFromWasm0(arg0, arg1));
} finally {
wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
}
}
function __wbindgen_object_drop_ref(arg0) {
takeObject(arg0);
}
function __wbg_log_5bb5f88f245d7762(arg0) {
console.log(getObject(arg0));
}
function __wbindgen_throw(arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
}
return {
setInstance(instance: WebAssembly.Instance) {
wasm = instance.exports;
},
exports: {
// Expose other methods that interact with the wasm instance
execute,
get_definition,
},
__wbindgen_string_new,
__wbindgen_object_drop_ref,
__wbg_new_abda76e883ba8a5f,
__wbg_error_f851667af71bcfc6,
__wbg_stack_658279fe44541cf6,
__wbg_log_5bb5f88f245d7762,
__wbindgen_throw,
};
}
export function createWasmWrapper(wasmBuffer: ArrayBuffer | Uint8Array) {
const wrapper = createWrapper();
const module = new WebAssembly.Module(wasmBuffer);
const instance = new WebAssembly.Instance(module, {
["./index_bg.js"]: wrapper,
});
wrapper.setInstance(instance);
return wrapper.exports;
return { execute, get_definition };
}