Conflicts:
	src/org/apollo/game/model/World.java
	src/org/apollo/game/model/area/Region.java
This commit is contained in:
atomicint
2015-04-10 15:43:02 -04:00
2 changed files with 260 additions and 238 deletions
+2 -2
View File
@@ -385,12 +385,12 @@ public final class World {
} }
/** /**
* Adds entities to regions in the {@link RegionRepository}. * Adds entities to regions in the {@link RegionRepository}. By default, we do not notify listeners.
* *
* @param entities The entities. * @param entities The entities.
*/ */
private void placeEntities(Entity... entities) { private void placeEntities(Entity... entities) {
Arrays.stream(entities).forEach(entity -> regions.fromPosition(entity.getPosition()).addEntity(entity)); Arrays.stream(entities).forEach(entity -> regions.fromPosition(entity.getPosition()).addEntity(entity, false));
} }
} }
+258 -236
View File
@@ -31,280 +31,302 @@ import com.google.common.collect.ImmutableSet;
*/ */
public final class Region { public final class Region {
/** /**
* A {@link RegionListener} for {@link UpdateOperation}s. * A {@link RegionListener} for {@link UpdateOperation}s.
* *
* @author Major * @author Major
*/ */
private static final class UpdateRegionListener implements RegionListener { private static final class UpdateRegionListener implements RegionListener {
@Override @Override
public void execute(Region region, Entity entity, EntityUpdateType update) { public void execute(Region region, Entity entity, EntityUpdateType update) {
EntityType type = entity.getEntityType(); EntityType type = entity.getEntityType();
if (type != EntityType.PLAYER && type != EntityType.NPC && (type != EntityType.STATIC_OBJECT || update == EntityUpdateType.REMOVE)) { if (type != EntityType.PLAYER && type != EntityType.NPC) {
region.record(entity, update); region.record(entity, update);
} }
} }
} }
/** /**
* The width and length of a Region, in tiles. * The width and length of a Region, in tiles.
*/ */
public static final int SIZE = 8; public static final int SIZE = 8;
static final long start = System.currentTimeMillis(); static final long start = System.currentTimeMillis();
/** /**
* The default size of newly-created sets, to reduce memory usage. * The default size of newly-created sets, to reduce memory usage.
*/ */
private static final int DEFAULT_SET_SIZE = 2; private static final int DEFAULT_SET_SIZE = 2;
/** /**
* The RegionCoordinates of this Region. * The RegionCoordinates of this Region.
*/ */
private final RegionCoordinates coordinates; private final RegionCoordinates coordinates;
/** /**
* The Map of Positions to Entities in that Position. * The Map of Positions to Entities in that Position.
*/ */
private final Map<Position, Set<Entity>> entities = new HashMap<>(); private final Map<Position, Set<Entity>> entities = new HashMap<>();
/** /**
* A List of RegionListeners registered to this Region. * A List of RegionListeners registered to this Region.
*/ */
private final List<RegionListener> listeners = new ArrayList<>(); private final List<RegionListener> listeners = new ArrayList<>();
/** /**
* The CollisionMatrix. * The CollisionMatrix.
*/ */
private final CollisionMatrix[] matrices = CollisionMatrix.createMatrices(Position.HEIGHT_LEVELS, SIZE, SIZE); private final CollisionMatrix[] matrices = CollisionMatrix.createMatrices(Position.HEIGHT_LEVELS, SIZE, SIZE);
/** /**
* The Set containing RegionUpdateMessages which can be sent to add every non-Mob Entity in this Region. * The Set containing RegionUpdateMessages which can be sent to add every non-Mob Entity in this Region.
*/ */
private final List<List<RegionUpdateMessage>> snapshots = new ArrayList<>(Position.HEIGHT_LEVELS); private final List<Map<Entity, RegionUpdateMessage>> snapshots = new ArrayList<>(Position.HEIGHT_LEVELS);
/** /**
* The Set containing UpdateOperations. * The Set containing UpdateOperations.
*/ */
private final List<List<RegionUpdateMessage>> updates = new ArrayList<>(Position.HEIGHT_LEVELS); private final List<List<RegionUpdateMessage>> updates = new ArrayList<>(Position.HEIGHT_LEVELS);
/** /**
* Creates a new Region. * Creates a new Region.
* *
* @param x The x coordinate of the Region. * @param x The x coordinate of the Region.
* @param y The y coordinate of the Region. * @param y The y coordinate of the Region.
*/ */
public Region(int x, int y) { public Region(int x, int y) {
this(new RegionCoordinates(x, y)); this(new RegionCoordinates(x, y));
} }
/** /**
* Creates a new Region with the specified {@link RegionCoordinates}. * Creates a new Region with the specified {@link RegionCoordinates}.
* *
* @param coordinates The coordinates. * @param coordinates The coordinates.
*/ */
public Region(RegionCoordinates coordinates) { public Region(RegionCoordinates coordinates) {
this.coordinates = coordinates; this.coordinates = coordinates;
listeners.add(new UpdateRegionListener()); listeners.add(new UpdateRegionListener());
for (int height = 0; height < Position.HEIGHT_LEVELS; height++) { for (int height = 0; height < Position.HEIGHT_LEVELS; height++) {
snapshots.add(new ArrayList<>()); snapshots.add(new HashMap<>());
updates.add(new ArrayList<>(DEFAULT_SET_SIZE)); updates.add(new ArrayList<>(DEFAULT_SET_SIZE));
} }
} }
/** /**
* Adds a {@link Entity} from to Region. Note that this does not spawn the Entity, or do any other action other than * Adds a {@link Entity} to the Region. Note that this does not spawn the Entity, or do any other action other than
* register it to this Region. * register it to this Region.
* *
* @param entity The Entity. * @param entity The Entity.
* @throws IllegalArgumentException If the Entity does not belong in this Region. * @param notify A flag indicating whether the {@link RegionListener}s for this Region should be notified.
*/ * @throws IllegalArgumentException If the Entity does not belong in this Region.
public void addEntity(Entity entity) { */
Position position = entity.getPosition(); public void addEntity(Entity entity, boolean notify) {
checkPosition(position); Position position = entity.getPosition();
checkPosition(position);
Set<Entity> local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE)); Set<Entity> local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE));
local.add(entity); local.add(entity);
if ((System.currentTimeMillis() - start) / 1000 > 10 && entity instanceof GameObject) { if ((System.currentTimeMillis() - start) / 1000 > 10 && (entity instanceof GameObject)) {
System.out.println("Adding entity " + entity + " to " + entity.getPosition()); System.out.println("Adding entity " + entity + " to " + entity.getPosition());
} }
notifyListeners(entity, EntityUpdateType.ADD); if (notify) {
} notifyListeners(entity, EntityUpdateType.ADD);
}
}
/** /**
* Checks if this Region contains the specified Entity. * Adds a {@link Entity} to the Region. Note that this does not spawn the Entity, or do any other action other than
* <p> * register it to this Region.
* This method operates in constant time. * <p/>
* * By default, this method notifies RegionListeners for this region of the addition.
* @param entity The Entity. *
* @return {@code true} if this Region contains the Entity, otherwise {@code false}. * @param entity The Entity.
*/ * @throws IllegalArgumentException If the Entity does not belong in this Region.
public boolean contains(Entity entity) { */
Position position = entity.getPosition(); public void addEntity(Entity entity) {
Set<Entity> local = entities.get(position); addEntity(entity, true);
}
return local != null && local.contains(entity); /**
} * Checks if this Region contains the specified Entity.
* <p/>
* This method operates in constant time.
*
* @param entity The Entity.
* @return {@code true} if this Region contains the Entity, otherwise {@code false}.
*/
/** public boolean contains(Entity entity) {
* Gets this Region's {@link RegionCoordinates}. Position position = entity.getPosition();
* Set<Entity> local = entities.get(position);
* @return The Region coordinates.
*/
public RegionCoordinates getCoordinates() {
return coordinates;
}
/** return local != null && local.contains(entity);
* Gets a shallow copy of the {@link Set} of {@link Entity} objects at the specified {@link Position}. The returned }
* type will be immutable.
*
* @param position The position containing the entities.
* @return The list.
*/
public Set<Entity> getEntities(Position position) {
Set<Entity> set = entities.get(position);
return set == null ? ImmutableSet.of() : ImmutableSet.copyOf(set);
}
/** /**
* Gets a shallow copy of the {@link Set} of {@link Entity}s with the specified {@link EntityType}(s). The returned * Gets this Region's {@link RegionCoordinates}.
* type will be immutable. Type will be inferred from the call, so ensure that the Entity type and the reference *
* correspond, or this method will fail at runtime. * @return The Region coordinates.
* */
* @param position The {@link Position} containing the entities. public RegionCoordinates getCoordinates() {
* @param types The {@link EntityType}s. return coordinates;
* @return The set of entities. }
*/
public <T extends Entity> Set<T> getEntities(Position position, EntityType... types) {
Set<Entity> local = entities.get(position);
if (local == null) {
return ImmutableSet.of();
}
Set<EntityType> set = new HashSet<>(Arrays.asList(types)); /**
@SuppressWarnings("unchecked") * Gets a shallow copy of the {@link Set} of {@link Entity} objects at the specified {@link Position}. The returned
Set<T> filtered = (Set<T>) local.stream().filter(entity -> set.contains(entity.getEntityType())).collect(Collectors.toSet()); * type will be immutable.
return ImmutableSet.copyOf(filtered); *
} * @param position The position containing the entities.
* @return The list.
*/
public Set<Entity> getEntities(Position position) {
Set<Entity> set = entities.get(position);
return (set == null) ? ImmutableSet.of() : ImmutableSet.copyOf(set);
}
/** /**
* Gets the {@link CollisionMatrix} at the specified height level. * Gets a shallow copy of the {@link Set} of {@link Entity}s with the specified {@link EntityType}(s). The returned
* * type will be immutable. Type will be inferred from the call, so ensure that the Entity type and the reference
* @param height The height level. * correspond, or this method will fail at runtime.
* @return The CollisionMatrix. *
*/ * @param position The {@link Position} containing the entities.
public CollisionMatrix getMatrix(int height) { * @param types The {@link EntityType}s.
Preconditions.checkElementIndex(height, matrices.length, "Matrix height level must be [0, " + matrices.length + ")."); * @return The set of entities.
return matrices[height]; */
} public <T extends Entity> Set<T> getEntities(Position position, EntityType... types) {
Set<Entity> local = entities.get(position);
if (local == null) {
return ImmutableSet.of();
}
/** Set<EntityType> set = new HashSet<>(Arrays.asList(types));
* Gets a {@link Set} containing {@link RegionUpdateMessage}s that add every {@link Entity} in this Region. @SuppressWarnings("unchecked")
* Set<T> filtered = (Set<T>) local.stream().filter(entity -> set.contains(entity.getEntityType())).collect(Collectors.toSet());
* @param height The height level to get the Set of RegionUpdateMessages for. return ImmutableSet.copyOf(filtered);
* @return The Set of RegionUpdateMessages. }
*/
public List<RegionUpdateMessage> getSnapshot(int height) {
List<RegionUpdateMessage> copy = new ArrayList<>(snapshots.get(height));
Collections.sort(copy);
return ImmutableList.copyOf(copy);
}
/** /**
* Gets the updates that have occurred in the last tick in this Region, as a {@link Set} of * Gets the {@link CollisionMatrix} at the specified height level.
* {@link RegionUpdateMessage}s. *
* * @param height The height level.
* @param height The height level to get the Set of RegionUpdateMessages for. * @return The CollisionMatrix.
* @return The Set of RegionUpdateMessages. */
*/ public CollisionMatrix getMatrix(int height) {
public List<RegionUpdateMessage> getUpdates(int height) { Preconditions.checkElementIndex(height, matrices.length, "Matrix height level must be [0, " + matrices.length + ").");
List<RegionUpdateMessage> original = updates.get(height); return matrices[height];
List<RegionUpdateMessage> updates = new ArrayList<>(original); }
original.clear();
Collections.sort(updates); /**
return ImmutableList.copyOf(updates); * Gets a {@link Set} containing {@link RegionUpdateMessage}s that add every {@link Entity} in this Region.
} *
* @param height The height level to get the Set of RegionUpdateMessages for.
* @return The Set of RegionUpdateMessages.
*/
public List<RegionUpdateMessage> getSnapshot(int height) {
List<RegionUpdateMessage> copy = new ArrayList<>(snapshots.get(height).values());
Collections.sort(copy);
return ImmutableList.copyOf(copy);
}
/** /**
* Notifies the {@link RegionListener}s registered to this Region that an update has occurred. * Gets the updates that have occurred in the last tick in this Region, as a {@link Set} of
* * {@link RegionUpdateMessage}s.
* @param entity The {@link Entity} that was updated. *
* @param type The {@link EntityUpdateType} that occurred. * @param height The height level to get the Set of RegionUpdateMessages for.
*/ * @return The Set of RegionUpdateMessages.
public void notifyListeners(Entity entity, EntityUpdateType type) { */
listeners.forEach(listener -> listener.execute(this, entity, type)); public List<RegionUpdateMessage> getUpdates(int height) {
} List<RegionUpdateMessage> original = this.updates.get(height);
List<RegionUpdateMessage> updates = new ArrayList<>(original);
original.clear();
/** Collections.sort(updates);
* Removes a {@link Entity} from this Region. return ImmutableList.copyOf(updates);
* }
* @param entity The Entity.
* @throws IllegalArgumentException If the Entity does not belong in this Region, or if it was never added.
*/
public void removeEntity(Entity entity) {
Position position = entity.getPosition();
checkPosition(position);
Set<Entity> local = entities.get(position); /**
* Notifies the {@link RegionListener}s registered to this Region that an update has occurred.
*
* @param entity The {@link Entity} that was updated.
* @param type The {@link EntityUpdateType} that occurred.
*/
public void notifyListeners(Entity entity, EntityUpdateType type) {
listeners.forEach(listener -> listener.execute(this, entity, type));
}
if (local == null || !local.remove(entity)) { /**
throw new IllegalArgumentException("Entity belongs in this Region (" + this + ") but does not exist."); * Removes a {@link Entity} from this Region.
} *
* @param entity The Entity.
* @throws IllegalArgumentException If the Entity does not belong in this Region, or if it was never added.
*/
public void removeEntity(Entity entity) {
Position position = entity.getPosition();
checkPosition(position);
notifyListeners(entity, EntityUpdateType.REMOVE); Set<Entity> local = entities.get(position);
}
@Override if (local == null || !local.remove(entity)) {
public String toString() { throw new IllegalArgumentException("Entity belongs in this Region (" + this + ") but does not exist.");
return MoreObjects.toStringHelper(this).add("coordinates", coordinates).toString(); }
}
/** notifyListeners(entity, EntityUpdateType.REMOVE);
* Returns whether or not an Entity of the specified {@link EntityType type} can traverse the tile at the specified }
* coordinate pair.
*
* @param position The {@link Position} of the tile.
* @param entity The {@link EntityType}.
* @param direction The {@link Direction} the Entity is approaching from.
* @return {@code true} if the tile at the specified coordinate pair is traversable, {@code false} if not.
*/
public boolean traversable(Position position, EntityType entity, Direction direction) {
CollisionMatrix matrix = matrices[position.getHeight()];
int x = position.getX(), y = position.getY();
return !matrix.untraversable(x % SIZE, y % SIZE, entity, direction); @Override
} public String toString() {
return MoreObjects.toStringHelper(this).add("coordinates", coordinates).toString();
}
/** /**
* Checks that the specified {@link Position} is included in this Region. * Returns whether or not an Entity of the specified {@link EntityType type} can traverse the tile at the specified
* * coordinate pair.
* @param position The position. *
* @throws IllegalArgumentException If the specified position is not included in this Region. * @param position The {@link Position} of the tile.
*/ * @param entity The {@link EntityType}.
private void checkPosition(Position position) { * @param direction The {@link Direction} the Entity is approaching from.
Preconditions.checkArgument(coordinates.equals(RegionCoordinates.fromPosition(position)), "Position is not included in this Region."); * @return {@code true} if the tile at the specified coordinate pair is traversable, {@code false} if not.
} */
public boolean traversable(Position position, EntityType entity, Direction direction) {
CollisionMatrix matrix = matrices[position.getHeight()];
int x = position.getX(), y = position.getY();
/** return !matrix.untraversable(x % SIZE, y % SIZE, entity, direction);
* Records the specified {@link Entity} as being updated this pulse. }
*
* @param entity The Entity.
* @param type The {@link EntityUpdateType}.
* @throws UnsupportedOperationException If the specified Entity cannot be operated on in this manner.
*/
private void record(Entity entity, EntityUpdateType type) {
RegionUpdateMessage message = entity.toUpdateOperation(this, type).toMessage();
int height = entity.getPosition().getHeight();
updates.get(height).add(message); /**
snapshots.get(height).add(message); * Checks that the specified {@link Position} is included in this Region.
} *
* @param position The position.
* @throws IllegalArgumentException If the specified position is not included in this Region.
*/
private void checkPosition(Position position) {
Preconditions.checkArgument(coordinates.equals(RegionCoordinates.fromPosition(position)), "Position is not included in this Region.");
}
/**
* Records the specified {@link Entity} as being updated this pulse.
*
* @param entity The Entity.
* @param type The {@link EntityUpdateType}.
* @throws UnsupportedOperationException If the specified Entity cannot be operated on in this manner.
*/
private void record(Entity entity, EntityUpdateType type) {
RegionUpdateMessage message = entity.toUpdateOperation(this, type).toMessage();
int height = entity.getPosition().getHeight();
updates.get(height).add(message);
snapshots.get(height).remove(entity);
if ((entity.getEntityType() == EntityType.STATIC_OBJECT && type == EntityUpdateType.REMOVE) ||
(entity.getEntityType() != EntityType.STATIC_OBJECT && type == EntityUpdateType.ADD)) {
snapshots.get(height).put(entity, message);
}
}
} }