Skip to content

Commit

Permalink
Changed architecture! No more debugging state clones.
Browse files Browse the repository at this point in the history
This commit changes the way debugger works.

Before: current line was stored in Debugger.
So debugger had functions like next_state, previous_state,
and so on. Now, it only has peek_at_state, which takes
a number - which state we want to look at.

This allows us to borrow Debugger as immutable from various
TUI implementations. This means not only that one Debugger
instance may be used from multiple TUIs concurrently,
but that we have much more simplified memory management.

For example, before, whenever we wanted to display *any*
state, we had to clone it. That was expensive! Especially
since each state contains String from the sed output itself.

Now we can just borrow those states as immutable instead.

Current TUI takes advantage of that and doesn't consume
Debugger anymore, and furthermore  just borrows all the
debugging states instead of cloning them all the time.
  • Loading branch information
SoptikHa2 committed Jun 12, 2020
1 parent c8dce9c commit a35af51
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 68 deletions.
7 changes: 2 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@ fn main() {
/// state if possible
fn run(target_state_number: usize) -> Result<()> {
let settings = cli::parse_arguments()?;
let mut debugger = Debugger::new(settings)?;
for _ in 0..target_state_number {
debugger.next_state();
}
let tui = Tui::new(debugger)?;
let debugger = Debugger::new(settings)?;
let tui = Tui::new(&debugger, target_state_number)?;
match tui.start()? {
ApplicationExitReason::UserExit => {
return Ok(());
Expand Down
43 changes: 11 additions & 32 deletions src/sed/debugger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,7 @@ pub struct Debugger {
/// they are spread out so one is on each line.
pub source_code: Vec<String>,
/// Previously visited debugging states, inclding the current one.
///
/// See `history_limit` for maximum debugging states stored.
/// We rotate them afterwards.
pub state_frames: Vec<DebuggingState>,
pub current_frame: usize,
state_frames: Vec<DebuggingState>,
}
impl Debugger {
/// Create new instance of debugger and launch sed.
Expand Down Expand Up @@ -53,42 +49,25 @@ impl Debugger {
Ok(Debugger {
source_code: data.program_source,
state_frames: states_shifted,
current_frame: 0,
})
}
pub fn current_state(&self) -> Option<DebuggingState> {
// TODO: Solve this without cloning. This is awful.
self.state_frames.get(self.current_frame).map(|s| s.clone())
}
/// Go to next sed execution step.
///
/// This might return None if we reached end of execution.
pub fn next_state(&mut self) -> Option<DebuggingState> {
if self.current_frame >= self.state_frames.len() - 1 {
return None;
}
self.current_frame += 1;
// TODO: Solve this without cloning. This is awful.
self.state_frames.get(self.current_frame).map(|s| s.clone())
/// Peek at state with target number (0-based).
///
/// This will return None if the state doesn't exist.
pub fn peek_at_state(&self, frame: usize) -> Option<&DebuggingState> {
self.state_frames.get(frame)
}
/// Go to previous sed execution step as saved in memory.
///
/// This might return None if we are at start of execution or
/// if there no longer any states left in history.
pub fn previous_state(&mut self) -> Option<DebuggingState> {
if self.current_frame == 0 {
return None;
}
self.current_frame -= 1;
// TODO: Solve this without cloning. This is awful.
self.state_frames.get(self.current_frame).map(|s| s.clone())

/// Returns number of states. Counting starts from one.
pub fn count_of_states(&self) -> usize {
self.state_frames.len()
}
}

/// One state of sed program execution.
///
/// Remembers state of sed program execution.
#[derive(Clone, Debug)]
#[derive(Debug)]
pub struct DebuggingState {
/// State of primary, or pattern, buffer
pub pattern_buffer: String,
Expand Down
61 changes: 30 additions & 31 deletions src/ui/tui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ use tui::terminal::Frame;
use tui::widgets::{Block, Borders, Paragraph, Text};
use tui::Terminal;

pub struct Tui {
debugger: Debugger,
pub struct Tui<'a> {
debugger: &'a Debugger,
terminal: Terminal<CrosstermBackend<io::Stdout>>,
/// Collection of lines which are designated as breakpoints
breakpoints: HashSet<usize>,
/// Remembers which line has user selected (has cursor on).
cursor: usize,
/// UI is refreshed automatically on user input.
/// However if no user input arrives, how often should
Expand All @@ -34,15 +36,17 @@ pub struct Tui {
/// be stored, but can't be executed because we don't know what to do (move up) until
/// we see the "k" character. The instant we see it, we read the whole buffer and clear it.
pressed_keys_buffer: String,
/// Remembers at which state are we currently. User can step back and forth.
current_state: usize,
}
impl Tui {
impl<'a> Tui<'a> {
/// Create new TUI that gathers data from the debugger.
///
/// This consumes the debugger, as it's used to advance debugging state.
#[allow(unused_must_use)]
// NOTE: We don't care that some actions here fail (for example mouse handling),
// as some features that we're trying to enable here are not necessary for desed.
pub fn new(debugger: Debugger) -> Result<Self> {
pub fn new(debugger: &'a Debugger, current_state: usize) -> Result<Self> {
let mut stdout = io::stdout();
execute!(stdout, event::EnableMouseCapture);
let backend = CrosstermBackend::new(stdout);
Expand All @@ -57,6 +61,7 @@ impl Tui {
cursor: 0,
forced_refresh_rate: 200,
pressed_keys_buffer: String::new(),
current_state
})
}

Expand Down Expand Up @@ -317,12 +322,8 @@ impl Tui {
}
}

impl UiAgent for Tui {
impl<'a> UiAgent for Tui<'a> {
fn start(mut self) -> Result<ApplicationExitReason> {
let mut current_state = self.debugger.current_state().with_context(||format!(
"It looks like the source code loaded was empty. Nothing to do. Are you sure sed can process the file? Make sure you didn't forget the -E option.",
))?;

// Setup event loop and input handling
let (tx, rx) = mpsc::channel();
let tick_rate = Duration::from_millis(self.forced_refresh_rate);
Expand Down Expand Up @@ -366,7 +367,9 @@ impl UiAgent for Tui {

// UI thread that manages drawing
loop {
let debugger = &mut self.debugger;
let current_state = self.debugger.peek_at_state(self.current_state)
.with_context(||"We got ourselves into impossible state. This is logical error, please report a bug.")?;
let debugger = &self.debugger;
let line_number = current_state.current_line;
// Wait for interrupt
match rx.recv()? {
Expand Down Expand Up @@ -435,8 +438,8 @@ impl UiAgent for Tui {
for _ in
0..Tui::get_pressed_key_buffer_as_number(&self.pressed_keys_buffer, 1)
{
if let Some(newstate) = debugger.next_state() {
current_state = newstate;
if self.current_state < debugger.count_of_states() - 1 {
self.current_state += 1;
}
}
use_execution_pointer_as_focus_line = true;
Expand All @@ -447,42 +450,38 @@ impl UiAgent for Tui {
for _ in
0..Tui::get_pressed_key_buffer_as_number(&self.pressed_keys_buffer, 1)
{
if let Some(prevstate) = debugger.previous_state() {
current_state = prevstate;
if self.current_state > 0 {
self.current_state -= 1;
}
}
use_execution_pointer_as_focus_line = true;
self.pressed_keys_buffer.clear();
}
// Run till end or breakpoint
KeyCode::Char('r') => loop {
if let Some(newstate) = debugger.next_state() {
current_state = newstate;
if self.breakpoints.contains(&current_state.current_line) {
KeyCode::Char('r') => {
use_execution_pointer_as_focus_line = true;
self.pressed_keys_buffer.clear();
while self.current_state < debugger.count_of_states() - 1 {
self.current_state += 1;
if self.breakpoints.contains(&self.debugger.peek_at_state(self.current_state).unwrap().current_line) {
break;
}
} else {
break;
}
use_execution_pointer_as_focus_line = true;
self.pressed_keys_buffer.clear();
},
// Same as 'r', but backwards
KeyCode::Char('R') => loop {
if let Some(newstate) = debugger.previous_state() {
current_state = newstate;
if self.breakpoints.contains(&current_state.current_line) {
KeyCode::Char('R') => {
use_execution_pointer_as_focus_line = true;
self.pressed_keys_buffer.clear();
while self.current_state > 0 {
self.current_state -= 1;
if self.breakpoints.contains(&self.debugger.peek_at_state(self.current_state).unwrap().current_line) {
break;
}
} else {
break;
}
use_execution_pointer_as_focus_line = true;
self.pressed_keys_buffer.clear();
},
// Reload source code and try to enter current state again
KeyCode::Char('l') => {
return Ok(ApplicationExitReason::Reload(self.debugger.current_frame));
return Ok(ApplicationExitReason::Reload(self.current_state));
}
KeyCode::Char(other) => match other {
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => {
Expand Down

1 comment on commit a35af51

@SoptikHa2
Copy link
Owner Author

Choose a reason for hiding this comment

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

This small change is one of the best commits I've ever made in this repository. And it's mostly just deleting code. Heh.

Please sign in to comment.