WeakMap.hx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. /*
  2. * Copyright (C)2005-2018 Haxe Foundation
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a
  5. * copy of this software and associated documentation files (the "Software"),
  6. * to deal in the Software without restriction, including without limitation
  7. * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  8. * and/or sell copies of the Software, and to permit persons to whom the
  9. * Software is furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  20. * DEALINGS IN THE SOFTWARE.
  21. */
  22. package haxe.ds;
  23. import java.NativeArray;
  24. import java.lang.ref.WeakReference;
  25. import java.lang.ref.ReferenceQueue;
  26. @:coreApi class WeakMap<K:{}, V> implements haxe.Constraints.IMap<K,V>
  27. {
  28. extern private static inline var HASH_UPPER = 0.77;
  29. extern private static inline var FLAG_EMPTY = 0;
  30. extern private static inline var FLAG_DEL = 1;
  31. /**
  32. * This is the most important structure here and the reason why it's so fast.
  33. * It's an array of all the hashes contained in the table. These hashes cannot be 0 nor 1,
  34. * which stand for "empty" and "deleted" states.
  35. *
  36. * The lookup algorithm will keep looking until a 0 or the key wanted is found;
  37. * The insertion algorithm will do the same but will also break when FLAG_DEL is found;
  38. */
  39. private var hashes:NativeArray<HashType>;
  40. private var entries:NativeArray<Entry<K,V>>;
  41. //weak map specific
  42. private var queue:ReferenceQueue<K>;
  43. private var nBuckets:Int;
  44. private var size:Int;
  45. private var nOccupied:Int;
  46. private var upperBound:Int;
  47. #if !no_map_cache
  48. private var cachedEntry:Entry<K,V>;
  49. private var cachedIndex:Int;
  50. #end
  51. #if DEBUG_HASHTBL
  52. private var totalProbes:Int;
  53. private var probeTimes:Int;
  54. private var sameHash:Int;
  55. private var maxProbe:Int;
  56. #end
  57. public function new() : Void
  58. {
  59. #if !no_map_cache
  60. cachedIndex = -1;
  61. #end
  62. queue = new ReferenceQueue();
  63. }
  64. @:analyzer(ignore)
  65. private function cleanupRefs():Void
  66. {
  67. var x:Dynamic = null, nOccupied = nOccupied;
  68. while (( x = queue.poll()) != null)
  69. {
  70. //even if not found on hashtable (already removed), release value
  71. var x:Entry<K,V> = cast x;
  72. x.value = null;
  73. //lookup index
  74. if (nOccupied != 0)
  75. {
  76. var mask = nBuckets - 1, hash = x.hash, nProbes = 0;
  77. var i = hash & mask;
  78. var last = i, flag;
  79. while(!isEmpty(flag = hashes[i]) && (isDel(flag) || flag != hash || entries[i] != x))
  80. {
  81. i = (i + ++nProbes) & mask;
  82. }
  83. if (entries[i] == x)
  84. {
  85. #if !no_map_cache
  86. if (cachedIndex == i)
  87. {
  88. cachedIndex = -1;
  89. cachedEntry = null;
  90. }
  91. #end
  92. entries[i] = null;
  93. hashes[i] = FLAG_DEL;
  94. --size;
  95. }
  96. }
  97. }
  98. }
  99. public function set( key : K, value : V ) : Void
  100. {
  101. cleanupRefs();
  102. var x:Int, k:Int;
  103. if (nOccupied >= upperBound)
  104. {
  105. if (nBuckets > (size << 1))
  106. resize(nBuckets - 1); //clear "deleted" elements
  107. else
  108. resize(nBuckets + 2);
  109. }
  110. k = hash(key);
  111. var hashes = hashes, entries = entries;
  112. {
  113. var mask = (nBuckets == 0) ? 0 : nBuckets - 1;
  114. var site = x = nBuckets;
  115. var i = k & mask, nProbes = 0;
  116. var delKey = -1;
  117. //for speed up
  118. if (isEmpty(hashes[i])) {
  119. x = i;
  120. } else {
  121. //var inc = getInc(k, mask);
  122. var last = i, flag;
  123. while(! (isEmpty(flag = hashes[i]) || (flag == k && entries[i].keyEquals(key) )) )
  124. {
  125. if (delKey == -1 && isDel(flag))
  126. delKey = i;
  127. i = (i + ++nProbes) & mask;
  128. #if DEBUG_HASHTBL
  129. probeTimes++;
  130. if (i == last)
  131. throw "assert";
  132. #end
  133. }
  134. if (isEmpty(flag) && delKey != -1)
  135. x = delKey;
  136. else
  137. x = i;
  138. }
  139. #if DEBUG_HASHTBL
  140. if (nProbes > maxProbe)
  141. maxProbe = nProbes;
  142. totalProbes++;
  143. #end
  144. }
  145. var flag = hashes[x], entry = new Entry(key,value,k,queue);
  146. if (isEmpty(flag))
  147. {
  148. entries[x] = entry;
  149. hashes[x] = k;
  150. size++;
  151. nOccupied++;
  152. } else if (isDel(flag)) {
  153. entries[x] = entry;
  154. hashes[x] = k;
  155. size++;
  156. } else {
  157. assert(entries[x].keyEquals(key));
  158. entries[x] = entry;
  159. }
  160. #if !no_map_cache
  161. cachedIndex = x;
  162. cachedEntry = entry;
  163. #end
  164. }
  165. @:final private function lookup( key : K ) : Int
  166. {
  167. if (nBuckets != 0)
  168. {
  169. var hashes = hashes, entries = entries;
  170. var mask = nBuckets - 1, hash = hash(key), k = hash, nProbes = 0;
  171. var i = k & mask;
  172. var last = i, flag;
  173. //var inc = getInc(k, mask);
  174. while (!isEmpty(flag = hashes[i]) && (isDel(flag) || flag != k || !entries[i].keyEquals(key)))
  175. {
  176. i = (i + ++nProbes) & mask;
  177. #if DEBUG_HASHTBL
  178. probeTimes++;
  179. if (i == last)
  180. throw "assert";
  181. #end
  182. }
  183. #if DEBUG_HASHTBL
  184. if (nProbes > maxProbe)
  185. maxProbe = nProbes;
  186. totalProbes++;
  187. #end
  188. return isEither(flag) ? -1 : i;
  189. }
  190. return -1;
  191. }
  192. @:final @:private function resize(newNBuckets:Int) : Void
  193. {
  194. //This function uses 0.25*n_bucktes bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets.
  195. var newHash = null;
  196. var j = 1;
  197. {
  198. newNBuckets = roundUp(newNBuckets);
  199. if (newNBuckets < 4) newNBuckets = 4;
  200. if (size >= (newNBuckets * HASH_UPPER + 0.5)) /* requested size is too small */
  201. {
  202. j = 0;
  203. } else { /* hash table size to be changed (shrink or expand); rehash */
  204. var nfSize = newNBuckets;
  205. newHash = new NativeArray( nfSize );
  206. if (nBuckets < newNBuckets) //expand
  207. {
  208. var e = new NativeArray(newNBuckets);
  209. if (entries != null)
  210. arrayCopy(entries, 0, e, 0, nBuckets);
  211. entries = e;
  212. } //otherwise shrink
  213. }
  214. }
  215. if (j != 0)
  216. { //rehashing is required
  217. //resetting cache
  218. #if !no_map_cache
  219. cachedEntry = null;
  220. cachedIndex = -1;
  221. #end
  222. j = -1;
  223. var nBuckets = nBuckets, entries = entries, hashes = hashes;
  224. var newMask = newNBuckets - 1;
  225. while (++j < nBuckets)
  226. {
  227. var k;
  228. if (!isEither(k = hashes[j]))
  229. {
  230. var entry = entries[j];
  231. entries[j] = null;
  232. hashes[j] = FLAG_DEL;
  233. while (true) /* kick-out process; sort of like in Cuckoo hashing */
  234. {
  235. var nProbes = 0;
  236. var i = k & newMask;
  237. while (!isEmpty(newHash[i]))
  238. i = (i + ++nProbes) & newMask;
  239. newHash[i] = k;
  240. if (i < nBuckets && !isEither(k = hashes[i])) /* kick out the existing element */
  241. {
  242. {
  243. var tmp = entries[i];
  244. entries[i] = entry;
  245. entry = tmp;
  246. }
  247. hashes[i] = FLAG_DEL; /* mark it as deleted in the old hash table */
  248. } else { /* write the element and jump out of the loop */
  249. entries[i] = entry;
  250. break;
  251. }
  252. }
  253. }
  254. }
  255. if (nBuckets > newNBuckets) /* shrink the hash table */
  256. {
  257. {
  258. var e = new NativeArray(newNBuckets);
  259. arrayCopy(entries, 0, e, 0, newNBuckets);
  260. this.entries = e;
  261. }
  262. }
  263. this.hashes = newHash;
  264. this.nBuckets = newNBuckets;
  265. this.nOccupied = size;
  266. this.upperBound = Std.int(newNBuckets * HASH_UPPER + .5);
  267. }
  268. }
  269. public function get( key : K ) : Null<V>
  270. {
  271. cleanupRefs();
  272. var idx = -1;
  273. #if !no_map_cache
  274. if (cachedEntry != null && cachedEntry.keyEquals(key) && ( (idx = cachedIndex) != -1 ))
  275. {
  276. return cachedEntry.value;
  277. }
  278. #end
  279. idx = lookup(key);
  280. if (idx != -1)
  281. {
  282. var entry = entries[idx];
  283. #if !no_map_cache
  284. cachedEntry = entry;
  285. cachedIndex = idx;
  286. #end
  287. return entry.value;
  288. }
  289. return null;
  290. }
  291. private function getDefault( key : K, def : V ) : V
  292. {
  293. cleanupRefs();
  294. var idx = -1;
  295. #if !no_map_cache
  296. if (cachedEntry != null && cachedEntry.keyEquals(key) && ( (idx = cachedIndex) != -1 ))
  297. {
  298. return cachedEntry.value;
  299. }
  300. #end
  301. idx = lookup(key);
  302. if (idx != -1)
  303. {
  304. var entry = entries[idx];
  305. #if !no_map_cache
  306. cachedEntry = entry;
  307. cachedIndex = idx;
  308. #end
  309. return entry.value;
  310. }
  311. return def;
  312. }
  313. public function exists( key : K ) : Bool
  314. {
  315. cleanupRefs();
  316. var idx = -1;
  317. #if !no_map_cache
  318. if (cachedEntry != null && cachedEntry.keyEquals(key) && ( (idx = cachedIndex) != -1 ))
  319. {
  320. return true;
  321. }
  322. #end
  323. idx = lookup(key);
  324. if (idx != -1)
  325. {
  326. var entry = entries[idx];
  327. #if !no_map_cache
  328. cachedEntry = entry;
  329. cachedIndex = idx;
  330. #end
  331. return true;
  332. }
  333. return false;
  334. }
  335. public function remove( key : K ) : Bool
  336. {
  337. cleanupRefs();
  338. var idx = -1;
  339. #if !no_map_cache
  340. if ( !(cachedEntry != null && cachedEntry.keyEquals(key) && ( (idx = cachedIndex) != -1 )) )
  341. #end
  342. {
  343. idx = lookup(key);
  344. }
  345. if (idx == -1)
  346. {
  347. return false;
  348. } else {
  349. #if !no_map_cache
  350. if (cachedEntry != null && cachedEntry.keyEquals(key))
  351. {
  352. cachedIndex = -1;
  353. cachedEntry = null;
  354. }
  355. #end
  356. hashes[idx] = FLAG_DEL;
  357. entries[idx] = null;
  358. --size;
  359. return true;
  360. }
  361. }
  362. /**
  363. Returns an iterator of all keys in the hashtable.
  364. Implementation detail: Do not set() any new value while iterating, as it may cause a resize, which will break iteration
  365. **/
  366. public inline function keys() : Iterator<K>
  367. {
  368. cleanupRefs();
  369. return new WeakMapKeyIterator(this);
  370. }
  371. /**
  372. Returns an iterator of all values in the hashtable.
  373. Implementation detail: Do not set() any new value while iterating, as it may cause a resize, which will break iteration
  374. **/
  375. public inline function iterator() : Iterator<V>
  376. {
  377. cleanupRefs();
  378. return new WeakMapValueIterator(this);
  379. }
  380. /**
  381. See `Map.keyValueIterator`
  382. **/
  383. public inline function keyValueIterator() : KeyValueIterator<K, V> {
  384. return new haxe.iterators.MapKeyValueIterator(this);
  385. }
  386. public function copy() : WeakMap<K,V> {
  387. var copied = new WeakMap();
  388. for(key in keys()) copied.set(key, get(key));
  389. return copied;
  390. }
  391. /**
  392. Returns an displayable representation of the hashtable content.
  393. **/
  394. public function toString() : String {
  395. var s = new StringBuf();
  396. s.add("{");
  397. var it = keys();
  398. for( i in it ) {
  399. s.add(Std.string(i));
  400. s.add(" => ");
  401. s.add(Std.string(get(i)));
  402. if( it.hasNext() )
  403. s.add(", ");
  404. }
  405. s.add("}");
  406. return s.toString();
  407. }
  408. extern private static inline function roundUp(x:Int):Int
  409. {
  410. --x;
  411. x |= (x) >>> 1;
  412. x |= (x) >>> 2;
  413. x |= (x) >>> 4;
  414. x |= (x) >>> 8;
  415. x |= (x) >>> 16;
  416. return ++x;
  417. }
  418. extern private static inline function getInc(k:Int, mask:Int):Int //return 1 for linear probing
  419. return (((k) >> 3 ^ (k) << 3) | 1) & (mask);
  420. extern private static inline function isEither(v:HashType):Bool
  421. return (v & 0xFFFFFFFE) == 0;
  422. extern private static inline function isEmpty(v:HashType):Bool
  423. return v == FLAG_EMPTY;
  424. extern private static inline function isDel(v:HashType):Bool
  425. return v == FLAG_DEL;
  426. //guarantee: Whatever this function is, it will never return 0 nor 1
  427. extern private static inline function hash(s:Dynamic):HashType
  428. {
  429. var k:Int = untyped s.hashCode();
  430. //k *= 357913941;
  431. //k ^= k << 24;
  432. //k += ~357913941;
  433. //k ^= k >> 31;
  434. //k ^= k << 31;
  435. k = (k+0x7ed55d16) + (k<<12);
  436. k = (k^0xc761c23c) ^ (k>>19);
  437. k = (k+0x165667b1) + (k<<5);
  438. k = (k+0xd3a2646c) ^ (k<<9);
  439. k = (k+0xfd7046c5) + (k<<3);
  440. k = (k^0xb55a4f09) ^ (k>>16);
  441. var ret = k;
  442. if (isEither(ret))
  443. {
  444. if (ret == 0)
  445. ret = 2;
  446. else
  447. ret = 0xFFFFFFFF;
  448. }
  449. return ret;
  450. }
  451. extern private static inline function arrayCopy(sourceArray:Dynamic, sourceIndex:Int, destinationArray:Dynamic, destinationIndex:Int, length:Int):Void
  452. java.lang.System.arraycopy(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
  453. extern private static inline function assert(x:Bool):Void
  454. {
  455. #if DEBUG_HASHTBL
  456. if (!x) throw "assert failed";
  457. #end
  458. }
  459. }
  460. private class Entry<K,V> extends WeakReference<K>
  461. {
  462. public var value:V;
  463. public var hash(default, null):Int;
  464. public function new(key:K, value:V, hash:Int, queue:ReferenceQueue<K>)
  465. {
  466. super(key, queue);
  467. this.value = value;
  468. this.hash = hash;
  469. }
  470. @:final inline public function keyEquals(k:K):Bool
  471. {
  472. return k != null && untyped k.equals(get());
  473. }
  474. }
  475. @:access(haxe.ds.WeakMap)
  476. @:final
  477. private class WeakMapKeyIterator<T:{},V> {
  478. var m:WeakMap<T,V>;
  479. var i:Int;
  480. var len:Int;
  481. var lastKey:T;
  482. public function new(m:WeakMap<T,V>) {
  483. this.i = 0;
  484. this.m = m;
  485. this.len = m.nBuckets;
  486. }
  487. public function hasNext():Bool {
  488. for (j in i...len)
  489. {
  490. if (!WeakMap.isEither(m.hashes[j]))
  491. {
  492. var entry = m.entries[j],
  493. last = entry.get();
  494. if (last != null)
  495. {
  496. #if !no_map_cache
  497. m.cachedIndex = i;
  498. m.cachedEntry = entry;
  499. #end
  500. lastKey = last; // keep a strong reference to the key while iterating, so it doesn't get collected
  501. i = j;
  502. return true;
  503. }
  504. }
  505. }
  506. lastKey = null;
  507. return false;
  508. }
  509. public function next() : T {
  510. i = i + 1;
  511. return lastKey;
  512. }
  513. }
  514. @:access(haxe.ds.WeakMap)
  515. @:final
  516. private class WeakMapValueIterator<K:{},T> {
  517. var m:WeakMap<K,T>;
  518. var i:Int;
  519. var len:Int;
  520. public function new(m:WeakMap<K,T>)
  521. {
  522. this.i = 0;
  523. this.m = m;
  524. this.len = m.nBuckets;
  525. }
  526. public function hasNext() : Bool {
  527. for (j in i...len)
  528. {
  529. if (!WeakMap.isEither(m.hashes[j]) && m.entries[j].get() != null)
  530. {
  531. i = j;
  532. return true;
  533. }
  534. }
  535. return false;
  536. }
  537. public inline function next():T {
  538. var ret = m.entries[i];
  539. i = i + 1;
  540. return ret.value;
  541. }
  542. }
  543. private typedef HashType = Int;