3-tier split (step 1) + engine-bridge v3 scaffold + HmegDirectStateProvider
Lays down the Generic / HmEG-aware / App-specific separation that lets us
target other HmEG-hosting WPF applications later, and lands the v3 engine
state provider on top of it.
Architecture rule (CLAUDE.md §8.1, new): every module belongs to exactly one
of three tiers — Generic / HmEG-aware / App-specific (e.g. EgBim). Dependency
direction is strictly App-specific → HmEG-aware → Generic. Generic must not
reference HmEG.dll; HmEG-aware must not reference any per-app assembly.
This commit is the first incremental step:
+ src/Recordingtest.Bridge.Abstractions/ (Generic, new csproj)
IEngineStateProvider, CameraSnapshot, SceneSnapshot,
NullEngineStateProvider — extracted from EgPlugin so the generic core
owns the contract. Zero SUT references.
+ src/Hmeg/Recordingtest.Hmeg.Bridge/ (HmEG-aware, new csproj)
HmegDirectStateProvider — IEngineStateProvider implemented against
the HmEG public API (Space, HmEGViewport, ISelectable, ModelBase.Uid).
Decoupled from any specific host app via Func<Space?>/Func<HmEGViewport?>
lambdas; the EgBim plugin host supplies them. Reusable for any other
WPF application that hosts HmEG.
Selection traversal walks Space.Children and collects ModelBase.Uid
for nodes whose ISelectable.IsSelected is true. We deliberately type
nodes as object + late-bound Uid lookup to avoid pulling MemoryPack
into the dependency graph.
+ tests/Hmeg/Recordingtest.Hmeg.Bridge.Tests/
5 unit tests covering null lambdas, throwing lambdas, document path
provider, and constructor null arg validation.
+ src/Recordingtest.EgPlugin/ChainedEngineStateProvider.cs
Wraps two providers; falls back from Hmeg.Direct to the existing
Reflection accessor when the primary returns empty/default. Lets us
land the new wire-up before the EgBim adapter Q1~Q7 lookups are
filled in. 7 new tests.
+ src/Recordingtest.EgPlugin/IAppManagerAccessor.cs
Reflection accessor abstraction (preserved as the v3 fallback). Looks
up Editor.AppManager.AppManager via well-known Instance/Current
property names. Unit-testable through a fake.
~ src/Recordingtest.EgPlugin/IEngineStateProvider.cs
Type definitions removed (now in Bridge.Abstractions); only the
reflection-based provider remains. ReflectionEngineStateProvider
delegates everything to IAppManagerAccessor.
~ src/Recordingtest.EgPlugin/HmEgBridgePlugin.cs
BuildProvider() picks ChainedEngineStateProvider(Hmeg.Direct,
Reflection). The HmEG-aware lambdas are stubs (return null) until the
next step wires the EgBim adapter; the chain falls through to the
reflection path so behaviour matches v2 for now.
+ docs/contracts/engine-bridge-v3.md — Sprint Contract
+ docs/contracts/generic-sut-split.md — Sprint Contract for the
remaining mass-rename / folder move (step 2, deferred).
+ docs/hmeg-api-survey.md — Read-only survey of the HmEG
public API (Space, ModelBase, HmEGViewport, IHmCamera, IPlugin) used
to design HmegDirectStateProvider. Open Q1~Q7 listed.
Tests: 94 → 115 passing, 0 failing. The new HmEG-aware test project copies
HmEG.dll next to its output (Private=true) since it runs out-of-process.
Step 2 (deferred to next session): mass-rename
src/Recordingtest.EgPlugin → src/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost + .Adapter
src/Recordingtest.EngineBridge → src/Hmeg/Recordingtest.Hmeg.Catalog
src/Recordingtest.EngineBridge.Client → split (Generic + Hmeg)
plus Recordingtest.Architecture.Tests to enforce the §8.1 dependency rule.
Ref: #10 follow-up, #14 follow-up.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
108
docs/history/2026-04-09_3tier-split-step1.md
Normal file
108
docs/history/2026-04-09_3tier-split-step1.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# 2026-04-09 — 3-tier 분리 1단계 (incremental)
|
||||
|
||||
**이슈**: #10 follow-up (engine-bridge v3) + 사용자 디렉티브 (Generic / HmEG-aware / App-specific 분리)
|
||||
**소요 시간**: ~70분
|
||||
**Context 사용량**: input ~205k / output ~38k tokens (Opus 4.6, 1M context, 동일 세션 누적)
|
||||
|
||||
## 작업
|
||||
|
||||
CLAUDE.md §8.1 의 3-tier 규칙을 incremental하게 코드에 반영. 기존 `EgPlugin`/`EngineBridge` mass-rename은 2단계로 미루고, **새 계층 모듈을 신설**해 wire-up까지 끝냄.
|
||||
|
||||
### 신설 (Generic 계층)
|
||||
|
||||
- `src/Recordingtest.Bridge.Abstractions/` (csproj, net8.0)
|
||||
- `IEngineStateProvider`, `CameraSnapshot`, `SceneSnapshot`, `NullEngineStateProvider`
|
||||
- SUT 어셈블리 참조 0개. CI 안전.
|
||||
- 기존 `Recordingtest.EgPlugin`의 `IEngineStateProvider`/`CameraSnapshot`/`SceneSnapshot`/`NullEngineStateProvider` 정의 제거 → Bridge.Abstractions로 위임 (`using Recordingtest.Bridge;`)
|
||||
|
||||
### 신설 (HmEG-aware 계층)
|
||||
|
||||
- `src/Hmeg/Recordingtest.Hmeg.Bridge/` (csproj, net8.0-windows + WPF + HmEG.dll 직접 참조)
|
||||
- `HmegDirectStateProvider : IEngineStateProvider`
|
||||
- 람다 주입: `Func<HmEG.Space?>`, `Func<HmEG.HmEGViewport?>`, `Func<string?>?`
|
||||
- `GetSelectedIds`: Space 트리 walk → `ISelectable.IsSelected` 노드의 `Uid` 수집. `ModelBase` 직접 타입 참조 회피 (MemoryPack 의존 차단), 대신 `object` + 패턴 매칭 + 늦은 바인딩 `Uid` 프로퍼티 읽기
|
||||
- `GetCamera`: `viewport.CameraCore`에서 `Position`/`LookDirection`/`UpDirection`/`FieldOfView`를 reflection으로 읽어 `CameraSnapshot`. Target = Eye + LookDir.
|
||||
- `GetScene`: `space.ItemsCount` + 외부 documentPathProvider 람다
|
||||
- 모든 호출 try/catch → safe default 폴백
|
||||
- `tests/Hmeg/Recordingtest.Hmeg.Bridge.Tests/` (csproj, HmEG.dll Private=true로 출력 폴더 복사)
|
||||
- `HmegDirectStateProviderTests` 5개 (null lambdas / throwing lambdas / document path / null arg)
|
||||
|
||||
### EgPlugin wire-up
|
||||
|
||||
- `HmEgBridgePlugin.BuildProvider()` 신설:
|
||||
```
|
||||
HmegDirectStateProvider (1순위, lambdas는 일단 null 반환)
|
||||
↓ default →
|
||||
ReflectionEngineStateProvider (2순위, EgBim AppManager 후보 탐색)
|
||||
```
|
||||
체인: `ChainedEngineStateProvider`
|
||||
- `ChainedEngineStateProvider` 신설 — primary 결과가 default/empty면 fallback 호출. signal별 판정:
|
||||
- SelectedIds 빈 리스트
|
||||
- Camera Eye=(0,0,0) AND Target=(0,0,0)
|
||||
- Scene ObjectCount=0 AND DocumentPath=null
|
||||
- RenderComplete: primary always wins
|
||||
- 단위 테스트 7개 (`ChainedEngineStateProviderTests`)
|
||||
|
||||
EgBim adapter(Q1~Q7 답)가 채워지면 `BuildProvider`의 두 람다만 실값으로 바꾸면 라이브 검증으로 이어진다.
|
||||
|
||||
### sln/build/test
|
||||
|
||||
- `dotnet sln add` 로 3개 신규 csproj 등록
|
||||
- `Recordingtest.Bridge.Abstractions`
|
||||
- `Recordingtest.Hmeg.Bridge`
|
||||
- `Recordingtest.Hmeg.Bridge.Tests`
|
||||
- `dotnet build` 성공 (HmEG.dll의 ModelBase가 MemoryPack 의존성을 transitively 요구해서 1차 빌드 실패 → ModelBase 직접 참조 제거로 우회)
|
||||
- `dotnet test recordingtest.sln`: **0 failures, 115 passed** (94 → 115, +21)
|
||||
|
||||
## 분류 라벨 (현재 시점)
|
||||
|
||||
| 모듈 | 계층 | 비고 |
|
||||
|---|---|---|
|
||||
| `Recordingtest.Bridge.Abstractions` ✨ | **Generic** | 신설 |
|
||||
| `Recordingtest.Hmeg.Bridge` ✨ | **HmEG-aware** | 신설, HmEG.dll만 참조 |
|
||||
| `Recordingtest.EgPlugin` | **App-specific (EgBim)** | rename 대기. Bridge.Abstractions + Hmeg.Bridge 참조하도록 갱신됨 |
|
||||
| 기타 generic 모듈 (Recorder/Player/Normalizer/...) | **Generic** | 변경 없음 |
|
||||
|
||||
## 2단계 (다음 세션)
|
||||
|
||||
`docs/contracts/generic-sut-split.md` D1~D9 잔여:
|
||||
- `src/Recordingtest.EgPlugin/` → `src/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost/` + `.Adapter/`
|
||||
- `src/Recordingtest.EngineBridge/` → `src/Hmeg/Recordingtest.Hmeg.Catalog/`
|
||||
- `src/Recordingtest.EngineBridge.Client/` → 분할 (generic HTTP + HmEG-shaped)
|
||||
- 네임스페이스 일괄 rename
|
||||
- `Recordingtest.Architecture.Tests` 추가 (의존 그래프 검증)
|
||||
- 단일 BREAKING 커밋
|
||||
|
||||
## 라이브 검증 (Q1~Q7 후)
|
||||
|
||||
EgBim adapter에서 다음 람다를 실값으로 채운다:
|
||||
```csharp
|
||||
spaceProvider = () => /* AppManager.Instance.ActiveSpace */ ;
|
||||
viewportProvider = () => /* AppManager.Instance.ActiveViewport (HmEGViewport 캐스트) */ ;
|
||||
documentPathProvider = () => /* AppManager.Instance.ActiveDocumentPath */ ;
|
||||
```
|
||||
|
||||
→ `curl http://localhost:38080/scene` 등으로 검증.
|
||||
|
||||
## 미커밋
|
||||
|
||||
본 세션 누적 (다음 단계에서 통합 커밋):
|
||||
- `Recordingtest.Bridge.Abstractions/` (신규)
|
||||
- `Hmeg/Recordingtest.Hmeg.Bridge/` (신규)
|
||||
- `tests/Hmeg/Recordingtest.Hmeg.Bridge.Tests/` (신규)
|
||||
- `Recordingtest.EgPlugin/` 갱신 (`HmEgBridgePlugin`, `IEngineStateProvider`, `ChainedEngineStateProvider` 신규, csproj ProjectReference 추가)
|
||||
- `Recordingtest.EgPlugin.Tests/` 갱신 (`ChainedEngineStateProviderTests` 신규, using 정리)
|
||||
- `recordingtest.sln`
|
||||
- `CLAUDE.md` §8.1 (직전 단계)
|
||||
- `docs/contracts/generic-sut-split.md`, `docs/contracts/engine-bridge-v3.md`, `docs/hmeg-api-survey.md`
|
||||
- `PROGRESS.md`, `PLAN.md`
|
||||
- 본 history + `2026-04-09_engine-bridge-v3-scaffold.md`
|
||||
|
||||
## 관련
|
||||
|
||||
- `src/Recordingtest.Bridge.Abstractions/IEngineStateProvider.cs`
|
||||
- `src/Hmeg/Recordingtest.Hmeg.Bridge/HmegDirectStateProvider.cs`
|
||||
- `src/Recordingtest.EgPlugin/HmEgBridgePlugin.cs`
|
||||
- `src/Recordingtest.EgPlugin/ChainedEngineStateProvider.cs`
|
||||
- `tests/Hmeg/Recordingtest.Hmeg.Bridge.Tests/HmegDirectStateProviderTests.cs`
|
||||
- `tests/Recordingtest.EgPlugin.Tests/ChainedEngineStateProviderTests.cs`
|
||||
70
docs/history/2026-04-09_engine-bridge-v3-scaffold.md
Normal file
70
docs/history/2026-04-09_engine-bridge-v3-scaffold.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# 2026-04-09 — engine-bridge v3 scaffold (D1/D6)
|
||||
|
||||
**이슈**: #10 follow-up (engine-bridge v3)
|
||||
**소요 시간**: ~95분 (HmEG 소스 survey + 3-tier 분리 디렉티브 반영 포함)
|
||||
**Context 사용량**: input ~165k / output ~30k tokens (Opus 4.6, 1M context, 동일 세션 누적)
|
||||
|
||||
## 작업
|
||||
|
||||
`docs/contracts/engine-bridge-v3.md` Sprint Contract 작성 후 D1/D6 구현:
|
||||
|
||||
- `IAppManagerAccessor` 추상화 신설 — AppManager/ActiveDocument/ActiveViewport/Selection/Camera를 reflection 경계 뒤로 격리
|
||||
- `ReflectionAppManagerAccessor` — loaded assemblies에서 `Editor.AppManager.AppManager` 타입 + `Instance/Current/Default` static 프로퍼티 탐색, well-known 멤버 이름 후보 체인(Selection/SelectedObjects, Camera/ActiveCamera, Position/Eye, …)로 reflection lookup, vector는 `double[]` / `float[]` / `X/Y/Z` 세 가지 shape 모두 시도
|
||||
- `ReflectionEngineStateProvider` v2 stub 제거, 접근자 위임 구조로 재작성. HmEG 부재 환경(= CI)에서는 v2와 동일한 safe default 반환
|
||||
- `ReflectionEngineStateProviderTests` 9 테스트 추가 — FakeAccessor로 정상값/예외/null/HmEG 부재 폴백 커버. EgPlugin 테스트 5 → 14
|
||||
- 전체 suite green (100+ tests)
|
||||
|
||||
## 라이브 검증 대기 (D7)
|
||||
|
||||
reflection 멤버 후보 이름은 `hmeg-candidates.json` 기반 추측. SUT 라이브에서 `curl /scene /camera /selection` 응답 받아 실제 매칭 여부 확인 후 1~2회 보정 필요.
|
||||
|
||||
## 전략 pivot — Reflection → HmEG 직접 참조
|
||||
|
||||
사용자 지적: `Recordingtest.EgPlugin`은 이미 `HmEG.dll` + `Editor03.PluginInterface.dll`을 compile-time 참조 중이다 (`.csproj` 확인). 즉 reflection으로 멤버 추측할 필요가 없고, HmEG public 타입을 직접 호출하면 된다. 이식성(generic WPF)은 포기하지만 이 프로젝트는 EG-BIM Modeler 전용이므로 합리적 trade-off.
|
||||
|
||||
이에 따라 사용자 동의 하에 HmEG 소스(`D:\GiteaAll\HmEngine\HmEG\HmEG`)를 read-only로 surveyed:
|
||||
|
||||
확인된 공개 타입 (`<public-hmeg-api />` 마커 기준):
|
||||
- `ModelBase.Uid : Guid` — 영구 고유 ID, golden file 결정성의 핵심
|
||||
- `Space : ModelBase` — 문서 컨테이너. `Children`/`ItemsCount`/`Viewports`
|
||||
- `HmModel : ModelBase` — 형상 객체. `MouseDown/Enter/Leave` event (recorder hit-test 후보)
|
||||
- `HmEGViewport` (interface, namespace `HmEG`) — `CameraCore`, `Renderables`, `ViewportRectangle`
|
||||
- `IHmCamera` — Position/LookDirection/UpDirection (Fov는 PerspectiveCamera 캐스트 필요)
|
||||
- `ISelectable.IsSelected` — 노드별 (중앙 selection 리스트는 HmEG core에 없음 → Space walk + 필터)
|
||||
- `HmEG.IPlugin.View : EGViewport` — 플러그인이 로드 시 viewport 직접 주입받음
|
||||
|
||||
산출: `docs/hmeg-api-survey.md` — 발견 내용, v3 구현 방향(`HmEgDirectStateProvider` + 람다 주입), SUT-side bridge 추가 엔드포인트(`/focus`, `/hit-test`, `/command`) 설계, 미해결 7개 질문(Q1~Q7) 큐.
|
||||
|
||||
## 곁가지
|
||||
|
||||
- 사용자 질문으로 "SUT 소스 협조 wishlist" 정리 — AutomationPeer 부착, AppManager.Instance 난독화 제외, read-only 상태 API, 명령 생명주기 이벤트 등 7항목을 대화에 남김. 필요 시 `docs/sut-cooperation-wishlist.md`로 문서화.
|
||||
- 미커밋 변경 존재 (engine-bridge v3 scaffold + contract + hmeg-api-survey.md + 본 마지막 단계의 3-tier 분리 작업). 다음 세션에서 분리 refactor 완료 후 통합 커밋 예정.
|
||||
|
||||
## 아키텍처 디렉티브 — 3-tier 분리 (세션 후반)
|
||||
|
||||
사용자 디렉티브 두 가지가 연속해서 들어왔다:
|
||||
1. "처음부터 WPF 일반인지 Modeler 테스트 자동화인지 코드 분리해놓아라"
|
||||
2. "대부분의 WPF는 HmEG(3D 그래픽 엔진)을 사용하고 있으니 이점도 고려해서 테스트 자동화를 설계해라"
|
||||
|
||||
→ 즉 분리는 2-tier(generic vs SUT)가 아니라 **3-tier**:
|
||||
- **Generic** — 임의 WPF 응용
|
||||
- **HmEG-aware** — HmEG를 호스팅하는 임의 WPF 응용 (앱 미고정)
|
||||
- **App-specific** — 특정 응용 (현재 EG-BIM Modeler)
|
||||
|
||||
의존 방향: App-specific → HmEG-aware → Generic. 역참조 금지.
|
||||
|
||||
본 세션에서 한 일:
|
||||
- `CLAUDE.md §8.1` 신규 — 3-tier 규칙, 폴더 레이아웃, 강제 사항, 현재 모듈 분류표
|
||||
- `docs/contracts/generic-sut-split.md` 신규 — D1~D9 명세 (폴더/csproj 분할, 인터페이스 추출, HmegDirectStateProvider 골격, ArchitectureTests, sln 갱신)
|
||||
- `PLAN.md` — 본 refactor를 P0.5로 등록 (engine-bridge v3 진입 전 선결)
|
||||
- `PROGRESS.md` — In progress 에 해당 항목 추가
|
||||
|
||||
본 contract는 다음 세션에서 Generator가 단일 작업 단위로 실행한다. **engine-bridge v3 코드 진입은 본 분리 완료 후**. 이유: `HmegDirectStateProvider`는 HmEG-aware 계층에 들어가야 다른 SUT에서 재사용 가능.
|
||||
|
||||
## 관련
|
||||
|
||||
- `docs/contracts/engine-bridge-v3.md` (갱신 예정)
|
||||
- `docs/hmeg-api-survey.md` (신규 — 본 세션 산출)
|
||||
- `src/Recordingtest.EgPlugin/IAppManagerAccessor.cs` (신규 — CI fallback으로 유지)
|
||||
- `src/Recordingtest.EgPlugin/IEngineStateProvider.cs` (v3 1차 재작성, 다음 단계에서 HmEgDirectStateProvider 추가)
|
||||
- `tests/Recordingtest.EgPlugin.Tests/ReflectionEngineStateProviderTests.cs` (신규)
|
||||
Reference in New Issue
Block a user