fix(dtmm): Fix change detection for mod info

This commit is contained in:
Lucas Schwiderski 2023-03-06 15:24:14 +01:00
parent 3252e66a3f
commit ba9c190a96
Signed by: lucas
GPG key ID: AA12679AAA6DF4D8
8 changed files with 47 additions and 41 deletions

View file

@ -6,6 +6,7 @@
- dtmt: split `build` into `build` and `package`
- dtmt: implement deploying built bundles
- dtmm: indicate when a deployment is necessary
== 2023-03-01

View file

@ -1,6 +1,7 @@
use std::collections::HashMap;
use std::io::{Cursor, ErrorKind, Read};
use std::path::Path;
use std::sync::Arc;
use color_eyre::eyre::{self, Context};
use color_eyre::{Help, Result};
@ -107,7 +108,7 @@ pub(crate) async fn import_mod(state: State, info: FileInfo) -> Result<ModInfo>
let packages = files
.into_iter()
.map(|(name, files)| PackageInfo::new(name, files.into_iter().collect()))
.map(|(name, files)| Arc::new(PackageInfo::new(name, files.into_iter().collect())))
.collect();
let info = ModInfo::new(mod_cfg, packages);
@ -171,14 +172,14 @@ async fn read_mod_dir_entry(res: Result<DirEntry>) -> Result<ModInfo> {
let packages = files
.into_iter()
.map(|(name, files)| PackageInfo::new(name, files.into_iter().collect()))
.map(|(name, files)| Arc::new(PackageInfo::new(name, files.into_iter().collect())))
.collect();
let info = ModInfo::new(cfg, packages);
Ok(info)
}
#[tracing::instrument(skip(mod_order))]
pub(crate) fn load_mods<'a, P, S>(mod_dir: P, mod_order: S) -> Result<Vector<ModInfo>>
pub(crate) fn load_mods<'a, P, S>(mod_dir: P, mod_order: S) -> Result<Vector<Arc<ModInfo>>>
where
S: Iterator<Item = &'a LoadOrderEntry>,
P: AsRef<Path> + std::fmt::Debug,
@ -214,7 +215,7 @@ where
.filter_map(|entry| {
if let Some(mut info) = mods.remove(&entry.id) {
info.enabled = entry.enabled;
Some(info)
Some(Arc::new(info))
} else {
None
}

View file

@ -41,7 +41,7 @@ async fn handle_action(
.await
.submit_command(
ACTION_FINISH_ADD_MOD,
SingleUse::new(mod_info),
SingleUse::new(Arc::new(mod_info)),
Target::Auto,
)
.expect("failed to send command");

View file

@ -17,7 +17,7 @@ impl Default for View {
}
}
#[derive(Clone, Data, Debug)]
#[derive(Clone, Data, Debug, PartialEq)]
pub struct PackageInfo {
pub name: String,
pub files: Vector<String>,
@ -29,14 +29,14 @@ impl PackageInfo {
}
}
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct ModResourceInfo {
pub init: PathBuf,
pub data: Option<PathBuf>,
pub localization: Option<PathBuf>,
}
#[derive(Clone, Data, Debug, Lens)]
#[derive(Clone, Data, Debug, Lens, PartialEq)]
pub(crate) struct ModInfo {
pub id: String,
pub name: String,
@ -44,14 +44,14 @@ pub(crate) struct ModInfo {
pub enabled: bool,
#[lens(ignore)]
#[data(ignore)]
pub packages: Vector<PackageInfo>,
pub packages: Vector<Arc<PackageInfo>>,
#[lens(ignore)]
#[data(ignore)]
pub resources: ModResourceInfo,
}
impl ModInfo {
pub fn new(cfg: ModConfig, packages: Vector<PackageInfo>) -> Self {
pub fn new(cfg: ModConfig, packages: Vector<Arc<PackageInfo>>) -> Self {
Self {
id: cfg.id,
name: cfg.name,
@ -67,16 +67,10 @@ impl ModInfo {
}
}
impl PartialEq for ModInfo {
fn eq(&self, other: &Self) -> bool {
self.name.eq(&other.name)
}
}
#[derive(Clone, Data, Lens)]
pub(crate) struct State {
pub current_view: View,
pub mods: Vector<ModInfo>,
pub mods: Vector<Arc<ModInfo>>,
pub selected_mod_index: Option<usize>,
pub dirty: bool,
pub is_deployment_in_progress: bool,
@ -123,8 +117,8 @@ impl State {
self.selected_mod_index = Some(index);
}
pub fn add_mod(&mut self, info: ModInfo) {
if let Some(pos) = self.mods.index_of(&info) {
pub fn add_mod(&mut self, info: Arc<ModInfo>) {
if let Some(pos) = self.mods.iter().position(|i| i.id == info.id) {
self.mods.set(pos, info);
self.selected_mod_index = Some(pos);
} else {

View file

@ -1,3 +1,5 @@
use std::sync::Arc;
use druid::{
AppDelegate, Command, DelegateCtx, Env, FileInfo, Handled, Selector, SingleUse, Target,
};
@ -9,9 +11,9 @@ pub(crate) const ACTION_SELECT_MOD: Selector<usize> = Selector::new("dtmm.action
pub(crate) const ACTION_SELECTED_MOD_UP: Selector = Selector::new("dtmm.action.selected-mod-up");
pub(crate) const ACTION_SELECTED_MOD_DOWN: Selector =
Selector::new("dtmm.action.selected-mod-down");
pub(crate) const ACTION_START_DELETE_SELECTED_MOD: Selector<SingleUse<ModInfo>> =
pub(crate) const ACTION_START_DELETE_SELECTED_MOD: Selector<SingleUse<Arc<ModInfo>>> =
Selector::new("dtmm.action.srart-delete-selected-mod");
pub(crate) const ACTION_FINISH_DELETE_SELECTED_MOD: Selector<SingleUse<ModInfo>> =
pub(crate) const ACTION_FINISH_DELETE_SELECTED_MOD: Selector<SingleUse<Arc<ModInfo>>> =
Selector::new("dtmm.action.finish-delete-selected-mod");
pub(crate) const ACTION_START_DEPLOY: Selector = Selector::new("dtmm.action.start-deploy");
@ -23,7 +25,7 @@ pub(crate) const ACTION_FINISH_RESET_DEPLOYMENT: Selector =
Selector::new("dtmm.action.finish-reset-deployment");
pub(crate) const ACTION_ADD_MOD: Selector<FileInfo> = Selector::new("dtmm.action.add-mod");
pub(crate) const ACTION_FINISH_ADD_MOD: Selector<SingleUse<ModInfo>> =
pub(crate) const ACTION_FINISH_ADD_MOD: Selector<SingleUse<Arc<ModInfo>>> =
Selector::new("dtmm.action.finish-add-mod");
pub(crate) const ACTION_LOG: Selector<SingleUse<String>> = Selector::new("dtmm.action.log");
@ -39,7 +41,7 @@ pub(crate) enum AsyncAction {
DeployMods(State),
ResetDeployment(State),
AddMod((State, FileInfo)),
DeleteMod((State, ModInfo)),
DeleteMod((State, Arc<ModInfo>)),
SaveSettings(State),
}
@ -168,7 +170,6 @@ impl AppDelegate<State> for Delegate {
};
state.mods.remove(index);
// ctx.submit_command(ACTION_START_SAVE_SETTINGS);
Handled::Yes
}
@ -191,7 +192,6 @@ impl AppDelegate<State> for Delegate {
.expect("command type matched but didn't contain the expected value");
if let Some(info) = info.take() {
state.add_mod(info);
// ctx.submit_command(ACTION_START_SAVE_SETTINGS);
}
Handled::Yes
}

View file

@ -1,3 +1,5 @@
use std::sync::Arc;
use druid::im::Vector;
use druid::{Data, Lens};
@ -5,9 +7,9 @@ use super::{ModInfo, State};
pub(crate) struct SelectedModLens;
impl Lens<State, Option<ModInfo>> for SelectedModLens {
impl Lens<State, Option<Arc<ModInfo>>> for SelectedModLens {
#[tracing::instrument(name = "SelectedModLens::with", skip_all)]
fn with<V, F: FnOnce(&Option<ModInfo>) -> V>(&self, data: &State, f: F) -> V {
fn with<V, F: FnOnce(&Option<Arc<ModInfo>>) -> V>(&self, data: &State, f: F) -> V {
let info = data
.selected_mod_index
.and_then(|i| data.mods.get(i).cloned());
@ -16,16 +18,16 @@ impl Lens<State, Option<ModInfo>> for SelectedModLens {
}
#[tracing::instrument(name = "SelectedModLens::with_mut", skip_all)]
fn with_mut<V, F: FnOnce(&mut Option<ModInfo>) -> V>(&self, data: &mut State, f: F) -> V {
fn with_mut<V, F: FnOnce(&mut Option<Arc<ModInfo>>) -> V>(&self, data: &mut State, f: F) -> V {
match data.selected_mod_index {
Some(i) => {
let mut info = data.mods.get_mut(i).cloned();
let ret = f(&mut info);
if let Some(info) = info {
if let Some(new) = info {
// TODO: Figure out a way to check for equality and
// only update when needed
data.mods.set(i, info);
data.mods.set(i, new);
} else {
data.selected_mod_index = None;
}

View file

@ -1,3 +1,5 @@
use std::sync::Arc;
use druid::im::Vector;
use druid::lens;
use druid::widget::{
@ -82,9 +84,11 @@ fn build_top_bar() -> impl Widget<State> {
fn build_mod_list() -> impl Widget<State> {
let list = List::new(|| {
let checkbox =
Checkbox::new("").lens(lens!((usize, ModInfo, bool), 1).then(ModInfo::enabled));
let name = Label::raw().lens(lens!((usize, ModInfo, bool), 1).then(ModInfo::name));
let checkbox = Checkbox::new("")
.lens(lens!((usize, Arc<ModInfo>, bool), 1).then(ModInfo::enabled.in_arc()));
let name =
Label::raw().lens(lens!((usize, Arc<ModInfo>, bool), 1).then(ModInfo::name.in_arc()));
Flex::row()
.must_fill_main_axis(true)
@ -114,8 +118,10 @@ fn build_mod_list() -> impl Widget<State> {
.collect::<Vector<_>>()
},
|state, infos| {
infos.into_iter().for_each(|(i, info, _)| {
state.mods.set(i, info);
infos.into_iter().for_each(|(i, new, _)| {
if state.mods.get(i).cloned() != Some(new.clone()) {
state.mods.set(i, new);
}
});
},
));
@ -147,12 +153,12 @@ fn build_mod_details_buttons() -> impl Widget<State> {
.on_click(|_ctx, enabled: &mut bool, _env| {
*enabled = !(*enabled);
})
.lens(ModInfo::enabled)
.lens(ModInfo::enabled.in_arc())
},
// TODO: Gray out
|| Button::new("Enable Mod"),
)
.disabled_if(|info: &Option<ModInfo>, _env: &druid::Env| info.is_none())
.disabled_if(|info: &Option<Arc<ModInfo>>, _env: &druid::Env| info.is_none())
.lens(State::selected_mod);
let button_add_mod = Button::new("Add Mod").on_click(|ctx, _state: &mut State, _env| {
@ -167,14 +173,14 @@ fn build_mod_details_buttons() -> impl Widget<State> {
});
let button_delete_mod = Button::new("Delete Mod")
.on_click(|ctx, data: &mut Option<ModInfo>, _env| {
.on_click(|ctx, data: &mut Option<Arc<ModInfo>>, _env| {
if let Some(info) = data {
ctx.submit_command(
ACTION_START_DELETE_SELECTED_MOD.with(SingleUse::new(info.clone())),
);
}
})
.disabled_if(|info: &Option<ModInfo>, _env: &druid::Env| info.is_none())
.disabled_if(|info: &Option<Arc<ModInfo>>, _env: &druid::Env| info.is_none())
.lens(State::selected_mod);
Flex::column()
@ -208,10 +214,10 @@ fn build_mod_details_info() -> impl Widget<State> {
// Force the label to take up the entire details' pane width,
// so that we can center-align it.
.expand_width()
.lens(ModInfo::name);
.lens(ModInfo::name.in_arc());
let description = Label::raw()
.with_line_break_mode(LineBreaking::WordWrap)
.lens(ModInfo::description);
.lens(ModInfo::description.in_arc());
Flex::column()
.cross_axis_alignment(CrossAxisAlignment::Start)

View file

@ -1,5 +1,6 @@
use std::io::ErrorKind;
use std::path::PathBuf;
use std::sync::Arc;
use std::{fs, path::Path};
use clap::{parser::ValueSource, ArgMatches};
@ -38,6 +39,7 @@ impl<'a> From<&'a State> for ConfigSerialize<'a> {
mod_order: state
.mods
.iter()
.map(Arc::as_ref)
.map(LoadOrderEntrySerialize::from)
.collect(),
}