value: add structure intersect/union/is_subset/fixate implementations
authorMatthew Waters <matthew@centricular.com>
Thu, 8 Dec 2016 10:01:52 +0000 (21:01 +1100)
committerMatthew Waters <matthew@centricular.com>
Thu, 15 Dec 2016 02:39:57 +0000 (13:39 +1100)
Allows proper usage of structures in structures in caps.  Subtraction
is not implemented due to complications with empty fields representing
all possible values.

The only implementation that doesn't delegate to the already existing
GstStructure functions is the union function.

https://bugzilla.gnome.org/show_bug.cgi?id=775796

gst/gstvalue.c
tests/check/gst/gstvalue.c

index f103c62..746b3ef 100644 (file)
@@ -327,7 +327,8 @@ gst_type_is_fixed (GType type)
   /* our fundamental types that are certainly not fixed */
   if (type == GST_TYPE_INT_RANGE || type == GST_TYPE_DOUBLE_RANGE ||
       type == GST_TYPE_INT64_RANGE ||
-      type == GST_TYPE_LIST || type == GST_TYPE_FRACTION_RANGE) {
+      type == GST_TYPE_LIST || type == GST_TYPE_FRACTION_RANGE ||
+      type == GST_TYPE_STRUCTURE) {
     return FALSE;
   }
   /* other (boxed) types that are fixed */
@@ -3550,6 +3551,21 @@ gst_value_is_subset_flagset_flagset (const GValue * value1,
   return TRUE;
 }
 
+static gboolean
+gst_value_is_subset_structure_structure (const GValue * value1,
+    const GValue * value2)
+{
+  const GstStructure *s1, *s2;
+
+  g_return_val_if_fail (GST_VALUE_HOLDS_STRUCTURE (value1), FALSE);
+  g_return_val_if_fail (GST_VALUE_HOLDS_STRUCTURE (value2), FALSE);
+
+  s1 = gst_value_get_structure (value1);
+  s2 = gst_value_get_structure (value2);
+
+  return gst_structure_is_subset (s1, s2);
+}
+
 /**
  * gst_value_is_subset:
  * @value1: a #GValue
@@ -3574,6 +3590,9 @@ gst_value_is_subset (const GValue * value1, const GValue * value2)
   } else if (GST_VALUE_HOLDS_FLAG_SET (value1) &&
       GST_VALUE_HOLDS_FLAG_SET (value2)) {
     return gst_value_is_subset_flagset_flagset (value1, value2);
+  } else if (GST_VALUE_HOLDS_STRUCTURE (value1)
+      && GST_VALUE_HOLDS_STRUCTURE (value2)) {
+    return gst_value_is_subset_structure_structure (value1, value2);
   }
 
   /*
@@ -3773,6 +3792,81 @@ gst_value_union_flagset_flagset (GValue * dest, const GValue * src1,
   return TRUE;
 }
 
+/* iterating over the result taking the union with the other structure's value */
+static gboolean
+structure_field_union_into (GQuark field_id, GValue * val, gpointer user_data)
+{
+  GstStructure *other = user_data;
+  const GValue *other_value;
+  GValue res_value = G_VALUE_INIT;
+
+  other_value = gst_structure_id_get_value (other, field_id);
+  /* no value in the other struct, just keep this value */
+  if (!other_value)
+    return TRUE;
+
+  if (!gst_value_union (&res_value, val, other_value))
+    return FALSE;
+
+  g_value_unset (val);
+  gst_value_init_and_copy (val, &res_value);
+  return TRUE;
+}
+
+/* iterating over the other source structure adding missing values */
+static gboolean
+structure_field_union_from (GQuark field_id, const GValue * other_val,
+    gpointer user_data)
+{
+  GstStructure *result = user_data;
+  const GValue *result_value;
+
+  result_value = gst_structure_id_get_value (result, field_id);
+  if (!result_value)
+    gst_structure_id_set_value (result, field_id, other_val);
+
+  return TRUE;
+}
+
+static gboolean
+gst_value_union_structure_structure (GValue * dest, const GValue * src1,
+    const GValue * src2)
+{
+  const GstStructure *s1, *s2;
+  GstStructure *result;
+  gboolean ret;
+
+  g_return_val_if_fail (GST_VALUE_HOLDS_STRUCTURE (src1), FALSE);
+  g_return_val_if_fail (GST_VALUE_HOLDS_STRUCTURE (src2), FALSE);
+
+  s1 = gst_value_get_structure (src1);
+  s2 = gst_value_get_structure (src2);
+
+  /* Can't join two structures with different names into a single structure */
+  if (!gst_structure_has_name (s1, gst_structure_get_name (s2))) {
+    gst_value_list_concat (dest, src1, src2);
+    return TRUE;
+  }
+
+  result = gst_structure_copy (s1);
+  ret =
+      gst_structure_map_in_place (result, structure_field_union_into,
+      (gpointer) s2);
+  if (!ret)
+    goto out;
+  ret =
+      gst_structure_foreach (s2, structure_field_union_from, (gpointer) result);
+
+  if (ret) {
+    g_value_init (dest, GST_TYPE_STRUCTURE);
+    gst_value_set_structure (dest, result);
+  }
+
+out:
+  gst_structure_free (result);
+  return ret;
+}
+
 /****************
  * intersection *
  ****************/
@@ -4152,6 +4246,29 @@ gst_value_intersect_flagset_flagset (GValue * dest,
   return TRUE;
 }
 
+static gboolean
+gst_value_intersect_structure_structure (GValue * dest,
+    const GValue * src1, const GValue * src2)
+{
+  const GstStructure *s1, *s2;
+  GstStructure *d1;
+
+  s1 = gst_value_get_structure (src1);
+  s2 = gst_value_get_structure (src2);
+
+  d1 = gst_structure_intersect (s1, s2);
+  if (!d1)
+    return FALSE;
+
+  if (dest) {
+    g_value_init (dest, GST_TYPE_STRUCTURE);
+    gst_value_set_structure (dest, d1);
+  }
+
+  gst_structure_free (d1);
+  return TRUE;
+}
+
 /***************
  * subtraction *
  ***************/
@@ -4696,7 +4813,6 @@ gst_value_subtract_fraction_range_fraction_range (GValue * dest,
   return TRUE;
 }
 
-
 /**************
  * comparison *
  **************/
@@ -5305,6 +5421,8 @@ gst_value_can_subtract (const GValue * minuend, const GValue * subtrahend)
   /* special cases */
   if (mtype == GST_TYPE_LIST || stype == GST_TYPE_LIST)
     return TRUE;
+  if (mtype == GST_TYPE_STRUCTURE || stype == GST_TYPE_STRUCTURE)
+    return FALSE;
 
   len = gst_value_subtract_funcs->len;
   for (i = 0; i < len; i++) {
@@ -5491,6 +5609,13 @@ gst_value_deserialize (GValue * dest, const gchar * src)
   return FALSE;
 }
 
+static gboolean
+structure_field_is_fixed (GQuark field_id, const GValue * val,
+    gpointer user_data)
+{
+  return gst_value_is_fixed (val);
+}
+
 /**
  * gst_value_is_fixed:
  * @value: the #GValue to check
@@ -5532,6 +5657,9 @@ gst_value_is_fixed (const GValue * value)
   } else if (GST_VALUE_HOLDS_FLAG_SET (value)) {
     /* Flagsets are only fixed if there are no 'don't care' bits */
     return (gst_value_get_flagset_mask (value) == GST_FLAG_SET_MASK_EXACT);
+  } else if (GST_VALUE_HOLDS_STRUCTURE (value)) {
+    return gst_structure_foreach (gst_value_get_structure (value),
+        structure_field_is_fixed, NULL);
   }
   return gst_type_is_fixed (type);
 }
@@ -5607,6 +5735,19 @@ gst_value_fixate (GValue * dest, const GValue * src)
     g_value_init (dest, G_VALUE_TYPE (src));
     gst_value_set_flagset (dest, flags, GST_FLAG_SET_MASK_EXACT);
     return TRUE;
+  } else if (GST_VALUE_HOLDS_STRUCTURE (src)) {
+    const GstStructure *str = (GstStructure *) gst_value_get_structure (src);
+    GstStructure *kid;
+
+    if (!str)
+      return FALSE;
+
+    kid = gst_structure_copy (str);
+    gst_structure_fixate (kid);
+    g_value_init (dest, GST_TYPE_STRUCTURE);
+    gst_value_set_structure (dest, kid);
+    gst_structure_free (kid);
+    return TRUE;
   } else {
     return FALSE;
   }
@@ -6771,9 +6912,10 @@ G_STMT_START {                                                          \
 
 /* These initial sizes are used for the tables
  * below, and save a couple of reallocs at startup */
+
 static const gint GST_VALUE_TABLE_DEFAULT_SIZE = 35;
-static const gint GST_VALUE_UNION_TABLE_DEFAULT_SIZE = 3;
-static const gint GST_VALUE_INTERSECT_TABLE_DEFAULT_SIZE = 10;
+static const gint GST_VALUE_UNION_TABLE_DEFAULT_SIZE = 4;
+static const gint GST_VALUE_INTERSECT_TABLE_DEFAULT_SIZE = 11;
 static const gint GST_VALUE_SUBTRACT_TABLE_DEFAULT_SIZE = 12;
 
 void
@@ -6905,6 +7047,8 @@ _priv_gst_value_initialize (void)
       gst_value_intersect_fraction_range_fraction_range);
   gst_value_register_intersect_func (GST_TYPE_FLAG_SET, GST_TYPE_FLAG_SET,
       gst_value_intersect_flagset_flagset);
+  gst_value_register_intersect_func (GST_TYPE_STRUCTURE, GST_TYPE_STRUCTURE,
+      gst_value_intersect_structure_structure);
 
   gst_value_register_subtract_func (G_TYPE_INT, GST_TYPE_INT_RANGE,
       gst_value_subtract_int_int_range);
@@ -6945,6 +7089,8 @@ _priv_gst_value_initialize (void)
       gst_value_union_int_range_int_range);
   gst_value_register_union_func (GST_TYPE_FLAG_SET, GST_TYPE_FLAG_SET,
       gst_value_union_flagset_flagset);
+  gst_value_register_union_func (GST_TYPE_STRUCTURE, GST_TYPE_STRUCTURE,
+      gst_value_union_structure_structure);
 
 #if GST_VERSION_NANO == 1
   /* If building from git master, check starting array sizes matched actual size
index cca7260..6e36232 100644 (file)
@@ -3115,6 +3115,164 @@ GST_START_TEST (test_stepped_int_range_ops)
 
 GST_END_TEST;
 
+GST_START_TEST (test_structure_basic)
+{
+  GstStructure *s1, *s2;
+  GValue v1 = G_VALUE_INIT, v2 = G_VALUE_INIT;
+
+  /* sanity test */
+  s1 = gst_structure_from_string ("foo,bar=1", NULL);
+  g_value_init (&v1, GST_TYPE_STRUCTURE);
+  gst_value_set_structure (&v1, s1);
+  fail_unless (gst_structure_is_equal (s1, gst_value_get_structure (&v1)));
+
+  s2 = gst_structure_copy (s1);
+  g_value_init (&v2, GST_TYPE_STRUCTURE);
+  gst_value_set_structure (&v2, s2);
+
+  /* can do everything but subtract */
+  fail_unless (gst_value_can_compare (&v1, &v2));
+  fail_unless (gst_value_can_intersect (&v1, &v2));
+  fail_unless (!gst_value_can_subtract (&v1, &v2));
+  fail_unless (gst_value_can_union (&v1, &v2));
+
+  gst_structure_free (s1);
+  gst_structure_free (s2);
+  g_value_unset (&v1);
+  g_value_unset (&v2);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_structure_single_ops)
+{
+  static const struct
+  {
+    const gchar *str1;
+    gboolean is_fixed;
+    gboolean can_fixate;
+  } single_struct[] = {
+    {
+    "foo,bar=(int)1", TRUE, TRUE}, {
+  "foo,bar=(int)[1,2]", FALSE, TRUE},};
+  gint i;
+
+  for (i = 0; i < G_N_ELEMENTS (single_struct); i++) {
+    GstStructure *s1 = gst_structure_from_string (single_struct[i].str1, NULL);
+    GValue v1 = G_VALUE_INIT;
+    GValue v2 = G_VALUE_INIT;
+
+    fail_unless (s1 != NULL);
+
+    GST_DEBUG ("checking structure %" GST_PTR_FORMAT, s1);
+
+    g_value_init (&v1, GST_TYPE_STRUCTURE);
+    gst_value_set_structure (&v1, s1);
+
+    fail_unless (gst_value_is_fixed (&v1) == single_struct[i].is_fixed);
+    fail_unless (gst_value_fixate (&v2, &v1) == single_struct[i].can_fixate);
+    if (single_struct[i].can_fixate)
+      g_value_unset (&v2);
+
+    g_value_unset (&v1);
+    gst_structure_free (s1);
+  }
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_structure_ops)
+{
+  struct
+  {
+    const gchar *str1;
+    const gchar *str2;
+    const gchar *op;
+    gint ret;
+    GType str_type;
+    const gchar *str_result;
+  } comparisons[] = {
+    /* *INDENT-OFF* */
+    {"foo,bar=(int)1", "foo,bar=(int)1", "compare", GST_VALUE_EQUAL, 0, NULL},
+    {"foo,bar=(int)1", "foo,bar=(int)1", "is_subset", TRUE, 0, NULL},
+    {"foo,bar=(int)1", "foo,bar=(int)1", "intersect", TRUE, GST_TYPE_STRUCTURE, "foo,bar=(int)1"},
+    {"foo,bar=(int)1", "foo,bar=(int)1", "union", TRUE, GST_TYPE_STRUCTURE, "foo,bar=(int)1"},
+    {"foo,bar=(int)[1,2]", "foo,bar=(int)1", "compare", GST_VALUE_UNORDERED, 0, NULL},
+    {"foo,bar=(int)[1,2]", "foo,bar=(int)1", "is_subset", FALSE, 0, NULL},
+    {"foo,bar=(int)[1,2]", "foo,bar=(int)1", "intersect", TRUE, GST_TYPE_STRUCTURE, "foo,bar=(int)1"},
+    {"foo,bar=(int)[1,2]", "foo,bar=(int)1", "union", TRUE, GST_TYPE_STRUCTURE, "foo,bar=(int)[1,2]"},
+    {"foo,bar=(int)1", "foo,bar=(int)[1,2]", "compare", GST_VALUE_UNORDERED, 0, NULL},
+    {"foo,bar=(int)1", "foo,bar=(int)[1,2]", "is_subset", TRUE, 0, NULL},
+    {"foo,bar=(int)1", "foo,bar=(int)[1,2]", "intersect", TRUE, GST_TYPE_STRUCTURE, "foo,bar=(int)1"},
+    {"foo,bar=(int)1", "foo,bar=(int)[1,2]", "union", TRUE, GST_TYPE_STRUCTURE, "foo,bar=(int)[1,2]"},
+    {"foo,bar=(int)1", "foo,bar=(int)2", "compare", GST_VALUE_UNORDERED, 0, NULL},
+    {"foo,bar=(int)1", "foo,bar=(int)2", "is_subset", FALSE, 0, NULL},
+    {"foo,bar=(int)1", "foo,bar=(int)2", "intersect", FALSE, 0, NULL},
+    {"foo,bar=(int)1", "foo,bar=(int)2", "union", TRUE, GST_TYPE_STRUCTURE, "foo,bar=(int)[1,2]"},
+    {"foo,bar=(int)1", "baz,bar=(int)1", "compare", GST_VALUE_UNORDERED, 0, NULL},
+    {"foo,bar=(int)1", "baz,bar=(int)1", "is_subset", FALSE, 0, NULL},
+    {"foo,bar=(int)1", "baz,bar=(int)1", "intersect", FALSE, 0, NULL},
+#if 0
+    /* deserializing lists is not implemented (but this should still work!) */
+    {"foo,bar=(int)1", "baz,bar=(int)1", "union", TRUE, G_TYPE_LIST, "{foo,bar=(int)1;, baz,bar=(int)1;}"},
+#endif
+    /* *INDENT-ON* */
+  };
+  gint i;
+
+  for (i = 0; i < G_N_ELEMENTS (comparisons); i++) {
+    GstStructure *s1 = gst_structure_from_string (comparisons[i].str1, NULL);
+    GstStructure *s2 = gst_structure_from_string (comparisons[i].str2, NULL);
+    GValue v1 = G_VALUE_INIT, v2 = G_VALUE_INIT, v3 = G_VALUE_INIT;
+
+    fail_unless (s1 != NULL);
+    fail_unless (s2 != NULL);
+
+    GST_DEBUG ("checking %s with structure1 %" GST_PTR_FORMAT " structure2 %"
+        GST_PTR_FORMAT " is %d, %s", comparisons[i].op, s1, s2,
+        comparisons[i].ret, comparisons[i].str_result);
+
+    g_value_init (&v1, GST_TYPE_STRUCTURE);
+    gst_value_set_structure (&v1, s1);
+    g_value_init (&v2, GST_TYPE_STRUCTURE);
+    gst_value_set_structure (&v2, s2);
+
+    if (g_strcmp0 (comparisons[i].op, "compare") == 0) {
+      fail_unless (gst_value_compare (&v1, &v2) == comparisons[i].ret);
+    } else if (g_strcmp0 (comparisons[i].op, "is_subset") == 0) {
+      fail_unless (gst_value_is_subset (&v1, &v2) == comparisons[i].ret);
+    } else {
+      if (g_strcmp0 (comparisons[i].op, "intersect") == 0) {
+        fail_unless (gst_value_intersect (&v3, &v1, &v2) == comparisons[i].ret);
+      } else if (g_strcmp0 (comparisons[i].op, "union") == 0) {
+        fail_unless (gst_value_union (&v3, &v1, &v2) == comparisons[i].ret);
+      }
+      if (comparisons[i].ret) {
+        GValue result = G_VALUE_INIT;
+        gchar *str;
+
+        str = gst_value_serialize (&v3);
+        GST_LOG ("result %s", str);
+        g_free (str);
+
+        g_value_init (&result, comparisons[i].str_type);
+        fail_unless (gst_value_deserialize (&result,
+                comparisons[i].str_result));
+        fail_unless (gst_value_compare (&result, &v3) == GST_VALUE_EQUAL);
+        g_value_unset (&v3);
+        g_value_unset (&result);
+      }
+    }
+
+    gst_structure_free (s1);
+    gst_structure_free (s2);
+    g_value_unset (&v1);
+    g_value_unset (&v2);
+  }
+}
+
+GST_END_TEST;
+
 static Suite *
 gst_value_suite (void)
 {
@@ -3160,6 +3318,9 @@ gst_value_suite (void)
   tcase_add_test (tc_chain, test_stepped_int_range_parsing);
   tcase_add_test (tc_chain, test_stepped_int_range_ops);
   tcase_add_test (tc_chain, test_flagset);
+  tcase_add_test (tc_chain, test_structure_basic);
+  tcase_add_test (tc_chain, test_structure_single_ops);
+  tcase_add_test (tc_chain, test_structure_ops);
 
   return s;
 }