98 Commits

Author SHA1 Message Date
Kaan D.
60bbe3b710 Update README.md 2025-01-17 22:05:30 +03:00
Taiyou06
c2a7c25ec3 complete refactor and cleanup 2024-08-09 14:59:20 +03:00
Taiyou06
3e5edd79e8 "small" refactor 2024-08-09 14:31:06 +03:00
Taiyou06
346acf5e98 Merge pull request #5 from Gensokyo-Reimagined/1_21
1.21 update
2024-08-09 14:02:21 +03:00
Taiyou06
29715101b3 make it compile 2024-08-04 14:10:01 +03:00
Taiyou06
610c5fb21e Merge remote-tracking branch 'origin/1_21' into 1_21
# Conflicts:
#	src/main/java/net/gensokyoreimagined/nitori/mixin/needs_testing/util/block_tracking/ChunkSectionMixin.java
#	src/main/resources/mixins.core.json
2024-08-02 02:37:43 +03:00
Taiyou06
ac69c6222c properly rebase 2024-08-02 02:24:54 +03:00
Taiyou06
82e8165108 1.21 "works" 2024-08-02 02:23:23 +03:00
Altiami
808112315d 🔥🎵I'M NOT A BIG FAN OF PAPER🎵🔥 2024-08-02 02:20:47 +03:00
Taiyou06
4aada290b1 pain 2024-08-02 02:20:47 +03:00
Taiyou06
c8f42ea2be 1.21 update cycle begins 2024-08-02 02:20:46 +03:00
Taiyou06
1361c4b05c 1.21 "works" 2024-07-31 19:18:28 +03:00
Altiami
036aaf8b72 🔥🎵I'M NOT A BIG FAN OF PAPER🎵🔥 2024-07-31 03:35:43 -07:00
Taiyou06
c244617cfd pain 2024-07-30 22:53:01 +03:00
Taiyou06
1feb46711e 1.21 update cycle begins 2024-07-30 22:11:06 +03:00
Taiyou06
fd09c9ae8a Merge pull request #4 from Gensokyo-Reimagined/next
Nitori v1.4
2024-07-30 21:26:39 +03:00
Taiyou06
eea4f8e0d9 52 changed files 2024-07-30 20:51:14 +03:00
Taiyou06
d4ae8a6336 forgot to re add these back (was removed due the compile) 2024-07-22 11:35:36 +03:00
Taiyou06
59422498c4 faster predicates, faster angles etc... 2024-07-22 11:30:39 +03:00
DoggySazHi
566eebfe5a W.I.P. implement virtual threading 2024-07-20 23:23:14 -07:00
Taiyou06
663483c5e6 add package info about virtual threads 2024-07-21 02:51:34 +03:00
Taiyou06
26ae51054d update to 8.9 2024-07-20 14:53:46 +03:00
Taiyou06
f539a95579 Update build-commit.yml 2024-07-20 14:48:52 +03:00
Taiyou06
0bf5e28ef3 Revert "no daemon?"
This reverts commit b1cfedae2e.
2024-07-20 14:44:08 +03:00
Taiyou06
b1cfedae2e no daemon? 2024-07-20 14:41:14 +03:00
Taiyou06
711f9c08d5 try to make gradle happy 2024-07-20 14:36:02 +03:00
Taiyou06
604c2fc817 Update gradle.properties 2024-07-20 14:24:40 +03:00
Taiyou06
eb97d71e9e 1.4 start, virtual threads support 2024-07-20 14:16:03 +03:00
Taiyou06
5762d8aabd Merge branch 'refs/heads/master' into next
# Conflicts:
#	src/main/java/net/gensokyoreimagined/nitori/mixin/network/microopt/VarIntsMixin.java
#	src/main/java/net/gensokyoreimagined/nitori/mixin/util/block_tracking/ChunkSectionMixin.java
2024-07-20 12:16:09 +03:00
Taiyou06
afa4f6f932 chunksection mixin no worky with MT tracker 2024-07-20 12:15:28 +03:00
DoggySazHi
417f9e6f4b Move package for VarIntUtil to avoid mixin classloader issues, and removed an @Overwrite conflict with the Block class 2024-07-17 21:08:24 -07:00
DoggySazHi
a1a0a67283 re-enable ChunkSectionMixin 2024-07-17 21:04:21 -07:00
Taiyou06
7cad031876 micro optimizations (mirai patches) 2024-07-18 03:17:46 +03:00
Taiyou06
e036eb1ca6 faster collections 2024-07-15 14:09:57 +03:00
Taiyou06
728ba09022 Make Randoms 100x faster and fix VarInts 2024-07-15 10:07:10 +03:00
Taiyou06
b8dd2ddd4c mojang why 2024-07-14 17:03:49 +03:00
Taiyou06
377cdd6a7a Update README.md 2024-07-13 22:08:41 +03:00
Taiyou06
00f122741b Remove unusued IThreadedAnvilChunkStorage accessor 2024-07-13 19:25:55 +03:00
Taiyou06
c65eb6eecf Merge pull request #3 from Gensokyo-Reimagined/next
Nitori v1.3
2024-07-13 13:51:20 +03:00
Taiyou06
6cbee7408f Update VarIntsMixin.java 2024-07-13 13:29:15 +03:00
Taiyou06
dabb5dd159 Faster Filtering, Faster Voxel Shapes 2024-07-12 08:21:07 +03:00
Taiyou06
a98e77aade W.I.P Enum Redirector, Improve Encoding 2024-07-12 05:00:11 +03:00
Taiyou06
8a772889b5 Inline VarInts, Improve Running particles 2024-07-12 04:28:17 +03:00
Taiyou06
330a563d22 NoFallDamage check at ground 2024-07-12 01:28:46 +03:00
Taiyou06
eb42627111 Move stuff and small refactor 2024-07-11 08:23:07 +03:00
Taiyou06
4d5b6e71b6 Inlining, Faster Math 2024-07-11 07:51:08 +03:00
DoggySazHi
2a7939026f reimu AAAAAAAAAAAAAAAAA 2024-07-09 23:22:17 -07:00
Taiyou06
eb47da2772 disable regressed mixin and doggy pls check the commented mixins 2024-07-10 07:48:04 +03:00
Taiyou06
cd9e06fea7 Improve MobSpawns and BlockEntities prep to palette changes 2024-07-10 03:47:04 +03:00
Taiyou06
4aa707b8b5 we are at the end game 2024-07-09 04:51:11 +03:00
DoggySazHi
f598d6a5f0 Merge branch 'next' of github.com:/Gensokyo-Reimagined/Nitori into deez nuts 2024-07-08 15:38:00 -07:00
DoggySazHi
b5f1450bb0 MixinEntityTickList 2024-07-08 15:37:40 -07:00
Taiyou06
f155fec797 math.MathHelperIntrinsicMixin, math.GenericFastMath 2024-07-09 00:45:05 +03:00
Taiyou06
e9fd0a0363 vec.FastMathVec3DMixin 2024-07-09 00:09:04 +03:00
Taiyou06
123ffb0988 collections.GoalSelectorMixin 2024-07-09 00:05:25 +03:00
Taiyou06
ed6e029363 SortedArraySetMixin 2024-07-08 21:47:46 +03:00
Taiyou06
a824c25224 disable problematic mixin 2024-07-08 20:24:31 +03:00
Taiyou06
5bb7e63d75 perf testing begins 2024-07-08 16:28:30 +03:00
Taiyou06
c1e1e5d27c ok i eep 2024-07-07 11:49:30 +03:00
Taiyou06
5898896704 send help 2024-07-07 11:31:30 +03:00
Taiyou06
9fb9753202 we ball 2024-07-07 10:35:20 +03:00
Taiyou06
1e93913b63 Update README.md 2024-07-07 09:51:19 +03:00
Taiyou06
feaec74c94 just work 2024-07-07 05:08:36 +03:00
Taiyou06
7e795056a8 Optimize Secondary POI and remove spotless fr 2024-07-07 05:05:32 +03:00
Taiyou06
ce6219c206 just remove spotless not the publishing too 2024-07-07 04:25:34 +03:00
Taiyou06
0dbf375906 remove unneeded stuff
- remove BoxMixin as it's the same with MixinAABB (lmao)
- refactor MixinAABB's loaction
- disable problematic mixins for now
2024-07-07 04:11:54 +03:00
Taiyou06
a0100cd103 Revert "regression testings pt1"
This reverts commit 4651de95e9.
2024-07-07 04:03:49 +03:00
Taiyou06
c51fcad23a Revert "regression testings pt2"
This reverts commit 5a9831e94c.
2024-07-07 04:03:47 +03:00
Taiyou06
c3993313b9 Revert "whoops forgot this"
This reverts commit d191e500f4.
2024-07-07 04:03:45 +03:00
Taiyou06
d191e500f4 whoops forgot this 2024-07-07 03:41:07 +03:00
Taiyou06
5a9831e94c regression testings pt2 2024-07-07 03:36:18 +03:00
Taiyou06
4651de95e9 regression testings pt1 2024-07-07 03:27:39 +03:00
Taiyou06
4ce57781de Update WorldMixin.java 2024-07-07 02:37:51 +03:00
Taiyou06
a5fe6b78ab Update DirectionMixin.java 2024-07-07 02:35:11 +03:00
Taiyou06
b77e7c5cc7 fix build? 2024-07-07 02:30:59 +03:00
Taiyou06
a9be79a6a0 faster methods 2024-07-07 02:28:22 +03:00
Taiyou06
f5b994768f inline world height and reduce alloc more remove entity tracker alloc mixin 2024-07-06 23:59:45 +03:00
Taiyou06
0a0ed20dde small refactors here n there 2024-07-06 22:03:31 +03:00
Taiyou06
15703cbcef improve blockstate lookups 2024-07-06 21:34:19 +03:00
DoggySazHi
3d69cdcb52 unfuck the repo pt. 1/??? 2024-07-05 22:47:44 -07:00
Taiyou06
c7b4f3aaeb Update mixins.core.json 2024-07-06 08:27:25 +03:00
Taiyou06
4ee7f0621a i forgor to comment it out 2024-07-06 08:25:17 +03:00
Taiyou06
bc1b851a4e refactor and block_entity_retrieval mixin 2024-07-06 08:24:41 +03:00
Taiyou06
8314c4b05f ServerChunkManager - Alloc reduction 2024-07-06 07:54:01 +03:00
Taiyou06
870c519bc3 chunk_random alloc improvements 2024-07-06 07:48:45 +03:00
Taiyou06
0a35a86110 StateCache to reduce allocation 2024-07-06 06:30:30 +03:00
Taiyou06
d985b507e4 Merge pull request #2 from Gensokyo-Reimagined/doggy-dev/taiyouh-nuclear-bomb-io
Asynchronous NBT dat file records IO
2024-06-25 12:49:07 +02:00
DoggySazHi
71fc6f332b spotless applied 2024-06-24 21:49:59 -07:00
DoggySazHi
a0794eaab9 Implement asynchronous NBT saving 2024-06-24 21:47:56 -07:00
DoggySazHi
e84af35838 Fix Citizens compat for 1.20.6 2024-06-19 22:43:20 -07:00
DoggySazHi
354362e4e9 jithub act on deez 2024-06-16 22:14:42 -07:00
DoggySazHi
61648b6990 Added compat for Paper 1.20.6 to use the Ignite path for the ReobfServer task 2024-06-16 22:11:20 -07:00
DoggySazHi
ef7de4b6c3 Update mixins to support 1.20.6 NMS 2024-06-16 17:52:05 -07:00
DoggySazHi
1f87ad2546 Java 21/Gradle 8.8/Shadow bump 2024-06-16 17:48:56 -07:00
Taiyou06
6183877840 Update README.md 2024-05-08 00:17:36 +03:00
Altiami
f8c45602df Remove unused import statement 2024-05-05 09:43:56 -07:00
Altiami
e2591a635b Merge branch 'dev-multithreaded-tracker' 2024-05-05 09:42:05 -07:00
Altiami
218206b54b Hey, get back in there! 2024-04-04 14:48:34 -07:00
231 changed files with 11329 additions and 894 deletions

View File

@@ -20,7 +20,7 @@ jobs:
uses: actions/setup-java@v2
with:
distribution: 'temurin'
java-version: 17
java-version: 21
- name: Initialize caches
uses: actions/cache@v3
with:
@@ -31,17 +31,17 @@ jobs:
key: ${{ runner.os }}-build-commit-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-build-commit-
- name: Gradle deez
uses: gradle/actions/setup-gradle@v3
- name: Build artifacts
run: ./gradlew clean :spotlessApply build
run: ./gradlew build
- name: Upload artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: nitori-artifacts-${{ steps.ref.outputs.branch }}
path: build/libs/*.jar
- name: Reposilite upload
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
with:
arguments: publish
run: ./gradlew publish
env:
USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_GENSOREPOUSERNAME }}
TOKEN: ${{ secrets.ORG_GRADLE_PROJECT_GENSOREPOPASSWORD }}

View File

@@ -1,34 +1,42 @@
# Nitori
A performance mod that converts patches into mixins using the Ignite Framework for Paper/Spigot.
## Optimizations
This plugin provides the following optimizations:
- [x] Iterate entity trackers faster by using Int2ObjectLinkedOpenHashMap
- [ ] Reduce constants allocations
- [x] Entity Micro Optimizations
- [ ] Lithium mixins
- [x] Fast util
- [x] HashedReferenceList
- [x] CompactSineLUT
- [x] Fast retrieval
- [x] Cached hashcode
- [x] Store gamerules in fastutil hashmap
- [ ] Precompute shape arrays
- [ ] Collections.attributes
- [ ] Collections.entity_by_type
- [ ] Collections.entity_filtering
- [ ] Chunk serialization
- [x] Cache iterate outwards
- [ ] Block moving block shapes
- [ ] Shapes blockstate cache
- [x] Lithium gen
- [ ] Ai sensor secondary POI
- [ ] World tick scheduler
- [x] Smarter statistics-ticking
- [ ] Async Pathfinding
- [ ] Multithreaded Tracker
> [!CAUTION]
> Nitori is more or less depraced to use [Leaf](https://github.com/Winds-Studio/Leaf) in it's replacement for performance orianted features.
**NOTE: This mod may or may not alter the default behaviors of some mob AI.**
## Optimizations:
This plugin provides the following optimizations:
- Faster Entity tracker by utilizing **Multiple Cores** this will allow larger servers to have way more entities
- Async NBT data saving which improves where paper doesn't on world saving
- Many of Lithium's Optimization patches which includes:
- Faster Math
- Faster Entity retrieval
- Reduced Memory allocations
- Improved inlined logics
- Improved collections
- Pre-Computed Shape arrays
- Improved Block Entity tickings
- Lithium Chunk Gen
- Mob Ai Improvements (soon)
- Fast BlockPos
- Faster entity related inmterractions (Hand swing, Sprinting particles etc.)
- ...and much more
- Includes some of the patches from Very Many Players (VMP)'s
- Improved player tracking logic
- Improved TypeFilterableList
- If entity velocity is zero it won't send it as packets
- Improved Player lookups
- Faster VarInts
- Some patches from Potatoptimize
- Way faster math
- Faster rotation logic
- Many Inlined logics
## Optimizations To-Do:
- Async Mob Pathfinding
- Multithreading starlight using ScaleableLux
- Easier config to toggle optimizations on and off
- Improving EntityTickList further
---

View File

@@ -16,15 +16,15 @@ dependencies {
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
kotlin {
target {
compilations.configureEach {
kotlinOptions {
jvmTarget = "11"
jvmTarget = "21"
}
}
}

View File

@@ -1,9 +1,8 @@
plugins {
`java-library`
id("com.github.johnrengelman.shadow")
id("io.github.goooler.shadow")
id("io.papermc.paperweight.userdev")
id("com.diffplug.spotless")
id("maven-publish")
}
@@ -11,7 +10,7 @@ plugins {
val libs = extensions.getByType(org.gradle.accessors.dm.LibrariesForLibs::class)
java {
javaTarget(17)
javaTarget(21)
withSourcesJar()
}
@@ -38,49 +37,36 @@ tasks {
build {
dependsOn(reobfJar)
}
}
spotless {
format("misc") {
target(project.files("*.gradle.kts", "gradle.properties", "settings.gradle.kts", "gradle/libs.versions.toml"))
trimTrailingWhitespace()
indentWithSpaces(4)
endWithNewline()
var jarFile = file("build/libs/%s-%s.jar".format(project.name, project.version))
var jarArtifact = artifacts.add("default", jarFile) {
type = "jar"
builtBy("jar")
}
java {
licenseHeaderFile("LICENSE_header.txt")
}
}
var jarFile = file("build/libs/%s-%s.jar".format(project.name, project.version))
var jarArtifact = artifacts.add("default", jarFile) {
type = "jar"
builtBy("jar")
}
publishing {
publications {
create<MavenPublication>("mavenJava") {
artifact(jarArtifact)
group = "plugins"
}
}
repositories {
maven {
name = "gensorepo"
credentials {
username = project.findProperty("gpr.user") as String? ?: System.getenv("USERNAME")
password = project.findProperty("gpr.key") as String? ?: System.getenv("TOKEN")
publishing {
publications {
create<MavenPublication>("mavenJava") {
artifact(jarArtifact)
group = "plugins"
}
}
repositories {
maven {
name = "gensorepo"
credentials {
username = project.findProperty("gpr.user") as String? ?: System.getenv("USERNAME")
password = project.findProperty("gpr.key") as String? ?: System.getenv("TOKEN")
}
// url to the releases maven repository
url = uri("https://repo.gensokyoreimagined.net/")
}
// url to the releases maven repository
url = uri("https://repo.gensokyoreimagined.net/")
}
}
}
tasks.named("publishMavenJavaPublicationToGensorepoRepository") {
dependsOn("reobfJar")
}
tasks.named("publishMavenJavaPublicationToGensorepoRepository") {
dependsOn("reobfJar")
}
}

View File

@@ -1,5 +1,5 @@
group=net.gensokyoreimagined.nitori
version=1.1-SNAPSHOT
version=1.5-SNAPSHOT
description=Converting patches into mixins, for the Ignite Framework
org.gradle.parallel=true

View File

@@ -6,10 +6,10 @@ jetbrains = "24.1.0"
ignite = "1.0.1"
mixin = "0.8.5"
mixinExtras = "0.3.5"
paperweight = "1.5.11"
shadow = "8.1.1"
paperweight = "1.7.1"
shadow = "8.1.2"
spotless = "6.25.0"
paper = "1.20.4-R0.1-SNAPSHOT"
paper = "1.21-R0.1-SNAPSHOT"
[libraries]
jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains" }
@@ -19,5 +19,5 @@ mixin = { group = "org.spongepowered", name = "mixin", version.ref = "mixin" }
mixinExtras = { group = "io.github.llamalad7", name = "mixinextras-common", version.ref = "mixinExtras" }
build-paperweight = { module = "io.papermc.paperweight.userdev:io.papermc.paperweight.userdev.gradle.plugin", version.ref = "paperweight" }
build-shadow = { module = "com.github.johnrengelman:shadow", version.ref = "shadow" }
build-shadow = { module = "io.github.goooler:shadow", version.ref = "shadow" }
build-spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@@ -14,9 +14,9 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.access;
import com.destroystokyo.paper.util.maplist.EntityList;
public interface IMixinChunkEntitySlicesAccess {
@SuppressWarnings("EmptyMethod")
EntityList getEntities();
}
//import ca.spottedleaf.moonrise.common.list.EntityList;
//
//public interface IMixinChunkEntitySlicesAccess {
// @SuppressWarnings("EmptyMethod")
// EntityList getEntities();
//}

View File

@@ -14,7 +14,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.access;
public interface IMixinIteratorSafeOrderedReferenceSetAccess {
@SuppressWarnings("EmptyMethod")
int getListSize();
}
//public interface IMixinIteratorSafeOrderedReferenceSetAccess {
// @SuppressWarnings("EmptyMethod")
// int getListSize();
//}

View File

@@ -0,0 +1,8 @@
package net.gensokyoreimagined.nitori.access;
public interface ITypeFilterableList {
Object[] getBackingArray();
}

View File

@@ -0,0 +1,40 @@
package net.gensokyoreimagined.nitori.api.inventory;
import net.minecraft.core.NonNullList;
import net.minecraft.world.entity.vehicle.ContainerEntity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
public interface LithiumInventory extends ContainerEntity {
/**
* Getter for the inventory stack list of this inventory.
*
* @return inventory stack list
*/
NonNullList<ItemStack> getInventoryLithium();
/**
* Setter for the inventory stack list of this inventory.
* Used to replace the stack list with Lithium's custom stack list.
*
* @param inventory inventory stack list
*/
void setInventoryLithium(NonNullList<ItemStack> inventory);
/**
* Generates the loot like a hopper access would do in vanilla.
* <p>
* If a modded inventory has custom loot generation code, it will be required to override this
* loot generation method. Otherwise, its loot may be generated too late.
*/
default void generateLootLithium() {
if (this instanceof RandomizableContainerBlockEntity) {
((RandomizableContainerBlockEntity) this).unpackLootTable(null);
}
if (this instanceof ContainerEntity) {
((ContainerEntity) this).unpackChestVehicleLootTable(null);
}
}
}

View File

@@ -0,0 +1,6 @@
package net.gensokyoreimagined.nitori.common.ai;
public interface MemoryModificationCounter {
long lithium$getModCount();
}

View File

@@ -0,0 +1,45 @@
package net.gensokyoreimagined.nitori.common.ai;
import net.minecraft.world.entity.ai.behavior.ShufflingList;
import java.util.Iterator;
public interface WeightedListIterable<U> extends Iterable<U> {
/**
* {@inheritDoc}
*/
Iterator<U> iterator();
/**
* Returns an {@link Iterable} over the elements in the {@param list}. This allows code to circumvent the usage
* of streams, providing a speed-up in other areas of the game.
*/
@SuppressWarnings("unchecked")
static <T> Iterable<? extends T> cast(ShufflingList<T> list) {
return ((WeightedListIterable<T>) list);
}
/**
* A wrapper type for an iterator over the entries of a {@link ShufflingList} which de-references the contained
* values for consumers.
*
* @param <U> The value type stored in each list entry
*/
class ListIterator<U> implements Iterator<U> {
private final Iterator<ShufflingList.WeightedEntry<? extends U>> inner;
public ListIterator(Iterator<ShufflingList.WeightedEntry<? extends U>> inner) {
this.inner = inner;
}
@Override
public boolean hasNext() {
return this.inner.hasNext();
}
@Override
public U next() {
return this.inner.next().getData();
}
}
}

View File

@@ -0,0 +1,8 @@
package net.gensokyoreimagined.nitori.common.ai.pathing;
import net.minecraft.world.level.pathfinder.PathType;
public interface BlockStatePathingCache {
PathType lithium$getPathNodeType();
PathType lithium$getNeighborPathNodeType();
}

View File

@@ -0,0 +1,133 @@
package net.gensokyoreimagined.nitori.common.ai.pathing;
import net.gensokyoreimagined.nitori.common.block.BlockCountingSection;
import net.gensokyoreimagined.nitori.common.block.BlockStateFlags;
import net.gensokyoreimagined.nitori.common.util.Pos;
import net.gensokyoreimagined.nitori.common.world.ChunkView;
import net.gensokyoreimagined.nitori.common.world.WorldHelper;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.pathfinder.PathfindingContext;
import net.minecraft.world.level.pathfinder.WalkNodeEvaluator;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.pathfinder.PathType;
import net.gensokyoreimagined.nitori.mixin.unapplied.ai.pathing.PathContextAccessor;
public abstract class PathNodeCache {
private static boolean isChunkSectionDangerousNeighbor(LevelChunkSection section) {
return section.getStates().maybeHas(state -> getNeighborPathType(state) != PathType.OPEN);
}
public static PathType getPathType(BlockState state) {
return ((BlockStatePathingCache) state).lithium$getPathNodeType();
}
public static PathType getNeighborPathType(BlockBehaviour.BlockStateBase state) {
return ((BlockStatePathingCache) state).lithium$getNeighborPathNodeType();
}
/**
* Returns whether a chunk section is free of dangers. This makes use of a caching layer to greatly
* accelerate neighbor danger checks when path-finding.
*
* @param section The chunk section to test for dangers
* @return True if this neighboring section is free of any dangers, otherwise false if it could
* potentially contain dangers
*/
public static boolean isSectionSafeAsNeighbor(LevelChunkSection section) {
// Empty sections can never contribute a danger
if (section.hasOnlyAir()) {
return true;
}
if (BlockStateFlags.ENABLED) {
return !((BlockCountingSection) section).lithium$mayContainAny(BlockStateFlags.PATH_NOT_OPEN);
}
return !isChunkSectionDangerousNeighbor(section);
}
public static PathType getNodeTypeFromNeighbors(PathfindingContext context, int x, int y, int z, PathType fallback) {
BlockGetter world = context.level();
LevelChunkSection section = null;
// Check that all the block's neighbors are within the same chunk column. If so, we can isolate all our block
// reads to just one chunk and avoid hits against the server chunk manager.
if (world instanceof ChunkView chunkView && WorldHelper.areNeighborsWithinSameChunkSection(x, y, z)) {
// If the y-coordinate is within bounds, we can cache the chunk section. Otherwise, the if statement to check
// if the cached chunk section was initialized will early-exit.
if (!world.isOutsideBuildHeight(y)) {
ChunkAccess chunk = chunkView.lithium$getLoadedChunk(Pos.ChunkCoord.fromBlockCoord(x), Pos.ChunkCoord.fromBlockCoord(z));
// If the chunk is absent, the cached section above will remain null, as there is no chunk section anyway.
// An empty chunk or section will never pose any danger sources, which will be caught later.
if (chunk != null) {
section = chunk.getSections()[Pos.SectionYIndex.fromBlockCoord(world, y)];
}
}
// If we can guarantee that blocks won't be modified while the cache is active, try to see if the chunk
// section is empty or contains any dangerous blocks within the palette. If not, we can assume any checks
// against this chunk section will always fail, allowing us to fast-exit.
if (section == null || PathNodeCache.isSectionSafeAsNeighbor(section)) {
return fallback; //TODO side effects of vanilla's path node caching
}
}
int xStart = x - 1;
int yStart = y - 1;
int zStart = z - 1;
int xEnd = x + 1;
int yEnd = y + 1;
int zEnd = z + 1;
// Vanilla iteration order is XYZ
for (int adjX = xStart; adjX <= xEnd; adjX++) {
for (int adjY = yStart; adjY <= yEnd; adjY++) {
for (int adjZ = zStart; adjZ <= zEnd; adjZ++) {
// Skip the vertical column of the origin block
if (adjX == x && adjZ == z) {
continue;
}
BlockState state;
// If we're not accessing blocks outside a given section, we can greatly accelerate block state
// retrieval by calling upon the cached chunk directly.
if (section != null) {
state = section.getBlockState(adjX & 15, adjY & 15, adjZ & 15);
} else {
BlockPos.MutableBlockPos pos = ((PathContextAccessor) context).getMutablePos().set(adjX, adjY, adjZ);
state = world.getBlockState(pos);
}
if (state.isAir()) {
continue;
}
PathType neighborType = PathNodeCache.getNeighborPathType(state);
if (neighborType == null) { //Here null means that no path node type is cached (uninitialized or dynamic)
//Passing null as previous node type to the method signals to other lithium mixins that we only want the neighbor behavior of this block and not its neighbors
neighborType = WalkNodeEvaluator.checkNeighbourBlocks(context, adjX + 1, adjY + 1, adjZ + 1, null);
//Here null means that the path node type is not changed by the block!
if (neighborType == null) {
neighborType = PathType.OPEN;
}
}
if (neighborType != PathType.OPEN) {
return neighborType;
}
}
}
}
return fallback;
}
}

View File

@@ -0,0 +1,9 @@
package net.gensokyoreimagined.nitori.common.block;
import net.minecraft.world.level.block.state.BlockState;
public interface BlockCountingSection {
boolean lithium$mayContainAny(TrackedBlockStatePredicate trackedBlockStatePredicate);
void lithium$trackBlockStateChange(BlockState newState, BlockState oldState);
}

View File

@@ -0,0 +1,14 @@
package net.gensokyoreimagined.nitori.common.block;
//import net.gensokyoreimagined.nitori.common.entity.block_tracking.SectionedBlockChangeTracker;
//import net.minecraft.core.SectionPos;
//import net.minecraft.world.level.Level;
//
//public interface BlockListeningSection {
//
// void lithium$addToCallback(ListeningBlockStatePredicate blockGroup, SectionedBlockChangeTracker tracker, long sectionPos, Level world);
//
// void lithium$removeFromCallback(ListeningBlockStatePredicate blockGroup, SectionedBlockChangeTracker tracker);
//
// void lithium$invalidateListeningSection(SectionPos sectionPos);
//}

View File

@@ -0,0 +1,28 @@
package net.gensokyoreimagined.nitori.common.block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.material.FluidState;
public class BlockStateCounter implements PalettedContainer.CountConsumer<BlockState> {
public int nonEmptyBlockCount;
public int randomTickableBlockCount;
public int nonEmptyFluidCount;
@Override
public void accept(BlockState arg, int i) {
FluidState lv = arg.getFluidState();
if (!arg.isAir()) {
this.nonEmptyBlockCount += i;
if (arg.isRandomlyTicking()) {
this.randomTickableBlockCount += i;
}
}
if (!lv.isEmpty()) {
this.nonEmptyBlockCount += i;
if (lv.isRandomlyTicking()) {
this.nonEmptyFluidCount += i;
}
}
}
}

View File

@@ -0,0 +1,5 @@
package net.gensokyoreimagined.nitori.common.block;
public interface BlockStateFlagHolder {
int lithium$getAllFlags();
}

View File

@@ -0,0 +1,123 @@
package net.gensokyoreimagined.nitori.common.block;
import it.unimi.dsi.fastutil.objects.Reference2BooleanArrayMap;
import net.gensokyoreimagined.nitori.common.ai.pathing.BlockStatePathingCache;
import net.gensokyoreimagined.nitori.common.ai.pathing.PathNodeCache;
import net.gensokyoreimagined.nitori.common.entity.FluidCachingEntity;
import net.gensokyoreimagined.nitori.common.reflection.ReflectionUtil;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.entity.Entity;
import net.minecraft.tags.FluidTags;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.pathfinder.PathType;
import java.util.ArrayList;
public class BlockStateFlags {
public static final boolean ENABLED = BlockCountingSection.class.isAssignableFrom(LevelChunkSection.class);
public static final int NUM_LISTENING_FLAGS;
public static final ListeningBlockStatePredicate[] LISTENING_FLAGS;
public static final int LISTENING_MASK_OR;
//Listening Flag
public static final ListeningBlockStatePredicate ANY;
public static final int NUM_TRACKED_FLAGS;
public static final TrackedBlockStatePredicate[] TRACKED_FLAGS;
//Counting flags
public static final TrackedBlockStatePredicate OVERSIZED_SHAPE;
public static final TrackedBlockStatePredicate PATH_NOT_OPEN;
public static final TrackedBlockStatePredicate WATER;
public static final TrackedBlockStatePredicate LAVA;
public static final TrackedBlockStatePredicate[] FLAGS;
//Non counting flags
public static final TrackedBlockStatePredicate ENTITY_TOUCHABLE;
static {
Reference2BooleanArrayMap<ListeningBlockStatePredicate> listeningFlags = new Reference2BooleanArrayMap<>();
ANY = new ListeningBlockStatePredicate(listeningFlags.size()) {
@Override
public boolean test(BlockState operand) {
return true;
}
};
//false -> we listen to changes of all blocks that pass the predicate test.
//true -> we only listen to changes of the predicate test result
listeningFlags.put(ANY, false);
NUM_LISTENING_FLAGS = listeningFlags.size();
int listenMaskOR = 0;
int iteration = 0;
for (var entry : listeningFlags.reference2BooleanEntrySet()) {
boolean listenOnlyXOR = entry.getBooleanValue();
listenMaskOR |= listenOnlyXOR ? 0 : 1 << iteration;
}
LISTENING_MASK_OR = listenMaskOR;
LISTENING_FLAGS = listeningFlags.keySet().toArray(new ListeningBlockStatePredicate[NUM_LISTENING_FLAGS]);
ArrayList<TrackedBlockStatePredicate> countingFlags = new ArrayList<>(listeningFlags.keySet());
OVERSIZED_SHAPE = new TrackedBlockStatePredicate(countingFlags.size()) {
@Override
public boolean test(BlockState operand) {
return operand.hasLargeCollisionShape();
}
};
countingFlags.add(OVERSIZED_SHAPE);
if (FluidCachingEntity.class.isAssignableFrom(Entity.class)) {
WATER = new TrackedBlockStatePredicate(countingFlags.size()) {
@Override
public boolean test(BlockState operand) {
return operand.getFluidState().getType().is(FluidTags.WATER);
}
};
countingFlags.add(WATER);
LAVA = new TrackedBlockStatePredicate(countingFlags.size()) {
@Override
public boolean test(BlockState operand) {
return operand.getFluidState().getType().is(FluidTags.LAVA);
}
};
countingFlags.add(LAVA);
} else {
WATER = null;
LAVA = null;
}
if (BlockStatePathingCache.class.isAssignableFrom(BlockBehaviour.BlockStateBase.class)) {
PATH_NOT_OPEN = new TrackedBlockStatePredicate(countingFlags.size()) {
@Override
public boolean test(BlockState operand) {
return PathNodeCache.getNeighborPathType(operand) != PathType.OPEN;
}
};
countingFlags.add(PATH_NOT_OPEN);
} else {
PATH_NOT_OPEN = null;
}
NUM_TRACKED_FLAGS = countingFlags.size();
TRACKED_FLAGS = countingFlags.toArray(new TrackedBlockStatePredicate[NUM_TRACKED_FLAGS]);
ArrayList<TrackedBlockStatePredicate> flags = new ArrayList<>(countingFlags);
ENTITY_TOUCHABLE = new TrackedBlockStatePredicate(countingFlags.size()) {
@Override
public boolean test(BlockState operand) {
return ReflectionUtil.isBlockStateEntityTouchable(operand);
}
};
flags.add(ENTITY_TOUCHABLE);
FLAGS = flags.toArray(new TrackedBlockStatePredicate[0]);
}
}

View File

@@ -0,0 +1,10 @@
package net.gensokyoreimagined.nitori.common.block;
public abstract class ListeningBlockStatePredicate extends TrackedBlockStatePredicate {
public static int LISTENING_MASK;
protected ListeningBlockStatePredicate(int index) {
super(index);
LISTENING_MASK |= (1 << this.getIndex());
}
}

View File

@@ -0,0 +1,30 @@
package net.gensokyoreimagined.nitori.common.block;
import net.minecraft.world.level.block.state.BlockState;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
public abstract class TrackedBlockStatePredicate implements Predicate<BlockState> {
public static final AtomicBoolean FULLY_INITIALIZED;
static {
FULLY_INITIALIZED = new AtomicBoolean(false);
if (!BlockStateFlags.ENABLED) { //classload the BlockStateFlags class which initializes the content of ALL_FLAGS
System.out.println("Lithium Cached BlockState Flags are disabled!");
}
}
private final int index;
public TrackedBlockStatePredicate(int index) {
if (FULLY_INITIALIZED.get()) {
throw new IllegalStateException("Lithium Cached BlockState Flags: Cannot register more flags after assuming to be fully initialized.");
}
this.index = index;
}
public int getIndex() {
return this.index;
}
}

View File

@@ -0,0 +1,37 @@
package net.gensokyoreimagined.nitori.common.block.entity;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.TickingBlockEntity;
import org.jetbrains.annotations.NotNull;
public record SleepUntilTimeBlockEntityTickInvoker(BlockEntity sleepingBlockEntity, long sleepUntilTickExclusive,
TickingBlockEntity delegate) implements TickingBlockEntity {
@Override
public void tick() {
//noinspection ConstantConditions
long tickTime = this.sleepingBlockEntity.getLevel().getGameTime();
if (tickTime >= this.sleepUntilTickExclusive) {
((SleepingBlockEntity) this.sleepingBlockEntity).setTicker(this.delegate);
this.delegate.tick();
}
}
@Override
public boolean isRemoved() {
return this.sleepingBlockEntity.isRemoved();
}
@Override
public @NotNull BlockPos getPos() {
return this.sleepingBlockEntity.getBlockPos();
}
@Override
public String getType() {
//noinspection ConstantConditions
return BlockEntityType.getKey(this.sleepingBlockEntity.getType()).toString();
}
}

View File

@@ -0,0 +1,80 @@
package net.gensokyoreimagined.nitori.common.block.entity;
import net.gensokyoreimagined.nitori.mixin.world.block_entity_ticking.sleeping.WrappedBlockEntityTickInvokerAccessor;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.TickingBlockEntity;
public interface SleepingBlockEntity {
TickingBlockEntity SLEEPING_BLOCK_ENTITY_TICKER = new TickingBlockEntity() {
public void tick() {
}
public boolean isRemoved() {
return false;
}
public BlockPos getPos() {
return null;
}
public String getType() {
return "<lithium_sleeping>";
}
};
WrappedBlockEntityTickInvokerAccessor lithium$getTickWrapper();
void lithium$setTickWrapper(WrappedBlockEntityTickInvokerAccessor tickWrapper);
TickingBlockEntity lithium$getSleepingTicker();
void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker);
default boolean nitori$startSleeping() {
if (this.isSleeping()) {
return false;
}
WrappedBlockEntityTickInvokerAccessor tickWrapper = this.lithium$getTickWrapper();
if (tickWrapper == null) {
return false;
}
this.lithium$setSleepingTicker(tickWrapper.getTicker());
tickWrapper.callRebind(SleepingBlockEntity.SLEEPING_BLOCK_ENTITY_TICKER);
return true;
}
default void sleepOnlyCurrentTick() {
TickingBlockEntity sleepingTicker = this.lithium$getSleepingTicker();
WrappedBlockEntityTickInvokerAccessor tickWrapper = this.lithium$getTickWrapper();
if (sleepingTicker == null) {
sleepingTicker = tickWrapper.getTicker();
}
Level world = ((BlockEntity) this).getLevel();
tickWrapper.callRebind(new SleepUntilTimeBlockEntityTickInvoker((BlockEntity) this, world.getGameTime() + 1, sleepingTicker));
this.lithium$setSleepingTicker(null);
}
default void wakeUpNow() {
TickingBlockEntity sleepingTicker = this.lithium$getSleepingTicker();
if (sleepingTicker == null) {
return;
}
this.setTicker(sleepingTicker);
this.lithium$setSleepingTicker(null);
}
default void setTicker(TickingBlockEntity delegate) {
WrappedBlockEntityTickInvokerAccessor tickWrapper = this.lithium$getTickWrapper();
if (tickWrapper == null) {
return;
}
tickWrapper.callRebind(delegate);
}
default boolean isSleeping() {
return this.lithium$getSleepingTicker() != null;
}
}

View File

@@ -0,0 +1,9 @@
package net.gensokyoreimagined.nitori.common.chunkwatching;
public interface PlayerClientVDTracking {
boolean isClientViewDistanceChanged();
int getClientViewDistance();
}

View File

@@ -0,0 +1,111 @@
package net.gensokyoreimagined.nitori.common.entity;
import it.unimi.dsi.fastutil.objects.Reference2ByteOpenHashMap;
import net.gensokyoreimagined.nitori.common.reflection.ReflectionUtil;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
import net.minecraft.world.entity.monster.Shulker;
import net.minecraft.world.entity.projectile.windcharge.WindCharge;
import net.minecraft.world.entity.vehicle.Minecart;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.logging.Logger;
/**
* Class for grouping Entity classes by some property for use in TypeFilterableList
* It is intended that an EntityClassGroup acts as if it was immutable, however we cannot predict which subclasses of
* Entity might appear. Therefore we evaluate whether a class belongs to the class group when it is first seen.
* Once a class was evaluated the result of it is cached and cannot be changed.
*
* @author 2No2Name
*/
public class EntityClassGroup {
public static final EntityClassGroup CUSTOM_COLLIDE_LIKE_MINECART_BOAT_WINDCHARGE; //aka entities that will attempt to collide with all other entities when moving
static {
// String remapped_collidesWith = FabricLoader.getInstance().getMappingResolver().mapMethodName("intermediary", "net.minecraft.class_1297", "method_30949", "(Lnet/minecraft/class_1297;)Z");
String remapped_collidesWith = "canCollideWith";
CUSTOM_COLLIDE_LIKE_MINECART_BOAT_WINDCHARGE = new EntityClassGroup(
(Class<?> entityClass) -> ReflectionUtil.hasMethodOverride(entityClass, Entity.class, true, remapped_collidesWith, Entity.class));
//sanity check: in case intermediary mappings changed, we fail
if ((!CUSTOM_COLLIDE_LIKE_MINECART_BOAT_WINDCHARGE.contains(Minecart.class))) {
throw new AssertionError();
}
if ((!CUSTOM_COLLIDE_LIKE_MINECART_BOAT_WINDCHARGE.contains(WindCharge.class)) || (!CUSTOM_COLLIDE_LIKE_MINECART_BOAT_WINDCHARGE.contains(WindCharge.class))) {
throw new AssertionError();
}
if ((CUSTOM_COLLIDE_LIKE_MINECART_BOAT_WINDCHARGE.contains(Shulker.class))) {
//should not throw an Error here, because another mod *could* add the method to ShulkerEntity. Warning when this sanity check fails.
Logger.getLogger("Lithium EntityClassGroup").warning("Either Lithium EntityClassGroup is broken or something else gave Shulkers the minecart-like collision behavior.");
}
CUSTOM_COLLIDE_LIKE_MINECART_BOAT_WINDCHARGE.clear();
}
private final Predicate<Class<?>> classFitEvaluator;
private volatile Reference2ByteOpenHashMap<Class<?>> class2GroupContains;
public EntityClassGroup(Predicate<Class<?>> classFitEvaluator) {
this.class2GroupContains = new Reference2ByteOpenHashMap<>();
Objects.requireNonNull(classFitEvaluator);
this.classFitEvaluator = classFitEvaluator;
}
public void clear() {
this.class2GroupContains = new Reference2ByteOpenHashMap<>();
}
public boolean contains(Class<?> entityClass) {
byte contains = this.class2GroupContains.getOrDefault(entityClass, (byte) 2);
if (contains != 2) {
return contains == 1;
} else {
return this.testAndAddClass(entityClass);
}
}
boolean testAndAddClass(Class<?> entityClass) {
byte contains;
//synchronizing here to avoid multiple threads replacing the map at the same time, and therefore possibly undoing progress
//it could also be fixed by using an AtomicReference's CAS, but we are writing very rarely (less than 150 times for the total game runtime in vanilla)
synchronized (this) {
//test the same condition again after synchronizing, as the collection might have been updated while this thread blocked
contains = this.class2GroupContains.getOrDefault(entityClass, (byte) 2);
if (contains != 2) {
return contains == 1;
}
//construct new map instead of updating the old map to avoid thread safety problems
//the map is not modified after publication
Reference2ByteOpenHashMap<Class<?>> newMap = this.class2GroupContains.clone();
contains = this.classFitEvaluator.test(entityClass) ? (byte) 1 : (byte) 0;
newMap.put(entityClass, contains);
//publish the new map in a volatile field, so that all threads reading after this write can also see all changes to the map done before the write
this.class2GroupContains = newMap;
}
return contains == 1;
}
public static class NoDragonClassGroup extends EntityClassGroup {
public static final NoDragonClassGroup BOAT_SHULKER_LIKE_COLLISION; //aka entities that other entities will do block-like collisions with when moving
static {
// String remapped_isCollidable = FabricLoader.getInstance().getMappingResolver().mapMethodName("intermediary", "net.minecraft.class_1297", "method_30948", "()Z");
String remapped_isCollidable = "canBeCollidedWith";
BOAT_SHULKER_LIKE_COLLISION = new NoDragonClassGroup(
(Class<?> entityClass) -> ReflectionUtil.hasMethodOverride(entityClass, Entity.class, true, remapped_isCollidable));
if ((!BOAT_SHULKER_LIKE_COLLISION.contains(Shulker.class))) {
throw new AssertionError();
}
BOAT_SHULKER_LIKE_COLLISION.clear();
}
public NoDragonClassGroup(Predicate<Class<?>> classFitEvaluator) {
super(classFitEvaluator);
if (classFitEvaluator.test(EnderDragon.class)) {
throw new IllegalArgumentException("EntityClassGroup.NoDragonClassGroup cannot be initialized: Must exclude EnderDragonEntity!");
}
}
}
}

View File

@@ -0,0 +1,4 @@
package net.gensokyoreimagined.nitori.common.entity;
public interface FluidCachingEntity {
}

View File

@@ -0,0 +1,14 @@
package net.gensokyoreimagined.nitori.common.entity;
import net.minecraft.world.entity.ai.navigation.PathNavigation;
public interface NavigatingEntity {
boolean lithium$isRegisteredToWorld();
void lithium$setRegisteredToWorld(PathNavigation navigation);
PathNavigation lithium$getRegisteredNavigation();
void lithium$updateNavigationRegistration();
}

View File

@@ -0,0 +1,104 @@
package net.gensokyoreimagined.nitori.common.entity.block_tracking;
//import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
//import net.gensokyoreimagined.nitori.common.block.BlockListeningSection;
//import net.gensokyoreimagined.nitori.common.block.BlockStateFlags;
//import net.gensokyoreimagined.nitori.common.block.ListeningBlockStatePredicate;
//import net.gensokyoreimagined.nitori.common.util.Pos;
//import net.gensokyoreimagined.nitori.common.world.LithiumData;
//import net.gensokyoreimagined.nitori.common.world.chunk.ChunkStatusTracker;
//import net.minecraft.core.SectionPos;
//import net.minecraft.world.level.Level;
//import net.minecraft.world.level.chunk.LevelChunkSection;
//
//import java.util.ArrayList;
//
//public final class ChunkSectionChangeCallback {
// private final ArrayList<SectionedBlockChangeTracker>[] trackers;
// private short listeningMask;
//
// static {
// if (BlockListeningSection.class.isAssignableFrom(LevelChunkSection.class)) {
// ChunkStatusTracker.registerUnloadCallback((serverWorld, chunkPos) -> {
// Long2ReferenceOpenHashMap<ChunkSectionChangeCallback> changeCallbacks = ((LithiumData) serverWorld).lithium$getData().chunkSectionChangeCallbacks();
// int x = chunkPos.x;
// int z = chunkPos.z;
// for (int y = Pos.SectionYCoord.getMinYSection(serverWorld); y <= Pos.SectionYCoord.getMaxYSectionInclusive(serverWorld); y++) {
// SectionPos chunkSectionPos = SectionPos.of(x, y, z);
// ChunkSectionChangeCallback chunkSectionChangeCallback = changeCallbacks.remove(chunkSectionPos.asLong());
// if (chunkSectionChangeCallback != null) {
// chunkSectionChangeCallback.onChunkSectionInvalidated(chunkSectionPos);
// }
// }
// });
// }
// }
//
// public ChunkSectionChangeCallback() {
// //noinspection unchecked
// this.trackers = new ArrayList[BlockStateFlags.NUM_LISTENING_FLAGS];
// this.listeningMask = 0;
// }
//
// public static ChunkSectionChangeCallback create(long sectionPos, Level world) {
// ChunkSectionChangeCallback chunkSectionChangeCallback = new ChunkSectionChangeCallback();
// Long2ReferenceOpenHashMap<ChunkSectionChangeCallback> changeCallbacks = ((LithiumData) world).lithium$getData().chunkSectionChangeCallbacks();
// ChunkSectionChangeCallback previous = changeCallbacks.put(sectionPos, chunkSectionChangeCallback);
// if (previous != null) {
// previous.onChunkSectionInvalidated(SectionPos.of(sectionPos));
// }
// return chunkSectionChangeCallback;
// }
//
// public short onBlockChange(int blockGroupIndex, BlockListeningSection section) {
// ArrayList<SectionedBlockChangeTracker> sectionedBlockChangeTrackers = this.trackers[blockGroupIndex];
// this.trackers[blockGroupIndex] = null;
// if (sectionedBlockChangeTrackers != null) {
// //noinspection ForLoopReplaceableByForEach
// for (int i = 0; i < sectionedBlockChangeTrackers.size(); i++) {
// sectionedBlockChangeTrackers.get(i).setChanged(section);
// }
// }
// this.listeningMask &= (short) ~(1 << blockGroupIndex);
//
// return this.listeningMask;
// }
//
// public short addTracker(SectionedBlockChangeTracker tracker, ListeningBlockStatePredicate blockGroup) {
// int blockGroupIndex = blockGroup.getIndex();
// ArrayList<SectionedBlockChangeTracker> sectionedBlockChangeTrackers = this.trackers[blockGroupIndex];
// if (sectionedBlockChangeTrackers == null) {
// this.trackers[blockGroupIndex] = (sectionedBlockChangeTrackers = new ArrayList<>());
// }
// sectionedBlockChangeTrackers.add(tracker);
//
// this.listeningMask |= (short) (1 << blockGroupIndex);
// return this.listeningMask;
// }
//
// public short removeTracker(SectionedBlockChangeTracker tracker, ListeningBlockStatePredicate blockGroup) {
// int blockGroupIndex = blockGroup.getIndex();
// ArrayList<SectionedBlockChangeTracker> sectionedBlockChangeTrackers = this.trackers[blockGroupIndex];
// if (sectionedBlockChangeTrackers != null) {
// sectionedBlockChangeTrackers.remove(tracker);
// if (sectionedBlockChangeTrackers.isEmpty()) {
// this.listeningMask &= (short) ~(1 << blockGroup.getIndex());
// }
// }
// return this.listeningMask;
// }
//
// public void onChunkSectionInvalidated(SectionPos sectionPos) {
// for (int flagIndex = 0; flagIndex < this.trackers.length; flagIndex++) {
// ArrayList<SectionedBlockChangeTracker> sectionedBlockChangeTrackers = this.trackers[flagIndex];
// this.trackers[flagIndex] = null;
// if (sectionedBlockChangeTrackers != null) {
// //noinspection ForLoopReplaceableByForEach
// for (int i = 0; i < sectionedBlockChangeTrackers.size(); i++) {
// sectionedBlockChangeTrackers.get(i).onChunkSectionInvalidated(sectionPos);
// }
// }
// }
// this.listeningMask = 0;
// }
//}

View File

@@ -0,0 +1,211 @@
package net.gensokyoreimagined.nitori.common.entity.block_tracking;
//import net.gensokyoreimagined.nitori.common.block.BlockListeningSection;
//import net.gensokyoreimagined.nitori.common.block.ListeningBlockStatePredicate;
//import net.gensokyoreimagined.nitori.common.util.Pos;
//import net.gensokyoreimagined.nitori.common.util.deduplication.LithiumInterner;
//import net.gensokyoreimagined.nitori.common.util.tuples.WorldSectionBox;
//import net.gensokyoreimagined.nitori.common.world.LithiumData;
//import net.minecraft.world.phys.AABB;
//import net.minecraft.core.SectionPos;
//import net.minecraft.world.level.Level;
//import net.minecraft.world.level.chunk.ChunkAccess;
//import net.minecraft.world.level.chunk.LevelChunkSection;
//import net.minecraft.world.level.chunk.status.ChunkStatus;
//
//import java.util.ArrayList;
//import java.util.Objects;
//
//public class SectionedBlockChangeTracker {
// public final WorldSectionBox trackedWorldSections;
// public final ListeningBlockStatePredicate blockGroup;
//
// private long maxChangeTime;
//
// private int timesRegistered;
// //Some sections may not exist / be unloaded. We have to be aware of those. //TODO Invalidation when sections / chunks unload (but the entity does not (?), not sure whether this is possible)
// boolean isListeningToAll = false;
// private ArrayList<SectionPos> sectionsNotListeningTo = null;
// private ArrayList<BlockListeningSection> sectionsUnsubscribed = null;
//
// public SectionedBlockChangeTracker(WorldSectionBox trackedWorldSections, ListeningBlockStatePredicate blockGroup) {
// this.trackedWorldSections = trackedWorldSections;
// this.blockGroup = blockGroup;
//
// this.maxChangeTime = 0;
// }
//
// public boolean matchesMovedBox(AABB box) {
// return this.trackedWorldSections.matchesRelevantBlocksBox(box);
// }
//
// public static SectionedBlockChangeTracker registerAt(Level world, AABB entityBoundingBox, ListeningBlockStatePredicate blockGroup) {
// WorldSectionBox worldSectionBox = WorldSectionBox.relevantExpandedBlocksBox(world, entityBoundingBox);
// SectionedBlockChangeTracker tracker = new SectionedBlockChangeTracker(worldSectionBox, blockGroup);
//
// LithiumInterner<SectionedBlockChangeTracker> blockChangeTrackers = ((LithiumData) world).lithium$getData().blockChangeTrackers();
// tracker = blockChangeTrackers.getCanonical(tracker);
//
// tracker.register();
// return tracker;
// }
//
// long getWorldTime() {
// return this.trackedWorldSections.world().getGameTime();
// }
//
// public void register() {
// if (this.timesRegistered == 0) {
// WorldSectionBox trackedSections = this.trackedWorldSections;
// for (int x = trackedSections.chunkX1(); x < trackedSections.chunkX2(); x++) {
// for (int z = trackedSections.chunkZ1(); z < trackedSections.chunkZ2(); z++) {
// Level world = trackedSections.world();
// ChunkAccess chunk = world.getChunk(x, z, ChunkStatus.FULL, false);
// LevelChunkSection[] sectionArray = chunk == null ? null : chunk.getSections();
// for (int y = trackedSections.chunkY1(); y < trackedSections.chunkY2(); y++) {
// if (Pos.SectionYCoord.getMinYSection(world) > y || Pos.SectionYCoord.getMaxYSectionExclusive(world) <= y) {
// continue;
// }
// SectionPos sectionPos = SectionPos.of(x, y, z);
// if (sectionArray == null) {
// if (this.sectionsNotListeningTo == null) {
// this.sectionsNotListeningTo = new ArrayList<>();
// }
// this.sectionsNotListeningTo.add(sectionPos);
// continue;
// }
// LevelChunkSection section = sectionArray[Pos.SectionYIndex.fromSectionCoord(world, y)];
//
// BlockListeningSection blockListeningSection = (BlockListeningSection) section;
// blockListeningSection.lithium$addToCallback(this.blockGroup, this, SectionPos.asLong(x, y, z), world);
// }
// }
// }
// this.isListeningToAll = (this.sectionsNotListeningTo == null || this.sectionsNotListeningTo.isEmpty())
// && (this.sectionsUnsubscribed == null || this.sectionsUnsubscribed.isEmpty());
// this.setChanged(this.getWorldTime());
// }
// this.timesRegistered++;
// }
//
// public void unregister() {
// if (--this.timesRegistered > 0) {
// return;
// }
// WorldSectionBox trackedSections = this.trackedWorldSections;
// Level world = trackedSections.world();
// for (int x = trackedSections.chunkX1(); x < trackedSections.chunkX2(); x++) {
// for (int z = trackedSections.chunkZ1(); z < trackedSections.chunkZ2(); z++) {
// ChunkAccess chunk = world.getChunk(x, z, ChunkStatus.FULL, false);
// LevelChunkSection[] sectionArray = chunk == null ? null : chunk.getSections();
// for (int y = trackedSections.chunkY1(); y < trackedSections.chunkY2(); y++) {
//
// if (sectionArray == null) {
// continue;
// }
// if (Pos.SectionYCoord.getMinYSection(world) > y || Pos.SectionYCoord.getMaxYSectionExclusive(world) <= y) {
// continue;
// }
// LevelChunkSection section = sectionArray[Pos.SectionYIndex.fromSectionCoord(world, y)];
//
// BlockListeningSection blockListeningSection = (BlockListeningSection) section;
// blockListeningSection.lithium$removeFromCallback(this.blockGroup, this);
// }
// }
// }
// this.sectionsNotListeningTo = null;
// LithiumInterner<SectionedBlockChangeTracker> blockChangeTrackers = ((LithiumData) world).lithium$getData().blockChangeTrackers();
// blockChangeTrackers.deleteCanonical(this);
// }
//
// public void listenToAllSections() {
// boolean changed = false;
// ArrayList<SectionPos> notListeningTo = this.sectionsNotListeningTo;
// if (notListeningTo != null) {
// for (int i = notListeningTo.size() - 1; i >= 0; i--) {
// changed = true;
// SectionPos chunkSectionPos = notListeningTo.get(i);
// Level world = this.trackedWorldSections.world();
// ChunkAccess chunk = world.getChunk(chunkSectionPos.getX(), chunkSectionPos.getZ(), ChunkStatus.FULL, false);
// if (chunk != null) {
// notListeningTo.remove(i);
// } else {
// //Chunk not loaded, cannot listen to all sections.
// return;
// }
// LevelChunkSection section = chunk.getSections()[Pos.SectionYIndex.fromSectionCoord(world, chunkSectionPos.getY())];
// BlockListeningSection blockListeningSection = (BlockListeningSection) section;
// blockListeningSection.lithium$addToCallback(this.blockGroup, this, chunkSectionPos.asLong(), world);
// }
// }
// if (this.sectionsUnsubscribed != null) {
// ArrayList<BlockListeningSection> unsubscribed = this.sectionsUnsubscribed;
// for (int i = unsubscribed.size() - 1; i >= 0; i--) {
// changed = true;
// BlockListeningSection blockListeningSection = unsubscribed.remove(i);
// blockListeningSection.lithium$addToCallback(this.blockGroup, this, Long.MIN_VALUE, null);
// }
// }
// this.isListeningToAll = true;
// if (changed) {
// this.setChanged(this.getWorldTime());
// }
// }
//
// public void setChanged(BlockListeningSection section) {
// if (this.sectionsUnsubscribed == null) {
// this.sectionsUnsubscribed = new ArrayList<>();
// }
// this.sectionsUnsubscribed.add(section);
// this.setChanged(this.getWorldTime());
// this.isListeningToAll = false;
// }
//
// public void setChanged(long atTime) {
// if (atTime > this.maxChangeTime) {
// this.maxChangeTime = atTime;
// }
// }
//
// /**
// * Method to quickly check whether any relevant blocks changed inside the relevant chunk sections after
// * the last test.
// *
// * @param lastCheckedTime time of the last interaction attempt
// * @return whether any relevant entity moved in the tracked area
// */
// public boolean isUnchangedSince(long lastCheckedTime) {
// if (lastCheckedTime <= this.maxChangeTime) {
// return false;
// }
// if (!this.isListeningToAll) {
// this.listenToAllSections();
// return this.isListeningToAll && lastCheckedTime > this.maxChangeTime;
// }
// return true;
// }
//
// //Do not modify, used for deduplication of instances
// @Override
// public boolean equals(Object obj) {
// if (obj == this) return true;
// if (obj == null || obj.getClass() != this.getClass()) return false;
// var that = (SectionedBlockChangeTracker) obj;
// return Objects.equals(this.trackedWorldSections, that.trackedWorldSections) &&
// Objects.equals(this.blockGroup, that.blockGroup);
// }
// //Do not modify, used for deduplication of instances
// @Override
// public int hashCode() {
// return this.getClass().hashCode() ^ this.trackedWorldSections.hashCode() ^ this.blockGroup.hashCode();
// }
//
// public void onChunkSectionInvalidated(SectionPos sectionPos) {
// if (this.sectionsNotListeningTo == null) {
// this.sectionsNotListeningTo = new ArrayList<>();
// }
// this.sectionsNotListeningTo.add(sectionPos);
// this.setChanged(this.getWorldTime());
// this.isListeningToAll = false;
// }
//}

View File

@@ -0,0 +1,349 @@
package net.gensokyoreimagined.nitori.common.entity.movement;
//
//import com.google.common.collect.AbstractIterator;
//import net.gensokyoreimagined.nitori.common.block.BlockCountingSection;
//import net.gensokyoreimagined.nitori.common.block.BlockStateFlags;
//import net.gensokyoreimagined.nitori.common.shapes.VoxelShapeCaster;
//import net.gensokyoreimagined.nitori.common.util.Pos;
//import net.minecraft.world.level.block.state.BlockState;
//import net.minecraft.world.level.block.Blocks;
//import net.minecraft.world.phys.shapes.CollisionContext;
//import net.minecraft.world.entity.Entity;
//import net.minecraft.world.phys.shapes.BooleanOp;
//import net.minecraft.core.BlockPos;
//import net.minecraft.world.phys.AABB;
//import net.minecraft.util.Mth;
//import net.minecraft.world.phys.shapes.VoxelShape;
//import net.minecraft.world.phys.shapes.Shapes;
//import net.minecraft.world.level.Level;
//import net.minecraft.world.level.chunk.ChunkAccess;
//import net.minecraft.world.level.chunk.LevelChunkSection;
//import net.minecraft.world.level.chunk.status.ChunkStatus;
//import org.jetbrains.annotations.Nullable;
//
//import java.util.ArrayList;
//import java.util.Iterator;
//import java.util.List;
//import java.util.NoSuchElementException;
//
//import static net.gensokyoreimagined.nitori.common.entity.LithiumEntityCollisions.EPSILON;
//
///**
// * ChunkAwareBlockCollisionSweeper iterates over blocks in one chunk section at a time. Together with the chunk
// * section keeping track of the amount of oversized blocks inside the number of iterations can often be reduced.
// */
//public class ChunkAwareBlockCollisionSweeper extends AbstractIterator<VoxelShape> {
//
// private final BlockPos.Mutable pos = new BlockPos.Mutable();
//
// /**
// * The collision box being swept through the world.
// */
// private final AABB box;
//
// /**
// * The VoxelShape of the collision box being swept through the world.
// */
// private final VoxelShape shape;
//
// private final Level world;
//
// private final CollisionContext context;
//
// //limits of the area without extension for oversized blocks
// private final int minX, minY, minZ, maxX, maxY, maxZ;
//
// //variables prefixed with c refer to the iteration of the currently cached chunk section
// private int chunkX, chunkYIndex, chunkZ;
// private int cStartX, cStartZ;
// private int cEndX, cEndZ;
// private int cX, cY, cZ;
//
// private int maxHitX;
// private int maxHitY;
// private int maxHitZ;
// private VoxelShape maxShape;
// private final boolean hideLastCollision;
//
//
// private int cTotalSize;
// private int cIterated;
//
// private boolean sectionOversizedBlocks;
// private ChunkAccess cachedChunk;
// private LevelChunkSection cachedChunkSection;
//
// public ChunkAwareBlockCollisionSweeper(Level world, @Nullable Entity entity, AABB box) {
// this(world, entity, box, false);
// }
// public ChunkAwareBlockCollisionSweeper(Level world, @Nullable Entity entity, AABB box, boolean hideLastCollision) {
// this.box = box;
// this.shape = Shapes.cuboid(box);
// this.context = entity == null ? CollisionContext.absent() : CollisionContext.of(entity);
// this.world = world;
//
// this.minX = Mth.floor(box.minX - EPSILON);
// this.maxX = Mth.floor(box.maxX + EPSILON);
// this.minY = Mth.clamp(Mth.floor(box.minY - EPSILON), Pos.BlockCoord.getMinY(this.world), Pos.BlockCoord.getMaxYInclusive(this.world));
// this.maxY = Mth.clamp(Mth.floor(box.maxY + EPSILON), Pos.BlockCoord.getMinY(this.world), Pos.BlockCoord.getMaxYInclusive(this.world));
// this.minZ = Mth.floor(box.minZ - EPSILON);
// this.maxZ = Mth.floor(box.maxZ + EPSILON);
//
// this.chunkX = Pos.ChunkCoord.fromBlockCoord(expandMin(this.minX));
// this.chunkZ = Pos.ChunkCoord.fromBlockCoord(expandMin(this.minZ));
//
// this.cIterated = 0;
// this.cTotalSize = 0;
//
// this.maxHitX = Integer.MIN_VALUE;
// this.maxHitY = Integer.MIN_VALUE;
// this.maxHitZ = Integer.MIN_VALUE;
// this.maxShape = null;
// this.hideLastCollision = hideLastCollision;
//
// //decrement as first nextSection call will increment it again
// this.chunkX--;
// }
//
//
// public VoxelShape getLastCollision() {
// return this.maxShape;
// }
//
// public Iterator<VoxelShape> getLastCollisionIterator() {
// return new Iterator<>() {
// @Override
// public boolean hasNext() {
// return hideLastCollision && maxShape != null;
// }
//
// @Override
// public VoxelShape next() {
// if (this.hasNext()) {
// VoxelShape previousMaxShape = maxShape;
// maxShape = null;
// return previousMaxShape;
// }
// throw new NoSuchElementException();
// }
// };
// }
//
// private boolean nextSection() {
// do {
// do {
// //find the coordinates of the next section inside the area expanded by 1 block on all sides
// //note: this.minX, maxX etc are not expanded, so there are lots of +1 and -1 around.
// if (
// this.cachedChunk != null &&
// this.chunkYIndex < Pos.SectionYIndex.getMaxYSectionIndexInclusive(this.world) &&
// this.chunkYIndex < Pos.SectionYIndex.fromBlockCoord(this.world,expandMax(this.maxY))
// ) {
// this.chunkYIndex++;
// this.cachedChunkSection = this.cachedChunk.getSectionArray()[this.chunkYIndex];
// } else {
// this.chunkYIndex = Mth.clamp(
// Pos.SectionYIndex.fromBlockCoord(this.world, expandMin(this.minY)),
// Pos.SectionYIndex.getMinYSectionIndex(this.world),
// Pos.SectionYIndex.getMaxYSectionIndexInclusive(this.world)
// );
//
// if (this.chunkX < Pos.ChunkCoord.fromBlockCoord(expandMax(this.maxX))) {
// //first initialization takes this branch
// this.chunkX++;
// } else {
// this.chunkX = Pos.ChunkCoord.fromBlockCoord(expandMin(this.minX));
//
// if (this.chunkZ < Pos.ChunkCoord.fromBlockCoord(expandMax(this.maxZ))) {
// this.chunkZ++;
// } else {
// return false; //no more sections to iterate
// }
// }
// this.cachedChunk = this.world.getChunk(this.chunkX, this.chunkZ, ChunkStatus.FULL, false);
// if (this.cachedChunk != null) {
// this.cachedChunkSection = this.cachedChunk.getSectionArray()[this.chunkYIndex];
// }
// }
// //skip empty chunks and empty chunk sections
// } while (this.cachedChunk == null || this.cachedChunkSection == null || this.cachedChunkSection.isEmpty());
//
// this.sectionOversizedBlocks = hasChunkSectionOversizedBlocks(this.cachedChunk, this.chunkYIndex);
//
// int sizeExtension = this.sectionOversizedBlocks ? 1 : 0;
//
// this.cEndX = Math.min(this.maxX + sizeExtension, Pos.BlockCoord.getMaxInSectionCoord(this.chunkX));
// int cEndY = Math.min(this.maxY + sizeExtension, Pos.BlockCoord.getMaxYInSectionIndex(this.world, this.chunkYIndex));
// this.cEndZ = Math.min(this.maxZ + sizeExtension, Pos.BlockCoord.getMaxInSectionCoord(this.chunkZ));
//
// this.cStartX = Math.max(this.minX - sizeExtension, Pos.BlockCoord.getMinInSectionCoord(this.chunkX));
// int cStartY = Math.max(this.minY - sizeExtension, Pos.BlockCoord.getMinYInSectionIndex(this.world, this.chunkYIndex));
// this.cStartZ = Math.max(this.minZ - sizeExtension, Pos.BlockCoord.getMinInSectionCoord(this.chunkZ));
// this.cX = this.cStartX;
// this.cY = cStartY;
// this.cZ = this.cStartZ;
//
// this.cTotalSize = (this.cEndX - this.cStartX + 1) * (cEndY - cStartY + 1) * (this.cEndZ - this.cStartZ + 1);
// //skip completely empty section iterations
// } while (this.cTotalSize == 0);
// this.cIterated = 0;
//
// return true;
// }
//
// /**
// * Advances the sweep forward until finding a block with a box-colliding VoxelShape
// *
// * @return null if no VoxelShape is left in the area, otherwise the next VoxelShape
// */
// @Override
// public VoxelShape computeNext() {
// while (true) {
// if (this.cIterated >= this.cTotalSize) {
// if (!this.nextSection()) {
// break;
// }
// }
//
// this.cIterated++;
//
//
// final int x = this.cX;
// final int y = this.cY;
// final int z = this.cZ;
//
// //The iteration order within a chunk section is chosen so that it causes a mostly linear array access in the storage.
// //In net.minecraft.world.chunk.PalettedContainer.toIndex x gets the 4 least significant bits, z the 4 above, and y the 4 even higher ones.
// //Linearly accessing arrays is faster than other access patterns.
// if (this.cX < this.cEndX) {
// this.cX++;
// } else if (this.cZ < this.cEndZ) {
// this.cX = this.cStartX;
// this.cZ++;
// } else {
// this.cX = this.cStartX;
// this.cZ = this.cStartZ;
// this.cY++;
// //stop condition was already checked using this.cIterated at the start of the method
// }
//
// //using < minX and > maxX instead of <= and >= in vanilla, because minX, maxX are the coordinates
// //of the box that wasn't extended for oversized blocks yet.
// final int edgesHit = this.sectionOversizedBlocks ?
// (x < this.minX || x > this.maxX ? 1 : 0) +
// (y < this.minY || y > this.maxY ? 1 : 0) +
// (z < this.minZ || z > this.maxZ ? 1 : 0) : 0;
//
// if (edgesHit == 3) {
// continue;
// }
//
// final BlockState state = this.cachedChunkSection.getBlockState(x & 15, y & 15, z & 15);
//
// if (!canInteractWithBlock(state, edgesHit)) {
// continue;
// }
//
// this.pos.set(x, y, z);
//
// VoxelShape collisionShape = state.getCollisionShape(this.world, this.pos, this.context);
//
// if (collisionShape != Shapes.empty() && collisionShape != null /*collisionShape should never be null, but we received crash reports.*/) {
// VoxelShape collidedShape = getCollidedShape(this.box, this.shape, collisionShape, x, y, z);
// if (collidedShape != null) {
// if (z >= this.maxHitZ && (z > this.maxHitZ || y >= this.maxHitY && (y > this.maxHitY || x > this.maxHitX))) {
// this.maxHitX = x;
// this.maxHitY = y;
// this.maxHitZ = z;
// //Always make sure the shape at the maximum position is the last one returned, because
// // the last shape has a different 1e-7 behavior (no next shape that clips movement to 0).
// // This does affect certain contraptions: https://github.com/CaffeineMC/lithium-fabric/issues/443
// VoxelShape previousMaxShape = this.maxShape;
// this.maxShape = collidedShape;
// if (previousMaxShape != null) {
// return previousMaxShape;
// }
// } else {
// return collidedShape;
// }
// }
// }
// }
//
// if (!this.hideLastCollision && this.maxShape != null) {
// VoxelShape previousMaxShape = this.maxShape;
// this.maxShape = null;
// return previousMaxShape;
// }
//
// return this.endOfData();
// }
//
// /**
// * This is an artifact from vanilla which is used to avoid testing shapes in the extended portion of a volume
// * unless they are a shape which exceeds their voxel. Pistons must be special-cased here.
// *
// * @return True if the shape can be interacted with at the given edge boundary
// */
// private static boolean canInteractWithBlock(BlockState state, int edgesHit) {
// return (edgesHit != 1 || state.exceedsCube()) && (edgesHit != 2 || state.getBlock() == Blocks.MOVING_PISTON);
// }
//
// /**
// * Checks if the {@param entityShape} or {@param entityBox} intersects the given {@param shape} which is translated
// * to the given position. This is a very specialized implementation which tries to avoid going through VoxelShape
// * for full-cube shapes.
// *
// * @return A {@link VoxelShape} which contains the shape representing that which was collided with, otherwise null
// */
// private static VoxelShape getCollidedShape(AABB entityBox, VoxelShape entityShape, VoxelShape shape, int x, int y, int z) {
// if (shape == Shapes.fullCube()) {
// return entityBox.intersects(x, y, z, x + 1.0, y + 1.0, z + 1.0) ? shape.offset(x, y, z) : null;
// }
// if (shape instanceof VoxelShapeCaster) {
// if (((VoxelShapeCaster) shape).intersects(entityBox, x, y, z)) {
// return shape.offset(x, y, z);
// } else {
// return null;
// }
// }
//
// shape = shape.offset(x, y, z);
//
// if (Shapes.matchesAnywhere(shape, entityShape, BooleanOp.AND)) {
// return shape;
// }
//
// return null;
// }
//
// private static int expandMin(int coord) {
// return coord - 1;
// }
// private static int expandMax(int coord) {
// return coord + 1;
// }
//
// /**
// * Checks the cached information whether the {@param chunkY} section of the {@param chunk} has oversized blocks.
// *
// * @return Whether there are any oversized blocks in the chunk section.
// */
// private static boolean hasChunkSectionOversizedBlocks(ChunkAccess chunk, int chunkY) {
// if (BlockStateFlags.ENABLED) {
// LevelChunkSection section = chunk.getSectionArray()[chunkY];
// return section != null && ((BlockCountingSection) section).lithium$mayContainAny(BlockStateFlags.OVERSIZED_SHAPE);
// }
// return true; //like vanilla, assume that a chunk section has oversized blocks, when the section mixin isn't loaded
// }
//
// public List<VoxelShape> collectAll() {
// ArrayList<VoxelShape> collisions = new ArrayList<>();
//
// while (this.hasNext()) {
// collisions.add(this.next());
// }
//
// return collisions;
// }
//}

View File

@@ -0,0 +1,18 @@
package net.gensokyoreimagined.nitori.common.entity.movement_tracker;
//import net.minecraft.world.level.entity.EntityAccess;
//import net.minecraft.world.level.entity.EntitySectionStorage;
//
//public interface EntityMovementTrackerSection {
// void lithium$addListener(SectionedEntityMovementTracker<?, ?> listener);
//
// void lithium$removeListener(EntitySectionStorage<?> sectionedEntityCache, SectionedEntityMovementTracker<?, ?> listener);
//
// void lithium$trackEntityMovement(int notificationMask, long time);
//
// long lithium$getChangeTime(int trackedClass);
//
// <S, E extends EntityAccess> void lithium$listenToMovementOnce(SectionedEntityMovementTracker<E, S> listener, int trackedClass);
//
// <S, E extends EntityAccess> void lithium$removeListenToMovementOnce(SectionedEntityMovementTracker<E, S> listener, int trackedClass);
//}

View File

@@ -0,0 +1,54 @@
package net.gensokyoreimagined.nitori.common.entity.movement_tracker;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import net.gensokyoreimagined.nitori.api.inventory.LithiumInventory;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.level.block.entity.HopperBlockEntity;
import net.minecraft.world.level.entity.EntityAccess;
import java.util.List;
public abstract class MovementTrackerHelper {
public static final List<Class<?>> MOVEMENT_NOTIFYING_ENTITY_CLASSES;
public static volatile Reference2IntOpenHashMap<Class<? extends EntityAccess>> CLASS_2_NOTIFY_MASK;
public static final int NUM_MOVEMENT_NOTIFYING_CLASSES;
static {
if (LithiumInventory.class.isAssignableFrom(HopperBlockEntity.class)) {
MOVEMENT_NOTIFYING_ENTITY_CLASSES = List.of(ItemEntity.class, Inventory.class);
} else {
MOVEMENT_NOTIFYING_ENTITY_CLASSES = List.of();
}
CLASS_2_NOTIFY_MASK = new Reference2IntOpenHashMap<>();
CLASS_2_NOTIFY_MASK.defaultReturnValue(-1);
NUM_MOVEMENT_NOTIFYING_CLASSES = MOVEMENT_NOTIFYING_ENTITY_CLASSES.size();
}
public static int getNotificationMask(Class<? extends EntityAccess> entityClass) {
int notificationMask = CLASS_2_NOTIFY_MASK.getInt(entityClass);
if (notificationMask == -1) {
notificationMask = calculateNotificationMask(entityClass);
}
return notificationMask;
}
private static int calculateNotificationMask(Class<? extends EntityAccess> entityClass) {
int mask = 0;
for (int i = 0; i < MOVEMENT_NOTIFYING_ENTITY_CLASSES.size(); i++) {
Class<?> superclass = MOVEMENT_NOTIFYING_ENTITY_CLASSES.get(i);
if (superclass.isAssignableFrom(entityClass)) {
mask |= 1 << i;
}
}
//progress can be lost here, but it can only cost performance
//copy on write followed by publication in volatile field guarantees visibility of the final state
Reference2IntOpenHashMap<Class<? extends EntityAccess>> copy = CLASS_2_NOTIFY_MASK.clone();
copy.put(entityClass, mask);
CLASS_2_NOTIFY_MASK = copy;
return mask;
}
}

View File

@@ -0,0 +1,5 @@
package net.gensokyoreimagined.nitori.common.entity.movement_tracker;
public interface SectionedEntityMovementListener {
void lithium$handleEntityMovement(Class<?> category);
}

View File

@@ -0,0 +1,197 @@
package net.gensokyoreimagined.nitori.common.entity.movement_tracker;
//import it.unimi.dsi.fastutil.HashCommon;
//import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
//import net.gensokyoreimagined.nitori.common.util.tuples.WorldSectionBox;
//import net.gensokyoreimagined.nitori.common.world.LithiumData;
//import net.gensokyoreimagined.nitori.mixin.util.accessors.ServerEntityManagerAccessor;
//import net.gensokyoreimagined.nitori.mixin.util.accessors.ServerWorldAccessor;
//import net.minecraft.server.level.ServerLevel;
//import net.minecraft.core.SectionPos;
//import net.minecraft.world.level.Level;
//import net.minecraft.world.level.entity.EntityAccess;
//import net.minecraft.world.level.entity.EntitySection;
//import net.minecraft.world.level.entity.EntitySectionStorage;
//
//import java.util.ArrayList;
//
//public abstract class SectionedEntityMovementTracker<E extends EntityAccess, S> {
// final WorldSectionBox trackedWorldSections;
// final Class<S> clazz;
// private final int trackedClass;
// ArrayList<EntitySection<E>> sortedSections;
// boolean[] sectionVisible;
// private int timesRegistered;
// private final ArrayList<EntityMovementTrackerSection> sectionsNotListeningTo;
//
// private long maxChangeTime;
//
// private ReferenceOpenHashSet<SectionedEntityMovementListener> sectionedEntityMovementListeners;
//
// public SectionedEntityMovementTracker(WorldSectionBox interactionChunks, Class<S> clazz) {
// this.clazz = clazz;
// this.trackedWorldSections = interactionChunks;
// this.trackedClass = MovementTrackerHelper.MOVEMENT_NOTIFYING_ENTITY_CLASSES.indexOf(clazz);
// assert this.trackedClass != -1;
// this.sectionedEntityMovementListeners = null;
// this.sectionsNotListeningTo = new ArrayList<>();
// }
//
// @Override
// public int hashCode() {
// return HashCommon.mix(this.trackedWorldSections.hashCode()) ^ HashCommon.mix(this.trackedClass) ^ this.getClass().hashCode();
// }
//
// @Override
// public boolean equals(Object obj) {
// return obj.getClass() == this.getClass() &&
// this.clazz == ((SectionedEntityMovementTracker<?, ?>) obj).clazz &&
// this.trackedWorldSections.equals(((SectionedEntityMovementTracker<?, ?>) obj).trackedWorldSections);
// }
//
// /**
// * Method to quickly check whether any relevant entities moved inside the relevant entity sections after
// * the last interaction attempt.
// *
// * @param lastCheckedTime time of the last interaction attempt
// * @return whether any relevant entity moved in the tracked area
// */
// public boolean isUnchangedSince(long lastCheckedTime) {
// if (lastCheckedTime <= this.maxChangeTime) {
// return false;
// }
// if (!this.sectionsNotListeningTo.isEmpty()) {
// this.setChanged(this.listenToAllSectionsAndGetMaxChangeTime());
// return lastCheckedTime > this.maxChangeTime;
// }
// return true;
// }
//
// private long listenToAllSectionsAndGetMaxChangeTime() {
// long maxChangeTime = Long.MIN_VALUE;
// ArrayList<EntityMovementTrackerSection> notListeningTo = this.sectionsNotListeningTo;
// for (int i = notListeningTo.size() - 1; i >= 0; i--) {
// EntityMovementTrackerSection entityMovementTrackerSection = notListeningTo.remove(i);
// entityMovementTrackerSection.lithium$listenToMovementOnce(this, this.trackedClass);
// maxChangeTime = Math.max(maxChangeTime, entityMovementTrackerSection.lithium$getChangeTime(this.trackedClass));
// }
// return maxChangeTime;
// }
//
// public void register(ServerLevel world) {
// assert world == this.trackedWorldSections.world();
//
// if (this.timesRegistered == 0) {
// //noinspection unchecked
// EntitySectionStorage<E> cache = ((ServerEntityManagerAccessor<E>) ((ServerWorldAccessor) world).getEntityManager()).getSectionStorage();
//
// WorldSectionBox trackedSections = this.trackedWorldSections;
// int size = trackedSections.numSections();
// assert size > 0;
// this.sortedSections = new ArrayList<>(size);
// this.sectionVisible = new boolean[size];
//
// //vanilla iteration order in EntitySectionStorage is xzy
// //WorldSectionBox upper coordinates are exclusive
// for (int x = trackedSections.chunkX1(); x < trackedSections.chunkX2(); x++) {
// for (int z = trackedSections.chunkZ1(); z < trackedSections.chunkZ2(); z++) {
// for (int y = trackedSections.chunkY1(); y < trackedSections.chunkY2(); y++) {
// EntitySection<E> section = cache.getSection(SectionPos.asLong(x, y, z));
// EntityMovementTrackerSection sectionAccess = (EntityMovementTrackerSection) section;
// this.sortedSections.add(section);
// sectionAccess.lithium$addListener(this);
// }
// }
// }
// this.setChanged(world.getGameTime());
// }
//
// this.timesRegistered++;
// }
//
// public void unRegister(Level world) {
// assert world == this.trackedWorldSections.world();
// if (--this.timesRegistered > 0) {
// return;
// }
// assert this.timesRegistered == 0;
// //noinspection unchecked
// EntitySectionStorage<E> cache = ((ServerEntityManagerAccessor<E>) ((ServerWorldAccessor) world).getEntityManager()).getSectionStorage();
// ((LithiumData) world).lithium$getData().entityMovementTrackers().deleteCanonical(this);
//
// ArrayList<EntitySection<E>> sections = this.sortedSections;
// for (int i = sections.size() - 1; i >= 0; i--) {
// EntitySection<E> section = sections.get(i);
// EntityMovementTrackerSection sectionAccess = (EntityMovementTrackerSection) section;
// sectionAccess.lithium$removeListener(cache, this);
// if (!this.sectionsNotListeningTo.remove(section)) {
// ((EntityMovementTrackerSection) section).lithium$removeListenToMovementOnce(this, this.trackedClass);
// }
// }
// this.setChanged(world.getGameTime());
// }
//
// /**
// * Register an entity section to this listener, so this listener can look for changes in the section.
// */
// public void onSectionEnteredRange(EntityMovementTrackerSection section) {
// this.setChanged(this.trackedWorldSections.world().getGameTime());
// //noinspection SuspiciousMethodCalls
// int sectionIndex = this.sortedSections.lastIndexOf(section);
// this.sectionVisible[sectionIndex] = true;
//
// this.sectionsNotListeningTo.add(section);
// this.notifyAllListeners();
// }
//
// public void onSectionLeftRange(EntityMovementTrackerSection section) {
// this.setChanged(this.trackedWorldSections.world().getGameTime());
// //noinspection SuspiciousMethodCalls
// int sectionIndex = this.sortedSections.lastIndexOf(section);
//
// this.sectionVisible[sectionIndex] = false;
//
// if (!this.sectionsNotListeningTo.remove(section)) {
// section.lithium$removeListenToMovementOnce(this, this.trackedClass);
// this.notifyAllListeners();
// }
// }
//
// /**
// * Method that marks that new entities might have appeared or moved in the tracked chunk sections.
// */
// private void setChanged(long atTime) {
// if (atTime > this.maxChangeTime) {
// this.maxChangeTime = atTime;
// }
// }
//
// public void listenToEntityMovementOnce(SectionedEntityMovementListener listener) {
// if (this.sectionedEntityMovementListeners == null) {
// this.sectionedEntityMovementListeners = new ReferenceOpenHashSet<>();
// }
// this.sectionedEntityMovementListeners.add(listener);
//
// if (!this.sectionsNotListeningTo.isEmpty()) {
// this.setChanged(this.listenToAllSectionsAndGetMaxChangeTime());
// }
//
// }
//
// public void emitEntityMovement(int classMask, EntityMovementTrackerSection section) {
// if ((classMask & (1 << this.trackedClass)) != 0) {
// this.notifyAllListeners();
// this.sectionsNotListeningTo.add(section);
// }
// }
//
// private void notifyAllListeners() {
// ReferenceOpenHashSet<SectionedEntityMovementListener> listeners = this.sectionedEntityMovementListeners;
// if (listeners != null && !listeners.isEmpty()) {
// for (SectionedEntityMovementListener listener : listeners) {
// listener.lithium$handleEntityMovement(this.clazz);
// }
// listeners.clear();
// }
// }
//}

View File

@@ -0,0 +1,20 @@
package net.gensokyoreimagined.nitori.common.entity.pushable;
import net.minecraft.world.level.block.state.BlockState;
public interface BlockCachingEntity {
default void lithium$OnBlockCacheDeleted() {
}
default void lithium$OnBlockCacheSet(BlockState newState) {
}
default void lithium$SetClimbingMobCachingSectionUpdateBehavior(boolean listening) {
throw new UnsupportedOperationException();
}
BlockState lithium$getCachedFeetBlockState();
}

View File

@@ -0,0 +1,14 @@
package net.gensokyoreimagined.nitori.common.entity.pushable;
import java.util.function.Predicate;
public abstract class EntityPushablePredicate<S> implements Predicate<S> {
public static <T> Predicate<T> and(Predicate<? super T> first, Predicate<? super T> second) {
return new EntityPushablePredicate<T>() {
@Override
public boolean test(T t) {
return first.test(t) && second.test(t);
}
};
}
}

View File

@@ -0,0 +1,46 @@
package net.gensokyoreimagined.nitori.common.math;
/**
* @author jafama library
* https://github.com/jeffhain/jafama
*/
public final class FasterMathUtil {
private static final int MAX_FLOAT_EXPONENT = 127;
private static final int MAX_DOUBLE_EXPONENT = 1023;
public static int round(float a) {
final int bits = Float.floatToRawIntBits(a);
final int biasedExp = ((bits >> 23) & 0xFF);
final int shift = (23 - 1 + MAX_FLOAT_EXPONENT) - biasedExp;
if ((shift & -32) == 0) {
int bitsSignum = (((bits >> 31) << 1) + 1);
int extendedMantissa = (0x00800000 | (bits & 0x007FFFFF)) * bitsSignum;
return ((extendedMantissa >> shift) + 1) >> 1;
} else {
return (int) a;
}
}
public static long round(double a) {
final long bits = Double.doubleToRawLongBits(a);
final int biasedExp = (((int)(bits >> 52)) & 0x7FF);
final int shift = (52 - 1 + MAX_DOUBLE_EXPONENT) - biasedExp;
if ((shift & -64) == 0) {
long bitsSignum = (((bits >> 63) << 1) + 1);
long extendedMantissa = (0x0010000000000000L | (bits & 0x000FFFFFFFFFFFFFL)) * bitsSignum;
return ((extendedMantissa >> shift) + 1L) >> 1;
} else {
return (long) a;
}
}
public static float positiveModuloForPositiveIntegerDivisor(float dividend, float divisor) {
var modulo = dividend % divisor;
return modulo < 0 ? modulo + divisor : modulo;
}
public static double positiveModuloForPositiveIntegerDivisor(double dividend, double divisor) {
var modulo = dividend % divisor;
return modulo < 0 ? modulo + divisor : modulo;
}
}

View File

@@ -0,0 +1,116 @@
package net.gensokyoreimagined.nitori.common.math.random;
import com.google.common.annotations.VisibleForTesting;
import net.minecraft.util.Mth;
import net.minecraft.world.level.levelgen.BitRandomSource;
import net.minecraft.world.level.levelgen.LegacyRandomSource;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.levelgen.PositionalRandomFactory;
import java.util.random.RandomGenerator;
import java.util.random.RandomGeneratorFactory;
public class RandomGeneratorRandom implements BitRandomSource {
private static final RandomGeneratorFactory<RandomGenerator.SplittableGenerator> RANDOM_GENERATOR_FACTORY = RandomGeneratorFactory.of("L64X128MixRandom");
private static final int INT_BITS = 48;
private static final long SEED_MASK = 0xFFFFFFFFFFFFL;
private static final long MULTIPLIER = 25214903917L;
private static final long INCREMENT = 11L;
private long seed;
private RandomGenerator.SplittableGenerator randomGenerator;
public RandomGeneratorRandom(long seed) {
this.seed = seed;
this.randomGenerator = RANDOM_GENERATOR_FACTORY.create(seed);
}
@Override
public RandomSource fork() {
return new RandomGeneratorRandom(this.nextLong());
}
@Override
public PositionalRandomFactory forkPositional() {
return new Splitter(this.nextLong());
}
@Override
public void setSeed(long seed) {
this.seed = seed;
this.randomGenerator = RANDOM_GENERATOR_FACTORY.create(seed);
}
@Override
public int next(int bits) {
// >>> instead of Mojang's >> fixes MC-239059
return (int) ((seed * MULTIPLIER + INCREMENT & SEED_MASK) >>> INT_BITS - bits);
}
@Override
public int nextInt() {
return randomGenerator.nextInt();
}
@Override
public int nextInt(int bound) {
return randomGenerator.nextInt(bound);
}
@Override
public long nextLong() {
return randomGenerator.nextLong();
}
@Override
public boolean nextBoolean() {
return randomGenerator.nextBoolean();
}
@Override
public float nextFloat() {
return randomGenerator.nextFloat();
}
@Override
public double nextDouble() {
return randomGenerator.nextDouble();
}
@Override
public double nextGaussian() {
return randomGenerator.nextGaussian();
}
private record Splitter(long seed) implements PositionalRandomFactory {
@Override
public RandomSource at(int x, int y, int z) {
return new RandomGeneratorRandom(Mth.getSeed(x, y, z) ^ this.seed);
}
@Override
public RandomSource fromHashOf(String seed) {
return new RandomGeneratorRandom((long) seed.hashCode() ^ this.seed);
}
@Override
public RandomSource fromSeed(long seed) {
return new RandomGeneratorRandom(seed ^ this.seed);
}
/*
@Override
public Random split(long seed) {
return new RandomGeneratorRandom(seed);
}
*/
@Override
@VisibleForTesting
public void parityConfigString(StringBuilder info) {
info.append("RandomGeneratorRandom$Splitter{").append(this.seed).append("}");
}
}
}

View File

@@ -0,0 +1,71 @@
package net.gensokyoreimagined.nitori.common.reflection;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.entity.Entity;
import net.minecraft.ReportedException;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.WeakHashMap;
public class ReflectionUtil {
public static boolean hasMethodOverride(Class<?> clazz, Class<?> superclass, boolean fallbackResult, String methodName, Class<?>... methodArgs) {
while (clazz != null && clazz != superclass && superclass.isAssignableFrom(clazz)) {
try {
clazz.getDeclaredMethod(methodName, methodArgs);
return true;
} catch (NoSuchMethodException e) {
clazz = clazz.getSuperclass();
} catch (NoClassDefFoundError error) {
Logger logger = LogManager.getLogger("Lithium Class Analysis");
logger.warn("Lithium Class Analysis Error: Class " + clazz.getName() + " cannot be analysed, because" +
" getting declared methods crashes with NoClassDefFoundError: " + error.getMessage() +
". This is usually caused by modded" +
" entities declaring methods that have a return type or parameter type that is annotated" +
" with @Environment(value=EnvType.CLIENT). Loading the type is not possible, because" +
" it only exists in the CLIENT environment. The recommended fix is to annotate the method with" +
" this argument or return type with the same annotation." +
" Lithium handles this error by assuming the class cannot be included in some optimizations.");
return fallbackResult;
} catch (Throwable e) {
final String crashedClass = clazz.getName();
CrashReport crashReport = CrashReport.forThrowable(e, "Lithium Class Analysis");
CrashReportCategory crashReportSection = crashReport.addCategory(e.getClass().toString() + " when getting declared methods.");
crashReportSection.setDetail("Analyzed class", crashedClass);
crashReportSection.setDetail("Analyzed method name", methodName);
crashReportSection.setDetail("Analyzed method args", methodArgs);
throw new ReportedException(crashReport);
}
}
return false;
}
//How to find the remapped methods:
//1) Run in the debugger: System.out.println(FabricLoader.getInstance().getMappingResolver().getNamespaceData("intermediary").methodNames)
//2) Ctrl+F for the method name, in this case "onEntityCollision". Make sure to find the correct one.
// private static final String REMAPPED_ON_ENTITY_COLLISION = FabricLoader.getInstance().getMappingResolver().mapMethodName("intermediary", "net.minecraft.class_4970", "method_9548", "(Lnet/minecraft/class_2680;Lnet/minecraft/class_1937;Lnet/minecraft/class_2338;Lnet/minecraft/class_1297;)V");
private static final String REMAPPED_ON_ENTITY_COLLISION = "entityInside";
private static final WeakHashMap<Class<?>, Boolean> CACHED_IS_ENTITY_TOUCHABLE = new WeakHashMap<>();
public static boolean isBlockStateEntityTouchable(BlockState operand) {
Class<? extends Block> blockClazz = operand.getBlock().getClass();
//Caching results in hashmap as this calculation takes over a second for all blocks together
Boolean result = CACHED_IS_ENTITY_TOUCHABLE.get(blockClazz);
if (result != null) {
return result;
}
boolean res = ReflectionUtil.hasMethodOverride(blockClazz, BlockBehaviour.class, true, REMAPPED_ON_ENTITY_COLLISION, BlockState.class, Level.class, BlockPos.class, Entity.class);
CACHED_IS_ENTITY_TOUCHABLE.put(blockClazz, res);
return res;
}
}
//TODO: There's bunch of client sided options, we need to safely remove them if possible if not keep the behaviour since mixins work regardless of client or not

View File

@@ -0,0 +1,19 @@
package net.gensokyoreimagined.nitori.common.shapes;
//import net.minecraft.world.phys.shapes.BooleanOp;
//import net.minecraft.world.phys.AABB;
//import net.minecraft.world.phys.shapes.VoxelShape;
//
//
//public interface VoxelShapeCaster {
// /**
// * Checks whether an entity's bounding box collides with this shape translated to the given coordinates.
// *
// * @param box The entity's bounding box
// * @param blockX The x-coordinate of this shape
// * @param blockY The y-coordinate of this shape
// * @param blockZ The z-coordinate of this shape
// * @return True if the box intersects with this shape, otherwise false
// */
// boolean intersects(AABB box, double blockX, double blockY, double blockZ);
//}

View File

@@ -12,7 +12,7 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.core;
package net.gensokyoreimagined.nitori.common.state;
import com.google.common.collect.Table;
import it.unimi.dsi.fastutil.Hash;

View File

@@ -12,7 +12,7 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.core;
package net.gensokyoreimagined.nitori.common.state;
import it.unimi.dsi.fastutil.Hash;
import it.unimi.dsi.fastutil.ints.IntArrays;

View File

@@ -0,0 +1,55 @@
// Nitori Copyright (C) 2024 Gensokyo Reimagined
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.common.state;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
/**
* Many of the column and row key arrays in block state tables will be duplicated, leading to an unnecessary waste of
* memory. Since we have very limited options for trying to construct more optimized table types without throwing
* maintainability or mod compatibility out the window, this class acts as a dirty way to find and de-duplicate arrays
* after we construct our table types.
* <p>
* While this global cache does not provide the ability to remove or clear entries from it, the reality is that it
* shouldn't matter because block state tables are only initialized once and remain loaded for the entire lifetime of
* the game. Even in the event of classloader pre-boot shenanigans, we still shouldn't leak memory as our cache will be
* dropped along with the rest of the loaded classes when the class loader is reaped.
*/
public class StatePropertyTableCache {
public static final FastImmutableTableCache<Property<?>, Comparable<?>, BlockState> BLOCK_STATE_TABLE =
new FastImmutableTableCache<>();
public static final FastImmutableTableCache<Property<?>, Comparable<?>, FluidState> FLUID_STATE_TABLE =
new FastImmutableTableCache<>();
@SuppressWarnings("unchecked")
public static <S, O> FastImmutableTableCache<Property<?>, Comparable<?>, S> getTableCache(O owner) {
if (owner instanceof Block) {
return (FastImmutableTableCache<Property<?>, Comparable<?>, S>) BLOCK_STATE_TABLE;
} else if (owner instanceof Fluid) {
return (FastImmutableTableCache<Property<?>, Comparable<?>, S>) FLUID_STATE_TABLE;
} else {
throw new IllegalArgumentException("");
}
}
}

View File

@@ -0,0 +1,11 @@
package net.gensokyoreimagined.nitori.common.util;
/**
* Pre-initialized constants to avoid unnecessary allocations.
*/
public final class ArrayConstants {
private ArrayConstants() {}
public static final int[] EMPTY = new int[0];
public static final int[] ZERO = new int[]{0};
}

View File

@@ -0,0 +1,12 @@
package net.gensokyoreimagined.nitori.common.util;
import net.minecraft.world.entity.EquipmentSlot;
/**
* Pre-initialized constants to avoid unnecessary allocations.
*/
public final class EquipmentSlotConstants {
private EquipmentSlotConstants() {}
public static final EquipmentSlot[] ALL = EquipmentSlot.values();
}

View File

@@ -0,0 +1,106 @@
package net.gensokyoreimagined.nitori.common.util.collections;
import org.jetbrains.annotations.NotNull;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class LazyList<T> extends AbstractList<T> {
private final ArrayList<T> delegate;
private Iterator<T> iterator;
public LazyList(ArrayList<T> delegate, Iterator<T> iterator) {
this.delegate = delegate;
this.iterator = iterator;
}
private boolean produceToIndex(int n) {
n -= this.delegate.size();
if (n >= 0 && this.iterator != null) {
while (this.iterator.hasNext()) {
this.delegate.add(this.iterator.next());
if (--n < 0) {
return true;
}
}
this.iterator = null;
}
return n < 0;
}
@Override
public T get(int index) {
this.produceToIndex(index);
return this.delegate.get(index);
}
@Override
public int size() {
this.produceToIndex(Integer.MAX_VALUE);
return this.delegate.size();
}
@Override
public @NotNull Iterator<T> iterator() {
return new Iterator<T>() {
private int index = 0;
@Override
public boolean hasNext() {
return LazyList.this.produceToIndex(this.index);
}
@Override
public T next() {
return LazyList.this.get(this.index++);
}
};
}
@Override
public T set(int index, T element) {
this.produceToIndex(index);
return this.delegate.set(index, element);
}
@Override
public void add(int index, T element) {
this.produceToIndex(index - 1);
this.delegate.add(index, element);
}
@Override
public T remove(int index) {
this.produceToIndex(index);
return this.delegate.remove(index);
}
@Override
public void clear() {
this.delegate.clear();
this.iterator = null;
}
@Override
public boolean add(T t) {
this.produceToIndex(Integer.MAX_VALUE);
return this.delegate.add(t);
}
@Override
public boolean isEmpty() {
return !this.produceToIndex(0);
}
@Override
public boolean addAll(int index, Collection<? extends T> c) {
if (c.isEmpty()) {
return false;
}
this.produceToIndex(index - 1);
return this.delegate.addAll(index, c);
}
}

View File

@@ -0,0 +1,149 @@
package net.gensokyoreimagined.nitori.common.util.collections;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.*;
import java.util.function.Consumer;
public class MaskedList<E> extends AbstractList<E> {
private final ObjectArrayList<E> allElements;
private final BitSet visibleMask;
private final Object2IntOpenHashMap<E> element2Index;
private final boolean defaultVisibility;
private int numCleared;
public MaskedList(ObjectArrayList<E> allElements, boolean defaultVisibility) {
this.allElements = new ObjectArrayList<>();
this.visibleMask = new BitSet();
this.defaultVisibility = defaultVisibility;
this.element2Index = new Object2IntOpenHashMap<>();
this.element2Index.defaultReturnValue(-1);
this.addAll(allElements);
}
public MaskedList() {
this(new ObjectArrayList<>(), true);
}
public int totalSize() {
return this.allElements.size();
}
public void addOrSet(E element, boolean visible) {
int index = this.element2Index.getInt(element);
if (index != -1) {
this.visibleMask.set(index, visible);
} else {
this.add(element);
this.setVisible(element, visible);
}
}
public void setVisible(E element, final boolean visible) {
int index = this.element2Index.getInt(element);
if (index != -1) {
this.visibleMask.set(index, visible);
}
//ignore when the element is not in the collection
}
@Override
public Iterator<E> iterator() {
return new Iterator<>() {
int nextIndex = 0;
int cachedNext = -1;
@Override
public boolean hasNext() {
return (this.cachedNext = MaskedList.this.visibleMask.nextSetBit(this.nextIndex)) != -1;
}
@Override
public E next() {
int index = this.cachedNext;
this.cachedNext = -1;
this.nextIndex = index + 1;
return MaskedList.this.allElements.get(index);
}
};
}
@Override
public Spliterator<E> spliterator() {
return new Spliterators.AbstractSpliterator<E>(Long.MAX_VALUE, Spliterator.ORDERED | Spliterator.NONNULL) {
int nextIndex = 0;
@Override
public boolean tryAdvance(Consumer<? super E> action) {
int index = MaskedList.this.visibleMask.nextSetBit(this.nextIndex);
if (index == -1) {
return false;
}
this.nextIndex = index + 1;
action.accept(MaskedList.this.allElements.get(index));
return true;
}
};
}
@Override
public boolean add(E e) {
int oldIndex = this.element2Index.put(e, this.allElements.size());
if (oldIndex != -1) {
throw new IllegalStateException("MaskedList must not contain duplicates! Trying to add " + e + " but it is already present at index " + oldIndex + ". Current size: " + this.allElements.size());
}
this.visibleMask.set(this.allElements.size(), this.defaultVisibility);
return this.allElements.add(e);
}
@Override
public boolean remove(Object o) {
int index = this.element2Index.removeInt(o);
if (index == -1) {
return false;
}
this.visibleMask.clear(index);
this.allElements.set(index, null);
this.numCleared++;
if (this.numCleared * 2 > this.allElements.size()) {
ObjectArrayList<E> clonedElements = this.allElements.clone();
BitSet clonedVisibleMask = (BitSet) this.visibleMask.clone();
this.allElements.clear();
this.visibleMask.clear();
this.element2Index.clear();
for (int i = 0; i < clonedElements.size(); i++) {
E element = clonedElements.get(i);
int newIndex = this.allElements.size();
this.allElements.add(element);
this.visibleMask.set(newIndex, clonedVisibleMask.get(i));
this.element2Index.put(element, newIndex);
}
this.numCleared = 0;
}
return true;
}
@Override
public E get(int index) {
if (index < 0 || index >= this.size()) {
throw new IndexOutOfBoundsException(index);
}
int i = 0;
while (index >= 0) {
index--;
i = this.visibleMask.nextSetBit(i + 1);
}
return this.allElements.get(i);
}
@Override
public int size() {
return this.visibleMask.cardinality();
}
}

View File

@@ -0,0 +1,74 @@
// Nitori Copyright (C) 2024 Gensokyo Reimagined
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.common.util.collections;
import it.unimi.dsi.fastutil.HashCommon;
import net.minecraft.util.Mth;
import java.util.function.Predicate;
/**
* A lossy hashtable implementation that stores a mapping between an object and a boolean.
* <p>
* Any hash collisions will result in an overwrite: this is safe because the correct value can always be recomputed,
* given that the given operator is deterministic.
* <p>
* This implementation is safe to use from multiple threads
*/
public final class Object2BooleanCacheTable<T> {
private final int mask;
private final Node<T>[] nodes;
private final Predicate<T> operator;
@SuppressWarnings("unchecked")
public Object2BooleanCacheTable(int capacity, Predicate<T> operator) {
int capacity1 = Mth.smallestEncompassingPowerOfTwo(capacity);
this.mask = capacity1 - 1;
this.nodes = (Node<T>[]) new Node[capacity1];
this.operator = operator;
}
private static <T> int hash(T key) {
return HashCommon.mix(key.hashCode());
}
public boolean get(T key) {
int idx = hash(key) & this.mask;
Node<T> node = this.nodes[idx];
if (node != null && key.equals(node.key)) {
return node.value;
}
boolean test = this.operator.test(key);
this.nodes[idx] = new Node<>(key, test);
return test;
}
static class Node<T> {
final T key;
final boolean value;
Node(T key, boolean value) {
this.key = key;
this.value = value;
}
}
}

View File

@@ -0,0 +1,16 @@
package net.gensokyoreimagined.nitori.common.util.deduplication;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
public class LithiumInterner<T> {
private final ObjectOpenHashSet<T> canonicalStorage = new ObjectOpenHashSet<>();
public <S extends T> S getCanonical(S value) {
//noinspection unchecked
return (S) this.canonicalStorage.addOrGet(value);
}
public void deleteCanonical(T value) {
this.canonicalStorage.remove(value);
}
}

View File

@@ -0,0 +1,8 @@
package net.gensokyoreimagined.nitori.common.util.deduplication;
public interface LithiumInternerWrapper<T> {
T lithium$getCanonical(T value);
void lithium$deleteCanonical(T value);
}

View File

@@ -0,0 +1,20 @@
package net.gensokyoreimagined.nitori.common.util.network;
/**
* Maps VarInt byte sizes to a lookup table corresponding to the number of bits in the integer,
* from zero to 32.
*/
public class VarIntUtil {
private static final int[] VARINT_EXACT_BYTE_LENGTHS = new int[33];
static {
for (int i = 0; i <= 32; ++i) {
VARINT_EXACT_BYTE_LENGTHS[i] = (int) Math.ceil((31d - (i - 1)) / 7d);
}
VARINT_EXACT_BYTE_LENGTHS[32] = 1; // Special case for 0.
}
public static int getVarIntLength(int value) {
return VARINT_EXACT_BYTE_LENGTHS[Integer.numberOfLeadingZeros(value)];
}
}

View File

@@ -0,0 +1,57 @@
package net.gensokyoreimagined.nitori.common.util.tuples;
import net.minecraft.world.phys.AABB;
import net.minecraft.core.SectionPos;
import net.minecraft.util.Mth;
import net.minecraft.world.level.Level;
//Y values use coordinates, not indices (y=0 -> chunkY=0)
//upper bounds are EXCLUSIVE
public record WorldSectionBox(Level world, int chunkX1, int chunkY1, int chunkZ1, int chunkX2, int chunkY2,
int chunkZ2) {
public static WorldSectionBox entityAccessBox(Level world, AABB box) {
int minX = SectionPos.blockToSectionCoord(box.minX - 2.0D);
int minY = SectionPos.blockToSectionCoord(box.minY - 4.0D);
int minZ = SectionPos.blockToSectionCoord(box.minZ - 2.0D);
int maxX = SectionPos.blockToSectionCoord(box.maxX + 2.0D) + 1;
int maxY = SectionPos.blockToSectionCoord(box.maxY) + 1;
int maxZ = SectionPos.blockToSectionCoord(box.maxZ + 2.0D) + 1;
return new WorldSectionBox(world, minX, minY, minZ, maxX, maxY, maxZ);
}
//Relevant block box: Entity hitbox expanded to all blocks it touches. Then expand the resulting box by 1 block in each direction.
//Include all chunk sections that contain blocks inside the expanded box.
public static WorldSectionBox relevantExpandedBlocksBox(Level world, AABB box) {
int minX = SectionPos.blockToSectionCoord(Mth.floor(box.minX) - 1);
int minY = SectionPos.blockToSectionCoord(Mth.floor(box.minY) - 1);
int minZ = SectionPos.blockToSectionCoord(Mth.floor(box.minZ) - 1);
int maxX = SectionPos.blockToSectionCoord(Mth.floor(box.maxX) + 1) + 1;
int maxY = SectionPos.blockToSectionCoord(Mth.floor(box.maxY) + 1) + 1;
int maxZ = SectionPos.blockToSectionCoord(Mth.floor(box.maxZ) + 1) + 1;
return new WorldSectionBox(world, minX, minY, minZ, maxX, maxY, maxZ);
}
//Like relevant blocks, but not expanded, because fluids never exceed the 1x1x1 volume of a block
public static WorldSectionBox relevantFluidBox(Level world, AABB box) {
int minX = SectionPos.blockToSectionCoord(Mth.floor(box.minX));
int minY = SectionPos.blockToSectionCoord(Mth.floor(box.minY));
int minZ = SectionPos.blockToSectionCoord(Mth.floor(box.minZ));
int maxX = SectionPos.blockToSectionCoord(Mth.floor(box.maxX)) + 1;
int maxY = SectionPos.blockToSectionCoord(Mth.floor(box.maxY)) + 1;
int maxZ = SectionPos.blockToSectionCoord(Mth.floor(box.maxZ)) + 1;
return new WorldSectionBox(world, minX, minY, minZ, maxX, maxY, maxZ);
}
public int numSections() {
return (this.chunkX2 - this.chunkX1) * (this.chunkY2 - this.chunkY1) * (this.chunkZ2 - this.chunkZ1);
}
public boolean matchesRelevantBlocksBox(AABB box) {
return SectionPos.blockToSectionCoord(Mth.floor(box.minX) - 1) == this.chunkX1 &&
SectionPos.blockToSectionCoord(Mth.floor(box.minY) - 1) == this.chunkY1 &&
SectionPos.blockToSectionCoord(Mth.floor(box.minZ) - 1) == this.chunkZ1 &&
SectionPos.blockToSectionCoord(Mth.ceil(box.maxX) + 1) + 1 == this.chunkX2 &&
SectionPos.blockToSectionCoord(Mth.ceil(box.maxY) + 1) + 1 == this.chunkY2 &&
SectionPos.blockToSectionCoord(Mth.ceil(box.maxZ) + 1) + 1 == this.chunkZ2;
}
}

View File

@@ -0,0 +1,9 @@
package net.gensokyoreimagined.nitori.common.world;
//import net.minecraft.world.level.entity.EntityAccess;
//
//public interface ChunkAwareEntityIterable<T extends EntityAccess> {
// Iterable<T> lithium$IterateEntitiesInTrackedSections();
//}
//PersistentEntitySectionManager does not work on paper

View File

@@ -12,20 +12,17 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.core;
package net.gensokyoreimagined.nitori.common.world;
import com.destroystokyo.paper.util.maplist.EntityList;
import io.papermc.paper.world.ChunkEntitySlices;
import net.gensokyoreimagined.nitori.access.IMixinChunkEntitySlicesAccess;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.core.BlockPos;
@Mixin(ChunkEntitySlices.class)
public abstract class MixinChunkEntitySlices implements IMixinChunkEntitySlicesAccess {
// Implementation of 0107-Multithreaded-Tracker.patch
@Override
@Final
@Accessor
public abstract EntityList getEntities();
// Taken from Lithium
// https://github.com/CaffeineMC/lithium-fabric/blob/427dd75ffc922cc1858c1db4b283cc54744567e0/src/main/java/me/jellysquid/mods/lithium/common/world/ChunkRandomSource.java
public interface ChunkRandomSource {
/**
* Alternative implementation of {@link net.minecraft.server.level.ServerLevel#getBlockRandomPos(int, int, int, int)} which does not allocate
* a new {@link BlockPos}.
*/
void nitori$getRandomPosInChunk(int x, int y, int z, int mask, BlockPos.MutableBlockPos out);
}

View File

@@ -0,0 +1,10 @@
package net.gensokyoreimagined.nitori.common.world;
import net.minecraft.world.level.chunk.ChunkAccess;
import org.jetbrains.annotations.Nullable;
public interface ChunkView {
@Nullable
ChunkAccess lithium$getLoadedChunk(int chunkX, int chunkZ);
}

View File

@@ -0,0 +1,17 @@
package net.gensokyoreimagined.nitori.common.world;
import net.gensokyoreimagined.nitori.common.entity.pushable.BlockCachingEntity;
import net.gensokyoreimagined.nitori.common.entity.pushable.EntityPushablePredicate;
import net.minecraft.util.AbortableIterationConsumer;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.AABB;
import java.util.ArrayList;
public interface ClimbingMobCachingSection {
AbortableIterationConsumer.Continuation lithium$collectPushableEntities(Level world, Entity except, AABB box, EntityPushablePredicate<? super Entity> entityPushablePredicate, ArrayList<Entity> entities);
void lithium$onEntityModifiedCachedBlock(BlockCachingEntity entity, BlockState newBlockState);
}

View File

@@ -0,0 +1,55 @@
package net.gensokyoreimagined.nitori.common.world;
//import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
//import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
//import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
//import net.gensokyoreimagined.nitori.common.entity.block_tracking.ChunkSectionChangeCallback;
//import net.gensokyoreimagined.nitori.common.entity.block_tracking.SectionedBlockChangeTracker;
//import net.gensokyoreimagined.nitori.common.entity.movement_tracker.SectionedEntityMovementTracker;
//import net.gensokyoreimagined.nitori.common.util.deduplication.LithiumInterner;
//import net.minecraft.world.entity.ai.navigation.PathNavigation;
//import net.minecraft.world.item.ItemStack;
//import net.minecraft.core.registries.Registries;
//import net.minecraft.world.entity.raid.Raid;
//import net.minecraft.world.level.Level;
//import net.minecraft.world.level.gameevent.GameEventDispatcher;
//import net.minecraft.world.level.gameevent.GameEventListenerRegistry;
//
//public interface LithiumData {
//
// record Data(
// // Map of chunk position -> y section -> game event dispatcher
// // This should be faster than the chunk lookup, since there are usually a lot more chunks than
// // chunk with game event dispatchers (we only initialize them when non-empty set of listeners)
// // All Int2ObjectMap objects are also stored in a field of the corresponding WorldChunk.
// Long2ReferenceOpenHashMap<Int2ObjectMap<GameEventDispatcher>> gameEventDispatchersByChunk,
//
// // Cached ominous banner, must not be mutated.
// ItemStack ominousBanner,
//
// // Set of active mob navigations (active = have a path)
// ReferenceOpenHashSet<PathNavigation> activeNavigations,
//
// // Block change tracker deduplication
// LithiumInterner<SectionedBlockChangeTracker> blockChangeTrackers,
//
// // Entity movement tracker deduplication
// LithiumInterner<SectionedEntityMovementTracker<?, ?>> entityMovementTrackers,
//
// // Block ChunkSection listeners
// Long2ReferenceOpenHashMap<ChunkSectionChangeCallback> chunkSectionChangeCallbacks
// ) {
// public Data(Level world) {
// this(
// new Long2ReferenceOpenHashMap<>(),
// world.registryAccess().lookup(Registries.BANNER_PATTERN).map(Raid::getLeaderBannerInstance).orElse(null),
// new ReferenceOpenHashSet<>(),
// new LithiumInterner<>(),
// new LithiumInterner<>(),
// new Long2ReferenceOpenHashMap<>()
// );
// }
// }
//
// LithiumData.Data lithium$getData();
//}

View File

@@ -0,0 +1,9 @@
package net.gensokyoreimagined.nitori.common.world;
import net.minecraft.world.entity.Mob;
public interface ServerWorldExtended {
void lithium$setNavigationActive(Mob mobEntity);
void lithium$setNavigationInactive(Mob mobEntity);
}

View File

@@ -0,0 +1,120 @@
package net.gensokyoreimagined.nitori.common.world;
import net.gensokyoreimagined.nitori.common.entity.EntityClassGroup;
import net.gensokyoreimagined.nitori.common.entity.pushable.EntityPushablePredicate;
import net.gensokyoreimagined.nitori.mixin.util.accessors.EntityTrackingSectionAccessor;
import net.gensokyoreimagined.nitori.common.world.chunk.ClassGroupFilterableList;
import net.minecraft.core.BlockPos;
import net.minecraft.util.ClassInstanceMultiMap;
import net.minecraft.util.AbortableIterationConsumer;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.level.EntityGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.entity.EntitySectionStorage;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
public class WorldHelper {
public static final boolean CUSTOM_TYPE_FILTERABLE_LIST_DISABLED = !ClassGroupFilterableList.class.isAssignableFrom(ClassInstanceMultiMap.class);
/**
* Partial [VanillaCopy]
* The returned entity iterator is only used for collision interactions. As most entities do not collide with other
* entities (cramming is different), getting them is not necessary. This is why we only get entities when they override
* {@link Entity#canBeCollidedWith()} if the reference entity does not override {@link Entity#canCollideWith(Entity)}.
* Note that the returned iterator contains entities that override these methods. This does not mean that these methods
* always return true.
*
* @param entityView the world
* @param box the box the entities have to collide with
* @param collidingEntity the entity that is searching for the colliding entities
* @return iterator of entities with collision boxes
*/
public static List<Entity> getEntitiesForCollision(EntityGetter entityView, AABB box, Entity collidingEntity) {
if (!CUSTOM_TYPE_FILTERABLE_LIST_DISABLED && entityView instanceof Level world && (collidingEntity == null || !EntityClassGroup.CUSTOM_COLLIDE_LIKE_MINECART_BOAT_WINDCHARGE.contains(collidingEntity.getClass()))) {
EntitySectionStorage<Entity> cache = getEntityCacheOrNull(world);
if (cache != null) {
world.getProfiler().incrementCounter("getEntities");
return getEntitiesOfClassGroup(cache, collidingEntity, EntityClassGroup.NoDragonClassGroup.BOAT_SHULKER_LIKE_COLLISION, box);
}
}
//use vanilla code in case the shortcut is not applicable
// due to the reference entity implementing special collision or the mixin being disabled in the config
return entityView.getEntities(collidingEntity, box);
}
public static List<Entity> getOtherEntitiesForCollision(EntityGetter entityView, AABB box, @Nullable Entity collidingEntity, Predicate<? super Entity> entityPredicate) {
if (!CUSTOM_TYPE_FILTERABLE_LIST_DISABLED && entityView instanceof Level world) {
if (collidingEntity == null || !EntityClassGroup.CUSTOM_COLLIDE_LIKE_MINECART_BOAT_WINDCHARGE.contains(collidingEntity.getClass())) {
EntitySectionStorage<Entity> cache = getEntityCacheOrNull(world);
if (cache != null) {
world.getProfiler().incrementCounter("getEntities");
return getEntitiesOfClassGroup(cache, collidingEntity, EntityClassGroup.NoDragonClassGroup.BOAT_SHULKER_LIKE_COLLISION, box);
}
}
}
//use vanilla code in case the shortcut is not applicable
// due to the reference entity implementing special collision or the mixin being disabled in the config
return entityView.getEntities(collidingEntity, box, entityPredicate);
}
//Requires util.accessors
public static EntitySectionStorage<Entity> getEntityCacheOrNull(Level world) {
// Does not work on Paper...
// if (world instanceof ServerWorldAccessor) {
// //noinspection unchecked
// return ((ServerEntityManagerAccessor<Entity>) ((ServerWorldAccessor) world).getEntityManager()).getSectionStorage();
// }
return null;
}
public static List<Entity> getEntitiesOfClassGroup(EntitySectionStorage<Entity> cache, Entity collidingEntity, EntityClassGroup.NoDragonClassGroup entityClassGroup, AABB box) {
ArrayList<Entity> entities = new ArrayList<>();
cache.forEachAccessibleNonEmptySection(box, section -> {
//noinspection unchecked
ClassInstanceMultiMap<Entity> allEntities = ((EntityTrackingSectionAccessor<Entity>) section).getStorage();
//noinspection unchecked
Collection<Entity> entitiesOfType = ((ClassGroupFilterableList<Entity>) allEntities).lithium$getAllOfGroupType(entityClassGroup);
if (!entitiesOfType.isEmpty()) {
for (Entity entity : entitiesOfType) {
if (entity.getBoundingBox().intersects(box) && !entity.isSpectator() && entity != collidingEntity) {
//skip the dragon piece check without issues by only allowing EntityClassGroup.NoDragonClassGroup as type
entities.add(entity);
}
}
}
return AbortableIterationConsumer.Continuation.CONTINUE;
});
return entities;
}
public static List<Entity> getPushableEntities(Level world, EntitySectionStorage<Entity> cache, Entity except, AABB box, EntityPushablePredicate<? super Entity> entityPushablePredicate) {
ArrayList<Entity> entities = new ArrayList<>();
cache.forEachAccessibleNonEmptySection(box, section -> ((ClimbingMobCachingSection) section).lithium$collectPushableEntities(world, except, box, entityPushablePredicate, entities));
return entities;
}
public static boolean areNeighborsWithinSameChunk(BlockPos pos) {
int localX = pos.getX() & 15;
int localZ = pos.getZ() & 15;
return localX > 0 && localZ > 0 && localX < 15 && localZ < 15;
}
public static boolean areNeighborsWithinSameChunkSection(int x, int y, int z) {
int localX = x & 15;
int localY = y & 15;
int localZ = z & 15;
return localX > 0 && localY > 0 && localZ > 0 && localX < 15 && localY < 15 && localZ < 15;
}
public static boolean arePosWithinSameChunk(BlockPos pos1, BlockPos pos2) {
return pos1.getX() >> 4 == pos2.getX() >> 4 && pos1.getZ() >> 4 == pos2.getZ() >> 4;
}
}

View File

@@ -12,16 +12,11 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.access;
package net.gensokyoreimagined.nitori.common.world.blockentity;
import com.destroystokyo.paper.util.misc.PooledLinkedHashSets;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.core.BlockPos;
public interface IMixinChunkMap_TrackedEntityAccess {
@SuppressWarnings("EmptyMethod")
Entity getEntity();
@SuppressWarnings("EmptyMethod")
void callUpdatePlayers(PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newTrackerCandidates);
}
public interface BlockEntityGetter {
BlockEntity lithium$getLoadedExistingBlockEntity(BlockPos pos);
}

View File

@@ -0,0 +1,5 @@
package net.gensokyoreimagined.nitori.common.world.blockentity;
public interface SupportCache {
boolean lithium$isSupported();
}

View File

@@ -0,0 +1,142 @@
package net.gensokyoreimagined.nitori.common.world.blockview;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.core.BlockPos;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.CollisionGetter;
import net.minecraft.world.level.border.WorldBorder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Optional;
public record SingleBlockBlockView(BlockState state, BlockPos pos) implements BlockGetter, CollisionGetter {
public static SingleBlockBlockView of(BlockState blockState, BlockPos blockPos) {
return new SingleBlockBlockView(blockState, blockPos.immutable());
}
@Override
public BlockState getBlockState(BlockPos pos) {
if (pos.equals(this.pos())) {
return this.state();
} else {
throw SingleBlockViewException.INSTANCE;
}
}
@Override
public FluidState getFluidState(BlockPos pos) {
if (pos.equals(this.pos())) {
return this.state().getFluidState();
} else {
throw SingleBlockViewException.INSTANCE;
}
}
@Nullable
@Override
public BlockEntity getBlockEntity(BlockPos pos) {
throw SingleBlockViewException.INSTANCE;
}
@Override
public int getHeight() {
throw SingleBlockViewException.INSTANCE;
}
@Override
public int getMinBuildHeight() {
throw SingleBlockViewException.INSTANCE;
}
@Override
public WorldBorder getWorldBorder() {
throw SingleBlockViewException.INSTANCE;
}
@Nullable
@Override
public BlockGetter getChunkForCollisions(int chunkX, int chunkZ) {
throw SingleBlockViewException.INSTANCE;
}
@Override
public boolean isUnobstructed(@Nullable Entity except, VoxelShape shape) {
throw SingleBlockViewException.INSTANCE;
}
@Override
public boolean isUnobstructed(BlockState state, BlockPos pos, CollisionContext context) {
throw SingleBlockViewException.INSTANCE;
}
@Override
public boolean isUnobstructed(Entity entity) {
throw SingleBlockViewException.INSTANCE;
}
@Override
public boolean noCollision(@Nullable Entity entity, AABB box) {
throw SingleBlockViewException.INSTANCE;
}
@Override
public List<VoxelShape> getEntityCollisions(@Nullable Entity entity, AABB box) {
throw SingleBlockViewException.INSTANCE;
}
@Override
public Iterable<VoxelShape> getCollisions(@Nullable Entity entity, AABB box) {
throw SingleBlockViewException.INSTANCE;
}
@Override
public Iterable<VoxelShape> getBlockCollisions(@Nullable Entity entity, AABB box) {
throw SingleBlockViewException.INSTANCE;
}
@Override
public boolean collidesWithSuffocatingBlock(@Nullable Entity entity, AABB box) {
throw SingleBlockViewException.INSTANCE;
}
@Override
public Optional<Vec3> findFreePosition(@Nullable Entity entity, VoxelShape shape, Vec3 target, double x, double y, double z) {
throw SingleBlockViewException.INSTANCE;
}
public static class SingleBlockViewException extends RuntimeException {
public static final SingleBlockViewException INSTANCE = new SingleBlockViewException();
private SingleBlockViewException() {
this.setStackTrace(new StackTraceElement[0]);
}
@Override
public synchronized Throwable fillInStackTrace() {
this.setStackTrace(new StackTraceElement[0]);
return this;
}
}
@javax.annotation.Nullable
@Override
public BlockState getBlockStateIfLoaded(@NotNull BlockPos block) {
throw SingleBlockViewException.INSTANCE;
}
@javax.annotation.Nullable
@Override
public FluidState getFluidIfLoaded(@NotNull BlockPos block) {
throw SingleBlockViewException.INSTANCE;
}
}

View File

@@ -0,0 +1,26 @@
package net.gensokyoreimagined.nitori.common.world.chunk;
import net.minecraft.server.level.FullChunkStatus;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import java.util.ArrayList;
import java.util.function.BiConsumer;
public class ChunkStatusTracker {
//Add other callback types in the future when needed
private static final ArrayList<BiConsumer<ServerLevel, ChunkPos>> UNLOAD_CALLBACKS = new ArrayList<>();
public static void onChunkStatusChange(ServerLevel serverWorld, ChunkPos pos, FullChunkStatus levelType) {
boolean loaded = levelType.isOrAfter(FullChunkStatus.FULL);
if (!loaded) {
for (int i = 0; i < UNLOAD_CALLBACKS.size(); i++) {
UNLOAD_CALLBACKS.get(i).accept(serverWorld, pos);
}
}
}
public static void registerUnloadCallback(BiConsumer<ServerLevel, ChunkPos> callback) {
UNLOAD_CALLBACKS.add(callback);
}
}

View File

@@ -0,0 +1,8 @@
package net.gensokyoreimagined.nitori.common.world.chunk;
import net.gensokyoreimagined.nitori.common.entity.EntityClassGroup;
import java.util.Collection;
public interface ClassGroupFilterableList<T> {
Collection<T> lithium$getAllOfGroupType(EntityClassGroup type);
}

View File

@@ -0,0 +1,199 @@
package net.gensokyoreimagined.nitori.common.world.chunk;
//import com.google.common.collect.ImmutableList;
//import it.unimi.dsi.fastutil.HashCommon;
//import it.unimi.dsi.fastutil.objects.Reference2IntMap;
//import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
//import net.gensokyoreimagined.nitori.mixin.removed.palette.PaletteResizeAccessor;
//import net.minecraft.network.FriendlyByteBuf;
//import net.minecraft.network.VarInt;
//import net.minecraft.core.IdMap;
//import net.minecraft.world.level.chunk.MissingPaletteEntryException;
//import net.minecraft.world.level.chunk.Palette;
//import org.jetbrains.annotations.NotNull;
//
//import java.util.Arrays;
//import java.util.List;
//import java.util.function.Predicate;
//
//import static it.unimi.dsi.fastutil.Hash.FAST_LOAD_FACTOR;
//
///**
//* Generally provides better performance over the vanilla {@link net.minecraft.world.level.chunk.HashMapPalette} when calling
//* {@link LithiumHashPalette#idFor(Object)} through using a faster backing map and reducing pointer chasing.
//*/
//public class LithiumHashPalette<T> implements Palette<T> {
// private static final int ABSENT_VALUE = -1;
//
// private final IdMap<T> idList;
// // private final PaletteResize<T> resizeHandler;
// private final PaletteResizeAccessor<T> resizeHandler;
// private final int indexBits;
//
// private final Reference2IntMap<T> table;
// private T[] entries;
// private int size = 0;
//
// public LithiumHashPalette(IdMap<T> idList, PaletteResizeAccessor<T> resizeHandler, int indexBits, T[] entries, Reference2IntMap<T> table, int size) {
// this.idList = idList;
// this.resizeHandler = resizeHandler;
// this.indexBits = indexBits;
// this.entries = entries;
// this.table = table;
// this.size = size;
// }
//
// public LithiumHashPalette(IdMap<T> idList, int bits, PaletteResizeAccessor<T> resizeHandler, List<T> list) {
// this(idList, bits, resizeHandler);
//
// for (T t : list) {
// this.addEntry(t);
// }
// }
//
// @SuppressWarnings("unchecked")
// public LithiumHashPalette(IdMap<T> idList, int bits, PaletteResizeAccessor<T> resizeHandler) {
// this.idList = idList;
// this.indexBits = bits;
// this.resizeHandler = resizeHandler;
//
// int capacity = 1 << bits;
//
// this.entries = (T[]) new Object[capacity];
// this.table = new Reference2IntOpenHashMap<>(capacity, FAST_LOAD_FACTOR);
// this.table.defaultReturnValue(ABSENT_VALUE);
// }
//
// @Override
// public int idFor(T obj) {
// int id = this.table.getInt(obj);
//
// if (id == ABSENT_VALUE) {
// id = this.computeEntry(obj);
// }
//
// return id;
// }
//
// @Override
// public boolean maybeHas(Predicate<T> predicate) {
// for (int i = 0; i < this.size; ++i) {
// if (predicate.test(this.entries[i])) {
// return true;
// }
// }
//
// return false;
// }
//
// private int computeEntry(T obj) {
// int id = this.addEntry(obj);
//
// if (id >= 1 << this.indexBits) {
// if (this.resizeHandler == null) {
// throw new IllegalStateException("Cannot grow");
// } else {
// id = this.resizeHandler.callOnResize(this.indexBits + 1, obj);
// }
// }
//
// return id;
// }
//
// private int addEntry(T obj) {
// int nextId = this.size;
//
// if (nextId >= this.entries.length) {
// this.resize(this.size);
// }
//
// this.table.put(obj, nextId);
// this.entries[nextId] = obj;
//
// this.size++;
//
// return nextId;
// }
//
// private void resize(int neededCapacity) {
// this.entries = Arrays.copyOf(this.entries, HashCommon.nextPowerOfTwo(neededCapacity + 1));
// }
//
// @Override
// public @NotNull T valueFor(int id) {
// T[] entries = this.entries;
//
// T entry = null;
// if (id >= 0 && id < entries.length) {
// entry = entries[id];
// }
//
// if (entry != null) {
// return entry;
// } else {
// throw new MissingPaletteEntryException(id);
// }
// }
//
// @Override
// public void read(FriendlyByteBuf buf) {
// this.clear();
//
// int entryCount = buf.readVarInt();
//
// for (int i = 0; i < entryCount; ++i) {
// this.addEntry(this.idList.byId(buf.readVarInt()));
// }
// }
//
// @Override
// public void write(FriendlyByteBuf buf) {
// int size = this.size;
// buf.writeVarInt(size);
//
// for (int i = 0; i < size; ++i) {
// buf.writeVarInt(this.idList.getId(this.valueFor(i)));
// }
// }
//
// @Override
// public int getSerializedSize() {
// int size = VarInt.getByteSize(this.size);
//
// for (int i = 0; i < this.size; ++i) {
// size += VarInt.getByteSize(this.idList.getId(this.valueFor(i)));
// }
//
// return size;
// }
//
// @Override
// public int getSize() {
// return this.size;
// }
//
// @Override
// public Palette<T> copy() {
// return new LithiumHashPalette<>(this.idList, this.resizeHandler, this.indexBits, this.entries.clone(), new Reference2IntOpenHashMap<>(this.table), this.size);
// }
//
// private void clear() {
// Arrays.fill(this.entries, null);
// this.table.clear();
// this.size = 0;
// }
//
// public List<T> getElements() {
// ImmutableList.Builder<T> builder = new ImmutableList.Builder<>();
// for (T entry : this.entries) {
// if (entry != null) {
// builder.add(entry);
// }
// }
// return builder.build();
// }
//
// public static <A> Palette<A> create(int bits, IdMap<A> idList, PaletteResizeAccessor<A> listener, List<A> list) {
// return new LithiumHashPalette<>(idList, bits, listener, list);
// }
//}

View File

@@ -0,0 +1,14 @@
package net.gensokyoreimagined.nitori.common.world.interests;
import net.minecraft.core.Holder;
import net.minecraft.world.entity.ai.village.poi.PoiRecord;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import net.minecraft.world.entity.ai.village.poi.PoiType;
import java.util.function.Consumer;
import java.util.function.Predicate;
public interface PointOfInterestSetExtended {
void lithium$collectMatchingPoints(Predicate<Holder<PoiType>> type, PoiManager.Occupancy status,
Consumer<PoiRecord> consumer);
}

View File

@@ -0,0 +1,19 @@
package net.gensokyoreimagined.nitori.common.world.interests.iterator;
import net.minecraft.core.Holder;
import net.minecraft.world.entity.ai.village.poi.PoiType;
import java.util.function.Predicate;
public record SinglePointOfInterestTypeFilter(
Holder<PoiType> type) implements Predicate<Holder<PoiType>> {
@Override
public boolean test(Holder<PoiType> other) {
return this.type == other;
}
public Holder<PoiType> getType() {
return this.type;
}
}

View File

@@ -0,0 +1,24 @@
package net.gensokyoreimagined.nitori.common.world.interests.types;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunkSection;
import java.util.Set;
import java.util.function.Predicate;
public class PointOfInterestTypeHelper {
private static Predicate<BlockState> POI_BLOCKSTATE_PREDICATE;
public static void init(Set<BlockState> types) {
if (POI_BLOCKSTATE_PREDICATE != null) {
throw new IllegalStateException("Already initialized");
}
POI_BLOCKSTATE_PREDICATE = types::contains;
}
public static boolean mayHavePoi(LevelChunkSection chunkSection) {
return chunkSection.maybeHas(POI_BLOCKSTATE_PREDICATE);
}
}

View File

@@ -0,0 +1,192 @@
package net.gensokyoreimagined.nitori.common.world.scheduler;
// ChunkTickSchedulerMixin needs this
//import it.unimi.dsi.fastutil.HashCommon;
//import net.minecraft.world.ticks.ScheduledTick;
//
//import java.util.*;
//
//public class OrderedTickQueue<T> extends AbstractQueue<ScheduledTick<T>> {
// private static final int INITIAL_CAPACITY = 16;
// private static final Comparator<ScheduledTick<?>> COMPARATOR = Comparator.comparingLong(ScheduledTick::subTickOrder);
//
// private ScheduledTick<T>[] arr;
//
// private int lastIndexExclusive;
// private int firstIndex;
//
// private long currentMaxSubTickOrder = Long.MIN_VALUE;
// private boolean isSorted;
// private ScheduledTick<T> unsortedPeekResult;
//
// @SuppressWarnings("unchecked")
// public OrderedTickQueue(int capacity) {
// this.arr = (ScheduledTick<T>[]) new ScheduledTick[capacity];
// this.lastIndexExclusive = 0;
// this.isSorted = true;
// this.unsortedPeekResult = null;
// this.firstIndex = 0;
// }
//
// public OrderedTickQueue() {
// this(INITIAL_CAPACITY);
// }
//
// @Override
// public void clear() {
// Arrays.fill(this.arr, null);
// this.lastIndexExclusive = 0;
// this.firstIndex = 0;
// this.currentMaxSubTickOrder = Long.MIN_VALUE;
// this.isSorted = true;
// this.unsortedPeekResult = null;
// }
//
// @Override
// public Iterator<ScheduledTick<T>> iterator() {
// if (this.isEmpty()) {
// return Collections.emptyIterator();
// }
// this.sort();
// return new Iterator<>() {
// int nextIndex = OrderedTickQueue.this.firstIndex;
//
// @Override
// public boolean hasNext() {
// return this.nextIndex < OrderedTickQueue.this.lastIndexExclusive;
// }
//
// @Override
// public ScheduledTick<T> next() {
// return OrderedTickQueue.this.arr[this.nextIndex++];
// }
// };
// }
//
// @Override
// public ScheduledTick<T> poll() {
// if (this.isEmpty()) {
// return null;
// }
// if (!this.isSorted) {
// this.sort();
// }
// ScheduledTick<T> nextTick;
// int polledIndex = this.firstIndex++;
// ScheduledTick<T>[] ticks = this.arr;
// nextTick = ticks[polledIndex];
// ticks[polledIndex] = null;
// return nextTick;
// }
//
// @Override
// public ScheduledTick<T> peek() {
// if (!this.isSorted) {
// return this.unsortedPeekResult;
// } else if (this.lastIndexExclusive > this.firstIndex) {
// return this.getTickAtIndex(this.firstIndex);
// }
// return null;
// }
//
// public boolean offer(ScheduledTick<T> tick) {
// if (this.lastIndexExclusive >= this.arr.length) {
// //todo remove consumed elements first
// this.arr = copyArray(this.arr, HashCommon.nextPowerOfTwo(this.arr.length + 1));
// }
// if (tick.subTickOrder() <= this.currentMaxSubTickOrder) {
// //Set to unsorted instead of slowing down the insertion
// //This is rare but may happen in bulk
// //Sorting later needs O(n*log(n)) time, but it only needs to happen when unordered insertion needs to happen
// //Therefore it is better than n times log(n) time of the PriorityQueue that happens on ordered insertion too
// ScheduledTick<T> firstTick = this.isSorted ? this.size() > 0 ? this.arr[this.firstIndex] : null : this.unsortedPeekResult;
// this.isSorted = false;
// this.unsortedPeekResult = firstTick == null || tick.subTickOrder() < firstTick.subTickOrder() ? tick : firstTick;
// } else {
// this.currentMaxSubTickOrder = tick.subTickOrder();
// }
// this.arr[this.lastIndexExclusive++] = tick;
// return true;
// }
//
// public int size() {
// return this.lastIndexExclusive - this.firstIndex;
// }
//
// private void handleCompaction(int size) {
// // Only compact the array if it is less than 50% filled
// if (this.arr.length > INITIAL_CAPACITY && size < this.arr.length / 2) {
// this.arr = copyArray(this.arr, size);
// } else {
// // Fill the unused array elements with nulls to release our references to the elements in it
// Arrays.fill(this.arr, size, this.arr.length, null);
// }
//
// this.firstIndex = 0;
// this.lastIndexExclusive = size;
//
// if (size == 0 || !this.isSorted) {
// this.currentMaxSubTickOrder = Long.MIN_VALUE;
// } else {
// ScheduledTick<T> tick = this.arr[size - 1];
// this.currentMaxSubTickOrder = tick == null ? Long.MIN_VALUE : tick.subTickOrder();
// }
// }
//
// public void sort() {
// if (this.isSorted) {
// return;
// }
// this.removeNullsAndConsumed();
// Arrays.sort(this.arr, this.firstIndex, this.lastIndexExclusive, COMPARATOR);
// this.isSorted = true;
// this.unsortedPeekResult = null;
// }
//
// public void removeNullsAndConsumed() {
// int src = this.firstIndex;
// int dst = 0;
// while (src < this.lastIndexExclusive) {
// ScheduledTick<T> ScheduledTick = this.arr[src];
// if (ScheduledTick != null) {
// this.arr[dst] = ScheduledTick;
// dst++;
// }
// src++;
// }
// this.handleCompaction(dst);
// }
//
// public ScheduledTick<T> getTickAtIndex(int index) {
// if (!this.isSorted) {
// throw new IllegalStateException("Unexpected access on unsorted queue!");
// }
// return this.arr[index];
// }
//
// public void setTickAtIndex(int index, ScheduledTick<T> tick) {
// if (!this.isSorted) {
// throw new IllegalStateException("Unexpected access on unsorted queue!");
// }
// this.arr[index] = tick;
// }
//
// @SuppressWarnings("unchecked")
// private static <T> ScheduledTick<T>[] copyArray(ScheduledTick<T>[] src, int size) {
// final ScheduledTick<T>[] copy = new ScheduledTick[Math.max(INITIAL_CAPACITY, size)];
//
// if (size != 0) {
// System.arraycopy(src, 0, copy, 0, Math.min(src.length, size));
// }
//
// return copy;
// }
//
// @Override
// public boolean isEmpty() {
// return this.lastIndexExclusive <= this.firstIndex;
// }
//}

View File

@@ -22,7 +22,6 @@ import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier;
/**

View File

@@ -24,9 +24,9 @@ import java.util.Map;
public final class PluginCompatibilityCitizens extends BasePluginCompatibility {
private static final String CITIZENS_PLUGIN_NAME = "Citizens";
private static final ClassReflectionReferenceResolver citizensPluginCitizensEntityTrackerClassResolver = new ClassReflectionReferenceResolver("net.citizensnpcs.nms.v1_20_R3.util.CitizensEntityTracker");
private static final ClassReflectionReferenceResolver citizensPluginCitizensEntityTrackerClassResolver = new ClassReflectionReferenceResolver("net.citizensnpcs.nms.v1_20_R4.util.CitizensEntityTracker");
private static final ClassReflectionReferenceResolver citizensPluginEntityHumanNPCClassResolver = new ClassReflectionReferenceResolver("net.citizensnpcs.nms.v1_20_R3.entity.EntityHumanNPC");
private static final ClassReflectionReferenceResolver citizensPluginEntityHumanNPCClassResolver = new ClassReflectionReferenceResolver("net.citizensnpcs.nms.v1_20_R4.entity.EntityHumanNPC");
PluginCompatibilityCitizens() {
super(new String[]{CITIZENS_PLUGIN_NAME});

View File

@@ -1,170 +0,0 @@
// Nitori Copyright (C) 2024 Gensokyo Reimagined
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.core;
import com.destroystokyo.paper.util.misc.PooledLinkedHashSets;
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
import it.unimi.dsi.fastutil.objects.ReferenceSets;
//import net.gensokyoreimagined.nitori.config.NitoriConfig;
import net.gensokyoreimagined.nitori.access.IMixinChunkMapAccess;
import net.gensokyoreimagined.nitori.access.IMixinChunkMap_TrackedEntityAccess;
import net.gensokyoreimagined.nitori.compatibility.PluginCompatibilityRegistry;
import net.gensokyoreimagined.nitori.tracker.MultithreadedTracker;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerEntity;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerPlayerConnection;
import net.minecraft.world.entity.Entity;
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.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
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 java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import javax.annotation.Nullable;
@Mixin(ChunkMap.class)
public class ChunkMapMixin implements IMixinChunkMapAccess {
@Mutable
@Shadow @Final public Int2ObjectMap<ChunkMap.TrackedEntity> entityMap;
// Implementation of 0107-Multithreaded-Tracker.patch
@Final
@Shadow
public ServerLevel level;
// Implementation of 0107-Multithreaded-Tracker.patch
@Unique
private @Nullable MultithreadedTracker gensouHacks$multithreadedTracker;
// Implementation of 0107-Multithreaded-Tracker.patch
@Final
@Unique
private final ConcurrentLinkedQueue<Runnable> gensouHacks$trackerMainThreadTasks = new ConcurrentLinkedQueue<>();
// Implementation of 0107-Multithreaded-Tracker.patch
@Unique
private boolean gensouHacks$tracking = false;
@Inject(method = "<init>", at = @At("RETURN"))
private void reassignEntityTrackers(CallbackInfo ci) {
this.entityMap = new Int2ObjectLinkedOpenHashMap<>();
}
// Implementation of 0107-Multithreaded-Tracker.patch
@Override
@Unique
public void gensouHacks$runOnTrackerMainThread(final Runnable runnable) {
if (this.gensouHacks$tracking) {
this.gensouHacks$trackerMainThreadTasks.add(runnable);
} else {
runnable.run();
}
}
@Inject(method = "processTrackQueue", at = @At("HEAD"), cancellable = true)
private void atProcessTrackQueueHead(CallbackInfo callbackInfo) {
// Implementation of 0107-Multithreaded-Tracker.patch
//TODO: Restore config condition
//if (NitoriConfig.enableAsyncEntityTracker) {
if (this.gensouHacks$multithreadedTracker == null) {
this.gensouHacks$multithreadedTracker = new MultithreadedTracker(this.level.chunkSource.entityTickingChunks, this.gensouHacks$trackerMainThreadTasks);
}
this.gensouHacks$tracking = true;
try {
this.gensouHacks$multithreadedTracker.tick();
} finally {
this.gensouHacks$tracking = false;
}
callbackInfo.cancel();
//}
// Mirai end
}
@Mixin(ChunkMap.TrackedEntity.class)
public static abstract class TrackedEntity implements IMixinChunkMap_TrackedEntityAccess {
// Implementation of 0107-Multithreaded-Tracker.patch
@Override
@Final
@Accessor
public abstract Entity getEntity(); // Mirai -> public
@Final
@Mutable
@Shadow
public Set<ServerPlayerConnection> seenBy;
@Shadow
public abstract void updatePlayer(ServerPlayer player);
@Inject(method = "<init>", at = @At("RETURN"))
private void reassignSeenBy(CallbackInfo ci) {
// Implementation of 0107-Multithreaded-Tracker.patch
this.seenBy = ReferenceSets.synchronize(new ReferenceOpenHashSet<>()); // Mirai - sync
}
// Implementation of 0107-Multithreaded-Tracker.patch
@Override
@Final
@Invoker
public abstract void callUpdatePlayers(PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newTrackerCandidates); // Mirai -> public
@Redirect(method = "updatePlayers(Lcom/destroystokyo/paper/util/misc/PooledLinkedHashSets$PooledObjectLinkedOpenHashSet;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ChunkMap$TrackedEntity;updatePlayer(Lnet/minecraft/server/level/ServerPlayer;)V"))
private void handleCitizensPluginTracking(ChunkMap.TrackedEntity self, ServerPlayer serverPlayer) {
// Nitori - Citizens tracker must run on the main thread to avoid cyclic wait
if (PluginCompatibilityRegistry.CITIZENS.shouldRedirectToMainThread(self, serverPlayer)) {
((IMixinChunkMapAccess) (Object) ((ServerLevel) serverPlayer.level()).chunkSource.chunkMap).gensouHacks$runOnTrackerMainThread(() ->
this.updatePlayer(serverPlayer)
);
} else {
this.updatePlayer(serverPlayer);
}
}
// Implementation of 0107-Multithreaded-Tracker.patch
@SuppressWarnings("EmptyMethod")
@Redirect(method = "removePlayer", at = @At(value = "INVOKE", target = "Lorg/spigotmc/AsyncCatcher;catchOp(Ljava/lang/String;)V"))
private void skipSpigotAsyncPlayerTrackerClear(String reason) {} // Mirai - we can remove async too
// Implementation of 0107-Multithreaded-Tracker.patch
@SuppressWarnings("EmptyMethod")
@Redirect(method = "updatePlayer", at = @At(value = "INVOKE", target = "Lorg/spigotmc/AsyncCatcher;catchOp(Ljava/lang/String;)V"))
private void skipSpigotAsyncPlayerTrackerUpdate(String reason) {} // Mirai - we can update async
@Redirect(method = "updatePlayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerEntity;addPairing(Lnet/minecraft/server/level/ServerPlayer;)V"))
private void handleTrainCartsPluginAddPairing(ServerEntity self, ServerPlayer serverPlayer) {
if (PluginCompatibilityRegistry.TRAIN_CARTS.shouldRedirectToMainThread((ChunkMap.TrackedEntity) (Object) this)) {
((IMixinChunkMapAccess) (Object) ((ServerLevel) serverPlayer.level()).chunkSource.chunkMap).gensouHacks$runOnTrackerMainThread(() ->
self.addPairing(serverPlayer)
);
} else {
self.addPairing(serverPlayer);
}
}
}
}

View File

@@ -1,46 +0,0 @@
// Nitori Copyright (C) 2024 Gensokyo Reimagined
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.core;
import net.minecraft.core.Direction;
import net.minecraft.util.RandomSource;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(Direction.class)
public class MixinDirection {
@Shadow @Final private static Direction[] VALUES;
@Shadow @Final private int oppositeIndex;
/**
* @author DoggySazHi
* @reason Implementation of 0005-lithium-fast-util.patch, requires a overwrite to avoid calling `from3DDataValue`
*/
@Overwrite
public Direction getOpposite() {
return VALUES[this.oppositeIndex];
}
/**
* @author DoggySazHi
* @reason Implementation of 0005-lithium-fast-util.patch, requires a overwrite to avoid calling `Util.getRandom`
*/
@Overwrite
public static Direction getRandom(RandomSource random) {
return VALUES[random.nextInt(VALUES.length)];
}
}

View File

@@ -1,30 +0,0 @@
// Nitori Copyright (C) 2024 Gensokyo Reimagined
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.core;
import io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet;
import net.gensokyoreimagined.nitori.access.IMixinIteratorSafeOrderedReferenceSetAccess;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(IteratorSafeOrderedReferenceSet.class)
public abstract class MixinIteratorSafeOrderedReferenceSet implements IMixinIteratorSafeOrderedReferenceSetAccess {
// Implementation of 0107-Multithreaded-Tracker.patch
// @Accessor happens to do exactly what is needed, making for nice shorthand
@Override
@Accessor
public abstract int getListSize(); // Mirai - expose listSize
}

View File

@@ -1,49 +0,0 @@
// Nitori Copyright (C) 2024 Gensokyo Reimagined
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.core;
import com.google.common.collect.Lists;
import net.gensokyoreimagined.nitori.common.util.collections.HashedReferenceList;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.TickingBlockEntity;
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.List;
@Mixin(Level.class)
public class MixinLevel {
// Implementation of 0006-lithium-HashedReferenceList.patch
@Mutable
@Final @Shadow
public List<TickingBlockEntity> blockEntityTickers;
// Implementation of 0006-lithium-HashedReferenceList.patch
@Mutable
@Final @Shadow
private List<TickingBlockEntity> pendingBlockEntityTickers;
// Implementation of 0006-lithium-HashedReferenceList.patch
@Inject(method = "<init>", at = @At("RETURN"))
private void onInit(CallbackInfo ci) {
this.blockEntityTickers = new HashedReferenceList<>(Lists.newArrayList());
this.pendingBlockEntityTickers = new HashedReferenceList<>(Lists.newArrayList());
}
}

View File

@@ -1,129 +0,0 @@
// Nitori Copyright (C) 2024 Gensokyo Reimagined
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.core;
import com.google.common.collect.Lists;
import com.llamalad7.mixinextras.sugar.Local;
import net.gensokyoreimagined.nitori.access.IMixinChunkMapAccess;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.protocol.game.ClientboundBundlePacket;
import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;
import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket;
import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerEntity;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
@Mixin(ServerEntity.class)
public abstract class MixinServerEntity {
@Final
@Shadow
private Entity entity;
@Shadow
public void sendPairingData(ServerPlayer serverplayer, Consumer<Packet<ClientGamePacketListener>> consumer) {
throw new AssertionError("Mixin failed to apply!");
}
// Implementation of 0107-Multithreaded-Tracker.patch
@SuppressWarnings("EmptyMethod")
@Redirect(method = "removePairing", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerGamePacketListenerImpl;send(Lnet/minecraft/network/protocol/Packet;)V"))
private void skipSendForOriginalRemovePairing(ServerGamePacketListenerImpl self, Packet<?> packet) {}
// Implementation of 0107-Multithreaded-Tracker.patch
@Inject(method = "removePairing", at = @At(value = "TAIL"))
private void invokeRemovePairingSendOnMain(ServerPlayer serverplayer, CallbackInfo callbackInfo) {
// Mirai start - ensure main thread
((IMixinChunkMapAccess) (Object) ((ServerLevel) this.entity.level()).chunkSource.chunkMap).gensouHacks$runOnTrackerMainThread(() ->
serverplayer.connection.send(new ClientboundRemoveEntitiesPacket(this.entity.getId()))
);
// Mirai end
}
// Implementation of 0107-Multithreaded-Tracker.patch
@SuppressWarnings("EmptyMethod")
@Redirect(method = "addPairing", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerEntity;sendPairingData(Lnet/minecraft/server/level/ServerPlayer;Ljava/util/function/Consumer;)V"))
private void skipSendPrepForOriginalAddPairing(ServerEntity self, ServerPlayer serverplayer, Consumer<Packet<ClientGamePacketListener>> consumer) {}
// Implementation of 0107-Multithreaded-Tracker.patch
@SuppressWarnings("EmptyMethod")
@Redirect(method = "addPairing", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerGamePacketListenerImpl;send(Lnet/minecraft/network/protocol/Packet;)V"))
private void skipSendForOriginalAddPairing(ServerGamePacketListenerImpl self, Packet<?> packet) {}
// Implementation of 0107-Multithreaded-Tracker.patch
@Inject(method = "addPairing", locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "FIELD", target = "Lnet/minecraft/server/level/ServerEntity;entity:Lnet/minecraft/world/entity/Entity;", opcode = Opcodes.GETFIELD, shift = At.Shift.BEFORE))
private void invokeAddPairingSendOnMain(ServerPlayer serverplayer, CallbackInfo callbackInfo, @Local List<Packet<ClientGamePacketListener>> list) {
((IMixinChunkMapAccess) (Object) ((ServerLevel) this.entity.level()).chunkSource.chunkMap).gensouHacks$runOnTrackerMainThread(() -> { // Mirai - main thread
this.sendPairingData(serverplayer, list::add);
serverplayer.connection.send(new ClientboundBundlePacket(list));
});
}
// Implementation of 0107-Multithreaded-Tracker.patch
@SuppressWarnings("EmptyMethod")
@Redirect(method = "sendDirtyEntityData", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerEntity;broadcastAndSend(Lnet/minecraft/network/protocol/Packet;)V"))
private void skipTrasmitForNonDefault(ServerEntity self, Packet<?> packet) {}
// Implementation of 0107-Multithreaded-Tracker.patch
@Inject(method = "sendDirtyEntityData", locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "FIELD", target = "Lnet/minecraft/server/level/ServerEntity;entity:Lnet/minecraft/world/entity/Entity;", opcode = Opcodes.GETFIELD, ordinal = 2, shift = At.Shift.BY, by = -4))
private void invokeSendForGenericDirtyEntityDataOnMain(CallbackInfo callbackInfo, @Local SynchedEntityData synchedentitydata, @Local List<SynchedEntityData.DataValue<?>> list) {
// Mirai start - sync
((IMixinChunkMapAccess) (Object) ((ServerLevel) this.entity.level()).chunkSource.chunkMap).gensouHacks$runOnTrackerMainThread(() ->
this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list))
);
// Mirai end
}
// stubbing of broadcastAndSend in if (this.entity instanceof LivingEntity) handled in skipTrasmitForNonDefault
// Implementation of 0107-Multithreaded-Tracker.patch
@Inject(method = "sendDirtyEntityData", at = @At(value = "INVOKE", target = "Ljava/util/Set;clear()V", shift = At.Shift.BY, by = -4))
private void invokeSendForLivingDirtyEntityDataOnMain(CallbackInfo callbackInfo, @Local Set<AttributeInstance> set) {
// Mirai start - sync
final var copy = Lists.newArrayList(set);
((IMixinChunkMapAccess) (Object) ((ServerLevel) this.entity.level()).chunkSource.chunkMap).gensouHacks$runOnTrackerMainThread(() -> {
// CraftBukkit start - Send scaled max health
if (this.entity instanceof ServerPlayer) {
((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(copy, false);
}
// CraftBukkit end
this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), copy));
});
// Mirai end
}
@Shadow
private void broadcastAndSend(Packet<?> packet) {
throw new AssertionError("Mixin failed to apply!");
}
}

View File

@@ -1,33 +0,0 @@
// Nitori Copyright (C) 2024 Gensokyo Reimagined
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.core;
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.callback.CallbackInfoReturnable;
@Mixin(targets = "gg.pufferfish.pufferfish.simd.SIMDDetection")
public abstract class MixinSpongeSIMD {
@Inject(method = "getJavaVersion", at = @At("RETURN"), cancellable = true)
private static void loadPufferfishConfig(CallbackInfoReturnable<Integer> callback) {
// We troll the Pufferfish developers by changing the return value of the method
// System.out.println("Thought that it was " + callback.getReturnValue() + " for java version");
if (callback.getReturnValue() == 21) {
System.out.println("Successfully trolled Pufferfish into thinking we're on Java 19!");
callback.setReturnValue(19);
}
}
}

View File

@@ -1,82 +0,0 @@
// Nitori Copyright (C) 2024 Gensokyo Reimagined
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.core;
import net.gensokyoreimagined.nitori.common.util.Pos;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkStatus;
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.List;
@Mixin(WorldGenRegion.class)
public class MixinWorldGenRegion {
@Shadow @Final private ChunkPos firstPos;
@Shadow @Final private int size;
@Unique
private ChunkAccess[] gensouHacks$chunksArr;
@Unique
private int gensouHacks$minChunkX;
@Unique
private int gensouHacks$minChunkZ;
@Inject(method = "<init>", at = @At("RETURN"))
private void onInit(ServerLevel world, List<ChunkAccess> chunks, ChunkStatus status, int placementRadius, CallbackInfo ci) {
this.gensouHacks$minChunkX = this.firstPos.x;
this.gensouHacks$minChunkZ = this.firstPos.z;
this.gensouHacks$chunksArr = chunks.toArray(new ChunkAccess[0]);
}
@Inject(method = "getChunk(II)Lnet/minecraft/world/level/chunk/ChunkAccess;", at = @At("HEAD"), cancellable = true)
public void getChunk(int chunkX, int chunkZ, CallbackInfoReturnable<ChunkAccess> cir) {
int x = chunkX - this.gensouHacks$minChunkX;
int z = chunkZ - this.gensouHacks$minChunkZ;
int w = this.size;
if (x >= 0 && z >= 0 && x < w && z < w) {
cir.setReturnValue(this.gensouHacks$chunksArr[x + z * w]);
cir.cancel();
} else {
throw new NullPointerException("No chunk exists at " + new ChunkPos(chunkX, chunkZ));
}
}
@Inject(method = "getBlockState", at = @At("HEAD"), cancellable = true)
public void getBlockState(BlockPos pos, CallbackInfoReturnable<BlockState> cir) {
int x = (Pos.ChunkCoord.fromBlockCoord(pos.getX())) - this.gensouHacks$minChunkX;
int z = (Pos.ChunkCoord.fromBlockCoord(pos.getZ())) - this.gensouHacks$minChunkZ;
int w = this.size;
if (x >= 0 && z >= 0 && x < w && z < w) {
cir.setReturnValue(this.gensouHacks$chunksArr[x + z * w].getBlockState(pos));
cir.cancel();
} else {
throw new NullPointerException("No chunk exists at " + new ChunkPos(pos));
}
}
}

View File

@@ -12,7 +12,7 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.core;
package net.gensokyoreimagined.nitori.mixin;
import it.unimi.dsi.fastutil.longs.LongList;
import net.gensokyoreimagined.nitori.cached_blockpos_iteration.IterateOutwardsCache;

View File

@@ -12,7 +12,7 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.core;
package net.gensokyoreimagined.nitori.mixin;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
@@ -39,7 +39,7 @@ public abstract class MixinEntity {
* Implementation of 0065-some-entity-micro-opts.patch
*/
@Unique
public float gensouHacks$getLightLevelDependentMagicValue(BlockPos pos) {
public float nitori$getLightLevelDependentMagicValue(BlockPos pos) {
return this.level.hasChunkAt(this.getBlockX(), this.getBlockZ()) ? this.level.getLightLevelDependentMagicValue(pos) : 0.0F;
}
@@ -48,7 +48,7 @@ public abstract class MixinEntity {
*/
@Inject(method = "getLightLevelDependentMagicValue", at = @At("HEAD"), cancellable = true)
private void getLightLevelDependentMagicValue(CallbackInfoReturnable<Float> cir) {
cir.setReturnValue(this.gensouHacks$getLightLevelDependentMagicValue(new BlockPos(this.getBlockX(), (int) this.getEyeY(), this.getBlockZ())));
cir.setReturnValue(this.nitori$getLightLevelDependentMagicValue(new BlockPos(this.getBlockX(), (int) this.getEyeY(), this.getBlockZ())));
cir.cancel();
}
}

View File

@@ -12,7 +12,7 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.core;
package net.gensokyoreimagined.nitori.mixin;
import com.llamalad7.mixinextras.sugar.Local;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
@@ -79,12 +79,12 @@ public class MixinEntitySectionStorage<T extends EntityAccess> {
// are placed somewhere inside the packed long
for (int x = j; x <= m; x++) {
for (int z = Math.max(l, 0); z <= o; z++) {
this.gensouHacks$forEachInColumn(x, k, n, z, consumer);
this.nitori$forEachInColumn(x, k, n, z, consumer);
}
int bound = Math.min(-1, o);
for (int z = l; z <= bound; z++) {
this.gensouHacks$forEachInColumn(x, k, n, z, consumer);
this.nitori$forEachInColumn(x, k, n, z, consumer);
}
}
}
@@ -95,19 +95,19 @@ public class MixinEntitySectionStorage<T extends EntityAccess> {
// Mirai start - lithium: fast retrieval
@Unique
private void gensouHacks$forEachInColumn(int x, int k, int n, int z, AbortableIterationConsumer<EntitySection<T>> action) {
private void nitori$forEachInColumn(int x, int k, int n, int z, AbortableIterationConsumer<EntitySection<T>> action) {
// y from negative to positive, but y is treated as unsigned
for (int y = Math.max(k, 0); y <= n; y++) {
this.gensouHacks$consumeSection(SectionPos.asLong(x, y, z), action);
this.nitori$consumeSection(SectionPos.asLong(x, y, z), action);
}
int bound = Math.min(-1, n);
for (int y = k; y <= bound; y++) {
this.gensouHacks$consumeSection(SectionPos.asLong(x, y, z), action);
this.nitori$consumeSection(SectionPos.asLong(x, y, z), action);
}
}
@Unique
private void gensouHacks$consumeSection(long pos, AbortableIterationConsumer<EntitySection<T>> action) {
private void nitori$consumeSection(long pos, AbortableIterationConsumer<EntitySection<T>> action) {
EntitySection<T> entitySection = this.getSection(pos);
if (entitySection != null && !entitySection.isEmpty() && entitySection.getStatus().isAccessible()) {
action.accept(entitySection);

View File

@@ -0,0 +1,70 @@
package net.gensokyoreimagined.nitori.mixin;
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.entity.EntityTickList;
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 org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.function.Consumer;
@Mixin(EntityTickList.class)
public class MixinEntityTickList {
@Unique
private Int2ObjectLinkedOpenHashMap<Entity> nitori$entities = new Int2ObjectLinkedOpenHashMap<>();
@Unique
private Int2ObjectLinkedOpenHashMap<Entity> nitori$iteratorPointer = null;
// I've decided I hate Paper's design.
// All of these are effectively Overwrites.
@Unique
private void nitori$ensureActiveIsNotIterated() {
if (nitori$iteratorPointer == nitori$entities) {
// Avoid a ConcurrentModificationException by cloning the map before modifying it.
// Side effect is that it allocates more memory to avoid blocking the main thread, but it's all pointers anyway.
nitori$entities = nitori$entities.clone();
}
}
@Inject(method = "add", at = @At("HEAD"), cancellable = true)
public void add(Entity entity, CallbackInfo ci) {
nitori$ensureActiveIsNotIterated();
nitori$entities.put(entity.getId(), entity);
ci.cancel();
}
@Inject(method = "remove", at = @At("HEAD"), cancellable = true)
public void remove(Entity entity, CallbackInfo ci) {
nitori$ensureActiveIsNotIterated();
nitori$entities.remove(entity.getId());
ci.cancel();
}
@Inject(method = "contains", at = @At("HEAD"), cancellable = true)
public void contains(Entity entity, CallbackInfoReturnable<Boolean> ci) {
ci.setReturnValue(nitori$entities.containsKey(entity.getId()));
ci.cancel();
}
@Inject(method = "forEach", at = @At("HEAD"), cancellable = true)
public void forEach(Consumer<Entity> action, CallbackInfo ci) {
if (nitori$iteratorPointer == nitori$entities) {
nitori$entities = nitori$entities.clone(); // Avoid a ConcurrentModificationException by cloning the map before iterating over it.
}
nitori$iteratorPointer = nitori$entities; // Mark the map as being iterated.
try {
nitori$iteratorPointer.values().forEach(action); // Iterate over the map.
} finally {
nitori$iteratorPointer = null; // Mark the map as no longer being iterated.
}
ci.cancel();
}
}

View File

@@ -12,7 +12,7 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.core;
package net.gensokyoreimagined.nitori.mixin;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;

View File

@@ -0,0 +1,51 @@
// Nitori Copyright (C) 2024 Gensokyo Reimagined
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.mixin;
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.callback.CallbackInfoReturnable;
import space.vectrix.ignite.Blackboard;
import java.net.URISyntaxException;
import java.nio.file.Path;
@Mixin(targets = "io.papermc.paper.pluginremap.ReobfServer")
public class MixinReobfServer {
@Inject(method = "serverJar", at = @At("HEAD"), cancellable = true)
private static void serverJar(CallbackInfoReturnable<Path> cir) {
try {
var igniteGameJarPath = Blackboard.get(Blackboard.GAME_JAR);
System.out.println("Nitori: ReobfServer.serverJar() called" + igniteGameJarPath);
if (igniteGameJarPath.isPresent()) {
System.out.println("Nitori: ReobfServer.serverJar() found Ignite, returning " + igniteGameJarPath.get());
cir.setReturnValue(igniteGameJarPath.get());
cir.cancel();
return;
}
// This is a fallback in case the game jar path is not set for some reason
var path = Path.of(MixinReobfServer.class.getProtectionDomain().getCodeSource().getLocation().toURI());
System.out.println("Nitori: ReobfServer.serverJar() tried loading, returning " + path);
cir.setReturnValue(path);
cir.cancel();
} catch (final URISyntaxException ex) {
throw new RuntimeException(ex);
}
}
}

View File

@@ -0,0 +1,34 @@
package net.gensokyoreimagined.nitori.mixin.alloc;
import net.minecraft.resources.ResourceLocation;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
@Mixin(ResourceLocation.class)
public class MixinIdentifier {
@Shadow @Final
private String namespace;
@Shadow @Final
private String path;
@Unique
private String nitori$cachedString = null;
/**
* @author ishland
* @reason cache toString
*/
@Overwrite
public String toString() {
if (this.nitori$cachedString != null) return this.nitori$cachedString;
final String s = this.namespace + ":" + this.path;
this.nitori$cachedString = s;
return s;
}
}

View File

@@ -0,0 +1,39 @@
package net.gensokyoreimagined.nitori.mixin.alloc.biome_temprature_leak;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import it.unimi.dsi.fastutil.longs.Long2FloatLinkedOpenHashMap;
import net.minecraft.world.level.biome.Biome;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import java.util.function.Supplier;
@Mixin(Biome.class)
public abstract class Biome_threadLocalMixin {
/*
* Not only do I absolutely hate this garbage cache, cause it barely works at all.
* HOW did nobody at mojang notice that this thread local was not static, how on earth is a non-static private
* value suppose to run on multiple threads at the same time.
* So now its static so that it actually works like its suppose to, and you don't need to declare a billion of them
*
* This is a significant performance boost & reduces a lot of memory usage
*/
private static ThreadLocal<Long2FloatLinkedOpenHashMap> memoryLeakFix$betterTempCache;
@WrapOperation(
method = "<init>",
at = @At(
value = "INVOKE",
target = "Ljava/lang/ThreadLocal;withInitial(Ljava/util/function/Supplier;)Ljava/lang/ThreadLocal;"
)
)
private ThreadLocal<Long2FloatLinkedOpenHashMap> memoryLeakFix$useStaticThreadLocal(Supplier<?> supplier, Operation<ThreadLocal<Long2FloatLinkedOpenHashMap>> original) {
if (memoryLeakFix$betterTempCache == null) {
memoryLeakFix$betterTempCache = original.call(supplier);
}
return memoryLeakFix$betterTempCache;
}
}

View File

@@ -0,0 +1,49 @@
// Nitori Copyright (C) 2024 Gensokyo Reimagined
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.mixin.alloc.blockstate;
import com.google.common.collect.Table;
import net.gensokyoreimagined.nitori.common.state.FastImmutableTable;
import net.gensokyoreimagined.nitori.common.state.StatePropertyTableCache;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.StateHolder;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.material.Fluid;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
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;
@Mixin(StateHolder.class)
public class StateMixin<O, S> {
@Shadow
private Table<Property<?>, Comparable<?>, S> neighbours;
@Shadow
@Final
protected O owner;
@Inject(method = "populateNeighbours", at = @At("RETURN"))
private void postCreateWithTable(Map<Map<Property<?>, Comparable<?>>, S> states, CallbackInfo ci) {
if (this.owner instanceof Block || this.owner instanceof Fluid) {
this.neighbours = new FastImmutableTable<Property<?>, Comparable<?>, S>(this.neighbours, StatePropertyTableCache.getTableCache(this.owner));
}
}
}

View File

@@ -0,0 +1,23 @@
package net.gensokyoreimagined.nitori.mixin.alloc.chunk_random;
import net.gensokyoreimagined.nitori.common.world.ChunkRandomSource;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(Level.class)
public class WorldMixin implements ChunkRandomSource {
@Shadow
protected int randValue;
/**
* {@inheritDoc}
*/
@Override
public void nitori$getRandomPosInChunk(int x, int y, int z, int mask, BlockPos.MutableBlockPos out) {
this.randValue = this.randValue * 3 + 1013904223;
int rand = this.randValue >> 2;
out.set(x + (rand & 15), y + (rand >> 16 & mask), z + (rand >> 8 & 15));
}
}

View File

@@ -0,0 +1,46 @@
package net.gensokyoreimagined.nitori.mixin.alloc.composter;
import net.gensokyoreimagined.nitori.common.util.ArrayConstants;
import net.minecraft.world.WorldlyContainer;
import net.minecraft.core.Direction;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
public class ComposterMixin {
@Mixin(targets = "net.minecraft.world.level.block.ComposterBlock$InputContainer")
static abstract class ComposterBlockComposterInventoryMixin implements WorldlyContainer {
/**
* @author 2No2Name
* @reason avoid allocation
*/
@Overwrite
public int[] getSlotsForFace(Direction side) {
return side == Direction.UP ? ArrayConstants.ZERO : ArrayConstants.EMPTY;
}
}
@Mixin(targets = "net.minecraft.world.level.block.ComposterBlock$EmptyContainer")
static abstract class ComposterBlockDummyInventoryMixin implements WorldlyContainer {
/**
* @author 2No2Name
* @reason avoid allocation
*/
@Overwrite
public int[] getSlotsForFace(Direction side) {
return ArrayConstants.EMPTY;
}
}
@Mixin(targets = "net.minecraft.world.level.block.ComposterBlock$OutputContainer")
static abstract class ComposterBlockFullComposterInventoryMixin implements WorldlyContainer {
/**
* @author 2No2Name
* @reason avoid allocation
*/
@Overwrite
public int[] getSlotsForFace(Direction side) {
return side == Direction.DOWN ? ArrayConstants.ZERO : ArrayConstants.EMPTY;
}
}
}

View File

@@ -0,0 +1,6 @@
package net.gensokyoreimagined.nitori.mixin.alloc;
/*
/nbt/NbtCompoundMixin is not needed
*/

View File

@@ -12,7 +12,7 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.core;
package net.gensokyoreimagined.nitori.mixin.cached_hashcode;
import net.minecraft.core.Direction;
import net.minecraft.world.level.block.Block;
@@ -22,16 +22,8 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/*
* Originally from CaffeineMC, licensed under GNU Lesser General Public License v3.0
* See https://github.com/CaffeineMC/lithium-fabric for more information/sources
*/
@Mixin(Block.BlockStatePairKey.class)
public class MixinBlock {
@Unique
private int gensouHacks$hash;
public class BlockNeighborGroupMixin {
@Shadow
@Final
private BlockState first;
@@ -44,20 +36,27 @@ public class MixinBlock {
@Final
private Direction direction;
@Unique
private int nitori$hash;
/**
* @reason Initialize the object's hashcode and cache it
*/
@Inject(method = "<init>", at = @At("RETURN"))
private void init(CallbackInfo info) {
int hash = first.hashCode();
hash = 31 * hash + second.hashCode();
hash = 31 * hash + direction.hashCode();
this.gensouHacks$hash = hash;
private void generateHash(BlockState blockState_1, BlockState blockState_2, Direction direction_1, CallbackInfo ci) {
int hash = this.first.hashCode();
hash = 31 * hash + this.second.hashCode();
hash = 31 * hash + this.direction.hashCode();
this.nitori$hash = hash;
}
/**
* @author DoggySazHi
* @reason Implementation of 0045-lithium-cached-hashcode.patch
* @reason Uses the cached hashcode
* @author JellySquid
*/
@Overwrite
@Overwrite(remap = false)
public int hashCode() {
return this.gensouHacks$hash;
return this.nitori$hash;
}
}
}

View File

@@ -0,0 +1,86 @@
package net.gensokyoreimagined.nitori.mixin.chunk.entity_class_groups;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceArrayMap;
import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
import net.gensokyoreimagined.nitori.common.entity.EntityClassGroup;
import net.gensokyoreimagined.nitori.common.world.chunk.ClassGroupFilterableList;
import net.minecraft.world.entity.Entity;
import net.minecraft.util.ClassInstanceMultiMap;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Patches {@link ClassInstanceMultiMap} to allow grouping entities by arbitrary groups of classes instead of one class only.
*/
@Mixin(ClassInstanceMultiMap.class)
public abstract class TypeFilterableListMixin<T> implements ClassGroupFilterableList<T> {
@Shadow
@Final
private List<T> allInstances;
private final Reference2ReferenceArrayMap<EntityClassGroup, ReferenceLinkedOpenHashSet<T>> nitori$entitiesByGroup =
new Reference2ReferenceArrayMap<>();
/**
* Update our collections
*/
@ModifyVariable(method = "add(Ljava/lang/Object;)Z", at = @At("HEAD"), argsOnly = true)
public T add(T entity) {
for (Map.Entry<EntityClassGroup, ReferenceLinkedOpenHashSet<T>> entityGroupAndSet : this.nitori$entitiesByGroup.entrySet()) {
EntityClassGroup entityGroup = entityGroupAndSet.getKey();
if (entityGroup.contains(((Entity) entity).getClass())) {
entityGroupAndSet.getValue().add((entity));
}
}
return entity;
}
/**
* Update our collections
*/
@ModifyVariable(method = "remove(Ljava/lang/Object;)Z", at = @At("HEAD"), argsOnly = true)
public Object remove(Object o) {
for (ReferenceLinkedOpenHashSet<T> entitySet : this.nitori$entitiesByGroup.values()) {
//noinspection SuspiciousMethodCalls
entitySet.remove(o);
}
return o;
}
/**
* Get entities of a class group
*/
public Collection<T> lithium$getAllOfGroupType(EntityClassGroup type) {
Collection<T> collection = this.nitori$entitiesByGroup.get(type);
if (collection == null) {
collection = this.nitori$createAllOfGroupType(type);
}
return collection;
}
/**
* Start grouping by a new class group
*/
private Collection<T> nitori$createAllOfGroupType(EntityClassGroup type) {
ReferenceLinkedOpenHashSet<T> allOfType = new ReferenceLinkedOpenHashSet<>();
for (T entity : this.allInstances) {
if (type.contains(entity.getClass())) {
allOfType.add(entity);
}
}
this.nitori$entitiesByGroup.put(type, allOfType);
return allOfType;
}
}

View File

@@ -0,0 +1,38 @@
package net.gensokyoreimagined.nitori.mixin.collections.block_entity_tickers;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.levelgen.blending.BlendingData;
import net.minecraft.world.ticks.LevelChunkTicks;
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.Coerce;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Map;
@Mixin(LevelChunk.class)
public class WorldChunkMixin {
@Mutable
@Shadow
@Final
private Map<BlockPos, ?> tickersInLevel;
@Inject(
method = "<init>(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/chunk/UpgradeData;Lnet/minecraft/world/ticks/LevelChunkTicks;Lnet/minecraft/world/ticks/LevelChunkTicks;J[Lnet/minecraft/world/level/chunk/LevelChunkSection;Lnet/minecraft/world/level/chunk/LevelChunk$PostLoadProcessor;Lnet/minecraft/world/level/levelgen/blending/BlendingData;)V",
at = @At("TAIL")
)
@Coerce
private void createFastUtilMap(Level world, ChunkPos pos, UpgradeData upgradeData, LevelChunkTicks<?> blockTickScheduler, LevelChunkTicks<?> fluidTickScheduler, long inhabitedTime, LevelChunkSection[] sectionArrayInitializer, LevelChunk.PostLoadProcessor entityLoader, BlendingData blendingData, CallbackInfo ci) {
this.tickersInLevel = new Object2ObjectOpenHashMap<>();
}
}

View File

@@ -0,0 +1,53 @@
package net.gensokyoreimagined.nitori.mixin.collections.brain;
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import net.minecraft.world.entity.schedule.Activity;
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.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
@Mixin(Brain.class)
public class BrainMixin {
@Mutable
@Shadow
@Final
private Map<?, ?> memories;
@Mutable
@Shadow
@Final
private Map<?, ?> sensors;
@Shadow
@Final
@Mutable
private Map<Activity, Set<Pair<MemoryModuleType<?>, MemoryStatus>>> activityRequirements;
@Inject(
method = "<init>",
at = @At("RETURN")
)
private void reinitializeBrainCollections(Collection<?> memories, Collection<?> sensors, ImmutableList<?> memoryEntries, Supplier<?> codecSupplier, CallbackInfo ci) {
this.memories = new Reference2ReferenceOpenHashMap<>(this.memories);
this.sensors = new Reference2ReferenceLinkedOpenHashMap<>(this.sensors);
this.activityRequirements = new Object2ObjectOpenHashMap<>(this.activityRequirements);
}
}

View File

@@ -0,0 +1,52 @@
package net.gensokyoreimagined.nitori.mixin.collections.chunk_tickets;
import net.minecraft.util.SortedArraySet;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import java.util.Collection;
import java.util.function.Predicate;
@Mixin(SortedArraySet.class)
public abstract class SortedArraySetMixin<T> implements Collection<T> {
@Shadow
int size;
@Shadow
T[] contents ;
/**
* Add an optimized implementation of {@link Collection#removeIf(Predicate)} which doesn't attempt to shift
* the values in the array multiple times with each removal. This also eliminates a number of object allocations
* and works on the direct backing array, speeding things up a fair chunk.
*/
@Override
public boolean removeIf(Predicate<? super T> filter) {
T[] arr = this.contents ;
int writeLim = this.size;
int writeIdx = 0;
for (int readIdx = 0; readIdx < writeLim; readIdx++) {
T obj = arr[readIdx];
// If the filter does not pass the object, simply skip over it. The write pointer will
// not be advanced and the next element to pass will instead take this one's place.
if (filter.test(obj)) {
continue;
}
// If the read and write pointers are the same, then no removals have occurred so far. This
// allows us to skip copying unchanged values back into the array.
if (writeIdx != readIdx) {
arr[writeIdx] = obj;
}
writeIdx++;
}
this.size = writeIdx;
return writeLim != writeIdx;
}
}

View File

@@ -0,0 +1,28 @@
package net.gensokyoreimagined.nitori.mixin.collections.entity_by_type;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
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.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
import java.util.Map;
@Mixin(ClassInstanceMultiMap.class)
public class TypeFilterableListMixin {
@Mutable
@Shadow
@Final
private Map<Class<?>, List<?>> byClass;
@Inject(method = "<init>", at = @At("RETURN"))
private void init(Class<?> elementType, CallbackInfo ci) {
this.byClass = new Reference2ReferenceOpenHashMap<>(this.byClass);
}
}

View File

@@ -0,0 +1,55 @@
package net.gensokyoreimagined.nitori.mixin.collections.entity_filtering;
import net.minecraft.util.ClassInstanceMultiMap;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import java.util.*;
/**
* Patches {@link ClassInstanceMultiMap} to improve performance when entities are being queried in the world.
*/
@Mixin(ClassInstanceMultiMap.class)
public class TypeFilterableListMixin<T> {
@Shadow
@Final
private Map<Class<?>, List<T>> byClass;
@Shadow
@Final
private List<T> allInstances;
/**
* @reason Only perform the slow Class#isAssignableFrom(Class) if a list doesn't exist for the type, otherwise
* we can assume it's already valid. The slow-path code is moved to a separate method to help the JVM inline this.
* @author JellySquid
*/
@SuppressWarnings("unchecked")
@Overwrite
public <S> Collection<S> find(Class<S> type) {
Collection<T> collection = this.byClass.get(type);
if (collection == null) {
collection = this.createAllOfType(type);
}
return (Collection<S>) Collections.unmodifiableCollection(collection);
}
private <S> Collection<T> createAllOfType(Class<S> type) {
List<T> list = new ArrayList<>();
for (T allElement : this.allInstances) {
if (type.isInstance(allElement)) {
list.add(allElement);
}
}
this.byClass.put(type, list);
return list;
}
}

View File

@@ -0,0 +1,44 @@
package net.gensokyoreimagined.nitori.mixin.collections.fluid_submersion;
import it.unimi.dsi.fastutil.objects.ReferenceArraySet;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.tags.TagKey;
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Set;
@Mixin(Entity.class)
public class EntityMixin {
@Mutable
@Shadow
@Final
private Set<TagKey<Fluid>> fluidOnEyes;
@Inject(
method = "<init>",
at = @At("RETURN")
)
private void useReferenceArraySet(CallbackInfo ci) {
this.fluidOnEyes = new ReferenceArraySet<>(this.fluidOnEyes);
}
@Redirect(
method = "updateFluidOnEyes",
at = @At(value = "INVOKE", target = "Ljava/util/Set;clear()V"),
require = 0
)
private void clearIfNotEmpty(Set<?> set) {
if (!set.isEmpty()) {
set.clear();
}
}
}

View File

@@ -12,7 +12,7 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package net.gensokyoreimagined.nitori.core;
package net.gensokyoreimagined.nitori.mixin.collections.gamerules;
import com.google.common.collect.ImmutableMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;

Some files were not shown because too many files have changed in this diff Show More