flatcc_emitter.h (7416B)
1 #ifndef FLATCC_EMITTER_H 2 #define FLATCC_EMITTER_H 3 4 #ifdef __cplusplus 5 extern "C" { 6 #endif 7 8 /* 9 * Default implementation of a flatbuilder emitter. 10 * 11 * This may be used as a starting point for more advanced emitters, 12 * for example writing completed pages to disk or network and 13 * the recycling those pages. 14 */ 15 16 #include <stdlib.h> 17 #include <string.h> 18 19 #include "flatcc_types.h" 20 #include "flatcc_iov.h" 21 #include "flatcc_alloc.h" 22 23 /* 24 * The buffer steadily grows during emission but the design allows for 25 * an extension where individual pages can recycled before the buffer 26 * is complete, for example because they have been transmitted. 27 * 28 * When done, the buffer can be cleared to free all memory, or reset to 29 * maintain an adaptive page pool for next buffer construction. 30 * 31 * Unlike an exponentially growing buffer, each buffer page remains 32 * stable in memory until reset, clear or recycle is called. 33 * 34 * Design notes for possible extensions: 35 * 36 * The buffer is a ring buffer marked by a front and a back page. The 37 * front and back may be the same page and may initially be absent. 38 * Anything outside these pages are unallocated pages for recycling. 39 * Any page between (but excluding) the front and back pages may be 40 * recycled by unlinking and relinking outside the front and back pages 41 * but then copy operations no longer makes sense. Each page stores the 42 * logical offset within the buffer but isn't otherwise used by the 43 * implemention - it might be used for network transmission. The buffer 44 * is not explicitly designed for multithreaded access but any page 45 * strictly between front and back is not touched unless recycled and in 46 * this case aligned allocation is useful to prevent cache line sharing. 47 */ 48 49 /* 50 * Memory is allocated in fixed length page units - the first page is 51 * split between front and back so each get half the page size. If the 52 * size is a multiple of 128 then each page offset will be a multiple of 53 * 64, which may be useful for sequencing etc. 54 */ 55 #ifndef FLATCC_EMITTER_PAGE_SIZE 56 #define FLATCC_EMITTER_MAX_PAGE_SIZE 3000 57 #define FLATCC_EMITTER_PAGE_MULTIPLE 64 58 #define FLATCC_EMITTER_PAGE_SIZE ((FLATCC_EMITTER_MAX_PAGE_SIZE) &\ 59 ~(2 * (FLATCC_EMITTER_PAGE_MULTIPLE) - 1)) 60 #endif 61 62 #ifndef FLATCC_EMITTER_ALLOC 63 #ifdef FLATCC_EMITTER_USE_ALIGNED_ALLOC 64 /* 65 * <stdlib.h> does not always provide aligned_alloc, so include whatever 66 * is required when enabling this feature. 67 */ 68 #define FLATCC_EMITTER_ALLOC(n) aligned_alloc(FLATCC_EMITTER_PAGE_MULTIPLE,\ 69 (((n) + FLATCC_EMITTER_PAGE_MULTIPLE - 1) & ~(FLATCC_EMITTER_PAGE_MULTIPLE - 1))) 70 #ifndef FLATCC_EMITTER_FREE 71 #define FLATCC_EMITTER_FREE(p) aligned_free(p) 72 #endif 73 #endif 74 #endif 75 76 #ifndef FLATCC_EMITTER_ALLOC 77 #define FLATCC_EMITTER_ALLOC(n) FLATCC_ALLOC(n) 78 #endif 79 #ifndef FLATCC_EMITTER_FREE 80 #define FLATCC_EMITTER_FREE(p) FLATCC_FREE(p) 81 #endif 82 83 typedef struct flatcc_emitter_page flatcc_emitter_page_t; 84 typedef struct flatcc_emitter flatcc_emitter_t; 85 86 struct flatcc_emitter_page { 87 uint8_t page[FLATCC_EMITTER_PAGE_SIZE]; 88 flatcc_emitter_page_t *next; 89 flatcc_emitter_page_t *prev; 90 /* 91 * The offset is relative to page start, but not necessarily 92 * to any present content if part of front or back page, 93 * and undefined for unused pages. 94 */ 95 flatbuffers_soffset_t page_offset; 96 }; 97 98 /* 99 * Must be allocated and zeroed externally, e.g. on the stack 100 * then provided as emit_context to the flatbuilder along 101 * with the `flatcc_emitter` function. 102 */ 103 struct flatcc_emitter { 104 flatcc_emitter_page_t *front, *back; 105 uint8_t *front_cursor; 106 size_t front_left; 107 uint8_t *back_cursor; 108 size_t back_left; 109 size_t used; 110 size_t capacity; 111 size_t used_average; 112 }; 113 114 /* Optional helper to ensure emitter is zeroed initially. */ 115 static inline void flatcc_emitter_init(flatcc_emitter_t *E) 116 { 117 memset(E, 0, sizeof(*E)); 118 } 119 120 /* Deallocates all buffer memory making the emitter ready for next use. */ 121 void flatcc_emitter_clear(flatcc_emitter_t *E); 122 123 /* 124 * Similar to `clear_flatcc_emitter` but heuristacally keeps some allocated 125 * memory between uses while gradually reducing peak allocations. 126 * For small buffers, a single page will remain available with no 127 * additional allocations or deallocations after first use. 128 */ 129 void flatcc_emitter_reset(flatcc_emitter_t *E); 130 131 /* 132 * Helper function that allows a page between front and back to be 133 * recycled while the buffer is still being constructed - most likely as part 134 * of partial copy or transmission. Attempting to recycle front or back 135 * pages will result in an error. Recycling pages outside the 136 * front and back will be valid but pointless. After recycling and copy 137 * operations are no longer well-defined and should be replaced with 138 * whatever logic is recycling the pages. The reset operation 139 * automatically recycles all (remaining) pages when emission is 140 * complete. After recycling, the `flatcc_emitter_size` function will 141 * return as if recycle was not called, but will only represent the 142 * logical size, not the size of the active buffer. Because a recycled 143 * page is fully utilized, it is fairly easy to compensate for this if 144 * required. 145 * 146 * Returns 0 on success. 147 */ 148 int flatcc_emitter_recycle_page(flatcc_emitter_t *E, flatcc_emitter_page_t *p); 149 150 /* 151 * The amount of data copied with `flatcc_emitter_copy_buffer` and related 152 * functions. Normally called at end of buffer construction but is 153 * always valid, as is the copy functions. The size is a direct 154 * function of the amount emitted data so the flatbuilder itself can 155 * also provide this information. 156 */ 157 static inline size_t flatcc_emitter_get_buffer_size(flatcc_emitter_t *E) 158 { 159 return E->used; 160 } 161 162 /* 163 * Returns buffer start iff the buffer fits on a single internal page. 164 * Only useful for fairly small buffers - about half the page size since 165 * one half of first page goes to vtables that likely use little space. 166 * Returns null if request could not be served. 167 * 168 * If `size_out` is not null, it is set to the buffer size, or 0 if 169 * operation failed. 170 */ 171 static inline void *flatcc_emitter_get_direct_buffer(flatcc_emitter_t *E, size_t *size_out) 172 { 173 if (E->front == E->back) { 174 if (size_out) { 175 *size_out = E->used; 176 } 177 return E->front_cursor; 178 } 179 if (size_out) { 180 *size_out = 0; 181 } 182 return 0; 183 } 184 185 /* 186 * Copies the internal flatcc_emitter representation to an externally 187 * provided linear buffer that must have size `flatcc_emitter_get_size`. 188 * 189 * If pages have been recycled, only the remaining pages will be copied 190 * and thus less data than what `flatcc_emitter_get_size` would suggest. It 191 * makes more sense to provide a customized copy operation when 192 * recycling pages. 193 * 194 * If the buffer is too small, nothing is copied, otherwise the 195 * full buffer is copied and the input buffer is returned. 196 */ 197 void *flatcc_emitter_copy_buffer(flatcc_emitter_t *E, void *buf, size_t size); 198 199 /* 200 * The emitter interface function to the flatbuilder API. 201 * `emit_context` should be of type `flatcc_emitter_t` for this 202 * particular implementation. 203 * 204 * This function is compatible with the `flatbuilder_emit_fun` 205 * type defined in "flatbuilder.h". 206 */ 207 int flatcc_emitter(void *emit_context, 208 const flatcc_iovec_t *iov, int iov_count, 209 flatbuffers_soffset_t offset, size_t len); 210 211 #ifdef __cplusplus 212 } 213 #endif 214 215 #endif /* FLATCC_EMITTER_H */