Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 42 additions & 7 deletions src/main/java/net/tfminecraft/dowsing/managers/NodeManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.commons.lang.WordUtils;
import org.bukkit.Bukkit;
Expand Down Expand Up @@ -116,13 +117,42 @@ public static double getTotalUpkeep(Guild g) {
public static Integer getNodeAmount(Guild g) {
Integer i = 0;
if(g == null) return i;
if(passCounts != null) return g.getId() == null ? i : passCounts.getOrDefault(g.getId(), i);
for(Node n : nodes) {
if(!n.hasGuild()) continue;
if(n.getBlock().isSpecial()) continue;
if(n.getGuild().getId().equalsIgnoreCase(g.getId())) i++;
}
return i;
}
/**
* Claimed-node counts per guild for one particle pass. The pass checks every claimed node four
* times a second, and each check counted its guild's nodes by walking every node, so a pass cost
* nodes x nodes guild lookups. Counted once per pass instead, and kept right by {@link #released}.
*/
private static Map<String, Integer> passCounts;

static void beginCountPass() {
Map<String, Integer> counts = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for(Node n : nodes) {
if(!n.hasGuild()) continue;
if(n.getBlock().isSpecial()) continue;
String id = n.getGuild().getId();
if(id != null) counts.merge(id, 1, Integer::sum);
}
passCounts = counts;
}

static void endCountPass() {
passCounts = null;
}

/** A node of {@code g} was just let go during a particle pass, so its guild holds one fewer. */
public static void released(Guild g, Node n) {
if(passCounts == null || g == null || g.getId() == null || n.getBlock().isSpecial()) return;
passCounts.computeIfPresent(g.getId(), (id, count) -> count - 1);
}

public static Integer getNodeCapacity(Guild g) {
int capacity = 1;
if(g == null) return capacity;
Expand Down Expand Up @@ -211,14 +241,19 @@ public void particleCycle(){
public void run()
{
if (!DowsingMain.plugin.nodesReady()) return;
for(Node n : nodes){
if(n.isClaimable()){
Location loc = n.getLoc().clone().add(0.5, 1, 0.5);
loc.getWorld().spawnParticle(Particle.HAPPY_VILLAGER, loc, 10);
} else {
n.check();
beginCountPass();
try {
for(Node n : nodes){
if(n.isClaimable()){
Location loc = n.getLoc().clone().add(0.5, 1, 0.5);
loc.getWorld().spawnParticle(Particle.HAPPY_VILLAGER, loc, 10);
} else {
n.check();
}
}
}
} finally {
endCountPass();
}
}
}.runTaskTimer(DowsingMain.plugin, 0L, 5L);
}
Expand Down
1 change: 1 addition & 0 deletions src/main/java/net/tfminecraft/dowsing/objects/Node.java
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ public void check() {
p.closeInventory();
}
guild = null;
NodeManager.released(g, this);
NodeManager.requestNodeBenefitSync();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,53 @@ void cachingReloadsBlockTypeAndPreviouslySelectedProductionMethods() {
NodeManager.nodes.clear(); f.manager.cacheNodes(); assertTrue(f.manager.cached.isEmpty());
}

@Test
void aParticlePassCountsEachGuildOnceAndTracksNodesLetGo() {
Node second = f.nodeAt(f.location), unowned = f.nodeAt(f.location), other = f.nodeAt(f.location), special = f.nodeAt(f.location), noId = f.nodeAt(f.location);
when(unowned.hasGuild()).thenReturn(false);
Guild otherGuild = mock(Guild.class); when(otherGuild.getId()).thenReturn("OTHER"); when(other.getGuild()).thenReturn(otherGuild);
NodeBlock specialBlock = mock(NodeBlock.class); when(specialBlock.isSpecial()).thenReturn(true); when(special.getBlock()).thenReturn(specialBlock);
Guild idless = mock(Guild.class); when(noId.getGuild()).thenReturn(idless);
NodeManager.nodes.addAll(List.of(f.node, second, unowned, other, special));
assertEquals(2, NodeManager.getNodeAmount(f.guild));
NodeManager.nodes.add(noId); // the live walk throws on a guild without an id; a pass skips it
NodeManager.beginCountPass();
try {
assertEquals(2, NodeManager.getNodeAmount(f.guild)); assertEquals(1, NodeManager.getNodeAmount(otherGuild));
assertEquals(0, NodeManager.getNodeAmount(idless)); assertEquals(0, NodeManager.getNodeAmount(null));
when(f.node.hasGuild()).thenReturn(false);
assertEquals(2, NodeManager.getNodeAmount(f.guild), "counted once at the start of the pass");
NodeManager.released(f.guild, f.node); assertEquals(1, NodeManager.getNodeAmount(f.guild));
NodeManager.released(f.guild, special); NodeManager.released(null, f.node); NodeManager.released(idless, f.node);
Guild unknown = mock(Guild.class); when(unknown.getId()).thenReturn("unknown"); NodeManager.released(unknown, f.node);
assertEquals(1, NodeManager.getNodeAmount(f.guild)); assertEquals(0, NodeManager.getNodeAmount(unknown));
} finally {
NodeManager.endCountPass();
}
NodeManager.nodes.remove(noId); NodeManager.released(f.guild, second);
assertEquals(1, NodeManager.getNodeAmount(f.guild), "live again once the pass ends");
}

@Test
void aRealCheckThatLetsANodeGoLowersItsGuildsCountForTheRestOfThePass() {
Cache.extraCapacity = false;
Node first = new Node(UUID.randomUUID(), f.block, f.location, f.guild, true, 1, 0, f.type, 60, 0, 25);
Node second = new Node(UUID.randomUUID(), f.block, f.location, f.guild, true, 1, 0, f.type, 60, 0, 25);
NodeManager.nodes.addAll(List.of(first, second));
assertEquals(1, NodeManager.getNodeCapacity(f.guild));
NodeManager.beginCountPass();
try {
assertEquals(2, NodeManager.getNodeAmount(f.guild));
first.check();
assertTrue(first.isClaimable(), "over capacity, so the first node is let go");
assertEquals(1, NodeManager.getNodeAmount(f.guild), "Node.check lowers the pass count");
second.check();
assertFalse(second.isClaimable(), "back within capacity, so the second node stays");
} finally {
NodeManager.endCountPass();
}
}

@Test
void timersUseTheirIntendedCadenceAndRunParticlesChecksAndEfficiencyGrowth() {
Node claimed = f.node, unclaimed = f.nodeAt(f.location);
Expand Down