diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 3dd17f94..672eb932 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -10,10 +10,7 @@ assignees: AxalotLDev, FurryMileon
### ๐ Describe the bug
A clear and concise description of what the bug is.
-### ๐ Reproducibility
-- [ ] This bug is not consistently reproducible
-
-### ๐ Steps to reproduce *(skip if checked above)*
+### ๐ Steps to reproduce*
1.
2.
3.
@@ -26,9 +23,10 @@ What actually happened?
### ๐ฅ๏ธ Environment
- ๐ง Async version:
+- ๐ฎ Minecraft enviroment (Client or Server):
- ๐ฎ Minecraft version:
- โ๏ธ Modloader & version:
-- ๐ฆ Other mods installed (if relevant):
+- ๐ฆ Other mods installed:
### ๐ Logs
Please attach your `latest.log` or crash report if available.
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..cbf125da
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,3 @@
+- Fixed incompatibility with VMP
+- Fixed an error receiving a remote entity's packet
+- Config fixes (JordanOlivet)
\ No newline at end of file
diff --git a/README.md b/README.md
index 5399318d..f9034cbb 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,11 @@
-
-
# Async - Minecraft Entity Multi-Threading Mod โ๏ธ
-
-[](https://modrinth.com/mod/async)
-[](https://discord.com/invite/scvCQ2qKS3)
-[](https://github.com/AxalotLDev/Async/issues)
-
-
-
-
**Async** is a Fabric mod designed to improve entity performance by processing them in parallel using multiple CPU cores and threads.
-
## Importantโ
**Async** is currently in alpha testing and is experimental. Its use may lead to incorrect entity behavior and crashes.
-
+## What is Async? ๐ค
+Async is a Fabric mod that enhances the performance of entity processing. The mod leverages multithreading, which allows multiple CPU cores to improve performance when handling a large number of entities.
### ๐ก Key Benefits:
- โก **Improved TPS**: Maintains stable tick times even with a large number of entities.
@@ -42,7 +32,7 @@ Concurrent Chunk Management Engine, Fabric API, FerriteCore, Lithium, ScalableLu
## โ ๏ธ Incompatible Mods
-- โ Moonrise - Known incompatibility
+- โ Moonrise - Known incompatibility
- โ ๏ธ ...and there may be conflicts with other mods.
*If you encounter issues with other mods, please report them on our [GitHub](https://github.com/AxalotLDev/Async/issues) or [Discord](https://discord.com/invite/scvCQ2qKS3).*
@@ -55,7 +45,8 @@ Concurrent Chunk Management Engine, Fabric API, FerriteCore, Lithium, ScalableLu
- `/async config synchronizedEntities remove` โ Removes selected entity from synchronized processing.
- `/async stats` โ Displays the number of threads in use.
- `/async stats entity` โ Shows the number of entities processed by Async in various worlds.
-- `/async stats entity [number]` โ Shows the top [number] entity types by count in descending order. For example, `/async stats entity 10` displays the top 10 most numerous entity types.
+- `/async stats entity [number]` โ Shows the top [number] entity types by count in descending order. For example, `/async stats entity 10` displays the top 10 most numerous entity types.
+- `/async stats entity [number] [ticks]` displays the top [number] most numerous entity types with their average mspt usage per [ticks].
## ๐ฅ Download
The mod is available on [Modrinth](https://modrinth.com/mod/async)
@@ -71,4 +62,4 @@ You can also chat with us on Discord:
[](https://discord.com/invite/scvCQ2qKS3)
## ๐ Acknowledgements
-This mod is based on code from [MCMTFabric](https://modrinth.com/mod/mcmtfabric), which in turn was based on [JMT-MCMT](https://github.com/jediminer543/JMT-MCMT). Huge thanks to Grider and jediminer543 for their invaluable contributions!
+This mod is based on code from [MCMTFabric](https://modrinth.com/mod/mcmtfabric), which in turn was based on [JMT-MCMT](https://github.com/jediminer543/JMT-MCMT). Huge thanks to Grider and jediminer543 for their invaluable contributions!
\ No newline at end of file
diff --git a/api/build.gradle b/api/build.gradle
new file mode 100644
index 00000000..9dd86fc3
--- /dev/null
+++ b/api/build.gradle
@@ -0,0 +1,96 @@
+plugins {
+ id 'java'
+ id 'maven-publish'
+ id 'com.gradleup.nmcp' version '1.4.4'
+ id 'signing'
+ id 'idea'
+ id 'fabric-loom'
+}
+
+group = 'io.github.axalotldev'
+version = "1.0.4"
+
+base {
+ archivesName = "${mod_id}-${project.name}"
+}
+
+java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(project.java_version)
+ }
+
+ withSourcesJar()
+ withJavadocJar()
+}
+
+repositories {
+ mavenCentral()
+ maven { url = "https://maven.parchmentmc.org/" }
+ maven { url = "https://maven.fabricmc.net/" }
+ maven { url = "https://libraries.minecraft.net/" }
+ maven { url = "https://maven.neoforged.net/releases/" }
+}
+
+dependencies {
+ minecraft("com.mojang:minecraft:${minecraft_version}")
+ mappings loom.layered {
+ officialMojangMappings()
+ if (project.hasProperty('parchment_minecraft_version') && project.hasProperty('parchment_mappings_version')) {
+ def mcVer = project.parchment_minecraft_version
+ def mapVer = project.parchment_mappings_version
+
+ if (mcVer != null && mapVer != null && !mcVer.isBlank() && !mapVer.isBlank()) {
+ parchment("org.parchmentmc.data:parchment-${mcVer}:${mapVer}@zip")
+ }
+ }
+ }
+ compileOnly("net.fabricmc:fabric-loader:$fabric_loader_version")
+ compileOnly "net.fabricmc.fabric-api:fabric-api:${fabric_version}"
+ compileOnly(annotationProcessor'org.projectlombok:lombok:1.18.44')
+}
+
+publishing {
+ publications {
+ create("mavenJava", MavenPublication) {
+ groupId = group
+ artifactId = "${mod_id}-${project.name}"
+ version = version
+ from components.java
+ pom {
+ name = mod_id
+ description = "Async API"
+ url = "https://github.com/axalotldev/${mod_id}"
+ licenses {
+ license {
+ name = "MIT License"
+ url = "https://opensource.org/license/mit/"
+ }
+ }
+ developers {
+ developer {
+ id = "axalotldev"
+ name = "AxalotlDev"
+ }
+ }
+ scm {
+ connection = "scm:git:git://github.com/axalotldev/${mod_id}.git"
+ developerConnection = "scm:git:ssh://github.com/axalotldev/${mod_id}.git"
+ url = "https://github.com/axalotldev/${mod_id}"
+ }
+ }
+ }
+ }
+}
+
+nmcp {
+ publishAllPublicationsToCentralPortal {
+ username = findProperty("mavenCentralUsername")
+ password = findProperty("mavenCentralPassword")
+ publishingType = "USER_MANAGED"
+ }
+}
+
+signing {
+ useGpgCmd()
+ sign publishing.publications.mavenJava
+}
diff --git a/api/src/main/java/com/axalotl/async/api/fastutil/ConcurrentLongLinkedOpenHashSet.java b/api/src/main/java/com/axalotl/async/api/fastutil/ConcurrentLongLinkedOpenHashSet.java
new file mode 100644
index 00000000..5b917a2e
--- /dev/null
+++ b/api/src/main/java/com/axalotl/async/api/fastutil/ConcurrentLongLinkedOpenHashSet.java
@@ -0,0 +1,175 @@
+package com.axalotl.async.api.fastutil;
+
+import it.unimi.dsi.fastutil.Hash;
+import it.unimi.dsi.fastutil.HashCommon;
+import it.unimi.dsi.fastutil.longs.*;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentSkipListSet;
+
+/**
+ * Thread-safe implementation of LongLinkedOpenHashSet using ConcurrentSkipListSet as backing storage.
+ * This implementation provides concurrent access and maintains elements in sorted order.
+ *
+ *
+ * A type-specific linked hash set with a fast, small-footprint implementation.
+ *
+ *
+ * Instances of this class use a hash table to represent a set. The table is filled up to a
+ * specified load factor, and then doubled in size to accommodate new entries. If the table
+ * is emptied below one fourth of the load factor, it is halved in size; however, the table
+ * is never reduced to a size smaller than that at creation time: this approach makes it possible to
+ * create sets with a large capacity in which insertions and deletions do not cause immediately
+ * rehashing. Moreover, halving is not performed when deleting entries from an iterator, as it would
+ * interfere with the iteration process.
+ *
+ *
+ * Note that {@link #clear()} does not modify the hash table size. Rather, a family of
+ * {@linkplain #trim() trimming methods} lets you control the size of the table; this is
+ * particularly useful if you reuse instances of this class.
+ *
+ *
+ * Iterators generated by this set will enumerate elements in the same order in which they have been
+ * added to the set (addition of elements already present in the set does not change the iteration
+ * order). Note that this order has nothing in common with the natural order of the keys. The order
+ * is kept by means of a doubly linked list, represented via an array of longs parallel to
+ * the table.
+ *
+ *
+ * This class implements the interface of a sorted set, so to allow easy access of the iteration
+ * order: for instance, you can get the first element in iteration order with {@code first()}
+ * without having to create an iterator; however, this class partially violates the
+ * {@link java.util.SortedSet} contract because all subset methods throw an exception and
+ * {@link #comparator()} returns always {@code null}.
+ *
+ *
+ * Additional methods, such as {@code addAndMoveToFirst()}, make it easy to use instances of this
+ * class as a cache (e.g., with LRU policy).
+ *
+ *
+ * The iterators provided by this class are type-specific {@linkplain java.util.ListIterator list
+ * iterators}, and can be started at any element which is in the set (if the provided
+ * element is not in the set, a {@link NoSuchElementException} exception will be thrown). If,
+ * however, the provided element is not the first or last element in the set, the first access to
+ * the list index will require linear time, as in the worst case the entire set must be scanned in
+ * iteration order to retrieve the positional index of the starting element. If you use just the
+ * methods of a type-specific {@link it.unimi.dsi.fastutil.BidirectionalIterator}, however, all
+ * operations will be performed in constant time.
+ *
+ * @see Hash
+ * @see HashCommon
+ */
+public class ConcurrentLongLinkedOpenHashSet extends LongLinkedOpenHashSet {
+
+ /**
+ * Thread-safe sorted backing set used for storing Long values.
+ * Delegates concurrency and ordering to ConcurrentSkipListSet.
+ */
+ private final ConcurrentSkipListSet backing = new ConcurrentSkipListSet<>();
+
+ public ConcurrentLongLinkedOpenHashSet() {
+ }
+
+ @Override
+ public boolean add(final long k) {
+ return backing.add(k);
+ }
+
+ @Override
+ public boolean addAll(Collection extends Long> c) {
+ Objects.requireNonNull(c, "Collection cannot be null");
+ return backing.addAll(c);
+ }
+
+ @Override
+ public boolean contains(final long k) {
+ return backing.contains(k);
+ }
+
+ @Override
+ public boolean remove(final long k) {
+ return backing.remove(k);
+ }
+
+ @Override
+ public void clear() {
+ backing.clear();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return backing.isEmpty();
+ }
+
+ @Override
+ public int size() {
+ return backing.size();
+ }
+
+ /**
+ * Returns the first element of this set in iteration order.
+ *
+ * @return the first element in iteration order.
+ */
+ @Override
+ public long firstLong() {
+ return Optional.ofNullable(backing.first())
+ .orElseThrow(() -> new NoSuchElementException("Set is empty"));
+ }
+
+ /**
+ * Returns the last element of this set in iteration order.
+ *
+ * @return the last element in iteration order.
+ */
+ @Override
+ public long lastLong() {
+ return Optional.ofNullable(backing.last())
+ .orElseThrow(() -> new NoSuchElementException("Set is empty"));
+ }
+
+ /**
+ * Removes the first key in iteration order.
+ *
+ * @return the first key.
+ */
+ @Override
+ public long removeFirstLong() {
+ long first = firstLong();
+ backing.remove(first);
+ return first;
+ }
+
+ /**
+ * Removes the last key in iteration order.
+ *
+ * @return the last key.
+ */
+ @Override
+ public long removeLastLong() {
+ long last = lastLong();
+ backing.remove(last);
+ return last;
+ }
+
+ @Override
+ public @NotNull LongListIterator iterator() {
+ return FastUtilHackUtil.wrap(backing.iterator());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj || (obj instanceof ConcurrentLongLinkedOpenHashSet other && backing.equals(other.backing));
+ }
+
+ @Override
+ public int hashCode() {
+ return backing.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return backing.toString();
+ }
+}
diff --git a/common/src/main/java/com/axalotl/async/common/parallelised/fastutil/ConcurrentLongSortedSet.java b/api/src/main/java/com/axalotl/async/api/fastutil/ConcurrentLongSortedSet.java
similarity index 55%
rename from common/src/main/java/com/axalotl/async/common/parallelised/fastutil/ConcurrentLongSortedSet.java
rename to api/src/main/java/com/axalotl/async/api/fastutil/ConcurrentLongSortedSet.java
index f4bb1f0b..62c0ca0b 100644
--- a/common/src/main/java/com/axalotl/async/common/parallelised/fastutil/ConcurrentLongSortedSet.java
+++ b/api/src/main/java/com/axalotl/async/api/fastutil/ConcurrentLongSortedSet.java
@@ -1,15 +1,27 @@
-package com.axalotl.async.common.parallelised.fastutil;
+package com.axalotl.async.api.fastutil;
import it.unimi.dsi.fastutil.longs.*;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Objects;
+import java.util.SortedSet;
import java.util.concurrent.ConcurrentSkipListSet;
/**
* A thread-safe implementation of LongSortedSet backed by ConcurrentSkipListSet.
* Provides concurrent access and maintains elements in sorted order.
+ *
+ *
+ * A type-specific {@link ConcurrentSkipListSet}; provides some additional methods that use polymorphism to
+ * avoid (un)boxing.
+ *
+ *
+ * Additionally, this interface strengthens {@link #iterator()}, {@link #comparator()} (for
+ * primitive types), {@link ConcurrentSkipListSet#subSet(Object, Object)}, {@link ConcurrentSkipListSet#headSet(Object)} and
+ * {@link ConcurrentSkipListSet#tailSet(Object)}.
+ *
+ * @see SortedSet
*/
public final class ConcurrentLongSortedSet implements LongSortedSet {
@@ -18,7 +30,8 @@ public final class ConcurrentLongSortedSet implements LongSortedSet {
/**
* Creates a new empty concurrent sorted set
*/
- public ConcurrentLongSortedSet() {}
+ public ConcurrentLongSortedSet() {
+ }
/**
* Creates a new concurrent sorted set containing elements from the given collection
@@ -31,11 +44,51 @@ public ConcurrentLongSortedSet(Collection collection) {
addAll(Objects.requireNonNull(collection, "Initial collection cannot be null"));
}
+ /**
+ * Returns a type-specific {@link it.unimi.dsi.fastutil.BidirectionalIterator} on the elements in
+ * this set, starting from a given element of the domain (optional operation).
+ *
+ *
+ * This method returns a type-specific bidirectional iterator with given starting point. The
+ * starting point is any element comparable to the elements of this set (even if it does not
+ * actually belong to the set). The next element of the returned iterator is the least element of
+ * the set that is greater than the starting point (if there are no elements greater than the
+ * starting point, {@link it.unimi.dsi.fastutil.BidirectionalIterator#hasNext() hasNext()} will
+ * return {@code false}). The previous element of the returned iterator is the greatest element of
+ * the set that is smaller than or equal to the starting point (if there are no elements smaller
+ * than or equal to the starting point,
+ * {@link it.unimi.dsi.fastutil.BidirectionalIterator#hasPrevious() hasPrevious()} will return
+ * {@code false}).
+ *
+ *
+ * Note that passing the last element of the set as starting point and calling
+ * {@link it.unimi.dsi.fastutil.BidirectionalIterator#previous() previous()} you can traverse the
+ * entire set in reverse order.
+ *
+ * @param fromElement an element to start from.
+ * @return a bidirectional iterator on the element in this set, starting at the given element.
+ * @throws UnsupportedOperationException if this set does not support iterators with a starting
+ * point.
+ */
@Override
public LongBidirectionalIterator iterator(long fromElement) {
return FastUtilHackUtil.wrap(backing.tailSet(fromElement).iterator());
}
+ /**
+ * Returns a type-specific {@link it.unimi.dsi.fastutil.BidirectionalIterator} on the elements in
+ * this set.
+ *
+ *
+ * This method returns a parameterised bidirectional iterator. The iterator can be moreover safely
+ * cast to a type-specific iterator.
+ *
+ *
+ * This specification strengthens the one given in the corresponding type-specific
+ * {@link Collection}.
+ *
+ * @return a bidirectional iterator on the element in this set.
+ */
@Override
public @NotNull LongBidirectionalIterator iterator() {
return FastUtilHackUtil.wrap(backing.iterator());
@@ -158,31 +211,67 @@ public boolean remove(long k) {
return backing.remove(k);
}
+ /**
+ * Returns a view of the portion of this sorted set whose elements range from {@code fromElement},
+ * inclusive, to {@code toElement}, exclusive.
+ *
+ * This specification strengthens the one given in {@link ConcurrentSkipListSet#subSet(Object,Object)}.
+ * @see ConcurrentSkipListSet#subSet(Object,Object)
+ */
@Override
public LongSortedSet subSet(long fromElement, long toElement) {
return new ConcurrentLongSortedSet(backing.subSet(Math.min(fromElement, toElement), Math.max(fromElement, toElement)));
}
+ /**
+ * Returns a view of the portion of this sorted set whose elements are strictly less than
+ * {@code toElement}.
+ *
+ * This specification strengthens the one given in {@link ConcurrentSkipListSet#headSet(Object)}.
+ * @see ConcurrentSkipListSet#headSet(Object)
+ */
@Override
public LongSortedSet headSet(long toElement) {
return new ConcurrentLongSortedSet(backing.headSet(toElement));
}
+ /**
+ * Returns a view of the portion of this sorted set whose elements are greater than or equal to
+ * {@code fromElement}.
+ *
+ * This specification strengthens the one given in {@link ConcurrentSkipListSet#headSet(Object)}.
+ * @see ConcurrentSkipListSet#tailSet(Object)
+ */
@Override
public LongSortedSet tailSet(long fromElement) {
return new ConcurrentLongSortedSet(backing.tailSet(fromElement));
}
+ /**
+ * {@inheritDoc}
+ *
+ * This specification strengthens the one given in {@link ConcurrentSkipListSet#comparator()}.
+ */
@Override
public LongComparator comparator() {
return null;
}
+ /**
+ * Returns the first (lowest) element currently in this set.
+ *
+ * @see ConcurrentSkipListSet#first()
+ */
@Override
public long firstLong() {
return backing.first();
}
+ /**
+ * Returns the last (highest) element currently in this set.
+ *
+ * @see ConcurrentSkipListSet#last()
+ */
@Override
public long lastLong() {
return backing.last();
diff --git a/common/src/main/java/com/axalotl/async/common/parallelised/fastutil/FastUtilHackUtil.java b/api/src/main/java/com/axalotl/async/api/fastutil/FastUtilHackUtil.java
similarity index 93%
rename from common/src/main/java/com/axalotl/async/common/parallelised/fastutil/FastUtilHackUtil.java
rename to api/src/main/java/com/axalotl/async/api/fastutil/FastUtilHackUtil.java
index 9df81951..6edeb57a 100644
--- a/common/src/main/java/com/axalotl/async/common/parallelised/fastutil/FastUtilHackUtil.java
+++ b/api/src/main/java/com/axalotl/async/api/fastutil/FastUtilHackUtil.java
@@ -1,4 +1,4 @@
-package com.axalotl.async.common.parallelised.fastutil;
+package com.axalotl.async.api.fastutil;
import java.util.*;
import java.util.function.Function;
@@ -19,16 +19,41 @@
import it.unimi.dsi.fastutil.objects.ObjectSet;
import org.jetbrains.annotations.NotNull;
+
+/**
+ * Utility bridge between standard Java Collections and FastUtil primitive collections.
+ *
+ * This class provides adapters (โwrappersโ) that allow:
+ *
+ * - Using {@link Collection} as FastUtil primitive collections
+ * - Converting Map entries between boxed and primitive key/value pairs
+ * - Wrapping primitive FastUtil iterators around Java iterators
+ *
+ *
+ * Purpose: Reduce boilerplate when interoperating between
+ * FastUtil and standard Java collections while avoiding full data copies
+ * where possible.
+ *
+ * This class is not intended to be instantiated.
+ */
public final class FastUtilHackUtil {
private FastUtilHackUtil() {
throw new AssertionError("No instances");
}
+ /**
+ * Wraps a {@link Collection} of {@link Byte} into a FastUtil {@link ByteCollection}.
+ */
public static ByteCollection wrapBytes(Collection c) {
return new WrappingByteCollection(c);
}
+ /**
+ * Adapter that exposes a {@link Collection} as a FastUtil {@link ByteCollection}.
+ *
+ * Backed directly by the original collection.
+ */
public static class WrappingByteCollection implements ByteCollection {
Collection backing;
@@ -133,6 +158,9 @@ public boolean retainAll(ByteCollection c) {
}
}
+ /**
+ * Wraps a Java map entry set into FastUtil Long/Int/primitive entry sets.
+ */
public static ObjectSet entrySetLongByteWrap(Map map) {
return new ConvertingObjectSet<>(map.entrySet(), FastUtilHackUtil::longByteEntryForwards, FastUtilHackUtil::longByteEntryBackwards);
}
@@ -316,6 +344,9 @@ public ObjectIterator> fastIterator() {
}
+ /**
+ * Wraps a Java {@link Iterator} of {@link Byte} into a FastUtil {@link ByteIterator}.
+ */
public static class WrappingByteIterator implements ByteIterator {
Iterator parent;
@@ -340,10 +371,16 @@ public byte nextByte() {
}
}
+ /**
+ * Returns a FastUtil {@link ByteIterator} from an {@link Iterable} of boxed bytes.
+ */
public static ByteIterator itrByteWrap(Iterable backing) {
return new WrappingByteIterator(backing.iterator());
}
+ /**
+ * Wraps a {@link Set} as a FastUtil {@link ObjectSet} with conversion functions.
+ */
public static class ConvertingObjectSet implements ObjectSet {
private final Set backing;
private final Function forward;
@@ -541,6 +578,9 @@ private static Map.Entry longEntryBackwards(Long2ObjectMap.Entry
return entry;
}
+ /**
+ * Iterator adapting boxed {@link Integer} to FastUtil {@link IntIterator}.
+ */
static class WrappingIntIterator implements IntIterator {
private final Iterator backing;
@@ -610,6 +650,9 @@ public void remove() {
}
}
+ /**
+ * Wraps a Java {@link Set} of boxed integers into a FastUtil {@link IntSet}.
+ */
public static class WrappingIntSet implements IntSet {
private final Set backing;
@@ -820,7 +863,9 @@ public boolean remove(long k) {
}
}
- // Utility methods
+ /**
+ * Utility methods
+ */
public static ObjectSet> entrySetIntWrap(Map map) {
return new ConvertingObjectSet<>(
map.entrySet(),
@@ -902,6 +947,9 @@ public void remove() {
}
+ /**
+ * Wraps a {@link Collection} into a FastUtil {@link ObjectCollection}.
+ */
public static class WrappingObjectCollection implements ObjectCollection {
private final Collection backing;
@@ -975,11 +1023,16 @@ public void clear() {
}
}
- // Utility methods
+ /**
+ * Utility methods
+ */
public static ObjectCollection wrap(Collection c) {
return new WrappingObjectCollection<>(c);
}
+ /**
+ * Wraps an {@link Iterable} into a FastUtil {@link ObjectIterator}.
+ */
private record WrapperObjectIterator(Iterator parent) implements ObjectIterator {
private WrapperObjectIterator(Iterator parent) {
this.parent = Objects.requireNonNull(parent);
@@ -1001,6 +1054,9 @@ public void remove() {
}
}
+ /**
+ * Returns a FastUtil {@link ObjectIterator} from an {@link Iterable}.
+ */
public static ObjectIterator itrWrap(Iterable in) {
return new WrapperObjectIterator<>(in.iterator());
}
diff --git a/common/src/main/java/com/axalotl/async/common/parallelised/fastutil/Int2ObjectConcurrentHashMap.java b/api/src/main/java/com/axalotl/async/api/fastutil/Int2ObjectConcurrentHashMap.java
similarity index 78%
rename from common/src/main/java/com/axalotl/async/common/parallelised/fastutil/Int2ObjectConcurrentHashMap.java
rename to api/src/main/java/com/axalotl/async/api/fastutil/Int2ObjectConcurrentHashMap.java
index 14e6ded3..075d4f2a 100644
--- a/common/src/main/java/com/axalotl/async/common/parallelised/fastutil/Int2ObjectConcurrentHashMap.java
+++ b/api/src/main/java/com/axalotl/async/api/fastutil/Int2ObjectConcurrentHashMap.java
@@ -1,4 +1,4 @@
-package com.axalotl.async.common.parallelised.fastutil;
+package com.axalotl.async.api.fastutil;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.IntSet;
@@ -16,16 +16,27 @@
* Provides concurrent access and high performance for integer-keyed maps.
*
* @param the type of values maintained by this map
+ * A type-specific {@link Map}; provides some additional methods that use polymorphism to avoid
+ * (un)boxing, and handling of a default return value.
+ *
+ *
+ * Besides extending the corresponding type-specific {@linkplain it.unimi.dsi.fastutil.Function
+ * function}, this interface strengthens {@link Map#entrySet()}, {@link #keySet()} and
+ * {@link #values()}. Moreover, a number of methods, such as {@link #size()},
+ * {@link #defaultReturnValue()}, etc., are un-defaulted as their function default do not make sense
+ * for a map. Maps returning entry sets of type {@link FastEntrySet} support also fast iteration.
+ *
+ *
+ * A submap or subset may or may not have an independent default return value (which however must be
+ * initialized to the default return value of the originator).
+ *
+ * @see Map
*/
public final class Int2ObjectConcurrentHashMap implements Int2ObjectMap {
- private final ConcurrentHashMap backing;
+ private final ConcurrentHashMap backing = new ConcurrentHashMap<>(16, 0.9f, 1);
private V defaultReturnValue;
- public Int2ObjectConcurrentHashMap() {
- this.backing = new ConcurrentHashMap<>(16, 0.9f, 1);
- }
-
@Override
public V get(int key) {
return backing.getOrDefault(key, defaultReturnValue);
@@ -137,4 +148,4 @@ public boolean replace(int key, V oldValue, V newValue) {
public V replace(int key, V value) {
return backing.replace(key, value);
}
-}
\ No newline at end of file
+}
diff --git a/common/src/main/java/com/axalotl/async/common/parallelised/fastutil/Long2LongConcurrentHashMap.java b/api/src/main/java/com/axalotl/async/api/fastutil/Long2LongConcurrentHashMap.java
similarity index 87%
rename from common/src/main/java/com/axalotl/async/common/parallelised/fastutil/Long2LongConcurrentHashMap.java
rename to api/src/main/java/com/axalotl/async/api/fastutil/Long2LongConcurrentHashMap.java
index aae5eeb2..8236e242 100644
--- a/common/src/main/java/com/axalotl/async/common/parallelised/fastutil/Long2LongConcurrentHashMap.java
+++ b/api/src/main/java/com/axalotl/async/api/fastutil/Long2LongConcurrentHashMap.java
@@ -1,4 +1,4 @@
-package com.axalotl.async.common.parallelised.fastutil;
+package com.axalotl.async.api.fastutil;
import it.unimi.dsi.fastutil.longs.*;
import it.unimi.dsi.fastutil.objects.AbstractObjectSet;
@@ -12,6 +12,23 @@
/**
* Thread-safe Long2LongMap implementation using ConcurrentHashMap.
* Designed for safe concurrent iteration and modification.
+ *
+ *
+ * A type-specific {@link Map}; provides some additional methods that use polymorphism to avoid
+ * (un)boxing, and handling of a default return value.
+ *
+ *
+ * Besides extending the corresponding type-specific {@linkplain it.unimi.dsi.fastutil.Function
+ * function}, this interface strengthens {@link Map#entrySet()}, {@link #keySet()} and
+ * {@link #values()}. Moreover, a number of methods, such as {@link #size()},
+ * {@link #defaultReturnValue()}, etc., are un-defaulted as their function default do not make sense
+ * for a map. Maps returning entry sets of type {@link FastEntrySet} support also fast iteration.
+ *
+ *
+ * A submap or subset may or may not have an independent default return value (which however must be
+ * initialized to the default return value of the originator).
+ *
+ * @see Map
*/
public final class Long2LongConcurrentHashMap implements Long2LongMap {
@@ -98,7 +115,6 @@ private final class EntrySet extends AbstractObjectSet implements ObjectS
public @NotNull ObjectIterator iterator() {
final Iterator> backingIt = backing.entrySet().iterator();
return new ObjectIterator<>() {
- private Map.Entry current;
@Override
public boolean hasNext() {
@@ -107,7 +123,7 @@ public boolean hasNext() {
@Override
public Entry next() {
- current = backingIt.next();
+ Map.Entry current = backingIt.next();
// Return Entry that writes back to the map
return new Entry() {
private final long key = current.getKey();
diff --git a/common/src/main/java/com/axalotl/async/common/parallelised/fastutil/Long2ObjectConcurrentHashMap.java b/api/src/main/java/com/axalotl/async/api/fastutil/Long2ObjectConcurrentHashMap.java
similarity index 96%
rename from common/src/main/java/com/axalotl/async/common/parallelised/fastutil/Long2ObjectConcurrentHashMap.java
rename to api/src/main/java/com/axalotl/async/api/fastutil/Long2ObjectConcurrentHashMap.java
index 5997c593..72f16590 100644
--- a/common/src/main/java/com/axalotl/async/common/parallelised/fastutil/Long2ObjectConcurrentHashMap.java
+++ b/api/src/main/java/com/axalotl/async/api/fastutil/Long2ObjectConcurrentHashMap.java
@@ -1,4 +1,4 @@
-package com.axalotl.async.common.parallelised.fastutil;
+package com.axalotl.async.api.fastutil;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.LongSet;
@@ -20,14 +20,13 @@
*/
public final class Long2ObjectConcurrentHashMap implements Long2ObjectMap {
- private final ConcurrentHashMap backing;
+ private final ConcurrentHashMap backing = new ConcurrentHashMap<>();
private V defaultReturnValue;
/**
- * Creates a new empty concurrent map with default initial capacity
+ * Creates an empty thread-safe Long2ObjectMap backed by ConcurrentHashMap.
*/
public Long2ObjectConcurrentHashMap() {
- this.backing = new ConcurrentHashMap<>();
}
@Override
diff --git a/api/src/main/java/com/axalotl/async/api/utils/AsyncCompatible.java b/api/src/main/java/com/axalotl/async/api/utils/AsyncCompatible.java
new file mode 100644
index 00000000..c8e46f5a
--- /dev/null
+++ b/api/src/main/java/com/axalotl/async/api/utils/AsyncCompatible.java
@@ -0,0 +1,31 @@
+package com.axalotl.async.api.utils;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a class as explicitly compatible with asynchronous processing.
+ *
+ *
+ * By default, all modded entities are assumed to be
+ * not thread-safe and will be executed synchronously on the main thread.
+ * Entity classes annotated with {@code @AsyncCompatible} indicate that they are
+ * designed to be safely used in asynchronous contexts.
+ *
+ *
+ * Usage
+ * {@code
+ * @AsyncCompatible
+ * public class MyCustomEntity extends Entity {
+ * // safe async logic here
+ * }
+ * }
+ *
+ * @see "com.axalotl.async.common.ParallelProcessor#entitySupportsAsyncApi(Entity entity)"
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface AsyncCompatible {
+}
\ No newline at end of file
diff --git a/common/src/main/java/com/axalotl/async/common/parallelised/ConcurrentCollections.java b/api/src/main/java/com/axalotl/async/api/utils/ConcurrentCollections.java
similarity index 83%
rename from common/src/main/java/com/axalotl/async/common/parallelised/ConcurrentCollections.java
rename to api/src/main/java/com/axalotl/async/api/utils/ConcurrentCollections.java
index bce264b5..8bcf254b 100644
--- a/common/src/main/java/com/axalotl/async/common/parallelised/ConcurrentCollections.java
+++ b/api/src/main/java/com/axalotl/async/api/utils/ConcurrentCollections.java
@@ -1,11 +1,10 @@
-package com.axalotl.async.common.parallelised;
+package com.axalotl.async.api.utils;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
/**
@@ -39,9 +38,9 @@ public static Map newHashMap() {
* Creates a collector that accumulates elements into a thread-safe list
*
* @param the type of elements in the list
- * @return a collector that accumulates elements into a CopyOnWriteArrayList
+ * @return a collector that accumulates elements into a thread-safe list
*/
public static java.util.stream.Collector> toList() {
- return Collectors.toCollection(CopyOnWriteArrayList::new);
+ return Collectors.toCollection(ConcurrentObjectArrayList::new);
}
}
\ No newline at end of file
diff --git a/api/src/main/java/com/axalotl/async/api/utils/ConcurrentObjectArrayList.java b/api/src/main/java/com/axalotl/async/api/utils/ConcurrentObjectArrayList.java
new file mode 100644
index 00000000..d8afa31a
--- /dev/null
+++ b/api/src/main/java/com/axalotl/async/api/utils/ConcurrentObjectArrayList.java
@@ -0,0 +1,100 @@
+package com.axalotl.async.api.utils;
+
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import it.unimi.dsi.fastutil.objects.ObjectListIterator;
+
+import java.util.Collection;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+/**
+ * A thread-safe {@link ObjectArrayList}.
+ *
+ * All mutating and reading operations are synchronized on the list itself. Iteration helpers
+ * ({@link #iterator()}, {@link #forEach(Consumer)}, {@link #stream()}) operate on a snapshot taken
+ * under the lock, so concurrent readers never observe a partially-mutated list or throw
+ * {@link java.util.ConcurrentModificationException}.
+ */
+public class ConcurrentObjectArrayList extends ObjectArrayList {
+
+ public ConcurrentObjectArrayList() {
+ super();
+ }
+
+ @Override
+ public synchronized boolean add(T element) {
+ return super.add(element);
+ }
+
+ @Override
+ public synchronized void add(int index, T element) {
+ super.add(index, element);
+ }
+
+ @Override
+ public synchronized boolean addAll(Collection extends T> c) {
+ return super.addAll(c);
+ }
+
+ @Override
+ public synchronized boolean remove(Object element) {
+ return super.remove(element);
+ }
+
+ @Override
+ public synchronized T remove(int index) {
+ return super.remove(index);
+ }
+
+ @Override
+ public synchronized boolean removeAll(Collection> c) {
+ return super.removeAll(c);
+ }
+
+ @Override
+ public synchronized void clear() {
+ super.clear();
+ }
+
+ @Override
+ public synchronized T get(int index) {
+ return super.get(index);
+ }
+
+ @Override
+ public synchronized T set(int index, T element) {
+ return super.set(index, element);
+ }
+
+ @Override
+ public synchronized int size() {
+ return super.size();
+ }
+
+ @Override
+ public synchronized boolean contains(Object element) {
+ return super.contains(element);
+ }
+
+ @SuppressWarnings("unchecked")
+ private synchronized T[] snapshot() {
+ return (T[]) toArray();
+ }
+
+ @Override
+ public ObjectListIterator iterator() {
+ return ObjectArrayList.wrap(snapshot()).iterator();
+ }
+
+ @Override
+ public void forEach(Consumer super T> action) {
+ for (T element : snapshot()) {
+ action.accept(element);
+ }
+ }
+
+ @Override
+ public Stream stream() {
+ return Stream.of(snapshot());
+ }
+}
diff --git a/common/src/main/java/com/axalotl/async/common/parallelised/utils/IteratorSafeOrderedReferenceSet.java b/api/src/main/java/com/axalotl/async/api/utils/IteratorSafeOrderedReferenceSet.java
similarity index 63%
rename from common/src/main/java/com/axalotl/async/common/parallelised/utils/IteratorSafeOrderedReferenceSet.java
rename to api/src/main/java/com/axalotl/async/api/utils/IteratorSafeOrderedReferenceSet.java
index 87c757ea..a0c64019 100644
--- a/common/src/main/java/com/axalotl/async/common/parallelised/utils/IteratorSafeOrderedReferenceSet.java
+++ b/api/src/main/java/com/axalotl/async/api/utils/IteratorSafeOrderedReferenceSet.java
@@ -1,4 +1,4 @@
-package com.axalotl.async.common.parallelised.utils;
+package com.axalotl.async.api.utils;
import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -9,6 +9,29 @@
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicInteger;
+/**
+ * A high-performance, insertion-ordered set backed by a primitive reference-to-index map
+ * and a dense array storage.
+ *
+ * This structure provides:
+ *
+ * - Fast O(1) add/contains/remove operations (amortized)
+ * - Stable insertion order iteration
+ * - Safe iteration with optional concurrent modifications tracking
+ * - Automatic defragmentation to reduce logical gaps
+ *
+ *
+ * Internally:
+ *
+ * - {@link Reference2IntLinkedOpenHashMap} maps elements to array indices
+ * - {@code listElements[]} stores elements in insertion order
+ * - Removed elements are nulled, creating fragmentation
+ *
+ *
+ * Defragmentation is triggered when fragmentation exceeds {@code maxFragFactor}.
+ *
+ * @param element type (reference-based, identity-sensitive depending on map behavior)
+ */
public final class IteratorSafeOrderedReferenceSet {
public static final int ITERATOR_FLAG_SEE_ADDITIONS = 1;
@@ -18,19 +41,54 @@ public final class IteratorSafeOrderedReferenceSet {
private final AtomicInteger firstInvalidIndex = new AtomicInteger(-1);
private E[] listElements;
+
+ /**
+ * Current number of slots ever used in {@code listElements}.
+ * Not necessarily equal to {@link #size()} due to removals.
+ */
@Getter
private int listSize;
+ /**
+ * Maximum allowed fragmentation ratio before triggering defragmentation.
+ */
private final double maxFragFactor;
+ /**
+ * Number of active iterators that require safe iteration tracking.
+ */
private int iteratorCount;
+ /**
+ * If true, iteration safety features (defragmentation during iteration) are disabled.
+ */
private final boolean threadRestricted;
+ /**
+ * Creates a default set with:
+ *
+ * - Initial capacity: 16
+ * - Load factor: 0.75
+ * - Array capacity: 16
+ * - Max fragmentation factor: 0.2
+ * - Component type: Object
+ * - Thread restriction: false
+ *
+ */
public IteratorSafeOrderedReferenceSet() {
this(16, 0.75f, 16, 0.2, Object.class, false);
}
+ /**
+ * Full constructor.
+ *
+ * @param setCapacity initial capacity for backing map
+ * @param setLoadFactor load factor for backing map
+ * @param arrayCapacity initial size of element array
+ * @param maxFragFactor threshold for triggering defragmentation (0.0โ1.0)
+ * @param arrComponent component type of internal array
+ * @param threadRestricted if true, disables safe iteration optimizations
+ */
@SuppressWarnings("unchecked")
public IteratorSafeOrderedReferenceSet(final int setCapacity, final float setLoadFactor, final int arrayCapacity,
final double maxFragFactor, final Class super E> arrComponent,
@@ -58,6 +116,17 @@ public void finishRawIterator() {
}
}
+ /**
+ * Removes an element from the set.
+ *
+ * If removal creates fragmentation beyond threshold, defragmentation
+ * may be triggered (depending on iterator state and configuration).
+ *
+ * @param element element to remove
+ * @return true if element was present and removed
+ *
+ * @throws IllegalStateException if internal array consistency is violated
+ */
public boolean remove(final E element) {
final int index = this.indexMap.removeInt(element);
if (index >= 0) {
@@ -83,10 +152,23 @@ public boolean remove(final E element) {
return false;
}
+ /**
+ * Checks whether the set contains the given element.
+ *
+ * @param element element to check
+ * @return true if present
+ */
public boolean contains(final E element) {
return this.indexMap.containsKey(element);
}
+ /**
+ * Adds an element to the set if it is not already present.
+ *
+ * @param element element to add
+ * @return true if added, false if already present
+ *
+ */
public boolean add(final E element) {
final int listSize = this.listSize;
@@ -161,14 +243,35 @@ public E getKey() {
this.firstInvalidIndex.set(-1);
}
+ /**
+ * Returns the number of elements currently in the set.
+ *
+ * @return logical size of the set
+ */
public int size() {
return this.indexMap.size();
}
+ /**
+ * Returns an iterator over the set in insertion order.
+ *
+ *
Iteration is safe against removals and may optionally allow
+ * seeing additions depending on flags.
+ *
+ * @return iterator instance
+ */
public Iterator iterator() {
return this.iterator(0);
}
+ /**
+ * Returns an iterator with behavior flags.
+ *
+ * @param flags iterator behavior flags
+ * @return iterator instance
+ *
+ * @see #ITERATOR_FLAG_SEE_ADDITIONS
+ */
public Iterator iterator(final int flags) {
if (this.allowSafeIteration()) {
++this.iteratorCount;
@@ -181,10 +284,24 @@ public Iterator iterator(final int flags) {
);
}
+ /**
+ * Extended iterator that supports explicit completion signaling.
+ */
public interface Iterator extends java.util.Iterator {
+ /**
+ * Signals that iteration is complete.
+ *
+ * May trigger internal cleanup or defragmentation.
+ */
void finishedIterating();
}
+ /**
+ * Internal iterator implementation.
+ *
+ *
Iterates over dense array, skipping nulls.
+ * Supports optional visibility of newly added elements.
+ */
private static final class BaseIterator implements Iterator {
private final IteratorSafeOrderedReferenceSet set;
@@ -202,6 +319,11 @@ private BaseIterator(final IteratorSafeOrderedReferenceSet set, final boolean
this.maxIndex = maxIndex;
}
+ /**
+ * Checks if more non-null elements exist in range.
+ *
+ * @return true if next element exists
+ */
@Override
public boolean hasNext() {
if (this.finished) {
@@ -230,6 +352,12 @@ public boolean hasNext() {
return false;
}
+ /**
+ * Returns next available element.
+ *
+ * @return next element
+ * @throws NoSuchElementException if no elements remain
+ */
@Override
public E next() {
if (!this.hasNext()) {
@@ -244,6 +372,11 @@ public E next() {
return ret;
}
+ /**
+ * Removes last returned element from the underlying set.
+ *
+ * @throws IllegalStateException if next() was not called or element already removed
+ */
@Override
public void remove() {
final E lastReturned = this.lastReturned;
@@ -256,6 +389,13 @@ public void remove() {
this.set.remove(lastReturned);
}
+ /**
+ * Marks iteration as complete and releases iterator tracking.
+ *
+ * May trigger defragmentation if enabled.
+ *
+ * @throws IllegalStateException if already finished or not allowed
+ */
@Override
public void finishedIterating() {
if (this.finished || !this.canFinish) {
diff --git a/api/src/main/java/com/axalotl/async/api/utils/SensorUtils.java b/api/src/main/java/com/axalotl/async/api/utils/SensorUtils.java
new file mode 100644
index 00000000..ca6f554a
--- /dev/null
+++ b/api/src/main/java/com/axalotl/async/api/utils/SensorUtils.java
@@ -0,0 +1,37 @@
+package com.axalotl.async.api.utils;
+
+import net.minecraft.world.entity.Entity;
+
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Utility class for entity-related sensor operations.
+ */
+public class SensorUtils {
+
+ /**
+ * Hidden constructor to prevent instantiation.
+ */
+ private SensorUtils() {
+ throw new UnsupportedOperationException("Utility class");
+ }
+
+ /**
+ * Creates a comparator that sorts entities by squared distance to a source entity.
+ *
+ * @param type of entity being compared
+ * @param source reference entity used as distance origin
+ * @return comparator ordering entities by distance to the source
+ */
+ public static Comparator comparingDouble(Entity source) {
+ Map cache = new HashMap<>();
+ return (a, b) -> {
+ double d1 = cache.computeIfAbsent(a, e -> e.distanceToSqr(source));
+ double d2 = cache.computeIfAbsent(b, e -> e.distanceToSqr(source));
+ return Double.compare(d1, d2);
+ };
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/com/axalotl/async/api/utils/SyncItemPickup.java b/api/src/main/java/com/axalotl/async/api/utils/SyncItemPickup.java
new file mode 100644
index 00000000..cc99737e
--- /dev/null
+++ b/api/src/main/java/com/axalotl/async/api/utils/SyncItemPickup.java
@@ -0,0 +1,56 @@
+package com.axalotl.async.api.utils;
+
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.entity.item.ItemEntity;
+
+/**
+ * Sync wrap for custom pickUpItem method
+ *
+ * Usage
+ * {@code
+ * protected void pickUpItem(final ServerLevel level, final ItemEntity entity) {
+ * SyncItemPickup.wrap(level, entity, () -> {
+ * this.onItemPickup(entity);
+ * PiglinAi.pickUpItem(level, this, entity);
+ * }
+ * }}
+ */
+public class SyncItemPickup {
+ /**
+ * Hidden constructor to prevent instantiation.
+ */
+ private SyncItemPickup() {
+ throw new UnsupportedOperationException("Utility class");
+ }
+
+ /**
+ * Executes the provided pickup logic in a synchronized context.
+ *
+ * If the given {@link ItemEntity} has already been removed, the action
+ * will not be executed.
+ *
+ * @param level the server level where the pickup occurs
+ * @param entity the item entity being picked up
+ * @param action the pickup logic to execute safely
+ */
+ public static void wrap(ServerLevel level, ItemEntity entity, Runnable action) {
+ wrap(entity, action);
+ }
+
+ /**
+ * Executes the provided pickup logic in a synchronized context.
+ *
+ * If the given {@link ItemEntity} has already been removed, the action
+ * will not be executed.
+ *
+ * @param entity the item entity being picked up
+ * @param action the pickup logic to execute safely
+ */
+ public static void wrap(ItemEntity entity, Runnable action) {
+ synchronized (entity) {
+ if (!entity.isRemoved()) {
+ action.run();
+ }
+ }
+ }
+}
diff --git a/buildSrc/src/main/groovy/multiloader-common.gradle b/buildSrc/src/main/groovy/multiloader-common.gradle
index f3956b72..64cfe790 100644
--- a/buildSrc/src/main/groovy/multiloader-common.gradle
+++ b/buildSrc/src/main/groovy/multiloader-common.gradle
@@ -1,6 +1,5 @@
plugins {
id 'java-library'
- id 'maven-publish'
}
base {
@@ -14,29 +13,6 @@ java {
repositories {
mavenCentral()
maven { url = 'https://maven.bawnorton.com/releases' }
- // https://docs.gradle.org/current/userguide/declaring_repositories.html#declaring_content_exclusively_found_in_one_repository
- exclusiveContent {
- forRepository {
- maven {
- name = 'Sponge'
- url = 'https://repo.spongepowered.org/repository/maven-public'
- }
- }
- filter { includeGroupAndSubgroups('org.spongepowered') }
- }
- exclusiveContent {
- forRepositories(
- maven {
- name = 'ParchmentMC'
- url = 'https://maven.parchmentmc.org/'
- },
- maven {
- name = "NeoForge"
- url = 'https://maven.neoforged.net/releases'
- }
- )
- filter { includeGroup('org.parchmentmc.data') }
- }
maven {
name = 'BlameJared'
url = 'https://maven.blamejared.com'
@@ -64,7 +40,7 @@ jar {
processResources {
var expandProps = [
'version' : version,
- 'group' : project.group, //Else we target the task's group.
+ 'group' : project.group,
'minecraft_version' : minecraft_version,
'minecraft_version_range' : minecraft_version_range,
'fabric_version' : fabric_version,
@@ -72,11 +48,9 @@ processResources {
'mod_name' : mod_name,
'mod_author' : mod_author,
'mod_id' : mod_id,
- 'license' : license,
'description' : project.description,
'neoforge_version' : neoforge_version,
'neoforge_loader_version_range': neoforge_loader_version_range,
- 'credits' : credits,
'java_version' : java_version
]
@@ -84,18 +58,4 @@ processResources {
expand expandProps
}
inputs.properties(expandProps)
-}
-
-publishing {
- publications {
- register('mavenJava', MavenPublication) {
- artifactId base.archivesName.get()
- from components.java
- }
- }
- repositories {
- maven {
- url System.getenv('local_maven_url')
- }
- }
-}
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/multiloader-loader.gradle b/buildSrc/src/main/groovy/multiloader-loader.gradle
index 9a558d9c..3a7bb83e 100644
--- a/buildSrc/src/main/groovy/multiloader-loader.gradle
+++ b/buildSrc/src/main/groovy/multiloader-loader.gradle
@@ -28,4 +28,4 @@ tasks.named('compileJava', JavaCompile) {
processResources {
dependsOn(configurations.commonResources)
from(configurations.commonResources)
-}
\ No newline at end of file
+}
diff --git a/common/build.gradle b/common/build.gradle
index 6133fcb7..5bddee52 100644
--- a/common/build.gradle
+++ b/common/build.gradle
@@ -5,7 +5,6 @@ plugins {
neoForge {
neoFormVersion = neoform_version
- // Automatically enable AccessTransformers if the file exists
def at = file('src/main/resources/META-INF/accesstransformer.cfg')
if (at.exists()) {
accessTransformers.from(at.absolutePath)
@@ -27,6 +26,7 @@ neoForge {
repositories {
maven { url = 'https://maven.bawnorton.com/releases' }
+ maven { url = 'https://maven.parchmentmc.org' }
maven { url 'https://api.modrinth.com/maven' }
}
@@ -37,9 +37,10 @@ dependencies {
implementation(annotationProcessor("com.github.bawnorton.mixinsquared:mixinsquared-common:${mixinsquared_version}"))
implementation("org.ow2.asm:asm:9.6")
implementation("org.ow2.asm:asm-tree:9.6")
- compileOnly 'org.projectlombok:lombok:1.18.32'
- annotationProcessor 'org.projectlombok:lombok:1.18.32'
+ compileOnly 'org.projectlombok:lombok:1.18.44'
+ annotationProcessor 'org.projectlombok:lombok:1.18.44'
implementation "maven.modrinth:lithium:${lithium_version}-neoforge"
+ implementation project(":api")
}
configurations {
@@ -56,4 +57,4 @@ configurations {
artifacts {
commonJava sourceSets.main.java.sourceDirectories.singleFile
commonResources sourceSets.main.resources.sourceDirectories.singleFile
-}
\ No newline at end of file
+}
diff --git a/common/src/main/java/com/axalotl/async/common/AsyncCommon.java b/common/src/main/java/com/axalotl/async/common/AsyncCommon.java
index bf69c195..f80da296 100644
--- a/common/src/main/java/com/axalotl/async/common/AsyncCommon.java
+++ b/common/src/main/java/com/axalotl/async/common/AsyncCommon.java
@@ -4,7 +4,6 @@
public abstract class AsyncCommon {
public static final String MODID = "async";
- public static boolean LITHIUM = PlatformUtils.isModLoaded("lithium");
public final void initialize() {
PlatformUtils.initialize();
diff --git a/common/src/main/java/com/axalotl/async/common/ParallelProcessor.java b/common/src/main/java/com/axalotl/async/common/ParallelProcessor.java
index a6e1eacf..8cf4af11 100644
--- a/common/src/main/java/com/axalotl/async/common/ParallelProcessor.java
+++ b/common/src/main/java/com/axalotl/async/common/ParallelProcessor.java
@@ -1,79 +1,92 @@
package com.axalotl.async.common;
+import com.axalotl.async.api.utils.AsyncCompatible;
import com.axalotl.async.common.config.AsyncConfig;
-import com.axalotl.async.common.parallelised.utils.PortalCreationCache;
import lombok.Getter;
import lombok.Setter;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
-import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.item.FallingBlockEntity;
import net.minecraft.world.entity.monster.Shulker;
+import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.Projectile;
-import net.minecraft.world.entity.vehicle.AbstractMinecart;
import net.minecraft.world.entity.vehicle.Boat;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.LongAdder;
+
+import static com.axalotl.async.common.utils.TickStats.*;
public class ParallelProcessor {
- public static final Logger LOGGER = LogManager.getLogger(ParallelProcessor.class);
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ParallelProcessor.class);
@Getter
@Setter
private static MinecraftServer server;
- public static final AtomicInteger currentEntities = new AtomicInteger();
- private static final AtomicInteger threadPoolID = new AtomicInteger();
- public static ExecutorService tickPool;
- private static final Set blacklistedEntity = ConcurrentHashMap.newKeySet();
- private static final Map>> mcThreadTracker = new ConcurrentHashMap<>();
- public static final Set> BLOCKED_ENTITIES = Set.of(
+ //Thread pool
+ public static ExecutorService executor;
+ private static final AtomicInteger THREAD_POOL_ID = new AtomicInteger();
+ private static volatile boolean isShuttingDown = false;
+
+ //Blacklist
+ private static final Set BLACKLISTED_ENTITIES = ConcurrentHashMap.newKeySet();
+ private static final Set> BLOCKED_ENTITIES = Set.of(
FallingBlockEntity.class,
Shulker.class,
Boat.class
);
- private static volatile boolean isShuttingDown = false;
+
+ //Cache
+ private static final Map, Boolean> ASYNC_API_CACHE = new ConcurrentHashMap<>();
+
+ //Threads
+ private static final Map>> MC_THREAD_TRACKER = new ConcurrentHashMap<>();
public static void setupThreadPool(int parallelism, Class> asyncClass) {
isShuttingDown = false;
+
ThreadFactory threadFactory = runnable -> {
- Thread thread = new Thread(runnable,
- "Async-Tick-Pool-Thread-" + threadPoolID.getAndIncrement());
+ Thread thread = new Thread(runnable, "Async-Tick-Pool-Thread-" + THREAD_POOL_ID.getAndIncrement());
registerThread("Async-Tick", thread);
- thread.setDaemon(false);
+ thread.setDaemon(true);
thread.setPriority(Thread.NORM_PRIORITY - 1);
thread.setContextClassLoader(asyncClass.getClassLoader());
return thread;
};
- ThreadPoolExecutor executor = new ThreadPoolExecutor(
+
+ ThreadPoolExecutor pool = new ThreadPoolExecutor(
parallelism,
parallelism,
- 0L, TimeUnit.MILLISECONDS,
+ 0L,
+ TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
threadFactory
);
- executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
- executor.allowCoreThreadTimeOut(false);
- executor.prestartAllCoreThreads();
- tickPool = executor;
+ pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
+ pool.allowCoreThreadTimeOut(false);
+ pool.prestartAllCoreThreads();
+
+ executor = pool;
LOGGER.info("Initialized Pool with {} threads", parallelism);
}
public static void registerThread(String poolName, Thread thread) {
- mcThreadTracker
- .computeIfAbsent(poolName, key -> ConcurrentHashMap.newKeySet())
+ MC_THREAD_TRACKER
+ .computeIfAbsent(poolName, ignored -> ConcurrentHashMap.newKeySet())
.add(new WeakReference<>(thread));
}
private static boolean isThreadInPool(Thread thread) {
- return mcThreadTracker.getOrDefault("Async-Tick", Set.of()).stream()
+ return MC_THREAD_TRACKER.getOrDefault("Async-Tick", Set.of()).stream()
.map(WeakReference::get)
.anyMatch(thread::equals);
}
@@ -83,38 +96,54 @@ public static boolean isServerExecutionThread() {
}
public static int getPoolSize() {
- return ((ThreadPoolExecutor) tickPool).getCorePoolSize();
+ if (executor instanceof ThreadPoolExecutor pool) {
+ return pool.getCorePoolSize();
+ }
+ return 0;
}
@SuppressWarnings("unchecked")
public static void callEntityTickBatch(ServerLevel world, List entities) {
if (entities.isEmpty()) return;
- if (AsyncConfig.disabled || ParallelProcessor.tickPool.isShutdown()) {
- entities.forEach(e -> tickSynchronously(world, e));
+
+ if (AsyncConfig.disabled) {
+ entities.forEach(e -> tickEntity(world, e, false));
return;
}
- int poolSize = getPoolSize();
- int chunkSize = (entities.size() + poolSize - 1) / poolSize;
-
- List> futures = new ArrayList<>();
- for (int i = 0; i < entities.size(); i += chunkSize) {
- List chunk = entities.subList(i, Math.min(i + chunkSize, entities.size()));
- Future future = (Future) tickPool.submit(() -> {
- for (Entity entity : chunk) {
- if (shouldTickSynchronously(entity)) continue;
- performAsyncEntityTick(world, entity);
- }
- });
- futures.add(future);
+ List asyncEntities = new ArrayList<>();
+ List syncEntities = new ArrayList<>();
+ final Set seen = Collections.newSetFromMap(new IdentityHashMap<>(entities.size()));
+ for (Entity entity : entities) {
+ if (entity == null || entity.isRemoved() || !seen.add(entity)) {
+ continue;
+ }
+ if (shouldTickSynchronously(entity)) {
+ syncEntities.add(entity);
+ } else {
+ asyncEntities.add(entity);
+ }
}
-
- for (Entity e : entities) {
- if (shouldTickSynchronously(e)) {
- tickSynchronously(world, e);
+ final List> futures = new ArrayList<>();
+ if (!asyncEntities.isEmpty()) {
+ final int poolSize = getPoolSize();
+ final int chunkSize = Math.max(1, (asyncEntities.size() + poolSize - 1) / poolSize);
+
+ for (int i = 0; i < asyncEntities.size(); i += chunkSize) {
+ final List chunk = asyncEntities.subList(i, Math.min(i + chunkSize, asyncEntities.size()));
+ Future future = (Future) executor.submit(() -> {
+ for (Entity entity : chunk) {
+ tickEntity(world, entity, true);
+ }
+ });
+ futures.add(future);
}
}
+ syncEntities.forEach(e -> tickEntity(world, e, false));
+ waitForFutures(futures);
+ }
+ private static void waitForFutures(List> futures) {
boolean allDone;
do {
allDone = futures.stream().allMatch(Future::isDone);
@@ -123,74 +152,87 @@ public static void callEntityTickBatch(ServerLevel world, List entities)
for (ServerLevel lvl : server.getAllLevels()) {
pumped |= lvl.getChunkSource().pollTask();
}
- if (!pumped) {
- Thread.onSpinWait();
- }
+ if (!pumped) Thread.onSpinWait();
}
} while (!allDone);
for (Future future : futures) {
try {
future.get();
- } catch (Exception e) {
- LOGGER.error("Error in async entity tick", e);
+ } catch (ExecutionException e) {
+ LOGGER.error("Error during async entity tick", e.getCause());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return;
}
}
}
public static boolean shouldTickSynchronously(Entity entity) {
- if (isShuttingDown) {
- return true;
- }
- if (entity.level().isClientSide()) {
+ if (isShuttingDown || entity.level().isClientSide() || entity.portalProcess != null) {
return true;
}
UUID entityId = entity.getUUID();
- return AsyncConfig.disabled ||
- entity instanceof Projectile ||
- entity instanceof AbstractMinecart ||
- entity instanceof ServerPlayer ||
- BLOCKED_ENTITIES.contains(entity.getClass()) ||
- blacklistedEntity.contains(entityId) ||
- AsyncConfig.isEntitySynchronized(EntityType.getKey(entity.getType()));
+ return AsyncConfig.disabled
+ || entitySupportsAsyncApi(entity)
+ || entity instanceof Projectile
+ || entity instanceof Player
+ || BLOCKED_ENTITIES.contains(entity.getClass())
+ || BLACKLISTED_ENTITIES.contains(entityId)
+ || AsyncConfig.isEntitySynchronized(EntityType.getKey(entity.getType()));
}
- private static void tickSynchronously(ServerLevel world, Entity entity) {
- try {
- world.tickNonPassenger(entity);
- } catch (Exception e) {
- logEntityError(entity, e);
- }
+ public static boolean entitySupportsAsyncApi(Entity entity) {
+ Class> cls = entity.getClass();
+ Boolean cached = ASYNC_API_CACHE.get(cls);
+ if (cached != null) return cached;
+ boolean result = !"minecraft".equals(EntityType.getKey(entity.getType()).getNamespace()) && !cls.isAnnotationPresent(AsyncCompatible.class);
+ ASYNC_API_CACHE.put(cls, result);
+ return result;
}
- private static void performAsyncEntityTick(ServerLevel world, Entity entity) {
- currentEntities.incrementAndGet();
+ private static void tickEntity(ServerLevel world, Entity entity, boolean async) {
+ long start = System.nanoTime();
try {
world.tickNonPassenger(entity);
+ } catch (Exception e) {
+ LOGGER.error("Error during {} tick. Entity: {}, UUID: {}",
+ async ? "async" : "sync", entity.getType(), entity.getUUID(), e);
} finally {
- currentEntities.decrementAndGet();
+ if (RECORDING_TICKS_LEFT.get() > 0) {
+ EntityType> type = entity.getType();
+ long elapsed = System.nanoTime() - start;
+
+ if (async) {
+ ASYNC_TICK_TIME_NS.computeIfAbsent(type, ignored -> new LongAdder()).add(elapsed);
+ ASYNC_TICK_COUNT.computeIfAbsent(type, ignored -> new LongAdder()).increment();
+ } else {
+ TICK_TIME_NS.computeIfAbsent(type, ignored -> new LongAdder()).add(elapsed);
+ TICK_COUNT.computeIfAbsent(type, ignored -> new LongAdder()).increment();
+ }
+ }
}
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public static void stop() {
isShuttingDown = true;
- if (tickPool != null) {
- LOGGER.info("Waiting for Async tickPool to shutdown...");
- tickPool.shutdown();
+
+ if (executor != null) {
+ LOGGER.info("Waiting for Async poll to shutdown...");
+ executor.shutdown();
try {
- tickPool.awaitTermination(60L, TimeUnit.SECONDS);
- } catch (InterruptedException ignored) {
+ executor.awaitTermination(60L, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ LOGGER.warn("Interrupted while waiting for thread pool shutdown", e);
}
}
- AsyncConfig.clearCaches();
- blacklistedEntity.clear();
- PortalCreationCache.clear();
- }
- private static void logEntityError(Entity entity, Throwable e) {
- LOGGER.error("{} Entity Type: {}, UUID: {}", "Error during synchronous tick", entity.getType().toString(), entity.getUUID(), e);
+ AsyncConfig.clearCaches();
+ BLACKLISTED_ENTITIES.clear();
+ resetEntityTickStats();
}
-}
\ No newline at end of file
+}
diff --git a/common/src/main/java/com/axalotl/async/common/commands/ConfigCommand.java b/common/src/main/java/com/axalotl/async/common/commands/ConfigCommand.java
index 7cd4dbcb..1f4ee2f9 100644
--- a/common/src/main/java/com/axalotl/async/common/commands/ConfigCommand.java
+++ b/common/src/main/java/com/axalotl/async/common/commands/ConfigCommand.java
@@ -1,7 +1,7 @@
package com.axalotl.async.common.commands;
import com.axalotl.async.common.config.AsyncConfig;
-import com.axalotl.async.common.platform.Permission;
+import com.axalotl.async.common.platform.PlatformPermission;
import com.axalotl.async.common.platform.PlatformUtils;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
@@ -25,7 +25,7 @@ public class ConfigCommand {
public static LiteralArgumentBuilder registerConfig(LiteralArgumentBuilder root) {
return root.then(literal("config")
- .requires(Permission.require("command.config", 4))
+ .requires(PlatformPermission.require("command.config", 4))
.then(buildToggleCommand())
.then(buildReloadCommand())
.then(buildSynchronizedEntitiesCommand())
@@ -184,7 +184,7 @@ private static int addEntity(CommandContext ctx) {
return 1;
}
- if (AsyncConfig.isEntitySynchronized(id)) {
+ if (AsyncConfig.synchronizedEntities.contains(id.toString())) {
sendErrorMessage(ctx, "Error entity class ", id.toString(), " is already synchronized.");
return 1;
}
@@ -211,7 +211,7 @@ private static int addNamespace(CommandContext ctx) {
private static int removeEntity(CommandContext ctx) {
ResourceLocation id = ResourceLocationArgument.getId(ctx, "entity");
- if (!AsyncConfig.isEntitySynchronized(id)) {
+ if (!AsyncConfig.synchronizedEntities.contains(id.toString())) {
sendErrorMessage(ctx, "Error entity class ", id.toString(), " is not in the synchronized list.");
return 1;
}
@@ -224,11 +224,6 @@ private static int removeEntity(CommandContext ctx) {
private static int removeNamespace(CommandContext ctx) {
String namespace = StringArgumentType.getString(ctx, "namespace");
- ResourceLocation id = ResourceLocation.tryParse(namespace);
-
- if (id != null) {
- return 1;
- }
if (!AsyncConfig.synchronizedEntities.contains(namespace)) {
sendErrorMessage(ctx, "Error namespace ", namespace, " is not in the synchronized list.");
@@ -264,6 +259,6 @@ private static void sendErrorMessage(CommandContext ctx, Str
.append(Component.literal(prefix).withStyle(style -> style.withColor(ChatFormatting.RED)))
.append(Component.literal(error).withStyle(style -> style.withColor(ChatFormatting.RED)))
.append(Component.literal(suffix).withStyle(style -> style.withColor(ChatFormatting.RED)));
- ctx.getSource().sendSuccess(() -> message, true);
+ ctx.getSource().sendFailure(message);
}
-}
\ No newline at end of file
+}
diff --git a/common/src/main/java/com/axalotl/async/common/commands/StatsCommand.java b/common/src/main/java/com/axalotl/async/common/commands/StatsCommand.java
index 4bcb4540..1c0d5f92 100644
--- a/common/src/main/java/com/axalotl/async/common/commands/StatsCommand.java
+++ b/common/src/main/java/com/axalotl/async/common/commands/StatsCommand.java
@@ -2,7 +2,8 @@
import com.axalotl.async.common.ParallelProcessor;
import com.axalotl.async.common.config.AsyncConfig;
-import com.axalotl.async.common.platform.Permission;
+import com.axalotl.async.common.platform.PlatformPermission;
+import com.axalotl.async.common.utils.TickStats;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.ChatFormatting;
@@ -20,28 +21,41 @@
import static com.axalotl.async.common.ParallelProcessor.getPoolSize;
import static com.axalotl.async.common.commands.AsyncCommand.prefix;
+import static com.axalotl.async.common.utils.TickStats.resetEntityTickStats;
import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
public class StatsCommand {
public static LiteralArgumentBuilder registerStatus(LiteralArgumentBuilder root) {
- return root.then(literal("stats").requires(Permission.require("command.statistics", 0))
+ return root.then(literal("stats").requires(PlatformPermission.require("command.statistics", 0))
.executes(cmdCtx -> {
showGeneralStats(cmdCtx.getSource());
return 1;
})
.then(literal("entity")
.executes(cmdCtx -> {
- showEntityStats(cmdCtx.getSource(), 0);
+ showEntityStats(cmdCtx.getSource(), 0, false, 0);
return 1;
})
.then(argument("count", IntegerArgumentType.integer(1, 100))
.executes(cmdCtx -> {
int count = IntegerArgumentType.getInteger(cmdCtx, "count");
- showEntityStats(cmdCtx.getSource(), count);
+ showEntityStats(cmdCtx.getSource(), count, false, 0);
return 1;
- }))));
+ })
+ .then(argument("ticks", IntegerArgumentType.integer())
+ .executes(cmdCtx -> {
+ int count = IntegerArgumentType.getInteger(cmdCtx, "count");
+ int ticks = IntegerArgumentType.getInteger(cmdCtx, "ticks");
+ startRecordingAndShow(cmdCtx.getSource(), count, ticks);
+ return 1;
+ })))));
+ }
+
+ private static void startRecordingAndShow(CommandSourceStack source, int topCount, int ticks) {
+ source.sendSuccess(() -> prefix.copy().append(Component.literal("Recording entity ticks for " + ticks + " ticks...").withStyle(ChatFormatting.YELLOW)), false);
+ TickStats.startRecording(ticks, () -> showEntityStats(source, topCount, true, ticks));
}
private static void showGeneralStats(CommandSourceStack source) {
@@ -86,7 +100,7 @@ private static void showGeneralStats(CommandSourceStack source) {
source.sendSuccess(() -> message, false);
}
- private static void showEntityStats(CommandSourceStack source, int topCount) {
+ private static void showEntityStats(CommandSourceStack source, int topCount, boolean showTickStats, int ticks) {
MinecraftServer server = source.getServer();
server.execute(() -> {
Map, Integer> entityTypeCounts = new HashMap<>();
@@ -156,11 +170,17 @@ private static void showEntityStats(CommandSourceStack source, int topCount) {
.withStyle(isAsync ? ChatFormatting.AQUA : ChatFormatting.RED))
.append(Component.literal("]").withStyle(ChatFormatting.DARK_GRAY));
+ if (showTickStats && ticks > 0) {
+ double mspt = TickStats.getMSPTForType(type, ticks);
+ message.append(Component.literal(" "))
+ .append(Component.literal(String.format("%.3fms avg", mspt)).withStyle(ChatFormatting.GREEN));
+ }
+
rank[0]++;
});
}
-
+ resetEntityTickStats();
source.sendSuccess(() -> message, false);
});
}
-}
\ No newline at end of file
+}
diff --git a/common/src/main/java/com/axalotl/async/common/config/AsyncConfig.java b/common/src/main/java/com/axalotl/async/common/config/AsyncConfig.java
index 4c6c4bd5..f447c139 100644
--- a/common/src/main/java/com/axalotl/async/common/config/AsyncConfig.java
+++ b/common/src/main/java/com/axalotl/async/common/config/AsyncConfig.java
@@ -1,6 +1,5 @@
package com.axalotl.async.common.config;
-import com.axalotl.async.common.parallelised.utils.ModCompatible;
import com.axalotl.async.common.platform.PlatformUtils;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
@@ -15,7 +14,7 @@ public class AsyncConfig {
public static boolean disabled = false;
public static int maxThreads = -1;
- public static boolean enableAsyncSpawn = true;
+ public static boolean enableAsyncSpawn = false;
public static boolean enableAsyncRandomTicks = false;
public static Set synchronizedEntities = getDefaultSynchronizedEntities();
@@ -25,18 +24,16 @@ public class AsyncConfig {
private static final Set namespaceWildcards = new HashSet<>();
public static Set getDefaultSynchronizedEntities() {
- final Set defaultSynchronizedEntities = new HashSet<>(ModCompatible.addUnsupportedMods());
- defaultSynchronizedEntities.addAll(Set.of(
+ return new HashSet<>(Set.of(
"minecraft:tnt",
"minecraft:item",
"minecraft:experience_orb"
));
- return defaultSynchronizedEntities;
}
public static int getParallelism() {
if (maxThreads <= 0) return Runtime.getRuntime().availableProcessors();
- return Math.max(1, Math.min(Runtime.getRuntime().availableProcessors(), maxThreads));
+ return Math.max(1, Math.min(maxThreads, Runtime.getRuntime().availableProcessors()));
}
public static boolean isNamespaceWildcard(String input) {
@@ -120,5 +117,7 @@ public static void onConfigLoaded() {
public static void clearCaches() {
syncCache.clear();
+ exactEntities.clear();
+ namespaceWildcards.clear();
}
-}
\ No newline at end of file
+}
diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/AllayMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/AllayMixin.java
index 4a4e5801..b90cf409 100644
--- a/common/src/main/java/com/axalotl/async/common/mixin/entity/AllayMixin.java
+++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/AllayMixin.java
@@ -1,24 +1,17 @@
package com.axalotl.async.common.mixin.entity;
+import com.axalotl.async.api.utils.SyncItemPickup;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import net.minecraft.world.entity.animal.allay.Allay;
import net.minecraft.world.entity.item.ItemEntity;
import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Unique;
@Mixin(Allay.class)
public abstract class AllayMixin {
- @Unique
- private static final Object async$lock = new Object();
-
@WrapMethod(method = "pickUpItem")
- private void pickUpItem(ItemEntity itemEntity, Operation original) {
- synchronized (async$lock) {
- if (!itemEntity.isRemoved()) {
- original.call(itemEntity);
- }
- }
+ private void pickUpItem(ItemEntity entity, Operation original) {
+ SyncItemPickup.wrap(entity, () -> original.call(entity));
}
-}
\ No newline at end of file
+}
diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/AnimalMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/AnimalMixin.java
index 7006da1b..4eefc05a 100644
--- a/common/src/main/java/com/axalotl/async/common/mixin/entity/AnimalMixin.java
+++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/AnimalMixin.java
@@ -17,38 +17,48 @@
public abstract class AnimalMixin extends Entity {
@Unique
- private final AtomicBoolean async$breedingFlag = new AtomicBoolean(false);
+ private final AtomicBoolean breedingFlag = new AtomicBoolean(false);
@Unique
- private final AtomicBoolean async$breedingBabyFlag = new AtomicBoolean(false);
+ private final AtomicBoolean breedingBabyFlag = new AtomicBoolean(false);
public AnimalMixin(EntityType> entityType, Level level) {
super(entityType, level);
}
@WrapMethod(method = "spawnChildFromBreeding")
- private void breed(ServerLevel world, Animal other, Operation original) {
- if (this.getId() > other.getId()) return;
- AnimalMixin otherMixin = (AnimalMixin) (Object) other;
- if (this.async$breedingFlag.compareAndSet(false, true) && otherMixin.async$breedingFlag.compareAndSet(false, true)) {
- try {
- original.call(world, other);
- } finally {
- this.async$breedingFlag.set(false);
- otherMixin.async$breedingFlag.set(false);
+ private void breed(ServerLevel level, Animal partner, Operation original) {
+ if (this.getId() > partner.getId()) return;
+ AnimalMixin other = (AnimalMixin) (Object) partner;
+
+ if (this.breedingFlag.compareAndSet(false, true)) {
+ if (other.breedingFlag.compareAndSet(false, true)) {
+ try {
+ original.call(level, partner);
+ } finally {
+ this.breedingFlag.set(false);
+ other.breedingFlag.set(false);
+ }
+ } else {
+ this.breedingFlag.set(false);
}
}
}
@WrapMethod(method = "finalizeSpawnChildFromBreeding")
- private void breed(ServerLevel world, Animal other, AgeableMob baby, Operation original) {
- if (this.getId() > other.getId()) return;
- AnimalMixin otherMixin = (AnimalMixin) (Object) other;
- if (this.async$breedingBabyFlag.compareAndSet(false, true) && otherMixin.async$breedingBabyFlag.compareAndSet(false, true)) {
- try {
- original.call(world, other, baby);
- } finally {
- this.async$breedingBabyFlag.set(false);
- otherMixin.async$breedingBabyFlag.set(false);
+ private void breed(ServerLevel level, Animal partner, AgeableMob offspring, Operation original) {
+ if (this.getId() > partner.getId()) return;
+ AnimalMixin other = (AnimalMixin) (Object) partner;
+
+ if (this.breedingBabyFlag.compareAndSet(false, true)) {
+ if (other.breedingBabyFlag.compareAndSet(false, true)) {
+ try {
+ original.call(level, partner, offspring);
+ } finally {
+ this.breedingBabyFlag.set(false);
+ other.breedingBabyFlag.set(false);
+ }
+ } else {
+ this.breedingBabyFlag.set(false);
}
}
}
diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/AreaEffectCloudMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/AreaEffectCloudMixin.java
index 43281683..64565271 100644
--- a/common/src/main/java/com/axalotl/async/common/mixin/entity/AreaEffectCloudMixin.java
+++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/AreaEffectCloudMixin.java
@@ -1,6 +1,6 @@
package com.axalotl.async.common.mixin.entity;
-import com.axalotl.async.common.parallelised.ConcurrentCollections;
+import com.axalotl.async.api.utils.ConcurrentCollections;
import net.minecraft.world.entity.AreaEffectCloud;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
@@ -23,7 +23,7 @@ public class AreaEffectCloudMixin {
private Map victims;
@Inject(method = "(Lnet/minecraft/world/entity/EntityType;Lnet/minecraft/world/level/Level;)V", at = @At("RETURN"))
- private void makeCollectionsThreadSafe(EntityType extends AreaEffectCloud> entityType, Level level, CallbackInfo ci) {
+ private void makeCollectionsThreadSafe(EntityType extends AreaEffectCloud> type, Level level, CallbackInfo ci) {
this.victims = ConcurrentCollections.newHashMap();
}
}
\ No newline at end of file
diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/AttributeInstanceMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/AttributeInstanceMixin.java
index ce7e2e59..b2e6e43d 100644
--- a/common/src/main/java/com/axalotl/async/common/mixin/entity/AttributeInstanceMixin.java
+++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/AttributeInstanceMixin.java
@@ -1,6 +1,6 @@
package com.axalotl.async.common.mixin.entity;
-import com.axalotl.async.common.parallelised.ConcurrentCollections;
+import com.axalotl.async.api.utils.ConcurrentCollections;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import net.minecraft.resources.ResourceLocation;
@@ -25,6 +25,6 @@ public class AttributeInstanceMixin {
@WrapMethod(method = "getModifiers(Lnet/minecraft/world/entity/ai/attributes/AttributeModifier$Operation;)Ljava/util/Map;")
private Map getModifiersConcurrent(AttributeModifier.Operation operation, Operation