Skip to main content

mcvm_config/
profile.rs

1use std::collections::HashMap;
2
3use anyhow::bail;
4use mcvm_shared::id::ProfileID;
5use mcvm_shared::Side;
6#[cfg(feature = "schema")]
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9
10use super::instance::{merge_instance_configs, InstanceConfig};
11use super::package::PackageConfigDeser;
12
13/// Configuration for a profile
14#[derive(Deserialize, Serialize, Clone)]
15#[cfg_attr(feature = "schema", derive(JsonSchema))]
16pub struct ProfileConfig {
17	/// The configuration for the instance
18	#[serde(flatten)]
19	pub instance: InstanceConfig,
20	/// Package configuration
21	#[serde(default)]
22	pub packages: ProfilePackageConfiguration,
23}
24
25impl ProfileConfig {
26	/// Merge this profile with another one
27	pub fn merge(&mut self, other: Self) {
28		self.instance = merge_instance_configs(&self.instance, other.instance);
29	}
30}
31
32/// Different representations of package configuration on a profile
33#[derive(Deserialize, Serialize, Debug, Clone)]
34#[cfg_attr(feature = "schema", derive(JsonSchema))]
35#[serde(untagged)]
36pub enum ProfilePackageConfiguration {
37	/// Is just a list of packages for every instance
38	Simple(Vec<PackageConfigDeser>),
39	/// Full configuration
40	Full {
41		/// Packages to apply to every instance
42		#[serde(default)]
43		global: Vec<PackageConfigDeser>,
44		/// Packages to apply to only clients
45		#[serde(default)]
46		client: Vec<PackageConfigDeser>,
47		/// Packages to apply to only servers
48		#[serde(default)]
49		server: Vec<PackageConfigDeser>,
50	},
51}
52
53impl Default for ProfilePackageConfiguration {
54	fn default() -> Self {
55		Self::Simple(Vec::new())
56	}
57}
58
59impl ProfilePackageConfiguration {
60	/// Validate all the configured packages
61	pub fn validate(&self) -> anyhow::Result<()> {
62		match &self {
63			Self::Simple(global) => {
64				for pkg in global {
65					pkg.validate()?;
66				}
67			}
68			Self::Full {
69				global,
70				client,
71				server,
72			} => {
73				for pkg in global.iter().chain(client.iter()).chain(server.iter()) {
74					pkg.validate()?;
75				}
76			}
77		}
78
79		Ok(())
80	}
81
82	/// Iterate over all of the packages
83	pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PackageConfigDeser> + 'a> {
84		match &self {
85			Self::Simple(global) => Box::new(global.iter()),
86			Self::Full {
87				global,
88				client,
89				server,
90			} => Box::new(global.iter().chain(client.iter()).chain(server.iter())),
91		}
92	}
93
94	/// Iterate over the global package list
95	pub fn iter_global(&self) -> impl Iterator<Item = &PackageConfigDeser> {
96		match &self {
97			Self::Simple(global) => global,
98			Self::Full { global, .. } => global,
99		}
100		.iter()
101	}
102
103	/// Iterate over the package list for a specific side
104	pub fn iter_side(&self, side: Side) -> impl Iterator<Item = &PackageConfigDeser> {
105		match &self {
106			Self::Simple(..) => [].iter(),
107			Self::Full { client, server, .. } => match side {
108				Side::Client => client.iter(),
109				Side::Server => server.iter(),
110			},
111		}
112	}
113
114	/// Adds a package to the global list
115	pub fn add_global_package(&mut self, pkg: PackageConfigDeser) {
116		match self {
117			Self::Simple(global) => global.push(pkg),
118			Self::Full { global, .. } => global.push(pkg),
119		}
120	}
121
122	/// Adds a package to the client list
123	pub fn add_client_package(&mut self, pkg: PackageConfigDeser) {
124		match self {
125			Self::Simple(global) => {
126				*self = Self::Full {
127					global: global.clone(),
128					client: vec![pkg],
129					server: Vec::new(),
130				}
131			}
132			Self::Full { client, .. } => client.push(pkg),
133		}
134	}
135
136	/// Adds a package to the server list
137	pub fn add_server_package(&mut self, pkg: PackageConfigDeser) {
138		match self {
139			Self::Simple(global) => {
140				*self = Self::Full {
141					global: global.clone(),
142					client: Vec::new(),
143					server: vec![pkg],
144				}
145			}
146			Self::Full { server, .. } => server.push(pkg),
147		}
148	}
149}
150
151/// Consolidates profile configs into the full profiles
152pub fn consolidate_profile_configs(
153	profiles: HashMap<ProfileID, ProfileConfig>,
154	global_profile: Option<&ProfileConfig>,
155) -> anyhow::Result<HashMap<ProfileID, ProfileConfig>> {
156	let mut out: HashMap<_, ProfileConfig> = HashMap::with_capacity(profiles.len());
157
158	let max_iterations = 10000;
159
160	// We do this by repeatedly finding a profile with an already resolved ancenstor
161	let mut i = 0;
162	while out.len() != profiles.len() {
163		for (id, profile) in &profiles {
164			// Don't redo profiles that are already done
165			if out.contains_key(id) {
166				continue;
167			}
168
169			if profile.instance.common.from.is_empty() {
170				// Profiles with no ancestor can just be added directly to the output, after deriving from the global profile
171				let mut profile = profile.clone();
172				if let Some(global_profile) = global_profile {
173					let overlay = profile;
174					profile = global_profile.clone();
175					profile.merge(overlay);
176				}
177				out.insert(id.clone(), profile);
178			} else {
179				for parent in profile.instance.common.from.iter() {
180					// If the parent is already in the map (already consolidated) then we can derive from it and add to the map
181					if let Some(parent) = out.get(&ProfileID::from(parent.clone())) {
182						let mut new = parent.clone();
183						new.merge(profile.clone());
184						out.insert(id.clone(), new);
185					} else {
186						bail!("Parent profile '{parent}' does not exist");
187					}
188				}
189			}
190		}
191
192		i += 1;
193		if i > max_iterations {
194			panic!("Max iterations exceeded while resolving profiles. This is a bug in MCVM.");
195		}
196	}
197
198	Ok(out)
199}