From 2d5d1c56a8e3588be9fd1ba997995b4fb6d92393 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Thu, 13 Feb 2003 21:55:22 +0000 Subject: [PATCH] Small changes to the cluster services: cluster information is published in the scene object; clusters are centered within their group of occupants; a player must start a cluster with another player rather than by themselves. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@2278 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../whirled/spot/client/SpotService.java | 12 +- .../threerings/whirled/spot/data/Cluster.java | 38 ++++++ .../whirled/spot/data/SpotSceneObject.dobj | 5 +- .../whirled/spot/data/SpotSceneObject.java | 54 +++++++- .../whirled/spot/server/SpotSceneManager.java | 129 ++++++++++++++---- 5 files changed, 204 insertions(+), 34 deletions(-) create mode 100644 src/java/com/threerings/whirled/spot/data/Cluster.java diff --git a/src/java/com/threerings/whirled/spot/client/SpotService.java b/src/java/com/threerings/whirled/spot/client/SpotService.java index 17ba1698e..d647bda56 100644 --- a/src/java/com/threerings/whirled/spot/client/SpotService.java +++ b/src/java/com/threerings/whirled/spot/client/SpotService.java @@ -1,5 +1,5 @@ // -// $Id: SpotService.java,v 1.12 2003/02/12 07:23:31 mdb Exp $ +// $Id: SpotService.java,v 1.13 2003/02/13 21:55:22 mdb Exp $ package com.threerings.whirled.spot.client; @@ -44,11 +44,11 @@ public interface SpotService extends InvocationService * location. * * @param loc the location to which to move. - * @param cluster if zero, a new cluster will be created and assigned - * to the calling user; if -1, the calling user will be removed from - * any cluster they currently occupy and not made to occupy a new - * cluster; if the bodyOid of another user, the calling user will be - * made to join the target user's cluster. + * @param cluster if -1, the calling user will be removed from any + * cluster they currently occupy and not made to occupy a new cluster; + * if the bodyOid of another user, the calling user will be made to + * join the target user's cluster, or create a cluster with the target + * user if they are not already in one. */ public void changeLoc (Client client, Location loc, int cluster, ChangeLocListener listener); diff --git a/src/java/com/threerings/whirled/spot/data/Cluster.java b/src/java/com/threerings/whirled/spot/data/Cluster.java new file mode 100644 index 000000000..6bc07cab0 --- /dev/null +++ b/src/java/com/threerings/whirled/spot/data/Cluster.java @@ -0,0 +1,38 @@ +// +// $Id: Cluster.java,v 1.1 2003/02/13 21:55:22 mdb Exp $ + +package com.threerings.whirled.spot.data; + +import com.threerings.presents.dobj.DSet; + +/** + * Contains information on clusters. + */ +public class Cluster + implements DSet.Entry +{ + /** A unique identifier for this cluster (also the distributed object + * id of the cluster chat object). */ + public int clusterOid; + + /** The x-coordinate of the cluster in the scene. */ + public int x; + + /** The y-coordinate of the cluster in the scene. */ + public int y; + + /** The number of occupants in this cluster. */ + public int occupants; + + // documentation inherited + public Comparable getKey () + { + if (_key == null) { + _key = new Integer(clusterOid); + } + return _key; + } + + /** Used for {@link #geyKey}. */ + protected transient Integer _key; +} diff --git a/src/java/com/threerings/whirled/spot/data/SpotSceneObject.dobj b/src/java/com/threerings/whirled/spot/data/SpotSceneObject.dobj index 637d2fbc6..374a3e3f5 100644 --- a/src/java/com/threerings/whirled/spot/data/SpotSceneObject.dobj +++ b/src/java/com/threerings/whirled/spot/data/SpotSceneObject.dobj @@ -1,5 +1,5 @@ // -// $Id: SpotSceneObject.dobj,v 1.1 2003/02/12 07:23:31 mdb Exp $ +// $Id: SpotSceneObject.dobj,v 1.2 2003/02/13 21:55:22 mdb Exp $ package com.threerings.whirled.spot.data; @@ -14,4 +14,7 @@ public class SpotSceneObject extends SceneObject /** A distributed set containing {@link SceneLocation} records for all * occupants of this scene. */ public DSet occupantLocs = new DSet(); + + /** Contains information on all {@link Cluster}s in this scene. */ + public DSet clusters = new DSet(); } diff --git a/src/java/com/threerings/whirled/spot/data/SpotSceneObject.java b/src/java/com/threerings/whirled/spot/data/SpotSceneObject.java index 81a5f4e9e..c5247a18f 100644 --- a/src/java/com/threerings/whirled/spot/data/SpotSceneObject.java +++ b/src/java/com/threerings/whirled/spot/data/SpotSceneObject.java @@ -1,5 +1,5 @@ // -// $Id: SpotSceneObject.java,v 1.1 2003/02/12 07:23:31 mdb Exp $ +// $Id: SpotSceneObject.java,v 1.2 2003/02/13 21:55:22 mdb Exp $ package com.threerings.whirled.spot.data; @@ -14,10 +14,16 @@ public class SpotSceneObject extends SceneObject /** The field name of the occupantLocs field. */ public static final String OCCUPANT_LOCS = "occupantLocs"; + /** The field name of the clusters field. */ + public static final String CLUSTERS = "clusters"; + /** A distributed set containing {@link SceneLocation} records for all * occupants of this scene. */ public DSet occupantLocs = new DSet(); + /** Contains information on all {@link Cluster}s in this scene. */ + public DSet clusters = new DSet(); + /** * Requests that the specified entry be added to the * occupantLocs set. The set will not change until the event is @@ -63,4 +69,50 @@ public class SpotSceneObject extends SceneObject this.occupantLocs = occupantLocs; requestAttributeChange(OCCUPANT_LOCS, occupantLocs); } + + /** + * Requests that the specified entry be added to the + * clusters set. The set will not change until the event is + * actually propagated through the system. + */ + public void addToClusters (DSet.Entry elem) + { + requestEntryAdd(CLUSTERS, elem); + } + + /** + * Requests that the entry matching the supplied key be removed from + * the clusters set. The set will not change until the + * event is actually propagated through the system. + */ + public void removeFromClusters (Object key) + { + requestEntryRemove(CLUSTERS, key); + } + + /** + * Requests that the specified entry be updated in the + * clusters set. The set will not change until the event is + * actually propagated through the system. + */ + public void updateClusters (DSet.Entry elem) + { + requestEntryUpdate(CLUSTERS, elem); + } + + /** + * Requests that the clusters 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 setClusters (DSet clusters) + { + this.clusters = clusters; + requestAttributeChange(CLUSTERS, clusters); + } } diff --git a/src/java/com/threerings/whirled/spot/server/SpotSceneManager.java b/src/java/com/threerings/whirled/spot/server/SpotSceneManager.java index d81f8a60a..039de5e3a 100644 --- a/src/java/com/threerings/whirled/spot/server/SpotSceneManager.java +++ b/src/java/com/threerings/whirled/spot/server/SpotSceneManager.java @@ -1,8 +1,9 @@ // -// $Id: SpotSceneManager.java,v 1.25 2003/02/12 07:23:31 mdb Exp $ +// $Id: SpotSceneManager.java,v 1.26 2003/02/13 21:55:22 mdb Exp $ package com.threerings.whirled.spot.server; +import java.awt.Point; import java.util.Iterator; import com.samskivert.util.ArrayIntSet; @@ -22,6 +23,7 @@ import com.threerings.crowd.server.CrowdServer; import com.threerings.whirled.server.SceneManager; import com.threerings.whirled.spot.Log; +import com.threerings.whirled.spot.data.Cluster; import com.threerings.whirled.spot.data.ClusterObject; import com.threerings.whirled.spot.data.ClusteredBodyObject; import com.threerings.whirled.spot.data.Location; @@ -171,6 +173,8 @@ public class SpotSceneManager extends SceneManager throw new InvocationException(INTERNAL_ERROR); } + // TODO: make sure the location isn't too close to another user + // update the user's location information in the scene which will // indicate to the client that their avatar should be moved from // its current position to their new position @@ -186,16 +190,10 @@ public class SpotSceneManager extends SceneManager } // handle the cluster situation - switch (cluster) { - case 0: - createNewCluster(source); - break; - case -1: + if (cluster == -1) { removeFromCluster(source.getOid()); - break; - default: - addToCluster(cluster, source); - break; + } else { + createOrJoinCluster(cluster, source); } } @@ -219,25 +217,38 @@ public class SpotSceneManager extends SceneManager /** * Adds the specified user to the cluster occupied by the specified - * other user. + * other user or creates a new cluster for the two users if neither is + * already clustered. */ - protected void addToCluster (int memberOid, BodyObject joiner) + protected void createOrJoinCluster (int memberOid, BodyObject joiner) { ClusterRecord clrec = (ClusterRecord)_clusters.get(memberOid); - if (clrec == null) { - // this isn't completely impossible as the last user might - // leave a cluster just as another user is entering it, but - // before the entering user's client was informed that the - // cluster went away - Log.info("Can't add user to non-existent cluster; " + - "creating a new one just for them " + - "[memberOid=" + memberOid + - ", joiner=" + joiner.who() + "]."); - clrec = new ClusterRecord(); + if (clrec != null) { + // if the cluster already exists, add this user and be done + if (clrec.addBody(joiner)) { + _clusters.put(joiner.getOid(), clrec); + } + return; } + + // otherwise we have to create a new cluster and add our two + // charter members! + clrec = new ClusterRecord(); + BodyObject member = (BodyObject)CrowdServer.omgr.getObject(memberOid); + if (member == null) { + Log.warning("Can't create cluster, missing target " + + "[creator=" + joiner.who() + + ", targetOid=" + memberOid + "]."); + return; + } + + // add our two lovely users to the newly created cluster if (clrec.addBody(joiner)) { _clusters.put(joiner.getOid(), clrec); } + if (clrec.addBody(member)) { + _clusters.put(member.getOid(), clrec); + } } /** @@ -269,11 +280,39 @@ public class SpotSceneManager extends SceneManager } } + /** + * Converts the x and y coordinates in the supplied location object to + * Cartesian coordinates that can be manipulated geometrically. The + * default implementation assumes the location coordinates are + * Cartesian, but systems that use different coordinate systems will + * want to override this method and perform the appropriate + * conversions. + */ + protected void locationToCoords (int lx, int ly, Point coords) + { + coords.x = lx; + coords.y = ly; + } + + /** + * Converts the supplied x and y coordinates (obtained from a prior + * call to {@link #locationToCoords}) to location coordinates that can + * be sent back to the client. The default implementation assumes the + * location coordinates are Cartesian, but systems that use different + * coordinate systems will want to override this method and perform + * the appropriate conversions. + */ + protected void coordsToLocation (int cx, int cy, Point loc) + { + loc.x = cx; + loc.y = cy; + } + /** * Used to manage clusters which are groups of users that can chat to * one another. */ - protected static class ClusterRecord extends HashIntMap + protected class ClusterRecord extends HashIntMap implements Subscriber { public ClusterRecord () @@ -284,11 +323,14 @@ public class SpotSceneManager extends SceneManager public boolean addBody (BodyObject body) { if (body instanceof ClusteredBodyObject) { + put(body.getOid(), body); + _cluster.occupants++; + recomputeCenter(); if (_clobj != null) { + _ssobj.updateClusters(_cluster); + _clobj.addToOccupants(body.getOid()); ((ClusteredBodyObject)body).setClusterOid(_clobj.getOid()); } - put(body.getOid(), body); - _clobj.addToOccupants(body.getOid()); return true; } else { @@ -308,7 +350,12 @@ public class SpotSceneManager extends SceneManager ", size=" + size() + ", who=" + bodyOid + "]."); } else { body.setClusterOid(-1); - _clobj.removeFromOccupants(bodyOid); + _cluster.occupants--; + recomputeCenter(); + if (_clobj != null) { + _clobj.removeFromOccupants(bodyOid); + _ssobj.updateClusters(_cluster); + } } // if we've removed our last body; stick a fork in ourselves @@ -327,11 +374,16 @@ public class SpotSceneManager extends SceneManager // keep this feller around _clobj = (ClusterObject)object; + // configure our cluster record and publish it + _cluster.clusterOid = _clobj.getOid(); + _ssobj.addToClusters(_cluster); + // let any mapped users know about our cluster Iterator iter = values().iterator(); while (iter.hasNext()) { ClusteredBodyObject body = (ClusteredBodyObject)iter.next(); body.setClusterOid(_clobj.getOid()); + _clobj.addToOccupants(((BodyObject)body).getOid()); } // if we didn't manage to add our creating user when we first @@ -354,6 +406,29 @@ public class SpotSceneManager extends SceneManager } } + protected void recomputeCenter () + { + int tx = 0, ty = 0, count = 0; + + // compute the average x and y position of all our cluster + // occupants + Iterator iter = _ssobj.occupantLocs.entries(); + while (iter.hasNext()) { + SceneLocation sloc = (SceneLocation)iter.next(); + if (containsKey(sloc.bodyOid)) { + locationToCoords(sloc.x, sloc.y, _scratch); + tx += _scratch.x; + ty += _scratch.x; + count++; + } + } + + // convert the center back to "location" coordinates + coordsToLocation(tx/count, ty/count, _scratch); + _cluster.x = _scratch.x; + _cluster.y = _scratch.y; + } + protected void destroy () { Log.info("Cluster empty, going away " + @@ -362,6 +437,8 @@ public class SpotSceneManager extends SceneManager } protected ClusterObject _clobj; + protected Cluster _cluster = new Cluster(); + protected Point _scratch = new Point(); } /** A casted reference to our place object. */