Skip to content

Commit

Permalink
v3.0.6
Browse files Browse the repository at this point in the history
- Added `UniqueCaller` and `parseIntList`.
- Null safety migration adjustments.
  • Loading branch information
gmpassos committed Mar 22, 2021
1 parent b3da818 commit 21da2a3
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 8 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 3.0.6

- Added `UniqueCaller` and `parseIntList`.
- Null safety migration adjustments.

## 3.0.5

- Null safety migration adjustments.
Expand Down
147 changes: 143 additions & 4 deletions lib/src/events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'dart:async';

import 'package:swiss_knife/src/collections.dart';

import 'math.dart';

class _ListenSignature {
final Object _identifier;

Expand Down Expand Up @@ -322,7 +324,7 @@ class EventStream<T> implements Stream<T> {
ListenerWrapper<T>? listenOneShot(void Function(T event) onData,
{Function? onError,
void Function()? onDone,
required bool cancelOnError,
bool cancelOnError = false,
Object? singletonIdentifier,
bool? singletonIdentifyByInstance = true}) {
var listenerWrapper = ListenerWrapper<T>(this, onData,
Expand Down Expand Up @@ -421,20 +423,21 @@ class EventStream<T> implements Stream<T> {
class EventStreamDelegator<T> implements EventStream<T> {
EventStream<T>? _eventStream;

final EventStream<T> Function()? _eventStreamProvider;
final EventStream<T>? Function()? _eventStreamProvider;

EventStreamDelegator(EventStream<T> eventStream)
: _eventStream = eventStream,
_eventStreamProvider = null;

EventStreamDelegator.provider(EventStream<T> Function() eventStreamProvider)
EventStreamDelegator.provider(EventStream<T>? Function() eventStreamProvider)
: _eventStream = null,
_eventStreamProvider = eventStreamProvider;

/// Returns the main [EventStream].
EventStream<T>? get eventStream {
if (_eventStream == null) {
_eventStream = _eventStreamProvider!();
_eventStream =
_eventStreamProvider != null ? _eventStreamProvider!() : null;
if (_eventStream != null) {
flush();
}
Expand Down Expand Up @@ -1274,3 +1277,139 @@ class ListenerWrapper<T> {
_subscription = null;
}
}

/// Ensures that a call is executed only 1 per time.
class UniqueCaller<R> {
final FutureOr<R?> Function() function;

final void Function(UniqueCaller<R> caller)? onDuplicatedCall;

final _IdentifierWrapper _identifier;

static String stackTraceIdentifier([int stackOffset = 0]) {
var stackTracer = StackTrace.current;
var stackTraceStr = stackTracer.toString();

var lines = stackTraceStr.split(RegExp(r'[\r\n]+', multiLine: false));

var start = Math.min(1 + stackOffset, lines.length - 1);
var end = Math.min(3, lines.length);

lines = lines.sublist(start, Math.max(start, end));

var s = lines.join('\n');
return s;
}

UniqueCaller(this.function,
{Object? identifier,
StackTrace? stackTraceIdentifier,
this.onDuplicatedCall})
: _identifier = _IdentifierWrapper(identifier ??
stackTraceIdentifier?.toString() ??
UniqueCaller.stackTraceIdentifier());

_IdentifierWrapper get identifier => _identifier;

static final Set<_IdentifierWrapper> _calling = {};

static final Map<_IdentifierWrapper, Future> _callsFuture = {};

static Future<R?> getCallFuture<R>(Object identifier) {
var identifierWrapper = _IdentifierWrapper(identifier);
return _callsFuture[identifierWrapper] as Future<R?>;
}

static List<Future> get calling {
return _callsFuture.values.toList();
}

Future<R?> callAsync() async {
if (_calling.contains(_identifier)) {
if (onDuplicatedCall != null) onDuplicatedCall!(this);
return Future<R?>.value(null);
}

_calling.add(_identifier);
try {
var ret = function();
if (ret is Future) {
_callsFuture[_identifier] = ret as Future;
return await ret;
} else {
return ret;
}
} finally {
_finalizeCall();
}
}

R? call() {
if (_calling.contains(_identifier)) {
if (onDuplicatedCall != null) onDuplicatedCall!(this);
return null;
}

_calling.add(_identifier);
try {
var ret = function();
if (ret is Future) {
var future = ret as Future;
_callsFuture[_identifier] = future;
future.then((value) {
_finalizeCall();
return value;
});
return null;
} else {
_finalizeCall();
return ret;
}
} catch (e) {
_finalizeCall();
rethrow;
}
}

void _finalizeCall() {
// ignore: unawaited_futures
_callsFuture.remove(_identifier);
_calling.remove(_identifier);
}
}

class _IdentifierWrapper {
final Object identifier;

_IdentifierWrapper(this.identifier);

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is _IdentifierWrapper &&
runtimeType == other.runtimeType &&
_identical(identifier, other.identifier);

bool _identical(Object? o1, Object? o2) {
if (o1 == null && o2 == null) return true;
if (identical(o1, o2)) return true;
if (o1 != null && o2 == null) return false;
if (o1 == null && o2 != null) return false;

if (o1.runtimeType != o2.runtimeType) return false;

if (o1 is Future || o1 is Function) {
return false;
} else {
return o1 == o2;
}
}

@override
int get hashCode => identifier.hashCode;

@override
String toString() {
return '_IdentifierWrapper{hashCode: $hashCode ; identifier: $identifier}';
}
}
17 changes: 17 additions & 0 deletions lib/src/math.dart
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,23 @@ int? parseInt(Object? v, [int? def]) {
return n as int? ?? def;
}

/// Parses [l] as [List<int>].
///
/// [def] The default value if [l] is invalid.
List<int>? parseIntList(Object? l, [List<int>? def]) {
if (l == null) return def;

if (l is List) {
var l2 = l.map((e) => parseInt(e)).whereType<int>().toList();
return l2.isNotEmpty ? l2 : (def ?? l2);
} else if (l is String) {
var l2 = parseIntsFromInlineList(l, _REGEXP_SPLIT_COMMA);
return l2 != null && l2.isNotEmpty ? l2 : (def ?? l2);
} else {
return def;
}
}

/// Parses [v] to [double].
///
/// [def] The default value if [v] is invalid.
Expand Down
6 changes: 3 additions & 3 deletions lib/src/resource.dart
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ class ContextualResource<T, C extends Comparable<C>>
context = _resolveContext<T, C>(resource, context);

static List<ContextualResource<T, C>> toList<T, C extends Comparable<C>>(
Iterable<T> resources, C Function(T resource) context) =>
Iterable<T> resources, C? Function(T resource) context) =>
resources.map((r) => ContextualResource<T, C>(r, context)).toList();

@override
Expand Down Expand Up @@ -482,14 +482,14 @@ class ContextualResourceResolver<T, C extends Comparable<C>> {
var low = 0;
var high = options.length - 1;

var comparator = contextComparator as int Function(C?, C)?;
var comparator = contextComparator;

while (low <= high) {
var mid = (low + high) ~/ 2;
var midVal = options[mid];

var cmp = comparator != null
? comparator(midVal.context, context)
? comparator(midVal.context!, context)
: midVal.compareContext(context);

if (cmp < 0) {
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: swiss_knife
description: Dart Useful Tools - collections, math, date, uri, json, events, resources, regexp, etc...
version: 3.0.5
version: 3.0.6
homepage: https://github.com/gmpassos/swiss_knife

environment:
Expand Down
46 changes: 46 additions & 0 deletions test/swiss_knife_events_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ Future<void> _sleep(int delayMs) async {
await Future.delayed(Duration(milliseconds: delayMs), () {});
}

void _asyncCall(List<String> calls) async {
var callID = DateTime.now().microsecondsSinceEpoch;
calls.add('$callID> a');
await Future.delayed(Duration(seconds: 2));
calls.add('$callID> b');
}

void _doUniqueCall(List<String> calls) async {
var uniqueCaller = UniqueCaller(() => _asyncCall(calls));
uniqueCaller.call();
}

void main() {
group('Events', () {
setUp(() {});
Expand Down Expand Up @@ -68,5 +80,39 @@ void main() {
expect(interactionCompleter.isTriggerScheduled, isFalse);
expect(counter.value, equals(1));
});

test('NON UniqueCaller', () async {
var calls = <String>[];

expect(UniqueCaller.calling, isEmpty);

_asyncCall(calls);
_asyncCall(calls);

expect(UniqueCaller.calling, isEmpty);

print('NON UniqueCaller> $calls');

expect(calls.where((e) => e.contains(' a')).length, equals(2));
});

test('UniqueCaller', () async {
var calls = <String>[];

expect(UniqueCaller.calling, isEmpty);

_doUniqueCall(calls);
_doUniqueCall(calls);

expect(UniqueCaller.calling.length, equals(1));

await Future.wait(UniqueCaller.calling);

expect(UniqueCaller.calling.length, equals(0));

print('UniqueCaller> $calls');

expect(calls.where((e) => e.contains(' a')).length, equals(1));
});
});
}

0 comments on commit 21da2a3

Please sign in to comment.