diff --git a/.github/ISSUE_TEMPLATE/typed_data.md b/.github/ISSUE_TEMPLATE/typed_data.md new file mode 100644 index 00000000..81670b6c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/typed_data.md @@ -0,0 +1,5 @@ +--- +name: "package:typed_data" +about: "Create a bug or file a feature request against package:typed_data." +labels: "package:typed_data" +--- \ No newline at end of file diff --git a/.github/labeler.yml b/.github/labeler.yml index ce828680..9428f8f3 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -39,3 +39,7 @@ "package:platform": - changed-files: - any-glob-to-any-file: 'pkgs/platform/**' + +"package:typed_data": + - changed-files: + - any-glob-to-any-file: 'pkgs/typed_data/**' diff --git a/.github/workflows/typed_data.yaml b/.github/workflows/typed_data.yaml new file mode 100644 index 00000000..dcd63c6a --- /dev/null +++ b/.github/workflows/typed_data.yaml @@ -0,0 +1,76 @@ +name: package:typed_data + +on: + # Run CI on pushes to the main branch, and on PRs against main. + push: + branches: [ main ] + paths: + - '.github/workflows/typed_data.yaml' + - 'pkgs/typed_data/**' + pull_request: + branches: [ main ] + paths: + - '.github/workflows/typed_data.yaml' + - 'pkgs/typed_data/**' + schedule: + - cron: "0 0 * * 0" +env: + PUB_ENVIRONMENT: bot.github + +defaults: + run: + working-directory: pkgs/typed_data/ + +jobs: + # Check code formatting and static analysis on a single OS (linux) + # against Dart dev. + analyze: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sdk: [dev] + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 + with: + sdk: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Check formatting + run: dart format --output=none --set-exit-if-changed . + if: always() && steps.install.outcome == 'success' + - name: Analyze code + run: dart analyze --fatal-infos + if: always() && steps.install.outcome == 'success' + + # Run tests on a matrix consisting of two dimensions: + # 1. OS: ubuntu-latest, (macos-latest, windows-latest) + # 2. release channel: dev + test: + needs: analyze + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + # Add macos-latest and/or windows-latest if relevant for this package. + os: [ubuntu-latest] + sdk: [3.5, dev] + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 + with: + sdk: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Run VM tests + run: dart test --platform vm + if: always() && steps.install.outcome == 'success' + - name: Run Chrome tests + run: dart test --platform chrome + if: always() && steps.install.outcome == 'success' + - name: Run Chrome tests - wasm + run: dart test --platform chrome --compiler dart2wasm + if: always() && steps.install.outcome == 'success' diff --git a/README.md b/README.md index 8adfa3a9..634751fb 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ This repository is home to various Dart packages under the [dart.dev](https://pu | [os_detect](pkgs/os_detect/) | Platform independent OS detection. | [![pub package](https://img.shields.io/pub/v/os_detect.svg)](https://pub.dev/packages/os_detect) | | [path](pkgs/path/) | A string-based path manipulation library for all of the path operations you know and love. | [![pub package](https://img.shields.io/pub/v/path.svg)](https://pub.dev/packages/path) | | [platform](pkgs/platform/) | A pluggable, mockable platform information abstraction for Dart. | [![pub package](https://img.shields.io/pub/v/platform.svg)](https://pub.dev/packages/platform) | +| [typed_data](pkgs/typed_data/) | Utility functions and classes related to the dart:typed_data library. | [![pub package](https://img.shields.io/pub/v/typed_data.svg)](https://pub.dev/packages/typed_data) | ## Publishing automation diff --git a/pkgs/typed_data/.gitignore b/pkgs/typed_data/.gitignore new file mode 100644 index 00000000..efbbce15 --- /dev/null +++ b/pkgs/typed_data/.gitignore @@ -0,0 +1,10 @@ +.buildlog +.DS_Store +.idea +.dart_tool/ +.pub/ +.settings/ +build/ +packages +.packages +pubspec.lock diff --git a/pkgs/typed_data/AUTHORS b/pkgs/typed_data/AUTHORS new file mode 100644 index 00000000..e8063a8c --- /dev/null +++ b/pkgs/typed_data/AUTHORS @@ -0,0 +1,6 @@ +# Below is a list of people and organizations that have contributed +# to the project. Names should be added to the list like so: +# +# Name/Organization + +Google Inc. diff --git a/pkgs/typed_data/CHANGELOG.md b/pkgs/typed_data/CHANGELOG.md new file mode 100644 index 00000000..8763a879 --- /dev/null +++ b/pkgs/typed_data/CHANGELOG.md @@ -0,0 +1,69 @@ +## 1.4.0 + +* The type of the `buffer` constructor argument to `TypedDataBuffer` is now + `TypeDataList` (instead of `List`). While this is breaking change + statically there was a runtime cast that makes this change a no-op in + practice. +* Require Dart 3.5 +* Move to `dart-lang/core` monorepo. + +## 1.3.2 + +* Added package topics to the pubspec file. +* Require Dart 2.17. + +## 1.3.1 + +* Switch to using `package:lints`. +* Populate the pubspec `repository` field. + +## 1.3.0 + +* Stable release for null safety. +* Update SDK constraints to `>=2.12.0-0 <3.0.0` based on beta release + guidelines. + +## 1.2.0 + +* Add typed queue classes such as `Uint8Queue`. These classes implement both + `Queue` and `List` with a highly-efficient typed-data-backed implementation. + Their `sublist()` methods also return typed data classes. +* Update min Dart SDK to `2.4.0`. + +## 1.1.6 + +* Set max SDK version to `<3.0.0`, and adjust other dependencies. + +## 1.1.5 + +* Undo unnecessary SDK version constraint tweak. + +## 1.1.4 + +* Expand the SDK version constraint to include `<2.0.0-dev.infinity`. + +## 1.1.3 + +* Fix all strong-mode warnings. + +## 1.1.2 + +* Fix a bug where `TypedDataBuffer.insertAll` could fail to insert some elements + of an `Iterable`. + +## 1.1.1 + +* Optimize `insertAll` with an `Iterable` argument and no end-point. + +## 1.1.0 + +* Add `start` and `end` parameters to the `addAll()` and `insertAll()` methods + for the typed data buffer classes. These allow efficient concatenation of + slices of existing typed data. + +* Make `addAll()` for typed data buffer classes more efficient for lists, + especially typed data lists. + +## 1.0.0 + +* ChangeLog starts here diff --git a/pkgs/typed_data/LICENSE b/pkgs/typed_data/LICENSE new file mode 100644 index 00000000..dbd2843a --- /dev/null +++ b/pkgs/typed_data/LICENSE @@ -0,0 +1,27 @@ +Copyright 2015, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pkgs/typed_data/README.md b/pkgs/typed_data/README.md new file mode 100644 index 00000000..512f43bb --- /dev/null +++ b/pkgs/typed_data/README.md @@ -0,0 +1,21 @@ +[![Dart CI](https://github.com/dart-lang/core/actions/workflows/typed_data.yaml/badge.svg)](https://github.com/dart-lang/core/actions/workflows/typed_data.yaml) +[![pub package](https://img.shields.io/pub/v/typed_data.svg)](https://pub.dev/packages/typed_data) +[![package publisher](https://img.shields.io/pub/publisher/typed_data.svg)](https://pub.dev/packages/typed_data/publisher) + +Helper libraries for working with typed data lists. + +The `typed_data` package contains utility functions and classes that makes working with typed data lists easier. + +## Using + +The `typed_data` package can be imported using: + +```dart +import 'package:typed_data/typed_data.dart'; +``` + +## Typed buffers + +Typed buffers are growable lists backed by typed arrays. These are similar to +the growable lists created by `[]` or `[]`, but store typed data +like a typed data list. diff --git a/pkgs/typed_data/analysis_options.yaml b/pkgs/typed_data/analysis_options.yaml new file mode 100644 index 00000000..543787c0 --- /dev/null +++ b/pkgs/typed_data/analysis_options.yaml @@ -0,0 +1,13 @@ +# https://dart.dev/guides/language/analysis-options +include: package:dart_flutter_team_lints/analysis_options.yaml + +analyzer: + language: + strict-casts: true + +linter: + rules: + - avoid_unused_constructor_parameters + - cancel_subscriptions + - no_adjacent_strings_in_list + - package_api_docs diff --git a/pkgs/typed_data/lib/src/typed_buffer.dart b/pkgs/typed_data/lib/src/typed_buffer.dart new file mode 100644 index 00000000..dcb2c585 --- /dev/null +++ b/pkgs/typed_data/lib/src/typed_buffer.dart @@ -0,0 +1,417 @@ +// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:collection' show ListBase; +import 'dart:typed_data'; + +abstract class TypedDataBuffer extends ListBase { + static const int _initialLength = 8; + + /// The underlying data buffer. + TypedDataList _buffer; + + /// The length of the list being built. + int _length; + + TypedDataBuffer(TypedDataList buffer) + : _buffer = buffer, + _length = buffer.length; + + @override + int get length => _length; + + @override + E operator [](int index) { + if (index >= length) throw RangeError.index(index, this); + return _buffer[index]; + } + + @override + void operator []=(int index, E value) { + if (index >= length) throw RangeError.index(index, this); + _buffer[index] = value; + } + + @override + set length(int newLength) { + if (newLength < _length) { + var defaultValue = _defaultValue; + for (var i = newLength; i < _length; i++) { + _buffer[i] = defaultValue; + } + } else if (newLength > _buffer.length) { + TypedDataList newBuffer; + if (_buffer.isEmpty) { + newBuffer = _createBuffer(newLength); + } else { + newBuffer = _createBiggerBuffer(newLength); + } + newBuffer.setRange(0, _length, _buffer); + _buffer = newBuffer; + } + _length = newLength; + } + + void _add(E value) { + if (_length == _buffer.length) _grow(_length); + _buffer[_length++] = value; + } + + // We override the default implementation of `add` because it grows the list + // by setting the length in increments of one. We want to grow by doubling + // capacity in most cases. + @override + void add(E element) { + _add(element); + } + + /// Appends all objects of [values] to the end of this buffer. + /// + /// This adds values from [start] (inclusive) to [end] (exclusive) in + /// [values]. If [end] is omitted, it defaults to adding all elements of + /// [values] after [start]. + /// + /// The [start] value must be non-negative. The [values] iterable must have at + /// least [start] elements, and if [end] is specified, it must be greater than + /// or equal to [start] and [values] must have at least [end] elements. + @override + void addAll(Iterable values, [int start = 0, int? end]) { + RangeError.checkNotNegative(start, 'start'); + if (end != null && start > end) { + throw RangeError.range(end, start, null, 'end'); + } + + _addAll(values, start, end); + } + + /// Inserts all objects of [values] at position [index] in this list. + /// + /// This adds values from [start] (inclusive) to [end] (exclusive) in + /// [values]. If [end] is omitted, it defaults to adding all elements of + /// [values] after [start]. + /// + /// The [start] value must be non-negative. The [values] iterable must have at + /// least [start] elements, and if [end] is specified, it must be greater than + /// or equal to [start] and [values] must have at least [end] elements. + @override + void insertAll(int index, Iterable values, [int start = 0, int? end]) { + RangeError.checkValidIndex(index, this, 'index', _length + 1); + RangeError.checkNotNegative(start, 'start'); + if (end != null) { + if (start > end) { + throw RangeError.range(end, start, null, 'end'); + } + if (start == end) return; + } + + // If we're adding to the end of the list anyway, use [_addAll]. This lets + // us avoid converting [values] into a list even if [end] is null, since we + // can add values iteratively to the end of the list. We can't do so in the + // center because copying the trailing elements every time is non-linear. + if (index == _length) { + _addAll(values, start, end); + return; + } + + if (end == null && values is List) { + end = values.length; + } + if (end != null) { + _insertKnownLength(index, values, start, end); + return; + } + + // Add elements at end, growing as appropriate, then put them back at + // position [index] using flip-by-double-reverse. + var writeIndex = _length; + var skipCount = start; + for (var value in values) { + if (skipCount > 0) { + skipCount--; + continue; + } + if (writeIndex == _buffer.length) { + _grow(writeIndex); + } + _buffer[writeIndex++] = value; + } + + if (skipCount > 0) { + throw StateError('Too few elements'); + } + if (end != null && writeIndex < end) { + throw RangeError.range(end, start, writeIndex, 'end'); + } + + // Swap [index.._length) and [_length..writeIndex) by double-reversing. + _reverse(_buffer, index, _length); + _reverse(_buffer, _length, writeIndex); + _reverse(_buffer, index, writeIndex); + _length = writeIndex; + return; + } + + // Reverses the range [start..end) of buffer. + static void _reverse(List buffer, int start, int end) { + end--; // Point to last element, not after last element. + while (start < end) { + var first = buffer[start]; + var last = buffer[end]; + buffer[end] = first; + buffer[start] = last; + start++; + end--; + } + } + + /// Does the same thing as [addAll]. + /// + /// This allows [addAll] and [insertAll] to share implementation without a + /// subclass unexpectedly overriding both when it intended to only override + /// [addAll]. + void _addAll(Iterable values, [int start = 0, int? end]) { + if (values is List) end ??= values.length; + + // If we know the length of the segment to add, do so with [addRange]. This + // way we know how much to grow the buffer in advance, and it may be even + // more efficient for typed data input. + if (end != null) { + _insertKnownLength(_length, values, start, end); + return; + } + + // Otherwise, just add values one at a time. + var i = 0; + for (var value in values) { + if (i >= start) add(value); + i++; + } + if (i < start) throw StateError('Too few elements'); + } + + /// Like [insertAll], but with a guaranteed non-`null` [start] and [end]. + void _insertKnownLength(int index, Iterable values, int start, int end) { + if (values is List) { + if (start > values.length || end > values.length) { + throw StateError('Too few elements'); + } + } + + var valuesLength = end - start; + var newLength = _length + valuesLength; + _ensureCapacity(newLength); + + _buffer.setRange( + index + valuesLength, _length + valuesLength, _buffer, index); + _buffer.setRange(index, index + valuesLength, values, start); + _length = newLength; + } + + @override + void insert(int index, E element) { + if (index < 0 || index > _length) { + throw RangeError.range(index, 0, _length); + } + if (_length < _buffer.length) { + _buffer.setRange(index + 1, _length + 1, _buffer, index); + _buffer[index] = element; + _length++; + return; + } + var newBuffer = _createBiggerBuffer(null); + newBuffer.setRange(0, index, _buffer); + newBuffer.setRange(index + 1, _length + 1, _buffer, index); + newBuffer[index] = element; + _length++; + _buffer = newBuffer; + } + + /// Ensures that [_buffer] is at least [requiredCapacity] long, + /// + /// Grows the buffer if necessary, preserving existing data. + void _ensureCapacity(int requiredCapacity) { + if (requiredCapacity <= _buffer.length) return; + var newBuffer = _createBiggerBuffer(requiredCapacity); + newBuffer.setRange(0, _length, _buffer); + _buffer = newBuffer; + } + + /// Create a bigger buffer. + /// + /// This method determines how much bigger a bigger buffer should + /// be. If [requiredCapacity] is not null, it will be at least that + /// size. It will always have at least have double the capacity of + /// the current buffer. + TypedDataList _createBiggerBuffer(int? requiredCapacity) { + var newLength = _buffer.length * 2; + if (requiredCapacity != null && newLength < requiredCapacity) { + newLength = requiredCapacity; + } else if (newLength < _initialLength) { + newLength = _initialLength; + } + return _createBuffer(newLength); + } + + /// Grows the buffer. + /// + /// This copies the first [length] elements into the new buffer. + void _grow(int length) { + _buffer = _createBiggerBuffer(null)..setRange(0, length, _buffer); + } + + @override + void setRange(int start, int end, Iterable iterable, [int skipCount = 0]) { + if (end > _length) throw RangeError.range(end, 0, _length); + _setRange(start, end, iterable, skipCount); + } + + /// Like [setRange], but with no bounds checking. + void _setRange(int start, int end, Iterable source, int skipCount) { + if (source is TypedDataBuffer) { + _buffer.setRange(start, end, source._buffer, skipCount); + } else { + _buffer.setRange(start, end, source, skipCount); + } + } + + // TypedData. + + int get elementSizeInBytes => _buffer.elementSizeInBytes; + + int get lengthInBytes => _length * _buffer.elementSizeInBytes; + + int get offsetInBytes => _buffer.offsetInBytes; + + /// Returns the underlying [ByteBuffer]. + /// + /// The returned buffer may be replaced by operations that change the [length] + /// of this list. + /// + /// The buffer may be larger than [lengthInBytes] bytes, but never smaller. + ByteBuffer get buffer => _buffer.buffer; + + // Specialization for the specific type. + + // Return zero for integers, 0.0 for floats, etc. + // Used to fill buffer when changing length. + E get _defaultValue; + + // Create a new typed list to use as buffer. + TypedDataList _createBuffer(int size); +} + +abstract class _IntBuffer extends TypedDataBuffer { + _IntBuffer(super.buffer); + + @override + int get _defaultValue => 0; +} + +abstract class _FloatBuffer extends TypedDataBuffer { + _FloatBuffer(super.buffer); + + @override + double get _defaultValue => 0.0; +} + +class Uint8Buffer extends _IntBuffer { + Uint8Buffer([int initialLength = 0]) : super(Uint8List(initialLength)); + + @override + Uint8List _createBuffer(int size) => Uint8List(size); +} + +class Int8Buffer extends _IntBuffer { + Int8Buffer([int initialLength = 0]) : super(Int8List(initialLength)); + + @override + Int8List _createBuffer(int size) => Int8List(size); +} + +class Uint8ClampedBuffer extends _IntBuffer { + Uint8ClampedBuffer([int initialLength = 0]) + : super(Uint8ClampedList(initialLength)); + + @override + Uint8ClampedList _createBuffer(int size) => Uint8ClampedList(size); +} + +class Uint16Buffer extends _IntBuffer { + Uint16Buffer([int initialLength = 0]) : super(Uint16List(initialLength)); + + @override + Uint16List _createBuffer(int size) => Uint16List(size); +} + +class Int16Buffer extends _IntBuffer { + Int16Buffer([int initialLength = 0]) : super(Int16List(initialLength)); + + @override + Int16List _createBuffer(int size) => Int16List(size); +} + +class Uint32Buffer extends _IntBuffer { + Uint32Buffer([int initialLength = 0]) : super(Uint32List(initialLength)); + + @override + Uint32List _createBuffer(int size) => Uint32List(size); +} + +class Int32Buffer extends _IntBuffer { + Int32Buffer([int initialLength = 0]) : super(Int32List(initialLength)); + + @override + Int32List _createBuffer(int size) => Int32List(size); +} + +class Uint64Buffer extends _IntBuffer { + Uint64Buffer([int initialLength = 0]) : super(Uint64List(initialLength)); + + @override + Uint64List _createBuffer(int size) => Uint64List(size); +} + +class Int64Buffer extends _IntBuffer { + Int64Buffer([int initialLength = 0]) : super(Int64List(initialLength)); + + @override + Int64List _createBuffer(int size) => Int64List(size); +} + +class Float32Buffer extends _FloatBuffer { + Float32Buffer([int initialLength = 0]) : super(Float32List(initialLength)); + + @override + Float32List _createBuffer(int size) => Float32List(size); +} + +class Float64Buffer extends _FloatBuffer { + Float64Buffer([int initialLength = 0]) : super(Float64List(initialLength)); + + @override + Float64List _createBuffer(int size) => Float64List(size); +} + +class Int32x4Buffer extends TypedDataBuffer { + static final Int32x4 _zero = Int32x4(0, 0, 0, 0); + + Int32x4Buffer([int initialLength = 0]) : super(Int32x4List(initialLength)); + + @override + Int32x4 get _defaultValue => _zero; + + @override + Int32x4List _createBuffer(int size) => Int32x4List(size); +} + +class Float32x4Buffer extends TypedDataBuffer { + Float32x4Buffer([int initialLength = 0]) + : super(Float32x4List(initialLength)); + + @override + Float32x4 get _defaultValue => Float32x4.zero(); + + @override + Float32x4List _createBuffer(int size) => Float32x4List(size); +} diff --git a/pkgs/typed_data/lib/src/typed_queue.dart b/pkgs/typed_data/lib/src/typed_queue.dart new file mode 100644 index 00000000..84ce5297 --- /dev/null +++ b/pkgs/typed_data/lib/src/typed_queue.dart @@ -0,0 +1,695 @@ +// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:collection'; +import 'dart:typed_data'; + +import 'package:collection/collection.dart'; + +import 'typed_buffer.dart'; + +/// The shared superclass of all the typed queue subclasses. +abstract class _TypedQueue> with ListMixin { + /// The underlying data buffer. + TypedDataList _table; + + int _head; + int _tail; + + /// Create an empty queue. + _TypedQueue(this._table) + : _head = 0, + _tail = 0; + + // Iterable interface. + + @override + int get length => (_tail - _head) & (_table.length - 1); + + @override + List toList({bool growable = true}) { + var list = growable ? _createBuffer(length) : _createList(length); + _writeToList(list); + return list; + } + + @override + QueueList cast() { + if (this is QueueList) return this as QueueList; + throw UnsupportedError('$this cannot be cast to the desired type.'); + } + + @Deprecated('Use `cast` instead') + QueueList retype() => cast(); + + // Queue interface. + + void addLast(E value) { + _table[_tail] = value; + _tail = (_tail + 1) & (_table.length - 1); + if (_head == _tail) _growAtCapacity(); + } + + void addFirst(E value) { + _head = (_head - 1) & (_table.length - 1); + _table[_head] = value; + if (_head == _tail) _growAtCapacity(); + } + + E removeFirst() { + if (_head == _tail) throw StateError('No element'); + var result = _table[_head]; + _head = (_head + 1) & (_table.length - 1); + return result; + } + + @override + E removeLast() { + if (_head == _tail) throw StateError('No element'); + _tail = (_tail - 1) & (_table.length - 1); + return _table[_tail]; + } + + // List interface. + + @override + void add(E value) => addLast(value); + + @override + set length(int value) { + RangeError.checkNotNegative(value, 'length'); + + var delta = value - length; + if (delta >= 0) { + var needsToGrow = _table.length <= value; + if (needsToGrow) _growTo(value); + _tail = (_tail + delta) & (_table.length - 1); + + // If we didn't copy into a new table, make sure that we overwrite the + // existing data so that users don't accidentally depend on it still + // existing. + if (!needsToGrow) fillRange(value - delta, value, _defaultValue); + } else { + removeRange(value, length); + } + } + + @override + E operator [](int index) { + RangeError.checkValidIndex(index, this, null, length); + return _table[(_head + index) & (_table.length - 1)]; + } + + @override + void operator []=(int index, E value) { + RangeError.checkValidIndex(index, this); + _table[(_head + index) & (_table.length - 1)] = value; + } + + @override + void removeRange(int start, int end) { + var length = this.length; + RangeError.checkValidRange(start, end, length); + + // Special-case removing an initial or final range because we can do it very + // efficiently by adjusting `_head` or `_tail`. + if (start == 0) { + _head = (_head + end) & (_table.length - 1); + return; + } + + var elementsAfter = length - end; + if (elementsAfter == 0) { + _tail = (_head + start) & (_table.length - 1); + return; + } + + // Choose whether to copy from the beginning of the end of the queue based + // on which will require fewer copied elements. + var removedElements = end - start; + if (start < elementsAfter) { + setRange(removedElements, end, this); + _head = (_head + removedElements) & (_table.length - 1); + } else { + setRange(start, length - removedElements, this, end); + _tail = (_tail - removedElements) & (_table.length - 1); + } + } + + @override + void setRange(int start, int end, Iterable iterable, [int skipCount = 0]) { + RangeError.checkValidRange(start, end, length); + if (start == end) return; + + var targetStart = (_head + start) & (_table.length - 1); + var targetEnd = (_head + end) & (_table.length - 1); + var targetIsContiguous = targetStart < targetEnd; + if (identical(iterable, this)) { + // If we're copying this queue to itself, we can copy [_table] in directly + // which requires some annoying case analysis but in return bottoms out on + // an extremely efficient `memmove` call. However, we may need to do three + // copies to avoid overwriting data we'll need to use later. + var sourceStart = (_head + skipCount) & (_table.length - 1); + var sourceEnd = (sourceStart + (end - start)) & (_table.length - 1); + if (sourceStart == targetStart) return; + + var sourceIsContiguous = sourceStart < sourceEnd; + if (targetIsContiguous && sourceIsContiguous) { + // If both the source and destination ranges are contiguous, we can + // do a single [setRange]. Hooray! + _table.setRange(targetStart, targetEnd, _table, sourceStart); + } else if (!targetIsContiguous && !sourceIsContiguous) { + // If neither range is contiguous, we need to do three copies. + if (sourceStart > targetStart) { + // [=====| targetEnd targetStart |======] + // [========| sourceEnd sourceStart |===] + + // Copy front to back. + var startGap = sourceStart - targetStart; + var firstEnd = _table.length - startGap; + _table.setRange(targetStart, firstEnd, _table, sourceStart); + _table.setRange(firstEnd, _table.length, _table); + _table.setRange(0, targetEnd, _table, startGap); + } else if (sourceEnd < targetEnd) { + // [=====| targetEnd targetStart |======] + // [==| sourceEnd sourceStart |=========] + + // Copy back to front. + var firstStart = targetEnd - sourceEnd; + _table.setRange(firstStart, targetEnd, _table); + _table.setRange(0, firstStart, _table, _table.length - firstStart); + _table.setRange(targetStart, _table.length, _table, sourceStart); + } + } else if (sourceStart < targetEnd) { + // Copying twice is safe here as long as we copy front to back. + if (sourceIsContiguous) { + // [=====| targetEnd targetStart |======] + // [ |===========| sourceEnd ] + // sourceStart + _table.setRange(targetStart, _table.length, _table, sourceStart); + _table.setRange(0, targetEnd, _table, + sourceStart + (_table.length - targetStart)); + } else { + // targetEnd + // [ targetStart |===========| ] + // [=====| sourceEnd sourceStart |======] + var firstEnd = _table.length - sourceStart; + _table.setRange(targetStart, firstEnd, _table, sourceStart); + _table.setRange(firstEnd, targetEnd, _table); + } + } else { + // Copying twice is safe here as long as we copy back to front. This + // also covers the case where there's no overlap between the source and + // target ranges, in which case the direction doesn't matter. + if (sourceIsContiguous) { + // [=====| targetEnd targetStart |======] + // [ sourceStart |===========| ] + // sourceEnd + _table.setRange(0, targetEnd, _table, + sourceStart + (_table.length - targetStart)); + _table.setRange(targetStart, _table.length, _table, sourceStart); + } else { + // targetStart + // [ |===========| targetEnd ] + // [=====| sourceEnd sourceStart |======] + var firstStart = targetEnd - sourceEnd; + _table.setRange(firstStart, targetEnd, _table); + _table.setRange(targetStart, firstStart, _table, sourceStart); + } + } + } else if (targetIsContiguous) { + // If the range is contiguous within the table, we can set it with a + // single underlying [setRange] call. + _table.setRange(targetStart, targetEnd, iterable, skipCount); + } else if (iterable is List) { + // If the range isn't contiguous and [iterable] is actually a [List] (but + // not this queue), set it with two underlying [setRange] calls. + _table.setRange(targetStart, _table.length, iterable, skipCount); + _table.setRange( + 0, targetEnd, iterable, skipCount + (_table.length - targetStart)); + } else { + // If [iterable] isn't a [List], we don't want to make two different + // [setRange] calls because it could materialize a lazy iterable twice. + // Instead we just fall back to the default iteration-based + // implementation. + super.setRange(start, end, iterable, skipCount); + } + } + + @override + void fillRange(int start, int end, [E? value]) { + var startInTable = (_head + start) & (_table.length - 1); + var endInTable = (_head + end) & (_table.length - 1); + if (startInTable <= endInTable) { + _table.fillRange(startInTable, endInTable, value); + } else { + _table.fillRange(startInTable, _table.length, value); + _table.fillRange(0, endInTable, value); + } + } + + @override + L sublist(int start, [int? end]) { + var length = this.length; + var nonNullEnd = RangeError.checkValidRange(start, end, length); + + var list = _createList(nonNullEnd - start); + _writeToList(list, start, nonNullEnd); + return list; + } + + // Internal helper functions. + + /// Writes the contents of `this` between [start] (which defaults to 0) and + /// [end] (which defaults to [length]) to the beginning of [target]. + /// + /// This is functionally identical to `target.setRange(0, end - start, this, + /// start)`, but it's more efficient when [target] is typed data. + /// + /// Returns the number of elements written to [target]. + int _writeToList(List target, [int? start, int? end]) { + start ??= 0; + end ??= length; + assert(target.length >= end - start); + assert(start <= end); + + var elementsToWrite = end - start; + var startInTable = (_head + start) & (_table.length - 1); + var endInTable = (_head + end) & (_table.length - 1); + if (startInTable <= endInTable) { + target.setRange(0, elementsToWrite, _table, startInTable); + } else { + var firstPartSize = _table.length - startInTable; + target.setRange(0, firstPartSize, _table, startInTable); + target.setRange(firstPartSize, firstPartSize + endInTable, _table, 0); + } + return elementsToWrite; + } + + /// Assumes the table is currently full to capacity, and grows it to the next + /// power of two. + void _growAtCapacity() { + assert(_head == _tail); + + var newTable = _createList(_table.length * 2); + + // We can't use [_writeToList] here because when `_head == _tail` it thinks + // the queue is empty rather than full. + var partitionPoint = _table.length - _head; + newTable.setRange(0, partitionPoint, _table, _head); + if (partitionPoint != _table.length) { + newTable.setRange(partitionPoint, _table.length, _table); + } + _head = 0; + _tail = _table.length; + _table = newTable; + } + + /// Grows the tableso it's at least large enough size to include that many + /// elements. + void _growTo(int newElementCount) { + assert(newElementCount >= length); + + // Add some extra room to ensure that there's room for more elements after + // expansion. + newElementCount += newElementCount >> 1; + var newTable = _createList(_nextPowerOf2(newElementCount)); + _tail = _writeToList(newTable); + _table = newTable; + _head = 0; + } + + // Specialization for the specific type. + + // Create a new typed list. + L _createList(int size); + + // Create a new typed buffer of the given type. + TypedDataBuffer _createBuffer(int size); + + /// The default value used to fill the queue when changing length. + E get _defaultValue; +} + +abstract class _IntQueue> + extends _TypedQueue { + _IntQueue(super.queue); + + @override + int get _defaultValue => 0; +} + +abstract class _FloatQueue> + extends _TypedQueue { + _FloatQueue(super.queue); + + @override + double get _defaultValue => 0.0; +} + +/// A [QueueList] that efficiently stores 8-bit unsigned integers. +/// +/// For long queues, this implementation can be considerably more space- and +/// time-efficient than a default [QueueList] implementation. +/// +/// Integers stored in this are truncated to their low eight bits, interpreted +/// as an unsigned 8-bit integer with values in the range 0 to 255. +class Uint8Queue extends _IntQueue implements QueueList { + /// Creates an empty [Uint8Queue] with the given initial internal capacity (in + /// elements). + Uint8Queue([int? initialCapacity]) + : super(Uint8List(_chooseRealInitialCapacity(initialCapacity))); + + /// Creates a [Uint8Queue] with the same length and contents as [elements]. + factory Uint8Queue.fromList(List elements) => + Uint8Queue(elements.length)..addAll(elements); + + @override + Uint8List _createList(int size) => Uint8List(size); + @override + Uint8Buffer _createBuffer(int size) => Uint8Buffer(size); +} + +/// A [QueueList] that efficiently stores 8-bit signed integers. +/// +/// For long queues, this implementation can be considerably more space- and +/// time-efficient than a default [QueueList] implementation. +/// +/// Integers stored in this are truncated to their low eight bits, interpreted +/// as a signed 8-bit two's complement integer with values in the range -128 to +/// +127. +class Int8Queue extends _IntQueue implements QueueList { + /// Creates an empty [Int8Queue] with the given initial internal capacity (in + /// elements). + Int8Queue([int? initialCapacity]) + : super(Int8List(_chooseRealInitialCapacity(initialCapacity))); + + /// Creates a [Int8Queue] with the same length and contents as [elements]. + factory Int8Queue.fromList(List elements) => + Int8Queue(elements.length)..addAll(elements); + + @override + Int8List _createList(int size) => Int8List(size); + @override + Int8Buffer _createBuffer(int size) => Int8Buffer(size); +} + +/// A [QueueList] that efficiently stores 8-bit unsigned integers. +/// +/// For long queues, this implementation can be considerably more space- and +/// time-efficient than a default [QueueList] implementation. +/// +/// Integers stored in this are clamped to an unsigned eight bit value. That is, +/// all values below zero are stored as zero and all values above 255 are stored +/// as 255. +class Uint8ClampedQueue extends _IntQueue + implements QueueList { + /// Creates an empty [Uint8ClampedQueue] with the given initial internal + /// capacity (in elements). + Uint8ClampedQueue([int? initialCapacity]) + : super(Uint8ClampedList(_chooseRealInitialCapacity(initialCapacity))); + + /// Creates a [Uint8ClampedQueue] with the same length and contents as + /// [elements]. + factory Uint8ClampedQueue.fromList(List elements) => + Uint8ClampedQueue(elements.length)..addAll(elements); + + @override + Uint8ClampedList _createList(int size) => Uint8ClampedList(size); + @override + Uint8ClampedBuffer _createBuffer(int size) => Uint8ClampedBuffer(size); +} + +/// A [QueueList] that efficiently stores 16-bit unsigned integers. +/// +/// For long queues, this implementation can be considerably more space- and +/// time-efficient than a default [QueueList] implementation. +/// +/// Integers stored in this are truncated to their low 16 bits, interpreted as +/// an unsigned 16-bit integer with values in the range 0 to 65535. +class Uint16Queue extends _IntQueue implements QueueList { + /// Creates an empty [Uint16Queue] with the given initial internal capacity + /// (in elements). + Uint16Queue([int? initialCapacity]) + : super(Uint16List(_chooseRealInitialCapacity(initialCapacity))); + + /// Creates a [Uint16Queue] with the same length and contents as [elements]. + factory Uint16Queue.fromList(List elements) => + Uint16Queue(elements.length)..addAll(elements); + + @override + Uint16List _createList(int size) => Uint16List(size); + @override + Uint16Buffer _createBuffer(int size) => Uint16Buffer(size); +} + +/// A [QueueList] that efficiently stores 16-bit signed integers. +/// +/// For long queues, this implementation can be considerably more space- and +/// time-efficient than a default [QueueList] implementation. +/// +/// Integers stored in this are truncated to their low 16 bits, interpreted as a +/// signed 16-bit two's complement integer with values in the range -32768 to +/// +32767. +class Int16Queue extends _IntQueue implements QueueList { + /// Creates an empty [Int16Queue] with the given initial internal capacity (in + /// elements). + Int16Queue([int? initialCapacity]) + : super(Int16List(_chooseRealInitialCapacity(initialCapacity))); + + /// Creates a [Int16Queue] with the same length and contents as [elements]. + factory Int16Queue.fromList(List elements) => + Int16Queue(elements.length)..addAll(elements); + + @override + Int16List _createList(int size) => Int16List(size); + @override + Int16Buffer _createBuffer(int size) => Int16Buffer(size); +} + +/// A [QueueList] that efficiently stores 32-bit unsigned integers. +/// +/// For long queues, this implementation can be considerably more space- and +/// time-efficient than a default [QueueList] implementation. +/// +/// Integers stored in this are truncated to their low 32 bits, interpreted as +/// an unsigned 32-bit integer with values in the range 0 to 4294967295. +class Uint32Queue extends _IntQueue implements QueueList { + /// Creates an empty [Uint32Queue] with the given initial internal capacity + /// (in elements). + Uint32Queue([int? initialCapacity]) + : super(Uint32List(_chooseRealInitialCapacity(initialCapacity))); + + /// Creates a [Uint32Queue] with the same length and contents as [elements]. + factory Uint32Queue.fromList(List elements) => + Uint32Queue(elements.length)..addAll(elements); + + @override + Uint32List _createList(int size) => Uint32List(size); + @override + Uint32Buffer _createBuffer(int size) => Uint32Buffer(size); +} + +/// A [QueueList] that efficiently stores 32-bit signed integers. +/// +/// For long queues, this implementation can be considerably more space- and +/// time-efficient than a default [QueueList] implementation. +/// +/// Integers stored in this are truncated to their low 32 bits, interpreted as a +/// signed 32-bit two's complement integer with values in the range -2147483648 +/// to 2147483647. +class Int32Queue extends _IntQueue implements QueueList { + /// Creates an empty [Int32Queue] with the given initial internal capacity (in + /// elements). + Int32Queue([int? initialCapacity]) + : super(Int32List(_chooseRealInitialCapacity(initialCapacity))); + + /// Creates a [Int32Queue] with the same length and contents as [elements]. + factory Int32Queue.fromList(List elements) => + Int32Queue(elements.length)..addAll(elements); + + @override + Int32List _createList(int size) => Int32List(size); + @override + Int32Buffer _createBuffer(int size) => Int32Buffer(size); +} + +/// A [QueueList] that efficiently stores 64-bit unsigned integers. +/// +/// For long queues, this implementation can be considerably more space- and +/// time-efficient than a default [QueueList] implementation. +/// +/// Integers stored in this are truncated to their low 64 bits, interpreted as +/// an unsigned 64-bit integer with values in the range 0 to +/// 18446744073709551615. +class Uint64Queue extends _IntQueue implements QueueList { + /// Creates an empty [Uint64Queue] with the given initial internal capacity + /// (in elements). + Uint64Queue([int? initialCapacity]) + : super(Uint64List(_chooseRealInitialCapacity(initialCapacity))); + + /// Creates a [Uint64Queue] with the same length and contents as [elements]. + factory Uint64Queue.fromList(List elements) => + Uint64Queue(elements.length)..addAll(elements); + + @override + Uint64List _createList(int size) => Uint64List(size); + @override + Uint64Buffer _createBuffer(int size) => Uint64Buffer(size); +} + +/// A [QueueList] that efficiently stores 64-bit signed integers. +/// +/// For long queues, this implementation can be considerably more space- and +/// time-efficient than a default [QueueList] implementation. +/// +/// Integers stored in this are truncated to their low 64 bits, interpreted as a +/// signed 64-bit two's complement integer with values in the range +/// -9223372036854775808 to +9223372036854775807. +class Int64Queue extends _IntQueue implements QueueList { + /// Creates an empty [Int64Queue] with the given initial internal capacity (in + /// elements). + Int64Queue([int? initialCapacity]) + : super(Int64List(_chooseRealInitialCapacity(initialCapacity))); + + /// Creates a [Int64Queue] with the same length and contents as [elements]. + factory Int64Queue.fromList(List elements) => + Int64Queue(elements.length)..addAll(elements); + + @override + Int64List _createList(int size) => Int64List(size); + @override + Int64Buffer _createBuffer(int size) => Int64Buffer(size); +} + +/// A [QueueList] that efficiently stores IEEE 754 single-precision binary +/// floating-point numbers. +/// +/// For long queues, this implementation can be considerably more space- and +/// time-efficient than a default [QueueList] implementation. +/// +/// Doubles stored in this are converted to the nearest single-precision value. +/// Values read are converted to a double value with the same value. +class Float32Queue extends _FloatQueue + implements QueueList { + /// Creates an empty [Float32Queue] with the given initial internal capacity + /// (in elements). + Float32Queue([int? initialCapacity]) + : super(Float32List(_chooseRealInitialCapacity(initialCapacity))); + + /// Creates a [Float32Queue] with the same length and contents as [elements]. + factory Float32Queue.fromList(List elements) => + Float32Queue(elements.length)..addAll(elements); + + @override + Float32List _createList(int size) => Float32List(size); + @override + Float32Buffer _createBuffer(int size) => Float32Buffer(size); +} + +/// A [QueueList] that efficiently stores IEEE 754 double-precision binary +/// floating-point numbers. +/// +/// For long queues, this implementation can be considerably more space- and +/// time-efficient than a default [QueueList] implementation. +class Float64Queue extends _FloatQueue + implements QueueList { + /// Creates an empty [Float64Queue] with the given initial internal capacity + /// (in elements). + Float64Queue([int? initialCapacity]) + : super(Float64List(_chooseRealInitialCapacity(initialCapacity))); + + /// Creates a [Float64Queue] with the same length and contents as [elements]. + factory Float64Queue.fromList(List elements) => + Float64Queue(elements.length)..addAll(elements); + + @override + Float64List _createList(int size) => Float64List(size); + @override + Float64Buffer _createBuffer(int size) => Float64Buffer(size); +} + +/// A [QueueList] that efficiently stores [Int32x4] numbers. +/// +/// For long queues, this implementation can be considerably more space- and +/// time-efficient than a default [QueueList] implementation. +class Int32x4Queue extends _TypedQueue + implements QueueList { + static final Int32x4 _zero = Int32x4(0, 0, 0, 0); + + /// Creates an empty [Int32x4Queue] with the given initial internal capacity + /// (in elements). + Int32x4Queue([int? initialCapacity]) + : super(Int32x4List(_chooseRealInitialCapacity(initialCapacity))); + + /// Creates a [Int32x4Queue] with the same length and contents as [elements]. + factory Int32x4Queue.fromList(List elements) => + Int32x4Queue(elements.length)..addAll(elements); + + @override + Int32x4List _createList(int size) => Int32x4List(size); + @override + Int32x4Buffer _createBuffer(int size) => Int32x4Buffer(size); + @override + Int32x4 get _defaultValue => _zero; +} + +/// A [QueueList] that efficiently stores [Float32x4] numbers. +/// +/// For long queues, this implementation can be considerably more space- and +/// time-efficient than a default [QueueList] implementation. +class Float32x4Queue extends _TypedQueue + implements QueueList { + /// Creates an empty [Float32x4Queue] with the given initial internal capacity + /// (in elements). + Float32x4Queue([int? initialCapacity]) + : super(Float32x4List(_chooseRealInitialCapacity(initialCapacity))); + + /// Creates a [Float32x4Queue] with the same length and contents as + /// [elements]. + factory Float32x4Queue.fromList(List elements) => + Float32x4Queue(elements.length)..addAll(elements); + + @override + Float32x4List _createList(int size) => Float32x4List(size); + @override + Float32x4Buffer _createBuffer(int size) => Float32x4Buffer(size); + @override + Float32x4 get _defaultValue => Float32x4.zero(); +} + +/// The initial capacity of queues if the user doesn't specify one. +const _defaultInitialCapacity = 16; + +/// Choose the next-highest power of two given a user-specified +/// [initialCapacity] for a queue. +int _chooseRealInitialCapacity(int? initialCapacity) { + if (initialCapacity == null || initialCapacity < _defaultInitialCapacity) { + return _defaultInitialCapacity; + } else if (!_isPowerOf2(initialCapacity)) { + return _nextPowerOf2(initialCapacity); + } else { + return initialCapacity; + } +} + +/// Whether [number] is a power of two. +/// +/// Only works for positive numbers. +bool _isPowerOf2(int number) => (number & (number - 1)) == 0; + +/// Rounds [number] up to the nearest power of 2. +/// +/// If [number] is a power of 2 already, it is returned. +/// +/// Only works for positive numbers. +int _nextPowerOf2(int number) { + assert(number > 0); + number = (number << 1) - 1; + for (;;) { + var nextNumber = number & (number - 1); + if (nextNumber == 0) return number; + number = nextNumber; + } +} diff --git a/pkgs/typed_data/lib/typed_buffers.dart b/pkgs/typed_data/lib/typed_buffers.dart new file mode 100644 index 00000000..05c8993d --- /dev/null +++ b/pkgs/typed_data/lib/typed_buffers.dart @@ -0,0 +1,16 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +/// Growable typed-data lists. +/// +/// These lists works just as a typed-data list, except that they are growable. +/// They use an underlying buffer, and when that buffer becomes too small, it +/// is replaced by a new buffer. +/// +/// That means that using the `buffer` getter is not guaranteed +/// to return the same result each time it is used, and that the buffer may +/// be larger than what the list is using. +library; + +export 'src/typed_buffer.dart' hide TypedDataBuffer; diff --git a/pkgs/typed_data/lib/typed_data.dart b/pkgs/typed_data/lib/typed_data.dart new file mode 100644 index 00000000..cc94c32c --- /dev/null +++ b/pkgs/typed_data/lib/typed_data.dart @@ -0,0 +1,9 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +/// Utilities and functionality related to the "dart:typed_data" library. +library; + +export 'src/typed_queue.dart'; +export 'typed_buffers.dart'; diff --git a/pkgs/typed_data/pubspec.yaml b/pkgs/typed_data/pubspec.yaml new file mode 100644 index 00000000..a03331fc --- /dev/null +++ b/pkgs/typed_data/pubspec.yaml @@ -0,0 +1,18 @@ +name: typed_data +version: 1.4.0 +description: >- + Utility functions and classes related to the dart:typed_data library. +repository: https://github.com/dart-lang/core/tree/main/pkgs/typed_data + +topics: + - data-structures + +environment: + sdk: ^3.5.0 + +dependencies: + collection: ^1.15.0 + +dev_dependencies: + dart_flutter_team_lints: ^3.0.0 + test: ^1.16.6 diff --git a/pkgs/typed_data/test/queue_test.dart b/pkgs/typed_data/test/queue_test.dart new file mode 100644 index 00000000..c0b1368d --- /dev/null +++ b/pkgs/typed_data/test/queue_test.dart @@ -0,0 +1,315 @@ +// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// ignore_for_file: avoid_function_literals_in_foreach_calls + +import 'package:test/test.dart'; +import 'package:typed_data/typed_data.dart'; + +/// The initial capacity of queues if the user doesn't specify one. +const capacity = 16; + +void main() { + group('Uint8Queue()', () { + test('creates an empty Uint8Queue', () { + expect(Uint8Queue(), isEmpty); + }); + + test('takes an initial capacity', () { + expect(Uint8Queue(100), isEmpty); + }); + }); + + group('add() adds an element to the end', () { + forEachInternalRepresentation((queue) { + queue.add(16); + expect(queue, equals(oneThrough(capacity))); + }); + }); + + group('addFirst() adds an element to the beginning', () { + forEachInternalRepresentation((queue) { + queue.addFirst(0); + expect(queue, equals([0, ...oneThrough(capacity - 1)])); + }); + }); + + group('removeFirst() removes an element from the beginning', () { + forEachInternalRepresentation((queue) { + expect(queue.removeFirst(), equals(1)); + expect(queue, equals(oneThrough(capacity - 1).skip(1))); + }); + + test('throws a StateError for an empty queue', () { + expect(Uint8Queue().removeFirst, throwsStateError); + }); + }); + + group('removeLast() removes an element from the end', () { + forEachInternalRepresentation((queue) { + expect(queue.removeLast(), equals(15)); + expect(queue, equals(oneThrough(capacity - 2))); + }); + + test('throws a StateError for an empty queue', () { + expect(Uint8Queue().removeLast, throwsStateError); + }); + }); + + group('removeRange()', () { + group('removes a prefix', () { + forEachInternalRepresentation((queue) { + queue.removeRange(0, 5); + expect(queue, equals(oneThrough(capacity - 1).skip(5))); + }); + }); + + group('removes a suffix', () { + forEachInternalRepresentation((queue) { + queue.removeRange(10, 15); + expect(queue, equals(oneThrough(capacity - 6))); + }); + }); + + group('removes from the middle', () { + forEachInternalRepresentation((queue) { + queue.removeRange(5, 10); + expect(queue, equals([1, 2, 3, 4, 5, 11, 12, 13, 14, 15])); + }); + }); + + group('removes everything', () { + forEachInternalRepresentation((queue) { + queue.removeRange(0, 15); + expect(queue, isEmpty); + }); + }); + + test('throws a RangeError for an invalid range', () { + expect(() => Uint8Queue().removeRange(0, 1), throwsRangeError); + }); + }); + + group('setRange()', () { + group('sets a range to the contents of an iterable', () { + forEachInternalRepresentation((queue) { + queue.setRange(5, 10, oneThrough(10).map((n) => 100 + n), 2); + expect(queue, + [1, 2, 3, 4, 5, 103, 104, 105, 106, 107, 11, 12, 13, 14, 15]); + }); + }); + + group('sets a range to the contents of a list', () { + forEachInternalRepresentation((queue) { + queue.setRange(5, 10, oneThrough(10).map((n) => 100 + n).toList(), 2); + expect(queue, + [1, 2, 3, 4, 5, 103, 104, 105, 106, 107, 11, 12, 13, 14, 15]); + }); + }); + + group( + 'sets a range to a section of the same queue overlapping at the ' + 'beginning', () { + forEachInternalRepresentation((queue) { + queue.setRange(5, 10, queue, 2); + expect(queue, [1, 2, 3, 4, 5, 3, 4, 5, 6, 7, 11, 12, 13, 14, 15]); + }); + }); + + group('sets a range to a section of the same queue overlapping at the end', + () { + forEachInternalRepresentation((queue) { + queue.setRange(5, 10, queue, 6); + expect(queue, [1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 11, 12, 13, 14, 15]); + }); + }); + + test('throws a RangeError for an invalid range', () { + expect(() => Uint8Queue().setRange(0, 1, [1]), throwsRangeError); + }); + }); + + group('length returns the length', () { + forEachInternalRepresentation((queue) { + expect(queue.length, equals(15)); + }); + }); + + group('length=', () { + group('empties', () { + forEachInternalRepresentation((queue) { + queue.length = 0; + expect(queue, isEmpty); + }); + }); + + group('shrinks', () { + forEachInternalRepresentation((queue) { + queue.length = 5; + expect(queue, equals([1, 2, 3, 4, 5])); + }); + }); + + group('grows', () { + forEachInternalRepresentation((queue) { + queue.length = 20; + expect( + queue, + equals(oneThrough(capacity - 1) + + List.filled(20 - (capacity - 1), 0))); + }); + }); + + group('zeroes out existing data', () { + forEachInternalRepresentation((queue) { + queue.length = 0; + queue.length = 15; + expect(queue, equals([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])); + }); + }); + + test('throws a RangeError if length is less than 0', () { + expect(() => Uint8Queue().length = -1, throwsRangeError); + }); + }); + + group('[]', () { + group('returns individual entries', () { + forEachInternalRepresentation((queue) { + for (var i = 0; i < capacity - 1; i++) { + expect(queue[i], equals(i + 1)); + } + }); + }); + + test('throws a RangeError if the index is less than 0', () { + var queue = Uint8Queue.fromList([1, 2, 3]); + expect(() => queue[-1], throwsRangeError); + }); + + test( + 'throws a RangeError if the index is greater than or equal to the ' + 'length', () { + var queue = Uint8Queue.fromList([1, 2, 3]); + expect(() => queue[3], throwsRangeError); + }); + }); + + group('[]=', () { + group('sets individual entries', () { + forEachInternalRepresentation((queue) { + for (var i = 0; i < capacity - 1; i++) { + queue[i] = 100 + i; + } + expect(queue, equals(List.generate(capacity - 1, (i) => 100 + i))); + }); + }); + + test('throws a RangeError if the index is less than 0', () { + var queue = Uint8Queue.fromList([1, 2, 3]); + expect(() { + queue[-1] = 0; + }, throwsRangeError); + }); + + test( + 'throws a RangeError if the index is greater than or equal to the ' + 'length', () { + var queue = Uint8Queue.fromList([1, 2, 3]); + expect(() { + queue[3] = 4; + }, throwsRangeError); + }); + }); + + group('throws a modification error for', () { + late Uint8Queue queue; + setUp(() { + queue = Uint8Queue.fromList([1, 2, 3]); + }); + + test('add', () { + expect(() => queue.forEach((_) => queue.add(4)), + throwsConcurrentModificationError); + }); + + test('addAll', () { + expect(() => queue.forEach((_) => queue.addAll([4, 5, 6])), + throwsConcurrentModificationError); + }); + + test('addFirst', () { + expect(() => queue.forEach((_) => queue.addFirst(0)), + throwsConcurrentModificationError); + }); + + test('removeFirst', () { + expect(() => queue.forEach((_) => queue.removeFirst()), + throwsConcurrentModificationError); + }); + + test('removeLast', () { + expect(() => queue.forEach((_) => queue.removeLast()), + throwsConcurrentModificationError); + }); + + test('length=', () { + expect(() => queue.forEach((_) => queue.length = 1), + throwsConcurrentModificationError); + }); + }); +} + +/// Runs [callback] in multiple tests, all with queues containing numbers from +/// one through 15 in various different internal states. +void forEachInternalRepresentation(void Function(Uint8Queue queue) callback) { + // Test with a queue whose internal table has plenty of room. + group("for a queue that's below capacity", () { + // Test with a queue whose elements are in one contiguous block, so `_head < + // _tail`. + test('with contiguous elements', () { + callback(Uint8Queue(capacity * 2)..addAll(oneThrough(capacity - 1))); + }); + + // Test with a queue whose elements are split across the ends of the table, + // so `_head > _tail`. + test('with an internal gap', () { + var queue = Uint8Queue(capacity * 2); + for (var i = capacity ~/ 2; i < capacity; i++) { + queue.add(i); + } + for (var i = capacity ~/ 2 - 1; i > 0; i--) { + queue.addFirst(i); + } + callback(queue); + }); + }); + + // Test with a queue whose internal table will need to expand if one more + // element is added. + group("for a queue that's at capacity", () { + test('with contiguous elements', () { + callback(Uint8Queue()..addAll(oneThrough(capacity - 1))); + }); + + test('with an internal gap', () { + var queue = Uint8Queue(); + for (var i = capacity ~/ 2; i < capacity; i++) { + queue.add(i); + } + for (var i = capacity ~/ 2 - 1; i > 0; i--) { + queue.addFirst(i); + } + callback(queue); + }); + }); +} + +/// Returns a list containing the integers from one through [n]. +List oneThrough(int n) => List.generate(n, (i) => i + 1); + +/// Returns a matcher that expects that a closure throws a +/// [ConcurrentModificationError]. +final throwsConcurrentModificationError = + throwsA(const TypeMatcher()); diff --git a/pkgs/typed_data/test/typed_buffers_test.dart b/pkgs/typed_data/test/typed_buffers_test.dart new file mode 100644 index 00000000..9d73040f --- /dev/null +++ b/pkgs/typed_data/test/typed_buffers_test.dart @@ -0,0 +1,567 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +@TestOn('!vm') +library; + +import 'dart:typed_data'; + +import 'package:test/test.dart'; +import 'package:typed_data/src/typed_buffer.dart'; + +const List browserSafeIntSamples = [ + 0x8000000000000000, // 2^63 + 0x100000001, + 0x100000000, // 2^32 + 0x0ffffffff, + 0xaaaaaaaa, + 0x80000001, + 0x80000000, // 2^31 + 0x7fffffff, + 0x55555555, + 0x10001, + 0x10000, // 2^16 + 0x0ffff, + 0xaaaa, + 0x8001, + 0x8000, // 2^15 + 0x7fff, + 0x5555, + 0x101, + 0x100, // 2^8 + 0x0ff, + 0xaa, + 0x81, + 0x80, // 2^7 + 0x7f, + 0x55, + 0x02, + 0x01, + 0x00 +]; + +void main() { + initTests(browserSafeIntSamples); +} + +void initTests(List intSamples) { + testUint(intSamples, 8, Uint8Buffer.new); + testInt(intSamples, 8, Int8Buffer.new); + test('Uint8ClampedBuffer', () { + testIntBuffer(intSamples, 8, 0, 255, Uint8ClampedBuffer.new, clampUint8); + }); + testUint(intSamples, 16, Uint16Buffer.new); + testInt(intSamples, 16, Int16Buffer.new); + testUint(intSamples, 32, Uint32Buffer.new); + + testInt(intSamples, 32, Int32Buffer.new); + + testUint(intSamples, 64, Uint64Buffer.new, + // JS doesn't support 64-bit ints, so only test this on the VM. + testOn: 'dart-vm'); + testInt(intSamples, 64, Int64Buffer.new, + // JS doesn't support 64-bit ints, so only test this on the VM. + testOn: 'dart-vm'); + + testInt32x4Buffer(intSamples); + + var roundedFloatSamples = floatSamples.map(roundToFloat).toList(); + testFloatBuffer(32, roundedFloatSamples, Float32Buffer.new, roundToFloat); + testFloatBuffer(64, doubleSamples, Float64Buffer.new, (x) => x); + + testFloat32x4Buffer(roundedFloatSamples); + + group('addAll', () { + for (var type in ['a list', 'an iterable']) { + group('with $type', () { + late Iterable source; + late Uint8Buffer buffer; + setUp(() { + source = [1, 2, 3, 4, 5]; + if (type == 'an iterable') { + source = (source as List).reversed.toList().reversed; + } + buffer = Uint8Buffer(); + }); + + test('adds values to the buffer', () { + buffer.addAll(source, 1, 4); + expect(buffer, equals([2, 3, 4])); + + buffer.addAll(source, 4); + expect(buffer, equals([2, 3, 4, 5])); + + buffer.addAll(source, 0, 1); + expect(buffer, equals([2, 3, 4, 5, 1])); + }); + + test('does nothing for empty slices', () { + buffer.addAll([6, 7, 8, 9, 10]); + + buffer.addAll(source, 0, 0); + expect(buffer, equals([6, 7, 8, 9, 10])); + + buffer.addAll(source, 3, 3); + expect(buffer, equals([6, 7, 8, 9, 10])); + + buffer.addAll(source, 5); + expect(buffer, equals([6, 7, 8, 9, 10])); + + buffer.addAll(source, 5, 5); + expect(buffer, equals([6, 7, 8, 9, 10])); + }); + + test('throws errors for invalid start and end', () { + expect(() => buffer.addAll(source, -1), throwsRangeError); + expect(() => buffer.addAll(source, -1, 2), throwsRangeError); + expect(() => buffer.addAll(source, 10), throwsStateError); + expect(() => buffer.addAll(source, 10, 11), throwsStateError); + expect(() => buffer.addAll(source, 3, 2), throwsRangeError); + expect(() => buffer.addAll(source, 3, 10), throwsStateError); + expect(() => buffer.addAll(source, 3, -1), throwsRangeError); + }); + }); + } + }); + + group('insertAll', () { + for (var type in ['a list', 'an iterable']) { + group('with $type', () { + late Iterable source; + late Uint8Buffer buffer; + setUp(() { + source = [1, 2, 3, 4, 5]; + if (type == 'an iterable') { + source = (source as List).reversed.toList().reversed; + } + buffer = Uint8Buffer()..addAll([6, 7, 8, 9, 10]); + }); + + test('inserts values into the buffer', () { + buffer.insertAll(0, source, 1, 4); + expect(buffer, equals([2, 3, 4, 6, 7, 8, 9, 10])); + + buffer.insertAll(3, source, 4); + expect(buffer, equals([2, 3, 4, 5, 6, 7, 8, 9, 10])); + + buffer.insertAll(5, source, 0, 1); + expect(buffer, equals([2, 3, 4, 5, 6, 1, 7, 8, 9, 10])); + }); + + // Regression test for #1. + test('inserts values into the buffer after removeRange()', () { + buffer.removeRange(1, 4); + buffer.insertAll(1, source); + expect(buffer, equals([6, 1, 2, 3, 4, 5, 10])); + }); + + test('does nothing for empty slices', () { + buffer.insertAll(1, source, 0, 0); + expect(buffer, equals([6, 7, 8, 9, 10])); + + buffer.insertAll(2, source, 3, 3); + expect(buffer, equals([6, 7, 8, 9, 10])); + + buffer.insertAll(3, source, 5); + expect(buffer, equals([6, 7, 8, 9, 10])); + + buffer.insertAll(4, source, 5, 5); + expect(buffer, equals([6, 7, 8, 9, 10])); + }); + + test('throws errors for invalid start and end', () { + expect(() => buffer.insertAll(-1, source), throwsRangeError); + expect(() => buffer.insertAll(6, source), throwsRangeError); + expect(() => buffer.insertAll(1, source, -1), throwsRangeError); + expect(() => buffer.insertAll(2, source, -1, 2), throwsRangeError); + expect(() => buffer.insertAll(3, source, 10), throwsStateError); + expect(() => buffer.insertAll(4, source, 10, 11), throwsStateError); + expect(() => buffer.insertAll(5, source, 3, 2), throwsRangeError); + expect(() => buffer.insertAll(1, source, 3, 10), throwsStateError); + expect(() => buffer.insertAll(2, source, 3, -1), throwsRangeError); + }); + }); + } + }); +} + +const doubleSamples = [ + 0.0, + 5e-324, // Minimal denormal value. + 2.225073858507201e-308, // Maximal denormal value. + 2.2250738585072014e-308, // Minimal normal value. + 0.9999999999999999, // Maximum value < 1. + 1.0, + 1.0000000000000002, // Minimum value > 1. + 4294967295.0, // 2^32 -1. + 4294967296.0, // 2^32. + 4503599627370495.5, // Maximal fractional value. + 9007199254740992.0, // Maximal exact value (adding one gets lost). + 1.7976931348623157e+308, // Maximal value. + 1.0 / 0.0, // Infinity. + 0.0 / 0.0, // NaN. + 0.49999999999999994, // Round-traps 1-3 (adding 0.5 and rounding towards + 4503599627370497.0, // minus infinity will not be the same as rounding + 9007199254740991.0 // to nearest with 0.5 rounding up). +]; + +const floatSamples = [ + 0.0, + 1.4e-45, // Minimal denormal value. + 1.1754942E-38, // Maximal denormal value. + 1.17549435E-38, // Minimal normal value. + 0.99999994, // Maximal value < 1. + 1.0, + 1.0000001, // Minimal value > 1. + 8388607.5, // Maximal fractional value. + 16777216.0, // Maximal exact value. + 3.4028235e+38, // Maximal value. + 1.0 / 0.0, // Infinity. + 0.0 / 0.0, // NaN. + 0.99999994, // Round traps 1-3. + 8388609.0, + 16777215.0 +]; + +int clampUint8(int x) => x < 0 + ? 0 + : x > 255 + ? 255 + : x; + +void doubleEqual(num x, num y) { + if (y.isNaN) { + expect(x.isNaN, isTrue); + } else { + expect(x, equals(y)); + } +} + +Rounder intRounder(int bits) { + var highBit = 1 << (bits - 1); + var mask = highBit - 1; + return (int x) => (x & mask) - (x & highBit); +} + +double roundToFloat(double value) { + return (Float32List(1)..[0] = value)[0]; +} + +void testFloat32x4Buffer(List floatSamples) { + var float4Samples = []; + for (var i = 0; i < floatSamples.length - 3; i++) { + float4Samples.add(Float32x4(floatSamples[i], floatSamples[i + 1], + floatSamples[i + 2], floatSamples[i + 3])); + } + + void floatEquals(num x, num y) { + if (y.isNaN) { + expect(x.isNaN, isTrue); + } else { + expect(x, equals(y)); + } + } + + void x4Equals(Float32x4 x, Float32x4 y) { + floatEquals(x.x, y.x); + floatEquals(x.y, y.y); + floatEquals(x.z, y.z); + floatEquals(x.w, y.w); + } + + test('Float32x4Buffer', () { + var buffer = Float32x4Buffer(5); + expect(buffer, const TypeMatcher>()); + + expect(buffer.length, equals(5)); + expect(buffer.elementSizeInBytes, equals(128 ~/ 8)); + expect(buffer.lengthInBytes, equals(5 * 128 ~/ 8)); + expect(buffer.offsetInBytes, equals(0)); + + x4Equals(buffer[0], Float32x4.zero()); + buffer.length = 0; + expect(buffer.length, equals(0)); + + for (var sample in float4Samples) { + buffer.add(sample); + x4Equals(buffer[buffer.length - 1], sample); + } + expect(buffer.length, equals(float4Samples.length)); + + buffer.addAll(float4Samples); + expect(buffer.length, equals(float4Samples.length * 2)); + for (var i = 0; i < float4Samples.length; i++) { + x4Equals(buffer[i], buffer[float4Samples.length + i]); + } + + buffer.removeRange(4, 4 + float4Samples.length); + for (var i = 0; i < float4Samples.length; i++) { + x4Equals(buffer[i], float4Samples[i]); + } + + // Test underlying buffer. + buffer.length = 1; + buffer[0] = float4Samples[0]; // Does not contain NaN. + + var floats = Float32List.view(buffer.buffer); + expect(floats[0], equals(buffer[0].x)); + expect(floats[1], equals(buffer[0].y)); + expect(floats[2], equals(buffer[0].z)); + expect(floats[3], equals(buffer[0].w)); + }); +} + +// Takes bit-size, min value, max value, function to create a buffer, and +// the rounding that is applied when storing values outside the valid range +// into the buffer. +void testFloatBuffer( + int bitSize, + List samples, + TypedDataBuffer Function() create, + double Function(double v) round, +) { + test('Float${bitSize}Buffer', () { + var buffer = create(); + expect(buffer, const TypeMatcher>()); + var byteSize = bitSize ~/ 8; + + expect(buffer.length, equals(0)); + buffer.add(0.0); + expect(buffer.length, equals(1)); + expect(buffer.removeLast(), equals(0.0)); + expect(buffer.length, equals(0)); + + for (var value in samples) { + buffer.add(value); + doubleEqual(buffer[buffer.length - 1], round(value)); + } + expect(buffer.length, equals(samples.length)); + + buffer.addAll(samples); + expect(buffer.length, equals(samples.length * 2)); + for (var i = 0; i < samples.length; i++) { + doubleEqual(buffer[i], buffer[samples.length + i]); + } + + buffer.removeRange(samples.length, buffer.length); + expect(buffer.length, equals(samples.length)); + + buffer.insertAll(0, samples); + expect(buffer.length, equals(samples.length * 2)); + for (var i = 0; i < samples.length; i++) { + doubleEqual(buffer[i], buffer[samples.length + i]); + } + + buffer.length = samples.length; + expect(buffer.length, equals(samples.length)); + + // TypedData. + expect(buffer.elementSizeInBytes, equals(byteSize)); + expect(buffer.lengthInBytes, equals(byteSize * buffer.length)); + expect(buffer.offsetInBytes, equals(0)); + + // Accessing the buffer works. + // Accessing the underlying buffer works. + buffer.length = 2; + buffer[0] = samples[0]; + buffer[1] = samples[1]; + var bytes = Uint8List.view(buffer.buffer); + for (var i = 0; i < byteSize; i++) { + var tmp = bytes[i]; + bytes[i] = bytes[byteSize + i]; + bytes[byteSize + i] = tmp; + } + doubleEqual(buffer[0], round(samples[1])); + doubleEqual(buffer[1], round(samples[0])); + }); +} + +void testInt( + List intSamples, + int bits, + TypedDataBuffer Function(int length) buffer, { + String? testOn, +}) { + var min = -(1 << (bits - 1)); + var max = -(min + 1); + test('Int${bits}Buffer', () { + testIntBuffer(intSamples, bits, min, max, buffer, intRounder(bits)); + }, testOn: testOn); +} + +void testInt32x4Buffer(List intSamples) { + test('Int32x4Buffer', () { + var bytes = 128 ~/ 8; + Matcher equals32x4(Int32x4 expected) => MatchesInt32x4(expected); + + var buffer = Int32x4Buffer(0); + expect(buffer, const TypeMatcher>()); + expect(buffer.length, equals(0)); + + expect(buffer.elementSizeInBytes, equals(bytes)); + expect(buffer.lengthInBytes, equals(0)); + expect(buffer.offsetInBytes, equals(0)); + + var sample = Int32x4(-0x80000000, -1, 0, 0x7fffffff); + buffer.add(sample); + expect(buffer.length, equals(1)); + expect(buffer[0], equals32x4(sample)); + + expect(buffer.elementSizeInBytes, equals(bytes)); + expect(buffer.lengthInBytes, equals(bytes)); + expect(buffer.offsetInBytes, equals(0)); + + buffer.length = 0; + expect(buffer.length, equals(0)); + + var samples = intSamples + .map((value) => Int32x4(value, -value, ~value, ~ -value)) + .toList(); + for (var value in samples) { + var length = buffer.length; + buffer.add(value); + expect(buffer.length, equals(length + 1)); + expect(buffer[length], equals32x4(value)); + } + + buffer.addAll(samples); // Add all the values at once. + for (var i = 0; i < samples.length; i++) { + expect(buffer[samples.length + i], equals32x4(buffer[i])); + } + + // Remove range works and changes length. + buffer.removeRange(samples.length, buffer.length); + expect(buffer.length, equals(samples.length)); + + // Accessing the underlying buffer works. + buffer.length = 2; + buffer[0] = Int32x4(-80000000, 0x7fffffff, 0, -1); + var byteBuffer = Uint8List.view(buffer.buffer); + var halfBytes = bytes ~/ 2; + for (var i = 0; i < halfBytes; i++) { + var tmp = byteBuffer[i]; + byteBuffer[i] = byteBuffer[halfBytes + i]; + byteBuffer[halfBytes + i] = tmp; + } + var result = Int32x4(0, -1, -80000000, 0x7fffffff); + expect(buffer[0], equals32x4(result)); + }); +} + +void testIntBuffer( + List intSamples, + int bits, + int min, + int max, + TypedDataBuffer Function(int length) create, + int Function(int val) round, +) { + assert(round(min) == min); + assert(round(max) == max); + // All int buffers default to the value 0. + var buffer = create(0); + expect(buffer, const TypeMatcher>()); + expect(buffer.length, equals(0)); + var bytes = bits ~/ 8; + + expect(buffer.elementSizeInBytes, equals(bytes)); + expect(buffer.lengthInBytes, equals(0)); + expect(buffer.offsetInBytes, equals(0)); + + buffer.add(min); + expect(buffer.length, equals(1)); + expect(buffer[0], equals(min)); + + expect(buffer.elementSizeInBytes, equals(bytes)); + expect(buffer.lengthInBytes, equals(bytes)); + expect(buffer.offsetInBytes, equals(0)); + + buffer.length = 0; + expect(buffer.length, equals(0)); + + var samples = intSamples.toList()..addAll(intSamples.map((x) => -x)); + for (var value in samples) { + var length = buffer.length; + buffer.add(value); + expect(buffer.length, equals(length + 1)); + expect(buffer[length], equals(round(value))); + } + buffer.addAll(samples); // Add all the values at once. + for (var i = 0; i < samples.length; i++) { + expect(buffer[samples.length + i], equals(buffer[i])); + } + + // Remove range works and changes length. + buffer.removeRange(samples.length, buffer.length); + expect(buffer.length, equals(samples.length)); + + // Both values are in `samples`, but equality is performed without rounding. + // For signed 64 bit ints, min and max wrap around, min-1=max and max+1=min + if (bits == 64) { + // TODO(keertip): fix tests for Uint64 / Int64 as now Uints are represented + // as signed ints. + expect(buffer.contains(min - 1), isTrue); + expect(buffer.contains(max + 1), isTrue); + } else { + // Both values are in `samples`, but equality is performed without rounding. + expect(buffer.contains(min - 1), isFalse); + expect(buffer.contains(max + 1), isFalse); + } + expect(buffer.contains(round(min - 1)), isTrue); + expect(buffer.contains(round(max + 1)), isTrue); + + // Accessing the underlying buffer works. + buffer.length = 2; + buffer[0] = min; + buffer[1] = max; + var byteBuffer = Uint8List.view(buffer.buffer); + var byteSize = buffer.elementSizeInBytes; + for (var i = 0; i < byteSize; i++) { + var tmp = byteBuffer[i]; + byteBuffer[i] = byteBuffer[byteSize + i]; + byteBuffer[byteSize + i] = tmp; + } + expect(buffer[0], equals(max)); + expect(buffer[1], equals(min)); +} + +void testUint( + List intSamples, + int bits, + TypedDataBuffer Function(int length) buffer, { + String? testOn, +}) { + var min = 0; + var rounder = uintRounder(bits); + var max = rounder(-1); + test('Uint${bits}Buffer', () { + testIntBuffer(intSamples, bits, min, max, buffer, rounder); + }, testOn: testOn); +} + +Rounder uintRounder(int bits) { + var halfbits = (1 << (bits ~/ 2)) - 1; + var mask = halfbits | (halfbits << (bits ~/ 2)); + return (int x) => x & mask; +} + +typedef Rounder = int Function(int value); + +class MatchesInt32x4 extends Matcher { + Int32x4 result; + + MatchesInt32x4(this.result); + + @override + Description describe(Description description) => + description.add('Int32x4.=='); + + @override + bool matches(Object? item, Map matchState) => + item is Int32x4 && + result.x == item.x && + result.y == item.y && + result.z == item.z && + result.w == item.w; +} diff --git a/pkgs/typed_data/test/typed_buffers_vm_test.dart b/pkgs/typed_data/test/typed_buffers_vm_test.dart new file mode 100644 index 00000000..62afcef7 --- /dev/null +++ b/pkgs/typed_data/test/typed_buffers_vm_test.dart @@ -0,0 +1,24 @@ +// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +@TestOn('vm') +library; + +import 'package:test/test.dart'; + +import 'typed_buffers_test.dart'; + +void main() { + var browserUnsafe = [ + 0x0ffffffffffffffff, + 0xaaaaaaaaaaaaaaaa, + 0x8000000000000001, + 0x7fffffffffffffff, + 0x5555555555555555, + ]; + initTests([ + ...browserSafeIntSamples, + ...browserUnsafe, + ]); +}