|
1 | 1 | #include <cstdarg> |
2 | | -#include <cstdio> |
3 | 2 | #include <cstdint> |
| 3 | +#include <cstdio> |
4 | 4 | #include <cstdlib> |
5 | | -#include <string_view> |
6 | | -#include <new> |
7 | | - |
8 | | -using LibRetroLogLevel = std::uint32_t; |
9 | | - |
10 | | -// set to 1 to enable debug messages for the string pool |
11 | | -#define STRINGPOOL_DEBUG 0 |
12 | | - |
13 | | -#if STRINGPOOL_DEBUG |
14 | | - #define STRINGPOOL_DEBUG_PRINTF(fmt, ...) printf("stringpool debug: " fmt, ##__VA_ARGS__) |
15 | | -#else |
16 | | - #define STRINGPOOL_DEBUG_PRINTF(fmt, ...) |
17 | | -#endif |
18 | | - |
19 | | -/// A very simple string pool implemented using a linked list as a very bad freelist. |
20 | | -struct StringPool { |
21 | | - /// The max amount of strings that can be in the string pool's freelist. |
22 | | - constexpr static auto kMaxStringPoolSize = 32; |
23 | | - |
24 | | - /// A pooled string. |
25 | | - struct PooledString { |
26 | | - PooledString() = default; |
27 | | - PooledString(const PooledString&) = delete; |
28 | | - PooledString(PooledString&&) = delete; |
29 | | - |
30 | | - std::string_view GetView() { |
31 | | - return std::string_view(GetPointer(), length); |
32 | | - } |
33 | | - |
34 | | - char* GetPointer() { |
35 | | - // String memory is allocated after this header, so we can just use this. |
36 | | - return reinterpret_cast<char*>(this + 1); |
37 | | - } |
38 | | - |
39 | | - std::size_t GetLength() const { |
40 | | - return length; |
41 | | - } |
42 | | - |
43 | | - protected: |
44 | | - friend StringPool; |
45 | | - PooledString* freeListNext; |
46 | | - std::size_t length; |
47 | | - bool inUse; |
48 | | - }; |
49 | | - |
50 | | - StringPool() = default; |
51 | | - StringPool(const StringPool&) = delete; |
52 | | - StringPool(StringPool&&) = delete; |
53 | | - |
54 | | - ~StringPool() { |
55 | | - Clear(); |
56 | | - } |
57 | | - |
58 | | - /// Either gets a string from the string pool with a suitable capacity, |
59 | | - /// or if no string was found with a suitable capacity that is not in use, allocates one. |
60 | | - /// |
61 | | - /// This function is also permitted to allocate a string if it cannot find |
62 | | - /// a string that's not in use in the freelist, even if it would have found |
63 | | - /// a string with a suitable capacity. |
64 | | - /// |
65 | | - /// May return nullptr if allocating a string (if this function had to allocate) failed. |
66 | | - /// |
67 | | - /// The return value of this function MUST be provided to [StringPool::ReturnString] when |
68 | | - /// the string is no longer in active use. |
69 | | - /// |
70 | | - PooledString* GetString(std::size_t wantedLength) { |
71 | | - PooledString* pIter = freeListHead; |
72 | | - while(pIter) { |
73 | | - // We were able to find a pooled string which has a suitable capacity & is not in use. |
74 | | - // Simply return that string. (Happy path) |
75 | | - if(pIter->length >= wantedLength && !pIter->inUse) { |
76 | | - STRINGPOOL_DEBUG_PRINTF("Found unused string in freelist with suitable length for user request (user request %lu, actual length %lu)\n", wantedLength, pIter->length); |
77 | | - pIter->inUse = true; |
78 | | - return pIter; |
79 | | - } |
80 | | - pIter = pIter->freeListNext; |
81 | | - } |
82 | | - |
83 | | - STRINGPOOL_DEBUG_PRINTF("Could not find string for user requested length %lu, allocating a new one\n", wantedLength); |
84 | | - |
85 | | - // Give up and allocate a new string not on the freelist. (sad path) |
86 | | - return AllocateString(wantedLength); |
87 | | - } |
88 | | - |
89 | | - /// "Returns" a string from the string pool. If the string is not in the freelist, |
90 | | - /// it is added to the freelist, otherwise nothing happens. |
91 | | - void ReturnString(PooledString* pPooledString) { |
92 | | - if(pPooledString == nullptr) |
93 | | - return; |
94 | | - |
95 | | - bool foundInList = false; |
96 | | - |
97 | | - if(freeListHead != nullptr) { |
98 | | - PooledString* pFindIter = freeListHead; |
99 | | - while(pFindIter) { |
100 | | - if(pFindIter == pPooledString) { |
101 | | - STRINGPOOL_DEBUG_PRINTF("Pooled string %p was found in freelist (%p)\n", pPooledString, pFindIter); |
102 | | - foundInList = true; |
103 | | - break; |
104 | | - } |
105 | | - |
106 | | - pFindIter = pFindIter->freeListNext; |
107 | | - } |
108 | | - |
109 | | - if(!foundInList) { |
110 | | - STRINGPOOL_DEBUG_PRINTF("Did not find pooled string %p in list\n", pPooledString); |
111 | | - } |
112 | | - } |
113 | | - |
114 | | - // Mark the string as free. |
115 | | - if(pPooledString->inUse) |
116 | | - pPooledString->inUse = false; |
117 | | - |
118 | | - // The string is already in the pool's freelist, |
119 | | - // so we do not need to re-add it. |
120 | | - if(foundInList) |
121 | | - return; |
122 | | - |
123 | | - STRINGPOOL_DEBUG_PRINTF("Current freelist size: %lu\n", PoolSize()); |
124 | 5 |
|
125 | | - // Make sure the pool doesn't get so large that we start to be more of a memory leak. |
126 | | - // FIXME: This doesn't consider strings which are in use. To make this fully safe for re-entrant usage |
127 | | - // we probably should like.. do that. |
128 | | - if(PoolSize() >= kMaxStringPoolSize) { |
129 | | - STRINGPOOL_DEBUG_PRINTF("Clearing freelist, it is too large.\n"); |
130 | | - Clear(); |
131 | | - } |
| 6 | +#include "string_pool.hpp" |
132 | 7 |
|
133 | | - if(freeListHead == nullptr) { |
134 | | - STRINGPOOL_DEBUG_PRINTF("First freelist node\n"); |
135 | | - freeListHead = pPooledString; |
136 | | - } else { |
137 | | - STRINGPOOL_DEBUG_PRINTF("Not the first freelist node\n"); |
138 | | - |
139 | | - // Walk the freelist for a node which has a null next pointer. |
140 | | - // We will insert there. |
141 | | - PooledString* pInsertIter = freeListHead; |
142 | | - while(true) { |
143 | | - if(pInsertIter->freeListNext == nullptr) |
144 | | - break; |
145 | | - |
146 | | - STRINGPOOL_DEBUG_PRINTF("DEBUG: node %p, next %p\n", pInsertIter, pInsertIter->freeListNext); |
147 | | - pInsertIter = pInsertIter->freeListNext; |
148 | | - } |
149 | | - |
150 | | - pInsertIter->freeListNext = pPooledString; |
151 | | - } |
152 | | - } |
153 | | - |
154 | | - /// Clears the pool's freelist. |
155 | | - void Clear() { |
156 | | - PooledString* pIter = freeListHead; |
157 | | - while(pIter) { |
158 | | - // Need to store the possible next (or lack thereof) |
159 | | - // since we are freeing the pooled string immediately. |
160 | | - // |
161 | | - // Bad, but it uses 16 bytes of stack space at the most, |
162 | | - // compared to needing a list of pointers to free or something. |
163 | | - auto next = pIter->freeListNext; |
164 | | - FreeString(pIter); |
165 | | - pIter = next; |
166 | | - } |
167 | | - |
168 | | - freeListHead = nullptr; |
169 | | - } |
170 | | - |
171 | | - /// Returns the size of the pool's freelist. |
172 | | - std::size_t PoolSize() { |
173 | | - // It is empty. |
174 | | - if(freeListHead == nullptr) |
175 | | - return 0; |
176 | | - |
177 | | - PooledString* pInsertIter = freeListHead; |
178 | | - std::size_t i = 0; |
179 | | - while(pInsertIter) { |
180 | | - i++; |
181 | | - pInsertIter = pInsertIter->freeListNext; |
182 | | - } |
183 | | - |
184 | | - return i; |
185 | | - } |
186 | | - |
187 | | -private: |
188 | | - |
189 | | - PooledString* AllocateString(std::size_t length) { |
190 | | - // A string is allocated with the header (the PooledString structure) at the start, |
191 | | - // then the string data (plus an additional character for the ~~C Mistake~~ NUL terminator) |
192 | | - auto pAlloced = calloc((length + 1 * sizeof(char)) + sizeof(PooledString), 1); |
193 | | - if(pAlloced == nullptr) |
194 | | - return nullptr; |
195 | | - |
196 | | - // The "hip" way of doing this is e.g: std::start_lifetime_as, |
197 | | - // but placement new works just fine, and accomplishes the same goal. |
198 | | - auto pString = new (pAlloced) PooledString; |
199 | | - |
200 | | - // Initialize the pooled string structure. |
201 | | - pString->length = length; |
202 | | - pString->freeListNext = nullptr; |
203 | | - pString->inUse = false; |
204 | | - return pString; |
205 | | - } |
206 | | - |
207 | | - void FreeString(PooledString* pPooled) { |
208 | | - pPooled->~PooledString(); |
209 | | - free(pPooled); |
210 | | - } |
211 | | - |
212 | | - PooledString* freeListHead; |
213 | | -}; |
| 8 | +using LibRetroLogLevel = std::uint32_t; |
214 | 9 |
|
215 | | -StringPool TheStringPool; |
| 10 | +letsplay::StringPool TheLoggerStringPool; |
216 | 11 |
|
217 | 12 | extern "C" { |
218 | 13 |
|
219 | | - /// This function is defined in Rust and recieves our formatted log messages. |
220 | | - void letsplay_retro_frontend_log(LibRetroLogLevel level, const char* buf); |
221 | | - |
222 | | - /// This helper function is given to Rust code to implement the libretro logging |
223 | | - /// (because it's a C-varadic function; that requires nightly/unstable Rust) |
224 | | - /// |
225 | | - /// By implementing it in C++, we can dodge all that and keep using stable rustc. |
226 | | - void letsplay_retro_frontend_libretro_log(LibRetroLogLevel level, const char* format, ...) { |
227 | | - va_list val; |
228 | | - |
229 | | - // First query how long the formatted string would be. |
230 | | - va_start(val, format); |
231 | | - auto formatLength = std::vsnprintf(nullptr, 0, format, val); |
232 | | - if(formatLength == -1) |
233 | | - return; |
234 | | - va_end(val); |
235 | | - |
236 | | - // Try and find (possibly allocating) a string on the string pool with that length. |
237 | | - // If allocating a string fails, give up entirely. |
238 | | - auto* pString = TheStringPool.GetString(formatLength); |
239 | | - if(pString == nullptr) |
240 | | - return; |
241 | | - |
242 | | - auto ptr = pString->GetPointer(); |
243 | | - |
244 | | - va_start(val, format); |
245 | | - // Format the string |
246 | | - std::vsnprintf(ptr, formatLength, format, val); |
247 | | - va_end(val); |
248 | | - |
249 | | - // Remove the last newline and replace it with a null terminator, since |
250 | | - // Tracing will write a newline on its own. |
251 | | - if(ptr[formatLength-1] == '\n') |
252 | | - ptr[formatLength-1] = '\0'; |
253 | | - |
254 | | - // Call the Rust-side reciever. |
255 | | - letsplay_retro_frontend_log(level, ptr); |
256 | | - |
257 | | - // Return the string back to the pool, adding it to the list of |
258 | | - // now freed strings that we can re-use. |
259 | | - TheStringPool.ReturnString(pString); |
260 | | - } |
| 14 | +/// This function is defined in Rust and recieves our formatted log messages. |
| 15 | +void letsplay_retro_frontend_log(LibRetroLogLevel level, const char* buf); |
| 16 | + |
| 17 | +/// This helper function is given to Rust code to implement the libretro logging |
| 18 | +/// (because it's a C-varadic function; that requires nightly/unstable Rust) |
| 19 | +/// |
| 20 | +/// By implementing it in C++, we can dodge all that and keep using stable rustc. |
| 21 | +void letsplay_retro_frontend_libretro_log(LibRetroLogLevel level, const char* format, ...) { |
| 22 | + va_list val; |
| 23 | + |
| 24 | + // First query how long the formatted string would be. |
| 25 | + va_start(val, format); |
| 26 | + auto formatLength = std::vsnprintf(nullptr, 0, format, val); |
| 27 | + if(formatLength == -1) |
| 28 | + return; |
| 29 | + va_end(val); |
| 30 | + |
| 31 | + // Try and find (possibly allocating) a string on the string pool with that length. |
| 32 | + // If allocating a string fails, give up entirely. |
| 33 | + auto* pString = TheLoggerStringPool.GetString(formatLength); |
| 34 | + if(pString == nullptr) |
| 35 | + return; |
| 36 | + |
| 37 | + auto ptr = pString->GetPointer(); |
| 38 | + |
| 39 | + va_start(val, format); |
| 40 | + // Format the string |
| 41 | + std::vsnprintf(ptr, formatLength, format, val); |
| 42 | + va_end(val); |
| 43 | + |
| 44 | + // Remove the last newline and replace it with a null terminator, since |
| 45 | + // Tracing will write a newline on its own. |
| 46 | + if(ptr[formatLength - 1] == '\n') |
| 47 | + ptr[formatLength - 1] = '\0'; |
| 48 | + |
| 49 | + // Call the Rust-side reciever. |
| 50 | + letsplay_retro_frontend_log(level, ptr); |
| 51 | + |
| 52 | + // Return the string back to the pool, adding it to the list of |
| 53 | + // now freed strings that we can re-use. |
| 54 | + TheLoggerStringPool.ReturnString(pString); |
| 55 | +} |
261 | 56 | } |
0 commit comments