/* * This file is part of Applied Energistics 2. * Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved. * * Applied Energistics 2 is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Applied Energistics 2 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Applied Energistics 2. If not, see . */ package appeng.parts.misc; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import net.minecraft.item.ItemStack; import net.minecraftforge.items.IItemHandler; import appeng.api.config.Actionable; import appeng.api.networking.security.BaseActionSource; import appeng.api.networking.storage.IBaseMonitor; import appeng.api.networking.ticking.TickRateModulation; import appeng.api.storage.IMEInventory; import appeng.api.storage.IMEMonitorHandlerReceiver; import appeng.api.storage.StorageChannel; import appeng.api.storage.data.IAEItemStack; import appeng.api.storage.data.IItemList; import appeng.core.AELog; import appeng.me.storage.ITickingMonitor; import appeng.util.Platform; import appeng.util.item.AEItemStack; /** * Wraps an Item Handler in such a way that it can be used as an IMEInventory for items. */ class ItemHandlerAdapter implements IMEInventory, IBaseMonitor, ITickingMonitor { private final Map, Object> listeners = new HashMap<>(); private BaseActionSource mySource; private final IItemHandler itemHandler; private ItemStack[] cachedStacks = new ItemStack[0]; private IAEItemStack[] cachedAeStacks = new IAEItemStack[0]; ItemHandlerAdapter( IItemHandler itemHandler ) { this.itemHandler = itemHandler; } @Override public IAEItemStack injectItems( IAEItemStack iox, Actionable type, BaseActionSource src ) { ItemStack orgInput = iox.getItemStack(); ItemStack remaining = orgInput; int slotCount = itemHandler.getSlots(); boolean simulate = ( type == Actionable.SIMULATE ); // This uses a brute force approach and tries to jam it in every slot the inventory exposes. for( int i = 0; i < slotCount && remaining != null; i++ ) { remaining = itemHandler.insertItem( i, remaining, simulate ); } // At this point, we still have some items left... if( remaining == orgInput ) { // The stack remained unmodified, target inventory is full return iox; } if( type == Actionable.MODULATE ) { this.onTick(); } return AEItemStack.create( remaining ); } @Override public IAEItemStack extractItems( IAEItemStack request, Actionable mode, BaseActionSource src ) { ItemStack req = request.getItemStack(); int remainingSize = req.stackSize; // Use this to gather the requested items ItemStack gathered = null; final boolean simulate = ( mode == Actionable.SIMULATE ); for( int i = 0; i < itemHandler.getSlots(); i++ ) { ItemStack sub = itemHandler.getStackInSlot( i ); if( !Platform.isSameItem( sub, req ) ) { continue; } ItemStack extracted; // We have to loop here because according to the docs, the handler shouldn't return a stack with size > maxSize, even if we // request more. So even if it returns a valid stack, it might have more stuff. do { extracted = itemHandler.extractItem( i, remainingSize, simulate ); if( extracted != null ) { if( extracted.stackSize > remainingSize ) { // Something broke. It should never return more than we requested... We're going to silently eat the remainder AELog.warn( "Mod that provided item handler {} is broken. Returned {} items, even though we requested {}.", itemHandler.getClass().getSimpleName(), extracted.stackSize, remainingSize ); extracted.stackSize = remainingSize; } // We're just gonna use the first stack we get our hands on as the template for the rest if( gathered == null ) { gathered = extracted; } else { gathered.stackSize += extracted.stackSize; } remainingSize -= gathered.stackSize; } } while( extracted != null && remainingSize > 0 ); // Done? if( remainingSize <= 0 ) { break; } } if( gathered != null ) { if( mode == Actionable.MODULATE ) { this.onTick(); } return AEItemStack.create( gathered ); } return null; } @Override public TickRateModulation onTick() { LinkedList changes = new LinkedList<>(); int slots = itemHandler.getSlots(); // Make room for new slots if( slots > cachedStacks.length ) { cachedStacks = Arrays.copyOf( cachedStacks, slots ); cachedAeStacks = Arrays.copyOf( cachedAeStacks, slots ); } for( int slot = 0; slot < slots; slot++ ) { // Save the old stuff ItemStack oldIS = cachedStacks[slot]; IAEItemStack oldAeIS = cachedAeStacks[slot]; ItemStack newIS = itemHandler.getStackInSlot( slot ); if( this.isDifferent( newIS, oldIS ) ) { addItemChange( slot, oldAeIS, newIS, changes ); } else if( newIS != null && oldIS != null ) { addPossibleStackSizeChange( slot, oldAeIS, newIS, changes ); } } // Handle cases where the number of slots actually is lower now than before if( slots < cachedStacks.length ) { for( int slot = slots; slot < cachedStacks.length; slot++ ) { IAEItemStack aeStack = cachedAeStacks[slot]; if( aeStack != null ) { IAEItemStack a = aeStack.copy(); a.setStackSize( -a.getStackSize() ); changes.add( a ); } } // Reduce the cache size cachedStacks = Arrays.copyOf( cachedStacks, slots ); cachedAeStacks = Arrays.copyOf( cachedAeStacks, slots ); } if( !changes.isEmpty() ) { this.postDifference( changes ); return TickRateModulation.URGENT; } else { return TickRateModulation.SLOWER; } } private void addItemChange( int slot, IAEItemStack oldAeIS, ItemStack newIS, List changes ) { // Completely different item cachedStacks[slot] = newIS; cachedAeStacks[slot] = AEItemStack.create( newIS ); // If we had a stack previously in this slot, notify the newtork about its disappearance if( oldAeIS != null ) { oldAeIS.setStackSize( -oldAeIS.getStackSize() ); changes.add( oldAeIS ); } // Notify the network about the new stack. Note that this is null if newIS was null if( cachedAeStacks[slot] != null ) { changes.add( cachedAeStacks[slot] ); } } private void addPossibleStackSizeChange( int slot, IAEItemStack oldAeIS, ItemStack newIS, List changes ) { // Still the same item, but amount might have changed long diff = newIS.stackSize - oldAeIS.getStackSize(); if( diff != 0 ) { IAEItemStack stack = oldAeIS.copy(); stack.setStackSize( newIS.stackSize ); cachedStacks[slot] = newIS; cachedAeStacks[slot] = stack; final IAEItemStack a = stack.copy(); a.setStackSize( diff ); changes.add( a ); } } private boolean isDifferent( final ItemStack a, final ItemStack b ) { if( a == b && b == null ) { return false; } return a == null || b == null || !Platform.isSameItemPrecise( a, b ); } private void postDifference( Iterable a ) { final Iterator, Object>> i = this.listeners.entrySet().iterator(); while( i.hasNext() ) { final Map.Entry, Object> l = i.next(); final IMEMonitorHandlerReceiver key = l.getKey(); if( key.isValid( l.getValue() ) ) { key.postChange( this, a, mySource ); } else { i.remove(); } } } @Override public void setActionSource( final BaseActionSource mySource ) { this.mySource = mySource; } @Override public IItemList getAvailableItems( IItemList out ) { for( int i = 0; i < itemHandler.getSlots(); i++ ) { out.addStorage( AEItemStack.create( itemHandler.getStackInSlot( i ) ) ); } return out; } @Override public StorageChannel getChannel() { return StorageChannel.ITEMS; } @Override public void addListener( final IMEMonitorHandlerReceiver l, final Object verificationToken ) { this.listeners.put( l, verificationToken ); } @Override public void removeListener( final IMEMonitorHandlerReceiver l ) { this.listeners.remove( l ); } }