├── .gitignore
├── README.md
├── README.md.meta
├── Scripts.meta
└── Scripts
├── TimingWheel.cs
└── TimingWheel.cs.meta
/.gitignore:
--------------------------------------------------------------------------------
1 | # This .gitignore file should be placed at the root of your Unity project directory
2 | #
3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore
4 | #
5 | /[Ll]ibrary/
6 | /[Tt]emp/
7 | /[Oo]bj/
8 | /[Bb]uild/
9 | /[Bb]uilds/
10 | /[Ll]ogs/
11 | /[Mm]emoryCaptures/
12 |
13 | # Asset meta data should only be ignored when the corresponding asset is also ignored
14 | !/[Aa]ssets/**/*.meta
15 |
16 | # Uncomment this line if you wish to ignore the asset store tools plugin
17 | # /[Aa]ssets/AssetStoreTools*
18 |
19 | # Autogenerated Jetbrains Rider plugin
20 | [Aa]ssets/Plugins/Editor/JetBrains*
21 |
22 | # Visual Studio cache directory
23 | .vs/
24 |
25 | # Gradle cache directory
26 | .gradle/
27 |
28 | # Autogenerated VS/MD/Consulo solution and project files
29 | ExportedObj/
30 | .consulo/
31 | *.csproj
32 | *.unityproj
33 | *.sln
34 | *.suo
35 | *.tmp
36 | *.user
37 | *.userprefs
38 | *.pidb
39 | *.booproj
40 | *.svd
41 | *.pdb
42 | *.mdb
43 | *.opendb
44 | *.VC.db
45 |
46 | # Unity3D generated meta files
47 | *.pidb.meta
48 | *.pdb.meta
49 | *.mdb.meta
50 |
51 | # Unity3D generated file on crash reports
52 | sysinfo.txt
53 |
54 | # Builds
55 | *.apk
56 | *.unitypackage
57 |
58 | # Crashlytics generated file
59 | crashlytics-build.properties
60 |
61 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TimingWheel
2 | 分层时间轮
3 |
--------------------------------------------------------------------------------
/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1fe1ba38e0fe57a40b263cb60c3294ae
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Scripts.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 9b3f0659cbe5ee34897a546cb45bb431
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Scripts/TimingWheel.cs:
--------------------------------------------------------------------------------
1 | #region 注 释
2 |
3 | /***
4 | *
5 | * Title:
6 | *
7 | * Description:
8 | *
9 | * Date:
10 | * Version:
11 | * Writer: 半只龙虾人
12 | * Github: https://github.com/haloman9527
13 | * Blog: https://www.haloman.net/
14 | *
15 | */
16 |
17 | #endregion
18 |
19 | using System;
20 | using System.Collections.Generic;
21 | using Atom;
22 |
23 | namespace Atom
24 | {
25 | public partial class TimingWheel : IDisposable
26 | {
27 | static TimingWheel()
28 | {
29 | ObjectPoolManager.RegisterPool(new LinkListNodePool());
30 | }
31 |
32 | ///
33 | /// 插槽
34 | ///
35 | private Slot[] slots;
36 |
37 | ///
38 | /// 插槽数量
39 | ///
40 | private int slotCount;
41 |
42 | ///
43 | /// 时间刻度
44 | ///
45 | private long tickSpan;
46 |
47 | ///
48 | /// 时间轮转动一圈的时间
49 | ///
50 | private long wheelSpan;
51 |
52 | ///
53 | /// 当前时间轮的开始时间
54 | ///
55 | private long startTime;
56 |
57 | ///
58 | /// 当前指针时间
59 | ///
60 | private long currentTime;
61 |
62 | ///
63 | /// 当前时间戳
64 | ///
65 | private long currentTimeStamp;
66 |
67 | ///
68 | /// 当前指针下标
69 | ///
70 | private int currentIndicator;
71 |
72 | ///
73 | /// 正在执行事件
74 | ///
75 | private bool executingTask;
76 |
77 | ///
78 | /// 整个分层事件轮共享数据
79 | ///
80 | private TimingWheelSharedInfo sharedInfo;
81 |
82 | ///
83 | /// 外轮(刻度更大的时间轮)
84 | ///
85 | private TimingWheel outerWheel;
86 |
87 | ///
88 | /// 内轮(刻度更小的时间轮)
89 | ///
90 | private TimingWheel innerWheel;
91 |
92 | ///
93 | /// 当前指针时间
94 | ///
95 | public long CurrentTime
96 | {
97 | get { return currentTime; }
98 | }
99 |
100 | ///
101 | /// 当前时间戳
102 | ///
103 | public long CurrentTimeStamp
104 | {
105 | get { return currentTimeStamp; }
106 | }
107 |
108 | ///
109 | /// 插槽数量
110 | ///
111 | public int SlotCount
112 | {
113 | get { return slotCount; }
114 | }
115 |
116 | ///
117 | /// 时间刻度
118 | ///
119 | public long TickSpan
120 | {
121 | get { return tickSpan; }
122 | }
123 |
124 | ///
125 | /// 一周时间
126 | ///
127 | public long WheelSpan
128 | {
129 | get { return wheelSpan; }
130 | }
131 |
132 | ///
133 | ///
134 | ///
135 | /// 插槽数量
136 | /// 时间刻度
137 | /// 开始时间
138 | public TimingWheel(int slotCount, long tickSpan, long startTime)
139 | {
140 | this.tickSpan = tickSpan;
141 | this.slotCount = slotCount;
142 | this.wheelSpan = tickSpan * slotCount;
143 | this.startTime = startTime;
144 | this.currentTimeStamp = startTime;
145 | this.currentTime = startTime;
146 | this.slots = new Slot[slotCount];
147 | this.sharedInfo = new TimingWheelSharedInfo();
148 | for (int i = 0; i < slotCount; i++)
149 | {
150 | slots[i] = new Slot();
151 | }
152 | }
153 |
154 | private void Tick_Internal()
155 | {
156 | if (currentIndicator == 0)
157 | {
158 | outerWheel?.Tick_Internal();
159 | }
160 |
161 | currentTime += tickSpan;
162 | currentIndicator = (currentIndicator + 1) % slots.Length;
163 |
164 | if (currentIndicator == 0)
165 | {
166 | startTime += wheelSpan;
167 | }
168 |
169 | var currentSlot = slots[currentIndicator];
170 | var taskCount = currentSlot.tasks.Count;
171 | var taskNode = currentSlot.tasks.First;
172 |
173 | while (taskNode != null && taskCount-- > 0)
174 | {
175 | var task = taskNode.Value;
176 | var taskInfo = sharedInfo.taskInfos[task];
177 |
178 | if (currentTime >= taskInfo.nextTime)
179 | {
180 | if (innerWheel != null)
181 | {
182 | RemoveTask_Internal(task);
183 | innerWheel.AddTask_Internal(task, taskInfo.nextTime);
184 | }
185 | else
186 | {
187 | RemoveTask_Internal(task);
188 |
189 | try
190 | {
191 | task.Invoke(this);
192 | }
193 | catch (Exception e)
194 | {
195 | throw e;
196 | }
197 |
198 | if (task.LoopTime < 0 || --task.LoopTime > 0)
199 | {
200 | taskInfo.nextTime += task.LoopInterval;
201 | AddTask_Internal(task, taskInfo.nextTime);
202 | }
203 | }
204 | }
205 |
206 | taskNode = taskNode.Next;
207 | }
208 | }
209 |
210 | ///
211 | ///
212 | ///
213 | ///
214 | /// must be bigger than current time
215 | private void AddTask_Internal(ITimeTask task, long nextTime)
216 | {
217 | var delta = nextTime - currentTime;
218 | if (delta <= 0)
219 | {
220 | try
221 | {
222 | task.Invoke(this);
223 | }
224 | catch (Exception e)
225 | {
226 | throw e;
227 | }
228 |
229 | if (task.LoopTime < 0 || --task.LoopTime > 0)
230 | {
231 | nextTime += task.LoopInterval;
232 | AddTask_Internal(task, nextTime);
233 | }
234 | }
235 | else if (delta <= wheelSpan)
236 | {
237 | if (delta < tickSpan && innerWheel != null)
238 | {
239 | innerWheel.AddTask_Internal(task, nextTime);
240 | }
241 | else
242 | {
243 | var offset = nextTime - startTime;
244 | var index = ((offset / tickSpan) + (offset % tickSpan > 0 ? 1 : 0)) % slotCount;
245 | var slot = slots[index];
246 | var taskNode = (LinkedListNode)ObjectPoolManager.Spawn(typeof(LinkedListNode));
247 | taskNode.Value = task;
248 | slot.tasks.AddLast(taskNode);
249 |
250 | var taskInfo = new TimeTaskInfo();
251 | taskInfo.nextTime = nextTime;
252 | taskInfo.wheel = this;
253 | taskInfo.slot = slot;
254 | taskInfo.linkListNode = taskNode;
255 | sharedInfo.taskInfos[task] = taskInfo;
256 | }
257 | }
258 | else if (delta > wheelSpan)
259 | {
260 | outerWheel?.AddTask_Internal(task, nextTime);
261 | }
262 | }
263 |
264 | private void RemoveTask_Internal(ITimeTask task)
265 | {
266 | var taskInfo = sharedInfo.taskInfos[task];
267 | sharedInfo.taskInfos.Remove(task);
268 | taskInfo.slot.tasks.Remove(taskInfo.linkListNode);
269 | ObjectPoolManager.Recycle(taskInfo.linkListNode);
270 | }
271 |
272 | ///
273 | ///
274 | ///
275 | /// 父轮插槽数量
276 | public void BuildParent(params int[] parentSlotCounts)
277 | {
278 | if (parentSlotCounts == null)
279 | return;
280 |
281 | var wheel = this;
282 | for (int i = 0; i < parentSlotCounts.Length; i++)
283 | {
284 | if (wheel.outerWheel != null)
285 | {
286 | wheel = outerWheel;
287 | }
288 | else
289 | {
290 | wheel.outerWheel = new TimingWheel(parentSlotCounts[i], wheelSpan, startTime);
291 | wheel.outerWheel.innerWheel = this;
292 | wheel.outerWheel.sharedInfo = this.sharedInfo;
293 | wheel = wheel.outerWheel;
294 | }
295 | }
296 | }
297 |
298 | ///
299 | /// 推进时间轮,前进1刻度()
300 | ///
301 | public void Tick()
302 | {
303 | currentTimeStamp += tickSpan;
304 | var delta = currentTimeStamp - currentTime;
305 | while (delta > tickSpan)
306 | {
307 | Tick_Internal();
308 | delta -= tickSpan;
309 | }
310 | }
311 |
312 | /// 推进时间轮
313 | /// 拨动指针,前进一段时间
314 | public void Tick(long tick)
315 | {
316 | currentTimeStamp += tick;
317 | var delta = currentTimeStamp - currentTime;
318 | while (delta > tickSpan)
319 | {
320 | Tick_Internal();
321 | delta -= tickSpan;
322 | }
323 | }
324 |
325 | public bool ContainsTask(ITimeTask task)
326 | {
327 | return sharedInfo.taskInfos.ContainsKey(task);
328 | }
329 |
330 | ///
331 | /// 添加时间任务
332 | ///
333 | ///
334 | ///
335 | public void AddTask(ITimeTask task, long startDelay = 0)
336 | {
337 | if (sharedInfo.taskInfos.ContainsKey(task))
338 | {
339 | return;
340 | }
341 |
342 | startDelay = Math.Max(startDelay, 0);
343 | var nextInvokeTime = currentTime + startDelay;
344 |
345 | AddTask_Internal(task, nextInvokeTime);
346 | }
347 |
348 | public void RemoveTask(ITimeTask task)
349 | {
350 | if (!sharedInfo.taskInfos.TryGetValue(task, out var taskInfo))
351 | {
352 | return;
353 | }
354 |
355 | RemoveTask_Internal(task);
356 | }
357 |
358 | public void ClearTasks()
359 | {
360 | foreach (var pair in sharedInfo.taskInfos)
361 | {
362 | var taskInfo = pair.Value;
363 | taskInfo.slot.tasks.Remove(taskInfo.linkListNode);
364 | ObjectPoolManager.Recycle(taskInfo.linkListNode);
365 | }
366 |
367 | sharedInfo.taskInfos.Clear();
368 | }
369 |
370 | public void Dispose()
371 | {
372 | ClearTasks();
373 | }
374 | }
375 |
376 |
377 | public partial class TimingWheel
378 | {
379 | public interface ITimeTask
380 | {
381 | ///
382 | /// 循环次数
383 | /// -1: 无限
384 | /// 0|1: 不循环
385 | ///
386 | int LoopTime { get; set; }
387 |
388 | ///
389 | /// 循环间隔
390 | ///
391 | long LoopInterval { get; set; }
392 |
393 | void Invoke(TimingWheel timingWheel);
394 | }
395 |
396 | public class CustomTask : ITimeTask
397 | {
398 | public int LoopTime { get; set; }
399 |
400 | public long LoopInterval { get; set; }
401 |
402 | public Action task;
403 |
404 | public void Invoke(TimingWheel timingWheel)
405 | {
406 | task?.Invoke();
407 | }
408 | }
409 |
410 | public class DayTask : ITimeTask
411 | {
412 | public int LoopTime { get; set; }
413 |
414 | public long LoopInterval
415 | {
416 | get => 86400000L;
417 | set { }
418 | }
419 |
420 | public Action task;
421 |
422 | public void Invoke(TimingWheel timingWheel)
423 | {
424 | task?.Invoke();
425 | }
426 | }
427 |
428 | public class WeekTask : ITimeTask
429 | {
430 | public int LoopTime { get; set; }
431 |
432 | public long LoopInterval
433 | {
434 | get => 604800000L;
435 | set { }
436 | }
437 |
438 | public Action task;
439 |
440 | public void Invoke(TimingWheel timingWheel)
441 | {
442 | task?.Invoke();
443 | }
444 | }
445 |
446 | public class MonthTask : ITimeTask
447 | {
448 | public int LoopTime { get; set; }
449 |
450 | public long LoopInterval
451 | {
452 | get => new DateTimeOffset(0, 1, 0, 0, 0, 0, TimeSpan.Zero).Millisecond;
453 | set { }
454 | }
455 |
456 | public Action task;
457 |
458 | public void Invoke(TimingWheel timingWheel)
459 | {
460 | task?.Invoke();
461 | }
462 | }
463 |
464 | public class YearTask : ITimeTask
465 | {
466 | public int LoopTime { get; set; }
467 |
468 | public long LoopInterval
469 | {
470 | get => new DateTimeOffset(1, 0, 0, 0, 0, 0, TimeSpan.Zero).Millisecond;
471 | set { }
472 | }
473 |
474 | public Action task;
475 |
476 | public void Invoke(TimingWheel timingWheel)
477 | {
478 | task?.Invoke();
479 | }
480 | }
481 |
482 | public struct TimeTaskInfo
483 | {
484 | ///
485 | /// 任务下次执行时间
486 | ///
487 | public long nextTime;
488 |
489 | ///
490 | /// 任务所在时间层
491 | ///
492 | public TimingWheel wheel;
493 |
494 | ///
495 | /// 任务所在刻度
496 | ///
497 | public Slot slot;
498 |
499 | ///
500 | /// 任务所在的链表的位置
501 | ///
502 | public LinkedListNode linkListNode;
503 | }
504 |
505 | ///
506 | /// 整个分层事件轮中共享的数据
507 | ///
508 | internal class TimingWheelSharedInfo
509 | {
510 | ///
511 | /// 任务数据
512 | ///
513 | public Dictionary taskInfos = new Dictionary();
514 | }
515 |
516 | public class Slot
517 | {
518 | public LinkedList tasks = new LinkedList();
519 | }
520 |
521 | public class LinkListNodePool : ObjectPoolBase>
522 | {
523 | protected override LinkedListNode Create()
524 | {
525 | return new LinkedListNode(default);
526 | }
527 | }
528 | }
529 | }
--------------------------------------------------------------------------------
/Scripts/TimingWheel.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7c9a3470a15996b4aad63ee2e501feb7
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------