StringStorage.cpp 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. /*
  2. * This source file is part of libRocket, the HTML/CSS Interface Middleware
  3. *
  4. * For the latest information, see http://www.librocket.com
  5. *
  6. * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
  7. *
  8. * Permission is hereby granted, free of charge, to any person obtaining a copy
  9. * of this software and associated documentation files (the "Software"), to deal
  10. * in the Software without restriction, including without limitation the rights
  11. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  12. * copies of the Software, and to permit persons to whom the Software is
  13. * furnished to do so, subject to the following conditions:
  14. *
  15. * The above copyright notice and this permission notice shall be included in
  16. * all copies or substantial portions of the Software.
  17. *
  18. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  24. * THE SOFTWARE.
  25. *
  26. */
  27. #include "precompiled.h"
  28. #include <Rocket/Core/StringStorage.h>
  29. namespace Rocket {
  30. namespace Core {
  31. const int MIN_STRING = 16; // Smallest string size.
  32. const int NUM_POOLS = 4; // Number of pools we have, in powers of 2 starting from the smallest string (16, 32, 64 and 128)
  33. const int ROCKET_MAX_POOL_SIZE = 128; // Maximum number of free strings to keep in a pool
  34. const int ROCKET_MAX_HASH_LENGTH = 32; // Maximum number of character to use when calculating the string hash (saves hashing HUGE strings)
  35. #define HASH(hval, string, length) \
  36. { \
  37. hval = 0; \
  38. unsigned char *bp = (unsigned char *)string; \
  39. unsigned char *be = bp + length; \
  40. \
  41. while (bp < be) \
  42. { \
  43. hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24); \
  44. hval ^= *bp++; \
  45. } \
  46. }
  47. struct StringEntry
  48. {
  49. char* buffer;
  50. size_t reference_count;
  51. StringEntry* next;
  52. StringEntry* prev;
  53. Hash hash;
  54. };
  55. struct Storage
  56. {
  57. typedef std::map<Hash, StringEntry*> Lookup;
  58. Lookup lookup;
  59. typedef std::list< char* > StringPool;
  60. StringPool pools[NUM_POOLS];
  61. };
  62. static char empty_string_buffer[] = {0, 0, 0, 0};
  63. char* StringStorage::empty_string = empty_string_buffer;
  64. static Storage* storage = NULL;
  65. #define BLOCK_SIZE(string_size) (string_size >= 1024 ? 1024 : (string_size >= 128 ? 128 : (string_size > MIN_STRING ? Math::ToPowerOfTwo(string_size) : MIN_STRING)))
  66. #define ALLOC_SIZE(string_size) (((string_size / BLOCK_SIZE(string_size)) + 1) * BLOCK_SIZE(string_size))
  67. // Clears all shared strings from the string pools.
  68. void StringStorage::ClearPools()
  69. {
  70. for (int i = 0; i < NUM_POOLS; ++i)
  71. {
  72. for (Storage::StringPool::iterator iterator = storage->pools[i].begin(); iterator != storage->pools[i].end(); ++iterator)
  73. free(*iterator);
  74. storage->pools[i].clear();
  75. }
  76. }
  77. char* StringStorage::ReallocString(char* string, size_t old_length, size_t new_length, size_t character_size)
  78. {
  79. ROCKET_ASSERT(old_length < (1 << 24));
  80. ROCKET_ASSERT(new_length < (1 << 24));
  81. ROCKET_ASSERT(new_length >= old_length);
  82. size_t new_size = (new_length + 1) * character_size;
  83. if (string == empty_string)
  84. {
  85. string = NULL;
  86. }
  87. else if (string != NULL)
  88. {
  89. size_t old_size = (old_length + 1) * character_size;
  90. if (new_size < ALLOC_SIZE(old_size))
  91. return string;
  92. }
  93. size_t alloc_size = ALLOC_SIZE(new_size);
  94. ROCKET_ASSERT(alloc_size > new_size);
  95. // Check if we can use an old allocation from our pools
  96. if (alloc_size < (MIN_STRING << NUM_POOLS))
  97. {
  98. // Ensure we have storage for strings
  99. if (!storage)
  100. storage = new Storage();
  101. size_t pool_index = 0;
  102. size_t size = alloc_size;
  103. while (size > MIN_STRING)
  104. {
  105. pool_index++;
  106. size = size >> 1;
  107. }
  108. ROCKET_ASSERT(pool_index < NUM_POOLS);
  109. if (!storage->pools[pool_index].empty())
  110. {
  111. char* new_string = storage->pools[pool_index].front();
  112. storage->pools[pool_index].pop_front();
  113. if (string)
  114. {
  115. // Copy the correct number of values across and null terminate
  116. size_t copy_size = (old_length < new_length ? old_length : new_length) * character_size;
  117. memcpy(new_string, string, copy_size);
  118. memset(&new_string[copy_size], 0, character_size);
  119. ReleaseString(string, old_length);
  120. }
  121. return new_string;
  122. }
  123. }
  124. // Standard realloc
  125. return (char*) realloc(string, alloc_size);
  126. }
  127. void StringStorage::ReleaseString(char* string, size_t size)
  128. {
  129. if (string == empty_string)
  130. return;
  131. if (storage == NULL)
  132. {
  133. free(string);
  134. return;
  135. }
  136. size_t alloc_size = ALLOC_SIZE(size);
  137. if (alloc_size < (MIN_STRING << NUM_POOLS))
  138. {
  139. size_t pool_index = 0;
  140. size_t size = alloc_size;
  141. while (size > MIN_STRING)
  142. {
  143. pool_index++;
  144. size = size >> 1;
  145. }
  146. ROCKET_ASSERT(pool_index < NUM_POOLS);
  147. if (storage->pools[pool_index].size() < ROCKET_MAX_POOL_SIZE)
  148. {
  149. storage->pools[pool_index].push_back(string);
  150. return;
  151. }
  152. }
  153. free(string);
  154. }
  155. StringStorage::StringID StringStorage::AddString(const char* &string, size_t string_length, size_t character_size)
  156. {
  157. size_t length = string_length * character_size;
  158. // Ensure we have storage for strings
  159. if (!storage)
  160. storage = new Storage();
  161. // Hash the incoming string
  162. Hash hash;
  163. size_t hash_length = (length < ROCKET_MAX_HASH_LENGTH ? length : ROCKET_MAX_HASH_LENGTH);
  164. HASH(hash, string, hash_length);
  165. // See if we can find the entry group for this hash ( strings with the same hash )
  166. Storage::Lookup::iterator itr = storage->lookup.find(hash);
  167. StringEntry* entry_group = NULL;
  168. if (itr != storage->lookup.end())
  169. {
  170. // If we found it, iterate all the strings in the group
  171. // looking for this specific string, if its found,
  172. // increase reference count and return it
  173. entry_group = (*itr).second;
  174. StringEntry* entry = entry_group;
  175. while (entry)
  176. {
  177. // If the memory check passes and the null terminator exists in the correct place
  178. if (memcmp(entry->buffer, string, length) == 0 && memcmp(&entry->buffer[length], empty_string, character_size) == 0)
  179. {
  180. free((char*)string);
  181. entry->reference_count++;
  182. string = entry->buffer;
  183. return (StringID)entry;
  184. }
  185. entry = entry->next;
  186. }
  187. }
  188. // Create a new entry
  189. StringEntry* entry = new StringEntry();
  190. entry->reference_count = 1;
  191. entry->next = NULL;
  192. entry->prev = NULL;
  193. entry->hash = hash;
  194. entry->buffer = (char*)string;
  195. // If we found an entry group for this string earlier,
  196. // insert the string size_to this entry group
  197. if (entry_group)
  198. {
  199. if (entry_group->next)
  200. {
  201. entry_group->next->prev = entry;
  202. entry->next = entry_group->next;
  203. }
  204. entry->prev = entry_group;
  205. entry_group->next = entry;
  206. }
  207. else
  208. {
  209. // Otherwise add as a new entry group
  210. storage->lookup[hash] = entry;
  211. }
  212. return (StringID)entry;
  213. }
  214. void StringStorage::AddReference(StringID string_id)
  215. {
  216. if (string_id == 0)
  217. return;
  218. // Simply increase the reference count on the given string id
  219. StringEntry* entry = (StringEntry*)string_id;
  220. entry->reference_count++;
  221. }
  222. void StringStorage::RemoveReference(StringID string_id)
  223. {
  224. if (string_id == 0)
  225. return;
  226. StringEntry* entry = (StringEntry*)string_id;
  227. ROCKET_ASSERT(entry->reference_count > 0);
  228. entry->reference_count--;
  229. if (entry->reference_count > 0)
  230. return;
  231. if (storage)
  232. {
  233. // If this is the only string in the hash group (hopefully most common case),
  234. // then we just remove it from the lookup table
  235. if (entry->prev == NULL && entry->next == NULL)
  236. {
  237. storage->lookup.erase(entry->hash);
  238. }
  239. // If we have a next and a previous, just remove us from the middle
  240. else if (entry->prev && entry->next)
  241. {
  242. entry->prev->next = entry->next;
  243. entry->next->prev = entry->prev;
  244. }
  245. // If we have a next only, we need to update the map index
  246. else if (entry->next)
  247. {
  248. storage->lookup[entry->hash] = entry->next;
  249. entry->next->prev = NULL;
  250. }
  251. // Otherwise we only have a previous, just remove us from the chain
  252. else
  253. {
  254. entry->prev->next = NULL;
  255. }
  256. ROCKET_ASSERT(storage->lookup.find(entry->hash) == storage->lookup.end() || (*storage->lookup.find(entry->hash)).second != entry);
  257. }
  258. free(entry->buffer);
  259. delete entry;
  260. }
  261. void StringStorage::OnLibraryShutdown()
  262. {
  263. ClearPools();
  264. delete storage;
  265. storage = NULL;
  266. }
  267. }
  268. }