Fixes a bunch of items related to making the cache smaller (#2383)

* Fixes a bunch of items related to making the cache smaller

* Fix test
This commit is contained in:
tastybento 2024-05-25 22:38:53 -07:00 committed by GitHub
parent 5e5707f2a2
commit ca15740a8c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 179 additions and 86 deletions

View File

@ -1044,9 +1044,11 @@ public class Island implements DataObject, MetaDataAble {
*
* @param flag - flag
* @param value - Use RanksManager settings, e.g. RanksManager.MEMBER
* @return this island
*/
public void setFlag(Flag flag, int value) {
public Island setFlag(Flag flag, int value) {
setFlag(flag, value, true);
return this;
}
/**
@ -1079,9 +1081,10 @@ public class Island implements DataObject, MetaDataAble {
/**
* Resets the flags to their default as set in config.yml for this island. If
* flags are missing from the config, the default hard-coded value is used and
* set
* set.
* @return this island
*/
public void setFlagsDefaults() {
public Island setFlagsDefaults() {
BentoBox plugin = BentoBox.getInstance();
Map<String, Integer> result = new HashMap<>();
plugin.getFlagsManager().getFlags().stream().filter(f -> f.getType().equals(Flag.Type.PROTECTION))
@ -1090,7 +1093,8 @@ public class Island implements DataObject, MetaDataAble {
plugin.getFlagsManager().getFlags().stream().filter(f -> f.getType().equals(Flag.Type.SETTING))
.forEach(f -> result.put(f.getID(),
plugin.getIWM().getDefaultIslandSettings(world).getOrDefault(f, f.getDefaultRank())));
this.setFlags(result);
setFlags(result);
return this;
}
/**

View File

@ -1455,7 +1455,7 @@ public class IslandsManager {
*/
public void saveAll(boolean schedule) {
if (!schedule) {
for (Island island : islandCache.getIslands()) {
for (Island island : islandCache.getCachedIslands()) {
if (island.isChanged()) {
try {
handler.saveObjectAsync(island);
@ -1468,7 +1468,7 @@ public class IslandsManager {
}
isSaveTaskRunning = true;
Queue<Island> queue = new LinkedList<>(islandCache.getIslands());
Queue<Island> queue = new LinkedList<>(islandCache.getCachedIslands());
new BukkitRunnable() {
@Override
public void run() {

View File

@ -9,11 +9,13 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.eclipse.jdt.annotation.NonNull;
@ -104,7 +106,8 @@ public class IslandCache {
return false;
}
if (addToGrid(island)) {
setIslandById(island);
// Insert a null into the map as a placeholder for cache
islandsById.put(island.getUniqueId().intern(), null);
// Only add islands to this map if they are owned
if (island.isOwned()) {
islandsByUUID.computeIfAbsent(island.getOwner(), k -> new HashSet<>()).add(island.getUniqueId());
@ -253,18 +256,40 @@ public class IslandCache {
/**
* Returns an <strong>unmodifiable collection</strong> of all the islands (even
* those who may be unowned).
* those who may be unowned). Gets them from the cache or from the database if not
* loaded.
*
* @return unmodifiable collection containing every island.
*/
@NonNull
public Collection<Island> getIslands() {
return Collections.unmodifiableCollection(islandsById.values());
List<Island> result = new ArrayList<>();
for (Entry<@NonNull String, @NonNull Island> entry : islandsById.entrySet()) {
Island island = entry.getValue() != null ? entry.getValue() : handler.loadObject(entry.getKey());
if (island != null) {
result.add(island);
}
}
return Collections.unmodifiableCollection(result);
}
/**
* Returns an <strong>unmodifiable collection</strong> of all the islands (even
* those who may be unowned) in the specified world.
* those who may be unowned) that are cached.
*
* @return unmodifiable collection containing every cached island.
*/
@NonNull
public Collection<Island> getCachedIslands() {
return islandsById.entrySet().stream().filter(en -> Objects.nonNull(en.getValue())).map(Map.Entry::getValue)
.toList();
}
/**
* Returns an <strong>unmodifiable collection</strong> of all the islands (even
* those that may be unowned) in the specified world.
* Gets islands from the cache if they have been loaded, or from the database if not
*
* @param world World of the gamemode.
* @return unmodifiable collection containing all the islands in the specified
@ -277,9 +302,16 @@ public class IslandCache {
if (overworld == null) {
return Collections.emptyList();
}
return islandsById.entrySet().stream()
.filter(entry -> overworld.equals(Util.getWorld(entry.getValue().getWorld()))) // shouldn't make NPEs
.map(Map.Entry::getValue).toList();
List<Island> result = new ArrayList<>();
for (Entry<@NonNull String, @NonNull Island> entry : islandsById.entrySet()) {
Island island = entry.getValue() != null ? entry.getValue() : handler.loadObject(entry.getKey());
if (island != null && overworld.equals(island.getWorld())) {
result.add(island);
}
}
return Collections.unmodifiableCollection(result);
}
/**
@ -294,7 +326,7 @@ public class IslandCache {
if (!islandsByUUID.containsKey(uuid)) {
return false;
}
return this.islandsByUUID.get(uuid).stream().map(islandsById::get).filter(Objects::nonNull)
return this.islandsByUUID.get(uuid).stream().map(this::getIslandById).filter(Objects::nonNull)
.filter(i -> world.equals(i.getWorld()))
.anyMatch(i -> uuid.equals(i.getOwner()));
}
@ -364,7 +396,7 @@ public class IslandCache {
}
/**
* Gets the number of islands in the cache for this world
* Gets the number of islands in this world
*
* @param world world to get the number of islands in
* @return the number of islands
@ -411,17 +443,14 @@ public class IslandCache {
}
/**
* Resets all islands in this game mode to default flag settings
* Resets all islands in this game mode to default flag settings.
*
* @param world - world
* @since 1.3.0
*/
public void resetAllFlags(World world) {
World w = Util.getWorld(world);
if (w == null) {
return;
}
islandsById.values().stream().filter(i -> i.getWorld().equals(w)).forEach(Island::setFlagsDefaults);
Bukkit.getScheduler().runTaskAsynchronously(BentoBox.getInstance(),
() -> this.getIslands(world).stream().forEach(Island::setFlagsDefaults));
}
/**
@ -432,13 +461,9 @@ public class IslandCache {
* @since 1.8.0
*/
public void resetFlag(World world, Flag flag) {
World w = Util.getWorld(world);
if (w == null) {
return;
}
int setting = BentoBox.getInstance().getIWM().getDefaultIslandFlags(w).getOrDefault(flag,
int setting = BentoBox.getInstance().getIWM().getDefaultIslandFlags(world).getOrDefault(flag,
flag.getDefaultRank());
islandsById.values().stream().filter(i -> i.getWorld().equals(w)).forEach(i -> i.setFlag(flag, setting));
this.getIslands(world).stream().forEach(i -> i.setFlag(flag, setting));
}
/**

View File

@ -13,8 +13,10 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.beans.IntrospectionException;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@ -29,6 +31,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
@ -54,6 +57,7 @@ import org.bukkit.plugin.PluginManager;
import org.bukkit.scheduler.BukkitScheduler;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@ -77,7 +81,9 @@ import world.bentobox.bentobox.Settings;
import world.bentobox.bentobox.api.configuration.WorldSettings;
import world.bentobox.bentobox.api.events.island.IslandDeleteEvent;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.Database;
import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.database.DatabaseSetup.DatabaseType;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.listeners.flags.AbstractCommonSetup;
@ -86,9 +92,10 @@ import world.bentobox.bentobox.managers.island.IslandCache;
import world.bentobox.bentobox.util.Util;
@RunWith(PowerMockRunner.class)
@PrepareForTest({ Bukkit.class, BentoBox.class, Util.class, Location.class, MultiLib.class })
@PrepareForTest({ Bukkit.class, BentoBox.class, Util.class, Location.class, MultiLib.class, DatabaseSetup.class, })
public class IslandsManagerTest extends AbstractCommonSetup {
private static AbstractDatabaseHandler<Object> h;
@Mock
private BentoBox plugin;
private UUID uuid;
@ -146,6 +153,20 @@ public class IslandsManagerTest extends AbstractCommonSetup {
// Class under test
IslandsManager im;
@SuppressWarnings("unchecked")
@BeforeClass
public static void beforeClass() throws IllegalAccessException, InvocationTargetException, IntrospectionException {
// This has to be done beforeClass otherwise the tests will interfere with each
// other
h = mock(AbstractDatabaseHandler.class);
// Database
PowerMockito.mockStatic(DatabaseSetup.class);
DatabaseSetup dbSetup = mock(DatabaseSetup.class);
when(DatabaseSetup.getDatabase()).thenReturn(dbSetup);
when(dbSetup.getHandler(any())).thenReturn(h);
when(h.saveObject(any())).thenReturn(CompletableFuture.completedFuture(true));
}
@Override
@SuppressWarnings("unchecked")
@Before
@ -603,23 +624,39 @@ public class IslandsManagerTest extends AbstractCommonSetup {
/**
* Test method for
* {@link world.bentobox.bentobox.managers.IslandsManager#getIsland(World, User)}
* @throws IntrospectionException
* @throws NoSuchMethodException
* @throws ClassNotFoundException
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws InstantiationException
*/
@Test
public void testGetIslandWorldUser() {
Island island = im.createIsland(location, user.getUniqueId());
assertEquals(island, im.getIsland(world, user));
public void testGetIslandWorldUser() throws InstantiationException, IllegalAccessException,
InvocationTargetException, ClassNotFoundException, NoSuchMethodException, IntrospectionException {
Island is = im.createIsland(location, user.getUniqueId());
when(h.loadObject(anyString())).thenReturn(is);
assertEquals(is, im.getIsland(world, user));
assertNull(im.getIsland(world, (User) null));
}
/**
* Test method for
* {@link world.bentobox.bentobox.managers.IslandsManager#getIsland(World, UUID)}.
* @throws IntrospectionException
* @throws NoSuchMethodException
* @throws ClassNotFoundException
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws InstantiationException
*/
@Test
public void testGetIsland() {
public void testGetIsland() throws InstantiationException, IllegalAccessException, InvocationTargetException,
ClassNotFoundException, NoSuchMethodException, IntrospectionException {
UUID owner = UUID.randomUUID();
Island island = im.createIsland(location, owner);
assertEquals(island, im.getIsland(world, owner));
Island is = im.createIsland(location, owner);
when(h.loadObject(anyString())).thenReturn(is);
assertEquals(is, im.getIsland(world, owner));
assertNull(im.getIsland(world, UUID.randomUUID()));
}
@ -647,12 +684,21 @@ public class IslandsManagerTest extends AbstractCommonSetup {
/**
* Test method for
* {@link world.bentobox.bentobox.managers.IslandsManager#getIslandLocation(World, UUID)}.
* @throws IntrospectionException
* @throws NoSuchMethodException
* @throws ClassNotFoundException
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws InstantiationException
*/
@Test
public void testGetIslandLocation() {
Island i = im.createIsland(location, uuid);
public void testGetIslandLocation() throws InstantiationException, IllegalAccessException,
InvocationTargetException, ClassNotFoundException, NoSuchMethodException, IntrospectionException {
// Store island in database
when(h.loadObject(anyString())).thenReturn(island);
im.createIsland(location, uuid);
assertEquals(world, im.getIslandLocation(world, uuid).getWorld());
assertEquals(i.getProtectionCenter(), im.getIslandLocation(world, uuid));
assertEquals(location, im.getIslandLocation(world, uuid));
assertNull(im.getIslandLocation(world, UUID.randomUUID()));
}
@ -719,41 +765,6 @@ public class IslandsManagerTest extends AbstractCommonSetup {
assertEquals(Optional.empty(), im.getProtectedIslandAt(location));
}
/**
* Test method for
* {@link world.bentobox.bentobox.managers.IslandsManager#getSafeHomeLocation(World, User, int)}.
*/
/*
* @Test public void testGetSafeHomeLocation() { im.setIslandCache(islandCache);
* when(island.getHome(any())).thenReturn(location);
* when(iwm.inWorld(eq(world))).thenReturn(true); assertEquals(location,
* im.getSafeHomeLocation(world, user, ""));
*
* // Change location so that it is not safe // TODO }
*/
/**
* Test method for
* {@link world.bentobox.bentobox.managers.IslandsManager#getSafeHomeLocation(World, User, int)}.
* Ensures that the method returns {@code null} if the world is not an island
* world.
*/
/*
* @Test public void testGetSafeHomeLocationWorldNotIslandWorld() {
* when(iwm.inWorld(world)).thenReturn(false);
* assertNull(im.getSafeHomeLocation(world, user, "")); }
*/
/**
* Test method for
* {@link world.bentobox.bentobox.managers.IslandsManager#getSafeHomeLocation(World, User, int)}.
*/
/*
* @Test public void testGetSafeHomeLocationNoIsland() {
* assertNull(im.getSafeHomeLocation(world, user, ""));
* verify(plugin).logWarning(eq("null player has no island in world world!")); }
*/
/**
* Test method for
* {@link world.bentobox.bentobox.managers.IslandsManager#getSpawnPoint(World)}.

View File

@ -74,7 +74,7 @@ import world.bentobox.bentobox.util.Util;
@PrepareForTest({ Bukkit.class, BentoBox.class, User.class, Util.class, Logger.class, DatabaseSetup.class, })
public class PlayersManagerTest {
private static AbstractDatabaseHandler<Object> h;
private static AbstractDatabaseHandler<Object> handler;
private Database<Players> db;
@Mock
private World end;
@ -110,13 +110,13 @@ public class PlayersManagerTest {
public static void beforeClass() throws IllegalAccessException, InvocationTargetException, IntrospectionException {
// This has to be done beforeClass otherwise the tests will interfere with each
// other
h = mock(AbstractDatabaseHandler.class);
handler = mock(AbstractDatabaseHandler.class);
// Database
PowerMockito.mockStatic(DatabaseSetup.class);
DatabaseSetup dbSetup = mock(DatabaseSetup.class);
when(DatabaseSetup.getDatabase()).thenReturn(dbSetup);
when(dbSetup.getHandler(any())).thenReturn(h);
when(h.saveObject(any())).thenReturn(CompletableFuture.completedFuture(true));
when(dbSetup.getHandler(any())).thenReturn(handler);
when(handler.saveObject(any())).thenReturn(CompletableFuture.completedFuture(true));
}
private void deleteAll(File file) throws IOException {
@ -244,15 +244,15 @@ public class PlayersManagerTest {
// Loading objects
Object players = new Players();
when(h.loadObject(anyString())).thenReturn(players);
when(handler.loadObject(anyString())).thenReturn(players);
// Set up names database
List<Object> names = new ArrayList<>();
Names name = new Names();
name.setUniqueId("tastybento");
name.setUuid(uuid);
names.add(name);
when(h.loadObjects()).thenReturn(names);
when(h.objectExists(anyString())).thenReturn(true);
when(handler.loadObjects()).thenReturn(names);
when(handler.objectExists(anyString())).thenReturn(true);
// Class under test
pm = new PlayersManager(plugin);
@ -637,7 +637,7 @@ public class PlayersManagerTest {
public void testSetPlayerName() throws IllegalAccessException, InvocationTargetException, IntrospectionException {
pm.setPlayerName(user);
// Player and names database saves
verify(h, atLeast(2)).saveObject(any());
verify(handler, atLeast(2)).saveObject(any());
}
/**

View File

@ -15,14 +15,25 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.beans.IntrospectionException;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.scheduler.BukkitScheduler;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@ -37,17 +48,20 @@ import com.google.common.collect.ImmutableSet.Builder;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.database.AbstractDatabaseHandler;
import world.bentobox.bentobox.database.Database;
import world.bentobox.bentobox.database.DatabaseSetup;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.bentobox.listeners.flags.AbstractCommonSetup;
import world.bentobox.bentobox.managers.IslandWorldManager;
import world.bentobox.bentobox.managers.IslandsManager;
import world.bentobox.bentobox.managers.RanksManagerBeforeClassTest;
import world.bentobox.bentobox.util.Util;
@RunWith(PowerMockRunner.class)
@PrepareForTest({ BentoBox.class, Util.class })
public class IslandCacheTest extends RanksManagerBeforeClassTest {
@PrepareForTest({ Bukkit.class, BentoBox.class, Util.class, Location.class, DatabaseSetup.class, })
public class IslandCacheTest extends AbstractCommonSetup {
private static AbstractDatabaseHandler<Object> handler;
@Mock
private World world;
@Mock
@ -67,6 +81,21 @@ public class IslandCacheTest extends RanksManagerBeforeClassTest {
// Database
Database<Island> db;
@SuppressWarnings("unchecked")
@BeforeClass
public static void beforeClass() throws IllegalAccessException, InvocationTargetException, IntrospectionException {
// This has to be done beforeClass otherwise the tests will interfere with each
// other
handler = mock(AbstractDatabaseHandler.class);
// Database
PowerMockito.mockStatic(DatabaseSetup.class);
DatabaseSetup dbSetup = mock(DatabaseSetup.class);
when(DatabaseSetup.getDatabase()).thenReturn(dbSetup);
when(dbSetup.getHandler(any())).thenReturn(handler);
when(handler.saveObject(any())).thenReturn(CompletableFuture.completedFuture(true));
}
@SuppressWarnings("unchecked")
@Before
public void setUp() throws Exception {
@ -110,14 +139,27 @@ public class IslandCacheTest extends RanksManagerBeforeClassTest {
// database must be mocked here
db = mock(Database.class);
when(db.loadObject(anyString())).thenReturn(island);
when(db.saveObjectAsync(any())).thenReturn(CompletableFuture.completedFuture(true));
// New cache
ic = new IslandCache(db);
}
@Override
@After
public void tearDown() {
public void tearDown() throws Exception {
super.tearDown();
Mockito.framework().clearInlineMocks();
deleteAll(new File("database"));
deleteAll(new File("database_backup"));
}
private void deleteAll(File file) throws IOException {
if (file.exists()) {
Files.walk(file.toPath()).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
}
}
/**
@ -169,9 +211,16 @@ public class IslandCacheTest extends RanksManagerBeforeClassTest {
/**
* Test for {@link IslandCache#getIslandAt(Location)}
* @throws IntrospectionException
* @throws NoSuchMethodException
* @throws ClassNotFoundException
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws InstantiationException
*/
@Test
public void testGetIslandAtLocation() {
public void testGetIslandAtLocation() throws InstantiationException, IllegalAccessException,
InvocationTargetException, ClassNotFoundException, NoSuchMethodException, IntrospectionException {
// Set coords to be in island space
when(island.inIslandSpace(any(Integer.class), any(Integer.class))).thenReturn(true);
// Set plugin
@ -256,8 +305,12 @@ public class IslandCacheTest extends RanksManagerBeforeClassTest {
@Test
public void testResetAllFlags() {
ic.addIsland(island);
BukkitScheduler scheduler = mock(BukkitScheduler.class);
PowerMockito.mockStatic(Bukkit.class, Mockito.RETURNS_MOCKS);
when(Bukkit.getScheduler()).thenReturn(scheduler);
ic.resetAllFlags(world);
verify(island).setFlagsDefaults();
verify(scheduler).runTaskAsynchronously(eq(plugin), any(Runnable.class));
}
/**