[feature] disable hardware acceleration and use symlinks work, just still need to translate

This commit is contained in:
MathieuG-P
2024-09-01 18:08:03 +02:00
parent ac62926f8d
commit 55aeab896c
14 changed files with 310 additions and 35 deletions
+1 -1
View File
@@ -153,7 +153,7 @@ export async function copyDirectoryWithJunctions(src: string, dest: string, opti
const symlinkTarget = await readlink(sourcePath);
const relativePath = path.relative(src, symlinkTarget);
const newTarget = path.join(dest, relativePath);
await symlink(newTarget, destinationPath, "junction");
await symlink(newTarget, destinationPath, "junction"); // Only junction to avoid right issues while copying content of BSManager folder
}
}
}
+1
View File
@@ -12,3 +12,4 @@ import "./bs-playlist-ipcs";
import "./model-saber.ipcs";
import "./bs-model-ipcs";
import "./bs-version-download/bs-download-ipcs";
import "./static-configuration.ipcs";
+11 -1
View File
@@ -3,6 +3,7 @@ import { NotificationService } from "../services/notification.service";
import { IpcService } from "../services/ipc.service";
import { from, of } from "rxjs";
import { readFileSync } from "fs-extra";
import log from "electron-log";
// TODO IMPROVE WINDOW CONTROL BY USING WINDOW SERVICE
@@ -16,7 +17,7 @@ ipc.on("choose-folder", (args, reply) => {
reply(from(dialog.showOpenDialog({ properties: ["openDirectory"], defaultPath: args ?? "" })));
});
ipc.on<string>("choose-file", async (args, reply) => {
ipc.on("choose-file", async (args, reply) => {
reply(from(dialog.showOpenDialog({ properties: ["openFile"], defaultPath: args ?? "" })));
});
@@ -62,3 +63,12 @@ ipc.on("choose-image", (args, reply) => {
return res.filePaths;
})));
});
ipc.on("restart-app", (_, reply) => {
log.info("App was requested to restart");
reply(of()); // Reply before restarting to avoid any issue
app.relaunch();
app.quit();
});
@@ -0,0 +1,14 @@
import { of } from "rxjs";
import { IpcService } from "../services/ipc.service";
import { StaticConfigurationService } from "../services/static-configuration.service";
const ipc = IpcService.getInstance();
const staticConfig = StaticConfigurationService.getInstance();
ipc.on("static-configuration.get", (args, reply) => {
reply(of(staticConfig.get(args)));
});
ipc.on("static-configuration.set", (args, reply) => {
reply(of(staticConfig.set(args.key, args.value)));
});
+10 -1
View File
@@ -24,8 +24,10 @@ import { SteamLauncherService } from "./services/bs-launcher/steam-launcher.serv
import { FileAssociationService } from "./services/file-association.service";
import { SongDetailsCacheService } from "./services/additional-content/maps/song-details-cache.service";
import { readdirSync, statSync, unlinkSync } from "fs-extra";
import { StaticConfigurationService } from "./services/static-configuration.service";
const isDebug = process.env.NODE_ENV === "development" || process.env.DEBUG_PROD === "true";
const staticConfig = StaticConfigurationService.getInstance();
export const filterStrings = new Set<string>();
export const filterPatterns = new Set<RegExp>();
@@ -37,6 +39,13 @@ initLogger();
deleteOlestLogs();
deleteOldLogs();
staticConfig.take("disable-hadware-acceleration", disabled => {
if(disabled === true){ // strictly check for true
log.info("Disabling hardware acceleration");
app.disableHardwareAcceleration();
}
});
if (process.env.NODE_ENV === "production") {
const sourceMapSupport = require("source-map-support");
@@ -108,7 +117,7 @@ if (!gotTheLock) {
app.whenReady().then(() => {
// C:\\Users\\Mathieu\\Desktop\\BSManager\\BSInstances\\My Version\\UserData\\SongDetailsCache.proto
app.setAppUserModelId(APP_NAME);
@@ -1,7 +1,6 @@
import path from "path";
import { ensureDirSync, existsSync, readFile, writeFile } from "fs-extra";
import { BehaviorSubject, Observable, catchError, filter, lastValueFrom, of, take, timeout } from "rxjs";
import { ConfigurationService } from "../../configuration.service";
import { RequestService } from "../../request.service";
import { tryit } from "shared/helpers/error.helpers";
import { CACHE_PATH, HTTP_STATUS_CODES } from "main/constants";
@@ -12,6 +11,7 @@ import { SongDetails } from "shared/models/maps/song-details-cache/song-details-
import { inflate } from "pako";
import { RawSongDetailsCache } from "shared/models/maps/song-details-cache/raw-song-details-cache.model";
import { RawSongDetailsDeserializer } from "shared/models/maps/song-details-cache/raw-song-details-deserializer.class";
import { StaticConfigurationService } from "main/services/static-configuration.service";
export class SongDetailsCacheService {
@@ -32,7 +32,7 @@ export class SongDetailsCacheService {
private readonly PROTO_CACHE_PATH = path.join(CACHE_PATH, "song-details-cache");
private readonly etagKey = "song-details-cache-etag";
private readonly config: ConfigurationService;
private readonly staticConfig: StaticConfigurationService;
private readonly request: RequestService;
private readonly utils: UtilsService;
@@ -41,7 +41,7 @@ export class SongDetailsCacheService {
private readonly _loaded$ = new BehaviorSubject<boolean>(null);
private constructor(){
this.config = ConfigurationService.getInstance();
this.staticConfig = StaticConfigurationService.getInstance();
this.request = RequestService.getInstance();
this.utils = UtilsService.getInstance();
this.loadCache()
@@ -49,10 +49,10 @@ export class SongDetailsCacheService {
private async loadCache(): Promise<void> {
const protoCacheExists = existsSync(this.PROTO_CACHE_PATH);
const etag = protoCacheExists ? this.config.get<string>(this.etagKey) : null;
const etag = protoCacheExists ? this.staticConfig.get(this.etagKey) : null;
await this.downloadCacheFile(etag).then(etag => {
this.config.set(this.etagKey, etag);
this.staticConfig.set(this.etagKey, etag);
log.info("SongDetailsCache downloaded");
this.songDetailsIdIndex = this.createIdIndex(this.songDetailsCache);
log.info("SongDetailsIdIndex created");
+21 -2
View File
@@ -6,6 +6,7 @@ import path from "path";
import { copy, readlink } from "fs-extra";
import { lastValueFrom } from "rxjs";
import { noop } from "shared/helpers/function.helpers";
import { StaticConfigurationService } from "./static-configuration.service";
export class FolderLinkerService {
private static instance: FolderLinkerService;
@@ -18,9 +19,20 @@ export class FolderLinkerService {
}
private readonly installLocationService = InstallationLocationService.getInstance();
private readonly staticConfig: StaticConfigurationService;
private linkingType: "junction" | "symlink" = "junction";
private constructor() {
this.installLocationService = InstallationLocationService.getInstance();
this.staticConfig = StaticConfigurationService.getInstance();
this.linkingType = this.staticConfig.get("use-symlinks") === true ? "symlink" : "junction";
this.staticConfig.$watch("use-symlinks").subscribe(({ newValue: useSymlink }) => {
this.linkingType = useSymlink === true ? "symlink" : "junction";
log.info(`Linking type set to ${this.linkingType}`);
});
}
private async sharedFolder(): Promise<string> {
@@ -51,6 +63,10 @@ export class FolderLinkerService {
});
}
private getLinkingType(): "junction" | undefined {
return this.linkingType === "junction" ? "junction" : undefined;
}
public async linkFolder(folderPath: string, options?: LinkOptions): Promise<void> {
const sharedPath = await this.getSharedFolder(folderPath, options?.intermediateFolder);
@@ -62,7 +78,9 @@ export class FolderLinkerService {
return;
}
await unlinkPath(folderPath);
return symlink(sharedPath, folderPath, "junction");
log.info(`Linking ${folderPath} to ${sharedPath}; type: ${this.linkingType}`);
return symlink(sharedPath, folderPath, this.getLinkingType());
}
await ensureFolderExist(sharedPath);
@@ -79,7 +97,8 @@ export class FolderLinkerService {
await deleteFolder(folderPath);
return symlink(sharedPath, folderPath, "junction");
log.info(`Linking ${folderPath} to ${sharedPath}; type: ${this.linkingType}`);
return symlink(sharedPath, folderPath, this.getLinkingType());
}
public async unlinkFolder(folderPath: string, options?: UnlinkOptions): Promise<void> {
@@ -1,9 +1,9 @@
import path from "path";
import { app } from "electron";
import ElectronStore from "electron-store";
import { copyDirectoryWithJunctions, deleteFolder, ensureFolderExist } from "../helpers/fs.helpers";
import { tryit } from "../../shared/helpers/error.helpers";
import { pathExistsSync } from "fs-extra";
import { StaticConfigurationService } from "./static-configuration.service";
export class InstallationLocationService {
private static instance: InstallationLocationService;
@@ -23,15 +23,15 @@ export class InstallationLocationService {
private readonly STORE_INSTALLATION_PATH_KEY = "installation-folder";
private readonly installPathConfig: ElectronStore;
private readonly staticConfig: StaticConfigurationService;
private readonly updateListeners: Set<Listener> = new Set();
private _installationDirectory: string;
private constructor() {
this.installPathConfig = new ElectronStore({ watch: true });
this.staticConfig = StaticConfigurationService.getInstance();
this.installPathConfig.onDidChange(this.STORE_INSTALLATION_PATH_KEY, () => {
this.staticConfig.$watch(this.STORE_INSTALLATION_PATH_KEY).subscribe(() => {
this.triggerListeners();
});
}
@@ -48,7 +48,7 @@ export class InstallationLocationService {
await copyDirectoryWithJunctions(oldDir, path.join(newDir, this.INSTALLATION_FOLDER), { overwrite: true });
this._installationDirectory = newDir;
this.installPathConfig.set(this.STORE_INSTALLATION_PATH_KEY, newDir);
this.staticConfig.set(this.STORE_INSTALLATION_PATH_KEY, newDir);
deleteFolder(oldDir);
@@ -66,8 +66,8 @@ export class InstallationLocationService {
return this._installationDirectory;
}
if(this.installPathConfig.has(this.STORE_INSTALLATION_PATH_KEY)) {
return this.installPathConfig.get(this.STORE_INSTALLATION_PATH_KEY) as string;
if(this.staticConfig.has(this.STORE_INSTALLATION_PATH_KEY)) {
return this.staticConfig.get(this.STORE_INSTALLATION_PATH_KEY) as string;
}
const { result: oldPath } = tryit(() => path.join(app.getPath("documents"), this.INSTALLATION_FOLDER));
@@ -0,0 +1,73 @@
import ElectronStore from "electron-store";
import { Observable } from "rxjs";
export class StaticConfigurationService {
private static instance: StaticConfigurationService;
public static getInstance(): StaticConfigurationService {
if (!StaticConfigurationService.instance) {
StaticConfigurationService.instance = new StaticConfigurationService();
}
return StaticConfigurationService.instance;
}
private readonly store: ElectronStore;
private constructor() {
this.store = new ElectronStore({ watch: true });
}
public has<K extends StaticConfigKeys>(key: K): boolean {
return this.store.has(key);
}
public get<K extends StaticConfigKeys>(key: K): StaticConfigKeyValues[K] {
return this.store.get<K>(key) as StaticConfigKeyValues[K];
}
public take<K extends StaticConfigKeys>(key: K, cb: (val: StaticConfigKeyValues[K]) => void): void {
cb(this.get(key));
}
public set<K extends StaticConfigKeys>(key: K, value: StaticConfigKeyValues[K]): void {
this.store.set(key, value);
}
public delete<K extends StaticConfigKeys>(key: K): void {
this.store.delete(key);
}
public getStore(): ElectronStore {
return this.store;
}
public $watch<K extends StaticConfigKeys>(key: K): Observable<{ newValue: StaticConfigKeyValues[K], oldValue: StaticConfigKeyValues[K] }> {
return new Observable(obs => {
const unsub = this.store.onDidChange(key, (newValue, oldValue) => {
obs.next({ newValue, oldValue } as { newValue: StaticConfigKeyValues[K], oldValue: StaticConfigKeyValues[K] });
});
return () => unsub();
})
}
}
export interface StaticConfigKeyValues {
"installation-folder": string;
"song-details-cache-etag": string;
"disable-hadware-acceleration": boolean;
"use-symlinks": boolean;
}
export type StaticConfigKeys = keyof StaticConfigKeyValues;
export type StaticConfigGetIpcRequestResponse<K extends StaticConfigKeys> = {
request: K;
response: StaticConfigKeyValues[K];
};
export type StaticConfigSetIpcRequest<K extends StaticConfigKeys> = {
request: { key: K, value: StaticConfigKeyValues[K] };
response: void;
}
@@ -0,0 +1,42 @@
import { BsmButton, BsmButtonType } from "renderer/components/shared/bsm-button.component";
import { BsmImage } from "renderer/components/shared/bsm-image.component";
import { cn } from "renderer/helpers/css-class.helpers";
import { useTranslation } from "renderer/hooks/use-translation.hook";
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service";
type BasicModalOptions = {
title: string;
image: string;
body?: string;
buttons?: {
id: string;
text: string;
type: BsmButtonType,
isCancel?: boolean;
}[];
buttonsLayout?: "row" | "column";
};
export const BasicModal: ModalComponent<BasicModalOptions["buttons"][0]["id"], BasicModalOptions> = ({ resolver, options: {
data: { title, image, body, buttons, buttonsLayout = "column" }
} }) => {
const t = useTranslation();
const handleClick = (button: BasicModalOptions["buttons"][0]) => {
resolver({ exitCode: button.isCancel ? ModalExitCode.CANCELED : ModalExitCode.COMPLETED, data: button.id });
}
return (
<form className="text-gray-800 dark:text-gray-200">
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t(title)}</h1>
<BsmImage className="mx-auto h-24" image={image} />
{ body && <p className="max-w-sm w-full">{t(body)}</p> }
<div className={cn("grid gap-2 mt-4")} style={{ gridAutoFlow: buttonsLayout, ...(buttonsLayout === "row" ? { gridTemplateRows: `repeat(${buttons.length}, 1fr)` } : { gridTemplateColumns: `repeat(${buttons.length}, 1fr)` }) }}>
{buttons.map(button => (
<BsmButton key={button.id} typeColor={button.type} className="h-8 rounded-md text-center flex justify-center items-center" onClick={() => handleClick(button)} withBar={false} text={button.text} />
))}
</div>
</form>
);
};
@@ -34,36 +34,36 @@ export function ToogleSwitch({ checked, className, classNames, bgColor, onChange
}, [dotColor]);
useEffect(() => {
if (checked !== undefined) {
setIsChecked(checked);
}
setIsChecked(checked);
}, [checked]);
useEffect(() => {
if (onChange) {
onChange(isChecked);
}
}, [isChecked]);
const handleCheckboxChange = () => {
if (middleWare) {
setIsChecked(prev => {
const newState = !prev;
console.log('newState', newState);
const result = middleWare(newState);
if (isPromise(result)) {
result.then(shouldChange => {
if (shouldChange) {
setIsChecked(newState);
setIsChecked(() => {
onChange?.(newState);
return newState;
});
}
}).catch(noop);
} else if (result) {
setIsChecked(newState);
setIsChecked(() => {
onChange?.(newState);
return newState;
});
}
return prev;
});
} else {
setIsChecked(prev => !prev);
setIsChecked(prev => {
onChange?.(!prev);
return !prev;
});
}
}
+76 -5
View File
@@ -41,9 +41,13 @@ import { OculusIcon } from "renderer/components/svgs/icons/oculus-icon.component
import { BsDownloaderService } from "renderer/services/bs-version-download/bs-downloader.service";
import { AutoUpdaterService } from "renderer/services/auto-updater.service";
import BeatWaitingImg from "../../../assets/images/apngs/beat-waiting.png";
import BeatConflict from "../../../assets/images/apngs/beat-conflict.png";
import { logRenderError } from "renderer";
import { BSLauncherService } from "renderer/services/bs-launcher.service";
import { SettingToogleSwitchGrid } from "renderer/components/settings/setting-toogle-switch-grid.component";
import { BasicModal } from "renderer/components/modal/basic-modal.component";
import { StaticConfigurationService } from "renderer/services/static-configuration.service";
import { tryit } from "shared/helpers/error.helpers";
export function SettingsPage() {
@@ -63,6 +67,7 @@ export function SettingsPage() {
const modelsManager = useService(ModelsManagerService);
const versionLinker = useService(VersionFolderLinkerService);
const autoUpdater = useService(AutoUpdaterService);
const staticConfig = useService(StaticConfigurationService);
const { firstColor, secondColor } = useThemeColor();
@@ -93,7 +98,7 @@ export function SettingsPage() {
const [playlistsDeepLinkEnabled, setPlaylistsDeepLinkEnabled] = useState(false);
const [modelsDeepLinkEnabled, setModelsDeepLinkEnabled] = useState(false);
const [hasDownloaderSession, setHasDownloaderSession] = useState(false);
const [hardwareAcceleration, setHardwareAcceleration] = useState(true);
const [hardwareAccelerationEnabled, setHardwareAccelerationEnabled] = useState(true);
const [useSymlink, setUseSymlink] = useState(false);
const appVersion = useObservable(() => ipcService.sendV2("current-version"));
@@ -106,6 +111,9 @@ export function SettingsPage() {
mapsManager.isDeepLinksEnabled().then(enabled => setMapDeepLinksEnabled(() => enabled));
playlistsManager.isDeepLinksEnabled().then(enabled => setPlaylistsDeepLinkEnabled(() => enabled));
modelsManager.isDeepLinksEnabled().then(enabled => setModelsDeepLinkEnabled(() => enabled));
staticConfig.get("disable-hadware-acceleration").then(disabled =>setHardwareAccelerationEnabled(() => disabled === false));
staticConfig.get("use-symlinks").then(useSymlinks => setUseSymlink(() => useSymlinks));
}, []);
const allDeepLinkEnabled = mapDeepLinksEnabled && playlistsDeepLinkEnabled && modelsDeepLinkEnabled;
@@ -222,14 +230,77 @@ export function SettingsPage() {
});
};
const onChangeHardwareAcceleration = async (enabled: boolean): Promise<boolean> => {
if(enabled === hardwareAcceleration){ return false; }
const onAboutToChangeHardwareAcceleration = async (enabled: boolean): Promise<boolean> => {
if(enabled === hardwareAccelerationEnabled){ return false; }
const res = await modalService.openModal(BasicModal, { data: {
title: "Restart Needed",
body: "Changing hardware acceleration setting will quit and re-launch BSManager. Are you sure you want to do this?",
image: BeatConflict,
buttons: [
{ id: "cancel", text: "Cancel", type: "cancel", isCancel: true },
{ id: "confirm", text: "Yes I'm sure", type: "error" }
]
}});
if(res.exitCode !== ModalExitCode.COMPLETED || res.data !== "confirm"){
return false;
}
return true;
};
const onChangeHardwareAcceleration = async (enabled: boolean) => {
const { error } = await tryit(() => staticConfig.set("disable-hadware-acceleration", !enabled));
if(error){
notificationService.notifyError({ title: "notifications.types.error", desc: "An error occur, unable to disable hardware acceleration" });
return;
}
setHardwareAccelerationEnabled(() => enabled);
if(!progressBarService.require()){
return;
}
await lastValueFrom(ipcService.sendV2("restart-app"));
};
const onAboutToChangeUseSymlinks = async (newUseSymlink: boolean): Promise<boolean> => {
if(newUseSymlink === useSymlink){ return false; }
if(!newUseSymlink){ return true; } // If we are disabling symlinks, no need to inform about admin rights
const res = await modalService.openModal(BasicModal, { data: {
title: "Permission Needed",
body: "In order to create symlinks, BSManager will need to run as administrator or your system will need to have developer mode enabled. Are you sure you want to do this?",
image: BeatConflict,
buttons: [
{ id: "cancel", text: "Cancel", type: "cancel", isCancel: true },
{ id: "confirm", text: "Yes I'm sure", type: "error" }
]
}});
if(res.exitCode !== ModalExitCode.COMPLETED || res.data !== "confirm"){
return false;
}
return true;
}
const onChangeUseSymlinks = async (useSymlink: boolean) => {
const { error } = await tryit(() => staticConfig.set("use-symlinks", useSymlink));
if(error){
notificationService.notifyError({ title: "notifications.types.error", desc: "An error occur, unable to change symlinks settings" });
return;
}
setUseSymlink(() => useSymlink);
}
const toogleShowSupporters = () => {
setShowSupporters(show => !show);
};
@@ -514,8 +585,8 @@ export function SettingsPage() {
<SettingContainer title="Advanced" description="Changes these stettings only if your are sure of what you are doing">
<SettingToogleSwitchGrid items={[
{ checked: hardwareAcceleration, text: "Hadware Acceleration", desc: "Enable Hardware Acceleration to use your GPU and improve BSManager's performance. Turn this off if you're experiencing frame drops.", middleWare: onChangeHardwareAcceleration },
{ checked: useSymlink, text: "Use Symlink", desc: "Use Symlinks instead of Junctions to link folders. Turn this on only if you really need it.", onChange: () => {} },
{ checked: hardwareAccelerationEnabled, text: "Hadware Acceleration", desc: "Enable Hardware Acceleration to use your GPU and improve BSManager's performance. Turn this off if you're experiencing frame drops.", middleWare: onAboutToChangeHardwareAcceleration, onChange: onChangeHardwareAcceleration },
{ checked: useSymlink, text: "Use Symlinks", desc: "Use Symlinks instead of Junctions to link folders. Turn this on only if you really need it.", middleWare: onAboutToChangeUseSymlinks, onChange: onChangeUseSymlinks },
]}/>
</SettingContainer>
@@ -0,0 +1,30 @@
import { StaticConfigKeys, StaticConfigKeyValues } from "main/services/static-configuration.service";
import { IpcService } from "./ipc.service";
import { lastValueFrom } from "rxjs";
export class StaticConfigurationService {
private static instance: StaticConfigurationService;
public static getInstance(): StaticConfigurationService {
if (!StaticConfigurationService.instance) {
StaticConfigurationService.instance = new StaticConfigurationService();
}
return StaticConfigurationService.instance;
}
private readonly ipc: IpcService;
private constructor(){
this.ipc = IpcService.getInstance();
}
public get<K extends StaticConfigKeys>(key: K): Promise<StaticConfigKeyValues[K]> {
return lastValueFrom(this.ipc.sendV2("static-configuration.get", key)) as Promise<StaticConfigKeyValues[K]>;
}
public set<K extends StaticConfigKeys>(key: K, value: StaticConfigKeyValues[K]): Promise<void> {
return lastValueFrom(this.ipc.sendV2("static-configuration.set", { key, value }));
}
}
+6
View File
@@ -19,6 +19,7 @@ import { SystemNotificationOptions } from "../notification/system-notification.m
import { Supporter } from "../supporters";
import { AppWindow } from "../window-manager/app-window.model";
import { LocalBPList, LocalBPListsDetails } from "../playlists/local-playlist.models";
import { StaticConfigGetIpcRequestResponse, StaticConfigKeys, StaticConfigSetIpcRequest } from "main/services/static-configuration.service";
export type IpcReplier<T> = (data: Observable<T>) => void;
@@ -124,6 +125,7 @@ export interface IpcChannelMapping {
"open-logs": { request: void, response: string };
"notify-system": { request: SystemNotificationOptions, response: void };
"view-path-in-explorer": { request: string, response: void };
"restart-app": { request: void, response: void };
/* ** supporters-ipcs ** */
"get-supporters": { request: void, response: Supporter[] };
@@ -136,6 +138,10 @@ export interface IpcChannelMapping {
"open-window-then-close-all": { request: AppWindow, response: void };
"open-window-or-focus": { request: AppWindow, response: void };
/* ** static-configuration.ipcs ** */
"static-configuration.get": StaticConfigGetIpcRequestResponse<StaticConfigKeys>;
"static-configuration.set": StaticConfigSetIpcRequest<StaticConfigKeys>;
/* ** OTHERS (if your IPC channel is not in a "-ipcs" file, put it here) ** */
"shortcut-launch-options": { request: void, response: LaunchOption };
}