Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nemesis_merge): test implementation to get an overview #4

Merged
merged 5 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 111 additions & 98 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jwalk = { version = "0.8.1" }
once_cell = "1.20.2"
phf = "0.11.2"
rayon = "1.10.0"
serde = { version = "1.0.215", features = ["derive"] } # Implement (De)Serialize
serde = { version = "1.0.216", features = ["derive"] } # Implement (De)Serialize
serde_json = "1.0.133" # gui: To avoid generate_context error.
serde_repr = "0.1.19" # C like Serialize enum into numbers.(Using progress reporing)
simd-json = "0.14.3"
Expand All @@ -43,11 +43,11 @@ tracing = { version = "0.1.41" } # logger
tracing-subscriber = "0.3.19"
winnow = { version = "0.6.20", features = ["simd"] }

serde_hkx = { git = "https://github.com/SARDONYX-sard/serde-hkx", tag = "0.5.0", default-features = false }
havok_classes = { git = "https://github.com/SARDONYX-sard/serde-hkx", tag = "0.5.0", default-features = false, features = [
serde_hkx = { git = "https://github.com/SARDONYX-sard/serde-hkx", tag = "0.6.0", default-features = false }
havok_classes = { git = "https://github.com/SARDONYX-sard/serde-hkx", tag = "0.6.0", default-features = false, features = [
"ignore_duplicates",
] }
serde_hkx_features = { git = "https://github.com/SARDONYX-sard/serde-hkx", tag = "0.5.0", default-features = false }
serde_hkx_features = { git = "https://github.com/SARDONYX-sard/serde-hkx", tag = "0.6.0", default-features = false }

# dev-dependencies
pretty_assertions = "1.4.1" # Color diff assertion
Expand Down
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,11 @@ The only thing we are considering at this time is support for the Nemesis patch.
Comments cannot be written in `Package.json`, so write them here.

Note that the following version must be fixed or it will not work for some
reason. (eslint in particular has many deprecated libraries at 8, but it can't
be helped)
reason.

- Biome:
- version: 1.9.3
- VS Code extension: 2.2.3
- eslint: ^8

- mui/x-data-grid, when changing from `7.22.2` to `7.23.1`, the `setState` in
`handleRowSelectionModelChange` is now
Expand Down
2 changes: 1 addition & 1 deletion core/nemesis_merge/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ tokio = { workspace = true, features = [
workspace = true

[features]
default = ["serde"]
default = ["serde", "tracing"]
# Output patch json, json just before hkx
debug = ["tracing"]
tracing = ["dep:tracing"]
Expand Down
44 changes: 14 additions & 30 deletions core/nemesis_merge/src/merger/apply_patches.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,30 @@
//! Processes a list of Nemesis XML paths and generates JSON output in the specified directory.
#![allow(clippy::mem_forget)]
use super::{
aliases::{BorrowedTemplateMap, ModPatchMap},
results::filter_results,
aliases::BorrowedTemplateMap, merge_patches::TemplatePatches, results::filter_results,
};
use crate::error::{Error, NemesisXmlErrSnafu, PatchSnafu, Result};
use crate::error::{Error, PatchSnafu, Result};
use json_patch::apply_patch;
use nemesis_xml::patch::parse_nemesis_patch;
use rayon::prelude::*;
use snafu::ResultExt;

pub fn apply_patches<'a, 'b: 'a>(
templates: &BorrowedTemplateMap<'a>,
patch_mod_map: &'b ModPatchMap,
patch_mod_map: TemplatePatches<'b>,
) -> Result<(), Vec<Error>> {
let results = patch_mod_map
.par_iter()
.flat_map(|(_mode_code, patch_map)| {
#[cfg(feature = "tracing")]
tracing::debug!(_mode_code);
patch_map.par_iter().map(|(template_target, nemesis_xml)| {
let patches_json =
parse_nemesis_patch(nemesis_xml).with_context(|_| NemesisXmlErrSnafu {
path: template_target.clone(),
.into_par_iter()
.map(|(template_target, patches)| {
if let Some(mut template_pair) = templates.get_mut(&template_target) {
let template = &mut template_pair.value_mut().1;
for patch in patches {
let patch_string = format!("{patch:#?}"); // TODO: Fix redundant copy
apply_patch(template, patch).with_context(|_| PatchSnafu {
template_name: template_target.clone(),
patch: patch_string,
})?;
#[cfg(feature = "tracing")]
tracing::debug!(template_target);
#[cfg(feature = "tracing")]
tracing::debug!("patches_json = {patches_json:#?}");

if let Some(mut template_pair) = templates.get_mut(template_target) {
let template = &mut template_pair.value_mut().1;
for patch in patches_json {
let patch_string = format!("{patch:#?}"); // TODO: Fix redundant copy
apply_patch(template, patch).with_context(|_| PatchSnafu {
template_name: template_target.clone(),
patch: patch_string,
})?;
}
}
Ok(())
})
}
Ok(())
})
.collect();

Expand Down
59 changes: 34 additions & 25 deletions core/nemesis_merge/src/merger/behavior_gen.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! Processes a list of Nemesis XML paths and generates JSON output in the specified directory.
#![allow(clippy::mem_forget)]
use super::{
apply_patches::apply_patches,
collect_templates_and_patches::collect_templates_and_patches,
config::{Config, Status},
hkx_files_gen::generate_hkx_files,
merge_patches::{merge_mod_patches, paths_to_ids},
write_errors::write_errors,
};
use crate::error::{Error, Result};
Expand All @@ -21,25 +21,31 @@ pub async fn behavior_gen(nemesis_paths: Vec<PathBuf>, options: Config) -> Resul

// 1/4:
options.report_status(Status::ReadingTemplatesAndPatches);
let (templates, patch_mod_map) = match collect_templates_and_patches(nemesis_paths, &options) {
Ok((new_templates, new_patch_mod_map)) => (new_templates, new_patch_mod_map),
let (templates, mod_patch_map) = match collect_templates_and_patches(&nemesis_paths, &options) {
Ok((templates, patch_mod_map)) => (templates, patch_mod_map),
Err(errors) => {
let errors_len = errors.len();
write_errors(&error_output, &errors).await?;
return Err(Error::FailedToReadTemplateAndPatches { errors_len });
}
};
#[cfg(feature = "tracing")]
tracing::debug!("mod_patch_map = {mod_patch_map:#?}");

{
// Lifetime inversion hack: `templates` require `patch_mod_map` to live longer than `templates`, but `templates` actually live longer than `templates`.
// HACK: Lifetime inversion hack: `templates` require `patch_mod_map` to live longer than `templates`, but `templates` actually live longer than `templates`.
// Therefore, reassign the local variable in the block to shorten the lifetime
let templates = templates;

// TODO: 2/4: Priority joins between patches may allow templates to be processed in a parallel loop.
// 2/4: Priority joins between patches may allow templates to be processed in a parallel loop.
let patches = {
let ids = paths_to_ids(&nemesis_paths);
merge_mod_patches(&mod_patch_map, ids)?
};

// 3/4: Apply patches
options.report_status(Status::ApplyingPatches);
let mut errors = match apply_patches(&templates, &patch_mod_map) {
let mut errors = match apply_patches(&templates, patches) {
Ok(()) => vec![],
Err(errors) => errors,
};
Expand Down Expand Up @@ -73,35 +79,38 @@ mod tests {

#[tokio::test]
#[ignore = "unimplemented yet"]
#[cfg_attr(feature = "tracing", quick_tracing::init)]
#[cfg_attr(
feature = "tracing",
quick_tracing::init(file = "../../dummy/merge_test.log", stdio = false)
)]
async fn merge_test() {
#[allow(clippy::iter_on_single_items)]
let ids = [
"../../dummy\\Data\\Nemesis_Engine\\mod\\aaaaa",
"../../dummy\\Data\\Nemesis_Engine\\mod\\bcbi",
"../../dummy\\Data\\Nemesis_Engine\\mod\\cbbi",
"../../dummy\\Data\\Nemesis_Engine\\mod\\gender",
"../../dummy\\Data\\Nemesis_Engine\\mod\\hmce",
"../../dummy\\Data\\Nemesis_Engine\\mod\\momo",
"../../dummy\\Data\\Nemesis_Engine\\mod\\na1w",
"../../dummy\\Data\\Nemesis_Engine\\mod\\nemesis",
"../../dummy\\Data\\Nemesis_Engine\\mod\\pscd",
"../../dummy\\Data\\Nemesis_Engine\\mod\\rthf",
"../../dummy\\Data\\Nemesis_Engine\\mod\\skice",
"../../dummy\\Data\\Nemesis_Engine\\mod\\sscb",
"../../dummy\\Data\\Nemesis_Engine\\mod\\tkuc",
"../../dummy\\Data\\Nemesis_Engine\\mod\\tudm",
"../../dummy\\Data\\Nemesis_Engine\\mod\\turn",
"../../dummy\\Data\\Nemesis_Engine\\mod\\zcbe",
// "../../dummy/Data/Nemesis_Engine/mod/aaaaa",
// "../../dummy/Data/Nemesis_Engine/mod/bcbi",
"../../dummy/Data/Nemesis_Engine/mod/cbbi",
// "../../dummy/Data/Nemesis_Engine/mod/gender",
// "../../dummy/Data/Nemesis_Engine/mod/hmce",
// "../../dummy/Data/Nemesis_Engine/mod/momo",
// "../../dummy/Data/Nemesis_Engine/mod/na1w",
// "../../dummy/Data/Nemesis_Engine/mod/nemesis",
// "../../dummy/Data/Nemesis_Engine/mod/pscd",
// "../../dummy/Data/Nemesis_Engine/mod/rthf",
// "../../dummy/Data/Nemesis_Engine/mod/skice",
// "../../dummy/Data/Nemesis_Engine/mod/sscb",
// "../../dummy/Data/Nemesis_Engine/mod/tkuc",
// "../../dummy/Data/Nemesis_Engine/mod/tudm",
// "../../dummy/Data/Nemesis_Engine/mod/turn",
// "../../dummy/Data/Nemesis_Engine/mod/zcbe",
]
.into_iter()
.into_par_iter()
.map(|s| s.into())
.collect();

behavior_gen(
ids,
Config {
resource_dir: "../../assets/templates".into(),
resource_dir: "../../resource/assets/templates".into(),
output_dir: "../../dummy/behavior_gen/output".into(),
status_report: None,
},
Expand Down
59 changes: 23 additions & 36 deletions core/nemesis_merge/src/merger/collect_templates_and_patches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ use snafu::ResultExt as _;
use std::fs;
use std::path::{Path, PathBuf};

type Results = Vec<Result<ModPatchPair, Vec<Error>>>;

pub fn collect_templates_and_patches<'a>(
nemesis_paths: Vec<PathBuf>,
nemesis_paths: &[PathBuf],
options: &Config,
) -> Result<(BorrowedTemplateMap<'a>, ModPatchMap), Vec<Error>> {
let templates = DashMap::new();

let results: Vec<Result<ModPatchPair, Vec<Error>>> = nemesis_paths
let results: Results = nemesis_paths
.par_iter()
.map(|patch_path| {
let result: Vec<_> = collect_nemesis_paths(patch_path)
Expand All @@ -46,21 +48,10 @@ pub fn collect_templates_and_patches<'a>(
})
.collect();

let (successes, errors): (Vec<ModPatchPair>, Vec<Vec<Error>>) =
results.into_par_iter().partition_map(|res| match res {
Ok((key, patch)) => Either::Left((key, patch)),
Err(errs) => Either::Right(errs),
});

let errors: Vec<Error> = errors.into_par_iter().flatten().collect();
if errors.is_empty() {
let patch_mod_map = successes.into_par_iter().collect();
Ok((templates, patch_mod_map))
} else {
Err(errors)
}
separate_results(results).map(|mod_patch_map| (templates, mod_patch_map))
}

/// Insert template & Get patch pairs
fn parse_and_process_path(
txt_path: &Path,
options: &Config,
Expand Down Expand Up @@ -107,27 +98,6 @@ fn parse_and_process_path(
let patch_txt = fs::read_to_string(txt_path).context(FailedIoSnafu {
path: txt_path.to_path_buf(),
})?;

#[cfg(feature = "debug")] // Output patch.json for debugging
{
let mut json_patch_path = options.output_dir.join(_relevant_path);
json_patch_path.set_extension("json");
let patches_json = nemesis_xml::patch::parse_nemesis_patch(&patch_txt).context(
crate::error::NemesisXmlErrSnafu {
path: json_patch_path.clone(),
},
)?;
fs::write(
&json_patch_path,
simd_json::to_string_pretty(&patches_json).context(JsonSnafu {
path: json_patch_path.clone(),
})?,
)
.context(FailedIoSnafu {
path: json_patch_path.clone(),
})?;
}

Ok((template_name_key, patch_txt))
}

Expand All @@ -136,3 +106,20 @@ fn template_xml_to_value(path: PathBuf) -> Result<BorrowedValue<'static>> {
let ast: ClassMap = serde_hkx::from_str(&template_xml)?;
to_borrowed_value(ast).context(JsonSnafu { path }) // TODO: fix needless realloc
}

/// Separate the result array into [`Ok`] and [`Err`] arrays.
#[inline]
fn separate_results(results: Results) -> Result<ModPatchMap, Vec<Error>> {
let (successes, errors): (Vec<ModPatchPair>, Vec<Vec<Error>>) =
results.into_par_iter().partition_map(|res| match res {
Ok((key, patch)) => Either::Left((key, patch)),
Err(errs) => Either::Right(errs),
});

let errors: Vec<Error> = errors.into_par_iter().flatten().collect();
if errors.is_empty() {
Ok(successes.into_par_iter().collect())
} else {
Err(errors)
}
}
Loading
Loading