impl.inc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. /**--------------------------------------------------------------------------**\
  2. ====================================
  3. y_hashmap - Link strings to values
  4. ====================================
  5. Description:
  6. Maps string indexes to integer indexes. Uses a fast hash to get an array
  7. slot, then a linked list to resolve collisions.
  8. Legal:
  9. Version: MPL 1.1
  10. The contents of this file are subject to the Mozilla Public License Version
  11. 1.1 (the "License"); you may not use this file except in compliance with
  12. the License. You may obtain a copy of the License at
  13. http://www.mozilla.org/MPL/
  14. Software distributed under the License is distributed on an "AS IS" basis,
  15. WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  16. for the specific language governing rights and limitations under the
  17. License.
  18. The Original Code is the YSI hashmap include.
  19. The Initial Developer of the Original Code is Alex "Y_Less" Cole.
  20. Portions created by the Initial Developer are Copyright (C) 2011
  21. the Initial Developer. All Rights Reserved.
  22. Contributors:
  23. ZeeX, koolk, JoeBullet/Google63, g_aSlice/Slice
  24. Thanks:
  25. JoeBullet/Google63 - Handy arbitrary ASM jump code using SCTRL.
  26. ZeeX - Very productive conversations.
  27. koolk - IsPlayerinAreaEx code.
  28. TheAlpha - Danish translation.
  29. breadfish - German translation.
  30. Fireburn - Dutch translation.
  31. yom - French translation.
  32. 50p - Polish translation.
  33. Zamaroht - Spanish translation.
  34. Dracoblue, sintax, mabako, Xtreme, other coders - Producing other modes
  35. for me to strive to better.
  36. Pixels^ - Running XScripters where the idea was born.
  37. Matite - Pestering me to release it and using it.
  38. Very special thanks to:
  39. Thiadmer - PAWN, whose limits continue to amaze me!
  40. Kye/Kalcor - SA:MP.
  41. SA:MP Team past, present and future - SA:MP.
  42. Version:
  43. 1.0
  44. Changelog:
  45. 23/06/13:
  46. First version.
  47. Functions:
  48. stock:
  49. HashMap_Init - Associate a hash map with an array.
  50. HashMap_Add - Add a value under a given string.
  51. HashMap_Get - Get a value from a string.
  52. HashMap_RemoveKey - Remove a string and its value from a hash map.
  53. HashMap_RemoveValue - Remove a value from a hash map.
  54. HashMap_Set - Change the value associated with a key.
  55. Definitions:
  56. HASH_MAP_DATA - What should be added to enums to be hash map referenced.
  57. HashMap - Declare a new hash map.
  58. \**--------------------------------------------------------------------------**/
  59. #define HASH_MAP_SIZE (256)
  60. #define HASH_MAP_PTR (HASH_MAP_SIZE)
  61. #define HASH_MAP_SIZE_1 (HASH_MAP_SIZE + 1)
  62. #define HASH_MAP_SIZE_2 (HASH_MAP_SIZE + 2)
  63. #define HASH_MAP_SIZE_3 (HASH_MAP_SIZE + 3)
  64. #define HashMap:%0<%1> %0[HASH_MAP_SIZE + 4]
  65. #define HASH_MAP_DIR_LEFT (4)
  66. #define HASH_MAP_DIR_RIGHT (8)
  67. // There are three pieces of hash map data associated with each stored string.
  68. // In byte offsets, these are:
  69. //
  70. // 0 - The hash of the stored string.
  71. // 4 - The left pointer for the binary tree.
  72. // 8 - The right pointer for the binary tree.
  73. //
  74. #define HASH_MAP_DATA (3)
  75. /**--------------------------------------------------------------------------**\
  76. <summary>HashMap_Hash</summary>
  77. <param name="str[]">String to hash.</param>
  78. <param name="&hash">Desination of the hash.</param>
  79. <returns>
  80. -
  81. </returns>
  82. <remarks>
  83. Quickly hashes the string using Bernstein. Caters for both packed and
  84. unpacked strings.
  85. </remarks>
  86. \**--------------------------------------------------------------------------**/
  87. #define HashMap_Hash(%0,%1) (%1=YHash(%0))
  88. /**--------------------------------------------------------------------------**\
  89. <summary>HashMap_Init</summary>
  90. <param name="HashMap:m<>">Hash map to initialise.</param>
  91. <param name="&target">Address of the hashmap data.</param>
  92. <param name="size1">Number of entries.</param>
  93. <param name="size2">Total Size of each entry IN BYTES.</param>
  94. <param name="&t2">Address of the name AND data start.</param>
  95. <returns>
  96. -
  97. </returns>
  98. <remarks>
  99. Finds the location of the hash map linked list data in the passed array data
  100. and uses that to read the data through pointers subsequently. It doesn't
  101. matter WHERE in the enum the hash map data is, and if its not there you'll
  102. get an error, or at least a warning.
  103. </remarks>
  104. \**--------------------------------------------------------------------------**/
  105. stock _HashMap_Init(HashMap:m<>, &target, size1, size2, &t2)
  106. {
  107. static
  108. HashMap:sInit<> = {0, ...};
  109. m = sInit;
  110. new
  111. ptr;
  112. // Save the pointer.
  113. #emit LOAD.S.pri target
  114. #emit STOR.S.pri ptr
  115. m[HASH_MAP_PTR] = ptr,
  116. // Store the number of elements in the array.
  117. m[HASH_MAP_SIZE_1] = size1,
  118. // Store the size of each element.
  119. m[HASH_MAP_SIZE_2] = size2;
  120. // Store the size of "_E_HASH_MAP_NAME" in bytes.
  121. #emit LOAD.S.pri target
  122. #emit LOAD.S.alt t2
  123. #emit SUB
  124. #emit STOR.S.pri ptr
  125. m[HASH_MAP_SIZE_3] = ptr;
  126. }
  127. // Uses "%2 - %2" to make a tagged 0 without knowing the tag.
  128. #define HashMap_Init(%0,%1,%2) _HashMap_Init(%0, %1[0][(%2)], sizeof (%1), sizeof (%1[]) * 4, %1[0][(%2) - (%2)])
  129. /**--------------------------------------------------------------------------**\
  130. <summary>HashMap_ByteLen</summary>
  131. <param name="str[]">String to get the size of.</param>
  132. <returns>
  133. The number of BYTES this string takes up including the NULL.
  134. </returns>
  135. <remarks>
  136. Caters for both packed and unpacked strings. The weirdness is basically
  137. just: "ispacked(str) ? (* 1) : (* 4)".
  138. </remarks>
  139. \**--------------------------------------------------------------------------**/
  140. #define HashMap_ByteLen(%0) ((strlen((%0)) + 1) << (2 * _:!ispacked((%0))))
  141. /**--------------------------------------------------------------------------**\
  142. <summary>HashMap_Add</summary>
  143. <param name="HashMap:m<>"></param>
  144. <param name="const str[]"></param>
  145. <param name="const value">More like the target slot.</param>
  146. <returns>
  147. -
  148. </returns>
  149. <remarks>
  150. Adds a value to the given hash map under the given string key.
  151. Actually more like adding an index, not a value...
  152. </remarks>
  153. \**--------------------------------------------------------------------------**/
  154. stock bool:HashMap_Add(HashMap:m<>, const str[], value, bool:ignorecase = false)
  155. {
  156. P:3("HashMap_Add called: %d <= %d < %d", 0, value, m[HASH_MAP_SIZE_1]);
  157. if (0 <= value < m[HASH_MAP_SIZE_1])
  158. {
  159. // Check if the index is already in a hash map. Doesn't work if the
  160. // entry is a leaf, since it will be a member, but also have both
  161. // pointers as 0... I don't think there is a nice way to do this.
  162. // if (AMX_Read(ptr + 4) || AMX_Read(ptr + 8)) return false;
  163. static
  164. ptr,
  165. hash,
  166. res;
  167. hash = YHash(str, .sensitive = !ignorecase);
  168. // Add this entry to the correct binary tree.
  169. P:5("HashMap_Add: mask = %d", hash & 0xFF);
  170. new
  171. prev = ref(m[hash & 0xFF]),
  172. next;
  173. for ( ; ; )
  174. {
  175. #emit LREF.S.pri prev
  176. #emit STOR.S.pri next
  177. if (!next)
  178. break;
  179. {}
  180. #emit LREF.S.pri next
  181. #emit STOR.pri res
  182. if (hash == res)
  183. {
  184. // It doesn't matter which way we go on matching hashes, as long
  185. // as the direction is consistent.
  186. #emit PUSH.C 0x7FFFFFFF
  187. #emit PUSH.S ignorecase
  188. #emit PUSH.S str
  189. #emit LOAD.S.pri m
  190. #emit ADD.C 1036 // 256 * 4 + 3 * 4
  191. #emit LOAD.I
  192. #emit LOAD.S.alt next
  193. #emit SUB.alt
  194. #emit PUSH.pri
  195. #emit PUSH.C 16
  196. #emit SYSREQ.C strcmp
  197. #emit STACK 20
  198. #emit STOR.pri ptr
  199. if (ptr == 0)
  200. {
  201. P:E("Trying to add two copies of a string to a hashmap: \"%s\", %d", str, value);
  202. return false;
  203. }
  204. else if (ptr < 0) // Lower, move left.
  205. prev = next + HASH_MAP_DIR_LEFT;
  206. else // Higher, move right.
  207. prev = next + HASH_MAP_DIR_RIGHT;
  208. }
  209. else if (hash < res) // Lower, move left.
  210. prev = next + HASH_MAP_DIR_LEFT;
  211. else // Higher, move right.
  212. prev = next + HASH_MAP_DIR_RIGHT;
  213. }
  214. P:6("HashMap_Add: used = %d", AMX_Read(m[HASH_MAP_PTR] + value * m[HASH_MAP_SIZE_2]));
  215. // Get the address of this structure.
  216. ptr = m[HASH_MAP_PTR] + value * m[HASH_MAP_SIZE_2],
  217. // Copy the hashed value.
  218. AMX_Write(ptr, hash),
  219. AMX_Write(ptr + HASH_MAP_DIR_LEFT, 0),
  220. AMX_Write(ptr + HASH_MAP_DIR_RIGHT, 0),
  221. // Add this hash to the hash list.
  222. AMX_Write(prev, ptr),
  223. // Get the hashed string destination size, and copy the string.
  224. next = m[HASH_MAP_SIZE_3],
  225. rawMemcpy(ptr - next, ref(str), next);
  226. return true;
  227. }
  228. return false;
  229. }
  230. /**--------------------------------------------------------------------------**\
  231. <summary>HashMap_Get</summary>
  232. <param name="HashMap:m<>">The hash map to search.</param>
  233. <param name="const str[]">The key to find.</param>
  234. <returns>
  235. The value associated with this key in the given hash map.
  236. </returns>
  237. <remarks>
  238. -
  239. </remarks>
  240. \**--------------------------------------------------------------------------**/
  241. stock HashMap_Get(HashMap:m<>, const str[], bool:ignorecase = false)
  242. {
  243. return HashMap_GetWithHash(m, str, YHash(str, .sensitive = !ignorecase), ignorecase);
  244. }
  245. /**--------------------------------------------------------------------------**\
  246. <summary>HashMap_GetWithHash</summary>
  247. <param name="HashMap:m<>">The hash map to search.</param>
  248. <param name="const str[]">The key to find.</param>
  249. <param name="hash">The hashed key.</param>
  250. <returns>
  251. The value associated with this key in the given hash map.
  252. </returns>
  253. <remarks>
  254. -
  255. </remarks>
  256. \**--------------------------------------------------------------------------**/
  257. stock HashMap_GetWithHash(HashMap:m<>, const str[], hash, bool:ignorecase = false)
  258. {
  259. P:3("HashMap_Get called: mask = %d (%d)", hash, hash & 0xFF);
  260. new
  261. prev = ref(m[hash & 0xFF]),
  262. next;
  263. static
  264. res;
  265. for ( ; ; )
  266. {
  267. #emit LREF.S.pri prev
  268. #emit STOR.S.pri next
  269. if (!next)
  270. break;
  271. {}
  272. #emit LREF.S.pri next
  273. #emit STOR.pri res
  274. if (hash == res)
  275. {
  276. // It doesn't matter which way we go on matching hashes, as long
  277. // as the direction is consistent.
  278. #emit PUSH.C 0x7FFFFFFF
  279. #emit PUSH.S ignorecase
  280. #emit PUSH.S str
  281. #emit LOAD.S.pri m
  282. #emit ADD.C 1036 // 256 * 4 + 3 * 4
  283. #emit LOAD.I
  284. #emit LOAD.S.alt next
  285. #emit SUB.alt
  286. #emit PUSH.pri
  287. #emit PUSH.C 16
  288. #emit SYSREQ.C strcmp
  289. #emit STACK 20
  290. #emit STOR.pri res
  291. if (res == 0)
  292. {
  293. return (next - m[HASH_MAP_PTR]) / m[HASH_MAP_SIZE_2];
  294. }
  295. else if (res < 0) // Lower, move left.
  296. prev = next + HASH_MAP_DIR_LEFT;
  297. else // Higher, move right.
  298. prev = next + HASH_MAP_DIR_RIGHT;
  299. }
  300. else if (hash < res) // Lower, move left.
  301. prev = next + HASH_MAP_DIR_LEFT;
  302. else // Higher, move right.
  303. prev = next + HASH_MAP_DIR_RIGHT;
  304. }
  305. return -1;
  306. }
  307. /**--------------------------------------------------------------------------**\
  308. <summary>HashMap_RemoveKey</summary>
  309. <param name="HashMap:m<>">The hash map to modify.</param>
  310. <param name="const str[]">The key to remove from the hash map.</param>
  311. <returns>
  312. -
  313. </returns>
  314. <remarks>
  315. Removes a given key and its associated value from the given hash map (if it
  316. can be found in the map in the first place).
  317. </remarks>
  318. \**--------------------------------------------------------------------------**/
  319. stock bool:HashMap_RemoveKeyWithHash(HashMap:m<>, const str[], hash, bool:ignorecase = false)
  320. {
  321. // First, find the key and it's parent.
  322. new
  323. prev = ref(m[hash & 0xFF]),
  324. next;
  325. static
  326. res;
  327. // First, find this key in the hashmap.
  328. for ( ; ; )
  329. {
  330. #emit LREF.S.pri prev
  331. #emit STOR.S.pri next
  332. if (!next)
  333. return false;
  334. {}
  335. #emit LREF.S.pri next
  336. #emit STOR.pri res
  337. if (hash == res)
  338. {
  339. // It doesn't matter which way we go on matching hashes, as long
  340. // as the direction is consistent.
  341. #emit PUSH.C 0x7FFFFFFF
  342. #emit PUSH.S ignorecase
  343. #emit PUSH.S str
  344. #emit LOAD.S.pri m
  345. #emit ADD.C 1036 // 256 * 4 + 3 * 4
  346. #emit LOAD.I
  347. #emit LOAD.S.alt next
  348. #emit SUB.alt
  349. #emit PUSH.pri
  350. #emit PUSH.C 16
  351. #emit SYSREQ.C strcmp
  352. #emit STACK 20
  353. #emit STOR.pri res
  354. if (res == 0)
  355. {
  356. break;
  357. }
  358. else if (res < 0) // Lower, move left.
  359. prev = next + HASH_MAP_DIR_LEFT;
  360. else // Higher, move right.
  361. prev = next + HASH_MAP_DIR_RIGHT;
  362. }
  363. else if (hash < res) // Lower, move left.
  364. prev = next + HASH_MAP_DIR_LEFT;
  365. else // Higher, move right.
  366. prev = next + HASH_MAP_DIR_RIGHT;
  367. }
  368. // The LEFT/RIGHT apparent swap below is correct. We want the right-most
  369. // value on the left branch, and vice-versa.
  370. new
  371. left = AMX_Read(next + HASH_MAP_DIR_LEFT),
  372. right = AMX_Read(next + HASH_MAP_DIR_RIGHT);
  373. // Find an empty branch, or the smallest branch.
  374. if (!left)
  375. {
  376. if (!right)
  377. AMX_Write(prev, 0);
  378. else
  379. AMX_Write(prev, right);
  380. }
  381. else if (!right)
  382. {
  383. AMX_Write(prev, left);
  384. }
  385. else
  386. {
  387. new
  388. lHeight = 1,
  389. rHeight = 1,
  390. lParent = 0,
  391. rParent = 0,
  392. lVal = HashMap_GetBranchEnd(left, lHeight, lParent, HASH_MAP_DIR_RIGHT),
  393. rVal = HashMap_GetBranchEnd(right, rHeight, rParent, HASH_MAP_DIR_LEFT);
  394. if (lHeight < rHeight)
  395. {
  396. // Reduce the right (larger) branch. If this branch is bigger, and
  397. // no branch is empty, then this branch MUST be at least two nodes
  398. // high, and so MUST have a parent.
  399. AMX_Write(prev, rVal),
  400. AMX_Write(rParent + HASH_MAP_DIR_LEFT, AMX_Read(rVal + HASH_MAP_DIR_RIGHT)),
  401. AMX_Write(rVal + HASH_MAP_DIR_RIGHT, right),
  402. AMX_Write(rVal + HASH_MAP_DIR_LEFT, left);
  403. }
  404. else
  405. {
  406. // Reduce the left (larger) branch. This branch MAY NOT have a
  407. // parent, if both branches are equal at one node high.
  408. AMX_Write(prev, lVal);
  409. if (lParent)
  410. {
  411. AMX_Write(lParent + HASH_MAP_DIR_RIGHT, AMX_Read(lVal + HASH_MAP_DIR_LEFT)),
  412. AMX_Write(lVal + HASH_MAP_DIR_RIGHT, right),
  413. AMX_Write(lVal + HASH_MAP_DIR_LEFT, left);
  414. }
  415. else
  416. {
  417. AMX_Write(lVal + HASH_MAP_DIR_RIGHT, right),
  418. AMX_Write(lVal + HASH_MAP_DIR_LEFT, 0);
  419. }
  420. }
  421. }
  422. // Clear the removed node's pointers and string.
  423. AMX_Write(next + HASH_MAP_DIR_LEFT, 0),
  424. AMX_Write(next + HASH_MAP_DIR_RIGHT, 0),
  425. AMX_Write(next - m[HASH_MAP_SIZE_3], 0);
  426. return true;
  427. }
  428. stock bool:HashMap_RemoveKey(HashMap:m<>, const str[], bool:ignorecase = false)
  429. {
  430. return HashMap_RemoveKeyWithHash(m, str, YHash(str, .sensitive = !ignorecase), ignorecase);
  431. }
  432. static stock HashMap_GetBranchEnd(cur, &height, &parent, dir)
  433. {
  434. // Find the node as close in (hash) value to the current node as possible.
  435. static
  436. next;
  437. while ((next = AMX_Read(cur + dir)))
  438. {
  439. ++height,
  440. parent = cur,
  441. cur = next;
  442. }
  443. return cur;
  444. }
  445. /**--------------------------------------------------------------------------**\
  446. <summary>HashMap_RemoveValue</summary>
  447. <param name="HashMap:m<>">Hash map to modify.</param>
  448. <param name="value">Value to remove.</param>
  449. <returns>
  450. -
  451. </returns>
  452. <remarks>
  453. Removes a value from the hash map. First it gets the string key for the
  454. value, then removes that (to update associated linked lists correctly).
  455. </remarks>
  456. \**--------------------------------------------------------------------------**/
  457. stock bool:HashMap_RemoveValue(HashMap:m<>, value)
  458. {
  459. if (0 <= value < m[HASH_MAP_SIZE_1])
  460. {
  461. static
  462. sString[128 char];
  463. new
  464. size = m[HASH_MAP_SIZE_3];
  465. rawMemcpy(ref(sString), m[HASH_MAP_PTR] + value * m[HASH_MAP_SIZE_2] - size, min(size, sizeof (sString) * 4));
  466. return HashMap_RemoveKey(m, sString);
  467. }
  468. return false;
  469. }
  470. /**--------------------------------------------------------------------------**\
  471. <summary>HashMap_Set</summary>
  472. <param name="HashMap:m<>">The hash map to modify.</param>
  473. <param name="const str[]">The key to modify.</param>
  474. <param name="const value">The new value for the given key.</param>
  475. <returns>
  476. -
  477. </returns>
  478. <remarks>
  479. If this key is already in the hash map it is removed, and then the new value
  480. is added in its place. If the string already exists, its associated data is
  481. removed. If the value already exists, it is removed as well.
  482. </remarks>
  483. \**--------------------------------------------------------------------------**/
  484. stock HashMap_Set(HashMap:m<>, const str[], const value)
  485. {
  486. return
  487. HashMap_RemoveKey(m, str),
  488. HashMap_RemoveValue(m, value),
  489. HashMap_Add(m, str, value);
  490. }