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
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<denareconomy.version>0.2.4</denareconomy.version>
<rpcharacters.version>2.0.1</rpcharacters.version>
<tlibs.version>2.0.0</tlibs.version>
<vehicleframework.version>2.8.0</vehicleframework.version>
<vehicleframework.version>2.10.0</vehicleframework.version>
<vfbuilders.version>2.1.0</vfbuilders.version>
</properties>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,12 @@ private boolean refreshTrackProvinces(Runnable recalculate) {
}
return net.tfminecraft.simplefactions.guild.hub.VehicleFrameworkTrackProvinces.sample(
Cache.worldName, provinceGrid, provinces);
}, recalculate,
}, () -> {
// Forget cached rail routes first; that also drops the graph's routes.
net.tfminecraft.simplefactions.guild.hub.HubNetwork.forgetRoutes();
net.tfminecraft.simplefactions.guild.network.TradeGraph.refresh(provinceManager);
recalculate.run();
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
message -> getLogger().warning("[SimpleFactions] " + message));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import net.tfminecraft.simplefactions.Cache;
import net.tfminecraft.simplefactions.SimpleFactions;
import net.tfminecraft.simplefactions.guild.Guild;
import net.tfminecraft.simplefactions.guild.network.TradeGraph;
import net.tfminecraft.simplefactions.guild.GuildModifierOverride;
import net.tfminecraft.simplefactions.guild.hub.HubTransport.Link;
import net.tfminecraft.simplefactions.guild.hub.HubTransport.Mode;
Expand Down Expand Up @@ -60,6 +61,7 @@ public static List<Link> linksFor(Guild guild) {

/** Rebuilds every guild's connections. Server thread only. */
public static void refresh(ProvinceManager provinces) {
TradeGraph.refresh(provinces);
Map<String, List<Link>> built = new HashMap<>();
List<Guild> guilds = SupplyHubService.allGuilds();
for (Guild guild : guilds) {
Expand Down Expand Up @@ -96,6 +98,7 @@ public static void refreshIfLive(ProvinceManager provinces) {
*/
public static void forgetRoutes() {
railDistances.clear();
TradeGraph.forgetRoutes();
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,16 @@
package net.tfminecraft.simplefactions.guild.hub;

import java.util.List;
import java.util.Map;
import java.util.Set;

import net.tfminecraft.simplefactions.map.ProvinceGrid;
import net.tfminecraft.simplefactions.map.infra.TrackProvinceLookup;
import net.tfminecraft.simplefactions.map.infra.TrackProvinceLookup.Point;
import net.tfminecraft.simplefactions.map.provinces.Province;
import net.tfminecraft.vehicleframework.VehicleFramework;
import net.tfminecraft.vehicleframework.tracks.TrackRegistry;
import net.tfminecraft.vehicleframework.tracks.TrackSamplePoint;

/** The only track sampler here that names VehicleFramework's sample point type. */
/** Track infrastructure sampling uses the same VehicleFramework boundary as hub routes. */
public final class VehicleFrameworkTrackProvinces {
private VehicleFrameworkTrackProvinces() {}

public static Set<Integer> sample(String world, ProvinceGrid grid, Map<Integer, Province> provinces) {
TrackRegistry tracks = VehicleFramework.getTrackRegistry();
if (tracks == null || world == null) return Set.of();
List<TrackSamplePoint> samples = tracks.sampleTrack(world, 8);
return TrackProvinceLookup.collect(grid, samples.stream()
.map(point -> new Point(point.x(), point.y(), point.z())).toList(), provinces);
return VehicleFrameworkTracks.sample(world, grid, provinces);
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
package net.tfminecraft.simplefactions.guild.hub;

import java.util.OptionalDouble;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import org.bukkit.Bukkit;

import net.tfminecraft.simplefactions.guild.network.RailRoutes.Route;
import net.tfminecraft.simplefactions.installation.Installation;
import net.tfminecraft.simplefactions.loaders.InstallationConfigLoader;
import net.tfminecraft.simplefactions.map.ProvinceGrid;
import net.tfminecraft.simplefactions.map.infra.TrackProvinceLookup;
import net.tfminecraft.simplefactions.map.infra.TrackProvinceLookup.Point;
import net.tfminecraft.simplefactions.map.provinces.Province;
import net.tfminecraft.vehicleframework.VehicleFramework;
import net.tfminecraft.vehicleframework.tracks.TrackRegistry;

/**
* The only class here that names VehicleFramework types, so it is loaded only once
* {@link HubNetwork} has checked that the plugin is enabled.
* the caller has checked that the plugin is enabled, or through the guarded route method.
*/
final class VehicleFrameworkTracks {
public final class VehicleFrameworkTracks {
private VehicleFrameworkTracks() {
}

Expand All @@ -29,4 +39,27 @@ static OptionalDouble distance(String world, Installation from, Installation to)
to.getCenterZ(),
InstallationConfigLoader.getRadius(to.getKind()));
}

/** Empty when VehicleFramework is missing or too old to return route points. */
public static Optional<Route> route(String world, Installation from, Installation to) {
try {
if (!Bukkit.getPluginManager().isPluginEnabled("VehicleFramework")) return Optional.empty();
TrackRegistry tracks = VehicleFramework.getTrackRegistry();
if (tracks == null || world == null) return Optional.empty();
return tracks.shortestRoute(world,
from.getCenterX(), from.getCenterZ(), InstallationConfigLoader.getRadius(from.getKind()),
to.getCenterX(), to.getCenterZ(), InstallationConfigLoader.getRadius(to.getKind()), 8)
.map(route -> new Route(route.length(), route.points().stream()
.map(point -> new Point(point.x(), point.y(), point.z())).toList()));
} catch (RuntimeException | LinkageError e) {
return Optional.empty();
}
}

static Set<Integer> sample(String world, ProvinceGrid grid, Map<Integer, Province> provinces) {
TrackRegistry tracks = VehicleFramework.getTrackRegistry();
if (tracks == null || world == null) return Set.of();
return TrackProvinceLookup.collect(grid, tracks.sampleTrack(world, 8).stream()
.map(point -> new Point(point.x(), point.y(), point.z())).toList(), provinces);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package net.tfminecraft.simplefactions.guild.network;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.bukkit.Bukkit;

import net.tfminecraft.simplefactions.Cache;
import net.tfminecraft.simplefactions.SimpleFactions;
import net.tfminecraft.simplefactions.guild.hub.VehicleFrameworkTracks;
import net.tfminecraft.simplefactions.guild.network.RailRoutes.Route;
import net.tfminecraft.simplefactions.guild.network.TradeGraphBuilder.ProvinceData;
import net.tfminecraft.simplefactions.guild.network.TradeGraphBuilder.Site;
import net.tfminecraft.simplefactions.installation.Installation;
import net.tfminecraft.simplefactions.installation.InstallationKind;
import net.tfminecraft.simplefactions.loaders.InstallationConfigLoader;
import net.tfminecraft.simplefactions.managers.FactionManager;
import net.tfminecraft.simplefactions.managers.ProvinceManager;
import net.tfminecraft.simplefactions.map.ProvinceGrid;
import net.tfminecraft.simplefactions.map.provinces.Province;
import net.tfminecraft.simplefactions.objects.Faction;

/** Takes the server-thread data needed by the pure builder. */
final class LiveTradeGraph {
private static final RailRouteCache routes = new RailRouteCache(System::currentTimeMillis);

private LiveTradeGraph() { }

static TradeGraph build(ProvinceManager provinces) {
List<Site> sites = new ArrayList<>();
Map<Installation, String> identities = new IdentityHashMap<>();
if (FactionManager.factions != null) {
for (Faction faction : FactionManager.factions) {
if (faction == null || faction.getId() == null || faction.getInstallationHandler() == null) continue;
// Pending construction is separate from getAll(), as in SupplyHubService.findInstallation.
for (Installation installation : faction.getInstallationHandler().getAll()) {
if (installation == null || installation.getKind() == InstallationKind.FORT) continue;
sites.add(new Site(faction.getId(), installation, InstallationConfigLoader.getHubSlots(
installation.getKind(), installation.getLevel()), true));
identities.put(installation,
faction.getId() + ":" + installation.getProvince() + ":" + installation.getId());
}
}
}
Map<Integer, ProvinceData> map = new HashMap<>();
for (Province province : provinces.getProvinces()) {
if (province.isValid()) {
map.put(province.getId(), new ProvinceData(province.getTerrain(), province.getNeighbours()));
}
}
SimpleFactions plugin = SimpleFactions.getInstance();
ProvinceGrid grid = plugin == null ? null : plugin.getProvinceGrid();
String world = Cache.worldName;
return TradeGraphBuilder.build(sites, map, (from, to) -> {
String left = identities.get(from);
String right = identities.get(to);
if (left == null || right == null) return Optional.empty();
return routes.route(left, right, () -> trackRoute(world, from, to));
}, point -> grid == null ? 0 : grid.getAt(
(int) Math.floor(point.x()), (int) Math.floor(point.z())));
}

/**
* The plugin check sits outside {@link VehicleFrameworkTracks}, matching hub distances.
* A missing or older plugin must not fail the refresh.
*/
private static Optional<Route> trackRoute(String world, Installation from, Installation to) {
try {
if (!Bukkit.getPluginManager().isPluginEnabled("VehicleFramework")) return Optional.empty();
return VehicleFrameworkTracks.route(world, from, to);
} catch (RuntimeException | LinkageError e) {
return Optional.empty();
}
}

static void forgetRoutes() {
routes.forget();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package net.tfminecraft.simplefactions.guild.network;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.LongSupplier;
import java.util.function.Supplier;

import net.tfminecraft.simplefactions.guild.network.RailRoutes.Route;

/** Hits last until forgotten; misses are tried again after thirty seconds. */
final class RailRouteCache {
private record Key(String first, String second) { }
private record Cached(Optional<Route> route, long expiresAt) { }

private final Map<Key, Cached> routes = new HashMap<>();
private final LongSupplier clock;

RailRouteCache(LongSupplier clock) {
this.clock = clock;
}

Optional<Route> route(String from, String to, Supplier<Optional<Route>> source) {
if (from == null || to == null) return Optional.empty();
boolean reverse = from.compareTo(to) > 0;
Key key = reverse ? new Key(to, from) : new Key(from, to);
long now = clock.getAsLong();
Cached known = routes.get(key);
if (known != null && (known.route().isPresent() || known.expiresAt() > now)) {
return reverse ? known.route().map(Route::reversed) : known.route();
}
Optional<Route> measured = source.get();
routes.put(key, new Cached(reverse ? measured.map(Route::reversed) : measured,
measured.isPresent() ? Long.MAX_VALUE : now + 30_000L));
return measured;
}

void forget() {
routes.clear();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package net.tfminecraft.simplefactions.guild.network;

import java.util.List;
import java.util.Optional;

import net.tfminecraft.simplefactions.installation.Installation;
import net.tfminecraft.simplefactions.map.infra.TrackProvinceLookup.Point;

/** Routes contain points in order from the first installation to the second. */
@FunctionalInterface
public interface RailRoutes {
Optional<Route> route(Installation from, Installation to);

record Route(double length, List<Point> points) {
public Route {
points = List.copyOf(points);
}

public Route reversed() {
return new Route(length, points.reversed());
}
}
}
Loading
Loading