T-128: delete IsolateClient / Backend / backend_entry.dart
test / unit + widget + golden + a11y (push) Failing after 37s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 29s
test / unit + widget + golden + a11y (push) Failing after 37s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 29s
Fifth slice of T-99. After T-127 the socket-loopback DaemonClient is the only IPC path; the isolate-backed third implementation (IsolateClient + Backend + backend_entry.dart) was never wired through and has no remaining references. Removed wholesale; the single service-registration site lives in main.dart's buildDispatcher. flutter analyze + the kernel and ipc suites stay green. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1860,3 +1860,5 @@ INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by,
|
|||||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-126', 'status', 'in_progress', 'done', NULL, '2026-05-18 16:04:29', '2026-05-18 16:04:29', '2026-05-18 16:04:29', NULL, 'b6c783f599585e55ecc36f69ccbcefb1', 1) ON CONFLICT(hash) DO NOTHING;
|
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-126', 'status', 'in_progress', 'done', NULL, '2026-05-18 16:04:29', '2026-05-18 16:04:29', '2026-05-18 16:04:29', NULL, 'b6c783f599585e55ecc36f69ccbcefb1', 1) ON CONFLICT(hash) DO NOTHING;
|
||||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-127', 'status', 'backlog', 'in_progress', NULL, '2026-05-19 09:57:18', '2026-05-19 09:57:18', '2026-05-19 09:57:18', NULL, '64a14cb5d8b9274fd9d72d1e7474040c', 1) ON CONFLICT(hash) DO NOTHING;
|
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-127', 'status', 'backlog', 'in_progress', NULL, '2026-05-19 09:57:18', '2026-05-19 09:57:18', '2026-05-19 09:57:18', NULL, '64a14cb5d8b9274fd9d72d1e7474040c', 1) ON CONFLICT(hash) DO NOTHING;
|
||||||
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-127', 'status', 'in_progress', 'done', NULL, '2026-05-19 10:03:26', '2026-05-19 10:03:26', '2026-05-19 10:03:26', NULL, 'b8bf877d2057f7baa5acf6af7a13d529', 1) ON CONFLICT(hash) DO NOTHING;
|
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-127', 'status', 'in_progress', 'done', NULL, '2026-05-19 10:03:26', '2026-05-19 10:03:26', '2026-05-19 10:03:26', NULL, 'b8bf877d2057f7baa5acf6af7a13d529', 1) ON CONFLICT(hash) DO NOTHING;
|
||||||
|
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-128', 'status', 'backlog', 'in_progress', NULL, '2026-05-19 10:05:09', '2026-05-19 10:05:09', '2026-05-19 10:05:09', NULL, '763a80ec25b5fe712e5ad44b75dbb22f', 1) ON CONFLICT(hash) DO NOTHING;
|
||||||
|
INSERT INTO ticket_history (ticket_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-128', 'status', 'in_progress', 'done', NULL, '2026-05-19 10:06:46', '2026-05-19 10:06:46', '2026-05-19 10:06:46', NULL, 'cc53b46764f28515d735aa77c8eeef2c', 1) ON CONFLICT(hash) DO NOTHING;
|
||||||
|
|||||||
@@ -2144,3 +2144,15 @@ Acceptance:
|
|||||||
5. Falls back to in-process direct dispatch if the perf delta is unacceptable; surface the finding in a Q-record before doing so.
|
5. Falls back to in-process direct dispatch if the perf delta is unacceptable; surface the finding in a Q-record before doing so.
|
||||||
|
|
||||||
Source: T-99 sketch. Depends on T-124 (server must exist). Blocks T-128 (legacy IPC cleanup).', 'done', 'high', NULL, NULL, NULL, '2026-05-18 11:59:02', '2026-05-19 10:03:26', NULL, 'af4a706880181411b2aecf51d90ea748', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
Source: T-99 sketch. Depends on T-124 (server must exist). Blocks T-128 (legacy IPC cleanup).', 'done', 'high', NULL, NULL, NULL, '2026-05-18 11:59:02', '2026-05-19 10:03:26', NULL, 'af4a706880181411b2aecf51d90ea748', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||||
|
INSERT INTO tickets (id, type, parent_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('T-128', 'task', 'T-99', 'delete IsolateClient + Backend + backend_entry.dart; collapse service registration', 'Fifth slice of T-99(a). Pure cleanup once T-127 lands.
|
||||||
|
|
||||||
|
Remove the third unused IPC path entirely. Today three implementations coexist: DaemonClient (socket — gets a real server in T-124), InProcessClient (deleted in T-127), and IsolateClient + Backend + backend_entry.dart (a backend isolate path that was never wired through). After T-127, none of those should still be referenced.
|
||||||
|
|
||||||
|
Also: consolidate the two duplicate service-wiring sites into one (currently main.dart''s buildDispatcher and the kernel facade both register subsystem handlers; one should own it).
|
||||||
|
|
||||||
|
Acceptance:
|
||||||
|
1. lib/kernel/src/ipc/isolate_client.dart, lib/src/daemon/backend.dart, lib/src/daemon/backend_entry.dart removed.
|
||||||
|
2. Single service-registration site; no duplicate registerPaneCommands / registerFilesCommands / etc. across files.
|
||||||
|
3. flutter analyze + full test suite green.
|
||||||
|
|
||||||
|
Source: T-99 sketch. Depends on T-127.', 'done', 'medium', NULL, NULL, NULL, '2026-05-18 11:59:06', '2026-05-19 10:06:46', NULL, '481c51b17b027b5a04279a0ef6a3a029', 1) ON CONFLICT(id) DO UPDATE SET type=excluded.type, parent_id=excluded.parent_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ heading, and (b) bumping `pubspec.yaml` `version:` in the same commit.
|
|||||||
|
|
||||||
- `lib/kernel/src/ipc/in_process.dart` (`InProcessClient`) — replaced
|
- `lib/kernel/src/ipc/in_process.dart` (`InProcessClient`) — replaced
|
||||||
by the socket-loopback `DaemonClient` (T-127).
|
by the socket-loopback `DaemonClient` (T-127).
|
||||||
|
- `lib/kernel/src/backend.dart`, `lib/kernel/src/backend_entry.dart`,
|
||||||
|
`lib/kernel/src/ipc/isolate_client.dart` — the third unused IPC
|
||||||
|
path (a backend-isolate model that was never wired through). Only
|
||||||
|
the socket model survives now (T-128).
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
/// Manages the backend isolate lifecycle.
|
|
||||||
///
|
|
||||||
/// Two-phase boot:
|
|
||||||
/// 1. [Backend.spawn] — starts the isolate, resolves toolchain (binary checks only).
|
|
||||||
/// 2. [Backend.openProject] — initializes services for a specific project root.
|
|
||||||
library;
|
|
||||||
|
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:isolate';
|
|
||||||
|
|
||||||
import 'package:clide/kernel/src/backend_entry.dart';
|
|
||||||
import 'package:clide/kernel/src/ipc/isolate_client.dart';
|
|
||||||
import 'package:clide/kernel/src/toolchain.dart';
|
|
||||||
|
|
||||||
class Backend {
|
|
||||||
Backend._({
|
|
||||||
required this.client,
|
|
||||||
required this.toolchain,
|
|
||||||
required SendPort backendRequestPort,
|
|
||||||
required Isolate isolate,
|
|
||||||
required ReceivePort receivePort,
|
|
||||||
}) : _backendRequestPort = backendRequestPort,
|
|
||||||
_isolate = isolate,
|
|
||||||
_receivePort = receivePort;
|
|
||||||
|
|
||||||
final IsolateClient client;
|
|
||||||
final Toolchain toolchain;
|
|
||||||
final SendPort _backendRequestPort;
|
|
||||||
final Isolate _isolate;
|
|
||||||
final ReceivePort _receivePort;
|
|
||||||
|
|
||||||
Completer<void>? _projectCompleter;
|
|
||||||
final Map<String, Completer<String?>> _validateCompleters = {};
|
|
||||||
int _validateId = 0;
|
|
||||||
|
|
||||||
/// Spawn the backend isolate. Returns when the toolchain is resolved.
|
|
||||||
/// No services are active yet — call [openProject] to activate.
|
|
||||||
static Future<Backend> spawn({
|
|
||||||
required IsolateClient Function(SendPort backendPort) clientFactory,
|
|
||||||
String? hintRoot,
|
|
||||||
}) async {
|
|
||||||
final receivePort = ReceivePort();
|
|
||||||
final completer = Completer<Backend>();
|
|
||||||
|
|
||||||
late final IsolateClient client;
|
|
||||||
late final Isolate isolate;
|
|
||||||
late final SendPort backendRequestPort;
|
|
||||||
late final Backend backend;
|
|
||||||
|
|
||||||
receivePort.listen((message) {
|
|
||||||
if (message is Map<String, Object?>) {
|
|
||||||
final type = message['type'] as String?;
|
|
||||||
if (type == 'ready') {
|
|
||||||
backendRequestPort = message['requestPort'] as SendPort;
|
|
||||||
client = clientFactory(backendRequestPort);
|
|
||||||
|
|
||||||
final tcData = message['toolchain'] as Map<String, Object?>;
|
|
||||||
final toolchain = Toolchain();
|
|
||||||
toolchain.applyResolved(ResolvedPaths(
|
|
||||||
git: tcData['git'] as String?,
|
|
||||||
pql: tcData['pql'] as String?,
|
|
||||||
tmux: tcData['tmux'] as String?,
|
|
||||||
shell: tcData['shell'] as String?,
|
|
||||||
gitEnv: (tcData['gitEnv'] as Map?)?.cast<String, String>(),
|
|
||||||
));
|
|
||||||
|
|
||||||
backend = Backend._(
|
|
||||||
client: client,
|
|
||||||
toolchain: toolchain,
|
|
||||||
backendRequestPort: backendRequestPort,
|
|
||||||
isolate: isolate,
|
|
||||||
receivePort: receivePort,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!completer.isCompleted) completer.complete(backend);
|
|
||||||
} else if (type == 'project.validated') {
|
|
||||||
final id = message['id'] as String;
|
|
||||||
final root = message['root'] as String?;
|
|
||||||
final c = backend._validateCompleters.remove(id);
|
|
||||||
if (c != null && !c.isCompleted) c.complete(root);
|
|
||||||
} else if (type == 'project.ready') {
|
|
||||||
// Update toolchain with project-specific paths.
|
|
||||||
final tcData = message['toolchain'] as Map<String, Object?>;
|
|
||||||
backend.toolchain.applyResolved(ResolvedPaths(
|
|
||||||
git: tcData['git'] as String?,
|
|
||||||
pql: tcData['pql'] as String?,
|
|
||||||
tmux: tcData['tmux'] as String?,
|
|
||||||
shell: tcData['shell'] as String?,
|
|
||||||
gitEnv: (tcData['gitEnv'] as Map?)?.cast<String, String>(),
|
|
||||||
));
|
|
||||||
backend._projectCompleter?.complete();
|
|
||||||
backend._projectCompleter = null;
|
|
||||||
} else {
|
|
||||||
// Response or event — forward to the client.
|
|
||||||
client.handleMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
isolate = await Isolate.spawn(
|
|
||||||
backendEntry,
|
|
||||||
BackendBootMessage(frontendPort: receivePort.sendPort, hintRoot: hintRoot),
|
|
||||||
);
|
|
||||||
|
|
||||||
return completer.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Validate a path as a git repo. Returns the repo root or null.
|
|
||||||
/// Runs git rev-parse in the backend isolate (no main-thread I/O).
|
|
||||||
Future<String?> validateProject(String path) {
|
|
||||||
final id = '${_validateId++}';
|
|
||||||
final c = Completer<String?>();
|
|
||||||
_validateCompleters[id] = c;
|
|
||||||
_backendRequestPort.send({
|
|
||||||
'type': 'project.validate',
|
|
||||||
'path': path,
|
|
||||||
'id': id,
|
|
||||||
});
|
|
||||||
return c.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Activate a project. The backend (re)initializes all services
|
|
||||||
/// for the given root directory. Returns when services are ready.
|
|
||||||
Future<void> openProject(String path) {
|
|
||||||
_projectCompleter = Completer<void>();
|
|
||||||
_backendRequestPort.send({
|
|
||||||
'type': 'project.open',
|
|
||||||
'path': path,
|
|
||||||
});
|
|
||||||
return _projectCompleter!.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shut down the backend isolate.
|
|
||||||
void dispose() {
|
|
||||||
_isolate.kill(priority: Isolate.beforeNextEvent);
|
|
||||||
_receivePort.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
/// Backend isolate entry point.
|
|
||||||
///
|
|
||||||
/// Two-phase boot:
|
|
||||||
/// 1. Resolve toolchain (find binaries) → report ready.
|
|
||||||
/// 2. On `project.open` message → initialize services for the project.
|
|
||||||
///
|
|
||||||
/// The dispatcher only registers command handlers after a project is
|
|
||||||
/// activated. IPC requests arriving before that get an error response.
|
|
||||||
library;
|
|
||||||
|
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:isolate';
|
|
||||||
|
|
||||||
import 'package:clide/kernel/src/toolchain.dart';
|
|
||||||
import 'package:clide/src/daemon/dispatcher.dart';
|
|
||||||
import 'package:clide/src/daemon/editor_commands.dart';
|
|
||||||
import 'package:clide/src/daemon/files_commands.dart';
|
|
||||||
import 'package:clide/src/daemon/git_commands.dart';
|
|
||||||
import 'package:clide/src/daemon/pane_commands.dart';
|
|
||||||
import 'package:clide/src/daemon/pql_commands.dart';
|
|
||||||
import 'package:clide/src/editor/registry.dart' show EditorRegistry;
|
|
||||||
import 'package:clide/src/git/client.dart';
|
|
||||||
import 'package:clide/src/ipc/envelope.dart';
|
|
||||||
import 'package:clide/src/ipc/schema_v1.dart';
|
|
||||||
import 'package:clide/src/panes/event_sink.dart';
|
|
||||||
import 'package:clide/src/panes/registry.dart';
|
|
||||||
import 'package:clide/src/pql/client.dart';
|
|
||||||
|
|
||||||
/// Message sent from main isolate to bootstrap the backend.
|
|
||||||
class BackendBootMessage {
|
|
||||||
const BackendBootMessage({required this.frontendPort, this.hintRoot});
|
|
||||||
final SendPort frontendPort;
|
|
||||||
|
|
||||||
/// Optional path hint for initial toolchain resolution (e.g. CLIDE_PROJECT).
|
|
||||||
/// Used to find project-local binaries like dugite before a project opens.
|
|
||||||
final String? hintRoot;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Top-level entry point for the backend isolate.
|
|
||||||
void backendEntry(BackendBootMessage boot) {
|
|
||||||
final frontendPort = boot.frontendPort;
|
|
||||||
final requestPort = ReceivePort();
|
|
||||||
final eventSink = _IsolateEventSink(frontendPort);
|
|
||||||
final dispatcher = DaemonDispatcher();
|
|
||||||
late Toolchain toolchain;
|
|
||||||
|
|
||||||
// Phase 1: resolve toolchain — just find binaries, don't init services.
|
|
||||||
// Dugite is resolved against the install dir; per T-98 the project
|
|
||||||
// root is never inspected during toolchain resolution.
|
|
||||||
toolchain = Toolchain();
|
|
||||||
toolchain.applyResolved(resolveToolchainPaths());
|
|
||||||
|
|
||||||
// Listen for messages from the frontend.
|
|
||||||
requestPort.listen((message) async {
|
|
||||||
if (message is! Map<String, Object?>) return;
|
|
||||||
final type = message['type'] as String?;
|
|
||||||
|
|
||||||
if (type == 'project.validate') {
|
|
||||||
// Validate a path as a git repo. Runs git rev-parse in the backend
|
|
||||||
// isolate (safe from the merged thread). Returns the repo root or null.
|
|
||||||
final path = message['path'] as String;
|
|
||||||
final id = message['id'] as String;
|
|
||||||
try {
|
|
||||||
final r = await Process.run(toolchain.git, ['rev-parse', '--show-toplevel'], workingDirectory: path, environment: toolchain.gitEnv);
|
|
||||||
if (r.exitCode == 0) {
|
|
||||||
final root = (r.stdout as String).trim();
|
|
||||||
frontendPort.send({'type': 'project.validated', 'id': id, 'root': root});
|
|
||||||
} else {
|
|
||||||
frontendPort.send({'type': 'project.validated', 'id': id, 'root': null});
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
frontendPort.send({'type': 'project.validated', 'id': id, 'root': null});
|
|
||||||
}
|
|
||||||
} else if (type == 'project.open') {
|
|
||||||
// Phase 2: (re)initialize services for the given project.
|
|
||||||
final projectPath = message['path'] as String;
|
|
||||||
final workDir = Directory(projectPath);
|
|
||||||
|
|
||||||
// Re-resolve toolchain. Project path is not inspected (T-98);
|
|
||||||
// dugite still comes from the install dir + env override.
|
|
||||||
toolchain = Toolchain();
|
|
||||||
toolchain.applyResolved(resolveToolchainPaths());
|
|
||||||
|
|
||||||
// Clear existing handlers and re-register with new project.
|
|
||||||
dispatcher.clear();
|
|
||||||
|
|
||||||
final filesService = FilesService(root: workDir, events: eventSink);
|
|
||||||
registerFilesCommands(dispatcher, filesService);
|
|
||||||
|
|
||||||
final editorRegistry = EditorRegistry(events: eventSink, workspaceRoot: workDir);
|
|
||||||
registerEditorCommands(dispatcher, editorRegistry);
|
|
||||||
|
|
||||||
final gitClient = GitClient(toolchain: toolchain, workDir: workDir);
|
|
||||||
registerGitCommands(dispatcher, gitClient, eventSink);
|
|
||||||
|
|
||||||
final pql = PqlClient(workDir: workDir, toolchain: toolchain);
|
|
||||||
registerPqlCommands(dispatcher, pql);
|
|
||||||
|
|
||||||
final paneRegistry = PaneRegistry(events: eventSink);
|
|
||||||
registerPaneCommands(dispatcher, paneRegistry);
|
|
||||||
|
|
||||||
// Tell the frontend the project is active.
|
|
||||||
frontendPort.send({
|
|
||||||
'type': 'project.ready',
|
|
||||||
'path': projectPath,
|
|
||||||
'toolchain': _serializeToolchain(toolchain),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// IPC request — dispatch if we have handlers.
|
|
||||||
final req = IpcRequest.fromJson(message);
|
|
||||||
if (dispatcher.isEmpty) {
|
|
||||||
frontendPort.send(IpcResponse.err(
|
|
||||||
id: req.id,
|
|
||||||
error: IpcError(
|
|
||||||
code: IpcExitCode.toolError,
|
|
||||||
kind: IpcErrorKind.toolError,
|
|
||||||
message: 'No project active',
|
|
||||||
hint: 'Open a project first',
|
|
||||||
),
|
|
||||||
).toJson());
|
|
||||||
} else {
|
|
||||||
final resp = await dispatcher.dispatch(req);
|
|
||||||
frontendPort.send(resp.toJson());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send ready with toolchain state and request port.
|
|
||||||
frontendPort.send({
|
|
||||||
'type': 'ready',
|
|
||||||
'requestPort': requestPort.sendPort,
|
|
||||||
'toolchain': _serializeToolchain(toolchain),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Object?> _serializeToolchain(Toolchain tc) => {
|
|
||||||
'git': tc.git,
|
|
||||||
'pql': tc.pql,
|
|
||||||
'tmux': tc.tmux,
|
|
||||||
'shell': tc.shell,
|
|
||||||
'gitEnv': tc.gitEnv,
|
|
||||||
'missing': tc.missing,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Sends IPC events to the frontend via SendPort.
|
|
||||||
class _IsolateEventSink implements DaemonEventSink {
|
|
||||||
_IsolateEventSink(this._port);
|
|
||||||
final SendPort _port;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void emit(IpcEvent event) {
|
|
||||||
_port.send(event.toJson());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
/// IPC client that sends requests to a backend isolate via SendPort.
|
|
||||||
///
|
|
||||||
/// Replaces [InProcessClient] for production use. The backend isolate
|
|
||||||
/// owns the [DaemonDispatcher] and all subprocess/file-I/O services.
|
|
||||||
/// Requests and responses travel as serialized Maps over SendPort,
|
|
||||||
/// reusing the existing IPC protocol (IpcRequest/IpcResponse/IpcEvent).
|
|
||||||
library;
|
|
||||||
|
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:isolate';
|
|
||||||
|
|
||||||
import 'package:clide/clide.dart';
|
|
||||||
import 'package:clide/kernel/src/events/bus.dart';
|
|
||||||
import 'package:clide/kernel/src/events/types.dart';
|
|
||||||
import 'package:clide/kernel/src/ipc/client.dart';
|
|
||||||
|
|
||||||
class IsolateClient extends DaemonClient {
|
|
||||||
IsolateClient({
|
|
||||||
required super.log,
|
|
||||||
required super.events,
|
|
||||||
required SendPort backendPort,
|
|
||||||
}) : _backendPort = backendPort,
|
|
||||||
_events = events,
|
|
||||||
super(socketPath: '');
|
|
||||||
|
|
||||||
final SendPort _backendPort;
|
|
||||||
final DaemonBus _events;
|
|
||||||
|
|
||||||
/// The event bus that receives events from the backend.
|
|
||||||
DaemonBus get events => _events;
|
|
||||||
final Map<String, Completer<IpcResponse>> _pending = {};
|
|
||||||
int _nextId = 0;
|
|
||||||
|
|
||||||
/// Called by [Backend] to feed incoming messages from the backend isolate.
|
|
||||||
void handleMessage(Map<String, Object?> msg) {
|
|
||||||
final type = msg['type'] as String?;
|
|
||||||
switch (type) {
|
|
||||||
case 'response':
|
|
||||||
final resp = IpcResponse.fromJson(msg);
|
|
||||||
final c = _pending.remove(resp.id);
|
|
||||||
if (c != null && !c.isCompleted) c.complete(resp);
|
|
||||||
case 'event':
|
|
||||||
final evt = IpcEvent.fromJson(msg);
|
|
||||||
_events.emit(DaemonEvent(
|
|
||||||
subsystem: evt.subsystem,
|
|
||||||
kind: evt.kind,
|
|
||||||
data: evt.data,
|
|
||||||
ts: evt.timestamp,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get isConnected => true;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> start() async {}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> stop() async {}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<IpcResponse> request(String cmd, {Map<String, Object?> args = const {}}) {
|
|
||||||
final id = '${_nextId++}';
|
|
||||||
final req = IpcRequest(id: id, cmd: cmd, args: args);
|
|
||||||
final c = Completer<IpcResponse>();
|
|
||||||
_pending[id] = c;
|
|
||||||
_backendPort.send(req.toJson());
|
|
||||||
return c.future;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user