diff --git a/.gitignore b/.gitignore
index 2f4d822..8e8efd6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,4 +23,7 @@ dist-ssr
*.sln
*.sw?
.nuxt
-.output
\ No newline at end of file
+.output
+
+src-tauri/flamegraph.svg
+src-tuair/perf*
\ No newline at end of file
diff --git a/DEBUG.md b/DEBUG.md
new file mode 100644
index 0000000..2a4478f
--- /dev/null
+++ b/DEBUG.md
@@ -0,0 +1,15 @@
+# How to create Flamegraph
+
+Run this in `src-tauri`:
+```
+WEBKIT_DISABLE_DMABUF_RENDERER=1 CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph --release
+```
+
+You can leave out `WEBKIT_DISABLE_DMABUF_RENDERER=1` if you're not on NVIDIA/Linux
+
+And then run this in the root dir:
+```
+yarn dev --port 1432
+```
+
+And then do what you want, and it'll create the flamegraph for you
diff --git a/app.vue b/app.vue
index 6ae77a5..a5c6584 100644
--- a/app.vue
+++ b/app.vue
@@ -5,6 +5,8 @@
diff --git a/src-tauri/src/downloads/download_agent.rs b/src-tauri/src/downloads/download_agent.rs
index 2b3c26a..0b429d1 100644
--- a/src-tauri/src/downloads/download_agent.rs
+++ b/src-tauri/src/downloads/download_agent.rs
@@ -1,6 +1,7 @@
use crate::auth::generate_authorization_header;
use crate::db::DatabaseImpls;
use crate::downloads::manifest::{DropDownloadContext, DropManifest};
+use crate::downloads::progress_object::ProgressHandle;
use crate::remote::RemoteAccessError;
use crate::DB;
use log::{debug, error, info};
@@ -28,7 +29,7 @@ pub struct GameDownloadAgent {
pub target_download_dir: usize,
contexts: Mutex>,
pub manifest: Mutex>,
- pub progress: ProgressObject,
+ pub progress: Arc,
sender: Sender,
}
@@ -76,7 +77,7 @@ impl GameDownloadAgent {
manifest: Mutex::new(None),
target_download_dir,
contexts: Mutex::new(Vec::new()),
- progress: ProgressObject::new(0, 0),
+ progress: Arc::new(ProgressObject::new(0, 0, sender.clone())),
sender,
}
}
@@ -234,7 +235,7 @@ impl GameDownloadAgent {
pub fn run(&self) -> Result<(), ()> {
info!("downloading game: {}", self.id);
- const DOWNLOAD_MAX_THREADS: usize = 4;
+ const DOWNLOAD_MAX_THREADS: usize = 1;
let pool = ThreadPoolBuilder::new()
.num_threads(DOWNLOAD_MAX_THREADS)
@@ -251,10 +252,11 @@ impl GameDownloadAgent {
let context = context.clone();
let control_flag = self.control_flag.clone(); // Clone arcs
let progress = self.progress.get(index); // Clone arcs
+ let progress_handle = ProgressHandle::new(progress, self.progress.clone());
let completed_indexes_ref = completed_indexes_loop_arc.clone();
scope.spawn(move |_| {
- match download_game_chunk(context.clone(), control_flag, progress) {
+ match download_game_chunk(context.clone(), control_flag, progress_handle) {
Ok(res) => match res {
true => {
let mut lock = completed_indexes_ref.lock().unwrap();
diff --git a/src-tauri/src/downloads/download_logic.rs b/src-tauri/src/downloads/download_logic.rs
index a290970..ff0bba6 100644
--- a/src-tauri/src/downloads/download_logic.rs
+++ b/src-tauri/src/downloads/download_logic.rs
@@ -19,6 +19,7 @@ use urlencoding::encode;
use super::download_agent::GameDownloadError;
use super::download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag};
+use super::progress_object::{ProgressHandle, ProgressObject};
pub struct DropWriter {
hasher: Context,
@@ -65,7 +66,7 @@ pub struct DropDownloadPipeline {
pub source: R,
pub destination: DropWriter,
pub control_flag: DownloadThreadControl,
- pub progress: Arc,
+ pub progress: ProgressHandle,
pub size: usize,
}
impl DropDownloadPipeline {
@@ -73,7 +74,7 @@ impl DropDownloadPipeline {
source: Response,
destination: DropWriter,
control_flag: DownloadThreadControl,
- progress: Arc,
+ progress: ProgressHandle,
size: usize,
) -> Self {
Self {
@@ -100,8 +101,7 @@ impl DropDownloadPipeline {
current_size += bytes_read;
buf_writer.write_all(©_buf[0..bytes_read])?;
- self.progress
- .fetch_add(bytes_read, std::sync::atomic::Ordering::Relaxed);
+ self.progress.add(bytes_read);
if current_size == self.size {
break;
@@ -120,11 +120,11 @@ impl DropDownloadPipeline {
pub fn download_game_chunk(
ctx: DropDownloadContext,
control_flag: DownloadThreadControl,
- progress: Arc,
+ progress: ProgressHandle,
) -> Result {
// If we're paused
if control_flag.get() == DownloadThreadControlFlag::Stop {
- progress.store(0, Ordering::Relaxed);
+ progress.set(0);
return Ok(false);
}
diff --git a/src-tauri/src/downloads/download_manager.rs b/src-tauri/src/downloads/download_manager.rs
index f57c87c..e70d53d 100644
--- a/src-tauri/src/downloads/download_manager.rs
+++ b/src-tauri/src/downloads/download_manager.rs
@@ -9,9 +9,11 @@ use std::{
};
use log::info;
+use serde::Serialize;
use super::{
download_agent::{GameDownloadAgent, GameDownloadError},
+ download_manager_builder::CurrentProgressObject,
progress_object::ProgressObject,
queue::Queue,
};
@@ -32,6 +34,8 @@ pub enum DownloadManagerSignal {
Cancel(String),
/// Any error which occurs in the agent
Error(GameDownloadError),
+ /// Pushes UI update
+ Update,
}
pub enum DownloadManagerStatus {
Downloading,
@@ -39,10 +43,12 @@ pub enum DownloadManagerStatus {
Empty,
Error(GameDownloadError),
}
+
+#[derive(Serialize, Clone)]
pub enum GameDownloadStatus {
+ Queued,
Downloading,
Paused,
- Uninitialised,
Error,
}
@@ -59,18 +65,20 @@ pub enum GameDownloadStatus {
pub struct DownloadManager {
terminator: JoinHandle>,
download_queue: Queue,
- progress: Arc>>,
+ progress: CurrentProgressObject,
command_sender: Sender,
}
-pub struct AgentInterfaceData {
+pub struct GameDownloadAgentQueueStandin {
pub id: String,
pub status: Mutex,
+ pub progress: Arc,
}
-impl From> for AgentInterfaceData {
+impl From> for GameDownloadAgentQueueStandin {
fn from(value: Arc) -> Self {
Self {
id: value.id.clone(),
- status: Mutex::from(GameDownloadStatus::Uninitialised),
+ status: Mutex::from(GameDownloadStatus::Queued),
+ progress: value.progress.clone(),
}
}
}
@@ -79,7 +87,7 @@ impl DownloadManager {
pub fn new(
terminator: JoinHandle>,
download_queue: Queue,
- progress: Arc>>,
+ progress: CurrentProgressObject,
command_sender: Sender,
) -> Self {
Self {
@@ -109,10 +117,10 @@ impl DownloadManager {
.send(DownloadManagerSignal::Cancel(game_id))
.unwrap();
}
- pub fn edit(&self) -> MutexGuard<'_, VecDeque>> {
+ pub fn edit(&self) -> MutexGuard<'_, VecDeque>> {
self.download_queue.edit()
}
- pub fn read_queue(&self) -> VecDeque> {
+ pub fn read_queue(&self) -> VecDeque> {
self.download_queue.read()
}
pub fn get_current_game_download_progress(&self) -> Option {
@@ -157,7 +165,7 @@ impl DownloadManager {
/// Takes in the locked value from .edit() and attempts to
/// get the index of whatever game_id is passed in
fn get_index_from_id(
- queue: &mut MutexGuard<'_, VecDeque>>,
+ queue: &mut MutexGuard<'_, VecDeque>>,
id: String,
) -> Option {
queue
diff --git a/src-tauri/src/downloads/download_manager_builder.rs b/src-tauri/src/downloads/download_manager_builder.rs
index a1578a5..4809707 100644
--- a/src-tauri/src/downloads/download_manager_builder.rs
+++ b/src-tauri/src/downloads/download_manager_builder.rs
@@ -7,21 +7,20 @@ use std::{
thread::spawn,
};
-use log::{error, info, warn};
-use rustbreak::Database;
+use log::{error, info};
use tauri::{AppHandle, Emitter};
use crate::{
db::DatabaseGameStatus,
- library::{on_game_complete, GameUpdateEvent},
+ library::{on_game_complete, GameUpdateEvent, QueueUpdateEvent, QueueUpdateEventQueueData},
DB,
};
use super::{
download_agent::{GameDownloadAgent, GameDownloadError},
download_manager::{
- AgentInterfaceData, DownloadManager, DownloadManagerSignal, DownloadManagerStatus,
- GameDownloadStatus,
+ DownloadManager, DownloadManagerSignal, DownloadManagerStatus,
+ GameDownloadAgentQueueStandin, GameDownloadStatus,
},
download_thread_control_flag::{DownloadThreadControl, DownloadThreadControlFlag},
progress_object::ProgressObject,
@@ -65,16 +64,19 @@ Behold, my madness - quexeky
*/
+// Refactored to consolidate this type. It's a monster.
+pub type CurrentProgressObject = Arc>>>;
+
pub struct DownloadManagerBuilder {
download_agent_registry: HashMap>,
download_queue: Queue,
command_receiver: Receiver,
sender: Sender,
- progress: Arc>>,
+ progress: CurrentProgressObject,
status: Arc>,
app_handle: AppHandle,
- current_game_interface: Option>, // Should be the only game download agent in the map with the "Go" flag
+ current_game_interface: Option>, // Should be the only game download agent in the map with the "Go" flag
active_control_flag: Option,
}
@@ -121,6 +123,21 @@ impl DownloadManagerBuilder {
.unwrap();
}
+ fn push_manager_update(&self) {
+ let queue = self.download_queue.read();
+ let queue_objs: Vec = queue
+ .iter()
+ .map(|interface| QueueUpdateEventQueueData {
+ id: interface.id.clone(),
+ status: interface.status.lock().unwrap().clone(),
+ progress: interface.progress.get_progress(),
+ })
+ .collect();
+
+ let event_data = QueueUpdateEvent { queue: queue_objs };
+ self.app_handle.emit("update_queue", event_data).unwrap();
+ }
+
fn manage_queue(mut self) -> Result<(), ()> {
loop {
let signal = match self.command_receiver.recv() {
@@ -153,6 +170,9 @@ impl DownloadManagerBuilder {
DownloadManagerSignal::Cancel(id) => {
self.manage_cancel_signal(id);
}
+ DownloadManagerSignal::Update => {
+ self.push_manager_update();
+ }
};
}
}
@@ -175,9 +195,18 @@ impl DownloadManagerBuilder {
self.active_control_flag = None;
*self.progress.lock().unwrap() = None;
- on_game_complete(game_id, download_agent.version.clone(), &self.app_handle);
+ if let Err(error) =
+ on_game_complete(game_id, download_agent.version.clone(), &self.app_handle)
+ {
+ self.sender
+ .send(DownloadManagerSignal::Error(
+ GameDownloadError::Communication(error),
+ ))
+ .unwrap();
+ }
}
}
+ self.sender.send(DownloadManagerSignal::Update).unwrap();
self.sender.send(DownloadManagerSignal::Go).unwrap();
}
@@ -189,10 +218,11 @@ impl DownloadManagerBuilder {
target_download_dir,
self.sender.clone(),
));
- let agent_status = GameDownloadStatus::Uninitialised;
- let interface_data = AgentInterfaceData {
+ let agent_status = GameDownloadStatus::Queued;
+ let interface_data = GameDownloadAgentQueueStandin {
id: id.clone(),
status: Mutex::new(agent_status),
+ progress: download_agent.progress.clone(),
};
let version_name = download_agent.version.clone();
self.download_agent_registry
@@ -200,6 +230,7 @@ impl DownloadManagerBuilder {
self.download_queue.append(interface_data);
self.set_game_status(id, DatabaseGameStatus::Queued { version_name });
+ self.sender.send(DownloadManagerSignal::Update).unwrap();
}
fn manage_go_signal(&mut self) {
@@ -217,6 +248,8 @@ impl DownloadManagerBuilder {
.unwrap()
.clone();
self.current_game_interface = Some(agent_data);
+ // Cloning option should be okay because it only clones the Arc inside, not the AgentInterfaceData
+ let agent_data = self.current_game_interface.clone().unwrap();
let version_name = download_agent.version.clone();
@@ -243,6 +276,11 @@ impl DownloadManagerBuilder {
};
});
+ // Set status for game
+ let mut status_handle = agent_data.status.lock().unwrap();
+ *status_handle = GameDownloadStatus::Downloading;
+
+ // Set flags for download manager
active_control_flag.set(DownloadThreadControlFlag::Go);
self.set_status(DownloadManagerStatus::Downloading);
self.set_game_status(
@@ -260,6 +298,8 @@ impl DownloadManagerBuilder {
self.current_game_interface.as_ref().unwrap().id.clone(),
DatabaseGameStatus::Remote {},
);
+
+ self.sender.send(DownloadManagerSignal::Update).unwrap();
}
fn manage_cancel_signal(&mut self, game_id: String) {
if let Some(current_flag) = &self.active_control_flag {
diff --git a/src-tauri/src/downloads/progress_object.rs b/src-tauri/src/downloads/progress_object.rs
index 9114003..13b6592 100644
--- a/src-tauri/src/downloads/progress_object.rs
+++ b/src-tauri/src/downloads/progress_object.rs
@@ -1,27 +1,84 @@
use std::{
sync::{
atomic::{AtomicUsize, Ordering},
+ mpsc::Sender,
Arc, Mutex,
},
time::Instant,
};
+use log::info;
+
+use super::download_manager::DownloadManagerSignal;
+
#[derive(Clone)]
pub struct ProgressObject {
max: Arc>,
progress_instances: Arc>>>,
start: Arc>,
+ sender: Sender,
+
+ points_towards_update: Arc,
+ points_to_push_update: Arc>,
}
+pub struct ProgressHandle {
+ progress: Arc,
+ progress_object: Arc,
+}
+
+impl ProgressHandle {
+ pub fn new(progress: Arc, progress_object: Arc) -> Self {
+ Self {
+ progress,
+ progress_object,
+ }
+ }
+ pub fn set(&self, amount: usize) {
+ self.progress.store(amount, Ordering::Relaxed);
+ }
+ pub fn add(&self, amount: usize) {
+ self.progress
+ .fetch_add(amount, std::sync::atomic::Ordering::Relaxed);
+ self.progress_object.check_push_update(amount);
+ }
+}
+
+static PROGRESS_UPDATES: usize = 100;
+
impl ProgressObject {
- pub fn new(max: usize, length: usize) -> Self {
+ pub fn new(max: usize, length: usize, sender: Sender) -> Self {
let arr = Mutex::new((0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect());
+ // TODO: consolidate this calculate with the set_max function below
+ let points_to_push_update = max / PROGRESS_UPDATES;
Self {
max: Arc::new(Mutex::new(max)),
progress_instances: Arc::new(arr),
start: Arc::new(Mutex::new(Instant::now())),
+ sender,
+
+ points_towards_update: Arc::new(AtomicUsize::new(0)),
+ points_to_push_update: Arc::new(Mutex::new(points_to_push_update)),
}
}
+
+ pub fn check_push_update(&self, amount_added: usize) {
+ let current_amount = self
+ .points_towards_update
+ .fetch_add(amount_added, Ordering::Relaxed);
+
+ let to_update_handle = self.points_to_push_update.lock().unwrap();
+ let to_update = to_update_handle.clone();
+ drop(to_update_handle);
+
+ if current_amount < to_update {
+ return;
+ }
+ self.points_towards_update
+ .fetch_sub(to_update, Ordering::Relaxed);
+ self.sender.send(DownloadManagerSignal::Update).unwrap();
+ }
+
pub fn set_time_now(&self) {
*self.start.lock().unwrap() = Instant::now();
}
@@ -37,13 +94,14 @@ impl ProgressObject {
*self.max.lock().unwrap()
}
pub fn set_max(&self, new_max: usize) {
- *self.max.lock().unwrap() = new_max
+ *self.max.lock().unwrap() = new_max;
+ *self.points_to_push_update.lock().unwrap() = new_max / PROGRESS_UPDATES;
+ info!("points to push update: {}", new_max / PROGRESS_UPDATES);
}
pub fn set_size(&self, length: usize) {
*self.progress_instances.lock().unwrap() =
(0..length).map(|_| Arc::new(AtomicUsize::new(0))).collect();
}
-
pub fn get_progress(&self) -> f64 {
self.sum() as f64 / self.get_max() as f64
}
diff --git a/src-tauri/src/downloads/queue.rs b/src-tauri/src/downloads/queue.rs
index 80e3dc0..f4139ac 100644
--- a/src-tauri/src/downloads/queue.rs
+++ b/src-tauri/src/downloads/queue.rs
@@ -3,11 +3,11 @@ use std::{
sync::{Arc, Mutex, MutexGuard},
};
-use super::download_manager::AgentInterfaceData;
+use super::download_manager::GameDownloadAgentQueueStandin;
#[derive(Clone)]
pub struct Queue {
- inner: Arc>>>,
+ inner: Arc>>>,
}
impl Queue {
@@ -16,13 +16,13 @@ impl Queue {
inner: Arc::new(Mutex::new(VecDeque::new())),
}
}
- pub fn read(&self) -> VecDeque> {
+ pub fn read(&self) -> VecDeque> {
self.inner.lock().unwrap().clone()
}
- pub fn edit(&self) -> MutexGuard<'_, VecDeque>> {
+ pub fn edit(&self) -> MutexGuard<'_, VecDeque>> {
self.inner.lock().unwrap()
}
- pub fn pop_front(&self) -> Option> {
+ pub fn pop_front(&self) -> Option> {
self.edit().pop_front()
}
pub fn empty(&self) -> bool {
@@ -30,17 +30,17 @@ impl Queue {
}
/// Either inserts `interface` at the specified index, or appends to
/// the back of the deque if index is greater than the length of the deque
- pub fn insert(&self, interface: AgentInterfaceData, index: usize) {
+ pub fn insert(&self, interface: GameDownloadAgentQueueStandin, index: usize) {
if self.read().len() > index {
self.append(interface);
} else {
self.edit().insert(index, Arc::new(interface));
}
}
- pub fn append(&self, interface: AgentInterfaceData) {
+ pub fn append(&self, interface: GameDownloadAgentQueueStandin) {
self.edit().push_back(Arc::new(interface));
}
- pub fn pop_front_if_equal(&self, game_id: String) -> Option> {
+ pub fn pop_front_if_equal(&self, game_id: String) -> Option> {
let mut queue = self.edit();
let front = match queue.front() {
Some(front) => front,
diff --git a/src-tauri/src/library.rs b/src-tauri/src/library.rs
index 041cf2e..9b38c5d 100644
--- a/src-tauri/src/library.rs
+++ b/src-tauri/src/library.rs
@@ -1,4 +1,4 @@
-use std::collections::HashMap;
+use std::collections::{HashMap, VecDeque};
use std::fmt::format;
use std::sync::Mutex;
@@ -42,6 +42,18 @@ pub struct GameUpdateEvent {
pub status: DatabaseGameStatus,
}
+#[derive(Serialize, Clone)]
+pub struct QueueUpdateEventQueueData {
+ pub id: String,
+ pub status: GameDownloadStatus,
+ pub progress: f64,
+}
+
+#[derive(serde::Serialize, Clone)]
+pub struct QueueUpdateEvent {
+ pub queue: Vec,
+}
+
// Game version with some fields missing and size information
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]