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:
84
docs/contracts/engine-bridge-v3.md
Normal file
84
docs/contracts/engine-bridge-v3.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Sprint Contract — engine-bridge-v3
|
||||
|
||||
## Goal
|
||||
|
||||
`Recordingtest.EgPlugin.ReflectionEngineStateProvider`를 v2의 stub에서 진짜 reflection 기반 매핑으로 격상한다. SUT(EG-BIM Modeler)의 HmEG 엔진 내부 상태(카메라/선택/씬)를 in-process plugin에서 reflection으로 읽어 HTTP `/state` 응답에 실제 값을 채운다. 이 값이 골든파일 sidecar JSON으로 들어가 회귀 비교의 핵심 결정성 신호가 된다.
|
||||
|
||||
## Background
|
||||
|
||||
- v1: 정적 분석으로 HmEG 어셈블리 멤버 후보 8000+ 카탈로그 (`docs/engine-catalog/hmeg-candidates.json`).
|
||||
- v2: MEF plugin masquerade + HttpListener + 8 + 3 tests. `ReflectionEngineStateProvider`는 stub만.
|
||||
- v3: stub을 실매핑으로 교체. SUT 환경에서 라이브 검증 필요.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
각 항목은 객관적으로 검증 가능해야 한다.
|
||||
|
||||
### D1. AppManager 발견
|
||||
- plugin이 SUT 프로세스 안에서 `AppManager`(또는 동등한 root) 인스턴스를 reflection으로 획득한다.
|
||||
- 실패 시 `NullEngineStateProvider`로 안전하게 폴백하고 stderr에 한 번만 경고 로그.
|
||||
|
||||
### D2. CameraSnapshot 실매핑
|
||||
- `GetCamera()`가 활성 뷰포트의 카메라 eye/target/up/fov를 **non-default 값**으로 반환.
|
||||
- 검증: 라이브 SUT에서 카메라 이동 후 두 번 호출 → 적어도 한 필드가 달라짐.
|
||||
|
||||
### D3. SceneSnapshot 실매핑
|
||||
- `GetScene()`가 현재 문서의 객체 수와 (열린 경우) 문서 경로를 반환.
|
||||
- 검증: Box 1개 생성 후 `ObjectCount >= 1`, 새 문서 만들면 0.
|
||||
|
||||
### D4. SelectedIds 실매핑
|
||||
- `GetSelectedIds()`가 현재 선택된 객체의 ID 리스트를 반환 (HmEG 내부 ID 또는 GUID 문자열).
|
||||
- 검증: 객체 선택 → 비어있지 않은 리스트.
|
||||
|
||||
### D5. 결정성 + 정규화
|
||||
- 응답 JSON은 normalizer가 처리 가능한 형태 (정렬된 키, 안정된 부동소수점 표현). normalizer 규칙은 기존 `mask_volatile_settings` / 부동소수점 epsilon으로 충분한지 확인하고 부족하면 신규 규칙 등록.
|
||||
|
||||
### D6. 단위 테스트
|
||||
- `ReflectionEngineStateProvider`의 reflection 경로를 mockable한 `IAppManagerAccessor` 추상화 뒤로 격리.
|
||||
- Fake accessor로 각 D2/D3/D4를 단위 테스트화 (라이브 SUT 없이 CI 가능).
|
||||
- 최소 6 신규 테스트, 전체 suite green (현 94+ → 100+).
|
||||
|
||||
### D7. 라이브 검증
|
||||
- 사용자 SUT 환경에서 plugin 로드 → `/state` GET 응답에 D2/D3/D4 실값 확인.
|
||||
- 결과는 `docs/history/2026-04-08_engine-bridge-v3.md` 에 캡처.
|
||||
|
||||
### D8. 문서
|
||||
- `docs/contracts/engine-bridge-v3.evaluation.md` (Evaluator 산출물)
|
||||
- `docs/guides/engine-bridge-deploy.md` 업데이트 (v3 응답 스키마 변경분)
|
||||
|
||||
## Out of scope
|
||||
|
||||
- HmEG 내부 데이터 변경/쓰기 (read-only)
|
||||
- viewport 픽셀 캡처 (별개 모듈)
|
||||
- 새 HTTP 엔드포인트 (기존 `/state` 라우트만 채움)
|
||||
|
||||
## Interfaces
|
||||
|
||||
```csharp
|
||||
// 새 추상화 (D6)
|
||||
public interface IAppManagerAccessor
|
||||
{
|
||||
object? GetAppManager();
|
||||
object? GetActiveDocument();
|
||||
object? GetActiveViewport();
|
||||
}
|
||||
|
||||
// 기존 IEngineStateProvider 시그니처 유지 — 구현만 교체
|
||||
```
|
||||
|
||||
## Evaluation plan
|
||||
|
||||
1. Evaluator는 `/contract engine-bridge-v3.md` 를 읽고 D1~D8을 차례로 채점.
|
||||
2. D2/D3/D4는 단위 테스트(D6)로 검증 가능 → CI에서 자동 grade.
|
||||
3. D7은 사용자 라이브 결과 첨부로 grade (orchestrator가 캡처 전달).
|
||||
4. fail 1회 → Generator 재작업. 누적 3회 → 자동 중단.
|
||||
|
||||
## Risks
|
||||
|
||||
- HmEG 내부 타입 이름이 obfuscation/난독화 가능 → reflection by-name이 깨질 수 있음. 완화: `hmeg-candidates.json` 카탈로그를 dictionary로 lookup, fallback 체인 다중화.
|
||||
- AppManager singleton 접근 패턴이 SUT 버전마다 다를 수 있음. 완화: D1에서 여러 후보 시도.
|
||||
- 카메라 좌표계가 right-handed/left-handed/up-axis 다양 → 정규화 규칙 필요.
|
||||
|
||||
## Estimated complexity
|
||||
|
||||
중. 단위 테스트만으로는 D2/D3/D4의 실매핑이 옳은지 확인 불가 — 라이브 검증(D7)이 critical path. 1차 사이클은 발견(discovery)에 시간 쏠릴 가능성.
|
||||
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회 → 자동 중단.
|
||||
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` (신규)
|
||||
205
docs/hmeg-api-survey.md
Normal file
205
docs/hmeg-api-survey.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# HmEG Public API Survey (engine-bridge v3 source)
|
||||
|
||||
> 본 문서는 `D:\GiteaAll\HmEngine\HmEG\HmEG` 소스를 read-only로 훑어 정리한 결과다.
|
||||
> 목적: `HmEgDirectStateProvider`와 SUT-side bridge 추가 엔드포인트(`/focus`, `/hit-test`, `/command`) 설계 근거.
|
||||
> **수정 금지** — 본 폴더는 우리 저장소 바깥의 SUT 소스다. 참조만.
|
||||
|
||||
## 식별 마커 — `<public-hmeg-api />`
|
||||
|
||||
HmEG는 `<public-hmeg-api />` XML doc 태그로 **공식 공개 surface**를 표시한다. v3에서 우리가 의존할 모든 멤버는 이 태그가 붙은 것만 사용한다. (난독화/리네이밍 시 해당 마커가 안전 목록 역할을 할 가능성이 큼.)
|
||||
|
||||
검색 (read-only):
|
||||
```
|
||||
grep -rn 'public-hmeg-api' D:\GiteaAll\HmEngine\HmEG\HmEG
|
||||
```
|
||||
|
||||
## 핵심 타입 (확인 완료)
|
||||
|
||||
### 1. `ModelBase` — 모든 모델 엔티티의 추상 base
|
||||
경로: `Model\Scene\ModelData\ModelBase.cs`
|
||||
|
||||
| 멤버 | 타입 | 비고 |
|
||||
|---|---|---|
|
||||
| `Uid` | `Guid` | **결정성 핵심** — 영구 고유 ID. golden file에 그대로 쓸 수 있음 |
|
||||
| `Name` | `string` | 사용자 보이는 이름 |
|
||||
| `ModelType` | `ModelType` (enum) | Mesh / Line / 빌보드 등 |
|
||||
| `GeoType` | `HmGEntityType` | HmGeometry 공통 |
|
||||
| `Tag` | `object` | 사용자 커스텀 |
|
||||
| `Label` | `string` | dwg export XData용 |
|
||||
| `ModelMatrix` | `HmMatrix3D` | 변환 행렬 |
|
||||
| `ItemsChanged` | `event EventHandler<OnChildModelChangedArgs>` | 자식 변경 알림 — wait_for 후보 |
|
||||
|
||||
### 2. `Space : ModelBase` — 문서 컨테이너 (= "Space" 트리의 루트)
|
||||
경로: `Model\Scene\ModelData\Space.cs`, `Space.Functions.cs`
|
||||
|
||||
| 멤버 | 타입 | 비고 |
|
||||
|---|---|---|
|
||||
| `Children` | `EgObservableFastList<ModelBase>` | **scene 트리 자식 노드** (= 객체 리스트) |
|
||||
| `ItemsCount` | `int` | **`/scene` 의 ObjectCount 가 직접 매핑** |
|
||||
| `Viewports` | `List<EGViewport>` | 이 Space에 연결된 뷰포트들 |
|
||||
| `SplitRow` / `SplitCol` | `int` | 뷰포트 분할 |
|
||||
| `EnableGroupSelection` | `bool` | |
|
||||
| `Add(ModelBase)` / `DeleteModel(...)` / `AddSpace(...)` / `Clear()` | mutator | **read-only로만 사용** |
|
||||
| `ImportFileModels` / `ImportInstancingModels` | event | 파일 import 알림 — wait_for 후보 |
|
||||
|
||||
### 3. `HmModel : ModelBase` — 실제 형상 객체
|
||||
경로: `Model\Scene\ModelData\HmModel.cs`
|
||||
|
||||
| 멤버 | 타입 | 비고 |
|
||||
|---|---|---|
|
||||
| `GEntity` | `HmGEntity` | HmGeometry 공통 |
|
||||
| `EGgEntity` | `EgObject` | EG geometry 객체 |
|
||||
| `BlockName` | `string` | |
|
||||
| `LayerName` | `string` | |
|
||||
| `LineTypeName` | `string` | |
|
||||
| `LineTypeScale` | `double` | |
|
||||
| `AttributeReferences` | `HmAttributeReferenceCollection` | |
|
||||
| `MouseEnter` / `MouseLeave` / `MouseDown` | event | **마우스 hit-test 결과 통지** — recorder에서 element 식별에 사용 가능 |
|
||||
|
||||
### 4. `HmEGViewport` (interface) — 뷰포트 공개 인터페이스
|
||||
경로: `Interface\IHmEGViewport.cs` (namespace `HmEG`)
|
||||
|
||||
| 멤버 | 타입 | 비고 |
|
||||
|---|---|---|
|
||||
| `CameraCore` | `CameraCore` (`Model\Camera\CameraCore.cs`) | **카메라 진입점** |
|
||||
| `Renderables` | `IEnumerable<SceneNode>` | 3D 씬 그래프 루트 노드 enumerable |
|
||||
| `D2DRenderables` | `IEnumerable<SceneNode2D>` | 2D 노드 |
|
||||
| `RenderHost` | `IRenderHost` | |
|
||||
| `EffectsManager` | `IEffectsManager` | |
|
||||
| `ViewportRectangle` | `EGRectangleI` | 픽셀 단위 |
|
||||
| `Attach(IRenderHost)` / `Detach()` | mutator | |
|
||||
| `Update(TimeSpan)` / `InvalidateRender(...)` / `InvalidateSceneGraph(...)` | mutator | |
|
||||
|
||||
### 5. `IHmCamera` — 카메라 표준 인터페이스
|
||||
경로: `Interface\IHmCamera.cs`
|
||||
|
||||
| 멤버 | 타입 | 매핑 |
|
||||
|---|---|---|
|
||||
| `Position` | `HmVector3D` | `CameraSnapshot.Eye` |
|
||||
| `LookDirection` | `HmVector3D` | `Eye + LookDir = Target` |
|
||||
| `UpDirection` | `HmVector3D` | `CameraSnapshot.Up` |
|
||||
| `CreateLeftHandSystem` | `bool` | 좌표계 정규화 시 필요 |
|
||||
| `CreateViewMatrix(HmMatrix3D)` | method | |
|
||||
| `CreateProjectionMatrix(double aspectRatio)` | method | fov는 별도 추출 (PerspectiveCamera에) |
|
||||
|
||||
> **주의**: `IHmCamera`에는 `Fov`/`FieldOfView`가 없다. `PerspectiveCamera : ProjectionCamera : CameraCore` 쪽에 있을 것으로 추정. v3 작성 시 `CameraCore` → `PerspectiveCamera` cast로 fov를 꺼낸다.
|
||||
|
||||
### 6. `ISelectable` — 노드 선택 상태
|
||||
경로: `Interface\Interfaces.cs:235`
|
||||
|
||||
```csharp
|
||||
public interface ISelectable
|
||||
{
|
||||
bool IsSelected { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
**중앙 집중 selection 리스트는 HmEG core에 없음.** SelectedIds를 얻으려면 Space 트리를 walk하면서 `ISelectable.IsSelected == true`인 노드를 모아야 한다. 또는 SUT 측 AppManager가 selection 리스트를 따로 들고 있을 수 있음 — **확인 필요**.
|
||||
|
||||
### 7. `HmSceneNode : MaterialGeometryNode, IDynamicReflectable, ISelectable`
|
||||
경로: `Model\Scene\HmSceneNode.cs`
|
||||
|
||||
씬 그래프의 실제 렌더링 노드. `IsSelected`를 가짐. `Renderables`에서 흘러나옴.
|
||||
|
||||
### 8. `HmEG.IPlugin` (interface)
|
||||
경로: `PlugIns\IPlugin.cs`
|
||||
|
||||
```csharp
|
||||
public interface IPlugin
|
||||
{
|
||||
string Name { get; }
|
||||
EGViewport View { get; set; } // ← 플러그인에 직접 주입되는 뷰포트
|
||||
bool RethrowException { get; set; }
|
||||
object Run(params object[] args);
|
||||
}
|
||||
```
|
||||
|
||||
**중요**: HmEG가 플러그인을 로드할 때 `View`를 직접 set 해준다. 즉 플러그인은 따로 Space/AppManager를 찾아갈 필요 없이 **`this.View`로 즉시 뷰포트에 도달**한다. 거기서 `CameraCore`, `Renderables` (씬 노드 enumerable) 모두 접근 가능. `Renderables`를 walk하면서 `ISelectable.IsSelected`로 선택된 노드 추출.
|
||||
|
||||
> 본 저장소의 `HmEgBridgePlugin`은 `EditorPlugin` (SUT-side `Editor03.PluginInterface`) 베이스를 쓴다. `EditorPlugin`이 내부적으로 `HmEG.IPlugin.View`를 set 해주는지, 아니면 다른 경로(예: `AppManager`)를 통해 Space에 접근하는지는 **`Editor03.PluginInterface.dll` 디컴파일 또는 SUT-side 소스가 있어야 확정 가능**.
|
||||
|
||||
## 아직 모르는 것 (확인 대기)
|
||||
|
||||
| | 항목 | 어디서 찾아야 하는가 |
|
||||
|---|---|---|
|
||||
| Q1 | "활성 Space" 진입점 — `EGViewport.Space`? `AppManager.Instance.ActiveSpace`? | `Editor03.PluginInterface` / SUT-side AppManager |
|
||||
| Q2 | "활성 Viewport" 가 여러 개일 때 어느 것이 active 인가 | 동일 |
|
||||
| Q3 | 중앙 selection 리스트 (예: `AppManager.Selection`) 또는 selection-changed 이벤트 | 동일 |
|
||||
| Q4 | 명령 (Command) 생명주기 이벤트 — `CommandStarted` / `CommandFinished` 같은 것 | 동일 (`Editor.AppManager.AppModeManager` 후보) |
|
||||
| Q5 | `PerspectiveCamera`에서 `FieldOfView` 정확한 프로퍼티 이름 | `Model\Camera\PerspectiveCamera.cs` (read-only로 한 번만 더 확인하면 됨) |
|
||||
| Q6 | 문서 파일 경로 — 저장 후의 `*.hmeg` 경로 보유처 | `Editor03.PluginInterface` 또는 `AppManager.ActiveDocument` |
|
||||
| Q7 | `EGViewport` 와 `HmEGViewport` 관계 — `EGViewport`가 후자를 구현? 아니면 별개 SUT-side 클래스? | `Editor03.PluginInterface` |
|
||||
|
||||
## v3 구현 방향 (Direct Provider)
|
||||
|
||||
```csharp
|
||||
// src/Recordingtest.EgPlugin/HmEgDirectStateProvider.cs
|
||||
public sealed class HmEgDirectStateProvider : IEngineStateProvider
|
||||
{
|
||||
private readonly Func<HmEG.HmEGViewport?> _getViewport; // plugin이 주입
|
||||
private readonly Func<Space?> _getSpace; // 동일
|
||||
|
||||
public IReadOnlyList<string> GetSelectedIds()
|
||||
{
|
||||
var sp = _getSpace();
|
||||
if (sp is null) return Array.Empty<string>();
|
||||
var ids = new List<string>();
|
||||
Walk(sp, ids);
|
||||
return ids;
|
||||
static void Walk(ModelBase node, List<string> ids)
|
||||
{
|
||||
if (node is ISelectable s && s.IsSelected) ids.Add(node.Uid.ToString());
|
||||
if (node is Space space)
|
||||
foreach (var child in space.Children) Walk(child, ids);
|
||||
}
|
||||
}
|
||||
|
||||
public CameraSnapshot GetCamera()
|
||||
{
|
||||
var vp = _getViewport();
|
||||
if (vp is null) return Default;
|
||||
var cam = vp.CameraCore;
|
||||
// CameraCore → IHmCamera 캐스트, 또는 Position/Look/Up 직접 접근
|
||||
// PerspectiveCamera 캐스트로 FOV
|
||||
...
|
||||
}
|
||||
|
||||
public SceneSnapshot GetScene()
|
||||
{
|
||||
var sp = _getSpace();
|
||||
return new SceneSnapshot(
|
||||
sp?.ItemsCount ?? 0,
|
||||
DocumentPathFromSomewhere()); // Q6
|
||||
}
|
||||
|
||||
public bool GetRenderComplete() => true; // 후속 작업
|
||||
}
|
||||
```
|
||||
|
||||
`_getViewport` / `_getSpace` 람다는 `HmEgBridgePlugin`이 자기 환경(`EditorPlugin`이 노출하는 진입점)에서 캡처해 넘긴다. 람다 형태로 두면 다음과 같은 이점이 있다:
|
||||
- 플러그인 base 클래스가 진입점을 어떻게 노출하는지가 바뀌어도 v3 provider는 영향 없음
|
||||
- 단위 테스트는 fake 람다를 넘겨서 검증
|
||||
|
||||
## SUT-side bridge 추가 엔드포인트 (Gap I 우회)
|
||||
|
||||
`BridgeHttpServer` / `StateRouter`에 추가:
|
||||
|
||||
| 엔드포인트 | 응답 | 사용처 |
|
||||
|---|---|---|
|
||||
| `GET /focus` | `{"path": "...", "type": "...", "name": "..."}` | recorder가 key_down 시 polling. `Keyboard.FocusedElement` (WPF)를 Dispatcher 위에서 호출 |
|
||||
| `GET /hit-test?x=&y=` | `{"hit": "HmModel#guid", "type": "...", "name": "..."}` | recorder가 click 시 호출. Space/Renderables walk + `VisualTreeHelper.HitTest` + (있으면) HmEG의 pick API |
|
||||
| `GET /command` | `{"running": "BOX", "phase": "awaiting_first_corner"}` | player의 wait_for. Q4 이벤트 구독 결과 캐시 |
|
||||
|
||||
이건 engine-bridge v3 본 contract와 별도 contract 권장 (`/contract sut-side-bridge`).
|
||||
|
||||
## 다음 액션
|
||||
|
||||
1. **engine-bridge-v3.md 계약 갱신** — reflection 항목 제거, 위 타입/멤버 이름으로 D2/D3/D4 고정
|
||||
2. **Q1~Q7 확인** — 사용자가 SUT-side(Editor03.PluginInterface 또는 AppManager) 소스/경로 알려주면 read-only로 1~2회 더 확인
|
||||
3. Q1/Q3 채워지면 `HmEgDirectStateProvider` 구현 + 플러그인 wire-up
|
||||
4. CI fallback — 기존 `ReflectionEngineStateProvider` + `IAppManagerAccessor`는 fake-friendly 형태로 유지 (HmEG 어셈블리 없는 단위 테스트 환경에서 빌드/테스트 가능해야 함)
|
||||
5. 라이브 검증 → curl 로 `/state` 확인 → 보정 → 커밋
|
||||
|
||||
## 라이선스 / 위생
|
||||
|
||||
본 저장소는 HmEG 소스 사본을 보관하지 **않는다**. 본 문서는 외부 소스에 대한 인터페이스 추출 메모일 뿐. 코드 발췌도 시그니처/주석 수준으로만 인용한다.
|
||||
Reference in New Issue
Block a user