Compare commits

...

17 commits

Author SHA1 Message Date
Martin Kavík 44b035517a Decoders Demo in README 2024-07-08 16:39:50 +02:00
Martin Kavík 8d16b8159e fix Python decoder 2024-07-08 16:00:59 +02:00
Martin Kavík c5df5eb6d3 fix JS testing decoder 2024-07-08 14:30:49 +02:00
Martin Kavík f873ed6587 request_timeline_redraw() 2024-07-08 03:12:47 +02:00
Martin Kavík 24e0e30d3e tauri runtime Mutex/RwLock, type_hint 2024-07-08 02:08:20 +02:00
Martin Kavík df353bd87d add_decoders finished 2024-07-07 23:53:37 +02:00
Martin Kavík 03e7491e12 DECODERS 2024-07-07 22:26:44 +02:00
Martin Kavík 6c6c9a132a remove_all_decoders 2024-07-07 14:53:30 +02:00
Martin Kavík 31613dbac9 fmt 2024-07-05 03:30:17 +02:00
Martin Kavík 7435f6c136 python decoder workaround 2024-07-05 02:54:30 +02:00
Martin Kavík 5559d24a9f wasi, Rust + JS decoder works 2024-07-04 16:16:50 +02:00
Martin Kavík aba76a4b00 wasmtime_component 2024-07-04 15:02:20 +02:00
Martin Kavík 6b52067a95 component_manager, strict_eval.js 2024-06-25 22:40:46 +02:00
Martin Kavík d97f98485b add_decoders 2024-06-25 16:21:09 +02:00
Martin Kavík 8f351bd639 python_decoder 2024-06-25 14:51:04 +02:00
Martin Kavík 139eb3d31a javascript_decoder 2024-06-25 02:11:02 +02:00
Martin Kavík 10b36c0621 rust_decoder 2024-06-25 01:12:42 +02:00
48 changed files with 2472 additions and 72 deletions

1073
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@ members = [
"frontend",
"backend",
"shared",
"src-tauri",
"src-tauri", "test_files/components/rust_decoder",
]
resolver = "2"

View file

@ -3,34 +3,44 @@
---
<p align="center">Browser (Firefox)</p>
<p align="center">
<img width="800" src="docs/screenshot_firefox.png" alt="Fastwave - Browser (Firefox)" />
Browser (Firefox)
</p>
<p align="center">Desktop, miller columns and tree</p>
<p align="center">
<img width="800" src="docs/video_desktop.gif" alt="Fastwave - Desktop, miller columns and tree" />
Desktop, miller columns and tree
</p>
<p align="center">Zoom, pan and basic number formats</p>
<p align="center">
<img width="800" src="docs/video_zoom_formatting_simple.gif" alt="Fastwave - Zoom, pan and basic number formats" />
Zoom, pan and basic number formats
</p>
<p align="center">Zoom and all formats</p>
<p align="center">
<img width="800" src="docs/video_zoom_formatting.gif" alt="Fastwave - Zoom and all formats" />
Zoom and all formats
</p>
<p align="center">Javascript commands</p>
<p align="center">
<img width="800" src="docs/video_javascript_commands.gif" alt="Fastwave - Javascript commands" />
Javascript commands
</p>
<p align="center">Load and save selected variables</p>
<p align="center">
<img width="800" src="docs/video_load_save_selected_vars.gif" alt="Fastwave - Load and save selected variables" />
Load and save selected variables
</p>
<p align="center">Decoders Demo</p>
<p align="center">
<img width="800" src="docs/video_decoders.gif" alt="Fastwave - Decoders demo" />
</p>
<p align="center">Decoder Interface</p>
<p align="center">
<img width="500" src="docs/screenshot_world_wit.png" alt="Fastwave - Decoder Interface" />
</p>
---

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
docs/video_decoders.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 KiB

View file

@ -241,8 +241,10 @@ impl HeaderPanel {
if raw_event.shift_key() {
// @TODO move `prevent_default` to MZ API (next to the `pass_to_parent` method?)
raw_event.prevent_default();
let result = script_bridge::strict_eval(&script.lock_ref());
Task::start(clone!((script, command_result) async move {
let result = script_bridge::strict_eval(&script.lock_ref()).await;
command_result.set(Some(result));
}));
}
}
})

View file

@ -8,7 +8,7 @@ mod controls_panel;
use controls_panel::ControlsPanel;
mod waveform_panel;
use waveform_panel::WaveformPanel;
use waveform_panel::{PixiController, WaveformPanel};
mod header_panel;
use header_panel::HeaderPanel;
@ -27,6 +27,7 @@ struct Store {
selected_var_refs: MutableVec<wellen::VarRef>,
hierarchy: Mutable<Option<Arc<wellen::Hierarchy>>>,
loaded_filename: Mutable<Option<Filename>>,
canvas_controller: Mutable<Mutable<Option<SendWrapper<PixiController>>>>,
}
static STORE: Lazy<Store> = lazy::default();
@ -45,6 +46,7 @@ fn root() -> impl Element {
let selected_var_refs = STORE.selected_var_refs.clone();
let layout: Mutable<Layout> = <_>::default();
let loaded_filename = STORE.loaded_filename.clone();
let canvas_controller = STORE.canvas_controller.clone();
Column::new()
.s(Height::fill())
.s(Scrollbars::y_and_clip_x())
@ -69,13 +71,15 @@ fn root() -> impl Element {
let hierarchy = hierarchy.clone();
let selected_var_refs = selected_var_refs.clone();
let loaded_filename = loaded_filename.clone();
let canvas_controller = canvas_controller.clone();
map_ref!{
let layout = layout.signal(),
let hierarchy_is_some = hierarchy.signal_ref(Option::is_some) => {
(*hierarchy_is_some && matches!(layout, Layout::Tree)).then(clone!((hierarchy, selected_var_refs, loaded_filename) move || WaveformPanel::new(
(*hierarchy_is_some && matches!(layout, Layout::Tree)).then(clone!((hierarchy, selected_var_refs, loaded_filename, canvas_controller) move || WaveformPanel::new(
hierarchy.clone(),
selected_var_refs.clone(),
loaded_filename.clone(),
canvas_controller.clone(),
)))
}
}
@ -85,10 +89,11 @@ fn root() -> impl Element {
map_ref!{
let layout = layout.signal(),
let hierarchy_is_some = hierarchy.signal_ref(Option::is_some) => {
(*hierarchy_is_some && matches!(layout, Layout::Columns)).then(clone!((hierarchy, selected_var_refs, loaded_filename) move || WaveformPanel::new(
(*hierarchy_is_some && matches!(layout, Layout::Columns)).then(clone!((hierarchy, selected_var_refs, loaded_filename, canvas_controller) move || WaveformPanel::new(
hierarchy.clone(),
selected_var_refs.clone(),
loaded_filename.clone(),
canvas_controller.clone(),
)))
}
}

View file

@ -3,6 +3,8 @@
// NOTE: `FASTWAVE_PLATFORM` is set in `Makefile.toml` tasks and then in `build.rs`
use crate::STORE;
#[cfg(FASTWAVE_PLATFORM = "TAURI")]
mod tauri;
#[cfg(FASTWAVE_PLATFORM = "TAURI")]
@ -15,6 +17,9 @@ use browser as platform;
type Filename = String;
type JavascriptCode = String;
type AddedDecodersCount = usize;
type RemovedDecodersCount = usize;
type DecoderPath = String;
pub async fn show_window() {
platform::show_window().await
@ -58,3 +63,25 @@ pub async fn load_signal_and_get_timeline(
pub async fn unload_signal(signal_ref: wellen::SignalRef) {
platform::unload_signal(signal_ref).await
}
pub async fn add_decoders(decoder_paths: Vec<DecoderPath>) -> AddedDecodersCount {
let count = platform::add_decoders(decoder_paths).await;
if count > 0 {
redraw_all_timeline_rows().await;
}
count
}
pub async fn remove_all_decoders() -> RemovedDecodersCount {
let count = platform::remove_all_decoders().await;
if count > 0 {
redraw_all_timeline_rows().await;
}
count
}
async fn redraw_all_timeline_rows() {
if let Some(controller) = STORE.canvas_controller.get_cloned().get_cloned() {
controller.redraw_all_rows().await
}
}

View file

@ -1,7 +1,7 @@
use shared::wellen_helpers;
use std::sync::Mutex;
use wellen::simple::Waveform;
use zoon::*;
use zoon::{eprintln, *};
#[derive(Default)]
struct BrowserPlatformStore {
@ -111,7 +111,9 @@ pub(super) async fn load_signal_and_get_timeline(
timeline_viewport_x,
block_height,
var_format,
);
|value| Box::pin(async { value }),
)
.await;
timeline
}
@ -120,3 +122,17 @@ pub(super) async fn unload_signal(signal_ref: wellen::SignalRef) {
let waveform = waveform_lock.as_mut().unwrap_throw();
waveform.unload_signals(&[signal_ref]);
}
pub(super) async fn add_decoders(
_decoder_paths: Vec<super::DecoderPath>,
) -> super::AddedDecodersCount {
// @TODO error message for user
eprintln!("Adding decoders is not supported in the browser.");
0
}
pub(super) async fn remove_all_decoders() -> super::RemovedDecodersCount {
// @TODO error message for user
eprintln!("Removing decoders is not supported in the browser.");
0
}

View file

@ -56,6 +56,18 @@ pub(super) async fn unload_signal(signal_ref: wellen::SignalRef) {
.unwrap_throw()
}
pub(super) async fn add_decoders(
decoder_paths: Vec<super::DecoderPath>,
) -> super::AddedDecodersCount {
serde_wasm_bindgen::from_value(tauri_glue::add_decoders(decoder_paths).await.unwrap_throw())
.unwrap_throw()
}
pub(super) async fn remove_all_decoders() -> super::RemovedDecodersCount {
serde_wasm_bindgen::from_value(tauri_glue::remove_all_decoders().await.unwrap_throw())
.unwrap_throw()
}
mod tauri_glue {
use zoon::*;
@ -86,5 +98,13 @@ mod tauri_glue {
#[wasm_bindgen(catch)]
pub async fn unload_signal(signal_ref_index: usize) -> Result<(), JsValue>;
#[wasm_bindgen(catch)]
pub async fn add_decoders(
decoder_paths: Vec<super::super::DecoderPath>,
) -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
pub async fn remove_all_decoders() -> Result<JsValue, JsValue>;
}
}

View file

@ -1,15 +1,16 @@
use crate::STORE;
use crate::{platform, STORE};
use wellen::GetItem;
use zoon::*;
type FullVarName = String;
type AddedDecodersCount = usize;
type RemovedDecodersCount = usize;
type DecoderPath = String;
#[wasm_bindgen(
inline_js = r#"export function strict_eval(code) { "use strict"; return eval?.(`${code}`) }"#
)]
#[wasm_bindgen(module = "/typescript/bundles/strict_eval.js")]
extern "C" {
#[wasm_bindgen(catch)]
pub fn strict_eval(code: &str) -> Result<JsValue, JsValue>;
pub async fn strict_eval(code: &str) -> Result<JsValue, JsValue>;
}
#[wasm_bindgen]
@ -67,4 +68,14 @@ impl FW {
}
Vec::new()
}
/// JS: `FW.add_decoders(["../test_files/components/rust_decoder/rust_decoder.wasm"])` -> `1`
pub async fn add_decoders(decoder_paths: Vec<DecoderPath>) -> AddedDecodersCount {
platform::add_decoders(decoder_paths).await
}
/// JS: `FW.remove_all_decoders()` -> `5`
pub async fn remove_all_decoders() -> RemovedDecodersCount {
platform::remove_all_decoders().await
}
}

View file

@ -4,7 +4,8 @@ use wellen::GetItem;
use zoon::*;
mod pixi_canvas;
use pixi_canvas::{PixiCanvas, PixiController};
use pixi_canvas::PixiCanvas;
pub use pixi_canvas::PixiController;
const ROW_HEIGHT: u32 = 40;
const ROW_GAP: u32 = 4;
@ -14,7 +15,7 @@ pub struct WaveformPanel {
selected_var_refs: MutableVec<wellen::VarRef>,
hierarchy: Mutable<Option<Arc<wellen::Hierarchy>>>,
loaded_filename: Mutable<Option<Filename>>,
canvas_controller: Mutable<ReadOnlyMutable<Option<PixiController>>>,
canvas_controller: Mutable<Mutable<Option<SendWrapper<PixiController>>>>,
}
impl WaveformPanel {
@ -22,12 +23,13 @@ impl WaveformPanel {
hierarchy: Mutable<Option<Arc<wellen::Hierarchy>>>,
selected_var_refs: MutableVec<wellen::VarRef>,
loaded_filename: Mutable<Option<Filename>>,
canvas_controller: Mutable<Mutable<Option<SendWrapper<PixiController>>>>,
) -> impl Element {
Self {
selected_var_refs,
hierarchy,
loaded_filename,
canvas_controller: Mutable::new(Mutable::default().read_only()),
canvas_controller,
}
.root()
}
@ -96,7 +98,7 @@ impl WaveformPanel {
if let Some(javascript_code) =
platform::load_file_with_selected_vars(None).await
{
match script_bridge::strict_eval(&javascript_code) {
match script_bridge::strict_eval(&javascript_code).await {
Ok(js_value) => {
zoon::println!("File with selected vars loaded: {js_value:?}")
}
@ -153,7 +155,7 @@ impl WaveformPanel {
if let Some(javascript_code) =
platform::load_file_with_selected_vars(Some(file)).await
{
match script_bridge::strict_eval(&javascript_code) {
match script_bridge::strict_eval(&javascript_code).await {
Ok(js_value) => zoon::println!(
"File with selected vars loaded: {js_value:?}"
),

View file

@ -5,7 +5,7 @@ use zoon::*;
pub struct PixiCanvas {
raw_el: RawHtmlEl<web_sys::HtmlElement>,
controller: ReadOnlyMutable<Option<js_bridge::PixiController>>,
controller: Mutable<Option<SendWrapper<js_bridge::PixiController>>>,
#[allow(dead_code)]
width: ReadOnlyMutable<u32>,
#[allow(dead_code)]
@ -32,7 +32,8 @@ impl HasIds for PixiCanvas {}
impl PixiCanvas {
pub fn new(row_height: u32, row_gap: u32) -> Self {
let controller: Mutable<Option<js_bridge::PixiController>> = Mutable::new(None);
let controller: Mutable<Option<SendWrapper<js_bridge::PixiController>>> =
Mutable::new(None);
let width = Mutable::new(0);
let height = Mutable::new(0);
let resize_task = Task::start_droppable(
@ -77,7 +78,7 @@ impl PixiCanvas {
));
// -- // --
Self {
controller: controller.read_only(),
controller: controller.clone(),
width: width.read_only(),
height: height.read_only(),
task_with_controller: task_with_controller.clone(),
@ -105,14 +106,14 @@ impl PixiCanvas {
})
.after_insert(clone!((controller, timeline_getter) move |element| {
Task::start(async move {
let pixi_controller = js_bridge::PixiController::new(
let pixi_controller = SendWrapper::new(js_bridge::PixiController::new(
1.,
width.get(),
0,
row_height,
row_gap,
&timeline_getter
);
));
pixi_controller.init(&element).await;
controller.set(Some(pixi_controller));
});
@ -131,7 +132,7 @@ impl PixiCanvas {
pub fn task_with_controller<FUT: Future<Output = ()> + 'static>(
self,
f: impl FnOnce(ReadOnlyMutable<Option<js_bridge::PixiController>>) -> FUT,
f: impl FnOnce(Mutable<Option<SendWrapper<js_bridge::PixiController>>>) -> FUT,
) -> Self {
self.task_with_controller
.set(Some(Task::start_droppable(f(self.controller.clone()))));
@ -186,6 +187,8 @@ mod js_bridge {
#[wasm_bindgen(method)]
pub fn destroy(this: &PixiController);
// -- FastWave-specific --
#[wasm_bindgen(method)]
pub fn get_timeline_zoom(this: &PixiController) -> f64;
@ -195,8 +198,6 @@ mod js_bridge {
#[wasm_bindgen(method)]
pub fn get_timeline_viewport_x(this: &PixiController) -> i32;
// -- FastWave-specific --
#[wasm_bindgen(method)]
pub fn set_var_format(this: &PixiController, index: usize, var_format: JsValue);
@ -224,5 +225,8 @@ mod js_bridge {
#[wasm_bindgen(method)]
pub fn clear_vars(this: &PixiController);
#[wasm_bindgen(method)]
pub async fn redraw_all_rows(this: &PixiController);
}
}

View file

@ -35265,6 +35265,8 @@ var PixiController = class {
clear_vars() {
this.var_signal_rows.slice().reverse().forEach((row) => row.destroy());
}
request_timeline_redraw() {
}
};
var VarSignalRow = class {
signal_ref_index;

View file

@ -0,0 +1,5 @@
"use strict";
export async function strict_eval(code) {
return await eval(code)
}

View file

@ -2536,11 +2536,19 @@ async function load_signal_and_get_timeline(signal_ref_index, timeline_zoom, tim
async function unload_signal(signal_ref_index) {
return await invoke2("unload_signal", { signal_ref_index });
}
async function add_decoders(decoder_paths) {
return await invoke2("add_decoders", { decoder_paths });
}
async function remove_all_decoders() {
return await invoke2("remove_all_decoders");
}
export {
add_decoders,
get_hierarchy,
load_file_with_selected_vars,
load_signal_and_get_timeline,
pick_and_load_waveform,
remove_all_decoders,
show_window,
unload_signal
};

View file

@ -199,6 +199,10 @@ export class PixiController {
clear_vars() {
this.var_signal_rows.slice().reverse().forEach(row => row.destroy());
}
request_timeline_redraw() {
}
}
class VarSignalRow {

View file

@ -9,6 +9,9 @@ type JavascriptCode = string;
type WellenHierarchy = unknown;
type Timeline = unknown;
type VarFormat = unknown;
type AddedDecodersCount = number;
type RemovedDecodersCount = number;
type DecoderPath = string;
export async function show_window(): Promise<void> {
return await invoke("show_window");
@ -47,3 +50,11 @@ export async function load_signal_and_get_timeline(
export async function unload_signal(signal_ref_index: number): Promise<void> {
return await invoke("unload_signal", { signal_ref_index });
}
export async function add_decoders(decoder_paths: Array<DecoderPath>): Promise<AddedDecodersCount> {
return await invoke("add_decoders", { decoder_paths });
}
export async function remove_all_decoders(): Promise<RemovedDecodersCount> {
return await invoke("remove_all_decoders");
}

View file

@ -1,13 +1,25 @@
use crate::*;
use future::BoxFuture;
use wellen::SignalValue;
pub fn signal_to_timeline(
signal: &wellen::Signal,
// @TODO remove once https://github.com/rust-lang/rust/issues/89976 is resolved
// (`error: implementation of `FnOnce` is not general enough`)
fn type_hint<F>(f: F) -> F
where
F: for<'a> FnMut((u32, SignalValue<'a>)) -> (f64, SignalValue<'a>),
{
f
}
pub async fn signal_to_timeline<'s>(
signal: &'s wellen::Signal,
time_table: &[wellen::Time],
timeline_zoom: f64,
timeline_viewport_width: u32,
timeline_viewport_x: i32,
block_height: u32,
var_format: VarFormat,
mut format_by_decoders: impl FnMut(String) -> BoxFuture<'s, String>,
) -> Timeline {
const MIN_BLOCK_WIDTH: u32 = 3;
// Courier New, 16px, sync with `label_style` in `pixi_canvas.rs`
@ -25,12 +37,12 @@ pub fn signal_to_timeline(
let mut x_value_pairs = signal
.iter_changes()
.map(|(index, value)| {
.map(type_hint(move |(index, value)| {
let index = index as usize;
let time = time_table[index] as f64;
let x = time / last_time * timeline_width - timeline_viewport_x;
(x, value)
})
}))
.peekable();
// @TODO parallelize?
@ -55,7 +67,8 @@ pub fn signal_to_timeline(
}
// @TODO cache?
let value = var_format.format(value);
let mut value = var_format.format(value);
value = format_by_decoders(value).await;
let value_width = (value.chars().count() as f64 * LETTER_WIDTH) as u32;
// @TODO Ellipsis instead of hiding?

View file

@ -23,3 +23,11 @@ serde = { version = "1.0", features = ["derive"] }
tauri = { version = "=2.0.0-beta.22", features = ["macos-private-api", "linux-ipc-protocol"] }
tauri-plugin-window-state = "=2.0.0-beta.9"
tauri-plugin-dialog = "=2.0.0-beta.9"
once_cell = "1.19.0"
futures = "0.3.30"
# wasmtime = "22.0.0"
# wasmtime-wasi = "22.0.0"
# ~23.0.0
wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", rev = "842fa767acdc26f096ac108605353b8b71e23169" }
wasmtime-wasi = { git = "https://github.com/bytecodealliance/wasmtime", rev = "842fa767acdc26f096ac108605353b8b71e23169" }

View file

@ -0,0 +1,125 @@
use crate::{AddedDecodersCount, DecoderPath, RemovedDecodersCount};
use once_cell::sync::Lazy;
use std::sync::Arc;
use tauri::async_runtime::{Mutex, RwLock};
use wasmtime::component::{Component as WasmtimeComponent, *};
use wasmtime::{AsContextMut, Engine, Store};
use wasmtime_wasi::{WasiCtx, WasiView};
bindgen!();
pub static DECODERS: Lazy<Arc<RwLock<Vec<Component>>>> = Lazy::new(<_>::default);
static ENGINE: Lazy<Engine> = Lazy::new(<_>::default);
static LINKER: Lazy<Linker<State>> = Lazy::new(|| {
let mut linker = Linker::new(&ENGINE);
wasmtime_wasi::add_to_linker_sync(&mut linker).unwrap();
Component::add_to_linker(&mut linker, |state: &mut State| state).unwrap();
linker
});
pub static STORE: Lazy<Arc<Mutex<Store<State>>>> = Lazy::new(|| {
let store = Store::new(
&ENGINE,
State {
ctx: WasiCtx::builder().build(),
table: ResourceTable::new(),
},
);
Arc::new(Mutex::new(store))
});
pub struct State {
ctx: WasiCtx,
table: ResourceTable,
}
impl WasiView for State {
fn ctx(&mut self) -> &mut WasiCtx {
&mut self.ctx
}
fn table(&mut self) -> &mut ResourceTable {
&mut self.table
}
}
impl component::decoder::host::Host for State {
fn log(&mut self, message: String) {
println!("Decoder: {message}");
}
}
pub async fn remove_all_decoders() -> RemovedDecodersCount {
let mut decoders = DECODERS.write().await;
let decoders_count = decoders.len();
decoders.clear();
decoders_count
}
// @TODO Make println work on Windows in release mode?
// https://github.com/tauri-apps/tauri/discussions/8626
// @TODO Remove / improve comments below
// Testing
//
// Rust
// FW.add_decoders(["../test_files/components/rust_decoder/rust_decoder.wasm"])
// FW.add_decoders(["../test_files/components/rust_decoder/rust_decoder.wasm", "../test_files/components/rust_decoder/rust_decoder.wasm"])
//
// JS
// FW.add_decoders(["../test_files/components/javascript_decoder/javascript_decoder.wasm"])
//
// Python
// FW.add_decoders(["../test_files/components/python_decoder/python_decoder.wasm"])
//
// Remove all
// FW.remove_all_decoders()
//
// All Debug
// FW.add_decoders(["../test_files/components/rust_decoder/rust_decoder.wasm", "../test_files/components/javascript_decoder/javascript_decoder.wasm", "../test_files/components/python_decoder/python_decoder.wasm"])
//
// All Release
// FW.add_decoders(["../../test_files/components/rust_decoder/rust_decoder.wasm", "../../test_files/components/javascript_decoder/javascript_decoder.wasm", "../../test_files/components/python_decoder/python_decoder.wasm"])
pub async fn add_decoders(decoder_paths: Vec<DecoderPath>) -> AddedDecodersCount {
println!("Decoders: {decoder_paths:#?}");
println!("Current dir: {:#?}", std::env::current_dir().unwrap());
let mut added_decoders_count = 0;
// @TODO (?) New thread to prevent "Cannot start a runtime from within a runtime."
// when a call to a component fails / panics
// std::thread::spawn(move || {
// futures::executor::block_on(async move {
for decoder_path in decoder_paths {
if let Err(error) = add_decoder(&decoder_path).await {
eprintln!("add_decoders error: {error:?}");
} else {
added_decoders_count += 1;
}
}
// })
// }).join().unwrap();
added_decoders_count
}
async fn add_decoder(path: &str) -> wasmtime::Result<()> {
let wasmtime_component = WasmtimeComponent::from_file(&ENGINE, path)?;
let mut store_lock = STORE.lock().await;
let mut store = store_lock.as_context_mut();
let component = Component::instantiate(&mut store, &wasmtime_component, &LINKER)?;
println!(
"Decoder name: {}",
component
.component_decoder_decoder()
.call_name(&mut store)?
);
component
.component_decoder_decoder()
.call_init(&mut store)?;
DECODERS.write().await.push(component);
Ok(())
}

View file

@ -1,14 +1,20 @@
use std::fs;
use std::sync::Mutex;
use tauri::async_runtime::RwLock;
use tauri_plugin_dialog::DialogExt;
use wasmtime::AsContextMut;
use wellen::simple::Waveform;
type Filename = String;
type JavascriptCode = String;
type AddedDecodersCount = usize;
type RemovedDecodersCount = usize;
type DecoderPath = String;
mod component_manager;
#[derive(Default)]
struct Store {
waveform: Mutex<Option<Waveform>>,
waveform: RwLock<Option<Waveform>>,
}
#[tauri::command(rename_all = "snake_case")]
@ -30,7 +36,7 @@ async fn pick_and_load_waveform(
let Ok(waveform) = waveform else {
panic!("Waveform file reading failed")
};
*store.waveform.lock().unwrap() = Some(waveform);
*store.waveform.write().await = Some(waveform);
Ok(Some(file_response.name.unwrap()))
}
@ -48,8 +54,9 @@ async fn load_file_with_selected_vars(app: tauri::AppHandle) -> Result<Option<Ja
#[tauri::command(rename_all = "snake_case")]
async fn get_hierarchy(store: tauri::State<'_, Store>) -> Result<serde_json::Value, ()> {
let waveform = store.waveform.lock().unwrap();
let hierarchy = waveform.as_ref().unwrap().hierarchy();
let waveform_lock = store.waveform.read().await;
let waveform = waveform_lock.as_ref().unwrap();
let hierarchy = waveform.hierarchy();
Ok(serde_json::to_value(hierarchy).unwrap())
}
@ -65,7 +72,7 @@ async fn load_signal_and_get_timeline(
) -> Result<serde_json::Value, ()> {
// @TODO run (all?) in a blocking thread?
let signal_ref = wellen::SignalRef::from_index(signal_ref_index).unwrap();
let mut waveform_lock = store.waveform.lock().unwrap();
let mut waveform_lock = store.waveform.write().await;
let waveform = waveform_lock.as_mut().unwrap();
waveform.load_signals_multi_threaded(&[signal_ref]);
let signal = waveform.get_signal(signal_ref).unwrap();
@ -78,19 +85,56 @@ async fn load_signal_and_get_timeline(
timeline_viewport_x,
block_height,
var_format,
);
|mut value: String| {
Box::pin(async {
// We need to spawn a (non-runtime-specific?) blocking task before calling component methods to prevent this error:
// "Cannot start a runtime from within a runtime. This happens because a function (like `block_on`) attempted to block the current thread while the thread is being used to drive asynchronous tasks."
// @TODO Workaround? Is it a problem only for non-Rust components? Is it needed only when there is a problem in the component (e.g. "`Err` value: wasm trap: cannot enter component instance"?)
// let value = std::thread::spawn(move || {
// futures::executor::block_on(async move {
let decoders = component_manager::DECODERS.read().await;
let mut store_lock = component_manager::STORE.lock().await;
let mut store = store_lock.as_context_mut();
for decoder in decoders.iter() {
value = decoder
.component_decoder_decoder()
.call_format_signal_value(&mut store, &value)
// @TODO Resolve panic when running non-Rust components:
// `Err` value: wasm trap: cannot enter component instance
// https://github.com/bytecodealliance/wasmtime/issues/8670 ?
.unwrap()
}
// value
// })
// }).join().unwrap();
value
})
},
)
.await;
Ok(serde_json::to_value(timeline).unwrap())
}
#[tauri::command(rename_all = "snake_case")]
async fn unload_signal(signal_ref_index: usize, store: tauri::State<'_, Store>) -> Result<(), ()> {
let signal_ref = wellen::SignalRef::from_index(signal_ref_index).unwrap();
let mut waveform_lock = store.waveform.lock().unwrap();
let mut waveform_lock = store.waveform.write().await;
let waveform = waveform_lock.as_mut().unwrap();
waveform.unload_signals(&[signal_ref]);
Ok(())
}
#[tauri::command(rename_all = "snake_case")]
async fn add_decoders(decoder_paths: Vec<DecoderPath>) -> Result<AddedDecodersCount, ()> {
Ok(component_manager::add_decoders(decoder_paths).await)
}
#[tauri::command(rename_all = "snake_case")]
async fn remove_all_decoders() -> Result<RemovedDecodersCount, ()> {
Ok(component_manager::remove_all_decoders().await)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
// https://github.com/tauri-apps/tauri/issues/8462
@ -109,6 +153,8 @@ pub fn run() {
get_hierarchy,
load_signal_and_get_timeline,
unload_signal,
add_decoders,
remove_all_decoders,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

16
src-tauri/wit/world.wit Normal file
View file

@ -0,0 +1,16 @@
package component:decoder;
interface host {
log: func(message: string);
}
interface decoder {
init: func();
name: func() -> string;
format-signal-value: func(value: string) -> string;
}
world component {
import host;
export decoder;
}

View file

@ -0,0 +1 @@
https://component-model.bytecodealliance.org/

View file

@ -0,0 +1 @@
node_modules

View file

@ -0,0 +1,10 @@
How to create and build the Javascript component:
1. Create the `javascript_decoder` folder
2. `cd javascript_decoder`
3. Create `.gitignore` with content `node_modules`
4. `npm install @bytecodealliance/jco @bytecodealliance/componentize-js binaryen`
5. Create the `src` folder with the file `index.js`
6. Create the `wit` folder with the file `world.wit`
7. Update code as needed
8. `npx jco componentize src/index.js --wit wit/world.wit --out javascript_decoder.wasm && npx jco opt javascript_decoder.wasm --output javascript_decoder.wasm`

View file

@ -0,0 +1,570 @@
{
"name": "javascript_decoder",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@bytecodealliance/componentize-js": "^0.8.3",
"@bytecodealliance/jco": "^1.2.4",
"binaryen": "^117.0.0"
}
},
"node_modules/@bytecodealliance/componentize-js": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/@bytecodealliance/componentize-js/-/componentize-js-0.8.3.tgz",
"integrity": "sha512-QyEHRtVg/Cg6RsA75AvRSbSOr0u+FLuXqB89X4Sys44K/VT5g/S9eMn8gqTotfuXVU3btS3Z4QAiyHSF2bja3w==",
"dependencies": {
"@bytecodealliance/jco": "1.1.1",
"@bytecodealliance/wizer": "^3.0.1",
"es-module-lexer": "^1.4.1"
}
},
"node_modules/@bytecodealliance/componentize-js/node_modules/@bytecodealliance/jco": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@bytecodealliance/jco/-/jco-1.1.1.tgz",
"integrity": "sha512-s8Zz6GFPlo2g+dsGp1OMIWXSZnM4FyIloxNAc4grF5TZwFoD00Gj8b0xvpmFSeZj36X/bJPa7x3za3j7Cfeetw==",
"dependencies": {
"@bytecodealliance/preview2-shim": "^0.16.1",
"binaryen": "^116.0.0",
"chalk-template": "^1",
"commander": "^12",
"mkdirp": "^3",
"ora": "^8",
"terser": "^5"
},
"bin": {
"jco": "src/jco.js"
}
},
"node_modules/@bytecodealliance/componentize-js/node_modules/binaryen": {
"version": "116.0.0",
"resolved": "https://registry.npmjs.org/binaryen/-/binaryen-116.0.0.tgz",
"integrity": "sha512-Hp0dXC6Cb/rTwWEoUS2BRghObE7g/S9umKtxuTDt3f61G6fNTE/YVew/ezyy3IdHcLx3f17qfh6LwETgCfvWkQ==",
"bin": {
"wasm-opt": "bin/wasm-opt",
"wasm2js": "bin/wasm2js"
}
},
"node_modules/@bytecodealliance/jco": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@bytecodealliance/jco/-/jco-1.2.4.tgz",
"integrity": "sha512-uuOm9UkYqWp5uElYDNzlhjbdrAmczEvETgQdI1hFTk79h+mrmHPRV32pgRP5o1eHVwMIpuk4XQkDIhFbksurUw==",
"dependencies": {
"@bytecodealliance/preview2-shim": "^0.16.2",
"binaryen": "^116.0.0",
"chalk-template": "^1",
"commander": "^12",
"mkdirp": "^3",
"ora": "^8",
"terser": "^5"
},
"bin": {
"jco": "src/jco.js"
}
},
"node_modules/@bytecodealliance/jco/node_modules/binaryen": {
"version": "116.0.0",
"resolved": "https://registry.npmjs.org/binaryen/-/binaryen-116.0.0.tgz",
"integrity": "sha512-Hp0dXC6Cb/rTwWEoUS2BRghObE7g/S9umKtxuTDt3f61G6fNTE/YVew/ezyy3IdHcLx3f17qfh6LwETgCfvWkQ==",
"bin": {
"wasm-opt": "bin/wasm-opt",
"wasm2js": "bin/wasm2js"
}
},
"node_modules/@bytecodealliance/preview2-shim": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@bytecodealliance/preview2-shim/-/preview2-shim-0.16.2.tgz",
"integrity": "sha512-36MwesmbLSf3Y5/OHcS85iBaF0N92CQ4gpjtDVKSbrjxmrBKCWlWVfoQ03F/cqDg8k5K7pzVaVBH0XBIbTCfTQ=="
},
"node_modules/@bytecodealliance/wizer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@bytecodealliance/wizer/-/wizer-3.0.1.tgz",
"integrity": "sha512-f0NBiBHCNBkbFHTPRbA7aKf/t4KyNhi2KvSqw3QzCgi8wFF/uLZ0dhejj93rbiKO/iwWbmU7v9K3SVkW81mcjQ==",
"bin": {
"wizer": "wizer.js"
},
"engines": {
"node": ">=16"
},
"optionalDependencies": {
"@bytecodealliance/wizer-darwin-arm64": "3.0.1",
"@bytecodealliance/wizer-darwin-x64": "3.0.1",
"@bytecodealliance/wizer-linux-arm64": "3.0.1",
"@bytecodealliance/wizer-linux-s390x": "3.0.1",
"@bytecodealliance/wizer-linux-x64": "3.0.1",
"@bytecodealliance/wizer-win32-x64": "3.0.1"
}
},
"node_modules/@bytecodealliance/wizer-darwin-arm64": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-darwin-arm64/-/wizer-darwin-arm64-3.0.1.tgz",
"integrity": "sha512-/8KYSajyhO9koAE3qQhYfC6belZheJw9X3XqW7hrizTpj6n4z4OJFhhqwJmiYFUUsPtC7OxcXMFFPbTuSQPBcw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"bin": {
"wizer-darwin-arm64": "wizer"
}
},
"node_modules/@bytecodealliance/wizer-darwin-x64": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-darwin-x64/-/wizer-darwin-x64-3.0.1.tgz",
"integrity": "sha512-bMReultN/r+W/BRXV0F+28U5dZwbQT/ZO0k4icZlhUhrv5/wpQJix7Z/ZvBnVQ+/JHb0QDUpFk2/zCtgkRXP6Q==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"bin": {
"wizer-darwin-x64": "wizer"
}
},
"node_modules/@bytecodealliance/wizer-linux-arm64": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-arm64/-/wizer-linux-arm64-3.0.1.tgz",
"integrity": "sha512-35ZhAeYxWK3bTqqgwysbBWlGlrlMNKNng3ZITQV2PAtafpE7aCeqywl7VAS4lLRG5eTb7wxNgN7zf8d3wiIFTQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"bin": {
"wizer-linux-arm64": "wizer"
}
},
"node_modules/@bytecodealliance/wizer-linux-s390x": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-s390x/-/wizer-linux-s390x-3.0.1.tgz",
"integrity": "sha512-Smvy9mguEMtX0lupDLTPshXUzAHeOhgscr1bhGNjeCCLD1sd8rIjBvWV19Wtra0BL1zTuU2EPOHjR/4k8WoyDg==",
"cpu": [
"s390x"
],
"optional": true,
"os": [
"linux"
],
"bin": {
"wizer-linux-s390x": "wizer"
}
},
"node_modules/@bytecodealliance/wizer-linux-x64": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-linux-x64/-/wizer-linux-x64-3.0.1.tgz",
"integrity": "sha512-uUue78xl7iwndsGgTsagHLTLyLBVHhwzuywiwHt1xw8y0X0O8REKRLBoB7+LdM+pttDPdFtKJgbTFL4UPAA7Yw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"bin": {
"wizer-linux-x64": "wizer"
}
},
"node_modules/@bytecodealliance/wizer-win32-x64": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@bytecodealliance/wizer-win32-x64/-/wizer-win32-x64-3.0.1.tgz",
"integrity": "sha512-ycd38sx1UTZpHZwh8IfH/4N3n0OQUB8awxkUSLXf9PolEd088YbxoPB3noHy4E+L2oYN7KZMrg9517pX0z2RhQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"bin": {
"wizer-win32-x64": "wizer"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/acorn": {
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz",
"integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/binaryen": {
"version": "117.0.0",
"resolved": "https://registry.npmjs.org/binaryen/-/binaryen-117.0.0.tgz",
"integrity": "sha512-1D+O881OXxY737WPKfIgEscCn3vWGqTsd0m5nGKzvbtadVYw5pZ3eebineH/oV5c/rAW80Bojrsa6firSSIsUw==",
"bin": {
"wasm-as": "bin/wasm-as",
"wasm-ctor-eval": "bin/wasm-ctor-eval",
"wasm-dis": "bin/wasm-dis",
"wasm-merge": "bin/wasm-merge",
"wasm-metadce": "bin/wasm-metadce",
"wasm-opt": "bin/wasm-opt",
"wasm-reduce": "bin/wasm-reduce",
"wasm-shell": "bin/wasm-shell",
"wasm2js": "bin/wasm2js"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
"node_modules/chalk": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chalk-template": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.0.tgz",
"integrity": "sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==",
"dependencies": {
"chalk": "^5.2.0"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/chalk/chalk-template?sponsor=1"
}
},
"node_modules/cli-cursor": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
"integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
"dependencies": {
"restore-cursor": "^4.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-spinners": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
"integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/commander": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"engines": {
"node": ">=18"
}
},
"node_modules/emoji-regex": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
"integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw=="
},
"node_modules/es-module-lexer": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw=="
},
"node_modules/get-east-asian-width": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz",
"integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-interactive": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
"integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-unicode-supported": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz",
"integrity": "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-symbols": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz",
"integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==",
"dependencies": {
"chalk": "^5.3.0",
"is-unicode-supported": "^1.3.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-symbols/node_modules/is-unicode-supported": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
"integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"engines": {
"node": ">=6"
}
},
"node_modules/mkdirp": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
"bin": {
"mkdirp": "dist/cjs/src/bin.js"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/onetime": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dependencies": {
"mimic-fn": "^2.1.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ora": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/ora/-/ora-8.0.1.tgz",
"integrity": "sha512-ANIvzobt1rls2BDny5fWZ3ZVKyD6nscLvfFRpQgfWsythlcsVUC9kL0zq6j2Z5z9wwp1kd7wpsD/T9qNPVLCaQ==",
"dependencies": {
"chalk": "^5.3.0",
"cli-cursor": "^4.0.0",
"cli-spinners": "^2.9.2",
"is-interactive": "^2.0.0",
"is-unicode-supported": "^2.0.0",
"log-symbols": "^6.0.0",
"stdin-discarder": "^0.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/restore-cursor": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
"integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
"dependencies": {
"onetime": "^5.1.0",
"signal-exit": "^3.0.2"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"node_modules/stdin-discarder": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz",
"integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz",
"integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==",
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/terser": {
"version": "5.31.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.31.1.tgz",
"integrity": "sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==",
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
"commander": "^2.20.0",
"source-map-support": "~0.5.20"
},
"bin": {
"terser": "bin/terser"
},
"engines": {
"node": ">=10"
}
},
"node_modules/terser/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
}
}
}

View file

@ -0,0 +1,7 @@
{
"dependencies": {
"@bytecodealliance/componentize-js": "^0.8.3",
"@bytecodealliance/jco": "^1.2.4",
"binaryen": "^117.0.0"
}
}

View file

@ -0,0 +1,17 @@
import { log } from "component:decoder/host"
const name = "Javascript Test Decoder"
export const decoder = {
init() {
log(`${name} initialized`)
},
name() {
return name
},
formatSignalValue(value) {
return value + "!"
}
}

View file

@ -0,0 +1,16 @@
package component:decoder;
interface host {
log: func(message: string);
}
interface decoder {
init: func();
name: func() -> string;
format-signal-value: func(value: string) -> string;
}
world component {
import host;
export decoder;
}

View file

@ -0,0 +1 @@
__pycache__

View file

@ -0,0 +1,10 @@
How to create and build the Python component:
1. `pip install componentize-py`
2. Create the `python_decoder` folder
3. `cd python_decoder`
4. Create `.gitignore` with content `__pycache__`
5. Create the `src` folder with the file `app.py`
6. Create the `wit` folder with the file `world.wit`
7. Update code as needed
8. `rm -rf src/bindings && componentize-py --wit-path wit/world.wit bindings src/bindings && componentize-py --wit-path wit/world.wit componentize src.app --output python_decoder.wasm`

View file

@ -0,0 +1,17 @@
from .bindings.component import exports
from .bindings.component.imports import host
name = "Python Test Decoder"
class Decoder(exports.Decoder):
def init(self) -> None:
# @TODO it panics with error `7: 0xae8683 - libcomponentize_py_runtime.so!componentize-py#Dispatch`
# - see https://github.com/bytecodealliance/componentize-py/blob/e20d9e6706ff1421cd8001449acb51eb9c87d0c6/runtime/src/lib.rs#L404
# host.log(f"{name} initialized")
return None
def name(self) -> str:
return name
def format_signal_value(self, value: str) -> str:
return value + "!"

View file

@ -0,0 +1,12 @@
from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self
from enum import Flag, Enum, auto
from dataclasses import dataclass
from abc import abstractmethod
import weakref
from .types import Result, Ok, Err, Some
class Component(Protocol):
pass

View file

@ -0,0 +1,24 @@
from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self
from enum import Flag, Enum, auto
from dataclasses import dataclass
from abc import abstractmethod
import weakref
from ..types import Result, Ok, Err, Some
class Decoder(Protocol):
@abstractmethod
def init(self) -> None:
raise NotImplementedError
@abstractmethod
def name(self) -> str:
raise NotImplementedError
@abstractmethod
def format_signal_value(self, value: str) -> str:
raise NotImplementedError

View file

@ -0,0 +1,9 @@
from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self
from enum import Flag, Enum, auto
from dataclasses import dataclass
from abc import abstractmethod
import weakref
from ..types import Result, Ok, Err, Some

View file

@ -0,0 +1,13 @@
from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self
from enum import Flag, Enum, auto
from dataclasses import dataclass
from abc import abstractmethod
import weakref
from ..types import Result, Ok, Err, Some
def log(message: str) -> None:
raise NotImplementedError

View file

@ -0,0 +1,23 @@
from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self
from enum import Flag, Enum, auto
from dataclasses import dataclass
from abc import abstractmethod
import weakref
S = TypeVar('S')
@dataclass
class Some(Generic[S]):
value: S
T = TypeVar('T')
@dataclass
class Ok(Generic[T]):
value: T
E = TypeVar('E')
@dataclass(frozen=True)
class Err(Generic[E], Exception):
value: E
Result = Union[Ok[T], Err[E]]

View file

@ -0,0 +1,16 @@
package component:decoder;
interface host {
log: func(message: string);
}
interface decoder {
init: func();
name: func() -> string;
format-signal-value: func(value: string) -> string;
}
world component {
import host;
export decoder;
}

View file

@ -0,0 +1,26 @@
[package]
name = "rust_decoder"
version.workspace = true
edition.workspace = true
repository.workspace = true
authors.workspace = true
readme.workspace = true
publish.workspace = true
[dependencies]
wit-bindgen-rt = { version = "0.26.0", features = ["bitflags"] }
[lib]
crate-type = ["cdylib"]
[profile.release]
codegen-units = 1
opt-level = "s"
debug = false
strip = true
lto = true
[package.metadata.component]
package = "component:rust-decoder"
[package.metadata.component.dependencies]

View file

@ -0,0 +1,7 @@
How to create and build the Rust component:
1. `cargo install cargo-component`
2. `cargo component new rust_decoder --lib`
3. `cd rust_decoder`
4. Update code as needed
5. `cargo component build --release --target wasm32-unknown-unknown && cp ../../../target/wasm32-unknown-unknown/release/rust_decoder.wasm .`

Binary file not shown.

View file

@ -0,0 +1,220 @@
// Generated by `wit-bindgen` 0.25.0. DO NOT EDIT!
// Options used:
#[allow(dead_code)]
pub mod component {
#[allow(dead_code)]
pub mod decoder {
#[allow(dead_code, clippy::all)]
pub mod host {
#[used]
#[doc(hidden)]
#[cfg(target_arch = "wasm32")]
static __FORCE_SECTION_REF: fn() =
super::super::super::__link_custom_section_describing_imports;
#[allow(unused_unsafe, clippy::all)]
pub fn log(message: &str) {
unsafe {
let vec0 = message;
let ptr0 = vec0.as_ptr().cast::<u8>();
let len0 = vec0.len();
#[cfg(target_arch = "wasm32")]
#[link(wasm_import_module = "component:decoder/host")]
extern "C" {
#[link_name = "log"]
fn wit_import(_: *mut u8, _: usize);
}
#[cfg(not(target_arch = "wasm32"))]
fn wit_import(_: *mut u8, _: usize) {
unreachable!()
}
wit_import(ptr0.cast_mut(), len0);
}
}
}
}
}
#[allow(dead_code)]
pub mod exports {
#[allow(dead_code)]
pub mod component {
#[allow(dead_code)]
pub mod decoder {
#[allow(dead_code, clippy::all)]
pub mod decoder {
#[used]
#[doc(hidden)]
#[cfg(target_arch = "wasm32")]
static __FORCE_SECTION_REF: fn() =
super::super::super::super::__link_custom_section_describing_imports;
use super::super::super::super::_rt;
#[doc(hidden)]
#[allow(non_snake_case)]
pub unsafe fn _export_init_cabi<T: Guest>() {
#[cfg(target_arch = "wasm32")]
_rt::run_ctors_once();
T::init();
}
#[doc(hidden)]
#[allow(non_snake_case)]
pub unsafe fn _export_name_cabi<T: Guest>() -> *mut u8 {
#[cfg(target_arch = "wasm32")]
_rt::run_ctors_once();
let result0 = T::name();
let ptr1 = _RET_AREA.0.as_mut_ptr().cast::<u8>();
let vec2 = (result0.into_bytes()).into_boxed_slice();
let ptr2 = vec2.as_ptr().cast::<u8>();
let len2 = vec2.len();
::core::mem::forget(vec2);
*ptr1.add(4).cast::<usize>() = len2;
*ptr1.add(0).cast::<*mut u8>() = ptr2.cast_mut();
ptr1
}
#[doc(hidden)]
#[allow(non_snake_case)]
pub unsafe fn __post_return_name<T: Guest>(arg0: *mut u8) {
let l0 = *arg0.add(0).cast::<*mut u8>();
let l1 = *arg0.add(4).cast::<usize>();
_rt::cabi_dealloc(l0, l1, 1);
}
#[doc(hidden)]
#[allow(non_snake_case)]
pub unsafe fn _export_format_signal_value_cabi<T: Guest>(
arg0: *mut u8,
arg1: usize,
) -> *mut u8 {
#[cfg(target_arch = "wasm32")]
_rt::run_ctors_once();
let len0 = arg1;
let bytes0 = _rt::Vec::from_raw_parts(arg0.cast(), len0, len0);
let result1 = T::format_signal_value(_rt::string_lift(bytes0));
let ptr2 = _RET_AREA.0.as_mut_ptr().cast::<u8>();
let vec3 = (result1.into_bytes()).into_boxed_slice();
let ptr3 = vec3.as_ptr().cast::<u8>();
let len3 = vec3.len();
::core::mem::forget(vec3);
*ptr2.add(4).cast::<usize>() = len3;
*ptr2.add(0).cast::<*mut u8>() = ptr3.cast_mut();
ptr2
}
#[doc(hidden)]
#[allow(non_snake_case)]
pub unsafe fn __post_return_format_signal_value<T: Guest>(arg0: *mut u8) {
let l0 = *arg0.add(0).cast::<*mut u8>();
let l1 = *arg0.add(4).cast::<usize>();
_rt::cabi_dealloc(l0, l1, 1);
}
pub trait Guest {
fn init();
fn name() -> _rt::String;
fn format_signal_value(value: _rt::String) -> _rt::String;
}
#[doc(hidden)]
macro_rules! __export_component_decoder_decoder_cabi{
($ty:ident with_types_in $($path_to_types:tt)*) => (const _: () = {
#[export_name = "component:decoder/decoder#init"]
unsafe extern "C" fn export_init() {
$($path_to_types)*::_export_init_cabi::<$ty>()
}
#[export_name = "component:decoder/decoder#name"]
unsafe extern "C" fn export_name() -> *mut u8 {
$($path_to_types)*::_export_name_cabi::<$ty>()
}
#[export_name = "cabi_post_component:decoder/decoder#name"]
unsafe extern "C" fn _post_return_name(arg0: *mut u8,) {
$($path_to_types)*::__post_return_name::<$ty>(arg0)
}
#[export_name = "component:decoder/decoder#format-signal-value"]
unsafe extern "C" fn export_format_signal_value(arg0: *mut u8,arg1: usize,) -> *mut u8 {
$($path_to_types)*::_export_format_signal_value_cabi::<$ty>(arg0, arg1)
}
#[export_name = "cabi_post_component:decoder/decoder#format-signal-value"]
unsafe extern "C" fn _post_return_format_signal_value(arg0: *mut u8,) {
$($path_to_types)*::__post_return_format_signal_value::<$ty>(arg0)
}
};);
}
#[doc(hidden)]
pub(crate) use __export_component_decoder_decoder_cabi;
#[repr(align(4))]
struct _RetArea([::core::mem::MaybeUninit<u8>; 8]);
static mut _RET_AREA: _RetArea = _RetArea([::core::mem::MaybeUninit::uninit(); 8]);
}
}
}
}
mod _rt {
#[cfg(target_arch = "wasm32")]
pub fn run_ctors_once() {
wit_bindgen_rt::run_ctors_once();
}
pub unsafe fn cabi_dealloc(ptr: *mut u8, size: usize, align: usize) {
if size == 0 {
return;
}
let layout = alloc::Layout::from_size_align_unchecked(size, align);
alloc::dealloc(ptr as *mut u8, layout);
}
pub use alloc_crate::string::String;
pub use alloc_crate::vec::Vec;
pub unsafe fn string_lift(bytes: Vec<u8>) -> String {
if cfg!(debug_assertions) {
String::from_utf8(bytes).unwrap()
} else {
String::from_utf8_unchecked(bytes)
}
}
pub use alloc_crate::alloc;
extern crate alloc as alloc_crate;
}
/// Generates `#[no_mangle]` functions to export the specified type as the
/// root implementation of all generated traits.
///
/// For more information see the documentation of `wit_bindgen::generate!`.
///
/// ```rust
/// # macro_rules! export{ ($($t:tt)*) => (); }
/// # trait Guest {}
/// struct MyType;
///
/// impl Guest for MyType {
/// // ...
/// }
///
/// export!(MyType);
/// ```
#[allow(unused_macros)]
#[doc(hidden)]
macro_rules! __export_component_impl {
($ty:ident) => (self::export!($ty with_types_in self););
($ty:ident with_types_in $($path_to_types_root:tt)*) => (
$($path_to_types_root)*::exports::component::decoder::decoder::__export_component_decoder_decoder_cabi!($ty with_types_in $($path_to_types_root)*::exports::component::decoder::decoder);
)
}
#[doc(inline)]
pub(crate) use __export_component_impl as export;
#[cfg(target_arch = "wasm32")]
#[link_section = "component-type:wit-bindgen:0.25.0:component:encoded world"]
#[doc(hidden)]
pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 315] = *b"\
\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xbb\x01\x01A\x02\x01\
A\x04\x01B\x02\x01@\x01\x07messages\x01\0\x04\0\x03log\x01\0\x03\x01\x16componen\
t:decoder/host\x05\0\x01B\x06\x01@\0\x01\0\x04\0\x04init\x01\0\x01@\0\0s\x04\0\x04\
name\x01\x01\x01@\x01\x05values\0s\x04\0\x13format-signal-value\x01\x02\x04\x01\x19\
component:decoder/decoder\x05\x01\x04\x01\x1bcomponent:decoder/component\x04\0\x0b\
\x0f\x01\0\x09component\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0dwit-c\
omponent\x070.208.1\x10wit-bindgen-rust\x060.25.0";
#[inline(never)]
#[doc(hidden)]
#[cfg(target_arch = "wasm32")]
pub fn __link_custom_section_describing_imports() {
wit_bindgen_rt::maybe_link_cabi_realloc();
}

View file

@ -0,0 +1,30 @@
#[allow(warnings)]
mod bindings;
use bindings::component::decoder::host;
use bindings::exports::component::decoder::decoder;
macro_rules! log {
($($arg:tt)*) => (host::log(&format!($($arg)*)))
}
static NAME: &str = "Rust Test Decoder";
struct Component;
impl decoder::Guest for Component {
fn init() {
log!("'{NAME}' initialized")
}
fn name() -> String {
NAME.to_string()
}
fn format_signal_value(mut value: String) -> String {
value.push('!');
value
}
}
bindings::export!(Component with_types_in bindings);

View file

@ -0,0 +1,16 @@
package component:decoder;
interface host {
log: func(message: string);
}
interface decoder {
init: func();
name: func() -> string;
format-signal-value: func(value: string) -> string;
}
world component {
import host;
export decoder;
}