Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track playtime and display it in the launcher #336

Merged
merged 31 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0ce3bb8
added playtime tracking
NeoFoxxo Sep 23, 2023
f3ff60a
stopped thread blocking & added error handling
NeoFoxxo Oct 3, 2023
0151dbc
Merge branch 'main' into main
NeoFoxxo Oct 3, 2023
bd1630f
build(deps-dev): bump the frontend-deps group with 9 updates (#335)
dependabot[bot] Oct 9, 2023
2971865
build(deps): bump the backend-deps group in /src-tauri with 4 updates…
dependabot[bot] Oct 9, 2023
43aa480
build(deps): bump actions/checkout from 3 to 4 (#328)
dependabot[bot] Oct 9, 2023
b65624f
build(deps): bump the backend-deps group in /src-tauri with 2 updates…
dependabot[bot] Oct 10, 2023
bbbd5c7
build(deps-dev): bump the frontend-deps group with 1 update (#339)
dependabot[bot] Oct 10, 2023
ed041dd
build(deps): bump the frontend-deps-tauri group with 2 updates (#338)
dependabot[bot] Oct 10, 2023
6fd650f
jak2: prepare for jak 2 support (#341)
xTVaser Oct 10, 2023
375c073
build(deps-dev): bump the frontend-deps group with 6 updates (#354)
dependabot[bot] Oct 24, 2023
c1cf1c7
build(deps): bump the frontend-deps-tauri group with 2 updates (#349)
dependabot[bot] Oct 24, 2023
fee0567
build(deps): bump the backend-deps group in /src-tauri with 4 updates…
dependabot[bot] Oct 24, 2023
8804a12
build(deps): bump the backend-deps-tauri group in /src-tauri with 1 u…
dependabot[bot] Oct 24, 2023
832ab90
build(deps-dev): bump the frontend-deps-tauri group with 1 update (#355)
dependabot[bot] Oct 24, 2023
74b1463
frontend/game: always update beta flag and change issue URL (#356)
xTVaser Oct 25, 2023
9bb890a
backend: conditionally add `--game` flag to binaries based on version…
xTVaser Nov 2, 2023
76a3519
New Crowdin updates (#361)
xTVaser Nov 2, 2023
4d13922
release: bump to version - v2.3.0
OpenGOALBot Nov 2, 2023
638c5f2
release: update release metadata to latest
OpenGOALBot Nov 2, 2023
2796692
Merge remote-tracking branch 'origin/main' into main
xTVaser Nov 4, 2023
1722042
lint: formatting
xTVaser Nov 4, 2023
fa2b430
it works, but the game takes 30-40 seconds to startup now?
xTVaser Nov 4, 2023
94f1ad5
lint: formatting
xTVaser Nov 4, 2023
37ddaf2
Playtime is now displayed in the launcher
NeoFoxxo Nov 5, 2023
9b0eb35
lint: formatting
xTVaser Nov 5, 2023
7100ed2
backend: cleanup and improve my wip code -- spawn the process outside…
xTVaser Nov 5, 2023
e35e5f6
Merge remote-tracking branch 'origin/main' into main
xTVaser Nov 5, 2023
4d51fce
frontend: also add support for seconds
xTVaser Nov 5, 2023
40c53e6
i18n: make played for text translatable
xTVaser Nov 5, 2023
26f4054
frontend: remove seconds
xTVaser Nov 6, 2023
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
58 changes: 55 additions & 3 deletions src-tauri/src/commands/binaries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ use std::{
collections::HashMap,
path::{Path, PathBuf},
process::Command,
time::Instant,
};

use log::{info, warn};
use semver::Version;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tauri::Manager;

use crate::{
config::LauncherConfig,
util::file::{create_dir, overwrite_dir, read_last_lines_from_file},
TAURI_APP,
};

use super::CommandError;
Expand Down Expand Up @@ -731,7 +734,7 @@ pub async fn launch_game(
let config_info = common_prelude(&config_lock)?;

let exec_info = get_exec_location(&config_info, "gk")?;
let args = generate_launch_game_string(&config_info, game_name, in_debug)?;
let args = generate_launch_game_string(&config_info, game_name.clone(), in_debug)?;

log::info!(
"Launching game version {:?} -> {:?} with args: {:?}",
Expand All @@ -740,8 +743,9 @@ pub async fn launch_game(
args
);

// TODO - log rotation here would be nice too, and for it to be game specific
let log_file = create_log_file(&app_handle, "game.log", false)?;

// TODO - log rotation here would be nice too, and for it to be game specific
let mut command = Command::new(exec_info.executable_path);
command
.args(args)
Expand All @@ -752,6 +756,54 @@ pub async fn launch_game(
{
command.creation_flags(0x08000000);
}
command.spawn()?;
// Start the process here so if there is an error, we can return immediately
let mut child = command.spawn()?;
// if all goes well, we await the child to exit in the background (separate thread)
tokio::spawn(async move {
let start_time = Instant::now(); // get the start time of the game
// start waiting for the game to exit
if let Err(err) = child.wait() {
log::error!("Error occured when waiting for game to exit: {}", err);
return;
}
// once the game exits pass the time the game started to the track_playtine function
if let Err(err) = track_playtime(start_time, game_name).await {
log::error!("Error occured when tracking playtime: {}", err);
return;
}
});
Ok(())
}

async fn track_playtime(
start_time: std::time::Instant,
game_name: String,
) -> Result<(), CommandError> {
let app_handle = TAURI_APP
.get()
.ok_or_else(|| {
CommandError::BinaryExecution("Cannot access global app state to persist playtime".to_owned())
})?
.app_handle();
let config = app_handle.state::<tokio::sync::Mutex<LauncherConfig>>();
let mut config_lock = config.lock().await;

// get the playtime of the session
let elapsed_time = start_time.elapsed().as_secs();
log::info!("elapsed time: {}", elapsed_time);

config_lock
.update_game_seconds_played(&game_name, elapsed_time)
.map_err(|_| CommandError::Configuration("Unable to persist time played".to_owned()))?;

// send an event to the front end so that it can refresh the playtime on screen
if let Err(err) = app_handle.emit_all("playtimeUpdated", ()) {
log::error!("Failed to emit playtimeUpdated event: {}", err);
return Err(CommandError::BinaryExecution(format!(
"Failed to emit playtimeUpdated event: {}",
err
)));
}

Ok(())
}
15 changes: 15 additions & 0 deletions src-tauri/src/commands/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,3 +414,18 @@ pub async fn does_active_tooling_version_support_game(
_ => Ok(false),
}
}

#[tauri::command]
pub async fn get_playtime(
config: tauri::State<'_, tokio::sync::Mutex<LauncherConfig>>,
game_name: String,
) -> Result<u64, CommandError> {
let mut config_lock = config.lock().await;
match config_lock.get_game_seconds_played(&game_name) {
Ok(playtime) => Ok(playtime),
Err(err) => Err(CommandError::Configuration(format!(
"Error occurred when getting game playtime: {}",
err
))),
}
}
25 changes: 25 additions & 0 deletions src-tauri/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ pub struct GameConfig {
pub version: Option<String>,
pub version_folder: Option<String>,
pub features: Option<GameFeatureConfig>,
pub seconds_played: Option<u64>,
}

impl GameConfig {
Expand All @@ -125,6 +126,7 @@ impl GameConfig {
version: None,
version_folder: None,
features: Some(GameFeatureConfig::default()),
seconds_played: Some(0),
}
}
}
Expand Down Expand Up @@ -558,4 +560,27 @@ impl LauncherConfig {
self.save_config()?;
Ok(())
}

pub fn update_game_seconds_played(
&mut self,
game_name: &String,
additional_seconds: u64,
) -> Result<(), ConfigError> {
let game_config = self.get_supported_game_config_mut(game_name)?;
match game_config.seconds_played {
Some(seconds) => {
game_config.seconds_played = Some(seconds + additional_seconds);
}
None => {
game_config.seconds_played = Some(additional_seconds);
}
}
self.save_config()?;
Ok(())
}

pub fn get_game_seconds_played(&mut self, game_name: &String) -> Result<u64, ConfigError> {
let game_config = self.get_supported_game_config_mut(&game_name)?;
Ok(game_config.seconds_played.unwrap_or(0))
}
}
6 changes: 6 additions & 0 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use directories::UserDirs;
use fern::colors::{Color, ColoredLevelConfig};
use tauri::{Manager, RunEvent};
use tokio::sync::OnceCell;
use util::file::create_dir;

use backtrace::Backtrace;
Expand Down Expand Up @@ -44,13 +45,17 @@ fn panic_hook(info: &std::panic::PanicInfo) {
log_crash(Some(info), None);
}

static TAURI_APP: OnceCell<tauri::AppHandle> = OnceCell::const_new();

fn main() {
// In the event that some catastrophic happens, atleast log it out
// the panic_hook will log to a file in the folder of the executable
std::panic::set_hook(Box::new(panic_hook));

let tauri_setup = tauri::Builder::default()
.setup(|app| {
TAURI_APP.set(app.app_handle());

// Setup Logging
let log_path = app
.path_resolver()
Expand Down Expand Up @@ -141,6 +146,7 @@ fn main() {
commands::config::cleanup_enabled_texture_packs,
commands::config::delete_old_data_directory,
commands::config::does_active_tooling_version_support_game,
commands::config::get_playtime,
commands::config::finalize_installation,
commands::config::get_active_tooling_version_folder,
commands::config::get_active_tooling_version,
Expand Down
58 changes: 57 additions & 1 deletion src/components/games/GameControls.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
import { resetGameSettings, uninstallGame } from "$lib/rpc/game";
import { platform } from "@tauri-apps/api/os";
import { getLaunchGameString, launchGame, openREPL } from "$lib/rpc/binaries";
import { getPlaytime } from "$lib/rpc/config";
import { _ } from "svelte-i18n";
import { navigate } from "svelte-navigator";
import { listen } from "@tauri-apps/api/event";
import { toastStore } from "$lib/stores/ToastStore";

export let activeGame: SupportedGame;
Expand All @@ -26,6 +28,7 @@
let settingsDir = undefined;
let savesDir = undefined;
let isLinux = false;
let playtime = "";

onMount(async () => {
isLinux = (await platform()) === "linux";
Expand All @@ -42,15 +45,68 @@
"saves",
);
});

// format the time from the settings file which is stored as seconds
function formatPlaytime(playtimeRaw: number) {
// calculate the number of hours and minutes
const hours = Math.floor(playtimeRaw / 3600);
const minutes = Math.floor((playtimeRaw % 3600) / 60);
const seconds = Math.floor(playtimeRaw % 60);

// initialize the formatted playtime string
let formattedPlaytime = "";

// add the hours to the formatted playtime string
if (hours > 0) {
formattedPlaytime += `${hours} hour${hours > 1 ? "s" : ""}`;
}

// add the minutes to the formatted playtime string
if (minutes > 0) {
// add a comma if there are already hours in the formatted playtime string
if (formattedPlaytime.length > 0) {
formattedPlaytime += ", ";
}
formattedPlaytime += `${minutes} minute${minutes > 1 ? "s" : ""}`;
}

// add the seconds to the formatted playtime string
if (seconds > 0) {
// add a comma if there are already hours or minutes in the formatted playtime string
if (formattedPlaytime.length > 0) {
formattedPlaytime += ", ";
}
formattedPlaytime += `${seconds} second${seconds > 1 ? "s" : ""}`;
}

// return the formatted playtime string
return formattedPlaytime;
}

// get the playtime from the backend, format it, and assign it to the playtime variable when the page first loads
getPlaytime(getInternalName(activeGame)).then((result) => {
playtime = formatPlaytime(result);
});

// listen for the custom playtiemUpdated event from the backend and then refresh the playtime on screen
listen<string>("playtimeUpdated", (event) => {
getPlaytime(getInternalName(activeGame)).then((result) => {
playtime = formatPlaytime(result);
});
});
</script>

<div class="flex flex-col justify-end items-end mt-auto">
<!-- TOOO - time played -->
<h1
class="tracking-tighter text-2xl font-bold pb-3 text-orange-500 text-outline pointer-events-none"
>
{$_(`gameName_${getInternalName(activeGame)}`)}
</h1>
{#if playtime}
<h1 class="pb-4 text-xl text-outline tracking-tighter font-extrabold">
{`Played For ${playtime}`}
</h1>
{/if}
<div class="flex flex-row gap-2">
<Button
class="border-solid border-2 border-slate-900 rounded bg-slate-900 hover:bg-slate-800 text-sm text-white font-semibold px-5 py-2"
Expand Down
4 changes: 4 additions & 0 deletions src/lib/rpc/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,7 @@ export async function doesActiveToolingVersionSupportGame(
() => false,
);
}

export async function getPlaytime(gameName: string): Promise<number> {
return await invoke_rpc("get_playtime", { gameName: gameName }, () => 0);
}
Loading