mirror of
https://github.com/vczh-libraries/Release.git
synced 2026-03-23 07:42:52 +08:00
Update prompt
This commit is contained in:
5
.github/Agent/README.md
vendored
5
.github/Agent/README.md
vendored
@@ -5,7 +5,10 @@ A TypeScript-based application for interacting with GitHub Copilot models, featu
|
|||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli) must be installed and authenticated
|
- [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli) must be installed and authenticated
|
||||||
- Node.js 18+ and Yarn
|
- Node.js 24+ and Yarn
|
||||||
|
- Powershell 7
|
||||||
|
|
||||||
|
**IMPORTANT**: You'd better upgrade both copilot cli and sdk to the latest version before using it.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
|
|||||||
7
.github/Guidelines/Building.md
vendored
7
.github/Guidelines/Building.md
vendored
@@ -20,6 +20,13 @@ cd SOLUTION-ROOT
|
|||||||
& REPO-ROOT\.github\Scripts\copilotBuild.ps1
|
& REPO-ROOT\.github\Scripts\copilotBuild.ps1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Ensure Target Configuration
|
||||||
|
|
||||||
|
`-Configuration` and `-Platform` arguments are available to specify the target configuration:
|
||||||
|
- `-Configuration` could be `Debug` (default) or `Release`.
|
||||||
|
- `-Platform` could be `x64` (default) or `Win32`
|
||||||
|
- Pick the default option (omit both arguments) when there is no specific requirements.
|
||||||
|
|
||||||
## The Correct Way to Read Compiler Result
|
## The Correct Way to Read Compiler Result
|
||||||
|
|
||||||
- The only source of trust is the raw output of the compiler.
|
- The only source of trust is the raw output of the compiler.
|
||||||
|
|||||||
2
.github/Guidelines/Debugging.md
vendored
2
.github/Guidelines/Debugging.md
vendored
@@ -6,7 +6,7 @@ CDB accepts the exact same commands as WinDBG.
|
|||||||
|
|
||||||
### Start a Debugger
|
### Start a Debugger
|
||||||
|
|
||||||
Read `REPO-ROOT/.github/Project.md` to understand the solution folder and the unit test project name you are working with.
|
Read `REPO-ROOT/Project.md` to understand the solution folder and the unit test project name you are working with.
|
||||||
Additional information could be found in THE FIRST LINE in `REPO-ROOT/.github/Scripts/Execute.log`.
|
Additional information could be found in THE FIRST LINE in `REPO-ROOT/.github/Scripts/Execute.log`.
|
||||||
Execute the following PowerShell commands:
|
Execute the following PowerShell commands:
|
||||||
|
|
||||||
|
|||||||
28
.github/Guidelines/Running-CLI.md
vendored
28
.github/Guidelines/Running-CLI.md
vendored
@@ -1,3 +1,29 @@
|
|||||||
# Running a CLI Application Project
|
# Running a CLI Application Project
|
||||||
|
|
||||||
(to edit ...)
|
- Only run `copilotExecute.ps1` to run a unit test project.
|
||||||
|
- DO NOT call executables or scripts yourself.
|
||||||
|
|
||||||
|
## Executing copilotExecute.ps1
|
||||||
|
|
||||||
|
`PROJECT-NAME` is the name of the project.
|
||||||
|
|
||||||
|
Before testing, ensure the debugger has stopped.
|
||||||
|
If there is any error message, it means the debugger is not alive, it is good.
|
||||||
|
|
||||||
|
```
|
||||||
|
& REPO-ROOT\.github\Scripts\copilotDebug_Stop.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
And then run test cases in `SOLUTION-ROOT\PROJECT-NAME\PROJECT-NAME.vcxproj`:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd SOLUTION-ROOT
|
||||||
|
& REPO-ROOT\.github\Scripts\copilotExecute.ps1 -Mode UnitTest -Executable PROJECT-NAME
|
||||||
|
```
|
||||||
|
|
||||||
|
`-Configuration` and `-Platform` arguments are available to specify the target configuration:
|
||||||
|
- When both arguments are omitted, the last build configuration will be picked up.
|
||||||
|
- Both arguments should be omitted or not omitted at the same time.
|
||||||
|
- `-Configuration` could be `Debug` (default) or `Release`.
|
||||||
|
- `-Platform` could be `x64` (default) or `Win32`
|
||||||
|
- Pick the default option (omit both arguments) when there is no specific requirements.
|
||||||
|
|||||||
9
.github/Guidelines/Running-UnitTest.md
vendored
9
.github/Guidelines/Running-UnitTest.md
vendored
@@ -18,9 +18,16 @@ And then run test cases in `SOLUTION-ROOT\PROJECT-NAME\PROJECT-NAME.vcxproj`:
|
|||||||
|
|
||||||
```
|
```
|
||||||
cd SOLUTION-ROOT
|
cd SOLUTION-ROOT
|
||||||
& REPO-ROOT\.github\Scripts\copilotExecute.ps1 -Executable PROJECT-NAME
|
& REPO-ROOT\.github\Scripts\copilotExecute.ps1 -Mode UnitTest -Executable PROJECT-NAME
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`-Configuration` and `-Platform` arguments are available to specify the target configuration:
|
||||||
|
- When both arguments are omitted, the last build configuration will be picked up.
|
||||||
|
- Both arguments should be omitted or not omitted at the same time.
|
||||||
|
- `-Configuration` could be `Debug` (default) or `Release`.
|
||||||
|
- `-Platform` could be `x64` (default) or `Win32`
|
||||||
|
- Pick the default option (omit both arguments) when there is no specific requirements.
|
||||||
|
|
||||||
## Ensure Expected Test Files are Selected
|
## Ensure Expected Test Files are Selected
|
||||||
|
|
||||||
Test cases are organized in multiple test files.
|
Test cases are organized in multiple test files.
|
||||||
|
|||||||
58
.github/KnowledgeBase/Index.md
vendored
58
.github/KnowledgeBase/Index.md
vendored
@@ -549,6 +549,24 @@ It provides a comprehensive testing framework, XML-to-C++ compilation, and integ
|
|||||||
|
|
||||||
### Choosing APIs
|
### Choosing APIs
|
||||||
|
|
||||||
|
#### Remote Protocol Unit Test Framework
|
||||||
|
|
||||||
|
Testing GacUI applications without real OS windows or rendering, using the remote protocol architecture with a mock renderer (`UnitTestRemoteProtocol`) that captures rendering snapshots and simulates user input.
|
||||||
|
|
||||||
|
- Use `GacUIUnitTest_Initialize` and `GacUIUnitTest_Finalize` for global test executable setup and teardown.
|
||||||
|
- Use `GacUIUnitTest_SetGuiMainProxy` to register frame-based test callbacks per test case.
|
||||||
|
- Use `GacUIUnitTest_LinkGuiMainProxy` for decorator-style proxy chaining to compose setup layers.
|
||||||
|
- Use `GacUIUnitTest_StartFast_WithResourceAsText<Theme>` for the most common entry point that compiles XML resources, registers themes, creates windows, and runs the application.
|
||||||
|
- Use `GacUIUnitTest_Start` and `GacUIUnitTest_StartAsync` for synchronous and async protocol stack tests.
|
||||||
|
- Use `OnNextIdleFrame(name, callback)` on `UnitTestRemoteProtocol` to register frame callbacks; the name describes the rendering result, not the upcoming action.
|
||||||
|
- Use `LocationOf(controlOrComposition)` to compute absolute screen coordinates for input simulation.
|
||||||
|
- Use `LClick`, `RClick`, `MClick`, `LDBClick`, `MouseMove` for mouse input simulation.
|
||||||
|
- Use `KeyPress`, `KeyDown`, `KeyUp`, `TypeString` for keyboard input simulation.
|
||||||
|
- Use `TryFindObjectByName<T>(window, name)` to look up named controls from GacUI XML resources.
|
||||||
|
- Use `GetApplication()->InvokeInMainThread` to defer IO actions that would trigger blocking functions like `ShowDialog`.
|
||||||
|
|
||||||
|
[API Explanation](./KB_GacUI_RemoteProtocolUnitTestFramework.md)
|
||||||
|
|
||||||
### Design Explanation
|
### Design Explanation
|
||||||
|
|
||||||
#### Platform Initialization and Multi-Platform Architecture
|
#### Platform Initialization and Multi-Platform Architecture
|
||||||
@@ -637,6 +655,46 @@ It provides a comprehensive testing framework, XML-to-C++ compilation, and integ
|
|||||||
|
|
||||||
[Design Explanation](./KB_GacUI_Design_AddingNewControl.md)
|
[Design Explanation](./KB_GacUI_Design_AddingNewControl.md)
|
||||||
|
|
||||||
|
#### Hosted Mode Window Management
|
||||||
|
|
||||||
|
- Hosted mode runs the entire GUI application inside a single OS native window, virtualizing all sub-windows, dialogs, popups, and menus as graphics within that window.
|
||||||
|
- `GuiHostedController` is the central class wrapping a real native controller, implementing its own window manager, input dispatching, and service delegation while replacing the global `INativeController`.
|
||||||
|
- The window manager template `hosted_window_manager::WindowManager<T>` maintains z-ordered lists (ordinary and top-most), a parent-child tree, and handles activation, focus, hit testing, dragging, and resizing.
|
||||||
|
- `GuiHostedWindow` implements `INativeWindow` with a proxy pattern: `PlaceholderProxy` for unassigned windows, `MainProxy` delegating to the real native window, and `NonMainProxy` operating purely through the window manager.
|
||||||
|
- Input dispatching uses `HandleMouseCallback` templates with pluggable PreAction/GetSelectedWindow/PostAction strategies, and `HandleKeyboardCallback` routes to the active window.
|
||||||
|
- The rendering pipeline renders all hosted windows in a single begin/end session via `StartHostedRendering`/`StopHostedRendering`, with per-window offset via `GetRenderingOffset()`.
|
||||||
|
- Remote mode inherently requires hosted mode, with `GuiHostedController` wrapping `GuiRemoteController` in the same architecture.
|
||||||
|
|
||||||
|
[Design Explanation](./KB_GacUI_Design_HostedModeWindowManagement.md)
|
||||||
|
|
||||||
|
#### Remote Protocol Core Architecture
|
||||||
|
|
||||||
|
- Remote protocol mode separates GacUI into a core side (application logic) and a renderer side (rendering and OS services), communicating through `IGuiRemoteProtocol`.
|
||||||
|
- Messages flow core → renderer via `IGuiRemoteProtocolMessages`; events and responses flow renderer → core via `IGuiRemoteProtocolEvents`.
|
||||||
|
- `GuiRemoteMessages` provides synchronous batched request-response with auto-incrementing IDs and blocking `Submit()`.
|
||||||
|
- `GuiRemoteController` implements `INativeController` and all sub-services as virtual stubs: single window only, intentionally null clipboard/dialog services, synchronous key state queries.
|
||||||
|
- Connection lifecycle: `SetupRemoteNativeController` creates a layered stack (`GuiRemoteController` → `GuiHostedController` → resource managers), with connect/disconnect/reconnection handling that re-sends all window state.
|
||||||
|
- Rendering pipeline: element lifecycle via ID allocation, diff-based element updates, frame rendering flow (`StartRenderingOnNativeWindow` → traversal → `StopRenderingOnNativeWindow`), and measurement feedback loop (font heights, min sizes, image metadata, inline object bounds).
|
||||||
|
- `GuiRemoteGraphicsParagraph` handles rich text with run property system, incremental diff synchronization, delegated layout queries, and caret bounds caching.
|
||||||
|
- DOM diff layer (`GuiRemoteProtocolDomDiffConverter`) converts per-frame command streams into diffed tree structures.
|
||||||
|
- Protocol combinator and filter layers enable composable transformations and traffic optimization via `[@DropRepeat]`/`[@DropConsecutive]` annotations.
|
||||||
|
- Channel layer (`IGuiRemoteProtocolChannel`, `GuiRemoteProtocolAsyncChannelSerializer`) supports real remote deployment with async IO on a separate thread.
|
||||||
|
|
||||||
|
[Design Explanation](./KB_GacUI_Design_RemoteProtocolCoreArchitecture.md)
|
||||||
|
|
||||||
|
#### Remote Protocol Renderer and Serialization
|
||||||
|
|
||||||
|
- `GuiRemoteRendererSingle` is the renderer-side implementation that bridges `IGuiRemoteProtocol` to a real native window with actual graphics rendering, relying on an existing platform provider (e.g., Windows Direct2D).
|
||||||
|
- It implements `IGuiRemoteProtocol` to receive protocol messages and translates them into native element operations, and implements `INativeWindowListener`/`INativeControllerListener` to forward OS events back as protocol events.
|
||||||
|
- Rendering pipeline: receives `RequestRendererBeginRendering` with `OrdinaryElementDescVariant` updates, applies them to real graphics elements, renders the DOM tree in `GlobalTimer()`, and returns measurement feedback via `RespondRendererEndRendering`.
|
||||||
|
- Event forwarding coalesces high-frequency events (mouse move, wheel, key auto-repeat) and sends discrete events immediately; hit testing is performed locally by traversing the rendering DOM tree.
|
||||||
|
- Layered channel architecture for protocol serialization: `IGuiRemoteProtocol` ↔ `JsonObject` (via `GuiRemoteProtocolFromJsonChannel`/`GuiRemoteJsonChannelFromProtocol`) ↔ `WString` (via `JsonToStringSerializer`) ↔ user-implemented transport.
|
||||||
|
- JSON envelope format with `semantic`, `id`, `name`, `arguments` fields; protocol types code-generated from `Protocol/*.txt` with `JsonHelper<T>` specializations.
|
||||||
|
- `GuiRemoteProtocolAsyncChannelSerializer` provides thread separation (channel thread for IO, UI thread for application logic) with queued event delivery and connection-safe request matching.
|
||||||
|
- Demo project pair (`RemotingTest_Core` and `RemotingTest_Rendering_Win32`) demonstrates full protocol stack assembly for both core and renderer sides with named-pipe/HTTP transport.
|
||||||
|
|
||||||
|
[Design Explanation](./KB_GacUI_Design_RemoteProtocolRendererAndSerialization.md)
|
||||||
|
|
||||||
## Experiences and Learnings
|
## Experiences and Learnings
|
||||||
|
|
||||||
# Copy of Online Manual
|
# Copy of Online Manual
|
||||||
|
|||||||
307
.github/KnowledgeBase/KB_GacUI_Design_HostedModeWindowManagement.md
vendored
Normal file
307
.github/KnowledgeBase/KB_GacUI_Design_HostedModeWindowManagement.md
vendored
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
Hosted Mode Window Management
|
||||||
|
|
||||||
|
Hosted mode is a GacUI operating mode in which the entire GUI application runs inside a single OS native window. Instead of creating one native window per `GuiWindow`, all sub-windows, dialogs, popups, and menus are rendered as graphics within the sole native window. The hosted mode implements its own window manager to handle z-ordering, hit testing, focus, activation, dragging, resizing, and input dispatching — functions normally provided by the OS.
|
||||||
|
|
||||||
|
The implementation lives in `Source/PlatformProviders/Hosted/` with these files:
|
||||||
|
- `GuiHostedController.h/.cpp` — the core controller wrapping a native controller
|
||||||
|
- `GuiHostedWindow.h/.cpp` — virtual window implementing `INativeWindow`
|
||||||
|
- `GuiHostedWindowManager.h` — template-based window management algorithm
|
||||||
|
- `GuiHostedGraphics.h/.cpp` — graphics resource manager wrapper
|
||||||
|
- `GuiHostedApplication.h/.cpp` — global hosted application accessor
|
||||||
|
- `GuiHostedWindowProxy_Main.cpp` — proxy for the main window
|
||||||
|
- `GuiHostedWindowProxy_NonMain.cpp` — proxy for non-main windows
|
||||||
|
- `GuiHostedWindowProxy_Placeholder.cpp` — proxy for windows before role assignment
|
||||||
|
|
||||||
|
## Entry Points and Activation
|
||||||
|
|
||||||
|
Hosted mode is activated through specific setup functions:
|
||||||
|
- `SetupHostedWindowsDirect2DRenderer()` calls `SetupWindowsDirect2DRendererInternal(true, false)`
|
||||||
|
- `SetupHostedWindowsGDIRenderer()` calls `SetupWindowsGDIRendererInternal(true, false)`
|
||||||
|
- `SetupRemoteNativeController(protocol)` creates a `GuiHostedController` wrapping `GuiRemoteController` — remote mode inherently requires hosted mode
|
||||||
|
|
||||||
|
The internal setup functions (`SetupWindowsDirect2DRendererInternal`, `SetupWindowsGDIRendererInternal`) take a `bool hosted` parameter. When `hosted` is true:
|
||||||
|
1. `StartWindowsNativeController(hInstance)` creates the real native controller
|
||||||
|
2. A `GuiHostedController` is created wrapping the native controller
|
||||||
|
3. `SetNativeController(hostedController)` replaces the global native controller
|
||||||
|
4. `SetHostedApplication(hostedController->GetHostedApplication())` sets the hosted application global
|
||||||
|
5. `RendererMainDirect2D` / `RendererMainGDI` is called with the hosted controller
|
||||||
|
6. Inside the renderer main, `GuiHostedGraphicsResourceManager` wraps the native resource manager
|
||||||
|
7. `hostedController->Initialize()` creates the single native window
|
||||||
|
8. `GuiApplicationMain()` runs the application
|
||||||
|
9. `hostedController->Finalize()` tears down
|
||||||
|
|
||||||
|
For remote mode in `SetupRemoteNativeController`:
|
||||||
|
1. `GuiRemoteController` wraps the protocol
|
||||||
|
2. `GuiHostedController` wraps the `GuiRemoteController`
|
||||||
|
3. Both remote and hosted resource managers are created in a chain
|
||||||
|
4. The same initialize/run/finalize sequence applies
|
||||||
|
|
||||||
|
## GuiHostedController Architecture
|
||||||
|
|
||||||
|
`GuiHostedController` is the central class of hosted mode. It inherits from:
|
||||||
|
- `Object` — the GacUI base class
|
||||||
|
- `hosted_window_manager::WindowManager<GuiHostedWindow*>` — the window management algorithm
|
||||||
|
- `INativeWindowListener` — to listen to events on the single real native window
|
||||||
|
- `INativeControllerListener` — to receive global timer, clipboard, and shortcut events
|
||||||
|
- `INativeController` — to replace the real native controller for the rest of GacUI
|
||||||
|
- `INativeAsyncService` — to redirect async operations through the real native controller
|
||||||
|
- `INativeScreenService` / `INativeScreen` — to provide a virtual single screen
|
||||||
|
- `INativeWindowService` — to create, destroy, and manage virtual windows
|
||||||
|
- `IGuiHostedApplication` — to expose the native window host
|
||||||
|
|
||||||
|
### Service Delegation
|
||||||
|
|
||||||
|
The controller delegates most services to the underlying native controller:
|
||||||
|
- `ResourceService()` → `nativeController->ResourceService()`
|
||||||
|
- `ClipboardService()` → `nativeController->ClipboardService()`
|
||||||
|
- `ImageService()` → `nativeController->ImageService()`
|
||||||
|
- `InputService()` → `nativeController->InputService()`
|
||||||
|
- `DialogService()` → returns `nullptr` (replaced by `FakeDialogServiceBase`)
|
||||||
|
|
||||||
|
Services it implements itself:
|
||||||
|
- `CallbackService()` → local `SharedCallbackService` instance
|
||||||
|
- `AsyncService()` → itself (delegates internally to native async service, translates window references)
|
||||||
|
- `ScreenService()` → itself (provides a single virtual screen)
|
||||||
|
- `WindowService()` → itself (manages virtual hosted windows)
|
||||||
|
|
||||||
|
### Virtual Screen
|
||||||
|
|
||||||
|
`GuiHostedController` acts as both `INativeScreenService` and `INativeScreen`:
|
||||||
|
- `GetScreenCount()` returns 1
|
||||||
|
- `GetScreen()` returns itself
|
||||||
|
- Screen bounds are derived from the native window's client size
|
||||||
|
- DPI scaling is forwarded from the real screen
|
||||||
|
|
||||||
|
### Native Window Lifecycle
|
||||||
|
|
||||||
|
`Initialize()` creates one real native OS window via `nativeController->WindowService()->CreateNativeWindow()` and installs itself as `INativeWindowListener` on it.
|
||||||
|
|
||||||
|
`Finalize()` destroys the native window and uninstalls listeners.
|
||||||
|
|
||||||
|
### Window Frame Configuration
|
||||||
|
|
||||||
|
`GuiHostedController` provides two frame configs:
|
||||||
|
- `GetMainWindowFrameConfig()` — delegates to the native controller (the main window gets the real OS frame)
|
||||||
|
- `GetNonMainWindowFrameConfig()` — returns a static config with `MaximizedBoxOption = AlwaysFalse`, `MinimizedBoxOption = AlwaysFalse`, `CustomFrameEnabled = AlwaysTrue` (non-main windows are always custom-frame rendered)
|
||||||
|
|
||||||
|
## Window Manager Template
|
||||||
|
|
||||||
|
### WindowManager<T> (GuiHostedWindowManager.h)
|
||||||
|
|
||||||
|
The core window management algorithm is template class `hosted_window_manager::WindowManager<T>` parameterized by window ID type. It maintains:
|
||||||
|
- `registeredWindows` — dictionary of all registered windows
|
||||||
|
- `ordinaryWindowsInOrder` — z-ordered list of normal windows (front to back)
|
||||||
|
- `topMostedWindowsInOrder` — z-ordered list of top-most windows (front to back)
|
||||||
|
- `mainWindow` — the root window
|
||||||
|
- `activeWindow` — currently focused window
|
||||||
|
- `needRefresh` — dirty flag for rendering
|
||||||
|
|
||||||
|
Pure virtual callbacks used by `GuiHostedController`:
|
||||||
|
- `OnOpened` / `OnClosed` — fire `Opened` / `Closed` events on hosted window listeners
|
||||||
|
- `OnEnabled` / `OnDisabled` — fire `Enabled` / `Disabled` events
|
||||||
|
- `OnGotFocus` / `OnLostFocus` — fire `GotFocus` / `LostFocus` events
|
||||||
|
- `OnActivated` / `OnDeactivated` — fire `Activated` / `Deactivated` events
|
||||||
|
|
||||||
|
### Window<T>
|
||||||
|
|
||||||
|
Each `Window<T>` holds:
|
||||||
|
- `parent` — parent window pointer forming a tree structure
|
||||||
|
- `children` — child windows list
|
||||||
|
- `bounds` — position and size in the parent's client coordinate space
|
||||||
|
- `topMost`, `visible`, `enabled`, `active`, `renderedAsActive` — state flags
|
||||||
|
|
||||||
|
Key operations:
|
||||||
|
- `SetParent(value)` — reparents the window; null parent defaults to main window; updates z-order via `FixWindowInOrder`
|
||||||
|
- `SetVisible(bool)` — shows or hides; triggers `FixWindowInOrder` and `OnOpened`/`OnClosed` callbacks
|
||||||
|
- `SetTopMost(bool)` — toggles top-most status; moves window between ordinary and top-most z-order lists
|
||||||
|
- `SetEnabled(bool)` — enables or disables; triggers callbacks
|
||||||
|
- `BringToFront()` — moves window and its visible subtree to the front of z-order within its priority level
|
||||||
|
- `Activate()` — sets focus; finds common parent with previously active window; updates `renderedAsActive` chain; calls `BringToFront`
|
||||||
|
- `Deactivate()` — walks up parent chain to find next enabled window to activate
|
||||||
|
- `Show()` — shorthand for `SetVisible(true)` + `Activate()`
|
||||||
|
|
||||||
|
### Z-Ordering
|
||||||
|
|
||||||
|
Two separate lists (`ordinaryWindowsInOrder` and `topMostedWindowsInOrder`) track window order. `IsEventuallyTopMost()` checks if a window or any ancestor is top-most and visible. `FixWindowInOrder` ensures windows are placed in the correct list based on their top-most status. `EnsureChildrenMovedInFrontOf` maintains the invariant that children appear before (in front of) their parent in the z-order list.
|
||||||
|
|
||||||
|
### HitTest
|
||||||
|
|
||||||
|
`WindowManager::HitTest(position)` iterates `topMostedWindowsInOrder` then `ordinaryWindowsInOrder`, returning the first visible window whose bounds contain the position — giving top-most windows priority.
|
||||||
|
|
||||||
|
### Start and Stop
|
||||||
|
|
||||||
|
`WindowManager::Start(mainWindow)` makes orphan windows children of the main window. `WindowManager::Stop()` clears all state and fires deactivation/close events.
|
||||||
|
|
||||||
|
## GuiHostedWindow — Virtual INativeWindow
|
||||||
|
|
||||||
|
`GuiHostedWindow` implements `INativeWindow` and inherits from `GuiHostedWindowData`, which holds all window properties (title, icon, cursor, size state, border options, etc.). Each `GuiHostedWindow` contains a `hosted_window_manager::Window<GuiHostedWindow*> wmWindow` embedded in its data.
|
||||||
|
|
||||||
|
### Proxy Pattern
|
||||||
|
|
||||||
|
`GuiHostedWindow` delegates behavior to an `IGuiHostedWindowProxy`. There are three proxy types:
|
||||||
|
1. **PlaceholderProxy** (`GuiHostedWindowProxy_Placeholder.cpp`) — initial no-op proxy for newly created windows before role assignment
|
||||||
|
2. **MainProxy** (`GuiHostedWindowProxy_Main.cpp`, `GuiMainHostedWindowProxy`) — for the main window; delegates title, icon, size, border properties, maximized/minimized/show/hide to the real native window
|
||||||
|
3. **NonMainProxy** (`GuiHostedWindowProxy_NonMain.cpp`, `GuiNonMainHostedWindowProxy`) — for all other windows; operates purely through the window manager; disallows maximized/minimized boxes; enforces custom frame mode when system borders are used
|
||||||
|
|
||||||
|
`BecomeMainWindow()` and `BecomeNonMainWindow()` switch the proxy. This happens in `SettingHostedWindowsBeforeRunning()` when the main window is known, and in `CreateNativeWindow()` for windows created after the main window.
|
||||||
|
|
||||||
|
### Coordinate System
|
||||||
|
|
||||||
|
- `GetRenderingOffset()` returns the window's top-left position in the native window's client space as `NativeSize`
|
||||||
|
- `GetBounds()` returns `wmWindow.bounds` directly (position is in parent's client space)
|
||||||
|
- `GetClientSize()` equals `GetBounds().GetSize()` (no frame for hosted windows)
|
||||||
|
- `GetClientBoundsInScreen()` equals `GetBounds()` (screen = client area of native window)
|
||||||
|
- DPI conversion functions delegate to the real native window
|
||||||
|
|
||||||
|
### Capture and Cursor
|
||||||
|
|
||||||
|
- `RequireCapture()` sets `controller->capturingWindow` and calls `nativeWindow->RequireCapture()`
|
||||||
|
- `ReleaseCapture()` clears `controller->capturingWindow` and calls `nativeWindow->ReleaseCapture()`
|
||||||
|
- `SetWindowCursor()` only updates the real native window cursor if this is the currently hovering window
|
||||||
|
- `SetCaretPoint()` adjusts by `GetRenderingOffset()` and sets on the real native window if this is the active window
|
||||||
|
|
||||||
|
## Window Creation and Destruction
|
||||||
|
|
||||||
|
### CreateNativeWindow(windowMode)
|
||||||
|
|
||||||
|
1. Creates a `GuiHostedWindow` with placeholder proxy
|
||||||
|
2. Adds to `createdWindows` sorted list
|
||||||
|
3. Registers with `wmManager`
|
||||||
|
4. Fires `InvokeNativeWindowCreated` callback
|
||||||
|
5. If main window already exists, calls `BecomeNonMainWindow()`
|
||||||
|
|
||||||
|
### DestroyNativeWindow(window)
|
||||||
|
|
||||||
|
1. Clears references (`enteringWindow`, `hoveringWindow`, `capturingWindow`, and WM-tracking pointers)
|
||||||
|
2. Fires `Destroying` on listeners
|
||||||
|
3. Fires `InvokeNativeWindowDestroying` callback
|
||||||
|
4. Unregisters from `wmManager` (which reparents children)
|
||||||
|
5. Removes from `createdWindows`
|
||||||
|
|
||||||
|
### Run(window)
|
||||||
|
|
||||||
|
1. Sets `mainWindow`
|
||||||
|
2. Calls `SettingHostedWindowsBeforeRunning()` which:
|
||||||
|
- Switches all windows to their proper proxies (main/non-main)
|
||||||
|
- Centers native window on screen
|
||||||
|
- Calls `wmManager->Start(&mainWindow->wmWindow)`
|
||||||
|
3. Delegates to `nativeController->WindowService()->Run(nativeWindow)` for the message loop
|
||||||
|
4. Handles exceptions in unit test mode
|
||||||
|
5. Calls `DestroyHostedWindowsAfterRunning()` to tear down all hosted windows
|
||||||
|
|
||||||
|
## Input Event Dispatching
|
||||||
|
|
||||||
|
### Mouse Events
|
||||||
|
|
||||||
|
`GuiHostedController` listens to mouse events on the single native window. Mouse dispatching uses a template-based system:
|
||||||
|
|
||||||
|
`HandleMouseCallback<PreAction, GetSelectedWindow, PostAction, Callback>` is the core template:
|
||||||
|
1. Calls `PreAction` (may trigger window manager operations)
|
||||||
|
2. If not in a window-manager operation (`wmWindow == nullptr`), calls `GetSelectedWindow` to find the target
|
||||||
|
3. Adjusts mouse coordinates by subtracting the target window's `wmWindow.bounds` top-left
|
||||||
|
4. Dispatches to the target window's listeners
|
||||||
|
5. Calls `PostAction`
|
||||||
|
|
||||||
|
Three `GetSelectedWindow` strategies:
|
||||||
|
- `GetSelectedWindow_MouseDown` — closes popup windows not in the hovering chain, returns `capturingWindow` or `hoveringWindow`
|
||||||
|
- `GetSelectedWindow_MouseMoving` — updates `hoveringWindow` via `UpdateHoveringWindow` and `enteringWindow` via `UpdateEnteringWindow`
|
||||||
|
- `GetSelectedWindow_Other` — returns `capturingWindow` or `hoveringWindow`
|
||||||
|
|
||||||
|
Individual mouse event methods (`LeftButtonDown`, `MouseMoving`, etc.) are wired to the right combination of PreAction/GetSelectedWindow/PostAction via `IMPLEMENT_MOUSE_CALLBACK` macros.
|
||||||
|
|
||||||
|
### Window Manager Dragging and Resizing
|
||||||
|
|
||||||
|
`PreAction_LeftButtonDown` checks non-main enabled windows for hit test results (Title, BorderLeft, BorderRight, etc.). If a border/title hit is detected:
|
||||||
|
- Sets `wmOperation` to the appropriate `WindowManagerOperation` enum value
|
||||||
|
- Stores `wmWindow` pointer and `wmRelative` offset
|
||||||
|
- Captures the native window
|
||||||
|
|
||||||
|
`PreAction_MouseMoving` handles ongoing drag/resize when `wmWindow` is set: recalculates bounds based on mouse position and operation type, calls `Moving` and `Moved` listeners.
|
||||||
|
|
||||||
|
`PostAction_LeftButtonUp` checks for `ButtonClose` hit test to close windows, and ends window manager operations.
|
||||||
|
|
||||||
|
### Mouse Hovering and Entering
|
||||||
|
|
||||||
|
- `UpdateHoveringWindow(location)` stores mouse position and calls `HitTestInClientSpace` to find the window under cursor
|
||||||
|
- `UpdateEnteringWindow(window)` fires `MouseLeaved` on the old entering window and `MouseEntered` on the new one
|
||||||
|
- `MouseLeaved` on the native window calls `UpdateEnteringWindow(nullptr)`
|
||||||
|
|
||||||
|
### Mouse-Down Activation
|
||||||
|
|
||||||
|
`PreAction_MouseDown` activates the hovering window when a mouse button is pressed (if the window is enabled and activation-enabled).
|
||||||
|
|
||||||
|
### Popup Closure on Click
|
||||||
|
|
||||||
|
`GetSelectedWindow_MouseDown` implements the pattern of closing popup windows (non-`Normal` mode windows) that are not ancestors of the clicked window.
|
||||||
|
|
||||||
|
### Keyboard Events
|
||||||
|
|
||||||
|
`HandleKeyboardCallback` dispatches keyboard events (`KeyDown`, `KeyUp`, `Char`) to the active window's listeners.
|
||||||
|
|
||||||
|
## Rendering Pipeline
|
||||||
|
|
||||||
|
### Hosted Rendering in GlobalTimer
|
||||||
|
|
||||||
|
`GuiHostedController::GlobalTimer()` drives the rendering cycle. On each global timer tick:
|
||||||
|
1. Skip if the native window is not visible or already in hosted rendering
|
||||||
|
2. Check all visible windows' listeners for `NeedRefresh()` — if any returns true, set `needRefresh`
|
||||||
|
3. If no refresh is needed and nothing was updated last frame, skip rendering
|
||||||
|
4. Enter rendering loop:
|
||||||
|
- Call `renderTarget->StartHostedRendering()` on the native resource manager's render target
|
||||||
|
- Iterate ordinary windows (back to front, reversed list order) then top-most windows
|
||||||
|
- For each window, call each listener's `ForceRefresh(false, updated, failureByResized, failureByLostDevice)`
|
||||||
|
- Call `renderTarget->StopHostedRendering()`
|
||||||
|
- Handle failures: `LostDevice` → `RecreateRenderTarget`; `ResizeWhileRendering` → `ResizeRenderTarget`
|
||||||
|
- On success, call `nativeWindow->RedrawContent()` and break
|
||||||
|
|
||||||
|
### Hosted Rendering Protocol (GuiGraphicsRenderTarget)
|
||||||
|
|
||||||
|
`GuiGraphicsRenderTarget` (base class for D2D/GDI render targets) supports hosted rendering:
|
||||||
|
- `StartHostedRendering()` sets `hostedRendering = true` and calls `StartRenderingOnNativeWindow()` once
|
||||||
|
- During hosted rendering, individual `StartRendering()` / `StopRendering()` pairs do NOT call `StartRenderingOnNativeWindow()` / `StopRenderingOnNativeWindow()` — they just toggle the `rendering` flag
|
||||||
|
- `StopHostedRendering()` calls `StopRenderingOnNativeWindow()` once
|
||||||
|
- This means all hosted windows render within a single begin/end rendering session on the native render target
|
||||||
|
|
||||||
|
### Per-Window Rendering Offset (GuiGraphicsHost)
|
||||||
|
|
||||||
|
`GuiGraphicsHost::Render()` (called from `ForceRefresh`) applies the rendering offset:
|
||||||
|
1. `hostRecord.renderTarget->StartRendering()`
|
||||||
|
2. `auto nativeOffset = hostRecord.nativeWindow->GetRenderingOffset()` — for hosted windows this returns the window position
|
||||||
|
3. `auto localOffset = hostRecord.nativeWindow->Convert(nativeOffset)` — converts to logical pixels
|
||||||
|
4. `windowComposition->Render(localOffset)` — renders the composition tree at the offset
|
||||||
|
5. `hostRecord.renderTarget->StopRendering()`
|
||||||
|
|
||||||
|
The rendering offset mechanism is the key: in non-hosted mode, `GetRenderingOffset()` returns `(0,0)` because each window has its own render target. In hosted mode, it returns the window's position in the shared native window coordinate space, so all window content is rendered at the correct position within the single render target.
|
||||||
|
|
||||||
|
### GuiHostedGraphicsResourceManager
|
||||||
|
|
||||||
|
`GuiHostedGraphicsResourceManager` wraps the native resource manager:
|
||||||
|
- `GetRenderTarget(window)` always returns the native render target for the single native window, regardless of which hosted window asks
|
||||||
|
- `RecreateRenderTarget(window)` and `ResizeRenderTarget(window)` are no-ops (managed by the global timer loop)
|
||||||
|
- All other methods (element registration, renderer factories, layout provider) delegate to the native manager
|
||||||
|
|
||||||
|
## Native Window Event Forwarding
|
||||||
|
|
||||||
|
`GuiHostedController` as `INativeWindowListener` on the single real native window forwards events to hosted windows:
|
||||||
|
- `HitTest(location)` — if main window is enabled and cursor is on it, performs hit test via main window's listeners
|
||||||
|
- `Moving(bounds)` — adjusts bounds to main window's coordinate space and forwards to main window's listeners
|
||||||
|
- `Moved()` — syncs main window bounds to native window client size
|
||||||
|
- `DpiChanged(preparing)` — recreates render target and broadcasts to all hosted windows
|
||||||
|
- `GotFocus()` — reactivates last focused window (or main window)
|
||||||
|
- `LostFocus()` — stores last focused window and deactivates all
|
||||||
|
- `BeforeClosing(cancel)` / `AfterClosing()` — forwards to main window's listeners
|
||||||
|
- `Opened()` — forwards to main window's listeners
|
||||||
|
|
||||||
|
## Dialog Service
|
||||||
|
|
||||||
|
`GuiHostedController::DialogService()` returns `nullptr`, causing GacUI to use `FakeDialogServiceBase` — GacUI-implemented dialogs rendered inside the hosted environment. This is critical because OS-native dialogs would create separate windows outside the hosted environment.
|
||||||
|
|
||||||
|
`FakeDialogServiceBase` provides view models (`IMessageBoxDialogViewModel`, `IColorDialogViewModel`, `IFontDialogViewModel`, `IOpenFileDialogViewModel`, `ISaveFileDialogViewModel`) and creates corresponding GacUI windows that appear as hosted sub-windows.
|
||||||
|
|
||||||
|
## Remote Mode Integration
|
||||||
|
|
||||||
|
Remote mode (`SetupRemoteNativeController`) always uses hosted mode:
|
||||||
|
- `GuiRemoteController` implements `INativeController` through protocol communication
|
||||||
|
- `GuiHostedController` wraps `GuiRemoteController` just as it wraps Windows native controllers
|
||||||
|
- `GuiRemoteGraphicsResourceManager` wraps `GuiRemoteController`, then `GuiHostedGraphicsResourceManager` wraps that
|
||||||
|
- The single native window in remote mode is the `GuiRemoteWindow`, which sends rendering commands through the protocol rather than to a real screen
|
||||||
322
.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolCoreArchitecture.md
vendored
Normal file
322
.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolCoreArchitecture.md
vendored
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
Remote Protocol Core Architecture
|
||||||
|
|
||||||
|
Remote protocol mode separates GacUI into two sides: the **core side** runs all application logic and framework code, while the **renderer side** (client) handles actual rendering and OS services. They communicate through `IGuiRemoteProtocol`. The core side is always wrapped in hosted mode — `GuiHostedController` wraps `GuiRemoteController`.
|
||||||
|
|
||||||
|
Implementation lives in `Source/PlatformProviders/Remote/`.
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
In native modes (Windows Direct2D, GDI), GacUI couples directly to OS window management, rendering APIs, and input handling. Remote protocol decouples all of this: the core side becomes platform-independent and only speaks a protocol. This enables:
|
||||||
|
- Running GacUI applications in a web browser, terminal, or any custom renderer
|
||||||
|
- Unit testing without a real window system
|
||||||
|
- Reconnection support — the core side can survive renderer disconnection and restore full visual state when a new renderer connects
|
||||||
|
|
||||||
|
## Protocol Design: Messages, Events, and Responses
|
||||||
|
|
||||||
|
The protocol has a strict directional split defined by three interface layers in `GuiRemoteProtocol.h`:
|
||||||
|
|
||||||
|
### IGuiRemoteProtocolMessages (Core Side → Renderer Side)
|
||||||
|
|
||||||
|
Messages are requests sent from the core side to the renderer side. They cover controller configuration queries, window operations, rendering commands, and image management. Some messages expect responses (indicated by `_RES` tag in the protocol definition).
|
||||||
|
|
||||||
|
### IGuiRemoteProtocolEvents (Renderer Side → Core Side)
|
||||||
|
|
||||||
|
Events are notifications sent from the renderer side to the core side. They include connection lifecycle (`OnControllerConnect`, `OnControllerDisconnect`, `OnControllerRequestExit`), user input (`OnIOKeyDown`, `OnIOKeyUp`, `OnIOMouseMoving`, etc.), and window state changes (`OnWindowBoundsUpdated`).
|
||||||
|
|
||||||
|
Responses are also delivered through `IGuiRemoteProtocolEvents` via `RespondNAME(id, arguments)` methods, matched to their originating request by request ID.
|
||||||
|
|
||||||
|
### Protocol Definition Files
|
||||||
|
|
||||||
|
Protocol messages and events are defined in `Protocol/*.txt` files (e.g., `Protocol_Controller.txt`, `Protocol_Renderer.txt`, `Protocol_IO.txt`) and code-generated into `GuiRemoteProtocolSchema.h`. The generated macros `GACUI_REMOTEPROTOCOL_MESSAGES` and `GACUI_REMOTEPROTOCOL_EVENTS` carry drop annotations (`[@DropRepeat]`, `[@DropConsecutive]`) used by the filter layer for traffic optimization.
|
||||||
|
|
||||||
|
## Request/Response Synchronization (GuiRemoteMessages)
|
||||||
|
|
||||||
|
`GuiRemoteMessages` wraps `IGuiRemoteProtocol` to provide a synchronous request-response model with batching:
|
||||||
|
|
||||||
|
1. **Queue requests**: Each `RequestNAME(...)` call enqueues a message. If the message expects a response, an auto-incrementing request ID (`++id`) is assigned and returned.
|
||||||
|
2. **Submit**: `Submit()` flushes all queued messages to the renderer side via `IGuiRemoteProtocol::Submit()`. This call is **blocking** — it waits until all responses have been received.
|
||||||
|
3. **Retrieve responses**: `RetrieveNAME(id)` fetches the response for a given request ID.
|
||||||
|
|
||||||
|
The core side operates synchronously from its own perspective despite the underlying protocol potentially being asynchronous. Batching multiple requests before a single `Submit()` is the primary mechanism for reducing round-trip overhead.
|
||||||
|
|
||||||
|
`GuiRemoteEvents` implements `IGuiRemoteProtocolEvents` on the core side. It receives both responses (stored for later retrieval by `GuiRemoteMessages`) and events (dispatched to `GuiRemoteController`, `GuiRemoteWindow`, etc.).
|
||||||
|
|
||||||
|
## GuiRemoteController: Virtual Native Controller
|
||||||
|
|
||||||
|
`GuiRemoteController` implements `INativeController` and all sub-services as virtual stubs that communicate via protocol. It inherits from:
|
||||||
|
- `Object`
|
||||||
|
- `INativeController`
|
||||||
|
- `INativeResourceService`
|
||||||
|
- `INativeInputService`
|
||||||
|
- `INativeScreenService` / `INativeScreen`
|
||||||
|
- `INativeWindowService`
|
||||||
|
|
||||||
|
### Service Design Decisions
|
||||||
|
|
||||||
|
- **Single window only**: `CreateNativeWindow` can only be called once (enforced by `CHECK_ERROR`). Multiple sub-windows are managed by `GuiHostedController` through hosted mode.
|
||||||
|
- **Intentionally null services**: `ClipboardService()` and `DialogService()` return `nullptr`, causing GacUI to fall back to built-in fakes (`FakeDialogServiceBase`). This is intentional — the core side cannot access real OS clipboard or dialogs.
|
||||||
|
- **Synchronous key state queries**: `IsKeyPressing()` and `IsKeyToggled()` perform synchronous request-submit-retrieve round-trips. This is expensive but rarely called.
|
||||||
|
- **Global shortcut keys**: Tracked core-side and batch-sent to the renderer side on update.
|
||||||
|
|
||||||
|
## Connection Lifecycle
|
||||||
|
|
||||||
|
### Startup (SetupRemoteNativeController)
|
||||||
|
|
||||||
|
`SetupRemoteNativeController(IGuiRemoteProtocol* protocol)` creates a layered stack:
|
||||||
|
1. `GuiRemoteController` — implements `INativeController`, wraps the protocol
|
||||||
|
2. `GuiHostedController` — wraps `GuiRemoteController` for hosted mode sub-window support
|
||||||
|
3. `GuiRemoteGraphicsResourceManager` — implements `GuiGraphicsResourceManager` and `IGuiGraphicsLayoutProvider`
|
||||||
|
4. `GuiHostedGraphicsResourceManager` — wraps the above for hosted mode rendering
|
||||||
|
|
||||||
|
Initialization order: remote → resource manager → hosted controller. Finalization order: reverse. Then `GuiApplicationMain()` runs.
|
||||||
|
|
||||||
|
### Connection Establishing
|
||||||
|
|
||||||
|
A connection begins when the renderer side fires `OnControllerConnect(ControllerGlobalConfig)`. The core side responds:
|
||||||
|
1. Acknowledges via `RequestControllerConnectionEstablished()`
|
||||||
|
2. Fetches `FontConfig` and `ScreenConfig` in one batched `Submit()`
|
||||||
|
3. Propagates the connection event to `remoteWindow`, `imageService`, and `resourceManager`, each of which restores its state
|
||||||
|
|
||||||
|
`GuiRemoteWindow::OnControllerConnect()` re-sends all current window styles when `applicationRunning` is true, enabling seamless reconnection — the renderer side experiences a fresh complete window state setup.
|
||||||
|
|
||||||
|
### Disconnection
|
||||||
|
|
||||||
|
The renderer side fires `OnControllerDisconnect()`. Each subsystem (`GuiRemoteWindow`, `GuiRemoteGraphicsImageService`, `GuiRemoteGraphicsResourceManager`) marks itself disconnected and suspends protocol communication until reconnection.
|
||||||
|
|
||||||
|
### Graceful Exit
|
||||||
|
|
||||||
|
The renderer side fires `OnControllerRequestExit()`:
|
||||||
|
1. `remoteWindow.Hide(true)` triggers the close sequence
|
||||||
|
2. `BeforeClosing` / `AfterClosing` listener chain fires
|
||||||
|
3. `DestroyNativeWindow` is scheduled
|
||||||
|
|
||||||
|
`connectionForcedToStop` bypasses the `BeforeClosing` cancellation check for forced shutdown.
|
||||||
|
|
||||||
|
### Run Loop (RunOneCycle)
|
||||||
|
|
||||||
|
`GuiRemoteController::RunOneCycle()` drives the core side:
|
||||||
|
1. Process incoming events via `IGuiRemoteEventProcessor::ProcessRemoteEvents()`
|
||||||
|
2. `Submit()` flushes queued messages
|
||||||
|
3. If timer enabled: `InvokeGlobalTimer()` — triggers hosted controller rendering
|
||||||
|
4. Execute async tasks
|
||||||
|
|
||||||
|
The timer-driven rendering is crucial: `InvokeGlobalTimer()` causes `GuiHostedController` to decide whether to render, which calls into `GuiRemoteGraphicsRenderTarget`.
|
||||||
|
|
||||||
|
## GuiRemoteWindow: Virtual Native Window
|
||||||
|
|
||||||
|
`GuiRemoteWindow` implements `INativeWindow` for one virtual window.
|
||||||
|
|
||||||
|
### Timer-Driven Rendering
|
||||||
|
|
||||||
|
`IsActivelyRefreshing()` returns `true`, forcing hosted mode to render every cycle. `RedrawContent()` is a no-op because rendering is driven by the timer in `RunOneCycle`, not on-demand.
|
||||||
|
|
||||||
|
### Size and Bounds
|
||||||
|
|
||||||
|
Bounds and sizing use `remoteWindowSizingConfig` obtained from the renderer side. A `sizingConfigInvalidated` flag triggers a synchronous re-fetch when bounds are read — this ensures that style changes that affect the window frame are reflected before the next layout pass.
|
||||||
|
|
||||||
|
### Style Change Protocol
|
||||||
|
|
||||||
|
Two macros manage window style synchronization:
|
||||||
|
- `SET_REMOTE_WINDOW_STYLE(STYLE, VALUE)` — checks for value change, then sends `RequestWindowNotifySetSTYLE` if changed
|
||||||
|
- `SET_REMOTE_WINDOW_STYLE_INVALIDATE(STYLE, VALUE)` — same as above, but also marks `sizingConfigInvalidated = true` for styles that affect window sizing (e.g., border, title bar)
|
||||||
|
|
||||||
|
Managed properties: Title, CustomFrameMode, ShowInTaskBar, MaximizedBox, MinimizedBox, Border, SizeBox, IconVisible, TitleBar, TopMost.
|
||||||
|
|
||||||
|
### Reconnection
|
||||||
|
|
||||||
|
On reconnection (`OnControllerConnect`), `GuiRemoteWindow` re-sends all current window styles to the renderer side, so the renderer side can reconstruct the window exactly as it was.
|
||||||
|
|
||||||
|
## Rendering Pipeline
|
||||||
|
|
||||||
|
The rendering system bridges GacUI's composition-tree rendering model and the remote protocol's message-based communication. In native mode, rendering is immediate: traverse the composition tree, call graphics APIs. In remote mode, each element exists as an ID on both sides. The core side must track changes, batch element updates, send rendering commands, and receive measurement feedback.
|
||||||
|
|
||||||
|
### Element Lifecycle
|
||||||
|
|
||||||
|
Every visual element has a core-side renderer implementing `IGuiRemoteProtocolElementRender`, with a base class template `GuiRemoteProtocolElementRenderer<TElement, TRenderer, _RendererType>`. The concrete renderer classes include:
|
||||||
|
- `GuiFocusRectangleElementRenderer`
|
||||||
|
- `GuiRawElementRenderer`
|
||||||
|
- `GuiSolidBorderElementRenderer`, `Gui3DBorderElementRenderer`, `Gui3DSplitterElementRenderer`
|
||||||
|
- `GuiSolidBackgroundElementRenderer`, `GuiGradientBackgroundElementRenderer`
|
||||||
|
- `GuiInnerShadowElementRenderer`
|
||||||
|
- `GuiSolidLabelElementRenderer`
|
||||||
|
- `GuiImageFrameElementRenderer`
|
||||||
|
- `GuiPolygonElementRenderer`
|
||||||
|
|
||||||
|
Element lifecycle:
|
||||||
|
1. **Creation**: When a renderer's render target is set (`RenderTargetChangedInternal`), it allocates an ID via `AllocateNewElementId()` and registers with `GuiRemoteGraphicsRenderTarget`. The ID is added to `createdRenderers`.
|
||||||
|
2. **Notification**: `EnsureRequestedRenderersCreated()` (called at the start of each frame) batches pending creates and destroys into `RequestRendererCreated` / `RequestRendererDestroyed` messages to the renderer side.
|
||||||
|
3. **Destruction**: `FinalizeInternal()` calls `UnregisterRenderer()`, moving the ID to `destroyedRenderers`.
|
||||||
|
|
||||||
|
### Frame Rendering Flow
|
||||||
|
|
||||||
|
A single rendering frame follows this sequence:
|
||||||
|
|
||||||
|
**StartRenderingOnNativeWindow()**:
|
||||||
|
1. Capture current canvas size from `remoteWindow.GetClientSize()`
|
||||||
|
2. Increment `renderingBatchId` (tracks which elements rendered this frame)
|
||||||
|
3. `EnsureRequestedRenderersCreated()` — flush element create/destroy batches
|
||||||
|
4. Build `ElementBeginRendering` with updated element descriptions:
|
||||||
|
- **Normal mode**: iterate all renderers, collect only those where `IsUpdated()` returns true
|
||||||
|
- **Reconnect mode** (`needFullElementUpdateOnNextFrame`): collect ALL renderers with `fullContent=true`, ensuring the new renderer side gets complete state
|
||||||
|
5. Each renderer's `SendUpdateElementMessages()` serializes its current state into the update list
|
||||||
|
6. For renderers needing min size from cache (`NeedUpdateMinSizeFromCache()`), add to `renderersAskingForCache`
|
||||||
|
7. Send `RequestRendererBeginRendering` with the frame ID and all updates
|
||||||
|
|
||||||
|
**Composition tree traversal**:
|
||||||
|
- Each element's `Render(bounds)` sends `RequestRendererRenderElement` with the element ID, bounds, and clipped area
|
||||||
|
- Clipper push/pop sends `RequestRendererBeginBoundary` / `RequestRendererEndBoundary` with hit test results and cursor types per composition
|
||||||
|
|
||||||
|
**StopRenderingOnNativeWindow()**:
|
||||||
|
1. Send `RequestRendererEndRendering`, `Submit()`, retrieve `ElementMeasurings` response
|
||||||
|
2. Process four categories of measurement feedback (see below)
|
||||||
|
3. If any min sizes changed, call `hostedController->RequestRefresh()` to trigger another frame
|
||||||
|
4. Return `ResizeWhileRendering` if canvas size changed during rendering
|
||||||
|
|
||||||
|
### Element Update Mechanism (Diff-Based)
|
||||||
|
|
||||||
|
Each element renderer tracks its own dirty state:
|
||||||
|
- `updated` flag: set by `OnElementStateChanged()` when element properties change
|
||||||
|
- `renderTargetChanged` flag: set when the render target is first assigned
|
||||||
|
|
||||||
|
`SendUpdateElementMessages(fullContent, updatedElements)` serializes the element's current visual state. The `fullContent` parameter controls whether to include all properties or just changed ones.
|
||||||
|
|
||||||
|
For example, `GuiSolidLabelElementRenderer::SendUpdateElementMessages()` only includes font and text when they actually changed (comparing `lastFont`/`lastText`), unless `fullContent` or `renderTargetChanged` is true. This diff-based update minimizes protocol traffic — for a typical frame, most elements are unchanged.
|
||||||
|
|
||||||
|
### Measurement Feedback Loop
|
||||||
|
|
||||||
|
The core side cannot measure text or images locally — it must ask the renderer side. This creates a feedback loop processed in `StopRenderingOnNativeWindow()`:
|
||||||
|
|
||||||
|
1. **Font heights**: `GuiSolidLabelElementRenderer` includes a `measuringRequest` field (either `FontHeight` or `TotalSize`). The renderer side measures and returns `fontHeights` in `ElementMeasurings`, cached in `renderTarget->fontHeights` keyed by `(fontFamily, fontSize)`.
|
||||||
|
|
||||||
|
2. **Min sizes**: The renderer side returns `minSizes` per element ID. `StopRenderingOnNativeWindow()` calls `UpdateMinSize()` on each renderer.
|
||||||
|
|
||||||
|
3. **Image metadata**: Newly created images return metadata (dimensions, format) via `createdImages`. `GuiRemoteGraphicsImage::UpdateFromImageMetadata()` stores this.
|
||||||
|
|
||||||
|
4. **Inline object bounds**: For `DocumentParagraph` elements with inline objects, the renderer returns layout bounds per callback ID via `inlineObjectBounds`.
|
||||||
|
|
||||||
|
**Convergence**: If any min size changed, `hostedController->RequestRefresh()` schedules another rendering frame. The layout engine uses the new min sizes, potentially repositioning elements, requiring another render. This converges quickly because min sizes stabilize after one or two iterations.
|
||||||
|
|
||||||
|
**Font height caching**: `TryFetchMinSizeFromCache()` in `GuiSolidLabelElementRenderer` checks whether the needed font height is already in `renderTarget->fontHeights`. If cached, it uses the value directly and skips the measurement request, avoiding redundant round-trips.
|
||||||
|
|
||||||
|
### Rendering Boundary Tracking
|
||||||
|
|
||||||
|
During composition tree traversal, clipper operations emit boundary messages to enable renderer-side hit testing and cursor display:
|
||||||
|
- `AfterPushedClipper()`: inspects the composition for hit test results and cursor types. If either is set and differs from the current stack top, sends `RequestRendererBeginBoundary` with the composition's `remoteId`, bounds, and clipped area.
|
||||||
|
- `AfterPoppedClipper()`: sends `RequestRendererEndBoundary` for the matching boundary.
|
||||||
|
|
||||||
|
This allows the renderer side to know which composition each pixel belongs to without needing the full composition tree.
|
||||||
|
|
||||||
|
## GuiRemoteGraphicsParagraph (IGuiGraphicsParagraph)
|
||||||
|
|
||||||
|
`GuiRemoteGraphicsParagraph` is the remote implementation of `IGuiGraphicsParagraph`, handling rich text layout for document elements. It is the most protocol-intensive part of the rendering system.
|
||||||
|
|
||||||
|
### Why Paragraphs Are Special
|
||||||
|
|
||||||
|
Unlike ordinary elements (borders, backgrounds, labels) that send their visual state and let the renderer side draw them, paragraphs need **bidirectional communication**:
|
||||||
|
- The core side defines text content, styling, and inline objects
|
||||||
|
- The renderer side performs text layout (line breaking, glyph positioning)
|
||||||
|
- The core side needs layout results (sizes, caret positions, inline object positions) for hit testing, caret navigation, and selection
|
||||||
|
|
||||||
|
### Lifecycle
|
||||||
|
|
||||||
|
Paragraphs are created by `GuiRemoteGraphicsResourceManager::CreateParagraph()`. Each paragraph gets its own element ID (shared ID space with element renderers), registered via `RegisterParagraph()` instead of `RegisterRenderer()`. Creations are batched in `pendingParagraphCreations` alongside element creates in `EnsureRequestedRenderersCreated()`.
|
||||||
|
|
||||||
|
### Run Property System
|
||||||
|
|
||||||
|
Paragraph styling uses a run-based system with three layers:
|
||||||
|
1. **`DocumentTextRunPropertyMap textRuns`**: text-level properties (font, size, color, style) by `CaretRange`
|
||||||
|
2. **`DocumentInlineObjectRunPropertyMap inlineObjectRuns`**: inline objects (images, controls embedded in text) by `CaretRange`
|
||||||
|
3. **`DocumentRunPropertyMap stagedRuns` / `committedRuns`**: merged result used for diff computation
|
||||||
|
|
||||||
|
`AddTextRun()` handles overlapping ranges by splitting and merging, supporting incremental property changes. `AddInlineObjectRun()` requires exact range matches (inline objects cannot be split). `MergeRuns()` combines both maps, with inline objects taking priority over text runs.
|
||||||
|
|
||||||
|
### Core Synchronization (EnsureRemoteParagraphSynced)
|
||||||
|
|
||||||
|
This is the core synchronization method, called before any query:
|
||||||
|
1. `EnsureRequestedRenderersCreated()` — ensures the paragraph element exists on the renderer side
|
||||||
|
2. Merge text and inline object runs into `stagedRuns`
|
||||||
|
3. `DiffRuns(committedRuns, stagedRuns, desc)` — compute diff between last committed and current state
|
||||||
|
4. Send `RequestRendererUpdateElement_DocumentParagraph(desc)` with the diff
|
||||||
|
5. `Submit()` synchronously — the response includes the new `documentSize`
|
||||||
|
6. Store `cachedSize` from response, swap `stagedRuns` to `committedRuns`
|
||||||
|
|
||||||
|
The first sync sends full text content; subsequent syncs send only run property diffs. This is efficient for scenarios like syntax highlighting where only styling changes between frames.
|
||||||
|
|
||||||
|
### Layout Queries (Delegated to Renderer Side)
|
||||||
|
|
||||||
|
All layout-dependent operations require synchronous round-trips or cached data:
|
||||||
|
- **`GetSize()`**: returns `cachedSize` from the last sync
|
||||||
|
- **`GetCaret(comparingCaret, position, preferFrontSide)`**: sends `RequestDocumentParagraph_GetCaret` — the renderer side performs text layout-aware caret navigation
|
||||||
|
- **`GetCaretBounds(caret, frontSide)`**: uses `GetCaretBoundsInternal()` which lazily fetches ALL caret bounds (front and back arrays) in one request via `RequestDocumentParagraph_GetCaretBounds` and caches them in `cachedCaretBounds`. Subsequent calls use the cache.
|
||||||
|
- **`GetCaretFromPoint(point)`**: iterates all caret positions locally using cached bounds from `GetCaretBoundsInternal()`, finding the nearest by Manhattan distance — no additional remote call if bounds are cached
|
||||||
|
- **`GetInlineObjectFromPoint(point)`**: sends `RequestDocumentParagraph_GetInlineObjectFromPoint` for hit-test, then looks up `inlineObjectProperties` locally
|
||||||
|
- **`GetNearestCaretFromTextPos(textPos, frontSide)`**: sends `RequestDocumentParagraph_GetNearestCaretFromTextPos`
|
||||||
|
- **`IsValidCaret(caret)`**: sends `RequestDocumentParagraph_IsValidCaret`
|
||||||
|
- **`IsValidTextPos(textPos)`**: purely local — checks bounds against `text.Length()`
|
||||||
|
|
||||||
|
### Caret Display
|
||||||
|
|
||||||
|
- `EnableCaret()` sends `RequestDocumentParagraph_OpenCaret` with position, color, and front/back side
|
||||||
|
- `DisableCaret()` sends `RequestDocumentParagraph_CloseCaret`
|
||||||
|
- `BlinkCaret()` returns false — blinking is handled renderer-side
|
||||||
|
|
||||||
|
### Paragraph Rendering
|
||||||
|
|
||||||
|
`GuiRemoteGraphicsParagraph::Render(bounds)`:
|
||||||
|
1. `EnsureRemoteParagraphSynced()` — ensure current state sent to renderer side
|
||||||
|
2. For each inline object with cached bounds, call `callback->OnRenderInlineObject()` — if the inline object's size changed, update `inlineObjectRuns` and mark dirty
|
||||||
|
3. Send `RequestRendererRenderElement` — same as ordinary elements
|
||||||
|
|
||||||
|
### Dirty Tracking
|
||||||
|
|
||||||
|
`MarkParagraphDirty(invalidateSize, invalidateCaretBoundsCache)` sets:
|
||||||
|
- `needUpdate = true` always
|
||||||
|
- `cachedSize = {0,0}` if `invalidateSize` is true (forcing re-measurement from renderer side)
|
||||||
|
- `needUpdateCaretBoundsCache = true` if `invalidateCaretBoundsCache` is true (forcing caret bounds refetch)
|
||||||
|
|
||||||
|
Size-affecting changes (font, size, text content, wrap line, max width) set both invalidation flags. Color-only changes skip size invalidation because they don't affect layout.
|
||||||
|
|
||||||
|
## DOM Diff Layer
|
||||||
|
|
||||||
|
### Motivation
|
||||||
|
|
||||||
|
Without DOM diff, each frame sends per-element rendering commands (`RenderElement`, `BeginBoundary`, `EndBoundary`). For a typical GacUI application with hundreds of elements, most of which don't move between frames, this is wasteful. The DOM diff layer converts the per-frame command stream into a tree structure and diffs against the previous frame, sending only structural changes.
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
`GuiRemoteProtocolDomDiffConverter` extends `GuiRemoteProtocolCombinator_PassingThrough<GuiRemoteEventDomDiffConverter>` and wraps `IGuiRemoteProtocol`:
|
||||||
|
- During rendering, `RenderingDomBuilder` builds a `RenderingDom` tree from the intercepted boundary and element commands
|
||||||
|
- On `RequestRendererEndRendering`:
|
||||||
|
- First frame: sends `RequestRendererRenderDom` with the full tree
|
||||||
|
- Subsequent frames: `DiffDom(lastDom, lastDomIndex, newDom, newDomIndex, diffs)` computes structural diffs, sends `RequestRendererRenderDomDiff`
|
||||||
|
- `lastDom` is stored for next frame; `OnControllerConnect` clears it to force a full DOM send on reconnection
|
||||||
|
|
||||||
|
DOM node IDs encode their type: element IDs use `(elementId << 2) + 0`, hit test compositions use `(compositionId << 2) + 2`, with parent variants at `+1` and `+3`.
|
||||||
|
|
||||||
|
## Protocol Combinator Layer
|
||||||
|
|
||||||
|
The combinator pattern allows composing protocol transformations as a pipeline. `GuiRemoteProtocolCombinator<TEvents>` (in `GuiRemoteProtocol_Shared.h`) wraps an `IGuiRemoteProtocol*` and interposes a `TEvents` event combinator. This creates a chain where messages flow core → combinator → target protocol, and events flow target protocol → event combinator → core.
|
||||||
|
|
||||||
|
`GuiRemoteProtocolCombinator_PassingThrough` (specialization with `void` events plus `GuiRemoteEventCombinator_PassingThrough`) forwards everything unchanged, allowing subclasses to selectively override only the messages they care about. Both the filter layer and DOM diff layer use this pattern.
|
||||||
|
|
||||||
|
## Protocol Filter Layer
|
||||||
|
|
||||||
|
`GuiRemoteProtocolFilter` wraps `IGuiRemoteProtocol` via `GuiRemoteProtocolCombinator<GuiRemoteEventFilter>` and optimizes traffic by dropping redundant messages/events:
|
||||||
|
- `[@DropRepeat]`: drop messages or events with identical arguments to the previous send, regardless of what happened in between
|
||||||
|
- `[@DropConsecutive]`: drop events with identical arguments to the immediately preceding event of the same type
|
||||||
|
|
||||||
|
The filter queues messages internally and applies drop logic in `ProcessRequests()` before `Submit()`.
|
||||||
|
|
||||||
|
## Channel Layer
|
||||||
|
|
||||||
|
For real remote deployment, `IGuiRemoteProtocolChannel<T>` provides a bidirectional channel abstraction. The typical stack:
|
||||||
|
1. `GuiRemoteProtocolFromJsonChannel` adapts a JSON channel to `IGuiRemoteProtocol`
|
||||||
|
2. `GuiRemoteProtocolAsyncChannelSerializer<Ptr<JsonObject>>` (aliased as `GuiRemoteProtocolAsyncJsonChannelSerializer`) runs channel IO on a separate thread
|
||||||
|
|
||||||
|
The async serializer's design: the UI thread batches messages and blocks on `Submit()` until the channel thread completes the round-trip. Events from the channel thread are queued and processed on the UI thread during `ProcessRemoteEvents()`. Connection/disconnection is tracked via a `connectionCounter` to handle race conditions when the channel thread delivers events after disconnection.
|
||||||
|
|
||||||
|
## Image Service
|
||||||
|
|
||||||
|
`GuiRemoteGraphicsImageService` implements `INativeImageService` and creates `GuiRemoteGraphicsImage` objects with core-side binary copies. Image metadata (dimensions, format) is lazily fetched from the renderer side via `ImageCreated` messages in the `ElementMeasurings` response.
|
||||||
|
|
||||||
|
On reconnection (`OnControllerConnect`), all existing images are re-registered with the renderer side so it can recreate its local image objects. On disconnection (`OnControllerDisconnect`), the service suspends sending and resets metadata that may have become stale.
|
||||||
190
.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolRendererAndSerialization.md
vendored
Normal file
190
.github/KnowledgeBase/KB_GacUI_Design_RemoteProtocolRendererAndSerialization.md
vendored
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
#### Remote Protocol Renderer and Serialization
|
||||||
|
|
||||||
|
The remote protocol renderer side receives protocol messages from the core side and translates them into native window operations and graphics rendering. The serialization and channel infrastructure provides composable layers that convert between typed protocol calls and transport-ready strings, enabling GacUI applications to run across process boundaries over any user-provided transport (named pipe, HTTP, WebSocket, etc.).
|
||||||
|
|
||||||
|
## GuiRemoteRendererSingle
|
||||||
|
|
||||||
|
`GuiRemoteRendererSingle` (in `Source/PlatformProviders/RemoteRenderer/`) is the renderer-side implementation that bridges `IGuiRemoteProtocol` to a real native window with actual graphics rendering. It is not a full `INativeController` — it relies on a real platform provider (e.g., Windows Direct2D via `SetupRawWindowsDirect2DRenderer()`) already running, and registers itself as a listener on a native window.
|
||||||
|
|
||||||
|
### Class Hierarchy
|
||||||
|
|
||||||
|
`GuiRemoteRendererSingle` inherits from:
|
||||||
|
- `Object` — standard base
|
||||||
|
- `IGuiRemoteProtocol` (virtual) — receives all protocol messages from the core side
|
||||||
|
- `INativeWindowListener` (protected virtual) — listens to native window events (mouse, keyboard, window lifecycle)
|
||||||
|
- `INativeControllerListener` (protected virtual) — listens to global timer and shortcut key events
|
||||||
|
|
||||||
|
### Key Members
|
||||||
|
|
||||||
|
- `window` / `screen` / `events`: The native window handle, current screen, and `IGuiRemoteProtocolEvents` callback for firing events to the core side.
|
||||||
|
- `availableElements`: `Dictionary<vint, Ptr<IGuiGraphicsElement>>` mapping element IDs to actual graphics elements created by the real rendering engine (e.g., `GuiSolidBorderElement`, `GuiSolidLabelElement`).
|
||||||
|
- `availableImages`: `Dictionary<vint, Ptr<INativeImage>>` mapping image IDs to loaded native images.
|
||||||
|
- `renderingDom` / `renderingDomIndex`: The rendering DOM tree received from the core side, used for on-screen rendering and hit testing.
|
||||||
|
- `solidLabelMeasurings` / `fontHeightMeasurings`: Caches and measurement tracking for text elements, supporting the measurement feedback loop.
|
||||||
|
- `pendingMouseMove`, `pendingHWheel`, `pendingVWheel`, `pendingKeyAutoDown`, `pendingWindowBoundsUpdate`: Accumulation fields for coalescing high-frequency events before sending.
|
||||||
|
|
||||||
|
### Source File Organization
|
||||||
|
|
||||||
|
- `GuiRemoteRendererSingle.cpp`: Construction, destruction, main window registration, connection lifecycle (`Opened`, `BeforeClosing`, `Closed`), screen/config management.
|
||||||
|
- `GuiRemoteRendererSingle_Controller.cpp`: Controller-level requests — `RequestControllerGetFontConfig`, `RequestControllerGetScreenConfig`, `RequestControllerConnectionEstablished`, `RequestControllerConnectionStopped`.
|
||||||
|
- `GuiRemoteRendererSingle_MainWindow.cpp`: Window style notifications — `RequestWindowNotifySetBounds`, `RequestWindowNotifySetTitle`, `RequestWindowNotifySetEnabled`, `RequestWindowNotifyShow`, etc.
|
||||||
|
- `GuiRemoteRendererSingle_IO.cpp`: IO requests (global shortcuts, mouse capture, key state queries) and native-to-protocol input event conversion. Contains `SendAccumulatedMessages()` for batching high-frequency events.
|
||||||
|
- `GuiRemoteRendererSingle_Rendering.cpp`: Core rendering pipeline — element creation/destruction (`RequestRendererCreated`, `RequestRendererDestroyed`), begin/end rendering, DOM rendering (`Render` recursive traversal), hit testing, and `GlobalTimer`/`Paint` driven refresh.
|
||||||
|
- `GuiRemoteRendererSingle_Rendering_Elements.cpp`: Property updates on ordinary graphics elements (solid border, sink border, splitter, background, gradient, inner shadow, polygon).
|
||||||
|
- `GuiRemoteRendererSingle_Rendering_Label.cpp`: Solid label measurement and property updates.
|
||||||
|
- `GuiRemoteRendererSingle_Rendering_Image.cpp`: Image creation, metadata, and image frame element updates.
|
||||||
|
- `GuiRemoteRendererSingle_Rendering_Document.cpp`: Complex document paragraph element rendering via `GuiRemoteDocumentParagraphElement` inner class.
|
||||||
|
|
||||||
|
### Rendering Pipeline
|
||||||
|
|
||||||
|
When the core sends `RequestRendererBeginRendering(ElementBeginRendering)`, the `updatedElements` field contains a list of `OrdinaryElementDescVariant`. Each variant is dispatched via `Apply(Overloading(...))` to the corresponding `RequestRendererUpdateElement_*` helper, which looks up the element by ID in `availableElements` and sets its properties.
|
||||||
|
|
||||||
|
The core sends `RequestRendererRenderDom` or `RequestRendererRenderDomDiff` to update the rendering DOM tree. The legacy command-based rendering path (`RequestRendererBeginBoundary`/`RequestRendererEndBoundary`/`RequestRendererRenderElement`) is disabled with `CHECK_FAIL` — the DOM-diff approach is required.
|
||||||
|
|
||||||
|
Actual painting happens in `GlobalTimer()`: if `needRefresh` is true, it calls `Render(renderingDom, rt)` which recursively traverses the DOM tree, rendering each element in order with clipping. After rendering, it checks if label measurements changed and populates `elementMeasurings`. The response is returned via `RespondRendererEndRendering(id, elementMeasurings)`.
|
||||||
|
|
||||||
|
### Event Forwarding
|
||||||
|
|
||||||
|
`GuiRemoteRendererSingle` implements all `INativeWindowListener` mouse/keyboard callbacks to forward OS events as protocol events:
|
||||||
|
- **Discrete events** (button clicks, key presses): Sent immediately via `events->OnIOMouseLeft`, `events->OnIOKeyDown`, etc.
|
||||||
|
- **High-frequency events** (mouse move, wheel, key auto-repeat): Accumulated and coalesced:
|
||||||
|
- `pendingMouseMove`: Only the latest mouse position is kept.
|
||||||
|
- `pendingHWheel` / `pendingVWheel`: Wheel deltas are summed across frames.
|
||||||
|
- `pendingKeyAutoDown`: Only the latest auto-repeat key is kept.
|
||||||
|
- `SendAccumulatedMessages()` is called from `GlobalTimer()` to flush these accumulated events.
|
||||||
|
- **Window lifecycle events** (`Opened`, `BeforeClosing`, `Moved`, `DpiChanged`): Translated to protocol events like `OnControllerConnect`, `OnControllerRequestExit`, `OnWindowBoundsUpdated`.
|
||||||
|
|
||||||
|
### Hit Testing
|
||||||
|
|
||||||
|
Hit testing is performed locally in the renderer by traversing the rendering DOM tree via `HitTestInternal`. Each DOM node may have `hitTestResult` and `cursor` attributes set by the core side. The renderer walks the tree and finds the matching node for a given point. This avoids round-trips — hit testing stays entirely renderer-side.
|
||||||
|
|
||||||
|
## Protocol Serialization and Channel Architecture
|
||||||
|
|
||||||
|
The serialization system uses a layered channel architecture to convert between typed protocol calls and transport-ready data. Each layer is a composable building block.
|
||||||
|
|
||||||
|
### Core Interfaces
|
||||||
|
|
||||||
|
- `IGuiRemoteProtocolChannel<TPackage>`: A bidirectional channel that can `Write` packages and receives packages via `IGuiRemoteProtocolChannelReceiver<TPackage>::OnReceive`.
|
||||||
|
- `IGuiRemoteProtocol`: The high-level typed protocol interface with named methods (`RequestNAME`, `OnNAME`, `RespondNAME`).
|
||||||
|
|
||||||
|
### Layer 1: IGuiRemoteProtocol ↔ JsonObject Channel
|
||||||
|
|
||||||
|
Two adapter classes handle bidirectional conversion between typed protocol calls and JSON objects:
|
||||||
|
|
||||||
|
- `GuiRemoteProtocolFromJsonChannel`: Wraps `IGuiRemoteProtocolChannel<Ptr<JsonObject>>` and implements `IGuiRemoteProtocol`. When `RequestNAME(arguments)` is called, it serializes arguments to JSON via `ConvertCustomTypeToJson()`, packs them into a JSON envelope with `JsonChannelPack()`, and calls `channel->Write(package)`. When it receives a JSON package via `OnReceive`, it unpacks with `JsonChannelUnpack()`, dispatches by name, deserializes via `ConvertJsonToCustomType()`, and calls `events->OnNAME()` or `events->RespondNAME()`.
|
||||||
|
- `GuiRemoteJsonChannelFromProtocol`: The reverse — wraps `IGuiRemoteProtocol` and implements `IGuiRemoteProtocolChannel<Ptr<JsonObject>>`. Converts incoming JSON packages to protocol calls, and outgoing events/responses to JSON packages.
|
||||||
|
|
||||||
|
### Layer 2: JsonObject ↔ WString Channel
|
||||||
|
|
||||||
|
`JsonToStringSerializer` handles JSON-to-string conversion:
|
||||||
|
- `Serialize`: Converts `Ptr<JsonObject>` to `WString` using `JsonToString` with compact formatting.
|
||||||
|
- `Deserialize`: Parses `WString` to `Ptr<JsonObject>` using `JsonParse`.
|
||||||
|
- Type aliases: `GuiRemoteJsonChannelStringSerializer` and `GuiRemoteJsonChannelStringDeserializer` for `GuiRemoteProtocolChannelSerializer<JsonToStringSerializer>` and `GuiRemoteProtocolChannelDeserializer<JsonToStringSerializer>`.
|
||||||
|
|
||||||
|
### Layer 3: WString ↔ Transport (User-Implemented)
|
||||||
|
|
||||||
|
The user provides an `IGuiRemoteProtocolChannel<WString>` implementation that sends and receives strings over any transport mechanism (named pipe, HTTP, WebSocket, etc.). GacUI does not provide transport implementations directly.
|
||||||
|
|
||||||
|
### Layer 4: UTF String Conversion (Optional)
|
||||||
|
|
||||||
|
`GuiRemoteUtfStringChannelSerializer` / `GuiRemoteUtfStringChannelDeserializer` can convert between `WString` and other UTF string types if the transport requires a specific encoding.
|
||||||
|
|
||||||
|
### Channel Transformer Pattern
|
||||||
|
|
||||||
|
`GuiRemoteProtocolChannelTransformerBase<TFrom, TTo>` bridges two channel types:
|
||||||
|
- `GuiRemoteProtocolChannelSerializer<TSerialization>`: Calls `TSerialization::Serialize` on `Write` and `TSerialization::Deserialize` on `OnReceive`.
|
||||||
|
- `GuiRemoteProtocolChannelDeserializer<TSerialization>`: The reverse — calls `Deserialize` on `Write` and `Serialize` on `OnReceive`.
|
||||||
|
|
||||||
|
The `TSerialization` concept requires: `SourceType`, `DestType`, `ContextType`, and static `Serialize`/`Deserialize` methods.
|
||||||
|
|
||||||
|
### JSON Envelope Format
|
||||||
|
|
||||||
|
Every package is a JSON object with fields:
|
||||||
|
- `"semantic"`: One of `"Message"`, `"Request"`, `"Response"`, `"Event"`.
|
||||||
|
- `"id"`: (optional) An integer request/response ID, present only for `Request` and `Response`.
|
||||||
|
- `"name"`: The message/event name (e.g., `"ControllerGetFontConfig"`, `"IOMouseMoving"`).
|
||||||
|
- `"arguments"`: (optional) The serialized arguments as a JSON value.
|
||||||
|
|
||||||
|
Messages (no response expected) use `"Message"`. Requests (response expected) use `"Request"` with an ID. Responses use `"Response"` with the matching ID. Events use `"Event"`.
|
||||||
|
|
||||||
|
### JSON Serialization of Protocol Types
|
||||||
|
|
||||||
|
Protocol types are code-generated from `Protocol/*.txt` files into `GuiRemoteProtocolSchema.h`/`.cpp`. Each struct gets a `JsonHelper<T>` specialization with `ToJson` and `FromJson` methods. The shared infrastructure in `GuiRemoteProtocolSchemaShared.h` provides:
|
||||||
|
- Primitive type serializers: `bool`, `vint`, `float`, `double`, `WString`, `wchar_t`, `VKEY`, `Color`, `Ptr<MemoryStream>` (Base64-encoded).
|
||||||
|
- Generic container serializers: `Nullable<T>`, `Ptr<T>`, `List<T>`, `ArrayMap<K,V,F>`, `Dictionary<K,V>`.
|
||||||
|
- `Variant` types are serialized with a type discriminator field.
|
||||||
|
- `ConvertCustomTypeToJsonField` adds a named field to a JSON object.
|
||||||
|
|
||||||
|
### Async Channel
|
||||||
|
|
||||||
|
`GuiRemoteProtocolAsyncChannelSerializer<TPackage>` provides thread separation for real remote deployment:
|
||||||
|
- A **channel thread** handles all `Write`/`OnReceive` calls on the underlying channel (IO operations).
|
||||||
|
- A **UI thread** runs the GacUI application logic.
|
||||||
|
- `Start(channel, uiMainProc, startingProc)` launches both threads. The `startingProc` is responsible for creating the threads.
|
||||||
|
- Events received asynchronously are queued and dispatched on the UI thread during `Submit()`.
|
||||||
|
- Responses are matched to pending requests using a `PendingRequestGroup` with `connectionCounter` for safe handling of disconnection/reconnection races.
|
||||||
|
- `ExecuteInChannelThread()` allows queueing work from any thread to the channel thread.
|
||||||
|
|
||||||
|
## Demo Project Pair
|
||||||
|
|
||||||
|
Two projects in `Test/GacUISrc/` demonstrate a full remote protocol deployment. They are paired: one is the core side (console application) and the other is the renderer side (Windows application).
|
||||||
|
|
||||||
|
### RemotingTest_Core (Console Application)
|
||||||
|
|
||||||
|
Located at `Test/GacUISrc/RemotingTest_Core/`. Accepts `/Pipe` or `/Http` arguments to start either a named-pipe server or HTTP server.
|
||||||
|
|
||||||
|
**Protocol stack setup** (`StartServer<TServer>` in `GuiMain.cpp`):
|
||||||
|
1. Creates `CoreChannel` wrapping the transport server — implements `IGuiRemoteProtocolChannel<WString>`.
|
||||||
|
2. Creates `GuiRemoteJsonChannelStringSerializer` for JSON ↔ String conversion.
|
||||||
|
3. Creates `GuiRemoteProtocolAsyncJsonChannelSerializer` for async channel/UI thread separation.
|
||||||
|
4. Starts via `asyncChannelSender.Start()` with a UI main proc that builds the protocol stack: `GuiRemoteProtocolFromJsonChannel` → `GuiRemoteProtocolFilter` → `GuiRemoteProtocolDomDiffConverter` → `SetupRemoteNativeController`.
|
||||||
|
5. Waits for a renderer client to connect, then waits for the async channel to stop, then waits for disconnection.
|
||||||
|
|
||||||
|
**CoreChannel** bridges async channel and network transport:
|
||||||
|
- `Write(WString)`: Accumulates messages in a pending list.
|
||||||
|
- `Submit()`: Dispatches accumulated messages via `networkProtocol->SendStringArray()`.
|
||||||
|
- `OnReadStringThreadUnsafe()`: Called when strings arrive from the network, queues them for channel thread processing via `asyncChannel->ExecuteInChannelThread()`.
|
||||||
|
- Detects `ControllerConnect` event JSON to track connection state.
|
||||||
|
- On reconnection (`OnReconnectedUnsafe`), injects a `ControllerDisconnect` event.
|
||||||
|
|
||||||
|
### RemotingTest_Rendering_Win32 (Windows Application)
|
||||||
|
|
||||||
|
Located at `Test/GacUISrc/RemotingTest_Rendering_Win32/`. Accepts `/Pipe` or `/Http` arguments to start as a named-pipe or HTTP client.
|
||||||
|
|
||||||
|
**Protocol stack setup** (`StartClient<TClient>` in `GuiMain.cpp`):
|
||||||
|
1. Creates `GuiRemoteRendererSingle` — the renderer implementing `IGuiRemoteProtocol`.
|
||||||
|
2. Creates `GuiRemoteJsonChannelFromProtocol` wrapping the renderer — converts protocol calls to JSON.
|
||||||
|
3. Creates `GuiRemoteJsonChannelStringDeserializer` for String ↔ JSON conversion.
|
||||||
|
4. Creates `RendererChannel` connecting renderer, transport, and channel.
|
||||||
|
5. Hooks `BeforeWrite`/`BeforeOnReceive` events for request/response caching in `RendererChannel`.
|
||||||
|
6. Calls `SetupRawWindowsDirect2DRenderer()` to run the native window event loop.
|
||||||
|
|
||||||
|
**RendererChannel** bridges network transport and JSON channel:
|
||||||
|
- `OnReadStringThreadUnsafe()`: Receives strings from network, dispatches to UI thread via `InvokeInMainThread()`. Handles error strings (prefixed with `!`) by displaying a `MessageBox` and calling `renderer->ForceExitByFatelError()`.
|
||||||
|
- `OnReceive(WString)`: Uses a caching mechanism (`isCaching`/`cachedPackages`) to batch responses — when a `Request` semantic is detected (via `BeforeWrite`), caching is enabled; when a `Response` semantic is detected (via `BeforeOnReceive`), caching is disabled and all cached packages are sent as a batch.
|
||||||
|
|
||||||
|
### Protocol Stack Direction
|
||||||
|
|
||||||
|
**Core side** (messages flow outward):
|
||||||
|
```
|
||||||
|
SetupRemoteNativeController
|
||||||
|
→ GuiRemoteProtocolDomDiffConverter
|
||||||
|
→ GuiRemoteProtocolFilter
|
||||||
|
→ GuiRemoteProtocolFromJsonChannel
|
||||||
|
→ GuiRemoteProtocolAsyncJsonChannelSerializer
|
||||||
|
→ GuiRemoteJsonChannelStringSerializer
|
||||||
|
→ CoreChannel (IGuiRemoteProtocolChannel<WString>)
|
||||||
|
→ Network transport (user-implemented)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Renderer side** (messages flow inward):
|
||||||
|
```
|
||||||
|
Network transport (user-implemented)
|
||||||
|
→ RendererChannel
|
||||||
|
→ GuiRemoteJsonChannelStringDeserializer
|
||||||
|
→ GuiRemoteJsonChannelFromProtocol
|
||||||
|
→ GuiRemoteRendererSingle (IGuiRemoteProtocol)
|
||||||
|
→ Native window rendering
|
||||||
|
```
|
||||||
|
|
||||||
|
Events flow in the reverse direction through the same stack.
|
||||||
313
.github/KnowledgeBase/KB_GacUI_RemoteProtocolUnitTestFramework.md
vendored
Normal file
313
.github/KnowledgeBase/KB_GacUI_RemoteProtocolUnitTestFramework.md
vendored
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
# Remote Protocol Unit Test Framework
|
||||||
|
|
||||||
|
The remote protocol unit test framework allows testing GacUI applications without real OS windows or rendering. It reuses the same remote protocol architecture as production remote deployment, replacing the renderer side with a mock implementation (`UnitTestRemoteProtocol`) that captures rendering results as snapshots and feeds simulated user input through the protocol pipeline. The GacUI core side runs identically to production.
|
||||||
|
|
||||||
|
Implementation lives in `Source/UnitTestUtilities/`.
|
||||||
|
|
||||||
|
## Comprehensive Example
|
||||||
|
|
||||||
|
Below is a complete example demonstrating the key concepts of the framework: test executable setup, GacUI XML resource definition, frame-based interaction with named snapshots, control lookup, and mouse input simulation.
|
||||||
|
|
||||||
|
### Test Executable Entry Point
|
||||||
|
|
||||||
|
The test executable's `main` function initializes the framework once, runs all test cases, and finalizes:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int UnitTestMain(int argc, T* argv[])
|
||||||
|
{
|
||||||
|
UnitTestFrameworkConfig config;
|
||||||
|
config.snapshotFolder = GetTestSnapshotPath();
|
||||||
|
config.resourceFolder = GetTestDataPath();
|
||||||
|
|
||||||
|
GacUIUnitTest_Initialize(&config);
|
||||||
|
int result = UnitTest::RunAndDisposeTests(argc, argv);
|
||||||
|
GacUIUnitTest_Finalize();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### A Test Case: Clicking a Button
|
||||||
|
|
||||||
|
This example defines a GacUI XML resource with a window containing an OK button, then tests hovering, pressing, and releasing the button in separate frames. Each frame callback describes the action it performs; the frame name is assigned to the **rendering snapshot that results from the previous frame's action**.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const auto resource = LR"GacUISrc(
|
||||||
|
<Resource>
|
||||||
|
<Instance name="MainWindowResource">
|
||||||
|
<Instance ref.Class="gacuisrc_unittest::MainWindow">
|
||||||
|
<Window ref.Name="self" Text="Hello, world!" ClientSize="x:320 y:240">
|
||||||
|
<Button ref.Name="buttonOK" Text="OK">
|
||||||
|
<att.BoundsComposition-set AlignmentToParent="left:5 top:5 right:-1 bottom:-1"/>
|
||||||
|
<ev.Clicked-eval><![CDATA {
|
||||||
|
Application::GetApplication().InvokeInMainThread(self, func():void{
|
||||||
|
// changing UI structures that affects the sender of an event
|
||||||
|
// is supposed to be put in InvokeInMainThread
|
||||||
|
// Otherwise followed up events on the same control may gone
|
||||||
|
self.Close();
|
||||||
|
});
|
||||||
|
}]]></ev.Clicked-eval>
|
||||||
|
</Button>
|
||||||
|
</Window>
|
||||||
|
</Instance>
|
||||||
|
</Instance>
|
||||||
|
</Resource>
|
||||||
|
)GacUISrc";
|
||||||
|
|
||||||
|
TEST_CASE(L"Click Button")
|
||||||
|
{
|
||||||
|
GacUIUnitTest_SetGuiMainProxy([](UnitTestRemoteProtocol* protocol, IUnitTestContext*)
|
||||||
|
{
|
||||||
|
// Frame 0: snapshot named "Ready" — shows the initial rendering after the window opens.
|
||||||
|
// Callback action: move the mouse on the OK button.
|
||||||
|
protocol->OnNextIdleFrame(L"Ready", [=]()
|
||||||
|
{
|
||||||
|
auto window = GetApplication()->GetMainWindow();
|
||||||
|
auto buttonOK = TryFindObjectByName<GuiButton>(window, L"buttonOK");
|
||||||
|
// MouseMove(location) + LClick() could be merged into one operation
|
||||||
|
// LClick(location) will perform both
|
||||||
|
// but when the side effect of MouseMove is important
|
||||||
|
// you are given choices
|
||||||
|
protocol->MouseMove(protocol->LocationOf(buttonOK));
|
||||||
|
});
|
||||||
|
// Frame 1: snapshot named "Hover" — shows the button in hover state (result of MouseMove above).
|
||||||
|
// Callback action: click the OK button, triggering Clicked → window closes.
|
||||||
|
protocol->OnNextIdleFrame(L"Hover", [=]()
|
||||||
|
{
|
||||||
|
protocol->LClick();
|
||||||
|
});
|
||||||
|
// If the user interaction above does not cause the application to exit,
|
||||||
|
// an additional frame is needed to close the window explicitly:
|
||||||
|
//
|
||||||
|
// protocol->OnNextIdleFrame(L"Frame name describing what the previous frame does", [=]()
|
||||||
|
// {
|
||||||
|
// auto window = GetApplication()->GetMainWindow();
|
||||||
|
// window->Hide();
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
GacUIUnitTest_StartFast_WithResourceAsText<darkskin::Theme>(
|
||||||
|
WString::Unmanaged(L"UnitTestFramework/WindowWithOKButton"),
|
||||||
|
WString::Unmanaged(L"gacuisrc_unittest::MainWindow"),
|
||||||
|
resource
|
||||||
|
);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### What This Example Demonstrates
|
||||||
|
|
||||||
|
- **`GacUIUnitTest_SetGuiMainProxy`**: Registers the test's frame callback function. It receives `UnitTestRemoteProtocol*` (for input simulation and snapshot access) and `IUnitTestContext*`.
|
||||||
|
- **`OnNextIdleFrame(name, callback)`**: Registers a callback to run after the next rendering frame settles. The `name` is attached to the snapshot that was just captured (the rendering result), not to what the callback will do.
|
||||||
|
- **`TryFindObjectByName<T>(window, name)`**: Looks up a named control from the GacUI XML resource by its `ref.Name`.
|
||||||
|
- **`protocol->LocationOf(control)`**: Computes the absolute screen coordinate of the center of a control, for use with mouse input methods.
|
||||||
|
- **`LClick(location)`**: Simulates a full left mouse click (mouse move + button down + button up) at the given location. Use this for simple clicks instead of separate `_LDown`/`_LUp` calls.
|
||||||
|
- **`GacUIUnitTest_StartFast_WithResourceAsText<Theme>`**: Compiles the XML resource, registers the theme, creates the window, runs the application, and captures snapshots — all in one call.
|
||||||
|
- **Frame name semantics**: `"Ready"` names the initial rendering. Each snapshot file (`frame_0.json`) records the full rendering DOM at that point.
|
||||||
|
- **Closing the window**: The button's click handler uses `InvokeInMainThread` to defer `self.Close()`. This is necessary because `Close()` would otherwise be called synchronously inside the IO event dispatch, which could block the frame callback (see the blocking function caveat below). If user interaction does not cause the application to exit, an extra frame must be added at the end to call `window->Hide()` explicitly.
|
||||||
|
|
||||||
|
## Test Executable Initialization and Finalization
|
||||||
|
|
||||||
|
### Global Setup (GacUIUnitTest_Initialize)
|
||||||
|
|
||||||
|
Each test executable's `main` calls `GacUIUnitTest_Initialize(&config)` with a `UnitTestFrameworkConfig` containing `snapshotFolder` and `resourceFolder`. This function:
|
||||||
|
- Sets `GACUI_UNITTEST_ONLY_SKIP_THREAD_LOCAL_STORAGE_DISPOSE_STORAGES` and `GACUI_UNITTEST_ONLY_SKIP_TYPE_AND_PLUGIN_LOAD_UNLOAD` to `true`, allowing the type manager and plugin system to be loaded once and reused across all test cases.
|
||||||
|
- Calls `GetGlobalTypeManager()->Load()` and `GetPluginManager()->Load(true, false)` once.
|
||||||
|
|
||||||
|
### Global Teardown (GacUIUnitTest_Finalize)
|
||||||
|
|
||||||
|
Called after all tests complete. Tears down the type manager and plugin manager.
|
||||||
|
|
||||||
|
## Per-Test-Case Setup and the Proxy Chaining Pattern
|
||||||
|
|
||||||
|
### Registering Frame Callbacks
|
||||||
|
|
||||||
|
Each test case follows a standard pattern:
|
||||||
|
1. Call `GacUIUnitTest_SetGuiMainProxy(callback)` to register a `UnitTestMainFunc` (signature: `void(UnitTestRemoteProtocol*, IUnitTestContext*)`). This is the test's frame-based action callback that registers idle frame handlers via `OnNextIdleFrame`.
|
||||||
|
2. Optionally call `GacUIUnitTest_LinkGuiMainProxy(linkCallback)` to wrap additional setup around the test proxy.
|
||||||
|
3. Call one of the start functions to launch the test.
|
||||||
|
|
||||||
|
### Proxy Chaining (GacUIUnitTest_LinkGuiMainProxy)
|
||||||
|
|
||||||
|
`GacUIUnitTest_LinkGuiMainProxy` captures the current proxy as `previousMainProxy` and installs a new proxy that calls the link function with the previous proxy as a parameter. This enables decorator-style composition — each link layer can perform setup before delegating to the inner proxy and cleanup after.
|
||||||
|
|
||||||
|
### GacUIUnitTest_StartFast_WithResourceAsText
|
||||||
|
|
||||||
|
This template function is the most commonly used entry point. It internally calls `GacUIUnitTest_LinkGuiMainProxy` to inject:
|
||||||
|
1. Sending `OnControllerConnect` with `ControllerGlobalConfig` to the protocol.
|
||||||
|
2. Registering a theme (e.g., `DarkSkin`).
|
||||||
|
3. Compiling the GacUI XML resource via `GacUIUnitTest_CompileAndLoad`.
|
||||||
|
4. Creating the main window from the resource via `Value::Create(windowTypeFullName)`.
|
||||||
|
5. Calling `previousMainProxy(protocol, context)` to let the test register its frame callbacks.
|
||||||
|
6. Running the application via `GetApplication()->Run(window)`.
|
||||||
|
7. Unregistering the theme on cleanup.
|
||||||
|
|
||||||
|
It also saves the compiled Workflow script text as a snapshot file (`[x64].txt` or `[x86].txt`) for Workflow generation stability verification.
|
||||||
|
|
||||||
|
### Start Functions
|
||||||
|
|
||||||
|
- `GacUIUnitTest_Start(appName, config)` — synchronous in-process test with optional serialization channel.
|
||||||
|
- `GacUIUnitTest_StartAsync(appName, config)` — async test with core and renderer on separate threads.
|
||||||
|
- `GacUIUnitTest_Start_WithResourceAsText(appName, config, resourceText)` — wraps resource compilation via `GacUIUnitTest_LinkGuiMainProxy`, then delegates to `GacUIUnitTest_Start` or `GacUIUnitTest_StartAsync` depending on `config.useChannel`.
|
||||||
|
|
||||||
|
## Protocol Stack Construction
|
||||||
|
|
||||||
|
### Synchronous Mode (GacUIUnitTest_Start)
|
||||||
|
|
||||||
|
`GacUIUnitTest_Start` constructs the full protocol stack in-process:
|
||||||
|
|
||||||
|
**Renderer side (deserialization direction):**
|
||||||
|
- `UnitTestRemoteProtocol` — mock `IGuiRemoteProtocol` implementation.
|
||||||
|
- `GuiRemoteJsonChannelFromProtocol` — converts protocol calls to JSON.
|
||||||
|
- `GuiRemoteJsonChannelStringDeserializer` — JSON to String.
|
||||||
|
- `GuiRemoteUtfStringChannelDeserializer<wchar_t, char8_t>` — String to UTF-8.
|
||||||
|
|
||||||
|
**Core side (serialization direction, mirrors back):**
|
||||||
|
- `GuiRemoteUtfStringChannelSerializer<wchar_t, char8_t>` — UTF-8 to String.
|
||||||
|
- `GuiRemoteJsonChannelStringSerializer` — String to JSON.
|
||||||
|
- `GuiRemoteProtocolFromJsonChannel` — JSON to typed protocol calls.
|
||||||
|
|
||||||
|
**Protocol filter layers on core side:**
|
||||||
|
- `GuiRemoteProtocolFilterVerifier` — validates repeat-filtering invariants.
|
||||||
|
- `GuiRemoteProtocolFilter` — filters redundant messages.
|
||||||
|
- `GuiRemoteProtocolDomDiffConverter` — (optional, when `useDomDiff` is true) converts full DOM to DOM diffs.
|
||||||
|
|
||||||
|
When `useChannel == UnitTestRemoteChannel::None`, the verifier directly wraps `UnitTestRemoteProtocol`'s `IGuiRemoteProtocol`, bypassing the serialization layers for speed. Otherwise, the full serialization channel is used for testing round-trip fidelity.
|
||||||
|
|
||||||
|
Finally, `SetupRemoteNativeController(protocol)` creates the runtime stack: `GuiRemoteController` → `GuiHostedController` → resource managers, then calls `GuiApplicationMain()` → `GuiMain()` → the registered test proxy.
|
||||||
|
|
||||||
|
### Async Mode (GacUIUnitTest_StartAsync)
|
||||||
|
|
||||||
|
`GacUIUnitTest_StartAsync` inserts `GuiRemoteProtocolAsyncJsonChannelSerializer` into the channel, placing the core and renderer on separate threads. Two threads are spawned via `RunInNewThread`: a channel thread for serialization I/O and a UI thread for the GacUI application. The call waits for `asyncChannelSender.WaitForStopped()` before writing snapshots.
|
||||||
|
|
||||||
|
## UnitTestRemoteProtocol Class Hierarchy
|
||||||
|
|
||||||
|
`UnitTestRemoteProtocol` inherits from `UnitTestRemoteProtocolFeatures` and `IGuiRemoteEventProcessor`. Its base classes are:
|
||||||
|
- `UnitTestRemoteProtocolBase` — holds `IGuiRemoteProtocolEvents*`, `UnitTestScreenConfig`, default `Impl_*` stubs, `Initialize`, `GetExecutablePath`.
|
||||||
|
- `UnitTestRemoteProtocol_MainWindow` — simulates window management (bounds, sizing config, styles).
|
||||||
|
- `UnitTestRemoteProtocol_IO` — simulates controller connection and IO event forwarding.
|
||||||
|
- `UnitTestRemoteProtocol_Rendering` — simulates element creation/destruction, rendering begin/end, DOM capture.
|
||||||
|
- `UnitTestRemoteProtocol_IOCommands` — provides high-level input simulation methods.
|
||||||
|
- `UnitTestRemoteProtocolFeatures` — combines all feature bases and implements `LogRenderingResult()`, the core frame capture mechanism.
|
||||||
|
|
||||||
|
`UnitTestRemoteProtocol` adds:
|
||||||
|
- `processRemoteEvents` — a list of `(Nullable<WString>, Func<void()>)` pairs representing named frame callbacks.
|
||||||
|
- `OnNextIdleFrame(callback)` and `OnNextIdleFrame(name, callback)` — register callbacks to execute after specific rendering frames settle.
|
||||||
|
- `ProcessRemoteEvents()` — the main event processing method called each cycle.
|
||||||
|
|
||||||
|
## The Main Event Loop
|
||||||
|
|
||||||
|
`GuiRemoteController::RunOneCycle()` drives the core side per cycle:
|
||||||
|
1. `remoteProtocol->GetRemoteEventProcessor()->ProcessRemoteEvents()` — this invokes `UnitTestRemoteProtocol::ProcessRemoteEvents()`.
|
||||||
|
2. `remoteMessages.Submit(disconnected)` — flushes pending protocol messages.
|
||||||
|
3. `callbackService.InvokeGlobalTimer()` — triggers `GuiHostedController::GlobalTimer()`, which performs layout and rendering, generating new DOM/element data.
|
||||||
|
4. `asyncService.ExecuteAsyncTasks()`.
|
||||||
|
|
||||||
|
The cycle repeats until `connectionStopped` is true (application closes).
|
||||||
|
|
||||||
|
## Frame Capture and Snapshot Mechanism
|
||||||
|
|
||||||
|
### Passive Rendering Capture (UnitTestRemoteProtocol_Rendering)
|
||||||
|
|
||||||
|
Each time the GacUI core calls `RequestRendererBeginRendering`, `UnitTestRemoteProtocol_Rendering::Impl_RendererBeginRendering` creates a new `UnitTestLoggedFrame` and records the `frameId` from the core's monotonic counter. When `Impl_RendererEndRendering` is called, the frame's `renderingDom` is captured from the rendering commands.
|
||||||
|
|
||||||
|
### Active Frame Logging (LogRenderingResult)
|
||||||
|
|
||||||
|
`LogRenderingResult()` is called at the start of each `ProcessRemoteEvents()` cycle in `UnitTestRemoteProtocolFeatures`:
|
||||||
|
1. `TryGetLastRenderingFrameAndReset()` checks if a rendering frame completed since the last check. If yes, it becomes the `candidateFrame`.
|
||||||
|
2. On the next cycle where no new rendering occurred (the UI has settled), the `candidateFrame` is committed to `loggedTrace.frames` with a copy of `lastElementDescs` and `sizingConfig`. This ensures the snapshot captures the final stable rendering state after all layout passes.
|
||||||
|
3. `LogRenderingResult()` returns `true`, signaling that a frame is ready.
|
||||||
|
4. In `ProcessRemoteEvents()`, the `frameName` is set on the just-committed frame (from the callback's registered name), and then the callback function executes.
|
||||||
|
5. If 100 consecutive cycles pass without any rendering change after previously rendering, the test fails with an error.
|
||||||
|
|
||||||
|
### Frame Name Semantics
|
||||||
|
|
||||||
|
The frame name is assigned to the **already-committed snapshot** (the rendering result), then the callback executes. This means:
|
||||||
|
- The `frameName` describes what **led to** the current visual state, not what the callback will do next.
|
||||||
|
- The first frame is conventionally named `"Ready"`, representing the initial rendering state after the window opens.
|
||||||
|
- Subsequent frame names describe the action taken in the previous callback (e.g., `"Hover"` means the previous callback moved the mouse, and this snapshot shows the hover state).
|
||||||
|
|
||||||
|
The sequence is:
|
||||||
|
1. Application renders → DOM/elements captured as `candidateFrame`.
|
||||||
|
2. UI settles (no more rendering) → `candidateFrame` committed to `loggedTrace.frames`.
|
||||||
|
3. `frameName` is set on the committed frame from the callback registration.
|
||||||
|
4. Frame callback executes (may trigger events → triggering more rendering).
|
||||||
|
5. Return to step 1.
|
||||||
|
|
||||||
|
### Snapshot File Structure
|
||||||
|
|
||||||
|
For a test named `Controls/Basic/GuiButton/ClickOnMouseUp`:
|
||||||
|
- **Main trace file**: `{snapshotFolder}/Controls/Basic/GuiButton/ClickOnMouseUp.json` — `UnitTest_RenderingTrace` with `createdElements`, `imageCreations`, `imageMetadatas`, and `frames` (each frame only has `frameId` and `frameName`, detail stripped).
|
||||||
|
- **Per-frame files**: `{snapshotFolder}/Controls/Basic/GuiButton/ClickOnMouseUp/frame_0.json`, `frame_1.json`, etc. — full `UnitTest_RenderingFrame` with `frameId`, `frameName`, `windowSize`, `elements` (all rendered element descriptions keyed by ID), and `root` (the rendering DOM tree).
|
||||||
|
- **Workflow snapshot**: `ClickOnMouseUp[x64].txt` or `[x86].txt` — compiled Workflow script text for generation stability verification.
|
||||||
|
- **Commands log**: `ClickOnMouseUp[commands].txt` — rendering command logs per frame (when not using DOM diff).
|
||||||
|
- **Diffs log**: `ClickOnMouseUp[diffs].txt` — DOM diff logs per frame.
|
||||||
|
|
||||||
|
### Write Strategy (GacUIUnitTest_LogUI)
|
||||||
|
|
||||||
|
`GacUIUnitTest_LogUI` performs the following:
|
||||||
|
1. Serializes the full `loggedTrace` to JSON and verifies round-trip fidelity (serialize → deserialize → re-serialize must match).
|
||||||
|
2. Writes each frame as an individual `frame_N.json` file via `GacUIUnitTest_WriteSnapshotFileIfChanged`, which only writes if content differs from the existing file (enabling clean `git diff`).
|
||||||
|
3. Strips frame detail from the in-memory trace (keeping only `frameId` and `frameName`) and writes the main trace file.
|
||||||
|
4. Deletes any leftover `frame_*.json` files that no longer correspond to actual frames.
|
||||||
|
|
||||||
|
## User Interaction Simulation
|
||||||
|
|
||||||
|
### UnitTestRemoteProtocol_IOCommands
|
||||||
|
|
||||||
|
`UnitTestRemoteProtocol_IOCommands` provides methods to simulate all user input. It maintains virtual state:
|
||||||
|
- `mousePosition` — current mouse position (`Nullable<NativePoint>`, initially unset; first `MouseMove` triggers `OnIOMouseEntered`).
|
||||||
|
- `pressingKeys` — `SortedList<VKEY>` of currently pressed keys.
|
||||||
|
- `leftPressing`, `middlePressing`, `rightPressing` — mouse button states.
|
||||||
|
- `capslockToggled` — capslock toggle state.
|
||||||
|
|
||||||
|
Each method constructs the appropriate `NativeWindowMouseInfo`, `NativeWindowKeyInfo`, or `NativeWindowCharInfo` struct (via `MakeMouseInfo()`, `MakeKeyInfo()`, `MakeCharInfo()`) and calls the corresponding event on `IGuiRemoteProtocolEvents` (obtained via `UseEvents()`).
|
||||||
|
|
||||||
|
### Location Calculation (LocationOf)
|
||||||
|
|
||||||
|
`LocationOf(controlOrComposition, ratioX, ratioY, offsetX, offsetY)` computes the target point in absolute screen coordinates:
|
||||||
|
1. Gets the composition's `GetGlobalBounds()` (logical coordinates).
|
||||||
|
2. Converts to native coordinates using `nativeWindow->Convert()`.
|
||||||
|
3. Applies the ratio (default 0.5 = center) and offset.
|
||||||
|
4. Adds the window's screen position `nativeWindow->GetBounds().LeftTop()`.
|
||||||
|
|
||||||
|
Overloads accept either `GuiGraphicsComposition*` or `GuiControl*`.
|
||||||
|
|
||||||
|
### Mouse Input Methods
|
||||||
|
|
||||||
|
- `MouseMove(location)` — sends `OnIOMouseMoving`. If mouse was previously unset, first sends `OnIOMouseEntered`.
|
||||||
|
- `_LDown(location)` / `_LUp(location)` — low-level left button down/up. `_LDown` calls `MouseMove` if position changed, then `UseEvents().OnIOButtonDown({Left, ...})`.
|
||||||
|
- `LClick(location)` — `_LDown` followed by `_LUp`.
|
||||||
|
- `LDBClick(location)` — two `LClick` calls (the framework detects double click from timing).
|
||||||
|
- Analogous `RClick`, `MClick`, `RDBClick`, `MDBClick` for right and middle buttons.
|
||||||
|
- `WheelUp(jumps)` / `WheelDown(jumps)` — `OnIOVWheel` with delta scaled by 120 per jump.
|
||||||
|
- `HWheelLeft(jumps)` / `HWheelRight(jumps)` — `OnIOHWheel` similarly.
|
||||||
|
|
||||||
|
### Key and Character Input Methods
|
||||||
|
|
||||||
|
- `KeyDown(key)` / `KeyUp(key)` — sends `OnIOKeyDown` / `OnIOKeyUp`. Tracks `pressingKeys` state. Special handling for `VKEY::KEY_CAPITAL` toggles `capslockToggled`.
|
||||||
|
- `KeyPress(key)` — `KeyDown` followed by `KeyUp`.
|
||||||
|
- `KeyPress(key, ctrl, shift, alt)` — wraps the key press with modifier key down/up events.
|
||||||
|
- `TypeString(text)` — sends a sequence of `OnIOChar` events for each character via `MakeCharInfo`, without synthesizing key down/up events.
|
||||||
|
|
||||||
|
### Event Flow Through the Pipeline
|
||||||
|
|
||||||
|
When a test calls `protocol->LClick(location)`:
|
||||||
|
1. `_LDown(location)` → `MouseMove(location)` if position changed → `UseEvents().OnIOButtonDown({Left, MakeMouseInfo()})`.
|
||||||
|
2. `_LUp(location)` → `UseEvents().OnIOButtonUp({Left, MakeMouseInfo()})`.
|
||||||
|
3. These events reach `GuiRemoteEvents` (the `IGuiRemoteProtocolEvents` implementation in `GuiRemoteController`).
|
||||||
|
4. `GuiRemoteEvents::OnIOButtonDown()` dispatches to `listener->LeftButtonDown(info)` on `remoteWindow`.
|
||||||
|
5. `GuiGraphicsHost` receives the call as `INativeWindowListener`.
|
||||||
|
6. `GuiGraphicsHost::LeftButtonDown()` calls `OnMouseInput()`, which converts native coordinates to logical coordinates, performs hit-testing via `FindVisibleComposition()`, creates `GuiMouseEventArgs`, and dispatches via `RaiseMouseEvent()`.
|
||||||
|
7. The event bubbles through the composition tree, triggering control-level handlers.
|
||||||
|
|
||||||
|
All of this happens **synchronously within a single `ProcessRemoteEvents()` call**, so by the time the frame callback returns, all side effects of the interaction have been processed.
|
||||||
|
|
||||||
|
### Blocking Function Caveat
|
||||||
|
|
||||||
|
If an IO action triggers a blocking function (like `ShowDialog`), the frame callback would never return. The unit test framework detects this via the `frameExecuting` flag and fails. The solution is to wrap the IO action in `GetApplication()->InvokeInMainThread(window, [=]() { protocol->LClick(location); })`, which defers the action to the next event loop iteration.
|
||||||
|
|
||||||
|
## Running Multiple Test Applications
|
||||||
|
|
||||||
|
Each call to `GacUIUnitTest_Start` runs one complete application lifecycle (from `GuiMain()` to window close). Typically each `TEST_CASE` calls the full `GacUIUnitTest_SetGuiMainProxy` → `GacUIUnitTest_Start` sequence independently.
|
||||||
|
|
||||||
|
Within a single application instance, multiple windows can be created inside frame callbacks using `Value::Create(L"namespace::ClassName")` and shown with `Show()`, `ShowModal()`, or `ShowDialog()`.
|
||||||
|
|
||||||
|
`GacUIUnitTest_LinkGuiMainProxy` can be called multiple times before `GacUIUnitTest_Start`, chaining multiple setup layers (e.g., one for resources, one for theme, one for additional initialization).
|
||||||
14
.github/Scripts/copilotBuild.ps1
vendored
14
.github/Scripts/copilotBuild.ps1
vendored
@@ -1,3 +1,15 @@
|
|||||||
|
param(
|
||||||
|
[string]$Configuration = "Debug",
|
||||||
|
[string]$Platform = "x64"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (($Configuration -ne "Debug") -and ($Configuration -ne "Release")) {
|
||||||
|
throw "Invalid configuration: $Configuration. Allowed values are Debug or Release."
|
||||||
|
}
|
||||||
|
if (($Platform -ne "x64") -and ($Platform -ne "Win32")) {
|
||||||
|
throw "Invalid platform: $Platform. Allowed values are x64 or Win32."
|
||||||
|
}
|
||||||
|
|
||||||
. $PSScriptRoot\copilotShared.ps1
|
. $PSScriptRoot\copilotShared.ps1
|
||||||
|
|
||||||
# Remove log files
|
# Remove log files
|
||||||
@@ -17,8 +29,6 @@ if ($vsdevcmd -eq $null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Execute msbuild with output to both console and log file
|
# Execute msbuild with output to both console and log file
|
||||||
$Configuration = "Debug"
|
|
||||||
$Platform = "x64"
|
|
||||||
$msbuild_arguments = "MSBUILD `"$solutionFile`" /m:8 $rebuildControl /p:Configuration=`"$Configuration`";Platform=`"$Platform`""
|
$msbuild_arguments = "MSBUILD `"$solutionFile`" /m:8 $rebuildControl /p:Configuration=`"$Configuration`";Platform=`"$Platform`""
|
||||||
$cmd_arguments = "`"`"$vsdevcmd`" & $msbuild_arguments"
|
$cmd_arguments = "`"`"$vsdevcmd`" & $msbuild_arguments"
|
||||||
|
|
||||||
|
|||||||
49
.github/Scripts/copilotExecute.ps1
vendored
49
.github/Scripts/copilotExecute.ps1
vendored
@@ -1,14 +1,36 @@
|
|||||||
param(
|
param(
|
||||||
[Parameter(Mandatory=$true)]
|
[Parameter(Mandatory=$true)]
|
||||||
[string]$Executable
|
[string]$Mode,
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$Executable,
|
||||||
|
[string]$Configuration = $null,
|
||||||
|
[string]$Platform = $null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (($Mode -ne "CLI") -and ($Mode -ne "UnitTest")) {
|
||||||
|
throw "Invalid mode: $Mode. Allowed values are CLI or UnitTest."
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($Configuration -ne $null) -ne ($Platform -ne $null)) {
|
||||||
|
throw "Configuration and Platform parameters should be set or unset at the same time."
|
||||||
|
}
|
||||||
|
if ($Configuration -ne $null) {
|
||||||
|
if (($Configuration -ne "Debug") -and ($Configuration -ne "Release")) {
|
||||||
|
throw "Invalid configuration: $Configuration. Allowed values are Debug or Release."
|
||||||
|
}
|
||||||
|
if (($Platform -ne "x64") -and ($Platform -ne "Win32")) {
|
||||||
|
throw "Invalid platform: $Platform. Allowed values are x64 or Win32."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
. $PSScriptRoot\copilotShared.ps1
|
. $PSScriptRoot\copilotShared.ps1
|
||||||
|
|
||||||
# Remove log files
|
if ($Mode -eq "UnitTest") {
|
||||||
$logFile = "$PSScriptRoot\Execute.log"
|
# Remove log files
|
||||||
$logFileUnfinished = "$logFile.unfinished"
|
$logFile = "$PSScriptRoot\Execute.log"
|
||||||
Remove-Item -Path $logFile, $logFileUnfinished -Force -ErrorAction SilentlyContinue
|
$logFileUnfinished = "$logFile.unfinished"
|
||||||
|
Remove-Item -Path $logFile, $logFileUnfinished -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
# Ensure the executable name has .exe extension
|
# Ensure the executable name has .exe extension
|
||||||
if ($Executable.EndsWith(".exe")) {
|
if ($Executable.EndsWith(".exe")) {
|
||||||
@@ -20,14 +42,23 @@ $executableName = $Executable + ".exe"
|
|||||||
$solutionFolder = GetSolutionDir
|
$solutionFolder = GetSolutionDir
|
||||||
|
|
||||||
# Find the file with the latest modification time
|
# Find the file with the latest modification time
|
||||||
$latestFile = GetLatestModifiedExecutable $solutionFolder $executableName
|
if ($Configuration -ne $null) {
|
||||||
|
$latestFile = GetLatestModifiedExecutable $solutionFolder $executableName
|
||||||
|
} else {
|
||||||
|
$latestFile = GetSpecifiedExecutable $solutionFolder $executableName $Configuration $Platform
|
||||||
|
}
|
||||||
Write-Host "Selected $executableName`: $($latestFile.Path) (Modified: $($latestFile.LastWriteTime))"
|
Write-Host "Selected $executableName`: $($latestFile.Path) (Modified: $($latestFile.LastWriteTime))"
|
||||||
|
|
||||||
# Try to read debug arguments from the corresponding .vcxproj.user file
|
# Try to read debug arguments from the corresponding .vcxproj.user file
|
||||||
$debugArgs = GetDebugArgs $solutionFolder $latestFile $Executable
|
$debugArgs = GetDebugArgs $solutionFolder $latestFile $Executable
|
||||||
|
|
||||||
# Execute the selected executable with debug arguments and save output to log file
|
# Execute the selected executable with debug arguments and save output to log file
|
||||||
$commandLine = "`"$($latestFile.Path)`" /C $debugArgs"
|
if ($Mode -eq "UnitTest") {
|
||||||
& { $commandLine; & cmd.exe /S /C $commandLine 2>&1 } | Tee-Object -FilePath $logFileUnfinished
|
$commandLine = "`"$($latestFile.Path)`" /C $debugArgs"
|
||||||
Rename-Item -Path $logFileUnfinished -NewName $logFile -Force
|
& { $commandLine; & cmd.exe /S /C $commandLine 2>&1 } | Tee-Object -FilePath $logFileUnfinished
|
||||||
|
Rename-Item -Path $logFileUnfinished -NewName $logFile -Force
|
||||||
|
} else {
|
||||||
|
$commandLine = "`"$($latestFile.Path)`" $debugArgs"
|
||||||
|
& { $commandLine; & cmd.exe /S /C $commandLine 2>&1 }
|
||||||
|
}
|
||||||
exit $LASTEXITCODE
|
exit $LASTEXITCODE
|
||||||
|
|||||||
31
.github/Scripts/copilotShared.ps1
vendored
31
.github/Scripts/copilotShared.ps1
vendored
@@ -28,14 +28,37 @@ function GetSolutionDir {
|
|||||||
return $solutionFolder
|
return $solutionFolder
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetLatestModifiedExecutable($solutionFolder, $executableName) {
|
function GetExecutableMap($solutionFolder, $executableName) {
|
||||||
# Define configuration to path mappings
|
return @{
|
||||||
$configToPathMap = @{
|
|
||||||
"Debug|Win32" = "$solutionFolder\Debug\$executableName"
|
"Debug|Win32" = "$solutionFolder\Debug\$executableName"
|
||||||
"Release|Win32" = "$solutionFolder\Release\$executableName"
|
"Release|Win32" = "$solutionFolder\Release\$executableName"
|
||||||
"Debug|x64" = "$solutionFolder\x64\Debug\$executableName"
|
"Debug|x64" = "$solutionFolder\x64\Debug\$executableName"
|
||||||
"Release|x64" = "$solutionFolder\x64\Release\$executableName"
|
"Release|x64" = "$solutionFolder\x64\Release\$executableName"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetSpecifiedExecutable($solutionFolder, $executableName, $configuration, $platform) {
|
||||||
|
# Define configuration to path mappings
|
||||||
|
$configToPathMap = GetExecutableMap $solutionFolder $executableName
|
||||||
|
|
||||||
|
# Find existing files and get their modification times with configuration info
|
||||||
|
$path = $configToPathMap["$configuration|$platform"];
|
||||||
|
|
||||||
|
if (Test-Path $path) {
|
||||||
|
$fileInfo = Get-Item $path
|
||||||
|
return [PSCustomObject]@{
|
||||||
|
Path = $path
|
||||||
|
Configuration = $config
|
||||||
|
LastWriteTime = $fileInfo.LastWriteTime
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw "No $executableName for $configuration|$platform does not exist, please make sure the build was successful."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetLatestModifiedExecutable($solutionFolder, $executableName) {
|
||||||
|
# Define configuration to path mappings
|
||||||
|
$configToPathMap = GetExecutableMap $solutionFolder $executableName
|
||||||
|
|
||||||
# Find existing files and get their modification times with configuration info
|
# Find existing files and get their modification times with configuration info
|
||||||
$existingFiles = @()
|
$existingFiles = @()
|
||||||
@@ -52,7 +75,7 @@ function GetLatestModifiedExecutable($solutionFolder, $executableName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($existingFiles.Count -eq 0) {
|
if ($existingFiles.Count -eq 0) {
|
||||||
throw "No $executableName files found in any of the expected locations."
|
throw "No $executableName files found in any of the expected locations, please make sure the build was successful."
|
||||||
}
|
}
|
||||||
|
|
||||||
return $existingFiles | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
return $existingFiles | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||||
|
|||||||
4
.github/copilot-instructions.md
vendored
4
.github/copilot-instructions.md
vendored
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
- `REPO-ROOT` refers to the root directory of the repository.
|
- `REPO-ROOT` refers to the root directory of the repository.
|
||||||
- `SOLUTION-ROOT` refers to the root directory of a solution (`*.sln` or `*.slnx`).
|
- `SOLUTION-ROOT` refers to the root directory of a solution (`*.sln` or `*.slnx`).
|
||||||
- Solutions and projects you need to work on could be found in `REPO-ROOT/.github/Project.md`.
|
- Solutions and projects you need to work on could be found in `REPO-ROOT/Project.md`.
|
||||||
- Following `Leveraging the Knowledge Base`, find knowledge and documents for this project in `REPO-ROOT/.github/KnowledgeBase/Index.md`.
|
- Following `Leveraging the Knowledge Base`, find knowledge and documents for this project in `REPO-ROOT/.github/KnowledgeBase/Index.md`.
|
||||||
- Before writing to a source file, read it again and make sure you respect my parallel editing.
|
- Before writing to a source file, read it again and make sure you respect my parallel editing.
|
||||||
- If any `*.prompt.md` file is referenced, take immediate action following the instructions in that file.
|
- If any `*.prompt.md` file is referenced, take immediate action following the instructions in that file.
|
||||||
@@ -44,7 +44,7 @@ otherwise it won't work properly.
|
|||||||
- Building a Solution: `REPO-ROOT/.github/Guidelines/Building.md`
|
- Building a Solution: `REPO-ROOT/.github/Guidelines/Building.md`
|
||||||
- Running a Project:
|
- Running a Project:
|
||||||
- Unit Test: `REPO-ROOT/.github/Guidelines/Running-UnitTest.md`
|
- Unit Test: `REPO-ROOT/.github/Guidelines/Running-UnitTest.md`
|
||||||
- CLI Application: `REPO-ROOT/.github/Guidelines/Running-CLI.md` (to edit ...)
|
- CLI Application: `REPO-ROOT/.github/Guidelines/Running-CLI.md`
|
||||||
- GacUI Application: `REPO-ROOT/.github/Guidelines/Running-GacUI.md` (to edit ...)
|
- GacUI Application: `REPO-ROOT/.github/Guidelines/Running-GacUI.md` (to edit ...)
|
||||||
- Debugging a Project: `REPO-ROOT/.github/Guidelines/Debugging.md`
|
- Debugging a Project: `REPO-ROOT/.github/Guidelines/Debugging.md`
|
||||||
- Using Unit Test Framework: `REPO-ROOT/.github/KnowledgeBase/manual/unittest/vlpp.md`
|
- Using Unit Test Framework: `REPO-ROOT/.github/KnowledgeBase/manual/unittest/vlpp.md`
|
||||||
|
|||||||
2
.github/prompts/0-scrum.prompt.md
vendored
2
.github/prompts/0-scrum.prompt.md
vendored
@@ -32,7 +32,7 @@
|
|||||||
## Step 1. Identify the Problem
|
## Step 1. Identify the Problem
|
||||||
|
|
||||||
- The problem I would like to solve is in the chat messages sent with this request.
|
- The problem I would like to solve is in the chat messages sent with this request.
|
||||||
- Find `# Problem` or `# Update` or `# Learn` in the LATEST chat message.
|
- Find `# Problem` or `# Update` or `# Learn` in the LATEST chat message.
|
||||||
- Ignore any of these titles in the chat history.
|
- Ignore any of these titles in the chat history.
|
||||||
- If there is nothing: it means you are accidentally stopped. Please continue your work.
|
- If there is nothing: it means you are accidentally stopped. Please continue your work.
|
||||||
|
|
||||||
|
|||||||
20
.github/prompts/1-design.prompt.md
vendored
20
.github/prompts/1-design.prompt.md
vendored
@@ -56,6 +56,7 @@ I am going to propose some change to `Copilot_Task.md`.
|
|||||||
|
|
||||||
- Copy precisely my problem description in `# Update` from the LATEST chat message to the `# PROBLEM DESCRIPTION` section, with a new sub-section `## UPDATE`.
|
- Copy precisely my problem description in `# Update` from the LATEST chat message to the `# PROBLEM DESCRIPTION` section, with a new sub-section `## UPDATE`.
|
||||||
- The new `## UPDATE` should be appended to the end of the existing `# UPDATES` section (aka before `# INSIGHTS AND REASONING`).
|
- The new `## UPDATE` should be appended to the end of the existing `# UPDATES` section (aka before `# INSIGHTS AND REASONING`).
|
||||||
|
- **NOTICE**: When the update is triggered by the automatic review process (aka `review.prompt.md`), do not insert such `## UPDATE`.
|
||||||
- Follow my update to change the design document.
|
- Follow my update to change the design document.
|
||||||
|
|
||||||
## Step 2. Understand the Goal and Quality Requirement
|
## Step 2. Understand the Goal and Quality Requirement
|
||||||
@@ -85,12 +86,19 @@ I am going to propose some change to `Copilot_Task.md`.
|
|||||||
|
|
||||||
- Your goal is to write a design document to `Copilot_Task.md`. DO NOT update any other file including source code.
|
- Your goal is to write a design document to `Copilot_Task.md`. DO NOT update any other file including source code.
|
||||||
- Whatever you think or found, write it down in the `# INSIGHTS AND REASONING` section.
|
- Whatever you think or found, write it down in the `# INSIGHTS AND REASONING` section.
|
||||||
- Fill the `# AFFECTED PROJECTS` section:
|
|
||||||
- Solutions and projects you need to work on could be found in `REPO-ROOT/.github/Project.md`.
|
## Step 3.1. Fill the "# AFFECTED PROJECTS" section
|
||||||
- Complete this section in this format:
|
|
||||||
- Identify affected solutions, write `- Build the solution in folder <SOLUTION-ROOT>`.
|
- Solutions and projects you need to work on could be found in `REPO-ROOT/Project.md`.
|
||||||
- For each solution, identify affected unit test projects, write ` - Run Test Project <PROJECT-NAME>`.
|
- Identify all changes you need to make in a high level.
|
||||||
- The list should only include unit test projects.
|
- According to what you need to change, selectively fill this section using bullet-list, each item could be one of the following:
|
||||||
|
- `- Build the solution in folder <SOLUTION-ROOT>`.
|
||||||
|
- When the configuration is important, also specify the configuration, e.g., `Debug|x64`.
|
||||||
|
- `- Run CLI|UnitTest project <PROJECT-NAME>`.
|
||||||
|
- When the configuration is important, also specify the configuration, e.g., `Debug|x64`.
|
||||||
|
- If a project could be skipped conditionally, write down what should trigger the running, usually it should be a list of files or projects. But you can describe it in a high level instead of listing all files.
|
||||||
|
- If a project should always run, use `Always Run` instead of just `Run`.
|
||||||
|
- The bullet-list will be executed in its order in the future.
|
||||||
|
|
||||||
## Step 4. Mark the Completion
|
## Step 4. Mark the Completion
|
||||||
|
|
||||||
|
|||||||
21
.github/prompts/2-planning.prompt.md
vendored
21
.github/prompts/2-planning.prompt.md
vendored
@@ -48,6 +48,7 @@ I am going to propose some change to `Copilot_Planning.md`.
|
|||||||
|
|
||||||
- Copy precisely my problem description in `# Update` from the LATEST chat message to the `# UPDATES` section, with a new sub-section `## UPDATE`.
|
- Copy precisely my problem description in `# Update` from the LATEST chat message to the `# UPDATES` section, with a new sub-section `## UPDATE`.
|
||||||
- The new `## UPDATE` should be appended to the end of the existing `# UPDATES` section (aka before `# AFFECTED PROJECTS`).
|
- The new `## UPDATE` should be appended to the end of the existing `# UPDATES` section (aka before `# AFFECTED PROJECTS`).
|
||||||
|
- **NOTICE**: When the update is triggered by the automatic review process (aka `review.prompt.md`), do not insert such `## UPDATE`.
|
||||||
- Follow my update to change the planning document.
|
- Follow my update to change the planning document.
|
||||||
|
|
||||||
## Step 2. Understand the Goal and Quality Requirement
|
## Step 2. Understand the Goal and Quality Requirement
|
||||||
@@ -76,12 +77,20 @@ I am going to propose some change to `Copilot_Planning.md`.
|
|||||||
- Your goal is to write a design document to `Copilot_Planning.md`. DO NOT update any other file including source code.
|
- Your goal is to write a design document to `Copilot_Planning.md`. DO NOT update any other file including source code.
|
||||||
- The code change proposed in the improvement plan must contain actual code. I need to review them before going to the next phase.
|
- The code change proposed in the improvement plan must contain actual code. I need to review them before going to the next phase.
|
||||||
- DO NOT copy `# UPDATES` from `Copilot_Task.md` to `Copilot_Planning.md`.
|
- DO NOT copy `# UPDATES` from `Copilot_Task.md` to `Copilot_Planning.md`.
|
||||||
- Fill the `# AFFECTED PROJECTS` section:
|
|
||||||
- Solutions and projects you need to work on could be found in `REPO-ROOT/.github/Project.md`.
|
## Step 3.1. Fill the "# AFFECTED PROJECTS" section
|
||||||
- When creating `Copilot_Planning.md` from the first time, copy `# AFFECTED PROJECTS` section from `Copilot_Task.md`. Otherwise, review the list whenever `Copilot_Planning.md` is updated, and fix this section in the following format:
|
|
||||||
- Identify affected solutions, write `- Build the solution in folder <SOLUTION-ROOT>`.
|
- Solutions and projects you need to work on could be found in `REPO-ROOT/Project.md`.
|
||||||
- For each solution, identify affected unit test projects, write ` - Run Test Project <PROJECT-NAME>`.
|
- When creating `Copilot_Planning.md` from the first time, copy `# AFFECTED PROJECTS` section from `Copilot_Task.md`.
|
||||||
- The list should only include unit test projects.
|
- When updating `Copilot_Planning.md`, carefully review `# AFFECTED PROJECTS` and fix this section if necessary.
|
||||||
|
- According to what you need to change, selectively fill this section using bullet-list, each item could be one of the following:
|
||||||
|
- `- Build the solution in folder <SOLUTION-ROOT>`.
|
||||||
|
- When the configuration is important, also specify the configuration, e.g., `Debug|x64`.
|
||||||
|
- `- Run CLI|UnitTest project <PROJECT-NAME>`.
|
||||||
|
- When the configuration is important, also specify the configuration, e.g., `Debug|x64`.
|
||||||
|
- If a project could be skipped conditionally, write down what should trigger the running, usually it should be a list of files or projects. But you can describe it in a high level instead of listing all files.
|
||||||
|
- If a project should always run, use `Always Run` instead of just `Run`.
|
||||||
|
- The bullet-list will be executed in its order in the future.
|
||||||
|
|
||||||
## Step 4. Mark the Completion
|
## Step 4. Mark the Completion
|
||||||
|
|
||||||
|
|||||||
21
.github/prompts/3-summarizing.prompt.md
vendored
21
.github/prompts/3-summarizing.prompt.md
vendored
@@ -47,6 +47,7 @@ I am going to propose some change to `Copilot_Execution.md`.
|
|||||||
|
|
||||||
- Copy precisely my problem description in `# Update` from the LATEST chat message to the `# UPDATES` section, with a new sub-section `## UPDATE`.
|
- Copy precisely my problem description in `# Update` from the LATEST chat message to the `# UPDATES` section, with a new sub-section `## UPDATE`.
|
||||||
- The new `## UPDATE` should be appended to the end of the existing `# UPDATES` section (aka before `# AFFECTED PROJECTS`).
|
- The new `## UPDATE` should be appended to the end of the existing `# UPDATES` section (aka before `# AFFECTED PROJECTS`).
|
||||||
|
- **NOTICE**: When the update is triggered by the automatic review process (aka `review.prompt.md`), do not insert such `## UPDATE`.
|
||||||
- Follow my update to change the execution document.
|
- Follow my update to change the execution document.
|
||||||
|
|
||||||
## Step 2. Finish the Document
|
## Step 2. Finish the Document
|
||||||
@@ -74,12 +75,20 @@ I am going to propose some change to `Copilot_Execution.md`.
|
|||||||
|
|
||||||
- Is `Copilot_Execution.md` contains enough information so that one can follow the document to make actual code change, without having to refer to `Copilot_Planning.md`?
|
- Is `Copilot_Execution.md` contains enough information so that one can follow the document to make actual code change, without having to refer to `Copilot_Planning.md`?
|
||||||
- Does `Copilot_Execution.md` include all code changes mentioned in `Copilot_Planning.md`?
|
- Does `Copilot_Execution.md` include all code changes mentioned in `Copilot_Planning.md`?
|
||||||
- Fill the `# AFFECTED PROJECTS` section:
|
|
||||||
- Solutions and projects you need to work on could be found in `REPO-ROOT/.github/Project.md`.
|
## Step 3.1. Fill the "# AFFECTED PROJECTS" section
|
||||||
- When creating `Copilot_Execution.md` from the first time, copy `# AFFECTED PROJECTS` section from `Copilot_Planning.md`. Otherwise, review the list whenever `Copilot_Execution.md` is updated, and fix this section in the following format:
|
|
||||||
- Identify affected solutions, write `- Build the solution in folder <SOLUTION-ROOT>`.
|
- Solutions and projects you need to work on could be found in `REPO-ROOT/Project.md`.
|
||||||
- For each solution, identify affected unit test projects, write ` - Run Test Project <PROJECT-NAME>`.
|
- When creating `Copilot_Execution.md` from the first time, copy `# AFFECTED PROJECTS` section from `Copilot_Planning.md`.
|
||||||
- The list should only include unit test projects.
|
- When updating `Copilot_Execution.md`, carefully review `# AFFECTED PROJECTS` and fix this section if necessary.
|
||||||
|
- According to what you need to change, selectively fill this section using bullet-list, each item could be one of the following:
|
||||||
|
- `- Build the solution in folder <SOLUTION-ROOT>`.
|
||||||
|
- When the configuration is important, also specify the configuration, e.g., `Debug|x64`.
|
||||||
|
- `- Run CLI|UnitTest project <PROJECT-NAME>`.
|
||||||
|
- When the configuration is important, also specify the configuration, e.g., `Debug|x64`.
|
||||||
|
- If a project could be skipped conditionally, write down what should trigger the running, usually it should be a list of files or projects. But you can describe it in a high level instead of listing all files.
|
||||||
|
- If a project should always run, use `Always Run` instead of just `Run`.
|
||||||
|
- The bullet-list will be executed in its order in the future.
|
||||||
|
|
||||||
## Step 4. Completion
|
## Step 4. Completion
|
||||||
- Ensure there is a `# !!!FINISHED!!!` mark at the end of `Copilot_Execution.md` to indicate the document reaches the end.
|
- Ensure there is a `# !!!FINISHED!!!` mark at the end of `Copilot_Execution.md` to indicate the document reaches the end.
|
||||||
|
|||||||
35
.github/prompts/4-execution.prompt.md
vendored
35
.github/prompts/4-execution.prompt.md
vendored
@@ -43,22 +43,22 @@ I am going to propose some change to the source code.
|
|||||||
- Follow my update to change the source code.
|
- Follow my update to change the source code.
|
||||||
- Update the document to keep it consistent with the source code.
|
- Update the document to keep it consistent with the source code.
|
||||||
|
|
||||||
## Step 2. Make Sure the Code Compiles but DO NOT Run Unit Test
|
## Step 2. Make Sure the Code Compiles
|
||||||
|
|
||||||
- Check out `External Tools Environment and Context` in `REPO-ROOT/.github/copilot-instructions.md` for accessing scripts for building.
|
- Check out `External Tools Environment and Context` in `REPO-ROOT/.github/copilot-instructions.md` for accessing scripts for building.
|
||||||
- Strictly follow the instruction above as this repo does not use ordinary tools.
|
- Strictly follow the instruction above as this repo does not use ordinary tools.
|
||||||
- Each attempt of build-fix process should be executed in a sub agent.
|
- Each attempt of build-fix process should be executed in a sub agent.
|
||||||
- One build-fix process includes one attempt following `Build Unit Test` and `Fix Compile Errors`.
|
- One build-fix process includes one attempt with the following instructions.
|
||||||
- The main agent should call different sub agent for each build-fix process.
|
- The main agent should call different sub agent for each build-fix process.
|
||||||
- Do not build and retrieve build results in the main agent.
|
- Do not build and retrieve build results in the main agent.
|
||||||
|
|
||||||
### Use a sub agent to run the following instructions (`Build Unit Test` and `Fix Compile Errors`)
|
### Use a sub agent to run all following instructions (`Build the Solution`, `Fix Compile Errors`, `Code Generation`, `Finishing Code Change`)
|
||||||
|
|
||||||
#### Build Unit Test
|
#### Build the Solution
|
||||||
|
|
||||||
- Check out `# AFFECTED PROJECTS` in `Copilot_Execution.md` to find out what solutions you need to build.
|
- Check out `# AFFECTED PROJECTS` in `Copilot_Execution.md` to find out what solutions you need to build.
|
||||||
- Find out if there is any warning or error.
|
- Find out if there is any warning or error.
|
||||||
- `External Tools Environment and Context` has the instruction about how to check compile result.
|
- `External Tools Environment and Context` in `REPO-ROOT/.github/copilot-instructions.md` has the instruction about how to check compile result.
|
||||||
|
|
||||||
#### Fix Compile Errors
|
#### Fix Compile Errors
|
||||||
|
|
||||||
@@ -70,11 +70,28 @@ I am going to propose some change to the source code.
|
|||||||
- Explain what you need to do.
|
- Explain what you need to do.
|
||||||
- Explain why you think it would solve the build break.
|
- Explain why you think it would solve the build break.
|
||||||
- Log these in `Copilot_Execution.md`, with section `## Fixing attempt No.<attempt_number>` in `# FIXING ATTEMPTS`.
|
- Log these in `Copilot_Execution.md`, with section `## Fixing attempt No.<attempt_number>` in `# FIXING ATTEMPTS`.
|
||||||
- After finishing fixing, exit the current sub agent and tell the main agent to go back to `Step 2. Make Sure the Code Compiles but DO NOT Run Unit Test`.
|
|
||||||
- When the code compiles:
|
|
||||||
- DO NOT run any tests, the code will be verified in future tasks.
|
|
||||||
|
|
||||||
## Step 3. Verify Coding Style
|
### Code Generation
|
||||||
|
|
||||||
|
- Check out `## Projects for Verification` in `REPO-ROOT/Project.md`.
|
||||||
|
- Check out `# AFFECTED PROJECTS` in `Copilot_Execution.md`.
|
||||||
|
- Find out if any code generation is necessary.
|
||||||
|
- If there is no need to run any code generation, you can skip this step.
|
||||||
|
- Otherwise, pay attention to:
|
||||||
|
- What configuration is needed to build the solution.
|
||||||
|
- What project should be executed, and what configuration is needed.
|
||||||
|
- It is possible that a project needs to be executed multiple times in different configuration.
|
||||||
|
- It is possible that building is required between two runs of code generation projects.
|
||||||
|
- The building and future code generation project execution should be handled by the next sub agent.
|
||||||
|
- If a unit test project is also a code generation project, execute it accordingly.
|
||||||
|
- If a unit test project is not a code generation project, DO NOT execute it. Pure unit test projects will be executed in the future.
|
||||||
|
|
||||||
|
### Finishing Code Change
|
||||||
|
|
||||||
|
- Exit the current sub agent and tell the main agent to go back to `Step 2. Make Sure the Code Compiles`.
|
||||||
|
- If there is any unfinished work according to `Code Generation`, make sure to tell the main agent about them.
|
||||||
|
|
||||||
|
## Step 4. Verify Coding Style
|
||||||
|
|
||||||
- Code changes in `Copilot_Execution.md` may not have correct indentation and coding style.
|
- Code changes in `Copilot_Execution.md` may not have correct indentation and coding style.
|
||||||
- Go over each code change and ensure:
|
- Go over each code change and ensure:
|
||||||
|
|||||||
41
.github/prompts/5-verifying.prompt.md
vendored
41
.github/prompts/5-verifying.prompt.md
vendored
@@ -19,22 +19,22 @@
|
|||||||
- It means I edited them. I have my reason. DO NOT change the code to match `Copilot_Execution.md`.
|
- It means I edited them. I have my reason. DO NOT change the code to match `Copilot_Execution.md`.
|
||||||
- Write down every difference you spotted, make a `## User Update Spotted` section in the `# UPDATES` section in `Copilot_Execution.md`.
|
- Write down every difference you spotted, make a `## User Update Spotted` section in the `# UPDATES` section in `Copilot_Execution.md`.
|
||||||
|
|
||||||
## Step 2. Compile
|
## Step 2. Make Sure the Code Compiles
|
||||||
|
|
||||||
- Check out `External Tools Environment and Context` in `REPO-ROOT/.github/copilot-instructions.md` for accessing scripts for building.
|
- Check out `External Tools Environment and Context` in `REPO-ROOT/.github/copilot-instructions.md` for accessing scripts for building.
|
||||||
- Strictly follow the instruction above as this repo does not use ordinary tools.
|
- Strictly follow the instruction above as this repo does not use ordinary tools.
|
||||||
- Each attempt of build-fix process should be executed in a sub agent.
|
- Each attempt of build-fix process should be executed in a sub agent.
|
||||||
- One build-fix process includes one attempt following `Build Unit Test` and `Fix Compile Errors`.
|
- One build-fix process includes one attempt with the following instructions.
|
||||||
- The main agent should call different sub agent for each build-fix process.
|
- The main agent should call different sub agent for each build-fix process.
|
||||||
- Do not build and retrieve build results in the main agent.
|
- Do not build and retrieve build results in the main agent.
|
||||||
|
|
||||||
### Use a sub agent to run the following instructions (`Build Unit Test` and `Fix Compile Errors`)
|
### Use a sub agent to run all following instructions (`Build the Solution`, `Fix Compile Errors`, `Code Generation`, `Finishing Code Change`)
|
||||||
|
|
||||||
#### Build Unit Test
|
#### Build the Solution
|
||||||
|
|
||||||
- Check out `# AFFECTED PROJECTS` in `Copilot_Execution.md` to find out what solutions you need to build.
|
- Check out `# AFFECTED PROJECTS` in `Copilot_Execution.md` to find out what solutions you need to build.
|
||||||
- Find out if there is any warning or error.
|
- Find out if there is any warning or error.
|
||||||
- `External Tools Environment and Context` has the instruction about how to check compile result.
|
- `External Tools Environment and Context` in `REPO-ROOT/.github/copilot-instructions.md` has the instruction about how to check compile result.
|
||||||
|
|
||||||
#### Fix Compile Errors
|
#### Fix Compile Errors
|
||||||
|
|
||||||
@@ -44,9 +44,28 @@
|
|||||||
- For every attempt of fixing the source code:
|
- For every attempt of fixing the source code:
|
||||||
- Explain why the original change did not work.
|
- Explain why the original change did not work.
|
||||||
- Explain what you need to do.
|
- Explain what you need to do.
|
||||||
- Explain why you think it would solve the build break or test break.
|
- Explain why you think it would solve the build break.
|
||||||
- Log these in `Copilot_Execution.md`, with section `## Fixing attempt No.<attempt_number>` in `# FIXING ATTEMPTS`.
|
- Log these in `Copilot_Execution.md`, with section `## Fixing attempt No.<attempt_number>` in `# FIXING ATTEMPTS`.
|
||||||
- After finishing fixing, exit the current sub agent and tell the main agent to go back to `Step 2. Compile`
|
|
||||||
|
### Code Generation
|
||||||
|
|
||||||
|
- Check out `## Projects for Verification` in `REPO-ROOT/Project.md`.
|
||||||
|
- Check out `# AFFECTED PROJECTS` in `Copilot_Execution.md`.
|
||||||
|
- Find out if any code generation is necessary.
|
||||||
|
- If there is no need to run any code generation, you can skip this step.
|
||||||
|
- Otherwise, pay attention to:
|
||||||
|
- What configuration is needed to build the solution.
|
||||||
|
- What project should be executed, and what configuration is needed.
|
||||||
|
- It is possible that a project needs to be executed multiple times in different configuration.
|
||||||
|
- It is possible that building is required between two runs of code generation projects.
|
||||||
|
- The building and future code generation project execution should be handled by the next sub agent.
|
||||||
|
- If a unit test project is also a code generation project, execute it accordingly.
|
||||||
|
- If a unit test project is not a code generation project, DO NOT execute it. Pure unit test projects will be executed in the future.
|
||||||
|
|
||||||
|
### Finishing Code Change
|
||||||
|
|
||||||
|
- Exit the current sub agent and tell the main agent to go back to `Step 2. Make Sure the Code Compiles`.
|
||||||
|
- If there is any unfinished work according to `Code Generation`, make sure to tell the main agent about them.
|
||||||
|
|
||||||
## Step 3. Run Unit Test
|
## Step 3. Run Unit Test
|
||||||
|
|
||||||
@@ -57,7 +76,7 @@
|
|||||||
- The main agent should call different sub agent for each test-fix process.
|
- The main agent should call different sub agent for each test-fix process.
|
||||||
- Do not test and retrieve test results in the main agent.
|
- Do not test and retrieve test results in the main agent.
|
||||||
|
|
||||||
### Use a sub agent to run the following instructions (`Execute Unit Test`, `Identify the Cause of Failure` and `Fix Failed Test Cases`)
|
### Use a sub agent to run the following instructions (`Execute Unit Test`, `Identify the Cause of Failure`, `Fix Failed Test Cases`)
|
||||||
|
|
||||||
#### Execute Unit Test
|
#### Execute Unit Test
|
||||||
|
|
||||||
@@ -93,9 +112,9 @@
|
|||||||
- Explain what you need to do.
|
- Explain what you need to do.
|
||||||
- Explain why you think it would solve the build break or test break.
|
- Explain why you think it would solve the build break or test break.
|
||||||
- Log these in `Copilot_Execution.md`, with section `## Fixing attempt No.<attempt_number>` in `# FIXING ATTEMPTS`.
|
- Log these in `Copilot_Execution.md`, with section `## Fixing attempt No.<attempt_number>` in `# FIXING ATTEMPTS`.
|
||||||
- After finishing fixing, exit the current sub agent and tell the main agent to go back to `Step 2. Compile`
|
- After finishing fixing, exit the current sub agent and tell the main agent to go back to `Step 2. Make Sure the Code Compiles`
|
||||||
- `Step 2. Compile` and `Step 3. Run Unit Test` are absolutely no problem. If you didn't see any progress, the only reason is that your change is not correct.
|
- `Step 2. Make Sure the Code Compiles` and `Step 3. Run Unit Test` are absolutely no problem. If you didn't see any progress, the only reason is that your change is not correct.
|
||||||
|
|
||||||
## Step 4. Check it Again
|
## Step 4. Check it Again
|
||||||
|
|
||||||
- Go back to `Step 2. Compile`, follow all instructions and all steps again.
|
- Go back to `Step 2. Make Sure the Code Compiles`, follow all instructions and all steps again.
|
||||||
|
|||||||
39
.github/prompts/code.prompt.md
vendored
39
.github/prompts/code.prompt.md
vendored
@@ -12,20 +12,20 @@
|
|||||||
|
|
||||||
- Follow the chat message to implement the task.
|
- Follow the chat message to implement the task.
|
||||||
|
|
||||||
## Step 2. Compile
|
## Step 2. Make Sure the Code Compiles
|
||||||
|
|
||||||
- Check out `External Tools Environment and Context` in `REPO-ROOT/.github/copilot-instructions.md` for accessing scripts for building.
|
- Check out `External Tools Environment and Context` in `REPO-ROOT/.github/copilot-instructions.md` for accessing scripts for building.
|
||||||
- Strictly follow the instruction above as this repo does not use ordinary tools.
|
- Strictly follow the instruction above as this repo does not use ordinary tools.
|
||||||
- Each attempt of build-fix process should be executed in a sub agent.
|
- Each attempt of build-fix process should be executed in a sub agent.
|
||||||
- One build-fix process includes one attempt following `Build Unit Test` and `Fix Compile Errors`.
|
- One build-fix process includes one attempt with the following instructions.
|
||||||
- The main agent should call different sub agent for each build-fix process.
|
- The main agent should call different sub agent for each build-fix process.
|
||||||
- Do not build and retrieve build results in the main agent.
|
- Do not build and retrieve build results in the main agent.
|
||||||
|
|
||||||
### Use a sub agent to run the following instructions (`Build Unit Test` and `Fix Compile Errors`)
|
### Use a sub agent to run all following instructions (`Build the Solution`, `Fix Compile Errors`, `Code Generation`, `Finishing Code Change`)
|
||||||
|
|
||||||
#### Build Unit Test
|
#### Build the Solution
|
||||||
|
|
||||||
- Check out `REPO-ROOT/.github/Project.md` to find out what solutions you need to build.
|
- Check out `REPO-ROOT/Project.md` to find out what solutions you need to build.
|
||||||
- Find out if there is any warning or error.
|
- Find out if there is any warning or error.
|
||||||
- `External Tools Environment and Context` in `REPO-ROOT/.github/copilot-instructions.md` has the instruction about how to check compile result.
|
- `External Tools Environment and Context` in `REPO-ROOT/.github/copilot-instructions.md` has the instruction about how to check compile result.
|
||||||
|
|
||||||
@@ -34,7 +34,26 @@
|
|||||||
- If there is any compilation error, address all of them:
|
- If there is any compilation error, address all of them:
|
||||||
- If there is any compile warning, only fix warnings that caused by your code change. Do not fix any other warnings.
|
- If there is any compile warning, only fix warnings that caused by your code change. Do not fix any other warnings.
|
||||||
- If there is any compile error, you need to carefully identify, is the issue in the callee side or the caller side. Check out similar code before making a decision.
|
- If there is any compile error, you need to carefully identify, is the issue in the callee side or the caller side. Check out similar code before making a decision.
|
||||||
- After finishing fixing, exit the current sub agent and tell the main agent to go back to `Step 2. Compile`
|
- After finishing fixing, exit the current sub agent and tell the main agent to go back to `Step 2. Make Sure the Code Compiles`
|
||||||
|
|
||||||
|
### Code Generation
|
||||||
|
|
||||||
|
- Check out `## Projects for Verification` in `REPO-ROOT/Project.md`.
|
||||||
|
- Find out if any code generation is necessary.
|
||||||
|
- If there is no need to run any code generation, you can skip this step.
|
||||||
|
- Otherwise, pay attention to:
|
||||||
|
- What configuration is needed to build the solution.
|
||||||
|
- What project should be executed, and what configuration is needed.
|
||||||
|
- It is possible that a project needs to be executed multiple times in different configuration.
|
||||||
|
- It is possible that building is required between two runs of code generation projects.
|
||||||
|
- The building and future code generation project execution should be handled by the next sub agent.
|
||||||
|
- If a unit test project is also a code generation project, execute it accordingly.
|
||||||
|
- If a unit test project is not a code generation project, DO NOT execute it. Pure unit test projects will be executed in the future.
|
||||||
|
|
||||||
|
### Finishing Code Change
|
||||||
|
|
||||||
|
- Exit the current sub agent and tell the main agent to go back to `Step 2. Make Sure the Code Compiles`.
|
||||||
|
- If there is any unfinished work according to `Code Generation`, make sure to tell the main agent about them.
|
||||||
|
|
||||||
## Step 3. Run Unit Test
|
## Step 3. Run Unit Test
|
||||||
|
|
||||||
@@ -49,7 +68,7 @@
|
|||||||
|
|
||||||
#### Execute Unit Test
|
#### Execute Unit Test
|
||||||
|
|
||||||
- Check out `REPO-ROOT/.github/Project.md` to find out what projects you need to execute.
|
- Check out `REPO-ROOT/Project.md` to find out what projects you need to execute.
|
||||||
- Run the unit test and see if they passed. If everything is good, you will only see test files and test cases that are executed.
|
- Run the unit test and see if they passed. If everything is good, you will only see test files and test cases that are executed.
|
||||||
- Make sure added test cases are actually executed.
|
- Make sure added test cases are actually executed.
|
||||||
- If any test case fails on a test assertion, the content of `TEST_ASSERT` or other macros will be printed to the output.
|
- If any test case fails on a test assertion, the content of `TEST_ASSERT` or other macros will be printed to the output.
|
||||||
@@ -75,9 +94,9 @@
|
|||||||
|
|
||||||
- Apply fixes to source files.
|
- Apply fixes to source files.
|
||||||
- DO NOT delete any test case.
|
- DO NOT delete any test case.
|
||||||
- After finishing fixing, exit the current sub agent and tell the main agent to go back to `Step 2. Compile`
|
- After finishing fixing, exit the current sub agent and tell the main agent to go back to `Step 2. Make Sure the Code Compiles`
|
||||||
- `Step 2. Compile` and `Step 3. Run Unit Test` are absolutely no problem. If you didn't see any progress, the only reason is that your change is not correct.
|
- `Step 2. Make Sure the Code Compiles` and `Step 3. Run Unit Test` are absolutely no problem. If you didn't see any progress, the only reason is that your change is not correct.
|
||||||
|
|
||||||
## Step 4. Check it Again
|
## Step 4. Check it Again
|
||||||
|
|
||||||
- Go back to `Step 2. Compile`, follow all instructions and all steps again.
|
- Go back to `Step 2. Make Sure the Code Compiles`, follow all instructions and all steps again.
|
||||||
|
|||||||
23
.github/prompts/kb-api.prompt.md
vendored
23
.github/prompts/kb-api.prompt.md
vendored
@@ -1,23 +0,0 @@
|
|||||||
# Update Knowledge Base
|
|
||||||
|
|
||||||
- Only checkout the LATEST chat message, ignore all chat history. It has the request about what to do about the knowledge base.
|
|
||||||
- Your goal is to update the knowledge base according to the request.
|
|
||||||
|
|
||||||
## Implement the Knowledge Base
|
|
||||||
|
|
||||||
- Check out `Leveraging the Knowledge Base` in `REPO-ROOT/.github/copilot-instructions.md`, read the entry file and understand how it is organized: `REPO-ROOT/.github/KnowledgeBase/Index.md`.
|
|
||||||
- There could be multiple places as the request might include multiple objectives. For each objective:
|
|
||||||
- Find out which project does it belong to.
|
|
||||||
- Read through all categories, find out which category is the best fit.
|
|
||||||
- If there is no obvious best answer, create a new category. A new category comes with a new guideline file, you must add it to the `KnowledgeBase` project.
|
|
||||||
- Please be careful about the place to insert the new category, a new category should be added at the end of the belonging project.
|
|
||||||
- Update the category description if you think there is anything worth mentioning.
|
|
||||||
- Read through the file of the hyperlink in the category, update the content to reflect the change I want you to make.
|
|
||||||
|
|
||||||
## Adding or Updating an `API Explanation` guideline
|
|
||||||
|
|
||||||
- Content in such guideline must be compact. Do not repeat anything that could be read from the source code.
|
|
||||||
- Do not simply add code samples.
|
|
||||||
- If you do, keep the code sample sticking to only usage of APIs.
|
|
||||||
- Do not add code sample just to show best practices or what it can do or what it is commonly used or something like that, describe these topics in words.
|
|
||||||
- A code sample is only necessary when many functions or classes must be involved in a specific order. A good example would be the `TEST_FILE` structure.
|
|
||||||
105
.github/prompts/kb-design.prompt.md
vendored
105
.github/prompts/kb-design.prompt.md
vendored
@@ -1,105 +0,0 @@
|
|||||||
# Update Knowledge Base
|
|
||||||
|
|
||||||
## Goal and Constraints
|
|
||||||
|
|
||||||
- Your goal is to draft a document for the knowledge base in `Copilot_KB.md`.
|
|
||||||
- You are only allowed to update `Copilot_KB.md` and the knowledge base.
|
|
||||||
- You are not allowed to modify any other files.
|
|
||||||
- Code references must be wrapped in either `single-line` or ```multi-line``` quotes.
|
|
||||||
- Check out `Leveraging the Knowledge Base` in `REPO-ROOT/.github/copilot-instructions.md`, read the entry file and understand how it is organized: `REPO-ROOT/.github/KnowledgeBase/Index.md`.
|
|
||||||
- `Index.md` below means this file.
|
|
||||||
|
|
||||||
## Identify the Problem
|
|
||||||
|
|
||||||
- Your goal is to draft a document for the knowledge base, about the topic I just give you.
|
|
||||||
- Find `# Topic` or `# Ask` or `# Draft` or `# Improve` or `# Execute` in the LATEST chat message.
|
|
||||||
- Ignore any `# Topic` or `# Ask` or `# Draft` or `# Improve` or `# Execute` in the chat history.
|
|
||||||
|
|
||||||
- If there is a `# Topic` section: it means you are on a fresh start.
|
|
||||||
- You should override `Copilot_KB.md` with only one title `# !!!KNOWLEDGE BASE!!!`.
|
|
||||||
- After overriding, copy precisely my problem description in `# Topic` from the LATEST chat message under a `# DOCUMENT REQUEST`, with a new sub-section `## TOPIC`.
|
|
||||||
- Find `Steps for Topic` section for the complete instructions.
|
|
||||||
|
|
||||||
- If there is an `# Ask` section: it means I want you to clarify your findings.
|
|
||||||
- Copy precisely my problem description in `# Ask` from the LATEST chat message to the `# DOCUMENT REQUEST` section, with a new sub-section `## ASK`.
|
|
||||||
- Find `Steps for Ask` section for the complete instructions.
|
|
||||||
|
|
||||||
- If there is a `# Draft` section: it means I think you are ready to draft the document based on information you found.
|
|
||||||
- Copy precisely my problem description in `# Draft` from the LATEST chat message to the `# DOCUMENT REQUEST` section, with a new sub-section `## DRAFT`.
|
|
||||||
- Find `Steps for Draft` section for the complete instructions.
|
|
||||||
|
|
||||||
- If there is an `# Improve` section: it means I want you to improve the draft.
|
|
||||||
- Copy precisely my problem description in `# Improve` from the LATEST chat message to the `# DOCUMENT REQUEST` section, with a new sub-section `## IMPROVE`.
|
|
||||||
- Find `Steps for Improve` section for the complete instructions.
|
|
||||||
|
|
||||||
- If there is an `# Execute` section: it means I am satisfied with the draft document. You are going to add it to the knowledge base.
|
|
||||||
- Find `Steps for Execute` section for the complete instructions.
|
|
||||||
|
|
||||||
- If there is nothing: it means you are accidentally stopped. Please continue your work.
|
|
||||||
- Read `Copilot_KB.md` thoroughly, it is highly possible that you were working on the request described in the last section in `# DOCUMENT REQUEST`.
|
|
||||||
|
|
||||||
- Existing content in `# DOCUMENT REQUEST` should be frozen, you can change it only when you find out the analysis is incorrect during my clarification (try to limit the scope to `## TOPIC`).
|
|
||||||
|
|
||||||
## Steps for Topic
|
|
||||||
|
|
||||||
- Your goal is to complete a `### Insight` section in the `## TOPIC` of the `# DOCUMENT REQUEST` section.
|
|
||||||
- The topic I would like you to research about is in the `# Topic` section in the LATEST chat message.
|
|
||||||
- The topic is around a feature of the project. It involves multiple places of the source code across many components.
|
|
||||||
- You need to find out the details of the code logic about:
|
|
||||||
- The entry point.
|
|
||||||
- The core part.
|
|
||||||
- Whether there are multiple branches of cases, find all of them.
|
|
||||||
- Whether there are recursive calls, find the structure.
|
|
||||||
- Explain in detail about the design and:
|
|
||||||
- architecture
|
|
||||||
- organization of components
|
|
||||||
- execution flows
|
|
||||||
- design patterns (if applicable)
|
|
||||||
- Each point you mention should provide proof from the source code.
|
|
||||||
- When listing cases from a choice, try your best to include all of them.
|
|
||||||
- You are writing all of this so that, when you need to change it in the future, you know what to change.
|
|
||||||
- You should keep the content compact.
|
|
||||||
- When you refer to the source code, do not include code snippets.
|
|
||||||
- Say the function name, and if the function is big, a little bit more to locate the code in it.
|
|
||||||
- Do not use line number as source codes are subject to change rapidly.
|
|
||||||
- The document is for understanding the source code, so you must keep mentioning names instead of using a too high-level abstraction language.
|
|
||||||
|
|
||||||
## Steps for Ask
|
|
||||||
|
|
||||||
- Your goal is to complete a `### Insight` section in the `## ASK` of the `# DOCUMENT REQUEST` section.
|
|
||||||
- The finding I would like you to clarify is in the `# Ask` section in the LATEST chat message.
|
|
||||||
- I will point out that what you are wrong about, what I still do not understand, where you should need to go deeper.
|
|
||||||
- You will have to answer my question in `### Insight` of `## ASK`, and fix `## TOPIC` under `# DOCUMENT REQUEST` if there is anything wrong.
|
|
||||||
|
|
||||||
## Steps for Draft
|
|
||||||
|
|
||||||
- Your goal is to complete a draft document for the knowledge base in `Copilot_KB.md`, append the draft after the file.
|
|
||||||
- Keep `# DOCUMENT REQUEST` unchanged, do not edit anything in it. Instead, you have to read `# DOCUMENT REQUEST` carefully.
|
|
||||||
- Extra information is provided in the `# Draft` section in the LATEST chat message.
|
|
||||||
- Make a `# DRAFT-LOCATION` section, you need to describe which part you would like to update in the knowledge base.
|
|
||||||
- It will be a new topic under the `Design Explanation` section of a project in `Index.md` of the knowledge base.
|
|
||||||
- But at the moment only edit `Copilot_KB.md`, do not edit `Index.md`. You are drafting a document, it will be implemented in `Index.md` only after my approval.
|
|
||||||
- Make a `# DRAFT-TITLE` section, you need to make a comprehensive but short title for the draft document.
|
|
||||||
- Make a `# DRAFT-CONTENT` section, you need to complete the content of the draft document here.
|
|
||||||
- The draft document is completely based on the source code of the project, and all findings in the `# DOCUMENT REQUEST` section.
|
|
||||||
- You must not miss any details, you must use every single point mentioned in the document.
|
|
||||||
- Since `# DOCUMENT REQUEST` is organized as multiple rounds of questions and answers, it cannot be just directly used as a document. You must reorganize them.
|
|
||||||
- Similar knowledge under the same categories might be spread in different answers, pay attention to them, bring a well-organized document.
|
|
||||||
- Quality of the draft:
|
|
||||||
- The document is for understanding the source code, so you must keep mentioning names instead of using a too high-level abstraction language.
|
|
||||||
- You must use everything in `# DOCUMENT REQUEST`. Do not just make a summary, `# DOCUMENT REQUEST` is already a summary.
|
|
||||||
- Multiple levels of `#` markdown topic containing bullet points are favored.
|
|
||||||
|
|
||||||
## Steps for Improve
|
|
||||||
|
|
||||||
- Your goal is to update `# DRAFT-*` based on my suggestion.
|
|
||||||
- The suggestion is in the `# Improve` section in the LATEST chat message.
|
|
||||||
|
|
||||||
## Steps for Execute
|
|
||||||
|
|
||||||
- Follow `# DRAFT-LOCATION` to add a new topic under the `Design Explanation` section of a project in `Index.md` of the knowledge base.
|
|
||||||
- Use bullet points for the description of the topic to cover the most important points so that it will be easy to identify in the future if this topic is relevant to a work to do.
|
|
||||||
- Create a file according to the hyperlink and add it to the `KnowledgeBase` project.
|
|
||||||
- The title of the document is in the `# DRAFT-TITLE` section.
|
|
||||||
- The content must be exactly and precisely the content under `# DRAFT-CONTENT`. But do not copy the `# DRAFT-CONTENT` title itself.
|
|
||||||
- Keep `Copilot_KB.md` unchanged.
|
|
||||||
162
.github/prompts/kb.prompt.md
vendored
Normal file
162
.github/prompts/kb.prompt.md
vendored
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
# Update Knowledge Base
|
||||||
|
|
||||||
|
- Check out `Accessing Task Documents` and `Accessing Script Files` in `REPO-ROOT/.github/copilot-instructions.md` for context about mentioned `*.md` and `*.ps1` files.
|
||||||
|
- All `*.md` and `*.ps1` files should exist; you should not create any new files unless explicitly instructed.
|
||||||
|
- The `Copilot_KB.md` file should already exist, it may or may not contain content from the last knowledge base writing.
|
||||||
|
- If you cannot find the file, you are looking at a wrong folder.
|
||||||
|
- Following `Leveraging the Knowledge Base` in `REPO-ROOT/.github/copilot-instructions.md`, find knowledge and documents for this project in `REPO-ROOT/.github/KnowledgeBase/Index.md`.
|
||||||
|
- `Index.md` below means this file.
|
||||||
|
|
||||||
|
## Goal and Constraints
|
||||||
|
|
||||||
|
- Your goal is to draft a document for the knowledge base in `Copilot_KB.md`.
|
||||||
|
- You are only allowed to update `Copilot_KB.md` and the knowledge base.
|
||||||
|
- You are not allowed to modify any other files.
|
||||||
|
- The phrasing of the request may look like asking for code change, but your actual work is to write the design document.
|
||||||
|
- Code references must be wrapped in either `single-line` or ```multi-line``` quotes.
|
||||||
|
|
||||||
|
## Copilot_KB.md Structure
|
||||||
|
|
||||||
|
- `# !!!KNOWLEDGE BASE!!!`: This file always begins with this title.
|
||||||
|
- `# DESIGN REQUEST`: The exact copy of the problem description i gave you.
|
||||||
|
- `# INSIGHT`: Your insight about the topic after deep research.
|
||||||
|
- `# ASKS`:
|
||||||
|
- `## QUESTION`: The exact copy of the question I gave you.
|
||||||
|
- `### ANSWER`: Your insight about the question after deep research.
|
||||||
|
- `# DRAFT`:
|
||||||
|
- `## DRAFT REQUEST`: The exact copy of the draft request I have you.
|
||||||
|
- `## IMPROVEMENTS`:
|
||||||
|
- `### IMPROVEMENT`: The exact copy of the improvement request I have you.
|
||||||
|
- `## (API|DESIGN) EXPLANATION`: The title of the drafting KB document, and where to put the document in `Index.md` and surrounding anchors.
|
||||||
|
- `## DOCUMENT`: The drafting KB document.
|
||||||
|
|
||||||
|
## Identify the Problem
|
||||||
|
|
||||||
|
- Your goal is to draft a document for the knowledge base, about the topic I just give you.
|
||||||
|
- Find `# Topic` or `# Ask` or `# Draft` or `# Improve` or `# Execute` in the LATEST chat message.
|
||||||
|
- Ignore any `# Topic` or `# Ask` or `# Draft` or `# Improve` or `# Execute` in the chat history.
|
||||||
|
- Ignore any of these titles in the chat history.
|
||||||
|
- If there is nothing:
|
||||||
|
- It means you are accidentally stopped. Please continue your work.
|
||||||
|
- Read `Copilot_KB.md` thoroughly, it is highly possible that you were working on the request described in the last section in `# DOCUMENT REQUEST`.
|
||||||
|
|
||||||
|
### Research on a Topic (only when "# Topic" appears in the latest chat message)
|
||||||
|
|
||||||
|
- You should override `Copilot_KB.md` with only one title `# !!!KNOWLEDGE BASE!!!`.
|
||||||
|
- DO clear the content of the file before researching.
|
||||||
|
- DO NOT do researching and then fix each section one by one.
|
||||||
|
- After overriding, copy precisely my problem description in `# Topic` from the LATEST chat message under `# DOCUMENT REQUEST`.
|
||||||
|
- Add another title `# INSIGHT`.
|
||||||
|
- Add another title `# ASKS`.
|
||||||
|
- Add another title `# DRAFT`, with all sub-sections under it without content.
|
||||||
|
- Find `Steps for Topic` section for the complete instructions for filling `# INSIGHT`.
|
||||||
|
- DO NOT TOUCH the `# DRAFT` part, when researching for a certain topic no document drafting is expected.
|
||||||
|
|
||||||
|
### Answer a Question (only when "# Ask" appears in the latest chat message)
|
||||||
|
|
||||||
|
- Copy precisely my question in `# Ask` from the LATEST chat message to a new sub-section `## QUESTION` under `# ASKS`.
|
||||||
|
- The new `## QUESTION` and `### ANSWER` should be added before `# DRAFT` as it is the latest question.
|
||||||
|
- Find `Steps for Ask` section for the complete instructions for filling `### ANSWER`.
|
||||||
|
- DO NOT TOUCH the `# DRAFT` part, when researching for a certain topic no document drafting is expected.
|
||||||
|
|
||||||
|
### Draft the KB Document (only when "# Draft" appears in the latest chat message)
|
||||||
|
|
||||||
|
- Copy precisely my draft request in `# Draft` from the LATEST chat message to the `## DRAFT REQUEST` section.
|
||||||
|
- Find `Steps for Draft` section for the complete instructions for filling `## (API|DESIGN) EXPLANATION` and `## DOCUMENT`.
|
||||||
|
|
||||||
|
### Improve the KB Document (only when "# Improve" appears in the latest chat message)
|
||||||
|
|
||||||
|
- Copy precisely my improvement request in a new `# Improve` from the LATEST chat message to a new sub-section `### IMPROVEMENT` section under `## IMPROVEMENTS`.
|
||||||
|
- The new `### IMPROVEMENT` should be added before `## DOCUMENT` as it is the latest improvement.
|
||||||
|
- Find `Steps for Improve` section for the complete instructions for updating `## DOCUMENT`.
|
||||||
|
|
||||||
|
### Add the KB Document to KB (only when "# Execute" appears in the latest chat message)
|
||||||
|
|
||||||
|
- Find `Steps for Execute` section for the complete instructions.
|
||||||
|
|
||||||
|
## Steps for Topic
|
||||||
|
|
||||||
|
- Your goal is to complete the `# INSIGHT`.
|
||||||
|
- The topic I would like you to research about is already copied to `# DESIGN REQUEST`.
|
||||||
|
- The topic is around a feature of the project. It involves multiple places of the source code across many components.
|
||||||
|
- You need to find out the details of the code logic about:
|
||||||
|
- The entry point.
|
||||||
|
- The core part.
|
||||||
|
- Whether there are multiple branches of cases, find all of them.
|
||||||
|
- Whether there are recursive calls, find the structure.
|
||||||
|
- Explain in detail about the design and:
|
||||||
|
- architecture
|
||||||
|
- organization of components
|
||||||
|
- execution flows
|
||||||
|
- design patterns (if applicable)
|
||||||
|
- Each point you mention should provide proof from the source code.
|
||||||
|
- When listing cases from a choice, try your best to include all of them.
|
||||||
|
- You are writing all of this so that, when you need to change it in the future, you know what to change.
|
||||||
|
- You should keep the content compact.
|
||||||
|
- When you refer to the source code, do not include code snippets.
|
||||||
|
- Say the function name, and if the function is big, a little bit more to locate the code in it.
|
||||||
|
- Do not use line number as source codes are subject to change rapidly.
|
||||||
|
- The document is for understanding the source code, so you must keep mentioning names instead of using a too high-level abstraction language.
|
||||||
|
- Avoid doing pure code translation, focus more on the design and the logic, focus more on why and how.
|
||||||
|
|
||||||
|
## Steps for Ask
|
||||||
|
|
||||||
|
- Your goal is to complete the `### ANSWER` section in the `## QUESTION` section.
|
||||||
|
- The finding I would like you to clarify is already copied to the last `## QUESTION`.
|
||||||
|
- I will point out that what you are wrong about, what I still do not understand, where you should need to go deeper.
|
||||||
|
- You will have to answer my question in `### ANSWER`.
|
||||||
|
- Fix `# INSIGHT` if there is anything wrong, the insight to update is between `# INSIGHT` and `# ASKS`.
|
||||||
|
|
||||||
|
## Steps for Draft
|
||||||
|
|
||||||
|
- Your goal is to complete the `## (API|DESIGN) EXPLANATION` and `## DOCUMENT` section.
|
||||||
|
- Keep everything before `# DRAFT` unchanged, DO NOT edit anything in it.
|
||||||
|
- Read everything before `# DRAFT` carefully, you are going to draft the document based on every information there, including necessary supporting materials mentioned there.
|
||||||
|
- Extra information is already copied to `## DRAFT REQUEST` if any.
|
||||||
|
- You are not going to edit `Index.md` at the moment, you are only editing `Copilot_KB.md`.
|
||||||
|
|
||||||
|
### Decide the Type of the Document
|
||||||
|
|
||||||
|
- You are going to decide which project this document belongs to.
|
||||||
|
- You are going to decide the type of the document.
|
||||||
|
- Change `## (API|DESIGN) EXPLANATION` to `## API EXPLANATION (PROJECT)` if it is more about the usage and contract of APIs.
|
||||||
|
- It means eventually a new section will be added under `### Choosing APIs` under the specified project in `Index.md`.
|
||||||
|
- Change `## (API|DESIGN) EXPLANATION` to `## DESIGN EXPLANATION (PROJECT)` if it is more about how a set of APIs are working together and the design and implementation of the source code.
|
||||||
|
- It means eventually a new section will be added under `### Design Explanation` under the specified project in `Index.md`.
|
||||||
|
- Fill the section to describe which part you would like to update in the knowledge base.
|
||||||
|
|
||||||
|
### Draft the Document
|
||||||
|
|
||||||
|
- You are going to draft the document under `## DOCUMENT`.
|
||||||
|
- The draft document is completely based on the source code of the project, and all findings before `# DRAFT`.
|
||||||
|
- You must not miss any details, you must use every single point mentioned in the document.
|
||||||
|
- Since `# DOCUMENT REQUEST` is organized as multiple rounds of questions and answers, it cannot be just directly used as a document. You must reorganize them.
|
||||||
|
- Similar knowledge under the same categories might be spread in different answers, pay attention to them, bring a well-organized document.
|
||||||
|
- Quality of the draft:
|
||||||
|
- The document is for understanding the source code, so you must keep mentioning names instead of using a too high-level abstraction language.
|
||||||
|
- You must use everything before `# DRAFT` with details. Do not just make a summary, `# DOCUMENT REQUEST` is already a summary.
|
||||||
|
- Multiple levels of `#` markdown topic containing bullet points are favored.
|
||||||
|
|
||||||
|
## Steps for Improve
|
||||||
|
|
||||||
|
- Your goal is to update `## DOCUMENT` based on my suggestion.
|
||||||
|
- The finding I would like you to clarify is already copied to the last `### IMPROVEMENT`.
|
||||||
|
- You are not going to edit `Index.md` at the moment, you are only editing `Copilot_KB.md`.
|
||||||
|
|
||||||
|
## Steps for Execute
|
||||||
|
|
||||||
|
- There is `## API EXPLANATION (PROJECT)` or `## DESIGN EXPLANATION (PROJECT)` section filled with the title and content of the document you drafted.
|
||||||
|
- If it is `## API EXPLANATION (PROJECT)`:
|
||||||
|
- A new section will be added under `### Choosing APIs` under the specified project in `Index.md`.
|
||||||
|
- At the end of the new section, a `[API Explanation]()` link is expected.
|
||||||
|
- If it is `## DESIGN EXPLANATION (PROJECT)`:
|
||||||
|
- A new section will be added under `### Design Explanation` under the specified project in `Index.md`.
|
||||||
|
- At the end of the new section, a `[Design Explanation]()` link is expected.
|
||||||
|
- Create a new section in `Index.md`.
|
||||||
|
- Following `Leveraging the Knowledge Base` in `REPO-ROOT/.github/copilot-instructions.md` to figure out the expected format.
|
||||||
|
- The document is super long, you have to carefully figure out where is the exact place to add the new section.
|
||||||
|
- Use bullet points for the description of the topic to cover the most important points so that it will be easy to identify in the future if this topic is relevant to a work to do.
|
||||||
|
- Create a document file according to the hyperlink.
|
||||||
|
- The title of the new document file should follow `## API EXPLANATION (PROJECT)` or `## DESIGN EXPLANATION (PROJECT)`.
|
||||||
|
- The content must be exactly and precisely the content under `## DOCUMENT`. But do not copy the `## DOCUMENT` title itself.
|
||||||
|
- Keep `Copilot_KB.md` unchanged.
|
||||||
@@ -15,6 +15,7 @@ Read the first word of the request, and read an additional instruction file when
|
|||||||
- "verify": REPO-ROOT/.github/prompts/5-verifying.prompt.md
|
- "verify": REPO-ROOT/.github/prompts/5-verifying.prompt.md
|
||||||
- "ask": REPO-ROOT/.github/prompts/ask.prompt.md
|
- "ask": REPO-ROOT/.github/prompts/ask.prompt.md
|
||||||
- "code": REPO-ROOT/.github/prompts/code.prompt.md
|
- "code": REPO-ROOT/.github/prompts/code.prompt.md
|
||||||
|
- "kb": REPO-ROOT/.github/prompts/kb.prompt.md
|
||||||
- "refine": REPO-ROOT/.github/prompts/refine.prompt.md
|
- "refine": REPO-ROOT/.github/prompts/refine.prompt.md
|
||||||
- "review": REPO-ROOT/.github/prompts/review.prompt.md
|
- "review": REPO-ROOT/.github/prompts/review.prompt.md
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ Read the first word of the request, and read an additional instruction file when
|
|||||||
- "summary"
|
- "summary"
|
||||||
- "execute"
|
- "execute"
|
||||||
- "review"
|
- "review"
|
||||||
|
- "kb"
|
||||||
- Read the second word if it exists, convert it to a title `# THE-WORD`.
|
- Read the second word if it exists, convert it to a title `# THE-WORD`.
|
||||||
|
|
||||||
## Step 3
|
## Step 3
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Read the first word of the request, and read an additional instruction file when
|
|||||||
- "verify": REPO-ROOT/.github/prompts/5-verifying.prompt.md
|
- "verify": REPO-ROOT/.github/prompts/5-verifying.prompt.md
|
||||||
- "ask": REPO-ROOT/.github/prompts/ask.prompt.md
|
- "ask": REPO-ROOT/.github/prompts/ask.prompt.md
|
||||||
- "code": REPO-ROOT/.github/prompts/code.prompt.md
|
- "code": REPO-ROOT/.github/prompts/code.prompt.md
|
||||||
|
- "kb": REPO-ROOT/.github/prompts/kb.prompt.md
|
||||||
- "refine": REPO-ROOT/.github/prompts/refine.prompt.md
|
- "refine": REPO-ROOT/.github/prompts/refine.prompt.md
|
||||||
- "review": REPO-ROOT/.github/prompts/review.prompt.md
|
- "review": REPO-ROOT/.github/prompts/review.prompt.md
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ Read the first word of the request, and read an additional instruction file when
|
|||||||
- "summary"
|
- "summary"
|
||||||
- "execute"
|
- "execute"
|
||||||
- "review"
|
- "review"
|
||||||
|
- "kb"
|
||||||
- Read the second word if it exists, convert it to a title `# THE-WORD`.
|
- Read the second word if it exists, convert it to a title `# THE-WORD`.
|
||||||
|
|
||||||
## Step 3
|
## Step 3
|
||||||
|
|||||||
Reference in New Issue
Block a user