Compare commits

...

56 Commits

Author SHA1 Message Date
Jason Penilla
8040c7a264 0.1.0-beta.7 2024-10-24 11:40:46 -07:00
Jason Penilla
a9e36795e5 fabric: fix crash when fabric-lifecycle-events-v1 not present 2024-10-23 18:32:06 -07:00
Jason Penilla
a70073ae3e Setup issue templates (#53)
* Setup issue templates

* try to change order
2024-10-22 09:21:13 -07:00
Jason Penilla
6724814c02 Apply Lithium overrides on NeoForge (#57)
* Apply Lithium overrides on NeoForge

closes #56

* Update readme

* Sort overrides
2024-10-22 09:20:40 -07:00
Jason Penilla
f32a08738e Adjust min/max section optimizations (#55)
* Change min/max section optimisations

* Correctly init dimension type

We need to initialise the field as early as possible in the constructor
to avoid problems.

Also, do not cache min/max section in EntityLookup. Note that mods
that implement worlds with variable heights will not work still,
as the entity slices expect a fixed height.

* Cache calculated height values

---------

Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com>
2024-10-17 18:02:22 -07:00
Jason Penilla
56e48ed069 Back to 0.1.0-SNAPSHOT 2024-10-15 13:04:47 -07:00
Jason Penilla
9da99576a6 0.1.0-beta.6 2024-10-15 12:56:32 -07:00
Jason Penilla
dfbe1bcf8b Fix Mixin application failure in production
Work around https://github.com/FabricMC/tiny-remapper/issues/137
2024-10-15 12:45:22 -07:00
Jason Penilla
ae29196221 Back to 0.1.0-SNAPSHOT 2024-10-14 19:45:01 -07:00
Jason Penilla
f1eb61cc51 0.1.0-beta.5 2024-10-14 19:17:38 -07:00
Jason Penilla
58c933938f fabric: Call ServerChunkEvents.CHUNK_LOAD/CHUNK_UNLOAD, add and call FabricHooks.ON_EXPLOSION event (#52)
fixes #16
related to #45
2024-10-14 19:06:58 -07:00
Jason Penilla
1dc3cb5f14 Improve LeafProfiler output formatting (#28)
make it readable
2024-10-14 18:03:04 -07:00
Jason Penilla
2acfc6a68e Add missing require = 3 to util_threading_detector PalettedContainer constructor injection, and simplify fast_palette PalettedContainer constructor injection. 2024-10-02 10:54:52 -07:00
Jason Penilla
c22538c364 Back to 0.1.0-SNAPSHOT 2024-09-30 19:22:12 -07:00
Jason Penilla
e244c60375 0.1.0-beta.4 2024-09-30 19:10:12 -07:00
Jason Penilla
65253d5199 Re-fix dev-time conflict with lithium height overrides
See cfa80c44 and 2acd5cc2
2024-09-30 19:00:00 -07:00
Jason Penilla
6e82b034b7 Fix typo in previous commit
Also for some reason the issue resolved by cfa80c44 seems to be back...
2024-09-30 18:52:01 -07:00
Jason Penilla
a7cd78e63b Disable lithium mixin.util.entity_movement_tracking mixins, see #33 2024-09-30 18:43:25 -07:00
Jason Penilla
05ba7066d9 Add NeoForge/Mojang names for redirects in NaturalSpawnerMixin 2024-09-30 10:17:28 -07:00
Spottedleaf
340ac4e8f5 Fix incorrect fluid pushing velocity
Forgot to re-assign the flow vector variable with the scaled
flow.
2024-09-30 04:39:05 -07:00
Spottedleaf
a4770aca2b Fix performance regression in Entity#updateFluidHeightAndDoFluidPushing
The upper bounds from Vanilla are exclusive, but we were treating
them as inclusive which changes behavior and increases the number
of blocks read.
2024-09-30 04:39:05 -07:00
Spottedleaf
a8d4ce526b Optimise block/chunk reading in Entity#updateFluidHeightAndDoFluidPushing
Like other block reading code, a lot of time gets spent retrieving
the chunk and retrieving the section which we can avoid.
2024-09-30 04:39:05 -07:00
Spottedleaf
2acd5cc213 Optimise Level#isOutsideBuildHeight
We can cache the min/max build height to avoid the indirection
required to compute them.
2024-09-30 04:39:05 -07:00
Spottedleaf
07dce0ffe6 Change ShapesMixin#or to perform unoptimised intermediate joins
We can delay the shape optimisation to the final operation, which
should provide a perform boost for merging many shapes.
2024-09-30 04:39:05 -07:00
Spottedleaf
c2e2f0b9f2 Optimise countEntries for low size SimpleBitStorage
We can use a simple array lookup table by palette id when the
number of palette entries is low. This eliminates the need for
the map lookup on each entry.
2024-09-30 04:39:05 -07:00
Spottedleaf
1374ea34ee Replace ticking block coordinate set with ShortList
We can store a position (ranging from 0-4095) into a short,
so we can use ShortList to cut the memory usage in half.
2024-09-30 04:39:05 -07:00
Spottedleaf
fef4872a3a Do not calculate block counts on the client
It appears that this call is very expensive. We instead substitute
an exact check with setting the special colliding blocks to whether
the palette initially contains a special colliding block, and after
on block updating by whether any special colliding block was set.
2024-09-30 04:39:04 -07:00
Spottedleaf
a9678c76eb Fix missing range scale for passenger entities in entity tracker 2024-09-30 04:39:04 -07:00
Spottedleaf
bafa195546 Ensure relative block position methods return an immutable instance
If the relative distance is 0, then we would return the current
instance - which may be a mutable instance. However, Vanilla overrides
the relative function to return an immutable instance even when
the distance is zero. To ensure that this behavior is kept, we return
an immutable instance when the distance is zero.
2024-09-30 04:39:04 -07:00
Spottedleaf
04c6e6c01a Track broadcast chunks separately from player ticking chunks
Not all broadcast chunks are ticking in the new chunk system,
so we need to track them separately to ensure updates take place
in non-simulation range chunks.
2024-09-30 04:39:04 -07:00
Spottedleaf
7351872730 Avoid performing biome lookup for mob cost when entity has no cost
Most mob types do not have a cost with any biomes, so there is
no point in performing the lookup.
2024-09-30 04:39:04 -07:00
Spottedleaf
77b977ac17 Reduce chunk tick iteration count
By only iterating over chunks that are both ticking AND near players
or marked for ticking, we can reduce the number of chunks to iterate
over in higher SD settings.
2024-09-30 04:39:04 -07:00
Spottedleaf
f0d122f358 Optimise MobCounts implementation
Use of a map for category->count is inefficient as the key
is an enum. We can use a simple int array, which reduces
the get() and compute() calls to an array access.
2024-09-30 02:29:22 -07:00
Spottedleaf
aa240eb16d Modify isInWall to use same block retrieval as CollisionUtil 2024-09-30 02:25:24 -07:00
Spottedleaf
f9c99d1e32 Avoid streams for block retrieval in Entity#move
Profiling shows that the streams alone account for half of the time
for this specific check (inside fire/lava).
2024-09-30 02:25:24 -07:00
Spottedleaf
3fa0ff67a7 Optimise checkInsideBlocks
Use the same block retrieving algorithm as CollisionUtil
2024-09-30 02:25:24 -07:00
Spottedleaf
f7d9e5b422 Optimise findSupportingBlock to be like CollisionUtil
CollisionUtil's block iteration supports using the special
colliding blocks, as well as a superior (but more verbose)
chunk iteration.
2024-09-30 02:25:24 -07:00
Spottedleaf
5d1975154b Add dev override for max view distance 2024-09-30 02:25:24 -07:00
Spottedleaf
d21fa9f48c Use SimpleRandom for chunk tick shuffling 2024-09-30 02:25:24 -07:00
Spottedleaf
1c150afe83 Add and use reference counted ChunkData
ChunkData is stored directly on chunk holders, entity slices,
and on entities. Currently, this allows access to
NearbyPlayers$TrackedChunk without performing a map lookup.
2024-09-30 02:25:24 -07:00
Spottedleaf
d9988c86f4 Add direct lookup by chunk for NearbyPlayers
This simplifies the lookup code by reducing the number of "random"
accesses to 1 (get() call) assuming that the directByChunk array
is cached.
2024-09-30 02:25:24 -07:00
Jason Penilla
0958439e54 Improve mod compat for ShapesMixin (#36)
Use an inject instead of overwrite to avoid mixin conflicts - obviously this will still disregard any changes made by other
mixins, but at least it won't conflict immediately. This is useful because some library mods mixin here when only the content
mod actually needs the change.

Fixes incompatibility with CreativeCore but not LittleTiles
2024-09-29 18:32:04 -07:00
Jason Penilla
8d9d6488e8 Improve mod compat for EntityTickListMixin (#35)
Replaces an overwrite with a cancellable inject at head.

Fixes compat with mod AllTheLeaks
2024-09-29 18:23:17 -07:00
Jason Penilla
8ba83bc9c2 Adjust fluid state cache init for better compatibility (#31)
Some mods rely on data initialized in their fluid subclass constructor for the methods we call, which won't be available at the superclass constructor return.

Injecting into the registry add is a little ugly, but should be fine.

Fixes compat with Immersive Engineering.
2024-09-29 18:18:02 -07:00
Jason Penilla
0c60a6ac08 Update dependencies
Newer NeoForge has moved their coremods from JS to Java, which should slightly improve startup times (as the JS engine won't load without another mod that uses it), making this update relevant for development
2024-09-28 12:20:46 -07:00
Jason Penilla
d404dbc6c5 Fix entity cramming (#32)
We want to count non-passengers, not passengers
2024-09-24 15:29:11 -07:00
Jason Penilla
8d456890f1 Back to 0.1.0-SNAPSHOT 2024-09-17 16:58:55 -07:00
Jason Penilla
23eddfe918 0.1.0-beta.3 2024-09-15 13:53:16 -07:00
Jason Penilla
55ff406372 Update readme and mod metadata for Radium changes (#18) 2024-09-15 13:47:10 -07:00
Jason Penilla
3e25a2f4aa Update publishing metadata 2024-09-15 13:40:43 -07:00
Jason Penilla
cfa80c4488 Raise priority of collisions LevelMixin to apply after Lithium
Lithium replaces some of the height-related methods used by WorldUtil to return fields initialized in their constructor hook. We need to apply afterward to have them return valid data.

Given the application-order sensitive nature of the issue, it only presented in-dev.
2024-09-15 13:36:29 -07:00
Jason Penilla
453b635ef2 Fix explosion conflict with lithium 2024-09-15 12:43:13 -07:00
Jason Penilla
e07e4fdcc4 Reorder lithium overrides 2024-09-15 12:42:44 -07:00
Jason Penilla
81bb9701da Reformat FMJ to match editorconfig settings 2024-09-15 12:41:20 -07:00
Jason Penilla
d0a7f9af62 Add readme badges (#23) 2024-09-14 18:58:52 -07:00
Jason Penilla
fe7bcfc56a Back to 0.1.0-SNAPSHOT 2024-09-07 18:01:51 -07:00
84 changed files with 2585 additions and 547 deletions

View File

@@ -0,0 +1,53 @@
name: Mod Incompatibility
description: Report an incompatibility with another mod.
title: '[Incompatibility: <NeoForge/Fabric/NeoForge & Fabric>]: Mod Name'
labels:
- mod-incompatibility
- needs-triage
body:
- type: input
id: mc_ver
attributes:
label: Minecraft Version
placeholder: e.g. 1.21.1 or 1.21.2
validations:
required: true
- type: input
id: moonrise_ver
attributes:
label: Moonrise Version
placeholder: e.g. 0.1.0-beta.6 or Git commit hash
validations:
required: true
- type: dropdown
id: mod_loader
attributes:
label: Mod Loader
description: >-
The mod loader(s) this conflict happens with. When selecting 'Both' it
is expected you will submit logs for both as well. Be sure to update the
issue title.
options:
- NeoForge
- Fabric
- Both (NeoForge and Fabric)
validations:
required: true
- type: textarea
id: logs_crashes
attributes:
label: Logs and Crash Reports
description: >-
Include links to all relevant logs and crash reports (prefer using
https://gist.github.com/ or https://mclo.gs/). In case this is not
applicable fill in 'N/A' (it is expected you will provide context
below).
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional Context
description: Provide any relevant context, including version of the conflicting mod.
validations:
required: true

55
.github/ISSUE_TEMPLATE/b_bug.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: Bug Report
description: Report a bug or Vanilla behavior parity issue.
title: '[Bug: <NeoForge/Fabric/NeoForge & Fabric>]: Short description'
labels:
- bug
- needs-triage
body:
- type: input
id: mc_ver
attributes:
label: Minecraft Version
placeholder: e.g. 1.21.1 or 1.21.2
validations:
required: true
- type: input
id: moonrise_ver
attributes:
label: Moonrise Version
placeholder: e.g. 0.1.0-beta.6 or Git commit hash
validations:
required: true
- type: dropdown
id: mod_loader
attributes:
label: Mod Loader
description: >-
The mod loader(s) this bug happens with. When selecting 'Both' it
is expected you will submit logs for both as well. Be sure to update the
issue title.
options:
- NeoForge
- Fabric
- Both (NeoForge and Fabric)
validations:
required: true
- type: textarea
id: logs_crashes
attributes:
label: Logs and Crash Reports
description: >-
Include links to all relevant logs and crash reports (prefer using
https://gist.github.com/ or https://mclo.gs/). In case this is not
applicable fill in 'N/A' (it is expected you will provide context
below).
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional Context
description: >-
Provide any relevant context, including expected vs. actual behavior,
and reproduction steps.
validations:
required: true

View File

@@ -0,0 +1,65 @@
name: Performance Problem
description: Report a performance problem that doesn't fall under the bug category.
title: '[Performance: <NeoForge/Fabric/NeoForge & Fabric>]: Short description'
labels:
- performance
- needs-triage
body:
- type: input
id: mc_ver
attributes:
label: Minecraft Version
placeholder: e.g. 1.21.1, 1.21.2
validations:
required: true
- type: input
id: moonrise_ver
attributes:
label: Moonrise Version
placeholder: e.g. 0.1.0-beta.6 or Git commit hash
validations:
required: true
- type: dropdown
id: mod_loader
attributes:
label: Mod Loader
description: >-
The mod loader(s) this bug happens with. When selecting 'Both' it is
expected you have tested both as well. Be sure to update the issue
title.
options:
- NeoForge
- Fabric
- Both (NeoForge and Fabric)
validations:
required: true
- type: textarea
id: logs_crashes
attributes:
label: Logs and Crash Reports
description: >-
Include links to all relevant logs and crash reports (prefer using
https://gist.github.com/ or https://mclo.gs/). In case this is not
applicable fill in 'N/A' (it is expected you will provide context
below).
validations:
required: true
- type: textarea
id: profiler
attributes:
label: Profiler Data
description: >-
Provide profiler data showing the problem, from Spark or other tools. In
case this is not applicable fill in 'N/A' (it is expected you will
provide context below).
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional Context
description: >-
Provide any relevant context, including reproduction steps, world data,
videos, etc.
validations:
required: true

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Discord
url: https://discord.gg/tuinity
about: If you are unsure about something or have general questions, ask in our Discord before opening an issue.

34
.github/ISSUE_TEMPLATE/d_feature.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Feature Request
description: Request a new feature.
title: '[Feature Request: <NeoForge/Fabric/NeoForge & Fabric>]: Short description'
labels:
- enhancement
- needs-triage
body:
- type: input
id: mc_ver
attributes:
label: Minecraft Version
placeholder: e.g. 1.21.1, 1.21.2
validations:
required: true
- type: dropdown
id: mod_loader
attributes:
label: Mod Loader
description: The mod loader(s) this feature request is relevant for.
options:
- NeoForge
- Fabric
- Both (NeoForge and Fabric)
validations:
required: true
- type: textarea
id: context
attributes:
label: Description
description: >-
Describe the feature request in detail, including alternatives
considered.
validations:
required: true

View File

@@ -1,5 +1,10 @@
Moonrise
==
[![Modrinth](https://img.shields.io/badge/Modrinth-gray?logo=modrinth)](https://modrinth.com/mod/moonrise-opt)
[![CurseForge](https://img.shields.io/badge/CurseForge-gray?logo=curseforge)](https://www.curseforge.com/minecraft/mc-mods/moonrise)
[![Release](https://img.shields.io/github/v/release/Tuinity/Moonrise?include_prereleases)](https://github.com/Tuinity/Moonrise/releases)
[![License](https://img.shields.io/github/license/Tuinity/Moonrise)](LICENSE.md)
Fabric/NeoForge mod for optimising performance of the integrated (singleplayer/LAN) and dedicated server.
@@ -20,7 +25,7 @@ patches. Listed below are notable patches:
|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Lithium | <details><summary>✅ compatible</summary>Lithium optimises many of the same parts of the game as Moonrise, for example the chunk system. Moonrise will automatically disable conflicting parts of Lithium. This mechanism needs to be manually validated for each Moonrise and Lithium release.</details> |
| FerriteCore | <details><summary>📝 requires config changes</summary>In `config/ferritecore-mixin.toml`:<br/>Set `replaceNeighborLookup` and `replacePropertyMap` to `false`</details> |
| C2ME | <details><summary>❌ incompatible</summary>C2ME is based around modifications to the chunk system, which Moonrise replaces wholesale. This makes them fundamentally incompatible.</details> |
| C2ME | <details><summary>❌ incompatible</summary>C2ME is based around modifications to the chunk system, which Moonrise replaces wholesale. This makes them fundamentally incompatible.</details> |
## Configuration
Moonrise provides documented configuration options for tuning the chunk system and enabling bugfixes in the config file `$mcdir$/config/moonrise.yml`.

View File

@@ -112,18 +112,45 @@ subprojects {
}
changelog = providers.environmentVariable("RELEASE_NOTES")
List<String> supportedMcVersions = rootProject.supported_minecraft_versions.split(',')
modrinth {
projectId = "KOHu7RCS"
accessToken = providers.environmentVariable("MODRINTH_TOKEN")
minecraftVersions = [rootProject.minecraft_version]
minecraftVersions = supportedMcVersions
}
curseforge {
projectId = "1096335"
accessToken = providers.environmentVariable("CURSEFORGE_TOKEN")
minecraftVersions = [rootProject.minecraft_version]
minecraftVersions = supportedMcVersions
}
}
// Setup a run with lithium for compatibility testing
sourceSets.create("lithium")
configurations.create("lithium")
loom {
createRemapConfigurations(sourceSets.lithium)
runs {
register("lithiumClient") {
client()
property "mixin.debug", "true"
}
}
}
tasks.named("runLithiumClient", net.fabricmc.loom.task.RunGameTask.class) {
getClasspath().from(configurations.modRuntimeClasspathLithiumMapped)
}
dependencies {
String coordinates = "maven.modrinth:lithium:"
if (getProject().name == "Moonrise-NeoForge") {
coordinates += rootProject.neo_lithium_version
} else {
coordinates += rootProject.fabric_lithium_version
}
modLithiumRuntimeOnly coordinates
}
}
loom.runs.all {

View File

@@ -25,6 +25,7 @@ dependencies {
modImplementation "com.terraformersmc:modmenu:11.0.1"
modImplementation fabricApiLibs.command.api.v2
modImplementation fabricApiLibs.lifecycle.events.v1
include fabricApiLibs.command.api.v2
include fabricApiLibs.base
}
@@ -63,22 +64,3 @@ publishMods {
)
}
}
// Setup a run with lithium for compatibility testing
sourceSets.create("lithium")
configurations.create("lithium")
loom {
createRemapConfigurations(sourceSets.lithium)
runs {
register("lithiumClient") {
client()
property "mixin.debug", "true"
}
}
}
tasks.named("runLithiumClient", net.fabricmc.loom.task.RunGameTask.class) {
getClasspath().from(configurations.modRuntimeClasspathLithiumMapped)
}
dependencies {
modLithiumRuntimeOnly "maven.modrinth:lithium:${rootProject.lithium_version}"
}

View File

@@ -3,6 +3,10 @@ package ca.spottedleaf.moonrise.fabric;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.common.util.ConfigHolder;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.GenerationChunkHolder;
@@ -25,6 +29,21 @@ import java.util.function.Predicate;
public final class FabricHooks implements PlatformHooks {
private static final boolean HAS_FABRIC_LIFECYCLE_EVENTS = FabricLoader.getInstance().isModLoaded("fabric-lifecycle-events-v1");
public interface OnExplosionDetonate {
void onExplosion(final Level world, final Explosion explosion, final List<Entity> possiblyAffecting, final double diameter);
}
public static final Event<OnExplosionDetonate> ON_EXPLOSION_DETONATE = EventFactory.createArrayBacked(
OnExplosionDetonate.class,
listeners -> (final Level world, final Explosion explosion, final List<Entity> possiblyAffecting, final double diameter) -> {
for (int i = 0; i < listeners.length; i++) {
listeners[i].onExplosion(world, explosion, possiblyAffecting, diameter);
}
}
);
@Override
public String getBrand() {
return "Moonrise";
@@ -44,7 +63,7 @@ public final class FabricHooks implements PlatformHooks {
@Override
public void onExplosion(final Level world, final Explosion explosion, final List<Entity> possiblyAffecting, final double diameter) {
ON_EXPLOSION_DETONATE.invoker().onExplosion(world, explosion, possiblyAffecting, diameter);
}
@Override
@@ -69,7 +88,9 @@ public final class FabricHooks implements PlatformHooks {
@Override
public void chunkFullStatusComplete(final LevelChunk newChunk, final ProtoChunk original) {
if (HAS_FABRIC_LIFECYCLE_EVENTS) {
ServerChunkEvents.CHUNK_LOAD.invoker().onChunkLoad((ServerLevel) newChunk.getLevel(), newChunk);
}
}
@Override
@@ -84,7 +105,9 @@ public final class FabricHooks implements PlatformHooks {
@Override
public void chunkUnloadFromWorld(final LevelChunk chunk) {
if (HAS_FABRIC_LIFECYCLE_EVENTS) {
ServerChunkEvents.CHUNK_UNLOAD.invoker().onChunkUnload((ServerLevel) chunk.getLevel(), chunk);
}
}
@Override

View File

@@ -0,0 +1,183 @@
package ca.spottedleaf.moonrise.fabric.mixin.collisions;
import ca.spottedleaf.moonrise.common.util.WorldUtil;
import it.unimi.dsi.fastutil.objects.Object2DoubleMap;
import net.minecraft.core.BlockPos;
import net.minecraft.tags.TagKey;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(Entity.class)
abstract class EntityMixin {
@Shadow
public abstract boolean touchingUnloadedChunk();
@Shadow
public abstract AABB getBoundingBox();
@Shadow
@Deprecated
public abstract boolean isPushedByFluid();
@Shadow
private Level level;
@Shadow
@Deprecated
protected Object2DoubleMap<TagKey<Fluid>> fluidHeight;
@Shadow
public abstract Vec3 getDeltaMovement();
@Shadow
public abstract void setDeltaMovement(final Vec3 arg);
/**
* @reason Optimise the block reading in this function
* @author Spottedleaf
*/
@Overwrite
public boolean updateFluidHeightAndDoFluidPushing(final TagKey<Fluid> fluid, final double flowScale) {
if (this.touchingUnloadedChunk()) {
return false;
}
final AABB boundingBox = this.getBoundingBox().deflate(1.0E-3);
final Level world = this.level;
final int minSection = WorldUtil.getMinSection(world);
final int minBlockX = Mth.floor(boundingBox.minX);
final int minBlockY = Math.max((minSection << 4), Mth.floor(boundingBox.minY));
final int minBlockZ = Mth.floor(boundingBox.minZ);
// note: bounds are exclusive in Vanilla, so we subtract 1 - our loop expects bounds to be inclusive
final int maxBlockX = Mth.ceil(boundingBox.maxX) - 1;
final int maxBlockY = Math.min((WorldUtil.getMaxSection(world) << 4) | 15, Mth.ceil(boundingBox.maxY) - 1);
final int maxBlockZ = Mth.ceil(boundingBox.maxZ) - 1;
final boolean isPushable = this.isPushedByFluid();
final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
Vec3 pushVector = Vec3.ZERO;
double totalPushes = 0.0;
double maxHeightDiff = 0.0;
boolean inFluid = false;
final int minChunkX = minBlockX >> 4;
final int maxChunkX = maxBlockX >> 4;
final int minChunkY = minBlockY >> 4;
final int maxChunkY = maxBlockY >> 4;
final int minChunkZ = minBlockZ >> 4;
final int maxChunkZ = maxBlockZ >> 4;
final ChunkSource chunkSource = world.getChunkSource();
for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
final LevelChunkSection[] sections = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, false).getSections();
// bound y
for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) {
final int sectionIdx = currChunkY - minSection;
if (sectionIdx < 0 || sectionIdx >= sections.length) {
continue;
}
final LevelChunkSection section = sections[sectionIdx];
if (section.hasOnlyAir()) {
// empty
continue;
}
final PalettedContainer<BlockState> blocks = section.states;
final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) : 0;
final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) : 15;
final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) : 0;
final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) : 15;
final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) : 0;
final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) : 15;
for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) {
for (int currX = minXIterate; currX <= maxXIterate; ++currX) {
final FluidState fluidState = blocks.get((currX) | (currZ << 4) | ((currY) << 8)).getFluidState();
if (fluidState.isEmpty() || !fluidState.is(fluid)) {
continue;
}
mutablePos.set(currX | (currChunkX << 4), currY | (currChunkY << 4), currZ | (currChunkZ << 4));
final double height = (double)((float)mutablePos.getY() + fluidState.getHeight(world, mutablePos));
final double diff = height - boundingBox.minY;
if (diff < 0.0) {
continue;
}
inFluid = true;
maxHeightDiff = Math.max(maxHeightDiff, diff);
if (!isPushable) {
continue;
}
++totalPushes;
final Vec3 flow = fluidState.getFlow(world, mutablePos);
if (diff < 0.4) {
pushVector = pushVector.add(flow.scale(diff));
} else {
pushVector = pushVector.add(flow);
}
}
}
}
}
}
}
this.fluidHeight.put(fluid, maxHeightDiff);
if (pushVector.lengthSqr() == 0.0) {
return inFluid;
}
// note: totalPushes != 0 as pushVector != 0
pushVector = pushVector.scale(1.0 / totalPushes);
final Vec3 currMovement = this.getDeltaMovement();
if (!((Entity)(Object)this instanceof Player)) {
pushVector = pushVector.normalize();
}
pushVector = pushVector.scale(flowScale);
if (Math.abs(currMovement.x) < 0.003 && Math.abs(currMovement.z) < 0.003 && pushVector.length() < 0.0045000000000000005) {
pushVector = pushVector.normalize().scale(0.0045000000000000005);
}
this.setDeltaMovement(currMovement.add(pushVector));
// note: inFluid = true here as pushVector != 0
return true;
}
}

View File

@@ -1,71 +1,74 @@
{
"schemaVersion": 1,
"id": "moonrise",
"version": "${version}",
"name": "Moonrise",
"description": "Optimisation mod for the dedicated and integrated server.",
"authors": [
"Spottedleaf"
],
"contact": {
"issues": "https://github.com/Tuinity/Moonrise/issues",
"sources": "https://github.com/Tuinity/Moonrise",
"discord": "https://discord.gg/tuinity",
"homepage": "https://www.curseforge.com/minecraft/mc-mods/moonrise"
},
"breaks": {
"notenoughcrashes": "*",
"starlight": "*",
"c2me": "*"
},
"license": "GPL-3.0-only",
"icon": "assets/moonrise/icon.png",
"environment": "*",
"entrypoints": {
"modmenu": [
"ca.spottedleaf.moonrise.fabric.MoonriseModMenuHook"
"schemaVersion": 1,
"id": "moonrise",
"version": "${version}",
"name": "Moonrise",
"description": "Optimisation mod for the dedicated and integrated server.",
"authors": [
"Spottedleaf"
],
"client": [
"ca.spottedleaf.moonrise.fabric.MoonriseFabricClient"
]
},
"mixins": [
"moonrise.mixins.json",
"moonrise-fabric.mixins.json"
],
"accessWidener": "moonrise.accesswidener",
"depends": {
"fabricloader": ">=${loader_version}",
"minecraft": ">=1.21 <=1.21.1",
"fabric-command-api-v2": "*"
},
"custom": {
"lithium:options": {
"mixin.collections.chunk_tickets": false,
"mixin.world.temperature_cache": false,
"mixin.block.flatten_states": false,
"mixin.math.fast_util": false,
"mixin.minimal_nonvanilla.collisions.empty_space": false,
"mixin.alloc.deep_passengers": false,
"mixin.math.fast_blockpos": false,
"mixin.shapes.blockstate_cache": false,
"mixin.world.block_entity_ticking": false,
"mixin.collections.entity_ticking": false,
"mixin.world.chunk_access": false,
"mixin.ai.poi": false,
"mixin.chunk.no_validation": false,
"mixin.minimal_nonvanilla.world.expiring_chunk_tickets": false,
"mixin.world.tick_scheduler": false,
"mixin.alloc.chunk_ticking": false,
"mixin.entity.replace_entitytype_predicates": false,
"mixin.util.block_tracking": false,
"mixin.shapes.specialized_shapes": false,
"mixin.shapes.optimized_matching": false,
"mixin.entity.collisions.intersection": false,
"mixin.entity.collisions.movement": false,
"mixin.entity.collisions.unpushable_cramming": false,
"mixin.chunk.entity_class_groups": false,
"mixin.alloc.entity_tracker": false
"contact": {
"issues": "https://github.com/Tuinity/Moonrise/issues",
"sources": "https://github.com/Tuinity/Moonrise",
"discord": "https://discord.gg/tuinity",
"homepage": "https://www.curseforge.com/minecraft/mc-mods/moonrise"
},
"breaks": {
"notenoughcrashes": "*",
"starlight": "*",
"c2me": "*"
},
"license": "GPL-3.0-only",
"icon": "assets/moonrise/icon.png",
"environment": "*",
"entrypoints": {
"modmenu": [
"ca.spottedleaf.moonrise.fabric.MoonriseModMenuHook"
],
"client": [
"ca.spottedleaf.moonrise.fabric.MoonriseFabricClient"
]
},
"mixins": [
"moonrise.mixins.json",
"moonrise-fabric.mixins.json"
],
"accessWidener": "moonrise.accesswidener",
"depends": {
"fabricloader": ">=${loader_version}",
"minecraft": ">=1.21 <=1.21.1",
"fabric-command-api-v2": "*"
},
"custom": {
"lithium:options": {
"mixin.ai.poi": false,
"mixin.alloc.chunk_ticking": false,
"mixin.alloc.deep_passengers": false,
"mixin.alloc.entity_tracker": false,
"mixin.block.flatten_states": false,
"mixin.chunk.entity_class_groups": false,
"mixin.chunk.no_validation": false,
"mixin.collections.chunk_tickets": false,
"mixin.collections.entity_ticking": false,
"mixin.entity.collisions.intersection": false,
"mixin.entity.collisions.movement": false,
"mixin.entity.collisions.unpushable_cramming": false,
"mixin.entity.replace_entitytype_predicates": false,
"mixin.math.fast_blockpos": false,
"mixin.math.fast_util": false,
"mixin.minimal_nonvanilla.collisions.empty_space": false,
"mixin.minimal_nonvanilla.world.expiring_chunk_tickets": false,
"mixin.shapes.blockstate_cache": false,
"mixin.shapes.optimized_matching": false,
"mixin.shapes.specialized_shapes": false,
"mixin.util.block_tracking": false,
"mixin.util.entity_movement_tracking": false,
"mixin.world.block_entity_ticking": false,
"mixin.world.chunk_access": false,
"mixin.world.explosions.block_raycast": false,
"mixin.world.explosions.cache_exposure": false,
"mixin.world.temperature_cache": false,
"mixin.world.tick_scheduler": false
}
}
}
}

View File

@@ -4,7 +4,8 @@
"mixins": [
"chunk_system.FabricDistanceManagerMixin",
"chunk_system.FabricMinecraftServerMixin",
"chunk_system.FabricServerLevelMixin"
"chunk_system.FabricServerLevelMixin",
"collisions.EntityMixin"
],
"client": [
"command.ClientSuggestionProviderMixin"

View File

@@ -4,13 +4,16 @@ org.gradle.daemon=false
# Fabric Properties
# check these on https://modmuss50.me/fabric.html
minecraft_version=1.21.1
supported_minecraft_versions=1.21,1.21.1
loader_version=0.16.5
neoforge_version=21.1.42
neoforge_version=21.1.62
snakeyaml_version=2.2
concurrentutil_version=0.0.2-SNAPSHOT
cloth_version=15.0.128
lithium_version=mc1.21.1-0.13.0
# version ids from modrinth
fabric_lithium_version=mc1.21.1-0.14.0-beta.1
neo_lithium_version=BrMIoIMv
# Mod Properties
mod_version=0.1.0-beta.2
mod_version=0.1.0-beta.7
maven_group=ca.spottedleaf.moonrise
archives_base_name=moonrise

View File

@@ -54,16 +54,14 @@ publishMods {
incompatible(
"notenoughcrashes",
"starlight-neoforge",
"canary",
"radium"
"canary"
)
}
curseforge {
incompatible(
"not-enough-crashes-forge",
"starlight-neoforge",
"canary",
"radium-reforged"
"canary"
)
}
}

View File

@@ -1,8 +1,8 @@
package ca.spottedleaf.moonrise.neoforge.mixin.chunk_system;
import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemDistanceManager;
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction;
import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.server.level.ChunkLevel;
import net.minecraft.server.level.DistanceManager;
@@ -15,13 +15,13 @@ 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(DistanceManager.class)
abstract class NeoForgeDistanceManagerMixin implements ChunkSystemDistanceManager {
@Unique
private final ConcurrentLong2ReferenceChainedHashTable<SortedArraySet<Ticket<?>>> mtSafeForcedTickets = new ConcurrentLong2ReferenceChainedHashTable<>();
@Shadow
@Final
private Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> forcedTickets;
/**
* @reason Route to new chunk system
@@ -34,7 +34,7 @@ abstract class NeoForgeDistanceManagerMixin implements ChunkSystemDistanceManage
if (forceTicks) {
final Ticket<T> forceTicket = new Ticket<>(type, level, identifier, forceTicks);
this.mtSafeForcedTickets.compute(pos.toLong(), (final long keyInMap, final SortedArraySet<Ticket<?>> valueInMap) -> {
this.forcedTickets.compute(pos.toLong(), (final Long keyInMap, final SortedArraySet<Ticket<?>> valueInMap) -> {
final SortedArraySet<Ticket<?>> ret;
if (valueInMap != null) {
ret = valueInMap;
@@ -42,7 +42,11 @@ abstract class NeoForgeDistanceManagerMixin implements ChunkSystemDistanceManage
ret = SortedArraySet.create(4);
}
ret.add(forceTicket);
if (ret.add(forceTicket)) {
((ChunkTickServerLevel)NeoForgeDistanceManagerMixin.this.moonrise$getChunkMap().level).moonrise$addPlayerTickingRequest(
CoordinateUtils.getChunkX(keyInMap.longValue()), CoordinateUtils.getChunkZ(keyInMap.longValue())
);
}
return ret;
});
@@ -60,8 +64,12 @@ abstract class NeoForgeDistanceManagerMixin implements ChunkSystemDistanceManage
if (forceTicks) {
final Ticket<T> forceTicket = new Ticket<>(type, level, identifier, forceTicks);
this.mtSafeForcedTickets.computeIfPresent(pos.toLong(), (final long keyInMap, final SortedArraySet<Ticket<?>> valueInMap) -> {
valueInMap.remove(forceTicket);
this.forcedTickets.computeIfPresent(pos.toLong(), (final Long keyInMap, final SortedArraySet<Ticket<?>> valueInMap) -> {
if (valueInMap.remove(forceTicket)) {
((ChunkTickServerLevel)NeoForgeDistanceManagerMixin.this.moonrise$getChunkMap().level).moonrise$removePlayerTickingRequest(
CoordinateUtils.getChunkX(keyInMap.longValue()), CoordinateUtils.getChunkZ(keyInMap.longValue())
);
}
return valueInMap.isEmpty() ? null : valueInMap;
});
@@ -69,11 +77,11 @@ abstract class NeoForgeDistanceManagerMixin implements ChunkSystemDistanceManage
}
/**
* @reason Make this API thread-safe
* @reason Only use containsKey, as we fix the leak with this impl
* @author Spottedleaf
*/
@Overwrite
public boolean shouldForceTicks(final long chunkPos) {
return this.mtSafeForcedTickets.containsKey(chunkPos);
return this.forcedTickets.containsKey(chunkPos);
}
}

View File

@@ -0,0 +1,199 @@
package ca.spottedleaf.moonrise.neoforge.mixin.collisions;
import ca.spottedleaf.moonrise.common.util.WorldUtil;
import ca.spottedleaf.moonrise.neoforge.patches.collisions.FluidPushCalculation;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceArrayMap;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.neoforged.neoforge.common.extensions.IEntityExtension;
import net.neoforged.neoforge.fluids.FluidType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import java.util.Iterator;
@Mixin(Entity.class)
abstract class EntityMixin implements IEntityExtension {
@Shadow
public abstract boolean touchingUnloadedChunk();
@Shadow
public abstract AABB getBoundingBox();
@Shadow
private Level level;
@Shadow
protected abstract void setFluidTypeHeight(final FluidType type, final double height);
@Shadow
public abstract Vec3 getDeltaMovement();
@Shadow
public abstract void setDeltaMovement(final Vec3 arg);
/**
* @reason Optimise the block reading in this function
* @author Spottedleaf
*/
@Overwrite
public void updateFluidHeightAndDoFluidPushing() {
if (this.touchingUnloadedChunk()) {
return;
}
final AABB boundingBox = this.getBoundingBox().deflate(1.0E-3);
final Level world = this.level;
final int minSection = WorldUtil.getMinSection(world);
final int minBlockX = Mth.floor(boundingBox.minX);
final int minBlockY = Math.max((minSection << 4), Mth.floor(boundingBox.minY));
final int minBlockZ = Mth.floor(boundingBox.minZ);
// note: bounds are exclusive in Vanilla, so we subtract 1
final int maxBlockX = Mth.ceil(boundingBox.maxX) - 1;
final int maxBlockY = Math.min((WorldUtil.getMaxSection(world) << 4) | 15, Mth.ceil(boundingBox.maxY) - 1);
final int maxBlockZ = Mth.ceil(boundingBox.maxZ) - 1;
final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
final int minChunkX = minBlockX >> 4;
final int maxChunkX = maxBlockX >> 4;
final int minChunkY = minBlockY >> 4;
final int maxChunkY = maxBlockY >> 4;
final int minChunkZ = minBlockZ >> 4;
final int maxChunkZ = maxBlockZ >> 4;
final ChunkSource chunkSource = world.getChunkSource();
final Reference2ReferenceArrayMap<FluidType, FluidPushCalculation> calculations = new Reference2ReferenceArrayMap<>();
for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
final LevelChunkSection[] sections = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, false).getSections();
// bound y
for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) {
final int sectionIdx = currChunkY - minSection;
if (sectionIdx < 0 || sectionIdx >= sections.length) {
continue;
}
final LevelChunkSection section = sections[sectionIdx];
if (section.hasOnlyAir()) {
// empty
continue;
}
final PalettedContainer<BlockState> blocks = section.states;
final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) : 0;
final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) : 15;
final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) : 0;
final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) : 15;
final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) : 0;
final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) : 15;
for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) {
for (int currX = minXIterate; currX <= maxXIterate; ++currX) {
final FluidState fluidState = blocks.get((currX) | (currZ << 4) | ((currY) << 8)).getFluidState();
if (fluidState.isEmpty()) {
continue;
}
mutablePos.set(currX | (currChunkX << 4), currY | (currChunkY << 4), currZ | (currChunkZ << 4));
final FluidType type = fluidState.getFluidType();
// note: assume fluidState.isEmpty() == type.isAir()
final FluidPushCalculation calculation = calculations.computeIfAbsent(type, (final FluidType key) -> {
return new FluidPushCalculation();
});
final double height = (double)((float)mutablePos.getY() + fluidState.getHeight(world, mutablePos));
final double diff = height - boundingBox.minY;
if (diff < 0.0) {
continue;
}
calculation.maxHeightDiff = Math.max(calculation.maxHeightDiff, diff);
if (calculation.isPushed == Boolean.FALSE) {
continue;
} else if (calculation.isPushed == null) {
final boolean isPushed = this.isPushedByFluid(type);
calculation.isPushed = Boolean.valueOf(isPushed);
if (!isPushed) {
continue;
}
}
++calculation.totalPushes;
final Vec3 flow = fluidState.getFlow(world, mutablePos);
if (diff < 0.4) {
calculation.pushVector = calculation.pushVector.add(flow.scale(diff));
} else {
calculation.pushVector = calculation.pushVector.add(flow);
}
}
}
}
}
}
}
if (calculations.isEmpty()) {
return;
}
for (final Iterator<Reference2ReferenceMap.Entry<FluidType, FluidPushCalculation>> iterator = calculations.reference2ReferenceEntrySet().fastIterator(); iterator.hasNext();) {
final Reference2ReferenceMap.Entry<FluidType, FluidPushCalculation> entry = iterator.next();
final FluidType type = entry.getKey();
final FluidPushCalculation calculation = entry.getValue();
this.setFluidTypeHeight(type, calculation.maxHeightDiff);
Vec3 pushVector = calculation.pushVector;
if (pushVector.lengthSqr() == 0.0) {
continue;
}
// note: totalPushes != 0 as pushVector != 0
pushVector = pushVector.scale(1.0 / calculation.totalPushes);
final Vec3 currMovement = this.getDeltaMovement();
if (!((Entity)(Object)this instanceof Player)) {
pushVector = pushVector.normalize();
}
pushVector = pushVector.scale(this.getFluidMotionScale(type));
if (Math.abs(currMovement.x) < 0.003 && Math.abs(currMovement.z) < 0.003 && pushVector.length() < 0.0045000000000000005) {
pushVector = pushVector.normalize().scale(0.0045000000000000005);
}
this.setDeltaMovement(currMovement.add(pushVector));
}
}
}

View File

@@ -0,0 +1,12 @@
package ca.spottedleaf.moonrise.neoforge.patches.collisions;
import net.minecraft.world.phys.Vec3;
public final class FluidPushCalculation {
public Vec3 pushVector = Vec3.ZERO;
public double totalPushes = 0.0;
public double maxHeightDiff = 0.0;
public Boolean isPushed = null;
}

View File

@@ -36,20 +36,11 @@ type = "incompatible"
modId = "starlight"
type = "incompatible"
[[dependencies.moonrise]]
modId = "lithium"
type = "incompatible"
[[dependencies.moonrise]]
# unofficial lithium port
modId = "canary"
type = "incompatible"
[[dependencies.moonrise]]
# unofficial lithium port
modId = "radium"
type = "incompatible"
[[dependencies.moonrise]]
modId = "c2me"
type = "incompatible"
@@ -59,3 +50,33 @@ config = "moonrise.mixins.json"
[[mixins]]
config = "moonrise-neoforge.mixins.json"
[mods."lithium:options"]
"mixin.ai.poi" = false
"mixin.alloc.chunk_ticking" = false
"mixin.alloc.deep_passengers" = false
"mixin.alloc.entity_tracker" = false
"mixin.block.flatten_states" = false
"mixin.chunk.entity_class_groups" = false
"mixin.chunk.no_validation" = false
"mixin.collections.chunk_tickets" = false
"mixin.collections.entity_ticking" = false
"mixin.entity.collisions.intersection" = false
"mixin.entity.collisions.movement" = false
"mixin.entity.collisions.unpushable_cramming" = false
"mixin.entity.replace_entitytype_predicates" = false
"mixin.math.fast_blockpos" = false
"mixin.math.fast_util" = false
"mixin.minimal_nonvanilla.collisions.empty_space" = false
"mixin.minimal_nonvanilla.world.expiring_chunk_tickets" = false
"mixin.shapes.blockstate_cache" = false
"mixin.shapes.optimized_matching" = false
"mixin.shapes.specialized_shapes" = false
"mixin.util.block_tracking" = false
"mixin.util.entity_movement_tracking" = false
"mixin.world.block_entity_ticking" = false
"mixin.world.chunk_access" = false
"mixin.world.explosions.block_raycast" = false
"mixin.world.explosions.cache_exposure" = false
"mixin.world.temperature_cache" = false
"mixin.world.tick_scheduler" = false

View File

@@ -4,7 +4,8 @@
"mixins": [
"chunk_system.NeoForgeDistanceManagerMixin",
"chunk_system.NeoForgeMinecraftServerMixin",
"chunk_system.NeoForgeServerLevelMixin"
"chunk_system.NeoForgeServerLevelMixin",
"collisions.EntityMixin"
],
"client": [
"command.ClientCommandSourceStackMixin"

View File

@@ -19,6 +19,13 @@ public final class IntList {
return this.count;
}
public void setMinCapacity(final int len) {
final int[] byIndex = this.byIndex;
if (byIndex.length < len) {
this.byIndex = Arrays.copyOf(byIndex, len);
}
}
public int getRaw(final int index) {
return this.byIndex[index];
}

View File

@@ -0,0 +1,77 @@
package ca.spottedleaf.moonrise.common.list;
import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap;
import java.util.Arrays;
public final class ShortList {
private final Short2ShortOpenHashMap map = new Short2ShortOpenHashMap();
{
this.map.defaultReturnValue(Short.MIN_VALUE);
}
private static final short[] EMPTY_LIST = new short[0];
private short[] byIndex = EMPTY_LIST;
private short count;
public int size() {
return (int)this.count;
}
public short getRaw(final int index) {
return this.byIndex[index];
}
public void setMinCapacity(final int len) {
final short[] byIndex = this.byIndex;
if (byIndex.length < len) {
this.byIndex = Arrays.copyOf(byIndex, len);
}
}
public boolean add(final short value) {
final int count = (int)this.count;
final short currIndex = this.map.putIfAbsent(value, (short)count);
if (currIndex != Short.MIN_VALUE) {
return false; // already in this list
}
short[] list = this.byIndex;
if (list.length == count) {
// resize required
list = this.byIndex = Arrays.copyOf(list, (int)Math.max(4L, count * 2L)); // overflow results in negative
}
list[count] = value;
this.count = (short)(count + 1);
return true;
}
public boolean remove(final short value) {
final short index = this.map.remove(value);
if (index == Short.MIN_VALUE) {
return false;
}
// move the entry at the end to this index
final short endIndex = --this.count;
final short end = this.byIndex[endIndex];
if (index != endIndex) {
// not empty after this call
this.map.put(end, index);
}
this.byIndex[(int)index] = end;
this.byIndex[(int)endIndex] = (short)0;
return true;
}
public void clear() {
this.count = (short)0;
this.map.clear();
}
}

View File

@@ -4,13 +4,17 @@ import ca.spottedleaf.moonrise.common.list.ReferenceList;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.common.util.MoonriseConstants;
import ca.spottedleaf.moonrise.common.util.ChunkSystem;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData;
import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants;
import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel;
import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.ChunkPos;
import java.util.ArrayList;
public final class NearbyPlayers {
@@ -20,7 +24,27 @@ public final class NearbyPlayers {
GENERAL_REALLY_SMALL,
TICK_VIEW_DISTANCE,
VIEW_DISTANCE,
SPAWN_RANGE, // Moonrise - chunk tick iteration
// Moonrise start - chunk tick iteration
SPAWN_RANGE {
@Override
void addTo(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) {
((ChunkTickServerLevel)world).moonrise$addPlayerTickingRequest(chunkX, chunkZ);
}
@Override
void removeFrom(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) {
((ChunkTickServerLevel)world).moonrise$removePlayerTickingRequest(chunkX, chunkZ);
}
};
// Moonrise end - chunk tick iteration
void addTo(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) {
}
void removeFrom(final ServerPlayer player, final ServerLevel world, final int chunkX, final int chunkZ) {
}
}
private static final NearbyMapType[] MAP_TYPES = NearbyMapType.values();
@@ -37,6 +61,12 @@ public final class NearbyPlayers {
private final ServerLevel world;
private final Reference2ReferenceOpenHashMap<ServerPlayer, TrackedPlayer[]> players = new Reference2ReferenceOpenHashMap<>();
private final Long2ReferenceOpenHashMap<TrackedChunk> byChunk = new Long2ReferenceOpenHashMap<>();
private final Long2ReferenceOpenHashMap<ReferenceList<ServerPlayer>>[] directByChunk = new Long2ReferenceOpenHashMap[TOTAL_MAP_TYPES];
{
for (int i = 0; i < this.directByChunk.length; ++i) {
this.directByChunk[i] = new Long2ReferenceOpenHashMap<>();
}
}
public NearbyPlayers(final ServerLevel world) {
this.world = world;
@@ -70,6 +100,16 @@ public final class NearbyPlayers {
}
}
public void clear() {
if (this.players.isEmpty()) {
return;
}
for (final ServerPlayer player : new ArrayList<>(this.players.keySet())) {
this.removePlayer(player);
}
}
public void tickPlayer(final ServerPlayer player) {
final TrackedPlayer[] players = this.players.get(player);
if (players == null) {
@@ -94,38 +134,41 @@ public final class NearbyPlayers {
return this.byChunk.get(CoordinateUtils.getChunkKey(pos));
}
public ReferenceList<ServerPlayer> getPlayers(final BlockPos pos, final NearbyMapType type) {
final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos));
public TrackedChunk getChunk(final int chunkX, final int chunkZ) {
return this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
}
return chunk == null ? null : chunk.players[type.ordinal()];
public ReferenceList<ServerPlayer> getPlayers(final BlockPos pos, final NearbyMapType type) {
return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(pos));
}
public ReferenceList<ServerPlayer> getPlayers(final ChunkPos pos, final NearbyMapType type) {
final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(pos));
return chunk == null ? null : chunk.players[type.ordinal()];
return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(pos));
}
public ReferenceList<ServerPlayer> getPlayersByChunk(final int chunkX, final int chunkZ, final NearbyMapType type) {
final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
return chunk == null ? null : chunk.players[type.ordinal()];
return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
}
public ReferenceList<ServerPlayer> getPlayersByBlock(final int blockX, final int blockZ, final NearbyMapType type) {
final TrackedChunk chunk = this.byChunk.get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4));
return chunk == null ? null : chunk.players[type.ordinal()];
return this.directByChunk[type.ordinal()].get(CoordinateUtils.getChunkKey(blockX >> 4, blockZ >> 4));
}
public static final class TrackedChunk {
private static final ServerPlayer[] EMPTY_PLAYERS_ARRAY = new ServerPlayer[0];
private final long chunkKey;
private final NearbyPlayers nearbyPlayers;
private final ReferenceList<ServerPlayer>[] players = new ReferenceList[TOTAL_MAP_TYPES];
private int nonEmptyLists;
private long updateCount;
public TrackedChunk(final long chunkKey, final NearbyPlayers nearbyPlayers) {
this.chunkKey = chunkKey;
this.nearbyPlayers = nearbyPlayers;
}
public boolean isEmpty() {
return this.nonEmptyLists == 0;
}
@@ -145,7 +188,10 @@ public final class NearbyPlayers {
final ReferenceList<ServerPlayer> list = this.players[idx];
if (list == null) {
++this.nonEmptyLists;
(this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY)).add(player);
final ReferenceList<ServerPlayer> players = (this.players[idx] = new ReferenceList<>(EMPTY_PLAYERS_ARRAY));
this.nearbyPlayers.directByChunk[idx].put(this.chunkKey, players);
players.add(player);
return;
}
@@ -169,6 +215,7 @@ public final class NearbyPlayers {
if (list.size() == 0) {
this.players[idx] = null;
this.nearbyPlayers.directByChunk[idx].remove(this.chunkKey);
--this.nonEmptyLists;
}
}
@@ -187,9 +234,19 @@ public final class NearbyPlayers {
protected void addCallback(final ServerPlayer parameter, final int chunkX, final int chunkZ) {
final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);
NearbyPlayers.this.byChunk.computeIfAbsent(chunkKey, (final long keyInMap) -> {
return new TrackedChunk();
}).addPlayer(parameter, this.type);
final TrackedChunk chunk = NearbyPlayers.this.byChunk.get(chunkKey);
final NearbyMapType type = this.type;
if (chunk != null) {
chunk.addPlayer(parameter, type);
type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ);
} else {
final TrackedChunk created = new TrackedChunk(chunkKey, NearbyPlayers.this);
NearbyPlayers.this.byChunk.put(chunkKey, created);
created.addPlayer(parameter, type);
type.addTo(parameter, NearbyPlayers.this.world, chunkX, chunkZ);
((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$requestChunkData(chunkKey).nearbyPlayers = created;
}
}
@Override
@@ -201,10 +258,16 @@ public final class NearbyPlayers {
throw new IllegalStateException("Chunk should exist at " + new ChunkPos(chunkKey));
}
chunk.removePlayer(parameter, this.type);
final NearbyMapType type = this.type;
chunk.removePlayer(parameter, type);
type.removeFrom(parameter, NearbyPlayers.this.world, chunkX, chunkZ);
if (chunk.isEmpty()) {
NearbyPlayers.this.byChunk.remove(chunkKey);
final ChunkData chunkData = ((ChunkSystemLevel)NearbyPlayers.this.world).moonrise$releaseChunkData(chunkKey);
if (chunkData != null) {
chunkData.nearbyPlayers = null;
}
}
}
}

View File

@@ -6,6 +6,7 @@ import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk;
import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader;
import ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemServerChunkCache;
import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel;
import com.mojang.logging.LogUtils;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.FullChunkStatus;
@@ -121,12 +122,14 @@ public final class ChunkSystem {
}
((ServerLevel)chunk.getLevel()).startTickingChunk(chunk);
((ServerLevel)chunk.getLevel()).getChunkSource().chunkMap.tickingGenerated.incrementAndGet();
((ChunkTickServerLevel)(ServerLevel)chunk.getLevel()).moonrise$markChunkForPlayerTicking(chunk); // Moonrise - chunk tick iteration
}
public static void onChunkNotTicking(final LevelChunk chunk, final ChunkHolder holder) {
((ChunkSystemServerLevel)((ServerLevel)chunk.getLevel())).moonrise$getTickingChunks().remove(
((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
);
((ChunkTickServerLevel)(ServerLevel)chunk.getLevel()).moonrise$removeChunkForPlayerTicking(chunk); // Moonrise - chunk tick iteration
}
public static void onChunkEntityTicking(final LevelChunk chunk, final ChunkHolder holder) {

View File

@@ -35,6 +35,7 @@ public final class ConfigHolder {
program arguments.
-DMoonrise.ConfigFile=<file> - Override the config file location. Might be useful for multiple game versions.
-DMoonrise.WorkerThreadCount=<number> - Override the auto configured worker thread counts (worker-threads).
-DMoonrise.MaxViewDistance=<number> - Overrides the maximum view distance, should only use for debugging purposes.
""";
static {

View File

@@ -2,7 +2,7 @@ package ca.spottedleaf.moonrise.common.util;
public final class MoonriseConstants {
public static final int MAX_VIEW_DISTANCE = 32;
public static final int MAX_VIEW_DISTANCE = Integer.getInteger("Moonrise.MaxViewDistance", 32);
private MoonriseConstants() {}

View File

@@ -11,10 +11,18 @@ public final class WorldUtil {
return world.getMaxSection() - 1; // getMaxSection() is exclusive
}
public static int getMaxSection(final Level world) {
return world.getMaxSection() - 1; // getMaxSection() is exclusive
}
public static int getMinSection(final LevelHeightAccessor world) {
return world.getMinSection();
}
public static int getMinSection(final Level world) {
return world.getMinSection();
}
public static int getMaxLightSection(final LevelHeightAccessor world) {
return getMaxSection(world) + 1;
}

View File

@@ -3,6 +3,7 @@ package ca.spottedleaf.moonrise.mixin.block_counting;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingBitStorage;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import net.minecraft.util.BitStorage;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -18,15 +19,15 @@ interface BitStorageMixin extends BlockCountingBitStorage {
// provide default impl in case mods implement this...
@Override
public default Int2ObjectOpenHashMap<IntArrayList> moonrise$countEntries() {
final Int2ObjectOpenHashMap<IntArrayList> ret = new Int2ObjectOpenHashMap<>();
public default Int2ObjectOpenHashMap<ShortArrayList> moonrise$countEntries() {
final Int2ObjectOpenHashMap<ShortArrayList> ret = new Int2ObjectOpenHashMap<>();
final int size = this.getSize();
for (int index = 0; index < size; ++index) {
final int paletteIdx = this.get(index);
ret.computeIfAbsent(paletteIdx, (final int key) -> {
return new IntArrayList();
}).add(index);
return new ShortArrayList(64);
}).add((short)index);
}
return ret;

View File

@@ -1,13 +1,13 @@
package ca.spottedleaf.moonrise.mixin.block_counting;
import ca.spottedleaf.moonrise.common.list.IntList;
import ca.spottedleaf.moonrise.common.list.ShortList;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingBitStorage;
import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection;
import com.llamalad7.mixinextras.sugar.Local;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import net.minecraft.util.BitStorage;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunkSection;
@@ -48,26 +48,32 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
public abstract boolean maybeHas(Predicate<BlockState> predicate);
@Unique
private static final IntArrayList FULL_LIST = new IntArrayList(16*16*16);
private static final ShortArrayList FULL_LIST = new ShortArrayList(16*16*16);
static {
for (int i = 0; i < (16*16*16); ++i) {
for (short i = 0; i < (16*16*16); ++i) {
FULL_LIST.add(i);
}
}
@Unique
private int specialCollidingBlocks;
private boolean isClient;
@Unique
private final IntList tickingBlocks = new IntList();
private static final short CLIENT_FORCED_SPECIAL_COLLIDING_BLOCKS = (short)9999;
@Unique
private short specialCollidingBlocks;
@Unique
private final ShortList tickingBlocks = new ShortList();
@Override
public final int moonrise$getSpecialCollidingBlocks() {
return this.specialCollidingBlocks;
public final boolean moonrise$hasSpecialCollidingBlocks() {
return this.specialCollidingBlocks != 0;
}
@Override
public final IntList moonrise$getTickingBlockList() {
public final ShortList moonrise$getTickingBlockList() {
return this.tickingBlocks;
}
@@ -86,20 +92,35 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
if (oldState == newState) {
return;
}
if (CollisionUtil.isSpecialCollidingBlock(oldState)) {
--this.specialCollidingBlocks;
}
if (CollisionUtil.isSpecialCollidingBlock(newState)) {
++this.specialCollidingBlocks;
if (this.isClient) {
if (CollisionUtil.isSpecialCollidingBlock(newState)) {
this.specialCollidingBlocks = CLIENT_FORCED_SPECIAL_COLLIDING_BLOCKS;
}
return;
}
final int position = x | (z << 4) | (y << (4+4));
if (oldState.isRandomlyTicking()) {
this.tickingBlocks.remove(position);
final boolean isSpecialOld = CollisionUtil.isSpecialCollidingBlock(oldState);
final boolean isSpecialNew = CollisionUtil.isSpecialCollidingBlock(newState);
if (isSpecialOld != isSpecialNew) {
if (isSpecialOld) {
--this.specialCollidingBlocks;
} else {
++this.specialCollidingBlocks;
}
}
if (newState.isRandomlyTicking()) {
this.tickingBlocks.add(position);
final boolean oldTicking = oldState.isRandomlyTicking();
final boolean newTicking = newState.isRandomlyTicking();
if (oldTicking != newTicking) {
final ShortList tickingBlocks = this.tickingBlocks;
final short position = (short)(x | (z << 4) | (y << (4+4)));
if (oldTicking) {
tickingBlocks.remove(position);
} else {
tickingBlocks.add(position);
}
}
}
@@ -137,7 +158,7 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
final int paletteSize = palette.getSize();
final BitStorage storage = data.storage;
final Int2ObjectOpenHashMap<IntArrayList> counts;
final Int2ObjectOpenHashMap<ShortArrayList> counts;
if (paletteSize == 1) {
counts = new Int2ObjectOpenHashMap<>(1);
counts.put(0, FULL_LIST);
@@ -145,10 +166,10 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
counts = ((BlockCountingBitStorage)storage).moonrise$countEntries();
}
for (final Iterator<Int2ObjectMap.Entry<IntArrayList>> iterator = counts.int2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
final Int2ObjectMap.Entry<IntArrayList> entry = iterator.next();
for (final Iterator<Int2ObjectMap.Entry<ShortArrayList>> iterator = counts.int2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
final Int2ObjectMap.Entry<ShortArrayList> entry = iterator.next();
final int paletteIdx = entry.getIntKey();
final IntArrayList coordinates = entry.getValue();
final ShortArrayList coordinates = entry.getValue();
final int paletteCount = coordinates.size();
final BlockState state = palette.valueFor(paletteIdx);
@@ -158,16 +179,21 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
}
if (CollisionUtil.isSpecialCollidingBlock(state)) {
this.specialCollidingBlocks += paletteCount;
this.specialCollidingBlocks += (short)paletteCount;
}
this.nonEmptyBlockCount += paletteCount;
this.nonEmptyBlockCount += (short)paletteCount;
if (state.isRandomlyTicking()) {
this.tickingBlockCount += paletteCount;
final int[] raw = coordinates.elements();
this.tickingBlockCount += (short)paletteCount;
final short[] raw = coordinates.elements();
final int rawLen = raw.length;
Objects.checkFromToIndex(0, paletteCount, raw.length);
final ShortList tickingBlocks = this.tickingBlocks;
tickingBlocks.setMinCapacity(Math.min((rawLen + tickingBlocks.size()) * 3 / 2, 16*16*16));
Objects.checkFromToIndex(0, paletteCount, rawLen);
for (int i = 0; i < paletteCount; ++i) {
this.tickingBlocks.add(raw[i]);
tickingBlocks.add(raw[i]);
}
}
@@ -176,7 +202,7 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
if (!fluid.isEmpty()) {
//this.nonEmptyBlockCount += count; // fix vanilla bug: make non-empty block count correct
if (fluid.isRandomlyTicking()) {
this.tickingFluidCount += paletteCount;
this.tickingFluidCount += (short)paletteCount;
}
}
}
@@ -184,7 +210,7 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
}
/**
* @reason Call recalcBlockCounts on the client, as the client does not invoke it when deserializing chunk sections.
* @reason Set up special colliding blocks on the client, as it is too expensive to perform a full calculation
* @author Spottedleaf
*/
@Inject(
@@ -194,6 +220,8 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
)
)
private void callRecalcBlocksClient(final CallbackInfo ci) {
this.recalcBlockCounts();
this.isClient = true;
// force has special colliding blocks to be true
this.specialCollidingBlocks = this.nonEmptyBlockCount != (short)0 && this.maybeHas(CollisionUtil::isSpecialCollidingBlock) ? CLIENT_FORCED_SPECIAL_COLLIDING_BLOCKS : (short)0;
}
}

View File

@@ -3,6 +3,7 @@ package ca.spottedleaf.moonrise.mixin.block_counting;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingBitStorage;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import net.minecraft.util.BitStorage;
import net.minecraft.util.SimpleBitStorage;
import org.spongepowered.asm.mixin.Final;
@@ -24,43 +25,66 @@ abstract class SimpleBitStorageMixin implements BitStorage, BlockCountingBitStor
@Final
private int bits;
@Shadow
@Final
private long mask;
@Shadow
@Final
private int size;
@Override
public final Int2ObjectOpenHashMap<IntArrayList> moonrise$countEntries() {
public final Int2ObjectOpenHashMap<ShortArrayList> moonrise$countEntries() {
final int valuesPerLong = this.valuesPerLong;
final int bits = this.bits;
final long mask = this.mask;
final long mask = (1L << bits) - 1L;
final int size = this.size;
// we may be backed by global palette, so limit bits for init capacity
final Int2ObjectOpenHashMap<IntArrayList> ret = new Int2ObjectOpenHashMap<>(
1 << Math.min(6, bits)
);
if (bits <= 6) {
final ShortArrayList[] byId = new ShortArrayList[1 << bits];
final Int2ObjectOpenHashMap<ShortArrayList> ret = new Int2ObjectOpenHashMap<>(1 << bits);
int index = 0;
int index = 0;
for (long value : this.data) {
int li = 0;
do {
final int paletteIdx = (int)(value & mask);
value >>= bits;
for (long value : this.data) {
int li = 0;
do {
final int paletteIdx = (int)(value & mask);
value >>= bits;
++li;
ret.computeIfAbsent(paletteIdx, (final int key) -> {
return new IntArrayList();
}).add(index);
final ShortArrayList coords = byId[paletteIdx];
if (coords != null) {
coords.add((short)index++);
continue;
} else {
final ShortArrayList newCoords = new ShortArrayList(64);
byId[paletteIdx] = newCoords;
newCoords.add((short)index++);
ret.put(paletteIdx, newCoords);
continue;
}
} while (li < valuesPerLong && index < size);
}
++li;
++index;
} while (li < valuesPerLong && index < size);
return ret;
} else {
final Int2ObjectOpenHashMap<ShortArrayList> ret = new Int2ObjectOpenHashMap<>(
1 << 6
);
int index = 0;
for (long value : this.data) {
int li = 0;
do {
final int paletteIdx = (int)(value & mask);
value >>= bits;
++li;
ret.computeIfAbsent(paletteIdx, (final int key) -> {
return new ShortArrayList(64);
}).add((short)index++);
} while (li < valuesPerLong && index < size);
}
return ret;
}
return ret;
}
}

View File

@@ -3,6 +3,7 @@ package ca.spottedleaf.moonrise.mixin.block_counting;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingBitStorage;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
import net.minecraft.util.BitStorage;
import net.minecraft.util.ZeroBitStorage;
import org.spongepowered.asm.mixin.Final;
@@ -17,17 +18,17 @@ abstract class ZeroBitStorageMixin implements BitStorage, BlockCountingBitStorag
private int size;
@Override
public final Int2ObjectOpenHashMap<IntArrayList> moonrise$countEntries() {
public final Int2ObjectOpenHashMap<ShortArrayList> moonrise$countEntries() {
final int size = this.size;
final int[] raw = new int[size];
final short[] raw = new short[size];
for (int i = 0; i < size; ++i) {
raw[i] = i;
raw[i] = (short)i;
}
final IntArrayList coordinates = IntArrayList.wrap(raw, size);
final ShortArrayList coordinates = ShortArrayList.wrap(raw, size);
final Int2ObjectOpenHashMap<IntArrayList> ret = new Int2ObjectOpenHashMap<>(1);
final Int2ObjectOpenHashMap<ShortArrayList> ret = new Int2ObjectOpenHashMap<>(1);
ret.put(0, coordinates);
return ret;
}

View File

@@ -66,6 +66,9 @@ abstract class ChunkHolderMixin extends GenerationChunkHolder implements ChunkSy
@Unique
private final ReferenceList<ServerPlayer> playersSentChunkTo = new ReferenceList<>(EMPTY_PLAYER_ARRAY);
@Unique
private boolean isMarkedDirtyForPlayers;
@Unique
private ChunkMap getChunkMap() {
return (ChunkMap)this.playerProvider;
@@ -120,6 +123,16 @@ abstract class ChunkHolderMixin extends GenerationChunkHolder implements ChunkSy
return ret;
}
@Override
public final boolean moonrise$isMarkedDirtyForPlayers() {
return this.isMarkedDirtyForPlayers;
}
@Override
public final void moonrise$markDirtyForPlayers(final boolean value) {
this.isMarkedDirtyForPlayers = value;
}
@Unique
private static final ServerPlayer[] EMPTY_PLAYER_ARRAY = new ServerPlayer[0];
@@ -287,7 +300,14 @@ abstract class ChunkHolderMixin extends GenerationChunkHolder implements ChunkSy
// no players to sent to, so don't need to update anything
return null;
}
return this.getChunkToSend();
final LevelChunk ret = this.getChunkToSend();
if (ret != null) {
((ChunkSystemServerLevel)this.getChunkMap().level).moonrise$addUnsyncedChunk((ChunkHolder)(Object)this);
return ret;
}
return ret;
}
/**
@@ -302,7 +322,18 @@ abstract class ChunkHolderMixin extends GenerationChunkHolder implements ChunkSy
)
)
private LevelChunk redirectLightUpdate(final ChunkHolder instance) {
return this.getChunkToSend();
if (this.playersSentChunkTo.size() == 0) {
// no players to sent to, so don't need to update anything
return null;
}
final LevelChunk ret = this.getChunkToSend();
if (ret != null) {
((ChunkSystemServerLevel)this.getChunkMap().level).moonrise$addUnsyncedChunk((ChunkHolder)(Object)this);
return ret;
}
return ret;
}
/**

View File

@@ -63,6 +63,21 @@ abstract class ClientLevelMixin extends Level implements ChunkSystemLevel {
this.moonrise$setEntityLookup(new ClientEntityLookup(this, ((ClientLevel)(Object)this).new EntityCallbacks()));
}
@Override
public final boolean moonrise$areChunksLoaded(final int fromX, final int fromZ, final int toX, final int toZ) {
final ClientChunkCache chunkSource = this.chunkSource;
for (int currZ = fromZ; currZ <= toZ; ++currZ) {
for (int currX = fromX; currX <= toX; ++currX) {
if (!chunkSource.hasChunk(currX, currZ)) {
return false;
}
}
}
return true;
}
/**
* @reason Redirect to new entity manager
* @author Spottedleaf

View File

@@ -1,5 +1,6 @@
package ca.spottedleaf.moonrise.mixin.chunk_system;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData;
import ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel;
import com.google.common.collect.ImmutableList;
@@ -49,6 +50,9 @@ abstract class EntityMixin implements ChunkSystemEntity {
@Unique
private FullChunkStatus chunkStatus;
@Unique
private ChunkData chunkData;
@Unique
private int sectionX = Integer.MIN_VALUE;
@@ -76,6 +80,16 @@ abstract class EntityMixin implements ChunkSystemEntity {
this.chunkStatus = status;
}
@Override
public final ChunkData moonrise$getChunkData() {
return this.chunkData;
}
@Override
public final void moonrise$setChunkData(final ChunkData chunkData) {
this.chunkData = chunkData;
}
@Override
public final int moonrise$getSectionX() {
return this.sectionX;

View File

@@ -99,8 +99,18 @@ abstract class EntityTickListMixin {
* @reason Route to new entity list
* @author Spottedleaf
*/
@Overwrite
public void forEach(final Consumer<Entity> action) {
@Inject(
method = "forEach",
at = @At("HEAD"),
cancellable = true
)
private void injectForEach(final Consumer<Entity> consumer, final CallbackInfo ci) {
this.forEach(consumer);
ci.cancel();
}
@Unique
private void forEach(final Consumer<Entity> action) {
// To ensure nothing weird happens with dimension travelling, do not iterate over new entries...
// (by dfl iterator() is configured to not iterate over new entries)
final IteratorSafeOrderedReferenceSet.Iterator<Entity> iterator = this.entities.iterator();

View File

@@ -1,6 +1,9 @@
package ca.spottedleaf.moonrise.mixin.chunk_system;
import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel;
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup;
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl.DefaultEntityLookup;
@@ -13,6 +16,7 @@ import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.entity.EntityTypeTest;
@@ -46,6 +50,9 @@ abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityGetter,
@Unique
private EntityLookup entityLookup;
@Unique
private final ConcurrentLong2ReferenceChainedHashTable<ChunkData> chunkData = new ConcurrentLong2ReferenceChainedHashTable<>();
@Override
public final EntityLookup moonrise$getEntityLookup() {
return this.entityLookup;
@@ -217,6 +224,52 @@ abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityGetter,
// no-op on ClientLevel
}
@Override
public final ChunkData moonrise$getChunkData(final long chunkKey) {
return this.chunkData.get(chunkKey);
}
@Override
public final ChunkData moonrise$getChunkData(final int chunkX, final int chunkZ) {
return this.chunkData.get(CoordinateUtils.getChunkKey(chunkX, chunkZ));
}
@Override
public final ChunkData moonrise$requestChunkData(final long chunkKey) {
return this.chunkData.compute(chunkKey, (final long keyInMap, final ChunkData valueInMap) -> {
if (valueInMap == null) {
final ChunkData ret = new ChunkData();
ret.increaseRef();
return ret;
}
valueInMap.increaseRef();
return valueInMap;
});
}
@Override
public final ChunkData moonrise$releaseChunkData(final long chunkKey) {
return this.chunkData.compute(chunkKey, (final long keyInMap, final ChunkData chunkData) -> {
return chunkData.decreaseRef() == 0 ? null : chunkData;
});
}
@Override
public boolean moonrise$areChunksLoaded(final int fromX, final int fromZ, final int toX, final int toZ) {
final ChunkSource chunkSource = this.getChunkSource();
for (int currZ = fromZ; currZ <= toZ; ++currZ) {
for (int currX = fromX; currX <= toX; ++currX) {
if (!chunkSource.hasChunk(currX, currZ)) {
return false;
}
}
}
return true;
}
/**
* @reason Declare method in this class so that any invocations are virtual, and not interface.
* @author Spottedleaf
@@ -226,6 +279,13 @@ abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityGetter,
return this.getChunkSource().hasChunk(x, z);
}
@Override
public boolean hasChunksAt(final int minBlockX, final int minBlockZ, final int maxBlockX, final int maxBlockZ) {
return this.moonrise$areChunksLoaded(
minBlockX >> 4, minBlockZ >> 4, maxBlockX >> 4, maxBlockZ >> 4
);
}
/**
* @reason Turn all getChunk(x, z, status) calls into virtual invokes, instead of interface invokes:
* 1. The interface invoke is expensive

View File

@@ -3,6 +3,7 @@ package ca.spottedleaf.moonrise.mixin.chunk_system;
import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTable;
import ca.spottedleaf.concurrentutil.util.Priority;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.common.list.ReferenceList;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.common.util.TickThread;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
@@ -33,6 +34,8 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
@@ -282,7 +285,8 @@ abstract class ServerChunkCacheMixin extends ChunkSource implements ChunkSystemS
*/
@Overwrite
public void getFullChunk(final long pos, final Consumer<LevelChunk> consumer) {
final LevelChunk fullChunk = this.getChunkNow(CoordinateUtils.getChunkX(pos), CoordinateUtils.getChunkZ(pos));
// note: bypass currentlyLoaded from getChunkNow
final LevelChunk fullChunk = this.fullChunks.get(pos);
if (fullChunk != null) {
consumer.accept(fullChunk);
}
@@ -356,4 +360,35 @@ abstract class ServerChunkCacheMixin extends ChunkSource implements ChunkSystemS
private boolean shortShouldTickBlocks(final ServerLevel instance, final long pos) {
return true;
}
/**
* @reason Since chunks in non-simulation range are only brought up to FULL status, not TICKING,
* those chunks may not be present in the ticking list and as a result we need to use our own list
* to ensure these chunks broadcast changes
* @author Spottedleaf
*/
@Redirect(
method = "tickChunks",
at = @At(
value = "INVOKE",
target = "Ljava/util/List;forEach(Ljava/util/function/Consumer;)V"
)
)
private void fixBroadcastChanges(final List<ServerChunkCache.ChunkAndHolder> instance,
final Consumer<ServerChunkCache.ChunkAndHolder> consumer) {
final ReferenceList<ChunkHolder> unsyncedChunks = ((ChunkSystemServerLevel)this.level).moonrise$getUnsyncedChunks();
final ChunkHolder[] chunkHolders = unsyncedChunks.getRawDataUnchecked();
final int totalUnsyncedChunks = unsyncedChunks.size();
Objects.checkFromToIndex(0, totalUnsyncedChunks, chunkHolders.length);
for (int i = 0; i < totalUnsyncedChunks; ++i) {
final ChunkHolder chunkHolder = chunkHolders[i];
final LevelChunk chunk = chunkHolder.getChunkToSend();
if (chunk != null) {
chunkHolder.broadcastChanges(chunk);
}
}
((ChunkSystemServerLevel)this.level).moonrise$clearUnsyncedChunks();
}
}

View File

@@ -10,6 +10,7 @@ import ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.EntityData
import ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.PoiDataController;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevelReader;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder;
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup;
import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager;
@@ -26,6 +27,7 @@ import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.DistanceManager;
import net.minecraft.server.level.ServerChunkCache;
@@ -62,6 +64,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
@@ -120,6 +123,9 @@ abstract class ServerLevelMixin extends Level implements ChunkSystemServerLevel,
@Unique
private static final ServerChunkCache.ChunkAndHolder[] EMPTY_CHUNK_AND_HOLDERS = new ServerChunkCache.ChunkAndHolder[0];
@Unique
private static final ChunkHolder[] EMPTY_CHUNK_HOLDERS = new ChunkHolder[0];
@Unique
private final ReferenceList<ServerChunkCache.ChunkAndHolder> loadedChunks = new ReferenceList<>(EMPTY_CHUNK_AND_HOLDERS);
@@ -129,6 +135,9 @@ abstract class ServerLevelMixin extends Level implements ChunkSystemServerLevel,
@Unique
private final ReferenceList<ServerChunkCache.ChunkAndHolder> entityTickingChunks = new ReferenceList<>(EMPTY_CHUNK_AND_HOLDERS);
@Unique
private final ReferenceList<ChunkHolder> unsyncedChunks = new ReferenceList<>(EMPTY_CHUNK_HOLDERS);
/**
* @reason Initialise fields / destroy entity manager state
* @author Spottedleaf
@@ -345,6 +354,60 @@ abstract class ServerLevelMixin extends Level implements ChunkSystemServerLevel,
return this.entityTickingChunks;
}
@Override
public final ReferenceList<ChunkHolder> moonrise$getUnsyncedChunks() {
return this.unsyncedChunks;
}
@Override
public final void moonrise$addUnsyncedChunk(final ChunkHolder chunkHolder) {
if (((ChunkSystemChunkHolder)chunkHolder).moonrise$isMarkedDirtyForPlayers()) {
return;
}
((ChunkSystemChunkHolder)chunkHolder).moonrise$markDirtyForPlayers(true);
this.unsyncedChunks.add(chunkHolder);
}
@Override
public final void moonrise$removeUnsyncedChunk(final ChunkHolder chunkHolder) {
if (!((ChunkSystemChunkHolder)chunkHolder).moonrise$isMarkedDirtyForPlayers()) {
return;
}
((ChunkSystemChunkHolder)chunkHolder).moonrise$markDirtyForPlayers(false);
this.unsyncedChunks.remove(chunkHolder);
}
@Override
public final void moonrise$clearUnsyncedChunks() {
final ChunkHolder[] chunkHolders = this.unsyncedChunks.getRawDataUnchecked();
final int totalUnsyncedChunks = this.unsyncedChunks.size();
Objects.checkFromToIndex(0, totalUnsyncedChunks, chunkHolders.length);
for (int i = 0; i < totalUnsyncedChunks; ++i) {
final ChunkHolder chunkHolder = chunkHolders[i];
((ChunkSystemChunkHolder)chunkHolder).moonrise$markDirtyForPlayers(false);
}
this.unsyncedChunks.clear();
}
@Override
public final boolean moonrise$areChunksLoaded(final int fromX, final int fromZ, final int toX, final int toZ) {
final ServerChunkCache chunkSource = this.chunkSource;
for (int currZ = fromZ; currZ <= toZ; ++currZ) {
for (int currX = fromX; currX <= toX; ++currX) {
if (!chunkSource.hasChunk(currX, currZ)) {
return false;
}
}
}
return true;
}
/**
* @reason Declare method in this class so that any invocations are virtual, and not interface.
* @author Spottedleaf

View File

@@ -33,7 +33,7 @@ abstract class ChunkMapMixin {
public ServerLevel level;
@Shadow
protected abstract boolean playerIsCloseEnoughForSpawning(ServerPlayer serverPlayer, ChunkPos chunkPos);
public abstract boolean playerIsCloseEnoughForSpawning(ServerPlayer serverPlayer, ChunkPos chunkPos);
/**
* @reason Hook for updating the spawn tracker in distance manager. We add our own hook instead of using the
@@ -116,15 +116,15 @@ abstract class ChunkMapMixin {
*/
@Overwrite
public List<ServerPlayer> getPlayersCloseForSpawning(final ChunkPos pos) {
final List<ServerPlayer> ret = new ArrayList<>();
final ReferenceList<ServerPlayer> players = ((ChunkSystemServerLevel)this.level).moonrise$getNearbyPlayers().getPlayers(
pos, NearbyPlayers.NearbyMapType.SPAWN_RANGE
);
if (players == null) {
return ret;
return new ArrayList<>();
}
List<ServerPlayer> ret = null;
final ServerPlayer[] raw = players.getRawDataUnchecked();
final int len = players.size();
@@ -132,10 +132,15 @@ abstract class ChunkMapMixin {
for (int i = 0; i < len; ++i) {
final ServerPlayer player = raw[i];
if (this.playerIsCloseEnoughForSpawning(player, pos)) {
ret.add(player);
if (ret == null) {
ret = new ArrayList<>(len - i);
ret.add(player);
} else {
ret.add(player);
}
}
}
return ret;
return ret == null ? new ArrayList<>() : ret;
}
}

View File

@@ -1,20 +1,35 @@
package ca.spottedleaf.moonrise.mixin.chunk_tick_iteration;
import ca.spottedleaf.moonrise.common.list.ReferenceList;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
import ca.spottedleaf.moonrise.common.misc.NearbyPlayers;
import ca.spottedleaf.moonrise.common.util.SimpleRandom;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk;
import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel;
import com.llamalad7.mixinextras.sugar.Local;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.Util;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.LevelChunk;
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.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
@@ -30,6 +45,12 @@ abstract class ServerChunkCacheMixin extends ChunkSource {
@Unique
private ServerChunkCache.ChunkAndHolder[] iterationCopy;
@Unique
private int iterationCopyLen;
@Unique
private final SimpleRandom shuffleRandom = new SimpleRandom(0L);
/**
* @reason Avoid creating the list, which is sized at the chunkholder count. The actual number of ticking
* chunks is always lower. The mixin below will initialise the list to non-null.
@@ -60,7 +81,7 @@ abstract class ServerChunkCacheMixin extends ChunkSource {
)
private List<ServerChunkCache.ChunkAndHolder> initTickChunks(final List<ServerChunkCache.ChunkAndHolder> shouldBeNull) {
final ReferenceList<ServerChunkCache.ChunkAndHolder> tickingChunks =
((ChunkSystemServerLevel)this.level).moonrise$getTickingChunks();
((ChunkTickServerLevel)this.level).moonrise$getPlayerTickingChunks();
final ServerChunkCache.ChunkAndHolder[] raw = tickingChunks.getRawDataUnchecked();
final int size = tickingChunks.size();
@@ -68,6 +89,7 @@ abstract class ServerChunkCacheMixin extends ChunkSource {
if (this.iterationCopy == null || this.iterationCopy.length < size) {
this.iterationCopy = new ServerChunkCache.ChunkAndHolder[raw.length];
}
this.iterationCopyLen = size;
System.arraycopy(raw, 0, this.iterationCopy, 0, size);
return ObjectArrayList.wrap(
@@ -75,6 +97,23 @@ abstract class ServerChunkCacheMixin extends ChunkSource {
);
}
/**
* @reason Use random implementation which does not use CAS and has a faster nextInt(int)
* function
* @author Spottedleaf
*/
@Redirect(
method = "tickChunks",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/Util;shuffle(Ljava/util/List;Lnet/minecraft/util/RandomSource;)V"
)
)
private <T> void useBetterRandom(final List<T> list, final RandomSource randomSource) {
this.shuffleRandom.setSeed(randomSource.nextLong());
Util.shuffle(list, this.shuffleRandom);
}
/**
* @reason Do not initialise ticking chunk list, as we did that above.
* @author Spottedleaf
@@ -92,29 +131,59 @@ abstract class ServerChunkCacheMixin extends ChunkSource {
}
/**
* @reason Clear the iteration array, and at the same time broadcast chunk changes.
* @reason Use the nearby players cache
* @author Spottedleaf
*/
@Redirect(
method = "tickChunks",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/server/level/ChunkMap;anyPlayerCloseEnoughForSpawning(Lnet/minecraft/world/level/ChunkPos;)Z"
)
)
private boolean useNearbyCache(final ChunkMap instance, final ChunkPos chunkPos,
@Local(ordinal = 0, argsOnly = false) final LevelChunk levelChunk) {
final ChunkData chunkData =
((ChunkSystemChunkHolder)((ChunkSystemLevelChunk)levelChunk).moonrise$getChunkAndHolder().holder())
.moonrise$getRealChunkHolder().holderData;
final NearbyPlayers.TrackedChunk nearbyPlayers = chunkData.nearbyPlayers;
if (nearbyPlayers == null) {
return false;
}
final ReferenceList<ServerPlayer> players = nearbyPlayers.getPlayers(NearbyPlayers.NearbyMapType.SPAWN_RANGE);
if (players == null) {
return false;
}
final ServerPlayer[] raw = players.getRawDataUnchecked();
final int len = players.size();
Objects.checkFromIndexSize(0, len, raw.length);
for (int i = 0; i < len; ++i) {
if (instance.playerIsCloseEnoughForSpawning(raw[i], chunkPos)) {
return true;
}
}
return false;
}
/**
* @reason Clear the iteration array after the list is done being used.
* @author Spottedleaf
*/
@Inject(
method = "tickChunks",
at = @At(
value = "INVOKE",
target = "Ljava/util/List;forEach(Ljava/util/function/Consumer;)V",
ordinal = 0
ordinal = 0,
shift = At.Shift.AFTER
)
)
private void broadcastChanges(final List<ServerChunkCache.ChunkAndHolder> instance,
final Consumer<ServerChunkCache.ChunkAndHolder> consumer) {
final ObjectArrayList<ServerChunkCache.ChunkAndHolder> chunks = (ObjectArrayList<ServerChunkCache.ChunkAndHolder>)instance;
final ServerChunkCache.ChunkAndHolder[] raw = chunks.elements();
final int size = chunks.size();
Objects.checkFromToIndex(0, size, raw.length);
for (int i = 0; i < size; ++i) {
final ServerChunkCache.ChunkAndHolder holder = raw[i];
raw[i] = null;
holder.holder().broadcastChanges(holder.chunk());
}
private void broadcastChanges(final CallbackInfo ci) {
Arrays.fill(this.iterationCopy, 0, this.iterationCopyLen, null);
}
}

View File

@@ -0,0 +1,100 @@
package ca.spottedleaf.moonrise.mixin.chunk_tick_iteration;
import ca.spottedleaf.moonrise.common.list.ReferenceList;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.common.util.TickThread;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.LevelChunk;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(ServerLevel.class)
abstract class ServerLevelMixin implements ChunkTickServerLevel {
@Unique
private static final ServerChunkCache.ChunkAndHolder[] EMPTY_PLAYER_CHUNK_HOLDERS = new ServerChunkCache.ChunkAndHolder[0];
@Unique
private final ReferenceList<ServerChunkCache.ChunkAndHolder> playerTickingChunks = new ReferenceList<>(EMPTY_PLAYER_CHUNK_HOLDERS);
@Unique
private final Long2IntOpenHashMap playerTickingRequests = new Long2IntOpenHashMap();
@Override
public final ReferenceList<ServerChunkCache.ChunkAndHolder> moonrise$getPlayerTickingChunks() {
return this.playerTickingChunks;
}
@Override
public final void moonrise$markChunkForPlayerTicking(final LevelChunk chunk) {
final ChunkPos pos = chunk.getPos();
if (!this.playerTickingRequests.containsKey(CoordinateUtils.getChunkKey(pos))) {
return;
}
this.playerTickingChunks.add(((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder());
}
@Override
public final void moonrise$removeChunkForPlayerTicking(final LevelChunk chunk) {
this.playerTickingChunks.remove(((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder());
}
@Override
public final void moonrise$addPlayerTickingRequest(final int chunkX, final int chunkZ) {
TickThread.ensureTickThread((ServerLevel)(Object)this, chunkX, chunkZ, "Cannot add ticking request async");
final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);
if (this.playerTickingRequests.addTo(chunkKey, 1) != 0) {
// already added
return;
}
final NewChunkHolder chunkHolder = ((ChunkSystemServerLevel)(ServerLevel)(Object)this).moonrise$getChunkTaskScheduler()
.chunkHolderManager.getChunkHolder(chunkKey);
if (chunkHolder == null || !chunkHolder.isTickingReady()) {
return;
}
this.playerTickingChunks.add(
((ChunkSystemLevelChunk)(LevelChunk)chunkHolder.getCurrentChunk()).moonrise$getChunkAndHolder()
);
}
@Override
public final void moonrise$removePlayerTickingRequest(final int chunkX, final int chunkZ) {
TickThread.ensureTickThread((ServerLevel)(Object)this, chunkX, chunkZ, "Cannot remove ticking request async");
final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ);
final int val = this.playerTickingRequests.addTo(chunkKey, -1);
if (val <= 0) {
throw new IllegalStateException("Negative counter");
}
if (val != 1) {
// still has at least one request
return;
}
final NewChunkHolder chunkHolder = ((ChunkSystemServerLevel)(ServerLevel)(Object)this).moonrise$getChunkTaskScheduler()
.chunkHolderManager.getChunkHolder(chunkKey);
if (chunkHolder == null || !chunkHolder.isTickingReady()) {
return;
}
this.playerTickingChunks.remove(
((ChunkSystemLevelChunk)(LevelChunk)chunkHolder.getCurrentChunk()).moonrise$getChunkAndHolder()
);
}
}

View File

@@ -4,10 +4,14 @@ import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(BlockPos.class)
abstract class BlockPosMixin extends Vec3i {
@Shadow
public abstract BlockPos immutable();
public BlockPosMixin(int i, int j, int k) {
super(i, j, k);
}
@@ -29,7 +33,7 @@ abstract class BlockPosMixin extends Vec3i {
@Overwrite
@Override
public BlockPos above(final int distance) {
return distance == 0 ? (BlockPos)(Object)this : new BlockPos(this.getX(), this.getY() + distance, this.getZ());
return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY() + distance, this.getZ());
}
/**
@@ -49,7 +53,7 @@ abstract class BlockPosMixin extends Vec3i {
@Overwrite
@Override
public BlockPos below(final int distance) {
return distance == 0 ? (BlockPos)(Object)this : new BlockPos(this.getX(), this.getY() - distance, this.getZ());
return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY() - distance, this.getZ());
}
/**
@@ -69,7 +73,7 @@ abstract class BlockPosMixin extends Vec3i {
@Overwrite
@Override
public BlockPos north(final int distance) {
return distance == 0 ? (BlockPos)(Object)this : new BlockPos(this.getX(), this.getY(), this.getZ() - distance);
return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY(), this.getZ() - distance);
}
/**
@@ -89,7 +93,7 @@ abstract class BlockPosMixin extends Vec3i {
@Overwrite
@Override
public BlockPos south(final int distance) {
return distance == 0 ? (BlockPos)(Object)this : new BlockPos(this.getX(), this.getY(), this.getZ() + distance);
return distance == 0 ? this.immutable() : new BlockPos(this.getX(), this.getY(), this.getZ() + distance);
}
/**
@@ -109,7 +113,7 @@ abstract class BlockPosMixin extends Vec3i {
@Overwrite
@Override
public BlockPos west(final int distance) {
return distance == 0 ? (BlockPos)(Object)this : new BlockPos(this.getX() - distance, this.getY(), this.getZ());
return distance == 0 ? this.immutable() : new BlockPos(this.getX() - distance, this.getY(), this.getZ());
}
/**
@@ -129,6 +133,6 @@ abstract class BlockPosMixin extends Vec3i {
@Overwrite
@Override
public BlockPos east(final int distance) {
return distance == 0 ? (BlockPos)(Object)this : new BlockPos(this.getX() + distance, this.getY(), this.getZ());
return distance == 0 ? this.immutable() : new BlockPos(this.getX() + distance, this.getY(), this.getZ());
}
}

View File

@@ -1,20 +1,24 @@
package ca.spottedleaf.moonrise.mixin.collisions;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.common.util.WorldUtil;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel;
import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
import ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState;
import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape;
import ca.spottedleaf.moonrise.patches.collisions.util.NoneMatchStream;
import it.unimi.dsi.fastutil.floats.FloatArraySet;
import it.unimi.dsi.fastutil.floats.FloatArrays;
import net.minecraft.core.BlockPos;
import net.minecraft.tags.BlockTags;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
@@ -23,8 +27,11 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
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.Redirect;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
@Mixin(Entity.class)
abstract class EntityMixin {
@@ -48,7 +55,13 @@ abstract class EntityMixin {
public abstract Vec3 getEyePosition();
@Shadow
public abstract boolean onGround();
private boolean onGround;
@Shadow
public abstract boolean isAlive();
@Shadow
protected abstract void onInsideBlock(final BlockState blockState);
@Unique
private static float[] calculateStepHeights(final AABB box, final List<VoxelShape> voxels, final List<AABB> aabbs, final float stepHeight,
@@ -144,9 +157,9 @@ abstract class EntityMixin {
final boolean collidedDownwards = collidedY && movement.y < 0.0;
final double stepHeight = (double)this.maxUpStep();
final double stepHeight;
if (stepHeight <= 0.0 || (!collidedDownwards && !this.onGround()) || (!collidedX && !collidedZ)) {
if ((!collidedDownwards && !this.onGround) || (!collidedX && !collidedZ) || (stepHeight = (double)this.maxUpStep()) <= 0.0) {
return collided;
}
@@ -184,68 +197,262 @@ abstract class EntityMixin {
return false;
}
final float reducedWith = this.dimensions.width() * 0.8F;
final AABB box = AABB.ofSize(this.getEyePosition(), reducedWith, 1.0E-6D, reducedWith);
final double reducedWith = (double)(this.dimensions.width() * 0.8F);
final AABB boundingBox = AABB.ofSize(this.getEyePosition(), reducedWith, 1.0E-6D, reducedWith);
final Level world = this.level;
if (CollisionUtil.isEmpty(box)) {
if (CollisionUtil.isEmpty(boundingBox)) {
return false;
}
final BlockPos.MutableBlockPos tempPos = new BlockPos.MutableBlockPos();
final int minBlockX = Mth.floor(boundingBox.minX);
final int minBlockY = Mth.floor(boundingBox.minY);
final int minBlockZ = Mth.floor(boundingBox.minZ);
final int minX = Mth.floor(box.minX);
final int minY = Mth.floor(box.minY);
final int minZ = Mth.floor(box.minZ);
final int maxX = Mth.floor(box.maxX);
final int maxY = Mth.floor(box.maxY);
final int maxZ = Mth.floor(box.maxZ);
final int maxBlockX = Mth.floor(boundingBox.maxX);
final int maxBlockY = Mth.floor(boundingBox.maxY);
final int maxBlockZ = Mth.floor(boundingBox.maxZ);
final ChunkSource chunkProvider = this.level.getChunkSource();
final int minChunkX = minBlockX >> 4;
final int minChunkY = minBlockY >> 4;
final int minChunkZ = minBlockZ >> 4;
long lastChunkKey = ChunkPos.INVALID_CHUNK_POS;
LevelChunk lastChunk = null;
for (int fz = minZ; fz <= maxZ; ++fz) {
tempPos.setZ(fz);
for (int fx = minX; fx <= maxX; ++fx) {
final int newChunkX = fx >> 4;
final int newChunkZ = fz >> 4;
final LevelChunk chunk = lastChunkKey == (lastChunkKey = CoordinateUtils.getChunkKey(newChunkX, newChunkZ)) ?
lastChunk : (lastChunk = (LevelChunk)chunkProvider.getChunk(newChunkX, newChunkZ, ChunkStatus.FULL, true));
tempPos.setX(fx);
for (int fy = minY; fy <= maxY; ++fy) {
tempPos.setY(fy);
final int maxChunkX = maxBlockX >> 4;
final int maxChunkY = maxBlockY >> 4;
final int maxChunkZ = maxBlockZ >> 4;
final BlockState state = chunk.getBlockState(tempPos);
final int minSection = WorldUtil.getMinSection(world);
final ChunkSource chunkSource = world.getChunkSource();
final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
if (((CollisionBlockState)state).moonrise$emptyCollisionShape() || !state.isSuffocating(this.level, tempPos)) {
for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
final LevelChunkSection[] sections = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, true).getSections();
for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) {
final int sectionIdx = currChunkY - minSection;
if (sectionIdx < 0 || sectionIdx >= sections.length) {
continue;
}
final LevelChunkSection section = sections[sectionIdx];
if (section.hasOnlyAir()) {
// empty
continue;
}
// Yes, it does not use the Entity context stuff.
final VoxelShape collisionShape = state.getCollisionShape(this.level, tempPos);
final PalettedContainer<BlockState> blocks = section.states;
if (collisionShape.isEmpty()) {
continue;
}
final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) : 0;
final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) : 15;
final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) : 0;
final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) : 15;
final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) : 0;
final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) : 15;
final AABB toCollide = box.move(-(double)fx, -(double)fy, -(double)fz);
for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
final int blockY = currY | (currChunkY << 4);
mutablePos.setY(blockY);
for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) {
final int blockZ = currZ | (currChunkZ << 4);
mutablePos.setZ(blockZ);
for (int currX = minXIterate; currX <= maxXIterate; ++currX) {
final int blockX = currX | (currChunkX << 4);
mutablePos.setX(blockX);
final AABB singleAABB = ((CollisionVoxelShape)collisionShape).moonrise$getSingleAABBRepresentation();
if (singleAABB != null) {
if (CollisionUtil.voxelShapeIntersect(singleAABB, toCollide)) {
return true;
final BlockState blockState = blocks.get((currX) | (currZ << 4) | ((currY) << 8));
if (((CollisionBlockState)blockState).moonrise$emptyCollisionShape()
|| !blockState.isSuffocating(world, mutablePos)) {
continue;
}
// Yes, it does not use the Entity context stuff.
final VoxelShape collisionShape = blockState.getCollisionShape(world, mutablePos);
if (collisionShape.isEmpty()) {
continue;
}
final AABB toCollide = boundingBox.move(-(double)blockX, -(double)blockY, -(double)blockZ);
final AABB singleAABB = ((CollisionVoxelShape)collisionShape).moonrise$getSingleAABBRepresentation();
if (singleAABB != null) {
if (CollisionUtil.voxelShapeIntersect(singleAABB, toCollide)) {
return true;
}
continue;
}
if (CollisionUtil.voxelShapeIntersectNoEmpty(collisionShape, toCollide)) {
return true;
}
continue;
}
}
continue;
}
if (CollisionUtil.voxelShapeIntersectNoEmpty(collisionShape, toCollide)) {
return true;
}
continue;
}
}
}
return false;
}
/**
* @reason Avoid streams for retrieving blocks
* @author Spottedleaf
*/
@Redirect(
method = "move",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/Level;getBlockStatesIfLoaded(Lnet/minecraft/world/phys/AABB;)Ljava/util/stream/Stream;",
ordinal = 0
)
)
private <T> Stream<T> avoidStreams(final Level world, final AABB boundingBox) {
final int minBlockX = Mth.floor(boundingBox.minX);
final int minBlockY = Mth.floor(boundingBox.minY);
final int minBlockZ = Mth.floor(boundingBox.minZ);
final int maxBlockX = Mth.floor(boundingBox.maxX);
final int maxBlockY = Mth.floor(boundingBox.maxY);
final int maxBlockZ = Mth.floor(boundingBox.maxZ);
final int minChunkX = minBlockX >> 4;
final int minChunkY = minBlockY >> 4;
final int minChunkZ = minBlockZ >> 4;
final int maxChunkX = maxBlockX >> 4;
final int maxChunkY = maxBlockY >> 4;
final int maxChunkZ = maxBlockZ >> 4;
if (!((ChunkSystemLevel)world).moonrise$areChunksLoaded(minChunkX, minChunkZ, maxChunkX, maxChunkZ)) {
return new NoneMatchStream<>(true);
}
final int minSection = WorldUtil.getMinSection(world);
final ChunkSource chunkSource = world.getChunkSource();
for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
final LevelChunkSection[] sections = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, false).getSections();
for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) {
final int sectionIdx = currChunkY - minSection;
if (sectionIdx < 0 || sectionIdx >= sections.length) {
continue;
}
final LevelChunkSection section = sections[sectionIdx];
if (section.hasOnlyAir()) {
// empty
continue;
}
final PalettedContainer<BlockState> blocks = section.states;
final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) : 0;
final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) : 15;
final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) : 0;
final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) : 15;
final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) : 0;
final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) : 15;
for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) {
for (int currX = minXIterate; currX <= maxXIterate; ++currX) {
final BlockState blockState = blocks.get((currX) | (currZ << 4) | ((currY) << 8));
if (blockState.is(Blocks.LAVA) || blockState.is(BlockTags.FIRE)) {
return new NoneMatchStream<>(false);
}
}
}
}
}
}
}
return new NoneMatchStream<>(true);
}
/**
* @reason Retrieve blocks more efficiently
* @author Spottedleaf
*/
@Overwrite
public void checkInsideBlocks() {
final AABB boundingBox = this.getBoundingBox();
final int minBlockX = Mth.floor(boundingBox.minX + CollisionUtil.COLLISION_EPSILON);
final int minBlockY = Mth.floor(boundingBox.minY + CollisionUtil.COLLISION_EPSILON);
final int minBlockZ = Mth.floor(boundingBox.minZ + CollisionUtil.COLLISION_EPSILON);
final int maxBlockX = Mth.floor(boundingBox.maxX - CollisionUtil.COLLISION_EPSILON);
final int maxBlockY = Mth.floor(boundingBox.maxY - CollisionUtil.COLLISION_EPSILON);
final int maxBlockZ = Mth.floor(boundingBox.maxZ - CollisionUtil.COLLISION_EPSILON);
final int minChunkX = minBlockX >> 4;
final int minChunkY = minBlockY >> 4;
final int minChunkZ = minBlockZ >> 4;
final int maxChunkX = maxBlockX >> 4;
final int maxChunkY = maxBlockY >> 4;
final int maxChunkZ = maxBlockZ >> 4;
final Level world = this.level;
if (!((ChunkSystemLevel)world).moonrise$areChunksLoaded(minChunkX, minChunkZ, maxChunkX, maxChunkZ)) {
return;
}
final int minSection = WorldUtil.getMinSection(world);
final ChunkSource chunkSource = world.getChunkSource();
final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
final LevelChunkSection[] sections = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, false).getSections();
for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) {
final int sectionIdx = currChunkY - minSection;
if (sectionIdx < 0 || sectionIdx >= sections.length) {
continue;
}
final LevelChunkSection section = sections[sectionIdx];
if (section.hasOnlyAir()) {
// empty
continue;
}
final PalettedContainer<BlockState> blocks = section.states;
final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) : 0;
final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) : 15;
final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) : 0;
final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) : 15;
final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) : 0;
final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) : 15;
for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
mutablePos.setY(currY | (currChunkY << 4));
for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) {
mutablePos.setZ(currZ | (currChunkZ << 4));
for (int currX = minXIterate; currX <= maxXIterate; ++currX) {
mutablePos.setX(currX | (currChunkX << 4));
final BlockState blockState = blocks.get((currX) | (currZ << 4) | ((currY) << 8));
if (!this.isAlive()) {
return;
}
blockState.entityInside(world, mutablePos, (Entity)(Object)this);
this.onInsideBlock(blockState);
}
}
}
}
}
}
}
}

View File

@@ -2,7 +2,7 @@ package ca.spottedleaf.moonrise.mixin.collisions;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.patches.chunk_getblock.GetBlockChunk;
import ca.spottedleaf.moonrise.patches.getblock.GetBlockChunk;
import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
import ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState;
import ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache;

View File

@@ -1,11 +1,10 @@
package ca.spottedleaf.moonrise.mixin.collisions;
import ca.spottedleaf.moonrise.common.util.WorldUtil;
import ca.spottedleaf.moonrise.patches.chunk_getblock.GetBlockChunk;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection;
import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
import ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState;
import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape;
import ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
@@ -16,6 +15,7 @@ import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.border.WorldBorder;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
@@ -27,20 +27,18 @@ import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.BooleanOp;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
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 java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Mixin(Level.class)
abstract class LevelMixin implements CollisionLevel, LevelAccessor, AutoCloseable {
abstract class LevelMixin implements LevelAccessor, AutoCloseable {
@Shadow
public abstract LevelChunk getChunk(int x, int z);
@@ -48,38 +46,6 @@ abstract class LevelMixin implements CollisionLevel, LevelAccessor, AutoCloseabl
@Shadow
public abstract WorldBorder getWorldBorder();
@Unique
private int minSection;
@Unique
private int maxSection;
@Override
public final int moonrise$getMinSection() {
return this.minSection;
}
@Override
public final int moonrise$getMaxSection() {
return this.maxSection;
}
/**
* @reason Init min/max section
* @author Spottedleaf
*/
@Inject(
method = "<init>",
at = @At(
value = "RETURN"
)
)
private void init(final CallbackInfo ci) {
this.minSection = WorldUtil.getMinSection(this);
this.maxSection = WorldUtil.getMaxSection(this);
}
/**
* Route to faster lookup.
* See {@link EntityGetterMixin#isUnobstructed(Entity, VoxelShape)} for expected behavior
@@ -173,7 +139,7 @@ abstract class LevelMixin implements CollisionLevel, LevelAccessor, AutoCloseabl
int lastChunkY = Integer.MIN_VALUE;
int lastChunkZ = Integer.MIN_VALUE;
final int minSection = ((CollisionLevel)level).moonrise$getMinSection();
final int minSection = WorldUtil.getMinSection(level);
for (;;) {
currPos.set(currX, currY, currZ);
@@ -308,7 +274,7 @@ abstract class LevelMixin implements CollisionLevel, LevelAccessor, AutoCloseabl
*/
@Override
public Optional<Vec3> findFreePosition(final Entity entity, final VoxelShape boundsShape, final Vec3 fromPosition,
final double rangeX, final double rangeY, final double rangeZ) {
final double rangeX, final double rangeY, final double rangeZ) {
if (boundsShape.isEmpty()) {
return Optional.empty();
}
@@ -368,102 +334,138 @@ abstract class LevelMixin implements CollisionLevel, LevelAccessor, AutoCloseabl
*/
@Override
public Optional<BlockPos> findSupportingBlock(final Entity entity, final AABB aabb) {
final int minSection = WorldUtil.getMinSection((Level)(Object)this);
final int minBlockX = Mth.floor(aabb.minX - CollisionUtil.COLLISION_EPSILON) - 1;
final int maxBlockX = Mth.floor(aabb.maxX + CollisionUtil.COLLISION_EPSILON) + 1;
final int minBlockY = Mth.floor(aabb.minY - CollisionUtil.COLLISION_EPSILON) - 1;
final int maxBlockY = Mth.floor(aabb.maxY + CollisionUtil.COLLISION_EPSILON) + 1;
final int minBlockY = Math.max((minSection << 4) - 1, Mth.floor(aabb.minY - CollisionUtil.COLLISION_EPSILON) - 1);
final int maxBlockY = Math.min((WorldUtil.getMaxSection((Level)(Object)this) << 4) + 16, Mth.floor(aabb.maxY + CollisionUtil.COLLISION_EPSILON) + 1);
final int minBlockZ = Mth.floor(aabb.minZ - CollisionUtil.COLLISION_EPSILON) - 1;
final int maxBlockZ = Mth.floor(aabb.maxZ + CollisionUtil.COLLISION_EPSILON) + 1;
CollisionUtil.LazyEntityCollisionContext collisionContext = null;
final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
final CollisionContext collisionShape = new CollisionUtil.LazyEntityCollisionContext(entity);
BlockPos selected = null;
double selectedDistance = Double.MAX_VALUE;
final Vec3 entityPos = entity.position();
LevelChunk lastChunk = null;
int lastChunkX = Integer.MIN_VALUE;
int lastChunkZ = Integer.MIN_VALUE;
// special cases:
if (minBlockY > maxBlockY) {
// no point in checking
return Optional.empty();
}
final int minChunkX = minBlockX >> 4;
final int maxChunkX = maxBlockX >> 4;
final int minChunkY = minBlockY >> 4;
final int maxChunkY = maxBlockY >> 4;
final int minChunkZ = minBlockZ >> 4;
final int maxChunkZ = maxBlockZ >> 4;
final ChunkSource chunkSource = this.getChunkSource();
for (int currZ = minBlockZ; currZ <= maxBlockZ; ++currZ) {
pos.setZ(currZ);
for (int currX = minBlockX; currX <= maxBlockX; ++currX) {
pos.setX(currX);
for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
final ChunkAccess chunk = chunkSource.getChunk(currChunkX, currChunkZ, ChunkStatus.FULL, false);
final int newChunkX = currX >> 4;
final int newChunkZ = currZ >> 4;
if (((newChunkX ^ lastChunkX) | (newChunkZ ^ lastChunkZ)) != 0) {
lastChunkX = newChunkX;
lastChunkZ = newChunkZ;
lastChunk = (LevelChunk)chunkSource.getChunk(newChunkX, newChunkZ, ChunkStatus.FULL, false);
}
if (lastChunk == null) {
if (chunk == null) {
continue;
}
for (int currY = minBlockY; currY <= maxBlockY; ++currY) {
int edgeCount = ((currX == minBlockX || currX == maxBlockX) ? 1 : 0) +
((currY == minBlockY || currY == maxBlockY) ? 1 : 0) +
((currZ == minBlockZ || currZ == maxBlockZ) ? 1 : 0);
if (edgeCount == 3) {
final LevelChunkSection[] sections = chunk.getSections();
// bound y
for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) {
final int sectionIdx = currChunkY - minSection;
if (sectionIdx < 0 || sectionIdx >= sections.length) {
continue;
}
final LevelChunkSection section = sections[sectionIdx];
if (section.hasOnlyAir()) {
// empty
continue;
}
pos.setY(currY);
final boolean hasSpecial = ((BlockCountingChunkSection)section).moonrise$hasSpecialCollidingBlocks();
final int sectionAdjust = !hasSpecial ? 1 : 0;
final double distance = pos.distToCenterSqr(entityPos);
if (distance > selectedDistance || (distance == selectedDistance && selected.compareTo(pos) >= 0)) {
continue;
}
final PalettedContainer<BlockState> blocks = section.states;
final BlockState state = ((GetBlockChunk)lastChunk).moonrise$getBlock(currX, currY, currZ);
if (((CollisionBlockState)state).moonrise$emptyContextCollisionShape()) {
continue;
}
final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) + sectionAdjust : 0;
final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) - sectionAdjust : 15;
final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) + sectionAdjust : 0;
final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) - sectionAdjust : 15;
final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) + sectionAdjust : 0;
final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) - sectionAdjust : 15;
VoxelShape blockCollision = ((CollisionBlockState)state).moonrise$getConstantContextCollisionShape();
for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
final int blockY = currY | (currChunkY << 4);
mutablePos.setY(blockY);
for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) {
final int blockZ = currZ | (currChunkZ << 4);
mutablePos.setZ(blockZ);
for (int currX = minXIterate; currX <= maxXIterate; ++currX) {
final int localBlockIndex = (currX) | (currZ << 4) | ((currY) << 8);
final int blockX = currX | (currChunkX << 4);
mutablePos.setX(blockX);
if ((edgeCount != 1 || state.hasLargeCollisionShape()) && (edgeCount != 2 || state.getBlock() == Blocks.MOVING_PISTON)) {
if (collisionContext == null) {
collisionContext = new CollisionUtil.LazyEntityCollisionContext(entity);
}
final int edgeCount = hasSpecial ? ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) +
((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) +
((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0) : 0;
if (edgeCount == 3) {
continue;
}
if (blockCollision == null) {
blockCollision = state.getCollisionShape((Level)(Object)this, pos, collisionContext);
}
final double distance = mutablePos.distToCenterSqr(entityPos);
if (distance > selectedDistance || (distance == selectedDistance && selected.compareTo(mutablePos) >= 0)) {
continue;
}
if (blockCollision.isEmpty()) {
continue;
}
final BlockState blockData = blocks.get(localBlockIndex);
// avoid VoxelShape#move by shifting the entity collision shape instead
final AABB shiftedAABB = aabb.move(-(double)currX, -(double)currY, -(double)currZ);
if (((CollisionBlockState)blockData).moonrise$emptyContextCollisionShape()) {
continue;
}
final AABB singleAABB = ((CollisionVoxelShape)blockCollision).moonrise$getSingleAABBRepresentation();
if (singleAABB != null) {
if (!CollisionUtil.voxelShapeIntersect(singleAABB, shiftedAABB)) {
continue;
VoxelShape blockCollision = ((CollisionBlockState)blockData).moonrise$getConstantContextCollisionShape();
if (edgeCount == 0 || ((edgeCount != 1 || blockData.hasLargeCollisionShape()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON))) {
if (blockCollision == null) {
blockCollision = blockData.getCollisionShape((Level)(Object)this, mutablePos, collisionShape);
if (blockCollision.isEmpty()) {
continue;
}
}
// avoid VoxelShape#move by shifting the entity collision shape instead
final AABB shiftedAABB = aabb.move(-(double)blockX, -(double)blockY, -(double)blockZ);
final AABB singleAABB = ((CollisionVoxelShape)blockCollision).moonrise$getSingleAABBRepresentation();
if (singleAABB != null) {
if (!CollisionUtil.voxelShapeIntersect(singleAABB, shiftedAABB)) {
continue;
}
selected = mutablePos.immutable();
selectedDistance = distance;
continue;
}
if (!CollisionUtil.voxelShapeIntersectNoEmpty(blockCollision, shiftedAABB)) {
continue;
}
selected = mutablePos.immutable();
selectedDistance = distance;
continue;
}
}
selected = pos.immutable();
selectedDistance = distance;
continue;
}
if (!CollisionUtil.voxelShapeIntersectNoEmpty(blockCollision, shiftedAABB)) {
continue;
}
selected = pos.immutable();
selectedDistance = distance;
continue;
}
}
}

View File

@@ -41,7 +41,7 @@ abstract class LivingEntityMixin extends Entity implements Attackable {
int nonPassengers = 0;
for (int i = 0, len = nearby.size(); i < len; ++i) {
final Entity entity = nearby.get(i);
nonPassengers += (entity.isPassenger() ? 1 : 0);
nonPassengers += (entity.isPassenger() ? 0 : 1);
this.doPush(entity);
}

View File

@@ -22,9 +22,11 @@ import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.Arrays;
import java.util.function.Supplier;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(Shapes.class)
abstract class ShapesMixin {
@@ -168,13 +170,13 @@ abstract class ShapesMixin {
final VoxelShape first = tmp[i];
final VoxelShape second = tmp[next];
tmp[newSize++] = Shapes.or(first, second);
tmp[newSize++] = Shapes.joinUnoptimized(first, second, BooleanOp.OR);
}
}
size = newSize;
}
return tmp[0];
return tmp[0].optimize();
}
@Unique
@@ -219,11 +221,24 @@ abstract class ShapesMixin {
}
/**
* Use an inject instead of overwrite to avoid mixin conflicts - obviously this will still disregard any changes made by other
* mixins, but at least it won't conflict immediately. This is useful because some library mods mixin here when only the content
* mod actually needs the change.
*
* @reason Route to faster logic
* @author Spottedleaf
*/
@Overwrite
public static VoxelShape joinUnoptimized(final VoxelShape first, final VoxelShape second, final BooleanOp mergeFunction) {
@Inject(
method = "joinUnoptimized",
at = @At("HEAD"),
cancellable = true
)
private static void injectJoinUnoptimized(final VoxelShape first, final VoxelShape second, final BooleanOp mergeFunction, final CallbackInfoReturnable<VoxelShape> cir) {
cir.setReturnValue(joinUnoptimized(first, second, mergeFunction));
}
@Unique
private static VoxelShape joinUnoptimized(final VoxelShape first, final VoxelShape second, final BooleanOp mergeFunction) {
final VoxelShape ret = CollisionUtil.joinUnoptimized(first, second, mergeFunction);
if (DEBUG_SHAPE_MERGING) {
final VoxelShape vanilla = joinUnoptimizedVanilla(first, second, mergeFunction);
@@ -239,9 +254,19 @@ abstract class ShapesMixin {
/**
* @reason Route to faster logic
* @author Spottedleaf
* @see #injectJoinUnoptimized(VoxelShape, VoxelShape, BooleanOp, CallbackInfoReturnable)
*/
@Overwrite
public static boolean joinIsNotEmpty(final VoxelShape first, final VoxelShape second, final BooleanOp mergeFunction) {
@Inject(
method = "joinIsNotEmpty(Lnet/minecraft/world/phys/shapes/VoxelShape;Lnet/minecraft/world/phys/shapes/VoxelShape;Lnet/minecraft/world/phys/shapes/BooleanOp;)Z",
at = @At("HEAD"),
cancellable = true
)
private static void injectJoinIsNotEmpty(final VoxelShape first, final VoxelShape second, final BooleanOp mergeFunction, final CallbackInfoReturnable<Boolean> cir) {
cir.setReturnValue(joinIsNotEmpty(first, second, mergeFunction));
}
@Unique
private static boolean joinIsNotEmpty(final VoxelShape first, final VoxelShape second, final BooleanOp mergeFunction) {
final boolean ret = CollisionUtil.isJoinNonEmpty(first, second, mergeFunction);
if (DEBUG_SHAPE_MERGING) {
if (ret != !joinUnoptimizedVanilla(first, second, mergeFunction).isEmpty()) {

View File

@@ -67,7 +67,6 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkHolder.PlayerP
)
)
private boolean newTrackerTick(final Iterator<?> iterator) {
final NearbyPlayers nearbyPlayers = ((ChunkSystemServerLevel)this.level).moonrise$getNearbyPlayers();
final ServerEntityLookup entityLookup = (ServerEntityLookup)((ChunkSystemServerLevel)this.level).moonrise$getEntityLookup();;
final ReferenceList<Entity> trackerEntities = entityLookup.trackerEntities;
@@ -78,7 +77,7 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkHolder.PlayerP
if (tracker == null) {
continue;
}
((EntityTrackerTrackedEntity)tracker).moonrise$tick(nearbyPlayers.getChunk(entity.chunkPosition()));
((EntityTrackerTrackedEntity)tracker).moonrise$tick(((ChunkSystemEntity)entity).moonrise$getChunkData().nearbyPlayers);
if (((EntityTrackerTrackedEntity)tracker).moonrise$hasPlayers()
|| ((ChunkSystemEntity)entity).moonrise$getChunkStatus().isOrAfter(FullChunkStatus.ENTITY_TICKING)) {
tracker.serverEntity.sendChanges();

View File

@@ -4,16 +4,16 @@ import ca.spottedleaf.moonrise.common.list.ReferenceList;
import ca.spottedleaf.moonrise.common.misc.NearbyPlayers;
import ca.spottedleaf.moonrise.common.util.TickThread;
import ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerTrackedEntity;
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerPlayerConnection;
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 net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.List;
import java.util.Set;
@Mixin(ChunkMap.TrackedEntity.class)
@@ -22,26 +22,22 @@ abstract class TrackedEntityMixin implements EntityTrackerTrackedEntity {
@Final
private Set<ServerPlayerConnection> seenBy;
@Shadow
@Final
private int range;
@Shadow
public abstract void updatePlayer(ServerPlayer serverPlayer);
@Shadow
public abstract void removePlayer(ServerPlayer serverPlayer);
/**
* @reason ReferenceOpenHashSet is a better choice than a wrapped IdentityHashMap
* @author Spottedleaf
*/
@Redirect(
method = "<init>",
at = @At(
value = "INVOKE",
target = "Lcom/google/common/collect/Sets;newIdentityHashSet()Ljava/util/Set;"
)
)
private <E> Set<E> useBetterIdentitySet() {
return new ReferenceOpenHashSet<>();
}
@Shadow
protected abstract int scaledRange(final int i);
@Shadow
@Final
Entity entity;
@Unique
private long lastChunkUpdate = -1L;
@@ -126,4 +122,42 @@ abstract class TrackedEntityMixin implements EntityTrackerTrackedEntity {
public final boolean moonrise$hasPlayers() {
return !this.seenBy.isEmpty();
}
/**
* @reason ReferenceOpenHashSet is a better choice than a wrapped IdentityHashMap
* @author Spottedleaf
*/
@Redirect(
method = "<init>",
at = @At(
value = "INVOKE",
target = "Lcom/google/common/collect/Sets;newIdentityHashSet()Ljava/util/Set;"
)
)
private <E> Set<E> useBetterIdentitySet() {
return new ReferenceOpenHashSet<>();
}
/**
* @reason Optimise impl to not retrieve indirect passengers unless needed
* @author Spottedleaf
*/
@Overwrite
public int getEffectiveRange() {
int range = this.range;
final Entity entity = this.entity;
if (entity.getPassengers() == ImmutableList.<Entity>of()) {
return this.scaledRange(range);
}
// note: we change to List
final List<Entity> passengers = (List<Entity>)entity.getIndirectPassengers();
for (int i = 0, len = passengers.size(); i < len; ++i) {
// note: max should be branchless
range = Math.max(range, passengers.get(i).getType().clientTrackingRange() << 4);
}
return this.scaledRange(range);
}
}

View File

@@ -35,40 +35,18 @@ abstract class PalettedContainerMixin<T> implements PaletteResize<T>, PalettedCo
* @author Spottedleaf
*/
@Inject(
method = "<init>(Lnet/minecraft/core/IdMap;Ljava/lang/Object;Lnet/minecraft/world/level/chunk/PalettedContainer$Strategy;)V",
// cannot use `<init>*` due to https://github.com/FabricMC/tiny-remapper/issues/137
method = {
"<init>(Lnet/minecraft/core/IdMap;Lnet/minecraft/world/level/chunk/PalettedContainer$Strategy;Lnet/minecraft/world/level/chunk/PalettedContainer$Configuration;Lnet/minecraft/util/BitStorage;Ljava/util/List;)V",
"<init>(Lnet/minecraft/core/IdMap;Lnet/minecraft/world/level/chunk/PalettedContainer$Strategy;Lnet/minecraft/world/level/chunk/PalettedContainer$Data;)V",
"<init>(Lnet/minecraft/core/IdMap;Ljava/lang/Object;Lnet/minecraft/world/level/chunk/PalettedContainer$Strategy;)V"
},
at = @At(
value = "RETURN"
)
),
require = 3 // Require matching all 3 constructors
)
private void constructorHook1(final CallbackInfo ci) {
this.updateData(this.data);
}
/**
* @reason Hook to update raw palette data on object construction
* @author Spottedleaf
*/
@Inject(
method = "<init>(Lnet/minecraft/core/IdMap;Lnet/minecraft/world/level/chunk/PalettedContainer$Strategy;Lnet/minecraft/world/level/chunk/PalettedContainer$Configuration;Lnet/minecraft/util/BitStorage;Ljava/util/List;)V",
at = @At(
value = "RETURN"
)
)
private void constructorHook2(final CallbackInfo ci) {
this.updateData(this.data);
}
/**
* @reason Hook to update raw palette data on object construction
* @author Spottedleaf
*/
@Inject(
method = "<init>(Lnet/minecraft/core/IdMap;Lnet/minecraft/world/level/chunk/PalettedContainer$Strategy;Lnet/minecraft/world/level/chunk/PalettedContainer$Data;)V",
at = @At(
value = "RETURN"
)
)
private void constructorHook3(final CallbackInfo ci) {
private void constructorHook(final CallbackInfo ci) {
this.updateData(this.data);
}

View File

@@ -1,5 +1,6 @@
package ca.spottedleaf.moonrise.mixin.fluid;
import ca.spottedleaf.moonrise.patches.fluid.FluidFluidState;
import com.mojang.serialization.MapCodec;
import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap;
import net.minecraft.world.level.block.state.BlockState;
@@ -11,12 +12,9 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
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;
@Mixin(FluidState.class)
abstract class FluidStateMixin extends StateHolder<Fluid, FluidState> {
abstract class FluidStateMixin extends StateHolder<Fluid, FluidState> implements FluidFluidState {
@Shadow
public abstract Fluid getType();
@@ -43,23 +41,9 @@ abstract class FluidStateMixin extends StateHolder<Fluid, FluidState> {
@Unique
private BlockState legacyBlock;
/**
* @reason Initialise caches
* @author Spottedleaf
*/
@Inject(
method = "<init>",
at = @At(
value = "RETURN"
)
)
private void init(final CallbackInfo ci) {
try {
this.amount = this.getType().getAmount((FluidState)(Object)this);
} catch (final Exception ex) {
// https://github.com/JDKDigital/productivetrees/issues/16
new RuntimeException("Failed to retrieve fluid amount for " + this, ex).printStackTrace();
}
@Override
public void moonrise$initCaches() {
this.amount = this.getType().getAmount((FluidState)(Object)this);
this.isEmpty = this.getType().isEmpty();
this.isSource = this.getType().isSource((FluidState)(Object)this);
this.ownHeight = this.getType().getOwnHeight((FluidState)(Object)this);

View File

@@ -0,0 +1,38 @@
package ca.spottedleaf.moonrise.mixin.fluid;
import ca.spottedleaf.moonrise.patches.fluid.FluidFluidState;
import net.minecraft.core.Holder;
import net.minecraft.core.MappedRegistry;
import net.minecraft.core.RegistrationInfo;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
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(MappedRegistry.class)
abstract class MappedRegistryMixin<T> {
/**
* @reason Initialise caches
* @author jpenilla
*/
@Inject(
method = "register(Lnet/minecraft/resources/ResourceKey;Ljava/lang/Object;Lnet/minecraft/core/RegistrationInfo;)Lnet/minecraft/core/Holder$Reference;",
at = @At("RETURN")
)
private void injectFluidRegister(
final ResourceKey<?> resourceKey,
final T object,
final RegistrationInfo registrationInfo,
final CallbackInfoReturnable<Holder.Reference<T>> cir
) {
if (resourceKey.registryKey() == (Object) Registries.FLUID) {
for (final FluidState possibleState : ((Fluid) object).getStateDefinition().getPossibleStates()) {
((FluidFluidState) (Object) possibleState).moonrise$initCaches();
}
}
}
}

View File

@@ -1,4 +1,4 @@
package ca.spottedleaf.moonrise.mixin.chunk_getblock;
package ca.spottedleaf.moonrise.mixin.getblock;
import ca.spottedleaf.moonrise.common.util.WorldUtil;
import net.minecraft.core.Holder;

View File

@@ -1,7 +1,7 @@
package ca.spottedleaf.moonrise.mixin.chunk_getblock;
package ca.spottedleaf.moonrise.mixin.getblock;
import ca.spottedleaf.moonrise.common.util.WorldUtil;
import ca.spottedleaf.moonrise.patches.chunk_getblock.GetBlockChunk;
import ca.spottedleaf.moonrise.patches.getblock.GetBlockChunk;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry;
import net.minecraft.world.level.ChunkPos;
@@ -81,6 +81,7 @@ abstract class LevelChunkMixin extends ChunkAccess implements GetBlockChunk {
* @reason Route to optimized getBlock
* @author Spottedleaf
*/
@Override
@Overwrite
public BlockState getBlockState(final BlockPos pos) {
return this.moonrise$getBlock(pos.getX(), pos.getY(), pos.getZ());
@@ -101,7 +102,7 @@ abstract class LevelChunkMixin extends ChunkAccess implements GetBlockChunk {
}
@Override
public BlockState moonrise$getBlock(final int x, final int y, final int z) {
public final BlockState moonrise$getBlock(final int x, final int y, final int z) {
if (this.debug) {
return this.getBlockDebug(x, y, z);
}

View File

@@ -0,0 +1,112 @@
package ca.spottedleaf.moonrise.mixin.getblock;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.dimension.DimensionType;
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;
// Higher priority to apply after Lithium mixin.world.inline_height.WorldMixin
@Mixin(value = Level.class, priority = 1100)
abstract class LevelMixin implements LevelAccessor, AutoCloseable {
@Unique
private int height;
@Unique
private int minBuildHeight;
@Unique
private int maxBuildHeight;
@Unique
private int minSection;
@Unique
private int maxSection;
@Unique
private int sectionsCount;
/**
* @reason Init min/max section
* @author Spottedleaf
*/
@Inject(
method = "<init>",
at = @At(
value = "CTOR_HEAD"
)
)
private void init(final CallbackInfo ci,
@Local(ordinal = 0, argsOnly = true) final Holder<DimensionType> dimensionTypeHolder) {
final DimensionType dimType = dimensionTypeHolder.value();
this.height = dimType.height();
this.minBuildHeight = dimType.minY();
this.maxBuildHeight = this.minBuildHeight + this.height;
this.minSection = this.minBuildHeight >> 4;
this.maxSection = ((this.maxBuildHeight - 1) >> 4) + 1;
this.sectionsCount = this.maxSection - this.minSection;
}
@Override
public int getHeight() {
return this.height;
}
@Override
public int getMinBuildHeight() {
return this.minBuildHeight;
}
@Override
public int getMaxBuildHeight() {
return this.maxBuildHeight;
}
@Override
public int getMinSection() {
return this.minSection;
}
@Override
public int getMaxSection() {
return this.maxSection;
}
@Override
public boolean isOutsideBuildHeight(final int y) {
return y < this.minBuildHeight || y >= this.maxBuildHeight;
}
@Override
public boolean isOutsideBuildHeight(final BlockPos blockPos) {
return this.isOutsideBuildHeight(blockPos.getY());
}
@Override
public int getSectionIndex(final int blockY) {
return (blockY >> 4) - this.minSection;
}
@Override
public int getSectionIndexFromSectionY(final int sectionY) {
return sectionY - this.minSection;
}
@Override
public int getSectionYFromSectionIndex(final int sectionIdx) {
return sectionIdx + this.minSection;
}
@Override
public int getSectionsCount() {
return this.sectionsCount;
}
}

View File

@@ -0,0 +1,23 @@
package ca.spottedleaf.moonrise.mixin.mob_spawning;
import ca.spottedleaf.moonrise.patches.mob_spawning.MobSpawningEntityType;
import net.minecraft.world.entity.EntityType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(EntityType.class)
abstract class EntityTypeMixin implements MobSpawningEntityType {
@Unique
private boolean hasBiomeCost = false;
@Override
public final boolean moonrise$hasAnyBiomeCost() {
return this.hasBiomeCost;
}
@Override
public final void moonrise$setHasBiomeCost() {
this.hasBiomeCost = true;
}
}

View File

@@ -0,0 +1,96 @@
package ca.spottedleaf.moonrise.mixin.mob_spawning;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.LocalMobCapCalculator;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LocalMobCapCalculator.MobCounts.class)
abstract class LocalMobCapCalculator$MobCountsMixin {
@Shadow
@Mutable
@Final
private Object2IntMap<MobCategory> counts;
@Unique
private static final MobCategory[] CATEGORIES = MobCategory.values();
@Unique
private static final Object2IntOpenHashMap<?> DUMMY = new Object2IntOpenHashMap<>();
@Unique
private final int[] newCounts = new int[CATEGORIES.length];
/**
* @reason Ensure accesses of old field blow up
* @author Spottedleaf
*/
@Inject(
method = "<init>",
at = @At(
value = "RETURN"
)
)
private void destroyField(final CallbackInfo ci) {
this.counts = null;
}
/**
* @reason Avoid allocating the map, we null it later
* @author Spottedleaf
*/
@Redirect(
method = "<init>",
at = @At(
value = "NEW",
target = "(I)Lit/unimi/dsi/fastutil/objects/Object2IntOpenHashMap;"
)
)
private <T> Object2IntOpenHashMap<T> avoidAllocation(final int expected) {
return (Object2IntOpenHashMap<T>)DUMMY;
}
/**
* @reason Do not allocate MobCategory[]
* @author Spottedleaf
*/
@Redirect(
method = "<init>",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/MobCategory;values()[Lnet/minecraft/world/entity/MobCategory;"
)
)
private MobCategory[] useCachedArray() {
return CATEGORIES;
}
/**
* @reason Use simple array instead of compute call
* @author Spottedleaf
*/
@Overwrite
public void add(final MobCategory category) {
++this.newCounts[category.ordinal()];
}
/**
* @reason Use simple array instead of map get call
* @author Spottedleaf
*/
@Overwrite
public boolean canSpawn(final MobCategory category) {
return this.newCounts[category.ordinal()] < category.getMaxInstancesPerChunk();
}
}

View File

@@ -0,0 +1,36 @@
package ca.spottedleaf.moonrise.mixin.mob_spawning;
import ca.spottedleaf.moonrise.patches.mob_spawning.MobSpawningEntityType;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.biome.MobSpawnSettings;
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(MobSpawnSettings.class)
abstract class MobSpawnSettingsMixin {
@Shadow
@Final
private Map<EntityType<?>, MobSpawnSettings.MobSpawnCost> mobSpawnCosts;
/**
* @reason Set biome cost flag for any EntityType which has a cost
* @author Spottedleaf
*/
@Inject(
method = "<init>",
at = @At(
value = "RETURN"
)
)
private void initBiomeCost(final CallbackInfo ci) {
for (final EntityType<?> type : this.mobSpawnCosts.keySet()) {
((MobSpawningEntityType)type).moonrise$setHasBiomeCost();
}
}
}

View File

@@ -0,0 +1,77 @@
package ca.spottedleaf.moonrise.mixin.mob_spawning;
import ca.spottedleaf.moonrise.patches.mob_spawning.MobSpawningEntityType;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.NaturalSpawner;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.MobSpawnSettings;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunk;
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.Redirect;
@Mixin(NaturalSpawner.class)
abstract class NaturalSpawnerMixin {
@Shadow
static Biome getRoughBiome(final BlockPos arg, final ChunkAccess arg2) {
return null;
}
/**
* @reason Delay until we determine if the entity type even has a cost
* @author Spottedleaf
*/
@Redirect(
method = {"method_27819", "lambda$createState$2"}, // Fabric, NeoForge
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/NaturalSpawner;getRoughBiome(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/chunk/ChunkAccess;)Lnet/minecraft/world/level/biome/Biome;"
)
)
private static Biome delayRoughBiome(final BlockPos pos, final ChunkAccess chunk) {
return null;
}
/**
* @reason Delay until we determine if the entity type even has a cost
* @author Spottedleaf
*/
@Redirect(
method = {"method_27819", "lambda$createState$2"}, // Fabric, NeoForge
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/biome/Biome;getMobSettings()Lnet/minecraft/world/level/biome/MobSpawnSettings;"
)
)
private static MobSpawnSettings delayMobSpawnSettings(final Biome biome) {
return null;
}
/**
* @reason Avoid looking up biomes for mobs which have no cost
* @author Spottedleaf
*/
@Redirect(
method = {"method_27819", "lambda$createState$2"}, // Fabric, NeoForge
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/biome/MobSpawnSettings;getMobSpawnCost(Lnet/minecraft/world/entity/EntityType;)Lnet/minecraft/world/level/biome/MobSpawnSettings$MobSpawnCost;"
)
)
private static MobSpawnSettings.MobSpawnCost avoidBiomeLookupIfPossible(final MobSpawnSettings isNull,
final EntityType<?> type,
@Local(ordinal = 0, argsOnly = true) final BlockPos pos,
@Local(ordinal = 0, argsOnly = true) final LevelChunk chunk) {
if (!((MobSpawningEntityType)type).moonrise$hasAnyBiomeCost()) {
// if the type has no associated cost with any biome, then no point in looking
return null;
}
return getRoughBiome(pos, chunk).getMobSettings().getMobSpawnCost(type);
}
}

View File

@@ -1,7 +1,7 @@
package ca.spottedleaf.moonrise.mixin.random_ticking;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.common.list.IntList;
import ca.spottedleaf.moonrise.common.list.ShortList;
import ca.spottedleaf.moonrise.common.util.SimpleRandom;
import ca.spottedleaf.moonrise.common.util.WorldUtil;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection;
@@ -85,11 +85,11 @@ abstract class ServerLevelMixin extends Level implements WorldGenLevel {
final int offsetY = (sectionIndex + minSection) << 4;
final LevelChunkSection section = sections[sectionIndex];
final PalettedContainer<BlockState> states = section.states;
if (section == null || !section.isRandomlyTickingBlocks()) {
if (!section.isRandomlyTickingBlocks()) {
continue;
}
final IntList tickList = ((BlockCountingChunkSection)section).moonrise$getTickingBlockList();
final ShortList tickList = ((BlockCountingChunkSection)section).moonrise$getTickingBlockList();
for (int i = 0; i < tickSpeed; ++i) {
final int tickingBlocks = tickList.size();
@@ -100,7 +100,7 @@ abstract class ServerLevelMixin extends Level implements WorldGenLevel {
continue;
}
final int location = tickList.getRaw(index);
final int location = (int)tickList.getRaw(index) & 0xFFFF;
final BlockState state = states.get(location);
// do not use a mutable pos, as some random tick implementations store the input without calling immutable()!

View File

@@ -17,11 +17,17 @@ abstract class PalettedContainerMixin {
* @author jpenilla
*/
@Redirect(
method = "<init>*",
// cannot use `<init>*` due to https://github.com/FabricMC/tiny-remapper/issues/137
method = {
"<init>(Lnet/minecraft/core/IdMap;Lnet/minecraft/world/level/chunk/PalettedContainer$Strategy;Lnet/minecraft/world/level/chunk/PalettedContainer$Configuration;Lnet/minecraft/util/BitStorage;Ljava/util/List;)V",
"<init>(Lnet/minecraft/core/IdMap;Lnet/minecraft/world/level/chunk/PalettedContainer$Strategy;Lnet/minecraft/world/level/chunk/PalettedContainer$Data;)V",
"<init>(Lnet/minecraft/core/IdMap;Ljava/lang/Object;Lnet/minecraft/world/level/chunk/PalettedContainer$Strategy;)V"
},
at = @At(
value = "NEW",
target = "Lnet/minecraft/util/ThreadingDetector;"
)
),
require = 3 // Require matching all 3 constructors
)
private static ThreadingDetector threadingDetector(final String name) {
return THREADING_DETECTOR;

View File

@@ -1,10 +1,10 @@
package ca.spottedleaf.moonrise.patches.block_counting;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
public interface BlockCountingBitStorage {
public Int2ObjectOpenHashMap<IntArrayList> moonrise$countEntries();
public Int2ObjectOpenHashMap<ShortArrayList> moonrise$countEntries();
}

View File

@@ -1,11 +1,11 @@
package ca.spottedleaf.moonrise.patches.block_counting;
import ca.spottedleaf.moonrise.common.list.IntList;
import ca.spottedleaf.moonrise.common.list.ShortList;
public interface BlockCountingChunkSection {
public int moonrise$getSpecialCollidingBlocks();
public boolean moonrise$hasSpecialCollidingBlocks();
public IntList moonrise$getTickingBlockList();
public ShortList moonrise$getTickingBlockList();
}

View File

@@ -1,5 +1,6 @@
package ca.spottedleaf.moonrise.patches.chunk_system.entity;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData;
import net.minecraft.server.level.FullChunkStatus;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.monster.Shulker;
@@ -19,6 +20,10 @@ public interface ChunkSystemEntity {
public void moonrise$setChunkStatus(final FullChunkStatus status);
public ChunkData moonrise$getChunkData();
public void moonrise$setChunkData(final ChunkData chunkData);
public int moonrise$getSectionX();
public void moonrise$setSectionX(final int x);

View File

@@ -1,5 +1,6 @@
package ca.spottedleaf.moonrise.patches.chunk_system.level;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData;
import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.EntityLookup;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunk;
@@ -19,4 +20,14 @@ public interface ChunkSystemLevel {
public void moonrise$midTickTasks();
public ChunkData moonrise$getChunkData(final long chunkKey);
public ChunkData moonrise$getChunkData(final int chunkX, final int chunkZ);
public ChunkData moonrise$requestChunkData(final long chunkKey);
public ChunkData moonrise$releaseChunkData(final long chunkKey);
public boolean moonrise$areChunksLoaded(final int fromX, final int fromZ, final int toX, final int toZ);
}

View File

@@ -7,6 +7,7 @@ import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO;
import ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.status.ChunkStatus;
@@ -60,4 +61,12 @@ public interface ChunkSystemServerLevel extends ChunkSystemLevel {
public ReferenceList<ServerChunkCache.ChunkAndHolder> moonrise$getTickingChunks();
public ReferenceList<ServerChunkCache.ChunkAndHolder> moonrise$getEntityTickingChunks();
public ReferenceList<ChunkHolder> moonrise$getUnsyncedChunks();
public void moonrise$addUnsyncedChunk(final ChunkHolder chunkHolder);
public void moonrise$removeUnsyncedChunk(final ChunkHolder chunkHolder);
public void moonrise$clearUnsyncedChunks();
}

View File

@@ -0,0 +1,21 @@
package ca.spottedleaf.moonrise.patches.chunk_system.level.chunk;
import ca.spottedleaf.moonrise.common.misc.NearbyPlayers;
public final class ChunkData {
private int referenceCount = 0;
public NearbyPlayers.TrackedChunk nearbyPlayers; // Moonrise - nearby players
public ChunkData() {
}
public int increaseRef() {
return ++this.referenceCount;
}
public int decreaseRef() {
return --this.referenceCount;
}
}

View File

@@ -23,4 +23,8 @@ public interface ChunkSystemChunkHolder {
public LevelChunk moonrise$getFullChunk();
public boolean moonrise$isMarkedDirtyForPlayers();
public void moonrise$markDirtyForPlayers(final boolean value);
}

View File

@@ -1,6 +1,7 @@
package ca.spottedleaf.moonrise.patches.chunk_system.level.entity;
import ca.spottedleaf.moonrise.common.list.EntityList;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData;
import ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity;
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
@@ -42,6 +43,7 @@ public final class ChunkEntitySlices {
private final EntityList entities = new EntityList();
public FullChunkStatus status;
public final ChunkData chunkData;
private boolean isTransient;
@@ -54,7 +56,7 @@ public final class ChunkEntitySlices {
}
public ChunkEntitySlices(final Level world, final int chunkX, final int chunkZ, final FullChunkStatus status,
final int minSection, final int maxSection) { // inclusive, inclusive
final ChunkData chunkData, final int minSection, final int maxSection) { // inclusive, inclusive
this.minSection = minSection;
this.maxSection = maxSection;
this.chunkX = chunkX;
@@ -67,6 +69,7 @@ public final class ChunkEntitySlices {
this.entitiesByType = new Reference2ObjectOpenHashMap<>();
this.status = status;
this.chunkData = chunkData;
}
public static List<Entity> readEntities(final ServerLevel world, final CompoundTag compoundTag) {
@@ -223,6 +226,7 @@ public final class ChunkEntitySlices {
return false;
}
((ChunkSystemEntity)entity).moonrise$setChunkStatus(this.status);
((ChunkSystemEntity)entity).moonrise$setChunkData(this.chunkData);
final int sectionIndex = chunkSection - this.minSection;
this.allEntities.addEntity(entity, sectionIndex);
@@ -256,6 +260,7 @@ public final class ChunkEntitySlices {
return false;
}
((ChunkSystemEntity)entity).moonrise$setChunkStatus(null);
((ChunkSystemEntity)entity).moonrise$setChunkData(null);
final int sectionIndex = chunkSection - this.minSection;
this.allEntities.removeEntity(entity, sectionIndex);
@@ -788,4 +793,4 @@ public final class ChunkEntitySlices {
return false;
}
}
}
}

View File

@@ -46,8 +46,6 @@ public abstract class EntityLookup implements LevelEntityGetter<Entity> {
protected final SWMRLong2ObjectHashTable<ChunkSlicesRegion> regions = new SWMRLong2ObjectHashTable<>(128, 0.5f);
protected final int minSection; // inclusive
protected final int maxSection; // inclusive
protected final LevelCallback<Entity> worldCallback;
protected final ConcurrentLong2ReferenceChainedHashTable<Entity> entityById = new ConcurrentLong2ReferenceChainedHashTable<>();
@@ -56,8 +54,6 @@ public abstract class EntityLookup implements LevelEntityGetter<Entity> {
public EntityLookup(final Level world, final LevelCallback<Entity> worldCallback) {
this.world = world;
this.minSection = WorldUtil.getMinSection(world);
this.maxSection = WorldUtil.getMaxSection(world);
this.worldCallback = worldCallback;
}
@@ -404,7 +400,7 @@ public abstract class EntityLookup implements LevelEntityGetter<Entity> {
protected boolean addEntity(final Entity entity, final boolean fromDisk, final boolean event) {
final BlockPos pos = entity.blockPosition();
final int sectionX = pos.getX() >> 4;
final int sectionY = Mth.clamp(pos.getY() >> 4, this.minSection, this.maxSection);
final int sectionY = Mth.clamp(pos.getY() >> 4, WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world));
final int sectionZ = pos.getZ() >> 4;
this.checkThread(sectionX, sectionZ, "Cannot add entity off-main thread");
@@ -523,7 +519,7 @@ public abstract class EntityLookup implements LevelEntityGetter<Entity> {
final int sectionZ = ((ChunkSystemEntity)entity).moonrise$getSectionZ();
final BlockPos newPos = entity.blockPosition();
final int newSectionX = newPos.getX() >> 4;
final int newSectionY = Mth.clamp(newPos.getY() >> 4, this.minSection, this.maxSection);
final int newSectionY = Mth.clamp(newPos.getY() >> 4, WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world));
final int newSectionZ = newPos.getZ() >> 4;
if (newSectionX == sectionX && newSectionY == sectionY && newSectionZ == sectionZ) {

View File

@@ -46,7 +46,8 @@ public final class ClientEntityLookup extends EntityLookup {
final ChunkEntitySlices ret = new ChunkEntitySlices(
this.world, chunkX, chunkZ,
ticking ? FullChunkStatus.ENTITY_TICKING : FullChunkStatus.FULL, WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world)
ticking ? FullChunkStatus.ENTITY_TICKING : FullChunkStatus.FULL, null,
WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world)
);
// note: not handled by superclass

View File

@@ -32,7 +32,7 @@ public final class DefaultEntityLookup extends EntityLookup {
protected ChunkEntitySlices createEntityChunk(final int chunkX, final int chunkZ, final boolean transientChunk) {
final ChunkEntitySlices ret = new ChunkEntitySlices(
this.world, chunkX, chunkZ, FullChunkStatus.FULL,
WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world)
null, WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world)
);
// note: not handled by superclass

View File

@@ -5,7 +5,6 @@ import ca.spottedleaf.concurrentutil.map.ConcurrentLong2ReferenceChainedHashTabl
import ca.spottedleaf.concurrentutil.util.Priority;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.common.util.MoonriseCommon;
import ca.spottedleaf.moonrise.common.util.TickThread;
import ca.spottedleaf.moonrise.common.util.WorldUtil;
import ca.spottedleaf.moonrise.common.util.ChunkSystem;
@@ -1023,11 +1022,10 @@ public final class ChunkHolderManager {
}
private void removeChunkHolder(final NewChunkHolder holder) {
holder.markUnloaded();
holder.onUnload();
this.autoSaveQueue.remove(holder);
ChunkSystem.onChunkHolderDelete(this.world, holder.vanillaChunkHolder);
this.chunkHolders.remove(CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ));
}
// note: never call while inside the chunk system, this will absolutely break everything

View File

@@ -14,7 +14,9 @@ import ca.spottedleaf.moonrise.common.util.WorldUtil;
import ca.spottedleaf.moonrise.common.util.ChunkSystem;
import ca.spottedleaf.moonrise.patches.chunk_system.ChunkSystemFeatures;
import ca.spottedleaf.moonrise.patches.chunk_system.async_save.AsyncChunkSaveData;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkData;
import ca.spottedleaf.moonrise.patches.chunk_system.io.MoonriseRegionFileIO;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkHolder;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemChunkStatus;
@@ -61,6 +63,8 @@ public final class NewChunkHolder {
private static final Logger LOGGER = LoggerFactory.getLogger(NewChunkHolder.class);
public final ChunkData holderData;
public final ServerLevel world;
public final int chunkX;
public final int chunkZ;
@@ -92,7 +96,7 @@ public final class NewChunkHolder {
if (this.entityChunk == null) {
ret = this.entityChunk = new ChunkEntitySlices(
this.world, this.chunkX, this.chunkZ, this.getChunkStatus(),
WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world)
this.holderData, WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world)
);
ret.setTransient(transientChunk);
@@ -654,6 +658,7 @@ public final class NewChunkHolder {
world.getLightEngine(), null, world.getChunkSource().chunkMap
);
((ChunkSystemChunkHolder)this.vanillaChunkHolder).moonrise$setRealChunkHolder(this);
this.holderData = ((ChunkSystemLevel)this.world).moonrise$requestChunkData(CoordinateUtils.getChunkKey(chunkX, chunkZ));
}
public ChunkAccess getCurrentChunk() {
@@ -753,8 +758,10 @@ public final class NewChunkHolder {
/** Unloaded from chunk map */
private boolean unloaded;
void markUnloaded() {
void onUnload() {
this.unloaded = true;
((ChunkSystemServerLevel)this.world).moonrise$removeUnsyncedChunk(this.vanillaChunkHolder);
((ChunkSystemLevel)this.world).moonrise$releaseChunkData(CoordinateUtils.getChunkKey(this.chunkX, this.chunkZ));
}
private boolean inUnloadQueue = false;

View File

@@ -0,0 +1,19 @@
package ca.spottedleaf.moonrise.patches.chunk_tick_iteration;
import ca.spottedleaf.moonrise.common.list.ReferenceList;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.world.level.chunk.LevelChunk;
public interface ChunkTickServerLevel {
public ReferenceList<ServerChunkCache.ChunkAndHolder> moonrise$getPlayerTickingChunks();
public void moonrise$markChunkForPlayerTicking(final LevelChunk chunk);
public void moonrise$removeChunkForPlayerTicking(final LevelChunk chunk);
public void moonrise$addPlayerTickingRequest(final int chunkX, final int chunkZ);
public void moonrise$removePlayerTickingRequest(final int chunkX, final int chunkZ);
}

View File

@@ -1,12 +1,12 @@
package ca.spottedleaf.moonrise.patches.collisions;
import ca.spottedleaf.moonrise.common.util.WorldUtil;
import ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter;
import ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState;
import ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity;
import ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData;
import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape;
import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape;
import ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel;
import ca.spottedleaf.moonrise.patches.block_counting.BlockCountingChunkSection;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import it.unimi.dsi.fastutil.doubles.DoubleList;
@@ -1077,10 +1077,11 @@ public final class CollisionUtil {
final int s1z = mergedZ.firstIndices[idxZ];
final int s2z = mergedZ.secondIndices[idxZ];
int idx;
int idx1;
int idx2;
final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx) & 1L);
final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx) & 1L);
final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx1 = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx1) & 1L);
final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx2 = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx2) & 1L);
// idx ff -> 0
// idx ft -> 1
@@ -1143,10 +1144,11 @@ public final class CollisionUtil {
final int s1z = mergedZ.firstIndices[idxZ];
final int s2z = mergedZ.secondIndices[idxZ];
int idx;
int idx1;
int idx2;
final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx) & 1L);
final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx) & 1L);
final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx1 = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx1) & 1L);
final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx2 = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx2) & 1L);
// idx ff -> 0
// idx ft -> 1
@@ -1932,13 +1934,13 @@ public final class CollisionUtil {
}
}
final int minSection = ((CollisionLevel)world).moonrise$getMinSection();
final int minSection = WorldUtil.getMinSection(world);
final int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1;
final int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1;
final int minBlockY = Math.max((minSection << 4) - 1, Mth.floor(aabb.minY - COLLISION_EPSILON) - 1);
final int maxBlockY = Math.min((((CollisionLevel)world).moonrise$getMaxSection() << 4) + 16, Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1);
final int maxBlockY = Math.min((WorldUtil.getMaxSection(world) << 4) + 16, Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1);
final int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1;
final int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1;
@@ -1989,12 +1991,12 @@ public final class CollisionUtil {
continue;
}
final LevelChunkSection section = sections[sectionIdx];
if (section == null || section.hasOnlyAir()) {
if (section.hasOnlyAir()) {
// empty
continue;
}
final boolean hasSpecial = ((BlockCountingChunkSection)section).moonrise$getSpecialCollidingBlocks() != 0;
final boolean hasSpecial = ((BlockCountingChunkSection)section).moonrise$hasSpecialCollidingBlocks();
final int sectionAdjust = !hasSpecial ? 1 : 0;
final PalettedContainer<BlockState> blocks = section.states;

View File

@@ -22,13 +22,17 @@ import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
public final class EmptyStreamForMoveCall<T> implements Stream<T> {
public final class NoneMatchStream<T> implements Stream<T> {
public static final EmptyStreamForMoveCall INSTANCE = new EmptyStreamForMoveCall();
private final boolean value;
public NoneMatchStream(final boolean value) {
this.value = value;
}
@Override
public boolean noneMatch(Predicate<? super T> predicate) {
return false; // important: ret false so the branch is never taken by mojang code
return this.value;
}
@Override

View File

@@ -1,9 +0,0 @@
package ca.spottedleaf.moonrise.patches.collisions.world;
public interface CollisionLevel {
public int moonrise$getMinSection();
public int moonrise$getMaxSection();
}

View File

@@ -0,0 +1,5 @@
package ca.spottedleaf.moonrise.patches.fluid;
public interface FluidFluidState {
public void moonrise$initCaches();
}

View File

@@ -1,4 +1,4 @@
package ca.spottedleaf.moonrise.patches.chunk_getblock;
package ca.spottedleaf.moonrise.patches.getblock;
import net.minecraft.world.level.block.state.BlockState;

View File

@@ -0,0 +1,9 @@
package ca.spottedleaf.moonrise.patches.mob_spawning;
public interface MobSpawningEntityType {
public boolean moonrise$hasAnyBiomeCost();
public void moonrise$setHasBiomeCost();
}

View File

@@ -2,6 +2,8 @@ package ca.spottedleaf.moonrise.patches.profiler;
import com.mojang.logging.LogUtils;
import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
import org.slf4j.Logger;
@@ -169,6 +171,7 @@ public final class LeafProfiler {
public final List<ProfileNode> children = new ArrayList<>();
public long childrenTimingCount;
public int depth = -1;
public boolean lastChild;
private ProfileNode(final ProfileNode parent, final int nodeId, final LProfilerRegistry.ProfilerEntry profiler,
final long totalTime, final long totalCount) {
@@ -188,11 +191,6 @@ public final class LeafProfiler {
long[] timers,
long[] counters
) {
private static final char[][] INDENT_PATTERNS = new char[][] {
"|---".toCharArray(),
"|+++".toCharArray(),
};
public List<String> dumpToString() {
final List<LProfileGraph.GraphNode> graphDFS = this.graph.getDFS();
final Reference2ReferenceOpenHashMap<LProfileGraph.GraphNode, ProfileNode> nodeMap = new Reference2ReferenceOpenHashMap<>();
@@ -232,14 +230,10 @@ public final class LeafProfiler {
totalTime += node.totalTime;
}
ProfileNode profileNode;
final StringBuilder builder = new StringBuilder();
while ((profileNode = orderedNodes.pollFirst()) != null) {
if (profileNode.nodeId != LProfileGraph.ROOT_NODE && profileNode.totalCount == 0L) {
// skip nodes not recorded
continue;
}
final ArrayDeque<ProfileNode> flatOrderedNodes = new ArrayDeque<>();
ProfileNode profileNode;
while ((profileNode = orderedNodes.pollFirst()) != null) {
final int depth = profileNode.depth;
profileNode.children.sort((final ProfileNode p1, final ProfileNode p2) -> {
final int typeCompare = p1.profiler.type().compareTo(p2.profiler.type());
@@ -257,12 +251,32 @@ public final class LeafProfiler {
}
});
boolean first = true;
for (int i = profileNode.children.size() - 1; i >= 0; --i) {
final ProfileNode child = profileNode.children.get(i);
if (child.totalCount == 0L) {
// skip nodes not recorded
continue;
}
if (first) {
child.lastChild = true;
first = false;
}
child.depth = depth + 1;
orderedNodes.addFirst(child);
}
flatOrderedNodes.addLast(profileNode);
}
final StringBuilder builder = new StringBuilder();
final IntList closed = new IntArrayList();
while ((profileNode = flatOrderedNodes.pollFirst()) != null) {
final int depth = profileNode.depth;
closed.removeIf((int d) -> d >= depth);
if (profileNode.lastChild) {
closed.add(depth);
}
if (profileNode.nodeId == LProfileGraph.ROOT_NODE) {
// don't display root
continue;
@@ -280,9 +294,18 @@ public final class LeafProfiler {
// <indent>#<name> avg X sum Y
builder.setLength(0);
// prepare indent
final char[] indent = INDENT_PATTERNS[ret.size() % INDENT_PATTERNS.length];
for (int i = 0; i < depth; ++i) {
builder.append(indent);
if (i == depth - 1) {
if (flatOrderedNodes.peekFirst() == null || profileNode.lastChild) {
builder.append(" └─");
} else {
builder.append(" ├─");
}
} else if (!closed.contains(i + 1)) {
builder.append("");
} else {
builder.append(" ");
}
}
switch (profilerEntry.type()) {

View File

@@ -209,7 +209,7 @@ public final class BlockStarLightEngine extends StarLightEngine {
final LevelChunkSection[] sections = chunk.getSections();
for (int sectionY = this.minSection; sectionY <= this.maxSection; ++sectionY) {
final LevelChunkSection section = sections[sectionY - this.minSection];
if (section == null || section.hasOnlyAir()) {
if (section.hasOnlyAir()) {
// no sources in empty sections
continue;
}
@@ -220,16 +220,17 @@ public final class BlockStarLightEngine extends StarLightEngine {
final PalettedContainer<BlockState> states = section.states;
final int offY = sectionY << 4;
final BlockPos.MutableBlockPos mutablePos = this.mutablePos1;
for (int index = 0; index < (16 * 16 * 16); ++index) {
final BlockState state = states.get(index);
this.mutablePos1.set(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15));
mutablePos.set(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15));
if ((platformHooks.getLightEmission(state, world, this.mutablePos1)) == 0) {
if ((platformHooks.getLightEmission(state, world, mutablePos)) == 0) {
continue;
}
// index = x | (z << 4) | (y << 8)
sources.add(this.mutablePos1.immutable());
sources.add(mutablePos.immutable());
}
}

View File

@@ -44,6 +44,7 @@ accessible method net/minecraft/server/level/ChunkMap upgradeChunkTag (Lnet/mine
accessible field net/minecraft/server/level/ChunkMap worldGenContext Lnet/minecraft/world/level/chunk/status/WorldGenContext;
accessible field net/minecraft/server/level/ChunkMap tickingGenerated Ljava/util/concurrent/atomic/AtomicInteger;
accessible field net/minecraft/server/level/ChunkMap progressListener Lnet/minecraft/server/level/progress/ChunkProgressListener;
accessible method net/minecraft/server/level/ChunkMap playerIsCloseEnoughForSpawning (Lnet/minecraft/server/level/ServerPlayer;Lnet/minecraft/world/level/ChunkPos;)Z
# ChunkLevel
accessible field net/minecraft/server/level/ChunkLevel FULL_CHUNK_LEVEL I
@@ -293,3 +294,7 @@ accessible class net/minecraft/world/level/chunk/storage/RegionFile$ChunkBuffer
# DensityFunctions$EndIslandDensityFunction
accessible class net/minecraft/world/level/levelgen/DensityFunctions$EndIslandDensityFunction
# LocalMobCapCalculator$MobCounts
accessible class net/minecraft/world/level/LocalMobCapCalculator$MobCounts

View File

@@ -16,8 +16,9 @@
"blockstate_propertyaccess.IntegerPropertyMixin",
"blockstate_propertyaccess.PropertyMixin",
"blockstate_propertyaccess.StateHolderMixin",
"chunk_getblock.ChunkAccessMixin",
"chunk_getblock.LevelChunkMixin",
"getblock.ChunkAccessMixin",
"getblock.LevelChunkMixin",
"getblock.LevelMixin",
"chunk_system.ChunkBufferMixin",
"chunk_system.ChunkGeneratorMixin",
"chunk_system.ChunkHolderMixin",
@@ -56,6 +57,7 @@
"chunk_tick_iteration.ChunkMapMixin",
"chunk_tick_iteration.DistanceManagerMixin",
"chunk_tick_iteration.ServerChunkCacheMixin",
"chunk_tick_iteration.ServerLevelMixin",
"collisions.ArmorStandMixin",
"collisions.ArrayVoxelShapeMixin",
"collisions.BitSetDiscreteVoxelShapeMixin",
@@ -89,7 +91,12 @@
"fast_palette.SingleValuePaletteMixin",
"fluid.FlowingFluidMixin",
"fluid.FluidStateMixin",
"fluid.MappedRegistryMixin",
"keep_alive_client.ServerGamePacketListenerImplMixin",
"mob_spawning.EntityTypeMixin",
"mob_spawning.LocalMobCapCalculator$MobCountsMixin",
"mob_spawning.MobSpawnSettingsMixin",
"mob_spawning.NaturalSpawnerMixin",
"poi_lookup.AcquirePoiMixin",
"poi_lookup.PoiManagerMixin",
"poi_lookup.PortalForcerMixin",