From 10992b77a0aafdaf1166493df3d0c044bf8975ea Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Mon, 23 Aug 2021 12:20:08 -0700 Subject: [PATCH] Only iterate panes one time when updating settings (#10997) The original code for settings reload iterated the entire tree of panes for every profile in the new settings (O(mn)) and constructed a TerminalSettings object for every profile even if it later went unused. This implementation: 1. Collects all new profiles keyed by guid 1.a. Adds the "defaults" profile to the map 2. Iterates every pane, just once, and updates its profile if it shows up in the list by GUID. I've merged all of the per-tab code into a single loop. Because of 1.a., this code can now update panes that are hosting the "base" profile. --- src/cascadia/TerminalApp/Pane.cpp | 50 ++++++---------- src/cascadia/TerminalApp/Pane.h | 8 +++ src/cascadia/TerminalApp/TerminalPage.cpp | 70 +++++++++++++++-------- src/cascadia/TerminalApp/TerminalTab.cpp | 11 ++-- src/cascadia/TerminalApp/TerminalTab.h | 4 +- 5 files changed, 77 insertions(+), 66 deletions(-) diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 127884b58..75def5f58 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -1049,10 +1049,7 @@ void Pane::_FocusFirstChild() } // Method Description: -// - Attempts to update the settings of this pane or any children of this pane. -// * If this pane is a leaf, and our profile guid matches the parameter, then -// we'll apply the new settings to our control. -// * If we're not a leaf, we'll recurse on our children. +// - Updates the settings of this pane, presuming that it is a leaf. // Arguments: // - settings: The new TerminalSettings to apply to any matching controls // - profile: The profile from which these settings originated. @@ -1060,37 +1057,24 @@ void Pane::_FocusFirstChild() // - void Pane::UpdateSettings(const TerminalSettingsCreateResult& settings, const Profile& profile) { - if (!_IsLeaf()) + assert(_IsLeaf()); + + _profile = profile; + auto controlSettings = _control.Settings().as(); + // Update the parent of the control's settings object (and not the object itself) so + // that any overrides made by the control don't get affected by the reload + controlSettings.SetParent(settings.DefaultSettings()); + auto unfocusedSettings{ settings.UnfocusedSettings() }; + if (unfocusedSettings) { - _firstChild->UpdateSettings(settings, profile); - _secondChild->UpdateSettings(settings, profile); - } - else - { - // Because this call may be coming in with a different settings tree, - // we want to map the incoming profile based on its GUID. - // Failure to find a matching profile will result in a pane holding - // a reference to a deleted profile (which is okay!). - if (profile.Guid() == _profile.Guid()) - { - _profile = profile; - auto controlSettings = _control.Settings().as(); - // Update the parent of the control's settings object (and not the object itself) so - // that any overrides made by the control don't get affected by the reload - controlSettings.SetParent(settings.DefaultSettings()); - auto unfocusedSettings{ settings.UnfocusedSettings() }; - if (unfocusedSettings) - { - // Note: the unfocused settings needs to be entirely unchanged _except_ we need to - // set its parent to the settings object that lives in the control. This is because - // the overrides made by the control live in that settings object, so we want to make - // sure the unfocused settings inherit from that. - unfocusedSettings.SetParent(controlSettings); - } - _control.UnfocusedAppearance(unfocusedSettings); - _control.UpdateSettings(); - } + // Note: the unfocused settings needs to be entirely unchanged _except_ we need to + // set its parent to the settings object that lives in the control. This is because + // the overrides made by the control live in that settings object, so we want to make + // sure the unfocused settings inherit from that. + unfocusedSettings.SetParent(controlSettings); } + _control.UnfocusedAppearance(unfocusedSettings); + _control.UpdateSettings(); } // Method Description: diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index 681fd7ba9..76bab850b 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -55,6 +55,14 @@ public: winrt::Microsoft::Terminal::Control::TermControl GetTerminalControl(); winrt::Microsoft::Terminal::Settings::Model::Profile GetFocusedProfile(); + // Method Description: + // - If this is a leaf pane, return its profile. + // - If this is a branch/root pane, return nullptr. + winrt::Microsoft::Terminal::Settings::Model::Profile GetProfile() const + { + return _profile; + } + winrt::Windows::UI::Xaml::Controls::Grid GetRootElement(); bool WasLastFocused() const noexcept; diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 07bd2e19f..12cec3594 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -1996,36 +1996,56 @@ namespace winrt::TerminalApp::implementation _HookupKeyBindings(_settings.ActionMap()); // Refresh UI elements - auto profiles = _settings.ActiveProfiles(); - for (const auto& profile : profiles) - { - try - { - auto settings{ TerminalSettings::CreateWithProfile(_settings, profile, *_bindings) }; - for (auto tab : _tabs) - { - if (auto terminalTab = _GetTerminalTabImpl(tab)) - { - terminalTab->UpdateSettings(settings, profile); - } - } - } - CATCH_LOG(); + // Mapping by GUID isn't _excellent_ because the defaults profile doesn't have a stable GUID; however, + // when we stabilize its guid this will become fully safe. + std::unordered_map> profileGuidSettingsMap; + const auto profileDefaults{ _settings.ProfileDefaults() }; + const auto allProfiles{ _settings.AllProfiles() }; + + profileGuidSettingsMap.reserve(allProfiles.Size() + 1); + + // Include the Defaults profile for consideration + profileGuidSettingsMap.insert_or_assign(profileDefaults.Guid(), std::pair{ profileDefaults, nullptr }); + for (const auto& newProfile : allProfiles) + { + // Avoid creating a TerminalSettings right now. They're not totally cheap, and we suspect that users with many + // panes may not be using all of their profiles at the same time. Lazy evaluation is king! + profileGuidSettingsMap.insert_or_assign(newProfile.Guid(), std::pair{ newProfile, nullptr }); } - // GH#2455: If there are any panes with controls that had been - // initialized with a Profile that no longer exists in our list of - // profiles, we'll leave it unmodified. The profile doesn't exist - // anymore, so we can't possibly update its settings. - - // Update the icon of the tab for the currently focused profile in that tab. - // Only do this for TerminalTabs. Other types of tabs won't have multiple panes - // and profiles so the Title and Icon will be set once and only once on init. - for (auto tab : _tabs) + for (const auto& tab : _tabs) { - if (auto terminalTab = _GetTerminalTabImpl(tab)) + if (auto terminalTab{ _GetTerminalTabImpl(tab) }) { + terminalTab->UpdateSettings(); + + // Manually enumerate the panes in each tab; this will let us recycle TerminalSettings + // objects but only have to iterate one time. + terminalTab->GetRootPane()->WalkTree([&](auto&& pane) { + if (const auto profile{ pane->GetProfile() }) + { + const auto found{ profileGuidSettingsMap.find(profile.Guid()) }; + // GH#2455: If there are any panes with controls that had been + // initialized with a Profile that no longer exists in our list of + // profiles, we'll leave it unmodified. The profile doesn't exist + // anymore, so we can't possibly update its settings. + if (found != profileGuidSettingsMap.cend()) + { + auto& pair{ found->second }; + if (!pair.second) + { + pair.second = TerminalSettings::CreateWithProfile(_settings, pair.first, *_bindings); + } + pane->UpdateSettings(pair.second, pair.first); + } + } + return false; + }); + + // Update the icon of the tab for the currently focused profile in that tab. + // Only do this for TerminalTabs. Other types of tabs won't have multiple panes + // and profiles so the Title and Icon will be set once and only once on init. _UpdateTabIcon(*terminalTab); // Force the TerminalTab to re-grab its currently active control's title. diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index c3fc23242..6f415754c 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -263,16 +263,13 @@ namespace winrt::TerminalApp::implementation } // Method Description: - // - Attempts to update the settings of this tab's tree of panes. - // Arguments: - // - settings: The new TerminalSettingsCreateResult to apply to any matching controls - // - profile: The GUID of the profile these settings should apply to. + // - Attempts to update the settings that apply to this tab. + // - Panes are handled elsewhere, by somebody who can establish broader knowledge + // of the settings that apply to all tabs. // Return Value: // - - void TerminalTab::UpdateSettings(const TerminalSettingsCreateResult& settings, const Profile& profile) + void TerminalTab::UpdateSettings() { - _rootPane->UpdateSettings(settings, profile); - // The tabWidthMode may have changed, update the header control accordingly _UpdateHeaderControlMaxWidth(); } diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h index b72372d18..4952fad45 100644 --- a/src/cascadia/TerminalApp/TerminalTab.h +++ b/src/cascadia/TerminalApp/TerminalTab.h @@ -62,7 +62,7 @@ namespace winrt::TerminalApp::implementation bool SwapPane(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction); bool FocusPane(const uint32_t id); - void UpdateSettings(const Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, const winrt::Microsoft::Terminal::Settings::Model::Profile& profile); + void UpdateSettings(); winrt::fire_and_forget UpdateTitle(); void Shutdown() override; @@ -90,6 +90,8 @@ namespace winrt::TerminalApp::implementation std::shared_ptr GetActivePane() const; winrt::TerminalApp::TaskbarState GetCombinedTaskbarState() const; + std::shared_ptr GetRootPane() const { return _rootPane; } + winrt::TerminalApp::TerminalTabStatus TabStatus() { return _tabStatus;