UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

220 lines (219 loc) 7.88 kB
"use strict"; // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. Object.defineProperty(exports, "__esModule", { value: true }); /** * LevelDbIndex manages metadata about LevelDB files to enable lazy loading. * * Instead of loading all files upfront, this class: * 1. Parses the MANIFEST to understand file metadata * 2. Tracks key ranges per file for efficient lookup * 3. Enables on-demand loading of specific files when keys are requested * * This dramatically reduces initial memory usage for large worlds. */ class LevelDbIndex { /** Indexed LDB files with metadata */ ldbFileIndexes = []; /** Indexed LOG files with metadata */ logFileIndexes = []; /** Map of file index to file info for quick lookup */ filesByIndex = new Map(); /** Deleted file numbers from manifest */ deletedFileNumbers = new Set(); /** Total number of files in the index */ get totalFiles() { return this.ldbFileIndexes.length + this.logFileIndexes.length; } /** Number of files that have been loaded */ get loadedFileCount() { let count = 0; for (const idx of this.ldbFileIndexes) { if (idx.isLoaded) count++; } for (const idx of this.logFileIndexes) { if (idx.isLoaded) count++; } return count; } /** * Initialize the index from manifest metadata. * This does NOT load any file contents - just organizes metadata. */ initFromManifest(ldbFiles, logFiles, newFileLevel, newFileNumber, newFileSmallest, newFileLargest, deletedFileNumber) { // Track deleted files this.deletedFileNumbers.clear(); if (deletedFileNumber) { for (const num of deletedFileNumber) { this.deletedFileNumbers.add(num); } } // Index LDB files this.ldbFileIndexes = []; this.filesByIndex.clear(); for (const file of ldbFiles) { try { const index = parseInt(file.name); // Skip deleted files if (this.deletedFileNumbers.has(index)) { continue; } // Find level and key range from manifest let level = 0; let smallest; let largest; if (newFileLevel && newFileNumber) { for (let j = 0; j < newFileNumber.length; j++) { if (newFileNumber[j] === index) { level = newFileLevel[j]; if (newFileSmallest) smallest = newFileSmallest[j]; if (newFileLargest) largest = newFileLargest[j]; break; } } } const fileIndex = { fileInfo: { index: index, file: file, isDeleted: false, level: level, }, smallestKey: smallest, largestKey: largest, isLoaded: false, // Higher level = lower priority (older data, less likely to be superceded) priority: 1000 - level, }; this.ldbFileIndexes.push(fileIndex); this.filesByIndex.set(index, fileIndex); } catch (e) { // Skip files that don't have numeric names } } // Sort LDB files by level (descending) then index (ascending) // This ensures we process files in the correct order for supercession this.ldbFileIndexes.sort((a, b) => { if (a.fileInfo.level === b.fileInfo.level) { return a.fileInfo.index - b.fileInfo.index; } return b.fileInfo.level - a.fileInfo.level; }); // Index LOG files this.logFileIndexes = []; for (const file of logFiles) { this.logFileIndexes.push({ file: file, name: file.name, isLoaded: false, }); } // Sort LOG files by name (ascending) - later files supercede earlier this.logFileIndexes.sort((a, b) => a.name.localeCompare(b.name)); } /** * Find files that might contain a specific key based on key range metadata. * Returns files in priority order (LOG files first, then LDB by level). * * Note: This is a heuristic - if key ranges aren't available from manifest, * all files are considered potential matches. */ findPotentialFilesForKey(key) { const result = []; // LOG files always have priority (most recent writes) // We need to check all LOG files since they don't have key range metadata for (const logIdx of this.logFileIndexes) { result.push(logIdx); } // Filter LDB files by key range if available for (const ldbIdx of this.ldbFileIndexes) { const hasRange = ldbIdx.smallestKey !== undefined && ldbIdx.largestKey !== undefined; if (hasRange) { // Check if key falls within the file's key range if (key >= ldbIdx.smallestKey && key <= ldbIdx.largestKey) { result.push(ldbIdx); } } else { // No range metadata - must check this file result.push(ldbIdx); } } return result; } /** * Get files that should be loaded for initial chunk enumeration. * This returns a subset of files that contain chunk metadata keys * (keys that are 9-14 bytes, which identify chunk coordinates). * * Returns files in the order they should be processed for correct supercession. */ getFilesForChunkEnumeration() { // For initial enumeration, we need all files in the correct order // LDB files first (sorted by level desc, index asc), then LOG files const result = []; for (const ldbIdx of this.ldbFileIndexes) { result.push(ldbIdx); } for (const logIdx of this.logFileIndexes) { result.push(logIdx); } return result; } /** * Mark a file as loaded. */ markFileLoaded(file) { for (const ldbIdx of this.ldbFileIndexes) { if (ldbIdx.fileInfo.file === file) { ldbIdx.isLoaded = true; return; } } for (const logIdx of this.logFileIndexes) { if (logIdx.file === file) { logIdx.isLoaded = true; return; } } } /** * Check if all files have been loaded. */ isFullyLoaded() { for (const ldbIdx of this.ldbFileIndexes) { if (!ldbIdx.isLoaded) return false; } for (const logIdx of this.logFileIndexes) { if (!logIdx.isLoaded) return false; } return true; } /** * Get unloaded files in priority order for background loading. */ getUnloadedFiles() { const result = []; // LDB files in priority order for (const ldbIdx of this.ldbFileIndexes) { if (!ldbIdx.isLoaded) { result.push(ldbIdx); } } // LOG files (highest priority for supercession) for (const logIdx of this.logFileIndexes) { if (!logIdx.isLoaded) { result.push(logIdx); } } return result; } } exports.default = LevelDbIndex;