Skip to content

Commit abc8b59

Browse files
committed
Move metal block finding to another thread
Adds a fair amount of complexity, but does remove basically the entire fps impact of iron/steel
1 parent 0c5b3d9 commit abc8b59

File tree

2 files changed

+92
-18
lines changed

2 files changed

+92
-18
lines changed

src/main/java/com/legobmw99/allomancy/modules/powers/client/ClientEventHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ public void onRenderLevelStage(final RenderLevelStageEvent event) {
218218

219219

220220
if ((data.isBurning(Metal.IRON) || data.isBurning(Metal.STEEL))) {
221-
this.tracking.forEachMetalicEntity(entity -> ClientUtils.drawMetalLine(stack, playervec, entity.position(), 1.5F, 0F, 0.6F, 1F));
221+
this.tracking.forEachMetallicEntity(entity -> ClientUtils.drawMetalLine(stack, playervec, entity.position(), 1.5F, 0F, 0.6F, 1F));
222222

223223
this.tracking.forEachMetalBlob(blob -> ClientUtils.drawMetalLine(stack, playervec, blob.getCenter(), Mth.clamp(0.3F + blob.size() * 0.4F, 0.5F, 7.5F), 0F, 0.6F, 1F));
224224
}

src/main/java/com/legobmw99/allomancy/modules/powers/client/util/SensoryTracking.java

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.legobmw99.allomancy.modules.powers.PowersConfig;
77
import com.legobmw99.allomancy.modules.powers.data.AllomancerAttachment;
88
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
9+
import net.minecraft.Util;
910
import net.minecraft.client.Minecraft;
1011
import net.minecraft.core.BlockPos;
1112
import net.minecraft.world.entity.Entity;
@@ -16,58 +17,66 @@
1617
import net.minecraft.world.phys.Vec3;
1718

1819
import java.util.*;
20+
import java.util.concurrent.ExecutorService;
21+
import java.util.concurrent.Future;
22+
import java.util.concurrent.atomic.AtomicInteger;
23+
import java.util.concurrent.locks.Lock;
24+
import java.util.concurrent.locks.ReentrantLock;
1925
import java.util.function.Consumer;
2026

2127
public class SensoryTracking {
2228

29+
2330
private final List<Entity> metal_entities = new ArrayList<>();
24-
private final List<MetalBlockBlob> metal_blobs = new ArrayList<>();
31+
private final SyncList<MetalBlockBlob> metal_blobs = new SyncList<>();
2532
private final List<Player> nearby_allomancers = new ArrayList<>();
26-
27-
private int tickOffset = 0;
2833
private final Deque<BlockPos> to_consider = new ArrayDeque<>(20 * 20);
2934
// trick taken from BlockPos#breadthFirstTraversal used in SpongeBlock
3035
private final Set<Long> seen = new LongOpenHashSet(20 * 20);
3136

32-
public void tick() {
33-
this.tickOffset = (this.tickOffset + 1) % 2;
34-
if (this.tickOffset == 0) {
35-
populateSensoryLists();
36-
}
37-
}
37+
private Future<?> blobFuture = null;
3838

3939
public void forEachSeeked(Consumer<Player> f) {
4040
this.nearby_allomancers.forEach(f);
4141
}
4242

43-
public void forEachMetalicEntity(Consumer<Entity> f) {
43+
public void forEachMetallicEntity(Consumer<Entity> f) {
4444
this.metal_entities.forEach(f);
4545
}
4646

4747
public void forEachMetalBlob(Consumer<MetalBlockBlob> f) {
4848
this.metal_blobs.forEach(f);
4949
}
5050

51-
private void populateSensoryLists() {
51+
public void tick() {
5252
Player player = Minecraft.getInstance().player;
5353
IAllomancerData data = player.getData(AllomancerAttachment.ALLOMANCY_DATA);
5454

55-
this.metal_blobs.clear();
56-
this.metal_entities.clear();
5755
if (data.isBurning(Metal.IRON) || data.isBurning(Metal.STEEL)) {
5856
int max = PowersConfig.max_metal_detection.get();
5957
var negative = player.blockPosition().offset(-max, -max, -max);
6058
var positive = player.blockPosition().offset(max, max, max);
6159

6260
// Add metal entities to metal list
61+
this.metal_entities.clear();
6362
this.metal_entities.addAll(
6463
player.level().getEntitiesOfClass(Entity.class, AABB.encapsulatingFullBlocks(negative, positive), e -> PowerUtils.isEntityMetal(e) && !e.equals(player)));
6564

6665
// Add metal blobs to metal list
67-
this.seen.clear();
68-
BlockPos
69-
.betweenClosed(negative.getX(), negative.getY(), negative.getZ(), positive.getX(), positive.getY(), positive.getZ())
70-
.forEach(starter -> searchNearbyMetalBlocks(player.blockPosition(), max, starter, player.level()));
66+
if (this.blobFuture == null || this.blobFuture.isDone()) {
67+
this.blobFuture = Util.backgroundExecutor().submit(() -> {
68+
this.seen.clear();
69+
BlockPos
70+
.betweenClosed(negative.getX(), negative.getY(), negative.getZ(), positive.getX(), positive.getY(), positive.getZ())
71+
.forEach(starter -> searchNearbyMetalBlocks(player.blockPosition(), max, starter, player.level()));
72+
this.metal_blobs.swapAndClearOld();
73+
});
74+
}
75+
76+
} else if (this.blobFuture != null) { // previously we were burning
77+
this.blobFuture = null;
78+
this.metal_blobs.clearBothAsync(Util.backgroundExecutor());
79+
this.metal_entities.clear();
7180
}
7281

7382
// Populate our list of nearby allomancy users
@@ -135,6 +144,71 @@ private boolean addSeeked(IAllomancerData data, Player otherPlayer) {
135144
}
136145

137146

147+
private static class SyncList<T> {
148+
private final List<T> list_a = new ArrayList<>();
149+
private final List<T> list_b = new ArrayList<>();
150+
151+
private final Lock swapLock = new ReentrantLock();
152+
153+
/**
154+
* When this is even, we are reading A and writing B
155+
*/
156+
private final AtomicInteger AorB = new AtomicInteger(0);
157+
158+
159+
/**
160+
* Intended to be invoked from the main thread
161+
*/
162+
public void forEach(Consumer<T> f) {
163+
this.swapLock.lock();
164+
try {
165+
if (this.AorB.get() % 2 == 0) {
166+
this.list_a.forEach(f);
167+
} else {
168+
this.list_b.forEach(f);
169+
}
170+
} finally {
171+
this.swapLock.unlock();
172+
}
173+
}
174+
175+
public void add(T t) {
176+
if (this.AorB.get() % 2 == 1) {
177+
this.list_a.add(t);
178+
} else {
179+
this.list_b.add(t);
180+
}
181+
}
182+
183+
184+
/**
185+
* Intended to be invoked from a thread other than main
186+
*/
187+
public void swapAndClearOld() {
188+
this.swapLock.lock();
189+
int newAB = this.AorB.incrementAndGet();
190+
this.swapLock.unlock();
191+
if (newAB % 2 == 1) {
192+
this.list_a.clear();
193+
} else {
194+
this.list_b.clear();
195+
}
196+
}
197+
198+
public void clearBothAsync(ExecutorService ex) {
199+
ex.submit(() -> {
200+
this.swapLock.lock();
201+
try {
202+
this.list_a.clear();
203+
this.list_b.clear();
204+
} finally {
205+
this.swapLock.unlock();
206+
}
207+
});
208+
}
209+
}
210+
211+
138212
public static class MetalBlockBlob {
139213

140214
private static final Level level = Minecraft.getInstance().level;

0 commit comments

Comments
 (0)