mirror of
https://github.com/PaperMC/Waterfall.git
synced 2024-11-28 13:15:31 +01:00
2187ad029c
When we changed the disconnect check to use proper concurrent behavior (using CAS), the disconnecting flag wasn't set the first time we disconnect because the OR short-circited the CAS. Upstream doesn't have this issue because they don't set the flag in the if statement (with CAS), and therefore aren't short circuted. I have no idea why this check was an OR statement in the first place, so maybe this will cause more crazy bugs.
298 lines
8.7 KiB
Diff
298 lines
8.7 KiB
Diff
From 19c64c2c2af38732cebc271aef220a5f894135aa 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..3a062b6
|
|
--- /dev/null
|
|
+++ b/api/src/main/java/io/github/waterfallmc/waterfall/utils/LowMemorySet.java
|
|
@@ -0,0 +1,177 @@
|
|
+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 ma 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") // nope
|
|
+ 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) 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>() {
|
|
+ private T last;
|
|
+
|
|
+ @Override
|
|
+ public boolean hasNext() {
|
|
+ return backing.hasNext();
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public T next() {
|
|
+ return (last = backing.next());
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void remove() {
|
|
+ LowMemorySet.this.remove(last);
|
|
+ }
|
|
+
|
|
+ @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) {
|
|
+ T old = backing.remove(indexOf(o));
|
|
+ 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
|
|
+ @SuppressWarnings("unchecked")
|
|
+ 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.8.3
|
|
|