From fb9436c47e6220f70b40ea74bbc02b1a988a8fed Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Tue, 10 Feb 2015 15:01:35 +0100 Subject: [PATCH] PUB/SUB and HELP commands. Several GUI fixes. --- pixelwar/.settings/org.eclipse.jdt.ui.prefs | 24 ++- pixelwar/pom.xml | 5 + pixelwar/src/de/paws/pixelwar/NetCanvas.java | 129 +++++++----- .../de/paws/pixelwar/PixelClientHandler.java | 197 ++++++++++++++---- .../src/de/paws/pixelwar/PixelServer.java | 18 +- 5 files changed, 255 insertions(+), 118 deletions(-) diff --git a/pixelwar/.settings/org.eclipse.jdt.ui.prefs b/pixelwar/.settings/org.eclipse.jdt.ui.prefs index 1e757e8..6b47616 100644 --- a/pixelwar/.settings/org.eclipse.jdt.ui.prefs +++ b/pixelwar/.settings/org.eclipse.jdt.ui.prefs @@ -13,12 +13,14 @@ sp_cleanup.always_use_blocks=true sp_cleanup.always_use_parentheses_in_expressions=false sp_cleanup.always_use_this_for_non_static_field_access=false sp_cleanup.always_use_this_for_non_static_method_access=false -sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.convert_functional_interfaces=false +sp_cleanup.convert_to_enhanced_for_loop=true sp_cleanup.correct_indentation=false sp_cleanup.format_source_code=true sp_cleanup.format_source_code_changes_only=false -sp_cleanup.make_local_variable_final=false -sp_cleanup.make_parameters_final=false +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.make_local_variable_final=true +sp_cleanup.make_parameters_final=true sp_cleanup.make_private_fields_final=true sp_cleanup.make_type_abstract_if_missing_method=false sp_cleanup.make_variable_declarations_final=true @@ -29,15 +31,16 @@ sp_cleanup.organize_imports=true sp_cleanup.qualify_static_field_accesses_with_declaring_class=false sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true -sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_with_declaring_class=true sp_cleanup.qualify_static_method_accesses_with_declaring_class=false sp_cleanup.remove_private_constructors=true -sp_cleanup.remove_trailing_whitespaces=false +sp_cleanup.remove_redundant_type_arguments=false +sp_cleanup.remove_trailing_whitespaces=true sp_cleanup.remove_trailing_whitespaces_all=true sp_cleanup.remove_trailing_whitespaces_ignore_empty=false sp_cleanup.remove_unnecessary_casts=true sp_cleanup.remove_unnecessary_nls_tags=false -sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_imports=true sp_cleanup.remove_unused_local_variables=false sp_cleanup.remove_unused_private_fields=true sp_cleanup.remove_unused_private_members=false @@ -45,10 +48,13 @@ sp_cleanup.remove_unused_private_methods=true sp_cleanup.remove_unused_private_types=true sp_cleanup.sort_members=false sp_cleanup.sort_members_all=false -sp_cleanup.use_blocks=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_blocks=true sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=false sp_cleanup.use_parentheses_in_expressions=false -sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access=true sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true -sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access=true sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true +sp_cleanup.use_type_arguments=false diff --git a/pixelwar/pom.xml b/pixelwar/pom.xml index ec7c128..f9ea782 100644 --- a/pixelwar/pom.xml +++ b/pixelwar/pom.xml @@ -56,5 +56,10 @@ netty-all 4.0.18.Final + + org.apache.commons + commons-lang3 + 3.3.2 + \ No newline at end of file diff --git a/pixelwar/src/de/paws/pixelwar/NetCanvas.java b/pixelwar/src/de/paws/pixelwar/NetCanvas.java index e6a7f92..bed6e5f 100644 --- a/pixelwar/src/de/paws/pixelwar/NetCanvas.java +++ b/pixelwar/src/de/paws/pixelwar/NetCanvas.java @@ -8,69 +8,42 @@ import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.Transparency; import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; import java.awt.image.BufferStrategy; import java.awt.image.BufferedImage; import javax.swing.JFrame; -public class NetCanvas { +public class NetCanvas implements ComponentListener, KeyListener { private volatile BufferedImage drawBuffer; private volatile BufferedImage overlayBuffer; private volatile BufferedImage paintBuffer; - private final BufferStrategy strategy; + private BufferStrategy strategy; private final Thread refresher; private final JFrame frame; private final Canvas canvas; public NetCanvas() { - - frame = new JFrame() { - private static final long serialVersionUID = 1L; - - @Override - protected void processKeyEvent(KeyEvent e) { - super.processKeyEvent(e); - if (e.getKeyChar() == 'c') { - Graphics g = drawBuffer.getGraphics(); - g.setColor(Color.BLACK); - g.fillRect(0, 0, drawBuffer.getWidth(), - drawBuffer.getHeight()); - g.dispose(); - } - if (e.getKeyChar() == 'q' - || e.getKeyCode() == KeyEvent.VK_ESCAPE) { - System.exit(0); - } - } - - @Override - protected void processComponentEvent(ComponentEvent e) { - super.processComponentEvent(e); - if (e.getID() == ComponentEvent.COMPONENT_RESIZED) { - resizeBuffer(getWidth(), getHeight()); - } - } - }; - + frame = new JFrame(); canvas = new Canvas(); - canvas.setSize(800, 600); + resizeBuffer(1024, 1024); + frame.addComponentListener(this); + canvas.addKeyListener(this); + + canvas.setSize(800, 600); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setTitle("Pixelflut"); // frame.setUndecorated(true); frame.setResizable(true); // frame.setAlwaysOnTop(true); - frame.add(canvas); frame.pack(); frame.setVisible(true); - canvas.createBufferStrategy(2); - strategy = canvas.getBufferStrategy(); - resizeBuffer(1024, 1024); - refresher = new Thread(new Runnable() { @Override public void run() { @@ -79,34 +52,37 @@ public class NetCanvas { draw(); Thread.sleep(1000 / 30); } - } catch (InterruptedException e) { + } catch (final InterruptedException e) { } } }); refresher.start(); + } - private void blit(BufferedImage source, BufferedImage target) { - Graphics g = target.getGraphics(); + private void blit(final BufferedImage source, final BufferedImage target) { + final Graphics g = target.getGraphics(); g.drawImage(source, 0, 0, null); g.dispose(); } - synchronized private void resizeBuffer(int w, int h) { + synchronized private void resizeBuffer(final int w, final int h) { BufferedImage newBuffer; int mw = w, mh = h; if (drawBuffer != null) { mw = Math.max(w, drawBuffer.getWidth()); mh = Math.max(h, drawBuffer.getHeight()); - if (mw > w && mh > h) + if (mw > w && mh > h) { return; + } } newBuffer = frame.getGraphicsConfiguration().createCompatibleImage(w, h, Transparency.OPAQUE); - if (drawBuffer != null) + if (drawBuffer != null) { blit(drawBuffer, newBuffer); + } drawBuffer = newBuffer; overlayBuffer = frame.getGraphicsConfiguration().createCompatibleImage( @@ -117,7 +93,7 @@ public class NetCanvas { } private void drawOverlay() { - Graphics2D g = overlayBuffer.createGraphics(); + final Graphics2D g = overlayBuffer.createGraphics(); g.setComposite(AlphaComposite.Clear); g.fillRect(0, 0, overlayBuffer.getWidth(), overlayBuffer.getHeight()); g.setComposite(AlphaComposite.SrcOver); @@ -128,34 +104,38 @@ public class NetCanvas { } synchronized public void draw() { + if (!frame.isVisible()) { + return; + } drawOverlay(); // Prepare composite buffer - Graphics2D g = paintBuffer.createGraphics(); + final Graphics2D g = paintBuffer.createGraphics(); g.drawImage(drawBuffer, 0, 0, null); g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); g.drawImage(overlayBuffer, 0, 0, null); g.dispose(); // Blit into double buffer - Graphics g2 = strategy.getDrawGraphics(); + final Graphics g2 = strategy.getDrawGraphics(); g2.drawImage(paintBuffer, 0, 0, null); g2.dispose(); strategy.show(); } - public void setPixel(int x, int y, int argb) { + public void setPixel(final int x, final int y, final int argb) { // Below this values 8bit color channels are nulled. - BufferedImage img = drawBuffer; + final BufferedImage img = drawBuffer; if (x >= 0 && x < img.getWidth() && y >= 0 && y < img.getHeight()) { - int alpha = (argb >>> 24) % 256; + final int alpha = (argb >>> 24) % 256; int rgb = argb & 0xffffff; - if (alpha < 1) + if (alpha < 1) { return; + } if (alpha < 255) { - int target = img.getRGB(x, y); + final int target = img.getRGB(x, y); int r, g, b; r = ((target >>> 16) & 0xff) * (255 - alpha); @@ -176,10 +156,11 @@ public class NetCanvas { } } - public int getPixel(int x, int y) { - BufferedImage img = drawBuffer; - if (x >= 0 && x < img.getWidth() && y >= 0 && y < img.getHeight()) + public int getPixel(final int x, final int y) { + final BufferedImage img = drawBuffer; + if (x >= 0 && x < img.getWidth() && y >= 0 && y < img.getHeight()) { return img.getRGB(x, y) & 0x00ffffff; + } return 0; } @@ -191,4 +172,44 @@ public class NetCanvas { return drawBuffer.getHeight(); } + @Override + public void keyReleased(final KeyEvent e) { + if (e.getKeyChar() == 'c') { + final Graphics g = drawBuffer.getGraphics(); + g.setColor(Color.BLACK); + g.fillRect(0, 0, drawBuffer.getWidth(), drawBuffer.getHeight()); + g.dispose(); + } else if (e.getKeyChar() == 'q' + || e.getKeyCode() == KeyEvent.VK_ESCAPE) { + System.exit(0); + } + } + + @Override + public void keyPressed(final KeyEvent e) { + } + + @Override + public void keyTyped(final KeyEvent e) { + } + + @Override + public void componentResized(final ComponentEvent e) { + resizeBuffer(canvas.getWidth(), canvas.getHeight()); + } + + @Override + public void componentMoved(final ComponentEvent e) { + } + + @Override + public void componentShown(final ComponentEvent e) { + canvas.createBufferStrategy(2); + strategy = canvas.getBufferStrategy(); + } + + @Override + public void componentHidden(final ComponentEvent e) { + } + } diff --git a/pixelwar/src/de/paws/pixelwar/PixelClientHandler.java b/pixelwar/src/de/paws/pixelwar/PixelClientHandler.java index 0198365..a2399fa 100644 --- a/pixelwar/src/de/paws/pixelwar/PixelClientHandler.java +++ b/pixelwar/src/de/paws/pixelwar/PixelClientHandler.java @@ -2,90 +2,195 @@ 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; +import java.util.Map; import java.util.Set; +import org.apache.commons.lang3.StringUtils; + public class PixelClientHandler extends SimpleChannelInboundHandler { + public interface CommandHandler { + public void handle(PixelClientHandler client, String[] args); + } + private static Set clients = new HashSet<>(); private final NetCanvas canvas; - private long connected; - private long c = 0; - private ChannelHandlerContext channel; + private ChannelHandlerContext channelContext; + private final Set subscriptions = new HashSet<>(); + private final Map handlers = new HashMap<>(); - public PixelClientHandler(NetCanvas canvas) { + private long connectedTime; + private long pixelCount = 0; + private long missedMessages = 0; + + public PixelClientHandler(final NetCanvas canvas) { this.canvas = canvas; + final TrafficCounter tc = new TrafficCounter(null, null, null, + connectedTime); + } + + public void installHandler(final String command, + final CommandHandler handler) { + handlers.put(command.toUpperCase(), handler); } @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception { + public void channelActive(final ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); - this.connected = System.currentTimeMillis(); - channel = ctx; + connectedTime = System.currentTimeMillis(); + channelContext = ctx; synchronized (clients) { clients.add(this); } } @Override - public void channelInactive(ChannelHandlerContext ctx) throws Exception { + public void channelInactive(final ChannelHandlerContext ctx) + throws Exception { super.channelActive(ctx); - long passed = System.currentTimeMillis() - connected; - System.out.print(c * 1000 / passed); + final long passed = System.currentTimeMillis() - connectedTime; synchronized (clients) { clients.remove(this); } } - public void writeIfPossible(String str) { - if (channel.channel().isWritable()) { - channel.writeAndFlush(str.trim() + "\n"); + public void writeIfPossible(final String str) { + if (channelContext.channel().isWritable()) { + channelContext.write(str + "\n"); + } else { + missedMessages++; } } + private void writeChannel(final String channel, final String message) { + if (subscriptions.contains(channel) || subscriptions.contains("*")) { + writeIfPossible(message); + } + } + + public void write(final String str) { + channelContext.writeAndFlush(str + "\n"); + } + + public void error(final String msg) { + write("ERR " + msg + "\n"); + channelContext.close(); + } + @Override - protected void channelRead0(ChannelHandlerContext ctx, String msg) - throws Exception { - String[] parts = msg.split(" "); - if (parts.length < 1) + protected void channelRead0(final ChannelHandlerContext ctx, + final String msg) throws Exception { + final int split = msg.indexOf(" "); + String command; + String data; + if (split == -1) { + command = msg.toUpperCase(); + data = ""; + } else { + command = msg.substring(0, split).toUpperCase(); + data = msg.substring(split + 1).trim(); + } + + switch (command) { + case ("PX"): + handle_PX(ctx, data); + break; + case ("SIZE"): + handle_SIZE(ctx, data); + break; + case ("PUB"): + handle_PUB(ctx, data); + break; + case ("SUB"): + handle_SUB(ctx, data); + break; + case ("HELP"): + handle_HELP(ctx, data); + break; + default: + error("Unknown command"); + } + } + + private void handle_HELP(final ChannelHandlerContext ctx, final String data) { + writeIfPossible("HELP Commands:\n" + "HELP SIZE\n" + + "HELP PX \n" + "HELP PX \n" + + "HELP SUB\n" + "HELP SUB [-]\n" + + "HELP PUB \n"); + } + + private void handle_SUB(final ChannelHandlerContext ctx, final String data) { + if (data.startsWith("-")) { + final String channel = data.substring(1); + if (!subscriptions.remove(channel.toLowerCase())) { + error("Not subscribed to this channel."); + } + } else if (data.length() > 0) { + final String channel = data.toLowerCase(); + if (!channel.matches("^[a-zA-Z0-9_]+$")) { + error("Invalid channel name"); + } else if (subscriptions.size() >= 16) { + error("Too many subscriptions"); + } else { + subscriptions.add(channel); + } + } + + write("SUB " + StringUtils.join(subscriptions, " ")); + } + + private void handle_PUB(final ChannelHandlerContext ctx, final String data) { + final String[] parts = data.split(" ", 1); + if (parts.length != 2) { + error("Usage: PUB "); return; + } - String command = parts[0].toUpperCase(); + final String channel = parts[0].toLowerCase(); + final String message = "PUB " + channel + " " + parts[1].trim(); - if (command.equals("PX")) { - c++; - if (parts.length == 3) { - int x = Integer.parseInt(parts[1]); - int y = Integer.parseInt(parts[2]); - int c = canvas.getPixel(x, y); - ctx.write(String.format("PX %d %d %06x\n", x, y, c)); - ctx.flush(); - } else if (parts.length == 4) { - int x = Integer.parseInt(parts[1]); - int y = Integer.parseInt(parts[2]); - int color = (int) Long.parseLong(parts[3], 16); - if (parts[3].length() == 6) + 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) { + error("SIZE"); + } + write(String + .format("SIZE %d %d", canvas.getWidth(), canvas.getHeight())); + } + + 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]); + final int c = canvas.getPixel(x, y); + write(String.format("PX %d %d %06x", x, y, c)); + } else if (args.length == 3) { + final int x = Integer.parseInt(args[0]); + final int y = Integer.parseInt(args[1]); + int color = (int) Long.parseLong(args[2], 16); + if (args[2].length() == 6) { color += 0xff000000; + } canvas.setPixel(x, y, color); } else { - ctx.writeAndFlush("ERR\n"); - ctx.close(); + error("Usage: PX x y [rrggbb[aa]]"); } - } else if (command.equals("SIZE")) { - ctx.writeAndFlush(String.format("SIZE %d %d\n", canvas.getWidth(), - canvas.getHeight())); - } else if (command.equals("MSG")) { - String text = command + " " - + msg.substring(command.length()).trim(); - for (PixelClientHandler c : clients) { - if (c != this) - c.writeIfPossible(text); - } - } else { - ctx.writeAndFlush("ERR\n"); - ctx.close(); + } catch (final NumberFormatException e) { + 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 9fcaa9a..2e351e1 100644 --- a/pixelwar/src/de/paws/pixelwar/PixelServer.java +++ b/pixelwar/src/de/paws/pixelwar/PixelServer.java @@ -22,25 +22,25 @@ public class PixelServer extends ChannelHandlerAdapter { private final NetCanvas canvas; private final int port; - public PixelServer(int port) { + public PixelServer(final int port) { this.port = port; - this.canvas = new NetCanvas(); + canvas = new NetCanvas(); } public void run() throws InterruptedException { - EventLoopGroup bossGroup = new NioEventLoopGroup(1); - EventLoopGroup workerGroup = new NioEventLoopGroup(); + final EventLoopGroup bossGroup = new NioEventLoopGroup(1); + final EventLoopGroup workerGroup = new NioEventLoopGroup(); try { - ServerBootstrap b = new ServerBootstrap(); + final ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer() { @Override - public void initChannel(SocketChannel ch) + public void initChannel(final SocketChannel ch) throws Exception { - ChannelPipeline p = ch.pipeline(); + final ChannelPipeline p = ch.pipeline(); // p.addLast("logger", new LoggingHandler( // LogLevel.DEBUG)); @@ -55,7 +55,7 @@ public class PixelServer extends ChannelHandlerAdapter { }); // Start the server. - ChannelFuture f = b.bind(port).sync(); + final ChannelFuture f = b.bind(port).sync(); // Wait until the server socket is closed. f.channel().closeFuture().sync(); @@ -66,7 +66,7 @@ public class PixelServer extends ChannelHandlerAdapter { } } - public static void main(String[] args) throws InterruptedException { + public static void main(final String[] args) throws InterruptedException { new PixelServer(8080).run(); }