--- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -196,7 +196,7 @@ private final GameProfileRepository field_152365_W; private final PlayerProfileCache field_152366_X; private long field_147142_T; - protected final Thread field_175590_aa = Util.func_200696_a(new Thread(this, "Server thread"), (p_213187_0_) -> { + protected final Thread field_175590_aa = Util.func_200696_a(new Thread(net.minecraftforge.fml.common.thread.SidedThreadGroups.SERVER, this, "Server thread"), (p_213187_0_) -> { p_213187_0_.setUncaughtExceptionHandler((p_213206_0_, p_213206_1_) -> { field_147145_h.error(p_213206_1_); }); @@ -336,6 +336,8 @@ this.func_200245_b(new TranslationTextComponent("menu.loadingLevel")); SaveHandler savehandler = this.func_71254_M().func_197715_a(p_71247_1_, this); this.func_175584_a(this.func_71270_I(), savehandler); + // Move factory creation earlier to prevent startupquery deadlock + IChunkStatusListener ichunkstatuslistener = this.field_213220_d.create(11); WorldInfo worldinfo = savehandler.func_75757_d(); WorldSettings worldsettings; if (worldinfo == null) { @@ -357,13 +359,13 @@ worldinfo.func_230145_a_(this.getServerModName(), this.func_230045_q_().isPresent()); this.func_195560_a(savehandler.func_75765_b(), worldinfo); - IChunkStatusListener ichunkstatuslistener = this.field_213220_d.create(11); this.func_213194_a(savehandler, worldinfo, worldsettings, ichunkstatuslistener); this.func_147139_a(this.func_147135_j(), true); this.func_213186_a(ichunkstatuslistener); } protected void func_213194_a(SaveHandler p_213194_1_, WorldInfo p_213194_2_, WorldSettings p_213194_3_, IChunkStatusListener p_213194_4_) { + net.minecraftforge.common.DimensionManager.fireRegister(); if (this.func_71242_L()) { p_213194_2_.func_176127_a(field_213219_c); } @@ -407,6 +409,7 @@ if (dimensiontype != DimensionType.field_223227_a_) { this.field_71305_c.put(dimensiontype, new ServerMultiWorld(serverworld1, this, this.field_213217_au, p_213194_1_, dimensiontype, this.field_71304_b, p_213194_4_)); } + net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.world.WorldEvent.Load(field_71305_c.get(dimensiontype))); } } @@ -565,6 +568,7 @@ for(ServerWorld serverworld1 : this.func_212370_w()) { if (serverworld1 != null) { try { + net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.world.WorldEvent.Unload(serverworld1)); serverworld1.close(); } catch (IOException ioexception) { field_147145_h.error("Exception closing the level", (Throwable)ioexception); @@ -605,6 +609,7 @@ public void run() { try { if (this.func_71197_b()) { + net.minecraftforge.fml.server.ServerLifecycleHooks.handleServerStarted(this); this.field_211151_aa = Util.func_211177_b(); this.field_147147_p.func_151315_a(new StringTextComponent(this.field_71286_C)); this.field_147147_p.func_151321_a(new ServerStatusResponse.Version(SharedConstants.func_215069_a().getName(), SharedConstants.func_215069_a().getProtocolVersion())); @@ -636,9 +641,15 @@ this.field_71304_b.func_219897_b(); this.field_71296_Q = true; } + net.minecraftforge.fml.server.ServerLifecycleHooks.handleServerStopping(this); + net.minecraftforge.fml.server.ServerLifecycleHooks.expectServerStopped(); // has to come before finalTick to avoid race conditions } else { + net.minecraftforge.fml.server.ServerLifecycleHooks.expectServerStopped(); // has to come before finalTick to avoid race conditions this.func_71228_a((CrashReport)null); } + } catch (net.minecraftforge.fml.StartupQuery.AbortedException e) { + // ignore silently + net.minecraftforge.fml.server.ServerLifecycleHooks.expectServerStopped(); // has to come before finalTick to avoid race conditions } catch (Throwable throwable1) { field_147145_h.error("Encountered an unexpected exception", throwable1); CrashReport crashreport; @@ -655,6 +666,7 @@ field_147145_h.error("We were unable to save this crash report to disk."); } + net.minecraftforge.fml.server.ServerLifecycleHooks.expectServerStopped(); // has to come before finalTick to avoid race conditions this.func_71228_a(crashreport); } finally { try { @@ -663,6 +675,7 @@ } catch (Throwable throwable) { field_147145_h.error("Exception stopping the server", throwable); } finally { + net.minecraftforge.fml.server.ServerLifecycleHooks.handleServerStopped(this); this.func_71240_o(); } @@ -764,6 +777,7 @@ protected void func_71217_p(BooleanSupplier p_71217_1_) { long i = Util.func_211178_c(); + net.minecraftforge.fml.hooks.BasicEventHooks.onPreServerTick(); ++this.field_71315_w; this.func_71190_q(p_71217_1_); if (i - this.field_147142_T >= 5000000000L) { @@ -778,6 +792,7 @@ Collections.shuffle(Arrays.asList(agameprofile)); this.field_147147_p.func_151318_b().func_151330_a(agameprofile); + this.field_147147_p.invalidateJson(); } if (this.field_71315_w % 6000 == 0) { @@ -805,6 +820,7 @@ long i1 = Util.func_211178_c(); this.field_213215_ap.func_181747_a(i1 - i); this.field_71304_b.func_76319_b(); + net.minecraftforge.fml.hooks.BasicEventHooks.onPostServerTick(); } protected void func_71190_q(BooleanSupplier p_71190_1_) { @@ -812,7 +828,8 @@ this.func_193030_aL().func_73660_a(); this.field_71304_b.func_219895_b("levels"); - for(ServerWorld serverworld : this.func_212370_w()) { + for(ServerWorld serverworld : this.getWorldArray()) { + long tickStart = Util.func_211178_c(); if (serverworld.field_73011_w.func_186058_p() == DimensionType.field_223227_a_ || this.func_71255_r()) { this.field_71304_b.func_194340_a(() -> { return serverworld.func_72912_H().func_76065_j() + " " + Registry.field_212622_k.func_177774_c(serverworld.field_73011_w.func_186058_p()); @@ -824,6 +841,7 @@ } this.field_71304_b.func_76320_a("tick"); + net.minecraftforge.fml.hooks.BasicEventHooks.onPreWorldTick(serverworld); try { serverworld.func_72835_b(p_71190_1_); @@ -832,12 +850,16 @@ serverworld.func_72914_a(crashreport); throw new ReportedException(crashreport); } + net.minecraftforge.fml.hooks.BasicEventHooks.onPostWorldTick(serverworld); this.field_71304_b.func_76319_b(); this.field_71304_b.func_76319_b(); } + perWorldTickTimes.computeIfAbsent(serverworld.func_201675_m().func_186058_p(), k -> new long[100])[this.field_71315_w % 100] = Util.func_211178_c() - tickStart; } + this.field_71304_b.func_219895_b("dim_unloading"); + net.minecraftforge.common.DimensionManager.unloadWorlds(this, this.field_71315_w % 200 == 0); this.field_71304_b.func_219895_b("connection"); this.func_147137_ag().func_151269_c(); this.field_71304_b.func_219895_b("players"); @@ -878,6 +900,7 @@ OptionSpec optionspec10 = optionparser.accepts("port").withRequiredArg().ofType(Integer.class).defaultsTo(-1); OptionSpec optionspec11 = optionparser.accepts("serverId").withRequiredArg(); OptionSpec optionspec12 = optionparser.nonOptions(); + optionparser.accepts("gameDir").withRequiredArg().ofType(File.class).defaultsTo(new File(".")); //Forge: Consume this argument, we use it in the launcher, and the client side. try { OptionSet optionset = optionparser.parse(p_main_0_); @@ -910,6 +933,10 @@ GameProfileRepository gameprofilerepository = yggdrasilauthenticationservice.createProfileRepository(); PlayerProfileCache playerprofilecache = new PlayerProfileCache(gameprofilerepository, new File(s, field_152367_a.getName())); String s1 = Optional.ofNullable(optionset.valueOf(optionspec9)).orElse(serverpropertiesprovider.func_219034_a().field_219021_o); + if (s1 == null || s1.isEmpty() || new File(s, s1).getAbsolutePath().equals(new File(s).getAbsolutePath())) { + field_147145_h.error("Invalid world directory specified, must not be null, empty or the same directory as your universe! " + s1); + return; + } final DedicatedServer dedicatedserver = new DedicatedServer(new File(s), serverpropertiesprovider, DataFixesManager.func_210901_a(), yggdrasilauthenticationservice, minecraftsessionservice, gameprofilerepository, playerprofilecache, LoggingChunkStatusListener::new, s1); dedicatedserver.func_71224_l(optionset.valueOf(optionspec7)); dedicatedserver.func_71208_b(optionset.valueOf(optionspec10)); @@ -927,6 +954,7 @@ Thread thread = new Thread("Server Shutdown Thread") { public void run() { dedicatedserver.func_71263_m(true); + LogManager.shutdown(); // we're manually managing the logging shutdown on the server. Make sure we do it here at the end. } }; thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(field_147145_h)); @@ -971,7 +999,7 @@ } public ServerWorld func_71218_a(DimensionType p_71218_1_) { - return this.field_71305_c.get(p_71218_1_); + return net.minecraftforge.common.DimensionManager.getWorld(this, p_71218_1_, true, true); } public Iterable func_212370_w() { @@ -1010,7 +1038,7 @@ } public String getServerModName() { - return "vanilla"; + return net.minecraftforge.fml.BrandingControl.getServerBranding(); } public CrashReport func_71230_b(CrashReport p_71230_1_) { @@ -1567,6 +1595,31 @@ public abstract boolean func_213199_b(GameProfile p_213199_1_); + private Map perWorldTickTimes = Maps.newIdentityHashMap(); + @Nullable + public long[] getTickTime(DimensionType dim) { + return perWorldTickTimes.get(dim); + } + + @Deprecated //Forge Internal use Only, You can screw up a lot of things if you mess with this map. + public synchronized Map forgeGetWorldMap() { + return this.field_71305_c; + } + private int worldArrayMarker = 0; + private int worldArrayLast = -1; + private ServerWorld[] worldArray; + @Deprecated //Forge Internal use Only, use to protect against concurrent modifications in the world tick loop. + public synchronized void markWorldsDirty() { + worldArrayMarker++; + } + private ServerWorld[] getWorldArray() { + if (worldArrayMarker == worldArrayLast && worldArray != null) + return worldArray; + worldArray = this.field_71305_c.values().stream().toArray(x -> new ServerWorld[x]); + worldArrayLast = worldArrayMarker; + return worldArray; + } + public void func_223711_a(Path p_223711_1_) throws IOException { Path path = p_223711_1_.resolve("levels");