From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Thinkofdeath <thethinkofdeath@gmail.com>
Date: Sun, 20 Apr 2014 13:18:55 +0100
Subject: [PATCH] Convert player skulls async


diff --git a/src/main/java/net/minecraft/server/ItemSkull.java b/src/main/java/net/minecraft/server/ItemSkull.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/ItemSkull.java
+++ b/src/main/java/net/minecraft/server/ItemSkull.java
@@ -0,0 +0,0 @@ public class ItemSkull extends Item {
         return super.a(itemstack);
     }
 
-    public boolean a(NBTTagCompound nbttagcompound) {
+    public boolean a(final NBTTagCompound nbttagcompound) { // Spigot - make final
         super.a(nbttagcompound);
         if (nbttagcompound.hasKeyOfType("SkullOwner", 8) && nbttagcompound.getString("SkullOwner").length() > 0) {
             GameProfile gameprofile = new GameProfile((UUID) null, nbttagcompound.getString("SkullOwner"));
 
-            gameprofile = TileEntitySkull.b(gameprofile);
-            nbttagcompound.set("SkullOwner", GameProfileSerializer.serialize(new NBTTagCompound(), gameprofile));
+            // Spigot start
+            TileEntitySkull.b(gameprofile, new com.google.common.base.Predicate<GameProfile>() {
+
+                @Override
+                public boolean apply(GameProfile gameprofile) {
+                    nbttagcompound.set("SkullOwner", GameProfileSerializer.serialize(new NBTTagCompound(), gameprofile));                    
+                    return false;
+                }
+            });
+            // Spigot end
             return true;
         } else {
             return false;
diff --git a/src/main/java/net/minecraft/server/TileEntitySkull.java b/src/main/java/net/minecraft/server/TileEntitySkull.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/TileEntitySkull.java
+++ b/src/main/java/net/minecraft/server/TileEntitySkull.java
@@ -0,0 +0,0 @@ import com.mojang.authlib.GameProfile;
 import com.mojang.authlib.properties.Property;
 import java.util.UUID;
 
+// Spigot start
+import com.google.common.base.Predicate;
+import com.google.common.cache.LoadingCache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.mojang.authlib.Agent;
+import com.mojang.authlib.ProfileLookupCallback;
+// Spigot end
+
 public class TileEntitySkull extends TileEntity {
 
     private int a;
     private int rotation;
     private GameProfile g = null;
+    // Spigot start
+    public static final Executor executor = Executors.newFixedThreadPool(3,
+            new ThreadFactoryBuilder()
+                    .setNameFormat("Head Conversion Thread - %1$d")
+                    .build()
+    );
+    public static final LoadingCache<String, GameProfile> skinCache = CacheBuilder.newBuilder()
+            .maximumSize( 5000 )
+            .expireAfterAccess( 60, TimeUnit.MINUTES )
+            .build( new CacheLoader<String, GameProfile>()
+            {
+                @Override
+                public GameProfile load(String key) throws Exception
+                {
+                    final GameProfile[] profiles = new GameProfile[1];
+                    ProfileLookupCallback gameProfileLookup = new ProfileLookupCallback() {
+
+                        @Override
+                        public void onProfileLookupSucceeded(GameProfile gp) {
+                            profiles[0] = gp;
+                        }
+
+                        @Override
+                        public void onProfileLookupFailed(GameProfile gp, Exception excptn) {
+                            profiles[0] = gp;
+                        }
+                    };
+
+                    MinecraftServer.getServer().getGameProfileRepository().findProfilesByNames(new String[] { key }, Agent.MINECRAFT, gameProfileLookup);
+
+                    GameProfile profile = profiles[ 0 ];
+                    if (profile == null) {
+                        UUID uuid = EntityHuman.a(new GameProfile(null, key));
+                        profile = new GameProfile(uuid, key);
+
+                        gameProfileLookup.onProfileLookupSucceeded(profile);
+                    } else
+                    {
+
+                        Property property = Iterables.getFirst( profile.getProperties().get( "textures" ), null );
+
+                        if ( property == null )
+                        {
+                            profile = MinecraftServer.getServer().aD().fillProfileProperties( profile, true );
+                        }
+                    }
+
+
+                    return profile;
+                }
+            } );
+    
+    // Spigot end
 
     public TileEntitySkull() {}
 
@@ -0,0 +0,0 @@ public class TileEntitySkull extends TileEntity {
     }
 
     private void e() {
-        this.g = b(this.g);
-        this.update();
+        // Spigot start
+        GameProfile profile = this.g;
+        setSkullType( 0 ); // Work around client bug
+        b(profile, new Predicate<GameProfile>() {
+
+            @Override
+            public boolean apply(GameProfile input) {
+                setSkullType(3); // Work around client bug
+                g = input;
+                update();
+                if (world != null) {
+                    world.notify(position);
+                }
+                return false;
+            }
+        }); 
+        // Spigot end
     }
 
-    public static GameProfile b(GameProfile gameprofile) {
+    // Spigot start - Support async lookups
+    public static void b(final GameProfile gameprofile, final Predicate<GameProfile> callback) {
         if (gameprofile != null && !UtilColor.b(gameprofile.getName())) {
             if (gameprofile.isComplete() && gameprofile.getProperties().containsKey("textures")) {
-                return gameprofile;
+                callback.apply(gameprofile);
             } else if (MinecraftServer.getServer() == null) {
-                return gameprofile;
+                callback.apply(gameprofile);
             } else {
-                GameProfile gameprofile1 = MinecraftServer.getServer().getUserCache().getProfile(gameprofile.getName());
-
-                if (gameprofile1 == null) {
-                    return gameprofile;
+                GameProfile profile = skinCache.getIfPresent(gameprofile.getName());
+                if (profile != null && Iterables.getFirst(profile.getProperties().get("textures"), (Object) null) != null) {
+                    callback.apply(profile);
                 } else {
-                    Property property = (Property) Iterables.getFirst(gameprofile1.getProperties().get("textures"), (Object) null);
-
-                    if (property == null) {
-                        gameprofile1 = MinecraftServer.getServer().aD().fillProfileProperties(gameprofile1, true);
-                    }
-
-                    return gameprofile1;
+                    executor.execute(new Runnable() {
+                        @Override
+                        public void run() {
+                            final GameProfile profile = skinCache.getUnchecked(gameprofile.getName().toLowerCase());                            
+                            MinecraftServer.getServer().processQueue.add(new Runnable() {
+                                @Override
+                                public void run() {
+                                    if (profile == null) {
+                                        callback.apply(gameprofile);
+                                    } else {
+                                        callback.apply(profile);
+                                    }
+                                }
+                            });
+                        }
+                    });
                 }
             }
         } else {
-            return gameprofile;
+            callback.apply(gameprofile);
         }
     }
+    // Spigot end    
 
     public int getSkullType() {
         return this.a;
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java
@@ -0,0 +0,0 @@ class CraftMetaSkull extends CraftMetaItem implements SkullMeta {
     }
 
     @Override
-    void applyToItem(NBTTagCompound tag) {
+    void applyToItem(final NBTTagCompound tag) { // Spigot - make final
         super.applyToItem(tag);
 
         if (hasOwner()) {
             NBTTagCompound owner = new NBTTagCompound();
             GameProfileSerializer.serialize(owner, profile);
-            tag.set(SKULL_OWNER.NBT, owner);
+            tag.set( SKULL_OWNER.NBT, owner );
+            // Spigot start - do an async lookup of the profile. 
+            // Unfortunately there is not way to refresh the holding
+            // inventory, so that responsibility is left to the user.
+            net.minecraft.server.TileEntitySkull.b(profile, new com.google.common.base.Predicate<GameProfile>() {
+
+                @Override
+                public boolean apply(GameProfile input) {
+                    NBTTagCompound owner = new NBTTagCompound();
+                    GameProfileSerializer.serialize( owner, input );
+                    tag.set( SKULL_OWNER.NBT, owner );
+                    return false;
+                }
+            });
+            // Spigot end
         }
     }
 
--