import type Theme from './Theme'; import type { DerivativeFunc } from './interface'; // ================================== Cache ================================== type ThemeCacheMap = Map< DerivativeFunc, { map?: ThemeCacheMap; value?: [Theme, number]; } >; type DerivativeOptions = DerivativeFunc[]; export function sameDerivativeOption(left: DerivativeOptions, right: DerivativeOptions) { if (left.length !== right.length) { return false; } for (let i = 0; i < left.length; i++) { if (left[i] !== right[i]) { return false; } } return true; } export default class ThemeCache { public static MAX_CACHE_SIZE = 20; public static MAX_CACHE_OFFSET = 5; private readonly cache: ThemeCacheMap; private keys: DerivativeOptions[]; private cacheCallTimes: number; constructor() { this.cache = new Map(); this.keys = []; this.cacheCallTimes = 0; } public size(): number { return this.keys.length; } private internalGet( derivativeOption: DerivativeOptions, updateCallTimes = false, ): [Theme, number] | undefined { let cache: ReturnType = { map: this.cache }; derivativeOption.forEach(derivative => { if (!cache) { cache = undefined; } else { cache = cache?.map?.get(derivative); } }); if (cache?.value && updateCallTimes) { cache.value[1] = this.cacheCallTimes++; } return cache?.value; } public get(derivativeOption: DerivativeOptions): Theme | undefined { return this.internalGet(derivativeOption, true)?.[0]; } public has(derivativeOption: DerivativeOptions): boolean { return !!this.internalGet(derivativeOption); } public set(derivativeOption: DerivativeOptions, value: Theme): void { // New cache if (!this.has(derivativeOption)) { if (this.size() + 1 > ThemeCache.MAX_CACHE_SIZE + ThemeCache.MAX_CACHE_OFFSET) { const [targetKey] = this.keys.reduce<[DerivativeOptions, number]>( (result, key) => { const [, callTimes] = result; if (this.internalGet(key)![1] < callTimes) { return [key, this.internalGet(key)![1]]; } return result; }, [this.keys[0], this.cacheCallTimes], ); this.delete(targetKey); } this.keys.push(derivativeOption); } let cache = this.cache; derivativeOption.forEach((derivative, index) => { if (index === derivativeOption.length - 1) { cache.set(derivative, { value: [value, this.cacheCallTimes++] }); } else { const cacheValue = cache.get(derivative); if (!cacheValue) { cache.set(derivative, { map: new Map() }); } else if (!cacheValue.map) { cacheValue.map = new Map(); } cache = cache.get(derivative)!.map!; } }); } private deleteByPath( currentCache: ThemeCacheMap, derivatives: DerivativeFunc[], ): Theme | undefined { const cache = currentCache.get(derivatives[0])!; if (derivatives.length === 1) { if (!cache.map) { currentCache.delete(derivatives[0]); } else { currentCache.set(derivatives[0], { map: cache.map }); } return cache.value?.[0]; } const result = this.deleteByPath(cache.map!, derivatives.slice(1)); if ((!cache.map || cache.map.size === 0) && !cache.value) { currentCache.delete(derivatives[0]); } return result; } public delete(derivativeOption: DerivativeOptions): Theme | undefined { // If cache exists if (this.has(derivativeOption)) { this.keys = this.keys.filter(item => !sameDerivativeOption(item, derivativeOption)); return this.deleteByPath(this.cache, derivativeOption); } return undefined; } }