Don't crash if Lua asks for an invalid event
[jpcrr.git] / org / jpc / emulator / EventRecorder.java
blobd01b3f183685d75bb9463f69cb88d994f9ab30ff
1 /*
2 JPC-RR: A x86 PC Hardware Emulator
3 Release 1
5 Copyright (C) 2007-2009 Isis Innovation Limited
6 Copyright (C) 2009-2011 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 java.math.BigInteger;
38 import static org.jpc.Misc.errorDialog;
40 public class EventRecorder implements TimerResponsive
42 private static final int EVENT_MAGIC_CLASS = 0;
43 private static final int EVENT_MAGIC_SAVESTATE = 1;
44 private static final int EVENT_MAGIC_NONE = -1;
46 public static final int EVENT_TIMED = 0;
47 public static final int EVENT_STATE_EFFECT_FUTURE = 1;
48 public static final int EVENT_STATE_EFFECT = 2;
49 public static final int EVENT_EXECUTE = 3;
51 public class ReturnEvent
53 public long timestamp;
54 public String[] eventData;
57 public class Event
59 public long timestamp; //Event timestamp (low bound)
60 public long sequenceNumber; //Sequence number.
61 public int magic; //Magic type.
62 public Class<? extends HardwareComponent> clazz; //Dispatch to where.
63 public String[] args; //Arguments to dispatch.
64 public Event prev; //Previous event.
65 public Event next; //Next event.
67 public void dispatch(PC target, int level) throws IOException
69 if(magic != EVENT_MAGIC_CLASS)
70 return; //We really don't want to dispatch these.
72 HardwareComponent hwc = target.getComponent(clazz);
73 if(hwc == null)
74 throw new IOException("Invalid event target \"" + clazz.getName() + "\": no component of such type");
75 EventDispatchTarget component = (EventDispatchTarget)hwc;
76 component.doEvent(timestamp, args, level);
80 private Event first;
81 private Event current;
82 private Event last;
83 private Event cache;
84 private Event firstUndispatched;
85 private Event lastUndispatched;
86 private PC pc;
87 private boolean directMode;
88 private Clock sysClock;
89 private Timer sysTimer;
90 private long timerInvokeTime;
91 private BigInteger savestateRerecordCount;
92 private boolean dirtyFlag;
93 private long cleanTime;
94 private long attachTime;
95 private String[][] headers;
96 private BigInteger movieRerecordCount;
97 private String projectID;
99 public String getProjectID()
101 return projectID;
104 public void setProjectID(String proj)
106 projectID = proj;
109 private boolean isDirty()
111 //sysClock == null should not happen, but include it just for sure.
112 return (dirtyFlag || sysClock == null || cleanTime != sysClock.getTime());
115 private void setClean()
117 dirtyFlag = false;
118 cleanTime = (sysClock != null) ? sysClock.getTime() : -1;
121 public long getAttachTime()
123 return attachTime;
126 public BigInteger getRerecordCount()
128 return movieRerecordCount.subtract(savestateRerecordCount);
131 public void setRerecordCount(BigInteger newCount)
133 movieRerecordCount = newCount;
136 public String[][] getHeaders()
138 return headers;
141 public void setHeaders(String[][] newHeaders)
143 if(newHeaders == null) {
144 headers = null;
145 return;
148 String[][] tmp = new String[newHeaders.length][];
149 for(int i = 0; i < tmp.length; i++)
150 if(newHeaders[i] != null)
151 tmp[i] = Arrays.copyOf(newHeaders[i], newHeaders[i].length);
153 headers = tmp;
156 public void setTimer(long time)
158 if(directMode) {
159 sysTimer.disable(); //No need for timers in direct mode.
160 return;
162 if((sysTimer.enabled() && timerInvokeTime <= time))
163 return; //No need for timer.
164 sysTimer.setExpiry(timerInvokeTime = time);
167 public void addEvent(long timeLowBound, Class<? extends HardwareComponent> clazz,
168 String[] args) throws IOException
170 /* Compute the final time for event. */
171 long timeNow = sysClock.getTime();
172 synchronized(this) {
173 long time = timeLowBound;
174 if(last != null && time < last.timestamp)
175 time = last.timestamp;
176 if(time < timeNow)
177 time = timeNow;
179 HardwareComponent hwc = pc.getComponent(clazz);
180 EventDispatchTarget component = (EventDispatchTarget)hwc;
181 long freeLowBound = -1;
182 try {
183 freeLowBound = component.getEventTimeLowBound(time, args);
184 } catch(Exception e) {}; //Shouldn't throw.
185 if(time < freeLowBound)
186 time = freeLowBound;
188 Event ev = new Event();
189 ev.timestamp = time;
190 ev.magic = EVENT_MAGIC_CLASS;
191 ev.clazz = clazz;
192 ev.args = args;
194 if(firstUndispatched == null)
195 firstUndispatched = ev;
196 if(lastUndispatched != null)
197 lastUndispatched.next = ev;
198 ev.prev = lastUndispatched;
199 lastUndispatched = ev;
202 if(directMode) {
203 handleUndispatchedEvents();
204 } else {
205 setTimer(timeNow); //Fire it as soon as possible.
210 private void handleUndispatchedEvents()
212 //This has toi be outside or we can have ABBA deadlock.
213 long timeNow = sysClock.getTime();
215 //Synchronize to prevent racing with addEvent()
216 synchronized(this) {
217 //First move undispatched events to main queue.
218 Event scan = firstUndispatched;
219 while(scan != null) {
220 dirtyFlag = true;
221 //Compute time for event.
222 Event scanNext = scan.next;
223 if(scan.timestamp < timeNow)
224 scan.timestamp = timeNow;
225 if(last != null && scan.timestamp < last.timestamp)
226 scan.timestamp = last.timestamp;
228 HardwareComponent hwc = pc.getComponent(scan.clazz);
229 EventDispatchTarget component = (EventDispatchTarget)hwc;
230 long freeLowBound = -1;
231 try {
232 freeLowBound = component.getEventTimeLowBound(scan.timestamp, scan.args);
233 } catch(Exception e) {}; //Shouldn't throw.
234 if(scan.timestamp < freeLowBound)
235 scan.timestamp = freeLowBound;
237 try {
238 scan.dispatch(pc, EVENT_TIMED);
239 } catch(Exception e) {
240 System.err.println("Error: Event dispatch failed.");
241 errorDialog(e, "Failed to dispatch event", null, "Dismiss");
242 scan = null;
245 //Because of constraints to time, the event must go last.
246 if(scan != null) {
247 //Sequence number for first event is 0 and then increments by 1 for each event.
248 scan.sequenceNumber = ((last != null) ? (last.sequenceNumber + 1) : 0);
249 scan.next = null;
250 scan.prev = last;
251 if(last != null)
252 last.next = scan;
253 last = scan;
254 if(current == null)
255 current = scan;
256 if(first == null)
257 first = scan;
259 scan = scanNext;
261 firstUndispatched = null;
262 lastUndispatched = null;
265 //Then fire apporiate events from main queue.
266 while(current != null && current.timestamp <= timeNow) {
267 try {
268 current.dispatch(pc, EVENT_EXECUTE);
269 } catch(Exception e) {
270 System.err.println("Error: Event dispatch failed.");
271 errorDialog(e, "Failed to dispatch event", null, "Dismiss");
273 current = current.next;
275 if(current != null)
276 setTimer(current.timestamp);
279 public synchronized void setPCRunStatus(boolean running)
281 directMode = !running;
282 if(directMode)
283 handleUndispatchedEvents();
284 if(current != null)
285 setTimer(current.timestamp);
288 public void truncateEventStream()
290 if(current != null) {
291 dirtyFlag = true;
292 if(cache != null && current.sequenceNumber <= cache.sequenceNumber)
293 cache = null; //Flush cache as event got truncated out.
294 last = current.prev;
295 current = null;
296 if(last != null)
297 last.next = null;
298 else
299 first = null;
300 Event scan = first;
301 dispatchStart(pc);
302 while(scan != null) {
303 try {
304 scan.dispatch(pc, EVENT_STATE_EFFECT);
305 } catch(Exception e) {}
306 scan = scan.next;
308 try {
309 dispatchEnd(pc);
310 } catch(Exception e) {}
312 firstUndispatched = null;
313 lastUndispatched = null;
316 private void dispatchStart(PC target)
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.setEventRecorder(this);
324 t.startEventCheck();
328 private void dispatchEnd(PC target) throws IOException
330 Set<HardwareComponent> pcParts = target.allComponents();
331 for(HardwareComponent hwc : pcParts) {
332 if(!EventDispatchTarget.class.isAssignableFrom(hwc.getClass()))
333 continue;
334 EventDispatchTarget t = (EventDispatchTarget)hwc;
335 t.endEventCheck();
339 public EventRecorder()
341 first = null;
342 current = null;
343 last = null;
344 cache = null;
345 firstUndispatched = null;
346 lastUndispatched = null;
347 directMode = true;
348 dirtyFlag = true;
349 cleanTime = -1;
350 savestateRerecordCount = new BigInteger("0");
351 movieRerecordCount = new BigInteger("0");
354 private static boolean isReservedName(String name)
356 for(int i = 0; i < name.length(); i++) {
357 char j = name.charAt(i);
358 if(j >= '0' && j <= '9')
359 continue;
360 if(j >= 'A' && j <= 'Z')
361 continue;
362 return false;
364 return true;
367 public EventRecorder(UTFInputLineStream lines) throws IOException
369 boolean relativeTime = false;
370 long lastTimestamp = 0;
371 dirtyFlag = true;
372 cleanTime = -1;
373 String[] components = nextParseLine(lines);
374 while(components != null) {
375 Event ev = new Event();
376 if(components.length < 2)
377 throw new IOException("Malformed event line");
378 long timeStamp;
379 try {
380 if(relativeTime)
381 ev.timestamp = timeStamp = lastTimestamp = Long.parseLong(components[0]) + lastTimestamp;
382 else
383 ev.timestamp = timeStamp = lastTimestamp = Long.parseLong(components[0]);
384 if(timeStamp < 0)
385 throw new IOException("Negative timestamp value " + timeStamp + " not allowed");
386 } catch(NumberFormatException e) {
387 throw new IOException("Invalid timestamp value \"" + components[0] + "\"");
389 if(last != null && timeStamp < last.timestamp)
390 throw new IOException("Timestamp order violation: " + timeStamp + "<" + last.timestamp);
391 String clazzName = components[1];
392 if(clazzName.equals("SAVESTATE")) {
393 if(components.length < 3 || components.length > 4)
394 throw new IOException("Malformed SAVESTATE line");
395 ev.magic = EVENT_MAGIC_SAVESTATE;
396 ev.clazz = null;
397 if(components.length == 3)
398 ev.args = new String[]{components[2], "0"};
399 else
400 ev.args = new String[]{components[2], components[3]};
401 } else if(clazzName.equals("OPTION")) {
402 if(components.length != 3)
403 throw new IOException("Malformed OPTION line");
404 if("RELATIVE".equals(components[2]))
405 relativeTime = true;
406 else if("ABSOLUTE".equals(components[2]))
407 relativeTime = false;
408 else
409 throw new IOException("Unknown OPTION: '" + components[2] + "'");
410 ev.magic = EVENT_MAGIC_NONE;
411 } else if(isReservedName(clazzName)) {
412 throw new IOException("Unknown special event type: '" + clazzName + "'");
413 } else {
414 //Something dispatchable.
415 ev.magic = EVENT_MAGIC_CLASS;
416 Class<?> clazz;
417 try {
418 clazz = Class.forName(clazzName);
419 if(!EventDispatchTarget.class.isAssignableFrom(clazz))
420 throw new Exception("bad class");
421 if(!HardwareComponent.class.isAssignableFrom(clazz))
422 throw new Exception("bad class");
423 } catch(Exception e) {
424 throw new IOException("\"" + clazzName + "\" is not valid event target");
426 ev.clazz = clazz.asSubclass(HardwareComponent.class);
427 if(components.length == 2)
428 ev.args = null;
429 else {
430 ev.args = new String[components.length - 2];
431 System.arraycopy(components, 2, ev.args, 0, ev.args.length);
435 if(ev.magic != EVENT_MAGIC_NONE) {
436 ev.prev = last;
437 //Sequence number for first event is 0 and then increments by 1 for each event.
438 ev.sequenceNumber = ((last != null) ? (last.sequenceNumber + 1) : 0);
439 if(last == null)
440 first = ev;
441 else
442 last.next = ev;
443 last = ev;
445 components = nextParseLine(lines);
448 firstUndispatched = null;
449 lastUndispatched = null;
450 directMode = true;
453 private void iterateIncrementSequence(Event from)
455 while(from != null) {
456 from.sequenceNumber++;
457 from = from.next;
461 public void markSave(String id, BigInteger rerecords) throws IOException
463 if(!isDirty())
464 rerecords = savestateRerecordCount;
465 else
466 savestateRerecordCount = rerecords;
467 /* Current is next event to dispatch. So add it before it. Null means add to
468 end. */
469 Event ev = new Event();
470 ev.timestamp = sysClock.getTime();
471 ev.magic = EVENT_MAGIC_SAVESTATE;
472 ev.clazz = null;
473 ev.args = new String[]{id, rerecords.toString()};
474 ev.next = current;
475 if(current != null) {
476 //Give it current's sequence number and increment the rest of sequence numbers.
477 ev.sequenceNumber = current.sequenceNumber;
478 iterateIncrementSequence(current);
479 ev.prev = current.prev;
480 if(ev.prev != null)
481 ev.prev.next = ev;
482 current.prev = ev;
483 ev.next = current;
484 } else {
485 //Sequence number for first event is 0 and then increments by 1 for each event.
486 ev.sequenceNumber = ((last != null) ? (last.sequenceNumber + 1) : 0);
487 ev.prev = last;
488 if(last != null)
489 last.next = ev;
490 last = ev;
492 if(ev.prev == null) {
493 first = ev;
495 setClean();
498 public void attach(PC aPC, String id) throws IOException
500 Event oldCurrent = current;
502 Clock newSysClock = (Clock)aPC.getComponent(Clock.class);
503 long expectedTime = newSysClock.getTime();
504 BigInteger rerecordCount = new BigInteger("0");
505 if(id == null) {
506 current = first;
507 } else {
508 Event scan = first;
509 while(scan != null) {
510 if(scan.magic == EVENT_MAGIC_SAVESTATE && scan.args[0].equals(id)) {
511 try {
512 if(scan.args.length > 1) {
513 rerecordCount = new BigInteger(scan.args[1]);
514 if(rerecordCount.signum() < 0)
515 throw new NumberFormatException("Negative rerecord count not allowed");
517 } catch(NumberFormatException e) {
518 throw new IOException("Savestate rerecord count invalid");
520 break;
522 scan = scan.next;
524 if(scan == null)
525 throw new IOException("Savestate not compatible with event stream");
527 if(scan.timestamp != expectedTime)
528 throw new IOException("Incorrect savestate event timestamp");
530 current = scan;
533 try {
534 Event scan = first;
535 dispatchStart(aPC);
536 boolean future = false;
537 while(scan != null) {
538 if(scan == current)
539 future = true;
540 if(future)
541 scan.dispatch(aPC, EVENT_STATE_EFFECT_FUTURE);
542 else
543 scan.dispatch(aPC, EVENT_STATE_EFFECT);
544 scan = scan.next;
546 dispatchEnd(aPC);
547 } catch(IOException e) {
548 //Back off the changes.
549 current = oldCurrent;
550 throw e;
553 pc = aPC;
554 sysClock = newSysClock;
555 sysTimer = sysClock.newTimer(this);
556 directMode = true; //Assume direct mode on attach.
557 timerInvokeTime = -1; //No wait in progress.
558 savestateRerecordCount = rerecordCount;
559 attachTime = expectedTime;
560 setClean();
562 handleUndispatchedEvents(); //Do the events that occur after and simultaneously with savestate.
565 public void saveEvents(UTFOutputLineStream lines) throws IOException
567 Event scan = first;
568 long lastTimestamp = 0;
569 lines.encodeLine("0", "OPTION", "RELATIVE");
570 while(scan != null) {
571 if(scan.magic == EVENT_MAGIC_SAVESTATE) {
572 lines.encodeLine(scan.timestamp - lastTimestamp, "SAVESTATE", scan.args[0], scan.args[1]);
573 } else {
574 int extra = (scan.args != null) ? scan.args.length : 0;
575 Object[] arr = new Object[2 + extra];
576 arr[0] = new Long(scan.timestamp - lastTimestamp);
577 arr[1] = scan.clazz.getName();
578 if(extra > 0)
579 System.arraycopy(scan.args, 0, arr, 2, extra);
580 lines.encodeLine(arr);
582 lastTimestamp = scan.timestamp;
583 scan = scan.next;
587 public long getLastEventTime()
589 Event scan = first;
590 long lastTimestamp = 0;
591 while(scan != null) {
592 if(scan.magic == EVENT_MAGIC_CLASS)
593 lastTimestamp = scan.timestamp;
594 scan = scan.next;
596 return lastTimestamp;
599 public boolean isAtMovieEnd()
601 return (current == null);
604 public synchronized long getEventCount()
606 return (last != null) ? (last.sequenceNumber + 1) : 0;
609 public synchronized long getEventCurrentSequence()
611 return (current != null) ? current.sequenceNumber : -1;
614 private String getReturnClass(Event ev)
616 if(ev.magic == EVENT_MAGIC_CLASS)
617 return ev.clazz.getName();
618 else if(ev.magic == EVENT_MAGIC_SAVESTATE)
619 return "SAVESTATE";
620 else
621 return "<BAD EVENT TYPE>";
624 private ReturnEvent convertToReturn(Event ev)
626 ReturnEvent evr = new ReturnEvent();
627 evr.timestamp = ev.timestamp;
628 if(ev.args == null)
629 evr.eventData = new String[1];
630 else {
631 evr.eventData = new String[1 + ev.args.length];
632 System.arraycopy(ev.args, 0, evr.eventData, 1, ev.args.length);
634 evr.eventData[0] = getReturnClass(ev);
635 return evr;
638 private int sMinFour(long a, long b, long c, long d)
640 //Compute maximum, ignoring -'s.
641 long max = a;
642 max = (b > max) ? b : max;
643 max = (c > max) ? c : max;
644 max = (d > max) ? d : max;
645 if(max < 0)
646 return -1;
648 //Now use max to get rid of -'s.
649 a = (a >= 0) ? a : (max + 1);
650 b = (b >= 0) ? b : (max + 1);
651 c = (c >= 0) ? c : (max + 1);
652 d = (d >= 0) ? d : (max + 1);
654 long min = a;
655 int mpos = 0;
656 if(b < min) {
657 min = b;
658 mpos = 1;
660 if(c < min) {
661 min = c;
662 mpos = 2;
664 if(d < min) {
665 min = d;
666 mpos = 2;
668 return mpos;
671 public synchronized ReturnEvent getEventBySequence(long sequence)
673 if(sequence < 0 || last == null || sequence > last.sequenceNumber)
674 return null;
676 long distFirst = -1;
677 long distLast = -1;
678 long distCurrent = -1;
679 long distCache = -1;
681 distFirst = Math.abs(first.sequenceNumber - sequence);
682 distLast = Math.abs(first.sequenceNumber - sequence);
683 if(current != null)
684 distCurrent = Math.abs(current.sequenceNumber - sequence);
685 if(cache != null)
686 distCache = Math.abs(cache.sequenceNumber - sequence);
688 //Find the nearest entrypoint.
689 switch(sMinFour(distFirst, distLast, distCurrent, distCache)) {
690 case 0:
691 cache = first;
692 break;
693 case 1:
694 cache = last;
695 break;
696 case 2:
697 cache = current;
698 break;
699 case 3:
700 //Cache = cache;
701 break;
702 default:
703 return null; //Can't happen.
706 while(cache != null && sequence < cache.sequenceNumber)
707 cache = cache.prev;
708 while(cache != null && sequence > cache.sequenceNumber)
709 cache = cache.next;
710 if(cache == null)
711 return null;
712 return convertToReturn(cache);
715 public void callback()
717 handleUndispatchedEvents();
720 public int getTimerType()
722 return 17;
725 public void dumpStatus(StatusDumper output)
729 public void dumpSRPartial(SRDumper output)