Revision: 463 http://skycastle.svn.sourceforge.net/skycastle/?rev=463&view=rev Author: zzorn Date: 2008-04-18 15:31:12 -0700 (Fri, 18 Apr 2008) Log Message: ----------- Separated the different functions of the previous client class into separate classes (ServerSessionHandler for communicating with server, ClientObject to provide client services and actions, and a SkycastleClient that initializes the client system). Also added a few UI classes (label, split panel), and created a rudimentary client UI with a working login and exit button. Next step is to work on the way parameters are passed to invoked actions from UI components, so that we can use a text field to edit the server address, username, etc. Modified Paths: -------------- trunk/skycastle/modules/client/pom.xml trunk/skycastle/modules/client/src/main/java/org/skycastle/client/SkycastleClient.java trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ClientContext.java trunk/skycastle/modules/core/src/main/java/org/skycastle/core/DefaultGameObject.java trunk/skycastle/modules/core/src/main/java/org/skycastle/core/GameObject.java trunk/skycastle/modules/core/src/main/java/org/skycastle/core/GameObjectContext.java trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ServerSideContext.java trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/ProtocolCommunicator.java trunk/skycastle/modules/server/src/main/java/org/skycastle/server/SkycastleClientSessionHandler.java trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/SwingClientUi.java trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/ParameterChecker.java Added Paths: ----------- trunk/skycastle/modules/client/src/main/java/org/skycastle/client/ClientObject.java trunk/skycastle/modules/client/src/main/java/org/skycastle/client/ExitAct.java trunk/skycastle/modules/client/src/main/java/org/skycastle/client/LoginAct.java trunk/skycastle/modules/client/src/main/java/org/skycastle/client/ServerSessionHandler.java trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ServerSideGameObject.java trunk/skycastle/modules/core/src/main/java/org/skycastle/core/acting/AbstractAct.java trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/LabelUi.java trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/SplitPanelUi.java Modified: trunk/skycastle/modules/client/pom.xml =================================================================== --- trunk/skycastle/modules/client/pom.xml 2008-04-17 18:19:22 UTC (rev 462) +++ trunk/skycastle/modules/client/pom.xml 2008-04-18 22:31:12 UTC (rev 463) @@ -242,6 +242,11 @@ <artifactId>skycastle-utils</artifactId> <version>0.0.4-SNAPSHOT</version> </dependency> + <dependency> + <groupId>org.skycastle</groupId> + <artifactId>skycastle-ui</artifactId> + <version>0.0.4-SNAPSHOT</version> + </dependency> <!-- <dependency> <groupId>scala</groupId> Added: trunk/skycastle/modules/client/src/main/java/org/skycastle/client/ClientObject.java =================================================================== --- trunk/skycastle/modules/client/src/main/java/org/skycastle/client/ClientObject.java (rev 0) +++ trunk/skycastle/modules/client/src/main/java/org/skycastle/client/ClientObject.java 2008-04-18 22:31:12 UTC (rev 463) @@ -0,0 +1,120 @@ +package org.skycastle.client; + +import org.skycastle.core.DefaultGameObject; +import org.skycastle.core.GameContext; +import org.skycastle.core.GameObject; +import org.skycastle.core.acting.ActionImpl; +import org.skycastle.ui.*; +import org.skycastle.util.parameters.ParameterSetMetadataImpl; + +/** + * Client services wrapped in a client side {@link GameObject}. + * + * @author Hans Haggstrom + */ +public final class ClientObject + extends DefaultGameObject +{ + + //====================================================================== + // Private Fields + + private ClientUi myClientUi = null; + + //====================================================================== + // Private Constants + + private static final long serialVersionUID = 1L; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Other Public Methods + + /** + * Starts the client - sets up some actions, and opens the UI. + */ + public void start() + { + setupActions(); + + myClientUi = createUi(); + + myClientUi.start(); + } + + public void login( String host, long port, String username, String password ) + { + // TODO: Handle multiple connected servers better. Dissallow multiple logins with same username to the same server? + + final ServerSessionHandler serverSessionHandler = new ServerSessionHandler( host, + port, + username, + password, + GameContext.getGameObjectContext() ); + + serverSessionHandler.login(); + } + + //====================================================================== + // Private Methods + + + private void setupActions() + { + addAction( new ActionImpl( "exit", + new ParameterSetMetadataImpl(), + ExitAct.class, + "Exit", + "exits the application", + null, + null, + false ) ); + + addAction( new ActionImpl( "login", + new ParameterSetMetadataImpl(), + LoginAct.class, + "Login", + "logs in to the server", + null, + null, + false ) ); + } + + private ClientUi createUi() + { + final ClientUi clientUi = new SwingClientUi(); + + clientUi.setRootUi( createClientUi() ); + + return clientUi; + } + + + private static UiObject createClientUi() + { + // TODO: Later maybe read this from config file or a persistent storage? + + final GameObject client = GameContext.getGameObjectContext().getGameObjectBoundToName( "client" ); + + final SplitPanelUi splitPanelUi = new SplitPanelUi(); + + final PanelUi clientBar = new PanelUi(); + clientBar.addUi( new LabelUi( "This is the client bar" ) ); + clientBar.addUi( new LabelUi( "<Connected server address goes here>" ) ); + clientBar.addUi( new ButtonUi( client.getId(), "exit" ) ); + //clientBar.addUi( new ButtonUi( ) ); // TODO: Create some named object that represents the client, that has methods like quit, logon to server, etc. + + final PanelUi loginWindow = new PanelUi(); + loginWindow.addUi( new LabelUi( "Server address:" ) ); + loginWindow.addUi( new LabelUi( "<Input text field goes here>" ) ); + loginWindow.addUi( new ButtonUi( client.getId(), "login" ) ); + + splitPanelUi.setFirstUi( clientBar ); + splitPanelUi.setSecondUi( loginWindow ); + + return splitPanelUi; + } + +} Added: trunk/skycastle/modules/client/src/main/java/org/skycastle/client/ExitAct.java =================================================================== --- trunk/skycastle/modules/client/src/main/java/org/skycastle/client/ExitAct.java (rev 0) +++ trunk/skycastle/modules/client/src/main/java/org/skycastle/client/ExitAct.java 2008-04-18 22:31:12 UTC (rev 463) @@ -0,0 +1,41 @@ +package org.skycastle.client; + +import org.skycastle.core.GameObject; +import org.skycastle.core.acting.AbstractAct; +import org.skycastle.core.acting.Act; +import org.skycastle.messaging.MessageListener; +import org.skycastle.util.parameters.ParameterSet; + +/** + * An {@link Act} that quits the client. + * + * @author Hans Haggstrom + */ +// TODO: Add are you sure dialog box +public final class ExitAct + extends AbstractAct +{ + + //====================================================================== + // Private Constants + + private static final long serialVersionUID = 1L; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Act Implementation + + public long start( final GameObject hostObject, + final MessageListener messageHandler, final ParameterSet actParameters, final ParameterSet configurationParameters ) + throws Exception + { + // Pull the cables! + System.exit( 0 ); + + // TODO: A more gentle exit, that exits gracefully from servers, etc. + return -1; + } + +} Added: trunk/skycastle/modules/client/src/main/java/org/skycastle/client/LoginAct.java =================================================================== --- trunk/skycastle/modules/client/src/main/java/org/skycastle/client/LoginAct.java (rev 0) +++ trunk/skycastle/modules/client/src/main/java/org/skycastle/client/LoginAct.java 2008-04-18 22:31:12 UTC (rev 463) @@ -0,0 +1,56 @@ +package org.skycastle.client; + +import org.skycastle.core.GameObject; +import org.skycastle.core.acting.AbstractAct; +import org.skycastle.messaging.MessageListener; +import org.skycastle.util.parameters.ParameterSet; + +/** + * Logs in to a server from the client + * + * @author Hans Haggstrom + */ +// TODO: How to pass client side object rfeferences to acts? +// Maybe make acts just calls to methods defiened on custom game objects. +public final class LoginAct + extends AbstractAct +{ + + //====================================================================== + // Private Constants + + private static final long serialVersionUID = 1L; + + /** + * The default hostname. + */ + private static final String DEFAULT_HOST = "localhost"; + + /** + * The default port. + */ + private static final long DEFAULT_PORT = 1139L; + + private static final String TEST_USER_NAME = "TestUser"; + private static final String TEST_PASSWORD = "foo"; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Act Implementation + + public long start( final GameObject hostObject, + final MessageListener messageHandler, + final ParameterSet actParameters, + final ParameterSet configurationParameters ) + throws Exception + { + // TODO: Fix better + + ( (ClientObject) hostObject ).login( DEFAULT_HOST, DEFAULT_PORT, TEST_USER_NAME, TEST_PASSWORD ); + + return -1; + } + +} Added: trunk/skycastle/modules/client/src/main/java/org/skycastle/client/ServerSessionHandler.java =================================================================== --- trunk/skycastle/modules/client/src/main/java/org/skycastle/client/ServerSessionHandler.java (rev 0) +++ trunk/skycastle/modules/client/src/main/java/org/skycastle/client/ServerSessionHandler.java 2008-04-18 22:31:12 UTC (rev 463) @@ -0,0 +1,291 @@ +package org.skycastle.client; + +import com.sun.sgs.client.ClientChannel; +import com.sun.sgs.client.ClientChannelListener; +import com.sun.sgs.client.simple.SimpleClient; +import com.sun.sgs.client.simple.SimpleClientListener; +import org.skycastle.core.GameObjectContext; +import org.skycastle.messaging.Message; +import org.skycastle.messaging.modifications.ModificationMessage; +import org.skycastle.messaging.updates.UpdateMessage; +import org.skycastle.protocol.ProtocolCommunicator; +import org.skycastle.protocol.ProtocolException; +import org.skycastle.protocol.negotiation.ClientSideProtocolNegotiator; +import org.skycastle.protocol.negotiation.NegotiationStatus; +import org.skycastle.protocol.registry.ProtocolRegistryImpl; +import org.skycastle.util.ParameterChecker; + +import javax.swing.*; +import java.io.IOException; +import java.net.PasswordAuthentication; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Listens to messages from a server we are connected to, and sends messages to it. + * + * @author Hans Haggstrom + */ +public final class ServerSessionHandler + extends ProtocolCommunicator + implements SimpleClientListener +{ + + //====================================================================== + // Private Fields + + @SuppressWarnings( { "NonSerializableFieldInSerializableClass" } ) + private final SimpleClient mySimpleClient; + + private final String myHost; + private final String myPort; + private final String myUserName; + private final GameObjectContext myGameObjectContext; + private final char[] myPassword; + + //====================================================================== + // Private Constants + + private static final int PROTOCOL_NEGOTIATION_TIMEOUT_MS = 10000; + + + /** + * The {@link Logger} for this class. + */ + private static final Logger LOGGER = Logger.getLogger( SkycastleClient.class.getName() ); + + private static final String CLIENT_TYPE = "SkycastleClient"; + private static final String CLIENT_VERSION = "0.1"; + private static final ProtocolRegistryImpl PROTOCOL_REGISTRY = new ProtocolRegistryImpl(); + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Constructors + + /** + * Creates a new {@link ServerSessionHandler}. + * + * @param host the hostname to connect to. Either an IP number, or a domain name. + * @param port the port to use when connecting + * @param userName username to use when logging in + * @param password password to use when logging in + * @param context a context that incoming messages should be applied to. + */ + public ServerSessionHandler( final String host, final long port, final String userName, final String password, final GameObjectContext context ) + { + super( new ClientSideProtocolNegotiator( PROTOCOL_REGISTRY, + CLIENT_TYPE, + CLIENT_VERSION ), + PROTOCOL_NEGOTIATION_TIMEOUT_MS ); + + ParameterChecker.checkNotNull( host, "host" ); + ParameterChecker.checkPositiveNonZeroInteger( port, "port" ); + ParameterChecker.checkNotNull( userName, "userName" ); + ParameterChecker.checkNotNull( password, "password" ); + ParameterChecker.checkNotNull( context, "context" ); + + myHost = host; + myPort = String.valueOf( port ); + myUserName = userName; + myPassword = password.toCharArray(); + myGameObjectContext = context; + + //noinspection ThisEscapedInObjectConstruction + mySimpleClient = new SimpleClient( this ); + } + + //---------------------------------------------------------------------- + // ServerSessionListener Implementation + + public ClientChannelListener joinedChannel( final ClientChannel clientChannel ) + { + LOGGER.info( "Joined channel '" + clientChannel + "'." ); + + return null; + } + + + public void receivedMessage( final byte[] serverMessage ) + { + try + { + handleReceivedEncodedMessage( serverMessage ); + } + catch ( ProtocolException e ) + { + LOGGER.warning( "Problem when decoding incoming message from server: " + e.getMessage() ); + } + } + + + public void reconnecting() + { + LOGGER.info( "Reconnecting" ); + } + + + public void reconnected() + { + LOGGER.info( "Reconnected" ); + } + + + public void disconnected( final boolean b, final String s ) + { + LOGGER.info( "Disconnected. Graceful: '" + b + "'. Message: '" + s + "'." ); + } + + //---------------------------------------------------------------------- + // SimpleClientListener Implementation + + public PasswordAuthentication getPasswordAuthentication() + { + return new PasswordAuthentication( myUserName, myPassword ); + } + + + public void loggedIn() + { + LOGGER.info( "Logged In. Now negotiationg protocol..." ); + + startProtocolNegotiations(); + } + + + public void loginFailed( final String s ) + { + LOGGER.info( "Login Failed. Reason: '" + s + "'." ); + } + + //---------------------------------------------------------------------- + // Other Public Methods + + /** + * Initiates asynchronous login to the SGS server specified by the host and port properties. + */ + @SuppressWarnings( { "AccessOfSystemProperties" } ) + public void login() + { + try + { + final Properties connectProps = new Properties(); + connectProps.setProperty( "host", myHost ); + connectProps.setProperty( "port", myPort ); + mySimpleClient.login( connectProps ); + } + catch ( IOException e ) + { + LOGGER.log( Level.SEVERE, "Problem when connecting to the server.", e ); + + disconnected( false, e.getMessage() ); + } + } + + /** + * Disconnects from the server gracefully. + */ + public void disconnect() + { + mySimpleClient.logout( true ); + } + + //====================================================================== + // Protected Methods + + @Override + protected void scheduleTimeoutCallback( final long timeout_ms ) + { + final Thread timeoutTimer = new Thread( new Runnable() + { + + public void run() + { + //noinspection UnusedCatchParameter + try + { + Thread.sleep( timeout_ms ); + } + catch ( InterruptedException e ) + { + // We were interrupted for some reason. Stop waiting, and call the timeout. + } + + // TODO: Assumes that the main game loop will run in the Swing event thread. Check this. + //noinspection InnerClassTooDeeplyNested + SwingUtilities.invokeLater( new Runnable() + { + + public void run() + { + onTimeoutCallback(); + } + + } ); + } + + } ); + + timeoutTimer.setDaemon( true ); // If the program exists, don't stay to wait for the timeout. + + timeoutTimer.start(); + } + + + @Override + protected void sendEncodedMessage( final byte[] encodedMessage ) + { + try + { + mySimpleClient.send( encodedMessage ); + } + catch ( IOException e ) + { + LOGGER.log( Level.WARNING, "Problem when sending an encoded message to the server.", e ); + } + } + + + @Override + protected void onProtocolNegotiationFailed( final NegotiationStatus status ) + { + LOGGER.severe( "Could not negotiate a common protocol with the server: " + status.toString() ); + + disconnect(); + } + + + @Override + protected void onProtocolNegotiationSucceeded( final String protocolId ) + { + LOGGER.info( "Protocol negotiated." ); + } + + + @Override + protected void onMessage( final Message message ) + throws ProtocolException + { + LOGGER.log( Level.INFO, "Recieved a message: " + message ); + + //noinspection ChainOfInstanceofChecks + if ( message instanceof UpdateMessage ) + { + final UpdateMessage updateMessage = (UpdateMessage) message; + + updateMessage.applyStateChangeToModel( myGameObjectContext ); + } + else if ( message instanceof ModificationMessage ) + { + throw new ProtocolException( + "The client does not accept any ModificationMessages, only UpdateMessages, but the incoming message was: " + message ); + } + else + { + throw new ProtocolException( "Unknown message type: " + message.getClass() ); + } + } + +} Modified: trunk/skycastle/modules/client/src/main/java/org/skycastle/client/SkycastleClient.java =================================================================== --- trunk/skycastle/modules/client/src/main/java/org/skycastle/client/SkycastleClient.java 2008-04-17 18:19:22 UTC (rev 462) +++ trunk/skycastle/modules/client/src/main/java/org/skycastle/client/SkycastleClient.java 2008-04-18 22:31:12 UTC (rev 463) @@ -1,24 +1,9 @@ package org.skycastle.client; -import com.sun.sgs.client.ClientChannel; -import com.sun.sgs.client.ClientChannelListener; -import com.sun.sgs.client.simple.SimpleClient; -import com.sun.sgs.client.simple.SimpleClientListener; import org.skycastle.core.ClientContext; import org.skycastle.core.GameContext; -import org.skycastle.messaging.Message; -import org.skycastle.messaging.modifications.ModificationMessage; -import org.skycastle.messaging.updates.UpdateMessage; -import org.skycastle.protocol.ProtocolCommunicator; -import org.skycastle.protocol.ProtocolException; -import org.skycastle.protocol.negotiation.ClientSideProtocolNegotiator; -import org.skycastle.protocol.negotiation.NegotiationStatus; -import org.skycastle.protocol.registry.ProtocolRegistryImpl; +import org.skycastle.core.GameObjectContext; -import javax.swing.*; -import java.io.IOException; -import java.net.PasswordAuthentication; -import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; @@ -54,50 +39,15 @@ // as this implementation of ProtocolCommunicator is not intended to be serizliaed. @SuppressWarnings( { "serial" } ) public class SkycastleClient - extends ProtocolCommunicator - implements SimpleClientListener { //====================================================================== - // Private Fields - - @SuppressWarnings( { "NonSerializableFieldInSerializableClass" } ) - private final SimpleClient mySimpleClient; - - //====================================================================== // Private Constants - private static final int PROTOCOL_NEGOTIATION_TIMEOUT_MS = 10000; - /** - * The name of the host property. - */ - private static final String HOST_PROPERTY = "skycastle.host"; - - /** - * The default hostname. - */ - private static final String DEFAULT_HOST = "localhost"; - - /** - * The name of the port property. - */ - private static final String PORT_PROPERTY = "skycastle.port"; - - /** - * The default port. - */ - private static final String DEFAULT_PORT = "1139"; - - private static final String TEST_USER_NAME = "TestUser"; - - /** * The {@link Logger} for this class. */ private static final Logger LOGGER = Logger.getLogger( SkycastleClient.class.getName() ); - private static final String CLIENT_TYPE = "SkycastleClient"; - private static final String CLIENT_VERSION = "0.1"; - private static final ProtocolRegistryImpl PROTOCOL_REGISTRY = new ProtocolRegistryImpl(); //====================================================================== // Public Methods @@ -117,239 +67,42 @@ // Initialize static context variable GameContext.setGameObjectContext( new ClientContext() ); -/* - // Do not show logging output below the WARNING level (JME outputs a lot of debugging info at INFO level). - // This way the console output is a bit more relevant. - LOGGER.setLevel( Level.WARNING ); -*/ + // Create object that contains client side services + final ClientObject clientObject = createClientObject(); - final SkycastleClient client = new SkycastleClient(); - - client.login(); - - // Idle forever - while ( true ) - { - try - { - Thread.sleep( 1000 ); - } - catch ( InterruptedException e ) - { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - } - } + // Start the client UI + clientObject.start(); } - //---------------------------------------------------------------------- - // Constructors - - /** - * Creates a new {@link org.skycastle.client.SkycastleClient}. - */ - public SkycastleClient() - { - super( new ClientSideProtocolNegotiator( PROTOCOL_REGISTRY, - CLIENT_TYPE, - CLIENT_VERSION ), - PROTOCOL_NEGOTIATION_TIMEOUT_MS ); - - mySimpleClient = new SimpleClient( this ); - } - - //---------------------------------------------------------------------- - // ServerSessionListener Implementation - - public ClientChannelListener joinedChannel( final ClientChannel clientChannel ) - { - LOGGER.info( "Joined channel '" + clientChannel + "'." ); - - return null; - } - - - public void receivedMessage( final byte[] serverMessage ) - { - try - { - handleReceivedEncodedMessage( serverMessage ); - } - catch ( ProtocolException e ) - { - LOGGER.warning( "Problem when decoding incoming message from server: " + e.getMessage() ); - } - } - - - public void reconnecting() - { - LOGGER.info( "Reconnecting" ); - } - - - public void reconnected() - { - LOGGER.info( "Reconnected" ); - } - - - public void disconnected( final boolean b, final String s ) - { - LOGGER.info( "Disconnected. Graceful: '" + b + "'. Message: '" + s + "'." ); - } - - //---------------------------------------------------------------------- - // SimpleClientListener Implementation - - public PasswordAuthentication getPasswordAuthentication() - { - // TODO: Query user about password - - // DEBUG - return new PasswordAuthentication( TEST_USER_NAME, "testPassword".toCharArray() ); - } - - - public void loggedIn() - { - LOGGER.info( "Logged In. Now negotiationg protocol..." ); - - startProtocolNegotiations(); - } - - - public void loginFailed( final String s ) - { - LOGGER.info( "Login Failed. Reason: '" + s + "'." ); - } - //====================================================================== - // Protected Methods + // Private Methods - @Override - protected void scheduleTimeoutCallback( final long timeout_ms ) + private static ClientObject createClientObject() { - final Thread timeoutTimer = new Thread( new Runnable() - { - - public void run() - { - try - { - Thread.sleep( timeout_ms ); - } - catch ( InterruptedException e ) - { - // We were interrupted for some reason. Stop waiting, and call the timeout. - } - - // TODO: Assumes that the main game loop will run in the Swing event thread. Check this. - SwingUtilities.invokeLater( new Runnable() - { - - public void run() - { - onTimeoutCallback(); - } - - } ); - } - - } ); - - timeoutTimer.setDaemon( true ); // If the program exists, don't stay to wait for the timeout. - - timeoutTimer.start(); - } - - - @Override - protected void sendEncodedMessage( final byte[] encodedMessage ) - { try { - mySimpleClient.send( encodedMessage ); - } - catch ( IOException e ) - { - LOGGER.log( Level.WARNING, "Problem when sending an encoded message to the server.", e ); - } - } + final GameObjectContext context = GameContext.getGameObjectContext(); + final ClientObject clientObject = context.createGameObject( ClientObject.class ); - @Override - protected void onProtocolNegotiationFailed( final NegotiationStatus status ) - { - LOGGER.severe( "Could not negotiate a common protocol with the server: " + status.toString() ); + context.bindGameObjectToName( "client", clientObject ); - disconnect(); - } - - - @Override - protected void onProtocolNegotiationSucceeded( final String protocolId ) - { - LOGGER.info( "Protocol negotiated." ); - } - - - @Override - protected void onMessage( final Message message ) throws ProtocolException - { - LOGGER.log( Level.INFO, "Recieved a message: " + message ); - - if ( message instanceof UpdateMessage ) - { - final UpdateMessage updateMessage = (UpdateMessage) message; - - updateMessage.applyStateChangeToModel( GameContext.getGameObjectContext() ); + return clientObject; } - else if ( message instanceof ModificationMessage ) + catch ( InstantiationException e ) { - throw new ProtocolException( - "The client does not accept any ModificationMessages, only UpdateMessages, but the incoming message was: " + message ); + LOGGER.log( Level.SEVERE, "Could not create client", e ); } - else + catch ( IllegalAccessException e ) { - throw new ProtocolException( "Unknown message type: " + message.getClass() ); + LOGGER.log( Level.SEVERE, "Could not create client", e ); } - } - //====================================================================== - // Private Methods - - /** - * Disconnects from the server gracefully. - */ - private void disconnect() - { - mySimpleClient.logout( true ); + throw new IllegalStateException( "Could not create ClientObject. See log for details." ); } - - /** - * Initiates asynchronous login to the SGS server specified by the host and port properties. - */ - // IDEA: This could be one action provided by a client side GameObject representing a server - @SuppressWarnings( { "AccessOfSystemProperties" } ) - private void login() + private SkycastleClient() { - final String host = System.getProperty( HOST_PROPERTY, DEFAULT_HOST ); - final String port = System.getProperty( PORT_PROPERTY, DEFAULT_PORT ); - - try - { - final Properties connectProps = new Properties(); - connectProps.setProperty( "host", host ); - connectProps.setProperty( "port", port ); - mySimpleClient.login( connectProps ); - } - catch ( Exception e ) - { - LOGGER.log( Level.SEVERE, "Problem when connecting to the server.", e ); - - disconnected( false, e.getMessage() ); - } } } Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ClientContext.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ClientContext.java 2008-04-17 18:19:22 UTC (rev 462) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ClientContext.java 2008-04-18 22:31:12 UTC (rev 463) @@ -24,6 +24,8 @@ private final Queue<CallbackEvent> myEvents = new PriorityQueue<CallbackEvent>(); private final Set<IssueListener> myIssueListeners = new HashSet<IssueListener>( 5 ); + private final Map<String, GameObject> myNamedGameObjects = new HashMap<String, GameObject>( 101 ); + private long myInGameClock_ms = 0L; private GameObjectId myRootObjectId; @@ -100,10 +102,28 @@ public GameObject createGameObject() { final DefaultGameObject gameObject = new DefaultGameObject(); - gameObject.setId( createGameObjectId( gameObject ) ); + setupGameObject( gameObject ); + return gameObject; + } + + private void setupGameObject( final GameObject gameObject ) + { + // NOTE: Casting to default game object so we can set the id. Might not work in all cases, maybe come up with better solution for the id setting? + ( (DefaultGameObject) gameObject ).setId( createGameObjectId( gameObject ) ); + addGameObject( gameObject ); + } + public <T extends GameObject> T createGameObject( final Class<T> gameObjectType ) + throws InstantiationException, IllegalAccessException + { + ParameterChecker.checkNotNull( gameObjectType, "gameObjectType" ); + + final T gameObject = gameObjectType.newInstance(); + + setupGameObject( gameObject ); + return gameObject; } @@ -236,6 +256,16 @@ myRootObjectId = rootObjectId; } + public void bindGameObjectToName( final String name, final GameObject gameObject ) + { + myNamedGameObjects.put( name, gameObject ); + } + + public GameObject getGameObjectBoundToName( final String name ) + { + return myNamedGameObjects.get( name ); + } + //====================================================================== // Private Methods Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/core/DefaultGameObject.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/DefaultGameObject.java 2008-04-17 18:19:22 UTC (rev 462) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/DefaultGameObject.java 2008-04-18 22:31:12 UTC (rev 463) @@ -20,6 +20,7 @@ /** * Common functionality for all/most implementations of GameObject. */ +// TODO: Remove public constructors, so that GameObjects always have to be managed through the GameContext. // IDEA: Create some kind of GameObject specific debug stream listening system? // An admin readable mostRecentError field? Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/core/GameObject.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/GameObject.java 2008-04-17 18:19:22 UTC (rev 462) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/GameObject.java 2008-04-18 22:31:12 UTC (rev 463) @@ -1,6 +1,5 @@ package org.skycastle.core; -import com.sun.sgs.app.ManagedObject; import com.sun.sgs.app.Task; import org.skycastle.core.acting.ActionFacade; import org.skycastle.core.property.PropertyFacade; @@ -12,7 +11,7 @@ // Return handlers pros: cleaner interfaces, leaner implementation, less places that change if a handler changes, less collision with POJOs. // Delegate pros: Simpler to use interface (but POJO usually provides a simple interface anyway? and other interfaces are used inside the framework..). public interface GameObject - extends ManagedObject, PropertyFacade, ActionFacade, MessagingFacade, Task + extends PropertyFacade, ActionFacade, MessagingFacade, Task { /** @@ -20,4 +19,5 @@ */ GameObjectId getId(); + } Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/core/GameObjectContext.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/GameObjectContext.java 2008-04-17 18:19:22 UTC (rev 462) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/GameObjectContext.java 2008-04-18 22:31:12 UTC (rev 463) @@ -50,4 +50,25 @@ * @return the created game object. Can be read and written to. */ GameObject createGameObject(); + + /** + * Creates a new game object of the specified type, registers it with the {@link GameObjectContext}, and returns it for + * reading and writing. + * + * @param gameObjectType a class that implements GameObject and has a no-arguments constructor. + * + * @return the created game object. Can be read and written to. + */ + <T extends GameObject> T createGameObject( Class<T> gameObjectType ) + throws InstantiationException, IllegalAccessException; + + /** + * Associates the specified name with the specified {@link GameObject} + */ + void bindGameObjectToName( String name, GameObject gameObject ); + + /** + * @return the {@link GameObject} that is bound to the specified name, or null if none found. + */ + GameObject getGameObjectBoundToName( String name ); } Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ServerSideContext.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ServerSideContext.java 2008-04-17 18:19:22 UTC (rev 462) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ServerSideContext.java 2008-04-18 22:31:12 UTC (rev 463) @@ -1,15 +1,14 @@ package org.skycastle.core; -import com.sun.sgs.app.AppContext; -import com.sun.sgs.app.DataManager; -import com.sun.sgs.app.ManagedReference; +import com.sun.sgs.app.*; +import org.skycastle.util.ParameterChecker; import java.io.Serializable; /** * @author Hans Häggström */ -public class ServerSideContext +public final class ServerSideContext implements GameObjectContext, Serializable { @@ -18,21 +17,27 @@ private static final long serialVersionUID = 1L; + private static final String USER_BINDING_PREFIX = "Binding_"; + //====================================================================== // Public Methods //---------------------------------------------------------------------- // GameObjectContext Implementation + // TODO: Change to reflect that the GameContext internally manages all game objs. public GameObjectId createGameObjectId( final GameObject gameObject ) { + ParameterChecker.checkNotNull( gameObject, "gameObject" ); + checkImplementsManagedObject( gameObject ); + final DataManager dataManager = AppContext.getDataManager(); // Assign unique ID, and bind it, so that we can access this object easily in the future - final ManagedReference reference = dataManager.createReference( gameObject ); + final ManagedReference reference = dataManager.createReference( (ManagedObject) gameObject ); final GameObjectId id = new GameObjectId( GameObjectId.GAME_OBJECT_BINDING_PREFIX + reference.getId().toString() ); - dataManager.setBinding( id.toString(), gameObject ); + dataManager.setBinding( id.toString(), (ManagedObject) gameObject ); return id; } @@ -52,7 +57,7 @@ if ( retrievedGameObject != null && forModification ) { - dataManager.markForUpdate( retrievedGameObject ); + dataManager.markForUpdate( (ManagedObject) retrievedGameObject ); } return retrievedGameObject; @@ -66,7 +71,67 @@ public GameObject createGameObject() { - throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT + final GameObject serverSideGameObject = new ServerSideGameObject(); + + // Assign ID and register the object. + createGameObjectId( serverSideGameObject ); + + return serverSideGameObject; } + public <T extends GameObject> T createGameObject( final Class<T> gameObjectType ) + throws InstantiationException, IllegalAccessException + { + ParameterChecker.checkNotNull( gameObjectType, "gameObjectType" ); + if ( !gameObjectType.isAssignableFrom( ServerSideGameObject.class ) ) + { + throw new IllegalArgumentException( "The type of the object has to be a subtype of ServerSideGameObject on the server, but it was '" + gameObjectType + "' ." ); + } + + final T serverSideGameObject = gameObjectType.newInstance(); + + // Assign ID and register the object. + createGameObjectId( serverSideGameObject ); + + return serverSideGameObject; + } + + public void bindGameObjectToName( final String name, final GameObject gameObject ) + { + ParameterChecker.checkNotNull( gameObject, "gameObject" ); + checkImplementsManagedObject( gameObject ); + + + final DataManager dataManager = AppContext.getDataManager(); + + dataManager.setBinding( USER_BINDING_PREFIX + name, (ManagedObject) gameObject ); + } + + public GameObject getGameObjectBoundToName( final String name ) + { + ParameterChecker.checkNotNull( name, "name" ); + + final DataManager dataManager = AppContext.getDataManager(); + + try + { + return dataManager.getBinding( USER_BINDING_PREFIX + name, GameObject.class ); + } + catch ( NameNotBoundException e ) + { + return null; + } + } + + //====================================================================== + // Private Methods + + private void checkImplementsManagedObject( final GameObject gameObject ) + { + if ( !( gameObject instanceof ManagedObject ) ) + { + throw new IllegalArgumentException( "On the server side, a GameObject must implement ManagedObject" ); + } + } + } Added: trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ServerSideGameObject.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ServerSideGameObject.java (rev 0) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ServerSideGameObject.java 2008-04-18 22:31:12 UTC (rev 463) @@ -0,0 +1,30 @@ +package org.skycastle.core; + +import com.sun.sgs.app.ManagedObject; + +/** + * {@link GameObject} for use on server side. Implements the {@link ManagedObject} interface. + * + * @author Hans Haggstrom + */ +public final class ServerSideGameObject + extends DefaultGameObject + implements ManagedObject +{ + + //====================================================================== + // Private Constants + + private static final long serialVersionUID = 1L; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Constructors + + ServerSideGameObject() + { + } + +} Added: trunk/skycastle/modules/core/src/main/java/org/skycastle/core/acting/AbstractAct.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/acting/AbstractAct.java (rev 0) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/acting/AbstractAct.java 2008-04-18 22:31:12 UTC (rev 463) @@ -0,0 +1,49 @@ +package org.skycastle.core.acting; + +import org.skycastle.core.GameObject; +import org.skycastle.messaging.MessageListener; +import org.skycastle.util.parameters.ParameterSet; + +/** + * An abstract act implementation that provides empty implementations for doStep and stop. + * + * @author Hans Haggstrom + */ +public abstract class AbstractAct + implements Act +{ + + //====================================================================== + // Private Constants + + private static final long serialVersionUID = 1L; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Act Implementation + + public long doStep( final GameObject hostObject, + final MessageListener messageHandler, + final ParameterSet parameters, + final ParameterSet configurationParameters, + final long timeSinceLastCall_ms ) + throws Exception + { + // Nothing to do + + return -1; // Do not repeat + } + + public void stop( final GameObject hostObject, + final MessageListener messageHandler, + final ParameterSet parameters, + final ParameterSet configurationParameters, + final long timeSinceLastCall_ms ) + throws Exception + { + // Nothing to do + } + +} Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/ProtocolCommunicator.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/ProtocolCommunicator.java 2008-04-17 18:19:22 UTC (rev 462) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/ProtocolCommunicator.java 2008-04-18 22:31:12 UTC (rev 463) @@ -5,8 +5,6 @@ import org.skycastle.protocol.negotiation.ProtocolNegotiator; import org.skycastle.util.ParameterChecker; -import java.io.Serializable; - /** * Takes care of negotiating a protocol, and after that forwarding decoded messages to a listener. * <p/> @@ -18,7 +16,6 @@ */ // IDEA: Move logging / some error handling to this class, and avoid throwing out exceptions? public abstract class ProtocolCommunicator - implements Serializable { //====================================================================== @@ -207,7 +204,8 @@ * * @param message the deocoded message. */ - protected abstract void onMessage( final Message message ) throws ProtocolException; + protected abstract void onMessage( final Message message ) + throws ProtocolException; //====================================================================== // Private Methods @@ -241,7 +239,8 @@ } - private void handleMessage( final byte[] byteMessage ) throws ProtocolException + private void handleMessage( final byte[] byteMessage ) + throws ProtocolException { // Decode incoming message and notify decendant implementation onMessage( myProtocolNegotiator.getProtocol().decode( byteMessage ) ); Modified: trunk/skycastle/modules/server/src/main/java/org/skycastle/server/SkycastleClientSessionHandler.java =================================================================== --- trunk/skycastle/modules/server/src/main/java/org/skycastle/server/SkycastleClientSessionHandler.java 2008-04-17 18:19:22 UTC (rev 462) +++ trunk/skycastle/modules/server/src/main/java/org/skycastle/server/SkycastleClientSessionHandler.java 2008-04-18 22:31:12 UTC (rev 463) @@ -1,6 +1,8 @@ package org.skycastle.server; -import com.sun.sgs.app.*; +import com.sun.sgs.app.ClientSession; +import com.sun.sgs.app.ClientSessionListener; +import com.sun.sgs.app.Task; import org.skycastle.core.*; import org.skycastle.messaging.Message; import org.skycastle.messaging.modifications.ModificationMessage; @@ -126,7 +128,8 @@ // Task Implementation - public void run() throws Exception + public void run() + throws Exception { onTimeoutCallback(); } @@ -186,7 +189,8 @@ @Override - protected void onMessage( final Message message ) throws ProtocolException + protected void onMessage( final Message message ) + throws ProtocolException { // Check the message sender id so that the client is not claiming to be someone else. if ( !myClientAccountId.equals( message.getSenderId() ) ) @@ -240,21 +244,16 @@ */ private GameObject getUserAccount( final String userLoginName ) { - final DataManager dataManager = AppContext.getDataManager(); - final String bindingName = ACCOUNT_PREFIX + userLoginName; - GameObject clientAccount; - - try + final GameObjectContext context = GameContext.getGameObjectContext(); + GameObject clientAccount = context.getGameObjectBoundToName( bindingName ); + if ( clientAccount == null ) { - clientAccount = dataManager.getBinding( bindingName, GameObject.class ); - } - catch ( NameNotBoundException e ) - { // If client doesn't yet have any account, create a new one - clientAccount = new DefaultGameObject(); + clientAccount = context.createGameObject(); + // TODO: Do we need to store the client user name with the account? Any other data? // TODO: This is a game / server extension point, as different servers could have different options and provided UI layouts here. // By default, we could however have some standard stuff like information about the server @@ -262,7 +261,7 @@ // TODO: Use some server configuration property or object to determine which class should be instantiated as the account object for new users. // Store created account for future access - dataManager.setBinding( bindingName, clientAccount ); + context.bindGameObjectToName( bindingName, clientAccount ); myClientAccountId = clientAccount.getId(); } Added: trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/LabelUi.java =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/LabelUi.java (rev 0) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/LabelUi.java 2008-04-18 22:31:12 UTC (rev 463) @@ -0,0 +1,68 @@ +package org.skycastle.ui; + +import javax.swing.*; + +/** + * A text label. + * + * @author Hans Haggstrom + */ +public final class LabelUi + extends UiObject +{ + + //====================================================================== + // Private Fields + + private String myText = ""; + private JLabel myLabel = null; + + //====================================================================== + // Private Constants + + private static final long serialVersionUID = 1L; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Constructors + + public LabelUi() + { + } + + public LabelUi( final String text ) + { + myText = text; + } + + //---------------------------------------------------------------------- + // Other Public Methods + + public String getText() + { + return myText; + } + + public void setText( final String text ) + { + myText = text; + + if ( myLabel != null ) + { + myLabel.setText( myText ); + } + } + + //====================================================================== + // Protected Methods + + @Override + protected JComponent createUi() + { + myLabel = new JLabel( myText ); + return myLabel; + } + +} Added: trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/SplitPanelUi.java =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/SplitPanelUi.java (rev 0) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/SplitPanelUi.java 2008-04-18 22:31:12 UTC (rev 463) @@ -0,0 +1,102 @@ +package org.skycastle.ui; + +import javax.swing.*; +import java.awt.BorderLayout; + +/** + * A panel that is split into two areas with a horizontal or vertical divider. + * + * @author Hans Haggstrom + */ +public class SplitPanelUi + extends UiObject +{ + + //====================================================================== + // Private Fields + + private JSplitPane mySplitPane = null; + + private int mySize; + private boolean myVerticalOrientation; // If false, orientation is horizontal. Should really be an enum instead. + private boolean myFirstEdgeAsSizeAnchor;// Size is relative to first or second edge - should really be an enum. + private boolean mySizeIsInPercent; // Relative or absolute size - should really be an enum. if false, size is in pixels, if true, in % + + private UiObject myFirstUi; + private UiObject mySecondUi; + private JPanel myFirstPanel; + private JPanel mySecondPanel; + + //====================================================================== + // Private Constants + + private static final long serialVersionUID = 1L; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Other Public Methods + + public UiObject getFirstUi() + { + return myFirstUi; + } + + public void setFirstUi( final UiObject firstUi ) + { + changeUi( myFirstUi, firstUi, myFirstPanel ); + myFirstUi = firstUi; + } + + public UiObject getSecondUi() + { + return mySecondUi; + } + + public void setSecondUi( final UiObject secondUi ) + { + changeUi( mySecondUi, secondUi, mySecondPanel ); + mySecondUi = secondUi; + } + + //====================================================================== + // Protected Methods + + @Override + protected JComponent createUi() + { + myFirstPanel = new JPanel( new BorderLayout() ); + mySecondPanel = new JPanel( new BorderLayout() ); + mySplitPane = new JSplitPane( JSplitPane.VERTICAL_SPLIT, myFirstPanel, mySecondPanel ); + + changeUi( null, myFirstUi, myFirstPanel ); + changeUi( null, mySecondUi, mySecondPanel ); + +// mySplitPane.setEnabled( false ); + + mySplitPane.setDividerLocation( 100 ); + // TODO: Listen to size changes, and update divider location + + return mySplitPane; + } + + //====================================================================== + // Private Methods + + private void changeUi( final UiObject oldValue, final UiObject newValue, final JPanel hostPanel ) + { + if ( oldValue != null && + hostPanel != null ) + { + hostPanel.remove( oldValue.getUi() ); + } + + if ( newValue != null && + hostPanel != null ) + { + hostPanel.add( newValue.getUi(), BorderLayout.CENTER ); + } + } + +} Modified: trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/SwingClientUi.java =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/SwingClientUi.java 2008-04-17 18:19:22 UTC (rev 462) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/ui/SwingClientUi.java 2008-04-18 22:31:12 UTC (rev 463) @@ -1,6 +1,7 @@ package org.skycastle.ui; import javax.swing.*; +import java.awt.BorderLayout; import java.awt.Dimension; /** @@ -35,7 +36,7 @@ myMainFrame.setVisible( true ); - myRootPanel = new JPanel(); + myRootPanel = new JPanel( new BorderLayout() ); myRootPanel.setPreferredSize( DEFAULT_SIZE ); myMainFrame.getContentPane().add( myRootPanel ); @@ -75,7 +76,7 @@ if ( myMainFrame != null && myRootUi != null ) { - myRootPanel.add( myRootUi.getUi() ); + myRootPanel.add( myRootUi.getUi(), BorderLayout.CENTER ); } if ( myRootPanel != null ) Modified: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/ParameterChecker.java =================================================================== --- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/ParameterChecker.java 2008-04-17 18:19:22 UTC (rev 462) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/ParameterChecker.java 2008-04-18 22:31:12 UTC (rev 463) @@ -117,7 +117,7 @@ * * @throws IllegalArgumentException if the check fails. */ - public static void checkPositiveNonZeroInteger( int parameter, String parameterName ) + public static void checkPositiveNonZeroInteger( long parameter, String parameterName ) { if ( parameter <= 0 ) { This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.