queue) {
14 | super.makeSharedCopy(queue);
15 | }
16 |
17 | /**
18 | * @reason Use double-buffering to avoid copying
19 | * @author JellySquid
20 | */
21 | @SuppressWarnings("ConstantConditions")
22 | @Overwrite
23 | public BlockLightStorage.Data copy() {
24 | // This will be called immediately by LightStorage in the constructor
25 | // We can take advantage of this fact to initialize our extra properties here without additional hacks
26 | if (!this.isInitialized()) {
27 | this.init();
28 | }
29 |
30 | BlockLightStorage.Data data = new BlockLightStorage.Data(this.arrays);
31 | ((SharedBlockLightData) (Object) data).makeSharedCopy(this.getUpdateQueue());
32 |
33 | return data;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/net/caffeinemc/phosphor/mixin/chunk/light/MixinChunkBlockLightProvider.java:
--------------------------------------------------------------------------------
1 | package net.caffeinemc.phosphor.mixin.chunk.light;
2 |
3 | import net.caffeinemc.phosphor.common.chunk.light.BlockLightStorageAccess;
4 | import net.caffeinemc.phosphor.common.util.LightUtil;
5 | import net.caffeinemc.phosphor.common.util.math.DirectionHelper;
6 | import net.minecraft.block.BlockState;
7 | import net.minecraft.util.math.BlockPos;
8 | import net.minecraft.util.math.ChunkSectionPos;
9 | import net.minecraft.util.math.Direction;
10 | import net.minecraft.util.shape.VoxelShape;
11 | import net.minecraft.world.chunk.light.ChunkBlockLightProvider;
12 | import org.spongepowered.asm.mixin.Final;
13 | import org.spongepowered.asm.mixin.Mixin;
14 | import org.spongepowered.asm.mixin.Overwrite;
15 | import org.spongepowered.asm.mixin.Shadow;
16 |
17 | import static net.minecraft.util.math.ChunkSectionPos.getSectionCoord;
18 |
19 | @Mixin(ChunkBlockLightProvider.class)
20 | public abstract class MixinChunkBlockLightProvider extends MixinChunkLightProvider {
21 | @Shadow
22 | protected abstract int getLightSourceLuminance(long blockPos);
23 |
24 | @Shadow
25 | @Final
26 | private static Direction[] DIRECTIONS;
27 |
28 | /**
29 | * @reason Use optimized variant
30 | * @author JellySquid
31 | */
32 | @Override
33 | @Overwrite
34 | public int getPropagatedLevel(long fromId, long toId, int currentLevel) {
35 | return this.getPropagatedLevel(fromId, null, toId, currentLevel);
36 | }
37 |
38 | /**
39 | * This breaks up the call to method_20479 into smaller parts so we do not have to pass a mutable heap object
40 | * to the method in order to extract the light result. This has a few other advantages, allowing us to:
41 | * - Avoid the de-optimization that occurs from allocating and passing a heap object
42 | * - Avoid unpacking coordinates twice for both the call to method_20479 and method_20710.
43 | * - Avoid the the specific usage of AtomicInteger, which has additional overhead for the atomic get/set operations.
44 | * - Avoid checking if the checked block is opaque twice.
45 | * - Avoid a redundant block state lookup by re-using {@param fromState}
46 | *
47 | * The rest of the implementation has been otherwise copied from vanilla, but is optimized to avoid constantly
48 | * (un)packing coordinates and to use an optimized direction lookup function.
49 | *
50 | * @param fromState The re-usable block state at position {@param fromId}
51 | * @author JellySquid
52 | */
53 | @Override
54 | public int getPropagatedLevel(long fromId, BlockState fromState, long toId, int currentLevel) {
55 | if (toId == Long.MAX_VALUE) {
56 | return 15;
57 | } else if (fromId == Long.MAX_VALUE && ((BlockLightStorageAccess) this.lightStorage).isLightEnabled(ChunkSectionPos.fromBlockPos(toId))) {
58 | // Disable blocklight sources before initial lighting
59 | return currentLevel + 15 - this.getLightSourceLuminance(toId);
60 | } else if (currentLevel >= 15) {
61 | return currentLevel;
62 | }
63 |
64 | int toX = BlockPos.unpackLongX(toId);
65 | int toY = BlockPos.unpackLongY(toId);
66 | int toZ = BlockPos.unpackLongZ(toId);
67 |
68 | int fromX = BlockPos.unpackLongX(fromId);
69 | int fromY = BlockPos.unpackLongY(fromId);
70 | int fromZ = BlockPos.unpackLongZ(fromId);
71 |
72 | Direction dir = DirectionHelper.getVecDirection(toX - fromX, toY - fromY, toZ - fromZ);
73 |
74 | if (dir != null) {
75 | BlockState toState = this.getBlockStateForLighting(toX, toY, toZ);
76 |
77 | if (toState == null) {
78 | return 15;
79 | }
80 |
81 | int newLevel = this.getSubtractedLight(toState, toX, toY, toZ);
82 |
83 | if (newLevel >= 15) {
84 | return 15;
85 | }
86 |
87 | if (fromState == null) {
88 | fromState = this.getBlockStateForLighting(fromX, fromY, fromZ);
89 | }
90 |
91 | VoxelShape aShape = this.getOpaqueShape(fromState, fromX, fromY, fromZ, dir);
92 | VoxelShape bShape = this.getOpaqueShape(toState, toX, toY, toZ, dir.getOpposite());
93 |
94 | if (!LightUtil.unionCoversFullCube(aShape, bShape)) {
95 | return currentLevel + Math.max(1, newLevel);
96 | }
97 | }
98 |
99 | return 15;
100 | }
101 |
102 | /**
103 | * Avoids constantly (un)packing coordinates. This strictly copies vanilla's implementation.
104 | * @reason Use faster implementation
105 | * @author JellySquid
106 | */
107 | @Overwrite
108 | public void propagateLevel(long id, int targetLevel, boolean mergeAsMin) {
109 | int x = BlockPos.unpackLongX(id);
110 | int y = BlockPos.unpackLongY(id);
111 | int z = BlockPos.unpackLongZ(id);
112 |
113 | long chunk = ChunkSectionPos.asLong(getSectionCoord(x), getSectionCoord(y), getSectionCoord(z));
114 |
115 | BlockState state = this.getBlockStateForLighting(x, y, z);
116 |
117 | for (Direction dir : DIRECTIONS) {
118 | int adjX = x + dir.getOffsetX();
119 | int adjY = y + dir.getOffsetY();
120 | int adjZ = z + dir.getOffsetZ();
121 |
122 | long adjChunk = ChunkSectionPos.asLong(getSectionCoord(adjX), getSectionCoord(adjY), getSectionCoord(adjZ));
123 |
124 | if ((chunk == adjChunk) || this.hasSection(adjChunk)) {
125 | this.propagateLevel(id, state, BlockPos.asLong(adjX, adjY, adjZ), targetLevel, mergeAsMin);
126 | }
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/main/java/net/caffeinemc/phosphor/mixin/chunk/light/MixinChunkLightProvider.java:
--------------------------------------------------------------------------------
1 | package net.caffeinemc.phosphor.mixin.chunk.light;
2 |
3 | import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
4 | import net.caffeinemc.phosphor.common.block.BlockStateLightInfo;
5 | import net.caffeinemc.phosphor.common.block.BlockStateLightInfoAccess;
6 | import net.caffeinemc.phosphor.common.chunk.light.InitialLightingAccess;
7 | import net.caffeinemc.phosphor.common.chunk.light.LightProviderUpdateTracker;
8 | import net.caffeinemc.phosphor.common.chunk.light.LightStorageAccess;
9 | import net.minecraft.block.BlockState;
10 | import net.minecraft.block.Blocks;
11 | import net.minecraft.util.math.BlockPos;
12 | import net.minecraft.util.math.ChunkPos;
13 | import net.minecraft.util.math.ChunkSectionPos;
14 | import net.minecraft.util.math.Direction;
15 | import net.minecraft.util.shape.VoxelShape;
16 | import net.minecraft.util.shape.VoxelShapes;
17 | import net.minecraft.world.chunk.Chunk;
18 | import net.minecraft.world.chunk.ChunkProvider;
19 | import net.minecraft.world.chunk.ChunkSection;
20 | import net.minecraft.world.chunk.light.ChunkLightProvider;
21 | import net.minecraft.world.chunk.light.LightStorage;
22 | import org.spongepowered.asm.mixin.Final;
23 | import org.spongepowered.asm.mixin.Mixin;
24 | import org.spongepowered.asm.mixin.Overwrite;
25 | import org.spongepowered.asm.mixin.Shadow;
26 | import org.spongepowered.asm.mixin.Unique;
27 | import org.spongepowered.asm.mixin.injection.At;
28 | import org.spongepowered.asm.mixin.injection.Inject;
29 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
30 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
31 |
32 | import java.util.Arrays;
33 | import java.util.BitSet;
34 |
35 | @Mixin(ChunkLightProvider.class)
36 | public abstract class MixinChunkLightProvider
37 | extends MixinLevelPropagator implements InitialLightingAccess, LightProviderUpdateTracker {
38 | private static final BlockState DEFAULT_STATE = Blocks.AIR.getDefaultState();
39 | private static final ChunkSection[] EMPTY_SECTION_ARRAY = new ChunkSection[0];
40 |
41 | @Shadow
42 | @Final
43 | protected BlockPos.Mutable reusableBlockPos;
44 |
45 | @Shadow
46 | @Final
47 | protected ChunkProvider chunkProvider;
48 |
49 | private final long[] cachedChunkPos = new long[2];
50 | private final ChunkSection[][] cachedChunkSections = new ChunkSection[2][];
51 |
52 | private final Long2ObjectOpenHashMap buckets = new Long2ObjectOpenHashMap<>();
53 |
54 | private long prevChunkBucketKey = ChunkPos.MARKER;
55 | private BitSet prevChunkBucketSet;
56 |
57 | @Inject(method = "clearChunkCache", at = @At("RETURN"))
58 | @SuppressWarnings("ConstantConditions")
59 | private void onCleanup(CallbackInfo ci) {
60 | // This callback may be executed from the constructor above, and the object won't be initialized then
61 | if (this.cachedChunkPos != null) {
62 | Arrays.fill(this.cachedChunkPos, ChunkPos.MARKER);
63 | Arrays.fill(this.cachedChunkSections, null);
64 | }
65 | }
66 |
67 | @Unique
68 | protected boolean hasSection(final long sectionPos) {
69 | return ((LightStorageAccess) this.lightStorage).callHasSection(sectionPos);
70 | }
71 |
72 | // [VanillaCopy] method_20479
73 | /**
74 | * Returns the BlockState which represents the block at the specified coordinates in the world. This may return
75 | * a different BlockState than what actually exists at the coordinates (such as if it is out of bounds), but will
76 | * always represent a state with valid light properties for that coordinate.
77 | */
78 | @Unique
79 | protected BlockState getBlockStateForLighting(int x, int y, int z) {
80 | final long chunkPos = ChunkPos.toLong(x >> 4, z >> 4);
81 |
82 | for (int i = 0; i < 2; i++) {
83 | if (this.cachedChunkPos[i] == chunkPos) {
84 | return this.getBlockStateFromSection(this.cachedChunkSections[i], x, y, z);
85 | }
86 | }
87 |
88 | return this.getBlockStateForLightingUncached(x, y, z);
89 | }
90 |
91 | private BlockState getBlockStateForLightingUncached(int x, int y, int z) {
92 | return this.getBlockStateFromSection(this.getAndCacheChunkSections(x >> 4, z >> 4), x, y, z);
93 | }
94 |
95 | private BlockState getBlockStateFromSection(final ChunkSection[] sections, final int x, final int y, final int z) {
96 | final int index = this.chunkProvider.getWorld().getSectionIndex(y);
97 |
98 | if (index >= 0 && index < sections.length) {
99 | final ChunkSection section = sections[index];
100 |
101 | if (!section.isEmpty()) {
102 | return section.getBlockState(x & 15, y & 15, z & 15);
103 | }
104 | }
105 |
106 | return DEFAULT_STATE;
107 | }
108 |
109 | private ChunkSection[] getAndCacheChunkSections(int x, int z) {
110 | final Chunk chunk = (Chunk) this.chunkProvider.getChunk(x, z);
111 | final ChunkSection[] sections = chunk != null ? chunk.getSectionArray() : EMPTY_SECTION_ARRAY;
112 |
113 | final ChunkSection[][] cachedSections = this.cachedChunkSections;
114 | cachedSections[1] = cachedSections[0];
115 | cachedSections[0] = sections;
116 |
117 | final long[] cachedCoords = this.cachedChunkPos;
118 | cachedCoords[1] = cachedCoords[0];
119 | cachedCoords[0] = ChunkPos.toLong(x, z);
120 |
121 | return sections;
122 | }
123 |
124 | // [VanillaCopy] method_20479
125 | /**
126 | * Returns the amount of light which is blocked at the specified coordinates by the BlockState.
127 | */
128 | @Unique
129 | protected int getSubtractedLight(BlockState state, int x, int y, int z) {
130 | BlockStateLightInfo info = ((BlockStateLightInfoAccess) state).getLightInfo();
131 |
132 | if (info != null) {
133 | return info.getLightSubtracted();
134 | } else {
135 | return this.getSubtractedLightFallback(state, x, y, z);
136 | }
137 | }
138 |
139 | private int getSubtractedLightFallback(BlockState state, int x, int y, int z) {
140 | return state.getBlock().getOpacity(state, this.chunkProvider.getWorld(), this.reusableBlockPos.set(x, y, z));
141 | }
142 |
143 | // [VanillaCopy] method_20479
144 | /**
145 | * Returns the VoxelShape of a block for lighting without making a second call to
146 | * {@link #getBlockStateForLighting(int, int, int)}.
147 | */
148 | @Unique
149 | protected VoxelShape getOpaqueShape(BlockState state, int x, int y, int z, Direction dir) {
150 | if (state != null && state.hasSidedTransparency()) {
151 | BlockStateLightInfo info = ((BlockStateLightInfoAccess) state).getLightInfo();
152 |
153 | if (info != null) {
154 | VoxelShape[] extrudedFaces = info.getExtrudedFaces();
155 |
156 | if (extrudedFaces != null) {
157 | return extrudedFaces[dir.ordinal()];
158 | }
159 | } else {
160 | return this.getOpaqueShapeFallback(state, x, y, z, dir);
161 | }
162 | }
163 |
164 | return VoxelShapes.empty();
165 | }
166 |
167 | private VoxelShape getOpaqueShapeFallback(BlockState state, int x, int y, int z, Direction dir) {
168 | return VoxelShapes.extrudeFace(state.getCullingShape(this.chunkProvider.getWorld(), this.reusableBlockPos.set(x, y, z)), dir);
169 | }
170 |
171 | /**
172 | * The vanilla implementation for removing pending light updates requires iterating over either every queued light
173 | * update (<8K checks) or every block position within a sub-chunk (16^3 checks). This is painfully slow and results
174 | * in a tremendous amount of CPU time being spent here when chunks are unloaded on the client and server.
175 | *
176 | * To work around this, we maintain a bit-field of queued updates by chunk position so we can simply select every
177 | * light update within a section without excessive iteration. The bit-field only requires 64 bytes of memory per
178 | * section with queued updates, and does not require expensive hashing in order to track updates within it. In order
179 | * to avoid as much overhead as possible when looking up a bit-field for a given chunk section, the previous lookup
180 | * is cached and used where possible. The integer key for each bucket can be computed by performing a simple bit
181 | * mask over the already-encoded block position value.
182 | */
183 | @Override
184 | public void cancelUpdatesForChunk(long sectionPos) {
185 | long key = getBucketKeyForSection(sectionPos);
186 | BitSet bits = this.removeChunkBucket(key);
187 |
188 | if (bits != null && !bits.isEmpty()) {
189 | int startX = ChunkSectionPos.unpackX(sectionPos) << 4;
190 | int startY = ChunkSectionPos.unpackY(sectionPos) << 4;
191 | int startZ = ChunkSectionPos.unpackZ(sectionPos) << 4;
192 |
193 | for (int i = bits.nextSetBit(0); i != -1; i = bits.nextSetBit(i + 1)) {
194 | int x = (i >> 8) & 15;
195 | int y = (i >> 4) & 15;
196 | int z = i & 15;
197 |
198 | this.removePendingUpdate(BlockPos.asLong(startX + x, startY + y, startZ + z));
199 | }
200 | }
201 | }
202 |
203 | @Override
204 | protected void onPendingUpdateRemoved(long blockPos) {
205 | long key = getBucketKeyForBlock(blockPos);
206 |
207 | BitSet bits;
208 |
209 | if (this.prevChunkBucketKey == key) {
210 | bits = this.prevChunkBucketSet;
211 | } else {
212 | bits = this.buckets.get(key);
213 |
214 | if (bits == null) {
215 | return;
216 | }
217 | }
218 |
219 | bits.clear(getLocalIndex(blockPos));
220 |
221 | if (bits.isEmpty()) {
222 | this.removeChunkBucket(key);
223 | }
224 | }
225 |
226 | @Override
227 | protected void onPendingUpdateAdded(long blockPos) {
228 | long key = getBucketKeyForBlock(blockPos);
229 |
230 | BitSet bits;
231 |
232 | if (this.prevChunkBucketKey == key) {
233 | bits = this.prevChunkBucketSet;
234 | } else {
235 | bits = this.buckets.get(key);
236 |
237 | if (bits == null) {
238 | this.buckets.put(key, bits = new BitSet(16 * 16 * 16));
239 | }
240 |
241 | this.prevChunkBucketKey = key;
242 | this.prevChunkBucketSet = bits;
243 | }
244 |
245 | bits.set(getLocalIndex(blockPos));
246 | }
247 |
248 | // Used to mask a long-encoded block position into a bucket key by dropping the first 4 bits of each component
249 | private static final long BLOCK_TO_BUCKET_KEY_MASK = ~BlockPos.asLong(15, 15, 15);
250 |
251 | private long getBucketKeyForBlock(long blockPos) {
252 | return blockPos & BLOCK_TO_BUCKET_KEY_MASK;
253 | }
254 |
255 | private long getBucketKeyForSection(long sectionPos) {
256 | return BlockPos.asLong(ChunkSectionPos.unpackX(sectionPos) << 4, ChunkSectionPos.unpackY(sectionPos) << 4, ChunkSectionPos.unpackZ(sectionPos) << 4);
257 | }
258 |
259 | private BitSet removeChunkBucket(long key) {
260 | BitSet set = this.buckets.remove(key);
261 |
262 | if (this.prevChunkBucketSet == set) {
263 | this.prevChunkBucketKey = ChunkPos.MARKER;
264 | this.prevChunkBucketSet = null;
265 | }
266 |
267 | return set;
268 | }
269 |
270 | // Finds the bit-flag index of a local position within a chunk section
271 | private static int getLocalIndex(long blockPos) {
272 | int x = BlockPos.unpackLongX(blockPos) & 15;
273 | int y = BlockPos.unpackLongY(blockPos) & 15;
274 | int z = BlockPos.unpackLongZ(blockPos) & 15;
275 |
276 | return (x << 8) | (y << 4) | z;
277 | }
278 |
279 | @Shadow
280 | @Final
281 | protected LightStorage> lightStorage;
282 |
283 | @Shadow
284 | protected void resetLevel(long id) {}
285 |
286 | /**
287 | * @author PhiPro
288 | * @reason Re-implement completely. Change specification of the method.
289 | * Now controls both source light and light updates. Disabling now additionally removes all light data associated to the chunk.
290 | */
291 | @SuppressWarnings("ConstantConditions")
292 | @Overwrite
293 | public void setColumnEnabled(final ChunkPos pos, final boolean enabled) {
294 | final long chunkPos = ChunkSectionPos.withZeroY(ChunkSectionPos.asLong(pos.x, 0, pos.z));
295 | final LightStorageAccess lightStorage = (LightStorageAccess) this.lightStorage;
296 |
297 | if (enabled) {
298 | lightStorage.invokeSetColumnEnabled(chunkPos, true);
299 | lightStorage.enableLightUpdates(chunkPos);
300 | } else {
301 | lightStorage.disableChunkLight(chunkPos, (ChunkLightProvider, ?>) (Object) this);
302 | }
303 | }
304 |
305 | @Override
306 | public void enableSourceLight(final long chunkPos) {
307 | ((LightStorageAccess) this.lightStorage).invokeSetColumnEnabled(chunkPos, true);
308 | }
309 |
310 | @Override
311 | public void enableLightUpdates(final long chunkPos) {
312 | ((LightStorageAccess) this.lightStorage).enableLightUpdates(chunkPos);
313 | }
314 |
315 | @Inject(
316 | method = "doLightUpdates(IZZ)I",
317 | at = @At(
318 | value = "INVOKE",
319 | target = "Lnet/minecraft/world/chunk/light/LightStorage;notifyChanges()V"
320 | )
321 | )
322 | private void runCleanups(final CallbackInfoReturnable ci) {
323 | ((LightStorageAccess) this.lightStorage).runCleanups();
324 | }
325 | }
326 |
--------------------------------------------------------------------------------
/src/main/java/net/caffeinemc/phosphor/mixin/chunk/light/MixinChunkSkyLightProvider.java:
--------------------------------------------------------------------------------
1 | package net.caffeinemc.phosphor.mixin.chunk.light;
2 |
3 | import net.caffeinemc.phosphor.common.chunk.light.SkyLightStorageAccess;
4 | import net.caffeinemc.phosphor.common.util.LightUtil;
5 | import net.caffeinemc.phosphor.common.util.math.ChunkSectionPosHelper;
6 | import net.caffeinemc.phosphor.common.util.math.DirectionHelper;
7 | import net.minecraft.block.BlockState;
8 | import net.minecraft.block.Blocks;
9 | import net.minecraft.util.math.BlockPos;
10 | import net.minecraft.util.math.ChunkSectionPos;
11 | import net.minecraft.util.math.Direction;
12 | import net.minecraft.util.shape.VoxelShape;
13 | import net.minecraft.util.shape.VoxelShapes;
14 | import net.minecraft.world.chunk.light.ChunkSkyLightProvider;
15 | import org.spongepowered.asm.mixin.Final;
16 | import org.spongepowered.asm.mixin.Mixin;
17 | import org.spongepowered.asm.mixin.Overwrite;
18 | import org.spongepowered.asm.mixin.Shadow;
19 |
20 | import static net.minecraft.util.math.ChunkSectionPos.getLocalCoord;
21 | import static net.minecraft.util.math.ChunkSectionPos.getSectionCoord;
22 |
23 | @Mixin(ChunkSkyLightProvider.class)
24 | public abstract class MixinChunkSkyLightProvider extends MixinChunkLightProvider {
25 | private static final BlockState AIR_BLOCK = Blocks.AIR.getDefaultState();
26 |
27 | @Shadow
28 | @Final
29 | private static Direction[] HORIZONTAL_DIRECTIONS;
30 |
31 | @Shadow
32 | @Final
33 | private static Direction[] DIRECTIONS;
34 |
35 | /**
36 | * @author JellySquid
37 | * @reason Use optimized method below
38 | */
39 | @Override
40 | @Overwrite
41 | public int getPropagatedLevel(long fromId, long toId, int currentLevel) {
42 | return this.getPropagatedLevel(fromId, null, toId, currentLevel);
43 | }
44 |
45 | /**
46 | * This breaks up the call to method_20479 into smaller parts so we do not have to pass a mutable heap object
47 | * to the method in order to extract the light result. This has a few other advantages, allowing us to:
48 | * - Avoid the de-optimization that occurs from allocating and passing a heap object
49 | * - Avoid unpacking coordinates twice for both the call to method_20479 and method_20710.
50 | * - Avoid the the specific usage of AtomicInteger, which has additional overhead for the atomic get/set operations.
51 | * - Avoid checking if the checked block is opaque twice.
52 | * - Avoid a redundant block state lookup by re-using {@param fromState}
53 | *
54 | * The rest of the implementation has been otherwise copied from vanilla, but is optimized to avoid constantly
55 | * (un)packing coordinates and to use an optimized direction lookup function.
56 | *
57 | * @param fromState The re-usable block state at position {@param fromId}
58 | */
59 | @Override
60 | public int getPropagatedLevel(long fromId, BlockState fromState, long toId, int currentLevel) {
61 | if (toId == Long.MAX_VALUE || fromId == Long.MAX_VALUE) {
62 | return 15;
63 | } else if (currentLevel >= 15) {
64 | return currentLevel;
65 | }
66 |
67 | int toX = BlockPos.unpackLongX(toId);
68 | int toY = BlockPos.unpackLongY(toId);
69 | int toZ = BlockPos.unpackLongZ(toId);
70 |
71 | BlockState toState = this.getBlockStateForLighting(toX, toY, toZ);
72 |
73 | int fromX = BlockPos.unpackLongX(fromId);
74 | int fromY = BlockPos.unpackLongY(fromId);
75 | int fromZ = BlockPos.unpackLongZ(fromId);
76 |
77 | if (fromState == null) {
78 | fromState = this.getBlockStateForLighting(fromX, fromY, fromZ);
79 | }
80 |
81 | // Most light updates will happen between two empty air blocks, so use this to assume some properties
82 | boolean airPropagation = toState == AIR_BLOCK && fromState == AIR_BLOCK;
83 | boolean verticalOnly = fromX == toX && fromZ == toZ;
84 |
85 | // The direction the light update is propagating
86 | Direction dir = DirectionHelper.getVecDirection(toX - fromX, toY - fromY, toZ - fromZ);
87 |
88 | if (dir == null) {
89 | throw new IllegalStateException(String.format("Light was spread in illegal direction %d, %d, %d", Integer.signum(toX - fromX), Integer.signum(toY - fromY), Integer.signum(toZ - fromZ)));
90 | }
91 |
92 | // Shape comparison checks are only meaningful if the blocks involved have non-empty shapes
93 | // If we're comparing between air blocks, this is meaningless
94 | if (!airPropagation) {
95 | VoxelShape toShape = this.getOpaqueShape(toState, toX, toY, toZ, dir.getOpposite());
96 |
97 | if (toShape != VoxelShapes.fullCube()) {
98 | VoxelShape fromShape = this.getOpaqueShape(fromState, fromX, fromY, fromZ, dir);
99 |
100 | if (LightUtil.unionCoversFullCube(fromShape, toShape)) {
101 | return 15;
102 | }
103 | }
104 | }
105 |
106 | int out = this.getSubtractedLight(toState, toX, toY, toZ);
107 |
108 | if (out == 0 && currentLevel == 0 && verticalOnly && fromY > toY) {
109 | return 0;
110 | }
111 |
112 | return currentLevel + Math.max(1, out);
113 | }
114 |
115 | /**
116 | * A few key optimizations are made here, in particular:
117 | * - The code avoids un-packing coordinates as much as possible and stores the results into local variables.
118 | * - When necessary, coordinate re-packing is reduced to the minimum number of operations. Most of them can be reduced
119 | * to only updating the Y-coordinate versus re-computing the entire integer.
120 | * - Coordinate re-packing is removed where unnecessary (such as when only comparing the Y-coordinate of two positions)
121 | * - A special propagation method is used that allows the BlockState at {@param id} to be passed, allowing the code
122 | * which follows to simply re-use it instead of redundantly retrieving another block state.
123 | *
124 | * Additionally this implements the cleanups discussed in MC-196542. In particular:
125 | * - This always passes adjacent positions to {@link #propagateLevel(long, BlockState, long, int, boolean)}
126 | * - This reduces map lookups in the skylight optimization code
127 | *
128 | * @reason Use faster implementation
129 | * @author JellySquid, PhiPro
130 | */
131 | @Overwrite
132 | public void propagateLevel(long id, int targetLevel, boolean mergeAsMin) {
133 | long chunkId = ChunkSectionPos.fromBlockPos(id);
134 |
135 | int x = BlockPos.unpackLongX(id);
136 | int y = BlockPos.unpackLongY(id);
137 | int z = BlockPos.unpackLongZ(id);
138 |
139 | int localX = getLocalCoord(x);
140 | int localY = getLocalCoord(y);
141 | int localZ = getLocalCoord(z);
142 |
143 | BlockState fromState = this.getBlockStateForLighting(x, y, z);
144 |
145 | // Fast-path: Use much simpler logic if we do not need to access adjacent chunks
146 | if (localX > 0 && localX < 15 && localY > 0 && localY < 15 && localZ > 0 && localZ < 15) {
147 | for (Direction dir : DIRECTIONS) {
148 | this.propagateLevel(id, fromState, BlockPos.asLong(x + dir.getOffsetX(), y + dir.getOffsetY(), z + dir.getOffsetZ()), targetLevel, mergeAsMin);
149 | }
150 |
151 | return;
152 | }
153 |
154 | int chunkY = getSectionCoord(y);
155 | int chunkOffsetY = 0;
156 |
157 | // Skylight optimization: Try to find bottom-most non-empty chunk
158 | if (localY == 0) {
159 | while (!this.hasSection(ChunkSectionPos.offset(chunkId, 0, -chunkOffsetY - 1, 0))
160 | && ((SkyLightStorageAccess) this.lightStorage).callIsAboveMinHeight(chunkY - chunkOffsetY - 1)) {
161 | ++chunkOffsetY;
162 | }
163 | }
164 |
165 | int belowY = y + (-1 - chunkOffsetY * 16);
166 | int belowChunkY = getSectionCoord(belowY);
167 |
168 | if (chunkY == belowChunkY || this.hasSection(ChunkSectionPosHelper.updateYLong(chunkId, belowChunkY))) {
169 | // MC-196542: Pass adjacent source position
170 | BlockState state = chunkY == belowChunkY ? fromState : AIR_BLOCK;
171 | this.propagateLevel(BlockPos.asLong(x, belowY + 1, z), state, BlockPos.asLong(x, belowY, z), targetLevel, mergeAsMin);
172 | }
173 |
174 | int aboveY = y + 1;
175 | int aboveChunkY = getSectionCoord(aboveY);
176 |
177 | if (chunkY == aboveChunkY || this.hasSection(ChunkSectionPosHelper.updateYLong(chunkId, aboveChunkY))) {
178 | this.propagateLevel(id, fromState, BlockPos.asLong(x, aboveY, z), targetLevel, mergeAsMin);
179 | }
180 |
181 | for (Direction dir : HORIZONTAL_DIRECTIONS) {
182 | int adjX = x + dir.getOffsetX();
183 | int adjZ = z + dir.getOffsetZ();
184 |
185 | long offsetId = BlockPos.asLong(adjX, y, adjZ);
186 | long offsetChunkId = ChunkSectionPos.fromBlockPos(offsetId);
187 |
188 | boolean isWithinOriginChunk = chunkId == offsetChunkId;
189 |
190 | if (isWithinOriginChunk || this.hasSection(offsetChunkId)) {
191 | this.propagateLevel(id, fromState, offsetId, targetLevel, mergeAsMin);
192 | }
193 |
194 | if (isWithinOriginChunk) {
195 | continue;
196 | }
197 |
198 | // MC-196542: First iterate over sections to reduce map lookups
199 | for (int offsetChunkY = chunkY - 1; offsetChunkY > belowChunkY; --offsetChunkY) {
200 | if (!this.hasSection(ChunkSectionPosHelper.updateYLong(offsetChunkId, offsetChunkY))) {
201 | continue;
202 | }
203 |
204 | for (int offsetY = 15; offsetY >= 0; --offsetY) {
205 | int adjY = ChunkSectionPos.getBlockCoord(offsetChunkY) + offsetY;
206 | offsetId = BlockPos.asLong(adjX, adjY, adjZ);
207 |
208 | this.propagateLevel(BlockPos.asLong(x, adjY, z), AIR_BLOCK, offsetId, targetLevel, mergeAsMin);
209 | }
210 | }
211 | }
212 | }
213 |
214 | /**
215 | * @author PhiPro
216 | * @reason MC-196542: There is no need to reset any level other than the directly requested position.
217 | */
218 | @Overwrite
219 | public void resetLevel(long id) {
220 | super.resetLevel(id);
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/src/main/java/net/caffeinemc/phosphor/mixin/chunk/light/MixinChunkToNibbleArrayMap.java:
--------------------------------------------------------------------------------
1 | package net.caffeinemc.phosphor.mixin.chunk.light;
2 |
3 | import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
4 | import net.caffeinemc.phosphor.common.util.collections.DoubleBufferedLong2ObjectHashMap;
5 | import net.minecraft.world.chunk.ChunkNibbleArray;
6 | import net.minecraft.world.chunk.ChunkToNibbleArrayMap;
7 | import org.spongepowered.asm.mixin.Final;
8 | import org.spongepowered.asm.mixin.Mixin;
9 | import org.spongepowered.asm.mixin.Overwrite;
10 | import org.spongepowered.asm.mixin.Shadow;
11 | import org.spongepowered.asm.mixin.Unique;
12 |
13 | @Mixin(ChunkToNibbleArrayMap.class)
14 | public abstract class MixinChunkToNibbleArrayMap {
15 | @Shadow
16 | private boolean cacheEnabled;
17 |
18 | @Shadow
19 | @Final
20 | private long[] cachePositions;
21 |
22 | @Shadow
23 | @Final
24 | private ChunkNibbleArray[] cacheArrays;
25 |
26 | @Shadow
27 | public abstract void clearCache();
28 |
29 | @Shadow
30 | @Final
31 | protected Long2ObjectOpenHashMap arrays;
32 |
33 | @Unique
34 | private DoubleBufferedLong2ObjectHashMap queue;
35 | @Unique
36 | private boolean isShared;
37 | // Indicates whether or not the extended data structures have been initialized
38 | @Unique
39 | private boolean init;
40 |
41 | @Unique
42 | protected boolean isShared() {
43 | return this.isShared;
44 | }
45 |
46 | @Unique
47 | protected boolean isInitialized() {
48 | return this.init;
49 | }
50 |
51 | /**
52 | * @reason Allow shared access, avoid copying
53 | * @author JellySquid
54 | */
55 | @Overwrite
56 | public void replaceWithCopy(long pos) {
57 | this.checkExclusiveOwner();
58 |
59 | this.queue.putSync(pos, this.queue.getSync(pos).copy());
60 |
61 | this.clearCache();
62 | }
63 |
64 | /**
65 | * @reason Allow shared access, avoid copying
66 | * @author JellySquid
67 | */
68 | @SuppressWarnings("OverwriteModifiers")
69 | @Overwrite
70 | public ChunkNibbleArray get(long pos) {
71 | if (this.cacheEnabled) {
72 | // Hoist array field access out of the loop to allow the JVM to drop bounds checks
73 | long[] cachePositions = this.cachePositions;
74 |
75 | for(int i = 0; i < cachePositions.length; ++i) {
76 | if (pos == cachePositions[i]) {
77 | return this.cacheArrays[i];
78 | }
79 | }
80 | }
81 |
82 | // Move to a separate method to help the JVM inline methods
83 | return this.getUncached(pos);
84 | }
85 |
86 | @Unique
87 | private ChunkNibbleArray getUncached(long pos) {
88 | ChunkNibbleArray array;
89 |
90 | if (this.isShared) {
91 | array = this.queue.getAsync(pos);
92 | } else {
93 | array = this.queue.getSync(pos);
94 | }
95 |
96 | if (array == null) {
97 | return null;
98 | }
99 |
100 | if (this.cacheEnabled) {
101 | long[] cachePositions = this.cachePositions;
102 | ChunkNibbleArray[] cacheArrays = this.cacheArrays;
103 |
104 | for(int i = cacheArrays.length - 1; i > 0; --i) {
105 | cachePositions[i] = cachePositions[i - 1];
106 | cacheArrays[i] = cacheArrays[i - 1];
107 | }
108 |
109 | cachePositions[0] = pos;
110 | cacheArrays[0] = array;
111 | }
112 |
113 | return array;
114 | }
115 |
116 | /**
117 | * @reason Allow shared access, avoid copying
118 | * @author JellySquid
119 | */
120 | @Overwrite
121 | public void put(long pos, ChunkNibbleArray data) {
122 | this.checkExclusiveOwner();
123 |
124 | this.queue.putSync(pos, data);
125 | }
126 |
127 | /**
128 | * @reason Allow shared access, avoid copying
129 | * @author JellySquid
130 | */
131 | @SuppressWarnings("OverwriteModifiers")
132 | @Overwrite
133 | public ChunkNibbleArray removeChunk(long chunkPos) {
134 | this.checkExclusiveOwner();
135 |
136 | return this.queue.removeSync(chunkPos);
137 | }
138 |
139 | /**
140 | * @reason Allow shared access, avoid copying
141 | * @author JellySquid
142 | */
143 | @Overwrite
144 | public boolean containsKey(long chunkPos) {
145 | if (this.isShared) {
146 | return this.queue.getAsync(chunkPos) != null;
147 | } else {
148 | return this.queue.containsSync(chunkPos);
149 | }
150 | }
151 |
152 | /**
153 | * Check if the light array table is exclusively owned (not shared). If not, an exception is thrown to catch the
154 | * invalid state. Synchronous writes can only occur while the table is exclusively owned by the writer/actor thread.
155 | */
156 | @Unique
157 | protected void checkExclusiveOwner() {
158 | if (this.isShared) {
159 | throw new IllegalStateException("Tried to synchronously write to light data array table after it was made shareable");
160 | }
161 | }
162 |
163 | /**
164 | * Returns the queue of pending changes for this map.
165 | */
166 | @Unique
167 | protected DoubleBufferedLong2ObjectHashMap getUpdateQueue() {
168 | return this.queue;
169 | }
170 |
171 | /**
172 | * Makes this map a shared copy of another. The shared copy cannot be directly written into.
173 | */
174 | protected void makeSharedCopy(final DoubleBufferedLong2ObjectHashMap queue) {
175 | this.queue = queue;
176 | this.isShared = true;
177 |
178 | this.queue.flushChangesSync();
179 |
180 | // Copies of this map should not re-initialize the data structures!
181 | this.init = true;
182 | }
183 |
184 | /**
185 | * Initializes the data for this extended chunk array map. This should only be called once with the initialization
186 | * of a subtype.
187 | * @throws IllegalStateException If the map has already been initialized
188 | */
189 | @Unique
190 | protected void init() {
191 | if (this.init) {
192 | throw new IllegalStateException("Map already initialized");
193 | }
194 |
195 | this.queue = new DoubleBufferedLong2ObjectHashMap<>();
196 | this.init = true;
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/src/main/java/net/caffeinemc/phosphor/mixin/chunk/light/MixinLevelPropagator.java:
--------------------------------------------------------------------------------
1 | package net.caffeinemc.phosphor.mixin.chunk.light;
2 |
3 | import it.unimi.dsi.fastutil.longs.Long2ByteMap;
4 | import net.caffeinemc.phosphor.common.chunk.light.LevelPropagatorAccess;
5 | import net.minecraft.block.BlockState;
6 | import net.minecraft.util.math.MathHelper;
7 | import net.minecraft.world.chunk.light.LevelPropagator;
8 | import org.spongepowered.asm.mixin.Final;
9 | import org.spongepowered.asm.mixin.Mixin;
10 | import org.spongepowered.asm.mixin.Shadow;
11 | import org.spongepowered.asm.mixin.Unique;
12 | import org.spongepowered.asm.mixin.gen.Invoker;
13 | import org.spongepowered.asm.mixin.injection.At;
14 | import org.spongepowered.asm.mixin.injection.Redirect;
15 |
16 | @Mixin(LevelPropagator.class)
17 | public abstract class MixinLevelPropagator implements LevelPropagatorAccess {
18 | @Shadow
19 | @Final
20 | private Long2ByteMap pendingUpdates;
21 |
22 | @Shadow
23 | protected abstract int getLevel(long id);
24 |
25 | @Shadow
26 | @Final
27 | private int levelCount;
28 |
29 | @Shadow
30 | protected abstract int getPropagatedLevel(long sourceId, long targetId, int level);
31 |
32 | @Shadow
33 | protected abstract void updateLevel(long sourceId, long id, int level, int currentLevel, int pendingLevel, boolean decrease);
34 |
35 | @Shadow
36 | private volatile boolean hasPendingUpdates;
37 |
38 | @Shadow
39 | private int minPendingLevel;
40 |
41 | @Override
42 | @Invoker("propagateLevel")
43 | public abstract void invokePropagateLevel(long sourceId, long targetId, int level, boolean decrease);
44 |
45 | @Shadow
46 | protected abstract void propagateLevel(long sourceId, long targetId, int level, boolean decrease);
47 |
48 | @Shadow
49 | protected abstract void removePendingUpdate(long id);
50 |
51 | @Override
52 | public void propagateLevel(long sourceId, long targetId, boolean decrease) {
53 | this.propagateLevel(sourceId, targetId, this.getLevel(sourceId), decrease);
54 | }
55 |
56 | @Override
57 | public void checkForUpdates() {
58 | this.hasPendingUpdates = this.minPendingLevel < this.levelCount;
59 | }
60 |
61 | // [VanillaCopy] LevelPropagator#propagateLevel(long, long, int, boolean)
62 | /**
63 | * Mirrors {@link LevelPropagator#propagateLevel(long, int, boolean)}, but allows a block state to be passed to
64 | * prevent subsequent lookup later.
65 | */
66 | @Unique
67 | protected void propagateLevel(long sourceId, BlockState sourceState, long targetId, int level, boolean decrease) {
68 | int pendingLevel = this.pendingUpdates.get(targetId) & 0xFF;
69 |
70 | int propagatedLevel = this.getPropagatedLevel(sourceId, sourceState, targetId, level);
71 | int clampedLevel = MathHelper.clamp(propagatedLevel, 0, this.levelCount - 1);
72 |
73 | if (decrease) {
74 | this.updateLevel(sourceId, targetId, clampedLevel, this.getLevel(targetId), pendingLevel, true);
75 |
76 | return;
77 | }
78 |
79 | boolean flag;
80 | int resultLevel;
81 |
82 | if (pendingLevel == 0xFF) {
83 | flag = true;
84 | resultLevel = MathHelper.clamp(this.getLevel(targetId), 0, this.levelCount - 1);
85 | } else {
86 | resultLevel = pendingLevel;
87 | flag = false;
88 | }
89 |
90 | if (clampedLevel == resultLevel) {
91 | this.updateLevel(sourceId, targetId, this.levelCount - 1, flag ? resultLevel : this.getLevel(targetId), pendingLevel, false);
92 | }
93 | }
94 |
95 | /**
96 | * Copy of {@link #getPropagatedLevel(long, long, int)} but with an additional argument to pass the
97 | * block state belonging to {@param sourceId}.
98 | */
99 | @Unique
100 | protected int getPropagatedLevel(long sourceId, BlockState sourceState, long targetId, int level) {
101 | return this.getPropagatedLevel(sourceId, targetId, level);
102 | }
103 |
104 | @Redirect(method = { "removePendingUpdate(JIIZ)V", "applyPendingUpdates" }, at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/longs/Long2ByteMap;remove(J)B", remap = false))
105 | private byte redirectRemovePendingUpdate(Long2ByteMap map, long key) {
106 | byte ret = map.remove(key);
107 |
108 | if (ret != map.defaultReturnValue()) {
109 | this.onPendingUpdateRemoved(key);
110 | }
111 |
112 | return ret;
113 | }
114 |
115 | @Redirect(method = "addPendingUpdate", at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/longs/Long2ByteMap;put(JB)B", remap = false))
116 | private byte redirectAddPendingUpdate(Long2ByteMap map, long key, byte value) {
117 | byte ret = map.put(key, value);
118 |
119 | if (ret == map.defaultReturnValue()) {
120 | this.onPendingUpdateAdded(key);
121 | }
122 |
123 | return ret;
124 | }
125 |
126 | @Unique
127 | protected void onPendingUpdateAdded(long key) {
128 | // NO-OP
129 | }
130 |
131 | @Unique
132 | protected void onPendingUpdateRemoved(long key) {
133 | // NO-OP
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/main/java/net/caffeinemc/phosphor/mixin/chunk/light/MixinLightStorage.java:
--------------------------------------------------------------------------------
1 | package net.caffeinemc.phosphor.mixin.chunk.light;
2 |
3 | import it.unimi.dsi.fastutil.ints.Int2ByteMap;
4 | import it.unimi.dsi.fastutil.ints.Int2ByteOpenHashMap;
5 | import it.unimi.dsi.fastutil.ints.IntArrayList;
6 | import it.unimi.dsi.fastutil.ints.IntIterable;
7 | import it.unimi.dsi.fastutil.ints.IntIterator;
8 | import it.unimi.dsi.fastutil.ints.IntIterators;
9 | import it.unimi.dsi.fastutil.ints.IntList;
10 | import it.unimi.dsi.fastutil.longs.Long2IntMap;
11 | import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
12 | import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
13 | import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
14 | import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
15 | import it.unimi.dsi.fastutil.longs.LongIterator;
16 | import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
17 | import it.unimi.dsi.fastutil.longs.LongSet;
18 | import it.unimi.dsi.fastutil.objects.ObjectIterator;
19 | import net.caffeinemc.phosphor.common.chunk.light.IReadonly;
20 | import net.caffeinemc.phosphor.common.chunk.light.LevelPropagatorAccess;
21 | import net.caffeinemc.phosphor.common.chunk.light.LightProviderUpdateTracker;
22 | import net.caffeinemc.phosphor.common.chunk.light.LightStorageAccess;
23 | import net.caffeinemc.phosphor.common.util.chunk.light.EmptyChunkNibbleArray;
24 | import net.caffeinemc.phosphor.common.util.math.ChunkSectionPosHelper;
25 | import net.minecraft.util.Util;
26 | import net.minecraft.util.math.BlockPos;
27 | import net.minecraft.util.math.ChunkSectionPos;
28 | import net.minecraft.util.math.Direction;
29 | import net.minecraft.world.LightType;
30 | import net.minecraft.world.SectionDistanceLevelPropagator;
31 | import net.minecraft.world.chunk.ChunkNibbleArray;
32 | import net.minecraft.world.chunk.ChunkProvider;
33 | import net.minecraft.world.chunk.ChunkToNibbleArrayMap;
34 | import net.minecraft.world.chunk.light.ChunkLightProvider;
35 | import net.minecraft.world.chunk.light.LightStorage;
36 | import org.objectweb.asm.Opcodes;
37 | import org.spongepowered.asm.mixin.Final;
38 | import org.spongepowered.asm.mixin.Mixin;
39 | import org.spongepowered.asm.mixin.Mutable;
40 | import org.spongepowered.asm.mixin.Overwrite;
41 | import org.spongepowered.asm.mixin.Shadow;
42 | import org.spongepowered.asm.mixin.Unique;
43 | import org.spongepowered.asm.mixin.gen.Invoker;
44 | import org.spongepowered.asm.mixin.injection.At;
45 | import org.spongepowered.asm.mixin.injection.Inject;
46 | import org.spongepowered.asm.mixin.injection.Redirect;
47 | import org.spongepowered.asm.mixin.injection.Slice;
48 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
49 |
50 | import java.util.concurrent.locks.StampedLock;
51 | import java.util.function.LongPredicate;
52 |
53 | @Mixin(LightStorage.class)
54 | public abstract class MixinLightStorage extends SectionDistanceLevelPropagator implements LightStorageAccess {
55 | protected MixinLightStorage() {
56 | super(0, 0, 0);
57 | }
58 |
59 | @Shadow
60 | @Final
61 | protected ChunkToNibbleArrayMap> storage;
62 |
63 | @Mutable
64 | @Shadow
65 | @Final
66 | protected LongSet dirtySections;
67 |
68 | @Mutable
69 | @Shadow
70 | @Final
71 | protected LongSet notifySections;
72 |
73 | @Shadow
74 | protected abstract int getLevel(long id);
75 |
76 | @Mutable
77 | @Shadow
78 | @Final
79 | protected LongSet readySections;
80 |
81 | @Mutable
82 | @Shadow
83 | @Final
84 | protected LongSet markedReadySections;
85 |
86 | @Mutable
87 | @Shadow
88 | @Final
89 | protected LongSet markedNotReadySections;
90 |
91 | @Shadow
92 | protected abstract void onLoadSection(long blockPos);
93 |
94 | @Shadow
95 | protected volatile boolean hasLightUpdates;
96 |
97 | @Shadow
98 | protected volatile ChunkToNibbleArrayMap> uncachedStorage;
99 |
100 | @Shadow
101 | protected abstract ChunkNibbleArray createSection(long pos);
102 |
103 | @Shadow
104 | @Final
105 | protected Long2ObjectMap queuedSections;
106 |
107 | @Shadow
108 | protected abstract boolean hasLightUpdates();
109 |
110 | @Shadow
111 | protected abstract void onUnloadSection(long l);
112 |
113 | @Shadow
114 | @Final
115 | private static Direction[] DIRECTIONS;
116 |
117 | @Shadow
118 | protected abstract void removeSection(ChunkLightProvider, ?> storage, long blockChunkPos);
119 |
120 | @Shadow
121 | protected abstract ChunkNibbleArray getLightSection(long sectionPos, boolean cached);
122 |
123 | @Shadow
124 | @Final
125 | private ChunkProvider chunkProvider;
126 |
127 | @Shadow
128 | @Final
129 | private LightType lightType;
130 |
131 | @Shadow
132 | @Final
133 | private LongSet queuedEdgeSections;
134 |
135 | // A multiset of 'interesting' sections per chunk. Determines which sections to iterate over when enabling/disabling chunks, for initial lighting, etc.
136 | // This class tracks non-trivial lightmaps, (scheduled) non-empty chunks and nonOptimizable sections resp. Vanilla lightmaps
137 | @Unique
138 | private final Long2ObjectMap trackedSectionsByChunk = new Long2ObjectOpenHashMap<>();
139 |
140 | @Unique
141 | protected void trackSection(final long sectionPos) {
142 | this.trackSection(ChunkSectionPos.withZeroY(sectionPos), ChunkSectionPos.unpackY(sectionPos));
143 | }
144 |
145 | @Unique
146 | protected void trackSection(final long chunkPos, final int y) {
147 | final Int2ByteMap sections = this.trackedSectionsByChunk.computeIfAbsent(chunkPos, __ -> new Int2ByteOpenHashMap());
148 | sections.put(y, (byte) (sections.get(y) + 1));
149 | }
150 |
151 | @Unique
152 | protected void untrackSection(final long sectionPos) {
153 | this.untrackSection(ChunkSectionPos.withZeroY(sectionPos), ChunkSectionPos.unpackY(sectionPos));
154 | }
155 |
156 | @Unique
157 | protected void untrackSection(final long chunkPos, final int y) {
158 | final Int2ByteMap sections = this.trackedSectionsByChunk.get(chunkPos);
159 |
160 | final byte count = (byte) (sections.get(y) - 1);
161 |
162 | if (count == 0) {
163 | sections.remove(y);
164 |
165 | if (sections.isEmpty()) {
166 | this.trackedSectionsByChunk.remove(chunkPos);
167 | }
168 | } else {
169 | sections.put(y, count);
170 | }
171 | }
172 |
173 | @Unique
174 | protected IntIterator getTrackedSections(final long chunkPos) {
175 | final Int2ByteMap sections = this.trackedSectionsByChunk.get(chunkPos);
176 |
177 | return sections == null ? IntIterators.EMPTY_ITERATOR : IntIterators.wrap(sections.keySet().toIntArray());
178 | }
179 |
180 | /**
181 | * The lock which wraps {@link #uncachedStorage}. Locking should always be
182 | * performed when accessing values in the aforementioned storage.
183 | */
184 | @Unique
185 | protected final StampedLock uncachedLightArraysLock = new StampedLock();
186 |
187 | /**
188 | * Replaces the two set of calls to unpack the XYZ coordinates from the input to just one, storing the result as local
189 | * variables.
190 | *
191 | * Additionally, this handles lookups for positions without an associated lightmap.
192 | *
193 | * @reason Use faster implementation
194 | * @author JellySquid
195 | */
196 | @Overwrite
197 | public int get(long blockPos) {
198 | int x = BlockPos.unpackLongX(blockPos);
199 | int y = BlockPos.unpackLongY(blockPos);
200 | int z = BlockPos.unpackLongZ(blockPos);
201 |
202 | long chunk = ChunkSectionPos.asLong(ChunkSectionPos.getSectionCoord(x), ChunkSectionPos.getSectionCoord(y), ChunkSectionPos.getSectionCoord(z));
203 |
204 | ChunkNibbleArray array = this.getLightSection(chunk, true);
205 |
206 | if (array == null) {
207 | return this.getLightWithoutLightmap(blockPos);
208 | }
209 |
210 | return array.get(ChunkSectionPos.getLocalCoord(x), ChunkSectionPos.getLocalCoord(y), ChunkSectionPos.getLocalCoord(z));
211 | }
212 |
213 | /**
214 | * An extremely important optimization is made here in regards to adding items to the pending notification set. The
215 | * original implementation attempts to add the coordinate of every chunk which contains a neighboring block position
216 | * even though a huge number of loop iterations will simply map to block positions within the same updating chunk.
217 | *
218 | * Our implementation here avoids this by pre-calculating the min/max chunk coordinates so we can iterate over only
219 | * the relevant chunk positions once. This reduces what would always be 27 iterations to just 1-8 iterations.
220 | *
221 | * @reason Use faster implementation
222 | * @author JellySquid
223 | */
224 | @Overwrite
225 | public void set(long blockPos, int value) {
226 | int x = BlockPos.unpackLongX(blockPos);
227 | int y = BlockPos.unpackLongY(blockPos);
228 | int z = BlockPos.unpackLongZ(blockPos);
229 |
230 | long chunkPos = ChunkSectionPos.asLong(x >> 4, y >> 4, z >> 4);
231 |
232 | final ChunkNibbleArray lightmap = this.getOrAddLightmap(chunkPos);
233 | final int oldVal = lightmap.get(x & 15, y & 15, z & 15);
234 |
235 | this.beforeLightChange(blockPos, oldVal, value, lightmap);
236 | this.changeLightmapComplexity(chunkPos, this.getLightmapComplexityChange(blockPos, oldVal, value, lightmap));
237 |
238 | if (this.dirtySections.add(chunkPos)) {
239 | this.storage.replaceWithCopy(chunkPos);
240 | }
241 |
242 | ChunkNibbleArray nibble = this.getLightSection(chunkPos, true);
243 | nibble.set(x & 15, y & 15, z & 15, value);
244 |
245 | for (int z2 = (z - 1) >> 4; z2 <= (z + 1) >> 4; ++z2) {
246 | for (int x2 = (x - 1) >> 4; x2 <= (x + 1) >> 4; ++x2) {
247 | for (int y2 = (y - 1) >> 4; y2 <= (y + 1) >> 4; ++y2) {
248 | this.notifySections.add(ChunkSectionPos.asLong(x2, y2, z2));
249 | }
250 | }
251 | }
252 | }
253 |
254 | /**
255 | * @author PhiPro
256 | * @reason Move large parts of the logic to other methods
257 | */
258 | @Overwrite
259 | public void setLevel(long id, int level) {
260 | int oldLevel = this.getLevel(id);
261 |
262 | if (oldLevel != 0 && level == 0) {
263 | this.readySections.add(id);
264 | this.markedReadySections.remove(id);
265 | }
266 |
267 | if (oldLevel == 0 && level != 0) {
268 | this.readySections.remove(id);
269 | this.markedNotReadySections.remove(id);
270 | this.untrackSection(id);
271 | }
272 |
273 | if (oldLevel >= 2 && level < 2) {
274 | this.nonOptimizableSections.add(id);
275 |
276 | if (this.enabledChunks.contains(ChunkSectionPos.withZeroY(id))) {
277 | if (!this.vanillaLightmapsToRemove.remove(id)) {
278 | if (this.getLightSection(id, true) == null) {
279 | this.storage.put(id, this.createTrivialVanillaLightmap(id));
280 | this.dirtySections.add(id);
281 | this.storage.clearCache();
282 | }
283 |
284 | this.trackSection(id);
285 | }
286 | } else {
287 | this.trackSection(id);
288 | }
289 | }
290 |
291 | if (oldLevel < 2 && level >= 2) {
292 | this.nonOptimizableSections.remove(id);
293 |
294 | if (this.enabledChunks.contains(ChunkSectionPos.withZeroY(id))) {
295 | final ChunkNibbleArray lightmap = this.getLightSection(id, true);
296 |
297 | if (lightmap != null && ((IReadonly) lightmap).isReadonly()) {
298 | this.vanillaLightmapsToRemove.add(id);
299 | this.markForLightUpdates();
300 | } else {
301 | this.untrackSection(id);
302 | }
303 | } else {
304 | this.untrackSection(id);
305 | }
306 | }
307 | }
308 |
309 | /**
310 | * @reason Drastically improve efficiency by making removals O(n) instead of O(16*16*16)
311 | * @author JellySquid
312 | */
313 | @Inject(method = "removeSection", at = @At("HEAD"), cancellable = true)
314 | protected void preRemoveSection(ChunkLightProvider, ?> provider, long pos, CallbackInfo ci) {
315 | if (provider instanceof LightProviderUpdateTracker) {
316 | ((LightProviderUpdateTracker) provider).cancelUpdatesForChunk(pos);
317 |
318 | ci.cancel();
319 | }
320 | }
321 |
322 | @Override
323 | public void runCleanups() {
324 | this.runCleanups(null);
325 | }
326 |
327 | @Unique
328 | protected void runCleanups(final ChunkLightProvider, ?> lightProvider) {
329 | if (!this.hasLightUpdates) {
330 | return;
331 | }
332 |
333 | this.removeTrivialLightmaps(lightProvider);
334 | this.removeVanillaLightmaps(lightProvider);
335 |
336 | if (lightProvider == null) {
337 | this.checkForUpdates();
338 | }
339 | }
340 |
341 | /**
342 | * @author PhiPro
343 | * @reason Re-implement completely
344 | */
345 | @Overwrite
346 | public void updateLight(final ChunkLightProvider, ?> lightProvider, final boolean doSkylight, final boolean skipEdgeLightPropagation) {
347 | if (!this.hasLightUpdates()) {
348 | return;
349 | }
350 |
351 | this.initializeChunks();
352 | this.addQueuedLightmaps(lightProvider);
353 | this.runCleanups(lightProvider);
354 |
355 | final LongIterator it;
356 |
357 | if (!skipEdgeLightPropagation) {
358 | it = this.queuedSections.keySet().iterator();
359 | } else {
360 | it = this.queuedEdgeSections.iterator();
361 | }
362 |
363 | while (it.hasNext()) {
364 | this.updateSection(lightProvider, it.nextLong());
365 | }
366 |
367 | this.queuedEdgeSections.clear();
368 | this.queuedSections.clear();
369 |
370 | // Vanilla would normally iterate back over the map of light arrays to remove those we worked on, but
371 | // that is unneeded now because we removed them earlier.
372 |
373 | this.hasLightUpdates = false;
374 | }
375 |
376 | /**
377 | * @reason Avoid integer boxing, reduce map lookups and iteration as much as possible
378 | * @author JellySquid
379 | */
380 | @Overwrite
381 | private void updateSection(final ChunkLightProvider, ?> chunkLightProvider, long pos) {
382 | if (this.hasSection(pos)) {
383 | final LevelPropagatorAccess levelPropagator = (LevelPropagatorAccess) chunkLightProvider;
384 |
385 | int x = ChunkSectionPos.getBlockCoord(ChunkSectionPos.unpackX(pos));
386 | int y = ChunkSectionPos.getBlockCoord(ChunkSectionPos.unpackY(pos));
387 | int z = ChunkSectionPos.getBlockCoord(ChunkSectionPos.unpackZ(pos));
388 |
389 | for (Direction dir : DIRECTIONS) {
390 | long adjPos = ChunkSectionPos.offset(pos, dir);
391 |
392 | // Avoid updating initializing chunks unnecessarily
393 | if (this.queuedSections.containsKey(adjPos)) {
394 | continue;
395 | }
396 |
397 | // If there is no light data for this section yet, skip it
398 | if (!this.hasSection(adjPos)) {
399 | continue;
400 | }
401 |
402 | for (int u1 = 0; u1 < 16; ++u1) {
403 | for (int u2 = 0; u2 < 16; ++u2) {
404 | long a;
405 | long b;
406 |
407 | switch (dir) {
408 | case DOWN:
409 | a = BlockPos.asLong(x + u2, y, z + u1);
410 | b = BlockPos.asLong(x + u2, y - 1, z + u1);
411 | break;
412 | case UP:
413 | a = BlockPos.asLong(x + u2, y + 15, z + u1);
414 | b = BlockPos.asLong(x + u2, y + 16, z + u1);
415 | break;
416 | case NORTH:
417 | a = BlockPos.asLong(x + u1, y + u2, z);
418 | b = BlockPos.asLong(x + u1, y + u2, z - 1);
419 | break;
420 | case SOUTH:
421 | a = BlockPos.asLong(x + u1, y + u2, z + 15);
422 | b = BlockPos.asLong(x + u1, y + u2, z + 16);
423 | break;
424 | case WEST:
425 | a = BlockPos.asLong(x, y + u1, z + u2);
426 | b = BlockPos.asLong(x - 1, y + u1, z + u2);
427 | break;
428 | case EAST:
429 | a = BlockPos.asLong(x + 15, y + u1, z + u2);
430 | b = BlockPos.asLong(x + 16, y + u1, z + u2);
431 | break;
432 | default:
433 | continue;
434 | }
435 |
436 | levelPropagator.propagateLevel(a, b, false);
437 | levelPropagator.propagateLevel(b, a, false);
438 | }
439 | }
440 | }
441 |
442 | levelPropagator.checkForUpdates();
443 | }
444 | }
445 |
446 | /**
447 | * @reason
448 | * @author JellySquid
449 | */
450 | @Overwrite
451 | public void notifyChanges() {
452 | if (!this.dirtySections.isEmpty()) {
453 | // This could result in changes being flushed to various arrays, so write lock.
454 | long stamp = this.uncachedLightArraysLock.writeLock();
455 |
456 | try {
457 | // This only performs a shallow copy compared to before
458 | ChunkToNibbleArrayMap> map = this.storage.copy();
459 | map.disableCache();
460 |
461 | this.uncachedStorage = map;
462 | } finally {
463 | this.uncachedLightArraysLock.unlockWrite(stamp);
464 | }
465 |
466 | this.dirtySections.clear();
467 | }
468 |
469 | if (!this.notifySections.isEmpty()) {
470 | LongIterator it = this.notifySections.iterator();
471 |
472 | while(it.hasNext()) {
473 | long pos = it.nextLong();
474 |
475 | this.chunkProvider.onLightUpdate(this.lightType, ChunkSectionPos.from(pos));
476 | }
477 |
478 | this.notifySections.clear();
479 | }
480 | }
481 |
482 | /**
483 | * Returns the light value for a position that does not have an associated lightmap.
484 | * This is analogous to {@link LightStorage#getLight(long)}, but uses the cached light data.
485 | */
486 | @Unique
487 | protected int getLightWithoutLightmap(final long blockPos) {
488 | return 0;
489 | }
490 |
491 | /**
492 | * This method has an equivalent effect to calling {@link #onLoadSection(long)} on all lightmaps in the specified chunk.
493 | * Additional data can also be set up before enabling the chunk.
494 | */
495 | @Unique
496 | protected void beforeChunkEnabled(final long chunkPos) {
497 | for (final IntIterator it = this.getTrackedSections(chunkPos); it.hasNext(); ) {
498 | final long sectionPos = ChunkSectionPosHelper.updateYLong(chunkPos, it.nextInt());
499 |
500 | if (this.hasLightmap(sectionPos)) {
501 | this.onLoadSection(sectionPos);
502 | }
503 | }
504 | }
505 |
506 | /**
507 | * This method has an equivalent effect to calling {@link #onUnloadSection(long)} on all (already removed) lightmaps in the specified chunk.
508 | * Additional data can also be removed upon disabling the chunk.
509 | */
510 | @Unique
511 | protected void afterChunkDisabled(final long chunkPos, final IntIterable removedLightmaps) {
512 | for (IntIterator it = removedLightmaps.iterator(); it.hasNext(); ) {
513 | this.onUnloadSection(ChunkSectionPosHelper.updateYLong(chunkPos, it.nextInt()));
514 | }
515 | }
516 |
517 | @Unique
518 | protected final LongSet enabledChunks = new LongOpenHashSet();
519 | @Unique
520 | protected final Long2IntMap lightmapComplexities = Util.make(new Long2IntOpenHashMap(), map -> map.defaultReturnValue(-1));
521 |
522 | @Unique
523 | private final LongSet markedEnabledChunks = new LongOpenHashSet();
524 | @Unique
525 | private final LongSet trivialLightmaps = new LongOpenHashSet();
526 | @Unique
527 | private final LongSet vanillaLightmapsToRemove = new LongOpenHashSet();
528 |
529 | // This is put here since the relevant methods to overwrite are located in LightStorage
530 | @Unique
531 | protected final LongSet nonOptimizableSections = new LongOpenHashSet();
532 |
533 | @Unique
534 | protected ChunkNibbleArray getOrAddLightmap(final long sectionPos) {
535 | ChunkNibbleArray lightmap = this.getLightSection(sectionPos, true);
536 |
537 | if (lightmap == null) {
538 | lightmap = this.createSection(sectionPos);
539 | } else {
540 | if (((IReadonly) lightmap).isReadonly()) {
541 | lightmap = lightmap.copy();
542 |
543 | if (this.vanillaLightmapsToRemove.remove(sectionPos)) {
544 | this.untrackSection(sectionPos);
545 | }
546 | } else {
547 | return lightmap;
548 | }
549 | }
550 |
551 | this.storage.put(sectionPos, lightmap);
552 | this.trackSection(sectionPos);
553 | this.dirtySections.add(sectionPos);
554 | this.storage.clearCache();
555 |
556 | this.onLoadSection(sectionPos);
557 | this.setLightmapComplexity(sectionPos, 0);
558 |
559 | return lightmap;
560 | }
561 |
562 | @Unique
563 | protected void setLightmapComplexity(final long sectionPos, final int complexity) {
564 | int oldComplexity = this.lightmapComplexities.put(sectionPos, complexity);
565 |
566 | if (oldComplexity == 0) {
567 | this.trivialLightmaps.remove(sectionPos);
568 | }
569 |
570 | if (complexity == 0) {
571 | this.trivialLightmaps.add(sectionPos);
572 | this.markForLightUpdates();
573 | }
574 | }
575 |
576 | @Unique
577 | private void checkForUpdates() {
578 | this.hasLightUpdates = !this.trivialLightmaps.isEmpty() || !this.vanillaLightmapsToRemove.isEmpty() || !this.markedEnabledChunks.isEmpty() || !this.queuedSections.isEmpty();
579 | }
580 |
581 | @Unique
582 | private void markForLightUpdates() {
583 | // Avoid volatile writes
584 | if (!this.hasLightUpdates) {
585 | this.hasLightUpdates = true;
586 | }
587 | }
588 |
589 | @Unique
590 | protected void changeLightmapComplexity(final long sectionPos, final int amount) {
591 | int complexity = this.lightmapComplexities.get(sectionPos);
592 |
593 | if (complexity == 0) {
594 | this.trivialLightmaps.remove(sectionPos);
595 | }
596 |
597 | complexity += amount;
598 | this.lightmapComplexities.put(sectionPos, complexity);
599 |
600 | if (complexity == 0) {
601 | this.trivialLightmaps.add(sectionPos);
602 | this.markForLightUpdates();
603 | }
604 | }
605 |
606 | @Unique
607 | protected ChunkNibbleArray getLightmap(final long sectionPos) {
608 | final ChunkNibbleArray lightmap = this.getLightSection(sectionPos, true);
609 | return lightmap == null || ((IReadonly) lightmap).isReadonly() ? null : lightmap;
610 | }
611 |
612 | @Unique
613 | protected boolean hasLightmap(final long sectionPos) {
614 | return this.getLightmap(sectionPos) != null;
615 | }
616 |
617 | /**
618 | * Set up lightmaps and adjust complexities as needed for the given light change.
619 | * Additional data for the given blockPos
itself need to be updated manually, whereas the main lightmap complexity will be updated automatically.
620 | */
621 | @Unique
622 | protected void beforeLightChange(final long blockPos, final int oldVal, final int newVal, final ChunkNibbleArray lightmap) {
623 | }
624 |
625 | @Unique
626 | protected int getLightmapComplexityChange(final long blockPos, final int oldVal, final int newVal, final ChunkNibbleArray lightmap) {
627 | return 0;
628 | }
629 |
630 | /**
631 | * Set up lightmaps and adjust complexities as needed for the given lightmap change.
632 | * If oldLightmap == null
, then {@link #onLoadSection(long)} will be called later on. Otherwise, additional data for the given sectionPos
itself need to be updated manually.
633 | * The main lightmap complexity will always be updated automatically.
634 | */
635 | @Unique
636 | protected void beforeLightmapChange(final long sectionPos, final ChunkNibbleArray oldLightmap, final ChunkNibbleArray newLightmap) {
637 | }
638 |
639 | @Unique
640 | protected int getInitialLightmapComplexity(final long sectionPos, final ChunkNibbleArray lightmap) {
641 | return 0;
642 | }
643 |
644 | @Invoker("hasSection")
645 | @Override
646 | public abstract boolean callHasSection(long sectionPos);
647 |
648 | /**
649 | * Determines whether light updates should be propagated into the given section.
650 | * @author PhiPro
651 | * @reason Method completely changed. Allow child mixins to properly extend this.
652 | */
653 | @Overwrite
654 | public boolean hasSection(final long sectionPos) {
655 | return this.enabledChunks.contains(ChunkSectionPos.withZeroY(sectionPos));
656 | }
657 |
658 | @Shadow
659 | protected abstract void setColumnEnabled(long columnPos, boolean enabled);
660 |
661 | /**
662 | * @author PhiPro
663 | * @reason Re-implement completely, ensuring data consistency
664 | */
665 | @Overwrite
666 | public void setSectionStatus(final long sectionPos, final boolean notReady) {
667 | if (notReady) {
668 | if (this.markedReadySections.remove(sectionPos)) {
669 | this.untrackSection(sectionPos);
670 | } else if (!this.readySections.contains(sectionPos) || !this.markedNotReadySections.add(sectionPos)) {
671 | return;
672 | }
673 |
674 | this.updateLevel(Long.MAX_VALUE, sectionPos, 2, false);
675 | } else {
676 | if (!this.markedNotReadySections.remove(sectionPos)) {
677 | if (this.readySections.contains(sectionPos) || !this.markedReadySections.add(sectionPos)) {
678 | return;
679 | }
680 |
681 | this.trackSection(sectionPos);
682 | }
683 |
684 | this.updateLevel(Long.MAX_VALUE, sectionPos, 0, true);
685 | }
686 | }
687 |
688 | @Override
689 | @Invoker("setColumnEnabled")
690 | public abstract void invokeSetColumnEnabled(final long chunkPos, final boolean enabled);
691 |
692 | @Override
693 | public void enableLightUpdates(final long chunkPos) {
694 | if (!this.enabledChunks.contains(chunkPos)){
695 | this.markedEnabledChunks.add(chunkPos);
696 | this.markForLightUpdates();
697 | }
698 | }
699 |
700 | @Unique
701 | private void initializeChunks() {
702 | this.storage.clearCache();
703 |
704 | for (final LongIterator cit = this.markedEnabledChunks.iterator(); cit.hasNext(); ) {
705 | final long chunkPos = cit.nextLong();
706 |
707 | // First need to register all lightmaps as this data is needed for calculating the initial complexity
708 |
709 | this.beforeChunkEnabled(chunkPos);
710 |
711 | // Now the initial complexities can be computed
712 |
713 | for (final IntIterator it = this.getTrackedSections(chunkPos); it.hasNext(); ) {
714 | final long sectionPos = ChunkSectionPos.asLong(ChunkSectionPos.unpackX(chunkPos), it.nextInt(), ChunkSectionPos.unpackZ(chunkPos));
715 |
716 | if (this.hasLightmap(sectionPos)) {
717 | this.setLightmapComplexity(sectionPos, this.getInitialLightmapComplexity(sectionPos, this.getLightSection(sectionPos, true)));
718 | }
719 | }
720 |
721 | // Add lightmaps for vanilla compatibility and try to recover stripped data from vanilla saves
722 |
723 | for (final IntIterator it = this.getTrackedSections(chunkPos); it.hasNext(); ) {
724 | final long sectionPos = ChunkSectionPos.asLong(ChunkSectionPos.unpackX(chunkPos), it.nextInt(), ChunkSectionPos.unpackZ(chunkPos));
725 |
726 | if (this.nonOptimizableSections.contains(sectionPos) && this.getLightSection(sectionPos, true) == null) {
727 | this.storage.put(sectionPos, this.createInitialVanillaLightmap(sectionPos));
728 | this.dirtySections.add(sectionPos);
729 | }
730 | }
731 |
732 | this.enabledChunks.add(chunkPos);
733 | }
734 |
735 | this.storage.clearCache();
736 |
737 | this.markedEnabledChunks.clear();
738 | }
739 |
740 | @Unique
741 | protected ChunkNibbleArray createInitialVanillaLightmap(final long sectionPos) {
742 | return this.createTrivialVanillaLightmap(sectionPos);
743 | }
744 |
745 | @Unique
746 | protected ChunkNibbleArray createTrivialVanillaLightmap(final long sectionPos) {
747 | return new EmptyChunkNibbleArray();
748 | }
749 |
750 | @Override
751 | public void disableChunkLight(final long chunkPos, final ChunkLightProvider, ?> lightProvider) {
752 | if (this.markedEnabledChunks.remove(chunkPos) || !this.enabledChunks.contains(chunkPos)) {
753 | for (final IntIterator it = this.getTrackedSections(chunkPos); it.hasNext(); ) {
754 | final int y = it.nextInt();
755 | final long sectionPos = ChunkSectionPos.asLong(ChunkSectionPos.unpackX(chunkPos), y, ChunkSectionPos.unpackZ(chunkPos));
756 |
757 | if (this.storage.removeChunk(sectionPos) != null) {
758 | this.untrackSection(chunkPos, y);
759 | this.dirtySections.add(sectionPos);
760 | }
761 | }
762 |
763 | this.setColumnEnabled(chunkPos, false);
764 | this.removeBlockData(chunkPos);
765 | } else {
766 | // First need to remove all pending light updates before changing any light value
767 |
768 | for (final IntIterator it = this.getTrackedSections(chunkPos); it.hasNext(); ) {
769 | final long sectionPos = ChunkSectionPos.asLong(ChunkSectionPos.unpackX(chunkPos), it.nextInt(), ChunkSectionPos.unpackZ(chunkPos));
770 |
771 | if (this.hasSection(sectionPos)) {
772 | this.removeSection(lightProvider, sectionPos);
773 | }
774 | }
775 |
776 | // Now the chunk can be disabled
777 |
778 | this.enabledChunks.remove(chunkPos);
779 |
780 | // Now lightmaps can be removed
781 |
782 | final IntList removedLightmaps = new IntArrayList();
783 |
784 | for (final IntIterator it = this.getTrackedSections(chunkPos); it.hasNext(); ) {
785 | final int y = it.nextInt();
786 | final long sectionPos = ChunkSectionPosHelper.updateYLong(chunkPos, y);
787 |
788 | if (this.removeLightmap(sectionPos)) {
789 | removedLightmaps.add(y);
790 | }
791 | }
792 |
793 | // These maps are cleared every update and should hence be small
794 | this.queuedSections.keySet().removeIf((LongPredicate) sectionPos -> ChunkSectionPos.withZeroY(sectionPos) == chunkPos);
795 | this.queuedEdgeSections.removeIf((LongPredicate) sectionPos -> ChunkSectionPos.withZeroY(sectionPos) == chunkPos);
796 |
797 | this.storage.clearCache();
798 | this.setColumnEnabled(chunkPos, false);
799 |
800 | // Remove all additional data
801 |
802 | this.afterChunkDisabled(chunkPos, removedLightmaps);
803 | this.removeBlockData(chunkPos);
804 | }
805 | }
806 |
807 | /**
808 | * Removes the lightmap associated to the provided sectionPos
, but does not call {@link #onUnloadSection(long)} or {@link ChunkToNibbleArrayMap#clearCache()}
809 | * @return Whether a lightmap was removed
810 | */
811 | @Unique
812 | protected boolean removeLightmap(final long sectionPos) {
813 | if (this.storage.removeChunk(sectionPos) == null) {
814 | return false;
815 | }
816 |
817 | this.dirtySections.add(sectionPos);
818 |
819 | if (this.lightmapComplexities.remove(sectionPos) == -1) {
820 | if (this.vanillaLightmapsToRemove.remove(sectionPos)) {
821 | this.untrackSection(sectionPos);
822 | }
823 |
824 | return false;
825 | } else {
826 | this.trivialLightmaps.remove(sectionPos);
827 | this.untrackSection(sectionPos);
828 | return true;
829 | }
830 | }
831 |
832 | @Unique
833 | private void removeBlockData(final long chunkPos) {
834 | for (final IntIterator it = this.getTrackedSections(chunkPos); it.hasNext(); ) {
835 | this.setSectionStatus(ChunkSectionPosHelper.updateYLong(chunkPos, it.nextInt()), true);
836 | }
837 | }
838 |
839 | @Unique
840 | private void removeTrivialLightmaps(final ChunkLightProvider, ?> lightProvider) {
841 | for (final LongIterator it = this.trivialLightmaps.iterator(); it.hasNext(); ) {
842 | final long sectionPos = it.nextLong();
843 |
844 | this.storage.removeChunk(sectionPos);
845 | this.lightmapComplexities.remove(sectionPos);
846 | this.untrackSection(sectionPos);
847 | this.dirtySections.add(sectionPos);
848 | }
849 |
850 | this.storage.clearCache();
851 |
852 | // Calling onUnloadSection() after removing all the lightmaps is slightly more efficient
853 |
854 | for (final LongIterator it = this.trivialLightmaps.iterator(); it.hasNext(); ) {
855 | this.onUnloadSection(it.nextLong());
856 | }
857 |
858 | // Add trivial lightmaps for vanilla compatibility
859 |
860 | for (final LongIterator it = this.trivialLightmaps.iterator(); it.hasNext(); ) {
861 | final long sectionPos = it.nextLong();
862 |
863 | if (this.nonOptimizableSections.contains(sectionPos)) {
864 | this.storage.put(sectionPos, this.createTrivialVanillaLightmap(sectionPos));
865 | }
866 | }
867 |
868 | this.storage.clearCache();
869 |
870 | // Remove pending light updates for sections that no longer support light propagations
871 |
872 | if (lightProvider != null) {
873 | for (final LongIterator it = this.trivialLightmaps.iterator(); it.hasNext(); ) {
874 | final long sectionPos = it.nextLong();
875 |
876 | if (!this.hasSection(sectionPos)) {
877 | this.removeSection(lightProvider, sectionPos);
878 | }
879 | }
880 | }
881 |
882 | this.trivialLightmaps.clear();
883 | }
884 |
885 | @Unique
886 | private void removeVanillaLightmaps(final ChunkLightProvider, ?> lightProvider) {
887 | for (final LongIterator it = this.vanillaLightmapsToRemove.iterator(); it.hasNext(); ) {
888 | final long sectionPos = it.nextLong();
889 |
890 | this.storage.removeChunk(sectionPos);
891 | this.untrackSection(sectionPos);
892 | this.dirtySections.add(sectionPos);
893 | }
894 |
895 | this.storage.clearCache();
896 |
897 | // Remove pending light updates for sections that no longer support light propagations
898 |
899 | if (lightProvider != null) {
900 | for (final LongIterator it = this.vanillaLightmapsToRemove.iterator(); it.hasNext(); ) {
901 | final long sectionPos = it.nextLong();
902 |
903 | if (!this.hasSection(sectionPos)) {
904 | this.removeSection(lightProvider, sectionPos);
905 | }
906 | }
907 | }
908 |
909 | this.vanillaLightmapsToRemove.clear();
910 | }
911 |
912 | @Unique
913 | private void addQueuedLightmaps(final ChunkLightProvider, ?> lightProvider) {
914 | for (final ObjectIterator> it = Long2ObjectMaps.fastIterator(this.queuedSections); it.hasNext(); ) {
915 | final Long2ObjectMap.Entry entry = it.next();
916 |
917 | final long sectionPos = entry.getLongKey();
918 | final ChunkNibbleArray lightmap = entry.getValue();
919 |
920 | final ChunkNibbleArray oldLightmap = this.getLightmap(sectionPos);
921 |
922 | if (lightmap != oldLightmap) {
923 | this.removeSection(lightProvider, sectionPos);
924 |
925 | this.beforeLightmapChange(sectionPos, oldLightmap, lightmap);
926 |
927 | this.storage.put(sectionPos, lightmap);
928 | this.storage.clearCache();
929 | this.dirtySections.add(sectionPos);
930 |
931 | if (oldLightmap == null) {
932 | this.trackSection(sectionPos);
933 | this.onLoadSection(sectionPos);
934 | }
935 |
936 | if (this.vanillaLightmapsToRemove.remove(sectionPos)) {
937 | this.untrackSection(sectionPos);
938 | }
939 |
940 | this.setLightmapComplexity(sectionPos, this.getInitialLightmapComplexity(sectionPos, lightmap));
941 | }
942 | }
943 | }
944 |
945 | /**
946 | * @author PhiPro
947 | * @reason Add lightmaps for disabled chunks directly to the world
948 | */
949 | @Overwrite
950 | public void enqueueSectionData(final long sectionPos, final ChunkNibbleArray array, final boolean bl) {
951 | final boolean chunkEnabled = this.enabledChunks.contains(ChunkSectionPos.withZeroY(sectionPos));
952 |
953 | if (array != null) {
954 | if (chunkEnabled) {
955 | this.queuedSections.put(sectionPos, array);
956 | this.markForLightUpdates();
957 | } else {
958 | this.storage.put(sectionPos, array);
959 | this.trackSection(sectionPos);
960 | this.dirtySections.add(sectionPos);
961 | }
962 |
963 | if (!bl) {
964 | this.queuedEdgeSections.add(sectionPos);
965 | }
966 | } else {
967 | if (chunkEnabled) {
968 | this.queuedSections.remove(sectionPos);
969 | } else {
970 | if (this.storage.removeChunk(sectionPos) != null) {
971 | this.untrackSection(sectionPos);
972 | this.dirtySections.add(sectionPos);
973 | }
974 | }
975 | }
976 | }
977 |
978 | // Queued lightmaps are only added to the world via updateLightmaps()
979 | @Redirect(
980 | method = "createSection(J)Lnet/minecraft/world/chunk/ChunkNibbleArray;",
981 | slice = @Slice(
982 | from = @At(
983 | value = "FIELD",
984 | target = "Lnet/minecraft/world/chunk/light/LightStorage;queuedSections:Lit/unimi/dsi/fastutil/longs/Long2ObjectMap;",
985 | opcode = Opcodes.GETFIELD
986 | )
987 | ),
988 | at = @At(
989 | value = "INVOKE",
990 | target = "Lit/unimi/dsi/fastutil/longs/Long2ObjectMap;get(J)Ljava/lang/Object;",
991 | ordinal = 0,
992 | remap = false
993 | )
994 | )
995 | private Object cancelLightmapLookupFromQueue(final Long2ObjectMap lightmapArray, final long pos) {
996 | return null;
997 | }
998 |
999 | @Redirect(
1000 | method = "getLevel(J)I",
1001 | slice = @Slice(
1002 | from = @At(
1003 | value = "FIELD",
1004 | target = "Lnet/minecraft/world/chunk/light/LightStorage;storage:Lnet/minecraft/world/chunk/ChunkToNibbleArrayMap;",
1005 | opcode = Opcodes.GETFIELD
1006 | )
1007 | ),
1008 | at = @At(
1009 | value = "INVOKE",
1010 | target = "Lnet/minecraft/world/chunk/ChunkToNibbleArrayMap;containsKey(J)Z",
1011 | ordinal = 0
1012 | )
1013 | )
1014 | private boolean isNonOptimizable(final ChunkToNibbleArrayMap> lightmapArray, final long sectionPos) {
1015 | return this.nonOptimizableSections.contains(sectionPos);
1016 | }
1017 | }
1018 |
--------------------------------------------------------------------------------
/src/main/java/net/caffeinemc/phosphor/mixin/chunk/light/MixinLightingProvider.java:
--------------------------------------------------------------------------------
1 | package net.caffeinemc.phosphor.mixin.chunk.light;
2 |
3 | import net.caffeinemc.phosphor.common.chunk.light.InitialLightingAccess;
4 | import net.minecraft.util.math.BlockPos;
5 | import net.minecraft.util.math.ChunkPos;
6 | import net.minecraft.util.math.ChunkSectionPos;
7 | import net.minecraft.world.HeightLimitView;
8 | import net.minecraft.world.chunk.light.ChunkLightProvider;
9 | import net.minecraft.world.chunk.light.LightingProvider;
10 | import org.spongepowered.asm.mixin.Final;
11 | import org.spongepowered.asm.mixin.Mixin;
12 | import org.spongepowered.asm.mixin.Shadow;
13 |
14 | @Mixin(LightingProvider.class)
15 | public abstract class MixinLightingProvider implements InitialLightingAccess
16 | {
17 | @Shadow
18 | @Final
19 | private ChunkLightProvider, ?> blockLightProvider;
20 |
21 | @Shadow
22 | @Final
23 | private ChunkLightProvider, ?> skyLightProvider;
24 |
25 | @Shadow
26 | public void setSectionStatus(ChunkSectionPos pos, boolean notReady) {
27 | }
28 |
29 | @Shadow
30 | public void setRetainData(ChunkPos pos, boolean retainData) {
31 | }
32 |
33 | @Shadow
34 | public void addLightSource(BlockPos pos, int level) {
35 | }
36 |
37 | @Shadow
38 | @Final
39 | protected HeightLimitView world;
40 |
41 | @Override
42 | public void enableSourceLight(final long chunkPos) {
43 | if (this.blockLightProvider != null) {
44 | ((InitialLightingAccess) this.blockLightProvider).enableSourceLight(chunkPos);
45 | }
46 |
47 | if (this.skyLightProvider != null) {
48 | ((InitialLightingAccess) this.skyLightProvider).enableSourceLight(chunkPos);
49 | }
50 | }
51 |
52 | @Override
53 | public void enableLightUpdates(final long chunkPos) {
54 | if (this.blockLightProvider != null) {
55 | ((InitialLightingAccess) this.blockLightProvider).enableLightUpdates(chunkPos);
56 | }
57 |
58 | if (this.skyLightProvider != null) {
59 | ((InitialLightingAccess) this.skyLightProvider).enableLightUpdates(chunkPos);
60 | }
61 | }
62 | }
63 |
64 |
--------------------------------------------------------------------------------
/src/main/java/net/caffeinemc/phosphor/mixin/chunk/light/MixinServerLightingProvider.java:
--------------------------------------------------------------------------------
1 | package net.caffeinemc.phosphor.mixin.chunk.light;
2 |
3 | import net.caffeinemc.phosphor.common.chunk.light.ServerLightingProviderAccess;
4 | import net.caffeinemc.phosphor.mixin.world.ThreadedAnvilChunkStorageAccess;
5 | import net.minecraft.server.world.ServerLightingProvider;
6 | import net.minecraft.server.world.ThreadedAnvilChunkStorage;
7 | import net.minecraft.util.Util;
8 | import net.minecraft.util.math.ChunkPos;
9 | import net.minecraft.util.math.ChunkSectionPos;
10 | import net.minecraft.world.chunk.Chunk;
11 | import net.minecraft.world.chunk.ChunkSection;
12 | import org.spongepowered.asm.mixin.Final;
13 | import org.spongepowered.asm.mixin.Mixin;
14 | import org.spongepowered.asm.mixin.Overwrite;
15 | import org.spongepowered.asm.mixin.Shadow;
16 |
17 | import java.util.concurrent.CompletableFuture;
18 | import java.util.function.IntSupplier;
19 |
20 | @Mixin(ServerLightingProvider.class)
21 | public abstract class MixinServerLightingProvider extends MixinLightingProvider implements ServerLightingProviderAccess {
22 | @Shadow
23 | protected abstract void enqueue(int x, int z, IntSupplier completedLevelSupplier, ServerLightingProvider.Stage stage, Runnable task);
24 |
25 | @Shadow
26 | protected abstract void enqueue(int x, int z, ServerLightingProvider.Stage stage, Runnable task);
27 |
28 | @Override
29 | public CompletableFuture setupLightmaps(final Chunk chunk) {
30 | final ChunkPos chunkPos = chunk.getPos();
31 |
32 | // This evaluates the non-empty subchunks concurrently on the lighting thread...
33 | this.enqueue(chunkPos.x, chunkPos.z, () -> 0, ServerLightingProvider.Stage.PRE_UPDATE, Util.debugRunnable(() -> {
34 | final ChunkSection[] chunkSections = chunk.getSectionArray();
35 |
36 | for (int i = 0; i < chunk.countVerticalSections(); ++i) {
37 | if (!chunkSections[i].isEmpty()) {
38 | super.setSectionStatus(ChunkSectionPos.from(chunkPos, this.world.sectionIndexToCoord(i)), false);
39 | }
40 | }
41 |
42 | if (chunk.isLightOn()) {
43 | super.enableSourceLight(ChunkSectionPos.withZeroY(ChunkSectionPos.asLong(chunkPos.x, 0, chunkPos.z)));
44 | }
45 |
46 | super.enableLightUpdates(ChunkSectionPos.withZeroY(ChunkSectionPos.asLong(chunkPos.x, 0, chunkPos.z)));
47 | },
48 | () -> "setupLightmaps " + chunkPos
49 | ));
50 |
51 | return CompletableFuture.supplyAsync(() -> {
52 | super.setRetainData(chunkPos, false);
53 | return chunk;
54 | },
55 | (runnable) -> this.enqueue(chunkPos.x, chunkPos.z, () -> 0, ServerLightingProvider.Stage.POST_UPDATE, runnable)
56 | );
57 | }
58 |
59 | @Shadow
60 | @Final
61 | private ThreadedAnvilChunkStorage chunkStorage;
62 |
63 | /**
64 | * @author PhiPro
65 | * @reason Move parts of the logic to {@link #setupLightmaps(Chunk)}
66 | */
67 | @Overwrite
68 | public CompletableFuture light(Chunk chunk, boolean excludeBlocks) {
69 | final ChunkPos chunkPos = chunk.getPos();
70 |
71 | this.enqueue(chunkPos.x, chunkPos.z, ServerLightingProvider.Stage.PRE_UPDATE, Util.debugRunnable(() -> {
72 | if (!chunk.isLightOn()) {
73 | super.enableSourceLight(ChunkSectionPos.withZeroY(ChunkSectionPos.asLong(chunkPos.x, 0, chunkPos.z)));
74 | }
75 |
76 | if (!excludeBlocks) {
77 | chunk.getLightSourcesStream().forEach(blockPos -> super.addLightSource(blockPos, chunk.getLuminance(blockPos)));
78 | }
79 | },
80 | () -> "lightChunk " + chunkPos + " " + excludeBlocks
81 | ));
82 |
83 | return CompletableFuture.supplyAsync(() -> {
84 | chunk.setLightOn(true);
85 | ((ThreadedAnvilChunkStorageAccess) this.chunkStorage).invokeReleaseLightTicket(chunkPos);
86 |
87 | return chunk;
88 | },
89 | (runnable) -> this.enqueue(chunkPos.x, chunkPos.z, ServerLightingProvider.Stage.POST_UPDATE, runnable)
90 | );
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/main/java/net/caffeinemc/phosphor/mixin/chunk/light/MixinSkyLightStorageData.java:
--------------------------------------------------------------------------------
1 | package net.caffeinemc.phosphor.mixin.chunk.light;
2 |
3 | import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
4 | import net.caffeinemc.phosphor.common.chunk.light.SharedSkyLightData;
5 | import net.caffeinemc.phosphor.common.chunk.light.SkyLightStorageDataAccess;
6 | import net.caffeinemc.phosphor.common.util.collections.DoubleBufferedLong2IntHashMap;
7 | import net.caffeinemc.phosphor.common.util.collections.DoubleBufferedLong2ObjectHashMap;
8 | import net.minecraft.world.chunk.ChunkNibbleArray;
9 | import net.minecraft.world.chunk.light.SkyLightStorage;
10 | import org.spongepowered.asm.mixin.Final;
11 | import org.spongepowered.asm.mixin.Mixin;
12 | import org.spongepowered.asm.mixin.Mutable;
13 | import org.spongepowered.asm.mixin.Overwrite;
14 | import org.spongepowered.asm.mixin.Shadow;
15 | import org.spongepowered.asm.mixin.Unique;
16 |
17 | @Mixin(SkyLightStorage.Data.class)
18 | public abstract class MixinSkyLightStorageData extends MixinChunkToNibbleArrayMap
19 | implements SkyLightStorageDataAccess, SharedSkyLightData {
20 | @Shadow
21 | int minSectionY;
22 |
23 | @Mutable
24 | @Shadow
25 | @Final
26 | Long2IntOpenHashMap columnToTopSection;
27 |
28 | // Our new double-buffered collection
29 | @Unique
30 | private DoubleBufferedLong2IntHashMap topArraySectionYQueue;
31 |
32 | @Override
33 | public void makeSharedCopy(final DoubleBufferedLong2ObjectHashMap queue, final DoubleBufferedLong2IntHashMap topSectionQueue) {
34 | this.makeSharedCopy(queue);
35 |
36 | this.topArraySectionYQueue = topSectionQueue;
37 |
38 | // We need to immediately see all updates on the thread this is being copied to
39 | this.topArraySectionYQueue.flushChangesSync();
40 | }
41 |
42 | /**
43 | * @reason Avoid copying large data structures
44 | * @author JellySquid
45 | */
46 | @SuppressWarnings("ConstantConditions")
47 | @Overwrite
48 | public SkyLightStorage.Data copy() {
49 | // This will be called immediately by LightStorage in the constructor
50 | // We can take advantage of this fact to initialize our extra properties here without additional hacks
51 | if (!this.isInitialized()) {
52 | this.init();
53 | }
54 |
55 | SkyLightStorage.Data data = new SkyLightStorage.Data(this.arrays, this.columnToTopSection, this.minSectionY);
56 | ((SharedSkyLightData) (Object) data).makeSharedCopy(this.getUpdateQueue(), this.topArraySectionYQueue);
57 |
58 | return data;
59 | }
60 |
61 | @Override
62 | protected void init() {
63 | super.init();
64 |
65 | this.topArraySectionYQueue = new DoubleBufferedLong2IntHashMap();
66 | this.columnToTopSection = this.topArraySectionYQueue.createSyncView();
67 | }
68 |
69 | @Override
70 | public int getDefaultHeight() {
71 | return this.minSectionY;
72 | }
73 |
74 | @Override
75 | public int getHeight(long pos) {
76 | if (this.isShared()) {
77 | return this.topArraySectionYQueue.getAsync(pos);
78 | } else {
79 | return this.topArraySectionYQueue.getSync(pos);
80 | }
81 | }
82 |
83 | @Override
84 | public void updateMinHeight(final int y) {
85 | this.checkExclusiveOwner();
86 |
87 | if (this.minSectionY > y) {
88 | this.minSectionY = y;
89 | this.topArraySectionYQueue.defaultReturnValueSync(this.minSectionY);
90 | }
91 | }
92 |
93 | @Override
94 | public void setHeight(final long chunkPos, final int y) {
95 | this.checkExclusiveOwner();
96 |
97 | if (y > this.minSectionY) {
98 | this.topArraySectionYQueue.putSync(chunkPos, y);
99 | } else {
100 | this.topArraySectionYQueue.removeSync(chunkPos);
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/java/net/caffeinemc/phosphor/mixin/world/ThreadedAnvilChunkStorageAccess.java:
--------------------------------------------------------------------------------
1 | package net.caffeinemc.phosphor.mixin.world;
2 |
3 | import org.spongepowered.asm.mixin.Mixin;
4 | import org.spongepowered.asm.mixin.gen.Invoker;
5 | import net.minecraft.server.world.ThreadedAnvilChunkStorage;
6 | import net.minecraft.util.math.ChunkPos;
7 |
8 | @Mixin(ThreadedAnvilChunkStorage.class)
9 | public interface ThreadedAnvilChunkStorageAccess {
10 | @Invoker("releaseLightTicket")
11 | void invokeReleaseLightTicket(ChunkPos pos);
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/resources/assets/phosphor/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaffeineMC/phosphor-fabric/4e8ed36936e6cef3264f884f40827af605aa389a/src/main/resources/assets/phosphor/icon.png
--------------------------------------------------------------------------------
/src/main/resources/fabric.mod.json:
--------------------------------------------------------------------------------
1 | {
2 | "schemaVersion": 1,
3 | "id": "phosphor",
4 | "version": "${version}",
5 | "name": "Phosphor",
6 | "description": "Phosphor is a free and open-source optimization mod for Minecraft which improves the performance of the lighting engine, resulting in significantly reduced world generation times and improved server tick rates. You can help feed me and support development on Patreon, gaining some special perks in the process! Any amount of support helps a ton. https://www.patreon.com/jellysquid",
7 | "authors": [
8 | "JellySquid",
9 | "PhiPro"
10 | ],
11 | "contact": {
12 | "homepage": "https://caffeinemc.net",
13 | "sources": "https://github.com/CaffeineMC/phosphor-fabric",
14 | "issues": "https://github.com/CaffeineMC/phosphor-fabric/issues"
15 | },
16 | "license": "LGPL-3.0-only",
17 | "icon": "assets/phosphor/icon.png",
18 | "environment": "*",
19 | "mixins": [
20 | "phosphor.mixins.json"
21 | ],
22 | "accessWidener": "phosphor.accesswidener",
23 | "depends": {
24 | "fabricloader": ">=0.8.0",
25 | "minecraft": "1.19.x"
26 | },
27 | "breaks": {
28 | "starlight": "*"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/resources/phosphor.accesswidener:
--------------------------------------------------------------------------------
1 | accessWidener v1 named
2 |
3 | accessible class net/minecraft/block/AbstractBlock$AbstractBlockState$ShapeCache
4 |
5 | accessible class net/minecraft/world/chunk/light/BlockLightStorage$Data
6 | accessible class net/minecraft/world/chunk/light/SkyLightStorage$Data
7 |
8 | accessible class net/minecraft/world/chunk/ChunkStatus$GenerationTask
9 | accessible class net/minecraft/world/chunk/ChunkStatus$LoadTask
10 |
11 | accessible class net/minecraft/server/world/ServerLightingProvider$Stage
12 |
13 | extendable class net/minecraft/world/chunk/ChunkNibbleArray
14 | extendable method net/minecraft/world/chunk/ChunkNibbleArray get (I)I
15 |
--------------------------------------------------------------------------------
/src/main/resources/phosphor.mixins.json:
--------------------------------------------------------------------------------
1 | {
2 | "required": true,
3 | "package": "net.caffeinemc.phosphor.mixin",
4 | "compatibilityLevel": "JAVA_17",
5 | "mixins": [
6 | "block.MixinAbstractBlockState",
7 | "block.MixinShapeCache",
8 | "chunk.MixinChunkNibbleArray",
9 | "chunk.MixinWorldChunk",
10 | "chunk.light.MixinLightingProvider",
11 | "chunk.light.MixinBlockLightStorageData",
12 | "chunk.light.MixinChunkBlockLightProvider",
13 | "chunk.light.MixinChunkLightProvider",
14 | "chunk.light.MixinChunkSkyLightProvider",
15 | "chunk.light.MixinChunkToNibbleArrayMap",
16 | "chunk.light.MixinLevelPropagator",
17 | "chunk.light.MixinLightStorage",
18 | "chunk.light.MixinBlockLightStorage",
19 | "chunk.light.MixinSkyLightStorage",
20 | "chunk.light.MixinSkyLightStorageData",
21 | "chunk.light.MixinServerLightingProvider",
22 | "chunk.MixinProtoChunk",
23 | "world.ThreadedAnvilChunkStorageAccess",
24 | "chunk.MixinChunkStatus"
25 | ],
26 | "injectors": {
27 | "defaultRequire": 1
28 | }
29 | }
30 |
--------------------------------------------------------------------------------