]>
Commit | Line | Data |
---|---|---|
bd6521f0 A |
1 | // |
2 | // PrintBuffer.hpp | |
3 | // system_cmds | |
4 | // | |
5 | // Created by James McIlree on 5/7/14. | |
6 | // | |
7 | // | |
8 | ||
9 | #ifndef __system_cmds__PrintBuffer__ | |
10 | #define __system_cmds__PrintBuffer__ | |
11 | ||
12 | // | |
13 | // Okay, here is how snprintf works. | |
14 | // | |
15 | // char buf[2]; | |
16 | // | |
17 | // snprintf(buf, 0, "a"); // Returns 1, buf is unchanged. | |
18 | // snprintf(buf, 1, "a"); // Returns 1, buf = \0 | |
19 | // snprintf(buf, 2, "a"); // Returns 1, buf = 'a', \0 | |
20 | // | |
21 | // So... For a buffer of size N, each print is valid if and only if | |
22 | // it consumes N-1 bytes. | |
23 | // | |
24 | ||
25 | class PrintBuffer { | |
26 | protected: | |
27 | char* _buffer; | |
28 | size_t _buffer_size; | |
29 | size_t _buffer_capacity; | |
30 | size_t _flush_boundary; | |
31 | int _flush_fd; | |
32 | ||
33 | public: | |
34 | PrintBuffer(size_t capacity, size_t flush_boundary, int flush_fd) : | |
35 | _buffer((char*)malloc(capacity)), | |
36 | _buffer_size(0), | |
37 | _buffer_capacity(capacity), | |
38 | _flush_boundary(flush_boundary), | |
39 | _flush_fd(flush_fd) | |
40 | { | |
41 | ASSERT(capacity > 0, "Sanity"); | |
42 | ASSERT(_buffer, "Sanity"); | |
43 | ASSERT(flush_boundary < capacity, "Sanity"); | |
44 | ASSERT(flush_fd != 0, "Must be a valid fd"); | |
45 | } | |
46 | ||
47 | ~PrintBuffer() { | |
48 | flush(); | |
49 | free(_buffer); | |
50 | } | |
51 | ||
52 | void set_capacity(size_t capacity) { | |
53 | ASSERT(_buffer_size == 0, "Attempt to reallocate buffer while it still contains data"); | |
54 | ||
55 | if (_buffer) { | |
56 | free(_buffer); | |
57 | } | |
58 | ||
59 | _buffer = (char*)malloc(capacity); | |
60 | _buffer_size = 0; | |
61 | _buffer_capacity = capacity; | |
62 | } | |
63 | ||
64 | void flush() { | |
65 | if (_buffer_size) { | |
66 | write(_flush_fd, _buffer, _buffer_size); | |
67 | _buffer_size = 0; | |
68 | } | |
69 | } | |
70 | ||
71 | void printf(const char* format, ...) __attribute__((format(printf, 2, 3))) { | |
72 | repeat: | |
73 | size_t remaining_bytes = _buffer_capacity - _buffer_size; | |
74 | ||
75 | va_list list; | |
76 | va_start(list, format); | |
77 | int bytes_needed = vsnprintf(&_buffer[_buffer_size], remaining_bytes, format, list); | |
78 | va_end(list); | |
79 | ||
80 | // There are three levels of "end" detection. | |
81 | // | |
82 | // 1) If bytes_needed is >= capacity, we must flush, grow capacity, and repeat. | |
83 | // 2) If bytes_needed is >= remaining_bytes, we must flush, and repeat. | |
84 | // 3) If bytes_needed + _buffer_size comes within _flush_boundary bytes of the end, flush. | |
85 | // | |
86 | // NOTE snprintf behavior, we need bytes_needed+1 bytes | |
87 | // to actually fully output all string characters. | |
88 | // | |
89 | // NOTE for any repeat condition, we do not commit the bytes that were written to the buffer. | |
90 | // | |
91 | ||
92 | // Condition 2 | |
93 | if (bytes_needed >= remaining_bytes) { | |
94 | flush(); | |
95 | ||
96 | // Save a common path if test by checking this only inside Condition 2 | |
97 | // | |
98 | // Condition 1 | |
99 | if (bytes_needed >= _buffer_capacity) { | |
100 | set_capacity(bytes_needed+1); | |
101 | } | |
102 | ||
103 | goto repeat; | |
104 | } | |
105 | ||
106 | // Commit the snprintf | |
107 | _buffer_size += bytes_needed; | |
108 | ||
109 | // Condition 3 | |
110 | if (remaining_bytes - bytes_needed <= _flush_boundary) { | |
111 | flush(); | |
112 | } | |
113 | } | |
114 | }; | |
115 | ||
116 | #endif /* defined(__system_cmds__PrintBuffer__) */ |