From 8101f9509b5719d5366c820c3cfef1b08dbbc1d9 Mon Sep 17 00:00:00 2001 From: Mathieu Dejean-Servieres Date: Mon, 2 Sep 2024 19:37:03 +0200 Subject: [PATCH 1/3] chore(deps): bump dev dependencies (ffigen, melos, lints) fix(ffigen): fix class type for tflite bindings --- .../tensorflow_lite_bindings_generated.dart | 209 +++++++++--------- pubspec.yaml | 6 +- 2 files changed, 111 insertions(+), 104 deletions(-) diff --git a/lib/src/bindings/tensorflow_lite_bindings_generated.dart b/lib/src/bindings/tensorflow_lite_bindings_generated.dart index f4d7119..ac69f58 100644 --- a/lib/src/bindings/tensorflow_lite_bindings_generated.dart +++ b/lib/src/bindings/tensorflow_lite_bindings_generated.dart @@ -1997,25 +1997,6 @@ class TensorFlowLiteBindings { _TfLiteXNNPackDelegateWeightsCacheDeletePtr.asFunction< void Function(ffi.Pointer)>(); - /// Populates TfLiteGpuDelegateOptionsV2 as follows: - /// is_precision_loss_allowed = false - /// inference_preference = TFLITE_GPU_INFERENCE_PREFERENCE_FAST_SINGLE_ANSWER - /// priority1 = TFLITE_GPU_INFERENCE_PRIORITY_MAX_PRECISION - /// priority2 = TFLITE_GPU_INFERENCE_PRIORITY_AUTO - /// priority3 = TFLITE_GPU_INFERENCE_PRIORITY_AUTO - /// experimental_flags = TFLITE_GPU_EXPERIMENTAL_FLAGS_ENABLE_QUANT - /// max_delegated_partitions = 1 - TfLiteGpuDelegateOptionsV2 TfLiteGpuDelegateOptionsV2Default() { - return _TfLiteGpuDelegateOptionsV2Default(); - } - - late final _TfLiteGpuDelegateOptionsV2DefaultPtr = - _lookup>( - 'TfLiteGpuDelegateOptionsV2Default'); - late final _TfLiteGpuDelegateOptionsV2Default = - _TfLiteGpuDelegateOptionsV2DefaultPtr.asFunction< - TfLiteGpuDelegateOptionsV2 Function()>(); - /// Creates a new delegate instance that need to be destroyed with /// TfLiteGpuDelegateV2Delete when delegate is no longer used by TFLite. /// @@ -2055,14 +2036,33 @@ class TensorFlowLiteBindings { 'TfLiteGpuDelegateV2Delete'); late final _TfLiteGpuDelegateV2Delete = _TfLiteGpuDelegateV2DeletePtr .asFunction)>(); + + /// Populates TfLiteGpuDelegateOptionsV2 as follows: + /// is_precision_loss_allowed = false + /// inference_preference = TFLITE_GPU_INFERENCE_PREFERENCE_FAST_SINGLE_ANSWER + /// priority1 = TFLITE_GPU_INFERENCE_PRIORITY_MAX_PRECISION + /// priority2 = TFLITE_GPU_INFERENCE_PRIORITY_AUTO + /// priority3 = TFLITE_GPU_INFERENCE_PRIORITY_AUTO + /// experimental_flags = TFLITE_GPU_EXPERIMENTAL_FLAGS_ENABLE_QUANT + /// max_delegated_partitions = 1 + TfLiteGpuDelegateOptionsV2 TfLiteGpuDelegateOptionsV2Default() { + return _TfLiteGpuDelegateOptionsV2Default(); + } + + late final _TfLiteGpuDelegateOptionsV2DefaultPtr = + _lookup>( + 'TfLiteGpuDelegateOptionsV2Default'); + late final _TfLiteGpuDelegateOptionsV2Default = + _TfLiteGpuDelegateOptionsV2DefaultPtr.asFunction< + TfLiteGpuDelegateOptionsV2 Function()>(); } -class TfLiteModel extends ffi.Opaque {} +final class TfLiteModel extends ffi.Opaque {} -class TfLiteInterpreterOptions extends ffi.Opaque {} +final class TfLiteInterpreterOptions extends ffi.Opaque {} /// WARNING: This is an experimental interface that is subject to change. -class TfLiteDelegate extends ffi.Struct { +final class TfLiteDelegate extends ffi.Struct { /// Data that delegate needs to identify itself. This data is owned by the /// delegate. The delegate is owned in the user code, so the delegate is /// responsible for deallocating this when it is destroyed. @@ -2171,7 +2171,7 @@ abstract class TfLiteStatus { /// Forward declare so dependent structs and methods can reference these types /// prior to the struct definitions. -class TfLiteContext extends ffi.Struct { +final class TfLiteContext extends ffi.Struct { /// Number of tensors in the context. @ffi.Size() external int tensors_size; @@ -2418,9 +2418,16 @@ class TfLiteContext extends ffi.Struct { /// Fixed size list of integers. Used for dimensions and inputs/outputs tensor /// indices -class TfLiteIntArray extends ffi.Opaque {} +final class TfLiteIntArray extends ffi.Struct { + @ffi.Int() + external int size; -class TfLiteTensor extends ffi.Struct { + /// Context for why this is needed is in http://b/189926408#comment21 + @ffi.Array.multi([1]) + external ffi.Array data; +} + +final class TfLiteTensor extends ffi.Struct { /// The data type specification for data stored in `data`. This affects /// what member of `data` union should be used. @ffi.Int32() @@ -2521,7 +2528,7 @@ abstract class TfLiteType { } /// A union of pointers that points to memory for a given tensor. -class TfLitePtrUnion extends ffi.Union { +final class TfLitePtrUnion extends ffi.Union { /// Do not access these members directly, if possible, use /// GetTensorData(tensor) instead, otherwise only access .data, as other /// members are deprecated. @@ -2562,13 +2569,13 @@ class TfLitePtrUnion extends ffi.Union { } /// Half precision data type compatible with the C99 definition. -class TfLiteFloat16 extends ffi.Struct { +final class TfLiteFloat16 extends ffi.Struct { @ffi.Uint16() external int data; } /// Single-precision complex data type compatible with the C99 definition. -class TfLiteComplex64 extends ffi.Struct { +final class TfLiteComplex64 extends ffi.Struct { /// real and imaginary parts, respectively. @ffi.Float() external double re; @@ -2578,7 +2585,7 @@ class TfLiteComplex64 extends ffi.Struct { } /// Double-precision complex data type compatible with the C99 definition. -class TfLiteComplex128 extends ffi.Struct { +final class TfLiteComplex128 extends ffi.Struct { /// real and imaginary parts, respectively. @ffi.Double() external double re; @@ -2593,7 +2600,7 @@ class TfLiteComplex128 extends ffi.Struct { /// Parameters for asymmetric quantization. Quantized values can be converted /// back to float using: /// real_value = scale * (quantized_value - zero_point) -class TfLiteQuantizationParams extends ffi.Struct { +final class TfLiteQuantizationParams extends ffi.Struct { @ffi.Float() external double scale; @@ -2626,9 +2633,10 @@ abstract class TfLiteAllocationType { /// The delegates should use zero or positive integers to represent handles. /// -1 is reserved from unallocated status. typedef TfLiteBufferHandle = ffi.Int; +typedef DartTfLiteBufferHandle = int; /// Structure specifying the quantization used by the tensor, if-any. -class TfLiteQuantization extends ffi.Struct { +final class TfLiteQuantization extends ffi.Struct { /// The type of quantization held by params. @ffi.Int32() external int type; @@ -2651,7 +2659,7 @@ abstract class TfLiteQuantizationType { /// Parameters used to encode a sparse tensor. For detailed explanation of each /// field please refer to lite/schema/schema.fbs. -class TfLiteSparsity extends ffi.Struct { +final class TfLiteSparsity extends ffi.Struct { external ffi.Pointer traversal_order; external ffi.Pointer block_map; @@ -2663,7 +2671,7 @@ class TfLiteSparsity extends ffi.Struct { } /// Metadata to encode each dimension in a sparse tensor. -class TfLiteDimensionMetadata extends ffi.Struct { +final class TfLiteDimensionMetadata extends ffi.Struct { @ffi.Int32() external int format; @@ -2684,7 +2692,7 @@ abstract class TfLiteDimensionType { /// A structure representing an instance of a node. /// This structure only exhibits the inputs, outputs, user defined data and some /// node properties (like statefulness), not other features like the type. -class TfLiteNode extends ffi.Struct { +final class TfLiteNode extends ffi.Struct { /// Inputs to this node expressed as indices into the simulator's tensors. external ffi.Pointer inputs; @@ -2724,7 +2732,7 @@ class TfLiteNode extends ffi.Struct { external bool might_have_side_effect; } -class TfLiteRegistration extends ffi.Struct { +final class TfLiteRegistration extends ffi.Struct { /// Initializes the op from serialized data. /// Called only *once* for the lifetime of the op, so any one-time allocations /// should be made here (unless they depend on tensor sizes). @@ -2810,14 +2818,14 @@ class TfLiteRegistration extends ffi.Struct { external ffi.Pointer registration_external; } -class TfLiteRegistrationExternal extends ffi.Opaque {} +final class TfLiteRegistrationExternal extends ffi.Opaque {} /// An external context is a collection of information unrelated to the TF Lite /// framework, but useful to a subset of the ops. TF Lite knows very little /// about the actual contexts, but it keeps a list of them, and is able to /// refresh them if configurations like the number of recommended threads /// change. -class TfLiteExternalContext extends ffi.Struct { +final class TfLiteExternalContext extends ffi.Struct { @ffi.Int32() external int type; @@ -2852,7 +2860,7 @@ abstract class TfLiteExternalContextType { /// `TfLiteNode` of the delegate node. /// /// See also the `CreateDelegateParams` function in `interpreter.cc` details. -class TfLiteDelegateParams extends ffi.Struct { +final class TfLiteDelegateParams extends ffi.Struct { external ffi.Pointer delegate; external ffi.Pointer nodes_to_replace; @@ -2866,7 +2874,7 @@ class TfLiteDelegateParams extends ffi.Struct { /// of information required for a kernel to run during TfLiteRegistration::Eval. /// TODO(b/160955687): Move this field into TF_LITE_STATIC_MEMORY when TFLM /// builds with this flag by default internally. -class TfLiteEvalTensor extends ffi.Struct { +final class TfLiteEvalTensor extends ffi.Struct { /// A union of data pointers. The appropriate type should be used for a typed /// tensor based on `type`. external TfLitePtrUnion data; @@ -2891,7 +2899,7 @@ class TfLiteEvalTensor extends ffi.Struct { /// the same as with `TfLiteDelegate`. /// /// WARNING: This is an experimental interface that is subject to change. -class TfLiteOpaqueDelegateBuilder extends ffi.Struct { +final class TfLiteOpaqueDelegateBuilder extends ffi.Struct { /// Data that delegate needs to identify itself. This data is owned by the /// delegate. The delegate is owned in the user code, so the delegate is /// responsible for deallocating this when it is destroyed. @@ -2939,13 +2947,13 @@ class TfLiteOpaqueDelegateBuilder extends ffi.Struct { external int flags; } -class TfLiteOpaqueContext extends ffi.Opaque {} +final class TfLiteOpaqueContext extends ffi.Opaque {} -class TfLiteOpaqueDelegateStruct extends ffi.Opaque {} +final class TfLiteOpaqueDelegateStruct extends ffi.Opaque {} -class TfLiteOpaqueTensor extends ffi.Opaque {} +final class TfLiteOpaqueTensor extends ffi.Opaque {} -class TfLiteInterpreter extends ffi.Opaque {} +final class TfLiteInterpreter extends ffi.Opaque {} /// The enum for builtin operators. /// Note: CUSTOM, DELEGATE, and PLACEHOLDER_FOR_GREATER_OP_CODES are 3 special @@ -3112,18 +3120,17 @@ abstract class TfLiteBuiltinOperator { static const int kTfLiteBuiltinSign = 158; } -class TfLiteOpaqueNode extends ffi.Opaque {} +final class TfLiteOpaqueNode extends ffi.Opaque {} -typedef va_list = __builtin_va_list; -typedef __builtin_va_list = ffi.Pointer; +typedef va_list = ffi.Pointer; -class TfLiteSignatureRunner extends ffi.Opaque {} +final class TfLiteSignatureRunner extends ffi.Opaque {} /// Old version of `TfLiteRegistration` to maintain binary backward /// compatibility. /// WARNING: This structure is deprecated / not an official part of the API. /// It should be only used for binary backward compatibility. -class TfLiteRegistration_V1 extends ffi.Struct { +final class TfLiteRegistration_V1 extends ffi.Struct { external ffi.Pointer< ffi.NativeFunction< ffi.Pointer Function(ffi.Pointer context, @@ -3167,7 +3174,7 @@ abstract class TfLiteCoreMlDelegateEnabledDevices { static const int TfLiteCoreMlDelegateAllDevices = 1; } -class TfLiteCoreMlDelegateOptions extends ffi.Struct { +final class TfLiteCoreMlDelegateOptions extends ffi.Struct { /// Only create delegate when Neural Engine is available on the device. @ffi.Int32() external int enabled_devices; @@ -3211,7 +3218,7 @@ abstract class TFLGpuDelegateWaitType { /// Creates a new delegate instance that need to be destroyed with /// DeleteFlowDelegate when delegate is no longer used by tflite. -class TFLGpuDelegateOptions extends ffi.Struct { +final class TFLGpuDelegateOptions extends ffi.Struct { /// Allows to quantify tensors, downcast values, process in float16 etc. @ffi.Bool() external bool allow_precision_loss; @@ -3224,9 +3231,9 @@ class TFLGpuDelegateOptions extends ffi.Struct { external bool enable_quantization; } -class TfLiteXNNPackDelegateWeightsCache extends ffi.Opaque {} +final class TfLiteXNNPackDelegateWeightsCache extends ffi.Opaque {} -class TfLiteXNNPackDelegateOptions extends ffi.Struct { +final class TfLiteXNNPackDelegateOptions extends ffi.Struct { /// Number of threads to use in the thread pool. /// 0 or negative value means no thread pool used. @ffi.Int32() @@ -3250,57 +3257,10 @@ class TfLiteXNNPackDelegateOptions extends ffi.Struct { external bool handle_variable_ops; } -/// Encapsulated compilation/runtime tradeoffs. -abstract class TfLiteGpuInferenceUsage { - /// Delegate will be used only once, therefore, bootstrap/init time should - /// be taken into account. - static const int TFLITE_GPU_INFERENCE_PREFERENCE_FAST_SINGLE_ANSWER = 0; - - /// Prefer maximizing the throughput. Same delegate will be used repeatedly on - /// multiple inputs. - static const int TFLITE_GPU_INFERENCE_PREFERENCE_SUSTAINED_SPEED = 1; -} - -abstract class TfLiteGpuInferencePriority { - /// AUTO priority is needed when a single priority is the most important - /// factor. For example, - /// priority1 = MIN_LATENCY would result in the configuration that achieves - /// maximum performance. - static const int TFLITE_GPU_INFERENCE_PRIORITY_AUTO = 0; - static const int TFLITE_GPU_INFERENCE_PRIORITY_MAX_PRECISION = 1; - static const int TFLITE_GPU_INFERENCE_PRIORITY_MIN_LATENCY = 2; - static const int TFLITE_GPU_INFERENCE_PRIORITY_MIN_MEMORY_USAGE = 3; -} - -/// Used to toggle experimental flags used in the delegate. Note that this is a -/// bitmask, so the values should be 1, 2, 4, 8, ...etc. -abstract class TfLiteGpuExperimentalFlags { - static const int TFLITE_GPU_EXPERIMENTAL_FLAGS_NONE = 0; - - /// Enables inference on quantized models with the delegate. - /// NOTE: This is enabled in TfLiteGpuDelegateOptionsV2Default. - static const int TFLITE_GPU_EXPERIMENTAL_FLAGS_ENABLE_QUANT = 1; - - /// Enforces execution with the provided backend. - static const int TFLITE_GPU_EXPERIMENTAL_FLAGS_CL_ONLY = 2; - static const int TFLITE_GPU_EXPERIMENTAL_FLAGS_GL_ONLY = 4; - - /// Enable serialization of GPU kernels & model data. Speeds up initilization - /// at the cost of space on disk. - /// Delegate performs serialization the first time it is applied with a new - /// model or inference params. Later initializations are fast. - /// ModifyGraphWithDelegate will fail if data cannot be serialized. - /// - /// NOTE: User also needs to set serialization_dir & model_token in - /// TfLiteGpuDelegateOptionsV2. - /// Currently works only if CL backend is used. - static const int TFLITE_GPU_EXPERIMENTAL_FLAGS_ENABLE_SERIALIZATION = 8; -} - /// IMPORTANT: Always use TfLiteGpuDelegateOptionsV2Default() method to create /// new instance of TfLiteGpuDelegateOptionsV2, otherwise every new added option /// may break inference. -class TfLiteGpuDelegateOptionsV2 extends ffi.Struct { +final class TfLiteGpuDelegateOptionsV2 extends ffi.Struct { /// When set to zero, computations are carried out in maximal possible /// precision. Otherwise, the GPU may quantify tensors, downcast values, /// process in FP16 to increase performance. For most models precision loss is @@ -3371,6 +3331,53 @@ class TfLiteGpuDelegateOptionsV2 extends ffi.Struct { external ffi.Pointer model_token; } +/// Encapsulated compilation/runtime tradeoffs. +abstract class TfLiteGpuInferenceUsage { + /// Delegate will be used only once, therefore, bootstrap/init time should + /// be taken into account. + static const int TFLITE_GPU_INFERENCE_PREFERENCE_FAST_SINGLE_ANSWER = 0; + + /// Prefer maximizing the throughput. Same delegate will be used repeatedly on + /// multiple inputs. + static const int TFLITE_GPU_INFERENCE_PREFERENCE_SUSTAINED_SPEED = 1; +} + +abstract class TfLiteGpuInferencePriority { + /// AUTO priority is needed when a single priority is the most important + /// factor. For example, + /// priority1 = MIN_LATENCY would result in the configuration that achieves + /// maximum performance. + static const int TFLITE_GPU_INFERENCE_PRIORITY_AUTO = 0; + static const int TFLITE_GPU_INFERENCE_PRIORITY_MAX_PRECISION = 1; + static const int TFLITE_GPU_INFERENCE_PRIORITY_MIN_LATENCY = 2; + static const int TFLITE_GPU_INFERENCE_PRIORITY_MIN_MEMORY_USAGE = 3; +} + +/// Used to toggle experimental flags used in the delegate. Note that this is a +/// bitmask, so the values should be 1, 2, 4, 8, ...etc. +abstract class TfLiteGpuExperimentalFlags { + static const int TFLITE_GPU_EXPERIMENTAL_FLAGS_NONE = 0; + + /// Enables inference on quantized models with the delegate. + /// NOTE: This is enabled in TfLiteGpuDelegateOptionsV2Default. + static const int TFLITE_GPU_EXPERIMENTAL_FLAGS_ENABLE_QUANT = 1; + + /// Enforces execution with the provided backend. + static const int TFLITE_GPU_EXPERIMENTAL_FLAGS_CL_ONLY = 2; + static const int TFLITE_GPU_EXPERIMENTAL_FLAGS_GL_ONLY = 4; + + /// Enable serialization of GPU kernels & model data. Speeds up initilization + /// at the cost of space on disk. + /// Delegate performs serialization the first time it is applied with a new + /// model or inference params. Later initializations are fast. + /// ModifyGraphWithDelegate will fail if data cannot be serialized. + /// + /// NOTE: User also needs to set serialization_dir & model_token in + /// TfLiteGpuDelegateOptionsV2. + /// Currently works only if CL backend is used. + static const int TFLITE_GPU_EXPERIMENTAL_FLAGS_ENABLE_SERIALIZATION = 8; +} + const int TFLITE_XNNPACK_DELEGATE_FLAG_QS8 = 1; const int TFLITE_XNNPACK_DELEGATE_FLAG_QU8 = 2; diff --git a/pubspec.yaml b/pubspec.yaml index b7c80fd..e1cb5dc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,11 +32,11 @@ dependencies: ffi: ^2.0.1 dev_dependencies: - ffigen: ^7.2.11 - flutter_lints: ^2.0.1 + ffigen: ^10.0.0 + flutter_lints: ^4.0.0 flutter_test: sdk: flutter - melos: ^3.1.0 + melos: ^6.1.0 test: ^1.6.4 # For information on the generic Dart part of this file, see the From be97988c30c526746c5411709768efec74105dad Mon Sep 17 00:00:00 2001 From: Mathieu Dejean-Servieres Date: Mon, 2 Sep 2024 19:37:03 +0200 Subject: [PATCH 2/3] chore(melos): update readme and add melos for FFI code generation --- README.md | 44 ++++++++++++++++++++++++++++++-------------- melos.yaml | 6 ++++++ 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index a0a3144..0d3bd90 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

- + Platform @@ -18,7 +18,6 @@ -

@@ -40,16 +39,14 @@ Thanks! TensorFlow Lite Flutter plugin provides a flexible and fast solution for accessing TensorFlow Lite interpreter and performing inference. The API is similar to the TFLite Java and Swift APIs. It directly binds to TFLite C API making it efficient (low-latency). Offers acceleration support using NNAPI, GPU delegates on Android, Metal and CoreML delegates on iOS, and XNNPack delegate on Desktop platforms. - ## Key Features -* Multi-platform Support for Android and iOS -* Flexibility to use any TFLite Model. -* Acceleration using multi-threading. -* Similar structure as TensorFlow Lite Java API. -* Inference speeds close to native Android Apps built using the Java API. -* Run inference in different isolates to prevent jank in UI thread. - +- Multi-platform Support for Android and iOS +- Flexibility to use any TFLite Model. +- Acceleration using multi-threading. +- Similar structure as TensorFlow Lite Java API. +- Inference speeds close to native Android Apps built using the Java API. +- Run inference in different isolates to prevent jank in UI thread. ## (Important) Initial setup : Add dynamic libraries to your app @@ -133,7 +130,7 @@ install( ## TFLite Flutter Helper Library -The helper library has been deprecated. New development underway for a replacement at https://github.com/google/flutter-mediapipe. Current timeline is to have wide support by the end of August, 2023. +The helper library has been deprecated. New development underway for a replacement at . Current timeline is to have wide support by the end of August, 2023. ## Import @@ -144,11 +141,12 @@ import 'package:tflite_flutter/tflite_flutter.dart'; ## Usage instructions ### Import the libraries + In the dependency section of `pubspec.yaml` file, add `tflite_flutter: ^0.10.1` (adjust the version accordingly based on the latest release) ### Creating the Interpreter -* **From asset** +- **From asset** Place `your_model.tflite` in `assets` directory. Make sure to include assets in `pubspec.yaml`. @@ -160,9 +158,10 @@ Refer to the documentation for info on creating interpreter from buffer or file. ### Performing inference -* **For single input and output** +- **For single input and output** Use `void run(Object input, Object output)`. + ```dart // For ex: if input tensor shape [1,5] and type is float32 var input = [[1.23, 6.54, 7.81, 3.21, 2.22]]; @@ -177,7 +176,7 @@ Refer to the documentation for info on creating interpreter from buffer or file. print(output); ``` -* **For multiple inputs and outputs** +- **For multiple inputs and outputs** Use `void runForMultipleInputs(List inputs, Map outputs)`. @@ -225,3 +224,20 @@ await isolateInterpreter.runForMultipleInputs(inputs, outputs); ``` By using `IsolateInterpreter`, the inference runs in a separate isolate. This ensures that the main isolate, responsible for UI tasks, remains unblocked and responsive. + +## Contribute to this package + +This package is managed using [melos](https://pub.dev/packages/melos). Before starting to work on the project, make sure to run the bootstrap command. + +```sh +dart pub global activate melos # Install or activate melos globally +melos bootstrap # Initialize the workspace and bootstrap the package +``` + +### Generated code + +This package uses [ffigen](https://pub.dev/packages/ffigen) to generate FFI bindings. To run code generation, you can use the following melos command: + +```sh +melos run ffigen +``` diff --git a/melos.yaml b/melos.yaml index 4242060..df79663 100644 --- a/melos.yaml +++ b/melos.yaml @@ -27,3 +27,9 @@ command: branch: main linkToCommits: true workspaceChangelog: true + +scripts: + ffigen: + name: ffigen + description: Call ffigen and generate bindings for `src/tensorflow_lite/c_api.h`. + run: flutter pub run ffigen --config ffigen_tensorflow_lite.yaml \ No newline at end of file From 38e430a320986e85db4c950b2ef36331e7c1d6c5 Mon Sep 17 00:00:00 2001 From: Craig Labenz Date: Tue, 3 Sep 2024 09:59:10 +0200 Subject: [PATCH 3/3] analysis fix --- example/reinforcement_learning/lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/reinforcement_learning/lib/main.dart b/example/reinforcement_learning/lib/main.dart index c28b926..c37cb90 100644 --- a/example/reinforcement_learning/lib/main.dart +++ b/example/reinforcement_learning/lib/main.dart @@ -28,7 +28,7 @@ class PlaneStrike extends StatefulWidget { // This widget is the root of your application. @override - _PlaneStrikeState createState() => _PlaneStrikeState(); + State createState() => _PlaneStrikeState(); } class _PlaneStrikeState extends State {