Skip to content

Commit c1e45e2

Browse files
authored
Improve falling tree performance and consistency on tree scan (#1112)
1 parent 0bcf8c3 commit c1e45e2

13 files changed

Lines changed: 589 additions & 373 deletions

File tree

common/src/main/java/fr/rakambda/fallingtree/common/FallingTreeCommon.java

Lines changed: 205 additions & 202 deletions
Large diffs are not rendered by default.

common/src/main/java/fr/rakambda/fallingtree/common/tree/Tree.java

Lines changed: 18 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -2,142 +2,41 @@
22

33
import fr.rakambda.fallingtree.common.wrapper.IBlockPos;
44
import fr.rakambda.fallingtree.common.wrapper.ILevel;
5-
import lombok.Getter;
6-
import lombok.RequiredArgsConstructor;
75
import org.jspecify.annotations.NonNull;
8-
import java.util.Arrays;
96
import java.util.Collection;
10-
import java.util.LinkedHashMap;
11-
import java.util.LinkedHashSet;
12-
import java.util.Map;
137
import java.util.Optional;
14-
import java.util.Set;
15-
import static java.util.Comparator.comparingInt;
16-
import static java.util.Objects.isNull;
17-
import static java.util.stream.Collectors.toSet;
8+
import java.util.stream.Stream;
189

19-
@RequiredArgsConstructor
20-
public class Tree{
21-
@Getter
22-
@NonNull
23-
private final ILevel level;
24-
@Getter
25-
@NonNull
26-
private final IBlockPos hitPos;
27-
@Getter
28-
private final Set<TreePart> parts = new LinkedHashSet<>();
29-
private final Map<TreePartType, Integer> partCounts = new LinkedHashMap<>();
10+
public interface Tree{
3011

31-
public void addPart(@NonNull TreePart treePart){
32-
parts.add(treePart);
33-
partCounts.compute(treePart.treePartType(), (key, value) -> {
34-
if(isNull(value)){
35-
return 1;
36-
}
37-
return value + 1;
38-
});
39-
}
12+
int getBreakableCount();
4013

41-
public void removePartsHigherThan(int y, @NonNull TreePartType partType){
42-
parts.removeIf(part -> {
43-
if(part.treePartType() == partType && part.blockPos().getY() > y){
44-
decrementPartCount(partType);
45-
return true;
46-
}
47-
return false;
48-
});
49-
}
14+
@NonNull Optional<TreePart> getLastSequencePart();
5015

51-
public int getBreakableCount(){
52-
return Arrays.stream(TreePartType.getValues())
53-
.filter(TreePartType::isBreakable)
54-
.mapToInt(this::getPartCount)
55-
.sum();
56-
}
16+
@NonNull Optional<TreePart> getLastSequenceLogPart();
5717

58-
private int getPartCount(@NonNull TreePartType treePartType){
59-
return partCounts.computeIfAbsent(treePartType, key -> 0);
60-
}
18+
@NonNull Collection<TreePart> getBreakableLogs();
6119

62-
public int getSize(){
63-
return partCounts.values().stream().mapToInt(i -> i).sum();
64-
}
20+
@NonNull Collection<TreePart> getBreakableParts();
6521

66-
private void decrementPartCount(@NonNull TreePartType partType){
67-
partCounts.computeIfPresent(partType, (type, count) -> Math.max(0, count - 1));
68-
}
22+
int getLogCount();
6923

70-
@NonNull
71-
public Optional<TreePart> getLastSequencePart(){
72-
return getParts().stream()
73-
.max(comparingInt(TreePart::sequence));
74-
}
24+
@NonNull Optional<IBlockPos> getTopMostLog();
7525

76-
@NonNull
77-
public Optional<TreePart> getLastSequenceLogPart(){
78-
return getParts().stream()
79-
.filter(part -> part.treePartType().isLog())
80-
.max(comparingInt(TreePart::sequence));
81-
}
26+
@NonNull Optional<IBlockPos> getBottomMostLog();
8227

83-
@NonNull
84-
public Collection<TreePart> getBreakableLogs(){
85-
return getParts().stream()
86-
.filter(part -> part.treePartType().isLog())
87-
.filter(part -> part.treePartType().isBreakable())
88-
.collect(toSet());
89-
}
28+
@NonNull Optional<IBlockPos> getTopMostPart();
9029

91-
@NonNull
92-
public Collection<TreePart> getBreakableParts(){
93-
return getParts().stream()
94-
.filter(part -> part.treePartType().isBreakable())
95-
.collect(toSet());
96-
}
30+
@NonNull Collection<TreePart> getNetherWarts();
9731

98-
public int getLogCount(){
99-
return getPartCount(TreePartType.LOG);
100-
}
32+
@NonNull Collection<TreePart> getMangroveRoots();
10133

102-
@NonNull
103-
public Optional<IBlockPos> getTopMostLog(){
104-
return getBreakableLogs().stream()
105-
.map(TreePart::blockPos)
106-
.max(comparingInt(IBlockPos::getY));
107-
}
34+
@NonNull Optional<TreePart> getStart();
10835

109-
@NonNull
110-
public Optional<IBlockPos> getBottomMostLog(){
111-
return getBreakableLogs().stream()
112-
.map(TreePart::blockPos)
113-
.min(comparingInt(IBlockPos::getY));
114-
}
36+
@NonNull Stream<TreePart> getPartsStream();
11537

116-
@NonNull
117-
private Optional<IBlockPos> getTopMostPart(){
118-
return getParts().stream()
119-
.map(TreePart::blockPos)
120-
.max(comparingInt(IBlockPos::getY));
121-
}
38+
ILevel getLevel();
12239

123-
@NonNull
124-
public Collection<TreePart> getNetherWarts(){
125-
return getParts().stream()
126-
.filter(part -> part.treePartType() == TreePartType.NETHER_WART)
127-
.collect(toSet());
128-
}
129-
130-
@NonNull
131-
public Collection<TreePart> getMangroveRoots(){
132-
return getParts().stream()
133-
.filter(part -> part.treePartType() == TreePartType.MANGROVE_ROOTS)
134-
.collect(toSet());
135-
}
136-
137-
@NonNull
138-
public Optional<TreePart> getStart(){
139-
return getParts().stream()
140-
.filter(part -> part.treePartType() == TreePartType.LOG_START)
141-
.findFirst();
142-
}
40+
IBlockPos getHitPos();
14341
}
42+

common/src/main/java/fr/rakambda/fallingtree/common/tree/TreeHandler.java

Lines changed: 60 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -22,41 +22,59 @@
2222
import org.jspecify.annotations.NonNull;
2323
import org.jspecify.annotations.Nullable;
2424
import java.util.Map;
25+
import java.util.Objects;
2526
import java.util.Optional;
2627
import java.util.UUID;
27-
import java.util.concurrent.ConcurrentHashMap;
2828
import static java.util.Objects.isNull;
2929

3030
@Log4j2
3131
@RequiredArgsConstructor
3232
public class TreeHandler{
3333
@NonNull
3434
private final FallingTreeCommon<?> mod;
35-
private final Map<UUID, CacheSpeed> speedCache = new ConcurrentHashMap<>();
35+
@NonNull
36+
private final Map<UUID, CacheSpeed> speedCache;
37+
38+
@NonNull
39+
private final IPlayer player;
40+
@NonNull
41+
private final ILevel level;
42+
@NonNull
43+
private final IBlockPos originPos;
44+
@NonNull
45+
private final IBlockState originState;
46+
@Nullable
47+
private final IBlockEntity originEntity;
3648

37-
public boolean shouldCancelEvent(@NonNull ILevel level, @NonNull IPlayer player, @NonNull IBlockPos originPos, @NonNull IBlockState originState, @Nullable IBlockEntity originEntity){
49+
@Nullable
50+
private Tree cachedTree = null;
51+
52+
@Nullable
53+
public Tree getTree() throws TreeTooBigException{
54+
if(Objects.isNull(cachedTree)){
55+
cachedTree = mod.getTreeBuilder().getTree(player, level, originPos, originState, originEntity).orElse(null);
56+
}
57+
return cachedTree;
58+
}
59+
60+
public boolean shouldCancelEvent(){
3861
if(!mod.isPlayerInRightState(player)){
3962
return false;
4063
}
4164
if(shouldPreserveTool(player)){
4265
return true;
4366
}
4467
try{
45-
mod.getTreeBuilder().getTree(player, level, originPos, originState, originEntity).isEmpty();
68+
getTree();
4669
}
4770
catch(TreeTooBigException e){
4871
return false;
4972
}
5073
return false;
5174
}
5275

53-
private boolean shouldPreserveTool(@NonNull IPlayer player){
54-
var handItem = player.getMainHandItem();
55-
return mod.getConfiguration().getTools().getDurabilityMode().shouldPreserve(handItem.getDurability());
56-
}
57-
5876
@NonNull
59-
public IBreakAttemptResult breakTree(boolean isCancellable, @NonNull ILevel level, @NonNull IPlayer player, @NonNull IBlockPos originPos, @NonNull IBlockState originState, @Nullable IBlockEntity originEntity){
77+
public IBreakAttemptResult breakTree(boolean isCancellable){
6078
if(!level.isServer()){
6179
return AbortedResult.NOT_SERVER;
6280
}
@@ -74,12 +92,11 @@ public IBreakAttemptResult breakTree(boolean isCancellable, @NonNull ILevel leve
7492
}
7593

7694
try{
77-
var treeOptional = mod.getTreeBuilder().getTree(player, level, originPos, originState, originEntity);
78-
if(treeOptional.isEmpty()){
95+
var tree = getTree();
96+
if(tree == null){
7997
return AbortedResult.NO_SUCH_TREE;
8098
}
8199

82-
var tree = treeOptional.get();
83100
var breakMode = getBreakMode(player.getMainHandItem());
84101
return getBreakingHandler(breakMode).breakTree(isCancellable, player, tree);
85102
}
@@ -98,25 +115,7 @@ public IBreakAttemptResult breakTree(boolean isCancellable, @NonNull ILevel leve
98115
}
99116

100117
@NonNull
101-
private BreakMode getBreakMode(@NonNull IItemStack itemStack){
102-
return itemStack.getBreakModeFromEnchant()
103-
.orElseGet(() -> mod.getConfiguration().getTrees().getBreakMode());
104-
}
105-
106-
@NonNull
107-
private ITreeBreakingHandler getBreakingHandler(@NonNull BreakMode breakMode){
108-
return switch(breakMode){
109-
case INSTANTANEOUS -> InstantaneousTreeBreakingHandler.getInstance(mod);
110-
case FALL_ITEM -> FallingAnimationTreeBreakingHandler.getInstance(mod, FallingAnimationTreeBreakingConfig.withRandomSpread(true, true));
111-
case FALL_ITEM_STRAIGHT -> FallingAnimationTreeBreakingHandler.getInstance(mod, FallingAnimationTreeBreakingConfig.straightDown(true, true));
112-
case FALL_BLOCK -> FallingAnimationTreeBreakingHandler.getInstance(mod, FallingAnimationTreeBreakingConfig.withRandomSpread(false, true));
113-
case FALL_ALL_BLOCK -> FallingAnimationTreeBreakingHandler.getInstance(mod, FallingAnimationTreeBreakingConfig.withRandomSpread(false, false));
114-
case SHIFT_DOWN -> ShiftDownTreeBreakingHandler.getInstance(mod);
115-
};
116-
}
117-
118-
@NonNull
119-
public Optional<Float> getBreakSpeed(@NonNull IPlayer player, @NonNull IBlockPos blockPos, @NonNull IBlockState blockState, float originalSpeed){
118+
public Optional<Float> getBreakSpeed(float originalSpeed){
120119
if(!mod.getConfiguration().getTrees().isTreeBreaking()){
121120
return Optional.empty();
122121
}
@@ -128,25 +127,48 @@ public Optional<Float> getBreakSpeed(@NonNull IPlayer player, @NonNull IBlockPos
128127
}
129128

130129
var cacheSpeed = speedCache.compute(player.getUUID(), (uuid, speed) -> {
131-
if(isNull(speed) || !speed.isValid(blockPos)){
132-
speed = getSpeed(player, blockPos, blockState, originalSpeed);
130+
if(isNull(speed) || !speed.isValid(originPos)){
131+
speed = getSpeed(originalSpeed);
133132
}
134133
return speed;
135134
});
136135
return Optional.ofNullable(cacheSpeed).map(CacheSpeed::getSpeed);
137136
}
138137

139138
@Nullable
140-
private CacheSpeed getSpeed(@NonNull IPlayer player, @NonNull IBlockPos pos, @NonNull IBlockState blockState, float originalSpeed){
139+
private CacheSpeed getSpeed(float originalSpeed){
141140
var speedMultiplicand = mod.getConfiguration().getTools().getSpeedMultiplicand();
142141
try{
143-
return speedMultiplicand <= 0 ? null :
144-
mod.getTreeBuilder().getTree(player, player.getLevel(), pos, blockState, null)
145-
.map(tree -> new CacheSpeed(pos, originalSpeed / ((float) speedMultiplicand * tree.getLogCount())))
142+
return speedMultiplicand <= 0
143+
? null
144+
: Optional.ofNullable(getTree())
145+
.map(tree -> new CacheSpeed(originPos, originalSpeed / ((float) speedMultiplicand * tree.getLogCount())))
146146
.orElse(null);
147147
}
148148
catch(TreeTooBigException e){
149149
return null;
150150
}
151151
}
152+
153+
private boolean shouldPreserveTool(@NonNull IPlayer player){
154+
var handItem = player.getMainHandItem();
155+
return mod.getConfiguration().getTools().getDurabilityMode().shouldPreserve(handItem.getDurability());
156+
}
157+
158+
@NonNull
159+
private BreakMode getBreakMode(@NonNull IItemStack itemStack){
160+
return itemStack.getBreakModeFromEnchant().orElseGet(() -> mod.getConfiguration().getTrees().getBreakMode());
161+
}
162+
163+
@NonNull
164+
private ITreeBreakingHandler getBreakingHandler(@NonNull BreakMode breakMode){
165+
return switch(breakMode){
166+
case INSTANTANEOUS -> InstantaneousTreeBreakingHandler.getInstance(mod);
167+
case FALL_ITEM -> FallingAnimationTreeBreakingHandler.getInstance(mod, FallingAnimationTreeBreakingConfig.withRandomSpread(true, true));
168+
case FALL_ITEM_STRAIGHT -> FallingAnimationTreeBreakingHandler.getInstance(mod, FallingAnimationTreeBreakingConfig.straightDown(true, true));
169+
case FALL_BLOCK -> FallingAnimationTreeBreakingHandler.getInstance(mod, FallingAnimationTreeBreakingConfig.withRandomSpread(false, true));
170+
case FALL_ALL_BLOCK -> FallingAnimationTreeBreakingHandler.getInstance(mod, FallingAnimationTreeBreakingConfig.withRandomSpread(false, false));
171+
case SHIFT_DOWN -> ShiftDownTreeBreakingHandler.getInstance(mod);
172+
};
173+
}
152174
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package fr.rakambda.fallingtree.common.tree;
2+
3+
import fr.rakambda.fallingtree.common.FallingTreeCommon;
4+
import fr.rakambda.fallingtree.common.utils.CacheSpeed;
5+
import fr.rakambda.fallingtree.common.wrapper.IBlockEntity;
6+
import fr.rakambda.fallingtree.common.wrapper.IBlockPos;
7+
import fr.rakambda.fallingtree.common.wrapper.IBlockState;
8+
import fr.rakambda.fallingtree.common.wrapper.ILevel;
9+
import fr.rakambda.fallingtree.common.wrapper.IPlayer;
10+
import lombok.RequiredArgsConstructor;
11+
import lombok.extern.log4j.Log4j2;
12+
import org.jspecify.annotations.NonNull;
13+
import org.jspecify.annotations.Nullable;
14+
import java.util.Map;
15+
import java.util.UUID;
16+
import java.util.concurrent.ConcurrentHashMap;
17+
18+
@Log4j2
19+
@RequiredArgsConstructor
20+
public class TreeHandlerFactory{
21+
@NonNull
22+
private final FallingTreeCommon<?> mod;
23+
private final Map<UUID, CacheSpeed> speedCache = new ConcurrentHashMap<>();
24+
25+
@NonNull
26+
public TreeHandler create(@NonNull ILevel level, @NonNull IPlayer player, @NonNull IBlockPos originPos, @NonNull IBlockState originState, @Nullable IBlockEntity originEntity){
27+
return new TreeHandler(mod, speedCache, player, level, originPos, originState, originEntity);
28+
}
29+
}

common/src/main/java/fr/rakambda/fallingtree/common/tree/breaking/FallingAnimationTreeBreakingHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public IBreakAttemptResult breakTree(boolean isCancellable, @NonNull IPlayer pla
8181
var lootHandler = new LootHandler(wantToBreakCount, mod.getConfiguration().getTrees().getTrunkLootPercentage());
8282
var brokenCount = 0;
8383
var breakablePartsLeft = wantToBreakCount;
84-
var breakableParts = tree.getParts().stream().sorted(mod.getConfiguration().getTrees().getBreakOrder().getComparator()).toList();
84+
var breakableParts = tree.getPartsStream().sorted(mod.getConfiguration().getTrees().getBreakOrder().getComparator()).toList();
8585
for(var part : breakableParts){
8686
if(part.treePartType().isBreakable()){
8787
if(breakablePartsLeft == 0){

0 commit comments

Comments
 (0)