From ef659d5f26d86c85c956279bb148547a5a3e8ea5 Mon Sep 17 00:00:00 2001 From: snowleo Date: Wed, 11 Jan 2012 04:36:57 +0100 Subject: [PATCH] Better Location code for lazy loading worlds This fixes problems with worlds that are loaded after the Location object is created and should prevent memory leaks when a world is unloaded. --- .../com/earth2me/essentials/Essentials.java | 6 + .../craftbukkit/BetterLocation.java | 254 ++++++++++++++++++ .../craftbukkit/OfflineBedLocation.java | 2 +- .../essentials/storage/BukkitConstructor.java | 10 +- .../essentials/storage/YamlStorageWriter.java | 42 +-- .../earth2me/essentials/user/UserBase.java | 13 +- 6 files changed, 300 insertions(+), 27 deletions(-) create mode 100644 Essentials/src/com/earth2me/essentials/craftbukkit/BetterLocation.java diff --git a/Essentials/src/com/earth2me/essentials/Essentials.java b/Essentials/src/com/earth2me/essentials/Essentials.java index fdf47255f..9e89e8f4f 100644 --- a/Essentials/src/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/com/earth2me/essentials/Essentials.java @@ -19,6 +19,7 @@ package com.earth2me.essentials; import static com.earth2me.essentials.I18n._; import com.earth2me.essentials.api.*; +import com.earth2me.essentials.craftbukkit.BetterLocation; import com.earth2me.essentials.craftbukkit.ItemDupeFix; import com.earth2me.essentials.listener.*; import com.earth2me.essentials.perm.PermissionsHandler; @@ -230,6 +231,10 @@ public class Essentials extends JavaPlugin implements IEssentials reloadList.add(jails); pm.registerEvent(Type.ENTITY_EXPLODE, tntListener, Priority.High, this); + + pm.registerEvent(Type.WORLD_LOAD, BetterLocation.getListener(), Priority.Monitor, this); + pm.registerEvent(Type.WORLD_UNLOAD, BetterLocation.getListener(), Priority.Monitor, this); + getServer().getScheduler().scheduleAsyncRepeatingTask(this, BetterLocation.getListener(), 200, 200); final EssentialsTimer timer = new EssentialsTimer(this); getServer().getScheduler().scheduleSyncRepeatingTask(this, timer, 1, 100); @@ -249,6 +254,7 @@ public class Essentials extends JavaPlugin implements IEssentials i18n.onDisable(); Economy.setEss(null); Trade.closeLog(); + BetterLocation.cleanup(); } @Override diff --git a/Essentials/src/com/earth2me/essentials/craftbukkit/BetterLocation.java b/Essentials/src/com/earth2me/essentials/craftbukkit/BetterLocation.java new file mode 100644 index 000000000..e57a7ddde --- /dev/null +++ b/Essentials/src/com/earth2me/essentials/craftbukkit/BetterLocation.java @@ -0,0 +1,254 @@ +package com.earth2me.essentials.craftbukkit; + +import java.lang.ref.WeakReference; +import java.util.*; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.event.world.WorldLoadEvent; +import org.bukkit.event.world.WorldUnloadEvent; + + +public class BetterLocation extends Location +{ + private transient String worldName; + private static BetterLocationListener listener = new BetterLocationListener(); + + public static BetterLocationListener getListener() + { + return listener; + } + + public static void cleanup() + { + synchronized (listener.locationMap) + { + listener.locationMap.clear(); + } + } + + public BetterLocation(final String worldName, final double x, final double y, final double z) + { + super(Bukkit.getWorld(worldName), x, y, z); + this.worldName = worldName; + addToMap(this); + } + + public BetterLocation(final String worldName, final double x, final double y, + final double z, final float yaw, final float pitch) + { + super(Bukkit.getWorld(worldName), x, y, z, yaw, pitch); + this.worldName = worldName; + addToMap(this); + } + + public BetterLocation(final World world, final double x, final double y, final double z) + { + super(world, x, y, z); + if (world == null) + { + throw new WorldNotLoadedException(); + } + this.worldName = world.getName(); + addToMap(this); + } + + public BetterLocation(final World world, final double x, final double y, + final double z, final float yaw, final float pitch) + { + super(world, x, y, z, yaw, pitch); + if (world == null) + { + throw new WorldNotLoadedException(); + } + this.worldName = world.getName(); + addToMap(this); + } + + public BetterLocation(final Location location) + { + super(location.getWorld(), location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + if (location.getWorld() == null) + { + throw new WorldNotLoadedException(); + } + this.worldName = location.getWorld().getName(); + addToMap(this); + } + + @Override + public World getWorld() + { + World world = super.getWorld(); + if (world == null) + { + world = Bukkit.getWorld(worldName); + } + if (world == null) + { + throw new WorldNotLoadedException(); + } + else + { + super.setWorld(world); + } + return world; + } + + @Override + public void setWorld(final World world) + { + if (world == null) + { + throw new WorldNotLoadedException(); + } + if (!world.getName().equals(this.worldName)) + { + getListener().removeLocation(this); + this.worldName = world.getName(); + addToMap(this); + } + super.setWorld(world); + } + + public String getWorldName() + { + return worldName; + } + + private void addToMap(final BetterLocation location) + { + synchronized (listener.locationMap) + { + List> locations = listener.locationMap.get(location.getWorldName()); + if (locations == null) + { + locations = new LinkedList>(); + listener.locationMap.put(location.getWorldName(), locations); + } + locations.add(new WeakReference(location)); + } + } + + + public static class WorldNotLoadedException extends RuntimeException + { + public WorldNotLoadedException() + { + super("World is not loaded."); + } + } + + + public static class BetterLocationListener extends org.bukkit.event.world.WorldListener implements Runnable + { + private static Random random = new Random(); + private final transient Map>> locationMap = new HashMap>>(); + + @Override + public void onWorldLoad(final WorldLoadEvent event) + { + final String worldName = event.getWorld().getName(); + synchronized (locationMap) + { + final List> locations = locationMap.get(worldName); + if (locations != null) + { + for (final Iterator> it = locations.iterator(); it.hasNext();) + { + final WeakReference weakReference = it.next(); + final Location loc = weakReference.get(); + if (loc == null) + { + it.remove(); + } + else + { + loc.setWorld(event.getWorld()); + } + } + } + } + } + + @Override + public void onWorldUnload(final WorldUnloadEvent event) + { + final String worldName = event.getWorld().getName(); + synchronized (locationMap) + { + final List> locations = locationMap.get(worldName); + if (locations != null) + { + for (final Iterator> it = locations.iterator(); it.hasNext();) + { + final WeakReference weakReference = it.next(); + final Location loc = weakReference.get(); + if (loc == null) + { + it.remove(); + } + else + { + loc.setWorld(null); + } + } + } + } + } + + @Override + public void run() + { + synchronized (locationMap) + { + // Pick a world by random + final Collection>> allWorlds = locationMap.values(); + final int randomPick = (allWorlds.isEmpty() ? 0 : random.nextInt(allWorlds.size())); + List> locations = null; + final Iterator>> iterator = allWorlds.iterator(); + for (int i = 0; iterator.hasNext() && i < randomPick; i++) + { + iterator.next(); + } + if (iterator.hasNext()) + { + locations = iterator.next(); + } + if (locations != null) + { + for (final Iterator> it = locations.iterator(); it.hasNext();) + { + final WeakReference weakReference = it.next(); + final Location loc = weakReference.get(); + if (loc == null) + { + it.remove(); + } + } + } + } + } + + private void removeLocation(final BetterLocation location) + { + final String worldName = location.getWorldName(); + synchronized (locationMap) + { + final List> locations = locationMap.get(worldName); + if (locations != null) + { + for (final Iterator> it = locations.iterator(); it.hasNext();) + { + final WeakReference weakReference = it.next(); + final Location loc = weakReference.get(); + if (loc == null || loc == location) + { + it.remove(); + } + } + } + } + } + } +} diff --git a/Essentials/src/com/earth2me/essentials/craftbukkit/OfflineBedLocation.java b/Essentials/src/com/earth2me/essentials/craftbukkit/OfflineBedLocation.java index 0cb7d060a..6e0dcf94a 100644 --- a/Essentials/src/com/earth2me/essentials/craftbukkit/OfflineBedLocation.java +++ b/Essentials/src/com/earth2me/essentials/craftbukkit/OfflineBedLocation.java @@ -38,7 +38,7 @@ public class OfflineBedLocation { spawnWorld = cserver.getWorlds().get(0).getName(); } - return new Location(cserver.getWorld(spawnWorld), playerStorage.getInt("SpawnX"), playerStorage.getInt("SpawnY"), playerStorage.getInt("SpawnZ")); + return new BetterLocation(spawnWorld, playerStorage.getInt("SpawnX"), playerStorage.getInt("SpawnY"), playerStorage.getInt("SpawnZ")); } return null; } diff --git a/Essentials/src/com/earth2me/essentials/storage/BukkitConstructor.java b/Essentials/src/com/earth2me/essentials/storage/BukkitConstructor.java index a86a69c62..95cf9efa4 100644 --- a/Essentials/src/com/earth2me/essentials/storage/BukkitConstructor.java +++ b/Essentials/src/com/earth2me/essentials/storage/BukkitConstructor.java @@ -1,15 +1,14 @@ package com.earth2me.essentials.storage; import com.earth2me.essentials.Essentials; +import com.earth2me.essentials.craftbukkit.BetterLocation; import java.lang.reflect.Field; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.regex.Pattern; -import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; -import org.bukkit.World; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemStack; import org.bukkit.material.MaterialData; @@ -281,12 +280,7 @@ public class BukkitConstructor extends Constructor { return null; } - final World world = Bukkit.getWorld(worldName); - if (world == null) - { - return null; - } - return new Location(world, x, y, z, yaw, pitch); + return new BetterLocation(worldName, x, y, z, yaw, pitch); } return super.construct(node); } diff --git a/Essentials/src/com/earth2me/essentials/storage/YamlStorageWriter.java b/Essentials/src/com/earth2me/essentials/storage/YamlStorageWriter.java index fe12735ef..0b741eda1 100644 --- a/Essentials/src/com/earth2me/essentials/storage/YamlStorageWriter.java +++ b/Essentials/src/com/earth2me/essentials/storage/YamlStorageWriter.java @@ -1,5 +1,6 @@ package com.earth2me.essentials.storage; +import com.earth2me.essentials.craftbukkit.BetterLocation; import java.io.PrintWriter; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -24,12 +25,12 @@ public class YamlStorageWriter implements IStorageWriter private transient static final Pattern NON_WORD_PATTERN = Pattern.compile("\\W"); private transient final PrintWriter writer; private transient static final Yaml YAML = new Yaml(); - + public YamlStorageWriter(final PrintWriter writer) { this.writer = writer; } - + @Override public void save(final StorageObject object) { @@ -46,7 +47,7 @@ public class YamlStorageWriter implements IStorageWriter Logger.getLogger(YamlStorageWriter.class.getName()).log(Level.SEVERE, null, ex); } } - + private void writeToFile(final Object object, final int depth, final Class clazz) throws IllegalAccessException { for (Field field : clazz.getDeclaredFields()) @@ -55,7 +56,7 @@ public class YamlStorageWriter implements IStorageWriter if (Modifier.isPrivate(modifier) && !Modifier.isTransient(modifier) && !Modifier.isStatic(modifier)) { field.setAccessible(true); - + final Object data = field.get(object); if (writeKey(field, depth, data)) { @@ -86,7 +87,7 @@ public class YamlStorageWriter implements IStorageWriter } } } - + private boolean writeKey(final Field field, final int depth, final Object data) { final boolean commentPresent = writeComment(field, depth); @@ -110,7 +111,7 @@ public class YamlStorageWriter implements IStorageWriter } return false; } - + private boolean writeComment(final Field field, final int depth) { final boolean commentPresent = field.isAnnotationPresent(Comment.class); @@ -132,7 +133,7 @@ public class YamlStorageWriter implements IStorageWriter } return commentPresent; } - + private void writeCollection(final Collection data, final int depth) throws IllegalAccessException { writer.println(); @@ -163,7 +164,7 @@ public class YamlStorageWriter implements IStorageWriter } writer.println(); } - + private void writeMap(final Map data, final int depth) throws IllegalArgumentException, IllegalAccessException { writer.println(); @@ -200,7 +201,7 @@ public class YamlStorageWriter implements IStorageWriter } } } - + private void writeIndention(final int depth) { for (int i = 0; i < depth; i++) @@ -208,7 +209,7 @@ public class YamlStorageWriter implements IStorageWriter writer.print(" "); } } - + private void writeScalar(final Object data) { if (data instanceof String || data instanceof Boolean || data instanceof Number) @@ -248,7 +249,7 @@ public class YamlStorageWriter implements IStorageWriter throw new UnsupportedOperationException(); } } - + private void writeKey(final Object data) { if (data instanceof String || data instanceof Boolean || data instanceof Number) @@ -286,12 +287,12 @@ public class YamlStorageWriter implements IStorageWriter throw new UnsupportedOperationException(); } } - + private void writeMaterial(final Object data) { writer.print(data.toString().toLowerCase(Locale.ENGLISH)); } - + private void writeMaterialData(final Object data) { final MaterialData matData = (MaterialData)data; @@ -302,7 +303,7 @@ public class YamlStorageWriter implements IStorageWriter writer.print(matData.getData()); } } - + private void writeItemStack(final Object data) { final ItemStack itemStack = (ItemStack)data; @@ -315,7 +316,7 @@ public class YamlStorageWriter implements IStorageWriter writeEnchantmentLevel(entry); } } - + private void writeEnchantmentLevel(Object data) { final Entry enchLevel = (Entry)data; @@ -323,13 +324,20 @@ public class YamlStorageWriter implements IStorageWriter writer.print(':'); writer.print(enchLevel.getValue()); } - + private void writeLocation(final Location entry, final int depth) { writer.println(); writeIndention(depth); writer.print("world: "); - writeScalar(entry.getWorld().getName()); + if (entry instanceof BetterLocation) + { + writeScalar(((BetterLocation)entry).getWorldName()); + } + else + { + writeScalar(entry.getWorld().getName()); + } writeIndention(depth); writer.print("x: "); writeScalar(entry.getX()); diff --git a/Essentials/src/com/earth2me/essentials/user/UserBase.java b/Essentials/src/com/earth2me/essentials/user/UserBase.java index d3f7c0a45..c4a338ce9 100644 --- a/Essentials/src/com/earth2me/essentials/user/UserBase.java +++ b/Essentials/src/com/earth2me/essentials/user/UserBase.java @@ -4,6 +4,7 @@ import com.earth2me.essentials.Util; import com.earth2me.essentials.api.IEssentials; import com.earth2me.essentials.api.ISettings; import com.earth2me.essentials.api.InvalidNameException; +import com.earth2me.essentials.craftbukkit.BetterLocation; import com.earth2me.essentials.craftbukkit.OfflineBedLocation; import com.earth2me.essentials.storage.AsyncStorageObjectHolder; import java.io.File; @@ -31,7 +32,7 @@ public abstract class UserBase extends AsyncStorageObjectHolder implem Player.class, Entity.class, CommandSender.class, ServerOperator.class, HumanEntity.class, ConfigurationSerializable.class, LivingEntity.class, Permissible.class - }, excludes = IOfflinePlayer.class) + }, excludes = {IOfflinePlayer.class, OtherExcludes.class}) protected Player base; protected transient OfflinePlayer offlinePlayer; @@ -436,4 +437,14 @@ public abstract class UserBase extends AsyncStorageObjectHolder implem unlock(); } } + + @Override + public Location getLocation() + { + return new BetterLocation(base.getLocation()); + } + + public static interface OtherExcludes { + Location getLocation(); + } }