diff --git a/src/as/com/threerings/ezgame/EZGame.as b/src/as/com/threerings/ezgame/EZGame.as index 8a557be8..56b0e074 100644 --- a/src/as/com/threerings/ezgame/EZGame.as +++ b/src/as/com/threerings/ezgame/EZGame.as @@ -178,5 +178,42 @@ public interface EZGame * End the game. The specified player indexes are winners! */ function endGame (winnerIndex :int, ... rest) :void; + +// function getCurrentRoom () :int; +// +// function sendPlayerToRoom (playerIndex :int, room :int) :void + + /** + * Get the user-specific game data for the specified user. The + * first time this is requested per game instance it will be retrieved + * from the database. After that, it will be returned from memory. + */ + function getUserCookie (playerIndex :int, callback :Function) :void; + + /** + * Store persistent data that can later be retrieved by an instance + * of this game. The maximum size of this data is 4096 bytes AFTER + * AMF3 encoding. + * + * Note: there is no playerIndex parameter because a cookie may only + * be stored for the current player. + * + * @return false if the cookie could not be encoded to 4096 bytes + * or less; true if the cookie is going to try to be saved. There is + * no guarantee it will be saved and no way to find out if it failed, + * but if it fails it will be because the shit hit the fan so hard that + * there's nothing you can do anyway. + */ + function setUserCookie (cookie :Object) :Boolean; +// +// /** +// * Check to see if the user has the specified token. +// */ +// function checkUserToken (token :String, callback :Function) :void; +// +// /** +// * Take the user to a purchase page. +// */ +// function purchaseUserToken (token :String, callback :Function) :void; } } diff --git a/src/as/com/threerings/ezgame/client/EZGameService.as b/src/as/com/threerings/ezgame/client/EZGameService.as index d8cac4ed..bdb5996f 100644 --- a/src/as/com/threerings/ezgame/client/EZGameService.as +++ b/src/as/com/threerings/ezgame/client/EZGameService.as @@ -21,6 +21,7 @@ package com.threerings.ezgame.client { +import flash.utils.ByteArray; import com.threerings.ezgame.client.EZGameService; import com.threerings.presents.client.Client; import com.threerings.presents.client.InvocationService; @@ -42,6 +43,9 @@ public interface EZGameService extends InvocationService // from Java interface EZGameService function endTurn (arg1 :Client, arg2 :int, arg3 :InvocationService_InvocationListener) :void; + // from Java interface EZGameService + function getCookie (arg1 :Client, arg2 :int, arg3 :InvocationService_InvocationListener) :void; + // from Java interface EZGameService function getFromCollection (arg1 :Client, arg2 :String, arg3 :Boolean, arg4 :int, arg5 :String, arg6 :int, arg7 :InvocationService_ConfirmListener) :void; @@ -51,6 +55,9 @@ public interface EZGameService extends InvocationService // from Java interface EZGameService function sendMessage (arg1 :Client, arg2 :String, arg3 :Object, arg4 :int, arg5 :InvocationService_InvocationListener) :void; + // from Java interface EZGameService + function setCookie (arg1 :Client, arg2 :ByteArray, arg3 :InvocationService_InvocationListener) :void; + // from Java interface EZGameService function setProperty (arg1 :Client, arg2 :String, arg3 :Object, arg4 :int, arg5 :InvocationService_InvocationListener) :void; diff --git a/src/as/com/threerings/ezgame/client/GameObjectImpl.as b/src/as/com/threerings/ezgame/client/GameObjectImpl.as index 64686510..f9707edd 100644 --- a/src/as/com/threerings/ezgame/client/GameObjectImpl.as +++ b/src/as/com/threerings/ezgame/client/GameObjectImpl.as @@ -7,6 +7,7 @@ import flash.events.EventDispatcher; import flash.utils.IExternalizable; import flash.utils.ByteArray; +import flash.utils.Dictionary; import com.threerings.io.TypedArray; @@ -18,11 +19,16 @@ import com.threerings.util.StringUtil; import com.threerings.presents.client.ConfirmAdapter; import com.threerings.presents.client.InvocationService_ConfirmListener; +import com.threerings.presents.dobj.EntryAddedEvent; +import com.threerings.presents.dobj.EntryUpdatedEvent; +import com.threerings.presents.dobj.SetAdapter; + import com.threerings.crowd.data.BodyObject; import com.threerings.crowd.util.CrowdContext; import com.threerings.ezgame.data.EZGameObject; import com.threerings.ezgame.data.PropertySetEvent; +import com.threerings.ezgame.data.UserCookie; import com.threerings.ezgame.util.EZObjectMarshaller; import com.threerings.ezgame.EZGame; @@ -41,6 +47,8 @@ public class GameObjectImpl extends EventDispatcher _ctx = ctx; _ezObj = ezObj; _gameData = new GameData(this, _ezObj.getUserProps()); + + _ezObj.addListener(new SetAdapter(entryAdded, entryUpdated, null)); } // from EZGame @@ -259,6 +267,52 @@ public class GameObjectImpl extends EventDispatcher return arr; } + // from EZGame + public function getUserCookie (playerIndex :int, callback :Function) :void + { + // see if that cookie is already published + if (_ezObj.userCookies != null) { + var uc :UserCookie = + (_ezObj.userCookies.get(playerIndex) as UserCookie); + if (uc != null) { + callback(uc.cookie); + return; + } + } + + if (_cookieCallbacks == null) { + _cookieCallbacks = new Dictionary(); + } + var arr :Array = (_cookieCallbacks[playerIndex] as Array); + if (arr == null) { + arr = []; + _cookieCallbacks[playerIndex] = arr; + } + arr.push(callback); + + // request it to be made so by the server + _ezObj.ezGameService.getCookie(_ctx.getClient(), playerIndex, + createLoggingListener("getUserCookie")); + } + + // from EZGame + public function setUserCookie (cookie :Object) :Boolean + { + var ba :ByteArray = + (EZObjectMarshaller.encode(cookie, false) as ByteArray); + if (ba.length > MAX_USER_COOKIE) { + // not saved! + return false; + } + + _ezObj.ezGameService.setCookie(_ctx.getClient(), null, // ba, + // TODO + /// + /// TODO + createLoggingListener("setUserCookie")); + return true; + } + // from EZGame public function isMyTurn () :Boolean { @@ -480,10 +534,55 @@ public class GameObjectImpl extends EventDispatcher } } + /** + * Handle entry updated + */ + private function entryAdded (event :EntryAddedEvent) :void + { + if (EZGameObject.USER_COOKIES == event.getName()) { + receivedUserCookie(event.getEntry() as UserCookie); + } + } + + /** + * Handle entry updated + */ + private function entryUpdated (event :EntryUpdatedEvent) :void + { + if (EZGameObject.USER_COOKIES == event.getName()) { + receivedUserCookie(event.getEntry() as UserCookie); + } + } + + /** + * Handle the arrival of a new UserCookie. + */ + private function receivedUserCookie (cookie :UserCookie) :void + { + if (_cookieCallbacks != null) { + var arr :Array = (_cookieCallbacks[cookie.playerIndex] as Array); + if (arr != null) { + delete _cookieCallbacks[cookie.playerIndex]; + for each (var fn :Function in arr) { + try { + fn(cookie.cookie); + } catch (err :Error) { + // cope + } + } + } + } + } + protected var _ctx :CrowdContext; protected var _ezObj :EZGameObject; protected var _gameData :GameData; + + /** playerIndex -> callback functions waiting for the cookie. */ + protected var _cookieCallbacks :Dictionary; + + protected static const MAX_USER_COOKIE :int = 4096; } } diff --git a/src/as/com/threerings/ezgame/data/EZGameConfig.as b/src/as/com/threerings/ezgame/data/EZGameConfig.as index fe450388..f8a1a44f 100644 --- a/src/as/com/threerings/ezgame/data/EZGameConfig.as +++ b/src/as/com/threerings/ezgame/data/EZGameConfig.as @@ -29,6 +29,10 @@ public class EZGameConfig extends GameConfig // For now, the configData is either a classname or url. public var configData :String; + /** If non-zero, a game id used to persistently identify the game. + * This could be thought of as a new-style rating id. */ + public var persistentGameId:int; + public function EZGameConfig () { // nothing needed @@ -89,6 +93,7 @@ public class EZGameConfig extends GameConfig super.readObject(ins); configData = (ins.readField(String) as String); + persistentGameId = ins.readInt(); } // from interface Streamable @@ -97,6 +102,7 @@ public class EZGameConfig extends GameConfig super.writeObject(out); out.writeField(configData); + out.writeInt(persistentGameId); } } } diff --git a/src/as/com/threerings/ezgame/data/EZGameMarshaller.as b/src/as/com/threerings/ezgame/data/EZGameMarshaller.as index 71ee6b7d..1fcb1023 100644 --- a/src/as/com/threerings/ezgame/data/EZGameMarshaller.as +++ b/src/as/com/threerings/ezgame/data/EZGameMarshaller.as @@ -21,6 +21,7 @@ package com.threerings.ezgame.data { +import flash.utils.ByteArray; import com.threerings.util.*; // for Float, Integer, etc. import com.threerings.ezgame.client.EZGameService; @@ -80,8 +81,21 @@ public class EZGameMarshaller extends InvocationMarshaller ]); } + /** The method id used to dispatch {@link #getCookie} requests. */ + public static const GET_COOKIE :int = 4; + + // from interface EZGameService + public function getCookie (arg1 :Client, arg2 :int, arg3 :InvocationService_InvocationListener) :void + { + var listener3 :InvocationMarshaller_ListenerMarshaller = new InvocationMarshaller_ListenerMarshaller(); + listener3.listener = arg3; + sendRequest(arg1, GET_COOKIE, [ + Integer.valueOf(arg2), listener3 + ]); + } + /** The method id used to dispatch {@link #getFromCollection} requests. */ - public static const GET_FROM_COLLECTION :int = 4; + public static const GET_FROM_COLLECTION :int = 5; // from interface EZGameService public function getFromCollection (arg1 :Client, arg2 :String, arg3 :Boolean, arg4 :int, arg5 :String, arg6 :int, arg7 :InvocationService_ConfirmListener) :void @@ -94,7 +108,7 @@ public class EZGameMarshaller extends InvocationMarshaller } /** The method id used to dispatch {@link #mergeCollection} requests. */ - public static const MERGE_COLLECTION :int = 5; + public static const MERGE_COLLECTION :int = 6; // from interface EZGameService public function mergeCollection (arg1 :Client, arg2 :String, arg3 :String, arg4 :InvocationService_InvocationListener) :void @@ -107,7 +121,7 @@ public class EZGameMarshaller extends InvocationMarshaller } /** The method id used to dispatch {@link #sendMessage} requests. */ - public static const SEND_MESSAGE :int = 6; + public static const SEND_MESSAGE :int = 7; // from interface EZGameService public function sendMessage (arg1 :Client, arg2 :String, arg3 :Object, arg4 :int, arg5 :InvocationService_InvocationListener) :void @@ -119,8 +133,21 @@ public class EZGameMarshaller extends InvocationMarshaller ]); } + /** The method id used to dispatch {@link #setCookie} requests. */ + public static const SET_COOKIE :int = 8; + + // from interface EZGameService + public function setCookie (arg1 :Client, arg2 :ByteArray, arg3 :InvocationService_InvocationListener) :void + { + var listener3 :InvocationMarshaller_ListenerMarshaller = new InvocationMarshaller_ListenerMarshaller(); + listener3.listener = arg3; + sendRequest(arg1, SET_COOKIE, [ + arg2, listener3 + ]); + } + /** The method id used to dispatch {@link #setProperty} requests. */ - public static const SET_PROPERTY :int = 7; + public static const SET_PROPERTY :int = 9; // from interface EZGameService public function setProperty (arg1 :Client, arg2 :String, arg3 :Object, arg4 :int, arg5 :InvocationService_InvocationListener) :void @@ -133,7 +160,7 @@ public class EZGameMarshaller extends InvocationMarshaller } /** The method id used to dispatch {@link #setTicker} requests. */ - public static const SET_TICKER :int = 8; + public static const SET_TICKER :int = 10; // from interface EZGameService public function setTicker (arg1 :Client, arg2 :String, arg3 :int, arg4 :InvocationService_InvocationListener) :void diff --git a/src/as/com/threerings/ezgame/data/EZGameObject.as b/src/as/com/threerings/ezgame/data/EZGameObject.as index e22994c7..77b0710c 100644 --- a/src/as/com/threerings/ezgame/data/EZGameObject.as +++ b/src/as/com/threerings/ezgame/data/EZGameObject.as @@ -10,6 +10,8 @@ import com.threerings.io.ObjectInputStream; import com.threerings.io.ObjectOutputStream; import com.threerings.io.TypedArray; +import com.threerings.presents.dobj.DSet; + import com.threerings.parlor.game.data.GameObject; import com.threerings.parlor.turn.data.TurnGameObject; @@ -31,6 +33,9 @@ public class EZGameObject extends GameObject /** The field name of the turnHolder field. */ public static const TURN_HOLDER :String = "turnHolder"; + /** The field name of the userCookies field. */ + public static const USER_COOKIES :String = "userCookies"; + /** The field name of the ezGameService field. */ public static const EZ_GAME_SERVICE :String = "ezGameService"; // AUTO-GENERATED: FIELDS END @@ -38,6 +43,9 @@ public class EZGameObject extends GameObject /** The current turn holder. */ public var turnHolder :Name; + /** A set of loaded user cookies. */ + public var userCookies :DSet; + /** The service interface for requesting special things from the server. */ public var ezGameService :EZGameMarshaller; @@ -134,6 +142,7 @@ public class EZGameObject extends GameObject // first read any regular bits turnHolder = (ins.readObject() as Name); + userCookies = (ins.readObject() as DSet); ezGameService = (ins.readObject() as EZGameMarshaller); // then user properties diff --git a/src/as/com/threerings/ezgame/data/UserCookie.as b/src/as/com/threerings/ezgame/data/UserCookie.as new file mode 100644 index 00000000..a7a7a574 --- /dev/null +++ b/src/as/com/threerings/ezgame/data/UserCookie.as @@ -0,0 +1,44 @@ +// +// $Id$ + +package com.threerings.ezgame.data { + +import flash.utils.ByteArray; + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; + +import com.threerings.presents.dobj.DSet_Entry; + +import com.threerings.ezgame.util.EZObjectMarshaller; + +public class UserCookie + implements DSet_Entry +{ + /** The index of the player that has this cookie. */ + public var playerIndex :int; + + /** The decoded cookie value. */ + public var cookie :Object; + + // from DSet_Entry + public function getKey () :Object + { + return playerIndex; + } + + // from superinterface Streamable + public function readObject (ins :ObjectInputStream) :void + { + playerIndex = ins.readInt(); + var ba :ByteArray = (ins.readObject() as ByteArray); + cookie = EZObjectMarshaller.decode(ba); + } + + // from superinterface Streamable + public function writeObject (out :ObjectOutputStream) :void + { + throw new Error(); + } +} +} diff --git a/src/as/com/threerings/ezgame/util/EZObjectMarshaller.as b/src/as/com/threerings/ezgame/util/EZObjectMarshaller.as index 326fccbc..518b4cb1 100644 --- a/src/as/com/threerings/ezgame/util/EZObjectMarshaller.as +++ b/src/as/com/threerings/ezgame/util/EZObjectMarshaller.as @@ -7,6 +7,8 @@ import flash.system.ApplicationDomain; import flash.utils.ByteArray; import flash.utils.Endian; +import com.threerings.util.StringUtil; + import com.threerings.io.TypedArray; /** @@ -45,6 +47,8 @@ public class EZObjectMarshaller bytes.endian = Endian.BIG_ENDIAN; bytes.objectEncoding = ObjectEncoding.AMF3; bytes.writeObject(obj); + Log.getLog(EZObjectMarshaller).info( + "The encoded bytes are: " + StringUtil.hexlate(bytes)); return bytes; } diff --git a/src/java/com/threerings/ezgame/client/EZGameService.java b/src/java/com/threerings/ezgame/client/EZGameService.java index d85a4e62..5c8ef239 100644 --- a/src/java/com/threerings/ezgame/client/EZGameService.java +++ b/src/java/com/threerings/ezgame/client/EZGameService.java @@ -83,4 +83,16 @@ public interface EZGameService extends InvocationService public void setTicker ( Client client, String tickerName, int msOfDelay, InvocationListener listener); + + /** + * Request to get the specified user's cookie. + */ + public void getCookie ( + Client client, int playerIndex, InvocationListener listener); + + /** + * Request to set our cookie. + */ + public void setCookie ( + Client client, byte[] cookie, InvocationListener listener); } diff --git a/src/java/com/threerings/ezgame/data/EZGameConfig.java b/src/java/com/threerings/ezgame/data/EZGameConfig.java index a40bfa0b..759248ee 100644 --- a/src/java/com/threerings/ezgame/data/EZGameConfig.java +++ b/src/java/com/threerings/ezgame/data/EZGameConfig.java @@ -24,6 +24,10 @@ public class EZGameConfig extends GameConfig // For now, the configData is either a classname or url. public String configData; + /** If non-zero, a game id used to persistently identify the game. + * This could be thought of as a new-style rating id. */ + public int persistentGameId; + // from abstract GameConfig public String getBundleName () { diff --git a/src/java/com/threerings/ezgame/data/EZGameMarshaller.java b/src/java/com/threerings/ezgame/data/EZGameMarshaller.java index c9146f9a..36550d63 100644 --- a/src/java/com/threerings/ezgame/data/EZGameMarshaller.java +++ b/src/java/com/threerings/ezgame/data/EZGameMarshaller.java @@ -76,8 +76,21 @@ public class EZGameMarshaller extends InvocationMarshaller }); } + /** The method id used to dispatch {@link #getCookie} requests. */ + public static final int GET_COOKIE = 4; + + // from interface EZGameService + public void getCookie (Client arg1, int arg2, InvocationService.InvocationListener arg3) + { + ListenerMarshaller listener3 = new ListenerMarshaller(); + listener3.listener = arg3; + sendRequest(arg1, GET_COOKIE, new Object[] { + Integer.valueOf(arg2), listener3 + }); + } + /** The method id used to dispatch {@link #getFromCollection} requests. */ - public static final int GET_FROM_COLLECTION = 4; + public static final int GET_FROM_COLLECTION = 5; // from interface EZGameService public void getFromCollection (Client arg1, String arg2, boolean arg3, int arg4, String arg5, int arg6, InvocationService.ConfirmListener arg7) @@ -90,7 +103,7 @@ public class EZGameMarshaller extends InvocationMarshaller } /** The method id used to dispatch {@link #mergeCollection} requests. */ - public static final int MERGE_COLLECTION = 5; + public static final int MERGE_COLLECTION = 6; // from interface EZGameService public void mergeCollection (Client arg1, String arg2, String arg3, InvocationService.InvocationListener arg4) @@ -103,7 +116,7 @@ public class EZGameMarshaller extends InvocationMarshaller } /** The method id used to dispatch {@link #sendMessage} requests. */ - public static final int SEND_MESSAGE = 6; + public static final int SEND_MESSAGE = 7; // from interface EZGameService public void sendMessage (Client arg1, String arg2, Object arg3, int arg4, InvocationService.InvocationListener arg5) @@ -115,8 +128,21 @@ public class EZGameMarshaller extends InvocationMarshaller }); } + /** The method id used to dispatch {@link #setCookie} requests. */ + public static final int SET_COOKIE = 8; + + // from interface EZGameService + public void setCookie (Client arg1, byte[] arg2, InvocationService.InvocationListener arg3) + { + ListenerMarshaller listener3 = new ListenerMarshaller(); + listener3.listener = arg3; + sendRequest(arg1, SET_COOKIE, new Object[] { + arg2, listener3 + }); + } + /** The method id used to dispatch {@link #setProperty} requests. */ - public static final int SET_PROPERTY = 7; + public static final int SET_PROPERTY = 9; // from interface EZGameService public void setProperty (Client arg1, String arg2, Object arg3, int arg4, InvocationService.InvocationListener arg5) @@ -129,7 +155,7 @@ public class EZGameMarshaller extends InvocationMarshaller } /** The method id used to dispatch {@link #setTicker} requests. */ - public static final int SET_TICKER = 8; + public static final int SET_TICKER = 10; // from interface EZGameService public void setTicker (Client arg1, String arg2, int arg3, InvocationService.InvocationListener arg4) diff --git a/src/java/com/threerings/ezgame/data/EZGameObject.java b/src/java/com/threerings/ezgame/data/EZGameObject.java index 9d5e8ec1..f379e82b 100644 --- a/src/java/com/threerings/ezgame/data/EZGameObject.java +++ b/src/java/com/threerings/ezgame/data/EZGameObject.java @@ -14,6 +14,8 @@ import com.threerings.io.ObjectInputStream; import com.threerings.io.ObjectOutputStream; import com.threerings.io.Streamable; +import com.threerings.presents.dobj.DSet; + import com.threerings.parlor.game.data.GameObject; import com.threerings.parlor.turn.data.TurnGameObject; @@ -38,6 +40,9 @@ public class EZGameObject extends GameObject /** The field name of the turnHolder field. */ public static final String TURN_HOLDER = "turnHolder"; + /** The field name of the userCookies field. */ + public static final String USER_COOKIES = "userCookies"; + /** The field name of the ezGameService field. */ public static final String EZ_GAME_SERVICE = "ezGameService"; // AUTO-GENERATED: FIELDS END @@ -45,6 +50,9 @@ public class EZGameObject extends GameObject /** The current turn holder. */ public Name turnHolder; + /** A set of loaded user cookies. */ + public DSet userCookies; + /** The service interface for requesting special things from the server. */ public EZGameMarshaller ezGameService; @@ -139,6 +147,54 @@ public class EZGameObject extends GameObject this.turnHolder = value; } + /** + * Requests that the specified entry be added to the + * userCookies set. The set will not change until the event is + * actually propagated through the system. + */ + public void addToUserCookies (UserCookie elem) + { + requestEntryAdd(USER_COOKIES, userCookies, elem); + } + + /** + * Requests that the entry matching the supplied key be removed from + * the userCookies set. The set will not change until the + * event is actually propagated through the system. + */ + public void removeFromUserCookies (Comparable key) + { + requestEntryRemove(USER_COOKIES, userCookies, key); + } + + /** + * Requests that the specified entry be updated in the + * userCookies set. The set will not change until the event is + * actually propagated through the system. + */ + public void updateUserCookies (UserCookie elem) + { + requestEntryUpdate(USER_COOKIES, userCookies, elem); + } + + /** + * Requests that the userCookies field be set to the + * specified value. Generally one only adds, updates and removes + * entries of a distributed set, but certain situations call for a + * complete replacement of the set value. The local value will be + * updated immediately and an event will be propagated through the + * system to notify all listeners that the attribute did + * change. Proxied copies of this object (on clients) will apply the + * value change when they received the attribute changed notification. + */ + public void setUserCookies (DSet value) + { + requestAttributeChange(USER_COOKIES, value, this.userCookies); + @SuppressWarnings("unchecked") DSet clone = + (value == null) ? null : value.typedClone(); + this.userCookies = clone; + } + /** * Requests that the ezGameService field be set to the * specified value. The local value will be updated immediately and an diff --git a/src/java/com/threerings/ezgame/data/UserCookie.java b/src/java/com/threerings/ezgame/data/UserCookie.java new file mode 100644 index 00000000..920c9c23 --- /dev/null +++ b/src/java/com/threerings/ezgame/data/UserCookie.java @@ -0,0 +1,51 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.ezgame.data; + +import com.threerings.presents.dobj.DSet; + +/** + * Represents a user's game-specific cookie data. + */ +public class UserCookie + implements DSet.Entry +{ + /** The index of the player that has this cookie. */ + public int playerIndex; + + /** The cookie value. */ + public byte[] cookie; + + /** + */ + public UserCookie (int playerIndex, byte[] cookie) + { + this.playerIndex = playerIndex; + this.cookie = cookie; + } + + // from DSet.Entry + public Comparable getKey () + { + return playerIndex; + } +} diff --git a/src/java/com/threerings/ezgame/server/EZGameDispatcher.java b/src/java/com/threerings/ezgame/server/EZGameDispatcher.java index 63406994..155851fb 100644 --- a/src/java/com/threerings/ezgame/server/EZGameDispatcher.java +++ b/src/java/com/threerings/ezgame/server/EZGameDispatcher.java @@ -77,6 +77,13 @@ public class EZGameDispatcher extends InvocationDispatcher ); return; + case EZGameMarshaller.GET_COOKIE: + ((EZGameProvider)provider).getCookie( + source, + ((Integer)args[0]).intValue(), (InvocationService.InvocationListener)args[1] + ); + return; + case EZGameMarshaller.GET_FROM_COLLECTION: ((EZGameProvider)provider).getFromCollection( source, @@ -98,6 +105,13 @@ public class EZGameDispatcher extends InvocationDispatcher ); return; + case EZGameMarshaller.SET_COOKIE: + ((EZGameProvider)provider).setCookie( + source, + (byte[])args[0], (InvocationService.InvocationListener)args[1] + ); + return; + case EZGameMarshaller.SET_PROPERTY: ((EZGameProvider)provider).setProperty( source, diff --git a/src/java/com/threerings/ezgame/server/EZGameManager.java b/src/java/com/threerings/ezgame/server/EZGameManager.java index 51e875c1..60d5f1c6 100644 --- a/src/java/com/threerings/ezgame/server/EZGameManager.java +++ b/src/java/com/threerings/ezgame/server/EZGameManager.java @@ -5,11 +5,15 @@ package com.threerings.ezgame.server; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; +import com.samskivert.util.ArrayIntSet; import com.samskivert.util.ArrayUtil; import com.samskivert.util.CollectionUtil; +import com.samskivert.util.HashIntMap; import com.samskivert.util.Interval; import com.samskivert.util.RandomUtil; +import com.samskivert.util.ResultListener; import com.threerings.util.Name; @@ -17,6 +21,7 @@ import com.threerings.presents.data.ClientObject; import com.threerings.presents.data.InvocationCodes; import com.threerings.presents.dobj.AccessController; import com.threerings.presents.dobj.DObjectManager; +import com.threerings.presents.dobj.DSet; import com.threerings.presents.dobj.MessageEvent; import com.threerings.presents.client.InvocationService; import com.threerings.presents.server.InvocationException; @@ -30,9 +35,13 @@ import com.threerings.parlor.game.server.GameManager; import com.threerings.parlor.turn.server.TurnGameManager; +import com.threerings.ezgame.data.EZGameConfig; import com.threerings.ezgame.data.EZGameObject; import com.threerings.ezgame.data.EZGameMarshaller; import com.threerings.ezgame.data.PropertySetEvent; +import com.threerings.ezgame.data.UserCookie; + +import static com.threerings.ezgame.server.Log.log; /** * A manager for "ez" games. @@ -238,6 +247,90 @@ public class EZGameManager extends GameManager } } + // from EZGameProvider + public void getCookie ( + ClientObject caller, final int playerIndex, + InvocationService.InvocationListener listener) + throws InvocationException + { + GameCookieManager gcm = getCookieManager(); + if (_gameObj.userCookies.containsKey(playerIndex)) { + // already loaded: we do nothing + return; + } + + if (_cookieLookups == null) { + _cookieLookups = new ArrayIntSet(); + } + // we only start looking up the cookie if nobody else already is + if (!_cookieLookups.contains(playerIndex)) { + gcm.getCookie(getPersistentGameId(), getPlayer(playerIndex), + new ResultListener() { + public void requestCompleted (byte[] result) { + // Result may be null: that's ok, it means + // we've looked up the user's nonexistant cookie. + // Only set the cookie if the playerIndex is + // still in the lookup set, otherwise they left! + if (_cookieLookups.remove(playerIndex) && + _gameObj.isActive()) { + _gameObj.addToUserCookies( + new UserCookie(playerIndex, result)); + } + } + + public void requestFailed (Exception cause) { + log.warning("Unable to retrieve cookie " + + "[cause=" + cause + "]."); + requestCompleted(null); + } + }); + + // indicate that we're looking up a cookie + _cookieLookups.add(playerIndex); + } + } + + // from EZGameProvider + public void setCookie ( + ClientObject caller, byte[] value, + InvocationService.InvocationListener listener) + throws InvocationException + { + int playerIndex = getPresentPlayerIndex(caller.getOid()); + if (playerIndex == -1) { + throw new InvocationException(ACCESS_DENIED); + } + + GameCookieManager gcm = getCookieManager(); + UserCookie cookie = new UserCookie(playerIndex, value); + if (_gameObj.userCookies.containsKey(playerIndex)) { + _gameObj.updateUserCookies(cookie); + } else { + _gameObj.addToUserCookies(cookie); + } + + gcm.setCookie(getPersistentGameId(), caller, value); + } + + /** + * Get the cookie manager, and do a bit of other setup. + */ + protected GameCookieManager getCookieManager () + throws InvocationException + { + GameCookieManager gcm = GameCookieManager.getInstance(); + if (gcm == null) { + log.warning("GameCookieManager not initialized."); + throw new InvocationException(INTERNAL_ERROR); + } + + if (_gameObj.userCookies == null) { + // lazy-init this + _gameObj.setUserCookies(new DSet()); + } + return gcm; + } + /** * Helper method to send a private message to the specified player * index (must already be verified). @@ -266,6 +359,19 @@ public class EZGameManager extends GameManager new PropertySetEvent(_gameObj.getOid(), propName, value, index)); } + /** + * Get the game id of this ezgame, as set in the config. + */ + protected int getPersistentGameId () + throws InvocationException + { + int id = ((EZGameConfig) _config).persistentGameId; + if (id == 0) { + throw new InvocationException("Persistent game id not set."); + } + return id; + } + /** * Validate that the specified user has access to do things in the game. */ @@ -328,6 +434,22 @@ public class EZGameManager extends GameManager super.gameDidEnd(); } + @Override + protected void playerGameDidEnd (int pidx) + { + super.playerGameDidEnd(pidx); + + // kill any of their cookies + if (_gameObj.userCookies != null && + _gameObj.userCookies.containsKey(pidx)) { + _gameObj.removeFromUserCookies(pidx); + } + // halt the loading of their cookie, if in progress + if (_cookieLookups != null) { + _cookieLookups.remove(pidx); + } + } + @Override protected void assignWinners (boolean[] winners) { @@ -411,15 +533,21 @@ public class EZGameManager extends GameManager /** Our turn delegate. */ protected EZGameTurnDelegate _turnDelegate; - /** The array of winners, after the user has filled it in. */ - protected int[] _winnerIndexes; - /** The map of collections, lazy-initialized. */ protected HashMap> _collections; /** The map of tickers, lazy-initialized. */ protected HashMap _tickers; + /** Tracks which cookies are currently being retrieved from the db. */ + protected ArrayIntSet _cookieLookups; + +// /** User tokens, lazy-initialized. */ +// protected HashIntMap> _tokens; + + /** The array of winners, after the user has filled it in. */ + protected int[] _winnerIndexes; + /** The minimum delay a ticker can have. */ protected static final int MIN_TICKER_DELAY = 50; diff --git a/src/java/com/threerings/ezgame/server/EZGameProvider.java b/src/java/com/threerings/ezgame/server/EZGameProvider.java index da2e932b..94e2ef83 100644 --- a/src/java/com/threerings/ezgame/server/EZGameProvider.java +++ b/src/java/com/threerings/ezgame/server/EZGameProvider.java @@ -51,6 +51,12 @@ public interface EZGameProvider extends InvocationProvider public void endTurn (ClientObject caller, int arg1, InvocationService.InvocationListener arg2) throws InvocationException; + /** + * Handles a {@link EZGameService#getCookie} request. + */ + public void getCookie (ClientObject caller, int arg1, InvocationService.InvocationListener arg2) + throws InvocationException; + /** * Handles a {@link EZGameService#getFromCollection} request. */ @@ -69,6 +75,12 @@ public interface EZGameProvider extends InvocationProvider public void sendMessage (ClientObject caller, String arg1, Object arg2, int arg3, InvocationService.InvocationListener arg4) throws InvocationException; + /** + * Handles a {@link EZGameService#setCookie} request. + */ + public void setCookie (ClientObject caller, byte[] arg1, InvocationService.InvocationListener arg2) + throws InvocationException; + /** * Handles a {@link EZGameService#setProperty} request. */ diff --git a/src/java/com/threerings/ezgame/server/GameCookieManager.java b/src/java/com/threerings/ezgame/server/GameCookieManager.java new file mode 100644 index 00000000..1524d740 --- /dev/null +++ b/src/java/com/threerings/ezgame/server/GameCookieManager.java @@ -0,0 +1,142 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.ezgame.server; + +import com.samskivert.io.PersistenceException; + +import com.samskivert.jdbc.ConnectionProvider; +import com.samskivert.jdbc.RepositoryListenerUnit; + +import com.samskivert.util.Invoker; +import com.samskivert.util.ResultListener; + +import com.threerings.presents.data.ClientObject; + +import com.threerings.crowd.server.CrowdServer; + +import com.threerings.ezgame.server.persist.GameCookieRepository; + +import static com.threerings.ezgame.server.Log.log; + +/** + * Manages access to game cookies. + */ +public class GameCookieManager +{ + /** + * An interface for identifying users. + */ + public interface UserIdentifier + { + /** Return the persistent user id for the specified player, + * or 0 if they're not a valid user, or a guest, + * or something like that (they'll have no cookies). */ + public int getUserId (ClientObject clientObj); + } + + /** + * Called to set up game cookie services for the server. + */ + public static void init (ConnectionProvider conprov, UserIdentifier ider) + throws PersistenceException + { + _singleton = new GameCookieManager(conprov, ider); + } + + /** + * Get an instance of the GameCookieManager. + */ + public static GameCookieManager getInstance () + { + return _singleton; + } + + /** + * Protected constructor. + */ + protected GameCookieManager + (ConnectionProvider conprov, UserIdentifier identifier) + throws PersistenceException + { + _repo = new GameCookieRepository(conprov); + _identifier = identifier; + if (_identifier == null) { + throw new IllegalArgumentException( + "UserIdentifier must be non-null"); + } + } + + /** + * Get the specified user's cookie. + */ + public void getCookie ( + final int gameId, ClientObject cliObj, ResultListener rl) + { + final int userId = _identifier.getUserId(cliObj); + if (userId == 0) { + rl.requestCompleted(null); + return; + } + CrowdServer.invoker.postUnit( + new RepositoryListenerUnit("getGameCookie", rl) { + public byte[] invokePersistResult () + throws PersistenceException + { + return _repo.getCookie(gameId, userId); + } + }); + } + + /** + * Set the specified user's cookie. + */ + public void setCookie ( + final int gameId, ClientObject cliObj, final byte[] cookie) + { + final int userId = _identifier.getUserId(cliObj); + if (userId == 0) { + // fail to save, silently + return; + } + CrowdServer.invoker.postUnit(new Invoker.Unit("setGameCookie") { + public boolean invoke () + { + try { + _repo.setCookie(gameId, userId, cookie); + } catch (PersistenceException pe) { + log.warning("Unable to save game cookie [pe=" + pe + "]."); + } + return false; + } + }); + } + + /** Our repository. */ + protected GameCookieRepository _repo; + + /** The entity we ask to identify users. */ + protected UserIdentifier _identifier; + + /** A reference to the single GameCookieManager instantiated. */ + protected static GameCookieManager _singleton; + +} diff --git a/src/java/com/threerings/ezgame/server/Log.java b/src/java/com/threerings/ezgame/server/Log.java new file mode 100644 index 00000000..05247bfb --- /dev/null +++ b/src/java/com/threerings/ezgame/server/Log.java @@ -0,0 +1,33 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.ezgame.server; + +import java.util.logging.Logger; + +/** + * Contains the log object. + */ +public class Log +{ + /** We dispatch our log messages through this logger. */ + public static Logger log = Logger.getLogger("com.threerings.ezgame"); +} diff --git a/src/java/com/threerings/ezgame/server/persist/GameCookieRepository.java b/src/java/com/threerings/ezgame/server/persist/GameCookieRepository.java new file mode 100644 index 00000000..fdbaefc1 --- /dev/null +++ b/src/java/com/threerings/ezgame/server/persist/GameCookieRepository.java @@ -0,0 +1,133 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.ezgame.server.persist; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import com.samskivert.io.PersistenceException; + +import com.samskivert.jdbc.ConnectionProvider; +import com.samskivert.jdbc.DatabaseLiaison; +import com.samskivert.jdbc.JDBCUtil; +import com.samskivert.jdbc.JORARepository; +import com.samskivert.jdbc.SimpleRepository; +import com.samskivert.jdbc.TransitionRepository; + + +/** + * Provides storage services for user cookies used in games. + */ +public class GameCookieRepository extends SimpleRepository +{ + /** The database identifier used when establishing a connection. */ + public static final String COOKIE_DB_IDENT = "gameCookiedb"; + + public GameCookieRepository (ConnectionProvider conprov) + throws PersistenceException + { + super(conprov, COOKIE_DB_IDENT); + + maintenance("analyze", "GAME_COOKIES"); + } + + @Override + protected void migrateSchema (Connection conn, DatabaseLiaison liaison) + throws SQLException, PersistenceException + { + super.migrateSchema(conn, liaison); + + JDBCUtil.createTableIfMissing(conn, "GAME_COOKIES", new String[] { + "GAME_ID integer not null", + "USER_ID integer not null", + "COOKIE blob not null", + "primary key (GAME_ID, USER_ID)" }, ""); + } + + /** + * Get the specified game cookie, or null if none. + */ + public byte[] getCookie (final int gameId, final int userId) + throws PersistenceException + { + return execute(new Operation() { + public byte[] invoke (Connection conn, DatabaseLiaison liaison) + throws SQLException, PersistenceException + { + Statement stmt = conn.createStatement(); + try { + ResultSet rs = stmt.executeQuery("select COOKIE " + + "from GAME_COOKIES where GAME_ID=" + gameId + + " and USER_ID=" + userId); + if (rs.next()) { + return rs.getBytes(1); + } + return null; + } finally { + JDBCUtil.close(stmt); + } + } + }); + } + + /** + * Set the specified user's game cookie. + */ + public void setCookie ( + final int gameId, final int userId, final byte[] cookie) + throws PersistenceException + { + executeUpdate(new Operation() { + public Void invoke (Connection conn, DatabaseLiaison liaison) + throws SQLException, PersistenceException + { + if (cookie == null) { + Statement stmt = conn.createStatement(); + try { + stmt.executeUpdate("delete from GAME_COOKIES" + + " where GAME_ID=" + gameId + + " and USER_ID=" + userId); + return null; + } finally { + JDBCUtil.close(stmt); + } + + } else { + PreparedStatement stmt = conn.prepareStatement( + "update GAME_COOKIES set COOKIE=?" + + " where GAME_ID=" + gameId + + " and USER_ID=" + userId); + try { + stmt.setBytes(1, cookie); + stmt.executeUpdate(); + return null; + } finally { + JDBCUtil.close(stmt); + } + } + } + }); + } +}