guard prefix and wine edits while their apps run
This commit is contained in:
parent
aca9f839e3
commit
38eea05789
9 changed files with 170 additions and 39 deletions
|
|
@ -17,7 +17,13 @@ function sendToWindow(win, channel, data) {
|
|||
if (win && !win.isDestroyed()) win.webContents.send(channel, data)
|
||||
}
|
||||
|
||||
// Game-process state: id -> pid (or null while the process is starting).
|
||||
// Game-process state: id -> { pid (null while the process is starting), the
|
||||
// prefix name and wine id the run was resolved to }. Both are frozen here:
|
||||
// the default prefix/wine can be switched and the app's own config edited
|
||||
// while it plays, and the in-use guards have to name what the process really
|
||||
// locked - not what the registry says now. Editing an app's own prefix while
|
||||
// it runs does not move the process, so the new folder is blocked next to the
|
||||
// snapshot (a pre-edit prefix does not exist in it yet).
|
||||
// onGamesChanged fires in the MAIN process whenever the running-games set
|
||||
// changes (the tray menu rebuilds itself from it).
|
||||
const STARTUP_GRACE_MS = 60000
|
||||
|
|
@ -47,10 +53,10 @@ function createGamesService({ getWindow, onGamesChanged }) {
|
|||
// restarted) would otherwise answer every later Play with "already
|
||||
// running".
|
||||
function prune() {
|
||||
for (const [id, pid] of [...running]) {
|
||||
if (alive(pid)) continue
|
||||
for (const [id, entry] of [...running]) {
|
||||
if (alive(entry.pid)) continue
|
||||
const age = Date.now() - (startedAt.get(id) || 0)
|
||||
if (!pid && age < STARTUP_GRACE_MS) continue // may still be starting
|
||||
if (!entry.pid && age < STARTUP_GRACE_MS) continue // may still be starting
|
||||
running.delete(id)
|
||||
startedAt.delete(id)
|
||||
}
|
||||
|
|
@ -64,6 +70,22 @@ function createGamesService({ getWindow, onGamesChanged }) {
|
|||
prune()
|
||||
return [...running.keys()]
|
||||
}
|
||||
// What the still-running games actually use, for the "is this prefix/wine in
|
||||
// use?" guards: [{ id, name, prefixes: [snapshot, ...], wine }]. The
|
||||
// snapshot alone goes stale when a running app is pointed at another prefix
|
||||
// (the process keeps its folder open); the live name alone misses the folder
|
||||
// it currently holds. Together they name every folder the run touches.
|
||||
const runningInfo = () => {
|
||||
prune()
|
||||
const out = []
|
||||
for (const [id, entry] of running) {
|
||||
const game = library.getGame(id)
|
||||
const live = game && game.prefix ? library.prefixNameForGame(game) : ''
|
||||
const prefixes = live && live !== entry.prefix ? [entry.prefix, live] : [entry.prefix]
|
||||
out.push({ id, name: (game && game.name) || id, prefixes, wine: entry.wine })
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// "Open with PLWL" handoff: queue the file for the renderer (picker popup
|
||||
// with wine version + prefix). Safe to call at any time - if the window is
|
||||
|
|
@ -90,16 +112,14 @@ function createGamesService({ getWindow, onGamesChanged }) {
|
|||
if (!game) return { ok: false, error: 'game-not-found' }
|
||||
prune()
|
||||
if (running.has(id)) return { ok: false, error: 'already-running' }
|
||||
running.set(id, null)
|
||||
startedAt.set(id, Date.now())
|
||||
let wine
|
||||
try {
|
||||
wine = resolveWine(game)
|
||||
} catch {
|
||||
running.delete(id)
|
||||
startedAt.delete(id)
|
||||
return { ok: false, error: 'wine-not-found' }
|
||||
}
|
||||
running.set(id, { pid: null, prefix: library.prefixNameForGame(game), wine: wine.id })
|
||||
startedAt.set(id, Date.now())
|
||||
try {
|
||||
const res = await runGame({ game, wine, systemWine: wine.type === 'system', onExit: () => handleExit(id) })
|
||||
// stop was requested while the process was still starting: kill it and
|
||||
|
|
@ -120,7 +140,8 @@ function createGamesService({ getWindow, onGamesChanged }) {
|
|||
startedAt.delete(id)
|
||||
return { ok: false, error: 'not-started' }
|
||||
}
|
||||
running.set(id, res.pid)
|
||||
const entry = running.get(id)
|
||||
if (entry) entry.pid = res.pid
|
||||
sendToWindow(win, 'games:changed')
|
||||
notifyChanged()
|
||||
return { ok: true, pid: res.pid, logFile: res.logFile || null }
|
||||
|
|
@ -134,8 +155,8 @@ function createGamesService({ getWindow, onGamesChanged }) {
|
|||
// Stop always clears the state: whether the process was running, still
|
||||
// starting (kill request for as soon as it has a pid) or already gone.
|
||||
async function kill(id) {
|
||||
const pid = running.get(id)
|
||||
if (pid) killProcessGroup(pid)
|
||||
const entry = running.get(id)
|
||||
if (entry) killProcessGroup(entry.pid)
|
||||
else if (running.has(id)) killRequests.add(id)
|
||||
running.delete(id)
|
||||
startedAt.delete(id)
|
||||
|
|
@ -177,7 +198,7 @@ function createGamesService({ getWindow, onGamesChanged }) {
|
|||
return importGameFromPrefix(prefix, srcDir)
|
||||
}
|
||||
|
||||
return { isRunning, listRunning, start, kill, runToolFor, import: importTo, importFromPrefix, setOnGameExit, openExternally }
|
||||
return { isRunning, listRunning, runningInfo, start, kill, runToolFor, import: importTo, importFromPrefix, setOnGameExit, openExternally }
|
||||
}
|
||||
|
||||
module.exports = { createGamesService, killProcessGroup, sendToWindow }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue