-
Notifications
You must be signed in to change notification settings - Fork 57
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
Added APC support #110
Added APC support #110
Conversation
…ation Several changes were made for this: - `STATE_CHANGES` now stores a tuple of `(State, Action)` as opposed to `u8` - The negative aspect of this is that it will now take over twice as much space in memory (4 KiB) to 8.5 KiB - On a modern system this should not come at a reasonable preformance cost - The justification for this is that further `Action`s were needed to help manage an APC command: `ApcBegin`, `ApcEnd`, and `ApcPut`. Therein it became necassary to extend the struct from a quasi-`u4` to a `u8` as these represent values 16, 17, and 18 respectively. - This also removes the functions `pack` and `unpack`, a possible benefit of the change. - This means code was also changed in the macro library `vte_generate_state_changes` - `Perform` now includes three new functions with signatures listed below: fn apc_begin(&mut self) {} fn apc_end(&mut self) {} fn apc_put(&mut self) {} These I believe are the best way to facilitate the use of APC. An alternative method could exist without `apc_begin`, although I believe that from a library perspective it is more ergonomic to have such as function, as it elimintates a potential check required by downstream libraries. - For illustration purposes there is a new examples `apc.rs`. I will delete this example in the next commit and merge its functionality with the already existing `parselog.rs` and the testing suite. I have merely kept it to help demonstrate the functionality provided in the commit, and to facilitate my preliminary testing of it
- This involved: - Removing its dedicated (and frankly useless) example - Writing a proper test - Having it store a buffer. It currently uses its own buffer although it could fairly easily be merged with that of OSC without any obvious drawbacks. Next commit? - Rather than having three functions within `Perform` it now has just one `acp_dispatch` which should be easier to code with.
src/definitions.rs
Outdated
#[inline(always)] | ||
pub fn unpack(delta: u8) -> (State, Action) { | ||
unsafe { | ||
( | ||
// State is stored in bottom 4 bits | ||
mem::transmute(delta & 0x0f), | ||
// Action is stored in top 4 bits | ||
mem::transmute(delta >> 4), | ||
) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be benchmarked. There is no way we'll merge a patch that reduces performance for a feature nobody should be using.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Running some initial benchmarks I've got the following results from cargo bench
:
test bench::state_changes ... bench: 108,197 ns/iter (+/- 3,199)
test bench::testfile ... bench: 121,021 ns/iter (+/- 3,617)
whilst the old code yields the following:
test bench::state_changes ... bench: 95,320 ns/iter (+/- 4,951)
test bench::testfile ... bench: 101,009 ns/iter (+/- 7,342)
Which equates to roughly an 12% ~ 16% decrease in performance. I am using flamegraph now to investigate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd also recommend testing with https://github.com/alacritty/vtebench. That's ultimately what decides which features get merged into Alacritty or not.
src/lib.rs
Outdated
@@ -73,7 +73,8 @@ impl<'a, P: Perform> utf8::Receiver for VtUtf8Receiver<'a, P> { | |||
/// [`Perform`]: trait.Perform.html | |||
/// | |||
/// Generic over the value for the size of the raw Operating System Command | |||
/// buffer. Only used when the `no_std` feature is enabled. | |||
/// buffer. Only used when the `no_std` feature is enabled. This buffer is | |||
/// also used for APC (Application Program Commands.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// also used for APC (Application Program Commands.) | |
/// also used for APC (Application Program Commands). |
src/lib.rs
Outdated
Action::ApcStart => { | ||
self.osc_raw.clear(); | ||
}, | ||
Action::ApcEnd => { | ||
self.apc_dispatch(performer); | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Statements should be inlined.
src/lib.rs
Outdated
/// Called at the end of an APC (application program command), where | ||
/// `bytes` is the content of the APC |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Called at the end of an APC (application program command), where | |
/// `bytes` is the content of the APC | |
/// Called at the end of an APC (application program command), where | |
/// `bytes` is the content of the APC. |
src/lib.rs
Outdated
const INPUT_1: &[u8] = b"\x1b_abc\x9c"; | ||
const INPUT_2: &[u8] = b"\x1b_abc\x1b\\"; | ||
|
||
// Test with ST terminator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Test with ST terminator | |
// Test with ST terminator. |
src/lib.rs
Outdated
} | ||
assert_eq!(dispatcher.dispatched, vec![Sequence::Apc(b"abc".to_vec()),]); | ||
|
||
// Test with ESC \ terminator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Test with ESC \ terminator | |
// Test with ESC \ terminator. |
src/table.rs
Outdated
0x00..=0x06 => (Anywhere, Ignore), | ||
0x08..=0x17 => (Anywhere, Ignore), | ||
0x19 => (Anywhere, Ignore), | ||
0x1c..=0x1f => (Anywhere, Ignore), | ||
0x20..=0xff => (Anywhere, ApcPut), | ||
0x9c => (Ground, None), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haven't checked these yet, just for personal reference.
This has a lower performance overhead. Needs to be tested further.
I've worked on performance, and have got the following results (from vtebench): Summary TableTL;DR About the same in both master and the patch
🟢 = fastest Raw ResultsNormal Alacritty:
Alacritty + John-Toohey:apc
vte's benchmarksTL;DR Approximately the same. alacritty/vte:master
John-Toohey:apc
|
As for how this new solution works, it basically takes the implementation of |
@John-Toohey If you have an Alacritty revision that is built against this, could you put that up in a draft PR on the Alacritty repo? That way I can easily run performance tests against it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've kinda been ignoring this PR for a while, mostly because I'm not particularly happy with the implementation but I also do not have any concrete suggestions without looking into this myself (which I have no interest in doing).
But hopefully this will at least keep the conversation going and provide some feedback on why I think it's not mergeable as-is.
@@ -19,7 +19,7 @@ pub enum State { | |||
#[default] | |||
Ground = 12, | |||
OscString = 13, | |||
SosPmApcString = 14, | |||
Null = 14, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just a bad name. A "null state" already exists, it's called Ground
. Looking at State::Null
alone would make it impossible to tell what that state is for.
match self.str_start { | ||
0x1d => { | ||
match param_idx { | ||
// Finish last parameter if not already maxed | ||
MAX_OSC_PARAMS => (), | ||
|
||
// First param is special - 0 to current byte index | ||
0 => { | ||
self.osc_params[param_idx] = (0, idx); | ||
self.osc_num_params += 1; | ||
}, | ||
|
||
// All other params depend on previous indexing | ||
_ => { | ||
let prev = self.osc_params[param_idx - 1]; | ||
let begin = prev.1; | ||
self.osc_params[param_idx] = (begin, idx); | ||
self.osc_num_params += 1; | ||
}, | ||
} | ||
self.osc_dispatch(performer, byte); | ||
}, | ||
|
||
// All other params depend on previous indexing | ||
_ => { | ||
let prev = self.osc_params[param_idx - 1]; | ||
let begin = prev.1; | ||
self.osc_params[param_idx] = (begin, idx); | ||
self.osc_num_params += 1; | ||
0x18 => { | ||
self.sos_dispatch(performer); | ||
}, | ||
0x1e => { | ||
self.pm_dispatch(performer); | ||
}, | ||
0x1F => { | ||
self.apc_dispatch(performer); | ||
}, | ||
// This condition should never be hit under the | ||
// current way in which the code is ran. | ||
_ => unreachable!(), | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This basically feels like an embedded parser. Most of our parser is structured in a way to stream through bytes and process them as they're coming in, however rather than creating state transitions you're storing a state transition to later then match on it instead. VTE is already too slow and I'm afraid this will just amplify that problem.
// This condition should never be hit under the | ||
// current way in which the code is ran. | ||
_ => unreachable!(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is basically just a long way to say unreachable!()
. It doesn't explain why it is unreachable it just states that it is, which unreachable!()
does too. Should either add a useful comment or remove this.
@@ -261,7 +278,7 @@ impl<const OSC_RAW_BUF_SIZE: usize> Parser<OSC_RAW_BUF_SIZE> { | |||
let idx = self.osc_raw.len(); | |||
|
|||
// Param separator | |||
if byte == b';' { | |||
if self.str_start == 0x1d && byte == b';' { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One extra branch isn't going to be the end of the world, but I don't see how this isn't just going to be a net-negative for anyone who doesn't use APC escapes (which is basically everyone).
Dead PR see #115. |
Fixes #109.
This adds support for APC (Application Program Command) in the same way OSC is implemented. It does, however, have the drawback of (just over) doubling the in-memory footprint of the
table::STATE_CHANGES
static, for it became necessary to splitState
andAction
as the amount of variants withinAction
exceeded 15, meaning it was no longer possible for it to be a pseudou4
.More detail in the commit messages.
Thank you for your time.