Merge branch 'hash' into 'main'

implement Hash on Structure and Caps

See merge request gstreamer/gstreamer-rs!1639
This commit is contained in:
Guillaume Desmottes 2025-04-13 01:24:35 +00:00
commit c7e93ba7ba
3 changed files with 200 additions and 0 deletions

View file

@ -1259,6 +1259,28 @@ impl PartialEq for CapsRef {
impl Eq for CapsRef {}
impl std::hash::Hash for CapsRef {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
if self.is_any() {
"ANY".hash(state);
} else if self.is_empty() {
"EMPTY".hash(state);
} else {
// do no sort as structure order matters
for (s, feature) in self.iter_with_features() {
s.hash(state);
feature.hash(state);
}
}
}
}
impl std::hash::Hash for Caps {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_ref().hash(state);
}
}
pub enum NoFeature {}
pub enum HasFeatures {}
@ -1761,4 +1783,62 @@ mod tests {
assert_eq!(format!("{caps:?}"), "Caps(video/x-raw(memory:SystemMemory) { array: Array([(gchararray) \"a\", (gchararray) \"b\", (gchararray) \"c\"]), list: List([(gchararray) \"d\", (gchararray) \"e\", (gchararray) \"f\"]) })");
}
#[test]
fn test_hash() {
crate::init().unwrap();
use std::hash::BuildHasher;
let bh = std::hash::RandomState::new();
let caps = Caps::builder("video/x-raw").build();
assert_eq!(bh.hash_one(&caps), bh.hash_one(&caps));
let caps_any = Caps::new_any();
let caps_empty = Caps::new_empty();
assert_eq!(bh.hash_one(&caps_any), bh.hash_one(&caps_any));
assert_eq!(bh.hash_one(&caps_empty), bh.hash_one(&caps_empty));
assert_ne!(bh.hash_one(&caps_any), bh.hash_one(&caps_empty));
// Same caps but fields in a different order
let caps_a = Caps::builder("video/x-raw")
.field("width", 1920u32)
.field("height", 1080u32)
.build();
let caps_b = Caps::builder("video/x-raw")
.field("height", 1080u32)
.field("width", 1920u32)
.build();
assert_eq!(bh.hash_one(&caps_a), bh.hash_one(&caps_a));
assert_eq!(bh.hash_one(&caps_b), bh.hash_one(&caps_b));
assert_eq!(bh.hash_one(&caps_a), bh.hash_one(&caps_b));
// Same fields but different feature
let caps_a = Caps::builder("video/x-raw")
.features(["memory:DMABuf"])
.field("width", 1920u32)
.field("height", 1080u32)
.build();
let caps_b = Caps::builder("video/x-raw")
.features(["memory:GLMemory"])
.field("height", 1080u32)
.field("width", 1920u32)
.build();
assert_eq!(bh.hash_one(&caps_a), bh.hash_one(&caps_a));
assert_eq!(bh.hash_one(&caps_b), bh.hash_one(&caps_b));
assert_ne!(bh.hash_one(&caps_a), bh.hash_one(&caps_b));
// Caps have the same structures but in different order, so they are actually different
let caps_a = Caps::builder_full()
.structure(Structure::builder("audio/x-raw").build())
.structure(Structure::builder("video/x-raw").build())
.build();
let caps_b = Caps::builder_full()
.structure(Structure::builder("video/x-raw").build())
.structure(Structure::builder("audio/x-raw").build())
.build();
assert_eq!(bh.hash_one(&caps_a), bh.hash_one(&caps_a));
assert_eq!(bh.hash_one(&caps_b), bh.hash_one(&caps_b));
assert_ne!(bh.hash_one(&caps_a), bh.hash_one(&caps_b));
}
}

View file

@ -882,6 +882,28 @@ impl ToOwned for CapsFeaturesRef {
}
}
impl std::hash::Hash for CapsFeaturesRef {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
use itertools::Itertools;
if self.is_any() {
"ANY".hash(state);
} else if self.is_empty() {
"EMPTY".hash(state);
} else {
for f in self.iter().sorted() {
f.hash(state);
}
}
}
}
impl std::hash::Hash for CapsFeatures {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_ref().hash(state);
}
}
unsafe impl Sync for CapsFeaturesRef {}
unsafe impl Send for CapsFeaturesRef {}
@ -1009,4 +1031,38 @@ mod tests {
assert!(cf.contains_by_id(overlay_comp));
assert!(!cf.contains("memory:GLMemory"));
}
#[test]
fn test_hash() {
crate::init().unwrap();
use std::hash::BuildHasher;
let bh = std::hash::RandomState::new();
let any = CapsFeatures::new_any();
let empty = CapsFeatures::new_empty();
assert_eq!(bh.hash_one(&any), bh.hash_one(&any));
assert_eq!(bh.hash_one(&empty), bh.hash_one(&empty));
assert_ne!(bh.hash_one(&any), bh.hash_one(&empty));
// Different names
let cf1 = CapsFeatures::from(gstr!("memory:DMABuf"));
let cf2 = CapsFeatures::from(gstr!("memory:GLMemory"));
assert_eq!(bh.hash_one(&cf1), bh.hash_one(&cf1));
assert_eq!(bh.hash_one(&cf2), bh.hash_one(&cf2));
assert_ne!(bh.hash_one(&cf1), bh.hash_one(&cf2));
// Same features, different order
let cf1 = CapsFeatures::from([
gstr!("memory:DMABuf"),
gstr!("meta:GstVideoOverlayComposition"),
]);
let cf2 = CapsFeatures::from([
gstr!("meta:GstVideoOverlayComposition"),
gstr!("memory:DMABuf"),
]);
assert_eq!(bh.hash_one(&cf1), bh.hash_one(&cf1));
assert_eq!(bh.hash_one(&cf2), bh.hash_one(&cf2));
assert_eq!(bh.hash_one(&cf1), bh.hash_one(&cf2));
}
}

View file

@ -1984,6 +1984,28 @@ impl std::iter::Extend<(glib::Quark, SendValue)> for StructureRef {
}
}
impl std::hash::Hash for StructureRef {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
// Use the string serialization for hashing but first sort fields by values so structures
// with the same fields but in different orders have the same hash.
use itertools::Itertools;
let sorted_s = Structure::from_iter(
self.name(),
self.iter()
.sorted_by(|(field_a, _), (field_b, _)| field_a.cmp(field_b))
.map(|(f, v)| (f, v.clone())),
);
sorted_s.to_string().hash(state);
}
}
impl std::hash::Hash for Structure {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_ref().hash(state);
}
}
#[derive(Debug)]
#[must_use = "The builder must be built to be used"]
pub struct Builder {
@ -2526,4 +2548,46 @@ mod tests {
assert_eq!(s.field_ids().collect::<Vec<_>>(), vec![&f1, &f2]);
}
#[test]
fn test_hash() {
crate::init().unwrap();
use std::hash::BuildHasher;
let bh = std::hash::RandomState::new();
// Different names
let s1 = Structure::builder("test1").build();
let s2 = Structure::builder("test2").build();
assert_eq!(bh.hash_one(&s1), bh.hash_one(&s1));
assert_eq!(bh.hash_one(&s2), bh.hash_one(&s2));
assert_ne!(bh.hash_one(&s1), bh.hash_one(&s2));
// Same name different fields
let s1 = Structure::builder("test").field("a", 1u32).build();
let s2 = Structure::builder("test").field("b", 1u32).build();
assert_eq!(bh.hash_one(&s1), bh.hash_one(&s1));
assert_eq!(bh.hash_one(&s2), bh.hash_one(&s2));
assert_ne!(bh.hash_one(&s1), bh.hash_one(&s2));
// Same name different field values
let s1 = Structure::builder("test").field("a", 1u32).build();
let s2 = Structure::builder("test").field("a", 2u32).build();
assert_eq!(bh.hash_one(&s1), bh.hash_one(&s1));
assert_eq!(bh.hash_one(&s2), bh.hash_one(&s2));
assert_ne!(bh.hash_one(&s1), bh.hash_one(&s2));
// Same structure but fields in a different order
let s1 = Structure::builder("test")
.field("a", 1u32)
.field("b", 2u32)
.build();
let s2 = Structure::builder("test")
.field("b", 2u32)
.field("a", 1u32)
.build();
assert_eq!(bh.hash_one(&s1), bh.hash_one(&s1));
assert_eq!(bh.hash_one(&s2), bh.hash_one(&s2));
assert_eq!(bh.hash_one(&s1), bh.hash_one(&s2));
}
}