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:
parent
7a75c170cc
commit
b180adeb44
2 changed files with 42 additions and 24 deletions
|
@ -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)]
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Reference in a new issue