Author: markt Date: Thu Dec 20 22:17:50 2012 New Revision: 1424733 URL: http://svn.apache.org/viewvc?rev=1424733&view=rev Log: Switch the snake WebSocket example to the new API.
Added: tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeAnnotation.java (with props) tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeTimer.java - copied, changed from r1424057, tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeWebSocketServlet.java Removed: tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeWebSocketServlet.java Modified: tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Location.java tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java tomcat/trunk/webapps/examples/WEB-INF/web.xml Modified: tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Location.java URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Location.java?rev=1424733&r1=1424732&r2=1424733&view=diff ============================================================================== --- tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Location.java (original) +++ tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Location.java Thu Dec 20 22:17:50 2012 @@ -29,13 +29,13 @@ public class Location { public Location getAdjacentLocation(Direction direction) { switch (direction) { case NORTH: - return new Location(x, y - SnakeWebSocketServlet.GRID_SIZE); + return new Location(x, y - SnakeAnnotation.GRID_SIZE); case SOUTH: - return new Location(x, y + SnakeWebSocketServlet.GRID_SIZE); + return new Location(x, y + SnakeAnnotation.GRID_SIZE); case EAST: - return new Location(x + SnakeWebSocketServlet.GRID_SIZE, y); + return new Location(x + SnakeAnnotation.GRID_SIZE, y); case WEST: - return new Location(x - SnakeWebSocketServlet.GRID_SIZE, y); + return new Location(x - SnakeAnnotation.GRID_SIZE, y); case NONE: // fall through default: Modified: tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java?rev=1424733&r1=1424732&r2=1424733&view=diff ============================================================================== --- tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java (original) +++ tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/Snake.java Thu Dec 20 22:17:50 2012 @@ -17,19 +17,18 @@ package websocket.snake; import java.io.IOException; -import java.nio.CharBuffer; import java.util.ArrayDeque; import java.util.Collection; import java.util.Deque; -import org.apache.catalina.websocket.WsOutbound; +import javax.websocket.Session; public class Snake { private static final int DEFAULT_LENGTH = 5; private final int id; - private final WsOutbound outbound; + private final Session session; private Direction direction; private int length = DEFAULT_LENGTH; @@ -37,35 +36,34 @@ public class Snake { private final Deque<Location> tail = new ArrayDeque<>(); private final String hexColor; - public Snake(int id, WsOutbound outbound) { + public Snake(int id, Session session) { this.id = id; - this.outbound = outbound; - this.hexColor = SnakeWebSocketServlet.getRandomHexColor(); + this.session = session; + this.hexColor = SnakeAnnotation.getRandomHexColor(); resetState(); } private void resetState() { this.direction = Direction.NONE; - this.head = SnakeWebSocketServlet.getRandomLocation(); + this.head = SnakeAnnotation.getRandomLocation(); this.tail.clear(); this.length = DEFAULT_LENGTH; } private synchronized void kill() { resetState(); - try { - CharBuffer response = CharBuffer.wrap("{'type': 'dead'}"); - outbound.writeTextMessage(response); - } catch (IOException ioe) { - // Ignore - } + sendMessage("{'type': 'dead'}"); } private synchronized void reward() { length++; + sendMessage("{'type': 'kill'}"); + } + + + protected void sendMessage(String msg) { try { - CharBuffer response = CharBuffer.wrap("{'type': 'kill'}"); - outbound.writeTextMessage(response); + session.getRemote().sendString(msg); } catch (IOException ioe) { // Ignore } @@ -73,17 +71,17 @@ public class Snake { public synchronized void update(Collection<Snake> snakes) { Location nextLocation = head.getAdjacentLocation(direction); - if (nextLocation.x >= SnakeWebSocketServlet.PLAYFIELD_WIDTH) { + if (nextLocation.x >= SnakeAnnotation.PLAYFIELD_WIDTH) { nextLocation.x = 0; } - if (nextLocation.y >= SnakeWebSocketServlet.PLAYFIELD_HEIGHT) { + if (nextLocation.y >= SnakeAnnotation.PLAYFIELD_HEIGHT) { nextLocation.y = 0; } if (nextLocation.x < 0) { - nextLocation.x = SnakeWebSocketServlet.PLAYFIELD_WIDTH; + nextLocation.x = SnakeAnnotation.PLAYFIELD_WIDTH; } if (nextLocation.y < 0) { - nextLocation.y = SnakeWebSocketServlet.PLAYFIELD_HEIGHT; + nextLocation.y = SnakeAnnotation.PLAYFIELD_HEIGHT; } if (direction != Direction.NONE) { tail.addFirst(head); Added: tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeAnnotation.java URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeAnnotation.java?rev=1424733&view=auto ============================================================================== --- tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeAnnotation.java (added) +++ tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeAnnotation.java Thu Dec 20 22:17:50 2012 @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package websocket.snake; + +import java.awt.Color; +import java.util.Iterator; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.websocket.Session; +import javax.websocket.WebSocketClose; +import javax.websocket.WebSocketEndpoint; +import javax.websocket.WebSocketMessage; +import javax.websocket.WebSocketOpen; + +@WebSocketEndpoint(value = "/websocket/snake") +public class SnakeAnnotation { + + public static final int PLAYFIELD_WIDTH = 640; + public static final int PLAYFIELD_HEIGHT = 480; + public static final int GRID_SIZE = 10; + + private static final AtomicInteger snakeIds = new AtomicInteger(0); + private static final Random random = new Random(); + + + private final int id; + private Snake snake; + + public static String getRandomHexColor() { + float hue = random.nextFloat(); + // sat between 0.1 and 0.3 + float saturation = (random.nextInt(2000) + 1000) / 10000f; + float luminance = 0.9f; + Color color = Color.getHSBColor(hue, saturation, luminance); + return '#' + Integer.toHexString( + (color.getRGB() & 0xffffff) | 0x1000000).substring(1); + } + + + public static Location getRandomLocation() { + int x = roundByGridSize(random.nextInt(PLAYFIELD_WIDTH)); + int y = roundByGridSize(random.nextInt(PLAYFIELD_HEIGHT)); + return new Location(x, y); + } + + + private static int roundByGridSize(int value) { + value = value + (GRID_SIZE / 2); + value = value / GRID_SIZE; + value = value * GRID_SIZE; + return value; + } + + public SnakeAnnotation() { + this.id = snakeIds.getAndIncrement(); + } + + + @WebSocketOpen + public void onOpen(Session session) { + this.snake = new Snake(id, session); + SnakeTimer.addSnake(snake); + StringBuilder sb = new StringBuilder(); + for (Iterator<Snake> iterator = SnakeTimer.getSnakes().iterator(); + iterator.hasNext();) { + Snake snake = iterator.next(); + sb.append(String.format("{id: %d, color: '%s'}", + Integer.valueOf(snake.getId()), snake.getHexColor())); + if (iterator.hasNext()) { + sb.append(','); + } + } + SnakeTimer.broadcast(String.format("{'type': 'join','data':[%s]}", + sb.toString())); + } + + + @WebSocketMessage + public void onTextMessage(String message) { + if ("west".equals(message)) { + snake.setDirection(Direction.WEST); + } else if ("north".equals(message)) { + snake.setDirection(Direction.NORTH); + } else if ("east".equals(message)) { + snake.setDirection(Direction.EAST); + } else if ("south".equals(message)) { + snake.setDirection(Direction.SOUTH); + } + } + + + @WebSocketClose + public void onClose() { + SnakeTimer.removeSnake(snake); + SnakeTimer.broadcast(String.format("{'type': 'leave', 'id': %d}", + Integer.valueOf(id))); + } +} Propchange: tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeAnnotation.java ------------------------------------------------------------------------------ svn:eol-style = native Copied: tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeTimer.java (from r1424057, tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeWebSocketServlet.java) URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeTimer.java?p2=tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeTimer.java&p1=tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeWebSocketServlet.java&r1=1424057&r2=1424733&rev=1424733&view=diff ============================================================================== --- tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeWebSocketServlet.java (original) +++ tomcat/trunk/webapps/examples/WEB-INF/classes/websocket/snake/SnakeTimer.java Thu Dec 20 22:17:50 2012 @@ -16,77 +16,58 @@ */ package websocket.snake; -import java.awt.Color; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; import java.util.Collection; import java.util.Collections; import java.util.Iterator; -import java.util.Random; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; - -import org.apache.catalina.websocket.MessageHandler; -import org.apache.catalina.websocket.StreamHandler; -import org.apache.catalina.websocket.WebSocketServlet; -import org.apache.catalina.websocket.WsOutbound; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; /** - * Example web socket servlet for simple multiplayer snake. + * Sets up the timer for the multi-player snake game WebSocket example. */ -public class SnakeWebSocketServlet extends WebSocketServlet { - - private static final long serialVersionUID = 1L; +public class SnakeTimer { private static final Log log = - LogFactory.getLog(SnakeWebSocketServlet.class); + LogFactory.getLog(SnakeTimer.class); - public static final int PLAYFIELD_WIDTH = 640; - public static final int PLAYFIELD_HEIGHT = 480; - public static final int GRID_SIZE = 10; + private static Timer gameTimer = null; private static final long TICK_DELAY = 100; - private static final Random random = new Random(); + private static final ConcurrentHashMap<Integer, Snake> snakes = + new ConcurrentHashMap<>(); - private final Timer gameTimer = - new Timer(SnakeWebSocketServlet.class.getSimpleName() + " Timer"); + protected static synchronized void addSnake(Snake snake) { + if (snakes.size() == 0) { + startTimer(); + } + snakes.put(Integer.valueOf(snake.getId()), snake); + } - private final AtomicInteger connectionIds = new AtomicInteger(0); - private final ConcurrentHashMap<Integer, Snake> snakes = - new ConcurrentHashMap<>(); - private final ConcurrentHashMap<Integer, SnakeMessageHandler> connections = - new ConcurrentHashMap<>(); - @Override - public void init() throws ServletException { - super.init(); - gameTimer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - try { - tick(); - } catch (RuntimeException e) { - log.error("Caught to prevent timer from shutting down", e); - } - } - }, TICK_DELAY, TICK_DELAY); + protected static Collection<Snake> getSnakes() { + return Collections.unmodifiableCollection(snakes.values()); + } + + + protected static synchronized void removeSnake(Snake snake) { + snakes.remove(Integer.valueOf(snake.getId())); + if (snakes.size() == 0) { + stopTimer(); + } } - private void tick() { + + protected static void tick() { StringBuilder sb = new StringBuilder(); - for (Iterator<Snake> iterator = getSnakes().iterator(); + for (Iterator<Snake> iterator = SnakeTimer.getSnakes().iterator(); iterator.hasNext();) { Snake snake = iterator.next(); - snake.update(getSnakes()); + snake.update(SnakeTimer.getSnakes()); sb.append(snake.getLocationsJson()); if (iterator.hasNext()) { sb.append(','); @@ -96,118 +77,31 @@ public class SnakeWebSocketServlet exten sb.toString())); } - private void broadcast(String message) { - for (SnakeMessageHandler connection : getConnections()) { - try { - CharBuffer buffer = CharBuffer.wrap(message); - connection.getWsOutbound().writeTextMessage(buffer); - } catch (IOException ignore) { - // Ignore - } + protected static void broadcast(String message) { + for (Snake snake : SnakeTimer.getSnakes()) { + snake.sendMessage(message); } } - private Collection<SnakeMessageHandler> getConnections() { - return Collections.unmodifiableCollection(connections.values()); - } - - private Collection<Snake> getSnakes() { - return Collections.unmodifiableCollection(snakes.values()); - } - public static String getRandomHexColor() { - float hue = random.nextFloat(); - // sat between 0.1 and 0.3 - float saturation = (random.nextInt(2000) + 1000) / 10000f; - float luminance = 0.9f; - Color color = Color.getHSBColor(hue, saturation, luminance); - return '#' + Integer.toHexString( - (color.getRGB() & 0xffffff) | 0x1000000).substring(1); - } - - public static Location getRandomLocation() { - int x = roundByGridSize( - random.nextInt(SnakeWebSocketServlet.PLAYFIELD_WIDTH)); - int y = roundByGridSize( - random.nextInt(SnakeWebSocketServlet.PLAYFIELD_HEIGHT)); - return new Location(x, y); - } - - private static int roundByGridSize(int value) { - value = value + (SnakeWebSocketServlet.GRID_SIZE / 2); - value = value / SnakeWebSocketServlet.GRID_SIZE; - value = value * SnakeWebSocketServlet.GRID_SIZE; - return value; - } - - @Override - public void destroy() { - super.destroy(); - if (gameTimer != null) { - gameTimer.cancel(); - } - } - - @Override - protected StreamHandler createWebSocketHandler(String subProtocol, - HttpServletRequest request) { - return new SnakeMessageHandler(connectionIds.incrementAndGet()); - } - - private final class SnakeMessageHandler extends MessageHandler { - - private final int id; - private Snake snake; - - private SnakeMessageHandler(int id) { - this.id = id; - } - - @Override - protected void onOpen(WsOutbound outbound) { - this.snake = new Snake(id, outbound); - snakes.put(Integer.valueOf(id), snake); - connections.put(Integer.valueOf(id), this); - StringBuilder sb = new StringBuilder(); - for (Iterator<Snake> iterator = getSnakes().iterator(); - iterator.hasNext();) { - Snake snake = iterator.next(); - sb.append(String.format("{id: %d, color: '%s'}", - Integer.valueOf(snake.getId()), snake.getHexColor())); - if (iterator.hasNext()) { - sb.append(','); + public static void startTimer() { + gameTimer = new Timer(SnakeTimer.class.getSimpleName() + " Timer"); + gameTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + try { + tick(); + } catch (RuntimeException e) { + log.error("Caught to prevent timer from shutting down", e); } } - broadcast(String.format("{'type': 'join','data':[%s]}", - sb.toString())); - } - - @Override - protected void onClose(int status) { - connections.remove(Integer.valueOf(id)); - snakes.remove(Integer.valueOf(id)); - broadcast(String.format("{'type': 'leave', 'id': %d}", - Integer.valueOf(id))); - } + }, TICK_DELAY, TICK_DELAY); + } - @Override - protected void onBinaryMessage(ByteBuffer message) throws IOException { - throw new UnsupportedOperationException( - "Binary message not supported."); - } - @Override - protected void onTextMessage(CharBuffer charBuffer) throws IOException { - String message = charBuffer.toString(); - if ("west".equals(message)) { - snake.setDirection(Direction.WEST); - } else if ("north".equals(message)) { - snake.setDirection(Direction.NORTH); - } else if ("east".equals(message)) { - snake.setDirection(Direction.EAST); - } else if ("south".equals(message)) { - snake.setDirection(Direction.SOUTH); - } + public static void stopTimer() { + if (gameTimer != null) { + gameTimer.cancel(); } } } Modified: tomcat/trunk/webapps/examples/WEB-INF/web.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/examples/WEB-INF/web.xml?rev=1424733&r1=1424732&r2=1424733&view=diff ============================================================================== --- tomcat/trunk/webapps/examples/WEB-INF/web.xml (original) +++ tomcat/trunk/webapps/examples/WEB-INF/web.xml Thu Dec 20 22:17:50 2012 @@ -354,17 +354,4 @@ <url-pattern>/async/stockticker</url-pattern> </servlet-mapping> - <!-- WebSocket Examples --> - <listener> - <listener-class>websocket.echo.WsConfigListener</listener-class> - </listener> - <servlet> - <servlet-name>wsSnake</servlet-name> - <servlet-class>websocket.snake.SnakeWebSocketServlet</servlet-class> - </servlet> - <servlet-mapping> - <servlet-name>wsSnake</servlet-name> - <url-pattern>/websocket/snake</url-pattern> - </servlet-mapping> - </web-app> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org