-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 21a4165
Showing
14 changed files
with
620 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Auto detect text files and perform LF normalization | ||
* text=auto |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Miscellaneous | ||
*.class | ||
*.log | ||
*.pyc | ||
*.swp | ||
.DS_Store | ||
.atom/ | ||
.buildlog/ | ||
.history | ||
.svn/ | ||
migrate_working_dir/ | ||
|
||
# IntelliJ related | ||
*.iml | ||
*.ipr | ||
*.iws | ||
.idea/ | ||
|
||
# The .vscode folder contains launch configuration and tasks you configure in | ||
# VS Code which you may wish to be included in version control, so this line | ||
# is commented out by default. | ||
#.vscode/ | ||
|
||
# Flutter/Dart/Pub related | ||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. | ||
/pubspec.lock | ||
**/doc/api/ | ||
.dart_tool/ | ||
build/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# This file tracks properties of this Flutter project. | ||
# Used by Flutter tool to assess capabilities and perform upgrades etc. | ||
# | ||
# This file should be version controlled and should not be manually edited. | ||
|
||
version: | ||
revision: "b0850beeb25f6d5b10426284f506557f66181b36" | ||
channel: "stable" | ||
|
||
project_type: package |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
## 0.1.0 | ||
|
||
- initial release |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
BSD 3-Clause License | ||
|
||
Copyright (c) 2024, Martin Klüpfel | ||
All rights reserved. | ||
|
||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are met: | ||
|
||
1. Redistributions of source code must retain the above copyright notice, this | ||
list of conditions and the following disclaimer. | ||
|
||
2. 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. | ||
|
||
3. Neither the name of the copyright holder 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 HOLDER 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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
<!-- | ||
This README describes the package. If you publish this package to pub.dev, | ||
this README's contents appear on the landing page for your package. | ||
For information about how to write a good package README, see the guide for | ||
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages). | ||
For general information about developing packages, see the Dart guide for | ||
[creating packages](https://dart.dev/guides/libraries/create-library-packages) | ||
and the Flutter guide for | ||
[developing packages and plugins](https://flutter.dev/developing-packages). | ||
--> | ||
|
||
TODO: Put a short description of the package here that helps potential users | ||
know whether this package might be useful for them. | ||
|
||
## Features | ||
|
||
TODO: List what your package can do. Maybe include images, gifs, or videos. | ||
|
||
## Getting started | ||
|
||
TODO: List prerequisites and provide or point to information on how to | ||
start using the package. | ||
|
||
## Usage | ||
|
||
TODO: Include short and useful examples for package users. Add longer examples | ||
to `/example` folder. | ||
|
||
```dart | ||
const like = 'sample'; | ||
``` | ||
|
||
## Additional information | ||
|
||
TODO: Tell users more about the package: where to find more information, how to | ||
contribute to the package, how to file issues, what response they can expect | ||
from the package authors, and more. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
include: package:flutter_lints/flutter.yaml | ||
|
||
# Additional information about this file can be found at | ||
# https://dart.dev/guides/language/analysis-options |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
SF:lib/src/token_bucket_state.dart | ||
DA:5,1 | ||
DA:7,1 | ||
DA:8,1 | ||
DA:9,1 | ||
DA:10,1 | ||
DA:14,1 | ||
DA:15,5 | ||
DA:17,1 | ||
DA:18,1 | ||
DA:19,1 | ||
DA:21,2 | ||
DA:24,2 | ||
DA:25,1 | ||
DA:26,2 | ||
DA:29,1 | ||
DA:32,1 | ||
DA:33,3 | ||
DA:34,3 | ||
DA:35,3 | ||
DA:37,1 | ||
DA:38,5 | ||
LF:21 | ||
LH:21 | ||
end_of_record | ||
SF:lib/src/token_bucket.dart | ||
DA:14,1 | ||
DA:19,2 | ||
DA:20,2 | ||
DA:21,2 | ||
DA:27,1 | ||
DA:28,2 | ||
DA:31,2 | ||
DA:32,1 | ||
DA:35,4 | ||
DA:36,2 | ||
DA:38,6 | ||
DA:40,1 | ||
DA:42,4 | ||
DA:50,1 | ||
DA:55,2 | ||
DA:57,1 | ||
DA:59,2 | ||
DA:60,5 | ||
DA:63,6 | ||
DA:66,1 | ||
DA:68,3 | ||
DA:69,3 | ||
DA:71,2 | ||
DA:72,3 | ||
DA:73,1 | ||
DA:74,2 | ||
DA:79,1 | ||
DA:80,4 | ||
DA:81,3 | ||
DA:82,2 | ||
DA:89,1 | ||
DA:94,2 | ||
DA:96,1 | ||
DA:98,5 | ||
DA:99,3 | ||
DA:102,1 | ||
DA:104,3 | ||
DA:105,3 | ||
DA:107,3 | ||
DA:108,1 | ||
DA:109,2 | ||
LF:41 | ||
LH:41 | ||
end_of_record | ||
SF:lib/src/token_bucket_storage.dart | ||
DA:7,1 | ||
DA:17,1 | ||
DA:22,1 | ||
DA:23,1 | ||
DA:25,1 | ||
DA:26,1 | ||
DA:30,1 | ||
LF:7 | ||
LH:7 | ||
end_of_record |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import 'dart:async'; | ||
import 'dart:math'; | ||
|
||
import 'package:clock/clock.dart'; | ||
import 'package:token_bucket_algorithm/src/token_bucket_storage.dart'; | ||
|
||
part 'token_bucket_state.dart'; | ||
|
||
abstract class _BaseTokenBucket<S extends AsyncTokenBucketStorage> { | ||
/// The maximum token amount of this bucket. | ||
final int size; | ||
|
||
/// The interval for refilling the bucket. | ||
final Duration refillInterval; | ||
|
||
/// The amount of tokens that is refilled every [refillInterval]. | ||
final int refillAmount; | ||
|
||
/// The storage for the internal [TokenBucketState]. | ||
final S storage; | ||
|
||
_BaseTokenBucket({ | ||
required this.size, | ||
required this.refillInterval, | ||
required this.refillAmount, | ||
required this.storage, | ||
}) : assert(size > 0), | ||
assert(refillAmount > 0), | ||
assert(refillInterval > Duration.zero); | ||
|
||
/// Returns the currently available tokens of this bucket. | ||
FutureOr<int> get availableTokens; | ||
|
||
/// Consumes [cost] tokens and returns whether the consuming was successful. | ||
FutureOr<bool> consume([int cost = 1]); | ||
|
||
TokenBucketState _refillBucket(TokenBucketState state) { | ||
final now = clock.now(); | ||
|
||
// relevant if e.g. user updates time in settings | ||
if (state.lastRefillTime.isAfter(now)) { | ||
return state.copyWith(lastRefillTime: now); | ||
} | ||
|
||
final refillTimes = (now.difference(state.lastRefillTime)).inMicroseconds ~/ | ||
refillInterval.inMicroseconds; | ||
|
||
final newTokenCount = min(size, state.tokens + refillTimes * refillAmount); | ||
|
||
return state.copyWith( | ||
tokens: newTokenCount, | ||
lastRefillTime: state.lastRefillTime.add(refillInterval * refillTimes), | ||
); | ||
} | ||
} | ||
|
||
/// An async token bucket which can have an async [storage]. | ||
class AsyncTokenBucket extends _BaseTokenBucket<AsyncTokenBucketStorage> { | ||
Future<void> _future = Future.value(); | ||
|
||
AsyncTokenBucket({ | ||
required super.size, | ||
required super.refillInterval, | ||
required super.refillAmount, | ||
AsyncTokenBucketStorage? storage, | ||
}) : super(storage: storage ?? MemoryTokenBucketStorage()); | ||
|
||
@override | ||
FutureOr<int> get availableTokens async { | ||
await _queueFuture(() async { | ||
await storage.set(_refillBucket(await storage.get())); | ||
return false; | ||
}); | ||
return Future.value(storage.get()).then((state) => state.tokens); | ||
} | ||
|
||
@override | ||
FutureOr<bool> consume([int cost = 1]) async { | ||
if (cost < 1 || cost > size) { | ||
throw ArgumentError('cost must be <=$size and >=1'); | ||
} | ||
return _queueFuture(() async { | ||
final state = _refillBucket(await storage.get()); | ||
final (result, newState) = state.consume(cost); | ||
await storage.set(newState); | ||
return result; | ||
}); | ||
} | ||
|
||
Future<T> _queueFuture<T>(Future<T> Function() computation) { | ||
final newFuture = _future.then((_) => computation()); | ||
_future = newFuture.whenComplete(() { | ||
if (_future == newFuture) _future = Future.value(); | ||
}); | ||
return newFuture; | ||
} | ||
} | ||
|
||
/// A standard token bucket. | ||
/// | ||
/// If you want to store the [TokenBucketState] in an [AsyncTokenBucketStorage], | ||
/// you have to use [AsyncTokenBucket] instead. | ||
class TokenBucket extends _BaseTokenBucket<TokenBucketStorage> { | ||
TokenBucket({ | ||
required super.size, | ||
required super.refillInterval, | ||
required super.refillAmount, | ||
TokenBucketStorage? storage, | ||
}) : super(storage: storage ?? MemoryTokenBucketStorage()); | ||
|
||
@override | ||
int get availableTokens { | ||
storage.set(_refillBucket(storage.get())); | ||
return storage.get().tokens; | ||
} | ||
|
||
@override | ||
bool consume([int cost = 1]) { | ||
if (cost < 1 || cost > size) { | ||
throw ArgumentError('cost must be <=$size and >=1'); | ||
} | ||
final state = _refillBucket(storage.get()); | ||
final (result, newState) = state.consume(cost); | ||
storage.set(newState); | ||
return result; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
part of 'token_bucket.dart'; | ||
|
||
class TokenBucketState { | ||
/// Currently available tokens. | ||
final int tokens; | ||
|
||
/// The last time the tokens were refilled. | ||
final DateTime lastRefillTime; | ||
|
||
const TokenBucketState({required this.tokens, required this.lastRefillTime}); | ||
|
||
/// Copies this state with [tokens] or [lastRefillTime] updated. | ||
TokenBucketState copyWith({int? tokens, DateTime? lastRefillTime}) { | ||
return TokenBucketState( | ||
tokens: tokens ?? this.tokens, | ||
lastRefillTime: lastRefillTime ?? this.lastRefillTime, | ||
); | ||
} | ||
|
||
(bool, TokenBucketState) consume(int cost) => | ||
tokens < cost ? (false, this) : (true, copyWith(tokens: tokens - cost)); | ||
|
||
factory TokenBucketState.fromJson(Map<String, dynamic> json) => | ||
TokenBucketState( | ||
tokens: json['tokens'] as int, | ||
lastRefillTime: | ||
DateTime.fromMicrosecondsSinceEpoch(json['lastRefillTime'] as int), | ||
); | ||
|
||
Map<String, dynamic> toJson() => { | ||
'tokens': tokens, | ||
'lastRefillTime': lastRefillTime.microsecondsSinceEpoch, | ||
}; | ||
|
||
@override | ||
bool operator ==(Object other) => | ||
identical(this, other) || | ||
other is TokenBucketState && | ||
runtimeType == other.runtimeType && | ||
tokens == other.tokens && | ||
lastRefillTime == other.lastRefillTime; | ||
|
||
@override | ||
int get hashCode => tokens.hashCode ^ lastRefillTime.hashCode; | ||
} |
Oops, something went wrong.