前端进度条性能优化、仿真结束后后处理优化;后端C代码生成流程优化:先识别来源,再按照已知未知量需求排序,最后局部求解

This commit is contained in:
ljz committed 2026-09-11 11:27:54 +08:00
1 parent 0dcb465d84
commit 91bd9fb252
77 files changed
+11223 -551

No files matched your search

+139
View File
@@ -0,0 +1,139 @@
export type ChartSample = { x: number; y: number; dataIndex: number; breakBefore: boolean };
type Bounds = { xMin: number; xMax: number; yMin: number; yMax: number; sorted: boolean };
const seriesCache = new WeakMap<number[], WeakMap<number[], ChartSample[]>>();
const boundsCache = new WeakMap<Array<{ x: number; y: number }>, Bounds>();
const cursorCache = new WeakMap<number[], ChartSample[]>();
const selectionCache = new WeakMap<ChartSample[], Map<string, ChartSample[]>>();
/** Cached only by immutable source arrays; collected together with their result snapshot. */
export function preparedChartSamples(time: number[], values: number[]) {
let byTime = seriesCache.get(values);
if (!byTime) { byTime = new WeakMap(); seriesCache.set(values, byTime); }
let samples = byTime.get(time);
if (samples) return samples;
samples = [];
let breakBefore = true;
for (let i = 0; i < time.length; i++) {
const x = Number(time[i]), y = Number(values[i]);
if (!Number.isFinite(x)) { breakBefore = true; continue; }
// Preserve the existing isolated-force-event interpolation policy.
if (!Number.isFinite(y)) continue;
samples.push({ x, y, dataIndex: i, breakBefore });
breakBefore = false;
}
byTime.set(time, samples);
return samples;
}
export function chartCursorSamples(time: number[]) {
let samples = cursorCache.get(time);
if (!samples) {
samples = [];
for (let i = 0; i < time.length; i++) {
if (Number.isFinite(time[i])) samples.push({ x: time[i], y: i, dataIndex: i, breakBefore: false });
}
cursorCache.set(time, samples);
}
return samples;
}
export function chartSampleBounds(samples: Array<{ x: number; y: number }>): Bounds {
const cached = boundsCache.get(samples);
if (cached) return cached;
const bounds = { xMin: Infinity, xMax: -Infinity, yMin: Infinity, yMax: -Infinity, sorted: true };
let previousX = -Infinity;
for (const sample of samples) {
bounds.xMin = Math.min(bounds.xMin, sample.x);
bounds.xMax = Math.max(bounds.xMax, sample.x);
bounds.yMin = Math.min(bounds.yMin, sample.y);
bounds.yMax = Math.max(bounds.yMax, sample.y);
if (sample.x < previousX) bounds.sorted = false;
previousX = sample.x;
}
boundsCache.set(samples, bounds);
return bounds;
}
export function combinedChartBounds(curves: Array<{ samples: Array<{ x: number; y: number }> }>) {
const result = { xMin: Infinity, xMax: -Infinity, yMin: Infinity, yMax: -Infinity };
for (const curve of curves) {
const bounds = chartSampleBounds(curve.samples);
result.xMin = Math.min(result.xMin, bounds.xMin);
result.xMax = Math.max(result.xMax, bounds.xMax);
result.yMin = Math.min(result.yMin, bounds.yMin);
result.yMax = Math.max(result.yMax, bounds.yMax);
}
return result;
}
function lowerBound(samples: Array<{ x: number }>, x: number, upper = false) {
let low = 0, high = samples.length;
while (low < high) {
const middle = (low + high) >>> 1;
if (samples[middle].x < x || (upper && samples[middle].x === x)) low = middle + 1;
else high = middle;
}
return low;
}
export function visibleSampleRange(samples: Array<{ x: number; y: number }>, xMin: number, xMax: number) {
if (!chartSampleBounds(samples).sorted) return [0, samples.length] as const;
// Include both outside neighbours, preserving segments crossing viewport boundaries.
return [Math.max(0, lowerBound(samples, xMin) - 1), Math.min(samples.length, lowerBound(samples, xMax, true) + 1)] as const;
}
/** Keep first/min/max/last per pixel column in original order; no averaging of peaks. */
export function chartDisplaySamples(samples: ChartSample[], xMin: number, xMax: number, pixelWidth: number) {
const width = Math.max(1, Math.ceil(pixelWidth));
const key = `${xMin}:${xMax}:${width}`;
let cache = selectionCache.get(samples);
if (!cache) { cache = new Map(); selectionCache.set(samples, cache); }
const cached = cache.get(key);
if (cached) return cached;
const [start, end] = visibleSampleRange(samples, xMin, xMax);
const result: ChartSample[] = [];
if (end - start <= width * 4 || !chartSampleBounds(samples).sorted) {
for (let i = start; i < end; i++) result.push(samples[i]);
} else {
let bucket = -Infinity;
let first = -1, last = -1, minimum = -1, maximum = -1;
const flush = () => {
if (first < 0) return;
const indices = [first, minimum, maximum, last].sort((a, b) => a - b);
let previous = -1;
for (const index of indices) {
if (index !== previous) result.push(samples[index]);
previous = index;
}
};
for (let i = start; i < end; i++) {
const sample = samples[i];
const nextBucket = Math.max(-1, Math.min(width, Math.floor((sample.x - xMin) / Math.max(xMax - xMin, 1e-12) * width)));
if (nextBucket !== bucket || sample.breakBefore || first < 0) {
flush();
first = last = minimum = maximum = i;
bucket = nextBucket;
} else {
last = i;
if (sample.y < samples[minimum].y) minimum = i;
if (sample.y > samples[maximum].y) maximum = i;
}
}
flush();
}
// A few active windows/viewports may share a curve without retaining unbounded history.
if (cache.size >= 4) cache.delete(cache.keys().next().value!);
cache.set(key, result);
return result;
}
export function sampledChartPath(
time: number[], values: number[],
xPosition: (value: number) => number, yPosition: (value: number) => number,
xMin: number, xMax: number, pixelWidth: number,
) {
const samples = chartDisplaySamples(preparedChartSamples(time, values), xMin, xMax, pixelWidth);
return samples.map((sample, index) =>
`${index === 0 || sample.breakBefore ? "M" : "L"} ${xPosition(sample.x).toFixed(2)} ${yPosition(sample.y).toFixed(2)}`,
).join(" ");
}