0696662254
No longer invalidates and refresh it with every single change. This should now also ensure that updates are send exactly once per network instead of multiple times scaling up with the amount of chained networks.
364 lines
8.6 KiB
Java
364 lines
8.6 KiB
Java
/*
|
|
* 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 <http://www.gnu.org/licenses/lgpl>.
|
|
*/
|
|
|
|
package appeng.me.cache;
|
|
|
|
|
|
import java.util.Collection;
|
|
import java.util.Deque;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.LinkedList;
|
|
import java.util.Map;
|
|
import java.util.Map.Entry;
|
|
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
|
|
import com.google.common.collect.ImmutableList;
|
|
|
|
import appeng.api.config.AccessRestriction;
|
|
import appeng.api.config.Actionable;
|
|
import appeng.api.networking.events.MENetworkStorageEvent;
|
|
import appeng.api.networking.security.BaseActionSource;
|
|
import appeng.api.storage.IMEInventoryHandler;
|
|
import appeng.api.storage.IMEMonitor;
|
|
import appeng.api.storage.IMEMonitorHandlerReceiver;
|
|
import appeng.api.storage.StorageChannel;
|
|
import appeng.api.storage.data.IAEStack;
|
|
import appeng.api.storage.data.IItemList;
|
|
import appeng.me.storage.ItemWatcher;
|
|
|
|
|
|
public class NetworkMonitor<T extends IAEStack<T>> implements IMEMonitor<T>
|
|
{
|
|
@Nonnull
|
|
private static final Deque<NetworkMonitor<?>> GLOBAL_DEPTH = new LinkedList<NetworkMonitor<?>>();
|
|
|
|
@Nonnull
|
|
private final GridStorageCache myGridCache;
|
|
@Nonnull
|
|
private final StorageChannel myChannel;
|
|
@Nonnull
|
|
private final IItemList<T> cachedList;
|
|
@Nonnull
|
|
private final Map<IMEMonitorHandlerReceiver<T>, Object> listeners = new HashMap<IMEMonitorHandlerReceiver<T>, Object>();
|
|
|
|
private boolean sendEvent = false;
|
|
private boolean hasChanged = false;
|
|
private int localDepthSemaphore = 0;
|
|
|
|
public NetworkMonitor( final GridStorageCache cache, final StorageChannel chan )
|
|
{
|
|
this.myGridCache = cache;
|
|
this.myChannel = chan;
|
|
this.cachedList = (IItemList<T>) chan.createList();
|
|
}
|
|
|
|
@Override
|
|
public void addListener( final IMEMonitorHandlerReceiver<T> l, final Object verificationToken )
|
|
{
|
|
this.listeners.put( l, verificationToken );
|
|
}
|
|
|
|
@Override
|
|
public boolean canAccept( final T input )
|
|
{
|
|
return this.getHandler().canAccept( input );
|
|
}
|
|
|
|
@Override
|
|
public T extractItems( final T request, final Actionable mode, final BaseActionSource src )
|
|
{
|
|
if( mode == Actionable.SIMULATE )
|
|
{
|
|
return this.getHandler().extractItems( request, mode, src );
|
|
}
|
|
|
|
localDepthSemaphore++;
|
|
final T leftover = this.getHandler().extractItems( request, mode, src );
|
|
localDepthSemaphore--;
|
|
|
|
if( localDepthSemaphore == 0 )
|
|
{
|
|
this.monitorDifference( request.copy(), leftover, true, src );
|
|
}
|
|
|
|
return leftover;
|
|
}
|
|
|
|
@Override
|
|
public AccessRestriction getAccess()
|
|
{
|
|
return this.getHandler().getAccess();
|
|
}
|
|
|
|
@Override
|
|
public IItemList<T> getAvailableItems( final IItemList out )
|
|
{
|
|
return this.getHandler().getAvailableItems( out );
|
|
}
|
|
|
|
@Override
|
|
public StorageChannel getChannel()
|
|
{
|
|
return this.getHandler().getChannel();
|
|
}
|
|
|
|
@Override
|
|
public int getPriority()
|
|
{
|
|
return this.getHandler().getPriority();
|
|
}
|
|
|
|
@Override
|
|
public int getSlot()
|
|
{
|
|
return this.getHandler().getSlot();
|
|
}
|
|
|
|
@Nonnull
|
|
@Override
|
|
public IItemList<T> getStorageList()
|
|
{
|
|
if( this.hasChanged )
|
|
{
|
|
this.hasChanged = false;
|
|
this.cachedList.resetStatus();
|
|
return this.getAvailableItems( this.cachedList );
|
|
}
|
|
|
|
return this.cachedList;
|
|
}
|
|
|
|
@Override
|
|
public T injectItems( final T input, final Actionable mode, final BaseActionSource src )
|
|
{
|
|
if( mode == Actionable.SIMULATE )
|
|
{
|
|
return this.getHandler().injectItems( input, mode, src );
|
|
}
|
|
|
|
localDepthSemaphore++;
|
|
final T leftover = this.getHandler().injectItems( input, mode, src );
|
|
localDepthSemaphore--;
|
|
|
|
if( localDepthSemaphore == 0 )
|
|
{
|
|
this.monitorDifference( input.copy(), leftover, false, src );
|
|
}
|
|
|
|
return leftover;
|
|
}
|
|
|
|
@Override
|
|
public boolean isPrioritized( final T input )
|
|
{
|
|
return this.getHandler().isPrioritized( input );
|
|
}
|
|
|
|
@Override
|
|
public void removeListener( final IMEMonitorHandlerReceiver<T> l )
|
|
{
|
|
this.listeners.remove( l );
|
|
}
|
|
|
|
@Override
|
|
public boolean validForPass( final int i )
|
|
{
|
|
return this.getHandler().validForPass( i );
|
|
}
|
|
|
|
@Nullable
|
|
@SuppressWarnings( "unchecked" )
|
|
private IMEInventoryHandler<T> getHandler()
|
|
{
|
|
switch( this.myChannel )
|
|
{
|
|
case ITEMS:
|
|
return (IMEInventoryHandler<T>) this.myGridCache.getItemInventoryHandler();
|
|
case FLUIDS:
|
|
return (IMEInventoryHandler<T>) this.myGridCache.getFluidInventoryHandler();
|
|
default:
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private Iterator<Entry<IMEMonitorHandlerReceiver<T>, Object>> getListeners()
|
|
{
|
|
return this.listeners.entrySet().iterator();
|
|
}
|
|
|
|
private T monitorDifference( final IAEStack original, final T leftOvers, final boolean extraction, final BaseActionSource src )
|
|
{
|
|
final T diff = (T) original.copy();
|
|
|
|
if( extraction )
|
|
{
|
|
diff.setStackSize( leftOvers == null ? 0 : -leftOvers.getStackSize() );
|
|
}
|
|
else if( leftOvers != null )
|
|
{
|
|
diff.decStackSize( leftOvers.getStackSize() );
|
|
}
|
|
|
|
if( diff.getStackSize() != 0 )
|
|
{
|
|
this.postChangesToListeners( ImmutableList.of( diff ), src );
|
|
}
|
|
|
|
return leftOvers;
|
|
}
|
|
|
|
private void notifyListenersOfChange( final Iterable<T> diff, final BaseActionSource src )
|
|
{
|
|
final Iterator<Entry<IMEMonitorHandlerReceiver<T>, Object>> i = this.getListeners();
|
|
|
|
while( i.hasNext() )
|
|
{
|
|
final Entry<IMEMonitorHandlerReceiver<T>, Object> o = i.next();
|
|
final IMEMonitorHandlerReceiver<T> receiver = o.getKey();
|
|
if( receiver.isValid( o.getValue() ) )
|
|
{
|
|
receiver.postChange( this, diff, src );
|
|
}
|
|
else
|
|
{
|
|
i.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void postChangesToListeners( final Iterable<T> changes, final BaseActionSource src )
|
|
{
|
|
this.postChange( true, changes, src );
|
|
}
|
|
|
|
private void updateCache( Iterable<T> changes, boolean add )
|
|
{
|
|
for( final T changedItem : changes )
|
|
{
|
|
T difference = changedItem;
|
|
|
|
if( !add && changedItem != null )
|
|
{
|
|
difference = changedItem.copy();
|
|
difference.setStackSize( -changedItem.getStackSize() );
|
|
}
|
|
|
|
this.cachedList.add( difference );
|
|
}
|
|
}
|
|
|
|
protected void postChange( final boolean add, final Iterable<T> changes, final BaseActionSource src )
|
|
{
|
|
if( localDepthSemaphore > 0 || GLOBAL_DEPTH.contains( this ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
GLOBAL_DEPTH.push( this );
|
|
localDepthSemaphore++;
|
|
|
|
this.sendEvent = true;
|
|
|
|
this.updateCache( changes, add );
|
|
this.notifyListenersOfChange( changes, src );
|
|
|
|
for( final T changedItem : changes )
|
|
{
|
|
T difference = changedItem;
|
|
|
|
if( !add && changedItem != null )
|
|
{
|
|
difference = changedItem.copy();
|
|
difference.setStackSize( -changedItem.getStackSize() );
|
|
}
|
|
|
|
if( this.myGridCache.getInterestManager().containsKey( changedItem ) )
|
|
{
|
|
final Collection<ItemWatcher> list = this.myGridCache.getInterestManager().get( changedItem );
|
|
|
|
if( !list.isEmpty() )
|
|
{
|
|
IAEStack fullStack = this.getStorageList().findPrecise( changedItem );
|
|
|
|
if( fullStack == null )
|
|
{
|
|
fullStack = changedItem.copy();
|
|
fullStack.setStackSize( 0 );
|
|
}
|
|
|
|
this.myGridCache.getInterestManager().enableTransactions();
|
|
|
|
for( final ItemWatcher iw : list )
|
|
{
|
|
iw.getHost().onStackChange( this.getStorageList(), fullStack, difference, src, this.getChannel() );
|
|
}
|
|
|
|
this.myGridCache.getInterestManager().disableTransactions();
|
|
}
|
|
}
|
|
}
|
|
|
|
final NetworkMonitor<?> last = GLOBAL_DEPTH.pop();
|
|
localDepthSemaphore--;
|
|
|
|
if( last != this )
|
|
{
|
|
throw new IllegalStateException( "Invalid Access to Networked Storage API detected." );
|
|
}
|
|
}
|
|
|
|
void forceUpdate( boolean reset )
|
|
{
|
|
if( reset )
|
|
{
|
|
this.hasChanged = true;
|
|
this.cachedList.resetStatus();
|
|
this.getAvailableItems( this.cachedList );
|
|
}
|
|
|
|
final Iterator<Entry<IMEMonitorHandlerReceiver<T>, Object>> i = this.getListeners();
|
|
|
|
while( i.hasNext() )
|
|
{
|
|
final Entry<IMEMonitorHandlerReceiver<T>, Object> o = i.next();
|
|
final IMEMonitorHandlerReceiver<T> receiver = o.getKey();
|
|
|
|
if( receiver.isValid( o.getValue() ) )
|
|
{
|
|
receiver.onListUpdate();
|
|
}
|
|
else
|
|
{
|
|
i.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
void onTick()
|
|
{
|
|
if( this.sendEvent )
|
|
{
|
|
this.sendEvent = false;
|
|
this.myGridCache.getGrid().postEvent( new MENetworkStorageEvent( this, this.myChannel ) );
|
|
}
|
|
}
|
|
|
|
}
|