Created
November 14, 2023 14:22
-
-
Save ufcpp/a8ab75458aa35bdb1ed600153da86962 to your computer and use it in GitHub Desktop.
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
namespace TimeProviderExtension; | |
public class PausableTimeProvider(TimeProvider innerProvider) : TimeProvider | |
{ | |
public PausableTimeProvider() : this(System) { } | |
private long _pausedTimestamp = innerProvider.GetTimestamp(); | |
private long _offsetTimestamp = 0; | |
private DateTimeOffset _pausedUtcNow = innerProvider.GetUtcNow(); | |
private TimeSpan _offsetUtcNow = default; | |
private bool _isPaused = false; | |
public bool IsPaused | |
{ | |
get => _isPaused; | |
set => ChangePaused(value); | |
} | |
private event Action<bool>? PauseChanged; | |
private void ChangePaused(bool value) | |
{ | |
if (_isPaused == value) return; | |
if (value) | |
{ | |
_pausedTimestamp = innerProvider.GetTimestamp(); | |
_pausedUtcNow = innerProvider.GetUtcNow(); | |
} | |
else | |
{ | |
var timestamp = innerProvider.GetTimestamp(); | |
_offsetTimestamp += timestamp - _pausedTimestamp; | |
var now = innerProvider.GetUtcNow(); | |
_offsetUtcNow += now - _pausedUtcNow; | |
} | |
_isPaused = value; | |
PauseChanged?.Invoke(value); | |
} | |
public override long GetTimestamp() | |
{ | |
if(IsPaused) return _pausedTimestamp; | |
var timestamp = innerProvider.GetTimestamp(); | |
return timestamp - _offsetTimestamp; | |
} | |
public override DateTimeOffset GetUtcNow() | |
{ | |
var now = innerProvider.GetUtcNow(); | |
return now - _offsetUtcNow; | |
} | |
private class XTimer : ITimer | |
{ | |
private readonly PausableTimeProvider _parent; | |
private readonly ITimer _innerTimer; | |
private TimeSpan _period; | |
public XTimer(PausableTimeProvider parent, ITimer innerTimer, TimeSpan period) | |
{ | |
_parent = parent; | |
_innerTimer = innerTimer; | |
_period = period; | |
parent.PauseChanged += OnPauseChanged; | |
} | |
private void OnPauseChanged(bool isPaused) | |
{ | |
if (isPaused) | |
{ | |
_innerTimer.Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); | |
} | |
else | |
{ | |
_innerTimer.Change(default, _period); // now - _pausedUtcNow 受け取って due の調整する方がいいかも。 | |
} | |
} | |
public bool Change(TimeSpan dueTime, TimeSpan period) | |
{ | |
if (_parent.IsPaused) | |
{ | |
_period = period; | |
return true; | |
} | |
else | |
{ | |
return _innerTimer.Change(dueTime, period); | |
} | |
} | |
public void Dispose() | |
{ | |
_parent.PauseChanged -= OnPauseChanged; | |
_innerTimer.Dispose(); | |
} | |
public async ValueTask DisposeAsync() | |
{ | |
_parent.PauseChanged -= OnPauseChanged; | |
await _innerTimer.DisposeAsync(); | |
} | |
} | |
public override ITimer CreateTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period) | |
{ | |
return new XTimer(this, innerProvider.CreateTimer(callback, state, dueTime, period), period); | |
} | |
public override TimeZoneInfo LocalTimeZone => innerProvider.LocalTimeZone; | |
public override long TimestampFrequency => innerProvider.TimestampFrequency; | |
} |
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
using TimeProviderExtension; | |
var scaled = new ScaledTimeProvider() { Scale = 1 }; | |
var pausable = new PausableTimeProvider(scaled); | |
static async Task LoopAsync(TimeProvider tp, CancellationToken ct) | |
{ | |
var t = new PeriodicTimer(TimeSpan.FromMilliseconds(500), tp); | |
while (!ct.IsCancellationRequested) | |
{ | |
await t.WaitForNextTickAsync(ct); | |
Console.WriteLine(tp.GetLocalNow()); | |
} | |
} | |
var cts = new CancellationTokenSource(); | |
var task = LoopAsync(pausable, cts.Token); | |
Console.Write(""" | |
p: pause | |
r: resume | |
1-9: change scale | |
q: quit | |
"""); | |
while (true) | |
{ | |
var c = Console.ReadKey().KeyChar; | |
Console.WriteLine(); | |
switch (c) | |
{ | |
case 'p': | |
Console.WriteLine("paused"); | |
pausable.IsPaused = true; | |
break; | |
case 'r': | |
Console.WriteLine("resumed"); | |
pausable.IsPaused = false; | |
break; | |
case >= '1' and <= '9': | |
var x = c - '0'; | |
Console.WriteLine($"scale changed to {x}"); | |
scaled.Scale = x; | |
break; | |
case 'q': | |
goto END_LOOP; | |
} | |
} | |
END_LOOP: | |
cts.Cancel(); | |
await task.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); |
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
namespace TimeProviderExtension; | |
public class ScaledTimeProvider(TimeProvider innerProvider) : TimeProvider | |
{ | |
public ScaledTimeProvider() : this(System) { } | |
private long _startTimestamp = innerProvider.GetTimestamp(); | |
private long _offsetTimestamp = 0; | |
private DateTimeOffset _startUtcNow = innerProvider.GetUtcNow(); | |
private TimeSpan _offsetUtcNow = default; | |
private double _scale = 1; | |
public double Scale | |
{ | |
get => _scale; | |
set => ChangeScale(value); | |
} | |
private event Action<double>? ScaleChanged; | |
private void ChangeScale(double value) | |
{ | |
if (_scale == value) return; | |
var s1 = GetTimestamp(); | |
var s2 = _startTimestamp = innerProvider.GetTimestamp(); | |
_offsetTimestamp = s1 - s2; | |
var n1 = GetUtcNow(); | |
var n2 = _startUtcNow = innerProvider.GetUtcNow(); | |
_offsetUtcNow = n1 - n2; | |
_scale = value; | |
ScaleChanged?.Invoke(value); | |
} | |
public override long GetTimestamp() | |
{ | |
var timestamp = innerProvider.GetTimestamp(); | |
return (long)(_startTimestamp + Scale * (timestamp - _startTimestamp)) + _offsetTimestamp; | |
} | |
public override DateTimeOffset GetUtcNow() | |
{ | |
var now = innerProvider.GetUtcNow(); | |
return _startUtcNow + Scale * (now - _startUtcNow) + _offsetUtcNow; | |
} | |
private class XTimer : ITimer | |
{ | |
private readonly ScaledTimeProvider _parent; | |
private readonly ITimer _innerTimer; | |
private TimeSpan _period; | |
public XTimer(ScaledTimeProvider parent, ITimer innerTimer) | |
{ | |
_parent = parent; | |
_innerTimer = innerTimer; | |
parent.ScaleChanged += OnScaleChanged; | |
} | |
private void OnScaleChanged(double scale) | |
{ | |
var inv = 1 / scale; | |
_innerTimer.Change(inv * _period, inv * _period); | |
} | |
public bool Change(TimeSpan dueTime, TimeSpan period) | |
{ | |
_period = period; | |
var inv = 1 / _parent.Scale; | |
return _innerTimer.Change(inv * dueTime, inv * period); | |
} | |
public void Dispose() | |
{ | |
_parent.ScaleChanged -= OnScaleChanged; | |
_innerTimer.Dispose(); | |
} | |
public async ValueTask DisposeAsync() | |
{ | |
_parent.ScaleChanged -= OnScaleChanged; | |
await _innerTimer.DisposeAsync(); | |
} | |
} | |
public override ITimer CreateTimer(TimerCallback callback, object? state, TimeSpan dueTime, TimeSpan period) | |
{ | |
var t = new XTimer(this, innerProvider.CreateTimer(callback, state, default, default)); | |
t.Change(dueTime, period); | |
return t; | |
} | |
public override TimeZoneInfo LocalTimeZone => innerProvider.LocalTimeZone; | |
public override long TimestampFrequency => innerProvider.TimestampFrequency; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment