From 493c5010e886591664ca587c2bf3c0987491ab54 Mon Sep 17 00:00:00 2001 From: Major- Date: Thu, 27 Aug 2015 22:25:22 +0100 Subject: [PATCH] Cache already-decoded Archives. --- .../main/org/apollo/cache/FileDescriptor.java | 17 +++ .../org/apollo/cache/IndexedFileSystem.java | 114 ++++++++++++------ .../cache/decoder/ItemDefinitionDecoder.java | 4 +- .../apollo/cache/decoder/MapFileDecoder.java | 112 ++++++++--------- .../cache/decoder/NpcDefinitionDecoder.java | 6 +- .../decoder/ObjectDefinitionDecoder.java | 54 ++++----- 6 files changed, 179 insertions(+), 128 deletions(-) diff --git a/cache/src/main/org/apollo/cache/FileDescriptor.java b/cache/src/main/org/apollo/cache/FileDescriptor.java index c81ff23c..e5df5b4e 100644 --- a/cache/src/main/org/apollo/cache/FileDescriptor.java +++ b/cache/src/main/org/apollo/cache/FileDescriptor.java @@ -1,5 +1,7 @@ package org.apollo.cache; +import java.io.File; + /** * A class which points to a file in the cache. * @@ -28,6 +30,16 @@ public final class FileDescriptor { this.file = file; } + @Override + public boolean equals(Object obj) { + if (obj instanceof FileDescriptor) { + FileDescriptor other = (FileDescriptor) obj; + return type == other.type && file == other.file; + } + + return false; + } + /** * Gets the file id. * @@ -46,4 +58,9 @@ public final class FileDescriptor { return type; } + @Override + public int hashCode() { + return file * FileSystemConstants.ARCHIVE_COUNT + type; + } + } \ No newline at end of file diff --git a/cache/src/main/org/apollo/cache/IndexedFileSystem.java b/cache/src/main/org/apollo/cache/IndexedFileSystem.java index 9eca93c7..2435a09d 100644 --- a/cache/src/main/org/apollo/cache/IndexedFileSystem.java +++ b/cache/src/main/org/apollo/cache/IndexedFileSystem.java @@ -4,13 +4,18 @@ import java.io.Closeable; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; +import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; import java.util.zip.CRC32; import com.google.common.base.Preconditions; +import org.apollo.cache.archive.Archive; /** * A file system based on top of the operating system's file system. It consists of a data file and index files. Index @@ -20,6 +25,21 @@ import com.google.common.base.Preconditions; */ public final class IndexedFileSystem implements Closeable { + /** + * The Map that caches already-decoded Archives. + */ + private final Map cache = new HashMap<>(FileSystemConstants.ARCHIVE_COUNT); + + /** + * The index files. + */ + private final RandomAccessFile[] indices = new RandomAccessFile[256]; + + /** + * Read only flag. + */ + private final boolean readOnly; + /** * The cached CRC table. */ @@ -35,16 +55,6 @@ public final class IndexedFileSystem implements Closeable { */ private RandomAccessFile data; - /** - * The index files. - */ - private final RandomAccessFile[] indices = new RandomAccessFile[256]; - - /** - * Read only flag. - */ - private final boolean readOnly; - /** * Creates the file system with the specified base directory. * @@ -75,30 +85,26 @@ public final class IndexedFileSystem implements Closeable { } /** - * Automatically detect the layout of the specified directory. + * Gets the {@link Archive} pointed to by the specified {@link FileDescriptor}. * - * @param base The base directory. - * @throws FileNotFoundException If the data files could not be found. + * @param type The file type. + * @param file The file id. + * @return The Archive. + * @throws IOException If there is an error decoding the Archive. */ - private void detectLayout(Path base) throws FileNotFoundException { - int indexCount = 0; - for (int index = 0; index < indices.length; index++) { - Path idx = base.resolve("main_file_cache.idx" + index); - if (Files.exists(idx) && !Files.isDirectory(idx)) { - indexCount++; - indices[index] = new RandomAccessFile(idx.toFile(), readOnly ? "r" : "rw"); + public Archive getArchive(int type, int file) throws IOException { + FileDescriptor descriptor = new FileDescriptor(type, file); + Archive cached = cache.get(descriptor); + + if (cached == null) { + cached = Archive.decode(getFile(descriptor)); + + synchronized (this) { + cache.put(descriptor, cached); } } - if (indexCount <= 0) { - throw new FileNotFoundException("No index file(s) present."); - } - Path resources = base.resolve("main_file_cache.dat"); - if (Files.exists(resources) && !Files.isDirectory(resources)) { - data = new RandomAccessFile(resources.toFile(), readOnly ? "r" : "rw"); - } else { - throw new FileNotFoundException("No data file present."); - } + return cached; } /** @@ -146,6 +152,7 @@ public final class IndexedFileSystem implements Closeable { return crcTable.duplicate(); } } + throw new IllegalStateException("Cannot get CRC table from a writable file system."); } @@ -244,6 +251,42 @@ public final class IndexedFileSystem implements Closeable { return getFile(new FileDescriptor(type, file)); } + /** + * Checks if this {@link IndexedFileSystem} is read only. + * + * @return {@code true} if so, {@code false} if not. + */ + public boolean isReadOnly() { + return readOnly; + } + + /** + * Automatically detect the layout of the specified directory. + * + * @param base The base directory. + * @throws FileNotFoundException If the data files could not be found. + */ + private void detectLayout(Path base) throws FileNotFoundException { + int indexCount = 0; + for (int index = 0; index < indices.length; index++) { + Path idx = base.resolve("main_file_cache.idx" + index); + if (Files.exists(idx) && !Files.isDirectory(idx)) { + indexCount++; + indices[index] = new RandomAccessFile(idx.toFile(), readOnly ? "r" : "rw"); + } + } + if (indexCount <= 0) { + throw new FileNotFoundException("No index file(s) present."); + } + + Path resources = base.resolve("main_file_cache.dat"); + if (Files.exists(resources) && !Files.isDirectory(resources)) { + data = new RandomAccessFile(resources.toFile(), readOnly ? "r" : "rw"); + } else { + throw new FileNotFoundException("No data file present."); + } + } + /** * Gets the number of files with the specified type. * @@ -251,7 +294,7 @@ public final class IndexedFileSystem implements Closeable { * @return The number of files. * @throws IOException If there is an error getting the length of the specified index file. * @throws IndexOutOfBoundsException If {@code type} is less than 0, or greater than or equal to the amount of - * indices. + * indices. */ private int getFileCount(int type) throws IOException { Preconditions.checkElementIndex(type, indices.length, "File type out of bounds."); @@ -269,7 +312,7 @@ public final class IndexedFileSystem implements Closeable { * @return The {@link Index}. * @throws IOException If there is an error reading from the index file. * @throws IndexOutOfBoundsException If the descriptor type is less than 0, or greater than or equal to the amount - * of indices. + * of indices. */ private Index getIndex(FileDescriptor descriptor) throws IOException { int index = descriptor.getType(); @@ -290,13 +333,4 @@ public final class IndexedFileSystem implements Closeable { return Index.decode(buffer); } - /** - * Checks if this {@link IndexedFileSystem} is read only. - * - * @return {@code true} if so, {@code false} if not. - */ - public boolean isReadOnly() { - return readOnly; - } - } \ No newline at end of file diff --git a/cache/src/main/org/apollo/cache/decoder/ItemDefinitionDecoder.java b/cache/src/main/org/apollo/cache/decoder/ItemDefinitionDecoder.java index 70338ff2..ae3fb8e9 100644 --- a/cache/src/main/org/apollo/cache/decoder/ItemDefinitionDecoder.java +++ b/cache/src/main/org/apollo/cache/decoder/ItemDefinitionDecoder.java @@ -36,7 +36,7 @@ public final class ItemDefinitionDecoder { * @throws IOException If an I/O error occurs. */ public ItemDefinition[] decode() throws IOException { - Archive config = Archive.decode(fs.getFile(0, 2)); + Archive config = fs.getArchive(0, 2); ByteBuffer data = config.getEntry("obj.dat").getBuffer(); ByteBuffer idx = config.getEntry("obj.idx").getBuffer(); @@ -63,7 +63,7 @@ public final class ItemDefinitionDecoder { * @param buffer The buffer. * @return The {@link ItemDefinition}. */ - private static ItemDefinition decode(int id, ByteBuffer buffer) { + private ItemDefinition decode(int id, ByteBuffer buffer) { ItemDefinition definition = new ItemDefinition(id); while (true) { int opcode = buffer.get() & 0xFF; diff --git a/cache/src/main/org/apollo/cache/decoder/MapFileDecoder.java b/cache/src/main/org/apollo/cache/decoder/MapFileDecoder.java index a91a2cba..d1c94812 100644 --- a/cache/src/main/org/apollo/cache/decoder/MapFileDecoder.java +++ b/cache/src/main/org/apollo/cache/decoder/MapFileDecoder.java @@ -17,48 +17,21 @@ import org.apollo.cache.archive.ArchiveEntry; */ public final class MapFileDecoder { - /** - * The width (and length) of a map file, in tiles. - */ - public static final int MAP_FILE_WIDTH = 64; - - /** - * The file id of the versions archive. - */ - private static final int VERSIONS_ARCHIVE_FILE_ID = 5; - - /** - * Decodes {@link MapDefinition}s from the specified {@link IndexedFileSystem}. - * - * @param fs The IndexedFileSystem. - * @return A {@link Map} of packed coordinates to their MapDefinitions. - * @throws IOException If there is an error reading or decoding the Archive. - */ - public static Map decode(IndexedFileSystem fs) throws IOException { - Archive archive = Archive.decode(fs.getFile(0, VERSIONS_ARCHIVE_FILE_ID)); - ArchiveEntry entry = archive.getEntry("map_index"); - Map definitions = new HashMap<>(); - - ByteBuffer buffer = entry.getBuffer(); - int count = buffer.capacity() / (3 * Short.BYTES + Byte.BYTES); - - for (int times = 0; times < count; times++) { - int packed = 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)); - } - - return definitions; - } - /** * A definition for a region. */ public static final class MapDefinition { + /** + * Indicates whether or not this map is members-only. + */ + private final boolean members; + + /** + * The object file id. + */ + private final int objects; + /** * The packed coordinates. */ @@ -69,16 +42,6 @@ public final class MapFileDecoder { */ private final int terrain; - /** - * The object file id. - */ - private final int objects; - - /** - * Indicates whether or not this map is members-only. - */ - private final boolean members; - /** * Creates the {@link MapDefinition}. * @@ -94,6 +57,15 @@ public final class MapFileDecoder { this.members = members; } + /** + * Gets the id of the file containing the object data. + * + * @return The file id. + */ + public int getObjectFile() { + return objects; + } + /** * Gets the packed coordinates. * @@ -112,15 +84,6 @@ public final class MapFileDecoder { return terrain; } - /** - * Gets the id of the file containing the object data. - * - * @return The file id. - */ - public int getObjectFile() { - return objects; - } - /** * Returns whether or not this MapDefinition is for a members-only area of the world. * @@ -132,4 +95,41 @@ public final class MapFileDecoder { } + /** + * The width (and length) of a map file, in tiles. + */ + public static final int MAP_FILE_WIDTH = 64; + + /** + * The file id of the versions archive. + */ + private static final int VERSIONS_ARCHIVE_FILE_ID = 5; + + /** + * Decodes {@link MapDefinition}s from the specified {@link IndexedFileSystem}. + * + * @param fs The IndexedFileSystem. + * @return A {@link Map} of packed coordinates to their MapDefinitions. + * @throws IOException If there is an error reading or decoding the Archive. + */ + public static Map decode(IndexedFileSystem fs) throws IOException { + Archive archive = fs.getArchive(0, VERSIONS_ARCHIVE_FILE_ID); + ArchiveEntry entry = archive.getEntry("map_index"); + Map definitions = new HashMap<>(); + + ByteBuffer buffer = entry.getBuffer(); + int count = buffer.capacity() / (3 * Short.BYTES + Byte.BYTES); + + for (int times = 0; times < count; times++) { + int packed = 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)); + } + + return definitions; + } + } \ No newline at end of file diff --git a/cache/src/main/org/apollo/cache/decoder/NpcDefinitionDecoder.java b/cache/src/main/org/apollo/cache/decoder/NpcDefinitionDecoder.java index d4be4167..b67ae28f 100644 --- a/cache/src/main/org/apollo/cache/decoder/NpcDefinitionDecoder.java +++ b/cache/src/main/org/apollo/cache/decoder/NpcDefinitionDecoder.java @@ -37,7 +37,7 @@ public final class NpcDefinitionDecoder { * @throws IOException If an I/O error occurs. */ public NpcDefinition[] decode() throws IOException { - Archive config = Archive.decode(fs.getFile(0, 2)); + Archive config = fs.getArchive(0, 2); ByteBuffer data = config.getEntry("npc.dat").getBuffer(); ByteBuffer idx = config.getEntry("npc.idx").getBuffer(); @@ -64,7 +64,7 @@ public final class NpcDefinitionDecoder { * @param buffer The buffer. * @return The {@link NpcDefinition}. */ - private static NpcDefinition decode(int id, ByteBuffer buffer) { + private NpcDefinition decode(int id, ByteBuffer buffer) { NpcDefinition definition = new NpcDefinition(id); while (true) { @@ -139,7 +139,7 @@ public final class NpcDefinitionDecoder { * @param value The value. * @return -1 if {@code value} is 65,535, otherwise {@code value}. */ - private static int wrap(int value) { + private int wrap(int value) { return value == 65_535 ? -1 : value; } diff --git a/cache/src/main/org/apollo/cache/decoder/ObjectDefinitionDecoder.java b/cache/src/main/org/apollo/cache/decoder/ObjectDefinitionDecoder.java index 1838a5ef..0283b753 100644 --- a/cache/src/main/org/apollo/cache/decoder/ObjectDefinitionDecoder.java +++ b/cache/src/main/org/apollo/cache/decoder/ObjectDefinitionDecoder.java @@ -29,32 +29,6 @@ public final class ObjectDefinitionDecoder { this.fs = fs; } - /** - * 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 = Archive.decode(fs.getFile(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; - } - /** * Decodes data from the cache into an {@link ObjectDefinition}. * @@ -62,7 +36,7 @@ public final class ObjectDefinitionDecoder { * @param data The {@link ByteBuffer} containing the data. * @return The object definition. */ - public static ObjectDefinition decode(int id, ByteBuffer data) { + public ObjectDefinition decode(int id, ByteBuffer data) { ObjectDefinition definition = new ObjectDefinition(id); while (true) { int opcode = data.get() & 0xFF; @@ -137,4 +111,30 @@ 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; + } + } \ No newline at end of file