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:
85
CLAUDE.md
85
CLAUDE.md
@@ -164,6 +164,91 @@ recordingtest/
|
||||
- 부하·성능 테스트
|
||||
- 단위 테스트 프레임워크 대체
|
||||
|
||||
## 8.1 코드 계층 분리 (의무 규칙)
|
||||
|
||||
본 도구는 **EG-BIM Modeler 외에도 다양한 WPF 응용**을 회귀 자동화 대상으로 삼는다. 또한 사용자 WPF 응용군의 **대다수가 자체 3D 엔진 `HmEG`를 공유**한다. 따라서 코드는 다음 **3개 계층** 중 하나에 명시적으로 속한다.
|
||||
|
||||
| 계층 | 의미 | 의존 가능 | 폴더/네이밍 |
|
||||
|---|---|---|---|
|
||||
| **Generic** | 임의 WPF 데스크톱 응용에 동작 | .NET BCL, FlaUI/UIA3, Win32, YamlDotNet 등 SUT-중립 라이브러리만 | `src/` 직속, 네임스페이스 `Recordingtest.*` |
|
||||
| **HmEG-aware** | HmEG 엔진을 호스팅하는 임의 WPF 응용에 동작 (앱은 고정 안 됨) | Generic + `HmEG.dll` 만 | `src/Hmeg/` 하위, 네임스페이스 `Recordingtest.Hmeg.*` |
|
||||
| **App-specific** | 특정 응용(예: EG-BIM Modeler)에만 동작 | Generic + HmEG-aware + 해당 앱 어셈블리 (`Editor03.PluginInterface.dll` 등) | `src/Sut/<AppName>/` 하위, 네임스페이스 `Recordingtest.Sut.<AppName>.*` |
|
||||
|
||||
### 의존 방향 (단방향)
|
||||
|
||||
```
|
||||
App-specific ──→ HmEG-aware ──→ Generic
|
||||
(e.g. EgBim) (e.g. HmegBridge) (Recorder/Player/...)
|
||||
```
|
||||
|
||||
역참조 금지: Generic은 HmEG-aware/App-specific을 모름. HmEG-aware는 App-specific을 모름.
|
||||
|
||||
### 강제 사항
|
||||
|
||||
1. **Generic 코드는 어떤 SUT 어셈블리도 참조하지 않는다.** `HmEG.dll`도 안 됨.
|
||||
2. **HmEG-aware 코드는 `HmEG.dll`만 참조**한다. 특정 앱(`Editor03.PluginInterface.dll` 등)은 참조 금지.
|
||||
3. **App-specific 코드만이 자기 앱의 어셈블리를 참조**한다.
|
||||
4. **확장 지점은 항상 한 계층 아래에 인터페이스로 둔다.** 예:
|
||||
- `IEngineStateProvider` (Generic) ← `HmegEngineStateProvider` (HmEG-aware) ← `EgBimAppManagerAdapter` (App-specific 진입점만 제공)
|
||||
- `ITargetResolver` (Generic) ← `HmegSceneNodeTargetResolver` (HmEG-aware) ← ...
|
||||
5. **시나리오 포맷, 베이스라인 포맷, 정규화 규칙은 Generic.** HmEG/앱 특화 정규화 규칙이 필요하면 그 계층에서 plugin 식으로 등록한다.
|
||||
6. **새 SUT를 추가할 때 HmEG-aware 코드는 재사용**한다. 새로 작성하지 말 것. 앱마다 다른 부분만 App-specific에 둔다 (보통: 플러그인 호스트 진입점 + 명령 lifecycle 이벤트 어댑터).
|
||||
|
||||
### 폴더 레이아웃 (목표 — 본 규칙 추가 후 점진 마이그레이션)
|
||||
|
||||
```
|
||||
src/
|
||||
├── Recordingtest.Recorder/ # Generic
|
||||
├── Recordingtest.Player/ # Generic
|
||||
├── Recordingtest.Normalizer/ # Generic
|
||||
├── Recordingtest.DiffReporter/ # Generic
|
||||
├── Recordingtest.Runner/ # Generic
|
||||
├── Recordingtest.SutProber/ # Generic
|
||||
├── Recordingtest.Bridge.Abstractions/ # Generic — IEngineStateProvider, ITargetResolver, ...
|
||||
├── Recordingtest.Bridge.Client/ # Generic — HTTP 클라이언트
|
||||
├── Hmeg/
|
||||
│ ├── Recordingtest.Hmeg.Bridge/ # HmEG-aware — Space/Camera/Selection을 HmEG 공개 API로 읽기
|
||||
│ ├── Recordingtest.Hmeg.TargetResolver/ # HmEG-aware — 씬 노드 hit-test/포커스 식별
|
||||
│ └── Recordingtest.Hmeg.Catalog/ # HmEG-aware — 정적 분석/카탈로그 (현 EngineBridge 일부)
|
||||
└── Sut/
|
||||
└── EgBim/ # EG-BIM Modeler 전용
|
||||
├── Recordingtest.Sut.EgBim.PluginHost/ # MEF entry, EditorPlugin base 상속
|
||||
└── Recordingtest.Sut.EgBim.Adapter/ # AppManager 진입점 / command lifecycle 어댑터
|
||||
tests/
|
||||
├── Recordingtest.*.Tests/ # Generic
|
||||
├── Hmeg/Recordingtest.Hmeg.*.Tests/ # HmEG-aware
|
||||
└── Sut/EgBim/Recordingtest.Sut.EgBim.*.Tests/
|
||||
```
|
||||
|
||||
새 SUT(예: 다른 HmEG 호스트 앱)를 추가할 때:
|
||||
```
|
||||
src/Sut/<NewApp>/
|
||||
Recordingtest.Sut.<NewApp>.PluginHost/
|
||||
Recordingtest.Sut.<NewApp>.Adapter/
|
||||
```
|
||||
이 두 개만 새로 만들면 되고 Generic + HmEG-aware는 그대로 재사용된다.
|
||||
|
||||
### 현재 모듈 분류 (2026-04-09 시점, 마이그레이션 전)
|
||||
|
||||
| 모듈 | 분류 | 비고 |
|
||||
|---|---|---|
|
||||
| `Recordingtest.Recorder` | **Generic** | OK |
|
||||
| `Recordingtest.Player` | **Generic** | OK |
|
||||
| `Recordingtest.Normalizer` | **Generic** | OK |
|
||||
| `Recordingtest.DiffReporter` | **Generic** | OK |
|
||||
| `Recordingtest.Runner` | **Generic** | OK |
|
||||
| `Recordingtest.SutProber` | **Generic** | OK |
|
||||
| `Recordingtest.EgPlugin` | **혼합 — 분할 필요** | 본체는 EG-BIM 전용(MEF entry), reflection accessor는 HmEG 비의존 generic, HmEG state는 HmEG-aware. 3개로 split. |
|
||||
| `Recordingtest.EngineBridge` | **HmEG-aware** | HmEG 카탈로그/CandidateFinder. rename → `Recordingtest.Hmeg.Catalog` |
|
||||
| `Recordingtest.EngineBridge.Client` | **혼합 — 분할 필요** | HTTP 호출부 → Generic, `HmEgHttpSnapshot` → HmEG-aware |
|
||||
| `IEngineStateProvider` (현 EgPlugin 안) | → **Generic** | `Recordingtest.Bridge.Abstractions` 로 추출 |
|
||||
| `ReflectionAppManagerAccessor` (현 EgPlugin 안) | → **App-specific** | EG-BIM Modeler의 `Editor.AppManager.AppManager`를 찾는 코드. EgBim 어댑터로 이동. CI fallback 용도로만 유지. |
|
||||
| 향후 `HmegDirectStateProvider` | **HmEG-aware** | `HmEG.dll` 공개 API 직접 호출. 모든 HmEG 호스트 앱에서 재사용 |
|
||||
|
||||
### Sprint Contract 의무
|
||||
|
||||
새 SUT를 추가하거나 기존 모듈을 generic↔SUT 사이에서 옮기는 모든 작업은 `/contract <name>` 으로 Sprint Contract를 먼저 작성한다. DoD에 "분류 라벨", "의존 그래프 검증", "네임스페이스 규칙" 항목 필수.
|
||||
|
||||
## 10. 결정 로그 위치
|
||||
|
||||
주요 기술 결정과 그 근거는 `docs/history/`와 Claude 메모리(`project_recordingtest_*`)에 분산 저장된다. 새 결정 시 반드시 둘 다 갱신한다.
|
||||
|
||||
Reference in New Issue
Block a user