Skip to content

Commit

Permalink
feat: support writing a separate cache file
Browse files Browse the repository at this point in the history
  • Loading branch information
jac3km4 committed Mar 7, 2024
1 parent 4f7586b commit d635455
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 37 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions scc/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ fn run(opts: Opts, r6_dir: &Path) -> anyhow::Result<bool> {
let settings = SccSettings {
r6_dir: r6_dir.into(),
custom_cache_file: Some(custom_cache_file.into()),
output_cache_file: opts.output_cache_file.map(PathBuf::into_boxed_path),
additional_script_paths,
};

Expand Down
25 changes: 25 additions & 0 deletions scc/cli/src/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ pub struct Opts {
pub profile: bool,
pub script_paths_file: Option<PathBuf>,
pub cache_file: Option<PathBuf>,
pub output_cache_file: Option<PathBuf>,
pub no_exec: bool,
pub no_debug: bool,
}
Expand Down Expand Up @@ -161,6 +162,19 @@ fn cache_dir() -> impl Parser<Option<PathBuf>> {
.catch()
}

fn output_cache_file() -> impl Parser<Option<PathBuf>> {
let tag = any::<String>("-outputCacheFile")
.help("A custom output path to write the final.redscripts to")
.guard(|s| s == "-outputCacheFile", "not outputCacheFile");
let value = positional::<PathBuf>("OUTPUT_CACHE_FILE").guard(is_not_slong, "starts with -");
construct!(tag, value)
.adjacent()
.anywhere()
.map(|pair| pair.1)
.optional()
.catch()
}

impl Opts {
pub fn get_parser() -> OptionParser<Opts> {
let optimize = toggle_options("-optimize", "Enable optimiziations. Off by default")
Expand Down Expand Up @@ -204,6 +218,7 @@ impl Opts {
no_breakpoint,
profile,
script_paths_file(),
output_cache_file(),
cache_file,
no_exec,
no_debug
Expand Down Expand Up @@ -296,13 +311,17 @@ mod test {
const CACHE_FILE: &str =
"C:\\Program Files (x86)\\Steam\\steamapps\\common\\Cyberpunk 2077\\r6\\cache\\final.redscripts";

const OUTPUT_CACHE_FILE: &str =
"C:\\Program Files (x86)\\Steam\\steamapps\\common\\Cyberpunk 2077\\r6\\cache\\final.redscripts.modded";

const CACHE_DIR: &str = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Cyberpunk 2077\\r6\\cache\\modded";

#[template]
#[rstest]
fn file_directory_orders(
#[values(SCRIPTS_DIR)] scripts_dir: &str,
#[values(Some(CACHE_FILE), None)] cache_file: Option<&str>,
#[values(Some(OUTPUT_CACHE_FILE), None)] output_cache_file: Option<&str>,
#[values(Some(CACHE_DIR), None)] cache_dir: Option<&str>,
#[values(Some(PATHS_FILE), None)] script_paths_file: Option<&str>,
#[values(&["none"], &[])] warnings: &[&str],
Expand Down Expand Up @@ -336,6 +355,7 @@ mod test {
fn standard(
scripts_dir: &str,
cache_file: Option<&str>,
output_cache_file: Option<&str>,
cache_dir: Option<&str>,
script_paths_file: Option<&str>,
warnings: &[&str],
Expand All @@ -353,6 +373,10 @@ mod test {
if let Some(value) = cache_file {
args.push(PathBuf::from(value).into());
}
if let Some(value) = output_cache_file {
args.push("-outputCacheFile".into());
args.push(PathBuf::from(value).into());
}
if let Some(value) = cache_dir {
args.push("-customCacheDir".into());
args.push(PathBuf::from(value).into());
Expand Down Expand Up @@ -402,6 +426,7 @@ mod test {

self::assert_eq!(opts.scripts_dir, PathBuf::from(scripts_dir));
self::assert_eq!(opts.cache_file, cache_file.map(PathBuf::from));
self::assert_eq!(opts.output_cache_file, output_cache_file.map(PathBuf::from));
self::assert_eq!(opts.cache_dir, cache_dir.map(PathBuf::from));
self::assert_eq!(opts.script_paths_file, script_paths_file.map(PathBuf::from));
self::assert_eq!(opts.warnings, warnings);
Expand Down
5 changes: 5 additions & 0 deletions scc/lib-tests/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ fn api_functions_are_stable() {
let SccApi {
settings_new,
settings_set_custom_cache_file,
settings_set_output_cache_file,
settings_add_script_path,
compile,
free_result,
Expand All @@ -32,6 +33,8 @@ fn api_functions_are_stable() {
let _settings_new: unsafe extern "C" fn(*const i8) -> *mut SccSettings = settings_new.unwrap();
let _settings_set_custom_cache_file: unsafe extern "C" fn(*mut SccSettings, *const i8) =
settings_set_custom_cache_file.unwrap();
let _settings_set_output_cache_file: unsafe extern "C" fn(*mut SccSettings, *const i8) =
settings_set_output_cache_file.unwrap();
let _settings_add_script_path: unsafe extern "C" fn(*mut SccSettings, *const i8) =
settings_add_script_path.unwrap();
let _compile: unsafe extern "C" fn(*mut SccSettings) -> *mut SccResult = compile.unwrap();
Expand Down Expand Up @@ -88,6 +91,7 @@ fn compiles_successfully() {
let settings = (api.settings_new.unwrap())(r6_dir_cstr.as_ptr() as _);
(api.settings_add_script_path.unwrap())(settings, script_path_cstr.as_ptr() as _);
(api.settings_set_custom_cache_file.unwrap())(settings, bundle_path_cstr.as_ptr() as _);
(api.settings_set_output_cache_file.unwrap())(settings, bundle_path_cstr.as_ptr() as _);

let result = (api.compile.unwrap())(settings);
let output = (api.get_success.unwrap())(result);
Expand Down Expand Up @@ -200,6 +204,7 @@ fn load_api() -> SccApi {
settings_new: lib.sym("scc_settings_new\0").unwrap(),
settings_set_custom_cache_file: lib.sym("scc_settings_set_custom_cache_file\0").unwrap(),
settings_add_script_path: lib.sym("scc_settings_add_script_path\0").unwrap(),
settings_set_output_cache_file: lib.sym("scc_settings_set_output_cache_file\0").unwrap(),
compile: lib.sym("scc_compile\0").unwrap(),
free_result: lib.sym("scc_free_result\0").unwrap(),
get_success: lib.sym("scc_get_success\0").unwrap(),
Expand Down
1 change: 1 addition & 0 deletions scc/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ flexi_logger.workspace = true
serde = { version = "1", features = ["derive"] }
toml = "0.8"
vmap = { version = "0.6", default-features = false, optional = true }
normpath = "1"
fd-lock = "4"
msgbox = { version = "0.7", optional = true }

Expand Down
12 changes: 12 additions & 0 deletions scc/lib/include/scc.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ typedef void scc_settings_set_custom_cache_file(
SccSettings* settings,
const char* cache_file);

typedef void scc_settings_set_output_cache_file(
SccSettings* settings,
const char* cache_file);

typedef void scc_settings_add_script_path(
SccSettings* settings,
const char* path);
Expand Down Expand Up @@ -71,6 +75,13 @@ typedef struct SccApi {
* Overrides the default cache file location.
*/
scc_settings_set_custom_cache_file* settings_set_custom_cache_file;
/**
* Sets a custom output cache file location.
*
* Added in redscript 0.5.18. It will be null if the loaded library version is older.
* The caller should do a null check if they want to maintain backward compatibility.
*/
scc_settings_set_output_cache_file* settings_set_output_cache_file;
/**
* Adds a path to be searched for scripts during compilation.
*/
Expand Down Expand Up @@ -133,6 +144,7 @@ inline SccApi scc_load_api(HMODULE module)
SccApi api = {
(scc_settings_new*)GetProcAddress(module, "scc_settings_new"),
(scc_settings_set_custom_cache_file*)GetProcAddress(module, "scc_settings_set_custom_cache_file"),
(scc_settings_set_output_cache_file*)GetProcAddress(module, "scc_settings_set_output_cache_file"),
(scc_settings_add_script_path*)GetProcAddress(module, "scc_settings_add_script_path"),
(scc_compile*)GetProcAddress(module, "scc_compile"),
(scc_free_result*)GetProcAddress(module, "scc_free_result"),
Expand Down
10 changes: 10 additions & 0 deletions scc/lib/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub unsafe extern "C" fn scc_settings_new(r6_dir: *const i8) -> Box<SccSettings>
Box::new(SccSettings {
r6_dir: PathBuf::from(CStr::from_ptr(r6_dir).to_string_lossy().as_ref()).into_boxed_path(),
custom_cache_file: None,
output_cache_file: None,
additional_script_paths: vec![],
})
}
Expand All @@ -29,6 +30,14 @@ pub unsafe extern "C" fn scc_settings_set_custom_cache_file(settings: &mut SccSe
settings.custom_cache_file = Some(PathBuf::from(CStr::from_ptr(path).to_string_lossy().as_ref()).into_boxed_path());
}

/// # Safety
/// The caller must ensure that `settings` is a valid pointer to a `SccSettings` struct and
/// `path` is a valid null-terminated UTF-8 string.
#[no_mangle]
pub unsafe extern "C" fn scc_settings_set_output_cache_file(settings: &mut SccSettings, path: *const i8) {
settings.output_cache_file = Some(PathBuf::from(CStr::from_ptr(path).to_string_lossy().as_ref()).into_boxed_path());
}

/// # Safety
/// The caller must ensure that `settings` is a valid pointer to a `SccSettings` struct and
/// `path` is a valid null-terminated UTF-8 string.
Expand Down Expand Up @@ -158,6 +167,7 @@ pub extern "C" fn scc_source_ref_line(output: &SccOutput, link: &SourceRef) -> u
pub struct SccSettings {
pub r6_dir: Box<Path>,
pub custom_cache_file: Option<Box<Path>>,
pub output_cache_file: Option<Box<Path>>,
pub additional_script_paths: Vec<Box<Path>>,
}

Expand Down
101 changes: 65 additions & 36 deletions scc/lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use flexi_logger::{Age, Cleanup, Criterion, Duplicate, FileSpec, LogSpecBuilder,
use hashbrown::{HashMap, HashSet};
use hints::UserHints;
use log::LevelFilter;
use normpath::PathExt;
use redscript::ast::Span;
use redscript::bundle::{ConstantPool, ScriptBundle};
use redscript::definition::{Definition, Enum};
Expand Down Expand Up @@ -46,7 +47,7 @@ pub fn compile(settings: &SccSettings) -> Box<SccResult> {

fn try_compile(settings: &SccSettings) -> anyhow::Result<SccResult> {
let default_cache_dir = settings.r6_dir.join("cache");
let cache_file = settings
let cache_path = settings
.custom_cache_file
.as_deref()
.map(PathBuf::from)
Expand All @@ -55,15 +56,18 @@ fn try_compile(settings: &SccSettings) -> anyhow::Result<SccResult> {
.chain(settings.additional_script_paths.iter().cloned())
.collect::<Vec<_>>();

if !cache_file.exists() {
if !cache_path.exists() {
let base_cache_file = get_base_bundle_path(&default_cache_dir);
let cache_dir = cache_file.parent().context("Provided cache file path has no parent")?;
let cache_dir = cache_path
.parent()
.context("Provided cache file path has no parent directory")?;
fs::create_dir_all(cache_dir).context("Failed to create the cache directory")?;
fs::copy(base_cache_file, &cache_file).context("Could not copy the base script cache file")?;
fs::copy(base_cache_file, &cache_path).context("Could not copy the base script cache file")?;
}

let backup_path = cache_file.with_extension(BACKUP_FILE_EXT);
let backup_path = cache_path.with_extension(BACKUP_FILE_EXT);
let fallback_backup_path = settings.r6_dir.join("cache").join(BACKUP_FILE_NAME);
let output_cache_path = settings.output_cache_file.as_deref().unwrap_or(&cache_path);

if fallback_backup_path.exists() && !backup_path.exists() {
log::info!("Re-initializing backup file from {}", fallback_backup_path.display());
Expand All @@ -72,9 +76,9 @@ fn try_compile(settings: &SccSettings) -> anyhow::Result<SccResult> {

let files = Files::from_dirs(&script_paths, &SourceFilter::None).context("Could not load script sources")?;

match try_compile_files(&settings.r6_dir, &cache_file, files) {
match try_compile_files(&settings.r6_dir, &cache_path, output_cache_path, files) {
Ok(output) => {
log::info!("Output successfully saved to {}", cache_file.display());
log::info!("Output successfully saved to {}", cache_path.display());
Ok(output)
}
Err(err) => {
Expand All @@ -92,13 +96,22 @@ fn try_compile(settings: &SccSettings) -> anyhow::Result<SccResult> {
}
}

fn try_compile_files(r6_dir: &Path, cache_file: &Path, files: Files) -> anyhow::Result<SccResult> {
let backup_path = cache_file.with_extension(BACKUP_FILE_EXT);
let timestamp_path = cache_file.with_extension(TIMESTAMP_FILE_EXT);

let fallback_timestamp_path = cache_file
fn try_compile_files(
r6_dir: &Path,
cache_path: &Path,
output_cache_path: &Path,
files: Files,
) -> anyhow::Result<SccResult> {
let backup_path = cache_path.with_extension(BACKUP_FILE_EXT);
let timestamp_path = cache_path.with_extension(TIMESTAMP_FILE_EXT);
#[cfg(windows)]
let is_output_file_separate = output_cache_path.normalize_virtually()? != cache_path.normalize_virtually()?;
#[cfg(not(windows))]
let is_output_file_separate = output_cache_path != cache_path;

let fallback_timestamp_path = cache_path
.parent()
.expect("script cache path should have a parent")
.expect("script cache path should have a parent directory")
.join(LEGACY_TIMESTAMP_FILE_NAME);

if !timestamp_path.exists() && fallback_timestamp_path.exists() {
Expand All @@ -119,43 +132,59 @@ fn try_compile_files(r6_dir: &Path, cache_file: &Path, files: Files) -> anyhow::
.write()
.context("Failed to acquire a write lock on the timestamp file")?;

let write_timestamp = File::open(cache_file)
let write_timestamp = File::open(cache_path)
.and_then(|f| CompileTimestamp::of_cache_file(&f))
.context("Failed to obtain a timestamp of the cache file")?;
let saved_timestamp =
CompileTimestamp::read(&mut *ts_file).context("Failed to read the existing timestamp file")?;

match saved_timestamp {
None if backup_path.exists() => {
log::info!("Previous cache backup file found");
}
saved_timestamp if saved_timestamp != Some(write_timestamp) => {
log::info!(
"Redscript cache file is not ours, copying it to {}",
backup_path.display()
);
fs::copy(cache_file, &backup_path).context("Failed to copy the cache file")?;
let input_cache_path = if is_output_file_separate {
match saved_timestamp {
saved_timestamp if saved_timestamp != Some(write_timestamp) && backup_path.exists() => {
log::info!("Removing a stale backup file at {}", backup_path.display());
fs::remove_file(&backup_path).context("Failed to remove a stale backup file")?;
}
_ if backup_path.exists() => {
log::info!("Restoring the backup file to {}", cache_path.display());
fs::rename(&backup_path, cache_path).context("Failed to restore the backup file")?;
}
_ => {}
}
Some(_) if !backup_path.exists() => {
return Err(anyhow::anyhow!(
"A REDScript timestamp was found, but backup files are missing, your installation \
might be corrupted, you should remove the 'r6/cache/redscript.ts' file and verify \
game files with Steam/GOG"
));
cache_path.to_path_buf()
} else {
match saved_timestamp {
None if backup_path.exists() => {
log::info!("Previous cache backup file found");
}
saved_timestamp if saved_timestamp != Some(write_timestamp) => {
log::info!(
"Redscript cache file is not ours, copying it to {}",
backup_path.display()
);
fs::copy(cache_path, &backup_path).context("Failed to copy the cache file")?;
}
Some(_) if !backup_path.exists() => {
return Err(anyhow::anyhow!(
"A REDScript timestamp was found, but backup files are missing, your \
installation might be corrupted, you should remove the \
'r6/cache/redscript.ts' file and verify game files with Steam/GOG"
));
}
_ => {}
}
_ => {}
}
backup_path
};

#[cfg(feature = "mmap")]
let mut bundle = {
let (map, _) = vmap::Map::with_options()
.open(backup_path)
.open(input_cache_path)
.context("Failed to open the original script cache file")?;
ScriptBundle::load(&mut io::Cursor::new(map.as_ref())).context("Failed to load the original script cache")?
};
#[cfg(not(feature = "mmap"))]
let mut bundle = {
let file = File::open(backup_path).context("Failed to open the original script cache file")?;
let file = File::open(input_cache_path).context("Failed to open the original script cache file")?;
ScriptBundle::load(&mut io::BufReader::new(file)).context("Failed to load the original script cache")?
};

Expand Down Expand Up @@ -183,10 +212,10 @@ fn try_compile_files(r6_dir: &Path, cache_file: &Path, files: Files) -> anyhow::

add_redscript_signature_def(&mut bundle.pool);

let mut file = File::create(cache_file).map_err(|err| match err.kind() {
let mut file = File::create(output_cache_path).map_err(|err| match err.kind() {
io::ErrorKind::PermissionDenied => anyhow::anyhow!(
"Could not write to '{}', make sure the file is not read-only",
cache_file.display()
output_cache_path.display()
),
_ => err.into(),
})?;
Expand Down
Loading

0 comments on commit d635455

Please sign in to comment.