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 package com.lhkbob.entreri.task;
28
29 import com.lhkbob.entreri.Component;
30
31 import java.lang.reflect.InvocationTargetException;
32 import java.lang.reflect.Method;
33 import java.util.*;
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 public class Job implements Runnable {
50 private final Task[] tasks;
51 private final Map<Class<? extends Result>, List<ResultReporter>> resultMethods;
52
53 private final boolean needsExclusiveLock;
54 private final List<Class<? extends Component>> locks;
55
56 private final Scheduler scheduler;
57 private final String name;
58
59 private final Set<Class<? extends Result>> singletonResults;
60 private int taskIndex;
61
62
63
64
65
66
67
68
69
70
71
72 Job(String name, Scheduler scheduler, Task... tasks) {
73 if (name == null) {
74 throw new NullPointerException("Name cannot be null");
75 }
76 this.scheduler = scheduler;
77 this.tasks = new Task[tasks.length];
78 this.name = name;
79
80 singletonResults = new HashSet<>();
81 resultMethods = new HashMap<>();
82 taskIndex = -1;
83
84 boolean exclusive = false;
85 Set<Class<? extends Component>> typeLocks = new HashSet<>();
86 for (int i = 0; i < tasks.length; i++) {
87 if (tasks[i] == null) {
88 throw new NullPointerException("Task cannot be null");
89 }
90
91 this.tasks[i] = tasks[i];
92
93
94
95 if (tasks[i] instanceof ParallelAware) {
96 ParallelAware pa = (ParallelAware) tasks[i];
97 exclusive |= pa.isEntitySetModified();
98 typeLocks.addAll(pa.getAccessedComponents());
99 } else {
100
101 exclusive = true;
102 }
103
104
105 for (Method m : tasks[i].getClass().getMethods()) {
106 if (m.getName().equals("report")) {
107 if (m.getReturnType().equals(void.class) &&
108 m.getParameterTypes().length == 1 &&
109 Result.class.isAssignableFrom(m.getParameterTypes()[0])) {
110
111 m.setAccessible(true);
112 ResultReporter reporter = new ResultReporter(m, i);
113 Class<? extends Result> type = reporter.getResultType();
114
115 List<ResultReporter> all = resultMethods.get(type);
116 if (all == null) {
117 all = new ArrayList<>();
118 resultMethods.put(type, all);
119 }
120
121 all.add(reporter);
122 }
123 }
124 }
125 }
126
127 if (exclusive) {
128 needsExclusiveLock = true;
129 locks = null;
130 } else {
131 needsExclusiveLock = false;
132 locks = new ArrayList<>(typeLocks);
133
134 Collections.sort(locks, new Comparator<Class<? extends Component>>() {
135 @Override
136 public int compare(Class<? extends Component> o1,
137 Class<? extends Component> o2) {
138 return o1.getName().compareTo(o2.getName());
139 }
140 });
141 }
142 }
143
144
145
146
147 public String getName() {
148 return name;
149 }
150
151
152
153
154 public Scheduler getScheduler() {
155 return scheduler;
156 }
157
158
159
160
161
162
163
164
165
166
167 @Override
168 public void run() {
169
170 Job toInvoke = this;
171 while (toInvoke != null) {
172 toInvoke = toInvoke.runJob();
173 }
174 }
175
176 private Job runJob() {
177
178 if (needsExclusiveLock) {
179 scheduler.getEntitySystemLock().writeLock().lock();
180 } else {
181 scheduler.getEntitySystemLock().readLock().lock();
182 for (int i = 0; i < locks.size(); i++) {
183 scheduler.getTypeLock(locks.get(i)).lock();
184 }
185 }
186
187 try {
188
189 taskIndex = 0;
190 singletonResults.clear();
191 for (int i = 0; i < tasks.length; i++) {
192 tasks[i].reset(scheduler.getEntitySystem());
193 }
194
195
196 List<Task> postProcess = new ArrayList<>();
197 for (int i = 0; i < tasks.length; i++) {
198 taskIndex = i;
199 Task after = tasks[i].process(scheduler.getEntitySystem(), this);
200 if (after != null) {
201 postProcess.add(after);
202 }
203 }
204
205
206
207 taskIndex = -1;
208
209 if (postProcess.isEmpty()) {
210
211 return null;
212 } else {
213 Task[] tasks = postProcess.toArray(new Task[postProcess.size()]);
214 return new Job(name + "-postprocess", scheduler, tasks);
215 }
216 } finally {
217
218 if (needsExclusiveLock) {
219 scheduler.getEntitySystemLock().writeLock().unlock();
220 } else {
221 for (int i = locks.size() - 1; i >= 0; i--) {
222 scheduler.getTypeLock(locks.get(i)).unlock();
223 }
224 scheduler.getEntitySystemLock().readLock().unlock();
225 }
226 }
227 }
228
229
230
231
232
233
234
235
236
237
238
239
240
241 public void report(Result r) {
242 if (r == null) {
243 throw new NullPointerException("Cannot report null results");
244 }
245 if (taskIndex < 0) {
246 throw new IllegalStateException(
247 "Can only be invoked by a task from within run()");
248 }
249
250 if (r.isSingleton()) {
251
252 if (!singletonResults.add(r.getClass())) {
253 throw new IllegalStateException(
254 "Singleton result of type: " + r.getClass() +
255 " has already been reported during " + name + "'s execution");
256 }
257 }
258
259 Class<?> type = r.getClass();
260 while (Result.class.isAssignableFrom(type)) {
261
262 List<ResultReporter> all = resultMethods.get(type);
263 if (all != null) {
264 int ct = all.size();
265 for (int i = 0; i < ct; i++) {
266
267
268 all.get(i).report(r);
269 }
270 }
271
272 type = type.getSuperclass();
273 }
274 }
275
276 @Override
277 public String toString() {
278 return "Job(" + name + ", # tasks=" + tasks.length + ")";
279 }
280
281 private class ResultReporter {
282 private final Method reportMethod;
283 private final int taskIndex;
284
285 public ResultReporter(Method reportMethod, int taskIndex) {
286 this.reportMethod = reportMethod;
287 this.taskIndex = taskIndex;
288 }
289
290 public void report(Result r) {
291 try {
292 if (taskIndex > Job.this.taskIndex) {
293 reportMethod.invoke(Job.this.tasks[taskIndex], r);
294 }
295 } catch (IllegalArgumentException | IllegalAccessException e) {
296
297 throw new RuntimeException(e);
298 } catch (InvocationTargetException e) {
299 throw new RuntimeException("Error reporting result", e.getCause());
300 }
301 }
302
303 @SuppressWarnings("unchecked")
304 public Class<? extends Result> getResultType() {
305 return (Class<? extends Result>) reportMethod.getParameterTypes()[0];
306 }
307 }
308 }