Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow ThrottledFunc to work on different types of dispatcher #10187

Merged
60 commits merged into from
Aug 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
e4478ee
Only access ControlInteractivity through the projection
zadjii-msft Apr 29, 2021
400b35f
Only 24 more errors to go
zadjii-msft Apr 30, 2021
42b970b
Totally upend the control AutomationPeer workings
zadjii-msft Apr 30, 2021
4578c13
some comments that should have been in the previous commit. 16 errors…
zadjii-msft Apr 30, 2021
d8cc047
GetHoveredCell/UpdateHoveredCell. 12 remain.
zadjii-msft Apr 30, 2021
21a97b6
Revert "Revert "Use DComp surface handle for Swap Chain management.""
zadjii-msft May 3, 2021
2e861d8
some minor cleanup
zadjii-msft May 3, 2021
e20caae
Merge branch 'dev/migrie/f/oop/use-dcomp-handle' into dev/migrie/inte…
zadjii-msft May 3, 2021
d667f3b
Use the HANDLE for the swapchainin the core projection
zadjii-msft May 3, 2021
62cbf30
it builds and runs so I guess it's done now
zadjii-msft May 3, 2021
445bdf6
Do the delayload dance
zadjii-msft May 6, 2021
1376721
Merge branch 'dev/migrie/f/oop/use-dcomp-handle' into dev/migrie/inte…
zadjii-msft May 6, 2021
a1ce7cb
What a ridiculous hack
zadjii-msft May 7, 2021
03fb764
fix accessibility
zadjii-msft May 7, 2021
5e7d270
Merge remote-tracking branch 'origin/main' into dev/migrie/interactiv…
zadjii-msft May 7, 2021
6e031e5
I want to get the padding out of interactivity, but the control bound…
zadjii-msft May 7, 2021
47410c9
now this works!
zadjii-msft May 7, 2021
0869456
Some cleanup
zadjii-msft May 7, 2021
dace0c4
a bit more cleanup
zadjii-msft May 7, 2021
12b78f5
Merge remote-tracking branch 'origin/main' into dev/migrie/f/oop/use-…
zadjii-msft May 7, 2021
e340fe9
Merge branch 'dev/migrie/f/oop/use-dcomp-handle' into dev/migrie/inte…
zadjii-msft May 7, 2021
b7b23a8
god I knew I left these behind
zadjii-msft May 7, 2021
086710a
Merge remote-tracking branch 'origin/main' into dev/migrie/interactiv…
zadjii-msft May 12, 2021
21a6370
fix the tests
zadjii-msft May 12, 2021
4efdf39
This fixes nvda, so nowwe just have to polish
zadjii-msft May 13, 2021
59e911d
minor typo cleanup
zadjii-msft May 13, 2021
6f07004
some consts
zadjii-msft May 13, 2021
ff3b808
this is almost exclusively nits
zadjii-msft May 13, 2021
559d1de
simple nits
zadjii-msft May 20, 2021
804a114
this one's easy too
zadjii-msft May 20, 2021
0f0af03
An enum does in fact work across the process boundary
zadjii-msft May 21, 2021
95300d4
Merge remote-tracking branch 'origin/main' into dev/migrie/interactiv…
zadjii-msft May 21, 2021
1de9485
Merge remote-tracking branch 'origin/main' into dev/migrie/interactiv…
zadjii-msft May 24, 2021
1bc27d0
fix the build
zadjii-msft May 24, 2021
3cace60
chances are, this will fix the x86 build
zadjii-msft May 24, 2021
265bd9f
I am ashamed, this should not be there
zadjii-msft May 24, 2021
c8a2957
Port the automation peer crash fix to this branch, @carlos-zamora
zadjii-msft May 24, 2021
074f67c
The comment literally says to put this first. Why didn't I put this f…
zadjii-msft May 25, 2021
5a0840a
Merge remote-tracking branch 'origin/main' into dev/migrie/interactiv…
zadjii-msft May 25, 2021
b8158d4
Revert "I really shouldn't be doing the throttledfunc thing in this PR"
zadjii-msft May 25, 2021
4e7a500
oops, that doesn't belong _anywhere_
zadjii-msft May 25, 2021
f5665c5
Merge remote-tracking branch 'origin/main' into dev/migrie/interactiv…
DHowett Jun 10, 2021
056c354
Merge remote-tracking branch 'origin/main' into dev/migrie/interactiv…
zadjii-msft Jul 8, 2021
e643885
minor nits from carlos
zadjii-msft Jul 8, 2021
33653e0
Merge branch 'dev/migrie/interactivity-projection-000' of https://git…
zadjii-msft Jul 8, 2021
326cf08
Merge remote-tracking branch 'origin/main' into dev/migrie/oop/thrott…
zadjii-msft Jul 8, 2021
a765769
Merge remote-tracking branch 'origin/dev/migrie/interactivity-project…
zadjii-msft Jul 8, 2021
3d59a67
Merge remote-tracking branch 'origin/main' into dev/migrie/interactiv…
zadjii-msft Jul 8, 2021
a4be792
Merge remote-tracking branch 'origin/dev/migrie/interactivity-project…
zadjii-msft Jul 8, 2021
cf15df7
Merge remote-tracking branch 'origin/main' into dev/migrie/oop/thrott…
zadjii-msft Jul 19, 2021
bcce581
fix tets
zadjii-msft Jul 20, 2021
a6099fb
Merge remote-tracking branch 'origin/main' into dev/migrie/oop/thrott…
zadjii-msft Aug 4, 2021
f9b4216
Remove one of the unnecessary throttling layers, IMEs still work as e…
zadjii-msft Aug 4, 2021
f43f897
this prevents the tests from just immediately crashing, but when Cont…
zadjii-msft Aug 4, 2021
122b9a3
these ones are a little volatile, run them in isolation
zadjii-msft Aug 5, 2021
a7ff38f
commentary
zadjii-msft Aug 5, 2021
3dbac4a
Revert "Remove one of the unnecessary throttling layers, IMEs still w…
zadjii-msft Aug 5, 2021
b8fa11a
frick
zadjii-msft Aug 5, 2021
a93b10b
Well, this makes the tests pass, but it feels stupid
zadjii-msft Aug 9, 2021
8baa4f4
get rid of this nonsense
zadjii-msft Aug 9, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/cascadia/TerminalApp/AppLogic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ namespace winrt::TerminalApp::implementation
_isElevated = _isUserAdmin();
_root = winrt::make_self<TerminalPage>();

_reloadSettings = std::make_shared<ThrottledFuncTrailing<>>(_root->Dispatcher(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() {
_reloadSettings = std::make_shared<ThrottledFuncTrailing<>>(winrt::Windows::System::DispatcherQueue::GetForCurrentThread(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() {
if (auto self{ weakSelf.get() })
{
self->_ReloadSettings();
Expand Down
106 changes: 88 additions & 18 deletions src/cascadia/TerminalControl/ControlCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ using namespace winrt::Windows::Graphics::Display;
using namespace winrt::Windows::System;
using namespace winrt::Windows::ApplicationModel::DataTransfer;

// The minimum delay between updates to the scroll bar's values.
// The updates are throttled to limit power usage.
constexpr const auto ScrollBarUpdateInterval = std::chrono::milliseconds(8);

// The minimum delay between updating the TSF input control.
constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(100);

// The minimum delay between updating the locations of regex patterns
constexpr const auto UpdatePatternLocationsInterval = std::chrono::milliseconds(500);

namespace winrt::Microsoft::Terminal::Control::implementation
{
// Helper static function to ensure that all ambiguous-width glyphs are reported as narrow.
Expand Down Expand Up @@ -94,6 +104,61 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto pfnTerminalTaskbarProgressChanged = std::bind(&ControlCore::_terminalTaskbarProgressChanged, this);
_terminal->TaskbarProgressChangedCallback(pfnTerminalTaskbarProgressChanged);

// Get our dispatcher. If we're hosted in-proc with XAML, this will get
// us the same dispatcher as TermControl::Dispatcher(). If we're out of
// proc, this'll return null. We'll need to instead make a new
// DispatcherQueue (on a new thread), so we can use that for throttled
// functions.
_dispatcher = winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
if (!_dispatcher)
{
auto controller{ winrt::Windows::System::DispatcherQueueController::CreateOnDedicatedThread() };
_dispatcher = controller.DispatcherQueue();
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
}

// A few different events should be throttled, so they don't fire absolutely all the time:
// * _tsfTryRedrawCanvas: When the cursor position moves, we need to
// inform TSF, so it can move the canvas for the composition. We
// throttle this so that we're not hopping across the process boundary
// every time that the cursor moves.
// * _updatePatternLocations: When there's new output, or we scroll the
// viewport, we should re-check if there are any visible hyperlinks.
// But we don't really need to do this every single time text is
// output, we can limit this update to once every 500ms.
// * _updateScrollBar: Same idea as the TSF update - we don't _really_
// need to hop across the process boundary every time text is output.
// We can throttle this to once every 8ms, which will get us out of
// the way of the main output & rendering threads.
_tsfTryRedrawCanvas = std::make_shared<ThrottledFuncTrailing<>>(
_dispatcher,
TsfRedrawInterval,
[weakThis = get_weak()]() {
if (auto core{ weakThis.get() }; !core->_IsClosing())
{
core->_CursorPositionChangedHandlers(*core, nullptr);
}
});

_updatePatternLocations = std::make_shared<ThrottledFuncTrailing<>>(
_dispatcher,
UpdatePatternLocationsInterval,
[weakThis = get_weak()]() {
if (auto core{ weakThis.get() }; !core->_IsClosing())
{
core->UpdatePatternLocations();
}
});

_updateScrollBar = std::make_shared<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>>(
_dispatcher,
ScrollBarUpdateInterval,
[weakThis = get_weak()](const auto& update) {
if (auto core{ weakThis.get() }; !core->_IsClosing())
{
core->_ScrollPositionChangedHandlers(*core, update);
}
});

UpdateSettings(settings);
}

Expand Down Expand Up @@ -1103,15 +1168,28 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// TODO GH#9617: refine locking around pattern tree
_terminal->ClearPatternTree();

_ScrollPositionChangedHandlers(*this,
winrt::make<ScrollPositionChangedArgs>(viewTop,
viewHeight,
bufferSize));
// Start the throttled update of our scrollbar.
auto update{ winrt::make<ScrollPositionChangedArgs>(viewTop,
viewHeight,
bufferSize) };
if (!_inUnitTests)
{
_updateScrollBar->Run(update);
}
else
{
_ScrollPositionChangedHandlers(*this, update);
}

// Additionally, start the throttled update of where our links are.
_updatePatternLocations->Run();
}

void ControlCore::_terminalCursorPositionChanged()
{
_CursorPositionChangedHandlers(*this, nullptr);
// When the buffer's cursor moves, start the throttled func to
// eventually dispatch a CursorPositionChanged event.
_tsfTryRedrawCanvas->Run();
Comment on lines +1190 to +1192
Copy link
Member

@lhecker lhecker Jul 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if I understand it correctly, this starts a throttled func to throw an CursorPositionChanged event every 100ms, right? But TermControl also got a _tsfTryRedrawCanvas which only triggers the update every 100ms. Now it's only updating every 200-300ms (depending on the race between the two timers). Shouldn't we remove either of the two throttled funcs then?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay so you have a point here. I'm gonna keep the throttling on the core side, not the control side. The Core will want to throttle how often it hops across the COM boundary to tell the UI to update. The UI doesn't care so much - it should just update whenever the Core tells it to.

}

void ControlCore::_terminalTaskbarProgressChanged()
Expand Down Expand Up @@ -1221,8 +1299,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation

void ControlCore::Close()
{
if (!_closing.exchange(true))
if (!_IsClosing())
{
_closing = true;

// Stop accepting new output and state changes before we disconnect everything.
_connection.TerminalOutput(_connectionOutputEventToken);
_connectionStateChangedRevoker.revoke();
Expand Down Expand Up @@ -1400,18 +1480,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
_terminal->Write(hstr);

// NOTE: We're raising an event here to inform the TermControl that
// output has been received, so it can queue up a throttled
// UpdatePatternLocations call. In the future, we should have the
// _updatePatternLocations ThrottledFunc internal to this class, and
// run on this object's dispatcher queue.
//
// We're not doing that quite yet, because the Core will eventually
// be out-of-proc from the UI thread, and won't be able to just use
// the UI thread as the dispatcher queue thread.
//
// See TODO: https://github.com/microsoft/terminal/projects/5#card-50760282
_ReceivedOutputHandlers(*this, nullptr);
// Start the throttled update of where our hyperlinks are.
_updatePatternLocations->Run();
}

}
23 changes: 22 additions & 1 deletion src/cascadia/TerminalControl/ControlCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation

private:
bool _initializedTerminal{ false };
std::atomic<bool> _closing{ false };
bool _closing{ false };

TerminalConnection::ITerminalConnection _connection{ nullptr };
event_token _connectionOutputEventToken;
Expand Down Expand Up @@ -206,6 +206,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
double _panelHeight{ 0 };
double _compositionScale{ 0 };

winrt::Windows::System::DispatcherQueue _dispatcher{ nullptr };
std::shared_ptr<ThrottledFuncTrailing<>> _tsfTryRedrawCanvas;
std::shared_ptr<ThrottledFuncTrailing<>> _updatePatternLocations;
std::shared_ptr<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>> _updateScrollBar;

winrt::fire_and_forget _asyncCloseConnection();

void _setFontSize(int fontSize);
Expand Down Expand Up @@ -239,8 +244,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _connectionOutputHandler(const hstring& hstr);
void _updateHoveredCell(const std::optional<til::point> terminalPosition);

inline bool _IsClosing() const noexcept
{
#ifndef NDEBUG
if (_dispatcher)
{
// _closing isn't atomic and may only be accessed from the main thread.
//
// Though, the unit tests don't actually run in TAEF's main
// thread, so we don't care when we're running in tests.
assert(_inUnitTests || _dispatcher.HasThreadAccess());
}
#endif
return _closing;
}

friend class ControlUnitTests::ControlCoreTests;
friend class ControlUnitTests::ControlInteractivityTests;
bool _inUnitTests{ false };
};
}

Expand Down
85 changes: 25 additions & 60 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ using namespace winrt::Windows::ApplicationModel::DataTransfer;
constexpr const auto ScrollBarUpdateInterval = std::chrono::milliseconds(8);

// The minimum delay between updating the TSF input control.
constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(100);
// This is already throttled primarily in the ControlCore, with a timeout of 100ms. We're adding another smaller one here, as the (potentially x-proc) call will come in off the UI thread
constexpr const auto TsfRedrawInterval = std::chrono::milliseconds(8);

// The minimum delay between updating the locations of regex patterns
constexpr const auto UpdatePatternLocationsInterval = std::chrono::milliseconds(500);
Expand Down Expand Up @@ -64,10 +65,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_interactivity = winrt::make<implementation::ControlInteractivity>(settings, connection);
_core = _interactivity.Core();

// Use a manual revoker on the output event, so we can immediately stop
// worrying about it on destruction.
_coreOutputEventToken = _core.ReceivedOutput({ this, &TermControl::_coreReceivedOutput });

// These events might all be triggered by the connection, but that
// should be drained and closed before we complete destruction. So these
// are safe.
Expand Down Expand Up @@ -104,37 +101,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
});

// Many of these ThrottledFunc's should be inside ControlCore. However,
// currently they depend on the Dispatcher() of the UI thread, which the
// Core eventually won't have access to. When we get to
// https://github.com/microsoft/terminal/projects/5#card-50760282
// then we'll move the applicable ones.
//
// These four throttled functions are triggered by terminal output and interact with the UI.
// Get our dispatcher. This will get us the same dispatcher as
// TermControl::Dispatcher().
auto dispatcher = winrt::Windows::System::DispatcherQueue::GetForCurrentThread();

// These three throttled functions are triggered by terminal output and interact with the UI.
// Since Close() is the point after which we are removed from the UI, but before the
// destructor has run, we MUST check control->_IsClosing() before actually doing anything.
_tsfTryRedrawCanvas = std::make_shared<ThrottledFuncTrailing<>>(
Dispatcher(),
TsfRedrawInterval,
[weakThis = get_weak()]() {
if (auto control{ weakThis.get() }; !control->_IsClosing())
{
control->TSFInputControl().TryRedrawCanvas();
}
});

_updatePatternLocations = std::make_shared<ThrottledFuncTrailing<>>(
Dispatcher(),
UpdatePatternLocationsInterval,
[weakThis = get_weak()]() {
if (auto control{ weakThis.get() }; !control->_IsClosing())
{
control->_core.UpdatePatternLocations();
}
});

_playWarningBell = std::make_shared<ThrottledFuncLeading>(
Dispatcher(),
dispatcher,
TerminalWarningBellInterval,
[weakThis = get_weak()]() {
if (auto control{ weakThis.get() }; !control->_IsClosing())
Expand All @@ -144,7 +119,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
});

_updateScrollBar = std::make_shared<ThrottledFuncTrailing<ScrollBarUpdate>>(
Dispatcher(),
dispatcher,
ScrollBarUpdateInterval,
[weakThis = get_weak()](const auto& update) {
if (auto control{ weakThis.get() }; !control->_IsClosing())
Expand Down Expand Up @@ -540,7 +515,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
// create a custom automation peer with this code pattern:
// (https://docs.microsoft.com/en-us/windows/uwp/design/accessibility/custom-automation-peers)
if (const auto& interactivityAutoPeer = _interactivity.OnCreateAutomationPeer())
if (const auto& interactivityAutoPeer{ _interactivity.OnCreateAutomationPeer() })
{
_automationPeer = winrt::make<implementation::TermControlAutomationPeer>(this, interactivityAutoPeer);
return _automationPeer;
Expand Down Expand Up @@ -1276,23 +1251,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
CATCH_LOG();
}

void TermControl::_coreReceivedOutput(const IInspectable& /*sender*/,
const IInspectable& /*args*/)
{
// Queue up a throttled UpdatePatternLocations call. In the future, we
// should have the _updatePatternLocations ThrottledFunc internal to
// ControlCore, and run on that object's dispatcher queue.
//
// We're not doing that quite yet, because the Core will eventually
// be out-of-proc from the UI thread, and won't be able to just use
// the UI thread as the dispatcher queue thread.
//
// THIS IS CALLED ON EVERY STRING OF TEXT OUTPUT TO THE TERMINAL. Think
// twice before adding anything here.

_updatePatternLocations->Run();
}

// Method Description:
// - Reset the font size of the terminal to its default size.
// Arguments:
Expand Down Expand Up @@ -1330,8 +1288,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_updateScrollBar->ModifyPending([](auto& update) {
update.newValue.reset();
});

_updatePatternLocations->Run();
}

// Method Description:
Expand Down Expand Up @@ -1665,18 +1621,27 @@ namespace winrt::Microsoft::Terminal::Control::implementation
update.newValue = args.ViewTop();

_updateScrollBar->Run(update);
_updatePatternLocations->Run();
}

// Method Description:
// - Tells TSFInputControl to redraw the Canvas/TextBlock so it'll update
// to be where the current cursor position is.
// Arguments:
// - N/A
void TermControl::_CursorPositionChanged(const IInspectable& /*sender*/,
const IInspectable& /*args*/)
{
_tsfTryRedrawCanvas->Run();
winrt::fire_and_forget TermControl::_CursorPositionChanged(const IInspectable& /*sender*/,
const IInspectable& /*args*/)
{
// Prior to GH#10187, this fired a trailing throttled func to update the
// TSF canvas only every 100ms. Now, the throttling occurs on the
// ControlCore side. If we're told to update the cursor position, we can
// just go ahead and do it.
// This can come in off the COM thread - hop back to the UI thread.
auto weakThis{ get_weak() };
co_await resume_foreground(Dispatcher());
if (auto control{ weakThis.get() }; !control->_IsClosing())
{
control->TSFInputControl().TryRedrawCanvas();
}
}

hstring TermControl::Title()
Expand Down Expand Up @@ -1730,8 +1695,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
_closing = true;

_core.ReceivedOutput(_coreOutputEventToken);
_RestorePointerCursorHandlers(*this, nullptr);

// Disconnect the TSF input control so it doesn't receive EditContext events.
TSFInputControl().Close();
_autoScrollTimer.Stop();
Expand Down
Loading