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 โš™๏ธ - -[![Modrinth Downloads](https://img.shields.io/modrinth/dt/async?style=for-the-badge&logo=modrinth)](https://modrinth.com/mod/async) -[![Discord](https://img.shields.io/discord/YOUR_DISCORD_ID?style=for-the-badge&logo=discord&label=Discord)](https://discord.com/invite/scvCQ2qKS3) -[![GitHub Issues](https://img.shields.io/github/issues/AxalotLDev/Async?style=for-the-badge)](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: [![Chat with us on Discord](https://img.shields.io/badge/Chat%20with%20us%20on-Discord-blue)](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 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: + *

+ * + *

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 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 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 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 entityType, Level level, CallbackInfo ci) { + private void makeCollectionsThreadSafe(EntityType 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> original) { - return modifiersByOperation.computeIfAbsent(operation, op -> ConcurrentCollections.newHashMap()); + return modifiersByOperation.computeIfAbsent(operation, ignored -> ConcurrentCollections.newHashMap()); } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/AttributeMapMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/AttributeMapMixin.java index 366b46e0..ba812537 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/AttributeMapMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/AttributeMapMixin.java @@ -1,26 +1,44 @@ package com.axalotl.async.common.mixin.entity; -import com.axalotl.async.common.parallelised.ConcurrentCollections; +import com.axalotl.async.api.utils.ConcurrentCollections; import net.minecraft.core.Holder; import net.minecraft.world.entity.ai.attributes.Attribute; import net.minecraft.world.entity.ai.attributes.AttributeInstance; import net.minecraft.world.entity.ai.attributes.AttributeMap; +import net.minecraft.world.entity.ai.attributes.AttributeSupplier; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -@Mixin(AttributeMap.class) +@Mixin(value = AttributeMap.class, priority = 1500) public class AttributeMapMixin { @Shadow - private final Set attributesToSync = ConcurrentHashMap.newKeySet(); + private final Map, AttributeInstance> attributes = ConcurrentCollections.newHashMap(); + @Mutable @Shadow - private final Map, AttributeInstance> attributes = ConcurrentCollections.newHashMap(); + @Final + private Set attributesToUpdate; + @Mutable @Shadow - private final Set attributesToUpdate = ConcurrentHashMap.newKeySet(); + @Final + private Set attributesToSync; + + @Inject( + method = "", + at = @At("RETURN") + ) + private void initCollections(AttributeSupplier supplier, CallbackInfo ci) { + this.attributesToUpdate = ConcurrentCollections.newHashSet(); + this.attributesToSync = ConcurrentCollections.newHashSet(); + } } \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/BeeMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/BeeMixin.java index 809a3627..49b5b587 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/BeeMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/BeeMixin.java @@ -4,17 +4,14 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import net.minecraft.world.entity.animal.Bee; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; @Mixin(Bee.class) public class BeeMixin { - @Unique - private static final Object async$lock = new Object(); @WrapMethod(method = "wantsToEnterHive") - private boolean loot(Operation original) { - synchronized (async$lock) { + private boolean wantsToEnterHive(Operation original) { + synchronized (this) { return original.call(); } } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/BrainMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/BrainMixin.java index 21b604fb..def1bb1b 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/BrainMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/BrainMixin.java @@ -1,142 +1,50 @@ package com.axalotl.async.common.mixin.entity; -import com.axalotl.async.common.config.AsyncConfig; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.ai.Brain; import net.minecraft.world.entity.ai.memory.ExpirableValue; import net.minecraft.world.entity.ai.memory.MemoryModuleType; import net.minecraft.world.entity.ai.memory.MemoryStatus; -import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import java.util.Map; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; @Mixin(value = Brain.class, priority = 1500) -public class BrainMixin { +public class BrainMixin { - @Shadow - @Final - private Map, Optional>> memories; - - @Unique - private volatile Map, Optional>> async$snapshot; - - @Unique - private volatile boolean async$needsRebuild = true; - - @Unique - private volatile boolean async$inTick; - - @Unique - private final Object async$writeLock = new Object(); - - @Inject(method = "tick", at = @At("HEAD")) - private void async$buildSnapshot(ServerLevel level, E entity, CallbackInfo ci) { - if (AsyncConfig.disabled) return; - - if (async$needsRebuild || async$snapshot == null) { - synchronized (async$writeLock) { - if (async$needsRebuild || async$snapshot == null) { - async$snapshot = new ConcurrentHashMap<>(this.memories); - async$needsRebuild = false; - } - } - } - - async$inTick = true; - } - - @Inject(method = "tick", at = @At("RETURN")) - private void async$endTick(ServerLevel level, E entity, CallbackInfo ci) { - async$inTick = false; - } - - @Inject(method = "getMemory", at = @At("HEAD"), cancellable = true) - private void async$getMemory(MemoryModuleType type, CallbackInfoReturnable> cir) { - if (AsyncConfig.disabled) return; - - Map, Optional>> snapshot = async$snapshot; - if (async$inTick && snapshot != null) { - Optional> value = snapshot.get(type); - if (value == null) { - cir.setReturnValue(Optional.empty()); - return; - } - @SuppressWarnings("unchecked") - Optional result = (Optional) value.map(ExpirableValue::getValue); - cir.setReturnValue(result); + @WrapMethod(method = "getMemory") + private Optional getMemory(MemoryModuleType type, Operation> original) { + synchronized (this) { + return original.call(type); } } - @Inject(method = "hasMemoryValue", at = @At("HEAD"), cancellable = true) - private void async$hasMemoryValue(MemoryModuleType type, CallbackInfoReturnable cir) { - if (AsyncConfig.disabled) return; - - Map, Optional>> snapshot = async$snapshot; - if (async$inTick && snapshot != null) { - Optional> value = snapshot.get(type); - cir.setReturnValue(value != null && value.isPresent()); + @WrapMethod(method = "hasMemoryValue") + private boolean hasMemoryValue(MemoryModuleType type, Operation original) { + synchronized (this) { + return original.call(type); } } - @Inject(method = "checkMemory", at = @At("HEAD"), cancellable = true) - private void async$checkMemory(MemoryModuleType type, MemoryStatus status, CallbackInfoReturnable cir) { - if (AsyncConfig.disabled) return; - - Map, Optional>> snapshot = async$snapshot; - if (async$inTick && snapshot != null) { - Optional> value = snapshot.get(type); - - boolean result = switch (status) { - case REGISTERED -> true; - case VALUE_PRESENT -> value != null && value.isPresent(); - case VALUE_ABSENT -> value == null || value.isEmpty(); - }; - - cir.setReturnValue(result); + @WrapMethod(method = "checkMemory") + private boolean checkMemory(MemoryModuleType type, MemoryStatus status, Operation original) { + synchronized (this) { + return original.call(type, status); } } @WrapMethod(method = "setMemoryInternal") - private void async$setMemory( - MemoryModuleType memoryType, - Optional> memory, - Operation original - ) { - if (AsyncConfig.disabled) { - original.call(memoryType, memory); - return; - } - - synchronized (async$writeLock) { - original.call(memoryType, memory); - if (async$snapshot != null && async$snapshot.containsKey(memoryType)) { - async$snapshot.put(memoryType, memory); - } + private void setMemory(MemoryModuleType type, Optional> value, Operation original) { + synchronized (this) { + original.call(type, value); } } @WrapMethod(method = "clearMemories") - private void async$clearMemories(Operation original) { - if (AsyncConfig.disabled) { - original.call(); - return; - } - - synchronized (async$writeLock) { + private void clearMemories(Operation original) { + synchronized (this) { original.call(); - async$needsRebuild = true; } } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/DolphinMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/DolphinMixin.java index 5e522004..ae906b00 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/DolphinMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/DolphinMixin.java @@ -1,5 +1,6 @@ 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.EntityType; @@ -8,24 +9,16 @@ import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.level.Level; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; @Mixin(Dolphin.class) public abstract class DolphinMixin extends WaterAnimal { - @Unique - private static final Object async$lock = new Object(); - - protected DolphinMixin(EntityType entityType, Level world) { - super(entityType, world); + protected DolphinMixin(EntityType entityType, Level level) { + super(entityType, level); } @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/EntityLookupMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/EntityLookupMixin.java index fbaef4a0..d0c52d60 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/EntityLookupMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/EntityLookupMixin.java @@ -1,7 +1,7 @@ package com.axalotl.async.common.mixin.entity; -import com.axalotl.async.common.parallelised.ConcurrentCollections; -import com.axalotl.async.common.parallelised.fastutil.Int2ObjectConcurrentHashMap; +import com.axalotl.async.api.fastutil.Int2ObjectConcurrentHashMap; +import com.axalotl.async.api.utils.ConcurrentCollections; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -49,7 +49,7 @@ private void threadSafeAdd(T entity, CallbackInfo ci) { UUID uuid = entity.getUUID(); int id = entity.getId(); - byUuid.compute(uuid, (k, existing) -> { + byUuid.compute(uuid, (key, existing) -> { if (existing == null) { byId.put(id, entity); return entity; @@ -70,7 +70,7 @@ private void threadSafeRemove(T entity, CallbackInfo ci) { UUID uuid = entity.getUUID(); int id = entity.getId(); - byUuid.computeIfPresent(uuid, (k, existing) -> { + byUuid.computeIfPresent(uuid, (key, existing) -> { if (existing.getId() == id) { byId.remove(id); return null; @@ -80,12 +80,12 @@ private void threadSafeRemove(T entity, CallbackInfo ci) { } @WrapMethod(method = "getEntity(Ljava/util/UUID;)Lnet/minecraft/world/level/entity/EntityAccess;") - private T getEntity(UUID uuid, Operation original) { - return uuid == null ? null : original.call(uuid); + private T getEntity(UUID id, Operation original) { + return id == null ? null : original.call(id); } @WrapMethod(method = "getEntity(I)Lnet/minecraft/world/level/entity/EntityAccess;") private T getEntity1(int id, Operation original) { return id == 0 ? null : original.call(id); } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/EntityMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/EntityMixin.java index 6bac2784..7f1e166b 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/EntityMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/EntityMixin.java @@ -2,10 +2,13 @@ import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; @@ -16,48 +19,62 @@ public abstract class EntityMixin { @Shadow public abstract Level level(); - @Unique - private static final Object async$lock = new Object(); + @Shadow + public abstract BlockPos blockPosition(); @WrapMethod(method = "setRemoved") private void setRemoved(Entity.RemovalReason reason, Operation original) { - synchronized (async$lock) { + synchronized (this) { original.call(reason); } } @WrapMethod(method = "getInBlockState") private BlockState wrapGetInBlockState(Operation original) { - BlockState blockState = original.call(); - return blockState != null ? blockState : Blocks.AIR.defaultBlockState(); + BlockState state = original.call(); + return state != null ? state : getBlockStateNow(); + } + + @Unique + private BlockState getBlockStateNow() { + Level level = this.level(); + if (level instanceof ServerLevel serverLevel) { + BlockPos pos = this.blockPosition(); + ChunkAccess chunk = serverLevel.getChunkSource() + .getChunkNow(pos.getX() >> 4, pos.getZ() >> 4); + if (chunk != null) { + return chunk.getBlockState(pos); + } + } + return Blocks.AIR.defaultBlockState(); } @WrapMethod(method = "addPassenger") private void addPassenger(Entity passenger, Operation original) { - synchronized (async$lock) { + synchronized (this) { original.call(passenger); } } @WrapMethod(method = "removePassenger") private void removePassenger(Entity passenger, Operation original) { - synchronized (async$lock) { + synchronized (this) { original.call(passenger); } } @WrapMethod(method = "ejectPassengers") private void ejectPassengers(Operation original) { - synchronized (async$lock) { + synchronized (this) { original.call(); } } @WrapMethod(method = "startRiding(Lnet/minecraft/world/entity/Entity;Z)Z") - private boolean startRiding(Entity vehicle, boolean force, Operation original) { + private boolean startRiding(Entity entityToRide, boolean force, Operation original) { synchronized (this) { - return original.call(vehicle, force); + return original.call(entityToRide, force); } } @@ -67,4 +84,4 @@ private void removeVehicle(Operation original) { original.call(); } } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/EntityTickListMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/EntityTickListMixin.java index 721d3a65..62f540ea 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/EntityTickListMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/EntityTickListMixin.java @@ -1,6 +1,6 @@ package com.axalotl.async.common.mixin.entity; -import com.axalotl.async.common.parallelised.utils.IteratorSafeOrderedReferenceSet; +import com.axalotl.async.api.utils.IteratorSafeOrderedReferenceSet; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import net.minecraft.world.entity.Entity; @@ -13,21 +13,21 @@ @Mixin(EntityTickList.class) public class EntityTickListMixin { @Unique - public final IteratorSafeOrderedReferenceSet async$entities = new IteratorSafeOrderedReferenceSet<>(); + public final IteratorSafeOrderedReferenceSet entities = new IteratorSafeOrderedReferenceSet<>(); @WrapMethod(method = "add") private void add(Entity entity, Operation original) { - this.async$entities.add(entity); + this.entities.add(entity); } @WrapMethod(method = "remove") private void remove(Entity entity, Operation original) { - this.async$entities.remove(entity); + this.entities.remove(entity); } @WrapMethod(method = "contains") private boolean contains(Entity entity, Operation original) { - return this.async$entities.contains(entity); + return this.entities.contains(entity); } @WrapMethod(method = "ensureActiveIsNotIterated") @@ -35,11 +35,11 @@ private void ensureActiveIsNotIterated(Operation original) { } @WrapMethod(method = "forEach") - private void forEach(Consumer p_entity, Operation original) { - final IteratorSafeOrderedReferenceSet.Iterator iterator = this.async$entities.iterator(); + private void forEach(Consumer output, Operation original) { + final IteratorSafeOrderedReferenceSet.Iterator iterator = this.entities.iterator(); try { while (iterator.hasNext()) { - p_entity.accept(iterator.next()); + output.accept(iterator.next()); } } finally { iterator.finishedIterating(); diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/FoxMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/FoxMixin.java index f07e459f..0f132ea4 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/FoxMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/FoxMixin.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.Fox; import net.minecraft.world.entity.item.ItemEntity; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; @Mixin(Fox.class) public class FoxMixin { - @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/GoalSelectorMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/GoalSelectorMixin.java index 0efc82e8..d76e77eb 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/GoalSelectorMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/GoalSelectorMixin.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.ai.goal.GoalSelector; import net.minecraft.world.entity.ai.goal.WrappedGoal; import org.spongepowered.asm.mixin.Mixin; diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/GossipContainerMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/GossipContainerMixin.java index 2c0c7878..869259a3 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/GossipContainerMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/GossipContainerMixin.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.ai.gossip.GossipContainer; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/InteractWithDoorMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/InteractWithDoorMixin.java index 93bc2680..66bcf070 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/InteractWithDoorMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/InteractWithDoorMixin.java @@ -19,8 +19,8 @@ public class InteractWithDoorMixin { at = @At("HEAD"), cancellable = true ) - private static void injectIsMobComingThroughDoor(Brain brain, BlockPos pos, CallbackInfoReturnable cir) { - Path path = brain.getMemory(MemoryModuleType.PATH).orElse(null); + private static void injectIsMobComingThroughDoor(Brain otherBrain, BlockPos doorPos, CallbackInfoReturnable cir) { + Path path = otherBrain.getMemory(MemoryModuleType.PATH).orElse(null); if (path == null || path.isDone()) { cir.setReturnValue(false); return; @@ -33,6 +33,6 @@ private static void injectIsMobComingThroughDoor(Brain brain, BlockPos pos, C } Node nextNode = path.getNextNode(); - cir.setReturnValue(pos.equals(prevNode.asBlockPos()) || pos.equals(nextNode.asBlockPos())); + cir.setReturnValue(doorPos.equals(prevNode.asBlockPos()) || doorPos.equals(nextNode.asBlockPos())); } } \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/LivingEntityMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/LivingEntityMixin.java index 33f65c11..9a8797ee 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/LivingEntityMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/LivingEntityMixin.java @@ -13,78 +13,74 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; -@Mixin(value = LivingEntity.class, priority = 1001) +@Mixin(value = LivingEntity.class) public abstract class LivingEntityMixin extends Entity { @Shadow final private Map, MobEffectInstance> activeEffects = new ConcurrentHashMap<>(); - @Unique - private static final Object async$lock = new Object(); - public LivingEntityMixin(EntityType type, Level world) { super(type, world); } @WrapMethod(method = "die") - private synchronized void die(DamageSource damageSource, Operation original) { - original.call(damageSource); + private synchronized void die(DamageSource source, Operation original) { + original.call(source); } @WrapMethod(method = "dropFromLootTable") - private synchronized void dropFromLootTable(DamageSource damageSource, boolean causedByPlayer, Operation original) { - original.call(damageSource, causedByPlayer); + private synchronized void dropFromLootTable(DamageSource source, boolean playerKilled, Operation original) { + original.call(source, playerKilled); } - @WrapMethod(method = "blockedByShield") - private void knockback(LivingEntity defender, Operation original) { - synchronized (async$lock) { - original.call(defender); + @WrapMethod(method = "knockback") + private void knockback(double power, double xd, double zd, Operation original) { + synchronized (this) { + original.call(power, xd, zd); } } @WrapMethod(method = "tickEffects") private void tickStatusEffects(Operation original) { - synchronized (async$lock) { + synchronized (this) { original.call(); } } - @WrapOperation(method = "tickEffects", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/effect/MobEffectInstance;tick(Lnet/minecraft/world/entity/LivingEntity;Ljava/lang/Runnable;)Z")) - private boolean tickEffects(MobEffectInstance instance, LivingEntity entity, Runnable runnable, Operation original) { - return instance != null ? original.call(instance, entity, runnable) : false; - } - - @Inject(method = "onEffectRemoved", at = @At("HEAD"), cancellable = true) - private void onEffectRemoved(MobEffectInstance effectInstance, CallbackInfo ci) { - if (effectInstance == null) { - ci.cancel(); - } + @WrapOperation( + method = "tickEffects", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/effect/MobEffectInstance;tick(Lnet/minecraft/world/entity/LivingEntity;Ljava/lang/Runnable;)Z" + ) + ) + private boolean wrapTickEffect(MobEffectInstance instance, LivingEntity target, Runnable onEffectUpdate, Operation original) { + return instance != null ? original.call(instance, target, onEffectUpdate) : false; } @WrapMethod(method = "addEffect(Lnet/minecraft/world/effect/MobEffectInstance;Lnet/minecraft/world/entity/Entity;)Z") - private boolean addEffect(MobEffectInstance effect, Entity source, Operation original) { - synchronized (async$lock) { - return effect != null ? original.call(effect, source) : false; + private boolean addEffect(MobEffectInstance newEffect, Entity source, Operation original) { + synchronized (this) { + return newEffect != null ? original.call(newEffect, source) : false; } } @WrapMethod(method = "removeEffect") private boolean removeEffect(Holder effect, Operation original) { - synchronized (async$lock) { + synchronized (this) { return effect != null ? original.call(effect) : false; } } @@ -96,13 +92,13 @@ public boolean hasEffect(Holder effect, Operation original) @WrapMethod(method = "removeAllEffects") private boolean removeAllEffects(Operation original) { - synchronized (async$lock) { + synchronized (this) { return original.call(); } } @Inject(method = "causeFallDamage", at = @At("HEAD"), cancellable = true) - private void causeFallDamage(float fallDistance, float multiplier, DamageSource source, CallbackInfoReturnable cir) { + private void causeFallDamage(float fallDistance, float damageModifier, DamageSource damageSource, CallbackInfoReturnable cir) { BlockPos pos = new BlockPos(Mth.floor(this.getX()), Mth.floor(this.getY()), Mth.floor(this.getZ())); BlockState currentBlock = this.level().getBlockState(pos); @@ -110,4 +106,14 @@ private void causeFallDamage(float fallDistance, float multiplier, DamageSource cir.setReturnValue(false); } } -} \ No newline at end of file + + @Redirect(method = "refreshDirtyAttributes", at = @At(value = "INVOKE", target = "Ljava/util/Set;iterator()Ljava/util/Iterator;")) + private Iterator snapshotIterator(Set set) { + Set snapshot; + synchronized (this) { + snapshot = new HashSet<>(set); + set.clear(); + } + return snapshot.iterator(); + } +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/MinecartHopperMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/MinecartHopperMixin.java new file mode 100644 index 00000000..7fe5e5eb --- /dev/null +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/MinecartHopperMixin.java @@ -0,0 +1,58 @@ +package com.axalotl.async.common.mixin.entity; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.core.BlockPos; +import net.minecraft.world.Container; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.vehicle.MinecartHopper; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.Hopper; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(MinecartHopper.class) +public class MinecartHopperMixin { + @Unique + private static final Object[] POSITION_LOCKS = new Object[256]; + + static { + for (int i = 0; i < POSITION_LOCKS.length; i++) { + POSITION_LOCKS[i] = new Object(); + } + } + + @Unique + private static Object lockForPos(BlockPos pos) { + return POSITION_LOCKS[pos.hashCode() & 0xFF]; + } + + @WrapOperation( + method = "suckInItems", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/level/block/entity/HopperBlockEntity;suckInItems(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/level/block/entity/Hopper;)Z" + ) + ) + private boolean wrapSuckInItems(Level level, Hopper hopper, Operation original) { + BlockPos blockPos = BlockPos.containing(hopper.getLevelX(), hopper.getLevelY() + (double) 1.0F, hopper.getLevelZ()); + synchronized (lockForPos(blockPos)) { + return original.call(level, hopper); + } + } + + @WrapOperation( + method = "suckInItems", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/level/block/entity/HopperBlockEntity;addItem(Lnet/minecraft/world/Container;Lnet/minecraft/world/entity/item/ItemEntity;)Z" + ) + ) + private boolean wrapItemPickup(Container container, ItemEntity entity, Operation original) { + synchronized (entity) { + if (!entity.isAlive()) return false; + return original.call(container, entity); + } + } +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/MobMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/MobMixin.java index 1a441d12..db5623a9 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/MobMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/MobMixin.java @@ -1,5 +1,6 @@ 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.EquipmentSlot; @@ -7,46 +8,40 @@ import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.item.ItemStack; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; @Mixin(Mob.class) public class MobMixin { - @Unique - private static final Object async$lock = new Object(); - @WrapMethod(method = "equipItemIfPossible") - private ItemStack tryEquip(ItemStack stack, Operation original) { - synchronized (async$lock) { - return original.call(stack); + private ItemStack tryEquip(ItemStack itemStack, Operation original) { + synchronized (this) { + return original.call(itemStack); } } @WrapMethod(method = "pickUpItem") - private void pickUpItem(ItemEntity itemEntity, Operation original) { - synchronized (async$lock) { - original.call(itemEntity); - } + private void pickUpItem(ItemEntity entity, Operation original) { + SyncItemPickup.wrap(entity, () -> original.call(entity)); } @WrapMethod(method = "setItemSlot") - private void equipStack(EquipmentSlot slot, ItemStack stack, Operation original) { - synchronized (async$lock) { - original.call(slot, stack); + private void equipStack(EquipmentSlot slot, ItemStack itemStack, Operation original) { + synchronized (this) { + original.call(slot, itemStack); } } @WrapMethod(method = "setItemSlotAndDropWhenKilled") - private void equipLootStack(EquipmentSlot slot, ItemStack stack, Operation original) { - synchronized (async$lock) { - original.call(slot, stack); + private void equipLootStack(EquipmentSlot slot, ItemStack itemStack, Operation original) { + synchronized (this) { + original.call(slot, itemStack); } } @WrapMethod(method = "setBodyArmorItem") - private void equipLootStack(ItemStack stack, Operation original) { - synchronized (async$lock) { - original.call(stack); + private void equipLootStack(ItemStack item, Operation original) { + synchronized (this) { + original.call(item); } } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/NearestVisibleLivingEntitiesMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/NearestVisibleLivingEntitiesMixin.java index 369bfc4e..76f9409a 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/NearestVisibleLivingEntitiesMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/NearestVisibleLivingEntitiesMixin.java @@ -24,13 +24,13 @@ public class NearestVisibleLivingEntitiesMixin { private Predicate lineOfSightTest; @Inject(method = "(Lnet/minecraft/world/entity/LivingEntity;Ljava/util/List;)V", at = @At("RETURN")) - private void init(LivingEntity owner, List entities, CallbackInfo ci) { - Object2BooleanOpenHashMap object2BooleanOpenHashMap = new Object2BooleanOpenHashMap<>(entities.size()); - Predicate predicate = target -> Sensor.isEntityTargetable(owner, target); + private void init(LivingEntity body, List livingEntities, CallbackInfo ci) { + Object2BooleanOpenHashMap object2BooleanOpenHashMap = new Object2BooleanOpenHashMap<>(livingEntities.size()); + Predicate predicate = target -> Sensor.isEntityTargetable(body, target); this.lineOfSightTest = entity -> { synchronized (object2BooleanOpenHashMap) { return object2BooleanOpenHashMap.computeIfAbsent(entity, predicate); } }; } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/PandaMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/PandaMixin.java index 072bfb34..097c88f3 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/PandaMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/PandaMixin.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.Panda; import net.minecraft.world.entity.item.ItemEntity; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; @Mixin(Panda.class) public class PandaMixin { - @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/PiglinMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/PiglinMixin.java index 5950d09c..7020ee80 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/PiglinMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/PiglinMixin.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.item.ItemEntity; import net.minecraft.world.entity.monster.piglin.Piglin; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; @Mixin(Piglin.class) public class PiglinMixin { - @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/PlayTagWithOtherKidsMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/PlayTagWithOtherKidsMixin.java index 4390ca63..637e1f70 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/PlayTagWithOtherKidsMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/PlayTagWithOtherKidsMixin.java @@ -12,7 +12,7 @@ public class PlayTagWithOtherKidsMixin { @Inject(method = "whoAreYouChasing", at = @At("HEAD"), cancellable = true) - private static void onWhoAreYouChasing(LivingEntity baby, CallbackInfoReturnable cir) { - cir.setReturnValue(baby.getBrain().getMemory(MemoryModuleType.INTERACTION_TARGET).orElse(null)); + private static void onWhoAreYouChasing(LivingEntity friend, CallbackInfoReturnable cir) { + cir.setReturnValue(friend.getBrain().getMemory(MemoryModuleType.INTERACTION_TARGET).orElse(null)); } } \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/RaidMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/RaidMixin.java index 71a6f1fd..98f59dc0 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/RaidMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/RaidMixin.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.world.entity.raid.Raid; @@ -40,7 +40,7 @@ private boolean addWaveMob(int wave, Raider entity, boolean countHealth, Operati @Redirect(method = "addWaveMob(ILnet/minecraft/world/entity/raid/Raider;Z)Z", at = @At(value = "INVOKE", target = "Ljava/util/Map;computeIfAbsent(Ljava/lang/Object;Ljava/util/function/Function;)Ljava/lang/Object;")) - private Object redirectComputeIfAbsent(Map> instance, Object k, Function key) { - return instance.computeIfAbsent((Integer) k, wave -> ConcurrentCollections.newHashSet()); + private Object redirectComputeIfAbsent(Map> instance, Object key, Function mappingFunction) { + return instance.computeIfAbsent((Integer) key, ignored -> ConcurrentCollections.newHashSet()); } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/RaiderMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/RaiderMixin.java index c1513249..42447adf 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/RaiderMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/RaiderMixin.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.item.ItemEntity; import net.minecraft.world.entity.raid.Raider; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; @Mixin(Raider.class) public class RaiderMixin { - @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/VillagerMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/VillagerMixin.java index 657a6e24..549a1feb 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/VillagerMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/VillagerMixin.java @@ -1,32 +1,25 @@ 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.server.level.ServerLevel; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.npc.Villager; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; @Mixin(Villager.class) public class VillagerMixin { - @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)); } @WrapMethod(method = "spawnGolemIfNeeded") - private void spawnGolemIfNeeded(ServerLevel world, long time, int requiredCount, Operation original) { - synchronized (async$lock) { - original.call(world, time, requiredCount); + private void spawnGolemIfNeeded(ServerLevel level, long timestamp, int villagersNeededToAgree, Operation original) { + synchronized (level) { + original.call(level, timestamp, villagersNeededToAgree); } } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/AnimalMakeLoveMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/AnimalMakeLoveMixin.java index 2e651089..a5817114 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/AnimalMakeLoveMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/AnimalMakeLoveMixin.java @@ -6,7 +6,6 @@ import net.minecraft.world.entity.animal.Animal; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -15,31 +14,28 @@ @Mixin(AnimalMakeLove.class) public abstract class AnimalMakeLoveMixin { - @Unique - private static final Object async$lock = new Object(); - @Shadow - protected abstract Animal getBreedTarget(Animal animal); + protected abstract Animal getBreedTarget(Animal body); @Inject(method = "tick(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/animal/Animal;J)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/ai/behavior/AnimalMakeLove;getBreedTarget(Lnet/minecraft/world/entity/animal/Animal;)Lnet/minecraft/world/entity/animal/Animal;"), cancellable = true) - private void tick(ServerLevel level, Animal owner, long gameTime, CallbackInfo ci) { - if (this.getBreedTarget(owner) == null) { + private void tick(ServerLevel level, Animal body, long timestamp, CallbackInfo ci) { + if (this.getBreedTarget(body) == null) { ci.cancel(); } } @Inject(method = "canStillUse(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/animal/Animal;J)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/ai/behavior/AnimalMakeLove;getBreedTarget(Lnet/minecraft/world/entity/animal/Animal;)Lnet/minecraft/world/entity/animal/Animal;"), cancellable = true) - private void canStillUse(ServerLevel level, Animal entity, long gameTime, CallbackInfoReturnable cir) { - if (this.getBreedTarget(entity) == null) { + private void canStillUse(ServerLevel level, Animal body, long timestamp, CallbackInfoReturnable cir) { + if (this.getBreedTarget(body) == null) { cir.cancel(); } } @Inject(method = "getBreedTarget", at = @At("HEAD"), cancellable = true) - private void syncBreedTarget(Animal animal, CallbackInfoReturnable cir) { - synchronized (async$lock) { - cir.setReturnValue((Animal) animal.getBrain().getMemory(MemoryModuleType.BREED_TARGET).orElse(null)); + private void syncBreedTarget(Animal body, CallbackInfoReturnable cir) { + synchronized (body) { + cir.setReturnValue((Animal) body.getBrain().getMemory(MemoryModuleType.BREED_TARGET).orElse(null)); } } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/FoxBreedGoalMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/FoxBreedGoalMixin.java index 7dacc97c..7a0127d6 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/FoxBreedGoalMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/FoxBreedGoalMixin.java @@ -8,7 +8,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import java.util.UUID; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Mixin(Fox.FoxBreedGoal.class) @@ -19,36 +19,26 @@ public FoxBreedGoalMixin(Fox fox, double speedModifier) { } @Unique - private static final ConcurrentHashMap async$breedingPairs = new ConcurrentHashMap<>(); + private static final Map breedingPairs = new ConcurrentHashMap<>(); @Inject(method = "start", at = @At("HEAD")) private void resetBreedingFlag(CallbackInfo ci) { - async$breedingPairs.remove(async$getPairKey()); + breedingPairs.remove(getPairKey()); } @Inject(method = "breed", at = @At("HEAD"), cancellable = true) private void preventDoubleBreed(CallbackInfo ci) { - String pairKey = async$getPairKey(); - if (async$breedingPairs.putIfAbsent(pairKey, Boolean.TRUE) != null) { + String pairKey = getPairKey(); + if (breedingPairs.putIfAbsent(pairKey, Boolean.TRUE) != null) { ci.cancel(); } } @Unique - private String async$getPairKey() { - UUID id1 = this.animal.getUUID(); - UUID id2 = null; - if (this.partner != null) { - id2 = this.partner.getUUID(); - } - String s1 = id1.toString(); - String s2 = null; - if (id2 != null) { - s2 = id2.toString(); - } - if (s2 != null) { - return s1.compareTo(s2) <= 0 ? s1 + "|" + s2 : s2 + "|" + s1; - } - return s1; + private String getPairKey() { + String s1 = this.animal.getUUID().toString(); + if (this.partner == null) return s1; + String s2 = this.partner.getUUID().toString(); + return s1.compareTo(s2) <= 0 ? s1 + "|" + s2 : s2 + "|" + s1; } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/FrogMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/FrogMixin.java index 6e708c99..7c9e2ec5 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/FrogMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/FrogMixin.java @@ -16,22 +16,27 @@ public abstract class FrogMixin extends Animal { @Unique - private final AtomicBoolean async$breedingFlag = new AtomicBoolean(false); + private final AtomicBoolean breedingFlag = new AtomicBoolean(false); protected FrogMixin(EntityType entityType, Level world) { super(entityType, world); } @WrapMethod(method = "spawnChildFromBreeding") - private void breed(ServerLevel world, Animal other, Operation original) { - if (this.getId() > other.getId()) return; - FrogMixin otherMixin = (FrogMixin) 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; + FrogMixin other = (FrogMixin) 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); } } } diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/SnifferMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/SnifferMixin.java index 24a6356a..3a5c5b4a 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/SnifferMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/SnifferMixin.java @@ -16,22 +16,27 @@ public abstract class SnifferMixin extends Animal { @Unique - private final AtomicBoolean async$breedingFlag = new AtomicBoolean(false); + private final AtomicBoolean breedingFlag = new AtomicBoolean(false); protected SnifferMixin(EntityType entityType, Level world) { super(entityType, world); } @WrapMethod(method = "spawnChildFromBreeding") - private void breed(ServerLevel world, Animal other, Operation original) { - if (this.getId() > other.getId()) return; - SnifferMixin otherMixin = (SnifferMixin) 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; + SnifferMixin other = (SnifferMixin) 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); } } } diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/TurtleMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/TurtleMixin.java index 852452d4..1d77969c 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/TurtleMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/breed/TurtleMixin.java @@ -21,13 +21,24 @@ public TurtleMixin(Animal animal, double speed) { } @Redirect(method = "breed()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/animal/Turtle;setHasEgg(Z)V")) - private void redirectSetHasEgg(Turtle instance, boolean value) { - if (this.partner != null && !this.turtle.hasEgg() && !((Turtle) this.partner).hasEgg()) { - if (this.turtle.getRandom().nextBoolean()) { - this.turtle.setHasEgg(true); - } else { - ((Turtle) this.partner).setHasEgg(true); + private void redirectSetHasEgg(Turtle instance, boolean onOff) { + if (this.partner == null) return; + + Turtle t1 = this.turtle.getId() < this.partner.getId() + ? this.turtle : (Turtle) this.partner; + Turtle t2 = t1 == this.turtle + ? (Turtle) this.partner : this.turtle; + + synchronized (t1) { + synchronized (t2) { + if (!this.turtle.hasEgg() && !((Turtle) this.partner).hasEgg()) { + if (this.turtle.getRandom().nextBoolean()) { + this.turtle.setHasEgg(true); + } else { + ((Turtle) this.partner).setHasEgg(true); + } + } } } } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/movement/EntitySectionMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/movement/EntitySectionMixin.java index da2add04..ff9c1519 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/movement/EntitySectionMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/movement/EntitySectionMixin.java @@ -2,14 +2,16 @@ import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import net.minecraft.util.AbortableIterationConsumer; import net.minecraft.util.ClassInstanceMultiMap; import net.minecraft.world.level.entity.EntityAccess; import net.minecraft.world.level.entity.EntitySection; +import net.minecraft.world.level.entity.EntityTypeTest; import net.minecraft.world.level.entity.Visibility; +import net.minecraft.world.phys.AABB; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; import java.util.Objects; import java.util.stream.Stream; @@ -24,30 +26,41 @@ public class EntitySectionMixin { @Shadow private volatile Visibility chunkStatus; - @Unique - private final Object async$storageLock = new Object(); - @WrapMethod(method = "add") - private void async$add(EntityAccess entity, Operation original) { - synchronized (async$storageLock) { + private void add(EntityAccess entity, Operation original) { + synchronized (this) { original.call(entity); } } @WrapMethod(method = "remove") - private boolean async$remove(EntityAccess entity, Operation original) { - synchronized (async$storageLock) { + private boolean remove(EntityAccess entity, Operation original) { + synchronized (this) { return original.call(entity); } } @WrapMethod(method = "getEntities()Ljava/util/stream/Stream;") - private Stream async$getEntities(Operation> original) { - synchronized (async$storageLock) { + private Stream getEntities(Operation> original) { + synchronized (this) { return storage.stream() .filter(Objects::nonNull) .toList() .stream(); } } + + @WrapMethod(method = "getEntities(Lnet/minecraft/world/phys/AABB;Lnet/minecraft/util/AbortableIterationConsumer;)Lnet/minecraft/util/AbortableIterationConsumer$Continuation;") + private AbortableIterationConsumer.Continuation getEntities(AABB bb, AbortableIterationConsumer entities, Operation original) { + synchronized (this) { + return original.call(bb, entities); + } + } + + @WrapMethod(method = "getEntities(Lnet/minecraft/world/level/entity/EntityTypeTest;Lnet/minecraft/world/phys/AABB;Lnet/minecraft/util/AbortableIterationConsumer;)Lnet/minecraft/util/AbortableIterationConsumer$Continuation;") + private AbortableIterationConsumer.Continuation getEntities(EntityTypeTest type, AABB bb, AbortableIterationConsumer consumer, Operation original) { + synchronized (this) { + return original.call(type, bb, consumer); + } + } } \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/movement/EntitySectionStorageMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/movement/EntitySectionStorageMixin.java index 343eb1c7..903c70d2 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/movement/EntitySectionStorageMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/movement/EntitySectionStorageMixin.java @@ -1,7 +1,7 @@ package com.axalotl.async.common.mixin.entity.movement; -import com.axalotl.async.common.parallelised.fastutil.ConcurrentLongSortedSet; -import com.axalotl.async.common.parallelised.fastutil.Long2ObjectConcurrentHashMap; +import com.axalotl.async.api.fastutil.ConcurrentLongSortedSet; +import com.axalotl.async.api.fastutil.Long2ObjectConcurrentHashMap; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; @@ -9,32 +9,46 @@ import net.minecraft.world.level.entity.EntityAccess; import net.minecraft.world.level.entity.EntitySection; import net.minecraft.world.level.entity.EntitySectionStorage; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.*; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.Objects; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.LongStream; import java.util.stream.Stream; -@Mixin(value = EntitySectionStorage.class) +@Mixin(value = EntitySectionStorage.class, priority = 1500) public abstract class EntitySectionStorageMixin { + @Unique + private ReentrantReadWriteLock.WriteLock writeLock; + + @Mutable + @Final @Shadow - private final Long2ObjectMap> sections = new Long2ObjectConcurrentHashMap<>(); + private Long2ObjectMap> sections; + @Mutable + @Final @Shadow - private final LongSortedSet sectionIds = new ConcurrentLongSortedSet(); + private LongSortedSet sectionIds; @Shadow - public abstract LongStream getExistingSectionPositionsInChunk(long pos); + public abstract LongStream getExistingSectionPositionsInChunk(long chunkKey); - @Unique - private static final Object async$lock = new Object(); + @Inject(method = "", at = @At("RETURN")) + private void replaceWithConcurrentCollections(CallbackInfo ci) { + ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); + this.writeLock = rwLock.writeLock(); + this.sections = new Long2ObjectConcurrentHashMap<>(); + this.sectionIds = new ConcurrentLongSortedSet(); + } @WrapMethod(method = "getExistingSectionsInChunk") - private Stream> getExistingSections(long pos, Operation>> original) { - return this.getExistingSectionPositionsInChunk(pos) + private Stream> getExistingSections(long chunkKey, Operation>> original) { + return this.getExistingSectionPositionsInChunk(chunkKey) .mapToObj(this.sections::get) .filter(Objects::nonNull) .toList() @@ -42,11 +56,27 @@ private Stream> getExistingSections(long pos, Operation getOrCreateSection(long pos, Operation> original) { - EntitySection existing = this.sections.get(pos); + private EntitySection getOrCreateSection(long key, Operation> original) { + EntitySection existing = sections.get(key); if (existing != null) return existing; - synchronized (async$lock) { - return original.call(pos); + writeLock.lock(); + try { + existing = sections.get(key); + if (existing != null) return existing; + return original.call(key); + } finally { + writeLock.unlock(); + } + } + + + @WrapMethod(method = "remove") + private void remove(long sectionKey, Operation original) { + writeLock.lock(); + try { + original.call(sectionKey); + } finally { + writeLock.unlock(); } } } \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/movement/PathFinderMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/movement/PathFinderMixin.java index b16db6a3..a102a713 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/movement/PathFinderMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/movement/PathFinderMixin.java @@ -16,7 +16,7 @@ public class PathFinderMixin { @WrapMethod(method = "findPath(Lnet/minecraft/world/level/PathNavigationRegion;Lnet/minecraft/world/entity/Mob;Ljava/util/Set;FIF)Lnet/minecraft/world/level/pathfinder/Path;") - private synchronized @Nullable Path syncFindPath(PathNavigationRegion world, Mob mob, Set positions, float followRange, int distance, float rangeMultiplier, Operation original) { - return original.call(world, mob, positions, followRange, distance, rangeMultiplier); + private synchronized @Nullable Path syncFindPath(PathNavigationRegion level, Mob entity, Set targets, float maxPathLength, int reachRange, float maxVisitedNodesMultiplier, Operation original) { + return original.call(level, entity, targets, maxPathLength, reachRange, maxVisitedNodesMultiplier); } } \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/movement/PoiManagerMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/movement/PoiManagerMixin.java index bbf396b9..208e5180 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/movement/PoiManagerMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/movement/PoiManagerMixin.java @@ -11,7 +11,6 @@ import net.minecraft.world.entity.ai.village.poi.PoiType; import net.minecraft.world.level.ChunkPos; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; import java.util.Optional; import java.util.function.Predicate; @@ -20,55 +19,53 @@ @Mixin(PoiManager.class) public class PoiManagerMixin { - @Unique - private static final Object async$lock = new Object(); - @WrapMethod(method = "getInSquare") - private Stream getInSquare(Predicate> typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus, Operation> original) { - synchronized (async$lock) { - return original.call(typePredicate, pos, radius, occupationStatus); + private Stream getInSquare(Predicate> predicate, BlockPos center, int radius, PoiManager.Occupancy occupancy, Operation> original) { + synchronized (this) { + return original.call(predicate, center, radius, occupancy).toList().stream(); } } @WrapMethod(method = "getInRange") - private Stream getInRange(Predicate> typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus, Operation> original) { - synchronized (async$lock) { - return original.call(typePredicate, pos, radius, occupationStatus); + private Stream getInRange(Predicate> predicate, BlockPos center, int radius, PoiManager.Occupancy occupancy, Operation> original) { + synchronized (this) { + return original.call(predicate, center, radius, occupancy).toList().stream(); } } @WrapMethod(method = "getInChunk") - private Stream getInChunk(Predicate> typePredicate, ChunkPos chunkPos, PoiManager.Occupancy occupationStatus, Operation> original) { - synchronized (async$lock) { - return original.call(typePredicate, chunkPos, occupationStatus); + private Stream getInChunk(Predicate> predicate, ChunkPos chunkPos, PoiManager.Occupancy occupancy, Operation> original) { + synchronized (this) { + return original.call(predicate, chunkPos, occupancy).toList().stream(); } } + @WrapMethod(method = "getCountInRange") - private long getInChunk(Predicate> typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus, Operation original) { - synchronized (async$lock) { - return original.call(typePredicate, pos, radius, occupationStatus); + private long getInChunk(Predicate> predicate, BlockPos center, int radius, PoiManager.Occupancy occupancy, Operation original) { + synchronized (this) { + return original.call(predicate, center, radius, occupancy); } } @WrapMethod(method = "findClosest(Ljava/util/function/Predicate;Lnet/minecraft/core/BlockPos;ILnet/minecraft/world/entity/ai/village/poi/PoiManager$Occupancy;)Ljava/util/Optional;") - private Optional getNearestPosition(Predicate> typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus, Operation> original) { - synchronized (async$lock) { - return original.call(typePredicate, pos, radius, occupationStatus); + private Optional getNearestPosition(Predicate> predicate, BlockPos center, int radius, PoiManager.Occupancy occupancy, Operation> original) { + synchronized (this) { + return original.call(predicate, center, radius, occupancy); } } @WrapMethod(method = "findClosest(Ljava/util/function/Predicate;Ljava/util/function/Predicate;Lnet/minecraft/core/BlockPos;ILnet/minecraft/world/entity/ai/village/poi/PoiManager$Occupancy;)Ljava/util/Optional;") - private Optional getNearestPosition(Predicate> typePredicate, Predicate posPredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus, Operation> original) { - synchronized (async$lock) { - return original.call(typePredicate, posPredicate, pos, radius, occupationStatus); + private Optional getNearestPosition(Predicate> predicate, Predicate filter, BlockPos center, int radius, PoiManager.Occupancy occupancy, Operation> original) { + synchronized (this) { + return original.call(predicate, filter, center, radius, occupancy); } } @WrapMethod(method = "getRandom(Ljava/util/function/Predicate;Ljava/util/function/Predicate;Lnet/minecraft/world/entity/ai/village/poi/PoiManager$Occupancy;Lnet/minecraft/core/BlockPos;ILnet/minecraft/util/RandomSource;)Ljava/util/Optional;") - private Optional getNearestPosition(Predicate> typePredicate, Predicate positionPredicate, PoiManager.Occupancy occupationStatus, BlockPos pos, int radius, RandomSource random, Operation> original) { - synchronized (async$lock) { - return original.call(typePredicate, positionPredicate, occupationStatus, pos, radius, random); + private Optional getNearestPosition(Predicate> predicate, Predicate filter, PoiManager.Occupancy occupancy, BlockPos center, int radius, RandomSource random, Operation> original) { + synchronized (this) { + return original.call(predicate, filter, occupancy, center, radius, random); } } } \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/sensor/NearestItemSensorMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/sensor/NearestItemSensorMixin.java index d5c7b4fc..92d7f866 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/sensor/NearestItemSensorMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/sensor/NearestItemSensorMixin.java @@ -1,8 +1,6 @@ package com.axalotl.async.common.mixin.entity.sensor; -import com.axalotl.async.common.parallelised.utils.FastBitRadixSort; -import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.axalotl.async.api.utils.SensorUtils; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.Brain; @@ -10,34 +8,27 @@ import net.minecraft.world.entity.ai.sensing.NearestItemSensor; import net.minecraft.world.entity.item.ItemEntity; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.Overwrite; import java.util.*; @Mixin(value = NearestItemSensor.class, priority = 1500) public class NearestItemSensorMixin { - @Unique - private static final FastBitRadixSort async$itemSorter = new FastBitRadixSort(); - @WrapMethod(method = "doTick(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/Mob;)V") - private void doTick(ServerLevel level, Mob entity, Operation original) { - Brain brain = entity.getBrain(); - List list = level.getEntitiesOfClass( - ItemEntity.class, - entity.getBoundingBox().inflate(32.0F, 16.0F, 32.0F), - (p_26703_) -> true - ); - - Object[] arr = list.toArray(); - async$itemSorter.sort(arr, arr.length, entity.position()); - - Optional optional = Arrays.stream(arr) - .map(o -> (ItemEntity) o) - .filter(e -> entity.wantsToPickUp(e.getItem())) - .filter(e -> e.closerThan(entity, 32.0F)) - .filter(entity::hasLineOfSight) + /** + * @author _Axa_lotL_ + * @reason async distance cache + */ + @Overwrite + protected void doTick(final ServerLevel level, final Mob body) { + Brain brain = body.getBrain(); + List items = level.getEntitiesOfClass(ItemEntity.class, body.getBoundingBox().inflate(32.0, 16.0, 32.0), ignored -> true); + items.sort(SensorUtils.comparingDouble(body)); + Optional nearestVisibleLovedItem = items.stream() + .filter(itemEntity -> body.wantsToPickUp(itemEntity.getItem())) + .filter(itemEntity -> itemEntity.closerThan(body, 32.0)) + .filter(body::hasLineOfSight) .findFirst(); - - brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, optional); + brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, nearestVisibleLovedItem); } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/sensor/NearestLivingEntitiesSensorMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/sensor/NearestLivingEntitiesSensorMixin.java index b707d11b..758be5db 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/sensor/NearestLivingEntitiesSensorMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/sensor/NearestLivingEntitiesSensorMixin.java @@ -1,8 +1,6 @@ package com.axalotl.async.common.mixin.entity.sensor; -import com.axalotl.async.common.parallelised.utils.FastBitRadixSort; -import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.axalotl.async.api.utils.SensorUtils; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.ai.Brain; @@ -10,38 +8,28 @@ import net.minecraft.world.entity.ai.memory.MemoryModuleType; import net.minecraft.world.entity.ai.memory.NearestVisibleLivingEntities; import net.minecraft.world.entity.ai.sensing.NearestLivingEntitySensor; +import net.minecraft.world.entity.ai.sensing.Sensor; import net.minecraft.world.phys.AABB; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.Overwrite; -import java.util.ArrayList; import java.util.List; @Mixin(value = NearestLivingEntitySensor.class, priority = 1500) -public class NearestLivingEntitiesSensorMixin { - @Unique - private static final FastBitRadixSort async$entitySorter = new FastBitRadixSort(); - - @WrapMethod(method = "doTick(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/LivingEntity;)V") - private void doTick(ServerLevel level, T entity, Operation original) { - double d0 = entity.getAttributeValue(Attributes.FOLLOW_RANGE); - AABB aabb = entity.getBoundingBox().inflate(d0, d0, d0); - - List list = level.getEntitiesOfClass( - LivingEntity.class, - aabb, - e -> e != entity && e.isAlive() - ); - - Object[] arr = list.toArray(); - async$entitySorter.sort(arr, arr.length, entity.position()); - - List sorted = new ArrayList<>(arr.length); - for (Object o : arr) sorted.add((LivingEntity) o); - - Brain brain = entity.getBrain(); - brain.setMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES, sorted); - brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, - new NearestVisibleLivingEntities(entity, sorted)); +public abstract class NearestLivingEntitiesSensorMixin extends Sensor { + + /** + * @author _Axa_lotL_ + * @reason async distance cache + */ + @Overwrite + protected void doTick(final ServerLevel level, final T body) { + double followRange = body.getAttributeValue(Attributes.FOLLOW_RANGE); + AABB boundingBox = body.getBoundingBox().inflate(followRange, followRange, followRange); + List livingEntities = level.getEntitiesOfClass(LivingEntity.class, boundingBox, mob -> mob != body && mob.isAlive()); + livingEntities.sort(SensorUtils.comparingDouble(body)); + Brain brain = body.getBrain(); + brain.setMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES, livingEntities); + brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, new NearestVisibleLivingEntities(body, livingEntities)); } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/sensor/PlayerSensorMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/sensor/PlayerSensorMixin.java index 7bfcec35..79b9a7d9 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/sensor/PlayerSensorMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/sensor/PlayerSensorMixin.java @@ -1,8 +1,6 @@ package com.axalotl.async.common.mixin.entity.sensor; -import com.axalotl.async.common.parallelised.utils.FastBitRadixSort; -import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.axalotl.async.api.utils.SensorUtils; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.EntitySelector; import net.minecraft.world.entity.LivingEntity; @@ -11,10 +9,10 @@ import net.minecraft.world.entity.ai.sensing.PlayerSensor; import net.minecraft.world.entity.player.Player; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.Overwrite; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; import static net.minecraft.world.entity.ai.sensing.Sensor.isEntityAttackable; import static net.minecraft.world.entity.ai.sensing.Sensor.isEntityTargetable; @@ -22,35 +20,25 @@ @Mixin(value = PlayerSensor.class, priority = 1500) public abstract class PlayerSensorMixin { - - @Unique - private static final FastBitRadixSort async$playerSort = new FastBitRadixSort(); - - @WrapMethod(method = "doTick(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/world/entity/LivingEntity;)V") - private void doTick(ServerLevel level, LivingEntity entity, Operation original) { - Object[] arr = level.players().stream() + /** + * @author _Axa_lotL_ + * @reason async distance cache + */ + @Overwrite + protected void doTick(final ServerLevel level, final LivingEntity body) { + List players = level.players() + .stream() .filter(EntitySelector.NO_SPECTATORS) - .filter(p_26744_ -> entity.closerThan(p_26744_, 16.0)) - .toArray(); - - async$playerSort.sort(arr, arr.length, entity.position()); - - List list = new ArrayList<>(arr.length); - for (Object o : arr) list.add((Player) o); - - Brain brain = entity.getBrain(); - brain.setMemory(MemoryModuleType.NEAREST_PLAYERS, list); - - List list1 = list.stream() - .filter(p -> isEntityTargetable(entity, p)) + .filter(player -> body.closerThan(player, 16.0)) + .sorted(SensorUtils.comparingDouble(body)) + .collect(Collectors.toList()); + Brain brain = body.getBrain(); + brain.setMemory(MemoryModuleType.NEAREST_PLAYERS, players); + List visiblePlayers = players.stream() + .filter(livingEntity -> isEntityTargetable(body, livingEntity)) .toList(); - - brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, list1.isEmpty() ? null : list1.getFirst()); - - List list2 = list1.stream() - .filter(p -> isEntityAttackable(entity, p)) - .toList(); - - brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, list2.isEmpty() ? null : list2.getFirst()); + brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, visiblePlayers.isEmpty() ? null : visiblePlayers.get(0)); + List visibleAttackablePlayers = visiblePlayers.stream().filter(livingEntity -> isEntityAttackable(body, livingEntity)).toList(); + brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, visibleAttackablePlayers.isEmpty() ? null : visibleAttackablePlayers.get(0)); } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/entity/spawn/LocalMobCapCalculatorMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/entity/spawn/LocalMobCapCalculatorMixin.java index a3138e16..faf940bf 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/entity/spawn/LocalMobCapCalculatorMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/entity/spawn/LocalMobCapCalculatorMixin.java @@ -1,7 +1,7 @@ package com.axalotl.async.common.mixin.entity.spawn; -import com.axalotl.async.common.parallelised.ConcurrentCollections; -import com.axalotl.async.common.parallelised.fastutil.Long2ObjectConcurrentHashMap; +import com.axalotl.async.api.fastutil.Long2ObjectConcurrentHashMap; +import com.axalotl.async.api.utils.ConcurrentCollections; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.ChunkPos; diff --git a/common/src/main/java/com/axalotl/async/common/mixin/lithium/LithiumGameEventDispatcherStorage.java b/common/src/main/java/com/axalotl/async/common/mixin/lithium/LithiumGameEventDispatcherStorage.java index 179fafb7..41e7c757 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/lithium/LithiumGameEventDispatcherStorage.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/lithium/LithiumGameEventDispatcherStorage.java @@ -12,32 +12,32 @@ public class LithiumGameEventDispatcherStorage { @Unique - private final Object async$lock = new Object(); + private final Object lock = new Object(); @WrapMethod(method = "addChunk") - private void async$addChunk(long pos, Int2ObjectMap dispatchers, Operation original) { - synchronized (async$lock) { + private void addChunk(long pos, Int2ObjectMap dispatchers, Operation original) { + synchronized (lock) { original.call(pos, dispatchers); } } @WrapMethod(method = "removeChunk") - private void async$removeChunk(long pos, Operation original) { - synchronized (async$lock) { + private void removeChunk(long pos, Operation original) { + synchronized (lock) { original.call(pos); } } @WrapMethod(method = "replace") - private void async$replace(long pos, Int2ObjectMap dispatchers, Operation original) { - synchronized (async$lock) { + private void replace(long pos, Int2ObjectMap dispatchers, Operation original) { + synchronized (lock) { original.call(pos, dispatchers); } } @WrapMethod(method = "get") - private Int2ObjectMap async$get(long pos, Operation> original) { - synchronized (async$lock) { + private Int2ObjectMap get(long pos, Operation> original) { + synchronized (lock) { return original.call(pos); } } diff --git a/common/src/main/java/com/axalotl/async/common/mixin/lithium/LithiumInternerMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/lithium/LithiumInternerMixin.java new file mode 100644 index 00000000..ab52fd4c --- /dev/null +++ b/common/src/main/java/com/axalotl/async/common/mixin/lithium/LithiumInternerMixin.java @@ -0,0 +1,20 @@ +package com.axalotl.async.common.mixin.lithium; + +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import net.caffeinemc.mods.lithium.common.util.deduplication.LithiumInterner; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(value = LithiumInterner.class, remap = false) +public class LithiumInternerMixin { + + @WrapMethod(method = "getCanonical") + private synchronized S wrapGetCanonical(S value, Operation original) { + return original.call(value); + } + + @WrapMethod(method = "deleteCanonical") + private synchronized void wrapDeleteCanonical(Object value, Operation original) { + original.call(value); + } +} \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/lithium/LithiumSectionedBlockChangeTrackerMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/lithium/LithiumSectionedBlockChangeTrackerMixin.java new file mode 100644 index 00000000..81a95a65 --- /dev/null +++ b/common/src/main/java/com/axalotl/async/common/mixin/lithium/LithiumSectionedBlockChangeTrackerMixin.java @@ -0,0 +1,35 @@ +package com.axalotl.async.common.mixin.lithium; + +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import net.caffeinemc.mods.lithium.common.tracking.block.SectionedBlockChangeTracker; +import net.caffeinemc.mods.lithium.common.util.deduplication.LithiumInterner; +import net.caffeinemc.mods.lithium.common.util.tuples.WorldSectionBox; +import net.caffeinemc.mods.lithium.common.world.LithiumData; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(value = SectionedBlockChangeTracker.class, remap = false) +public class LithiumSectionedBlockChangeTrackerMixin { + + @Shadow + @Final + public WorldSectionBox trackedWorldSections; + + @WrapMethod(method = "register") + private void wrapRegisterAt(Operation original) { + LithiumInterner blockChangeTrackers = ((LithiumData) this.trackedWorldSections.world()).lithium$getData().blockChangeTrackers(); + synchronized (blockChangeTrackers) { + original.call(); + } + } + + @WrapMethod(method = "unregister") + private void wrapUnregister(Operation original) { + LithiumInterner blockChangeTrackers = ((LithiumData) this.trackedWorldSections.world()).lithium$getData().blockChangeTrackers(); + synchronized (blockChangeTrackers) { + original.call(); + } + } +} \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/lithium/LithiumServerLevel.java b/common/src/main/java/com/axalotl/async/common/mixin/lithium/LithiumServerLevel.java index 6a8da13c..35b3b334 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/lithium/LithiumServerLevel.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/lithium/LithiumServerLevel.java @@ -1,5 +1,6 @@ package com.axalotl.async.common.mixin.lithium; +import com.axalotl.async.api.utils.ConcurrentCollections; import com.llamalad7.mixinextras.sugar.Local; import net.caffeinemc.mods.lithium.common.entity.NavigatingEntity; import net.caffeinemc.mods.lithium.common.world.ServerWorldExtended; @@ -8,7 +9,6 @@ import net.minecraft.core.RegistryAccess; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; -import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.navigation.PathNavigation; import net.minecraft.world.level.Level; @@ -16,22 +16,21 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.storage.WritableLevelData; +import net.minecraft.util.profiling.ProfilerFiller; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import java.util.Collections; import java.util.List; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; @Mixin(value = ServerLevel.class, priority = 1500) public abstract class LithiumServerLevel extends Level implements WorldGenLevel, ServerWorldExtended { @Unique - private final Set async$activeNavigationsOver = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final Set activeNavigationsOver = ConcurrentCollections.newHashSet(); protected LithiumServerLevel(WritableLevelData levelData, ResourceKey dimension, RegistryAccess registryAccess, Holder dimensionTypeRegistration, Supplier profiler, boolean isClientSide, boolean isDebug, long biomeZoomSeed, int maxChainedNeighborUpdates) { super(levelData, dimension, registryAccess, dimensionTypeRegistration, profiler, isClientSide, isDebug, biomeZoomSeed, maxChainedNeighborUpdates); @@ -44,21 +43,21 @@ protected LithiumServerLevel(WritableLevelData levelData, ResourceKey dim target = "Ljava/util/Set;iterator()Ljava/util/Iterator;" ) ) - private void updateActiveListeners(BlockPos pos, BlockState oldState, BlockState newState, int arg3, CallbackInfo ci, @Local List list) { - for (PathNavigation nav : async$activeNavigationsOver) { + private void updateActiveListeners(BlockPos pos, BlockState old, BlockState current, int updateFlags, CallbackInfo ci, @Local(name = "navigationsToUpdate") List navigationsToUpdate) { + for (PathNavigation nav : activeNavigationsOver) { if (nav.shouldRecomputePath(pos)) { - list.add(nav); + navigationsToUpdate.add(nav); } } } @Override public void lithium$setNavigationActive(Mob mobEntity) { - async$activeNavigationsOver.add(((NavigatingEntity) mobEntity).lithium$getRegisteredNavigation()); + activeNavigationsOver.add(((NavigatingEntity) mobEntity).lithium$getRegisteredNavigation()); } @Override public void lithium$setNavigationInactive(Mob mobEntity) { - async$activeNavigationsOver.remove(((NavigatingEntity) mobEntity).lithium$getRegisteredNavigation()); + activeNavigationsOver.remove(((NavigatingEntity) mobEntity).lithium$getRegisteredNavigation()); } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/lithium/ReferenceMaskedListMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/lithium/ReferenceMaskedListMixin.java new file mode 100644 index 00000000..c51b2cac --- /dev/null +++ b/common/src/main/java/com/axalotl/async/common/mixin/lithium/ReferenceMaskedListMixin.java @@ -0,0 +1,69 @@ +package com.axalotl.async.common.mixin.lithium; + +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import net.caffeinemc.mods.lithium.common.util.collections.ReferenceMaskedList; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +@Mixin(value = ReferenceMaskedList.class, priority = 1500, remap = false) +public class ReferenceMaskedListMixin { + + @Final + @Shadow + private Reference2IntOpenHashMap element2Index; + + @WrapMethod(method = "add(Ljava/lang/Object;)Z") + private boolean add(E e, Operation original) { + synchronized (this) { + if (element2Index.containsKey(e)) return false; + return original.call(e); + } + } + + @WrapMethod(method = "remove") + private boolean remove(Object o, Operation original) { + synchronized (this) { + return original.call(o); + } + } + + @WrapMethod(method = "addOrSet") + private void addOrSet(E element, boolean visible, Operation original) { + synchronized (this) { + original.call(element, visible); + } + } + + @WrapMethod(method = "setVisible") + private void setVisible(E element, boolean visible, Operation original) { + synchronized (this) { + original.call(element, visible); + } + } + + @WrapMethod(method = "iterator") + private Iterator iterator(Operation> original) { + synchronized (this) { + List snapshot = new ArrayList<>(); + Iterator it = original.call(); + while (it.hasNext()) { + snapshot.add(it.next()); + } + return snapshot.iterator(); + } + } + + @WrapMethod(method = "totalSize") + private int totalSize(Operation original) { + synchronized (this) { + return original.call(); + } + } +} \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/server/ChunkMapMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/server/ChunkMapMixin.java index 1f9cb43c..2691f73b 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/server/ChunkMapMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/server/ChunkMapMixin.java @@ -1,6 +1,6 @@ package com.axalotl.async.common.mixin.server; -import com.axalotl.async.common.parallelised.fastutil.Int2ObjectConcurrentHashMap; +import com.axalotl.async.api.fastutil.Int2ObjectConcurrentHashMap; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.mojang.datafixers.DataFixer; @@ -12,7 +12,10 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.level.chunk.storage.ChunkStorage; import net.minecraft.world.level.chunk.storage.RegionStorageInfo; -import org.spongepowered.asm.mixin.*; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -63,4 +66,4 @@ private synchronized void releaseGeneration(GenerationChunkHolder chunk, Operati private void skipThrowLoadEntity(Entity entity, CallbackInfo ci) { ci.cancel(); } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/server/ChunkMapTrackedEntityMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/server/ChunkMapTrackedEntityMixin.java index f8b67ab6..93bb6561 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/server/ChunkMapTrackedEntityMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/server/ChunkMapTrackedEntityMixin.java @@ -1,6 +1,6 @@ package com.axalotl.async.common.mixin.server; -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.server.level.ChunkMap; diff --git a/common/src/main/java/com/axalotl/async/common/mixin/server/MinecraftServerMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/server/MinecraftServerMixin.java index cc9f6538..1197edf8 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/server/MinecraftServerMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/server/MinecraftServerMixin.java @@ -1,23 +1,26 @@ package com.axalotl.async.common.mixin.server; import com.axalotl.async.common.ParallelProcessor; -import net.minecraft.commands.CommandSource; +import com.axalotl.async.common.utils.TickStats; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.TickTask; -import net.minecraft.util.thread.ReentrantBlockableEventLoop; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -@Mixin(value = MinecraftServer.class, priority = Integer.MAX_VALUE) -public abstract class MinecraftServerMixin extends ReentrantBlockableEventLoop implements CommandSource, AutoCloseable { +import java.util.function.BooleanSupplier; - public MinecraftServerMixin(String name) { - super(name); - } +@Mixin(value = MinecraftServer.class) +public class MinecraftServerMixin { @Redirect(method = "reloadResources", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;isSameThread()Z")) private boolean onServerExecutionThreadPatch(MinecraftServer minecraftServer) { return ParallelProcessor.isServerExecutionThread(); } + + @Inject(method = "tickServer", at = @At("TAIL")) + private void asyncStatsTick(BooleanSupplier haveTime, CallbackInfo ci) { + TickStats.onServerTick(); + } } \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/server/PersistentEntitySectionManagerCallbackMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/server/PersistentEntitySectionManagerCallbackMixin.java index 3288c3ad..b0a8e300 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/server/PersistentEntitySectionManagerCallbackMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/server/PersistentEntitySectionManagerCallbackMixin.java @@ -26,4 +26,4 @@ private void onRemove(Entity.RemovalReason reason, Operation original) { original.call(reason); } } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/server/PersistentEntitySectionManagerMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/server/PersistentEntitySectionManagerMixin.java index 2a68e446..cff4ddeb 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/server/PersistentEntitySectionManagerMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/server/PersistentEntitySectionManagerMixin.java @@ -2,38 +2,36 @@ import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.entity.EntityAccess; import net.minecraft.world.level.entity.PersistentEntitySectionManager; import net.minecraft.world.level.entity.Visibility; +import org.jetbrains.annotations.NotNull; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; + +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; @Mixin(PersistentEntitySectionManager.class) public abstract class PersistentEntitySectionManagerMixin implements AutoCloseable { @Shadow - private final Long2ObjectMap chunkVisibility = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); - - @Unique - private static final Object async$lock = new Object(); + private final Set knownUuids = ConcurrentHashMap.newKeySet(); @WrapMethod(method = "updateChunkStatus(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/entity/Visibility;)V") - private void updateChunkStatus(ChunkPos pos, Visibility p_visibility, Operation original) { - synchronized (async$lock) { - original.call(pos, p_visibility); + private void updateChunkStatus(ChunkPos pos, Visibility chunkStatus, @NotNull Operation original) { + synchronized (this) { + original.call(pos, chunkStatus); } } @WrapMethod(method = "getEffectiveStatus") - private static Visibility getEffectiveStatus(T entity, Visibility visibility, Operation original) { - Visibility result = original.call(entity, visibility); + private static Visibility getEffectiveStatus(T entity, Visibility status, Operation original) { + Visibility result = original.call(entity, status); if (result == null) { return entity.isAlwaysTicking() ? Visibility.TICKING : Visibility.TRACKED; } return result; } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/server/ServerChunkCacheMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/server/ServerChunkCacheMixin.java index bf4af50c..13cd1367 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/server/ServerChunkCacheMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/server/ServerChunkCacheMixin.java @@ -18,6 +18,8 @@ import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.status.ChunkStatus; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -27,14 +29,19 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import java.util.*; +import java.util.List; +import java.util.Objects; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.LockSupport; import java.util.function.Consumer; @Mixin(value = ServerChunkCache.class, priority = 1500) public abstract class ServerChunkCacheMixin extends ChunkSource { + @Unique + private static final Logger LOGGER = LoggerFactory.getLogger(ServerChunkCacheMixin.class); + @Shadow @Final public ChunkMap chunkMap; @@ -93,7 +100,12 @@ public abstract class ServerChunkCacheMixin extends ChunkSource { this.mainThreadProcessor ).thenCompose(f -> f); + long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(60); while (!future.isDone()) { + if (System.nanoTime() > deadline) { + future.cancel(false); + return; + } ChunkAccess cached = async$tryGetChunk(x, z, leastStatus); if (cached != null) { future.cancel(false); @@ -150,9 +162,10 @@ private void tick(CallbackInfo ci) { } if (async$spawnCountsReady.getAndSet(false)) { int l = this.distanceManager.getNaturalSpawnChunkCount(); - if (!ParallelProcessor.tickPool.isShutdown() - && !ParallelProcessor.tickPool.isTerminated()) { - ParallelProcessor.tickPool.submit(() -> { + if (ParallelProcessor.executor != null + && !ParallelProcessor.executor.isShutdown() + && !ParallelProcessor.executor.isTerminated()) { + ParallelProcessor.executor.submit(() -> { this.lastSpawnState = NaturalSpawner.createState( l, this.level.getAllEntities(), @@ -208,11 +221,12 @@ private void tickChunksSpawn(Operation original) { if (!AsyncConfig.disabled && AsyncConfig.enableAsyncSpawn && lastSpawnState != null - && !ParallelProcessor.tickPool.isShutdown() - && !ParallelProcessor.tickPool.isTerminated()) { + && ParallelProcessor.executor != null + && !ParallelProcessor.executor.isShutdown() + && !ParallelProcessor.executor.isTerminated()) { NaturalSpawner.SpawnState state = lastSpawnState; CompletableFuture.runAsync(() -> { - if (!ParallelProcessor.tickPool.isShutdown()) { + if (!ParallelProcessor.executor.isShutdown()) { for (ServerChunkCache.ChunkAndHolder entry : chunks) { LevelChunk chunk = entry.chunk(); ChunkPos pos = chunk.getPos(); @@ -237,8 +251,8 @@ private void tickChunksSpawn(Operation original) { } } } - }, ParallelProcessor.tickPool).exceptionally(e -> { - ParallelProcessor.LOGGER.error("Error in async entity spawning, switching to synchronous", e); + }, ParallelProcessor.executor).exceptionally(e -> { + LOGGER.error("Error in async entity spawning, switching to synchronous", e); for (ServerChunkCache.ChunkAndHolder entry : chunks) { LevelChunk chunk = entry.chunk(); ChunkPos pos = chunk.getPos(); @@ -301,4 +315,4 @@ private void tickChunksSpawn(Operation original) { profiler.pop(); } } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/server/ServerEntityMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/server/ServerEntityMixin.java new file mode 100644 index 00000000..4b14b811 --- /dev/null +++ b/common/src/main/java/com/axalotl/async/common/mixin/server/ServerEntityMixin.java @@ -0,0 +1,23 @@ +package com.axalotl.async.common.mixin.server; + +import com.axalotl.async.common.ParallelProcessor; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.server.level.ServerEntity; +import org.slf4j.Logger; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(value = ServerEntity.class, priority = 1500) +public class ServerEntityMixin { + + @WrapOperation( + method = "sendPairingData", + at = @At(value = "INVOKE", target = "Lorg/slf4j/Logger;warn(Ljava/lang/String;Ljava/lang/Object;)V", remap = false) + ) + private void async$suppressAsyncRemovedWarn(Logger logger, String message, Object arg, Operation original) { + if (!ParallelProcessor.isServerExecutionThread()) { + original.call(logger, message, arg); + } + } +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/server/ServerLevelEntityCallbacksMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/server/ServerLevelEntityCallbacksMixin.java index 4734ac80..1f9697a5 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/server/ServerLevelEntityCallbacksMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/server/ServerLevelEntityCallbacksMixin.java @@ -5,25 +5,17 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; @Mixin(ServerLevel.EntityCallbacks.class) public class ServerLevelEntityCallbacksMixin { - @Unique - private static final Object async$lock = new Object(); - @WrapMethod(method = "onTickingStart(Lnet/minecraft/world/entity/Entity;)V") private synchronized void onTickingStart(Entity entity, Operation original) { - synchronized (async$lock) { - original.call(entity); - } + original.call(entity); } @WrapMethod(method = "onTickingEnd(Lnet/minecraft/world/entity/Entity;)V") private synchronized void onTickingEnd(Entity entity, Operation original) { - synchronized (async$lock) { - original.call(entity); - } + original.call(entity); } } \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/server/ServerPlayerMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/server/ServerPlayerMixin.java index 09d4d31d..0a257f24 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/server/ServerPlayerMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/server/ServerPlayerMixin.java @@ -12,12 +12,12 @@ @Mixin(ServerPlayer.class) public abstract class ServerPlayerMixin extends Player { - public ServerPlayerMixin(Level world, BlockPos pos, float yaw, GameProfile gameProfile) { - super(world, pos, yaw, gameProfile); + public ServerPlayerMixin(Level level, GameProfile gameProfile) { + super(level, BlockPos.ZERO, 0.0F, gameProfile); } @WrapMethod(method = "die") - private synchronized void die(DamageSource damageSource, Operation original) { - original.call(damageSource); + private synchronized void die(DamageSource source, Operation original) { + original.call(source); } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/utils/FastUtilsMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/utils/FastUtilSynchronizeMixin.java similarity index 98% rename from common/src/main/java/com/axalotl/async/common/mixin/utils/FastUtilsMixin.java rename to common/src/main/java/com/axalotl/async/common/mixin/utils/FastUtilSynchronizeMixin.java index 48275b96..0f1210b9 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/utils/FastUtilsMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/utils/FastUtilSynchronizeMixin.java @@ -55,5 +55,5 @@ "it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap$MapIterator", "it.unimi.dsi.fastutil.objects.Reference2ByteOpenHashMap", }, priority = 50000) -public class FastUtilsMixin { +public class FastUtilSynchronizeMixin { } \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/utils/LegacyRandomSourceMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/utils/LegacyRandomSourceMixin.java deleted file mode 100644 index bb8d9675..00000000 --- a/common/src/main/java/com/axalotl/async/common/mixin/utils/LegacyRandomSourceMixin.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.axalotl.async.common.mixin.utils; - -import com.axalotl.async.common.ParallelProcessor; -import net.minecraft.world.level.levelgen.LegacyRandomSource; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.atomic.AtomicLong; - -@Mixin(LegacyRandomSource.class) -public abstract class LegacyRandomSourceMixin { - - @Unique - private final AtomicLong async$instanceSeed = new AtomicLong(ThreadLocalRandom.current().nextLong()); - - @Inject(method = "next", at = @At("HEAD"), cancellable = true) - private void async$threadLocalNext(int bits, CallbackInfoReturnable cir) { - if (ParallelProcessor.isServerExecutionThread()) { - long oldSeed, newSeed; - do { - oldSeed = async$instanceSeed.get(); - newSeed = oldSeed * 0x5DEECE66DL + 0xBL & 0xFFFFFFFFFFFFL; - } while (!async$instanceSeed.compareAndSet(oldSeed, newSeed)); - - cir.setReturnValue((int) (newSeed >>> (48 - bits))); - } - } -} \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/utils/AsyncModMixinCanceller.java b/common/src/main/java/com/axalotl/async/common/mixin/utils/MixinCanceller.java similarity index 71% rename from common/src/main/java/com/axalotl/async/common/mixin/utils/AsyncModMixinCanceller.java rename to common/src/main/java/com/axalotl/async/common/mixin/utils/MixinCanceller.java index 6d2d4c6d..1b120e52 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/utils/AsyncModMixinCanceller.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/utils/MixinCanceller.java @@ -1,10 +1,8 @@ package com.axalotl.async.common.mixin.utils; -import com.bawnorton.mixinsquared.api.MixinCanceller; - import java.util.List; -public class AsyncModMixinCanceller implements MixinCanceller { +public class MixinCanceller implements com.bawnorton.mixinsquared.api.MixinCanceller { private boolean LITHIUM = false; private boolean VMP = false; @@ -22,7 +20,11 @@ public boolean shouldCancel(List targetClassNames, String mixinClassName return true; } if (mixinClassName.endsWith("com.axalotl.async.common.mixin.lithium.LithiumServerLevel") || - mixinClassName.endsWith("com.axalotl.async.common.mixin.lithium.LithiumGameEventDispatcherStorage")) { + mixinClassName.endsWith("com.axalotl.async.common.mixin.lithium.LithiumSectionedBlockChangeTrackerMixin") || + mixinClassName.endsWith("com.axalotl.async.common.mixin.lithium.LithiumInternerMixin") || + mixinClassName.endsWith("com.axalotl.async.common.mixin.lithium.LithiumGameEventDispatcherStorage") || + mixinClassName.endsWith("com.axalotl.async.common.mixin.lithium.ReferenceMaskedListMixin") + ) { return !LITHIUM; } if (mixinClassName.endsWith("com.axalotl.async.common.mixin.vmp.VMPChunkMapMixin")) { diff --git a/common/src/main/java/com/axalotl/async/common/mixin/utils/NetherPortalBlockMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/utils/NetherPortalBlockMixin.java index b972918e..471ece11 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/utils/NetherPortalBlockMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/utils/NetherPortalBlockMixin.java @@ -44,7 +44,7 @@ private static DimensionTransition getDimensionTransitionFromExit( } @WrapMethod(method = "getExitPortal") - private DimensionTransition async_getExitPortal( + private DimensionTransition async$getExitPortal( ServerLevel level, Entity entity, BlockPos pos, @@ -136,4 +136,4 @@ private DimensionTransition async_getExitPortal( } }; } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/utils/SyncAllMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/utils/SyncAllMixin.java index ec078a5b..49bcf6b1 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/utils/SyncAllMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/utils/SyncAllMixin.java @@ -6,7 +6,6 @@ import net.minecraft.world.entity.ai.navigation.PathNavigation; import net.minecraft.world.entity.monster.warden.AngerManagement; import net.minecraft.world.level.border.WorldBorder; -import net.minecraft.world.level.entity.EntitySection; import net.minecraft.world.level.gameevent.EuclideanGameEventListenerRegistry; import net.minecraft.world.level.lighting.DynamicGraphMinFixedPoint; import net.minecraft.world.level.pathfinder.BinaryHeap; @@ -22,9 +21,8 @@ SimpleCriterionTrigger.class, AngerManagement.class, WorldBorder.class, - EntitySection.class, ClassInstanceMultiMap.class, ActiveProfiler.class, }) public class SyncAllMixin { -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/utils/SynchronisePlugin.java b/common/src/main/java/com/axalotl/async/common/mixin/utils/SynchronisePlugin.java index 34742542..e60d026b 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/utils/SynchronisePlugin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/utils/SynchronisePlugin.java @@ -1,12 +1,12 @@ package com.axalotl.async.common.mixin.utils; -import com.axalotl.async.common.platform.PlatformUtils; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; + import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; @@ -16,7 +16,8 @@ import java.util.TreeSet; public class SynchronisePlugin implements IMixinConfigPlugin { - private static final Logger LOGGER = LogManager.getLogger(); + private static final Logger LOGGER = LoggerFactory.getLogger(SynchronisePlugin.class); + private static final int FINAL_STATIC_PRIVATE_ABSTRACT = 0x1548; // final, static, private, abstract private static final int SYNCHRONIZED = 0x20; // synchronized private final Multimap mixin2MethodsMap = ArrayListMultimap.create(); @@ -26,13 +27,13 @@ public class SynchronisePlugin implements IMixinConfigPlugin { @Override public void onLoad(String mixinPackage) { mixin2MethodsExcludeMap.put("com.axalotl.async.common.mixin.utils.SyncAllMixin", "net.minecraft.world.level.chunk.ChunkStatus.isOrAfter"); - syncAllSet.add("com.axalotl.async.common.mixin.utils.FastUtilsMixin"); + syncAllSet.add("com.axalotl.async.common.mixin.utils.FastUtilSynchronizeMixin"); syncAllSet.add("com.axalotl.async.common.mixin.utils.SyncAllMixin"); } @Override public String getRefMapperConfig() { - return PlatformUtils.platformUsesRefmap() ? "async.refmap.json" : null; + return null; } @Override @@ -82,7 +83,7 @@ private void applySynchronizeBit(ClassNode targetClass, Collection targe } private void logSynchronize(String methodName, String targetClassName, String mixinClassName) { - if (mixinClassName == null || !mixinClassName.equals("com.axalotl.async.mixin.utils.FastUtilsMixin")) { + if (mixinClassName == null || !mixinClassName.equals("com.axalotl.async.mixin.utils.FastUtilSynchronizeMixin")) { String message = "Setting synchronize bit for " + methodName + " in " + targetClassName + "."; LOGGER.debug(message); } diff --git a/common/src/main/java/com/axalotl/async/common/mixin/utils/ThreadSafeLegacyRandomSourceMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/utils/ThreadSafeLegacyRandomSourceMixin.java new file mode 100644 index 00000000..83f090f4 --- /dev/null +++ b/common/src/main/java/com/axalotl/async/common/mixin/utils/ThreadSafeLegacyRandomSourceMixin.java @@ -0,0 +1,59 @@ +package com.axalotl.async.common.mixin.utils; + +import net.minecraft.world.level.levelgen.LegacyRandomSource; +import net.minecraft.world.level.levelgen.MarsagliaPolarGaussian; +import org.spongepowered.asm.mixin.*; + +import java.util.concurrent.atomic.AtomicLong; + +@Mixin(LegacyRandomSource.class) +public abstract class ThreadSafeLegacyRandomSourceMixin { + + @Unique + private final AtomicLong seed = new AtomicLong(); + + @Shadow + @Final + private MarsagliaPolarGaussian gaussianSource; + + @Unique + private static final long MULTIPLIER = 25214903917L; + @Unique + private static final long INCREMENT = 11L; + @Unique + private static final long MODULUS_MASK = 281474976710655L; + + /** + * @author Minecraft + * @reason ThreadSafeLegacyRandomSource + */ + @Overwrite + public void setSeed(long seed) { + this.seed.set((seed ^ MULTIPLIER) & MODULUS_MASK); + } + + /** + * @author Minecraft + * @reason ThreadSafeLegacyRandomSource + */ + @Overwrite + public int next(int bits) { + long i; + long j; + do { + i = this.seed.get(); + j = (i * MULTIPLIER + INCREMENT) & MODULUS_MASK; + } while (!this.seed.compareAndSet(i, j)); + + return (int)(j >>> (48 - bits)); + } + + /** + * @author Minecraft + * @reason ThreadSafeLegacyRandomSource + */ + @Overwrite + public double nextGaussian() { + return this.gaussianSource.nextGaussian(); + } +} \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/world/BlockMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/world/BlockMixin.java index 6173d264..7d369989 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/world/BlockMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/world/BlockMixin.java @@ -11,32 +11,28 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; @Mixin(Block.class) public class BlockMixin { - @Unique - private static final Object async$lock = new Object(); - @WrapMethod(method = "dropResources(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;)V") private static void dropResources(BlockState state, Level level, BlockPos pos, Operation original) { - synchronized (async$lock) { + synchronized (level) { original.call(state, level, pos); } } @WrapMethod(method = "dropResources(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/LevelAccessor;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/entity/BlockEntity;)V") private static void dropResources(BlockState state, LevelAccessor level, BlockPos pos, BlockEntity blockEntity, Operation original) { - synchronized (async$lock) { + synchronized (level) { original.call(state, level, pos, blockEntity); } } @WrapMethod(method = "dropResources(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/entity/BlockEntity;Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/item/ItemStack;)V") - private static void dropResources(BlockState state, Level level, BlockPos pos, BlockEntity blockEntity, Entity entity, ItemStack tool, Operation original) { - synchronized (async$lock) { - original.call(state, level, pos, blockEntity, entity, tool); + private static void dropResources(BlockState state, Level level, BlockPos pos, BlockEntity blockEntity, Entity breaker, ItemStack tool, Operation original) { + synchronized (level) { + original.call(state, level, pos, blockEntity, breaker, tool); } } } \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/world/ChunkHolderMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/world/ChunkHolderMixin.java index 0ebc8e1f..5bc2b24d 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/world/ChunkHolderMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/world/ChunkHolderMixin.java @@ -7,32 +7,28 @@ import net.minecraft.world.level.LightLayer; import net.minecraft.world.level.chunk.LevelChunk; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; @Mixin(value = ChunkHolder.class, priority = 1500) public class ChunkHolderMixin { - @Unique - private static final Object async$lock = new Object(); - @WrapMethod(method = "broadcastChanges") private void wrapBroadcastChanges(LevelChunk chunk, Operation original) { - synchronized (async$lock) { + synchronized (this) { original.call(chunk); } } @WrapMethod(method = "blockChanged") - private void wrapBlockChanged(BlockPos pos, Operation original) { - synchronized (async$lock) { + private void wrapBlockChanged(BlockPos pos, Operation original) { + synchronized (this) { original.call(pos); } } @WrapMethod(method = "sectionLightChanged") - private void wrapSectionLightChanged(LightLayer lightLayer, int y, Operation original) { - synchronized (async$lock) { - original.call(lightLayer, y); + private void wrapSectionLightChanged(LightLayer layer, int chunkY, Operation original) { + synchronized (this) { + original.call(layer, chunkY); } } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/world/CollectingNeighborUpdaterMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/world/CollectingNeighborUpdaterMixin.java index 955c762b..9495fc88 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/world/CollectingNeighborUpdaterMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/world/CollectingNeighborUpdaterMixin.java @@ -22,7 +22,7 @@ public abstract class CollectingNeighborUpdaterMixin implements NeighborUpdater private List addedThisLayer = new CopyOnWriteArrayList<>(); @WrapMethod(method = "addAndRun") - private synchronized void syncAddAndRun(BlockPos pos, CollectingNeighborUpdater.NeighborUpdates entry, Operation original) { - original.call(pos, entry); + private synchronized void syncAddAndRun(BlockPos pos, CollectingNeighborUpdater.NeighborUpdates update, Operation original) { + original.call(pos, update); } } \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/world/DistanceManagerMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/world/DistanceManagerMixin.java index f08d875e..d5a6fc40 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/world/DistanceManagerMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/world/DistanceManagerMixin.java @@ -1,7 +1,7 @@ package com.axalotl.async.common.mixin.world; -import com.axalotl.async.common.parallelised.ConcurrentCollections; -import com.axalotl.async.common.parallelised.fastutil.ConcurrentLongLinkedOpenHashSet; +import com.axalotl.async.api.fastutil.ConcurrentLongLinkedOpenHashSet; +import com.axalotl.async.api.utils.ConcurrentCollections; import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.server.level.ChunkHolder; import net.minecraft.server.level.DistanceManager; @@ -18,10 +18,10 @@ public abstract class DistanceManagerMixin { @Shadow @Final @Mutable - Set chunksToUpdateFutures = ConcurrentCollections.newHashSet(); + protected Set chunksToUpdateFutures = ConcurrentCollections.newHashSet(); @Shadow @Final @Mutable - LongSet ticketsToRelease = new ConcurrentLongLinkedOpenHashSet(); + private LongSet ticketsToRelease = new ConcurrentLongLinkedOpenHashSet(); } \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/world/LevelChunkMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/world/LevelChunkMixin.java index 30cb1237..ae67d5f0 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/world/LevelChunkMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/world/LevelChunkMixin.java @@ -1,6 +1,6 @@ package com.axalotl.async.common.mixin.world; -import com.axalotl.async.common.parallelised.fastutil.Int2ObjectConcurrentHashMap; +import com.axalotl.async.api.fastutil.Int2ObjectConcurrentHashMap; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -28,7 +28,7 @@ private void init(CallbackInfo ci) { } @WrapMethod(method = "getListenerRegistry") - private synchronized GameEventListenerRegistry getListenerRegistry(int ySectionCoord, Operation original) { - return original.call(ySectionCoord); + private synchronized GameEventListenerRegistry getListenerRegistry(int section, Operation original) { + return original.call(section); } } \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/mixin/world/LevelMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/world/LevelMixin.java index 1346178e..a576589f 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/world/LevelMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/world/LevelMixin.java @@ -75,4 +75,4 @@ private Explosion explode(Entity source, DamageSource damageSource, ExplosionDam return original.call(source, damageSource, damageCalculator, x, y, z, radius, fire, explosionInteraction, spawnParticles, smallExplosionParticles, largeExplosionParticles, explosionSound); } } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/world/LevelTicksMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/world/LevelTicksMixin.java index 122f5b7c..ec8c5210 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/world/LevelTicksMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/world/LevelTicksMixin.java @@ -1,7 +1,7 @@ package com.axalotl.async.common.mixin.world; -import com.axalotl.async.common.parallelised.fastutil.Long2LongConcurrentHashMap; -import com.axalotl.async.common.parallelised.fastutil.Long2ObjectConcurrentHashMap; +import com.axalotl.async.api.fastutil.Long2LongConcurrentHashMap; +import com.axalotl.async.api.fastutil.Long2ObjectConcurrentHashMap; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import it.unimi.dsi.fastutil.longs.Long2LongMap; @@ -23,41 +23,38 @@ public abstract class LevelTicksMixin implements LevelTickAccess { @Shadow private final Long2LongMap nextTickForContainer = Util.make(new Long2LongConcurrentHashMap(), p_193262_ -> p_193262_.defaultReturnValue(Long.MAX_VALUE)); - @Unique - private static final Object async$lock = new Object(); - @WrapMethod(method = "sortContainersToTick") - private void wrapContainersToTick(long gameTime, Operation original) { - synchronized (async$lock) { - original.call(gameTime); + private void wrapContainersToTick(long currentTick, Operation original) { + synchronized (this) { + original.call(currentTick); } } @WrapMethod(method = "collectTicks") - private void wrapCollectTicks(long gameTime, int maxAllowedTicks, ProfilerFiller profiler, Operation original) { - synchronized (async$lock) { - original.call(gameTime, maxAllowedTicks, profiler); + private void wrapCollectTicks(long currentTick, int maxTicksToProcess, ProfilerFiller profiler, Operation original) { + synchronized (this) { + original.call(currentTick, maxTicksToProcess, profiler); } } @WrapMethod(method = "schedule") - private void wrapSchedule(ScheduledTick p_193252_, Operation original) { - synchronized (async$lock) { - original.call(p_193252_); + private void wrapSchedule(ScheduledTick tick, Operation original) { + synchronized (this) { + original.call(tick); } } @WrapMethod(method = "addContainer") - private void wrapAddContainer(net.minecraft.world.level.ChunkPos pos, LevelChunkTicks<@NotNull T> ticks, Operation original) { - synchronized (async$lock) { - original.call(pos, ticks); + private void wrapAddContainer(net.minecraft.world.level.ChunkPos pos, LevelChunkTicks<@NotNull T> container, Operation original) { + synchronized (this) { + original.call(pos, container); } } @WrapMethod(method = "removeContainer") private void wrapRemoveContainer(net.minecraft.world.level.ChunkPos pos, Operation original) { - synchronized (async$lock) { + synchronized (this) { original.call(pos); } } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/world/RandomSequencesMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/world/RandomSequencesMixin.java index b4d89a68..ac6192a7 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/world/RandomSequencesMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/world/RandomSequencesMixin.java @@ -1,6 +1,6 @@ package com.axalotl.async.common.mixin.world; -import com.axalotl.async.common.parallelised.ConcurrentCollections; +import com.axalotl.async.api.utils.ConcurrentCollections; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.RandomSequence; import net.minecraft.world.RandomSequences; @@ -14,4 +14,4 @@ public class RandomSequencesMixin { @Shadow private final Map sequences = ConcurrentCollections.newHashMap(); -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/world/ScoreboardMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/world/ScoreboardMixin.java index c8ad40dd..7c7e69c0 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/world/ScoreboardMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/world/ScoreboardMixin.java @@ -1,6 +1,6 @@ package com.axalotl.async.common.mixin.world; -import com.axalotl.async.common.parallelised.ConcurrentCollections; +import com.axalotl.async.api.utils.ConcurrentCollections; import net.minecraft.world.scores.Objective; import net.minecraft.world.scores.Score; import net.minecraft.world.scores.Scoreboard; diff --git a/common/src/main/java/com/axalotl/async/common/mixin/world/SectionStorageMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/world/SectionStorageMixin.java index 80d9b74e..a4cb7389 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/world/SectionStorageMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/world/SectionStorageMixin.java @@ -1,7 +1,7 @@ package com.axalotl.async.common.mixin.world; -import com.axalotl.async.common.parallelised.fastutil.ConcurrentLongLinkedOpenHashSet; -import com.axalotl.async.common.parallelised.fastutil.Long2ObjectConcurrentHashMap; +import com.axalotl.async.api.fastutil.ConcurrentLongLinkedOpenHashSet; +import com.axalotl.async.api.fastutil.Long2ObjectConcurrentHashMap; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; @@ -29,4 +29,4 @@ public abstract class SectionStorageMixin implements AutoCloseable { private synchronized void release(ChunkPos chunkPos, Operation original) { original.call(chunkPos); } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/mixin/world/ServerLevelMixin.java b/common/src/main/java/com/axalotl/async/common/mixin/world/ServerLevelMixin.java index 6a85f775..c493e1cf 100644 --- a/common/src/main/java/com/axalotl/async/common/mixin/world/ServerLevelMixin.java +++ b/common/src/main/java/com/axalotl/async/common/mixin/world/ServerLevelMixin.java @@ -1,9 +1,8 @@ package com.axalotl.async.common.mixin.world; +import com.axalotl.async.api.utils.ConcurrentCollections; import com.axalotl.async.common.ParallelProcessor; import com.axalotl.async.common.config.AsyncConfig; -import com.axalotl.async.common.parallelised.ConcurrentCollections; -import com.axalotl.async.common.parallelised.utils.PortalCreationCache; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; @@ -19,7 +18,11 @@ import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Mob; -import net.minecraft.world.level.*; +import net.minecraft.world.level.BlockEventData; +import net.minecraft.world.level.Explosion; +import net.minecraft.world.level.ExplosionDamageCalculator; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.WorldGenLevel; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.entity.EntityTickList; @@ -33,16 +36,18 @@ import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.Inject; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; -import java.util.concurrent.*; -import java.util.function.BooleanSupplier; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.Supplier; @@ -71,9 +76,6 @@ protected ServerLevelMixin(WritableLevelData levelData, ResourceKey dimen super(levelData, dimension, registryAccess, dimensionTypeRegistration, profiler, isClientSide, isDebug, biomeZoomSeed, maxChainedNeighborUpdates); } - @Shadow - protected abstract boolean shouldDiscardEntity(Entity entity); - @Shadow public abstract @NotNull ServerLevel getLevel(); @@ -124,7 +126,8 @@ private void overwriteEntityTicking(EntityTickList entityTickList, Consumer> despawnTasks = new ArrayList<>(); for (int i = 0; i < toDespawnCheck.size(); i += chunkSize) { List chunk = toDespawnCheck.subList(i, Math.min(i + chunkSize, toDespawnCheck.size())); @@ -134,8 +137,14 @@ private void overwriteEntityTicking(EntityTickList entityTickList, Consumer original) { - if (!AsyncConfig.disabled && AsyncConfig.enableAsyncRandomTicks) { - CompletableFuture.runAsync(() -> original.call(chunk, randomTickSpeed), ParallelProcessor.tickPool).exceptionally(e -> { - ParallelProcessor.LOGGER.error("Error in async random ticks, switching to synchronous", e); + if (!AsyncConfig.disabled + && AsyncConfig.enableAsyncRandomTicks + && ParallelProcessor.executor != null + && !ParallelProcessor.executor.isShutdown()) { + CompletableFuture.runAsync(() -> original.call(chunk, randomTickSpeed), ParallelProcessor.executor).exceptionally(e -> { original.call(chunk, randomTickSpeed); return null; }); @@ -191,9 +202,4 @@ private void tickChunk(LevelChunk chunk, int randomTickSpeed, Operation or original.call(chunk, randomTickSpeed); } } - - @Inject(method = "tick", at = @At("HEAD")) - private void async_clearPortalCache(BooleanSupplier hasTimeLeft, CallbackInfo ci) { - PortalCreationCache.clear(); - } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/parallelised/fastutil/ConcurrentLongLinkedOpenHashSet.java b/common/src/main/java/com/axalotl/async/common/parallelised/fastutil/ConcurrentLongLinkedOpenHashSet.java deleted file mode 100644 index f096198b..00000000 --- a/common/src/main/java/com/axalotl/async/common/parallelised/fastutil/ConcurrentLongLinkedOpenHashSet.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.axalotl.async.common.parallelised.fastutil; - -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. - */ -public class ConcurrentLongLinkedOpenHashSet extends LongLinkedOpenHashSet { - - private final ConcurrentSkipListSet backing = new ConcurrentSkipListSet<>(); - - public ConcurrentLongLinkedOpenHashSet() { - } - - @Override - public boolean add(final long k) { - return backing.add(k); - } - - @Override - public boolean addAll(Collection 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(); - } - - @Override - public long firstLong() { - return Optional.ofNullable(backing.first()) - .orElseThrow(() -> new NoSuchElementException("Set is empty")); - } - - @Override - public long lastLong() { - return Optional.ofNullable(backing.last()) - .orElseThrow(() -> new NoSuchElementException("Set is empty")); - } - - @Override - public long removeFirstLong() { - long first = firstLong(); - backing.remove(first); - return first; - } - - @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(); - } -} \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/parallelised/utils/FastBitRadixSort.java b/common/src/main/java/com/axalotl/async/common/parallelised/utils/FastBitRadixSort.java deleted file mode 100644 index 9dd62c26..00000000 --- a/common/src/main/java/com/axalotl/async/common/parallelised/utils/FastBitRadixSort.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.axalotl.async.common.parallelised.utils; - -import net.minecraft.world.entity.Entity; - -public final class FastBitRadixSort { - private static final int SMALL_ARRAY_THRESHOLD = 6; - private static final ThreadLocal BITS_BUFFER = ThreadLocal.withInitial(() -> new long[0]); - - public void sort(Object[] entities, int size, net.minecraft.core.Position target) { - if (size <= 1) { - return; - } - - long[] bits = BITS_BUFFER.get(); - if (bits.length < size) { - bits = new long[size]; - BITS_BUFFER.set(bits); - } - - double tx = target.x(); - double ty = target.y(); - double tz = target.z(); - - for (int i = 0; i < size; i++) { - bits[i] = Double.doubleToRawLongBits(((Entity) entities[i]).distanceToSqr(tx, ty, tz)); - } - - fastRadixSort(entities, bits, 0, size - 1, 62); - } - - private static void fastRadixSort(Object[] ents, long[] bits, int low, int high, int bit) { - if (bit < 0 || low >= high) { - return; - } - if (high - low <= SMALL_ARRAY_THRESHOLD) { - insertionSort(ents, bits, low, high); - return; - } - - int i = low; - int j = high; - final long mask = 1L << bit; - - while (i <= j) { - while (i <= j && (bits[i] & mask) == 0) i++; - while (i <= j && (bits[j] & mask) != 0) j--; - if (i < j) swap(ents, bits, i++, j--); - } - - if (low < j) fastRadixSort(ents, bits, low, j, bit - 1); - if (i < high) fastRadixSort(ents, bits, i, high, bit - 1); - } - - private static void insertionSort(Object[] ents, long[] bits, int low, int high) { - for (int i = low + 1; i <= high; i++) { - int j = i; - Object currentEntity = ents[j]; - long currentBits = bits[j]; - while (j > low && bits[j - 1] > currentBits) { - ents[j] = ents[j - 1]; - bits[j] = bits[j - 1]; - j--; - } - ents[j] = currentEntity; - bits[j] = currentBits; - } - } - - private static void swap(Object[] ents, long[] bits, int a, int b) { - Object tempEntity = ents[a]; - ents[a] = ents[b]; - ents[b] = tempEntity; - long tempBits = bits[a]; - bits[a] = bits[b]; - bits[b] = tempBits; - } -} \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/parallelised/utils/ModCompatible.java b/common/src/main/java/com/axalotl/async/common/parallelised/utils/ModCompatible.java deleted file mode 100644 index 57d9ca71..00000000 --- a/common/src/main/java/com/axalotl/async/common/parallelised/utils/ModCompatible.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.axalotl.async.common.parallelised.utils; - -import com.axalotl.async.common.platform.PlatformUtils; - -import java.util.ArrayList; -import java.util.List; - -public class ModCompatible { - - public static List addUnsupportedMods() { - final List mods = new ArrayList<>(); - if (PlatformUtils.isModLoaded("create")) { - mods.add("create:*"); - } else if (PlatformUtils.isModLoaded("fowlplay")) { - mods.add("fowlplay:*"); - } - return mods; - } -} \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/parallelised/utils/PortalCreationCache.java b/common/src/main/java/com/axalotl/async/common/parallelised/utils/PortalCreationCache.java index 981346c9..9fe288df 100644 --- a/common/src/main/java/com/axalotl/async/common/parallelised/utils/PortalCreationCache.java +++ b/common/src/main/java/com/axalotl/async/common/parallelised/utils/PortalCreationCache.java @@ -22,4 +22,4 @@ public static void put(ResourceKey dimension, BlockUtil.FoundRectangle re public static void clear() { cache.clear(); } -} \ No newline at end of file +} diff --git a/common/src/main/java/com/axalotl/async/common/platform/ModPlatform.java b/common/src/main/java/com/axalotl/async/common/platform/ModPlatform.java index e80c0444..684cb219 100644 --- a/common/src/main/java/com/axalotl/async/common/platform/ModPlatform.java +++ b/common/src/main/java/com/axalotl/async/common/platform/ModPlatform.java @@ -6,6 +6,4 @@ public interface ModPlatform { void reloadConfig(); boolean isModLoaded(String id); - - boolean platformUsesRefmap(); } \ No newline at end of file diff --git a/common/src/main/java/com/axalotl/async/common/platform/Permission.java b/common/src/main/java/com/axalotl/async/common/platform/PlatformPermission.java similarity index 92% rename from common/src/main/java/com/axalotl/async/common/platform/Permission.java rename to common/src/main/java/com/axalotl/async/common/platform/PlatformPermission.java index 613ade55..e0bbdfa8 100644 --- a/common/src/main/java/com/axalotl/async/common/platform/Permission.java +++ b/common/src/main/java/com/axalotl/async/common/platform/PlatformPermission.java @@ -4,7 +4,7 @@ import java.util.function.Predicate; -public class Permission { +public class PlatformPermission { public static boolean check(CommandSourceStack source, String node, int level) { return PlatformUtils.hasPermission(source, node, level); diff --git a/common/src/main/java/com/axalotl/async/common/platform/PlatformUtils.java b/common/src/main/java/com/axalotl/async/common/platform/PlatformUtils.java index 4d897129..239a2372 100644 --- a/common/src/main/java/com/axalotl/async/common/platform/PlatformUtils.java +++ b/common/src/main/java/com/axalotl/async/common/platform/PlatformUtils.java @@ -12,10 +12,6 @@ public static void initialize() { PlatformUtils.minecraftPlatform = PlatformUtils.load(MinecraftPlatform.class); } - public static boolean platformUsesRefmap() { - return MOD_PLATFORM.platformUsesRefmap(); - } - public static void saveConfig() { MOD_PLATFORM.saveConfig(); } @@ -32,7 +28,6 @@ public static boolean hasPermission(CommandSourceStack source, String node, int return minecraftPlatform.hasPermission(source, node, level); } - private static T load(Class clazz) { return ServiceLoader.load(clazz, clazz.getClassLoader()).findFirst().orElseThrow(() -> new NullPointerException("Failed to load service for " + clazz.getName())); } diff --git a/common/src/main/java/com/axalotl/async/common/utils/TickStats.java b/common/src/main/java/com/axalotl/async/common/utils/TickStats.java new file mode 100644 index 00000000..7c24fc50 --- /dev/null +++ b/common/src/main/java/com/axalotl/async/common/utils/TickStats.java @@ -0,0 +1,69 @@ +package com.axalotl.async.common.utils; + +import com.axalotl.async.common.ParallelProcessor; +import net.minecraft.world.entity.EntityType; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.LongAdder; + +public class TickStats { + public static final Map, LongAdder> TICK_TIME_NS = new ConcurrentHashMap<>(); + public static final Map, LongAdder> TICK_COUNT = new ConcurrentHashMap<>(); + public static final Map, LongAdder> ASYNC_TICK_TIME_NS = new ConcurrentHashMap<>(); + public static final Map, LongAdder> ASYNC_TICK_COUNT = new ConcurrentHashMap<>(); + public static final AtomicInteger RECORDING_TICKS_LEFT = new AtomicInteger(0); + + private static volatile Runnable onComplete = null; + + public static void startRecording(int ticks, Runnable onComplete) { + clean(); + TickStats.onComplete = onComplete; + RECORDING_TICKS_LEFT.set(ticks); + } + + public static void clean() { + TICK_TIME_NS.clear(); + TICK_COUNT.clear(); + ASYNC_TICK_TIME_NS.clear(); + ASYNC_TICK_COUNT.clear(); + } + + public static void onServerTick() { + if (RECORDING_TICKS_LEFT.get() > 0 && RECORDING_TICKS_LEFT.decrementAndGet() == 0) { + Runnable cb = onComplete; + onComplete = null; + if (cb != null) { + cb.run(); + } + } + } + + public static double getMSPTForType(EntityType type, int recordedTicks) { + if (recordedTicks <= 0) return 0; + + double syncMs = 0; + double asyncMs = 0; + + LongAdder syncTime = TICK_TIME_NS.get(type); + if (syncTime != null) { + syncMs = syncTime.sum() / 1_000_000.0; + } + + LongAdder asyncTime = ASYNC_TICK_TIME_NS.get(type); + if (asyncTime != null) { + int currentPoolSize = ParallelProcessor.getPoolSize(); + double rawAsyncMs = asyncTime.sum() / 1_000_000.0; + asyncMs = currentPoolSize > 0 ? rawAsyncMs / currentPoolSize : rawAsyncMs; + } + + return (syncMs + asyncMs) / recordedTicks; + } + + public static void resetEntityTickStats() { + clean(); + RECORDING_TICKS_LEFT.set(0); + onComplete = null; + } +} \ No newline at end of file diff --git a/common/src/main/resources/META-INF/accesstransformer.cfg b/common/src/main/resources/META-INF/accesstransformer.cfg index 9d50ac25..4d0433c9 100644 --- a/common/src/main/resources/META-INF/accesstransformer.cfg +++ b/common/src/main/resources/META-INF/accesstransformer.cfg @@ -28,4 +28,5 @@ public net.minecraft.world.level.LocalMobCapCalculator$MobCounts public net.minecraft.world.entity.animal.Fox$FoxBreedGoal public net.minecraft.world.level.NaturalSpawner getRoughBiome(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/chunk/ChunkAccess;)Lnet/minecraft/world/level/biome/Biome; public net.minecraft.world.level.NaturalSpawner$SpawnState (ILit/unimi/dsi/fastutil/objects/Object2IntOpenHashMap;Lnet/minecraft/world/level/PotentialCalculator;Lnet/minecraft/world/level/LocalMobCapCalculator;)V -public net.minecraft.server.level.ChunkMap getChunks()Ljava/lang/Iterable; \ No newline at end of file +public net.minecraft.server.level.ChunkMap getChunks()Ljava/lang/Iterable; +public net.minecraft.server.level.ServerChunkCache$ChunkAndHolder (Lnet/minecraft/world/level/chunk/LevelChunk;Lnet/minecraft/server/level/ChunkHolder;)V diff --git a/common/src/main/resources/async.accesswidener b/common/src/main/resources/async.accesswidener index 6496aa5e..3ed4de83 100644 --- a/common/src/main/resources/async.accesswidener +++ b/common/src/main/resources/async.accesswidener @@ -31,4 +31,5 @@ accessible class net/minecraft/world/level/NaturalSpawner accessible method net/minecraft/world/level/NaturalSpawner getRoughBiome (Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/chunk/ChunkAccess;)Lnet/minecraft/world/level/biome/Biome; accessible class net/minecraft/world/level/NaturalSpawner$SpawnState accessible method net/minecraft/world/level/NaturalSpawner$SpawnState (ILit/unimi/dsi/fastutil/objects/Object2IntOpenHashMap;Lnet/minecraft/world/level/PotentialCalculator;Lnet/minecraft/world/level/LocalMobCapCalculator;)V -accessible method net/minecraft/server/level/ChunkMap getChunks ()Ljava/lang/Iterable; \ No newline at end of file +accessible method net/minecraft/server/level/ChunkMap getChunks ()Ljava/lang/Iterable; +accessible method net/minecraft/server/level/ServerChunkCache$ChunkAndHolder (Lnet/minecraft/world/level/chunk/LevelChunk;Lnet/minecraft/server/level/ChunkHolder;)V diff --git a/common/src/main/resources/async.common.mixins.json b/common/src/main/resources/async.common.mixins.json index 31fefe42..546088ac 100644 --- a/common/src/main/resources/async.common.mixins.json +++ b/common/src/main/resources/async.common.mixins.json @@ -22,6 +22,7 @@ "entity.GossipContainerMixin", "entity.InteractWithDoorMixin", "entity.LivingEntityMixin", + "entity.MinecartHopperMixin", "entity.MobMixin", "entity.NearestVisibleLivingEntitiesMixin", "entity.PandaMixin", @@ -31,7 +32,6 @@ "entity.RaidMixin", "entity.SensingMixin", "entity.ShowTradesToPlayerMixin", - "entity.VillagerMixin", "entity.breed.AnimalMakeLoveMixin", "entity.breed.FoxBreedGoalMixin", "entity.breed.FrogMixin", @@ -47,22 +47,26 @@ "entity.spawn.LocalMobCapCalculatorMixin", "entity.spawn.PotentialCalculatorMixin", "lithium.LithiumGameEventDispatcherStorage", + "lithium.LithiumInternerMixin", + "lithium.LithiumSectionedBlockChangeTrackerMixin", "lithium.LithiumServerLevel", + "lithium.ReferenceMaskedListMixin", "server.ChunkMapMixin", "server.ChunkMapTrackedEntityMixin", "server.MinecraftServerMixin", "server.PersistentEntitySectionManagerCallbackMixin", "server.PersistentEntitySectionManagerMixin", "server.ServerChunkCacheMixin", + "server.ServerEntityMixin", "server.ServerLevelEntityCallbacksMixin", "server.ServerPlayerMixin", - "utils.FastUtilsMixin", - "utils.LegacyRandomSourceMixin", + "utils.FastUtilSynchronizeMixin", "utils.LockHelperMixin", "utils.NetherPortalBlockMixin", "utils.ShufflingListMixin", "utils.SyncAllMixin", "utils.ThreadingDetectorMixin", + "utils.ThreadSafeLegacyRandomSourceMixin", "vmp.VMPChunkMapMixin", "world.BlockMixin", "world.ChunkHolderMixin", @@ -77,10 +81,11 @@ "world.ServerLevelMixin" ], "client": [], - "server": [], + "server": [ + "entity.VillagerMixin" + ], "injectors": { "defaultRequire": 1 }, "plugin": "com.axalotl.async.common.mixin.utils.SynchronisePlugin" } - diff --git a/common/src/main/resources/pack.mcmeta b/common/src/main/resources/pack.mcmeta deleted file mode 100644 index 52854ec4..00000000 --- a/common/src/main/resources/pack.mcmeta +++ /dev/null @@ -1,6 +0,0 @@ -{ - "pack": { - "description": "${mod_name}", - "pack_format": 8 - } -} \ No newline at end of file diff --git a/fabric/build.gradle b/fabric/build.gradle index e524431d..ba06a9dd 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -7,6 +7,7 @@ plugins { } repositories { + maven { url 'https://maven.parchmentmc.org' } maven { url 'https://api.modrinth.com/maven' } } @@ -50,10 +51,11 @@ dependencies { include(implementation(annotationProcessor("com.github.bawnorton.mixinsquared:mixinsquared-fabric:${mixinsquared_version}"))) modImplementation 'com.electronwill.night-config:toml:3.8.1' - include group: 'com.electronwill.night-config', name: 'toml', version: '3.8.1' - include group: 'com.electronwill.night-config', name: 'core', version: '3.8.1' - compileOnly 'org.projectlombok:lombok:1.18.32' - annotationProcessor 'org.projectlombok:lombok:1.18.32' + include "com.electronwill.night-config:toml:3.8.1" + include "com.electronwill.night-config:core:3.8.1" + compileOnly 'org.projectlombok:lombok:1.18.44' + annotationProcessor 'org.projectlombok:lombok:1.18.44' + implementation include(project(":api")) implementation "maven.modrinth:lithium:${lithium_version}-neoforge" modImplementation(include("me.lucko:fabric-permissions-api:${project.permission_api}")) @@ -93,4 +95,4 @@ loom { ) } } -} \ No newline at end of file +} diff --git a/fabric/src/main/java/com/axalotl/async/fabric/AsyncFabric.java b/fabric/src/main/java/com/axalotl/async/fabric/AsyncFabric.java index b15dc454..6c2e6dfd 100644 --- a/fabric/src/main/java/com/axalotl/async/fabric/AsyncFabric.java +++ b/fabric/src/main/java/com/axalotl/async/fabric/AsyncFabric.java @@ -31,4 +31,4 @@ public void onInitialize() { LOGGER.info("Async Initialized Successfully!"); } -} \ No newline at end of file +} diff --git a/fabric/src/main/java/com/axalotl/async/fabric/config/AsyncConfig.java b/fabric/src/main/java/com/axalotl/async/fabric/config/AsyncConfig.java index 6214c8de..4870fa6d 100644 --- a/fabric/src/main/java/com/axalotl/async/fabric/config/AsyncConfig.java +++ b/fabric/src/main/java/com/axalotl/async/fabric/config/AsyncConfig.java @@ -40,6 +40,7 @@ public static void init() { LOGGER.warn("Configuration not found. Creating defaults..."); setDefaultValues(); saveConfig(); + com.axalotl.async.common.config.AsyncConfig.onConfigLoaded(); } else { CONFIG.load(); loadConfigValues(); @@ -50,6 +51,7 @@ public static void init() { LOGGER.error("Error loading configuration. Resetting to defaults.", t); setDefaultValues(); saveConfig(); + com.axalotl.async.common.config.AsyncConfig.onConfigLoaded(); } } @@ -89,7 +91,6 @@ private static void loadConfigValues() { } restoreComments(); - CONFIG.save(); } private static void restoreComments() { @@ -128,7 +129,7 @@ private static void removeUnusedKeys() { private static void setDefaultValues() { disabled = false; maxThreads = -1; - enableAsyncSpawn = true; + enableAsyncSpawn = false; enableAsyncRandomTicks = false; synchronizedEntities = getDefaultSynchronizedEntities(); } diff --git a/fabric/src/main/java/com/axalotl/async/fabric/mixin/server/ServerWatchdogMixin.java b/fabric/src/main/java/com/axalotl/async/fabric/mixin/server/ServerWatchdogMixin.java index bb0dc3bc..6e69021e 100644 --- a/fabric/src/main/java/com/axalotl/async/fabric/mixin/server/ServerWatchdogMixin.java +++ b/fabric/src/main/java/com/axalotl/async/fabric/mixin/server/ServerWatchdogMixin.java @@ -33,4 +33,4 @@ private void addCustomCrashReport(CallbackInfo ci, long i, long j, long k, Threa return sb.toString(); }); } -} \ No newline at end of file +} diff --git a/fabric/src/main/java/com/axalotl/async/fabric/mixin/utils/ClassInstanceMultiMapMixin.java b/fabric/src/main/java/com/axalotl/async/fabric/mixin/utils/ClassInstanceMultiMapMixin.java index ab5be7c4..da702bec 100644 --- a/fabric/src/main/java/com/axalotl/async/fabric/mixin/utils/ClassInstanceMultiMapMixin.java +++ b/fabric/src/main/java/com/axalotl/async/fabric/mixin/utils/ClassInstanceMultiMapMixin.java @@ -1,48 +1,45 @@ package com.axalotl.async.fabric.mixin.utils; -import com.axalotl.async.common.parallelised.ConcurrentCollections; -import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.axalotl.async.api.utils.ConcurrentCollections; import net.minecraft.util.ClassInstanceMultiMap; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.*; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.axalotl.async.api.utils.ConcurrentObjectArrayList; import java.util.*; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collector; @Mixin(value = ClassInstanceMultiMap.class) public abstract class ClassInstanceMultiMapMixin extends AbstractCollection { - @Unique - private static final Object async$lock = new Object(); + @Mutable + @Final + @Shadow + private Map, List> byClass; + @Mutable + @Final @Shadow - private final Map, List> byClass = new ConcurrentHashMap<>(); + private List allInstances; + @Final @Shadow - private final List allInstances = new CopyOnWriteArrayList<>(); + private Class baseClass; + + @Inject(method = "", at = @At("RETURN")) + private void replaceConcurrentCollections(CallbackInfo ci) { + this.byClass = new ConcurrentHashMap<>(); + this.allInstances = new ConcurrentObjectArrayList<>(); + this.byClass.put(this.baseClass, this.allInstances); + } @ModifyArg(method = "method_15217", at = @At(value = "INVOKE", target = "Ljava/util/stream/Stream;collect(Ljava/util/stream/Collector;)Ljava/lang/Object;")) private Collector> overwriteCollectToList(Collector> collector) { return ConcurrentCollections.toList(); } - - @WrapMethod(method = "add") - private boolean add(Object e, Operation original) { - synchronized (async$lock) { - return original.call(e); - } - } - - @WrapMethod(method = "remove") - private boolean remove(Object o, Operation original) { - synchronized (async$lock) { - return original.call(o); - } - } -} \ No newline at end of file +} diff --git a/fabric/src/main/java/com/axalotl/async/fabric/mixin/utils/UtilMixin.java b/fabric/src/main/java/com/axalotl/async/fabric/mixin/utils/UtilMixin.java index 6ac0c591..05a2a2e5 100644 --- a/fabric/src/main/java/com/axalotl/async/fabric/mixin/utils/UtilMixin.java +++ b/fabric/src/main/java/com/axalotl/async/fabric/mixin/utils/UtilMixin.java @@ -16,7 +16,7 @@ public abstract class UtilMixin { @Inject(method = "method_28123", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/ForkJoinWorkerThread;setName(Ljava/lang/String;)V")) - private static void registerThread(String string, AtomicInteger atomicInteger, ForkJoinPool pool, CallbackInfoReturnable cir, @Local ForkJoinWorkerThread forkJoinWorkerThread) { - ParallelProcessor.registerThread(string, forkJoinWorkerThread); + private static void registerThread(String name, AtomicInteger workerCount, ForkJoinPool pool, CallbackInfoReturnable cir, @Local(name = "thread") ForkJoinWorkerThread thread) { + ParallelProcessor.registerThread(name, thread); } -} \ No newline at end of file +} diff --git a/fabric/src/main/java/com/axalotl/async/fabric/platform/FabricMinecraftPlatform.java b/fabric/src/main/java/com/axalotl/async/fabric/platform/FabricMinecraftPlatform.java index ae312c3b..859de792 100644 --- a/fabric/src/main/java/com/axalotl/async/fabric/platform/FabricMinecraftPlatform.java +++ b/fabric/src/main/java/com/axalotl/async/fabric/platform/FabricMinecraftPlatform.java @@ -12,4 +12,4 @@ public boolean hasPermission(CommandSourceStack source, String node, int level) String permission = String.format("%s.%s", AsyncCommon.MODID, node); return Permissions.check(source, permission, level); } -} \ No newline at end of file +} diff --git a/fabric/src/main/java/com/axalotl/async/fabric/platform/FabricModPlatform.java b/fabric/src/main/java/com/axalotl/async/fabric/platform/FabricModPlatform.java index dab8c926..4bb4e5b9 100644 --- a/fabric/src/main/java/com/axalotl/async/fabric/platform/FabricModPlatform.java +++ b/fabric/src/main/java/com/axalotl/async/fabric/platform/FabricModPlatform.java @@ -20,9 +20,4 @@ public void reloadConfig() { public boolean isModLoaded(String id) { return FabricLoader.getInstance().isModLoaded(id); } - - @Override - public boolean platformUsesRefmap() { - return true; - } } \ No newline at end of file diff --git a/fabric/src/main/resources/async.fabric.mixins.json b/fabric/src/main/resources/async.fabric.mixins.json index 41429486..ade9c58b 100644 --- a/fabric/src/main/resources/async.fabric.mixins.json +++ b/fabric/src/main/resources/async.fabric.mixins.json @@ -2,7 +2,6 @@ "required": true, "minVersion": "0.8", "package": "com.axalotl.async.fabric.mixin", - "refmap": "${mod_id}.refmap.json", "compatibilityLevel": "JAVA_21", "mixins": [ "server.ServerWatchdogMixin", @@ -15,4 +14,4 @@ "injectors": { "defaultRequire": 1 } -} \ No newline at end of file +} diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 6fa8814b..21fd1e74 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -1,46 +1,45 @@ { - "schemaVersion": 1, - "id": "async", - "version": "${version}", - "name": "${mod_name}", - "description": "${description}", - "authors": [ - "${mod_author}" + "schemaVersion": 1, + "id": "async", + "version": "${version}", + "name": "${mod_name}", + "description": "${description}", + "authors": [ + "${mod_author}" + ], + "contact": { + "homepage": "https://fabricmc.net/", + "sources": "https://github.com/FabricMC/fabric-example-mod" + }, + "license": "GPL-3.0", + "icon": "async/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "com.axalotl.async.fabric.AsyncFabric" ], - "contact": { - "homepage": "https://fabricmc.net/", - "sources": "https://github.com/FabricMC/fabric-example-mod" - }, - "license": "${license}", - "icon": "async/icon.png", - "environment": "*", - "entrypoints": { - "main": [ - "com.axalotl.async.fabric.AsyncFabric" - ], - "mixinsquared": [ - "com.axalotl.async.common.mixin.utils.AsyncModMixinCanceller" - ] - }, - "mixins": [ - "async.common.mixins.json", - "async.fabric.mixins.json" - ], - "accessWidener": "async.accesswidener", - "depends": { - "fabricloader": "*", - "fabric-api": "*", - "minecraft": ">=1.21", - "java": ">=21" - }, - "breaks": { - "moonrise": "*", - "openpartiesandclaims": "*" - }, - "custom": { - "lithium:options": { - "mixin.alloc.chunk_random": false, - "mixin.ai.task.memory_change_counting": false - } + "mixinsquared": [ + "com.axalotl.async.common.mixin.utils.MixinCanceller" + ] + }, + "mixins": [ + "async.common.mixins.json", + "async.fabric.mixins.json" + ], + "accessWidener": "async.accesswidener", + "depends": { + "fabricloader": "*", + "fabric-api": "*", + "minecraft": ">=1.21", + "java": ">=21" + }, + "breaks": { + "moonrise": "*", + "openpartiesandclaims": "*" + }, + "custom": { + "lithium:options": { + "mixin.alloc.chunk_random": false } -} \ No newline at end of file + } +} diff --git a/gradle.properties b/gradle.properties index bd760267..16aa40e5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # Project -version=0.2.1+alpha-1.21.1 +version=0.2.4+alpha-1.21.1 group=com.axalotl.async java_version=21 @@ -18,26 +18,28 @@ parchment_minecraft_version=1.21.1 parchment_mappings_version=2024.11.17 # Fabric +# https://fabricmc.net/develop/ fabric_version=0.116.8+1.21.1 fabric_loader_version=0.18.4 # NeoForge +# https://neoforged.net/ neoforge_version=21.1.219 neoform_version=1.21.1-20240808.144430 neoforge_loader_version_range=[1,) -# API Dependencies -permission_api=0.3.1 -lithium_version=mc1.21.1-0.15.2 - # Gradle org.gradle.jvmargs=-Xmx3G org.gradle.daemon=true org.gradle.parallel=true org.gradle.caching=true +# API Dependencies +permission_api=0.3.1 +lithium_version=mc1.21.1-0.15.2 + # Mixin versions mixin_version=0.8.5 mixin_min_version=0.8.5 mixinextras_version=0.4.1 -mixinsquared_version=0.3.3 \ No newline at end of file +mixinsquared_version=0.3.3 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 23449a2b..5dd3c012 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/neoforge/build.gradle b/neoforge/build.gradle index 1ee105b3..73cde12e 100644 --- a/neoforge/build.gradle +++ b/neoforge/build.gradle @@ -1,7 +1,6 @@ plugins { id 'multiloader-loader' id 'net.neoforged.moddev' - id 'java-library' id 'idea' id "com.modrinth.minotaur" version "2.+" } @@ -11,6 +10,8 @@ repositories { } apply plugin: 'com.modrinth.minotaur' + +evaluationDependsOn(':api') modrinth { token = System.getenv("MODRINTH_TOKEN") projectId = "async" @@ -33,52 +34,37 @@ modrinth { neoForge { version = neoforge_version - // Automatically enable neoforge AccessTransformers if the file exists def at = project(':common').file('src/main/resources/META-INF/accesstransformer.cfg') if (at.exists()) { accessTransformers.from(at.absolutePath) } runs { - configureEach { - systemProperty("neoforge.enabledGameTestNamespaces", mod_id.toString()) - ideName = "NeoForge ${it.name.capitalize()} (${project.path})" // Unify the run config names with fabric - } - client { + create("client") { client() } - data { - data() - } - server { + create("server") { server() } } mods { - "${mod_id}" { + create("${mod_id}") { sourceSet sourceSets.main } } - if (project.hasProperty('parchment_mappings_version') && project.hasProperty('parchment_minecraft_version')) { - def parchmentMappings = project.parchment_mappings_version - def parchmentMinecraft = project.parchment_minecraft_version - - if (parchmentMappings != null && parchmentMinecraft != null && - !parchmentMappings.toString().isBlank() && !parchmentMinecraft.toString().isBlank()) { - - parchment { - mappingsVersion = parchmentMappings - minecraftVersion = parchmentMinecraft - } - } - } } dependencies { compileOnly(annotationProcessor("com.github.bawnorton.mixinsquared:mixinsquared-common:${mixinsquared_version}")) implementation(jarJar("com.github.bawnorton.mixinsquared:mixinsquared-neoforge:${mixinsquared_version}")) implementation "maven.modrinth:lithium:${lithium_version}-neoforge" - 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 project(':api') } -sourceSets.main.resources { srcDir 'src/generated/resources' } \ No newline at end of file +sourceSets.main.resources { srcDir 'src/generated/resources' } + +tasks.named('jar', Jar) { + dependsOn project(':api').tasks.named('classes') + from(project(':api').sourceSets.main.output) +} diff --git a/neoforge/src/main/java/com/axalotl/async/neoforge/config/AsyncConfig.java b/neoforge/src/main/java/com/axalotl/async/neoforge/config/AsyncConfig.java index dad0a2d2..68951831 100644 --- a/neoforge/src/main/java/com/axalotl/async/neoforge/config/AsyncConfig.java +++ b/neoforge/src/main/java/com/axalotl/async/neoforge/config/AsyncConfig.java @@ -65,6 +65,8 @@ public static void loadConfig() { com.axalotl.async.common.config.AsyncConfig.synchronizedEntities = entities.isEmpty() ? getDefaultSynchronizedEntities() : entities; + + com.axalotl.async.common.config.AsyncConfig.onConfigLoaded(); } public static void saveConfig() { diff --git a/neoforge/src/main/java/com/axalotl/async/neoforge/mixin/server/ServerWatchdogMixin.java b/neoforge/src/main/java/com/axalotl/async/neoforge/mixin/server/ServerWatchdogMixin.java index 8b805630..c097ad38 100644 --- a/neoforge/src/main/java/com/axalotl/async/neoforge/mixin/server/ServerWatchdogMixin.java +++ b/neoforge/src/main/java/com/axalotl/async/neoforge/mixin/server/ServerWatchdogMixin.java @@ -16,7 +16,7 @@ @Mixin(ServerWatchdog.class) public class ServerWatchdogMixin { - @Inject(method = "run", at = @At(value = "INVOKE", target = "Lnet/minecraft/CrashReport;addCategory(Ljava/lang/String;)Lnet/minecraft/CrashReportCategory;"), locals = LocalCapture.CAPTURE_FAILSOFT) + @Inject(method = "run", at = @At(value = "INVOKE", target = "Lnet/minecraft/CrashReport;addCategory(Ljava/lang/String;)Lnet/minecraft/CrashReportCategory;", remap = false), locals = LocalCapture.CAPTURE_FAILSOFT) private void addCustomCrashReport(CallbackInfo ci, long i, long j, long k, ThreadMXBean threadmxbean, ThreadInfo[] athreadinfo, StringBuilder stringbuilder, Error error, CrashReport crashreport) { CrashReportCategory threadDumpSection = crashreport.addCategory("Async thread dump"); threadDumpSection.setDetail("All Threads", () -> { @@ -33,4 +33,4 @@ private void addCustomCrashReport(CallbackInfo ci, long i, long j, long k, Threa return sb.toString(); }); } -} \ No newline at end of file +} diff --git a/neoforge/src/main/java/com/axalotl/async/neoforge/mixin/utils/ClassInstanceMultiMapMixin.java b/neoforge/src/main/java/com/axalotl/async/neoforge/mixin/utils/ClassInstanceMultiMapMixin.java index 0161497f..e6a8ab10 100644 --- a/neoforge/src/main/java/com/axalotl/async/neoforge/mixin/utils/ClassInstanceMultiMapMixin.java +++ b/neoforge/src/main/java/com/axalotl/async/neoforge/mixin/utils/ClassInstanceMultiMapMixin.java @@ -1,48 +1,48 @@ package com.axalotl.async.neoforge.mixin.utils; -import com.axalotl.async.common.parallelised.ConcurrentCollections; -import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.axalotl.async.api.utils.ConcurrentCollections; import net.minecraft.util.ClassInstanceMultiMap; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.axalotl.async.api.utils.ConcurrentObjectArrayList; import java.util.*; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collector; @Mixin(value = ClassInstanceMultiMap.class) public abstract class ClassInstanceMultiMapMixin extends AbstractCollection { - @Unique - private static final Object async$lock = new Object(); + @Mutable + @Final + @Shadow + private Map, List> byClass; + @Mutable + @Final @Shadow - private final Map, List> byClass = new ConcurrentHashMap<>(); + private List allInstances; + @Final @Shadow - private final List allInstances = new CopyOnWriteArrayList<>(); + private Class baseClass; + + @Inject(method = "", at = @At("RETURN")) + private void replaceConcurrentCollections(CallbackInfo ci) { + this.byClass = new ConcurrentHashMap<>(); + this.allInstances = new ConcurrentObjectArrayList<>(); + this.byClass.put(this.baseClass, this.allInstances); + } @ModifyArg(method = {"lambda$find$0", "m_13537_"}, at = @At(value = "INVOKE", target = "Ljava/util/stream/Stream;collect(Ljava/util/stream/Collector;)Ljava/lang/Object;")) private Collector> overwriteCollectToList(Collector> collector) { return ConcurrentCollections.toList(); } - - @WrapMethod(method = "add") - private boolean add(Object e, Operation original) { - synchronized (async$lock) { - return original.call(e); - } - } - - @WrapMethod(method = "remove") - private boolean remove(Object o, Operation original) { - synchronized (async$lock) { - return original.call(o); - } - } -} \ No newline at end of file +} diff --git a/neoforge/src/main/java/com/axalotl/async/neoforge/mixin/utils/UtilMixin.java b/neoforge/src/main/java/com/axalotl/async/neoforge/mixin/utils/UtilMixin.java index 366329d8..511f17b8 100644 --- a/neoforge/src/main/java/com/axalotl/async/neoforge/mixin/utils/UtilMixin.java +++ b/neoforge/src/main/java/com/axalotl/async/neoforge/mixin/utils/UtilMixin.java @@ -16,7 +16,7 @@ public abstract class UtilMixin { @Inject(method = {"lambda$makeExecutor$3", "m_303912_"}, at = @At(value = "INVOKE", target = "Ljava/util/concurrent/ForkJoinWorkerThread;setName(Ljava/lang/String;)V")) - private static void registerThread(String serviceName, AtomicInteger atomicinteger, ForkJoinPool p_314383_, CallbackInfoReturnable cir, @Local ForkJoinWorkerThread forkJoinWorkerThread) { - ParallelProcessor.registerThread(serviceName, forkJoinWorkerThread); + private static void registerThread(String serviceName, AtomicInteger workerCount, ForkJoinPool pool, CallbackInfoReturnable cir, @Local ForkJoinWorkerThread thread) { + ParallelProcessor.registerThread(serviceName, thread); } -} \ No newline at end of file +} diff --git a/neoforge/src/main/java/com/axalotl/async/neoforge/mixin/world/LevelMixin.java b/neoforge/src/main/java/com/axalotl/async/neoforge/mixin/world/LevelMixin.java index 43728522..432a8bca 100644 --- a/neoforge/src/main/java/com/axalotl/async/neoforge/mixin/world/LevelMixin.java +++ b/neoforge/src/main/java/com/axalotl/async/neoforge/mixin/world/LevelMixin.java @@ -24,4 +24,4 @@ private synchronized boolean overwriteAdd(ArrayList instance, Obj private synchronized boolean overwriteRemove(ArrayList instance, Object object) { return capturedBlockSnapshots.remove((BlockSnapshot) object); } -} \ No newline at end of file +} diff --git a/neoforge/src/main/java/com/axalotl/async/neoforge/platform/NeoForgeMinecraftPlatform.java b/neoforge/src/main/java/com/axalotl/async/neoforge/platform/NeoForgeMinecraftPlatform.java index a70542f5..f62803fc 100644 --- a/neoforge/src/main/java/com/axalotl/async/neoforge/platform/NeoForgeMinecraftPlatform.java +++ b/neoforge/src/main/java/com/axalotl/async/neoforge/platform/NeoForgeMinecraftPlatform.java @@ -21,4 +21,4 @@ public boolean hasPermission(CommandSourceStack source, String node, int level) return PermissionAPI.getPermission(player, permission); } -} \ No newline at end of file +} diff --git a/neoforge/src/main/java/com/axalotl/async/neoforge/platform/NeoForgeModPlatform.java b/neoforge/src/main/java/com/axalotl/async/neoforge/platform/NeoForgeModPlatform.java index d1570598..bc7ea5b5 100644 --- a/neoforge/src/main/java/com/axalotl/async/neoforge/platform/NeoForgeModPlatform.java +++ b/neoforge/src/main/java/com/axalotl/async/neoforge/platform/NeoForgeModPlatform.java @@ -21,9 +21,4 @@ public void reloadConfig() { public boolean isModLoaded(String id) { return FMLLoader.getLoadingModList().getModFileById(id) != null; } - - @Override - public boolean platformUsesRefmap() { - return false; - } -} \ No newline at end of file +} diff --git a/neoforge/src/main/java/com/axalotl/async/neoforge/platform/NeoForgePermissions.java b/neoforge/src/main/java/com/axalotl/async/neoforge/platform/NeoForgePermissions.java index c413f299..a6ca0333 100644 --- a/neoforge/src/main/java/com/axalotl/async/neoforge/platform/NeoForgePermissions.java +++ b/neoforge/src/main/java/com/axalotl/async/neoforge/platform/NeoForgePermissions.java @@ -10,8 +10,6 @@ public class NeoForgePermissions { private static final Map> PERMISSIONS = NeoForgePermissions.build( - "command.config", - "command.statistics" ); public static PermissionNode getPermissionNode(String node) { @@ -24,11 +22,11 @@ public static void addNodes(PermissionGatherEvent.Nodes event) { } } - private static Map> build(String... nodes) { + private static Map> build() { Map> permissions = new Object2ObjectOpenHashMap<>(); - for (String node : nodes) { - permissions.put(node, new PermissionNode<>(AsyncCommon.MODID, node, PermissionTypes.BOOLEAN, (x, y, z) -> false)); + for (String node : new String[]{"command.config", "command.statistics"}) { + permissions.put(node, new PermissionNode<>(AsyncCommon.MODID, node, PermissionTypes.BOOLEAN, (player, uuid, context) -> false)); } return permissions; } -} \ No newline at end of file +} diff --git a/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/neoforge/src/main/resources/META-INF/neoforge.mods.toml index a1276e1a..eb3e2d6d 100644 --- a/neoforge/src/main/resources/META-INF/neoforge.mods.toml +++ b/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -1,36 +1,37 @@ modLoader = "javafml" loaderVersion = "${neoforge_loader_version_range}" -license = "${license}" +license = "GPL-3.0" [[mods]] -modId = "${mod_id}" +modId = "async" version = "${version}" -displayName = "${mod_name}" +displayName = "Async" logoFile = "async/icon.png" -credits = "${credits}" -authors = "${mod_author}" +authors = "AxalotL, Alchemy, Bliss, FurryMileon, Grider, jediminer543" description = '''${description}''' + [[mixins]] -config = "${mod_id}.common.mixins.json" +config = "async.common.mixins.json" + [[mixins]] -config = "${mod_id}.neoforge.mixins.json" +config = "async.neoforge.mixins.json" -[[dependencies.${ mod_id }]] +[[dependencies.async]] modId = "minecraft" type = "required" versionRange = "${minecraft_version_range}" ordering = "NONE" side = "BOTH" -[[dependencies.${ mod_id }]] +[[dependencies.async]] modId = "moonrise" type = "incompatible" versionRange = "" ordering = "NONE" side = "BOTH" -[[dependencies.${ mod_id }]] +[[dependencies.async]] modId = "openpartiesandclaims" type = "incompatible" versionRange = "" @@ -38,5 +39,4 @@ ordering = "NONE" side = "BOTH" ["lithium:options"] -"mixin.alloc.chunk_random" = false -"mixin.ai.task.memory_change_counting" = false \ No newline at end of file +"mixin.alloc.chunk_random" = false \ No newline at end of file diff --git a/neoforge/src/main/resources/META-INF/services/com.bawnorton.mixinsquared.api.MixinCanceller b/neoforge/src/main/resources/META-INF/services/com.bawnorton.mixinsquared.api.MixinCanceller index aba7f614..fb285024 100644 --- a/neoforge/src/main/resources/META-INF/services/com.bawnorton.mixinsquared.api.MixinCanceller +++ b/neoforge/src/main/resources/META-INF/services/com.bawnorton.mixinsquared.api.MixinCanceller @@ -1 +1 @@ -com.axalotl.async.common.mixin.utils.AsyncModMixinCanceller \ No newline at end of file +com.axalotl.async.common.mixin.utils.MixinCanceller \ No newline at end of file diff --git a/neoforge/src/main/resources/async.neoforge.mixins.json b/neoforge/src/main/resources/async.neoforge.mixins.json index 1f5b2db9..c3f9f7a0 100644 --- a/neoforge/src/main/resources/async.neoforge.mixins.json +++ b/neoforge/src/main/resources/async.neoforge.mixins.json @@ -14,4 +14,4 @@ "injectors": { "defaultRequire": 1 } -} \ No newline at end of file +} diff --git a/settings.gradle b/settings.gradle index 59e60c97..57dc1eea 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,31 +1,8 @@ pluginManagement { repositories { + maven { url = uri("https://maven.fabricmc.net/") } + maven { url = uri("https://maven.neoforged.net/releases/") } gradlePluginPortal() - mavenCentral() - exclusiveContent { - forRepository { - maven { - name = 'Fabric' - url = uri('https://maven.fabricmc.net') - } - } - filter { - includeGroupByRegex("net\\.fabricmc(\\..+)?") - includeGroup('fabric-loom') - } - } - - exclusiveContent { - forRepository { - maven { - name = 'Sponge' - url = uri('https://repo.spongepowered.org/repository/maven-public') - } - } - filter { - includeGroupAndSubgroups("org.spongepowered") - } - } } } @@ -33,8 +10,8 @@ plugins { id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' } -// This should match the folder name of the project, or else IDEA may complain (see https://youtrack.jetbrains.com/issue/IDEA-317606) -rootProject.name = 'AsyncMultiloader' +rootProject.name = 'Async' include('common') include('fabric') -include('neoforge') \ No newline at end of file +include('neoforge') +include('api') \ No newline at end of file