mirror of
https://github.com/PaperMC/Waterfall.git
synced 2024-11-28 21:25:47 +01:00
51993973fc
Upstream resolved the issue 3a0b0aa116
297 lines
8.7 KiB
Diff
297 lines
8.7 KiB
Diff
From 5eca7dd60b1298b1c5f45fb5991329bb4f634a0b Mon Sep 17 00:00:00 2001
|
|
From: Techcable <Techcable@techcable.net>
|
|
Date: Mon, 25 Apr 2016 23:46:00 -0700
|
|
Subject: [PATCH] Reduce the overhead of lots and lots of teams with the same
|
|
names
|
|
|
|
Featherboard (and other bad plugins) use persistent scoreboards (scoreboard.dat), causing every team ever to be sent to waterfall. This is bad, and takes tons of memory.
|
|
|
|
Uses String.intern() to avoid duplicating strings
|
|
Uses a sorted array to avoid the overhead of the hashset in a team.
|
|
|
|
diff --git a/api/src/main/java/io/github/waterfallmc/waterfall/utils/LowMemorySet.java b/api/src/main/java/io/github/waterfallmc/waterfall/utils/LowMemorySet.java
|
|
new file mode 100644
|
|
index 0000000..c62e3d4
|
|
--- /dev/null
|
|
+++ b/api/src/main/java/io/github/waterfallmc/waterfall/utils/LowMemorySet.java
|
|
@@ -0,0 +1,176 @@
|
|
+package io.github.waterfallmc.waterfall.utils;
|
|
+
|
|
+import lombok.*;
|
|
+
|
|
+import java.util.AbstractSet;
|
|
+import java.util.ArrayList;
|
|
+import java.util.Collection;
|
|
+import java.util.Collections;
|
|
+import java.util.Iterator;
|
|
+import java.util.List;
|
|
+import java.util.Set;
|
|
+import java.util.function.Consumer;
|
|
+import java.util.function.Predicate;
|
|
+import java.util.stream.Stream;
|
|
+
|
|
+import static com.google.common.base.Preconditions.checkNotNull;
|
|
+
|
|
+/**
|
|
+ * A set that uses a <a href=>binary search</a> to find objects in a <a href=https://en.wikipedia.org/wiki/Sorted_array>sorted array</a>.
|
|
+ * Avoids the memory cost of {@link java.util.HashSet}, while maintaining reasonable {@link Set#contains}
|
|
+ * <b>Insertions are O(N)!</b>
|
|
+ */
|
|
+public class LowMemorySet<T extends Comparable<T>> extends AbstractSet<T> implements Set<T> {
|
|
+ private final List<T> backing;
|
|
+ @Setter
|
|
+ private boolean trimAggressively;
|
|
+
|
|
+ protected LowMemorySet(List<T> list) {
|
|
+ this.backing = checkNotNull(list, "Null list");
|
|
+ this.sort(); // We have to sort any initial elements
|
|
+ this.trim(true);
|
|
+ }
|
|
+
|
|
+ public static <T extends Comparable<T>> LowMemorySet<T> create() {
|
|
+ return new LowMemorySet<>(new ArrayList<T>());
|
|
+ }
|
|
+
|
|
+ public static <T extends Comparable<T>> LowMemorySet<T> copyOf(Collection<T> c) {
|
|
+ return new LowMemorySet<>(new ArrayList<>(c));
|
|
+ }
|
|
+
|
|
+ @SuppressWarnings({"unchecked", "rawtypes"})
|
|
+ private int indexOf(Object o) {
|
|
+ return Collections.binarySearch((List) backing, o);
|
|
+ }
|
|
+
|
|
+ private void sort() {
|
|
+ backing.sort(null);
|
|
+ this.trim();
|
|
+ }
|
|
+
|
|
+ private void trim() {
|
|
+ trim(false);
|
|
+ }
|
|
+
|
|
+ private void trim(boolean force) {
|
|
+ if (backing instanceof ArrayList && force || trimAggressively) ((ArrayList<T>) backing).trimToSize();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int size() {
|
|
+ return backing.size();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean contains(Object o) {
|
|
+ return indexOf(o) >= 0;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Iterator<T> iterator() {
|
|
+ Iterator<T> backing = this.backing.iterator();
|
|
+ return new Iterator<T>() {
|
|
+ @Override
|
|
+ public boolean hasNext() {
|
|
+ return backing.hasNext();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public T next() {
|
|
+ return backing.next();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void remove() {
|
|
+ backing.remove();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void forEachRemaining(Consumer<? super T> action) {
|
|
+ backing.forEachRemaining(action);
|
|
+ }
|
|
+ };
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Object[] toArray() {
|
|
+ return backing.toArray();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public <T1> T1[] toArray(T1[] a) {
|
|
+ return backing.toArray(a);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean add(T t) {
|
|
+ if (contains(t)) return false;
|
|
+ backing.add(t);
|
|
+ this.sort();
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean remove(Object o) {
|
|
+ int index = indexOf(o);
|
|
+ if (index < 0) return false;
|
|
+ T old = backing.remove(index);
|
|
+ this.trim();
|
|
+ assert old == o;
|
|
+ return old != null;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean removeAll(Collection<?> c) {
|
|
+ int oldSize = this.size();
|
|
+ boolean result = backing.removeIf(c::contains);
|
|
+ this.trim(oldSize - this.size() > 10);
|
|
+ return result;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean retainAll(Collection<?> c) {
|
|
+ int oldSize = this.size();
|
|
+ boolean result = backing.removeIf((o) -> !c.contains(o));
|
|
+ this.trim(oldSize - this.size() > 10);
|
|
+ return result;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean addAll(Collection<? extends T> c) {
|
|
+ if (containsAll(c)) return false;
|
|
+ backing.addAll(c);
|
|
+ this.sort();
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void clear() {
|
|
+ backing.clear();
|
|
+ this.trim(true);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void forEach(Consumer<? super T> action) {
|
|
+ backing.forEach(action);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Stream<T> stream() {
|
|
+ return backing.stream();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Stream<T> parallelStream() {
|
|
+ return backing.parallelStream();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean removeIf(Predicate<? super T> filter) {
|
|
+ int oldSize = this.size();
|
|
+ boolean worked = backing.removeIf(filter);
|
|
+ this.trim(this.size() - oldSize > 10);
|
|
+ return worked;
|
|
+ }
|
|
+}
|
|
diff --git a/api/src/main/java/net/md_5/bungee/api/score/Team.java b/api/src/main/java/net/md_5/bungee/api/score/Team.java
|
|
index 4166037..f0f019b 100644
|
|
--- a/api/src/main/java/net/md_5/bungee/api/score/Team.java
|
|
+++ b/api/src/main/java/net/md_5/bungee/api/score/Team.java
|
|
@@ -1,11 +1,12 @@
|
|
package net.md_5.bungee.api.score;
|
|
|
|
+import lombok.*;
|
|
+
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
-import java.util.HashSet;
|
|
import java.util.Set;
|
|
-import lombok.Data;
|
|
-import lombok.NonNull;
|
|
+
|
|
+import io.github.waterfallmc.waterfall.utils.LowMemorySet;
|
|
|
|
@Data
|
|
public class Team
|
|
@@ -20,7 +21,7 @@ public class Team
|
|
private String nameTagVisibility;
|
|
private String collisionRule;
|
|
private byte color;
|
|
- private Set<String> players = new HashSet<>();
|
|
+ private Set<String> players = LowMemorySet.create();
|
|
|
|
public Collection<String> getPlayers()
|
|
{
|
|
@@ -29,7 +30,7 @@ public class Team
|
|
|
|
public void addPlayer(String name)
|
|
{
|
|
- players.add( name );
|
|
+ players.add(name.intern());
|
|
}
|
|
|
|
public void removePlayer(String name)
|
|
diff --git a/api/src/test/java/io/github/waterfallmc/waterfall/utils/LowMemorySetTest.java b/api/src/test/java/io/github/waterfallmc/waterfall/utils/LowMemorySetTest.java
|
|
new file mode 100644
|
|
index 0000000..5aa306a
|
|
--- /dev/null
|
|
+++ b/api/src/test/java/io/github/waterfallmc/waterfall/utils/LowMemorySetTest.java
|
|
@@ -0,0 +1,56 @@
|
|
+package io.github.waterfallmc.waterfall.utils;
|
|
+
|
|
+import com.google.common.collect.ImmutableList;
|
|
+import com.google.common.collect.ImmutableMap;
|
|
+
|
|
+import org.junit.Test;
|
|
+
|
|
+import static org.junit.Assert.*;
|
|
+
|
|
+public class LowMemorySetTest {
|
|
+
|
|
+ private static final ImmutableList<String> ELEMENTS = ImmutableList.of("test", "bob", "road", "food", "sleep", "sore-thought", "pain");
|
|
+
|
|
+ @Test
|
|
+ public void testContains() {
|
|
+ LowMemorySet<String> set = LowMemorySet.copyOf(ELEMENTS);
|
|
+ assertTrue(set.contains("test"));
|
|
+ assertTrue(set.contains("bob"));
|
|
+ assertFalse(set.contains("stupid"));
|
|
+ assertFalse(set.contains("head"));
|
|
+ }
|
|
+
|
|
+ @Test
|
|
+ public void testRemove() {
|
|
+ LowMemorySet<String> set = LowMemorySet.copyOf(ELEMENTS);
|
|
+ assertTrue(set.contains("test"));
|
|
+ set.remove("test");
|
|
+ assertFalse(set.contains("test"));
|
|
+ assertTrue(set.contains("bob"));
|
|
+ set.remove("bob");
|
|
+ assertFalse(set.contains("bob"));
|
|
+ assertTrue(ELEMENTS.size() - set.size() == 2);
|
|
+ assertTrue(set.contains("road"));
|
|
+ assertTrue(set.contains("food"));
|
|
+ assertTrue(set.contains("pain"));
|
|
+ set.removeAll(ImmutableList.of("road", "food", "pain"));
|
|
+ assertFalse(set.contains("road"));
|
|
+ assertFalse(set.contains("food"));
|
|
+ assertFalse(set.contains("pain"));
|
|
+ assertTrue(ELEMENTS.size() - set.size() == 5);
|
|
+ }
|
|
+
|
|
+ @Test
|
|
+ public void testAdd() {
|
|
+ LowMemorySet<String> set = LowMemorySet.copyOf(ELEMENTS);
|
|
+ assertFalse(set.contains("Techcable"));
|
|
+ set.add("Techcable");
|
|
+ assertTrue(set.contains("Techcable"));
|
|
+ set.addAll(ImmutableList.of("Techcable", "PhanaticD", "Dragonslayer293", "Aikar"));
|
|
+ assertTrue(set.contains("Techcable"));
|
|
+ assertTrue(set.contains("PhanaticD"));
|
|
+ assertTrue(set.contains("Aikar"));
|
|
+ assertFalse(set.contains("md_5"));
|
|
+ }
|
|
+
|
|
+}
|
|
--
|
|
2.10.0
|
|
|