diff --git a/src/extension.ts b/src/extension.ts index f3cb545..9727cc8 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -41,7 +41,7 @@ export function activate(context: vscode.ExtensionContext) { if (most_recent) { follow_cursor.set(most_recent) status_bar.notify( - `$(debug-line-by-line) Now following pid ${most_recent.process.pid}` + `$(debug-line-by-line) Now following pid ${most_recent.pid}` ) } } @@ -255,10 +255,7 @@ export function activate(context: vscode.ExtensionContext) { for (const process of pm) { if (!process.socket) return - logger.info( - 'reloading process on save', - process.process.pid - ) + logger.info('reloading process on save', process.pid) await process.set_autoreload() } } catch (error: any) { diff --git a/src/lib/launch.ts b/src/lib/launch.ts index ecf4f39..e5cd4a3 100644 --- a/src/lib/launch.ts +++ b/src/lib/launch.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode' import path from 'upath' import { sh } from 'puka' +import child_process from 'child_process' import { focus_window, ProcessManager, RenpyProcess } from './process' import { FollowCursor, sync_editor_with_renpy } from './follow_cursor' @@ -196,10 +197,13 @@ export async function launch_renpy({ cancellable: true, }, async (_, cancel) => { + logger.info('executing subshell:', cmd) + const process = child_process.exec(cmd) + logger.info('created process', process.pid) + const rpp = new RenpyProcess({ - cmd, + process, game_root, - socket_port, async message_handler(process, message) { if (message.type === 'current_line') { logger.debug( @@ -216,7 +220,6 @@ export async function launch_renpy({ logger.warn('unhandled message:', message) } }, - context, }) pm.add(running_nonce, rpp) @@ -234,7 +237,7 @@ export async function launch_renpy({ if (pm.length > 1) { status_bar.notify( - `$(debug-line-by-line) Now following pid ${rpp.process.pid}` + `$(debug-line-by-line) Now following pid ${rpp.pid}` ) } } diff --git a/src/lib/process.ts b/src/lib/process.ts index 3c21709..2bbcf2c 100644 --- a/src/lib/process.ts +++ b/src/lib/process.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode' -import child_process from 'node:child_process' +import child_process, { ChildProcess } from 'node:child_process' import { WebSocket } from 'ws' import { get_logger } from './logger' import pidtree from 'pidtree' @@ -45,63 +45,63 @@ export async function focus_window(pid: number) { } } -export class RenpyProcess { - cmd: string +interface RenpyProcessOptions { + process?: ChildProcess + pid?: number message_handler: ( process: RenpyProcess, data: SocketMessage ) => MaybePromise game_root: string - socket_port: number | undefined - process: child_process.ChildProcess +} + +export class RenpyProcess { + process?: child_process.ChildProcess + pid: number + game_root: string + message_handler: ( + process: RenpyProcess, + data: SocketMessage + ) => MaybePromise socket?: WebSocket = undefined dead: boolean = false - output_channel: vscode.OutputChannel + output_channel?: vscode.OutputChannel constructor({ - cmd, + process, + pid, message_handler, game_root, - socket_port, - context, - }: { - cmd: string - message_handler: typeof RenpyProcess.prototype.message_handler - game_root: string - socket_port: number | undefined - context: vscode.ExtensionContext - }) { - this.cmd = cmd + }: RenpyProcessOptions) { + this.process = process + this.pid = (process?.pid ?? pid) as number this.message_handler = message_handler this.game_root = game_root - this.socket_port = socket_port - - logger.info('executing subshell:', cmd) - this.process = child_process.exec(cmd) - - this.output_channel = vscode.window.createOutputChannel( - `Ren'Py Launch and Sync - Process Output (${this.process.pid})` - ) - context.subscriptions.push(this.output_channel) - this.process.stdout!.on('data', (data: string) => - this.output_channel!.append(data) - ) - this.process.stderr!.on('data', (data: string) => - this.output_channel!.append(data) - ) + if (this.process) { + this.output_channel = vscode.window.createOutputChannel( + `Ren'Py Launch and Sync - Process Output (${this.process.pid})` + ) - this.output_channel.appendLine(`process ${this.process.pid} started`) + this.process.stdout!.on('data', (data: string) => + this.output_channel!.append(data) + ) + this.process.stderr!.on('data', (data: string) => + this.output_channel!.append(data) + ) - this.process.on('exit', (code) => { - this.dead = true - logger.info(`process ${this.process.pid} exited with code ${code}`) - this.output_channel!.appendLine( - `process ${this.process.pid} exited with code ${code}` + this.output_channel.appendLine( + `process ${this.process.pid} started` ) - }) - logger.info('created process', this.process.pid) + this.process.on('exit', (code) => { + this.dead = true + logger.info(`process ${this.pid} exited with code ${code}`) + this.output_channel!.appendLine( + `process ${this.pid} exited with code ${code}` + ) + }) + } } async wait_for_socket(timeout_ms: number): Promise { @@ -133,7 +133,11 @@ export class RenpyProcess { } kill() { - this.process.kill() + process.kill(this.pid, 9) // SIGKILL, bypasses "are you sure" dialog + } + + dispose() { + this.output_channel?.dispose() } async ipc(message: SocketMessage): Promise { @@ -202,12 +206,12 @@ export class ProcessManager { } async add(id: number, process: RenpyProcess) { - if (!process.process.pid) throw new Error('no pid in process') + if (!process.pid) throw new Error('no pid in process') this.processes.set(id, process) - process.process.on('exit', (code) => { - if (!process.process.pid) throw new Error('no pid in process') + process.process!.on('exit', (code) => { + if (!process.pid) throw new Error('no pid in process') this.processes.delete(id) @@ -219,7 +223,7 @@ export class ProcessManager { 'Logs' ) .then((selected) => { - if (selected === 'Logs') process.output_channel.show() + if (selected === 'Logs') process.output_channel?.show() }) } @@ -236,13 +240,17 @@ export class ProcessManager { } kill_all() { - for (const { process } of this) { - process.kill(9) // SIGKILL, bypasses "are you sure" dialog + for (const { kill } of this) { + kill() } } dispose() { this.kill_all() + + for (const { dispose } of this) { + dispose() + } } get length() { diff --git a/src/lib/socket.ts b/src/lib/socket.ts index c221346..10dce4f 100644 --- a/src/lib/socket.ts +++ b/src/lib/socket.ts @@ -74,10 +74,8 @@ export async function start_websocket_server({ return } - const ppid = rpp.process.pid - logger.info( - `found new socket connection from process ${ppid}, with nonce ${nonce}` + `found new socket connection from process ${rpp.pid}, with nonce ${nonce}` ) if (rpp.socket) { @@ -88,19 +86,19 @@ export async function start_websocket_server({ rpp.socket = socket socket.on('message', async (data) => { - logger.debug(`websocket (${ppid}) <`, data.toString()) + logger.debug(`websocket (${rpp.pid}) <`, data.toString()) const message = JSON.parse(data.toString()) await rpp.message_handler(rpp, message) }) socket.on('close', () => { - logger.info(`websocket connection closed (pid ${ppid})`) + logger.info(`websocket connection closed (pid ${rpp.pid})`) rpp.socket = undefined }) socket.on('error', (error) => { - logger.error(`websocket error (pid ${ppid})`, error) + logger.error(`websocket error (pid ${rpp.pid})`, error) }) rpp.process.off('exit', process_exit_handler)