data_cache.js (5235B)
1 /** 2 * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts 3 **/ /** 4 * Utilities to improve the performance of the CTS, by caching data that is 5 * expensive to build using a two-level cache (in-memory, pre-computed file). 6 */import { assert } from '../util/util.js'; 7 8 9 10 11 12 /** Logger is a basic debug logger function */ 13 14 15 /** 16 * DataCacheNode represents a single cache entry in the LRU DataCache. 17 * DataCacheNode is a doubly linked list, so that least-recently-used entries can be removed, and 18 * cache hits can move the node to the front of the list. 19 */ 20 class DataCacheNode { 21 constructor(path, data) { 22 this.path = path; 23 this.data = data; 24 } 25 26 /** insertAfter() re-inserts this node in the doubly-linked list after `prev` */ 27 insertAfter(prev) { 28 this.unlink(); 29 this.next = prev.next; 30 this.prev = prev; 31 prev.next = this; 32 if (this.next) { 33 this.next.prev = this; 34 } 35 } 36 37 /** unlink() removes this node from the doubly-linked list */ 38 unlink() { 39 const prev = this.prev; 40 const next = this.next; 41 if (prev) { 42 prev.next = next; 43 } 44 if (next) { 45 next.prev = prev; 46 } 47 this.prev = null; 48 this.next = null; 49 } 50 51 // The file path this node represents 52 // The deserialized data for this node 53 prev = null; // The previous node in the doubly-linked list 54 next = null; // The next node in the doubly-linked list 55 } 56 57 /** DataCache is an interface to a LRU-cached data store used to hold data cached by path */ 58 export class DataCache { 59 constructor() { 60 this.lruHeadNode.next = this.lruTailNode; 61 this.lruTailNode.prev = this.lruHeadNode; 62 } 63 64 /** setDataStore() sets the backing data store used by the data cache */ 65 setStore(dataStore) { 66 this.dataStore = dataStore; 67 } 68 69 /** setDebugLogger() sets the verbose logger */ 70 setDebugLogger(logger) { 71 this.debugLogger = logger; 72 } 73 74 /** 75 * fetch() retrieves cacheable data from the data cache, first checking the 76 * in-memory cache, then the data store (if specified), then resorting to 77 * building the data and storing it in the cache. 78 */ 79 async fetch(cacheable) { 80 { 81 // First check the in-memory cache 82 const node = this.cache.get(cacheable.path); 83 if (node !== undefined) { 84 this.log('in-memory cache hit'); 85 node.insertAfter(this.lruHeadNode); 86 return Promise.resolve(node.data); 87 } 88 } 89 this.log('in-memory cache miss'); 90 // In in-memory cache miss. 91 // Next, try the data store. 92 if (this.dataStore !== null && !this.unavailableFiles.has(cacheable.path)) { 93 let serialized; 94 try { 95 serialized = await this.dataStore.load(cacheable.path); 96 this.log('loaded serialized'); 97 } catch (err) { 98 // not found in data store 99 this.log(`failed to load (${cacheable.path}): ${err}`); 100 this.unavailableFiles.add(cacheable.path); 101 } 102 if (serialized !== undefined) { 103 this.log(`deserializing`); 104 const data = cacheable.deserialize(serialized); 105 this.addToCache(cacheable.path, data); 106 return data; 107 } 108 } 109 // Not found anywhere. Build the data, and cache for future lookup. 110 this.log(`cache: building (${cacheable.path})`); 111 const data = await cacheable.build(); 112 this.addToCache(cacheable.path, data); 113 return data; 114 } 115 116 /** 117 * addToCache() creates a new node for `path` and `data`, inserting the new node at the front of 118 * the doubly-linked list. If the number of entries in the cache exceeds this.maxCount, then the 119 * least recently used entry is evicted 120 * @param path the file path for the data 121 * @param data the deserialized data 122 */ 123 addToCache(path, data) { 124 if (this.cache.size >= this.maxCount) { 125 const toEvict = this.lruTailNode.prev; 126 assert(toEvict !== null); 127 toEvict.unlink(); 128 this.cache.delete(toEvict.path); 129 this.log(`evicting ${toEvict.path}`); 130 } 131 const node = new DataCacheNode(path, data); 132 node.insertAfter(this.lruHeadNode); 133 this.cache.set(path, node); 134 this.log(`added ${path}. new count: ${this.cache.size}`); 135 } 136 137 log(msg) { 138 if (this.debugLogger !== null) { 139 this.debugLogger(`DataCache: ${msg}`); 140 } 141 } 142 143 // Max number of entries in the cache before LRU entries are evicted. 144 maxCount = 4; 145 146 cache = new Map(); 147 lruHeadNode = new DataCacheNode('', null); // placeholder node (no path or data) 148 lruTailNode = new DataCacheNode('', null); // placeholder node (no path or data) 149 unavailableFiles = new Set(); 150 dataStore = null; 151 debugLogger = null; 152 } 153 154 /** The data cache */ 155 export const dataCache = new DataCache(); 156 157 /** true if the current process is building the cache */ 158 let isBuildingDataCache = false; 159 160 /** @returns true if the data cache is currently being built */ 161 export function getIsBuildingDataCache() { 162 return isBuildingDataCache; 163 } 164 165 /** Sets whether the data cache is currently being built */ 166 export function setIsBuildingDataCache(value = true) { 167 isBuildingDataCache = value; 168 } 169 170 /** 171 * Cacheable is the interface to something that can be stored into the 172 * DataCache. 173 * The 'npm run gen_cache' tool will look for module-scope variables of this 174 * interface, with the name `d`. 175 */