package simulator; import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.awt.image.*; import java.util.*; import javax.swing.*; import core.*; import core.time.*; import events.*; import events.implementations.*; import events.internal.*; import prefs.*; import prefs.editors.*; import utils.*; public class VSSimulationCanvas extends Canvas implements Runnable, MouseMotionListener, MouseListener, HierarchyBoundsListener { private VSProcess highlightedProcess; private VSSimulation simulation; private VSPrefs prefs; private VSLogging logging; private int numProcesses; private int secondsSpaceing; private int threadSleep; private long untilTime; private volatile boolean isPaused = true; private volatile boolean isThreadStopped = false; private volatile boolean isFinished = false; private volatile boolean isResetted = false; private volatile boolean isAntiAliased = false; private volatile boolean isAntiAliasedChanged = false; private volatile boolean showLamport = false; private volatile boolean showVectorTime = false; private volatile long pauseTime; private volatile long startTime; private volatile long time; private volatile long lastTime; private VSTaskManager taskManager; private LinkedList messageLines; private LinkedList processes; private double clockSpeed; private double clockOffset; private long simulationTime; /* GFX buffering */ private BufferStrategy strategy; private Graphics2D g; /* Static constats */ private static final int LINE_WIDTH = 5; private static final int SEPLINE_WIDTH = 2; private static final int XOFFSET = 50; private static final int YOFFSET = 30; private static final int YOUTER_SPACEING = 15; private static final int YSEPLINE_SPACEING = 20; private static final int TEXT_SPACEING = 10; private static final int ROW_HEIGHT = 14; /* Constats, which have to get calculated once after start */ private Color processlineColor; private Color processSecondlineColor; private Color processSeplineColor; private Color messageArrivedColor; private Color messageSendingColor; private Color messageLostColor; private class VSMessageLine { private VSProcess receiverProcess; private Color color; private long sendTime; private long recvTime; private int senderNum; private int receiverNum; private int offset1; private int offset2; private boolean isArrived; private boolean isLost; private double x1; private double y1; private double x2; private double y2; private double x; private double y; private long outageTime; private long z; public VSMessageLine(VSProcess receiverProcess, long sendTime, long recvTime, long outageTime, int senderNum , int receiverNum) { this.receiverProcess = receiverProcess; this.sendTime = sendTime; this.recvTime = recvTime; this.outageTime = outageTime; this.senderNum = senderNum; this.receiverNum = receiverNum; this.isArrived = false; this.isLost = false; if (senderNum > receiverNum) { //offset1 = 1; offset2 = LINE_WIDTH; } else { offset1 = LINE_WIDTH - 1; //offset2 = 1; } /* Needed if the message gets lost after 0ms */ this.x = getTimeXPosition(sendTime); this.y = getProcessYPosition(senderNum) + offset1; recalcOnWindowChanged(); paint(); } public void recalcOnWindowChanged() { x1 = getTimeXPosition(sendTime); y1 = getProcessYPosition(senderNum) + offset1; x2 = getTimeXPosition(recvTime); y2 = getProcessYPosition(receiverNum) + offset2; if (isLost) { x = getTimeXPosition(z); y = y1 + ( ( (y2-y1) / (x2-x1)) * (x-x1)); } } public void draw(final Graphics2D g, final long globalTime) { if (isArrived) { g.setColor(color); g.drawLine((int) x1, (int) y1, (int) x2, (int) y2); } else if (isLost) { g.setColor(messageLostColor); g.drawLine((int) x1, (int) y1, (int) x, (int) y); } else if (globalTime >= recvTime) { isArrived = true; if (receiverProcess.isCrashed()) color = messageLostColor; else color = messageArrivedColor; draw(g, globalTime); } else if (outageTime >= 0 && outageTime <= globalTime){ isLost = true; draw(g, globalTime);; } else { z = globalTime; x = globalTimeXPosition; y = y1 + ( ( (y2-y1) / (x2-x1)) * (x-x1)); g.setColor(messageSendingColor); g.drawLine((int) x1, (int) y1, (int) x, (int) y); } } } public VSSimulationCanvas(VSPrefs prefs, VSSimulation simulation, VSLogging logging) { this.prefs = prefs; this.simulation = simulation; this.logging = logging; this.taskManager = new VSTaskManager(prefs); this.messageLines = new LinkedList(); this.processes = new LinkedList(); numProcesses = prefs.getInteger("sim.process.num"); updateFromPrefs(); VSProcess.resetProcessCounter(); for (int i = 0; i < numProcesses; ++i) createProcess(i); addMouseListener(this); addMouseMotionListener(this); addHierarchyBoundsListener(this); } double xPaintSize; double paintSize; double yDistance; double globalTimeXPosition; int xoffset_plus_xpaintsize; double xpaintsize_dividedby_untiltime; int paintProcessesOffset; int paintSecondlinesSeconds; int paintSecondlinesLine[] = new int[4]; int paintSecondlinesYStringPos1; int paintSecondlinesYStringPos2; int paintGlobalTimeYPosition; /* This method contains very ugly code. But this has to be in order to gain performance! */ private void recalcOnWindowChanged() { processlineColor = prefs.getColor("col.process.line"); processSecondlineColor = prefs.getColor("col.process.secondline"); processSeplineColor = prefs.getColor("col.process.sepline"); messageArrivedColor = prefs.getColor("col.message.arrived"); messageSendingColor = prefs.getColor("col.message.sending"); messageLostColor = prefs.getColor("col.message.lost"); paintSize = simulation.getPaintSize(); xPaintSize = simulation.getWidth() - (3 * XOFFSET + simulation.getSplitSize()); yDistance = (simulation.getPaintSize() - 2 * (YOFFSET + YOUTER_SPACEING))/ numProcesses; xpaintsize_dividedby_untiltime = xPaintSize / (double) untilTime; for (VSMessageLine messageLine : messageLines) messageLine.recalcOnWindowChanged(); /* paintProcesses optimization, precalc things */ { xoffset_plus_xpaintsize = XOFFSET + (int) xPaintSize; if (numProcesses > 1) paintProcessesOffset = (int) ((paintSize-2*(YOFFSET+YOUTER_SPACEING+YSEPLINE_SPACEING))/(numProcesses-1)); else paintProcessesOffset = (int) ((paintSize-2*(YOFFSET+YOUTER_SPACEING+YSEPLINE_SPACEING))); } /* paintSecondlines optimization, precalc things */ { int yMax = YOFFSET + YOUTER_SPACEING + (int) (numProcesses * yDistance); paintSecondlinesSeconds = (int) untilTime / 1000; paintSecondlinesLine[1] = YOFFSET; paintSecondlinesLine[3] = yMax; paintSecondlinesYStringPos1 = paintSecondlinesLine[1] - 5; paintSecondlinesYStringPos2 = paintSecondlinesLine[3] + 15; } /* paitnGlobalTime optimization, precalc things */ { paintGlobalTimeYPosition = YOFFSET + YOUTER_SPACEING + (int) (numProcesses * yDistance); } if (strategy != null) { synchronized (strategy) { g = (Graphics2D) strategy.getDrawGraphics(); g.setColor(Color.WHITE); if (isAntiAliased) g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } } } public VSProcess createProcess(int i) { VSProcess process = new VSProcess(prefs, this, logging); processes.add(process); logging.logg(prefs.getString("lang.process.new") + "; " + process); return process; } private void updateSimulation(final long globalTime, final long lastGlobalTime) { if (isPaused || isFinished) return; final long lastSimulationTime = simulationTime; long offset = globalTime - lastGlobalTime; clockOffset += offset * clockSpeed; while (clockOffset >= 1) { --clockOffset; ++simulationTime; } if (simulationTime > untilTime) simulationTime = untilTime; offset = simulationTime - lastSimulationTime; for (long l = 0; l < offset; ++l) taskManager.runTasks(l, offset, lastSimulationTime); for (VSProcess process : processes) process.syncTime(simulationTime); } public void paint() { while (getBufferStrategy() == null) { createBufferStrategy(3); strategy = getBufferStrategy(); if (strategy != null) { g = (Graphics2D) strategy.getDrawGraphics(); g.setColor(Color.WHITE); } } synchronized (strategy) { if (isAntiAliasedChanged) { if (isAntiAliased) g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); else g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); isAntiAliasedChanged = false; } g.fillRect(0, 0, getWidth(), getHeight()); final long globalTime = simulationTime; globalTimeXPosition = getTimeXPosition(globalTime); paintSecondlines(g); paintProcesses(g, globalTime); paintGlobalTime(g, globalTime); synchronized (messageLines) { for (VSMessageLine line : messageLines) line.draw(g, globalTime); } g.setColor(Color.WHITE); strategy.show(); } } private void paintProcesses(Graphics2D g, long globalTime) { /* First paint the horizontal process timelines * Second paint the processes */ final int yOffset = YOFFSET + YOUTER_SPACEING + YSEPLINE_SPACEING; final int xPoints[] = { XOFFSET, xoffset_plus_xpaintsize, xoffset_plus_xpaintsize, XOFFSET, XOFFSET }; final int yPoints[] = { yOffset, yOffset, yOffset + LINE_WIDTH, yOffset + LINE_WIDTH, yOffset }; for (VSProcess process : processes) { final long localTime = process.getTime(); g.setColor(process.getColor()); g.fillPolygon(xPoints, yPoints, 5); if (process.hasCrashed()) { g.setColor(process.getCrashedColor()); final Long crashHistory[] = process.getCrashHistoryArray(); final int length = crashHistory.length; for (int i = 0; i < length; i += 2) { final int crashStartPos = (int) getTimeXPosition(crashHistory[i].longValue()); int crashEndPos; if (i == length - 1) crashEndPos = xoffset_plus_xpaintsize; else crashEndPos = (int) getTimeXPosition(crashHistory[i+1].longValue()); final int xPointsCrashed[] = { crashStartPos, crashEndPos, crashEndPos, crashStartPos, crashStartPos }; g.fillPolygon(xPointsCrashed, yPoints, 5); } } g.setColor(process.getColor()); g.drawString("P" + process.getProcessID() + ":", XOFFSET - 30, yPoints[0] + LINE_WIDTH); final long tmp = localTime > untilTime ? untilTime : localTime; final int xPos = 1 + (int) getTimeXPosition(tmp); final int yStart = yPoints[0] - 14; final int yEnd = yPoints[0]; g.setColor(processlineColor); g.drawLine(xPos, yStart, xPos, yEnd); g.drawString(localTime+"ms", xPos + 2, yStart + TEXT_SPACEING); if (showLamport) paintTime(g, process.getLamportTimeArray(), process, yStart, 25); else if (showVectorTime) paintTime(g, process.getVectorTimeArray(), process, yStart, 20 * numProcesses); for (int i = 0; i < 5; ++i) yPoints[i] += paintProcessesOffset; } } private void paintTime(final Graphics2D g, final VSTime times[], final VSProcess process, final int yStart, final int distance) { final int lastPos[] = { -1, -1, -1, -1 }; for (VSTime time : times) { int xPos = (int) getTimeXPosition(time.getGlobalTime()); int bestRow[] = { -1, -1 }; for (int i = 0; i < 4; ++i) { if (lastPos[i] != -1) { int diff = xPos - lastPos[i]; if (diff > distance) { bestRow[0] = i; bestRow[1] = -1; break; } else if (bestRow[0] == -1) { bestRow[0] = i; bestRow[1] = diff; } else if (diff > bestRow[1]) { bestRow[0] = i; bestRow[1] = diff; } } else { bestRow[0] = i; bestRow[1] = -1; break; } } final int row = bestRow[0]; if (bestRow[1] != -1) xPos += distance - bestRow[1]; g.drawString(time.toString(), xPos + 2, yStart + 3 * TEXT_SPACEING + row * ROW_HEIGHT); lastPos[row] = xPos; } } private void paintSecondlines(Graphics2D g) { g.setColor(processSecondlineColor); int i; for (i = 0; i <= paintSecondlinesSeconds; i += secondsSpaceing) { paintSecondlinesLine[0] = paintSecondlinesLine[2] = (int) getTimeXPosition(i*1000); g.drawLine(paintSecondlinesLine[0], paintSecondlinesLine[1], paintSecondlinesLine[2], paintSecondlinesLine[3]); final int xStringPos = paintSecondlinesLine[0] - 5; g.drawString(i+"s", xStringPos, paintSecondlinesYStringPos1); if (!showVectorTime && !showLamport) g.drawString(i+"s", xStringPos, paintSecondlinesYStringPos2); } if (i > paintSecondlinesSeconds) { paintSecondlinesLine[0] = paintSecondlinesLine[2] = (int) getTimeXPosition(untilTime); g.drawLine(paintSecondlinesLine[0], paintSecondlinesLine[1], paintSecondlinesLine[2], paintSecondlinesLine[3]); } } private void paintGlobalTime(Graphics2D g, long globalTime) { g.setColor(processSeplineColor); final int xOffset = (int) globalTimeXPosition; final int xPoints[] = { xOffset, xOffset + SEPLINE_WIDTH, xOffset + SEPLINE_WIDTH, xOffset, xOffset }; final int yOffset = YOFFSET - 8; final int yPoints[] = { yOffset, yOffset, paintGlobalTimeYPosition, paintGlobalTimeYPosition, yOffset }; g.fillPolygon(xPoints, yPoints, 5); g.drawString(globalTime+"ms", xPoints[1]+1, yPoints[0]+TEXT_SPACEING); } private VSProcess getProcessAtYPos(int yPos) { final int reachDistance = (int) (yDistance/3); int y = YOFFSET + YOUTER_SPACEING + YSEPLINE_SPACEING; int yOffset = numProcesses > 1 ? (int) ((paintSize-2*(YOFFSET+YOUTER_SPACEING+YSEPLINE_SPACEING))/(numProcesses-1)) : (int) ((paintSize-2*(YOFFSET+YOUTER_SPACEING+YSEPLINE_SPACEING))); //System.out.println("JO " + yPos + " " + yDistance + " " + yOffset); for (int i = 0; i < numProcesses; ++i) { if (yPos < y + reachDistance && yPos > y - reachDistance - LINE_WIDTH) return processes.get(i); y += yOffset; } return null; } private double getTimeXPosition(long time) { return XOFFSET + xpaintsize_dividedby_untiltime * time; } private int getProcessYPosition(int i) { int y; if (numProcesses > 1) y = (int) ((paintSize - 2 * (YOFFSET + YOUTER_SPACEING + YSEPLINE_SPACEING))/ (numProcesses-1)); else y = (int) ((paintSize - 2 * (YOFFSET + YOUTER_SPACEING + YSEPLINE_SPACEING))); return y * (i - 1) + YOFFSET + YOUTER_SPACEING + YSEPLINE_SPACEING; } public long getTime() { return simulationTime; } public long getUntilTime() { return untilTime; } public long getStartTime() { return startTime; } public VSTaskManager getTaskManager() { return taskManager; } public int getNumProcesses() { return numProcesses; } public VSProcess getProcess(int processNum) { if (processNum >= processes.size()) return null; return processes.get(processNum); } public void run() { while (true) { while (!isThreadStopped && (isPaused || isFinished || isResetted)) { try { Thread.sleep(100); paint(); } catch (Exception e) { System.out.println(e); } } if (isThreadStopped) break; /* Exit the thread */ while (!isPaused && !isThreadStopped) { try { Thread.sleep(threadSleep); } catch (Exception e) { System.out.println(e); } updateSimulation(time, lastTime); if (simulationTime == untilTime) { finish(); break; } paint(); lastTime = time; time = System.currentTimeMillis() - startTime; } updateSimulation(time, lastTime); paint(); } } public void play() { logging.logg(prefs.getString("lang.simulation.started")); final long currentTime = System.currentTimeMillis(); for (VSProcess p : processes) p.play(); if (isResetted) isResetted = false; else if (isFinished) isFinished = false; if (isPaused) { isPaused = false; startTime += currentTime - pauseTime; time = currentTime - startTime; } else { startTime = currentTime; time = 0; } paint(); } public void finish() { for (VSProcess p : processes) p.finish(); simulation.finish(); isFinished = true; logging.logg(prefs.getString("lang.simulation.finished")); paint(); } public void pause() { isPaused = true; for (VSProcess p : processes) p.pause(); pauseTime = System.currentTimeMillis(); logging.logg(prefs.getString("lang.simulation.paused")); paint(); } public void reset() { if (!isResetted) { logging.logg(prefs.getString("lang.simulation.resetted")); isResetted = true; isPaused = false; isFinished = false; startTime = System.currentTimeMillis(); time = 0; lastTime = 0; clockOffset = 0; simulationTime = 0; for (VSProcess process : processes) process.reset(); /* Reset the task manager AFTER the processes, for the programmed tasks */ taskManager.reset(); for (VSProcess process : processes) process.createRandomCrashTask(); synchronized (messageLines) { messageLines.clear(); } paint(); logging.clear(); } } public void stopThread() { isThreadStopped = true; } public boolean isThreadStopped() { return isThreadStopped; } public void showLamport(boolean showLamport) { this.showLamport = showLamport; if (isPaused) paint(); } public void showVectorTime(boolean showVectorTime) { this.showVectorTime = showVectorTime; if (isPaused) paint(); } public void isAntiAliased(boolean isAntiAliased) { this.isAntiAliased = isAntiAliased; this.isAntiAliasedChanged = true; if (isPaused) paint(); } public void sendMessage(VSMessage message) { VSTask task = null; VSEvent messageReceiveEvent = null; VSProcess sendingProcess = message.getSendingProcess(); long deliverTime, outageTime, durationTime; boolean recvOwn = prefs.getBoolean("sim.message.own.recv"); for (VSProcess receiverProcess : processes) { if (receiverProcess.equals(sendingProcess)) { if (recvOwn) { deliverTime = sendingProcess.getGlobalTime(); messageReceiveEvent = new MessageReceiveEvent(message); task = new VSTask(deliverTime, receiverProcess, messageReceiveEvent, VSTask.GLOBAL); taskManager.addTask(task); } } else { durationTime = sendingProcess.getDurationTime(); deliverTime = sendingProcess.getGlobalTime() + durationTime; outageTime = sendingProcess.getARandomMessageOutageTime(durationTime); /* Only add a 'receiving message' task if the message will not get lost! */ if (outageTime == -1) { messageReceiveEvent = new MessageReceiveEvent(message); task = new VSTask(deliverTime, receiverProcess, messageReceiveEvent, VSTask.GLOBAL); taskManager.addTask(task); } synchronized (messageLines) { messageLines.add( new VSMessageLine(receiverProcess, sendingProcess.getGlobalTime(), deliverTime, outageTime, sendingProcess.getProcessID(), receiverProcess.getProcessID())); } } } } public void mouseClicked(MouseEvent me) { final VSProcess process = getProcessAtYPos(me.getY()); if (process == null) return; if (SwingUtilities.isRightMouseButton(me)) { ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent ae) { String actionCommand = ae.getActionCommand(); if (actionCommand.equals(prefs.getString("lang.edit"))) { editProcess(process); } else if (actionCommand.equals(prefs.getString("lang.crash"))) { VSEvent event = new ProcessCrashEvent(); event.init(process); taskManager.addTask(new VSTask(process.getGlobalTime(), process, event, VSTask.GLOBAL)); } else if (actionCommand.equals(prefs.getString("lang.recover"))) { VSEvent event = new ProcessRecoverEvent(); event.init(process); taskManager.addTask(new VSTask(process.getGlobalTime(), process, event, VSTask.GLOBAL)); } } }; JPopupMenu popup = new JPopupMenu(); JMenuItem item = new JMenuItem(prefs.getString("lang.edit")); item.addActionListener(actionListener); popup.add(item); item = new JMenuItem(prefs.getString("lang.crash")); if (process.isCrashed() || isPaused || time == 0 || isFinished) item.setEnabled(false); else item.addActionListener(actionListener); popup.add(item); item = new JMenuItem(prefs.getString("lang.recover")); if (!process.isCrashed() || isPaused || time == 0 || isFinished) item.setEnabled(false); else item.addActionListener(actionListener); popup.add(item); popup.show(me.getComponent(), me.getX(), me.getY()); } else { editProcess(process); } } public void editProcess(int processNum) { VSProcess process = processes.get(processNum); editProcess(process); } public void editProcess(VSProcess process) { if (process != null) { process.updatePrefs(); new VSEditorFrame(prefs, simulation.getSimulatorFrame(), new VSProcessEditor(prefs, process)); } } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { if (highlightedProcess != null) { highlightedProcess.highlightOff(); highlightedProcess = null; paint(); } } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } public void mouseDragged(MouseEvent e) { } public void mouseMoved(MouseEvent e) { VSProcess p = getProcessAtYPos(e.getY()); if (p == null) { if (highlightedProcess != null) { highlightedProcess.highlightOff(); highlightedProcess = null; } if (isPaused) paint(); return; } if (highlightedProcess != null) { if (highlightedProcess.getProcessID() != p.getProcessID()) { highlightedProcess.highlightOff(); highlightedProcess = p; p.highlightOn(); } } else { highlightedProcess = p; p.highlightOn(); } if (isPaused) paint(); } public void ancestorMoved(HierarchyEvent e) { } public void ancestorResized(HierarchyEvent e) { recalcOnWindowChanged(); } public ArrayList getProcessesArray() { ArrayList arr = new ArrayList(); for (VSProcess process : processes) arr.add(process); return arr; } public void updateFromPrefs() { untilTime = prefs.getInteger("sim.seconds") * 1000; clockSpeed = prefs.getFloat("sim.clock.speed"); secondsSpaceing = (int) (untilTime / 15000); if (secondsSpaceing == 0) secondsSpaceing = 1; threadSleep = (int) (untilTime / 7500); if (threadSleep == 0) threadSleep = 1; recalcOnWindowChanged(); } }