Skip to content

Commit

Permalink
integrate Rust and file picker
Browse files Browse the repository at this point in the history
  • Loading branch information
orangecms committed Oct 29, 2023
1 parent 348c138 commit 08fdf4a
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 16 deletions.
102 changes: 102 additions & 0 deletions app/lib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
const fourU8ToU32 = (f) => (f[0] << 24) | (f[1] << 16) | (f[2] << 8) | f[3];

const u8ArrToU32Arr = (u8a) => {
let res = [];
for (let i = 0; i < u8a.length/4; i++) {
const c = u8a.slice(i*4, (i+1)*4);
res.push(fourU8ToU32(c));
}
return res;
};

const u8ArrToStr = (u8a) => u8a.reduce((a,c,i) => {
if (i === u8a.length -1) {
return a;
}
const n = (c === 0) ? ";" : String.fromCharCode(c);
return `${a}${n}`;
}, "");

// some props are simple strings
const getStringProp = (n, pname) => {
const p = n.props.find(p => p[0] === pname);
if (p) {
return u8ArrToStr(p[1]);
}
};

// many props are just numbers
const getProp = (n, pname) => {
const p = n.props.find(p => p[0] === pname);
return p ? u8ArrToU32Arr(p[1]) : null;
};

// strings representation of lists of numbers for pretty-printing
const getPropStr = (n, pname) => {
const p = getProp(n, pname);
return p ? p.join(", ") : null;
};

// transform a node's props into numbers and strings, omitting many
const transformNode = (n) => {
const name = n.name || "root";
// phandle is an identifier to the node
const phandle = getProp(n, "phandle");
// phy-handle is a ref to another node
// TODO: make list of props that are refs
const phyHandle = getProp(n, "phy-handle");
const phySupply = getProp(n, "phy-supply");
const resets = getProp(n, "resets");
const dmas = getProp(n, "dmas");
const clks = getProp(n, "clocks");
const cnames = getStringProp(n, "clock-names");
const compat = getStringProp(n, "compatible");
return {
name,
...(phandle ? { phandle: phandle[0] } : null),
...(phySupply ? { phySupply: phySupply[0] } : null),
...(phyHandle ? { phyHandle: phyHandle[0] } : null),
...(resets ? { resets } : null),
...(dmas ? { dmas } : null),
...(clks ? { clks } : null),
...(cnames ? { cnames } : null),
...(compat ? { compat } : null),
};
};

export const transform = (n, id = "10000") => {
return {
...transformNode(n),
id,
children: n.children.map((c, i) => transform(c, `${id}_${i}`)),
}
};

// flatten tree to list of nodes, use IDs to define ReactFlow edges
export const getNodesEdges = (tree) => {
const nodes = [];
const edges = [];
const rec = (n, d=1,b=1) => {
nodes.push({
id: n.id,
type: "custom",
position: {
x: d*180,
y: b*(10-d)*12 + 50*n.children.length,
},
data: {
label: n.name,
},
});
n.children.forEach((c,i) => {
edges.push({
id: `${n.id}${c.id}`,
source: n.id,
target: c.id,
});
rec(c, d+1, b+1+i);
});
};
rec(tree);
return { nodes, edges };
};
75 changes: 65 additions & 10 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,94 @@
"use client";
import Image from "next/image"
import { useCallback, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import ReactFlow, {
useNodesState,
useEdgesState,
Controls,
MiniMap,
addEdge
} from "reactflow";
import { useFilePicker } from "use-file-picker";
import styles from "./page.module.css"
// TODO: wire up; this is a fixture
import { nodes as iNodes, edges as iEdges } from "./nodes-edges.json";
import { transform, getNodesEdges } from "./lib";

export default function Home() {
const [parseRes, setParseRes] = useState("");
const [nodes, , onNodesChange] = useNodesState(iNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(iEdges);
const [fbuf, setFbuf] = useState(null);
const [inProgress, setInProgress] = useState(false);
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);

const { openFilePicker, filesContent, loading, errors, plainFiles } =
useFilePicker({
multiple: false,
readAs: "ArrayBuffer",
maxFileSize: 1, // megabytes
});

const onConnect = useCallback(
(params: any) => setEdges((eds) => addEdge(params, eds)),
[setEdges]
);

// TODO: file picker, pass file to parser
const parseDtb = async() => {
const parser = await import("../parser/pkg");
const res = await parser.parse_dtb();
setParseRes(JSON.stringify(res));
const parseDtb = async(data) => {
setInProgress(true);
setTimeout(async () => {
try {
// TODO: only do this once
const parser = await import("../parser/pkg");
const res = await parser.parse_dtb([...data]);
const tree = transform(res.root);
const f = getNodesEdges(tree);
setNodes(f.nodes);
setEdges(f.edges);
} catch (e) {
console.error(e);
// setError((errors || []).concat(e));
} finally {
console.info("DONE:", new Date());
setInProgress(false);
}
}, 100);
};

/*
*/
const reanalyze = useCallback(() => {
if (fbuf) {
parseDtb(new Uint8Array(fbuf));
}
}, [fbuf]);

useEffect(() => {
reanalyze();
}, [reanalyze]);

useEffect(() => {
if (filesContent.length) {
const f = filesContent[0].content;
setFbuf(f);
}
}, [filesContent]);

const fileName = plainFiles.length > 0 ? plainFiles[0].name : "";

const pending = loading || inProgress;

return (
<main className={styles.main}>
<h1>Device Tree Visualizer</h1>
<button onClick={parseDtb}>parse DTB</button>
Result: <pre>{parseRes}</pre>
<div style={{ width: 300, display: "flex", justifyContent: "space-between" }}>
File: {fileName}
<button disabled={pending} onClick={openFilePicker}>
{pending ? "..." : "load DTB"}
</button>
</div>
<div>
nodes: {nodes.length}
</div>
<div style={{ width: "100vw", height: "90%" }}>
<ReactFlow
{...{ nodes, edges, onNodesChange, onEdgesChange, onConnect }}>
Expand Down
10 changes: 10 additions & 0 deletions parser/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ default = ["console_error_panic_hook"]
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.7", optional = true }
device_tree = { git = "https://github.com/orangecms/device_tree-rs", tag = "v2.0.0", package = "psi_device_tree" }
gloo-utils = { version = "0.2.0", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
wasm-bindgen = "0.2"
Expand Down
6 changes: 6 additions & 0 deletions parser/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[toolchain]
channel = "nightly-2023-10-13"
components = ["rust-src", "llvm-tools", "rustfmt", "clippy"]
targets = [
"wasm32-unknown-unknown",
]
16 changes: 10 additions & 6 deletions parser/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
mod utils;

use device_tree::DeviceTree;
use gloo_utils::format::JsValueSerdeExt;
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;

// TODO: add actual parser :p
mod utils;

type Bin = Vec<u8>;

#[derive(Serialize, Deserialize)]
struct Res {
b: u32,
}

#[wasm_bindgen]
pub fn parse_dtb() -> Result<JsValue, JsValue> {
pub fn parse_dtb(data: JsValue) -> js_sys::Promise {
utils::set_panic_hook();

let x = Res { b: 123 };
Ok(JsValue::from_serde(&x).unwrap())
let bin: Bin = data.into_serde().unwrap();

let res = DeviceTree::load(&bin.as_slice()).unwrap();
let res_val = JsValue::from_serde(&res).unwrap();
js_sys::Promise::resolve(&res_val)
}

0 comments on commit 08fdf4a

Please sign in to comment.