3
3
namespace React \EventLoop \Timer ;
4
4
5
5
use React \EventLoop \TimerInterface ;
6
- use SplObjectStorage ;
7
- use SplPriorityQueue ;
8
6
9
7
/**
10
8
* A scheduler implementation that can hold multiple timer instances
17
15
final class Timers
18
16
{
19
17
private $ time ;
20
- private $ timers ;
21
- private $ scheduler ;
22
-
23
- public function __construct ()
24
- {
25
- $ this ->timers = new SplObjectStorage ();
26
- $ this ->scheduler = new SplPriorityQueue ();
27
- }
18
+ private $ timers = array ();
19
+ private $ schedule = array ();
20
+ private $ sorted = true ;
28
21
29
22
public function updateTime ()
30
23
{
@@ -38,36 +31,32 @@ public function getTime()
38
31
39
32
public function add (TimerInterface $ timer )
40
33
{
41
- $ interval = $ timer ->getInterval ();
42
- $ scheduledAt = $ interval + microtime (true );
43
-
44
- $ this ->timers ->attach ($ timer , $ scheduledAt );
45
- $ this ->scheduler ->insert ($ timer , -$ scheduledAt );
34
+ $ id = spl_object_hash ($ timer );
35
+ $ this ->timers [$ id ] = $ timer ;
36
+ $ this ->schedule [$ id ] = $ timer ->getInterval () + microtime (true );
37
+ $ this ->sorted = false ;
46
38
}
47
39
48
40
public function contains (TimerInterface $ timer )
49
41
{
50
- return $ this ->timers -> contains ($ timer );
42
+ return isset ( $ this ->timers [ spl_oject_hash ($ timer)] );
51
43
}
52
44
53
45
public function cancel (TimerInterface $ timer )
54
46
{
55
- $ this ->timers ->detach ($ timer );
47
+ $ id = spl_object_hash ($ timer );
48
+ unset($ this ->timers [$ id ], $ this ->schedule [$ id ]);
56
49
}
57
50
58
51
public function getFirst ()
59
52
{
60
- while ($ this ->scheduler ->count ()) {
61
- $ timer = $ this ->scheduler ->top ();
62
-
63
- if ($ this ->timers ->contains ($ timer )) {
64
- return $ this ->timers [$ timer ];
65
- }
66
-
67
- $ this ->scheduler ->extract ();
53
+ // ensure timers are sorted to simply accessing next (first) one
54
+ if (!$ this ->sorted ) {
55
+ $ this ->sorted = true ;
56
+ asort ($ this ->schedule );
68
57
}
69
58
70
- return null ;
59
+ return reset ( $ this -> schedule ) ;
71
60
}
72
61
73
62
public function isEmpty ()
@@ -77,32 +66,34 @@ public function isEmpty()
77
66
78
67
public function tick ()
79
68
{
80
- $ time = $ this ->updateTime ();
81
- $ timers = $ this ->timers ;
82
- $ scheduler = $ this ->scheduler ;
83
-
84
- while (!$ scheduler ->isEmpty ()) {
85
- $ timer = $ scheduler ->top ();
69
+ // ensure timers are sorted so we can execute in order
70
+ if (!$ this ->sorted ) {
71
+ $ this ->sorted = true ;
72
+ asort ($ this ->schedule );
73
+ }
86
74
87
- if (!isset ($ timers [$ timer ])) {
88
- $ scheduler ->extract ();
89
- $ timers ->detach ($ timer );
75
+ $ time = $ this ->updateTime ();
90
76
91
- continue ;
77
+ foreach ($ this ->schedule as $ id => $ scheduled ) {
78
+ // schedule is ordered, so loop until first timer that is not scheduled for execution now
79
+ if ($ scheduled >= $ time ) {
80
+ break ;
92
81
}
93
82
94
- if ($ timers [$ timer ] >= $ time ) {
95
- break ;
83
+ // skip any timers that are removed while we process the current schedule
84
+ if (!isset ($ this ->schedule [$ id ]) || $ this ->schedule [$ id ] !== $ scheduled ) {
85
+ continue ;
96
86
}
97
87
98
- $ scheduler -> extract () ;
88
+ $ timer = $ this -> timers [ $ id ] ;
99
89
call_user_func ($ timer ->getCallback (), $ timer );
100
90
101
- if ($ timer ->isPeriodic () && isset ($ timers [$ timer ])) {
102
- $ timers [$ timer ] = $ scheduledAt = $ timer ->getInterval () + $ time ;
103
- $ scheduler ->insert ($ timer , -$ scheduledAt );
91
+ // re-schedule if this is a periodic timer and it has not been cancelled explicitly already
92
+ if ($ timer ->isPeriodic () && isset ($ this ->timers [$ id ])) {
93
+ $ this ->schedule [$ id ] = $ timer ->getInterval () + $ time ;
94
+ $ this ->sorted = false ;
104
95
} else {
105
- $ timers-> detach ( $ timer );
96
+ unset( $ this -> timers [ $ id ], $ this -> schedule [ $ id ] );
106
97
}
107
98
}
108
99
}
0 commit comments