diff --git a/pixelwar/src/de/paws/pixelwar/Drawable.java b/pixelwar/src/de/paws/pixelwar/Drawable.java new file mode 100644 index 0000000..93084bf --- /dev/null +++ b/pixelwar/src/de/paws/pixelwar/Drawable.java @@ -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); + +} diff --git a/pixelwar/src/de/paws/pixelwar/Label.java b/pixelwar/src/de/paws/pixelwar/Label.java new file mode 100644 index 0000000..b4af218 --- /dev/null +++ b/pixelwar/src/de/paws/pixelwar/Label.java @@ -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); + } + +} diff --git a/pixelwar/src/de/paws/pixelwar/NetCanvas.java b/pixelwar/src/de/paws/pixelwar/NetCanvas.java index bed6e5f..ed94f6c 100644 --- a/pixelwar/src/de/paws/pixelwar/NetCanvas.java +++ b/pixelwar/src/de/paws/pixelwar/NetCanvas.java @@ -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 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 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); + } + } + } diff --git a/pixelwar/src/de/paws/pixelwar/PixelClientHandler.java b/pixelwar/src/de/paws/pixelwar/PixelClientHandler.java index a2399fa..073a59e 100644 --- a/pixelwar/src/de/paws/pixelwar/PixelClientHandler.java +++ b/pixelwar/src/de/paws/pixelwar/PixelClientHandler.java @@ -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 { private final Set subscriptions = new HashSet<>(); private final Map 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 { @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 { 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 { } 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 "); return; @@ -155,9 +152,7 @@ public class PixelClientHandler extends SimpleChannelInboundHandler { final String message = "PUB " + channel + " " + parts[1].trim(); for (final PixelClientHandler c : clients) { - if (c != this) { - c.writeChannel(channel, message); - } + c.writeChannel(channel, message); } } @@ -172,7 +167,6 @@ public class PixelClientHandler extends SimpleChannelInboundHandler { 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 { if (args[2].length() == 6) { color += 0xff000000; } + label.setPos(x, y); canvas.setPixel(x, y, color); } else { error("Usage: PX x y [rrggbb[aa]]"); diff --git a/pixelwar/src/de/paws/pixelwar/PixelServer.java b/pixelwar/src/de/paws/pixelwar/PixelServer.java index 2e351e1..bce0d00 100644 --- a/pixelwar/src/de/paws/pixelwar/PixelServer.java +++ b/pixelwar/src/de/paws/pixelwar/PixelServer.java @@ -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(); }