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
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,25 @@ public class ProvinceManager {
private Map<Guild, Map<String, Double>> installationAccess = new HashMap<>();
private long stateVersion = 0;
private long lastCalculatedVersion = -1;
private Map<Guild, Boolean> lastCalculatedBankruptcy = Map.of();

public void markDirty() {
stateVersion++;
}

public void recalculateIfNeeded() {
if (stateVersion == lastCalculatedVersion) return;

if (!Cache.provincesEnabled) return;
// Realm treasury sign changes stability even when no map mutation marks us dirty.
if (stateVersion == lastCalculatedVersion && realmBankruptcy().equals(lastCalculatedBankruptcy)) return;
recalculate();
lastCalculatedVersion = stateVersion;
}

private Map<Guild, Boolean> realmBankruptcy() {
Map<Guild, Boolean> bankruptcy = new HashMap<>();
for (Guild guild : FactionManager.getAllGuilds()) {
if (guild.isBase()) bankruptcy.put(guild, guild.isBankrupt());
}
return bankruptcy;
}

public List<Province> getProvinces() { return new ArrayList<>(provinces.values()); }
Expand Down Expand Up @@ -116,6 +125,8 @@ public void recalculate() {
if (!Cache.provincesEnabled) {
return;
}
// A partial or failed flood must not leave the old full snapshot marked fresh.
lastCalculatedVersion = -1;
InstallationAccess.beginRecalculation();
BankruptcyMemo.Run memo = BankruptcyMemo.begin();
try {
Expand All @@ -131,6 +142,8 @@ public void recalculate() {
}
recalculateProsperity();
for(Guild guild : FactionManager.getAllGuilds()) getIncome(guild);
lastCalculatedBankruptcy = realmBankruptcy();
lastCalculatedVersion = stateVersion;
} finally {
BankruptcyMemo.end(memo);
InstallationAccess.endRecalculation();
Expand Down Expand Up @@ -180,6 +193,8 @@ public void recalculateForSingleGuild(Guild g, boolean save) {
if (!Cache.provincesEnabled) {
return;
}
// A partial or failed flood must not leave the old full snapshot marked fresh.
lastCalculatedVersion = -1;
InstallationAccess.beginRecalculation();
BankruptcyMemo.Run memo = BankruptcyMemo.begin();
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public void prepareUploadFiles() {

/** Cheap payloads that change continuously and ship on every cycle (main thread). */
public void prepareLiveFiles() {
SimpleFactions.getInstance().getProvinceManager().recalculateIfNeeded();
exportProvinces();
exportGuilds();
exportChronicle();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import net.tfminecraft.simplefactions.map.provinces.ProvinceDataEntry;
import net.tfminecraft.simplefactions.objects.Faction;
import net.tfminecraft.simplefactions.testsupport.FactionDomainFixture;
import net.tfminecraft.simplefactions.testsupport.TestTreasuries;
import net.tfminecraft.simplefactions.war.resolution.PillageTradeHit;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
Expand Down Expand Up @@ -299,6 +300,82 @@ void dirtyRecalculationReplacesEconomicsOnlyWhenTheModelChanges() {
assertEquals(prosperity, guild.getTradeBreakdown().getIncome(), 0.01);
}

@Test
void treasuryBankruptcyAndRecoveryInvalidateCachedRealmTrade() {
productiveBranch();
Faction home = fixture.saved("home", "Alice");
Guild guild = home.getOrCreateMainGuild();
Province source = province(1);
home.addProvince(1);
home.setCapital(1, true);
fixture.provincesEnabled(true);
try (TestTreasuries treasury = new TestTreasuries()) {
TestTreasuries.set(guild.getBank(), 100);
manager.recalculateIfNeeded();
assertTrue(source.getStoredGuildTrade(guild) > 0);
ProvinceDataEntry solvent = source.getData(guild.getId());
TestTreasuries.set(guild.getBank(), 90);
manager.recalculateIfNeeded();
assertSame(solvent, source.getData(guild.getId()));
TestTreasuries.set(guild.getBank(), -11.23);
manager.recalculateIfNeeded();
assertEquals(0, source.getStoredGuildTrade(guild));
assertEquals(0, guild.getTradeBreakdown().getIncome());
assertEquals(-11.23, guild.getBank().getWealth(), 0.001);
TestTreasuries.set(guild.getBank(), 0);
manager.recalculateIfNeeded();
assertTrue(source.getStoredGuildTrade(guild) > 0);
assertTrue(guild.getTradeBreakdown().getIncome() > 0);
assertEquals(0, guild.getBank().getWealth());
}
}

@ParameterizedTest
@ValueSource(booleans = {false, true})
void recoveryAfterAnUnguardedFloodRefreshesAgain(boolean singleGuild) {
productiveBranch();
Faction home = fixture.saved("home", "Alice");
Guild guild = home.getOrCreateMainGuild();
Province source = province(1);
home.addProvince(1);
home.setCapital(1, true);
fixture.provincesEnabled(true);
try (TestTreasuries treasury = new TestTreasuries()) {
TestTreasuries.set(guild.getBank(), 100);
manager.recalculateIfNeeded();
TestTreasuries.set(guild.getBank(), -11.23);
if (singleGuild) manager.recalculateForSingleGuild(guild, true);
else manager.recalculate();
assertEquals(0, source.getStoredGuildTrade(guild));
TestTreasuries.set(guild.getBank(), 0);
manager.recalculateIfNeeded();
assertTrue(source.getStoredGuildTrade(guild) > 0);
assertTrue(guild.getTradeBreakdown().getIncome() > 0);
}
}

@Test
void directFullFloodAlsoUpdatesTheGuardedCache() {
ProvinceManager watched = spy(manager);
productiveBranch();
Faction home = fixture.saved("home", "Alice");
Guild guild = home.getOrCreateMainGuild();
Province source = province(1);
home.addProvince(1);
home.setCapital(1, true);
fixture.provincesEnabled(true);
try (TestTreasuries treasury = new TestTreasuries()) {
TestTreasuries.set(guild.getBank(), 100);
watched.recalculateIfNeeded();
TestTreasuries.set(guild.getBank(), -11.23);
watched.recalculate();
assertEquals(0, source.getStoredGuildTrade(guild));
clearInvocations(watched);
watched.recalculateIfNeeded();
verify(watched, never()).recalculate();
}
}

@Test
void disabledIncomeQueriesAndRecalculationRespectWhetherSavingWasRequested() {
productiveBranch();
Expand All @@ -310,6 +387,7 @@ void disabledIncomeQueriesAndRecalculationRespectWhetherSavingWasRequested() {
source.setData(guild.getId(), new ProvinceDataEntry(guild, 10, 3));
source.setProsperity(40);
guild.getTradeBreakdown().setIncome(7);
manager.recalculateIfNeeded();
manager.recalculate();
manager.recalculateQuiet(null, null);
manager.recalculateForSingleGuild(guild, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ private void completeUpload() {
fixture.ui.runTasks();
}

@Test
void liveExportsRefreshEconomicsBeforeReadingPayloads() {
doAnswer(call -> {
assertFalse(Files.exists(files.root.resolve("MapAPI/chronicle.json")));
assertFalse(Files.exists(files.root.resolve("MapAPI/province_data.json")));
return null;
}).when(fixture.provinces).recalculateIfNeeded();
map.prepareLiveFiles();
verify(fixture.provinces).recalculateIfNeeded();
assertTrue(Files.exists(files.root.resolve("MapAPI/chronicle.json")));
}

@Test
void mutationsDuringAnUploadRemainQueuedForTheNextPublication() throws Exception {
map.enqueue("nation", "first");
Expand Down
Loading