Replace dasher-pack with unified animation-pack using original Blender bone names
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
@tool
|
||||
class_name McpServerState
|
||||
extends RefCounted
|
||||
|
||||
## State machine for the plugin's server-spawn / adopt / version-verify
|
||||
## lifecycle. Single source of truth — supersedes the boolean-flag thicket
|
||||
## (`_server_started_this_session`, `_awaiting_server_version`,
|
||||
## `_server_version_deadline_ms`, `_connection_blocked`,
|
||||
## `_can_recover_incompatible`, `_refresh_retried`,
|
||||
## `_adoption_watch_deadline_ms`) and the older terminal-only
|
||||
## McpSpawnState string union.
|
||||
##
|
||||
## The integer values matter — they're what `get_server_status()`
|
||||
## surfaces, what the dock pattern-matches on, and what the test suites
|
||||
## assert against. Reordering the enum is a breaking change.
|
||||
##
|
||||
## The transitions are documented in `can_transition()`. The lifecycle
|
||||
## manager calls `set_state()` which:
|
||||
## 1. Validates the transition (logs a warning + no-ops on illegal).
|
||||
## 2. Preserves first-writer-wins among terminal diagnoses so a late
|
||||
## CRASHED from the watch loop can't clobber an earlier
|
||||
## PORT_EXCLUDED from the proactive Windows reservation check.
|
||||
|
||||
## Fresh plugin instance, `_start_server` has not run yet. Default state.
|
||||
const UNINITIALIZED := 0
|
||||
## Process spawned via OS.create_process; watch loop is observing the
|
||||
## SPAWN_GRACE_MS window. Transitions directly to READY (handshake_ack
|
||||
## verifies a compatible version), CRASHED (process died early), or
|
||||
## INCOMPATIBLE (handshake reported a mismatch).
|
||||
const SPAWNING := 1
|
||||
## (slot 2 reserved — keep wire-compat for clients pattern-matching
|
||||
## numeric `editor_state.state` values; do not reuse.)
|
||||
## Server is healthy and version-verified. Happy path. Includes both
|
||||
## "spawned fresh" and "adopted compatible existing server" flavors —
|
||||
## adoption flavor is recorded separately via `McpAdoptionLabel`.
|
||||
const READY := 3
|
||||
## Live server on the HTTP port returned a version that doesn't match
|
||||
## what this plugin expects, OR returned no `handshake_ack` inside the
|
||||
## timeout. Connection is blocked; recovery requires a kill+respawn
|
||||
## click via `recover_incompatible_server`.
|
||||
const INCOMPATIBLE := 4
|
||||
## Spawned process exited inside the SPAWN_GRACE_MS window. Python
|
||||
## traceback went to Godot's output log. Terminal — reload the plugin
|
||||
## or restart the editor to retry.
|
||||
const CRASHED := 5
|
||||
## No server command resolved: no `.venv` Python, no `uvx` on PATH, no
|
||||
## system `godot-ai`. Terminal — install guidance shown in dock.
|
||||
const NO_COMMAND := 6
|
||||
## Windows reserved the HTTP port via Hyper-V / WSL2 / Docker exclusion
|
||||
## range. Caught proactively before bind. Terminal — port picker shown.
|
||||
const PORT_EXCLUDED := 7
|
||||
## HTTP port held by a process we didn't spawn (no matching managed
|
||||
## record). Plugin armed an adoption-confirmation watcher; if the foreign
|
||||
## occupant turns out to be a compatible godot-ai server,
|
||||
## `handle_server_version_verified` transitions to READY. If the
|
||||
## adoption deadline expires without a connection, the watcher self-
|
||||
## disarms but the state stays at FOREIGN_PORT — the dock keeps showing
|
||||
## "port held by another process" until the user reloads. The version-
|
||||
## check seam (separate from the adoption deadline) is what fires
|
||||
## INCOMPATIBLE on a positive-but-mismatched handshake.
|
||||
const FOREIGN_PORT := 8
|
||||
## Static re-entrancy guard fired (`_server_started_this_session` was
|
||||
## already true). The plugin is being re-enabled within the same editor
|
||||
## session; the previous instance still owns the spawn. Terminal — does
|
||||
## NOT block READY paths, just records that this enable cycle no-op'd.
|
||||
const GUARDED := 9
|
||||
## stop_server / prepare_for_update_reload in progress. Transitional —
|
||||
## next state is STOPPED.
|
||||
const STOPPING := 10
|
||||
## stop_server completed; `_server_pid` reset to -1, port may or may
|
||||
## not be free. From here a fresh `start_server` call moves back through
|
||||
## SPAWNING / READY.
|
||||
const STOPPED := 11
|
||||
|
||||
const _NAMES := {
|
||||
UNINITIALIZED: "uninitialized",
|
||||
SPAWNING: "spawning",
|
||||
READY: "ready",
|
||||
INCOMPATIBLE: "incompatible",
|
||||
CRASHED: "crashed",
|
||||
NO_COMMAND: "no_command",
|
||||
PORT_EXCLUDED: "port_excluded",
|
||||
FOREIGN_PORT: "foreign_port",
|
||||
GUARDED: "guarded",
|
||||
STOPPING: "stopping",
|
||||
STOPPED: "stopped",
|
||||
}
|
||||
|
||||
|
||||
## Human-readable label. Used in startup-trace logs and transition
|
||||
## warnings. Falls back to `unknown(<int>)` for unrecognised values so
|
||||
## a future enum addition won't crash the formatter.
|
||||
static func name_of(state: int) -> String:
|
||||
return _NAMES.get(state, "unknown(%d)" % state)
|
||||
|
||||
|
||||
## True for any state the dock should render as a non-OK diagnostic
|
||||
## panel. Used as the "should we hide the spawn-failure panel?" gate.
|
||||
static func is_terminal_diagnosis(state: int) -> bool:
|
||||
return (
|
||||
state == CRASHED
|
||||
or state == NO_COMMAND
|
||||
or state == PORT_EXCLUDED
|
||||
or state == INCOMPATIBLE
|
||||
or state == FOREIGN_PORT
|
||||
)
|
||||
|
||||
|
||||
## True only for READY. Other "ok-ish" states (SPAWNING) are still in
|
||||
## flight; READY is the only state where the plugin can treat the server
|
||||
## as fully healthy.
|
||||
static func is_healthy(state: int) -> bool:
|
||||
return state == READY
|
||||
|
||||
|
||||
## True when the dock should consider the server unsuitable for client
|
||||
## health checks (incompatible tool surface). Currently just INCOMPATIBLE
|
||||
## — FOREIGN_PORT is transitional and may resolve to READY if the
|
||||
## foreign occupant turns out to speak our handshake.
|
||||
static func blocks_client_health(state: int) -> bool:
|
||||
return state == INCOMPATIBLE
|
||||
|
||||
|
||||
## Transition validation table. Returns true when `from -> to` is a
|
||||
## legal transition the lifecycle manager should accept. Illegal
|
||||
## transitions are silently no-op'd at the call site (with a
|
||||
## `push_warning` log) — this preserves the first-writer-wins contract
|
||||
## that prevents a late CRASHED from the watch loop overwriting an
|
||||
## earlier PORT_EXCLUDED diagnosis.
|
||||
static func can_transition(from: int, to: int) -> bool:
|
||||
if from == to:
|
||||
return true
|
||||
## Stop is always legal — teardown / install reload short-circuits
|
||||
## any in-flight state.
|
||||
if to == STOPPING:
|
||||
return true
|
||||
if to == STOPPED and from == STOPPING:
|
||||
return true
|
||||
## STOPPED can also be reached directly when `_server_pid <= 0` and
|
||||
## stop_server early-returns; treat it as legal from any state to
|
||||
## keep the teardown path forgiving.
|
||||
if to == STOPPED:
|
||||
return true
|
||||
## STOPPED -> any (re-arm via restart paths).
|
||||
if from == STOPPED:
|
||||
return true
|
||||
## GUARDED is sticky for the rest of this enable cycle; only stop is
|
||||
## legal out of it. Already covered by the stop checks above.
|
||||
if from == GUARDED:
|
||||
return false
|
||||
## Terminal diagnoses freeze further forward transitions. Recovery
|
||||
## goes through STOPPING (covered above), so any other target is
|
||||
## rejected — this is the first-writer-wins contract.
|
||||
if (
|
||||
from == CRASHED
|
||||
or from == NO_COMMAND
|
||||
or from == PORT_EXCLUDED
|
||||
or from == INCOMPATIBLE
|
||||
):
|
||||
return false
|
||||
## UNINITIALIZED is the boot state — any target except STOPPING is
|
||||
## reachable directly (start_server's early branches set
|
||||
## terminal states without going through SPAWNING).
|
||||
if from == UNINITIALIZED:
|
||||
return true
|
||||
## In-flight forward transitions.
|
||||
match from:
|
||||
SPAWNING:
|
||||
return (
|
||||
to == READY
|
||||
or to == CRASHED
|
||||
or to == FOREIGN_PORT
|
||||
or to == INCOMPATIBLE
|
||||
)
|
||||
FOREIGN_PORT:
|
||||
return to == READY or to == INCOMPATIBLE
|
||||
READY:
|
||||
## Late incompatibility detection (e.g. version verifier
|
||||
## re-arms after a foreign-port reconnect that turns out
|
||||
## to be incompatible after all).
|
||||
return to == INCOMPATIBLE or to == CRASHED
|
||||
STOPPING:
|
||||
## Recovery rollback: kill-then-respawn paths that fail to
|
||||
## free the port re-latch INCOMPATIBLE (so the dock keeps
|
||||
## the diagnostic UI) or fall back to UNINITIALIZED (clean
|
||||
## baseline for a follow-up `_set_incompatible_server`).
|
||||
## STOPPING -> STOPPED is handled by the early checks above.
|
||||
return to == INCOMPATIBLE or to == UNINITIALIZED
|
||||
return false
|
||||
Reference in New Issue
Block a user