Optimise definition decoding for faster start-up.

This commit is contained in:
Major-
2015-08-29 19:57:57 +01:00
parent b134f6fdf5
commit 38af001083
15 changed files with 726 additions and 934 deletions
@@ -1,6 +1,7 @@
package org.apollo.cache.decoder;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import org.apollo.cache.IndexedFileSystem;
@@ -13,47 +14,46 @@ import org.apollo.util.BufferUtil;
*
* @author Graham
*/
public final class ItemDefinitionDecoder {
public final class ItemDefinitionDecoder implements Runnable {
/**
* The {@link IndexedFileSystem}.
* The IndexedFileSystem.
*/
private final IndexedFileSystem fs;
/**
* Creates the item definition decoder.
* Creates the ItemDefinitionDecoder.
*
* @param fs The indexed file system.
* @param fs The {@link IndexedFileSystem}.
*/
public ItemDefinitionDecoder(IndexedFileSystem fs) {
this.fs = fs;
}
/**
* Decodes the item definitions.
*
* @return The item definitions.
* @throws IOException If an I/O error occurs.
*/
public ItemDefinition[] decode() throws IOException {
Archive config = fs.getArchive(0, 2);
ByteBuffer data = config.getEntry("obj.dat").getBuffer();
ByteBuffer idx = config.getEntry("obj.idx").getBuffer();
@Override
public void run() {
try {
Archive config = fs.getArchive(0, 2);
ByteBuffer data = config.getEntry("obj.dat").getBuffer();
ByteBuffer idx = config.getEntry("obj.idx").getBuffer();
int count = idx.getShort(), index = 2;
int[] indices = new int[count];
for (int i = 0; i < count; i++) {
indices[i] = index;
index += idx.getShort();
int count = idx.getShort(), index = 2;
int[] indices = new int[count];
for (int i = 0; i < count; i++) {
indices[i] = index;
index += idx.getShort();
}
ItemDefinition[] definitions = new ItemDefinition[count];
for (int i = 0; i < count; i++) {
data.position(indices[i]);
definitions[i] = decode(i, data);
}
ItemDefinition.init(definitions);
} catch (IOException e) {
throw new UncheckedIOException("Error decoding ItemDefinitions.", e);
}
ItemDefinition[] defs = new ItemDefinition[count];
for (int i = 0; i < count; i++) {
data.position(indices[i]);
defs[i] = decode(i, data);
}
return defs;
}
/**
@@ -121,12 +121,12 @@ public final class MapFileDecoder {
int count = buffer.capacity() / (3 * Short.BYTES + Byte.BYTES);
for (int times = 0; times < count; times++) {
int packed = buffer.getShort() & 0xFFFF;
int id = buffer.getShort() & 0xFFFF;
int terrain = buffer.getShort() & 0xFFFF;
int objects = buffer.getShort() & 0xFFFF;
boolean members = buffer.get() == 1;
definitions.put(packed, new MapDefinition(packed, terrain, objects, members));
definitions.put(id, new MapDefinition(id, terrain, objects, members));
}
return definitions;
@@ -1,6 +1,7 @@
package org.apollo.cache.decoder;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
@@ -14,47 +15,47 @@ import org.apollo.util.BufferUtil;
*
* @author Major
*/
public final class NpcDefinitionDecoder {
public final class NpcDefinitionDecoder implements Runnable {
/**
* The {@link IndexedFileSystem}.
* The IndexedFileSystem.
*/
private final IndexedFileSystem fs;
/**
* Creates the npc definition decoder.
* Creates the NpcDefinitionDecoder.
*
* @param fs The indexed file system.
* @param fs The {@link IndexedFileSystem}.
*/
public NpcDefinitionDecoder(IndexedFileSystem fs) {
this.fs = fs;
}
/**
* Decodes the npc definitions.
*
* @return An array of all parsed npc definitions.
* @throws IOException If an I/O error occurs.
*/
public NpcDefinition[] decode() throws IOException {
Archive config = fs.getArchive(0, 2);
ByteBuffer data = config.getEntry("npc.dat").getBuffer();
ByteBuffer idx = config.getEntry("npc.idx").getBuffer();
@Override
public void run() {
try {
Archive config = fs.getArchive(0, 2);
ByteBuffer data = config.getEntry("npc.dat").getBuffer();
ByteBuffer idx = config.getEntry("npc.idx").getBuffer();
int count = idx.getShort(), index = 2;
int[] indices = new int[count];
for (int i = 0; i < count; i++) {
indices[i] = index;
index += idx.getShort();
int count = idx.getShort(), index = 2;
int[] indices = new int[count];
for (int i = 0; i < count; i++) {
indices[i] = index;
index += idx.getShort();
}
NpcDefinition[] definitions = new NpcDefinition[count];
for (int i = 0; i < count; i++) {
data.position(indices[i]);
definitions[i] = decode(i, data);
}
NpcDefinition.init(definitions);
} catch (IOException e) {
throw new UncheckedIOException("Error decoding NpcDefinitions.", e);
}
NpcDefinition[] defs = new NpcDefinition[count];
for (int i = 0; i < count; i++) {
data.position(indices[i]);
defs[i] = decode(i, data);
}
return defs;
}
/**
@@ -75,8 +76,8 @@ public final class NpcDefinitionDecoder {
} else if (opcode == 1) {
int length = buffer.get() & 0xFF;
int[] models = new int[length];
for (int i = 0; i < length; i++) {
models[i] = buffer.getShort();
for (int index = 0; index < length; index++) {
models[index] = buffer.getShort();
}
} else if (opcode == 2) {
definition.setName(BufferUtil.readString(buffer));
@@ -92,24 +93,27 @@ public final class NpcDefinitionDecoder {
definition
.setWalkAnimations(buffer.getShort(), buffer.getShort(), buffer.getShort(), buffer.getShort());
} else if (opcode >= 30 && opcode < 40) {
String str = BufferUtil.readString(buffer);
if (str.equals("hidden")) {
str = null;
String action = BufferUtil.readString(buffer);
if (action.equals("hidden")) {
action = null;
}
definition.setInteraction(opcode - 30, str);
definition.setInteraction(opcode - 30, action);
} else if (opcode == 40) {
int length = buffer.get() & 0xFF;
int[] originalColours = new int[length];
int[] replacementColours = new int[length];
for (int i = 0; i < length; i++) {
originalColours[i] = buffer.getShort();
replacementColours[i] = buffer.getShort();
for (int index = 0; index < length; index++) {
originalColours[index] = buffer.getShort();
replacementColours[index] = buffer.getShort();
}
} else if (opcode == 60) {
int length = buffer.get() & 0xFF;
int[] additionalModels = new int[length];
for (int i = 0; i < length; i++) {
additionalModels[i] = buffer.getShort();
for (int index = 0; index < length; index++) {
additionalModels[index] = buffer.getShort();
}
} else if (opcode >= 90 && opcode <= 92) {
buffer.getShort(); // Dummy
@@ -1,10 +1,12 @@
package org.apollo.cache.decoder;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import org.apollo.cache.IndexedFileSystem;
import org.apollo.cache.archive.Archive;
import org.apollo.cache.def.ItemDefinition;
import org.apollo.cache.def.ObjectDefinition;
import org.apollo.util.BufferUtil;
@@ -13,15 +15,15 @@ import org.apollo.util.BufferUtil;
*
* @author Major
*/
public final class ObjectDefinitionDecoder {
public final class ObjectDefinitionDecoder implements Runnable {
/**
* The {@link IndexedFileSystem}.
* The IndexedFileSystem.
*/
private final IndexedFileSystem fs;
/**
* Creates the decoder.
* Creates the ObjectDefinitionDecoder.
*
* @param fs The {@link IndexedFileSystem}.
*/
@@ -29,6 +31,32 @@ public final class ObjectDefinitionDecoder {
this.fs = fs;
}
@Override
public void run() {
try {
Archive config = fs.getArchive(0, 2);
ByteBuffer data = config.getEntry("loc.dat").getBuffer();
ByteBuffer idx = config.getEntry("loc.idx").getBuffer();
int count = idx.getShort(), index = 2;
int[] indices = new int[count];
for (int i = 0; i < count; i++) {
indices[i] = index;
index += idx.getShort();
}
ObjectDefinition[] definitions = new ObjectDefinition[count];
for (int i = 0; i < count; i++) {
data.position(indices[i]);
definitions[i] = decode(i, data);
}
ObjectDefinition.init(definitions);
} catch (IOException e) {
throw new UncheckedIOException("Error decoding ObjectDefinitions.", e);
}
}
/**
* Decodes data from the cache into an {@link ObjectDefinition}.
*
@@ -36,7 +64,7 @@ public final class ObjectDefinitionDecoder {
* @param data The {@link ByteBuffer} containing the data.
* @return The object definition.
*/
public ObjectDefinition decode(int id, ByteBuffer data) {
private ObjectDefinition decode(int id, ByteBuffer data) {
ObjectDefinition definition = new ObjectDefinition(id);
while (true) {
int opcode = data.get() & 0xFF;
@@ -111,30 +139,4 @@ public final class ObjectDefinitionDecoder {
}
}
/**
* Decodes all of the data into {@link ObjectDefinition}s.
*
* @return The definitions.
* @throws IOException If an error occurs when decoding the archive or finding an entry.
*/
public ObjectDefinition[] decode() throws IOException {
Archive config = fs.getArchive(0, 2);
ByteBuffer data = config.getEntry("loc.dat").getBuffer();
ByteBuffer idx = config.getEntry("loc.idx").getBuffer();
int count = idx.getShort(), index = 2;
int[] indices = new int[count];
for (int i = 0; i < count; i++) {
indices[i] = index;
index += idx.getShort();
}
ObjectDefinition[] defs = new ObjectDefinition[count];
for (int i = 0; i < count; i++) {
data.position(indices[i]);
defs[i] = decode(i, data);
}
return defs;
}
}
+2 -2
View File
@@ -36,14 +36,14 @@ public final class NpcDefinition {
* Initialises the class with the specified set of definitions.
*
* @param definitions The definitions.
* @throws RuntimeException If there is an id mismatch.
* @throws IllegalStateException If there is an id mismatch.
*/
public static void init(NpcDefinition[] definitions) {
NpcDefinition.definitions = definitions;
for (int id = 0; id < definitions.length; id++) {
NpcDefinition def = definitions[id];
if (def.getId() != id) {
throw new RuntimeException("Npc definition id mismatch.");
throw new IllegalStateException("Npc definition id mismatch.");
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,7 @@
package org.apollo.game.fs.decoder;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -11,6 +12,7 @@ import java.util.function.Predicate;
import org.apollo.cache.IndexedFileSystem;
import org.apollo.cache.decoder.MapFileDecoder;
import org.apollo.cache.decoder.MapFileDecoder.MapDefinition;
import org.apollo.cache.decoder.ObjectDefinitionDecoder;
import org.apollo.cache.def.ObjectDefinition;
import org.apollo.game.model.Position;
import org.apollo.game.model.World;
@@ -31,7 +33,7 @@ import com.google.common.collect.Iterables;
* @author Ryley
* @author Major
*/
public final class GameObjectDecoder {
public final class GameObjectDecoder implements Runnable {
/**
* A bit flag that indicates that the tile at the current Position is blocked.
@@ -58,44 +60,51 @@ public final class GameObjectDecoder {
*/
private final RegionRepository regions;
/**
* The World to place the objects in.
*/
private final World world;
/**
* Creates the GameObjectDecoder.
*
* @param fs The {@link IndexedFileSystem}.
* @param regions The {@link RegionRepository}.
* @param world The {@link World} to place the objects in.
*/
public GameObjectDecoder(IndexedFileSystem fs, RegionRepository regions) {
public GameObjectDecoder(IndexedFileSystem fs, World world) {
this.fs = fs;
this.regions = regions;
this.world = world;
regions = world.getRegionRepository();
}
/**
* Decodes the GameObjects from their MapDefinitions.
*
* @param world The {@link World} containing the StaticGameObjects.
* @return The decoded objects.
* @throws IOException If there is an error decoding the {@link MapDefinition}s.
*/
public GameObject[] decode(World world) throws IOException {
Map<Integer, MapDefinition> definitions = MapFileDecoder.decode(fs);
@Override
public void run() {
ObjectDefinitionDecoder decoder = new ObjectDefinitionDecoder(fs);
decoder.run();
for (Entry<Integer, MapDefinition> entry : definitions.entrySet()) {
MapDefinition definition = entry.getValue();
try {
Map<Integer, MapDefinition> definitions = MapFileDecoder.decode(fs);
int packed = definition.getPackedCoordinates();
int x = (packed >> 8 & 0xFF) * 64;
int y = (packed & 0xFF) * 64;
for (Entry<Integer, MapDefinition> entry : definitions.entrySet()) {
MapDefinition definition = entry.getValue();
ByteBuffer objects = fs.getFile(4, definition.getObjectFile());
ByteBuffer decompressed = ByteBuffer.wrap(CompressionUtil.degzip(objects));
decodeObjects(world, decompressed, x, y);
int packed = definition.getPackedCoordinates();
int x = (packed >> 8 & 0xFF) * 64;
int y = (packed & 0xFF) * 64;
ByteBuffer terrain = fs.getFile(4, definition.getTerrainFile());
decompressed = ByteBuffer.wrap(CompressionUtil.degzip(terrain));
decodeTerrain(decompressed, x, y);
ByteBuffer objects = fs.getFile(4, definition.getObjectFile());
ByteBuffer decompressed = ByteBuffer.wrap(CompressionUtil.degzip(objects));
decodeObjects(world, decompressed, x, y);
ByteBuffer terrain = fs.getFile(4, definition.getTerrainFile());
decompressed = ByteBuffer.wrap(CompressionUtil.degzip(terrain));
decodeTerrain(decompressed, x, y);
}
} catch (IOException e) {
throw new UncheckedIOException("Error decoding StaticGameObjects.", e);
}
return Iterables.toArray(objects, GameObject.class);
objects.forEach(object -> regions.fromPosition(object.getPosition()).addEntity(object, false));
}
/**
@@ -112,24 +121,21 @@ public final class GameObjectDecoder {
int x = position.getX(), y = position.getY(), height = position.getHeight();
CollisionMatrix matrix = region.getMatrix(height);
boolean block = false;
if (type == ObjectType.FLOOR_DECORATION.getValue() && definition.isInteractive()) {
block = true;
}
Predicate<Integer> walls = (value) -> value >= ObjectType.LENGTHWISE_WALL.getValue()
// TODO figure out the other ObjectTypes and get rid of all the getValue() calls
Predicate<Integer> walls = value -> value >= ObjectType.LENGTHWISE_WALL.getValue()
&& value <= ObjectType.RECTANGULAR_CORNER.getValue() || value == ObjectType.DIAGONAL_WALL.getValue();
Predicate<Integer> roofs = (value) -> value > ObjectType.DIAGONAL_INTERACTABLE.getValue()
Predicate<Integer> roofs = value -> value > ObjectType.DIAGONAL_INTERACTABLE.getValue()
&& value < ObjectType.FLOOR_DECORATION.getValue();
if (walls.test(type) || roofs.test(type)) {
block = true;
}
if (type == 10 && definition.isSolid()) {
if (walls.test(type) || roofs.test(type) || type == ObjectType.INTERACTABLE.getValue() && definition.isSolid()) {
block = true;
}
@@ -144,11 +150,13 @@ public final class GameObjectDecoder {
Position nextPosition = new Position(nextLocalX, nextLocalY);
Region next = regions.fromPosition(nextPosition);
int nextX = nextPosition.getX() % Region.SIZE + dx, nextY = nextPosition.getY() % Region.SIZE
+ dy;
int nextX = nextPosition.getX() % Region.SIZE + dx;
int nextY = nextPosition.getY() % Region.SIZE + dy;
if (nextX > 7) {
nextX -= 7;
}
if (nextY > 7) {
nextY -= 7;
}
@@ -173,12 +181,11 @@ public final class GameObjectDecoder {
Region region = regions.fromPosition(position);
int x = position.getX(), y = position.getY(), height = position.getHeight();
CollisionMatrix current = region.getMatrix(height);
boolean block = false;
if ((attributes & BLOCKED_TILE) != 0) {
block = true;
}
if ((attributes & BRIDGE_TILE) != 0) {
if (height > 0) {
block = true;
@@ -188,7 +195,7 @@ public final class GameObjectDecoder {
if (block) {
int localX = x % Region.SIZE, localY = y % Region.SIZE;
current.block(localX, localY);
region.getMatrix(height).block(localX, localY);
}
}
@@ -249,6 +256,7 @@ public final class GameObjectDecoder {
int attributes = 0;
while (true) {
int attributeId = buffer.get() & 0xFF;
if (attributeId == 0) {
decodeAttributes(attributes, position);
break;
@@ -0,0 +1,54 @@
package org.apollo.game.fs.decoder;
import org.apollo.util.ThreadUtil;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* A composite decoder that executes each child in parallel.
*
* @author Major
*/
public final class SynchronousDecoder {
/**
* The time to wait before cancelling the decoding.
*/
private static final int TIMEOUT = 15_000;
/**
* The Executor used to execute the Runnable(s).
*/
private final ExecutorService executor = Executors.newFixedThreadPool(ThreadUtil.AVAILABLE_PROCESSORS,
ThreadUtil.create("SynchronousDecoder"));
/**
* The List of Runnables.
*/
private final List<Runnable> runnables;
/**
* Creates the SynchronousDecoder.
*
* @param runnables The {@link Runnable}s to execute.
*/
public SynchronousDecoder(Runnable... runnables) {
this.runnables = Arrays.asList(runnables);
}
/**
* Starts this SynchronousDecoder.
*
* @throws InterruptedException If a decoder is still running after {@link #TIMEOUT} ms.
*/
public void block() throws InterruptedException {
runnables.forEach(executor::submit);
executor.shutdown();
executor.awaitTermination(TIMEOUT, TimeUnit.MILLISECONDS);
}
}
@@ -1,67 +1,83 @@
package org.apollo.game.io;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import org.apollo.cache.def.EquipmentDefinition;
/**
* A class that parses the {@code data/equipment-[release].dat} file to create an array of {@link EquipmentDefinition}s.
* A class that parses the {@code data/equipment-[release].dat} file to create an array of {@link
* EquipmentDefinition}s.
*
* @author Graham
*/
public final class EquipmentDefinitionParser {
public final class EquipmentDefinitionParser implements Runnable {
/**
* The input stream.
* Creates an {@link EquipmentDefinitionParser} that reads from the file located at the specified {@code path}.
*
* @param path The path to the file.
* @return The EquipmentDefinitionParser.
* @throws UncheckedIOException If there is an error creating the {@link FileInputStream}.
*/
public static EquipmentDefinitionParser fromFile(String path) {
try {
return new EquipmentDefinitionParser(new BufferedInputStream(new FileInputStream(path)));
} catch (IOException e) {
throw new UncheckedIOException("Error creating EquipmentDefinitionParser.", e);
}
}
/**
* The InputStream.
*/
private final InputStream is;
/**
* Creates the equipment definition parser.
* Creates the EquipmentDefinitionParser.
*
* @param is The input stream.
* @param is The {@link InputStream}.
*/
public EquipmentDefinitionParser(InputStream is) {
this.is = is;
}
/**
* Parses the input stream.
*
* @return The equipment definition array.
* @throws IOException If an I/O error occurs.
*/
public EquipmentDefinition[] parse() throws IOException {
DataInputStream dis = new DataInputStream(is);
@Override
public void run() {
try (DataInputStream in = new DataInputStream(is)) {
int count = in.readShort() & 0xFFFF;
EquipmentDefinition[] definitions = new EquipmentDefinition[count];
int count = dis.readShort() & 0xFFFF;
EquipmentDefinition[] definitions = new EquipmentDefinition[count];
for (int id = 0; id < count; id++) {
int slot = in.readByte() & 0xFF;
if (slot != 0xFF) {
boolean twoHanded = in.readBoolean();
boolean fullBody = in.readBoolean();
boolean fullHat = in.readBoolean();
boolean fullMask = in.readBoolean();
int attack = in.readByte() & 0xFF;
int strength = in.readByte() & 0xFF;
int defence = in.readByte() & 0xFF;
int ranged = in.readByte() & 0xFF;
int magic = in.readByte() & 0xFF;
for (int id = 0; id < count; id++) {
int slot = dis.readByte() & 0xFF;
if (slot != 0xFF) {
boolean twoHanded = dis.readBoolean();
boolean fullBody = dis.readBoolean();
boolean fullHat = dis.readBoolean();
boolean fullMask = dis.readBoolean();
int attack = dis.readByte() & 0xFF;
int strength = dis.readByte() & 0xFF;
int defence = dis.readByte() & 0xFF;
int ranged = dis.readByte() & 0xFF;
int magic = dis.readByte() & 0xFF;
EquipmentDefinition definition = new EquipmentDefinition(id);
definition.setLevels(attack, strength, defence, ranged, magic);
definition.setSlot(slot);
definition.setFlags(twoHanded, fullBody, fullHat, fullMask);
EquipmentDefinition definition = new EquipmentDefinition(id);
definition.setLevels(attack, strength, defence, ranged, magic);
definition.setSlot(slot);
definition.setFlags(twoHanded, fullBody, fullHat, fullMask);
definitions[id] = definition;
definitions[id] = definition;
}
}
}
return definitions;
EquipmentDefinition.init(definitions);
} catch (IOException e) {
throw new UncheckedIOException("Error parsing EquipmentDefinitions.", e);
}
}
}
+18 -61
View File
@@ -1,24 +1,18 @@
package org.apollo.game.model;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.apollo.Server;
import org.apollo.Service;
import org.apollo.cache.IndexedFileSystem;
import org.apollo.cache.decoder.ItemDefinitionDecoder;
import org.apollo.cache.decoder.NpcDefinitionDecoder;
import org.apollo.cache.decoder.ObjectDefinitionDecoder;
import org.apollo.cache.def.EquipmentDefinition;
import org.apollo.cache.def.ItemDefinition;
import org.apollo.cache.def.NpcDefinition;
import org.apollo.cache.def.ObjectDefinition;
import org.apollo.game.command.CommandDispatcher;
import org.apollo.game.fs.decoder.GameObjectDecoder;
import org.apollo.game.fs.decoder.SynchronousDecoder;
import org.apollo.game.io.EquipmentDefinitionParser;
import org.apollo.game.model.area.Region;
import org.apollo.game.model.area.RegionRepository;
@@ -27,7 +21,6 @@ import org.apollo.game.model.entity.EntityType;
import org.apollo.game.model.entity.MobRepository;
import org.apollo.game.model.entity.Npc;
import org.apollo.game.model.entity.Player;
import org.apollo.game.model.entity.obj.GameObject;
import org.apollo.game.model.event.Event;
import org.apollo.game.model.event.EventListener;
import org.apollo.game.model.event.EventListenerChainSet;
@@ -87,11 +80,6 @@ public final class World {
*/
private final EventListenerChainSet events = new EventListenerChainSet();
/**
* The ScheduledTask that moves Npcs.
*/
private NpcMovementTask npcMovement;
/**
* The {@link MobRepository} of {@link Npc}s.
*/
@@ -107,32 +95,30 @@ public final class World {
*/
private final Map<Long, Player> players = new HashMap<>();
/**
* The {@link PluginManager}.
*/
private PluginManager pluginManager;
/**
* This world's {@link RegionRepository}.
*/
private final RegionRepository regions = RegionRepository.immutable();
/**
* The release number (i.e. version) of this world.
*/
private int releaseNumber;
/**
* The scheduler.
*/
private final Scheduler scheduler = new Scheduler();
/**
* Creates the world.
* The ScheduledTask that moves Npcs.
*/
public World() {
private NpcMovementTask npcMovement;
}
/**
* The {@link PluginManager}.
*/
private PluginManager pluginManager;
/**
* The release number (i.e. version) of this world.
*/
private int releaseNumber;
/**
* Gets the command dispatcher.
@@ -210,32 +196,11 @@ public final class World {
public void init(int release, IndexedFileSystem fs, PluginManager manager) throws Exception {
releaseNumber = release;
ItemDefinitionDecoder itemDecoder = new ItemDefinitionDecoder(fs);
ItemDefinition[] items = itemDecoder.decode();
ItemDefinition.init(items);
logger.fine("Loaded " + items.length + " item definitions.");
SynchronousDecoder decoder = new SynchronousDecoder(new ItemDefinitionDecoder(fs),
new NpcDefinitionDecoder(fs), new GameObjectDecoder(fs, this),
EquipmentDefinitionParser.fromFile("data/equipment-" + release + "" + ".dat"));
try (InputStream is = new BufferedInputStream(new FileInputStream("data/equipment-" + release + ".dat"))) {
EquipmentDefinitionParser parser = new EquipmentDefinitionParser(is);
EquipmentDefinition[] defs = parser.parse();
EquipmentDefinition.init(defs);
logger.fine("Loaded " + defs.length + " equipment definitions.");
}
NpcDefinitionDecoder npcDecoder = new NpcDefinitionDecoder(fs);
NpcDefinition[] npcs = npcDecoder.decode();
NpcDefinition.init(npcs);
logger.fine("Loaded " + npcs.length + " npc definitions.");
ObjectDefinitionDecoder objectDecoder = new ObjectDefinitionDecoder(fs);
ObjectDefinition[] objectDefs = objectDecoder.decode();
ObjectDefinition.init(objectDefs);
logger.fine("Loaded " + objectDefs.length + " object definitions.");
GameObjectDecoder staticDecoder = new GameObjectDecoder(fs, regions);
GameObject[] objects = staticDecoder.decode(this);
placeEntities(objects);
logger.fine("Loaded " + objects.length + " static objects.");
decoder.block();
npcMovement = new NpcMovementTask(regions); // Must be exactly here because of ordering issues.
scheduler.schedule(npcMovement);
@@ -291,6 +256,7 @@ public final class World {
} else {
logger.warning("Failed to register npc, repository capacity reached: [count=" + npcRepository.size() + "]");
}
return success;
}
@@ -384,13 +350,4 @@ public final class World {
}
}
/**
* Adds entities to regions in the {@link RegionRepository}. By default, we do not notify listeners.
*
* @param entities The entities.
*/
private void placeEntities(Entity... entities) {
Arrays.stream(entities).forEach(entity -> regions.fromPosition(entity.getPosition()).addEntity(entity, false));
}
}
@@ -21,8 +21,13 @@ import org.apollo.game.sync.seg.SynchronizationSegment;
public final class NpcSynchronizationTask extends SynchronizationTask {
/**
* The maximum number of npcs to load per cycle. This prevents the update packet from becoming too large (the client
* uses a 5000 byte buffer) and also stops old spec PCs from crashing when they login or teleport.
* The maximum amount of local npcs.
*/
private static final int MAXIMUM_LOCAL_NPCS = 255;
/**
* The maximum number of npcs to load per cycle. This prevents the update packet from becoming too large (the
* client uses a 5000 byte buffer) and also stops old spec PCs from crashing when they login or teleport.
*/
private static final int NEW_NPCS_PER_CYCLE = 20;
@@ -42,41 +47,49 @@ public final class NpcSynchronizationTask extends SynchronizationTask {
@Override
public void run() {
List<Npc> localNpcs = player.getLocalNpcList();
List<Npc> locals = player.getLocalNpcList();
List<SynchronizationSegment> segments = new ArrayList<>();
int oldLocalNpcs = localNpcs.size();
int originalCount = locals.size();
final Position playerPosition = player.getPosition();
for (Iterator<Npc> it = localNpcs.iterator(); it.hasNext();) {
Npc npc = it.next();
if (!npc.isActive() || npc.isTeleporting() || npc.getPosition().getLongestDelta(playerPosition) > player.getViewingDistance() || !npc.getPosition().isWithinDistance(playerPosition, player.getViewingDistance())) {
it.remove();
int distance = player.getViewingDistance();
for (Iterator<Npc> iterator = locals.iterator(); iterator.hasNext(); ) {
Npc npc = iterator.next();
Position position = npc.getPosition();
if (!npc.isActive() || npc.isTeleporting() || position.getLongestDelta(playerPosition) > distance
|| !position.isWithinDistance(playerPosition, distance)) {
iterator.remove();
segments.add(new RemoveMobSegment());
} else {
segments.add(new MovementSegment(npc.getBlockSet(), npc.getDirections()));
}
}
int added = 0;
int added = 0, count = locals.size();
for (Npc npc : player.getWorld().getNpcRepository()) {
if (localNpcs.size() >= 255) {
if (count >= MAXIMUM_LOCAL_NPCS) {
player.flagExcessiveNpcs();
break;
} else if (added >= NEW_NPCS_PER_CYCLE) {
break;
}
Position npcPosition = npc.getPosition();
if (npcPosition.isWithinDistance(playerPosition, player.getViewingDistance()) && !localNpcs.contains(npc)) {
localNpcs.add(npc);
Position position = npc.getPosition();
if (position.isWithinDistance(playerPosition, distance) && !locals.contains(npc)) {
locals.add(npc);
count++;
added++;
npc.turnTo(npc.getFacingPosition());
segments.add(new AddNpcSegment(npc.getBlockSet(), npc.getIndex(), npcPosition, npc.getId()));
segments.add(new AddNpcSegment(npc.getBlockSet(), npc.getIndex(), position, npc.getId()));
}
}
player.send(new NpcSynchronizationMessage(playerPosition, segments, oldLocalNpcs));
player.send(new NpcSynchronizationMessage(playerPosition, segments, originalCount));
}
}
@@ -6,7 +6,6 @@ import java.util.List;
import org.apollo.game.message.impl.PlayerSynchronizationMessage;
import org.apollo.game.model.Position;
import org.apollo.game.model.entity.MobRepository;
import org.apollo.game.model.entity.Player;
import org.apollo.game.sync.block.AppearanceBlock;
import org.apollo.game.sync.block.ChatBlock;
@@ -25,6 +24,11 @@ import org.apollo.game.sync.seg.TeleportSegment;
*/
public final class PlayerSynchronizationTask extends SynchronizationTask {
/**
* The maximum amount of local players.
*/
private static final int MAXIMUM_LOCAL_PLAYERS = 255;
/**
* The maximum number of players to load per cycle. This prevents the update packet from becoming too large (the
* client uses a 5000 byte buffer) and also stops old spec PCs from crashing when they login or teleport.
@@ -32,14 +36,14 @@ public final class PlayerSynchronizationTask extends SynchronizationTask {
private static final int NEW_PLAYERS_PER_CYCLE = 20;
/**
* The player.
* The Player.
*/
private final Player player;
/**
* Creates the {@link PlayerSynchronizationTask} for the specified player.
* Creates the {@link PlayerSynchronizationTask} for the specified {@link Player}.
*
* @param player The player.
* @param player The Player.
*/
public PlayerSynchronizationTask(Player player) {
this.player = player;
@@ -50,80 +54,80 @@ public final class PlayerSynchronizationTask extends SynchronizationTask {
Position lastKnownRegion = player.getLastKnownRegion();
boolean regionChanged = player.hasRegionChanged();
SynchronizationSegment segment;
SynchronizationBlockSet blockSet = player.getBlockSet();
if (blockSet.contains(ChatBlock.class)) {
blockSet = blockSet.clone();
blockSet.remove(ChatBlock.class);
}
if (player.isTeleporting() || player.hasRegionChanged()) {
segment = new TeleportSegment(blockSet, player.getPosition());
} else {
segment = new MovementSegment(blockSet, player.getDirections());
}
Position position = player.getPosition();
SynchronizationSegment segment = (player.isTeleporting() || player.hasRegionChanged()) ?
new TeleportSegment(blockSet, position) : new MovementSegment(blockSet, player.getDirections());
List<Player> localPlayers = player.getLocalPlayerList();
int oldLocalPlayers = localPlayers.size();
int oldCount = localPlayers.size();
List<SynchronizationSegment> segments = new ArrayList<>();
int distance = player.getViewingDistance();
for (Iterator<Player> it = localPlayers.iterator(); it.hasNext(); ) {
Player other = it.next();
for (Iterator<Player> iterator = localPlayers.iterator(); iterator.hasNext(); ) {
Player other = iterator.next();
if (removePlayer(other)) {
it.remove();
if (removeable(position, distance, other)) {
iterator.remove();
segments.add(new RemoveMobSegment());
} else {
segments.add(new MovementSegment(other.getBlockSet(), other.getDirections()));
}
}
int added = 0;
int added = 0, count = localPlayers.size();
MobRepository<Player> repository = player.getWorld().getPlayerRepository();
for (Player other : repository) {
if (localPlayers.size() >= 255) {
for (Player other : player.getWorld().getPlayerRepository()) {
if (count >= MAXIMUM_LOCAL_PLAYERS) {
player.flagExcessivePlayers();
break;
} else if (added >= NEW_PLAYERS_PER_CYCLE) {
break;
}
if (other != player && other.getPosition().isWithinDistance(player.getPosition(), player.getViewingDistance()) && !localPlayers.contains(other)) {
Position local = other.getPosition();
if (other != player && local.isWithinDistance(position, distance) && !localPlayers.contains(other)) {
localPlayers.add(other);
count++;
added++;
blockSet = other.getBlockSet();
if (!blockSet.contains(AppearanceBlock.class)) {
// TODO check if client has cached appearance
if (!blockSet.contains(AppearanceBlock.class)) { // TODO check if client has cached appearance
blockSet = blockSet.clone();
blockSet.add(SynchronizationBlock.createAppearanceBlock(other));
}
segments.add(new AddPlayerSegment(blockSet, other.getIndex(), other.getPosition()));
segments.add(new AddPlayerSegment(blockSet, other.getIndex(), local));
}
}
PlayerSynchronizationMessage message = new PlayerSynchronizationMessage(lastKnownRegion, player.getPosition(),
regionChanged, segment, oldLocalPlayers, segments);
PlayerSynchronizationMessage message = new PlayerSynchronizationMessage(lastKnownRegion, position,
regionChanged, segment, oldCount, segments);
player.send(message);
}
/**
* Returns whether or not the specified {@link Player} should be removed.
*
* @param position The {@link Position} of the Player being updated.
* @param other The Player being tested.
* @return {@code true} iff the specified Player should be removed.
*/
private boolean removePlayer(Player other) {
private boolean removeable(Position position, int distance, Player other) {
if (other.isTeleporting() || !other.isActive()) {
return true;
}
Position position = player.getPosition();
Position otherPosition = other.getPosition();
int distance = player.getViewingDistance();
return otherPosition.getLongestDelta(position) > distance || !otherPosition.isWithinDistance(position, distance);
}
@@ -28,6 +28,7 @@ public final class PostPlayerSynchronizationTask extends SynchronizationTask {
player.setTeleporting(false);
player.setRegionChanged(false);
player.resetBlockSet();
if (!player.isExcessivePlayersSet()) {
player.incrementViewingDistance();
} else {
@@ -29,6 +29,7 @@ public final class CompressionUtil {
*/
public static byte[] bzip2(byte[] uncompressed) throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try (BZip2CompressorOutputStream os = new BZip2CompressorOutputStream(bout, 1)) {
os.write(uncompressed);
os.finish();
@@ -40,22 +41,6 @@ public final class CompressionUtil {
}
}
/**
* Gzips the specified array.
*
* @param uncompressed The uncompressed array.
* @return The compressed array.
* @throws IOException If there is an error compressing the array.
*/
public static byte[] gzip(byte[] uncompressed) throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try (DeflaterOutputStream os = new GZIPOutputStream(bout)) {
os.write(uncompressed);
os.finish();
return bout.toByteArray();
}
}
/**
* Debzip2s the compressed array and places the result into the decompressed array.
*
@@ -90,27 +75,44 @@ public final class CompressionUtil {
}
/**
* Degzips the compressed {@link ByteBuffer} and returns the result as a byte array.
* Degzips <strong>all</strong> of the datain the specified {@link ByteBuffer}.
*
* @param compressed The compressed buffer.
* @return The decompressed array.
* @throws IOException If there is an error decompressing the buffer.
*/
public static byte[] degzip(ByteBuffer compressed) throws IOException {
byte[] data = new byte[compressed.remaining()];
compressed.get(data);
try (InputStream is = new GZIPInputStream(new ByteArrayInputStream(compressed.array()));
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
try (InputStream is = new GZIPInputStream(new ByteArrayInputStream(data)); ByteArrayOutputStream os = new ByteArrayOutputStream()) {
while (true) {
byte[] buf = new byte[1024];
int read = is.read(buf, 0, buf.length);
int read = is.read(buffer, 0, buffer.length);
if (read == -1) {
break;
}
os.write(buf, 0, read);
out.write(buffer, 0, read);
}
return os.toByteArray();
return out.toByteArray();
}
}
/**
* Gzips the specified array.
*
* @param uncompressed The uncompressed array.
* @return The compressed array.
* @throws IOException If there is an error compressing the array.
*/
public static byte[] gzip(byte[] uncompressed) throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try (DeflaterOutputStream os = new GZIPOutputStream(bout)) {
os.write(uncompressed);
os.finish();
return bout.toByteArray();
}
}
+12 -11
View File
@@ -27,20 +27,20 @@ public final class ThreadUtil {
private static final Logger LOGGER = Logger.getLogger(ThreadUtil.class.getSimpleName());
/**
* The default {@link UncaughtExceptionHandler} which raises an error from the logger with the exception and name of
* The default {@link UncaughtExceptionHandler} which raises an error from the logger with the exception and name
* of
* the specified thread the exception occurred in.
*/
private static final UncaughtExceptionHandler DEFAULT_EXCEPTION_HANDLER = (thread, exception) -> LOGGER.log(Level.SEVERE,
"Exception occured in thread " + thread.getName(), exception);
private static final UncaughtExceptionHandler DEFAULT_EXCEPTION_HANDLER =
(thread, exception) -> LOGGER.log(Level.SEVERE, "Exception in thread " + thread.getName(), exception);
/**
* Builds a {@link ThreadFactory} using the specified {@code String} name-format, normal thread priority and the
* default {@link UncaughtExceptionHandler}.
*
* @see #DEFAULT_EXCEPTION_HANDLER
*
* @param name The name-format used when creating threads, may not be {@code null}.
* @return A new {@link ThreadFactory} from the specified parameters, never {@code null}.
* @param name The name-format used when creating threads. Must not be {@code null}.
* @return The {@link ThreadFactory}. Will never be {@code null}.
* @see #DEFAULT_EXCEPTION_HANDLER
*/
public static ThreadFactory create(String name) {
return create(name, Thread.NORM_PRIORITY, DEFAULT_EXCEPTION_HANDLER);
@@ -50,9 +50,9 @@ public final class ThreadUtil {
* Builds a {@link ThreadFactory} using the specified {@code String} name-format, priority and the
* {@link #DEFAULT_EXCEPTION_HANDLER}.
*
* @param name The name-format used when creating threads, may not be {@code null}.
* @param name The name-format used when creating threads. Must not be {@code null}.
* @param priority The priority used when creating threads.
* @return A new {@link ThreadFactory} from the specified parameters, never {@code null}.
* @return The {@link ThreadFactory}. Will never be {@code null}.
*/
public static ThreadFactory create(String name, int priority) {
return create(name, priority, DEFAULT_EXCEPTION_HANDLER);
@@ -65,10 +65,11 @@ public final class ThreadUtil {
* @param name The name-format used when creating threads. Must not be {@code null}.
* @param priority The priority used when creating threads.
* @param handler The {@link UncaughtExceptionHandler} used when creating threads. Must not be {@code null}.
* @return A new {@link ThreadFactory} using the specified parameters.
* @return The {@link ThreadFactory}. Will never be {@code null}.
*/
public static ThreadFactory create(String name, int priority, UncaughtExceptionHandler handler) {
Objects.requireNonNull(priority);
Objects.requireNonNull(name, "ThreadFactory name must not be null.");
Objects.requireNonNull(handler, "UncaughtExceptionHandler must not be null.");
ThreadFactoryBuilder builder = new ThreadFactoryBuilder();
builder.setNameFormat(name);