eina_binbuf: allow expand & usage of extra bytes.
authorGustavo Sverzut Barbieri <barbieri@profusion.mobi>
Mon, 19 Dec 2016 20:35:38 +0000 (18:35 -0200)
committerGustavo Sverzut Barbieri <barbieri@profusion.mobi>
Tue, 20 Dec 2016 12:18:32 +0000 (10:18 -0200)
Some code needs to read directly into eina_binbuf to avoid an extra
copy from eina_binbuf_append* variants.

This can be achieved by using eina_binbuf_expand(), which returns a
write-able slice of the spare bytes. Once they are used,
eina_binbuf_use() should be called to increment "buf->len", which is
used by all other binbuf functions such as eina_binbuf_length_get() or
eina_binbuf_append_slice().

src/lib/eina/eina_binbuf.h
src/lib/eina/eina_binbuf_template_c.x
src/lib/eina/eina_strbuf_common.c
src/lib/eina/eina_strbuf_common.h
src/tests/eina/eina_test_binbuf.c

index 051c5d172d08bf596b55a89e76eb23a788364185..e10728e11a7ce2afebe502c315e82f0d1441042c 100644 (file)
@@ -129,6 +129,58 @@ EAPI void eina_binbuf_free(Eina_Binbuf *buf) EINA_ARG_NONNULL(1);
  */
 EAPI void eina_binbuf_reset(Eina_Binbuf *buf) EINA_ARG_NONNULL(1);
 
+/**
+ * @brief Expand a buffer, making room for at least @a minimum_unused_space.
+ *
+ * One of the properties of the buffer is that it may overallocate
+ * space, thus it may have more than eina_binbuf_length_get() bytes
+ * allocated. How much depends on buffer growing logic, but this
+ * function allows one to request a minimum amount of bytes to be
+ * allocated at the end of the buffer.
+ *
+ * This is particularly useful to write directly to buffer's memory
+ * (ie: a call to read(2)). After the bytes are used call
+ * eina_binbuf_use() to mark them as such, so eina_binbuf_length_get()
+ * will consider the new bytes.
+ *
+ * @param buf The Buffer to expand.
+ * @param minimum_unused_space The minimum unused allocated space, in
+ *        bytes, at the end of the buffer. Zero can be used to query
+ *        the available slice of unused bytes.
+ *
+ * @return The slice of unused bytes. The slice length may be zero if
+ *         @a minimum_unused_space couldn't be allocated, otherwise it
+ *         will be at least @a minimum_unused_space. After bytes are used,
+ *         mark them as such using eina_binbuf_use().
+ *
+ * @see eina_binbuf_rw_slice_get()
+ * @see eina_binbuf_use()
+ *
+ * @since 1.19
+ */
+EAPI Eina_Rw_Slice eina_binbuf_expand(Eina_Binbuf *buf, size_t minimum_unused_space) EINA_ARG_NONNULL(1);
+
+/**
+ * @brief Mark more bytes as used.
+ *
+ * This function should be used after eina_binbuf_expand(), marking
+ * the extra bytes returned there as used, then they will be
+ * considered in all other functions, such as eina_binbuf_length_get().
+ *
+ * @param buf The buffer to mark extra bytes as used.
+ * @param extra_bytes the number of bytes to be considered used, must
+ *        be between zero and the length of the slice returned by
+ *        eina_binbuf_expand().
+ *
+ * @return #EINA_TRUE on success, #EINA_FALSE on failure, such as @a
+ *         extra_bytes is too big or @a buf is NULL.
+ *
+ * @see eina_binbuf_expand()
+ *
+ * @since 1.19
+ */
+EAPI Eina_Bool eina_binbuf_use(Eina_Binbuf *buf, size_t extra_bytes) EINA_ARG_NONNULL(1);
+
 /**
  * @brief Append a string of exact length to a buffer, reallocating as necessary.
  *
@@ -330,6 +382,10 @@ EAPI Eina_Slice eina_binbuf_slice_get(const Eina_Binbuf *buf) EINA_WARN_UNUSED_R
  * @return a read-write slice for the current contents. It may become
  *         invalid as soon as the @a buf is changed with calls such as
  *         eina_binbuf_append(), eina_binbuf_remove()
+ *
+ * @see eina_binbuf_expand()
+ *
+ * @since 1.19
  */
 EAPI Eina_Rw_Slice eina_binbuf_rw_slice_get(const Eina_Binbuf *buf) EINA_WARN_UNUSED_RESULT EINA_ARG_NONNULL(1);
 
index f074f04525ed1aac4c02616a21d4ae8a04f9ee65..72b6fd7972442c127d7a3dd56e778a6201c46f65 100644 (file)
@@ -100,6 +100,21 @@ _FUNC_EXPAND(reset)(_STRBUF_STRUCT_NAME *buf)
    eina_strbuf_common_reset(_STRBUF_CSIZE, buf);
 }
 
+EAPI Eina_Rw_Slice
+_FUNC_EXPAND(expand)(_STRBUF_STRUCT_NAME *buf, size_t minimum_unused_space)
+{
+   Eina_Rw_Slice ret = {.len = 0, .mem = NULL};
+   EINA_MAGIC_CHECK_STRBUF(buf, ret);
+   return eina_strbuf_common_expand(_STRBUF_CSIZE, buf, minimum_unused_space);
+}
+
+EAPI Eina_Bool
+_FUNC_EXPAND(use)(_STRBUF_STRUCT_NAME *buf, size_t extra_bytes)
+{
+   EINA_MAGIC_CHECK_STRBUF(buf, EINA_FALSE);
+   return eina_strbuf_common_use(buf, extra_bytes);
+}
+
 EAPI Eina_Bool
 _FUNC_EXPAND(append_length)(_STRBUF_STRUCT_NAME *buf, const _STRBUF_DATA_TYPE *str, size_t length)
 {
index 7b9aa9f0d72afe8c24d31d4dcfcc3a227d45468e..3a18d4d4d0282057748d8ce62e8da121fb84ea8c 100644 (file)
@@ -378,6 +378,88 @@ eina_strbuf_common_reset(size_t csize, Eina_Strbuf *buf)
    memset(buf->buf, 0, csize);
 }
 
+/**
+ * @internal
+ * @brief Expand a buffer, making room for at least @a minimum_unused_space.
+ *
+ * One of the properties of the buffer is that it may overallocate
+ * space, thus it may have more than eina_strbuf_common_length_get() bytes
+ * allocated. How much depends on buffer growing logic, but this
+ * function allows one to request a minimum amount of bytes to be
+ * allocated at the end of the buffer.
+ *
+ * This is particularly useful to write directly to buffer's memory
+ * (ie: a call to read(2)). After the bytes are used call
+ * eina_strbuf_common_use() to mark them as such, so
+ * eina_strbuf_common_length_get() will consider the new bytes.
+ *
+ * @param csize the character size
+ * @param buf The Buffer to expand.
+ * @param minimum_unused_space The minimum unused allocated space, in
+ *        bytes, at the end of the buffer. Zero can be used to query
+ *        the available slice of unused bytes.
+ *
+ * @return The slice of unused bytes. The slice length may be zero if
+ *         @a minimum_unused_space couldn't be allocated, otherwise it
+ *         will be at least @a minimum_unused_space. After bytes are used,
+ *         mark them as such using eina_strbuf_common_use().
+ *
+ * @see eina_strbuf_common_rw_slice_get()
+ * @see eina_strbuf_common_use()
+ *
+ * @since 1.19
+ */
+Eina_Rw_Slice
+eina_strbuf_common_expand(size_t csize,
+                          Eina_Strbuf *buf,
+                          size_t minimum_unused_space)
+{
+   Eina_Rw_Slice ret = { .mem = NULL, .len = 0 };
+
+   if (EINA_LIKELY(buf->len + minimum_unused_space < buf->size)) goto end;
+
+   if (EINA_UNLIKELY(!_eina_strbuf_common_grow(csize, buf, buf->len + minimum_unused_space)))
+      return ret;
+
+ end:
+   ret.mem = (unsigned char *)buf->buf + (buf->len * csize);
+   ret.len = buf->size - buf->len - 1;
+   return ret;
+}
+
+/**
+ * @internal
+ * @brief Mark more bytes as used.
+ *
+ * This function should be used after eina_strbuf_common_expand(),
+ * marking the extra bytes returned there as used, then they will be
+ * considered in all other functions, such as
+ * eina_strbuf_common_length_get().
+ *
+ * @param csize the character size
+ * @param buf The buffer to mark extra bytes as used.
+ * @param extra_bytes the number of bytes to be considered used, must
+ *        be between zero and the length of the slice returned by
+ *        eina_strbuf_common_expand().
+ *
+ * @return #EINA_TRUE on success, #EINA_FALSE on failure, such as @a
+ *         extra_bytes is too big or @a buf is NULL.
+ *
+ * @see eina_strbuf_common_expand()
+ *
+ * @since 1.19
+ */
+Eina_Bool
+eina_strbuf_common_use(Eina_Strbuf *buf,
+                       size_t extra_bytes)
+{
+   if (EINA_UNLIKELY(buf->size < buf->len + extra_bytes + 1))
+     return EINA_FALSE;
+
+   buf->len += extra_bytes;
+   return EINA_TRUE;
+}
+
 /**
  * @internal
  * @brief Append a string to a buffer, reallocating as necessary.
index ec86f25e5128a39fd26422162b614dd1b7b68127..931cae456dd7c5d96908aeacec5f64eecb79274f 100644 (file)
@@ -52,6 +52,15 @@ void
 eina_strbuf_common_free(Eina_Strbuf *buf);
 void
 eina_strbuf_common_reset(size_t csize, Eina_Strbuf *buf);
+
+Eina_Rw_Slice
+eina_strbuf_common_expand(size_t csize,
+                          Eina_Strbuf *buf,
+                          size_t minimum_unused_space);
+Eina_Bool
+eina_strbuf_common_use(Eina_Strbuf *buf,
+                       size_t extra_bytes);
+
 Eina_Bool
 eina_strbuf_common_append(size_t csize,
                           Eina_Strbuf *buf,
index 653d9ae617be3d387b70fd85c5a091d4a62942b9..ce3225d7227dd5b91731eb67210d6425bab3cfed 100644 (file)
@@ -306,6 +306,74 @@ START_TEST(binbuf_realloc)
 }
 END_TEST
 
+START_TEST(binbuf_expand)
+{
+   Eina_Binbuf *buf;
+   Eina_Rw_Slice rw_slice;
+   Eina_Slice ro_slice;
+   Eina_Slice hello_world = EINA_SLICE_STR_LITERAL("Hello World");
+   Eina_Slice hi_there = EINA_SLICE_STR_LITERAL("Hi There");
+   size_t i;
+   Eina_Bool r;
+
+   eina_init();
+
+   buf = eina_binbuf_new();
+   fail_if(!buf);
+
+   rw_slice = eina_binbuf_rw_slice_get(buf);
+   ck_assert_int_eq(rw_slice.len, 0);
+
+   /* force it to grow to 'Hello World' */
+   r = eina_binbuf_append_slice(buf, hello_world);
+   ck_assert_int_eq(r, EINA_TRUE);
+
+   ro_slice = eina_binbuf_slice_get(buf);
+   ck_assert_int_eq(ro_slice.len, hello_world.len);
+   ck_assert_int_eq(eina_slice_compare(ro_slice, hello_world), 0);
+
+   /* reset doesn't change allocated size, 'Hi there' will fit */
+   eina_binbuf_reset(buf);
+   rw_slice = eina_binbuf_expand(buf, hi_there.len);
+   ck_assert_int_ge(rw_slice.len, hi_there.len);
+   /* access bytes directly */
+   rw_slice = eina_rw_slice_copy(rw_slice, hi_there);
+   r = eina_binbuf_use(buf, rw_slice.len);
+   ck_assert_int_eq(r, EINA_TRUE);
+   ck_assert_int_eq(eina_slice_compare(eina_binbuf_slice_get(buf), hi_there), 0);
+
+   /* start with 'Hello World */
+   eina_binbuf_reset(buf);
+   r = eina_binbuf_append_slice(buf, hello_world);
+   ck_assert_int_eq(r, EINA_TRUE);
+
+   /* force it to realloc */
+   rw_slice = eina_binbuf_expand(buf, 8192);
+   ck_assert_int_ge(rw_slice.len, 8192);
+   ck_assert_ptr_ne(rw_slice.mem, NULL);
+
+   memset(rw_slice.mem, 0xfe, rw_slice.len);
+
+   r = eina_binbuf_use(buf, rw_slice.len);
+   ck_assert_int_eq(r, EINA_TRUE);
+
+   r = eina_binbuf_use(buf, 1); /* would go too much */
+   ck_assert_int_eq(r, EINA_FALSE);
+
+   ro_slice = eina_binbuf_slice_get(buf);
+   ck_assert_int_eq(ro_slice.len, hello_world.len + rw_slice.len);
+   ck_assert_int_eq(memcmp(ro_slice.mem, hello_world.mem, hello_world.len), 0);
+
+   for (i = hello_world.len; i < ro_slice.len; i++)
+     ck_assert_int_eq(ro_slice.bytes[i], 0xfe);
+
+   eina_binbuf_free(buf);
+
+   eina_shutdown();
+}
+END_TEST
+
+
 void
 eina_test_binbuf(TCase *tc)
 {
@@ -315,4 +383,5 @@ eina_test_binbuf(TCase *tc)
    tcase_add_test(tc, binbuf_realloc);
    tcase_add_test(tc, binbuf_manage_simple);
    tcase_add_test(tc, binbuf_manage_read_only_simple);
+   tcase_add_test(tc, binbuf_expand);
 }