Compare commits

...
This repository has been archived on 2025-05-11. You can view files and clone it, but cannot push or open issues or pull requests.

13 commits
konata ... main

Author SHA1 Message Date
Yehowshua Immanuel 6b6af9e826
Merge pull request #19 from JoyOfHardware/add_term_support
Add term support
2024-12-30 18:10:28 -05:00
Yehowshua Immanuel b97f105408 removing last println 2024-12-27 10:19:43 -05:00
Yehowshua Immanuel e4ac219bf9 addressing Martin's corrections 2024-12-27 10:19:09 -05:00
Yehowshua Immanuel fbe0a4f554 term now working 2024-12-24 19:31:46 -05:00
Yehowshua Immanuel 4fd4b6eb17 basic terminal functionality works 2024-12-24 19:07:11 -05:00
Yehowshua Immanuel e08c673e86 now sending chars yay 2024-12-24 18:34:47 -05:00
Yehowshua Immanuel f992a24719 at least we're now sending a character 2024-12-24 18:24:55 -05:00
Yehowshua Immanuel 24710414bd getting towards terminal integration 2024-12-23 15:22:17 -05:00
Martin Kavík 4c00a633af update Tauri deps 2024-12-04 15:15:43 +01:00
Martin Kavík 6500531270 video_konata/diagram_connector.gif, README update 2024-11-29 15:50:24 +01:00
Martin Kavík fdaacaa6f3 Konata fix for Unix 2024-11-29 15:50:24 +01:00
Martin Kavík ac484fdea8 Konata app integration 2024-11-29 15:50:24 +01:00
Martin Kavík 01a9501f86 diagram plugins 2024-11-29 15:50:24 +01:00
44 changed files with 3600 additions and 960 deletions

1863
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

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

View file

@ -165,7 +165,7 @@ description = "Install Tauri CLI (tauri) locally"
command = "cargo"
args = [
"install",
"tauri-cli@=2.0.0-beta.17",
"tauri-cli@=2.1.0",
"--locked",
"--root",
"tauri",

View file

@ -69,6 +69,16 @@
<img width="800" src="docs/video_diagrams.gif" alt="Fastwave - Diagrams" />
</p>
<p align="center">Diagram Connector Demo</p>
<p align="center">
<img width="800" src="docs/video_diagram_connector.gif" alt="Fastwave - Diagram Connector demo" />
</p>
<p align="center">Diagram Connector Code snippet</p>
<p align="center">
<img width="500" src="docs/screenshot_diagram_connector_rs.png" alt="Fastwave - Diagram Connector Code snippet" />
</p>
---
### Installation (desktop version):

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
docs/video_konata.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View file

@ -14,6 +14,7 @@ wasm-bindgen-test = "0.3.19"
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(FASTWAVE_PLATFORM)'] }
[dependencies]
unicode-segmentation = "1.10"
zoon.workspace = true
wellen.workspace = true
shared = { path = "../shared", features = ["frontend"] }

View file

@ -76,5 +76,15 @@ mod js_bridge {
#[wasm_bindgen(method)]
pub fn draw_diagram_element(this: &ExcalidrawController, excalidraw_element: JsValue);
#[wasm_bindgen(method)]
pub fn listen_for_component_text_changes(
this: &ExcalidrawController,
component_id: &str,
on_change: &Closure<dyn Fn(String)>,
);
#[wasm_bindgen(method)]
pub fn set_component_text(this: &ExcalidrawController, component_id: &str, text: &str);
}
}

View file

@ -1,6 +1,7 @@
use crate::{platform, theme::*, Filename, Layout, Mode};
use std::sync::Arc;
use zoon::*;
use crate::term::TERM_OPEN;
pub struct HeaderPanel {
hierarchy: Mutable<Option<Arc<wellen::Hierarchy>>>,
@ -36,7 +37,9 @@ impl HeaderPanel {
.s(Gap::both(15))
.item(self.load_button())
.item(self.layout_switcher())
.item(self.mode_switcher()),
.item(self.mode_switcher())
.item(self.open_terminal())
.item(self.open_konata_file()),
)
}
@ -193,4 +196,44 @@ impl HeaderPanel {
})
})
}
fn open_konata_file(&self) -> impl Element {
let (hovered, hovered_signal) = Mutable::new_and_signal(false);
Button::new()
.s(Padding::new().x(20).y(10))
.s(Background::new().color_signal(
hovered_signal.map_bool(|| COLOR_MEDIUM_SLATE_BLUE, || COLOR_SLATE_BLUE),
))
.s(Align::new().left())
.s(RoundedCorners::all(15))
.label(
El::new()
.s(Font::new().no_wrap())
.child("Open Konata file.."),
)
.on_hovered_change(move |is_hovered| hovered.set_neq(is_hovered))
.on_press(move || Task::start(platform::open_konata_file()))
}
fn open_terminal(&self) -> impl Element {
let (hovered, hovered_signal) = Mutable::new_and_signal(false);
Button::new()
.s(Padding::new().x(20).y(10))
.s(Background::new().color_signal(
hovered_signal.map_bool(|| COLOR_MEDIUM_SLATE_BLUE, || COLOR_SLATE_BLUE),
))
.s(Align::new().left())
.s(RoundedCorners::all(15))
.label(
El::new()
.s(Font::new().no_wrap())
.child("Open Terminal"),
)
.on_hovered_change(move |is_hovered| hovered.set_neq(is_hovered))
.on_press(move || {
let term_open = TERM_OPEN.get();
TERM_OPEN.set(!term_open);
})
}
}

View file

@ -1,4 +1,6 @@
use std::sync::Arc;
use shared::DiagramConnectorMessage;
use term::TERM_OPEN;
use std::{mem, sync::Arc};
use zoon::*;
mod platform;
@ -22,6 +24,9 @@ use command_panel::CommandPanel;
pub mod theme;
use theme::*;
pub mod term;
use shared::term::{TerminalDownMsg, TerminalScreen};
#[derive(Clone, Copy, Default)]
enum Layout {
Tree,
@ -53,11 +58,55 @@ static STORE: Lazy<Store> = lazy::default();
fn main() {
start_app("app", root);
Task::start(async {
// https://github.com/tauri-apps/tauri/issues/5170
Timer::sleep(100).await;
platform::show_window().await;
});
Task::start(async {
platform::listen_diagram_connectors_messages(|message| {
match message {
DiagramConnectorMessage::ListenForComponentTextChanges {
diagram_connector_name,
component_id,
} => {
let closure = Closure::new({
// @TODO Rcs/Arcs?
let diagram_connector_name = diagram_connector_name.clone();
let component_id = component_id.clone();
move |text| {
Task::start(platform::notify_diagram_connector_text_change(
diagram_connector_name.clone(),
component_id.clone(),
text,
));
}
});
STORE
.excalidraw_canvas_controller
.lock_ref()
.lock_ref()
.as_ref()
.unwrap_throw()
.listen_for_component_text_changes(&component_id, &closure);
// @TODO don't forget
mem::forget(closure);
}
DiagramConnectorMessage::SetComponentText { component_id, text } => STORE
.excalidraw_canvas_controller
.lock_ref()
.lock_ref()
.as_ref()
.unwrap_throw()
.set_component_text(&component_id, &text),
}
}).await;
platform::listen_term_update(|down_msg| {
term::TERMINAL_STATE.set(down_msg);
}).await;
});
}
fn root() -> impl Element {
@ -138,4 +187,20 @@ fn root() -> impl Element {
}
})))
.item(CommandPanel::new())
.item_signal(
TERM_OPEN.signal_cloned().map(
|term_open| {
match term_open {
true =>
El::new()
.s(Height::fill().max(400).min(400))
.s(Padding::all(5))
.child(term::root()),
false =>
El::new()
.child("")
}
}
)
)
}

View file

@ -4,6 +4,7 @@
// NOTE: `FASTWAVE_PLATFORM` is set in `Makefile.toml` tasks and then in `build.rs`
use crate::STORE;
use shared::DiagramConnectorMessage;
#[cfg(FASTWAVE_PLATFORM = "TAURI")]
mod tauri;
@ -17,10 +18,19 @@ use browser as platform;
type Filename = String;
type JavascriptCode = String;
type AddedDecodersCount = usize;
type RemovedDecodersCount = usize;
type DecoderPath = String;
type AddedDiagramConnectorsCount = usize;
type RemovedDiagramConnectorsCount = usize;
type DiagramConnectorPath = String;
type DiagramConnectorName = String;
type ComponentId = String;
use shared::term::{TerminalDownMsg, TerminalScreen};
pub async fn show_window() {
platform::show_window().await
}
@ -64,6 +74,10 @@ pub async fn unload_signal(signal_ref: wellen::SignalRef) {
platform::unload_signal(signal_ref).await
}
pub async fn send_char(c : String) {
platform::send_char(c).await
}
pub async fn add_decoders(decoder_paths: Vec<DecoderPath>) -> AddedDecodersCount {
let count = platform::add_decoders(decoder_paths).await;
if count > 0 {
@ -85,3 +99,39 @@ async fn redraw_all_timeline_rows() {
controller.redraw_all_rows().await
}
}
pub async fn add_diagram_connectors(
diagram_connector_paths: Vec<DecoderPath>,
) -> AddedDecodersCount {
let count = platform::add_diagram_connectors(diagram_connector_paths).await;
count
}
pub async fn remove_all_diagram_connectors() -> RemovedDecodersCount {
let count = platform::remove_all_diagram_connectors().await;
count
}
pub async fn listen_diagram_connectors_messages(
on_message: impl FnMut(DiagramConnectorMessage) + 'static,
) {
platform::listen_diagram_connectors_messages(on_message).await;
}
pub async fn listen_term_update(
on_message: impl FnMut(TerminalDownMsg) + 'static,
) {
platform::listen_term_update(on_message).await;
}
pub async fn notify_diagram_connector_text_change(
diagram_connector: DiagramConnectorName,
component_id: ComponentId,
text: String,
) {
platform::notify_diagram_connector_text_change(diagram_connector, component_id, text).await;
}
pub async fn open_konata_file() {
platform::open_konata_file().await;
}

View file

@ -136,3 +136,38 @@ pub(super) async fn remove_all_decoders() -> super::RemovedDecodersCount {
eprintln!("Removing decoders is not supported in the browser.");
0
}
pub(super) async fn add_diagram_connectors(
diagram_connector_paths: Vec<super::DecoderPath>,
) -> super::AddedDecodersCount {
// @TODO error message for user
eprintln!("Adding diagram connectors is not supported in the browser.");
0
}
pub(super) async fn remove_all_diagram_connectors() -> super::RemovedDiagramConnectorsCount {
// @TODO error message for user
eprintln!("Removing diagram connectors is not supported in the browser.");
0
}
pub async fn listen_diagram_connectors_messages(
on_message: impl FnMut(DiagramConnectorMessage) + 'static,
) {
// @TODO error message for user
eprintln!("Removing listen for diagram connectors messages is not supported in the browser.");
}
pub async fn notify_diagram_connector_text_change(
diagram_connector: DiagramConnectorName,
component_id: ComponentId,
text: String,
) {
// @TODO error message for user
eprintln!("Diagram connectors notifications are not supported in the browser.");
}
pub async fn open_konata_file() {
// @TODO error message for user
eprintln!("Opening Konata files is not supported in the browser.");
}

View file

@ -1,3 +1,5 @@
use shared::DiagramConnectorMessage;
use shared::term::{TerminalDownMsg, TerminalScreen};
use zoon::*;
pub(super) async fn show_window() {
@ -56,6 +58,12 @@ pub(super) async fn unload_signal(signal_ref: wellen::SignalRef) {
.unwrap_throw()
}
pub(super) async fn send_char(c : String) {
tauri_glue::send_char(c)
.await
.unwrap_throw()
}
pub(super) async fn add_decoders(
decoder_paths: Vec<super::DecoderPath>,
) -> super::AddedDecodersCount {
@ -68,6 +76,56 @@ pub(super) async fn remove_all_decoders() -> super::RemovedDecodersCount {
.unwrap_throw()
}
pub(super) async fn add_diagram_connectors(
diagram_connector_paths: Vec<super::DecoderPath>,
) -> super::AddedDiagramConnectorsCount {
serde_wasm_bindgen::from_value(
tauri_glue::add_diagram_connectors(diagram_connector_paths)
.await
.unwrap_throw(),
)
.unwrap_throw()
}
pub(super) async fn remove_all_diagram_connectors() -> super::RemovedDiagramConnectorsCount {
serde_wasm_bindgen::from_value(
tauri_glue::remove_all_diagram_connectors()
.await
.unwrap_throw(),
)
.unwrap_throw()
}
pub(super) async fn listen_diagram_connectors_messages(
mut on_message: impl FnMut(DiagramConnectorMessage) + 'static,
) {
let on_message =
move |message: JsValue| on_message(serde_wasm_bindgen::from_value(message).unwrap_throw());
tauri_glue::listen_diagram_connectors_messages(Closure::new(on_message).into_js_value()).await
}
pub(super) async fn listen_term_update(
mut on_message: impl FnMut(TerminalDownMsg) + 'static,
) {
let on_message =
move |message: JsValue| on_message(serde_wasm_bindgen::from_value(message).unwrap_throw());
tauri_glue::listen_term_update(Closure::new(on_message).into_js_value()).await
}
pub(super) async fn notify_diagram_connector_text_change(
diagram_connector: super::DiagramConnectorName,
component_id: super::ComponentId,
text: String,
) {
tauri_glue::notify_diagram_connector_text_change(diagram_connector, component_id, text)
.await
.unwrap_throw();
}
pub(super) async fn open_konata_file() {
tauri_glue::open_konata_file().await;
}
mod tauri_glue {
use zoon::*;
@ -99,6 +157,9 @@ mod tauri_glue {
#[wasm_bindgen(catch)]
pub async fn unload_signal(signal_ref_index: usize) -> Result<(), JsValue>;
#[wasm_bindgen(catch)]
pub async fn send_char(c : String) -> Result<(), JsValue>;
#[wasm_bindgen(catch)]
pub async fn add_decoders(
decoder_paths: Vec<super::super::DecoderPath>,
@ -106,5 +167,26 @@ mod tauri_glue {
#[wasm_bindgen(catch)]
pub async fn remove_all_decoders() -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
pub async fn add_diagram_connectors(
diagram_connector_paths: Vec<super::super::DiagramConnectorPath>,
) -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
pub async fn remove_all_diagram_connectors() -> Result<JsValue, JsValue>;
pub async fn listen_diagram_connectors_messages(on_event: JsValue);
pub async fn listen_term_update(on_event: JsValue);
#[wasm_bindgen(catch)]
pub async fn notify_diagram_connector_text_change(
diagram_connector: super::super::DiagramConnectorName,
component_id: super::super::ComponentId,
text: String,
) -> Result<(), JsValue>;
pub async fn open_konata_file();
}
}

View file

@ -3,10 +3,15 @@ use wellen::GetItem;
use zoon::*;
type FullVarName = String;
type AddedDecodersCount = usize;
type RemovedDecodersCount = usize;
type DecoderPath = String;
type AddedDiagramConnectorsCount = usize;
type RemovedDiagramConnectorsCount = usize;
type DiagramConnectorPath = String;
#[wasm_bindgen(module = "/typescript/bundles/strict_eval.js")]
extern "C" {
#[wasm_bindgen(catch)]
@ -91,4 +96,16 @@ impl FW {
controller.draw_diagram_element(excalidraw_element)
}
}
/// JS: `FW.add_diagram_connectors(["../test_files/components/rust_diagram_connector/rust_diagram_connector.wasm"])` -> `1`
pub async fn add_diagram_connectors(
connector_paths: Vec<DiagramConnectorPath>,
) -> AddedDiagramConnectorsCount {
platform::add_diagram_connectors(connector_paths).await
}
/// JS: `FW.remove_all_diagram_connectors()` -> `5`
pub async fn remove_all_diagram_connectors() -> RemovedDiagramConnectorsCount {
platform::remove_all_diagram_connectors().await
}
}

142
frontend/src/term.rs Normal file
View file

@ -0,0 +1,142 @@
use std::ops::Index;
use chrono::format;
use zoon::*;
use zoon::{println, eprintln, *};
use shared::term::{TerminalDownMsg, TerminalScreen, TerminalUpMsg};
use unicode_segmentation::UnicodeSegmentation;
// use tokio::time::timeout;
pub static TERM_OPEN: Lazy<Mutable<bool>> = Lazy::new(|| {false.into()});
pub const TERMINAL_COLOR: Oklch = color!("oklch(20% 0.125 262.26)");
pub static TERMINAL_STATE: Lazy<Mutable<TerminalDownMsg>> =
Lazy::new(|| {
Mutable::new(TerminalDownMsg::TermNotStarted)
});
pub fn root() -> impl Element {
let terminal =
El::new()
.s(Width::fill())
.s(Height::fill())
.s(Background::new().color(TERMINAL_COLOR))
.s(RoundedCorners::all(7))
.s(Font::new().family([
FontFamily::new("Lucida Console"),
FontFamily::new("Courier"),
FontFamily::new("monospace")
]))
.update_raw_el(|raw_el| {
raw_el.global_event_handler(|event: events::KeyDown| {
send_char(
(&event).key().as_str(),
(&event).ctrl_key(),
);
})
})
.child_signal(TERMINAL_STATE.signal_cloned().map(
|down_msg| {
match down_msg {
TerminalDownMsg::FullTermUpdate(term) => {
make_grid_with_newlines(&term)
},
TerminalDownMsg::TermNotStarted => {
"Term not yet started!".to_string()
},
TerminalDownMsg::BackendTermStartFailure(msg) => {
format!("Error: BackendTermStartFailure: {}", msg)
}
}
}
)
)
;
let root = Column::new()
.s(Width::fill())
.s(Height::fill())
.s(Align::new().top())
.item(terminal);
root
}
fn send_char(
s : &str,
has_control : bool,
) {
match process_str(s, has_control) {
Some(c) => {
let send_c = c.clone();
Task::start(async move {
crate::platform::send_char(send_c.to_string()).await;
});
}
None => {}
}
}
fn make_grid_with_newlines(term: &TerminalScreen) -> String {
let mut formatted = String::with_capacity(term.content.len() + (term.content.len() / term.cols as usize));
term.content.chars().enumerate().for_each(|(i, c)| {
formatted.push(c);
if (i + 1) as u16 % term.cols == 0 {
formatted.push('\n');
}
});
formatted
}
fn process_str(s: &str, has_ctrl: bool) -> Option<char> {
match s {
"Enter" => {return Some('\n');}
"Escape" => {return Some('\x1B');}
"Backspace" => {return Some('\x08');}
"ArrowUp" => {return Some('\x10');}
"ArrowDown" => {return Some('\x0E');}
"ArrowLeft" => {return Some('\x02');}
"ArrowRight" => {return Some('\x06');}
"Control" => {return None;}
"Shift" => {return None;}
"Meta" => {return None;}
"Alt" => {return None;}
_ => {}
}
let mut graphemes = s.graphemes(true);
let first = graphemes.next();
if let Some(g) = first {
if g.len() == 1 {
if let Some(c) = g.chars().next() {
let c = process_for_ctrl_char(c, has_ctrl);
return Some(c);
}
}
}
None
}
// Helper function to process control characters
fn is_lowercase_alpha(c: char) -> bool {
char_is_between_inclusive(c, 'a', 'z')
}
fn process_for_ctrl_char(c: char, has_ctrl: bool) -> char {
if has_ctrl {
(c as u8 & 0x1F) as char
} else {
c
}
}
fn char_is_between_inclusive(c : char, lo_char : char, hi_char : char) -> bool {
c >= lo_char && c <= hi_char
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -7,17 +7,64 @@ import * as React from 'react'
import * as ReactDOM from 'react-dom/client'
export class ExcalidrawController {
api: ExcalidrawImperativeAPI | undefined
api: Promise<ExcalidrawImperativeAPI>
resolve_api: (api: ExcalidrawImperativeAPI) => void
constructor() {}
constructor() {
this.resolve_api = (api) => {};
this.api = new Promise(resolve => {
this.resolve_api = (api) => resolve(api)
});
}
draw_diagram_element(excalidraw_element: ExcalidrawElement) {
if (typeof this.api !== 'undefined') {
const elements = this.api.getSceneElements()
this.api.updateScene({
this.api.then(api => {
const elements = api.getSceneElements()
api.updateScene({
elements: elements.concat(excalidraw_element)
})
}
});
}
listen_for_component_text_changes(id: string, on_change: (text: string) => void) {
this.api.then(api => {
let old_text: string | null = null;
api.onChange((elements: readonly ExcalidrawElement[]) => {
const element = elements.find(element => element.id === id);
if (typeof element !== 'undefined') {
if (element.type === 'text') {
if (old_text === null) {
old_text = element.text;
on_change(old_text);
} else {
if (old_text !== element.text) {
old_text = element.text;
on_change(old_text);
}
}
}
}
})
})
}
set_component_text(id: string, text: string) {
this.api.then(api => {
let element_found = false;
const elements = api.getSceneElements().map(element => {
if (element.id === id) {
element_found = true;
return { ...element, text: text, originalText: text }
} else {
return element
}
});
if (element_found) {
api.updateScene({
elements
})
}
})
}
async init(parent_element: HTMLElement) {
@ -37,7 +84,7 @@ export class ExcalidrawController {
// Font family: Code
currentItemFontFamily: 3,
}}}
excalidrawAPI={(api) => this.api = api}
excalidrawAPI={(api) => this.resolve_api(api)}
>
<MainMenu>
<MainMenu.DefaultItems.LoadScene />

View file

@ -1,18 +1,26 @@
// @TODO use TS and Tauri bindgens to make this code properly typed
import { core } from '@tauri-apps/api'
import { core, event } from '@tauri-apps/api'
const invoke = core.invoke;
const listen = event.listen;
type Filename = string;
type JavascriptCode = string;
type WellenHierarchy = unknown;
type Timeline = unknown;
type VarFormat = unknown;
type AddedDecodersCount = number;
type RemovedDecodersCount = number;
type DecoderPath = string;
type AddedDiagramConnectorsCount = number;
type RemovedDiagramConnectorsCount = number;
type DiagramConnectorPath = string;
type DiagramConnectorName = string;
type ComponentId = string;
export async function show_window(): Promise<void> {
return await invoke("show_window");
}
@ -30,20 +38,20 @@ export async function get_hierarchy(): Promise<WellenHierarchy> {
}
export async function load_signal_and_get_timeline(
signal_ref_index: number,
signal_ref_index: number,
timeline_zoom: number,
timeline_viewport_width: number,
timeline_viewport_x: number,
timeline_viewport_x: number,
block_height: number,
var_format: VarFormat,
): Promise<Timeline> {
return await invoke("load_signal_and_get_timeline", {
signal_ref_index,
timeline_zoom,
return await invoke("load_signal_and_get_timeline", {
signal_ref_index,
timeline_zoom,
timeline_viewport_width,
timeline_viewport_x,
block_height,
var_format
block_height,
var_format
});
}
@ -51,6 +59,10 @@ export async function unload_signal(signal_ref_index: number): Promise<void> {
return await invoke("unload_signal", { signal_ref_index });
}
export async function send_char(c : string): Promise<void> {
return await invoke("send_char", { c });
}
export async function add_decoders(decoder_paths: Array<DecoderPath>): Promise<AddedDecodersCount> {
return await invoke("add_decoders", { decoder_paths });
}
@ -58,3 +70,27 @@ export async function add_decoders(decoder_paths: Array<DecoderPath>): Promise<A
export async function remove_all_decoders(): Promise<RemovedDecodersCount> {
return await invoke("remove_all_decoders");
}
export async function add_diagram_connectors(diagram_connector_paths: Array<DiagramConnectorPath>): Promise<AddedDiagramConnectorsCount> {
return await invoke("add_diagram_connectors", { diagram_connector_paths });
}
export async function remove_all_diagram_connectors(): Promise<RemovedDiagramConnectorsCount> {
return await invoke("remove_all_diagram_connectors");
}
export async function listen_diagram_connectors_messages(on_message: (message: any) => void) {
return await listen("diagram_connector_message", (message) => on_message(message.payload));
}
export async function listen_term_update(on_message: (message: any) => void) {
return await listen("term_content", (message) => on_message(message.payload));
}
export async function notify_diagram_connector_text_change(diagram_connector: DiagramConnectorName, component_id: ComponentId, text: string): Promise<void> {
return await invoke("notify_diagram_connector_text_change", { diagram_connector, component_id, text });
}
export async function open_konata_file() {
return await invoke("open_konata_file");
}

View file

@ -7,6 +7,7 @@ mod signal_to_timeline;
pub use signal_to_timeline::signal_to_timeline;
pub mod wellen_helpers;
pub mod term;
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(crate = "serde")]
@ -30,3 +31,16 @@ pub struct TimeLineBlockLabel {
pub x: u32,
pub y: u32,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(crate = "serde")]
pub enum DiagramConnectorMessage {
ListenForComponentTextChanges {
diagram_connector_name: String,
component_id: String,
},
SetComponentText {
component_id: String,
text: String,
},
}

25
shared/src/term.rs Normal file
View file

@ -0,0 +1,25 @@
use moonlight::*;
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(crate = "serde")]
pub enum TerminalUpMsg {
RequestFullTermState,
RequestIncrementalTermStateUpdate,
SendCharacter(char),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(crate = "serde")]
pub enum TerminalDownMsg {
FullTermUpdate(TerminalScreen),
BackendTermStartFailure(String),
TermNotStarted
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(crate = "serde")]
pub struct TerminalScreen {
pub cols : u16,
pub rows : u16,
pub content : String,
}

View file

@ -13,18 +13,21 @@ name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "=2.0.0-beta.17", features = [] }
tauri-build = { version = "2.0.3", features = [] }
[dependencies]
wellen.workspace = true
alacritty_terminal = { git = "https://github.com/alacritty/alacritty", rev = "cacdb5bb3b72bad2c729227537979d95af75978f" }
shared = { path = "../shared", features = ["backend"] }
serde_json = "1.0"
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"
tauri = { version = "2.1.1", features = ["macos-private-api"] }
tauri-plugin-window-state = "2.0.2"
tauri-plugin-dialog = "2.0.4"
once_cell = "1.19.0"
futures = "0.3.30"
reqwest = "0.12.9"
tokio = "*"
# wasmtime = "22.0.0"
# wasmtime-wasi = "22.0.0"

View file

@ -4,14 +4,6 @@
"description": "enables the default permissions",
"windows": ["main"],
"permissions": [
"path:default",
"event:default",
"window:default",
"webview:default",
"app:default",
"resources:default",
"image:default",
"menu:default",
"tray:default"
"core:default"
]
}

2
src-tauri/f.txt Normal file
View file

@ -0,0 +1,2 @@
Hello, I'm typing a file in vim!!

116
src-tauri/src/aterm.rs Normal file
View file

@ -0,0 +1,116 @@
use std::result;
use std::sync::{mpsc, Arc};
use alacritty_terminal::event::{Event, EventListener};
use alacritty_terminal::event_loop::{EventLoop, Notifier};
use alacritty_terminal::sync::FairMutex;
use alacritty_terminal::term::{self, Term};
use alacritty_terminal::term::cell::Cell;
use alacritty_terminal::{tty, Grid};
use tauri::Emitter;
use shared::term::{TerminalDownMsg, TerminalScreen};
use crate::terminal_size;
#[derive(Clone)]
pub struct EventProxy(mpsc::Sender<Event>);
impl EventListener for EventProxy {
fn send_event(&self, event: Event) {
let _ = self.0.send(event.clone());
}
}
pub struct ATerm {
pub term: Arc<FairMutex<Term<EventProxy>>>,
pub rows : u16,
pub cols : u16,
/// Use tx to write things to terminal instance from outside world
pub tx: Notifier,
/// Use rx to read things from terminal instance.
/// Rx only has data when terminal state has changed,
/// otherwise, `std::sync::mpsc::recv` will block and sleep
/// until there is data.
pub rx: mpsc::Receiver<(u64, Event)>,
}
impl ATerm {
pub fn new() -> result::Result<ATerm, std::io::Error> {
let (rows, cols) = (21, 85);
let id = 1;
let pty_config = tty::Options {
shell: Some(tty::Shell::new("/bin/bash".to_string(), vec![])),
..tty::Options::default()
};
let config = term::Config::default();
let terminal_size = terminal_size::TerminalSize::new(rows, cols);
let pty = tty::new(&pty_config, terminal_size.into(), id)?;
let (event_sender, event_receiver) = mpsc::channel();
let event_proxy = EventProxy(event_sender);
let term = Term::new::<terminal_size::TerminalSize>(
config,
&terminal_size.into(),
event_proxy.clone(),
);
let term = Arc::new(FairMutex::new(term));
let pty_event_loop = EventLoop::new(term.clone(), event_proxy, pty, false, false)?;
let notifier = Notifier(pty_event_loop.channel());
let (pty_proxy_sender, pty_proxy_receiver) = std::sync::mpsc::channel();
// Start pty event loop
pty_event_loop.spawn();
std::thread::Builder::new()
.name(format!("pty_event_subscription_{}", id))
.spawn(move || loop {
if let Ok(event) = event_receiver.recv() {
if let Event::Exit = event {
break;
}
else {
if let Some(app_handle) = crate::APP_HANDLE.read().unwrap().clone() {
let term = crate::TERM.lock().unwrap();
let content = terminal_instance_to_string(&term);
let payload = TerminalScreen {
cols: term.cols,
rows: term.rows,
content: content
};
let payload = TerminalDownMsg::FullTermUpdate(payload);
let payload = serde_json::json!(payload);
app_handle.emit("term_content", payload).unwrap();
}
}
}
})?;
Ok(ATerm {
term,
rows,
cols,
tx: notifier,
rx: pty_proxy_receiver,
})
}
}
pub fn terminal_instance_to_string(terminal_instance: &ATerm) -> String {
let (rows, cols) = (terminal_instance.rows, terminal_instance.cols);
let term = terminal_instance.term.lock();
let grid = term.grid().clone();
return term_grid_to_string(&grid, rows, cols);
}
fn term_grid_to_string(grid: &Grid<Cell>, rows: u16, cols: u16) -> String {
let mut term_content = String::with_capacity((rows*cols) as usize);
// Populate string from grid
for indexed in grid.display_iter() {
let x = indexed.point.column.0 as usize;
let y = indexed.point.line.0 as usize;
if y < rows as usize && x < cols as usize {
term_content.push(indexed.c);
}
}
return term_content;
}

View file

@ -1,125 +1,2 @@
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(())
}
pub mod decoders;
pub mod diagram_connectors;

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!(in "wit/decoder");
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

@ -0,0 +1,273 @@
use crate::{
AddedDiagramConnectorsCount, ComponentId, DiagramConnectorName, DiagramConnectorPath,
RemovedDiagramConnectorsCount, APP_HANDLE, WAVEFORM,
};
use once_cell::sync::Lazy;
use shared::{DiagramConnectorMessage, VarFormat};
use std::sync::Arc;
use tauri::async_runtime::{Mutex, RwLock};
use tauri::Emitter;
use wasmtime::component::{Component as WasmtimeComponent, *};
use wasmtime::{AsContextMut, Engine, Store};
use wasmtime_wasi::{WasiCtx, WasiView};
use wellen::GetItem;
bindgen!(in "wit/diagram_connector");
pub static DIAGRAM_CONNECTORS: 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::diagram_connector::host::Host for State {
fn log(&mut self, message: String) {
println!("Diagram Connector: {message}");
}
fn listen_for_component_text_changes(
&mut self,
diagram_connector_name: String,
component_id: String,
) {
let message = DiagramConnectorMessage::ListenForComponentTextChanges {
diagram_connector_name,
component_id,
};
APP_HANDLE
.read()
.unwrap()
.as_ref()
.unwrap()
.emit("diagram_connector_message", message)
.unwrap();
}
fn set_component_text(&mut self, component_id: String, text: String) {
let message = DiagramConnectorMessage::SetComponentText { component_id, text };
APP_HANDLE
.read()
.unwrap()
.as_ref()
.unwrap()
.emit("diagram_connector_message", message)
.unwrap();
}
// @TODO `resource` in WIT or async in the future
// @TODO move business logic to the diagram connector
fn address_and_way(&mut self, time_text: String) -> Result<(String, Option<u32>), ()> {
let input_time = time_text.parse::<u64>().map_err(|error| {
eprintln!("Failed to parse time_text '{time_text}', error: {error:#}");
})?;
let waveform_wrapper = WAVEFORM.read().unwrap();
let mut maybe_waveform = waveform_wrapper.try_write().unwrap();
let waveform = maybe_waveform.as_mut().unwrap();
let hierarchy = waveform.hierarchy();
// @TODO remove
// let timescale = hierarchy.timescale().unwrap();
// println!("Timescale: {timescale:#?}");
let refill_valid_ref = hierarchy
.lookup_var(
&["TOP", "VexiiRiscv"],
&"FetchL1Plugin_logic_refill_start_valid",
)
.unwrap();
let refill_address_ref = hierarchy
.lookup_var(
&["TOP", "VexiiRiscv"],
&"FetchL1Plugin_logic_refill_start_address",
)
.unwrap();
let refill_way_ref = hierarchy
.lookup_var(
&["TOP", "VexiiRiscv"],
&"FetchL1Plugin_logic_refill_start_wayToAllocate",
)
.unwrap();
let refill_valid_var = hierarchy.get(refill_valid_ref);
let refill_address_var = hierarchy.get(refill_address_ref);
let refill_way_var = hierarchy.get(refill_way_ref);
let refill_valid_signal_ref = refill_valid_var.signal_ref();
let refill_address_signal_ref = refill_address_var.signal_ref();
let refill_way_signal_ref = refill_way_var.signal_ref();
let mut time_table_idx = None;
for (idx, time) in waveform.time_table().iter().enumerate() {
if *time >= input_time {
time_table_idx = Some(idx as u32);
break;
}
}
let Some(time_table_idx) = time_table_idx else {
eprintln!("time_table_idx is None");
Err(())?
};
waveform.load_signals_multi_threaded(&[
refill_valid_signal_ref,
refill_address_signal_ref,
refill_way_signal_ref,
]);
let refill_valid_signal = waveform.get_signal(refill_valid_signal_ref).unwrap();
let refill_valid_offset = refill_valid_signal.get_offset(time_table_idx).unwrap();
let refill_valid_value = refill_valid_signal
.get_value_at(&refill_valid_offset, 0)
.to_string();
let refill_address_signal = waveform.get_signal(refill_address_signal_ref).unwrap();
let refill_address_offset = refill_address_signal.get_offset(time_table_idx).unwrap();
let refill_address_value = refill_address_signal.get_value_at(&refill_address_offset, 0);
let refill_address_value = VarFormat::Hexadecimal.format(refill_address_value);
if refill_valid_value == "0" {
return Ok((refill_address_value, None));
}
let refill_way_signal = waveform.get_signal(refill_way_signal_ref).unwrap();
let refill_way_offset = refill_way_signal.get_offset(time_table_idx).unwrap();
let refill_way_value = refill_way_signal.get_value_at(&refill_way_offset, 0);
let refill_way_value = VarFormat::Unsigned.format(refill_way_value);
Ok((
refill_address_value,
Some(refill_way_value.parse().unwrap()),
))
}
}
pub async fn remove_all_diagram_connectors() -> RemovedDiagramConnectorsCount {
let mut diagram_connectors = DIAGRAM_CONNECTORS.write().await;
let diagram_connectors_count = diagram_connectors.len();
diagram_connectors.clear();
diagram_connectors_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_diagram_connectors(["../test_files/components/rust_diagram_connector/rust_diagram_connector.wasm"])
//
// Remove all
// FW.remove_all_diagram_connectors()
//
// All Debug
// FW.add_diagram_connectors(["../test_files/components/rust_diagram_connector/rust_diagram_connector.wasm"])
//
// All Release
// FW.add_diagram_connectors(["../../test_files/components/rust_diagram_connector/rust_diagram_connector.wasm"])
pub async fn add_diagram_connectors(
diagram_connector_paths: Vec<DiagramConnectorPath>,
) -> AddedDiagramConnectorsCount {
println!("Diagram Connectors: {diagram_connector_paths:#?}");
println!("Current dir: {:#?}", std::env::current_dir().unwrap());
let mut added_diagram_connectors_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 diagram_connector_path in diagram_connector_paths {
if let Err(error) = add_diagram_connector(&diagram_connector_path).await {
eprintln!("add_diagram_connectors error: {error:?}");
} else {
added_diagram_connectors_count += 1;
}
}
// })
// }).join().unwrap();
added_diagram_connectors_count
}
async fn add_diagram_connector(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!(
"Diagram Connector name: {}",
component
.component_diagram_connector_diagram_connector()
.call_name(&mut store)?
);
component
.component_diagram_connector_diagram_connector()
.call_init(&mut store)?;
DIAGRAM_CONNECTORS.write().await.push(component);
Ok(())
}
// @TODO rename `ComponentId` everywhere to something like `DiagramElementId`?
// @TODO get rid of unwraps
pub async fn notify_diagram_connector_text_change(
diagram_connector: DiagramConnectorName,
component_id: ComponentId,
text: String,
) {
let mut store_lock = STORE.lock().await;
let mut store = store_lock.as_context_mut();
let diagram_connectors = DIAGRAM_CONNECTORS.read().await;
// @TODO store diagram_collectors in a hashmap/btreemap?
let diagram_connector = diagram_connectors
.iter()
.find(|diagram_collector| {
let name = diagram_collector
.component_diagram_connector_diagram_connector()
.call_name(&mut store)
.unwrap();
name == diagram_connector
})
.unwrap();
diagram_connector
.component_diagram_connector_diagram_connector()
.call_on_component_text_changed(&mut store, &component_id, &text)
.unwrap();
}

View file

@ -1,20 +1,46 @@
use once_cell::sync::Lazy;
use std::fs;
use tauri::async_runtime::RwLock;
use std::process::Command;
use std::sync::{Arc, RwLock as StdRwLock};
use std::time::Duration;
use tauri::{async_runtime::RwLock, AppHandle};
use tauri_plugin_dialog::DialogExt;
use tokio::time::sleep;
use wasmtime::AsContextMut;
use wellen::simple::Waveform;
use tauri::Emitter;
type Filename = String;
type JavascriptCode = String;
type AddedDecodersCount = usize;
type RemovedDecodersCount = usize;
type DecoderPath = String;
type AddedDiagramConnectorsCount = usize;
type RemovedDiagramConnectorsCount = usize;
type DiagramConnectorPath = String;
type DiagramConnectorName = String;
type ComponentId = String;
use alacritty_terminal::event::Notify;
use shared::term::{TerminalDownMsg, TerminalScreen};
mod component_manager;
mod aterm;
mod terminal_size;
use std::sync::Mutex;
pub static APP_HANDLE: Lazy<Arc<StdRwLock<Option<AppHandle>>>> = Lazy::new(<_>::default);
pub static WAVEFORM: Lazy<StdRwLock<Arc<RwLock<Option<Waveform>>>>> = Lazy::new(<_>::default);
static TERM: Lazy<Mutex<aterm::ATerm>> = Lazy::new(|| {
Mutex::new(aterm::ATerm::new().expect("Failed to initialize ATerm"))
});
#[derive(Default)]
struct Store {
waveform: RwLock<Option<Waveform>>,
waveform: Arc<RwLock<Option<Waveform>>>,
val : Arc<RwLock<bool>>,
}
#[tauri::command(rename_all = "snake_case")]
@ -27,26 +53,30 @@ async fn pick_and_load_waveform(
store: tauri::State<'_, Store>,
app: tauri::AppHandle,
) -> Result<Option<Filename>, ()> {
let Some(file_response) = app.dialog().file().blocking_pick_file() else {
let Some(file_path) = app.dialog().file().blocking_pick_file() else {
return Ok(None);
};
let file_path = file_response.path.as_os_str().to_str().unwrap();
let file_buf = file_path.into_path().unwrap();
let file_str = file_buf.as_os_str().to_str().unwrap();
// @TODO `read` should accept `Path` instead of `&str`
let waveform = wellen::simple::read(file_path);
let waveform = wellen::simple::read(file_str);
let Ok(waveform) = waveform else {
panic!("Waveform file reading failed")
};
*store.waveform.write().await = Some(waveform);
Ok(Some(file_response.name.unwrap()))
*WAVEFORM.write().unwrap() = Arc::clone(&store.waveform);
Ok(Some(
file_buf.file_name().unwrap().to_string_lossy().to_string(),
))
}
#[tauri::command(rename_all = "snake_case")]
async fn load_file_with_selected_vars(app: tauri::AppHandle) -> Result<Option<JavascriptCode>, ()> {
let Some(file_response) = app.dialog().file().blocking_pick_file() else {
let Some(file_path) = app.dialog().file().blocking_pick_file() else {
return Ok(None);
};
// @TODO Tokio's `fs` or a Tauri `fs`?
let Ok(javascript_code) = fs::read_to_string(file_response.path) else {
let Ok(javascript_code) = fs::read_to_string(file_path.into_path().unwrap()) else {
panic!("Selected vars file reading failed")
};
Ok(Some(javascript_code))
@ -92,8 +122,8 @@ async fn load_signal_and_get_timeline(
// @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 decoders = component_manager::decoders::DECODERS.read().await;
let mut store_lock = component_manager::decoders::STORE.lock().await;
let mut store = store_lock.as_context_mut();
for decoder in decoders.iter() {
@ -125,14 +155,132 @@ async fn unload_signal(signal_ref_index: usize, store: tauri::State<'_, Store>)
Ok(())
}
#[tauri::command(rename_all = "snake_case")]
async fn send_char(c : String) -> Result<(), ()> {
if c.len() == 1 {
let term = TERM.lock().unwrap();
term.tx.notify(c.into_bytes());
Ok(())
} else {
Err(())
}
}
#[tauri::command(rename_all = "snake_case")]
async fn add_decoders(decoder_paths: Vec<DecoderPath>) -> Result<AddedDecodersCount, ()> {
Ok(component_manager::add_decoders(decoder_paths).await)
Ok(component_manager::decoders::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)
Ok(component_manager::decoders::remove_all_decoders().await)
}
#[tauri::command(rename_all = "snake_case")]
async fn add_diagram_connectors(
diagram_connector_paths: Vec<DiagramConnectorPath>,
) -> Result<AddedDiagramConnectorsCount, ()> {
Ok(
component_manager::diagram_connectors::add_diagram_connectors(diagram_connector_paths)
.await,
)
}
#[tauri::command(rename_all = "snake_case")]
async fn remove_all_diagram_connectors() -> Result<RemovedDiagramConnectorsCount, ()> {
Ok(component_manager::diagram_connectors::remove_all_diagram_connectors().await)
}
#[tauri::command(rename_all = "snake_case")]
async fn notify_diagram_connector_text_change(
diagram_connector: DiagramConnectorName,
component_id: ComponentId,
text: String,
) -> Result<(), ()> {
Ok(
component_manager::diagram_connectors::notify_diagram_connector_text_change(
diagram_connector,
component_id,
text,
)
.await,
)
}
#[tauri::command(rename_all = "snake_case")]
async fn open_konata_file(app: tauri::AppHandle) {
let Some(file_path) = app.dialog().file().blocking_pick_file() else {
return;
};
let file_str = file_path
.into_path()
.unwrap()
.into_os_string()
.into_string()
.unwrap();
let port = 30000;
let base_url = format!("http://localhost:{port}");
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_secs(1))
.build()
.unwrap();
let mut konata_server_ready = false;
let is_konata_server_ready = || async {
client
.get(format!("{base_url}/status"))
.send()
.await
.is_ok()
};
if is_konata_server_ready().await {
konata_server_ready = true;
} else {
spawn_konata_app();
}
let mut attempts = 1;
while !konata_server_ready {
attempts += 1;
if attempts > 5 {
eprintln!("Failed to get Konata server status (5 attempts)");
return;
}
konata_server_ready = is_konata_server_ready().await;
sleep(Duration::from_secs(1)).await;
}
client
.post(format!("{base_url}/open-konata-file"))
.json(&serde_json::json!({
"file_path": file_str
}))
.send()
.await
.unwrap()
.error_for_status()
.unwrap();
}
#[cfg(target_family = "windows")]
fn spawn_konata_app() {
Command::new("cscript")
.current_dir("../../Konata")
.arg("konata.vbs")
.spawn()
.unwrap();
}
#[cfg(target_family = "unix")]
fn spawn_konata_app() {
Command::new("sh")
.current_dir("../../Konata")
.arg("konata.sh")
.spawn()
.unwrap();
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
@ -145,7 +293,7 @@ pub fn run() {
.manage(Store::default())
.plugin(tauri_plugin_window_state::Builder::default().build())
.plugin(tauri_plugin_dialog::init())
// Npte: Add all handlers to `frontend/src/tauri_bridge.rs`
// Note: Add all handlers to `frontend/src/tauri_bridge.rs`
.invoke_handler(tauri::generate_handler![
show_window,
pick_and_load_waveform,
@ -153,9 +301,39 @@ pub fn run() {
get_hierarchy,
load_signal_and_get_timeline,
unload_signal,
send_char,
add_decoders,
remove_all_decoders,
add_diagram_connectors,
remove_all_diagram_connectors,
notify_diagram_connector_text_change,
open_konata_file,
])
.setup(|app| {
*APP_HANDLE.write().unwrap() = Some(app.handle().to_owned());
println!("Setting up yay!");
std::thread::spawn(move || {
// Simulate emitting a message after a delay
std::thread::sleep(std::time::Duration::from_secs(1));
//tart term and send initial update to backend
if let Some(app_handle) = crate::APP_HANDLE.read().unwrap().clone() {
let term = crate::TERM.lock().unwrap();
let content = crate::aterm::terminal_instance_to_string(&term);
let payload = TerminalScreen {
cols: term.cols,
rows: term.rows,
content: content
};
let payload = TerminalDownMsg::FullTermUpdate(payload);
let payload = serde_json::json!(payload);
app_handle.emit("term_content", payload).unwrap();
}
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View file

@ -1,6 +1,8 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod terminal_size;
fn main() {
app_lib::run();
}

View file

@ -0,0 +1,55 @@
use alacritty_terminal::event::{WindowSize};
use alacritty_terminal::grid::{Dimensions};
use alacritty_terminal::index::{Column, Line};
#[derive(Clone, Copy, Debug)]
pub struct TerminalSize {
pub cell_width: u16,
pub cell_height: u16,
pub num_cols: u16,
pub num_lines: u16,
}
impl TerminalSize {
pub fn new(rows : u16, cols : u16) -> Self {
Self {
cell_width: 1,
cell_height: 1,
num_cols: cols,
num_lines: rows,
}
}
}
impl Dimensions for TerminalSize {
fn total_lines(&self) -> usize {
self.screen_lines()
}
fn screen_lines(&self) -> usize {
self.num_lines as usize
}
fn columns(&self) -> usize {
self.num_cols as usize
}
fn last_column(&self) -> Column {
Column(self.num_cols as usize - 1)
}
fn bottommost_line(&self) -> Line {
Line(self.num_lines as i32 - 1)
}
}
impl From<TerminalSize> for WindowSize {
fn from(size: TerminalSize) -> Self {
Self {
num_lines: size.num_lines,
num_cols: size.num_cols,
cell_width: size.cell_width,
cell_height: size.cell_height,
}
}
}

View file

@ -0,0 +1,19 @@
package component:diagram-connector;
interface host {
log: func(message: string);
listen-for-component-text-changes: func(diagram-connect-name: string, component-id: string);
set-component-text: func(component-id: string, text: string);
address-and-way: func(time-text: string) -> result<tuple<string, option<u32>>>;
}
interface diagram-connector {
init: func();
name: func() -> string;
on-component-text-changed: func(component-id: string, text: string);
}
world component {
import host;
export diagram-connector;
}

1
test_files/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
sv39_mmu_cache_sim/

View file

@ -0,0 +1,431 @@
{
"type": "excalidraw",
"version": 2,
"source": "http://localhost:8080",
"elements": [
{
"type": "text",
"version": 174,
"versionNonce": 1552336377,
"isDeleted": false,
"id": "xPhIioz8O3Vjc7QcLfYes",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"angle": 0,
"x": 60,
"y": 120,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 1153.125,
"height": 57.599999999999994,
"seed": 1317703821,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1732565113996,
"link": null,
"locked": false,
"fontSize": 16,
"fontFamily": 3,
"text": "1. Extract 'test_files/sv39_mmu_cache_sim.zip' to 'test_files/sv39_mmu_cache_sim'\n2. Load file 'test_files/sv39_mmu_cache_sim/fst/wave.fst'\n3. Run command 'FW.add_diagram_connectors([\"../test_files/components/rust_diagram_connector/rust_diagram_connector.wasm\"])'",
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "1. Extract 'test_files/sv39_mmu_cache_sim.zip' to 'test_files/sv39_mmu_cache_sim'\n2. Load file 'test_files/sv39_mmu_cache_sim/fst/wave.fst'\n3. Run command 'FW.add_diagram_connectors([\"../test_files/components/rust_diagram_connector/rust_diagram_connector.wasm\"])'",
"lineHeight": 1.2,
"baseline": 54
},
{
"type": "rectangle",
"version": 133,
"versionNonce": 631769070,
"isDeleted": false,
"id": "WORIbkpEkf5c85sK8DhU-",
"fillStyle": "solid",
"strokeWidth": 4,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"angle": 0,
"x": 460,
"y": 220,
"strokeColor": "#1e1e1e",
"backgroundColor": "#a5d8ff",
"width": 300,
"height": 440.00000000000006,
"seed": 1957721512,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [],
"updated": 1732565668461,
"link": null,
"locked": false
},
{
"type": "text",
"version": 90,
"versionNonce": 1920851711,
"isDeleted": false,
"id": "mBGA6gGm1aDhGnBNB8gPz",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"angle": 0,
"x": 541.5560073852539,
"y": 260,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 144.50794982910156,
"height": 35,
"seed": 1040757672,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1732566083274,
"link": null,
"locked": false,
"fontSize": 28,
"fontFamily": 1,
"text": "SV39 MMU",
"textAlign": "center",
"verticalAlign": "top",
"containerId": null,
"originalText": "SV39 MMU",
"lineHeight": 1.25,
"baseline": 24
},
{
"type": "text",
"version": 153,
"versionNonce": 302406585,
"isDeleted": false,
"id": "8eNVs3Eiiy6Ns1KqVK-zj",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"angle": 0,
"x": 480,
"y": 440,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 73.369140625,
"height": 23,
"seed": 1170025128,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1732565113996,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 2,
"text": "Address",
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Address",
"lineHeight": 1.15,
"baseline": 18
},
{
"type": "rectangle",
"version": 116,
"versionNonce": 57845422,
"isDeleted": false,
"id": "IhK6hwJEwdKZ9bG8aTkN7",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"angle": 0,
"x": 480,
"y": 473.5732012743411,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 260,
"height": 40,
"seed": 590079912,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "ITxhJ7NtZ74YFd9JQ0_pl"
}
],
"updated": 1732565668739,
"link": null,
"locked": false
},
{
"type": "text",
"version": 37,
"versionNonce": 761868063,
"isDeleted": false,
"id": "ITxhJ7NtZ74YFd9JQ0_pl",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"angle": 0,
"x": 486.953125,
"y": 481.5732012743411,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 246.09375,
"height": 24,
"seed": 684450162,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1732566156524,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 3,
"text": " UNKNOWN ",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "IhK6hwJEwdKZ9bG8aTkN7",
"originalText": " UNKNOWN ",
"lineHeight": 1.2,
"baseline": 19
},
{
"type": "text",
"version": 301,
"versionNonce": 873757849,
"isDeleted": false,
"id": "Lcp_kf4ZNFv0MtP5S6q91",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"angle": 0,
"x": 480,
"y": 554.9486030561815,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 56.69921875,
"height": 23,
"seed": 1685601960,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1732565113996,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 2,
"text": "Status",
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Status",
"lineHeight": 1.15,
"baseline": 18
},
{
"type": "text",
"version": 148,
"versionNonce": 586282359,
"isDeleted": false,
"id": "cHL582ZJrMvSgzJTCSVQg",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"angle": 0,
"x": 480,
"y": 320,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 83.701171875,
"height": 23,
"seed": 1428509096,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1732565113996,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 2,
"text": "Time (ps)",
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Time (ps)",
"lineHeight": 1.15,
"baseline": 18
},
{
"type": "rectangle",
"version": 160,
"versionNonce": 896237945,
"isDeleted": false,
"id": "KsSzJicTZlMzyYamex99O",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"angle": 0,
"x": 480,
"y": 352,
"strokeColor": "#1e1e1e",
"backgroundColor": "#ffffff",
"width": 260,
"height": 40,
"seed": 825642664,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "afXu8_6Kqfq-q2IsjtAcP"
}
],
"updated": 1732565113996,
"link": null,
"locked": false
},
{
"type": "text",
"version": 170,
"versionNonce": 1243754135,
"isDeleted": false,
"id": "afXu8_6Kqfq-q2IsjtAcP",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"angle": 0,
"x": 485,
"y": 360,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 46.875,
"height": 24,
"seed": 902183336,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1732565113996,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 3,
"text": "3125",
"textAlign": "left",
"verticalAlign": "middle",
"containerId": "KsSzJicTZlMzyYamex99O",
"originalText": "3125",
"lineHeight": 1.2,
"baseline": 20
},
{
"type": "rectangle",
"version": 42,
"versionNonce": 55503449,
"isDeleted": false,
"id": "4l74x5uAeTeBuxx_8t_QY",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"angle": 0,
"x": 480,
"y": 584.4962525171356,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 260,
"height": 40,
"seed": 595254712,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"boundElements": [
{
"type": "text",
"id": "0iH5yRbH4IEseV3mnof3A"
}
],
"updated": 1732565113996,
"link": null,
"locked": false
},
{
"type": "text",
"version": 321,
"versionNonce": 940641777,
"isDeleted": false,
"id": "0iH5yRbH4IEseV3mnof3A",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 0,
"opacity": 100,
"angle": 0,
"x": 486.953125,
"y": 592.4962525171356,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"width": 246.09375,
"height": 24,
"seed": 2011882973,
"groupIds": [],
"frameId": null,
"roundness": null,
"boundElements": [],
"updated": 1732566143666,
"link": null,
"locked": false,
"fontSize": 20,
"fontFamily": 3,
"text": " UNKNOWN ",
"textAlign": "center",
"verticalAlign": "middle",
"containerId": "4l74x5uAeTeBuxx_8t_QY",
"originalText": " UNKNOWN ",
"lineHeight": 1.2,
"baseline": 19
}
],
"appState": {
"gridSize": 20,
"viewBackgroundColor": "#f5faff"
},
"files": {}
}

View file

@ -0,0 +1,10 @@
{
"rust-analyzer.check.overrideCommand": [
"cargo",
"component",
"check",
"--workspace",
"--all-targets",
"--message-format=json"
],
}

View file

@ -0,0 +1,26 @@
[package]
name = "rust_diagram_connector"
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-diagram-connector"
[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_diagram_connector --lib`
3. `cd rust_diagram_connector`
4. Update code as needed
5. `cargo component build --release --target wasm32-unknown-unknown && cp ../../../target/wasm32-unknown-unknown/release/rust_diagram_connector.wasm .`

View file

@ -0,0 +1,329 @@
// Generated by `wit-bindgen` 0.25.0. DO NOT EDIT!
// Options used:
#[allow(dead_code)]
pub mod component {
#[allow(dead_code)]
pub mod diagram_connector {
#[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;
use super::super::super::_rt;
#[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:diagram-connector/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(unused_unsafe, clippy::all)]
pub fn listen_for_component_text_changes(
diagram_connect_name: &str,
component_id: &str,
) {
unsafe {
let vec0 = diagram_connect_name;
let ptr0 = vec0.as_ptr().cast::<u8>();
let len0 = vec0.len();
let vec1 = component_id;
let ptr1 = vec1.as_ptr().cast::<u8>();
let len1 = vec1.len();
#[cfg(target_arch = "wasm32")]
#[link(wasm_import_module = "component:diagram-connector/host")]
extern "C" {
#[link_name = "listen-for-component-text-changes"]
fn wit_import(_: *mut u8, _: usize, _: *mut u8, _: usize);
}
#[cfg(not(target_arch = "wasm32"))]
fn wit_import(_: *mut u8, _: usize, _: *mut u8, _: usize) {
unreachable!()
}
wit_import(ptr0.cast_mut(), len0, ptr1.cast_mut(), len1);
}
}
#[allow(unused_unsafe, clippy::all)]
pub fn set_component_text(component_id: &str, text: &str) {
unsafe {
let vec0 = component_id;
let ptr0 = vec0.as_ptr().cast::<u8>();
let len0 = vec0.len();
let vec1 = text;
let ptr1 = vec1.as_ptr().cast::<u8>();
let len1 = vec1.len();
#[cfg(target_arch = "wasm32")]
#[link(wasm_import_module = "component:diagram-connector/host")]
extern "C" {
#[link_name = "set-component-text"]
fn wit_import(_: *mut u8, _: usize, _: *mut u8, _: usize);
}
#[cfg(not(target_arch = "wasm32"))]
fn wit_import(_: *mut u8, _: usize, _: *mut u8, _: usize) {
unreachable!()
}
wit_import(ptr0.cast_mut(), len0, ptr1.cast_mut(), len1);
}
}
#[allow(unused_unsafe, clippy::all)]
pub fn address_and_way(time_text: &str) -> Result<(_rt::String, Option<u32>), ()> {
unsafe {
#[repr(align(4))]
struct RetArea([::core::mem::MaybeUninit<u8>; 20]);
let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 20]);
let vec0 = time_text;
let ptr0 = vec0.as_ptr().cast::<u8>();
let len0 = vec0.len();
let ptr1 = ret_area.0.as_mut_ptr().cast::<u8>();
#[cfg(target_arch = "wasm32")]
#[link(wasm_import_module = "component:diagram-connector/host")]
extern "C" {
#[link_name = "address-and-way"]
fn wit_import(_: *mut u8, _: usize, _: *mut u8);
}
#[cfg(not(target_arch = "wasm32"))]
fn wit_import(_: *mut u8, _: usize, _: *mut u8) {
unreachable!()
}
wit_import(ptr0.cast_mut(), len0, ptr1);
let l2 = i32::from(*ptr1.add(0).cast::<u8>());
match l2 {
0 => {
let e = {
let l3 = *ptr1.add(4).cast::<*mut u8>();
let l4 = *ptr1.add(8).cast::<usize>();
let len5 = l4;
let bytes5 = _rt::Vec::from_raw_parts(l3.cast(), len5, len5);
let l6 = i32::from(*ptr1.add(12).cast::<u8>());
(
_rt::string_lift(bytes5),
match l6 {
0 => None,
1 => {
let e = {
let l7 = *ptr1.add(16).cast::<i32>();
l7 as u32
};
Some(e)
}
_ => _rt::invalid_enum_discriminant(),
},
)
};
Ok(e)
}
1 => {
let e = ();
Err(e)
}
_ => _rt::invalid_enum_discriminant(),
}
}
}
}
}
}
#[allow(dead_code)]
pub mod exports {
#[allow(dead_code)]
pub mod component {
#[allow(dead_code)]
pub mod diagram_connector {
#[allow(dead_code, clippy::all)]
pub mod diagram_connector {
#[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_on_component_text_changed_cabi<T: Guest>(
arg0: *mut u8,
arg1: usize,
arg2: *mut u8,
arg3: usize,
) {
#[cfg(target_arch = "wasm32")]
_rt::run_ctors_once();
let len0 = arg1;
let bytes0 = _rt::Vec::from_raw_parts(arg0.cast(), len0, len0);
let len1 = arg3;
let bytes1 = _rt::Vec::from_raw_parts(arg2.cast(), len1, len1);
T::on_component_text_changed(
_rt::string_lift(bytes0),
_rt::string_lift(bytes1),
);
}
pub trait Guest {
fn init();
fn name() -> _rt::String;
fn on_component_text_changed(component_id: _rt::String, text: _rt::String);
}
#[doc(hidden)]
macro_rules! __export_component_diagram_connector_diagram_connector_cabi{
($ty:ident with_types_in $($path_to_types:tt)*) => (const _: () = {
#[export_name = "component:diagram-connector/diagram-connector#init"]
unsafe extern "C" fn export_init() {
$($path_to_types)*::_export_init_cabi::<$ty>()
}
#[export_name = "component:diagram-connector/diagram-connector#name"]
unsafe extern "C" fn export_name() -> *mut u8 {
$($path_to_types)*::_export_name_cabi::<$ty>()
}
#[export_name = "cabi_post_component:diagram-connector/diagram-connector#name"]
unsafe extern "C" fn _post_return_name(arg0: *mut u8,) {
$($path_to_types)*::__post_return_name::<$ty>(arg0)
}
#[export_name = "component:diagram-connector/diagram-connector#on-component-text-changed"]
unsafe extern "C" fn export_on_component_text_changed(arg0: *mut u8,arg1: usize,arg2: *mut u8,arg3: usize,) {
$($path_to_types)*::_export_on_component_text_changed_cabi::<$ty>(arg0, arg1, arg2, arg3)
}
};);
}
#[doc(hidden)]
pub(crate) use __export_component_diagram_connector_diagram_connector_cabi;
#[repr(align(4))]
struct _RetArea([::core::mem::MaybeUninit<u8>; 8]);
static mut _RET_AREA: _RetArea = _RetArea([::core::mem::MaybeUninit::uninit(); 8]);
}
}
}
}
mod _rt {
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 unsafe fn invalid_enum_discriminant<T>() -> T {
if cfg!(debug_assertions) {
panic!("invalid enum discriminant")
} else {
core::hint::unreachable_unchecked()
}
}
#[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);
}
extern crate alloc as alloc_crate;
pub use alloc_crate::alloc;
}
/// 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::diagram_connector::diagram_connector::__export_component_diagram_connector_diagram_connector_cabi!($ty with_types_in $($path_to_types_root)*::exports::component::diagram_connector::diagram_connector);
)
}
#[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; 550] = *b"\
\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xa6\x03\x01A\x02\x01\
A\x04\x01B\x0b\x01@\x01\x07messages\x01\0\x04\0\x03log\x01\0\x01@\x02\x14diagram\
-connect-names\x0ccomponent-ids\x01\0\x04\0!listen-for-component-text-changes\x01\
\x01\x01@\x02\x0ccomponent-ids\x04texts\x01\0\x04\0\x12set-component-text\x01\x02\
\x01ky\x01o\x02s\x03\x01j\x01\x04\0\x01@\x01\x09time-texts\0\x05\x04\0\x0faddres\
s-and-way\x01\x06\x03\x01\x20component:diagram-connector/host\x05\0\x01B\x06\x01\
@\0\x01\0\x04\0\x04init\x01\0\x01@\0\0s\x04\0\x04name\x01\x01\x01@\x02\x0ccompon\
ent-ids\x04texts\x01\0\x04\0\x19on-component-text-changed\x01\x02\x04\x01-compon\
ent:diagram-connector/diagram-connector\x05\x01\x04\x01%component:diagram-connec\
tor/component\x04\0\x0b\x0f\x01\0\x09component\x03\0\0\0G\x09producers\x01\x0cpr\
ocessed-by\x02\x0dwit-component\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,63 @@
use std::borrow::Cow;
use std::cell::RefCell;
#[allow(warnings)]
mod bindings;
use bindings::component::diagram_connector::host;
use bindings::exports::component::diagram_connector::diagram_connector;
macro_rules! log {
($($arg:tt)*) => (host::log(&format!($($arg)*)))
}
static NAME: &str = "Rust Test Diagram Connector";
// Note: Ids from `test_files/cache_diagram.excalidraw`
const ADDRESS_COMPONENT_ID: &str = "ITxhJ7NtZ74YFd9JQ0_pl";
const TIME_COMPONENT_ID: &str = "afXu8_6Kqfq-q2IsjtAcP";
const STATUS_COMPONENT_ID: &str = "0iH5yRbH4IEseV3mnof3A";
thread_local! {
static TIME_TEXT: RefCell<String> = <_>::default();
}
struct Component;
impl diagram_connector::Guest for Component {
fn init() {
host::listen_for_component_text_changes(NAME, TIME_COMPONENT_ID);
log!("'{NAME}' initialized")
}
fn name() -> String {
NAME.to_string()
}
fn on_component_text_changed(component_id: String, text: String) {
match component_id.as_str() {
TIME_COMPONENT_ID => {
TIME_TEXT.set(text);
refresh_fields();
}
_ => (),
}
}
}
fn refresh_fields() {
let way = TIME_TEXT.with_borrow(|time_text| host::address_and_way(&time_text));
let Ok((address, way)) = way else {
return;
};
host::set_component_text(ADDRESS_COMPONENT_ID, &address);
let status_text: Cow<str> = if let Some(way) = way {
format!("VALID, WAY: {way}").into()
} else {
"NOT VALID".into()
};
host::set_component_text(STATUS_COMPONENT_ID, &status_text);
}
bindings::export!(Component with_types_in bindings);

View file

@ -0,0 +1,19 @@
package component:diagram-connector;
interface host {
log: func(message: string);
listen-for-component-text-changes: func(diagram-connect-name: string, component-id: string);
set-component-text: func(component-id: string, text: string);
address-and-way: func(time-text: string) -> result<tuple<string, option<u32>>>;
}
interface diagram-connector {
init: func();
name: func() -> string;
on-component-text-changed: func(component-id: string, text: string);
}
world component {
import host;
export diagram-connector;
}

Binary file not shown.