From aee7deb93234a0f7ce5a4467b189697b962594c5 Mon Sep 17 00:00:00 2001 From: alchemyyy Date: Wed, 24 Jun 2026 22:30:22 -0700 Subject: [PATCH] Backport master 26.1 changes to 1.21.1 This is a backport of the changes from the 26.1 branch after the 1.21.11 backport on 2026-02-23. This is a pretty nasty commit because of the amount of refactoring that got backported. - Moved shared concurrency helpers into a new api module, including the fastutil-backed concurrent maps/sets, IteratorSafeOrderedReferenceSet, ConcurrentCollections, ConcurrentObjectArrayList, AsyncCompatible, SensorUtils, and SyncItemPickup. - Updated common, Fabric, and NeoForge code to consume the shared API helpers. Refreshed the async entity ticking pipeline and related mixins: - update ParallelProcessor compatibility checks and tick error handling - added TickStats and refresh stats/config command output - synchronized item pickup call sites through SyncItemPickup - updated entity, AI sensor, breeding, movement, server, world, and Lithium mixins for the backported 26.1 concurrency changes - added MinecartHopperMixin, ServerEntityMixin, ThreadSafeLegacyRandomSourceMixin, LithiumInternerMixin, LithiumSectionedBlockChangeTrackerMixin, and ReferenceMaskedListMixin - removed older helpers only where they were replaced or unused on this branch 1.21.1-specific method signatures and injection targets that were left alone or restored after checking them against 1.21.1: - Mob tryEquip and pickUpItem wrappers on the 1.21.1 signatures - vanilla pickup mixins on pickUpItem(ItemEntity), not the newer ServerLevel signature - ChunkHolder blockChanged and sectionLightChanged wrappers void-returning - LivingEntity tickEffects List.of hook that does not exist in 1.21.1 - 1.21.1 Util and ClassInstanceMultiMap lambda names - ServerWatchdog local capture for the 1.21.1 watchdog body - CrashReport.addCategory for NeoForge production runtime - PersistentEntitySectionManager.Callback onMove/onRemove locking - NetherPortalBlock getExitPortal locking/cache with PortalCreationCache - NeoForge Level capturedBlockSnapshots add/remove synchronization - ServerChunkCache ChunkAndHolder constructor access transformer for clean CI builds I left the old LegacyRandomSourceMixin removed because ThreadSafeLegacyRandomSourceMixin now covers the same 1.21.1 methods directly with setSeed, next, and nextGaussian overwrites. Fixed the NeoForge API packaging by embedding the named API output directly into the NeoForge jar instead of jarJar'ing the Fabric/intermediary-remapped API artifact. This prevents runtime NoSuchMethodError failures such as SensorUtils.comparingDouble(Entity) and SyncItemPickup.wrap(ItemEntity, Runnable) resolving against intermediary Minecraft descriptors. Updated loader metadata, mixin configs, access wideners/transformers, Gradle config, README, changelog, and issue template metadata for the backported branch. Verified these changes with a cache-disabled CI-style build: ./gradlew clean processIncludeJars build --stacktrace --rerun-tasks --no-build-cache Also checked the sensitive mixin targets against the 1.21.1 generated sources/jars with javap/source spot checks, and ran some quick tests against my neoforge 1.21.1 modpack server. I have not verified fabric. --- .github/ISSUE_TEMPLATE/bug_report.md | 8 +- CHANGELOG.md | 3 + README.md | 21 +- api/build.gradle | 96 ++++++++ .../ConcurrentLongLinkedOpenHashSet.java | 175 +++++++++++++++ .../fastutil/ConcurrentLongSortedSet.java | 93 +++++++- .../async/api}/fastutil/FastUtilHackUtil.java | 62 +++++- .../fastutil/Int2ObjectConcurrentHashMap.java | 25 ++- .../fastutil/Long2LongConcurrentHashMap.java | 22 +- .../Long2ObjectConcurrentHashMap.java | 7 +- .../async/api/utils/AsyncCompatible.java | 31 +++ .../api/utils}/ConcurrentCollections.java | 7 +- .../api/utils/ConcurrentObjectArrayList.java | 100 +++++++++ .../IteratorSafeOrderedReferenceSet.java | 142 +++++++++++- .../axalotl/async/api/utils/SensorUtils.java | 37 ++++ .../async/api/utils/SyncItemPickup.java | 56 +++++ .../src/main/groovy/multiloader-common.gradle | 44 +--- .../src/main/groovy/multiloader-loader.gradle | 2 +- common/build.gradle | 9 +- .../com/axalotl/async/common/AsyncCommon.java | 1 - .../async/common/ParallelProcessor.java | 206 +++++++++++------- .../async/common/commands/ConfigCommand.java | 17 +- .../async/common/commands/StatsCommand.java | 36 ++- .../async/common/config/AsyncConfig.java | 13 +- .../async/common/mixin/entity/AllayMixin.java | 15 +- .../common/mixin/entity/AnimalMixin.java | 50 +++-- .../mixin/entity/AreaEffectCloudMixin.java | 4 +- .../mixin/entity/AttributeInstanceMixin.java | 6 +- .../mixin/entity/AttributeMapMixin.java | 30 ++- .../async/common/mixin/entity/BeeMixin.java | 9 +- .../async/common/mixin/entity/BrainMixin.java | 130 ++--------- .../common/mixin/entity/DolphinMixin.java | 19 +- .../mixin/entity/EntityLookupMixin.java | 14 +- .../common/mixin/entity/EntityMixin.java | 39 +++- .../mixin/entity/EntityTickListMixin.java | 16 +- .../async/common/mixin/entity/FoxMixin.java | 15 +- .../mixin/entity/GoalSelectorMixin.java | 2 +- .../mixin/entity/GossipContainerMixin.java | 2 +- .../mixin/entity/InteractWithDoorMixin.java | 6 +- .../mixin/entity/LivingEntityMixin.java | 72 +++--- .../mixin/entity/MinecartHopperMixin.java | 58 +++++ .../async/common/mixin/entity/MobMixin.java | 37 ++-- .../NearestVisibleLivingEntitiesMixin.java | 8 +- .../async/common/mixin/entity/PandaMixin.java | 15 +- .../common/mixin/entity/PiglinMixin.java | 15 +- .../entity/PlayTagWithOtherKidsMixin.java | 4 +- .../async/common/mixin/entity/RaidMixin.java | 8 +- .../common/mixin/entity/RaiderMixin.java | 15 +- .../common/mixin/entity/VillagerMixin.java | 21 +- .../entity/breed/AnimalMakeLoveMixin.java | 22 +- .../mixin/entity/breed/FoxBreedGoalMixin.java | 32 +-- .../common/mixin/entity/breed/FrogMixin.java | 25 ++- .../mixin/entity/breed/SnifferMixin.java | 25 ++- .../mixin/entity/breed/TurtleMixin.java | 25 ++- .../entity/movement/EntitySectionMixin.java | 33 ++- .../movement/EntitySectionStorageMixin.java | 64 ++++-- .../entity/movement/PathFinderMixin.java | 4 +- .../entity/movement/PoiManagerMixin.java | 47 ++-- .../entity/sensor/NearestItemSensorMixin.java | 43 ++-- .../NearestLivingEntitiesSensorMixin.java | 50 ++--- .../entity/sensor/PlayerSensorMixin.java | 58 ++--- .../spawn/LocalMobCapCalculatorMixin.java | 4 +- .../LithiumGameEventDispatcherStorage.java | 18 +- .../mixin/lithium/LithiumInternerMixin.java | 20 ++ ...thiumSectionedBlockChangeTrackerMixin.java | 35 +++ .../mixin/lithium/LithiumServerLevel.java | 19 +- .../lithium/ReferenceMaskedListMixin.java | 69 ++++++ .../common/mixin/server/ChunkMapMixin.java | 9 +- .../server/ChunkMapTrackedEntityMixin.java | 2 +- .../mixin/server/MinecraftServerMixin.java | 19 +- ...tentEntitySectionManagerCallbackMixin.java | 2 +- .../PersistentEntitySectionManagerMixin.java | 26 +-- .../mixin/server/ServerChunkCacheMixin.java | 34 ++- .../mixin/server/ServerEntityMixin.java | 23 ++ .../ServerLevelEntityCallbacksMixin.java | 12 +- .../mixin/server/ServerPlayerMixin.java | 10 +- ...xin.java => FastUtilSynchronizeMixin.java} | 2 +- .../mixin/utils/LegacyRandomSourceMixin.java | 32 --- ...ixinCanceller.java => MixinCanceller.java} | 10 +- .../mixin/utils/NetherPortalBlockMixin.java | 4 +- .../common/mixin/utils/SyncAllMixin.java | 4 +- .../common/mixin/utils/SynchronisePlugin.java | 15 +- .../ThreadSafeLegacyRandomSourceMixin.java | 59 +++++ .../async/common/mixin/world/BlockMixin.java | 14 +- .../common/mixin/world/ChunkHolderMixin.java | 18 +- .../world/CollectingNeighborUpdaterMixin.java | 4 +- .../mixin/world/DistanceManagerMixin.java | 8 +- .../common/mixin/world/LevelChunkMixin.java | 6 +- .../async/common/mixin/world/LevelMixin.java | 2 +- .../common/mixin/world/LevelTicksMixin.java | 35 ++- .../mixin/world/RandomSequencesMixin.java | 4 +- .../common/mixin/world/ScoreboardMixin.java | 2 +- .../mixin/world/SectionStorageMixin.java | 6 +- .../common/mixin/world/ServerLevelMixin.java | 48 ++-- .../ConcurrentLongLinkedOpenHashSet.java | 101 --------- .../parallelised/utils/FastBitRadixSort.java | 77 ------- .../parallelised/utils/ModCompatible.java | 19 -- .../utils/PortalCreationCache.java | 2 +- .../async/common/platform/ModPlatform.java | 2 - ...ermission.java => PlatformPermission.java} | 2 +- .../async/common/platform/PlatformUtils.java | 5 - .../axalotl/async/common/utils/TickStats.java | 69 ++++++ .../resources/META-INF/accesstransformer.cfg | 3 +- common/src/main/resources/async.accesswidener | 3 +- .../main/resources/async.common.mixins.json | 15 +- common/src/main/resources/pack.mcmeta | 6 - fabric/build.gradle | 12 +- .../com/axalotl/async/fabric/AsyncFabric.java | 2 +- .../async/fabric/config/AsyncConfig.java | 5 +- .../mixin/server/ServerWatchdogMixin.java | 2 +- .../utils/ClassInstanceMultiMapMixin.java | 49 ++--- .../async/fabric/mixin/utils/UtilMixin.java | 6 +- .../platform/FabricMinecraftPlatform.java | 2 +- .../fabric/platform/FabricModPlatform.java | 5 - .../main/resources/async.fabric.mixins.json | 3 +- fabric/src/main/resources/fabric.mod.json | 85 ++++---- gradle.properties | 14 +- gradle/wrapper/gradle-wrapper.properties | 2 +- neoforge/build.gradle | 42 ++-- .../async/neoforge/config/AsyncConfig.java | 2 + .../mixin/server/ServerWatchdogMixin.java | 4 +- .../utils/ClassInstanceMultiMapMixin.java | 48 ++-- .../async/neoforge/mixin/utils/UtilMixin.java | 6 +- .../neoforge/mixin/world/LevelMixin.java | 2 +- .../platform/NeoForgeMinecraftPlatform.java | 2 +- .../platform/NeoForgeModPlatform.java | 7 +- .../platform/NeoForgePermissions.java | 10 +- .../resources/META-INF/neoforge.mods.toml | 24 +- ....bawnorton.mixinsquared.api.MixinCanceller | 2 +- .../main/resources/async.neoforge.mixins.json | 2 +- settings.gradle | 33 +-- 131 files changed, 2143 insertions(+), 1358 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 api/build.gradle create mode 100644 api/src/main/java/com/axalotl/async/api/fastutil/ConcurrentLongLinkedOpenHashSet.java rename {common/src/main/java/com/axalotl/async/common/parallelised => api/src/main/java/com/axalotl/async/api}/fastutil/ConcurrentLongSortedSet.java (55%) rename {common/src/main/java/com/axalotl/async/common/parallelised => api/src/main/java/com/axalotl/async/api}/fastutil/FastUtilHackUtil.java (93%) rename {common/src/main/java/com/axalotl/async/common/parallelised => api/src/main/java/com/axalotl/async/api}/fastutil/Int2ObjectConcurrentHashMap.java (78%) rename {common/src/main/java/com/axalotl/async/common/parallelised => api/src/main/java/com/axalotl/async/api}/fastutil/Long2LongConcurrentHashMap.java (87%) rename {common/src/main/java/com/axalotl/async/common/parallelised => api/src/main/java/com/axalotl/async/api}/fastutil/Long2ObjectConcurrentHashMap.java (96%) create mode 100644 api/src/main/java/com/axalotl/async/api/utils/AsyncCompatible.java rename {common/src/main/java/com/axalotl/async/common/parallelised => api/src/main/java/com/axalotl/async/api/utils}/ConcurrentCollections.java (83%) create mode 100644 api/src/main/java/com/axalotl/async/api/utils/ConcurrentObjectArrayList.java rename {common/src/main/java/com/axalotl/async/common/parallelised => api/src/main/java/com/axalotl/async/api}/utils/IteratorSafeOrderedReferenceSet.java (63%) create mode 100644 api/src/main/java/com/axalotl/async/api/utils/SensorUtils.java create mode 100644 api/src/main/java/com/axalotl/async/api/utils/SyncItemPickup.java create mode 100644 common/src/main/java/com/axalotl/async/common/mixin/entity/MinecartHopperMixin.java create mode 100644 common/src/main/java/com/axalotl/async/common/mixin/lithium/LithiumInternerMixin.java create mode 100644 common/src/main/java/com/axalotl/async/common/mixin/lithium/LithiumSectionedBlockChangeTrackerMixin.java create mode 100644 common/src/main/java/com/axalotl/async/common/mixin/lithium/ReferenceMaskedListMixin.java create mode 100644 common/src/main/java/com/axalotl/async/common/mixin/server/ServerEntityMixin.java rename common/src/main/java/com/axalotl/async/common/mixin/utils/{FastUtilsMixin.java => FastUtilSynchronizeMixin.java} (98%) delete mode 100644 common/src/main/java/com/axalotl/async/common/mixin/utils/LegacyRandomSourceMixin.java rename common/src/main/java/com/axalotl/async/common/mixin/utils/{AsyncModMixinCanceller.java => MixinCanceller.java} (71%) create mode 100644 common/src/main/java/com/axalotl/async/common/mixin/utils/ThreadSafeLegacyRandomSourceMixin.java delete mode 100644 common/src/main/java/com/axalotl/async/common/parallelised/fastutil/ConcurrentLongLinkedOpenHashSet.java delete mode 100644 common/src/main/java/com/axalotl/async/common/parallelised/utils/FastBitRadixSort.java delete mode 100644 common/src/main/java/com/axalotl/async/common/parallelised/utils/ModCompatible.java rename common/src/main/java/com/axalotl/async/common/platform/{Permission.java => PlatformPermission.java} (92%) create mode 100644 common/src/main/java/com/axalotl/async/common/utils/TickStats.java delete mode 100644 common/src/main/resources/pack.mcmeta 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