src/buffer.c

changeset 1571
25ead2ffb9b5
parent 1542
197450c2b0b3
equal deleted inserted replaced
1570:8fd491bc2940 1571:25ead2ffb9b5
64 buffer->flags |= CX_BUFFER_FREE_CONTENTS; 64 buffer->flags |= CX_BUFFER_FREE_CONTENTS;
65 } else { 65 } else {
66 buffer->bytes = space; 66 buffer->bytes = space;
67 } 67 }
68 buffer->capacity = capacity; 68 buffer->capacity = capacity;
69 buffer->max_capacity = SIZE_MAX;
69 buffer->size = 0; 70 buffer->size = 0;
70 buffer->pos = 0; 71 buffer->pos = 0;
71 72
72 buffer->flush = NULL;
73
74 return 0;
75 }
76
77 int cxBufferEnableFlushing(
78 CxBuffer *buffer,
79 CxBufferFlushConfig config
80 ) {
81 buffer->flush = cxMallocDefault(sizeof(CxBufferFlushConfig));
82 if (buffer->flush == NULL) return -1; // LCOV_EXCL_LINE
83 memcpy(buffer->flush, &config, sizeof(CxBufferFlushConfig));
84 return 0; 73 return 0;
85 } 74 }
86 75
87 void cxBufferDestroy(CxBuffer *buffer) { 76 void cxBufferDestroy(CxBuffer *buffer) {
88 if (buffer->flags & CX_BUFFER_FREE_CONTENTS) { 77 if (buffer->flags & CX_BUFFER_FREE_CONTENTS) {
89 cxFree(buffer->allocator, buffer->bytes); 78 cxFree(buffer->allocator, buffer->bytes);
90 } 79 }
91 cxFreeDefault(buffer->flush);
92 memset(buffer, 0, sizeof(CxBuffer)); 80 memset(buffer, 0, sizeof(CxBuffer));
93 } 81 }
94 82
95 CxBuffer *cxBufferCreate( 83 CxBuffer *cxBufferCreate(
96 void *space, 84 void *space,
211 199
212 int cxBufferReserve(CxBuffer *buffer, size_t newcap) { 200 int cxBufferReserve(CxBuffer *buffer, size_t newcap) {
213 if (newcap == buffer->capacity) { 201 if (newcap == buffer->capacity) {
214 return 0; 202 return 0;
215 } 203 }
204 if (newcap > buffer->max_capacity) {
205 return -1;
206 }
216 const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND; 207 const int force_copy_flags = CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_COPY_ON_EXTEND;
217 if (buffer->flags & force_copy_flags) { 208 if (buffer->flags & force_copy_flags) {
218 void *newspace = cxMalloc(buffer->allocator, newcap); 209 void *newspace = cxMalloc(buffer->allocator, newcap);
219 if (NULL == newspace) return -1; 210 if (NULL == newspace) return -1;
220 memcpy(newspace, buffer->space, buffer->size); 211 memcpy(newspace, buffer->space, buffer->size);
234 } else { 225 } else {
235 return -1; // LCOV_EXCL_LINE 226 return -1; // LCOV_EXCL_LINE
236 } 227 }
237 } 228 }
238 229
239 static size_t cx_buffer_calculate_minimum_capacity(size_t mincap) { 230 int cxBufferMaximumCapacity(CxBuffer *buffer, size_t capacity) {
240 unsigned long pagesize = cx_system_page_size(); 231 if (capacity < buffer->capacity) {
241 // if page size is larger than 64 KB - for some reason - truncate to 64 KB 232 return -1;
242 if (pagesize > 65536) pagesize = 65536; 233 }
243 if (mincap < pagesize) { 234 buffer->max_capacity = capacity;
244 // when smaller as one page, map to the next power of two 235 return 0;
245 mincap--;
246 mincap |= mincap >> 1;
247 mincap |= mincap >> 2;
248 mincap |= mincap >> 4;
249 // last operation only needed for pages larger 4096 bytes
250 // but if/else would be more expensive than just doing this
251 mincap |= mincap >> 8;
252 mincap++;
253 } else {
254 // otherwise, map to a multiple of the page size
255 mincap -= mincap % pagesize;
256 mincap += pagesize;
257 // note: if newcap is already page aligned,
258 // this gives a full additional page (which is good)
259 }
260 return mincap;
261 } 236 }
262 237
263 int cxBufferMinimumCapacity(CxBuffer *buffer, size_t newcap) { 238 int cxBufferMinimumCapacity(CxBuffer *buffer, size_t newcap) {
264 if (newcap <= buffer->capacity) { 239 if (newcap <= buffer->capacity) {
265 return 0; 240 return 0;
266 } 241 }
267 newcap = cx_buffer_calculate_minimum_capacity(newcap); 242 if (newcap > buffer->max_capacity) {
243 return -1;
244 }
245 if (newcap < buffer->max_capacity) {
246 unsigned long pagesize = cx_system_page_size();
247 // if page size is larger than 64 KB - for some reason - truncate to 64 KB
248 if (pagesize > 65536) pagesize = 65536;
249 if (newcap < pagesize) {
250 // when smaller as one page, map to the next power of two
251 newcap--;
252 newcap |= newcap >> 1;
253 newcap |= newcap >> 2;
254 newcap |= newcap >> 4;
255 // last operation only needed for pages larger 4096 bytes
256 // but if/else would be more expensive than just doing this
257 newcap |= newcap >> 8;
258 newcap++;
259 } else {
260 // otherwise, map to a multiple of the page size
261 newcap -= newcap % pagesize;
262 newcap += pagesize;
263 // note: if newcap is already page aligned,
264 // this gives a full additional page (which is good)
265 }
266 if (newcap > buffer->max_capacity) {
267 newcap = buffer->max_capacity;
268 }
269 }
268 return cxBufferReserve(buffer, newcap); 270 return cxBufferReserve(buffer, newcap);
269 } 271 }
270 272
271 void cxBufferShrink( 273 void cxBufferShrink(
272 CxBuffer *buffer, 274 CxBuffer *buffer,
286 if (newCapacity < buffer->capacity) { 288 if (newCapacity < buffer->capacity) {
287 if (0 == cxReallocate(buffer->allocator, &buffer->bytes, newCapacity)) { 289 if (0 == cxReallocate(buffer->allocator, &buffer->bytes, newCapacity)) {
288 buffer->capacity = newCapacity; 290 buffer->capacity = newCapacity;
289 } 291 }
290 } 292 }
291 }
292
293 static size_t cx_buffer_flush_helper(
294 const CxBuffer *buffer,
295 const unsigned char *src,
296 size_t size,
297 size_t nitems
298 ) {
299 // flush data from an arbitrary source
300 // does not need to be the buffer's contents
301 size_t max_items = buffer->flush->blksize / size;
302 size_t fblocks = 0;
303 size_t flushed_total = 0;
304 while (nitems > 0 && fblocks < buffer->flush->blkmax) {
305 fblocks++;
306 size_t items = nitems > max_items ? max_items : nitems;
307 size_t flushed = buffer->flush->wfunc(
308 src, size, items, buffer->flush->target);
309 if (flushed > 0) {
310 flushed_total += flushed;
311 src += flushed * size;
312 nitems -= flushed;
313 } else {
314 // if no bytes can be flushed out anymore, we give up
315 break;
316 }
317 }
318 return flushed_total;
319 }
320
321 static size_t cx_buffer_flush_impl(CxBuffer *buffer, size_t size) {
322 // flush the current contents of the buffer
323 unsigned char *space = buffer->bytes;
324 size_t remaining = buffer->pos / size;
325 size_t flushed_total = cx_buffer_flush_helper(
326 buffer, space, size, remaining);
327
328 // shift the buffer left after flushing
329 // IMPORTANT: up to this point, copy on write must have been
330 // performed already, because we can't do error handling here
331 cxBufferShiftLeft(buffer, flushed_total*size);
332
333 return flushed_total;
334 }
335
336 size_t cxBufferFlush(CxBuffer *buffer) {
337 if (buffer_copy_on_write(buffer)) return 0;
338 return cx_buffer_flush_impl(buffer, 1);
339 } 293 }
340 294
341 size_t cxBufferWrite( 295 size_t cxBufferWrite(
342 const void *ptr, 296 const void *ptr,
343 size_t size, 297 size_t size,
353 buffer->size = buffer->pos; 307 buffer->size = buffer->pos;
354 } 308 }
355 return nitems; 309 return nitems;
356 } 310 }
357 311
358 size_t len, total_flushed = 0; 312 size_t len;
359 cx_buffer_write_retry:
360 if (cx_szmul(size, nitems, &len)) { 313 if (cx_szmul(size, nitems, &len)) {
361 errno = EOVERFLOW; 314 errno = EOVERFLOW;
362 return total_flushed; 315 return 0;
363 } 316 }
364 if (buffer->pos > SIZE_MAX - len) { 317 if (buffer->pos > SIZE_MAX - len) {
365 errno = EOVERFLOW; 318 errno = EOVERFLOW;
366 return total_flushed; 319 return 0;
367 } 320 }
368 321 const size_t required = buffer->pos + len;
369 size_t required = buffer->pos + len; 322
370 bool perform_flush = false; 323 // check if we need to auto-extend
371 if (required > buffer->capacity) { 324 if (required > buffer->capacity) {
372 if (buffer->flags & CX_BUFFER_AUTO_EXTEND) { 325 if (buffer->flags & CX_BUFFER_AUTO_EXTEND) {
373 if (buffer->flush != NULL) { 326 size_t newcap = required < buffer->max_capacity
374 size_t newcap = cx_buffer_calculate_minimum_capacity(required); 327 ? required : buffer->max_capacity;
375 if (newcap > buffer->flush->threshold) { 328 if (cxBufferMinimumCapacity(buffer, newcap)) {
376 newcap = buffer->flush->threshold; 329 return 0; // LCOV_EXCL_LINE
377 }
378 if (cxBufferReserve(buffer, newcap)) {
379 return total_flushed; // LCOV_EXCL_LINE
380 }
381 if (required > newcap) {
382 perform_flush = true;
383 }
384 } else {
385 if (cxBufferMinimumCapacity(buffer, required)) {
386 return total_flushed; // LCOV_EXCL_LINE
387 }
388 } 330 }
389 } else { 331 }
390 if (buffer->flush != NULL) { 332 }
391 perform_flush = true; 333
392 } else { 334 // check again and truncate data if capacity is still not enough
393 // truncate data, if we can neither extend nor flush 335 if (required > buffer->capacity) {
394 len = buffer->capacity - buffer->pos; 336 len = buffer->capacity - buffer->pos;
395 if (size > 1) { 337 if (size > 1) {
396 len -= len % size; 338 len -= len % size;
397 } 339 }
398 nitems = len / size; 340 nitems = len / size;
399 }
400 }
401 } 341 }
402 342
403 // check here and not above because of possible truncation 343 // check here and not above because of possible truncation
404 if (len == 0) { 344 if (len == 0) {
405 return total_flushed; 345 return 0;
406 } 346 }
407 347
408 // check if we need to copy 348 // check if we need to copy
409 if (buffer_copy_on_write(buffer)) return 0; 349 if (buffer_copy_on_write(buffer)) return 0;
410 350
411 // perform the operation 351 // perform the operation
412 if (perform_flush) { 352 memcpy(buffer->bytes + buffer->pos, ptr, len);
413 size_t items_flushed; 353 buffer->pos += len;
414 if (buffer->pos == 0) { 354 if (buffer->pos > buffer->size) {
415 // if we don't have data in the buffer, but are instructed 355 buffer->size = buffer->pos;
416 // to flush, it means that we are supposed to relay the data 356 }
417 items_flushed = cx_buffer_flush_helper(buffer, ptr, size, nitems); 357 return nitems;
418 if (items_flushed == 0) {
419 // we needed to relay data, but could not flush anything
420 // i.e. we have to give up to avoid endless trying
421 return 0;
422 }
423 nitems -= items_flushed;
424 total_flushed += items_flushed;
425 if (nitems > 0) {
426 ptr = ((unsigned char*)ptr) + items_flushed * size;
427 goto cx_buffer_write_retry;
428 }
429 return total_flushed;
430 } else {
431 items_flushed = cx_buffer_flush_impl(buffer, size);
432 if (items_flushed == 0) {
433 // flush target is full, let's try to truncate
434 size_t remaining_space;
435 if (buffer->flags & CX_BUFFER_AUTO_EXTEND) {
436 remaining_space = buffer->flush->threshold > buffer->pos
437 ? buffer->flush->threshold - buffer->pos
438 : 0;
439 } else {
440 remaining_space = buffer->capacity > buffer->pos
441 ? buffer->capacity - buffer->pos
442 : 0;
443 }
444 nitems = remaining_space / size;
445 if (nitems == 0) {
446 return total_flushed;
447 }
448 }
449 goto cx_buffer_write_retry;
450 }
451 } else {
452 memcpy(buffer->bytes + buffer->pos, ptr, len);
453 buffer->pos += len;
454 if (buffer->pos > buffer->size) {
455 buffer->size = buffer->pos;
456 }
457 return total_flushed + nitems;
458 }
459 } 358 }
460 359
461 size_t cxBufferAppend( 360 size_t cxBufferAppend(
462 const void *ptr, 361 const void *ptr,
463 size_t size, 362 size_t size,

mercurial