Skip to content
Open
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
9 changes: 9 additions & 0 deletions packages/app-lib/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,15 @@ impl std::fmt::Display for Error {
}
}

impl Clone for Error {
fn clone(&self) -> Self {
Self {
raw: Arc::clone(&self.raw),
source: Arc::clone(&self.raw).in_current_span(),
}
}
}

impl<E: Into<ErrorKind>> From<E> for Error {
fn from(source: E) -> Self {
let error = Into::<ErrorKind>::into(source);
Expand Down
7 changes: 3 additions & 4 deletions packages/app-lib/src/state/dirs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
pub(crate) mod move_app_directory;
use crate::LoadingBarType;
use crate::event::emit::{emit_loading, init_loading};
use crate::state::LAUNCHER_STATE;
use crate::state::Settings;
use crate::state::{Settings, State};
use crate::util::fetch::IoSemaphore;
use std::path::PathBuf;
use tokio::fs;
Expand All @@ -23,8 +22,8 @@ pub struct DirectoryInfo {
}

impl DirectoryInfo {
pub fn global_handle_if_ready() -> Option<&'static Self> {
LAUNCHER_STATE.get().map(|x| &x.directories)
pub fn global_handle_if_ready() -> Option<Self> {
State::get_if_initialized().map(|state| state.directories.clone())
}

pub fn get_initial_settings_dir(&self) -> Option<PathBuf> {
Expand Down
57 changes: 41 additions & 16 deletions packages/app-lib/src/state/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! Theseus state management system
use crate::util::fetch::{FetchSemaphore, IoSemaphore};
use dashmap::DashMap;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, LazyLock};
use tokio::sync::watch::Sender;
use tokio::sync::{Mutex, MutexGuard, OnceCell, OwnedMutexGuard, Semaphore};

use crate::state::instances::watcher::FileWatcher;
Expand Down Expand Up @@ -67,9 +68,15 @@ mod legacy_converter;
pub mod attached_world_data;
pub mod server_join_log;

// Global state
// RwLock on state only has concurrent reads, except for config dir change which takes control of the State
enum StartupPhase {
Pending,
Ready,
Failed(crate::Error),
}

static LAUNCHER_STATE: OnceCell<Arc<State>> = OnceCell::const_new();
static STATE_STARTUP: LazyLock<Sender<StartupPhase>> =
LazyLock::new(|| Sender::new(StartupPhase::Pending));
static STATE_STARTUP_LOCK: Mutex<()> = Mutex::const_new(());
const MAX_CONCURRENT_INSTALL_JOBS: usize = 3;
pub struct State {
Expand Down Expand Up @@ -172,9 +179,17 @@ impl State {

pub async fn init(app_identifier: String) -> crate::Result<()> {
let _startup = STATE_STARTUP_LOCK.lock().await;
let state = LAUNCHER_STATE
.get_or_try_init(move || Self::initialize_state(app_identifier))
.await?;
let result = LAUNCHER_STATE
.get_or_try_init(|| async {
STATE_STARTUP.send_replace(StartupPhase::Pending);
Self::initialize_state(app_identifier).await
})
.await;
STATE_STARTUP.send_replace(match &result {
Ok(_) => StartupPhase::Ready,
Err(error) => StartupPhase::Failed(error.clone()),
});
let state = result?;

if state
.startup_complete
Expand Down Expand Up @@ -297,18 +312,28 @@ impl State {

/// Get the current launcher state, waiting for initialization.
pub async fn get() -> crate::Result<Arc<Self>> {
if !LAUNCHER_STATE.initialized() {
tracing::error!(
"Attempted to get state before it is initialized - this should never happen!"
);
while !LAUNCHER_STATE.initialized() {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}
if let Some(state) = LAUNCHER_STATE.get() {
return Ok(Arc::clone(state));
}

Ok(Arc::clone(
LAUNCHER_STATE.get().expect("State is not initialized!"),
))
let mut phases = STATE_STARTUP.subscribe();
let phase = phases
.wait_for(|phase| !matches!(phase, StartupPhase::Pending))
.await
.expect("the startup sender is static and never dropped");
match &*phase {
StartupPhase::Ready => Ok(Arc::clone(
LAUNCHER_STATE
.get()
.expect("state is set before startup is marked ready"),
)),
StartupPhase::Failed(error) => Err(error.clone()),
StartupPhase::Pending => {
unreachable!(
"wait_for returns only once the phase has left Pending"
)
}
}
}

pub fn initialized() -> bool {
Expand Down
Loading