Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
maeddin committed Jul 27, 2024
0 parents commit 21a4165
Show file tree
Hide file tree
Showing 14 changed files with 620 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
29 changes: 29 additions & 0 deletions .gitignore
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/
10 changes: 10 additions & 0 deletions .metadata
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 0.1.0

- initial release
29 changes: 29 additions & 0 deletions LICENSE
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.
39 changes: 39 additions & 0 deletions README.md
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.
4 changes: 4 additions & 0 deletions analysis_options.yaml
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
81 changes: 81 additions & 0 deletions coverage/lcov.info
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
127 changes: 127 additions & 0 deletions lib/src/token_bucket.dart
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;
}
}
45 changes: 45 additions & 0 deletions lib/src/token_bucket_state.dart
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;
}
Loading

0 comments on commit 21a4165

Please sign in to comment.