From f4a7b183b0a9b4dafdcbc8170f744c2bce3e9e14 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 29 Oct 2026 15:05:16 +0900 Subject: [PATCH] =?UTF-8?q?fix(=ED=8F=89=EB=A9=B4):=20=EC=83=88=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=EB=AC=BC=20=EC=B8=A1=EC=A0=90=20=EB=86=93?= =?UTF-8?q?=EB=8A=94=20=EC=A6=89=EC=8B=9C=20=ED=8F=89=EB=A9=B4=20=EB=B9=84?= =?UTF-8?q?=ED=83=88=20=EB=81=9D=20=C2=B7=20=EB=A9=B4=EC=A0=81=20=3D=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EB=92=A4=20=E2=80=94=20=ED=8A=90=20?= =?UTF-8?q?=EC=97=86=EC=9D=8C=20=C2=B7=20=EC=98=AE=EA=B9=80=20=EA=B3=84?= =?UTF-8?q?=ED=9A=8D=EA=B3=A0=20(PLAN=20583-1=20=C2=B7=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=20441)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 원인: 구조물을 새 측점에 놓으면 측점 갈아끼움(비동기)이 끝나기 전 묶음 cross 단계가 옛 상세를 봐 엔진 길로 빠짐 — 구조물 얹기 없는 한 차례(벽 측점 비탈 끝 35 · 55 m · 기존 벽 측점 18.96 m)가 약 80 ms 평면에 튐 · 다음 차례(0.25 s)에 저장 값 - design_shared savedCross = 도는 갈아끼움(swapInFlight)을 기다려 그 상세로 다시 셈 · 상세 가게 swapInFlight 새로 - 종단도 listed() 옛 측점 = 저장 정본 상세 — 갈아끼운 상세로 셈해 옮긴 구조물 옛 측점 꼭짓점이 안 옮겨져 계획고 최대 2.4 cm 갈리던 것(옮김 면적 차 0.17 → 0.0175 ㎡) - 시험: test_272_swap_keeps_canon 에 도는 갈아끼움 기다림 · test_473_lane_ready 글 대조를 새 호출 꼴로 Co-Authored-By: Claude Opus 5.5 --- A00_Common/design/design_profile.ts | 10 +++++-- A00_Common/design/design_shared.ts | 11 +++++--- B06_Section/B06_Section_Section_Store.ts | 26 ++++++++++++++++++- resources/tester/helper_272_swap_canon.mjs | 11 +++++++- .../tester/helper_272_swap_canon_entry.ts | 1 + resources/tester/test_272_swap_keeps_canon.py | 5 ++++ resources/tester/test_473_lane_ready.py | 4 ++- 7 files changed, 60 insertions(+), 8 deletions(-) diff --git a/A00_Common/design/design_profile.ts b/A00_Common/design/design_profile.ts index 5f9804938..fbb2968d9 100644 --- a/A00_Common/design/design_profile.ts +++ b/A00_Common/design/design_profile.ts @@ -24,6 +24,7 @@ import { } from "../../B05_Profile/logic/B05_Profile_UI_Profile_Data"; import { showToast } from "@ui/ui_template_elements"; import { hasStaleDesigns } from "../../B06_Section/B06_Section_UI_Section_Common"; +import { savedSectionDetail } from "../../B06_Section/B06_Section_Section_Canon"; import { blocksMinCover, findMinCoverViolations, @@ -295,9 +296,14 @@ export function createDesignProfile(ctx: DesignProfileContext) { return true; } - /** 계획선 측점 줄 = 측점 한 벌 — 셈은 `design_profile_base` 몫(계산 묶음 planline 도 같은 기준). */ + /** 계획선 측점 줄 = 측점 한 벌 — 셈은 `design_profile_base` 몫(계산 묶음 planline 도 같은 기준). 옛 측점 = + * 저장 정본 상세 — 갈아끼운 상세(새 측점 목록)면 옮긴 구조물 옛 측점 꼭짓점이 안 옮겨져 계획고가 갈림(583). */ const listed = (): AlignmentBase | null => - listedBase(storedBase, detail, planted ? irregularStations : null); + listedBase( + storedBase, + (routeId !== null && savedSectionDetail(projectId, routeId)) || detail, + planted ? irregularStations : null, + ); /** 계획고 [저장] 조각(472-6) — 저장 뒤 서버 선형으로 기준을 갈아 다시 그림. */ const alignmentSave = createProfileSave({ diff --git a/A00_Common/design/design_shared.ts b/A00_Common/design/design_shared.ts index f8a5598e0..ea4b50815 100644 --- a/A00_Common/design/design_shared.ts +++ b/A00_Common/design/design_shared.ts @@ -53,6 +53,7 @@ import { holdSectionDetail, invalidateSectionDetail, restoreSectionDetail, + swapInFlight, swapSectionStations, } from "../../B06_Section/B06_Section_Section_Store"; import { @@ -256,15 +257,19 @@ function startPagePipeline(shared: DesignPageShared, profile: DesignProfile): vo onUnstable: () => showToast(L("Design_Pipeline_Unstable"), "warning"), // 저장 노선 횡단 = 다시 셈 창구 한 벌(365-4) — 카드 · 유토곡선 · [저장] 패치가 보는 상세 설계를 늘 고치고 그 // 값을 묶음 출력으로(발자국 · 평면 횡단 판) · 측점이 상세와 다르면(갈아끼움 전) 묶음 엔진(439) - savedCross: (detail, routeId, stations) => - refreshSavedCross( + // 새 측점(관 · 구조물 놓음) = 도는 측점 갈아끼움을 기다려 그 상세로 — 안 기다리면 엔진 길로 빠져 구조물 + // 얹기(평면 비탈 끝 · 폐회로 면적) 없는 한 차례가 화면에 튐(583) + savedCross: async (saved, routeId, stations) => { + const detail = (await swapInFlight(projectId, routeId))?.detail ?? saved; + return refreshSavedCross( { projectId, routeId, detail, edits: profile.pipelineSource().edits }, stations, (updated) => { if (updated.length) crossDesignsUpdated(shared, updated); profile.redraw(); }, - ), + ); + }, plantedBase, // 저장 노선 종단 기준 = 입력이 심는 측점(386-3 · 보기 심기 무관) // 저장 상세 측점마다 등록 열쇠 — 초안 자리로 뜬 시설 측점의 입력 짝(주인 id · 457) stationKeys: (routeId, detail) => diff --git a/B06_Section/B06_Section_Section_Store.ts b/B06_Section/B06_Section_Section_Store.ts index 3f3188268..9132b448e 100644 --- a/B06_Section/B06_Section_Section_Store.ts +++ b/B06_Section/B06_Section_Section_Store.ts @@ -258,13 +258,37 @@ export async function swapSectionStations( projectId: string, routeId: number, stations: ReadonlyArray, -): Promise<{ detail: SectionDetailResponse; moves: Map } | null> { +): Promise { const key = keyOf(projectId, routeId); const current = cache.get(key); if (!current || !stations.length) return null; const interval = stationIntervalOf(current); const oldKeys = registeredKeys(projectId, routeId, current.cross_sections, interval); if (sameStationList(current.cross_sections, oldKeys, stations)) return null; + const run = swapDetail(projectId, routeId, current, stations, oldKeys, interval); + swapping.set(key, run); + const clear = () => void (swapping.get(key) === run && swapping.delete(key)); + run.then(clear, clear); + return run; +} + +type SwapResult = { detail: SectionDetailResponse; moves: Map } | null; +/** 도는 갈아끼움(노선마다) — 묶음 cross 단계가 갈아끼운 상세로 셈하게 기다림(583). */ +const swapping = new Map>(); + +/** 이 노선에 도는 측점 갈아끼움 — 없으면 null(583). */ +export const swapInFlight = (projectId: string, routeId: number): Promise | null => + swapping.get(keyOf(projectId, routeId)) ?? null; + +async function swapDetail( + projectId: string, + routeId: number, + current: SectionDetailResponse, + stations: ReadonlyArray, + oldKeys: ReturnType, + interval: number, +): Promise { + const key = keyOf(projectId, routeId); // 계산 자리 fixed_browser = server 면 잠잠 뒤 서버 대조만(511-A) const swap = calcChecked("fixed_browser", "stations.swap", swapCrossSections, [ current, diff --git a/resources/tester/helper_272_swap_canon.mjs b/resources/tester/helper_272_swap_canon.mjs index 9b282925c..df7251cd0 100644 --- a/resources/tester/helper_272_swap_canon.mjs +++ b/resources/tester/helper_272_swap_canon.mjs @@ -246,7 +246,16 @@ const keyList = () => KEYS.map((key, i) => ({ key, chainageM: LIST[i][1] })); await m.loadSectionDetail(P, R); m.useStationKeyList(P, R, keyList()); pipeAt(P, 55); - const moved = await m.swapSectionStations(P, R, stationsAt(55)); + // 583 — 도는 갈아끼움 = swapInFlight(cross 단계가 기다림) · 같은 목록 다시 부름은 null · 끝나면 비움 + const run = m.swapSectionStations(P, R, stationsAt(55)); + const flight = m.swapInFlight(P, R); + const again = await m.swapSectionStations(P, R, stationsAt(55)); + const moved = await run; + out.inFlight = { + same: flight !== null && (await flight) === moved, + again: again === null, + cleared: m.swapInFlight(P, R) === null, + }; pipeAt(P, 45); const back = await m.swapSectionStations(P, R, stationsAt(45)); out.storeNoDraft = { diff --git a/resources/tester/helper_272_swap_canon_entry.ts b/resources/tester/helper_272_swap_canon_entry.ts index b9cd58c81..d30e7d65f 100644 --- a/resources/tester/helper_272_swap_canon_entry.ts +++ b/resources/tester/helper_272_swap_canon_entry.ts @@ -5,6 +5,7 @@ export { } from "../../B06_Section/logic/B06_Section_Station_Swap"; export { loadSectionDetail, + swapInFlight, swapSectionStations, } from "../../B06_Section/B06_Section_Section_Store"; export { useStationKeyList } from "../../B06_Section/B06_Section_Station_Keys"; diff --git a/resources/tester/test_272_swap_keeps_canon.py b/resources/tester/test_272_swap_keeps_canon.py index 8e06f1e81..4a9998812 100644 --- a/resources/tester/test_272_swap_keeps_canon.py +++ b/resources/tester/test_272_swap_keeps_canon.py @@ -9,6 +9,7 @@ - 노선 초안 차례 = 옛 규칙(축 허용 1e-4 · 넘으면 브라우저 표본). - 가게 길(`swapSectionStations`) = 가게 `routeDraft` 로 가름 — 없음 · 있음 · 초안 버린 직후(초안 표본이 안 남음). + - 583 — 도는 갈아끼움 = `swapInFlight`(묶음 cross 단계가 기다림) · 끝나면 비움. Node 헬퍼 `helper_272_swap_canon.mjs` 가 vite ssr 로 묶은 진짜 모듈을 부름. """ @@ -142,3 +143,7 @@ def test_관_지움_되돌림_정본_설계(out): assert r["design"] and r["copy"] # 지우기 전 설계(정본 사본) — 빈 기본 설계 아님(472 덧) assert r["place"] and r["bytes"] assert r["bareFresh"] + + +def test_583_도는_갈아끼움_기다림(out): + assert out["inFlight"] == {"same": True, "again": True, "cleared": True} diff --git a/resources/tester/test_473_lane_ready.py b/resources/tester/test_473_lane_ready.py index 419041f3f..97463ba0b 100644 --- a/resources/tester/test_473_lane_ready.py +++ b/resources/tester/test_473_lane_ready.py @@ -59,7 +59,9 @@ def test_심기_전_종단도_기준_저장분(got) -> None: def test_준비_전_빈_목록_안_심는_자리() -> None: profile = (ROOT / "A00_Common/design/design_profile.ts").read_text(encoding="utf-8") - assert "listedBase(storedBase, detail, planted ? irregularStations : null)" in profile + # 옛 측점 = 저장 정본 상세(583) · 심기 전 = null(저장분 그대로) + assert "savedSectionDetail(projectId, routeId)) || detail," in profile + assert "planted ? irregularStations : null," in profile sections = (ROOT / "A00_Common/design/design_view_profile_sections.ts").read_text( encoding="utf-8" )