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:
140
docs/contracts/generic-sut-split.md
Normal file
140
docs/contracts/generic-sut-split.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# Sprint Contract — generic-sut-split (3-tier)
|
||||
|
||||
## Goal
|
||||
|
||||
`CLAUDE.md §8.1` 신규 규칙(**Generic / HmEG-aware / App-specific 3계층 분리**)을 코드베이스에 실제로 반영한다. 향후 EG-BIM Modeler 외에도 HmEG를 호스팅하는 다양한 WPF 응용을 추가할 때 Generic 코어와 HmEG-aware 미들 계층을 재사용하고, 앱마다 다른 부분(플러그인 진입점, 명령 lifecycle 어댑터)만 새로 작성하면 되도록 한다.
|
||||
|
||||
## Background
|
||||
|
||||
지금까지 모든 모듈이 `src/Recordingtest.*` 평면에 있고, `Recordingtest.EgPlugin`/`Recordingtest.EngineBridge`가 "EG-BIM 전용"과 "HmEG 일반"과 "Generic"이 섞인 채로 존재한다. 사용자 디렉티브:
|
||||
1. 처음부터 Generic vs SUT-specific 분리
|
||||
2. **HmEG는 사용자 WPF 앱군의 공통 엔진** — HmEG-aware 미들 계층을 따로 두어 앱 간 재사용
|
||||
|
||||
따라서 분리는 2-tier가 아니라 **3-tier**: Generic → HmEG-aware → App-specific (EgBim).
|
||||
|
||||
## Definition of Done
|
||||
|
||||
### D1. 폴더/csproj 이동·분할
|
||||
|
||||
**Generic 신설**:
|
||||
- `src/Recordingtest.Bridge.Abstractions/` — `IEngineStateProvider`, `CameraSnapshot`, `SceneSnapshot`, 향후 `IFocusProbe`/`IHitTestProbe`/`ICommandLifecycle`. **`HmEG.dll` 참조 금지**.
|
||||
- `src/Recordingtest.Bridge.Client/` — generic HTTP 클라이언트 (`HttpClient` 래퍼, `BridgeClientException`, `IBridgeClient`)
|
||||
|
||||
**HmEG-aware 신설** (`src/Hmeg/` 하위):
|
||||
- `src/Hmeg/Recordingtest.Hmeg.Bridge/` — `HmegDirectStateProvider : IEngineStateProvider` (HmEG `Space`/`HmEGViewport`/`CameraCore` 직접 호출), HTTP 서버는 별도. **`HmEG.dll`만 참조**.
|
||||
- `src/Hmeg/Recordingtest.Hmeg.TargetResolver/` — 씬 노드 hit-test/포커스 식별 (Gap I 우회의 HmEG-aware 레이어)
|
||||
- `src/Hmeg/Recordingtest.Hmeg.Catalog/` — `Recordingtest.EngineBridge`의 정적 분석/CandidateFinder를 이쪽으로 이동
|
||||
- `src/Hmeg/Recordingtest.Hmeg.Bridge.Client/` — `HmEgHttpSnapshot` 등 HmEG-shaped 응답 타입 (현 `Recordingtest.EngineBridge.Client`에서 분리)
|
||||
|
||||
**App-specific (EgBim) 신설** (`src/Sut/EgBim/` 하위):
|
||||
- `src/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost/` — MEF entry. `EditorPlugin` 베이스 상속, `BridgeHttpServer` 부팅. 현재 `Recordingtest.EgPlugin.HmEgBridgePlugin` 이동.
|
||||
- `src/Sut/EgBim/Recordingtest.Sut.EgBim.Adapter/` — EG-BIM Modeler `AppManager` 진입점, command lifecycle 어댑터. 현재 `ReflectionAppManagerAccessor`는 여기로 (CI fallback 용도). EG-BIM 전용 SUT 멤버 이름 후보(예: `Editor.AppManager.AppManager`)는 이 모듈에만 등장.
|
||||
|
||||
**테스트 이동**:
|
||||
- `tests/Recordingtest.Bridge.Abstractions.Tests/` (신규)
|
||||
- `tests/Hmeg/Recordingtest.Hmeg.Bridge.Tests/` (현 EgPlugin 테스트의 HmEG 부분)
|
||||
- `tests/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost.Tests/` (현 EgPlugin 테스트의 EgBim 부분)
|
||||
- `tests/Hmeg/Recordingtest.Hmeg.Catalog.Tests/` (현 EngineBridge.Tests 이동)
|
||||
- `tests/Hmeg/Recordingtest.Hmeg.Catalog.IntegrationTests/` (현 EngineBridge.IntegrationTests 이동)
|
||||
|
||||
**제거**:
|
||||
- 기존 `src/Recordingtest.EgPlugin/`, `src/Recordingtest.EngineBridge/`, `src/Recordingtest.EngineBridge.Client/` 디렉터리 (내용물은 위 3계층으로 분배)
|
||||
|
||||
### D2. 네임스페이스 rename
|
||||
- `Recordingtest.Bridge.*` (Generic)
|
||||
- `Recordingtest.Hmeg.*` (HmEG-aware)
|
||||
- `Recordingtest.Sut.EgBim.*` (App-specific)
|
||||
- 모든 `using Recordingtest.EgPlugin;` 정리
|
||||
- `recordingtest.sln` 프로젝트 경로/이름 갱신
|
||||
|
||||
### D3. 인터페이스 추출
|
||||
- `IEngineStateProvider`, `CameraSnapshot`, `SceneSnapshot` → `Recordingtest.Bridge.Abstractions` (Generic)
|
||||
- 현재 `Recordingtest.EgPlugin` 내 정의 제거, 새 위치를 참조
|
||||
|
||||
### D4. HmegDirectStateProvider 골격 신설
|
||||
- `src/Hmeg/Recordingtest.Hmeg.Bridge/HmegDirectStateProvider.cs` 작성 (구현은 람다 주입 형태, `docs/hmeg-api-survey.md` §"v3 구현 방향" 참고)
|
||||
- 단위 테스트 — fake viewport/space 람다로 D2/D3/D4(원래 v3 contract)에 해당하는 동작 검증
|
||||
- **본 contract에서는 골격만**. 실제 HmEG 라이브 검증은 `engine-bridge-v3` 후속 contract.
|
||||
|
||||
### D5. EgBim PluginHost 보존
|
||||
- 현 `HmEgBridgePlugin`의 동작(MEF 발견, BridgeHttpServer 부팅, ReflectionEngineStateProvider 폴백)은 동일해야 함
|
||||
- provider 결정 로직: `HmegDirectStateProvider` 가능하면 사용, 실패 시 `ReflectionEngineStateProvider` 폴백, 최종 실패 시 `NullEngineStateProvider`
|
||||
|
||||
### D6. 의존 그래프 검증
|
||||
- Generic 모듈의 csproj는 `HmEG.dll` 또는 `Editor*PluginInterface.dll` 을 **직간접 참조하지 않음**
|
||||
- HmEG-aware 모듈의 csproj는 `HmEG.dll`만 참조, 특정 앱 어셈블리 참조 금지
|
||||
- App-specific 모듈만이 자기 앱 어셈블리 참조
|
||||
- 신규 `Recordingtest.Architecture.Tests` — `Assembly.GetReferencedAssemblies()` 검사로 위반 검출. 각 계층별 expected reference set assert.
|
||||
|
||||
### D7. 빌드/테스트 green
|
||||
- `dotnet build recordingtest.sln` 성공
|
||||
- `dotnet test recordingtest.sln` 100+ tests 모두 pass
|
||||
- 신규 ArchitectureTests + HmegDirectStateProvider 단위 테스트 통과
|
||||
|
||||
### D6. 빌드/테스트 green
|
||||
- `dotnet build recordingtest.sln` 성공
|
||||
- `dotnet test recordingtest.sln` 100+ tests 모두 pass (현재 상태 유지)
|
||||
- 신규 ArchitectureTests 통과
|
||||
|
||||
### D8. PROGRESS/PLAN 갱신 + history
|
||||
- PROGRESS.md Done에 항목 추가
|
||||
- PLAN.md에서 본 contract 제거
|
||||
- `docs/history/YYYY-MM-DD_generic-sut-split.md` 작성
|
||||
- CLAUDE.md §8.1 표(현재 모듈 분류)를 마이그레이션 후 상태로 갱신
|
||||
|
||||
### D9. 단일 커밋(권장) 또는 2단 커밋
|
||||
- 옵션 A: 단일 커밋 — sln 무결성 보장, BREAKING 명시
|
||||
- 옵션 B: (1) git mv만, (2) 내용 변경 — git rename detection 보존
|
||||
- Generator 판단. 어느 쪽이든 메시지 prefix `BREAKING:`
|
||||
|
||||
## Out of scope
|
||||
|
||||
- 새 SUT(다른 WPF 앱) 추가 — 본 contract는 구조만 만든다
|
||||
- engine-bridge v3의 `HmEgDirectStateProvider` 구현 — 그건 별도 contract `engine-bridge-v3` 후속
|
||||
- generic 코어의 기능 변경 — 순수 rename/이동만
|
||||
|
||||
## Interfaces
|
||||
|
||||
```csharp
|
||||
// src/Recordingtest.Bridge.Abstractions/IEngineStateProvider.cs (generic)
|
||||
namespace Recordingtest.Bridge;
|
||||
|
||||
public interface IEngineStateProvider
|
||||
{
|
||||
IReadOnlyList<string> GetSelectedIds();
|
||||
CameraSnapshot GetCamera();
|
||||
SceneSnapshot GetScene();
|
||||
bool GetRenderComplete();
|
||||
}
|
||||
|
||||
public sealed record CameraSnapshot(double[] Eye, double[] Target, double[] Up, double Fov);
|
||||
public sealed record SceneSnapshot(int ObjectCount, string? DocumentPath);
|
||||
```
|
||||
|
||||
```csharp
|
||||
// src/Sut/EgBim/Recordingtest.Sut.EgBim.Plugin/HmEgDirectStateProvider.cs (SUT)
|
||||
namespace Recordingtest.Sut.EgBim.Plugin;
|
||||
using Recordingtest.Bridge;
|
||||
public sealed class HmEgDirectStateProvider : IEngineStateProvider { ... }
|
||||
```
|
||||
|
||||
## Risks
|
||||
|
||||
- **sln 경로 깨짐**: csproj 이동 시 sln 갱신을 빠뜨리면 빌드 깨짐. 완화: D6에서 솔루션 빌드 + 테스트 강제.
|
||||
- **using 구문 누락**: 네임스페이스 rename 시 다른 프로젝트의 using이 깨짐. 완화: 빌드가 잡아냄.
|
||||
- **engine-bridge v3 진행 중 방해**: scaffold 미커밋 상태(IAppManagerAccessor 등). 본 refactor 전에 v3 scaffold를 먼저 커밋해 두는 게 안전.
|
||||
- **git rename detection**: 폴더 이동 + 내용 변경이 동시에 들어가면 git이 rename 인식을 못 할 수 있음. 완화: 가능한 한 "이동만" 한 번 커밋, "내용 변경"은 후속 커밋. (단일 커밋 vs rename 보존 trade-off — D8과 충돌 가능. Generator 판단.)
|
||||
|
||||
## Estimated complexity
|
||||
|
||||
중. 코드 로직 변경은 거의 없고 mass rename + 폴더 이동 + sln 정리. 시간보다 손실 위험 관리가 핵심.
|
||||
|
||||
## Evaluation plan
|
||||
|
||||
Evaluator는:
|
||||
1. D1~D5를 파일/폴더/csproj 검사로 채점
|
||||
2. D5의 ArchitectureTests 실행
|
||||
3. D6의 전체 build/test 실행
|
||||
4. D8의 git log 단일 커밋 확인
|
||||
|
||||
fail 1회 → Generator 재작업. 누적 3회 → 자동 중단.
|
||||
Reference in New Issue
Block a user