using AcDream.Plugin.Abstractions; namespace AcDream.Plugin.Tests.Fixtures.HostPlugin; /// /// Cross-host LA5 fixture. It deliberately takes the same path on graphical /// and no-window hosts: observe the capability, register UI, and subscribe to /// gameplay events. A headless registry must make the UI call harmless without /// retaining this instance in the default load context. /// public sealed class HostPlugin : IAcDreamPlugin { private IPluginHost? _host; private string? _assemblyDirectory; private bool _throwAfterRegistration; private int _entitiesSeen; public void Initialize(IPluginHost host) { _host = host ?? throw new ArgumentNullException(nameof(host)); _assemblyDirectory = Path.GetDirectoryName( typeof(HostPlugin).Assembly.Location); _throwAfterRegistration = File.Exists( Path.Combine(_assemblyDirectory!, "throw-after-register")); host.Log.Info($"fixture-initialized:hasUi={host.HasUi}"); } public void Enable() { IPluginHost host = _host ?? throw new InvalidOperationException("The fixture was not initialized."); host.Ui.AddMarkupPanel( Path.Combine(AppContext.BaseDirectory, "fixture-panel.xml"), this); host.Events.EntitySpawned += OnEntitySpawned; if (_throwAfterRegistration) { throw new InvalidOperationException( "fixture enable failed after registering UI and events"); } host.Log.Info( $"fixture-enabled:hasUi={host.HasUi}:entities={host.State.Entities.Count}"); } public void Disable() { IPluginHost? host = _host; if (host is null) return; if (_throwAfterRegistration) { throw new InvalidOperationException( "fixture disable intentionally refuses cleanup"); } host.Events.EntitySpawned -= OnEntitySpawned; host.Log.Info($"fixture-disabled:entitiesSeen={_entitiesSeen}"); _host = null; } private void OnEntitySpawned(WorldEntitySnapshot snapshot) { _entitiesSeen++; if (_throwAfterRegistration && _assemblyDirectory is not null) { File.AppendAllText( Path.Combine(_assemblyDirectory, "unexpected-callback"), $"{snapshot.Id}{Environment.NewLine}"); } } }