mirror of
https://github.com/vczh-libraries/Release.git
synced 2026-03-23 07:42:52 +08:00
Update copilot prompts
This commit is contained in:
832
.github/KnowledgeBase/Index.md
vendored
Normal file
832
.github/KnowledgeBase/Index.md
vendored
Normal file
File diff suppressed because it is too large
Load Diff
378
.github/KnowledgeBase/KB_GacUI_Design_AddingNewControl.md
vendored
Normal file
378
.github/KnowledgeBase/KB_GacUI_Design_AddingNewControl.md
vendored
Normal file
@@ -0,0 +1,378 @@
|
||||
# Adding a New Control
|
||||
|
||||
## Overview
|
||||
|
||||
Adding a new control to GacUI requires coordinated changes across multiple files to integrate the control into the class hierarchy, template system, theme management, reflection system, and XML compiler. This document provides a comprehensive guide covering control classes, templates, inheritance patterns, registration, and a minimal working example.
|
||||
|
||||
## Control Class Definition
|
||||
|
||||
### Header File Structure (`Source/Controls/*.h`)
|
||||
|
||||
The control class must:
|
||||
|
||||
- **Inherit from base class**: `GuiControl` (or another control) and `Description<YourControl>` for reflection
|
||||
- **Specify template type**: Use `GUI_SPECIFY_CONTROL_TEMPLATE_TYPE(TemplateName, BaseControlType)` macro
|
||||
- **Declare state variables**: Member variables to track control state
|
||||
- **Override lifecycle methods** from `GuiControl`:
|
||||
- `BeforeControlTemplateUninstalled_()` - cleanup before template removal
|
||||
- `AfterControlTemplateInstalled_(bool initialize)` - setup after template installation
|
||||
- `OnParentLineChanged()` - handle parent hierarchy changes
|
||||
- `OnActiveAlt()` - handle ALT key activation
|
||||
- `IsTabAvailable()` - control TAB navigation availability
|
||||
- **Attach event handlers**: In constructor to `boundsComposition->GetEventReceiver()` for mouse/keyboard events
|
||||
- **Define public events**: Using `compositions::GuiNotifyEvent`
|
||||
- **Define properties**: With getters/setters
|
||||
- **Accept theme parameter**: Constructor takes `theme::ThemeName` parameter and passes to base class
|
||||
|
||||
**Example pattern from `GuiButton`:**
|
||||
|
||||
```cpp
|
||||
class GuiButton : public GuiControl, public Description<GuiButton>
|
||||
{
|
||||
GUI_SPECIFY_CONTROL_TEMPLATE_TYPE(ButtonTemplate, GuiControl)
|
||||
protected:
|
||||
ButtonState controlState;
|
||||
void BeforeControlTemplateUninstalled_() override;
|
||||
void AfterControlTemplateInstalled_(bool initialize) override;
|
||||
void OnMouseDown(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments);
|
||||
void UpdateControlState();
|
||||
public:
|
||||
GuiButton(theme::ThemeName themeName);
|
||||
~GuiButton();
|
||||
|
||||
compositions::GuiNotifyEvent BeforeClicked;
|
||||
compositions::GuiNotifyEvent Clicked;
|
||||
compositions::GuiNotifyEvent AfterClicked;
|
||||
|
||||
bool GetAutoFocus();
|
||||
void SetAutoFocus(bool value);
|
||||
};
|
||||
```
|
||||
|
||||
## Control Implementation
|
||||
|
||||
### Implementation File (`Source/Controls/*.cpp`)
|
||||
|
||||
Implement:
|
||||
|
||||
- **Constructor**: Initialize state, set up event handlers on `boundsComposition`
|
||||
- Pattern: `GuiYourControl(theme::ThemeName themeName) : GuiControl(themeName)`
|
||||
- Set up events: `eventName.SetAssociatedComposition(boundsComposition)`
|
||||
- Attach handlers: `boundsComposition->GetEventReceiver()->eventName.AttachMethod(this, &GuiYourControl::Handler)`
|
||||
- **Destructor**: Cleanup (usually minimal, automatic cleanup happens)
|
||||
- **`BeforeControlTemplateUninstalled_()`**: Clear template-specific state
|
||||
- **`AfterControlTemplateInstalled_(bool initialize)`**: Sync state to template
|
||||
- Call template methods: `TypedControlTemplateObject(true)->SetState(controlState)`
|
||||
- **Event handlers**: Mouse events (`leftButtonDown`, `leftButtonUp`, `mouseEnter`, `mouseLeave`), keyboard events (`keyDown`, `keyUp`)
|
||||
- **Property getters/setters**: Update state and notify template
|
||||
- **Template access**: Use `TypedControlTemplateObject(true)` to get typed template with existence check, or `TypedControlTemplateObject(false)` without check
|
||||
|
||||
**Key patterns from `GuiButton`:**
|
||||
|
||||
- State machine pattern: `controlState` variable tracks visual state (Normal, Active, Pressed)
|
||||
- Event chaining: `BeforeClicked` → `Clicked` → `AfterClicked`
|
||||
- Mouse tracking: Separate flags for `mousePressingDirect`, `mousePressingIndirect`, `mouseHoving`
|
||||
- Keyboard support: SPACE and ENTER keys trigger click
|
||||
- Focus integration: `SetFocusableComposition(boundsComposition)` and `autoFocus` property
|
||||
- Template communication: Call template setters in `AfterControlTemplateInstalled_`
|
||||
|
||||
## Control Template System
|
||||
|
||||
### Template Declaration (`Source/Controls/Templates/GuiControlTemplates.h`)
|
||||
|
||||
Three required entries:
|
||||
|
||||
**1. Add to `GUI_CONTROL_TEMPLATE_DECL` macro:**
|
||||
|
||||
```cpp
|
||||
F(GuiYourTemplate, GuiBaseTemplate)
|
||||
```
|
||||
|
||||
**2. Define template properties macro:**
|
||||
|
||||
```cpp
|
||||
#define GuiYourTemplate_PROPERTIES(F)\
|
||||
F(GuiYourTemplate, PropertyType, PropertyName, DefaultValue)
|
||||
```
|
||||
|
||||
Properties are defined using the `F` macro with: template class name, property type, property name, default value.
|
||||
|
||||
**3. Forward declaration and class declaration:**
|
||||
|
||||
The macros `GUI_TEMPLATE_CLASS_FORWARD_DECL` and `GUI_TEMPLATE_CLASS_DECL` are applied to `GUI_CONTROL_TEMPLATE_DECL` to generate these automatically.
|
||||
|
||||
### Template Implementation (`Source/Controls/Templates/GuiControlTemplates.cpp`)
|
||||
|
||||
The template implementation is auto-generated by:
|
||||
|
||||
```cpp
|
||||
GUI_CONTROL_TEMPLATE_DECL(GUI_TEMPLATE_CLASS_IMPL)
|
||||
```
|
||||
|
||||
This macro expands to create:
|
||||
- Property getter/setter implementations with change events
|
||||
- Constructor that initializes event handlers
|
||||
- Destructor that calls `FinalizeAggregation()`
|
||||
|
||||
## Theme Integration
|
||||
|
||||
### Theme Name Registration (`Source/Application/Controls/GuiThemeManager.h`)
|
||||
|
||||
Add the control to `GUI_CONTROL_TEMPLATE_TYPES` macro:
|
||||
|
||||
```cpp
|
||||
#define GUI_CONTROL_TEMPLATE_TYPES(F) \
|
||||
... existing entries ...\
|
||||
F(YourTemplate, YourControl)
|
||||
```
|
||||
|
||||
This generates the corresponding `theme::ThemeName::YourControl` enum value used in control constructors.
|
||||
|
||||
## Reflection Registration
|
||||
|
||||
### Three-Step Registration Process
|
||||
|
||||
**Step 1: Add to type list** (`Source/Reflection/TypeDescriptors/GuiReflectionPlugin.h`):
|
||||
|
||||
Add to `GUIREFLECTIONCONTROLS_CLASS_TYPELIST` macro:
|
||||
|
||||
```cpp
|
||||
F(presentation::controls::GuiYourControl)
|
||||
```
|
||||
|
||||
**Step 2: Register control class** (`Source/Reflection/TypeDescriptors/GuiReflectionControls.cpp`):
|
||||
|
||||
```cpp
|
||||
BEGIN_CLASS_MEMBER(GuiYourControl)
|
||||
CLASS_MEMBER_BASE(GuiBaseControl)
|
||||
CONTROL_CONSTRUCTOR_CONTROLT_TEMPLATE(GuiYourControl)
|
||||
|
||||
CLASS_MEMBER_GUIEVENT(EventName)
|
||||
CLASS_MEMBER_PROPERTY_FAST(PropertyName)
|
||||
CLASS_MEMBER_PROPERTY_GUIEVENT_FAST(PropertyWithEvent)
|
||||
CLASS_MEMBER_METHOD(MethodName, {L"param1" _ L"param2"})
|
||||
END_CLASS_MEMBER(GuiYourControl)
|
||||
```
|
||||
|
||||
**Step 3: Template registration** (`Source/Reflection/TypeDescriptors/GuiReflectionTemplates.cpp`):
|
||||
|
||||
Templates are auto-registered via the `GUI_CONTROL_TEMPLATE` macro expansion applied to all templates declared in `GUI_CONTROL_TEMPLATE_DECL`.
|
||||
|
||||
## XML Compiler Integration
|
||||
|
||||
### Instance Loader Registration (`Source/Compiler/InstanceLoaders/GuiInstanceLoader_Plugin.cpp`)
|
||||
|
||||
Add to loader registration in `IGuiPlugin::Load()`:
|
||||
|
||||
**For a normal control:**
|
||||
|
||||
```cpp
|
||||
ADD_TEMPLATE_CONTROL(GuiYourControl, YourControl);
|
||||
```
|
||||
|
||||
**For a virtual control** (uses another control's implementation with different theme):
|
||||
|
||||
```cpp
|
||||
ADD_VIRTUAL_CONTROL(VirtualName, GuiActualControl, ThemeName);
|
||||
```
|
||||
|
||||
This uses `GuiTemplateControlInstanceLoader<T>` to register the control with the instance loader manager, making it available in GacUI XML files.
|
||||
|
||||
## Header File Organization
|
||||
|
||||
### Include Files
|
||||
|
||||
Add includes to:
|
||||
- `Source/Controls/IncludeForward.h` - forward declaration
|
||||
- `Source/Controls/IncludeAll.h` - full header inclusion
|
||||
|
||||
This ensures proper compilation order and accessibility throughout the framework.
|
||||
|
||||
## Creating a Control that Inherits from Another Control
|
||||
|
||||
When creating a control that inherits from another control (e.g., `GuiSelectableButton` inherits from `GuiButton`):
|
||||
|
||||
### Class Definition Differences
|
||||
|
||||
- **Inherit from parent control**: `class GuiDerivedControl : public GuiParentControl, public Description<GuiDerivedControl>`
|
||||
- **Specify parent in template macro**: `GUI_SPECIFY_CONTROL_TEMPLATE_TYPE(DerivedTemplate, GuiParentControl)`
|
||||
- **Do NOT re-attach parent event handlers**: Don't re-attach `leftButtonDown` if parent already handles it
|
||||
- **Attach to parent's events instead**: Example - `GuiSelectableButton` attaches to `GuiButton::AfterClicked`
|
||||
|
||||
### Constructor Pattern
|
||||
|
||||
- **Call parent constructor**: `GuiDerivedControl(theme::ThemeName themeName) : GuiParentControl(themeName)`
|
||||
- **Initialize additional events**: On `boundsComposition` (inherited from parent)
|
||||
- **Attach handlers to parent's events**: If extending behavior
|
||||
|
||||
### Template Inheritance
|
||||
|
||||
- **Template inherits from parent template**: In `GuiControlTemplates.h`, add:
|
||||
```cpp
|
||||
F(GuiDerivedTemplate, GuiParentTemplate)
|
||||
```
|
||||
- **Define additional properties**: Separate `GuiDerivedTemplate_PROPERTIES(F)` macro with new properties
|
||||
|
||||
### Reflection Registration
|
||||
|
||||
- **Use parent as base**: `CLASS_MEMBER_BASE(GuiParentControl)` instead of `CLASS_MEMBER_BASE(GuiControl)`
|
||||
- **Still use standard constructor macro**: `CONTROL_CONSTRUCTOR_CONTROLT_TEMPLATE(GuiDerivedControl)`
|
||||
- **Only register new members**: Properties/events/methods introduced by derived class
|
||||
|
||||
### Minimal Changes Approach
|
||||
|
||||
- **Parent handles lifecycle**: Override `BeforeControlTemplateUninstalled_`, etc. only if needed
|
||||
- **Parent's event handlers inherited**: No need to re-implement
|
||||
- **Focus on new functionality**: Don't repeat parent's work
|
||||
|
||||
### Example Pattern from `GuiSelectableButton`
|
||||
|
||||
- **Extends `GuiButton`** with selection state
|
||||
- **Attaches to parent's `AfterClicked`** event to toggle selection
|
||||
- **Adds new properties**: `GroupController`, `AutoSelection`, `Selected`
|
||||
- **Adds new template property**: `Selected` in `SelectableButtonTemplate`
|
||||
- **Template calls both**: `SetState()` (from parent) and `SetSelected()` (new)
|
||||
|
||||
## Template Property Access Pattern
|
||||
|
||||
Controls access template properties via:
|
||||
|
||||
- `TypedControlTemplateObject(true)` - gets the typed template with existence check
|
||||
- `TypedControlTemplateObject(false)` - gets the typed template without check
|
||||
- Template properties have auto-generated `Get/Set` methods and `Changed` events
|
||||
|
||||
## Control Template Macro System
|
||||
|
||||
The macro system provides:
|
||||
|
||||
- `GUI_TEMPLATE_CLASS_DECL`: Generates class declaration with properties
|
||||
- `GUI_TEMPLATE_CLASS_IMPL`: Generates implementation (constructor, destructor, property accessors)
|
||||
- `GUI_SPECIFY_CONTROL_TEMPLATE_TYPE`: Links control to its template type with automatic casting
|
||||
- Property macros: Generate private field, getter, setter, and change event
|
||||
|
||||
## Minimal Working Example
|
||||
|
||||
This example demonstrates the essential code structure for creating a custom control:
|
||||
|
||||
### Step 1: Header File (`Source/Controls/GuiMyControls.h`)
|
||||
|
||||
```cpp
|
||||
class GuiMyControl : public GuiControl, public Description<GuiMyControl>
|
||||
{
|
||||
GUI_SPECIFY_CONTROL_TEMPLATE_TYPE(MyControlTemplate, GuiControl)
|
||||
protected:
|
||||
// State variables
|
||||
bool myState = false;
|
||||
|
||||
// Lifecycle overrides
|
||||
void BeforeControlTemplateUninstalled_() override;
|
||||
void AfterControlTemplateInstalled_(bool initialize) override;
|
||||
|
||||
// Event handlers
|
||||
void OnMouseClick(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments);
|
||||
|
||||
public:
|
||||
GuiMyControl(theme::ThemeName themeName);
|
||||
~GuiMyControl();
|
||||
|
||||
// Events
|
||||
compositions::GuiNotifyEvent StateChanged;
|
||||
|
||||
// Properties
|
||||
bool GetMyState();
|
||||
void SetMyState(bool value);
|
||||
};
|
||||
```
|
||||
|
||||
### Step 2: Implementation File (`Source/Controls/GuiMyControls.cpp`)
|
||||
|
||||
```cpp
|
||||
void GuiMyControl::BeforeControlTemplateUninstalled_()
|
||||
{
|
||||
// Cleanup before template removal
|
||||
}
|
||||
|
||||
void GuiMyControl::AfterControlTemplateInstalled_(bool initialize)
|
||||
{
|
||||
// Sync state to template
|
||||
TypedControlTemplateObject(true)->SetMyState(myState);
|
||||
}
|
||||
|
||||
void GuiMyControl::OnMouseClick(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments)
|
||||
{
|
||||
SetMyState(!myState);
|
||||
}
|
||||
|
||||
GuiMyControl::GuiMyControl(theme::ThemeName themeName)
|
||||
: GuiControl(themeName)
|
||||
{
|
||||
StateChanged.SetAssociatedComposition(boundsComposition);
|
||||
boundsComposition->GetEventReceiver()->leftButtonUp.AttachMethod(this, &GuiMyControl::OnMouseClick);
|
||||
}
|
||||
|
||||
GuiMyControl::~GuiMyControl()
|
||||
{
|
||||
}
|
||||
|
||||
bool GuiMyControl::GetMyState()
|
||||
{
|
||||
return myState;
|
||||
}
|
||||
|
||||
void GuiMyControl::SetMyState(bool value)
|
||||
{
|
||||
if (myState != value)
|
||||
{
|
||||
myState = value;
|
||||
TypedControlTemplateObject(false)->SetMyState(myState);
|
||||
StateChanged.Execute(compositions::GuiEventArgs(boundsComposition));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Template Declaration (`Source/Controls/Templates/GuiControlTemplates.h`)
|
||||
|
||||
```cpp
|
||||
// Add to GUI_CONTROL_TEMPLATE_DECL macro:
|
||||
F(GuiMyControlTemplate, GuiControlTemplate)
|
||||
|
||||
// Define template properties:
|
||||
#define GuiMyControlTemplate_PROPERTIES(F)\
|
||||
F(GuiMyControlTemplate, bool, MyState, false)
|
||||
```
|
||||
|
||||
### Step 4: Theme Registration (`Source/Application/Controls/GuiThemeManager.h`)
|
||||
|
||||
```cpp
|
||||
// Add to GUI_CONTROL_TEMPLATE_TYPES macro:
|
||||
F(MyControlTemplate, MyControl)
|
||||
```
|
||||
|
||||
### Step 5: Reflection (`Source/Reflection/TypeDescriptors/GuiReflectionControls.cpp`)
|
||||
|
||||
```cpp
|
||||
BEGIN_CLASS_MEMBER(GuiMyControl)
|
||||
CLASS_MEMBER_BASE(GuiControl)
|
||||
CONTROL_CONSTRUCTOR_CONTROLT_TEMPLATE(GuiMyControl)
|
||||
|
||||
CLASS_MEMBER_GUIEVENT(StateChanged)
|
||||
CLASS_MEMBER_PROPERTY_GUIEVENT_FAST(MyState)
|
||||
END_CLASS_MEMBER(GuiMyControl)
|
||||
```
|
||||
|
||||
### Step 6: XML Loader (`Source/Compiler/InstanceLoaders/GuiInstanceLoader_Plugin.cpp`)
|
||||
|
||||
```cpp
|
||||
// Add in IGuiPlugin::Load():
|
||||
ADD_TEMPLATE_CONTROL(GuiMyControl, MyControl);
|
||||
```
|
||||
|
||||
### Core Pattern Summary
|
||||
|
||||
Each control has:
|
||||
|
||||
- **A C++ class** managing state and events
|
||||
- **A template** defining visual properties
|
||||
- **Reflection** for runtime access
|
||||
- **XML loader** for declarative usage
|
||||
- **Theme integration** for consistent styling
|
||||
309
.github/KnowledgeBase/KB_GacUI_Design_ControlFocusSwitchingAndTabAltHandling.md
vendored
Normal file
309
.github/KnowledgeBase/KB_GacUI_Design_ControlFocusSwitchingAndTabAltHandling.md
vendored
Normal file
@@ -0,0 +1,309 @@
|
||||
# Control Focus Switching and TAB/ALT Handling
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
Control focus management in GacUI operates across three distinct architectural layers, each with specific responsibilities:
|
||||
|
||||
### Layer 1: Composition-Level Focus (`GuiGraphicsComposition`)
|
||||
|
||||
The lowest layer handles focus at the composition level through `GuiGraphicsHost`:
|
||||
|
||||
- `GuiGraphicsHost` maintains the currently focused composition in the `focusedComposition` member variable
|
||||
- `GuiGraphicsHost::SetFocusInternal` is the core focus-switching method that:
|
||||
- Fires `lostFocus` event on the currently focused composition (if any)
|
||||
- Updates `focusedComposition` to the new composition
|
||||
- Resets caret point to (0,0)
|
||||
- Fires `gotFocus` event on the newly focused composition
|
||||
- `GuiGraphicsHost::SetFocus` validates that the composition belongs to this host before calling `SetFocusInternal`
|
||||
- `GuiGraphicsHost::ClearFocus` removes focus from any composition
|
||||
- `GuiGraphicsHost::GetFocusedComposition` returns the currently focused composition
|
||||
- Each composition has an event receiver that fires `gotFocus` and `lostFocus` events
|
||||
|
||||
### Layer 2: Control-Level Focus (`GuiControl`)
|
||||
|
||||
The middle layer connects controls to composition-level focus:
|
||||
|
||||
- Each `GuiControl` has a `focusableComposition` property (can be null)
|
||||
- `GuiControl::SetFocusableComposition` attaches `gotFocus` and `lostFocus` event handlers to the composition
|
||||
- When composition gains focus: `GuiControl::OnGotFocus` is called, which:
|
||||
- Sets `isFocused = true`
|
||||
- Updates control template state
|
||||
- Fires `FocusedChanged` event
|
||||
- When composition loses focus: `GuiControl::OnLostFocus` is called, which:
|
||||
- Sets `isFocused = false`
|
||||
- Updates control template state
|
||||
- Fires `FocusedChanged` event
|
||||
- `GuiControl::GetFocused` checks if the control is currently focused
|
||||
- `GuiControl::SetFocused` is the public API to focus a control:
|
||||
- Returns early if no focusable composition exists
|
||||
- Returns early if control is not visually enabled
|
||||
- Returns early if focusable composition is not eventually visible
|
||||
- Gets the graphics host from the focusable composition
|
||||
- Calls `host->SetFocus(focusableComposition)`
|
||||
|
||||
### Layer 3: Focus Loss on State Changes
|
||||
|
||||
Focus is automatically cleared when controls undergo certain state changes:
|
||||
|
||||
- **When disabled**: `GuiControl::SetEnabled(false)` calls `UpdateVisuallyEnabled`, which triggers `OnLostFocus` if the control was focused
|
||||
- **When hidden**: `GuiControl::SetVisible(false)` does NOT automatically clear focus (based on test case comments indicating unclear behavior)
|
||||
- **When deleted**: `GuiGraphicsHost::DisconnectCompositionInternal` is called recursively, clearing `focusedComposition` if it matches the disconnected composition
|
||||
|
||||
## TAB Key Navigation
|
||||
|
||||
### Architecture Components
|
||||
|
||||
TAB navigation is managed by `GuiTabActionManager`, which is created and owned by `GuiGraphicsHost`.
|
||||
|
||||
#### IGuiTabAction Interface
|
||||
|
||||
Controls implement `IGuiTabAction` service to participate in TAB navigation:
|
||||
|
||||
- `GetAcceptTabInput()`: Returns whether control accepts TAB character as input (if true, TAB key is passed to control instead of used for navigation)
|
||||
- `GetTabPriority()`: Returns priority for TAB order (-1 = lowest priority, 0, 1, 2... = higher priorities)
|
||||
- `IsTabEnabled()`: Returns whether control can be tabbed to (checks if control is visible and enabled)
|
||||
- `IsTabAvailable()`: Returns whether control is available for TAB navigation (checks if focusable composition exists)
|
||||
|
||||
#### Control List Building (`BuildControlList`)
|
||||
|
||||
The TAB manager builds a prioritized list of focusable controls:
|
||||
|
||||
- Uses `tab_focus::CollectControls` to recursively collect all controls from the control host
|
||||
- Groups controls by their `TabPriority` value
|
||||
- Inserts controls in priority order: 0, 1, 2, ..., then -1 (default/unprioritized)
|
||||
- For each control, recursively collects its children and inserts them after the parent
|
||||
- Result is a flattened list `controlsInOrder` sorted by TAB priority
|
||||
- Cache is invalidated when a control's `TabPriority` changes via `GuiControl::SetTabPriority`
|
||||
|
||||
#### Focus Navigation (`GetNextFocusControl`)
|
||||
|
||||
Finding the next focusable control:
|
||||
|
||||
- Rebuilds control list if cache is invalidated (`available == false`)
|
||||
- Takes current focused control and offset (+1 for forward TAB, -1 for backward SHIFT+TAB)
|
||||
- Finds start index in the control list
|
||||
- Wraps around using modulo arithmetic: `(index + offset + count) % count`
|
||||
- Loops through controls until finding one that is both `IsTabAvailable()` and `IsTabEnabled()`
|
||||
- Returns the next focusable control or null if none found
|
||||
|
||||
#### Key Event Handling
|
||||
|
||||
TAB key processing in `GuiTabActionManager::KeyDown`:
|
||||
|
||||
- Only processes TAB key when CTRL and ALT are NOT pressed
|
||||
- If focused composition's control has `GetAcceptTabInput() == true`, TAB is NOT intercepted (returns false to let control handle it)
|
||||
- Otherwise, calls `GetNextFocusControl` with offset based on SHIFT key state
|
||||
- If next control found: calls `next->SetFocused()` and sets `supressTabOnce = true` to prevent TAB character insertion
|
||||
- Returns true to indicate key was handled
|
||||
|
||||
Character suppression in `GuiTabActionManager::Char`:
|
||||
|
||||
- Suppresses the TAB character (`\t`) if `supressTabOnce` is true
|
||||
- Resets `supressTabOnce` to false after checking
|
||||
|
||||
## ALT Key Navigation
|
||||
|
||||
### Architecture Components
|
||||
|
||||
ALT navigation is managed by `GuiAltActionManager`, which is created and owned by `GuiGraphicsHost`.
|
||||
|
||||
#### IGuiAltAction Interface
|
||||
|
||||
Controls implement `IGuiAltAction` service to have ALT shortcuts:
|
||||
|
||||
- `GetAlt()`: Returns the ALT key string (e.g., "F" for ALT+F)
|
||||
- `IsAltEnabled()`: Returns whether ALT action is enabled (checks visible and enabled state)
|
||||
- `IsAltAvailable()`: Returns whether ALT action is available (checks if focusable composition and alt string exist)
|
||||
- `GetAltComposition()`: Returns the composition to show the ALT key label on
|
||||
- `GetActivatingAltHost()`: Returns the alt host to enter when this action is activated (null = just activate)
|
||||
- `OnActiveAlt()`: Called when ALT key is activated (default implementation calls `SetFocused()`)
|
||||
- `IGuiAltAction::IsLegalAlt`: Validates that ALT string contains only uppercase letters (A-Z) and digits (0-9)
|
||||
|
||||
#### IGuiAltActionContainer Interface
|
||||
|
||||
Controls can implement this to provide multiple ALT actions:
|
||||
|
||||
- `GetAltActionCount()`: Returns the number of ALT actions
|
||||
- `GetAltAction(index)`: Returns the ALT action at the specified index
|
||||
|
||||
#### IGuiAltActionHost Interface
|
||||
|
||||
Hosts create a hierarchy of ALT contexts that can be entered/exited:
|
||||
|
||||
- `GetAltComposition()`: Returns the composition for this host
|
||||
- `GetPreviousAltHost()`: Returns the parent host in the hierarchy
|
||||
- `OnActivatedAltHost(previousHost)` and `OnDeactivatedAltHost()`: Called when entering/leaving
|
||||
- `CollectAltActions(actions)`: Collects all ALT actions for this host
|
||||
|
||||
ALT action collection from controls (`IGuiAltActionHost::CollectAltActionsFromControl`):
|
||||
|
||||
- Recursively traverses control tree starting from the specified control
|
||||
- If control has `IGuiAltActionContainer`: collects all actions from it
|
||||
- Else if control has `IGuiAltAction` and `IsAltAvailable()` and `IsAltEnabled()`: adds single action
|
||||
- **Critical behavior**: When a control has an ALT action, executes `continue` which prevents its children from being added to the traversal queue
|
||||
- This creates a "barrier" effect where children are hidden unless you enter a nested ALT context
|
||||
- Recursively processes all children only if the control doesn't have its own ALT action
|
||||
|
||||
### ALT Mode Lifecycle
|
||||
|
||||
#### Activation
|
||||
|
||||
Pressing ALT (KEY_MENU) without CTRL or SHIFT:
|
||||
|
||||
- `GuiAltActionManager::KeyDown` checks if control host implements `IGuiAltActionHost`
|
||||
- Calls `EnterAltHost` to enter ALT mode
|
||||
|
||||
`EnterAltHost` process:
|
||||
|
||||
- Calls `ClearAltHost` to clean up previous state
|
||||
- Calls `host->CollectAltActions(actions)` to get all available actions
|
||||
- If no actions available: calls `CloseAltHost` and exits
|
||||
- Calls `host->OnActivatedAltHost(currentAltHost)` with previous host
|
||||
- Updates `currentAltHost` to the new host
|
||||
- Calls `CreateAltTitles` to create visual labels
|
||||
|
||||
#### Visual Label Creation (`CreateAltTitles`)
|
||||
|
||||
Creating ALT key labels:
|
||||
|
||||
- For each ALT action key (grouped by alt string):
|
||||
- If single action and non-empty key: uses key as-is
|
||||
- If multiple actions with same key: appends numeric suffix (0-999) with zero-padding
|
||||
- Creates `currentActiveAltActions` dictionary mapping full key (e.g., "F", "F00", "F01") to action
|
||||
- For each action in `currentActiveAltActions`:
|
||||
- Creates a `GuiLabel` with `ThemeName::ShortcutKey` theme
|
||||
- Sets label text to the key string
|
||||
- Adds label to the action's composition
|
||||
- Stores label in `currentActiveAltTitles` dictionary
|
||||
- Calls `FilterTitles` to update visibility
|
||||
|
||||
#### Key Input Handling (`EnterAltKey`)
|
||||
|
||||
Processing typed characters in ALT mode:
|
||||
|
||||
- Appends typed character to `currentAltPrefix`
|
||||
- Checks if `currentAltPrefix` matches any action key in `currentActiveAltActions`
|
||||
- If no match: calls `FilterTitles` and removes last character if no visible titles remain
|
||||
- If exact match found:
|
||||
- If action has `GetActivatingAltHost()`: calls `EnterAltHost` to enter nested ALT host
|
||||
- Otherwise: calls `CloseAltHost` to exit ALT mode
|
||||
- Calls `action->OnActiveAlt()` to activate the action (usually focuses the control)
|
||||
- Sets `supressAltKey` to prevent key from being processed further
|
||||
- Returns true
|
||||
|
||||
#### Label Filtering (`FilterTitles`)
|
||||
|
||||
Updating label visibility based on current prefix:
|
||||
|
||||
- Iterates through all labels in `currentActiveAltTitles`
|
||||
- If label's key starts with `currentAltPrefix`: shows label and formats text with brackets around next character (e.g., "F[0]0")
|
||||
- Otherwise: hides label
|
||||
- Returns count of visible labels
|
||||
|
||||
#### Exiting ALT Mode
|
||||
|
||||
Ways to exit ALT mode:
|
||||
|
||||
- Press ESCAPE: calls `LeaveAltHost` to exit current host and restore previous
|
||||
- Press BACKSPACE: calls `LeaveAltKey` to remove last character from prefix
|
||||
- Clicking or any other key: depends on whether key matches an action
|
||||
- `CloseAltHost`: clears all state, deletes all labels, and exits all hosts in the hierarchy
|
||||
|
||||
#### Character and Key Suppression
|
||||
|
||||
Input suppression while in ALT mode:
|
||||
|
||||
- `GuiAltActionManager::Char` returns true to suppress all character input while in ALT mode or if `supressAltKey` is set
|
||||
- `GuiAltActionManager::KeyUp` suppresses the key-up event for the key stored in `supressAltKey`
|
||||
|
||||
### Nested ALT Hosts
|
||||
|
||||
#### Why Nested ALT Hosts Are Needed
|
||||
|
||||
The `continue` statement in `CollectAltActionsFromControl` creates a "barrier" when a control has its own ALT action. This prevents the control's children from being collected at the parent level. Nested ALT hosts provide a mechanism to enter the control's context and collect children's ALT actions at a nested level.
|
||||
|
||||
**Design Rationales for Custom `GetActivatingAltHost` Implementations:**
|
||||
|
||||
1. **Non-Child Control Relationships** (`GuiMenuButton`):
|
||||
- Problem: A menu button's submenu is a separate popup window, NOT a child control
|
||||
- Default collection only traverses child controls, cannot reach submenu items
|
||||
- Solution: Returns `subMenu->QueryTypedService<IGuiAltActionHost>()` to switch context to submenu window
|
||||
- Result: Pressing ALT+[key] on menu button enters submenu's ALT context
|
||||
|
||||
2. **Intentional Blocking** (`GuiComboBoxBase`):
|
||||
- Problem: UX design requires ALT key to just open dropdown, NOT enter nested navigation
|
||||
- Default would collect ALT actions from dropdown's controls
|
||||
- Solution: Explicitly returns `GuiSelectableButton::GetActivatingAltHost()` (usually null)
|
||||
- Result: ALT key only opens dropdown without entering nested mode
|
||||
|
||||
3. **Dynamic/Temporary Content** (`GuiVirtualDataGrid`):
|
||||
- Problem: Cell editor is created on-demand, not a permanent child
|
||||
- Default collection only sees permanent children, misses temporary editor
|
||||
- Solution: When `currentEditor` exists and has ALT action, calls `SetAltComposition(currentEditor->GetTemplate())` and `SetAltControl(focusControl, true)`, returns `this`
|
||||
- Result: Can navigate within cell editor using ALT keys
|
||||
|
||||
4. **Scoped Navigation for Dense Control Groups** (`GuiRibbonGroup`):
|
||||
- Problem: Ribbon groups contain many buttons, exposing all at window level creates too many conflicts
|
||||
- Default would collect all buttons at same level as the group (if group didn't have its own ALT action)
|
||||
- But: `GuiRibbonGroup` has its own ALT action (from `GuiControl` base), which blocks children via `continue`
|
||||
- Solution: Returns `this` when `IsAltAvailable()` is true, creating two-level navigation
|
||||
- Result: First press ALT+[group-key] to enter group, then press ALT+[button-key] to select button
|
||||
- Benefit: Reduces conflicts and creates logical grouping
|
||||
|
||||
5. **Optional Scoped Navigation** (`GuiDatePicker`):
|
||||
- Problem: Date picker displays calendar with many clickable date cells, needs optional nested navigation
|
||||
- Solution: Returns `this` when `nestedAlt` constructor parameter is true
|
||||
- Result: When enabled, pressing ALT+[key] enters calendar's ALT context for date selection
|
||||
|
||||
#### The Critical Role of the `continue` Barrier
|
||||
|
||||
The `continue` statement in `CollectAltActionsFromControl` serves as a crucial design element:
|
||||
|
||||
- **Without nested hosts**: When a control has an ALT action, the `continue` prevents children from being collected, making them unreachable
|
||||
- **With nested hosts**: `GetActivatingAltHost` provides a way to "un-hide" children by entering a nested context that re-collects them
|
||||
- **For containers like `GuiRibbonGroup`**: This creates hierarchical navigation instead of flat navigation, reducing ALT key conflicts and improving scalability for interfaces with many controls
|
||||
|
||||
## Event Flow Integration
|
||||
|
||||
### Key Event Processing Chain
|
||||
|
||||
`GuiGraphicsHost::KeyDown` processes native window key events in this order:
|
||||
|
||||
1. First tries `GuiAltActionManager::KeyDown`: if ALT manager is active or activates, returns true to consume event
|
||||
2. Then tries `GuiTabActionManager::KeyDown`: if TAB key is handled, returns true to consume event
|
||||
3. Then tries shortcut key manager (if exists)
|
||||
4. Finally delivers to focused composition's event receiver if not consumed
|
||||
|
||||
### Character Event Processing Chain
|
||||
|
||||
`GuiGraphicsHost::Char` processes character input in this order:
|
||||
|
||||
1. First tries `GuiTabActionManager::Char`: suppresses TAB character if just navigated
|
||||
2. Then tries `GuiAltActionManager::Char`: suppresses all input while in ALT mode
|
||||
3. Finally delivers to focused composition's event receiver if not suppressed
|
||||
|
||||
### Control Visibility and Enable State
|
||||
|
||||
`GuiControl::IsControlVisibleAndEnabled` determines if a control can receive focus:
|
||||
|
||||
- Traverses up parent chain checking both `GetVisible()` and `GetEnabled()`
|
||||
- Used by both `IsAltEnabled()` and `IsTabEnabled()` to determine if control can receive focus via ALT or TAB
|
||||
- Ensures that if a parent is disabled or invisible, all children are excluded from ALT/TAB navigation
|
||||
|
||||
## Test Coverage
|
||||
|
||||
### TAB Navigation Tests
|
||||
|
||||
`TestApplication_Tab.cpp` contains comprehensive TAB navigation tests:
|
||||
|
||||
- `TabNavigate`: Tests forward TAB through 3 buttons and backward SHIFT+TAB
|
||||
- `TabNavigateWithContainer`: Tests TAB through 5 buttons including nested containers (GroupBox)
|
||||
- `FocusedAndDisable`: Tests that disabling focused control clears focus
|
||||
- `FocusedAndHide`: Tests hiding focused control (focus behavior noted as unclear in comment)
|
||||
- `FocusedAndDelete`: Tests deleting focused control clears focus
|
||||
|
||||
### ALT Focus Test
|
||||
|
||||
`TestControls_CoreApplication_GuiControl.cpp` contains `AltFocus` test:
|
||||
|
||||
- Tests ALT key activation in nested control scenarios
|
||||
180
.github/KnowledgeBase/KB_GacUI_Design_ImplementingIGuiGraphicsElement.md
vendored
Normal file
180
.github/KnowledgeBase/KB_GacUI_Design_ImplementingIGuiGraphicsElement.md
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
# Implementing IGuiGraphicsElement
|
||||
|
||||
This document explains the end-to-end design and required implementation steps for adding a new graphics element to GacUI (a lightweight element similar to `GuiSolidLabelElement`, not a complex document-like element). It is organized around the involved interfaces, lifecycle, renderer abstraction, platform integration, and the event / rendering pipeline.
|
||||
|
||||
## 1. Core Interfaces and Their Responsibilities
|
||||
|
||||
1. IGuiGraphicsElement
|
||||
- Declares `GetRenderer()` and `GetOwnerComposition()` plus the protected `SetOwnerComposition(compositions::GuiGraphicsComposition*)` (invoked only by `GuiGraphicsComposition` as its friend).
|
||||
- Every concrete element inherits (indirectly) from this interface through `GuiElementBase<TElement>`.
|
||||
|
||||
|
||||
2. IGuiGraphicsRenderer
|
||||
- Owns the platform-dependent drawing logic for exactly one bound element (lifecycle: `Initialize(element)`, `Finalize()`).
|
||||
- Responds to state changes: `OnElementStateChanged()`.
|
||||
- Receives a render target with `SetRenderTarget(IGuiGraphicsRenderTarget*)` and draws via `Render(Rect bounds)`.
|
||||
- Supplies layout info through `GetMinSize()`.
|
||||
|
||||
|
||||
3. IGuiGraphicsRendererFactory
|
||||
- Creates renderers. Registered in the global `GuiGraphicsResourceManager` through `RegisterRendererFactory()` indirectly by `GuiElementRendererBase<...>::Register()`.
|
||||
|
||||
|
||||
4. IGuiGraphicsRenderTarget (and concrete subclasses per backend)
|
||||
- Encapsulates drawing surface, clip stack (`PushClipper`, `PopClipper`, `GetClipper`, `IsClipperCoverWholeTarget`).
|
||||
- Manages rendering phases (`StartRendering`, `StopRendering`, hosted variants) and reports device failures (`RenderTargetFailure`).
|
||||
|
||||
|
||||
5. GuiGraphicsRenderTarget (base implementation)
|
||||
- Implements clip stack logic and rendering phase orchestration, deferring platform specifics to derived classes (e.g., Direct2D / GDI targets override native start/stop and resource creation behavior).
|
||||
|
||||
|
||||
## 2. Element Class Pattern (`GuiElementBase<TElement>`) and Example
|
||||
|
||||
A lightweight element (e.g., `GuiSolidLabelElement`) follows this template pattern provided by `GuiElementBase<T>`:
|
||||
- Static `ElementTypeName` literal (e.g., `L"SolidLabel"`).
|
||||
- A protected constructor initializing default property values.
|
||||
- Public getters/setters; setters compare old vs new and call `InvokeOnElementStateChanged()` only when a change occurs (see `GuiSolidLabelElement::SetText`, `SetColor`, etc.).
|
||||
- Shape / format or content properties stored as POD or small structs (e.g., `Color`, `FontProperties`, `Alignment`, `WString`).
|
||||
- No rendering code appears in the element itself; it only stores state.
|
||||
|
||||
Typical additional element examples in `GuiGraphicsElement.h / .cpp` (all using the same pattern):
|
||||
- `GuiSolidBorderElement`, `Gui3DBorderElement`, `Gui3DSplitterElement` (two-color, directional),
|
||||
- `GuiSolidBackgroundElement`, `GuiGradientBackgroundElement`,
|
||||
- `GuiInnerShadowElement`,
|
||||
- `GuiImageFrameElement`,
|
||||
- `GuiPolygonElement`.
|
||||
All rely on property change notification through `InvokeOnElementStateChanged()` to invalidate caches in their renderers.
|
||||
|
||||
## 3. Renderer Abstraction (`GuiElementRendererBase<TElement, TRenderer, TRenderTarget>`)
|
||||
|
||||
Renderer bases implement a consistent lifecycle with overridable hooks:
|
||||
- `InitializeInternal()` / `FinalizeInternal()` for creating / releasing persistent resources (e.g., brushes, text formats, cached geometries).
|
||||
- `RenderTargetChangedInternal(old, new)` for resources tied to the render target (re-create when target switches or becomes available; also release on null).
|
||||
- `Render(Rect bounds)` does immediate drawing each frame.
|
||||
- `OnElementStateChanged()` invalidates or rebuilds caches when properties changed.
|
||||
- `GetMinSize()` supplied by the base (often updated via specific helper like `UpdateMinSize()` in label renderers after layout recomputation).
|
||||
|
||||
Examples:
|
||||
- Direct2D: `GuiSolidLabelElementRenderer` caches `ID2D1SolidColorBrush`, `Direct2DTextFormatPackage`, `IDWriteTextLayout`, re-building in `CreateTextLayout()` when font, text, wrapping, or max width changes; updates min size with `UpdateMinSize()`.
|
||||
- GDI: `GuiSolidLabelElementRenderer` caches `WinFont` and updates min width/height similarly.
|
||||
- Shared brush helpers: `GuiSolidBrushElementRendererBase` and `GuiGradientBrushElementRendererBase` unify brush caching logic in Direct2D path.
|
||||
|
||||
## 4. Cross-Platform Renderer Families
|
||||
|
||||
Per backend a parallel renderer hierarchy exists sharing naming but differing resource types:
|
||||
- Direct2D (`elements_windows_d2d`): uses `IWindowsDirect2DRenderTarget`, `ID2D1RenderTarget`, DirectWrite text formatting, gradient/radial brushes, effects (e.g., focus rectangle effect in `GuiFocusRectangleElementRenderer`).
|
||||
- GDI (`elements_windows_gdi`): uses `IWindowsGDIRenderTarget`, `WinDC`, `WinFont`, `WinPen`, `WinBrush`, Uniscribe text shaping for complex paragraphs or colorized text.
|
||||
- Remote / Hosted modes: (not shown here) follow analogous patterns; Hosted leverages whichever native renderer factories are active.
|
||||
|
||||
Each concrete renderer is registered exactly once through its static `Register()` created by the CRTP base and invoked inside platform bootstrap functions (`RendererMainDirect2D`, `RendererMainGDI`, or remote manager initialization).
|
||||
|
||||
## 5. Registration Flow
|
||||
|
||||
1. Application selects backend (e.g., Direct2D or GDI), calling `RendererMainDirect2D()` or `RendererMainGDI()`.
|
||||
2. Inside these functions each renderer’s `Register()` is invoked (e.g., `GuiSolidLabelElementRenderer::Register()`).
|
||||
3. `Register()` calls `GetGuiGraphicsResourceManager()->RegisterRendererFactory(ElementTypeName, factory)` linking element type to a factory.
|
||||
4. When an element instance is created via `GuiElementBase<T>::Create()`, the resource manager looks up the factory and constructs a matching renderer, calls `Initialize(element)`.
|
||||
5. When a composition later receives a render target, it propagates to bound elements’ renderers through `SetRenderTarget` (under control of composition tree traversal / host initialization).
|
||||
|
||||
## 6. Composition Ownership and Rendering Invocation
|
||||
|
||||
- A `GuiGraphicsComposition` holds at most one `IGuiGraphicsElement`. On attach it calls the element’s protected `SetOwnerComposition`.
|
||||
- Rendering pipeline (`GuiGraphicsHost::Render()`):
|
||||
1. Host invokes `windowComposition->Render(offset)` recursively.
|
||||
2. Each composition pushes clipping, then (if it has an element) obtains `element->GetRenderer()->Render(bounds)`.
|
||||
3. Render target clipping stack managed by `GuiGraphicsRenderTarget::PushClipper` / `PopClipper` ensures nested composition visibility.
|
||||
4. After traversal `StopRendering()` finalizes; any `RenderTargetFailure` is processed (e.g., lost device or resize triggers re-creation of render target and re-run of render).
|
||||
|
||||
## 7. State Change Propagation and Invalidation Chain
|
||||
|
||||
1. Setter in element detects a change and calls `InvokeOnElementStateChanged()` (provided by `GuiElementBase<T>`).
|
||||
2. That method calls the bound renderer’s `OnElementStateChanged()` so it can drop caches (brushes, layouts) or lazily refresh on next `Render`.
|
||||
3. `InvokeOnElementStateChanged()` also raises composition invalidation causing `GuiGraphicsHost` to mark `needRender = true`.
|
||||
4. The main loop / timer triggers `GuiGraphicsHost::GlobalTimer()`, which if `needRender` calls `Render()`.
|
||||
5. Min size recalculation done inside renderer (e.g., `GuiSolidLabelElementRenderer::UpdateMinSize()`) influences subsequent layout passes.
|
||||
|
||||
## 8. Min Size Calculation Strategy
|
||||
|
||||
- Renderers compute intrinsic size based on current element state and cached layout objects.
|
||||
- Example: label renderer (Direct2D or GDI) measures text (wrapping, font, multiline) and stores result so `GetMinSize()` returns consistent values until invalidated.
|
||||
- Property changes that affect geometry (text, font, wrap flags) call `InvokeOnElementStateChanged()` which triggers `UpdateMinSize()` inside the renderer on next render/state change handling.
|
||||
|
||||
## 9. Resource Lifetime and Render Target Changes
|
||||
|
||||
- Persistent resources independent of target (e.g., cached last element values) retained across target switches.
|
||||
- Target-bound resources (Direct2D brushes, text layouts, GDI pens/brushes, bitmaps) created in `InitializeInternal()` if target already set, or in `RenderTargetChangedInternal()` when a new target arrives.
|
||||
- On target loss (device lost / resize reported via `RenderTargetFailure`), host re-acquires target; each renderer releases old target objects in `RenderTargetChangedInternal(old, nullptr)` then recreates after new target available.
|
||||
|
||||
## 10. Adding a New Lightweight Element (Checklist)
|
||||
|
||||
Element Class:
|
||||
- Add class `GuiXxxElement : public GuiElementBase<GuiXxxElement>` with `static constexpr const wchar_t* ElementTypeName = L"Xxx"`.
|
||||
- Define private/protected property fields and defaults in constructor.
|
||||
- Implement getters and setters in `.cpp`; setters compare old/new and call `InvokeOnElementStateChanged()` on change only.
|
||||
|
||||
Renderer (per backend):
|
||||
- Derive `GuiXxxElementRenderer` from `GuiElementRendererBase<GuiXxxElement, GuiXxxElementRenderer, IWindowsDirect2DRenderTarget>` (and analog for GDI / Remote).
|
||||
- Implement hooks: `InitializeInternal()`, `FinalizeInternal()`, `RenderTargetChangedInternal(old,new)`, `Render(bounds)`, `OnElementStateChanged()`.
|
||||
- Cache necessary graphics resources; rebuild only when affected element properties change.
|
||||
- Compute / update min size (helper method like `UpdateMinSize()`).
|
||||
|
||||
Registration:
|
||||
- Insert `GuiXxxElementRenderer::Register()` in each backend initialization (Direct2D: `RendererMainDirect2D`, GDI: `RendererMainGDI`, Remote: remote resource manager initialization).
|
||||
|
||||
Testing:
|
||||
- Instantiate via XML `<Xxx>` mapping to `presentation::elements::GuiXxxElement` or directly create in C++ via `GuiXxxElement::Create()`.
|
||||
- Verify property mutations trigger re-render (breakpoint or visual change) and min size recomputation.
|
||||
|
||||
## 11. Differences From Complex Elements (e.g., `GuiDocumentElement`)
|
||||
|
||||
`GuiDocumentElement` adds a multi-layer model / cache system beyond the lightweight pattern:
|
||||
- Manages `DocumentModel` with paragraphs, runs, hyperlink packages, embedded objects instead of flat properties.
|
||||
- Has nested renderer (`GuiDocumentElementRenderer`) implementing `IGuiGraphicsParagraphCallback` and paragraph caching (`ParagraphCache` array) with lazy `IGuiGraphicsParagraph` creation.
|
||||
- Maintains selection, caret, inline objects, per-paragraph measurement arrays, and dynamic caret rendering utilities (`OpenCaret`, `CloseCaret`, `SetSelection` etc.).
|
||||
- Invalidation granularity per paragraph (`NotifyParagraphUpdated`).
|
||||
- Rendering enumerates paragraphs, reflows only needed ones (width-sensitive caching via `lastMaxWidth`).
|
||||
- Thus: far more complex state synchronization path vs. single-primitive elements where all state sits directly on the element and renderer only depends on a small fixed property set.
|
||||
|
||||
## 12. Common Pitfalls
|
||||
|
||||
- Forgetting to compare old vs new value in setter: causes redundant invalidations and potential performance issues.
|
||||
- Allocating target-bound resources in constructor instead of `InitializeInternal()` / `RenderTargetChangedInternal()` leads to null target usage or leaks.
|
||||
- Not releasing resources in `FinalizeInternal()` or when `RenderTargetChangedInternal(new == nullptr)` => leaks on device reset.
|
||||
- Failing to update min size after relevant property change (text/font/wrap) => layout flickers or stale size.
|
||||
- Omitting `Register()` call => element silently renders nothing (renderer never created).
|
||||
- Using element state directly inside `Render()` without caching expensive conversions (e.g., text layout) => per-frame overhead.
|
||||
|
||||
## 13. End-to-End Flow Summary
|
||||
|
||||
Creation: `GuiXxxElement::Create()` -> element object + renderer via factory -> `renderer->Initialize(element)`.
|
||||
Attachment: Composition calls `element->SetOwnerComposition(this)` -> composition tree now owns element.
|
||||
Render Target Acquisition: Composition tree traversal sets render target on each renderer (`SetRenderTarget`). `RenderTargetChangedInternal()` fires; resources created.
|
||||
Property Change: Setter -> `InvokeOnElementStateChanged()` -> renderer invalidation -> host scheduled.
|
||||
Frame Render: Host `GuiGraphicsHost::Render()` -> composition traversal -> each renderer `Render(bounds)` with valid clipper.
|
||||
Shutdown: Renderer `Finalize()` (invokes `FinalizeInternal()`), composition releases element, element destructor runs.
|
||||
|
||||
## 14. Quick Implementation Template (Narrative)
|
||||
|
||||
1. Declare element with properties + static name.
|
||||
2. Implement setters calling `InvokeOnElementStateChanged()` only on real changes.
|
||||
3. Add renderer (per backend) caching required native resources.
|
||||
4. In renderer `OnElementStateChanged()` mark internal dirty flags, rebuild brushes / layouts next use (or immediately) and update min size.
|
||||
5. Register renderer in backend startup.
|
||||
6. Test lifecycle: create window, add composition + element, mutate properties, resize window, switch backend if applicable.
|
||||
|
||||
## 15. Lightweight Element Implementation Checklist (Condensed)
|
||||
|
||||
- [ ] Element class added with `ElementTypeName`.
|
||||
- [ ] Constructor sets defaults.
|
||||
- [ ] All setters compare and call `InvokeOnElementStateChanged()`.
|
||||
- [ ] Renderer(s) created for every required backend.
|
||||
- [ ] `InitializeInternal` / `FinalizeInternal` implemented.
|
||||
- [ ] `RenderTargetChangedInternal` re/creates target-bound resources.
|
||||
- [ ] `Render` draws respecting bounds & element properties.
|
||||
- [ ] `OnElementStateChanged` invalidates & triggers min size recalculation.
|
||||
- [ ] Registration calls inserted in each backend entry point.
|
||||
- [ ] Remote protocol enums / serialization (if remote supported) implemented.
|
||||
- [ ] Manual / unit tests cover property changes & rendering.
|
||||
|
||||
This pattern ensures every simple element integrates uniformly into the composition, rendering, and resource management infrastructure provided by GacUI.
|
||||
218
.github/KnowledgeBase/KB_GacUI_Design_LayoutAndGuiGraphicsComposition.md
vendored
Normal file
218
.github/KnowledgeBase/KB_GacUI_Design_LayoutAndGuiGraphicsComposition.md
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
# Layout and GuiGraphicsComposition (Core Layout & Responsive Families)
|
||||
|
||||
## 1. Scope & Purpose
|
||||
This document explains the GacUI layout architecture centered on `GuiGraphicsComposition` and its classification subclasses, the predefined layout container families, bidirectional constraint propagation, invalidation & recomputation workflow driven by `GuiGraphicsHost::Render`, extension guidelines, and the responsive ("reactive") composition family (`GuiResponsive*`). It consolidates prior Q&A insights into a coherent design reference for future maintenance and extension.
|
||||
|
||||
## 2. Core Abstractions
|
||||
### 2.1 Base Class: `GuiGraphicsComposition`
|
||||
Responsibilities:
|
||||
- Maintain hierarchy: `children`, `parent` (mutation via `InsertChild`, `RemoveChild`, `MoveChild`).
|
||||
- Own a single graphics element: `ownedElement` (setter `SetOwnedElement`).
|
||||
- Store presentation & interaction flags: `visible`, `transparentToMouse`.
|
||||
- Store layout configuration: `internalMargin`, `preferredMinSize`, `minSizeLimitation`.
|
||||
- Cache measurement: `cachedMinSize` (set only by `Layout_SetCachedMinSize`), and arrangement: `cachedBounds` (set only by `Layout_SetCachedBounds`).
|
||||
- Propagate host context: `relatedHostRecord` via `UpdateRelatedHostRecord`.
|
||||
- Provide virtual layout interface:
|
||||
* `Layout_CalculateMinSize()` (measurement)
|
||||
* `Layout_CalculateMinClientSizeForParent(Margin parentInternalMargin)` (child -> parent contribution)
|
||||
* `Layout_CalculateBounds(Size parentSize)` (arrangement)
|
||||
- Orchestrate recursive layout passes via helpers:
|
||||
* `Layout_UpdateMinSize()` (calls overridden min size function then caches and fires `CachedMinSizeChanged`).
|
||||
* `Layout_UpdateBounds(Size parentSize)` (computes own bounds then recurses to children).
|
||||
* `Layout_CalculateMinSizeHelper()` shared policy used by most containers.
|
||||
- Invalidation trigger: `InvokeOnCompositionStateChanged(forceRequestRender)` sets flags; does not directly perform layout.
|
||||
- Synchronous optional recompute: `ForceCalculateSizeImmediately()` (used sparingly, e.g., interactive splitters).
|
||||
- Rendering entry: `Render` consumes only `cachedBounds` (no layout recalculation inside rendering).
|
||||
|
||||
### 2.2 Classification Subclasses
|
||||
Purpose: Distinguish measurement & arrangement control patterns.
|
||||
1. `GuiGraphicsComposition_Trivial`
|
||||
- Container autonomously computes measurement and bounds using standard algorithm (often via `Layout_CalculateMinSizeHelper`).
|
||||
- Examples: `GuiBoundsComposition`, `GuiTableComposition`, `GuiStackComposition`, `GuiFlowComposition`, `GuiSharedSizeRootComposition` (directly or indirectly).
|
||||
2. `GuiGraphicsComposition_Controlled`
|
||||
- Measurement / bounds set externally by parent; overrides `Layout_CalculateMinSize` & `Layout_CalculateBounds` to simply return cached values (no internal recompute). Parent writes caches using specialized setters (e.g., `GuiCellComposition::Layout_SetCellBounds`, `GuiStackItemComposition::Layout_SetStackItemBounds`, `GuiFlowItemComposition::Layout_SetFlowItemBounds`).
|
||||
- Typical for helper items: `GuiCellComposition`, splitters, stack items, flow items, shared size items.
|
||||
3. `GuiGraphicsComposition_Specialized`
|
||||
- Container with custom arrangement while limiting child -> parent enlargement; usually still uses `Layout_CalculateMinSizeHelper` but overrides `Layout_CalculateMinClientSizeForParent` to `{0,0}`.
|
||||
- Examples: `GuiSideAlignedComposition`, `GuiPartialViewComposition`, `GuiWindowComposition`.
|
||||
|
||||
## 3. Layout Pass Orchestration
|
||||
### 3.1 Measurement Flow
|
||||
`Layout_UpdateMinSize()` executes:
|
||||
1. Calls overridden `Layout_CalculateMinSize()`.
|
||||
2. That implementation typically delegates to `Layout_CalculateMinSizeHelper()`:
|
||||
- Start from `preferredMinSize`.
|
||||
- If `minSizeLimitation != NoLimit`, merge element renderer minimum.
|
||||
- Add `internalMargin`.
|
||||
- For each child: `child->Layout_UpdateMinSize()`.
|
||||
- If own `minSizeLimitation == LimitToElementAndChildren`, incorporate each child's contribution via `child->Layout_CalculateMinClientSizeForParent(internalMargin)`.
|
||||
3. Result stored by `Layout_SetCachedMinSize()` raising `CachedMinSizeChanged` for observers (parents, containers, special logic like stack/table invalidation flags).
|
||||
|
||||
### 3.2 Arrangement Flow
|
||||
`Layout_UpdateBounds(parentSize)` executes:
|
||||
1. Compute own rectangle with overridden `Layout_CalculateBounds(parentSize)`, store via `Layout_SetCachedBounds`.
|
||||
2. For each child: call `child->Layout_UpdateBounds(ownSize)`.
|
||||
3. Controlled children rely on parent precomputed rectangles delivered via container-specific setters instead of independent calculation.
|
||||
|
||||
### 3.3 Host-Driven Iteration
|
||||
- Changes call `InvokeOnCompositionStateChanged()` marking layout invalid (and optionally requesting a render).
|
||||
- `GuiGraphicsHost::Render` loops: if any layout-invalid flag is set for the window root subtree, perform a full layout (measurement + arrangement cascade), then render. Repeats until no invalidation remains.
|
||||
- Ensures deterministic stabilization without requiring synchronous recalculation inside property setters.
|
||||
- `ForceCalculateSizeImmediately()` is an optimization for immediate feedback inside event handlers (e.g., splitter dragging) but never required for correctness.
|
||||
|
||||
## 4. Invalidation Sources & Flags
|
||||
### 4.1 Generic Triggers
|
||||
- Structural: `InsertChild`, `RemoveChild`, `MoveChild`.
|
||||
- Property mutations: `SetInternalMargin`, `SetPreferredMinSize`, `SetVisible`, `SetMinSizeLimitation`, alignment & anchor changes.
|
||||
- Element replacement: `SetOwnedElement`.
|
||||
|
||||
### 4.2 Container-Specific Triggers
|
||||
- Table: `SetRowOption`, `SetColumnOption`, `SetCellPadding`, `SetBorderVisible`, `SetRowsAndColumns` (set `layout_invalid`, `layout_invalidCellBounds`).
|
||||
- Stack: `SetDirection`, `SetPadding`, scroll / ensure visible logic (sets `layout_invalid`).
|
||||
- Flow: `SetAxis`, `SetRowPadding`, other adjustments setting `layout_invalid`.
|
||||
- SharedSizeRoot: any group membership changes or measuring resets dictionaries.
|
||||
- Splitters: mouse move adjusting adjacent option values then invalidating parent + optional immediate recompute.
|
||||
- Responsive (later section): level transitions call `InvokeOnCompositionStateChanged` at root responsive node.
|
||||
|
||||
### 4.3 Child-Originated Propagation
|
||||
- Container listens to children `CachedMinSizeChanged` to raise its own invalidation flags (e.g., table cell, stack item, flow item compositions) to recompute aggregated geometry lazily on next layout pass.
|
||||
|
||||
## 5. Bidirectional Constraints
|
||||
### 5.1 Parent -> Child Constraint
|
||||
- Parent supplies available size during `Layout_UpdateBounds` recursion.
|
||||
- Specialized containers compute per-child rectangles first (table grid, stack linear offsets, flow line wrapping) then assign them (especially controlled children) using internal `Layout_Set...Bounds` helpers.
|
||||
|
||||
### 5.2 Child -> Parent Constraint
|
||||
- During parent measurement, after calling each child's `Layout_UpdateMinSize`, parent optionally adds `child->Layout_CalculateMinClientSizeForParent(internalMargin)` to its accumulated client size if own `minSizeLimitation == LimitToElementAndChildren`.
|
||||
- Controlled children often contribute `{0,0}` because parent already manually accounts for them (e.g., table computing row/column sizes; stack computing cumulative lengths; flow computing wrapping layout).
|
||||
|
||||
## 6. Predefined Layout Families
|
||||
Distinct algorithms (excluding helper-only controlled items):
|
||||
1. Bounds: `GuiBoundsComposition` (absolute rectangle + `alignmentToParent`).
|
||||
2. Table: `GuiTableComposition` (mixed absolute / percentage / min, spanning, splitters via `GuiRowSplitterComposition`, `GuiColumnSplitterComposition`).
|
||||
3. Stack: `GuiStackComposition` (directional linear, reversed variants, ensure-visible virtualization support via internal offset logic).
|
||||
4. Flow: `GuiFlowComposition` (adaptive multi-row wrapping with `FlowAlignment` & `GuiFlowOption` baseline/alignment choices).
|
||||
5. Shared Size: `GuiSharedSizeRootComposition` (cross-subtree size normalization for named groups via per-dimension dictionaries; item: `GuiSharedSizeItemComposition`).
|
||||
6. Side Aligned: `GuiSideAlignedComposition` (dock to a specified side with length ratio / max constraints).
|
||||
7. Partial View: `GuiPartialViewComposition` (scroll / viewport style fractional sub-rectangle selection with offsets).
|
||||
8. Window Root: `GuiWindowComposition` (ties client area to native window size, root for layout passes).
|
||||
Helper controlled participants: `GuiCellComposition`, `GuiStackItemComposition`, `GuiFlowItemComposition`, `GuiSharedSizeItemComposition`, `GuiRowSplitterComposition`, `GuiColumnSplitterComposition`.
|
||||
|
||||
## 7. Internal Optimization Flags & Lazy Strategies
|
||||
- Table maintains `layout_invalid`, `layout_invalidCellBounds`, recalculating row/column metrics and cell rectangles only when flagged inside `Layout_CalculateMinSize` / `Layout_CalculateBounds` (helpers: `Layout_UpdateCellBoundsInternal`, `Layout_UpdateCellBoundsPercentages`, `Layout_UpdateCellBoundsOffsets`).
|
||||
- Stack uses `layout_invalid`; recalculations centralized in `Layout_UpdateStackItemMinSizes` and arrangement in `Layout_UpdateStackItemBounds`.
|
||||
- Flow uses `layout_invalid` plus last constraint width; layout regeneration in `Layout_UpdateFlowItemLayout` / `Layout_UpdateFlowItemLayoutByConstraint`.
|
||||
- Shared Size re-derives `CalculateOriginalMinSizes`, `CollectSizes`, `AlignSizes` each pass (group dictionary rebuild).
|
||||
- Responsive container (later) recalculates bounds iteratively while adjusting levels.
|
||||
|
||||
## 8. Responsive (Reactive) Composition Family
|
||||
### 8.1 Goals
|
||||
Add adaptive multi-level representations (compact to expanded) without duplicating control state, layering on top of existing layout passes and invalidation system.
|
||||
|
||||
### 8.2 Key Classes
|
||||
- Abstract: `GuiResponsiveCompositionBase` (inherits `GuiBoundsComposition`).
|
||||
- Strategy / aggregator:
|
||||
* `GuiResponsiveViewComposition` (collection of alternative view subtrees + shared control migration).
|
||||
* `GuiResponsiveStackComposition` (summation-based level aggregation; adjust one child per level change).
|
||||
* `GuiResponsiveGroupComposition` (max-based aggregation; synchronized level propagation across children).
|
||||
* `GuiResponsiveFixedComposition` (terminal level 0 node).
|
||||
* `GuiResponsiveSharedComposition` (placeholder that installs a control from view's `sharedControls`).
|
||||
* `GuiResponsiveContainerComposition` (auto-resizes target by dynamic LevelUp/LevelDown based on live size comparisons).
|
||||
|
||||
### 8.3 Level Model & Operations
|
||||
- Each responsive subtree exposes `GetLevelCount()` and `GetCurrentLevel()`.
|
||||
- Transition methods: `LevelUp()`, `LevelDown()` return bool (true if level changed) enabling parent fallbacks.
|
||||
- `GuiResponsiveViewComposition` maintains `currentView` pointer; switching occurs only when leaf view can't further down/up inside itself (delegation to child fails) then advances to next/previous view; fires `BeforeSwitchingView` event before installing new view child.
|
||||
- Aggregation formulas:
|
||||
* View: `levelCount` = ?(child.levelCount or 1 if direction mismatch); `currentLevel` computed by reverse accumulation.
|
||||
* Stack: `levelCount` = ?(children.levelCount - 1) + 1; `currentLevel` = ?(children.currentLevel); change selects best candidate child by size heuristic.
|
||||
* Group: `levelCount` = max(children.levelCount); `currentLevel` = max(children.currentLevel); change broadcasts attempts.
|
||||
* Fixed: constants (1,0).
|
||||
|
||||
### 8.4 Direction Filtering
|
||||
- Each node has `ResponsiveDirection direction` mask; only children whose direction overlaps (`child->GetDirection()`) participate in counts & changes (macro-assisted filtering in Stack/Group code).
|
||||
|
||||
### 8.5 Shared Control Migration
|
||||
- `GuiResponsiveSharedComposition` locates ancestor `GuiResponsiveViewComposition` in `OnParentLineChanged` and installs its designated shared control (validated membership in `sharedControls`).
|
||||
- On view switch: old subtree removed, new view added, placeholders reinstall matching shared controls preserving internal widget state.
|
||||
|
||||
### 8.6 Automatic Adaptive Wrapper
|
||||
- `GuiResponsiveContainerComposition` monitors actual bounds vs `minSizeLowerBound` / `minSizeUpperBound`.
|
||||
- If shrinking below lower bound: loop `Layout_AdjustLevelDown` (calls target `LevelDown` + remeasure until fits or minimal).
|
||||
- If expanding beyond upper bound: loop `Layout_AdjustLevelUp` (speculative raise then rollback if overshoot) using `Layout_CompareSize` to test width/height axes indicated by direction bits.
|
||||
- After each level change, invokes `responsiveTarget->Layout_UpdateMinSize()` to refresh bounds used for subsequent comparisons.
|
||||
|
||||
### 8.7 Invalidation & Events
|
||||
- Any level recalculation culminating at root triggers `InvokeOnCompositionStateChanged` (root determined when `responsiveParent == nullptr`).
|
||||
- Events: `LevelCountChanged`, `CurrentLevelChanged` emitted only on value change; `BeforeSwitchingView` emitted just prior to replacing the active view child.
|
||||
|
||||
### 8.8 Extension Guidelines (Responsive)
|
||||
- Subclass `GuiResponsiveCompositionBase`.
|
||||
- Implement level introspection and transitions (`GetLevelCount`, `GetCurrentLevel`, `LevelUp`, `LevelDown`).
|
||||
- Maintain internal child collections with insertion/removal hooks (`OnResponsiveChildInserted/Removed`) if aggregating.
|
||||
- Propagate recalculation upward via `OnResponsiveChildLevelUpdated` after recomputing own counts.
|
||||
- Apply direction filtering semantics uniformly (mask intersection tests).
|
||||
|
||||
## 9. Detailed Constraint Examples
|
||||
### 9.1 Table Example (Column Percentage Change)
|
||||
1. Setter `GuiTableComposition::SetColumnOption` updates model then calls `InvokeOnCompositionStateChanged()`.
|
||||
2. Next `GuiGraphicsHost::Render` detects invalid layout, triggers full window layout.
|
||||
3. Table `Layout_CalculateMinSize` sees `layout_invalid` -> recomputes metrics; `Layout_CalculateBounds` updates each `GuiCellComposition` via `Layout_SetCellBounds`.
|
||||
4. Rendering consumes new `cachedBounds`.
|
||||
|
||||
### 9.2 Interactive Splitter Drag
|
||||
1. Mouse move updates adjacent Absolute options.
|
||||
2. Each option setter invalidates table.
|
||||
3. Drag handler optionally calls `ForceCalculateSizeImmediately()` for immediate geometry update (without waiting for host loop) to achieve fluid cursor feedback.
|
||||
|
||||
## 10. Adding a New Layout Composition (Non-Responsive)
|
||||
Checklist:
|
||||
1. Choose subclass archetype (Trivial vs Specialized vs Controlled) based on whether parent or child owns measurement/arrangement logic.
|
||||
2. Define internal dirty flags mirroring existing patterns (e.g., `layout_invalid`).
|
||||
3. Implement measurement using either `Layout_CalculateMinSizeHelper()` or custom pass collecting controlled child min sizes first (as table/stack/flow do).
|
||||
4. Implement arrangement; for multi-phase (size distribution + placement) separate helpers and guard with dirty flags.
|
||||
5. Decide child -> parent influence by overriding `Layout_CalculateMinClientSizeForParent` (return `{0,0}` to suppress growth).
|
||||
6. Provide internal setters for controlled children to assign bounds then mark them (e.g., `Layout_SetXItemBounds`).
|
||||
7. Fire `InvokeOnCompositionStateChanged()` in every state-mutating setter.
|
||||
8. Subscribe to children `CachedMinSizeChanged` where dynamic re-aggregation is required.
|
||||
9. Use `ForceCalculateSizeImmediately()` only for real-time adjustments inside input handlers when latency matters.
|
||||
|
||||
## 11. Adding a New Responsive Strategy
|
||||
Steps:
|
||||
1. Subclass `GuiResponsiveCompositionBase`.
|
||||
2. Maintain children list & direction mask; filter by direction overlaps.
|
||||
3. Implement `GetLevelCount` / `GetCurrentLevel` with chosen aggregation formula.
|
||||
4. Implement `LevelDown` / `LevelUp` returning false when already at boundary (enabling parent fallback switching logic in view/stack/group containers).
|
||||
5. Invoke recalculation & root invalidation through `OnResponsiveChildLevelUpdated`.
|
||||
6. Integrate with container adaptation if needed (work with `GuiResponsiveContainerComposition`).
|
||||
7. Provide deterministic selection heuristics for partial adjustments (e.g., choose largest or smallest child) if not all children change simultaneously.
|
||||
|
||||
## 12. Separation of Concerns
|
||||
- Layout computation (measurement + arrangement) is strictly segregated from rendering (`Render` side-effect-free wrt layout state).
|
||||
- Invalidation is single entry (`InvokeOnCompositionStateChanged`) leaving scheduling & iteration to the host.
|
||||
- Responsive system layers on top without altering the base pass algorithm; it only manipulates subtree structure and min-size outputs between passes.
|
||||
|
||||
## 13. Reliability & Stability Patterns
|
||||
- Iterative host loop ensures eventual fixpoint when cascading invalidations occur (e.g., responsive level changes causing new size measurements).
|
||||
- Dirty flag + lazy recomputation prevents redundant heavy recomputations (especially table & flow geometry).
|
||||
- Controlled compositions decouple per-item geometry from measuring logic to simplify parent algorithms.
|
||||
|
||||
## 14. Proof Reference Index (Quick Lookup)
|
||||
- Invalidation Core: `GuiGraphicsComposition::InvokeOnCompositionStateChanged`.
|
||||
- Layout Core Helpers: `Layout_CalculateMinSizeHelper`, `Layout_UpdateMinSize`, `Layout_UpdateBounds`.
|
||||
- Forced Immediate Recompute: `ForceCalculateSizeImmediately`.
|
||||
- Table: `GuiTableComposition::{SetRowOption,SetColumnOption,Layout_CalculateMinSize,Layout_UpdateCellBoundsInternal,Layout_UpdateCellBoundsPercentages,Layout_UpdateCellBoundsOffsets}`, cell bounds setter `GuiCellComposition::Layout_SetCellBounds`.
|
||||
- Stack: `GuiStackComposition::{SetDirection,Layout_UpdateStackItemMinSizes,Layout_CalculateMinSize,Layout_UpdateStackItemBounds}`, item setter `GuiStackItemComposition::Layout_SetStackItemBounds`.
|
||||
- Flow: `GuiFlowComposition::{SetAxis,SetRowPadding,Layout_UpdateFlowItemLayout,Layout_UpdateFlowItemLayoutByConstraint}`, item setter `GuiFlowItemComposition::Layout_SetFlowItemBounds`.
|
||||
- Shared Size: `GuiSharedSizeRootComposition::{CalculateOriginalMinSizes,CollectSizes,AlignSizes}`, item logic `GuiSharedSizeItemComposition::Layout_CalculateMinSize`.
|
||||
- Splitters: `GuiTableSplitterCompositionBase::OnMouseMoveHelper`.
|
||||
- Responsive Base: `GuiResponsiveCompositionBase::{OnParentLineChanged,OnResponsiveChildLevelUpdated}`.
|
||||
- Responsive View: `GuiResponsiveViewComposition::{LevelDown,LevelUp,CalculateLevelCount,CalculateCurrentLevel}`.
|
||||
- Responsive Stack: `GuiResponsiveStackComposition::{CalculateLevelCount,CalculateCurrentLevel,LevelDown,LevelUp}`.
|
||||
- Responsive Group: `GuiResponsiveGroupComposition::{CalculateLevelCount,CalculateCurrentLevel,LevelDown,LevelUp}`.
|
||||
- Responsive Fixed: `GuiResponsiveFixedComposition` trivial implementations.
|
||||
- Responsive Shared: `GuiResponsiveSharedComposition::{OnParentLineChanged,SetSharedControl}`.
|
||||
- Responsive Container: `GuiResponsiveContainerComposition::{Layout_CalculateBounds,Layout_AdjustLevelUp,Layout_AdjustLevelDown,Layout_CompareSize}`.
|
||||
|
||||
## 15. Summary
|
||||
The GacUI layout engine employs a clean separation between invalidation, measurement, arrangement, and rendering, with composition classification enabling optimized parent-child cooperation patterns. Predefined container families cover common layout paradigms (grid, linear, wrapping, docking, viewport, shared sizing) while the responsive layer introduces adaptive multi-level UI transformation without complicating the core pass. Extension points are well-localized: new algorithms implement three virtual layout functions (or override contribution behavior), while responsive strategies implement discrete level semantics. Lazy flags and controlled item abstractions keep recomputation efficient, and host-driven iterative stabilization guarantees correctness even under cascading dynamic changes.
|
||||
1011
.github/KnowledgeBase/KB_GacUI_Design_ListControlArchitecture.md
vendored
Normal file
1011
.github/KnowledgeBase/KB_GacUI_Design_ListControlArchitecture.md
vendored
Normal file
File diff suppressed because it is too large
Load Diff
135
.github/KnowledgeBase/KB_GacUI_Design_MainWindowModalWindow.md
vendored
Normal file
135
.github/KnowledgeBase/KB_GacUI_Design_MainWindowModalWindow.md
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
Main Window and Modal Window Management
|
||||
|
||||
GacUI's window management system provides a sophisticated multi-layered architecture that enables consistent main window and modal window behavior across all supported platforms while maintaining platform-specific optimizations.
|
||||
|
||||
## Application Loop Architecture
|
||||
|
||||
The application loop operates through a hierarchical delegation pattern from high-level application management down to platform-specific message processing.
|
||||
|
||||
### GuiApplication Layer
|
||||
|
||||
`GuiApplication` serves as the primary application framework entry point with two essential methods:
|
||||
|
||||
- `Run(GuiWindow* mainWindow)`: Establishes the main window, delegates to the native controller's window service, and manages application lifecycle
|
||||
- `RunOneCycle()`: Processes a single iteration of the message loop by delegating to the platform-specific window service
|
||||
|
||||
The application layer maintains the main window reference and coordinates with the underlying native controller through `GetCurrentController()->WindowService()`.
|
||||
|
||||
### INativeWindowService Abstraction
|
||||
|
||||
The `INativeWindowService` interface provides platform-agnostic window lifecycle management:
|
||||
|
||||
- `Run(INativeWindow* window)`: Initiates the blocking message loop until the window closes
|
||||
- `RunOneCycle()`: Processes one iteration of events and returns continuation status
|
||||
|
||||
This abstraction enables consistent behavior across Windows native, hosted mode, and remote mode implementations.
|
||||
|
||||
## Platform-Specific Implementations
|
||||
|
||||
### Windows Native Platform
|
||||
|
||||
The Windows implementation follows traditional Win32 message pump patterns:
|
||||
- Direct integration with Windows message queue using `GetMessage`/`DispatchMessage`
|
||||
- Each `GuiWindow` maps to a dedicated HWND
|
||||
- Application exits when the main window's HWND is destroyed
|
||||
|
||||
### Hosted Mode Implementation
|
||||
|
||||
`GuiHostedController` virtualizes multiple windows within a single native window:
|
||||
- Delegates to underlying native controller while managing virtual windows through `WindowManager<GuiHostedWindow*>`
|
||||
- Performs coordinate transformation for multiple virtual windows
|
||||
- Provides windowing capabilities within embedded environments
|
||||
|
||||
### Remote Mode Implementation
|
||||
|
||||
`GuiRemoteController` completely abstracts platform dependencies:
|
||||
- Processes events through `IGuiRemoteProtocol` instead of OS message queues
|
||||
- All windows are virtualized and rendered through protocol messages
|
||||
- Application exits when protocol connection terminates
|
||||
|
||||
## Modal Window System
|
||||
|
||||
### Non-Blocking Modal Architecture
|
||||
|
||||
Modal windows in GacUI provide traditional blocking semantics without actually blocking the underlying event processing system. This is achieved through an event-driven callback architecture:
|
||||
|
||||
**Event-Driven Completion**: `ShowModal` returns immediately after setting up the modal state and relies on the `WindowReadyToClose` event to execute completion callbacks asynchronously.
|
||||
|
||||
**Selective Window Disabling**: Modal behavior is created by disabling the owner window (`owner->SetEnabled(false)`) while keeping the modal window and message loop fully operational.
|
||||
|
||||
**Continuous Message Processing**: The underlying `INativeWindowService::RunOneCycle` continues processing all events, timers, and async operations normally, ensuring the application remains responsive.
|
||||
|
||||
### Modal Window Variants
|
||||
|
||||
**ShowModal(owner, callback):**
|
||||
- Basic modal behavior with custom completion callback
|
||||
- Owner window disabled until modal closes
|
||||
- Establishes parent-child relationship and focus management
|
||||
|
||||
**ShowModalAndDelete(owner, callback):**
|
||||
- Combines modal behavior with automatic cleanup via `DeleteAfterProcessingAllEvents`
|
||||
- Provides optional separate callback for deletion completion
|
||||
- Ensures proper resource cleanup after modal completion
|
||||
|
||||
**ShowModalAsync(owner):**
|
||||
- Returns `IFuture` for async/await pattern integration
|
||||
- Demonstrates the truly non-blocking nature of the modal system
|
||||
- Enables modern asynchronous programming patterns
|
||||
|
||||
### Modal Record Management
|
||||
|
||||
The system tracks modal window chains through `ShowModalRecord` structures:
|
||||
|
||||
```cpp
|
||||
struct ShowModalRecord {
|
||||
GuiWindow* origin; // Root window that started modal chain
|
||||
GuiWindow* current; // Currently active modal window
|
||||
};
|
||||
```
|
||||
|
||||
This enables nested modal windows and proper cleanup when modal chains are established.
|
||||
|
||||
### Hosted Mode Modal Considerations
|
||||
|
||||
In hosted mode, modal windows receive special handling in `GuiWindow::BeforeClosing()`:
|
||||
- Main window close attempts are intercepted when modal windows exist
|
||||
- Active modal windows are either hidden or focused instead of allowing main window closure
|
||||
- Prevents premature application termination while modal dialogs are active
|
||||
|
||||
## Cross-Platform Event Processing
|
||||
|
||||
### Platform-Specific Message Handling
|
||||
|
||||
Despite different underlying implementations, modal windows work consistently across all platforms:
|
||||
|
||||
**Windows Native**: Win32 message pump continues processing `WM_*` messages while owner windows are disabled, allowing modal windows to remain interactive.
|
||||
|
||||
**Hosted Mode**: Virtual window manager continues processing events within the host window, with coordinate transformation handling multiple virtual windows.
|
||||
|
||||
**Remote Mode**: Protocol event processing continues normally, with modal state managed through the protocol abstraction layer.
|
||||
|
||||
### Memory Management During Modal Operations
|
||||
|
||||
The system handles object lifetime carefully during modal operations:
|
||||
- Uses `GetDisposedFlag()` to detect window destruction during modal operation
|
||||
- Employs `InvokeInMainThread` for safe event handler cleanup
|
||||
- Ensures proper resource management even if modal windows are unexpectedly destroyed
|
||||
|
||||
### Async Operations Integration
|
||||
|
||||
Modal windows integrate seamlessly with GacUI's async infrastructure:
|
||||
- Timer callbacks continue through `CallbackService.InvokeGlobalTimer()`
|
||||
- Async task execution proceeds via `asyncService.ExecuteAsyncTasks()`
|
||||
- All platforms maintain consistent async processing during modal operations
|
||||
|
||||
## Cross-Platform Consistency
|
||||
|
||||
The architecture successfully abstracts platform differences while providing rich windowing capabilities:
|
||||
|
||||
**Unified Modal API**: Same modal window interface across all platforms with identical semantics and behavior patterns.
|
||||
|
||||
**Consistent Event Processing**: All platforms maintain responsive user interfaces through continued message loop processing during modal operations.
|
||||
|
||||
**Platform-Optimized Implementation**: Each platform uses its optimal event processing mechanism while maintaining the same high-level behavior.
|
||||
|
||||
This design enables developers to create sophisticated GUI applications with complex modal dialog patterns that work consistently across Windows native, hosted, and remote environments while maintaining the responsiveness expected in modern applications.
|
||||
223
.github/KnowledgeBase/KB_GacUI_Design_PlatformInitialization.md
vendored
Normal file
223
.github/KnowledgeBase/KB_GacUI_Design_PlatformInitialization.md
vendored
Normal file
@@ -0,0 +1,223 @@
|
||||
GacUI implements a sophisticated multi-platform initialization system that provides consistent API across different operating systems and rendering backends while maintaining platform-specific optimizations. The initialization process follows a layered architecture that enables cross-platform support through well-defined abstraction layers.
|
||||
|
||||
## Platform Support Overview
|
||||
|
||||
GacUI is designed to support multiple platforms with different rendering backends:
|
||||
|
||||
1. **Windows Direct2D** - Modern Windows graphics using Direct2D and DirectWrite with hardware acceleration
|
||||
2. **Windows GDI** - Legacy Windows graphics using traditional GDI for compatibility with older systems
|
||||
3. **Linux GTK** - Linux platform support with GTK rendering (declared as `SetupGtkRenderer()` but implementation in separate repository)
|
||||
4. **macOS Cocoa** - macOS platform support with Core Graphics rendering (declared as `SetupOSXCoreGraphicsRenderer()` but implementation in separate repository)
|
||||
5. **Remote Rendering** - Platform-agnostic remote rendering over network protocols for testing and distributed applications
|
||||
6. **Code Generation** - Special mode for compile-time code generation (GacGen)
|
||||
|
||||
The actual Linux and macOS implementations are maintained in separate repositories, but the entry points are declared in this codebase to maintain API consistency. The architecture is designed for extensibility, with clear separation between platform-specific implementations and the core framework.
|
||||
|
||||
## Entry Point Architecture
|
||||
|
||||
The initialization system uses a consistent naming pattern for entry points: `Setup[Platform][Renderer][Mode]()`. Each combination provides different capabilities:
|
||||
|
||||
### Standard Mode Entry Points
|
||||
- `SetupWindowsDirect2DRenderer()` - Full Direct2D application with complete framework and native OS windows
|
||||
- `SetupWindowsGDIRenderer()` - Full GDI application with complete framework and native OS windows
|
||||
- `SetupGtkRenderer()` - Full Linux/GTK application (implementation in separate repository)
|
||||
- `SetupOSXCoreGraphicsRenderer()` - Full macOS application (implementation in separate repository)
|
||||
- `SetupRemoteNativeController(protocol)` - Full remote application with protocol communication
|
||||
|
||||
Standard mode provides the complete GacUI application framework including `GuiApplication`, native window management for each GacUI window, event handling, tooltips, global shortcuts, and all system services.
|
||||
|
||||
### Hosted Mode Entry Points
|
||||
- `SetupHostedWindowsDirect2DRenderer()` - Direct2D embedded within a single native OS window
|
||||
- `SetupHostedWindowsGDIRenderer()` - GDI embedded within a single native OS window
|
||||
|
||||
Hosted mode runs the entire GacUI application within only one native OS window. All GacUI sub-windows, dialogs, and menus are rendered as graphics rather than creating additional native OS windows. This is achieved by wrapping the native controller with `GuiHostedController`, which provides window abstraction while sharing the host application's window handle.
|
||||
|
||||
### Raw Mode Entry Points
|
||||
- `SetupRawWindowsDirect2DRenderer()` - Direct2D rendering without `GuiApplication` or `GuiWindow`
|
||||
- `SetupRawWindowsGDIRenderer()` - GDI rendering without `GuiApplication` or `GuiWindow`
|
||||
|
||||
Raw mode provides minimal rendering capabilities without the application framework. It completely bypasses `GuiApplication` and `GuiWindow` creation, calling `GuiRawMain()` instead of the full application initialization. This mode is suitable for custom applications that need direct control over initialization and only require graphics rendering capabilities.
|
||||
|
||||
## Initialization Flow
|
||||
|
||||
The initialization process follows a consistent six-phase sequence from platform entry point to user code:
|
||||
|
||||
### Phase 1: Platform Entry Point
|
||||
The process begins at platform-specific entry points (`WinMain` on Windows, `main` on Linux) which immediately delegate to the appropriate setup function based on desired renderer and mode.
|
||||
|
||||
### Phase 2: Setup Function Routing
|
||||
Setup functions route to internal implementation functions:
|
||||
- `SetupWindowsDirect2DRenderer()` ? `SetupWindowsDirect2DRendererInternal(false, false)`
|
||||
- `SetupHostedWindowsDirect2DRenderer()` ? `SetupWindowsDirect2DRendererInternal(true, false)`
|
||||
- `SetupRawWindowsDirect2DRenderer()` ? `SetupWindowsDirect2DRendererInternal(false, true)`
|
||||
|
||||
### Phase 3: Renderer Main Functions
|
||||
Internal setup functions call platform-specific renderer main functions:
|
||||
- `RendererMainDirect2D(hostedController, isRawMode)`
|
||||
- `RendererMainGDI(hostedController, isRawMode)`
|
||||
|
||||
### Phase 4: Application vs Raw Initialization
|
||||
Renderer main functions branch based on mode:
|
||||
- **Standard/Hosted Mode**: `GuiApplicationMain()` ? `GuiApplicationInitialize()`
|
||||
- **Raw Mode**: `GuiRawInitialize()` ? `GuiRawMain()`
|
||||
|
||||
### Phase 5: Framework Setup (Non-Raw Only)
|
||||
`GuiApplicationInitialize()` performs comprehensive framework initialization:
|
||||
1. Theme system initialization via `theme::InitializeTheme()`
|
||||
2. Reflection system loading via `GetGlobalTypeManager()->Load()`
|
||||
3. Plugin loading via `GetPluginManager()->Load(true, true)`
|
||||
4. Input timer startup via `GetCurrentController()->InputService()->StartTimer()`
|
||||
5. Async scheduler registration for UI and background threads
|
||||
6. Utility system initialization via `GuiInitializeUtilities()`
|
||||
|
||||
### Phase 6: Application Instance and User Code
|
||||
The framework creates a `GuiApplication` instance on the stack and calls the user-defined `GuiMain()` function:
|
||||
```cpp
|
||||
{
|
||||
GuiApplication app; // Creates application instance
|
||||
application = &app; // Sets global application pointer
|
||||
GuiMain(); // Calls user-defined function
|
||||
}
|
||||
application = nullptr; // Clears global application pointer
|
||||
```
|
||||
|
||||
## Platform-Specific Initialization
|
||||
|
||||
Each platform has distinct initialization requirements and optimizations:
|
||||
|
||||
### Windows Direct2D Platform
|
||||
Direct2D initialization focuses on hardware acceleration and modern graphics capabilities:
|
||||
- **DPI Awareness**: Enabled by default with `InitDpiAwareness(true)`
|
||||
- **COM Initialization**: `CoInitializeEx(NULL, COINIT_MULTITHREADED)` for Direct2D/DirectWrite
|
||||
- **Graphics Factories**: Creates `ID2D1Factory` with debug settings in debug builds
|
||||
- **Hardware Acceleration**: Creates D3D11 device with WARP fallback for software rendering
|
||||
- **Text Rendering**: Creates `IDWriteFactory` for advanced typography
|
||||
- **Render Target Strategy**: Chooses Direct2D 1.1 with D3D11 or Direct2D 1.0 fallback
|
||||
|
||||
### Windows GDI Platform
|
||||
GDI initialization emphasizes compatibility and traditional Windows graphics:
|
||||
- **DPI Awareness**: Disabled by default with `InitDpiAwareness(false)` for legacy compatibility
|
||||
- **COM Initialization**: `CoInitializeEx(NULL, COINIT_MULTITHREADED)` for system services
|
||||
- **Text Rendering**: Integrates with Uniscribe for complex text layout
|
||||
- **Font Linking**: Uses MLang for international font support
|
||||
- **Buffer Management**: Optimized bitmap buffers with intelligent repainting strategies
|
||||
|
||||
### Remote Platform
|
||||
Remote initialization abstracts away platform specifics entirely:
|
||||
- **No Platform Dependencies**: No direct OS graphics API calls
|
||||
- **Protocol Abstraction**: All rendering commands serialized through `IGuiRemoteProtocol`
|
||||
- **Virtual Resources**: Fonts, cursors, screens simulated from protocol configuration
|
||||
- **Event Virtualization**: Input events delivered through protocol messages rather than OS events
|
||||
|
||||
## Renderer and Resource Manager Initialization
|
||||
|
||||
The graphics system initialization occurs before application framework setup and follows the same pattern across all native platforms:
|
||||
|
||||
### Resource Manager Creation
|
||||
Each platform creates specialized resource managers:
|
||||
- **Direct2D**: `WindowsDirect2DResourceManager` with cached allocators for brushes, text formats, and char measurers
|
||||
- **GDI**: `WindowsGDIResourceManager` with cached allocators for pens, brushes, fonts, and char measurers
|
||||
- **Remote**: `GuiRemoteGraphicsResourceManager` for protocol communication
|
||||
|
||||
### Controller Integration
|
||||
Resource managers register as native controller listeners to receive window lifecycle events:
|
||||
```cpp
|
||||
windows::GetWindowsNativeController()->CallbackService()->InstallListener(&resourceManager);
|
||||
```
|
||||
|
||||
### Graphics Resource Manager Setup
|
||||
For hosted scenarios, an additional wrapper layer provides abstraction:
|
||||
```cpp
|
||||
auto hostedResourceManager = hostedController ?
|
||||
new GuiHostedGraphicsResourceManager(hostedController, &resourceManager) : nullptr;
|
||||
SetGuiGraphicsResourceManager(
|
||||
hostedResourceManager ? hostedResourceManager : &resourceManager);
|
||||
```
|
||||
|
||||
### Element Renderer Registration
|
||||
All graphics element renderers register with the resource manager through static `Register()` methods in the renderer main functions. The registration occurs in `RendererMainDirect2D()` and `RendererMainGDI()` before calling the application initialization:
|
||||
|
||||
- Focus rectangles, borders, backgrounds, shadows
|
||||
- Text labels, images, polygons
|
||||
- Specialized renderers like `GuiDirect2DElementRenderer` for custom graphics
|
||||
- Document elements for rich text rendering
|
||||
|
||||
Each renderer follows the factory pattern defined in `GuiGraphicsResourceManager.h`:
|
||||
```cpp
|
||||
static void Register()
|
||||
{
|
||||
auto manager = GetGuiGraphicsResourceManager();
|
||||
manager->RegisterRendererFactory(TElement::GetElementType(),
|
||||
Ptr(new typename TRenderer::Factory));
|
||||
}
|
||||
```
|
||||
|
||||
The registration mechanism uses the `GuiElementRendererBase` template which provides the `Register()` static method that calls `GetGuiGraphicsResourceManager()->RegisterRendererFactory()` to bind element types to renderer factories.
|
||||
|
||||
## Service Registration and Dependencies
|
||||
|
||||
The application framework registers services in a specific order to handle dependencies correctly:
|
||||
|
||||
1. **Callback Service** - Foundation event dispatch system
|
||||
2. **Resource Service** - System fonts, cursors, and default resources
|
||||
3. **Async Service** - Cross-thread communication infrastructure
|
||||
4. **Clipboard Service** - System clipboard integration
|
||||
5. **Image Service** - Image loading and processing capabilities
|
||||
6. **Screen Service** - Multi-monitor support and screen information
|
||||
7. **Window Service** - Window creation, management, and lifecycle
|
||||
8. **Input Service** - Keyboard, mouse, and timer services
|
||||
9. **Dialog Service** - File dialogs and message boxes
|
||||
|
||||
## Remote Mode Architecture
|
||||
|
||||
Remote mode implements a sophisticated abstraction layer that enables platform-independent operation and advanced testing capabilities.
|
||||
|
||||
### Controller Hierarchy
|
||||
Remote mode always operates through hosted mode with a layered controller architecture:
|
||||
1. **GuiRemoteController** - Platform-agnostic native controller interface
|
||||
2. **GuiHostedController** - Wrapper providing window management abstraction
|
||||
3. **Protocol Layer** - `IGuiRemoteProtocol` interface for communication abstraction
|
||||
|
||||
Remote mode inherently requires hosted mode because it has no real native windows, only virtual windows managed through protocol messages, and all rendering occurs within a single host window.
|
||||
|
||||
### Protocol Communication
|
||||
The remote controller implements all native controller services through bidirectional protocol communication:
|
||||
|
||||
**Outgoing Requests**: Font configuration queries, screen configuration requests, window operations, input state queries
|
||||
**Incoming Events**: Connection lifecycle, input events (keyboard/mouse), window state changes
|
||||
|
||||
### Unit Testing Integration
|
||||
Remote mode enables sophisticated unit testing through specialized protocol implementations:
|
||||
|
||||
**Protocol Composition**: Uses mixin pattern to combine testing features:
|
||||
- Main window simulation with configurable properties
|
||||
- Input event capture and replay
|
||||
- Rendering command recording and analysis
|
||||
- Frame-by-frame logging and verification
|
||||
- High-level testing commands (`LClick`, `KeyPress`, `MouseMove`)
|
||||
|
||||
**Frame-Based Testing**: Each `OnNextIdleFrame` represents one rendering frame with descriptive labels for test phases. The protocol logs all rendering commands, DOM changes, and element states per frame.
|
||||
|
||||
## Error Handling and Fallbacks
|
||||
|
||||
The initialization system includes comprehensive error handling and fallback mechanisms:
|
||||
|
||||
### Hardware/Software Fallbacks
|
||||
- **Direct2D 1.1 ? Direct2D 1.0**: Falls back if D3D11 device creation fails
|
||||
- **Hardware ? WARP**: Attempts hardware acceleration first, then software rendering
|
||||
- **Debug vs Release**: Debug builds include detailed error checking, release builds tolerate failures gracefully
|
||||
|
||||
### Unit Test Accommodations
|
||||
Special handling for testing scenarios:
|
||||
- `GACUI_UNITTEST_ONLY_SKIP_TYPE_AND_PLUGIN_LOAD_UNLOAD` skips heavy reflection loading
|
||||
- `GACUI_UNITTEST_ONLY_SKIP_THREAD_LOCAL_STORAGE_DISPOSE_STORAGES` avoids cleanup issues
|
||||
- Unit test mode affects plugin loading strategy and failure tolerance
|
||||
|
||||
### Memory Management
|
||||
Proper cleanup through systematic resource management:
|
||||
- Resource manager listener registration ensures cleanup when windows are destroyed
|
||||
- `FinalizeGlobalStorage()` called during shutdown for global variable cleanup
|
||||
- Thread-local storage proper disposal
|
||||
- Symmetric plugin loading and unloading
|
||||
|
||||
This comprehensive initialization system enables GacUI to provide consistent, high-performance cross-platform GUI capabilities while maintaining platform-specific optimizations and providing extensive testing and debugging support.
|
||||
111
.github/KnowledgeBase/KB_VlppOS_AdditionalStreams.md
vendored
Normal file
111
.github/KnowledgeBase/KB_VlppOS_AdditionalStreams.md
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
# Additional Streams
|
||||
|
||||
Specialized stream types for caching, recording, and broadcasting data operations.
|
||||
|
||||
## CacheStream
|
||||
|
||||
Use `CacheStream` for performance optimization with frequently accessed data.
|
||||
|
||||
`CacheStream` maintain a cache to reduce calls to the underlying stream.
|
||||
It increases the performance when there are too many data to read/write,
|
||||
or when the same part of the data needs to be modified repeatly.
|
||||
|
||||
### CacheStream Capabilities
|
||||
|
||||
`CacheStream` inherits the capabilities of its underlying stream:
|
||||
- Readable when the underlying stream is readable
|
||||
- Writable when the underlying stream is writable
|
||||
- Seekable when the underlying stream is seekable
|
||||
- Limited/finite when the underlying stream is limited/finite
|
||||
|
||||
### Use Cases
|
||||
|
||||
CacheStream is particularly useful for:
|
||||
- Random access patterns on large files
|
||||
- Repeated reading/writing of the same data sections
|
||||
- Reducing I/O operations on slow storage devices
|
||||
- Optimizing performance when working with network streams
|
||||
|
||||
## RecorderStream
|
||||
|
||||
Use `RecorderStream` for copying data from one stream to another during reading.
|
||||
|
||||
`RecorderStream` reads data from one readable stream while copying everything to another writable stream.
|
||||
|
||||
### RecorderStream Behavior
|
||||
|
||||
- It is a read-only stream that wraps another readable stream
|
||||
- Every read operation is simultaneously written to a target stream
|
||||
- Useful for creating backups or logs of data as it's being processed
|
||||
- The recorded data can be written to any writable stream (file, memory, etc.)
|
||||
|
||||
### Use Cases
|
||||
|
||||
RecorderStream is ideal for:
|
||||
- Creating audit trails of data processing
|
||||
- Simultaneous backup during data migration
|
||||
- Logging network communications
|
||||
- Splitting data streams for parallel processing
|
||||
|
||||
## BroadcastStream
|
||||
|
||||
Use `BroadcastStream` for writing the same data to multiple target streams.
|
||||
|
||||
`BroadcastStream` write the same data to multiple streams, which is managed by the `Targets()` method.
|
||||
|
||||
### Managing Broadcast Targets
|
||||
|
||||
Use `Targets()` method to manage BroadcastStream destinations.
|
||||
|
||||
The `Targets()` method returns a collection that allows you to:
|
||||
- Add new target streams for broadcasting
|
||||
- Remove existing target streams
|
||||
- Query the current list of active targets
|
||||
|
||||
### BroadcastStream Behavior
|
||||
|
||||
- It is a write-only stream that distributes writes to multiple targets
|
||||
- Each write operation is replicated to all registered target streams
|
||||
- If any target stream fails, the broadcast operation may fail
|
||||
- All target streams should be writable for proper operation
|
||||
|
||||
### Use Cases
|
||||
|
||||
BroadcastStream is useful for:
|
||||
- Creating multiple backup copies simultaneously
|
||||
- Writing to both file and network destinations
|
||||
- Distributing data to multiple processing pipelines
|
||||
- Creating redundant storage for critical data
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Stream Combination Patterns
|
||||
|
||||
These additional streams can be combined with basic streams and encoding streams to create sophisticated data processing pipelines:
|
||||
|
||||
1. **Cached File Processing**: `CacheStream` wrapping a `FileStream` for improved random access performance
|
||||
2. **Recorded Encoding**: `RecorderStream` capturing the output of an `EncoderStream` for debugging
|
||||
3. **Broadcast with Compression**: `BroadcastStream` sending data to both compressed and uncompressed destinations
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
- **CacheStream**: Trades memory usage for reduced I/O operations
|
||||
- **RecorderStream**: Doubles the write operations but provides valuable audit capabilities
|
||||
- **BroadcastStream**: Multiplies write operations by the number of targets
|
||||
|
||||
### Error Handling
|
||||
|
||||
Each specialized stream type has specific error scenarios:
|
||||
- **CacheStream**: May fail if the underlying stream becomes unavailable
|
||||
- **RecorderStream**: Can fail if either the source or target stream has issues
|
||||
- **BroadcastStream**: May partially succeed if only some target streams fail
|
||||
|
||||
### Memory Management
|
||||
|
||||
- **CacheStream**: Automatically manages cache size and eviction policies
|
||||
- **RecorderStream**: Doesn't store data itself, just passes it through
|
||||
- **BroadcastStream**: Minimal memory overhead as it doesn't buffer data
|
||||
|
||||
### Thread Safety
|
||||
|
||||
These stream types inherit the thread safety characteristics of their underlying streams. Additional synchronization may be needed when using them in multi-threaded scenarios, especially with `BroadcastStream` where multiple targets might be accessed concurrently.
|
||||
133
.github/KnowledgeBase/KB_VlppOS_EncodingDecoding.md
vendored
Normal file
133
.github/KnowledgeBase/KB_VlppOS_EncodingDecoding.md
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
# Encoding and Decoding
|
||||
|
||||
Text encoding conversion between different UTF formats with BOM support and binary data encoding.
|
||||
|
||||
## UTF Encoding with BOM Support
|
||||
|
||||
Use `BomEncoder` and `BomDecoder` for UTF encoding with BOM support.
|
||||
|
||||
`BomEncoder` and `BomDecoder` convert data between `wchar_t` and a specified UTF encoding with BOM added to the very beginning.
|
||||
|
||||
## UTF Encoding without BOM
|
||||
|
||||
Use `UtfGeneralEncoder<Native, Expect>` and `UtfGeneralDecoder<Native, Expect>` for UTF conversion without BOM.
|
||||
|
||||
`UtfGeneralEncoder<Native, Expect>` encode from `Expect` to `Native`, `UtfGeneralDecoder<Native, Expect>` decode from `Native` to `Expect`. They should be one of `wchar_t`, `char8_t`, `char16_t`, `char32_t` and `char16be_t`.
|
||||
|
||||
Unlike `BomEncoder` and `BomDecoder`, `UtfGeneralEncoder` and `UtfGeneralDecodes` is without BOM.
|
||||
|
||||
`char16be_t` means UTF-16 Big Endian, which is not a C++ native type, it can't be used with any string literal.
|
||||
|
||||
## Specific UTF Conversion Aliases
|
||||
|
||||
There are aliases for them to convert between `wchar_t` and any other UTF encoding:
|
||||
|
||||
- Use `Utf8Encoder` and `Utf8Decoder` for UTF-8 conversion
|
||||
- Use `Utf16Encoder` and `Utf16Decoder` for UTF-16 conversion
|
||||
- Use `Utf16BEEncoder` and `Utf16BEDecoder` for UTF-16 Big Endian conversion
|
||||
- Use `Utf32Encoder` and `Utf32Decoder` for UTF-32 conversion
|
||||
|
||||
## ASCII/MBCS Encoding
|
||||
|
||||
Use `MbcsEncoder` and `MbcsDecoder` for ASCII/MBCS conversion.
|
||||
|
||||
`MbcsEncoder` and `MbcsDecoder` convert data between `wchar_t` and `char`, which is ASCII.
|
||||
|
||||
`BomEncoder::Mbcs` also handles ASCII meanwhile there is no BOM for ASCII. A `BomEncoder(BomEncoder::Mbcs)` works like a `MbcsEncoder`.
|
||||
|
||||
The actual encoding of `char` depends on the user setting in the running OS.
|
||||
|
||||
## Automatic Encoding Detection
|
||||
|
||||
Use `TestEncoding` for automatic encoding detection.
|
||||
|
||||
There is a function `TestEncoding` to scan a binary data and guess the most possible UTF encoding.
|
||||
|
||||
## Base64 Encoding
|
||||
|
||||
Use `Utf8Base64Encoder` and `Utf8Base64Decoder` for Base64 encoding in UTF-8.
|
||||
|
||||
`Utf8Base64Encoder` and `Utf6Base64Decoder` convert between binary data to Base64 in UTF8 encoding.
|
||||
They can work with `UtfGeneralEncoder` and `UtfGeneralDecoder` to convert binary data to Base64 in a `WString`.
|
||||
|
||||
### Example: Converting Binary Data to Base64 WString
|
||||
|
||||
```cpp
|
||||
MemoryStream memoryStream;
|
||||
{
|
||||
UtfGeneralEncoder<wchar_t, char8_t> u8towEncoder;
|
||||
EncoderStream u8towStream(memoryStream, u8towEncoder);
|
||||
Utf8Base64Encoder base64Encoder;
|
||||
EncoderStream base64Stream(u8t0wStream, base64Encoder);
|
||||
base64Stream.Write(binary ...);
|
||||
}
|
||||
memoryStream.SeekFromBegin(0);
|
||||
{
|
||||
StreamReader reader(memoryStream);
|
||||
auto base64 = reader.ReadToEnd(reader);
|
||||
}
|
||||
```
|
||||
|
||||
### Example: Converting Base64 WString to Binary Data
|
||||
|
||||
```cpp
|
||||
MemoryStream memoryStreamn;
|
||||
{
|
||||
StreamWriter writer(memoryStream);
|
||||
writer.WriteString(base64);
|
||||
}
|
||||
memoryStream.SeekFromBegin(0);
|
||||
{
|
||||
UtfGeneralEncoder<wchar_t, char8_t> wtou8Decoder;
|
||||
DecoderStream wtou8Stream(memoryStream, wtou8Decoder);
|
||||
Utf8Base64Decoder base64Decoder;
|
||||
DecoderStream base64Stream(wtou8Stream, base64Decoder);
|
||||
base64Stream.Read(binary ...);
|
||||
}
|
||||
```
|
||||
|
||||
## Data Compression
|
||||
|
||||
Use `LzwEncoder` and `LzwDecoder` for data compression.
|
||||
|
||||
- `LzwEncoder` compress binary data.
|
||||
- `LzwDecoder` decompress binary data.
|
||||
|
||||
## Helper Functions
|
||||
|
||||
Use `CopyStream`, `CompressStream`, `DecompressStream` helper functions.
|
||||
|
||||
There are help functions `CopyStream`, `CompressStream` and `DecompressStream` to make the code simpler.
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Encoding Selection Guidelines
|
||||
|
||||
When choosing between different encoding methods:
|
||||
- Use BOM encoders when you need to ensure proper encoding detection by other applications
|
||||
- Use general UTF encoders for maximum compatibility and control over BOM presence
|
||||
- Use MBCS encoders only when working with legacy systems that require ASCII compatibility
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
Different encoding operations have varying performance characteristics:
|
||||
- ASCII/MBCS encoding is fastest but limited to basic character sets
|
||||
- UTF-8 encoding provides good balance between space efficiency and Unicode support
|
||||
- UTF-16 and UTF-32 provide different trade-offs between processing speed and memory usage
|
||||
|
||||
### Error Handling
|
||||
|
||||
Encoding operations may fail when:
|
||||
- Invalid byte sequences are encountered during decoding
|
||||
- Characters cannot be represented in the target encoding
|
||||
- BOM detection fails for corrupted files
|
||||
|
||||
Always handle potential encoding exceptions, especially when processing user-provided files.
|
||||
|
||||
### Pipeline Design
|
||||
|
||||
The encoder/decoder system is designed for pipeline composition. You can chain multiple encoding operations together to create complex data transformation workflows, such as:
|
||||
1. Base64 decode → UTF-8 decode → String processing
|
||||
2. String processing → UTF-8 encode → Compression → File output
|
||||
|
||||
This design provides flexibility for handling various data transformation scenarios efficiently.
|
||||
120
.github/KnowledgeBase/KB_VlppOS_FileSystemOperations.md
vendored
Normal file
120
.github/KnowledgeBase/KB_VlppOS_FileSystemOperations.md
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
# File System Operations
|
||||
|
||||
Cross-platform file and directory manipulation with path handling and content access.
|
||||
|
||||
## FilePath
|
||||
|
||||
`FilePath` is a string representation of file path.
|
||||
|
||||
- Use `GetName`, `GetFolder`, `GetFullPath` and `GetRelativePathFor` for path manipulation.
|
||||
- Use `IsFile`, `IsFolder` and `IsRoot` to tell the object represented by the path.
|
||||
|
||||
## File Class
|
||||
|
||||
When `FilePath::IsFile` returns true, `File` could be initialized with such path. It offers:
|
||||
|
||||
- Text reading by `ReadAllTextWithEncodingTesting`, `ReadAllTextByBom` and `ReadAllLinesByBom`.
|
||||
- Text writing by `WriteAllText`, `WriteAllLines`.
|
||||
- File operation by `Exists`, `Delete` and `Rename`.
|
||||
|
||||
### Text Reading Methods
|
||||
|
||||
Use `ReadAllTextWithEncodingTesting` for automatic encoding detection and text reading.
|
||||
Use `ReadAllTextByBom` for reading text files with BOM (Byte Order Mark) support.
|
||||
Use `ReadAllLinesByBom` for reading text files line by line with BOM support.
|
||||
|
||||
### Text Writing Methods
|
||||
|
||||
Use `WriteAllText` for writing complete text content to a file.
|
||||
Use `WriteAllLines` for writing multiple lines of text to a file.
|
||||
|
||||
### File Operations
|
||||
|
||||
Use `Exists` to check if a file exists at the specified path.
|
||||
Use `Delete` to remove an existing file.
|
||||
Use `Rename` to change the name or move a file to a different location.
|
||||
|
||||
## Folder Class
|
||||
|
||||
When `FilePath::IsFolder` or `FilePath::IsRoot` return true, `Folder` could be initialized with such path. It offers:
|
||||
|
||||
- Content enumerations by `GetFolders` and `GetFiles` to enumerate the content.
|
||||
- Folder operation by `Exists`, `Delete` and `Rename`.
|
||||
|
||||
### Content Enumeration
|
||||
|
||||
Use `GetFolders` to retrieve all subdirectories within the folder.
|
||||
Use `GetFiles` to retrieve all files within the folder.
|
||||
|
||||
### Folder Operations
|
||||
|
||||
Use `Exists` to check if a folder exists at the specified path.
|
||||
Use `Delete` to remove an existing folder and its contents.
|
||||
Use `Rename` to change the name or move a folder to a different location.
|
||||
|
||||
### Creating Folders
|
||||
|
||||
`Folder::Create` is special, it creates a new folder, which means you have to initialize `Folder` with an unexisting `FilePath` before doing that. In such case `FilePath::IsFolder` would return false before calling `Create`.
|
||||
|
||||
## Root Directory Handling
|
||||
|
||||
Initializing a `Folder` with a file path with `IsRoot` returning true, is just calling `Folder`'s default constructors.
|
||||
|
||||
- On Windows, the root contains all drives as folders, therefore root and drives cannot be removed or renamed. A drive's full path and name will be for example `C:`.
|
||||
- On Linux, the root means `/`.
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Implementation Injection
|
||||
|
||||
You can replace the default file system implementation with a custom one for testing and specialized scenarios:
|
||||
|
||||
- Use `InjectFileSystemImpl(impl)` to set a custom `IFileSystemImpl` implementation
|
||||
- Use `EjectFileSystemImpl(impl)` to remove a specific injected implementation
|
||||
- Use `EjectFileSystemImpl(nullptr)` to reset to the default OS-specific implementation by ejecting all injected implementations
|
||||
- Use `GetOSFileSystemImpl()` to get the OS-dependent default implementation (function not in header file, declare manually)
|
||||
|
||||
The injected implementation affects all `FilePath`, `File`, and `Folder` class operations that interact with the file system. This enables you to create in-memory file systems for testing, provide sandboxed file access, implement virtual file systems, or add custom file system behaviors like encryption or compression.
|
||||
|
||||
Implementation injection should typically be done during application startup before any multi-threaded usage begins, as it affects global state.
|
||||
|
||||
### File Stream Implementation
|
||||
|
||||
The file system implementation also provides file stream creation through `IFileSystemImpl::GetFileStreamImpl`. This method is used internally by `FileStream` constructors but can be useful when implementing custom file systems.
|
||||
|
||||
There is also a `CreateOSFileStreamImpl` function available that creates the OS-specific file stream implementation directly. Like `GetOSFileSystemImpl`, this function is not declared in header files and must be declared manually:
|
||||
|
||||
```cpp
|
||||
namespace vl
|
||||
{
|
||||
namespace stream
|
||||
{
|
||||
extern Ptr<IFileStreamImpl> CreateOSFileStreamImpl(const WString& fileName, FileStream::AccessRight accessRight);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Path Manipulation Best Practices
|
||||
|
||||
When working with file paths, always use `FilePath` for cross-platform compatibility. The class automatically handles path separators and normalization across different operating systems.
|
||||
|
||||
### Error Handling
|
||||
|
||||
File and folder operations may throw exceptions when encountering permission issues, missing files, or other I/O errors. Always consider wrapping file operations in appropriate exception handling.
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
For large files, consider using stream-based operations instead of reading entire files into memory with `ReadAllText` methods. The streaming approach provides better memory efficiency for large file processing.
|
||||
|
||||
### Encoding Detection
|
||||
|
||||
The `ReadAllTextWithEncodingTesting` method attempts to automatically detect the encoding of text files, making it suitable for processing files with unknown encodings. However, for better performance and when the encoding is known, use the specific BOM-based reading methods.
|
||||
|
||||
### Testing Applications
|
||||
|
||||
Implementation injection is particularly valuable for unit testing file system operations:
|
||||
|
||||
- Create isolated test environments without affecting the real file system
|
||||
- Simulate file system errors and edge cases
|
||||
- Test file operations with predictable directory structures
|
||||
- Mock file system behaviors for consistent testing across different environments
|
||||
95
.github/KnowledgeBase/KB_VlppOS_LocaleSupport.md
vendored
Normal file
95
.github/KnowledgeBase/KB_VlppOS_LocaleSupport.md
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
# Locale Support
|
||||
|
||||
Cross-platform localization and globalization with culture-aware string operations and formatting.
|
||||
|
||||
## Getting Locale Instances
|
||||
|
||||
`Locale` is a value type that provides access to culture-specific operations. You can obtain locale instances using these static methods:
|
||||
|
||||
- Use `Locale::Invariant()` or `INVLOC` macro for culture-invariant operations
|
||||
- Use `Locale::SystemDefault()` for OS code page interpretation
|
||||
- Use `Locale::UserDefault()` for user language and location settings
|
||||
- Use `Locale::Enumerate(locales)` to get all supported locales
|
||||
|
||||
The `Invariant()` locale does not change across OS settings, making it suitable for consistent operations that should not vary by user preferences. Use the `INVLOC` macro as a shorter version instead of writing `vl::Locale::Invariant()`.
|
||||
|
||||
The `SystemDefault()` locale returns the locale for OS code page interpretation and is not needed in most cases.
|
||||
|
||||
The `UserDefault()` locale returns the locale for user language and location settings, which is typically what you want for user-facing operations.
|
||||
|
||||
The `Enumerate(locales)` method populates the argument with all locales supported by the running OS.
|
||||
|
||||
## Date and Time Formatting
|
||||
|
||||
Once you have a `Locale` value, you can perform locale-aware date and time operations:
|
||||
|
||||
- Use `Get*Formats` methods for date-time format enumeration
|
||||
- Use `FormatDate` and `FormatTime` for locale-aware date/time formatting
|
||||
- Use `Get*Name` methods for localized week day and month names
|
||||
|
||||
When using `Get*Formats` methods, take the first filled format as the default one. The `FormatDate` and `FormatTime` methods convert `DateTime` objects to `WString` representations according to the locale's conventions.
|
||||
|
||||
The `Get*Name` methods provide locale-aware week day or month names, which are useful for creating localized calendar interfaces or date displays.
|
||||
|
||||
## Number and Currency Formatting
|
||||
|
||||
For numeric data formatting:
|
||||
|
||||
- Use `FormatNumber` and `FormatCurrency` for locale-aware number formatting
|
||||
|
||||
Both methods take a `WString` storing a number as input. You can use `itow` and `ftow` functions to convert integers and floating-point numbers to strings before formatting them according to locale conventions.
|
||||
|
||||
## Locale-Aware String Operations
|
||||
|
||||
The locale system provides comprehensive string manipulation functions with cultural awareness:
|
||||
|
||||
- Use `Compare`, `CompareOrdinal`, `CompareOrdinalIgnoreCase` for locale-aware string comparison
|
||||
- Use `FindFirst`, `FindLast` for normalized string searching
|
||||
- Use `StartsWith`, `EndsWith` for normalized string matching
|
||||
|
||||
The comparison functions (`Compare`, `CompareOrdinal`, and `CompareOrdinalIgnoreCase`) compare two strings using different approaches based on locale-specific rules.
|
||||
|
||||
The search functions (`FindFirst` and `FindLast`) find one string within another. Since strings are normalized before searching, these functions return a pair of numbers indicating the matched substring. The matched substring might not be identical to the substring being searched for, but they are equivalent under the given locale's normalization rules.
|
||||
|
||||
The `StartsWith` and `EndsWith` functions test if a substring appears at the expected location, using locale-aware normalization.
|
||||
|
||||
All these string manipulation functions internally rewrite the input strings using specified normalization rules before performing their operations, ensuring culturally appropriate behavior.
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Implementation Injection
|
||||
You can replace the default locale implementation with a custom one for testing and specialized scenarios:
|
||||
|
||||
- Use `InjectLocaleImpl(impl)` to set a custom `ILocaleImpl` implementation
|
||||
- Use `EjectLocaleImpl(impl)` to remove a specific injected implementation
|
||||
- Use `EjectLocaleImpl(nullptr)` to reset to the default OS-specific implementation by ejecting all injected implementations
|
||||
- Use `GetOSLocaleImpl()` to get the OS-dependent default implementation (function not in header file, declare manually)
|
||||
|
||||
The injected implementation affects all `Locale` class operations including formatting, string comparison, and cultural operations. This allows you to provide custom localization behavior, create mock locales for testing, or implement specialized cultural rules not supported by the default OS implementation.
|
||||
|
||||
Implementation injection should typically be done during application startup before any multi-threaded usage begins, as it affects global state.
|
||||
|
||||
### Platform-Independent Fallback Implementation
|
||||
|
||||
The `EnUsLocaleImpl` class provides a platform-independent implementation that only supports en-US locale operations. This implementation serves as a final fallback when no OS-specific locale support is available:
|
||||
|
||||
- `EnUsLocaleImpl` provides basic English (US) locale functionality
|
||||
- It implements all `ILocaleImpl` interface methods with en-US specific behavior
|
||||
- Use this implementation when you need consistent en-US behavior across all platforms (e.g. unit test)
|
||||
- This is the default implementation for non-Windows platform
|
||||
|
||||
### Character Normalization
|
||||
The locale system performs character normalization as part of its string operations. This means that characters that appear different but have the same semantic meaning (such as composed vs. decomposed Unicode characters) are treated as equivalent during comparisons and searches.
|
||||
|
||||
### Performance Considerations
|
||||
Locale-aware operations are generally more expensive than simple string operations because they involve character normalization and culture-specific rules. For performance-critical code that doesn't need localization, consider using the invariant locale or simple string operations.
|
||||
|
||||
### Threading Safety
|
||||
Locale objects are value types and can be safely used across multiple threads. However, be aware that system locale settings can change at runtime, so cache locale instances appropriately for your use case. The implementation injection functionality should be used with care in multi-threaded environments.
|
||||
|
||||
### Best Practices
|
||||
- Use `UserDefault()` for user-facing string operations
|
||||
- Use `Invariant()` for internal data processing and storage
|
||||
- Cache frequently used locale instances rather than creating them repeatedly
|
||||
- Be aware that locale-aware operations can be slower than simple string comparisons
|
||||
- When using injection, ensure proper cleanup between tests if different implementations are needed
|
||||
112
.github/KnowledgeBase/KB_VlppOS_MultiThreading.md
vendored
Normal file
112
.github/KnowledgeBase/KB_VlppOS_MultiThreading.md
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
# Multi-threading
|
||||
|
||||
Cross-platform threading primitives and synchronization mechanisms for concurrent programming.
|
||||
|
||||
## Thread Pool Operations
|
||||
|
||||
Use `ThreadPoolLite::Queue` and `ThreadPoolLite::QueueLambda` for thread pool execution.
|
||||
|
||||
Use static functions `ThreadPoolLite::Queue` or `ThreadPoolLite::QueueLambda` to run a function in another thread.
|
||||
|
||||
### ThreadPoolLite Benefits
|
||||
|
||||
The thread pool provides several advantages over manual thread creation:
|
||||
- Automatic thread lifecycle management
|
||||
- Resource pooling to avoid thread creation overhead
|
||||
- Better system resource utilization
|
||||
- Simplified concurrent programming model
|
||||
|
||||
### Usage Examples
|
||||
|
||||
```cpp
|
||||
// Queue a function pointer
|
||||
ThreadPoolLite::Queue(someFunction);
|
||||
|
||||
// Queue a lambda expression
|
||||
ThreadPoolLite::QueueLambda([]() {
|
||||
// Work to be done in background thread
|
||||
});
|
||||
```
|
||||
|
||||
## Thread Control Operations
|
||||
|
||||
### Thread Pausing
|
||||
|
||||
Use `Thread::Sleep` for thread pausing.
|
||||
|
||||
Use static function `Thread::Sleep` to pause the current thread for some milliseconds.
|
||||
|
||||
### Thread Identification
|
||||
|
||||
Use `Thread::GetCurrentThreadId` for thread identification.
|
||||
|
||||
Use static function `Thread::GetCurrentThreadId` to get an identifier for the OS native thread running the current function.
|
||||
|
||||
## Manual Thread Creation
|
||||
|
||||
Use `Thread::CreateAndStart` only when thread pool is insufficient.
|
||||
|
||||
`Thread::CreateAndStart` could be used to run a function in another thread while returning a `Thread*` to control it, but this is not recommended.
|
||||
Always use `ThreadPoolLite` if possible.
|
||||
|
||||
### When to Use Manual Threads
|
||||
|
||||
Manual thread creation should only be considered when:
|
||||
- You need fine-grained control over thread lifecycle
|
||||
- Thread-specific configurations are required
|
||||
- The thread pool doesn't meet specific timing requirements
|
||||
- Working with legacy code that requires direct thread handles
|
||||
|
||||
## Thread Pool with Synchronization
|
||||
|
||||
A `ThreadPoolLite` call with an `EventObject` is a better version of `Thread::Wait`.
|
||||
|
||||
This approach provides better resource management and avoids the complexities of manual thread synchronization.
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Threading Best Practices
|
||||
|
||||
1. **Prefer Thread Pool**: Use `ThreadPoolLite` for most concurrent operations
|
||||
2. **Avoid Thread Creation**: Manual thread creation adds overhead and complexity
|
||||
3. **Use Synchronization Primitives**: Combine threading with proper synchronization objects
|
||||
4. **Handle Exceptions**: Ensure proper exception handling in threaded code
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
- **Thread Pool**: Minimal overhead for task scheduling and execution
|
||||
- **Thread Creation**: Significant overhead for creating and destroying threads
|
||||
- **Context Switching**: Consider the cost of frequent context switches
|
||||
- **Resource Contention**: Be aware of shared resource access patterns
|
||||
|
||||
### Cross-Platform Considerations
|
||||
|
||||
The threading API provides a unified interface across platforms:
|
||||
- Windows: Uses Windows thread API internally
|
||||
- Linux: Uses pthread API internally
|
||||
- Behavior remains consistent across platforms
|
||||
- Thread IDs are platform-specific but the API is uniform
|
||||
|
||||
### Memory Management
|
||||
|
||||
- Thread pool automatically manages worker threads
|
||||
- Manual threads require explicit cleanup
|
||||
- Lambda captures should be carefully managed for thread safety
|
||||
- Avoid capturing references to stack variables in threaded lambdas
|
||||
|
||||
### Error Handling
|
||||
|
||||
Threading operations can fail due to:
|
||||
- System resource exhaustion
|
||||
- Permission restrictions
|
||||
- Platform-specific limitations
|
||||
|
||||
Always check return values and handle potential failures gracefully.
|
||||
|
||||
### Integration with Synchronization
|
||||
|
||||
Multi-threading works best when combined with appropriate synchronization primitives:
|
||||
- Use with `Mutex` for cross-process synchronization
|
||||
- Combine with `CriticalSection` for in-process protection
|
||||
- Integrate with `EventObject` for thread coordination
|
||||
- Apply `ConditionVariable` for complex waiting scenarios
|
||||
151
.github/KnowledgeBase/KB_VlppOS_StreamOperations.md
vendored
Normal file
151
.github/KnowledgeBase/KB_VlppOS_StreamOperations.md
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
# Stream Operations
|
||||
|
||||
Unified stream interface for file, memory, and data transformation operations with encoding support.
|
||||
|
||||
## IStream Interface
|
||||
|
||||
All stream implements `IStream` interface. Unfortunately there is another `IStream` in `Windows.h`, so it is not recommended to do `using namespace vl::stream;`, using `stream::` is recommended instead.
|
||||
|
||||
Streams are recommended to be used as value types, but they cannot be copied or moved.
|
||||
|
||||
### Stream Availability
|
||||
|
||||
A stream is available when `IsAvailable` returns true. All other methods can only be used in this case.
|
||||
Calling `Close` will release the resource behind the stream and make it unavailable.
|
||||
Usually we don't need to call `Close` explicitly, it will be called internally when the stream is destroyed.
|
||||
|
||||
### Stream Capabilities
|
||||
|
||||
Use `IsAvailable`, `CanRead`, `CanWrite`, `CanSeek`, `IsLimited` for capability checking.
|
||||
|
||||
#### Readable Streams
|
||||
|
||||
A stream is readable when `CanRead` returns true. `Read` and `Peek` can only be used in this case.
|
||||
|
||||
Here are all streams that guaranteed to be readable so no further checking is needed:
|
||||
- `FileStream` with `FileStream::ReadOnly` or `FileStream::ReadWrite` in the constructor.
|
||||
- `MemoryStream`
|
||||
- `MemoryWrapperStream`
|
||||
- `DecoderStream`
|
||||
- `RecorderStream`
|
||||
- The following streams are readable when their underlying streams are readable
|
||||
- `CacheStream`
|
||||
|
||||
#### Writable Streams
|
||||
|
||||
A stream is writable when `CanWrite` returns true. `Write` can only be used in this case.
|
||||
|
||||
Here are all streams that guaranteed to be writable so no further checking is needed:
|
||||
- `FileStream` with `FileStream::WriteOnly` or `FileStream::ReadWrite` in the constructor.
|
||||
- `MemoryStream`
|
||||
- `MemoryWrapperStream`
|
||||
- `EncoderStream`
|
||||
- `BroadcastStream`
|
||||
- The following streams are readable when their underlying streams are writable
|
||||
- `CacheStream`
|
||||
|
||||
#### Seekable Streams
|
||||
|
||||
A stream is random accessible when `CanSeek` returns true. `Seek`, `SeekFromBegin` can only be used in this case. `SeekFromEnd` can only be used when both `CanSeek` and `IsLimited` returns true.
|
||||
Use `Position` to know the current seeking position.
|
||||
`Read` and `Peek` will read the data at the seeking position.
|
||||
|
||||
Here are all streams that guaranteed to be seekable so no further checking is needed:
|
||||
- `FileStream`
|
||||
- `MemoryStream`
|
||||
- `MemoryWrapperStream`
|
||||
- The following streams are readable when their underlying streams are seekable
|
||||
- `CacheStream`
|
||||
|
||||
#### Limited/Finite Streams
|
||||
|
||||
A stream is finite when `IsLimited` returns true. A finite stream means there is limited data in the stream. An infinite stream means you can `Read` from the stream forever before it is broken or closed.
|
||||
The `Size` and `SeekFromEnd` method only make sense for a finite stream.
|
||||
|
||||
Here are all streams that guaranteed to be limited/finite so no further checking is needed:
|
||||
- `FileStream` with `FileStream::ReadOnly` in the constructor.
|
||||
- `MemoryWrapperStream`
|
||||
- The following streams are readable when their underlying streams are limited/finite
|
||||
- `DecoderStream`
|
||||
- `EncoderStream`
|
||||
- `CacheStream`
|
||||
- `RecorderStream`
|
||||
|
||||
Here are all streams that guaranteed to be infinite so no further checking is needed:
|
||||
- `FileStream` with `FileStream::WriteOnly` or `FileStream::ReadWrite` in the constructor.
|
||||
- `MemoryStream`
|
||||
- The following streams are readable when their underlying streams are limited/finite
|
||||
- `DecoderStream`
|
||||
- `EncoderStream`
|
||||
- `CacheStream`
|
||||
- `RecorderStream`
|
||||
|
||||
### Basic Stream Operations
|
||||
|
||||
Use `Read`, `Write`, `Peek`, `Seek`, `Position`, `Size` for stream operations.
|
||||
Use `Close` for resource cleanup (automatic on destruction).
|
||||
|
||||
## FileStream
|
||||
|
||||
Initialize `FileStream` with a file path (`WString` instead of `FilePath`) to open a file. One of `FileStream::ReadOnly`, `FileStream::WriteOnly` and `FileStream::ReadWrite` must be specified.
|
||||
|
||||
## MemoryStream
|
||||
|
||||
`MemoryStream` maintain a consecutive memory buffer to store data.
|
||||
Use `GetInternalBuffer` to get the pointer to the buffer.
|
||||
The pointer is only safe to use before `MemoryStream` is written to, because when the buffer is not long enough, a new one will be created and the old will will be deleted.
|
||||
The buffer will be deleted when `MemoryStream` is destroyed.
|
||||
|
||||
## MemoryWrapperStream
|
||||
|
||||
`MemoryWrapperStream` operates on a given memory buffer, `MemoryWrapperStream` will be delete the buffer.
|
||||
|
||||
## EncoderStream and DecoderStream
|
||||
|
||||
An `EncoderStream` transform the data using the given `IEncoder` and then write to a given writable stream. It is write only stream.
|
||||
|
||||
A `DecoderStream` read data from a given readable stream and then transform the data using the given `IDecoder`. It is a read only stream.
|
||||
|
||||
By stacking multiple encoders, decoders and stream together, we can create a pipeline of data processing.
|
||||
|
||||
### Example: Reading UTF-8 File
|
||||
|
||||
When we need to read a UTF-8 file into a `WString` we could use:
|
||||
```cpp
|
||||
FileStream fileStream(fullPath, FileStream::ReadOnly);
|
||||
Utf8Decoder decoder;
|
||||
DecoderStream decoderStream(fileStream, decoder);
|
||||
StreamReader reader(decoderStream);
|
||||
auto text = reader.ReadToEnd();
|
||||
```
|
||||
|
||||
### Example: Writing UTF-8 File with BOM
|
||||
|
||||
When we need to write a `WString` to a UTF-8 file with BOM enabled we could use:
|
||||
```cpp
|
||||
FileStream fileStream(fullPath, FileStream::WriteOnly);
|
||||
BomEncoder encoder(BomEncoder::Utf8);
|
||||
EncoderStream encoderStream(fileStream, encoder);
|
||||
StreamWriter writer(encoderStream);
|
||||
writer.WriteString(text);
|
||||
```
|
||||
|
||||
Or just use `File` to do the work which is much simpler.
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Stream Design Philosophy
|
||||
|
||||
The stream system in VlppOS follows a layered design pattern where basic streams provide fundamental I/O operations, and decorator streams add specific functionality like encoding/decoding, caching, or broadcasting.
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
When working with large amounts of data, prefer streaming operations over loading entire content into memory. Use `MemoryStream` for temporary buffers and `FileStream` for persistent storage. Or use `CacheStream` to improve `FileStream` performance when random access is not required.
|
||||
|
||||
### Error Handling
|
||||
|
||||
Stream operations may throw exceptions when encountering I/O errors, permission issues, or when operations are performed on streams that don't support them (e.g., writing to a read-only stream).
|
||||
|
||||
### Resource Management
|
||||
|
||||
Streams automatically manage their resources through RAII. The `Close()` method is called automatically in destructors, but can be called explicitly when immediate resource cleanup is needed.
|
||||
176
.github/KnowledgeBase/KB_VlppOS_SynchronizationPrimitives.md
vendored
Normal file
176
.github/KnowledgeBase/KB_VlppOS_SynchronizationPrimitives.md
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
# Synchronization Primitives
|
||||
|
||||
Non-waitable synchronization objects for protecting shared resources in multi-threaded environments.
|
||||
|
||||
## Lock Selection Guidelines
|
||||
|
||||
Only use `SpinLock` when the protected code exists super fast.
|
||||
Only use `CriticalSection` when the protected code costs time.
|
||||
|
||||
## SpinLock
|
||||
|
||||
Use `SpinLock` for protecting very fast code sections.
|
||||
|
||||
### SpinLock Operations
|
||||
|
||||
- `Enter` blocks the current thread, and when it returns, the current thread owns the spin lock.
|
||||
- Only one thread owns the spin lock.
|
||||
- `TryEnter` does not block the current thread, and there is a chance that the current thread will own the spin lock, indicated by the return value.
|
||||
- `Leave` releases the spin lock from the current thread.
|
||||
|
||||
### SpinLock Automation
|
||||
|
||||
Use `SPIN_LOCK`, `CS_LOCK`, `READER_LOCK`, `WRITER_LOCK` macros for exception-safe automatic locking.
|
||||
|
||||
When it is able to `Enter` and `Leave` in the same function, use `SPIN_LOCK` to simplify the code.
|
||||
It is also exception safety, so you don't need to worry about try-catch:
|
||||
|
||||
```cpp
|
||||
SpinLock lock;
|
||||
SPIN_LOCK(lock)
|
||||
{
|
||||
// fast code that owns the spin lock
|
||||
}
|
||||
```
|
||||
|
||||
## CriticalSection
|
||||
|
||||
Use `CriticalSection` for protecting time-consuming code sections.
|
||||
|
||||
### CriticalSection Operations
|
||||
|
||||
- `Enter` blocks the current thread, and when it returns, the current thread owns the critical section.
|
||||
- Only one thread owns the critical section.
|
||||
- `TryEnter` does not block the current thread, and there is a chance that the current thread will own the critical section, indicated by the return value.
|
||||
- `Leave` releases the critical section from the current thread.
|
||||
|
||||
### CriticalSection Automation
|
||||
|
||||
When it is able to `Enter` and `Leave` in the same function, use `CS_LOCK` to simplify the code.
|
||||
It is also exception safety, so you don't need to worry about try-catch:
|
||||
|
||||
```cpp
|
||||
CriticalSection cs;
|
||||
CS_LOCK(cs)
|
||||
{
|
||||
// slow code that owns the critical section
|
||||
}
|
||||
```
|
||||
|
||||
## ReaderWriterLock
|
||||
|
||||
Use `ReaderWriterLock` for multiple reader, single writer scenarios.
|
||||
|
||||
`ReaderWriterLock` is an advanced version of `CriticalSection`:
|
||||
- Multiple threads could own the same reader lock. When it happens, it prevents any thread from owning the writer lock.
|
||||
- Only one threads could own the writer lock. When it happens, it prevents any thread from owning the reader lock.
|
||||
|
||||
### ReaderWriterLock Operations
|
||||
|
||||
- Call `TryEnterReader`, `EnterReader` and `LeaveReader` to access the reader lock.
|
||||
- Call `TryEnterWriter`, `EnterWriter` and `LeaveWriter` to access the writer lock.
|
||||
|
||||
### ReaderWriterLock Automation
|
||||
|
||||
When it is able to `EnterReader` and `LeaveReader` in the same function, use `READER_LOCK` to simplify the code.
|
||||
When it is able to `EnterWriter` and `LeaveWriter` in the same function, use `WRITER_LOCK` to simplify the code.
|
||||
They are also exception safety, so you don't need to worry about try-catch:
|
||||
|
||||
```cpp
|
||||
ReaderWriterLock rwlock;
|
||||
READER_LOCK(rwlock)
|
||||
{
|
||||
// code that owns the reader lock
|
||||
}
|
||||
WRITER_LOCK(rwlock)
|
||||
{
|
||||
// code that owns the writer lock
|
||||
}
|
||||
```
|
||||
|
||||
## ConditionVariable
|
||||
|
||||
Use `ConditionVariable` with `SleepWith`, `SleepWithForTime` for conditional waiting.
|
||||
|
||||
A `ConditionVariable` works with a `CriticalSection` or a `ReaderWriterLock`.
|
||||
|
||||
### ConditionVariable with CriticalSection
|
||||
|
||||
- Call `SleepWith` to work with a `CriticalSection`. It works on both Windows and Linux.
|
||||
- Call `SleepWithForTime` to work with a `CriticalSection` with a timeout. It only works on Windows.
|
||||
|
||||
### ConditionVariable with ReaderWriterLock
|
||||
|
||||
- Call `SleepWithReader`, `SleepWithReaderForTime`, `SleepWriter` or `SleepWriterForTime` to work with a `ReaderWriterLock`. They only work on Windows.
|
||||
|
||||
### ConditionVariable Behavior
|
||||
|
||||
The `Sleep*` function temporarily releases the lock from the current thread, and block the current thread until it owns the lock again.
|
||||
|
||||
- Before calling the `Sleep*` function, the current thread must own the lock.
|
||||
- Calling the `Sleep*` function releases the lock from the current thread, and block the current thread.
|
||||
- The `Sleep*` function returns when `WakeOnePending` or `WaitAllPendings` is called.
|
||||
- The `Sleep*ForTime` function could also return when it reaches the timeout. But this will not always happen, because:
|
||||
- `WaitOnePending` only activates one thread pending on the condition variable.
|
||||
- `WaitAllPendings` activates all thread but they are also controlled by the lock.
|
||||
- When `Sleep*` returns, the current thread owns the lock.
|
||||
|
||||
### ConditionVariable Signaling
|
||||
|
||||
Use `WakeOnePending`, `WaitAllPendings` for condition variable signaling.
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Lock Performance Characteristics
|
||||
|
||||
Different synchronization primitives have varying performance profiles:
|
||||
|
||||
- **SpinLock**: Lowest overhead for very short critical sections, but wastes CPU cycles if held too long
|
||||
- **CriticalSection**: Higher overhead but efficient for longer critical sections
|
||||
- **ReaderWriterLock**: Most complex but allows concurrent reads
|
||||
|
||||
### Choosing the Right Primitive
|
||||
|
||||
Selection criteria for synchronization primitives:
|
||||
|
||||
1. **SpinLock**: Use when critical section execution time is less than a context switch (~50-100 microseconds)
|
||||
2. **CriticalSection**: Use for general-purpose mutual exclusion with longer critical sections
|
||||
3. **ReaderWriterLock**: Use when reads are frequent and writes are infrequent
|
||||
4. **ConditionVariable**: Use when threads need to wait for specific conditions
|
||||
|
||||
### Exception Safety
|
||||
|
||||
All automation macros (`SPIN_LOCK`, `CS_LOCK`, `READER_LOCK`, `WRITER_LOCK`) provide RAII-style exception safety:
|
||||
- Locks are automatically released when leaving the scope
|
||||
- Proper cleanup even if exceptions are thrown
|
||||
- No manual lock management required
|
||||
|
||||
### Platform Differences
|
||||
|
||||
Synchronization behavior varies across platforms:
|
||||
- Windows: Full support for all features including timeout operations
|
||||
- Linux: Limited timeout support for some operations
|
||||
- Cross-platform code should avoid Windows-only features when possible
|
||||
|
||||
### Deadlock Prevention
|
||||
|
||||
Best practices to avoid deadlocks:
|
||||
- Always acquire locks in the same order across all threads
|
||||
- Use timeout versions of lock operations when available
|
||||
- Keep critical sections as short as possible
|
||||
- Consider using higher-level synchronization abstractions
|
||||
|
||||
### Performance Optimization
|
||||
|
||||
Tips for optimal synchronization performance:
|
||||
- Minimize lock contention by keeping critical sections short
|
||||
- Use appropriate lock granularity (not too fine, not too coarse)
|
||||
- Consider lock-free alternatives for high-performance scenarios
|
||||
- Profile lock contention in performance-critical applications
|
||||
|
||||
### Integration with Threading
|
||||
|
||||
Synchronization primitives work best when combined with the multi-threading APIs:
|
||||
- Use with `ThreadPoolLite` for concurrent task execution
|
||||
- Combine with `ConditionVariable` for complex coordination scenarios
|
||||
- Apply appropriate synchronization for shared data structures
|
||||
152
.github/KnowledgeBase/KB_VlppOS_WaitableObjects.md
vendored
Normal file
152
.github/KnowledgeBase/KB_VlppOS_WaitableObjects.md
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
# Waitable Objects
|
||||
|
||||
Cross-process synchronization objects that support waiting operations with timeout capabilities.
|
||||
|
||||
## WaitableObject Interface
|
||||
|
||||
All locks mentioned here implements `WaitableObject`. A `WaitableObject` offer these functions to wait while blocking the current thread:
|
||||
|
||||
- `Wait`: wait until it signals. It could return false meaning the wait operation did not succeed and there is no guarantee about the status of the `WaitableObject`.
|
||||
- `WaitForTime`: wait until it signals with a timeout. It could return false like `Wait`, including reaching the timeout.
|
||||
- IMPORTANT: it is Windows only.
|
||||
|
||||
### Multiple Object Waiting
|
||||
|
||||
There are also static functions `WaitAll`, `WaitAllForTime`, `WaitAny`, `WaitAnyForTime` to wait for multiple `WaitableObject` at the same time.
|
||||
- IMPORTANT: they are Windows only.
|
||||
|
||||
## Platform Naming Convention
|
||||
|
||||
All following classes are named after Windows API. Their Linux version works exactly like Windows but with less features.
|
||||
|
||||
## Mutex
|
||||
|
||||
Use `Mutex` for cross-process mutual exclusion.
|
||||
|
||||
`Mutex` pretty much serves the same purpose like `SpinLock` and `CriticalSection`, but it could be shared across multiple processes, meanwhile costs more OS resource. Prefer `SpinLock` or `CriticalSection` one only operates in one process.
|
||||
|
||||
### Mutex Lifecycle
|
||||
|
||||
- The constructor does not actually create a mutex. You must call `Create` and `Open` later.
|
||||
- The `Create` method creates a mutex.
|
||||
- If the name is not empty, the mutex is associated to a name, which works across different processes.
|
||||
- No thread owns a mutex that is just created.
|
||||
- The `Open` method shares a created mutex with a name.
|
||||
|
||||
### Mutex Operations
|
||||
|
||||
Use `Create` and `Open` methods for establishing named synchronization objects.
|
||||
Use `Wait`, `WaitForTime` for blocking operations with optional timeout.
|
||||
Use `Release` for releasing mutex and semaphore ownership.
|
||||
|
||||
Calling `Wait` will block the current thread until it owns the mutex. Calling `Release` release the owned mutex to other threads.
|
||||
|
||||
## Semaphore
|
||||
|
||||
Use `Semaphore` for counting semaphore operations across processes.
|
||||
|
||||
### Semaphore Lifecycle
|
||||
|
||||
- The constructor does not actually create a semaphore. You must call `Create` and `Open` later.
|
||||
- The `Create` method creates a semaphore.
|
||||
- If the name is not empty, the semaphore is associated to a name, which works across different processes.
|
||||
- No thread owns a semaphore that is just created.
|
||||
- The `Open` method shares a created semaphore with a name.
|
||||
|
||||
### Semaphore Operations
|
||||
|
||||
Calling `Wait` will block the current thread until it owns the semaphore.
|
||||
- Calling `Release` release the semaphore, for once or multiple times.
|
||||
- Unlike `Mutex`, multiple threads could own the same semaphore, as long as enough `Release` is called. And a thread doesn't need to own a semaphore to release it.
|
||||
|
||||
## EventObject
|
||||
|
||||
Use `EventObject` for event signaling across processes.
|
||||
|
||||
### EventObject Lifecycle
|
||||
|
||||
- The constructor does not actually create an event object. You must call `CreateAutoUnsignal`, `CreateManualUnsignal` and `Open` later.
|
||||
- The `CreateAutoUnsignal` and `CreateManualUnsignal` method creates an event object.
|
||||
- An auto unsignal event object means, when it is owned by a thread, it automatically unsignaled. So only one thread will be unblocked. Otherwise multiple threads waiting for this event object will be unblocked at the same time.
|
||||
- If the name is not empty, the event object is associated to a name, which works across different processes.
|
||||
- The `Open` method shares a created event object with a name.
|
||||
|
||||
### EventObject Operations
|
||||
|
||||
Use `Signal`, `Unsignal` for event object state management.
|
||||
|
||||
- Calling `Wait` will block the current thread until it is signaled.
|
||||
- Calling `Signal` to signal an event object.
|
||||
- Calling `Unsignal` to unsignal an event object.
|
||||
|
||||
## Multiple Object Synchronization
|
||||
|
||||
Use `WaitAll`, `WaitAllForTime`, `WaitAny`, `WaitAnyForTime` for multiple object synchronization.
|
||||
|
||||
These static functions allow coordination with multiple waitable objects simultaneously:
|
||||
- `WaitAll`: Block until all specified objects are signaled
|
||||
- `WaitAny`: Block until any one of the specified objects is signaled
|
||||
- Timeout versions provide time-limited waiting capabilities
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Cross-Process Synchronization
|
||||
|
||||
Waitable objects provide the foundation for inter-process communication and synchronization:
|
||||
|
||||
- **Named Objects**: Objects with names can be shared across process boundaries
|
||||
- **Security**: Named objects inherit security attributes from the creating process
|
||||
- **Lifetime**: Objects persist as long as any process holds a reference
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
Waitable objects have different performance characteristics compared to in-process synchronization:
|
||||
|
||||
- **System Overhead**: Cross-process synchronization involves kernel mode switches
|
||||
- **Scalability**: Limited by system-wide synchronization object limits
|
||||
- **Network Transparency**: Not suitable for distributed synchronization
|
||||
|
||||
### Error Handling
|
||||
|
||||
Waitable object operations can fail due to various conditions:
|
||||
|
||||
- **Abandoned Objects**: When owning process terminates unexpectedly
|
||||
- **Timeout Conditions**: When wait operations exceed specified time limits
|
||||
- **System Limits**: When maximum number of handles is exceeded
|
||||
- **Permission Issues**: When processes lack appropriate access rights
|
||||
|
||||
### Best Practices
|
||||
|
||||
Guidelines for effective use of waitable objects:
|
||||
|
||||
1. **Prefer In-Process**: Use non-waitable primitives when cross-process sync isn't needed
|
||||
2. **Name Carefully**: Use descriptive, unique names for shared objects
|
||||
3. **Handle Timeouts**: Always handle timeout scenarios gracefully
|
||||
4. **Resource Management**: Properly close handles to avoid resource leaks
|
||||
5. **Security Aware**: Consider security implications of named objects
|
||||
|
||||
### Platform-Specific Features
|
||||
|
||||
Feature availability varies by platform:
|
||||
|
||||
- **Windows**: Full support for all waitable object features
|
||||
- **Linux**: Limited timeout support and fewer object types
|
||||
- **Portability**: Stick to basic features for cross-platform compatibility
|
||||
|
||||
### Integration Patterns
|
||||
|
||||
Common usage patterns with waitable objects:
|
||||
|
||||
- **Process Coordination**: Use `EventObject` for startup/shutdown signaling
|
||||
- **Resource Limiting**: Use `Semaphore` for controlling access to limited resources
|
||||
- **Mutual Exclusion**: Use `Mutex` for cross-process critical sections
|
||||
- **Complex Coordination**: Combine multiple objects with `WaitAny`/`WaitAll`
|
||||
|
||||
### Debugging and Monitoring
|
||||
|
||||
Tools and techniques for troubleshooting waitable objects:
|
||||
|
||||
- **Handle Tracking**: Monitor handle counts and leaks
|
||||
- **Deadlock Detection**: Watch for circular wait conditions
|
||||
- **Performance Profiling**: Measure synchronization overhead
|
||||
- **Event Tracing**: Use system tools to trace synchronization events
|
||||
140
.github/KnowledgeBase/KB_VlppReflection_ClassInterfaceRegistration.md
vendored
Normal file
140
.github/KnowledgeBase/KB_VlppReflection_ClassInterfaceRegistration.md
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
# Class and Interface Registration
|
||||
|
||||
## Comprehensive registration system for classes and interfaces with methods, properties, and events.
|
||||
|
||||
VlppReflection provides extensive macros for registering classes and interfaces, enabling complete runtime reflection support including methods, properties, events, constructors, and inheritance relationships. The system supports both classes and interfaces with shared registration patterns.
|
||||
|
||||
## Basic Registration Patterns
|
||||
|
||||
### Class Registration
|
||||
- Use `BEGIN_CLASS_MEMBER` and `END_CLASS_MEMBER` for class registration
|
||||
- Provides foundation for making class types accessible through reflection
|
||||
- Supports all class features including inheritance, methods, and properties
|
||||
|
||||
### Interface Registration
|
||||
- Use `BEGIN_INTERFACE_MEMBER` and `END_INTERFACE_MEMBER` for inheritable interfaces
|
||||
- Use `BEGIN_INTERFACE_MEMBER_NOPROXY` and `END_INTERFACE_MEMBER` for non-inheritable interfaces
|
||||
- Interfaces can be inherited in Workflow scripts when using the regular `BEGIN_INTERFACE_MEMBER`
|
||||
|
||||
## Registration Components
|
||||
|
||||
### Base Class Declaration
|
||||
- Use `CLASS_MEMBER_BASE` for reflectable base class declaration
|
||||
- Enables proper inheritance hierarchy in reflection
|
||||
- Supports multiple inheritance scenarios
|
||||
|
||||
### Field Registration
|
||||
- Use `CLASS_MEMBER_FIELD` for member field registration
|
||||
- Exposes public fields through the reflection system
|
||||
- Enables dynamic field access and manipulation
|
||||
|
||||
### Constructor Registration
|
||||
- Use `CLASS_MEMBER_CONSTRUCTOR` for constructor registration with `Ptr<Class>(types...)` or `Class*(types...)`
|
||||
- Use `CLASS_MEMBER_EXTERNALCTOR` for external function constructors
|
||||
- Constructor type determines whether instances are boxed in `Ptr<T>` or not
|
||||
|
||||
### Method Registration
|
||||
- Use `CLASS_MEMBER_METHOD` for method registration with parameter names
|
||||
- Use `CLASS_MEMBER_METHOD_OVERLOAD` for overloaded method registration
|
||||
- Use `CLASS_MEMBER_EXTERNALMETHOD` for external function methods
|
||||
- Use `CLASS_MEMBER_STATIC_METHOD` for static method registration
|
||||
|
||||
### Event Registration
|
||||
- Use `CLASS_MEMBER_EVENT` for event registration
|
||||
- Events are typically `Event<T>` type fields
|
||||
- Enables dynamic event subscription and notification
|
||||
|
||||
### Property Registration
|
||||
- Use `CLASS_MEMBER_PROPERTY_READONLY`, `CLASS_MEMBER_PROPERTY` for property registration
|
||||
- Use `CLASS_MEMBER_PROPERTY_READONLY_FAST`, `CLASS_MEMBER_PROPERTY_FAST` for standard getter/setter patterns
|
||||
- Use `CLASS_MEMBER_PROPERTY_EVENT_READONLY_FAST`, `CLASS_MEMBER_PROPERTY_EVENT_FAST` for properties with change events
|
||||
|
||||
## Parameter Name Specifications
|
||||
|
||||
### Function Arguments
|
||||
For constructors and methods, argument names are required in the declaration:
|
||||
- Use `NO_PARAMETER` for parameterless functions
|
||||
- Use `{ L"arg1" _ L"arg2" ... }` for parameter name lists
|
||||
- The `_` macro must be defined as `,` in the implementation file
|
||||
|
||||
## Registration Examples
|
||||
|
||||
### Basic Class Registration
|
||||
```cpp
|
||||
BEGIN_CLASS_MEMBER(MyClass)
|
||||
CLASS_MEMBER_FIELD(FirstField)
|
||||
CLASS_MEMBER_FIELD(SecondField)
|
||||
END_CLASS_MEMBER(MyClass)
|
||||
```
|
||||
|
||||
### Interface Registration
|
||||
```cpp
|
||||
BEGIN_INTERFACE_MEMBER(IMyInterface)
|
||||
CLASS_MEMBER_FIELD(FirstField)
|
||||
CLASS_MEMBER_FIELD(SecondField)
|
||||
END_INTERFACE_MEMBER(IMyInterface)
|
||||
```
|
||||
|
||||
### Complete Class with All Features
|
||||
```cpp
|
||||
#define _ ,
|
||||
|
||||
BEGIN_CLASS_MEMBER(MyClass)
|
||||
CLASS_MEMBER_BASE(BaseClass)
|
||||
CLASS_MEMBER_FIELD(fieldName)
|
||||
CLASS_MEMBER_CONSTRUCTOR(Ptr<MyClass>(vint), { L"value" })
|
||||
CLASS_MEMBER_METHOD(MethodName, { L"arg1" _ L"arg2" })
|
||||
CLASS_MEMBER_STATIC_METHOD(StaticMethod, NO_PARAMETER)
|
||||
CLASS_MEMBER_EVENT(SomeEvent)
|
||||
CLASS_MEMBER_PROPERTY_FAST(PropertyName)
|
||||
END_CLASS_MEMBER(MyClass)
|
||||
|
||||
#undef _
|
||||
```
|
||||
|
||||
## Interface Requirements
|
||||
|
||||
### Proxy Requirements
|
||||
Using `BEGIN_INTERFACE_MEMBER` requires a proxy to EXIST in the header file, which means the interface can be inherited in Workflow script.
|
||||
|
||||
Using `BEGIN_INTERFACE_MEMBER_NOPROXY` requires a proxy to NOT EXIST in the header file, which means the interface cannot be inherited in Workflow script.
|
||||
|
||||
### Constructor Limitations
|
||||
There is no constructor in an interface registration - only classes support constructor registration.
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Advanced Registration Features
|
||||
|
||||
#### Method Overloading
|
||||
For overloaded methods, use specific macros:
|
||||
- `CLASS_MEMBER_METHOD_OVERLOAD(name, parameter, function-type)`
|
||||
- `CLASS_MEMBER_METHOD_OVERLOAD_RENAME(new-name, name, parameter, function-type)`
|
||||
- Function type must be a pointer to member function
|
||||
|
||||
#### External Methods
|
||||
For methods implemented as external functions:
|
||||
- `CLASS_MEMBER_EXTERNALMETHOD(name, parameters, function-type, source)`
|
||||
- First parameter acts as `this` pointer
|
||||
- Should not appear in parameters or function-type
|
||||
|
||||
#### Property Shortcuts
|
||||
Fast property registration shortcuts:
|
||||
- `CLASS_MEMBER_PROPERTY_READONLY_FAST(X)` for `GetX()` getter and property `X`
|
||||
- `CLASS_MEMBER_PROPERTY_FAST(X)` for `GetX()` getter, `SetX()` setter, and property `X`
|
||||
- `CLASS_MEMBER_PROPERTY_EVENT_FAST(X)` includes `XChanged` event
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Complete Registration**: Register all public members that should be accessible
|
||||
2. **Consistent Naming**: Follow established naming conventions
|
||||
3. **Parameter Documentation**: Provide meaningful parameter names
|
||||
4. **Event Integration**: Use events to notify property changes
|
||||
5. **Type Safety**: Ensure all referenced types are properly registered
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
- Registration occurs at startup time with minimal runtime overhead
|
||||
- Dynamic method calls have slight performance cost compared to direct calls
|
||||
- Property access through reflection is slower than direct field access
|
||||
- Consider caching reflection results for frequently used operations
|
||||
62
.github/KnowledgeBase/KB_VlppReflection_CompilationLevels.md
vendored
Normal file
62
.github/KnowledgeBase/KB_VlppReflection_CompilationLevels.md
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
# Reflection Compilation Levels
|
||||
|
||||
## Three different compilation modes for reflection support with varying runtime capabilities.
|
||||
|
||||
The reflection system supports three compilation levels:
|
||||
- Full reflection: Complete metadata and runtime support for type registration and function calls
|
||||
- Metadata-only (`VCZH_DESCRIPTABLEOBJECT_WITH_METADATA`): Type metadata without runtime support
|
||||
- No reflection (`VCZH_DEBUG_NO_REFLECTION`): Reflection disabled entirely
|
||||
Always prefer code compatible with `VCZH_DEBUG_NO_REFLECTION` when possible.
|
||||
|
||||
## Compilation Levels
|
||||
|
||||
### Full Reflection (Default)
|
||||
When no special macros are defined, VlppReflection compiles with full reflection support:
|
||||
- Complete metadata and runtime support for type registration and function calls
|
||||
- You can register your own classes, get metadata from registered types and objects, and call reflectable functions at runtime
|
||||
- Executing a Workflow script runs in this level
|
||||
- Use `vl::reflection::description::GetTypeDescriptor<T>` to get the metadata of a type
|
||||
|
||||
### Metadata-Only Mode
|
||||
Defined by `VCZH_DESCRIPTABLEOBJECT_WITH_METADATA`:
|
||||
- Metadata of types are loaded from external sources
|
||||
- You can get metadata from types, but lose all runtime support
|
||||
- Running a Workflow or GacUI XML compiler operates in this level
|
||||
- Type information is available but dynamic invocation is not supported
|
||||
|
||||
### No Reflection Mode
|
||||
Defined by `VCZH_DEBUG_NO_REFLECTION`:
|
||||
- Reflection is completely disabled
|
||||
- Workflow or GacUI XML Compiler generated code should be able to run in this level
|
||||
- Minimal runtime overhead
|
||||
- No metadata or dynamic capabilities available
|
||||
|
||||
## Best Practices
|
||||
|
||||
Always prefer code that is compatible with `VCZH_DEBUG_NO_REFLECTION` when possible. This ensures:
|
||||
- Maximum performance with minimal overhead
|
||||
- Broader compatibility across different compilation scenarios
|
||||
- Cleaner separation between reflection-dependent and reflection-independent code
|
||||
|
||||
When reflection is enabled (when `VCZH_DEBUG_NO_REFLECTION` is not defined), you can access type metadata and perform dynamic operations.
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Conditional Compilation Patterns
|
||||
|
||||
Use conditional compilation to handle different reflection levels:
|
||||
|
||||
```cpp
|
||||
#ifndef VCZH_DEBUG_NO_REFLECTION
|
||||
// Code that requires reflection
|
||||
auto typeDescriptor = vl::reflection::description::GetTypeDescriptor<MyClass>();
|
||||
#endif
|
||||
```
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
- Full reflection: Highest capability, highest overhead
|
||||
- Metadata-only: Medium capability, medium overhead
|
||||
- No reflection: No reflection capability, minimal overhead
|
||||
|
||||
Choose the appropriate level based on your application's requirements for dynamic behavior versus performance.
|
||||
106
.github/KnowledgeBase/KB_VlppReflection_EnumRegistration.md
vendored
Normal file
106
.github/KnowledgeBase/KB_VlppReflection_EnumRegistration.md
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
# Enum Registration
|
||||
|
||||
## Registration patterns for enumeration types with support for simple lists and combinable flags.
|
||||
|
||||
VlppReflection provides specific macros for registering enumeration types, supporting both simple enumeration lists and combinable flag enumerations. The registration system accommodates different enum styles including C++ enum classes and traditional enums, as well as enums defined within other types.
|
||||
|
||||
## Basic Enum Registration
|
||||
|
||||
### Simple Enumeration Lists
|
||||
- Use `BEGIN_ENUM_ITEM` and `END_ENUM_ITEM` for simple enumeration lists
|
||||
- These enums represent discrete values that are not intended to be combined
|
||||
- Each enum value represents a single, distinct option
|
||||
|
||||
```cpp
|
||||
BEGIN_ENUM_ITEM(MyEnum)
|
||||
ENUM_CLASS_ITEM(FirstItem)
|
||||
ENUM_CLASS_ITEM(SecondItem)
|
||||
END_ENUM_ITEM(MyEnum)
|
||||
```
|
||||
|
||||
### Combinable Flag Enumerations
|
||||
- Use `BEGIN_ENUM_ITEM_MERGABLE` and `END_ENUM_ITEM` for combinable flag enumerations
|
||||
- These enums work like mixable flags, which are usually combined using the `|` operator
|
||||
- Support bitwise operations for creating composite values
|
||||
|
||||
```cpp
|
||||
BEGIN_ENUM_ITEM_MERGABLE(MyEnum)
|
||||
ENUM_CLASS_ITEM(FirstItem)
|
||||
ENUM_CLASS_ITEM(SecondItem)
|
||||
END_ENUM_ITEM(MyEnum)
|
||||
```
|
||||
|
||||
## Enum Member Registration
|
||||
|
||||
### Enum Class Members
|
||||
- Use `ENUM_CLASS_ITEM` for enum class members
|
||||
- Applied to C++11 scoped enumerations (enum class)
|
||||
- Provides type safety and scope isolation
|
||||
|
||||
### Traditional Enum Members
|
||||
- Use `ENUM_ITEM` for enum members
|
||||
- Applied to traditional C-style enums
|
||||
- Members are accessible in the surrounding scope
|
||||
|
||||
### Namespace-Scoped Enums
|
||||
- Use `ENUM_ITEM_NAMESPACE` and `ENUM_NAMESPACE_ITEM` for enums (not enum classes) defined inside other types
|
||||
- `ENUM_ITEM_NAMESPACE` declares the type name
|
||||
- `ENUM_NAMESPACE_ITEM` lists each member
|
||||
- Handles enums that are nested within classes or namespaces
|
||||
|
||||
## Usage Patterns
|
||||
|
||||
For any `enum` that works like a list of names:
|
||||
```cpp
|
||||
BEGIN_ENUM_ITEM(MyEnum)
|
||||
ENUM_CLASS_ITEM(FirstItem)
|
||||
ENUM_CLASS_ITEM(SecondItem)
|
||||
END_ENUM_ITEM(MyEnum)
|
||||
```
|
||||
|
||||
For any `enum` that works like mixable flags:
|
||||
```cpp
|
||||
BEGIN_ENUM_ITEM_MERGABLE(MyEnum)
|
||||
ENUM_CLASS_ITEM(FirstItem)
|
||||
ENUM_CLASS_ITEM(SecondItem)
|
||||
END_ENUM_ITEM(MyEnum)
|
||||
```
|
||||
|
||||
For items in an `enum class`, use `ENUM_CLASS_ITEM` to list each member.
|
||||
For items in an `enum`, use `ENUM_ITEM` to list each member.
|
||||
|
||||
If the `enum` (not `enum class`) is defined inside other type, use `ENUM_ITEM_NAMESPACE` to declare the type name, followed with `ENUM_NAMESPACE_ITEM` to list each member.
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Choose Appropriate Type**: Use `BEGIN_ENUM_ITEM_MERGABLE` only for enums that are truly intended to be combined with bitwise operations
|
||||
2. **Consistent Naming**: Follow consistent naming conventions for enum values
|
||||
3. **Complete Registration**: Register all enum values that should be accessible through reflection
|
||||
|
||||
### Advanced Scenarios
|
||||
|
||||
#### Nested Enum Registration
|
||||
When dealing with enums defined within classes:
|
||||
```cpp
|
||||
// For enum defined inside a class
|
||||
BEGIN_ENUM_ITEM(OuterClass::InnerEnum)
|
||||
ENUM_ITEM_NAMESPACE(OuterClass)
|
||||
ENUM_NAMESPACE_ITEM(Value1)
|
||||
ENUM_NAMESPACE_ITEM(Value2)
|
||||
END_ENUM_ITEM(OuterClass::InnerEnum)
|
||||
```
|
||||
|
||||
#### Flag Combination Support
|
||||
For mergable enums, the reflection system automatically supports:
|
||||
- Bitwise OR operations for combining flags
|
||||
- Bitwise AND operations for testing flags
|
||||
- Conversion to/from underlying integer types
|
||||
- String representation of combined values
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
- Enum registration has minimal runtime overhead
|
||||
- Reflection of enum values is typically fast
|
||||
- Flag combination operations maintain C++ performance characteristics
|
||||
138
.github/KnowledgeBase/KB_VlppReflection_InterfaceProxy.md
vendored
Normal file
138
.github/KnowledgeBase/KB_VlppReflection_InterfaceProxy.md
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
# Interface Proxy Implementation
|
||||
|
||||
## Proxy generation for interfaces to enable inheritance in Workflow scripts.
|
||||
|
||||
VlppReflection provides interface proxy macros that generate proxy implementations for interfaces, enabling them to be inherited and implemented in Workflow scripts. The proxy system handles the bridge between C++ interfaces and dynamic Workflow script implementations.
|
||||
|
||||
## Proxy Declaration Patterns
|
||||
|
||||
### Interfaces Without Base Interfaces
|
||||
- Use `BEGIN_INTERFACE_PROXY_NOPARENT_RAWPTR` for interfaces without base interfaces using raw pointers
|
||||
- Use `BEGIN_INTERFACE_PROXY_NOPARENT_SHAREDPTR` for interfaces without base interfaces using `Ptr<T>`
|
||||
- Choose based on the memory management strategy for interface implementations
|
||||
|
||||
### Interfaces With Base Interfaces
|
||||
- Use `BEGIN_INTERFACE_PROXY_RAWPTR` for interfaces with base interfaces using raw pointers
|
||||
- Use `BEGIN_INTERFACE_PROXY_SHAREDPTR` for interfaces with base interfaces using `Ptr<T>`
|
||||
- All reflectable base interfaces must be listed (`IDescriptable` doesn't count)
|
||||
|
||||
### Proxy Completion
|
||||
- Use `END_INTERFACE_PROXY` to complete proxy definition
|
||||
- Must be used regardless of which BEGIN macro was used
|
||||
|
||||
## Method Implementation Patterns
|
||||
|
||||
### Void Methods Without Parameters
|
||||
- Use `INVOKE_INTERFACE_PROXY_NOPARAMS` for void methods without parameters
|
||||
- Handles method calls that don't return values and don't take arguments
|
||||
|
||||
### Return Value Methods Without Parameters
|
||||
- Use `INVOKEGET_INTERFACE_PROXY_NOPARAMS` for return value methods without parameters
|
||||
- Automatically handles return value retrieval and type conversion
|
||||
|
||||
### Void Methods With Parameters
|
||||
- Use `INVOKE_INTERFACE_PROXY` for void methods with parameters
|
||||
- Pass all method arguments to the macro
|
||||
|
||||
### Return Value Methods With Parameters
|
||||
- Use `INVOKEGET_INTERFACE_PROXY` for return value methods with parameters
|
||||
- Automatically handles both parameter passing and return value retrieval
|
||||
|
||||
## Implementation Structure
|
||||
|
||||
### Proxy Pattern
|
||||
An interface proxy begins with the appropriate BEGIN macro and ends with `END_INTERFACE_PROXY(name)`:
|
||||
|
||||
```cpp
|
||||
BEGIN_INTERFACE_PROXY_NOPARENT_SHAREDPTR(IMyInterface)
|
||||
void VoidMethodNoParams() override
|
||||
{
|
||||
INVOKE_INTERFACE_PROXY_NOPARAMS(VoidMethodNoParams);
|
||||
}
|
||||
|
||||
int GetValueNoParams() override
|
||||
{
|
||||
INVOKEGET_INTERFACE_PROXY_NOPARAMS(GetValueNoParams);
|
||||
}
|
||||
|
||||
void VoidMethodWithParams(int a, const WString& b) override
|
||||
{
|
||||
INVOKE_INTERFACE_PROXY(VoidMethodWithParams, a, b);
|
||||
}
|
||||
|
||||
int GetValueWithParams(int a, const WString& b) override
|
||||
{
|
||||
INVOKEGET_INTERFACE_PROXY(GetValueWithParams, a, b);
|
||||
}
|
||||
END_INTERFACE_PROXY(IMyInterface)
|
||||
```
|
||||
|
||||
### Method Implementation Rules
|
||||
Inside the proxy, there are functions that implement the interface. In each function implementation there will be only one line of code from the appropriate INVOKE macro.
|
||||
|
||||
The `return` keyword is not necessary as `INVOKEGET_INTERFACE_PROXY_NOPARAMS` and `INVOKEGET_INTERFACE_PROXY` already handle return values automatically.
|
||||
|
||||
## Proxy Categories
|
||||
|
||||
### Raw or Shared Pointer Proxies
|
||||
This is decided by the natural usage of the interface. If an instance of the interface is usually stored in `Ptr<T>`, use the shared pointer version.
|
||||
|
||||
## Interface Inheritance Hierarchy
|
||||
|
||||
### Base Interface Considerations
|
||||
When registering interfaces with inheritance:
|
||||
1. Register base interfaces first
|
||||
2. List all reflectable base interfaces in the proxy declaration
|
||||
3. `IDescriptable` is handled automatically and should not be listed
|
||||
4. Ensure proper virtual inheritance in C++ interface definitions
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Advanced Proxy Scenarios
|
||||
|
||||
#### Multiple Inheritance
|
||||
For interfaces that inherit from multiple base interfaces:
|
||||
```cpp
|
||||
BEGIN_INTERFACE_PROXY_SHAREDPTR(IDerivedInterface, IBaseInterface1, IBaseInterface2)
|
||||
// Implement all methods from all interfaces
|
||||
END_INTERFACE_PROXY(IDerivedInterface)
|
||||
```
|
||||
|
||||
#### Complex Parameter Types
|
||||
Proxy methods can handle complex parameter types:
|
||||
- Custom struct types (must be registered)
|
||||
- Collection types
|
||||
- Other interface types
|
||||
- Enum types
|
||||
- Nullable types
|
||||
|
||||
#### Error Handling
|
||||
Proxy implementations automatically handle:
|
||||
- Type conversion errors
|
||||
- Method invocation failures
|
||||
- Parameter validation
|
||||
- Return value conversion
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
1. **Proxy Overhead**: Proxy calls have additional overhead compared to direct calls
|
||||
2. **Type Conversion**: Parameter and return value conversion can impact performance
|
||||
3. **Caching**: Consider caching proxy instances for frequently used interfaces
|
||||
4. **Memory Management**: Choose appropriate pointer type based on usage patterns
|
||||
|
||||
### Integration with Workflow Scripts
|
||||
|
||||
Interface proxies enable Workflow scripts to:
|
||||
- Implement C++ interfaces directly
|
||||
- Override virtual methods
|
||||
- Participate in callback scenarios
|
||||
- Integrate with event systems
|
||||
- Provide custom business logic implementations
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Choose Appropriate Pointer Type**: Use shared pointers for reference-counted scenarios
|
||||
2. **Complete Implementation**: Implement all interface methods in the proxy
|
||||
3. **Error Handling**: Consider error scenarios in proxy implementations
|
||||
4. **Documentation**: Document the purpose and usage of each proxy
|
||||
5. **Testing**: Thoroughly test proxy implementations with Workflow scripts
|
||||
98
.github/KnowledgeBase/KB_VlppReflection_StructRegistration.md
vendored
Normal file
98
.github/KnowledgeBase/KB_VlppReflection_StructRegistration.md
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
# Struct Registration
|
||||
|
||||
## Registration patterns for structure types with field access capabilities.
|
||||
|
||||
VlppReflection provides specific macros for registering structure types, enabling runtime access to struct fields through the reflection system. This allows structures to be used in dynamic programming scenarios while maintaining their value-type semantics.
|
||||
|
||||
## Basic Struct Registration
|
||||
|
||||
### Registration Pattern
|
||||
- Use `BEGIN_STRUCT_MEMBER` and `END_STRUCT_MEMBER` for struct registration
|
||||
- Provides the foundation for making struct types accessible through reflection
|
||||
- Enables dynamic field access and manipulation
|
||||
|
||||
```cpp
|
||||
BEGIN_STRUCT_MEMBER(MyStruct)
|
||||
STRUCT_MEMBER(FirstField)
|
||||
STRUCT_MEMBER(SecondField)
|
||||
END_STRUCT_MEMBER(MyStruct)
|
||||
```
|
||||
|
||||
### Field Registration
|
||||
- Use `STRUCT_MEMBER` to register each accessible field
|
||||
- Each field registered becomes accessible through the reflection system
|
||||
- Supports both reading and writing field values dynamically
|
||||
|
||||
## Registration Requirements
|
||||
|
||||
### Struct Definition
|
||||
The struct must be properly defined and accessible:
|
||||
- Must be a valid C++ struct type
|
||||
- Fields to be registered must be public members
|
||||
- Should follow value-type semantics
|
||||
|
||||
### Field Accessibility
|
||||
- Only register fields that should be accessible through reflection
|
||||
- Private and protected fields cannot be registered directly
|
||||
- Consider data encapsulation principles when choosing which fields to expose
|
||||
|
||||
## Usage Example
|
||||
|
||||
Register a struct like this:
|
||||
```cpp
|
||||
BEGIN_STRUCT_MEMBER(MyStruct)
|
||||
STRUCT_MEMBER(FirstField)
|
||||
STRUCT_MEMBER(SecondField)
|
||||
END_STRUCT_MEMBER(MyStruct)
|
||||
```
|
||||
|
||||
This registration pattern enables:
|
||||
- Dynamic field access by name
|
||||
- Type-safe field value retrieval and assignment
|
||||
- Integration with the broader reflection system
|
||||
- Use in Workflow scripts and other dynamic contexts
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Advanced Registration Scenarios
|
||||
|
||||
#### Complex Field Types
|
||||
Struct fields can be of various types:
|
||||
- Primitive types (int, double, bool, etc.)
|
||||
- String types (WString, etc.)
|
||||
- Other registered structs
|
||||
- Enum types
|
||||
|
||||
#### Nested Structs
|
||||
When registering structs that contain other struct types:
|
||||
1. Register the nested struct types first
|
||||
2. Then register the containing struct
|
||||
3. The reflection system will automatically handle the relationships
|
||||
|
||||
#### Read-Only Fields
|
||||
While `STRUCT_MEMBER` typically requires both reading and writing:
|
||||
- Some fields may be conceptually read-only
|
||||
- Consider the design implications of exposing mutable access
|
||||
- Document field mutability expectations clearly
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Selective Registration**: Only register fields that need dynamic access
|
||||
2. **Type Consistency**: Ensure all field types are also properly registered
|
||||
3. **Documentation**: Document the purpose and constraints of registered fields
|
||||
4. **Testing**: Verify registration works correctly with dynamic access patterns
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
- Struct registration has minimal overhead at registration time
|
||||
- Dynamic field access is slightly slower than direct access
|
||||
- Consider performance implications for frequently accessed fields
|
||||
- Batch operations when possible for better performance
|
||||
|
||||
### Integration with Other Systems
|
||||
|
||||
Registered structs can be used with:
|
||||
- Workflow script language
|
||||
- GacUI data binding
|
||||
- Serialization systems
|
||||
- Dynamic configuration systems
|
||||
72
.github/KnowledgeBase/KB_VlppReflection_TypeMetadata.md
vendored
Normal file
72
.github/KnowledgeBase/KB_VlppReflection_TypeMetadata.md
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
# Type Metadata Access
|
||||
|
||||
## Runtime type information retrieval and manipulation through the reflection system.
|
||||
|
||||
When reflection is enabled (when `VCZH_DEBUG_NO_REFLECTION` is not defined), you can access type metadata and perform dynamic operations. The reflection system provides comprehensive runtime type information that can be used for dynamic programming scenarios.
|
||||
|
||||
## Core APIs
|
||||
|
||||
### GetTypeDescriptor
|
||||
- Use `vl::reflection::description::GetTypeDescriptor<T>` for type metadata access when reflection is enabled
|
||||
- Returns a pointer to `ITypeDescriptor` containing comprehensive type information
|
||||
- Only available when reflection is compiled in (not in `VCZH_DEBUG_NO_REFLECTION` mode)
|
||||
|
||||
### Value Boxing
|
||||
- Use `vl::reflection::description::Value` for boxing any value type similar to C# object or std::any
|
||||
- Provides type-safe storage and retrieval of arbitrary values
|
||||
- Supports conversion between different compatible types
|
||||
- Works as a universal container for any reflectable type
|
||||
|
||||
### Reflectable Base Classes
|
||||
- Use `Description<T>` base class for making classes reflectable
|
||||
- Use `AggregatableDescription<T>` for classes that can be inherited in Workflow scripts
|
||||
- Use `IDescriptable` interface for reflectable interfaces without other base interfaces
|
||||
|
||||
## Type Hierarchy Requirements
|
||||
|
||||
A reflectable class must inherit from `public vl::reflection::Description<the class itself>`.
|
||||
Use `AggregatableDescription` to allow a class being inherited in a Workflow script class.
|
||||
Sub types of reflectable classes or interfaces do not automatically become reflectable, they must use `Description<T>` or `AggregatableDescription<T>`.
|
||||
|
||||
A reflectable interface must inherit from `public vl::reflection::Description<the class itself>`.
|
||||
If such interface does not implement any other interface, it must inherit from `public vl::reflection::IDescriptable`.
|
||||
|
||||
## Usage Patterns
|
||||
|
||||
When accessing reflectable members or functions, `vl::reflection::description::Value` is particularly helpful.
|
||||
It should be used as a value type and provides similar functionality to `object` in C# or Java, or `std::any` in C++.
|
||||
|
||||
When using `Value` to represent "anything", it does not require reflection to be enabled.
|
||||
|
||||
A type is reflectable only when it is registered through the reflection system's registration mechanisms.
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Conditional Usage
|
||||
|
||||
Always check if reflection is available before using reflection APIs:
|
||||
|
||||
```cpp
|
||||
#ifndef VCZH_DEBUG_NO_REFLECTION
|
||||
auto typeDescriptor = vl::reflection::description::GetTypeDescriptor<MyClass>();
|
||||
if (typeDescriptor)
|
||||
{
|
||||
// Use type descriptor for metadata access
|
||||
}
|
||||
#endif
|
||||
```
|
||||
|
||||
### Value Type Operations
|
||||
|
||||
The `Value` class supports:
|
||||
- Automatic boxing and unboxing of any reflectable type
|
||||
- Type checking and safe casting operations
|
||||
- Integration with the broader reflection system
|
||||
- Serialization and deserialization capabilities
|
||||
|
||||
### Type Safety
|
||||
|
||||
The reflection system maintains C++ type safety while providing dynamic capabilities:
|
||||
- Compile-time type checking where possible
|
||||
- Runtime type verification for dynamic operations
|
||||
- Exception-based error handling for type mismatches
|
||||
152
.github/KnowledgeBase/KB_VlppReflection_TypeRegistrationStructure.md
vendored
Normal file
152
.github/KnowledgeBase/KB_VlppReflection_TypeRegistrationStructure.md
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
# Type Registration Structure
|
||||
|
||||
## Organized approach for registering types with proper file organization and macro usage.
|
||||
|
||||
All type registration must occur in `vl::reflection::description` namespace with specific file organization. The reflection system requires a structured approach to type registration with clear separation between declarations and implementations, following established patterns from existing source code examples.
|
||||
|
||||
## File Organization Requirements
|
||||
|
||||
### Header File (.h) Structure
|
||||
- Type lists and interface proxies in `.h` files
|
||||
- Contains type declarations and interface proxy definitions
|
||||
- Must include all necessary forward declarations
|
||||
- Should follow the established macro patterns
|
||||
|
||||
### Implementation File (.cpp) Structure
|
||||
- Type metadata registration in `.cpp` files
|
||||
- Contains the actual registration logic and metadata
|
||||
- Implements the type loading mechanisms
|
||||
- Follows specific namespace and macro conventions
|
||||
|
||||
### Dedicated Registration Files
|
||||
- Registration code must be in dedicated files
|
||||
- Do not mix registration code with regular implementation
|
||||
- Follow established patterns from existing source code examples
|
||||
- Maintain clear separation of concerns
|
||||
|
||||
## Registration Structure Pattern
|
||||
|
||||
### Header File Example (.h)
|
||||
```cpp
|
||||
namespace vl::reflection::description
|
||||
{
|
||||
#ifndef VCZH_DEBUG_NO_REFLECTION
|
||||
|
||||
#define MY_TYPES(F)\
|
||||
F(::my::namespaces::First)\
|
||||
F(::my::namespaces::ISecond)\
|
||||
|
||||
MY_TYPES(DECL_TYPE_INFO)
|
||||
|
||||
#ifdef VCZH_DESCRIPTABLEOBJECT_WITH_METADATA
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4250)
|
||||
|
||||
BEGIN_INTERFACE_PROXY...(::my::namespaces::ISecond)
|
||||
...
|
||||
END_INTERFACE_PROXY(::my::namespaces::ISecond)
|
||||
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
extern bool LoadMyTypes();
|
||||
}
|
||||
```
|
||||
|
||||
### Implementation File Example (.cpp)
|
||||
```cpp
|
||||
namespace vl::reflection::description
|
||||
{
|
||||
|
||||
#ifndef VCZH_DEBUG_NO_REFLECTION
|
||||
|
||||
MY_TYPES(IMPL_CPP_TYPE_INFO)
|
||||
|
||||
#ifdef VCZH_DESCRIPTABLEOBJECT_WITH_METADATA
|
||||
#define _ ,
|
||||
|
||||
BEGIN_CLASS_MEMBER(::my::namespaces::ISecond)
|
||||
CLASS_MEMBER_METHOD(ThisFunction, NO_PARAMETER)
|
||||
CLASS_MEMBER_METHOD(ThatFunction, { L"arg1" _ L"arg2" })
|
||||
...
|
||||
END_CLASS_MEMBER(::my::namespaces::ISecond)
|
||||
|
||||
BEGIN_INTERFACE_MEMBER(::my::namespaces::ISecond)
|
||||
vint Func(vint a, vint b) override
|
||||
{
|
||||
INVOKEGET_INTERFACE_PROXY_NOPARAMS(Func, a, b);
|
||||
}
|
||||
...
|
||||
END_INTERFACE_MEMBER(::my::namespaces::ISecond)
|
||||
|
||||
#undef _
|
||||
|
||||
class MyTypeLoader : public Object, public virtual ITypeLoader
|
||||
{
|
||||
public:
|
||||
void Load(ITypeManager* manager) override
|
||||
{
|
||||
MY_TYPES(ADD_TYPE_INFO)
|
||||
}
|
||||
|
||||
void Unload(ITypeManager* manager) override
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
bool LoadMyTypes()
|
||||
{
|
||||
#ifdef VCZH_DESCRIPTABLEOBJECT_WITH_METADATA
|
||||
if (auto manager = GetGlobalTypeManager())
|
||||
{
|
||||
return manager->AddTypeLoader(Ptr(new MyTypeLoader));
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Key Components
|
||||
|
||||
### Type Lists
|
||||
Define macros that enumerate all types to be registered:
|
||||
- Use the `#define TYPE_LIST(F)` pattern
|
||||
- Include all fully qualified type names
|
||||
- Apply the macro to various registration operations
|
||||
|
||||
### Namespace Requirements
|
||||
- All registration must occur in `vl::reflection::description` namespace
|
||||
- Maintain consistent namespace usage throughout
|
||||
- Use fully qualified names for registered types
|
||||
|
||||
### Conditional Compilation
|
||||
- Wrap registration code with appropriate reflection level checks
|
||||
- Support different compilation modes (full, metadata-only, no reflection)
|
||||
- Use proper preprocessor guards
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Consistency**: Follow the exact patterns used in existing registration files
|
||||
2. **Organization**: Keep related registrations together in the same file
|
||||
3. **Dependencies**: Ensure proper dependency ordering between type registrations
|
||||
4. **Documentation**: Comment complex registration patterns for clarity
|
||||
|
||||
### Common Pitfalls
|
||||
|
||||
- Missing namespace qualifications
|
||||
- Incorrect conditional compilation guards
|
||||
- Mixing registration with implementation logic
|
||||
- Inconsistent file organization patterns
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Regularly review registration files for consistency
|
||||
- Update registration patterns when new reflection features are added
|
||||
- Ensure all new types follow the established registration structure
|
||||
118
.github/KnowledgeBase/KB_VlppRegex_PatternMatching.md
vendored
Normal file
118
.github/KnowledgeBase/KB_VlppRegex_PatternMatching.md
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
# Pattern Matching Operations
|
||||
|
||||
Text pattern matching and searching operations with support for different UTF encodings.
|
||||
|
||||
The definition and the string to match could be in different UTF encoding.
|
||||
`Regex_<T>` accepts `ObjectString<T>` as the definition.
|
||||
`MatchHead<U>`, `Match<U>`, `TestHead<U>`, `Test<U>`, `Search<U>`, `Split<U>` and `Cut<U>` accepts `ObjectString<U>` to match with the regular expression.
|
||||
|
||||
## Core Pattern Matching Methods
|
||||
|
||||
### MatchHead<U>
|
||||
|
||||
`MatchHead` finds the longest prefix of the string which matches the regular expression.
|
||||
|
||||
This method attempts to match the pattern starting from the beginning of the input string and returns the longest possible match found at the start. It will return detailed match information including captured groups if the pattern matches.
|
||||
|
||||
### Match<U>
|
||||
|
||||
`Match` finds the earliest substring which matches the regular expression.
|
||||
|
||||
This method searches through the entire input string to find the first occurrence of a substring that matches the pattern. Unlike `MatchHead`, it doesn't require the match to be at the beginning of the string.
|
||||
|
||||
### TestHead<U>
|
||||
|
||||
`TestHead` performs a similar action to `MatchHead`, but it only returns `bool` without detailed information.
|
||||
|
||||
This is an optimization when you only need to know whether the beginning of the string matches the pattern, without requiring access to the actual match data or captured groups.
|
||||
|
||||
### Test<U>
|
||||
|
||||
`Test` performs a similar action to `Match`, but it only returns `bool` without detailed information.
|
||||
|
||||
This is an optimization when you only need to know whether the string contains a substring that matches the pattern, without requiring access to the actual match data or captured groups.
|
||||
|
||||
## Advanced Pattern Operations
|
||||
|
||||
### Search<U>
|
||||
|
||||
`Search` finds all substrings which match the regular expression. All results do not overlap with each other.
|
||||
|
||||
This method returns a collection of all non-overlapping matches found in the input string. When multiple matches are possible at the same position, it will choose one and continue searching from after that match.
|
||||
|
||||
### Split<U>
|
||||
|
||||
`Split` use the regular expression as a splitter, finding all remaining substrings.
|
||||
|
||||
This method treats the pattern as a delimiter and splits the input string wherever the pattern matches, returning the parts between the matches. This is similar to string split operations but with the power of regular expressions.
|
||||
|
||||
### Cut<U>
|
||||
|
||||
`Cut` combines both `Search` and `Split`, finding all substrings in order, regardless if one matches or not.
|
||||
|
||||
This method returns all parts of the string in sequence, both the parts that match the pattern and the parts that don't match. This gives you a complete decomposition of the input string.
|
||||
|
||||
## Extra Content
|
||||
|
||||
### UTF Encoding Support
|
||||
|
||||
One of the key features of VlppRegex is its support for different UTF encodings between the pattern definition and the input text:
|
||||
|
||||
- The regex pattern is defined using `Regex_<T>` where `T` is the character type for the pattern
|
||||
- The input text uses type `U` in the matching methods
|
||||
- This allows patterns defined in one encoding to match text in another encoding
|
||||
- Supported character types include `wchar_t`, `char8_t`, `char16_t`, `char32_t`
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
The VlppRegex engine has specific performance characteristics:
|
||||
|
||||
- **DFA Compatible vs Incompatible**: Features that break DFA compatibility (like backreferences) significantly impact performance
|
||||
- **Escaping Optimization**: Using `/` instead of `\` for escaping can improve readability in C++ code
|
||||
- **Method Selection**: Choose simpler methods like `Test` or `TestHead` when you only need boolean results
|
||||
|
||||
### Syntax Differences from .NET
|
||||
|
||||
While mostly compatible with .NET regex syntax, VlppRegex has important differences:
|
||||
|
||||
- **Dot Character**: `.` matches literal '.' character, while `/.` or `\.` matches all characters
|
||||
- **Escaping**: Both `/` and `\` perform escaping (prefer `/` for C++ compatibility)
|
||||
- **Character Classes**: Standard character classes work the same as .NET
|
||||
- **Quantifiers**: Standard quantifiers (`*`, `+`, `?`, `{n,m}`) work as expected
|
||||
|
||||
### Error Handling
|
||||
|
||||
When using regex operations:
|
||||
|
||||
- Invalid patterns will cause compilation errors when constructing `Regex_<T>`
|
||||
- Invalid input strings generally won't cause errors but may produce no matches
|
||||
- Method calls on empty or invalid regex objects may result in undefined behavior
|
||||
|
||||
### Common Usage Patterns
|
||||
|
||||
**Simple validation**: (note that `^` is not required and `$` does not make sense here)
|
||||
```cpp
|
||||
Regex regex(L"[0-9]+");
|
||||
bool isNumber = regex.TestHead(input);
|
||||
```
|
||||
|
||||
**Extracting all matches**:
|
||||
```cpp
|
||||
Regex regex(L"\\w+");
|
||||
auto matches = regex.Search(text);
|
||||
for (auto match : matches) {
|
||||
// Process each word
|
||||
}
|
||||
```
|
||||
|
||||
**Splitting text**:
|
||||
```cpp
|
||||
Regex regex(L"[,;]");
|
||||
auto parts = regex.Split(csvLine);
|
||||
```
|
||||
|
||||
**Complete decomposition**:
|
||||
```cpp
|
||||
Regex regex(L"\\d+");
|
||||
auto parts = regex.Cut(mixedText); // Returns both numbers and non-numbers
|
||||
```
|
||||
164
.github/KnowledgeBase/KB_VlppRegex_TypeAliases.md
vendored
Normal file
164
.github/KnowledgeBase/KB_VlppRegex_TypeAliases.md
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
# Type Aliases
|
||||
|
||||
Convenient type aliases for common character encodings to simplify regex usage.
|
||||
|
||||
VlppRegex provides convenient type aliases that eliminate the need to specify template parameters when working with the most common character encoding - `wchar_t`. These aliases make the code more readable and easier to work with.
|
||||
|
||||
## Core Type Aliases
|
||||
|
||||
### RegexString
|
||||
|
||||
`RegexString` -> `RegexString_<wchar_t>`
|
||||
|
||||
This alias provides a convenient way to work with regex strings without specifying the character type explicitly when using wide characters.
|
||||
|
||||
### RegexMatch
|
||||
|
||||
`RegexMatch` -> `RegexMatch_<wchar_t>`
|
||||
|
||||
This alias represents match results for wide character regex operations, containing information about successful matches including position, length, and captured groups.
|
||||
|
||||
### Regex
|
||||
|
||||
`Regex` -> `Regex_<wchar_t>`
|
||||
|
||||
This is the main regex class alias for wide character patterns. This is the most commonly used alias when working with `WString` patterns and input text.
|
||||
|
||||
## Extended Type Aliases
|
||||
|
||||
### RegexToken
|
||||
|
||||
`RegexToken` -> `RegexToken_<wchar_t>`
|
||||
|
||||
This alias is used for tokenization operations where the regex engine breaks input into discrete tokens based on patterns.
|
||||
|
||||
### RegexProc
|
||||
|
||||
`RegexProc` -> `RegexProc_<wchar_t>`
|
||||
|
||||
This alias represents callback procedures used in advanced regex processing scenarios.
|
||||
|
||||
### RegexTokens
|
||||
|
||||
`RegexTokens` -> `RegexTokens_<wchar_t>`
|
||||
|
||||
This alias represents collections of tokens produced by regex tokenization operations.
|
||||
|
||||
## Lexer-Related Aliases
|
||||
|
||||
### RegexLexerWalker
|
||||
|
||||
`RegexLexerWalker` -> `RegexLexerWalker_<wchar_t>`
|
||||
|
||||
This alias is used for advanced lexical analysis where you need to walk through and analyze tokenized input systematically.
|
||||
|
||||
### RegexLexerColorizer
|
||||
|
||||
`RegexLexerColorizer` -> `RegexLexerColorizer_<wchar_t>`
|
||||
|
||||
This alias is used for syntax highlighting and text colorization based on regex pattern matching, commonly used in text editors and IDEs.
|
||||
|
||||
### RegexLexer
|
||||
|
||||
`RegexLexer` -> `RegexLexer_<wchar_t>`
|
||||
|
||||
This alias represents the main lexical analyzer that combines multiple regex patterns for comprehensive text analysis and tokenization.
|
||||
|
||||
## Extra Content
|
||||
|
||||
### When to Use Aliases vs Templates
|
||||
|
||||
**Use aliases when**:
|
||||
- Working with `WString` and wide character text (most common scenario)
|
||||
- Writing application code that doesn't need encoding flexibility
|
||||
- Simplifying code readability
|
||||
- Following the project's preference for `wchar_t`
|
||||
|
||||
**Use templates when**:
|
||||
- Working with mixed character encodings
|
||||
- Building reusable library components
|
||||
- Needing explicit control over character types
|
||||
- Interfacing with systems using different encodings
|
||||
|
||||
### Character Encoding Considerations
|
||||
|
||||
These aliases all resolve to `wchar_t`, which has platform-specific behavior:
|
||||
|
||||
- **Windows**: `wchar_t` is UTF-16 (16-bit)
|
||||
- **Other platforms**: `wchar_t` is UTF-32 (32-bit)
|
||||
|
||||
This means the same code using these aliases will handle different Unicode encodings transparently across platforms.
|
||||
|
||||
### Integration with VlppOS String Types
|
||||
|
||||
These aliases work seamlessly with the project's string types:
|
||||
|
||||
- `RegexString` works directly with `WString`
|
||||
- Pattern matching operations accept `WString` input
|
||||
- Results integrate with `WString` manipulation functions
|
||||
- Conversion functions like `wtou8`, `wtou16` can be used with results
|
||||
|
||||
### Performance Characteristics
|
||||
|
||||
Using aliases vs explicit templates has no performance impact:
|
||||
|
||||
- Aliases are resolved at compile time
|
||||
- No runtime overhead compared to template instantiation
|
||||
- Same optimization opportunities
|
||||
- Binary size remains identical
|
||||
|
||||
### Common Usage Examples
|
||||
|
||||
**Basic pattern matching**:
|
||||
```cpp
|
||||
Regex pattern(L"\\d+");
|
||||
auto match = pattern.Match(text);
|
||||
```
|
||||
|
||||
**Lexical analysis**:
|
||||
```cpp
|
||||
List<WString> tokenDefs;
|
||||
tokenDefs.Add(L"\\b(if|else|while)\\b"); // keywords
|
||||
tokenDefs.Add(L"\\b[a-zA-Z_][a-zA-Z0-9_]*\\b"); // identifiers
|
||||
tokenDefs.Add(L"\\d+"); // numbers
|
||||
|
||||
RegexLexer lexer(tokenDefs);
|
||||
auto tokens = lexer.Parse(sourceCode);
|
||||
```
|
||||
|
||||
**Syntax highlighting**:
|
||||
```cpp
|
||||
List<WString> tokenDefs;
|
||||
tokenDefs.Add(L"\\bclass\\b"); // token 0: keywords
|
||||
tokenDefs.Add(L"\"[^\"]*\""); // token 1: strings
|
||||
|
||||
RegexProc proc;
|
||||
proc.colorizeProc = [](void* argument, vint start, vint length, vint token)
|
||||
{
|
||||
// Apply colors based on token type
|
||||
if (token == 0) ApplyKeywordColor(start, length);
|
||||
if (token == 1) ApplyStringColor(start, length);
|
||||
};
|
||||
|
||||
RegexLexer lexer(tokenDefs);
|
||||
RegexLexerColorizer colorizer = lexer.Colorize(proc);
|
||||
```
|
||||
|
||||
### Migration Considerations
|
||||
|
||||
When migrating code:
|
||||
|
||||
- Replace `RegexString_<wchar_t>` with `RegexString`
|
||||
- Replace `Regex_<wchar_t>` with `Regex`
|
||||
- Other template instantiations can be simplified similarly
|
||||
- No functional changes required
|
||||
- Code becomes more readable and maintainable
|
||||
|
||||
### Template Specialization Background
|
||||
|
||||
These aliases exist because VlppRegex is built on a template system:
|
||||
|
||||
- Base templates like `Regex_<T>` support any character type
|
||||
- Most applications only need wide character support
|
||||
- Aliases eliminate template parameter repetition
|
||||
- Consistent with the project's preference for `wchar_t` over other character types
|
||||
175
.github/KnowledgeBase/KB_Vlpp_CollectionTypes.md
vendored
Normal file
175
.github/KnowledgeBase/KB_Vlpp_CollectionTypes.md
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
# Collection Types
|
||||
|
||||
Dynamic containers implementing IEnumerable interface with comprehensive manipulation capabilities.
|
||||
|
||||
## Overview
|
||||
|
||||
Every collection type in Vlpp implements `IEnumerable<T>`, providing a unified interface for enumeration. The collection system works similarly to C# collections, with `IEnumerable<T>` and `IEnumerator<T>` providing consistent iteration patterns across all container types.
|
||||
|
||||
## Core Collection Types
|
||||
|
||||
### Array<T>
|
||||
|
||||
Fixed-size collection with random access capabilities.
|
||||
|
||||
An `Array<T>` provides random access to elements and can be initialized with a specific size or copied from existing data.
|
||||
|
||||
**Initialization:**
|
||||
- `Array<T>(vint size)` - Creates array with specified size filled with random values
|
||||
- `Array<T>(T* buffer, vint size)` - Creates array copied from buffer
|
||||
|
||||
**Access and Query:**
|
||||
- Use `Count()` to know the size of the collection
|
||||
- Use `Get(index)` or `[index]` to get the value from a specified position
|
||||
- Use `Contains(value)` or `IndexOf(value)` to find a value
|
||||
|
||||
**Modification:**
|
||||
- Use `Resize(size)` to resize an array and keep the values, if the new size is larger than the old size, the array is filled with random values at the end
|
||||
- Use `Set(index, value)` or `[index] = value` to set the value to a specified position
|
||||
|
||||
### List<T>
|
||||
|
||||
Dynamic array with insertion and removal operations.
|
||||
|
||||
A `List<T>` works like an `Array<T>` with dynamic size, providing efficient insertion and removal operations.
|
||||
|
||||
**Access and Query:**
|
||||
- Use `Count()` to know the size of the collection
|
||||
- Use `Get(index)` or `[index]` to get the value from a specified position
|
||||
- Use `Contains(value)` or `IndexOf(value)` to find a value
|
||||
|
||||
**Modification:**
|
||||
- Use `Add(value)` to add a value at the end
|
||||
- Use `Insert(index, value)` to insert a value to a specified position, which means `l.Insert(l.Count(), value)` equivalents to `l.Add(value)`
|
||||
- Use `Remove(value)` to remove the first equivalent value
|
||||
- Use `RemoveAt(index)` to remove the value of the specified position
|
||||
- Use `RemoveRange(index, count)` to remove consecutive values
|
||||
- Use `Clear()` to remove all values
|
||||
- Use `Set(index, value)` or `[index] = value` to set the value to a specified position
|
||||
|
||||
### SortedList<T>
|
||||
|
||||
Automatically ordered collection maintaining sort order.
|
||||
|
||||
A `SortedList<T>` works like a `List<T>` but it always keeps all values in order. It has everything a `List<T>` has except `Insert` and `Set` operations (which would break the ordering).
|
||||
|
||||
**Access and Query:**
|
||||
- Use `Count()` to know the size of the collection
|
||||
- Use `Get(index)` or `[index]` to get the value from a specified position
|
||||
- Use `Contains(value)` or `IndexOf(value)` to find a value
|
||||
|
||||
**Modification:**
|
||||
- Use `Add(value)` to insert a value while keeping all values in order
|
||||
- Use `Remove(value)` to remove the first equivalent value
|
||||
- Use `RemoveAt(index)` to remove the value of the specified position
|
||||
- Use `RemoveRange(index, count)` to remove consecutive values
|
||||
- Use `Clear()` to remove all values
|
||||
|
||||
## Associative Collections
|
||||
|
||||
### Dictionary<Key, Value>
|
||||
|
||||
One-to-one key-value mapping with ordered keys.
|
||||
|
||||
A `Dictionary<K, V>` is an one-to-one map that keeps all values in the order of keys. It implements `IEnumerable<Pair<K, V>>`.
|
||||
|
||||
**Access and Query:**
|
||||
- Use `Count()` to know the size of the collection
|
||||
- Use `Keys()` to get an immutable collection of keys
|
||||
- Use `Values()` to get an immutable collection of values in the order of keys
|
||||
- Use `Get(key)` or `[key]` to access a value by its key
|
||||
|
||||
**Modification:**
|
||||
- Use `Add(key, value)` or `Add(pair)` to assign a value with a key, it crashes if the key exists
|
||||
- Use `Set(key, value)` or `Set(pair)` to assign a value with a key, it overrides the old value if the key exists
|
||||
- Use `Remove(key)` to remove the value with a key
|
||||
- Use `Clear()` to remove all values
|
||||
|
||||
### Group<Key, Value>
|
||||
|
||||
One-to-many key-value mapping with ordered keys.
|
||||
|
||||
A `Group<K, V>` is an one-to-many map that keeps all values in the order of keys. It implements `IEnumerable<Pair<K, V>>`.
|
||||
|
||||
**Access and Query:**
|
||||
- Use `Count()` to know the size of keys
|
||||
- Use `Keys()` to get an immutable collection of keys
|
||||
- Use `Get(key)` or `[key]` to access all values by its key. `g.GetByIndex(index)` equivalents to `g.Get(g.Keys()[index])`
|
||||
- Use `Contains(key)` to determine if there is any value assigned with the key
|
||||
- Use `Contains(key, value)` to determine if the value is assigned with the key
|
||||
|
||||
**Modification:**
|
||||
- Use `Add(key, value)` or `Add(pair)` to assign one more value with a key
|
||||
- Use `Remove(key, value)` to remove the value with a key
|
||||
- Use `Remove(key)` to remove all values with a key
|
||||
- Use `Clear()` to remove all values
|
||||
|
||||
## Enumeration and Iteration
|
||||
|
||||
### IEnumerable<T> Interface
|
||||
|
||||
All collection types implement `IEnumerable<T>`, providing consistent enumeration capabilities across the collection system.
|
||||
|
||||
**Range-based for loops:**
|
||||
```cpp
|
||||
List<vint> numbers;
|
||||
// ... populate numbers ...
|
||||
|
||||
for (auto number : numbers)
|
||||
{
|
||||
// Process each number
|
||||
}
|
||||
```
|
||||
|
||||
**Indexed enumeration:**
|
||||
Use the `indexed` function to convert an `IEnumerable<T>` to `IEnumerable<Pair<vint, T>>` for iteration with indices:
|
||||
|
||||
```cpp
|
||||
for (auto [index, value] : indexed(collection))
|
||||
{
|
||||
// Process value with its index
|
||||
}
|
||||
```
|
||||
|
||||
This pattern works with structured binding and provides both the index and value for each element.
|
||||
|
||||
## Integration with LINQ
|
||||
|
||||
All collection types can be used with LINQ operations through the `From(collection)` function, which creates a `LazyList<T>` for functional programming operations:
|
||||
|
||||
```cpp
|
||||
auto result = From(numbers)
|
||||
.Where([](vint x) { return x > 0; })
|
||||
.Select([](vint x) { return x * 2; })
|
||||
.ToList();
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Dictionary vs Group
|
||||
- Use `Dictionary<K, V>` when each key maps to exactly one value
|
||||
- Use `Group<K, V>` when keys can have multiple associated values
|
||||
|
||||
### Array vs List vs SortedList
|
||||
- Use `Array<T>` for fixed-size collections with known size
|
||||
- Use `List<T>` for dynamic collections with frequent insertions/removals
|
||||
- Use `SortedList<T>` when you need automatic ordering without manual sorting
|
||||
|
||||
### Value Types vs Reference Types
|
||||
Collections work with both value types and reference types:
|
||||
- Value types are stored directly in the collection
|
||||
- Reference types should use `Ptr<T>` for proper memory management
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Memory Management
|
||||
Collections handle memory management automatically. When storing reference types, use `Ptr<T>` for proper reference counting and automatic cleanup.
|
||||
|
||||
### Thread Safety
|
||||
Collections are not thread-safe by default. For concurrent access, use appropriate synchronization primitives from VlppOS.
|
||||
|
||||
### Collection Copying
|
||||
Collections support copy semantics - copying a collection creates a new independent instance with copies of all elements (for value types) or shared references (for `Ptr<T>` reference types).
|
||||
|
||||
### Comparison Operations
|
||||
Collections that maintain order (SortedList, Dictionary, Group) require their element types to support comparison operations. Ensure your types implement appropriate comparison operators or provide custom comparators.
|
||||
271
.github/KnowledgeBase/KB_Vlpp_ConsoleOperations.md
vendored
Normal file
271
.github/KnowledgeBase/KB_Vlpp_ConsoleOperations.md
vendored
Normal file
@@ -0,0 +1,271 @@
|
||||
# Console Operations
|
||||
|
||||
## Overview
|
||||
|
||||
Vlpp provides basic console input/output operations for command-line interface applications. These functions provide simple and cross-platform ways to output text to the console, essential for CLI tools, debugging output, and simple user interaction in console applications.
|
||||
|
||||
## Basic Output Functions
|
||||
|
||||
### Console::Write Function
|
||||
|
||||
The primary function for outputting text to the console without a line break.
|
||||
|
||||
```cpp
|
||||
Console::Write(L"Hello ");
|
||||
Console::Write(L"World");
|
||||
// Output: Hello World (on same line)
|
||||
```
|
||||
|
||||
**Function usage:**
|
||||
```cpp
|
||||
Console::Write(const WString& text);
|
||||
```
|
||||
|
||||
**Key characteristics:**
|
||||
- **No automatic line break**: Text is written directly to the console without adding a newline character
|
||||
- **Wide string support**: Accepts `WString` parameters, supporting Unicode text output
|
||||
- **Cross-platform**: Works consistently across Windows and Linux platforms
|
||||
- **Immediate output**: Text appears immediately in the console
|
||||
|
||||
### Console::WriteLine Function
|
||||
|
||||
The primary function for outputting text to the console with an automatic line break.
|
||||
|
||||
```cpp
|
||||
Console::WriteLine(L"First line");
|
||||
Console::WriteLine(L"Second line");
|
||||
// Output:
|
||||
// First line
|
||||
// Second line
|
||||
```
|
||||
|
||||
**Function usage:**
|
||||
```cpp
|
||||
Console::WriteLine(const WString& text);
|
||||
```
|
||||
|
||||
**Key characteristics:**
|
||||
- **Automatic line break**: Adds a newline character after the text
|
||||
- **Wide string support**: Accepts `WString` parameters for Unicode text
|
||||
- **Cross-platform compatibility**: Consistent behavior across operating systems
|
||||
- **Sequential output**: Each call produces a new line of output
|
||||
|
||||
## Usage Patterns
|
||||
|
||||
### Simple Text Output
|
||||
|
||||
For basic text output to the console:
|
||||
|
||||
```cpp
|
||||
Console::WriteLine(L"Application starting...");
|
||||
Console::WriteLine(L"Processing data...");
|
||||
Console::WriteLine(L"Operation completed.");
|
||||
```
|
||||
|
||||
### Mixed Output Formatting
|
||||
|
||||
Combining `Write` and `WriteLine` for formatted output:
|
||||
|
||||
```cpp
|
||||
Console::Write(L"Progress: ");
|
||||
Console::Write(L"50");
|
||||
Console::WriteLine(L"%");
|
||||
// Output: Progress: 50%
|
||||
```
|
||||
|
||||
### String Concatenation
|
||||
|
||||
Using string operations with console output:
|
||||
|
||||
```cpp
|
||||
WString userName = L"John";
|
||||
Console::WriteLine(L"Hello, " + userName + L"!");
|
||||
// Output: Hello, John!
|
||||
```
|
||||
|
||||
### Numeric Output
|
||||
|
||||
Converting numbers to strings for console display:
|
||||
|
||||
```cpp
|
||||
vint count = 42;
|
||||
Console::WriteLine(L"Total items: " + itow(count));
|
||||
// Output: Total items: 42
|
||||
|
||||
double value = 3.14159;
|
||||
Console::WriteLine(L"Pi value: " + ftow(value));
|
||||
// Output: Pi value: 3.14159
|
||||
```
|
||||
|
||||
## Integration with Application Types
|
||||
|
||||
### Console Applications
|
||||
|
||||
Use console operations in main functions for CLI applications:
|
||||
|
||||
```cpp
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
Console::WriteLine(L"My Console Application");
|
||||
Console::WriteLine(L"Version 1.0");
|
||||
|
||||
// Application logic here
|
||||
|
||||
Console::WriteLine(L"Press any key to exit...");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Debug Output
|
||||
|
||||
Console operations are useful for debugging and diagnostic output:
|
||||
|
||||
```cpp
|
||||
void ProcessData(const List<vint>& data)
|
||||
{
|
||||
Console::WriteLine(L"Processing " + itow(data.Count()) + L" items");
|
||||
|
||||
for (vint i = 0; i < data.Count(); i++)
|
||||
{
|
||||
Console::Write(L"Item " + itow(i) + L": ");
|
||||
Console::WriteLine(itow(data[i]));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Reporting
|
||||
|
||||
Simple error message display:
|
||||
|
||||
```cpp
|
||||
try
|
||||
{
|
||||
// Some operation that might fail
|
||||
}
|
||||
catch (const Exception& ex)
|
||||
{
|
||||
Console::WriteLine(L"Error: Operation failed");
|
||||
Console::WriteLine(L"Please check your input and try again");
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Use WString Consistently
|
||||
|
||||
Always use `WString` with console operations for proper Unicode support:
|
||||
|
||||
```cpp
|
||||
// Good: Using WString
|
||||
Console::WriteLine(L"Unicode text: 你好世界");
|
||||
|
||||
// Avoid: Don't mix with narrow strings
|
||||
// Console::WriteLine("Narrow string"); // Wrong - type mismatch
|
||||
```
|
||||
|
||||
### Format Strings Properly
|
||||
|
||||
Build formatted output using string operations and conversion functions:
|
||||
|
||||
```cpp
|
||||
vint fileSize = 1024;
|
||||
WString fileName = L"document.txt";
|
||||
|
||||
Console::WriteLine(L"File: " + fileName);
|
||||
Console::WriteLine(L"Size: " + itow(fileSize) + L" bytes");
|
||||
```
|
||||
|
||||
### Handle Long Output
|
||||
|
||||
Break long text into manageable lines:
|
||||
|
||||
```cpp
|
||||
WString longMessage = L"This is a very long message that should be broken into multiple lines for better readability";
|
||||
|
||||
Console::WriteLine(L"Status Report:");
|
||||
Console::WriteLine(L" " + longMessage.Left(40));
|
||||
Console::WriteLine(L" " + longMessage.Right(longMessage.Length() - 40));
|
||||
```
|
||||
|
||||
## Cross-Platform Considerations
|
||||
|
||||
### Character Encoding
|
||||
|
||||
Console operations handle character encoding appropriately across platforms:
|
||||
- **Windows**: UTF-16 wide character support
|
||||
- **Linux**: UTF-32 wide character support with proper conversion
|
||||
- **Automatic handling**: The framework manages encoding differences transparently
|
||||
|
||||
### Line Endings
|
||||
|
||||
`Console::WriteLine` uses platform-appropriate line endings:
|
||||
- **Windows**: CRLF (\\r\\n)
|
||||
- **Linux**: LF (\\n)
|
||||
- **Consistent behavior**: Code works the same way regardless of platform
|
||||
|
||||
### Console Availability
|
||||
|
||||
Console operations assume a console window is available:
|
||||
- **Console applications**: Always have console access
|
||||
- **GUI applications**: May not have console access on some platforms
|
||||
- **Service applications**: May redirect console output to logs
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
Console I/O is generally slower than file I/O, so for large amounts of output consider:
|
||||
- Buffering text before output
|
||||
- Using file operations for extensive logging
|
||||
- Limiting console output in performance-critical applications
|
||||
|
||||
### Alternative Output Methods
|
||||
|
||||
For different output needs, consider:
|
||||
- File operations from VlppOS for persistent output
|
||||
- Stream operations for more complex I/O scenarios
|
||||
- Logging frameworks for structured application logging
|
||||
|
||||
### Integration with Unit Testing
|
||||
|
||||
Console operations can be useful in unit tests for diagnostic output, but we favor TEST_PRINT.
|
||||
|
||||
```cpp
|
||||
TEST_CASE(L"MyTestCase")
|
||||
{
|
||||
Console::WriteLine(L"Starting test: MyTestCase");
|
||||
|
||||
// Test logic here
|
||||
TEST_ASSERT(condition);
|
||||
|
||||
TEST_PRINT(L"Test completed successfully");
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
Console operations are designed to be robust and typically don't throw exceptions, but in resource-constrained environments or when console access is unavailable, they may silently fail. For critical output, consider combining with file logging:
|
||||
|
||||
```cpp
|
||||
void LogMessage(const WString& message)
|
||||
{
|
||||
// Output to console for immediate feedback
|
||||
Console::WriteLine(message);
|
||||
|
||||
// Also log to file for persistence
|
||||
// (using VlppOS file operations)
|
||||
}
|
||||
```
|
||||
|
||||
### Internationalization
|
||||
|
||||
Since console operations use `WString`, they support international characters natively:
|
||||
|
||||
```cpp
|
||||
Console::WriteLine(L"English: Hello World");
|
||||
Console::WriteLine(L"Chinese: 你好世界");
|
||||
Console::WriteLine(L"Japanese: こんにちは世界");
|
||||
Console::WriteLine(L"Arabic: مرحبا بالعالم");
|
||||
```
|
||||
|
||||
The actual display depends on the console's font and locale settings, but the framework properly handles the character encoding.
|
||||
88
.github/KnowledgeBase/KB_Vlpp_DateTimeOperations.md
vendored
Normal file
88
.github/KnowledgeBase/KB_Vlpp_DateTimeOperations.md
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
# Date and Time Operations
|
||||
|
||||
Cross-platform date and time handling with timezone conversions and arithmetic operations.
|
||||
|
||||
## Overview
|
||||
|
||||
The `DateTime` class provides cross-platform date and time functionality with support for timezone conversions, arithmetic operations, and customizable implementations for testing scenarios.
|
||||
|
||||
## Current Time Operations
|
||||
|
||||
Retrieve current system time in different timezone contexts.
|
||||
|
||||
- Use `DateTime::LocalTime()` to get current time in local timezone
|
||||
- Use `DateTime::UtcTime()` to get current time in UTC timezone
|
||||
|
||||
Both static methods return a `DateTime` instance representing the current moment, but interpreted in different timezone contexts. The local time reflects the system's configured timezone, while UTC time provides a standardized reference point.
|
||||
|
||||
## DateTime Construction
|
||||
|
||||
Create specific date and time instances programmatically.
|
||||
|
||||
- Use `DateTime::FromDateTime(year, month, day, hour, minute, second, milliseconds)` for precise date/time creation
|
||||
- Use `DateTime::FromOSInternal(osInternal)` to create from OS-specific internal representation
|
||||
|
||||
The `FromDateTime` method allows you to specify all components of a date and time, from year down to milliseconds. All parameters are validated according to calendar rules.
|
||||
|
||||
The `FromOSInternal` method is typically used internally but can be useful when working with OS-specific time representations.
|
||||
|
||||
## Timezone Conversions
|
||||
|
||||
Convert between local and UTC time representations.
|
||||
|
||||
- Use `ToLocalTime()` to convert from UTC to local timezone
|
||||
- Use `ToUtcTime()` to convert from local to UTC timezone
|
||||
|
||||
These instance methods create new `DateTime` objects with the time converted to the target timezone. The conversion takes into account the system's timezone configuration and daylight saving time rules.
|
||||
|
||||
## Time Arithmetic
|
||||
|
||||
Perform calculations with date and time values.
|
||||
|
||||
- Use `Forward(milliseconds)` to add time to a DateTime instance
|
||||
- Use `Backward(milliseconds)` to subtract time from a DateTime instance
|
||||
|
||||
Both methods return new `DateTime` instances with the specified amount of time added or subtracted. The parameter is in milliseconds, allowing for precise time calculations.
|
||||
|
||||
## Implementation Injection
|
||||
|
||||
Override the default DateTime implementation for testing and customization.
|
||||
|
||||
- Use `InjectDateTimeImpl(impl)` to set a custom `IDateTimeImpl` implementation
|
||||
- Use `EjectDateTimeImpl(impl)` to remove a specific injected implementation from the injection chain
|
||||
- Use `EjectDateTimeImpl(nullptr)` to remove all injected implementations and restore the default OS-specific implementation
|
||||
|
||||
This functionality allows you to provide predictable time values for testing or implement custom time behaviors. The injected implementations form a chain where each implementation can delegate to the previous one in the chain.
|
||||
|
||||
When using injection for testing, always ensure proper cleanup by calling `EjectDateTimeImpl(nullptr)` to avoid affecting other test cases:
|
||||
|
||||
```cpp
|
||||
// Test example showing proper injection/ejection pattern
|
||||
MockDateTimeImpl mockImpl;
|
||||
|
||||
// Inject mock implementation
|
||||
InjectDateTimeImpl(&mockImpl);
|
||||
|
||||
// Store results before ejection to ensure cleanup happens even on test failure
|
||||
DateTime localResult = DateTime::LocalTime();
|
||||
|
||||
// Always eject to ensure no side effects on other tests
|
||||
EjectDateTimeImpl(nullptr);
|
||||
|
||||
// Verify results after cleanup
|
||||
TEST_ASSERT(localResult.year == 2000);
|
||||
```
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Platform Abstraction
|
||||
The `DateTime` class abstracts over platform-specific time APIs, providing consistent behavior across Windows and Linux. The underlying implementation handles differences in time representation and timezone handling between operating systems.
|
||||
|
||||
### Precision and Range
|
||||
DateTime operations maintain millisecond precision and can represent dates across a wide range suitable for most application needs. The exact range depends on the underlying platform implementation.
|
||||
|
||||
### Thread Safety
|
||||
`DateTime` instances are immutable value types and can be safely used across multiple threads. The implementation injection functionality should typically be used during application startup before multi-threaded usage begins.
|
||||
|
||||
### Integration with Other Components
|
||||
DateTime integrates with other parts of the framework, particularly the Locale system for culture-specific date formatting and the unit testing framework for time-dependent test scenarios.
|
||||
201
.github/KnowledgeBase/KB_Vlpp_Design_ImplementingInjectableFeature.md
vendored
Normal file
201
.github/KnowledgeBase/KB_Vlpp_Design_ImplementingInjectableFeature.md
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
## Overview
|
||||
|
||||
The FeatureInjection system provides a linked-list based dependency injection mechanism that allows runtime replacement and extension of feature implementations while maintaining delegation capabilities. This system enables testing with mock implementations, feature enhancement through layered behaviors, and platform-specific customizations without modifying core application code.
|
||||
|
||||
## Core Components Architecture
|
||||
|
||||
### IFeatureImpl Base Interface
|
||||
|
||||
The `IFeatureImpl` interface serves as the foundation for all injectable implementations:
|
||||
|
||||
- `GetPreviousImpl()`: Returns the previous implementation in the injection chain
|
||||
- `BeginInjection(IFeatureImpl* previousImpl)`: Lifecycle hook called when implementation becomes active in the chain
|
||||
- `EndInjection()`: Lifecycle hook called when implementation is being removed from the chain
|
||||
|
||||
**IMPORTANT**: The `EndInjection` lifecycle method has limited guarantees - it will only be invoked during explicit `Eject` and `EjectAll` operations. During application shutdown and object destruction, `EndInjection` calls cannot be guaranteed due to unpredictable destruction order.
|
||||
|
||||
### FeatureImpl<TImpl> Template Base Class
|
||||
|
||||
The `FeatureImpl<TImpl>` template provides type-safe delegation and chain management:
|
||||
|
||||
- Inherits from `Object` and virtually from `TImpl` interface
|
||||
- Maintains typed `_previousImpl` pointer for safe delegation access
|
||||
- Provides `Previous()` method returning correctly typed pointer to previous implementation
|
||||
- Handles automatic type casting and validation in `BeginInjection`
|
||||
- Offers virtual `BeginInjection(TImpl* previousImpl)` for derived classes to override
|
||||
|
||||
### FeatureInjection<TImpl> Manager Class
|
||||
|
||||
The `FeatureInjection<TImpl>` class manages the injection chain:
|
||||
|
||||
- Maintains `currentImpl` pointer to the active implementation
|
||||
- `Inject(TImpl* impl)`: Adds new implementation to top of chain (LIFO structure)
|
||||
- `Eject(TImpl* impl)`: Removes implementation and all subsequent ones from chain
|
||||
- `EjectAll()`: Removes all injected implementations, restoring default behavior
|
||||
- `Get()`: Returns current active implementation for feature usage
|
||||
|
||||
## Standard Implementation Pattern
|
||||
|
||||
### Interface Definition
|
||||
|
||||
Define the feature interface inheriting from `IFeatureImpl`:
|
||||
|
||||
```cpp
|
||||
class IMyFeatureImpl : public virtual IFeatureImpl
|
||||
{
|
||||
public:
|
||||
virtual void DoThis() = 0;
|
||||
virtual void DoThat() = 0;
|
||||
};
|
||||
```
|
||||
|
||||
### Default Implementation
|
||||
|
||||
Create default implementation inheriting from `FeatureImpl<TImpl>`:
|
||||
|
||||
```cpp
|
||||
class DefaultMyFeatureImpl : public FeatureImpl<IMyFeatureImpl>
|
||||
{
|
||||
public:
|
||||
void DoThis() override
|
||||
{
|
||||
// Default implementation - empty for demo purposes
|
||||
}
|
||||
|
||||
void DoThat() override
|
||||
{
|
||||
// Default implementation - empty for demo purposes
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Global Management Functions
|
||||
|
||||
Implement singleton pattern with static local variables for thread safety and initialization order independence:
|
||||
|
||||
```cpp
|
||||
IMyFeatureImpl* GetDefaultMyFeatureImpl()
|
||||
{
|
||||
static DefaultMyFeatureImpl impl;
|
||||
return &impl;
|
||||
}
|
||||
|
||||
FeatureInjection<IMyFeatureImpl>& GetMyFeatureInjection()
|
||||
{
|
||||
static FeatureInjection<IMyFeatureImpl> injection(GetDefaultMyFeatureImpl());
|
||||
return injection;
|
||||
}
|
||||
|
||||
void InjectMyFeatureImpl(IMyFeatureImpl* impl)
|
||||
{
|
||||
GetMyFeatureInjection().Inject(impl);
|
||||
}
|
||||
|
||||
void EjectMyFeatureImpl(IMyFeatureImpl* impl)
|
||||
{
|
||||
if (impl == nullptr)
|
||||
{
|
||||
GetMyFeatureInjection().EjectAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
GetMyFeatureInjection().Eject(impl);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Delegation Mechanism
|
||||
|
||||
### Type-Safe Previous Access
|
||||
|
||||
Injected implementations can delegate to previous implementations through the `Previous()` method provided by `FeatureImpl<TImpl>`:
|
||||
|
||||
```cpp
|
||||
class DemoMyFeatureImpl : public FeatureImpl<IMyFeatureImpl>
|
||||
{
|
||||
public:
|
||||
void DoThis() override
|
||||
{
|
||||
// Demo override - completely replace behavior
|
||||
}
|
||||
|
||||
void DoThat() override
|
||||
{
|
||||
// Demo delegation - calls previous implementation
|
||||
Previous()->DoThat();
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Chain Traversal Behavior
|
||||
|
||||
- **LIFO Structure**: Last injected implementation becomes current, previous current becomes its predecessor
|
||||
- **Cascading Ejection**: Ejecting from middle of chain removes all subsequent implementations
|
||||
- **Type Safety**: `FeatureImpl<TImpl>` ensures `Previous()` returns correctly typed pointer
|
||||
- **Lifecycle Management**: `BeginInjection`/`EndInjection` called automatically during inject/eject operations
|
||||
|
||||
## Real-World Example: DateTime System
|
||||
|
||||
### Interface Implementation
|
||||
|
||||
The DateTime system demonstrates the complete pattern through `IDateTimeImpl`:
|
||||
|
||||
- Extends `IFeatureImpl` with DateTime-specific methods: `FromDateTime`, `FromOSInternal`, `LocalTime`, `UtcTime`, `LocalToUtcTime`, `UtcToLocalTime`, `Forward`, `Backward`
|
||||
- Platform-specific implementations: `WindowsDateTimeImpl` and `LinuxDateTimeImpl` inherit from `FeatureImpl<IDateTimeImpl>`
|
||||
- Global management through `GetDateTimeInjection()`, `InjectDateTimeImpl()`, and `EjectDateTimeImpl()`
|
||||
|
||||
### Testing Integration
|
||||
|
||||
The DateTime tests demonstrate practical usage:
|
||||
|
||||
- `MockDateTimeImpl` overrides only `LocalTime()` and `UtcTime()` to return fixed values for deterministic testing
|
||||
- Other methods like `FromDateTime()` delegate through `Previous()->FromDateTime()` unchanged
|
||||
- Tests verify proper lifecycle management through call counts on `BeginInjection`/`EndInjection`
|
||||
|
||||
## Static Local Variables Pattern
|
||||
|
||||
### Critical Design Choice
|
||||
|
||||
The use of static local variables in `GetMyFeatureInjection()` and `GetDefaultMyFeatureImpl()` serves multiple purposes:
|
||||
|
||||
- **Thread-Safe Singleton**: C++11+ guarantees thread-safe initialization of static locals
|
||||
- **Global State Management**: Ensures application-wide consistency for feature behavior
|
||||
- **Lazy Initialization**: Objects created only when first accessed, avoiding startup overhead
|
||||
- **Initialization Order Independence**: Prevents static initialization order fiasco
|
||||
- **Automatic Lifetime**: Objects destroyed at program termination without manual cleanup
|
||||
|
||||
### Memory and Performance Benefits
|
||||
|
||||
- **No Heap Allocation**: Objects live in static storage, avoiding dynamic allocation overhead
|
||||
- **Guaranteed Availability**: Injection manager and default implementation always available when needed
|
||||
- **Cross-Module Sharing**: Static locals in functions provide controlled global access without namespace pollution
|
||||
|
||||
## Usage Example
|
||||
|
||||
### Complete Demo Program
|
||||
|
||||
```cpp
|
||||
void DemoMyFeature()
|
||||
{
|
||||
DemoMyFeatureImpl demoImpl;
|
||||
|
||||
// Inject demo implementation
|
||||
InjectMyFeatureImpl(&demoImpl);
|
||||
|
||||
// Test both methods - DoThis is overridden, DoThat delegates
|
||||
GetMyFeatureInjection().Get()->DoThis(); // Uses demo implementation
|
||||
GetMyFeatureInjection().Get()->DoThat(); // Delegates to default through Previous()
|
||||
|
||||
// Always cleanup to restore default behavior
|
||||
EjectMyFeatureImpl(nullptr);
|
||||
}
|
||||
```
|
||||
|
||||
### Key Usage Principles
|
||||
|
||||
- **Always Clean Up**: Use `EjectMyFeatureImpl(nullptr)` to restore default behavior
|
||||
- **LIFO Injection**: Last injected becomes current implementation
|
||||
- **Delegation Choice**: Each method can choose to override completely or delegate to previous
|
||||
- **Error Handling**: Include null pointer checks and descriptive error messages
|
||||
- **Testing Support**: Perfect for injecting mock implementations during unit tests
|
||||
- **IMPORTANT**: Restrict injection and ejection calls to application-level code, frameworks, or unit tests only - never from within library code. This constraint exists because injection/ejection operations must maintain strict ordering to prevent crashes. For instance, calling `Inject(a)`, `Inject(b)`, `Eject(a)`, `Eject(b)` will cause `Eject(a)` to remove both implementations, making the subsequent `Eject(b)` call fail. Applications and frameworks maintain global knowledge of the injection state and can ensure proper cleanup ordering, while libraries cannot guarantee safe usage patterns.
|
||||
54
.github/KnowledgeBase/KB_Vlpp_ExceptionHandling.md
vendored
Normal file
54
.github/KnowledgeBase/KB_Vlpp_ExceptionHandling.md
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
# Exception Handling
|
||||
|
||||
There are `Error` and `Exception`.
|
||||
|
||||
`Error` is a base class, representing fatal errors which you can't recover by try-catch.
|
||||
In some rare cases when you have to catch it, you must always raise it again in the catch statement.
|
||||
It can be used to report a condition that should never happen.
|
||||
Use `CHECK_ERROR(condition, L"string-literal")` to raise an `Error` when the assertion fails.
|
||||
Use `CHECK_FAIL(L"string-literal")` to raise an `Error`. It is similar to `CHECK_ERROR` but it allows you to say a failed assertion if the condition cannot be described by just a condition.
|
||||
|
||||
`Exception` is a base class, representing errors that you may want to catch.
|
||||
It can be used to report error that made by the user.
|
||||
Some code also use `Exception` as control flows. For example, you could define your own `Exception` sub class, raise it inside a deep recursion and catch it from the outside, as a way of quick exiting.
|
||||
|
||||
## Special Cases
|
||||
|
||||
Use `class` for defining new `Error` or `Exception` sub classes, although they are value types.
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Error Usage Patterns
|
||||
|
||||
When using `Error`, the philosophy is that these represent programming errors or system inconsistencies that should never occur in properly written code. Examples include:
|
||||
|
||||
- Array bounds violations that should have been checked
|
||||
- Invalid state transitions that violate class invariants
|
||||
- Null pointer dereferences that should have been prevented
|
||||
- Resource corruption that indicates system failure
|
||||
|
||||
### Exception Usage Patterns
|
||||
|
||||
`Exception` is more appropriate for user-facing errors or expected failure conditions that the application should handle gracefully:
|
||||
|
||||
- File not found when user specifies an invalid path
|
||||
- Network connectivity issues during data transfer
|
||||
- Invalid user input that needs validation feedback
|
||||
- Resource exhaustion that can be recovered from
|
||||
|
||||
### Control Flow Applications
|
||||
|
||||
The mention of using `Exception` for control flow refers to advanced patterns where exceptions are used for non-local exits:
|
||||
|
||||
- Breaking out of deeply nested loops
|
||||
- Early returns from complex recursive algorithms
|
||||
- Implementing coroutine-like behavior
|
||||
- Managing complex state machine transitions
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. Always prefer `Exception` over `Error` unless the condition truly represents a programming error
|
||||
2. When catching `Error`, always re-throw it after any necessary cleanup
|
||||
3. Use descriptive string literals in `CHECK_ERROR` and `CHECK_FAIL` to aid debugging
|
||||
4. Consider defining custom `Exception` subclasses for specific error domains
|
||||
5. Document whether functions can throw `Error` vs `Exception` vs both
|
||||
61
.github/KnowledgeBase/KB_Vlpp_LambdaExpressions.md
vendored
Normal file
61
.github/KnowledgeBase/KB_Vlpp_LambdaExpressions.md
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
# Lambda Expressions and Callable Types
|
||||
|
||||
Function objects and event handling with type-safe callable containers for Vlpp.
|
||||
|
||||
## Prefer Lambda Expressions for Callbacks
|
||||
|
||||
Prefer lambda expressions for callbacks, unless for handling GacUI events or when the source file shows a trend for native functions.
|
||||
|
||||
Prefer lambda expressions as local functions, since C++ doesn't directly support local functions, it is not possible to make lambda local functions recursive in any way.
|
||||
A lambda local function can only call other lambda local functions that defined before itself, by capturing their names as references.
|
||||
|
||||
## Func<T(TArgs...)>
|
||||
|
||||
`Func<F>` work just like `std::function<F>`, it can be initialized with:
|
||||
- A `Func<F>` with different type, when arguments and return types can be implicitly converted in the expected direction.
|
||||
- A lambda expression.
|
||||
- A pointer to a native function.
|
||||
- A pointer to a method of a class, in this case the first argument should be the pointer to the class or its subclass.
|
||||
|
||||
If a `Func<F>` is not assigned with any callable object, it is empty and can be detected using the `operator bool` operator.
|
||||
|
||||
`Func(callable-object)` could automatically infer the function type, even though it is unnecessary to say `Func` in most of the cases.
|
||||
|
||||
## Event<void(TArgs...)>
|
||||
|
||||
`Event<F>` can be assigned with multiple callable objects which are compatible with `Func<F>`, by calling the `Add` method.
|
||||
The `Add` method returns a handle which could be used in `Remove` to revert the assigning.
|
||||
When an `Event<F>` is called, all assigned callable objects are executed.
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
`Func<F>` uses type erasure similar to `std::function<F>`, which may involve heap allocation for large callable objects. For performance-critical code, consider using templates to avoid the overhead of function object wrapping.
|
||||
|
||||
### Memory Management
|
||||
|
||||
Both `Func<F>` and `Event<F>` follow value semantics and manage their internal resources automatically. They can safely capture objects by value or by reference, but be careful with reference captures when the referenced objects may be destroyed before the callable object is invoked.
|
||||
|
||||
### Thread Safety
|
||||
|
||||
`Event<F>` is not thread-safe by default. If you need to access them from multiple threads, appropriate synchronization mechanisms should be used to ensure thread safety.
|
||||
|
||||
### Usage Examples
|
||||
|
||||
```cpp
|
||||
// Basic Func usage
|
||||
Func<vint(vint, vint)> addFunc = [](vint a, vint b) { return a + b; };
|
||||
auto result = addFunc(3, 4); // result is 7
|
||||
|
||||
// Event usage
|
||||
Event<void(vint)> numberEvent;
|
||||
auto handle1 = numberEvent.Add([](vint x) { Console::WriteLine(itow(x)); });
|
||||
auto handle2 = numberEvent.Add([](vint x) { Console::WriteLine(L"Number: " + itow(x)); });
|
||||
|
||||
numberEvent(42); // Both handlers will be called
|
||||
|
||||
// Remove specific handler
|
||||
numberEvent.Remove(handle1);
|
||||
numberEvent(24); // Only the second handler will be called
|
||||
```
|
||||
49
.github/KnowledgeBase/KB_Vlpp_LinqOperations.md
vendored
Normal file
49
.github/KnowledgeBase/KB_Vlpp_LinqOperations.md
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
# LINQ Operations
|
||||
|
||||
Functional programming operations on collections with lazy evaluation and method chaining.
|
||||
|
||||
## Overview
|
||||
|
||||
`LazyList<T>` implements Linq for C++ just like C#. Use `From(collection)` to create a `LazyList<T>` from any collection objects implementing `IEnumerable<T>`.
|
||||
|
||||
`LazyList<T>` also implements `IEnumerable<T>`.
|
||||
|
||||
In `LazyList<T>` there are many collection operating methods just like Linq for C#. When the expression is too long, line breaks are recommended before the `.` character like:
|
||||
|
||||
```cpp
|
||||
From(xs)
|
||||
.Skip(3)
|
||||
.Reverse()
|
||||
.Where([](auto x){ return x < 5; })
|
||||
.Select([](auto x){ return x * 2; })
|
||||
```
|
||||
|
||||
## APIs
|
||||
|
||||
- Use `LazyList<T>` for LINQ-style operations on any IEnumerable collection
|
||||
- Use `From(collection)` to create LazyList from collections
|
||||
- Use method chaining with `Skip()`, `Reverse()`, `Where()`, `Select()` for data transformation
|
||||
- Use `indexed` function for enumeration with index access
|
||||
- Use range-based for loops with any IEnumerable implementation
|
||||
|
||||
## Iterating with Collections, Linq, and also C++ Arrays/Pointers/STL Iterators
|
||||
|
||||
The C++ range based for loop also works with any collection objects implementing `IEnumerable<T>`.
|
||||
|
||||
You can convert an `IEnumerable<T>` to `IEnumerable<Pair<vint, T>>` using the `indexed` function, which is designed for `for(auto [index, x] : indexed(xs))` to iterate xs with an index. This is also an example of `Pair<K, V>` with structured binding.
|
||||
|
||||
## Extra Content
|
||||
|
||||
Check out comments before `#ifndef VCZH_COLLECTIONS_OPERATION` for a full list of operators available in the LINQ implementation.
|
||||
|
||||
The LINQ operations in Vlpp provide lazy evaluation, meaning operations are not executed immediately but are deferred until the results are actually needed. This allows for efficient chaining of operations without creating intermediate collections.
|
||||
|
||||
Common LINQ operations include:
|
||||
- Filtering operations: `Where()`, `Take()`, `Skip()`, `TakeWhile()`, `SkipWhile()`
|
||||
- Projection operations: `Select()`, `SelectMany()`
|
||||
- Ordering operations: `OrderBy()`, `OrderByDescending()`, `Reverse()`
|
||||
- Aggregation operations: `Aggregate()`, `All()`, `Any()`, `Count()`, `First()`, `Last()`
|
||||
- Set operations: `Distinct()`, `Concat()`, `Union()`, `Intersect()`, `Except()`
|
||||
- Conversion operations: converting to other collection types
|
||||
|
||||
The implementation is designed to be compatible with the existing collection infrastructure and provides similar functionality to .NET's LINQ while maintaining C++ idioms and performance characteristics.
|
||||
317
.github/KnowledgeBase/KB_Vlpp_MemoryLeakDetection.md
vendored
Normal file
317
.github/KnowledgeBase/KB_Vlpp_MemoryLeakDetection.md
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
# Memory Leak Detection
|
||||
|
||||
## Overview
|
||||
|
||||
Vlpp provides memory leak detection capabilities for debugging and testing C++ applications on Windows. The system addresses the problem of global variables being destructed after the `main` function returns, which can cause false negatives in memory leak detection tools. The framework offers both basic detection mechanisms and a global storage system for managing application-wide resources that need explicit lifecycle control.
|
||||
|
||||
## Basic Memory Leak Detection
|
||||
|
||||
### Standard Detection Pattern
|
||||
|
||||
On Windows (guarded by the `VCZH_MSVC` macro), applications typically use this pattern in `main`, `wmain` or `WinMain` to detect memory leaks:
|
||||
|
||||
```cpp
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int result = unittest::UnitTest::RunAndDisposeTests(argc, argv);
|
||||
vl::FinalizeGlobalStorage();
|
||||
#ifdef VCZH_CHECK_MEMORY_LEAKS
|
||||
_CrtDumpMemoryLeaks();
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
**Key characteristics:**
|
||||
- **Platform-specific**: Only available on Windows with MSVC compiler
|
||||
- **Conditional compilation**: Protected by `VCZH_CHECK_MEMORY_LEAKS` macro
|
||||
- **Requires finalization**: Must call `FinalizeGlobalStorage()` before leak detection
|
||||
- **End-of-program checking**: Performs leak detection just before application exit
|
||||
|
||||
### The Global Variable Problem
|
||||
|
||||
Global variables are destructed after `main` returns, which causes `_CrtDumpMemoryLeaks()` to report false negatives. This happens because:
|
||||
|
||||
- Global destructors run after `main` completes
|
||||
- Memory allocated by global variables appears as leaks to the detection tool
|
||||
- The recommended solution is explicit initialization and finalization in `main`
|
||||
- When explicit management isn't feasible, the global storage system provides an alternative
|
||||
|
||||
## Global Storage System
|
||||
|
||||
### Definition and Purpose
|
||||
|
||||
Global storage provides a managed approach to handling application-wide resources that need explicit lifecycle control. It's designed for situations where direct global variable management in `main` isn't practical.
|
||||
|
||||
**Usage guidelines:**
|
||||
- **Not recommended**: Global storage should be avoided unless specifically instructed
|
||||
- **Explicit preference**: Direct initialization and finalization in `main` is preferred
|
||||
- **Fallback option**: Use only when direct management is not feasible
|
||||
- **Resource management**: Typically used for pointer-based global variables
|
||||
|
||||
### Global Storage Implementation
|
||||
|
||||
A global storage is defined using a specific macro pattern:
|
||||
|
||||
```cpp
|
||||
BEGIN_GLOBAL_STORAGE_CLASS(MyGlobalStorage)
|
||||
Ptr<vint> resource;
|
||||
|
||||
INITIALIZE_GLOBAL_STORAGE_CLASS
|
||||
resource = Ptr(new vint(100));
|
||||
|
||||
FINALIZE_GLOBAL_STORAGE_CLASS
|
||||
resource = nullptr;
|
||||
|
||||
END_GLOBAL_STORAGE_CLASS(MyGlobalStorage)
|
||||
```
|
||||
|
||||
**Implementation details:**
|
||||
- **Class definition**: `BEGIN_GLOBAL_STORAGE_CLASS` defines a class to contain resources
|
||||
- **Resource fields**: Declare global resources as class members
|
||||
- **Initialization block**: `INITIALIZE_GLOBAL_STORAGE_CLASS` contains setup code
|
||||
- **Finalization block**: `FINALIZE_GLOBAL_STORAGE_CLASS` contains cleanup code
|
||||
- **Access function**: Automatically generates `GetMyGlobalStorage()` function
|
||||
|
||||
### Accessing Global Storage
|
||||
|
||||
Use the generated access function to interact with global storage:
|
||||
|
||||
```cpp
|
||||
// Check if global storage is available
|
||||
if (GetMyGlobalStorage().IsInitialized())
|
||||
{
|
||||
// Access the resource
|
||||
auto value = GetMyGlobalStorage().resource;
|
||||
}
|
||||
```
|
||||
|
||||
**Access patterns:**
|
||||
- **Availability checking**: Use `IsInitialized()` to verify storage state
|
||||
- **Resource access**: Direct member access through the storage object
|
||||
- **Post-finalization**: Returns `false` after `FinalizeGlobalStorage()` is called
|
||||
- **Thread safety**: No automatic synchronization - add locks if needed
|
||||
|
||||
## Integration with Applications
|
||||
|
||||
### Unit Testing Integration
|
||||
|
||||
Global storage integrates with the unit testing framework:
|
||||
|
||||
```cpp
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int result = unittest::UnitTest::RunAndDisposeTests(argc, argv);
|
||||
vl::FinalizeGlobalStorage();
|
||||
#ifdef VCZH_CHECK_MEMORY_LEAKS
|
||||
_CrtDumpMemoryLeaks();
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
**Testing considerations:**
|
||||
- **Test isolation**: Global storage is shared across all tests
|
||||
- **Cleanup responsibility**: Tests should not assume global storage state
|
||||
- **Resource lifecycle**: Storage is finalized after all tests complete
|
||||
- **Memory validation**: Leak detection occurs after global storage cleanup
|
||||
|
||||
### Application Lifecycle
|
||||
|
||||
The typical application lifecycle with global storage:
|
||||
|
||||
1. **Application start**: Global storage is uninitialized
|
||||
2. **First access**: Storage is automatically initialized on first use
|
||||
3. **Normal operation**: Resources are accessible through storage object
|
||||
4. **Pre-shutdown**: Call `FinalizeGlobalStorage()` explicitly
|
||||
5. **Leak detection**: Run memory leak detection tools
|
||||
6. **Application exit**: Normal termination
|
||||
|
||||
## Best Practices
|
||||
|
||||
### When to Use Global Storage
|
||||
|
||||
**Appropriate scenarios:**
|
||||
- Global resources that cannot be managed directly in `main`
|
||||
- Application-wide caches or registries
|
||||
- Cross-module shared resources
|
||||
- Legacy code integration where direct management is impractical
|
||||
|
||||
**Inappropriate scenarios:**
|
||||
- Simple global variables that can be managed directly
|
||||
- Resources with clear ownership by specific modules
|
||||
- Temporary or short-lived data
|
||||
- Thread-local resources
|
||||
|
||||
### Resource Management
|
||||
|
||||
**Initialization practices:**
|
||||
- Initialize resources to valid states in `INITIALIZE_GLOBAL_STORAGE_CLASS`
|
||||
- Use smart pointers (`Ptr<T>`) for automatic reference counting
|
||||
- Avoid throwing exceptions during initialization
|
||||
- Keep initialization code simple and deterministic
|
||||
|
||||
**Finalization practices:**
|
||||
- Explicitly reset smart pointers to `nullptr` in `FINALIZE_GLOBAL_STORAGE_CLASS`
|
||||
- Release external resources (files, network connections, etc.)
|
||||
- Avoid complex operations that might fail during shutdown
|
||||
- Ensure proper cleanup order for interdependent resources
|
||||
|
||||
### Memory Management Integration
|
||||
|
||||
**With Ptr<T> smart pointers:**
|
||||
```cpp
|
||||
BEGIN_GLOBAL_STORAGE_CLASS(ResourceStorage)
|
||||
Ptr<SomeObject> sharedResource;
|
||||
|
||||
INITIALIZE_GLOBAL_STORAGE_CLASS
|
||||
sharedResource = Ptr(new SomeObject());
|
||||
|
||||
FINALIZE_GLOBAL_STORAGE_CLASS
|
||||
sharedResource = nullptr; // Automatic cleanup via reference counting
|
||||
|
||||
END_GLOBAL_STORAGE_CLASS(ResourceStorage)
|
||||
```
|
||||
|
||||
**With raw pointers (avoid when possible):**
|
||||
```cpp
|
||||
BEGIN_GLOBAL_STORAGE_CLASS(LegacyStorage)
|
||||
SomeObject* legacyResource;
|
||||
|
||||
INITIALIZE_GLOBAL_STORAGE_CLASS
|
||||
legacyResource = new SomeObject();
|
||||
|
||||
FINALIZE_GLOBAL_STORAGE_CLASS
|
||||
delete legacyResource;
|
||||
legacyResource = nullptr;
|
||||
|
||||
END_GLOBAL_STORAGE_CLASS(LegacyStorage)
|
||||
```
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Platform Considerations
|
||||
|
||||
**Windows-specific features:**
|
||||
- `_CrtDumpMemoryLeaks()` is a Microsoft Visual C++ runtime function
|
||||
- Memory leak detection is primarily designed for debug builds
|
||||
- Release builds may not include leak detection overhead
|
||||
- The `VCZH_MSVC` macro guards Windows-specific code paths
|
||||
|
||||
**Cross-platform compatibility:**
|
||||
- Global storage system works on all platforms
|
||||
- Memory leak detection is Windows-only
|
||||
- Other platforms may use different debugging tools
|
||||
- Application structure remains consistent across platforms
|
||||
|
||||
### Performance Implications
|
||||
|
||||
**Runtime overhead:**
|
||||
- Global storage initialization occurs on first access (lazy initialization)
|
||||
- `IsInitialized()` checks have minimal performance impact
|
||||
- Access through storage objects is essentially direct member access
|
||||
- Finalization is a one-time operation during shutdown
|
||||
|
||||
**Memory overhead:**
|
||||
- Storage objects themselves use minimal memory
|
||||
- Primary memory usage comes from stored resources
|
||||
- No additional indirection beyond the storage object access
|
||||
- Smart pointer overhead is minimal for reference counting
|
||||
|
||||
### Advanced Usage Patterns
|
||||
|
||||
**Multiple storage objects:**
|
||||
```cpp
|
||||
BEGIN_GLOBAL_STORAGE_CLASS(UIStorage)
|
||||
Ptr<FontManager> fontManager;
|
||||
Ptr<ImageCache> imageCache;
|
||||
// ... UI-related resources
|
||||
END_GLOBAL_STORAGE_CLASS(UIStorage)
|
||||
|
||||
BEGIN_GLOBAL_STORAGE_CLASS(NetworkStorage)
|
||||
Ptr<ConnectionPool> connectionPool;
|
||||
Ptr<CertificateStore> certificates;
|
||||
// ... network-related resources
|
||||
END_GLOBAL_STORAGE_CLASS(NetworkStorage)
|
||||
```
|
||||
|
||||
**Dependency management:**
|
||||
```cpp
|
||||
BEGIN_GLOBAL_STORAGE_CLASS(DependentStorage)
|
||||
Ptr<Database> database;
|
||||
Ptr<CacheManager> cache;
|
||||
|
||||
INITIALIZE_GLOBAL_STORAGE_CLASS
|
||||
database = Ptr(new Database());
|
||||
cache = Ptr(new CacheManager(database)); // Cache depends on database
|
||||
|
||||
FINALIZE_GLOBAL_STORAGE_CLASS
|
||||
cache = nullptr; // Clean up dependent resource first
|
||||
database = nullptr; // Clean up dependency second
|
||||
|
||||
END_GLOBAL_STORAGE_CLASS(DependentStorage)
|
||||
```
|
||||
|
||||
### Integration with Other Systems
|
||||
|
||||
**With VlppOS threading:**
|
||||
```cpp
|
||||
BEGIN_GLOBAL_STORAGE_CLASS(ThreadSafeStorage)
|
||||
CriticalSection lock;
|
||||
Ptr<SharedResource> resource;
|
||||
|
||||
INITIALIZE_GLOBAL_STORAGE_CLASS
|
||||
resource = Ptr(new SharedResource());
|
||||
|
||||
FINALIZE_GLOBAL_STORAGE_CLASS
|
||||
CS_LOCK(lock)
|
||||
{
|
||||
resource = nullptr;
|
||||
}
|
||||
|
||||
END_GLOBAL_STORAGE_CLASS(ThreadSafeStorage)
|
||||
```
|
||||
|
||||
**Error handling during initialization:**
|
||||
```cpp
|
||||
BEGIN_GLOBAL_STORAGE_CLASS(SafeStorage)
|
||||
Ptr<Resource> resource;
|
||||
bool initializationFailed;
|
||||
|
||||
INITIALIZE_GLOBAL_STORAGE_CLASS
|
||||
initializationFailed = false;
|
||||
try
|
||||
{
|
||||
resource = Ptr(new Resource());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
initializationFailed = true;
|
||||
resource = nullptr;
|
||||
}
|
||||
|
||||
FINALIZE_GLOBAL_STORAGE_CLASS
|
||||
resource = nullptr;
|
||||
|
||||
END_GLOBAL_STORAGE_CLASS(SafeStorage)
|
||||
```
|
||||
|
||||
### Debugging and Troubleshooting
|
||||
|
||||
**Common issues:**
|
||||
- Forgetting to call `FinalizeGlobalStorage()` before leak detection
|
||||
- Accessing global storage after finalization
|
||||
- Circular dependencies between global storage objects
|
||||
- Exception handling during initialization or finalization
|
||||
|
||||
**Debugging techniques:**
|
||||
- Use `IsInitialized()` to verify storage state at runtime
|
||||
- Add logging to initialization and finalization blocks
|
||||
- Use debugger breakpoints in storage lifecycle methods
|
||||
- Monitor resource creation and destruction patterns
|
||||
|
||||
**Memory leak investigation:**
|
||||
- Ensure all `Ptr<T>` references are properly reset to `nullptr`
|
||||
- Check for circular references that prevent automatic cleanup
|
||||
- Verify that external resources (files, handles) are properly released
|
||||
- Use memory profiling tools to identify actual leak sources
|
||||
249
.github/KnowledgeBase/KB_Vlpp_ObjectModel.md
vendored
Normal file
249
.github/KnowledgeBase/KB_Vlpp_ObjectModel.md
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
# Object Model and Memory Management
|
||||
|
||||
Reference counting smart pointers and object lifecycle management following specific inheritance patterns.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
Always use `struct` for value types and `class` for reference types.
|
||||
All reference types must inherits from `Object` or other reference types.
|
||||
All interface types must virtual inherits from `Interface` or other interface types.
|
||||
A reference type must virtual inherits an interface type to implement it.
|
||||
|
||||
## Ptr<T> - Smart Pointer
|
||||
|
||||
Prefer `Ptr<T>` to hold an initialized reference type instead of using C++ pointers, e.g. `auto x = Ptr(new X(...));`.
|
||||
`Ptr<T>` is similar to `std::shared_ptr<T>`.
|
||||
There is no `std::weak_ptr<T>` equivalent constructions, use raw C++ pointers in such cases, but you should try your best to avoid it.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```cpp
|
||||
// Creating a Ptr
|
||||
auto obj = Ptr(new MyClass(...));
|
||||
|
||||
// Assignment and copying
|
||||
Ptr<MyClass> another = obj; // Reference counting
|
||||
|
||||
// Checking if empty
|
||||
if (obj) {
|
||||
// Ptr is not empty
|
||||
}
|
||||
if (obj == nullptr) {
|
||||
// Ptr is empty
|
||||
}
|
||||
```
|
||||
|
||||
### Type Conversion
|
||||
|
||||
If `T*` could be implicitly converted to `U*`, `Ptr<U>` could be initialized with `Ptr<T>`.
|
||||
|
||||
If `T*` could be `dynamic_cast` to `U*`, use `Cast<U>()` method instead.
|
||||
|
||||
```cpp
|
||||
Ptr<BaseClass> base = Ptr(new DerivedClass(...));
|
||||
Ptr<DerivedClass> derived = base.Cast<DerivedClass>();
|
||||
```
|
||||
|
||||
### Resetting
|
||||
|
||||
To reset a `Ptr<T>`, assign it with `{}` or `nullptr`.
|
||||
|
||||
```cpp
|
||||
obj = {}; // Reset to empty
|
||||
obj = nullptr; // Reset to empty
|
||||
```
|
||||
|
||||
## ComPtr<T> - COM Object Pointer
|
||||
|
||||
`ComPtr<T>` is similar to `Ptr<T>` but it is for COM objects with Windows API only.
|
||||
|
||||
```cpp
|
||||
ComPtr<IUnknown> comObj = ...;
|
||||
```
|
||||
|
||||
## Nullable<T> - Optional Value Types
|
||||
|
||||
`Nullable<T>` adds `nullptr` semantics to value types. `Nullable<T>` can be assigned with `T`, it becomes non-empty, otherwise it is empty.
|
||||
|
||||
### Basic Operations
|
||||
|
||||
```cpp
|
||||
Nullable<vint> value; // Empty by default
|
||||
value = 42; // Now contains 42
|
||||
|
||||
// Checking if empty
|
||||
if (value) {
|
||||
// Has value
|
||||
vint actual = value.Value();
|
||||
}
|
||||
|
||||
// Resetting to empty
|
||||
value.Reset();
|
||||
|
||||
// Comparison
|
||||
Nullable<vint> other = 42;
|
||||
if (value == other) {
|
||||
// Equal comparison
|
||||
}
|
||||
```
|
||||
|
||||
### Key Methods
|
||||
|
||||
- **`Reset()`**: Makes the nullable empty
|
||||
- **`operator bool`**: Returns true if non-empty
|
||||
- **`Value()`**: Returns the contained value (only call if non-empty)
|
||||
|
||||
The `Value()` method can only be called if you are sure it is non-empty, and it returns the value inside it. `Value()` returns a immutable value, you can't change any data inside value, but you can assign it with a new value.
|
||||
|
||||
A `Nullable<T>` can be compared with another one in the same type in the standard C++ way.
|
||||
|
||||
## Inheritance Patterns
|
||||
|
||||
### Reference Types (class)
|
||||
|
||||
```cpp
|
||||
class MyRefType : public Object
|
||||
{
|
||||
// Reference type implementation
|
||||
};
|
||||
```
|
||||
|
||||
### Interface Types (class)
|
||||
|
||||
```cpp
|
||||
class IMyInterface : public virtual Interface
|
||||
{
|
||||
public:
|
||||
virtual void DoSomething() = 0;
|
||||
};
|
||||
```
|
||||
|
||||
### Implementing Interfaces
|
||||
|
||||
```cpp
|
||||
class MyImplementation : public Object, public virtual IMyInterface
|
||||
{
|
||||
public:
|
||||
void DoSomething() override
|
||||
{
|
||||
// Implementation
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Multiple Interface Inheritance
|
||||
|
||||
```cpp
|
||||
class IAdvancedInterface : public virtual IMyInterface
|
||||
{
|
||||
public:
|
||||
virtual void DoAdvancedThing() = 0;
|
||||
};
|
||||
```
|
||||
|
||||
## Special Cases
|
||||
|
||||
### Exception Classes
|
||||
|
||||
Use `class` for defining new `Error` or `Exception` sub classes, although they are value types.
|
||||
|
||||
```cpp
|
||||
class MyCustomError : public Error
|
||||
{
|
||||
public:
|
||||
MyCustomError(const WString& message) : Error(message) {}
|
||||
};
|
||||
```
|
||||
|
||||
### Function and Event Types
|
||||
|
||||
`Func<F>` and `Event<F>` are also classes, although they are value types.
|
||||
|
||||
### Collection Types
|
||||
|
||||
Collection types are also value types, although they implements `IEnumerable<T>` and `IEnumerator<T>`.
|
||||
This is also a reason we always use references instead of pointers on `IEnumerable<T>` and `IEnumerator<T>`.
|
||||
|
||||
### Struct in Ptr<T>
|
||||
|
||||
When really necessary, a struct could be used in `Ptr<T>` for sharing. But prefer `Nullable<T>` when `nullptr` is helpful but sharing is not necessary.
|
||||
|
||||
```cpp
|
||||
struct SharedData : public Object
|
||||
{
|
||||
vint value;
|
||||
WString name;
|
||||
};
|
||||
|
||||
Ptr<SharedData> sharedPtr = Ptr(new SharedData{42, L"Hello"});
|
||||
```
|
||||
|
||||
## Memory Management Best Practices
|
||||
|
||||
### Reference Counting
|
||||
|
||||
- `Ptr<T>` uses reference counting for automatic memory management
|
||||
- Objects are automatically deleted when the last `Ptr<T>` is destroyed
|
||||
- No need for manual memory management in most cases
|
||||
|
||||
### Avoiding Circular References
|
||||
|
||||
Since there's no weak pointer equivalent, be careful of circular references:
|
||||
|
||||
```cpp
|
||||
// Potential circular reference problem
|
||||
class Parent : public Object
|
||||
{
|
||||
public:
|
||||
List<Ptr<Child>> children;
|
||||
};
|
||||
|
||||
class Child : public Object
|
||||
{
|
||||
public:
|
||||
Ptr<Parent> parent; // This can create cycles
|
||||
};
|
||||
```
|
||||
|
||||
Use raw pointers for back-references when appropriate:
|
||||
|
||||
```cpp
|
||||
class Child : public Object
|
||||
{
|
||||
public:
|
||||
Parent* parent; // Raw pointer to avoid cycles
|
||||
};
|
||||
```
|
||||
|
||||
### Value vs Reference Types
|
||||
|
||||
- **Use `struct`**: For value types (simple data containers, POD types)
|
||||
- **Use `class`**: For reference types (complex objects, polymorphic types)
|
||||
- **Inherit from `Object`**: All reference types
|
||||
- **Virtual inherit from `Interface`**: All interface types
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Thread Safety
|
||||
|
||||
`Ptr<T>` reference counting is thread-safe, meaning multiple threads can safely copy and destroy `Ptr<T>` instances. However, the pointed-to object itself is not automatically thread-safe.
|
||||
|
||||
### Performance Considerations
|
||||
|
||||
- Reference counting has overhead compared to raw pointers
|
||||
- `Ptr<T>` creation and destruction involve atomic operations
|
||||
- Consider using raw pointers for temporary references within a single function
|
||||
- Use `Nullable<T>` instead of `Ptr<T>` for value types that need null semantics
|
||||
|
||||
### Design Philosophy
|
||||
|
||||
The object model enforces clear distinctions between:
|
||||
- Value types (lightweight, copyable) vs Reference types (heavyweight, shared)
|
||||
- Concrete classes vs Interfaces
|
||||
- Automatic memory management vs Manual lifetime control
|
||||
|
||||
This design promotes:
|
||||
- Clear ownership semantics
|
||||
- Reduced memory leaks
|
||||
- Type safety
|
||||
- Polymorphic behavior through interfaces
|
||||
221
.github/KnowledgeBase/KB_Vlpp_PrimitiveTypes.md
vendored
Normal file
221
.github/KnowledgeBase/KB_Vlpp_PrimitiveTypes.md
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
# Vlpp Primitive Value Types
|
||||
|
||||
Container types for organizing and manipulating related data values in the Vlpp library.
|
||||
|
||||
## Nullable<T>
|
||||
|
||||
`Nullable<T>` adds `nullptr` to `T`. `Nullable<T>` can be assigned with `T`, it becomes non-empty, otherwise it is empty.
|
||||
|
||||
To reset a `Nullable<T>` to empty, use the `Reset()` method. To detect if it is empty, use the `operator bool` operator.
|
||||
|
||||
The `Value()` method can only be called if you are sure it is non-empty, and it returns the value inside it. `Value()` returns a immutable value, you can't change any data inside value, but you can assign it with a new value.
|
||||
|
||||
A `Nullable<T>` can be compared with another one in the same type in the standard C++ way.
|
||||
|
||||
### Usage Examples
|
||||
|
||||
```cpp
|
||||
// Create nullable integer
|
||||
Nullable<vint> nullableInt;
|
||||
if (!nullableInt) {
|
||||
// nullableInt is empty
|
||||
}
|
||||
|
||||
// Assign a value
|
||||
nullableInt = 42;
|
||||
if (nullableInt) {
|
||||
vint value = nullableInt.Value(); // value is 42
|
||||
}
|
||||
|
||||
// Reset to empty
|
||||
nullableInt.Reset();
|
||||
```
|
||||
|
||||
## Pair<Key, Value>
|
||||
|
||||
`Pair<K, V>` store a `K` key and a `V` value, it is just an easy way to represent a tuple of 2 values. Use `key` and `value` field to access these values.
|
||||
|
||||
`Pair(k, v)` could be used to initialize a pair without specifying data type, make the code more readable.
|
||||
|
||||
A `Pair<K, V>` can be compared with another one in the same type in the standard C++ way.
|
||||
|
||||
A `Pair<K, V>` can be used with structured binding.
|
||||
|
||||
### Usage Examples
|
||||
|
||||
```cpp
|
||||
// Create a pair with type inference
|
||||
auto pair = Pair(L"name", 25);
|
||||
|
||||
// Access fields
|
||||
WString name = pair.key;
|
||||
vint age = pair.value;
|
||||
|
||||
// Structured binding
|
||||
auto [personName, personAge] = pair;
|
||||
|
||||
// Comparison
|
||||
Pair<WString, vint> pair1(L"Alice", 30);
|
||||
Pair<WString, vint> pair2(L"Bob", 25);
|
||||
bool isEqual = (pair1 == pair2); // false
|
||||
```
|
||||
|
||||
### Structured Binding with Collections
|
||||
|
||||
You can convert an `IEnumerable<T>` to `IEnumerable<Pair<vint, T>>` using the `indexed` function, which is designed for `for(auto [index, x] : indexed(xs))` to iterate xs with an index. This is also an example of `Pair<K, V>` with structured binding.
|
||||
|
||||
```cpp
|
||||
List<WString> names;
|
||||
names.Add(L"Alice");
|
||||
names.Add(L"Bob");
|
||||
|
||||
// Iterate with index using structured binding
|
||||
for(auto [index, name] : indexed(names)) {
|
||||
// index is vint, name is WString
|
||||
Console::WriteLine(itow(index) + L": " + name);
|
||||
}
|
||||
```
|
||||
|
||||
## Tuple<T...>
|
||||
|
||||
`Tuple<...>` is an easy way to organize multiple values without defining a `struct`.
|
||||
|
||||
`Tuple(a, b, c...)` could be used to initialize a tuple without specifying data types, make the code more readable. Values cannot be changed in a tuple, but you can assign it with another tuple.
|
||||
|
||||
`get<0>` method can be used to access the first value. You can use any other index but it has to be a compiled time constant.
|
||||
|
||||
A `Tuple<...>` can be compared with another one in the same type in the standard C++ way.
|
||||
|
||||
A `Tuple<...>` can be used with structured binding.
|
||||
|
||||
### Usage Examples
|
||||
|
||||
```cpp
|
||||
// Create tuple with type inference
|
||||
auto tuple = Tuple(L"Alice", 25, true);
|
||||
|
||||
// Access elements using get<index>()
|
||||
WString name = tuple.get<0>();
|
||||
vint age = tuple.get<1>();
|
||||
bool isActive = tuple.get<2>();
|
||||
|
||||
// Structured binding
|
||||
auto [personName, personAge, active] = tuple;
|
||||
|
||||
// Comparison
|
||||
auto tuple1 = Tuple(L"Alice", 25, true);
|
||||
auto tuple2 = Tuple(L"Bob", 30, false);
|
||||
bool isEqual = (tuple1 == tuple2); // false
|
||||
|
||||
// Assignment
|
||||
auto tuple3 = tuple1; // Copy assignment
|
||||
```
|
||||
|
||||
## Variant<T...>
|
||||
|
||||
`Variant<T...>` represents any but only one value of different types. It must be initialized or assigned with a value, a `Variant<T...>` could not be empty.
|
||||
|
||||
If you really want a nullable variable, add `nullptr_t` to the type list instead of using `Nullable<Variant<...>>`.
|
||||
|
||||
Use the `Index()` method to know the type stored inside a `Variant<T...>`, the return value starting from 0.
|
||||
|
||||
Use the `Get<T>()` method to get the value from a `Variant<T...>` if you are sure about the type.
|
||||
|
||||
Use the `TryGet<T>()` method to get the pointer to the value from a `Variant<T...>`, when the type is not the value stored in it, it returns `nullptr`.
|
||||
|
||||
Use the `Apply` with a callback to read the value in a generic way. The callback must be a lambda expression that could handle all different types, usually a template lambda expression.
|
||||
|
||||
You can use `Overloading` with `Apply` to handle the value of different types implicitly.
|
||||
|
||||
The `TryApply` method is similar to `Apply`, but you don't have to handle every cases.
|
||||
|
||||
### Usage Examples
|
||||
|
||||
```cpp
|
||||
// Create variant that can hold string, int, or bool
|
||||
Variant<WString, vint, bool> value = L"Hello";
|
||||
|
||||
// Check which type is stored
|
||||
vint typeIndex = value.Index(); // 0 for WString, 1 for vint, 2 for bool
|
||||
|
||||
// Get value if you know the type
|
||||
if (value.Index() == 0) {
|
||||
WString str = value.Get<WString>();
|
||||
}
|
||||
|
||||
// Try to get value safely
|
||||
if (auto ptr = value.TryGet<WString>()) {
|
||||
WString str = *ptr;
|
||||
}
|
||||
|
||||
// Handle different types using Apply with Overloading
|
||||
value.Apply(Overloading(
|
||||
[](WString& str) {
|
||||
Console::WriteLine(L"String: " + str);
|
||||
},
|
||||
[](const vint& num) {
|
||||
Console::WriteLine(L"Number: " + itow(num));
|
||||
},
|
||||
[](bool flag) {
|
||||
Console::WriteLine(flag ? L"True" : L"False");
|
||||
}
|
||||
));
|
||||
|
||||
// Using TryApply - only handle some cases
|
||||
value.TryApply(Overloading(
|
||||
[](WString& str) {
|
||||
Console::WriteLine(L"Found string: " + str);
|
||||
}
|
||||
// Other types are ignored
|
||||
));
|
||||
|
||||
// Template lambda for generic handling
|
||||
value.Apply([](auto& val) {
|
||||
// Handle any type generically
|
||||
using T = std::decay_t<decltype(val)>;
|
||||
if constexpr (std::is_same_v<T, WString>) {
|
||||
Console::WriteLine(L"String type");
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Nullable Variant
|
||||
|
||||
For a variant that can also be empty, include `nullptr_t` in the type list:
|
||||
|
||||
```cpp
|
||||
Variant<WString, vint, bool, nullptr_t> nullableValue = nullptr;
|
||||
|
||||
if (nullableValue.Index() == 3) {
|
||||
// Value is null
|
||||
}
|
||||
|
||||
// Assign a real value
|
||||
nullableValue = L"Not null anymore";
|
||||
```
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Design Considerations
|
||||
|
||||
The primitive value types in Vlpp are designed to be:
|
||||
|
||||
1. **Immutable by design**: Values inside these containers cannot be modified directly, promoting safer code patterns
|
||||
2. **C++20 compatible**: Full support for structured binding and modern C++ features
|
||||
3. **Performance optimized**: Value types with minimal overhead compared to standard library equivalents
|
||||
4. **Type-safe**: Strong typing prevents common errors while providing flexibility
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Use type inference**: Prefer `auto` with `Pair()`, `Tuple()` constructors for cleaner code
|
||||
2. **Leverage structured binding**: Use `auto [a, b, c] = tuple` for readable unpacking
|
||||
3. **Handle all Variant cases**: When using `Apply()`, handle all possible types to avoid runtime issues
|
||||
4. **Prefer TryGet for safety**: Use `TryGet<T>()` instead of `Get<T>()` when type is uncertain
|
||||
5. **Use Nullable judiciously**: Only use `Nullable<T>` when `nullptr` semantics are truly needed
|
||||
|
||||
### Memory and Performance Notes
|
||||
|
||||
- All primitive value types are stack-allocated and have minimal memory overhead
|
||||
- `Variant<T...>` uses union-like storage, only occupying space for the largest type plus type information
|
||||
- Structured binding operations have zero runtime cost in optimized builds
|
||||
- Comparison operations use lexicographical ordering for `Pair` and `Tuple`
|
||||
236
.github/KnowledgeBase/KB_Vlpp_SortingOrdering.md
vendored
Normal file
236
.github/KnowledgeBase/KB_Vlpp_SortingOrdering.md
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
# Sorting and Ordering
|
||||
|
||||
## Overview
|
||||
|
||||
Vlpp provides algorithms for arranging data with support for both total and partial ordering relationships. The sorting system supports standard comparison operations using modern C++ comparison operators and handles cases where data cannot be fully ordered through specialized partial ordering processors.
|
||||
|
||||
## Quick Sort Implementation
|
||||
|
||||
### Sort(T*, vint) Function
|
||||
|
||||
The primary sorting function performs quick sort on raw pointer ranges with custom comparators.
|
||||
|
||||
```cpp
|
||||
// Sort an array of integers
|
||||
vint numbers[] = {5, 2, 8, 1, 9};
|
||||
Sort(numbers, 5, [](vint a, vint b) { return a <=> b; });
|
||||
```
|
||||
|
||||
**Function signature:**
|
||||
```cpp
|
||||
template<typename T, typename Compare>
|
||||
void Sort(T* begin, vint count, Compare compare);
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- **`T* begin`**: Pointer to the first element of the array to sort
|
||||
- **`vint count`**: Number of elements in the array
|
||||
- **`Compare compare`**: Lambda expression or function object for comparison
|
||||
|
||||
**Key characteristics:**
|
||||
- **In-place sorting**: Modifies the original array
|
||||
- **Quick sort algorithm**: Efficient O(n log n) average case performance
|
||||
- **Custom comparators**: Flexible comparison logic through lambda expressions
|
||||
|
||||
## Modern C++ Comparison Support
|
||||
|
||||
### Using the Spaceship Operator (<=>)
|
||||
|
||||
The sorting system leverages C++20's three-way comparison operator for standardized ordering.
|
||||
|
||||
```cpp
|
||||
// Using spaceship operator directly
|
||||
std::strong_ordering ordering = a <=> b;
|
||||
|
||||
// In sorting comparators
|
||||
Sort(data, count, [](const MyType& a, const MyType& b) {
|
||||
return a <=> b;
|
||||
});
|
||||
```
|
||||
|
||||
### Comparison Return Types
|
||||
|
||||
#### std::strong_ordering
|
||||
Use for types with total ordering where all elements can be compared:
|
||||
```cpp
|
||||
Sort(numbers, count, [](vint a, vint b) -> std::strong_ordering {
|
||||
return a <=> b; // Returns strong_ordering::less, equal, or greater
|
||||
});
|
||||
```
|
||||
|
||||
#### std::weak_ordering
|
||||
Use for types where equivalent elements may not be identical:
|
||||
```cpp
|
||||
Sort(strings, count, [](const WString& a, const WString& b) -> std::weak_ordering {
|
||||
return a.Compare(b) <=> 0; // Case-insensitive comparison
|
||||
});
|
||||
```
|
||||
|
||||
### Lambda Expression Comparators
|
||||
|
||||
Sorting relies on lambda expressions returning ordering values rather than boolean comparisons:
|
||||
|
||||
```cpp
|
||||
// Correct: Using spaceship operator
|
||||
Sort(items, count, [](const Item& a, const Item& b) {
|
||||
return a.priority <=> b.priority;
|
||||
});
|
||||
|
||||
// Multi-level sorting
|
||||
Sort(items, count, [](const Item& a, const Item& b) {
|
||||
if (auto cmp = a.category <=> b.category; cmp != 0)
|
||||
return cmp;
|
||||
return a.name <=> b.name;
|
||||
});
|
||||
```
|
||||
|
||||
## Partial Ordering Support
|
||||
|
||||
### PartialOrderingProcessor
|
||||
|
||||
For scenarios where not all elements can be compared (partial ordering), use `PartialOrderingProcessor` instead of the standard `Sort` function.
|
||||
|
||||
```cpp
|
||||
// Example: Dependency sorting where some items have no ordering relationship
|
||||
PartialOrderingProcessor<MyType> processor;
|
||||
|
||||
// Add items and their relationships
|
||||
processor.AddItem(item1);
|
||||
processor.AddItem(item2);
|
||||
processor.AddItem(item3);
|
||||
|
||||
// Define partial ordering relationships
|
||||
processor.AddDependency(item2, item1); // item2 depends on item1
|
||||
processor.AddDependency(item3, item1); // item3 depends on item1
|
||||
|
||||
// Process to get topologically sorted result
|
||||
auto sortedItems = processor.Process();
|
||||
```
|
||||
|
||||
**Use cases for partial ordering:**
|
||||
- **Dependency resolution**: When items have prerequisites
|
||||
- **Task scheduling**: When some tasks must complete before others
|
||||
- **Type hierarchies**: When comparing types with inheritance relationships
|
||||
- **Version constraints**: When some versions are incomparable
|
||||
|
||||
### When Sort Doesn't Work
|
||||
|
||||
The standard `Sort` function requires total ordering - every pair of elements must be comparable. Use `PartialOrderingProcessor` when:
|
||||
|
||||
- Some elements cannot be meaningfully compared
|
||||
- Circular dependencies need to be detected
|
||||
- Topological sorting is required
|
||||
- The comparison relationship is not transitive across all elements
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Choosing the Right Approach
|
||||
|
||||
1. **Use `Sort()` when:**
|
||||
- All elements have a clear total ordering
|
||||
- Performance is critical (quick sort is very efficient)
|
||||
- Working with simple data types (numbers, strings)
|
||||
|
||||
2. **Use `PartialOrderingProcessor` when:**
|
||||
- Not all elements can be compared
|
||||
- Dealing with dependency graphs
|
||||
- Need topological sorting
|
||||
- Circular dependency detection is important
|
||||
|
||||
### Comparator Best Practices
|
||||
|
||||
```cpp
|
||||
// Good: Clear, concise comparator
|
||||
Sort(items, count, [](const Item& a, const Item& b) {
|
||||
return a.value <=> b.value;
|
||||
});
|
||||
|
||||
// Good: Multi-level comparison with early exit
|
||||
Sort(items, count, [](const Item& a, const Item& b) {
|
||||
if (auto cmp = a.priority <=> b.priority; cmp != 0)
|
||||
return cmp;
|
||||
if (auto cmp = a.category <=> b.category; cmp != 0)
|
||||
return cmp;
|
||||
return a.name <=> b.name;
|
||||
});
|
||||
|
||||
// Good: Custom comparison logic
|
||||
Sort(items, count, [](const Item& a, const Item& b) {
|
||||
// Reverse order (descending)
|
||||
return b.value <=> a.value;
|
||||
});
|
||||
```
|
||||
|
||||
### Memory Considerations
|
||||
|
||||
- **In-place sorting**: `Sort()` modifies the original array, no additional memory allocation
|
||||
- **Pointer-based**: Works directly with memory addresses for efficiency
|
||||
- **Custom types**: Ensure your types implement efficient comparison operators
|
||||
|
||||
## Integration with Collections
|
||||
|
||||
### Sorting Collections
|
||||
|
||||
The sorting functions work with raw pointers, but can be used with collection types:
|
||||
|
||||
```cpp
|
||||
List<vint> numbers;
|
||||
numbers.Add(5);
|
||||
numbers.Add(2);
|
||||
numbers.Add(8);
|
||||
|
||||
// Sort the underlying array
|
||||
Sort(&numbers[0], numbers.Count(), [](vint a, vint b) { return a <=> b; });
|
||||
```
|
||||
|
||||
### SortedList<T> Alternative
|
||||
|
||||
For automatically maintained sorted collections, consider using `SortedList<T>` instead of manual sorting:
|
||||
|
||||
```cpp
|
||||
SortedList<vint> sortedNumbers;
|
||||
sortedNumbers.Add(5); // Automatically maintains order
|
||||
sortedNumbers.Add(2);
|
||||
sortedNumbers.Add(8);
|
||||
// Collection is always sorted
|
||||
```
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Performance Characteristics
|
||||
|
||||
- **Quick Sort**: Average O(n log n), worst case O(n²)
|
||||
- **In-place**: No additional memory allocation for sorting
|
||||
- **Comparison-based**: Performance depends on comparison function complexity
|
||||
- **Cache-friendly**: Works with contiguous memory arrays
|
||||
|
||||
### Thread Safety
|
||||
|
||||
The sorting functions are not thread-safe. For concurrent access:
|
||||
- Ensure exclusive access to the array being sorted
|
||||
- Use appropriate synchronization primitives from VlppOS
|
||||
- Consider sorting copies for read-heavy scenarios
|
||||
|
||||
### Custom Type Requirements
|
||||
|
||||
For custom types used in sorting:
|
||||
- Implement appropriate comparison operators (`<=>` recommended)
|
||||
- Ensure comparison is consistent and transitive
|
||||
- Consider providing both strong and weak ordering overloads as needed
|
||||
|
||||
### Debugging Comparators
|
||||
|
||||
When sorting doesn't work as expected:
|
||||
- Verify comparator returns consistent results for same inputs
|
||||
- Check for transitive property: if A < B and B < C, then A < C
|
||||
- Ensure strict weak ordering for equivalent elements
|
||||
- Test with simple cases first
|
||||
|
||||
```cpp
|
||||
// Debug comparator
|
||||
Sort(items, count, [](const Item& a, const Item& b) {
|
||||
auto result = a.value <=> b.value;
|
||||
// Add debug output if needed
|
||||
return result;
|
||||
});
|
||||
```
|
||||
204
.github/KnowledgeBase/KB_Vlpp_StringTypes.md
vendored
Normal file
204
.github/KnowledgeBase/KB_Vlpp_StringTypes.md
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
# String Types and Handling
|
||||
|
||||
## Overview
|
||||
|
||||
Vlpp provides immutable string types for text processing across different encodings with UTF conversion capabilities. The project prefers `wchar_t` over other char types and uses immutable string types instead of standard C++ string types. The string system offers smart pointer-like memory management, UTF encoding conversion, and locale-aware operations.
|
||||
|
||||
## Core String Types
|
||||
|
||||
### ObjectString<T>
|
||||
|
||||
`ObjectString<T>` is an immutable string template that serves as the foundation for all string types. It cannot be modified after initialization - any updating operations result in a new string value being returned.
|
||||
|
||||
**Key characteristics:**
|
||||
- **Immutable**: Cannot be modified after creation
|
||||
- **Memory managed**: Automatic memory management with reference counting
|
||||
- **Template-based**: Works with different character types
|
||||
- **Cross-platform**: Handles platform differences (UTF-16 on Windows, UTF-32 on other platforms for wchar_t)
|
||||
|
||||
### Specific String Type Aliases
|
||||
|
||||
The project provides convenient aliases instead of using `ObjectString<T>` directly:
|
||||
|
||||
- **`WString`**: Wide character string (`ObjectString<wchar_t>`) - **preferred for general use**
|
||||
- UTF-16 on Windows, UTF-32 on other platforms
|
||||
- **`AString`**: ASCII string (`ObjectString<char>`) - for ASCII operations
|
||||
- **`U8String`**: UTF-8 string (`ObjectString<char8_t>`) - for UTF-8 handling
|
||||
- **`U16String`**: UTF-16 string (`ObjectString<char16_t>`) - for explicit UTF-16
|
||||
- **`U32String`**: UTF-32 string (`ObjectString<char32_t>`) - for explicit UTF-32
|
||||
|
||||
**Always use aliases instead of `ObjectString<T>` directly.**
|
||||
|
||||
## String Initialization
|
||||
|
||||
### Static Initialization Functions
|
||||
|
||||
Use these static functions to create string instances:
|
||||
|
||||
#### Unmanaged(L"string-literal")
|
||||
```cpp
|
||||
auto str = WString::Unmanaged(L"Hello World");
|
||||
```
|
||||
- **Use case**: String literals only
|
||||
- **Performance**: Zero-copy for string literals
|
||||
- **Safety**: Only works with compile-time string literals
|
||||
|
||||
#### CopyFrom(wchar_t*, vint)
|
||||
```cpp
|
||||
wchar_t* buffer = GetSomeBuffer();
|
||||
vint length = GetBufferLength();
|
||||
auto str = WString::CopyFrom(buffer, length);
|
||||
```
|
||||
- **Use case**: Copy from existing buffer when you have the length
|
||||
- **Memory**: Creates a copy of the data
|
||||
- **Safety**: Safe - creates independent copy
|
||||
|
||||
#### Constructor from wchar_t*
|
||||
```cpp
|
||||
wchar_t* buffer = GetSomeBuffer();
|
||||
WString str(buffer); // Automatically determines length and copies
|
||||
```
|
||||
- **Use case**: Copy from null-terminated buffer when length is unknown
|
||||
- **Memory**: Creates a copy of the data
|
||||
|
||||
#### TakeOver(wchar_t*, vint)
|
||||
```cpp
|
||||
wchar_t* buffer = new wchar_t[100];
|
||||
// ... fill buffer ...
|
||||
auto str = WString::TakeOver(buffer, actualLength);
|
||||
// buffer is now owned by str, will be deleted automatically
|
||||
```
|
||||
- **Use case**: Take ownership of a dynamically allocated buffer
|
||||
- **Memory**: Takes ownership, `delete[]` called automatically
|
||||
- **Warning**: Buffer must be allocated with `new[]`
|
||||
|
||||
## String to Number Conversion
|
||||
|
||||
### String to Integer
|
||||
- **`wtoi(WString)`**: Convert to `vint` (signed integer)
|
||||
- **`wtoi64(WString)`**: Convert to `vint64_t` (64-bit signed integer)
|
||||
- **`wtou(WString)`**: Convert to `vuint` (unsigned integer)
|
||||
- **`wtou64(WString)`**: Convert to `vuint64_t` (64-bit unsigned integer)
|
||||
|
||||
### String to Floating Point
|
||||
- **`wtof(WString)`**: Convert to `double`
|
||||
|
||||
### Examples
|
||||
```cpp
|
||||
WString numberStr = L"12345";
|
||||
vint value = wtoi(numberStr);
|
||||
double floatValue = wtof(L"123.45");
|
||||
```
|
||||
|
||||
## Number to String Conversion
|
||||
|
||||
### Integer to String
|
||||
- **`itow(vint)`**: Convert signed integer to `WString`
|
||||
- **`i64tow(vint64_t)`**: Convert 64-bit signed integer to `WString`
|
||||
- **`utow(vuint)`**: Convert unsigned integer to `WString`
|
||||
- **`u64tow(vuint64_t)`**: Convert 64-bit unsigned integer to `WString`
|
||||
|
||||
### Floating Point to String
|
||||
- **`ftow(double)`**: Convert double to `WString`
|
||||
|
||||
### Examples
|
||||
```cpp
|
||||
vint number = 12345;
|
||||
WString str = itow(number); // "12345"
|
||||
double value = 123.45;
|
||||
WString floatStr = ftow(value); // "123.45"
|
||||
```
|
||||
|
||||
## Case Conversion
|
||||
|
||||
### Case Transformation Functions
|
||||
- **`wupper(WString)`**: Convert all letters to uppercase
|
||||
- **`wlower(WString)`**: Convert all letters to lowercase
|
||||
|
||||
### Examples
|
||||
```cpp
|
||||
WString original = L"Hello World";
|
||||
WString upper = wupper(original); // "HELLO WORLD"
|
||||
WString lower = wlower(original); // "hello world"
|
||||
```
|
||||
|
||||
## UTF Encoding Conversion
|
||||
|
||||
### Template-Based Conversion
|
||||
|
||||
#### ConvertUtfString<From, To>
|
||||
```cpp
|
||||
WString wideStr = L"Hello ??";
|
||||
U8String utf8Str = ConvertUtfString<wchar_t, char8_t>(wideStr);
|
||||
U16String utf16Str = ConvertUtfString<wchar_t, char16_t>(wideStr);
|
||||
```
|
||||
- **Use case**: Template-based conversion when you don't know exact types
|
||||
- **Template parameters**: `From` and `To` are char types (`wchar_t`, `char8_t`, `char16_t`, `char32_t`)
|
||||
|
||||
### Direct Conversion Functions (AtoB pattern)
|
||||
|
||||
Where `A` and `B` can be:
|
||||
- **`w`**: `WString` (wide string)
|
||||
- **`u8`**: `U8String` (UTF-8)
|
||||
- **`u16`**: `U16String` (UTF-16)
|
||||
- **`u32`**: `U32String` (UTF-32)
|
||||
- **`a`**: `AString` (ASCII)
|
||||
|
||||
#### Common Conversion Functions
|
||||
```cpp
|
||||
// Wide string conversions
|
||||
U8String utf8 = wtou8(wideStr); // WString to UTF-8
|
||||
U16String utf16 = wtou16(wideStr); // WString to UTF-16
|
||||
U32String utf32 = wtou32(wideStr); // WString to UTF-32
|
||||
AString ascii = wtoa(wideStr); // WString to ASCII
|
||||
|
||||
// Back to wide string
|
||||
WString fromUtf8 = u8tow(utf8); // UTF-8 to WString
|
||||
WString fromUtf16 = u16tow(utf16); // UTF-16 to WString
|
||||
WString fromUtf32 = u32tow(utf32); // UTF-32 to WString
|
||||
WString fromAscii = atow(ascii); // ASCII to WString
|
||||
|
||||
// Between UTF encodings
|
||||
U16String u8to16 = u8tou16(utf8); // UTF-8 to UTF-16
|
||||
U8String u16to8 = u16tou8(utf16); // UTF-16 to UTF-8
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Character Type Preferences
|
||||
1. **Always prefer `wchar_t` and `WString`** for general text processing
|
||||
2. **Use `char8_t` and `U8String`** for UTF-8 when specifically needed
|
||||
3. **Avoid `char` and `std::string`** - use project's string types instead
|
||||
|
||||
### Initialization Best Practices
|
||||
1. **Use `WString::Unmanaged(L"...")`** for string literals
|
||||
2. **Use constructor or `CopyFrom`** when you need to copy external data
|
||||
3. **Use `TakeOver`** only when you want to transfer ownership of allocated memory
|
||||
|
||||
### Conversion Best Practices
|
||||
1. **Use direct `AtoB` functions** when you know the specific encodings
|
||||
2. **Use `ConvertUtfString<From, To>`** in template code or when types are variable
|
||||
3. **Cache conversion results** when the same conversion is used multiple times
|
||||
|
||||
### Memory Management
|
||||
1. **Strings are immutable** - operations create new string instances
|
||||
2. **Automatic memory management** - no need to manually free string memory
|
||||
3. **Reference counting** - efficient copying and assignment
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Platform Differences
|
||||
The `wchar_t` type behaves differently across platforms:
|
||||
- **Windows**: `wchar_t` is 16-bit (UTF-16)
|
||||
- **Linux/Unix**: `wchar_t` is 32-bit (UTF-32)
|
||||
|
||||
The string conversion system automatically handles these differences internally.
|
||||
|
||||
### Performance Considerations
|
||||
- **String literals with `Unmanaged`**: Zero-copy initialization
|
||||
- **Immutable strings**: Thread-safe sharing but creates new instances for modifications
|
||||
- **UTF conversions**: May involve memory allocation and encoding conversion overhead
|
||||
- **Case conversions**: Create new string instances rather than modifying in-place
|
||||
|
||||
### Thread Safety
|
||||
All string types are immutable and thread-safe for reading. Multiple threads can safely access the same string instance simultaneously. However, string creation and conversion operations may allocate memory and should be considered for performance in highly concurrent scenarios.
|
||||
171
.github/KnowledgeBase/KB_Vlpp_UnitTesting.md
vendored
Normal file
171
.github/KnowledgeBase/KB_Vlpp_UnitTesting.md
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
# Unit Testing Framework
|
||||
|
||||
Testing infrastructure with hierarchical test organization and assertion capabilities.
|
||||
|
||||
## Basic Test Structure
|
||||
|
||||
The unit testing framework in Vlpp provides a hierarchical structure for organizing and executing tests with assertion capabilities. All tests are written in C++ using specific macros that define the test structure.
|
||||
|
||||
### Writing Test Code
|
||||
|
||||
```C++
|
||||
using namespace vl;
|
||||
using namespace vl::unittest;
|
||||
|
||||
TEST_FILE
|
||||
{
|
||||
TEST_CASE(L"TOPIC-NAME")
|
||||
{
|
||||
TEST_ASSERT(EXPRESSION-TO-VERIFY);
|
||||
});
|
||||
|
||||
TEST_CATEGORY(L"CATEGORY-NAME")
|
||||
{
|
||||
TEST_CASE(L"TOPIC-NAME")
|
||||
{
|
||||
TEST_ASSERT(EXPRESSION-TO-VERIFY);
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
If a test case contains only one call to `TEST_ASSERT`, it can be simplified to `TEST_CASE_ASSERT(EXPRESSION-TO-VERIFY);`. The `TOPIC-NAME` becomes the condition.
|
||||
|
||||
## Hierarchical Organization
|
||||
|
||||
### TEST_FILE
|
||||
|
||||
`TEST_FILE` defines the test file scope and serves as the root container for all test cases and categories within a source file. There can be only one `TEST_FILE` per source file.
|
||||
|
||||
### TEST_CATEGORY
|
||||
|
||||
`TEST_CATEGORY(L"CATEGORY-NAME")` groups related tests under a descriptive category name. Key characteristics:
|
||||
|
||||
- Multiple `TEST_CATEGORY` blocks can exist within `TEST_FILE` or nested within other `TEST_CATEGORY` blocks
|
||||
- Categories can be nested inside other categories for hierarchical organization
|
||||
- Categories help organize tests logically by functionality or feature area
|
||||
- Category names should be descriptive and use wide character string literals
|
||||
|
||||
### TEST_CASE
|
||||
|
||||
`TEST_CASE(L"TOPIC-NAME")` defines individual test implementations. Key characteristics:
|
||||
|
||||
- Multiple `TEST_CASE` blocks can exist within `TEST_FILE` or `TEST_CATEGORY`
|
||||
- Test cases cannot be nested inside other test cases
|
||||
- Each test case should focus on testing a specific behavior or functionality
|
||||
- Test case names should clearly describe what is being tested
|
||||
|
||||
## Test Assertions
|
||||
|
||||
### TEST_ASSERT
|
||||
|
||||
`TEST_ASSERT(EXPRESSION-TO-VERIFY)` performs test assertions within test cases. Key characteristics:
|
||||
|
||||
- Can only appear inside `TEST_CASE` blocks
|
||||
- Takes a boolean expression that should evaluate to true for the test to pass
|
||||
- When the expression evaluates to false, the test fails and reports the failure
|
||||
- Multiple assertions can be used within a single test case
|
||||
|
||||
## Test Execution Integration
|
||||
|
||||
### Integration with Main Function
|
||||
|
||||
Tests are typically executed through the main function using the unit test runner:
|
||||
|
||||
```C++
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int result = unittest::UnitTest::RunAndDisposeTests(argc, argv);
|
||||
vl::FinalizeGlobalStorage();
|
||||
#ifdef VCZH_CHECK_MEMORY_LEAKS
|
||||
_CrtDumpMemoryLeaks();
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### Command Line Options
|
||||
|
||||
The unit test framework supports various command line options for controlling test execution:
|
||||
|
||||
- `/D`: Disable failure suppression (for debugging)
|
||||
- `/R`: Enable failure suppression (for release mode)
|
||||
- `/F:TestFile`: Run only specific test files
|
||||
|
||||
### Test Output and Reporting
|
||||
|
||||
The framework provides colored console output to distinguish between different types of messages:
|
||||
- Error messages (test failures)
|
||||
- Info messages (general information)
|
||||
- File messages (test file names)
|
||||
- Category messages (test category names)
|
||||
- Case messages (test case names)
|
||||
|
||||
## Extra Content
|
||||
|
||||
### Test Registration
|
||||
|
||||
Tests are automatically registered using a global registration system. Each test file is linked into a global list through the `UnitTestLink` structure, which contains:
|
||||
- The source file name for identification
|
||||
- A function pointer to the test procedure
|
||||
- A pointer to the next test file in the linked list
|
||||
|
||||
### Error Handling and Exception Safety
|
||||
|
||||
The unit testing framework includes comprehensive error handling:
|
||||
|
||||
**Assertion Errors**: When `TEST_ASSERT` fails, it throws a `UnitTestAssertError` containing the failure message.
|
||||
|
||||
**Configuration Errors**: Invalid test structure (like using `TEST_ASSERT` outside `TEST_CASE`) throws `UnitTestConfigError`.
|
||||
|
||||
**Exception Suppression**: The framework can suppress various types of exceptions during test execution:
|
||||
- C++ exceptions (`Error`, `Exception`, and standard exceptions)
|
||||
- System exceptions (on Windows using `__try/__except`)
|
||||
- Unknown exceptions
|
||||
|
||||
### Test Context Management
|
||||
|
||||
The framework maintains a hierarchical context during test execution:
|
||||
- **File Context**: Tracks the current test file being executed
|
||||
- **Category Context**: Tracks nested test categories
|
||||
- **Case Context**: Tracks individual test cases
|
||||
- **Failure Tracking**: Each context level tracks whether failures occurred
|
||||
|
||||
### Statistics Tracking
|
||||
|
||||
The framework maintains comprehensive statistics:
|
||||
- Total number of test files executed
|
||||
- Number of test files that passed
|
||||
- Total number of test cases executed
|
||||
- Number of test cases that passed
|
||||
- Overall pass/fail status
|
||||
|
||||
### Debugger Integration
|
||||
|
||||
The framework automatically detects whether a debugger is attached:
|
||||
- When debugging, failures are not suppressed by default (to break into debugger)
|
||||
- When running normally, failures are suppressed to continue execution
|
||||
- This behavior can be overridden with command line options
|
||||
|
||||
### Memory Leak Detection Integration
|
||||
|
||||
The unit testing framework integrates with the memory leak detection system:
|
||||
- Tests should call `FinalizeGlobalStorage()` before checking for memory leaks
|
||||
- This ensures proper cleanup of global storage objects before leak detection
|
||||
- On Windows with MSVC, `_CrtDumpMemoryLeaks()` can be called after finalization
|
||||
|
||||
### Cross-Platform Considerations
|
||||
|
||||
The framework is designed to work across platforms:
|
||||
- Console color support varies by platform
|
||||
- Exception handling mechanisms may differ (Windows has additional __try/__except support)
|
||||
- File path handling respects platform-specific path separators when filtering tests by file name
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Descriptive Names**: Use clear, descriptive names for test categories and cases
|
||||
2. **Single Responsibility**: Each test case should test one specific behavior
|
||||
3. **Assertion Clarity**: Write assertions that clearly indicate what is being tested
|
||||
4. **Hierarchical Organization**: Use categories to group related tests logically
|
||||
5. **Error Context**: When tests fail, the hierarchical structure provides clear context about which test failed
|
||||
6. **Resource Cleanup**: Ensure proper cleanup in tests, especially when testing resource management
|
||||
56
.github/KnowledgeBase/KnowledgeBase.vcxitems
vendored
Normal file
56
.github/KnowledgeBase/KnowledgeBase.vcxitems
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="Globals">
|
||||
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' < '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
<HasSharedItems>true</HasSharedItems>
|
||||
<ItemsProjectGuid>{D178E490-7C2B-43FA-A8B3-A3BED748EB38}</ItemsProjectGuid>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ProjectCapability Include="SourceItemsFromImports" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="$(MSBuildThisFileDirectory)Index.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)Learning.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_GacUI_Design_AddingNewControl.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_GacUI_Design_ControlFocusSwitchingAndTabAltHandling.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_GacUI_Design_ImplementingIGuiGraphicsElement.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_GacUI_Design_LayoutAndGuiGraphicsComposition.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_GacUI_Design_ListControlArchitecture.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_GacUI_Design_MainWindowModalWindow.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_GacUI_Design_PlatformInitialization.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_VlppOS_AdditionalStreams.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_VlppOS_EncodingDecoding.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_VlppOS_FileSystemOperations.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_VlppOS_LocaleSupport.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_VlppOS_MultiThreading.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_VlppOS_StreamOperations.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_VlppOS_SynchronizationPrimitives.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_VlppOS_WaitableObjects.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_VlppReflection_ClassInterfaceRegistration.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_VlppReflection_CompilationLevels.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_VlppReflection_EnumRegistration.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_VlppReflection_InterfaceProxy.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_VlppReflection_StructRegistration.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_VlppReflection_TypeMetadata.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_VlppReflection_TypeRegistrationStructure.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_VlppRegex_PatternMatching.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_VlppRegex_TypeAliases.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_Vlpp_CollectionTypes.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_Vlpp_ConsoleOperations.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_Vlpp_DateTimeOperations.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_Vlpp_Design_ImplementingInjectableFeature.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_Vlpp_ExceptionHandling.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_Vlpp_LambdaExpressions.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_Vlpp_LinqOperations.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_Vlpp_MemoryLeakDetection.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_Vlpp_ObjectModel.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_Vlpp_PrimitiveTypes.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_Vlpp_SortingOrdering.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_Vlpp_StringTypes.md" />
|
||||
<None Include="$(MSBuildThisFileDirectory)KB_Vlpp_UnitTesting.md" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
1
.github/KnowledgeBase/Learning.md
vendored
Normal file
1
.github/KnowledgeBase/Learning.md
vendored
Normal file
@@ -0,0 +1 @@
|
||||
# Learning
|
||||
56
.github/KnowledgeBase/manual/gacui/advanced/alt.md
vendored
Normal file
56
.github/KnowledgeBase/manual/gacui/advanced/alt.md
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
# ALT Sequence and Control Focus
|
||||
|
||||
If a focusable control is put on a window, by setting the [Alt](../.././gacui/components/controls/basic/home.md) property to a value, for example "X", then it could get focused by pressing **ALT** followed by **X** (not **ALT+X**).
|
||||
|
||||
The **Alt** property could accept a sequence of characters, not just one. Characters in such a sequence needs to be entered to activate this control.
|
||||
|
||||
If **ESC** is pressed, it goes back to the previous container.
|
||||
|
||||
If **BACKSPACE** is pressed, it cancels the last character in the sequence.
|
||||
|
||||
When **ALT** is pressed, all valid **ALT** sequences will be printed on the window. All labels rendering these sequences will get changed
|
||||
|
||||
## Example
|
||||
|
||||

|
||||
|
||||
The operation is done by pressing the following keys: - **ALT**: Enter the ALT sequence mode. - **P**: There are two controls with the **Alt** property set to "P". It filters them out and wait for a number key. - **0**: The dropdown button is activated, a sub menu is opened. - **ESC**: Close the sub menu and go back to the previous container, which is the window. - **P**: Filter again. - **BACKSPACE**: Cancel the last character "P" in the sequence, showing all available items. - **P**: Filter again. - **1**: The paste button is activated, a piece of text get pasted to the editor. The ALT sequence mode is exited because an item is finally chosen. - **ALT**: Enter the ALT sequence mode. - **D**: Give the focus to the editor control. - **CTRL+A**: Select all text. - **DELETE**: Delete the selection.
|
||||
|
||||
## Behavior after being Focused
|
||||
|
||||
There are three kinds of behaviors when a control is focused by a **ALT** sequence. If a control is focused by **TAB**, it could behaves differently. - `empty list item` **Just being focused** Controls with complex keyboard behaviors like editors or list controls, will just get focused and do nothing else. - `empty list item` **Execute a Command** Controls with simple executable behaviors like buttons, will do what it does when clicked, after being focused by an **ALT** sequence. - `empty list item` **Just being focused** Controls that are not executable but contains executable items, like menu items with a sub menu, will render all executable items after being focused by an **ALT** sequence. A menu item with a sub menu does nothing when it is clicked. But if it is activated by an **ALT** sequence, the sub menu will be opened and wait for more keys.
|
||||
|
||||
## Create your own Behavior
|
||||
|
||||
If a new control class is created, there are multiple ways to customize the behavior about how a control should react to an **ALT** sequence:
|
||||
|
||||
### Using IGuiAltAction
|
||||
|
||||
**IGuiAltAction** is a protected base class of all controls. If a new control is created, methods could be overriden.
|
||||
|
||||
Properly assigning an **ALT** sequence to the **Alt** property of a control could also make these methods behave as expected easily. **IsAltEnabled** returns **true** if the control is visible and enabled. **IsAltAvailable** returns **true** if the control is focusable and the **Alt** property is not empty.
|
||||
|
||||
When the window is in the **ALT** sequence mode, a label rendering the sequence for this control will be put in the composition from **GetAltComposition**.
|
||||
|
||||
If both **IsAltEnabled** and **IsAltAvailable** returns **true**, then the result of **GetAlt()**, which is also the value from the **Alt** property by default, becomes one of an candidate. **OnActiveAlt** will be called when this control is selected by a **ALT** sequence.
|
||||
|
||||
### Using IGuiAltActionContainer
|
||||
|
||||
**IGuiAltActionContainer** is a [ service object ](../.././gacui/components/controls/basic/home.md). A service object could be attached to a control by calling **AddService** or overriding **QueryService**.
|
||||
|
||||
If an **IGuiAltActionContainer** instance is attached to a control, then all methods in **IGuiAltAction** are ignored. Instead, multiple **IGuiAltAction** object returned from this interface will be used.
|
||||
|
||||
You could now assign multiple **ALT** sequence to a control, with each sequence binded to a different behavior.
|
||||
|
||||
All **IGuiAltAction** returned from **IGuiAltActionContainer** must be enabled and available.
|
||||
|
||||
### Using IGuiAltActionHost
|
||||
|
||||
An **IGuiAltActionHost** instance could be attached to a control by calling **SetActivatingAltHost** or overriding **GetActivatingAltHost**.
|
||||
|
||||
When a control is selected by a **ALT** sequence, if **IGuiAltActionHost** is attached to this control, then the **ALT** sequence mode will not exit. Instead, this **IGuiAltActionHost** is treated as a nested container, and GacUI calls **CollectAltActions** to collect all valid **IGuiAltAction**, renders all **ALT** and wait for keyboard input.
|
||||
|
||||
All top level controls like a window or a menu are attached by an **IGuiAltActionHost** by default. If a sub menu is created on a menu item, **IGuiAltActionHost** is attached to this control. So when it is activated, it opens the sub menu and continue to wait for more keys, instead of executing this menu item.
|
||||
|
||||
**GuiAltActionHostBase** is the default implementation of **IGuiAltActionHost**. If it is attached to a control, all child controls with valid **IGuiAltAction** are not visible from the container, instead they are available when the parent control is selected by an **ALT** sequence. **GuiAltActionHostBase::SetAltControl** must be called to initialize this class, it tells this implementation where to search for child controls to collect **IGuiAltAction**.
|
||||
|
||||
46
.github/KnowledgeBase/manual/gacui/advanced/animations.md
vendored
Normal file
46
.github/KnowledgeBase/manual/gacui/advanced/animations.md
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
# Animations
|
||||
|
||||
The GacUI XML [<Animation/>](../.././gacui/xmlres/tag_animation.md) resource creates helper classes for animations. It creates a class like this: ``` class MyAnimation { prop Current: STATE_CLASS^{} new (current: STATE_CLASS^){} func CreateAnimation(target: STATE_CLASS^, time: UInt64); } ``` **STATE_CLASS** is a class with fields of number types or **Color**. By creating an **MyAnimation^** in a window like this: ``` <Instance ...> <ref.Members><![CDATA[ var myAnimation : MyAnimation^ = new MyAnimation(initial_state); ]]></ref.Members> <Window ref.Name="self"> <Something PropertyToAnimate-bind="self.myAnimation.Current.FIELD"/> </Window> </Instance> ``` You are able to bind the expression **self.myAnimation.Current.FIELD** to any compatible property. When the animation is running, fields in **self.myAnimation.Current** will keep updating, so that to animate the UI.
|
||||
|
||||
## Interpolation Functions
|
||||
|
||||
The first step to animate a field in **self.myAnimation.Current** is to set a proper interpolation function, which is described in [<Animation/>](../.././gacui/xmlres/tag_animation.md). Only fields that mentioned by a **\<Target/\>** tag could be animated. All animated fields must associate an interpolation function. There is also a place for a default interpolation function, which will apply on all animated fields unless one is specified in **\<Target/\>**.
|
||||
|
||||
An interpolation function is an Workflow expression in a function type, which takes a **double** and returns a **double**.
|
||||
|
||||
The function parameter is the progress of the animation from **0** to **1**. If an animation is 10 seconds long, then **0** is the beginning, **1** is the ending, and **0.4** means **10 seconds * 0.4** which is at the end of the 4th second.
|
||||
|
||||
The return value is the interpolation of the animation from **0** to **1**. If the begin state is **5** and the end state is **15**, then returning **0** means **5**, returning **1** means **15**, and returning **0.4** means the **5 + (15 - 5) * 0.4** which is **9**.
|
||||
|
||||
The interpolation calculate that, where a field should go given the progress of the animation. If a linear interpolation function is expected, then just return the parameter. Such a function would typically be: - for [ordered lambda expression](../.././workflow/lang/expr.md) : **[$1]** - for [lambda expression](../.././workflow/lang/expr.md) : for example: **func (progress: double) : double { return progress; }** Sometimes it is better to make the field accelerate at the first half and decelerate at the second half, such a function could be easily created using: ``` func (x: double): double { if (x < 0.5) { return x * x * 2; } else { return 1 - (1 - x) * (1 - x) * 2; } } ```
|
||||
|
||||
## Running an Animation
|
||||
|
||||
In the **\<Animation/\>** generated class, there is a **CreateAnimation(state, time)** function, which means the animation begins from the current state, and run towards **state** in **time** milliseconds.
|
||||
|
||||
Calling this function doesn't make the animation run, instead it returns an **(vl::)presentation::controls::IGuiAnimation^** object. **AddAnimation** and **KillAnimation** of **(vl::)presentation::controls::GuiInstanceRootObject** controls how the animation run. This class is the base class for all UI [ root instances ](../.././gacui/xmlres/instance/root_instance.md), so **AddAnimation** and **KillAnimation**, or **self.AddAnimation** and **self.KillAnimation**, are accessible in the XML.
|
||||
|
||||
**AddAnimation** adds an **IGuiAnimation** object to the UI object, and start the animation immediately.
|
||||
|
||||
**KillAnimation** stops an **IGuiAnimation** object that has been added to the UI object, and stop the animation immediately.
|
||||
|
||||
For animations that need to switch from state to state for multiple times, like a button when the mouse is moving in and out, there is a pattern to control the animation: ``` <ref.Members><![CDATA[ var myAnimation : MyAnimation^ = new MyAnimation(initial_state); var lastAnimation : IGuiAnimation^ = null; ]]></ref.Members> ... var newAnimation = myAnimation.CreateAnimation(newState, animationLengthInMilliseconds); KillAnimation(lastAnimation); lastAnimation = newAnimation; AddAnimation(lastAnimation); ``` When the state needs to move to a new state when the animation is running, this piece of code stops the current running animation, and replace it with a new one. The new animation moves the current state from where it is to a new state, and it should look smooth.
|
||||
|
||||
**KillAnimation** could accept **null**, and it does nothing.
|
||||
|
||||
## Managing Multiple Animations
|
||||
|
||||
It is easy to copy the above pattern multiple times for each animation.
|
||||
|
||||
Please remember that, the created **lastAnimation** will change **myAnimation.Current.FIELD** when it is executing, so if multiple animation needs to run at the same time, multiple **lastAnimation** and **myAnimation** should be created at the same time. Please give them good names to make the code looks clear.
|
||||
|
||||
When a UI object is disposing, e.g. when the close button on a window is clicked, all animations will be shut down, and calling **AddAnimation** will result in an exception.
|
||||
|
||||
It is function idential to split all fields to multiple animation objects. But if multiple fields represent different parts of the same animation, do your best to make one animation for all these fields to increase the performance.
|
||||
|
||||
In [ this tutorial project ](https://github.com/vczh-libraries/Release/blob/master/SampleForDoc/GacUI/XmlRes/kb_animation/Resource.xml), a button is controlled by 4 colors, which changes in one animation because they represent different parts of the same animation. So only one **\<Animation/\>** and only one instance of it is needed for one button.
|
||||
|
||||
Here is how it looks like:
|
||||
|
||||
- Source code: [kb_animation](https://github.com/vczh-libraries/Release/blob/master/SampleForDoc/GacUI/XmlRes/kb_animation/Resource.xml) - 
|
||||
|
||||
46
.github/KnowledgeBase/manual/gacui/advanced/bindings.md
vendored
Normal file
46
.github/KnowledgeBase/manual/gacui/advanced/bindings.md
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
# Data Bindings
|
||||
|
||||
Data bindings in GacUI relies on [Workflow expression](../.././workflow/lang/bind.md). Basically, an expression is observable if it uses properties, and these properties must be observable by having their property changing events on the object. You are still able to combine some events on a property temporary just for this expression to make it observable.
|
||||
|
||||
In GacUI XML Resource, [-bind](../.././gacui/xmlres/instance/properties.md), [-format](../.././gacui/xmlres/instance/properties.md) and [-str](../.././gacui/xmlres/instance/properties.md) are based on data bindings.
|
||||
|
||||
## Binding to Control Properties
|
||||
|
||||
This is the most basic kind of data binding.
|
||||
|
||||
If you are binding something to a text property which will be displayed on the UI, e.g. **Text** of a **\<Label/\>**, make sure that: - The layout of controls is properly handled, so that they will re-arrange themselves to make sure that no truncation of text rendering will happen. - If the application could be send to people using different language, please consider using **Localization** from the very beginning.
|
||||
|
||||
Binding view models to control is also very straight-forward. A **\<ref.Paramter Name="Something" Class="..."/\>** creates a property that will never change once the window is created. The **X** property of **Something** can be binded to a control property just by: ``` <AControl AProperty-bind="Something.X"/> ```
|
||||
|
||||
It is also very simple to use data binding with your own objects like: ``` <Instance ref.Class="..."> <ref.Members><![CDATA[ var myObject = new MyObject(); ]]></ref.Members> <Window ref.Name="self"> <AControl AProperty-bind="self.myObject.X"/> </Window> </Instance> ``` Since **myObject** is a member of the window, you must first give the window a name **self**, to make XML knows what is **myObject**.
|
||||
|
||||
## Binding between Composition Properties
|
||||
|
||||
**JUST DON'T DO THAT.**
|
||||
|
||||
The built-in layout algorithm already make compositions bidirectionally react to each other. Binding one property in a composition to another property in (maybe) another composition could cause the reaction never end.
|
||||
|
||||
Here is a very bad example: ``` <Bounds ref.Name="parent" MinSizeLimitation="LimitToElementAndChildren"> <Bounds ref.Name="child" PreferredMinSize-bind="{x:(parent.Bounds.x2 - parent.Bounds.x1} y:0}" AlignmentToParent="{left:1 top:1 right:1 bottom:1}" /> </Bounds> ```
|
||||
|
||||
When **parent.Bounds** is changed, the observed expression is notified to re-evaluate, and than cause **child.PreferredMinSize** to change.
|
||||
|
||||
**child.PreferredMinSize** causes **parent.Bounds** to re-evaluate because **MinSizeLimitation="LimitToElementAndChildren"**.
|
||||
|
||||
Now parent grows bigger because **child.AlignmentToParent** is not **0** or **-1**, which causes **parent.Bounds** to change again.
|
||||
|
||||
The loop is infinite, and when it begins to happen (usually because of the window border is dragging to resize), **parent** will soon grows to super big and you are no longer able to properly operate against the window.
|
||||
|
||||
## Binding to ViewModel Properties
|
||||
|
||||
You could also bind something back to the view model by using **-set** property: ``` <Instance ref.Class="..."> <ref.Parameter Name="Something" Class="..."/> <Window ref.Name="self"> <att.Something-set X-bind="aControl.AProperty"/> <AControl ref.Name="aControl"/> </Window> </Instance> ``` **-set** here tells XML that, properties referenced in the tag **\<att.Something/\>** are in the object returning from the **Something** property of the window, which is the view model in **\<ref.Parameter/\>**. Now when **aControl.AProperty** is changed, the value will be updated to **Something.X** immediately.
|
||||
|
||||
## Bidirectional Binding
|
||||
|
||||
Bidirectional binding is a special type of binding, when two properties update each other.
|
||||
|
||||
[<CommonDatePickerLook/>](../.././gacui/components/ctemplates/commondatepickerlook.md) is a good example: ``` <Instance ref.CodeBehind="false" ref.Class="darkskin::DatePickerTemplate" ref.Styles="res://DarkSkin/Style"> <DatePickerTemplate ref.Name="self" Date-bind="look.Date" ...> <CommonDatePickerLook ref.Name="look" Date-bind="self.Date" .../> </DatePickerTemplate> </Instance> ``` In this example, when **self.Date** is changed, the value will be updates to **look.Date**, when **look.Date** is changed, the value will be updates to **self.Date**. Such biditional binding keep these two properties in sync, by using **-bind** twice on each other.
|
||||
|
||||
This doesn't cause the update loop to run infinitely because these two **Date** properties don't trigger their property changing event if the updated value is the same to the value before updating.
|
||||
|
||||
Properties create by the **prop Name : TYPE {}** syntax will not trigger the **NameChanged** event if the same value is updated to the property again. But for properties with explicit getter and setter, you must handle the setter properly: ``` class YourObject { var name: string = ""; event NameChanged(); func GetName(): string { return name; } func SetName(value: string): void { if (name != value) { name = value; NameChanged(); } } prop Name: string {GetName, SetName : NameChanged} } ``` Otherwise using bidirectional binding on this property will trigger an infinite loop.
|
||||
|
||||
38
.github/KnowledgeBase/manual/gacui/advanced/cxrr.md
vendored
Normal file
38
.github/KnowledgeBase/manual/gacui/advanced/cxrr.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# Cross XML Resource References
|
||||
|
||||
A GacUI XML Resource could [ use some resources in another XML ](../.././gacui/xmlres/cxrr.md). There are 3 types of resources: - Resource files: They could be accessed using **import-res://RESOURCE-NAME/...** in the **-uri binding**. - Objects that compiled to Workflow: They could be accessed using their generated class or interface names. - Everything else: They could not be accessed from another XML.
|
||||
|
||||
## XML Resource Header
|
||||
|
||||
To make a GacUI XML Resource be able to depend on or be depended by another resource, a header is required in **GacGenConfig/Metadata** like this: ``` <Resource> <Folder name="GacGenConfig"> <Xml name="Metadata"> <ResourceMetadata Name="EditorRibbon" Version="1.0"> <Dependencies> <Resource Name="EditorBase"/> </Dependencies> </ResourceMetadata> </Xml> ... </Folder> ... </Resource> ``` - **/ResourceMetadata/@Name**: The name of the resource. All resources that loaded into an application should either have a unique name or have no name. If a resource want to depend on or to be depended by another resource, it should have a name. - **/ResourceMetadata/@Version**: The only legal value is **1.0**. It is bounded to the GacUI XML Resource Compiler (GacGen.exe). To ensure that all depended resources are compiled using a compatible version of compiler, an resource could only depend on another resource that has exactly the same version. - **/ResourceMetadata/Dependencies/Resource/@Name**: Here are all resources that the current resource depends on. The name must match their **/ResourceMetadata/@Name** in their metadata.
|
||||
|
||||
## Compile Resource with Dependencies
|
||||
|
||||
[GacBuild.ps1](https://github.com/vczh-libraries/Release/tree/master/Tools) is the only tool to properly compile resources with dependencies.
|
||||
|
||||
You need to have a file called **GacUI.xml**: ``` <GacUI/> ``` A GacUI XML Resource and all its dependeices must be put inside the folder containing **GacUI.xml**. Resource files could be put in any level of sub folders.
|
||||
|
||||
Then you could call **GacBuild.ps1 -FileName path/to/GacUI.xml** in **PowerShell**. All resource files in the folder containing **GacUI.xml** will be found and compiled in the dependency order.
|
||||
|
||||
**GacBuild.ps1** forces incremental build. If a resource is not changed, and all its dependencies are not changed, then the resource will not be compiled again. You could use **GacClear.ps1 -FileName path/to/GacUI.xml** to clear all cache, the next time **GacBuild.ps1** will recompile every resource files.
|
||||
|
||||
Generated resource binaries, workflow binaries and C++ code will be put in places [ specified in each GacUI XML Resource ](../.././gacui/xmlres/cgc.md).
|
||||
|
||||
## Loading Resource Dependencies in your C++ Application
|
||||
|
||||
**vl::presentation::GetResourceManager()-\>LoadResourceOrPending(fileStream[, errors][, usage]);** is the only function to load compiled resource binaries: - **fileStream**: It could be any [vl::stream::IStream](../.././vlppos/using-streams.md), you could use **FileStream** here to load a resource from file. - **errors**: If the resource could not be loaded, errors will be stored in this list. An empty error list after calling this function means the resource is loaded properly. If the **errors** argument is not use, then the function will crash if there is any error. - **usage**: It could be **InstanceClass** if you include all Workflow binaries into this resource file, and **DataOnly** if not. A resource binary containing Workflow binaries could be produced using [res://GacGenCppConfig/ResX86/Resource and res://GacGenCppConfig/ResX/Resource](../.././gacui/xmlres/cgc.md)**The content of Workflow binaries is different for x86 or x64**, you need to load the correct one. Typically, if you use compiled resource with generated C++ files instead of Workflow binaries, you only need the first argument.
|
||||
|
||||
You don't need to worry about the order to load resource files, they will be taken care of in this function, just call **LoadResourceOrPending** for all of them.
|
||||
|
||||
Calling **LoadResourceOrPending** without all depended resources prepared results in delay loading. Such resource will be automatically loaded after all depended resources are loaded. You could call **GetResource** to see if a resource has already been loaded or not. If it returns **null** but **LoadResourceOrPending** succeeded, it means some depended resources are not prepared yet, and the loading is delayed.
|
||||
|
||||
## Get Rid of Resource Files
|
||||
|
||||
By using [CppResource or CppCompressed](../.././gacui/xmlres/cgc.md) in **res://GacGenConfig/Cpp**, you will get multiple cpp files for each GacUI XML Resource. Link all these cpp files to your project, resource binaries will be compiled into your executable file. When your application starts, all resources will be properly loaded before **GuiMain**.
|
||||
|
||||
## Using Resource in Dependedcies
|
||||
|
||||
If there is a resource object **res://path/to/the/resource** in a GacUI XML Resource called **BaseResource**, another GacUI XML Resource depending on it could use **import-res://BaseResource/path/to/the/resource**. Such resource path could be use in **-uri binding**, or in the **ResolveResource** method in all [root UI instance](../.././gacui/xmlres/instance/root_instance.md).
|
||||
|
||||
If a resource is compiled to Workflow classes or instances, just use the class or interface names directly. But if the resource is not in the dependency list (**/ResourceMetadata/Dependencies/Resource/@Name**), the class or interface name will not be found when running **GacBuild.ps1**, errors will be generated and the resource cannot be compiled.
|
||||
|
||||
6
.github/KnowledgeBase/manual/gacui/advanced/home.md
vendored
Normal file
6
.github/KnowledgeBase/manual/gacui/advanced/home.md
vendored
Normal file
File diff suppressed because one or more lines are too long
74
.github/KnowledgeBase/manual/gacui/advanced/impllistcontrol.md
vendored
Normal file
74
.github/KnowledgeBase/manual/gacui/advanced/impllistcontrol.md
vendored
Normal file
File diff suppressed because one or more lines are too long
4
.github/KnowledgeBase/manual/gacui/advanced/implosprovider.md
vendored
Normal file
4
.github/KnowledgeBase/manual/gacui/advanced/implosprovider.md
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# Porting GacUI to Other Platforms
|
||||
|
||||
This page is under construction.
|
||||
|
||||
28
.github/KnowledgeBase/manual/gacui/advanced/localization.md
vendored
Normal file
28
.github/KnowledgeBase/manual/gacui/advanced/localization.md
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# Localization
|
||||
|
||||
The GacUI XML [<LocalizedStrings/>](../.././gacui/xmlres/tag_localizedstrings.md) resource creates multi-language string template as bindable objects.
|
||||
|
||||
The GacUI XML [<LocalizedStringsInjection/>](../.././gacui/xmlres/tag_localizedstringsinjection.md) resource adds more languages to an existing **\<LocalizedStrings/\>**.
|
||||
|
||||
## \<ref.LocalizedStrings/\>
|
||||
|
||||
**\<ref.LocalizedStrings/\>** imports a **\<LocalizedStrings/\>** to the current UI instance: ``` <ref.LocalizedStrings Name="Strings" Class="demo::StringResource" Default="true"/> ``` - **Name**: The name of this string resource. It will also create a property in the current UI instance in the same name. The property value could be changed at runtime. Unlike **\<ref.Parameter/\>**, the instance knows how to initialize a string resource, so it doesn't create a constructor parameter. - **Class**: The full name of the string resource class. The resource should look like this to be referenced using **Class="demo::StringResource"**: ``` <LocalizedStrings ref.Class="demo::StringResource" DefaultLocale="en-US"> ... </LocalizedStrings> ``` - **Default**: Specify a default string resource. If there are multiple **\<LocalizedStrings/\>**, only one of them could be the default string resource.
|
||||
|
||||
## -str Binding
|
||||
|
||||
The value of the **-str** binding must be a [ Workflow call expression ](../.././workflow/lang/expr.md). The function could be either **METHOD** or **NAME.METHOD**. - **METHOD**: This syntax use the **default string resource**. **METHOD** must be one of the **Name** attribute of a **\<String/\>** in the referenced **\<LocalizedStrings/\>**. - **NAME.METHOD**: Unlike the above one, this syntax use the **\<ref.LocalizedStrings/\>** whose **Name** attribute is **NAME**. Once the string resource and the string item is located, the **-str** binding will check if arguments match the type requirement. Note that parameter types of the same string item in different languages should be exactly the same in one **\<LocalizedStrings/\>**, so it doesn't matter which is the current UI language. This also ensure that you are able to switch the UI language at runtime.
|
||||
|
||||
## Using -bind or -format on String Resources
|
||||
|
||||
For a string resource imported by: ``` <ref.LocalizedStrings Name="Strings" Class="demo::StringResource" Default="true"/> ``` The **-str** binding is also a data binding, so it knows when the **Strings** property is changed, and update the property value.
|
||||
|
||||
Assume the default string resource is **Strings**, the following binding code - **-str="Something(p1, p2, p3)"** - **-str="Strings.Something(p1, p2, p3)"** is equivalent to - **-bind="self.Strings.Something(p1, p2, p3)"** - **-format="$(self.Strings.Something(p1, p2, p3))"** when the current UI instance has a **ref.Name="self"** attribute.
|
||||
|
||||
## Switching the UI Language
|
||||
|
||||
The only way to specify the UI language is: - **C++**: GetApplication()-\>SetLocale(LOCALE); - **Workflow**: presentation::controls::GuiApplication.GetApplication().Locale = LOCALE; This configuration affect all windows in the current process.
|
||||
|
||||
For a string resource imported by: ``` <ref.LocalizedStrings Name="Strings" Class="demo::StringResource" Default="true"/> ``` When the application locale is changed, the **Strings** property of the current UI object will be replaced by a new object, which is from a **\<Strings/\>** in the referenced **\<LocalizedStrings/\>** for that locale. If the specified locale doesn't exist in that localized string, the default one will be used.
|
||||
|
||||
Such **\<LocalizedStrings/\>** will also create an interface called demo::**I**StringResource**Strings**, which becomes the type of the **Strings** property if it is imported using the above **\<ref.LocalizedStrings/\>**. Usually you don't need to use either the generated class **demo::StringResource**. or the generated interface **demo::IStringResourceStrings**.
|
||||
|
||||
22
.github/KnowledgeBase/manual/gacui/advanced/tab.md
vendored
Normal file
22
.github/KnowledgeBase/manual/gacui/advanced/tab.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# TAB and Control Focus
|
||||
|
||||
If a focusable control is put on a window, by setting the [TabPriority](../.././gacui/components/controls/basic/home.md) property to a value that is not **-1**, then it could get focused by pressing **TAB**.
|
||||
|
||||
Pressing **TAB** switches the focus between different controls in a window, in the order of their **TabPriority** from less to greater, when both **IsTabEnabled** and **IsTabAvailable** return **true**. If the value is less than **-1**, then they will be treated as **0xFFFFFFFFFFFFFFFF**. The order between controls with the same **TabPriority** is undefined.
|
||||
|
||||
## Behavior after being Focused
|
||||
|
||||
A focusable control will just get focused if **TAB** decides to give the focus to this control.
|
||||
|
||||
## Create your own Behavior
|
||||
|
||||
If a new control class is created, there are multiple ways to customize the behavior about how a control should react to **TAB**:
|
||||
|
||||
### Using IGuiTabAction
|
||||
|
||||
**IGuiTabAction** is a protected base class of all controls. If a new control is created, methods could be overriden.
|
||||
|
||||
**IsTabEnabled** returns **true** if the control is visible and enabled. **IsTabAvailable** returns **true** if the control is focusable.
|
||||
|
||||
The default value from **GetAcceptTabInput** is **false**. But a few controls like text controls or document controls set this value to **true**. When **GetAcceptTabInput** is **true**, it means this control defines the behavior for this key, like entering a **TAB** character, so that it doesn't want the focus to be switched to another control. Calling **SetAcceptTabInput** could change this value.
|
||||
|
||||
30
.github/KnowledgeBase/manual/gacui/advanced/vm.md
vendored
Normal file
30
.github/KnowledgeBase/manual/gacui/advanced/vm.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Interop with C++ View Model
|
||||
|
||||
It is recommended to use the [MVVM](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel) pattern with GacUI: - `empty list item` **Model**: It refers to a domain model. Code in **Model** solves real problem that users need. It could be an object-oriented design of a domain, it could also be a data access layer to the data in database, from the internet, or any possible way. - `empty list item` **ViewModel**: It refers to a set of interface exposing the **Model** in a convient way for **Views**. Data organization in **Model** doesn't have to be UI friendly. It is the **ViewModel** who exposes a right set of operations, to make **Views** access **Model** convenient. For example, there are many applications offer list sorting to users. Data in **Model** is not sorted, it could be indexed, because there is so many way to sort the data, it is a waste of storage to cache all sorting result. When a user sort the data in a specific way, it is the **ViewModel** who handels the sorting. the **ViewModel** accesses the data, return the data organization in a sorted way. - `empty list item` **View**: A **View** could be the actual UI of an application, it could also be a set of unit tests. **Views** access **ViewModel**, typically it doesn't access **Model**.
|
||||
|
||||
In GacUI, **ViewModels** are defined as a set of interfaces in [Workflow script files](../.././gacui/xmlres/tag_script.md). **Views** are defined as a set of [windows and controls](../.././gacui/xmlres/instance/root_instance.md).
|
||||
|
||||
All **ViewModels** that are needed in a **View** are defined as [<ref.Parameter/>](../.././gacui/xmlres/tag_instance.md), which represents constructor arguments to that **View**. The **Class** attributes of such **\<ref.Parameter/\>** will be interfaces for **ViewModels**.
|
||||
|
||||
In the GacUI entry point (the **GuiMain** function), a main window will be created. If the main window needs **ViewModels**, the main window **\<Instance/\>** will have one or more **\<ref.Parameter/\>**, then the constructor of the main window will have one or more constructor arguments.
|
||||
|
||||
Now, **GuiMain** just needs to create classes that implement these interfaces, and gives all of them to the constructor of the main window, then **ViewModels** and the **View** is connected together.
|
||||
|
||||
## Sample
|
||||
|
||||
[This](https://github.com/vczh-libraries/Release/tree/master/Tutorial/GacUI_HelloWorlds/MVVM) is a very simple example for using MVVM in GacUI.
|
||||
|
||||

|
||||
|
||||
The **ViewModel** is an interface: ``` interface IViewModel { func GetUserName() : string; func SetUserName(value : string) : void; prop UserName : string {GetUserName, SetUserName} func GetPassword() : string; func SetPassword(value : string) : void; prop Password : string {GetPassword, SetPassword} func GetUserNameError() : string; event UserNameErrorChanged(); prop UserNameError : string {GetUserNameError : UserNameErrorChanged} func GetPasswordError() : string; event PasswordErrorChanged(); prop PasswordError : string {GetPasswordError : PasswordErrorChanged} func SignUp() : bool; } ``` It defines all **View** needs to know: - Accessing the user name and the password. - Retriving validation results for the user name and the password.
|
||||
|
||||
When the content of a text box is changed, the data is stored to the view model. This is implemented using data bindings to the view model object: ``` <ref.Parameter Name="ViewModel" Class="vm::IViewModel"/> <Window ref.Name="self" Text="Let's Sign Up!" ClientSize="x:320 y:320"> <att.ViewModel-set UserName-bind="textBoxUserName.Text" Password-bind="textBoxPassword.Text"/> ... ```
|
||||
|
||||
When the validation result is changed, errors are displayed to the UI immediately. This is implemented using data bindings to UI objects: ``` <SolidLabel Text-bind="ViewModel.UserNameError"/> ```
|
||||
|
||||
When the user name or the password is stored to the view model, validation results are updated automatically. This is implemented in a C++ class which inherits the interface: ``` class ViewModel : public Object, public virtual vm::IViewModel { private: WString userName; WString password; Regex regexLcLetters; Regex regexUcLetters; Regex regexNumbers; public: ViewModel() :regexLcLetters(L"[a-z]") , regexUcLetters(L"[A-Z]") , regexNumbers(L"[0-9]") { } ... WString GetPassword()override { return password; } void SetPassword(const WString& value)override { password = value; PasswordErrorChanged(); } WString GetPasswordError()override { if (password == L"") { return L"Password should not be empty."; } bool containsLowerCases = regexLcLetters.Match(password); bool containsUpperCases = regexUcLetters.Match(password); bool containsNumbers = regexNumbers.Match(password); if (!containsLowerCases || !containsUpperCases || !containsNumbers) { return L"Password should contains at least one lower case letter, one upper case letter and one digit."; } return L""; } }; ```
|
||||
|
||||
It is very easy to connect the **ViewModel** to the **View**: ``` void GuiMain() { ... auto viewModel = Ptr(new ViewModel); auto window = new helloworld::MainWindow(viewModel); ... } ```
|
||||
|
||||
Collection objects or tree-like objects can be binded to [list controls](../.././gacui/components/controls/list/home.md) directly. Data bindings accept any [workflow expressions](../.././workflow/lang/bind.md) that compiles. MVVM in GacUI is very powerful, you are free to choose any way to define **ViewModel** interfaces, and it is always possible to bind it to the UI.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user