package com.vitco.layout.content.console;
import com.jidesoft.action.CommandMenuBar;
import com.threed.jpct.Texture;
import com.threed.jpct.TextureManager;
import com.vitco.Main;
import com.vitco.core.data.Data;
import com.vitco.core.data.container.Voxel;
import com.vitco.export.generic.ExportWorld;
import com.vitco.layout.content.JCustomScrollPane;
import com.vitco.layout.content.ViewPrototype;
import com.vitco.layout.frames.FrameLinkagePrototype;
import com.vitco.low.hull.HullManagerExt;
import com.vitco.manager.action.types.StateActionPrototype;
import com.vitco.manager.async.AsyncAction;
import com.vitco.manager.async.AsyncActionManager;
import com.vitco.manager.thread.LifeTimeThread;
import com.vitco.manager.thread.ThreadManagerInterface;
import com.vitco.settings.VitcoSettings;
import com.vitco.util.misc.DateTools;
import org.springframework.beans.factory.annotation.Autowired;
import javax.swing.*;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.net.URLDecoder;
import java.util.*;
/**
* Displays console content and buttons to user.
*/
public class ConsoleView extends ViewPrototype implements ConsoleViewInterface {
// var & setter
protected Data data;
@Autowired(required=true)
public final void setData(Data data) {
this.data = data;
}
// var & setter
protected AsyncActionManager asyncActionManager;
@Autowired(required=true)
public final void setAsyncActionManager(AsyncActionManager asyncActionManager) {
this.asyncActionManager = asyncActionManager;
}
private ThreadManagerInterface threadManager;
// set the action handler
@Autowired
public final void setThreadManager(ThreadManagerInterface threadManager) {
this.threadManager = threadManager;
}
@Override
public JComponent buildConsole(final FrameLinkagePrototype frame) {
// panel that holds everything
final JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
// the console
final JTextArea textArea = new JTextArea();
// set layout
textArea.setForeground(VitcoSettings.DEFAULT_TEXT_COLOR);
textArea.setBackground(VitcoSettings.DEFAULT_BG_COLOR);
// load the previous console data
final ArrayList<String> consoleData = console.getConsoleData();
for (String line : consoleData) {
textArea.append(line);
}
textArea.setEditable(false); // hide the caret
// to be able to handle auto show, auto scroll and
// tmp disable scroll for this textarea
class ScrollPane extends JCustomScrollPane {
public boolean autoScroll = true; // true iff auto scrolling is enabled
public boolean autoShow = true; // true iff auto showing is enabled
public boolean tempScrollStop = false; // true iff user scrolls (disable auto scroll!)
public ScrollPane(JComponent component) {
super(component);
}
}
final ScrollPane scrollPane = new ScrollPane(textArea);
scrollPane.setBorder(BorderFactory.createMatteBorder(1,0,1,1,VitcoSettings.DEFAULT_BORDER_COLOR));
// load the stored preferences for this
if (preferences.contains("console_auto_scroll_status")) {
scrollPane.autoScroll = preferences.loadBoolean("console_auto_scroll_status");
}
if (preferences.contains("console_auto_show_status")) {
scrollPane.autoShow = preferences.loadBoolean("console_auto_show_status");
}
// only scroll when the user is not scrolling
scrollPane.getVerticalScrollBar().addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
scrollPane.tempScrollStop = true;
}
@Override
public void mouseReleased(MouseEvent e) {
scrollPane.tempScrollStop = false;
}
});
// handle console events
console.addConsoleListener(new ConsoleListener() {
@Override
public void lineAdded(String line) {
// show if necessary
if (scrollPane.autoShow && (!frame.isVisible() || // not visible
(frame.isAutohide() && !frame.isAutohideShowing()))) { // visible but not showing (side)
frame.setVisible(true); // show the console whenever text is added
panel.repaint();
}
// add line
textArea.append(line);
// make sure there are not too many lines in the textarea
while (textArea.getLineCount() - 1 > Console.LINE_BUFFER_COUNT) {
try {
// remove first line
Element root = textArea.getDocument().getDefaultRootElement();
Element first = root.getElement(0);
textArea.getDocument().remove(first.getStartOffset(), first.getEndOffset());
} catch (BadLocationException e) {
errorHandler.handle(e);
}
}
// scroll to bottom if wanted
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (!scrollPane.tempScrollStop && scrollPane.autoScroll) {
// when the text changes, scroll down
scrollPane.validate();
scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getMaximum());
}
}
});
}
});
final JTextField inputField = new JTextField();
// produce error
actionManager.registerAction("create_error_for_debug", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
errorHandler.handle(new Exception("Debug Exception"));
}
});
// study the complexity of the currently visible polygon
actionManager.registerAction("study_object_complexity", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
asyncActionManager.addAsyncAction(new AsyncAction() {
@Override
public void performAction() {
console.addLine("Computing \"Reduced Triangle Count\"...");
ExportWorld exportWorld = new ExportWorld(data.getVisibleLayerVoxel());
int[] countInfo = exportWorld.analyzeTriCount(ExportWorld.ALGORITHM_GREEDY);
console.addLine("Naive Greedy Meshing: " + countInfo[0] + " triangles (" + countInfo[1] + " before) in " + countInfo[2] + "ms");
countInfo = exportWorld.analyzeTriCount(ExportWorld.ALGORITHM_GREEDY_OPTIMAL);
console.addLine("Optimal Greedy Meshing: " + countInfo[0] + " triangles (" + countInfo[1] + " before) in " + countInfo[2] + "ms");
countInfo = exportWorld.analyzeTriCount(ExportWorld.ALGORITHM_MONO);
console.addLine("Monotone Meshing: " + countInfo[0] + " triangles (" + countInfo[1] + " before) in " + countInfo[2] + "ms");
countInfo = exportWorld.analyzeTriCount(ExportWorld.ALGORITHM_MONO_SAVE);
console.addLine("Monotone Meshing (Save in 2D): " + countInfo[0] + " triangles (" + countInfo[1] + " before) in " + countInfo[2] + "ms");
countInfo = exportWorld.analyzeTriCount(ExportWorld.ALGORITHM_POLY2TRI);
console.addLine("Poly2Tri Meshing (Without mesh fixing): " + countInfo[0] +
" triangles (" + countInfo[1] + " before) in " + countInfo[2] + "ms");
}
});
}
});
// check for deadlock
actionManager.registerAction("check_for_deadlock_toggle", new AbstractAction() {
private boolean active = false;
private LifeTimeThread thread;
@Override
public void actionPerformed(ActionEvent e) {
active = !active;
if (active) {
thread = new LifeTimeThread() {
@Override
public void loop() throws InterruptedException {
// -- check for deadlocks
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
// Returns null if no threads are deadlocked.
long[] threadIds = bean.findDeadlockedThreads();
if (threadIds != null) {
// retrieve up to 100 lines of stack trace
ThreadInfo[] infos = bean.getThreadInfo(threadIds, 100);
// print the deadlock information to file
String path = getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
try {
String appJarLocation = URLDecoder.decode(path, "UTF-8");
File appJar = new File(appJarLocation);
String absolutePath = appJar.getAbsolutePath();
String filePath = absolutePath.
substring(0, absolutePath.lastIndexOf(File.separator) + 1);
PrintWriter out = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(filePath + "errorlog.txt", true),"UTF-8")));
out.println("===================");
out.println(DateTools.now("yyyy-MM-dd HH-mm-ss"));
out.println("-------------------");
out.println("Deadlock");
if (Main.isDebugMode()) {
System.err.println("Deadlock");
}
for (ThreadInfo info : infos) {
out.println(info.toString());
// Log or store stack trace information.
for (StackTraceElement ele : info.getStackTrace()) {
out.println(":: " + ele.toString());
}
out.println("-------------------");
if (Main.isDebugMode()) {
System.err.println(info.toString());
// Log or store stack trace information.
for (StackTraceElement ele : info.getStackTrace()) {
System.err.println(":: " + ele.toString());
}
System.err.println("======");
}
}
out.println();
out.close();
} catch (UnsupportedEncodingException ex) {
// If this fails, the program is not reporting.
if (Main.isDebugMode()) {
ex.printStackTrace();
}
} catch (IOException ex) {
// If this fails, the program is not reporting.
if (Main.isDebugMode()) {
ex.printStackTrace();
}
}
// prevent further printing
thread.stopThread();
}
Thread.sleep(5000);
}
};
threadManager.manage(thread);
console.addLine("Deadlock checking is activated.");
} else {
threadManager.remove(thread);
console.addLine("Deadlock checking is deactivated.");
}
}
});
// start/stop test mode (rapid adding/removing of voxel)
actionManager.registerAction("toggle_rapid_voxel_testing",new AbstractAction() {
private boolean active = false;
private final Random rand = new Random();
private LifeTimeThread thread;
private final int size = 9;
private final int perTick = 1;
@Override
public void actionPerformed(ActionEvent e) {
active = !active;
if (active) {
thread = new LifeTimeThread() {
private void toggle() {
int[] pos = new int[] {rand.nextInt(size) - size/2, rand.nextInt(size) - size/2, rand.nextInt(size) - size/2};
Voxel voxel = data.searchVoxel(pos, false);
if (voxel == null) {
data.addVoxel(new Color(rand.nextInt()), null, pos);
} else {
data.removeVoxel(voxel.id);
}
}
@Override
public void loop() throws InterruptedException {
asyncActionManager.addAsyncAction(new AsyncAction() {
@Override
public void performAction() {
for (int i = 0; i < perTick; i++) {
toggle();
}
}
});
synchronized (this) {
thread.wait(50);
}
}
};
threadManager.manage(thread);
console.addLine("Test activated.");
} else {
threadManager.remove(thread);
console.addLine("Test deactivated.");
}
}
});
// holds all console actions
final HashMap<String, String> consoleAction = new HashMap<String, String>();
// needs to be all lower case
consoleAction.put("/clear", "console_action_clear");
consoleAction.put("/debug exception", "create_error_for_debug");
consoleAction.put("/study", "study_object_complexity");
consoleAction.put("/check update", "force_update_check");
consoleAction.put("/test voxel", "toggle_rapid_voxel_testing");
consoleAction.put("/test camera", "toggle_rapid_camera_testing");
consoleAction.put("/texture", "texture_debug_information");
consoleAction.put("/shader", "toggle_shader_enabled");
consoleAction.put("/check deadlock", "check_for_deadlock_toggle");
consoleAction.put("/study holes", "study_holes_print_info");
consoleAction.put("/help", "display_console_commands");
actionManager.registerAction("display_console_commands", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
console.addLine("Available Commands:");
for (String action : consoleAction.keySet()) {
console.addLine(" " + action);
}
console.addLine("-------------------");
}
});
// check current content for holes and print info
actionManager.registerAction("study_holes_print_info", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
HullManagerExt<Integer> hullManager = new HullManagerExt<Integer>();
for (Voxel voxel : data.getVisibleLayerVoxel()) {
hullManager.update(voxel.posId, null);
}
boolean interiorFound = hullManager.computeExterior();
if (interiorFound) {
console.addLine("Holes were detected.");
console.addLine("Voxel faces with holes: " +
(hullManager.getHull(0).length + hullManager.getHull(1).length +
hullManager.getHull(2).length + hullManager.getHull(3).length +
hullManager.getHull(4).length + hullManager.getHull(5).length));
console.addLine("Voxel faces without holes: " +
(hullManager.getExteriorHull(0).length + hullManager.getExteriorHull(1).length +
hullManager.getExteriorHull(2).length + hullManager.getExteriorHull(3).length +
hullManager.getExteriorHull(4).length + hullManager.getExteriorHull(5).length));
} else {
console.addLine("No holes were detected.");
}
}
});
// display the currently loaded textures
actionManager.registerAction("texture_debug_information", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
asyncActionManager.addAsyncAction(new AsyncAction() {
@Override
public void performAction() {
TextureManager manager = TextureManager.getInstance();
console.addLine("Loaded Texture Information (" + manager.getTextureCount() + "):");
HashMap<Point, Integer> amounts = new HashMap<Point, Integer>();
HashMap<Point, Long> sizes = new HashMap<Point, Long>();
for (Enumeration names = manager.getNames(); names.hasMoreElements();) {
String name = names.nextElement().toString();
Texture texture = manager.getTexture(name);
Point dim = new Point(texture.getWidth(), texture.getHeight());
Integer count = amounts.get(dim); // must not be inline (null pointer exception)
if (count == null) {
count = 0;
}
amounts.put(dim, count + 1);
Long memUsage = sizes.get(dim);
if (memUsage == null) {
memUsage = 0L;
}
memUsage += texture.getMemoryUsage();
sizes.put(dim, memUsage);
}
long sizeSum = 0;
for (Map.Entry<Point, Integer> entry : amounts.entrySet()) {
int count = entry.getValue();
Point dim = entry.getKey();
long size = sizes.get(dim);
console.addLine("[" + dim.x + "," + dim.y + "]: " + count + " @ " + String.format("%,.1f", size / 1024.0) + " KB");
sizeSum += size;
}
console.addLine("Total Memory Usage: " + String.format("%,.1f", sizeSum / 1048576.0) + " MB");
}
});
}
});
// register all console actions (so debug know that they are used)
for (String action : consoleAction.values()) {
actionManager.registerActionIsUsed(action);
}
// set textarea colors
inputField.setBackground(VitcoSettings.DEFAULT_DARK_BG_COLOR);
inputField.setBorder(
BorderFactory.createCompoundBorder(
BorderFactory.createMatteBorder(0,0,1,1,VitcoSettings.DEFAULT_BORDER_COLOR),
BorderFactory.createEmptyBorder(3,3,3,3)
)
);
inputField.setForeground(VitcoSettings.SOFT_WHITE);
inputField.setCaretColor(VitcoSettings.SOFT_WHITE);
inputField.addKeyListener(new KeyAdapter() {
// holds previous console commands
String tmpCommand = null;
final ArrayList<String> commands = new ArrayList<String>();
int pos = 0;
@Override
public void keyPressed(final KeyEvent e) {
super.keyPressed(e);
switch (e.getKeyCode()) {
case 10:
String command = inputField.getText().toLowerCase().trim();
final String action = consoleAction.get(command);
if (action != null) {
console.addLine(command); // notify about execution
commands.remove(command); // delete older position
commands.add(command); // add to the end
tmpCommand = null; // reset the tmp command
if (commands.size() > 10) { // keep the history to 10
commands.remove(0);
}
pos = commands.size(); // latest position
inputField.setText(""); // remove command
actionManager.getAction(action).actionPerformed( // execute command
new ActionEvent(e.getSource(), e.hashCode(), e.toString())
);
}
break;
case KeyEvent.VK_UP: // previous command
if (tmpCommand == null) {
tmpCommand = inputField.getText().toLowerCase().trim();
}
if (commands.size() > 0 && pos > 0) {
inputField.setText(commands.get(pos-1));
pos--;
}
break;
case KeyEvent.VK_DOWN: // next command
if (pos + 1 < commands.size()) {
inputField.setText(commands.get(pos + 1));
pos++;
} else if (tmpCommand != null) {
inputField.setText(tmpCommand);
tmpCommand = null;
pos++;
}
break;
default: break;
}
}
});
final JPanel consolePanel = new JPanel();
consolePanel.setLayout(new BorderLayout());
consolePanel.add(scrollPane, BorderLayout.CENTER);
consolePanel.add(inputField, BorderLayout.SOUTH);
// the console itself is in the middle
panel.add(consolePanel, BorderLayout.CENTER);
// create menu bar to the left
CommandMenuBar menuPanel = new CommandMenuBar();
menuPanel.setOrientation(1); // top down orientation
menuGenerator.buildMenuFromXML(menuPanel, "com/vitco/layout/content/console/toolbar.xml");
panel.add(menuPanel, BorderLayout.WEST);
// register toggle actions (auto show / auto scroll)
StateActionPrototype toggleAutoShow = new StateActionPrototype() {
@Override
public boolean getStatus() {
return scrollPane.autoShow;
}
@Override
public void action(ActionEvent e) {
scrollPane.autoShow = !scrollPane.autoShow;
preferences.storeBoolean("console_auto_show_status", scrollPane.autoShow); // store in pref
console.addLine(
"Console Auto Show is " + (scrollPane.autoShow ? "enabled." : "disabled.")
);
}
};
StateActionPrototype toggleAutoScroll = new StateActionPrototype() {
@Override
public boolean getStatus() {
return scrollPane.autoScroll;
}
@Override
public void action(ActionEvent e) {
scrollPane.autoScroll = !scrollPane.autoScroll;
preferences.storeBoolean("console_auto_scroll_status", scrollPane.autoScroll); // store in pref
console.addLine(
"Console Auto Scroll is " + (scrollPane.autoScroll ? "enabled." : "disabled.")
);
}
};
actionManager.registerAction("console_toggle_auto_show", toggleAutoShow);
actionManager.registerAction("console_toggle_auto_scroll", toggleAutoScroll);
// register clear action
AbstractAction clearConsole = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
console.clear(); // the console
textArea.setText(""); // what we display
}
};
actionManager.registerAction("console_action_clear", clearConsole);
return panel;
}
}