From 4a504c7e3d0c50cb90907d7903bc325d7daaf369 Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Thu, 12 Mar 2026 15:40:18 +0800 Subject: feat(instance): finish multi instances system --- packages/ui/src/stores/game-store.ts | 152 +++++++++++++++++++++++++++-------- 1 file changed, 119 insertions(+), 33 deletions(-) (limited to 'packages/ui/src/stores/game-store.ts') diff --git a/packages/ui/src/stores/game-store.ts b/packages/ui/src/stores/game-store.ts index fa0f9f8..7e407de 100644 --- a/packages/ui/src/stores/game-store.ts +++ b/packages/ui/src/stores/game-store.ts @@ -1,49 +1,98 @@ +import { listen, type UnlistenFn } from "@tauri-apps/api/event"; import { toast } from "sonner"; import { create } from "zustand"; -import { getVersions } from "@/client"; +import { + getVersions, + getVersionsOfInstance, + startGame as startGameCommand, + stopGame as stopGameCommand, +} from "@/client"; +import type { Account } from "@/types/bindings/auth"; import type { Version } from "@/types/bindings/manifest"; +interface GameExitedEvent { + instanceId: string; + versionId: string; + exitCode: number | null; + wasStopped: boolean; +} + interface GameState { - // State versions: Version[]; selectedVersion: string; + runningInstanceId: string | null; + runningVersionId: string | null; + launchingInstanceId: string | null; + stoppingInstanceId: string | null; + lifecycleUnlisten: UnlistenFn | null; - // Computed property latestRelease: Version | undefined; + isGameRunning: boolean; - // Actions + initLifecycle: () => Promise; loadVersions: (instanceId?: string) => Promise; startGame: ( - currentAccount: any, + currentAccount: Account | null, openLoginModal: () => void, activeInstanceId: string | null, - setView: (view: any) => void, - ) => Promise; + versionId: string | null, + setView: (view: string) => void, + ) => Promise; + stopGame: (instanceId?: string | null) => Promise; setSelectedVersion: (version: string) => void; setVersions: (versions: Version[]) => void; } export const useGameStore = create((set, get) => ({ - // Initial state versions: [], selectedVersion: "", + runningInstanceId: null, + runningVersionId: null, + launchingInstanceId: null, + stoppingInstanceId: null, + lifecycleUnlisten: null, - // Computed property get latestRelease() { return get().versions.find((v) => v.type === "release"); }, - // Actions + get isGameRunning() { + return get().runningInstanceId !== null; + }, + + initLifecycle: async () => { + if (get().lifecycleUnlisten) { + return; + } + + const unlisten = await listen("game-exited", (event) => { + const { instanceId, versionId, wasStopped } = event.payload; + + set({ + runningInstanceId: null, + runningVersionId: null, + launchingInstanceId: null, + stoppingInstanceId: null, + }); + + if (wasStopped) { + toast.success(`Stopped Minecraft ${versionId} for instance ${instanceId}`); + } else { + toast.info(`Minecraft ${versionId} exited for instance ${instanceId}`); + } + }); + + set({ lifecycleUnlisten: unlisten }); + }, + loadVersions: async (instanceId?: string) => { - console.log("Loading versions for instance:", instanceId); try { - // Ask the backend for known versions (optionally scoped to an instance). - // The Tauri command `get_versions` is expected to return an array of `Version`. - const versions = await getVersions(); + const versions = instanceId + ? await getVersionsOfInstance(instanceId) + : await getVersions(); set({ versions: versions ?? [] }); } catch (e) { console.error("Failed to load versions:", e); - // Keep the store consistent on error by clearing versions. set({ versions: [] }); } }, @@ -52,42 +101,79 @@ export const useGameStore = create((set, get) => ({ currentAccount, openLoginModal, activeInstanceId, + versionId, setView, ) => { - const { selectedVersion } = get(); + const { isGameRunning } = get(); + const targetVersion = versionId ?? get().selectedVersion; if (!currentAccount) { - alert("Please login first!"); + toast.info("Please login first"); openLoginModal(); - return; + return null; } - if (!selectedVersion) { - alert("Please select a version!"); - return; + if (!targetVersion) { + toast.info("Please select a version first"); + return null; } if (!activeInstanceId) { - alert("Please select an instance first!"); + toast.info("Please select an instance first"); setView("instances"); - return; + return null; + } + + if (isGameRunning) { + toast.info("A game is already running"); + return null; } - toast.info("Preparing to launch " + selectedVersion + "..."); + set({ + launchingInstanceId: activeInstanceId, + selectedVersion: targetVersion, + }); + toast.info(`Preparing to launch ${targetVersion}...`); try { - // Note: In production, this would call Tauri invoke - // const msg = await invoke("start_game", { - // instanceId: activeInstanceId, - // versionId: selectedVersion, - // }); - - // Simulate success - await new Promise((resolve) => setTimeout(resolve, 1000)); - toast.success("Game started successfully!"); + const message = await startGameCommand(activeInstanceId, targetVersion); + set({ + launchingInstanceId: null, + runningInstanceId: activeInstanceId, + runningVersionId: targetVersion, + }); + toast.success(message); + return message; } catch (e) { console.error(e); + set({ launchingInstanceId: null }); toast.error(`Error: ${e}`); + return null; + } + }, + + stopGame: async (instanceId) => { + const { runningInstanceId } = get(); + + if (!runningInstanceId) { + toast.info("No running game found"); + return null; + } + + if (instanceId && instanceId !== runningInstanceId) { + toast.info("That instance is not the one currently running"); + return null; + } + + set({ stoppingInstanceId: runningInstanceId }); + + try { + return await stopGameCommand(); + } catch (e) { + console.error("Failed to stop game:", e); + set({ stoppingInstanceId: null }); + toast.error(`Failed to stop game: ${e}`); + return null; } }, -- cgit v1.2.3-70-g09d2 From 08bcd364e5f32be457263f665797c2301379efb9 Mon Sep 17 00:00:00 2001 From: 简律纯 Date: Wed, 18 Mar 2026 11:49:50 +0800 Subject: Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- packages/ui/src/stores/game-store.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'packages/ui/src/stores/game-store.ts') diff --git a/packages/ui/src/stores/game-store.ts b/packages/ui/src/stores/game-store.ts index 7e407de..0d896a1 100644 --- a/packages/ui/src/stores/game-store.ts +++ b/packages/ui/src/stores/game-store.ts @@ -171,9 +171,10 @@ export const useGameStore = create((set, get) => ({ return await stopGameCommand(); } catch (e) { console.error("Failed to stop game:", e); - set({ stoppingInstanceId: null }); toast.error(`Failed to stop game: ${e}`); return null; + } finally { + set({ stoppingInstanceId: null }); } }, -- cgit v1.2.3-70-g09d2 From e8929b27444908b1f90b9ed3a4d52c5fd63fdf8c Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Wed, 18 Mar 2026 12:12:45 +0800 Subject: fix(macro): update ts export macro https://github.com/HydroRoll-Team/DropOut/pull/117#discussion_r2922886584 --- packages/ui/src/client.ts | 8 +------- packages/ui/src/stores/game-store.ts | 8 +------- packages/ui/src/types/bindings/core.ts | 7 +++++++ packages/ui/src/types/bindings/instance.ts | 7 +++++++ src-tauri/src/main.rs | 3 ++- 5 files changed, 18 insertions(+), 15 deletions(-) (limited to 'packages/ui/src/stores/game-store.ts') diff --git a/packages/ui/src/client.ts b/packages/ui/src/client.ts index 0739861..6f354d2 100644 --- a/packages/ui/src/client.ts +++ b/packages/ui/src/client.ts @@ -12,6 +12,7 @@ import type { InstalledForgeVersion, InstalledVersion, Instance, + InstanceRepairResult, JavaCatalog, JavaDownloadInfo, JavaInstallation, @@ -25,13 +26,6 @@ import type { VersionMetadata, } from "@/types"; -export interface InstanceRepairResult { - restoredInstances: number; - removedStaleEntries: number; - createdDefaultActive: boolean; - activeInstanceId: string | null; -} - export function assistantChat(messages: Message[]): Promise { return invoke("assistant_chat", { messages, diff --git a/packages/ui/src/stores/game-store.ts b/packages/ui/src/stores/game-store.ts index 0d896a1..1eaf7e7 100644 --- a/packages/ui/src/stores/game-store.ts +++ b/packages/ui/src/stores/game-store.ts @@ -8,15 +8,9 @@ import { stopGame as stopGameCommand, } from "@/client"; import type { Account } from "@/types/bindings/auth"; +import type { GameExitedEvent } from "@/types/bindings/core"; import type { Version } from "@/types/bindings/manifest"; -interface GameExitedEvent { - instanceId: string; - versionId: string; - exitCode: number | null; - wasStopped: boolean; -} - interface GameState { versions: Version[]; selectedVersion: string; diff --git a/packages/ui/src/types/bindings/core.ts b/packages/ui/src/types/bindings/core.ts index 94e3bde..70cf804 100644 --- a/packages/ui/src/types/bindings/core.ts +++ b/packages/ui/src/types/bindings/core.ts @@ -11,6 +11,13 @@ export type FileInfo = { modified: bigint; }; +export type GameExitedEvent = { + instanceId: string; + versionId: string; + exitCode: number | null; + wasStopped: boolean; +}; + export type GithubRelease = { tagName: string; name: string; diff --git a/packages/ui/src/types/bindings/instance.ts b/packages/ui/src/types/bindings/instance.ts index 2c4f8ae..a8247a9 100644 --- a/packages/ui/src/types/bindings/instance.ts +++ b/packages/ui/src/types/bindings/instance.ts @@ -27,6 +27,13 @@ export type InstanceConfig = { activeInstanceId: string | null; }; +export type InstanceRepairResult = { + restoredInstances: number; + removedStaleEntries: number; + createdDefaultActive: boolean; + activeInstanceId: string | null; +}; + /** * Memory settings override for an instance */ diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 9d4d17c..63287cd 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -68,8 +68,9 @@ impl GameProcessState { } } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, Serialize, TS)] #[serde(rename_all = "camelCase")] +#[ts(export, export_to = "core.ts")] struct GameExitedEvent { instance_id: String, version_id: String, -- cgit v1.2.3-70-g09d2