This commit is contained in:
2026-08-02 19:11:54 +02:00
parent 4cc07b9abc
commit 44b2eaf273
18 changed files with 1035 additions and 278 deletions
+72 -5
View File
@@ -3,7 +3,7 @@ import { existsSync } from "node:fs";
import { join, dirname } from "node:path";
import { CachedDirectoryWalker } from "./indexer/fileScanner";
import { ModIndexer } from "./indexer/indexer";
import { DocumentCache, ShallowScanCache } from "./indexer/caches";
import { DocumentCache, IncludeResolveCache, IndexRecordsCache } from "./indexer/caches";
import type { ModIndex } from "./indexer/types";
import { readSettings, type ExtensionSettings } from "./settings";
@@ -20,13 +20,17 @@ export class ModWorkspace {
// only re-reads files whose stat changed (crucial for the ~2.6 GB of .w3x
// art assets in a project like Corona).
private documentCache = new DocumentCache();
private shallowCache = new ShallowScanCache();
private recordsCache = new IndexRecordsCache();
private resolveCache = new IncludeResolveCache();
private context: vscode.ExtensionContext;
private watchers: vscode.FileSystemWatcher[] = [];
private statusBar: vscode.StatusBarItem;
private rebuildTimer: ReturnType<typeof setTimeout> | null = null;
private building = false;
private dirty = false;
constructor(context: vscode.ExtensionContext) {
this.context = context;
this.settings = readSettings();
this.statusBar = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Left,
@@ -57,11 +61,69 @@ export class ModWorkspace {
this.statusBar.hide();
return;
}
this.startWatching();
this.statusBar.text = "$(sync~spin) RA3 XML: indexing…";
this.statusBar.show();
await this.rebuild();
}
/**
* Invalidates cached documents for a path (called by the file watcher and
* on document save), so the next rebuild re-reads it instead of trusting
* the cached copy.
*/
invalidate(path: string): void {
if (!path) return;
this.documentCache.invalidate(path);
this.recordsCache.invalidate(path);
}
/**
* Called when files are created or deleted: include-resolution results
* (which encode file existence) are no longer trustworthy.
*/
invalidateExistence(): void {
this.resolveCache.clear();
}
/**
* Watches the project, SDK and extra DATA roots for file changes and
* invalidates the corresponding cache entries. With caches invalidated
* precisely, rebuilds can trust every other cached file and skip per-file
* stats (huge win on mechanical drives; Corona rebuild dropped from ~38s
* to a few seconds).
*/
private startWatching(): void {
if (!this.projectRoot) return;
const roots = new Set([
this.projectRoot,
this.settings.sdkPath,
...this.settings.additionalDataSearchPaths,
]);
for (const root of roots) {
if (!existsSync(root)) continue;
try {
const watcher = vscode.workspace.createFileSystemWatcher(
new vscode.RelativePattern(root, "**/*"),
);
watcher.onDidCreate((uri) => {
this.invalidate(uri.fsPath);
this.invalidateExistence();
});
watcher.onDidChange((uri) => this.invalidate(uri.fsPath));
watcher.onDidDelete((uri) => {
this.invalidate(uri.fsPath);
this.invalidateExistence();
});
this.watchers.push(watcher);
this.context.subscriptions.push(watcher);
} catch {
// The root may be temporarily unavailable (e.g. removable drive);
// indexing still works, just without watcher-based invalidation.
}
}
}
scheduleRebuild(): void {
if (!this.projectRoot) return;
if (this.rebuildTimer) clearTimeout(this.rebuildTimer);
@@ -70,12 +132,13 @@ export class ModWorkspace {
}, REBUILD_DEBOUNCE_MS);
}
async rebuild(): Promise<void> {
async rebuild(force = false): Promise<void> {
if (!this.projectRoot) return;
if (this.building) {
this.dirty = true;
return;
}
if (force) this.resolveCache.clear();
this.building = true;
this.settings = readSettings();
try {
@@ -88,7 +151,11 @@ export class ModWorkspace {
additionalDataSearchPaths: this.settings.additionalDataSearchPaths,
walker: this.walker,
documentCache: this.documentCache,
shallowCache: this.shallowCache,
recordsCache: this.recordsCache,
resolveCache: this.resolveCache,
// Trust cache entries unless the user explicitly asked for a full
// verification (ra3modxml.reindex).
trustUnchanged: !force,
});
const started = Date.now();
this.index = await indexer.build();
@@ -121,7 +188,7 @@ export class ModWorkspace {
return {
file: { path, stat: null },
parse,
shallow: null,
records: null,
lineMap: new LineMap(text),
};
}