From 46d9a6e9758c662d5345720f12ebcdaeee2cc51d Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 1 Oct 2012 00:57:14 +0200 Subject: [PATCH] Ensure that the structure compiler is thread safe. --- .../protocol/injector/StructureCache.java | 58 ++++++++++++++++--- .../reflect/compiler/BackgroundCompiler.java | 27 ++++++++- .../reflect/compiler/CompileListener.java | 17 ++++++ 3 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 ProtocolLib/src/com/comphenix/protocol/reflect/compiler/CompileListener.java diff --git a/ProtocolLib/src/com/comphenix/protocol/injector/StructureCache.java b/ProtocolLib/src/com/comphenix/protocol/injector/StructureCache.java index bc80d438..6dff5dfc 100644 --- a/ProtocolLib/src/com/comphenix/protocol/injector/StructureCache.java +++ b/ProtocolLib/src/com/comphenix/protocol/injector/StructureCache.java @@ -17,12 +17,17 @@ package com.comphenix.protocol.injector; -import java.util.HashMap; -import java.util.Map; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import net.minecraft.server.Packet; import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; +import com.comphenix.protocol.reflect.compiler.CompileListener; +import com.comphenix.protocol.reflect.compiler.CompiledStructureModifier; /** * Caches structure modifiers. @@ -30,7 +35,10 @@ import com.comphenix.protocol.reflect.StructureModifier; */ public class StructureCache { // Structure modifiers - private static Map> structureModifiers = new HashMap>(); + private static ConcurrentMap> structureModifiers = + new ConcurrentHashMap>(); + + private static Set compiling = new HashSet(); /** * Creates an empty Minecraft packet of the given ID. @@ -53,15 +61,51 @@ public class StructureCache { * @return A structure modifier. */ public static StructureModifier getStructure(int id) { + // Compile structures by default + return getStructure(id, true); + } + + /** + * Retrieve a cached structure modifier for the given packet id. + * @param id - packet ID. + * @param compile - whether or not to asynchronously compile the structure modifier. + * @return A structure modifier. + */ + public static StructureModifier getStructure(int id, boolean compile) { StructureModifier result = structureModifiers.get(id); - - // Use the vanilla class definition + + // We don't want to create this for every lookup if (result == null) { - result = new StructureModifier( + // Use the vanilla class definition + final StructureModifier value = new StructureModifier( MinecraftRegistry.getPacketClassFromID(id, true), Packet.class, true); - structureModifiers.put(id, result); + result = structureModifiers.putIfAbsent(id, value); + + // We may end up creating multiple modifiers, but we'll agree on which to use + if (result == null) { + result = value; + } + } + + // Automatically compile the structure modifier + if (compile && !(result instanceof CompiledStructureModifier)) { + // Compilation is many orders of magnitude slower than synchronization + synchronized (compiling) { + final int idCopy = id; + final BackgroundCompiler compiler = BackgroundCompiler.getInstance(); + + if (!compiling.contains(id) && compiler != null) { + compiler.scheduleCompilation(result, new CompileListener() { + @Override + public void onCompiled(StructureModifier compiledModifier) { + structureModifiers.put(idCopy, compiledModifier); + } + }); + compiling.add(id); + } + } } return result; diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java index 7bf702ab..61a42d48 100644 --- a/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/BackgroundCompiler.java @@ -80,6 +80,27 @@ public class BackgroundCompiler { @SuppressWarnings("rawtypes") public void scheduleCompilation(final Map cache, final Class key) { + @SuppressWarnings("unchecked") + final StructureModifier uncompiled = cache.get(key); + + if (uncompiled != null) { + scheduleCompilation(uncompiled, new CompileListener() { + @Override + public void onCompiled(StructureModifier compiledModifier) { + // Update cache + cache.put(key, compiledModifier); + } + }); + } + } + + /** + * Ensure that the given structure modifier is eventually compiled. + * @param uncompiled - structure modifier to compile. + * @param listener - listener responsible for responding to the compilation. + */ + public void scheduleCompilation(final StructureModifier uncompiled, final CompileListener listener) { + // Only schedule if we're enabled if (enabled && !shuttingDown) { @@ -92,11 +113,11 @@ public class BackgroundCompiler { @Override public Object call() throws Exception { - StructureModifier modifier = cache.get(key); + StructureModifier modifier = uncompiled; - // Update the cache! + // Do our compilation modifier = compiler.compile(modifier); - cache.put(key, modifier); + listener.onCompiled(modifier); // We'll also return the new structure modifier return modifier; diff --git a/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/CompileListener.java b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/CompileListener.java new file mode 100644 index 00000000..5ab92978 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/reflect/compiler/CompileListener.java @@ -0,0 +1,17 @@ +package com.comphenix.protocol.reflect.compiler; + +import com.comphenix.protocol.reflect.StructureModifier; + +/** + * Used to save the result of an compilation. + * + * @author Kristian + * @param - type of the structure modifier field. + */ +public interface CompileListener { + /** + * Invoked when a structure modifier has been successfully compiled. + * @param compiledModifier - the compiled structure modifier. + */ + public void onCompiled(StructureModifier compiledModifier); +}