///|
/// Structural asset diffing for CI regression detection.
///|
pub(all) enum AssetDiffKind {
Added
Removed
Changed
Unchanged
} derive(Debug, ToJson, Eq)
///|
pub(all) struct AssetDifference {
kind : AssetDiffKind
category : String
index : Int
path : String
before : String?
after : String?
severity : DiagnosticSeverity
message : String
} derive(Debug, ToJson, Eq)
///|
pub(all) struct AssetDiffReport {
differences : Array[AssetDifference]
added_count : Int
removed_count : Int
changed_count : Int
unchanged_count : Int
breaking_count : Int
valid : Bool
} derive(Debug, ToJson, Eq)
///|
pub(all) struct AssetDiffPolicy {
fail_on_removed : Bool
fail_on_changed : Bool
compare_names : Bool
compare_geometry : Bool
compare_resources : Bool
} derive(Debug, ToJson, Eq)
///|
pub fn default_asset_diff_policy() -> AssetDiffPolicy {
{
fail_on_removed: true,
fail_on_changed: false,
compare_names: true,
compare_geometry: true,
compare_resources: true,
}
}
///|
fn difference(
kind : AssetDiffKind,
category : String,
index : Int,
path : String,
before : String?,
after : String?,
severity : DiagnosticSeverity,
message : String,
) -> AssetDifference {
{ kind, category, index, path, before, after, severity, message }
}
///|
fn compare_count(
category : String,
before : Int,
after : Int,
policy : AssetDiffPolicy,
result : Array[AssetDifference],
) -> Unit {
if before == after {
result.push(
difference(
Unchanged,
category,
-1,
category,
Some("\{before}"),
Some("\{after}"),
Info,
"count is unchanged",
),
)
} else if after > before {
result.push(
difference(
Added,
category,
-1,
category,
Some("\{before}"),
Some("\{after}"),
Info,
"count increased",
),
)
} else {
result.push(
difference(
Removed,
category,
-1,
category,
Some("\{before}"),
Some("\{after}"),
if policy.fail_on_removed {
Error
} else {
Warning
},
"count decreased",
),
)
}
}
///|
pub fn diff_documents(
before : GltfDocument,
after : GltfDocument,
policy : AssetDiffPolicy,
) -> AssetDiffReport {
let differences : Array[AssetDifference] = []
compare_count(
"scenes",
before.scenes.length(),
after.scenes.length(),
policy,
differences,
)
compare_count(
"nodes",
before.nodes.length(),
after.nodes.length(),
policy,
differences,
)
compare_count(
"meshes",
before.meshes.length(),
after.meshes.length(),
policy,
differences,
)
compare_count(
"materials",
before.materials.length(),
after.materials.length(),
policy,
differences,
)
compare_count(
"textures",
before.textures.length(),
after.textures.length(),
policy,
differences,
)
compare_count(
"images",
before.images.length(),
after.images.length(),
policy,
differences,
)
compare_count(
"buffers",
before.buffers.length(),
after.buffers.length(),
policy,
differences,
)
compare_count(
"accessors",
before.accessors.length(),
after.accessors.length(),
policy,
differences,
)
compare_count(
"animations",
before.animations.length(),
after.animations.length(),
policy,
differences,
)
compare_count(
"skins",
before.skins.length(),
after.skins.length(),
policy,
differences,
)
if before.version != after.version {
differences.push(
difference(
Changed,
"asset",
-1,
"asset.version",
Some(before.version),
Some(after.version),
Error,
"asset version changed",
),
)
}
if policy.compare_names {
let count = if before.nodes.length() < after.nodes.length() {
before.nodes.length()
} else {
after.nodes.length()
}
for index in 0.. {
if item.kind == Added {
count + 1
} else {
count
}
})
let removed = differences.fold(init=0, (count, item) => {
if item.kind == Removed {
count + 1
} else {
count
}
})
let changed = differences.fold(init=0, (count, item) => {
if item.kind == Changed {
count + 1
} else {
count
}
})
let unchanged = differences.fold(init=0, (count, item) => {
if item.kind == Unchanged {
count + 1
} else {
count
}
})
let breaking = differences.fold(init=0, (count, item) => {
if item.severity == Error {
count + 1
} else {
count
}
})
{
differences,
added_count: added,
removed_count: removed,
changed_count: changed,
unchanged_count: unchanged,
breaking_count: breaking,
valid: breaking == 0,
}
}
///|
pub fn diff_has_breaking_changes(report : AssetDiffReport) -> Bool {
report.breaking_count > 0
}
///|
pub fn diff_changes(report : AssetDiffReport) -> Array[AssetDifference] {
report.differences.filter(item => item.kind != Unchanged)
}
///|
pub fn diff_errors(report : AssetDiffReport) -> Array[AssetDifference] {
report.differences.filter(item => item.severity == Error)
}
///|
pub fn diff_summary_lines(report : AssetDiffReport) -> Array[String] {
[
"valid=\{report.valid}, added=\{report.added_count}, removed=\{report.removed_count}",
"changed=\{report.changed_count}, unchanged=\{report.unchanged_count}",
"breaking=\{report.breaking_count}",
]
}