From 57812c8638610f7bedb56c0ec291fad5c9fe5a3e Mon Sep 17 00:00:00 2001 From: Patrick Stewart Date: Tue, 3 Sep 2024 13:17:56 -0700 Subject: [PATCH] add(conduit): refactoring conduit --- packages/isolate/.gitignore | 7 + packages/isolate/CHANGELOG.md | 3 + packages/isolate/README.md | 39 ++++++ packages/isolate/analysis_options.yaml | 30 ++++ packages/isolate/lib/isolate.dart | 31 +++++ packages/isolate/lib/src/executable.dart | 63 +++++++++ packages/isolate/lib/src/executor.dart | 128 ++++++++++++++++++ .../isolate/lib/src/source_generator.dart | 104 ++++++++++++++ packages/isolate/pubspec.yaml | 19 +++ .../{config/lib/src => isolate/test}/.gitkeep | 0 10 files changed, 424 insertions(+) create mode 100644 packages/isolate/.gitignore create mode 100644 packages/isolate/CHANGELOG.md create mode 100644 packages/isolate/README.md create mode 100644 packages/isolate/analysis_options.yaml create mode 100644 packages/isolate/lib/isolate.dart create mode 100644 packages/isolate/lib/src/executable.dart create mode 100644 packages/isolate/lib/src/executor.dart create mode 100644 packages/isolate/lib/src/source_generator.dart create mode 100644 packages/isolate/pubspec.yaml rename packages/{config/lib/src => isolate/test}/.gitkeep (100%) diff --git a/packages/isolate/.gitignore b/packages/isolate/.gitignore new file mode 100644 index 0000000..3cceda5 --- /dev/null +++ b/packages/isolate/.gitignore @@ -0,0 +1,7 @@ +# https://dart.dev/guides/libraries/private-files +# Created by `dart pub` +.dart_tool/ + +# Avoid committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/packages/isolate/CHANGELOG.md b/packages/isolate/CHANGELOG.md new file mode 100644 index 0000000..effe43c --- /dev/null +++ b/packages/isolate/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/packages/isolate/README.md b/packages/isolate/README.md new file mode 100644 index 0000000..8b55e73 --- /dev/null +++ b/packages/isolate/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/packages/isolate/analysis_options.yaml b/packages/isolate/analysis_options.yaml new file mode 100644 index 0000000..dee8927 --- /dev/null +++ b/packages/isolate/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/packages/isolate/lib/isolate.dart b/packages/isolate/lib/isolate.dart new file mode 100644 index 0000000..595750a --- /dev/null +++ b/packages/isolate/lib/isolate.dart @@ -0,0 +1,31 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/// This library provides functionality for working with isolates in Dart. +/// It exports three main components: +/// 1. Executable: Defines the structure for tasks that can be executed in isolates. +/// 2. Executor: Provides mechanisms for running executables in isolates. +/// 3. SourceGenerator: Offers utilities for generating source code for isolates. +/// +/// These components work together to facilitate concurrent programming and +/// improve performance in Dart applications by leveraging isolates. +library isolate; + +export 'package:protevus_isolate/src/executable.dart'; +export 'package:protevus_isolate/src/executor.dart'; +export 'package:protevus_isolate/src/source_generator.dart'; diff --git a/packages/isolate/lib/src/executable.dart b/packages/isolate/lib/src/executable.dart new file mode 100644 index 0000000..f24d344 --- /dev/null +++ b/packages/isolate/lib/src/executable.dart @@ -0,0 +1,63 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import 'dart:async'; +import 'dart:isolate'; +import 'dart:mirrors'; + +abstract class Executable { + Executable(this.message) : _sendPort = message["_sendPort"]; + + Future execute(); + + final Map message; + final SendPort? _sendPort; + + U instanceOf( + String typeName, { + List positionalArguments = const [], + Map namedArguments = const {}, + Symbol constructorName = Symbol.empty, + }) { + ClassMirror? typeMirror = currentMirrorSystem() + .isolate + .rootLibrary + .declarations[Symbol(typeName)] as ClassMirror?; + + typeMirror ??= currentMirrorSystem() + .libraries + .values + .where((lib) => lib.uri.scheme == "package" || lib.uri.scheme == "file") + .expand((lib) => lib.declarations.values) + .firstWhere( + (decl) => + decl is ClassMirror && + MirrorSystem.getName(decl.simpleName) == typeName, + orElse: () => throw ArgumentError( + "Unknown type '$typeName'. Did you forget to import it?", + ), + ) as ClassMirror?; + + return typeMirror! + .newInstance( + constructorName, + positionalArguments, + namedArguments, + ) + .reflectee as U; + } + + void send(dynamic message) { + _sendPort!.send(message); + } + + void log(String message) { + _sendPort!.send({"_line_": message}); + } +} diff --git a/packages/isolate/lib/src/executor.dart b/packages/isolate/lib/src/executor.dart new file mode 100644 index 0000000..fa8946d --- /dev/null +++ b/packages/isolate/lib/src/executor.dart @@ -0,0 +1,128 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import 'dart:async'; +import 'dart:io'; +import 'dart:isolate'; +import 'package:protevus_isolate/isolate.dart'; + +class IsolateExecutor { + IsolateExecutor( + this.generator, { + this.packageConfigURI, + this.message = const {}, + }); + + final SourceGenerator generator; + final Map message; + final Uri? packageConfigURI; + final Completer completer = Completer(); + + Stream get events => _eventListener.stream; + + Stream get console => _logListener.stream; + + final StreamController _logListener = StreamController(); + final StreamController _eventListener = StreamController(); + + Future execute() async { + if (packageConfigURI != null && + !File.fromUri(packageConfigURI!).existsSync()) { + throw StateError( + "Package file '$packageConfigURI' not found. Run 'pub get' and retry.", + ); + } + + final scriptSource = Uri.encodeComponent(await generator.scriptSource); + + final onErrorPort = ReceivePort() + ..listen((err) async { + if (err is List) { + final stack = + StackTrace.fromString(err.last.replaceAll(scriptSource, "")); + + completer.completeError(StateError(err.first), stack); + } else { + completer.completeError(err); + } + }); + + final controlPort = ReceivePort() + ..listen((results) { + if (results is Map && results.length == 1) { + if (results.containsKey("_result")) { + completer.complete(results['_result']); + return; + } else if (results.containsKey("_line_")) { + _logListener.add(results["_line_"]); + return; + } + } + _eventListener.add(results); + }); + try { + message["_sendPort"] = controlPort.sendPort; + + final dataUri = Uri.parse( + "data:application/dart;charset=utf-8,$scriptSource", + ); + + await Isolate.spawnUri( + dataUri, + [], + message, + onError: onErrorPort.sendPort, + packageConfig: packageConfigURI, + automaticPackageResolution: packageConfigURI == null, + ); + return await completer.future; + } catch (e) { + print(e); + rethrow; + } finally { + onErrorPort.close(); + controlPort.close(); + _eventListener.close(); + _logListener.close(); + } + } + + static Future run( + Executable executable, { + List imports = const [], + Uri? packageConfigURI, + String? additionalContents, + List additionalTypes = const [], + void Function(dynamic event)? eventHandler, + void Function(String line)? logHandler, + }) async { + final source = SourceGenerator( + executable.runtimeType, + imports: imports, + additionalContents: additionalContents, + additionalTypes: additionalTypes, + ); + + final executor = IsolateExecutor( + source, + packageConfigURI: packageConfigURI, + message: executable.message, + ); + + if (eventHandler != null) { + executor.events.listen(eventHandler); + } + + if (logHandler != null) { + executor.console.listen(logHandler); + } + + return executor.execute(); + } +} diff --git a/packages/isolate/lib/src/source_generator.dart b/packages/isolate/lib/src/source_generator.dart new file mode 100644 index 0000000..7c4ba86 --- /dev/null +++ b/packages/isolate/lib/src/source_generator.dart @@ -0,0 +1,104 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import 'dart:io'; +import 'dart:isolate'; +import 'dart:mirrors'; +import 'package:analyzer/dart/analysis/analysis_context.dart'; +import 'package:analyzer/dart/analysis/context_builder.dart'; +import 'package:analyzer/dart/analysis/context_locator.dart'; +import 'package:analyzer/dart/analysis/results.dart'; +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/file_system/file_system.dart'; +import 'package:analyzer/file_system/physical_file_system.dart'; +import 'package:path/path.dart'; +import 'package:protevus_isolate/isolate.dart'; + +class SourceGenerator { + SourceGenerator( + this.executableType, { + this.imports = const [], + this.additionalTypes = const [], + this.additionalContents, + }); + + Type executableType; + + String get typeName => + MirrorSystem.getName(reflectType(executableType).simpleName); + final List imports; + final String? additionalContents; + final List additionalTypes; + + Future get scriptSource async { + final typeSource = (await _getClass(executableType)).toSource(); + final builder = StringBuffer(); + + builder.writeln("import 'dart:async';"); + builder.writeln("import 'dart:isolate';"); + builder.writeln("import 'dart:mirrors';"); + for (final anImport in imports) { + builder.writeln("import '$anImport';"); + } + builder.writeln( + """ +Future main (List args, Map message) async { + final sendPort = message['_sendPort']; + final executable = $typeName(message); + final result = await executable.execute(); + sendPort.send({"_result": result}); +} + """, + ); + builder.writeln(typeSource); + + builder.writeln((await _getClass(Executable)).toSource()); + for (final type in additionalTypes) { + final source = await _getClass(type); + builder.writeln(source.toSource()); + } + + if (additionalContents != null) { + builder.writeln(additionalContents); + } + + return builder.toString(); + } + + static Future _getClass(Type type) async { + final uri = + await Isolate.resolvePackageUri(reflectClass(type).location!.sourceUri); + final path = + absolute(normalize(uri!.toFilePath(windows: Platform.isWindows))); + + final context = _createContext(path); + final session = context.currentSession; + final unit = session.getParsedUnit(path) as ParsedUnitResult; + final typeName = MirrorSystem.getName(reflectClass(type).simpleName); + + return unit.unit.declarations + .whereType() + .firstWhere((classDecl) => classDecl.name.value() == typeName); + } +} + +AnalysisContext _createContext( + String path, { + ResourceProvider? resourceProvider, +}) { + resourceProvider ??= PhysicalResourceProvider.INSTANCE; + final builder = ContextBuilder(resourceProvider: resourceProvider); + final contextLocator = ContextLocator( + resourceProvider: resourceProvider, + ); + final root = contextLocator.locateRoots( + includedPaths: [path], + ); + return builder.createContext(contextRoot: root.first); +} diff --git a/packages/isolate/pubspec.yaml b/packages/isolate/pubspec.yaml new file mode 100644 index 0000000..649f416 --- /dev/null +++ b/packages/isolate/pubspec.yaml @@ -0,0 +1,19 @@ +name: protevus_isolate +description: This library contains types that allow for executing code in a spawned isolate, perhaps with additional imports. +version: 0.0.1 +homepage: https://protevus.com +documentation: https://docs.protevus.com +repository: https://git.protevus.com/protevus/platform + +environment: + sdk: ^3.4.3 + +# Add regular dependencies here. +dependencies: + analyzer: ^6.5.0 + glob: ^2.1.2 + path: ^1.9.0 + +dev_dependencies: + lints: ^3.0.0 + test: ^1.24.0 diff --git a/packages/config/lib/src/.gitkeep b/packages/isolate/test/.gitkeep similarity index 100% rename from packages/config/lib/src/.gitkeep rename to packages/isolate/test/.gitkeep