feat(511-A ③ 토량 · 구조물 셈 열기): 계산 자리 판 quantity · carry · fixed_browser 바꿀 수 있음(기본 both · 판 줄 차례 고정) · quantity = server 면 F7 저장 노선 면적 · C4 소단 사본 = 잠잠 뒤 서버 값만 · E1 유토곡선 = 서버 값으로 다시 그림(그 사이 앞 곡선 · 「서버 셈 중」) · F7 초안 면적 · C4 초안 소단 = 브라우저 값 + 서버 대조(다르면 얹음) · F8 = 브라우저 부름 없음 · 거울 시험(서버 답 = 브라우저 TS 답) — 요구 320-4
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
* serverCalc 한 번 묻기(signal 로 끊음 · 왕복 흉내 `delayMs` · 기록)
|
||||
* afterQuiet 잠잠 뒤 — 같은 칸 새 부름이 앞 것을 끊음(앞 것 = undefined · 늦은 답 버림)
|
||||
* calcChecked 동기 자리 — 브라우저 값 바로 · server 면 잠잠 뒤 같은 입력 서버 셈 → 대조 · 다르면 apply
|
||||
* calcOnServerOnly 비동기 자리 — server 면 서버 값만(잠잠 뒤 · 끊긴 부름 = undefined)
|
||||
* calcFromServer 그리기 자리 — server 면 서버 값만(입력 같은 답 · 없으면 앞 답) · 답 오면 redraw
|
||||
* 기록 = `window.__aisloCalcServer`(calls · local = 브라우저 ms · delayMs) — 시간 표 · 화면 검증용.
|
||||
* 조작 중 왕복 = 지침 5장 「네트워크 왕복 금지」의 시험용 예외(판 기본값 both).
|
||||
@@ -62,9 +63,6 @@ function wait(ms: number, signal?: AbortSignal): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
const bodyOf = (id: string, args: unknown[]): string =>
|
||||
JSON.stringify({ run: [id], inputs: { args: { [id]: args } } });
|
||||
|
||||
function record(call: CalcServerCall): void {
|
||||
const calls = log().calls;
|
||||
calls.push(call);
|
||||
@@ -96,18 +94,21 @@ export async function serverCalc(
|
||||
message?: string;
|
||||
};
|
||||
if (!response.ok || !data.results) throw new Error(data.message ?? `HTTP ${response.status}`);
|
||||
record({
|
||||
key,
|
||||
id: run.join("+"),
|
||||
browserMs: null,
|
||||
roundMs: r2(now() - t0),
|
||||
nodeMs: data.ms ?? null,
|
||||
wallMs: data.wall_ms ?? null,
|
||||
bytes: body.length,
|
||||
same: null,
|
||||
applied: true,
|
||||
...extra,
|
||||
});
|
||||
// extra 그 객체로 기록 — 대조 뒤 같음 · apply 를 부른 쪽이 그 자리에서 채움
|
||||
record(
|
||||
Object.assign(extra, {
|
||||
key,
|
||||
id: run.join("+"),
|
||||
browserMs: null,
|
||||
roundMs: r2(now() - t0),
|
||||
nodeMs: data.ms ?? null,
|
||||
wallMs: data.wall_ms ?? null,
|
||||
bytes: body.length,
|
||||
same: null,
|
||||
applied: true,
|
||||
...extra,
|
||||
}),
|
||||
);
|
||||
return data.results;
|
||||
}
|
||||
|
||||
@@ -134,13 +135,38 @@ export async function afterQuiet<T>(
|
||||
}
|
||||
}
|
||||
|
||||
/** 브라우저 셈 + 시간 기록(묶음 판 상관없이) · server 면 잠잠 뒤 서버 대조 · 다르면 apply(서버 값). */
|
||||
/** 서버 묻는 꼴 — 없으면 시험 길(`inputs.args[id]` = 함수 인자 · 답 = `results[id]`). */
|
||||
export interface CalcRequest {
|
||||
run: string[];
|
||||
inputs: Record<string, unknown>;
|
||||
pick: (results: Record<string, unknown>) => unknown;
|
||||
}
|
||||
|
||||
/** F7 구조물 면적 · C4 소단 구간 — 등록부 `node:` 입력 그대로. */
|
||||
export const areasRequest = (rows: unknown[]): CalcRequest => ({
|
||||
run: ["structure.layouts"],
|
||||
inputs: { detail: { cross_sections: rows } },
|
||||
pick: (results) => (results["structure.layouts"] as { areas: unknown }).areas,
|
||||
});
|
||||
export const bermRequest = (structures: unknown[]): CalcRequest => ({
|
||||
run: ["cross.berm_spans"],
|
||||
inputs: { berm: { structures, chainages: [] } },
|
||||
pick: (results) => (results["cross.berm_spans"] as { spans: unknown }).spans,
|
||||
});
|
||||
|
||||
const trialRequest = (id: string, args: unknown[]): CalcRequest => ({
|
||||
run: [id],
|
||||
inputs: { args: { [id]: args } },
|
||||
pick: (results) => results[id],
|
||||
});
|
||||
|
||||
/** 브라우저 셈 + 시간 기록(판 상관없이) · server 면 잠잠 뒤 서버 대조 · 다르면 apply(서버 값). */
|
||||
export function calcChecked<A extends unknown[], R>(
|
||||
key: string,
|
||||
id: string,
|
||||
fn: (...args: A) => R,
|
||||
args: A,
|
||||
apply?: (value: R) => void,
|
||||
options: { apply?: (value: R) => void; request?: CalcRequest } = {},
|
||||
): R {
|
||||
const t0 = now();
|
||||
const value = fn(...args);
|
||||
@@ -149,20 +175,40 @@ export function calcChecked<A extends unknown[], R>(
|
||||
local.push(browserMs);
|
||||
if (local.length > LOCAL_SIZE) local.shift();
|
||||
if (!calcOnServerNow(key)) return value;
|
||||
const body = bodyOf(id, args);
|
||||
const request = options.request ?? trialRequest(id, args);
|
||||
const body = JSON.stringify({ run: request.run, inputs: request.inputs });
|
||||
const mine = JSON.stringify(plain(value));
|
||||
const extra: Partial<CalcServerCall> = { browserMs, same: null, applied: false };
|
||||
void afterQuiet(id, (signal) => serverCalc(key, [id], body, signal, extra)).then((results) => {
|
||||
if (!results) return;
|
||||
extra.same = JSON.stringify(results[id]) === mine;
|
||||
if (!extra.same && apply) {
|
||||
apply(results[id] as R);
|
||||
extra.applied = true;
|
||||
}
|
||||
});
|
||||
void afterQuiet(id, (signal) => serverCalc(key, request.run, body, signal, extra)).then(
|
||||
(results) => {
|
||||
if (!results) return;
|
||||
const theirs = request.pick(results);
|
||||
extra.same = JSON.stringify(theirs) === mine;
|
||||
if (!extra.same && options.apply) {
|
||||
options.apply(theirs as R);
|
||||
extra.applied = true;
|
||||
}
|
||||
},
|
||||
);
|
||||
return value;
|
||||
}
|
||||
|
||||
/** 서버 값만(비동기 자리) — 잠잠 뒤 묻고 고른 값 · 새 부름 · outer 끊김 · 실패 = undefined. */
|
||||
export async function calcOnServerOnly<R>(
|
||||
key: string,
|
||||
slot: string,
|
||||
request: CalcRequest,
|
||||
outer?: AbortSignal,
|
||||
): Promise<R | undefined> {
|
||||
const body = JSON.stringify({ run: request.run, inputs: request.inputs });
|
||||
const results = await afterQuiet(
|
||||
slot,
|
||||
(signal) => serverCalc(key, request.run, body, signal),
|
||||
outer,
|
||||
);
|
||||
return results ? (request.pick(results) as R) : undefined;
|
||||
}
|
||||
|
||||
const held = new Map<string, { body: string; value: unknown }>();
|
||||
const asked = new Map<string, string>();
|
||||
|
||||
@@ -173,7 +219,8 @@ export function calcFromServer<R>(
|
||||
args: unknown[],
|
||||
redraw: () => void,
|
||||
): { value: R | undefined; fresh: boolean } {
|
||||
const body = bodyOf(id, args);
|
||||
const request = trialRequest(id, args);
|
||||
const body = JSON.stringify({ run: request.run, inputs: request.inputs });
|
||||
const hold = held.get(id);
|
||||
if (hold?.body === body) return { value: hold.value as R, fresh: true };
|
||||
if (asked.get(id) !== body) {
|
||||
|
||||
@@ -20,6 +20,8 @@ import {
|
||||
type BermSpan,
|
||||
} from "../../B06_Section/B06_Section_UI_Page_Persist";
|
||||
import { pipeIdAt, placePipeIds, prunePipeIds, withPipeIds } from "./design_pipe_ids";
|
||||
import { calcOnServerNow } from "./design_calc_sites";
|
||||
import { bermRequest, calcChecked, calcOnServerOnly } from "./design_calc_server";
|
||||
import { plannedIrregular } from "./design_structure_status";
|
||||
import { planStations } from "../../common_util/common_util_structure_station";
|
||||
import { followPipeReports } from "../../B05_Profile/logic/B05_Profile_Pipeline_Follow";
|
||||
@@ -155,8 +157,21 @@ export function createFacilityLane(
|
||||
function syncBerm(): void {
|
||||
const routeId = pageRouteId(projectId);
|
||||
if (!structuresKnown || routeId === null) return;
|
||||
// 계산 자리 quantity = server 면 잠잠 뒤 서버 값만(511-A) · 아니면 브라우저 셈
|
||||
if (calcOnServerNow("quantity")) {
|
||||
void calcOnServerOnly<BermSpan[]>("quantity", "C4.copy", bermRequest(structures)).then(
|
||||
(next) => next && putBerm(routeId, next),
|
||||
);
|
||||
return;
|
||||
}
|
||||
putBerm(
|
||||
routeId,
|
||||
calcChecked("quantity", "cross.berm_spans", bermSpansFromStructures, [structures]),
|
||||
);
|
||||
}
|
||||
|
||||
function putBerm(routeId: number, next: BermSpan[]): void {
|
||||
const copy = readState<BermSpan[]>("berm", projectId, routeId);
|
||||
const next = bermSpansFromStructures(structures);
|
||||
if (Array.isArray(copy) && JSON.stringify(copy) === JSON.stringify(next)) return;
|
||||
writeBermSpans(projectId, routeId, next);
|
||||
void Promise.all([import("./design_pipeline_cross"), import("./design_pipeline_page")])
|
||||
|
||||
@@ -59,6 +59,7 @@ import {
|
||||
type CrossRefreshInput,
|
||||
} from "../../B06_Section/B06_Section_Cross_Refresh";
|
||||
import { bermSpansFromStructures, bermSpecAt } from "../../B06_Section/B06_Section_UI_Page_Persist";
|
||||
import { areasRequest, bermRequest, calcChecked } from "./design_calc_server";
|
||||
import { registeredKeys, stationIntervalOf } from "../../B06_Section/B06_Section_Station_Keys";
|
||||
import { detailCross } from "../../B05_Profile/logic/B05_Profile_Pipeline_Detail";
|
||||
import type { CrossOutput, PipelineStation } from "./logic/design_pipeline_types";
|
||||
@@ -254,7 +255,11 @@ function startPagePipeline(shared: DesignPageShared, profile: DesignProfile): vo
|
||||
onUnstable: () => showToast(L("Design_Pipeline_Unstable"), "warning"),
|
||||
// 초안 소단 = 지금 구조물(따라간 자리)의 소단 구간(365-5 · 저장 노선은 다시 셈 창구가 세션 사본으로)
|
||||
bermAt: (structures) => {
|
||||
const spans = bermSpansFromStructures(Array.isArray(structures) ? structures : []);
|
||||
const list = Array.isArray(structures) ? structures : [];
|
||||
// 계산 자리 quantity = server 면 잠잠 뒤 서버 대조만(초안 미리보기 · 511-A)
|
||||
const spans = calcChecked("quantity", "cross.berm_spans", bermSpansFromStructures, [list], {
|
||||
request: bermRequest(list),
|
||||
});
|
||||
return (chainageM) => bermSpecAt(spans, chainageM);
|
||||
},
|
||||
// 저장 노선 횡단 = 다시 셈 창구 한 벌(365-4) — 카드 · 유토곡선 · [저장] 패치가 보는 상세 설계를 늘 고치고 그
|
||||
@@ -277,7 +282,12 @@ function startPagePipeline(shared: DesignPageShared, profile: DesignProfile): vo
|
||||
const routeId = routeIdOf(shared);
|
||||
if (routeId !== null) withDraftAdjusts(detail, projectId, routeId);
|
||||
const rows = detail.cross_sections;
|
||||
applyStructureAreaRows(rows, structureAreaRows(rows));
|
||||
// 계산 자리 quantity = server 면 잠잠 뒤 서버 값 대조 · 다르면 얹고 다시 그림(511-A)
|
||||
const areas = calcChecked("quantity", "structure.layouts", structureAreaRows, [rows], {
|
||||
request: areasRequest(rows),
|
||||
apply: (server) => (applyStructureAreaRows(rows, server), profile.redraw()),
|
||||
});
|
||||
applyStructureAreaRows(rows, areas);
|
||||
},
|
||||
// 초안 시작 — 초안 전 상태를 떠 둠 · 버리면 그대로(측점이 달라도 · 349-0o) · 상세가 없으면 묶음 측점으로 갈아끼움
|
||||
holdDraft: (routeId, saved, seed) => {
|
||||
|
||||
Reference in New Issue
Block a user