diff --git a/Assets/Tests/Editor/CompilationLockFileServiceTests.cs b/Assets/Tests/Editor/CompilationLockFileServiceTests.cs index 1a5a9e5bb..d4b73f0a5 100644 --- a/Assets/Tests/Editor/CompilationLockFileServiceTests.cs +++ b/Assets/Tests/Editor/CompilationLockFileServiceTests.cs @@ -19,7 +19,6 @@ public void SetUp() [TearDown] public void TearDown() { - _service.DeleteLockFile(); _stateStore.Delete(); } diff --git a/Assets/Tests/Editor/DomainReloadDetectionServiceTests.cs b/Assets/Tests/Editor/DomainReloadDetectionServiceTests.cs index ffbcad0e5..977ed3a86 100644 --- a/Assets/Tests/Editor/DomainReloadDetectionServiceTests.cs +++ b/Assets/Tests/Editor/DomainReloadDetectionServiceTests.cs @@ -24,7 +24,6 @@ public void SetUp() _stateStore = CreateTestStateStore(); _domainReloadDetectionService = new DomainReloadDetectionFileService(_editorSettingsService, _stateStore); UnityCliLoopEditorDomainReloadStateProvider.SetDomainReloadInProgressFromMainThread(false); - _domainReloadDetectionService.DeleteLockFile(); } [TearDown] @@ -32,13 +31,13 @@ public void TearDown() { _editorSettingsService.SaveSettings(_originalSettings); UnityCliLoopEditorDomainReloadStateProvider.SetDomainReloadInProgressFromMainThread(false); - _domainReloadDetectionService.DeleteLockFile(); _stateStore.Delete(); } [Test] - public void RollbackDomainReloadStart_ClearsTemporaryFlagsProviderStateAndLockFile() + public void RollbackDomainReloadStart_ClearsTemporaryFlagsProviderStateAndPublishesFailureState() { + // Verifies rollback clears transient reload state and records a failed readiness phase. const string correlationId = "test-correlation"; UnityCliLoopEditorDomainReloadStateProvider provider = new(); @@ -52,7 +51,6 @@ public void RollbackDomainReloadStart_ClearsTemporaryFlagsProviderStateAndLockFi Assert.That(startedSettings.showReconnectingUI, Is.True); Assert.That(startedSettings.showPostCompileReconnectingUI, Is.True); Assert.That(provider.IsDomainReloadInProgress(), Is.True); - Assert.That(_domainReloadDetectionService.IsLockFilePresent(), Is.True); _domainReloadDetectionService.RollbackDomainReloadStart(correlationId); @@ -64,7 +62,9 @@ public void RollbackDomainReloadStart_ClearsTemporaryFlagsProviderStateAndLockFi Assert.That(rolledBackSettings.showReconnectingUI, Is.False); Assert.That(rolledBackSettings.showPostCompileReconnectingUI, Is.False); Assert.That(provider.IsDomainReloadInProgress(), Is.False); - Assert.That(_domainReloadDetectionService.IsLockFilePresent(), Is.False); + ServerReadinessState state = _stateStore.Read(); + Assert.That(state.Phase, Is.EqualTo("failed")); + Assert.That(state.LastError, Is.Not.Empty); } private static UnityCliLoopEditorSettingsData CloneSettings(UnityCliLoopEditorSettingsData settings) diff --git a/Assets/Tests/Editor/DomainReloadRecoveryUseCaseTests.cs b/Assets/Tests/Editor/DomainReloadRecoveryUseCaseTests.cs index 0681495df..0c6c9d383 100644 --- a/Assets/Tests/Editor/DomainReloadRecoveryUseCaseTests.cs +++ b/Assets/Tests/Editor/DomainReloadRecoveryUseCaseTests.cs @@ -44,8 +44,6 @@ public void TearDown() showPostCompileReconnectingUI = false }); - // Clean up lock file created by ExecuteBeforeDomainReload - _domainReloadDetectionService.DeleteLockFile(); _stateStore.Delete(); } @@ -118,7 +116,6 @@ public void CompleteDomainReload_WhenServerWasNotRunning_ShouldPublishStoppedSta ServerReadinessState state = _stateStore.Read(); Assert.That(state.Phase, Is.EqualTo("stopped")); - Assert.That(_domainReloadDetectionService.IsLockFilePresent(), Is.False); } [Test] @@ -188,7 +185,7 @@ private sealed class TestServerInstance : IUnityCliLoopServerInstance public string Endpoint => "test"; - public void StartServer(bool clearServerStartingLockWhenReady = true) + public void StartServer() { IsRunning = true; } diff --git a/Assets/Tests/Editor/GetLogsUseCaseTests.cs b/Assets/Tests/Editor/GetLogsUseCaseTests.cs index 2410f2ff0..7233ee022 100644 --- a/Assets/Tests/Editor/GetLogsUseCaseTests.cs +++ b/Assets/Tests/Editor/GetLogsUseCaseTests.cs @@ -14,7 +14,6 @@ namespace io.github.hatayama.UnityCliLoop.Tests.Editor /// /// Unit tests for GetLogsUseCase /// Related classes: GetLogsUseCase, LogRetrievalService, LogFilteringService - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - UseCase + Tool Pattern (DDD Integration) /// Test philosophy: Following Kent Beck's TDD and t-wada's testing principles /// [TestFixture] diff --git a/Assets/Tests/Editor/ServerLifecycleContractTests.cs b/Assets/Tests/Editor/ServerLifecycleContractTests.cs index 347ad829d..4f4d93128 100644 --- a/Assets/Tests/Editor/ServerLifecycleContractTests.cs +++ b/Assets/Tests/Editor/ServerLifecycleContractTests.cs @@ -9,30 +9,6 @@ namespace io.github.hatayama.UnityCliLoop.Tests.Editor /// public class ServerLifecycleContractTests { - [Test] - // Verifies that the default startup request releases the startup lock after readiness. - public void ReleaseStartupLockWhenReady_ClearsStartupLock_WhenServerReportsReady() - { - ServerInitializationRequest request = - ServerInitializationRequest.ReleaseStartupLockWhenReady(); - - Assert.That(request.StartupLockReleasePolicy, Is.EqualTo(ServerStartupLockReleasePolicy.ReleaseWhenReady)); - Assert.That(request.ClearStartupLockWhenReady, Is.True); - } - - [Test] - // Verifies that explicit-release startup requests preserve the startup lock for another owner. - public void PreserveStartupLockUntilExplicitRelease_KeepsStartupLock_ForExplicitRelease() - { - ServerInitializationRequest request = - ServerInitializationRequest.PreserveStartupLockUntilExplicitRelease(); - - Assert.That( - request.StartupLockReleasePolicy, - Is.EqualTo(ServerStartupLockReleasePolicy.PreserveUntilExplicitRelease)); - Assert.That(request.ClearStartupLockWhenReady, Is.False); - } - [Test] // Verifies that a successful initialization result carries the application-owned server handle. public void RunningInitializationResult_CarriesServerInstanceAndSuccess() diff --git a/Assets/Tests/Editor/ServerStartingLockServiceTests.cs b/Assets/Tests/Editor/ServerStartingLockServiceTests.cs deleted file mode 100644 index 3dd7890b7..000000000 --- a/Assets/Tests/Editor/ServerStartingLockServiceTests.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System.IO; -using NUnit.Framework; -using UnityEngine; - -using io.github.hatayama.UnityCliLoop.Infrastructure; - -namespace io.github.hatayama.UnityCliLoop.Tests.Editor -{ - /// - /// Test fixture that verifies Server Starting Lock Service behavior. - /// - [TestFixture] - public class ServerStartingLockServiceTests - { - [Test] - public void DeleteOwnedLockFile_WhenOwnershipTokenIsMissing_ShouldPreserveExistingLockFile() - { - string lockFilePath = GetLockFilePath(); - bool hadExistingLockFile = File.Exists(lockFilePath); - string previousLockFileContents = hadExistingLockFile ? File.ReadAllText(lockFilePath) : null; - string createdToken = ServerStartingLockService.CreateLockFile(); - - try - { - ServerStartingLockService.DeleteOwnedLockFile(null); - - Assert.That(File.Exists(lockFilePath), Is.True); - Assert.That(File.ReadAllText(lockFilePath), Is.EqualTo(createdToken)); - } - finally - { - RestoreLockFile(lockFilePath, hadExistingLockFile, previousLockFileContents); - } - } - - [Test] - public void DeleteOwnedLockFile_WhenOwnershipTokenMatches_ShouldDeleteExistingLockFile() - { - string lockFilePath = GetLockFilePath(); - bool hadExistingLockFile = File.Exists(lockFilePath); - string previousLockFileContents = hadExistingLockFile ? File.ReadAllText(lockFilePath) : null; - string createdToken = ServerStartingLockService.CreateLockFile(); - - try - { - ServerStartingLockService.DeleteOwnedLockFile(createdToken); - - Assert.That(File.Exists(lockFilePath), Is.False); - } - finally - { - RestoreLockFile(lockFilePath, hadExistingLockFile, previousLockFileContents); - } - } - - [Test] - public void DeleteOwnedLockFile_WhenNewGenerationRecreatesLockAfterClaim_ShouldPreserveNewLockFile() - { - string lockFilePath = GetLockFilePath(); - bool hadExistingLockFile = File.Exists(lockFilePath); - string previousLockFileContents = hadExistingLockFile ? File.ReadAllText(lockFilePath) : null; - string createdToken = ServerStartingLockService.CreateLockFile(); - string recreatedToken = "recreated-token"; - ServerStartingLockService.OnOwnedLockFileClaimedForDeletionForTests = _ => - { - File.WriteAllText(lockFilePath, recreatedToken); - }; - - try - { - ServerStartingLockService.DeleteOwnedLockFile(createdToken); - - Assert.That(File.Exists(lockFilePath), Is.True); - Assert.That(File.ReadAllText(lockFilePath), Is.EqualTo(recreatedToken)); - } - finally - { - ServerStartingLockService.OnOwnedLockFileClaimedForDeletionForTests = null; - RestoreLockFile(lockFilePath, hadExistingLockFile, previousLockFileContents); - } - } - - private static string GetLockFilePath() - { - return Path.GetFullPath( - Path.Combine(UnityEngine.Application.dataPath, "..", "Temp", "serverstarting.lock")); - } - - private static void RestoreLockFile( - string lockFilePath, - bool hadExistingLockFile, - string previousLockFileContents) - { - if (hadExistingLockFile) - { - File.WriteAllText(lockFilePath, previousLockFileContents); - return; - } - - if (File.Exists(lockFilePath)) - { - File.Delete(lockFilePath); - } - } - } -} diff --git a/Assets/Tests/Editor/ServerStartingLockServiceTests.cs.meta b/Assets/Tests/Editor/ServerStartingLockServiceTests.cs.meta deleted file mode 100644 index 1d99dda28..000000000 --- a/Assets/Tests/Editor/ServerStartingLockServiceTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 7e743998a2abe9640be66cc17fe28d03 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Tests/Editor/UnityCliLoopEditorSettingsRecoveryTests.cs b/Assets/Tests/Editor/UnityCliLoopEditorSettingsRecoveryTests.cs index 181f08e0c..70485a4a6 100644 --- a/Assets/Tests/Editor/UnityCliLoopEditorSettingsRecoveryTests.cs +++ b/Assets/Tests/Editor/UnityCliLoopEditorSettingsRecoveryTests.cs @@ -327,7 +327,7 @@ private sealed class TestServerInstance : IUnityCliLoopServerInstance public string Endpoint => "test"; - public void StartServer(bool clearServerStartingLockWhenReady = true) + public void StartServer() { } diff --git a/Assets/Tests/Editor/UnityCliLoopServerControllerStartupLockTests.cs b/Assets/Tests/Editor/UnityCliLoopServerControllerStartupLockTests.cs index 756e299db..81b66dd49 100644 --- a/Assets/Tests/Editor/UnityCliLoopServerControllerStartupLockTests.cs +++ b/Assets/Tests/Editor/UnityCliLoopServerControllerStartupLockTests.cs @@ -10,32 +10,10 @@ namespace io.github.hatayama.UnityCliLoop.Tests.Editor { /// - /// Test fixture that verifies Unity CLI Loop Server Controller Startup Lock behavior. + /// Test fixture that verifies Unity CLI Loop Server Controller recovery behavior. /// - public class UnityCliLoopServerControllerStartupLockTests + public class UnityCliLoopServerControllerRecoveryTests { - [Test] - public void CreateOptionalServerStartingLock_WhenLockCreationSucceeds_ShouldReturnOwnershipToken() - { - // Tests that optional startup locks return the ownership token when creation succeeds. - UnityCliLoopServerControllerService service = CreateControllerService(); - - string token = service.CreateOptionalServerStartingLock(() => "token-123"); - - Assert.That(token, Is.EqualTo("token-123")); - } - - [Test] - public void CreateOptionalServerStartingLock_WhenLockCreationFails_ShouldContinueWithoutThrowing() - { - // Tests that optional startup locks do not fail server startup when creation fails. - UnityCliLoopServerControllerService service = CreateControllerService(); - - string token = service.CreateOptionalServerStartingLock(() => null); - - Assert.That(token, Is.Null); - } - [Test] public void ScheduleStartupRecovery_WhenCalled_ExposesRecoveryTaskBeforeDeferredActionRuns() { @@ -106,40 +84,6 @@ public async Task ScheduleStartupRecovery_WhenRecoveryIsAsync_KeepsTaskIncomplet Assert.That(service.RecoveryTask, Is.Null); } - [Test] - public async Task StartRecoveryIfNeededAsync_WhenStartupLockExists_ShouldReleaseLockAfterWarmup() - { - // Tests that recovery keeps the startup lock until post-bind warmup has completed. - TestServerInstanceFactory serverInstanceFactory = new(); - UnityCliLoopServerLifecycleRegistryService lifecycleRegistry = - new UnityCliLoopServerLifecycleRegistryService(); - UnityCliLoopEditorSettingsService editorSettingsService = - UnityCliLoopEditorSettingsTestFactory.CreateService(); - ServerReadinessStateStore stateStore = CreateTestStateStore(); - UnityCliLoopServerControllerService service = new( - serverInstanceFactory, - lifecycleRegistry, - new DomainReloadDetectionFileService(editorSettingsService, stateStore), - editorSettingsService, - stateStore, - new TestReadinessProbe()); - string claimedLockPath = null; - ServerStartingLockService.OnOwnedLockFileClaimedForDeletionForTests = path => claimedLockPath = path; - - try - { - await service.StartRecoveryIfNeededAsync(isAfterCompile: true, CancellationToken.None); - - Assert.That(serverInstanceFactory.LastCreated.ClearServerStartingLockWhenReady, Is.False); - Assert.That(claimedLockPath, Is.Not.Null); - } - finally - { - ServerStartingLockService.OnOwnedLockFileClaimedForDeletionForTests = null; - ServerStartingLockService.DeleteLockFile(); - } - } - [Test] public async Task StartRecoveryIfNeededAsync_WhenReadinessSucceeds_ShouldPublishReadyState() { @@ -270,13 +214,10 @@ private sealed class TestServerInstance : IUnityCliLoopServerInstance { public bool IsRunning { get; private set; } - public bool? ClearServerStartingLockWhenReady { get; private set; } - public string Endpoint => "test"; - public void StartServer(bool clearServerStartingLockWhenReady = true) + public void StartServer() { - ClearServerStartingLockWhenReady = clearServerStartingLockWhenReady; IsRunning = true; } diff --git a/Assets/Tests/Editor/UnityCliLoopServerStartupProtectionTests.cs b/Assets/Tests/Editor/UnityCliLoopServerStartupProtectionTests.cs index c6bf900b5..32e691963 100644 --- a/Assets/Tests/Editor/UnityCliLoopServerStartupProtectionTests.cs +++ b/Assets/Tests/Editor/UnityCliLoopServerStartupProtectionTests.cs @@ -53,7 +53,6 @@ public void OnBeforeAssemblyReload_ShouldClearStartupProtectionBeforeRecovery() finally { editorSettingsService.SaveSettings(originalSettings); - new DomainReloadDetectionFileService(editorSettingsService).DeleteLockFile(); service.ClearStartupProtection(); } } @@ -139,7 +138,7 @@ private sealed class TestServerInstance : IUnityCliLoopServerInstance public string Endpoint => "test"; - public void StartServer(bool clearServerStartingLockWhenReady = true) + public void StartServer() { } diff --git a/Packages/docs/ARCHITECTURE_Unity.md b/Packages/docs/ARCHITECTURE_Unity.md deleted file mode 100644 index b9b2bd7e5..000000000 --- a/Packages/docs/ARCHITECTURE_Unity.md +++ /dev/null @@ -1,649 +0,0 @@ -# Unity CLI Loop Unity Editor-Side Architecture - -## 1. Overview - -This document details the architecture of the C# code within the `Packages/src/Editor` directory. This code runs inside the Unity Editor and serves as the bridge between the Unity environment and the external `uloop` CLI. - -### System Architecture Overview - -```mermaid -graph TB - subgraph "1. CLI Caller" - Agent[AI Agent or Developer Shell] - CLI[uloop CLI] - end - - subgraph "2. Unity Editor (Project IPC Server)" - MB[McpBridgeServer
Project IPC Server
McpBridgeServer.cs] - CMD[Tool System
UnityApiHandler.cs] - UI[UnityCliLoopSettingsWindow
GUI
UnityCliLoopSettingsWindow.cs] - API[Unity APIs] - SM[McpSessionManager
McpSessionManager.cs] - end - - Agent -->|executes command| CLI - CLI <-->|Project IPC JSON-RPC| MB - MB <--> CMD - CMD <--> API - MB --> SM - - classDef client fill:#e1f5fe,stroke:#01579b,stroke-width:2px - classDef server fill:#f3e5f5,stroke:#4a148c,stroke-width:2px - classDef bridge fill:#fff3e0,stroke:#e65100,stroke-width:2px - - class Agent,CLI client - class MB server -``` - -### Client-Server Relationship Breakdown - -```mermaid -graph LR - subgraph "Communication Layers" - CLI[uloop CLI
CLIENT] - Unity[Unity Editor
PROJECT IPC SERVER] - end - - CLI -->|"Project IPC JSON-RPC
Port: 8700-9100"| Unity - - classDef client fill:#e1f5fe,stroke:#01579b,stroke-width:2px - classDef server fill:#f3e5f5,stroke:#4a148c,stroke-width:2px - classDef hybrid fill:#fff3e0,stroke:#e65100,stroke-width:2px - - class CLI client - class Unity server -``` - -### Protocol and Communication Details - -```mermaid -sequenceDiagram - participant Agent as AI Agent or Developer - participant CLI as uloop CLI
(CLIENT) - participant Unity as Unity Editor
(PROJECT IPC SERVER)
McpBridgeServer.cs - - Agent->>CLI: uloop command - CLI->>Unity: Project IPC JSON-RPC request - Unity->>CLI: Project IPC JSON-RPC response - CLI->>Agent: command output - - Note over CLI, Unity: Client-Server Roles: - Note over CLI: CLIENT: opens short-lived command sessions - Note over Unity: SERVER: accepts project IPC requests -``` - -### Communication Protocol Summary - -| Component | Role | Protocol | Port | Connection Type | -|-----------|------|----------|------|----------------| -| **uloop CLI** | **CLIENT** | Project IPC JSON-RPC | 8700-9100 | Initiates Unity tool requests | -| **Unity Editor** | **SERVER** | Project IPC JSON-RPC | 8700-9100 | Accepts local project IPC connections | - -### Communication Flow Details - -#### Layer 1: Caller ↔ uloop CLI -- **Protocol**: Command line invocation -- **Transport**: Local process execution -- **Connection**: A caller starts `uloop` commands as needed -- **Lifecycle**: Managed by the caller process - -#### Layer 2: uloop CLI ↔ Unity Editor (Project IPC Protocol) -- **Protocol**: Custom TCP with JSON-RPC 2.0 -- **Transport**: TCP Socket -- **Ports**: UNITY_TCP_PORT environment variable specified port (auto-discovery) -- **Connection**: `uloop` acts as the project IPC client -- **Lifecycle**: Managed per command invocation - -#### Key Architectural Points: -1. **uloop CLI is the external entry point**: Commands are translated into project IPC JSON-RPC requests. -2. **Unity Editor is the project IPC server**: Processes tool requests and executes Unity operations. -3. **Automatic Discovery**: The CLI discovers the matching Unity instance for the current project. - -### TCP/JSON-RPC Communication Specification - -#### Transport Layer -- **Protocol**: TCP/IP over localhost -- **Default Port**: 8700 (configurable via environment variable) -- **Message Format**: JSON-RPC 2.0 compliant -- **Message Framing**: Content-Length headers (RFC-compliant) -- **Dynamic Buffer Management**: Up to 1MB dynamic buffering capacity -- **Fragmented Message Support**: TCP fragmented message reassembly functionality - -#### JSON-RPC 2.0 Message Format - -**Framing Format:** -``` -Content-Length: \r\n\r\n -``` - -**Request Message Example:** -``` -Content-Length: 120 - -{ - "jsonrpc": "2.0", - "id": 1647834567890, - "method": "ping", - "params": { - "Message": "Hello Unity CLI Loop!" - } -} -``` - -**Success Response:** -```json -{ - "jsonrpc": "2.0", - "id": 1647834567890, - "result": { - "Message": "Unity CLI Loop bridge received: Hello Unity CLI Loop!", - "ExecutionTimeMs": 5 - } -} -``` - -**Error Response:** -```json -{ - "jsonrpc": "2.0", - "id": 1647834567890, - "error": { - "code": -32603, - "message": "Tool blocked by security settings", - "data": { - "type": "security_blocked", - "command": "find-gameobjects", - "reason": "GameObject search is disabled" - } - } -} -``` - -#### Connection Lifecycle - -1. **Initial Connection** - - `uloop` CLI connects to Unity `McpBridgeServer` - - TCP socket established on localhost:8700 - - Connection test with ping command - -2. **Command Processing** - - JSON-RPC requests processed through UnityApiHandler - - Security validation via McpSecurityChecker - - Tool execution through UnityCommandRegistry - -3. **Connection Lifecycle** - - Automatic reconnection on connection loss - - Periodic health checks via ping commands - - SafeTimer cleanup on process termination - -#### Error Handling - -- **SecurityBlocked**: Tool blocked by security settings -- **InternalError**: Unity internal processing errors -- **Timeout**: Network timeout (default: 2 minutes) -- **Connection Loss**: Automatic reconnection with exponential backoff - -#### Security Features - -- **localhost-only**: External connections blocked -- **Tool-level Security**: McpSecurityChecker validates each command -- **Configurable Access Control**: Unity Editor security settings -- **Session Management**: Client isolation and state tracking - -Its primary responsibilities are: -1. **Running a Project IPC Server (`McpBridgeServer`)**: Listens for local `uloop` CLI connections to receive tool requests. -2. **Executing Unity Operations**: Processes received tool requests to perform actions within the Unity Editor, such as compiling the project, running tests, or retrieving logs. -3. **Security Management**: Validates and controls tool execution through `McpSecurityChecker` to prevent unauthorized operations. -4. **Session Management**: Maintains server session state through `McpSessionManager`. -5. **Providing a User Interface (`UnityCliLoopSettingsWindow`)**: Offers a GUI within the Unity Editor for developers to manage the CLI setup, skills, security settings, and project IPC server state. -6. **Managing Configuration**: Persists Unity CLI Loop settings and skill installation state for supported AI tools. - -## 2. Core Architectural Principles - -The architecture is built upon several key design principles to ensure robustness, extensibility, and maintainability. - -### 2.1. UseCase + Tool Pattern (DDD Integration) -The system is centered around **Domain-Driven Design** integrated **UseCase + Tool Pattern**. Each action is structured according to DDD principles, with UseCase layer orchestrating business workflows and Application Service layer implementing single functions. - -#### UseCase Layer (Domain Workflow Orchestration) -- **Concrete UseCases**: Coordinate workflows through explicit methods (e.g., `UnityCliLoopServerInitializationUseCase`, `DomainReloadRecoveryUseCase`) -- **Domain/Application Request and Result Values**: UseCases accept request values and return result values owned by the domain or application boundary instead of transport-layer tool DTOs -- **Temporal Cohesion Separation**: Following Martin Fowler's refactoring principles, temporal cohesion is separated into UseCase classes - -#### Application Service Layer (Single Function Implementation) -- **Single Responsibility Enforcement**: Each Application Service implements only one function -- **Unified Service Results**: All services return results via `ServiceResult` -- **Examples**: `CompilationExecutionService`, `LogRetrievalService`, `TestExecutionService` - -#### Tool Layer (CLI Command Interface) -- **`IUnityTool`**: Common interface for all tools exposed through the CLI command dispatcher -- **`AbstractUnityTool`**: Provides type-safe handling of parameters and responses -- **`McpToolAttribute`**: Attribute for automatic tool registration -- **Tool Implementation**: Each tool calls UseCases to execute business logic - -#### Registry and Security -- **`UnityToolRegistry`**: Central registry that discovers and holds all available tools -- **`UnityApiHandler`**: Receives tool names and parameters, looks up tools in registry and executes them -- **`McpSecurityChecker`**: Validates execution permissions based on security settings - -This DDD-integrated architecture provides clear separation between business logic and infrastructure, achieving high extensibility and maintainability. - -### 2.2. Security Architecture -The system implements comprehensive security controls to prevent unauthorized tool execution: - -- **`McpSecurityChecker`**: Central security validation component that checks tool permissions before execution. -- **Attribute-Based Security**: Tools can be decorated with security attributes to define their execution requirements. -- **Default Deny Policy**: Unknown tools are blocked by default to prevent unauthorized operations. -- **Settings-Based Control**: Security policies can be configured through Unity Editor settings interface. - -### 2.3. Session Management -The system maintains robust session management to handle client connections and state: - -- **`McpSessionManager`**: Singleton session manager implemented as `ScriptableSingleton` for domain reload persistence. -- **Client State Tracking**: Maintains connection state, client identification, and session metadata. -- **Domain Reload Resilience**: Session state survives Unity domain reloads through persistent storage. -- **Reconnection Support**: Handles client reconnection scenarios gracefully. - -### 2.4. DDD-Integrated System Architecture - -```mermaid -classDiagram - class CompileUseCase { - -compilationStateService: CompilationStateValidationService - -executionService: CompilationExecutionService - +CompileAsync(UnityCliLoopCompileRequest, CancellationToken): Task~UnityCliLoopCompileResult~ - } - - class UnityCliLoopServerInitializationUseCase { - -securityService: ISecurityValidationService - -startupService: UnityCliLoopServerStartupService - +ExecuteAsync(ServerInitializationRequest, CancellationToken): Task~ServerInitializationResult~ - } - - class ServerInitializationRequest { - +StartupLockReleasePolicy: ServerStartupLockReleasePolicy - +ClearStartupLockWhenReady: bool - } - - class ServerInitializationResult { - +Success: bool - +IsRunning: bool - +Message: string - } - - class CompilationStateValidationService { - +ValidateCompilationState(): ServiceResult~ValidationResult~ - } - - class CompilationExecutionService { - +ExecuteCompilationAsync(bool): Task~ServiceResult~CompilationResult~~ - } - - class IUnityTool { - <> - +ToolName: string - +Description: string - +ParameterSchema: object - +ExecuteAsync(JToken): Task~object~ - } - - class CompileTool { - -useCase: CompileUseCase - +ExecuteAsync(CompileSchema): Task~CompileResponse~ - } - - class McpToolAttribute { - <> - +Description: string - +DisplayDevelopmentOnly: bool - +RequiredSecuritySetting: SecuritySettings - } - - CompileUseCase --> CompilationStateValidationService : uses - CompileUseCase --> CompilationExecutionService : uses - UnityCliLoopServerInitializationUseCase --> ServerInitializationRequest : accepts - UnityCliLoopServerInitializationUseCase --> ServerInitializationResult : returns - IUnityTool <|.. CompileTool : implements - CompileTool --> CompileUseCase : delegates to - CompileTool ..|> McpToolAttribute : uses -``` - -### 2.5. MVP + Helper Architecture for UI - -```mermaid -classDiagram - class UnityCliLoopSettingsWindow { - <> - -model: UnityCliLoopSettingsModel - -view: UnityCliLoopSettingsWindowUI - -eventHandler: UnityCliLoopSettingsWindowEventHandler - -serverOperations: McpServerOperations - +OnEnable() - +OnGUI() - +OnDisable() - } - - class UnityCliLoopSettingsModel { - <> - -serverPort: int - -isServerRunning: bool - -selectedEditor: EditorType - +LoadState() - +SaveState() - +UpdateServerStatus() - } - - class UnityCliLoopSettingsWindowUI { - <> - +DrawServerSection(ViewData) - +DrawConfigSection(ViewData) - +DrawDeveloperTools(ViewData) - } - - class UnityCliLoopSettingsWindowViewData { - <> - +ServerPort: int - +IsServerRunning: bool - +SelectedEditor: EditorType - } - - class UnityCliLoopSettingsWindowEventHandler { - <> - +HandleEditorUpdate() - +HandleServerEvents() - +HandleLogUpdates() - } - - class McpServerOperations { - <> - +StartServer() - +StopServer() - +ValidateServerConfig() - } - - UnityCliLoopSettingsWindow --> UnityCliLoopSettingsModel : manages state - UnityCliLoopSettingsWindow --> UnityCliLoopSettingsWindowUI : delegates rendering - UnityCliLoopSettingsWindow --> UnityCliLoopSettingsWindowEventHandler : delegates events - UnityCliLoopSettingsWindow --> McpServerOperations : delegates operations - UnityCliLoopSettingsWindowUI --> UnityCliLoopSettingsWindowViewData : receives - UnityCliLoopSettingsModel --> UnityCliLoopSettingsWindowViewData : creates -``` - -### 2.6. Schema-Driven and Type-Safe Communication -To avoid manual and error-prone JSON parsing, the system uses a schema-driven approach for commands. - -- **`*Schema.cs` files** (e.g., `CompileSchema.cs`, `GetLogsSchema.cs`): These classes define the expected parameters for a command using simple C# properties. Attributes like `[Description]` and default values are used to automatically generate a JSON Schema for the client. -- **`*Response.cs` files** (e.g., `CompileResponse.cs`): These define the structure of the data returned to the client. -- **`CommandParameterSchemaGenerator.cs`**: This utility uses reflection on the `*Schema.cs` files to generate the parameter schema dynamically, ensuring the C# code is the single source of truth. - -This design eliminates inconsistencies between the server and client and provides strong type safety within the C# code. - -### 2.7. SOLID Principles -- **Single Responsibility Principle (SRP)**: Each class has a well-defined responsibility. - - `McpBridgeServer`: Handles raw TCP communication. - - `McpServerController`: Manages the server's lifecycle and state across domain reloads. - - `McpEditorSettings`: Handles Editor window preference persistence. - - `ToolSkillSynchronizer`: Handles skill installation file updates. - - `JsonRpcProcessor`: Deals exclusively with parsing and formatting JSON-RPC 2.0 messages. - - **UI Layer Examples**: - - `UnityCliLoopSettingsModel`: Manages application state and business logic only. - - `UnityCliLoopSettingsWindowUI`: Handles UI rendering only. - - `UnityCliLoopSettingsWindowEventHandler`: Manages Unity Editor events only. - - `McpServerOperations`: Handles server operations only. -- **Open/Closed Principle (OCP)**: The system is open for extension but closed for modification. The Command Pattern is the prime example; new commands can be added without altering the core execution logic. The MVP + Helper pattern also demonstrates this principle - new functionality can be added by creating new helper classes without modifying existing components. - -### 2.8. MVP + Helper Pattern for UI Architecture -The UI layer implements a sophisticated **MVP (Model-View-Presenter) + Helper Pattern** that evolved from a monolithic 1247-line class into a well-structured, maintainable architecture. - -#### Pattern Components -- **Model (`UnityCliLoopSettingsModel`)**: Contains all application state, configuration data, and business logic. Provides methods for state updates while maintaining encapsulation. Handles persistence through Unity's `SessionState` and `EditorPrefs`. -- **View (`UnityCliLoopSettingsWindowUI`)**: Pure UI rendering component with no business logic. Receives all necessary data through `UnityCliLoopSettingsWindowViewData` transfer objects. -- **Presenter (`UnityCliLoopSettingsWindow`)**: Coordinates between Model and View, handles Unity-specific lifecycle events, and delegates complex operations to specialized helper classes. -- **Helper Classes**: Specialized components that handle specific aspects of functionality: - - Event management (`UnityCliLoopSettingsWindowEventHandler`) - - Server operations (`McpServerOperations`) - - Skill installation services (`ToolSkillSynchronizer`) - -#### Benefits of This Architecture -1. **Separation of Concerns**: Each component has a single, clear responsibility -2. **Testability**: Helper classes can be unit tested independently from Unity Editor context -3. **Maintainability**: Complex logic is broken down into manageable, focused components -4. **Extensibility**: New features can be added through new helper classes without modifying existing code -5. **Reduced Cognitive Load**: Developers can focus on one aspect of functionality at a time - -#### Implementation Guidelines -- **State Management**: All state changes go through the Model layer -- **UI Updates**: View receives data through transfer objects, never directly accesses Model -- **Complex Operations**: Delegate to appropriate helper classes rather than implementing in Presenter -- **Event Handling**: Isolate all Unity Editor event management in dedicated EventHandler - -### 2.9. Domain Reload Resilience (UseCase Integration) -A significant challenge in the Unity Editor is the "domain reload," which resets the application's state. The DDD-integrated architecture handles this gracefully at the UseCase level: - -#### Domain Reload Recovery UseCase -- **`DomainReloadRecoveryUseCase`**: Orchestrates the entire domain reload workflow -- **`DomainReloadDetectionService`**: Detects and determines domain reload state -- **`SessionRecoveryService`**: Handles session state preservation and restoration - -#### McpServerController Integration -- **`McpServerController`**: Uses `[InitializeOnLoad]` to hook into Editor lifecycle events -- **UseCase Invocation**: Executes UseCases in `OnBeforeAssemblyReload` and `OnAfterAssemblyReload` -- **`AssemblyReloadEvents`**: Delegates pre/post reload processing to UseCases -- **`SessionState`**: Domain reload data persistence (managed by UseCases) - -#### Orchestrated Workflow -1. **Before Reload**: `DomainReloadRecoveryUseCase.ExecuteBeforeDomainReload()` saves server state -2. **After Reload**: `DomainReloadRecoveryUseCase.ExecuteAfterDomainReloadAsync()` restores state - -This UseCase integration ensures domain reload processing is managed as a single business workflow, improving maintainability and reliability. - -## 3. Implemented UseCases and Tools - -The system currently implements 12 production-ready features using **Domain-Driven Design** architecture with **UseCase + Tool Pattern**. Each feature provides business workflow orchestration through UseCases, single-function implementation through Application Services, and CLI-facing tool commands: - -### 3.1. Core System UseCases and Tools -- **`PingTool`**: Connection health check and latency testing -- **`CompileUseCase` + `CompileTool`**: Compilation state validation and execution separated by Application Services, with detailed error reporting -- **`ClearConsoleTool`**: Unity Console log clearing with confirmation -- **`GetCommandDetailsTool`**: Tool introspection and metadata retrieval - -### 3.2. Information Retrieval UseCases and Tools -- **`GetLogsUseCase` + `GetLogsTool`**: Log retrieval and filtering separated by Application Services, with type selection -- **`GetHierarchyUseCase` + `GetHierarchyTool`**: Scene hierarchy information collection and export with component information -- **`GetMenuItemsUseCase` + `GetMenuItemsTool`**: Unity menu item discovery and metadata collection -- **`GetProviderDetailsUseCase` + `GetProviderDetailsTool`**: Unity Search provider information collection - -### 3.3. GameObject and Scene UseCases and Tools -- **`FindGameObjectsUseCase` + `FindGameObjectsTool`**: Multi-criteria search logic orchestrated by UseCase, advanced GameObject search -- **`UnitySearchUseCase` + `UnitySearchTool`**: Unity Search API integrated unified search across assets, scenes, and project resources - -### 3.4. Execution UseCases and Tools -- **`RunTestsUseCase` + `RunTestsTool`**: Test filter creation and execution separated by Application Services, NUnit XML export (security-controlled) -- **`ExecuteMenuItemUseCase` + `ExecuteMenuItemTool`**: Menu item search and execution orchestrated by UseCase, reflection-based execution (security-controlled) - -### 3.5. Security-Controlled UseCases and Tools -Several UseCases and Tools are subject to security restrictions and can be disabled via settings: -- **Test Execution**: `RunTestsUseCase`/`RunTestsTool` requires "Enable Tests Execution" setting -- **Menu Item Execution**: `ExecuteMenuItemUseCase`/`ExecuteMenuItemTool` requires "Allow Menu Item Execution" setting -- **Unknown Tools**: Blocked by default unless explicitly configured - -### 3.6. Server Lifecycle UseCases -- **`UnityCliLoopServerInitializationUseCase`**: Orchestrates server initialization through domain lifecycle request and result values -- **`UnityCliLoopServerShutdownUseCase`**: Manages server shutdown through domain lifecycle result values -- **`DomainReloadRecoveryUseCase`**: Completely orchestrates state management before/after domain reloads - -These UseCases are not directly exposed as CLI tools but are called internally by `UnityCliLoopServerController` to manage the system lifecycle. - -## 4. Key Components (Directory Breakdown) - -### `/Server` -This directory contains the core networking and lifecycle management components. -- **`McpBridgeServer.cs`**: The low-level TCP server. It listens on a specified port, accepts client connections, and handles the reading/writing of JSON data using Content-Length framing over the network stream. It operates on a background thread. -- **`FrameParser.cs`**: Specialized class for Content-Length header parsing and validation. Handles frame integrity verification and JSON content extraction. -- **`DynamicBufferManager.cs`**: Dynamic buffer pool management class. Achieves memory efficiency and buffer reuse. Supports dynamic buffering up to 1MB. -- **`MessageReassembler.cs`**: TCP fragmented message reassembly class. Properly handles partially received frames and extracts complete messages. -- **`McpServerController.cs`**: The high-level, static manager for the server. It controls the lifecycle (Start, Stop, Restart) of the `McpBridgeServer` instance. It is the central point for managing state across domain reloads. -- **`McpServerConfig.cs`**: A static class holding constants for server configuration (e.g., default port, buffer sizes). - -### `/Security` -Contains the security infrastructure for command execution control. -- **`McpSecurityChecker.cs`**: Central security validation component that implements permission checking for command execution. Evaluates security attributes and settings to determine if a command should be allowed to execute. - -### `/Api` -This is the heart of the command processing logic. -- **`/Commands`**: Contains the implementation of all supported commands. - - **`/Core`**: The foundational classes for the command system. - - **`IUnityCommand.cs`**: Defines the contract for all commands, including `CommandName`, `Description`, `ParameterSchema`, and the `ExecuteAsync` method. - - **`AbstractUnityCommand.cs`**: The generic base class that simplifies command creation by handling the boilerplate of parameter deserialization and response creation. - - **`UnityCommandRegistry.cs`**: Discovers all classes with the `[McpTool]` attribute and registers them in a dictionary, mapping a command name to its implementation. - - **`McpToolAttribute.cs`**: A simple attribute used to mark a class for automatic registration as a command. - - **Command-specific folders**: Each implemented command has its own folder containing: - - `*Command.cs`: The main command implementation - - `*Schema.cs`: Type-safe parameter definition - - `*Response.cs`: Structured response format - - Commands include: `/Compile`, `/RunTests`, `/GetLogs`, `/Ping`, `/ClearConsole`, `/FindGameObjects`, `/GetHierarchy`, `/GetMenuItems`, `/ExecuteMenuItem`, `/UnitySearch`, `/GetProviderDetails`, `/GetCommandDetails` -- **`JsonRpcProcessor.cs`**: Responsible for parsing incoming JSON strings into `JsonRpcRequest` objects and serializing response objects back into JSON strings, adhering to the JSON-RPC 2.0 specification. -- **`UnityApiHandler.cs`**: The entry point for API calls. It receives the method name and parameters from the `JsonRpcProcessor` and uses the `UnityCommandRegistry` to execute the appropriate command. Integrates with `McpSecurityChecker` for permission validation. - -### `/Core` -Contains core infrastructure components for session and state management. - -#### Session Management -- **`McpSessionManager.cs`**: Singleton session manager implemented as `ScriptableSingleton` that maintains server session metadata and survives domain reloads. - -### `/UI` -Contains the code for the user-facing Editor Window, implemented using the **MVP (Model-View-Presenter) + Helper Pattern**. - -#### Core MVP Components -- **`UnityCliLoopSettingsWindow.cs`**: The **Presenter** layer (503 lines). Acts as the coordinator between the Model and View, handling Unity-specific lifecycle events and user interactions. Delegates complex operations to specialized helper classes. -- **`UnityCliLoopSettingsModel.cs`**: The **Model** layer (470 lines). Manages all application state, persistence, and business logic. Contains UI state, server configuration, and provides methods for state updates with proper encapsulation. -- **`UnityCliLoopSettingsWindowUI.cs`**: The **View** layer. Handles pure UI rendering logic, completely separated from business logic. Receives data through `UnityCliLoopSettingsWindowViewData` and renders the interface. -- **`UnityCliLoopSettingsWindowViewData.cs`**: Data transfer object that carries all necessary information from the Model to the View, ensuring clean separation of concerns. - -#### Specialized Helper Classes -- **`UnityCliLoopSettingsWindowEventHandler.cs`**: Manages Unity Editor events. Handles `EditorApplication.update`, `McpCommunicationLogger.OnLogUpdated`, server lifecycle events, and state change detection. Completely isolates event management logic from the main window. -- **`McpServerOperations.cs`**: Handles complex server operations (131 lines). Contains server validation, starting, and stopping logic. Supports both user-interactive and internal operation modes with comprehensive error handling. -- **`McpCommunicationLog.cs`**: Manages the in-memory and `SessionState`-backed log of requests and responses displayed in the "Developer Tools" section of the window. - -#### Architectural Benefits -This MVP + Helper pattern provides: -- **Single Responsibility**: Each class has one clear, focused responsibility -- **Testability**: Helper classes can be unit tested independently -- **Maintainability**: Complex logic is separated into specialized, manageable components -- **Extensibility**: New features can be added by creating new helper classes without modifying existing code -- **Reduced Complexity**: The main Presenter went from 1247 lines to 503 lines (59% reduction) through proper responsibility distribution - -### `/Config` -Manages Unity CLI Loop Editor settings, tool access settings, skill installation, and project path resolution. -- **`McpEditorSettings.cs`**: Persists Editor window state and setup preferences. -- **`ULoopSettings.cs`**: Persists CLI-related setup and security-adjacent settings. -- **`ToolSettings.cs`**: Stores per-tool access settings used by the security layer. -- **`ToolSkillSynchronizer.cs`**: Installs generated skill files into supported AI tool directories. -- **`UnityMcpPathResolver.cs`**: Resolves the Unity project root and package paths. The class name is historical. - -### `/Tools` -Contains higher-level utilities that wrap core Unity Editor functionality. -- **`/ConsoleUtility` & `/ConsoleLogFetcher`**: A set of classes, primarily `ConsoleLogRetriever`, that use reflection to access Unity's internal console log entries. This allows the `getlogs` command to retrieve logs with specific types and filters. -- **`/TestRunner`**: Contains the logic for executing Unity tests. - - **`PlayModeTestExecuter.cs`**: A key class that handles the complexity of running PlayMode tests, which involves disabling domain reloads (`DomainReloadDisableScope`) to ensure the `async` task can complete successfully. - - **`NUnitXmlResultExporter.cs`**: Formats test results into NUnit-compatible XML files. -- **`/Util`**: General-purpose utilities. - - **`CompileController.cs`**: Wraps the `CompilationPipeline` API to provide a simple `async` interface for compiling the project. - -### `/Utils` -Contains low-level, general-purpose helper classes. -- **`MainThreadSwitcher.cs`**: A crucial utility that provides an `awaitable` object to switch execution from a background thread (like the TCP server's) back to Unity's main thread. This is essential because most Unity APIs can only be called from the main thread. -- **`EditorDelay.cs`**: A custom, `async/await`-compatible implementation of a frame-based delay, useful for waiting a few frames for the Editor to reach a stable state, especially after domain reloads. -- **`VibeLogger.cs`**: AI-friendly structured logger for Unity CLI Loop diagnostics. - -## 5. Key Workflows - -### 5.1. UseCase + Tool Execution Flow with Security - -```mermaid -sequenceDiagram - box CLI CLIENT - participant CLI as uloop CLI - end - - box Unity TCP SERVER - participant MB as McpBridgeServer
McpBridgeServer.cs - participant JP as JsonRpcProcessor
JsonRpcProcessor.cs - participant UA as UnityApiHandler
UnityApiHandler.cs - participant SC as McpSecurityChecker
McpSecurityChecker.cs - participant UR as UnityToolRegistry
UnityToolRegistry.cs - participant AT as AbstractUnityTool
AbstractUnityTool.cs - participant Tool as Concrete Tool
*Tool.cs - participant UC as UseCase
*UseCase.cs - participant AS as Application Service
*Service.cs - end - - CLI->>MB: JSON String - MB->>JP: ProcessRequest(json) - JP->>JP: Deserialize to JsonRpcRequest - JP->>UA: ExecuteToolAsync(name, params) - UA->>SC: ValidateTool(name, params) - alt Security Check Passed - SC-->>UA: Validation Success - UA->>UR: GetTool(name) - UR-->>UA: IUnityTool instance - UA->>AT: ExecuteAsync(JToken) - AT->>AT: Deserialize to Schema - AT->>Tool: ExecuteAsync(Schema) - Tool->>UC: ExecuteAsync(Schema, CancellationToken) - UC->>AS: Call Application Services - AS->>AS: Execute single function - AS-->>UC: ServiceResult - UC-->>Tool: UseCase Response - Tool-->>AT: Tool Response - AT-->>UA: Response - else Security Check Failed - SC-->>UA: Validation Failed - UA-->>UA: Create Error Response - end - UA-->>JP: Response - JP->>JP: Serialize to JSON - JP-->>MB: JSON Response - MB-->>CLI: Send Response -``` - -### 5.2. UI Interaction Flow (MVP + Helper Pattern) -1. **User Interaction**: User interacts with the Unity Editor window (button clicks, field changes, etc.). -2. **Presenter Processing**: `UnityCliLoopSettingsWindow` (Presenter) receives the Unity Editor event. -3. **State Update**: Presenter calls appropriate method on `UnityCliLoopSettingsModel` to update application state. -4. **Complex Operations**: For complex operations (server start/stop, validation), Presenter delegates to specialized helper classes: - - `McpServerOperations` for server-related operations - - `UnityCliLoopSettingsWindowEventHandler` for event management - - `ToolSkillSynchronizer` for skill installation operations -5. **View Data Preparation**: Model state is packaged into `UnityCliLoopSettingsWindowViewData` transfer objects. -6. **UI Rendering**: `UnityCliLoopSettingsWindowUI` receives the transfer objects and renders the interface. -7. **Event Propagation**: `UnityCliLoopSettingsWindowEventHandler` manages Unity Editor events and updates the Model accordingly. -8. **Persistence**: Model automatically handles state persistence through Unity's `SessionState` and `EditorPrefs`. - -This workflow ensures clean separation of concerns while maintaining responsiveness and proper state management throughout the application lifecycle. - -### 5.3. Security Validation Flow - -```mermaid -sequenceDiagram - box Unity TCP SERVER - participant UA as UnityApiHandler
UnityApiHandler.cs - participant SC as McpSecurityChecker
McpSecurityChecker.cs - participant Settings as Security Settings - participant Command as Command Instance
*Command.cs - end - - UA->>SC: ValidateCommand(commandName) - SC->>Settings: Check security policy - alt Command is security-controlled - Settings-->>SC: Security status - alt Security disabled - SC-->>UA: Validation Failed - else Security enabled - SC-->>UA: Validation Success - end - else Command is not security-controlled - SC-->>UA: Validation Success - end - UA->>Command: Execute (if validated) -``` diff --git a/Packages/docs/ARCHITECTURE_Unity.md.meta b/Packages/docs/ARCHITECTURE_Unity.md.meta deleted file mode 100644 index 184b7f710..000000000 --- a/Packages/docs/ARCHITECTURE_Unity.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: d87a6e3abaa9d4f0ea03bd3db0eaacc9 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Packages/docs/ARCHITECTURE_Unity_ja.md b/Packages/docs/ARCHITECTURE_Unity_ja.md deleted file mode 100644 index d15589842..000000000 --- a/Packages/docs/ARCHITECTURE_Unity_ja.md +++ /dev/null @@ -1,538 +0,0 @@ -# Unity CLI Loop Unity Editor側アーキテクチャ - -## 1. 概要 - -このドキュメントは、`Packages/src/Editor` ディレクトリ内のC#コードのアーキテクチャについて詳細に説明します。このコードはUnity Editor内で動作し、Unity環境と外部`uloop` CLIとの橋渡しの役割を果たします。 - -### システムアーキテクチャ概要 - -```mermaid -graph TB - subgraph "1. CLI呼び出し元" - Agent[AI Agentまたは開発者Shell] - CLI[uloop CLI] - end - - subgraph "2. Unity Editor(Project IPCサーバー)" - MB[McpBridgeServer
Project IPCサーバー
McpBridgeServer.cs] - CMD[ツールシステム
UnityApiHandler.cs] - UI[UnityCliLoopSettingsWindow
GUI
UnityCliLoopSettingsWindow.cs] - API[Unity APIs] - SM[McpSessionManager
McpSessionManager.cs] - end - - Agent -->|コマンド実行| CLI - CLI <-->|Project IPC JSON-RPC| MB - MB <--> CMD - CMD <--> API - MB --> SM - - classDef client fill:#e1f5fe,stroke:#01579b,stroke-width:2px - classDef server fill:#f3e5f5,stroke:#4a148c,stroke-width:2px - classDef bridge fill:#fff3e0,stroke:#e65100,stroke-width:2px - - class Agent,CLI client - class MB server -``` - -### クライアント・サーバー関係の内訳 - -```mermaid -graph LR - subgraph "通信レイヤー" - CLI[uloop CLI
クライアント] - Unity[Unity Editor
Project IPCサーバー] - end - - CLI -->|"Project IPC JSON-RPC
ポート: 8700-9100"| Unity - - classDef client fill:#e1f5fe,stroke:#01579b,stroke-width:2px - classDef server fill:#f3e5f5,stroke:#4a148c,stroke-width:2px - classDef hybrid fill:#fff3e0,stroke:#e65100,stroke-width:2px - - class CLI client - class Unity server -``` - -### プロトコルと通信詳細 - -```mermaid -sequenceDiagram - participant Agent as AI Agentまたは開発者 - participant CLI as uloop CLI
(クライアント) - participant Unity as Unity Editor
(Project IPCサーバー)
McpBridgeServer.cs - - Agent->>CLI: uloopコマンド - CLI->>Unity: Project IPC JSON-RPCリクエスト - Unity->>CLI: Project IPC JSON-RPCレスポンス - CLI->>Agent: コマンド出力 - - Note over CLI, Unity: クライアント・サーバーの役割: - Note over CLI: クライアント: 短命なコマンドセッションを開く - Note over Unity: サーバー: Project IPCリクエストを受け入れ -``` - -### 通信プロトコルの概要 - -| コンポーネント | 役割 | プロトコル | ポート | 接続タイプ | -|-----------|------|----------|------|----------------| -| **uloop CLI** | **クライアント** | Project IPC JSON-RPC | 8700-9100 | Unityツールリクエストを開始 | -| **Unity Editor** | **サーバー** | Project IPC JSON-RPC | 8700-9100 | ローカルProject IPC接続を受け入れ | - -主な責務は以下の通りです: -1. **Project IPCサーバーの実行(`McpBridgeServer`)**: ローカル`uloop` CLIからの接続をリッスンしてツールリクエストを受信 -2. **Unity操作の実行**: 受信したツールリクエストを処理してUnity Editor内でアクションを実行(プロジェクトのコンパイル、テスト実行、ログ取得など) -3. **セキュリティ管理**: `McpSecurityChecker`を通じてツール実行を検証・制御し、未承認操作を防止 -4. **セッション管理**: `McpSessionManager`を通じてクライアントセッションと接続状態を維持 -5. **ユーザーインターフェースの提供(`UnityCliLoopSettingsWindow`)**: 開発者がCLIセットアップ、スキル、セキュリティ設定、Project IPCサーバー状態を管理するためのUnity Editor内GUI -6. **設定管理**: Unity CLI Loopの設定と対応AIツール向けスキルのインストール状態を永続化 - -## 2. コアアーキテクチャ原則 - -アーキテクチャは、堅牢性、拡張性、保守性を確保するためのいくつかの重要な設計原則に基づいて構築されています。 - -### 2.1. UseCase + ツールパターン (DDD統合) -システムは**Domain-Driven Design**を統合した**UseCase + ツールパターン**を中心としています。各アクションはDDDの原則に従って構造化され、UseCase層でビジネスワークフローを統制し、Application Service層で単一機能を実装しています。 - -#### UseCase層(ドメインワークフロー統制) -- **具体的UseCase**: 明示的なメソッドでワークフローを統制(例:`UnityCliLoopServerInitializationUseCase`、`DomainReloadRecoveryUseCase`) -- **Domain/ApplicationのRequestとResult**: UseCaseはtransport層のtool DTOではなく、DomainまたはApplication境界が所有するrequest/result値を受け渡す -- **時間的結合の分離**: Martin Fowlerのリファクタリング原則に従い、時間的結合をUseCaseクラスに分離 - -#### Application Service層(単一機能実装) -- **単一責任の徹底**: 各Application Serviceは一つの機能のみを実装 -- **Service結果の統一**: すべてのサービスが`ServiceResult`で結果を返す -- **例**: `CompilationExecutionService`、`LogRetrievalService`、`TestExecutionService` - -#### ツール層(CLIコマンドインターフェース) -- **`IUnityTool`**: CLIコマンドディスパッチャー経由で公開されるすべてのツールの共通インターフェース -- **`AbstractUnityTool`**: パラメータとレスポンスの型安全な処理を提供 -- **`McpToolAttribute`**: ツールを自動登録にマークする属性 -- **ツール実装**: 各ツールはUseCaseを呼び出してビジネスロジックを実行 - -#### レジストリとセキュリティ -- **`UnityToolRegistry`**: 使用可能なすべてのツールを発見・保持する中央レジストリ -- **`UnityApiHandler`**: ツール名とパラメータを受け取り、レジストリでツールを検索して実行 -- **`McpSecurityChecker`**: セキュリティ設定に基づいた実行権限の検証 - -このDDD統合アーキテクチャにより、ビジネスロジックとインフラストラクチャが明確に分離され、高い拡張性と保守性を実現しています。 - -### 2.2. セキュリティアーキテクチャ -システムは未承認ツール実行を防ぐための包括的なセキュリティ制御を実装しています: - -- **`McpSecurityChecker`**: 実行前にツール権限をチェックする中央セキュリティ検証コンポーネント -- **属性ベースセキュリティ**: ツールを実行要件を定義するセキュリティ属性で装飾可能 -- **デフォルト拒否ポリシー**: 未知のツールはデフォルトで未承認操作を防ぐためにブロック -- **設定ベース制御**: Unity Editorの設定インターフェースを通じてセキュリティポリシーを設定可能 - -### 2.3. セッション管理 -システムはクライアント接続と状態を処理するための堅牢なセッション管理を維持します: - -- **`McpSessionManager`**: ドメインリロード永続化のための`ScriptableSingleton`として実装されたシングルトンセッションマネージャー -- **クライアント状態追跡**: 接続状態、クライアント識別、セッションメタデータを維持 -- **ドメインリロード耐性**: 永続ストレージを通じてUnityドメインリロードを乗り越える -- **再接続サポート**: クライアント再接続シナリオを優雅に処理 - -### 2.4. DDD統合システムアーキテクチャ - -```mermaid -classDiagram - class CompileUseCase { - -compilationStateService: CompilationStateValidationService - -executionService: CompilationExecutionService - +CompileAsync(UnityCliLoopCompileRequest, CancellationToken): Task~UnityCliLoopCompileResult~ - } - - class UnityCliLoopServerInitializationUseCase { - -securityService: ISecurityValidationService - -startupService: UnityCliLoopServerStartupService - +ExecuteAsync(ServerInitializationRequest, CancellationToken): Task~ServerInitializationResult~ - } - - class ServerInitializationRequest { - +StartupLockReleasePolicy: ServerStartupLockReleasePolicy - +ClearStartupLockWhenReady: bool - } - - class ServerInitializationResult { - +Success: bool - +IsRunning: bool - +Message: string - } - - class CompilationStateValidationService { - +ValidateCompilationState(): ServiceResult~ValidationResult~ - } - - class CompilationExecutionService { - +ExecuteCompilationAsync(bool): Task~ServiceResult~CompilationResult~~ - } - - class IUnityTool { - <> - +ToolName: string - +Description: string - +ParameterSchema: object - +ExecuteAsync(JToken): Task~object~ - } - - class CompileTool { - -useCase: CompileUseCase - +ExecuteAsync(CompileSchema): Task~CompileResponse~ - } - - class McpToolAttribute { - <> - +Description: string - +DisplayDevelopmentOnly: bool - +RequiredSecuritySetting: SecuritySettings - } - - CompileUseCase --> CompilationStateValidationService : uses - CompileUseCase --> CompilationExecutionService : uses - UnityCliLoopServerInitializationUseCase --> ServerInitializationRequest : accepts - UnityCliLoopServerInitializationUseCase --> ServerInitializationResult : returns - IUnityTool <|.. CompileTool : implements - CompileTool --> CompileUseCase : delegates to - CompileTool ..|> McpToolAttribute : uses -``` - -### 2.5. UI用MVP + Helperアーキテクチャ - -```mermaid -classDiagram - class UnityCliLoopSettingsWindow { - <> - -model: UnityCliLoopSettingsModel - -view: UnityCliLoopSettingsWindowUI - -eventHandler: UnityCliLoopSettingsWindowEventHandler - -serverOperations: McpServerOperations - +OnEnable() - +OnGUI() - +OnDisable() - } - - class UnityCliLoopSettingsModel { - <> - -serverPort: int - -isServerRunning: bool - -selectedEditor: EditorType - +LoadState() - +SaveState() - +UpdateServerStatus() - } - - class UnityCliLoopSettingsWindowUI { - <> - +DrawServerSection(ViewData) - +DrawConfigSection(ViewData) - +DrawDeveloperTools(ViewData) - } - - class UnityCliLoopSettingsWindowViewData { - <> - +ServerPort: int - +IsServerRunning: bool - +SelectedEditor: EditorType - } - - class UnityCliLoopSettingsWindowEventHandler { - <> - +HandleEditorUpdate() - +HandleServerEvents() - +HandleLogUpdates() - } - - class McpServerOperations { - <> - +StartServer() - +StopServer() - +ValidateServerConfig() - } - - UnityCliLoopSettingsWindow --> UnityCliLoopSettingsModel : 状態管理 - UnityCliLoopSettingsWindow --> UnityCliLoopSettingsWindowUI : レンダリング委譲 - UnityCliLoopSettingsWindow --> UnityCliLoopSettingsWindowEventHandler : イベント委譲 - UnityCliLoopSettingsWindow --> McpServerOperations : 操作委譲 - UnityCliLoopSettingsWindowUI --> UnityCliLoopSettingsWindowViewData : 受信 - UnityCliLoopSettingsModel --> UnityCliLoopSettingsWindowViewData : 作成 -``` - -### 2.6. スキーマ駆動型・型安全通信 -手動でエラーの起こりやすいJSON解析を避けるため、システムはコマンドにスキーマ駆動アプローチを使用します。 - -- **`*Schema.cs`ファイル**(例:`CompileSchema.cs`、`GetLogsSchema.cs`): 単純なC#プロパティを使用してコマンドの期待パラメータを定義。`[Description]`などの属性とデフォルト値を使用してクライアント用のJSONスキーマを自動生成 -- **`*Response.cs`ファイル**(例:`CompileResponse.cs`): クライアントに返されるデータの構造を定義 -- **`CommandParameterSchemaGenerator.cs`**: この汎用ユーティリティは`*Schema.cs`ファイルのリフレクションを使用してパラメータスキーマを動的に生成し、C#コードが単一の真実の源であることを保証 - -この設計により、サーバーとクライアント間の不整合を排除し、C#コード内で強力な型安全性を提供します。 - -### 2.7. SOLID原則 -- **単一責任原則(SRP)**: 各クラスは明確に定義された責任を持ちます。 - - `McpBridgeServer`: 生のTCP通信を処理 - - `McpServerController`: サーバーのライフサイクルとドメインリロード間の状態を管理 - - `McpEditorSettings`: Editor Window設定の永続化を処理 - - `ToolSkillSynchronizer`: スキルインストールファイルの更新を処理 - - `JsonRpcProcessor`: JSON-RPC 2.0メッセージの解析と整形のみを扱う - - **UIレイヤーの例**: - - `UnityCliLoopSettingsModel`: アプリケーション状態とビジネスロジックのみを管理 - - `UnityCliLoopSettingsWindowUI`: UIレンダリングのみを処理 - - `UnityCliLoopSettingsWindowEventHandler`: Unity Editorイベントのみを管理 - - `McpServerOperations`: サーバー操作のみを処理 -- **開放閉鎖原則(OCP)**: システムは拡張に対して開かれていますが、変更に対して閉じています。コマンドパターンが主要例です。新しいコマンドをコア実行ロジックを変更することなく追加できます。MVP + Helperパターンもこの原則を実証しています - 既存のコンポーネントを変更することなく新しいヘルパークラスを作成することで新機能を追加できます。 - -### 2.8. UI用MVP + Helperパターン -UIレイヤーは、1247行のモノリシッククラスから構造化された保守可能なアーキテクチャに進化した洗練された**MVP(Model-View-Presenter) + Helperパターン**を実装しています。 - -#### パターンコンポーネント -- **Model(`UnityCliLoopSettingsModel`)**: すべてのアプリケーション状態、設定データ、ビジネスロジックを含む。カプセル化を維持しながら状態更新のメソッドを提供。UnityのSuryaStateとEditorPrefsを通じて永続化を処理 -- **View(`UnityCliLoopSettingsWindowUI`)**: ビジネスロジックのない純粋なUIレンダリングコンポーネント。`UnityCliLoopSettingsWindowViewData`転送オブジェクトを通じてすべての必要なデータを受信 -- **Presenter(`UnityCliLoopSettingsWindow`)**: ModelとViewを調整し、Unity固有のライフサイクルイベントを処理し、複雑な操作を専門ヘルパークラスに委譲 -- **Helperクラス**: 機能の特定の側面を処理する専門コンポーネント: - - イベント管理(`UnityCliLoopSettingsWindowEventHandler`) - - サーバー操作(`McpServerOperations`) - - スキルインストールサービス(`ToolSkillSynchronizer`) - -#### このアーキテクチャの利点 -1. **関心の分離**: 各コンポーネントが単一の明確な責任を持つ -2. **テスト可能性**: HelperクラスはUnity Editorコンテキストから独立してユニットテスト可能 -3. **保守性**: 複雑なロジックが管理可能で焦点の絞られたコンポーネントに分解 -4. **拡張性**: 既存のコードを変更することなく新しいヘルパークラスで新機能を追加可能 -5. **認知負荷の軽減**: 開発者は一度に機能の一側面に集中可能 - -#### 実装ガイドライン -- **状態管理**: すべての状態変更はModelレイヤーを通じて行う -- **UI更新**: Viewは転送オブジェクトを通じてデータを受信し、Modelに直接アクセスしない -- **複雑な操作**: Presenterで実装するのではなく適切なヘルパークラスに委譲 -- **イベント処理**: すべてのUnity Editorイベント管理を専用EventHandlerに分離 - -### 2.9. ドメインリロードへの耐性(UseCase統合) -Unity Editorの重要な課題は、アプリケーションの状態をリセットする「ドメインリロード」です。DDD統合アーキテクチャではこれをUseCaseレベルで優雅に処理します: - -#### Domain Reload Recovery UseCase -- **`DomainReloadRecoveryUseCase`**: ドメインリロードのワークフロー全体を統制 -- **`DomainReloadDetectionService`**: ドメインリロードの検出と状態判定 -- **`SessionRecoveryService`**: セッション状態の保存と復元 - -#### McpServerController統合 -- **`McpServerController`**: `[InitializeOnLoad]`を使用してEditorライフサイクルイベントにフック -- **UseCase呼び出し**: `OnBeforeAssemblyReload`と`OnAfterAssemblyReload`でUseCaseを実行 -- **`AssemblyReloadEvents`**: リロード前後の処理をUseCaseに委譲 -- **`SessionState`**: ドメインリロード間でのデータ永続化(UseCaseが管理) - -#### 統制されたワークフロー -1. **リロード前**: `DomainReloadRecoveryUseCase.ExecuteBeforeDomainReload()`でサーバー状態を保存 -2. **リロード後**: `DomainReloadRecoveryUseCase.ExecuteAfterDomainReloadAsync()`で状態を復元 - -このUseCase統合により、ドメインリロード処理が単一のビジネスワークフローとして管理され、保守性と信頼性が向上しています。 - -## 3. 実装されたUseCaseとツール - -システムは現在、**Domain-Driven Design**アーキテクチャに基づく**UseCase + ツールパターン**で実装された12の本番対応機能を提供しています。各機能はUseCaseでビジネスワークフローを統制し、Application Serviceで単一機能を実装し、CLI向けツールコマンドとして提供しています: - -### 3.1. コアシステムUseCaseとツール -- **`PingTool`**: 接続ヘルスチェックと遅延テスト -- **`CompileUseCase` + `CompileTool`**: コンパイル状態検証と実行をApplication Serviceで分離、詳細エラーレポート付き -- **`ClearConsoleTool`**: 確認付きUnity Consoleログクリア -- **`GetCommandDetailsTool`**: ツール内省とメタデータ取得 - -### 3.2. 情報取得UseCaseとツール -- **`GetLogsUseCase` + `GetLogsTool`**: ログ取得とフィルタリングをApplication Serviceで分離、型選択付き -- **`GetHierarchyUseCase` + `GetHierarchyTool`**: シーン階層情報収集とコンポーネント情報付きエクスポート -- **`GetMenuItemsUseCase` + `GetMenuItemsTool`**: Unity メニューアイテム発見とメタデータ収集 -- **`GetProviderDetailsUseCase` + `GetProviderDetailsTool`**: Unity Search プロバイダー情報収集 - -### 3.3. GameObjectとシーンUseCaseとツール -- **`FindGameObjectsUseCase` + `FindGameObjectsTool`**: 複数条件検索ロジックをUseCaseで統制、高度なGameObject検索 -- **`UnitySearchUseCase` + `UnitySearchTool`**: Unity Search APIを統合したアセット、シーン、プロジェクトリソースの統合検索 - -### 3.4. 実行UseCaseとツール -- **`RunTestsUseCase` + `RunTestsTool`**: テストフィルタ作成と実行をApplication Serviceで分離、NUnit XMLエクスポート付き(セキュリティ制御) -- **`ExecuteMenuItemUseCase` + `ExecuteMenuItemTool`**: メニューアイテム検索と実行をUseCaseで統制、リフレクション経由実行(セキュリティ制御) - -### 3.5. セキュリティ制御UseCaseとツール -いくつかのUseCaseとツールはセキュリティ制限の対象であり、設定により無効化可能: -- **テスト実行**: `RunTestsUseCase`/`RunTestsTool`は「テスト実行有効化」設定が必要 -- **メニューアイテム実行**: `ExecuteMenuItemUseCase`/`ExecuteMenuItemTool`は「メニューアイテム実行許可」設定が必要 -- **未知のツール**: 明示的に設定されない限りデフォルトでブロック - -### 3.6. サーバーライフサイクルUseCase -- **`UnityCliLoopServerInitializationUseCase`**: Domainのライフサイクルrequest/result値を使ってサーバー初期化を統制 -- **`UnityCliLoopServerShutdownUseCase`**: Domainのライフサイクルresult値を使ってサーバー終了を管理 -- **`DomainReloadRecoveryUseCase`**: ドメインリロード前後の状態管理を完全に統制 - -これらのUseCaseはCLI向けツールコマンドとして直接公開されず、`UnityCliLoopServerController`の内部で呼び出されてシステムのライフサイクルを管理しています。 - -## 4. 主要コンポーネント(ディレクトリ別) - -### `/Server` -このディレクトリにはコアネットワーキングとライフサイクル管理コンポーネントが含まれています。 -- **`McpBridgeServer.cs`**: 低レベルTCPサーバー。指定ポートをリッスンし、クライアント接続を受け入れ、ネットワークストリーム上でContent-Lengthフレーミングを使用したJSONデータの読み書きを処理。バックグラウンドスレッドで動作 -- **`FrameParser.cs`**: Content-Lengthヘッダー解析と検証の専門クラス。フレーム整合性検証とJSONコンテンツ抽出を処理 -- **`DynamicBufferManager.cs`**: 動的バッファプール管理クラス。メモリ効率とバッファ再利用を実現。最大1MBまでの動的バッファリングをサポート -- **`MessageReassembler.cs`**: TCP断片化メッセージ再組み立てクラス。部分的に受信されたフレームを適切に処理し、完全なメッセージを抽出 -- **`McpServerController.cs`**: サーバーの高レベル静的マネージャー。`McpBridgeServer`インスタンスのライフサイクル(開始、停止、再起動)を制御。ドメインリロード間の状態管理の中心点 -- **`McpServerConfig.cs`**: サーバー設定の定数を保持する静的クラス(例:デフォルトポート、バッファサイズ) - -### `/Security` -コマンド実行制御のセキュリティインフラストラクチャを含みます。 -- **`McpSecurityChecker.cs`**: コマンド実行の権限チェックを実装する中央セキュリティ検証コンポーネント。セキュリティ属性と設定を評価してコマンドの実行許可を決定 - -### `/Api` -これがコマンド処理ロジックの中心です。 -- **`/Commands`**: サポートされているすべてのコマンドの実装を含む - - **`/Core`**: コマンドシステムの基盤クラス - - **`IUnityCommand.cs`**: `CommandName`、`Description`、`ParameterSchema`、`ExecuteAsync`メソッドを含むすべてのコマンドの契約を定義 - - **`AbstractUnityCommand.cs`**: パラメータのデシリアライゼーションとレスポンス作成の定型文を処理してコマンド作成を簡素化する汎用基底クラス - - **`UnityCommandRegistry.cs`**: `[McpTool]`属性を持つすべてのクラスを発見し、コマンド名をその実装にマッピングする辞書に登録 - - **`McpToolAttribute.cs`**: クラスをコマンドとして自動登録にマークするために使用される単純な属性 - - **コマンド固有フォルダー**: 実装された各コマンドにはそれぞれ以下を含むフォルダーがあります: - - `*Command.cs`: メインコマンド実装 - - `*Schema.cs`: 型安全パラメータ定義 - - `*Response.cs`: 構造化レスポンス形式 - - コマンドには以下が含まれます: `/Compile`、`/RunTests`、`/GetLogs`、`/Ping`、`/ClearConsole`、`/FindGameObjects`、`/GetHierarchy`、`/GetMenuItems`、`/ExecuteMenuItem`、`/UnitySearch`、`/GetProviderDetails`、`/GetCommandDetails` -- **`JsonRpcProcessor.cs`**: 受信JSON文字列を`JsonRpcRequest`オブジェクトに解析し、レスポンスオブジェクトをJSON文字列にシリアライズしてJSON-RPC 2.0仕様に準拠 -- **`UnityApiHandler.cs`**: API呼び出しのエントリーポイント。`JsonRpcProcessor`からメソッド名とパラメータを受け取り、`UnityCommandRegistry`を使用して適切なコマンドを実行。権限検証のために`McpSecurityChecker`と統合 - -### `/Core` -セッションと状態管理のコアインフラストラクチャコンポーネントを含みます。 - -#### セッション管理 -- **`McpSessionManager.cs`**: サーバーセッションメタデータを維持し、ドメインリロードを乗り越える`ScriptableSingleton`として実装されたシングルトンセッションマネージャー - -### `/UI` -**MVP(Model-View-Presenter) + Helperパターン**を使用して実装されたユーザー向けEditorWindowのコードを含みます。 - -#### コアMVPコンポーネント -- **`UnityCliLoopSettingsWindow.cs`**: **Presenter**レイヤー(503行)。ModelとViewの調整役として機能し、Unity固有のライフサイクルイベントとユーザーインタラクションを処理。複雑な操作を専門ヘルパークラスに委譲 -- **`UnityCliLoopSettingsModel.cs`**: **Model**レイヤー(470行)。すべてのアプリケーション状態、永続化、ビジネスロジックを管理。UI状態、サーバー設定を含み、適切なカプセル化で状態更新のメソッドを提供 -- **`UnityCliLoopSettingsWindowUI.cs`**: **View**レイヤー。純粋なUIレンダリングロジックを処理し、ビジネスロジックから完全に分離。`UnityCliLoopSettingsWindowViewData`を通じてデータを受信してインターフェースをレンダリング -- **`UnityCliLoopSettingsWindowViewData.cs`**: ModelからViewにすべての必要な情報を運ぶデータ転送オブジェクト、関心の清潔な分離を保証 - -#### 専門ヘルパークラス -- **`UnityCliLoopSettingsWindowEventHandler.cs`**: Unity Editorイベントを管理。`EditorApplication.update`、`McpCommunicationLogger.OnLogUpdated`、サーバーライフサイクルイベント、状態変更検出を処理。イベント管理ロジックをメインウィンドウから完全に分離 -- **`McpServerOperations.cs`**: 複雑なサーバー操作を処理(131行)。サーバー検証、開始、停止ロジックを含む。包括的エラー処理でユーザーインタラクティブと内部操作モードの両方をサポート -- **`McpCommunicationLog.cs`**: ウィンドウの「開発者ツール」セクションに表示されるリクエストとレスポンスのインメモリと`SessionState`バック付きログを管理 - -#### アーキテクチャの利点 -このMVP + Helperパターンが提供するもの: -- **単一責任**: 各クラスが一つの明確で焦点の絞られた責任を持つ -- **テスト可能性**: HelperクラスはUnity Editorコンテキストから独立してテスト可能 -- **保守性**: 複雑なロジックが専門化された管理可能なコンポーネントに分離 -- **拡張性**: 既存のコードを変更することなく新しいヘルパークラスで新機能を追加可能 -- **複雑性の軽減**: メインPresenterが適切な責任分散により1247行から503行(59%削減)に - -### `/Config` -Unity CLI LoopのEditor設定、ツールアクセス設定、スキルインストール、プロジェクトパス解決を管理します。 -- **`McpEditorSettings.cs`**: Editor Windowの状態とセットアップ設定を永続化 -- **`ULoopSettings.cs`**: CLI関連のセットアップ設定とセキュリティ隣接設定を永続化 -- **`ToolSettings.cs`**: セキュリティ層で使うツールごとのアクセス設定を保存 -- **`ToolSkillSynchronizer.cs`**: 生成されたスキルファイルを対応AIツールのディレクトリにインストール -- **`UnityMcpPathResolver.cs`**: Unityプロジェクトルートとパッケージパスを解決。クラス名は歴史的なもの - -### `/Tools` -コアUnity Editor機能をラップする高レベルユーティリティを含みます。 -- **`/ConsoleUtility` & `/ConsoleLogFetcher`**: 主に`ConsoleLogRetriever`のクラスセットで、リフレクションを使用してUnityの内部コンソールログエントリにアクセス。これにより`getlogs`コマンドが特定の型とフィルターでログを取得可能 -- **`/TestRunner`**: Unity テストを実行するロジックを含む - - **`PlayModeTestExecuter.cs`**: PlayModeテストの実行の複雑さを処理する重要クラス。`async`タスクが正常に完了できるようにドメインリロードを無効化(`DomainReloadDisableScope`)を含む - - **`NUnitXmlResultExporter.cs`**: テスト結果をNUnit互換XMLファイルに整形 -- **`/Util`**: 汎用ユーティリティ - - **`CompileController.cs`**: `CompilationPipeline` APIをラップしてプロジェクトコンパイルの単純な`async`インターフェースを提供 - -### `/Utils` -低レベル汎用ヘルパークラスを含みます。 -- **`MainThreadSwitcher.cs`**: バックグラウンドスレッド(TCPサーバーなど)からUnityのメインスレッドに実行を切り替える`awaitable`オブジェクトを提供する重要ユーティリティ。ほとんどのUnity APIはメインスレッドからのみ呼び出し可能なため不可欠 -- **`EditorDelay.cs`**: フレームベース遅延のカスタム`async/await`互換実装。特にドメインリロード後にEditorが安定状態に達するのを数フレーム待つのに有用 -- **`VibeLogger.cs`**: Unity CLI Loop診断向けのAIフレンドリーな構造化ロガー - -## 5. 主要ワークフロー - -### 5.1. セキュリティ付きUseCase + ツール実行フロー - -```mermaid -sequenceDiagram - box CLI クライアント - participant CLI as uloop CLI - end - - box Unity TCP サーバー - participant MB as McpBridgeServer
McpBridgeServer.cs - participant JP as JsonRpcProcessor
JsonRpcProcessor.cs - participant UA as UnityApiHandler
UnityApiHandler.cs - participant SC as McpSecurityChecker
McpSecurityChecker.cs - participant UR as UnityToolRegistry
UnityToolRegistry.cs - participant AT as AbstractUnityTool
AbstractUnityTool.cs - participant Tool as Concrete Tool
*Tool.cs - participant UC as UseCase
*UseCase.cs - participant AS as Application Service
*Service.cs - end - - CLI->>MB: JSON文字列 - MB->>JP: ProcessRequest(json) - JP->>JP: JsonRpcRequestにデシリアライズ - JP->>UA: ExecuteToolAsync(name, params) - UA->>SC: ValidateTool(name, params) - alt セキュリティチェック通過 - SC-->>UA: 検証成功 - UA->>UR: GetTool(name) - UR-->>UA: IUnityToolインスタンス - UA->>AT: ExecuteAsync(JToken) - AT->>AT: Schemaにデシリアライズ - AT->>Tool: ExecuteAsync(Schema) - Tool->>UC: ExecuteAsync(Schema, CancellationToken) - UC->>AS: 各Application Service呼び出し - AS->>AS: 単一機能実行 - AS-->>UC: ServiceResult - UC-->>Tool: UseCase Response - Tool-->>AT: Tool Response - AT-->>UA: レスポンス - else セキュリティチェック失敗 - SC-->>UA: 検証失敗 - UA-->>UA: エラーレスポンス作成 - end - UA-->>JP: レスポンス - JP->>JP: JSONにシリアライズ - JP-->>MB: JSONレスポンス - MB-->>CLI: レスポンス送信 -``` - -### 5.2. UIインタラクションフロー(MVP + Helperパターン) -1. **ユーザーインタラクション**: ユーザーがUnity Editorウィンドウとインタラクション(ボタンクリック、フィールド変更など) -2. **Presenter処理**: `UnityCliLoopSettingsWindow`(Presenter)がUnity Editorイベントを受信 -3. **状態更新**: Presenterが`UnityCliLoopSettingsModel`で適切なメソッドを呼び出してアプリケーション状態を更新 -4. **複雑な操作**: 複雑な操作(サーバー開始/停止、検証)については、Presenterが専門ヘルパークラスに委譲: - - サーバー関連操作は`McpServerOperations` - - イベント管理は`UnityCliLoopSettingsWindowEventHandler` - - スキルインストール操作は`ToolSkillSynchronizer` -5. **ビューデータ準備**: Model状態が`UnityCliLoopSettingsWindowViewData`転送オブジェクトにパッケージ化 -6. **UIレンダリング**: `UnityCliLoopSettingsWindowUI`が転送オブジェクトを受信してインターフェースをレンダリング -7. **イベント伝播**: `UnityCliLoopSettingsWindowEventHandler`がUnity Editorイベントを管理してModelを相応に更新 -8. **永続化**: ModelがUnityの`SessionState`と`EditorPrefs`を通じて状態永続化を自動処理 - -このワークフローにより、アプリケーションライフサイクル全体で応答性と適切な状態管理を維持しながら関心の清潔な分離を保証します。 - -### 5.3. セキュリティ検証フロー - -```mermaid -sequenceDiagram - box Unity TCP サーバー - participant UA as UnityApiHandler
UnityApiHandler.cs - participant SC as McpSecurityChecker
McpSecurityChecker.cs - participant Settings as セキュリティ設定 - participant Command as コマンドインスタンス
*Command.cs - end - - UA->>SC: ValidateCommand(commandName) - SC->>Settings: セキュリティポリシーチェック - alt コマンドがセキュリティ制御対象 - Settings-->>SC: セキュリティ状態 - alt セキュリティ無効 - SC-->>UA: 検証失敗 - else セキュリティ有効 - SC-->>UA: 検証成功 - end - else コマンドがセキュリティ制御対象外 - SC-->>UA: 検証成功 - end - UA->>Command: 実行(検証済みの場合) -``` diff --git a/Packages/docs/ARCHITECTURE_Unity_ja.md.meta b/Packages/docs/ARCHITECTURE_Unity_ja.md.meta deleted file mode 100644 index 0ef236752..000000000 --- a/Packages/docs/ARCHITECTURE_Unity_ja.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: ddb0bd893b6224c9986dab0777410e68 -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Packages/src/Cli~/dist/darwin-amd64/uloop b/Packages/src/Cli~/dist/darwin-amd64/uloop index 9eb3359f3..2e6bac940 100755 Binary files a/Packages/src/Cli~/dist/darwin-amd64/uloop and b/Packages/src/Cli~/dist/darwin-amd64/uloop differ diff --git a/Packages/src/Cli~/dist/darwin-arm64/uloop b/Packages/src/Cli~/dist/darwin-arm64/uloop index 3d2a05b9e..f45876ec1 100755 Binary files a/Packages/src/Cli~/dist/darwin-arm64/uloop and b/Packages/src/Cli~/dist/darwin-arm64/uloop differ diff --git a/Packages/src/Cli~/dist/windows-amd64/uloop.exe b/Packages/src/Cli~/dist/windows-amd64/uloop.exe index da0d06960..ba1642199 100755 Binary files a/Packages/src/Cli~/dist/windows-amd64/uloop.exe and b/Packages/src/Cli~/dist/windows-amd64/uloop.exe differ diff --git a/Packages/src/Cli~/internal/cli/fix.go b/Packages/src/Cli~/internal/cli/fix.go index ade4b62e0..0632f5a46 100644 --- a/Packages/src/Cli~/internal/cli/fix.go +++ b/Packages/src/Cli~/internal/cli/fix.go @@ -1,18 +1,11 @@ package cli import ( - "fmt" "io" "os" "path/filepath" ) -var staleLockFileNames = []string{ - "compiling.lock", - "domainreload.lock", - "serverstarting.lock", -} - // TODO: Extend fix to remove only project-owned stale IPC sockets after proving the listener is dead. func runFix(projectRoot string, stdout io.Writer, stderr io.Writer) int { cleaned, err := cleanupStaleRecoveryState(projectRoot) @@ -31,16 +24,7 @@ func runFix(projectRoot string, stdout io.Writer, stderr io.Writer) int { } func cleanupStaleRecoveryState(projectRoot string) (int, error) { - cleaned, err := cleanupServerStateFiles(projectRoot) - if err != nil { - return cleaned, err - } - - lockCleaned, err := cleanupStaleLockFiles(projectRoot) - if err != nil { - return cleaned, err - } - return cleaned + lockCleaned, nil + return cleanupServerStateFiles(projectRoot) } func cleanupServerStateFiles(projectRoot string) (int, error) { @@ -65,33 +49,3 @@ func cleanupServerStateFiles(projectRoot string) (int, error) { } return cleaned, nil } - -func cleanupStaleLockFiles(projectRoot string) (int, error) { - cleaned := 0 - tempDirectory := filepath.Join(projectRoot, "Temp") - tempInfo, err := os.Stat(tempDirectory) - if err != nil { - if os.IsNotExist(err) { - return cleaned, nil - } - return cleaned, err - } - if !tempInfo.IsDir() { - return cleaned, fmt.Errorf("temp path is not a directory: %s", tempDirectory) - } - - for _, lockFileName := range staleLockFileNames { - lockFilePath := filepath.Join(tempDirectory, lockFileName) - if _, err := os.Stat(lockFilePath); err != nil { - if !os.IsNotExist(err) { - return cleaned, err - } - continue - } - if err := os.Remove(lockFilePath); err != nil { - return cleaned, err - } - cleaned++ - } - return cleaned, nil -} diff --git a/Packages/src/Cli~/internal/cli/fix_test.go b/Packages/src/Cli~/internal/cli/fix_test.go index 253211ddd..dc11abb30 100644 --- a/Packages/src/Cli~/internal/cli/fix_test.go +++ b/Packages/src/Cli~/internal/cli/fix_test.go @@ -6,75 +6,38 @@ import ( "testing" ) -func TestCleanupStaleLockFilesRemovesKnownLocksOnly(t *testing.T) { +// Verifies that fix cleanup no longer treats project Temp lock hints as recovery state. +func TestCleanupStaleRecoveryStateIgnoresProjectTempLockHints(t *testing.T) { projectRoot := t.TempDir() tempDirectory := filepath.Join(projectRoot, "Temp") if err := os.MkdirAll(tempDirectory, 0o755); err != nil { t.Fatalf("failed to create Temp directory: %v", err) } - - for _, lockFileName := range staleLockFileNames { - if err := os.WriteFile(filepath.Join(tempDirectory, lockFileName), []byte("lock"), 0o644); err != nil { - t.Fatalf("failed to seed lock file: %v", err) - } - } - keepPath := filepath.Join(tempDirectory, "UnityLockfile") - if err := os.WriteFile(keepPath, []byte("keep"), 0o644); err != nil { - t.Fatalf("failed to seed keep file: %v", err) + lockPath := filepath.Join(tempDirectory, "domainreload.lock") + if err := os.WriteFile(lockPath, []byte("lock"), 0o644); err != nil { + t.Fatalf("failed to seed lock hint: %v", err) } - cleaned, err := cleanupStaleLockFiles(projectRoot) + cleaned, err := cleanupStaleRecoveryState(projectRoot) if err != nil { - t.Fatalf("cleanupStaleLockFiles failed: %v", err) - } - - if cleaned != len(staleLockFileNames) { - t.Fatalf("cleaned count mismatch: %d", cleaned) - } - for _, lockFileName := range staleLockFileNames { - if _, err := os.Stat(filepath.Join(tempDirectory, lockFileName)); err == nil { - t.Fatalf("lock file was not removed: %s", lockFileName) - } - } - if _, err := os.Stat(keepPath); err != nil { - t.Fatalf("unrelated lock file was removed: %v", err) + t.Fatalf("cleanupStaleRecoveryState failed: %v", err) } -} -func TestCleanupStaleLockFilesAllowsMissingTempDirectory(t *testing.T) { - cleaned, err := cleanupStaleLockFiles(t.TempDir()) - if err != nil { - t.Fatalf("cleanupStaleLockFiles failed: %v", err) - } if cleaned != 0 { t.Fatalf("cleaned count mismatch: %d", cleaned) } -} - -func TestCleanupStaleLockFilesReturnsUnexpectedStatError(t *testing.T) { - projectRoot := t.TempDir() - tempPath := filepath.Join(projectRoot, "Temp") - if err := os.WriteFile(tempPath, []byte("not a directory"), 0o644); err != nil { - t.Fatalf("failed to seed Temp file: %v", err) - } - - _, err := cleanupStaleLockFiles(projectRoot) - if err == nil { - t.Fatal("expected stat error") + if _, err := os.Stat(lockPath); err != nil { + t.Fatalf("project Temp lock hint was touched: %v", err) } } -// Verifies that fix cleanup removes the state file and older lock hints together. -func TestCleanupStaleRecoveryStateRemovesServerStateAndLegacyLocks(t *testing.T) { +// Verifies that fix cleanup removes server readiness state files. +func TestCleanupStaleRecoveryStateRemovesServerStateFiles(t *testing.T) { projectRoot := t.TempDir() - tempDirectory := filepath.Join(projectRoot, "Temp") statePath := filepath.Join(projectRoot, serverStateRelativePath) if err := os.MkdirAll(filepath.Dir(statePath), 0o755); err != nil { t.Fatalf("failed to create state directory: %v", err) } - if err := os.MkdirAll(tempDirectory, 0o755); err != nil { - t.Fatalf("failed to create Temp directory: %v", err) - } if err := os.WriteFile(statePath, []byte(`{"phase":"failed"}`), 0o644); err != nil { t.Fatalf("failed to seed state file: %v", err) } @@ -84,16 +47,13 @@ func TestCleanupStaleRecoveryStateRemovesServerStateAndLegacyLocks(t *testing.T) if err := os.WriteFile(statePath+serverStateInProgressTempSuffix, []byte(`{"phase":"recovering"}`), 0o644); err != nil { t.Fatalf("failed to seed in-progress temp state file: %v", err) } - if err := os.WriteFile(filepath.Join(tempDirectory, "domainreload.lock"), []byte("lock"), 0o644); err != nil { - t.Fatalf("failed to seed legacy lock file: %v", err) - } cleaned, err := cleanupStaleRecoveryState(projectRoot) if err != nil { t.Fatalf("cleanupStaleRecoveryState failed: %v", err) } - if cleaned != 4 { + if cleaned != 3 { t.Fatalf("cleaned count mismatch: %d", cleaned) } if _, err := os.Stat(statePath); err == nil { @@ -105,7 +65,4 @@ func TestCleanupStaleRecoveryStateRemovesServerStateAndLegacyLocks(t *testing.T) if _, err := os.Stat(statePath + serverStateInProgressTempSuffix); err == nil { t.Fatal("in-progress temporary server state file was not removed") } - if _, err := os.Stat(filepath.Join(tempDirectory, "domainreload.lock")); err == nil { - t.Fatal("legacy lock file was not removed") - } } diff --git a/Packages/src/Editor/Application/CompilationLockService.cs b/Packages/src/Editor/Application/CompilationLockService.cs index 00d9a7fe8..ba5316773 100644 --- a/Packages/src/Editor/Application/CompilationLockService.cs +++ b/Packages/src/Editor/Application/CompilationLockService.cs @@ -3,19 +3,17 @@ namespace io.github.hatayama.UnityCliLoop.Application { - // Port for the compilation lock file used by external CLI processes. /// - /// Defines the Compilation Lock operations required by the owning workflow. + /// Defines the Compilation Readiness operations required by the owning workflow. /// public interface ICompilationLockService { void RegisterForEditorStartup(); - void DeleteLockFile(); } // Static facade retained for Unity callbacks and server cleanup paths outside constructor control. /// - /// Provides Compilation Lock operations for its owning module. + /// Provides Compilation Readiness operations for its owning module. /// public static class CompilationLockService { @@ -33,18 +31,13 @@ public static void RegisterForEditorStartup() Service.RegisterForEditorStartup(); } - public static void DeleteLockFile() - { - Service.DeleteLockFile(); - } - private static ICompilationLockService Service { get { if (ServiceValue == null) { - throw new InvalidOperationException("Unity CLI Loop compilation lock service is not registered."); + throw new InvalidOperationException("Unity CLI Loop compilation readiness service is not registered."); } return ServiceValue; diff --git a/Packages/src/Editor/Application/SessionRecoveryService.cs b/Packages/src/Editor/Application/SessionRecoveryService.cs index 39fdce774..51b089a2e 100644 --- a/Packages/src/Editor/Application/SessionRecoveryService.cs +++ b/Packages/src/Editor/Application/SessionRecoveryService.cs @@ -52,13 +52,6 @@ public async Task RestoreServerStateIfNeededAsync(Cancellation IUnityCliLoopServerInstance currentServer = _recoveryCoordinator.CurrentServer; if (currentServer?.IsRunning == true) { - CompilationLockService.DeleteLockFile(); - _domainReloadDetectionService.DeleteLockFile(); - // Why: only the startup generation that created serverstarting.lock knows whether - // the canonical lock still belongs to it or has already been replaced by a newer - // generation. Why not delete it here: a stale domain-reload recovery path can race - // with an active startup/prewarm sequence and tear down another generation's lock. - if (isAfterCompile) { _editorSettingsService.ClearAfterCompileFlag(); diff --git a/Packages/src/Editor/Application/UnityCliLoopServerApplicationService.cs b/Packages/src/Editor/Application/UnityCliLoopServerApplicationService.cs index ac1395a68..d74fd08e6 100644 --- a/Packages/src/Editor/Application/UnityCliLoopServerApplicationService.cs +++ b/Packages/src/Editor/Application/UnityCliLoopServerApplicationService.cs @@ -13,7 +13,7 @@ public interface IUnityCliLoopServerInstance : IDisposable string Endpoint { get; } - void StartServer(bool clearServerStartingLockWhenReady = true); + void StartServer(); void StopServer(); } diff --git a/Packages/src/Editor/Application/UnityCliLoopServerStartupService.cs b/Packages/src/Editor/Application/UnityCliLoopServerStartupService.cs index 66c9a0636..029532222 100644 --- a/Packages/src/Editor/Application/UnityCliLoopServerStartupService.cs +++ b/Packages/src/Editor/Application/UnityCliLoopServerStartupService.cs @@ -21,13 +21,12 @@ public UnityCliLoopServerStartupService( _editorSettingsService = editorSettingsService ?? throw new System.ArgumentNullException(nameof(editorSettingsService)); } - public ServiceResult StartServer( - ServerInitializationRequest request) + public ServiceResult StartServer() { try { IUnityCliLoopServerInstance server = _serverInstanceFactory.Create(); - server.StartServer(request.ClearStartupLockWhenReady); + server.StartServer(); return ServiceResult.SuccessResult(server); } catch (System.Exception ex) diff --git a/Packages/src/Editor/Application/UseCases/DomainReloadRecoveryUseCase.cs b/Packages/src/Editor/Application/UseCases/DomainReloadRecoveryUseCase.cs index a3705a143..7beea1d85 100644 --- a/Packages/src/Editor/Application/UseCases/DomainReloadRecoveryUseCase.cs +++ b/Packages/src/Editor/Application/UseCases/DomainReloadRecoveryUseCase.cs @@ -10,7 +10,6 @@ namespace io.github.hatayama.UnityCliLoop.Application /// UseCase responsible for temporal cohesion of Domain Reload recovery processing /// Processing sequence: 1. Pre-stop processing, 2. Recovery processing, 3. Pending compile processing /// Related classes: DomainReloadDetectionService, SessionRecoveryService - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - UseCase + Tool Pattern (DDD Integration) /// public class DomainReloadRecoveryUseCase { diff --git a/Packages/src/Editor/Application/UseCases/UnityCliLoopServerInitializationUseCase.cs b/Packages/src/Editor/Application/UseCases/UnityCliLoopServerInitializationUseCase.cs index 8b70ebb6b..5cbd7747f 100644 --- a/Packages/src/Editor/Application/UseCases/UnityCliLoopServerInitializationUseCase.cs +++ b/Packages/src/Editor/Application/UseCases/UnityCliLoopServerInitializationUseCase.cs @@ -22,9 +22,7 @@ public UnityCliLoopServerInitializationUseCase( _startupService = startupService ?? throw new System.ArgumentNullException(nameof(startupService)); } - public Task> ExecuteAsync( - ServerInitializationRequest request, - CancellationToken ct) + public Task> ExecuteAsync(CancellationToken ct) { ct.ThrowIfCancellationRequested(); @@ -39,7 +37,7 @@ public Task> ExecuteAsyn ct.ThrowIfCancellationRequested(); ServiceResult serverResult = - _startupService.StartServer(request); + _startupService.StartServer(); if (!serverResult.Success) { ServerInitializationResult startupFailure = diff --git a/Packages/src/Editor/Domain/DomainReloadDetectionService.cs b/Packages/src/Editor/Domain/DomainReloadDetectionService.cs index ea947cbfc..cd25a8de2 100644 --- a/Packages/src/Editor/Domain/DomainReloadDetectionService.cs +++ b/Packages/src/Editor/Domain/DomainReloadDetectionService.cs @@ -1,6 +1,5 @@ namespace io.github.hatayama.UnityCliLoop.Domain { - // Domain reload is a tool-level lifecycle state, while Unity hooks and lock files stay behind this port. public interface IDomainReloadDetectionService { void RegisterForEditorStartup(); @@ -8,7 +7,5 @@ public interface IDomainReloadDetectionService void CompleteDomainReload(string correlationId); void RollbackDomainReloadStart(string correlationId); bool ShouldShowReconnectingUI(); - void DeleteLockFile(); - bool IsLockFilePresent(); } } diff --git a/Packages/src/Editor/Domain/ServerLifecycleContract.cs b/Packages/src/Editor/Domain/ServerLifecycleContract.cs index 3178ddea5..b30b37170 100644 --- a/Packages/src/Editor/Domain/ServerLifecycleContract.cs +++ b/Packages/src/Editor/Domain/ServerLifecycleContract.cs @@ -2,41 +2,6 @@ namespace io.github.hatayama.UnityCliLoop.Domain { - /// - /// Domain-owned startup lock policies keep server lifecycle contracts independent from Unity tool DTOs. - /// - public enum ServerStartupLockReleasePolicy - { - ReleaseWhenReady = 0, - PreserveUntilExplicitRelease = 1 - } - - /// - /// Request value for server initialization use cases. - /// - public readonly struct ServerInitializationRequest - { - public ServerStartupLockReleasePolicy StartupLockReleasePolicy { get; } - - public bool ClearStartupLockWhenReady => - StartupLockReleasePolicy == ServerStartupLockReleasePolicy.ReleaseWhenReady; - - public ServerInitializationRequest(ServerStartupLockReleasePolicy startupLockReleasePolicy) - { - StartupLockReleasePolicy = startupLockReleasePolicy; - } - - public static ServerInitializationRequest ReleaseStartupLockWhenReady() - { - return new ServerInitializationRequest(ServerStartupLockReleasePolicy.ReleaseWhenReady); - } - - public static ServerInitializationRequest PreserveStartupLockUntilExplicitRelease() - { - return new ServerInitializationRequest(ServerStartupLockReleasePolicy.PreserveUntilExplicitRelease); - } - } - /// /// Result value for server initialization use cases. /// diff --git a/Packages/src/Editor/FirstPartyTools/Common/Console/LogRetrievalService.cs b/Packages/src/Editor/FirstPartyTools/Common/Console/LogRetrievalService.cs index dac4e7ff2..fb1093a74 100644 --- a/Packages/src/Editor/FirstPartyTools/Common/Console/LogRetrievalService.cs +++ b/Packages/src/Editor/FirstPartyTools/Common/Console/LogRetrievalService.cs @@ -6,7 +6,6 @@ namespace io.github.hatayama.UnityCliLoop.FirstPartyTools /// Log retrieval service /// Single function: Retrieve Unity Console logs /// Related classes: LogGetter, GetLogsTool, GetLogsUseCase - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - Application Service Layer (Single Function Implementation) /// public class LogRetrievalService : IUnityCliLoopConsoleLogService { diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompilationExecutionService.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompilationExecutionService.cs index 9a10abf27..efc14b02e 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompilationExecutionService.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompilationExecutionService.cs @@ -7,7 +7,6 @@ namespace io.github.hatayama.UnityCliLoop.FirstPartyTools /// Compilation execution service /// Single function: Execute Unity project compilation /// Related classes: CompileController, CompileUseCase, CompileTool - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - Application Service Layer (Single Function Implementation) /// public class CompilationExecutionService { diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompilationStateValidationService.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompilationStateValidationService.cs index dc1a3b3ad..ec6b1b3d3 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompilationStateValidationService.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompilationStateValidationService.cs @@ -8,7 +8,6 @@ namespace io.github.hatayama.UnityCliLoop.FirstPartyTools /// Compilation state validation service /// Single function: Validate state before compilation execution /// Related classes: CompileTool, CompileUseCase, CompileSessionState - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - Application Service Layer (Single Function Implementation) /// public class CompilationStateValidationService { diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileResponse.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileResponse.cs index bc7cf33bb..73776d37d 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompileResponse.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileResponse.cs @@ -69,7 +69,7 @@ public class CompileResponse : UnityCliLoopToolResponse /// /// Unity project root path (from UnityEngine.Application.dataPath). - /// Set only when WaitForDomainReload=true so that TS/CLI can locate lock files + /// Set only when WaitForDomainReload=true so that the CLI can locate the compile result /// in the correct project even when connected to a remote Unity instance. /// public string ProjectRoot { get; set; } diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileTool.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileTool.cs index f819cce08..ff6d34195 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompileTool.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileTool.cs @@ -9,7 +9,6 @@ namespace io.github.hatayama.UnityCliLoop.FirstPartyTools /// Compile tool handler - Type-safe implementation using Schema and Response /// Handles Unity project compilation with optional force recompile /// Related classes: CompileUseCase, CompilationStateValidationService, CompilationExecutionService - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - UseCase + Tool Pattern (DDD Integration) /// [UnityCliLoopTool] public class CompileTool : UnityCliLoopTool diff --git a/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs b/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs index fc52b1c6e..c5524aeef 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/CompileUseCase.cs @@ -14,7 +14,6 @@ namespace io.github.hatayama.UnityCliLoop.FirstPartyTools /// Handles temporal cohesion for compilation processing /// Processing sequence: 1. Play Mode preparation, 2. Compilation state validation, 3. Compilation execution, 4. Result formatting /// Related classes: CompileTool, PlayModeCompilationPreparationService, CompilationStateValidationService, CompilationExecutionService - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - UseCase + Tool Pattern (DDD Integration) /// public class CompileUseCase : IUnityCliLoopCompilationService { diff --git a/Packages/src/Editor/FirstPartyTools/Compile/PlayModeCompilationPreparationService.cs b/Packages/src/Editor/FirstPartyTools/Compile/PlayModeCompilationPreparationService.cs index ccc312283..20bba10d9 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/PlayModeCompilationPreparationService.cs +++ b/Packages/src/Editor/FirstPartyTools/Compile/PlayModeCompilationPreparationService.cs @@ -6,7 +6,6 @@ namespace io.github.hatayama.UnityCliLoop.FirstPartyTools /// Play Mode compilation preparation service /// Single function: Determine preparation action based on Play Mode state and settings /// Related classes: CompileUseCase, CompilationStateValidationService - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - Application Service Layer (Single Function Implementation) /// public class PlayModeCompilationPreparationService { diff --git a/Packages/src/Editor/FirstPartyTools/Compile/Skill/SKILL.md b/Packages/src/Editor/FirstPartyTools/Compile/Skill/SKILL.md index 278fbf1de..532e640e8 100644 --- a/Packages/src/Editor/FirstPartyTools/Compile/Skill/SKILL.md +++ b/Packages/src/Editor/FirstPartyTools/Compile/Skill/SKILL.md @@ -57,7 +57,7 @@ Diagnose the failure mode before retrying. uloop fix ``` -This removes the Unity CLI Loop readiness state file and any leftover legacy lock files from the Unity project's Temp directory. Then retry `uloop compile`. +This removes stale Unity CLI Loop readiness state files from the Unity project's Temp directory. Then retry `uloop compile`. **Unity Editor not running** (CLI returns a connection failure and no Unity process is alive): diff --git a/Packages/src/Editor/FirstPartyTools/FindGameObjects/FindGameObjectsUseCase.cs b/Packages/src/Editor/FirstPartyTools/FindGameObjects/FindGameObjectsUseCase.cs index 291927f6c..098b14418 100644 --- a/Packages/src/Editor/FirstPartyTools/FindGameObjects/FindGameObjectsUseCase.cs +++ b/Packages/src/Editor/FirstPartyTools/FindGameObjects/FindGameObjectsUseCase.cs @@ -10,7 +10,6 @@ namespace io.github.hatayama.UnityCliLoop.FirstPartyTools /// Responsible for temporal cohesion of GameObject search processing /// Processing sequence: 1. Search criteria validation, 2. GameObject search execution, 3. Result conversion and formatting /// Related classes: FindGameObjectsTool, GameObjectFinderService, ComponentSerializer - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - UseCase + Tool Pattern (DDD Integration) /// public class FindGameObjectsUseCase : IUnityCliLoopGameObjectSearchService { diff --git a/Packages/src/Editor/FirstPartyTools/GetHierarchy/GetHierarchyUseCase.cs b/Packages/src/Editor/FirstPartyTools/GetHierarchy/GetHierarchyUseCase.cs index 5b7c5c828..e45d4ea50 100644 --- a/Packages/src/Editor/FirstPartyTools/GetHierarchy/GetHierarchyUseCase.cs +++ b/Packages/src/Editor/FirstPartyTools/GetHierarchy/GetHierarchyUseCase.cs @@ -10,7 +10,6 @@ namespace io.github.hatayama.UnityCliLoop.FirstPartyTools /// Responsible for temporal cohesion of Unity Hierarchy retrieval processing /// Processing sequence: 1. Hierarchy information retrieval, 2. Data conversion, 3. Response size determination and file output /// Related classes: GetHierarchyTool, HierarchyService, HierarchySerializer, HierarchyResultExporter - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - UseCase + Tool Pattern (DDD Integration) /// public class GetHierarchyUseCase : IUnityCliLoopHierarchyService { diff --git a/Packages/src/Editor/FirstPartyTools/GetLogs/LogFilteringService.cs b/Packages/src/Editor/FirstPartyTools/GetLogs/LogFilteringService.cs index 6ec887d7a..f8d45ce5b 100644 --- a/Packages/src/Editor/FirstPartyTools/GetLogs/LogFilteringService.cs +++ b/Packages/src/Editor/FirstPartyTools/GetLogs/LogFilteringService.cs @@ -7,7 +7,6 @@ namespace io.github.hatayama.UnityCliLoop.FirstPartyTools /// Log filtering service /// Single function: Filter and limit log entries /// Related classes: GetLogsTool, GetLogsUseCase, LogEntry - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - Application Service Layer (Single Function Implementation) /// public class LogFilteringService { diff --git a/Packages/src/Editor/FirstPartyTools/RunTests/RunTestsUseCase.cs b/Packages/src/Editor/FirstPartyTools/RunTests/RunTestsUseCase.cs index 149c48e9e..29ae7c370 100644 --- a/Packages/src/Editor/FirstPartyTools/RunTests/RunTestsUseCase.cs +++ b/Packages/src/Editor/FirstPartyTools/RunTests/RunTestsUseCase.cs @@ -11,7 +11,6 @@ namespace io.github.hatayama.UnityCliLoop.FirstPartyTools /// Handles temporal cohesion for test execution processing /// Processing sequence: 1. Test filter creation, 2. Test execution, 3. Result processing /// Related classes: RunTestsTool, TestFilterCreationService, TestExecutionService - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - UseCase + Tool Pattern (DDD Integration) /// public class RunTestsUseCase : IUnityCliLoopTestExecutionService { diff --git a/Packages/src/Editor/FirstPartyTools/RunTests/TestExecutionService.cs b/Packages/src/Editor/FirstPartyTools/RunTests/TestExecutionService.cs index ac16ec550..ea894d5c4 100644 --- a/Packages/src/Editor/FirstPartyTools/RunTests/TestExecutionService.cs +++ b/Packages/src/Editor/FirstPartyTools/RunTests/TestExecutionService.cs @@ -6,7 +6,6 @@ namespace io.github.hatayama.UnityCliLoop.FirstPartyTools /// Test execution service /// Single function: Execute tests using Unity Test Runner /// Related classes: PlayModeTestExecuter, RunTestsUseCase, RunTestsTool - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - Application Service Layer (Single Function Implementation) /// public class TestExecutionService { diff --git a/Packages/src/Editor/FirstPartyTools/RunTests/TestFilterCreationService.cs b/Packages/src/Editor/FirstPartyTools/RunTests/TestFilterCreationService.cs index 4e680a288..54a98c053 100644 --- a/Packages/src/Editor/FirstPartyTools/RunTests/TestFilterCreationService.cs +++ b/Packages/src/Editor/FirstPartyTools/RunTests/TestFilterCreationService.cs @@ -6,7 +6,6 @@ namespace io.github.hatayama.UnityCliLoop.FirstPartyTools /// Test filter creation service /// Single function: Create filters for test execution /// Related classes: RunTestsTool, RunTestsUseCase, TestExecutionFilter - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - Application Service Layer (Single Function Implementation) /// public class TestFilterCreationService { @@ -28,4 +27,4 @@ public TestExecutionFilter CreateFilter(TestFilterType filterType, string filter }; } } -} \ No newline at end of file +} diff --git a/Packages/src/Editor/Infrastructure/Server/CompilationLockFileService.cs b/Packages/src/Editor/Infrastructure/Server/CompilationLockFileService.cs index 7411b5808..c4b3784f2 100644 --- a/Packages/src/Editor/Infrastructure/Server/CompilationLockFileService.cs +++ b/Packages/src/Editor/Infrastructure/Server/CompilationLockFileService.cs @@ -1,4 +1,3 @@ -using System.IO; using UnityEditor; using UnityEditor.Compilation; @@ -8,14 +7,12 @@ namespace io.github.hatayama.UnityCliLoop.Infrastructure { /// - /// Application service responsible for compilation lock file management. - /// Single responsibility: Create/delete lock file during compilation for CLI detection. - /// Related classes: DomainReloadDetectionService (similar pattern for domain reload) - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - Application Service Layer (Single Function Implementation) + /// Application service responsible for publishing compilation readiness state. + /// Single responsibility: Mark the external readiness state while Unity is compiling. + /// Related classes: DomainReloadDetectionService (similar readiness state publishing) /// public sealed class CompilationLockFileService : ICompilationLockService { - private const string LOCK_FILE_NAME = "compiling.lock"; private readonly ServerReadinessStateStore _stateStore; private ServerReadinessState _stateBeforeCompilation; private string _activeCompilationGenerationId; @@ -25,8 +22,6 @@ internal CompilationLockFileService(ServerReadinessStateStore stateStore = null) _stateStore = stateStore ?? new ServerReadinessStateStore(UnityCliLoopPathResolver.GetProjectRoot()); } - private static string LockFilePath => Path.Combine(UnityEngine.Application.dataPath, "..", "Temp", LOCK_FILE_NAME); - public void RegisterForEditorStartup() { CompilationPipeline.compilationStarted -= OnCompilationStarted; @@ -42,7 +37,6 @@ private void OnCompilationStarted(object context) private void OnCompilationFinished(object context) { - DeleteLockFileCore(); string generationId = _activeCompilationGenerationId; ServerReadinessState stateBeforeCompilation = _stateBeforeCompilation; EditorApplication.delayCall += () => @@ -51,7 +45,6 @@ private void OnCompilationFinished(object context) internal void MarkCompilationStarted() { - CreateLockFile(); _stateBeforeCompilation = _stateStore.Read(); _activeCompilationGenerationId = ServerReadinessStateStore.CreateGenerationId(); _stateStore.Write( @@ -64,7 +57,6 @@ internal void MarkCompilationStarted() internal void MarkCompilationFinished() { - DeleteLockFileCore(); RestoreStateBeforeCompilationIfStillCurrent( _activeCompilationGenerationId, _stateBeforeCompilation); @@ -95,35 +87,5 @@ private void RestoreStateBeforeCompilationIfStillCurrent( _stateStore.Write(stateBeforeCompilation); } - - private static void CreateLockFile() - { - string lockPath = LockFilePath; - string tempDir = Path.GetDirectoryName(lockPath); - - if (!Directory.Exists(tempDir)) - { - return; - } - - File.WriteAllText(lockPath, System.DateTime.UtcNow.ToString("o")); - } - - /// - /// Delete lock file. Called on server startup to handle crash recovery. - /// - public void DeleteLockFile() - { - DeleteLockFileCore(); - } - - private static void DeleteLockFileCore() - { - string lockPath = LockFilePath; - if (File.Exists(lockPath)) - { - File.Delete(lockPath); - } - } } } diff --git a/Packages/src/Editor/Infrastructure/Server/DomainReloadDetectionFileService.cs b/Packages/src/Editor/Infrastructure/Server/DomainReloadDetectionFileService.cs index 58a40359a..b761badbf 100644 --- a/Packages/src/Editor/Infrastructure/Server/DomainReloadDetectionFileService.cs +++ b/Packages/src/Editor/Infrastructure/Server/DomainReloadDetectionFileService.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using UnityEditor; using io.github.hatayama.UnityCliLoop.Application; @@ -9,7 +8,7 @@ namespace io.github.hatayama.UnityCliLoop.Infrastructure { /// - /// Infrastructure implementation that persists Domain Reload detection state through files and editor settings. + /// Infrastructure implementation that persists Domain Reload readiness state through server state and editor settings. /// public sealed class DomainReloadDetectionFileService : IDomainReloadDetectionService { @@ -47,8 +46,6 @@ public void RegisterForEditorStartup() AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload; AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload; - AssemblyReloadEvents.afterAssemblyReload -= OnAfterAssemblyReload; - AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload; } private void OnBeforeAssemblyReload() @@ -58,7 +55,6 @@ private void OnBeforeAssemblyReload() return; } - CreateLockFile(); _stateStore.Write( ServerReadinessPhase.Reloading, ServerReadinessStateStore.CreateGenerationId(), @@ -67,16 +63,6 @@ private void OnBeforeAssemblyReload() null); } - private void OnAfterAssemblyReload() - { - // Lock file is deleted when server startup completes. - // to avoid a gap between domain reload end and server ready - } - - private const string LOCK_FILE_NAME = "domainreload.lock"; - - private static string LockFilePath => Path.Combine(UnityEngine.Application.dataPath, "..", "Temp", LOCK_FILE_NAME); - /// /// Execute Domain Reload start processing /// @@ -90,8 +76,6 @@ public void StartDomainReload(string correlationId, bool serverIsRunning) return; } - // Create lock file for external process detection (e.g., CLI tools) - CreateLockFile(); _stateStore.Write( ServerReadinessPhase.Reloading, correlationId, @@ -148,13 +132,7 @@ public void CompleteDomainReload(string correlationId) return; } - // Lock file is deleted when server startup completes. - // to avoid a gap between domain reload completion and server ready bool serverWillRecover = _editorSettingsService.GetIsServerRunning(); - if (!serverWillRecover) - { - DeleteLockFile(); - } _stateStore.Write( serverWillRecover ? ServerReadinessPhase.Recovering : ServerReadinessPhase.Stopped, @@ -193,7 +171,6 @@ public void RollbackDomainReloadStart(string correlationId) showPostCompileReconnectingUI = false }); UnityCliLoopEditorDomainReloadStateProvider.SetDomainReloadInProgressFromMainThread(false); - DeleteLockFile(); _stateStore.Write( ServerReadinessPhase.Failed, correlationId, @@ -216,40 +193,5 @@ public bool ShouldShowReconnectingUI() { return _editorSettingsService.GetShowReconnectingUI(); } - - private static void CreateLockFile() - { - string lockPath = LockFilePath; - string tempDir = Path.GetDirectoryName(lockPath); - - if (!Directory.Exists(tempDir)) - { - return; - } - - File.WriteAllText(lockPath, System.DateTime.UtcNow.ToString("o")); - } - - /// - /// Delete lock file to signal Domain Reload completion. - /// - public void DeleteLockFile() - { - string lockPath = LockFilePath; - if (File.Exists(lockPath)) - { - File.Delete(lockPath); - } - } - - /// - /// Check if Domain Reload lock file exists. - /// Used by external processes to detect Domain Reload state. - /// - /// True if lock file exists - public bool IsLockFilePresent() - { - return File.Exists(LockFilePath); - } } } diff --git a/Packages/src/Editor/Infrastructure/Server/ServerStartingLockService.cs b/Packages/src/Editor/Infrastructure/Server/ServerStartingLockService.cs deleted file mode 100644 index 8e04a27f2..000000000 --- a/Packages/src/Editor/Infrastructure/Server/ServerStartingLockService.cs +++ /dev/null @@ -1,170 +0,0 @@ -using System; -using System.IO; -using System.Threading; - -using io.github.hatayama.UnityCliLoop.ToolContracts; - -namespace io.github.hatayama.UnityCliLoop.Infrastructure -{ - /// - /// Application service responsible for server starting lock file management. - /// Single responsibility: Create/delete lock file during server startup for CLI detection. - /// Related classes: CompilationLockService, DomainReloadDetectionService (similar patterns) - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - Application Service Layer (Single Function Implementation) - /// - public static class ServerStartingLockService - { - private const string LOCK_FILE_NAME = "serverstarting.lock"; - private const int FILE_OPERATION_RETRY_COUNT = 3; - private const int FILE_OPERATION_RETRY_DELAY_MILLISECONDS = 50; - - private static string LockFilePath => Path.Combine(UnityEngine.Application.dataPath, "..", "Temp", LOCK_FILE_NAME); - - internal static Action OnOwnedLockFileClaimedForDeletionForTests { get; set; } - - /// - /// Create lock file to signal server is starting. - /// - public static string CreateLockFile() - { - string lockPath = LockFilePath; - string tempDir = Path.GetDirectoryName(lockPath); - - if (!Directory.Exists(tempDir)) - { - return null; - } - - string ownershipToken = System.Guid.NewGuid().ToString("N"); - for (int attempt = 0; attempt < FILE_OPERATION_RETRY_COUNT; attempt++) - { - try - { - File.WriteAllText(lockPath, ownershipToken); - return ownershipToken; - } - catch (IOException) - { - } - catch (UnauthorizedAccessException) - { - } - - if (attempt < FILE_OPERATION_RETRY_COUNT - 1) - { - Thread.Sleep(FILE_OPERATION_RETRY_DELAY_MILLISECONDS); - } - } - - VibeLogger.LogWarning("server_starting_lock_create_failed", $"Failed to create lock file: {lockPath}"); - return null; - } - - /// - /// Delete lock file. Called when server startup completes or on crash recovery. - /// - public static void DeleteLockFile(string ownershipToken = null) - { - string lockPath = LockFilePath; - if (File.Exists(lockPath)) - { - if (string.IsNullOrEmpty(ownershipToken)) - { - TryDeleteLockFile(lockPath); - return; - } - - string claimedLockPath = TryClaimOwnedLockFileForDeletion(lockPath, ownershipToken); - if (string.IsNullOrEmpty(claimedLockPath)) - { - return; - } - - TryDeleteLockFile(claimedLockPath); - } - } - - public static void DeleteOwnedLockFile(string ownershipToken) - { - if (string.IsNullOrEmpty(ownershipToken)) - { - return; - } - - DeleteLockFile(ownershipToken); - } - - private static string TryClaimOwnedLockFileForDeletion(string lockPath, string ownershipToken) - { - for (int attempt = 0; attempt < FILE_OPERATION_RETRY_COUNT; attempt++) - { - try - { - using FileStream stream = new FileStream(lockPath, FileMode.Open, FileAccess.Read, FileShare.Delete); - using StreamReader reader = new StreamReader(stream); - string existingOwnershipToken = reader.ReadToEnd(); - if (!string.Equals(existingOwnershipToken, ownershipToken, System.StringComparison.Ordinal)) - { - return null; - } - - string claimedLockPath = CreateClaimedLockFilePath(lockPath); - File.Move(lockPath, claimedLockPath); - // Why: tests need a deterministic point after the old generation has been - // detached from the canonical lock path but before we remove the claimed file. - // Why not observe this with FileSystemWatcher: the editor test runner missed the - // rename often enough to make the race coverage flaky on Windows. - OnOwnedLockFileClaimedForDeletionForTests?.Invoke(claimedLockPath); - return claimedLockPath; - } - catch (IOException) - { - } - catch (UnauthorizedAccessException) - { - } - - if (attempt < FILE_OPERATION_RETRY_COUNT - 1) - { - Thread.Sleep(FILE_OPERATION_RETRY_DELAY_MILLISECONDS); - } - } - - VibeLogger.LogWarning("server_starting_lock_claim_failed", $"Failed to claim owned lock file: {lockPath}"); - return null; - } - - private static string CreateClaimedLockFilePath(string lockPath) - { - return $"{lockPath}.{Guid.NewGuid():N}.owneddelete"; - } - - private static void TryDeleteLockFile(string lockPath) - { - for (int attempt = 0; attempt < FILE_OPERATION_RETRY_COUNT; attempt++) - { - try - { - File.Delete(lockPath); - if (!File.Exists(lockPath)) - { - return; - } - } - catch (IOException) - { - } - catch (UnauthorizedAccessException) - { - } - - if (attempt < FILE_OPERATION_RETRY_COUNT - 1) - { - Thread.Sleep(FILE_OPERATION_RETRY_DELAY_MILLISECONDS); - } - } - - VibeLogger.LogWarning("server_starting_lock_delete_failed", $"Failed to delete lock file: {lockPath}"); - } - } -} diff --git a/Packages/src/Editor/Infrastructure/Server/ServerStartingLockService.cs.meta b/Packages/src/Editor/Infrastructure/Server/ServerStartingLockService.cs.meta deleted file mode 100644 index 4746535a1..000000000 --- a/Packages/src/Editor/Infrastructure/Server/ServerStartingLockService.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 608437d7bfb014480a1167c3fa138ff2 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Packages/src/Editor/Infrastructure/Server/UnityCliLoopServerController.cs b/Packages/src/Editor/Infrastructure/Server/UnityCliLoopServerController.cs index 49e6814d8..c969c4fc7 100644 --- a/Packages/src/Editor/Infrastructure/Server/UnityCliLoopServerController.cs +++ b/Packages/src/Editor/Infrastructure/Server/UnityCliLoopServerController.cs @@ -213,12 +213,10 @@ private async Task StartServerWithUseCaseAsync() new UnityCliLoopServerInitializationUseCase( new EditorSecurityValidationService(), startupService); - ServerInitializationRequest request = - ServerInitializationRequest.ReleaseStartupLockWhenReady(); System.Threading.CancellationToken cancellationToken = System.Threading.CancellationToken.None; ServerInitializationResult result = - await useCase.ExecuteAsync(request, cancellationToken); + await useCase.ExecuteAsync(cancellationToken); if (!result.Success) { @@ -527,16 +525,9 @@ public async Task StartRecoveryIfNeededAsync(bool isAfterCompile, CancellationTo isAfterCompile ? "post-compile-recovery" : "server-recovery", null); - // Ensure stale reload locks are cleaned up before recovery. - // Why not clear serverstarting.lock here: a previous generation may still be finishing - // and ownership is now tracked per startup token below. - _domainReloadDetectionService.DeleteLockFile(); - CompilationLockService.DeleteLockFile(); - VibeLogger.LogInfo("startup_request", "transport=project_ipc"); await _startupSemaphore.WaitAsync(cancellationToken); - string serverStartingLockToken = null; try { // If any server is already running, ignore this request to prevent double-binding @@ -547,8 +538,6 @@ public async Task StartRecoveryIfNeededAsync(bool isAfterCompile, CancellationTo return; } - serverStartingLockToken = CreateOptionalServerStartingLock(); - // Ensure previous instance is fully disposed before trying to bind a new one if (_bridgeServer != null) { @@ -570,10 +559,7 @@ public async Task StartRecoveryIfNeededAsync(bool isAfterCompile, CancellationTo bool started = await TryBindWithWaitAsync( 5000, 250, - cancellationToken, - // Why: the endpoint can bind before recovery warmup finishes, and deleting - // the lock at bind time lets the next CLI request block on the main thread. - clearServerStartingLockWhenReady: false); + cancellationToken); if (!started) { @@ -596,14 +582,6 @@ public async Task StartRecoveryIfNeededAsync(bool isAfterCompile, CancellationTo await MarkServerReadyAsync(generationId, "server-recovery", cancellationToken); ActivateStartupProtection(5000); - // Why: external CLI waiters should observe readiness only after recovery - // warmup has finished on the Unity main thread. - ServerStartingLockService.DeleteOwnedLockFile(serverStartingLockToken); - } - catch - { - ServerStartingLockService.DeleteOwnedLockFile(serverStartingLockToken); - throw; } finally { @@ -614,8 +592,7 @@ public async Task StartRecoveryIfNeededAsync(bool isAfterCompile, CancellationTo private async Task TryBindWithWaitAsync( int maxWaitMs, int stepMs, - CancellationToken cancellationToken, - bool clearServerStartingLockWhenReady = true) + CancellationToken cancellationToken) { int remainingMs = maxWaitMs; while (true) @@ -643,7 +620,7 @@ private async Task TryBindWithWaitAsync( } server = _serverInstanceFactory.Create(); - server.StartServer(clearServerStartingLockWhenReady); + server.StartServer(); _bridgeServer = server; VibeLogger.LogInfo("binding_success", $"endpoint={server.Endpoint}"); return true; @@ -755,25 +732,6 @@ private void WriteServerState( _stateStore.Write(phase, generationId, reason, endpoint, lastError); } - internal string CreateOptionalServerStartingLock(Func createLockFile = null) - { - Func createLockFileCore = createLockFile ?? ServerStartingLockService.CreateLockFile; - string serverStartingLockToken = createLockFileCore(); - if (!string.IsNullOrEmpty(serverStartingLockToken)) - { - return serverStartingLockToken; - } - - // Why: serverstarting.lock only improves busy diagnostics for external callers; the - // listener itself can still start and recover safely without it. - // Why not fail fast here: a transient file lock would otherwise turn an optional - // readiness hint into a full startup outage for launch and recovery paths. - VibeLogger.LogWarning( - "server_starting_lock_optional", - "Proceeding without serverstarting.lock because the readiness hint could not be created."); - return null; - } - public void AddServerStateChangedHandler(Action handler) { _serverLifecycleRegistry.ServerStateChanged += handler; diff --git a/Packages/src/Editor/Infrastructure/UnityCliLoopBridgeServer.cs b/Packages/src/Editor/Infrastructure/UnityCliLoopBridgeServer.cs index 97ab1b59a..19a973139 100644 --- a/Packages/src/Editor/Infrastructure/UnityCliLoopBridgeServer.cs +++ b/Packages/src/Editor/Infrastructure/UnityCliLoopBridgeServer.cs @@ -153,7 +153,7 @@ private static UnityCliLoopEditorSettingsService CreateDefaultEditorSettingsServ /// public event Action OnError; - public void StartServer(bool clearServerStartingLockWhenReady = true) + public void StartServer() { if (_isRunning) { @@ -186,14 +186,6 @@ public void StartServer(bool clearServerStartingLockWhenReady = true) } }, TaskScheduler.Default); - // Server is now ready to accept connections - clean up compilation/reload locks. - CompilationLockService.DeleteLockFile(); - _domainReloadDetectionService.DeleteLockFile(); - if (clearServerStartingLockWhenReady) - { - ServerStartingLockService.DeleteLockFile(); - } - ServerStarted?.Invoke(); } diff --git a/Packages/src/Editor/ToolContracts/UnityCliLoopTool.cs b/Packages/src/Editor/ToolContracts/UnityCliLoopTool.cs index 103c6e53a..ecdf113a1 100644 --- a/Packages/src/Editor/ToolContracts/UnityCliLoopTool.cs +++ b/Packages/src/Editor/ToolContracts/UnityCliLoopTool.cs @@ -11,7 +11,6 @@ namespace io.github.hatayama.UnityCliLoop.ToolContracts // - UnityCliLoopToolParameterSchemaGenerator: Generates the JSON schema for tool parameters. /// /// Abstract base class for type-safe Unity tools using Schema and Response types - /// Design reference: @Packages/docs/ARCHITECTURE_Unity.md - Tool Layer (CLI Command Interface) /// /// Schema type for tool parameters /// Response type for tool results