@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
220 lines (219 loc) • 7.88 kB
JavaScript
"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;