|
@@ -0,0 +1,593 @@
|
|
|
|
|
+# 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<T>`) - 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<T> IConsoleOutput │
|
|
|
|
|
+│ IInputProcessor OutputBuffer │
|
|
|
|
|
+│ IComponentFactory<T> 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<Exception, bool>? 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<T>.RunIteration()`
|
|
|
|
|
+
|
|
|
|
|
+```csharp
|
|
|
|
|
+public void RunIteration()
|
|
|
|
|
+{
|
|
|
|
|
+ _loop.Iteration(); // Delegates to ApplicationMainLoop
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**Purpose:** Thin wrapper that delegates to ApplicationMainLoop.
|
|
|
|
|
+
|
|
|
|
|
+#### C. The ApplicationMainLoop Iteration (`ApplicationMainLoop<T>.Iteration()`)
|
|
|
|
|
+
|
|
|
|
|
+**Location:** `ApplicationMainLoop<T>.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<T>.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<T>.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<T>` 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<T>
|
|
|
|
|
+ ├─ InputBuffer (ConcurrentQueue<T>)
|
|
|
|
|
+ ├─ InputProcessor (processes queue)
|
|
|
|
|
+ ├─ OutputBuffer (buffered drawing)
|
|
|
|
|
+ ├─ ConsoleOutput (writes to terminal)
|
|
|
|
|
+ ├─ TimedEvents (timeout callbacks)
|
|
|
|
|
+ ├─ WindowSizeMonitor (detects resizing)
|
|
|
|
|
+ └─ ToplevelTransitionManager (handles Top changes)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**Coordinated by:**
|
|
|
|
|
+```
|
|
|
|
|
+MainLoopCoordinator<T>
|
|
|
|
|
+ ├─ Input thread: IConsoleInput<T>.Run()
|
|
|
|
|
+ ├─ Main thread: ApplicationMainLoop<T>.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<T>.Run(token)
|
|
|
|
|
+ │
|
|
|
|
|
+ └─> while (!token.IsCancellationRequested)
|
|
|
|
|
+ │
|
|
|
|
|
+ ├─> Read from console (BLOCKING)
|
|
|
|
|
+ │ ├─ DotNet: Console.ReadKey()
|
|
|
|
|
+ │ ├─ Windows: ReadConsoleInput() API
|
|
|
|
|
+ │ └─ Unix: Terminal read APIs
|
|
|
|
|
+ │
|
|
|
|
|
+ ├─> Parse raw input to <T>
|
|
|
|
|
+ │
|
|
|
|
|
+ ├─> InputBuffer.Enqueue(input) ← Thread-safe handoff
|
|
|
|
|
+ │
|
|
|
|
|
+ └─> Loop back
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Summary of Discoveries
|
|
|
|
|
+
|
|
|
|
|
+### 1. Dual Architecture
|
|
|
|
|
+- Modern: `ApplicationMainLoop<T>` 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<T>`
|
|
|
|
|
+- 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<T>` - 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.
|