///|
/// Structured diagnostics for tooling and CI. Reports contain measurements
/// rather than formatted terminal output so host applications can render
/// warnings in their own UI.
pub enum DiagnosticSeverity2 {
InfoDiagnostic
WarningDiagnostic
ErrorDiagnostic
} derive(Debug)
///|
pub fn diagnostic_severities2() -> Array[DiagnosticSeverity2] {
[InfoDiagnostic, WarningDiagnostic, ErrorDiagnostic]
}
///|
pub fn diagnostic_severity_name2(severity : DiagnosticSeverity2) -> String {
match severity {
InfoDiagnostic => "info"
WarningDiagnostic => "warning"
ErrorDiagnostic => "error"
}
}
///|
pub enum DiagnosticCode2 {
EmptyInputDiagnostic
NonFiniteDiagnostic
EndpointMismatchDiagnostic
VelocityLimitDiagnostic
SamplingErrorDiagnostic
ColorRangeDiagnostic
BoundsDiagnostic
} derive(Debug)
///|
pub fn diagnostic_codes2() -> Array[DiagnosticCode2] {
[
EmptyInputDiagnostic,
NonFiniteDiagnostic,
EndpointMismatchDiagnostic,
VelocityLimitDiagnostic,
SamplingErrorDiagnostic,
ColorRangeDiagnostic,
BoundsDiagnostic,
]
}
///|
pub fn diagnostic_code_name2(code : DiagnosticCode2) -> String {
match code {
EmptyInputDiagnostic => "empty-input"
NonFiniteDiagnostic => "non-finite"
EndpointMismatchDiagnostic => "endpoint-mismatch"
VelocityLimitDiagnostic => "velocity-limit"
SamplingErrorDiagnostic => "sampling-error"
ColorRangeDiagnostic => "color-range"
BoundsDiagnostic => "bounds"
}
}
///|
pub struct DiagnosticIssue2 {
severity : DiagnosticSeverity2
code : DiagnosticCode2
message : String
sample_index : Int
value : Double
limit : Double
} derive(Debug)
///|
pub fn DiagnosticIssue2::severity(
self : DiagnosticIssue2,
) -> DiagnosticSeverity2 {
self.severity
}
///|
pub fn DiagnosticIssue2::code(self : DiagnosticIssue2) -> DiagnosticCode2 {
self.code
}
///|
pub fn DiagnosticIssue2::message(self : DiagnosticIssue2) -> String {
self.message
}
///|
pub fn DiagnosticIssue2::sample_index(self : DiagnosticIssue2) -> Int {
self.sample_index
}
///|
pub fn DiagnosticIssue2::value(self : DiagnosticIssue2) -> Double {
self.value
}
///|
pub fn DiagnosticIssue2::limit(self : DiagnosticIssue2) -> Double {
self.limit
}
///|
pub struct DiagnosticReport2 {
name : String
issues : Array[DiagnosticIssue2]
checked_samples : Int
duration : Double
} derive(Debug)
///|
pub fn diagnostic_report2(name : String) -> DiagnosticReport2 {
{ name, issues: [], checked_samples: 0, duration: 0.0 }
}
///|
pub fn DiagnosticReport2::name(self : DiagnosticReport2) -> String {
self.name
}
///|
pub fn DiagnosticReport2::issues(
self : DiagnosticReport2,
) -> Array[DiagnosticIssue2] {
self.issues.copy()
}
///|
pub fn DiagnosticReport2::checked_samples(self : DiagnosticReport2) -> Int {
self.checked_samples
}
///|
pub fn DiagnosticReport2::duration(self : DiagnosticReport2) -> Double {
self.duration
}
///|
pub fn DiagnosticReport2::error_count(self : DiagnosticReport2) -> Int {
let mut count = 0
for issue in self.issues {
if issue.severity is ErrorDiagnostic {
count = count + 1
}
}
count
}
///|
pub fn DiagnosticReport2::warning_count(self : DiagnosticReport2) -> Int {
let mut count = 0
for issue in self.issues {
if issue.severity is WarningDiagnostic {
count = count + 1
}
}
count
}
///|
pub fn DiagnosticReport2::add(
self : DiagnosticReport2,
issue : DiagnosticIssue2,
) -> Unit {
self.issues.push(issue)
}
///|
pub fn DiagnosticReport2::with_measurements(
self : DiagnosticReport2,
samples : Int,
duration : Double,
) -> DiagnosticReport2 {
{
name: self.name,
issues: self.issues,
checked_samples: samples.max(0),
duration: duration.max(0.0),
}
}
///|
fn diagnostic_issue(
severity : DiagnosticSeverity2,
code : DiagnosticCode2,
message : String,
index : Int,
value : Double,
limit : Double,
) -> DiagnosticIssue2 {
{ severity, code, message, sample_index: index, value, limit }
}
///|
pub fn inspect_scalar_values(
name : String,
values : Array[Double],
tolerance : Double,
) -> DiagnosticReport2 {
let report = diagnostic_report2(name).with_measurements(values.length(), 0.0)
if values.length() == 0 {
report.add(
diagnostic_issue(
WarningDiagnostic,
EmptyInputDiagnostic,
"no samples were provided",
0,
0.0,
0.0,
),
)
return report
}
for index in 0.. 0 &&
(value - values[index - 1]).abs() > tolerance.abs() * 1000000.0 {
report.add(
diagnostic_issue(
WarningDiagnostic,
SamplingErrorDiagnostic,
"adjacent samples change abruptly",
index,
(value - values[index - 1]).abs(),
tolerance,
),
)
}
}
report
}
///|
pub fn inspect_curve_endpoints(
name : String,
curve : Curve,
tolerance : Double,
) -> DiagnosticReport2 {
let report = diagnostic_report2(name).with_measurements(2, 1.0)
let start = curve.apply(0.0)
let end = curve.apply(1.0)
if start.abs() > tolerance.abs() {
report.add(
diagnostic_issue(
WarningDiagnostic,
EndpointMismatchDiagnostic,
"curve does not start at zero",
0,
start,
tolerance,
),
)
}
if (end - 1.0).abs() > tolerance.abs() {
report.add(
diagnostic_issue(
WarningDiagnostic,
EndpointMismatchDiagnostic,
"curve does not end at one",
1,
end,
1.0,
),
)
}
report
}
///|
pub fn inspect_motion_limits(
name : String,
samples : Array[Double],
frame_rate : Double,
maximum_velocity : Double,
) -> DiagnosticReport2 {
let report = diagnostic_report2(name).with_measurements(samples.length(), 0.0)
if samples.length() < 2 {
return report
}
let dt = 1.0 / frame_rate.max(0.001)
for index in 1.. maximum_velocity {
report.add(
diagnostic_issue(
WarningDiagnostic,
VelocityLimitDiagnostic,
"sample exceeds velocity budget",
index,
velocity,
maximum_velocity,
),
)
}
}
report
}
///|
pub fn inspect_colors(name : String, colors : Array[Rgba]) -> DiagnosticReport2 {
let report = diagnostic_report2(name).with_measurements(colors.length(), 0.0)
for index in 0.. 1.0 ||
color.green() < 0.0 ||
color.green() > 1.0 ||
color.blue() < 0.0 ||
color.blue() > 1.0 ||
color.alpha() < 0.0 ||
color.alpha() > 1.0 {
report.add(
diagnostic_issue(
ErrorDiagnostic,
ColorRangeDiagnostic,
"color channel is outside normalized range",
index,
color.alpha(),
1.0,
),
)
}
}
report
}
///|
pub struct DiagnosticSummary2 {
reports : Int
errors : Int
warnings : Int
samples : Int
} derive(Debug)
///|
pub fn summarize_diagnostics(
reports : Array[DiagnosticReport2],
) -> DiagnosticSummary2 {
let mut errors = 0
let mut warnings = 0
let mut samples = 0
for report in reports {
errors = errors + report.error_count()
warnings = warnings + report.warning_count()
samples = samples + report.checked_samples
}
{ reports: reports.length(), errors, warnings, samples }
}
///|
pub fn DiagnosticSummary2::reports(self : DiagnosticSummary2) -> Int {
self.reports
}
///|
pub fn DiagnosticSummary2::errors(self : DiagnosticSummary2) -> Int {
self.errors
}
///|
pub fn DiagnosticSummary2::warnings(self : DiagnosticSummary2) -> Int {
self.warnings
}
///|
pub fn DiagnosticSummary2::samples(self : DiagnosticSummary2) -> Int {
self.samples
}
///|
pub fn DiagnosticSummary2::passed(self : DiagnosticSummary2) -> Bool {
self.errors == 0
}