A reactive, async dataflow engine in Rust — wire blocks together, run programs natively or in the browser via WebAssembly, watch values propagate as inputs change.
wasm32 build. The bundled web editor and a server-side controller speak to identical block semantics.°F, °C, K, Pa, kPa, s, min, h, …) and convert internally — courtesy of libhaystack. Blocks like Reset, Deadband, Clamp, EMA, and TrimRespond propagate units to their outputs so downstream consumers see the right quantity.#[block] attribute macro, or in JavaScript/TypeScript with defineBlock(...) + Zod schemas when running in a browser.Future; the scheduler drives them on Tokio (or wasm-bindgen-futures in a browser) and only resumes blocks whose inputs have actually changed.| Category | Blocks |
|---|---|
| Control | Pid, Reset, Deadband, Clamp, Sequencer, LeadLag, TrimRespond, Economizer, PriorityArray |
| Timers | OnDelay, OffDelay, OneShot, RateLimit, Runtime, CycleCount |
| Time | Now, Schedule, Calendar, Sun |
| Logic | And, Or, Not, Xor, Equal, NotEqual, GreaterThan, GreaterThanEq, LessThan, LessThanEq, FlipFlop, Latch, Trigger |
| Math | Add, Sub, Mul, Div, Modulus, Neg, Abs, Pow, Sqrt, Exp, Log10, LogN, Sin/Cos/Tan (+ inverses), Min, Max, Average, Median, Even, Odd |
| Misc | Ema, MovingAverage, Derivative, Integrator, ChangeOfValue, SampleHold, Random, SineWave, HasValue, ParseBool, ParseNumber |
| Bitwise | BitwiseAnd, BitwiseOr, BitwiseXor, BitwiseNot |
| Psychrometrics | Enthalpy, Dewpoint, WetBulb |
| Collections / Strings | Dict, List, Get, Keys, Values, Len, Concat, Replace |
A live SvelteKit editor is hosted at https://rracariu.github.io/logic-mesh/. Drag blocks, wire pins, watch the engine react.
It bundles a UI block set (Slider, Gauge, Bar, Display, Led, Chart, MultiChart, Button, Checkbox, ComboBox, Table, Input, Label) and five worked example programs you can switch between:
Sequencer → LeadLag → fan LEDs with on/off delays and rotation.Enthalpy of OA vs RA → LessThan → free-cooling LED, with both enthalpies on a MultiChart.OnDelay warmup + OffDelay cool-down lockout.Sun (sunrise/sunset) + Schedule + boolean composition driving a streetlight.[dependencies]
logic-mesh = "1.0"
Wire two sine waves into an adder and run them:
use logic_mesh::{
base::{block::Block, block::connect::connect_output},
blocks::{math::Add, misc::SineWave},
SingleThreadedEngine,
};
#[tokio::main]
async fn main() {
let mut add1 = Add::new();
let mut sine1 = SineWave::new();
sine1.amplitude.val = Some(3.into());
sine1.freq.val = Some(200.into());
connect_output(&mut sine1.out, add1.inputs_mut()[0]).expect("connected");
let mut sine2 = SineWave::new();
sine2.amplitude.val = Some(7.into());
sine2.freq.val = Some(400.into());
sine2.connect_output("out", add1.inputs_mut()[1]).expect("connected");
let mut engine = SingleThreadedEngine::new();
engine.schedule(add1);
engine.schedule(sine1);
engine.schedule(sine2);
engine.run().await;
}
A multi-threaded engine is available behind the multi-threaded Cargo feature.
npm install logic-mesh
The npm package wraps the WASM build. Define a UI block in TypeScript:
import { defineBlock, initEngine } from 'logic-mesh';
import { z } from 'zod';
const Gauge = defineBlock({
desc: { name: 'Gauge', dis: 'Gauge', lib: 'ui', ver: '0.0.1', category: 'UI', doc: 'Round gauge' },
inputs: [['in', z.number()]] as const,
outputs: [['out', z.number()]] as const,
execute: async ([input]) => [input],
});
const engine = initEngine();
Gauge.register(engine);
// then wire blocks via engine.engineCommand() and engine.run()
async fn execute(&mut self). The #[block] attribute macro generates the boilerplate (description, registration, default impl).read_inputs_until_ready (event-driven) or wait_on_inputs(timeout) (event + periodic throttle). The engine only resumes blocks whose data has actually changed.Number, Bool, Str, Dict, List, Null) are validated at link time; mismatched values fault the receiving block instead of silently corrupting state.features = ["multi-threaded"]) engines on native; WASM uses single-threaded with the browser event loop.build.rs walks src/blocks/<category>/ and assembles the static block registry, so adding a new block is one file plus a mod.rs re-export.src/
base/ core traits, engine, link/pin model
blocks/ block implementations, organized by category
tokio_impl/ native (Tokio) reader/output/engine impls
wasm/ wasm-bindgen entry points + JS-facing types
block_macro/ #[block] proc-macro
web/
packages/logic-mesh/ TypeScript wrapper around the WASM build
app/ SvelteKit web editor (the demo at the link above)