mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2024-11-25 12:11:33 +08:00
Performance improvement by refactoring pixel styles (#704)
Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
This commit is contained in:
parent
00e63993ce
commit
e2a205ed0d
@ -38,6 +38,8 @@ current (development)
|
|||||||
### Screen
|
### Screen
|
||||||
- Breaking: `WordBreakProperty` becomes a uint8_t enum. This yields a 0.8%
|
- Breaking: `WordBreakProperty` becomes a uint8_t enum. This yields a 0.8%
|
||||||
performance improvement.
|
performance improvement.
|
||||||
|
- Breaking: Remove user defined Pixel constructor and equality operator.
|
||||||
|
- Performance: 19% faster on benchmarks.
|
||||||
|
|
||||||
|
|
||||||
### Build
|
### Build
|
||||||
|
@ -15,19 +15,15 @@ namespace ftxui {
|
|||||||
/// @brief A unicode character and its associated style.
|
/// @brief A unicode character and its associated style.
|
||||||
/// @ingroup screen
|
/// @ingroup screen
|
||||||
struct Pixel {
|
struct Pixel {
|
||||||
bool operator==(const Pixel& other) const;
|
Pixel()
|
||||||
|
: blink(false),
|
||||||
// The graphemes stored into the pixel. To support combining characters,
|
bold(false),
|
||||||
// like: a⃦, this can potentially contain multiple codepoints.
|
dim(false),
|
||||||
std::string character = " ";
|
inverted(false),
|
||||||
|
underlined(false),
|
||||||
// The hyperlink associated with the pixel.
|
underlined_double(false),
|
||||||
// 0 is the default value, meaning no hyperlink.
|
strikethrough(false),
|
||||||
uint8_t hyperlink = 0;
|
automerge(false) {}
|
||||||
|
|
||||||
// Colors:
|
|
||||||
Color background_color = Color::Default;
|
|
||||||
Color foreground_color = Color::Default;
|
|
||||||
|
|
||||||
// A bit field representing the style:
|
// A bit field representing the style:
|
||||||
bool blink : 1;
|
bool blink : 1;
|
||||||
@ -39,15 +35,17 @@ struct Pixel {
|
|||||||
bool strikethrough : 1;
|
bool strikethrough : 1;
|
||||||
bool automerge : 1;
|
bool automerge : 1;
|
||||||
|
|
||||||
Pixel()
|
// The hyperlink associated with the pixel.
|
||||||
: blink(false),
|
// 0 is the default value, meaning no hyperlink.
|
||||||
bold(false),
|
uint8_t hyperlink = 0;
|
||||||
dim(false),
|
|
||||||
inverted(false),
|
// The graphemes stored into the pixel. To support combining characters,
|
||||||
underlined(false),
|
// like: a⃦, this can potentially contain multiple codepoints.
|
||||||
underlined_double(false),
|
std::string character = " ";
|
||||||
strikethrough(false),
|
|
||||||
automerge(false) {}
|
// Colors:
|
||||||
|
Color background_color = Color::Default;
|
||||||
|
Color foreground_color = Color::Default;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// @brief Define how the Screen's dimensions should look like.
|
/// @brief Define how the Screen's dimensions should look like.
|
||||||
|
@ -91,7 +91,7 @@ Canvas::Canvas(int width, int height)
|
|||||||
/// @param y the y coordinate of the cell.
|
/// @param y the y coordinate of the cell.
|
||||||
Pixel Canvas::GetPixel(int x, int y) const {
|
Pixel Canvas::GetPixel(int x, int y) const {
|
||||||
auto it = storage_.find(XY{x, y});
|
auto it = storage_.find(XY{x, y});
|
||||||
return (it == storage_.end()) ? Pixel{} : it->second.content;
|
return (it == storage_.end()) ? Pixel() : it->second.content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Draw a braille dot.
|
/// @brief Draw a braille dot.
|
||||||
|
@ -18,6 +18,23 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Macro for hinting that an expression is likely to be false.
|
||||||
|
#if !defined(FTXUI_UNLIKELY)
|
||||||
|
#if defined(COMPILER_GCC) || defined(__clang__)
|
||||||
|
#define FTXUI_UNLIKELY(x) __builtin_expect(!!(x), 0)
|
||||||
|
#else
|
||||||
|
#define FTXUI_UNLIKELY(x) (x)
|
||||||
|
#endif // defined(COMPILER_GCC)
|
||||||
|
#endif // !defined(FTXUI_UNLIKELY)
|
||||||
|
|
||||||
|
#if !defined(FTXUI_LIKELY)
|
||||||
|
#if defined(COMPILER_GCC) || defined(__clang__)
|
||||||
|
#define FTXUI_LIKELY(x) __builtin_expect(!!(x), 1)
|
||||||
|
#else
|
||||||
|
#define FTXUI_LIKELY(x) (x)
|
||||||
|
#endif // defined(COMPILER_GCC)
|
||||||
|
#endif // !defined(FTXUI_LIKELY)
|
||||||
|
|
||||||
namespace ftxui {
|
namespace ftxui {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -53,80 +70,53 @@ void WindowsEmulateVT100Terminal() {
|
|||||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||||
void UpdatePixelStyle(const Screen* screen,
|
void UpdatePixelStyle(const Screen* screen,
|
||||||
std::stringstream& ss,
|
std::stringstream& ss,
|
||||||
Pixel& previous,
|
const Pixel& prev,
|
||||||
const Pixel& next) {
|
const Pixel& next) {
|
||||||
// See https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
|
// See https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
|
||||||
if (next.hyperlink != previous.hyperlink) {
|
if (FTXUI_UNLIKELY(next.hyperlink != prev.hyperlink)) {
|
||||||
ss << "\x1B]8;;" << screen->Hyperlink(next.hyperlink) << "\x1B\\";
|
ss << "\x1B]8;;" << screen->Hyperlink(next.hyperlink) << "\x1B\\";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((!next.bold && previous.bold) || //
|
// Bold
|
||||||
(!next.dim && previous.dim)) {
|
if (FTXUI_UNLIKELY(next.bold != prev.bold || next.dim != prev.dim)) {
|
||||||
ss << "\x1B[22m"; // BOLD_RESET and DIM_RESET
|
// BOLD_AND_DIM_RESET:
|
||||||
// We might have wrongfully reset dim or bold because they share the same
|
ss << ((prev.bold && !next.bold) || (prev.dim && !next.dim) ? "\x1B[22m"
|
||||||
// resetter. Take it into account so that the side effect will cause it to
|
: "");
|
||||||
// be set again below.
|
ss << (next.bold ? "\x1B[1m" : ""); // BOLD_SET
|
||||||
previous.bold = false;
|
ss << (next.dim ? "\x1B[2m" : ""); // DIM_SET
|
||||||
previous.dim = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((!next.underlined && previous.underlined) ||
|
// Underline
|
||||||
(!next.underlined_double && previous.underlined_double)) {
|
if (FTXUI_UNLIKELY(next.underlined != prev.underlined ||
|
||||||
// We might have wrongfully reset underlined or underlinedbold because they
|
next.underlined_double != prev.underlined_double)) {
|
||||||
// share the same resetter. Take it into account so that the side effect
|
ss << (next.underlined ? "\x1B[4m" // UNDERLINE
|
||||||
// will cause it to be set again below.
|
: next.underlined_double ? "\x1B[21m" // UNDERLINE_DOUBLE
|
||||||
ss << "\x1B[24m"; // UNDERLINED_RESET
|
: "\x1B[24m"); // UNDERLINE_RESET
|
||||||
previous.underlined = false;
|
|
||||||
previous.underlined_double = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (next.bold && !previous.bold) {
|
// Blink
|
||||||
ss << "\x1B[1m"; // BOLD_SET
|
if (FTXUI_UNLIKELY(next.blink != prev.blink)) {
|
||||||
|
ss << (next.blink ? "\x1B[5m" // BLINK_SET
|
||||||
|
: "\x1B[25m"); // BLINK_RESET
|
||||||
}
|
}
|
||||||
|
|
||||||
if (next.dim && !previous.dim) {
|
// Inverted
|
||||||
ss << "\x1B[2m"; // DIM_SET
|
if (FTXUI_UNLIKELY(next.inverted != prev.inverted)) {
|
||||||
|
ss << (next.inverted ? "\x1B[7m" // INVERTED_SET
|
||||||
|
: "\x1B[27m"); // INVERTED_RESET
|
||||||
}
|
}
|
||||||
|
|
||||||
if (next.underlined && !previous.underlined) {
|
// StrikeThrough
|
||||||
ss << "\x1B[4m"; // UNDERLINED_SET
|
if (FTXUI_UNLIKELY(next.strikethrough != prev.strikethrough)) {
|
||||||
|
ss << (next.strikethrough ? "\x1B[9m" // CROSSED_OUT
|
||||||
|
: "\x1B[29m"); // CROSSED_OUT_RESET
|
||||||
}
|
}
|
||||||
|
|
||||||
if (next.blink && !previous.blink) {
|
if (FTXUI_UNLIKELY(next.foreground_color != prev.foreground_color ||
|
||||||
ss << "\x1B[5m"; // BLINK_SET
|
next.background_color != prev.background_color)) {
|
||||||
}
|
|
||||||
|
|
||||||
if (!next.blink && previous.blink) {
|
|
||||||
ss << "\x1B[25m"; // BLINK_RESET
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next.inverted && !previous.inverted) {
|
|
||||||
ss << "\x1B[7m"; // INVERTED_SET
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!next.inverted && previous.inverted) {
|
|
||||||
ss << "\x1B[27m"; // INVERTED_RESET
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next.strikethrough && !previous.strikethrough) {
|
|
||||||
ss << "\x1B[9m"; // CROSSED_OUT
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!next.strikethrough && previous.strikethrough) {
|
|
||||||
ss << "\x1B[29m"; // CROSSED_OUT_RESET
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next.underlined_double && !previous.underlined_double) {
|
|
||||||
ss << "\x1B[21m"; // DOUBLE_UNDERLINED_SET
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next.foreground_color != previous.foreground_color ||
|
|
||||||
next.background_color != previous.background_color) {
|
|
||||||
ss << "\x1B[" + next.foreground_color.Print(false) + "m";
|
ss << "\x1B[" + next.foreground_color.Print(false) + "m";
|
||||||
ss << "\x1B[" + next.background_color.Print(true) + "m";
|
ss << "\x1B[" + next.background_color.Print(true) + "m";
|
||||||
}
|
}
|
||||||
|
|
||||||
previous = next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TileEncoding {
|
struct TileEncoding {
|
||||||
@ -373,18 +363,6 @@ bool ShouldAttemptAutoMerge(Pixel& pixel) {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool Pixel::operator==(const Pixel& other) const {
|
|
||||||
return character == other.character && //
|
|
||||||
background_color == other.background_color && //
|
|
||||||
foreground_color == other.foreground_color && //
|
|
||||||
blink == other.blink && //
|
|
||||||
bold == other.bold && //
|
|
||||||
dim == other.dim && //
|
|
||||||
inverted == other.inverted && //
|
|
||||||
underlined == other.underlined && //
|
|
||||||
automerge == other.automerge; //
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A fixed dimension.
|
/// A fixed dimension.
|
||||||
/// @see Fit
|
/// @see Fit
|
||||||
/// @see Full
|
/// @see Full
|
||||||
@ -435,25 +413,31 @@ Screen::Screen(int dimx, int dimy)
|
|||||||
std::string Screen::ToString() const {
|
std::string Screen::ToString() const {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
|
|
||||||
Pixel previous_pixel;
|
const Pixel default_pixel;
|
||||||
const Pixel final_pixel;
|
const Pixel* previous_pixel_ref = &default_pixel;
|
||||||
|
|
||||||
for (int y = 0; y < dimy_; ++y) {
|
for (int y = 0; y < dimy_; ++y) {
|
||||||
|
// New line in between two lines.
|
||||||
if (y != 0) {
|
if (y != 0) {
|
||||||
UpdatePixelStyle(this, ss, previous_pixel, final_pixel);
|
UpdatePixelStyle(this, ss, *previous_pixel_ref, default_pixel);
|
||||||
|
previous_pixel_ref = &default_pixel;
|
||||||
ss << "\r\n";
|
ss << "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// After printing a fullwith character, we need to skip the next cell.
|
||||||
bool previous_fullwidth = false;
|
bool previous_fullwidth = false;
|
||||||
for (const auto& pixel : pixels_[y]) {
|
for (const auto& pixel : pixels_[y]) {
|
||||||
if (!previous_fullwidth) {
|
if (!previous_fullwidth) {
|
||||||
UpdatePixelStyle(this, ss, previous_pixel, pixel);
|
UpdatePixelStyle(this, ss, *previous_pixel_ref, pixel);
|
||||||
|
previous_pixel_ref = &pixel;
|
||||||
ss << pixel.character;
|
ss << pixel.character;
|
||||||
}
|
}
|
||||||
previous_fullwidth = (string_width(pixel.character) == 2);
|
previous_fullwidth = (string_width(pixel.character) == 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdatePixelStyle(this, ss, previous_pixel, final_pixel);
|
// Reset the style to default:
|
||||||
|
UpdatePixelStyle(this, ss, *previous_pixel_ref, default_pixel);
|
||||||
|
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user