Updates to EZGame to allow games to store 'user cookies'.

Then intention is that other games besides EZGame may use this as well,
so it's semi-separated, but for now it's part of EZGame.


git-svn-id: svn+ssh://src.earth.threerings.net/vilya/trunk@123 c613c5cb-e716-0410-b11b-feb51c14d237
This commit is contained in:
Ray Greenwell
2006-11-15 03:01:49 +00:00
parent a4ae0dd2dc
commit cff4238b3e
19 changed files with 857 additions and 13 deletions
@@ -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);
}
@@ -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 ()
{
@@ -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)
@@ -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 <code>turnHolder</code> field. */
public static final String TURN_HOLDER = "turnHolder";
/** The field name of the <code>userCookies</code> field. */
public static final String USER_COOKIES = "userCookies";
/** The field name of the <code>ezGameService</code> 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<UserCookie> 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
* <code>userCookies</code> 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 <code>userCookies</code> 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
* <code>userCookies</code> 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 <code>userCookies</code> 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<com.threerings.ezgame.data.UserCookie> value)
{
requestAttributeChange(USER_COOKIES, value, this.userCookies);
@SuppressWarnings("unchecked") DSet<com.threerings.ezgame.data.UserCookie> clone =
(value == null) ? null : value.typedClone();
this.userCookies = clone;
}
/**
* Requests that the <code>ezGameService</code> field be set to the
* specified value. The local value will be updated immediately and an
@@ -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;
}
}
@@ -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,
@@ -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<byte[]>() {
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<UserCookie>());
}
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<String, ArrayList<byte[]>> _collections;
/** The map of tickers, lazy-initialized. */
protected HashMap<String, Ticker> _tickers;
/** Tracks which cookies are currently being retrieved from the db. */
protected ArrayIntSet _cookieLookups;
// /** User tokens, lazy-initialized. */
// protected HashIntMap<HashSet<String>> _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;
@@ -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.
*/
@@ -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<byte[]> rl)
{
final int userId = _identifier.getUserId(cliObj);
if (userId == 0) {
rl.requestCompleted(null);
return;
}
CrowdServer.invoker.postUnit(
new RepositoryListenerUnit<byte[]>("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;
}
@@ -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");
}
@@ -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<byte[]>() {
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<Void>() {
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);
}
}
}
});
}
}