Optimize Row::reset

Now, only cells that have been used are cleared. This is achieved by
using a "occupied" memo on the Row itself. The value, `occ`, is updated
wherever the Row is accessed mutably, and it's cleared to zero in
Row::reset.

The tests for grid scroll_up and scroll_down were updated to include a
test on the value `occ` and slightly refactored, but are otherwise
equivalent to the previous implementation of those tests.

Because of the change to the `Row` struct, the ref tests were updated so
Deserialization keeps working as expected.
This commit is contained in:
Joe Wilm 2018-05-19 11:18:37 -07:00
parent 4b1a3b1e92
commit c61a912f62
24 changed files with 145 additions and 104 deletions

View File

@ -3,18 +3,31 @@
require 'json'
Dir.glob('./tests/ref/**/grid.json').each do |path|
puts "Migrating #{path}"
# Read contents
s = File.open(path) { |f| f.read }
# Parse
grid = JSON.parse(s)
# Check if it's already migrated / make this migration idempotent
next if grid['raw'][0][0].is_a? Array
# Normalize Storage serialization
if grid['raw'].is_a? Array
grid['raw'] = {
'inner' => grid['raw'][0],
'zero' => grid['raw'][1],
'visible_lines' => grid['raw'][2]
}
end
# Transform
grid['raw'].reverse!
grid['raw'] = [grid['raw'], 0, grid['lines'] - 1]
# Migrate Row serialization
grid['raw']['inner'].map! do |row|
if row.is_a? Hash
row
else
{ inner: row, occ: row.length }
end
end
# Write updated grid
File.open(path, 'w') { |f| f << JSON.generate(grid) }

View File

@ -14,43 +14,78 @@
//! Defines the Row type which makes up lines in the grid
use std::ops::{Deref, DerefMut, Index, IndexMut};
use std::ops::{Index, IndexMut};
use std::ops::{Range, RangeTo, RangeFrom, RangeFull};
use std::cmp::{max, min};
use std::slice;
use index::Column;
/// A row in the grid
#[derive(Default, Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct Row<T>(Vec<T>);
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
pub struct Row<T> {
inner: Vec<T>,
/// occupied entries
///
/// Semantically, this value can be understood as the **end** of an
/// Exclusive Range. Thus,
///
/// - Zero means there are no occupied entries
/// - 1 means there is a value at index zero, but nowhere else
/// - `occ == inner.len` means every value is occupied
pub(crate) occ: usize,
}
impl<T: PartialEq> PartialEq for Row<T> {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
impl<T: Copy + Clone> Row<T> {
pub fn new(columns: Column, template: &T) -> Row<T> {
Row(vec![*template; *columns])
Row {
inner: vec![*template; *columns],
occ: 0,
}
}
pub fn grow(&mut self, cols: Column, template: &T) {
assert!(self.len() < * cols);
while self.len() != *cols {
self.0.push(*template);
self.inner.push(*template);
}
}
/// Resets contents to the contents of `other`
#[inline]
#[inline(never)]
pub fn reset(&mut self, other: &T) {
for item in &mut self.0 {
let occ = self.occ;
for item in &mut self.inner[..occ] {
*item = *other;
}
self.occ = 0;
}
}
impl<T> Row<T> {
pub fn shrink(&mut self, cols: Column) {
while self.len() != *cols {
self.pop();
self.inner.pop();
}
self.occ = min(self.occ, *cols);
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn iter<'a>(&'a self) -> slice::Iter<'a, T> {
self.inner.iter()
}
}
@ -71,24 +106,8 @@ impl<'a, T> IntoIterator for &'a mut Row<T> {
#[inline]
fn into_iter(self) -> slice::IterMut<'a, T> {
self.iter_mut()
}
}
impl<T> Deref for Row<T> {
type Target = Vec<T>;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for Row<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
self.occ = self.len();
self.inner.iter_mut()
}
}
@ -97,14 +116,15 @@ impl<T> Index<Column> for Row<T> {
#[inline]
fn index(&self, index: Column) -> &T {
&self.0[index.0]
&self.inner[index.0]
}
}
impl<T> IndexMut<Column> for Row<T> {
#[inline]
fn index_mut(&mut self, index: Column) -> &mut T {
&mut self.0[index.0]
self.occ = max(self.occ, *index + 1);
&mut self.inner[index.0]
}
}
@ -117,14 +137,15 @@ impl<T> Index<Range<Column>> for Row<T> {
#[inline]
fn index(&self, index: Range<Column>) -> &[T] {
&self.0[(index.start.0)..(index.end.0)]
&self.inner[(index.start.0)..(index.end.0)]
}
}
impl<T> IndexMut<Range<Column>> for Row<T> {
#[inline]
fn index_mut(&mut self, index: Range<Column>) -> &mut [T] {
&mut self.0[(index.start.0)..(index.end.0)]
self.occ = max(self.occ, *index.end);
&mut self.inner[(index.start.0)..(index.end.0)]
}
}
@ -133,14 +154,15 @@ impl<T> Index<RangeTo<Column>> for Row<T> {
#[inline]
fn index(&self, index: RangeTo<Column>) -> &[T] {
&self.0[..(index.end.0)]
&self.inner[..(index.end.0)]
}
}
impl<T> IndexMut<RangeTo<Column>> for Row<T> {
#[inline]
fn index_mut(&mut self, index: RangeTo<Column>) -> &mut [T] {
&mut self.0[..(index.end.0)]
self.occ = max(self.occ, *index.end);
&mut self.inner[..(index.end.0)]
}
}
@ -149,14 +171,15 @@ impl<T> Index<RangeFrom<Column>> for Row<T> {
#[inline]
fn index(&self, index: RangeFrom<Column>) -> &[T] {
&self.0[(index.start.0)..]
&self.inner[(index.start.0)..]
}
}
impl<T> IndexMut<RangeFrom<Column>> for Row<T> {
#[inline]
fn index_mut(&mut self, index: RangeFrom<Column>) -> &mut [T] {
&mut self.0[(index.start.0)..]
self.occ = self.len();
&mut self.inner[(index.start.0)..]
}
}
@ -165,13 +188,14 @@ impl<T> Index<RangeFull> for Row<T> {
#[inline]
fn index(&self, _: RangeFull) -> &[T] {
&self.0[..]
&self.inner[..]
}
}
impl<T> IndexMut<RangeFull> for Row<T> {
#[inline]
fn index_mut(&mut self, _: RangeFull) -> &mut [T] {
&mut self.0[..]
self.occ = self.len();
&mut self.inner[..]
}
}

View File

@ -20,73 +20,77 @@ use index::{Point, Line, Column};
// Scroll up moves lines upwards
#[test]
fn scroll_up() {
info!("");
println!("");
let mut grid = Grid::new(Line(10), Column(1), 0, 0);
for i in 0..10 {
grid[Line(i)][Column(0)] = i;
}
info!("grid: {:?}", grid);
println!("grid: {:?}", grid);
grid.scroll_up(&(Line(0)..Line(10)), Line(2), &0);
info!("grid: {:?}", grid);
println!("grid: {:?}", grid);
let mut other = Grid::new(Line(10), Column(1), 0, 9);
other[Line(0)][Column(0)] = 2;
other[Line(1)][Column(0)] = 3;
other[Line(2)][Column(0)] = 4;
other[Line(3)][Column(0)] = 5;
other[Line(4)][Column(0)] = 6;
other[Line(5)][Column(0)] = 7;
other[Line(6)][Column(0)] = 8;
other[Line(7)][Column(0)] = 9;
other[Line(8)][Column(0)] = 0; // should be cleared on scroll; was 0
other[Line(9)][Column(0)] = 0; // should be cleared on scroll; was 1
for i in 0..10 {
assert_eq!(grid[Line(i)][Column(0)], other[Line(i)][Column(0)],
"index={}; actual: {:?}, expected: {:?}",
Line(i), grid[Line(i)][Column(0)], other[Line(i)][Column(0)]);
}
assert_eq!(grid[Line(0)][Column(0)], 2);
assert_eq!(grid[Line(0)].occ, 1);
assert_eq!(grid[Line(1)][Column(0)], 3);
assert_eq!(grid[Line(1)].occ, 1);
assert_eq!(grid[Line(2)][Column(0)], 4);
assert_eq!(grid[Line(2)].occ, 1);
assert_eq!(grid[Line(3)][Column(0)], 5);
assert_eq!(grid[Line(3)].occ, 1);
assert_eq!(grid[Line(4)][Column(0)], 6);
assert_eq!(grid[Line(4)].occ, 1);
assert_eq!(grid[Line(5)][Column(0)], 7);
assert_eq!(grid[Line(5)].occ, 1);
assert_eq!(grid[Line(6)][Column(0)], 8);
assert_eq!(grid[Line(6)].occ, 1);
assert_eq!(grid[Line(7)][Column(0)], 9);
assert_eq!(grid[Line(7)].occ, 1);
assert_eq!(grid[Line(8)][Column(0)], 0); // was 0
assert_eq!(grid[Line(8)].occ, 0);
assert_eq!(grid[Line(9)][Column(0)], 0); // was 1
assert_eq!(grid[Line(9)].occ, 0);
}
// Scroll down moves lines downwards
#[test]
fn scroll_down() {
info!("");
println!("");
let mut grid = Grid::new(Line(10), Column(1), 0, 0);
for i in 0..10 {
grid[Line(i)][Column(0)] = i;
}
info!("grid: {:?}", grid);
println!("grid: {:?}", grid);
grid.scroll_down(&(Line(0)..Line(10)), Line(2), &0);
info!("grid: {:?}", grid);
println!("grid: {:?}", grid);
let mut other = Grid::new(Line(10), Column(1), 0, 9);
other[Line(0)][Column(0)] = 0; // Should be cleared upon recycle; was 8
other[Line(1)][Column(0)] = 0; // Should be cleared upon recycle; was 9
other[Line(2)][Column(0)] = 0;
other[Line(3)][Column(0)] = 1;
other[Line(4)][Column(0)] = 2;
other[Line(5)][Column(0)] = 3;
other[Line(6)][Column(0)] = 4;
other[Line(7)][Column(0)] = 5;
other[Line(8)][Column(0)] = 6;
other[Line(9)][Column(0)] = 7;
for i in 0..10 {
assert_eq!(grid[Line(i)][Column(0)], other[Line(i)][Column(0)],
"index={}; actual: {:?}, expected: {:?}",
Line(i), grid[Line(i)][Column(0)], other[Line(i)][Column(0)]);
}
assert_eq!(grid[Line(0)][Column(0)], 0); // was 8
assert_eq!(grid[Line(0)].occ, 0);
assert_eq!(grid[Line(1)][Column(0)], 0); // was 9
assert_eq!(grid[Line(1)].occ, 0);
assert_eq!(grid[Line(2)][Column(0)], 0);
assert_eq!(grid[Line(2)].occ, 1);
assert_eq!(grid[Line(3)][Column(0)], 1);
assert_eq!(grid[Line(3)].occ, 1);
assert_eq!(grid[Line(4)][Column(0)], 2);
assert_eq!(grid[Line(4)].occ, 1);
assert_eq!(grid[Line(5)][Column(0)], 3);
assert_eq!(grid[Line(5)].occ, 1);
assert_eq!(grid[Line(6)][Column(0)], 4);
assert_eq!(grid[Line(6)].occ, 1);
assert_eq!(grid[Line(7)][Column(0)], 5);
assert_eq!(grid[Line(7)].occ, 1);
assert_eq!(grid[Line(8)][Column(0)], 6);
assert_eq!(grid[Line(8)].occ, 1);
assert_eq!(grid[Line(9)][Column(0)], 7);
assert_eq!(grid[Line(9)].occ, 1);
}
// Test that GridIterator works

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long