/* * 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.hooks; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.Queue; import java.util.WeakHashMap; import java.util.concurrent.TimeUnit; import com.google.common.base.Stopwatch; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.Multimap; import net.minecraft.world.World; import net.minecraftforge.event.world.ChunkEvent; import net.minecraftforge.event.world.WorldEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; import net.minecraftforge.fml.common.gameevent.TickEvent.Phase; import net.minecraftforge.fml.common.gameevent.TickEvent.Type; import net.minecraftforge.fml.common.gameevent.TickEvent.WorldTickEvent; import appeng.api.AEApi; import appeng.api.networking.IGridNode; import appeng.api.parts.CableRenderMode; import appeng.api.util.AEColor; import appeng.core.AEConfig; import appeng.core.AELog; import appeng.core.CommonHelper; import appeng.core.sync.packets.PacketPaintedEntity; import appeng.crafting.CraftingJob; import appeng.me.Grid; import appeng.me.NetworkList; import appeng.tile.AEBaseTile; import appeng.util.IWorldCallable; import appeng.util.Platform; public class TickHandler { public static final TickHandler INSTANCE = new TickHandler(); private final Queue<IWorldCallable<?>> serverQueue = new LinkedList<IWorldCallable<?>>(); private final Multimap<World, CraftingJob> craftingJobs = LinkedListMultimap.create(); private final WeakHashMap<World, Queue<IWorldCallable<?>>> callQueue = new WeakHashMap<World, Queue<IWorldCallable<?>>>(); private final HandlerRep server = new HandlerRep(); private final HandlerRep client = new HandlerRep(); private final HashMap<Integer, PlayerColor> cliPlayerColors = new HashMap<Integer, PlayerColor>(); private final HashMap<Integer, PlayerColor> srvPlayerColors = new HashMap<Integer, PlayerColor>(); private CableRenderMode crm = CableRenderMode.STANDARD; public HashMap<Integer, PlayerColor> getPlayerColors() { if( Platform.isServer() ) { return this.srvPlayerColors; } return this.cliPlayerColors; } public void addCallable( final World w, final IWorldCallable<?> c ) { if( w == null ) { this.serverQueue.add( c ); } else { Queue<IWorldCallable<?>> queue = this.callQueue.get( w ); if( queue == null ) { queue = new LinkedList<IWorldCallable<?>>(); this.callQueue.put( w, queue ); } queue.add( c ); } } public void addInit( final AEBaseTile tile ) { if( Platform.isServer() ) // for no there is no reason to care about this on the client... { this.getRepo().tiles.add( tile ); } } private HandlerRep getRepo() { if( Platform.isServer() ) { return this.server; } return this.client; } public void addNetwork( final Grid grid ) { if( Platform.isServer() ) // for no there is no reason to care about this on the client... { this.getRepo().networks.add( grid ); } } public void removeNetwork( final Grid grid ) { if( Platform.isServer() ) // for no there is no reason to care about this on the client... { this.getRepo().networks.remove( grid ); } } public Iterable<Grid> getGridList() { return this.getRepo().networks; } public void shutdown() { this.getRepo().clear(); } @SubscribeEvent public void unloadWorld( final WorldEvent.Unload ev ) { if( Platform.isServer() ) // for no there is no reason to care about this on the client... { final LinkedList<IGridNode> toDestroy = new LinkedList<IGridNode>(); for( final Grid g : this.getRepo().networks ) { for( final IGridNode n : g.getNodes() ) { if( n.getWorld() == ev.getWorld() ) { toDestroy.add( n ); } } } for( final IGridNode n : toDestroy ) { n.destroy(); } } } @SubscribeEvent public void onChunkLoad( final ChunkEvent.Load load ) { for( final Object te : load.getChunk().getTileEntityMap().values() ) { if( te instanceof AEBaseTile ) { ( (AEBaseTile) te ).onChunkLoad(); } } } @SubscribeEvent public void onTick( final TickEvent ev ) { if( ev.type == Type.CLIENT && ev.phase == Phase.START ) { this.tickColors( this.cliPlayerColors ); final CableRenderMode currentMode = AEApi.instance().partHelper().getCableRenderMode(); if( currentMode != this.crm ) { this.crm = currentMode; CommonHelper.proxy.triggerUpdates(); } } if( ev.type == Type.WORLD && ev.phase == Phase.END ) { final WorldTickEvent wte = (WorldTickEvent) ev; synchronized( this.craftingJobs ) { final Collection<CraftingJob> jobSet = this.craftingJobs.get( wte.world ); if( !jobSet.isEmpty() ) { final int simTime = Math.max( 1, AEConfig.instance().getCraftingCalculationTimePerTick() / jobSet.size() ); final Iterator<CraftingJob> i = jobSet.iterator(); while( i.hasNext() ) { final CraftingJob cj = i.next(); if( !cj.simulateFor( simTime ) ) { i.remove(); } } } } } // for no there is no reason to care about this on the client... else if( ev.type == Type.SERVER && ev.phase == Phase.END ) { this.tickColors( this.srvPlayerColors ); // ready tiles. final HandlerRep repo = this.getRepo(); while( !repo.tiles.isEmpty() ) { final AEBaseTile bt = repo.tiles.poll(); if( !bt.isInvalid() ) { bt.onReady(); } } // tick networks. for( final Grid g : this.getRepo().networks ) { g.update(); } // cross world queue. this.processQueue( this.serverQueue, null ); } // world synced queue(s) if( ev.type == Type.WORLD && ev.phase == Phase.START ) { final World world = ( (WorldTickEvent) ev ).world; final Queue<IWorldCallable<?>> queue = this.callQueue.get( world ); this.processQueue( queue, world ); } } private void tickColors( final HashMap<Integer, PlayerColor> playerSet ) { final Iterator<PlayerColor> i = playerSet.values().iterator(); while( i.hasNext() ) { final PlayerColor pc = i.next(); if( pc.ticksLeft <= 0 ) { i.remove(); } pc.ticksLeft--; } } private void processQueue( final Queue<IWorldCallable<?>> queue, final World world ) { if( queue == null ) { return; } final Stopwatch sw = Stopwatch.createStarted(); IWorldCallable<?> c = null; while( ( c = queue.poll() ) != null ) { try { c.call( world ); if( sw.elapsed( TimeUnit.MILLISECONDS ) > 50 ) { break; } } catch( final Exception e ) { AELog.debug( e ); } } // long time = sw.elapsed( TimeUnit.MILLISECONDS ); // if ( time > 0 ) // AELog.info( "processQueue Time: " + time + "ms" ); } public void registerCraftingSimulation( final World world, final CraftingJob craftingJob ) { synchronized( this.craftingJobs ) { this.craftingJobs.put( world, craftingJob ); } } private static class HandlerRep { private Queue<AEBaseTile> tiles = new LinkedList<AEBaseTile>(); private Collection<Grid> networks = new NetworkList(); private void clear() { this.tiles = new LinkedList<AEBaseTile>(); this.networks = new NetworkList(); } } public static class PlayerColor { public final AEColor myColor; private final int myEntity; private int ticksLeft; public PlayerColor( final int id, final AEColor col, final int ticks ) { this.myEntity = id; this.myColor = col; this.ticksLeft = ticks; } public PacketPaintedEntity getPacket() { return new PacketPaintedEntity( this.myEntity, this.myColor, this.ticksLeft ); } } }