mirror of
https://github.com/exogen/t2-mapper.git
synced 2026-02-26 01:53:53 +00:00
add loading progress support and new indicator
This commit is contained in:
parent
8e6ae456f0
commit
2a730b8a44
55 changed files with 207 additions and 71609 deletions
|
|
@ -7,6 +7,7 @@ import { renderObject } from "./renderObject";
|
|||
import { memo, useEffect, useState } from "react";
|
||||
import { RuntimeProvider } from "./RuntimeProvider";
|
||||
import {
|
||||
createProgressTracker,
|
||||
createScriptCache,
|
||||
FileSystemHandler,
|
||||
runServer,
|
||||
|
|
@ -50,6 +51,7 @@ function useParsedMission(name: string) {
|
|||
interface ExecutedMissionState {
|
||||
missionGroup: TorqueObject | undefined;
|
||||
runtime: TorqueRuntime | undefined;
|
||||
progress: number;
|
||||
}
|
||||
|
||||
function useExecutedMission(
|
||||
|
|
@ -59,6 +61,7 @@ function useExecutedMission(
|
|||
const [state, setState] = useState<ExecutedMissionState>({
|
||||
missionGroup: undefined,
|
||||
runtime: undefined,
|
||||
progress: 0,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -70,6 +73,13 @@ function useExecutedMission(
|
|||
// FIXME: Always just runs as the first game type for now...
|
||||
const missionType = parsedMission.missionTypes[0];
|
||||
|
||||
// Create progress tracker and update state on changes
|
||||
const progressTracker = createProgressTracker();
|
||||
const handleProgress = () => {
|
||||
setState((prev) => ({ ...prev, progress: progressTracker.progress }));
|
||||
};
|
||||
progressTracker.on("update", handleProgress);
|
||||
|
||||
const { runtime } = runServer({
|
||||
missionName,
|
||||
missionType,
|
||||
|
|
@ -78,10 +88,12 @@ function useExecutedMission(
|
|||
fileSystem,
|
||||
cache: scriptCache,
|
||||
signal: controller.signal,
|
||||
progress: progressTracker,
|
||||
ignoreScripts: [
|
||||
"scripts/admin.cs",
|
||||
"scripts/ai.cs",
|
||||
"scripts/aiCTF.cs",
|
||||
"scripts/aiTDM.cs",
|
||||
"scripts/aiHunters.cs",
|
||||
"scripts/deathMessages.cs",
|
||||
"scripts/graphBuild.cs",
|
||||
|
|
@ -92,11 +104,12 @@ function useExecutedMission(
|
|||
},
|
||||
onMissionLoadDone: () => {
|
||||
const missionGroup = runtime.getObjectByName("MissionGroup");
|
||||
setState({ missionGroup, runtime });
|
||||
setState({ missionGroup, runtime, progress: 1 });
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
progressTracker.off("update", handleProgress);
|
||||
controller.abort();
|
||||
runtime.destroy();
|
||||
};
|
||||
|
|
@ -107,7 +120,7 @@ function useExecutedMission(
|
|||
|
||||
interface MissionProps {
|
||||
name: string;
|
||||
onLoadingChange?: (isLoading: boolean) => void;
|
||||
onLoadingChange?: (isLoading: boolean, progress?: number) => void;
|
||||
}
|
||||
|
||||
export const Mission = memo(function Mission({
|
||||
|
|
@ -115,12 +128,15 @@ export const Mission = memo(function Mission({
|
|||
onLoadingChange,
|
||||
}: MissionProps) {
|
||||
const { data: parsedMission } = useParsedMission(name);
|
||||
const { missionGroup, runtime } = useExecutedMission(name, parsedMission);
|
||||
const { missionGroup, runtime, progress } = useExecutedMission(
|
||||
name,
|
||||
parsedMission,
|
||||
);
|
||||
const isLoading = !missionGroup || !runtime;
|
||||
|
||||
useEffect(() => {
|
||||
onLoadingChange?.(isLoading);
|
||||
}, [isLoading, onLoadingChange]);
|
||||
onLoadingChange?.(isLoading, progress);
|
||||
}, [isLoading, progress, onLoadingChange]);
|
||||
|
||||
if (isLoading) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { TorqueObject, TorqueRuntime, TorqueRuntimeOptions } from "./types";
|
|||
export { generate, type GeneratorOptions } from "./codegen";
|
||||
export type { Program } from "./ast";
|
||||
export { createBuiltins } from "./builtins";
|
||||
export { createProgressTracker, type ProgressTracker } from "./progress";
|
||||
export { createRuntime, createScriptCache } from "./runtime";
|
||||
export { normalizePath } from "./utils";
|
||||
export type {
|
||||
|
|
|
|||
75
src/torqueScript/progress.ts
Normal file
75
src/torqueScript/progress.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
export type ProgressEventType = "update";
|
||||
export type ProgressListener = () => void;
|
||||
|
||||
export interface ProgressTracker {
|
||||
/** Total items discovered so far (increases as dependencies are found) */
|
||||
readonly total: number;
|
||||
/** Items completed */
|
||||
readonly loaded: number;
|
||||
/** Currently loading item path, if any */
|
||||
readonly current: string | null;
|
||||
/** Progress as a ratio from 0 to 1 (or 0 if total is 0) */
|
||||
readonly progress: number;
|
||||
/** Subscribe to progress updates */
|
||||
on(event: ProgressEventType, listener: ProgressListener): void;
|
||||
/** Unsubscribe from progress updates */
|
||||
off(event: ProgressEventType, listener: ProgressListener): void;
|
||||
}
|
||||
|
||||
export interface ProgressTrackerInternal extends ProgressTracker {
|
||||
/** Increment total count when a new item is discovered */
|
||||
addItem(path: string): void;
|
||||
/** Mark an item as completed */
|
||||
completeItem(): void;
|
||||
/** Set the currently loading item */
|
||||
setCurrent(path: string | null): void;
|
||||
}
|
||||
|
||||
export function createProgressTracker(): ProgressTrackerInternal {
|
||||
const listeners = new Set<ProgressListener>();
|
||||
|
||||
let total = 0;
|
||||
let loaded = 0;
|
||||
let current: string | null = null;
|
||||
|
||||
function notify(): void {
|
||||
for (const listener of listeners) {
|
||||
listener();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
get total() {
|
||||
return total;
|
||||
},
|
||||
get loaded() {
|
||||
return loaded;
|
||||
},
|
||||
get current() {
|
||||
return current;
|
||||
},
|
||||
get progress() {
|
||||
return total === 0 ? 0 : loaded / total;
|
||||
},
|
||||
on(_event: ProgressEventType, listener: ProgressListener) {
|
||||
listeners.add(listener);
|
||||
},
|
||||
off(_event: ProgressEventType, listener: ProgressListener) {
|
||||
listeners.delete(listener);
|
||||
},
|
||||
addItem(path: string) {
|
||||
total++;
|
||||
current = path;
|
||||
notify();
|
||||
},
|
||||
completeItem() {
|
||||
loaded++;
|
||||
current = null;
|
||||
notify();
|
||||
},
|
||||
setCurrent(path: string | null) {
|
||||
current = path;
|
||||
notify();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1121,12 +1121,16 @@ export function createRuntime(
|
|||
return;
|
||||
}
|
||||
|
||||
// Track this script in progress
|
||||
options.progress?.addItem(ref);
|
||||
|
||||
const loadPromise = (async () => {
|
||||
// Pass original path to loader - it handles its own normalization
|
||||
const source = await loader(ref);
|
||||
if (source == null) {
|
||||
console.warn(`Script not found: ${ref}`);
|
||||
state.failedScripts.add(normalized);
|
||||
options.progress?.completeItem();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1136,6 +1140,7 @@ export function createRuntime(
|
|||
} catch (err) {
|
||||
console.warn(`Failed to parse script: ${ref}`, err);
|
||||
state.failedScripts.add(normalized);
|
||||
options.progress?.completeItem();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1152,6 +1157,7 @@ export function createRuntime(
|
|||
|
||||
// Store the parsed AST
|
||||
state.scripts.set(normalized, depAst);
|
||||
options.progress?.completeItem();
|
||||
})();
|
||||
|
||||
loadingPromises.set(normalized, loadPromise);
|
||||
|
|
@ -1173,13 +1179,19 @@ export function createRuntime(
|
|||
return createLoadedScript(state.scripts.get(normalized)!, path);
|
||||
}
|
||||
|
||||
// Track this script in progress
|
||||
options.progress?.addItem(path);
|
||||
|
||||
// Pass original path to loader - it handles its own normalization
|
||||
const source = await loader(path);
|
||||
if (source == null) {
|
||||
options.progress?.completeItem();
|
||||
throw new Error(`Script not found: ${path}`);
|
||||
}
|
||||
|
||||
return loadFromSource(source, { path });
|
||||
const result = await loadFromSource(source, { path });
|
||||
options.progress?.completeItem();
|
||||
return result;
|
||||
}
|
||||
|
||||
async function loadFromSource(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { Program } from "./ast";
|
||||
import type { ProgressTrackerInternal } from "./progress";
|
||||
import type { CaseInsensitiveMap } from "./utils";
|
||||
|
||||
export type TorqueFunction = (...args: any[]) => any;
|
||||
|
|
@ -108,6 +109,12 @@ export interface TorqueRuntimeOptions {
|
|||
* Create with `createScriptCache()`.
|
||||
*/
|
||||
cache?: ScriptCache;
|
||||
/**
|
||||
* Progress tracker for monitoring script loading. If provided, the runtime
|
||||
* will report loading progress as scripts are discovered and loaded.
|
||||
* Create with `createProgressTracker()`.
|
||||
*/
|
||||
progress?: ProgressTrackerInternal;
|
||||
}
|
||||
|
||||
export interface LoadScriptOptions {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue