Add read interface to event stream from Lua
[jpcrr.git] / org / jpc / emulator / EventRecorder.java
blob8a6e5484975606815dd154e6520c6e9d2f6c09bf
1 /*
2 JPC-RR: A x86 PC Hardware Emulator
3 Release 1
5 Copyright (C) 2007-2009 Isis Innovation Limited
6 Copyright (C) 2009-2010 H. Ilari Liusvaara
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License version 2 as published by
10 the Free Software Foundation.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License along
18 with this program; if not, write to the Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 Based on JPC x86 PC Hardware emulator,
22 A project from the Physics Dept, The University of Oxford
24 Details about original JPC can be found at:
26 www-jpc.physics.ox.ac.uk
30 package org.jpc.emulator;
32 import org.jpc.jrsr.UTFInputLineStream;
33 import org.jpc.jrsr.UTFOutputLineStream;
34 import static org.jpc.Misc.nextParseLine;
35 import java.io.*;
36 import java.util.*;
37 import static org.jpc.Misc.errorDialog;
39 public class EventRecorder implements TimerResponsive
41 private static final int EVENT_MAGIC_CLASS = 0;
42 private static final int EVENT_MAGIC_SAVESTATE = 1;
43 private static final int EVENT_MAGIC_NONE = -1;
45 public static final int EVENT_TIMED = 0;
46 public static final int EVENT_STATE_EFFECT_FUTURE = 1;
47 public static final int EVENT_STATE_EFFECT = 2;
48 public static final int EVENT_EXECUTE = 3;
50 public class ReturnEvent
52 public long timestamp;
53 public String[] eventData;
56 public class Event
58 public long timestamp; //Event timestamp (low bound)
59 public long sequenceNumber; //Sequence number.
60 public int magic; //Magic type.
61 public Class<? extends HardwareComponent> clazz; //Dispatch to where.
62 public String[] args; //Arguments to dispatch.
63 public Event prev; //Previous event.
64 public Event next; //Next event.
66 public void dispatch(PC target, int level) throws IOException
68 if(magic != EVENT_MAGIC_CLASS)
69 return; //We really don't want to dispatch these.
71 HardwareComponent hwc = target.getComponent(clazz);
72 if(hwc == null)
73 throw new IOException("Invalid event target \"" + clazz.getName() + "\": no component of such type");
74 EventDispatchTarget component = (EventDispatchTarget)hwc;
75 component.doEvent(timestamp, args, level);
79 private Event first;
80 private Event current;
81 private Event last;
82 private Event cache;
83 private Event firstUndispatched;
84 private Event lastUndispatched;
85 private PC pc;
86 private boolean directMode;
87 private Clock sysClock;
88 private Timer sysTimer;
89 private long timerInvokeTime;
90 private long savestateRerecordCount;
91 private boolean dirtyFlag;
92 private long cleanTime;
93 private long attachTime;
94 private String[][] headers;
95 private long movieRerecordCount;
97 private boolean isDirty()
99 //sysClock == null should not happen, but include it just for sure.
100 return (dirtyFlag || sysClock == null || cleanTime != sysClock.getTime());
103 private void setClean()
105 dirtyFlag = false;
106 cleanTime = (sysClock != null) ? sysClock.getTime() : -1;
109 public long getAttachTime()
111 return attachTime;
114 public long getRerecordCount()
116 return movieRerecordCount - savestateRerecordCount;
119 public void setRerecordCount(long newCount)
121 movieRerecordCount = newCount;
124 public String[][] getHeaders()
126 return headers;
129 public void setHeaders(String[][] newHeaders)
131 if(newHeaders == null) {
132 headers = null;
133 return;
136 String[][] tmp = new String[newHeaders.length][];
137 for(int i = 0; i < tmp.length; i++)
138 if(newHeaders[i] != null)
139 tmp[i] = Arrays.copyOf(newHeaders[i], newHeaders[i].length);
141 headers = tmp;
144 public void setTimer(long time)
146 if(directMode) {
147 sysTimer.disable(); //No need for timers in direct mode.
148 return;
150 if((sysTimer.enabled() && timerInvokeTime <= time))
151 return; //No need for timer.
152 sysTimer.setExpiry(timerInvokeTime = time);
155 public void addEvent(long timeLowBound, Class<? extends HardwareComponent> clazz,
156 String[] args) throws IOException
158 /* Compute the final time for event. */
159 long timeNow = sysClock.getTime();
160 synchronized(this) {
161 long time = timeLowBound;
162 if(last != null && time < last.timestamp)
163 time = last.timestamp;
164 if(time < timeNow)
165 time = timeNow;
167 HardwareComponent hwc = pc.getComponent(clazz);
168 EventDispatchTarget component = (EventDispatchTarget)hwc;
169 long freeLowBound = -1;
170 try {
171 freeLowBound = component.getEventTimeLowBound(time, args);
172 } catch(Exception e) {}; //Shouldn't throw.
173 if(time < freeLowBound)
174 time = freeLowBound;
176 Event ev = new Event();
177 ev.timestamp = time;
178 ev.magic = EVENT_MAGIC_CLASS;
179 ev.clazz = clazz;
180 ev.args = args;
182 if(firstUndispatched == null)
183 firstUndispatched = ev;
184 if(lastUndispatched != null)
185 lastUndispatched.next = ev;
186 ev.prev = lastUndispatched;
187 lastUndispatched = ev;
190 if(directMode) {
191 handleUndispatchedEvents();
192 } else {
193 setTimer(timeNow); //Fire it as soon as possible.
198 private void handleUndispatchedEvents()
200 //This has toi be outside or we can have ABBA deadlock.
201 long timeNow = sysClock.getTime();
203 //Synchronize to prevent racing with addEvent()
204 synchronized(this) {
205 //First move undispatched events to main queue.
206 Event scan = firstUndispatched;
207 while(scan != null) {
208 dirtyFlag = true;
209 //Compute time for event.
210 Event scanNext = scan.next;
211 if(scan.timestamp < timeNow)
212 scan.timestamp = timeNow;
213 if(last != null && scan.timestamp < last.timestamp)
214 scan.timestamp = last.timestamp;
216 HardwareComponent hwc = pc.getComponent(scan.clazz);
217 EventDispatchTarget component = (EventDispatchTarget)hwc;
218 long freeLowBound = -1;
219 try {
220 freeLowBound = component.getEventTimeLowBound(scan.timestamp, scan.args);
221 } catch(Exception e) {}; //Shouldn't throw.
222 if(scan.timestamp < freeLowBound)
223 scan.timestamp = freeLowBound;
225 try {
226 scan.dispatch(pc, EVENT_TIMED);
227 } catch(Exception e) {
228 System.err.println("Error: Event dispatch failed.");
229 errorDialog(e, "Failed to dispatch event", null, "Dismiss");
230 scan = null;
233 //Because of constraints to time, the event must go last.
234 if(scan != null) {
235 //Sequence number for first event is 0 and then increments by 1 for each event.
236 scan.sequenceNumber = ((last != null) ? (last.sequenceNumber + 1) : 0);
237 scan.next = null;
238 scan.prev = last;
239 if(last != null)
240 last.next = scan;
241 last = scan;
242 if(current == null)
243 current = scan;
244 if(first == null)
245 first = scan;
247 scan = scanNext;
249 firstUndispatched = null;
250 lastUndispatched = null;
253 //Then fire apporiate events from main queue.
254 while(current != null && current.timestamp <= timeNow) {
255 try {
256 current.dispatch(pc, EVENT_EXECUTE);
257 } catch(Exception e) {
258 System.err.println("Error: Event dispatch failed.");
259 errorDialog(e, "Failed to dispatch event", null, "Dismiss");
261 current = current.next;
263 if(current != null)
264 setTimer(current.timestamp);
267 public synchronized void setPCRunStatus(boolean running)
269 directMode = !running;
270 if(directMode)
271 handleUndispatchedEvents();
272 if(current != null)
273 setTimer(current.timestamp);
276 public void truncateEventStream()
278 if(current != null) {
279 dirtyFlag = true;
280 if(current.sequenceNumber <= cache.sequenceNumber)
281 cache = null; //Flush cache as event got truncated out.
282 last = current.prev;
283 current = null;
284 if(last != null)
285 last.next = null;
286 else
287 first = null;
288 Event scan = first;
289 dispatchStart(pc);
290 while(scan != null) {
291 try {
292 scan.dispatch(pc, EVENT_STATE_EFFECT);
293 } catch(Exception e) {}
294 scan = scan.next;
296 try {
297 dispatchEnd(pc);
298 } catch(Exception e) {}
300 firstUndispatched = null;
301 lastUndispatched = null;
304 private void dispatchStart(PC target)
306 Set<HardwareComponent> pcParts = target.allComponents();
307 for(HardwareComponent hwc : pcParts) {
308 if(!EventDispatchTarget.class.isAssignableFrom(hwc.getClass()))
309 continue;
310 EventDispatchTarget t = (EventDispatchTarget)hwc;
311 t.setEventRecorder(this);
312 t.startEventCheck();
316 private void dispatchEnd(PC target) throws IOException
318 Set<HardwareComponent> pcParts = target.allComponents();
319 for(HardwareComponent hwc : pcParts) {
320 if(!EventDispatchTarget.class.isAssignableFrom(hwc.getClass()))
321 continue;
322 EventDispatchTarget t = (EventDispatchTarget)hwc;
323 t.endEventCheck();
327 public EventRecorder()
329 first = null;
330 current = null;
331 last = null;
332 cache = null;
333 firstUndispatched = null;
334 lastUndispatched = null;
335 directMode = true;
336 dirtyFlag = true;
337 cleanTime = -1;
340 private static boolean isReservedName(String name)
342 for(int i = 0; i < name.length(); i++) {
343 char j = name.charAt(i);
344 if(j >= '0' && j <= '9')
345 continue;
346 if(j >= 'A' && j <= 'Z')
347 continue;
348 return false;
350 return true;
353 public EventRecorder(UTFInputLineStream lines) throws IOException
355 boolean relativeTime = false;
356 long lastTimestamp = 0;
357 dirtyFlag = true;
358 cleanTime = -1;
359 String[] components = nextParseLine(lines);
360 while(components != null) {
361 Event ev = new Event();
362 if(components.length < 2)
363 throw new IOException("Malformed event line");
364 long timeStamp;
365 try {
366 if(relativeTime)
367 ev.timestamp = timeStamp = lastTimestamp = Long.parseLong(components[0]) + lastTimestamp;
368 else
369 ev.timestamp = timeStamp = lastTimestamp = Long.parseLong(components[0]);
370 if(timeStamp < 0)
371 throw new IOException("Negative timestamp value " + timeStamp + " not allowed");
372 } catch(NumberFormatException e) {
373 throw new IOException("Invalid timestamp value \"" + components[0] + "\"");
375 if(last != null && timeStamp < last.timestamp)
376 throw new IOException("Timestamp order violation: " + timeStamp + "<" + last.timestamp);
377 String clazzName = components[1];
378 if(clazzName.equals("SAVESTATE")) {
379 if(components.length < 3 || components.length > 4)
380 throw new IOException("Malformed SAVESTATE line");
381 ev.magic = EVENT_MAGIC_SAVESTATE;
382 ev.clazz = null;
383 if(components.length == 3)
384 ev.args = new String[]{components[2], "0"};
385 else
386 ev.args = new String[]{components[2], components[3]};
387 } else if(clazzName.equals("OPTION")) {
388 if(components.length != 3)
389 throw new IOException("Malformed OPTION line");
390 if("RELATIVE".equals(components[2]))
391 relativeTime = true;
392 else if("ABSOLUTE".equals(components[2]))
393 relativeTime = false;
394 else
395 throw new IOException("Unknown OPTION: '" + components[2] + "'");
396 ev.magic = EVENT_MAGIC_NONE;
397 } else if(isReservedName(clazzName)) {
398 throw new IOException("Unknown special event type: '" + clazzName + "'");
399 } else {
400 //Something dispatchable.
401 ev.magic = EVENT_MAGIC_CLASS;
402 Class<?> clazz;
403 try {
404 clazz = Class.forName(clazzName);
405 if(!EventDispatchTarget.class.isAssignableFrom(clazz))
406 throw new Exception("bad class");
407 if(!HardwareComponent.class.isAssignableFrom(clazz))
408 throw new Exception("bad class");
409 } catch(Exception e) {
410 throw new IOException("\"" + clazzName + "\" is not valid event target");
412 ev.clazz = clazz.asSubclass(HardwareComponent.class);
413 if(components.length == 2)
414 ev.args = null;
415 else {
416 ev.args = new String[components.length - 2];
417 System.arraycopy(components, 2, ev.args, 0, ev.args.length);
421 if(ev.magic != EVENT_MAGIC_NONE) {
422 ev.prev = last;
423 //Sequence number for first event is 0 and then increments by 1 for each event.
424 ev.sequenceNumber = ((last != null) ? (last.sequenceNumber + 1) : 0);
425 if(last == null)
426 first = ev;
427 else
428 last.next = ev;
429 last = ev;
431 components = nextParseLine(lines);
434 firstUndispatched = null;
435 lastUndispatched = null;
436 directMode = true;
439 private void iterateIncrementSequence(Event from)
441 while(from != null) {
442 from.sequenceNumber++;
443 from = from.next;
447 public void markSave(String id, long rerecords) throws IOException
449 if(!isDirty())
450 rerecords = savestateRerecordCount;
451 else
452 savestateRerecordCount = rerecords;
453 /* Current is next event to dispatch. So add it before it. Null means add to
454 end. */
455 Event ev = new Event();
456 ev.timestamp = sysClock.getTime();
457 ev.magic = EVENT_MAGIC_SAVESTATE;
458 ev.clazz = null;
459 ev.args = new String[]{id, (new Long(rerecords)).toString()};
460 ev.next = current;
461 if(current != null) {
462 //Give it current's sequence number and increment the rest of sequence numbers.
463 ev.sequenceNumber = current.sequenceNumber;
464 iterateIncrementSequence(current);
465 ev.prev = current.prev;
466 if(ev.prev != null)
467 ev.prev.next = ev;
468 current.prev = ev;
469 ev.next = current;
470 } else {
471 //Sequence number for first event is 0 and then increments by 1 for each event.
472 ev.sequenceNumber = ((last != null) ? (last.sequenceNumber + 1) : 0);
473 ev.prev = last;
474 if(last != null)
475 last.next = ev;
476 last = ev;
478 if(ev.prev == null) {
479 first = ev;
481 setClean();
484 public void attach(PC aPC, String id) throws IOException
486 Event oldCurrent = current;
488 Clock newSysClock = (Clock)aPC.getComponent(Clock.class);
489 long expectedTime = newSysClock.getTime();
490 long rerecordCount = 0;
491 if(id == null) {
492 current = first;
493 } else {
494 Event scan = first;
495 while(scan != null) {
496 if(scan.magic == EVENT_MAGIC_SAVESTATE && scan.args[0].equals(id)) {
497 try {
498 if(scan.args.length > 1)
499 rerecordCount = Long.parseLong(scan.args[1]);
500 if(rerecordCount < 0)
501 throw new NumberFormatException("Negative rerecord count not allowed");
502 } catch(NumberFormatException e) {
503 throw new IOException("Savestate rerecord count invalid");
505 break;
507 scan = scan.next;
509 if(scan == null)
510 throw new IOException("Savestate not compatible with event stream");
512 if(scan.timestamp != expectedTime)
513 throw new IOException("Incorrect savestate event timestamp");
515 current = scan;
518 try {
519 Event scan = first;
520 dispatchStart(aPC);
521 boolean future = false;
522 while(scan != null) {
523 if(scan == current)
524 future = true;
525 if(future)
526 scan.dispatch(aPC, EVENT_STATE_EFFECT_FUTURE);
527 else
528 scan.dispatch(aPC, EVENT_STATE_EFFECT);
529 scan = scan.next;
531 dispatchEnd(aPC);
532 } catch(IOException e) {
533 //Back off the changes.
534 current = oldCurrent;
535 throw e;
538 pc = aPC;
539 sysClock = newSysClock;
540 sysTimer = sysClock.newTimer(this);
541 directMode = true; //Assume direct mode on attach.
542 timerInvokeTime = -1; //No wait in progress.
543 savestateRerecordCount = rerecordCount;
544 attachTime = expectedTime;
545 setClean();
547 handleUndispatchedEvents(); //Do the events that occur after and simultaneously with savestate.
550 public void saveEvents(UTFOutputLineStream lines) throws IOException
552 Event scan = first;
553 long lastTimestamp = 0;
554 lines.encodeLine("0", "OPTION", "RELATIVE");
555 while(scan != null) {
556 if(scan.magic == EVENT_MAGIC_SAVESTATE) {
557 lines.encodeLine(scan.timestamp - lastTimestamp, "SAVESTATE", scan.args[0], scan.args[1]);
558 } else {
559 int extra = (scan.args != null) ? scan.args.length : 0;
560 Object[] arr = new Object[2 + extra];
561 arr[0] = new Long(scan.timestamp - lastTimestamp);
562 arr[1] = scan.clazz.getName();
563 if(extra > 0)
564 System.arraycopy(scan.args, 0, arr, 2, extra);
565 lines.encodeLine(arr);
567 lastTimestamp = scan.timestamp;
568 scan = scan.next;
572 public long getLastEventTime()
574 Event scan = first;
575 long lastTimestamp = 0;
576 while(scan != null) {
577 if(scan.magic == EVENT_MAGIC_CLASS)
578 lastTimestamp = scan.timestamp;
579 scan = scan.next;
581 return lastTimestamp;
584 public boolean isAtMovieEnd()
586 return (current == null);
589 public synchronized long getEventCount()
591 return (last != null) ? (last.sequenceNumber + 1) : 0;
594 public synchronized long getEventCurrentSequence()
596 return (current != null) ? current.sequenceNumber : -1;
599 private String getReturnClass(Event ev)
601 if(ev.magic == EVENT_MAGIC_CLASS)
602 return ev.clazz.getName();
603 else if(ev.magic == EVENT_MAGIC_SAVESTATE)
604 return "SAVESTATE";
605 else
606 return "<BAD EVENT TYPE>";
609 private ReturnEvent convertToReturn(Event ev)
611 ReturnEvent evr = new ReturnEvent();
612 evr.timestamp = ev.timestamp;
613 if(ev.args == null)
614 evr.eventData = new String[1];
615 else {
616 evr.eventData = new String[1 + ev.args.length];
617 System.arraycopy(ev.args, 0, evr.eventData, 1, ev.args.length);
619 evr.eventData[0] = getReturnClass(ev);
620 return evr;
623 private int sMinFour(long a, long b, long c, long d)
625 //Compute maximum, ignoring -'s.
626 long max = a;
627 max = (b > max) ? b : max;
628 max = (c > max) ? c : max;
629 max = (d > max) ? d : max;
630 if(max < 0)
631 return -1;
633 //Now use max to get rid of -'s.
634 a = (a >= 0) ? a : (max + 1);
635 b = (b >= 0) ? b : (max + 1);
636 c = (c >= 0) ? c : (max + 1);
637 d = (d >= 0) ? d : (max + 1);
639 long min = a;
640 int mpos = 0;
641 if(b < min) {
642 min = b;
643 mpos = 1;
645 if(c < min) {
646 min = c;
647 mpos = 2;
649 if(d < min) {
650 min = d;
651 mpos = 2;
653 return mpos;
656 public synchronized ReturnEvent getEventBySequence(long sequence)
658 if(sequence < 0 || last == null || sequence > last.sequenceNumber)
659 return null;
661 long distFirst = -1;
662 long distLast = -1;
663 long distCurrent = -1;
664 long distCache = -1;
666 distFirst = Math.abs(first.sequenceNumber - sequence);
667 distLast = Math.abs(first.sequenceNumber - sequence);
668 if(current != null)
669 distCurrent = Math.abs(current.sequenceNumber - sequence);
670 if(cache != null)
671 distCache = Math.abs(cache.sequenceNumber - sequence);
673 //Find the nearest entrypoint.
674 switch(sMinFour(distFirst, distLast, distCurrent, distCache)) {
675 case 0:
676 cache = first;
677 break;
678 case 1:
679 cache = last;
680 break;
681 case 2:
682 cache = current;
683 break;
684 case 3:
685 //Cache = cache;
686 break;
687 default:
688 return null; //Can't happen.
691 while(sequence < cache.sequenceNumber)
692 cache = cache.prev;
693 while(sequence > cache.sequenceNumber)
694 cache = cache.next;
695 return convertToReturn(cache);
698 public void callback()
700 handleUndispatchedEvents();
703 public int getTimerType()
705 return 17;
708 public void dumpStatus(StatusDumper output)
712 public void dumpSRPartial(SRDumper output)