src/buffer.c

changeset 1110
a0e9be7ed131
parent 1065
6eb7b54975ee
--- a/src/buffer.c	Sun Jan 05 14:03:30 2025 +0100
+++ b/src/buffer.c	Sun Jan 05 18:19:42 2025 +0100
@@ -71,12 +71,18 @@
     buffer->size = 0;
     buffer->pos = 0;
 
-    buffer->flush_func = NULL;
-    buffer->flush_target = NULL;
-    buffer->flush_blkmax = 0;
-    buffer->flush_blksize = 4096;
-    buffer->flush_threshold = SIZE_MAX;
+    buffer->flush = NULL;
+
+    return 0;
+}
 
+int cxBufferEnableFlushing(
+    CxBuffer *buffer,
+    CxBufferFlushConfig config
+) {
+    buffer->flush = malloc(sizeof(CxBufferFlushConfig));
+    if (buffer->flush == NULL) return -1; // LCOV_EXCL_LINE
+    memcpy(buffer->flush, &config, sizeof(CxBufferFlushConfig));
     return 0;
 }
 
@@ -84,6 +90,7 @@
     if (buffer->flags & CX_BUFFER_FREE_CONTENTS) {
         cxFree(buffer->allocator, buffer->bytes);
     }
+    free(buffer->flush);
     memset(buffer, 0, sizeof(CxBuffer));
 }
 
@@ -196,39 +203,52 @@
     }
 }
 
-/**
- * Helps flushing data to the flush target of a buffer.
- *
- * @param buffer the buffer containing the config
- * @param space the data to flush
- * @param size the element size
- * @param nitems the number of items
- * @return the number of items flushed
- */
-static size_t cx_buffer_write_flush_helper(
-        CxBuffer *buffer,
-        const unsigned char *space,
+static size_t cx_buffer_flush_helper(
+        const CxBuffer *buffer,
         size_t size,
+        const unsigned char *src,
         size_t nitems
 ) {
-    size_t pos = 0;
-    size_t remaining = nitems;
-    size_t max_items = buffer->flush_blksize / size;
-    while (remaining > 0) {
-        size_t items = remaining > max_items ? max_items : remaining;
-        size_t flushed = buffer->flush_func(
-                space + pos,
-                size, items,
-                buffer->flush_target);
+    // flush data from an arbitrary source
+    // does not need to be the buffer's contents
+    size_t max_items = buffer->flush->blksize / size;
+    size_t fblocks = 0;
+    size_t flushed_total = 0;
+    while (nitems > 0 && fblocks < buffer->flush->blkmax) {
+        fblocks++;
+        size_t items = nitems > max_items ? max_items : nitems;
+        size_t flushed = buffer->flush->wfunc(
+            src, size, items, buffer->flush->target);
         if (flushed > 0) {
-            pos += (flushed * size);
-            remaining -= flushed;
+            flushed_total += flushed;
+            src += flushed * size;
+            nitems -= flushed;
         } else {
             // if no bytes can be flushed out anymore, we give up
             break;
         }
     }
-    return nitems - remaining;
+    return flushed_total;
+}
+
+static size_t cx_buffer_flush_impl(CxBuffer *buffer, size_t size) {
+    // flush the current contents of the buffer
+    unsigned char *space = buffer->bytes;
+    size_t remaining = buffer->pos / size;
+    size_t flushed_total = cx_buffer_flush_helper(
+        buffer, size, space, remaining);
+
+    // shift the buffer left after flushing
+    // IMPORTANT: up to this point, copy on write must have been
+    // performed already, because we can't do error handling here
+    cxBufferShiftLeft(buffer, flushed_total*size);
+
+    return flushed_total;
+}
+
+size_t cxBufferFlush(CxBuffer *buffer) {
+    if (buffer_copy_on_write(buffer)) return 0;
+    return cx_buffer_flush_impl(buffer, 1);
 }
 
 size_t cxBufferWrite(
@@ -249,20 +269,20 @@
     }
 
     size_t len;
-    size_t nitems_out = nitems;
     if (cx_szmul(size, nitems, &len)) {
         errno = EOVERFLOW;
         return 0;
     }
+    if (buffer->pos > SIZE_MAX - len) {
+        errno = EOVERFLOW;
+        return 0;
+    }
     size_t required = buffer->pos + len;
-    if (buffer->pos > required) {
-        return 0;
-    }
 
     bool perform_flush = false;
     if (required > buffer->capacity) {
         if (buffer->flags & CX_BUFFER_AUTO_EXTEND) {
-            if (buffer->flush_blkmax > 0 && required > buffer->flush_threshold) {
+            if (buffer->flush != NULL && required > buffer->flush->threshold) {
                 perform_flush = true;
             } else {
                 if (cxBufferMinimumCapacity(buffer, required)) {
@@ -270,71 +290,57 @@
                 }
             }
         } else {
-            if (buffer->flush_blkmax > 0) {
+            if (buffer->flush != NULL) {
                 perform_flush = true;
             } else {
-                // truncate data to be written, if we can neither extend nor flush
+                // truncate data, if we can neither extend nor flush
                 len = buffer->capacity - buffer->pos;
                 if (size > 1) {
                     len -= len % size;
                 }
-                nitems_out = len / size;
+                nitems = len / size;
             }
         }
     }
 
+    // check here and not above because of possible truncation
     if (len == 0) {
-        return len;
+        return 0;
     }
 
-    if (perform_flush) {
-        size_t flush_max;
-        if (cx_szmul(buffer->flush_blkmax, buffer->flush_blksize, &flush_max)) {
-            errno = EOVERFLOW;
-            return 0;
-        }
-        size_t flush_pos = buffer->flush_func == NULL || buffer->flush_target == NULL
-                           ? buffer->pos
-                           : cx_buffer_write_flush_helper(buffer, buffer->bytes, 1, buffer->pos);
-        if (flush_pos == buffer->pos) {
-            // entire buffer has been flushed, we can reset
-            buffer->size = buffer->pos = 0;
-
-            size_t items_flush; // how many items can also be directly flushed
-            size_t items_keep; // how many items have to be written to the buffer
+    // check if we need to copy
+    if (buffer_copy_on_write(buffer)) return 0;
 
-            items_flush = flush_max >= required ? nitems : (flush_max - flush_pos) / size;
-            if (items_flush > 0) {
-                items_flush = cx_buffer_write_flush_helper(buffer, ptr, size, items_flush / size);
-                // in case we could not flush everything, keep the rest
+    // perform the operation
+    if (perform_flush) {
+        size_t items_flush;
+        if (buffer->pos == 0) {
+            // if we don't have data in the buffer, but are instructed
+            // to flush, it means that we are supposed to relay the data
+            items_flush = cx_buffer_flush_helper(buffer, size, ptr, nitems);
+            if (items_flush == 0) {
+                // we needed to flush, but could not flush anything
+                // give up and avoid endless trying
+                return 0;
             }
-            items_keep = nitems - items_flush;
-            if (items_keep > 0) {
-                // try again with the remaining stuff
-                const unsigned char *new_ptr = ptr;
-                new_ptr += items_flush * size;
-                // report the directly flushed items as written plus the remaining stuff
-                return items_flush + cxBufferWrite(new_ptr, size, items_keep, buffer);
-            } else {
-                // all items have been flushed - report them as written
-                return nitems;
+            size_t ritems = nitems - items_flush;
+            const unsigned char *rest = ptr;
+            rest += items_flush * size;
+            return items_flush + cxBufferWrite(rest, size, ritems, buffer);
+        } else {
+            items_flush = cx_buffer_flush_impl(buffer, size);
+            if (items_flush == 0) {
+                return 0;
             }
-        } else if (flush_pos == 0) {
-            // nothing could be flushed at all, we immediately give up without writing any data
-            return 0;
-        } else {
-            // we were partially successful, we shift left and try again
-            cxBufferShiftLeft(buffer, flush_pos);
             return cxBufferWrite(ptr, size, nitems, buffer);
         }
     } else {
-        if (buffer_copy_on_write(buffer)) return 0;
         memcpy(buffer->bytes + buffer->pos, ptr, len);
         buffer->pos += len;
         if (buffer->pos > buffer->size) {
             buffer->size = buffer->pos;
         }
-        return nitems_out;
+        return nitems;
     }
 
 }

mercurial