2016-06-22 23:49:47 -04:00
/*
* Minecraft Forge
* Copyright (c) 2016.
*
* This library 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 version 2.1
* of the License.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
2012-08-09 16:47:35 -07:00
package net.minecraftforge.common ;
2012-02-24 22:07:58 -08:00
2012-12-20 08:48:14 -05:00
import java.io.File ;
2012-08-14 23:59:15 -07:00
import java.util.ArrayList ;
2013-01-28 17:26:02 -08:00
import java.util.Arrays ;
2012-10-03 01:54:40 -04:00
import java.util.BitSet ;
2012-02-24 22:07:58 -08:00
import java.util.Hashtable ;
2013-01-27 10:43:35 -05:00
import java.util.List ;
import java.util.ListIterator ;
2012-09-22 01:43:54 -04:00
import java.util.Map ;
2012-08-10 18:15:10 -07:00
import java.util.Map.Entry ;
2013-01-27 10:43:35 -05:00
import java.util.concurrent.ConcurrentMap ;
2013-12-21 00:10:35 -08:00
2017-05-06 21:19:01 +02:00
import it.unimi.dsi.fastutil.ints.IntArrayList ;
import it.unimi.dsi.fastutil.ints.IntListIterator ;
2018-04-01 08:54:22 +01:00
import it.unimi.dsi.fastutil.ints.IntOpenHashSet ;
import it.unimi.dsi.fastutil.ints.IntSet ;
import it.unimi.dsi.fastutil.ints.IntSets ;
2012-06-27 17:21:41 -04:00
2013-04-23 07:51:07 -04:00
import com.google.common.collect.HashMultiset ;
2013-01-27 10:43:35 -05:00
import com.google.common.collect.Lists ;
import com.google.common.collect.MapMaker ;
2013-04-23 07:51:07 -04:00
import com.google.common.collect.Multiset ;
2015-11-13 10:13:47 -05:00
2012-12-12 23:27:57 -08:00
import net.minecraft.nbt.NBTTagCompound ;
2012-08-10 18:15:10 -07:00
import net.minecraft.server.MinecraftServer ;
2016-03-21 17:12:34 -07:00
import net.minecraft.world.DimensionType ;
2012-12-12 23:27:57 -08:00
import net.minecraft.world.MinecraftException ;
import net.minecraft.world.World ;
2016-05-18 03:29:10 -07:00
import net.minecraft.world.ServerWorldEventHandler ;
2012-12-12 23:27:57 -08:00
import net.minecraft.world.WorldProvider ;
import net.minecraft.world.WorldServer ;
import net.minecraft.world.WorldServerMulti ;
import net.minecraft.world.storage.ISaveHandler ;
2012-10-03 02:34:50 +03:00
import net.minecraftforge.event.world.WorldEvent ;
2015-11-13 10:13:47 -05:00
import net.minecraftforge.fml.common.FMLLog ;
2012-02-24 22:07:58 -08:00
2017-01-11 15:17:56 -08:00
import javax.annotation.Nullable ;
2012-02-24 22:07:58 -08:00
public class DimensionManager
{
2017-05-06 21:19:01 +02:00
private static class Dimension
{
private final DimensionType type ;
private int ticksWaited ;
private Dimension ( DimensionType type )
{
this . type = type ;
this . ticksWaited = 0 ;
}
}
2012-02-24 22:07:58 -08:00
private static boolean hasInit = false ;
2018-04-01 08:54:22 +01:00
private static final Hashtable < Integer , WorldServer > worlds = new Hashtable <> ();
private static final Hashtable < Integer , Dimension > dimensions = new Hashtable <> ();
private static final IntSet keepLoaded = IntSets . synchronize ( new IntOpenHashSet ());
private static final IntArrayList unloadQueue = new IntArrayList ();
private static final BitSet dimensionMap = new BitSet ( Long . SIZE << 4 );
private static final ConcurrentMap < World , World > weakWorldMap = new MapMaker (). weakKeys (). weakValues (). makeMap ();
private static final Multiset < Integer > leakedWorlds = HashMultiset . create ();
2012-02-24 22:07:58 -08:00
2013-01-28 17:26:02 -08:00
/**
2016-03-21 17:12:34 -07:00
* Returns a list of dimensions associated with this DimensionType.
2013-01-28 17:26:02 -08:00
*/
2016-03-21 17:12:34 -07:00
public static int [] getDimensions ( DimensionType type )
2013-01-28 17:26:02 -08:00
{
int [] ret = new int [ dimensions . size () ] ;
int x = 0 ;
2017-05-06 21:19:01 +02:00
for ( Map . Entry < Integer , Dimension > ent : dimensions . entrySet ())
2013-01-28 17:26:02 -08:00
{
2017-05-06 21:19:01 +02:00
if ( ent . getValue (). type == type )
2013-01-28 17:26:02 -08:00
{
ret [ x ++] = ent . getKey ();
}
}
return Arrays . copyOf ( ret , x );
}
2012-02-24 22:07:58 -08:00
public static void init ()
{
if ( hasInit )
{
return ;
}
2013-01-01 20:57:45 -08:00
hasInit = true ;
2016-03-21 17:12:34 -07:00
registerDimension ( 0 , DimensionType . OVERWORLD );
registerDimension ( - 1 , DimensionType . NETHER );
registerDimension ( 1 , DimensionType . THE_END );
2012-02-24 22:07:58 -08:00
}
2016-03-21 17:12:34 -07:00
public static void registerDimension ( int id , DimensionType type )
2012-02-24 22:07:58 -08:00
{
2016-03-21 17:12:34 -07:00
DimensionType . getById ( type . getId ()); //Check if type is invalid {will throw an error} No clue how it would be invalid tho...
2012-08-10 18:15:10 -07:00
if ( dimensions . containsKey ( id ))
{
2012-10-03 02:34:50 +03:00
throw new IllegalArgumentException ( String . format ( "Failed to register dimension for id %d, One is already registered" , id ));
2012-08-10 18:15:10 -07:00
}
2017-05-06 21:19:01 +02:00
dimensions . put ( id , new Dimension ( type ));
2012-10-03 01:54:40 -04:00
if ( id >= 0 )
{
dimensionMap . set ( id );
}
2012-10-03 02:34:50 +03:00
}
/**
* For unregistering a dimension when the save is changed (disconnected from a server or loaded a new save
*/
public static void unregisterDimension ( int id )
{
if ( ! dimensions . containsKey ( id ))
{
throw new IllegalArgumentException ( String . format ( "Failed to unregister dimension for id %d; No provider registered" , id ));
}
dimensions . remove ( id );
2012-08-10 18:15:10 -07:00
}
2013-05-22 22:29:00 -04:00
public static boolean isDimensionRegistered ( int dim )
{
return dimensions . containsKey ( dim );
}
2016-03-21 17:12:34 -07:00
public static DimensionType getProviderType ( int dim )
2012-08-10 18:15:10 -07:00
{
if ( ! dimensions . containsKey ( dim ))
{
throw new IllegalArgumentException ( String . format ( "Could not get provider type for dimension %d, does not exist" , dim ));
}
2017-05-06 21:19:01 +02:00
return dimensions . get ( dim ). type ;
2012-02-24 22:07:58 -08:00
}
2012-08-14 15:03:40 -07:00
public static WorldProvider getProvider ( int dim )
{
return getWorld ( dim ). provider ;
}
2013-01-27 10:43:35 -05:00
public static Integer [] getIDs ( boolean check )
{
if ( check )
{
List < World > allWorlds = Lists . newArrayList ( weakWorldMap . keySet ());
allWorlds . removeAll ( worlds . values ());
for ( ListIterator < World > li = allWorlds . listIterator (); li . hasNext (); )
{
World w = li . next ();
2013-04-23 07:51:07 -04:00
leakedWorlds . add ( System . identityHashCode ( w ));
2013-01-27 10:43:35 -05:00
}
2013-04-23 07:51:07 -04:00
for ( World w : allWorlds )
2013-01-27 10:43:35 -05:00
{
2013-04-23 07:51:07 -04:00
int leakCount = leakedWorlds . count ( System . identityHashCode ( w ));
if ( leakCount == 5 )
{
2017-06-23 07:33:11 +02:00
FMLLog . log . debug ( "The world {} ({}) may have leaked: first encounter (5 occurrences).\n" , Integer . toHexString ( System . identityHashCode ( w )), w . getWorldInfo (). getWorldName ());
2013-04-23 07:51:07 -04:00
}
else if ( leakCount % 5 == 0 )
2013-01-27 10:43:35 -05:00
{
2017-06-23 07:33:11 +02:00
FMLLog . log . debug ( "The world {} ({}) may have leaked: seen {} times.\n" , Integer . toHexString ( System . identityHashCode ( w )), w . getWorldInfo (). getWorldName (), leakCount );
2013-01-27 10:43:35 -05:00
}
}
}
return getIDs ();
}
2012-02-24 22:07:58 -08:00
public static Integer [] getIDs ()
{
2012-10-03 02:34:50 +03:00
return worlds . keySet (). toArray ( new Integer [ worlds . size () ] ); //Only loaded dims, since usually used to cycle through loaded worlds
2012-02-24 22:07:58 -08:00
}
2017-01-11 15:17:56 -08:00
public static void setWorld ( int id , @Nullable WorldServer world , MinecraftServer server )
2012-02-24 22:07:58 -08:00
{
2013-09-05 13:16:01 -04:00
if ( world != null )
{
2012-10-03 02:34:50 +03:00
worlds . put ( id , world );
2013-01-27 10:43:35 -05:00
weakWorldMap . put ( world , world );
2016-03-07 23:34:40 -08:00
server . worldTickTimes . put ( id , new long [ 100 ] );
2017-06-23 07:33:11 +02:00
FMLLog . log . info ( "Loading dimension {} ({}) ({})" , id , world . getWorldInfo (). getWorldName (), world . getMinecraftServer ());
2013-09-05 13:16:01 -04:00
}
else
{
2012-10-03 02:34:50 +03:00
worlds . remove ( id );
2016-03-07 23:34:40 -08:00
server . worldTickTimes . remove ( id );
2017-06-23 07:33:11 +02:00
FMLLog . log . info ( "Unloading dimension {}" , id );
2012-10-03 02:34:50 +03:00
}
2012-08-10 18:15:10 -07:00
2012-08-14 23:59:15 -07:00
ArrayList < WorldServer > tmp = new ArrayList < WorldServer > ();
2012-10-03 02:34:50 +03:00
if ( worlds . get ( 0 ) != null )
tmp . add ( worlds . get ( 0 ));
if ( worlds . get ( - 1 ) != null )
tmp . add ( worlds . get ( - 1 ));
if ( worlds . get ( 1 ) != null )
tmp . add ( worlds . get ( 1 ));
2012-08-10 18:15:10 -07:00
for ( Entry < Integer , WorldServer > entry : worlds . entrySet ())
{
int dim = entry . getKey ();
if ( dim >= - 1 && dim <= 1 )
{
continue ;
}
2012-08-14 23:59:15 -07:00
tmp . add ( entry . getValue ());
2012-08-10 18:15:10 -07:00
}
2016-12-21 15:52:30 -08:00
server . worlds = tmp . toArray ( new WorldServer [ tmp . size () ] );
2012-10-03 02:34:50 +03:00
}
2015-01-08 23:02:25 -08:00
public static void initDimension ( int dim )
{
2012-10-03 02:34:50 +03:00
WorldServer overworld = getWorld ( 0 );
2013-09-05 13:16:01 -04:00
if ( overworld == null )
{
2012-10-03 02:34:50 +03:00
throw new RuntimeException ( "Cannot Hotload Dim: Overworld is not Loaded!" );
}
2013-09-05 13:16:01 -04:00
try
{
2012-10-03 02:34:50 +03:00
DimensionManager . getProviderType ( dim );
2013-09-05 13:16:01 -04:00
}
catch ( Exception e )
{
2017-06-27 22:56:54 -07:00
FMLLog . log . error ( "Cannot Hotload Dim: {}" , dim , e );
2013-09-05 13:16:01 -04:00
return ; // If a provider hasn't been registered then we can't hotload the dim
2012-10-03 02:34:50 +03:00
}
2014-12-01 19:11:17 -08:00
MinecraftServer mcServer = overworld . getMinecraftServer ();
2012-10-03 02:34:50 +03:00
ISaveHandler savehandler = overworld . getSaveHandler ();
2015-11-26 19:38:21 -08:00
//WorldSettings worldSettings = new WorldSettings(overworld.getWorldInfo());
2012-10-03 02:34:50 +03:00
2017-06-10 17:59:01 -07:00
WorldServer world = ( dim == 0 ? overworld : ( WorldServer )( new WorldServerMulti ( mcServer , savehandler , dim , overworld , mcServer . profiler ). init ()));
2016-05-18 03:29:10 -07:00
world . addEventListener ( new ServerWorldEventHandler ( mcServer , world ));
2012-10-04 17:35:22 -04:00
MinecraftForge . EVENT_BUS . post ( new WorldEvent . Load ( world ));
2012-10-03 02:34:50 +03:00
if ( ! mcServer . isSinglePlayer ())
{
world . getWorldInfo (). setGameType ( mcServer . getGameType ());
}
2014-11-25 19:56:35 -08:00
mcServer . setDifficultyForAllWorlds ( mcServer . getDifficulty ());
2012-02-24 22:07:58 -08:00
}
2012-08-10 18:15:10 -07:00
public static WorldServer getWorld ( int id )
2012-02-24 22:07:58 -08:00
{
return worlds . get ( id );
}
2012-08-10 18:15:10 -07:00
public static WorldServer [] getWorlds ()
2012-02-24 22:07:58 -08:00
{
2013-01-01 20:57:45 -08:00
return worlds . values (). toArray ( new WorldServer [ worlds . size () ] );
2012-02-24 22:07:58 -08:00
}
static
{
init ();
}
2012-06-27 17:21:41 -04:00
2012-10-04 00:07:59 -04:00
/**
* Not public API: used internally to get dimensions that should load at
* server startup
*/
public static Integer [] getStaticDimensionIDs ()
{
return dimensions . keySet (). toArray ( new Integer [ dimensions . keySet (). size () ] );
}
2012-08-10 18:15:10 -07:00
public static WorldProvider createProviderFor ( int dim )
{
try
{
if ( dimensions . containsKey ( dim ))
{
2016-03-21 17:12:34 -07:00
WorldProvider ret = getProviderType ( dim ). createDimension ();
ret . setDimension ( dim );
return ret ;
2012-08-10 18:15:10 -07:00
}
else
{
2012-10-03 02:34:50 +03:00
throw new RuntimeException ( String . format ( "No WorldProvider bound for dimension %d" , dim )); //It's going to crash anyway at this point. Might as well be informative
2012-08-10 18:15:10 -07:00
}
2012-09-22 01:43:54 -04:00
}
2012-08-10 18:15:10 -07:00
catch ( Exception e )
{
2017-06-23 07:33:11 +02:00
FMLLog . log . error ( "An error occurred trying to create an instance of WorldProvider {} ({})" ,
dim , getProviderType ( dim ), e );
2012-08-10 18:15:10 -07:00
throw new RuntimeException ( e );
}
}
2012-10-03 02:34:50 +03:00
2017-05-06 21:19:01 +02:00
/**
2018-04-01 08:54:22 +01:00
* Sets if a dimension should stay loaded.
* @param dim the dimension ID
* @param keep whether or not the dimension should be kept loaded
* @return true iff the dimension's status changed
*/
public static boolean keepDimensionLoaded ( int dim , boolean keep )
{
return keep ? keepLoaded . add ( dim ) : keepLoaded . remove ( dim );
}
private static boolean canUnloadWorld ( WorldServer world )
{
return ForgeChunkManager . getPersistentChunksFor ( world ). isEmpty ()
&& world . playerEntities . isEmpty ()
&& ! world . provider . getDimensionType (). shouldLoadSpawn ()
&& ! keepLoaded . contains ( world . provider . getDimension ());
}
/**
* Queues a dimension to unload, if it can be unloaded.
* If the dimension is already queued, it will reset the delay to unload.
2017-05-06 21:19:01 +02:00
* @param id The id of the dimension
*/
public static void unloadWorld ( int id )
{
2018-04-01 08:54:22 +01:00
WorldServer world = worlds . get ( id );
if ( world == null || ! canUnloadWorld ( world )) return ;
if ( ! unloadQueue . contains ( id ))
2017-05-06 21:19:01 +02:00
{
2017-06-23 07:33:11 +02:00
FMLLog . log . debug ( "Queueing dimension {} to unload" , id );
2017-05-06 21:19:01 +02:00
unloadQueue . add ( id );
}
else
{
dimensions . get ( id ). ticksWaited = 0 ;
}
}
public static boolean isWorldQueuedToUnload ( int id )
{
return unloadQueue . contains ( id );
2012-10-03 02:34:50 +03:00
}
/*
* To be called by the server at the appropriate time, do not call from mod code.
*/
public static void unloadWorlds ( Hashtable < Integer , long []> worldTickTimes ) {
2017-05-06 21:19:01 +02:00
IntListIterator queueIterator = unloadQueue . iterator ();
while ( queueIterator . hasNext ()) {
int id = queueIterator . next ();
Dimension dimension = dimensions . get ( id );
if ( dimension . ticksWaited < ForgeModContainer . dimensionUnloadQueueDelay )
{
dimension . ticksWaited ++ ;
continue ;
}
2013-01-23 23:36:59 -05:00
WorldServer w = worlds . get ( id );
2017-05-06 21:19:01 +02:00
queueIterator . remove ();
dimension . ticksWaited = 0 ;
2018-04-01 08:54:22 +01:00
// Don't unload the world if the status changed
if ( w == null || ! canUnloadWorld ( w ))
2017-05-06 21:19:01 +02:00
{
2017-06-23 07:33:11 +02:00
FMLLog . log . debug ( "Aborting unload for dimension {} as status changed" , id );
2017-05-06 21:19:01 +02:00
continue ;
}
try
{
w . saveAllChunks ( true , null );
}
catch ( MinecraftException e )
{
2017-06-27 22:56:54 -07:00
FMLLog . log . error ( "Caught an exception while saving all chunks:" , e );
2012-10-03 02:34:50 +03:00
}
2013-01-23 23:36:59 -05:00
finally
{
2017-05-06 21:19:01 +02:00
MinecraftForge . EVENT_BUS . post ( new WorldEvent . Unload ( w ));
w . flush ();
setWorld ( id , null , w . getMinecraftServer ());
2013-01-23 23:36:59 -05:00
}
2012-10-03 02:34:50 +03:00
}
}
2012-10-03 01:54:40 -04:00
/**
* Return the next free dimension ID. Note: you are not guaranteed a contiguous
* block of free ids. Always call for each individual ID you wish to get.
2013-01-21 21:56:04 -05:00
* @return the next free dimension ID
2012-10-03 01:54:40 -04:00
*/
2012-10-03 02:34:50 +03:00
public static int getNextFreeDimId () {
2012-10-03 01:54:40 -04:00
int next = 0 ;
while ( true )
{
next = dimensionMap . nextClearBit ( next );
if ( dimensions . containsKey ( next ))
{
dimensionMap . set ( next );
}
else
{
return next ;
}
}
}
public static NBTTagCompound saveDimensionDataMap ()
{
int [] data = new int [ ( dimensionMap . length () + Integer . SIZE - 1 ) / Integer . SIZE ] ;
NBTTagCompound dimMap = new NBTTagCompound ();
for ( int i = 0 ; i < data . length ; i ++ )
{
int val = 0 ;
for ( int j = 0 ; j < Integer . SIZE ; j ++ )
{
val |= dimensionMap . get ( i * Integer . SIZE + j ) ? ( 1 << j ) : 0 ;
}
data [ i ] = val ;
}
dimMap . setIntArray ( "DimensionArray" , data );
return dimMap ;
}
2017-01-11 15:17:56 -08:00
public static void loadDimensionDataMap ( @Nullable NBTTagCompound compoundTag )
2012-10-03 01:54:40 -04:00
{
2014-05-12 11:46:03 -07:00
dimensionMap . clear ();
2012-10-03 01:54:40 -04:00
if ( compoundTag == null )
{
for ( Integer id : dimensions . keySet ())
{
if ( id >= 0 )
{
dimensionMap . set ( id );
}
}
}
else
{
int [] intArray = compoundTag . getIntArray ( "DimensionArray" );
for ( int i = 0 ; i < intArray . length ; i ++ )
{
for ( int j = 0 ; j < Integer . SIZE ; j ++ )
{
dimensionMap . set ( i * Integer . SIZE + j , ( intArray [ i ] & ( 1 << j )) != 0 );
}
}
}
2012-10-03 02:34:50 +03:00
}
2012-12-20 08:48:14 -05:00
/**
2013-01-21 21:56:04 -05:00
* Return the current root directory for the world save. Accesses getSaveHandler from the overworld
* @return the root directory of the save
2012-12-20 08:48:14 -05:00
*/
2017-01-11 15:17:56 -08:00
@Nullable
2012-12-20 08:48:14 -05:00
public static File getCurrentSaveRootDirectory ()
{
if ( DimensionManager . getWorld ( 0 ) != null )
{
2016-03-23 17:34:48 +03:00
return DimensionManager . getWorld ( 0 ). getSaveHandler (). getWorldDirectory ();
2016-03-07 23:34:40 -08:00
} /*
2013-01-22 10:07:41 -05:00
else if (MinecraftServer.getServer() != null)
{
MinecraftServer srv = MinecraftServer.getServer();
SaveHandler saveHandler = (SaveHandler) srv.getActiveAnvilConverter().getSaveLoader(srv.getFolderName(), false);
2013-04-10 16:37:25 -04:00
return saveHandler.getWorldDirectory();
2016-03-07 23:34:40 -08:00
}*/
2012-12-20 08:48:14 -05:00
else
{
return null ;
}
}
2016-03-07 23:34:40 -08:00
}