package crazypants.enderio.machine.transceiver; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import net.minecraft.item.ItemStack; import net.minecraftforge.common.DimensionManager; import net.minecraftforge.common.util.ForgeDirection; import net.minecraftforge.fluids.Fluid; import net.minecraftforge.fluids.FluidStack; import net.minecraftforge.fluids.FluidTankInfo; import org.apache.commons.io.FileUtils; import com.enderio.core.common.util.ItemUtil; import com.enderio.core.common.util.PlayerUtil; import com.enderio.core.common.util.RoundRobinIterator; import com.google.common.collect.ListMultimap; import com.google.common.collect.MultimapBuilder; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import crazypants.enderio.Log; import crazypants.enderio.config.Config; import crazypants.enderio.machine.SlotDefinition; import crazypants.util.UserIdent; public class ServerChannelRegister extends ChannelRegister { public static ServerChannelRegister instance = new ServerChannelRegister(); private static final ExecutorService saveExecutor = Executors.newSingleThreadExecutor(); public static void load() { instance.reset(); File dataFile = getDataFile(); if(!dataFile.exists()) { dataFile = getFallbackDataFile(); if(!dataFile.exists()) { return; } else { Log.warn("ServerChannelRegister: Using fallback save location " + dataFile.getAbsolutePath()); } } try { JsonReader reader = new JsonReader(new FileReader(getDataFile())); reader.beginArray(); while (reader.hasNext()) { String name = null; int ordinal = -1; UserIdent ident = UserIdent.nobody; String uuid = null; String playername = null; String legacyUser = null; reader.beginObject(); while (reader.hasNext()) { String key = reader.nextName(); if ("name".equals(key)) { name = reader.nextString(); } else if ("user".equals(key)) { legacyUser = reader.nextString(); } else if ("uuid".equals(key)) { uuid = reader.nextString(); } else if ("playername".equals(key)) { playername = reader.nextString(); } else if ("type".equals(key)) { ordinal = reader.nextInt(); } else { Log.warn("ServerChannelRegister: Unknown key '" + key + "' in dimensionalTransceiver.json"); } } reader.endObject(); if (name == null || ordinal == -1) { Log.warn("ServerChannelRegister: Incomplete channel in dimensionalTransceiver.json"); } else { if (uuid != null || playername != null) { ident = UserIdent.create(uuid, playername); } else if (legacyUser != null) { ident = UserIdent.create(legacyUser); } Channel chan = new Channel(name, ident, ChannelType.values()[ordinal]); instance.addChannel(chan); } } reader.endArray(); reader.close(); } catch (Exception e) { Log.error("Could not read Dimensional Transceiver channels from " + getDataFile().getAbsolutePath() + " : " + e); } } public static void store() { Future<?> future = saveExecutor.submit(new SaveRunnable(copyChannels())); try { //wait up to 5 seconds for it to finish future.get(5, TimeUnit.SECONDS); } catch (Exception e) { Log.warn("Failed to write Transciever Channels on exit: " + e); future.cancel(false); } } private static void queueStore() { saveExecutor.execute(new SaveRunnable(copyChannels())); } private static void doStore(ListMultimap<ChannelType, Channel> channels) { File dataFile = getDataFile(); if(!createFolderAndWriteFile(channels, dataFile)) { dataFile = getFallbackDataFile(); Log.error("ServerChannelRegister: Attempting to write Dimensional Transceiver data to fallback location: " + dataFile.getAbsolutePath()); try { writeFile(copyChannels(), dataFile); } catch (Exception e) { Log.error("ServerChannelRegister: Could not write Dimensional Transceiver data fallback location " + dataFile.getAbsolutePath() + " channels not saved: " + e.getMessage()); return; } } Log.info("ServerChannelRegister: Dimensional Transceiver data saved to " + dataFile.getAbsolutePath()); } private static boolean createFolderAndWriteFile(ListMultimap<ChannelType, Channel> channels, File dataFile) { try { File parentFolder = dataFile.getParentFile(); FileUtils.forceMkdir(parentFolder); writeFile(channels, dataFile); return true; } catch (Exception e) { Log.error("ServerChannelRegister: Could not write Dimensional Transceiver channels to " + dataFile.getAbsolutePath() + " : " + e); return false; } } protected static void writeFile(ListMultimap<ChannelType, Channel> channels, File dataFile) throws IOException { if(dataFile.exists()) { File tmpFile = new File(dataFile.getAbsolutePath() + ".tmp"); doWriteFile(channels, tmpFile); if(FileUtils.deleteQuietly(dataFile)) { tmpFile.renameTo(dataFile); } } else { doWriteFile(channels, dataFile); } } protected static void doWriteFile(ListMultimap<ChannelType, Channel> channels, File dataFile) throws IOException { JsonWriter writer = new JsonWriter(new FileWriter(dataFile, false)); writer.setIndent(" "); writer.beginArray(); for (Channel chan : channels.values()) { writer.beginObject(); writer.name("name").value(chan.getName()); if (chan.getUser() != null && chan.getUser() != UserIdent.nobody) { writer.name("uuid").value(chan.getUser().getUUIDString()); writer.name("playername").value(chan.getUser().getPlayerName()); } writer.name("type").value(chan.getType().ordinal()); writer.endObject(); } writer.endArray(); writer.close(); } private static File getDataFile() { return new File(DimensionManager.getCurrentSaveRootDirectory(), "enderio/dimensionalTransceiver.json"); } private static File getFallbackDataFile() { return new File(DimensionManager.getCurrentSaveRootDirectory(), "dimensionalTransceiver.json"); } private static ListMultimap<ChannelType, Channel> copyChannels() { //NB: deep copy not needed as all types are immuatble ListMultimap<ChannelType, Channel> copy = MultimapBuilder.enumKeys(ChannelType.class).arrayListValues().build(); for (Entry<ChannelType, Channel> entry : instance.channels.entries()) { copy.put(entry.getKey(), entry.getValue()); } return copy; } //----------------------------------------------------------------------------------------------- private final List<TileTransceiver> transceivers = new ArrayList<TileTransceiver>(); private Map<Channel, RoundRobinIterator<TileTransceiver>> iterators = new HashMap<Channel, RoundRobinIterator<TileTransceiver>>(); private ServerChannelRegister() { } public void register(TileTransceiver transceiver) { transceivers.add(transceiver); } public void dergister(TileTransceiver transceiver) { transceivers.remove(transceiver); } @Override public void reset() { super.reset(); transceivers.clear(); iterators.clear(); } @Override public void removeChannel(Channel channel) { super.removeChannel(channel); for (TileTransceiver trans : transceivers) { trans.removeRecieveChanel(channel); trans.removeSendChanel(channel); } iterators.remove(channel); queueStore(); } @Override public void addChannel(Channel channel) { super.addChannel(channel); queueStore(); } public RoundRobinIterator<TileTransceiver> getIterator(Channel channel) { RoundRobinIterator<TileTransceiver> res = iterators.get(channel); if(res == null) { res = new RoundRobinIterator<TileTransceiver>(transceivers); iterators.put(channel, res); } return res; } //Power public void sendPower(TileTransceiver sender, int canSend, Channel channel) { RoundRobinIterator<TileTransceiver> iter = getIterator(channel); for (TileTransceiver trans : iter) { if(trans != sender && trans.getRecieveChannels(ChannelType.POWER).contains(channel)) { double invLoss = 1 - Config.transceiverEnergyLoss; int canSendWithLoss = (int) Math.round(canSend * invLoss); int recieved = trans.receiveEnergy(ForgeDirection.UNKNOWN, canSendWithLoss, false); if(recieved > 0) { int recievedPlusLoss = (int) Math.round(recieved / invLoss); sender.usePower(recievedPlusLoss); } } } } //Fluid public FluidTankInfo[] getTankInfoForChannels(TileTransceiver tileTransceiver, Set<Channel> channels) { List<FluidTankInfo> infos = new ArrayList<FluidTankInfo>(); for (TileTransceiver tran : transceivers) { if(tran != tileTransceiver) { tran.getRecieveTankInfo(infos, channels); } } return infos.toArray(new FluidTankInfo[infos.size()]); } public boolean canFill(TileTransceiver tileTransceiver, Set<Channel> set, Fluid fluid) { for (TileTransceiver tran : transceivers) { if(tran != tileTransceiver) { if(tran.canReceive(set, fluid)) { return true; } } } return false; } public int fill(TileTransceiver from, Set<Channel> list, FluidStack resource, boolean doFill) { if(resource == null || !from.hasPower()) { return 0; } for (Channel channel : list) { RoundRobinIterator<TileTransceiver> iter = getIterator(channel); for (TileTransceiver trans : iter) { if(trans != from) { int val = trans.recieveFluid(list, resource, doFill); if(val > 0) { if(doFill && Config.transceiverBucketTransmissionCostRF > 0) { int powerUsed = (int) Math.max(1, Config.transceiverBucketTransmissionCostRF * val / 1000d); from.usePower(powerUsed); } return val; } } } } return 0; } //Item public void sendItem(TileTransceiver from, Set<Channel> channels, int slot, ItemStack contents) { if(!from.hasPower()) { return; } if(!from.getSendItemFilter().doesItemPassFilter(null, contents)) { return; } for (Channel channel : channels) { RoundRobinIterator<TileTransceiver> iter = getIterator(channel); for (TileTransceiver trans : iter) { if(trans != from && trans.getRecieveChannels(ChannelType.ITEM).contains(channel) && trans.getRedstoneChecksPassed()) { contents = sendItem(from, slot, contents, trans); if(contents == null) { return; } } } } } private ItemStack sendItem(TileTransceiver from, int slot, ItemStack contents, TileTransceiver to) { SlotDefinition sd = to.getSlotDefinition(); if(!to.getReceiveItemFilter().doesItemPassFilter(null, contents)) { return contents; } //try merging into existing stacks boolean sendComplete = false; // Only allow 1 stack per item type for (int i = sd.minOutputSlot; i <= sd.maxOutputSlot && !sendComplete; i++) { ItemStack existing = to.getStackInSlot(i); if(ItemUtil.areStacksEqual(existing, contents)) { sendComplete = true; if(existing.stackSize < to.getInventoryStackLimit()) { int numCanMerge = existing.getMaxStackSize() - existing.stackSize; numCanMerge = Math.min(numCanMerge, contents.stackSize); ItemStack remaining; if(numCanMerge >= contents.stackSize) { remaining = null; } else { remaining = contents.copy(); remaining.stackSize -= numCanMerge; } ItemStack destStack = existing.copy(); destStack.stackSize += numCanMerge; to.setInventorySlotContents(i, destStack); from.setInventorySlotContents(slot, remaining); if(remaining == null) { return null; } else { contents = remaining.copy(); } } } } if(!sendComplete) { //then fill empty stack for (int i = sd.minOutputSlot; i <= sd.maxOutputSlot; i++) { ItemStack existing = to.getStackInSlot(i); if(existing == null) { int numCanMerge = Math.min(contents.stackSize, to.getInventoryStackLimit()); if(numCanMerge > 0) { ItemStack destStack = contents.copy(); destStack.stackSize = numCanMerge; to.setInventorySlotContents(i, destStack); ItemStack remaining = contents.copy(); remaining.stackSize -= numCanMerge; if(remaining.stackSize == 0) { remaining = null; } from.setInventorySlotContents(slot, remaining); return null; } } } } return contents; } private static class SaveRunnable implements Runnable { private ListMultimap<ChannelType, Channel> chans; public SaveRunnable(ListMultimap<ChannelType, Channel> chans) { this.chans = chans; } @Override public void run() { doStore(chans); } } }