feat: make node definitions type safe with zod

This commit is contained in:
2024-04-22 00:33:04 +02:00
parent 4c7c4cac2c
commit ad197db873
28 changed files with 221 additions and 147 deletions

View File

@ -5,14 +5,14 @@ use std::env;
use std::fs;
use std::path::Path;
use syn::{parse_macro_input, LitStr};
use types::NodeType;
use types::NodeDefinition;
#[proc_macro]
pub fn define_node(input: TokenStream) -> TokenStream {
pub fn node_definition(input: TokenStream) -> TokenStream {
let input_string = parse_macro_input!(input as LitStr).value();
// Validate JSON format
let json: NodeType = match serde_json::from_str(&input_string) {
let json: NodeDefinition = match serde_json::from_str(&input_string) {
Ok(json) => json,
Err(e) => panic!("Invalid JSON input: {}", e),
};
@ -49,7 +49,7 @@ pub fn include_definition_file(input: TokenStream) -> TokenStream {
});
// Optionally, validate that the content is valid JSON
let _: NodeType = serde_json::from_str(&json_content)
let _: NodeDefinition = serde_json::from_str(&json_content)
.unwrap_or_else(|err| panic!("JSON file contains invalid JSON: {}", err));
// Generate the function that returns the JSON string

View File

@ -1,4 +1,5 @@
import type { NodeInput } from "./inputs";
import { z } from "zod";
import { NodeInputSchema } from "./inputs";
export type { NodeInput } from "./inputs";
export type Node = {
@ -12,7 +13,7 @@ export type Node = {
parents?: Node[],
children?: Node[],
inputNodes?: Record<string, Node>
type?: NodeType;
type?: NodeDefinition;
downX?: number;
downY?: number;
x?: number;
@ -28,15 +29,19 @@ export type Node = {
position: [x: number, y: number]
}
export type NodeType = {
id: string;
inputs?: Record<string, NodeInput>
outputs?: string[];
meta?: {
title?: string;
},
execute?: (args: Int32Array) => Int32Array;
}
export const NodeDefinitionSchema = z.object({
id: z.string(),
inputs: z.record(NodeInputSchema).optional(),
outputs: z.array(z.string()).optional(),
meta: z.object({
description: z.string().optional(),
title: z.string().optional(),
}).optional(),
});
export type NodeDefinition = z.infer<typeof NodeDefinitionSchema> & {
execute(input: Int32Array): Int32Array;
};
export type Socket = {
node: Node;
@ -63,12 +68,12 @@ export interface NodeRegistry {
* @param id - The id of the node to get
* @returns The node with the given id, or undefined if no such node exists
*/
getNode: (id: string) => NodeType | undefined;
getNode: (id: string) => NodeDefinition | undefined;
/**
* Get all nodes
* @returns An array of all nodes
*/
getAllNodes: () => NodeType[];
getAllNodes: () => NodeDefinition[];
}
export interface RuntimeExecutor {

View File

@ -1,65 +1,78 @@
type NodeInputFloat = {
type: "float";
element?: "slider";
value?: number;
min?: number;
max?: number;
step?: number;
}
import { z } from "zod";
type NodeInputInteger = {
type: "integer";
element?: "slider";
value?: number;
min?: number;
max?: number;
}
type NodeInputBoolean = {
type: "boolean";
value?: boolean;
}
type NodeInputSelect = {
type: "select";
options: string[];
value?: number;
}
type NodeInputSeed = {
type: "seed"
value?: number;
}
type NodeInputVec3 = {
type: "vec3";
value?: number[];
}
type NodeInputModel = {
type: "model";
}
type NodeInputPlant = {
type: "plant"
}
type InputTypes = (NodeInputSeed | NodeInputBoolean | NodeInputFloat | NodeInputInteger | NodeInputSelect | NodeInputSeed | NodeInputVec3 | NodeInputModel | NodeInputPlant);
type InputId = InputTypes["type"];
type DefaultOptions = {
internal?: boolean;
external?: boolean;
setting?: string;
label?: string | false;
}
export type NodeInput = InputTypes & {
type: InputId | InputId[];
} & DefaultOptions;
const DefaultOptionsSchema = z.object({
internal: z.boolean().optional(),
external: z.boolean().optional(),
setting: z.string().optional(),
label: z.string().optional(),
description: z.string().optional(),
accepts: z.array(z.string()).optional(),
});
export type NodeInputType<T extends Record<string, NodeInput>> = {
[K in keyof T]: T[K]["value"]
};
export const NodeInputFloatSchema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("float"),
element: z.literal("slider").optional(),
value: z.number().optional(),
min: z.number().optional(),
max: z.number().optional(),
step: z.number().optional(),
});
export const NodeInputIntegerSchema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("integer"),
element: z.literal("slider").optional(),
value: z.number().optional(),
min: z.number().optional(),
max: z.number().optional(),
});
export const NodeInputBooleanSchema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("boolean"),
vale: z.boolean().optional(),
});
export const NodeInputSelectSchema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("select"),
value: z.number().optional(),
});
export const NodeInputSeedSchema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("seed"),
value: z.number().optional(),
});
export const NodeInputVec3Schema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("vec3"),
value: z.array(z.number()).optional(),
});
export const NodeInputModelSchema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("model"),
});
export const NodeInputPlantSchema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("plant"),
});
export const NodeInputSchema = z.union([
NodeInputSeedSchema,
NodeInputBooleanSchema,
NodeInputFloatSchema,
NodeInputIntegerSchema,
NodeInputSelectSchema,
NodeInputSeedSchema,
NodeInputVec3Schema,
NodeInputModelSchema,
NodeInputPlantSchema
]);
export type NodeInput = z.infer<typeof InputSchema>;

View File

@ -90,7 +90,7 @@ pub struct DefaultOptions {
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum NodeTypeOrArray {
pub enum NodeDefinitionOrArray {
Single(InputTypes),
Multiple(Vec<String>),
}
@ -124,10 +124,10 @@ impl<'de> Deserialize<'de> for NodeInput {
}
#[derive(Deserialize, Debug, Serialize)]
pub struct NodeType {
pub struct NodeDefinition {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub inputs: Option<HashMap<String, NodeInput>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub outputs: Option<Vec<String>>,
}

View File

@ -1,4 +1,4 @@
use crate::{decode_float, log};
use crate::decode_float;
pub fn get_args(args: &[i32]) -> Vec<&[i32]> {
let mut idx: usize = 0;

View File

@ -1,4 +1,4 @@
import { NodeType } from "@nodes/types";
import { NodeDefinition } from "@nodes/types";
const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
const cachedTextEncoder = new TextEncoder();
@ -17,12 +17,9 @@ const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
};
});
export function createWasmWrapper() {
function createWrapper() {
let wasm: any;
let cachedUint8Memory0: Uint8Array | null = null;
let cachedInt32Memory0: Int32Array | null = null;
let cachedUint32Memory0: Uint32Array | null = null;
@ -120,7 +117,7 @@ export function createWasmWrapper() {
deferred1_0 = r0;
deferred1_1 = r1;
const string = getStringFromWasm0(r0, r1);
return JSON.parse(string) as Omit<NodeType, "id">;
return JSON.parse(string) as NodeDefinition;
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
@ -226,9 +223,12 @@ export function createWasmWrapper() {
wasm = instance.exports;
},
// Expose other methods that interact with the wasm instance
execute,
get_definition,
exports: {
// Expose other methods that interact with the wasm instance
execute,
get_definition,
},
__wbindgen_string_new,
__wbindgen_object_drop_ref,
@ -240,3 +240,11 @@ export function createWasmWrapper() {
};
}
export function createWasmWrapper(wasmBuffer: ArrayBuffer) {
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;
}