BREAKING: 3-tier split step 2 + engine-bridge v3 EgBim lambdas wired
Completes the Generic / HmEG-aware / App-specific separation started in
f6b6e74. The legacy EgPlugin / EngineBridge / EngineBridge.Client /
EngineBridge.Probe modules are moved into their proper tiers, namespaces
and csproj/sln entries are renamed, and the HmegDirectStateProvider
lambdas are finally populated with real handles from the EgBim plugin
host. A new Recordingtest.Architecture.Tests project enforces the tier
rule at build time.
Moves (git mv + csproj/RootNamespace/AssemblyName rename + sln):
src/Recordingtest.EgPlugin
-> src/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost
src/Recordingtest.EngineBridge
-> src/Hmeg/Recordingtest.Hmeg.Catalog
src/Recordingtest.EngineBridge.Client
-> src/Hmeg/Recordingtest.Hmeg.Bridge.Client
src/Recordingtest.EngineBridge.Probe
-> src/Hmeg/Recordingtest.Hmeg.Catalog.Probe
tests/Recordingtest.EgPlugin.Tests
-> tests/Sut/EgBim/Recordingtest.Sut.EgBim.PluginHost.Tests
tests/Recordingtest.EngineBridge.Tests
-> tests/Hmeg/Recordingtest.Hmeg.Catalog.Tests
tests/Recordingtest.EngineBridge.IntegrationTests
-> tests/Hmeg/Recordingtest.Hmeg.Catalog.IntegrationTests
Namespace rename applied across all .cs files and csproj RootNamespace:
Recordingtest.EgPlugin -> Recordingtest.Sut.EgBim.PluginHost
Recordingtest.EngineBridge -> Recordingtest.Hmeg.Catalog
Recordingtest.EngineBridge.Client -> Recordingtest.Hmeg.Bridge.Client
Recordingtest.EngineBridge.Probe -> Recordingtest.Hmeg.Catalog.Probe
New: tests/Recordingtest.Architecture.Tests/
DependencyGraphTests walks Assembly.GetReferencedAssemblies() for each
tier and fails if a forbidden reference leaks in:
- Generic modules must not reference HmEG or any app-specific DLL
- HmEG-aware modules must not reference app-specific DLLs
- Recordingtest.Hmeg.Bridge must reference HmEG (positive check)
11 tests, all passing. Prevents future drift from CLAUDE.md §8.1.
Engine-bridge v3 wire-up (HmEgBridgePlugin.BuildProvider):
Previously the HmegDirectStateProvider lambdas returned null and the
chain fell through to reflection. They now call directly into the
EditorPlugin base class that HmEgBridgePlugin inherits:
spaceProvider = () => RootSpace
// AppManager.ViewportManager.RootSpace
viewportProvider = () => View
// EGViewport : Control, HmEGViewport
documentPathProvider = () => AppManager?.FileManager?.CurrentFile
Every lambda is wrapped in try/catch so plugin construction still
cannot throw back into the SUT. Editor02.HmEGAppManager.dll added as
a reference on Recordingtest.Sut.EgBim.PluginHost.csproj — app-
specific tier, which is allowed by the architecture tests.
Entry points were confirmed from read-only review of the SUT sources at
D:\GiteaAll\EG-BIM_Modeler\EditorPluginInterface\EditorPlugin.cs
D:\GiteaAll\EG-BIM_Modeler\HmEGApplicationManagementLibrary\HmEGAppManager.cs
D:\GiteaAll\EG-BIM_Modeler\HmEGApplicationManagementLibrary\SubManager\FileManager.cs
closing out Q1/Q2/Q6/Q7 from docs/hmeg-api-survey.md.
Tests: 115 -> 126 (+11 Architecture), 0 failures.
Next step: live verification of /scene /camera /selection with a real
SUT session; any discrepancy in HmegDirectStateProvider reflection will
be tightened after observing real HmEG camera field names.
Ref: #10 follow-up, #14 follow-up, docs/contracts/generic-sut-split.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
namespace Recordingtest.Hmeg.Bridge.Client;
|
||||
|
||||
public sealed class EngineBridgeException : Exception
|
||||
{
|
||||
public string Endpoint { get; }
|
||||
public EngineBridgeException(string endpoint, string message, Exception? inner = null)
|
||||
: base($"engine-bridge {endpoint}: {message}", inner)
|
||||
{
|
||||
Endpoint = endpoint;
|
||||
}
|
||||
}
|
||||
122
src/Hmeg/Recordingtest.Hmeg.Bridge.Client/HmEgHttpSnapshot.cs
Normal file
122
src/Hmeg/Recordingtest.Hmeg.Bridge.Client/HmEgHttpSnapshot.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using Recordingtest.Hmeg.Catalog;
|
||||
|
||||
namespace Recordingtest.Hmeg.Bridge.Client;
|
||||
|
||||
public sealed class HmEgHttpSnapshot : IEngineSnapshot, IDisposable
|
||||
{
|
||||
public const string DefaultBaseUrl = "http://localhost:38080";
|
||||
private readonly HttpClient _http;
|
||||
private readonly bool _ownsClient;
|
||||
private readonly string _baseUrl;
|
||||
|
||||
public HmEgHttpSnapshot(string baseUrl = DefaultBaseUrl, HttpClient? httpClient = null, TimeSpan? timeout = null)
|
||||
{
|
||||
_baseUrl = baseUrl.TrimEnd('/');
|
||||
if (httpClient is null)
|
||||
{
|
||||
_http = new HttpClient { Timeout = timeout ?? TimeSpan.FromSeconds(2) };
|
||||
_ownsClient = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_http = httpClient;
|
||||
if (timeout.HasValue) _http.Timeout = timeout.Value;
|
||||
_ownsClient = false;
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<string> SelectedObjectIds
|
||||
{
|
||||
get
|
||||
{
|
||||
using var doc = Get("/selection");
|
||||
var arr = doc.RootElement.GetProperty("selected_ids");
|
||||
var list = new List<string>(arr.GetArrayLength());
|
||||
foreach (var e in arr.EnumerateArray()) list.Add(e.GetString() ?? string.Empty);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
public CameraState Camera
|
||||
{
|
||||
get
|
||||
{
|
||||
using var doc = Get("/camera");
|
||||
var r = doc.RootElement;
|
||||
return new CameraState(
|
||||
ToArray(r.GetProperty("eye")),
|
||||
ToArray(r.GetProperty("target")),
|
||||
ToArray(r.GetProperty("up")),
|
||||
r.GetProperty("fov").GetDouble());
|
||||
}
|
||||
}
|
||||
|
||||
public SceneSummary Scene
|
||||
{
|
||||
get
|
||||
{
|
||||
using var doc = Get("/scene");
|
||||
var r = doc.RootElement;
|
||||
string? path = r.TryGetProperty("document_path", out var dp) && dp.ValueKind == JsonValueKind.String ? dp.GetString() : null;
|
||||
return new SceneSummary(r.GetProperty("object_count").GetInt32(), path);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRenderComplete
|
||||
{
|
||||
get
|
||||
{
|
||||
using var doc = Get("/render");
|
||||
return doc.RootElement.GetProperty("complete").GetBoolean();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsHealthy
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
using var doc = Get("/health");
|
||||
return doc.RootElement.TryGetProperty("status", out var s) && s.GetString() == "ok";
|
||||
}
|
||||
catch (EngineBridgeException) { return false; }
|
||||
}
|
||||
}
|
||||
|
||||
private JsonDocument Get(string endpoint)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var resp = _http.GetAsync(_baseUrl + endpoint).GetAwaiter().GetResult();
|
||||
if (!resp.IsSuccessStatusCode)
|
||||
throw new EngineBridgeException(endpoint, $"HTTP {(int)resp.StatusCode}");
|
||||
var body = resp.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||||
return JsonDocument.Parse(body);
|
||||
}
|
||||
catch (EngineBridgeException) { throw; }
|
||||
catch (TaskCanceledException ex)
|
||||
{
|
||||
throw new EngineBridgeException(endpoint, "timeout", ex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new EngineBridgeException(endpoint, ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static double[] ToArray(JsonElement e)
|
||||
{
|
||||
var arr = new double[e.GetArrayLength()];
|
||||
int i = 0;
|
||||
foreach (var item in e.EnumerateArray()) arr[i++] = item.GetDouble();
|
||||
return arr;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_ownsClient) _http.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<RootNamespace>Recordingtest.Hmeg.Bridge.Client</RootNamespace>
|
||||
<AssemblyName>Recordingtest.Hmeg.Bridge.Client</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Recordingtest.Hmeg.Catalog\Recordingtest.Hmeg.Catalog.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user