fix: serialize WeatherV2 particle loads
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled

This commit is contained in:
OpenAI Codex
2026-09-09 20:42:17 +08:00
parent e15f583c65
commit 266ef6a184
3 changed files with 28 additions and 3 deletions

View File

@@ -20,6 +20,7 @@ export default function App() {
const [frames, setFrames] = useState<Record<string, string>>({ T0: "PENDING", T1: "PENDING", T2: "PENDING", T3: "PENDING" });
const [transport, setTransport] = useState("PENDING: FILE_REF / FLOAT32_LE / INTERLEAVED_UV");
const [httpTransport, setHttpTransport] = useState("PENDING: WeatherV2 HTTP → canonical U/V → WeatherRuntime");
const [httpLoading, setHttpLoading] = useState(false);
const [weatherVisible, setWeatherVisible] = useState(true);
const [particleVisible, setParticleVisible] = useState(true);
const [visual, setVisual] = useState(initialVisual);
@@ -61,6 +62,8 @@ export default function App() {
const loadHttpWeather = useCallback(async () => {
if (reactTag == null) return setResult("MAP_NOT_READY: wait for GSI/style ready");
if (httpLoading) return;
setHttpLoading(true);
try {
setHttpTransport("LOADING: manifest → time index → WVECBIN1 → canonical U/V");
const loaded = await loadHttpWeatherV2(reactTag);
@@ -71,8 +74,10 @@ export default function App() {
} catch (error) {
setHttpTransport(`ERROR: ${String(error)}`);
setResult(`LOAD HTTP WEATHER: ${String(error)}`);
} finally {
setHttpLoading(false);
}
}, [reactTag]);
}, [httpLoading, reactTag]);
const updateVisual = useCallback((key: keyof typeof initialVisual, value: string) => setVisual((current) => ({ ...current, [key]: value })), []);
const applyVisual = useCallback(() => void command("particle visual", async (tag) => {
@@ -93,7 +98,7 @@ export default function App() {
<Text>Basemap: GSI {basemap}</Text><Text>Dataset: ECMWF HRES 20260807 · 513×769 · 3.01 MiB/frame</Text><Text>Runtime: {snapshot.state} / {snapshot.playbackState} / {snapshot.targetFps}</Text><Text selectable>REAL DATA TRANSPORT = {transport}</Text>
{realWeatherFrames.map((frame) => <Text selectable key={frame.label}>{frame.label} {frame.cycle}: {frames[frame.label]}</Text>)}
<Text selectable>{result}</Text><Button title="LOAD REAL WEATHER T0-T3" onPress={() => void loadRealWeather()} />
<Text selectable>HTTP DATA TRANSPORT = {httpTransport}</Text><Button title="LOAD HTTP WEATHER T0-T3" onPress={() => void loadHttpWeather()} />
<Text selectable>HTTP DATA TRANSPORT = {httpTransport}</Text><Button title={httpLoading ? "LOADING HTTP WEATHER..." : "LOAD HTTP WEATHER T0-T3"} disabled={httpLoading} onPress={() => void loadHttpWeather()} />
<View style={styles.row}>{controls.map(([label, operation]) => <Button key={label} title={label} onPress={() => void command(label, operation)} />)}</View>
<View style={styles.row}><Button title={`Weather ${weatherVisible ? "visible" : "hidden"}`} onPress={() => { const next = !weatherVisible; setWeatherVisible(next); void command("weather visible", (tag) => WeatherRuntime.setWeatherColorVisible(tag, next)); }} /><Button title={`Particle ${particleVisible ? "visible" : "hidden"}`} onPress={() => { const next = !particleVisible; setParticleVisible(next); void command("particle visible", (tag) => WeatherRuntime.setParticleVisible(tag, next)); }} /></View>
<View style={styles.row}>{realWeatherFrames.map((frame) => <Button key={`seek-${frame.label}`} title={`seek ${frame.label}`} onPress={() => void command(`seek ${frame.label}`, (tag) => WeatherRuntime.setForecastTime(tag, frame.validTime))} />)}</View>

View File

@@ -6,6 +6,7 @@
- 包含 WeatherColor、RasterParticle、HostField binary file ingress、Timeline / Playback 与 React Native WeatherRuntime。
- Native/RN binary ingress 已在 STEP-061 验证;GSI + 真实 WeatherV2 + Particle Android 综合验证已在 STEP-062 完成。
- 发布前 Consumer 验证修正 MLRN 依赖为 peer dependency,避免嵌套 native module 与 Windows codegen 长路径。
- WeatherV2 HTTP 全帧加载增加按 map `reactTag` 的 single-flight 保护,避免重复点击造成并发 clear/submit 与非单调快照拒绝。
- 新增纯 JS/TS WeatherV2 HTTP 客户端:manifest/time-index → WVECBIN1 full wind frame → canonical interleaved U/V。
- 新增 `loadWeatherV2WindIntoRuntime()`:顺序下载、转换、写临时 binary file 并通过现有 WeatherRuntime 提交,不建立第二套 Timeline/MIX。
- WeatherV2 文件存储通过 `WeatherV2BinaryFileStore` 注入,包本身不强依赖 Expo FileSystem,NavSea 可复用自己的缓存实现。

View File

@@ -21,7 +21,9 @@ function expectedSubmit(index: number): FieldSubmitResult {
* U/V contract and submits it through the existing map-owned WeatherRuntime.
* Core remains the sole owner of T0/T1/MIX and playback interpolation.
*/
export async function loadWeatherV2WindIntoRuntime(
const inFlightLoads = new Map<number, Promise<LoadWeatherV2WindResult>>();
async function loadWeatherV2WindIntoRuntimeInternal(
options: LoadWeatherV2WindOptions,
): Promise<LoadWeatherV2WindResult> {
const { reactTag, client, fileStore } = options;
@@ -93,3 +95,20 @@ export async function loadWeatherV2WindIntoRuntime(
}
return { context, frames: loaded, runtime };
}
/**
* Serializes full WeatherV2 loads per map view. Repeated taps while a load is in flight
* reuse the same Promise instead of clearing/submitting a second overlapping timeline.
*/
export function loadWeatherV2WindIntoRuntime(
options: LoadWeatherV2WindOptions,
): Promise<LoadWeatherV2WindResult> {
const existing = inFlightLoads.get(options.reactTag);
if (existing != null) return existing;
const task = loadWeatherV2WindIntoRuntimeInternal(options).finally(() => {
if (inFlightLoads.get(options.reactTag) === task) inFlightLoads.delete(options.reactTag);
});
inFlightLoads.set(options.reactTag, task);
return task;
}