use std::collections::HashMap;
use anyhow::Context;
use mcvm_core::auth_crate::mc::ClientId;
use mcvm_core::io::java::args::MemoryNum;
use mcvm_core::io::java::install::JavaInstallationKind;
use mcvm_core::user::UserManager;
use mcvm_plugin::hook_call::HookHandle;
use mcvm_plugin::hooks::{
InstanceLaunchArg, OnInstanceLaunch, OnInstanceStop, WhileInstanceLaunch,
};
use mcvm_shared::id::InstanceID;
use mcvm_shared::output::{MCVMOutput, MessageContents, MessageLevel};
use mcvm_shared::{translate, UpdateDepth};
use reqwest::Client;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::tracking::RunningInstanceRegistry;
use super::update::manager::UpdateManager;
use crate::config::instance::QuickPlay;
use crate::io::lock::Lockfile;
use crate::io::paths::Paths;
use crate::plugin::PluginManager;
use super::Instance;
impl Instance {
pub async fn launch(
&mut self,
paths: &Paths,
users: &mut UserManager,
plugins: &PluginManager,
settings: LaunchSettings,
o: &mut impl MCVMOutput,
) -> anyhow::Result<InstanceHandle> {
o.display(
MessageContents::StartProcess(translate!(o, StartUpdatingInstance, "inst" = &self.id)),
MessageLevel::Important,
);
let mut manager = UpdateManager::new(UpdateDepth::Shallow);
let client = Client::new();
manager.set_version(&self.config.version);
manager.add_requirements(self.get_requirements());
manager.set_client_id(settings.ms_client_id);
if settings.offline_auth {
manager.offline_auth();
}
manager
.fulfill_requirements(users, plugins, paths, &client, o)
.await
.context("Update failed")?;
let mut lock = Lockfile::open(paths).context("Failed to open lockfile")?;
let result = self
.setup(&mut manager, plugins, paths, users, &mut lock, o)
.await
.context("Failed to update instance")?;
manager.add_result(result);
let hook_arg = InstanceLaunchArg {
id: self.id.to_string(),
side: Some(self.get_side()),
dir: self.dirs.get().inst_dir.to_string_lossy().into(),
game_dir: self.dirs.get().game_dir.to_string_lossy().into(),
version_info: manager.version_info.get_clone(),
custom_config: self.config.plugin_config.clone(),
pid: None,
};
let mut installed_version = manager
.get_core_version(o)
.await
.context("Failed to get core version")?;
let mut instance = self
.create_core_instance(&mut installed_version, paths, o)
.await
.context("Failed to create core instance")?;
o.end_process();
o.display(
MessageContents::StartProcess(translate!(o, PreparingLaunch)),
MessageLevel::Important,
);
let results = plugins
.call_hook(OnInstanceLaunch, &hook_arg, paths, o)
.context("Failed to call on launch hook")?;
for result in results {
result.result(o)?;
}
let handle = instance
.launch_with_handle(o)
.await
.context("Failed to launch core instance")?;
let hook_handles = plugins
.call_hook(WhileInstanceLaunch, &hook_arg, paths, o)
.context("Failed to call while launch hook")?;
let handle = InstanceHandle {
inner: handle,
instance_id: self.id.clone(),
hook_handles,
hook_arg,
};
let mut running_instance_registry = RunningInstanceRegistry::open(paths)
.context("Failed to open registry of running instances")?;
running_instance_registry.add_instance(handle.get_pid(), &self.id);
let _ = running_instance_registry.write();
Ok(handle)
}
}
pub struct LaunchSettings {
pub ms_client_id: ClientId,
pub offline_auth: bool,
}
#[derive(Debug)]
pub struct LaunchOptions {
pub java: JavaInstallationKind,
pub jvm_args: Vec<String>,
pub game_args: Vec<String>,
pub min_mem: Option<MemoryNum>,
pub max_mem: Option<MemoryNum>,
pub env: HashMap<String, String>,
pub wrapper: Option<WrapperCommand>,
pub quick_play: QuickPlay,
pub use_log4j_config: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct WrapperCommand {
pub cmd: String,
#[serde(default)]
pub args: Vec<String>,
}
pub struct InstanceHandle {
inner: mcvm_core::InstanceHandle,
instance_id: InstanceID,
hook_handles: Vec<HookHandle<WhileInstanceLaunch>>,
hook_arg: InstanceLaunchArg,
}
impl InstanceHandle {
pub fn wait(
mut self,
plugins: &PluginManager,
paths: &Paths,
o: &mut impl MCVMOutput,
) -> anyhow::Result<std::process::ExitStatus> {
let pid = self.get_pid();
let result = self.inner.wait();
for handle in self.hook_handles {
handle
.kill(o)
.context("Failed to kill plugin sibling process")?;
}
Self::on_stop(&self.instance_id, pid, &self.hook_arg, plugins, paths, o)?;
result.context("Failed to get process exit status")
}
pub fn kill(
mut self,
plugins: &PluginManager,
paths: &Paths,
o: &mut impl MCVMOutput,
) -> anyhow::Result<()> {
let pid = self.get_pid();
for handle in self.hook_handles {
handle
.kill(o)
.context("Failed to kill plugin sibling process")?;
}
self.inner
.kill()
.context("Failed to kill inner instance handle")?;
Self::on_stop(&self.instance_id, pid, &self.hook_arg, plugins, paths, o)?;
Ok(())
}
pub fn get_process(self) -> std::process::Child {
self.inner.get_process()
}
pub fn get_pid(&self) -> u32 {
self.inner.get_pid()
}
fn on_stop(
instance_id: &str,
pid: u32,
arg: &InstanceLaunchArg,
plugins: &PluginManager,
paths: &Paths,
o: &mut impl MCVMOutput,
) -> anyhow::Result<()> {
let running_instance_registry = RunningInstanceRegistry::open(paths);
if let Ok(mut running_instance_registry) = running_instance_registry {
running_instance_registry.remove_instance(pid, instance_id);
let _ = running_instance_registry.write();
}
let results = plugins
.call_hook(OnInstanceStop, arg, paths, o)
.context("Failed to call on stop hook")?;
for result in results {
result.result(o)?;
}
Ok(())
}
}