From 649560fa09f1e680cfff85e3e9cd1bb8ed37ec66 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Wed, 5 Mar 2025 07:12:07 +0000 Subject: [PATCH] Decouple PluginSortingData and Plugin The SortingPlugin trait will make testing the sorting code easier, as it won't need to load plugin files. --- src/sorting/plugins.rs | 113 +++++++++++++++++++++++++++++----------- src/sorting/validate.rs | 33 ++++++------ 2 files changed, 101 insertions(+), 45 deletions(-) diff --git a/src/sorting/plugins.rs b/src/sorting/plugins.rs index fe96c092..1b590b49 100644 --- a/src/sorting/plugins.rs +++ b/src/sorting/plugins.rs @@ -25,8 +25,8 @@ use super::{ }; #[derive(Debug)] -pub struct PluginSortingData<'a> { - plugin: &'a Plugin, +pub struct PluginSortingData<'a, T: SortingPlugin> { + plugin: &'a T, pub(super) is_master: bool, override_record_count: usize, @@ -40,9 +40,9 @@ pub struct PluginSortingData<'a> { pub(super) user_req: Vec, } -impl<'a> PluginSortingData<'a> { +impl<'a, T: SortingPlugin> PluginSortingData<'a, T> { pub fn new( - plugin: &'a Plugin, + plugin: &'a T, masterlist_metadata: Option<&PluginMetadata>, user_metadata: Option<&PluginMetadata>, load_order_index: usize, @@ -91,15 +91,56 @@ impl<'a> PluginSortingData<'a> { self.plugin.masters() } - fn do_records_overlap(&self, other: &PluginSortingData) -> Result { + fn do_records_overlap(&self, other: &Self) -> Result { self.plugin.do_records_overlap(other.plugin) } - fn do_assets_overlap(&self, other: &PluginSortingData) -> bool { + fn do_assets_overlap(&self, other: &Self) -> bool { self.plugin.do_assets_overlap(other.plugin) } } +pub trait SortingPlugin { + fn name(&self) -> &str; + fn is_master(&self) -> bool; + fn is_blueprint_plugin(&self) -> bool; + fn masters(&self) -> Result, PluginDataError>; + fn override_record_count(&self) -> Result; + fn asset_count(&self) -> usize; + fn do_records_overlap(&self, other: &Self) -> Result; + fn do_assets_overlap(&self, other: &Self) -> bool; +} + +impl SortingPlugin for Plugin { + fn name(&self) -> &str { + self.name() + } + fn is_master(&self) -> bool { + self.is_master() + } + + fn is_blueprint_plugin(&self) -> bool { + self.is_blueprint_plugin() + } + + fn masters(&self) -> Result, PluginDataError> { + self.masters() + } + fn override_record_count(&self) -> Result { + self.override_record_count() + } + fn asset_count(&self) -> usize { + self.asset_count() + } + + fn do_records_overlap(&self, other: &Self) -> Result { + self.do_records_overlap(other) + } + fn do_assets_overlap(&self, other: &Self) -> bool { + self.do_assets_overlap(other) + } +} + fn to_filenames(files: &[File]) -> Vec { files .iter() @@ -107,21 +148,21 @@ fn to_filenames(files: &[File]) -> Vec { .collect() } -type InnerPluginsGraph<'a> = Graph>, EdgeType>; +type InnerPluginsGraph<'a, T> = Graph>, EdgeType>; -#[derive(Debug, Default)] -struct PluginsGraph<'a> { +#[derive(Debug)] +struct PluginsGraph<'a, T: SortingPlugin> { // Put the sorting data in Rc so that it can be held onto while mutating the graph. - inner: InnerPluginsGraph<'a>, + inner: InnerPluginsGraph<'a, T>, paths_cache: HashMap>, } -impl<'a> PluginsGraph<'a> { +impl<'a, T: SortingPlugin> PluginsGraph<'a, T> { fn new() -> Self { PluginsGraph::default() } - fn add_node(&mut self, plugin: PluginSortingData<'a>) { + fn add_node(&mut self, plugin: PluginSortingData<'a, T>) { self.inner.add_node(Rc::new(plugin)); } @@ -711,16 +752,26 @@ impl<'a> PluginsGraph<'a> { } } -impl<'a> std::ops::Index for PluginsGraph<'a> { - type Output = Rc>; +// The derive macro for Default requires T: Default, but it's not actually necessary. +impl std::default::Default for PluginsGraph<'_, T> { + fn default() -> Self { + Self { + inner: Default::default(), + paths_cache: Default::default(), + } + } +} + +impl<'a, T: SortingPlugin> std::ops::Index for PluginsGraph<'a, T> { + type Output = Rc>; fn index(&self, index: NodeIndex) -> &Self::Output { &self.inner[index] } } -pub fn sort_plugins( - mut plugins_sorting_data: Vec, +pub fn sort_plugins( + mut plugins_sorting_data: Vec>, groups_graph: &GroupsGraph, early_loading_plugins: &[String], ) -> Result, SortingError> { @@ -778,8 +829,8 @@ pub fn sort_plugins( Ok(masters_load_order) } -fn sort_plugins_partition( - plugins_sorting_data: Vec, +fn sort_plugins_partition( + plugins_sorting_data: Vec>, groups_graph: &GroupsGraph, early_loading_plugins: &[String], ) -> Result, SortingError> { @@ -824,7 +875,7 @@ fn sort_plugins_partition( Ok(sorted_plugin_names) } -fn path_to_string(graph: &InnerPluginsGraph, path: &[NodeIndex]) -> String { +fn path_to_string(graph: &InnerPluginsGraph, path: &[NodeIndex]) -> String { path.iter() .map(|i| graph[*i].name()) .collect::>() @@ -832,8 +883,8 @@ fn path_to_string(graph: &InnerPluginsGraph, path: &[NodeIndex]) -> String { } #[derive(Debug)] -struct PathFinder<'a, 'b> { - graph: &'a InnerPluginsGraph<'b>, +struct PathFinder<'a, 'b, T: SortingPlugin> { + graph: &'a InnerPluginsGraph<'b, T>, cache: &'a mut HashMap>, from_node_index: NodeIndex, to_node_index: NodeIndex, @@ -842,9 +893,9 @@ struct PathFinder<'a, 'b> { intersection_node: Option, } -impl<'a, 'b> PathFinder<'a, 'b> { +impl<'a, 'b, T: SortingPlugin> PathFinder<'a, 'b, T> { fn new( - graph: &'a InnerPluginsGraph<'b>, + graph: &'a InnerPluginsGraph<'b, T>, cache: &'a mut HashMap>, from_node_index: NodeIndex, to_node_index: NodeIndex, @@ -914,7 +965,7 @@ impl<'a, 'b> PathFinder<'a, 'b> { } } -impl BidirBfsVisitor for PathFinder<'_, '_> { +impl BidirBfsVisitor for PathFinder<'_, '_, T> { fn visit_forward_bfs_edge(&mut self, source: NodeIndex, target: NodeIndex) { self.cache_path(self.from_node_index, target); @@ -939,7 +990,9 @@ struct PathCacher<'a> { to_node_index: NodeIndex, } -fn get_plugins_in_groups(graph: &InnerPluginsGraph) -> HashMap> { +fn get_plugins_in_groups( + graph: &InnerPluginsGraph, +) -> HashMap> { let mut plugins_in_groups: HashMap> = HashMap::new(); for node in graph.node_indices() { @@ -985,8 +1038,8 @@ impl BidirBfsVisitor for PathCacher<'_> { type PluginNodeIndex = NodeIndex; type GroupNodeIndex = NodeIndex; -struct GroupsPathVisitor<'a, 'b, 'c, 'd, 'e> { - plugins_graph: &'a mut PluginsGraph<'b>, +struct GroupsPathVisitor<'a, 'b, 'c, 'd, 'e, T: SortingPlugin> { + plugins_graph: &'a mut PluginsGraph<'b, T>, groups_graph: &'e GroupsGraph, groups_plugins: &'c HashMap>, finished_group_vertices: &'d mut HashSet, @@ -995,9 +1048,9 @@ struct GroupsPathVisitor<'a, 'b, 'c, 'd, 'e> { unfinishable_nodes: HashSet, } -impl<'a, 'b, 'c, 'd, 'e> GroupsPathVisitor<'a, 'b, 'c, 'd, 'e> { +impl<'a, 'b, 'c, 'd, 'e, T: SortingPlugin> GroupsPathVisitor<'a, 'b, 'c, 'd, 'e, T> { fn new( - plugins_graph: &'a mut PluginsGraph<'b>, + plugins_graph: &'a mut PluginsGraph<'b, T>, groups_graph: &'e GroupsGraph, groups_plugins: &'c HashMap>, finished_group_vertices: &'d mut HashSet, @@ -1079,7 +1132,7 @@ impl<'a, 'b, 'c, 'd, 'e> GroupsPathVisitor<'a, 'b, 'c, 'd, 'e> { } } -impl<'e> DfsVisitor<'e> for GroupsPathVisitor<'_, '_, '_, '_, 'e> { +impl<'e, T: SortingPlugin> DfsVisitor<'e> for GroupsPathVisitor<'_, '_, '_, '_, 'e, T> { fn visit_tree_edge(&mut self, edge_ref: EdgeReference<'e, EdgeType>) { let source = edge_ref.source(); let target = edge_ref.target(); diff --git a/src/sorting/validate.rs b/src/sorting/validate.rs index 2182acae..0a16de9b 100644 --- a/src/sorting/validate.rs +++ b/src/sorting/validate.rs @@ -7,10 +7,13 @@ use crate::{ sorting::error::{CyclicInteractionError, PluginGraphValidationError, UndefinedGroupError}, }; -use super::{groups::GroupsGraph, plugins::PluginSortingData}; +use super::{ + groups::GroupsGraph, + plugins::{PluginSortingData, SortingPlugin}, +}; -pub fn validate_plugin_groups( - plugins_sorting_data: &[PluginSortingData<'_>], +pub fn validate_plugin_groups( + plugins_sorting_data: &[PluginSortingData<'_, T>], groups_graph: &GroupsGraph, ) -> Result<(), UndefinedGroupError> { let group_names: HashSet<&String> = groups_graph @@ -27,10 +30,10 @@ pub fn validate_plugin_groups( Ok(()) } -pub fn validate_specific_and_hardcoded_edges( - masters: &[PluginSortingData<'_>], - blueprint_masters: &[PluginSortingData<'_>], - non_masters: &[PluginSortingData<'_>], +pub fn validate_specific_and_hardcoded_edges( + masters: &[PluginSortingData<'_, T>], + blueprint_masters: &[PluginSortingData<'_, T>], + non_masters: &[PluginSortingData<'_, T>], early_loading_plugins: &[String], ) -> Result<(), PluginGraphValidationError> { log::trace!("Validating specific and early-loading plugin edges..."); @@ -53,8 +56,8 @@ pub fn validate_specific_and_hardcoded_edges( Ok(()) } -fn validate_masters( - masters: &[PluginSortingData<'_>], +fn validate_masters( + masters: &[PluginSortingData<'_, T>], non_masters: &HashSet>, blueprint_masters: &HashSet>, ) -> Result<(), PluginGraphValidationError> { @@ -66,8 +69,8 @@ fn validate_masters( .try_for_each(|m| validate_plugin(m, non_masters, blueprint_masters)) } -fn validate_non_masters( - non_masters: &[PluginSortingData<'_>], +fn validate_non_masters( + non_masters: &[PluginSortingData<'_, T>], blueprint_masters: &HashSet>, ) -> Result<(), PluginGraphValidationError> { log::trace!("Validating specific and early-loading plugin edges for non-master files..."); @@ -80,8 +83,8 @@ fn validate_non_masters( .try_for_each(|p| validate_plugin(p, &empty_set, blueprint_masters)) } -fn validate_plugin( - plugin: &PluginSortingData<'_>, +fn validate_plugin( + plugin: &PluginSortingData<'_, T>, non_masters: &HashSet>, blueprint_masters: &HashSet>, ) -> Result<(), PluginGraphValidationError> { @@ -176,9 +179,9 @@ fn validate_files( Ok(()) } -fn validate_early_loading_plugins( +fn validate_early_loading_plugins( early_loading_plugins: &[String], - masters: &[PluginSortingData<'_>], + masters: &[PluginSortingData<'_, T>], non_masters: &HashSet>, ) -> Result<(), CyclicInteractionError> { if let Some(master) = masters.first() {