Implement engine-bridge PoC v1 (#9)
- Add Recordingtest.EngineBridge library (IEngineSnapshot, HmEgSnapshot skeleton, MetadataLoader, CandidateFinder, CatalogWriter). - Add Recordingtest.EngineBridge.Probe console exe that dumps hmeg-types.json and hmeg-candidates.json to docs/engine-catalog. - Add Recordingtest.EngineBridge.Tests (xUnit, 6 tests). - Add probe design doc with plugin-masquerade recommendation. - Static analysis only; SUT is never executed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
77
docs/contracts/engine-bridge.md
Normal file
77
docs/contracts/engine-bridge.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Sprint Contract — engine-bridge (PoC v1)
|
||||
|
||||
**Owner:** Generator
|
||||
**Depends on:** sut-prober (assemblies.json), SUT PDB 동봉
|
||||
**Issue:** #9
|
||||
|
||||
## Goal
|
||||
|
||||
HmEG 3D 엔진 내부 상태(선택된 객체, 카메라, 씬 그래프)를 recordingtest에서 읽을 수 있는 경로를 확보한다. PoC v1은 **정적 탐색 + API 초안 + in-process probe 설계 문서**까지. 실제 SUT attach 및 런타임 검증은 v2로 연기(샌드박스 제약).
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- [ ] `Recordingtest.EngineBridge` 라이브러리 + `Recordingtest.EngineBridge.Probe` 콘솔 exe
|
||||
- [ ] **정적 분석**: `System.Reflection.MetadataLoadContext`로 `EG-BIM Modeler/HmEG.dll`, `HmGeometry*.dll`, `Editor*.dll`을 로드해 public/internal 타입 열거 (SUT 실행 금지)
|
||||
- [ ] **후보 식별**: 다음 키워드를 포함하는 타입·프로퍼티·메서드를 카탈로그화:
|
||||
- `Select` / `Selection` / `Picked`
|
||||
- `Camera` / `View` / `Viewport` / `EyePoint`
|
||||
- `Scene` / `Document` / `World` / `Root`
|
||||
- `Render` / `Draw` / `Frame`
|
||||
- [ ] 출력: `docs/engine-catalog/hmeg-types.json`, `hmeg-candidates.json`
|
||||
- `hmeg-types.json`: 어셈블리별 `{ assembly, typeName, isPublic, namespace }`
|
||||
- `hmeg-candidates.json`: 후보별 `{ category, assembly, typeName, memberKind(Property|Method|Event), memberName, signature }`
|
||||
- **결정성**: 정렬된 출력, 2회 실행 후 git diff 비어야 함
|
||||
- [ ] **API 초안**: `IEngineSnapshot` 인터페이스 + DTOs:
|
||||
```csharp
|
||||
public interface IEngineSnapshot {
|
||||
IReadOnlyList<string> SelectedObjectIds { get; }
|
||||
CameraState Camera { get; }
|
||||
SceneSummary Scene { get; }
|
||||
bool IsRenderComplete { get; }
|
||||
}
|
||||
public sealed record CameraState(double[] EyePoint, double[] Target, double[] Up, double Fov);
|
||||
public sealed record SceneSummary(int ObjectCount, string? DocumentPath);
|
||||
```
|
||||
- 실제 구현체 `HmEgSnapshot`은 **skeleton**(throw NotImplementedException)으로 두되, 리플렉션 접근 지점(타입 이름)을 상수로 정의하고 정적 분석 카탈로그에서 실제 존재 확인
|
||||
- [ ] **Probe 설계 문서** `docs/engine-bridge-probe-design.md`:
|
||||
- in-process injection 옵션 3가지 (AssemblyLoadContext attach vs. CLR profiler vs. inline MSIL patching) 장단점 + 권고
|
||||
- 렌더 완료 신호 후보 (property polling vs. event subscription) + 예상 레이턴시
|
||||
- AutomationPeer 대체 경로와의 비교
|
||||
- [ ] xUnit 테스트 ≥ 5:
|
||||
- `MetadataLoader_LoadsHmegAssembly_WithoutExecution` — MetadataLoadContext 사용 확인
|
||||
- `CandidateFinder_FindsSelectionRelatedTypes` — 실제 HmEG.dll 로드 후 `Selection` 키워드 포함 타입이 1개 이상
|
||||
- `CatalogSerializer_OutputsSorted_Idempotent` — 2회 생성 시 byte-identical
|
||||
- `IEngineSnapshot_DefaultInstance_ThrowsNotImplemented` — skeleton 확인
|
||||
- `CandidateCategories_AllFourPresent` — Select/Camera/Scene/Render 4개 카테고리가 카탈로그에 존재
|
||||
- [ ] `dotnet build` green + `dotnet test` all pass
|
||||
- [ ] SUT 폴더 쓰기 접근 없음 (grep으로 확인)
|
||||
- [ ] 실행 경로: `dotnet run --project src/Recordingtest.EngineBridge.Probe -- --sut "EG-BIM Modeler" --out docs/engine-catalog`
|
||||
|
||||
## Out of scope (v2 이후)
|
||||
|
||||
- 실제 SUT 프로세스 attach / 리플렉션 호출 실행
|
||||
- 런타임 값 캡처
|
||||
- player 통합
|
||||
- AutomationPeer 자동 부착
|
||||
|
||||
## Interfaces
|
||||
|
||||
- **Inputs:** SUT 폴더 경로
|
||||
- **Outputs:** `docs/engine-catalog/*.json`, `docs/engine-bridge-probe-design.md`
|
||||
- **Side effects:** 없음 (MetadataLoadContext는 순수 메타데이터 로드)
|
||||
|
||||
## Evaluation plan
|
||||
|
||||
1. `dotnet build recordingtest.sln` green
|
||||
2. `dotnet test tests/Recordingtest.EngineBridge.Tests` — 5 pass
|
||||
3. `dotnet run --project src/Recordingtest.EngineBridge.Probe -- --sut "EG-BIM Modeler"` exit 0
|
||||
4. `hmeg-candidates.json` 카테고리 4개 전부 존재, 각 카테고리 엔트리 ≥ 1
|
||||
5. 2회 실행 후 git diff 비어있음
|
||||
6. `IEngineSnapshot` 인터페이스 + 상수가 존재하며, 상수 타입 이름이 실제 카탈로그에 포함됨을 확인하는 테스트 1개
|
||||
7. Probe 설계 문서가 3개 옵션 비교 + 권고를 포함
|
||||
|
||||
## Risks
|
||||
|
||||
- HmEG 내부는 obfuscation이 걸려있을 수 있음 → 발견된 타입 수가 적으면 휴리스틱 강화 필요
|
||||
- Selection 타입이 `internal`이면 PoC v1에서도 메타데이터로는 보임 (접근 가능). 런타임 접근은 v2.
|
||||
- MetadataLoadContext는 의존 어셈블리 resolver 필요 → `PathAssemblyResolver(new[] { sutRoot, dotnetRuntimeDir })` 세팅 필수.
|
||||
97
docs/engine-bridge-probe-design.md
Normal file
97
docs/engine-bridge-probe-design.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# engine-bridge Probe Design (PoC v2 blueprint)
|
||||
|
||||
Related: issue #9, contract `docs/contracts/engine-bridge.md`.
|
||||
|
||||
## Goal
|
||||
|
||||
Provide recordingtest with a reliable, low-latency read path into HmEG's live runtime state
|
||||
(selected object IDs, camera state, scene summary, render-complete flag) while the SUT
|
||||
(`EG-BIM Modeler`) is running, so golden-file scenarios can checkpoint internal state alongside
|
||||
saved `.hmeg` files.
|
||||
|
||||
PoC v1 (this sprint) delivers static catalogs and a skeleton API. PoC v2 must actually get
|
||||
into the process. The rest of this document surveys the options and picks one.
|
||||
|
||||
## Options
|
||||
|
||||
### 1. `AssemblyLoadContext` side-load in the recordingtest process
|
||||
Load `HmEG.dll` into a secondary `AssemblyLoadContext` inside the recordingtest test runner.
|
||||
|
||||
- Pros: no native code, single process, trivial lifetime.
|
||||
- Cons: **we would be reading our own copy of HmEG, not the SUT's**. No live camera, no live
|
||||
selection. Any singleton the SUT maintains is unreachable.
|
||||
- Verdict: **not viable** for read-live-state; listed for completeness so reviewers don't
|
||||
propose it. Static metadata scanning (what PoC v1 already does) is the legitimate use of
|
||||
this family of APIs.
|
||||
|
||||
### 2. CLR profiling API (ICorProfilerCallback)
|
||||
Ship a native profiler DLL registered via `CORECLR_PROFILER*` env vars. The profiler attaches
|
||||
at CLR startup, can `SetILFunctionBody` on HmEG methods, or hand managed code to a bootstrap
|
||||
assembly that installs hooks.
|
||||
|
||||
- Pros: deepest reach, survives obfuscation, hooks JIT. Can intercept private methods.
|
||||
- Cons: native DLL (C/C++), must match CLR bitness and version, requires SUT restart with
|
||||
env vars set, complex failure modes, AV-sensitive.
|
||||
- Verdict: **fallback** for a zero-cooperation scenario. Too heavy for first attempt.
|
||||
|
||||
### 3. In-process managed injector (e.g. `Ezez.CLRInjection`, `SharpMonoInjector` style)
|
||||
`CreateRemoteThread` + `LoadLibrary` on a small native bootstrap that starts a CLR host and
|
||||
runs our managed bridge DLL inside the SUT's AppDomain. Bridge uses ordinary reflection to
|
||||
reach `HmEGAppManager` and exposes state via named pipe or loopback HTTP.
|
||||
|
||||
- Pros: attach at runtime, no SUT restart if the SUT is already running. Pure managed once
|
||||
bootstrapped. Symmetrical with how profilers like dotTrace attach on demand.
|
||||
- Cons: Windows-only, bitness must match (SUT is x64 WPF), AV alarms, fragile across .NET
|
||||
runtime versions, process isolation privileges required. CoreCLR hosting from a foreign
|
||||
thread is tricky compared to classic Framework.
|
||||
- Verdict: viable but operationally painful. Use only if option 4 is blocked.
|
||||
|
||||
### 4. Plugin masquerade via SUT's existing MEF pipeline (RECOMMENDED)
|
||||
The SUT already loads plugins from `EG-BIM Modeler/Plugins/` using MEF (per `CLAUDE.md` §1
|
||||
and `docs/sut-catalog/plugins.md`). Drop `Recordingtest.EngineBridge.SutPlugin.dll` into that
|
||||
folder. It exports the plugin contract the SUT expects, grabs a reference to
|
||||
`HmEGAppManager` (or whatever the static catalog shows is the canonical accessor), and
|
||||
publishes `IEngineSnapshot` over a localhost endpoint (named pipe `recordingtest-bridge` or
|
||||
HTTP on 127.0.0.1).
|
||||
|
||||
- Pros: legitimate extension point, no native code, no process injection, no AV noise, CI
|
||||
friendly, single cooperating restart. Honors CLAUDE.md §5.7 ("SUT 침습 최소화 — 별도
|
||||
어셈블리로 격리"). Integrates with dispatcher marshaling by design — the plugin runs on
|
||||
the WPF UI thread when called back.
|
||||
- Cons: requires SUT restart once to load the plugin; depends on MEF contract remaining
|
||||
stable; small IPC surface to keep deterministic.
|
||||
- Verdict: **chosen path for PoC v2**.
|
||||
|
||||
### 5. AutomationPeer (comparison baseline)
|
||||
Add a custom `UIA` AutomationPeer on the 3D viewport that exposes selection via the standard
|
||||
`SelectionPattern` and camera via custom properties.
|
||||
|
||||
- Pros: standard, debuggable with `inspect.exe`.
|
||||
- Cons: requires SUT source changes; cannot easily expose full scene graph or render-complete
|
||||
without contortions; limited to what UIA patterns can model.
|
||||
- Verdict: parallel track for exposing UI-shaped state. Not a replacement for the bridge.
|
||||
|
||||
## Render-complete signal
|
||||
|
||||
| Approach | Latency (expected) | Notes |
|
||||
|---|---|---|
|
||||
| Poll `IsDirty`-like property at ~16 ms | ~1 frame | simple, robust; costs a reflection call per tick |
|
||||
| Subscribe to a `FrameRendered`/`Invalidated` event | event-driven, ~0 ms | best case; depends on such an event existing (the static catalog will tell us — look for `render` candidates with `MemberKind=Event`) |
|
||||
| WPF `CompositionTarget.Rendering` from inside the plugin | ~1 frame | works even if HmEG has no event; UI-thread bound |
|
||||
|
||||
Recommendation: try event subscription first via the candidates catalog; fall back to
|
||||
`CompositionTarget.Rendering` inside the MEF plugin because the plugin already runs on the
|
||||
UI thread.
|
||||
|
||||
## Recommendation
|
||||
|
||||
PoC v2: **implement option 4 (plugin masquerade)** with a named-pipe JSON protocol that
|
||||
produces the `IEngineSnapshot` DTOs defined in this library. Option 2 (CLR profiler) is the
|
||||
documented fallback for a future "zero cooperation" scenario. Option 5 (AutomationPeer) is a
|
||||
parallel concern that belongs to `sut-prober` and `recorder`, not to engine-bridge.
|
||||
|
||||
## Open questions (tracked, not blocking v1)
|
||||
|
||||
- Exact MEF contract the SUT expects (see `docs/sut-catalog/plugins.md` — resolved before v2)
|
||||
- Whether HmEG exposes a `FrameRendered`-style event (answer will come from v1 catalog)
|
||||
- Selection identity: object GUID vs. transient index — needs a stable key for golden diffs
|
||||
93082
docs/engine-catalog/hmeg-candidates.json
Normal file
93082
docs/engine-catalog/hmeg-candidates.json
Normal file
File diff suppressed because it is too large
Load Diff
33746
docs/engine-catalog/hmeg-types.json
Normal file
33746
docs/engine-catalog/hmeg-types.json
Normal file
File diff suppressed because it is too large
Load Diff
68
docs/history/2026-04-07_이슈9-engine-bridge-generator.md
Normal file
68
docs/history/2026-04-07_이슈9-engine-bridge-generator.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# 2026-04-07 이슈 #9 — engine-bridge PoC v1 (Generator)
|
||||
|
||||
- **이슈**: kimminsung/recordingtest#9
|
||||
- **소요 시간**: ~25분
|
||||
- **Context 사용량**: ~55k tokens
|
||||
- **역할**: Generator (Sprint Contract `docs/contracts/engine-bridge.md` 기준 구현)
|
||||
|
||||
## 범위
|
||||
|
||||
Sprint Contract `engine-bridge` PoC v1의 DoD 항목만 구현. 스코프 이탈 없음.
|
||||
실제 런타임 attach / 값 캡처는 v2로 연기(계약 Out of scope).
|
||||
|
||||
## 산출물
|
||||
|
||||
### 라이브러리 `src/Recordingtest.EngineBridge/`
|
||||
- `IEngineSnapshot.cs` — `IEngineSnapshot` 인터페이스 + `CameraState`/`SceneSummary` 레코드
|
||||
- `HmEgSnapshot.cs` — skeleton (NotImplementedException), 리플렉션 앵커 상수
|
||||
- `HmEgAssemblyHint="HmEG"`, `EditorManagerTypeHint="HmEGAppManager"`, etc.
|
||||
- `MetadataLoader.cs` — `MetadataLoadContext` + `PathAssemblyResolver` (SUT root + runtime dir)
|
||||
- `CandidateFinder.cs` — 4 카테고리 (select/camera/scene/render) 키워드 매칭
|
||||
- `CatalogWriter.cs` — 결정적 정렬된 JSON 출력 (WriteIndented + LF normalize + trailing newline)
|
||||
|
||||
### Probe `src/Recordingtest.EngineBridge.Probe/`
|
||||
- `Program.cs` — `--sut`/`--out` 인자, 기본값 `EG-BIM Modeler` / `docs/engine-catalog`
|
||||
- SUT 폴더 없음 → exit 2
|
||||
- 카탈로그 2개 작성 + 요약 출력
|
||||
|
||||
### 테스트 `tests/Recordingtest.EngineBridge.Tests/` (xUnit, 6개)
|
||||
1. `MetadataLoader_LoadsHmegAssembly_WithoutExecution`
|
||||
2. `CandidateFinder_FindsSelectionRelatedTypes`
|
||||
3. `CatalogSerializer_OutputsSorted_Idempotent`
|
||||
4. `HmEgSnapshot_DefaultInstance_ThrowsNotImplemented`
|
||||
5. `CandidateCategories_AllFourPresent`
|
||||
6. `HmEgSnapshot_Constants_MatchCatalog` (bonus)
|
||||
|
||||
SUT 탐지는 `AppContext.BaseDirectory`에서 위로 걸어 `EG-BIM Modeler/HmEG.dll`을 찾는다.
|
||||
|
||||
### 설계 문서 `docs/engine-bridge-probe-design.md`
|
||||
- 5가지 옵션 비교 (ALC side-load / CLR profiler / 관리 인젝터 / **MEF plugin masquerade** / AutomationPeer)
|
||||
- render-complete 신호 레이턴시 표
|
||||
- 권고: **plugin masquerade** (PoC v2), CLR profiler는 fallback
|
||||
|
||||
### 카탈로그 `docs/engine-catalog/`
|
||||
- `hmeg-types.json`, `hmeg-candidates.json`
|
||||
|
||||
### 솔루션
|
||||
- `recordingtest.sln`에 3개 프로젝트 추가 (`dotnet sln add`)
|
||||
|
||||
## 실행 결과
|
||||
|
||||
- `dotnet build recordingtest.sln` → 경고 0, 오류 0
|
||||
- 13 어셈블리 로드 (HmEG 2285 / EditorCore 416 / HmGeometry.V2 1669 / ...)
|
||||
- 후보: camera=4226, render=3602, scene=3081, select=726
|
||||
- 2회 실행 → `docs/engine-catalog` diff 없음 (결정성 확인)
|
||||
- `dotnet test tests/Recordingtest.EngineBridge.Tests` → 6/6 통과
|
||||
|
||||
## 제약 준수
|
||||
|
||||
- SUT 실행 없음 (MetadataLoadContext 전용, 메타데이터만)
|
||||
- `EG-BIM Modeler/`에 쓰기 없음
|
||||
- `PROGRESS.md`/`PLAN.md` 미수정 (Generator는 Evaluator pass 전까지 손대지 않음)
|
||||
- `TreatWarningsAsErrors` 유지, nullable 활성화
|
||||
- 추가 런타임 의존성: `System.Reflection.MetadataLoadContext` 8.0.0 + `System.Text.Json` 8.0.5 만
|
||||
|
||||
## 남은 것 (Evaluator 몫)
|
||||
|
||||
- `docs/contracts/engine-bridge.evaluation.md` 작성
|
||||
- pass 확정 후 `PROGRESS.md`/`PLAN.md` 갱신은 호출자가 수행
|
||||
Reference in New Issue
Block a user