feat: 跨实例同步移植 WIP 检查点(第1轮+2a+2b+synced_servers)
Sync source to CNB / Sync Git ref (push) Has been cancelled
Sync source to CNB / Sync Git ref (push) Has been cancelled
从 Axolotl 移植跨实例同步子系统,当前进度: - 第1轮:synced_options/files.rs + mod.rs 骨架,state 层补齐 SyncedOption/InstanceSyncedOptions/CanonicalValue/GameOptionKind 等 - 2a:game_options 的 api_types/options_file/catalog(10文件) - 2b:game_options 除 resource_pack_options/local_settings_editor 外的全部(settings_editor/write_shared_settings/fullscreen/launch_overrides/instance_support/read_instance_changes/pack_updates/source_selection + locales/5文件);State 补 synced_options_lock/game_locale_indexer/lock_synced_options - synced_servers:6 文件全部搬入 - 35+20 处 sqlx::query! 宏改写为非宏 query+bind 已知未完成:synced_servers::operations 依赖 synced_options::instance_option_supported(属 orchestration,第5轮),当前编译不通。
This commit is contained in:
1 parent
5a289c3a15
commit
ed61a5942c
43 files changed
+10607
No files matched your search
@@ -68,10 +68,17 @@ impl DirectoryInfo {
|
||||
|
||||
/// Get the Minecraft instance metadata directory
|
||||
#[inline]
|
||||
|
||||
pub fn metadata_dir(&self) -> PathBuf {
|
||||
self.config_dir.join(METADATA_FOLDER_NAME)
|
||||
}
|
||||
|
||||
/// Get the directory containing launcher-wide synced option files.
|
||||
#[inline]
|
||||
pub(crate) fn synced_options_dir(&self) -> PathBuf {
|
||||
self.config_dir.join("synced-options")
|
||||
}
|
||||
|
||||
/// Get the Minecraft java versions metadata directory
|
||||
#[inline]
|
||||
pub fn java_versions_dir(&self) -> PathBuf {
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
use super::{CanonicalValue, GameOptionKind, StoredOption, StoredPreference};
|
||||
use sqlx::{Row, SqlitePool};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub(crate) async fn shared_game_options_exist(
|
||||
pool: &SqlitePool,
|
||||
) -> crate::Result<bool> {
|
||||
Ok(sqlx::query_scalar("SELECT EXISTS(SELECT 1 FROM synced_game_option_state WHERE singleton = 1)").fetch_one(pool).await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn game_options_sync_is_enabled(
|
||||
pool: &SqlitePool,
|
||||
) -> crate::Result<bool> {
|
||||
Ok(sqlx::query_scalar("SELECT globally_enabled FROM sync_feature_settings WHERE feature = 'game_options'").fetch_optional(pool).await?.unwrap_or(false))
|
||||
}
|
||||
|
||||
pub(crate) async fn load_game_options_sync_state(
|
||||
pool: &SqlitePool,
|
||||
current_catalog_revision: u32,
|
||||
) -> crate::Result<(u64, u32)> {
|
||||
let row = sqlx::query("SELECT revision, catalog_revision FROM synced_game_option_state WHERE singleton = 1").fetch_optional(pool).await?;
|
||||
Ok(row
|
||||
.map(|r| {
|
||||
(
|
||||
r.get::<i64, _>("revision").max(0) as u64,
|
||||
(r.get::<i64, _>("catalog_revision").max(1) as u32)
|
||||
.max(current_catalog_revision),
|
||||
)
|
||||
})
|
||||
.unwrap_or((0, current_catalog_revision)))
|
||||
}
|
||||
|
||||
pub(crate) async fn load_shared_game_options(
|
||||
pool: &SqlitePool,
|
||||
) -> crate::Result<HashMap<String, StoredOption>> {
|
||||
let rows = sqlx::query("SELECT option_id, kind, raw_key, canonical_value_json, seeded, revision FROM synced_game_option_values").fetch_all(pool).await?;
|
||||
let mut out = HashMap::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
let option_id: String = row.get("option_id");
|
||||
let kind: String = row.get("kind");
|
||||
let raw_key: Option<String> = row.try_get("raw_key")?;
|
||||
let json: Option<String> = row.try_get("canonical_value_json")?;
|
||||
let seeded: bool = row.get("seeded");
|
||||
let revision: i64 = row.get("revision");
|
||||
out.insert(
|
||||
option_id.clone(),
|
||||
StoredOption {
|
||||
option_id,
|
||||
kind: if kind == "external" {
|
||||
GameOptionKind::External
|
||||
} else {
|
||||
GameOptionKind::Vanilla
|
||||
},
|
||||
raw_key,
|
||||
value: json.as_deref().map(serde_json::from_str).transpose()?,
|
||||
seeded,
|
||||
revision: revision.max(0) as u64,
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub(crate) async fn load_game_option_preferences(
|
||||
pool: &SqlitePool,
|
||||
) -> crate::Result<HashMap<String, StoredPreference>> {
|
||||
Ok(sqlx::query("SELECT option_id, enabled, revision FROM synced_game_option_preferences").fetch_all(pool).await?.into_iter().map(|r| { let option_id: String = r.get("option_id"); let enabled: bool = r.get("enabled"); let revision: i64 = r.get("revision"); (option_id, StoredPreference { enabled, revision: revision.max(0) as u64 }) }).collect())
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", content = "value", rename_all = "snake_case")]
|
||||
pub enum CanonicalValue {
|
||||
Bool(bool),
|
||||
Integer(i64),
|
||||
Decimal(String),
|
||||
Enum(String),
|
||||
Text(String),
|
||||
StringList(Vec<String>),
|
||||
KeyBinding(String),
|
||||
ExternalRaw(String),
|
||||
}
|
||||
|
||||
impl CanonicalValue {
|
||||
pub(crate) fn type_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Bool(_) => "bool",
|
||||
Self::Integer(_) => "integer",
|
||||
Self::Decimal(_) => "decimal",
|
||||
Self::Enum(_) => "enum",
|
||||
Self::Text(_) => "text",
|
||||
Self::StringList(_) => "string_list",
|
||||
Self::KeyBinding(_) => "key_binding",
|
||||
Self::ExternalRaw(_) => "external_raw",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GameOptionKind {
|
||||
Vanilla,
|
||||
External,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct StoredOption {
|
||||
pub(crate) option_id: String,
|
||||
pub(crate) kind: GameOptionKind,
|
||||
pub(crate) raw_key: Option<String>,
|
||||
pub(crate) value: Option<CanonicalValue>,
|
||||
pub(crate) seeded: bool,
|
||||
pub(crate) revision: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct StoredPreference {
|
||||
pub(crate) enabled: bool,
|
||||
pub(crate) revision: u64,
|
||||
}
|
||||
@@ -109,3 +109,47 @@ pub struct InstanceSyncedOptions {
|
||||
#[serde(default)]
|
||||
pub data_packs: bool,
|
||||
}
|
||||
|
||||
|
||||
/// The set of per-instance synchronization features. Mirrors the Axolotl
|
||||
/// `SyncedOption` enum, including its string keys used by the
|
||||
/// `instance_sync_preferences` / `sync_feature_settings` tables.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SyncedOption {
|
||||
GameOptions,
|
||||
CommandHistory,
|
||||
MultiplayerServers,
|
||||
CreativeHotbars,
|
||||
Screenshots,
|
||||
ResourcePacks,
|
||||
DataPacks,
|
||||
}
|
||||
|
||||
impl SyncedOption {
|
||||
pub const ALL: [Self; 7] = [
|
||||
Self::GameOptions,
|
||||
Self::CommandHistory,
|
||||
Self::MultiplayerServers,
|
||||
Self::CreativeHotbars,
|
||||
Self::Screenshots,
|
||||
Self::ResourcePacks,
|
||||
Self::DataPacks,
|
||||
];
|
||||
|
||||
pub const fn is_available(self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::GameOptions => "game_options",
|
||||
Self::CommandHistory => "command_history",
|
||||
Self::MultiplayerServers => "multiplayer_servers",
|
||||
Self::CreativeHotbars => "creative_hotbars",
|
||||
Self::Screenshots => "screenshots",
|
||||
Self::ResourcePacks => "resource_packs",
|
||||
Self::DataPacks => "data_packs",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,3 +65,5 @@ pub use self::update_check::*;
|
||||
fn unknown_value(kind: &str, value: &str) -> crate::Error {
|
||||
crate::ErrorKind::InputError(format!("Unknown {kind} {value}")).into()
|
||||
}
|
||||
mod game_options;
|
||||
pub use self::game_options::*;
|
||||
@@ -67,6 +67,9 @@ pub use self::mr_auth::*;
|
||||
|
||||
mod legacy_converter;
|
||||
|
||||
mod game_options;
|
||||
pub use self::game_options::*;
|
||||
|
||||
pub mod attached_world_data;
|
||||
pub mod server_join_log;
|
||||
|
||||
@@ -148,6 +151,9 @@ pub struct State {
|
||||
configured_http_client_update: AsyncMutex<()>,
|
||||
|
||||
pub(crate) file_watcher: FileWatcher,
|
||||
|
||||
pub(crate) synced_options_lock: Arc<AsyncMutex<()>>,
|
||||
pub(crate) game_locale_indexer: crate::api::instance::synced_options::game_options::locales::GameLocaleIndexer,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -555,6 +561,12 @@ impl State {
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn lock_synced_options(
|
||||
&self,
|
||||
) -> tokio::sync::OwnedMutexGuard<()> {
|
||||
self.synced_options_lock.clone().lock_owned().await
|
||||
}
|
||||
|
||||
pub fn initialized() -> bool {
|
||||
LAUNCHER_STATE.initialized()
|
||||
}
|
||||
@@ -969,6 +981,13 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn instance_has_running_process(
|
||||
instance_id: &str,
|
||||
state: &State,
|
||||
) -> crate::Result<bool> {
|
||||
Ok(state.process_manager.has_instance_process(instance_id))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) async fn test_state(
|
||||
directories: DirectoryInfo,
|
||||
@@ -1016,6 +1035,8 @@ pub(crate) async fn test_state(
|
||||
configured_http_client: RwLock::new(configured_http_client),
|
||||
configured_http_client_update: AsyncMutex::new(()),
|
||||
file_watcher,
|
||||
synced_options_lock: Arc::new(AsyncMutex::new(())),
|
||||
game_locale_indexer: Default::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user