Skip to content

Commit

Permalink
devtools: use display info
Browse files Browse the repository at this point in the history
  • Loading branch information
kobkaz committed Jan 10, 2024
1 parent 1a84d0a commit 17bd1ed
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 18 deletions.
6 changes: 6 additions & 0 deletions tmtc-c2a/devtools_frontend/crates/wasm-interpolate/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/target
**/*.rs.bk
Cargo.lock
bin/
pkg/
wasm-pack.log
29 changes: 29 additions & 0 deletions tmtc-c2a/devtools_frontend/crates/wasm-interpolate/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "wasm-interpolate"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[features]
default = ["console_error_panic_hook"]

[dependencies]
wasm-bindgen = "0.2.84"

# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# 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 }
interpolator = { version = "0.5.0", features = ["number"] }
js-sys = "0.3.66"
anyhow = "1.0.79"

[dev-dependencies]
wasm-bindgen-test = "0.3.34"

[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"
56 changes: 56 additions & 0 deletions tmtc-c2a/devtools_frontend/crates/wasm-interpolate/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
mod utils;

use interpolator::{format, Formattable};
use wasm_bindgen::prelude::*;

use anyhow::{anyhow, Result};
use js_sys::BigInt;
use std::collections::HashMap;

enum Value {
I64(i64),
F64(f64),
String(String),
}

impl Value {
pub fn formattable(&self) -> Formattable {
use Value::*;
match self {
I64(v) => Formattable::integer(v),
F64(v) => Formattable::float(v),
String(v) => Formattable::display(v),
}
}
}

impl TryFrom<&JsValue> for Value {
type Error = anyhow::Error;
fn try_from(value: &JsValue) -> Result<Self> {
if value.is_bigint() {
let value = BigInt::new(&value)
.map_err(|_| anyhow!("not a bigint"))?
.try_into()
.map_err(|_| anyhow!("couldn't convert bigint to i64"))?;
Ok(Value::I64(value))
} else if let Some(v) = value.as_f64() {
Ok(Value::F64(v))
} else if let Some(s) = value.as_string() {
Ok(Value::String(s))
} else {
Err(anyhow!("not a string, f64, or bigint"))
}
}
}

pub fn format_value_inner(format_string: &str, arg: &JsValue) -> Result<String> {
let arg = Value::try_from(arg)?;
let arg = arg.formattable();
let args = HashMap::from([("value", arg)]);
format(format_string, &args).map_err(Into::into)
}

#[wasm_bindgen]
pub fn format_value(format_string: &str, arg: &JsValue) -> Result<String, String> {
format_value_inner(format_string, arg).map_err(|e| e.to_string())
}
10 changes: 10 additions & 0 deletions tmtc-c2a/devtools_frontend/crates/wasm-interpolate/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
8 changes: 7 additions & 1 deletion tmtc-c2a/devtools_frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@
"codegen:proto:tmtc_generic_c2a": "protoc --ts_out src/proto --proto_path ../../tmtc-c2a/proto ../../tmtc-c2a/proto/tmtc_generic_c2a.proto",
"codegen:proto": "run-p codegen:proto:*",
"codegen": "run-s codegen:proto",
"crate:build": "cd crates && wasm-pack build --target web --release",
"crate:dev": "cd crates && cargo watch -s 'wasm-pack build --target web --dev' -C",
"crate": "yarn crate:${MODE:-build}",
"crates:wasm-interpolate": "yarn crate wasm-interpolate",
"dev:crates": "MODE=dev run-p crates:*",
"dev:vite": "vite --host",
"dev": "run-p dev:*",
"build:crates": "run-s crates:*",
"build:vite": "vite build",
"build": "run-s build:vite",
"build": "run-s build:crates build:vite",
"typecheck": "tsc",
"lint:prettier": "prettier . --check",
"lint:eslint": "eslint . --format stylish",
Expand Down
51 changes: 39 additions & 12 deletions tmtc-c2a/devtools_frontend/src/components/TelemetryView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,34 @@ import { useParams } from "react-router-dom";
import { Helmet } from "react-helmet-async";
import { TelemetrySchema } from "../proto/tmtc_generic_c2a";

import initInterpolate, * as interpolate from "../../crates/wasm-interpolate/pkg";

initInterpolate();

type DisplayInfo = {
formatString: string;
};

const buildTelemetryFieldTreeBlueprintFromSchema = (
tlm: TelemetrySchema,
): TreeNamespace<undefined> => {
const fieldNames = tlm.fields.map((f) => f.name);
const root: TreeNamespace<undefined> = new Map();
for (const fieldName of fieldNames) {
const path = fieldName.split(".");
addToNamespace(root, path, undefined);
): TreeNamespace<DisplayInfo> => {
const root: TreeNamespace<DisplayInfo> = new Map();
for (const field of tlm.fields) {
const path = field.name.split(".");
const formatString = field.metadata?.displayFormat ?? "";
addToNamespace(root, path, { formatString });
}
return root;
};

type TelemetryValuePair = {
displayInfo: DisplayInfo;
converted: TmivField["value"] | null;
raw: TmivField["value"] | null;
};

const buildTelemetryFieldTree = (
blueprint: TreeNamespace<undefined>,
blueprint: TreeNamespace<DisplayInfo>,
fields: TmivField[],
): TreeNamespace<TelemetryValuePair> => {
const convertedFieldMap = new Map<string, TmivField["value"]>();
Expand All @@ -38,15 +47,33 @@ const buildTelemetryFieldTree = (
convertedFieldMap.set(field.name, field.value);
}
}
return mapNamespace(blueprint, (path, _key) => {
return mapNamespace(blueprint, (path, displayInfo) => {
const key = path.join(".");
const converted = convertedFieldMap.get(key) ?? null;
const raw = rawFieldMap.get(key) ?? null;
return { converted, raw };
return { displayInfo, converted, raw };
});
};

const prettyprintValue = (value: TmivField["value"] | null) => {
const prettyprintValue = (
value: TmivField["value"] | null,
displayInfo: DisplayInfo,
) => {
if (value === null) {
return "****";
}
try {
const ks = Object.keys(value).find((k) => k !== "oneofKind")!;
const rawValue = value[ks as keyof typeof value]!; //FIXME: ????
const interpolated = interpolate.format_value("{value:#0x}", rawValue);
return defaultPrettyPrint(value) + "/" + interpolated;
} catch (e) {
// TODO: show warning
return defaultPrettyPrint(value) + "!";
}
};

const defaultPrettyPrint = (value: TmivField["value"] | null) => {
if (value === null) {
return "****";
}
Expand Down Expand Up @@ -76,7 +103,7 @@ const LeafCell: React.FC<ValueCellProps> = ({ name, value }) => {
<span className="text-slate-300">{name}</span>
<span className="min-w-[2ch]" />
<span className="font-bold text-right">
{prettyprintValue(value.converted)}
{prettyprintValue(value.converted, value.displayInfo)}
</span>
</div>
);
Expand Down Expand Up @@ -149,7 +176,7 @@ const InlineNamespaceContentCell: React.FC<InlineNamespaceContentCellProps> = ({
<span className="ml-[0.5ch]" key={name}>
<span className="text-slate-300">{name}:</span>
<span className="font-bold">
{prettyprintValue(v.value.converted)}
{prettyprintValue(v.value.converted, v.value.displayInfo)}
</span>
</span>
);
Expand Down
35 changes: 30 additions & 5 deletions tmtc-c2a/devtools_frontend/src/proto/tmtc_generic_c2a.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,15 @@ export interface TelemetryFieldSchema {
name: string; // TODO: TelemetryFieldDataType data_type = 3;
}
/**
* TODO: string description = 1;
*
* @generated from protobuf message tmtc_generic_c2a.TelemetryFieldSchemaMetadata
*/
export interface TelemetryFieldSchemaMetadata {
/**
* TODO: string description = 1;
*
* @generated from protobuf field: string display_format = 1;
*/
displayFormat: string;
}
/**
* @generated from protobuf message tmtc_generic_c2a.TelemetryChannelSchema
Expand Down Expand Up @@ -1070,19 +1074,40 @@ export const TelemetryFieldSchema = new TelemetryFieldSchema$Type();
// @generated message type with reflection information, may provide speed optimized methods
class TelemetryFieldSchemaMetadata$Type extends MessageType<TelemetryFieldSchemaMetadata> {
constructor() {
super("tmtc_generic_c2a.TelemetryFieldSchemaMetadata", []);
super("tmtc_generic_c2a.TelemetryFieldSchemaMetadata", [
{ no: 1, name: "display_format", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
]);
}
create(value?: PartialMessage<TelemetryFieldSchemaMetadata>): TelemetryFieldSchemaMetadata {
const message = {};
const message = { displayFormat: "" };
globalThis.Object.defineProperty(message, MESSAGE_TYPE, { enumerable: false, value: this });
if (value !== undefined)
reflectionMergePartial<TelemetryFieldSchemaMetadata>(this, message, value);
return message;
}
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: TelemetryFieldSchemaMetadata): TelemetryFieldSchemaMetadata {
return target ?? this.create();
let message = target ?? this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* string display_format */ 1:
message.displayFormat = reader.string();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message: TelemetryFieldSchemaMetadata, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
/* string display_format = 1; */
if (message.displayFormat !== "")
writer.tag(1, WireType.LengthDelimited).string(message.displayFormat);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
Expand Down

0 comments on commit 17bd1ed

Please sign in to comment.