OSDN Git Service

発言詳細欄の更新間隔をThrottlingTimerで制御する, TweenMain._colorize を廃止
[opentween/open-tween.git] / OpenTween / ThrottlingTimer.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2018 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
4 //
5 // This file is part of OpenTween.
6 //
7 // This program is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General public License as published by the Free
9 // Software Foundation; either version 3 of the License, or (at your option)
10 // any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General public License
15 // for more details.
16 //
17 // You should have received a copy of the GNU General public License along
18 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 // Boston, MA 02110-1301, USA.
21
22 using System;
23 using System.Threading;
24 using System.Threading.Tasks;
25
26 namespace OpenTween
27 {
28     /// <summary>
29     /// コールバック先の関数を <see cref="Interval"/> 未満の頻度で呼ばないよう制御するタイマー
30     /// </summary>
31     public class ThrottlingTimer : IDisposable
32     {
33         private const int TIMER_DISABLED = 0;
34         private const int TIMER_ENABLED = 1;
35
36         private readonly Timer throttlingTimer;
37         private readonly Func<Task> timerCallback;
38
39         private DateTimeUtc lastCalled = DateTimeUtc.MinValue;
40         private DateTimeUtc lastInvoked = DateTimeUtc.MinValue;
41         private int refreshTimerEnabled = 0;
42
43         public TimeSpan Interval { get; }
44         public TimeSpan MaxWait { get; }
45
46         public ThrottlingTimer(Func<Task> timerCallback, TimeSpan interval, TimeSpan maxWait)
47         {
48             this.timerCallback = timerCallback;
49             this.Interval = interval;
50             this.MaxWait = maxWait;
51             this.throttlingTimer = new Timer(this.Execute);
52         }
53
54         public void Call()
55         {
56             this.lastCalled = DateTimeUtc.Now;
57
58             if (this.refreshTimerEnabled == TIMER_DISABLED)
59             {
60                 lock (this.throttlingTimer)
61                 {
62                     if (Interlocked.CompareExchange(ref this.refreshTimerEnabled, TIMER_ENABLED, TIMER_DISABLED) == TIMER_DISABLED)
63                         this.throttlingTimer.Change(dueTime: 0, period: Timeout.Infinite);
64                 }
65             }
66         }
67
68         private async void Execute(object _)
69         {
70             var timerExpired = this.lastCalled < this.lastInvoked;
71             if (timerExpired)
72             {
73                 // 前回実行時より後に lastInvoked が更新されていなければタイマーを止める
74                 Interlocked.CompareExchange(ref this.refreshTimerEnabled, TIMER_DISABLED, TIMER_ENABLED);
75             }
76             else
77             {
78                 var now = DateTimeUtc.Now;
79                 if ((now - this.lastCalled) >= this.Interval || (now - this.lastInvoked) >= this.MaxWait)
80                 {
81                     this.lastInvoked = now;
82                     await this.timerCallback().ConfigureAwait(false);
83                 }
84
85                 // dueTime は Execute が呼ばれる度に再設定する (period は使用しない)
86                 // これにより timerCallback の実行に Interval 以上の時間が掛かっても重複して実行されることはなくなる
87                 lock (this.throttlingTimer)
88                     this.throttlingTimer.Change(dueTime: (int)this.Interval.TotalMilliseconds, period: Timeout.Infinite);
89             }
90         }
91
92         public void Dispose()
93             => this.throttlingTimer.Dispose();
94
95         // lodash.js の _.throttle, _.debounce 的な処理をしたかったメソッド群
96         public static ThrottlingTimer Throttle(Func<Task> callback, TimeSpan wait)
97             => new ThrottlingTimer(callback, wait, maxWait: wait);
98
99         public static ThrottlingTimer Debounce(Func<Task> callback, TimeSpan wait)
100             => new ThrottlingTimer(callback, wait, maxWait: TimeSpan.MaxValue);
101     }
102 }