package mpe;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.lang.ProcessBuilder.Redirect;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Vector;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicBoolean;
//we need to access processing from this class
import processing.core.*;
//import processing.event.KeyEvent;
//import processing.event.MouseEvent;
/**
* This class facilitates the execution of multiple Processing applications across a distributed environment.
* @author Brandt Westing TACC
*
*/
public class Process extends Thread {
public final static String VERSION = "##version##";
// used for timers
public static long start;
public static long end;
public static long elapsed;
// contains identifier and screen-space info for this process
Configuration config_;
/*
* A process may either be either a leader or follower.
* There is only one leader - designated in configuration file as 'head'
* The leader maintains a connection to all followers and issues frame events.
* Followers simply maintain a connection to the leader and wait for frame events.
*/
// if we are the leader, this will contain follower communication channels
Vector<Connection> clients_;
// if we are a follower, this will be our communication layer to the leader
Connection leader_;
// the processing applet that is running on this host
PApplet pApplet_;
// true if the processing sketch uses P3D, OpenGL, or GLGraphics
boolean enable3D_ = false;
// true when every follower is connected to the leader process
boolean allConnected_ = false;
// true while thread is executing
boolean running_ = false;
// true if the camera should be placed during placeScreen. Set to false for peasyCam, etc.
boolean placeCamera_ = true;
// true if auto-start is enabled (Default)
boolean autostart_ = true;
// keeps track of the state of the followers
FollowerState followerState_;
// are we in debug mode?
boolean debug_ = false;
// this is the object on which synchronization is based
private final FrameLock frameLock_ = new FrameLock();
// the cyclic barrier servers as a barrier synchronization for all render clients
public final CyclicBarrier barrier_;
// have we notified?
AtomicBoolean notified_;
// do we have input to send along with the next FE message? (leader)
boolean sendAttributes_;
// do we have input from the server along with the FE message? (follower)
boolean receivedAttributes_;
// this is the actual object that will be sent along with the FE message
Object attribute_;
// mouse and keyboard events sent every frame
//MouseEvent mouseEvent_ = null;
//KeyEvent keyEvent_ = null;
// parameters needed to properly set viewing frustum
float cameraZ_;
float fov_ = 45.0f;
// socket if the process is a follower, and complementary streams
Socket processSocket_;
ObjectInputStream ois_;
ObjectOutputStream oos_;
AutoLauncher autoLauncher_;
// by default, do not serialize mouse and keyboard events (they are not serializable yet) //todo
//boolean enableDefaultSerialization_ = false;
/*
* Begin public methods
*/
/**
* Constructor.
*
* @param config The configuration file for the tile configuration.
*/
public Process(Configuration config)
{
pApplet_ = config.getApplet();
notified_ = new AtomicBoolean(false);
// construct the configuration object
config_ = config;
debug_ = config_.getDebug();
// create the followerState, which keeps track of how many renderers have rendered and are waiting
followerState_ = new FollowerState(config_.getNumFollowers());
if(debug_) print("Number of followers: " + config_.getNumFollowers());
// is this a 3D (P3D/OpenGL/GLGraphics) or 2D (P2D) sketch?
enable3D_ = true;
// set the Z location of the camera
cameraZ_ = (config_.getMasterDim()[1]/2.0f) / PApplet.tan(PConstants.PI * fov_/180.0f);
// after the sketch calls draw, it will call the draw method in this class
pApplet_.registerMethod("draw", this);
pApplet_.registerMethod("pre", this);
// when the sketch is stopped, it will call the dispose method in this class
//pApplet_.registerMethod("dispose", this);
// by default, automatically serialize mouse and keyboard events
barrier_ = new CyclicBarrier(config_.numFollowers_ + 1);
//set the initial window location of the processing sketch
pApplet_.frame.setLocation(config_.getWindowLocation()[0],config_.getWindowLocation()[1]);
pApplet_.frame.setTitle(pApplet_.getClass().getName()+ " window, rank: " + config_.getRank());
if (debug_) {
System.out.println("Setting window location to: "+config_.getWindowLocation()[0]+", "+config_.getWindowLocation()[1]);
}
if (debug_)
config_.printSettings();
running_ = true;
}
/**
* Registered with the PApplet pre() function and called before each draw loop.
*/
public void pre()
{
if(debug_) print("Trying to acquire framelock!");
// wait on framelock to be unlocked
frameLock_.acquire();
if(debug_) print("Acquired framelock!");
placeScreen();
}
/**
* Registered with the PApplet draw() function and called after each draw loop.
*/
public void draw()
{
// send end-of-frame message if not leader
if(!config_.isLeader())
endFrame();
}
/**
* Called to free resources before shutting down. This should only be called by PApplet.
* The dispose() method is what gets called when the host applet is being shut down,
* so this should stop any threads, disconnect from the net, unload memory, etc.
*/
public void dispose()
{
if(debug_) print("Shutting down MPE");
//autoLauncher_.shutDown();
System.exit(0);
for(int i = 0; i < clients_.size(); i++)
{
if(debug_) print("Shuting down client process "+i);
// shut down client processes here
//Connection c = clients_.elementAt(i);
//c = null;
clients_.elementAt(i).interrupt();
}
return;
}
/**
* Registered with the PApplet mouseEvent() function and called when mouse interaction occurs.
*/
/*
public void mouseEvent(MouseEvent e)
{
// create mouseEvent object
mouseEvent_ = e;
}
*/
/**
* Registered with the PApplet keyEvent() function and called when keyboard interaction occurs.
*/
/*
public void keyEvent(KeyEvent e)
{
// create mouseEvent object
keyEvent_ = e;
}
*/
/**
* Starts the communication thread.
*/
public void start()
{
/*
if(enableDefaultSerialization_)
{
//pApplet_.registerMouseEvent(this);
//pApplet_.registerKeyEvent(this);
//pApplet_.registerMethod("mouseEvent", this);
//pApplet_.registerMethod("keyEvent", this);
}
*/
// we are just a follower, register with leader
if(!config_.isLeader())
{
// set up socket to leader, retrying every two seconds if fail
boolean notConnected = true;
while(notConnected)
{
notConnected = false;
try {
processSocket_ = new Socket(config_.getServer(), config_.getPort());
// disable Nagle's algorithm, otherwise we get TCP delays of ~40ms
processSocket_.setTcpNoDelay(true);
oos_ = new ObjectOutputStream(processSocket_.getOutputStream());
ois_ = new ObjectInputStream(processSocket_.getInputStream());
} catch (UnknownHostException e) {
System.out.println("Can't connect to leader process! Did you specify a 'head' process in config? Retrying!");
e.printStackTrace();
notConnected = true;
} catch (IOException e) {
System.out.println("Can't connect to leader process! Did you specify a 'head' process in config? Retrying!");
e.printStackTrace();
notConnected = true;
}
// sleep for 2 seconds and then retry to connect to leader
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
// we are the leader, create connection listener(s)
if(config_.isLeader())
{
clients_ = new Vector<Connection>();
// create thread to launch processes on remote nodes
if(autostart_)
{
autoLauncher_ = new AutoLauncher(config_.getFilename(), pApplet_.sketchPath);
autoLauncher_.start();
//AutoLauncher autoLauncher = new AutoLauncher(config_.getFilename(), pApplet_.sketchPath);
//autoLauncher.start();
}
// set listener for all connections
ServerSocket listener = null;
Socket followerSocket = null;
try {
listener = new ServerSocket(config_.getPort());
} catch (IOException e) {
System.out.println("Unable to listen on port " + config_.getPort() + " , quitting.");
System.exit(-1);
}
// whenever a new connection is made, create a handler thread
while(!followerState_.allConnected())
{
try {
followerSocket = listener.accept();
// disable Nagle's algorithm, otherwise we get TCP delays of ~40ms
followerSocket.setTcpNoDelay(true);
if(debug_) print("Received a client connection: " + followerSocket.getInetAddress());
} catch (IOException e) {
System.out.println("Unable to accept connection!");
}
// new client, so increment the counter of the number of connected
followerState_.incrementConnected();
// create a new connection object for this client and run communication in a separate thread
Connection conn = new Connection(followerSocket, followerState_, this);
conn.start();
// add the client to the clients vectors, so that we can later broadcast, etc.
clients_.add(conn);
}
if(debug_) print("All clients have connected. Start event loop");
}
// calls the run() command based on Java thread semantics
super.start();
}
/**
* Called auto-magically by start().
*/
public void run()
{
while(running_)
{
// wait for all followers to be ready and broadcast msg to render
if(config_.isLeader())
{
try {
barrier_.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
barrier_.reset();
// release the framelock so master can render
if(debug_) print("Releasing framelock!");
frameLock_.release();
// send a FE message to all clients so they render the next scene
Process.start = System.currentTimeMillis();
broadcastFE();
}
// we are a follower and we should receive a msg
else
{
Command command = null;
try {
command = (Command) ois_.readObject();
} catch(Exception e){
try {
shutDown();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if(command == null)
break; // remote end hung up
readCommand(command);
}
}
}
/**
* Gets the pixel width of the local display.
*
* @return The pixel width of the local display.
*/
public int getLWidth()
{
return config_.getLocalDim()[0];
}
/**
* Gets the pixel height of the local display.
*
* @return The pixel height of the local display.
*/
public int getLHeight()
{
return config_.getLocalDim()[1];
}
public int getMWidth()
{
return config_.getMasterDim()[0];
}
public int getMHeight()
{
return config_.getMasterDim()[1];
}
/**
* Disables MPE camera placement at each draw. Used for libraries such as PeasyCam, etc.
*/
public void disableCameraReset()
{
placeCamera_ = false;
}
/**
* Disables autostart of MPE children across cluster. To use MPE you must use mperun script.
*/
public void disableAutoStart()
{
autostart_ = false;
}
/**
* Disables the automatic serialization of mouse and keyboard events from the head process to the render processes.
*/
/*
public void disableDefaultSerialization()
{
enableDefaultSerialization_ = false;
}
*/
/**
* Allows the user to manually set the field of view of the camera.
* @param fov The desired field of view in degrees.
*/
public void setFOV(float fov)
{
fov_ = fov;
cameraZ_ = (config_.getMasterDim()[1]/2.0f) / PApplet.tan(PConstants.PI * fov_/180.0f);
}
/*
public void compensateMullions(PApplet pa)
{
PGraphics buf = createGraphics(config_.getLWidth(), config_.getLHeight(), P3D);
PGraphics screen = pa.g;
}
*/
/*
* Begin private methods
*/
private void placeScreen()
{
if(enable3D_)
placeScreen3D();
else
placeScreen2D();
}
// computes the correct viewing frustum for the perspective view
private void placeScreen3D()
{
/*
* This is tough.
*
* Processing flips the Y coordinate in OpenGL vs. P3D, first of all.
* We wont be using P3D, so this shouldn't matter, the resolutions in question
* preclude the use of anything but OpenGL.
*/
if(placeCamera_)
pApplet_.camera(config_.getMasterDim()[0]/2.0f, config_.getMasterDim()[1]/2.0f, cameraZ_,
config_.getMasterDim()[0]/2.0f, config_.getMasterDim()[1]/2.0f, 0,
0, 1, 0);
// Frustum information assuming X = 0 @ top left, and Y = 0 @ top left
/* from my processing work
float left = offsetX - mX/2;
float right = (offsetX + lX) - mX/2;
float top = offsetY - mY/2;
float bottom = (offsetY + lY) - mY/2;
float near = cameraZ;
float far = 1000;
*/
float mod = 0.1f;
float left = (config_.getOffsets()[0] - config_.getMasterDim()[0]/2.0f)*mod;
float right = ((config_.getOffsets()[0] + config_.getLocalDim()[0]) - config_.getMasterDim()[0]/2.0f)*mod;
float bottom = (config_.getOffsets()[1] - config_.getMasterDim()[1]/2.0f)*mod;
float top = ((config_.getOffsets()[1] + config_.getLocalDim()[1]) - config_.getMasterDim()[1]/2.0f)*mod;
float near = cameraZ_*mod;
float far = 10000;
pApplet_.frustum(left,right,bottom,top,near,far);
if(debug_) print("left: "+left+" right: "+right+" bottom: "+bottom+" top: "+top);
if(debug_) print("master width: "+getMWidth()+" master height: "+getMHeight()+" local width: "+getLWidth()+" local height: "+getLHeight());
if(debug_) print("offset 0: "+config_.getOffsets()[0]+", offset 1: "+config_.getOffsets()[1]);
/*
double near = 0.1;
double far = 10000.;
double aspect = getMHeight() / getMWidth() * getLHeight() / getLWidth();
double winFovY = fov_ * aspect;
double top = PApplet.tan(0.5 * winFovY * PConstants.PI/180.) * near;
double bottom = -top;
double left = 1./aspect * bottom;
double right = 1./aspect * top;
double fleft = left + ()
*/
}
// simply offsets the screen in space
private void placeScreen2D()
{
if (debug_)
System.out.println("Placing screen at: " + config_.getOffsets()[0]*-1 + ", " + config_.getOffsets()[1]*-1);
pApplet_.translate(config_.getOffsets()[0] * -1, config_.getOffsets()[1] * -1);
}
// reads the command from the leader, releases the frameLock if the FE command was sent
private void readCommand(Command c)
{
// received a frame event command from server, unlock framelock object
if(c.command.equals("fe"))
{
if(debug_) print("Received FE");
// receives the attribute if not null
if(c.att != null)
{
receivedAttributes_ = true;
attribute_ = c.att;
}
/*
// gets mouse and keyboard events from head process
if(c.k != null)
{
pApplet_.keyEvent = c.k;
}
if(c.m != null)
{
pApplet_.mouseEvent = c.m;
}
*/
if(debug_) print("Releasing framelock!");
// release the framelock
frameLock_.release();
}
}
private void broadcastFE()
{
// create frame event command
Command command = new Command();
command.command = "fe";
// set keyboard and mouse
//command.k = keyEvent_;
//command.m = mouseEvent_;
if(sendAttributes_)
{
command.att = attribute_;
}
else command.att = null;
// we have appended the attribute to the command, and can now set it back to false such that repeated
// messages are not sent
sendAttributes_ = false;
for(int i = 0; i < clients_.size(); i++)
{
clients_.elementAt(i).sendCommand(command);
}
// set events to null so they are not resent
//keyEvent_ = null;
//mouseEvent_ = null;
}
// sends msg to leader indicating the frame has been drawn
private void endFrame()
{
Command command = new Command();
command.command = "ef";
try {
oos_.writeObject(command);
} catch (IOException e) {
System.out.println("Unable to write to server! Server disconnected.");
System.exit(-1);
}
}
public void print(String msg)
{
System.out.println(config_.getRank() + ": " + msg);
}
public boolean getDebug()
{
return debug_;
}
// the sketch requested that the attributes be sent to all other processes
// note, interaction intended to occur on leader process, for other interaction use
// asynchronous client
/**
* Broadcasts the object(s) to all client processes.
* @param attribute The object to broadcast. Can be casted to any object.
*/
public void broadcast(Object attribute)
{
sendAttributes_ = true;
attribute_ = attribute;
}
// returns true if the last FE message was received with non-null atts
/**
* Determines if a message has been received since last frame draw.
* @return True if a message has been received since last frame draw.
*/
public boolean messageReceived()
{
return receivedAttributes_;
}
// returns the attributes that were received from the leader
/**
* If a message has been received, returns the message.
* @return The message that has been received. Cast to the desired object.
*/
public Object getMessage()
{
if(receivedAttributes_)
{
receivedAttributes_ = false;
return attribute_;
}
else
{
print("No attributes were received! Check w/ messageReceived() first!");
return null;
}
}
public void shutDown() throws IOException
{
// kill all previously launched process'
java.lang.Process kp;
java.lang.ProcessBuilder pb;
try {
pb = new ProcessBuilder("pkill", "-9", "-f", "agentlib");
String path = "/home/vislab/Processing/sketchbook/MPEPeasy/log";
if(debug_){
// BufferedWriter pw = new BufferedWriter(new FileWriter(path + config_.getRank()));
File log = new File(path);
pb.redirectErrorStream(true);
pb.redirectOutput(Redirect.appendTo(log));
// pw.append(config_.getRank() + ": inside shutdown");
// pw.close();
}
kp = pb.start();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}