fix: defer Plant activation until document is quiescent
This commit is contained in:
@@ -139,6 +139,8 @@ internal sealed class FirstLimitedApplyAutomationCoordinator
|
|||||||
private CoordinatorState previousState = CoordinatorState.Created;
|
private CoordinatorState previousState = CoordinatorState.Created;
|
||||||
private bool documentHandlersSubscribed;
|
private bool documentHandlersSubscribed;
|
||||||
private bool lifecycleHandlersSubscribed;
|
private bool lifecycleHandlersSubscribed;
|
||||||
|
private bool fixtureDocumentActivated;
|
||||||
|
private bool projectActivationStarted;
|
||||||
private bool exitQueued;
|
private bool exitQueued;
|
||||||
private bool exitHandlerSubscribed;
|
private bool exitHandlerSubscribed;
|
||||||
private string? exceptionText;
|
private string? exceptionText;
|
||||||
@@ -155,24 +157,24 @@ internal sealed class FirstLimitedApplyAutomationCoordinator
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (string.IsNullOrWhiteSpace(config.OrthoDrawingPath)) throw new InvalidOperationException("AUTOMATION_ORTHO_PATH_REQUIRED");
|
if (string.IsNullOrWhiteSpace(config.OrthoDrawingPath)) throw new InvalidOperationException("AUTOMATION_ORTHO_PATH_REQUIRED");
|
||||||
transitions.Add("ProjectActivationStarted");
|
// A first startup Idle is sufficient to arm automation, but not to
|
||||||
var projectXml = Path.Combine(Path.GetDirectoryName(config.FixtureMarkerPath)!, "Project.xml");
|
// call Plant APIs. Establish an AutoCAD document first and wait for
|
||||||
var project = PlantProject.LoadProject(projectXml, false, string.Empty, string.Empty);
|
// its activation plus a quiescent callback before project activation.
|
||||||
PlantApplication.SetCurrentProject(project, true);
|
AcadApplication.DocumentManager.DocumentActivated += OnDocumentActivated;
|
||||||
transitions.Add("ProjectActivated");
|
AcadApplication.Idle += OnIdle;
|
||||||
|
lifecycleHandlersSubscribed = true;
|
||||||
if (!string.IsNullOrWhiteSpace(config.ModelDrawingPath)) automationOpenedDocuments.Add(AcadApplication.DocumentManager.Open(config.ModelDrawingPath, false));
|
if (!string.IsNullOrWhiteSpace(config.ModelDrawingPath)) automationOpenedDocuments.Add(AcadApplication.DocumentManager.Open(config.ModelDrawingPath, false));
|
||||||
transitions.Add("DocumentOpenStarted");
|
transitions.Add("DocumentOpenStarted");
|
||||||
|
RuntimeDiagnostics.Write("AutomationBeforeDocumentOpen", resultStatus: "PostStartupReady");
|
||||||
document = AcadApplication.DocumentManager.Open(config.OrthoDrawingPath, false);
|
document = AcadApplication.DocumentManager.Open(config.OrthoDrawingPath, false);
|
||||||
automationOpenedDocuments.Add(document);
|
automationOpenedDocuments.Add(document);
|
||||||
AcadApplication.DocumentManager.MdiActiveDocument = document;
|
AcadApplication.DocumentManager.MdiActiveDocument = document;
|
||||||
transitions.Add("DocumentOpened");
|
transitions.Add("DocumentOpened");
|
||||||
|
RuntimeDiagnostics.Write("AutomationAfterDocumentOpen", resultStatus: "WaitingForDocumentActivation", document: document);
|
||||||
document.CommandEnded += OnCommandEnded;
|
document.CommandEnded += OnCommandEnded;
|
||||||
document.CommandFailed += OnCommandFailed;
|
document.CommandFailed += OnCommandFailed;
|
||||||
document.CommandCancelled += OnCommandCancelled;
|
document.CommandCancelled += OnCommandCancelled;
|
||||||
AcadApplication.DocumentManager.DocumentActivated += OnDocumentActivated;
|
|
||||||
AcadApplication.Idle += OnIdle;
|
|
||||||
documentHandlersSubscribed = true;
|
documentHandlersSubscribed = true;
|
||||||
lifecycleHandlersSubscribed = true;
|
|
||||||
Transition(CoordinatorState.Running, "AutomationRunning");
|
Transition(CoordinatorState.Running, "AutomationRunning");
|
||||||
}
|
}
|
||||||
catch (System.Exception ex) { FailFromCallback("Start", ex, "AUTOMATION_START_FAILED"); }
|
catch (System.Exception ex) { FailFromCallback("Start", ex, "AUTOMATION_START_FAILED"); }
|
||||||
@@ -188,23 +190,35 @@ internal sealed class FirstLimitedApplyAutomationCoordinator
|
|||||||
RequestControlledExit(idleSafe: true);
|
RequestControlledExit(idleSafe: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnIdle(object? sender, EventArgs e) => RunCallback("Idle", BeginContextVerification);
|
private void OnIdle(object? sender, EventArgs e) => RunCallback("Idle", TryActivateProjectAndVerifyContext);
|
||||||
private void OnDocumentActivated(object sender, DocumentCollectionEventArgs e)
|
private void OnDocumentActivated(object sender, DocumentCollectionEventArgs e)
|
||||||
{
|
{
|
||||||
RunCallback("DocumentActivated", () =>
|
RunCallback("DocumentActivated", () =>
|
||||||
{
|
{
|
||||||
if (document is null || e.Document is null) return;
|
if (document is null || e.Document is null) return;
|
||||||
if (string.Equals(e.Document.Name, document.Name, StringComparison.OrdinalIgnoreCase)) BeginContextVerification();
|
if (!string.Equals(e.Document.Name, document.Name, StringComparison.OrdinalIgnoreCase)) return;
|
||||||
|
fixtureDocumentActivated = true;
|
||||||
|
transitions.Add("FixtureDocumentActivated");
|
||||||
|
TryActivateProjectAndVerifyContext();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
private void BeginContextVerification()
|
private void TryActivateProjectAndVerifyContext()
|
||||||
{
|
{
|
||||||
if (IsTerminal || document is null) return;
|
if (IsTerminal || document is null || !fixtureDocumentActivated || projectActivationStarted) return;
|
||||||
|
if (!AcadApplication.IsQuiescent) return;
|
||||||
|
projectActivationStarted = true;
|
||||||
AcadApplication.Idle -= OnIdle;
|
AcadApplication.Idle -= OnIdle;
|
||||||
AcadApplication.DocumentManager.DocumentActivated -= OnDocumentActivated;
|
AcadApplication.DocumentManager.DocumentActivated -= OnDocumentActivated;
|
||||||
lifecycleHandlersSubscribed = false;
|
lifecycleHandlersSubscribed = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
transitions.Add("ProjectActivationStarted");
|
||||||
|
RuntimeDiagnostics.Write("AutomationBeforeProjectActivation", resultStatus: "DocumentActivatedAndQuiescent", document: document);
|
||||||
|
var projectXml = Path.Combine(Path.GetDirectoryName(config!.FixtureMarkerPath)!, "Project.xml");
|
||||||
|
var project = PlantProject.LoadProject(projectXml, false, string.Empty, string.Empty);
|
||||||
|
PlantApplication.SetCurrentProject(project, true);
|
||||||
|
transitions.Add("ProjectActivated");
|
||||||
|
RuntimeDiagnostics.Write("AutomationAfterProjectActivation", resultStatus: "ProjectAvailable", document: document);
|
||||||
transitions.Add("ContextVerificationStarted");
|
transitions.Add("ContextVerificationStarted");
|
||||||
var context = new PlantRuntimeContextReader().ReadCurrentContext(document);
|
var context = new PlantRuntimeContextReader().ReadCurrentContext(document);
|
||||||
if (!context.HasPlantProject || !context.IsOrthoDrawing || context.Viewports.Count != 1) { Complete(false, $"AUTOMATION_ORTHO_CONTEXT_INVALID:P={context.HasPlantProject};O={context.IsOrthoDrawing};V={context.Viewports.Count}"); return; }
|
if (!context.HasPlantProject || !context.IsOrthoDrawing || context.Viewports.Count != 1) { Complete(false, $"AUTOMATION_ORTHO_CONTEXT_INVALID:P={context.HasPlantProject};O={context.IsOrthoDrawing};V={context.Viewports.Count}"); return; }
|
||||||
|
|||||||
@@ -82,6 +82,22 @@ public sealed class FoundationTests
|
|||||||
Assert.Contains("PostStartupReady", source);
|
Assert.Contains("PostStartupReady", source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact] public void AutomationProjectActivation_WaitsForFixtureDocumentAndQuiescence()
|
||||||
|
{
|
||||||
|
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
|
||||||
|
var activation = source[source.IndexOf("private void TryActivateProjectAndVerifyContext", StringComparison.Ordinal)..];
|
||||||
|
Assert.Contains("!fixtureDocumentActivated", activation);
|
||||||
|
Assert.Contains("!AcadApplication.IsQuiescent", activation);
|
||||||
|
Assert.True(activation.IndexOf("PlantProject.LoadProject", StringComparison.Ordinal) > activation.IndexOf("AutomationBeforeProjectActivation", StringComparison.Ordinal));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact] public void AutomationDocumentActivation_IsSubscribedBeforeFixtureOpen()
|
||||||
|
{
|
||||||
|
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
|
||||||
|
Assert.True(source.IndexOf("DocumentActivated += OnDocumentActivated", StringComparison.Ordinal) < source.IndexOf("DocumentManager.Open(config.OrthoDrawingPath", StringComparison.Ordinal));
|
||||||
|
Assert.Contains("FixtureDocumentActivated", source);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact] public void AutomationControlledExit_QueuesNativeQuitAfterPublicCommand()
|
[Fact] public void AutomationControlledExit_QueuesNativeQuitAfterPublicCommand()
|
||||||
{
|
{
|
||||||
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
|
var source = File.ReadAllText(Path.Combine(ProjectRoot, "src", "PRecAnnot.Plant3D2026", "FirstLimitedApplyAutomation.cs"));
|
||||||
|
|||||||
Reference in New Issue
Block a user