mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-27 00:07:58 +01:00
Fix MAINLOOP_DEEP_DIVE.md filename
Co-authored-by: tig <585482+tig@users.noreply.github.com>
This commit is contained in:
@@ -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<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.
|
||||
Reference in New Issue
Block a user