PrettyPipes/src/main/java/de/ellpeck/prettypipes/network/PipeNetwork.java

292 lines
12 KiB
Java
Raw Normal View History

2020-04-14 04:21:28 +02:00
package de.ellpeck.prettypipes.network;
2020-04-15 02:16:23 +02:00
import com.google.common.collect.Streams;
2020-04-16 00:39:53 +02:00
import de.ellpeck.prettypipes.PrettyPipes;
2020-04-14 04:21:28 +02:00
import de.ellpeck.prettypipes.Registry;
2020-04-14 14:10:58 +02:00
import de.ellpeck.prettypipes.Utility;
2020-04-16 04:42:42 +02:00
import de.ellpeck.prettypipes.pipe.PipeBlock;
import de.ellpeck.prettypipes.pipe.PipeTileEntity;
2020-04-14 17:14:24 +02:00
import de.ellpeck.prettypipes.packets.PacketHandler;
import de.ellpeck.prettypipes.packets.PacketItemEnterPipe;
2020-04-14 04:21:28 +02:00
import net.minecraft.block.BlockState;
2020-04-14 17:14:24 +02:00
import net.minecraft.item.ItemStack;
2020-04-14 04:21:28 +02:00
import net.minecraft.nbt.CompoundNBT;
2020-04-14 15:02:21 +02:00
import net.minecraft.nbt.ListNBT;
import net.minecraft.nbt.NBTUtil;
2020-04-14 04:21:28 +02:00
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilitySerializable;
2020-04-14 15:02:21 +02:00
import net.minecraftforge.common.util.Constants;
2020-04-14 04:21:28 +02:00
import net.minecraftforge.common.util.LazyOptional;
2020-04-14 17:14:24 +02:00
import org.jgrapht.GraphPath;
2020-04-15 02:16:23 +02:00
import org.jgrapht.ListenableGraph;
import org.jgrapht.alg.interfaces.ShortestPathAlgorithm;
2020-04-14 04:21:28 +02:00
import org.jgrapht.alg.shortestpath.DijkstraShortestPath;
2020-04-15 02:16:23 +02:00
import org.jgrapht.event.GraphEdgeChangeEvent;
import org.jgrapht.event.GraphListener;
import org.jgrapht.event.GraphVertexChangeEvent;
import org.jgrapht.graph.DefaultListenableGraph;
2020-04-14 04:21:28 +02:00
import org.jgrapht.graph.SimpleWeightedGraph;
2020-04-15 02:16:23 +02:00
import org.jgrapht.traverse.BreadthFirstIterator;
2020-04-14 04:21:28 +02:00
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
2020-04-15 02:16:23 +02:00
import java.util.*;
2020-04-16 23:40:35 +02:00
import java.util.function.Function;
2020-04-14 21:04:41 +02:00
import java.util.function.Supplier;
2020-04-15 02:16:23 +02:00
import java.util.stream.Collectors;
2020-04-14 04:21:28 +02:00
2020-04-15 02:16:23 +02:00
public class PipeNetwork implements ICapabilitySerializable<CompoundNBT>, GraphListener<BlockPos, NetworkEdge> {
2020-04-14 04:21:28 +02:00
2020-04-15 02:16:23 +02:00
public final ListenableGraph<BlockPos, NetworkEdge> graph;
private final DijkstraShortestPath<BlockPos, NetworkEdge> dijkstra;
private final Map<BlockPos, List<BlockPos>> nodeToConnectedNodes = new HashMap<>();
2020-04-14 17:14:24 +02:00
private final Map<BlockPos, PipeTileEntity> tileCache = new HashMap<>();
2020-04-14 04:21:28 +02:00
private final World world;
public PipeNetwork(World world) {
this.world = world;
2020-04-15 02:16:23 +02:00
this.graph = new DefaultListenableGraph<>(new SimpleWeightedGraph<>(NetworkEdge.class));
this.graph.addGraphListener(this);
this.dijkstra = new DijkstraShortestPath<>(this.graph);
2020-04-14 04:21:28 +02:00
}
@Nonnull
@Override
public <T> LazyOptional<T> getCapability(@Nonnull Capability<T> cap, @Nullable Direction side) {
return cap == Registry.pipeNetworkCapability ? LazyOptional.of(() -> (T) this) : null;
}
@Override
public CompoundNBT serializeNBT() {
2020-04-14 15:02:21 +02:00
CompoundNBT nbt = new CompoundNBT();
ListNBT nodes = new ListNBT();
for (BlockPos node : this.graph.vertexSet())
nodes.add(NBTUtil.writeBlockPos(node));
nbt.put("nodes", nodes);
ListNBT edges = new ListNBT();
for (NetworkEdge edge : this.graph.edgeSet())
edges.add(edge.serializeNBT());
nbt.put("edges", edges);
return nbt;
2020-04-14 04:21:28 +02:00
}
@Override
public void deserializeNBT(CompoundNBT nbt) {
2020-04-14 15:02:21 +02:00
this.graph.removeAllVertices(new ArrayList<>(this.graph.vertexSet()));
ListNBT nodes = nbt.getList("nodes", Constants.NBT.TAG_COMPOUND);
for (int i = 0; i < nodes.size(); i++)
this.graph.addVertex(NBTUtil.readBlockPos(nodes.getCompound(i)));
ListNBT edges = nbt.getList("edges", Constants.NBT.TAG_COMPOUND);
2020-04-14 17:14:24 +02:00
for (int i = 0; i < edges.size(); i++)
this.addEdge(new NetworkEdge(edges.getCompound(i)));
2020-04-14 04:21:28 +02:00
}
public void addNode(BlockPos pos, BlockState state) {
2020-04-14 17:14:24 +02:00
if (!this.isNode(pos)) {
2020-04-14 04:21:28 +02:00
this.graph.addVertex(pos);
this.refreshNode(pos, state);
}
}
public void removeNode(BlockPos pos) {
2020-04-14 17:14:24 +02:00
if (this.isNode(pos))
2020-04-14 04:21:28 +02:00
this.graph.removeVertex(pos);
}
2020-04-14 17:14:24 +02:00
public boolean isNode(BlockPos pos) {
return this.graph.containsVertex(pos);
}
2020-04-14 04:21:28 +02:00
public void onPipeChanged(BlockPos pos, BlockState state) {
2020-04-14 14:10:58 +02:00
List<NetworkEdge> neighbors = this.createAllEdges(pos, state, true);
2020-04-14 04:21:28 +02:00
// if we only have one neighbor, then there can't be any new connections
2020-04-14 17:14:24 +02:00
if (neighbors.size() <= 1 && !this.isNode(pos))
2020-04-14 04:21:28 +02:00
return;
2020-04-14 15:02:21 +02:00
for (NetworkEdge edge : neighbors)
this.refreshNode(edge.endPipe, this.world.getBlockState(edge.endPipe));
2020-04-14 04:21:28 +02:00
}
2020-04-15 23:13:05 +02:00
public boolean tryInsertItem(BlockPos startPipePos, BlockPos startInventory, ItemStack stack) {
2020-04-16 23:40:35 +02:00
return this.routeItem(startPipePos, startInventory, stack, speed -> new PipeItem(stack, speed));
2020-04-14 21:04:41 +02:00
}
2020-04-16 23:40:35 +02:00
public boolean routeItem(BlockPos startPipePos, BlockPos startInventory, ItemStack stack, Function<Float, PipeItem> itemSupplier) {
2020-04-14 17:14:24 +02:00
if (!this.isNode(startPipePos))
return false;
2020-04-14 21:04:41 +02:00
if (!this.world.isBlockLoaded(startPipePos))
return false;
2020-04-14 17:14:24 +02:00
PipeTileEntity startPipe = this.getPipe(startPipePos);
if (startPipe == null)
return false;
2020-04-16 00:39:53 +02:00
this.startProfile("find_destination");
2020-04-15 02:16:23 +02:00
for (BlockPos pipePos : this.getOrderedDestinations(startPipePos)) {
PipeTileEntity pipe = this.getPipe(pipePos);
2020-04-14 17:14:24 +02:00
BlockPos dest = pipe.getAvailableDestination(stack);
2020-04-16 00:39:53 +02:00
if (dest != null) {
this.endProfile();
2020-04-15 23:13:05 +02:00
return this.routeItemToLocation(startPipePos, startInventory, pipe.getPos(), dest, itemSupplier);
2020-04-16 00:39:53 +02:00
}
2020-04-14 17:14:24 +02:00
}
2020-04-16 00:39:53 +02:00
this.endProfile();
2020-04-14 17:14:24 +02:00
return false;
}
2020-04-16 23:40:35 +02:00
public boolean routeItemToLocation(BlockPos startPipePos, BlockPos startInventory, BlockPos destPipe, BlockPos destInventory, Function<Float, PipeItem> itemSupplier) {
2020-04-14 21:04:41 +02:00
if (!this.isNode(startPipePos))
return false;
if (!this.world.isBlockLoaded(startPipePos))
return false;
PipeTileEntity startPipe = this.getPipe(startPipePos);
if (startPipe == null)
return false;
2020-04-16 00:39:53 +02:00
this.startProfile("get_path");
2020-04-14 21:04:41 +02:00
GraphPath<BlockPos, NetworkEdge> path = this.dijkstra.getPath(startPipePos, destPipe);
2020-04-16 00:39:53 +02:00
this.endProfile();
2020-04-15 23:13:05 +02:00
if (path == null)
return false;
2020-04-16 23:40:35 +02:00
PipeItem item = itemSupplier.apply(startPipe.getItemSpeed());
2020-04-15 23:13:05 +02:00
item.setDestination(startPipePos, startInventory, destPipe, destInventory, path);
2020-04-14 21:04:41 +02:00
if (!startPipe.items.contains(item))
startPipe.items.add(item);
PacketHandler.sendToAllLoaded(this.world, startPipePos, new PacketItemEnterPipe(startPipePos, item));
return true;
}
2020-04-14 17:14:24 +02:00
public PipeTileEntity getPipe(BlockPos pos) {
PipeTileEntity tile = this.tileCache.get(pos);
if (tile == null || tile.isRemoved()) {
tile = Utility.getTileEntity(PipeTileEntity.class, this.world, pos);
this.tileCache.put(pos, tile);
}
return tile;
}
2020-04-14 04:21:28 +02:00
private void refreshNode(BlockPos pos, BlockState state) {
2020-04-16 00:39:53 +02:00
this.startProfile("refresh_node");
2020-04-14 15:02:21 +02:00
this.graph.removeAllEdges(new ArrayList<>(this.graph.edgesOf(pos)));
for (NetworkEdge edge : this.createAllEdges(pos, state, false))
this.addEdge(edge);
2020-04-16 00:39:53 +02:00
this.endProfile();
2020-04-14 15:02:21 +02:00
}
2020-04-14 04:21:28 +02:00
2020-04-14 15:02:21 +02:00
private void addEdge(NetworkEdge edge) {
this.graph.addEdge(edge.startPipe, edge.endPipe, edge);
2020-04-15 02:16:23 +02:00
// only use size - 1 so that nodes aren't counted twice for multi-edge paths
this.graph.setEdgeWeight(edge, edge.pipes.size() - 1);
2020-04-14 04:21:28 +02:00
}
2020-04-14 14:10:58 +02:00
private List<NetworkEdge> createAllEdges(BlockPos pos, BlockState state, boolean allAround) {
2020-04-16 00:39:53 +02:00
this.startProfile("create_all_edges");
2020-04-14 14:10:58 +02:00
List<NetworkEdge> edges = new ArrayList<>();
for (Direction dir : Direction.values()) {
NetworkEdge edge = this.createEdge(pos, state, dir, allAround);
if (edge != null)
edges.add(edge);
}
2020-04-16 00:39:53 +02:00
this.endProfile();
2020-04-14 14:10:58 +02:00
return edges;
2020-04-14 04:21:28 +02:00
}
2020-04-14 14:10:58 +02:00
private NetworkEdge createEdge(BlockPos pos, BlockState state, Direction dir, boolean allAround) {
if (!allAround && !state.get(PipeBlock.DIRECTIONS.get(dir)).isConnected())
return null;
BlockPos currPos = pos.offset(dir);
BlockState currState = this.world.getBlockState(currPos);
if (!(currState.getBlock() instanceof PipeBlock))
return null;
2020-04-16 00:39:53 +02:00
this.startProfile("create_edge");
2020-04-14 17:14:24 +02:00
NetworkEdge edge = new NetworkEdge();
2020-04-14 15:02:21 +02:00
edge.startPipe = pos;
edge.pipes.add(pos);
edge.pipes.add(currPos);
2020-04-14 14:10:58 +02:00
while (true) {
// if we found a vertex, we can stop since that's the next node
// we do this here since the first offset pipe also needs to check this
2020-04-14 17:14:24 +02:00
if (this.isNode(currPos)) {
2020-04-14 14:10:58 +02:00
edge.endPipe = edge.pipes.get(edge.pipes.size() - 1);
2020-04-16 00:39:53 +02:00
this.endProfile();
2020-04-14 14:10:58 +02:00
return edge;
}
boolean found = false;
for (Direction nextDir : Direction.values()) {
if (!currState.get(PipeBlock.DIRECTIONS.get(nextDir)).isConnected())
continue;
BlockPos offset = currPos.offset(nextDir);
BlockState offState = this.world.getBlockState(offset);
if (!(offState.getBlock() instanceof PipeBlock))
continue;
2020-04-14 15:02:21 +02:00
if (edge.pipes.contains(offset))
continue;
edge.pipes.add(offset);
2020-04-14 14:10:58 +02:00
currPos = offset;
currState = offState;
found = true;
2020-04-14 04:21:28 +02:00
break;
}
2020-04-14 14:10:58 +02:00
if (!found)
break;
2020-04-14 04:21:28 +02:00
}
2020-04-16 00:39:53 +02:00
this.endProfile();
2020-04-14 14:10:58 +02:00
return null;
2020-04-14 04:21:28 +02:00
}
public static PipeNetwork get(World world) {
return world.getCapability(Registry.pipeNetworkCapability).orElse(null);
}
2020-04-15 02:16:23 +02:00
private List<BlockPos> getOrderedDestinations(BlockPos node) {
List<BlockPos> ret = this.nodeToConnectedNodes.get(node);
if (ret == null) {
2020-04-16 00:39:53 +02:00
this.startProfile("compile_connected_nodes");
2020-04-15 02:16:23 +02:00
ShortestPathAlgorithm.SingleSourcePaths<BlockPos, NetworkEdge> paths = this.dijkstra.getPaths(node);
// sort destinations first by their priority (eg trash pipes should be last)
// and then by their distance from the specified node
ret = Streams.stream(new BreadthFirstIterator<>(this.graph, node))
.sorted(Comparator.<BlockPos>comparingInt(p -> this.getPipe(p).getPriority()).reversed().thenComparing(paths::getWeight))
.collect(Collectors.toList());
this.nodeToConnectedNodes.put(node, ret);
2020-04-16 00:39:53 +02:00
this.endProfile();
2020-04-15 02:16:23 +02:00
}
return ret;
}
@Override
public void edgeAdded(GraphEdgeChangeEvent<BlockPos, NetworkEdge> e) {
this.edgeModified(e);
}
@Override
public void edgeRemoved(GraphEdgeChangeEvent<BlockPos, NetworkEdge> e) {
this.edgeModified(e);
}
private void edgeModified(GraphEdgeChangeEvent<BlockPos, NetworkEdge> e) {
// uncache all connection infos that contain the removed edge's vertices
2020-04-16 00:39:53 +02:00
this.startProfile("clear_node_cache");
2020-04-15 02:16:23 +02:00
this.nodeToConnectedNodes.values().removeIf(
nodes -> nodes.stream().anyMatch(n -> n.equals(e.getEdgeSource()) || n.equals(e.getEdgeTarget())));
2020-04-16 00:39:53 +02:00
this.endProfile();
2020-04-15 02:16:23 +02:00
}
@Override
public void vertexAdded(GraphVertexChangeEvent<BlockPos> e) {
}
@Override
public void vertexRemoved(GraphVertexChangeEvent<BlockPos> e) {
}
2020-04-16 00:39:53 +02:00
private void startProfile(String name) {
this.world.getProfiler().startSection(() -> PrettyPipes.ID + ":pipe_network_" + name);
}
private void endProfile() {
this.world.getProfiler().endSection();
}
2020-04-14 04:21:28 +02:00
}