package speedytools.serverside; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.util.BlockPos; import net.minecraft.world.World; import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; import net.minecraftforge.fml.relauncher.Side; import speedytools.common.network.Packet250Base; import speedytools.common.network.Packet250ServerSelectionGeneration; import speedytools.common.network.Packet250Types; import speedytools.common.network.multipart.*; import speedytools.common.selections.BlockVoxelMultiSelector; import speedytools.common.selections.VoxelSelectionWithOrigin; import speedytools.common.utilities.ErrorLog; import speedytools.serverside.network.PacketHandlerRegistryServer; import speedytools.serverside.network.PacketSenderServer; import java.lang.ref.WeakReference; import java.util.Deque; import java.util.Iterator; import java.util.LinkedList; import java.util.WeakHashMap; /** * User: The Grey Ghost * Date: 19/05/2014 * Handles synchronisation of the voxelselection for each client: * 1) Remembers the current voxel selection of each player, received from the client. *2) In response to a Packet250ServerSelectionGeneration command from the client, generates a local selection * and then sends it back to the client * * Automatically registers itself for addition/removal of players, processing of incoming packets * Usage: * (1) tick() must be called at frequent intervals to check for timeouts - at least once per second * (2) getCurrentSelection() to retrieve the current VoxelSelection for a player. The current VoxelSelection * isn't updated until an entire new selection is received / generated. */ public class ServerVoxelSelections { public ServerVoxelSelections(PacketHandlerRegistryServer i_packetHandlerRegistryServer, PlayerTrackerRegistry playerTrackerRegistry) { packetHandlerVoxelsFromClient = this.new PacketHandlerVoxelsFromClient(); packetHandlerVoxelsToClient = this.new PacketHandlerVoxelsToClient(); packetHandlerRegistryServer = i_packetHandlerRegistryServer; Packet250MultipartSegment.registerHandler(packetHandlerRegistryServer, packetHandlerVoxelsFromClient, Side.SERVER, Packet250Types.PACKET250_SELECTION_PACKET); Packet250MultipartSegmentAcknowledge.registerHandler(packetHandlerRegistryServer, packetHandlerVoxelsToClient, Side.SERVER, Packet250Types.PACKET250_SELECTION_PACKET_ACKNOWLEDGE); Packet250ServerSelectionGeneration.registerHandler(packetHandlerRegistryServer, this.new PacketHandlerServerSelectionGeneration(), Side.SERVER); playerTracker = this.new PlayerTracker(); playerTrackerRegistry.registerHandler(playerTracker); } /** returns the current VoxelSelection for this player, or null if none * The current VoxelSelection isn't updated until an entire new selection is received. * @param player * @return current selection for the given player, or null if none */ public VoxelSelectionWithOrigin getVoxelSelection(EntityPlayerMP player) { return playerSelections.get(player); } /** * handle timeouts etc * @param maximumDurationInNS - the maximum amount of time to spend generating selections for clients. 0 = don't generate any. */ public void tick(long maximumDurationInNS) { for (MultipartOneAtATimeReceiver receiver : playerMOATreceivers.values()) { receiver.onTick(); } for (MultipartOneAtATimeSender sender : playerMOATsenders.values()) { sender.onTick(); } if (maximumDurationInNS == 0) return; boolean foundSuitable = false; CommandQueueEntry currentCommand; do { currentCommand = commandQueue.peekFirst(); if (currentCommand == null) return; if (currentCommand.entityPlayerMP.get() == null) { commandQueue.removeFirst(); } else { foundSuitable = true; } } while (!foundSuitable); EntityPlayerMP entityPlayerMP = currentCommand.entityPlayerMP.get(); World playerWorld = entityPlayerMP.getEntityWorld(); Packet250ServerSelectionGeneration commandPacket = currentCommand.commandPacket; if (!currentCommand.hasStarted) { BlockVoxelMultiSelector blockVoxelMultiSelector = new BlockVoxelMultiSelector(); playerBlockVoxelMultiSelectors.put(entityPlayerMP, blockVoxelMultiSelector); playerCommandStatus.put(entityPlayerMP, CommandStatus.EXECUTING); currentCommand.blockVoxelMultiSelector = blockVoxelMultiSelector; switch (commandPacket.getCommand()) { case ALL_IN_BOX: { blockVoxelMultiSelector.selectAllInBoxStart(playerWorld, commandPacket.getCorner1(), commandPacket.getCorner2()); break; } case UNBOUND_FILL: { blockVoxelMultiSelector.selectUnboundFillStart(playerWorld, commandPacket.getFillAlgorithmSettings()); break; } case BOUND_FILL: { blockVoxelMultiSelector.selectBoundFillStart(playerWorld, commandPacket.getFillAlgorithmSettings(), commandPacket.getCorner1(), commandPacket.getCorner2()); break; } default: { ErrorLog.defaultLog().severe("Invalid command in ServerVoxelSelections: " + commandPacket.getCommand()); break; } } currentCommand.hasStarted = true; } else { BlockVoxelMultiSelector blockVoxelMultiSelector = currentCommand.blockVoxelMultiSelector; float progress = blockVoxelMultiSelector.continueSelectionGeneration(playerWorld, maximumDurationInNS); if (progress < 0) { // finished BlockPos origin = blockVoxelMultiSelector.getWorldOrigin(); VoxelSelectionWithOrigin newSelection = new VoxelSelectionWithOrigin(origin.getX(), origin.getY(), origin.getZ(), blockVoxelMultiSelector.getSelection()); // System.out.println("New selection origin: [" + newSelection.getWxOrigin() // + ", " + newSelection.getWyOrigin() // + ", " + newSelection.getWzOrigin()+"]"); playerSelections.put(entityPlayerMP, newSelection); playerBlockVoxelMultiSelectors.remove(entityPlayerMP); playerCommandStatus.put(entityPlayerMP, CommandStatus.COMPLETED); MultipartOneAtATimeSender sender = playerMOATsenders.get(entityPlayerMP); if (sender != null) { SelectionPacket selectionPacket = SelectionPacket.createSenderPacket(blockVoxelMultiSelector, Side.SERVER); SenderLinkage newLinkage = new SenderLinkage(entityPlayerMP, selectionPacket.getUniqueID()); playerSenderLinkages.put(entityPlayerMP, newLinkage); // System.out.println("send new Multipart Selection from server to client, ID = " + selectionPacket.getUniqueID()); // todo remove sender.sendMultipartPacket(newLinkage, selectionPacket); } assert (commandQueue.peekFirst() == currentCommand); commandQueue.removeFirst(); } } } // private BlockVoxelMultiSelector.Matcher getMatcherTranslation(Packet250ServerSelectionGeneration.MatcherType matcherType) // { // switch (matcherType) { // case ANY_NON_AIR: { // return BlockVoxelMultiSelector.Matcher.ALL_NON_AIR; // } // case STARTING_BLOCK_ONLY: { // return BlockVoxelMultiSelector.Matcher.STARTING_BLOCK_ONLY; // } // default: { // ErrorLog.defaultLog().severe("Illegal matcherType:" + matcherType); // return null; // } // } // } // the linkage doesn't actually need to do anything public class SenderLinkage implements MultipartOneAtATimeSender.PacketLinkage { public SenderLinkage(EntityPlayerMP entityPlayerMP, int uniqueID) { myEntityPlayerMP = new WeakReference<EntityPlayerMP>(entityPlayerMP); myUniqueID = uniqueID; } public void progressUpdate(int percentComplete) {} public void packetCompleted() {} public void packetAborted() {} public int getPacketID() {return myUniqueID;} private WeakReference<EntityPlayerMP> myEntityPlayerMP; private int myUniqueID; } // ------------ private void addPlayer(EntityPlayerMP newPlayer) { if (!players.containsKey(newPlayer)) { players.put(newPlayer, 1); // playerSelections starts off null = no selection MultipartOneAtATimeReceiver newReceiver = new MultipartOneAtATimeReceiver(); newReceiver.registerPacketCreator(new SelectionPacket.SelectionPacketCreator()); newReceiver.registerLinkageFactory(new VoxelLinkageFactory(newPlayer)); newReceiver.setPacketSender(new PacketSenderServer(packetHandlerRegistryServer, newPlayer)); playerMOATreceivers.put(newPlayer, newReceiver); MultipartOneAtATimeSender newSender = new MultipartOneAtATimeSender(packetHandlerRegistryServer, null, Packet250Types.PACKET250_SELECTION_PACKET_ACKNOWLEDGE, Side.SERVER); newSender.setPacketSender(new PacketSenderServer(packetHandlerRegistryServer, newPlayer)); playerMOATsenders.put(newPlayer, newSender); // playerBlockVoxelMultiSelectors.put(newPlayer, new BlockVoxelMultiSelector()); } } private void removePlayer(EntityPlayerMP whichPlayer) { players.remove(whichPlayer); playerSelections.remove(whichPlayer); playerMOATreceivers.remove(whichPlayer); playerMOATsenders.remove(whichPlayer); playerBlockVoxelMultiSelectors.remove(whichPlayer); playerSenderLinkages.remove(whichPlayer); } private WeakHashMap<EntityPlayerMP, VoxelSelectionWithOrigin> playerSelections = new WeakHashMap<EntityPlayerMP, VoxelSelectionWithOrigin>(); private WeakHashMap<EntityPlayerMP, Integer> players = new WeakHashMap<EntityPlayerMP, Integer>(); // Integer is a dummy vble private WeakHashMap<EntityPlayerMP, MultipartOneAtATimeReceiver> playerMOATreceivers = new WeakHashMap<EntityPlayerMP, MultipartOneAtATimeReceiver>(); private WeakHashMap<EntityPlayerMP, MultipartOneAtATimeSender> playerMOATsenders = new WeakHashMap<EntityPlayerMP, MultipartOneAtATimeSender>(); private WeakHashMap<EntityPlayerMP, SenderLinkage> playerSenderLinkages = new WeakHashMap<EntityPlayerMP, SenderLinkage>(); private class PlayerTracker implements PlayerTrackerRegistry.IPlayerTracker { public void onPlayerLogin(EntityPlayer player) { EntityPlayerMP entityPlayerMP = (EntityPlayerMP)player; ServerVoxelSelections.this.addPlayer(entityPlayerMP); } public void onPlayerLogout(EntityPlayer player) { EntityPlayerMP entityPlayerMP = (EntityPlayerMP)player; ServerVoxelSelections.this.removePlayer(entityPlayerMP); } public void onPlayerChangedDimension(EntityPlayer player) {} public void onPlayerRespawn(EntityPlayer player) {} } private PlayerTracker playerTracker; // ------ handler for responding to Selection generation commands from the client // Response to commands is a STATUS_REPLY - with 0 for success, -ve number for failure public class PacketHandlerServerSelectionGeneration implements Packet250ServerSelectionGeneration.PacketHandlerMethod { public Packet250ServerSelectionGeneration handlePacket(Packet250ServerSelectionGeneration packet, MessageContext ctx) { final float ERROR_STATUS = -10.0F; EntityPlayerMP entityPlayerMP = ctx.getServerHandler().playerEntity; int uniqueID = packet.getUniqueID(); if (!players.containsKey(entityPlayerMP)) { ErrorLog.defaultLog().info("ServerVoxelSelections:: Packet received from player not in players"); Packet250Base message = Packet250ServerSelectionGeneration.replyFractionCompleted(uniqueID, ERROR_STATUS); sendReplyMessageToClient(message, entityPlayerMP); return null; } Integer lastCommandID = playerLastCommandID.get(entityPlayerMP); if (lastCommandID == null) lastCommandID = Integer.MIN_VALUE; if (uniqueID < lastCommandID) return null; // discard old commands switch(packet.getCommand()) { case STATUS_REQUEST: { if (uniqueID != lastCommandID) return null; // discard old or too-new commands CommandStatus commandStatus = playerCommandStatus.get(entityPlayerMP); if (commandStatus == null) return Packet250ServerSelectionGeneration.replyFractionCompleted(uniqueID, ERROR_STATUS); final float JUST_STARTED_VALUE = 0.01F; Packet250Base message = null; switch (commandStatus) { case QUEUED: { message = Packet250ServerSelectionGeneration.replyFractionCompleted(uniqueID, JUST_STARTED_VALUE); break; } case COMPLETED: { message = Packet250ServerSelectionGeneration.replyFractionCompleted(uniqueID, 1.0F); break; } case EXECUTING: { BlockVoxelMultiSelector blockVoxelMultiSelector = playerBlockVoxelMultiSelectors.get(entityPlayerMP); if (blockVoxelMultiSelector == null) { message = Packet250ServerSelectionGeneration.replyFractionCompleted(uniqueID, ERROR_STATUS); } else { message = Packet250ServerSelectionGeneration.replyFractionCompleted(uniqueID, blockVoxelMultiSelector.getEstimatedFractionComplete()); } break; } default: assert false : "Invalid commandStatus in ServerVoxelSelections:" + commandStatus; } if (message != null) { sendReplyMessageToClient(message, entityPlayerMP); } return null; } case ABORT: { if (uniqueID == lastCommandID) { abortCurrentCommand(entityPlayerMP); } return null; } case ALL_IN_BOX: case UNBOUND_FILL: case BOUND_FILL: { if (uniqueID < lastCommandID) return null; // discard old commands if (uniqueID == lastCommandID) { CommandStatus commandStatus = playerCommandStatus.get(entityPlayerMP); if (commandStatus != CommandStatus.COMPLETED) return null; // ignore this command if we're currently processing it } boolean success = enqueueSelectionCommand(entityPlayerMP, packet); if (success) { playerLastCommandID.put(entityPlayerMP, uniqueID); } Packet250Base message = Packet250ServerSelectionGeneration.replyFractionCompleted(uniqueID, success ? 0.0F : ERROR_STATUS); sendReplyMessageToClient(message, entityPlayerMP); return null; } default: { ErrorLog.defaultLog().severe("Invalid command received in ServerVoxelSelections:" + packet.getCommand()); return null; } } } } // send a reply message back to the client private void sendReplyMessageToClient(Packet250Base message, EntityPlayerMP entityPlayerMP) { packetHandlerRegistryServer.sendToClientSinglePlayer(message, entityPlayerMP); } /** * a client has requested the server to create a selection and send it back. Queue it up for later processing. * @param commandPacket * @return */ private boolean enqueueSelectionCommand(EntityPlayerMP entityPlayerMP, Packet250ServerSelectionGeneration commandPacket) { removeCommandsForPlayer(entityPlayerMP); commandQueue.addLast(new CommandQueueEntry(entityPlayerMP, commandPacket)); playerCommandStatus.put(entityPlayerMP, CommandStatus.QUEUED); return true; } private void abortCurrentCommand(EntityPlayerMP entityPlayerMP) { MultipartOneAtATimeSender sender = playerMOATsenders.get(entityPlayerMP); SenderLinkage senderLinkage = playerSenderLinkages.get(entityPlayerMP); if (sender != null && senderLinkage != null) { sender.abortPacket(senderLinkage); } removeCommandsForPlayer(entityPlayerMP); } private void removeCommandsForPlayer(EntityPlayerMP entityPlayerMP) { Iterator<CommandQueueEntry> iterator = commandQueue.iterator(); while (iterator.hasNext()) { CommandQueueEntry commandQueueEntry = iterator.next(); if (commandQueueEntry.entityPlayerMP.get() == entityPlayerMP) { iterator.remove(); } } playerBlockVoxelMultiSelectors.remove(entityPlayerMP); playerSenderLinkages.remove(entityPlayerMP); } private enum CommandStatus {QUEUED, EXECUTING, COMPLETED} private WeakHashMap<EntityPlayerMP, Integer> playerLastCommandID = new WeakHashMap<EntityPlayerMP, Integer>(); private WeakHashMap<EntityPlayerMP, BlockVoxelMultiSelector> playerBlockVoxelMultiSelectors = new WeakHashMap<EntityPlayerMP, BlockVoxelMultiSelector>(); private WeakHashMap<EntityPlayerMP, CommandStatus> playerCommandStatus = new WeakHashMap<EntityPlayerMP, CommandStatus>(); private class CommandQueueEntry { public WeakReference<EntityPlayerMP> entityPlayerMP; public Packet250ServerSelectionGeneration commandPacket; public boolean hasStarted; public BlockVoxelMultiSelector blockVoxelMultiSelector; public CommandQueueEntry(EntityPlayerMP i_entityPlayerMP, Packet250ServerSelectionGeneration i_commandPacket) { entityPlayerMP = new WeakReference<EntityPlayerMP>(i_entityPlayerMP); commandPacket = i_commandPacket; hasStarted = false; } } Deque<CommandQueueEntry> commandQueue = new LinkedList<CommandQueueEntry>(); // ------ handlers for sending selection to clients public class PacketHandlerVoxelsToClient implements Packet250MultipartSegmentAcknowledge.PacketHandlerMethod { @Override public boolean handlePacket(Packet250MultipartSegmentAcknowledge packetAck, MessageContext ctx) { { EntityPlayerMP entityPlayerMP = ctx.getServerHandler().playerEntity; if (!playerMOATsenders.containsKey(entityPlayerMP)) { ErrorLog.defaultLog().info("ServerVoxelSelections:: Packet received from player not in playerMOATsenders"); return false; } // System.out.println("ServerVoxelSelections packet received"); return playerMOATsenders.get(entityPlayerMP).handleIncomingPacket(packetAck); } } } private PacketHandlerVoxelsToClient packetHandlerVoxelsToClient; // ------ handlers for incoming selections from client public class PacketHandlerVoxelsFromClient implements Packet250MultipartSegment.PacketHandlerMethod { @Override public boolean handlePacket(Packet250MultipartSegment packet250MultipartSegment, MessageContext ctx) { { EntityPlayerMP entityPlayerMP = ctx.getServerHandler().playerEntity; if (!playerMOATreceivers.containsKey(entityPlayerMP)) { ErrorLog.defaultLog().info("ServerVoxelSelections:: Packet received from player not in playerMOATreceivers"); return false; } // System.out.println("ServerVoxelSelections packet received"); return playerMOATreceivers.get(entityPlayerMP).processIncomingPacket(packet250MultipartSegment); } } } private PacketHandlerVoxelsFromClient packetHandlerVoxelsFromClient; /** * This class is used by the MultipartOneAtATimeReceiver to communicate the packet transmission progress to the receiver * When the incoming packet is completed, it overwrites the player's current VoxelSelection with the new one. */ public class VoxelPacketLinkage implements MultipartOneAtATimeReceiver.PacketLinkage { public VoxelPacketLinkage(EntityPlayerMP player, SelectionPacket linkedPacket) { // System.out.println("ServerVoxelSelection::VoxelPacketLinkage constructed for Selection Packet ID " + linkedPacket.getUniqueID()); // todo remove myLinkedPacket = linkedPacket; myPlayer = new WeakReference<EntityPlayerMP>(player); } @Override public void progressUpdate(int percentComplete) {} @Override public void packetCompleted() { // System.out.println("VoxelPacketLinkage - completed packet ID " + myLinkedPacket.getUniqueID()); if (myPlayer == null || myPlayer.get() == null || myLinkedPacket == null) return; playerSelections.put(myPlayer.get(), myLinkedPacket.retrieveVoxelSelection()); } @Override public void packetAborted() {} @Override public int getPacketID() {return myLinkedPacket.getUniqueID();} private WeakReference<EntityPlayerMP> myPlayer; private SelectionPacket myLinkedPacket; } /** * The Factory creates a new linkage, which will be used to communicate the packet receiving progress to the recipient */ public class VoxelLinkageFactory implements MultipartOneAtATimeReceiver.PacketReceiverLinkageFactory { public VoxelLinkageFactory(EntityPlayerMP playerMP) { myPlayer = new WeakReference<EntityPlayerMP>(playerMP); } @Override public VoxelPacketLinkage createNewLinkage(MultipartPacket linkedPacket) { assert linkedPacket instanceof SelectionPacket; return ServerVoxelSelections.this.new VoxelPacketLinkage(myPlayer.get(), (SelectionPacket)linkedPacket); } private WeakReference<EntityPlayerMP> myPlayer; } private PacketHandlerRegistryServer packetHandlerRegistryServer; }