Compare commits

..

32 Commits

Author SHA1 Message Date
Spottedleaf
6841805446 Set version to 0.1.0-beta.13 2025-06-09 02:13:51 -07:00
Spottedleaf
eb20846213 Fix infinite loop in RegionFile IO
If an exception is thrown during decompress then the read process
would be started again, which of course would eventually throw in
the decompress process.
2025-06-09 02:12:01 -07:00
Spottedleaf
66a0850ac9 Set version to 0.1.0-SNAPSHOT 2025-02-24 20:59:03 -08:00
Spottedleaf
9a16a91fb8 Set version to 0.1.0-beta.12 2025-02-24 20:52:25 -08:00
Jason Penilla
580041a1a1 Clear lastSection on game event listener removal (fixes #87) (backport of #99) 2025-02-24 17:40:26 -07:00
Spottedleaf
fb31d0e1f7 Set version to 0.1.0-SNAPSHOT 2025-01-27 08:20:45 -08:00
Spottedleaf
0b2070d781 Set version to 0.1.0-beta.11 2025-01-27 08:12:17 -08:00
Spottedleaf
30a79469e3 Correctly retrun true for empty input shapes in EntityGetter#isUnobstructed
Vanilla will return true for empty shapes, so we should as well.
2025-01-27 07:59:20 -08:00
Jason Penilla
254b331259 Exclude transitive deps provided by Minecraft 2024-12-04 10:19:20 -07:00
Jason Penilla
a21c44ad31 Use libraries from Paper repo (#82)
* Use YamlConfig from Paper repo

* Add comment to settings

* Use ConcurrentUtil from Paper repo and use release of YamlConfig

* Use ConcurrentUtil release
2024-12-03 20:33:24 -07:00
Spottedleaf
77ba4a4584 Throw when sync loading chunks after shutdown
The caller would block indefinitely if a sync load is requested
during shutdown. Rather than block indefinitely, we should throw
an exception to indicate to the caller that the chunk system is
unable to process requests.
2024-12-03 20:32:14 -07:00
Spottedleaf
a510acbd78 Set version to 0.1.0-SNAPSHOT 2024-12-01 15:57:47 -08:00
Spottedleaf
6fdc3cb1df Set version to 0.1.0-beta.10 2024-12-01 15:52:36 -08:00
Spottedleaf
04e2b976ac Fix several off-by-one errors in view distance calculations
1. For NearbyPlayers, we need to be using the view distance, and
   not the load distance (which is +1 of the view distance).
2. Correctly clamp tick distance to view distance. Since
   load distance is +1 of view distance, we need to subtract
   one from the load distance when clamping.

Additionally, add checks inside ViewDistances to ensure that
the inputs are in range to catch future errors.
2024-12-01 15:23:59 -08:00
Spottedleaf
bd938e6e35 Clamp simulation distance
Values above MAX_VIEW_DISTANCE do not make sense to configure,
as it is clamped to the load view distance.
2024-12-01 14:24:33 -08:00
Spottedleaf
3624d261f8 Adjust constant collision shape determination
Our previous hack did not actually catch every case. For now,
it will only assume a constant collision shape of EMPTY for
air blocks.

Fixes https://github.com/PaperMC/Paper/issues/11697
2024-12-01 13:33:31 -08:00
Spottedleaf
6c75d6935e Force LazyEntityCollisionContext#getEntity() to delegate
By delegating when the entity is retrieved, we can correctly catch
cases where the collision method is inspecting some entity state.
2024-12-01 13:33:14 -08:00
Spottedleaf
e40c40613d Only call config initialisers if reload succeeded 2024-12-01 13:33:01 -08:00
Jason Penilla
84e7106762 Disable immediate loading screen closure by default 2024-11-26 15:53:44 -07:00
Spottedleaf
0218cb5979 Move YamlConfig to own project 2024-11-25 09:39:41 -08:00
Jason Penilla
7d0d5a56dd Back to 0.1.0-SNAPSHOT 2024-11-17 11:32:05 -07:00
Jason Penilla
4597f04a6d 0.1.0-beta.9 2024-11-17 10:56:53 -07:00
Jason Penilla
20764890a7 Update lithium, NeoForge, loom 2024-11-16 17:37:11 -07:00
Jason Penilla
f719abdb64 Update NeoForge lithium overrides 2024-11-16 17:35:43 -07:00
Jason Penilla
0dce47bba6 Apply FerriteCore config overrides automatically
New versions of FC added this mechanism

closes #66
2024-11-05 16:13:46 -07:00
Jason Penilla
47584ceee1 Use declaration order for state holder property iteration
Mostly an aesthetic change for serialization, should not have any impact on performance or correctness.
2024-10-27 18:32:42 -07:00
Jason Penilla
2fc858a683 Back to 0.1.0-SNAPSHOT 2024-10-26 11:05:51 -07:00
Jason Penilla
68ea18bc51 0.1.0-beta.8 2024-10-26 11:03:18 -07:00
Jason Penilla
9944946b29 Update Fabric API and call ServerChunkEvents.CHUNK_GENERATE 2024-10-26 09:54:34 -07:00
Jason Penilla
ec1120ed10 Back to 0.1.0-SNAPSHOT 2024-10-24 11:48:08 -07:00
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
132 changed files with 2043 additions and 2909 deletions

View File

@@ -31,8 +31,6 @@ jobs:
key: ${{ runner.os }}-project-local-gradle-caches-${{ hashFiles('**/libs.versions.toml', '**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-project-local-gradle-caches-
- name: "setup concurrentutil"
run: ./installConcurrentUtil.sh
- name: "execute gradle build"
run: ./gradlew build
- name: Determine Snapshot Status

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "ConcurrentUtil"]
path = ConcurrentUtil
url = https://github.com/Spottedleaf/ConcurrentUtil.git

Submodule ConcurrentUtil deleted from 08d3ca3241

View File

@@ -24,7 +24,7 @@ patches. Listed below are notable patches:
| Mod | Status |
|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 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> |
| FerriteCore | <details><summary>✅ compatible</summary>FerriteCore optimises some of the same parts of the game as Moonrise. Moonrise will automatically disable conflicting parts of FerriteCore. This mechanism needs to be manually validated for each Moonrise and FerriteCore release.</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

View File

@@ -22,7 +22,8 @@ def getGitCommit = { ->
dependencies {
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
api("ca.spottedleaf:concurrentutil:${rootProject.concurrentutil_version}")
api("ca.spottedleaf:concurrentutil:${rootProject.concurrentutil_version}") { setTransitive(false) }
api("ca.spottedleaf:yamlconfig:${rootProject.yamlconfig_version}") { setTransitive(false) }
api("org.yaml:snakeyaml:${rootProject.snakeyaml_version}")
modImplementation "me.shedaniel.cloth:cloth-config:${rootProject.cloth_version}"
@@ -45,9 +46,10 @@ allprojects {
}
repositories {
mavenLocal {
maven {
url "https://repo.papermc.io/repository/maven-public/"
mavenContent {
includeModule("ca.spottedleaf", "concurrentutil")
includeGroup("ca.spottedleaf")
}
}
maven {
@@ -98,8 +100,7 @@ subprojects {
}
loom.runs.all {
ideConfigGenerated true
property "mixin.debug", "true"
property "Moonrise.MaxViewDistance", "128"
// property "mixin.debug", "true"
}
plugins.apply("me.modmuss50.mod-publish-plugin")

View File

@@ -17,12 +17,13 @@ dependencies {
runtimeOnly(project(":").sourceSets.main.output)
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
libs("ca.spottedleaf:concurrentutil:${rootProject.concurrentutil_version}")
libs("ca.spottedleaf:concurrentutil:${rootProject.concurrentutil_version}") { setTransitive(false) }
libs("ca.spottedleaf:yamlconfig:${rootProject.yamlconfig_version}") { setTransitive(false) }
libs("org.yaml:snakeyaml:${rootProject.snakeyaml_version}")
modImplementation "me.shedaniel.cloth:cloth-config-fabric:${rootProject.cloth_version}"
include "me.shedaniel.cloth:cloth-config-fabric:${rootProject.cloth_version}"
modImplementation "com.terraformersmc:modmenu:${rootProject.modmenu_version}"
modImplementation "com.terraformersmc:modmenu:11.0.1"
modImplementation fabricApiLibs.command.api.v2
modImplementation fabricApiLibs.lifecycle.events.v1
@@ -43,6 +44,7 @@ shadowJar {
destinationDirectory = layout.buildDirectory.dir("libs")
configurations = [project.configurations.shadow]
relocate 'ca.spottedleaf.concurrentutil', 'ca.spottedleaf.moonrise.libs.ca.spottedleaf.concurrentutil'
relocate 'ca.spottedleaf.yamlconfig', 'ca.spottedleaf.moonrise.libs.ca.spottedleaf.yamlconfig'
relocate 'org.yaml.snakeyaml', 'ca.spottedleaf.moonrise.libs.org.yaml.snakeyaml'
}
@@ -60,7 +62,7 @@ publishMods {
incompatible(
"not-enough-crashes",
"starlight",
"c2me-fabric"
"c2me"
)
}
}

View File

@@ -2,31 +2,29 @@ package ca.spottedleaf.moonrise.fabric;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.common.util.ConfigHolder;
import com.mojang.datafixers.DSL;
import com.mojang.datafixers.DataFixer;
import com.mojang.serialization.Dynamic;
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.nbt.NbtOps;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.GenerationChunkHolder;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Explosion;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ImposterProtoChunk;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.status.ChunkStatusTasks;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import java.util.List;
import java.util.function.Predicate;
@@ -34,6 +32,19 @@ 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";
@@ -51,6 +62,16 @@ 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
public Vec3 modifyExplosionKnockback(final Level world, final Explosion explosion, final Entity entity, final Vec3 original) {
return original;
}
@Override
public boolean hasCurrentlyLoadingChunk() {
return false;
@@ -69,7 +90,7 @@ 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);
ServerChunkEvents.CHUNK_LOAD.invoker().onChunkLoad((ServerLevel) newChunk.getLevel(), newChunk);
if (!(original instanceof ImposterProtoChunk)) {
ServerChunkEvents.CHUNK_GENERATE.invoker().onChunkGenerate((ServerLevel)newChunk.getLevel(), newChunk);
}
@@ -82,19 +103,19 @@ public final class FabricHooks implements PlatformHooks {
}
@Override
public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel) {
public void onChunkHolderTicketChange(final ServerLevel world, final NewChunkHolder holder, final int oldLevel, final int newLevel) {
}
@Override
public void chunkUnloadFromWorld(final LevelChunk chunk) {
if (HAS_FABRIC_LIFECYCLE_EVENTS) {
ServerChunkEvents.CHUNK_UNLOAD.invoker().onChunkUnload((ServerLevel)chunk.getLevel(), chunk);
ServerChunkEvents.CHUNK_UNLOAD.invoker().onChunkUnload((ServerLevel) chunk.getLevel(), chunk);
}
}
@Override
public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) {
public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final CompoundTag data) {
}
@@ -166,12 +187,12 @@ public final class FabricHooks implements PlatformHooks {
}
@Override
public long configAutoSaveInterval(final ServerLevel world) {
public long configAutoSaveInterval() {
return ConfigHolder.getConfig().chunkSaving.autoSaveInterval.getTimeTicks();
}
@Override
public int configMaxAutoSavePerTick(final ServerLevel world) {
public int configMaxAutoSavePerTick() {
return ConfigHolder.getConfig().chunkSaving.maxAutoSaveChunksPerTick;
}
@@ -179,47 +200,4 @@ public final class FabricHooks implements PlatformHooks {
public boolean configFixMC159283() {
return ConfigHolder.getConfig().bugFixes.fixMC159283;
}
@Override
public boolean forceNoSave(final ChunkAccess chunk) {
return false;
}
@Override
public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
final int fromVersion, final int toVersion) {
return (CompoundTag)dataFixer.update(
type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion
).getValue();
}
@Override
public boolean hasMainChunkLoadHook() {
return false;
}
@Override
public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData) {
}
@Override
public List<Entity> modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List<Entity> entities) {
return entities;
}
@Override
public void unloadEntity(final Entity entity) {
entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK);
}
@Override
public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) {
ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities());
}
@Override
public int modifyEntityTrackingRange(final Entity entity, final int currentRange) {
return currentRange;
}
}

View File

@@ -36,7 +36,7 @@
"accessWidener": "moonrise.accesswidener",
"depends": {
"fabricloader": ">=${loader_version}",
"minecraft": ">1.21.1 <1.21.4",
"minecraft": ">=1.21 <=1.21.1",
"fabric-command-api-v2": "*"
},
"custom": {
@@ -69,6 +69,10 @@
"mixin.world.explosions.cache_exposure": false,
"mixin.world.temperature_cache": false,
"mixin.world.tick_scheduler": false
}
},
"ferritecore:disabled_options": [
"replaceNeighborLookup",
"replacePropertyMap"
]
}
}

View File

@@ -3,19 +3,18 @@ org.gradle.jvmargs=-Xmx2G
org.gradle.daemon=false
# Fabric Properties
# check these on https://modmuss50.me/fabric.html
minecraft_version=1.21.3
loader_version=0.16.7
supported_minecraft_versions=1.21.3
neoforge_version=21.3.0-beta
fabric_api_version=0.107.0+1.21.3
snakeyaml_version=2.2
concurrentutil_version=0.0.2-SNAPSHOT
cloth_version=16.0.141
modmenu_version=12.0.0-beta.1
minecraft_version=1.21.1
supported_minecraft_versions=1.21,1.21.1
loader_version=0.16.5
neoforge_version=21.1.79
snakeyaml_version=2.3
concurrentutil_version=0.0.2
yamlconfig_version=1.0.2
cloth_version=15.0.128
# version ids from modrinth
fabric_lithium_version=mc1.21.1-0.14.0-beta.1
neo_lithium_version=BrMIoIMv
fabric_lithium_version=frXUdgvL
neo_lithium_version=KhdehJ6l
# Mod Properties
mod_version=0.2.0-beta.2
mod_version=0.1.0-beta.13
maven_group=ca.spottedleaf.moonrise
archives_base_name=moonrise

View File

@@ -1,6 +0,0 @@
#!/bin/bash
set -eou pipefail
git submodule update --init --recursive
cd ConcurrentUtil
mvn install

View File

@@ -21,7 +21,8 @@ dependencies {
add('shadow', project([path: ":", configuration: "namedElements"]))
neoForge "net.neoforged:neoforge:${rootProject.neoforge_version}"
shadow("ca.spottedleaf:concurrentutil:${rootProject.concurrentutil_version}")
shadow("ca.spottedleaf:concurrentutil:${rootProject.concurrentutil_version}") { setTransitive(false) }
shadow("ca.spottedleaf:yamlconfig:${rootProject.yamlconfig_version}") { setTransitive(false) }
shadow("org.yaml:snakeyaml:${rootProject.snakeyaml_version}")
forgeExtra("org.yaml:snakeyaml:${rootProject.snakeyaml_version}")
@@ -42,6 +43,7 @@ shadowJar {
destinationDirectory = layout.buildDirectory.dir("libs")
configurations = [project.configurations.shadow]
relocate 'ca.spottedleaf.concurrentutil', 'ca.spottedleaf.moonrise.libs.ca.spottedleaf.concurrentutil'
relocate 'ca.spottedleaf.yamlconfig', 'ca.spottedleaf.moonrise.libs.ca.spottedleaf.yamlconfig'
relocate 'org.yaml.snakeyaml', 'ca.spottedleaf.moonrise.libs.org.yaml.snakeyaml'
}

View File

@@ -3,13 +3,9 @@ package ca.spottedleaf.moonrise.neoforge;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.common.util.ConfigHolder;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import com.mojang.datafixers.DSL;
import com.mojang.datafixers.DataFixer;
import com.mojang.serialization.Dynamic;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.GenerationChunkHolder;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
@@ -17,16 +13,16 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.EmptyBlockGetter;
import net.minecraft.world.level.Explosion;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ImposterProtoChunk;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.status.ChunkStatusTasks;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import net.neoforged.neoforge.common.CommonHooks;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.entity.PartEntity;
@@ -56,6 +52,16 @@ public final class NeoForgeHooks implements PlatformHooks {
};
}
@Override
public void onExplosion(final Level world, final Explosion explosion, final List<Entity> possiblyAffecting, final double diameter) {
EventHooks.onExplosionDetonate(world, explosion, possiblyAffecting, diameter);
}
@Override
public Vec3 modifyExplosionKnockback(final Level world, final Explosion explosion, final Entity entity, final Vec3 original) {
return EventHooks.getExplosionKnockback(world, explosion, entity, original);
}
@Override
public boolean hasCurrentlyLoadingChunk() {
return true;
@@ -82,12 +88,10 @@ public final class NeoForgeHooks implements PlatformHooks {
}
@Override
public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel) {
final ChunkPos pos = holder.getPos();
public void onChunkHolderTicketChange(final ServerLevel world, final NewChunkHolder holder, final int oldLevel, final int newLevel) {
EventHooks.fireChunkTicketLevelUpdated(
world, CoordinateUtils.getChunkKey(pos.x, pos.z),
oldLevel, newLevel, holder
world, CoordinateUtils.getChunkKey(holder.chunkX, holder.chunkZ),
oldLevel, newLevel, holder.vanillaChunkHolder
);
}
@@ -97,7 +101,7 @@ public final class NeoForgeHooks implements PlatformHooks {
}
@Override
public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data) {
public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final CompoundTag data) {
NeoForge.EVENT_BUS.post(new ChunkDataEvent.Save(chunk, world, data));
}
@@ -189,12 +193,12 @@ public final class NeoForgeHooks implements PlatformHooks {
}
@Override
public long configAutoSaveInterval(final ServerLevel world) {
public long configAutoSaveInterval() {
return ConfigHolder.getConfig().chunkSaving.autoSaveInterval.getTimeTicks();
}
@Override
public int configMaxAutoSavePerTick(final ServerLevel world) {
public int configMaxAutoSavePerTick() {
return ConfigHolder.getConfig().chunkSaving.maxAutoSaveChunksPerTick;
}
@@ -202,47 +206,4 @@ public final class NeoForgeHooks implements PlatformHooks {
public boolean configFixMC159283() {
return ConfigHolder.getConfig().bugFixes.fixMC159283;
}
@Override
public boolean forceNoSave(final ChunkAccess chunk) {
return false;
}
@Override
public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
final int fromVersion, final int toVersion) {
return (CompoundTag)dataFixer.update(
type, new Dynamic<>(NbtOps.INSTANCE, nbt), fromVersion, toVersion
).getValue();
}
@Override
public boolean hasMainChunkLoadHook() {
return true;
}
@Override
public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData) {
NeoForge.EVENT_BUS.post(new ChunkDataEvent.Load(chunk, chunkData));
}
@Override
public List<Entity> modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List<Entity> entities) {
return entities;
}
@Override
public void unloadEntity(final Entity entity) {
entity.setRemoved(Entity.RemovalReason.UNLOADED_TO_CHUNK);
}
@Override
public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk) {
ChunkStatusTasks.postLoadProtoChunk(world, chunk.getEntities());
}
@Override
public int modifyEntityTrackingRange(final Entity entity, final int currentRange) {
return currentRange;
}
}

View File

@@ -13,6 +13,10 @@ displayURL = "https://github.com/Tuinity/Moonrise"
authors = "Spottedleaf"
description = "Optimisation mod for the dedicated and integrated server."
displayTest = "IGNORE_ALL_VERSION"
"ferritecore:disabled_options" = [
"replaceNeighborLookup",
"replacePropertyMap"
]
[[dependencies.moonrise]]
modId = "neoforge"
@@ -24,7 +28,7 @@ side = "BOTH"
[[dependencies.moonrise]]
modId = "minecraft"
type = "required"
versionRange = "(1.21.1,1.21.4)"
versionRange = "[1.21,1.21.2)"
ordering = "NONE"
side = "BOTH"
@@ -51,7 +55,7 @@ config = "moonrise.mixins.json"
[[mixins]]
config = "moonrise-neoforge.mixins.json"
[mods."lithium:options"]
["lithium:options"]
"mixin.ai.poi" = false
"mixin.alloc.chunk_ticking" = false
"mixin.alloc.deep_passengers" = false

View File

@@ -24,7 +24,7 @@ pluginManagement {
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
id("xyz.jpenilla.quiet-architectury-loom") version "1.7.297" apply false
id("xyz.jpenilla.quiet-architectury-loom") version "1.7.300" apply false
id 'com.gradleup.shadow' version '8.3.0' apply false
}
@@ -37,7 +37,7 @@ dependencyResolutionManagement {
}
versionCatalogs {
create("fabricApiLibs") {
from("net.fabricmc.fabric-api:fabric-api-catalog:${fabric_api_version}")
from("net.fabricmc.fabric-api:fabric-api-catalog:0.107.0+1.21.1")
}
}
}
@@ -48,3 +48,6 @@ include("fabric")
findProject(":fabric").name = "Moonrise-Fabric"
include("neoforge")
findProject(":neoforge").name = "Moonrise-NeoForge"
// includeBuild("../YamlConfig") // Uncomment to use local YamlConfig
// includeBuild("../ConcurrentUtil") // Uncomment to use local ConcurrentUtil

View File

@@ -1,25 +1,23 @@
package ca.spottedleaf.moonrise.common;
import com.mojang.datafixers.DSL;
import com.mojang.datafixers.DataFixer;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.GenerationChunkHolder;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.datafix.DataFixTypes;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Explosion;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import java.util.List;
import java.util.ServiceLoader;
import java.util.function.Predicate;
@@ -35,6 +33,10 @@ public interface PlatformHooks {
public Predicate<BlockState> maybeHasLightEmission();
public void onExplosion(final Level world, final Explosion explosion, final List<Entity> possiblyAffecting, final double diameter);
public Vec3 modifyExplosionKnockback(final Level world, final Explosion explosion, final Entity entity, final Vec3 original);
public boolean hasCurrentlyLoadingChunk();
public LevelChunk getCurrentlyLoadingChunk(final GenerationChunkHolder holder);
@@ -45,11 +47,11 @@ public interface PlatformHooks {
public boolean allowAsyncTicketUpdates();
public void onChunkHolderTicketChange(final ServerLevel world, final ChunkHolder holder, final int oldLevel, final int newLevel);
public void onChunkHolderTicketChange(final ServerLevel world, final NewChunkHolder holder, final int oldLevel, final int newLevel);
public void chunkUnloadFromWorld(final LevelChunk chunk);
public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final SerializableChunkData data);
public void chunkSyncSave(final ServerLevel world, final ChunkAccess chunk, final CompoundTag data);
public void onChunkWatch(final ServerLevel world, final LevelChunk chunk, final ServerPlayer player);
@@ -80,30 +82,12 @@ public interface PlatformHooks {
public int configPlayerMaxConcurrentGens();
public long configAutoSaveInterval(final ServerLevel world);
public long configAutoSaveInterval();
public int configMaxAutoSavePerTick(final ServerLevel world);
public int configMaxAutoSavePerTick();
public boolean configFixMC159283();
// support for CB chunk mustNotSave
public boolean forceNoSave(final ChunkAccess chunk);
public CompoundTag convertNBT(final DSL.TypeReference type, final DataFixer dataFixer, final CompoundTag nbt,
final int fromVersion, final int toVersion);
public boolean hasMainChunkLoadHook();
public void mainChunkLoad(final ChunkAccess chunk, final SerializableChunkData chunkData);
public List<Entity> modifySavedEntities(final ServerLevel world, final int chunkX, final int chunkZ, final List<Entity> entities);
public void unloadEntity(final Entity entity);
public void postLoadProtoChunk(final ServerLevel world, final ProtoChunk chunk);
public int modifyEntityTrackingRange(final Entity entity, final int currentRange);
public static final class Holder {
private Holder() {
}

View File

@@ -1,7 +0,0 @@
package ca.spottedleaf.moonrise.common.config;
public interface InitialiseHook {
public void initialise();
}

View File

@@ -1,11 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter;
import java.lang.reflect.Type;
public abstract class TypeAdapter<T, S> {
public abstract T deserialize(final TypeAdapterRegistry registry, final Object input, final Type type);
public abstract S serialize(final TypeAdapterRegistry registry, final T value, final Type type);
}

View File

@@ -1,307 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter;
import ca.spottedleaf.moonrise.common.config.InitialiseHook;
import ca.spottedleaf.moonrise.common.config.adapter.collection.CollectionTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.collection.ListTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.collection.SortedMapTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.primitive.BooleanTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.primitive.ByteTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.primitive.DoubleTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.primitive.FloatTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.primitive.IntegerTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.primitive.LongTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.primitive.ShortTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.primitive.StringTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.type.BigDecimalTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.type.BigIntegerTypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.type.DurationTypeAdapter;
import ca.spottedleaf.moonrise.common.config.annotation.Adaptable;
import ca.spottedleaf.moonrise.common.config.annotation.Serializable;
import ca.spottedleaf.moonrise.common.config.type.Duration;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public final class TypeAdapterRegistry {
private final Map<Class<?>, TypeAdapter<?, ?>> adapters = new HashMap<>();
{
this.adapters.put(boolean.class, BooleanTypeAdapter.INSTANCE);
this.adapters.put(byte.class, ByteTypeAdapter.INSTANCE);
this.adapters.put(short.class, ShortTypeAdapter.INSTANCE);
this.adapters.put(int.class, IntegerTypeAdapter.INSTANCE);
this.adapters.put(long.class, LongTypeAdapter.INSTANCE);
this.adapters.put(float.class, FloatTypeAdapter.INSTANCE);
this.adapters.put(double.class, DoubleTypeAdapter.INSTANCE);
this.adapters.put(Boolean.class, BooleanTypeAdapter.INSTANCE);
this.adapters.put(Byte.class, ByteTypeAdapter.INSTANCE);
this.adapters.put(Short.class, ShortTypeAdapter.INSTANCE);
this.adapters.put(Integer.class, IntegerTypeAdapter.INSTANCE);
this.adapters.put(Long.class, LongTypeAdapter.INSTANCE);
this.adapters.put(Float.class, FloatTypeAdapter.INSTANCE);
this.adapters.put(Double.class, DoubleTypeAdapter.INSTANCE);
this.adapters.put(String.class, StringTypeAdapter.INSTANCE);
this.adapters.put(Collection.class, CollectionTypeAdapter.INSTANCE);
this.adapters.put(List.class, ListTypeAdapter.INSTANCE);
this.adapters.put(Map.class, SortedMapTypeAdapter.SORTED_CASE_INSENSITIVE);
this.adapters.put(LinkedHashMap.class, SortedMapTypeAdapter.SORTED_CASE_INSENSITIVE);
this.adapters.put(BigInteger.class, BigIntegerTypeAdapter.INSTANCE);
this.adapters.put(BigDecimal.class, BigDecimalTypeAdapter.INSTANCE);
this.adapters.put(Duration.class, DurationTypeAdapter.INSTANCE);
}
public TypeAdapter<?, ?> putAdapter(final Class<?> clazz, final TypeAdapter<?, ?> adapter) {
return this.adapters.put(clazz, adapter);
}
public TypeAdapter<?, ?> getAdapter(final Class<?> clazz) {
return this.adapters.get(clazz);
}
public Object deserialize(final Object input, final Type type) {
TypeAdapter<?, ?> adapter = null;
if (type instanceof Class<?> clazz) {
adapter = this.adapters.get(clazz);
}
if (adapter == null && (type instanceof ParameterizedType parameterizedType)) {
adapter = this.adapters.get((Class<?>)parameterizedType.getRawType());
}
if (adapter == null) {
throw new IllegalArgumentException("No adapter for " + input.getClass() + " with type " + type);
}
return ((TypeAdapter)adapter).deserialize(this, input, type);
}
public Object serialize(final Object input, final Type type) {
TypeAdapter<?, ?> adapter = null;
if (type instanceof Class<?> clazz) {
adapter = this.adapters.get(clazz);
}
if (adapter == null && (type instanceof ParameterizedType parameterizedType)) {
adapter = this.adapters.get((Class<?>)parameterizedType.getRawType());
}
if (adapter == null) {
adapter = this.adapters.get(input.getClass());
}
if (adapter == null) {
throw new IllegalArgumentException("No adapter for " + input.getClass() + " with type " + type);
}
return ((TypeAdapter)adapter).serialize(this, input, type);
}
public <T> TypeAdapter<T, Map<Object, Object>> makeAdapter(final Class<? extends T> clazz) throws Exception {
final TypeAdapter<T, Map<Object, Object>> ret = new AutoTypeAdapter<>(this, clazz);
this.putAdapter(clazz, ret);
return ret;
}
public <T> void callInitialisers(final T object) {
if (object == null) {
return;
}
final TypeAdapter<?, ?> adapter = this.getAdapter(object.getClass());
if (!(adapter instanceof AutoTypeAdapter<?> autoTypeAdapter)) {
return;
}
((AutoTypeAdapter<T>)autoTypeAdapter).callInitialisers(object);
}
private static final class AutoTypeAdapter<T> extends TypeAdapter<T, Map<Object, Object>> {
private final TypeAdapterRegistry registry;
private final Constructor<? extends T> constructor;
private final SerializableField[] fields;
public AutoTypeAdapter(final TypeAdapterRegistry registry, final Class<? extends T> clazz) throws Exception {
this.registry = registry;
this.constructor = clazz.getConstructor();
this.fields = findSerializableFields(registry, clazz);
}
private static TypeAdapter<?, ?> findOrMakeAdapter(final TypeAdapterRegistry registry, final Class<?> clazz) throws Exception {
final TypeAdapter<?, ?> ret = registry.getAdapter(clazz);
if (ret != null) {
return ret;
}
for (final Annotation annotation : clazz.getAnnotations()) {
if (annotation instanceof Adaptable adaptable) {
return registry.makeAdapter(clazz);
}
}
throw new IllegalArgumentException("No type adapter for " + clazz + " (Forgot @Adaptable?)");
}
private static String makeSerializedKey(final String input) {
final StringBuilder ret = new StringBuilder();
for (final char c : input.toCharArray()) {
if (!Character.isUpperCase(c)) {
ret.append(c);
continue;
}
ret.append('-');
ret.append(Character.toLowerCase(c));
}
return ret.toString();
}
private static record SerializableField(
Field field,
boolean required,
String comment,
TypeAdapter<?, ?> adapter,
boolean serialize,
String serializedKey
) {}
private static SerializableField[] findSerializableFields(final TypeAdapterRegistry registry, Class<?> clazz) throws Exception {
final List<SerializableField> ret = new ArrayList<>();
do {
for (final Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
for (final Annotation annotation : field.getAnnotations()) {
if (!(annotation instanceof Serializable serializable)) {
continue;
}
final TypeAdapter<?, ?> adapter;
if (serializable.adapter() != TypeAdapter.class) {
adapter = serializable.adapter().getConstructor().newInstance();
} else {
adapter = findOrMakeAdapter(registry, field.getType());
}
String serializedKey = serializable.serializedKey();
if (serializedKey.isEmpty()) {
serializedKey = makeSerializedKey(field.getName());
}
ret.add(new SerializableField(
field, serializable.required(), serializable.comment(), adapter,
serializable.serialize(), serializedKey
));
}
}
} while ((clazz = clazz.getSuperclass()) != Object.class);
ret.sort((final SerializableField c1, final SerializableField c2) -> {
return c1.serializedKey.compareTo(c2.serializedKey);
});
return ret.toArray(new SerializableField[0]);
}
@Override
public T deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (!(input instanceof Map<?,?> inputMap)) {
throw new IllegalArgumentException("Not a map type: " + input.getClass());
}
try {
final T ret = this.constructor.newInstance();
for (final SerializableField field : this.fields) {
final Object fieldValue = inputMap.get(field.serializedKey);
if (fieldValue == null) {
if (field.required) {
throw new IllegalArgumentException("Missing required field '" + field.serializedKey + "' in " + this.constructor.getDeclaringClass());
}
continue;
}
field.field.set(ret, field.adapter.deserialize(registry, fieldValue, field.field.getGenericType()));
}
return ret;
} catch (final Exception ex) {
throw new RuntimeException(ex);
}
}
@Override
public Map<Object, Object> serialize(final TypeAdapterRegistry registry, final T value, final Type type) {
final LinkedHashMap<Object, Object> ret = new LinkedHashMap<>();
for (final SerializableField field : this.fields) {
if (!field.serialize) {
continue;
}
final Object fieldValue;
try {
fieldValue = field.field.get(value);
} catch (final Exception ex) {
throw new RuntimeException(ex);
}
if (fieldValue != null) {
ret.put(
field.comment.isBlank() ? field.serializedKey : new CommentedData(field.comment, field.serializedKey),
((TypeAdapter)field.adapter).serialize(
registry, fieldValue, field.field.getGenericType()
)
);
}
}
return ret;
}
public void callInitialisers(final T value) {
for (final SerializableField field : this.fields) {
final Object fieldValue;
try {
fieldValue = field.field.get(value);
} catch (final Exception ex) {
throw new RuntimeException(ex);
}
if (fieldValue instanceof InitialiseHook initialiseHook) {
initialiseHook.initialise();
}
this.registry.callInitialisers(fieldValue);
}
}
}
public static final class CommentedData {
public final String comment;
public final Object data;
public CommentedData(final String comment, final Object data) {
this.comment = comment;
this.data = data;
}
}
}

View File

@@ -1,46 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.collection;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import ca.spottedleaf.moonrise.common.config.adapter.primitive.StringTypeAdapter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public final class CollectionTypeAdapter extends TypeAdapter<Collection<Object>, List<Object>> {
public static final CollectionTypeAdapter INSTANCE = new CollectionTypeAdapter();
@Override
public Collection<Object> deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (!(type instanceof ParameterizedType parameterizedType)) {
throw new IllegalArgumentException("Collection field must specify generic type");
}
final Type elementType = parameterizedType.getActualTypeArguments()[0];
if (input instanceof Collection<?> collection) {
final List<Object> ret = new ArrayList<>(collection.size());
for (final Object v : collection) {
ret.add(registry.deserialize(v, elementType));
}
return ret;
}
throw new IllegalArgumentException("Not a collection type: " + input.getClass());
}
@Override
public List<Object> serialize(final TypeAdapterRegistry registry, final Collection<Object> value, final Type type) {
final List<Object> ret = new ArrayList<>(value.size());
final Type elementType = type instanceof ParameterizedType parameterizedType ? parameterizedType.getActualTypeArguments()[0] : null;
for (final Object v : value) {
ret.add(registry.serialize(v, elementType == null ? v.getClass() : elementType));
}
return ret;
}
}

View File

@@ -1,45 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.collection;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public final class ListTypeAdapter extends TypeAdapter<List<Object>, List<Object>> {
public static final ListTypeAdapter INSTANCE = new ListTypeAdapter();
@Override
public List<Object> deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (!(type instanceof ParameterizedType parameterizedType)) {
throw new IllegalArgumentException("Collection field must specify generic type");
}
final Type elementType = parameterizedType.getActualTypeArguments()[0];
if (input instanceof Collection<?> collection) {
final List<Object> ret = new ArrayList<>(collection.size());
for (final Object v : collection) {
ret.add(registry.deserialize(v, elementType));
}
return ret;
}
throw new IllegalArgumentException("Not a collection type: " + input.getClass());
}
@Override
public List<Object> serialize(final TypeAdapterRegistry registry, final List<Object> value, final Type type) {
final List<Object> ret = new ArrayList<>(value.size());
final Type elementType = type instanceof ParameterizedType parameterizedType ? parameterizedType.getActualTypeArguments()[0] : null;
for (final Object v : value) {
ret.add(registry.serialize(v, elementType));
}
return ret;
}
}

View File

@@ -1,59 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.collection;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
public final class SortedMapTypeAdapter extends TypeAdapter<Map<String, Object>, Map<String, Object>> {
public static final SortedMapTypeAdapter SORTED_CASE_INSENSITIVE = new SortedMapTypeAdapter(String.CASE_INSENSITIVE_ORDER);
public static final SortedMapTypeAdapter SORTED_CASE_SENSITIVE = new SortedMapTypeAdapter(null);
private final Comparator<String> keyComparator;
public SortedMapTypeAdapter(final Comparator<String> keyComparator) {
this.keyComparator = keyComparator;
}
@Override
public Map<String, Object> deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (!(type instanceof ParameterizedType parameterizedType)) {
throw new IllegalArgumentException("Collection field must specify generic type");
}
final Type valueType = parameterizedType.getActualTypeArguments()[1];
if (input instanceof Map<?,?> inputMap) {
final Map<String, Object> castedInput = (Map<String, Object>)inputMap;
final TreeMap<String, Object> ret = new TreeMap<>(this.keyComparator);
for (final Map.Entry<String, Object> entry : castedInput.entrySet()) {
ret.put(entry.getKey(), registry.deserialize(entry.getValue(), valueType));
}
// transform to linked so that get() is O(1)
return new LinkedHashMap<>(ret);
}
throw new IllegalArgumentException("Not a map type: " + input.getClass());
}
@Override
public Map<String, Object> serialize(final TypeAdapterRegistry registry, final Map<String, Object> value, final Type type) {
final TreeMap<String, Object> ret = new TreeMap<>(this.keyComparator);
final Type valueType = type instanceof ParameterizedType parameterizedType ? parameterizedType.getActualTypeArguments()[1] : null;
for (final Map.Entry<String, Object> entry : value.entrySet()) {
ret.put(entry.getKey(), registry.serialize(entry.getValue(), valueType));
}
// transform to linked so that get() is O(1)
return new LinkedHashMap<>(ret);
}
}

View File

@@ -1,47 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.collection;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
public final class UnsortedMapTypeAdapter extends TypeAdapter<Map<String, Object>, Map<String, Object>> {
public static final UnsortedMapTypeAdapter INSTANCE = new UnsortedMapTypeAdapter();
@Override
public Map<String, Object> deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (!(type instanceof ParameterizedType parameterizedType)) {
throw new IllegalArgumentException("Collection field must specify generic type");
}
final Type valueType = parameterizedType.getActualTypeArguments()[1];
if (input instanceof Map<?,?> inputMap) {
final Map<String, Object> castedInput = (Map<String, Object>)inputMap;
final LinkedHashMap<String, Object> ret = new LinkedHashMap<>();
for (final Map.Entry<String, Object> entry : castedInput.entrySet()) {
ret.put(entry.getKey(), registry.deserialize(entry.getValue(), valueType));
}
return ret;
}
throw new IllegalArgumentException("Not a map type: " + input.getClass());
}
@Override
public Map<String, Object> serialize(final TypeAdapterRegistry registry, final Map<String, Object> value, final Type type) {
final LinkedHashMap<String, Object> ret = new LinkedHashMap<>();
final Type valueType = type instanceof ParameterizedType parameterizedType ? parameterizedType.getActualTypeArguments()[1] : null;
for (final Map.Entry<String, Object> entry : value.entrySet()) {
ret.put(entry.getKey(), registry.serialize(entry.getValue(), valueType));
}
return ret;
}
}

View File

@@ -1,33 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.primitive;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
public final class BooleanTypeAdapter extends TypeAdapter<Boolean, Boolean> {
public static final BooleanTypeAdapter INSTANCE = new BooleanTypeAdapter();
@Override
public Boolean deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Boolean ret) {
return ret;
}
if (input instanceof String str) {
if (str.equalsIgnoreCase("false")) {
return Boolean.FALSE;
}
if (str.equalsIgnoreCase("true")) {
return Boolean.TRUE;
}
throw new IllegalArgumentException("Not a boolean: " + str);
}
throw new IllegalArgumentException("Not a boolean type: " + input.getClass());
}
@Override
public Boolean serialize(final TypeAdapterRegistry registry, final Boolean value, final Type type) {
return value;
}
}

View File

@@ -1,36 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.primitive;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
import java.math.BigInteger;
public final class ByteTypeAdapter extends TypeAdapter<Byte, Byte> {
public static final ByteTypeAdapter INSTANCE = new ByteTypeAdapter();
private static Byte cast(final Object original, final long value) {
if (value < (long)Byte.MIN_VALUE || value > (long)Byte.MAX_VALUE) {
throw new IllegalArgumentException("Byte value is out of range: " + original.toString());
}
return Byte.valueOf((byte)value);
}
@Override
public Byte deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Number number) {
// note: silently discard floating point significand
return cast(input, number instanceof BigInteger bigInteger ? bigInteger.longValueExact() : number.longValue());
}
if (input instanceof String string) {
return cast(input, (long)Double.parseDouble(string));
}
throw new IllegalArgumentException("Not a byte type: " + input.getClass());
}
@Override
public Byte serialize(final TypeAdapterRegistry registry, final Byte value, final Type type) {
return value;
}
}

View File

@@ -1,27 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.primitive;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
public final class DoubleTypeAdapter extends TypeAdapter<Double, Double> {
public static final DoubleTypeAdapter INSTANCE = new DoubleTypeAdapter();
@Override
public Double deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Number number) {
return Double.valueOf(number.doubleValue());
}
if (input instanceof String string) {
return Double.valueOf(Double.parseDouble(string));
}
throw new IllegalArgumentException("Not a byte type: " + input.getClass());
}
@Override
public Double serialize(final TypeAdapterRegistry registry, final Double value, final Type type) {
return value;
}
}

View File

@@ -1,35 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.primitive;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
public final class FloatTypeAdapter extends TypeAdapter<Float, Float> {
public static final FloatTypeAdapter INSTANCE = new FloatTypeAdapter();
private static Float cast(final Object original, final double value) {
if (value < -(double)Float.MAX_VALUE || value > (double)Float.MAX_VALUE) {
throw new IllegalArgumentException("Byte value is out of range: " + original.toString());
}
// note: silently ignore precision loss
return Float.valueOf((float)value);
}
@Override
public Float deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Number number) {
return cast(input, number.doubleValue());
}
if (input instanceof String string) {
return cast(input, Double.parseDouble(string));
}
throw new IllegalArgumentException("Not a byte type: " + input.getClass());
}
@Override
public Float serialize(final TypeAdapterRegistry registry, final Float value, final Type type) {
return value;
}
}

View File

@@ -1,36 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.primitive;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
import java.math.BigInteger;
public final class IntegerTypeAdapter extends TypeAdapter<Integer, Integer> {
public static final IntegerTypeAdapter INSTANCE = new IntegerTypeAdapter();
private static Integer cast(final Object original, final long value) {
if (value < (long)Integer.MIN_VALUE || value > (long)Integer.MAX_VALUE) {
throw new IllegalArgumentException("Integer value is out of range: " + original.toString());
}
return Integer.valueOf((int)value);
}
@Override
public Integer deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Number number) {
// note: silently discard floating point significand
return cast(input, number instanceof BigInteger bigInteger ? bigInteger.longValueExact() : number.longValue());
}
if (input instanceof String string) {
return cast(input, (long)Double.parseDouble(string));
}
throw new IllegalArgumentException("Not an integer type: " + input.getClass());
}
@Override
public Integer serialize(final TypeAdapterRegistry registry, final Integer value, final Type type) {
return value;
}
}

View File

@@ -1,33 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.primitive;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
import java.math.BigInteger;
public final class LongTypeAdapter extends TypeAdapter<Long, Long> {
public static final LongTypeAdapter INSTANCE = new LongTypeAdapter();
@Override
public Long deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Number number) {
// note: silently discard floating point significand
return number instanceof BigInteger bigInteger ? bigInteger.longValueExact() : number.longValue();
}
if (input instanceof String string) {
try {
return Long.valueOf(Long.parseLong(string));
} catch (final NumberFormatException ex) {
return Long.valueOf((long)Double.parseDouble(string));
}
}
throw new IllegalArgumentException("Not a long type: " + input.getClass());
}
@Override
public Long serialize(final TypeAdapterRegistry registry, final Long value, final Type type) {
return value;
}
}

View File

@@ -1,36 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.primitive;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
import java.math.BigInteger;
public final class ShortTypeAdapter extends TypeAdapter<Short, Short> {
public static final ShortTypeAdapter INSTANCE = new ShortTypeAdapter();
private static Short cast(final Object original, final long value) {
if (value < (long)Short.MIN_VALUE || value > (long)Short.MAX_VALUE) {
throw new IllegalArgumentException("Short value is out of range: " + original.toString());
}
return Short.valueOf((short)value);
}
@Override
public Short deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Number number) {
// note: silently discard floating point significand
return cast(input, number instanceof BigInteger bigInteger ? bigInteger.longValueExact() : number.longValue());
}
if (input instanceof String string) {
return cast(input, (long)Double.parseDouble(string));
}
throw new IllegalArgumentException("Not a short type: " + input.getClass());
}
@Override
public Short serialize(final TypeAdapterRegistry registry, final Short value, final Type type) {
return value;
}
}

View File

@@ -1,29 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.primitive;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
public final class StringTypeAdapter extends TypeAdapter<String, String> {
public static final StringTypeAdapter INSTANCE = new StringTypeAdapter();
@Override
public String deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Boolean bool) {
return String.valueOf(bool.booleanValue());
}
if (input instanceof Number number) {
return number.toString();
}
if (input instanceof String string) {
return string;
}
throw new IllegalArgumentException("Not a string type: " + input.getClass());
}
@Override
public String serialize(final TypeAdapterRegistry registry, final String value, final Type type) {
return value;
}
}

View File

@@ -1,30 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.type;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
public final class BigDecimalTypeAdapter extends TypeAdapter<BigDecimal, String> {
public static final BigDecimalTypeAdapter INSTANCE = new BigDecimalTypeAdapter();
@Override
public BigDecimal deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Number number) {
// safest to catch all number impls is to use toString
return new BigDecimal(number.toString());
}
if (input instanceof String string) {
return new BigDecimal(string);
}
throw new IllegalArgumentException("Not an BigDecimal type: " + input.getClass());
}
@Override
public String serialize(final TypeAdapterRegistry registry, final BigDecimal value, final Type type) {
return value.toString();
}
}

View File

@@ -1,37 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.type;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
public final class BigIntegerTypeAdapter extends TypeAdapter<BigInteger, String> {
public static final BigIntegerTypeAdapter INSTANCE = new BigIntegerTypeAdapter();
@Override
public BigInteger deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof Number number) {
if (number instanceof BigInteger bigInteger) {
return bigInteger;
}
// note: silently discard floating point significand
if (number instanceof BigDecimal bigDecimal) {
return bigDecimal.toBigInteger();
}
return BigInteger.valueOf(number.longValue());
}
if (input instanceof String string) {
return new BigDecimal(string).toBigInteger();
}
throw new IllegalArgumentException("Not an BigInteger type: " + input.getClass());
}
@Override
public String serialize(final TypeAdapterRegistry registry, final BigInteger value, final Type type) {
return value.toString();
}
}

View File

@@ -1,24 +0,0 @@
package ca.spottedleaf.moonrise.common.config.adapter.type;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import ca.spottedleaf.moonrise.common.config.type.Duration;
import java.lang.reflect.Type;
public final class DurationTypeAdapter extends TypeAdapter<Duration, String> {
public static final DurationTypeAdapter INSTANCE = new DurationTypeAdapter();
@Override
public Duration deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (!(input instanceof String string)) {
throw new IllegalArgumentException("Not a string: " + input.getClass());
}
return Duration.parse(string);
}
@Override
public String serialize(final TypeAdapterRegistry registry, final Duration value, final Type type) {
return value.toString();
}
}

View File

@@ -1,15 +0,0 @@
package ca.spottedleaf.moonrise.common.config.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation used on a class to indicate that its type adapter may automatically be generated. The class must have
* a public no-args constructor.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Adaptable {
}

View File

@@ -1,45 +0,0 @@
package ca.spottedleaf.moonrise.common.config.annotation;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.function.Function;
/**
* Annotation indicating that a field should be deserialized or serialized from the config.
* By default, this annotation is not assumed.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Serializable {
/**
* Indicates whether this field is required to be present in the config. If the field is not present,
* and {@code required = true}, then an exception will be thrown during deserialization. If {@code required = false}
* and the field is not present, then the field value will remain unmodified.
*/
public boolean required() default false;
/**
* The comment to apply before the element when serializing.
*/
public String comment() default "";
/**
* Adapter override class. The class must have a public no-args constructor.
*/
public Class<? extends TypeAdapter> adapter() default TypeAdapter.class;
/**
* Whether to serialize the value to the config.
*/
public boolean serialize() default true;
/**
* When not empty, this value overrides the auto generated serialized key in the config.
*/
public String serializedKey() default "";
}

View File

@@ -1,178 +0,0 @@
package ca.spottedleaf.moonrise.common.config.config;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.comments.CommentLine;
import org.yaml.snakeyaml.comments.CommentType;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.representer.Represent;
import org.yaml.snakeyaml.representer.Representer;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public final class YamlConfig<T> {
public final TypeAdapterRegistry typeAdapters;
private final Class<? extends T> clazz;
public volatile T config;
private final Yaml yaml;
private final LoaderOptions loaderOptions;
private final DumperOptions dumperOptions;
public YamlConfig(final Class<? extends T> clazz, final T dfl) throws Exception {
this(clazz, dfl, new TypeAdapterRegistry());
}
public YamlConfig(final Class<? extends T> clazz, final T dfl, final TypeAdapterRegistry registry) throws Exception {
this.clazz = clazz;
this.config = dfl;
this.typeAdapters = registry;
this.typeAdapters.makeAdapter(clazz);
final LoaderOptions loaderOptions = new LoaderOptions();
loaderOptions.setProcessComments(true);
final DumperOptions dumperOptions = new DumperOptions();
dumperOptions.setProcessComments(true);
dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
this.loaderOptions = loaderOptions;
this.dumperOptions = dumperOptions;
this.yaml = new Yaml(new YamlConstructor(loaderOptions), new YamlRepresenter(dumperOptions), dumperOptions, loaderOptions);
}
public void load(final File file) throws IOException {
try (final InputStream is = new BufferedInputStream(new FileInputStream(file))) {
this.load(is);
}
}
public void load(final InputStream is) throws IOException {
final Object serialized = this.yaml.load(new InputStreamReader(is, StandardCharsets.UTF_8));
this.config = (T)this.typeAdapters.deserialize(serialized, this.clazz);
}
public void save(final File file) throws IOException {
this.save(file, "");
}
public void save(final File file, final String header) throws IOException {
if (file.isDirectory()) {
throw new IOException("File is a directory");
}
final File parent = file.getParentFile();
if (parent != null) {
parent.mkdirs();
}
final File tmp = new File(parent, file.getName() + ".tmp");
tmp.delete();
tmp.createNewFile();
try {
try (final OutputStream os = new BufferedOutputStream(new FileOutputStream(tmp))) {
this.save(os, header);
}
try {
Files.move(tmp.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} catch (final AtomicMoveNotSupportedException ex) {
Files.move(tmp.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
} finally {
tmp.delete();
}
}
public void save(final OutputStream os) throws IOException {
os.write(this.saveToString().getBytes(StandardCharsets.UTF_8));
}
public void save(final OutputStream os, final String header) throws IOException {
os.write(this.saveToString(header).getBytes(StandardCharsets.UTF_8));
}
public String saveToString() {
return this.yaml.dump(this.typeAdapters.serialize(this.config, this.clazz));
}
public String saveToString(final String header) {
if (header.isBlank()) {
return this.saveToString();
}
final StringBuilder ret = new StringBuilder();
final String lineBreak = this.dumperOptions.getLineBreak().getString();
for (final String line : header.split("\n")) {
ret.append("# ").append(line.trim()).append(lineBreak);
}
ret.append(lineBreak);
return ret.append(this.saveToString()).toString();
}
public void callInitialisers() {
this.typeAdapters.callInitialisers(this.config);
}
private static final class YamlConstructor extends Constructor {
public YamlConstructor(final LoaderOptions loadingConfig) {
super(loadingConfig);
}
}
private static final class YamlRepresenter extends Representer {
public YamlRepresenter(final DumperOptions options) {
super(options);
this.representers.put(TypeAdapterRegistry.CommentedData.class, new CommentedDataRepresenter());
}
private final class CommentedDataRepresenter implements Represent {
@Override
public Node representData(final Object data0) {
final TypeAdapterRegistry.CommentedData commentedData = (TypeAdapterRegistry.CommentedData)data0;
final Node node = YamlRepresenter.this.representData(commentedData.data);
final List<CommentLine> comments = new ArrayList<>();
for (final String line : commentedData.comment.split("\n")) {
comments.add(new CommentLine(null, null, " ".concat(line.trim()), CommentType.BLOCK));
}
node.setBlockComments(comments);
return node;
}
}
}
}

View File

@@ -1,12 +1,12 @@
package ca.spottedleaf.moonrise.common.config.moonrise;
import ca.spottedleaf.moonrise.common.config.InitialiseHook;
import ca.spottedleaf.moonrise.common.config.annotation.Adaptable;
import ca.spottedleaf.moonrise.common.config.ui.ClothConfig;
import ca.spottedleaf.moonrise.common.config.annotation.Serializable;
import ca.spottedleaf.moonrise.common.config.type.Duration;
import ca.spottedleaf.moonrise.common.util.MoonriseCommon;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler;
import ca.spottedleaf.yamlconfig.InitialiseHook;
import ca.spottedleaf.yamlconfig.annotation.Adaptable;
import ca.spottedleaf.yamlconfig.annotation.Serializable;
import ca.spottedleaf.yamlconfig.type.Duration;
@Adaptable
public final class MoonriseConfig {
@@ -251,4 +251,21 @@ public final class MoonriseConfig {
)
public boolean fixMC159283 = false;
}
@Serializable
public Misc misc = new Misc();
@Adaptable
public static final class Misc {
@Serializable(
serializedKey = "immediately-close-loading-screen",
comment = """
Whether the loading screen should be closed immediately when joining servers/SP worlds.
This will let you in game faster, but may result in getting in game before enough chunks are
loaded for rendering.
"""
)
public boolean immediatelyCloseLoadingScreen = false;
}
}

View File

@@ -1,38 +0,0 @@
package ca.spottedleaf.moonrise.common.config.moonrise.adapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapter;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import ca.spottedleaf.moonrise.common.config.moonrise.type.DefaultedValue;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public final class DefaultedTypeAdapter extends TypeAdapter<DefaultedValue<?>, Object> {
private static final String DEFAULT_STRING = "default";
@Override
public DefaultedValue<?> deserialize(final TypeAdapterRegistry registry, final Object input, final Type type) {
if (input instanceof String string && string.equalsIgnoreCase(DEFAULT_STRING)) {
return new DefaultedValue<>();
}
if (!(type instanceof ParameterizedType parameterizedType)) {
throw new IllegalArgumentException("DefaultedValue field must specify generic type");
}
final Type valueType = parameterizedType.getActualTypeArguments()[0];
return new DefaultedValue<>(registry.deserialize(input, valueType));
}
@Override
public Object serialize(final TypeAdapterRegistry registry, final DefaultedValue<?> value, final Type type) {
final Object raw = value.getValueRaw();
if (raw == null) {
return DEFAULT_STRING;
}
final Type valueType = type instanceof ParameterizedType parameterizedType ? parameterizedType.getActualTypeArguments()[0] : null;
return registry.serialize(raw, valueType);
}
}

View File

@@ -1,22 +0,0 @@
package ca.spottedleaf.moonrise.common.config.moonrise.type;
public final class DefaultedValue<T> {
private final T value;
public DefaultedValue() {
this(null);
}
public DefaultedValue(final T value) {
this.value = value;
}
public T getValueRaw() {
return value;
}
public T getOrDefault(final T dfl) {
return this.value != null ? this.value : dfl;
}
}

View File

@@ -1,76 +0,0 @@
package ca.spottedleaf.moonrise.common.config.type;
import java.math.BigDecimal;
public final class Duration {
private final String string;
private final long timeNS;
private Duration(final String string, final long timeNS) {
this.string = string;
this.timeNS = timeNS;
}
public static Duration parse(final String value) {
if (value.length() < 2) {
throw new IllegalArgumentException("Invalid duration: " + value);
}
final char last = value.charAt(value.length() - 1);
final long multiplier;
switch (last) {
case 's': {
multiplier = (1000L * 1000L * 1000L) * 1L;
break;
}
case 't': {
multiplier = (1000L * 1000L * 1000L) / 20L;
break;
}
case 'm': {
multiplier = (1000L * 1000L * 1000L) * 60L;
break;
}
case 'h': {
multiplier = (1000L * 1000L * 1000L) * 60L * 60L;
break;
}
case 'd': {
multiplier = (1000L * 1000L * 1000L) * 24L * 60L * 60L;
break;
}
default: {
throw new IllegalArgumentException("Duration must end with one of: [s, t, m, h, d]");
}
}
final BigDecimal parsed = new BigDecimal(value.substring(0, value.length() - 1))
.multiply(new BigDecimal(multiplier));
return new Duration(value, parsed.toBigInteger().longValueExact());
}
public long getTimeNS() {
return this.timeNS;
}
public long getTimeMS() {
return this.timeNS / (1000L * 1000L);
}
public long getTimeS() {
return this.timeNS / (1000L * 1000L * 1000L);
}
public long getTimeTicks() {
return this.timeNS / ((1000L * 1000L * 1000L) / (20L));
}
@Override
public String toString() {
return this.string;
}
}

View File

@@ -122,7 +122,7 @@ public final class NearbyPlayers {
players[NearbyMapType.GENERAL_SMALL.ordinal()].update(chunk.x, chunk.z, GENERAL_SMALL_VIEW_DISTANCE);
players[NearbyMapType.GENERAL_REALLY_SMALL.ordinal()].update(chunk.x, chunk.z, GENERAL_REALLY_SMALL_VIEW_DISTANCE);
players[NearbyMapType.TICK_VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getTickViewDistance(player));
players[NearbyMapType.VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getLoadViewDistance(player));
players[NearbyMapType.VIEW_DISTANCE.ordinal()].update(chunk.x, chunk.z, ChunkSystem.getViewDistance(player));
players[NearbyMapType.SPAWN_RANGE.ordinal()].update(chunk.x, chunk.z, ChunkTickConstants.PLAYER_SPAWN_TRACK_RANGE); // Moonrise - chunk tick iteration
}

View File

@@ -2,7 +2,6 @@ package ca.spottedleaf.moonrise.common.misc;
import ca.spottedleaf.concurrentutil.util.IntPairUtil;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.objects.ReferenceSet;
@@ -15,10 +14,6 @@ public final class PositionCountingAreaMap<T> {
return this.counters.keySet();
}
public LongSet getPositions() {
return this.positions.keySet();
}
public int getTotalPositions() {
return this.positions.size();
}

View File

@@ -118,7 +118,7 @@ public final class ChunkSystem {
((ChunkSystemLevelChunk)chunk).moonrise$getChunkAndHolder()
);
if (!((ChunkSystemLevelChunk)chunk).moonrise$isPostProcessingDone()) {
chunk.postProcessGeneration((ServerLevel)chunk.getLevel());
chunk.postProcessGeneration();
}
((ServerLevel)chunk.getLevel()).startTickingChunk(chunk);
((ServerLevel)chunk.getLevel()).getChunkSource().chunkMap.tickingGenerated.incrementAndGet();
@@ -152,8 +152,8 @@ public final class ChunkSystem {
return RegionizedPlayerChunkLoader.getAPISendViewDistance(player);
}
public static int getLoadViewDistance(final ServerPlayer player) {
return RegionizedPlayerChunkLoader.getLoadViewDistance(player);
public static int getViewDistance(final ServerPlayer player) {
return RegionizedPlayerChunkLoader.getAPIViewDistance(player);
}
public static int getTickViewDistance(final ServerPlayer player) {

View File

@@ -1,11 +1,8 @@
package ca.spottedleaf.moonrise.common.util;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.common.config.adapter.TypeAdapterRegistry;
import ca.spottedleaf.moonrise.common.config.config.YamlConfig;
import ca.spottedleaf.moonrise.common.config.moonrise.MoonriseConfig;
import ca.spottedleaf.moonrise.common.config.moonrise.adapter.DefaultedTypeAdapter;
import ca.spottedleaf.moonrise.common.config.moonrise.type.DefaultedValue;
import ca.spottedleaf.yamlconfig.adapter.TypeAdapterRegistry;
import ca.spottedleaf.yamlconfig.config.YamlConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
@@ -14,19 +11,17 @@ public final class ConfigHolder {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigHolder.class);
private static final File CONFIG_FILE = new File(System.getProperty(PlatformHooks.get().getBrand() + ".ConfigFile", "config/moonrise.yml"));
private static final File CONFIG_FILE = new File(System.getProperty("Moonrise.ConfigFile", "config/moonrise.yml"));
private static final TypeAdapterRegistry CONFIG_ADAPTERS = new TypeAdapterRegistry();
private static final YamlConfig<MoonriseConfig> CONFIG;
static {
CONFIG_ADAPTERS.putAdapter(DefaultedValue.class, new DefaultedTypeAdapter());
try {
CONFIG = new YamlConfig<>(MoonriseConfig.class, new MoonriseConfig(), CONFIG_ADAPTERS);
} catch (final Exception ex) {
throw new RuntimeException(ex);
}
}
private static final String CONFIG_HEADER = String.format("""
private static final String CONFIG_HEADER = """
This is the configuration file for Moonrise.
Each configuration option is prefixed with a comment to explain what it does. Additional changes to this file
@@ -34,10 +29,10 @@ public final class ConfigHolder {
Below are the Moonrise startup flags. Note that startup flags must be placed in the JVM arguments, not
program arguments.
-D%1$s.ConfigFile=<file> - Override the config file location. Might be useful for multiple game versions.
-D%1$s.WorkerThreadCount=<number> - Override the auto configured worker thread counts (worker-threads).
-D%1$s.MaxViewDistance=<number> - Overrides the maximum view distance, should only use for debugging purposes.
""", PlatformHooks.get().getBrand());
-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 {
reloadConfig();
@@ -52,14 +47,6 @@ public final class ConfigHolder {
}
public static boolean reloadConfig() {
final boolean ret = reloadConfig0();
CONFIG.callInitialisers();
return ret;
}
private static boolean reloadConfig0() {
synchronized (CONFIG) {
if (CONFIG_FILE.exists()) {
try {
@@ -70,6 +57,8 @@ public final class ConfigHolder {
}
}
CONFIG.callInitialisers();
// write back any changes, or create if needed
return saveConfig();
}

View File

@@ -3,12 +3,8 @@ package ca.spottedleaf.moonrise.common.util;
public final class MixinWorkarounds {
// mixins tries to find the owner of the clone() method, which doesn't exist and NPEs
// https://github.com/FabricMC/Mixin/pull/147
public static long[] clone(final long[] values) {
return values.clone();
}
public static byte[] clone(final byte[] values) {
return values.clone();
}
}

View File

@@ -19,7 +19,7 @@ public final class MoonriseCommon {
@Override
public void accept(Thread thread) {
thread.setDaemon(true);
thread.setName(PlatformHooks.get().getBrand() + " Common Worker #" + this.idGenerator.getAndIncrement());
thread.setName("Moonrise Common Worker #" + this.idGenerator.getAndIncrement());
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(final Thread thread, final Throwable throwable) {
@@ -44,7 +44,7 @@ public final class MoonriseCommon {
} else {
defaultWorkerThreads = defaultWorkerThreads / 2;
}
defaultWorkerThreads = Integer.getInteger(PlatformHooks.get().getBrand() + ".WorkerThreadCount", Integer.valueOf(defaultWorkerThreads));
defaultWorkerThreads = Integer.getInteger("Moonrise.WorkerThreadCount", Integer.valueOf(defaultWorkerThreads));
int workerThreads = configWorkerThreads;

View File

@@ -1,10 +1,8 @@
package ca.spottedleaf.moonrise.common.util;
import ca.spottedleaf.moonrise.common.PlatformHooks;
public final class MoonriseConstants {
public static final int MAX_VIEW_DISTANCE = Integer.getInteger(PlatformHooks.get().getBrand() + ".MaxViewDistance", 32);
public static final int MAX_VIEW_DISTANCE = Integer.getInteger("Moonrise.MaxViewDistance", 32);
private MoonriseConstants() {}

View File

@@ -39,6 +39,7 @@ public final class SimpleRandom extends LegacyRandomSource {
return (int)(seed >>> (BITS - Integer.SIZE));
}
@Override
public int nextInt(final int bound) {
if (bound <= 0) {

View File

@@ -8,19 +8,19 @@ public final class WorldUtil {
// min, max are inclusive
public static int getMaxSection(final LevelHeightAccessor world) {
return world.getMaxSectionY();
return world.getMaxSection() - 1; // getMaxSection() is exclusive
}
public static int getMaxSection(final Level world) {
return world.getMaxSectionY();
return world.getMaxSection() - 1; // getMaxSection() is exclusive
}
public static int getMinSection(final LevelHeightAccessor world) {
return world.getMinSectionY();
return world.getMinSection();
}
public static int getMinSection(final Level world) {
return world.getMinSectionY();
return world.getMinSection();
}
public static int getMaxLightSection(final LevelHeightAccessor world) {

View File

@@ -154,9 +154,9 @@ abstract class LevelChunkSectionMixin implements BlockCountingChunkSection {
if (this.maybeHas((final BlockState state) -> !state.isAir())) {
final PalettedContainer.Data<BlockState> data = this.states.data;
final Palette<BlockState> palette = data.palette();
final Palette<BlockState> palette = data.palette;
final int paletteSize = palette.getSize();
final BitStorage storage = data.storage();
final BitStorage storage = data.storage;
final Int2ObjectOpenHashMap<ShortArrayList> counts;
if (paletteSize == 1) {

View File

@@ -6,7 +6,6 @@ import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.Property;
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.Constant;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -18,9 +17,6 @@ abstract class BooleanPropertyMixin extends Property<Boolean> implements Propert
super(string, class_);
}
@Unique
private static final Boolean[] BY_ID = new Boolean[]{ Boolean.FALSE, Boolean.TRUE };
@Override
public final int moonrise$getIdFor(final Boolean value) {
return value.booleanValue() ? 1 : 0;
@@ -37,6 +33,23 @@ abstract class BooleanPropertyMixin extends Property<Boolean> implements Propert
)
)
private void init(final CallbackInfo ci) {
this.moonrise$setById(BY_ID);
this.moonrise$setById(new Boolean[]{ Boolean.FALSE, Boolean.TRUE });
}
/**
* This skips all ops after the identity comparison in the original code.
*
* @reason Properties are identity comparable
* @author Spottedleaf
*/
@WrapOperation(
method = "equals",
constant = @Constant(
classValue = BooleanProperty.class,
ordinal = 0
)
)
private boolean skipFurtherComparison(final Object obj, final Operation<Boolean> orig) {
return false;
}
}

View File

@@ -18,6 +18,9 @@ import java.util.Collection;
@Mixin(EnumProperty.class)
abstract class EnumPropertyMixin<T extends Enum<T> & StringRepresentable> extends Property<T> implements PropertyAccess<T> {
@Shadow
public abstract Collection<T> getPossibleValues();
@Unique
private int[] idLookupTable;

View File

@@ -83,6 +83,7 @@ abstract class StateHolderMixin<O, S> implements PropertyAccessStateHolder {
}
// remove values arrays
this.values = null;
for (final Map.Entry<Map<Property<?>, Comparable<?>>, S> entry : map.entrySet()) {
final S value = entry.getValue();
((StateHolderMixin<O, S>)(Object)(StateHolder<O, S>)value).values = null;
@@ -125,8 +126,8 @@ abstract class StateHolderMixin<O, S> implements PropertyAccessStateHolder {
* @author Spottedleaf
*/
@Overwrite
public <T extends Comparable<T>> T getNullableValue(Property<T> property) {
return property == null ? null : this.optimisedTable.get(this.tableIndex, property);
public <T extends Comparable<T>> Optional<T> getOptionalValue(final Property<T> property) {
return property == null ? Optional.empty() : Optional.ofNullable(this.optimisedTable.get(this.tableIndex, property));
}
/**

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;
}
/**
@@ -306,7 +326,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;
}
/**

View File

@@ -11,17 +11,14 @@ import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.NewChunkHolder;
import com.mojang.datafixers.DataFixer;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntConsumer;
import java.util.function.Supplier;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.StreamTagVisitor;
import net.minecraft.server.level.ChunkGenerationTask;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ChunkResult;
import net.minecraft.server.level.ChunkTaskDispatcher;
import net.minecraft.server.level.ChunkTaskPriorityQueueSorter;
import net.minecraft.server.level.ChunkTrackingView;
import net.minecraft.server.level.GeneratingChunkMap;
import net.minecraft.server.level.GenerationChunkHolder;
@@ -31,6 +28,7 @@ import net.minecraft.server.level.progress.ChunkProgressListener;
import net.minecraft.util.Mth;
import net.minecraft.util.StaticCache2D;
import net.minecraft.util.thread.BlockableEventLoop;
import net.minecraft.util.thread.ProcessorHandle;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
@@ -80,10 +78,13 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
private volatile Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunkMap;
@Shadow
private ChunkTaskDispatcher worldgenTaskDispatcher;
private ChunkTaskPriorityQueueSorter queueSorter;
@Shadow
private ChunkTaskDispatcher lightTaskDispatcher;
private ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> worldgenMailbox;
@Shadow
private ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> mainThreadMailbox;
@Shadow
private int serverViewDistance;
@@ -97,12 +98,6 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
@Shadow
private Queue<Runnable> unloadQueue;
@Shadow
private LongSet chunksToEagerlySave;
@Shadow
private AtomicInteger activeChunkWrites;
public ChunkMapMixin(RegionStorageInfo regionStorageInfo, Path path, DataFixer dataFixer, boolean bl) {
super(regionStorageInfo, path, dataFixer, bl);
}
@@ -133,12 +128,11 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
this.updatingChunkMap = null;
this.visibleChunkMap = null;
this.pendingUnloads = null;
this.worldgenTaskDispatcher = null;
this.lightTaskDispatcher = null;
this.queueSorter = null;
this.worldgenMailbox = null;
this.mainThreadMailbox = null;
this.pendingGenerationTasks = null;
this.unloadQueue = null;
this.chunksToEagerlySave = null;
this.activeChunkWrites = null;
// Dummy impl for mods that try to loadAsync directly
this.worker = new IOWorker(
@@ -158,7 +152,7 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
@Override
public CompletableFuture<Optional<CompoundTag>> loadAsync(final ChunkPos chunkPos) {
final CompletableFuture<Optional<CompoundTag>> future = new CompletableFuture<>();
MoonriseRegionFileIO.loadDataAsync(ChunkMapMixin.this.level, chunkPos.x, chunkPos.z, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, (final CompoundTag tag, final Throwable throwable) -> {
MoonriseRegionFileIO.loadDataAsync(ChunkMapMixin.this.level, chunkPos.x, chunkPos.z, MoonriseRegionFileIO.RegionFileType.CHUNK_DATA, (tag, throwable) -> {
if (throwable != null) {
future.completeExceptionally(throwable);
} else {
@@ -190,15 +184,6 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
};
}
/**
* @reason This map is not needed, we maintain our own ordered set of chunks to autosave.
* @author Spottedleaf
*/
@Overwrite
public void setChunkUnsaved(final ChunkPos pos) {
}
/**
* @reason Route to new chunk system hooks
* @author Spottedleaf
@@ -276,15 +261,6 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
throw new UnsupportedOperationException();
}
/**
* @reason Destroy old chunk system hooks
* @author Spottedleaf
*/
@Overwrite
public void onLevelChange(final ChunkPos chunkPos, final IntSupplier intSupplier, final int i, final IntConsumer intConsumer) {
throw new UnsupportedOperationException();
}
/**
* @reason Destroy old chunk system hooks
* @author Spottedleaf
@@ -333,15 +309,6 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.autoSave();
}
/**
* @reason Destroy old chunk system hooks
* @author Spottedleaf
*/
@Overwrite
public void saveChunksEagerly(final BooleanSupplier hasTime) {
throw new UnsupportedOperationException();
}
/**
* @reason Destroy old chunk system hooks
* @author Spottedleaf
@@ -436,7 +403,7 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
* @author Spottedleaf
*/
@Overwrite
public void onChunkReadyToSend(final ChunkHolder holder, final LevelChunk chunk) {
public void onChunkReadyToSend(final LevelChunk chunk) {
throw new UnsupportedOperationException();
}
@@ -455,7 +422,7 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
* @see NewChunkHolder#save(boolean)
*/
@Overwrite
public boolean saveChunkIfNeeded(final ChunkHolder chunkHolder, final long time) {
public boolean saveChunkIfNeeded(final ChunkHolder chunkHolder) {
throw new UnsupportedOperationException();
}
@@ -538,21 +505,6 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
throw new UnsupportedOperationException();
}
/**
* @reason Route to new chunk system
* @author Spottedleaf
*/
@Redirect(
method = "forEachSpawnCandidateChunk",
at = @At(
value = "INVOKE",
target = "Lit/unimi/dsi/fastutil/longs/Long2ObjectLinkedOpenHashMap;get(J)Ljava/lang/Object;"
)
)
private <V> V redirectChunkHolderGet(final Long2ObjectLinkedOpenHashMap<V> instance, final long key) {
return (V)this.getVisibleChunkIfPresent(key);
}
@Override
public CompletableFuture<Optional<CompoundTag>> read(final ChunkPos pos) {
final CompletableFuture<Optional<CompoundTag>> ret = new CompletableFuture<>();
@@ -572,11 +524,10 @@ abstract class ChunkMapMixin extends ChunkStorage implements ChunkSystemChunkMap
}
@Override
public CompletableFuture<Void> write(final ChunkPos pos, final Supplier<CompoundTag> tag) {
public CompletableFuture<Void> write(final ChunkPos pos, final CompoundTag tag) {
MoonriseRegionFileIO.scheduleSave(
this.level, pos.x, pos.z, tag.get(),
MoonriseRegionFileIO.RegionFileType.CHUNK_DATA
);
this.level, pos.x, pos.z, tag,
MoonriseRegionFileIO.RegionFileType.CHUNK_DATA);
return null;
}

View File

@@ -3,13 +3,13 @@ package ca.spottedleaf.moonrise.mixin.chunk_system;
import net.minecraft.core.SectionPos;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.storage.SerializableChunkData;
import net.minecraft.world.level.chunk.storage.ChunkSerializer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(SerializableChunkData.class)
abstract class SerializableChunkDataMixin {
@Mixin(ChunkSerializer.class)
abstract class ChunkSerializerMixin {
/**
* @reason Chunk system handles this during full transition
@@ -17,12 +17,12 @@ abstract class SerializableChunkDataMixin {
* @see ca.spottedleaf.moonrise.patches.chunk_system.scheduling.task.ChunkFullTask
*/
@Redirect(
method = "read",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/ai/village/poi/PoiManager;checkConsistencyWithBlocks(Lnet/minecraft/core/SectionPos;Lnet/minecraft/world/level/chunk/LevelChunkSection;)V"
)
method = "read",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/ai/village/poi/PoiManager;checkConsistencyWithBlocks(Lnet/minecraft/core/SectionPos;Lnet/minecraft/world/level/chunk/LevelChunkSection;)V"
)
)
private void skipConsistencyCheck(final PoiManager instance, final SectionPos sectionPos, final LevelChunkSection levelChunkSection) {}
private static void skipConsistencyCheck(PoiManager instance, SectionPos sectionPos, LevelChunkSection levelChunkSection) {}
}

View File

@@ -22,13 +22,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
@Mixin(ChunkStorage.class)
abstract class ChunkStorageMixin implements ChunkSystemChunkStorage, AutoCloseable {
@Shadow
public IOWorker worker;
private IOWorker worker;
@Unique
private static final Logger LOGGER = LogUtils.getLogger();
@@ -119,13 +118,13 @@ abstract class ChunkStorageMixin implements ChunkSystemChunkStorage, AutoCloseab
method = "write",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/chunk/storage/IOWorker;store(Lnet/minecraft/world/level/ChunkPos;Ljava/util/function/Supplier;)Ljava/util/concurrent/CompletableFuture;"
target = "Lnet/minecraft/world/level/chunk/storage/IOWorker;store(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/nbt/CompoundTag;)Ljava/util/concurrent/CompletableFuture;"
)
)
private CompletableFuture<Void> redirectWrite(final IOWorker instance, final ChunkPos chunkPos,
final Supplier<CompoundTag> compoundTag) {
final CompoundTag compoundTag) {
try {
this.storage.write(chunkPos, compoundTag.get());
this.storage.write(chunkPos, compoundTag);
return CompletableFuture.completedFuture(null);
} catch (final Throwable throwable) {
return CompletableFuture.failedFuture(throwable);

View File

@@ -41,8 +41,8 @@ abstract class ClientLevelMixin extends Level implements ChunkSystemLevel {
@Final
private ClientChunkCache chunkSource;
protected ClientLevelMixin(final WritableLevelData writableLevelData, final ResourceKey<Level> resourceKey, final RegistryAccess registryAccess, final Holder<DimensionType> holder, final boolean bl, final boolean bl2, final long l, final int i) {
super(writableLevelData, resourceKey, registryAccess, holder, bl, bl2, l, i);
protected ClientLevelMixin(WritableLevelData writableLevelData, ResourceKey<Level> resourceKey, RegistryAccess registryAccess, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l, int i) {
super(writableLevelData, resourceKey, registryAccess, holder, supplier, bl, bl2, l, i);
}
/**
@@ -55,8 +55,9 @@ abstract class ClientLevelMixin extends Level implements ChunkSystemLevel {
value = "RETURN"
)
)
private void init(ClientPacketListener clientPacketListener, ClientLevel.ClientLevelData clientLevelData, ResourceKey<Level> resourceKey,
Holder<DimensionType> holder, int i, int j, LevelRenderer levelRenderer, boolean bl, long l, int k, CallbackInfo ci) {
private void init(ClientPacketListener clientPacketListener, ClientLevel.ClientLevelData clientLevelData,
ResourceKey<Level> resourceKey, Holder<DimensionType> holder, int i, int j, Supplier<ProfilerFiller> supplier,
LevelRenderer levelRenderer, boolean bl, long l, CallbackInfo ci) {
this.entityStorage = null;
this.moonrise$setEntityLookup(new ClientEntityLookup(this, ((ClientLevel)(Object)this).new EntityCallbacks()));

View File

@@ -1,5 +1,6 @@
package ca.spottedleaf.moonrise.mixin.chunk_system;
import ca.spottedleaf.moonrise.common.util.MoonriseConstants;
import ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel;
import ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemDistanceManager;
import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager;
@@ -8,12 +9,14 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.server.level.ChunkHolder;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ChunkTaskPriorityQueueSorter;
import net.minecraft.server.level.DistanceManager;
import net.minecraft.server.level.ThrottlingChunkTaskDispatcher;
import net.minecraft.server.level.Ticket;
import net.minecraft.server.level.TicketType;
import net.minecraft.server.level.TickingTracker;
import net.minecraft.util.Mth;
import net.minecraft.util.SortedArraySet;
import net.minecraft.util.thread.ProcessorHandle;
import net.minecraft.world.level.ChunkPos;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
@@ -44,7 +47,13 @@ abstract class DistanceManagerMixin implements ChunkSystemDistanceManager {
Set<ChunkHolder> chunksToUpdateFutures;
@Shadow
ThrottlingChunkTaskDispatcher ticketDispatcher;
ChunkTaskPriorityQueueSorter ticketThrottler;
@Shadow
ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> ticketThrottlerInput;
@Shadow
ProcessorHandle<ChunkTaskPriorityQueueSorter.Release> ticketThrottlerReleaser;
@Shadow
LongSet ticketsToRelease;
@@ -53,8 +62,10 @@ abstract class DistanceManagerMixin implements ChunkSystemDistanceManager {
Executor mainThreadExecutor;
@Shadow
private int simulationDistance;
private DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter;
@Shadow
private int simulationDistance;
@Override
public ChunkMap moonrise$getChunkMap() {
@@ -77,14 +88,16 @@ abstract class DistanceManagerMixin implements ChunkSystemDistanceManager {
this.tickingTicketsTracker = null;
this.playerTicketManager = null;
this.chunksToUpdateFutures = null;
this.ticketDispatcher = null;
this.ticketThrottler = null;
this.ticketThrottlerInput = null;
this.ticketThrottlerReleaser = null;
this.ticketsToRelease = null;
this.mainThreadExecutor = null;
this.simulationDistance = -1;
}
@Override
public final ChunkHolderManager moonrise$getChunkHolderManager() {
public ChunkHolderManager moonrise$getChunkHolderManager() {
return ((ChunkSystemServerLevel)this.moonrise$getChunkMap().level).moonrise$getChunkTaskScheduler().chunkHolderManager;
}
@@ -285,7 +298,10 @@ abstract class DistanceManagerMixin implements ChunkSystemDistanceManager {
*/
@Overwrite
public void updateSimulationDistance(final int simulationDistance) {
((ChunkSystemServerLevel)this.moonrise$getChunkMap().level).moonrise$getPlayerChunkLoader().setTickDistance(simulationDistance);
// note: vanilla does not clamp to 0, but we do simply because we need a min of 0
final int clamped = Mth.clamp(simulationDistance, 0, MoonriseConstants.MAX_VIEW_DISTANCE);
((ChunkSystemServerLevel)this.moonrise$getChunkMap().level).moonrise$getPlayerChunkLoader().setTickDistance(clamped);
}
/**
@@ -315,15 +331,6 @@ abstract class DistanceManagerMixin implements ChunkSystemDistanceManager {
throw new UnsupportedOperationException();
}
/**
* @reason Remove old chunk system hooks
* @author Spottedleaf
*/
@Overwrite
public LongSet getTickingChunks() {
throw new UnsupportedOperationException();
}
/**
* @reason This hack is not required anymore, see {@link MinecraftServerMixin}
* @author Spottedleaf

View File

@@ -0,0 +1,25 @@
package ca.spottedleaf.moonrise.mixin.chunk_system;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.gameevent.DynamicGameEventListener;
import org.jetbrains.annotations.Nullable;
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;
@Mixin(DynamicGameEventListener.class)
abstract class DynamicGameEventListenerMixin {
@Shadow @Nullable private SectionPos lastSection;
@Inject(method = "remove", at = @At("RETURN"))
private void onRemove(final CallbackInfo ci) {
// We need to unset the last section when removed, otherwise if the same instance is re-added at the same position it
// will assume there was no change and fail to re-register.
// In vanilla, chunks rarely unload and re-load quickly enough to trigger this issue. However, our chunk system has a
// quirk where fast chunk reload cycles will often occur on player login (see PR #22).
// So we fix this vanilla oversight as our changes cause it to manifest in bugs much more often (see issue #87).
this.lastSection = null;
}
}

View File

@@ -195,6 +195,15 @@ abstract class GenerationChunkHolderMixin {
throw new UnsupportedOperationException();
}
/**
* @reason Chunk system is not built on futures anymore
* @author Spottedleaf
*/
@Overwrite
public int getGenerationRefCount() {
throw new UnsupportedOperationException();
}
/**
* @reason Route to new chunk holder
* @author Spottedleaf

View File

@@ -93,15 +93,11 @@ abstract class LevelChunkMixin extends ChunkAccess implements ChunkSystemLevelCh
}
@Override
public boolean tryMarkSaved() {
if (!this.isUnsaved()) {
return false;
public void setUnsaved(final boolean needsSaving) {
if (!needsSaving) {
((ChunkSystemLevelChunkTicks)this.blockTicks).moonrise$clearDirty();
((ChunkSystemLevelChunkTicks)this.fluidTicks).moonrise$clearDirty();
}
((ChunkSystemLevelChunkTicks)this.blockTicks).moonrise$clearDirty();
((ChunkSystemLevelChunkTicks)this.fluidTicks).moonrise$clearDirty();
super.tryMarkSaved();
return true;
super.setUnsaved(needsSaving);
}
}

View File

@@ -10,7 +10,6 @@ import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl.DefaultEnti
import ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.FullChunkStatus;
import net.minecraft.util.profiling.Profiler;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
@@ -38,6 +37,9 @@ import java.util.function.Predicate;
@Mixin(Level.class)
abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityGetter, LevelAccessor, AutoCloseable {
@Shadow
public abstract ProfilerFiller getProfiler();
@Shadow
public abstract LevelChunk getChunk(int i, int j);
@@ -57,7 +59,7 @@ abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityGetter,
}
@Override
public final void moonrise$setEntityLookup(final EntityLookup entityLookup) {
public void moonrise$setEntityLookup(final EntityLookup entityLookup) {
if (this.entityLookup != null && !(this.entityLookup instanceof DefaultEntityLookup)) {
throw new IllegalStateException("Entity lookup already initialised");
}
@@ -85,7 +87,7 @@ abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityGetter,
@Overwrite
@Override
public List<Entity> getEntities(final Entity entity, final AABB boundingBox, final Predicate<? super Entity> predicate) {
Profiler.get().incrementCounter("getEntities");
this.getProfiler().incrementCounter("getEntities");
final List<Entity> ret = new ArrayList<>();
((ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(entity, boundingBox, ret, predicate);
@@ -103,7 +105,7 @@ abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityGetter,
public <T extends Entity> void getEntities(final EntityTypeTest<Entity, T> entityTypeTest,
final AABB boundingBox, final Predicate<? super T> predicate,
final List<? super T> into, final int maxCount) {
Profiler.get().incrementCounter("getEntities");
this.getProfiler().incrementCounter("getEntities");
if (entityTypeTest instanceof EntityType<T> byType) {
if (maxCount != Integer.MAX_VALUE) {
@@ -176,7 +178,7 @@ abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityGetter,
*/
@Override
public final <T extends Entity> List<T> getEntitiesOfClass(final Class<T> entityClass, final AABB boundingBox, final Predicate<? super T> predicate) {
Profiler.get().incrementCounter("getEntities");
this.getProfiler().incrementCounter("getEntities");
final List<T> ret = new ArrayList<>();
((ChunkSystemLevel)this).moonrise$getEntityLookup().getEntities(entityClass, null, boundingBox, ret, predicate);
@@ -194,7 +196,7 @@ abstract class LevelMixin implements ChunkSystemLevel, ChunkSystemEntityGetter,
*/
@Override
public final List<Entity> moonrise$getHardCollidingEntities(final Entity entity, final AABB box, final Predicate<? super Entity> predicate) {
Profiler.get().incrementCounter("getEntities");
this.getProfiler().incrementCounter("getEntities");
final List<Entity> ret = new ArrayList<>();
((ChunkSystemLevel)this).moonrise$getEntityLookup().getHardCollidingEntities(entity, box, ret, predicate);

View File

@@ -240,7 +240,6 @@ abstract class MinecraftServerMixin extends ReentrantBlockableEventLoop<TickTask
private void closeIOThreads(final CallbackInfo ci) {
LOGGER.info("Waiting for I/O tasks to complete...");
MoonriseRegionFileIO.flush((MinecraftServer)(Object)this);
LOGGER.info("All I/O tasks to complete");
if ((Object)this instanceof DedicatedServer) {
MoonriseCommon.haltExecutors();
}

View File

@@ -43,4 +43,17 @@ abstract class PlayerListMixin {
)
private void doNotAdjustVD(final PlayerList instance, final Packet<?> packet) {}
/**
* @reason The RegionizedPlayerChunkLoader will handle the SD packet
* @author Spottedleaf
*/
@Redirect(
method = "setSimulationDistance",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/server/players/PlayerList;broadcastAll(Lnet/minecraft/network/protocol/Packet;)V"
)
)
private void doNotAdjustSD(final PlayerList instance, final Packet<?> packet) {}
}

View File

@@ -36,7 +36,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -45,14 +44,7 @@ import java.util.stream.Stream;
@Mixin(PoiManager.class)
// Declare the generic type as Object so that our Overrides match the method signature of the superclass
// Specifically, getOrCreate must return Object so that existing invokes do not route to the superclass
public abstract class PoiManagerMixin extends SectionStorage<Object, Object> implements ChunkSystemPoiManager {
public PoiManagerMixin(final SimpleRegionStorage simpleRegionStorage, final Codec<Object> codec, final Function<Object, Object> function,
final BiFunction<Object, Runnable, Object> biFunction, final Function<Runnable, Object> function2,
final RegistryAccess registryAccess, final ChunkIOErrorReporter chunkIOErrorReporter,
final LevelHeightAccessor levelHeightAccessor) {
super(simpleRegionStorage, codec, function, biFunction, function2, registryAccess, chunkIOErrorReporter, levelHeightAccessor);
}
public abstract class PoiManagerMixin extends SectionStorage<Object> implements ChunkSystemPoiManager {
@Shadow
abstract boolean isVillageCenter(long l);
@@ -60,6 +52,10 @@ public abstract class PoiManagerMixin extends SectionStorage<Object, Object> imp
@Shadow
public abstract void checkConsistencyWithBlocks(SectionPos sectionPos, LevelChunkSection levelChunkSection);
public PoiManagerMixin(SimpleRegionStorage simpleRegionStorage, Function<Runnable, Codec<Object>> function, Function<Runnable, Object> function2, RegistryAccess registryAccess, ChunkIOErrorReporter chunkIOErrorReporter, LevelHeightAccessor levelHeightAccessor) {
super(simpleRegionStorage, function, function2, registryAccess, chunkIOErrorReporter, levelHeightAccessor);
}
@Unique
private ServerLevel world;
@@ -155,8 +151,8 @@ public abstract class PoiManagerMixin extends SectionStorage<Object, Object> imp
TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Accessing poi chunk off-main");
final PoiChunk ret = ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager
.getPoiChunkIfLoaded(chunkX, chunkZ, true);
final ChunkHolderManager manager = ((ChunkSystemServerLevel)this.world).moonrise$getChunkTaskScheduler().chunkHolderManager;
final PoiChunk ret = manager.getPoiChunkIfLoaded(chunkX, chunkZ, true);
return ret == null ? Optional.empty() : (Optional)ret.getSectionForVanilla(chunkY);
}
@@ -210,13 +206,9 @@ public abstract class PoiManagerMixin extends SectionStorage<Object, Object> imp
public final void moonrise$onUnload(final long coordinate) { // Paper - rewrite chunk system
final int chunkX = CoordinateUtils.getChunkX(coordinate);
final int chunkZ = CoordinateUtils.getChunkZ(coordinate);
final int minY = WorldUtil.getMinSection(this.world);
final int maxY = WorldUtil.getMaxSection(this.world);
TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Unloading poi chunk off-main");
for (int sectionY = minY; sectionY <= maxY; ++sectionY) {
final long sectionPos = SectionPos.asLong(chunkX, sectionY, chunkZ);
for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) {
final long sectionPos = SectionPos.asLong(chunkX, section, chunkZ);
this.updateDistanceTracking(sectionPos);
}
}
@@ -225,12 +217,8 @@ public abstract class PoiManagerMixin extends SectionStorage<Object, Object> imp
public final void moonrise$loadInPoiChunk(final PoiChunk poiChunk) {
final int chunkX = poiChunk.chunkX;
final int chunkZ = poiChunk.chunkZ;
final int minY = WorldUtil.getMinSection(this.world);
final int maxY = WorldUtil.getMaxSection(this.world);
TickThread.ensureTickThread(this.world, chunkX, chunkZ, "Loading poi chunk off-main");
for (int sectionY = minY; sectionY <= maxY; ++sectionY) {
for (int sectionY = this.levelHeightAccessor.getMinSection(); sectionY < this.levelHeightAccessor.getMaxSection(); ++sectionY) {
final PoiSection section = poiChunk.getSection(sectionY);
if (section != null && !((ChunkSystemPoiSection)section).moonrise$isEmpty()) {
this.onSectionLoad(SectionPos.asLong(chunkX, sectionY, chunkZ));
@@ -266,4 +254,20 @@ public abstract class PoiManagerMixin extends SectionStorage<Object, Object> imp
private <T> Stream<T> skipLoadedSet(final Stream<T> instance, final Predicate<? super T> predicate) {
return instance;
}
@Override
public final void moonrise$close() throws IOException {}
@Override
public final CompoundTag moonrise$read(final int chunkX, final int chunkZ) throws IOException {
return MoonriseRegionFileIO.loadData(
this.world, chunkX, chunkZ, MoonriseRegionFileIO.RegionFileType.POI_DATA,
MoonriseRegionFileIO.getIOBlockingPriorityForCurrentThread()
);
}
@Override
public final void moonrise$write(final int chunkX, final int chunkZ, final CompoundTag data) throws IOException {
MoonriseRegionFileIO.scheduleSave(this.world, chunkX, chunkZ, data, MoonriseRegionFileIO.RegionFileType.POI_DATA);
}
}

View File

@@ -30,7 +30,7 @@ abstract class PoiSectionMixin implements ChunkSystemPoiSection {
@Unique
private final Optional<PoiSection> noAllocOptional = Optional.of((PoiSection)(Object)this);
private final Optional<PoiSection> noAllocOptional = Optional.of((PoiSection)(Object)this);;
@Override
public final boolean moonrise$isEmpty() {

View File

@@ -2,6 +2,8 @@ package ca.spottedleaf.moonrise.mixin.chunk_system;
import ca.spottedleaf.moonrise.patches.chunk_system.level.storage.ChunkSystemSectionStorage;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.RegistryOps;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.storage.RegionFileStorage;
import net.minecraft.world.level.chunk.storage.SectionStorage;
@@ -21,11 +23,15 @@ import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Mixin(SectionStorage.class)
abstract class SectionStorageMixin<R, P> implements ChunkSystemSectionStorage, AutoCloseable {
abstract class SectionStorageMixin implements ChunkSystemSectionStorage, AutoCloseable {
@Shadow
private SimpleRegionStorage simpleRegionStorage;
@Shadow
@Final
private static Logger LOGGER;
@Unique
private RegionFileStorage storage;
@@ -35,9 +41,6 @@ abstract class SectionStorageMixin<R, P> implements ChunkSystemSectionStorage, A
return this.storage;
}
@Override
public void moonrise$close() throws IOException {}
/**
* @reason Retrieve storage from IOWorker, and then nuke it
* @author Spottedleaf
@@ -58,8 +61,12 @@ abstract class SectionStorageMixin<R, P> implements ChunkSystemSectionStorage, A
* @author Spottedleaf
*/
@Overwrite
public final CompletableFuture<Optional<SectionStorage.PackedChunk<P>>> tryRead(final ChunkPos pos) {
throw new IllegalStateException("Only chunk system can write state, offending class:" + this.getClass().getName());
public final CompletableFuture<Optional<CompoundTag>> tryRead(final ChunkPos pos) {
try {
return CompletableFuture.completedFuture(Optional.ofNullable(this.moonrise$read(pos.x, pos.z)));
} catch (final Throwable thr) {
return CompletableFuture.failedFuture(thr);
}
}
/**
@@ -67,17 +74,29 @@ abstract class SectionStorageMixin<R, P> implements ChunkSystemSectionStorage, A
* @author Spottedleaf
*/
@Overwrite
public void unpackChunk(final ChunkPos chunkPos, final SectionStorage.PackedChunk<P> packedChunk) {
public void readColumn(final ChunkPos pos, final RegistryOps<Tag> ops, final CompoundTag data) {
throw new IllegalStateException("Only chunk system can load in state, offending class:" + this.getClass().getName());
}
/**
* @reason Destroy old chunk system hook
* @reason Route to new chunk system hook
* @author Spottedleaf
*/
@Overwrite
private void writeChunk(final ChunkPos chunkPos) {
throw new IllegalStateException("Only chunk system can write state, offending class:" + this.getClass().getName());
@Redirect(
method = "writeColumn(Lnet/minecraft/world/level/ChunkPos;)V",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/chunk/storage/SimpleRegionStorage;write(Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/nbt/CompoundTag;)Ljava/util/concurrent/CompletableFuture;"
)
)
private CompletableFuture<Void> redirectWrite(final SimpleRegionStorage instance, final ChunkPos pos,
final CompoundTag tag) {
try {
this.moonrise$write(pos.x, pos.z, tag);
} catch (final IOException ex) {
LOGGER.error("Error writing poi chunk data to disk for chunk " + pos, ex);
}
return null;
}
/**
@@ -94,4 +113,4 @@ abstract class SectionStorageMixin<R, P> implements ChunkSystemSectionStorage, A
private void redirectClose(final SimpleRegionStorage instance) throws IOException {
this.moonrise$close();
}
}
}

View File

@@ -24,7 +24,6 @@ import net.minecraft.world.level.chunk.ChunkSource;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LightChunk;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.storage.DimensionDataStorage;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
@@ -52,10 +51,6 @@ abstract class ServerChunkCacheMixin extends ChunkSource implements ChunkSystemS
@Final
public ServerLevel level;
@Shadow
@Final
private DimensionDataStorage dataStorage;
@Unique
private final ConcurrentLong2ReferenceChainedHashTable<LevelChunk> fullChunks = new ConcurrentLong2ReferenceChainedHashTable<>();
@@ -86,6 +81,13 @@ abstract class ServerChunkCacheMixin extends ChunkSource implements ChunkSystemS
completable::complete
);
if (!completable.isDone() && chunkTaskScheduler.hasShutdown()) {
throw new IllegalStateException(
"Chunk system has shut down, cannot process chunk requests in world '" + ca.spottedleaf.moonrise.common.util.WorldUtil.getWorldName(this.level) + "' at "
+ "(" + chunkX + "," + chunkZ + ") status: " + toStatus
);
}
if (TickThread.isTickThreadFor(this.level, chunkX, chunkZ)) {
ChunkTaskScheduler.pushChunkWait(this.level, chunkX, chunkZ);
this.mainThreadProcessor.managedBlock(completable::isDone);
@@ -266,7 +268,6 @@ abstract class ServerChunkCacheMixin extends ChunkSource implements ChunkSystemS
@Override
@Overwrite
public void close() throws IOException {
this.dataStorage.close();
((ChunkSystemServerLevel)this.level).moonrise$getChunkTaskScheduler().chunkHolderManager.close(true, true);
}
@@ -320,7 +321,7 @@ abstract class ServerChunkCacheMixin extends ChunkSource implements ChunkSystemS
* @author Spottedleaf
*/
@Inject(
method = "tickChunks(Lnet/minecraft/util/profiling/ProfilerFiller;JLjava/util/List;)V",
method = "tickChunks",
at = @At(
value = "INVOKE",
shift = At.Shift.AFTER,
@@ -341,13 +342,60 @@ abstract class ServerChunkCacheMixin extends ChunkSource implements ChunkSystemS
* @author Spottedleaf
*/
@Redirect(
method = "tickChunks(Lnet/minecraft/util/profiling/ProfilerFiller;JLjava/util/List;)V",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/server/level/ServerLevel;shouldTickBlocksAt(J)Z"
)
method = "tickChunks",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/server/level/ServerLevel;isNaturalSpawningAllowed(Lnet/minecraft/world/level/ChunkPos;)Z"
)
)
private boolean shortNaturalSpawning(final ServerLevel instance, final ChunkPos chunkPos) {
return true;
}
/**
* @reason In the chunk system, ticking chunks always have loaded entities. Of course, they are also always
* marked to be as ticking as well.
* @author Spottedleaf
*/
@Redirect(
method = "tickChunks",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/server/level/ServerLevel;shouldTickBlocksAt(J)Z"
)
)
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

@@ -86,8 +86,8 @@ abstract class ServerLevelMixin extends Level implements ChunkSystemServerLevel,
@Final
private ServerChunkCache chunkSource;
protected ServerLevelMixin(final WritableLevelData writableLevelData, final ResourceKey<Level> resourceKey, final RegistryAccess registryAccess, final Holder<DimensionType> holder, final boolean bl, final boolean bl2, final long l, final int i) {
super(writableLevelData, resourceKey, registryAccess, holder, bl, bl2, l, i);
protected ServerLevelMixin(WritableLevelData writableLevelData, ResourceKey<Level> resourceKey, RegistryAccess registryAccess, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l, int i) {
super(writableLevelData, resourceKey, registryAccess, holder, supplier, bl, bl2, l, i);
}
@Unique
@@ -123,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);
@@ -132,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
@@ -348,6 +354,45 @@ 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;

View File

@@ -84,21 +84,12 @@ abstract class ChunkMapMixin {
((ChunkTickDistanceManager)this.distanceManager).moonrise$removePlayer(player, SectionPos.of(player));
}
/**
* @reason Avoid checking first if there are nearby players, as we make internal perform this implicitly.
* @author Spottedleaf
*/
@Overwrite
public boolean anyPlayerCloseEnoughForSpawning(final ChunkPos pos) {
return this.anyPlayerCloseEnoughForSpawningInternal(pos);
}
/**
* @reason Use nearby players to avoid iterating over all online players
* @author Spottedleaf
*/
@Overwrite
public boolean anyPlayerCloseEnoughForSpawningInternal(final ChunkPos pos) {
public boolean anyPlayerCloseEnoughForSpawning(final ChunkPos pos) {
final ReferenceList<ServerPlayer> players = ((ChunkSystemServerLevel)this.level).moonrise$getNearbyPlayers().getPlayers(
pos, NearbyPlayers.NearbyMapType.SPAWN_RANGE
);

View File

@@ -4,7 +4,6 @@ import ca.spottedleaf.moonrise.common.misc.PositionCountingAreaMap;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickConstants;
import ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickDistanceManager;
import it.unimi.dsi.fastutil.longs.LongIterator;
import net.minecraft.core.SectionPos;
import net.minecraft.server.level.DistanceManager;
import net.minecraft.server.level.ServerPlayer;
@@ -107,13 +106,4 @@ abstract class DistanceManagerMixin implements ChunkTickDistanceManager {
public boolean hasPlayersNearby(final long pos) {
return this.spawnChunkTracker.hasObjectsNear(CoordinateUtils.getChunkX(pos), CoordinateUtils.getChunkZ(pos));
}
/**
* @reason Use spawnChunkTracker instead
* @author Spottedleaf
*/
@Overwrite
public LongIterator getSpawnCandidateChunks() {
return this.spawnChunkTracker.getPositions().iterator();
}
}

View File

@@ -7,6 +7,8 @@ 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;
@@ -16,15 +18,22 @@ 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.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.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;
import java.util.function.Consumer;
@Mixin(ServerChunkCache.class)
abstract class ServerChunkCacheMixin extends ChunkSource {
@@ -33,18 +42,110 @@ abstract class ServerChunkCacheMixin extends ChunkSource {
@Final
public ServerLevel level;
@Shadow
@Final
public ChunkMap chunkMap;
@Unique
private ServerChunkCache.ChunkAndHolder[] iterationCopy;
@Unique
private int iterationCopyLen;
@Unique
private final SimpleRandom shuffleRandom = new SimpleRandom(0L);
@Unique
private boolean isChunkNearPlayer(final ChunkMap chunkMap, final ChunkPos chunkPos, final LevelChunk levelChunk) {
final ChunkData chunkData = ((ChunkSystemChunkHolder)((ChunkSystemLevelChunk)levelChunk).moonrise$getChunkAndHolder().holder())
.moonrise$getRealChunkHolder().holderData;
/**
* @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.
* @author Spottedleaf
*/
@Redirect(
method = "tickChunks",
at = @At(
value = "INVOKE",
target = "Lcom/google/common/collect/Lists;newArrayListWithCapacity(I)Ljava/util/ArrayList;"
)
)
private <T> ArrayList<T> avoidListCreation(final int initialArraySize) {
return null;
}
/**
* @reason Initialise the list to contain only the ticking chunks.
* @author Spottedleaf
*/
@ModifyVariable(
method = "tickChunks",
at = @At(
value = "STORE",
opcode = Opcodes.ASTORE,
ordinal = 0
)
)
private List<ServerChunkCache.ChunkAndHolder> initTickChunks(final List<ServerChunkCache.ChunkAndHolder> shouldBeNull) {
final ReferenceList<ServerChunkCache.ChunkAndHolder> tickingChunks =
((ChunkTickServerLevel)this.level).moonrise$getPlayerTickingChunks();
final ServerChunkCache.ChunkAndHolder[] raw = tickingChunks.getRawDataUnchecked();
final int size = tickingChunks.size();
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(
this.iterationCopy, size
);
}
/**
* @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
*/
@Redirect(
method = "tickChunks",
at = @At(
value = "INVOKE",
target = "Ljava/util/Iterator;hasNext()Z",
ordinal = 0
)
)
private <E> boolean skipTickAdd(final Iterator<E> instance) {
return false;
}
/**
* @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;
@@ -61,7 +162,7 @@ abstract class ServerChunkCacheMixin extends ChunkSource {
Objects.checkFromIndexSize(0, len, raw.length);
for (int i = 0; i < len; ++i) {
if (chunkMap.playerIsCloseEnoughForSpawning(raw[i], chunkPos)) {
if (instance.playerIsCloseEnoughForSpawning(raw[i], chunkPos)) {
return true;
}
}
@@ -70,47 +171,19 @@ abstract class ServerChunkCacheMixin extends ChunkSource {
}
/**
* @reason Use the player ticking chunks list, which already contains chunks that are:
* 1. block ticking
* 2. within spawn range (8 chunks on any axis)
* @reason Clear the iteration array after the list is done being used.
* @author Spottedleaf
*/
@Overwrite
private void collectTickingChunks(final List<LevelChunk> list) {
final ReferenceList<ServerChunkCache.ChunkAndHolder> tickingChunks =
((ChunkTickServerLevel)this.level).moonrise$getPlayerTickingChunks();
final ServerChunkCache.ChunkAndHolder[] raw = tickingChunks.getRawDataUnchecked();
final int size = tickingChunks.size();
final ChunkMap chunkMap = this.chunkMap;
for (int i = 0; i < size; ++i) {
final ServerChunkCache.ChunkAndHolder chunkAndHolder = raw[i];
final LevelChunk levelChunk = chunkAndHolder.chunk();
if (!this.isChunkNearPlayer(chunkMap, levelChunk.getPos(), levelChunk)) {
continue;
}
list.add(levelChunk);
}
}
/**
* @reason Use random implementation which does not use CAS and has a faster nextInt(int)
* function
* @author Spottedleaf
*/
@Redirect(
method = "tickChunks()V",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/Util;shuffle(Ljava/util/List;Lnet/minecraft/util/RandomSource;)V"
)
@Inject(
method = "tickChunks",
at = @At(
value = "INVOKE",
target = "Ljava/util/List;forEach(Ljava/util/function/Consumer;)V",
ordinal = 0,
shift = At.Shift.AFTER
)
)
private <T> void useBetterRandom(final List<T> list, final RandomSource randomSource) {
this.shuffleRandom.setSeed(randomSource.nextLong());
Util.shuffle(list, this.shuffleRandom);
private void broadcastChanges(final CallbackInfo ci) {
Arrays.fill(this.iterationCopy, 0, this.iterationCopyLen, null);
}
}

View File

@@ -6,32 +6,37 @@ import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.entity.vehicle.AbstractMinecart;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import java.util.List;
import java.util.function.Predicate;
@Mixin(ArmorStand.class)
abstract class ArmorStandMixin extends LivingEntity {
@Shadow
@Final
private static Predicate<Entity> RIDABLE_MINECARTS;
protected ArmorStandMixin(EntityType<? extends LivingEntity> entityType, Level level) {
super(entityType, level);
}
/**
* @reason Optimise by making it use the class lookup
* @reason Optimise this method by making it use the class lookup
* @author Spottedleaf
*/
@Redirect(
method = "pushEntities",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/Level;getEntities(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/AABB;Ljava/util/function/Predicate;)Ljava/util/List;"
)
)
private List<Entity> redirectGetEntities(final Level instance, final Entity entity, final AABB list, final Predicate<? super Entity> arg) {
return (List)this.level().getEntitiesOfClass(AbstractMinecart.class, this.getBoundingBox(), arg);
@Overwrite
public void pushEntities() {
final List<AbstractMinecart> nearby = this.level().getEntitiesOfClass(AbstractMinecart.class, this.getBoundingBox(), RIDABLE_MINECARTS);
for (int i = 0, len = nearby.size(); i < len; ++i) {
final AbstractMinecart minecart = nearby.get(i);
if (this.distanceToSqr(minecart) <= 0.2) {
minecart.push(this);
}
}
}
}

View File

@@ -5,16 +5,12 @@ import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape;
import com.mojang.serialization.MapCodec;
import it.unimi.dsi.fastutil.HashCommon;
import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateHolder;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.phys.AABB;
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;
@@ -32,13 +28,7 @@ abstract class BlockStateBaseMixin extends StateHolder<Block, BlockState> implem
protected BlockBehaviour.BlockStateBase.Cache cache;
@Shadow
public abstract VoxelShape getCollisionShape(BlockGetter blockGetter, BlockPos blockPos, CollisionContext collisionContext);
@Shadow
public VoxelShape[] occlusionShapesByFace;
@Shadow
public VoxelShape occlusionShape;
public abstract boolean isAir();
protected BlockStateBaseMixin(Block object, Reference2ObjectArrayMap<Property<?>, Comparable<?>> reference2ObjectArrayMap, MapCodec<BlockState> mapCodec) {
super(object, reference2ObjectArrayMap, mapCodec);
@@ -80,7 +70,7 @@ abstract class BlockStateBaseMixin extends StateHolder<Block, BlockState> implem
}
if (neighbours) {
for (final Direction direction : DIRECTIONS_CACHED) {
initCaches(((CollisionVoxelShape)shape).moonrise$getFaceShapeClamped(direction), false);
initCaches(Shapes.getFaceShape(shape, direction), false);
initCaches(shape.getFaceShape(direction), false);
}
}
@@ -99,10 +89,9 @@ abstract class BlockStateBaseMixin extends StateHolder<Block, BlockState> implem
private void initCollisionState(final CallbackInfo ci) {
if (this.cache != null) {
final VoxelShape collisionShape = this.cache.collisionShape;
try {
this.constantCollisionShape = this.getCollisionShape(null, null, null);
} catch (final Throwable throwable) {
// :(
if (this.isAir()) {
this.constantCollisionShape = Shapes.empty();
} else {
this.constantCollisionShape = null;
}
this.occludesFullBlock = ((CollisionVoxelShape)collisionShape).moonrise$occludesFullBlock();
@@ -113,21 +102,17 @@ abstract class BlockStateBaseMixin extends StateHolder<Block, BlockState> implem
if (this.constantCollisionShape != null) {
initCaches(this.constantCollisionShape, true);
}
if (this.cache.occlusionShapes != null) {
for (final VoxelShape shape : this.cache.occlusionShapes) {
initCaches(shape, false);
}
}
} else {
this.occludesFullBlock = false;
this.emptyCollisionShape = false;
this.emptyConstantCollisionShape = false;
this.constantCollisionShape = null;
}
if (this.occlusionShape != null) {
initCaches(this.occlusionShape, true);
}
if (this.occlusionShapesByFace != null) {
for (final VoxelShape shape : this.occlusionShapesByFace) {
initCaches(shape, true);
}
}
}
@Override

View File

@@ -74,7 +74,7 @@ interface EntityGetterMixin {
@Overwrite
default boolean isUnobstructed(final Entity entity, final VoxelShape voxel) {
if (voxel.isEmpty()) {
return false;
return true;
}
final AABB singleAABB = ((CollisionVoxelShape)voxel).moonrise$getSingleAABBRepresentation();

View File

@@ -1,16 +1,20 @@
package ca.spottedleaf.moonrise.mixin.collisions;
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.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.LevelChunkSection;
@@ -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 {
@@ -50,6 +57,12 @@ abstract class EntityMixin {
@Shadow
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,
final float collidedY) {
@@ -283,4 +296,163 @@ abstract class EntityMixin {
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

@@ -1,5 +1,6 @@
package ca.spottedleaf.moonrise.mixin.collisions;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.common.util.CoordinateUtils;
import ca.spottedleaf.moonrise.patches.getblock.GetBlockChunk;
import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
@@ -9,15 +10,20 @@ import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.Mth;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.item.PrimedTnt;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Explosion;
import net.minecraft.world.level.ExplosionDamageCalculator;
import net.minecraft.world.level.ServerExplosion;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
@@ -27,20 +33,33 @@ 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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Mixin(ServerExplosion.class)
abstract class ServerExplosionMixin {
@Mixin(Explosion.class)
abstract class ExplosionMixin {
@Shadow
@Final
private ServerLevel level;
private Level level;
@Shadow
@Final
private Entity source;
@Shadow
@Final
private double x;
@Shadow
@Final
private double y;
@Shadow
@Final
private double z;
@Shadow
@Final
@@ -50,13 +69,21 @@ abstract class ServerExplosionMixin {
@Final
private float radius;
@Shadow
@Final
private ObjectArrayList<BlockPos> toBlow;
@Shadow
@Final
private Map<Player, Vec3> hitPlayers;
@Shadow
@Final
private boolean fire;
@Shadow
@Final
private Vec3 center;
private DamageSource damageSource;
@Unique
@@ -115,12 +142,6 @@ abstract class ServerExplosionMixin {
@Unique
private LevelChunk[] chunkCache = null;
@Unique
private ExplosionBlockCache[] directMappedBlockCache;
@Unique
private BlockPos.MutableBlockPos mutablePos;
@Unique
private ExplosionBlockCache getOrCacheExplosionBlock(final int x, final int y, final int z,
final long key, final boolean calculateResistance) {
@@ -322,45 +343,29 @@ abstract class ServerExplosionMixin {
return (float)missedRays / (float)totalRays;
}
/**
* @reason Init cache fields
* @author Spottedleaf
*/
@Inject(
method = "explode",
at = @At(
value = "HEAD"
)
)
private void initCacheFields(final CallbackInfo ci) {
this.blockCache = new Long2ObjectOpenHashMap<>();
this.chunkPosCache = new long[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH];
Arrays.fill(this.chunkPosCache, ChunkPos.INVALID_CHUNK_POS);
this.chunkCache = new LevelChunk[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH];
this.directMappedBlockCache = new ExplosionBlockCache[BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH];
this.mutablePos = new BlockPos.MutableBlockPos();
}
/**
* @reason Rewrite ray casting for performance
* @reason Rewrite ray casting and seen fraction calculation for performance
* @author Spottedleaf
*/
@Overwrite
public List<BlockPos> calculateExplodedPositions() {
final ObjectArrayList<BlockPos> ret = new ObjectArrayList<>();
public void explode() {
this.level.gameEvent(this.source, GameEvent.EXPLODE, new Vec3(this.x, this.y, this.z));
final Vec3 center = this.center;
this.blockCache = new Long2ObjectOpenHashMap<>();
final ExplosionBlockCache[] blockCache = this.directMappedBlockCache;
this.chunkPosCache = new long[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH];
Arrays.fill(this.chunkPosCache, ChunkPos.INVALID_CHUNK_POS);
this.chunkCache = new LevelChunk[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH];
final ExplosionBlockCache[] blockCache = new ExplosionBlockCache[BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH];
// use initial cache value that is most likely to be used: the source position
final ExplosionBlockCache initialCache;
{
final int blockX = Mth.floor(center.x);
final int blockY = Mth.floor(center.y);
final int blockZ = Mth.floor(center.z);
final int blockX = Mth.floor(this.x);
final int blockY = Mth.floor(this.y);
final int blockZ = Mth.floor(this.z);
final long key = BlockPos.asLong(blockX, blockY, blockZ);
@@ -376,9 +381,9 @@ abstract class ServerExplosionMixin {
for (int ray = 0, len = CACHED_RAYS.length; ray < len;) {
ExplosionBlockCache cachedBlock = initialCache;
double currX = center.x;
double currY = center.y;
double currZ = center.z;
double currX = this.x;
double currY = this.y;
double currZ = this.z;
final double incX = CACHED_RAYS[ray];
final double incY = CACHED_RAYS[ray + 1];
@@ -397,7 +402,7 @@ abstract class ServerExplosionMixin {
if (cachedBlock.key != key) {
final int cacheKey =
(blockX & BLOCK_EXPLOSION_CACHE_MASK) |
(blockX & BLOCK_EXPLOSION_CACHE_MASK) |
(blockY & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT) |
(blockZ & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT + BLOCK_EXPLOSION_CACHE_SHIFT);
cachedBlock = blockCache[cacheKey];
@@ -419,7 +424,7 @@ abstract class ServerExplosionMixin {
cachedBlock.shouldExplode = shouldExplode ? Boolean.TRUE : Boolean.FALSE;
if (shouldExplode) {
if (this.fire || !cachedBlock.blockState.isAir()) {
ret.add(cachedBlock.immutablePos);
this.toBlow.add(cachedBlock.immutablePos);
}
}
}
@@ -431,39 +436,83 @@ abstract class ServerExplosionMixin {
} while (power > 0.0f);
}
return ret;
}
final double diameter = (double)this.radius * 2.0;
final List<Entity> entities = this.level.getEntities(this.source,
new AABB(
(double)Mth.floor(this.x - (diameter + 1.0)),
(double)Mth.floor(this.y - (diameter + 1.0)),
(double)Mth.floor(this.z - (diameter + 1.0)),
/**
* @reason Use optimised getSeenPercent implementation
* @author Spottedleaf
*/
@Redirect(
method = "hurtEntities",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/ServerExplosion;getSeenPercent(Lnet/minecraft/world/phys/Vec3;Lnet/minecraft/world/entity/Entity;)F"
)
)
private float useBetterSeenPercent(final Vec3 center, final Entity target) {
return this.getSeenFraction(center, target, this.directMappedBlockCache, this.mutablePos);
}
(double)Mth.floor(this.x + (diameter + 1.0)),
(double)Mth.floor(this.y + (diameter + 1.0)),
(double)Mth.floor(this.z + (diameter + 1.0))
)
);
final Vec3 center = new Vec3(this.x, this.y, this.z);
final BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos();
final PlatformHooks platformHooks = PlatformHooks.get();
platformHooks.onExplosion(this.level, (Explosion)(Object)this, entities, diameter);
for (int i = 0, len = entities.size(); i < len; ++i) {
final Entity entity = entities.get(i);
if (entity.ignoreExplosion((Explosion)(Object)this)) {
continue;
}
final double normalizedDistanceToCenter = Math.sqrt(entity.distanceToSqr(center)) / diameter;
if (normalizedDistanceToCenter > 1.0) {
continue;
}
double distX = entity.getX() - this.x;
double distY = (entity instanceof PrimedTnt ? entity.getY() : entity.getEyeY()) - this.y;
double distZ = entity.getZ() - this.z;
final double distMag = Math.sqrt(distX * distX + distY * distY + distZ * distZ);
if (distMag == 0.0) {
continue;
}
distX /= distMag;
distY /= distMag;
distZ /= distMag;
// route to new visible fraction calculation, using the existing block cache
final double seenFraction = (double)this.getSeenFraction(center, entity, blockCache, blockPos);
if (this.damageCalculator.shouldDamageEntity((Explosion)(Object)this, entity)) {
// inline getEntityDamageAmount so that we can avoid double calling getSeenPercent, which is the MOST
// expensive part of this loop!!!!
final double factor = (1.0 - normalizedDistanceToCenter) * seenFraction;
entity.hurt(this.damageSource, (float)((factor * factor + factor) / 2.0 * 7.0 * diameter + 1.0));
}
final double intensityFraction = (1.0 - normalizedDistanceToCenter) * seenFraction * (double)this.damageCalculator.getKnockbackMultiplier(entity);
final double knockbackFraction;
if (entity instanceof LivingEntity livingEntity) {
knockbackFraction = intensityFraction * (1.0 - livingEntity.getAttributeValue(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE));
} else {
knockbackFraction = intensityFraction;
}
Vec3 knockback = new Vec3(distX * knockbackFraction, distY * knockbackFraction, distZ * knockbackFraction);
knockback = platformHooks.modifyExplosionKnockback(this.level, (Explosion)(Object)this, entity, knockback);
entity.setDeltaMovement(entity.getDeltaMovement().add(knockback));
if (entity instanceof Player player) {
if (!player.isSpectator() && (!player.isCreative() || !player.getAbilities().flying)) {
this.hitPlayers.put(player, knockback);
}
}
entity.onExplosionHit(this.source);
}
/**
* @reason Destroy cache fields
* @author Spottedleaf
*/
@Inject(
method = "explode",
at = @At(
value = "RETURN"
)
)
private void destroyCacheFields(final CallbackInfo ci) {
this.blockCache = null;
this.chunkPosCache = null;
this.chunkCache = null;
this.directMappedBlockCache = null;
this.mutablePos = null;
}
}

View File

@@ -55,7 +55,7 @@ abstract class LevelMixin implements LevelAccessor, AutoCloseable {
public boolean isUnobstructed(final Entity entity) {
final AABB boundingBox = entity.getBoundingBox();
if (CollisionUtil.isEmpty(boundingBox)) {
return false;
return true;
}
final List<Entity> entities = this.getEntities(
@@ -83,7 +83,7 @@ abstract class LevelMixin implements LevelAccessor, AutoCloseable {
final Vec3 to = clipContext.getTo();
final Vec3 from = clipContext.getFrom();
return BlockHitResult.miss(to, Direction.getApproximateNearest(from.x - to.x, from.y - to.y, from.z - to.z), BlockPos.containing(to.x, to.y, to.z));
return BlockHitResult.miss(to, Direction.getNearest(from.x - to.x, from.y - to.y, from.z - to.z), BlockPos.containing(to.x, to.y, to.z));
}
@Unique

View File

@@ -4,7 +4,9 @@ import ca.spottedleaf.moonrise.patches.collisions.CollisionUtil;
import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import net.minecraft.client.renderer.block.LiquidBlockRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.shapes.ArrayVoxelShape;
import net.minecraft.world.phys.shapes.BooleanOp;
@@ -21,7 +23,12 @@ abstract class LiquidBlockRendererMixin {
* @author Spottedleaf
*/
@Overwrite
private static boolean isFaceOccludedByState(final Direction direction, final float height, final BlockState state) {
private static boolean isFaceOccludedByState(final BlockGetter world, final Direction direction, final float height,
final BlockPos pos, final BlockState state) {
if (!state.canOcclude()) {
return false;
}
// check for created shape is empty
if (height < (float)CollisionUtil.COLLISION_EPSILON) {
return false;
@@ -43,26 +50,25 @@ abstract class LiquidBlockRendererMixin {
} else {
// the extrusion includes the height
heightShape = new ArrayVoxelShape(
Shapes.block().shape,
CollisionUtil.ZERO_ONE,
DoubleArrayList.wrap(new double[] { 0.0, heightDouble }),
CollisionUtil.ZERO_ONE
Shapes.block().shape,
CollisionUtil.ZERO_ONE,
DoubleArrayList.wrap(new double[] { 0.0, heightDouble }),
CollisionUtil.ZERO_ONE
);
}
final VoxelShape occlusionShape = ((CollisionVoxelShape)state.getFaceOcclusionShape(direction.getOpposite()))
.moonrise$getFaceShapeClamped(direction.getOpposite());
final VoxelShape stateShape = ((CollisionVoxelShape)state.getOcclusionShape(world, pos)).moonrise$getFaceShapeClamped(direction.getOpposite());
if (occlusionShape.isEmpty()) {
if (stateShape.isEmpty()) {
// cannot occlude
return false;
}
// fast check for box
if (heightShape == occlusionShape) {
if (heightShape == stateShape) {
return true;
}
return !Shapes.joinIsNotEmpty(heightShape, occlusionShape, BooleanOp.ONLY_FIRST);
return !Shapes.joinIsNotEmpty(heightShape, stateShape, BooleanOp.ONLY_FIRST);
}
}

View File

@@ -0,0 +1,55 @@
package ca.spottedleaf.moonrise.mixin.collisions;
import net.minecraft.world.entity.Attackable;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntitySelector;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.Level;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import java.util.List;
@Mixin(LivingEntity.class)
abstract class LivingEntityMixin extends Entity implements Attackable {
@Shadow
protected abstract void doPush(Entity entity);
public LivingEntityMixin(EntityType<?> entityType, Level level) {
super(entityType, level);
}
/**
* @reason Optimise this method
* @author Spottedleaf
*/
@Overwrite
public void pushEntities() {
if (this.level().isClientSide()) {
final List<Player> players = this.level().getEntitiesOfClass(Player.class, this.getBoundingBox(), EntitySelector.pushableBy(this));
for (int i = 0, len = players.size(); i < len; ++i) {
this.doPush(players.get(i));
}
} else {
final List<Entity> nearby = this.level().getEntities(this, this.getBoundingBox(), EntitySelector.pushableBy(this));
// only iterate ONCE
int nonPassengers = 0;
for (int i = 0, len = nearby.size(); i < len; ++i) {
final Entity entity = nearby.get(i);
nonPassengers += (entity.isPassenger() ? 0 : 1);
this.doPush(entity);
}
int maxCramming;
if (nonPassengers != 0 && (maxCramming = this.level().getGameRules().getInt(GameRules.RULE_MAX_ENTITY_CRAMMING)) > 0
&& nonPassengers > (maxCramming - 1) && this.random.nextInt(4) == 0) {
this.hurt(this.damageSources().cramming(), 6.0F);
}
}
}
}

View File

@@ -79,7 +79,8 @@ abstract class ParticleMixin {
final List<VoxelShape> voxels = new ArrayList<>();
final boolean collided = CollisionUtil.getCollisionsForBlocksOrWorldBorder(
world, entity, collisionBox, voxels, boxes,
0, null
0,
null
);
if (!collided) {

View File

@@ -278,6 +278,15 @@ abstract class ShapesMixin {
return ret;
}
/**
* @reason Route to use cache
* @author Spottedleaf
*/
@Overwrite
public static VoxelShape getFaceShape(final VoxelShape shape, final Direction direction) {
return ((CollisionVoxelShape)shape).moonrise$getFaceShapeClamped(direction);
}
@Unique
private static boolean mergedMayOccludeBlock(final VoxelShape shape1, final VoxelShape shape2) {
// if the combined bounds of the two shapes cannot occlude, then neither can the merged

View File

@@ -1,7 +1,9 @@
package ca.spottedleaf.moonrise.mixin.collisions;
import ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape;
import net.minecraft.core.Direction;
import net.minecraft.world.phys.shapes.SliceShape;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -20,7 +22,7 @@ abstract class SliceShapeMixin {
value = "RETURN"
)
)
private void initState(final CallbackInfo ci) {
private void initState(final VoxelShape parent, final Direction.Axis forAxis, final int forIndex, final CallbackInfo ci) {
((CollisionVoxelShape)this).moonrise$initCache();
}
}

View File

@@ -687,13 +687,13 @@ abstract class VoxelShapeMixin implements CollisionVoxelShape {
final AABB singleAABB = this.singleAABBRepresentation;
if (singleAABB != null) {
if (singleAABB.contains(fromBehindOffsetX, fromBehindOffsetY, fromBehindOffsetZ)) {
return new BlockHitResult(fromBehind, Direction.getApproximateNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), offset, true);
return new BlockHitResult(fromBehind, Direction.getNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), offset, true);
}
return clip(singleAABB, from, to, offset);
}
if (CollisionUtil.strictlyContains((VoxelShape)(Object)this, fromBehindOffsetX, fromBehindOffsetY, fromBehindOffsetZ)) {
return new BlockHitResult(fromBehind, Direction.getApproximateNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), offset, true);
return new BlockHitResult(fromBehind, Direction.getNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), offset, true);
}
return AABB.clip(((VoxelShape)(Object)this).toAabbs(), from, to, offset);

View File

@@ -1,6 +1,5 @@
package ca.spottedleaf.moonrise.mixin.entity_tracker;
import ca.spottedleaf.moonrise.common.PlatformHooks;
import ca.spottedleaf.moonrise.common.list.ReferenceList;
import ca.spottedleaf.moonrise.common.misc.NearbyPlayers;
import ca.spottedleaf.moonrise.common.util.TickThread;
@@ -145,8 +144,8 @@ abstract class TrackedEntityMixin implements EntityTrackerTrackedEntity {
*/
@Overwrite
public int getEffectiveRange() {
int range = this.range;
final Entity entity = this.entity;
int range = PlatformHooks.get().modifyEntityTrackingRange(entity, this.range);
if (entity.getPassengers() == ImmutableList.<Entity>of()) {
return this.scaledRange(range);
@@ -155,9 +154,8 @@ abstract class TrackedEntityMixin implements EntityTrackerTrackedEntity {
// note: we change to List
final List<Entity> passengers = (List<Entity>)entity.getIndirectPassengers();
for (int i = 0, len = passengers.size(); i < len; ++i) {
final Entity passenger = passengers.get(i);
// note: max should be branchless
range = Math.max(range, PlatformHooks.get().modifyEntityTrackingRange(passenger, passenger.getType().clientTrackingRange() << 4));
range = Math.max(range, passengers.get(i).getType().clientTrackingRange() << 4);
}
return this.scaledRange(range);

View File

@@ -24,7 +24,7 @@ abstract class PalettedContainerMixin<T> implements PaletteResize<T>, PalettedCo
private void updateData(final PalettedContainer.Data<T> data) {
if (data != null) {
((FastPaletteData<T>)(Object)data).moonrise$setPalette(
((FastPalette<T>)data.palette()).moonrise$getRawPalette((FastPaletteData<T>)(Object)data)
((FastPalette<T>)data.palette).moonrise$getRawPalette((FastPaletteData<T>)(Object)data)
);
}
}
@@ -80,7 +80,7 @@ abstract class PalettedContainerMixin<T> implements PaletteResize<T>, PalettedCo
@Unique
private T readPaletteSlow(final PalettedContainer.Data<T> data, final int paletteIdx) {
return data.palette().valueFor(paletteIdx);
return data.palette.valueFor(paletteIdx);
}
@Unique
@@ -103,9 +103,9 @@ abstract class PalettedContainerMixin<T> implements PaletteResize<T>, PalettedCo
*/
@Overwrite
public T getAndSet(final int index, final T value) {
final int paletteIdx = this.data.palette().idFor(value);
final int paletteIdx = this.data.palette.idFor(value);
final PalettedContainer.Data<T> data = this.data;
final int prev = data.storage().getAndSet(index, paletteIdx);
final int prev = data.storage.getAndSet(index, paletteIdx);
return this.readPalette(data, prev);
}
@@ -116,6 +116,6 @@ abstract class PalettedContainerMixin<T> implements PaletteResize<T>, PalettedCo
@Overwrite
public T get(final int index) {
final PalettedContainer.Data<T> data = this.data;
return this.readPalette(data, data.storage().get(index));
return this.readPalette(data, data.storage.get(index));
}
}

View File

@@ -114,16 +114,16 @@ abstract class FlowingFluidMixin extends Fluid {
private static final int COLLISION_OCCLUSION_CACHE_SIZE = 2048;
@Unique
private static final ThreadLocal<FluidOcclusionCacheKey[]> COLLISION_OCCLUSION_CACHE = ThreadLocal.withInitial(() -> new FluidOcclusionCacheKey[COLLISION_OCCLUSION_CACHE_SIZE]);
private static final FluidOcclusionCacheKey[] COLLISION_OCCLUSION_CACHE = new FluidOcclusionCacheKey[COLLISION_OCCLUSION_CACHE_SIZE];
/**
* @reason Try to avoid going to the cache for simple cases; additionally use better caching strategy
* @author Spottedleaf
*/
@Overwrite
public static boolean canPassThroughWall(final Direction direction, final BlockGetter level,
final BlockPos fromPos, final BlockState fromState,
final BlockPos toPos, final BlockState toState) {
private boolean canPassThroughWall(final Direction direction, final BlockGetter level,
final BlockPos fromPos, final BlockState fromState,
final BlockPos toPos, final BlockState toState) {
if (((CollisionBlockState)fromState).moonrise$emptyCollisionShape() & ((CollisionBlockState)toState).moonrise$emptyCollisionShape()) {
// don't even try to cache simple cases
return true;
@@ -135,7 +135,7 @@ abstract class FlowingFluidMixin extends Fluid {
}
final FluidOcclusionCacheKey[] cache = ((CollisionBlockState)fromState).moonrise$hasCache() & ((CollisionBlockState)toState).moonrise$hasCache() ?
COLLISION_OCCLUSION_CACHE.get() : null;
COLLISION_OCCLUSION_CACHE : null;
final int keyIndex
= (((CollisionBlockState)fromState).moonrise$uniqueId1() ^ ((CollisionBlockState)toState).moonrise$uniqueId2() ^ ((CollisionDirection)(Object)direction).moonrise$uniqueId())

View File

@@ -42,7 +42,7 @@ abstract class FluidStateMixin extends StateHolder<Fluid, FluidState> implements
private BlockState legacyBlock;
@Override
public final void moonrise$initCaches() {
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);

View File

@@ -29,9 +29,9 @@ abstract class MappedRegistryMixin<T> {
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();
if (resourceKey.registryKey() == (Object) Registries.FLUID) {
for (final FluidState possibleState : ((Fluid) object).getStateDefinition().getPossibleStates()) {
((FluidFluidState) (Object) possibleState).moonrise$initCaches();
}
}
}

View File

@@ -16,20 +16,20 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = Level.class, priority = 1100)
abstract class LevelMixin implements LevelAccessor, AutoCloseable {
@Unique
private int minY;
@Unique
private int height;
@Unique
private int maxY;
private int minBuildHeight;
@Unique
private int minSectionY;
private int maxBuildHeight;
@Unique
private int maxSectionY;
private int minSection;
@Unique
private int maxSection;
@Unique
private int sectionsCount;
@@ -47,17 +47,12 @@ abstract class LevelMixin implements LevelAccessor, AutoCloseable {
private void init(final CallbackInfo ci,
@Local(ordinal = 0, argsOnly = true) final Holder<DimensionType> dimensionTypeHolder) {
final DimensionType dimType = dimensionTypeHolder.value();
this.minY = dimType.minY();
this.height = dimType.height();
this.maxY = this.minY + this.height - 1;
this.minSectionY = this.minY >> 4;
this.maxSectionY = this.maxY >> 4;
this.sectionsCount = this.maxSectionY - this.minSectionY + 1;
}
@Override
public int getMinY() {
return this.minY;
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
@@ -66,52 +61,52 @@ abstract class LevelMixin implements LevelAccessor, AutoCloseable {
}
@Override
public int getMaxY() {
return this.maxY;
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;
}
@Override
public int getMinSectionY() {
return this.minSectionY;
}
@Override
public int getMaxSectionY() {
return this.maxSectionY;
}
@Override
public boolean isInsideBuildHeight(final int blockY) {
return blockY >= this.minY && blockY <= this.maxY;
}
@Override
public boolean isOutsideBuildHeight(final BlockPos pos) {
return this.isOutsideBuildHeight(pos.getY());
}
@Override
public boolean isOutsideBuildHeight(final int blockY) {
return blockY < this.minY || blockY > this.maxY;
}
@Override
public int getSectionIndex(final int blockY) {
return (blockY >> 4) - this.minSectionY;
}
@Override
public int getSectionIndexFromSectionY(final int sectionY) {
return sectionY - this.minSectionY;
}
@Override
public int getSectionYFromSectionIndex(final int sectionIdx) {
return sectionIdx + this.minSectionY;
}
}

View File

@@ -1,5 +1,6 @@
package ca.spottedleaf.moonrise.mixin.loading_screen;
import ca.spottedleaf.moonrise.common.util.ConfigHolder;
import net.minecraft.client.multiplayer.LevelLoadStatusManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -25,6 +26,9 @@ abstract class LevelLoadStatusManagerMixin {
)
)
private void immediatelyClose(final CallbackInfo ci) {
if (!ConfigHolder.getConfig().misc.immediatelyCloseLoadingScreen) {
return;
}
if (this.status == LevelLoadStatusManager.Status.WAITING_FOR_SERVER) {
this.status = LevelLoadStatusManager.Status.LEVEL_READY;
ci.cancel();

View File

@@ -20,17 +20,16 @@ import org.spongepowered.asm.mixin.Overwrite;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
@Mixin(PoiManager.class)
abstract class PoiManagerMixin extends SectionStorage<PoiSection, PoiSection.Packed> {
abstract class PoiManagerMixin extends SectionStorage<PoiSection> {
public PoiManagerMixin(final SimpleRegionStorage simpleRegionStorage, final Codec<PoiSection.Packed> codec, final Function<PoiSection, PoiSection.Packed> function, final BiFunction<PoiSection.Packed, Runnable, PoiSection> biFunction, final Function<Runnable, PoiSection> function2, final RegistryAccess registryAccess, final ChunkIOErrorReporter chunkIOErrorReporter, final LevelHeightAccessor levelHeightAccessor) {
super(simpleRegionStorage, codec, function, biFunction, function2, registryAccess, chunkIOErrorReporter, levelHeightAccessor);
public PoiManagerMixin(SimpleRegionStorage simpleRegionStorage, Function<Runnable, Codec<PoiSection>> function, Function<Runnable, PoiSection> function2, RegistryAccess registryAccess, ChunkIOErrorReporter chunkIOErrorReporter, LevelHeightAccessor levelHeightAccessor) {
super(simpleRegionStorage, function, function2, registryAccess, chunkIOErrorReporter, levelHeightAccessor);
}
/**

View File

@@ -49,7 +49,7 @@ abstract class MinecraftMixin extends ReentrantBlockableEventLoop<Runnable> impl
return;
}
cir.setReturnValue(ret == null || ret == InactiveProfiler.INSTANCE ? this.leafProfiler : ProfilerFiller.combine(this.leafProfiler, ret));
cir.setReturnValue(ret == null || ret == InactiveProfiler.INSTANCE ? this.leafProfiler : ProfilerFiller.tee(this.leafProfiler, ret));
}
/**

View File

@@ -10,14 +10,14 @@ import org.spongepowered.asm.mixin.Shadow;
abstract class BiomeMixin {
@Shadow
protected abstract float getHeightAdjustedTemperature(BlockPos blockPos, int seaLevel);
protected abstract float getHeightAdjustedTemperature(BlockPos blockPos);
/**
* @reason Cache appears ineffective
* @author Spottedleaf
*/
@Overwrite
public float getTemperature(final BlockPos pos, final int seaLevel) {
return this.getHeightAdjustedTemperature(pos, seaLevel);
public float getTemperature(final BlockPos pos) {
return this.getHeightAdjustedTemperature(pos);
}
}

View File

@@ -12,6 +12,7 @@ import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.WorldGenLevel;
@@ -26,12 +27,13 @@ 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.Redirect;
import java.util.function.Supplier;
@Mixin(ServerLevel.class)
abstract class ServerLevelMixin extends Level implements WorldGenLevel {
protected ServerLevelMixin(final WritableLevelData writableLevelData, final ResourceKey<Level> resourceKey, final RegistryAccess registryAccess, final Holder<DimensionType> holder, final boolean bl, final boolean bl2, final long l, final int i) {
super(writableLevelData, resourceKey, registryAccess, holder, bl, bl2, l, i);
protected ServerLevelMixin(WritableLevelData writableLevelData, ResourceKey<Level> resourceKey, RegistryAccess registryAccess, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean bl, boolean bl2, long l, int i) {
super(writableLevelData, resourceKey, registryAccess, holder, supplier, bl, bl2, l, i);
}
@Unique

View File

@@ -10,7 +10,7 @@ abstract class ServerSelectionListMixin {
/**
* @reason Massively increase the threadpool count so that slow servers do not stall the pinging of other servers
* on the status list
* on the status list
* @author Spottedleaf
*/
@ModifyConstant(

View File

@@ -19,10 +19,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(BlockBehaviour.BlockStateBase.class)
abstract class BlockStateBaseMixin extends StateHolder<Block, BlockState> implements StarlightAbstractBlockState {
protected BlockStateBaseMixin(Block object, Reference2ObjectArrayMap<Property<?>, Comparable<?>> reference2ObjectArrayMap, MapCodec<BlockState> mapCodec) {
super(object, reference2ObjectArrayMap, mapCodec);
}
@Shadow
@Final
private boolean useShapeForLightOcclusion;
@@ -31,26 +27,29 @@ abstract class BlockStateBaseMixin extends StateHolder<Block, BlockState> implem
@Final
private boolean canOcclude;
@Shadow
protected BlockBehaviour.BlockStateBase.Cache cache;
@Unique
private boolean isConditionallyFullOpaque;
protected BlockStateBaseMixin(Block object, Reference2ObjectArrayMap<Property<?>, Comparable<?>> reference2ObjectArrayMap, MapCodec<BlockState> mapCodec) {
super(object, reference2ObjectArrayMap, mapCodec);
}
/**
* Initialises our light state for this block.
*/
@Inject(
method = "initCache",
at = @At("RETURN")
)
public void initLightAccessState(final CallbackInfo ci) {
this.isConditionallyFullOpaque = this.canOcclude & this.useShapeForLightOcclusion;
}
@Override
public final boolean starlight$isConditionallyFullOpaque() {
return this.isConditionallyFullOpaque;
}
/**
* @reason Initialises our light state for this block.
* @author Spottedleaf
*/
@Inject(
method = "initCache",
at = @At(
value = "RETURN"
)
)
public void initLightAccessState(final CallbackInfo ci) {
this.isConditionallyFullOpaque = this.canOcclude & this.useShapeForLightOcclusion;
}
}

View File

@@ -2,16 +2,11 @@ package ca.spottedleaf.moonrise.mixin.starlight.chunk;
import ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk;
import ca.spottedleaf.moonrise.patches.starlight.light.SWMRNibbleArray;
import net.minecraft.core.Registry;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.level.chunk.ImposterProtoChunk;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.levelgen.blending.BlendingData;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -23,8 +18,8 @@ abstract class ImposterProtoChunkMixin extends ProtoChunk implements StarlightCh
@Shadow
private LevelChunk wrapped;
public ImposterProtoChunkMixin(final ChunkPos chunkPos, final UpgradeData upgradeData, final LevelHeightAccessor levelHeightAccessor, final Registry<Biome> registry, @Nullable final BlendingData blendingData) {
super(chunkPos, upgradeData, levelHeightAccessor, registry, blendingData);
public ImposterProtoChunkMixin(final LevelChunk levelChunk, final boolean bl) {
super(levelChunk.getPos(), UpgradeData.EMPTY, levelChunk, levelChunk.getLevel().registryAccess().registryOrThrow(Registries.BIOME), levelChunk.getBlendingData());
}
@Override

View File

@@ -54,24 +54,7 @@ abstract class LevelLightEngineMixin implements LightEventListener, StarLightLig
* TODO since this is a constructor inject, check on update for new constructors
*/
@Inject(
method = "<init>()V",
at = @At(
value = "TAIL"
)
)
public void constructEmpty(final CallbackInfo ci) {
this.lightEngine = new StarLightInterface(null, false, false, (LevelLightEngine)(Object)this);
}
/**
*
* TODO since this is a constructor inject, check on update for new constructors
*/
@Inject(
method = "<init>(Lnet/minecraft/world/level/chunk/LightChunkGetter;ZZ)V",
at = @At(
value = "TAIL"
)
method = "<init>", at = @At("TAIL")
)
public void construct(final LightChunkGetter chunkProvider, final boolean hasBlockLight, final boolean hasSkyLight,
final CallbackInfo ci) {
@@ -196,10 +179,8 @@ abstract class LevelLightEngineMixin implements LightEventListener, StarLightLig
* @author Spottedleaf
*/
@Overwrite
public boolean lightOnInColumn(final long pos) {
final long key = CoordinateUtils.getChunkKey(
CoordinateUtils.getChunkSectionX(pos), CoordinateUtils.getChunkSectionZ(pos)
);
public boolean lightOnInSection(final SectionPos pos) {
final long key = CoordinateUtils.getChunkKey(pos.getX(), pos.getZ());
return (!this.lightEngine.hasBlockLight() || this.blockLightMap.get(key) != null) && (!this.lightEngine.hasSkyLight() || this.skyLightMap.get(key) != null);
}

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