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; package org.apollo.cache.decoder;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.apollo.cache.IndexedFileSystem; import org.apollo.cache.IndexedFileSystem;
@@ -13,47 +14,46 @@ import org.apollo.util.BufferUtil;
* *
* @author Graham * @author Graham
*/ */
public final class ItemDefinitionDecoder { public final class ItemDefinitionDecoder implements Runnable {
/** /**
* The {@link IndexedFileSystem}. * The IndexedFileSystem.
*/ */
private final IndexedFileSystem fs; 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) { public ItemDefinitionDecoder(IndexedFileSystem fs) {
this.fs = fs; this.fs = fs;
} }
/** @Override
* Decodes the item definitions. public void run() {
* try {
* @return The item definitions. Archive config = fs.getArchive(0, 2);
* @throws IOException If an I/O error occurs. ByteBuffer data = config.getEntry("obj.dat").getBuffer();
*/ ByteBuffer idx = config.getEntry("obj.idx").getBuffer();
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();
int count = idx.getShort(), index = 2; int count = idx.getShort(), index = 2;
int[] indices = new int[count]; int[] indices = new int[count];
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
indices[i] = index; indices[i] = index;
index += idx.getShort(); 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); int count = buffer.capacity() / (3 * Short.BYTES + Byte.BYTES);
for (int times = 0; times < count; times++) { for (int times = 0; times < count; times++) {
int packed = buffer.getShort() & 0xFFFF; int id = buffer.getShort() & 0xFFFF;
int terrain = buffer.getShort() & 0xFFFF; int terrain = buffer.getShort() & 0xFFFF;
int objects = buffer.getShort() & 0xFFFF; int objects = buffer.getShort() & 0xFFFF;
boolean members = buffer.get() == 1; 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; return definitions;
@@ -1,6 +1,7 @@
package org.apollo.cache.decoder; package org.apollo.cache.decoder;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays; import java.util.Arrays;
@@ -14,47 +15,47 @@ import org.apollo.util.BufferUtil;
* *
* @author Major * @author Major
*/ */
public final class NpcDefinitionDecoder { public final class NpcDefinitionDecoder implements Runnable {
/** /**
* The {@link IndexedFileSystem}. * The IndexedFileSystem.
*/ */
private final IndexedFileSystem fs; 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) { public NpcDefinitionDecoder(IndexedFileSystem fs) {
this.fs = fs; this.fs = fs;
} }
/** @Override
* Decodes the npc definitions. public void run() {
* try {
* @return An array of all parsed npc definitions. Archive config = fs.getArchive(0, 2);
* @throws IOException If an I/O error occurs. ByteBuffer data = config.getEntry("npc.dat").getBuffer();
*/ ByteBuffer idx = config.getEntry("npc.idx").getBuffer();
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();
int count = idx.getShort(), index = 2; int count = idx.getShort(), index = 2;
int[] indices = new int[count]; int[] indices = new int[count];
for (int i = 0; i < count; i++) {
indices[i] = index; for (int i = 0; i < count; i++) {
index += idx.getShort(); 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) { } else if (opcode == 1) {
int length = buffer.get() & 0xFF; int length = buffer.get() & 0xFF;
int[] models = new int[length]; int[] models = new int[length];
for (int i = 0; i < length; i++) { for (int index = 0; index < length; index++) {
models[i] = buffer.getShort(); models[index] = buffer.getShort();
} }
} else if (opcode == 2) { } else if (opcode == 2) {
definition.setName(BufferUtil.readString(buffer)); definition.setName(BufferUtil.readString(buffer));
@@ -92,24 +93,27 @@ public final class NpcDefinitionDecoder {
definition definition
.setWalkAnimations(buffer.getShort(), buffer.getShort(), buffer.getShort(), buffer.getShort()); .setWalkAnimations(buffer.getShort(), buffer.getShort(), buffer.getShort(), buffer.getShort());
} else if (opcode >= 30 && opcode < 40) { } else if (opcode >= 30 && opcode < 40) {
String str = BufferUtil.readString(buffer); String action = BufferUtil.readString(buffer);
if (str.equals("hidden")) { if (action.equals("hidden")) {
str = null; action = null;
} }
definition.setInteraction(opcode - 30, str);
definition.setInteraction(opcode - 30, action);
} else if (opcode == 40) { } else if (opcode == 40) {
int length = buffer.get() & 0xFF; int length = buffer.get() & 0xFF;
int[] originalColours = new int[length]; int[] originalColours = new int[length];
int[] replacementColours = new int[length]; int[] replacementColours = new int[length];
for (int i = 0; i < length; i++) {
originalColours[i] = buffer.getShort(); for (int index = 0; index < length; index++) {
replacementColours[i] = buffer.getShort(); originalColours[index] = buffer.getShort();
replacementColours[index] = buffer.getShort();
} }
} else if (opcode == 60) { } else if (opcode == 60) {
int length = buffer.get() & 0xFF; int length = buffer.get() & 0xFF;
int[] additionalModels = new int[length]; 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) { } else if (opcode >= 90 && opcode <= 92) {
buffer.getShort(); // Dummy buffer.getShort(); // Dummy
@@ -1,10 +1,12 @@
package org.apollo.cache.decoder; package org.apollo.cache.decoder;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.apollo.cache.IndexedFileSystem; import org.apollo.cache.IndexedFileSystem;
import org.apollo.cache.archive.Archive; import org.apollo.cache.archive.Archive;
import org.apollo.cache.def.ItemDefinition;
import org.apollo.cache.def.ObjectDefinition; import org.apollo.cache.def.ObjectDefinition;
import org.apollo.util.BufferUtil; import org.apollo.util.BufferUtil;
@@ -13,15 +15,15 @@ import org.apollo.util.BufferUtil;
* *
* @author Major * @author Major
*/ */
public final class ObjectDefinitionDecoder { public final class ObjectDefinitionDecoder implements Runnable {
/** /**
* The {@link IndexedFileSystem}. * The IndexedFileSystem.
*/ */
private final IndexedFileSystem fs; private final IndexedFileSystem fs;
/** /**
* Creates the decoder. * Creates the ObjectDefinitionDecoder.
* *
* @param fs The {@link IndexedFileSystem}. * @param fs The {@link IndexedFileSystem}.
*/ */
@@ -29,6 +31,32 @@ public final class ObjectDefinitionDecoder {
this.fs = fs; 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}. * 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. * @param data The {@link ByteBuffer} containing the data.
* @return The object definition. * @return The object definition.
*/ */
public ObjectDefinition decode(int id, ByteBuffer data) { private ObjectDefinition decode(int id, ByteBuffer data) {
ObjectDefinition definition = new ObjectDefinition(id); ObjectDefinition definition = new ObjectDefinition(id);
while (true) { while (true) {
int opcode = data.get() & 0xFF; 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. * Initialises the class with the specified set of definitions.
* *
* @param definitions The 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) { public static void init(NpcDefinition[] definitions) {
NpcDefinition.definitions = definitions; NpcDefinition.definitions = definitions;
for (int id = 0; id < definitions.length; id++) { for (int id = 0; id < definitions.length; id++) {
NpcDefinition def = definitions[id]; NpcDefinition def = definitions[id];
if (def.getId() != 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; package org.apollo.game.fs.decoder;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -11,6 +12,7 @@ import java.util.function.Predicate;
import org.apollo.cache.IndexedFileSystem; import org.apollo.cache.IndexedFileSystem;
import org.apollo.cache.decoder.MapFileDecoder; import org.apollo.cache.decoder.MapFileDecoder;
import org.apollo.cache.decoder.MapFileDecoder.MapDefinition; import org.apollo.cache.decoder.MapFileDecoder.MapDefinition;
import org.apollo.cache.decoder.ObjectDefinitionDecoder;
import org.apollo.cache.def.ObjectDefinition; import org.apollo.cache.def.ObjectDefinition;
import org.apollo.game.model.Position; import org.apollo.game.model.Position;
import org.apollo.game.model.World; import org.apollo.game.model.World;
@@ -31,7 +33,7 @@ import com.google.common.collect.Iterables;
* @author Ryley * @author Ryley
* @author Major * @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. * 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; private final RegionRepository regions;
/**
* The World to place the objects in.
*/
private final World world;
/** /**
* Creates the GameObjectDecoder. * Creates the GameObjectDecoder.
* *
* @param fs The {@link IndexedFileSystem}. * @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.fs = fs;
this.regions = regions; this.world = world;
regions = world.getRegionRepository();
} }
/** @Override
* Decodes the GameObjects from their MapDefinitions. public void run() {
* ObjectDefinitionDecoder decoder = new ObjectDefinitionDecoder(fs);
* @param world The {@link World} containing the StaticGameObjects. decoder.run();
* @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);
for (Entry<Integer, MapDefinition> entry : definitions.entrySet()) { try {
MapDefinition definition = entry.getValue(); Map<Integer, MapDefinition> definitions = MapFileDecoder.decode(fs);
int packed = definition.getPackedCoordinates(); for (Entry<Integer, MapDefinition> entry : definitions.entrySet()) {
int x = (packed >> 8 & 0xFF) * 64; MapDefinition definition = entry.getValue();
int y = (packed & 0xFF) * 64;
ByteBuffer objects = fs.getFile(4, definition.getObjectFile()); int packed = definition.getPackedCoordinates();
ByteBuffer decompressed = ByteBuffer.wrap(CompressionUtil.degzip(objects)); int x = (packed >> 8 & 0xFF) * 64;
decodeObjects(world, decompressed, x, y); int y = (packed & 0xFF) * 64;
ByteBuffer terrain = fs.getFile(4, definition.getTerrainFile()); ByteBuffer objects = fs.getFile(4, definition.getObjectFile());
decompressed = ByteBuffer.wrap(CompressionUtil.degzip(terrain)); ByteBuffer decompressed = ByteBuffer.wrap(CompressionUtil.degzip(objects));
decodeTerrain(decompressed, x, y); 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(); int x = position.getX(), y = position.getY(), height = position.getHeight();
CollisionMatrix matrix = region.getMatrix(height); CollisionMatrix matrix = region.getMatrix(height);
boolean block = false; boolean block = false;
if (type == ObjectType.FLOOR_DECORATION.getValue() && definition.isInteractive()) { if (type == ObjectType.FLOOR_DECORATION.getValue() && definition.isInteractive()) {
block = true; 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(); && 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(); && value < ObjectType.FLOOR_DECORATION.getValue();
if (walls.test(type) || roofs.test(type)) { if (walls.test(type) || roofs.test(type) || type == ObjectType.INTERACTABLE.getValue() && definition.isSolid()) {
block = true;
}
if (type == 10 && definition.isSolid()) {
block = true; block = true;
} }
@@ -144,11 +150,13 @@ public final class GameObjectDecoder {
Position nextPosition = new Position(nextLocalX, nextLocalY); Position nextPosition = new Position(nextLocalX, nextLocalY);
Region next = regions.fromPosition(nextPosition); Region next = regions.fromPosition(nextPosition);
int nextX = nextPosition.getX() % Region.SIZE + dx, nextY = nextPosition.getY() % Region.SIZE int nextX = nextPosition.getX() % Region.SIZE + dx;
+ dy; int nextY = nextPosition.getY() % Region.SIZE + dy;
if (nextX > 7) { if (nextX > 7) {
nextX -= 7; nextX -= 7;
} }
if (nextY > 7) { if (nextY > 7) {
nextY -= 7; nextY -= 7;
} }
@@ -173,12 +181,11 @@ public final class GameObjectDecoder {
Region region = regions.fromPosition(position); Region region = regions.fromPosition(position);
int x = position.getX(), y = position.getY(), height = position.getHeight(); int x = position.getX(), y = position.getY(), height = position.getHeight();
CollisionMatrix current = region.getMatrix(height);
boolean block = false; boolean block = false;
if ((attributes & BLOCKED_TILE) != 0) { if ((attributes & BLOCKED_TILE) != 0) {
block = true; block = true;
} }
if ((attributes & BRIDGE_TILE) != 0) { if ((attributes & BRIDGE_TILE) != 0) {
if (height > 0) { if (height > 0) {
block = true; block = true;
@@ -188,7 +195,7 @@ public final class GameObjectDecoder {
if (block) { if (block) {
int localX = x % Region.SIZE, localY = y % Region.SIZE; 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; int attributes = 0;
while (true) { while (true) {
int attributeId = buffer.get() & 0xFF; int attributeId = buffer.get() & 0xFF;
if (attributeId == 0) { if (attributeId == 0) {
decodeAttributes(attributes, position); decodeAttributes(attributes, position);
break; 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; package org.apollo.game.io;
import java.io.BufferedInputStream;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UncheckedIOException;
import org.apollo.cache.def.EquipmentDefinition; 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 * @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; 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) { public EquipmentDefinitionParser(InputStream is) {
this.is = is; this.is = is;
} }
/** @Override
* Parses the input stream. public void run() {
* try (DataInputStream in = new DataInputStream(is)) {
* @return The equipment definition array. int count = in.readShort() & 0xFFFF;
* @throws IOException If an I/O error occurs. EquipmentDefinition[] definitions = new EquipmentDefinition[count];
*/
public EquipmentDefinition[] parse() throws IOException {
DataInputStream dis = new DataInputStream(is);
int count = dis.readShort() & 0xFFFF; for (int id = 0; id < count; id++) {
EquipmentDefinition[] definitions = new EquipmentDefinition[count]; 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++) { EquipmentDefinition definition = new EquipmentDefinition(id);
int slot = dis.readByte() & 0xFF; definition.setLevels(attack, strength, defence, ranged, magic);
if (slot != 0xFF) { definition.setSlot(slot);
boolean twoHanded = dis.readBoolean(); definition.setFlags(twoHanded, fullBody, fullHat, fullMask);
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); definitions[id] = definition;
definition.setLevels(attack, strength, defence, ranged, magic); }
definition.setSlot(slot);
definition.setFlags(twoHanded, fullBody, fullHat, fullMask);
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; 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.HashMap;
import java.util.Map; import java.util.Map;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.apollo.Server;
import org.apollo.Service; import org.apollo.Service;
import org.apollo.cache.IndexedFileSystem; import org.apollo.cache.IndexedFileSystem;
import org.apollo.cache.decoder.ItemDefinitionDecoder; import org.apollo.cache.decoder.ItemDefinitionDecoder;
import org.apollo.cache.decoder.NpcDefinitionDecoder; import org.apollo.cache.decoder.NpcDefinitionDecoder;
import org.apollo.cache.decoder.ObjectDefinitionDecoder; 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.command.CommandDispatcher;
import org.apollo.game.fs.decoder.GameObjectDecoder; import org.apollo.game.fs.decoder.GameObjectDecoder;
import org.apollo.game.fs.decoder.SynchronousDecoder;
import org.apollo.game.io.EquipmentDefinitionParser; import org.apollo.game.io.EquipmentDefinitionParser;
import org.apollo.game.model.area.Region; import org.apollo.game.model.area.Region;
import org.apollo.game.model.area.RegionRepository; 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.MobRepository;
import org.apollo.game.model.entity.Npc; import org.apollo.game.model.entity.Npc;
import org.apollo.game.model.entity.Player; 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.Event;
import org.apollo.game.model.event.EventListener; import org.apollo.game.model.event.EventListener;
import org.apollo.game.model.event.EventListenerChainSet; import org.apollo.game.model.event.EventListenerChainSet;
@@ -87,11 +80,6 @@ public final class World {
*/ */
private final EventListenerChainSet events = new EventListenerChainSet(); private final EventListenerChainSet events = new EventListenerChainSet();
/**
* The ScheduledTask that moves Npcs.
*/
private NpcMovementTask npcMovement;
/** /**
* The {@link MobRepository} of {@link Npc}s. * The {@link MobRepository} of {@link Npc}s.
*/ */
@@ -107,32 +95,30 @@ public final class World {
*/ */
private final Map<Long, Player> players = new HashMap<>(); private final Map<Long, Player> players = new HashMap<>();
/**
* The {@link PluginManager}.
*/
private PluginManager pluginManager;
/** /**
* This world's {@link RegionRepository}. * This world's {@link RegionRepository}.
*/ */
private final RegionRepository regions = RegionRepository.immutable(); private final RegionRepository regions = RegionRepository.immutable();
/**
* The release number (i.e. version) of this world.
*/
private int releaseNumber;
/** /**
* The scheduler. * The scheduler.
*/ */
private final Scheduler scheduler = new 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. * Gets the command dispatcher.
@@ -210,32 +196,11 @@ public final class World {
public void init(int release, IndexedFileSystem fs, PluginManager manager) throws Exception { public void init(int release, IndexedFileSystem fs, PluginManager manager) throws Exception {
releaseNumber = release; releaseNumber = release;
ItemDefinitionDecoder itemDecoder = new ItemDefinitionDecoder(fs); SynchronousDecoder decoder = new SynchronousDecoder(new ItemDefinitionDecoder(fs),
ItemDefinition[] items = itemDecoder.decode(); new NpcDefinitionDecoder(fs), new GameObjectDecoder(fs, this),
ItemDefinition.init(items); EquipmentDefinitionParser.fromFile("data/equipment-" + release + "" + ".dat"));
logger.fine("Loaded " + items.length + " item definitions.");
try (InputStream is = new BufferedInputStream(new FileInputStream("data/equipment-" + release + ".dat"))) { decoder.block();
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.");
npcMovement = new NpcMovementTask(regions); // Must be exactly here because of ordering issues. npcMovement = new NpcMovementTask(regions); // Must be exactly here because of ordering issues.
scheduler.schedule(npcMovement); scheduler.schedule(npcMovement);
@@ -291,6 +256,7 @@ public final class World {
} else { } else {
logger.warning("Failed to register npc, repository capacity reached: [count=" + npcRepository.size() + "]"); logger.warning("Failed to register npc, repository capacity reached: [count=" + npcRepository.size() + "]");
} }
return success; 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 { 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 * The maximum amount of local npcs.
* uses a 5000 byte buffer) and also stops old spec PCs from crashing when they login or teleport. */
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; private static final int NEW_NPCS_PER_CYCLE = 20;
@@ -42,41 +47,49 @@ public final class NpcSynchronizationTask extends SynchronizationTask {
@Override @Override
public void run() { public void run() {
List<Npc> localNpcs = player.getLocalNpcList(); List<Npc> locals = player.getLocalNpcList();
List<SynchronizationSegment> segments = new ArrayList<>(); List<SynchronizationSegment> segments = new ArrayList<>();
int oldLocalNpcs = localNpcs.size();
int originalCount = locals.size();
final Position playerPosition = player.getPosition(); final Position playerPosition = player.getPosition();
for (Iterator<Npc> it = localNpcs.iterator(); it.hasNext();) { int distance = player.getViewingDistance();
Npc npc = it.next(); for (Iterator<Npc> iterator = locals.iterator(); iterator.hasNext(); ) {
if (!npc.isActive() || npc.isTeleporting() || npc.getPosition().getLongestDelta(playerPosition) > player.getViewingDistance() || !npc.getPosition().isWithinDistance(playerPosition, player.getViewingDistance())) { Npc npc = iterator.next();
it.remove(); Position position = npc.getPosition();
if (!npc.isActive() || npc.isTeleporting() || position.getLongestDelta(playerPosition) > distance
|| !position.isWithinDistance(playerPosition, distance)) {
iterator.remove();
segments.add(new RemoveMobSegment()); segments.add(new RemoveMobSegment());
} else { } else {
segments.add(new MovementSegment(npc.getBlockSet(), npc.getDirections())); segments.add(new MovementSegment(npc.getBlockSet(), npc.getDirections()));
} }
} }
int added = 0; int added = 0, count = locals.size();
for (Npc npc : player.getWorld().getNpcRepository()) { for (Npc npc : player.getWorld().getNpcRepository()) {
if (localNpcs.size() >= 255) { if (count >= MAXIMUM_LOCAL_NPCS) {
player.flagExcessiveNpcs(); player.flagExcessiveNpcs();
break; break;
} else if (added >= NEW_NPCS_PER_CYCLE) { } else if (added >= NEW_NPCS_PER_CYCLE) {
break; break;
} }
Position npcPosition = npc.getPosition(); Position position = npc.getPosition();
if (npcPosition.isWithinDistance(playerPosition, player.getViewingDistance()) && !localNpcs.contains(npc)) { if (position.isWithinDistance(playerPosition, distance) && !locals.contains(npc)) {
localNpcs.add(npc); locals.add(npc);
count++;
added++; added++;
npc.turnTo(npc.getFacingPosition()); 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.message.impl.PlayerSynchronizationMessage;
import org.apollo.game.model.Position; import org.apollo.game.model.Position;
import org.apollo.game.model.entity.MobRepository;
import org.apollo.game.model.entity.Player; import org.apollo.game.model.entity.Player;
import org.apollo.game.sync.block.AppearanceBlock; import org.apollo.game.sync.block.AppearanceBlock;
import org.apollo.game.sync.block.ChatBlock; import org.apollo.game.sync.block.ChatBlock;
@@ -25,6 +24,11 @@ import org.apollo.game.sync.seg.TeleportSegment;
*/ */
public final class PlayerSynchronizationTask extends SynchronizationTask { 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 * 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. * 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; private static final int NEW_PLAYERS_PER_CYCLE = 20;
/** /**
* The player. * The Player.
*/ */
private final Player 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) { public PlayerSynchronizationTask(Player player) {
this.player = player; this.player = player;
@@ -50,80 +54,80 @@ public final class PlayerSynchronizationTask extends SynchronizationTask {
Position lastKnownRegion = player.getLastKnownRegion(); Position lastKnownRegion = player.getLastKnownRegion();
boolean regionChanged = player.hasRegionChanged(); boolean regionChanged = player.hasRegionChanged();
SynchronizationSegment segment;
SynchronizationBlockSet blockSet = player.getBlockSet(); SynchronizationBlockSet blockSet = player.getBlockSet();
if (blockSet.contains(ChatBlock.class)) { if (blockSet.contains(ChatBlock.class)) {
blockSet = blockSet.clone(); blockSet = blockSet.clone();
blockSet.remove(ChatBlock.class); blockSet.remove(ChatBlock.class);
} }
if (player.isTeleporting() || player.hasRegionChanged()) { Position position = player.getPosition();
segment = new TeleportSegment(blockSet, player.getPosition());
} else { SynchronizationSegment segment = (player.isTeleporting() || player.hasRegionChanged()) ?
segment = new MovementSegment(blockSet, player.getDirections()); new TeleportSegment(blockSet, position) : new MovementSegment(blockSet, player.getDirections());
}
List<Player> localPlayers = player.getLocalPlayerList(); List<Player> localPlayers = player.getLocalPlayerList();
int oldLocalPlayers = localPlayers.size(); int oldCount = localPlayers.size();
List<SynchronizationSegment> segments = new ArrayList<>(); List<SynchronizationSegment> segments = new ArrayList<>();
int distance = player.getViewingDistance();
for (Iterator<Player> it = localPlayers.iterator(); it.hasNext(); ) { for (Iterator<Player> iterator = localPlayers.iterator(); iterator.hasNext(); ) {
Player other = it.next(); Player other = iterator.next();
if (removePlayer(other)) { if (removeable(position, distance, other)) {
it.remove(); iterator.remove();
segments.add(new RemoveMobSegment()); segments.add(new RemoveMobSegment());
} else { } else {
segments.add(new MovementSegment(other.getBlockSet(), other.getDirections())); 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 : player.getWorld().getPlayerRepository()) {
for (Player other : repository) { if (count >= MAXIMUM_LOCAL_PLAYERS) {
if (localPlayers.size() >= 255) {
player.flagExcessivePlayers(); player.flagExcessivePlayers();
break; break;
} else if (added >= NEW_PLAYERS_PER_CYCLE) { } else if (added >= NEW_PLAYERS_PER_CYCLE) {
break; 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); localPlayers.add(other);
count++;
added++; added++;
blockSet = other.getBlockSet(); blockSet = other.getBlockSet();
if (!blockSet.contains(AppearanceBlock.class)) { if (!blockSet.contains(AppearanceBlock.class)) { // TODO check if client has cached appearance
// TODO check if client has cached appearance
blockSet = blockSet.clone(); blockSet = blockSet.clone();
blockSet.add(SynchronizationBlock.createAppearanceBlock(other)); 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(), PlayerSynchronizationMessage message = new PlayerSynchronizationMessage(lastKnownRegion, position,
regionChanged, segment, oldLocalPlayers, segments); regionChanged, segment, oldCount, segments);
player.send(message); player.send(message);
} }
/** /**
* Returns whether or not the specified {@link Player} should be removed. * 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. * @param other The Player being tested.
* @return {@code true} iff the specified Player should be removed. * @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()) { if (other.isTeleporting() || !other.isActive()) {
return true; return true;
} }
Position position = player.getPosition();
Position otherPosition = other.getPosition(); Position otherPosition = other.getPosition();
int distance = player.getViewingDistance();
return otherPosition.getLongestDelta(position) > distance || !otherPosition.isWithinDistance(position, distance); return otherPosition.getLongestDelta(position) > distance || !otherPosition.isWithinDistance(position, distance);
} }
@@ -28,6 +28,7 @@ public final class PostPlayerSynchronizationTask extends SynchronizationTask {
player.setTeleporting(false); player.setTeleporting(false);
player.setRegionChanged(false); player.setRegionChanged(false);
player.resetBlockSet(); player.resetBlockSet();
if (!player.isExcessivePlayersSet()) { if (!player.isExcessivePlayersSet()) {
player.incrementViewingDistance(); player.incrementViewingDistance();
} else { } else {
@@ -29,6 +29,7 @@ public final class CompressionUtil {
*/ */
public static byte[] bzip2(byte[] uncompressed) throws IOException { public static byte[] bzip2(byte[] uncompressed) throws IOException {
ByteArrayOutputStream bout = new ByteArrayOutputStream(); ByteArrayOutputStream bout = new ByteArrayOutputStream();
try (BZip2CompressorOutputStream os = new BZip2CompressorOutputStream(bout, 1)) { try (BZip2CompressorOutputStream os = new BZip2CompressorOutputStream(bout, 1)) {
os.write(uncompressed); os.write(uncompressed);
os.finish(); 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. * 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. * @param compressed The compressed buffer.
* @return The decompressed array. * @return The decompressed array.
* @throws IOException If there is an error decompressing the buffer. * @throws IOException If there is an error decompressing the buffer.
*/ */
public static byte[] degzip(ByteBuffer compressed) throws IOException { public static byte[] degzip(ByteBuffer compressed) throws IOException {
byte[] data = new byte[compressed.remaining()]; try (InputStream is = new GZIPInputStream(new ByteArrayInputStream(compressed.array()));
compressed.get(data); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
try (InputStream is = new GZIPInputStream(new ByteArrayInputStream(data)); ByteArrayOutputStream os = new ByteArrayOutputStream()) {
while (true) { while (true) {
byte[] buf = new byte[1024]; int read = is.read(buffer, 0, buffer.length);
int read = is.read(buf, 0, buf.length);
if (read == -1) { if (read == -1) {
break; 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()); 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. * the specified thread the exception occurred in.
*/ */
private static final UncaughtExceptionHandler DEFAULT_EXCEPTION_HANDLER = (thread, exception) -> LOGGER.log(Level.SEVERE, private static final UncaughtExceptionHandler DEFAULT_EXCEPTION_HANDLER =
"Exception occured in thread " + thread.getName(), exception); (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 * Builds a {@link ThreadFactory} using the specified {@code String} name-format, normal thread priority and the
* default {@link UncaughtExceptionHandler}. * default {@link UncaughtExceptionHandler}.
*
* @see #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}.
* @return A new {@link ThreadFactory} from the specified parameters, never {@code null}. * @return The {@link ThreadFactory}. Will never be {@code null}.
* @see #DEFAULT_EXCEPTION_HANDLER
*/ */
public static ThreadFactory create(String name) { public static ThreadFactory create(String name) {
return create(name, Thread.NORM_PRIORITY, DEFAULT_EXCEPTION_HANDLER); 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 * Builds a {@link ThreadFactory} using the specified {@code String} name-format, priority and the
* {@link #DEFAULT_EXCEPTION_HANDLER}. * {@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. * @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) { public static ThreadFactory create(String name, int priority) {
return create(name, priority, DEFAULT_EXCEPTION_HANDLER); 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 name The name-format used when creating threads. Must not be {@code null}.
* @param priority The priority used when creating threads. * @param priority The priority used when creating threads.
* @param handler The {@link UncaughtExceptionHandler} used when creating threads. Must not be {@code null}. * @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) { 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(); ThreadFactoryBuilder builder = new ThreadFactoryBuilder();
builder.setNameFormat(name); builder.setNameFormat(name);