Reputation: 125
I'm currently working in following class, a paint program with a while loop sending packages from client to server:
public class TCPClient extends JPanel {
public static ArrayList<Point> location = new ArrayList<>();
private JTextArea consoleOutput = new JTextArea(1,20);
public void addComponentToPane(Container pane) {
consoleOutput.setEditable(false);
}
public TCPClient() {
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
location.add(e.getPoint());
}
});
addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
location.add(e.getPoint());
repaint();
}
});
setPreferredSize(new Dimension(800, 500));
setBackground(Color.WHITE);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if(location.isEmpty()){
return;
}
Point p = location.get(0);
for (int i = 1; i < location.size(); i++) {
Point q = location.get(i);
g.drawLine(p.x, p.y, q.x, q.y);
p = q;
}
}
public static void main(String argv[]) throws Exception {
InetAddress SERVERIP = InetAddress.getLocalHost();
JFrame frame = new JFrame("Drawing with friends");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TCPClient(), BorderLayout.CENTER);
JTextArea IPadress = new JTextArea(1,20);
IPadress.setEditable(false);
IPadress.append("DEVICE IP: " + SERVERIP.getHostAddress());
frame.add(IPadress, BorderLayout.SOUTH);
frame.setSize(new Dimension(800,600));
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setVisible(true);
while(true) {
try {
//BufferedReader inFromUser = new BufferedReader(new InputStreamReader(System.in));
Socket clientSocket = new Socket("localhost", 9000);
ObjectOutputStream outToServer = new ObjectOutputStream(clientSocket.getOutputStream());
//ObjectInputStream inFromServer = new ObjectInputStream(clientSocket.getInputStream());
outToServer.writeObject(location);
outToServer.flush();
clientSocket.close();
Thread.sleep(100);
} catch (SocketException e) {
System.err.println(e.toString());
}
}
}
}
As it my while loop runs constantly with a sleep(100) every loop. This works as intended, however for effeciency I want the loop to run everytime something is changed in my ArrayList locations, so it doesn't send irrellevant packages.
What I'm trying to achieve:
if(change in location) {
Send information to server
}
Upvotes: 1
Views: 106
Reputation: 44995
You will need to wrap any accesses (read
and write
) to your variable location into a synchronized
block with a notify()
in case you update it and then in your loop you replace the sleep
with a wait
as next:
Modification of location:
synchronized (location) {
location.add(e.getPoint());
location.notify();
}
Read access to location:
synchronized (location) {
if(location.isEmpty()){
return;
}
Point p = location.get(0);
for (int i = 1; i < location.size(); i++) {
Point q = location.get(i);
g.drawLine(p.x, p.y, q.x, q.y);
p = q;
}
}
Final loop inside a synchronized block:
synchronized (location) {
while(true) {
//BufferedReader inFromUser = new BufferedReader(new InputStreamReader(System.in));
Socket clientSocket = new Socket("localhost", 9000);
ObjectOutputStream outToServer = new ObjectOutputStream(clientSocket.getOutputStream());
//ObjectInputStream inFromServer = new ObjectInputStream(clientSocket.getInputStream());
outToServer.writeObject(location);
outToServer.flush();
clientSocket.close();
location.wait();
}
}
Upvotes: 1
Reputation: 347314
Basically, you should use some kind of BlockingQueue
, this will allow your "socket" thread to "block" while waiting for new Point
s to arrive.
Instead of sending the whole List
, you should instead, send each Point
individually, this will save on time as the number of points grows.
Because of the nature of the queue, you can keep adding more points to it and the "socket" thread can process them at it's own pace, this will allow the queue to increase in size as the user draws, but allow the "socket" thread to catch up when the user stops, without your doing anything special.
The following example uses a Thread.sleep
to generate an artificial delay between the processing of each point from the queue for demonstration purposes and, obviously, you shouldn't use it (Thread.sleep
), but it demonstrates the points above, the thread will continue to dump points from the queue to the stdout after you stop drawing
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
private BlockingQueue<Point> queue;
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
queue = new LinkedBlockingQueue<>();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TCPClient(queue));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Thread t = new Thread(new Consumer(queue));
t.setDaemon(true);
t.start();
}
});
}
public class TCPClient extends JPanel {
private JTextArea consoleOutput = new JTextArea(1, 20);
private Queue<Point> queue;
private List<Point> cache;
public TCPClient(Queue<Point> queue) {
this.queue = queue;
cache = new ArrayList<>(25);
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
queue.add(e.getPoint());
cache.add(e.getPoint());
}
});
addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
queue.add(e.getPoint());
cache.add(e.getPoint());
repaint();
}
});
setPreferredSize(new Dimension(800, 500));
setBackground(Color.WHITE);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (cache.isEmpty()) {
return;
}
Point p = cache.get(0);
for (int i = 1; i < cache.size(); i++) {
Point q = cache.get(i);
g.drawLine(p.x, p.y, q.x, q.y);
p = q;
}
}
}
public class Consumer implements Runnable {
private BlockingQueue<Point> queue;
public Consumer(BlockingQueue<Point> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
Point p = queue.poll(Long.MAX_VALUE, TimeUnit.DAYS);
if (p != null) {
System.out.println("-> Got " + p);
Thread.sleep(125);
}
} catch (InterruptedException ex) {
}
}
}
}
}
Upvotes: 1
Reputation: 4544
You could create your own CallbackArrayList
, which extends ArrayList
.
Add abstract callback methods such as onAdded
and onRemoved
.
Override those methods of ArrayList
which you want to monitor and call your callback methods inside, depending on the result.
abstract class CallbackArrayList<T> extends ArrayList<T> {
public abstract void onAddSuccess(T object);
public abstract void onAddFailure(T object);
@Override
public boolean add(T object) {
boolean success = super.add(object);
if(success) {
onAddSuccess(object);
}
else {
onAddFailure(object);
}
return success;
}
}
Then when assigning your list you can do
location = new CallbackArrayList<>() {
@Override
public void onAddSuccess(Point object) {
// handle send information to server
}
@Override
public void onAddFailure(Point object) {
// handle failure
}
};
Whenever you call location.add(e.getPoint())
, one of the callback methods are called afterwards.
Upvotes: 0
Reputation: 4049
Instead of allowing other objects to modify your ArrayList
directly, make it private and create a getter, a setter, and also (because you're working with collections) an adder and a remover method.
Inside those, you can notify the other parts of the program, that the ArrayList
was changed, and you need to send a packet.
Upvotes: 0