Skip to content

Commit 6b64c49

Browse files
committed
Fixed incorrect light cache.
Temporarily fixed a CME issue.
1 parent 5e3c7e7 commit 6b64c49

File tree

18 files changed

+189
-52
lines changed

18 files changed

+189
-52
lines changed

common/src/main/java/fun/qu_an/minecraft/asyncparticles/client/addon/LightCachedParticleAddon.java

+7-16
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,19 @@
77
import net.minecraft.client.particle.Particle;
88

99
public interface LightCachedParticleAddon {
10-
short INITIAL_LIGHT_CACHE = -1;
11-
static int unpackLight(byte lightCache) {
12-
return (lightCache & 0xF) << 4 | (lightCache & 0xF0) << 16;
10+
byte INITIAL_LIGHT_CACHE = 0;
11+
12+
static byte compress(int light) {
13+
return (byte) (light >> 4 & 0xF | light >> 16 & 0xF0);
1314
}
1415

15-
/**
16-
* With some complex logic, we can avoid unnecessary calls to {@link Particle#getLightColor}<p>
17-
* See {@link MixinParticle_LightCache#wrapGetLightColor},<p>
18-
* {@link MixinParticle_LightCache#asyncParticles$refresh}<p>
19-
* for more details.
20-
*/
21-
static boolean isLightCacheValid(short lightCache) {
22-
return lightCache >= 0 || (
23-
lightCache != INITIAL_LIGHT_CACHE
24-
&& (ThreadUtil.isOnParticleRendererThread()
25-
|| (RenderSystem.isOnRenderThread() && !AsyncTicker.isTickingSync()))
26-
);
16+
static int decompress(byte lightCache) {
17+
return (lightCache & 0xF) << 4 | (lightCache & 0xF0) << 16;
2718
}
2819

2920
void asyncParticles$setLight(int light);
3021

31-
short asyncParticles$getPackedLight();
22+
byte asyncParticles$getCompressedLight();
3223

3324
void asyncParticles$refresh();
3425

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package fun.qu_an.minecraft.asyncparticles.client.mixin;
2+
3+
import net.minecraft.world.level.chunk.ChunkAccess;
4+
import net.minecraft.world.level.chunk.LevelChunk;
5+
import org.spongepowered.asm.mixin.Mixin;
6+
import org.spongepowered.asm.mixin.injection.At;
7+
import org.spongepowered.asm.mixin.injection.Redirect;
8+
import org.spongepowered.asm.mixin.injection.Slice;
9+
10+
import java.util.HashMap;
11+
12+
@Mixin(ChunkAccess.class)
13+
public class MixinChunkAccess {
14+
@Redirect(method = "<init>",
15+
slice = @Slice(
16+
from = @At(value = "FIELD", shift = At.Shift.BEFORE, target = "Lnet/minecraft/world/level/chunk/ChunkAccess;blockEntities:Ljava/util/Map;")
17+
),
18+
at = @At(value = "INVOKE", ordinal = 0, remap = false, target = "Lcom/google/common/collect/Maps;newHashMap()Ljava/util/HashMap;"))
19+
private <K, V> HashMap<K, V> newHashMap() {
20+
return (Object) this instanceof LevelChunk ? null : new HashMap<>();
21+
}
22+
}

common/src/main/java/fun/qu_an/minecraft/asyncparticles/client/mixin/MixinLevel.java

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package fun.qu_an.minecraft.asyncparticles.client.mixin;
22

3+
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
4+
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
5+
import fun.qu_an.minecraft.asyncparticles.client.AsyncTicker;
36
import net.minecraft.world.level.Level;
47
import net.minecraft.world.level.block.entity.TickingBlockEntity;
58
import org.spongepowered.asm.mixin.Final;
@@ -22,7 +25,7 @@ public class MixinLevel {
2225
// This is only necessary on the client side
2326
@SuppressWarnings("SynchronizeOnNonFinalField")
2427
@Redirect(method = "tickBlockEntities", at = @At(value = "INVOKE", target = "Ljava/util/List;isEmpty()Z"))
25-
private boolean onTickBlockEntities(List instance) {
28+
private boolean onTickBlockEntities(List<?> instance) {
2629
if (!isClientSide) {
2730
return instance.isEmpty();
2831
}
@@ -36,4 +39,20 @@ private boolean onTickBlockEntities(List instance) {
3639
}
3740
return true;
3841
}
42+
43+
@WrapOperation(method = "tickBlockEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/entity/TickingBlockEntity;tick()V"))
44+
private void onTickBlockEntity(TickingBlockEntity instance, Operation<Void> original) {
45+
if (!isClientSide) {
46+
original.call(instance);
47+
return;
48+
}
49+
try {
50+
original.call(instance);
51+
} catch (Exception e) {
52+
if (!AsyncTicker.isTolerable(e)) {
53+
throw e;
54+
}
55+
AsyncTicker.LOGGER.warn("Exception while ticking block entity {}", instance, e);
56+
}
57+
}
3958
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package fun.qu_an.minecraft.asyncparticles.client.mixin;
2+
3+
import net.minecraft.core.Registry;
4+
import net.minecraft.world.level.ChunkPos;
5+
import net.minecraft.world.level.Level;
6+
import net.minecraft.world.level.LevelHeightAccessor;
7+
import net.minecraft.world.level.biome.Biome;
8+
import net.minecraft.world.level.chunk.ChunkAccess;
9+
import net.minecraft.world.level.chunk.LevelChunk;
10+
import net.minecraft.world.level.chunk.LevelChunkSection;
11+
import net.minecraft.world.level.chunk.UpgradeData;
12+
import net.minecraft.world.level.levelgen.blending.BlendingData;
13+
import org.jetbrains.annotations.Nullable;
14+
import org.spongepowered.asm.mixin.Final;
15+
import org.spongepowered.asm.mixin.Mixin;
16+
import org.spongepowered.asm.mixin.Shadow;
17+
import org.spongepowered.asm.mixin.injection.At;
18+
import org.spongepowered.asm.mixin.injection.Inject;
19+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
20+
21+
import java.util.HashMap;
22+
import java.util.concurrent.ConcurrentHashMap;
23+
24+
@Mixin(LevelChunk.class)
25+
public abstract class MixinLevelChunk extends ChunkAccess {
26+
@Shadow
27+
@Final
28+
Level level;
29+
30+
public MixinLevelChunk(ChunkPos chunkPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor, Registry<Biome> biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sections, @Nullable BlendingData blendingData) {
31+
super(chunkPos, upgradeData, levelHeightAccessor, biomeRegistry, inhabitedTime, sections, blendingData);
32+
}
33+
34+
// FIXME: any better way to do this?
35+
@Inject(method = "<init>(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/chunk/UpgradeData;Lnet/minecraft/world/ticks/LevelChunkTicks;Lnet/minecraft/world/ticks/LevelChunkTicks;J[Lnet/minecraft/world/level/chunk/LevelChunkSection;Lnet/minecraft/world/level/chunk/LevelChunk$PostLoadProcessor;Lnet/minecraft/world/level/levelgen/blending/BlendingData;)V", at = @At("RETURN"))
36+
private void onInit(CallbackInfo ci) {
37+
if (level.isClientSide) {
38+
blockEntities = new ConcurrentHashMap<>();
39+
} else {
40+
blockEntities = new HashMap<>();
41+
}
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package fun.qu_an.minecraft.asyncparticles.client.mixin;
2+
3+
import com.chailotl.particular.particles.FireflyParticle;
4+
import fun.qu_an.minecraft.asyncparticles.client.addon.LightCachedParticleAddon;
5+
import net.minecraft.client.particle.*;
6+
import org.spongepowered.asm.mixin.Mixin;
7+
import org.spongepowered.asm.mixin.Pseudo;
8+
9+
@Pseudo
10+
@Mixin({
11+
HugeExplosionParticle.class,
12+
AttackSweepParticle.class,
13+
SculkChargeParticle.class,
14+
SculkChargePopParticle.class,
15+
SimpleAnimatedParticle.class,
16+
ShriekParticle.class,
17+
FireflyParticle.class,
18+
VibrationSignalParticle.class,
19+
// Add more particle classes here if needed
20+
})
21+
public abstract class MixinMixinParticle_NoRefreshLightCache implements LightCachedParticleAddon {
22+
@Override
23+
public void asyncParticles$refresh() {
24+
}
25+
}

common/src/main/java/fun/qu_an/minecraft/asyncparticles/client/mixin/MixinParticle_LightCache.java

+23-13
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
44
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
55
import fun.qu_an.minecraft.asyncparticles.client.addon.LightCachedParticleAddon;
6+
import fun.qu_an.minecraft.asyncparticles.client.config.SimplePropertiesConfig;
7+
import net.minecraft.client.multiplayer.ClientLevel;
68
import net.minecraft.client.particle.Particle;
9+
import net.minecraft.client.renderer.LevelRenderer;
10+
import net.minecraft.core.BlockPos;
11+
import org.spongepowered.asm.mixin.Final;
712
import org.spongepowered.asm.mixin.Mixin;
813
import org.spongepowered.asm.mixin.Shadow;
914
import org.spongepowered.asm.mixin.Unique;
@@ -13,34 +18,39 @@
1318

1419
@Mixin(Particle.class)
1520
public abstract class MixinParticle_LightCache implements LightCachedParticleAddon {
21+
@Shadow @Final public ClientLevel level;
22+
@Shadow public double x;
23+
@Shadow public double y;
24+
@Shadow public double z;
25+
@Unique
26+
private byte asyncParticles$lightCache = INITIAL_LIGHT_CACHE;
1627
@Shadow
1728
public abstract int getLightColor(float partialTick);
18-
@Unique
19-
private short asyncParticles$lightCache = INITIAL_LIGHT_CACHE;
2029

2130
@WrapMethod(method = "getLightColor")
2231
private int wrapGetLightColor(float partialTick, Operation<Integer> original) {
23-
short lightCache = asyncParticles$getPackedLight();
24-
return LightCachedParticleAddon.isLightCacheValid(lightCache)
25-
? unpackLight((byte) lightCache)
32+
return SimplePropertiesConfig.particleLightCache()
33+
? decompress(asyncParticles$getCompressedLight())
2634
: original.call(partialTick);
2735
}
2836

2937
@Override
30-
public void asyncParticles$setLight(int light) {
31-
asyncParticles$lightCache = (short) (light >> 4 & 0xF | light >> 16 & 0xF0);
38+
public void asyncParticles$refresh() {
39+
// for some particles, light is hard coded to 15, so this is not necessary for all particles
40+
// do we need a better design?
41+
BlockPos blockPos = BlockPos.containing(x, y, z);
42+
int light = level.hasChunkAt(blockPos) ? LevelRenderer.getLightColor(level, blockPos) : 0;
43+
asyncParticles$setLight(light);
3244
}
3345

3446
@Override
35-
public short asyncParticles$getPackedLight() {
36-
return asyncParticles$lightCache;
47+
public void asyncParticles$setLight(int light) {
48+
asyncParticles$lightCache = compress(light);
3749
}
3850

3951
@Override
40-
public void asyncParticles$refresh() {
41-
// mark as outdated, we don't set to -1 because -1 was used as a special value for initial cache
42-
asyncParticles$lightCache |= Short.MIN_VALUE;
43-
asyncParticles$setLight(getLightColor(0));
52+
public byte asyncParticles$getCompressedLight() {
53+
return asyncParticles$lightCache;
4454
}
4555

4656
@Override

common/src/main/java/fun/qu_an/minecraft/asyncparticles/client/mixin/create/MixinParticle_LightCache.java

+16-6
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,27 @@
66
import com.simibubi.create.content.kinetics.steamEngine.SteamJetParticle;
77
import com.simibubi.create.foundation.particle.AirParticle;
88
import fun.qu_an.minecraft.asyncparticles.client.addon.LightCachedParticleAddon;
9+
import fun.qu_an.minecraft.asyncparticles.client.config.SimplePropertiesConfig;
10+
import net.minecraft.client.renderer.LevelRenderer;
11+
import net.minecraft.core.BlockPos;
912
import org.spongepowered.asm.mixin.Mixin;
1013

11-
import static fun.qu_an.minecraft.asyncparticles.client.addon.LightCachedParticleAddon.unpackLight;
14+
import static fun.qu_an.minecraft.asyncparticles.client.addon.LightCachedParticleAddon.decompress;
1215

1316
@Mixin({AirFlowParticle.class, AirParticle.class, SteamJetParticle.class})
14-
public abstract class MixinParticle_LightCache implements LightCachedParticleAddon {
17+
public abstract class MixinParticle_LightCache
18+
extends fun.qu_an.minecraft.asyncparticles.client.mixin.MixinParticle_LightCache {
1519
@WrapMethod(method = "getLightColor")
16-
public int wrapGetLightColor(float partialTick, Operation<Integer> original) {
17-
short lightCache = asyncParticles$getPackedLight();
18-
return LightCachedParticleAddon.isLightCacheValid(lightCache)
19-
? unpackLight((byte) lightCache)
20+
private int wrapGetLightColor(float partialTick, Operation<Integer> original) {
21+
return SimplePropertiesConfig.particleLightCache()
22+
? decompress(asyncParticles$getCompressedLight())
2023
: original.call(partialTick);
2124
}
25+
26+
@Override
27+
public void asyncParticles$refresh() {
28+
BlockPos blockpos = BlockPos.containing(x, y, z);
29+
int light = level.isLoaded(blockpos) ? LevelRenderer.getLightColor(level, blockpos) : 0;
30+
asyncParticles$setLight(light);
31+
}
2232
}

common/src/main/java/fun/qu_an/minecraft/asyncparticles/client/mixin/fabric/create_5/MixinDepotBehaviour.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.simibubi.create.foundation.blockEntity.behaviour.BlockEntityBehaviour;
1111
import com.simibubi.create.foundation.item.ItemHelper;
1212
import com.simibubi.create.foundation.utility.VecHelper;
13+
import fun.qu_an.minecraft.asyncparticles.client.util.ThreadUtil;
1314
import net.minecraft.nbt.CompoundTag;
1415
import net.minecraft.nbt.ListTag;
1516
import net.minecraft.world.Containers;
@@ -73,7 +74,7 @@ private void onInit(SmartBlockEntity be, CallbackInfo ci) {
7374
Level level = be.getLevel();
7475
// 这个列表很小,不会过于影响性能
7576
if (level == null) {
76-
if (RenderSystem.isOnRenderThread()) {
77+
if (ThreadUtil.isOnClientTickThread()) {
7778
incoming = new CopyOnWriteArrayList<>(incoming);
7879
}
7980
} else if (level.isClientSide) {
@@ -87,7 +88,7 @@ private <T> List<T> readCompoundList(ListTag listNBT, Function<CompoundTag, T> d
8788
Level level = blockEntity.getLevel();
8889
// 这个列表很小,不会过于影响性能
8990
if (level == null) {
90-
if (RenderSystem.isOnRenderThread()) {
91+
if (ThreadUtil.isOnClientTickThread()) {
9192
return new CopyOnWriteArrayList<>(original.call(listNBT, deserializer));
9293
}
9394
} else if (level.isClientSide) {

common/src/main/java/fun/qu_an/minecraft/asyncparticles/client/mixin/fabric/create_5/MixinEjectorBlockEntity.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
44
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
55
import com.llamalad7.mixinextras.sugar.Local;
6-
import com.mojang.blaze3d.systems.RenderSystem;
76
import com.simibubi.create.content.kinetics.base.KineticBlockEntity;
87
import com.simibubi.create.content.logistics.depot.EjectorBlockEntity;
98
import com.simibubi.create.foundation.utility.LongAttached;
109
import com.simibubi.create.foundation.utility.Pair;
10+
import fun.qu_an.minecraft.asyncparticles.client.util.ThreadUtil;
1111
import net.minecraft.core.BlockPos;
1212
import net.minecraft.nbt.CompoundTag;
1313
import net.minecraft.nbt.ListTag;
@@ -78,7 +78,7 @@ private void onInit(BlockEntityType<?> typeIn, BlockPos pos, BlockState state, C
7878
Level level = getLevel();
7979
// 这个列表很小,不会过于影响性能
8080
if (level == null) {
81-
if (RenderSystem.isOnRenderThread()) {
81+
if (ThreadUtil.isOnClientTickThread()) {
8282
launchedItems = new CopyOnWriteArrayList<>(launchedItems);
8383
}
8484
} else if (level.isClientSide) {
@@ -91,7 +91,7 @@ private <T> List<T> readCompoundList(ListTag listNBT, Function<CompoundTag, T> d
9191
Level level = getLevel();
9292
// 这个列表很小,不会过于影响性能
9393
if (level == null) {
94-
if (RenderSystem.isOnRenderThread()) {
94+
if (ThreadUtil.isOnClientTickThread()) {
9595
return new CopyOnWriteArrayList<>(original.call(listNBT, deserializer));
9696
}
9797
} else if (level.isClientSide) {

common/src/main/java/fun/qu_an/minecraft/asyncparticles/client/util/ThreadUtil.java

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package fun.qu_an.minecraft.asyncparticles.client.util;
22

3+
import com.mojang.blaze3d.systems.RenderSystem;
34
import fun.qu_an.minecraft.asyncparticles.client.AsyncRenderer;
45
import fun.qu_an.minecraft.asyncparticles.client.AsyncTicker;
56

@@ -37,4 +38,8 @@ public static boolean isOnParticleRendererThread() {
3738
public static boolean isOnParticleTickerThread() {
3839
return Thread.currentThread() instanceof ForkJoinWorkerThread t && t.getPool() == AsyncTicker.EXECUTOR;
3940
}
41+
42+
public static boolean isOnClientTickThread() {
43+
return RenderSystem.isOnRenderThread() || isOnParticleTickerThread();
44+
}
4045
}

common/src/main/resources/asyncparticles-common.accesswidener

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ accessible field net/minecraft/client/particle/Particle INITIAL_AABB Lnet/minecr
4646
accessible class net/minecraft/client/multiplayer/ClientChunkCache$Storage
4747
accessible field net/minecraft/client/multiplayer/ClientChunkCache$Storage chunks Ljava/util/concurrent/atomic/AtomicReferenceArray;
4848
accessible field net/minecraft/world/level/chunk/ChunkAccess blockEntities Ljava/util/Map;
49+
transitive-mutable field net/minecraft/world/level/chunk/ChunkAccess blockEntities Ljava/util/Map;
4950
accessible method net/minecraft/client/particle/Particle getLightColor (F)I
5051
accessible method com/mojang/blaze3d/vertex/PoseStack$Pose <init> (Lorg/joml/Matrix4f;Lorg/joml/Matrix3f;)V
5152
accessible field net/minecraft/client/particle/ParticleEngine textureManager Lnet/minecraft/client/renderer/texture/TextureManager;

common/src/main/resources/asyncparticles-common.mixins.json

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
"refmap": "asyncparticles-common-refmap.json",
77
"plugin": "fun.qu_an.minecraft.asyncparticles.client.APMixinPlugin",
88
"client": [
9+
"MixinChunkAccess",
10+
"MixinLevelChunk",
911
"MixinActiveProfiler",
1012
"MixinClientChunkCache",
1113
"MixinClientLevel",
@@ -14,6 +16,7 @@
1416
"MixinLevelRenderer",
1517
"MixinLevelRenderer_EarlyFrustum",
1618
"MixinMinecraft",
19+
"MixinMixinParticle_NoRefreshLightCache",
1720
"MixinMultiBufferSource$BufferSource",
1821
"MixinParticle",
1922
"MixinParticle_LightCache",

fabric/build.gradle

+4
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ dependencies {
5959
localRuntime "org.anarres:jcpp:1.4.14"
6060
modLocalRuntime "maven.modrinth:indium:1.0.36+mc1.20.1"
6161

62+
if (rootProject.optimizations_only == 'true') {
63+
return
64+
}
65+
6266
/* Valkyrien Skies */
6367
modLocalRuntime "net.fabricmc:fabric-language-kotlin:1.13.1+kotlin.2.1.10"
6468
modLocalRuntime "maven.modrinth:valkyrien-skies:1.20.1-fabric-2.3.0-beta.5"

0 commit comments

Comments
 (0)