1
Fork 0
mirror of https://github.com/redstrate/Physis.git synced 2025-04-20 19:57:45 +00:00

Improve performance when extracting files by caching open index files

In Hotspot it was revealed that a huge chunk of performance is lost due
to repeated IndexFile::from_existing calls, which may be on the same
index file. Now GameData transparently keeps a cache of these in memory
which speeds up the slowest link here.
This commit is contained in:
Joshua Goins 2023-10-13 16:58:08 -04:00
parent 7a75c170cc
commit b180adeb44
2 changed files with 42 additions and 24 deletions

View file

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com> // SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
use std::collections::HashMap;
use std::fs; use std::fs;
use std::fs::{DirEntry, ReadDir}; use std::fs::{DirEntry, ReadDir};
use std::path::PathBuf; use std::path::PathBuf;
@ -25,6 +26,8 @@ pub struct GameData {
/// Repositories in the game directory. /// Repositories in the game directory.
pub repositories: Vec<Repository>, pub repositories: Vec<Repository>,
index_files: HashMap<String, IndexFile>
} }
fn is_valid(path: &str) -> bool { fn is_valid(path: &str) -> bool {
@ -70,6 +73,7 @@ impl GameData {
true => Some(Self { true => Some(Self {
game_directory: String::from(directory), game_directory: String::from(directory),
repositories: vec![], repositories: vec![],
index_files: HashMap::new()
}), }),
false => { false => {
println!("Game data is not valid!"); println!("Game data is not valid!");
@ -119,21 +123,6 @@ impl GameData {
self.repositories.sort(); self.repositories.sort();
} }
fn get_index_file(&self, path: &str) -> Option<IndexFile> {
let (repository, category) = self.parse_repository_category(path)?;
let index_path: PathBuf = [
self.game_directory.clone(),
"sqpack".to_string(),
repository.name.clone(),
repository.index_filename(category),
]
.iter()
.collect();
IndexFile::from_existing(index_path.to_str()?)
}
fn get_dat_file(&self, path: &str, data_file_id: u32) -> Option<DatFile> { fn get_dat_file(&self, path: &str, data_file_id: u32) -> Option<DatFile> {
let (repository, category) = self.parse_repository_category(path).unwrap(); let (repository, category) = self.parse_repository_category(path).unwrap();
@ -162,11 +151,13 @@ impl GameData {
/// println!("Oh noes!"); /// println!("Oh noes!");
/// } /// }
/// ``` /// ```
pub fn exists(&self, path: &str) -> bool { pub fn exists(&mut self, path: &str) -> bool {
let hash = calculate_hash(path); let hash = calculate_hash(path);
let index_path = self.get_index_filename(path);
self.cache_index_file(&index_path);
let index_file = self let index_file = self
.get_index_file(path) .get_index_file(&index_path)
.expect("Failed to find index file."); .expect("Failed to find index file.");
index_file.entries.iter().any(|s| s.hash == hash) index_file.entries.iter().any(|s| s.hash == hash)
@ -186,12 +177,14 @@ impl GameData {
/// let mut file = std::fs::File::create("root.exl").unwrap(); /// let mut file = std::fs::File::create("root.exl").unwrap();
/// file.write(data.as_slice()).unwrap(); /// file.write(data.as_slice()).unwrap();
/// ``` /// ```
pub fn extract(&self, path: &str) -> Option<ByteBuffer> { pub fn extract(&mut self, path: &str) -> Option<ByteBuffer> {
debug!(file=path, "Extracting file"); debug!(file=path, "Extracting file");
let hash = calculate_hash(path); let hash = calculate_hash(path);
let index_path = self.get_index_filename(path);
let index_file = self.get_index_file(path)?; self.cache_index_file(&index_path);
let index_file = self.get_index_file(&index_path)?;
let slice = index_file.entries.iter().find(|s| s.hash == hash); let slice = index_file.entries.iter().find(|s| s.hash == hash);
match slice { match slice {
@ -222,7 +215,22 @@ impl GameData {
Some((&self.repositories[0], string_to_category(tokens[0])?)) Some((&self.repositories[0], string_to_category(tokens[0])?))
} }
pub fn read_excel_sheet_header(&self, name: &str) -> Option<EXH> { fn get_index_filename(&self, path: &str) -> String {
let (repository, category) = self.parse_repository_category(path).unwrap();
let index_path: PathBuf = [
&self.game_directory,
"sqpack",
&repository.name,
&repository.index_filename(category),
]
.iter()
.collect();
index_path.into_os_string().into_string().unwrap()
}
pub fn read_excel_sheet_header(&mut self, name: &str) -> Option<EXH> {
let root_exl_file = self.extract("exd/root.exl")?; let root_exl_file = self.extract("exd/root.exl")?;
let root_exl = EXL::from_existing(&root_exl_file)?; let root_exl = EXL::from_existing(&root_exl_file)?;
@ -240,7 +248,7 @@ impl GameData {
None None
} }
pub fn get_all_sheet_names(&self) -> Option<Vec<String>> { pub fn get_all_sheet_names(&mut self) -> Option<Vec<String>> {
let root_exl_file = self.extract("exd/root.exl")?; let root_exl_file = self.extract("exd/root.exl")?;
let root_exl = EXL::from_existing(&root_exl_file)?; let root_exl = EXL::from_existing(&root_exl_file)?;
@ -254,7 +262,7 @@ impl GameData {
} }
pub fn read_excel_sheet( pub fn read_excel_sheet(
&self, &mut self,
name: &str, name: &str,
exh: &EXH, exh: &EXH,
language: Language, language: Language,
@ -366,6 +374,16 @@ impl GameData {
Ok(()) Ok(())
} }
fn cache_index_file(&mut self, filename: &str) {
if !self.index_files.contains_key(filename) {
self.index_files.insert(filename.to_string(), IndexFile::from_existing(filename).unwrap());
}
}
fn get_index_file(&self, filename: &str) -> Option<&IndexFile> {
self.index_files.get(filename)
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -23,7 +23,7 @@ pub enum RepositoryType {
/// Encapsulates a directory of game data, such as "ex1". This data is also versioned. /// Encapsulates a directory of game data, such as "ex1". This data is also versioned.
/// This handles calculating the correct dat and index filenames, mainly for `GameData`. /// This handles calculating the correct dat and index filenames, mainly for `GameData`.
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Repository { pub struct Repository {
/// The folder name, such as "ex1". /// The folder name, such as "ex1".
pub name: String, pub name: String,
@ -65,7 +65,7 @@ impl PartialOrd for Repository {
/// This refers to the specific root directory a file is located in. /// This refers to the specific root directory a file is located in.
/// This is a fixed list of directories, and all of them are known. /// This is a fixed list of directories, and all of them are known.
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Category { pub enum Category {
/// Common files such as game fonts, and other data that doesn't really fit anywhere else. /// Common files such as game fonts, and other data that doesn't really fit anywhere else.
Common, Common,