diff --git a/MAINLOOP_DEEP_DIVE.md.backup b/MAINLOOP_DEEP_DIVE.md.backup deleted file mode 100644 index 390562421..000000000 --- a/MAINLOOP_DEEP_DIVE.md.backup +++ /dev/null @@ -1,593 +0,0 @@ -# Deep Dive: Terminal.Gui MainLoop Architecture Analysis - -## Executive Summary - -After independently analyzing the Terminal.Gui codebase, I've discovered Terminal.Gui uses a **dual-architecture system** with modern and legacy mainloop implementations. The architecture separates concerns into distinct threads and phases, with the "MainLoop" actually referring to multiple different concepts depending on context. - -## Key Discovery: Two MainLoop Implementations - -Terminal.Gui has **TWO distinct MainLoop architectures**: - -1. **Modern Architecture** (`ApplicationMainLoop`) - Used by v2 drivers (DotNet, Windows, Unix) -2. **Legacy Architecture** (`MainLoop`) - Marked obsolete, used only by FakeDriver for backward compatibility - -This explains the confusion around terminology - "MainLoop" means different things in different contexts. - -## Architecture Overview - -### The Modern Architecture (v2) - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ APPLICATION LAYER │ -│ │ -│ Application.Run(toplevel) │ -│ ├─> Begin(toplevel) → RunState token │ -│ ├─> LOOP: while(toplevel.Running) │ -│ │ └─> Coordinator.RunIteration() │ -│ └─> End(runState) │ -│ │ -└──────────────────────────┬───────────────────────────────────────┘ - │ -┌──────────────────────────▼───────────────────────────────────────┐ -│ MAINLOOP COORDINATOR │ -│ │ -│ Two separate threads: │ -│ │ -│ ┌────────────────────┐ ┌─────────────────────────┐ │ -│ │ INPUT THREAD │ │ MAIN UI THREAD │ │ -│ │ │ │ │ │ -│ │ ConsoleInput.Run()│ │ ApplicationMainLoop │ │ -│ │ (blocking read) │ │ .Iteration() │ │ -│ │ │ │ │ │ │ -│ │ ▼ │ │ 1. ProcessQueue() │ │ -│ │ InputBuffer │───────>│ 2. Check for changes │ │ -│ │ (ConcurrentQueue) │ │ 3. Layout if needed │ │ -│ │ │ │ 4. Draw if needed │ │ -│ │ │ │ 5. RunTimers() │ │ -│ │ │ │ 6. Throttle │ │ -│ └────────────────────┘ └─────────────────────────┘ │ -│ │ -└──────────────────────────┬───────────────────────────────────────┘ - │ -┌──────────────────────────▼───────────────────────────────────────┐ -│ DRIVER LAYER │ -│ │ -│ IConsoleInput IConsoleOutput │ -│ IInputProcessor OutputBuffer │ -│ IComponentFactory IWindowSizeMonitor │ -│ │ -└──────────────────────────────────────────────────────────────────┘ -``` - -## Detailed Component Analysis - -### 1. Application.Run() - The Complete Lifecycle - -**Location:** `Terminal.Gui/App/Application.Run.cs` - -```csharp -public static void Run(Toplevel view, Func? errorHandler = null) -{ - RunState rs = Application.Begin(view); // Setup phase - - while (toplevel.Running) // The actual "run loop" - { - Coordinator.RunIteration(); // One iteration - } - - Application.End(rs); // Cleanup phase -} -``` - -**Purpose:** High-level convenience method that orchestrates the complete lifecycle. - -**Key insight:** `Run()` is a WRAPPER, not the loop itself. - -### 2. Application.Begin() - Setup Phase - -**What it does:** -1. Creates `RunState` token (handle for Begin/End pairing) -2. Pushes Toplevel onto `TopLevels` stack -3. Sets `Application.Top` to the new toplevel -4. Initializes and lays out the toplevel -5. Draws initial screen -6. Fires `NotifyNewRunState` event - -**Purpose:** Prepares a Toplevel for execution but does NOT start the loop. - -### 3. The Loop Layer - Where Things Get Interesting - -There are actually **THREE different "loop" concepts**: - -#### A. The Application Loop (`while (toplevel.Running)`) - -**Location:** `ApplicationImpl.Run()` - -```csharp -while (_topLevels.TryPeek(out found) && found == view && view.Running) -{ - _coordinator.RunIteration(); // Call coordinator -} -``` - -**Purpose:** The main control loop at the Application level. Continues until `RequestStop()` is called. - -**This is NOT a "RunLoop"** - it's the application's execution loop. - -#### B. The Coordinator Iteration (`Coordinator.RunIteration()`) - -**Location:** `MainLoopCoordinator.RunIteration()` - -```csharp -public void RunIteration() -{ - _loop.Iteration(); // Delegates to ApplicationMainLoop -} -``` - -**Purpose:** Thin wrapper that delegates to ApplicationMainLoop. - -#### C. The ApplicationMainLoop Iteration (`ApplicationMainLoop.Iteration()`) - -**Location:** `ApplicationMainLoop.Iteration()` and `.IterationImpl()` - -```csharp -public void Iteration() -{ - DateTime dt = Now(); - int timeAllowed = 1000 / MaximumIterationsPerSecond; - - IterationImpl(); // Do the actual work - - // Throttle to respect MaximumIterationsPerSecond - TimeSpan sleepFor = TimeSpan.FromMilliseconds(timeAllowed) - (Now() - dt); - if (sleepFor.Milliseconds > 0) - Task.Delay(sleepFor).Wait(); -} - -internal void IterationImpl() -{ - InputProcessor.ProcessQueue(); // 1. Process buffered input - ToplevelTransitionManager.RaiseReadyEventIfNeeded(); - ToplevelTransitionManager.HandleTopMaybeChanging(); - - // 2. Check if any views need layout or drawing - bool needsDrawOrLayout = AnySubViewsNeedDrawn(...); - bool sizeChanged = WindowSizeMonitor.Poll(); - - if (needsDrawOrLayout || sizeChanged) - { - Application.LayoutAndDraw(true); // 3. Layout and draw - Out.Write(OutputBuffer); // 4. Flush to console - } - - SetCursor(); // 5. Update cursor - TimedEvents.RunTimers(); // 6. Run timeout callbacks -} -``` - -**Purpose:** This is the REAL "main loop iteration" that does all the work. - -### 4. The Legacy MainLoop (for FakeDriver) - -**Location:** `Terminal.Gui/App/MainLoop/LegacyMainLoopDriver.cs` - -```csharp -[Obsolete("This class is for legacy FakeDriver compatibility only")] -public class MainLoop : IDisposable -{ - internal void RunIteration() - { - RunAnsiScheduler(); - MainLoopDriver?.Iteration(); - TimedEvents.RunTimers(); - } -} -``` - -**Purpose:** Backward compatibility with v1 FakeDriver. Marked obsolete. - -**Key difference:** This is what `Application.RunLoop()` calls when using legacy drivers. - -### 5. Application.RunLoop() - The Misnamed Method - -**Location:** `Application.Run.cs` - -```csharp -public static void RunLoop(RunState state) -{ - var firstIteration = true; - - for (state.Toplevel.Running = true; state.Toplevel?.Running == true;) - { - if (MainLoop is { }) - MainLoop.Running = true; - - if (EndAfterFirstIteration && !firstIteration) - return; - - firstIteration = RunIteration(ref state, firstIteration); - } - - if (MainLoop is { }) - MainLoop.Running = false; -} -``` - -**Purpose:** For legacy code that manually controls the loop. - -**Key insight:** This is a LEGACY compatibility method that calls either: -- Modern: The application loop (via `RunIteration`) -- Legacy: `MainLoop.RunIteration()` when legacy driver is used - -**The name "RunLoop" is misleading** because: -1. It doesn't "run" a loop - it IS a loop -2. It's actually the application-level control loop -3. The real "mainloop" work happens inside `RunIteration()` - -### 6. Application.RunIteration() - One Cycle - -**Location:** `Application.Run.cs` - -```csharp -public static bool RunIteration(ref RunState state, bool firstIteration = false) -{ - // If driver has events pending, do an iteration of the driver MainLoop - if (MainLoop is { Running: true } && MainLoop.EventsPending()) - { - if (firstIteration) - state.Toplevel.OnReady(); - - MainLoop.RunIteration(); // LEGACY path - Iteration?.Invoke(null, new()); - } - - firstIteration = false; - - if (Top is null) - return firstIteration; - - LayoutAndDraw(TopLevels.Any(v => v.NeedsLayout || v.NeedsDraw)); - - if (PositionCursor()) - Driver?.UpdateCursor(); - - return firstIteration; -} -``` - -**Purpose:** Process ONE iteration - either legacy or modern path. - -**Modern path:** Called by `ApplicationImpl.Run()` via coordinator -**Legacy path:** Calls `MainLoop.RunIteration()` (obsolete) - -### 7. Application.End() - Cleanup Phase - -**What it does:** -1. Fires `Toplevel.OnUnloaded()` event -2. Pops Toplevel from `TopLevels` stack -3. Fires `Toplevel.OnClosed()` event -4. Restores previous `Top` -5. Clears RunState.Toplevel -6. Disposes RunState token -7. Forces final layout/draw - -**Purpose:** Balances `Begin()` - cleans up after execution. - -## The Input Threading Model - -One of the most important aspects is the **two-thread architecture**: - -### Input Thread (Background) - -``` -IConsoleInput.Run(CancellationToken) - └─> Infinite loop: - 1. Read from console (BLOCKING) - 2. Parse raw input - 3. Push to InputBuffer (thread-safe ConcurrentQueue) - 4. Repeat until cancelled -``` - -**Purpose:** Dedicated thread for blocking console I/O operations. - -**Platform-specific implementations:** -- `NetInput` (DotNet driver) - Uses `Console.ReadKey()` -- `WindowsInput` - Uses Win32 API `ReadConsoleInput()` -- `UnixDriver.UnixInput` - Uses Unix terminal APIs - -### Main UI Thread (Foreground) - -``` -ApplicationMainLoop.IterationImpl() - └─> One iteration: - 1. InputProcessor.ProcessQueue() - └─> Drain InputBuffer - └─> Translate to Key/Mouse events - └─> Fire KeyDown/KeyUp/MouseEvent - 2. Check for view changes - 3. Layout if needed - 4. Draw if needed - 5. Update output buffer - 6. Flush to console - 7. Run timeout callbacks -``` - -**Purpose:** Main thread where all UI operations happen. - -**Thread safety:** `ConcurrentQueue` provides thread-safe handoff between threads. - -## The RequestStop Flow - -``` -User Action (e.g., Ctrl+Q) - │ - ▼ -InputProcessor processes key - │ - ▼ -Fires KeyDown event - │ - ▼ -Application.Keyboard handles QuitKey - │ - ▼ -Application.RequestStop(toplevel) - │ - ▼ -Sets toplevel.Running = false - │ - ▼ -while(toplevel.Running) loop exits - │ - ▼ -Application.End() cleans up -``` - -## Key Terminology Issues Discovered - -### Issue 1: "RunLoop" is Ambiguous - -The term "RunLoop" refers to: -1. **`Application.RunLoop()` method** - Application-level control loop (legacy compatibility) -2. **`MainLoop` class** - Legacy main loop driver (obsolete) -3. **`MainLoop.RunIteration()`** - Legacy iteration method -4. **The actual application loop** - `while(toplevel.Running)` -5. **The coordinator iteration** - `Coordinator.RunIteration()` -6. **The main loop iteration** - `ApplicationMainLoop.Iteration()` - -**Problem:** Six different concepts all contain "Run" or "Loop"! - -### Issue 2: "RunState" Doesn't Hold State - -`RunState` is actually a **token** or **handle** for the Begin/End pairing: - -```csharp -public class RunState : IDisposable -{ - public Toplevel Toplevel { get; internal set; } - // That's it - no "state" data! -} -``` - -**Purpose:** Ensures `End()` is called with the same Toplevel that `Begin()` set up. - -### Issue 3: "RunIteration" Has Two Paths - -`Application.RunIteration()` does different things depending on the driver: - -**Legacy path** (FakeDriver): -``` -RunIteration() - → MainLoop.EventsPending() - → MainLoop.RunIteration() - → LayoutAndDraw() -``` - -**Modern path** (Normal use): -``` -ApplicationImpl.Run() - → Coordinator.RunIteration() - → ApplicationMainLoop.Iteration() - → (includes LayoutAndDraw internally) -``` - -## The True MainLoop Architecture - -The **actual mainloop** in modern Terminal.Gui is: - -``` -ApplicationMainLoop - ├─ InputBuffer (ConcurrentQueue) - ├─ InputProcessor (processes queue) - ├─ OutputBuffer (buffered drawing) - ├─ ConsoleOutput (writes to terminal) - ├─ TimedEvents (timeout callbacks) - ├─ WindowSizeMonitor (detects resizing) - └─ ToplevelTransitionManager (handles Top changes) -``` - -**Coordinated by:** -``` -MainLoopCoordinator - ├─ Input thread: IConsoleInput.Run() - ├─ Main thread: ApplicationMainLoop.Iteration() - └─ Synchronization via ConcurrentQueue -``` - -**Exposed via:** -``` -ApplicationImpl - └─ IMainLoopCoordinator (hides implementation details) -``` - -## Complete Execution Flow Diagram - -``` -Application.Init() - ├─> Create Coordinator - ├─> Start input thread (IConsoleInput.Run) - ├─> Initialize ApplicationMainLoop - └─> Return to caller - -Application.Run(toplevel) - │ - ├─> Application.Begin(toplevel) - │ ├─> Create RunState token - │ ├─> Push to TopLevels stack - │ ├─> Initialize & layout toplevel - │ ├─> Initial draw - │ └─> Fire NotifyNewRunState - │ - ├─> while (toplevel.Running) ← APPLICATION CONTROL LOOP - │ │ - │ └─> Coordinator.RunIteration() - │ │ - │ └─> ApplicationMainLoop.Iteration() - │ │ - │ ├─> Throttle based on MaxIterationsPerSecond - │ │ - │ └─> IterationImpl(): - │ │ - │ ├─> 1. InputProcessor.ProcessQueue() - │ │ └─> Drain InputBuffer - │ │ └─> Fire Key/Mouse events - │ │ - │ ├─> 2. ToplevelTransitionManager events - │ │ - │ ├─> 3. Check if layout/draw needed - │ │ - │ ├─> 4. If needed: - │ │ ├─> Application.LayoutAndDraw() - │ │ └─> Out.Write(OutputBuffer) - │ │ - │ ├─> 5. SetCursor() - │ │ - │ └─> 6. TimedEvents.RunTimers() - │ - └─> Application.End(runState) - ├─> Fire OnUnloaded event - ├─> Pop from TopLevels stack - ├─> Fire OnClosed event - ├─> Restore previous Top - ├─> Clear & dispose RunState - └─> Final layout/draw - -Application.Shutdown() - ├─> Coordinator.Stop() - │ └─> Cancel input thread - └─> Cleanup all resources -``` - -## Parallel: Input Thread Flow - -``` -Input Thread (runs concurrently): - -IConsoleInput.Run(token) - │ - └─> while (!token.IsCancellationRequested) - │ - ├─> Read from console (BLOCKING) - │ ├─ DotNet: Console.ReadKey() - │ ├─ Windows: ReadConsoleInput() API - │ └─ Unix: Terminal read APIs - │ - ├─> Parse raw input to - │ - ├─> InputBuffer.Enqueue(input) ← Thread-safe handoff - │ - └─> Loop back -``` - -## Summary of Discoveries - -### 1. Dual Architecture -- Modern: `ApplicationMainLoop` with coordinator pattern -- Legacy: `MainLoop` class for FakeDriver only (obsolete) - -### 2. Two-Thread Model -- Input thread: Blocking console reads -- Main UI thread: Event processing, layout, drawing - -### 3. The "MainLoop" Misnomer -- There's no single "MainLoop" - it's distributed across: - - Application control loop (`while (toplevel.Running)`) - - Coordinator iteration - - ApplicationMainLoop iteration - - Legacy MainLoop (obsolete) - -### 4. RunState is a Token -- Not state data -- Just a handle to pair Begin/End -- Contains only the Toplevel reference - -### 5. Iteration Hierarchy -``` -Application.RunLoop() ← Legacy compatibility method (the LOOP) - └─> Application.RunIteration() ← One iteration (modern or legacy) - └─> Modern: Coordinator.RunIteration() - └─> ApplicationMainLoop.Iteration() - └─> IterationImpl() ← The REAL mainloop work -``` - -### 6. The Real MainLoop -- Is `ApplicationMainLoop` -- Runs on the main thread -- Does one iteration per call to `Iteration()` -- Is throttled to MaximumIterationsPerSecond -- Processes input, layouts, draws, runs timers - -## Terminology Recommendations Based on Analysis - -Based on this deep dive, here are the terminology issues: - -### Critical Issues - -1. **`RunState` should be `RunToken`** - - It's a token, not state - - Analogy: `CancellationToken` - -2. **`EndAfterFirstIteration` should be `StopAfterFirstIteration`** - - Controls loop stopping, not lifecycle cleanup - - "End" suggests `End()` method - - "Stop" aligns with `RequestStop()` - -### Less Critical (But Still Confusing) - -3. **`Application.RunLoop()` is misnamed** - - It IS a loop, not something that "runs" a loop - - Legacy compatibility method - - Better name would describe it's the application control loop - -4. **"MainLoop" is overloaded** - - `MainLoop` class (obsolete) - - `ApplicationMainLoop` class (modern) - - The loop concept itself - - Used in variable names throughout - -### What Works Well - -- `Begin` / `End` - Clear lifecycle pairing -- `RequestStop` - "Request" conveys non-blocking -- `RunIteration` - Clear it processes one iteration -- `Coordinator` - Good separation of concerns pattern -- `ApplicationMainLoop` - Descriptive class name - -## Conclusion - -Terminal.Gui's mainloop is actually a sophisticated multi-threaded architecture with: -- Separate input/UI threads -- Thread-safe queue for handoff -- Coordinator pattern for lifecycle management -- Throttled iterations for performance -- Clean separation between drivers and application logic - -The confusion stems from: -1. Legacy compatibility with FakeDriver -2. Overloaded "Run" terminology -3. Misleading "RunState" name -4. The term "MainLoop" meaning different things - -The modern architecture is well-designed, but the naming reflects the evolution from v1 to v2, carrying forward legacy terminology that no longer accurately describes the current implementation.