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:
@@ -0,0 +1,73 @@
|
||||
using Recordingtest.Bridge;
|
||||
using Recordingtest.Hmeg.Bridge;
|
||||
using Xunit;
|
||||
|
||||
namespace Recordingtest.Hmeg.Bridge.Tests;
|
||||
|
||||
public class HmegDirectStateProviderTests
|
||||
{
|
||||
[Fact]
|
||||
public void NullLambdas_Return_SafeDefaults_NoThrow()
|
||||
{
|
||||
var p = new HmegDirectStateProvider(
|
||||
spaceProvider: () => null,
|
||||
viewportProvider: () => null);
|
||||
|
||||
Assert.Empty(p.GetSelectedIds());
|
||||
|
||||
var c = p.GetCamera();
|
||||
Assert.Equal(45.0, c.Fov);
|
||||
Assert.Equal(new double[] { 0, 0, 1 }, c.Up);
|
||||
|
||||
var s = p.GetScene();
|
||||
Assert.Equal(0, s.ObjectCount);
|
||||
Assert.Null(s.DocumentPath);
|
||||
|
||||
Assert.True(p.GetRenderComplete());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Throwing_Lambdas_Are_Swallowed_Returns_SafeDefaults()
|
||||
{
|
||||
var p = new HmegDirectStateProvider(
|
||||
spaceProvider: () => throw new InvalidOperationException("boom"),
|
||||
viewportProvider: () => throw new InvalidOperationException("boom"));
|
||||
|
||||
Assert.Empty(p.GetSelectedIds());
|
||||
var c = p.GetCamera();
|
||||
Assert.Equal(45.0, c.Fov);
|
||||
var s = p.GetScene();
|
||||
Assert.Equal(0, s.ObjectCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DocumentPathProvider_Is_Used_For_Scene()
|
||||
{
|
||||
var p = new HmegDirectStateProvider(
|
||||
spaceProvider: () => null,
|
||||
viewportProvider: () => null,
|
||||
documentPathProvider: () => "C:/sample.hmeg");
|
||||
var s = p.GetScene();
|
||||
Assert.Equal("C:/sample.hmeg", s.DocumentPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DocumentPathProvider_Throwing_Is_Swallowed()
|
||||
{
|
||||
var p = new HmegDirectStateProvider(
|
||||
spaceProvider: () => null,
|
||||
viewportProvider: () => null,
|
||||
documentPathProvider: () => throw new InvalidOperationException());
|
||||
var s = p.GetScene();
|
||||
Assert.Null(s.DocumentPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_Throws_OnNullProviders()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new HmegDirectStateProvider(null!, () => null));
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new HmegDirectStateProvider(() => null, null!));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>Recordingtest.Hmeg.Bridge.Tests</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Hmeg\Recordingtest.Hmeg.Bridge\Recordingtest.Hmeg.Bridge.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Recordingtest.Bridge.Abstractions\Recordingtest.Bridge.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="HmEG">
|
||||
<HintPath>..\..\..\EG-BIM Modeler\HmEG.dll</HintPath>
|
||||
<!-- Test process is standalone (not loaded into the SUT), so the
|
||||
assembly must be copied next to the test dll at runtime. -->
|
||||
<Private>true</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user