terminal/src/cascadia/TerminalControl/ThreadSafeOptional.h
greg904 25df527743
Throttle scrollbar updates in TermControl to ~one per 8ms (#4608)
In addition to the below (original) description, this commit introduces
a ThrottledFunc template that can throttle _any_ function. It applies
that type to muffle updates to the scrollbar.

---

Redo #3531 but without the bug that it caused (#3622) which is why it
was reverted.

I'm sorry if I explain this badly. If you don't understand a part, make
sure to let me know and I will explain it better.

### Explanation

How it worked before: `Terminal` signals that viewport changed ->
`TermControl::_TerminalScrollPositionChanged` gets called on the
terminal thread -> it dispatches work for later to be ran the UI thread
to updates the scrollbar's values

Why it's bad:
* If we have many viewport changes, it will create a long stack of
  operations to run. Instead, we should just update the scroll bar with
  the most recent information that we know.
* Imagine if the rate that the work gets pushed on the UI thread is
  greater than the rate that it can handle: it might freeze?
* No need to be real time, we can wait just a little bit (8ms) to
  accumulate viewport changes before we actually change the scroll bar's
  value because it appears to be expensive (see perf below).

Now: `Terminal` signals that viewport changed ->
`TermControl::_TerminalScrollPositionChanged` gets called on the
terminal thread -> it tells the `ScrollBarUpdater` about a new update ->
the `ScrollBarUpdater` only runs one job (I don't know if that's the
right term) on the UI thread at a time. If a job is already running but
hasn't updated the scroll bar yet, it changes the setting in the already
existing job to update the scroll bar with the new values. A job "waits"
some time before doing the update to throttle updates because we don't
need real time scroll bar updates. -> eventually, it updates the scroll
bar If the user scrolls when a scroll bar update is pending, we keep the
scroll bar's Maximum and Minimum but let the user choose its new Value
with the `CancelPendingValueChange` method.

### Note

Also I changed a little bit the code from the Terminal to notify the
TermControl less often when possible.

I tried to scroll with the scroll bar, with the mouse wheel. I tried to
scroll while content is being outputted.

I tried to reproduce the crash from #2248 without success (good).

Co-authored-by: Leonard Hecker <leonard@hecker.io>

Closes #3622
2020-06-12 12:51:37 -07:00

55 lines
1.2 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "pch.h"
template<typename T>
class ThreadSafeOptional
{
public:
template<class... Args>
bool Emplace(Args&&... args)
{
std::lock_guard guard{ _lock };
bool hadValue = _inner.has_value();
_inner.emplace(std::forward<Args>(args)...);
return !hadValue;
}
std::optional<T> Take()
{
std::lock_guard guard{ _lock };
std::optional<T> value;
_inner.swap(value);
return value;
}
// Method Description:
// - If the optional has a value, then call the specified function with a
// reference to the value.
// - This method is always thread-safe. It can be called multiple times on
// different threads.
// Arguments:
// - f: the function to call with a reference to the value
// Return Value:
// - <none>
template<typename F>
void ModifyValue(F f)
{
std::lock_guard guard{ _lock };
if (_inner.has_value())
{
f(_inner.value());
}
}
private:
std::mutex _lock;
std::optional<T> _inner;
};