From d3f61f5ce8ae42a1fb0a3cb19acbbec018b8f221 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 23 Jan 2024 14:15:38 -0500 Subject: [PATCH 1/3] Documented everything --- analysis_options.yaml | 1 + bin/example.dart | 58 ------------------------------------- lib/src/camera_isolate.dart | 38 ++++++++++++------------ lib/src/collection.dart | 13 +++++++++ lib/src/frame.dart | 56 +++++++++++++++++++++++++++-------- lib/src/parent_isolate.dart | 37 ++++++++++++++++------- lib/src/server.dart | 5 +++- 7 files changed, 108 insertions(+), 100 deletions(-) delete mode 100644 bin/example.dart diff --git a/analysis_options.yaml b/analysis_options.yaml index 4b0cc46..49c9dd0 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -45,6 +45,7 @@ linter: sort_constructors_first: false # final properties, then constructor avoid_dynamic_calls: false # this lint takes over errors in the IDE omit_local_variable_types: false # it can be helpful sometimes to annotate types + cascade_invocations: false # Cascades are less readable # Temporarily disabled until we are ready to document # public_member_api_docs: false diff --git a/bin/example.dart b/bin/example.dart deleted file mode 100644 index abccda3..0000000 --- a/bin/example.dart +++ /dev/null @@ -1,58 +0,0 @@ -// ignore_for_file: avoid_print - -import "package:typed_isolate/typed_isolate.dart"; - -class NumberSender extends IsolateParent { - @override - Future run() async { - print("Opening parent..."); - print("Sending: 1"); - //send(1, "braces"); - send(1, "brackets"); - await Future.delayed(const Duration(seconds: 1)); - - print("Sending: 2"); - send(2, "brackets"); - //send(2, "braces"); - await Future.delayed(const Duration(seconds: 1)); - - print("Sending: 3"); - //send(3, "braces"); - send(3, "brackets"); - await Future.delayed(const Duration(seconds: 1)); - } - - @override - void onData(int data) => print("Got: $data"); -} - -class NumberConverter extends IsolateChild { - NumberConverter() : super(id: "brackets"); - - @override - void run() => print("Opening child..."); - - @override - void onData(int data) => send(data + 5); -} - -class NumberConverter2 extends IsolateChild { - NumberConverter2() : super(id: "braces"); - - @override - void run() => print("Opening child..."); - - @override - void onData(int data) => send("{$data}"); -} - -void main() async { - final parent = NumberSender(); - final isolate1 = await parent.spawn(NumberConverter()); - //final isolate2 = await parent.spawn(NumberConverter2()); - await Future.delayed(const Duration(seconds: 1)); - await parent.run(); - isolate1.kill(); - //isolate2.kill(); - parent.close(); -} diff --git a/lib/src/camera_isolate.dart b/lib/src/camera_isolate.dart index 6d3c2bc..59777ee 100644 --- a/lib/src/camera_isolate.dart +++ b/lib/src/camera_isolate.dart @@ -12,8 +12,8 @@ import "periodic_timer.dart"; /// /// This class accepts [VideoCommand]s and calls [updateDetails] with the newly-received details. /// When a frame is read, instead of sending the [VideoData], this class sends only the pointer -/// to the [OpenCVImage] via the [FrameData] class, and the image is read by the parent isolate. -class CameraIsolate extends IsolateChild{ +/// to the [OpenCVImage] via the [IsolatePayload] class, and the image is read by the parent isolate. +class CameraIsolate extends IsolateChild{ /// The native camera object from OpenCV. late final Camera camera; /// Holds the current details of the camera. @@ -28,26 +28,28 @@ class CameraIsolate extends IsolateChild{ /// Records how many FPS this camera is actually running at. int fpsCount = 0; - /// The log level at which this isolate should be reporting. - LogLevel logLevel; - /// Creates a new manager for the given camera and default details. - CameraIsolate({required this.details, required this.logLevel}) : super(id: details.name); + CameraIsolate({required this.details}) : super(id: details.name); /// The name of this camera (where it is on the rover). CameraName get name => details.name; /// Sends the current status to the dashboard (with an empty frame). - void sendStatus([_]) => send(FrameData(details: details, address: 0, length: 0)); + void sendStatus([_]) => send(DetailsPayload(details)); + + /// Logs a message by sending a [LogPayload] to the parent isolate. + /// + /// Note: it is important to _not_ log this message directly in _this_ isolate, as it will + /// not be configurable by the parent isolate and will not be sent to the Dashboard. + void log(LogLevel level, String message) => send(LogPayload(level: level, message: message)); @override Future run() async { - Logger.level = logLevel; - logger.debug("Initializing camera: $name"); + log(LogLevel.debug, "Initializing camera: $name"); camera = getCamera(name); statusTimer = Timer.periodic(const Duration(seconds: 5), sendStatus); if (!camera.isOpened) { - logger.warning("Camera $name is not connected"); + log(LogLevel.warning, "Camera $name is not connected"); updateDetails(CameraDetails(status: CameraStatus.CAMERA_DISCONNECTED)); } start(); @@ -69,24 +71,24 @@ class CameraIsolate extends IsolateChild{ frameTimer?.cancel(); fpsTimer?.cancel(); statusTimer?.cancel(); - logger.info("Disposed camera $name"); + log(LogLevel.info, "Disposed camera $name"); } /// Starts the camera and timers. void start() { if (details.status != CameraStatus.CAMERA_ENABLED) return; - logger.debug("Starting camera $name. Status=${details.status}"); + log(LogLevel.debug, "Starting camera $name. Status=${details.status}"); final interval = details.fps == 0 ? Duration.zero : Duration(milliseconds: 1000 ~/ details.fps); frameTimer = PeriodicTimer(interval, sendFrame); fpsTimer = Timer.periodic(const Duration(seconds: 5), (_) { - logger.trace("Camera $name sent ${fpsCount ~/ 5} frames"); + log(LogLevel.trace, "Camera $name sent ${fpsCount ~/ 5} frames"); fpsCount = 0; }); } /// Cancels all timers and stops reading the camera. void stop() { - logger.debug("Stopping camera $name"); + log(LogLevel.debug, "Stopping camera $name"); frameTimer?.cancel(); fpsTimer?.cancel(); } @@ -100,16 +102,16 @@ class CameraIsolate extends IsolateChild{ Future sendFrame() async { final frame = camera.getJpg(quality: details.quality); if (frame == null) { // Error getting the frame - logger.warning("Camera $name didn't respond"); + log(LogLevel.warning, "Camera $name didn't respond"); updateDetails(CameraDetails(status: CameraStatus.CAMERA_NOT_RESPONDING)); } else if (frame.data.length < 60000) { // Frame can be sent - send(FrameData(address: frame.pointer.address, length: frame.data.length, details: details)); + send(FramePayload(address: frame.pointer.address, length: frame.data.length, details: details)); fpsCount++; } else if (details.quality > 25) { // Frame too large, try lowering quality - logger.debug("Lowering quality for $name from ${details.quality}"); + log(LogLevel.debug, "Lowering quality for $name from ${details.quality}"); updateDetails(CameraDetails(quality: details.quality - 1)); } else { // Frame too large, cannot lower quality anymore - logger.warning("$name's frames are too large (${frame.data.length} bytes, quality=${details.quality})"); + log(LogLevel.warning, "$name's frames are too large (${frame.data.length} bytes, quality=${details.quality})"); updateDetails(CameraDetails(status: CameraStatus.FRAME_TOO_LARGE)); } } diff --git a/lib/src/collection.dart b/lib/src/collection.dart index 9a12133..3ddefd5 100644 --- a/lib/src/collection.dart +++ b/lib/src/collection.dart @@ -45,6 +45,19 @@ class Collection { await parent.run(); logger.info("Video program initialized"); } + + /// Stops all cameras and disconnects from the hardware. + Future dispose() async { + await videoServer.dispose(); + parent.stopAll(); + parent.clear(); + } + + /// Restarts the video program. + Future restart() async { + await dispose(); + await init(); + } } /// Holds all the devices connected diff --git a/lib/src/frame.dart b/lib/src/frame.dart index a57f0f0..5a7279e 100644 --- a/lib/src/frame.dart +++ b/lib/src/frame.dart @@ -1,21 +1,53 @@ +import "dart:ffi"; + import "package:burt_network/burt_network.dart"; +import "package:burt_network/logging.dart"; +import "package:opencv_ffi/opencv_ffi.dart"; + +/// A payload containing some data to report back to the parent isolate. +/// +/// Instead of having nullable fields on this class, we subclass it and provide +/// only the relevant fields for each subclass. That way, for example, you cannot +/// accidentally send a frame without a [CameraDetails]. +sealed class IsolatePayload { const IsolatePayload(); } + +/// A payload representing the status of the given camera. +class DetailsPayload extends IsolatePayload { + /// The details being sent. + final CameraDetails details; + /// A const constructor. + const DetailsPayload(this.details); +} /// A container for a pointer to a native buffer that can be sent across isolates. /// /// Sending a buffer across isolates would mean that data is copied, which is not ideal for /// buffers containing an entire JPG image, from multiple isolates, multiple frames per second. -/// Since we cannot yet send FFI pointers across isolates, we have to send its raw address -/// instead. -class FrameData { - /// The [CameraDetails] for this frame. +/// Since we cannot yet send FFI pointers across isolates, we have to send its raw address. +class FramePayload extends IsolatePayload { + /// The details of the camera this frame came from. final CameraDetails details; - /// The address of the FFI pointer containing the image. - final int? address; - /// The amount of bytes to read past [address]. - final int? length; + /// The address in FFI memory this frame starts at. + final int address; + /// The length of this frame in bytes. + final int length; + + /// A const constructor. + const FramePayload({required this.details, required this.address, required this.length}); + + /// The underlying data held at [address]. + /// + /// This cannot be a normal field as [Pointer]s cannot be sent across isolates, and this should + /// not be a getter because the underlying memory needs to be freed and cannot be used again. + OpenCVImage getFrame() => OpenCVImage(pointer: Pointer.fromAddress(address), length: length); +} - /// Creates a [FrameData] containing an actual frame. - FrameData({required this.details, required this.address, required this.length}); - /// Creates a [FrameData] that only has [CameraDetails]. - FrameData.details(this.details) : address = null, length = null; +/// A class to send log messages across isolates. The parent isolate is responsible for logging. +class LogPayload extends IsolatePayload { + /// The level to log this message. + final LogLevel level; + /// The message to log. + final String message; + /// A const constructor. + const LogPayload({required this.level, required this.message}); } diff --git a/lib/src/parent_isolate.dart b/lib/src/parent_isolate.dart index 61dd423..b0fef03 100644 --- a/lib/src/parent_isolate.dart +++ b/lib/src/parent_isolate.dart @@ -1,5 +1,4 @@ import "dart:async"; -import "dart:ffi"; import "package:opencv_ffi/opencv_ffi.dart"; import "package:typed_isolate/typed_isolate.dart"; @@ -13,18 +12,17 @@ import "camera_isolate.dart"; /// A parent isolate that spawns [CameraIsolate]s to manage the cameras. /// /// With one isolate per camera, each camera can read in parallel. This class sends [VideoCommand]s -/// from the dashboard to the appropriate [CameraIsolate], and receives [FrameData]s which it uses +/// from the dashboard to the appropriate [CameraIsolate], and receives [IsolatePayload]s which it uses /// to read an [OpenCVImage] from native memory and send to the dashboard. By not sending the frame /// from child isolate to the parent (just the pointer), we save a whole JPG image's worth of bytes /// from every camera, every frame, every second. That could be up to 5 MB per second of savings. -class VideoController extends IsolateParent{ +class VideoController extends IsolateParent{ @override Future run() async { for (final name in CameraName.values) { if (name == CameraName.CAMERA_NAME_UNDEFINED) continue; await spawn( CameraIsolate( - logLevel: Logger.level, details: getDefaultDetails(name), ), ); @@ -32,13 +30,30 @@ class VideoController extends IsolateParent{ } @override - void onData(FrameData data) { - if (data.address == null) { - collection.videoServer.sendMessage(VideoData(details: data.details)); - } else { - final frame = OpenCVImage(pointer: Pointer.fromAddress(data.address!), length: data.length!); - collection.videoServer.sendMessage(VideoData(frame: frame.data, details: data.details)); - frame.dispose(); + void onData(IsolatePayload data) { + switch (data) { + case DetailsPayload(): + collection.videoServer.sendMessage(VideoData(details: data.details)); + case FramePayload(): + final frame = data.getFrame(); + collection.videoServer.sendMessage(VideoData(frame: frame.data, details: data.details)); + frame.dispose(); + case LogPayload(): switch (data.level) { + case LogLevel.all: logger.info(data.message); + // ignore: deprecated_member_use + case LogLevel.verbose: logger.trace(data.message); + case LogLevel.trace: logger.trace(data.message); + case LogLevel.debug: logger.debug(data.message); + case LogLevel.info: logger.info(data.message); + case LogLevel.warning: logger.warning(data.message); + case LogLevel.error: logger.error(data.message); + // ignore: deprecated_member_use + case LogLevel.wtf: logger.info(data.message); + case LogLevel.fatal: logger.critical(data.message); + // ignore: deprecated_member_use + case LogLevel.nothing: logger.info(data.message); + case LogLevel.off: logger.info(data.message); + } } } diff --git a/lib/src/server.dart b/lib/src/server.dart index 6b41d64..9fa628b 100644 --- a/lib/src/server.dart +++ b/lib/src/server.dart @@ -3,7 +3,7 @@ import "package:burt_network/burt_network.dart"; import "collection.dart"; /// Class for the video program to interact with the dashboard -class VideoServer extends ServerSocket { +class VideoServer extends RoverServer { /// Requires a port to communicate through VideoServer({required super.port}) : super(device: Device.VIDEO); @@ -15,4 +15,7 @@ class VideoServer extends ServerSocket { sendMessage(command); // Echo the request collection.parent.send(command, command.details.name); } + + @override + void restart() => collection.restart(); } From 0a83ac2c92cd6fa178561a36b58b5d04c168fc86 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 23 Jan 2024 14:17:21 -0500 Subject: [PATCH 2/3] Added lint explanation to deprecated_member_use --- lib/src/parent_isolate.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/src/parent_isolate.dart b/lib/src/parent_isolate.dart index b0fef03..848146a 100644 --- a/lib/src/parent_isolate.dart +++ b/lib/src/parent_isolate.dart @@ -39,6 +39,8 @@ class VideoController extends IsolateParent{ collection.videoServer.sendMessage(VideoData(frame: frame.data, details: data.details)); frame.dispose(); case LogPayload(): switch (data.level) { + // Turns out using deprecated members when you *have* to still results in a lint. + // See https://github.com/dart-lang/linter/issues/4852 for why we ignore it. case LogLevel.all: logger.info(data.message); // ignore: deprecated_member_use case LogLevel.verbose: logger.trace(data.message); From 6bf4384dcb46c8f29ac6c609e24feb4edd3828f1 Mon Sep 17 00:00:00 2001 From: Levi Lesches Date: Tue, 23 Jan 2024 14:57:46 -0500 Subject: [PATCH 3/3] Upgraded package:typed_isolate --- lib/src/collection.dart | 6 +-- lib/src/parent_isolate.dart | 6 +-- lib/src/server.dart | 2 +- pubspec.lock | 78 ++++++++++++++++++------------------- pubspec.yaml | 2 +- 5 files changed, 47 insertions(+), 47 deletions(-) diff --git a/lib/src/collection.dart b/lib/src/collection.dart index 3ddefd5..480343e 100644 --- a/lib/src/collection.dart +++ b/lib/src/collection.dart @@ -42,15 +42,15 @@ class Collection { Future init() async { logger..trace("Running in trace mode")..debug("Running in debug mode"); await videoServer.init(); - await parent.run(); + await parent.init(); logger.info("Video program initialized"); } /// Stops all cameras and disconnects from the hardware. Future dispose() async { - await videoServer.dispose(); parent.stopAll(); - parent.clear(); + parent.killAll(); + await videoServer.dispose(); } /// Restarts the video program. diff --git a/lib/src/parent_isolate.dart b/lib/src/parent_isolate.dart index 848146a..0483884 100644 --- a/lib/src/parent_isolate.dart +++ b/lib/src/parent_isolate.dart @@ -18,7 +18,7 @@ import "camera_isolate.dart"; /// from every camera, every frame, every second. That could be up to 5 MB per second of savings. class VideoController extends IsolateParent{ @override - Future run() async { + Future init() async { for (final name in CameraName.values) { if (name == CameraName.CAMERA_NAME_UNDEFINED) continue; await spawn( @@ -30,7 +30,7 @@ class VideoController extends IsolateParent{ } @override - void onData(IsolatePayload data) { + void onData(IsolatePayload data, Object id) { switch (data) { case DetailsPayload(): collection.videoServer.sendMessage(VideoData(details: data.details)); @@ -64,7 +64,7 @@ class VideoController extends IsolateParent{ final command = VideoCommand(details: CameraDetails(status: CameraStatus.CAMERA_DISABLED)); for (final name in CameraName.values) { if (name == CameraName.CAMERA_NAME_UNDEFINED) continue; - send(command, name); + send(data: command, id: name); } } } diff --git a/lib/src/server.dart b/lib/src/server.dart index 9fa628b..4449a95 100644 --- a/lib/src/server.dart +++ b/lib/src/server.dart @@ -13,7 +13,7 @@ class VideoServer extends RoverServer { if (wrapper.name != VideoCommand().messageName) return; final command = VideoCommand.fromBuffer(wrapper.data); sendMessage(command); // Echo the request - collection.parent.send(command, command.details.name); + collection.parent.send(data: command, id: command.details.name); } @override diff --git a/pubspec.lock b/pubspec.lock index 719a12b..9475db6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0816708f5fbcacca324d811297153fe3c8e047beb5c6752e12292d2974c17045" + sha256: "0f7b1783ddb1e4600580b8c00d0ddae5b06ae7f0382bd4fcce5db4df97b618e1" url: "https://pub.dev" source: hosted - version: "62.0.0" + version: "66.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "21862995c9932cd082f89d72ae5f5e2c110d1a0204ad06e4ebaee8307b76b834" + sha256: "5e8bdcda061d91da6b034d64d8e4026f355bcb8c3e7a0ac2da1523205a91a737" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.4.0" args: dependency: transitive description: @@ -68,10 +68,10 @@ packages: dependency: transitive description: name: coverage - sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" + sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76" url: "https://pub.dev" source: hosted - version: "1.6.3" + version: "1.7.2" crypto: dependency: transitive description: @@ -80,14 +80,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" - dart_internal: - dependency: transitive - description: - name: dart_internal - sha256: "689dccc3d5f62affd339534cca548dce12b3a6b32f0f10861569d3025efc0567" - url: "https://pub.dev" - source: hosted - version: "0.2.9" ffi: dependency: transitive description: @@ -156,10 +148,10 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: "4186c61b32f99e60f011f7160e32c89a758ae9b1d0c6d28e2c02ef0382300e2b" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.7.0" logger: dependency: transitive description: @@ -180,18 +172,18 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.11.0" mime: dependency: transitive description: @@ -227,10 +219,10 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" pool: dependency: transitive description: @@ -347,26 +339,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "67ec5684c7a19b2aba91d2831f3d305a6fd8e1504629c5818f8d64478abf4f38" + sha256: "694c108e13c6b35b15fcb0f8f03eddf8373f93b044c9497b5e81ce09f7381bda" url: "https://pub.dev" source: hosted - version: "1.24.4" + version: "1.25.1" test_api: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.0" test_core: dependency: transitive description: name: test_core - sha256: "6b753899253c38ca0523bb0eccff3934ec83d011705dae717c61ecf209e333c9" + sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4" url: "https://pub.dev" source: hosted - version: "0.5.4" + version: "0.6.0" typed_data: dependency: transitive description: @@ -379,26 +371,26 @@ packages: dependency: "direct main" description: name: typed_isolate - sha256: e9b8cc039c8ecc3367f7d2ade7feafa2164aa5e2f12df4fb06ae01b2ad47c355 + sha256: b5a4fb51cb65f60f281dc287dfb8a71395dad97d7480159cbcb95043240503d8 url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "3.0.0" very_good_analysis: dependency: "direct dev" description: name: very_good_analysis - sha256: "5e4ea72d2a9188630f0dd8f120a541de730090ef8863243fedca8267a84508b8" + sha256: "9ae7f3a3bd5764fb021b335ca28a34f040cd0ab6eec00a1b213b445dae58a4b8" url: "https://pub.dev" source: hosted - version: "5.0.0+1" + version: "5.1.0" vm_service: dependency: transitive description: name: vm_service - sha256: ada49637c27973c183dad90beb6bd781eea4c9f5f955d35da172de0af7bd3440 + sha256: a2662fb1f114f4296cf3f5a50786a2d888268d7776cf681aa17d660ffa23b246 url: "https://pub.dev" source: hosted - version: "11.8.0" + version: "14.0.0" watcher: dependency: transitive description: @@ -407,22 +399,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" + url: "https://pub.dev" + source: hosted + version: "0.4.2" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "939ab60734a4f8fa95feacb55804fa278de28bdeef38e616dc08e44a84adea23" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.3" webkit_inspection_protocol: dependency: transitive description: name: webkit_inspection_protocol - sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" yaml: dependency: transitive description: @@ -432,4 +432,4 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.0 <3.3.0" + dart: ">=3.2.0 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 5729303..35ce3ab 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: git: https://github.com/Levi-Lesches/opencv_ffi burt_network: git: https://github.com/BinghamtonRover/Networking - typed_isolate: ^1.0.1 + typed_isolate: ^3.0.0 dev_dependencies: test: ^1.21.0