前端进度条性能优化、仿真结束后后处理优化;后端C代码生成流程优化:先识别来源,再按照已知未知量需求排序,最后局部求解
This commit is contained in:
1 parent
0dcb465d84
commit
91bd9fb252
77 files changed
+11223
-551
No files matched your search
@@ -0,0 +1,165 @@
|
||||
import type { SimulationResultsSnapshot } from "./SimulationResultsView";
|
||||
|
||||
export const RESULT_SNAPSHOT_KEY = "system-simulation-flow:latest-result";
|
||||
const DATABASE = "system-simulation-results";
|
||||
const CHUNK_SIZE = 32768;
|
||||
const BATCH_VALUES = 131072;
|
||||
type Header = {
|
||||
snapshot: SimulationResultsSnapshot;
|
||||
lengths: Record<string, number>;
|
||||
};
|
||||
type Chunk = { key: IDBValidKey; values: Float64Array };
|
||||
let database: Promise<IDBDatabase> | undefined;
|
||||
let saveSequence = 0;
|
||||
let pendingSaves = 0;
|
||||
let ownedCacheId: string | undefined;
|
||||
|
||||
export function resultSavePending() { return pendingSaves > 0; }
|
||||
|
||||
function openDatabase() {
|
||||
if (!database) database = new Promise<IDBDatabase>((resolve, reject) => {
|
||||
let blocked = false;
|
||||
const request = indexedDB.open(DATABASE, 1);
|
||||
request.onupgradeneeded = () => {
|
||||
request.result.createObjectStore("headers");
|
||||
request.result.createObjectStore("chunks");
|
||||
};
|
||||
request.onerror = () => reject(request.error);
|
||||
request.onblocked = () => {
|
||||
blocked = true;
|
||||
reject(new Error("结果数据库被其他页面占用,请关闭旧页面后重试"));
|
||||
};
|
||||
request.onsuccess = () => {
|
||||
const db = request.result;
|
||||
if (blocked) { db.close(); return; }
|
||||
db.onversionchange = () => { db.close(); database = undefined; };
|
||||
resolve(db);
|
||||
};
|
||||
}).catch((error) => { database = undefined; throw error; });
|
||||
return database;
|
||||
}
|
||||
|
||||
function writeBatch(db: IDBDatabase, chunks: Chunk[], header?: [string, Header]) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const transaction = db.transaction(["headers", "chunks"], "readwrite");
|
||||
transaction.oncomplete = () => resolve();
|
||||
transaction.onabort = () => reject(transaction.error ?? new Error("结果保存事务已中止"));
|
||||
transaction.onerror = () => {}; // onabort reports the transaction failure once.
|
||||
try {
|
||||
for (const chunk of chunks) transaction.objectStore("chunks").put(chunk.values, chunk.key);
|
||||
if (header) transaction.objectStore("headers").put(header[1], header[0]);
|
||||
} catch (error) {
|
||||
transaction.abort();
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteCache(db: IDBDatabase, cacheId: string) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const transaction = db.transaction(["headers", "chunks"], "readwrite");
|
||||
transaction.oncomplete = () => resolve();
|
||||
transaction.onabort = () => reject(transaction.error);
|
||||
transaction.objectStore("headers").delete(cacheId);
|
||||
transaction.objectStore("chunks").delete(IDBKeyRange.bound([cacheId], [cacheId, []]));
|
||||
});
|
||||
}
|
||||
|
||||
/** Publish the small session pointer only after every data chunk is durably committed. */
|
||||
export async function storeResultSnapshot(snapshot: SimulationResultsSnapshot) {
|
||||
const sequence = ++saveSequence;
|
||||
const cacheId = typeof crypto.randomUUID === "function"
|
||||
? crypto.randomUUID()
|
||||
: `${Date.now()}-${sequence}-${Math.random().toString(36).slice(2)}`;
|
||||
pendingSaves++;
|
||||
let db: IDBDatabase | undefined;
|
||||
let committed = false;
|
||||
try {
|
||||
// Yield before serialization so the ready result and controls can paint first.
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
||||
if (sequence !== saveSequence) return false;
|
||||
db = await openDatabase();
|
||||
const lengths: Record<string, number> = Object.create(null);
|
||||
let batch: Chunk[] = [];
|
||||
let batchSize = 0;
|
||||
for (const [name, values] of Object.entries(snapshot.result.series)) {
|
||||
lengths[name] = values.length;
|
||||
for (let offset = 0; offset < values.length; offset += CHUNK_SIZE) {
|
||||
if (sequence !== saveSequence) return false;
|
||||
const length = Math.min(CHUNK_SIZE, values.length - offset);
|
||||
const block = new Float64Array(length);
|
||||
for (let i = 0; i < length; i++) block[i] = values[offset + i];
|
||||
batch.push({ key: [cacheId, name, offset], values: block });
|
||||
batchSize += length;
|
||||
if (batchSize >= BATCH_VALUES) {
|
||||
await writeBatch(db, batch);
|
||||
batch = [];
|
||||
batchSize = 0;
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sequence !== saveSequence) return false;
|
||||
await writeBatch(db, batch, [cacheId, {
|
||||
snapshot: { ...snapshot, result: { ...snapshot.result, series: Object.create(null) } }, lengths,
|
||||
}]);
|
||||
if (sequence !== saveSequence) return false;
|
||||
sessionStorage.setItem(RESULT_SNAPSHOT_KEY, JSON.stringify({ storage: "indexeddb", version: 1, cacheId }));
|
||||
committed = true;
|
||||
// Only retire this page's own previous save, never another tab's loaded snapshot.
|
||||
const previous = ownedCacheId;
|
||||
ownedCacheId = cacheId;
|
||||
if (previous) void deleteCache(db, previous).catch(() => {});
|
||||
return true;
|
||||
} finally {
|
||||
pendingSaves--;
|
||||
if (db && !committed) await deleteCache(db, cacheId).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadStoredResultSnapshot(): Promise<unknown | null> {
|
||||
const raw = sessionStorage.getItem(RESULT_SNAPSHOT_KEY);
|
||||
if (!raw) return null;
|
||||
const value = JSON.parse(raw);
|
||||
// Read legacy sessionStorage data without rewriting or duplicating it at startup.
|
||||
if (value?.storage !== "indexeddb") return value;
|
||||
if (value.version !== 1 || typeof value.cacheId !== "string") return null;
|
||||
const db = await openDatabase();
|
||||
return new Promise<SimulationResultsSnapshot | null>((resolve, reject) => {
|
||||
const transaction = db.transaction(["headers", "chunks"], "readonly");
|
||||
let result: SimulationResultsSnapshot | null = null;
|
||||
let failure: Error | null = null;
|
||||
transaction.oncomplete = () => failure ? reject(failure) : resolve(result);
|
||||
transaction.onabort = () => reject(transaction.error);
|
||||
const request = transaction.objectStore("headers").get(value.cacheId);
|
||||
request.onsuccess = () => {
|
||||
const header = request.result as Header | undefined;
|
||||
if (!header) { failure = new Error("结果缓存已丢失,请重新载入结果文件"); return; }
|
||||
result = header.snapshot;
|
||||
for (const [name, length] of Object.entries(header.lengths)) {
|
||||
result.result.series[name] = new Array<number>(length);
|
||||
}
|
||||
const loaded: Record<string, number> = Object.create(null);
|
||||
const cursor = transaction.objectStore("chunks").openCursor(IDBKeyRange.bound([value.cacheId], [value.cacheId, []]));
|
||||
cursor.onsuccess = () => {
|
||||
const entry = cursor.result;
|
||||
if (!entry) {
|
||||
for (const [name, length] of Object.entries(header.lengths)) {
|
||||
if ((loaded[name] ?? 0) !== length) failure = new Error("结果缓存不完整,请重新载入结果文件");
|
||||
}
|
||||
return;
|
||||
}
|
||||
const [, name, offset] = entry.key as [string, string, number];
|
||||
const block = entry.value as Float64Array;
|
||||
const values = result!.result.series[name];
|
||||
if (!values || offset < 0 || offset + block.length > values.length) {
|
||||
failure = new Error("结果缓存分块无效");
|
||||
} else {
|
||||
for (let i = 0; i < block.length; i++) values[offset + i] = block[i];
|
||||
loaded[name] = (loaded[name] ?? 0) + block.length;
|
||||
}
|
||||
entry.continue();
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user