├── .github ├── dependabot.yml └── workflows │ ├── no-response.yml │ ├── publish.yaml │ └── test-package.yml ├── .gitignore ├── AUTHORS ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── lib ├── src │ ├── typed_buffer.dart │ └── typed_queue.dart ├── typed_buffers.dart └── typed_data.dart ├── pubspec.yaml └── test ├── queue_test.dart ├── typed_buffers_test.dart └── typed_buffers_vm_test.dart /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Dependabot configuration file. 2 | # See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates 3 | version: 2 4 | 5 | updates: 6 | - package-ecosystem: github-actions 7 | directory: / 8 | schedule: 9 | interval: monthly 10 | labels: 11 | - autosubmit 12 | groups: 13 | github-actions: 14 | patterns: 15 | - "*" 16 | -------------------------------------------------------------------------------- /.github/workflows/no-response.yml: -------------------------------------------------------------------------------- 1 | # A workflow to close issues where the author hasn't responded to a request for 2 | # more information; see https://github.com/actions/stale. 3 | 4 | name: No Response 5 | 6 | # Run as a daily cron. 7 | on: 8 | schedule: 9 | # Every day at 8am 10 | - cron: '0 8 * * *' 11 | 12 | # All permissions not specified are set to 'none'. 13 | permissions: 14 | issues: write 15 | pull-requests: write 16 | 17 | jobs: 18 | no-response: 19 | runs-on: ubuntu-latest 20 | if: ${{ github.repository_owner == 'dart-lang' }} 21 | steps: 22 | - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e 23 | with: 24 | # Don't automatically mark inactive issues+PRs as stale. 25 | days-before-stale: -1 26 | # Close needs-info issues and PRs after 14 days of inactivity. 27 | days-before-close: 14 28 | stale-issue-label: "needs-info" 29 | close-issue-message: > 30 | Without additional information we're not able to resolve this issue. 31 | Feel free to add more info or respond to any questions above and we 32 | can reopen the case. Thanks for your contribution! 33 | stale-pr-label: "needs-info" 34 | close-pr-message: > 35 | Without additional information we're not able to resolve this PR. 36 | Feel free to add more info or respond to any questions above. 37 | Thanks for your contribution! 38 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | # A CI configuration to auto-publish pub packages. 2 | 3 | name: Publish 4 | 5 | on: 6 | pull_request: 7 | branches: [ master ] 8 | push: 9 | tags: [ 'v[0-9]+.[0-9]+.[0-9]+' ] 10 | 11 | jobs: 12 | publish: 13 | if: ${{ github.repository_owner == 'dart-lang' }} 14 | uses: dart-lang/ecosystem/.github/workflows/publish.yaml@main 15 | permissions: 16 | id-token: write # Required for authentication using OIDC 17 | pull-requests: write # Required for writing the pull request note 18 | -------------------------------------------------------------------------------- /.github/workflows/test-package.yml: -------------------------------------------------------------------------------- 1 | name: Dart CI 2 | 3 | on: 4 | # Run on PRs and pushes to the default branch. 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | schedule: 10 | - cron: "0 0 * * 0" 11 | 12 | env: 13 | PUB_ENVIRONMENT: bot.github 14 | 15 | jobs: 16 | # Check code formatting and static analysis on a single OS (linux) 17 | # against Dart dev. 18 | analyze: 19 | runs-on: ubuntu-latest 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | sdk: [dev] 24 | steps: 25 | - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 26 | - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 27 | with: 28 | sdk: ${{ matrix.sdk }} 29 | - id: install 30 | name: Install dependencies 31 | run: dart pub get 32 | - name: Check formatting 33 | run: dart format --output=none --set-exit-if-changed . 34 | if: always() && steps.install.outcome == 'success' 35 | - name: Analyze code 36 | run: dart analyze --fatal-infos 37 | if: always() && steps.install.outcome == 'success' 38 | 39 | # Run tests on a matrix consisting of two dimensions: 40 | # 1. OS: ubuntu-latest, (macos-latest, windows-latest) 41 | # 2. release channel: dev 42 | test: 43 | needs: analyze 44 | runs-on: ${{ matrix.os }} 45 | strategy: 46 | fail-fast: false 47 | matrix: 48 | # Add macos-latest and/or windows-latest if relevant for this package. 49 | os: [ubuntu-latest] 50 | sdk: [3.5, dev] 51 | steps: 52 | - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 53 | - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 54 | with: 55 | sdk: ${{ matrix.sdk }} 56 | - id: install 57 | name: Install dependencies 58 | run: dart pub get 59 | - name: Run VM tests 60 | run: dart test --platform vm 61 | if: always() && steps.install.outcome == 'success' 62 | - name: Run Chrome tests 63 | run: dart test --platform chrome 64 | if: always() && steps.install.outcome == 'success' 65 | - name: Run Chrome tests - wasm 66 | run: dart test --platform chrome --compiler dart2wasm 67 | if: always() && steps.install.outcome == 'success' 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .buildlog 2 | .DS_Store 3 | .idea 4 | .dart_tool/ 5 | .pub/ 6 | .settings/ 7 | build/ 8 | packages 9 | .packages 10 | pubspec.lock 11 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Below is a list of people and organizations that have contributed 2 | # to the project. Names should be added to the list like so: 3 | # 4 | # Name/Organization 5 | 6 | Google Inc. 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.4.0-wip 2 | 3 | * The type of the `buffer` constructor argument to `TypedDataBuffer` is now 4 | `TypeDataList` (instead of `List`). While this is breaking change 5 | statically there was a runtime cast that makes this change a no-op in 6 | practice. 7 | * Require Dart 3.5 8 | 9 | ## 1.3.2 10 | 11 | * Added package topics to the pubspec file. 12 | * Require Dart 2.17. 13 | 14 | ## 1.3.1 15 | 16 | * Switch to using `package:lints`. 17 | * Populate the pubspec `repository` field. 18 | 19 | ## 1.3.0 20 | 21 | * Stable release for null safety. 22 | * Update SDK constraints to `>=2.12.0-0 <3.0.0` based on beta release 23 | guidelines. 24 | 25 | ## 1.2.0 26 | 27 | * Add typed queue classes such as `Uint8Queue`. These classes implement both 28 | `Queue` and `List` with a highly-efficient typed-data-backed implementation. 29 | Their `sublist()` methods also return typed data classes. 30 | * Update min Dart SDK to `2.4.0`. 31 | 32 | ## 1.1.6 33 | 34 | * Set max SDK version to `<3.0.0`, and adjust other dependencies. 35 | 36 | ## 1.1.5 37 | 38 | * Undo unnecessary SDK version constraint tweak. 39 | 40 | ## 1.1.4 41 | 42 | * Expand the SDK version constraint to include `<2.0.0-dev.infinity`. 43 | 44 | ## 1.1.3 45 | 46 | * Fix all strong-mode warnings. 47 | 48 | ## 1.1.2 49 | 50 | * Fix a bug where `TypedDataBuffer.insertAll` could fail to insert some elements 51 | of an `Iterable`. 52 | 53 | ## 1.1.1 54 | 55 | * Optimize `insertAll` with an `Iterable` argument and no end-point. 56 | 57 | ## 1.1.0 58 | 59 | * Add `start` and `end` parameters to the `addAll()` and `insertAll()` methods 60 | for the typed data buffer classes. These allow efficient concatenation of 61 | slices of existing typed data. 62 | 63 | * Make `addAll()` for typed data buffer classes more efficient for lists, 64 | especially typed data lists. 65 | 66 | ## 1.0.0 67 | 68 | * ChangeLog starts here 69 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at 2 | the end). 3 | 4 | ### Before you contribute 5 | Before we can use your code, you must sign the 6 | [Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) 7 | (CLA), which you can do online. The CLA is necessary mainly because you own the 8 | copyright to your changes, even after your contribution becomes part of our 9 | codebase, so we need your permission to use and distribute your code. We also 10 | need to be sure of various other things—for instance that you'll tell us if you 11 | know that your code infringes on other people's patents. You don't have to sign 12 | the CLA until after you've submitted your code for review and a member has 13 | approved it, but you must do it before we can put your code into our codebase. 14 | 15 | Before you start working on a larger contribution, you should get in touch with 16 | us first through the issue tracker with your idea so that we can help out and 17 | possibly guide you. Coordinating up front makes it much easier to avoid 18 | frustration later on. 19 | 20 | ### Code reviews 21 | All submissions, including submissions by project members, require review. 22 | 23 | ### File headers 24 | All files in the project must start with the following header. 25 | 26 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 27 | // for details. All rights reserved. Use of this source code is governed by a 28 | // BSD-style license that can be found in the LICENSE file. 29 | 30 | ### The small print 31 | Contributions made by corporations are covered by a different agreement than the 32 | one above, the 33 | [Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate). 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > This repo has moved to https://github.com/dart-lang/core/tree/main/pkgs/typed_data 3 | 4 | [![Dart CI](https://github.com/dart-lang/typed_data/actions/workflows/test-package.yml/badge.svg)](https://github.com/dart-lang/typed_data/actions/workflows/test-package.yml) 5 | [![pub package](https://img.shields.io/pub/v/typed_data.svg)](https://pub.dev/packages/typed_data) 6 | [![package publisher](https://img.shields.io/pub/publisher/typed_data.svg)](https://pub.dev/packages/typed_data/publisher) 7 | 8 | Helper libraries for working with typed data lists. 9 | 10 | The `typed_data` package contains utility functions and classes that makes working with typed data lists easier. 11 | 12 | ## Using 13 | 14 | The `typed_data` package can be imported using: 15 | 16 | ```dart 17 | import 'package:typed_data/typed_data.dart'; 18 | ``` 19 | 20 | ## Typed buffers 21 | 22 | Typed buffers are growable lists backed by typed arrays. These are similar to 23 | the growable lists created by `[]` or `[]`, but store typed data 24 | like a typed data list. 25 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # https://dart.dev/guides/language/analysis-options 2 | include: package:dart_flutter_team_lints/analysis_options.yaml 3 | 4 | analyzer: 5 | language: 6 | strict-casts: true 7 | 8 | linter: 9 | rules: 10 | - avoid_unused_constructor_parameters 11 | - cancel_subscriptions 12 | - no_adjacent_strings_in_list 13 | - package_api_docs 14 | -------------------------------------------------------------------------------- /lib/src/typed_buffer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:collection' show ListBase; 6 | import 'dart:typed_data'; 7 | 8 | abstract class TypedDataBuffer extends ListBase { 9 | static const int _initialLength = 8; 10 | 11 | /// The underlying data buffer. 12 | TypedDataList _buffer; 13 | 14 | /// The length of the list being built. 15 | int _length; 16 | 17 | TypedDataBuffer(TypedDataList buffer) 18 | : _buffer = buffer, 19 | _length = buffer.length; 20 | 21 | @override 22 | int get length => _length; 23 | 24 | @override 25 | E operator [](int index) { 26 | if (index >= length) throw RangeError.index(index, this); 27 | return _buffer[index]; 28 | } 29 | 30 | @override 31 | void operator []=(int index, E value) { 32 | if (index >= length) throw RangeError.index(index, this); 33 | _buffer[index] = value; 34 | } 35 | 36 | @override 37 | set length(int newLength) { 38 | if (newLength < _length) { 39 | var defaultValue = _defaultValue; 40 | for (var i = newLength; i < _length; i++) { 41 | _buffer[i] = defaultValue; 42 | } 43 | } else if (newLength > _buffer.length) { 44 | TypedDataList newBuffer; 45 | if (_buffer.isEmpty) { 46 | newBuffer = _createBuffer(newLength); 47 | } else { 48 | newBuffer = _createBiggerBuffer(newLength); 49 | } 50 | newBuffer.setRange(0, _length, _buffer); 51 | _buffer = newBuffer; 52 | } 53 | _length = newLength; 54 | } 55 | 56 | void _add(E value) { 57 | if (_length == _buffer.length) _grow(_length); 58 | _buffer[_length++] = value; 59 | } 60 | 61 | // We override the default implementation of `add` because it grows the list 62 | // by setting the length in increments of one. We want to grow by doubling 63 | // capacity in most cases. 64 | @override 65 | void add(E element) { 66 | _add(element); 67 | } 68 | 69 | /// Appends all objects of [values] to the end of this buffer. 70 | /// 71 | /// This adds values from [start] (inclusive) to [end] (exclusive) in 72 | /// [values]. If [end] is omitted, it defaults to adding all elements of 73 | /// [values] after [start]. 74 | /// 75 | /// The [start] value must be non-negative. The [values] iterable must have at 76 | /// least [start] elements, and if [end] is specified, it must be greater than 77 | /// or equal to [start] and [values] must have at least [end] elements. 78 | @override 79 | void addAll(Iterable values, [int start = 0, int? end]) { 80 | RangeError.checkNotNegative(start, 'start'); 81 | if (end != null && start > end) { 82 | throw RangeError.range(end, start, null, 'end'); 83 | } 84 | 85 | _addAll(values, start, end); 86 | } 87 | 88 | /// Inserts all objects of [values] at position [index] in this list. 89 | /// 90 | /// This adds values from [start] (inclusive) to [end] (exclusive) in 91 | /// [values]. If [end] is omitted, it defaults to adding all elements of 92 | /// [values] after [start]. 93 | /// 94 | /// The [start] value must be non-negative. The [values] iterable must have at 95 | /// least [start] elements, and if [end] is specified, it must be greater than 96 | /// or equal to [start] and [values] must have at least [end] elements. 97 | @override 98 | void insertAll(int index, Iterable values, [int start = 0, int? end]) { 99 | RangeError.checkValidIndex(index, this, 'index', _length + 1); 100 | RangeError.checkNotNegative(start, 'start'); 101 | if (end != null) { 102 | if (start > end) { 103 | throw RangeError.range(end, start, null, 'end'); 104 | } 105 | if (start == end) return; 106 | } 107 | 108 | // If we're adding to the end of the list anyway, use [_addAll]. This lets 109 | // us avoid converting [values] into a list even if [end] is null, since we 110 | // can add values iteratively to the end of the list. We can't do so in the 111 | // center because copying the trailing elements every time is non-linear. 112 | if (index == _length) { 113 | _addAll(values, start, end); 114 | return; 115 | } 116 | 117 | if (end == null && values is List) { 118 | end = values.length; 119 | } 120 | if (end != null) { 121 | _insertKnownLength(index, values, start, end); 122 | return; 123 | } 124 | 125 | // Add elements at end, growing as appropriate, then put them back at 126 | // position [index] using flip-by-double-reverse. 127 | var writeIndex = _length; 128 | var skipCount = start; 129 | for (var value in values) { 130 | if (skipCount > 0) { 131 | skipCount--; 132 | continue; 133 | } 134 | if (writeIndex == _buffer.length) { 135 | _grow(writeIndex); 136 | } 137 | _buffer[writeIndex++] = value; 138 | } 139 | 140 | if (skipCount > 0) { 141 | throw StateError('Too few elements'); 142 | } 143 | if (end != null && writeIndex < end) { 144 | throw RangeError.range(end, start, writeIndex, 'end'); 145 | } 146 | 147 | // Swap [index.._length) and [_length..writeIndex) by double-reversing. 148 | _reverse(_buffer, index, _length); 149 | _reverse(_buffer, _length, writeIndex); 150 | _reverse(_buffer, index, writeIndex); 151 | _length = writeIndex; 152 | return; 153 | } 154 | 155 | // Reverses the range [start..end) of buffer. 156 | static void _reverse(List buffer, int start, int end) { 157 | end--; // Point to last element, not after last element. 158 | while (start < end) { 159 | var first = buffer[start]; 160 | var last = buffer[end]; 161 | buffer[end] = first; 162 | buffer[start] = last; 163 | start++; 164 | end--; 165 | } 166 | } 167 | 168 | /// Does the same thing as [addAll]. 169 | /// 170 | /// This allows [addAll] and [insertAll] to share implementation without a 171 | /// subclass unexpectedly overriding both when it intended to only override 172 | /// [addAll]. 173 | void _addAll(Iterable values, [int start = 0, int? end]) { 174 | if (values is List) end ??= values.length; 175 | 176 | // If we know the length of the segment to add, do so with [addRange]. This 177 | // way we know how much to grow the buffer in advance, and it may be even 178 | // more efficient for typed data input. 179 | if (end != null) { 180 | _insertKnownLength(_length, values, start, end); 181 | return; 182 | } 183 | 184 | // Otherwise, just add values one at a time. 185 | var i = 0; 186 | for (var value in values) { 187 | if (i >= start) add(value); 188 | i++; 189 | } 190 | if (i < start) throw StateError('Too few elements'); 191 | } 192 | 193 | /// Like [insertAll], but with a guaranteed non-`null` [start] and [end]. 194 | void _insertKnownLength(int index, Iterable values, int start, int end) { 195 | if (values is List) { 196 | if (start > values.length || end > values.length) { 197 | throw StateError('Too few elements'); 198 | } 199 | } 200 | 201 | var valuesLength = end - start; 202 | var newLength = _length + valuesLength; 203 | _ensureCapacity(newLength); 204 | 205 | _buffer.setRange( 206 | index + valuesLength, _length + valuesLength, _buffer, index); 207 | _buffer.setRange(index, index + valuesLength, values, start); 208 | _length = newLength; 209 | } 210 | 211 | @override 212 | void insert(int index, E element) { 213 | if (index < 0 || index > _length) { 214 | throw RangeError.range(index, 0, _length); 215 | } 216 | if (_length < _buffer.length) { 217 | _buffer.setRange(index + 1, _length + 1, _buffer, index); 218 | _buffer[index] = element; 219 | _length++; 220 | return; 221 | } 222 | var newBuffer = _createBiggerBuffer(null); 223 | newBuffer.setRange(0, index, _buffer); 224 | newBuffer.setRange(index + 1, _length + 1, _buffer, index); 225 | newBuffer[index] = element; 226 | _length++; 227 | _buffer = newBuffer; 228 | } 229 | 230 | /// Ensures that [_buffer] is at least [requiredCapacity] long, 231 | /// 232 | /// Grows the buffer if necessary, preserving existing data. 233 | void _ensureCapacity(int requiredCapacity) { 234 | if (requiredCapacity <= _buffer.length) return; 235 | var newBuffer = _createBiggerBuffer(requiredCapacity); 236 | newBuffer.setRange(0, _length, _buffer); 237 | _buffer = newBuffer; 238 | } 239 | 240 | /// Create a bigger buffer. 241 | /// 242 | /// This method determines how much bigger a bigger buffer should 243 | /// be. If [requiredCapacity] is not null, it will be at least that 244 | /// size. It will always have at least have double the capacity of 245 | /// the current buffer. 246 | TypedDataList _createBiggerBuffer(int? requiredCapacity) { 247 | var newLength = _buffer.length * 2; 248 | if (requiredCapacity != null && newLength < requiredCapacity) { 249 | newLength = requiredCapacity; 250 | } else if (newLength < _initialLength) { 251 | newLength = _initialLength; 252 | } 253 | return _createBuffer(newLength); 254 | } 255 | 256 | /// Grows the buffer. 257 | /// 258 | /// This copies the first [length] elements into the new buffer. 259 | void _grow(int length) { 260 | _buffer = _createBiggerBuffer(null)..setRange(0, length, _buffer); 261 | } 262 | 263 | @override 264 | void setRange(int start, int end, Iterable iterable, [int skipCount = 0]) { 265 | if (end > _length) throw RangeError.range(end, 0, _length); 266 | _setRange(start, end, iterable, skipCount); 267 | } 268 | 269 | /// Like [setRange], but with no bounds checking. 270 | void _setRange(int start, int end, Iterable source, int skipCount) { 271 | if (source is TypedDataBuffer) { 272 | _buffer.setRange(start, end, source._buffer, skipCount); 273 | } else { 274 | _buffer.setRange(start, end, source, skipCount); 275 | } 276 | } 277 | 278 | // TypedData. 279 | 280 | int get elementSizeInBytes => _buffer.elementSizeInBytes; 281 | 282 | int get lengthInBytes => _length * _buffer.elementSizeInBytes; 283 | 284 | int get offsetInBytes => _buffer.offsetInBytes; 285 | 286 | /// Returns the underlying [ByteBuffer]. 287 | /// 288 | /// The returned buffer may be replaced by operations that change the [length] 289 | /// of this list. 290 | /// 291 | /// The buffer may be larger than [lengthInBytes] bytes, but never smaller. 292 | ByteBuffer get buffer => _buffer.buffer; 293 | 294 | // Specialization for the specific type. 295 | 296 | // Return zero for integers, 0.0 for floats, etc. 297 | // Used to fill buffer when changing length. 298 | E get _defaultValue; 299 | 300 | // Create a new typed list to use as buffer. 301 | TypedDataList _createBuffer(int size); 302 | } 303 | 304 | abstract class _IntBuffer extends TypedDataBuffer { 305 | _IntBuffer(super.buffer); 306 | 307 | @override 308 | int get _defaultValue => 0; 309 | } 310 | 311 | abstract class _FloatBuffer extends TypedDataBuffer { 312 | _FloatBuffer(super.buffer); 313 | 314 | @override 315 | double get _defaultValue => 0.0; 316 | } 317 | 318 | class Uint8Buffer extends _IntBuffer { 319 | Uint8Buffer([int initialLength = 0]) : super(Uint8List(initialLength)); 320 | 321 | @override 322 | Uint8List _createBuffer(int size) => Uint8List(size); 323 | } 324 | 325 | class Int8Buffer extends _IntBuffer { 326 | Int8Buffer([int initialLength = 0]) : super(Int8List(initialLength)); 327 | 328 | @override 329 | Int8List _createBuffer(int size) => Int8List(size); 330 | } 331 | 332 | class Uint8ClampedBuffer extends _IntBuffer { 333 | Uint8ClampedBuffer([int initialLength = 0]) 334 | : super(Uint8ClampedList(initialLength)); 335 | 336 | @override 337 | Uint8ClampedList _createBuffer(int size) => Uint8ClampedList(size); 338 | } 339 | 340 | class Uint16Buffer extends _IntBuffer { 341 | Uint16Buffer([int initialLength = 0]) : super(Uint16List(initialLength)); 342 | 343 | @override 344 | Uint16List _createBuffer(int size) => Uint16List(size); 345 | } 346 | 347 | class Int16Buffer extends _IntBuffer { 348 | Int16Buffer([int initialLength = 0]) : super(Int16List(initialLength)); 349 | 350 | @override 351 | Int16List _createBuffer(int size) => Int16List(size); 352 | } 353 | 354 | class Uint32Buffer extends _IntBuffer { 355 | Uint32Buffer([int initialLength = 0]) : super(Uint32List(initialLength)); 356 | 357 | @override 358 | Uint32List _createBuffer(int size) => Uint32List(size); 359 | } 360 | 361 | class Int32Buffer extends _IntBuffer { 362 | Int32Buffer([int initialLength = 0]) : super(Int32List(initialLength)); 363 | 364 | @override 365 | Int32List _createBuffer(int size) => Int32List(size); 366 | } 367 | 368 | class Uint64Buffer extends _IntBuffer { 369 | Uint64Buffer([int initialLength = 0]) : super(Uint64List(initialLength)); 370 | 371 | @override 372 | Uint64List _createBuffer(int size) => Uint64List(size); 373 | } 374 | 375 | class Int64Buffer extends _IntBuffer { 376 | Int64Buffer([int initialLength = 0]) : super(Int64List(initialLength)); 377 | 378 | @override 379 | Int64List _createBuffer(int size) => Int64List(size); 380 | } 381 | 382 | class Float32Buffer extends _FloatBuffer { 383 | Float32Buffer([int initialLength = 0]) : super(Float32List(initialLength)); 384 | 385 | @override 386 | Float32List _createBuffer(int size) => Float32List(size); 387 | } 388 | 389 | class Float64Buffer extends _FloatBuffer { 390 | Float64Buffer([int initialLength = 0]) : super(Float64List(initialLength)); 391 | 392 | @override 393 | Float64List _createBuffer(int size) => Float64List(size); 394 | } 395 | 396 | class Int32x4Buffer extends TypedDataBuffer { 397 | static final Int32x4 _zero = Int32x4(0, 0, 0, 0); 398 | 399 | Int32x4Buffer([int initialLength = 0]) : super(Int32x4List(initialLength)); 400 | 401 | @override 402 | Int32x4 get _defaultValue => _zero; 403 | 404 | @override 405 | Int32x4List _createBuffer(int size) => Int32x4List(size); 406 | } 407 | 408 | class Float32x4Buffer extends TypedDataBuffer { 409 | Float32x4Buffer([int initialLength = 0]) 410 | : super(Float32x4List(initialLength)); 411 | 412 | @override 413 | Float32x4 get _defaultValue => Float32x4.zero(); 414 | 415 | @override 416 | Float32x4List _createBuffer(int size) => Float32x4List(size); 417 | } 418 | -------------------------------------------------------------------------------- /lib/src/typed_queue.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:collection'; 6 | import 'dart:typed_data'; 7 | 8 | import 'package:collection/collection.dart'; 9 | 10 | import 'typed_buffer.dart'; 11 | 12 | /// The shared superclass of all the typed queue subclasses. 13 | abstract class _TypedQueue> with ListMixin { 14 | /// The underlying data buffer. 15 | TypedDataList _table; 16 | 17 | int _head; 18 | int _tail; 19 | 20 | /// Create an empty queue. 21 | _TypedQueue(this._table) 22 | : _head = 0, 23 | _tail = 0; 24 | 25 | // Iterable interface. 26 | 27 | @override 28 | int get length => (_tail - _head) & (_table.length - 1); 29 | 30 | @override 31 | List toList({bool growable = true}) { 32 | var list = growable ? _createBuffer(length) : _createList(length); 33 | _writeToList(list); 34 | return list; 35 | } 36 | 37 | @override 38 | QueueList cast() { 39 | if (this is QueueList) return this as QueueList; 40 | throw UnsupportedError('$this cannot be cast to the desired type.'); 41 | } 42 | 43 | @Deprecated('Use `cast` instead') 44 | QueueList retype() => cast(); 45 | 46 | // Queue interface. 47 | 48 | void addLast(E value) { 49 | _table[_tail] = value; 50 | _tail = (_tail + 1) & (_table.length - 1); 51 | if (_head == _tail) _growAtCapacity(); 52 | } 53 | 54 | void addFirst(E value) { 55 | _head = (_head - 1) & (_table.length - 1); 56 | _table[_head] = value; 57 | if (_head == _tail) _growAtCapacity(); 58 | } 59 | 60 | E removeFirst() { 61 | if (_head == _tail) throw StateError('No element'); 62 | var result = _table[_head]; 63 | _head = (_head + 1) & (_table.length - 1); 64 | return result; 65 | } 66 | 67 | @override 68 | E removeLast() { 69 | if (_head == _tail) throw StateError('No element'); 70 | _tail = (_tail - 1) & (_table.length - 1); 71 | return _table[_tail]; 72 | } 73 | 74 | // List interface. 75 | 76 | @override 77 | void add(E value) => addLast(value); 78 | 79 | @override 80 | set length(int value) { 81 | RangeError.checkNotNegative(value, 'length'); 82 | 83 | var delta = value - length; 84 | if (delta >= 0) { 85 | var needsToGrow = _table.length <= value; 86 | if (needsToGrow) _growTo(value); 87 | _tail = (_tail + delta) & (_table.length - 1); 88 | 89 | // If we didn't copy into a new table, make sure that we overwrite the 90 | // existing data so that users don't accidentally depend on it still 91 | // existing. 92 | if (!needsToGrow) fillRange(value - delta, value, _defaultValue); 93 | } else { 94 | removeRange(value, length); 95 | } 96 | } 97 | 98 | @override 99 | E operator [](int index) { 100 | RangeError.checkValidIndex(index, this, null, length); 101 | return _table[(_head + index) & (_table.length - 1)]; 102 | } 103 | 104 | @override 105 | void operator []=(int index, E value) { 106 | RangeError.checkValidIndex(index, this); 107 | _table[(_head + index) & (_table.length - 1)] = value; 108 | } 109 | 110 | @override 111 | void removeRange(int start, int end) { 112 | var length = this.length; 113 | RangeError.checkValidRange(start, end, length); 114 | 115 | // Special-case removing an initial or final range because we can do it very 116 | // efficiently by adjusting `_head` or `_tail`. 117 | if (start == 0) { 118 | _head = (_head + end) & (_table.length - 1); 119 | return; 120 | } 121 | 122 | var elementsAfter = length - end; 123 | if (elementsAfter == 0) { 124 | _tail = (_head + start) & (_table.length - 1); 125 | return; 126 | } 127 | 128 | // Choose whether to copy from the beginning of the end of the queue based 129 | // on which will require fewer copied elements. 130 | var removedElements = end - start; 131 | if (start < elementsAfter) { 132 | setRange(removedElements, end, this); 133 | _head = (_head + removedElements) & (_table.length - 1); 134 | } else { 135 | setRange(start, length - removedElements, this, end); 136 | _tail = (_tail - removedElements) & (_table.length - 1); 137 | } 138 | } 139 | 140 | @override 141 | void setRange(int start, int end, Iterable iterable, [int skipCount = 0]) { 142 | RangeError.checkValidRange(start, end, length); 143 | if (start == end) return; 144 | 145 | var targetStart = (_head + start) & (_table.length - 1); 146 | var targetEnd = (_head + end) & (_table.length - 1); 147 | var targetIsContiguous = targetStart < targetEnd; 148 | if (identical(iterable, this)) { 149 | // If we're copying this queue to itself, we can copy [_table] in directly 150 | // which requires some annoying case analysis but in return bottoms out on 151 | // an extremely efficient `memmove` call. However, we may need to do three 152 | // copies to avoid overwriting data we'll need to use later. 153 | var sourceStart = (_head + skipCount) & (_table.length - 1); 154 | var sourceEnd = (sourceStart + (end - start)) & (_table.length - 1); 155 | if (sourceStart == targetStart) return; 156 | 157 | var sourceIsContiguous = sourceStart < sourceEnd; 158 | if (targetIsContiguous && sourceIsContiguous) { 159 | // If both the source and destination ranges are contiguous, we can 160 | // do a single [setRange]. Hooray! 161 | _table.setRange(targetStart, targetEnd, _table, sourceStart); 162 | } else if (!targetIsContiguous && !sourceIsContiguous) { 163 | // If neither range is contiguous, we need to do three copies. 164 | if (sourceStart > targetStart) { 165 | // [=====| targetEnd targetStart |======] 166 | // [========| sourceEnd sourceStart |===] 167 | 168 | // Copy front to back. 169 | var startGap = sourceStart - targetStart; 170 | var firstEnd = _table.length - startGap; 171 | _table.setRange(targetStart, firstEnd, _table, sourceStart); 172 | _table.setRange(firstEnd, _table.length, _table); 173 | _table.setRange(0, targetEnd, _table, startGap); 174 | } else if (sourceEnd < targetEnd) { 175 | // [=====| targetEnd targetStart |======] 176 | // [==| sourceEnd sourceStart |=========] 177 | 178 | // Copy back to front. 179 | var firstStart = targetEnd - sourceEnd; 180 | _table.setRange(firstStart, targetEnd, _table); 181 | _table.setRange(0, firstStart, _table, _table.length - firstStart); 182 | _table.setRange(targetStart, _table.length, _table, sourceStart); 183 | } 184 | } else if (sourceStart < targetEnd) { 185 | // Copying twice is safe here as long as we copy front to back. 186 | if (sourceIsContiguous) { 187 | // [=====| targetEnd targetStart |======] 188 | // [ |===========| sourceEnd ] 189 | // sourceStart 190 | _table.setRange(targetStart, _table.length, _table, sourceStart); 191 | _table.setRange(0, targetEnd, _table, 192 | sourceStart + (_table.length - targetStart)); 193 | } else { 194 | // targetEnd 195 | // [ targetStart |===========| ] 196 | // [=====| sourceEnd sourceStart |======] 197 | var firstEnd = _table.length - sourceStart; 198 | _table.setRange(targetStart, firstEnd, _table, sourceStart); 199 | _table.setRange(firstEnd, targetEnd, _table); 200 | } 201 | } else { 202 | // Copying twice is safe here as long as we copy back to front. This 203 | // also covers the case where there's no overlap between the source and 204 | // target ranges, in which case the direction doesn't matter. 205 | if (sourceIsContiguous) { 206 | // [=====| targetEnd targetStart |======] 207 | // [ sourceStart |===========| ] 208 | // sourceEnd 209 | _table.setRange(0, targetEnd, _table, 210 | sourceStart + (_table.length - targetStart)); 211 | _table.setRange(targetStart, _table.length, _table, sourceStart); 212 | } else { 213 | // targetStart 214 | // [ |===========| targetEnd ] 215 | // [=====| sourceEnd sourceStart |======] 216 | var firstStart = targetEnd - sourceEnd; 217 | _table.setRange(firstStart, targetEnd, _table); 218 | _table.setRange(targetStart, firstStart, _table, sourceStart); 219 | } 220 | } 221 | } else if (targetIsContiguous) { 222 | // If the range is contiguous within the table, we can set it with a 223 | // single underlying [setRange] call. 224 | _table.setRange(targetStart, targetEnd, iterable, skipCount); 225 | } else if (iterable is List) { 226 | // If the range isn't contiguous and [iterable] is actually a [List] (but 227 | // not this queue), set it with two underlying [setRange] calls. 228 | _table.setRange(targetStart, _table.length, iterable, skipCount); 229 | _table.setRange( 230 | 0, targetEnd, iterable, skipCount + (_table.length - targetStart)); 231 | } else { 232 | // If [iterable] isn't a [List], we don't want to make two different 233 | // [setRange] calls because it could materialize a lazy iterable twice. 234 | // Instead we just fall back to the default iteration-based 235 | // implementation. 236 | super.setRange(start, end, iterable, skipCount); 237 | } 238 | } 239 | 240 | @override 241 | void fillRange(int start, int end, [E? value]) { 242 | var startInTable = (_head + start) & (_table.length - 1); 243 | var endInTable = (_head + end) & (_table.length - 1); 244 | if (startInTable <= endInTable) { 245 | _table.fillRange(startInTable, endInTable, value); 246 | } else { 247 | _table.fillRange(startInTable, _table.length, value); 248 | _table.fillRange(0, endInTable, value); 249 | } 250 | } 251 | 252 | @override 253 | L sublist(int start, [int? end]) { 254 | var length = this.length; 255 | var nonNullEnd = RangeError.checkValidRange(start, end, length); 256 | 257 | var list = _createList(nonNullEnd - start); 258 | _writeToList(list, start, nonNullEnd); 259 | return list; 260 | } 261 | 262 | // Internal helper functions. 263 | 264 | /// Writes the contents of `this` between [start] (which defaults to 0) and 265 | /// [end] (which defaults to [length]) to the beginning of [target]. 266 | /// 267 | /// This is functionally identical to `target.setRange(0, end - start, this, 268 | /// start)`, but it's more efficient when [target] is typed data. 269 | /// 270 | /// Returns the number of elements written to [target]. 271 | int _writeToList(List target, [int? start, int? end]) { 272 | start ??= 0; 273 | end ??= length; 274 | assert(target.length >= end - start); 275 | assert(start <= end); 276 | 277 | var elementsToWrite = end - start; 278 | var startInTable = (_head + start) & (_table.length - 1); 279 | var endInTable = (_head + end) & (_table.length - 1); 280 | if (startInTable <= endInTable) { 281 | target.setRange(0, elementsToWrite, _table, startInTable); 282 | } else { 283 | var firstPartSize = _table.length - startInTable; 284 | target.setRange(0, firstPartSize, _table, startInTable); 285 | target.setRange(firstPartSize, firstPartSize + endInTable, _table, 0); 286 | } 287 | return elementsToWrite; 288 | } 289 | 290 | /// Assumes the table is currently full to capacity, and grows it to the next 291 | /// power of two. 292 | void _growAtCapacity() { 293 | assert(_head == _tail); 294 | 295 | var newTable = _createList(_table.length * 2); 296 | 297 | // We can't use [_writeToList] here because when `_head == _tail` it thinks 298 | // the queue is empty rather than full. 299 | var partitionPoint = _table.length - _head; 300 | newTable.setRange(0, partitionPoint, _table, _head); 301 | if (partitionPoint != _table.length) { 302 | newTable.setRange(partitionPoint, _table.length, _table); 303 | } 304 | _head = 0; 305 | _tail = _table.length; 306 | _table = newTable; 307 | } 308 | 309 | /// Grows the tableso it's at least large enough size to include that many 310 | /// elements. 311 | void _growTo(int newElementCount) { 312 | assert(newElementCount >= length); 313 | 314 | // Add some extra room to ensure that there's room for more elements after 315 | // expansion. 316 | newElementCount += newElementCount >> 1; 317 | var newTable = _createList(_nextPowerOf2(newElementCount)); 318 | _tail = _writeToList(newTable); 319 | _table = newTable; 320 | _head = 0; 321 | } 322 | 323 | // Specialization for the specific type. 324 | 325 | // Create a new typed list. 326 | L _createList(int size); 327 | 328 | // Create a new typed buffer of the given type. 329 | TypedDataBuffer _createBuffer(int size); 330 | 331 | /// The default value used to fill the queue when changing length. 332 | E get _defaultValue; 333 | } 334 | 335 | abstract class _IntQueue> 336 | extends _TypedQueue { 337 | _IntQueue(super.queue); 338 | 339 | @override 340 | int get _defaultValue => 0; 341 | } 342 | 343 | abstract class _FloatQueue> 344 | extends _TypedQueue { 345 | _FloatQueue(super.queue); 346 | 347 | @override 348 | double get _defaultValue => 0.0; 349 | } 350 | 351 | /// A [QueueList] that efficiently stores 8-bit unsigned integers. 352 | /// 353 | /// For long queues, this implementation can be considerably more space- and 354 | /// time-efficient than a default [QueueList] implementation. 355 | /// 356 | /// Integers stored in this are truncated to their low eight bits, interpreted 357 | /// as an unsigned 8-bit integer with values in the range 0 to 255. 358 | class Uint8Queue extends _IntQueue implements QueueList { 359 | /// Creates an empty [Uint8Queue] with the given initial internal capacity (in 360 | /// elements). 361 | Uint8Queue([int? initialCapacity]) 362 | : super(Uint8List(_chooseRealInitialCapacity(initialCapacity))); 363 | 364 | /// Creates a [Uint8Queue] with the same length and contents as [elements]. 365 | factory Uint8Queue.fromList(List elements) => 366 | Uint8Queue(elements.length)..addAll(elements); 367 | 368 | @override 369 | Uint8List _createList(int size) => Uint8List(size); 370 | @override 371 | Uint8Buffer _createBuffer(int size) => Uint8Buffer(size); 372 | } 373 | 374 | /// A [QueueList] that efficiently stores 8-bit signed integers. 375 | /// 376 | /// For long queues, this implementation can be considerably more space- and 377 | /// time-efficient than a default [QueueList] implementation. 378 | /// 379 | /// Integers stored in this are truncated to their low eight bits, interpreted 380 | /// as a signed 8-bit two's complement integer with values in the range -128 to 381 | /// +127. 382 | class Int8Queue extends _IntQueue implements QueueList { 383 | /// Creates an empty [Int8Queue] with the given initial internal capacity (in 384 | /// elements). 385 | Int8Queue([int? initialCapacity]) 386 | : super(Int8List(_chooseRealInitialCapacity(initialCapacity))); 387 | 388 | /// Creates a [Int8Queue] with the same length and contents as [elements]. 389 | factory Int8Queue.fromList(List elements) => 390 | Int8Queue(elements.length)..addAll(elements); 391 | 392 | @override 393 | Int8List _createList(int size) => Int8List(size); 394 | @override 395 | Int8Buffer _createBuffer(int size) => Int8Buffer(size); 396 | } 397 | 398 | /// A [QueueList] that efficiently stores 8-bit unsigned integers. 399 | /// 400 | /// For long queues, this implementation can be considerably more space- and 401 | /// time-efficient than a default [QueueList] implementation. 402 | /// 403 | /// Integers stored in this are clamped to an unsigned eight bit value. That is, 404 | /// all values below zero are stored as zero and all values above 255 are stored 405 | /// as 255. 406 | class Uint8ClampedQueue extends _IntQueue 407 | implements QueueList { 408 | /// Creates an empty [Uint8ClampedQueue] with the given initial internal 409 | /// capacity (in elements). 410 | Uint8ClampedQueue([int? initialCapacity]) 411 | : super(Uint8ClampedList(_chooseRealInitialCapacity(initialCapacity))); 412 | 413 | /// Creates a [Uint8ClampedQueue] with the same length and contents as 414 | /// [elements]. 415 | factory Uint8ClampedQueue.fromList(List elements) => 416 | Uint8ClampedQueue(elements.length)..addAll(elements); 417 | 418 | @override 419 | Uint8ClampedList _createList(int size) => Uint8ClampedList(size); 420 | @override 421 | Uint8ClampedBuffer _createBuffer(int size) => Uint8ClampedBuffer(size); 422 | } 423 | 424 | /// A [QueueList] that efficiently stores 16-bit unsigned integers. 425 | /// 426 | /// For long queues, this implementation can be considerably more space- and 427 | /// time-efficient than a default [QueueList] implementation. 428 | /// 429 | /// Integers stored in this are truncated to their low 16 bits, interpreted as 430 | /// an unsigned 16-bit integer with values in the range 0 to 65535. 431 | class Uint16Queue extends _IntQueue implements QueueList { 432 | /// Creates an empty [Uint16Queue] with the given initial internal capacity 433 | /// (in elements). 434 | Uint16Queue([int? initialCapacity]) 435 | : super(Uint16List(_chooseRealInitialCapacity(initialCapacity))); 436 | 437 | /// Creates a [Uint16Queue] with the same length and contents as [elements]. 438 | factory Uint16Queue.fromList(List elements) => 439 | Uint16Queue(elements.length)..addAll(elements); 440 | 441 | @override 442 | Uint16List _createList(int size) => Uint16List(size); 443 | @override 444 | Uint16Buffer _createBuffer(int size) => Uint16Buffer(size); 445 | } 446 | 447 | /// A [QueueList] that efficiently stores 16-bit signed integers. 448 | /// 449 | /// For long queues, this implementation can be considerably more space- and 450 | /// time-efficient than a default [QueueList] implementation. 451 | /// 452 | /// Integers stored in this are truncated to their low 16 bits, interpreted as a 453 | /// signed 16-bit two's complement integer with values in the range -32768 to 454 | /// +32767. 455 | class Int16Queue extends _IntQueue implements QueueList { 456 | /// Creates an empty [Int16Queue] with the given initial internal capacity (in 457 | /// elements). 458 | Int16Queue([int? initialCapacity]) 459 | : super(Int16List(_chooseRealInitialCapacity(initialCapacity))); 460 | 461 | /// Creates a [Int16Queue] with the same length and contents as [elements]. 462 | factory Int16Queue.fromList(List elements) => 463 | Int16Queue(elements.length)..addAll(elements); 464 | 465 | @override 466 | Int16List _createList(int size) => Int16List(size); 467 | @override 468 | Int16Buffer _createBuffer(int size) => Int16Buffer(size); 469 | } 470 | 471 | /// A [QueueList] that efficiently stores 32-bit unsigned integers. 472 | /// 473 | /// For long queues, this implementation can be considerably more space- and 474 | /// time-efficient than a default [QueueList] implementation. 475 | /// 476 | /// Integers stored in this are truncated to their low 32 bits, interpreted as 477 | /// an unsigned 32-bit integer with values in the range 0 to 4294967295. 478 | class Uint32Queue extends _IntQueue implements QueueList { 479 | /// Creates an empty [Uint32Queue] with the given initial internal capacity 480 | /// (in elements). 481 | Uint32Queue([int? initialCapacity]) 482 | : super(Uint32List(_chooseRealInitialCapacity(initialCapacity))); 483 | 484 | /// Creates a [Uint32Queue] with the same length and contents as [elements]. 485 | factory Uint32Queue.fromList(List elements) => 486 | Uint32Queue(elements.length)..addAll(elements); 487 | 488 | @override 489 | Uint32List _createList(int size) => Uint32List(size); 490 | @override 491 | Uint32Buffer _createBuffer(int size) => Uint32Buffer(size); 492 | } 493 | 494 | /// A [QueueList] that efficiently stores 32-bit signed integers. 495 | /// 496 | /// For long queues, this implementation can be considerably more space- and 497 | /// time-efficient than a default [QueueList] implementation. 498 | /// 499 | /// Integers stored in this are truncated to their low 32 bits, interpreted as a 500 | /// signed 32-bit two's complement integer with values in the range -2147483648 501 | /// to 2147483647. 502 | class Int32Queue extends _IntQueue implements QueueList { 503 | /// Creates an empty [Int32Queue] with the given initial internal capacity (in 504 | /// elements). 505 | Int32Queue([int? initialCapacity]) 506 | : super(Int32List(_chooseRealInitialCapacity(initialCapacity))); 507 | 508 | /// Creates a [Int32Queue] with the same length and contents as [elements]. 509 | factory Int32Queue.fromList(List elements) => 510 | Int32Queue(elements.length)..addAll(elements); 511 | 512 | @override 513 | Int32List _createList(int size) => Int32List(size); 514 | @override 515 | Int32Buffer _createBuffer(int size) => Int32Buffer(size); 516 | } 517 | 518 | /// A [QueueList] that efficiently stores 64-bit unsigned integers. 519 | /// 520 | /// For long queues, this implementation can be considerably more space- and 521 | /// time-efficient than a default [QueueList] implementation. 522 | /// 523 | /// Integers stored in this are truncated to their low 64 bits, interpreted as 524 | /// an unsigned 64-bit integer with values in the range 0 to 525 | /// 18446744073709551615. 526 | class Uint64Queue extends _IntQueue implements QueueList { 527 | /// Creates an empty [Uint64Queue] with the given initial internal capacity 528 | /// (in elements). 529 | Uint64Queue([int? initialCapacity]) 530 | : super(Uint64List(_chooseRealInitialCapacity(initialCapacity))); 531 | 532 | /// Creates a [Uint64Queue] with the same length and contents as [elements]. 533 | factory Uint64Queue.fromList(List elements) => 534 | Uint64Queue(elements.length)..addAll(elements); 535 | 536 | @override 537 | Uint64List _createList(int size) => Uint64List(size); 538 | @override 539 | Uint64Buffer _createBuffer(int size) => Uint64Buffer(size); 540 | } 541 | 542 | /// A [QueueList] that efficiently stores 64-bit signed integers. 543 | /// 544 | /// For long queues, this implementation can be considerably more space- and 545 | /// time-efficient than a default [QueueList] implementation. 546 | /// 547 | /// Integers stored in this are truncated to their low 64 bits, interpreted as a 548 | /// signed 64-bit two's complement integer with values in the range 549 | /// -9223372036854775808 to +9223372036854775807. 550 | class Int64Queue extends _IntQueue implements QueueList { 551 | /// Creates an empty [Int64Queue] with the given initial internal capacity (in 552 | /// elements). 553 | Int64Queue([int? initialCapacity]) 554 | : super(Int64List(_chooseRealInitialCapacity(initialCapacity))); 555 | 556 | /// Creates a [Int64Queue] with the same length and contents as [elements]. 557 | factory Int64Queue.fromList(List elements) => 558 | Int64Queue(elements.length)..addAll(elements); 559 | 560 | @override 561 | Int64List _createList(int size) => Int64List(size); 562 | @override 563 | Int64Buffer _createBuffer(int size) => Int64Buffer(size); 564 | } 565 | 566 | /// A [QueueList] that efficiently stores IEEE 754 single-precision binary 567 | /// floating-point numbers. 568 | /// 569 | /// For long queues, this implementation can be considerably more space- and 570 | /// time-efficient than a default [QueueList] implementation. 571 | /// 572 | /// Doubles stored in this are converted to the nearest single-precision value. 573 | /// Values read are converted to a double value with the same value. 574 | class Float32Queue extends _FloatQueue 575 | implements QueueList { 576 | /// Creates an empty [Float32Queue] with the given initial internal capacity 577 | /// (in elements). 578 | Float32Queue([int? initialCapacity]) 579 | : super(Float32List(_chooseRealInitialCapacity(initialCapacity))); 580 | 581 | /// Creates a [Float32Queue] with the same length and contents as [elements]. 582 | factory Float32Queue.fromList(List elements) => 583 | Float32Queue(elements.length)..addAll(elements); 584 | 585 | @override 586 | Float32List _createList(int size) => Float32List(size); 587 | @override 588 | Float32Buffer _createBuffer(int size) => Float32Buffer(size); 589 | } 590 | 591 | /// A [QueueList] that efficiently stores IEEE 754 double-precision binary 592 | /// floating-point numbers. 593 | /// 594 | /// For long queues, this implementation can be considerably more space- and 595 | /// time-efficient than a default [QueueList] implementation. 596 | class Float64Queue extends _FloatQueue 597 | implements QueueList { 598 | /// Creates an empty [Float64Queue] with the given initial internal capacity 599 | /// (in elements). 600 | Float64Queue([int? initialCapacity]) 601 | : super(Float64List(_chooseRealInitialCapacity(initialCapacity))); 602 | 603 | /// Creates a [Float64Queue] with the same length and contents as [elements]. 604 | factory Float64Queue.fromList(List elements) => 605 | Float64Queue(elements.length)..addAll(elements); 606 | 607 | @override 608 | Float64List _createList(int size) => Float64List(size); 609 | @override 610 | Float64Buffer _createBuffer(int size) => Float64Buffer(size); 611 | } 612 | 613 | /// A [QueueList] that efficiently stores [Int32x4] numbers. 614 | /// 615 | /// For long queues, this implementation can be considerably more space- and 616 | /// time-efficient than a default [QueueList] implementation. 617 | class Int32x4Queue extends _TypedQueue 618 | implements QueueList { 619 | static final Int32x4 _zero = Int32x4(0, 0, 0, 0); 620 | 621 | /// Creates an empty [Int32x4Queue] with the given initial internal capacity 622 | /// (in elements). 623 | Int32x4Queue([int? initialCapacity]) 624 | : super(Int32x4List(_chooseRealInitialCapacity(initialCapacity))); 625 | 626 | /// Creates a [Int32x4Queue] with the same length and contents as [elements]. 627 | factory Int32x4Queue.fromList(List elements) => 628 | Int32x4Queue(elements.length)..addAll(elements); 629 | 630 | @override 631 | Int32x4List _createList(int size) => Int32x4List(size); 632 | @override 633 | Int32x4Buffer _createBuffer(int size) => Int32x4Buffer(size); 634 | @override 635 | Int32x4 get _defaultValue => _zero; 636 | } 637 | 638 | /// A [QueueList] that efficiently stores [Float32x4] numbers. 639 | /// 640 | /// For long queues, this implementation can be considerably more space- and 641 | /// time-efficient than a default [QueueList] implementation. 642 | class Float32x4Queue extends _TypedQueue 643 | implements QueueList { 644 | /// Creates an empty [Float32x4Queue] with the given initial internal capacity 645 | /// (in elements). 646 | Float32x4Queue([int? initialCapacity]) 647 | : super(Float32x4List(_chooseRealInitialCapacity(initialCapacity))); 648 | 649 | /// Creates a [Float32x4Queue] with the same length and contents as 650 | /// [elements]. 651 | factory Float32x4Queue.fromList(List elements) => 652 | Float32x4Queue(elements.length)..addAll(elements); 653 | 654 | @override 655 | Float32x4List _createList(int size) => Float32x4List(size); 656 | @override 657 | Float32x4Buffer _createBuffer(int size) => Float32x4Buffer(size); 658 | @override 659 | Float32x4 get _defaultValue => Float32x4.zero(); 660 | } 661 | 662 | /// The initial capacity of queues if the user doesn't specify one. 663 | const _defaultInitialCapacity = 16; 664 | 665 | /// Choose the next-highest power of two given a user-specified 666 | /// [initialCapacity] for a queue. 667 | int _chooseRealInitialCapacity(int? initialCapacity) { 668 | if (initialCapacity == null || initialCapacity < _defaultInitialCapacity) { 669 | return _defaultInitialCapacity; 670 | } else if (!_isPowerOf2(initialCapacity)) { 671 | return _nextPowerOf2(initialCapacity); 672 | } else { 673 | return initialCapacity; 674 | } 675 | } 676 | 677 | /// Whether [number] is a power of two. 678 | /// 679 | /// Only works for positive numbers. 680 | bool _isPowerOf2(int number) => (number & (number - 1)) == 0; 681 | 682 | /// Rounds [number] up to the nearest power of 2. 683 | /// 684 | /// If [number] is a power of 2 already, it is returned. 685 | /// 686 | /// Only works for positive numbers. 687 | int _nextPowerOf2(int number) { 688 | assert(number > 0); 689 | number = (number << 1) - 1; 690 | for (;;) { 691 | var nextNumber = number & (number - 1); 692 | if (nextNumber == 0) return number; 693 | number = nextNumber; 694 | } 695 | } 696 | -------------------------------------------------------------------------------- /lib/typed_buffers.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Growable typed-data lists. 6 | /// 7 | /// These lists works just as a typed-data list, except that they are growable. 8 | /// They use an underlying buffer, and when that buffer becomes too small, it 9 | /// is replaced by a new buffer. 10 | /// 11 | /// That means that using the `buffer` getter is not guaranteed 12 | /// to return the same result each time it is used, and that the buffer may 13 | /// be larger than what the list is using. 14 | library; 15 | 16 | export 'src/typed_buffer.dart' hide TypedDataBuffer; 17 | -------------------------------------------------------------------------------- /lib/typed_data.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// Utilities and functionality related to the "dart:typed_data" library. 6 | library; 7 | 8 | export 'src/typed_queue.dart'; 9 | export 'typed_buffers.dart'; 10 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: typed_data 2 | version: 1.4.0-wip 3 | description: >- 4 | Utility functions and classes related to the dart:typed_data library. 5 | repository: https://github.com/dart-lang/typed_data 6 | 7 | topics: 8 | - data-structures 9 | 10 | environment: 11 | sdk: ^3.5.0 12 | 13 | dependencies: 14 | collection: ^1.15.0 15 | 16 | dev_dependencies: 17 | dart_flutter_team_lints: ^3.0.0 18 | test: ^1.16.6 19 | -------------------------------------------------------------------------------- /test/queue_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | // ignore_for_file: avoid_function_literals_in_foreach_calls 6 | 7 | import 'package:test/test.dart'; 8 | import 'package:typed_data/typed_data.dart'; 9 | 10 | /// The initial capacity of queues if the user doesn't specify one. 11 | const capacity = 16; 12 | 13 | void main() { 14 | group('Uint8Queue()', () { 15 | test('creates an empty Uint8Queue', () { 16 | expect(Uint8Queue(), isEmpty); 17 | }); 18 | 19 | test('takes an initial capacity', () { 20 | expect(Uint8Queue(100), isEmpty); 21 | }); 22 | }); 23 | 24 | group('add() adds an element to the end', () { 25 | forEachInternalRepresentation((queue) { 26 | queue.add(16); 27 | expect(queue, equals(oneThrough(capacity))); 28 | }); 29 | }); 30 | 31 | group('addFirst() adds an element to the beginning', () { 32 | forEachInternalRepresentation((queue) { 33 | queue.addFirst(0); 34 | expect(queue, equals([0, ...oneThrough(capacity - 1)])); 35 | }); 36 | }); 37 | 38 | group('removeFirst() removes an element from the beginning', () { 39 | forEachInternalRepresentation((queue) { 40 | expect(queue.removeFirst(), equals(1)); 41 | expect(queue, equals(oneThrough(capacity - 1).skip(1))); 42 | }); 43 | 44 | test('throws a StateError for an empty queue', () { 45 | expect(Uint8Queue().removeFirst, throwsStateError); 46 | }); 47 | }); 48 | 49 | group('removeLast() removes an element from the end', () { 50 | forEachInternalRepresentation((queue) { 51 | expect(queue.removeLast(), equals(15)); 52 | expect(queue, equals(oneThrough(capacity - 2))); 53 | }); 54 | 55 | test('throws a StateError for an empty queue', () { 56 | expect(Uint8Queue().removeLast, throwsStateError); 57 | }); 58 | }); 59 | 60 | group('removeRange()', () { 61 | group('removes a prefix', () { 62 | forEachInternalRepresentation((queue) { 63 | queue.removeRange(0, 5); 64 | expect(queue, equals(oneThrough(capacity - 1).skip(5))); 65 | }); 66 | }); 67 | 68 | group('removes a suffix', () { 69 | forEachInternalRepresentation((queue) { 70 | queue.removeRange(10, 15); 71 | expect(queue, equals(oneThrough(capacity - 6))); 72 | }); 73 | }); 74 | 75 | group('removes from the middle', () { 76 | forEachInternalRepresentation((queue) { 77 | queue.removeRange(5, 10); 78 | expect(queue, equals([1, 2, 3, 4, 5, 11, 12, 13, 14, 15])); 79 | }); 80 | }); 81 | 82 | group('removes everything', () { 83 | forEachInternalRepresentation((queue) { 84 | queue.removeRange(0, 15); 85 | expect(queue, isEmpty); 86 | }); 87 | }); 88 | 89 | test('throws a RangeError for an invalid range', () { 90 | expect(() => Uint8Queue().removeRange(0, 1), throwsRangeError); 91 | }); 92 | }); 93 | 94 | group('setRange()', () { 95 | group('sets a range to the contents of an iterable', () { 96 | forEachInternalRepresentation((queue) { 97 | queue.setRange(5, 10, oneThrough(10).map((n) => 100 + n), 2); 98 | expect(queue, 99 | [1, 2, 3, 4, 5, 103, 104, 105, 106, 107, 11, 12, 13, 14, 15]); 100 | }); 101 | }); 102 | 103 | group('sets a range to the contents of a list', () { 104 | forEachInternalRepresentation((queue) { 105 | queue.setRange(5, 10, oneThrough(10).map((n) => 100 + n).toList(), 2); 106 | expect(queue, 107 | [1, 2, 3, 4, 5, 103, 104, 105, 106, 107, 11, 12, 13, 14, 15]); 108 | }); 109 | }); 110 | 111 | group( 112 | 'sets a range to a section of the same queue overlapping at the ' 113 | 'beginning', () { 114 | forEachInternalRepresentation((queue) { 115 | queue.setRange(5, 10, queue, 2); 116 | expect(queue, [1, 2, 3, 4, 5, 3, 4, 5, 6, 7, 11, 12, 13, 14, 15]); 117 | }); 118 | }); 119 | 120 | group('sets a range to a section of the same queue overlapping at the end', 121 | () { 122 | forEachInternalRepresentation((queue) { 123 | queue.setRange(5, 10, queue, 6); 124 | expect(queue, [1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 11, 12, 13, 14, 15]); 125 | }); 126 | }); 127 | 128 | test('throws a RangeError for an invalid range', () { 129 | expect(() => Uint8Queue().setRange(0, 1, [1]), throwsRangeError); 130 | }); 131 | }); 132 | 133 | group('length returns the length', () { 134 | forEachInternalRepresentation((queue) { 135 | expect(queue.length, equals(15)); 136 | }); 137 | }); 138 | 139 | group('length=', () { 140 | group('empties', () { 141 | forEachInternalRepresentation((queue) { 142 | queue.length = 0; 143 | expect(queue, isEmpty); 144 | }); 145 | }); 146 | 147 | group('shrinks', () { 148 | forEachInternalRepresentation((queue) { 149 | queue.length = 5; 150 | expect(queue, equals([1, 2, 3, 4, 5])); 151 | }); 152 | }); 153 | 154 | group('grows', () { 155 | forEachInternalRepresentation((queue) { 156 | queue.length = 20; 157 | expect( 158 | queue, 159 | equals(oneThrough(capacity - 1) + 160 | List.filled(20 - (capacity - 1), 0))); 161 | }); 162 | }); 163 | 164 | group('zeroes out existing data', () { 165 | forEachInternalRepresentation((queue) { 166 | queue.length = 0; 167 | queue.length = 15; 168 | expect(queue, equals([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])); 169 | }); 170 | }); 171 | 172 | test('throws a RangeError if length is less than 0', () { 173 | expect(() => Uint8Queue().length = -1, throwsRangeError); 174 | }); 175 | }); 176 | 177 | group('[]', () { 178 | group('returns individual entries', () { 179 | forEachInternalRepresentation((queue) { 180 | for (var i = 0; i < capacity - 1; i++) { 181 | expect(queue[i], equals(i + 1)); 182 | } 183 | }); 184 | }); 185 | 186 | test('throws a RangeError if the index is less than 0', () { 187 | var queue = Uint8Queue.fromList([1, 2, 3]); 188 | expect(() => queue[-1], throwsRangeError); 189 | }); 190 | 191 | test( 192 | 'throws a RangeError if the index is greater than or equal to the ' 193 | 'length', () { 194 | var queue = Uint8Queue.fromList([1, 2, 3]); 195 | expect(() => queue[3], throwsRangeError); 196 | }); 197 | }); 198 | 199 | group('[]=', () { 200 | group('sets individual entries', () { 201 | forEachInternalRepresentation((queue) { 202 | for (var i = 0; i < capacity - 1; i++) { 203 | queue[i] = 100 + i; 204 | } 205 | expect(queue, equals(List.generate(capacity - 1, (i) => 100 + i))); 206 | }); 207 | }); 208 | 209 | test('throws a RangeError if the index is less than 0', () { 210 | var queue = Uint8Queue.fromList([1, 2, 3]); 211 | expect(() { 212 | queue[-1] = 0; 213 | }, throwsRangeError); 214 | }); 215 | 216 | test( 217 | 'throws a RangeError if the index is greater than or equal to the ' 218 | 'length', () { 219 | var queue = Uint8Queue.fromList([1, 2, 3]); 220 | expect(() { 221 | queue[3] = 4; 222 | }, throwsRangeError); 223 | }); 224 | }); 225 | 226 | group('throws a modification error for', () { 227 | late Uint8Queue queue; 228 | setUp(() { 229 | queue = Uint8Queue.fromList([1, 2, 3]); 230 | }); 231 | 232 | test('add', () { 233 | expect(() => queue.forEach((_) => queue.add(4)), 234 | throwsConcurrentModificationError); 235 | }); 236 | 237 | test('addAll', () { 238 | expect(() => queue.forEach((_) => queue.addAll([4, 5, 6])), 239 | throwsConcurrentModificationError); 240 | }); 241 | 242 | test('addFirst', () { 243 | expect(() => queue.forEach((_) => queue.addFirst(0)), 244 | throwsConcurrentModificationError); 245 | }); 246 | 247 | test('removeFirst', () { 248 | expect(() => queue.forEach((_) => queue.removeFirst()), 249 | throwsConcurrentModificationError); 250 | }); 251 | 252 | test('removeLast', () { 253 | expect(() => queue.forEach((_) => queue.removeLast()), 254 | throwsConcurrentModificationError); 255 | }); 256 | 257 | test('length=', () { 258 | expect(() => queue.forEach((_) => queue.length = 1), 259 | throwsConcurrentModificationError); 260 | }); 261 | }); 262 | } 263 | 264 | /// Runs [callback] in multiple tests, all with queues containing numbers from 265 | /// one through 15 in various different internal states. 266 | void forEachInternalRepresentation(void Function(Uint8Queue queue) callback) { 267 | // Test with a queue whose internal table has plenty of room. 268 | group("for a queue that's below capacity", () { 269 | // Test with a queue whose elements are in one contiguous block, so `_head < 270 | // _tail`. 271 | test('with contiguous elements', () { 272 | callback(Uint8Queue(capacity * 2)..addAll(oneThrough(capacity - 1))); 273 | }); 274 | 275 | // Test with a queue whose elements are split across the ends of the table, 276 | // so `_head > _tail`. 277 | test('with an internal gap', () { 278 | var queue = Uint8Queue(capacity * 2); 279 | for (var i = capacity ~/ 2; i < capacity; i++) { 280 | queue.add(i); 281 | } 282 | for (var i = capacity ~/ 2 - 1; i > 0; i--) { 283 | queue.addFirst(i); 284 | } 285 | callback(queue); 286 | }); 287 | }); 288 | 289 | // Test with a queue whose internal table will need to expand if one more 290 | // element is added. 291 | group("for a queue that's at capacity", () { 292 | test('with contiguous elements', () { 293 | callback(Uint8Queue()..addAll(oneThrough(capacity - 1))); 294 | }); 295 | 296 | test('with an internal gap', () { 297 | var queue = Uint8Queue(); 298 | for (var i = capacity ~/ 2; i < capacity; i++) { 299 | queue.add(i); 300 | } 301 | for (var i = capacity ~/ 2 - 1; i > 0; i--) { 302 | queue.addFirst(i); 303 | } 304 | callback(queue); 305 | }); 306 | }); 307 | } 308 | 309 | /// Returns a list containing the integers from one through [n]. 310 | List oneThrough(int n) => List.generate(n, (i) => i + 1); 311 | 312 | /// Returns a matcher that expects that a closure throws a 313 | /// [ConcurrentModificationError]. 314 | final throwsConcurrentModificationError = 315 | throwsA(const TypeMatcher()); 316 | -------------------------------------------------------------------------------- /test/typed_buffers_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn('!vm') 6 | library; 7 | 8 | import 'dart:typed_data'; 9 | 10 | import 'package:test/test.dart'; 11 | import 'package:typed_data/src/typed_buffer.dart'; 12 | 13 | const List browserSafeIntSamples = [ 14 | 0x8000000000000000, // 2^63 15 | 0x100000001, 16 | 0x100000000, // 2^32 17 | 0x0ffffffff, 18 | 0xaaaaaaaa, 19 | 0x80000001, 20 | 0x80000000, // 2^31 21 | 0x7fffffff, 22 | 0x55555555, 23 | 0x10001, 24 | 0x10000, // 2^16 25 | 0x0ffff, 26 | 0xaaaa, 27 | 0x8001, 28 | 0x8000, // 2^15 29 | 0x7fff, 30 | 0x5555, 31 | 0x101, 32 | 0x100, // 2^8 33 | 0x0ff, 34 | 0xaa, 35 | 0x81, 36 | 0x80, // 2^7 37 | 0x7f, 38 | 0x55, 39 | 0x02, 40 | 0x01, 41 | 0x00 42 | ]; 43 | 44 | void main() { 45 | initTests(browserSafeIntSamples); 46 | } 47 | 48 | void initTests(List intSamples) { 49 | testUint(intSamples, 8, Uint8Buffer.new); 50 | testInt(intSamples, 8, Int8Buffer.new); 51 | test('Uint8ClampedBuffer', () { 52 | testIntBuffer(intSamples, 8, 0, 255, Uint8ClampedBuffer.new, clampUint8); 53 | }); 54 | testUint(intSamples, 16, Uint16Buffer.new); 55 | testInt(intSamples, 16, Int16Buffer.new); 56 | testUint(intSamples, 32, Uint32Buffer.new); 57 | 58 | testInt(intSamples, 32, Int32Buffer.new); 59 | 60 | testUint(intSamples, 64, Uint64Buffer.new, 61 | // JS doesn't support 64-bit ints, so only test this on the VM. 62 | testOn: 'dart-vm'); 63 | testInt(intSamples, 64, Int64Buffer.new, 64 | // JS doesn't support 64-bit ints, so only test this on the VM. 65 | testOn: 'dart-vm'); 66 | 67 | testInt32x4Buffer(intSamples); 68 | 69 | var roundedFloatSamples = floatSamples.map(roundToFloat).toList(); 70 | testFloatBuffer(32, roundedFloatSamples, Float32Buffer.new, roundToFloat); 71 | testFloatBuffer(64, doubleSamples, Float64Buffer.new, (x) => x); 72 | 73 | testFloat32x4Buffer(roundedFloatSamples); 74 | 75 | group('addAll', () { 76 | for (var type in ['a list', 'an iterable']) { 77 | group('with $type', () { 78 | late Iterable source; 79 | late Uint8Buffer buffer; 80 | setUp(() { 81 | source = [1, 2, 3, 4, 5]; 82 | if (type == 'an iterable') { 83 | source = (source as List).reversed.toList().reversed; 84 | } 85 | buffer = Uint8Buffer(); 86 | }); 87 | 88 | test('adds values to the buffer', () { 89 | buffer.addAll(source, 1, 4); 90 | expect(buffer, equals([2, 3, 4])); 91 | 92 | buffer.addAll(source, 4); 93 | expect(buffer, equals([2, 3, 4, 5])); 94 | 95 | buffer.addAll(source, 0, 1); 96 | expect(buffer, equals([2, 3, 4, 5, 1])); 97 | }); 98 | 99 | test('does nothing for empty slices', () { 100 | buffer.addAll([6, 7, 8, 9, 10]); 101 | 102 | buffer.addAll(source, 0, 0); 103 | expect(buffer, equals([6, 7, 8, 9, 10])); 104 | 105 | buffer.addAll(source, 3, 3); 106 | expect(buffer, equals([6, 7, 8, 9, 10])); 107 | 108 | buffer.addAll(source, 5); 109 | expect(buffer, equals([6, 7, 8, 9, 10])); 110 | 111 | buffer.addAll(source, 5, 5); 112 | expect(buffer, equals([6, 7, 8, 9, 10])); 113 | }); 114 | 115 | test('throws errors for invalid start and end', () { 116 | expect(() => buffer.addAll(source, -1), throwsRangeError); 117 | expect(() => buffer.addAll(source, -1, 2), throwsRangeError); 118 | expect(() => buffer.addAll(source, 10), throwsStateError); 119 | expect(() => buffer.addAll(source, 10, 11), throwsStateError); 120 | expect(() => buffer.addAll(source, 3, 2), throwsRangeError); 121 | expect(() => buffer.addAll(source, 3, 10), throwsStateError); 122 | expect(() => buffer.addAll(source, 3, -1), throwsRangeError); 123 | }); 124 | }); 125 | } 126 | }); 127 | 128 | group('insertAll', () { 129 | for (var type in ['a list', 'an iterable']) { 130 | group('with $type', () { 131 | late Iterable source; 132 | late Uint8Buffer buffer; 133 | setUp(() { 134 | source = [1, 2, 3, 4, 5]; 135 | if (type == 'an iterable') { 136 | source = (source as List).reversed.toList().reversed; 137 | } 138 | buffer = Uint8Buffer()..addAll([6, 7, 8, 9, 10]); 139 | }); 140 | 141 | test('inserts values into the buffer', () { 142 | buffer.insertAll(0, source, 1, 4); 143 | expect(buffer, equals([2, 3, 4, 6, 7, 8, 9, 10])); 144 | 145 | buffer.insertAll(3, source, 4); 146 | expect(buffer, equals([2, 3, 4, 5, 6, 7, 8, 9, 10])); 147 | 148 | buffer.insertAll(5, source, 0, 1); 149 | expect(buffer, equals([2, 3, 4, 5, 6, 1, 7, 8, 9, 10])); 150 | }); 151 | 152 | // Regression test for #1. 153 | test('inserts values into the buffer after removeRange()', () { 154 | buffer.removeRange(1, 4); 155 | buffer.insertAll(1, source); 156 | expect(buffer, equals([6, 1, 2, 3, 4, 5, 10])); 157 | }); 158 | 159 | test('does nothing for empty slices', () { 160 | buffer.insertAll(1, source, 0, 0); 161 | expect(buffer, equals([6, 7, 8, 9, 10])); 162 | 163 | buffer.insertAll(2, source, 3, 3); 164 | expect(buffer, equals([6, 7, 8, 9, 10])); 165 | 166 | buffer.insertAll(3, source, 5); 167 | expect(buffer, equals([6, 7, 8, 9, 10])); 168 | 169 | buffer.insertAll(4, source, 5, 5); 170 | expect(buffer, equals([6, 7, 8, 9, 10])); 171 | }); 172 | 173 | test('throws errors for invalid start and end', () { 174 | expect(() => buffer.insertAll(-1, source), throwsRangeError); 175 | expect(() => buffer.insertAll(6, source), throwsRangeError); 176 | expect(() => buffer.insertAll(1, source, -1), throwsRangeError); 177 | expect(() => buffer.insertAll(2, source, -1, 2), throwsRangeError); 178 | expect(() => buffer.insertAll(3, source, 10), throwsStateError); 179 | expect(() => buffer.insertAll(4, source, 10, 11), throwsStateError); 180 | expect(() => buffer.insertAll(5, source, 3, 2), throwsRangeError); 181 | expect(() => buffer.insertAll(1, source, 3, 10), throwsStateError); 182 | expect(() => buffer.insertAll(2, source, 3, -1), throwsRangeError); 183 | }); 184 | }); 185 | } 186 | }); 187 | } 188 | 189 | const doubleSamples = [ 190 | 0.0, 191 | 5e-324, // Minimal denormal value. 192 | 2.225073858507201e-308, // Maximal denormal value. 193 | 2.2250738585072014e-308, // Minimal normal value. 194 | 0.9999999999999999, // Maximum value < 1. 195 | 1.0, 196 | 1.0000000000000002, // Minimum value > 1. 197 | 4294967295.0, // 2^32 -1. 198 | 4294967296.0, // 2^32. 199 | 4503599627370495.5, // Maximal fractional value. 200 | 9007199254740992.0, // Maximal exact value (adding one gets lost). 201 | 1.7976931348623157e+308, // Maximal value. 202 | 1.0 / 0.0, // Infinity. 203 | 0.0 / 0.0, // NaN. 204 | 0.49999999999999994, // Round-traps 1-3 (adding 0.5 and rounding towards 205 | 4503599627370497.0, // minus infinity will not be the same as rounding 206 | 9007199254740991.0 // to nearest with 0.5 rounding up). 207 | ]; 208 | 209 | const floatSamples = [ 210 | 0.0, 211 | 1.4e-45, // Minimal denormal value. 212 | 1.1754942E-38, // Maximal denormal value. 213 | 1.17549435E-38, // Minimal normal value. 214 | 0.99999994, // Maximal value < 1. 215 | 1.0, 216 | 1.0000001, // Minimal value > 1. 217 | 8388607.5, // Maximal fractional value. 218 | 16777216.0, // Maximal exact value. 219 | 3.4028235e+38, // Maximal value. 220 | 1.0 / 0.0, // Infinity. 221 | 0.0 / 0.0, // NaN. 222 | 0.99999994, // Round traps 1-3. 223 | 8388609.0, 224 | 16777215.0 225 | ]; 226 | 227 | int clampUint8(int x) => x < 0 228 | ? 0 229 | : x > 255 230 | ? 255 231 | : x; 232 | 233 | void doubleEqual(num x, num y) { 234 | if (y.isNaN) { 235 | expect(x.isNaN, isTrue); 236 | } else { 237 | expect(x, equals(y)); 238 | } 239 | } 240 | 241 | Rounder intRounder(int bits) { 242 | var highBit = 1 << (bits - 1); 243 | var mask = highBit - 1; 244 | return (int x) => (x & mask) - (x & highBit); 245 | } 246 | 247 | double roundToFloat(double value) { 248 | return (Float32List(1)..[0] = value)[0]; 249 | } 250 | 251 | void testFloat32x4Buffer(List floatSamples) { 252 | var float4Samples = []; 253 | for (var i = 0; i < floatSamples.length - 3; i++) { 254 | float4Samples.add(Float32x4(floatSamples[i], floatSamples[i + 1], 255 | floatSamples[i + 2], floatSamples[i + 3])); 256 | } 257 | 258 | void floatEquals(num x, num y) { 259 | if (y.isNaN) { 260 | expect(x.isNaN, isTrue); 261 | } else { 262 | expect(x, equals(y)); 263 | } 264 | } 265 | 266 | void x4Equals(Float32x4 x, Float32x4 y) { 267 | floatEquals(x.x, y.x); 268 | floatEquals(x.y, y.y); 269 | floatEquals(x.z, y.z); 270 | floatEquals(x.w, y.w); 271 | } 272 | 273 | test('Float32x4Buffer', () { 274 | var buffer = Float32x4Buffer(5); 275 | expect(buffer, const TypeMatcher>()); 276 | 277 | expect(buffer.length, equals(5)); 278 | expect(buffer.elementSizeInBytes, equals(128 ~/ 8)); 279 | expect(buffer.lengthInBytes, equals(5 * 128 ~/ 8)); 280 | expect(buffer.offsetInBytes, equals(0)); 281 | 282 | x4Equals(buffer[0], Float32x4.zero()); 283 | buffer.length = 0; 284 | expect(buffer.length, equals(0)); 285 | 286 | for (var sample in float4Samples) { 287 | buffer.add(sample); 288 | x4Equals(buffer[buffer.length - 1], sample); 289 | } 290 | expect(buffer.length, equals(float4Samples.length)); 291 | 292 | buffer.addAll(float4Samples); 293 | expect(buffer.length, equals(float4Samples.length * 2)); 294 | for (var i = 0; i < float4Samples.length; i++) { 295 | x4Equals(buffer[i], buffer[float4Samples.length + i]); 296 | } 297 | 298 | buffer.removeRange(4, 4 + float4Samples.length); 299 | for (var i = 0; i < float4Samples.length; i++) { 300 | x4Equals(buffer[i], float4Samples[i]); 301 | } 302 | 303 | // Test underlying buffer. 304 | buffer.length = 1; 305 | buffer[0] = float4Samples[0]; // Does not contain NaN. 306 | 307 | var floats = Float32List.view(buffer.buffer); 308 | expect(floats[0], equals(buffer[0].x)); 309 | expect(floats[1], equals(buffer[0].y)); 310 | expect(floats[2], equals(buffer[0].z)); 311 | expect(floats[3], equals(buffer[0].w)); 312 | }); 313 | } 314 | 315 | // Takes bit-size, min value, max value, function to create a buffer, and 316 | // the rounding that is applied when storing values outside the valid range 317 | // into the buffer. 318 | void testFloatBuffer( 319 | int bitSize, 320 | List samples, 321 | TypedDataBuffer Function() create, 322 | double Function(double v) round, 323 | ) { 324 | test('Float${bitSize}Buffer', () { 325 | var buffer = create(); 326 | expect(buffer, const TypeMatcher>()); 327 | var byteSize = bitSize ~/ 8; 328 | 329 | expect(buffer.length, equals(0)); 330 | buffer.add(0.0); 331 | expect(buffer.length, equals(1)); 332 | expect(buffer.removeLast(), equals(0.0)); 333 | expect(buffer.length, equals(0)); 334 | 335 | for (var value in samples) { 336 | buffer.add(value); 337 | doubleEqual(buffer[buffer.length - 1], round(value)); 338 | } 339 | expect(buffer.length, equals(samples.length)); 340 | 341 | buffer.addAll(samples); 342 | expect(buffer.length, equals(samples.length * 2)); 343 | for (var i = 0; i < samples.length; i++) { 344 | doubleEqual(buffer[i], buffer[samples.length + i]); 345 | } 346 | 347 | buffer.removeRange(samples.length, buffer.length); 348 | expect(buffer.length, equals(samples.length)); 349 | 350 | buffer.insertAll(0, samples); 351 | expect(buffer.length, equals(samples.length * 2)); 352 | for (var i = 0; i < samples.length; i++) { 353 | doubleEqual(buffer[i], buffer[samples.length + i]); 354 | } 355 | 356 | buffer.length = samples.length; 357 | expect(buffer.length, equals(samples.length)); 358 | 359 | // TypedData. 360 | expect(buffer.elementSizeInBytes, equals(byteSize)); 361 | expect(buffer.lengthInBytes, equals(byteSize * buffer.length)); 362 | expect(buffer.offsetInBytes, equals(0)); 363 | 364 | // Accessing the buffer works. 365 | // Accessing the underlying buffer works. 366 | buffer.length = 2; 367 | buffer[0] = samples[0]; 368 | buffer[1] = samples[1]; 369 | var bytes = Uint8List.view(buffer.buffer); 370 | for (var i = 0; i < byteSize; i++) { 371 | var tmp = bytes[i]; 372 | bytes[i] = bytes[byteSize + i]; 373 | bytes[byteSize + i] = tmp; 374 | } 375 | doubleEqual(buffer[0], round(samples[1])); 376 | doubleEqual(buffer[1], round(samples[0])); 377 | }); 378 | } 379 | 380 | void testInt( 381 | List intSamples, 382 | int bits, 383 | TypedDataBuffer Function(int length) buffer, { 384 | String? testOn, 385 | }) { 386 | var min = -(1 << (bits - 1)); 387 | var max = -(min + 1); 388 | test('Int${bits}Buffer', () { 389 | testIntBuffer(intSamples, bits, min, max, buffer, intRounder(bits)); 390 | }, testOn: testOn); 391 | } 392 | 393 | void testInt32x4Buffer(List intSamples) { 394 | test('Int32x4Buffer', () { 395 | var bytes = 128 ~/ 8; 396 | Matcher equals32x4(Int32x4 expected) => MatchesInt32x4(expected); 397 | 398 | var buffer = Int32x4Buffer(0); 399 | expect(buffer, const TypeMatcher>()); 400 | expect(buffer.length, equals(0)); 401 | 402 | expect(buffer.elementSizeInBytes, equals(bytes)); 403 | expect(buffer.lengthInBytes, equals(0)); 404 | expect(buffer.offsetInBytes, equals(0)); 405 | 406 | var sample = Int32x4(-0x80000000, -1, 0, 0x7fffffff); 407 | buffer.add(sample); 408 | expect(buffer.length, equals(1)); 409 | expect(buffer[0], equals32x4(sample)); 410 | 411 | expect(buffer.elementSizeInBytes, equals(bytes)); 412 | expect(buffer.lengthInBytes, equals(bytes)); 413 | expect(buffer.offsetInBytes, equals(0)); 414 | 415 | buffer.length = 0; 416 | expect(buffer.length, equals(0)); 417 | 418 | var samples = intSamples 419 | .map((value) => Int32x4(value, -value, ~value, ~ -value)) 420 | .toList(); 421 | for (var value in samples) { 422 | var length = buffer.length; 423 | buffer.add(value); 424 | expect(buffer.length, equals(length + 1)); 425 | expect(buffer[length], equals32x4(value)); 426 | } 427 | 428 | buffer.addAll(samples); // Add all the values at once. 429 | for (var i = 0; i < samples.length; i++) { 430 | expect(buffer[samples.length + i], equals32x4(buffer[i])); 431 | } 432 | 433 | // Remove range works and changes length. 434 | buffer.removeRange(samples.length, buffer.length); 435 | expect(buffer.length, equals(samples.length)); 436 | 437 | // Accessing the underlying buffer works. 438 | buffer.length = 2; 439 | buffer[0] = Int32x4(-80000000, 0x7fffffff, 0, -1); 440 | var byteBuffer = Uint8List.view(buffer.buffer); 441 | var halfBytes = bytes ~/ 2; 442 | for (var i = 0; i < halfBytes; i++) { 443 | var tmp = byteBuffer[i]; 444 | byteBuffer[i] = byteBuffer[halfBytes + i]; 445 | byteBuffer[halfBytes + i] = tmp; 446 | } 447 | var result = Int32x4(0, -1, -80000000, 0x7fffffff); 448 | expect(buffer[0], equals32x4(result)); 449 | }); 450 | } 451 | 452 | void testIntBuffer( 453 | List intSamples, 454 | int bits, 455 | int min, 456 | int max, 457 | TypedDataBuffer Function(int length) create, 458 | int Function(int val) round, 459 | ) { 460 | assert(round(min) == min); 461 | assert(round(max) == max); 462 | // All int buffers default to the value 0. 463 | var buffer = create(0); 464 | expect(buffer, const TypeMatcher>()); 465 | expect(buffer.length, equals(0)); 466 | var bytes = bits ~/ 8; 467 | 468 | expect(buffer.elementSizeInBytes, equals(bytes)); 469 | expect(buffer.lengthInBytes, equals(0)); 470 | expect(buffer.offsetInBytes, equals(0)); 471 | 472 | buffer.add(min); 473 | expect(buffer.length, equals(1)); 474 | expect(buffer[0], equals(min)); 475 | 476 | expect(buffer.elementSizeInBytes, equals(bytes)); 477 | expect(buffer.lengthInBytes, equals(bytes)); 478 | expect(buffer.offsetInBytes, equals(0)); 479 | 480 | buffer.length = 0; 481 | expect(buffer.length, equals(0)); 482 | 483 | var samples = intSamples.toList()..addAll(intSamples.map((x) => -x)); 484 | for (var value in samples) { 485 | var length = buffer.length; 486 | buffer.add(value); 487 | expect(buffer.length, equals(length + 1)); 488 | expect(buffer[length], equals(round(value))); 489 | } 490 | buffer.addAll(samples); // Add all the values at once. 491 | for (var i = 0; i < samples.length; i++) { 492 | expect(buffer[samples.length + i], equals(buffer[i])); 493 | } 494 | 495 | // Remove range works and changes length. 496 | buffer.removeRange(samples.length, buffer.length); 497 | expect(buffer.length, equals(samples.length)); 498 | 499 | // Both values are in `samples`, but equality is performed without rounding. 500 | // For signed 64 bit ints, min and max wrap around, min-1=max and max+1=min 501 | if (bits == 64) { 502 | // TODO(keertip): fix tests for Uint64 / Int64 as now Uints are represented 503 | // as signed ints. 504 | expect(buffer.contains(min - 1), isTrue); 505 | expect(buffer.contains(max + 1), isTrue); 506 | } else { 507 | // Both values are in `samples`, but equality is performed without rounding. 508 | expect(buffer.contains(min - 1), isFalse); 509 | expect(buffer.contains(max + 1), isFalse); 510 | } 511 | expect(buffer.contains(round(min - 1)), isTrue); 512 | expect(buffer.contains(round(max + 1)), isTrue); 513 | 514 | // Accessing the underlying buffer works. 515 | buffer.length = 2; 516 | buffer[0] = min; 517 | buffer[1] = max; 518 | var byteBuffer = Uint8List.view(buffer.buffer); 519 | var byteSize = buffer.elementSizeInBytes; 520 | for (var i = 0; i < byteSize; i++) { 521 | var tmp = byteBuffer[i]; 522 | byteBuffer[i] = byteBuffer[byteSize + i]; 523 | byteBuffer[byteSize + i] = tmp; 524 | } 525 | expect(buffer[0], equals(max)); 526 | expect(buffer[1], equals(min)); 527 | } 528 | 529 | void testUint( 530 | List intSamples, 531 | int bits, 532 | TypedDataBuffer Function(int length) buffer, { 533 | String? testOn, 534 | }) { 535 | var min = 0; 536 | var rounder = uintRounder(bits); 537 | var max = rounder(-1); 538 | test('Uint${bits}Buffer', () { 539 | testIntBuffer(intSamples, bits, min, max, buffer, rounder); 540 | }, testOn: testOn); 541 | } 542 | 543 | Rounder uintRounder(int bits) { 544 | var halfbits = (1 << (bits ~/ 2)) - 1; 545 | var mask = halfbits | (halfbits << (bits ~/ 2)); 546 | return (int x) => x & mask; 547 | } 548 | 549 | typedef Rounder = int Function(int value); 550 | 551 | class MatchesInt32x4 extends Matcher { 552 | Int32x4 result; 553 | 554 | MatchesInt32x4(this.result); 555 | 556 | @override 557 | Description describe(Description description) => 558 | description.add('Int32x4.=='); 559 | 560 | @override 561 | bool matches(Object? item, Map matchState) => 562 | item is Int32x4 && 563 | result.x == item.x && 564 | result.y == item.y && 565 | result.z == item.z && 566 | result.w == item.w; 567 | } 568 | -------------------------------------------------------------------------------- /test/typed_buffers_vm_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | @TestOn('vm') 6 | library; 7 | 8 | import 'package:test/test.dart'; 9 | 10 | import 'typed_buffers_test.dart'; 11 | 12 | void main() { 13 | var browserUnsafe = [ 14 | 0x0ffffffffffffffff, 15 | 0xaaaaaaaaaaaaaaaa, 16 | 0x8000000000000001, 17 | 0x7fffffffffffffff, 18 | 0x5555555555555555, 19 | ]; 20 | initTests([ 21 | ...browserSafeIntSamples, 22 | ...browserUnsafe, 23 | ]); 24 | } 25 | --------------------------------------------------------------------------------