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

This file was deleted.

110 changes: 110 additions & 0 deletions src/main/java/net/tfminecraft/simplefactions/economy/FrozenReads.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package net.tfminecraft.simplefactions.economy;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Supplier;

import net.tfminecraft.denareconomy.api.Denars;
import net.tfminecraft.simplefactions.espionage.EspionageService;
import net.tfminecraft.simplefactions.government.StabilityModifier;
import net.tfminecraft.simplefactions.guild.Guild;
import net.tfminecraft.simplefactions.guild.income.Ledger;
import net.tfminecraft.simplefactions.managers.FactionManager;
import net.tfminecraft.simplefactions.objects.Bank;
import net.tfminecraft.simplefactions.objects.Faction;

/**
* Guild treasuries, node upkeep and espionage stability read once on the main thread.
* DenarEconomy, Dowsing and TLibs character identity refuse reads from other threads, so
* income previews that run async read these instead.
*/
public final class FrozenReads {
private static final ThreadLocal<FrozenReads> CURRENT = new ThreadLocal<>();

private final Map<String, Long> cents;
private final Map<UUID, Double> nodeUpkeep;
private final Map<String, List<StabilityModifier>> espionage;

private FrozenReads(
Map<String, Long> cents, Map<UUID, Double> nodeUpkeep, Map<String, List<StabilityModifier>> espionage) {
this.cents = Map.copyOf(cents);
this.nodeUpkeep = Map.copyOf(nodeUpkeep);
this.espionage = Map.copyOf(espionage);
}

/** Reads every guild treasury and node upkeep and every realm espionage modifier now. Call on the main thread. */
public static FrozenReads capture() {
long now = System.currentTimeMillis();
Map<String, List<StabilityModifier>> espionage = new HashMap<>();
for (Faction faction : FactionManager.factions) {
// Stability asks for each realm guild, so create any missing one here rather than on a worker.
if (faction != null && faction.getGuildHandler() != null) {
faction.getOrCreateMainGuild();
}
// Spymaster vacancy asks TLibs whether the holder's character is dead.
if (faction != null && faction.getId() != null && faction.getEspionage() != null) {
espionage.put(faction.getId(), List.copyOf(EspionageService.stabilityModifiers(faction, now)));
}
}
Map<String, Long> cents = new HashMap<>();
Map<UUID, Double> nodeUpkeep = new HashMap<>();
for (Guild guild : FactionManager.getAllGuilds()) {
Bank bank = guild == null ? null : guild.getBank();
if (bank != null) {
String account = bank.accountId();
cents.put(account, Denars.balance(account));
}
UUID incarnation = guild == null ? null : guild.getIncarnationId();
if (incarnation != null) {
nodeUpkeep.put(incarnation, Ledger.liveNodeUpkeep(guild));
}
}
return new FrozenReads(cents, nodeUpkeep, espionage);
}

/** Runs the work with these reads standing in for the live state on this thread. */
public <T> T read(Supplier<T> work) {
FrozenReads previous = CURRENT.get();
CURRENT.set(this);
try {
return work.get();
} finally {
if (previous == null) {
CURRENT.remove();
} else {
CURRENT.set(previous);
}
}
}

/** The frozen balance inside {@link #read}, otherwise the live ledger balance. */
public static long balance(String account) {
FrozenReads current = CURRENT.get();
if (current != null) {
return current.cents.getOrDefault(account, 0L);
}
return Denars.balance(account);
}

/** The frozen node upkeep inside {@link #read}, otherwise the live upkeep. */
public static double nodeUpkeep(Guild guild) {
FrozenReads current = CURRENT.get();
if (current != null) {
UUID incarnation = guild.getIncarnationId();
return incarnation == null ? 0.0 : current.nodeUpkeep.getOrDefault(incarnation, 0.0);
}
return Ledger.liveNodeUpkeep(guild);
}

/** The frozen espionage modifiers inside {@link #read}, otherwise the live ones at {@code now}. */
public static List<StabilityModifier> espionage(Faction faction, long now) {
FrozenReads current = CURRENT.get();
if (current != null) {
String id = faction.getId();
return id == null ? List.of() : current.espionage.getOrDefault(id, List.of());
}
return EspionageService.stabilityModifiers(faction, now);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ public void addStabilityModifier(StabilityModifier modifier) {

public List<StabilityModifier> getStabilityModifiers() {
if (f == null || f.getEspionage() == null) return stabilityModifiers;
var active = net.tfminecraft.simplefactions.espionage.EspionageService.stabilityModifiers(f, System.currentTimeMillis());
var active = net.tfminecraft.simplefactions.economy.FrozenReads.espionage(f, System.currentTimeMillis());
if (active.isEmpty()) return stabilityModifiers;
List<StabilityModifier> result = new ArrayList<>(stabilityModifiers);
result.addAll(active);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import java.util.Map;
import java.util.Set;

import net.tfminecraft.simplefactions.economy.FrozenBalances;
import net.tfminecraft.simplefactions.economy.FrozenReads;
import net.tfminecraft.simplefactions.enums.GuildModifier;
import net.tfminecraft.simplefactions.government.proposal.TaxTarget;
import net.tfminecraft.simplefactions.guild.Guild;
Expand All @@ -31,15 +31,15 @@ private BranchIncomePreview() {}

public static final class Prepared {
private final ProvinceManager snapshot;
private final FrozenBalances balances = FrozenBalances.capture();
private final FrozenReads reads = FrozenReads.capture();

private Prepared(ProvinceManager snapshot) {
this.snapshot = snapshot;
}

/** Guild treasuries as they were when this copy was made, for work off the main thread. */
public FrozenBalances balances() {
return balances;
/** Main-thread state as it was when this copy was made, for work off the main thread. */
public FrozenReads reads() {
return reads;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import net.tfminecraft.simplefactions.SimpleFactions;
import net.tfminecraft.simplefactions.diplomacy.RelationType;
import net.tfminecraft.simplefactions.economy.FrozenBalances;
import net.tfminecraft.simplefactions.economy.FrozenReads;
import net.tfminecraft.simplefactions.government.proposal.TaxTarget;
import net.tfminecraft.simplefactions.guild.Guild;
import net.tfminecraft.simplefactions.laws.Law;
Expand All @@ -25,15 +25,15 @@ private EconomicPreview() {}

public static final class Prepared {
private final ProvinceManager snapshot;
private final FrozenBalances balances = FrozenBalances.capture();
private final FrozenReads reads = FrozenReads.capture();

private Prepared(ProvinceManager snapshot) {
this.snapshot = snapshot;
}

/** Guild treasuries as they were when this copy was made, for work off the main thread. */
public FrozenBalances balances() {
return balances;
/** Main-thread state as it was when this copy was made, for work off the main thread. */
public FrozenReads reads() {
return reads;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import java.util.Set;
import java.util.function.ToDoubleFunction;

import net.tfminecraft.simplefactions.economy.FrozenReads;

import net.tfminecraft.simplefactions.vehicles.pool.FactionVehiclePoolService;

import org.bukkit.Bukkit;
Expand Down Expand Up @@ -71,6 +73,11 @@ public static void setNodeUpkeepLookup(ToDoubleFunction<Guild> lookup) {
nodeUpkeepLookup = lookup == null ? guild -> 0.0 : lookup;
}

/** The registered node upkeep, read live. Dowsing answers only on the main thread. */
public static double liveNodeUpkeep(Guild guild) {
return nodeUpkeepLookup.applyAsDouble(guild);
}

// Pool vehicle upkeep is withdrawn in VehicleUpkeepService. This lookup is the ledger line.
private static ToDoubleFunction<String> factionVehicleUpkeep = FactionVehiclePoolService::dailyUpkeepOf;

Expand Down Expand Up @@ -399,7 +406,7 @@ public double getIncome(Cashflow cashflow) {
amount *= net.tfminecraft.simplefactions.inactivity.InactivityService.outputFactor(guild);
break;
case NODES:
amount = -nodeUpkeepLookup.applyAsDouble(guild);
amount = -FrozenReads.nodeUpkeep(guild);
break;
//Mercenary contracts
case MERCENARY_CONTRACT:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public static void schedule(
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
BranchIncomePreview.Estimate estimate;
try {
estimate = prepared.balances().read(
estimate = prepared.reads().read(
() -> BranchIncomePreview.estimate(prepared, guild, current, hypothetical));
} catch (RuntimeException ex) {
plugin.getLogger().log(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import org.bukkit.Chunk;
import net.tfminecraft.denareconomy.api.Denars;
import net.tfminecraft.simplefactions.Cache;
import net.tfminecraft.simplefactions.economy.FrozenBalances;
import net.tfminecraft.simplefactions.economy.FrozenReads;
import net.tfminecraft.simplefactions.economy.Money;
import net.tfminecraft.simplefactions.guild.Guild;

Expand All @@ -24,7 +24,7 @@ public Bank(Guild guild, double amount, Chunk chunk) {
this.chunk = chunk;
}
public String accountId() { return Denars.guild(guild.getIncarnationId()); }
public Double getWealth() { return Money.denars(FrozenBalances.balance(accountId())); }
public Double getWealth() { return Money.denars(FrozenReads.balance(accountId())); }
/** Read only by the one-time, ledger-keyed legacy importer during startup. */
public double legacyBalance() { return legacyBalance; }
public Chunk getChunk() { return chunk; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ private static void start(SimpleFactions plugin, Player player, long token) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
Map<Guild, Double> deltas;
try {
deltas = pending.prepared.balances().read(() -> pending.calculator.calculate(pending.prepared));
deltas = pending.prepared.reads().read(() -> pending.calculator.calculate(pending.prepared));
} catch (RuntimeException ex) {
plugin.getLogger().log(Level.WARNING, "Economic impact preview failed", ex);
deltas = null;
Expand Down
Loading
Loading