LRUCache.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. class LRUCache {
  2. constructor() {
  3. // options
  4. this.maxSize = 800;
  5. this.minSize = 600;
  6. this.unloadPercent = 0.2;
  7. this.usedSet = new Set();
  8. this.itemSet = new Set();
  9. this.itemList = [];
  10. this.callbacks = new Map();
  11. }
  12. // Returns whether or not the cache has reached the maximum size
  13. isFull() {
  14. return this.itemSet.size >= this.maxSize;
  15. }
  16. add( item, removeCb ) {
  17. const itemSet = this.itemSet;
  18. if ( itemSet.has( item ) ) {
  19. return false;
  20. }
  21. if ( this.isFull() ) {
  22. return false;
  23. }
  24. const usedSet = this.usedSet;
  25. const itemList = this.itemList;
  26. const callbacks = this.callbacks;
  27. itemList.push( item );
  28. usedSet.add( item );
  29. itemSet.add( item );
  30. callbacks.set( item, removeCb );
  31. return true;
  32. }
  33. remove( item ) {
  34. const usedSet = this.usedSet;
  35. const itemSet = this.itemSet;
  36. const itemList = this.itemList;
  37. const callbacks = this.callbacks;
  38. if ( itemSet.has( item ) ) {
  39. callbacks.get( item )( item );
  40. const index = itemList.indexOf( item );
  41. itemList.splice( index, 1 );
  42. usedSet.delete( item );
  43. itemSet.delete( item );
  44. callbacks.delete( item );
  45. return true;
  46. }
  47. return false;
  48. }
  49. markUsed( item ) {
  50. const itemSet = this.itemSet;
  51. const usedSet = this.usedSet;
  52. if ( itemSet.has( item ) && ! usedSet.has( item ) ) {
  53. const itemList = this.itemList;
  54. const index = itemList.indexOf( item );
  55. itemList.splice( index, 1 );
  56. itemList.push( item );
  57. usedSet.add( item );
  58. }
  59. }
  60. markAllUnused() {
  61. this.usedSet.clear();
  62. }
  63. unloadUnusedContent( prioritySortCb ) {
  64. const unloadPercent = this.unloadPercent;
  65. const targetSize = this.minSize;
  66. const itemList = this.itemList;
  67. const itemSet = this.itemSet;
  68. const usedSet = this.usedSet;
  69. const callbacks = this.callbacks;
  70. const unused = itemList.length - usedSet.size;
  71. if ( itemList.length > targetSize && unused > 0 ) {
  72. // TODO: sort by priority
  73. let nodesToUnload = Math.max( itemList.length - targetSize, targetSize ) * unloadPercent;
  74. nodesToUnload = Math.ceil( nodesToUnload );
  75. nodesToUnload = Math.min( unused, nodesToUnload );
  76. const removedItems = itemList.splice( 0, nodesToUnload );
  77. for ( let i = 0, l = removedItems.length; i < l; i ++ ) {
  78. const item = removedItems[ i ];
  79. callbacks.get( item )( item );
  80. itemSet.delete( item );
  81. callbacks.delete( item );
  82. }
  83. }
  84. }
  85. scheduleUnload( prioritySortCb, markAllUnused = true ) {
  86. if ( ! this.scheduled ) {
  87. this.scheduled = true;
  88. Promise.resolve().then( () => {
  89. this.scheduled = false;
  90. this.unloadUnusedContent( prioritySortCb );
  91. if ( markAllUnused ) {
  92. this.markAllUnused();
  93. }
  94. } );
  95. }
  96. }
  97. }
  98. export { LRUCache };