save/load buffers and client-pointer and more optimisation

This commit is contained in:
Marcel Hellkamp 2015-02-17 18:53:44 +01:00
parent fb9436c47e
commit 7f195ccdc4
5 changed files with 197 additions and 62 deletions

View file

@ -0,0 +1,25 @@
package de.paws.pixelwar;
import java.awt.Graphics2D;
public abstract class Drawable {
private boolean alive = true;
public final boolean isAlive() {
return alive;
}
public final void setAlive(final boolean state) {
alive = state;
}
public void tick(final long dt) {
}
public void dispose() {
}
public abstract void draw(final Graphics2D g);
}

View file

@ -0,0 +1,46 @@
package de.paws.pixelwar;
import java.awt.Color;
import java.awt.Graphics2D;
public class Label extends Drawable {
public static boolean show = true;
float x = 0;
float y = 0;
int x2 = 0;
int y2 = 0;
String text = "";
public String getText() {
return text;
}
public void setText(final String text) {
this.text = text;
}
public void setPos(final int x, final int y) {
x2 = x;
y2 = y;
}
@Override
public void tick(final long dt) {
x += ((x2 - x) * dt * 0.0001);
y += ((y2 - y) * dt * 0.0001);
}
@Override
public void draw(final Graphics2D g) {
if (!show) {
return;
}
g.setColor(Color.WHITE);
g.drawString(text, (int) x, (int) y);
g.drawLine((int) x, (int) y, x2, y2);
g.drawOval(x2 - 5, y2 - 5, 10, 10);
}
}

View file

@ -11,20 +11,28 @@ import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
public class NetCanvas implements ComponentListener, KeyListener {
private volatile BufferedImage drawBuffer;
private volatile BufferedImage overlayBuffer;
private volatile BufferedImage paintBuffer;
private BufferStrategy strategy;
public class NetCanvas implements ComponentListener, KeyListener,
MouseMotionListener {
private volatile BufferedImage pxBuffer;
private volatile BufferStrategy strategy;
private final Thread refresher;
private final JFrame frame;
private final Canvas canvas;
private final List<Drawable> drawables = new ArrayList<>();
private long lastDraw;
public NetCanvas() {
frame = new JFrame();
@ -33,6 +41,7 @@ public class NetCanvas implements ComponentListener, KeyListener {
frame.addComponentListener(this);
canvas.addKeyListener(this);
canvas.addMouseMotionListener(this);
canvas.setSize(800, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
@ -44,20 +53,36 @@ public class NetCanvas implements ComponentListener, KeyListener {
frame.pack();
frame.setVisible(true);
canvas.createBufferStrategy(2);
strategy = canvas.getBufferStrategy();
refresher = new Thread(new Runnable() {
@Override
public void run() {
lastDraw = System.currentTimeMillis() - 1;
try {
while (true) {
draw();
Thread.sleep(1000 / 30);
final long d1 = System.currentTimeMillis();
draw(d1 - lastDraw);
final long d2 = System.currentTimeMillis();
lastDraw = d1;
Thread.sleep(Math.max(1, (1000 / 30) - (d2 - d1)));
}
} catch (final InterruptedException e) {
}
}
});
refresher.start();
}
public void saveAs(final File file) throws IOException {
ImageIO.write(pxBuffer, "png", file);
}
public void loadFrom(final File file) throws IOException {
final BufferedImage img = ImageIO.read(file);
resizeBuffer(img.getWidth(), img.getHeight());
blit(img, pxBuffer);
}
private void blit(final BufferedImage source, final BufferedImage target) {
@ -70,9 +95,9 @@ public class NetCanvas implements ComponentListener, KeyListener {
BufferedImage newBuffer;
int mw = w, mh = h;
if (drawBuffer != null) {
mw = Math.max(w, drawBuffer.getWidth());
mh = Math.max(h, drawBuffer.getHeight());
if (pxBuffer != null) {
mw = Math.max(w, pxBuffer.getWidth());
mh = Math.max(h, pxBuffer.getHeight());
if (mw > w && mh > h) {
return;
}
@ -80,51 +105,50 @@ public class NetCanvas implements ComponentListener, KeyListener {
newBuffer = frame.getGraphicsConfiguration().createCompatibleImage(w,
h, Transparency.OPAQUE);
if (drawBuffer != null) {
blit(drawBuffer, newBuffer);
if (pxBuffer != null) {
blit(pxBuffer, newBuffer);
}
drawBuffer = newBuffer;
pxBuffer = newBuffer;
overlayBuffer = frame.getGraphicsConfiguration().createCompatibleImage(
w, h, Transparency.TRANSLUCENT);
paintBuffer = frame.getGraphicsConfiguration().createCompatibleImage(w,
h, Transparency.OPAQUE);
frame.getGraphicsConfiguration().createCompatibleImage(w, h,
Transparency.OPAQUE);
}
private void drawOverlay() {
final Graphics2D g = overlayBuffer.createGraphics();
g.setComposite(AlphaComposite.Clear);
g.fillRect(0, 0, overlayBuffer.getWidth(), overlayBuffer.getHeight());
private void drawOverlay(final Graphics2D g, final long dt) {
g.setComposite(AlphaComposite.SrcOver);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.drawString(String.format("Pixelflut"), 2, 10);
g.dispose();
g.drawString(String.format("Pixelflut FPS:%.2f", 1000.0 / dt), 2, 10);
synchronized (drawables) {
final Iterator<Drawable> i = drawables.iterator();
while (i.hasNext()) {
final Drawable d = i.next();
if (d.isAlive()) {
d.tick(dt);
d.draw(g);
} else {
d.dispose();
i.remove();
}
}
}
}
synchronized public void draw() {
synchronized public void draw(final long dt) {
if (!frame.isVisible()) {
return;
}
drawOverlay();
// Prepare composite buffer
final Graphics2D g = paintBuffer.createGraphics();
g.drawImage(drawBuffer, 0, 0, null);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
g.drawImage(overlayBuffer, 0, 0, null);
final Graphics2D g = (Graphics2D) strategy.getDrawGraphics();
g.drawImage(pxBuffer, 0, 0, null);
drawOverlay(g, dt);
g.dispose();
// Blit into double buffer
final Graphics g2 = strategy.getDrawGraphics();
g2.drawImage(paintBuffer, 0, 0, null);
g2.dispose();
strategy.show();
}
public void setPixel(final int x, final int y, final int argb) {
// Below this values 8bit color channels are nulled.
final BufferedImage img = drawBuffer;
final BufferedImage img = pxBuffer;
if (x >= 0 && x < img.getWidth() && y >= 0 && y < img.getHeight()) {
final int alpha = (argb >>> 24) % 256;
@ -157,7 +181,7 @@ public class NetCanvas implements ComponentListener, KeyListener {
}
public int getPixel(final int x, final int y) {
final BufferedImage img = drawBuffer;
final BufferedImage img = pxBuffer;
if (x >= 0 && x < img.getWidth() && y >= 0 && y < img.getHeight()) {
return img.getRGB(x, y) & 0x00ffffff;
}
@ -165,23 +189,31 @@ public class NetCanvas implements ComponentListener, KeyListener {
}
public int getWidth() {
return drawBuffer.getWidth();
return pxBuffer.getWidth();
}
public int getHeight() {
return drawBuffer.getHeight();
return pxBuffer.getHeight();
}
@Override
public void keyReleased(final KeyEvent e) {
if (e.getKeyChar() == 'c') {
final Graphics g = drawBuffer.getGraphics();
final Graphics g = pxBuffer.getGraphics();
g.setColor(Color.BLACK);
g.fillRect(0, 0, drawBuffer.getWidth(), drawBuffer.getHeight());
g.fillRect(0, 0, pxBuffer.getWidth(), pxBuffer.getHeight());
g.dispose();
} else if (e.getKeyChar() == 'q'
|| e.getKeyCode() == KeyEvent.VK_ESCAPE) {
System.exit(0);
} else if (e.getKeyChar() == 'l') {
Label.show = !Label.show;
} else if (e.getKeyChar() == 's') {
try {
saveAs(new File("/tmp/canvas.png"));
} catch (final IOException e1) {
e1.printStackTrace();
}
}
}
@ -212,4 +244,26 @@ public class NetCanvas implements ComponentListener, KeyListener {
public void componentHidden(final ComponentEvent e) {
}
@Override
public void mouseDragged(final MouseEvent e) {
// System.err.println(e.getPoint());
// setPixel(e.getX(), e.getY(), 0xffffffff);
final Graphics2D g = (Graphics2D) pxBuffer.getGraphics();
g.setColor(Color.MAGENTA);
g.fillOval(e.getX() - 50, e.getY() - 50, 100, 100);
g.dispose();
}
@Override
public void mouseMoved(final MouseEvent e) {
// TODO Auto-generated method stub
}
public void addDrawable(final Label label) {
synchronized (drawables) {
drawables.add(label);
}
}
}

View file

@ -2,7 +2,6 @@ package de.paws.pixelwar;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.traffic.TrafficCounter;
import java.util.HashMap;
import java.util.HashSet;
@ -24,14 +23,10 @@ public class PixelClientHandler extends SimpleChannelInboundHandler<String> {
private final Set<String> subscriptions = new HashSet<>();
private final Map<String, CommandHandler> handlers = new HashMap<>();
private long connectedTime;
private long pixelCount = 0;
private long missedMessages = 0;
private Label label;
public PixelClientHandler(final NetCanvas canvas) {
this.canvas = canvas;
final TrafficCounter tc = new TrafficCounter(null, null, null,
connectedTime);
}
public void installHandler(final String command,
@ -42,7 +37,9 @@ public class PixelClientHandler extends SimpleChannelInboundHandler<String> {
@Override
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
connectedTime = System.currentTimeMillis();
label = new Label();
label.setText(ctx.channel().remoteAddress().toString());
canvas.addDrawable(label);
channelContext = ctx;
synchronized (clients) {
clients.add(this);
@ -53,17 +50,17 @@ public class PixelClientHandler extends SimpleChannelInboundHandler<String> {
public void channelInactive(final ChannelHandlerContext ctx)
throws Exception {
super.channelActive(ctx);
final long passed = System.currentTimeMillis() - connectedTime;
synchronized (clients) {
clients.remove(this);
}
label.setAlive(false);
}
public void writeIfPossible(final String str) {
if (channelContext.channel().isWritable()) {
channelContext.write(str + "\n");
channelContext.flush();
} else {
missedMessages++;
}
}
@ -145,7 +142,7 @@ public class PixelClientHandler extends SimpleChannelInboundHandler<String> {
}
private void handle_PUB(final ChannelHandlerContext ctx, final String data) {
final String[] parts = data.split(" ", 1);
final String[] parts = data.split(" ", 2);
if (parts.length != 2) {
error("Usage: PUB <channel> <message>");
return;
@ -155,11 +152,9 @@ public class PixelClientHandler extends SimpleChannelInboundHandler<String> {
final String message = "PUB " + channel + " " + parts[1].trim();
for (final PixelClientHandler c : clients) {
if (c != this) {
c.writeChannel(channel, message);
}
}
}
private void handle_SIZE(final ChannelHandlerContext ctx, final String data) {
if (data.length() > 0) {
@ -172,7 +167,6 @@ public class PixelClientHandler extends SimpleChannelInboundHandler<String> {
private void handle_PX(final ChannelHandlerContext ctx, final String data) {
final String[] args = data.split(" ");
try {
pixelCount++;
if (args.length == 2) {
final int x = Integer.parseInt(args[0]);
final int y = Integer.parseInt(args[1]);
@ -185,6 +179,7 @@ public class PixelClientHandler extends SimpleChannelInboundHandler<String> {
if (args[2].length() == 6) {
color += 0xff000000;
}
label.setPos(x, y);
canvas.setPixel(x, y, color);
} else {
error("Usage: PX x y [rrggbb[aa]]");

View file

@ -17,17 +17,26 @@ import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
public class PixelServer extends ChannelHandlerAdapter {
private final NetCanvas canvas;
private final int port;
private final File savefile = new File("/tmp/canvas.png");
public PixelServer(final int port) {
public PixelServer(final int port) throws IOException {
this.port = port;
canvas = new NetCanvas();
if (savefile.exists()) {
canvas.loadFrom(savefile);
}
}
public void run() throws InterruptedException {
public void run() throws InterruptedException, UnknownHostException {
final EventLoopGroup bossGroup = new NioEventLoopGroup(1);
final EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
@ -48,14 +57,14 @@ public class PixelServer extends ChannelHandlerAdapter {
128, Delimiters.lineDelimiter()));
p.addLast("decoder", new StringDecoder());
p.addLast("encoder", new StringEncoder());
// and then business logic.
p.addLast("handler", new PixelClientHandler(canvas));
}
});
// Start the server.
final ChannelFuture f = b.bind(port).sync();
final ChannelFuture f = b.bind(
new InetSocketAddress("0.0.0.0", port)).sync();
// Wait until the server socket is closed.
f.channel().closeFuture().sync();
@ -63,10 +72,16 @@ public class PixelServer extends ChannelHandlerAdapter {
// Shut down all event loops to terminate all threads.
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
try {
canvas.saveAs(savefile);
} catch (final IOException e) {
e.printStackTrace();
}
}
}
public static void main(final String[] args) throws InterruptedException {
public static void main(final String[] args) throws InterruptedException,
IOException {
new PixelServer(8080).run();
}