package com.ibm.jactors.test; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import javax.sound.midi.Instrument; import javax.sound.midi.MidiChannel; import javax.sound.midi.MidiSystem; import javax.sound.midi.Sequence; import javax.sound.midi.Sequencer; import javax.sound.midi.Soundbank; import javax.sound.midi.Synthesizer; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JSlider; import javax.swing.JSpinner; import javax.swing.JSplitPane; import javax.swing.JTextArea; import javax.swing.SpinnerNumberModel; import javax.swing.SwingUtilities; import javax.swing.border.EmptyBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import com.ibm.jactors.AbstractActor; import com.ibm.jactors.Actor; import com.ibm.jactors.DefaultActorManager; import com.ibm.jactors.DefaultActorManager.ActorRunnable; import com.ibm.jactors.DefaultMessage; import com.ibm.jactors.Message; import com.ibm.jactors.logging.DefaultLogger; import com.ibm.jactors.utils.Utils; /** * A GUI for running and visualizing Actors. * The implementation of this GUI is not discussed in this associated article. * * @author BFEIGENB * */ public class ActorDemo extends JPanel implements ChangeListener { // TODO: move to new package once imports/publics sorted out public static final int METER_HEIGHT = 50; public static final int METER_WIDTH = 300; public static final int VIEWPORT_WIDTH = 500; public static final int VIEWPORT_HEIGHT = 400; DefaultActorManager actorManager; protected JLabel messageLine, titleLine; // protected JTextField optionsField; protected JComboBox demosBox; protected JButton runButton, stopButton, nextButton, randomizeButton, addTaskButton, removeTaskButton; protected JSplitPane splitPane; protected JSlider stepSlider; protected JLabel stepLabel, threadCountLabel; protected JCheckBox transparentCheckBox; protected JTextArea logArea; protected JSpinner threadCounter; protected JRadioButton noSoundButton, soundWhenIdleButton, soundWhenBusyButton; protected Sequencer sequencer; protected Sequence sequence; protected Synthesizer synthesizer; protected Instrument instruments[]; public Instrument[] getInstruments() { return instruments; } protected int currentChannel; public int getCurrentChannel() { return currentChannel; } public void setCurrentChannel(int currentChannel) { this.currentChannel = currentChannel; } public void startNote(int note, int attack) { if (isMidiOpen()) { midiChannels[currentChannel].noteOn(note, attack); } } public void stopNote(int note, int attack) { if (isMidiOpen()) { midiChannels[currentChannel].noteOff(note, attack); } } public void stopAllNotes() { if (isMidiOpen()) { midiChannels[currentChannel].allNotesOff(); } } MidiChannel[] midiChannels; public MidiChannel[] getMidiChannels() { return midiChannels; } public boolean isMidiOpen() { return synthesizer != null && synthesizer.isOpen(); } public void setupMidi() throws Exception { synthesizer = MidiSystem.getSynthesizer(); synthesizer.open(); midiChannels = synthesizer.getChannels(); sequencer = MidiSystem.getSequencer(); sequence = new Sequence(Sequence.PPQ, 10); Soundbank sb = synthesizer.getDefaultSoundbank(); if (sb != null) { instruments = synthesizer.getDefaultSoundbank().getInstruments(); for (int i = 0; i < instruments.length; i++) { System.out.printf("%4d: %s%n", i, instruments[i]); } if (instruments.length > 0) { synthesizer.loadInstrument(instruments[0]); midiChannels[currentChannel].programChange(0); } if (instruments.length > 14) { synthesizer.loadInstrument(instruments[14]); midiChannels[currentChannel].programChange(14); } } } public void shutdownMidi() { if (synthesizer != null) { synthesizer.close(); } if (sequencer != null) { sequencer.close(); } sequencer = null; synthesizer = null; instruments = null; } public void initUI(JFrame frame) { JPanel cp = (JPanel) frame.getContentPane(); cp.setLayout(new BorderLayout(5, 5)); splitPane = new JSplitPane(); cp.add(splitPane, BorderLayout.CENTER); JPanel main = new JPanel(new BorderLayout(5, 5)); splitPane.setLeftComponent(main); JPanel topPanel = new JPanel(); main.add(topPanel, BorderLayout.NORTH); buildTopPanel(topPanel); JPanel bottomPanel = new JPanel(); main.add(bottomPanel, BorderLayout.SOUTH); buildBottomPanel(bottomPanel); logArea = new JTextArea(); logArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); splitPane.setRightComponent(new JScrollPane(logArea)); DefaultLogger.getDefaultInstance().setIncludeCaller(false); DefaultLogger.getDefaultInstance().setLogToFile(false); DefaultLogger.getDefaultInstance().setLogArea(logArea); } protected void buildTopPanel(JPanel inputs) { inputs.setBorder(new EmptyBorder(5, 5, 5, 5)); GridBagLayout gbl = new GridBagLayout(); inputs.setLayout(gbl); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(1, 2, 1, 2); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2; gbc.gridheight = 1; titleLine = new JLabel("Nothing selected"); titleLine.setHorizontalAlignment(JLabel.CENTER); inputs.add(titleLine, gbc); gbc.gridx = 0; gbc.gridy = 1; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1; // optionsField = new JTextField(50); // optionsField.setText("pc"); // inputs.add(optionsField, gbc); String[] testNames = DefaultActorTest.getTestNames(); String[] xtestNames = new String[testNames.length + 1]; System.arraycopy(testNames, 0, xtestNames, 0, testNames.length); xtestNames[testNames.length] = "All demos in sequence"; demosBox = new JComboBox(xtestNames); inputs.add(demosBox, gbc); demosBox.setSelectedIndex(demosBox.getItemCount() - 1); gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.fill = GridBagConstraints.HORIZONTAL; final int maxSends = 50; sendRateBar = new JProgressBar(JProgressBar.HORIZONTAL, 0, maxSends); sendRateBar.setBackground(Color.LIGHT_GRAY.brighter()); sendRateBar.setStringPainted(true); sendRateBar.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { int value = sendRateBar.getValue(); sendRateBar.setString(String .format(value > maxSends - 1 ? (">=" + maxSends + " mps") : "%d mps", value)); } }); inputs.add(sendRateBar, gbc); gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.fill = GridBagConstraints.HORIZONTAL; final int maxThreads = 50; // actorManager.getThreads().length * 2; dispatchRateBar = new JProgressBar(JProgressBar.HORIZONTAL, 0, maxThreads); dispatchRateBar.setBackground(Color.LIGHT_GRAY.brighter()); dispatchRateBar.setStringPainted(true); dispatchRateBar.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { int value = dispatchRateBar.getValue(); dispatchRateBar.setString(String.format(value > maxThreads - 1 ? (">=" + maxThreads + " cps") : "%d dps", value)); } }); inputs.add(dispatchRateBar, gbc); gbc.gridx = 0; gbc.gridy = 4; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.fill = GridBagConstraints.BOTH; trendLineView = new ImageView(); trendLineView.setPreferredSize(new Dimension(METER_WIDTH, METER_HEIGHT)); inputs.add(trendLineView, gbc); gbc.gridx = 1; gbc.gridy = 4; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 0.30; stepSlider = new JSlider(4, 9, 8); stepSlider.setMajorTickSpacing(1); stepSlider.setPaintLabels(true); stepSlider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { stepLabel.setText(formatSteps()); } }); inputs.add(stepSlider, gbc); gbc.gridx = 2; gbc.gridy = 4; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 0.15; stepLabel = new JLabel("", JLabel.RIGHT); stepLabel.setText(formatSteps()); inputs.add(stepLabel, gbc); gbc.gridx = 1; gbc.gridy = 1; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 0.15; runButton = new JButton("Start"); inputs.add(runButton, gbc); runButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { doStart(); doRun(); } }); gbc.gridx = 2; gbc.gridy = 1; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 0.15; stopButton = new JButton("Stop"); stopButton.setEnabled(false); inputs.add(stopButton, gbc); stopButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { doStop(); messageLine.setText("Stopped"); stopButton.setEnabled(false); runButton.setEnabled(true); } }); gbc.gridx = 1; gbc.gridy = 2; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 0.15; randomizeButton = new JButton("Redistribute"); inputs.add(randomizeButton, gbc); randomizeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (actorManager != null) { actorManager.randomizeActors(); } } }); gbc.gridx = 1; gbc.gridy = 3; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 0.15; addTaskButton = new JButton("Add Task"); inputs.add(addTaskButton, gbc); addTaskButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (actorManager != null) { Thread t = actorManager.addThread("new" + taskId++); t.start(); } } }); gbc.gridx = 2; gbc.gridy = 3; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 0.15; removeTaskButton = new JButton("Remove Task"); inputs.add(removeTaskButton, gbc); removeTaskButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (actorManager != null) { Thread[] ta = actorManager.getThreads(); for (Thread t : ta) { if (t.getName().startsWith("new")) { actorManager.removeThread(t.getName()); break; } } } } }); gbc.gridx = 1; gbc.gridy = 5; gbc.gridwidth = 2; gbc.gridheight = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 0.30; transparentCheckBox = new JCheckBox("Transparent Actors"); transparentCheckBox.setSelected(true); transparentCheckBox.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { isTransparentActors = transparentCheckBox.isSelected(); } }); inputs.add(transparentCheckBox, gbc); gbc.gridx = 0; gbc.gridy = 6; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.fill = GridBagConstraints.HORIZONTAL; //gbc.weightx = 0.15; ButtonGroup bg = new ButtonGroup(); JPanel soundPanel = new JPanel(); noSoundButton = new JRadioButton("No Sound"); soundPanel.add(noSoundButton); bg.add(noSoundButton); soundWhenIdleButton = new JRadioButton("Idle"); soundPanel.add(soundWhenIdleButton); bg.add(soundWhenIdleButton); soundWhenBusyButton = new JRadioButton("Busy"); soundPanel.add(soundWhenBusyButton); bg.add(soundWhenBusyButton); noSoundButton.setSelected(true); inputs.add(soundPanel, gbc); gbc.gridx = 1; gbc.gridy = 6; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 0.15; SpinnerNumberModel model = new SpinnerNumberModel(10, 2, 25, 1); threadCounter = new JSpinner(model); threadCounter.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { threadCount = (Integer) threadCounter.getValue(); } }); inputs.add(threadCounter, gbc); gbc.gridx = 2; gbc.gridy = 6; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 0.15; threadCountLabel = new JLabel("Thread Count", JLabel.RIGHT); inputs.add(threadCountLabel, gbc); } protected int threadCount = 10; protected int taskId; protected boolean isTransparentActors = true; protected JProgressBar sendRateBar, dispatchRateBar; private String formatSteps() { return String.format("%d steps", (int) Math.pow(2, stepSlider.getValue())); } protected ImageView trendLineView; protected ImageView threadHistoryView; protected ImageView mainActorView; protected ImageView[] subActorViews; protected final int subActorViewCount = 5; protected void buildBottomPanel(JPanel views) { views.setLayout(new BorderLayout(5, 5)); JPanel mp = new JPanel(new BorderLayout(5, 5)); views.add(mp, BorderLayout.CENTER); threadHistoryView = new ImageView(); threadHistoryView.setPreferredSize(new Dimension(VIEWPORT_WIDTH, VIEWPORT_HEIGHT / 5)); mp.add(threadHistoryView, BorderLayout.NORTH); mainActorView = new ImageView(); mainActorView.setPreferredSize(new Dimension(VIEWPORT_WIDTH, VIEWPORT_HEIGHT)); mp.add(mainActorView, BorderLayout.CENTER); JPanel gp = new JPanel(new GridLayout(0, subActorViewCount, 5, 5)); mp.add(gp, BorderLayout.SOUTH); subActorViews = new ImageView[subActorViewCount]; for (int i = 0; i < subActorViewCount; i++) { ImageView av = new ImageView(); subActorViews[i] = av; av.setPreferredSize(new Dimension((VIEWPORT_WIDTH - subActorViewCount * 5) / subActorViewCount, VIEWPORT_HEIGHT / 5)); gp.add(av); } messageLine = new JLabel(" "); views.add(messageLine, BorderLayout.SOUTH); } protected String reduceName(String name) { return name.toLowerCase().replaceAll("\\s+", ""); } protected void doRun() { runButton.setEnabled(false); // nextButton.setEnabled(true); stopButton.setEnabled(true); logArea.setText(""); logArea.setCaretPosition(0); // final String[] args = optionsField.getText().split("\\s+"); int selectedIndex = demosBox.getSelectedIndex(); if (selectedIndex >= 0) { // mySize = getSize(); setTitle((String) demosBox.getSelectedItem()); final String args = selectedIndex < demosBox.getItemCount() - 1 ? reduceName(DefaultActorTest .getTestNames()[selectedIndex]) : "*"; Thread t = new Thread(new Runnable() { @Override public void run() { try { if ("*".equals(args)) { for (String name : DefaultActorTest.getTestNames()) { doStart(); setTitle(name); runTest(reduceName(name)); doStop(); try { Thread.sleep(10 * 1000); } catch (InterruptedException e) { break; } } } else { doStart(); runTest(args); doStop(); } } finally { SwingUtilities.invokeLater(new Runnable() { public void run() { // TODO: make multiple runs possible // runButton.setEnabled(true); stopButton.setEnabled(false); runButton.setEnabled(true); messageLine.setText("Done"); } }); } } }); t.setDaemon(true); t.start(); } } protected void doStart() { if (updater != null) { updater.interrupt(); } updater = new Thread(new UpdateRunnable()); updating = true; updater.setDaemon(true); updater.start(); } protected void doNext() { // TODO: finish doStop(); runButton.setEnabled(false); // nextButton.setEnabled(true); stopButton.setEnabled(true); logArea.setText(""); logArea.setCaretPosition(0); // final String[] args = optionsField.getText().split("\\s+"); int selectedIndex = demosBox.getSelectedIndex(); if (selectedIndex >= 0) { selectedIndex++; // mySize = getSize(); setTitle((String) demosBox.getSelectedItem()); final String args = selectedIndex < demosBox.getItemCount() - 1 ? reduceName(DefaultActorTest .getTestNames()[selectedIndex]) : "*"; Thread t = new Thread(new Runnable() { @Override public void run() { try { if ("*".equals(args)) { for (String name : DefaultActorTest.getTestNames()) { doStart(); setTitle(name); runTest(reduceName(name)); doStop(); try { Thread.sleep(10 * 1000); } catch (InterruptedException e) { break; } } } else { doStart(); runTest(args); doStop(); } } finally { SwingUtilities.invokeLater(new Runnable() { public void run() { // TODO: make multiple runs possible // runButton.setEnabled(true); stopButton.setEnabled(false); runButton.setEnabled(true); messageLine.setText("Done"); } }); } } }); t.setDaemon(true); t.start(); } } protected void doStop() { test.terminateRun(); updating = false; } public String getMessage() { final String[] res = new String[1]; if (messageLine != null) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { res[0] = messageLine.getText(); } }); } return res[0]; } public void setMessage(final String message) { if (messageLine != null) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { messageLine.setText(message != null ? message : ""); } }); } } public String getTitle() { final String[] res = new String[1]; if (titleLine != null) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { res[0] = titleLine.getText(); } }); } return res[0]; } public void setTitle(final String message) { if (titleLine != null) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { titleLine.setText(message != null ? message : ""); } }); } } // volatile Dimension mySize; volatile boolean updating; public static final int TIMES_PER_SECOND = 10; protected class UpdateRunnable implements Runnable { boolean inRun; @Override public void run() { if (inRun) { return; } inRun = true; actorColorMap.clear(); int xiteration = 0; while (updating) { try { Utils.sleep(1000 / TIMES_PER_SECOND); if (xiteration % TIMES_PER_SECOND == 0) { updateRates(); } if (xiteration % (5 * TIMES_PER_SECOND) == 0) { renderSnapshots(); } ImageView avMain = mainActorView; BufferedImage bi = new BufferedImage(avMain.getWidth(), avMain.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics g = bi.getGraphics(); renderTo(g, bi.getWidth(), bi.getHeight()); avMain.setImage(bi); // System.out.printf("repaint: %s%n", renderImage1); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { // System.out.printf("**** repaint%n"); mainActorView.repaint(); for (int i = 0; i < subActorViews.length; i++) { subActorViews[i].repaint(); } threadHistoryView.repaint(); } }); } catch (Exception e) { e.printStackTrace(); } xiteration++; } updateRates(); inRun = false; } protected void renderSnapshots() { // System.out.printf("**** UpdateRunnable run: %d %n", // xiteration); int last = subActorViews.length - 1; for (int i = 0; i < last; i++) { ImageView av = subActorViews[i]; ImageView avNext = subActorViews[i + 1]; BufferedImage bi = avNext.getImage(); addTimeStamp(-(last - i + 1) * 5, bi); av.setImage(bi); } ImageView avLast = subActorViews[last]; BufferedImage bi = new BufferedImage(avLast.getWidth(), avLast.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics g = bi.getGraphics(); renderTo(g, bi.getWidth(), bi.getHeight()); addTimeStamp(-5, bi); avLast.setImage(bi); } protected void updateRates() { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { int sendPerSecondCount = -1, dispatchPerSecondCount = -1; if (actorManager != null) { if (sendRateBar != null) { sendPerSecondCount = actorManager.getSendPerSecondCount(); sendRateBar.setValue(sendPerSecondCount); } if (dispatchRateBar != null) { dispatchPerSecondCount = actorManager.getDispatchPerSecondCount(); dispatchRateBar.setValue(dispatchPerSecondCount); } // System.out.printf("rates: sendPerSecondCount=%d, dispatchPerSecondCount=%d%n", // sendPerSecondCount, dispatchPerSecondCount); if (trendLineView != null) { trendLineView.setImage(renderTrendLine(actorManager.getTrendValue(), actorManager.getMaxTrendValue())); } } } }); } protected void addTimeStamp(int delta, BufferedImage bi) { if (bi != null) { Graphics g = bi.getGraphics(); g.setColor(Color.CYAN); g.fillRect(0, 0, 20, 15); g.setColor(Color.BLACK); g.drawString(String.format("%d", delta), 0, 14); } } } protected BufferedImage trendLineImage; public BufferedImage getTrendLineImage() { return trendLineImage; } protected Thread updater; protected static Random rand = new Random(); // TOOD: need more contrast in colors. // may need to manually define the colors protected static List<Color> colors = new ArrayList<Color>(); static { /* * for (int r = 0; r < 3; r++) { for (int g = 0; g < 3; g++) { for (int * b = 0; b < 3; b++) { // don't get too close to white Color c = new * Color(128 + rand.nextInt(100), 128 + rand.nextInt(100), 128 + * rand.nextInt(100)); if (!colors.contains(c)) { colors.add(c); } } } } */ int r = 0, g = 0, b = 0; for (int i = 0; colors.size() < 64 && i < 1000; i++) { Color c = new Color(Math.min(128, (150 + r) % 256), Math.min(128, (128 + g) % 256), Math.min(128, (128 + b) % 256)).brighter(); int dist = Integer.MAX_VALUE; for (Color xc : colors) { int xdist = getColorDistance(xc, c); if (dist > xdist) { dist = xdist; } } if (dist > 25) { // Utils.logger.trace("adding color %d: %s", colors.size(), c); colors.add(c); } else { // Utils.logger.trace("skipping color %d: %s", colors.size(), // c); } r += 81; g += 11; b += 59; } } protected static int getColorDistance(Color c1, Color c2) { int r = Math.abs(c1.getRed() - c2.getRed()); int g = Math.abs(c1.getGreen() - c2.getGreen()); int b = Math.abs(c1.getBlue() - c2.getBlue()); return r + g + b; } protected Map<String, Color> actorColorMap = new HashMap<String, Color>(); int errorY; protected void initThreadHistory(Thread[] threads) { for (Thread t : threads) { String name = t.getName(); ThreadHistory th = threadHistoryMap.get(name); if (th == null) { th = new ThreadHistory(); threadHistoryMap.put(name, th); } } } protected void renderTo(Graphics g, int width, int height) { // System.out.printf("renderTo: %dx%d%n", width, height); Graphics2D g2d = (Graphics2D) g; errorY = 0; if (actorManager != null) { Font f = g2d.getFont(); try { if (width < 200 || height < 200) { g2d.setFont(new Font(f.getFontName(), f.getSize(), f.getSize() / 2)); } Set<Actor> selfSends = new HashSet<Actor>(); AbstractActor[] actors = actorManager.getActors(); Thread[] threads = actorManager.getThreads(); initThreadHistory(threads); int base = Math.min(width, height); g2d.setBackground(Color.LIGHT_GRAY.brighter()); g2d.fillRect(0, 0, width, height); int tdiam = base / 20; int tradius = (int) (base * 0.85) / 2; int adiam = base / 15; int aradius = (int) (base * 0.7) / 2; // TODO: move positioning from here to setSize(); Map<String, AbstractActor> actorMap = new HashMap<String, AbstractActor>(); Map<String, Integer> actorXMap = new HashMap<String, Integer>(); Map<String, Integer> actorYMap = new HashMap<String, Integer>(); Map<String, Integer> actorIdMap = new HashMap<String, Integer>(); double angle = 0; int count = 0, acount = 0; for (AbstractActor aa : actors) { String cname = aa.getClass().getName(); actorIdMap.put(aa.getName(), acount++); if (!actorColorMap.containsKey(cname)) { actorColorMap.put(cname, colors.get(rand.nextInt(colors.size()))); } angle = renderMessageConnections(g2d, actors, height, width, aradius, adiam, actorMap, actorXMap, actorYMap, angle, aa, colors.get(count++ % colors.size()), selfSends); } renderActors(g2d, selfSends, actors, adiam, actorXMap, actorYMap, actorIdMap); renderHistories(width, height, g2d, threads, tdiam, tradius, actorIdMap); } catch (Exception e) { paintError("renderTo", g2d, e); } finally { g2d.setFont(f); } } } protected void renderHistories(int width, int height, Graphics2D g2d, Thread[] threads, int tdiam, int tradius, Map<String, Integer> actorIdMap) { double angle; List<ThreadHistory> histories = new ArrayList<ThreadHistory>(); angle = 0; for (Thread t : threads) { String name = t.getName(); angle = renderThread(g2d, threads, height, width, tdiam, tradius, angle, t, actorIdMap); ThreadHistory th = threadHistoryMap.get(name); if (th != null) { th.setLast(name); histories.add(th); } } renderHistory(histories); } protected void renderActors(Graphics2D g2d, Set<Actor> selfSends, AbstractActor[] actors, int adiam, Map<String, Integer> actorXMap, Map<String, Integer> actorYMap, Map<String, Integer> actorIdMap) { int acount; acount = 0; for (AbstractActor aa : actors) { renderActor(g2d, adiam, actorXMap, actorYMap, actorColorMap, actorIdMap, aa, selfSends, acount++, actors.length); } } protected void renderHistory(List<ThreadHistory> histories) { int hvwidth = threadHistoryView.getWidth(); int hvheight = threadHistoryView.getHeight(); if (hvwidth > 0 && hvheight > 0) { BufferedImage bi = new BufferedImage(hvwidth, hvheight, BufferedImage.TYPE_INT_ARGB); Graphics2D biG2d = (Graphics2D) bi.getGraphics(); biG2d.setColor(Color.LIGHT_GRAY.brighter()); biG2d.fillRect(0, 0, hvwidth, hvheight); int hsize = histories.size(); if (hsize > 0) { int noteStart = 30; int noteStep = (60 + hsize - 1) / hsize; int cwidth = (hvwidth + hsize - 1) / hsize; int pstart = 0; stopAllNotes(); for (int i = 0; i < hsize; i++) { biG2d.setColor(Color.YELLOW); biG2d.fillRect(cwidth * i, 0, cwidth * (i + 1), hvheight); ThreadHistory threadHistory = histories.get(i); int max = threadHistory.getMaxCount(); int cur = threadHistory.getRunningCount(); String name = threadHistory.name; int start = (int) (hvheight * (double) (max - cur) / max); //System.out.printf("Start=%d, height=%d%n", start, hvheight); boolean busySelected = soundWhenBusyButton.isSelected(); boolean idleSelected = soundWhenIdleButton.isSelected(); if ((start <= hvheight / 5) && busySelected) { //System.out.printf("Start note busy%n"); startNote(noteStart + i * noteStep, (500 * (max - cur)) / max); } else if ((start >= 4 * hvheight / 5 && start < hvheight) && idleSelected) { //System.out.printf("Start note idle%n"); startNote(noteStart + i * noteStep, (500 * (max - cur)) / max); } biG2d.setColor(Color.GREEN); biG2d.fillRect(cwidth * i, start, cwidth * (i + 1), hvheight); biG2d.setColor(name != null && name.startsWith("new") ? Color.CYAN : Color.BLACK); biG2d.drawRect(cwidth * i, 0, cwidth * (i + 1), hvheight); biG2d.setColor(Color.MAGENTA); biG2d.setStroke(new BasicStroke(1)); if (busySelected) { biG2d.drawLine(cwidth * i, hvheight / 5, cwidth * (i + 1), hvheight / 5); } if (idleSelected) { biG2d.drawLine(cwidth * i, 4 * hvheight / 5, cwidth * (i + 1), 4 * hvheight / 5); } if (i > 0) { biG2d.setColor(Color.RED); biG2d.setStroke(new BasicStroke(2)); biG2d.drawLine(cwidth * (i - 1) + cwidth / 2, pstart, cwidth * i + cwidth / 2, start); } pstart = start; } } else { biG2d.setColor(Color.LIGHT_GRAY.brighter()); biG2d.fillRect(0, 0, hvwidth, hvheight); } threadHistoryView.setImage(bi); } } protected class ThreadHistory { // public Thread thread; public boolean[] running; public String name; public ThreadHistory() { running = new boolean[TIMES_PER_SECOND]; } public void setLast(boolean f) { for (int i = 0; i < running.length - 1; i++) { running[i] = running[i + 1]; } running[running.length - 1] = f; } public void setLast(String name) { this.name = name; ActorRunnable ar = actorManager.getRunnable(name); setLast(ar != null && ar.hasThread); } public int getMaxCount() { return running.length; } public int getRunningCount() { int count = 0; for (int i = 0; i < running.length; i++) { if (running[i]) { count++; } } return count; } } protected Map<String, ThreadHistory> threadHistoryMap = new HashMap<String, ThreadHistory>(); protected int safeGetInt(Map<String, Integer> map, String key) { Integer i = map.get(key); return i != null ? i : -1; } protected double renderMessageConnections(Graphics2D g2d, AbstractActor[] actors, int height, int width, int aradius, int adiam, Map<String, AbstractActor> actorMap, Map<String, Integer> actorXMap, Map<String, Integer> actorYMap, double angle, AbstractActor aa, Color color, Set<Actor> selfSends) { String actorName = aa.getName(); actorMap.put(actorName, aa); int x = width / 2 + (int) (Math.cos(angle) * aradius); actorXMap.put(actorName, x); int y = height / 2 + (int) (Math.sin(angle) * aradius); actorYMap.put(actorName, y); angle += 2 * Math.PI / actors.length; Message[] sent = actorManager.getAndClearSentMessages(aa); if (sent != null && sent.length > 0) { for (Message m : sent) { Actor from = m.getSource(), to = aa; try { if (from != null) { if (from != to) { // System.out.printf("sent: %s to %s%n", from, to); String fromName = from.getName(); int fx = safeGetInt(actorXMap, fromName), fy = safeGetInt(actorYMap, fromName); if (fx >= 0 && fy >= 0) { String toName = to.getName(); int tx = safeGetInt(actorXMap, toName), ty = safeGetInt(actorYMap, toName); if (tx >= 0 && ty >= 0) { g2d.setColor(Color.RED); // g2d.setStroke(new BasicStroke(1, // BasicStroke.CAP_BUTT, // BasicStroke.JOIN_MITER, 10, // new float[] { 5, 2, 3, 2 }, 1)); g2d.setStroke(new BasicStroke(4)); g2d.drawLine(fx, fy, tx, ty); // System.out.printf("drawSend: (d,%d) -> (%d,%d)%n", // fx, fy, tx, ty); } } } else { selfSends.add(aa); } } } catch (Exception e) { paintError("send", g2d, e); } } } Map<String, Integer> fromToCounts = new HashMap<String, Integer>(); DefaultMessage[] messages = aa.getMessages(); for (DefaultMessage m : messages) { Actor from = m != null ? m.getSource() : null, to = aa; if (from != null) { try { String fromName = from.getName(); int fx = safeGetInt(actorXMap, fromName), fy = safeGetInt(actorYMap, fromName); if (fx >= 0 && fy >= 0) { String toName = to.getName(); int tx = safeGetInt(actorXMap, toName), ty = safeGetInt(actorYMap, toName); if (tx >= 0 && ty >= 0) { g2d.setColor(color); g2d.setStroke(new BasicStroke(2)); g2d.drawLine(fx, fy, tx, ty); int dx = tx - fx, dy = ty - fy; int count = getCount(fromToCounts, fromName, toName); int markerWidth = (int) (adiam * (0.1 + 0.1 * count)); int xx = fx + (int) (dx * 0.85) - markerWidth / 2, xy = fy + (int) (dy * 0.85) - markerWidth / 2; g2d.fillOval(xx, xy, markerWidth, markerWidth); } } } catch (Exception e) { paintError("pending", g2d, e); } } } return angle; } protected int getCount(Map<String, Integer> fromToCounts, String fromName, String toName) { String fromToName = fromName + toName; Integer count = fromToCounts.get(fromToName); if (count == null) { count = 0; } count++; fromToCounts.put(fromToName, count); return count; } protected double renderThread(Graphics2D g2d, Thread[] threads, int height, int width, int tdiam, int tradius, double angle, Thread t, Map<String, Integer> actorIdMap) { try { int x = width / 2 + (int) (Math.cos(angle) * tradius); int y = height / 2 + (int) (Math.sin(angle) * tradius); ActorRunnable ar = actorManager.getRunnable(t.getName()); if (ar != null) { g2d.setColor(ar.hasThread ? Color.GREEN.darker() : Color.YELLOW); g2d.fillRect(x - tdiam / 2, y - tdiam / 2, tdiam, tdiam); Actor a = ar.actor; if (ar.hasThread && a != null) { int theight = tdiam / 2; Color c = actorColorMap.get(a.getClass().getName()); if (c != null) { g2d.setColor(c); g2d.fillOval(x - theight / 2, y - theight / 2, theight, theight); } Integer count = actorIdMap.get(a.getName()); if (count != null) { g2d.setColor(Color.BLACK); g2d.drawString(Integer.toString(count), x - tdiam / 2, y + (y > height / 2 ? tdiam + 10 : -tdiam - 10)); } } } angle += 2 * Math.PI / threads.length; } catch (Exception e) { paintError("thread", g2d, e); } return angle; } protected void renderActor(Graphics2D g2d, int adiam, Map<String, Integer> actorXMap, Map<String, Integer> actorYMap, Map<String, Color> actorColorMap, Map<String, Integer> actorIdMap, AbstractActor aa, Set<Actor> selfSends, int index, int total) { try { String name = aa.getName(); int x = safeGetInt(actorXMap, name), y = safeGetInt(actorYMap, name); if (x >= 0 && y >= 0) { Color acolor = actorColorMap.get(aa.getClass().getName()); if (acolor == null) { acolor = Color.RED; } Color xacolor = aa.getHasThread() ? acolor.darker() : acolor; g2d.setColor(xacolor); if (selfSends.contains(aa)) { // System.out.printf("renderActor: self %s%n", aa); g2d.drawOval(x - adiam / 2, y - adiam / 2, adiam, adiam); } else { Color c = isTransparentActors ? new Color(xacolor.getRed(), xacolor.getGreen(), xacolor.getBlue(), 150) : xacolor; g2d.setColor(c); // System.out.printf("renderActor: other %s%n", aa); g2d.fillOval(x - adiam / 2, y - adiam / 2, adiam, adiam); if (adiam > 20) { g2d.setColor(Color.BLACK); g2d.drawOval(x - adiam / 2, y - adiam / 2, adiam, adiam); } } // g2d.setFont(font); // g2d.setColor(Color.BLACK); // if (name.length() > 10) { // name = name.substring(name.length() - 10); // } // g2d.drawString(name, x - adiam / 2, y); if (index == total - 1) { Integer count = actorIdMap.get(name); if (count != null) { g2d.setColor(Color.BLACK); g2d.drawString(Integer.toString(count), x - 10, y + 5); } } } } catch (Exception e) { paintError("actor", g2d, e); } } protected BufferedImage renderTrendLine(int trendValue, int maxTrendValue) { BufferedImage bi = new BufferedImage(METER_WIDTH, METER_HEIGHT, BufferedImage.TYPE_INT_ARGB); int width = bi.getWidth(); int halfWidth = width / 2; int height = bi.getHeight(); // System.out.printf("renderTrendLine %dx%d: %d, %d%n", width, height, // trendValue, maxTrendValue); Graphics2D g2d = (Graphics2D) bi.getGraphics(); // g2d.scale(1, 1); g2d.setColor(Color.LIGHT_GRAY.brighter()); g2d.fillRect(0, 0, width, height); int atv = Math.abs(trendValue); Color c = Color.GREEN; if (atv > maxTrendValue / 3) { c = Color.YELLOW; if (atv > maxTrendValue * 2 / 3) { c = Color.ORANGE; } if (atv > maxTrendValue) { c = Color.RED; } } g2d.setColor(c); int tlv = Math.min(atv, maxTrendValue); int xl = halfWidth * tlv / maxTrendValue; int textX = 0; if (trendValue < 0) { g2d.fillRect(halfWidth - xl, 0, xl, height); textX = halfWidth + 5; } else { g2d.fillRect(halfWidth, 0, xl, height); textX = 5; } g2d.setColor(Color.BLACK); g2d.setFont(new Font("monospace", Font.BOLD, 24)); g2d.drawString(String.format("%d", trendValue), textX, height / 2); g2d.setStroke(new BasicStroke(2)); g2d.drawLine(halfWidth, 1, halfWidth, height - 2); g2d.setColor(Color.DARK_GRAY); g2d.drawRect(1, 1, width - 1, height - 2); return bi; } volatile BufferedImage renderImageMain; protected void paintError(String from, Graphics2D g2d, Exception e) { g2d.setColor(Color.RED); errorY += 50; g2d.drawString(e.toString(), 25, errorY); System.out.printf("paint exception %s: %s%n", from, e); e.printStackTrace(System.out); } Font font; public void postShow() { Dimension d = getSize(); double posn = d.width >= 800 ? 0.33 : (d.width >= 400 ? 0.40 : 0.50); splitPane.setDividerLocation(posn); font = new Font("Courier New", Font.PLAIN, 10); } @Override public void stateChanged(ChangeEvent e) { setMessage(String.format("Count down to halt: %d", test.getStepCount())); } DefaultActorTest test; public DefaultActorTest getTest() { return test; } public void setActorTest(DefaultActorTest test) { this.test = test; } public void runTest(String args) { actorManager = new DefaultActorManager(); test = new DefaultActorTest(); test.setActorManager(actorManager); // am.detachAllActors(); if (!isMidiOpen()) { try { setupMidi(); } catch (Exception e) { e.printStackTrace(); } } test.addChangeListener(this); int value = (int) Math.pow(2, stepSlider.getValue()); test.run((args + " -sc:" + value + " -tc:" + threadCount).split("\\s+")); test.removeChangeListener(this); // am.detachAllActors(); if (isMidiOpen()) { try { shutdownMidi(); } catch (Exception e) { e.printStackTrace(); } } } public static void main(String[] args) { ActorDemo av = new ActorDemo(); JFrame frame = new JFrame("ActorDemo v1.0"); // frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { System.exit(0); } @Override public void windowClosed(WindowEvent e) { System.exit(0); } }); frame.setSize(1000, 850); av.initUI(frame); frame.setVisible(true); av.postShow(); // av.runTest(args); Utils.logger.trace("Done"); } }