Skip to content

Commit a3ff885

Browse files
fix: random fixes
fix some layout in the game library fix freezing in game library loading fix deadlock when trying to change library path
1 parent 9299f06 commit a3ff885

File tree

7 files changed

+72
-65
lines changed

7 files changed

+72
-65
lines changed

src/components/game-box.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ export function GameBox({ game }: { game: GameRow; isFirst?: boolean }) {
175175

176176
return (
177177
<ContextMenu onOpenChange={setContextOpen}>
178-
<ContextMenuTrigger ref={contextMenuRef}>
178+
<ContextMenuTrigger asChild ref={contextMenuRef}>
179179
<Navigable onButtonPress={onButtonPress}>
180180
<div
181181
className="group relative aspect-square h-auto w-full min-w-[150px] max-w-[200px] flex-1 cursor-pointer overflow-hidden rounded-sm bg-zinc-800 transition-transform focus-within:scale-110 hover:scale-110 data-gamepad-focus:scale-110"

src/components/game-library.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ function Grid({ search }: { search: string }) {
5454
"use no memo"; // Temporary while https://github.com/TanStack/virtual/pull/851 is not merged
5555

5656
const parentRef = useRef<HTMLDivElement | null>(null);
57-
const { games } = useAtomValue(atomGameLibrary);
57+
const games = useAtomValue(atomGameLibrary);
5858
const sortType = useAtomValue(atomGameLibrarySorting);
5959
const [itemPerRow, setItemPerRow] = useState(1);
6060

src/components/modals/version-manager-modal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ function TabAvailableVersion() {
194194
<TableBody>
195195
{data?.map((v) => (
196196
<VersionTableRow
197-
date={format(v.date, "PP")}
197+
date={v.date ? format(v.date, "PP") : "Unknown"}
198198
key={JSON.stringify(v)}
199199
prePelease={v.prerelease}
200200
release={v.name}

src/components/toolbar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { useDebounceEffect } from "@/lib/hooks/useDebounceEffect";
1515
import { cn } from "@/lib/utils/ui";
1616
import { atomFolderConfigModalIsOpen, oficialRepo } from "@/store/common";
1717
import {
18-
atomGameLibrary,
18+
atomGameLibraryIsIndexing,
1919
atomGameLibrarySorting,
2020
SortType,
2121
} from "@/store/game-library";
@@ -129,7 +129,7 @@ export function Toolbar({ onSearch = () => void 0 }: Props) {
129129
const setFolderConfigModalOpen = useSetAtom(atomFolderConfigModalIsOpen);
130130
const [sort, setSort] = useAtom(atomGameLibrarySorting);
131131
const [isSortOpen, setSortOpen] = useState(false);
132-
const { indexing } = useAtomValue(atomGameLibrary);
132+
const indexing = useAtomValue(atomGameLibraryIsIndexing);
133133

134134
const [search, setSearch] = useState("");
135135
useDebounceEffect(search, 200, onSearch);

src/store/db.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ export const db = {
3030
async removeGame(path: string): Promise<void> {
3131
await conn.execute("DELETE FROM games WHERE path = $1", [path]);
3232
},
33+
async removeAllGames(): Promise<void> {
34+
await conn.execute("DELETE FROM games");
35+
},
3336
async addGame(data: GameRow): Promise<void> {
3437
await conn.execute(
3538
"INSERT INTO games (path, cusa, title, version, fw_version, sfo_json) VALUES ($1, $2, $3, $4, $5, $6)",

src/store/game-library.ts

Lines changed: 63 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,8 @@ export const atomGameLibrarySorting = atomWithTauriStore<SortType, false>(
2121
{ initialValue: SortType.NONE },
2222
);
2323

24-
export const atomGameLibrary = atom<{
25-
indexing: boolean;
26-
games: GameRow[];
27-
}>({
28-
indexing: false,
29-
games: [],
30-
});
24+
export const atomGameLibraryIsIndexing = atom(false);
25+
export const atomGameLibrary = atom<GameRow[]>([]);
3126

3227
async function loadGameData(path: string): Promise<GameRow> {
3328
try {
@@ -83,53 +78,60 @@ async function loadGameData(path: string): Promise<GameRow> {
8378
}
8479
}
8580

86-
async function registerGamePath(path: string) {
87-
console.debug(`Loading game from ${path}`);
88-
const gameData = await loadGameData(path);
89-
if (!("error" in gameData)) {
90-
db.addGame(gameData);
81+
const gameRegisterQueue: string[] = [];
82+
let gameRegisterQueueIsUse = false;
83+
84+
async function registerGamePath(workPath: string) {
85+
console.debug(`Loading game from ${workPath}`);
86+
gameRegisterQueue.push(workPath);
87+
if (gameRegisterQueueIsUse) {
88+
return;
89+
}
90+
gameRegisterQueueIsUse = true;
91+
while (gameRegisterQueue.length > 0) {
92+
const path = gameRegisterQueue.shift();
93+
if (!path) {
94+
break;
95+
}
96+
const gameData = await loadGameData(path);
97+
if (!("error" in gameData)) {
98+
db.addGame(gameData);
99+
}
100+
defaultStore.set(atomGameLibrary, (prev) =>
101+
prev.filter((e) => e.path !== path).concat(gameData),
102+
);
91103
}
92-
defaultStore.set(atomGameLibrary, (prev) => ({
93-
...prev,
94-
games: prev.games.filter((e) => e.path !== path).concat(gameData),
95-
}));
104+
gameRegisterQueueIsUse = false;
96105
}
97106

98107
async function unregisterGamePathPrefix(
99108
pathPrefix: string,
100109
knownPaths: Set<string>,
101110
) {
102-
defaultStore.set(atomGameLibrary, (prev) => {
103-
return {
104-
...prev,
105-
games: prev.games.filter((e) => {
106-
const toRemove = e.path.startsWith(pathPrefix);
107-
if (toRemove) {
108-
knownPaths.delete(e.path);
109-
db.removeGame(e.path);
110-
}
111-
return !toRemove;
112-
}),
113-
};
114-
});
111+
defaultStore.set(atomGameLibrary, (prev) =>
112+
prev.filter((e) => {
113+
const toRemove = e.path.startsWith(pathPrefix);
114+
if (toRemove) {
115+
knownPaths.delete(e.path);
116+
db.removeGame(e.path);
117+
}
118+
return !toRemove;
119+
}),
120+
);
115121
}
116122

117123
async function isGame(path: string) {
118124
const eBootPath = await join(path, "eboot.bin");
119125
return await exists(eBootPath);
120126
}
121127

122-
let indexingCount = 0;
123-
124128
async function scanDirectory(
125129
path: string,
126130
knownPaths: Set<string>,
127131
signal: AbortSignal,
128132
recursionLevel: number,
129133
) {
130134
try {
131-
indexingCount++;
132-
133135
if (recursionLevel > 3 || signal.aborted) {
134136
return;
135137
}
@@ -140,14 +142,14 @@ async function scanDirectory(
140142
return;
141143
}
142144
if (await isGame(path)) {
143-
setTimeout(() => registerGamePath(path), 1);
145+
void registerGamePath(path);
144146
return;
145147
}
146148
const children = await readDir(path);
147149
for (const c of children) {
148150
if (c.isDirectory) {
149151
const childPath = await join(path, c.name);
150-
scanDirectory(
152+
await scanDirectory(
151153
childPath,
152154
knownPaths,
153155
signal,
@@ -157,54 +159,47 @@ async function scanDirectory(
157159
}
158160
} catch (e: unknown) {
159161
console.error(`Error discovering game at "${path}"`, e);
160-
} finally {
161-
indexingCount--;
162-
if (indexingCount === 0) {
163-
defaultStore.set(atomGameLibrary, (prev) => ({
164-
...prev,
165-
indexing: false,
166-
}));
167-
}
168162
}
169163
}
170164

171165
(async () => {
172166
const cachedGames = await db.listGames();
173-
defaultStore.set(atomGameLibrary, {
174-
indexing: true,
175-
games: cachedGames,
176-
});
167+
defaultStore.set(atomGameLibraryIsIndexing, true);
168+
defaultStore.set(atomGameLibrary, cachedGames);
177169

178170
const knownPaths = new Set<string>();
179171

180172
for (const e of cachedGames) {
181173
knownPaths.add(e.path);
182174
}
183175

184-
let first = true;
185-
176+
let prevPath: string | null = null;
186177
let cancel: (() => void) | undefined;
178+
187179
defaultStore.sub(atomGamesPath, async () => {
188180
cancel?.();
189181
cancel = undefined;
190182

191183
try {
192-
if (!first) {
193-
first = false;
194-
defaultStore.set(atomGameLibrary, {
195-
indexing: true,
196-
games: [],
197-
});
184+
const path = defaultStore.get(atomGamesPath);
185+
if (prevPath != null && prevPath !== path) {
186+
await db.removeAllGames();
187+
defaultStore.set(atomGameLibraryIsIndexing, true);
188+
defaultStore.set(atomGameLibrary, []);
198189
knownPaths.clear();
199190
}
200-
indexingCount = 0;
201-
const path = defaultStore.get(atomGamesPath);
191+
prevPath = path;
202192
if (path) {
203193
if (!(await exists(path))) {
204194
await mkdir(path, { recursive: true });
205195
}
206196
const abortController = new AbortController();
207-
scanDirectory(path, knownPaths, abortController.signal, 0);
197+
await scanDirectory(
198+
path,
199+
knownPaths,
200+
abortController.signal,
201+
0,
202+
);
208203
const unsub = await watch(path, async (e) => {
209204
if (typeof e.type === "object") {
210205
if ("create" in e.type) {
@@ -220,12 +215,20 @@ async function scanDirectory(
220215
return;
221216
}
222217
}
223-
scanDirectory(
218+
defaultStore.set(
219+
atomGameLibraryIsIndexing,
220+
true,
221+
);
222+
await scanDirectory(
224223
newPath,
225224
knownPaths,
226225
abortController.signal,
227226
1,
228227
);
228+
defaultStore.set(
229+
atomGameLibraryIsIndexing,
230+
false,
231+
);
229232
}
230233
} else if ("remove" in e.type) {
231234
const newPath = e.paths[0];
@@ -235,6 +238,7 @@ async function scanDirectory(
235238
}
236239
}
237240
});
241+
defaultStore.set(atomGameLibraryIsIndexing, false);
238242
cancel = () => {
239243
unsub();
240244
abortController.abort();

src/store/version-manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export const atomAvailableVersions = atomWithQuery((get) => ({
9797
queryKey: [, , list],
9898
}: {
9999
queryKey: [string, string, string[]];
100-
}) => {
100+
}): Promise<RemoteEmulatorVersion[]> => {
101101
return (
102102
await Promise.all(
103103
list.map(async (repoSource) => {

0 commit comments

Comments
 (0)