Revision: 441 http://skycastle.svn.sourceforge.net/skycastle/?rev=441&view=rev Author: zzorn Date: 2008-04-05 12:04:43 -0700 (Sat, 05 Apr 2008) Log Message: ----------- More work on server and client. Replaced the GameModel with GameObjectContext. The code should compile and tests should be green. Modified Paths: -------------- 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/GameObjectId.java trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ServerSideContext.java trunk/skycastle/modules/core/src/main/java/org/skycastle/core/UnitTestingContext.java trunk/skycastle/modules/core/src/main/java/org/skycastle/core/clientside/GameModel.java trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/UpdateMessage.java trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/action/ActionFinishedMessage.java trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/action/ActionInvocationFailedMessage.java trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/action/ActionStartedMessage.java trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/action/ActionStatusChangeMessage.java trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/object/GameObjectAddedMessage.java trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/object/GameObjectRemovedMessage.java trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/property/PropertyAddedMessage.java trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/property/PropertyChangedMessage.java trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/property/PropertyRemovedMessage.java trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/AbstractProtocolNegotiator.java trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/ClientSideProtocolNegotiator.java trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/NegotiationStatus.java trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/ProtocolNegotiator.java trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/ServerSideProtocolNegotiator.java trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/protocols/AbstractProtocol.java trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/protocols/Protocol.java trunk/skycastle/modules/core/src/test/java/org/skycastle/core/AbstractGameObjectActionsTest.java trunk/skycastle/modules/core/src/test/java/org/skycastle/core/AbstractGameObjectPropertiesTest.java trunk/skycastle/modules/core/src/test/java/org/skycastle/core/geometry/space/grid/GridTest.java trunk/skycastle/modules/core/src/test/java/org/skycastle/protocol/negotiation/ProtocolNegotiationTest.java trunk/skycastle/modules/core/src/test/java/org/skycastle/protocol/protocols/SerializationProtocolTest.java trunk/skycastle/modules/server/src/main/java/org/skycastle/server/SkycastleClientSessionHandler.java trunk/skycastle/modules/server/src/main/java/org/skycastle/server/SkycastleServer.java trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/issue/IssueImpl.java Added Paths: ----------- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/CallbackEvent.java trunk/skycastle/modules/core/src/main/java/org/skycastle/core/DirectReference.java trunk/skycastle/modules/core/src/main/java/org/skycastle/core/GameObjectReference.java trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ObjectReference.java trunk/skycastle/modules/core/src/main/java/org/skycastle/core/PersistentReference.java Removed Paths: ------------- trunk/skycastle/modules/client/src/main/java/org/skycastle/client/model/AbstractGameModel.java trunk/skycastle/modules/client/src/main/java/org/skycastle/client/model/clientside/ trunk/skycastle/modules/client/src/main/java/org/skycastle/client/model/composite/ trunk/skycastle/modules/client/src/main/java/org/skycastle/client/model/local/ trunk/skycastle/modules/client/src/test/java/org/skycastle/client/model/clientside/ trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/AbstractProtocol.java trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/Protocol.java 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-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/client/src/main/java/org/skycastle/client/SkycastleClient.java 2008-04-05 19:04:43 UTC (rev 441) @@ -4,6 +4,7 @@ import com.sun.sgs.client.ClientChannelListener; import com.sun.sgs.client.simple.SimpleClient; import com.sun.sgs.client.simple.SimpleClientListener; +import org.skycastle.core.GameContext; import org.skycastle.messaging.Message; import org.skycastle.messaging.updates.UpdateMessage; import org.skycastle.protocol.ProtocolException; @@ -135,7 +136,7 @@ // Apply the update message to the client side model // TODO: Use cient side GameContext directly instead, and specify a namespace prefix for this server and account - updateMessage.applyStateChangeToModel(); + updateMessage.applyStateChangeToModel( GameContext.getGameObjectContext() ); } catch ( ProtocolException e ) { Deleted: trunk/skycastle/modules/client/src/main/java/org/skycastle/client/model/AbstractGameModel.java =================================================================== --- trunk/skycastle/modules/client/src/main/java/org/skycastle/client/model/AbstractGameModel.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/client/src/main/java/org/skycastle/client/model/AbstractGameModel.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,92 +0,0 @@ -package org.skycastle.client.model; - -import org.skycastle.core.GameObject; -import org.skycastle.core.GameObjectId; -import org.skycastle.core.clientside.GameModel; -import org.skycastle.core.clientside.GameModelListener; -import org.skycastle.util.ParameterChecker; - -import java.util.ArrayList; -import java.util.List; - -/** - * Common functionality. - */ -public abstract class AbstractGameModel - implements GameModel -{ - - //====================================================================== - // Private Fields - - private final List<GameModelListener> myListeners = new ArrayList<GameModelListener>(); - - private final GameModelListener myListenerDelegate = new GameModelListener() - { - - public void onGameObjectAdded( final GameObject addedObject ) - { - for ( GameModelListener listener : myListeners ) - { - listener.onGameObjectAdded( addedObject ); - } - } - - - public void onGameObjectRemoved( final GameObjectId removedObjectId ) - { - for ( GameModelListener listener : myListeners ) - { - listener.onGameObjectRemoved( removedObjectId ); - } - } - - }; - - //====================================================================== - // Public Methods - - //---------------------------------------------------------------------- - // GameModel Implementation - - - public final void addGameModelListener( GameModelListener addedGameModelListener ) - { - ParameterChecker.checkNotNull( addedGameModelListener, "addedGameModelListener" ); - ParameterChecker.checkNotAlreadyContained( addedGameModelListener, myListeners, "myListeners" ); - - myListeners.add( addedGameModelListener ); - } - - - public final void removeGameModelListener( GameModelListener removedGameModelListener ) - { - ParameterChecker.checkNotNull( removedGameModelListener, "removedGameModelListener" ); - ParameterChecker.checkContained( removedGameModelListener, myListeners, "myListeners" ); - - myListeners.remove( removedGameModelListener ); - } - - //====================================================================== - // Protected Methods - - //---------------------------------------------------------------------- - // Protected Constructors - - /** - * Creates a new {@link AbstractGameModel}. - */ - protected AbstractGameModel() - { - } - - - /** - * @return a listener that will forward any events to all registered listeners. - */ - protected final GameModelListener getListenerDelegate() - { - return myListenerDelegate; - } - -} Added: trunk/skycastle/modules/core/src/main/java/org/skycastle/core/CallbackEvent.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/CallbackEvent.java (rev 0) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/CallbackEvent.java 2008-04-05 19:04:43 UTC (rev 441) @@ -0,0 +1,63 @@ +package org.skycastle.core; + +/** + * Represents a future callback that needs to be done to a specific {@link GameObject}. + * + * @author Hans Häggström + */ +class CallbackEvent + implements Comparable<CallbackEvent> +{ + + //====================================================================== + // Private Fields + + private final long myTime; + private final GameObject myGameObject; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Comparable Implementation + + public int compareTo( final CallbackEvent o ) + { + if ( myTime < o.getTime() ) + { + return -1; + } + else if ( myTime > o.getTime() ) + { + return 1; + } + else + { + return 0; + } + } + + //---------------------------------------------------------------------- + // Other Public Methods + + public long getTime() + { + return myTime; + } + + + public GameObject getGameObject() + { + return myGameObject; + } + + //====================================================================== + // Private Methods + + protected CallbackEvent( final GameObject gameObject, final long time ) + { + myGameObject = gameObject; + myTime = time; + } + +} 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-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ClientContext.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,9 +1,16 @@ package org.skycastle.core; -import org.skycastle.core.clientside.GameModel; import org.skycastle.util.ParameterChecker; +import org.skycastle.util.issue.Issue; +import org.skycastle.util.issue.IssueImpl; +import org.skycastle.util.issue.IssueListener; +import org.skycastle.util.issue.Severity; +import java.util.*; + /** + * A client side implementation of {@link GameObjectContext}. + * * @author Hans Häggström */ public class ClientContext @@ -13,9 +20,11 @@ //====================================================================== // Private Fields - private final GameModel myGameModel; + private final Map<GameObjectId, GameObject> myGameObjects = new HashMap<GameObjectId, GameObject>( 1001 ); + private final Queue<CallbackEvent> myEvents = new PriorityQueue<CallbackEvent>(); + private final Set<IssueListener> myIssueListeners = new HashSet<IssueListener>( 5 ); - private GameObjectId myId; + private long myInGameClock_ms = 0L; //====================================================================== // Public Methods @@ -24,42 +33,182 @@ // Constructors /** - * Creates a new {@link org.skycastle.core.ClientContext}. + * Creates a new {@link ClientContext}. + * <p/> + * The in-game time starts at 0. */ - public ClientContext( final GameModel gameModel, final GameObjectId id ) + public ClientContext() { - myId = id; - ParameterChecker.checkNotNull( gameModel, "gameModel" ); - ParameterChecker.checkNotNull( id, "id" ); + } - myGameModel = gameModel; + + /** + * Creates a new {@link ClientContext}. + * + * @param inGameClock_ms starting value for the in-game time in milliseconds since in-game epoch. + */ + public ClientContext( final long inGameClock_ms ) + { + myInGameClock_ms = inGameClock_ms; } + + /** + * Creates a new {@link ClientContext}. + * + * @param inGameClock_ms starting value for the in-game time in milliseconds since in-game epoch. + * @param issueListener an {@link IssueListener} to register with this {@link ClientContext}.¨ It will be + * notified of any problems, such as exceptions when calling scheduled callbacks. + */ + public ClientContext( final long inGameClock_ms, final IssueListener issueListener ) + { + this( inGameClock_ms ); + + addIssueListener( issueListener ); + } + //---------------------------------------------------------------------- // GameObjectContext Implementation + public final GameObjectId createGameObjectId( final GameObject gameObject ) + { + // IMPLEMENT: TODO: How should we create the ID? For client side GameObjects, we need some specific prefix... + // DEBUG + return new GameObjectId( GameObjectId.GAME_OBJECT_BINDING_PREFIX + String.valueOf( gameObject.hashCode() ) ); + } - public GameObjectId createGameObjectId( final GameObject gameObject ) + + public final void addTaskCallback( final long timeOfCallback_ms, final GameObject gameObject ) { - return myId; + myEvents.add( new CallbackEvent( gameObject, timeOfCallback_ms ) ); } - public void addTaskCallback( final long timeToNextInvocation_ms, final GameObject gameObject ) + public final GameObject getGameObjectById( final GameObjectId gameObjectId, + final boolean forModification ) { - throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT + return myGameObjects.get( gameObjectId ); } - public GameObject getGameObjectById( final GameObjectId gameObjectId, final boolean forModification ) + public final long getCurrentGameTime_ms() { - return myGameModel.getGameObject( gameObjectId ); + return myInGameClock_ms; } + //---------------------------------------------------------------------- + // Other Public Methods - public long getCurrentGameTime_ms() + /** + * @param gameObject the {@link GameObject} to add to this {@link ClientContext}. + */ + public final void addGameObject( final GameObject gameObject ) { - throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT + ParameterChecker.checkNotNull( gameObject, "gameObject" ); + + myGameObjects.put( gameObject.getId(), gameObject ); } + + /** + * @param gameObject the {@link GameObject} to remove from this {@link ClientContext}. + */ + public final void removeGameObject( final GameObjectId gameObject ) + { + ParameterChecker.checkNotNull( gameObject, "gameObject" ); + + myGameObjects.remove( gameObject ); + } + + + /** + * Adds the specified {@link IssueListener}. + * + * @param addedIssueListener A listener that is notified about any errors, warnings, and other issues that + * are detected during client operation, such as exceptions when calling + * scheduled callbacks. Should not be null or already added. + */ + public final void addIssueListener( final IssueListener addedIssueListener ) + { + ParameterChecker.checkNotNull( addedIssueListener, "addedIssueListener" ); + ParameterChecker.checkNotAlreadyContained( addedIssueListener, myIssueListeners, "myIssueListeners" ); + + myIssueListeners.add( addedIssueListener ); + } + + + /** + * Removes the specified {@link IssueListener}. + * + * @param removedIssueListener should not be null, and should be present. + */ + public final void removeIssueListener( final IssueListener removedIssueListener ) + { + ParameterChecker.checkNotNull( removedIssueListener, "removedIssueListener" ); + ParameterChecker.checkContained( removedIssueListener, myIssueListeners, "myIssueListeners" ); + + myIssueListeners.remove( removedIssueListener ); + } + + + /** + * Increases the in-game time with the specied number of milliseconds, and calls any scheduled callbacks. + * <p/> + * If there are problems running any of the tasks, an issue is filed that is visible to the registered + * {@link IssueListener}s. + * + * @param timeIntervallToAdvance_ms number of in-game milliseconds to advance the time. + */ + public final void advanceTimeAndCallCallbacks( final long timeIntervallToAdvance_ms ) + { + setTimeAndCallCallbacks( myInGameClock_ms + timeIntervallToAdvance_ms ); + } + + + /** + * Sets the in-game time to a specified value and calls any scheduled callbacks. All callbacks that are + * scheduled at the current time or in the past will be called. + * <p/> + * If there are problems running any of the tasks, an issue is filed that is visible to the registered + * {@link IssueListener}s. + * + * @param newTime_ms the new in-game time to use, specified as in-game milliseconds since in-game epoch. + */ + public final void setTimeAndCallCallbacks( final long newTime_ms ) + { + myInGameClock_ms = newTime_ms; + + while ( myEvents.peek() != null && + myEvents.peek().getTime() <= myInGameClock_ms ) + { + final GameObject gameObject = myEvents.poll().getGameObject(); + + try + { + gameObject.run(); + } + catch ( Exception e ) + { + final Issue issue = new IssueImpl( + "Problem when calling a client side task callback on the game object '" + gameObject + "'.", + e, + Severity.ERROR, + "TaskCallbackException" ); + + reportIssue( issue ); + } + } + } + + //====================================================================== + // Private Methods + + private void reportIssue( final Issue issue ) + { + for ( final IssueListener issueListener : myIssueListeners ) + { + issueListener.onIssue( issue ); + } + } + } Added: trunk/skycastle/modules/core/src/main/java/org/skycastle/core/DirectReference.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/DirectReference.java (rev 0) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/DirectReference.java 2008-04-05 19:04:43 UTC (rev 441) @@ -0,0 +1,49 @@ +package org.skycastle.core; + +/** + * A simple direct reference, useful for client side and unit testing contexts, where we don't need to take + * into account multi-node issues or persistency. + * + * @author Hans Häggström + */ +public final class DirectReference<T> + implements ObjectReference<T> +{ + + //====================================================================== + // Private Fields + + private final T myReferencedObject; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Constructors + + /** + * Creates a new {@link org.skycastle.core.DirectReference}. + * + * @param objectToBeReferenced the object to reference to. + */ + public DirectReference( final T objectToBeReferenced ) + { + myReferencedObject = objectToBeReferenced; + } + + //---------------------------------------------------------------------- + // ObjectReference Implementation + + + public T getForReading() + { + return myReferencedObject; + } + + + public T getForWriting() + { + return myReferencedObject; + } + +} Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/core/GameObjectId.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/GameObjectId.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/GameObjectId.java 2008-04-05 19:04:43 UTC (rev 441) @@ -9,6 +9,7 @@ */ // TODO: Use the ID used by managed objects? - no way to get object by id, only by binding a string name to it. // IDEA: Could keep track also of on which server or client the object is? +// IDEA: Merge with GameObjectReference?? public final class GameObjectId implements Serializable { @@ -19,6 +20,15 @@ private final String myBoundName; //====================================================================== + // Public Constants + + /** + * The prefix to use for all {@link GameObject} identifiers. Allows other parts of the binding name space + * to be used for other purposes . + */ + public static final String GAME_OBJECT_BINDING_PREFIX = "GameObject_"; + + //====================================================================== // Private Constants private static final long serialVersionUID = 1L; @@ -36,34 +46,39 @@ { ParameterChecker.checkIsIdentifier( boundGameObjectName, "boundGameObjectName" ); + if ( !boundGameObjectName.startsWith( GAME_OBJECT_BINDING_PREFIX ) ) + { + throw new IllegalArgumentException( "The GameObjectId should should start with '" + GAME_OBJECT_BINDING_PREFIX + "', but it was '" + boundGameObjectName + "'." ); + } + myBoundName = boundGameObjectName; } //---------------------------------------------------------------------- - // Other Public Methods - - //---------------------------------------------------------------------- // Caononical Methods + @Override public String toString() { return myBoundName; } - public boolean equals( final Object o ) + @Override + public boolean equals( final Object obj ) { - if ( this == o ) + if ( this == obj ) { return true; } - if ( o == null || getClass() != o.getClass() ) + if ( obj == null || getClass() != obj.getClass() ) { return false; } - final GameObjectId that = (GameObjectId) o; + final GameObjectId that = (GameObjectId) obj; + //noinspection RedundantIfStatement,AccessingNonPublicFieldOfAnotherObject if ( myBoundName != null ? !myBoundName.equals( that.myBoundName ) : that.myBoundName != null ) { return false; @@ -73,9 +88,10 @@ } + @Override public int hashCode() { - return ( myBoundName != null ? myBoundName.hashCode() : 0 ); + return myBoundName != null ? myBoundName.hashCode() : 0; } } Added: trunk/skycastle/modules/core/src/main/java/org/skycastle/core/GameObjectReference.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/GameObjectReference.java (rev 0) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/GameObjectReference.java 2008-04-05 19:04:43 UTC (rev 441) @@ -0,0 +1,36 @@ +package org.skycastle.core; + +/** + * A reference to a {@link GameObject}. The reference can be serialized and persisted on the server side. + * + * @author Hans Häggström + */ +// IDEA: Could GameObjectId and GameObjectReference be combined? They seem to have more or less the same +// purpose, although GameObjectId is backed up by bindings, and works on both server and client side, +// while GameObjectReference is backed up by a ManagedObjectReference and only works on server side.. +public class GameObjectReference + extends PersistentReference<GameObject> +{ + + //====================================================================== + // Private Constants + + private static final long serialVersionUID = 1L; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Constructors + + /** + * Creates a new {@link org.skycastle.core.GameObjectReference} to the specified {@link GameObject}. + * + * @param gameObject the {@link GameObject} to create a reference to. + */ + public GameObjectReference( final GameObject gameObject ) + { + super( gameObject ); + } + +} Added: trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ObjectReference.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ObjectReference.java (rev 0) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ObjectReference.java 2008-04-05 19:04:43 UTC (rev 441) @@ -0,0 +1,25 @@ +package org.skycastle.core; + +import java.io.Serializable; + +/** + * Reference to some object, which can be retrieved either for reading or reading and writing. + * + * @author Hans Häggström + */ +public interface ObjectReference<T> + extends Serializable +{ + + /** + * @return the referenced object. The object should only be read from. + */ + T getForReading(); + + /** + * @return the referenced object. The object can be read from and written to. Note that using this if the + * object is only read from is bad for the performance, as reads can be done in parallell, but not + * writes. + */ + T getForWriting(); +} Added: trunk/skycastle/modules/core/src/main/java/org/skycastle/core/PersistentReference.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/PersistentReference.java (rev 0) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/PersistentReference.java 2008-04-05 19:04:43 UTC (rev 441) @@ -0,0 +1,68 @@ +package org.skycastle.core; + +import com.sun.sgs.app.AppContext; +import com.sun.sgs.app.ManagedObject; +import com.sun.sgs.app.ManagedReference; + +import java.io.Serializable; + +/** + * A reference to a persistent {@link ManagedObject} on the server, that uses generics to maintain the type of + * the object during compile time, to enable better correctness checking. + * <p/> + * Wraps a {@link ManagedReference}. + * + * @author Hans Häggström + * @type T the type of the wrapped object. + */ +public class PersistentReference<T extends ManagedObject> + implements ObjectReference<T>, Serializable +{ + + //====================================================================== + // Private Fields + + private final ManagedReference myManagedReference; + + //====================================================================== + // Private Constants + + private static final long serialVersionUID = 1L; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Constructors + + /** + * Creates a new {@link PersistentReference}. + * + * @param objectToReference the {@link ManagedObject} to create a reference to. + */ + public PersistentReference( final T objectToReference ) + { + myManagedReference = AppContext.getDataManager().createReference( objectToReference ); + } + + //---------------------------------------------------------------------- + // ObjectReference Implementation + + /** + * @return the referenced object. The object should preferably only be read from. It can be written to + * also, but that will result in slower code than if getForWriting had been called instead. + */ + public T getForReading() + { + //noinspection unchecked + return (T) myManagedReference.get( Object.class ); + } + + + public T getForWriting() + { + //noinspection unchecked + return (T) myManagedReference.getForUpdate( Object.class ); + } + +} 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-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/ServerSideContext.java 2008-04-05 19:04:43 UTC (rev 441) @@ -35,7 +35,7 @@ } - public void addTaskCallback( final long timeToNextInvocation_ms, final GameObject gameObject ) + public void addTaskCallback( final long timeOfCallback_ms, final GameObject gameObject ) { throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT } Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/core/UnitTestingContext.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/UnitTestingContext.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/UnitTestingContext.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,125 +1,37 @@ package org.skycastle.core; -import java.util.PriorityQueue; -import java.util.Queue; +import junit.framework.Assert; +import org.skycastle.util.issue.Issue; +import org.skycastle.util.issue.IssueListener; /** * A {@link GameObjectContext} used for unit testing. + * <p/> + * Works like a {@link ClientContext}, but fails directly when an issue is reported. * * @author Hans Häggström */ -public class UnitTestingContext - implements GameObjectContext +public final class UnitTestingContext + extends ClientContext { - //====================================================================== - // Private Fields - - private final Queue<CallbackEvent> myEvents = new PriorityQueue<CallbackEvent>(); - - private long myInGameClock_ms = 0; - - //====================================================================== - // Public Methods - //---------------------------------------------------------------------- - // GameObjectContext Implementation + // Constructors - public GameObjectId createGameObjectId( final GameObject gameObject ) + /** + * Creates a {@link UnitTestingContext}. + */ + public UnitTestingContext() { - return new GameObjectId( "TestGameObject" + String.valueOf( gameObject.hashCode() ) ); - } - - - public void addTaskCallback( final long timeOfCallback_ms, final GameObject gameObject ) - { - myEvents.add( new CallbackEvent( gameObject, timeOfCallback_ms ) ); - } - - - public GameObject getGameObjectById( final GameObjectId gameObjectId, final boolean forModification ) - { - throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT - } - - - public long getCurrentGameTime_ms() - { - return myInGameClock_ms; - } - - //---------------------------------------------------------------------- - // Other Public Methods - - public void advanceTimeAndCallCallbacks( final long timeIntervallToAdvance_ms ) throws Exception - { - myInGameClock_ms += timeIntervallToAdvance_ms; - - while ( myEvents.peek() != null && - myEvents.peek().getTime() <= myInGameClock_ms ) + super( 0L, new IssueListener() { - myEvents.poll().getGameObject().run(); - } - } - //====================================================================== - // Inner Classes - - private class CallbackEvent - implements Comparable<CallbackEvent> - { - - //====================================================================== - // Private Fields - - private final long myTime; - private final GameObject myGameObject; - - //====================================================================== - // Public Methods - - //---------------------------------------------------------------------- - // Comparable Implementation - - public int compareTo( final CallbackEvent o ) - { - if ( myTime < o.getTime() ) + public void onIssue( final Issue issue ) { - return -1; + Assert.fail( issue.toString() ); } - else if ( myTime > o.getTime() ) - { - return 1; - } - else - { - return 0; - } - } - //---------------------------------------------------------------------- - // Other Public Methods - - public long getTime() - { - return myTime; - } - - - public GameObject getGameObject() - { - return myGameObject; - } - - //====================================================================== - // Private Methods - - private CallbackEvent( final GameObject gameObject, final long time ) - { - myGameObject = gameObject; - myTime = time; - } - + } ); } } Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/core/clientside/GameModel.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/core/clientside/GameModel.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/core/clientside/GameModel.java 2008-04-05 19:04:43 UTC (rev 441) @@ -25,17 +25,4 @@ */ void removeGameObject( GameObjectId gameObjectId ); - /** - * Adds the specified GameModelListener. - * - * @param addedGameModelListener should not be null or already added. - */ - void addGameModelListener( GameModelListener addedGameModelListener ); - - /** - * Removes the specified GameModelListener. - * - * @param removedGameModelListener should not be null, and should be present. - */ - void removeGameModelListener( GameModelListener removedGameModelListener ); } Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/UpdateMessage.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/UpdateMessage.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/UpdateMessage.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,6 +1,8 @@ package org.skycastle.messaging.updates; import org.skycastle.core.GameObject; +import org.skycastle.core.GameObjectContext; +import org.skycastle.core.GameObjectId; import org.skycastle.core.clientside.GameModel; import org.skycastle.messaging.AbstractMessage; import org.skycastle.messaging.Message; @@ -15,13 +17,17 @@ public interface UpdateMessage extends Message { + /** * Applies this update to a {@link GameModel}. * <p/> * Typically used on the client data model, which consists of proxy objects that should mirror the state * of the real objects on the server side. * - * @param gameModel The {@link GameModel} to update. + * @param gameObjectContext A {@link GameObjectContext} that provides methods to access {@link + * GameObject}s by {@link GameObjectId}s and do other modifications to the local + * data model. */ - void applyStateChangeToModel( final GameModel gameModel ); + void applyStateChangeToModel( final GameObjectContext gameObjectContext ); + } Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/action/ActionFinishedMessage.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/action/ActionFinishedMessage.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/action/ActionFinishedMessage.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,12 +1,13 @@ package org.skycastle.messaging.updates.action; +import org.skycastle.core.GameObjectContext; import org.skycastle.core.GameObjectId; -import org.skycastle.core.clientside.GameModel; /** * Indicates that the specified action has stopped, either by a failure or by completing naturally. * <p/> - * If the action is immediate, no separate {@link ActionStartedMessage} will be sent, just an {@link ActionFinishedMessage}. + * If the action is immediate, no separate {@link ActionStartedMessage} will be sent, just an {@link + * ActionFinishedMessage}. * * @author Hans H�ggstr�m */ @@ -26,7 +27,9 @@ //---------------------------------------------------------------------- // Constructors - public ActionFinishedMessage( final GameObjectId updatedObjectId, final String memberIdentifier, final long actId ) + public ActionFinishedMessage( final GameObjectId updatedObjectId, + final String memberIdentifier, + final long actId ) { super( updatedObjectId, memberIdentifier, actId ); } @@ -34,9 +37,9 @@ //---------------------------------------------------------------------- // UpdateMessage Implementation - public void applyStateChangeToModel( final GameModel gameModel ) + public void applyStateChangeToModel( final GameObjectContext gameObjectContext ) { throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT + } - } Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/action/ActionInvocationFailedMessage.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/action/ActionInvocationFailedMessage.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/action/ActionInvocationFailedMessage.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,10 +1,11 @@ package org.skycastle.messaging.updates.action; +import org.skycastle.core.GameObjectContext; import org.skycastle.core.GameObjectId; -import org.skycastle.core.clientside.GameModel; /** - * Indicates that an {@link org.skycastle.messaging.modifications.action.InvokeActionMessage} failed for some reason. + * Indicates that an {@link org.skycastle.messaging.modifications.action.InvokeActionMessage} failed for some + * reason. * * @author Hans H�ggstr�m */ @@ -41,9 +42,10 @@ // UpdateMessage Implementation - public void applyStateChangeToModel( final GameModel gameModel ) + public void applyStateChangeToModel( final GameObjectContext gameObjectContext ) { throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT + } //---------------------------------------------------------------------- Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/action/ActionStartedMessage.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/action/ActionStartedMessage.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/action/ActionStartedMessage.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,7 +1,7 @@ package org.skycastle.messaging.updates.action; +import org.skycastle.core.GameObjectContext; import org.skycastle.core.GameObjectId; -import org.skycastle.core.clientside.GameModel; /** * @author Hans H�ggstr�m @@ -21,7 +21,9 @@ //---------------------------------------------------------------------- // Constructors - public ActionStartedMessage( final GameObjectId updatedObjectId, final String memberIdentifier, final long actId ) + public ActionStartedMessage( final GameObjectId updatedObjectId, + final String memberIdentifier, + final long actId ) { super( updatedObjectId, memberIdentifier, actId ); } @@ -29,10 +31,11 @@ //---------------------------------------------------------------------- // UpdateMessage Implementation - - public void applyStateChangeToModel( final GameModel gameModel ) + public void applyStateChangeToModel( final GameObjectContext gameObjectContext ) { throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT + } + } Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/action/ActionStatusChangeMessage.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/action/ActionStatusChangeMessage.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/action/ActionStatusChangeMessage.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,7 +1,7 @@ package org.skycastle.messaging.updates.action; +import org.skycastle.core.GameObjectContext; import org.skycastle.core.GameObjectId; -import org.skycastle.core.clientside.GameModel; import org.skycastle.util.parameters.ValidationError; import java.util.Set; @@ -67,11 +67,11 @@ // UpdateMessage Implementation - public void applyStateChangeToModel( final GameModel gameModel ) + public void applyStateChangeToModel( final GameObjectContext gameObjectContext ) { throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT + } - //---------------------------------------------------------------------- // Other Public Methods Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/object/GameObjectAddedMessage.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/object/GameObjectAddedMessage.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/object/GameObjectAddedMessage.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,9 +1,8 @@ package org.skycastle.messaging.updates.object; import org.skycastle.core.GameObject; +import org.skycastle.core.GameObjectContext; import org.skycastle.core.GameObjectId; -import org.skycastle.core.clientside.GameModel; -import org.skycastle.core.clientside.ProxyGameObject; import org.skycastle.messaging.AbstractMessage; import org.skycastle.messaging.updates.UpdateMessage; import org.skycastle.util.ParameterChecker; @@ -17,6 +16,13 @@ * Any available properties, actions, etc. will be indicated with further update messages. * * @author Hans Haggstrom + * @deprecated This is not really needed. The client only needs the root account object, after that all game + * objects can be accessed through it. + * <p/> + * TODO: We do need messages to notify collection properties about added and removed entries + * though, without having to resend the whole collection. + * <p/> + * Maybe change these messages to work on collection properties of GameObjects instead? */ public final class GameObjectAddedMessage extends AbstractMessage @@ -55,7 +61,6 @@ //---------------------------------------------------------------------- // Message Implementation - @Override public ValidationError validate( final Set<String> allowedContainedTypes, final String errorPrefix ) { @@ -72,12 +77,9 @@ //---------------------------------------------------------------------- // UpdateMessage Implementation - - public void applyStateChangeToModel( final GameModel gameModel ) + public void applyStateChangeToModel( final GameObjectContext gameObjectContext ) { - ParameterChecker.checkNotNull( gameModel, "gameModel" ); + throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT - gameModel.addGameObject( new ProxyGameObject( myAddedObjectId ) ); } - } Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/object/GameObjectRemovedMessage.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/object/GameObjectRemovedMessage.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/object/GameObjectRemovedMessage.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,7 +1,7 @@ package org.skycastle.messaging.updates.object; +import org.skycastle.core.GameObjectContext; import org.skycastle.core.GameObjectId; -import org.skycastle.core.clientside.GameModel; import org.skycastle.messaging.AbstractMessage; import org.skycastle.messaging.updates.UpdateMessage; import org.skycastle.util.ParameterChecker; @@ -13,6 +13,7 @@ * A message to indicate that the specified GameObject is no longer available for the client. * * @author Hans Haggstrom + * @deprecated This is not really needed. */ public final class GameObjectRemovedMessage extends AbstractMessage @@ -68,12 +69,11 @@ //---------------------------------------------------------------------- // UpdateMessage Implementation - - public void applyStateChangeToModel( final GameModel gameModel ) + public void applyStateChangeToModel( final GameObjectContext gameObjectContext ) { - ParameterChecker.checkNotNull( gameModel, "gameModel" ); + throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT - gameModel.removeGameObject( myRemovedObjectId ); } + } Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/property/PropertyAddedMessage.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/property/PropertyAddedMessage.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/property/PropertyAddedMessage.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,7 +1,7 @@ package org.skycastle.messaging.updates.property; +import org.skycastle.core.GameObjectContext; import org.skycastle.core.GameObjectId; -import org.skycastle.core.clientside.GameModel; import org.skycastle.messaging.updates.MemberUpdateMessage; import org.skycastle.util.ParameterChecker; import org.skycastle.util.parameters.ParameterMetadata; @@ -79,9 +79,10 @@ //---------------------------------------------------------------------- // UpdateMessage Implementation - public void applyStateChangeToModel( final GameModel gameModel ) + public void applyStateChangeToModel( final GameObjectContext gameObjectContext ) { throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT + } //---------------------------------------------------------------------- Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/property/PropertyChangedMessage.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/property/PropertyChangedMessage.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/property/PropertyChangedMessage.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,7 +1,7 @@ package org.skycastle.messaging.updates.property; +import org.skycastle.core.GameObjectContext; import org.skycastle.core.GameObjectId; -import org.skycastle.core.clientside.GameModel; import org.skycastle.messaging.updates.MemberUpdateMessage; import org.skycastle.util.parameters.ValidationError; @@ -32,7 +32,7 @@ //---------------------------------------------------------------------- // Constructors - public void applyStateChangeToModel( final GameModel gameModel ) + public void applyStateChangeToModel( final GameObjectContext gameObjectContext ) { throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/property/PropertyRemovedMessage.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/property/PropertyRemovedMessage.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/messaging/updates/property/PropertyRemovedMessage.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,8 +1,8 @@ package org.skycastle.messaging.updates.property; import org.skycastle.core.GameObject; +import org.skycastle.core.GameObjectContext; import org.skycastle.core.GameObjectId; -import org.skycastle.core.clientside.GameModel; import org.skycastle.messaging.updates.MemberUpdateMessage; /** @@ -34,9 +34,10 @@ super( updatedObjectId, propertyName ); } - public void applyStateChangeToModel( final GameModel gameModel ) + public void applyStateChangeToModel( final GameObjectContext gameObjectContext ) { throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT } + } Deleted: trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/AbstractProtocol.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/AbstractProtocol.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/AbstractProtocol.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,50 +0,0 @@ -package org.skycastle.protocol; - -import org.skycastle.util.ParameterChecker; - -/** - * Provides common functionality. - * - * @author Hans Haggstrom - */ -public abstract class AbstractProtocol - implements Protocol -{ - - //====================================================================== - // Private Fields - - private String myProtocolId; - - //====================================================================== - // Private Constants - - //====================================================================== - // Public Methods - - //---------------------------------------------------------------------- - // Protocol Implementation - - public final String getProtocolId() - { - return myProtocolId; - } - - //====================================================================== - // Protected Methods - - //---------------------------------------------------------------------- - // Protected Constructors - - /** - * @param protocolId an identifier for this protocol. Used for protocol negotiation. Should be Java identifier - * style (start with letter, no whitespace, etc). - */ - protected AbstractProtocol( final String protocolId ) - { - ParameterChecker.checkIsIdentifier( protocolId, "protocolId" ); - - myProtocolId = protocolId; - } - -} Deleted: trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/Protocol.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/Protocol.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/Protocol.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,41 +0,0 @@ -package org.skycastle.protocol; - -import org.skycastle.messaging.Message; - -/** - * A protocol used to communicate between server and client. - * <p/> - * The transport happens as byte array messages. - * <p/> - * The transfered data is messages with source and target game object, a message type, and a set of named - * parameters of primitive data structures and collections. - * - * @author Hans Haggstrom - */ -public interface Protocol -{ - /** - * @return a string ID used to identify this protocol during protocol negotiations. The ID should be a - * valid Java identifier (sart with letter, contain no spaces, etc). - */ - String getProtocolId(); - - /** - * @param messageToSend the message to encode to bytes for network transfer. - * - * @return the bytes representing the message to transfer - * - * @throws ProtocolException if there was some error when encoding the message. - */ - byte[] encode( Message messageToSend ) throws ProtocolException; - - /** - * @param receivedBytes an array of bytes received over the network. Note that malicious crackers may - * send any kind of messages, so the error handling should be robust. - * - * @return the message represented by the specified bytes. - * - * @throws ProtocolException if there was some error when decoding the received message. - */ - Message decode( byte[] receivedBytes ) throws ProtocolException; -} Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/AbstractProtocolNegotiator.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/AbstractProtocolNegotiator.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/AbstractProtocolNegotiator.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,5 +1,6 @@ package org.skycastle.protocol.negotiation; +import org.skycastle.core.ObjectReference; import org.skycastle.protocol.protocols.Protocol; import org.skycastle.protocol.registry.ProtocolRegistry; import org.skycastle.util.ParameterChecker; @@ -19,7 +20,7 @@ //====================================================================== // Private Fields - private final ProtocolRegistry myProtocolRegistry; + private final ObjectReference<ProtocolRegistry> myProtocolRegistryReference; private NegotiationStatus myStatus = NegotiationStatus.ONGOING; private Protocol myProtocol = null; @@ -34,10 +35,14 @@ protected static final String VERSION_KEY = "Version"; protected static final String CLIENT_KEY = "Client"; + @SuppressWarnings( { "HardcodedLineSeparator" } ) + protected static final String PROTOCOL_NEWLINE_CHARACTER = "\n"; + //====================================================================== // Private Constants private static final int MAX_MESSAGE_LENGTH = 2000; + private static final long serialVersionUID = 1L; //====================================================================== // Public Methods @@ -88,13 +93,13 @@ // Protected Constructors /** - * @param protocolRegistry a registry containing the available protocols. + * @param protocolRegistryReference a registry containing the available protocols. */ - protected AbstractProtocolNegotiator( final ProtocolRegistry protocolRegistry ) + protected AbstractProtocolNegotiator( final ObjectReference<ProtocolRegistry> protocolRegistryReference ) { - ParameterChecker.checkNotNull( protocolRegistry, "protocolRegistry" ); + ParameterChecker.checkNotNull( protocolRegistryReference, "protocolRegistryReference" ); - myProtocolRegistry = protocolRegistry; + myProtocolRegistryReference = protocolRegistryReference; } //---------------------------------------------------------------------- @@ -119,7 +124,7 @@ */ protected final Set<String> getAvailableProtocolsIds() { - return myProtocolRegistry.getAvailableProtocolIds(); + return myProtocolRegistryReference.getForReading().getAvailableProtocolIds(); } @@ -137,10 +142,13 @@ */ protected final void setProtocol( final String protocolId ) { - myProtocol = myProtocolRegistry.getProtocol( protocolId ); + myProtocol = myProtocolRegistryReference.getForReading().getProtocol( protocolId ); } + /** + * @return a space separated list of the string ID:s of the available protocols . + */ protected final String getAvailableProtocolIdsAsString() { final StringBuffer protocolIds = new StringBuffer(); @@ -158,11 +166,11 @@ private Map<String, String> splitIntoKeysAndValues( final String incomingString ) { - final Map<String, String> parameters = new HashMap<String, String>(); + final Map<String, String> parameters = new HashMap<String, String>( 10 ); if ( incomingString != null ) { - final String[] lines = incomingString.split( "\n" ); + final String[] lines = incomingString.split( PROTOCOL_NEWLINE_CHARACTER ); for ( final String line : lines ) { final int spaceIndex = line.indexOf( " " ); Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/ClientSideProtocolNegotiator.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/ClientSideProtocolNegotiator.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/ClientSideProtocolNegotiator.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,5 +1,6 @@ package org.skycastle.protocol.negotiation; +import org.skycastle.core.DirectReference; import org.skycastle.protocol.registry.ProtocolRegistry; import java.util.Map; @@ -24,6 +25,11 @@ private int myStep = 0; //====================================================================== + // Private Constants + + private static final long serialVersionUID = 1L; + + //====================================================================== // Public Methods //---------------------------------------------------------------------- @@ -38,7 +44,7 @@ final String clientType, final String version ) { - super( protocolRegistry ); + super( new DirectReference<ProtocolRegistry>( protocolRegistry ) ); myClientType = clientType; myVersion = version; } @@ -90,10 +96,10 @@ myServerType = parameters.get( SERVER_KEY ); myServerVersion = parameters.get( VERSION_KEY ); - return PROTOCOL_NEGOTIATION_START + "\n" + - CLIENT_KEY + " " + myClientType + "\n" + - VERSION_KEY + " " + myVersion + "\n" + - KNOWN_PROTOCOLS_KEY + " " + getAvailableProtocolIdsAsString() + "\n"; + return PROTOCOL_NEGOTIATION_START + PROTOCOL_NEWLINE_CHARACTER + + CLIENT_KEY + " " + myClientType + PROTOCOL_NEWLINE_CHARACTER + + VERSION_KEY + " " + myVersion + PROTOCOL_NEWLINE_CHARACTER + + KNOWN_PROTOCOLS_KEY + " " + getAvailableProtocolIdsAsString() + PROTOCOL_NEWLINE_CHARACTER; case 2: // Read protocol selected by server Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/NegotiationStatus.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/NegotiationStatus.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/NegotiationStatus.java 2008-04-05 19:04:43 UTC (rev 441) @@ -94,11 +94,11 @@ /** - * @return true if the negotiation succeeded, false if it failed or is still ongoing. + * @return true if the negotiation are finished and succeeded, false if it failed or is still ongoing. */ public boolean isSuccess() { - return mySuccess; + return isFinished() && mySuccess; } Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/ProtocolNegotiator.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/ProtocolNegotiator.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/ProtocolNegotiator.java 2008-04-05 19:04:43 UTC (rev 441) @@ -2,6 +2,8 @@ import org.skycastle.protocol.protocols.Protocol; +import java.io.Serializable; + /** * A state machine that can negotiate a protocol to use with another party connected over a network. * <p/> @@ -11,6 +13,7 @@ * @author Hans Haggstrom */ public interface ProtocolNegotiator + extends Serializable { /** * Does the next step in the negotiation. Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/ServerSideProtocolNegotiator.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/ServerSideProtocolNegotiator.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/negotiation/ServerSideProtocolNegotiator.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,5 +1,8 @@ package org.skycastle.protocol.negotiation; +import com.sun.sgs.app.ManagedObject; +import org.skycastle.core.ObjectReference; +import org.skycastle.protocol.protocols.Protocol; import org.skycastle.protocol.registry.ProtocolRegistry; import org.skycastle.util.ParameterChecker; @@ -29,19 +32,28 @@ private int myStep = 0; //====================================================================== + // Private Constants + + private static final long serialVersionUID = 1L; + + //====================================================================== // Public Methods //---------------------------------------------------------------------- // Constructors /** - * @param protocolRegistry a registry containing the available protocols. + * @param protocolRegistryReference a referenced to the {@link ProtocolRegistry} that contains available + * {@link Protocol}s. We use a reference as the {@link ProtocolRegistry} + * is stored as a separate {@link ManagedObject} on the server side. + * @param serverType a string identifying the server to the clients. + * @param serverVersion a version string identifying the server version to the clients. */ - public ServerSideProtocolNegotiator( final ProtocolRegistry protocolRegistry, + public ServerSideProtocolNegotiator( final ObjectReference<ProtocolRegistry> protocolRegistryReference, final String serverType, final String serverVersion ) { - super( protocolRegistry ); + super( protocolRegistryReference ); ParameterChecker.checkNotNull( serverType, "serverType" ); ParameterChecker.checkNotNull( serverVersion, "serverVersion" ); @@ -53,7 +65,6 @@ //---------------------------------------------------------------------- // ProtocolNegotiator Implementation - public boolean startsNegotiations() { return true; @@ -82,16 +93,17 @@ //====================================================================== // Protected Methods + @Override protected String handleMessage( final String incomingMessage, final Map<String, String> parameters ) { myStep++; switch ( myStep ) { case 1: // Take contact with client - return PROTOCOL_NEGOTIATION_START + "\n" + - SERVER_KEY + " " + myServerType + "\n" + - VERSION_KEY + " " + myServerVersion + "\n" + - KNOWN_PROTOCOLS_KEY + " " + getAvailableProtocolIdsAsString() + "\n"; + return PROTOCOL_NEGOTIATION_START + PROTOCOL_NEWLINE_CHARACTER + + SERVER_KEY + " " + myServerType + PROTOCOL_NEWLINE_CHARACTER + + VERSION_KEY + " " + myServerVersion + PROTOCOL_NEWLINE_CHARACTER + + KNOWN_PROTOCOLS_KEY + " " + getAvailableProtocolIdsAsString() + PROTOCOL_NEWLINE_CHARACTER; case 2: // Read introduction from client, send protocol selection @@ -116,7 +128,7 @@ setStatus( NegotiationStatus.NO_COMMON_PROTOCOL_FOUND ); } - return SELECTED_PROTOCOL_KEY + " " + selectedProtocol + "\n"; + return SELECTED_PROTOCOL_KEY + " " + selectedProtocol + PROTOCOL_NEWLINE_CHARACTER; default: // Too many steps @@ -133,9 +145,8 @@ if ( spaceSeparatedClientProtocols != null ) { final Set<String> clientKnownProtocols = new HashSet<String>( Arrays.asList( - spaceSeparatedClientProtocols.split( - " " ) ) ); - for ( String supportedProtocol : getAvailableProtocolsIds() ) + spaceSeparatedClientProtocols.split( " " ) ) ); + for ( final String supportedProtocol : getAvailableProtocolsIds() ) { if ( clientKnownProtocols.contains( supportedProtocol ) ) { Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/protocols/AbstractProtocol.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/protocols/AbstractProtocol.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/protocols/AbstractProtocol.java 2008-04-05 19:04:43 UTC (rev 441) @@ -8,7 +8,6 @@ import org.skycastle.util.parameters.validators.ParameterValidator; import org.skycastle.util.parameters.validators.type.TypeValidator; -import java.io.Serializable; import java.util.Collection; import java.util.Map; @@ -18,7 +17,7 @@ * @author Hans Haggstrom */ public abstract class AbstractProtocol - implements Protocol, Serializable + implements Protocol { //====================================================================== Modified: trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/protocols/Protocol.java =================================================================== --- trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/protocols/Protocol.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/main/java/org/skycastle/protocol/protocols/Protocol.java 2008-04-05 19:04:43 UTC (rev 441) @@ -3,6 +3,8 @@ import org.skycastle.messaging.Message; import org.skycastle.protocol.ProtocolException; +import java.io.Serializable; + /** * A protocol used to communicate between server and client. * <p/> @@ -14,6 +16,7 @@ * @author Hans Haggstrom */ public interface Protocol + extends Serializable { /** * @return a string ID used to identify this protocol during protocol negotiations. The ID should be a Modified: trunk/skycastle/modules/core/src/test/java/org/skycastle/core/AbstractGameObjectActionsTest.java =================================================================== --- trunk/skycastle/modules/core/src/test/java/org/skycastle/core/AbstractGameObjectActionsTest.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/test/java/org/skycastle/core/AbstractGameObjectActionsTest.java 2008-04-05 19:04:43 UTC (rev 441) @@ -136,7 +136,7 @@ myGameObject.addAction( testAction ); assertTrue( "Action should be added", myGameObject.hasAction( TEST_ACTION_NAME ) ); - myCallerId = new GameObjectId( "someSender" ); + myCallerId = new GameObjectId( "GameObject_someSender" ); } //====================================================================== @@ -202,7 +202,8 @@ private long calculateTimeUntilNextInvocation( final GameObject hostObject ) { long timeUntilNextCallback_ms = -1; - if ( hostObject.getPropertyValue( STEPS_CALLED, 0 ) < hostObject.getPropertyValue( NUMBER_OF_STEPS, 0 ) ) + if ( hostObject.getPropertyValue( STEPS_CALLED, + 0 ) < hostObject.getPropertyValue( NUMBER_OF_STEPS, 0 ) ) { timeUntilNextCallback_ms = hostObject.getPropertyValue( REPEAT_INTERVALL, -1 ); } Modified: trunk/skycastle/modules/core/src/test/java/org/skycastle/core/AbstractGameObjectPropertiesTest.java =================================================================== --- trunk/skycastle/modules/core/src/test/java/org/skycastle/core/AbstractGameObjectPropertiesTest.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/test/java/org/skycastle/core/AbstractGameObjectPropertiesTest.java 2008-04-05 19:04:43 UTC (rev 441) @@ -31,6 +31,7 @@ private GameObject myGameObject; private List<Message> mySentMessages; + private static final String SOME_OTHER_GAME_OBJECT_ID = "GameObject_SomeOtherObjectMaybePlayer"; //====================================================================== // Public Methods @@ -132,7 +133,7 @@ { assertEquals( 0, getNumberOfProperties() ); - myGameObject.onMessage( new AddPropertyMessage( new GameObjectId( "SomeOtherObjectMaybePlayer" ), + myGameObject.onMessage( new AddPropertyMessage( new GameObjectId( SOME_OTHER_GAME_OBJECT_ID ), myGameObject.getId(), "breakfast", "juice", @@ -141,7 +142,7 @@ assertEquals( "juice", myGameObject.getPropertyValue( "breakfast", null ) ); assertEquals( 1, getNumberOfProperties() ); - myGameObject.onMessage( new SetPropertyMessage( new GameObjectId( "SomeOtherObjectMaybePlayer" ), + myGameObject.onMessage( new SetPropertyMessage( new GameObjectId( SOME_OTHER_GAME_OBJECT_ID ), myGameObject.getId(), "breakfast", "sandwich" ) ); @@ -149,7 +150,7 @@ assertEquals( "sandwich", myGameObject.getPropertyValue( "breakfast", null ) ); assertEquals( 1, getNumberOfProperties() ); - myGameObject.onMessage( new RemovePropertyMessage( new GameObjectId( "SomeOtherObjectMaybePlayer" ), + myGameObject.onMessage( new RemovePropertyMessage( new GameObjectId( SOME_OTHER_GAME_OBJECT_ID ), myGameObject.getId(), "breakfast" ) ); Modified: trunk/skycastle/modules/core/src/test/java/org/skycastle/core/geometry/space/grid/GridTest.java =================================================================== --- trunk/skycastle/modules/core/src/test/java/org/skycastle/core/geometry/space/grid/GridTest.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/test/java/org/skycastle/core/geometry/space/grid/GridTest.java 2008-04-05 19:04:43 UTC (rev 441) @@ -49,19 +49,19 @@ public void testAddingAndAccessingGameObjects() throws Exception { assertObjectAccessWorks( true, 5.5f, 4.5f, 5, 4 ); - assertObjectAccessWorks( true, 5, 4, 5, 4 ); + assertObjectAccessWorks( true, 5.0f, 4.0f, 5, 4 ); assertObjectAccessWorks( true, 0.5f, 0.5f, 0, 0 ); assertObjectAccessWorks( true, 9.5f, 9.5f, 9, 9 ); assertObjectAccessWorks( false, 6.5f, 4.5f, 5, 4 ); assertObjectAccessWorks( false, 6.5f, 5.5f, 5, 4 ); - assertObjectAccessWorks( false, 6, 4.5f, 5, 4 ); + assertObjectAccessWorks( false, 6.0f, 4.5f, 5, 4 ); } public void testTileTypeSettingAndGetting() throws Exception { - assertEquals( null, myGridSpace.getTileType( 4, 5 ) ); + assertNull( myGridSpace.getTileType( 4, 5 ) ); myGridSpace.setTileType( 4, 5, TEST_TILE_TYPE ); @@ -93,7 +93,7 @@ myGridSpace.addObject( testObject, x, y, 0 ); - final List<GameObject> collectionBasket = new ArrayList<GameObject>(); + final List<GameObject> collectionBasket = new ArrayList<GameObject>( 10 ); myGridSpace.collectObjectsInTile( xGrid, yGrid, collectionBasket ); assertEquals( shouldContain, collectionBasket.contains( testObject ) ); Modified: trunk/skycastle/modules/core/src/test/java/org/skycastle/protocol/negotiation/ProtocolNegotiationTest.java =================================================================== --- trunk/skycastle/modules/core/src/test/java/org/skycastle/protocol/negotiation/ProtocolNegotiationTest.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/test/java/org/skycastle/protocol/negotiation/ProtocolNegotiationTest.java 2008-04-05 19:04:43 UTC (rev 441) @@ -5,11 +5,13 @@ */ import junit.framework.TestCase; +import org.skycastle.core.DirectReference; import org.skycastle.messaging.Message; import org.skycastle.protocol.MessageValidator; import org.skycastle.protocol.ProtocolException; import org.skycastle.protocol.protocols.AbstractProtocol; import org.skycastle.protocol.protocols.Protocol; +import org.skycastle.protocol.registry.ProtocolRegistry; import org.skycastle.protocol.registry.ProtocolRegistryImpl; import org.skycastle.util.StringUtilities; @@ -160,7 +162,8 @@ myClientSideNegotiator = new ClientSideProtocolNegotiator( myClientProtocolRegistry, CLIENT_TYPE, CLIENT_VERSION ); - myServerSideNegotiator = new ServerSideProtocolNegotiator( myServerProtocolRegistry, + myServerSideNegotiator = new ServerSideProtocolNegotiator( new DirectReference<ProtocolRegistry>( + myServerProtocolRegistry ), "Test Server", "0.1" ); } Modified: trunk/skycastle/modules/core/src/test/java/org/skycastle/protocol/protocols/SerializationProtocolTest.java =================================================================== --- trunk/skycastle/modules/core/src/test/java/org/skycastle/protocol/protocols/SerializationProtocolTest.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/core/src/test/java/org/skycastle/protocol/protocols/SerializationProtocolTest.java 2008-04-05 19:04:43 UTC (rev 441) @@ -35,7 +35,8 @@ public void testNonAllowedType() throws Exception { - final PropertyChangedMessage sentMessage = new PropertyChangedMessage( new GameObjectId( "foo" ), + final PropertyChangedMessage sentMessage = new PropertyChangedMessage( new GameObjectId( + "GameObject_foo" ), "TakeThisIfYouCan", new StringBuffer( "Evil infiltrator buffer" ) ); @@ -71,7 +72,7 @@ someMoreProps.setParameter( "ShouldBeRecursive", true ); parameterSet.setObjectParameter( "PropertiesOfFavouriteProperty", someMoreProps ); - final PropertyChangedMessage message = new PropertyChangedMessage( new GameObjectId( "foo" ), + final PropertyChangedMessage message = new PropertyChangedMessage( new GameObjectId( "GameObject_foo" ), "bar", parameterSet ); 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-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/server/src/main/java/org/skycastle/server/SkycastleClientSessionHandler.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,13 +1,17 @@ package org.skycastle.server; -import com.sun.sgs.app.*; -import org.skycastle.core.DefaultGameObject; -import org.skycastle.core.GameObject; +import com.sun.sgs.app.AppContext; +import com.sun.sgs.app.ClientSession; +import com.sun.sgs.app.ClientSessionListener; +import com.sun.sgs.app.DataManager; +import org.skycastle.core.*; import org.skycastle.messaging.Message; import org.skycastle.messaging.modifications.ModificationMessage; +import org.skycastle.messaging.updates.UpdateMessage; import org.skycastle.protocol.ProtocolException; import org.skycastle.protocol.negotiation.ProtocolNegotiator; import org.skycastle.protocol.negotiation.ServerSideProtocolNegotiator; +import org.skycastle.protocol.registry.ProtocolRegistry; import java.io.Serializable; import java.util.logging.Level; @@ -26,36 +30,33 @@ /** * The session this {@code ClientSessionListener} is listening to. */ - private final ClientSession mySession; + // NOTE: The ClientSession interface doesn't extend Serializable, but the implementation does, + // so we can store it in a Serializable class. + @SuppressWarnings( { "NonSerializableFieldInSerializableClass" } ) + private final ClientSession myClientSession; + private final ProtocolNegotiator myProtocolNegotiator; - private ManagedReference myProtocolRegistryReferenceReference; - private ManagedReference myClientAccountReference; + // NOTE: For some reason the ManagedReference interface is not serializable, but the implementation is, + // so we supress a serialization warning for IDE:s that look for those problems. + @SuppressWarnings( { "NonSerializableFieldInSerializableClass" } ) - //====================================================================== - // Non-Private Fields + private GameObjectReference myClientAccountReference; - /** - * The server listener. This is used for querying connected users etc. - */ - protected ManagedReference serverListener; - //====================================================================== // Private Constants /** - * The version of the serialized form of this class. + * The {@link Logger} for this class. */ - private static final long serialVersionUID = 1L; + private static final Logger LOGGER = Logger.getLogger( SkycastleClientSessionHandler.class.getName() ); - /** - * The {@link Logger} for this class. - */ - private static final Logger LOGGER = - Logger.getLogger( SkycastleClientSessionHandler.class.getName() ); private static final String SERVER_TYPE = "SkycastleServer"; private static final String SERVER_VERSION = "0.1"; + private static final long serialVersionUID = 1L; + private static final String ACCOUNT_PREFIX = "account_"; + //====================================================================== // Public Methods @@ -66,49 +67,24 @@ * Creates a new {@code HelloChannelsSessionListener} for the given session, and joins it to the given * channels. * - * @param clientSession the session this listener is associated with - * @param protocolRegistryReferenceReference - * + * @param clientSession the session this listener is associated with + * @param protocolRegistryReference a reference to the {@link ProtocolRegistry} that contains the + * available protocols. */ - // TODO: Implement our own generic version of ManagedReference that just wraps a ManagedReference - public SkycastleClientSessionHandler( ClientSession clientSession, - SkycastleServer initServerListener, - final ManagedReference protocolRegistryReferenceReference ) + public SkycastleClientSessionHandler( final ClientSession clientSession, + final PersistentReference<ProtocolRegistry> protocolRegistryReference ) { - mySession = clientSession; - myProtocolRegistryReferenceReference = protocolRegistryReferenceReference; + myClientSession = clientSession; - final DataManager dataManager = AppContext.getDataManager(); - serverListener = dataManager.createReference( initServerListener ); - - // TODO: We need to pass in a reference instead of a concrete class, because we store the protocol negotiator in this object.... - myProtocolNegotiator = new ServerSideProtocolNegotiator( myProtocolRegistryReferenceReference, + myProtocolNegotiator = new ServerSideProtocolNegotiator( protocolRegistryReference, SERVER_TYPE, SERVER_VERSION ); - + // If the server should start protocol negotiations, have it send the opening message if ( myProtocolNegotiator.startsNegotiations() ) { - final byte[] firstMessage = myProtocolNegotiator.handleMessage( null ); - clientSession.send( firstMessage ); + clientSession.send( myProtocolNegotiator.handleMessage( null ) ); } - - // Get users account. The account will typically contain actions for resuming some existing avatars - // or creating new avatars, and doing general account management and maybe out-of-game chat. - - // NOTE: The client session should already be authenticated, so just use the username to find the account - GameObject clientAccount = dataManager.getBinding( "account_" + clientSession.getName(), - GameObject.class ); - - // If client doesn't yet have any account, create a new one - if ( clientAccount == null ) - { - clientAccount = new DefaultGameObject(); - } - - myClientAccountReference = dataManager.createReference( clientAccount ); - - // TODO: Notify the users account that the user connected } //---------------------------------------------------------------------- @@ -119,44 +95,29 @@ * <p/> * Logs when data arrives from the client, and echoes the message back. */ - public void receivedMessage( final byte[] message ) + public void receivedMessage( final byte[] byteMessage ) { if ( myProtocolNegotiator.getStatus().isFinished() ) { - try + if ( myProtocolNegotiator.getStatus().isSuccess() ) { - // Decode incoming message - final Message decodedMessage = myProtocolNegotiator.getProtocol().decode( message ); - - // TODO: Set the messages sender and such so that the client is not claiming to be someone else... - - // TODO: Should we validate the message here also? - //decodedMessage.validate( ) - - // Send the incoming message to the clients account for processing - final GameObject clientAvatar = myClientAccountReference.get( GameObject.class ); - clientAvatar.onMessage( decodedMessage ); - - // TODO: Or should we send it directly to the target game object?? - // If the message is a ModificationMessage it has a target. - // The client should anyway send only modification messages, update messages don't make that much sense. - ModificationMessage modificationMessage = (ModificationMessage) decodedMessage; - // TODO: Get game object for specific gameObjectId, using e.g. the GameContext - we could build this functionality into the GameObjectId now! Maybe even support generics? - GameObject target = modificationMessage.getTargetId().getGameObjectForModification(); - target.onMessage( modificationMessage ); + handleMessage( byteMessage ); } - catch ( ProtocolException e ) - { - // TODO: Log exception with client somehow? Enable fast detection of errorneous or flooding DoS:in clients - e.printStackTrace(); - } } else { // Continue protocol negotiation - final byte[] reply = myProtocolNegotiator.handleMessage( message ); + final byte[] reply = myProtocolNegotiator.handleMessage( byteMessage ); - mySession.send( reply ); + if ( reply != null ) + { + myClientSession.send( reply ); + } + + if ( myProtocolNegotiator.getStatus().isFinished() ) + { + onProtocolNegotiationFinished( myProtocolNegotiator.getStatus().isSuccess() ); + } } } @@ -166,7 +127,7 @@ * <p/> * Logs when the client disconnects. */ - public void disconnected( boolean graceful ) + public void disconnected( final boolean graceful ) { // TODO: Notify the users account that the user disconnected (the avatars can go into off-line mode, etc). @@ -174,8 +135,113 @@ final String grace = graceful ? "graceful" : "forced"; LOGGER.log( Level.INFO, "User {0} has logged out {1}", - new Object[]{ mySession.getName(), grace } + new Object[]{ myClientSession.getName(), grace } ); } + //====================================================================== + // Private Methods + + private void handleMessage( final byte[] byteMessage ) + { + try + { + // Decode incoming message + final Message message = myProtocolNegotiator.getProtocol().decode( byteMessage ); + + // TODO: Set the messages sender and such so that the client is not claiming to be someone else... + + + if ( message instanceof ModificationMessage ) + { + handleModificationMessage( message, (ModificationMessage) message ); + } + else if ( message instanceof UpdateMessage ) + { + throw new ProtocolException( + "The client can not send UpdateMessages to the server. Recieved a message of type: '" + message.getClass() + "'." ); + } + else + { + throw new ProtocolException( "Unknown message type '" + message.getClass() + "'." ); + } + } + catch ( ProtocolException e ) + { + LOGGER.warning( "Problem when decoding message from a client: " + e.getMessage() ); + + // TODO: Log exception with client/account somehow? Enable fast detection of errorneous or flooding DoS:in clients + e.printStackTrace(); + } + } + + + private void handleModificationMessage( final Message message, + final ModificationMessage modificationMessage ) + { + final GameObjectId targetId = modificationMessage.getTargetId(); + final GameObject target = GameContext.getGameObjectContext().getGameObjectById( + targetId, + true ); + + target.onMessage( message ); + } + + + private void onProtocolNegotiationFinished( final boolean success ) + { + if ( success ) + { + // If the protocol negotiation finished successfully, get the users account + final GameObject account = getUserAccount( myClientSession.getName() ); + + // TODO: Notify the account object that the user logged in. + + myClientAccountReference = new GameObjectReference( account ); + } + else + { + // If negotiation failed with error, disconnect + myClientSession.disconnect(); + } + } + + + /** + * Get users account. The account will typically contain actions for resuming some existing avatars or + * creating new avatars, and doing general account management and maybe out-of-game chat. + * <p/> + * The client session should already be authenticated, so just use the username to find the account + * <p/> + * If the specified userLoginName does not yet have an account associated with it, a new one is created. + * + * @param userLoginName the login name of the user. It should already have been authenticated and + * verified. + * + * @return a {@link GameObject} with the specified users account object. + */ + private GameObject getUserAccount( final String userLoginName ) + { + final DataManager dataManager = AppContext.getDataManager(); + + final String bindingName = ACCOUNT_PREFIX + userLoginName; + + GameObject clientAccount = dataManager.getBinding( bindingName, GameObject.class ); + if ( clientAccount == null ) + { + // If client doesn't yet have any account, create a new one + clientAccount = new DefaultGameObject(); + // 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 + // and account, and a list of available games and the users avatars in them. + // 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 ); + } + + return clientAccount; + } + } \ No newline at end of file Modified: trunk/skycastle/modules/server/src/main/java/org/skycastle/server/SkycastleServer.java =================================================================== --- trunk/skycastle/modules/server/src/main/java/org/skycastle/server/SkycastleServer.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/server/src/main/java/org/skycastle/server/SkycastleServer.java 2008-04-05 19:04:43 UTC (rev 441) @@ -1,6 +1,10 @@ package org.skycastle.server; -import com.sun.sgs.app.*; +import com.sun.sgs.app.AppListener; +import com.sun.sgs.app.ClientSession; +import com.sun.sgs.app.ClientSessionListener; +import org.skycastle.core.PersistentReference; +import org.skycastle.protocol.registry.ProtocolRegistry; import org.skycastle.protocol.registry.ProtocolRegistryImpl; import java.io.Serializable; @@ -19,15 +23,20 @@ { //====================================================================== + // Private Fields + + private PersistentReference<ProtocolRegistry> myProtocolRegistryReference; + + //====================================================================== // Private Constants /** * The {@link Logger} for this class. */ - private static final Logger LOGGER = - Logger.getLogger( SkycastleServer.class.getName() ); - private ManagedReference myProtocolRegistryReferenceReference; + private static final Logger LOGGER = Logger.getLogger( SkycastleServer.class.getName() ); + private static final long serialVersionUID = 1L; + //====================================================================== // Public Methods @@ -37,7 +46,7 @@ public void initialize( final Properties properties ) { // Create protocol registry - myProtocolRegistryReferenceReference = AppContext.getDataManager().createReference( new ProtocolRegistryImpl() ); + myProtocolRegistryReference = new PersistentReference<ProtocolRegistry>( new ProtocolRegistryImpl() ); } @@ -46,7 +55,7 @@ // DEBUG: LOGGER.log( Level.INFO, "User {0} has logged in", clientSession.getName() ); - return new SkycastleClientSessionHandler( clientSession, this, myProtocolRegistryReferenceReference ); + return new SkycastleClientSessionHandler( clientSession, myProtocolRegistryReference ); } } Modified: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/issue/IssueImpl.java =================================================================== --- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/issue/IssueImpl.java 2008-04-05 14:11:05 UTC (rev 440) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/issue/IssueImpl.java 2008-04-05 19:04:43 UTC (rev 441) @@ -98,7 +98,7 @@ */ public String toString() { - return "Report{ severity: " + mySeverity + + return "Issue{ severity: " + mySeverity + ", type: " + myType + ", summary: " + mySummary + ", cause: " + myCause + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.