using Editor.PluginInterface; using HmEG; using Recordingtest.Bridge; using Recordingtest.Hmeg.Bridge; namespace Recordingtest.Sut.EgBim.PluginHost; /// /// MEF/PluginLoader-discovered plugin. Inherits the SUT's EditorPlugin /// abstract base (which itself implements HmEG.IPlugin), and on construction /// boots a localhost HTTP bridge that exposes HmEG state to recordingtest. /// public sealed class HmEgBridgePlugin : EditorPlugin, IDisposable { private BridgeHttpServer? _server; public HmEgBridgePlugin() { StartBridge(); } public override string Name => "Recordingtest.Sut.EgBim.PluginHost"; public override string Description => "recordingtest engine-bridge v3 (HTTP sidecar)"; protected override void Initialize() { // Construction already started the bridge; Initialize is a no-op safeguard. } private void StartBridge() { try { var port = PortResolver.Resolve(); var provider = BuildProvider(); var router = new StateRouter(provider, port); _server = new BridgeHttpServer(router, port); _server.Start(); } catch { // never throw out of plugin construction; SUT must remain stable. } } /// /// Choose the best available : /// /// 1. HmegDirectStateProvider — HmEG-aware tier, calls into HmEG /// public API. Reusable across any HmEG-hosting WPF app. Active /// space/viewport handles are resolved via lambdas; the EgBim /// app-specific entry-point lookup lives in this method only. /// 2. ReflectionEngineStateProvider — fallback that uses /// IAppManagerAccessor reflection on Editor.AppManager.AppManager. /// Used when the direct provider can't resolve any handle. /// /// Both providers are wrapped to never throw; the SUT must remain stable. /// private IEngineStateProvider BuildProvider() { // EG-BIM Modeler entry points (resolved via EditorPlugin base): // RootSpace = AppManager.ViewportManager.RootSpace // View (EGViewport : HmEGViewport) — plugin-injected active viewport // AppManager.FileManager.CurrentFile — document path on disk // // Each lambda is wrapped in try/catch so plugin construction never // throws even if the host state is in flight at boot time. Func spaceProvider = () => { try { return RootSpace; } catch { return null; } }; Func viewportProvider = () => { // EditorPlugin.View is only populated when the plugin is actually // Run() by a user command; our bridge plugin lives as a long- // running HTTP server and never runs a trigger, so View stays // null. Instead pull the active viewport from the global // ViewportManager, preferring FocusedViewport, then falling back // to any registered viewport. EGViewport implements HmEGViewport. try { var vm = AppManager?.ViewportManager; if (vm is null) return null; var focused = vm.FocusedViewport; if (focused is not null) return focused; var any = vm.Viewports; if (any is null) return null; foreach (var v in any) { if (v is not null) return v; } return null; } catch { return null; } }; Func documentPathProvider = () => { try { var path = AppManager?.FileManager?.CurrentFile; return string.IsNullOrEmpty(path) ? null : path; } catch { return null; } }; var direct = new HmegDirectStateProvider(spaceProvider, viewportProvider, documentPathProvider); var fallback = new ReflectionEngineStateProvider(this); return new ChainedEngineStateProvider(direct, fallback); } public void Dispose() { try { _server?.Dispose(); } catch { } _server = null; } }