Created
October 5, 2021 09:13
-
-
Save lrhn/9568522557db0b62d987185e0ff01ae7 to your computer and use it in GitHub Desktop.
Dart expiring cache
This file contains hidden or 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
// Copyright 2021 Google LLC. | |
// SPDX-License-Identifier: BSD-3-Clause | |
/// Cache for a value which invalidates itself after a predetermined duration. | |
/// | |
/// The cache will store a [value], but after a specified keep-alive duration | |
/// has passed, the cache will invalidate itself and no longer provide access | |
/// to the cached value. | |
/// | |
/// Reading [value] will return either the cached value, or `null` if the | |
/// cache is no longer valid. | |
/// Writing to [value] will set a new cached value and restart the keep-alive | |
/// duration. You can write to the cache before it invalidates, and you | |
/// can invalidate it early by calling [clear]. | |
/// If no new writes happen to [value] before the keep-alive duration has | |
/// expired, the [value] will return `null` on the further reads. | |
/// | |
/// Example: | |
/// ```dart | |
/// final _nowCache = ExpiringCache<DateTime>(const Duration(minutes: 5)); | |
/// DateTime get _now => _nowCache.value ??= DateTime.now()); | |
/// // ... | |
/// doSomething(_now); | |
/// ``` | |
/// This cache will automatically clear, and its [value] become `null`, | |
/// five minutes after a value was last set. The `_now` helper getter here | |
/// will reuse the cache value when it's there, and write a new value | |
/// when the cached value is no longer available. | |
/// | |
/// **Notice:** The cache *lazily* invalidates itself, so the cached value won't | |
/// be garbage collectable until you either call [clear] or read the [value] | |
/// and sees it being `null`. | |
class ExpiringCache<T> { | |
/// Duration to keep cached values valid. | |
final Duration _keepAlive; | |
/// Clock which starts ticking when a value is stored. | |
/// | |
/// The cache is valid if this timer is running and below [_keepAlive]. | |
/// If it runs past [_keepAlive] then the next check of [value] | |
/// or [hasValue] will see the cache as expired. | |
/// If [cancel]ed before that, we stop the stopwatch instead, since we have | |
/// now way to advance it past [_keepAlive]. The stopwatch is also stopped | |
/// until the first value is cached. | |
final Stopwatch _timer = Stopwatch(); | |
T? _value; | |
/// Creates a cache which keeps its value alive for a duration of [keepAlive]. | |
ExpiringCache(Duration keepAlive) : _keepAlive = keepAlive; | |
/// Checks whether the cache currently has a value. | |
/// | |
/// If the cache has a value then the timer is active and its elapsed time is | |
/// below [_keepAlive]. | |
bool get _hasValue => _timer.isRunning && _timer.elapsedMicroseconds <= _keepAlive.inMicroseconds; | |
/// The value currently stored in the cache, or `null` if the cache has no value. | |
/// | |
/// Retains the last value stored until the keep-alive duration has passed, | |
/// or until someone explicitly calls [clear]. | |
/// When the cache has expired or been cleared, the value is [null] instead. | |
/// | |
/// If the value type of the cache is nullable, you can use [hasValue] to see | |
/// whether a value of [null] is a valid cached value or it represents | |
/// the absence of a value. | |
T? get value => _hasValue ? _value : (_value = null); | |
void set value(T value) { | |
_value = value; | |
_timer..reset()..start(); | |
} | |
/// Clears and invalidates the cache. | |
/// | |
/// Clears the cached value, if any. | |
/// If the cache is currently valid, it is invalidated. That is, | |
/// after clearing, [hasValue] is `false` and [value] is `null`. | |
void clear() { | |
_value = null; | |
_timer.stop(); // Stop timer to mark value as invalidated. | |
} | |
/// Whether the cache currently has a value. | |
/// | |
/// If the value type is nullable, it might be valuable to distinguish a | |
/// cached value of `null` from a cleared cache providing `null` instead | |
/// of a value. | |
/// For non-nullable value types, it's usually more convenient to just | |
/// check whether [value] is `null`, since that can be combined with | |
/// using the value when it's not `null`. | |
bool get hasValue => _hasValue; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment