bevy_window_manager 0.20.2

Bevy plugin for primary window restoration and multi-monitor support
Documentation
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
//! Type definitions for window restoration.

use std::path::PathBuf;

use bevy::prelude::*;
use bevy::window::MonitorSelection;
use bevy::window::VideoMode;
use bevy::window::VideoModeSelection;
use bevy::window::WindowMode;
use bevy_kana::ToI32;
use bevy_kana::ToU32;
use serde::Deserialize;
use serde::Serialize;

use super::WindowKey;
use super::constants::DEFAULT_SCALE_FACTOR;
use super::constants::SETTLE_STABILITY_SECS;
use super::constants::SETTLE_TIMEOUT_SECS;

/// Event fired when a window restore completes and the window becomes visible.
///
/// This is an [`EntityEvent`] triggered on the window entity at the end of the restore
/// process, after position, size, and mode have been applied. Dependent crates can
/// observe this event to know the final restored window state.
///
/// Use an observer to receive this event:
/// ```ignore
/// // For all windows
/// app.add_observer(|trigger: On<WindowRestored>| {
///     let event = trigger.event();
///     // Use event.entity, event.size, event.mode, etc.
/// });
///
/// // For primary window only - check event.entity against PrimaryWindow query
/// fn on_window_restored(
///     trigger: On<WindowRestored>,
///     primary_window: Query<(), With<PrimaryWindow>>,
/// ) {
///     let event = trigger.event();
///     if primary_window.get(event.entity).is_ok() {
///         // Handle primary window only
///     }
/// }
/// ```
#[derive(EntityEvent, Debug, Clone, Reflect)]
pub struct WindowRestored {
    /// The window entity this event targets.
    pub entity:        Entity,
    /// Identifier for this window (primary or managed name).
    pub window_id:     WindowKey,
    /// Target position that was applied (None on Wayland).
    pub position:      Option<IVec2>,
    /// Target physical size that was applied (content area).
    pub size:          UVec2,
    /// Target logical size that was applied (content area).
    pub logical_size:  UVec2,
    /// Window mode that was applied.
    pub mode:          WindowMode,
    /// Monitor index the window was restored to.
    pub monitor_index: usize,
}

/// Event fired when the actual window state doesn't match what was requested.
///
/// After `try_apply_restore` completes, the library compares the intended restore
/// target against the live window state. If any field differs, this event fires
/// instead of [`WindowRestored`].
///
/// ## Sources
///
/// **Expected values** come from [`TargetPosition`], which is computed from the saved
/// RON state file at startup. These represent what the restore *intended* to achieve.
///
/// **Actual values** come from two live ECS sources, each chosen for accuracy:
///
/// - **`monitor_index`** → [`CurrentMonitor`](crate::CurrentMonitor) component, maintained by
///   `update_current_monitor` which queries winit's `current_monitor()` and maps it to the
///   `Monitors` list. This updates quickly when the compositor moves the window.
///
/// - **`position`, `size`, `mode`, `scale`** → the [`Window`](bevy::window::Window) component.
///   Position and size reflect `window.position` / `window.resolution`, and scale comes from
///   `window.resolution.scale_factor()`. These lag behind the compositor because they only update
///   when winit fires corresponding events (`ScaleFactorChanged`, `Resized`, `Moved`). A common
///   mismatch is the scale factor still reflecting the launch monitor while `CurrentMonitor` has
///   already updated to the target monitor.
///
/// This intentional split means a mismatch signals that the window hasn't fully settled
/// — the compositor accepted the request but winit hasn't yet delivered all the
/// resulting state changes.
#[derive(EntityEvent, Debug, Clone, Reflect)]
pub struct WindowRestoreMismatch {
    /// The window entity this event targets.
    pub entity:                Entity,
    /// Identifier for this window (primary or managed name).
    pub window_id:             WindowKey,
    /// Target position from `TargetPosition` (None on Wayland).
    pub expected_position:     Option<IVec2>,
    /// Actual position from `Window.position` (None on Wayland).
    pub actual_position:       Option<IVec2>,
    /// Target physical size from `TargetPosition`.
    pub expected_size:         UVec2,
    /// Actual physical size from `Window.resolution`.
    pub actual_size:           UVec2,
    /// Expected logical size from `TargetPosition`.
    pub expected_logical_size: UVec2,
    /// Actual logical size from `Window.resolution.width()`/`height()`.
    pub actual_logical_size:   UVec2,
    /// Target window mode from `TargetPosition`.
    pub expected_mode:         WindowMode,
    /// Actual window mode from `Window.mode`.
    pub actual_mode:           WindowMode,
    /// Target monitor index from `TargetPosition`.
    pub expected_monitor:      usize,
    /// Actual monitor index from `CurrentMonitor` (winit `current_monitor()`).
    pub actual_monitor:        usize,
    /// Target scale factor from `TargetPosition.target_scale`.
    pub expected_scale:        f64,
    /// Actual scale factor from `Window.resolution.scale_factor()`.
    /// Lags behind monitor changes; updates only on winit `ScaleFactorChanged`.
    pub actual_scale:          f64,
}

/// Saved video mode for exclusive fullscreen.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Reflect)]
pub(crate) struct SavedVideoMode {
    pub physical_size:           UVec2,
    pub bit_depth:               u16,
    pub refresh_rate_millihertz: u32,
}

impl SavedVideoMode {
    /// Convert to Bevy's `VideoMode`.
    #[must_use]
    pub const fn to_video_mode(&self) -> VideoMode {
        VideoMode {
            physical_size:           self.physical_size,
            bit_depth:               self.bit_depth,
            refresh_rate_millihertz: self.refresh_rate_millihertz,
        }
    }
}

/// Serializable window mode.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Reflect)]
pub(crate) enum SavedWindowMode {
    Windowed,
    BorderlessFullscreen,
    /// Exclusive fullscreen with optional specific video mode.
    Fullscreen {
        /// Video mode if explicitly set (`None` = use current display mode).
        video_mode: Option<SavedVideoMode>,
    },
}

impl SavedWindowMode {
    /// Convert to Bevy's `WindowMode` with the given monitor index.
    #[must_use]
    pub const fn to_window_mode(&self, monitor_index: usize) -> WindowMode {
        let selection = MonitorSelection::Index(monitor_index);
        match self {
            Self::Windowed => WindowMode::Windowed,
            Self::BorderlessFullscreen => WindowMode::BorderlessFullscreen(selection),
            Self::Fullscreen { video_mode: None } => {
                WindowMode::Fullscreen(selection, VideoModeSelection::Current)
            },
            Self::Fullscreen {
                video_mode: Some(saved),
            } => WindowMode::Fullscreen(
                selection,
                VideoModeSelection::Specific(saved.to_video_mode()),
            ),
        }
    }

    /// Check if this is a fullscreen mode (borderless or exclusive).
    #[must_use]
    pub const fn is_fullscreen(&self) -> bool { !matches!(self, Self::Windowed) }
}

impl From<&WindowMode> for SavedWindowMode {
    fn from(mode: &WindowMode) -> Self {
        match mode {
            WindowMode::Windowed => Self::Windowed,
            WindowMode::BorderlessFullscreen(_) => Self::BorderlessFullscreen,
            WindowMode::Fullscreen(_, video_mode_selection) => Self::Fullscreen {
                video_mode: match video_mode_selection {
                    VideoModeSelection::Current => None,
                    VideoModeSelection::Specific(mode) => Some(SavedVideoMode {
                        physical_size:           mode.physical_size,
                        bit_depth:               mode.bit_depth,
                        refresh_rate_millihertz: mode.refresh_rate_millihertz,
                    }),
                },
            },
        }
    }
}

/// Window decoration dimensions (title bar, borders).
pub(crate) struct WindowDecoration {
    pub width:  u32,
    pub height: u32,
}

/// Information from winit captured at startup.
#[derive(Resource)]
pub(crate) struct WinitInfo {
    pub starting_monitor_index: usize,
    pub decoration:             WindowDecoration,
}

impl WinitInfo {
    /// Get window decoration dimensions as a `UVec2`.
    #[must_use]
    pub const fn decoration(&self) -> UVec2 {
        UVec2::new(self.decoration.width, self.decoration.height)
    }
}

/// Token indicating X11 frame extent compensation is complete (W6 workaround).
///
/// This component gates `restore_windows` - the restore system cannot process
/// a window until this token exists on the entity. On Linux X11 with W6 workaround
/// enabled, this ensures frame extents are queried and position is compensated
/// before restore proceeds. On other platforms/configurations, the token is
/// inserted immediately during `load_target_position` since no compensation is needed.
#[derive(Component)]
pub(crate) struct X11FrameCompensated;

/// State for `MonitorScaleStrategy::HigherToLower` (high→low DPI restore).
///
/// When restoring from a high-DPI to low-DPI monitor, we must set position BEFORE size
/// because Bevy's `changed_windows` system processes size changes before position changes.
/// If we set both together, the window resizes first while still at the old position,
/// temporarily extending into the wrong monitor and triggering a scale factor bounce from macOS.
///
/// By moving a 1x1 window to the final position first, we ensure the window is already
/// at the correct location when we later apply size in `ApplySize`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)]
pub(crate) enum WindowRestoreState {
    /// Initial state: window needs to be moved to the target monitor to trigger a scale change.
    /// Handled by `restore_windows` which calls `apply_initial_move` and transitions to
    /// `WaitingForScaleChange`. This unified entry point replaces the old separate paths
    /// (`PreStartup` `move_to_target_monitor` for primary, inline guard for managed).
    NeedInitialMove,
    /// Position applied with compensation, waiting for `ScaleChanged` message.
    WaitingForScaleChange,
    /// Scale changed, ready to apply final size (position already set in phase 1).
    ApplySize,
}

/// Phase-based fullscreen restore state machine.
///
/// Fullscreen restore requires platform-specific sequencing:
///
/// - **Linux X11**: Move to target monitor first, wait a frame for the compositor to process, then
///   apply fullscreen mode as a fresh change.
/// - **Linux Wayland**: Apply mode directly (no position available).
/// - **Windows (DX12)**: Wait for surface creation before applying fullscreen (see <https://github.com/rust-windowing/winit/issues/3124>).
/// - **macOS**: Apply mode directly.
///
/// The key insight: on X11, if fullscreen mode is set in the same frame as
/// position, the compositor may briefly honor it then revert. Splitting into
/// separate frames ensures each change is processed independently.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)]
pub(crate) enum FullscreenRestoreState {
    /// Move window to target monitor position. Skipped on Wayland (no position).
    MoveToMonitor,
    /// Wait for compositor to process the position change (1 frame).
    WaitForMove,
    /// Wait for GPU surface creation (Windows DX12 workaround, winit #3124).
    WaitForSurface,
    /// Apply the fullscreen mode.
    ApplyMode,
}

/// Restore strategy based on scale factor relationship between launch and target monitors.
///
/// # The Problem
///
/// Winit's `request_inner_size` and `set_outer_position` use the current window's scale factor
/// when interpreting coordinates, rather than the target monitor's scale factor. This causes
/// incorrect sizing/positioning when restoring windows across monitors with different DPIs.
///
/// See: <https://github.com/rust-windowing/winit/issues/4440>
///
/// # Platform Differences
///
/// ## Windows
///
/// - **Position**: Winit uses physical coordinates directly - no compensation needed
/// - **Size**: Winit applies scale conversion using current monitor's scale - needs compensation
/// - Strategy: `CompensateSizeOnly` when scales differ
///
/// Note: Windows has a separate issue where `GetWindowRect` includes an invisible
/// resize border (~7-11 pixels). See: <https://github.com/rust-windowing/winit/issues/4107>
///
/// ## macOS / Linux X11
///
/// - **Position**: Winit converts using current monitor's scale - needs compensation
/// - **Size**: Winit converts using current monitor's scale - needs compensation
/// - Strategy: `LowerToHigher` or `HigherToLower` depending on scale relationship
///
/// ## Linux Wayland
///
/// Cannot detect starting monitor or set position, so no compensation is applied.
///
/// # Variants
///
/// - **`ApplyUnchanged`**: Apply position and size directly without compensation.
///
/// - **`CompensateSizeOnly`**: Windows only. Uses two-phase approach via `WindowRestoreState`:
///   1. Apply position directly + compensated size (triggers `WM_DPICHANGED`)
///   2. After scale changes, re-apply exact target size to eliminate rounding errors
///
/// - **`LowerToHigher`**: macOS/Linux X11. Low→High DPI (1x→2x, ratio < 1). Multiply both position
///   and size by ratio.
///
/// - **`HigherToLower`**: macOS/Linux X11. High→Low DPI (2x→1x, ratio > 1). Uses two-phase approach
///   via `WindowRestoreState` to avoid size clamping:
///   1. Move a 1x1 window to final position (compensated) to trigger scale change
///   2. After scale changes, apply size without compensation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)]
pub(crate) enum MonitorScaleStrategy {
    /// Same scale - apply position and size directly.
    ApplyUnchanged,
    /// Windows cross-DPI: position direct, size in two phases.
    CompensateSizeOnly(WindowRestoreState),
    /// Low→High DPI (1x→2x) - apply with compensation (ratio < 1).
    LowerToHigher,
    /// High→Low DPI (2x→1x) - requires two phases (see enum docs).
    HigherToLower(WindowRestoreState),
}

/// Holds the target window state during the restore process.
///
/// All values are pre-computed with proper types. Casting from saved state
/// happens once during loading, not scattered throughout the restore logic.
///
/// Dimensions stored here are **inner** (content area only), matching what
/// Bevy's `Window.resolution` represents and what we save to the state file.
/// Outer dimensions (including title bar) are only used during loading for
/// clamping calculations.
#[derive(Component, Reflect)]
#[reflect(Component)]
pub(crate) struct TargetPosition {
    /// Final clamped position (adjusted to fit within target monitor).
    /// None on Wayland where clients can't access window position.
    pub position:             Option<IVec2>,
    /// Target width in physical pixels (content area, excluding window decoration).
    /// Computed from `logical_width * target_scale`. Applied via `set_physical_resolution()`.
    pub width:                u32,
    /// Target height in physical pixels (content area, excluding window decoration).
    /// Computed from `logical_height * target_scale`. Applied via `set_physical_resolution()`.
    pub height:               u32,
    /// Target width in logical pixels, from `WindowState.logical_width`.
    /// Used for event reporting.
    pub logical_width:        u32,
    /// Target height in logical pixels, from `WindowState.logical_height`.
    /// Used for event reporting.
    pub logical_height:       u32,
    /// Scale factor of the target monitor.
    pub target_scale:         f64,
    /// Scale factor of the monitor where the window starts (keyboard focus monitor).
    pub starting_scale:       f64,
    /// Strategy for handling scale factor differences between monitors.
    pub scale_strategy:       MonitorScaleStrategy,
    /// Window mode to restore.
    pub mode:                 SavedWindowMode,
    /// Target monitor index for fullscreen restore.
    /// On non-Wayland platforms, this could be derived from position, but Wayland
    /// doesn't provide window position, so we store it explicitly.
    pub target_monitor_index: usize,
    /// Fullscreen restore state (DX12/DXGI workaround).
    pub fullscreen_state:     Option<FullscreenRestoreState>,
    /// Settling state. When set, `try_apply_restore` has completed and we're waiting
    /// for the compositor/winit to deliver stable, matching state.
    ///
    /// Uses a two-timer approach:
    /// - **Stability timer** (200ms): resets whenever any compared value changes between frames.
    ///   Fires `WindowRestored` when all values have been stable for 200ms.
    /// - **Total timeout** (1s): hard deadline. If values never stabilize for 200ms continuously,
    ///   fires `WindowRestoreMismatch` with whatever state exists at timeout.
    ///
    /// This handles compositor artifacts like Wayland `wl_surface.enter`/`leave` bounces
    /// where `current_monitor()` transiently reports the wrong monitor during fullscreen
    /// transitions.
    pub settle_state:         Option<SettleState>,
}

/// Tracks the two-timer settling state after restore completes.
#[derive(Debug, Clone, Reflect)]
pub(crate) struct SettleState {
    /// Hard deadline timer — fires mismatch if stability is never reached.
    pub total_timeout:   Timer,
    /// Resets whenever any compared value changes between frames.
    pub stability_timer: Timer,
    /// Snapshot of last frame's compared values, used to detect changes.
    pub last_snapshot:   Option<SettleSnapshot>,
}

impl SettleState {
    /// Create a new settle state with default durations.
    #[must_use]
    pub fn new() -> Self {
        Self {
            total_timeout:   Timer::from_seconds(SETTLE_TIMEOUT_SECS, TimerMode::Once),
            stability_timer: Timer::from_seconds(SETTLE_STABILITY_SECS, TimerMode::Once),
            last_snapshot:   None,
        }
    }
}

/// Snapshot of compared values for change detection between frames.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)]
pub(crate) struct SettleSnapshot {
    pub position: Option<IVec2>,
    pub size:     UVec2,
    pub mode:     WindowMode,
    pub monitor:  usize,
}

impl TargetPosition {
    /// Get the target position as an `IVec2`, if available.
    #[must_use]
    pub const fn position(&self) -> Option<IVec2> { self.position }

    /// Get the target physical size as a `UVec2`.
    #[must_use]
    pub const fn size(&self) -> UVec2 { UVec2::new(self.width, self.height) }

    /// Get the target logical size as a `UVec2`.
    #[must_use]
    pub const fn logical_size(&self) -> UVec2 {
        UVec2::new(self.logical_width, self.logical_height)
    }

    /// Scale ratio between starting and target monitors.
    #[must_use]
    pub fn ratio(&self) -> f64 { self.starting_scale / self.target_scale }

    /// Position compensated for scale factor differences.
    ///
    /// Multiplies position by the ratio to account for winit dividing by launch scale.
    /// Returns None if position is not available (Wayland).
    #[must_use]
    pub fn compensated_position(&self) -> Option<IVec2> {
        let ratio = self.ratio();
        self.position.map(|pos| {
            IVec2::new(
                (f64::from(pos.x) * ratio).to_i32(),
                (f64::from(pos.y) * ratio).to_i32(),
            )
        })
    }

    /// Size compensated for scale factor differences.
    ///
    /// Multiplies size by the ratio to account for winit dividing by launch scale.
    #[must_use]
    pub fn compensated_size(&self) -> UVec2 {
        let ratio = self.ratio();
        UVec2::new(
            (f64::from(self.width) * ratio).to_u32(),
            (f64::from(self.height) * ratio).to_u32(),
        )
    }
}

/// Configuration for the `RestoreWindowPlugin`.
#[derive(Resource, Clone)]
pub(crate) struct RestoreWindowConfig {
    /// Full path to the state file.
    pub path:          PathBuf,
    /// Snapshot of window states as loaded from the file at startup.
    /// Populated during restore so downstream code can compare intended vs actual state.
    /// Entries persist as a read-only snapshot for the example's File column.
    pub loaded_states: std::collections::HashMap<WindowKey, WindowState>,
}

/// Saved window state persisted to the RON file.
///
/// All spatial values are in **logical pixels** — they represent the user's visual intent
/// and are independent of scale factor. On restore, both position and size are converted
/// to physical pixels using the target monitor's scale factor in
/// [`compute_target_position`](crate::restore_plan::compute_target_position).
///
/// `monitor_scale` records the scale factor of the monitor at save time. It is informational
/// only — restore uses the target monitor's live scale factor, not this saved value.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct WindowState {
    /// Top-left corner of the window content area in logical pixels.
    /// `None` on Wayland where clients cannot access window position.
    #[serde(alias = "position")]
    pub logical_position: Option<(i32, i32)>,
    /// Content area width in logical pixels (excludes window decoration).
    pub logical_width:    u32,
    /// Content area height in logical pixels (excludes window decoration).
    pub logical_height:   u32,
    /// Scale factor of the monitor at save time (informational, not used during restore).
    #[serde(default = "default_monitor_scale")]
    pub monitor_scale:    f64,
    pub monitor_index:    usize,
    pub mode:             SavedWindowMode,
    #[serde(default)]
    pub app_name:         String,
}

/// Default monitor scale for deserialization of legacy files missing the field.
const fn default_monitor_scale() -> f64 { DEFAULT_SCALE_FACTOR }

/// Marks a window entity as managed by the window manager plugin.
///
/// Add this component to any secondary window entity to opt into automatic
/// save/restore behavior. The primary window is always managed automatically
/// using the key `"primary"` in the state file.
///
/// Each managed window must have a unique `name`. Duplicate names
/// will cause a panic.
///
/// # Example
///
/// ```ignore
/// commands.spawn((
///     Window { title: "Inspector".into(), ..default() },
///     ManagedWindow { name: "inspector".into() },
/// ));
/// ```
#[derive(Component, Clone, Reflect)]
#[reflect(Component)]
pub struct ManagedWindow {
    /// Unique name used as the key in the state file.
    pub name: String,
}

/// Controls what happens to saved state when a managed window is despawned.
///
/// Set as a resource on the app to control persistence behavior for all windows.
#[derive(Resource, Default, Clone, Debug, PartialEq, Eq, Reflect)]
#[reflect(Resource)]
pub enum ManagedWindowPersistence {
    /// Default: saved state persists even if window is closed during the session.
    /// All windows ever opened are remembered in the state file.
    #[default]
    RememberAll,
    /// Only windows open at time of save are persisted.
    /// Closing a window removes its entry from the state file.
    ActiveOnly,
}

/// Internal registry to track managed window names and detect duplicates.
#[derive(Resource, Default)]
pub(crate) struct ManagedWindowRegistry {
    /// Set of registered window names (for duplicate detection).
    pub(crate) names:    std::collections::HashSet<String>,
    /// Map from entity to window name (for cleanup on removal).
    pub(crate) entities: std::collections::HashMap<Entity, String>,
}