Skip to content

Commit

Permalink
Optimizations related to image caching
Browse files Browse the repository at this point in the history
  • Loading branch information
Futsch1 committed Jan 6, 2022
1 parent 37b6dc6 commit e5e6de2
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 30 deletions.
9 changes: 8 additions & 1 deletion src/item_sort_list/file_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use super::item_traits::PropertyResolver;
pub type HashType = ImageHash<Vec<u8>>;

/// A single file item with all properties required by image_sieve
#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
#[derive(Eq, Debug, Clone, Serialize, Deserialize)]
pub struct FileItem {
/// Actual file path
pub path: PathBuf,
Expand Down Expand Up @@ -255,6 +255,13 @@ impl Display for FileItem {
}
}

impl PartialEq for FileItem {
/// Check if two file items are equal
fn eq(&self, other: &Self) -> bool {
self.path == other.path
}
}

impl PartialOrd for FileItem {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
Expand Down
46 changes: 27 additions & 19 deletions src/main_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,15 @@ impl MainWindow {

move |i: i32| {
let index = list_model.row_data(i as usize).local_index as usize;
let item_list = &item_list.lock().unwrap();
synchronize_images_model(
index,
&item_list.lock().unwrap(),
item_list,
similar_items_model.clone(),
window_weak.clone(),
&image_cache,
);
prefetch(i, item_list, &list_model, &image_cache);
}
});

Expand Down Expand Up @@ -432,13 +434,14 @@ impl MainWindow {
}
}

/// Removes all items from a model
fn empty_model<T: 'static + Clone>(item_list_model: Rc<sixtyfps::VecModel<T>>) {
for _ in 0..item_list_model.row_count() {
item_list_model.remove(0);
}
}

/// Synchronizes the list of found items from the internal data structure with the sixtyfps VecModel
/// Fills the list of found items from the internal data structure to the sixtyfps VecModel
pub fn populate_item_list_model(
item_list: &ItemList,
item_list_model: &sixtyfps::VecModel<ListItem>,
Expand All @@ -447,7 +450,7 @@ pub fn populate_item_list_model(
for image in item_list
.items
.iter()
.filter(|item| filter_file_item(item, filters))
.filter(|item| filter_file_items(item, filters))
{
let list_item = list_item_from_file_item(image, item_list);
item_list_model.push(list_item);
Expand Down Expand Up @@ -559,27 +562,31 @@ fn synchronize_images_model(
add_item(image_index, false, window.clone());
}

// Prefetch next two images
let mut prefetch_index = selected_item_index + 1;
let mut prefetches = 2;
while prefetches > 0 && prefetch_index < item_list.items.len() {
if !similars.contains(&prefetch_index) {
if let Some(file_item) = item_list.items.get(prefetch_index) {
if file_item.is_image() {
image_cache.load(file_item, Purpose::Prefetch, None);
prefetches -= 1;
}
}
}
prefetch_index += 1;
}

// Set properties
window
.unwrap()
.set_current_image(similar_items_model.row_data(0));
}

/// Prefetch the next imagesin the model list
fn prefetch(
model_index: i32,
item_list: &ItemList,
list_model: &sixtyfps::VecModel<ListItem>,
image_cache: &ImageCache,
) {
// Prefetch next two images
for i in model_index + 1..model_index + 3 {
if i < list_model.row_count() as i32 {
let list_item = &list_model.row_data(i as usize);
let file_item = &item_list.items[list_item.local_index as usize];
if file_item.is_image() {
image_cache.load(file_item, Purpose::Prefetch, None);
}
}
}
}

/// Gets the text of a an item at a given index as a SharedString
pub fn get_item_text(index: usize, item_list: &ItemList) -> SharedString {
let item = &item_list.items[index];
Expand Down Expand Up @@ -659,7 +666,8 @@ pub fn sieve(
});
}

fn filter_file_item(file_item: &FileItem, filters: &Filters) -> bool {
/// Filter file items to display in the item list
fn filter_file_items(file_item: &FileItem, filters: &Filters) -> bool {
let mut visible = true;
if !filters.images && file_item.is_image() {
visible = false;
Expand Down
46 changes: 36 additions & 10 deletions src/misc/image_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ type ImagesMapMutex = Mutex<LruMap<ImageBuffer, String, 64>>;
type LoadQueue = Mutex<VecDeque<LoadImageCommand>>;
/// The callback which is executed when an image was loade
pub type DoneCallback = Box<dyn Fn(ImageBuffer) + Send + 'static>;
/// The command sent to the load thread for a new image
pub type LoadImageCommand = (FileItem, u32, u32, Option<DoneCallback>);

const HOURGLASS_PNG: &[u8; 5533] = include_bytes!("hourglass.png");

Expand All @@ -31,6 +29,19 @@ pub enum Purpose {
Prefetch,
}

struct LoadImageCommand {
pub file_item: FileItem,
pub width: u32,
pub height: u32,
pub callback: Option<DoneCallback>,
}

impl PartialEq for LoadImageCommand {
fn eq(&self, other: &Self) -> bool {
self.file_item == other.file_item
}
}

/// An image cache that provides some priorization on the images to load. The cache loads images in the background and executes
/// a callback when the image is loaded.
/// The cache can restrict the sizes of loaded images to reduce memory usage.
Expand Down Expand Up @@ -126,7 +137,12 @@ impl ImageCache {
/// The purpose of the image needs to be indicated to determine the loading priority. When the image was loaded,
/// the done callback is executed.
pub fn load(&self, item: &FileItem, purpose: Purpose, done_callback: Option<DoneCallback>) {
let command = (item.clone(), self.max_width, self.max_width, done_callback);
let command = LoadImageCommand {
file_item: item.clone(),
width: self.max_width,
height: self.max_height,
callback: done_callback,
};
match purpose {
Purpose::SelectedImage => {
let mut queue = self.primary_queue.lock().unwrap();
Expand All @@ -141,7 +157,9 @@ impl ImageCache {
}
Purpose::Prefetch => {
let mut queue = self.secondary_queue.lock().unwrap();
queue.push_back(command);
if !queue.contains(&command) {
queue.push_back(command);
}
self.secondary_sender.send(()).ok();
}
}
Expand All @@ -160,26 +178,34 @@ fn load_image_thread(
if next_item.is_none() {
continue;
}
let (prefetch_item, max_width, max_height, callback) = next_item.unwrap();
let item_path = prefetch_item.path.to_str().unwrap();
let command = next_item.unwrap();
let item_path = command.file_item.path.to_str().unwrap();
// First try to get the image from the cache
let contains_key = {
let map = cache.lock().unwrap();
map.contains(String::from(item_path))
};
// If it is not in the cache, load it from the file and put it into the cache
if !contains_key {
let image_buffer = if prefetch_item.is_image() {
crate::misc::images::get_image_buffer(&prefetch_item, max_width, max_height)
let image_buffer = if command.file_item.is_image() {
crate::misc::images::get_image_buffer(
&command.file_item,
command.width,
command.height,
)
} else {
crate::misc::video_to_image::get_image_buffer(&prefetch_item, max_width, max_height)
crate::misc::video_to_image::get_image_buffer(
&command.file_item,
command.width,
command.height,
)
};
let mut map = cache.lock().unwrap();
map.put(String::from(item_path), image_buffer.clone());
}

// If a callback was indicated, execute it passing a clone of the image
if let Some(callback) = callback {
if let Some(callback) = command.callback {
let image = {
let mut map = cache.lock().unwrap();
map.get(String::from(item_path)).cloned()
Expand Down

0 comments on commit e5e6de2

Please sign in to comment.