Use human-readable memory cache capacity in config
| @@ -138,8 +138,8 @@ performance: | |||
| 138 | # Enables lazy loading of character cards. Improves performances with large card libraries. | 138 | # Enables lazy loading of character cards. Improves performances with large card libraries. |
| 139 | # May have compatibility issues with some extensions. | 139 | # May have compatibility issues with some extensions. |
| 140 | lazyLoadCharacters: false | 140 | lazyLoadCharacters: false |
| 141 | # The maximum amount of memory that parsed character cards can use in MB | 141 | # The maximum amount of memory that parsed character cards can use. Set to 0 to disable memory caching. |
| 142 | cardsCacheCapacity: 100 | 142 | memoryCacheCapacity: '100mb' |
| 143 | 143 | ||
| 144 | # Allow secret keys exposure via API | 144 | # Allow secret keys exposure via API |
| 145 | allowKeysExposure: false | 145 | allowKeysExposure: false |
| @@ -98,8 +98,8 @@ const keyMigrationMap = [ | |||
| 98 | }, | 98 | }, |
| 99 | { | 99 | { |
| 100 | oldKey: 'cardsCacheCapacity', | 100 | oldKey: 'cardsCacheCapacity', |
| 101 | newKey: 'performance.cardsCacheCapacity', | 101 | newKey: 'performance.memoryCacheCapacity', |
| 102 | migrate: (value) => value, | 102 | migrate: (value) => `${value}mb`, |
| 103 | }, | 103 | }, |
| 104 | // uncomment one release after 1.12.13 | 104 | // uncomment one release after 1.12.13 |
| 105 | /* | 105 | /* |
| @@ -23,10 +23,9 @@ import { invalidateThumbnail } from './thumbnails.js'; | |||
| 23 | import { importRisuSprites } from './sprites.js'; | 23 | import { importRisuSprites } from './sprites.js'; |
| 24 | const defaultAvatarPath = './public/img/ai4.png'; | 24 | const defaultAvatarPath = './public/img/ai4.png'; |
| 25 | 25 | ||
| 26 | // KV-store for parsed character data | ||
| 27 | const cacheCapacity = Number(getConfigValue('performance.cardsCacheCapacity', 100, 'number')); // MB | ||
| 28 | // With 100 MB limit it would take roughly 3000 characters to reach this limit | 26 | // With 100 MB limit it would take roughly 3000 characters to reach this limit |
| 29 | const characterDataCache = new MemoryLimitedMap(1024 * 1024 * cacheCapacity); | 27 | const memoryCacheCapacity = getConfigValue('performance.memoryCacheCapacity', '100mb'); |
| 28 | const memoryCache = new MemoryLimitedMap(memoryCacheCapacity); | ||
| 30 | // Some Android devices require tighter memory management | 29 | // Some Android devices require tighter memory management |
| 31 | const isAndroid = process.platform === 'android'; | 30 | const isAndroid = process.platform === 'android'; |
| 32 | // Use shallow character data for the character list | 31 | // Use shallow character data for the character list |
| @@ -41,12 +40,12 @@ const useShallowCharacters = !!getConfigValue('performance.lazyLoadCharacters', | |||
| 41 | async function readCharacterData(inputFile, inputFormat = 'png') { | 40 | async function readCharacterData(inputFile, inputFormat = 'png') { |
| 42 | const stat = fs.statSync(inputFile); | 41 | const stat = fs.statSync(inputFile); |
| 43 | const cacheKey = `${inputFile}-${stat.mtimeMs}`; | 42 | const cacheKey = `${inputFile}-${stat.mtimeMs}`; |
| 44 | if (characterDataCache.has(cacheKey)) { | 43 | if (memoryCache.has(cacheKey)) { |
| 45 | return characterDataCache.get(cacheKey); | 44 | return memoryCache.get(cacheKey); |
| 46 | } | 45 | } |
| 47 | 46 | ||
| 48 | const result = parse(inputFile, inputFormat); | 47 | const result = parse(inputFile, inputFormat); |
| 49 | !isAndroid && characterDataCache.set(cacheKey, result); | 48 | !isAndroid && memoryCache.set(cacheKey, result); |
| 50 | return result; | 49 | return result; |
| 51 | } | 50 | } |
| 52 | 51 | ||
| @@ -62,12 +61,12 @@ async function readCharacterData(inputFile, inputFormat = 'png') { | |||
| 62 | async function writeCharacterData(inputFile, data, outputFile, request, crop = undefined) { | 61 | async function writeCharacterData(inputFile, data, outputFile, request, crop = undefined) { |
| 63 | try { | 62 | try { |
| 64 | // Reset the cache | 63 | // Reset the cache |
| 65 | for (const key of characterDataCache.keys()) { | 64 | for (const key of memoryCache.keys()) { |
| 66 | if (Buffer.isBuffer(inputFile)) { | 65 | if (Buffer.isBuffer(inputFile)) { |
| 67 | break; | 66 | break; |
| 68 | } | 67 | } |
| 69 | if (key.startsWith(inputFile)) { | 68 | if (key.startsWith(inputFile)) { |
| 70 | characterDataCache.delete(key); | 69 | memoryCache.delete(key); |
| 71 | break; | 70 | break; |
| 72 | } | 71 | } |
| 73 | } | 72 | } |
| @@ -16,6 +16,7 @@ import mime from 'mime-types'; | |||
| 16 | import { default as simpleGit } from 'simple-git'; | 16 | import { default as simpleGit } from 'simple-git'; |
| 17 | import chalk from 'chalk'; | 17 | import chalk from 'chalk'; |
| 18 | import { LOG_LEVELS } from './constants.js'; | 18 | import { LOG_LEVELS } from './constants.js'; |
| 19 | import bytes from 'bytes'; | ||
| 19 | 20 | ||
| 20 | /** | 21 | /** |
| 21 | * Parsed config object. | 22 | * Parsed config object. |
| @@ -856,14 +857,10 @@ export function setupLogLevel() { | |||
| 856 | export class MemoryLimitedMap { | 857 | export class MemoryLimitedMap { |
| 857 | /** | 858 | /** |
| 858 | * Creates an instance of MemoryLimitedMap. | 859 | * Creates an instance of MemoryLimitedMap. |
| 859 | * @param {number} maxMemoryInBytes - The maximum allowed memory in bytes for string values. | 860 | * @param {string} cacheCapacity - Maximum memory usage in human-readable format (e.g., '1 GB'). |
| 860 | */ | 861 | */ |
| 861 | constructor(maxMemoryInBytes) { | 862 | constructor(cacheCapacity) { |
| 862 | if (typeof maxMemoryInBytes !== 'number' || maxMemoryInBytes <= 0 || isNaN(maxMemoryInBytes)) { | 863 | this.maxMemory = bytes.parse(cacheCapacity) ?? 0; |
| 863 | console.warn('Invalid maxMemoryInBytes, using a fallback value of 1 GB.'); | ||
| 864 | maxMemoryInBytes = 1024 * 1024 * 1024; // 1 GB | ||
| 865 | } | ||
| 866 | this.maxMemory = maxMemoryInBytes; | ||
| 867 | this.currentMemory = 0; | 864 | this.currentMemory = 0; |
| 868 | this.map = new Map(); | 865 | this.map = new Map(); |
| 869 | this.queue = []; | 866 | this.queue = []; |
| @@ -886,6 +883,10 @@ export class MemoryLimitedMap { | |||
| 886 | * @param {string} value | 883 | * @param {string} value |
| 887 | */ | 884 | */ |
| 888 | set(key, value) { | 885 | set(key, value) { |
| 886 | if (this.maxMemory <= 0) { | ||
| 887 | return; | ||
| 888 | } | ||
| 889 | |||
| 889 | if (typeof key !== 'string' || typeof value !== 'string') { | 890 | if (typeof key !== 'string' || typeof value !== 'string') { |
| 890 | return; | 891 | return; |
| 891 | } | 892 | } |