forked from Unity-Technologies/UnityCsReference
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathScheduler.cs
More file actions
383 lines (317 loc) · 13.2 KB
/
Scheduler.cs
File metadata and controls
383 lines (317 loc) · 13.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
// Unity C# reference source
// Copyright (c) Unity Technologies. For terms of use, see
// https://unity3d.com/legal/licenses/Unity_Reference_Only_License
using System;
using System.Collections.Generic;
namespace UnityEngine.UIElements
{
// All values are milliseconds
// notes on precision:
// the event will be fired no sooner than delayMs, and repeat no sooner than intervalMs
// this means that depending on system and application state the events could be fired less often or intervals skipped entirely.
// it is the registrar's responsibility to read the TimerState to determine the actual event timing
// and make sure things like animation are smooth and time based.
// a delayMs of 0 and intervalMs of 0 will be interpreted as "as often as possible" this should be used sparingly and the work done should be very small
public struct TimerState : IEquatable<TimerState>
{
public long start { get; set; }
public long now { get; set; }
public long deltaTime
{
get
{
return now - start;
}
}
public override bool Equals(object obj)
{
return obj is TimerState && Equals((TimerState)obj);
}
public bool Equals(TimerState other)
{
return start == other.start &&
now == other.now &&
deltaTime == other.deltaTime;
}
public override int GetHashCode()
{
var hashCode = 540054806;
hashCode = hashCode * -1521134295 + start.GetHashCode();
hashCode = hashCode * -1521134295 + now.GetHashCode();
hashCode = hashCode * -1521134295 + deltaTime.GetHashCode();
return hashCode;
}
public static bool operator==(TimerState state1, TimerState state2)
{
return state1.Equals(state2);
}
public static bool operator!=(TimerState state1, TimerState state2)
{
return !(state1 == state2);
}
}
// the scheduler public interface
internal interface IScheduler
{
ScheduledItem ScheduleOnce(Action<TimerState> timerUpdateEvent, long delayMs);
ScheduledItem ScheduleUntil(Action<TimerState> timerUpdateEvent, long delayMs, long intervalMs, Func<bool> stopCondition = null);
ScheduledItem ScheduleForDuration(Action<TimerState> timerUpdateEvent, long delayMs, long intervalMs, long durationMs);
// removes the event.
// an event that is never stopped will not be stopped until the panel is cleaned-up.
void Unschedule(ScheduledItem item);
void Schedule(ScheduledItem item);
void UpdateScheduledEvents();
}
internal abstract class ScheduledItem //: IScheduledItem
{
// delegate that returns a boolean
public Func<bool> timerUpdateStopCondition;
public static readonly Func<bool> OnceCondition = () => true;
public static readonly Func<bool> ForeverCondition = () => false;
public long startMs { get; set; }
public long delayMs { get; set; }
public long intervalMs { get; set; }
public long endTimeMs { get; private set; }
public ScheduledItem()
{
ResetStartTime();
timerUpdateStopCondition = OnceCondition;
}
protected void ResetStartTime()
{
this.startMs = Panel.TimeSinceStartupMs();
}
public void SetDuration(long durationMs)
{
endTimeMs = startMs + durationMs;
}
public abstract void PerformTimerUpdate(TimerState state);
internal virtual void OnItemUnscheduled()
{}
public virtual bool ShouldUnschedule()
{
if (timerUpdateStopCondition != null)
{
return timerUpdateStopCondition();
}
return false;
}
}
// default scheduler implementation
internal class TimerEventScheduler : IScheduler
{
private readonly List<ScheduledItem> m_ScheduledItems = new List<ScheduledItem>();
private bool m_TransactionMode;
private readonly List<ScheduledItem> m_ScheduleTransactions = new List<ScheduledItem>(); // order is important schedules are executed in add order
private readonly HashSet<ScheduledItem> m_UnscheduleTransactions = new HashSet<ScheduledItem>(); // order no important. removal from m_ScheduledItems has no side effect
internal bool disableThrottling = false;
private int m_LastUpdatedIndex = -1;
private class TimerEventSchedulerItem : ScheduledItem
{
// delegate that takes a timer state and returns void
private readonly Action<TimerState> m_TimerUpdateEvent;
public TimerEventSchedulerItem(Action<TimerState> updateEvent)
{
m_TimerUpdateEvent = updateEvent;
}
public override void PerformTimerUpdate(TimerState state)
{
m_TimerUpdateEvent?.Invoke(state);
}
public override string ToString()
{
return m_TimerUpdateEvent.ToString();
}
}
public void Schedule(ScheduledItem item)
{
if (item == null)
return;
ScheduledItem scheduledItem = item as ScheduledItem;
if (scheduledItem == null)
{
throw new NotSupportedException("Scheduled Item type is not supported by this scheduler");
}
if (m_TransactionMode)
{
if (m_UnscheduleTransactions.Remove(scheduledItem))
{
// The item was unscheduled then rescheduled in the same transaction.
}
else if (m_ScheduledItems.Contains(scheduledItem) || m_ScheduleTransactions.Contains(scheduledItem))
{
throw new ArgumentException(string.Concat("Cannot schedule function ", scheduledItem, " more than once"));
}
else
{
m_ScheduleTransactions.Add(scheduledItem);
}
}
else
{
if (m_ScheduledItems.Contains(scheduledItem))
{
throw new ArgumentException(string.Concat("Cannot schedule function ", scheduledItem, " more than once"));
}
else
{
m_ScheduledItems.Add(scheduledItem);
}
}
}
public ScheduledItem ScheduleOnce(Action<TimerState> timerUpdateEvent, long delayMs)
{
var scheduleItem = new TimerEventSchedulerItem(timerUpdateEvent)
{
delayMs = delayMs
};
Schedule(scheduleItem);
return scheduleItem;
}
public ScheduledItem ScheduleUntil(Action<TimerState> timerUpdateEvent, long delayMs, long intervalMs,
Func<bool> stopCondition)
{
var scheduleItem = new TimerEventSchedulerItem(timerUpdateEvent)
{
delayMs = delayMs,
intervalMs = intervalMs,
timerUpdateStopCondition = stopCondition
};
Schedule(scheduleItem);
return scheduleItem;
}
public ScheduledItem ScheduleForDuration(Action<TimerState> timerUpdateEvent, long delayMs, long intervalMs,
long durationMs)
{
var scheduleItem = new TimerEventSchedulerItem(timerUpdateEvent)
{
delayMs = delayMs,
intervalMs = intervalMs,
timerUpdateStopCondition = null
};
scheduleItem.SetDuration(durationMs);
Schedule(scheduleItem);
return scheduleItem;
}
private bool RemovedScheduledItemAt(int index)
{
if (index >= 0)
{
var item = m_ScheduledItems[index];
m_ScheduledItems.RemoveAt(index);
return true;
}
return false;
}
public void Unschedule(ScheduledItem item)
{
ScheduledItem sItem = item as ScheduledItem;
if (sItem != null)
{
if (m_TransactionMode)
{
if (m_UnscheduleTransactions.Contains(sItem))
{
throw new ArgumentException("Cannot unschedule scheduled function twice" + sItem);
}
else if (m_ScheduleTransactions.Remove(sItem))
{
// A item has been scheduled then unscheduled in the same transaction. which is valid.
}
else if (m_ScheduledItems.Contains(sItem))
{
// Only add it to m_UnscheduleTransactions if it is in m_ScheduledItems.
// if it was successfully removed from m_ScheduleTransaction we are fine.
m_UnscheduleTransactions.Add(sItem);
}
else
{
throw new ArgumentException("Cannot unschedule unknown scheduled function " + sItem);
}
}
else
{
if (!PrivateUnSchedule(sItem))
{
throw new ArgumentException("Cannot unschedule unknown scheduled function " + sItem);
}
}
sItem.OnItemUnscheduled(); // Call OnItemUnscheduled immediately after successful removal even if we are in transaction mode
}
}
bool PrivateUnSchedule(ScheduledItem sItem)
{
return m_ScheduleTransactions.Remove(sItem) || RemovedScheduledItemAt(m_ScheduledItems.IndexOf(sItem));
}
public void UpdateScheduledEvents()
{
try
{
m_TransactionMode = true;
// TODO: On a GAME Panel game time should be per frame and not change during a frame.
// TODO: On an Editor Panel time should be real time
long currentTime = Panel.TimeSinceStartupMs();
int itemsCount = m_ScheduledItems.Count;
const long maxMsPerUpdate = 20;
long maxTime = currentTime + maxMsPerUpdate;
int startIndex = m_LastUpdatedIndex + 1;
if (startIndex >= itemsCount)
startIndex = 0;
for (int i = 0; i < itemsCount; i++)
{
currentTime = Panel.TimeSinceStartupMs();
if (!disableThrottling && currentTime >= maxTime)
{
//We spent too much time on this frame updating items, we break for now, we'll resume next frame
break;
}
int index = startIndex + i;
if (index >= itemsCount)
{
index -= itemsCount;
}
ScheduledItem scheduledItem = m_ScheduledItems[index];
bool unscheduleItem = false;
if (currentTime - scheduledItem.delayMs >= scheduledItem.startMs)
{
TimerState timerState = new TimerState { start = scheduledItem.startMs, now = currentTime };
if (!m_UnscheduleTransactions.Contains(scheduledItem)) // Don't execute items that have been marked for future removal
scheduledItem.PerformTimerUpdate(timerState);
scheduledItem.startMs = currentTime;
scheduledItem.delayMs = scheduledItem.intervalMs;
if (scheduledItem.ShouldUnschedule())
{
unscheduleItem = true;
}
}
if (unscheduleItem || (scheduledItem.endTimeMs > 0 && currentTime > scheduledItem.endTimeMs))
{
// if the scheduledItem has been unscheduled explicitly in PerformTimerUpdate then
// it will be in m_UnscheduleTransactions and we shouldn't unschedule it again
if (!m_UnscheduleTransactions.Contains(scheduledItem))
{
Unschedule(scheduledItem);
}
}
m_LastUpdatedIndex = index;
}
}
finally
{
m_TransactionMode = false;
// Rule: remove unscheduled transactions first
foreach (var item in m_UnscheduleTransactions)
{
PrivateUnSchedule(item);
}
m_UnscheduleTransactions.Clear();
// Then add scheduled transactions
foreach (var item in m_ScheduleTransactions)
{
Schedule(item);
}
m_ScheduleTransactions.Clear();
}
}
}
}