feat: optimize achievements sync

This commit is contained in:
Zamitto
2025-06-02 21:42:21 -03:00
parent bacf6804e4
commit 2a74526b0f
6 changed files with 70 additions and 43 deletions

View File

@@ -1,13 +1,13 @@
import { registerEvent } from "../register-event";
import type { GameShop } from "@types";
import { createGame } from "@main/services/library-sync";
import { updateLocalUnlockedAchievements } from "@main/services/achievements/update-local-unlocked-achivements";
import {
downloadsSublevel,
gamesShopAssetsSublevel,
gamesSublevel,
levelKeys,
} from "@main/level";
import { AchievementWatcherManager } from "@main/services/achievements/achievement-watcher-manager";
const addGameToLibrary = async (
_event: Electron.IpcMainInvokeEvent,
@@ -43,7 +43,10 @@ const addGameToLibrary = async (
await createGame(game).catch(() => {});
updateLocalUnlockedAchievements(game);
AchievementWatcherManager.firstSyncWithRemoteIfNeeded(
game.shop,
game.objectId
);
};
registerEvent("addGameToLibrary", addGameToLibrary);

View File

@@ -3,6 +3,7 @@ import { registerEvent } from "../register-event";
import { HydraApi } from "@main/services";
import { db, levelKeys } from "@main/level";
import { AchievementWatcherManager } from "@main/services/achievements/achievement-watcher-manager";
const getComparedUnlockedAchievements = async (
_event: Electron.IpcMainInvokeEvent,
@@ -10,6 +11,8 @@ const getComparedUnlockedAchievements = async (
shop: GameShop,
userId: string
) => {
await AchievementWatcherManager.firstSyncWithRemoteIfNeeded(shop, objectId);
const userPreferences = await db.get<string, UserPreferences | null>(
levelKeys.userPreferences,
{

View File

@@ -2,12 +2,15 @@ import type { GameShop, UserAchievement, UserPreferences } from "@types";
import { registerEvent } from "../register-event";
import { getGameAchievementData } from "@main/services/achievements/get-game-achievement-data";
import { db, gameAchievementsSublevel, levelKeys } from "@main/level";
import { AchievementWatcherManager } from "@main/services/achievements/achievement-watcher-manager";
export const getUnlockedAchievements = async (
objectId: string,
shop: GameShop,
useCachedData: boolean
): Promise<UserAchievement[]> => {
AchievementWatcherManager.firstSyncWithRemoteIfNeeded(shop, objectId);
const cachedAchievements = await gameAchievementsSublevel.get(
levelKeys.game(shop, objectId)
);

View File

@@ -10,6 +10,7 @@ import {
import type {
AchievementFile,
Game,
GameShop,
UnlockedAchievement,
UserPreferences,
} from "@types";
@@ -146,7 +147,48 @@ const processAchievementFileDiff = async (
};
export class AchievementWatcherManager {
private static hasFinishedMergingWithRemote = false;
private static _hasFinishedMergingWithRemote = false;
public static get hasFinishedMergingWithRemote() {
return this._hasFinishedMergingWithRemote;
}
public static readonly alreadySyncedGames: Map<string, boolean> = new Map();
public static async firstSyncWithRemoteIfNeeded(
shop: GameShop,
objectId: string
) {
const gameKey = levelKeys.game(shop, objectId);
if (this.alreadySyncedGames.get(gameKey)) return;
const game = await gamesSublevel.get(gameKey).catch(() => null);
if (!game) return;
const gameAchievementFiles = findAchievementFiles(game);
const achievementFileInsideDirectory =
findAchievementFileInExecutableDirectory(game);
gameAchievementFiles.push(...achievementFileInsideDirectory);
const unlockedAchievements: UnlockedAchievement[] = [];
for (const achievementFile of gameAchievementFiles) {
const localAchievementFile = parseAchievementFile(
achievementFile.filePath,
achievementFile.type
);
if (localAchievementFile.length) {
unlockedAchievements.push(...localAchievementFile);
}
}
this.alreadySyncedGames.set(gameKey, true);
return mergeAchievements(game, unlockedAchievements, false);
}
public static watchAchievements() {
if (!this.hasFinishedMergingWithRemote) return;
@@ -295,6 +337,6 @@ export class AchievementWatcherManager {
achievementsLogger.error("Error on preSearchAchievements", err);
}
this.hasFinishedMergingWithRemote = true;
this._hasFinishedMergingWithRemote = true;
}
}

View File

@@ -14,6 +14,7 @@ import { SubscriptionRequiredError } from "@shared";
import { achievementsLogger } from "../logger";
import { db, gameAchievementsSublevel, levelKeys } from "@main/level";
import { getGameAchievementData } from "./get-game-achievement-data";
import { AchievementWatcherManager } from "./achievement-watcher-manager";
const isRareAchievement = (points: number) => {
const rawPercentage = (50 - Math.sqrt(points)) * 2;
@@ -56,9 +57,9 @@ export const mergeAchievements = async (
achievements: UnlockedAchievement[],
publishNotification: boolean
) => {
let localGameAchievement = await gameAchievementsSublevel.get(
levelKeys.game(game.shop, game.objectId)
);
const gameKey = levelKeys.game(game.shop, game.objectId);
let localGameAchievement = await gameAchievementsSublevel.get(gameKey);
const userPreferences = await db.get<string, UserPreferences>(
levelKeys.userPreferences,
{
@@ -68,9 +69,7 @@ export const mergeAchievements = async (
if (!localGameAchievement) {
await getGameAchievementData(game.objectId, game.shop, true);
localGameAchievement = await gameAchievementsSublevel.get(
levelKeys.game(game.shop, game.objectId)
);
localGameAchievement = await gameAchievementsSublevel.get(gameKey);
}
const achievementsData = localGameAchievement?.achievements ?? [];
@@ -153,7 +152,12 @@ export const mergeAchievements = async (
}
}
if (game.remoteId) {
const shouldSyncWithRemote =
game.remoteId &&
(newAchievements.length ||
AchievementWatcherManager.hasFinishedMergingWithRemote);
if (shouldSyncWithRemote) {
await HydraApi.put<UpdatedUnlockedAchievements | undefined>(
"/profile/games/achievements",
{
@@ -194,8 +198,11 @@ export const mergeAchievements = async (
mergedLocalAchievements,
publishNotification
);
})
.finally(() => {
AchievementWatcherManager.alreadySyncedGames.set(gameKey, true);
});
} else {
} else if (newAchievements.length) {
await saveAchievementsOnLocal(
game.objectId,
game.shop,

View File

@@ -1,31 +0,0 @@
import {
findAchievementFiles,
findAchievementFileInExecutableDirectory,
} from "./find-achivement-files";
import { parseAchievementFile } from "./parse-achievement-file";
import { mergeAchievements } from "./merge-achievements";
import type { Game, UnlockedAchievement } from "@types";
export const updateLocalUnlockedAchievements = async (game: Game) => {
const gameAchievementFiles = findAchievementFiles(game);
const achievementFileInsideDirectory =
findAchievementFileInExecutableDirectory(game);
gameAchievementFiles.push(...achievementFileInsideDirectory);
const unlockedAchievements: UnlockedAchievement[] = [];
for (const achievementFile of gameAchievementFiles) {
const localAchievementFile = parseAchievementFile(
achievementFile.filePath,
achievementFile.type
);
if (localAchievementFile.length) {
unlockedAchievements.push(...localAchievementFile);
}
}
mergeAchievements(game, unlockedAchievements, false);
};