aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/docs/notes/avalonia-gotchas.md
blob: 5150505c3187c887e1f39d881bcd90e9837a6556 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# Avalonia / Desktop gotchas

Each one cost debugging time once. The code has comments at each site;
this file is the index.

- **The GL view isn't hit-testable by default.** `OpenGlControlBase` draws
  through GL, not Avalonia's scene. Pointer events and `Cursor` go to the
  panel beneath unless the control implements `ICustomHitTest`
  (`GameView.HitTest`). Display-only overlays need `IsHitTestVisible =
  false` so the pointer reaches the game.
- **Deactivation ≠ lost focus.** Clicking another app deactivates the window
  but keeps logical focus (`IsFocused` stays true), so `OnLostFocus` never
  fires. Release held keys on `Window.Deactivated` too, because `OnKeyUp`
  never arrives for a key released elsewhere.
- **The sim only advances while frames render.** `GameLoop.Frame` runs
  inside `OnOpenGlRender`. Stopping `RequestNextFrameRendering` (paused)
  stops the sim, and "keep simulating while minimized" would need a timer.
- **The render callback runs on the UI thread** (verified on this Linux/X11
  setup). The overlay and `FrameTimeStats` share state without locks for
  that reason. Re-check if a platform moves rendering off-thread.
- **No pointer lock API** in Avalonia 12.1.2. Free look reads deltas between
  pointer positions and stalls at the window edge. Recentering uses a
  platform call (`CursorWarp`, X11 `XWarpPointer`; off where it fails).
- **Escape vs focused controls.** A clicked `CheckBox` takes keyboard focus,
  so `Esc` for the options menu is a window-level *tunnel* handler, not
  `GameView.OnKeyDown`.
- **Tests: float vs double.** `FixedStepClock.TickSeconds` is `float`. Compare
  it against double arithmetic with a tolerance (~1e-6), not 9 places.
- **A clicked in-game `Button` steals the game's keys.** It takes focus, so
  WASD stops reaching `GameView` and Space presses the button. HUD buttons
  are `Focusable = false` (`Ui.Action`).
- **Raising `Button.ClickEvent` doesn't toggle a `CheckBox`.** Toggle logic
  lives in `ToggleButton.OnClick`; scripted/pad presses use `Ui.Press`.
- **`KeyBindings` inside a control resolves to Avalonia's
  `InputElement.KeyBindings`**, not ours — qualify the game's own `KeyBindings` type fully.
- **Invisible controls aren't templated.** `IsVisible = false` skips
  measure/template, so the first real open pays for it (first NPC dialog
  ~170 ms). `Ui.WarmUp()` shows a zero-opacity card briefly instead.
- **Routed key events only reach controls on the route.** A `KeyDown` raised
  on the focused `GameView` tunnels window → `GameScreen` → `GameView`; a
  sibling panel (options) never sees it. Screen-wide key handling lives on
  `GameScreen` (e.g. key-rebind capture).
- **Scripted input vs X injection.** On KDE Wayland `xdotool` keys don't reach
  a focused XWayland window (and may type into the user's terminal); drive
  smoke runs with `smoke_run.sh --input FILE` (routed events in-process).
- **The HUD is redrawn every frame over the GL view.** The GL control changes every frame
  and covers the window, so the compositor re-draws every overlay each frame — text is
  the expensive part. `CacheMode = new BitmapCache()` on HUD cards (`Ui.Card`) makes them
  re-render only on change; set `RenderAtScale` to any scale transform above them or they
  blur. Merging text blocks into fewer visuals doesn't help.
- **Don't judge perf by CPU % on this machine** (±50 % with other load). Use the `instr:`
  smoke step, interleaved runs, `PauseWhenInactive` off; see game-loop spec §7.