Skip to content

Commit

Permalink
Move metal block finding to another thread
Browse files Browse the repository at this point in the history
Adds a fair amount of complexity, but does remove basically the entire fps impact of iron/steel
  • Loading branch information
legobmw99 committed Jan 20, 2024
1 parent 0c5b3d9 commit abc8b59
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ public void onRenderLevelStage(final RenderLevelStageEvent event) {


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

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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.legobmw99.allomancy.modules.powers.PowersConfig;
import com.legobmw99.allomancy.modules.powers.data.AllomancerAttachment;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
Expand All @@ -16,58 +17,66 @@
import net.minecraft.world.phys.Vec3;

import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;

public class SensoryTracking {


private final List<Entity> metal_entities = new ArrayList<>();
private final List<MetalBlockBlob> metal_blobs = new ArrayList<>();
private final SyncList<MetalBlockBlob> metal_blobs = new SyncList<>();
private final List<Player> nearby_allomancers = new ArrayList<>();

private int tickOffset = 0;
private final Deque<BlockPos> to_consider = new ArrayDeque<>(20 * 20);
// trick taken from BlockPos#breadthFirstTraversal used in SpongeBlock
private final Set<Long> seen = new LongOpenHashSet(20 * 20);

public void tick() {
this.tickOffset = (this.tickOffset + 1) % 2;
if (this.tickOffset == 0) {
populateSensoryLists();
}
}
private Future<?> blobFuture = null;

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

public void forEachMetalicEntity(Consumer<Entity> f) {
public void forEachMetallicEntity(Consumer<Entity> f) {
this.metal_entities.forEach(f);
}

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

private void populateSensoryLists() {
public void tick() {
Player player = Minecraft.getInstance().player;
IAllomancerData data = player.getData(AllomancerAttachment.ALLOMANCY_DATA);

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

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

// Add metal blobs to metal list
this.seen.clear();
BlockPos
.betweenClosed(negative.getX(), negative.getY(), negative.getZ(), positive.getX(), positive.getY(), positive.getZ())
.forEach(starter -> searchNearbyMetalBlocks(player.blockPosition(), max, starter, player.level()));
if (this.blobFuture == null || this.blobFuture.isDone()) {
this.blobFuture = Util.backgroundExecutor().submit(() -> {
this.seen.clear();
BlockPos
.betweenClosed(negative.getX(), negative.getY(), negative.getZ(), positive.getX(), positive.getY(), positive.getZ())
.forEach(starter -> searchNearbyMetalBlocks(player.blockPosition(), max, starter, player.level()));
this.metal_blobs.swapAndClearOld();
});
}

} else if (this.blobFuture != null) { // previously we were burning
this.blobFuture = null;
this.metal_blobs.clearBothAsync(Util.backgroundExecutor());
this.metal_entities.clear();
}

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


private static class SyncList<T> {
private final List<T> list_a = new ArrayList<>();
private final List<T> list_b = new ArrayList<>();

private final Lock swapLock = new ReentrantLock();

/**
* When this is even, we are reading A and writing B
*/
private final AtomicInteger AorB = new AtomicInteger(0);


/**
* Intended to be invoked from the main thread
*/
public void forEach(Consumer<T> f) {
this.swapLock.lock();
try {
if (this.AorB.get() % 2 == 0) {
this.list_a.forEach(f);
} else {
this.list_b.forEach(f);
}
} finally {
this.swapLock.unlock();
}
}

public void add(T t) {
if (this.AorB.get() % 2 == 1) {
this.list_a.add(t);
} else {
this.list_b.add(t);
}
}


/**
* Intended to be invoked from a thread other than main
*/
public void swapAndClearOld() {
this.swapLock.lock();
int newAB = this.AorB.incrementAndGet();
this.swapLock.unlock();
if (newAB % 2 == 1) {
this.list_a.clear();
} else {
this.list_b.clear();
}
}

public void clearBothAsync(ExecutorService ex) {
ex.submit(() -> {
this.swapLock.lock();
try {
this.list_a.clear();
this.list_b.clear();
} finally {
this.swapLock.unlock();
}
});
}
}


public static class MetalBlockBlob {

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

0 comments on commit abc8b59

Please sign in to comment.