1
1
package edu .wpi .grip .core ;
2
2
3
+ import com .google .common .collect .ImmutableList ;
3
4
import com .google .common .eventbus .EventBus ;
4
5
import com .google .common .eventbus .Subscribe ;
5
6
import com .google .inject .Singleton ;
11
12
12
13
import javax .inject .Inject ;
13
14
import java .util .*;
15
+ import java .util .concurrent .locks .Lock ;
16
+ import java .util .concurrent .locks .ReadWriteLock ;
17
+ import java .util .concurrent .locks .ReentrantReadWriteLock ;
18
+ import java .util .function .Consumer ;
19
+ import java .util .function .Function ;
14
20
import java .util .stream .Collectors ;
15
21
16
22
import static com .google .common .base .Preconditions .checkArgument ;
@@ -35,7 +41,14 @@ public class Pipeline {
35
41
@ XStreamOmitField
36
42
private NTManager ntManager ;
37
43
44
+ /*
45
+ * We have separate locks for sources and steps because we don't want to
46
+ * block access to both resources when only one is in use.
47
+ */
48
+
49
+ private transient final ReadWriteLock sourceLock = new ReentrantReadWriteLock ();
38
50
private final List <Source > sources = new ArrayList <>();
51
+ private transient ReadWriteLock stepLock = new ReentrantReadWriteLock ();
39
52
private final List <Step > steps = new ArrayList <>();
40
53
private final Set <Connection > connections = new HashSet <>();
41
54
private ProjectSettings settings = new ProjectSettings ();
@@ -44,27 +57,108 @@ public class Pipeline {
44
57
* Remove everything in the pipeline
45
58
*/
46
59
public void clear () {
47
- this . steps . stream (). collect ( Collectors . toList () ).forEach (this ::removeStep );
48
-
60
+ getSteps ( ).forEach (this ::removeStep );
61
+ // We collect the list first because the event modifies the list
49
62
this .sources .stream ()
50
63
.map (SourceRemovedEvent ::new )
51
64
.collect (Collectors .toList ())
52
65
.forEach (this .eventBus ::post );
53
66
}
54
67
68
+ private final <R > R readSourcesSafely (Function <List <Source >, R > sourceListFunction ) {
69
+ return accessSafely (sourceLock .readLock (), Collections .unmodifiableList (sources ), sourceListFunction );
70
+ }
71
+
72
+ /**
73
+ * Returns a snapshot of all of the sources in the pipeline.
74
+ *
75
+ * @return an Immutable copy of the sources at the current point in the pipeline.
76
+ * @see <a href="https://youtu.be/ZeO_J2OcHYM?t=16m35s">Why we use ImmutableList return type</a>
77
+ */
78
+ public final ImmutableList <Source > getSources () {
79
+ return readSourcesSafely (ImmutableList ::copyOf );
80
+ }
81
+
55
82
/**
56
- * @return The unmodifiable list of sources for inputs to the algorithm
57
- * @see Source
83
+ * @param stepListFunction The function to read the steps with.
84
+ * @param <R> The return type of the function
85
+ * @return The value returned by the function.
58
86
*/
59
- public List < Source > getSources ( ) {
60
- return Collections .unmodifiableList (this . sources );
87
+ private final < R > R readStepsSafely ( Function < List < Step >, R > stepListFunction ) {
88
+ return accessSafely ( stepLock . readLock (), Collections .unmodifiableList (steps ), stepListFunction );
61
89
}
62
90
63
91
/**
64
- * @return The unmodifiable list of steps in the computer vision algorithm
92
+ * Returns a snapshot of all of the steps in the pipeline.
93
+ *
94
+ * @return an Immutable copy of the steps at the current point in the pipeline.
95
+ * @see <a href="https://youtu.be/ZeO_J2OcHYM?t=16m35s">Why we use ImmutableList return type</a>
65
96
*/
66
- public List <Step > getSteps () {
67
- return Collections .unmodifiableList (this .steps );
97
+ public final ImmutableList <Step > getSteps () {
98
+ return readStepsSafely (ImmutableList ::copyOf );
99
+ }
100
+
101
+ /*
102
+ * These methods should not be made public.
103
+ * If you do so you are making a poor design decision and should move whatever you are trying to do into
104
+ * this class.
105
+ */
106
+
107
+ /**
108
+ * @param stepListWriterFunction A function that modifies the step list passed to the operation.
109
+ * @param <R> The return type of the function
110
+ * @return The value returned by the function.
111
+ */
112
+ private <R > R writeStepsSafely (Function <List <Step >, R > stepListWriterFunction ) {
113
+ return accessSafely (stepLock .writeLock (), steps , stepListWriterFunction );
114
+ }
115
+
116
+ /**
117
+ * @param stepListWriterConsumer A consumer that can modify the list that is passed to it.
118
+ */
119
+ private void writeStepsSafelyConsume (Consumer <List <Step >> stepListWriterConsumer ) {
120
+ writeStepsSafely (stepList -> {
121
+ stepListWriterConsumer .accept (stepList );
122
+ return null ;
123
+ });
124
+ }
125
+
126
+ private <R > R writeSourcesSafely (Function <List <Source >, R > sourceListWriterFunction ) {
127
+ return accessSafely (sourceLock .writeLock (), sources , sourceListWriterFunction );
128
+ }
129
+
130
+ private void writeSourcesSafelyConsume (Consumer <List <Source >> sourceListWriterFunction ) {
131
+ writeSourcesSafely (sources -> {
132
+ sourceListWriterFunction .accept (sources );
133
+ return null ;
134
+ });
135
+ }
136
+
137
+ /*
138
+ * End of methods that should not be made public
139
+ */
140
+
141
+ /**
142
+ * Locks the resource with the specified lock and performs the function.
143
+ * When the function is complete then the lock unlocked again.
144
+ *
145
+ * @param lock The lock for the given resource
146
+ * @param list The list that will be accessed while the resource is locked
147
+ * @param listFunction The function that either modifies or accesses the list
148
+ * @param <T> The type of list
149
+ * @param <R> The return value for the function
150
+ * @return The value returned by the list function
151
+ */
152
+ private static <T , R > R accessSafely (Lock lock , List <T > list , Function <List <T >, R > listFunction ) {
153
+ final R returnValue ;
154
+ lock .lock ();
155
+ try {
156
+ returnValue = listFunction .apply (list );
157
+ } finally {
158
+ // Ensure that no matter what may get thrown while reading the steps we unlock
159
+ lock .unlock ();
160
+ }
161
+ return returnValue ;
68
162
}
69
163
70
164
/**
@@ -132,43 +226,53 @@ public boolean canConnect(Socket socket1, Socket socket2) {
132
226
/**
133
227
* @return true if the step1 is before step2 in the pipeline
134
228
*/
135
- private synchronized boolean isBefore (Step step1 , Step step2 ) {
136
- return this . steps .indexOf (step1 ) < this . steps .indexOf (step2 );
229
+ private boolean isBefore (Step step1 , Step step2 ) {
230
+ return readStepsSafely ( steps -> steps .indexOf (step1 ) < steps .indexOf (step2 ) );
137
231
}
138
232
139
233
@ Subscribe
140
234
public void onSourceAdded (SourceAddedEvent event ) {
141
- this .sources .add (event .getSource ());
235
+ writeSourcesSafelyConsume (sources -> {
236
+ sources .add (event .getSource ());
237
+ });
142
238
}
143
239
144
240
@ Subscribe
145
241
public void onSourceRemoved (SourceRemovedEvent event ) {
146
- this .sources .remove (event .getSource ());
242
+ writeSourcesSafelyConsume (sources -> {
243
+ sources .remove (event .getSource ());
244
+ });
147
245
148
246
// Sockets of deleted sources should not be previewed
149
247
for (OutputSocket <?> socket : event .getSource ().getOutputSockets ()) {
150
248
socket .setPreviewed (false );
151
249
}
152
250
}
153
251
154
- public synchronized void addStep (int index , Step step ) {
252
+ public void addStep (int index , Step step ) {
155
253
checkNotNull (step , "The step can not be null" );
156
- this .steps .add (index , step );
254
+ checkArgument (!step .removed (), "The step must not have been disabled already" );
255
+
256
+ writeStepsSafelyConsume (steps -> steps .add (index , step ));
257
+
157
258
this .eventBus .register (step );
158
259
this .eventBus .post (new StepAddedEvent (step , index ));
159
260
}
160
261
161
- public synchronized void addStep (Step step ) {
262
+ public void addStep (Step step ) {
162
263
addStep (this .steps .size (), step );
163
264
}
164
265
165
- public synchronized void removeStep (Step step ) {
266
+ public void removeStep (Step step ) {
166
267
checkNotNull (step , "The step can not be null" );
167
- this .steps .remove (step );
268
+
269
+ writeStepsSafelyConsume (steps -> steps .remove (step ));
270
+
168
271
// Sockets of deleted steps should not be previewed
169
272
for (OutputSocket <?> socket : step .getOutputSockets ()) {
170
273
socket .setPreviewed (false );
171
274
}
275
+ step .setRemoved ();
172
276
this .eventBus .unregister (step );
173
277
this .eventBus .post (new StepRemovedEvent (step ));
174
278
}
@@ -177,13 +281,19 @@ public synchronized void moveStep(Step step, int delta) {
177
281
checkNotNull (step , "The step can not be null" );
178
282
checkArgument (this .steps .contains (step ), "The step must exist in the pipeline to be moved" );
179
283
180
- final int oldIndex = this .steps .indexOf (step );
181
- this .steps .remove (oldIndex );
284
+ // We are modifying the steps array
285
+ writeStepsSafelyConsume (steps -> {
286
+ final int oldIndex = this .steps .indexOf (step );
287
+ this .steps .remove (oldIndex );
182
288
183
- // Compute the new index of the step, clamping to the beginning or end of pipeline if it goes past either end
184
- final int newIndex = Math .min (Math .max (oldIndex + delta , 0 ), this .steps .size ());
185
- this .steps .add (newIndex , step );
289
+ // Compute the new index of the step, clamping to the beginning or end of pipeline if it goes past either end
290
+ final int newIndex = Math .min (Math .max (oldIndex + delta , 0 ), this .steps .size ());
291
+ this .steps .add (newIndex , step );
292
+ });
293
+
294
+ // Do not lock while posting the event
186
295
eventBus .post (new StepMovedEvent (step , delta ));
296
+
187
297
}
188
298
189
299
@ Subscribe
0 commit comments