Skip to content

Commit 0f48645

Browse files
committed
further optimzation of leaves (BFS instead of DFS)
1 parent a784e6b commit 0f48645

File tree

1 file changed

+67
-45
lines changed

1 file changed

+67
-45
lines changed

src/main/java/com/ferreusveritas/dynamictrees/block/leaves/DynamicLeavesBlock.java

+67-45
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import net.minecraft.sounds.SoundSource;
3030
import net.minecraft.util.Mth;
3131
import net.minecraft.util.RandomSource;
32+
import net.minecraft.util.Tuple;
3233
import net.minecraft.world.entity.Entity;
3334
import net.minecraft.world.entity.LivingEntity;
3435
import net.minecraft.world.entity.animal.Bee;
@@ -58,10 +59,7 @@
5859

5960
import javax.annotation.Nonnull;
6061
import javax.annotation.Nullable;
61-
import java.util.ArrayList;
62-
import java.util.Collections;
63-
import java.util.HashSet;
64-
import java.util.List;
62+
import java.util.*;
6563

6664
@SuppressWarnings("deprecation")
6765
public class DynamicLeavesBlock extends LeavesBlock implements TreePart, Ageable, RayTraceCollision {
@@ -143,7 +141,7 @@ public ItemStack getCloneItemStack(BlockState state, HitResult target, BlockGett
143141

144142
@Override
145143
public int age(LevelAccessor level, BlockPos pos, BlockState state, RandomSource rand, SafeChunkBounds safeBounds) {
146-
return updateLeaves(level, pos, state, rand,safeBounds == SafeChunkBounds.ANY_WG,null, 0);
144+
return updateLeaves(level, pos, state, rand,safeBounds == SafeChunkBounds.ANY_WG);
147145
}
148146

149147
@Override
@@ -181,42 +179,66 @@ public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource
181179
}
182180

183181
/**
184-
* Pulses recursively through the canopy, updating hydro and growing around if possible.
185-
* If visitedPositions is null, it is not recursive and only the first block will be updated.
186-
* @param visitedPositions keeps track of visited blocks for recursive calls. Set to null to make it pulse this block only.
187-
* @return the new hydro value
182+
* Pulses recursively through the canopy using BFS, updating hydro and growing around if possible.
183+
* @return False if the leaves decayed. True if they survived.
188184
*/
189-
public int updateLeaves(LevelAccessor level, BlockPos pos, BlockState state, RandomSource rand, boolean worldGen, @Nullable HashSet<BlockPos> visitedPositions, int fromHydro){
190-
boolean recursive = visitedPositions != null;
191-
final LeavesProperties leavesProperties = getProperties();
192-
if (recursive){
193-
if (visitedPositions.contains(pos) || visitedPositions.size() > leavesProperties.maxLeavesRecursion()) return 0;
194-
visitedPositions.add(pos);
195-
}
196-
197-
int newHydro = updateHydro(level, pos, state, worldGen);
198-
if (recursive && newHydro > fromHydro) return newHydro; //We do not recurse back through bigger hydro values
199-
//Grows new leaves around
200-
// We should do this even if the hydro is only 1. Since there could be adjacent branch blocks that could use a leaves block
201-
for (Direction dir : Direction.values()) { // Go on all 6 sides of this block
202-
if (newHydro > 1 || rand.nextInt(4) == 0) { // we'll give it a 1 in 4 chance to grow leaves if hydro is low to help performance
203-
BlockPos offpos = pos.relative(dir);
204-
if (recursive && visitedPositions.contains(offpos)) continue;
205-
//attempt to grow new leaves, a null hydro will be calculated from neighbors.
206-
growLeavesIfLocationIsSuitable(level, leavesProperties, offpos, null);
207-
if (recursive){
208-
//We recursively visit nearby leaves telling them to update too
209-
BlockState sideState = level.getBlockState(offpos);
185+
public boolean updateAllLeaves(LevelAccessor level, BlockPos startPos, BlockState startState, RandomSource rand, boolean worldGen){
186+
//We store the position and hydro of the next block.
187+
Queue<Tuple<BlockPos, Integer>> toProcess = new ArrayDeque<>();
188+
Set<BlockPos> processedPositions = new HashSet<>();
189+
int firstHydro = updateHydro(level, startPos, startState, worldGen);
190+
toProcess.add(new Tuple<>(startPos, firstHydro));
191+
if (firstHydro == 0) return false;
192+
while (!toProcess.isEmpty() && processedPositions.size() <= getProperties().maxLeavesRecursion()){
193+
Tuple<BlockPos, Integer> tup = toProcess.remove();
194+
BlockPos pos = tup.getA();
195+
int hydro = tup.getB();
196+
processedPositions.add(pos);
197+
for (Direction dir : Direction.values()) { // Go on all 6 sides of this block
198+
if (hydro > 1 || rand.nextInt(4) == 0) { // we'll give it a 1 in 4 chance to grow leaves if hydro is low to help performance
199+
BlockPos sidePos = pos.relative(dir);
200+
if (processedPositions.contains(sidePos)) continue;
201+
BlockState sideState = level.getBlockState(sidePos);
202+
//Check for surrounding leaves. Grow them if there aren't any.
203+
204+
if (!TreeHelper.isLeaves(sideState)) { //There were no leaves, attempt to grow some
205+
growLeavesIfLocationIsSuitable(level, getProperties(), sidePos, null);
206+
}
207+
int sideHydro;
210208
if (TreeHelper.isLeaves(sideState)){
211-
updateLeaves(level, offpos, sideState, rand, worldGen, visitedPositions, newHydro);
209+
sideHydro = updateHydro(level, sidePos, sideState, worldGen);
210+
} else {
211+
sideHydro = 0;
212+
}
213+
//Do not iterate back through bigger hydro values
214+
//or if the leaves failed to grow
215+
if (sideHydro == 0 || sideHydro <= hydro){
216+
toProcess.add(new Tuple<>(sidePos, sideHydro));
212217
}
213218
}
219+
}
220+
}
221+
return true;
222+
}
223+
224+
public int updateLeaves(LevelAccessor level, BlockPos pos, BlockState state, RandomSource rand, boolean worldGen){
225+
int newHydro = updateHydro(level, pos, state, worldGen);
226+
if (newHydro == 0) return 0; //If the leaves died don't bother
227+
228+
if (!worldGen && removeIfLightIsInadequate(state, level, pos, rand)) {
229+
return 0;
230+
}
214231

232+
for (Direction dir : Direction.values()) {
233+
if (newHydro > 1 || rand.nextInt(4) == 0) {
234+
BlockPos sidePos = pos.relative(dir);
235+
growLeavesIfLocationIsSuitable(level, getProperties(), sidePos, null);
215236
}
216237
}
217238
return newHydro;
218239
}
219240

241+
220242
protected boolean canCheckSurroundings(LevelAccessor accessor, BlockPos pos) {
221243
if (accessor instanceof Level level){
222244
// Check 2 blocks away for loaded chunks
@@ -229,32 +251,32 @@ protected boolean canCheckSurroundings(LevelAccessor accessor, BlockPos pos) {
229251
return accessor.isAreaLoaded(pos, 2);
230252
}
231253

232-
//TICK -> Destroy leaves if invalid
254+
//RANDOM TICK -> Destroy leaves if invalid
233255
//NEIGHBOR UPDATE -> recalculate hydro
234256
//TREE PULSE -> recalculate hydro
235257
// grows new leaves around (recursive)
236-
public int updateHydro(LevelAccessor accesor, BlockPos pos, BlockState state, boolean worldGen){
258+
public int updateHydro(LevelAccessor accessor, BlockPos pos, BlockState state, boolean worldGen){
237259
final LeavesProperties leavesProperties = getProperties();
238260
final int oldHydro = state.getValue(DISTANCE);
239261

240-
if (!canCheckSurroundings(accesor, pos)) return oldHydro;
262+
if (!canCheckSurroundings(accessor, pos)) return oldHydro;
241263

242264
// Check hydration level. Dry leaves are dead leaves.
243-
final int newHydro = getHydrationLevelFromNeighbors(accesor, pos, leavesProperties);
265+
final int newHydro = getHydrationLevelFromNeighbors(accessor, pos, leavesProperties);
244266

245267
if (oldHydro != newHydro) { // Only update if the hydro has changed. A little performance gain.
246-
BlockState placeState = getLeavesBlockStateForPlacement(accesor, pos, leavesProperties.getDynamicLeavesState(newHydro), oldHydro, worldGen);
268+
BlockState placeState = getLeavesBlockStateForPlacement(accessor, pos, leavesProperties.getDynamicLeavesState(newHydro), oldHydro, worldGen);
247269
// We do not use the 0x02 flag(update client) for performance reasons. The clients do not need to know the hydration level of the leaves blocks as it
248270
// does not affect appearance or behavior, unless appearanceChangesWithHydro. For the same reason we use the 0x04 flag to prevent the block from being re-rendered.
249271
// however if the new hydro is 0, it means the leaves were removed and we do need to update, so the flag is 3.
250272
int flag = newHydro == 0 ? 3 : (appearanceChangesWithHydro(oldHydro, newHydro) ? 2 : 4);
251-
if (newHydro == 0 && !worldGen
252-
&& (accesor instanceof Level level)
253-
//if the old hydro is the default then its most likely a block that was just placed and failed
254-
&& oldHydro != getProperties().getCellKit().getDefaultHydration()) {
255-
dropResources(state, level, pos);
256-
}
257-
accesor.setBlock(pos, placeState, flag);
273+
// if (newHydro == 0 && !worldGen
274+
// && (accessor instanceof Level level)
275+
// //if the old hydro is the default then its most likely a block that was just placed and failed
276+
// && oldHydro != getProperties().getCellKit().getDefaultHydration()) {
277+
// dropResources(state, level, pos);
278+
// }
279+
accessor.setBlock(pos, placeState, flag);
258280
}
259281
return newHydro;
260282
}
@@ -487,9 +509,9 @@ public GrowSignal branchOut(Level level, BlockPos pos, GrowSignal signal) {
487509
}
488510

489511
//Pulse through the leaves to update the canopy shape and their hydro values
490-
int hydro = updateLeaves(level, pos, level.getBlockState(pos), signal.rand, false, new HashSet<>(), Integer.MAX_VALUE);
512+
boolean survived = updateAllLeaves(level, pos, level.getBlockState(pos), signal.rand, false);
491513
//if hydro was 0 then the leaves have been removed
492-
if (hydro == 0){
514+
if (!survived){
493515
signal.success = false;
494516
return signal;
495517
}

0 commit comments

Comments
 (0)