1
0
mirror of git://jb55.com/damus synced 2024-09-16 02:03:45 +00:00
damus/nostrdb/flatcc/flatcc_emitter.h
2023-08-25 19:05:34 -07:00

216 lines
7.2 KiB
C

#ifndef FLATCC_EMITTER_H
#define FLATCC_EMITTER_H
#ifdef __cplusplus
extern "C" {
#endif
/*
* Default implementation of a flatbuilder emitter.
*
* This may be used as a starting point for more advanced emitters,
* for example writing completed pages to disk or network and
* the recycling those pages.
*/
#include <stdlib.h>
#include <string.h>
#include "flatcc_types.h"
#include "flatcc_iov.h"
#include "flatcc_alloc.h"
/*
* The buffer steadily grows during emission but the design allows for
* an extension where individual pages can recycled before the buffer
* is complete, for example because they have been transmitted.
*
* When done, the buffer can be cleared to free all memory, or reset to
* maintain an adaptive page pool for next buffer construction.
*
* Unlike an exponentially growing buffer, each buffer page remains
* stable in memory until reset, clear or recycle is called.
*
* Design notes for possible extensions:
*
* The buffer is a ring buffer marked by a front and a back page. The
* front and back may be the same page and may initially be absent.
* Anything outside these pages are unallocated pages for recycling.
* Any page between (but excluding) the front and back pages may be
* recycled by unlinking and relinking outside the front and back pages
* but then copy operations no longer makes sense. Each page stores the
* logical offset within the buffer but isn't otherwise used by the
* implemention - it might be used for network transmission. The buffer
* is not explicitly designed for multithreaded access but any page
* strictly between front and back is not touched unless recycled and in
* this case aligned allocation is useful to prevent cache line sharing.
*/
/*
* Memory is allocated in fixed length page units - the first page is
* split between front and back so each get half the page size. If the
* size is a multiple of 128 then each page offset will be a multiple of
* 64, which may be useful for sequencing etc.
*/
#ifndef FLATCC_EMITTER_PAGE_SIZE
#define FLATCC_EMITTER_MAX_PAGE_SIZE 3000
#define FLATCC_EMITTER_PAGE_MULTIPLE 64
#define FLATCC_EMITTER_PAGE_SIZE ((FLATCC_EMITTER_MAX_PAGE_SIZE) &\
~(2 * (FLATCC_EMITTER_PAGE_MULTIPLE) - 1))
#endif
#ifndef FLATCC_EMITTER_ALLOC
#ifdef FLATCC_EMITTER_USE_ALIGNED_ALLOC
/*
* <stdlib.h> does not always provide aligned_alloc, so include whatever
* is required when enabling this feature.
*/
#define FLATCC_EMITTER_ALLOC(n) aligned_alloc(FLATCC_EMITTER_PAGE_MULTIPLE,\
(((n) + FLATCC_EMITTER_PAGE_MULTIPLE - 1) & ~(FLATCC_EMITTER_PAGE_MULTIPLE - 1)))
#ifndef FLATCC_EMITTER_FREE
#define FLATCC_EMITTER_FREE(p) aligned_free(p)
#endif
#endif
#endif
#ifndef FLATCC_EMITTER_ALLOC
#define FLATCC_EMITTER_ALLOC(n) FLATCC_ALLOC(n)
#endif
#ifndef FLATCC_EMITTER_FREE
#define FLATCC_EMITTER_FREE(p) FLATCC_FREE(p)
#endif
typedef struct flatcc_emitter_page flatcc_emitter_page_t;
typedef struct flatcc_emitter flatcc_emitter_t;
struct flatcc_emitter_page {
uint8_t page[FLATCC_EMITTER_PAGE_SIZE];
flatcc_emitter_page_t *next;
flatcc_emitter_page_t *prev;
/*
* The offset is relative to page start, but not necessarily
* to any present content if part of front or back page,
* and undefined for unused pages.
*/
flatbuffers_soffset_t page_offset;
};
/*
* Must be allocated and zeroed externally, e.g. on the stack
* then provided as emit_context to the flatbuilder along
* with the `flatcc_emitter` function.
*/
struct flatcc_emitter {
flatcc_emitter_page_t *front, *back;
uint8_t *front_cursor;
size_t front_left;
uint8_t *back_cursor;
size_t back_left;
size_t used;
size_t capacity;
size_t used_average;
};
/* Optional helper to ensure emitter is zeroed initially. */
static inline void flatcc_emitter_init(flatcc_emitter_t *E)
{
memset(E, 0, sizeof(*E));
}
/* Deallocates all buffer memory making the emitter ready for next use. */
void flatcc_emitter_clear(flatcc_emitter_t *E);
/*
* Similar to `clear_flatcc_emitter` but heuristacally keeps some allocated
* memory between uses while gradually reducing peak allocations.
* For small buffers, a single page will remain available with no
* additional allocations or deallocations after first use.
*/
void flatcc_emitter_reset(flatcc_emitter_t *E);
/*
* Helper function that allows a page between front and back to be
* recycled while the buffer is still being constructed - most likely as part
* of partial copy or transmission. Attempting to recycle front or back
* pages will result in an error. Recycling pages outside the
* front and back will be valid but pointless. After recycling and copy
* operations are no longer well-defined and should be replaced with
* whatever logic is recycling the pages. The reset operation
* automatically recycles all (remaining) pages when emission is
* complete. After recycling, the `flatcc_emitter_size` function will
* return as if recycle was not called, but will only represent the
* logical size, not the size of the active buffer. Because a recycled
* page is fully utilized, it is fairly easy to compensate for this if
* required.
*
* Returns 0 on success.
*/
int flatcc_emitter_recycle_page(flatcc_emitter_t *E, flatcc_emitter_page_t *p);
/*
* The amount of data copied with `flatcc_emitter_copy_buffer` and related
* functions. Normally called at end of buffer construction but is
* always valid, as is the copy functions. The size is a direct
* function of the amount emitted data so the flatbuilder itself can
* also provide this information.
*/
static inline size_t flatcc_emitter_get_buffer_size(flatcc_emitter_t *E)
{
return E->used;
}
/*
* Returns buffer start iff the buffer fits on a single internal page.
* Only useful for fairly small buffers - about half the page size since
* one half of first page goes to vtables that likely use little space.
* Returns null if request could not be served.
*
* If `size_out` is not null, it is set to the buffer size, or 0 if
* operation failed.
*/
static inline void *flatcc_emitter_get_direct_buffer(flatcc_emitter_t *E, size_t *size_out)
{
if (E->front == E->back) {
if (size_out) {
*size_out = E->used;
}
return E->front_cursor;
}
if (size_out) {
*size_out = 0;
}
return 0;
}
/*
* Copies the internal flatcc_emitter representation to an externally
* provided linear buffer that must have size `flatcc_emitter_get_size`.
*
* If pages have been recycled, only the remaining pages will be copied
* and thus less data than what `flatcc_emitter_get_size` would suggest. It
* makes more sense to provide a customized copy operation when
* recycling pages.
*
* If the buffer is too small, nothing is copied, otherwise the
* full buffer is copied and the input buffer is returned.
*/
void *flatcc_emitter_copy_buffer(flatcc_emitter_t *E, void *buf, size_t size);
/*
* The emitter interface function to the flatbuilder API.
* `emit_context` should be of type `flatcc_emitter_t` for this
* particular implementation.
*
* This function is compatible with the `flatbuilder_emit_fun`
* type defined in "flatbuilder.h".
*/
int flatcc_emitter(void *emit_context,
const flatcc_iovec_t *iov, int iov_count,
flatbuffers_soffset_t offset, size_t len);
#ifdef __cplusplus
}
#endif
#endif /* FLATCC_EMITTER_H */