diff --git a/patches/minecraft/net/minecraft/world/World.java.patch b/patches/minecraft/net/minecraft/world/World.java.patch index 0afb51c2d2..ddf3c1a305 100644 --- a/patches/minecraft/net/minecraft/world/World.java.patch +++ b/patches/minecraft/net/minecraft/world/World.java.patch @@ -402,7 +402,16 @@ throw new ReportedException(crashreport); } -@@ -1648,6 +1759,12 @@ +@@ -1641,13 +1752,21 @@ + { + try + { ++ net.minecraftforge.server.timings.TimeTracker.ENTITY_UPDATE.trackStart(entity2); + this.func_72870_g(entity2); ++ net.minecraftforge.server.timings.TimeTracker.ENTITY_UPDATE.trackEnd(entity2); + } + catch (Throwable throwable1) + { CrashReport crashreport1 = CrashReport.func_85055_a(throwable1, "Ticking entity"); CrashReportCategory crashreportcategory1 = crashreport1.func_85058_a("Entity being ticked"); entity2.func_85029_a(crashreportcategory1); @@ -415,7 +424,7 @@ throw new ReportedException(crashreport1); } } -@@ -1674,14 +1791,23 @@ +@@ -1674,14 +1793,23 @@ this.field_72984_F.func_76318_c("blockEntities"); @@ -442,7 +451,7 @@ Iterator iterator = this.field_175730_i.iterator(); while (iterator.hasNext()) -@@ -1692,7 +1818,7 @@ +@@ -1692,7 +1820,7 @@ { BlockPos blockpos = tileentity.func_174877_v(); @@ -451,7 +460,17 @@ { try { -@@ -1708,6 +1834,13 @@ +@@ -1700,7 +1828,9 @@ + { + return String.valueOf((Object)TileEntity.func_190559_a(tileentity.getClass())); + }); ++ net.minecraftforge.server.timings.TimeTracker.TILE_ENTITY_UPDATE.trackStart(tileentity); + ((ITickable)tileentity).func_73660_a(); ++ net.minecraftforge.server.timings.TimeTracker.TILE_ENTITY_UPDATE.trackEnd(tileentity); + this.field_72984_F.func_76319_b(); + } + catch (Throwable throwable) +@@ -1708,6 +1838,13 @@ CrashReport crashreport2 = CrashReport.func_85055_a(throwable, "Ticking block entity"); CrashReportCategory crashreportcategory2 = crashreport2.func_85058_a("Block entity being ticked"); tileentity.func_145828_a(crashreportcategory2); @@ -465,7 +484,7 @@ throw new ReportedException(crashreport2); } } -@@ -1720,7 +1853,10 @@ +@@ -1720,7 +1857,10 @@ if (this.func_175667_e(tileentity.func_174877_v())) { @@ -477,7 +496,7 @@ } } } -@@ -1764,12 +1900,18 @@ +@@ -1764,12 +1904,18 @@ public boolean func_175700_a(TileEntity p_175700_1_) { @@ -496,7 +515,7 @@ if (this.field_72995_K) { -@@ -1785,6 +1927,11 @@ +@@ -1785,6 +1931,11 @@ { if (this.field_147481_N) { @@ -508,7 +527,7 @@ this.field_147484_a.addAll(p_147448_1_); } else -@@ -1807,9 +1954,13 @@ +@@ -1807,9 +1958,13 @@ { int j2 = MathHelper.func_76128_c(p_72866_1_.field_70165_t); int k2 = MathHelper.func_76128_c(p_72866_1_.field_70161_v); @@ -524,7 +543,7 @@ { return; } -@@ -1831,6 +1982,7 @@ +@@ -1831,6 +1986,7 @@ } else { @@ -532,7 +551,7 @@ p_72866_1_.func_70071_h_(); } } -@@ -1914,7 +2066,7 @@ +@@ -1914,7 +2070,7 @@ { Entity entity4 = list.get(j2); @@ -541,7 +560,7 @@ { return false; } -@@ -2011,6 +2163,11 @@ +@@ -2011,6 +2167,11 @@ blockpos$pooledmutableblockpos.func_185344_t(); return true; } @@ -553,7 +572,7 @@ } } } -@@ -2050,6 +2207,16 @@ +@@ -2050,6 +2211,16 @@ IBlockState iblockstate1 = this.func_180495_p(blockpos$pooledmutableblockpos); Block block = iblockstate1.func_177230_c(); @@ -570,7 +589,7 @@ if (iblockstate1.func_185904_a() == p_72918_2_) { double d0 = (double)((float)(i4 + 1) - BlockLiquid.func_149801_b(((Integer)iblockstate1.func_177229_b(BlockLiquid.field_176367_b)).intValue())); -@@ -2116,6 +2283,7 @@ +@@ -2116,6 +2287,7 @@ public Explosion func_72885_a(@Nullable Entity p_72885_1_, double p_72885_2_, double p_72885_4_, double p_72885_6_, float p_72885_8_, boolean p_72885_9_, boolean p_72885_10_) { Explosion explosion = new Explosion(this, p_72885_1_, p_72885_2_, p_72885_4_, p_72885_6_, p_72885_8_, p_72885_9_, p_72885_10_); @@ -578,7 +597,7 @@ explosion.func_77278_a(); explosion.func_77279_a(true); return explosion; -@@ -2238,6 +2406,7 @@ +@@ -2238,6 +2410,7 @@ public void func_175690_a(BlockPos p_175690_1_, @Nullable TileEntity p_175690_2_) { @@ -586,7 +605,7 @@ if (!this.func_189509_E(p_175690_1_)) { if (p_175690_2_ != null && !p_175690_2_.func_145837_r()) -@@ -2245,6 +2414,8 @@ +@@ -2245,6 +2418,8 @@ if (this.field_147481_N) { p_175690_2_.func_174878_a(p_175690_1_); @@ -595,7 +614,7 @@ Iterator iterator1 = this.field_147484_a.iterator(); while (iterator1.hasNext()) -@@ -2262,7 +2433,8 @@ +@@ -2262,7 +2437,8 @@ } else { @@ -605,7 +624,7 @@ this.func_175700_a(p_175690_2_); } } -@@ -2277,6 +2449,8 @@ +@@ -2277,6 +2453,8 @@ { tileentity2.func_145843_s(); this.field_147484_a.remove(tileentity2); @@ -614,7 +633,7 @@ } else { -@@ -2289,6 +2463,7 @@ +@@ -2289,6 +2467,7 @@ this.func_175726_f(p_175713_1_).func_177425_e(p_175713_1_); } @@ -622,7 +641,7 @@ } public void func_147457_a(TileEntity p_147457_1_) -@@ -2315,7 +2490,7 @@ +@@ -2315,7 +2494,7 @@ if (chunk1 != null && !chunk1.func_76621_g()) { IBlockState iblockstate1 = this.func_180495_p(p_175677_1_); @@ -631,7 +650,7 @@ } else { -@@ -2338,6 +2513,7 @@ +@@ -2338,6 +2517,7 @@ { this.field_72985_G = p_72891_1_; this.field_72992_H = p_72891_2_; @@ -639,7 +658,7 @@ } public void func_72835_b() -@@ -2347,6 +2523,11 @@ +@@ -2347,6 +2527,11 @@ protected void func_72947_a() { @@ -651,7 +670,7 @@ if (this.field_72986_A.func_76059_o()) { this.field_73004_o = 1.0F; -@@ -2360,6 +2541,11 @@ +@@ -2360,6 +2545,11 @@ protected void func_72979_l() { @@ -663,7 +682,7 @@ if (this.field_73011_w.func_191066_m()) { if (!this.field_72995_K) -@@ -2484,6 +2670,11 @@ +@@ -2484,6 +2674,11 @@ public boolean func_175670_e(BlockPos p_175670_1_, boolean p_175670_2_) { @@ -675,7 +694,7 @@ Biome biome = this.func_180494_b(p_175670_1_); float f = biome.func_180626_a(p_175670_1_); -@@ -2525,6 +2716,11 @@ +@@ -2525,6 +2720,11 @@ public boolean func_175708_f(BlockPos p_175708_1_, boolean p_175708_2_) { @@ -687,7 +706,7 @@ Biome biome = this.func_180494_b(p_175708_1_); float f = biome.func_180626_a(p_175708_1_); -@@ -2542,7 +2738,7 @@ +@@ -2542,7 +2742,7 @@ { IBlockState iblockstate1 = this.func_180495_p(p_175708_1_); @@ -696,7 +715,7 @@ { return true; } -@@ -2574,10 +2770,11 @@ +@@ -2574,10 +2774,11 @@ else { IBlockState iblockstate1 = this.func_180495_p(p_175638_1_); @@ -711,7 +730,7 @@ { k2 = 1; } -@@ -2683,7 +2880,8 @@ +@@ -2683,7 +2884,8 @@ int k6 = k4 + enumfacing.func_96559_d(); int l6 = l4 + enumfacing.func_82599_e(); blockpos$pooledmutableblockpos.func_181079_c(j6, k6, l6); @@ -721,7 +740,7 @@ j5 = this.func_175642_b(p_180500_1_, blockpos$pooledmutableblockpos); if (j5 == i5 - i7 && k2 < this.field_72994_J.length) -@@ -2791,10 +2989,10 @@ +@@ -2791,10 +2993,10 @@ public List func_175674_a(@Nullable Entity p_175674_1_, AxisAlignedBB p_175674_2_, @Nullable Predicate p_175674_3_) { List list = Lists.newArrayList(); @@ -736,7 +755,7 @@ for (int j3 = j2; j3 <= k2; ++j3) { -@@ -2847,10 +3045,10 @@ +@@ -2847,10 +3049,10 @@ public List func_175647_a(Class p_175647_1_, AxisAlignedBB p_175647_2_, @Nullable Predicate p_175647_3_) { @@ -751,7 +770,7 @@ List list = Lists.newArrayList(); for (int j3 = j2; j3 < k2; ++j3) -@@ -2930,11 +3128,13 @@ +@@ -2930,11 +3132,13 @@ public void func_175650_b(Collection p_175650_1_) { @@ -768,7 +787,7 @@ } } -@@ -2958,7 +3158,7 @@ +@@ -2958,7 +3162,7 @@ } else { @@ -777,7 +796,7 @@ } } -@@ -3042,7 +3242,7 @@ +@@ -3042,7 +3246,7 @@ public int func_175651_c(BlockPos p_175651_1_, EnumFacing p_175651_2_) { IBlockState iblockstate1 = this.func_180495_p(p_175651_1_); @@ -786,7 +805,7 @@ } public boolean func_175640_z(BlockPos p_175640_1_) -@@ -3208,6 +3408,8 @@ +@@ -3208,6 +3412,8 @@ d2 *= ((Double)MoreObjects.firstNonNull(p_184150_11_.apply(entityplayer1), Double.valueOf(1.0D))).doubleValue(); } @@ -795,7 +814,7 @@ if ((p_184150_9_ < 0.0D || Math.abs(entityplayer1.field_70163_u - p_184150_3_) < p_184150_9_ * p_184150_9_) && (p_184150_7_ < 0.0D || d1 < d2 * d2) && (d0 == -1.0D || d1 < d0)) { d0 = d1; -@@ -3269,7 +3471,7 @@ +@@ -3269,7 +3475,7 @@ public long func_72905_C() { @@ -804,7 +823,7 @@ } public long func_82737_E() -@@ -3279,17 +3481,17 @@ +@@ -3279,17 +3485,17 @@ public long func_72820_D() { @@ -825,7 +844,7 @@ if (!this.func_175723_af().func_177746_a(blockpos1)) { -@@ -3301,7 +3503,7 @@ +@@ -3301,7 +3507,7 @@ public void func_175652_B(BlockPos p_175652_1_) { @@ -834,7 +853,7 @@ } @SideOnly(Side.CLIENT) -@@ -3321,12 +3523,18 @@ +@@ -3321,12 +3527,18 @@ if (!this.field_72996_f.contains(p_72897_1_)) { @@ -853,7 +872,7 @@ return true; } -@@ -3428,8 +3636,7 @@ +@@ -3428,8 +3640,7 @@ public boolean func_180502_D(BlockPos p_180502_1_) { @@ -863,7 +882,7 @@ } @Nullable -@@ -3490,12 +3697,12 @@ +@@ -3490,12 +3701,12 @@ public int func_72800_K() { @@ -878,7 +897,7 @@ } public Random func_72843_D(int p_72843_1_, int p_72843_2_, int p_72843_3_) -@@ -3539,7 +3746,7 @@ +@@ -3539,7 +3750,7 @@ @SideOnly(Side.CLIENT) public double func_72919_O() { @@ -887,7 +906,7 @@ } public void func_175715_c(int p_175715_1_, BlockPos p_175715_2_, int p_175715_3_) -@@ -3573,7 +3780,7 @@ +@@ -3573,7 +3784,7 @@ public void func_175666_e(BlockPos p_175666_1_, Block p_175666_2_) { @@ -896,7 +915,7 @@ { BlockPos blockpos1 = p_175666_1_.func_177972_a(enumfacing); -@@ -3581,18 +3788,15 @@ +@@ -3581,18 +3792,15 @@ { IBlockState iblockstate1 = this.func_180495_p(blockpos1); @@ -919,7 +938,7 @@ } } } -@@ -3658,6 +3862,124 @@ +@@ -3658,6 +3866,124 @@ return j2 >= -128 && j2 <= 128 && k2 >= -128 && k2 <= 128; } diff --git a/src/main/java/net/minecraftforge/server/ForgeTimeTracker.java b/src/main/java/net/minecraftforge/server/ForgeTimeTracker.java index 53a7372b0a..698a18358d 100644 --- a/src/main/java/net/minecraftforge/server/ForgeTimeTracker.java +++ b/src/main/java/net/minecraftforge/server/ForgeTimeTracker.java @@ -20,34 +20,54 @@ package net.minecraftforge.server; import java.lang.ref.WeakReference; -import java.util.Arrays; import java.util.Map; -import java.util.Map.Entry; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMap.Builder; import com.google.common.collect.MapMaker; import net.minecraft.entity.Entity; import net.minecraft.tileentity.TileEntity; +import net.minecraftforge.server.timings.TimeTracker; +/** + * @deprecated To be removed in 1.13 - Implementation has been moved + * @see net.minecraftforge.server.timings.TimeTracker + */ +@Deprecated public class ForgeTimeTracker { + /** + * @deprecated To be removed in 1.13 - Implementation has been moved + * @see net.minecraftforge.server.timings.TimeTracker + */ + @Deprecated public static boolean tileEntityTracking; + + /** + * @deprecated To be removed in 1.13 - Implementation has been moved + * @see net.minecraftforge.server.timings.TimeTracker + */ + @Deprecated public static int tileEntityTrackingDuration; + + /** + * @deprecated To be removed in 1.13 - Implementation has been moved + * @see net.minecraftforge.server.timings.TimeTracker + */ + @Deprecated public static long tileEntityTrackingTime; - + private Map tileEntityTimings; private WeakReference tile; - + private static final ForgeTimeTracker INSTANCE = new ForgeTimeTracker(); /* not implemented private WeakReference entity; private Map entityTimings; */ - + private long timing; - + private ForgeTimeTracker() { MapMaker mm = new MapMaker(); @@ -55,7 +75,7 @@ public class ForgeTimeTracker { tileEntityTimings = mm.makeMap(); //entityTimings = mm.makeMap(); } - + private void trackTileStart(TileEntity tileEntity, long nanoTime) { @@ -67,7 +87,7 @@ public class ForgeTimeTracker { { tileEntityTracking = false; tileEntityTrackingTime = 0; - + return; } tile = new WeakReference(tileEntity); @@ -88,42 +108,61 @@ public class ForgeTimeTracker { timings[idx] = (int) (nanoTime - timing); } + /** + * @deprecated To be removed in 1.13 - Implementation has been moved + * @see TimeTracker#getTimingData() + */ + @Deprecated public static ImmutableMap getTileTimings() { return INSTANCE.buildImmutableTileEntityTimingMap(); } - + private ImmutableMap buildImmutableTileEntityTimingMap() { - Builder builder = ImmutableMap.builder(); - for (Entry entry : tileEntityTimings.entrySet()) - { - builder.put(entry.getKey(), Arrays.copyOfRange(entry.getValue(), 0, 100)); - } + ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); + TimeTracker.TILE_ENTITY_UPDATE.getTimingData().stream() + .filter(t -> t.getObject().get() != null).forEach(e -> builder.put(e.getObject().get(), e.getRawTimingData())); return builder.build(); } - + /** + * @deprecated To be removed in 1.13 - Implementation has been moved + * @see TimeTracker#trackStart(Object) + */ + @Deprecated public static void trackStart(TileEntity tileEntity) { - if (!tileEntityTracking) return; - INSTANCE.trackTileStart(tileEntity, System.nanoTime()); + TimeTracker.TILE_ENTITY_UPDATE.trackStart(tileEntity); } + /** + * @deprecated To be removed in 1.13 - Implementation has been moved + * @see TimeTracker#trackEnd(Object) + */ + @Deprecated public static void trackEnd(TileEntity tileEntity) { - if (!tileEntityTracking) return; - INSTANCE.trackTileEnd(tileEntity, System.nanoTime()); + TimeTracker.TILE_ENTITY_UPDATE.trackEnd(tileEntity); } + /** + * @deprecated To be removed in 1.13 - Implementation has been moved + * @see TimeTracker#trackStart(Object) + */ + @Deprecated public static void trackStart(Entity par1Entity) { - } + /** + * @deprecated To be removed in 1.13 - Implementation has been moved + * @see TimeTracker#trackEnd(Object) + */ + @Deprecated public static void trackEnd(Entity par1Entity) { - + } } diff --git a/src/main/java/net/minecraftforge/server/command/CommandTrack.java b/src/main/java/net/minecraftforge/server/command/CommandTrack.java index fd3e4f89d3..a5b0a22d3a 100644 --- a/src/main/java/net/minecraftforge/server/command/CommandTrack.java +++ b/src/main/java/net/minecraftforge/server/command/CommandTrack.java @@ -19,19 +19,42 @@ package net.minecraftforge.server.command; import javax.annotation.Nullable; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.List; import net.minecraft.command.CommandBase; import net.minecraft.command.CommandException; import net.minecraft.command.ICommandSender; import net.minecraft.command.WrongUsageException; +import net.minecraft.entity.Entity; import net.minecraft.server.MinecraftServer; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; -import net.minecraftforge.server.ForgeTimeTracker; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.world.DimensionType; +import net.minecraftforge.common.DimensionManager; +import net.minecraftforge.server.timings.ForgeTimings; +import net.minecraftforge.server.timings.TimeTracker; -class CommandTrack extends CommandBase +class CommandTrack extends CommandTreeBase { + + private static final DecimalFormat TIME_FORMAT = new DecimalFormat("#####0.00"); + + public CommandTrack() + { + addSubcommand(new StartTrackingCommand()); + addSubcommand(new ResetTrackingCommand()); + addSubcommand(new TrackResultsTileEntity()); + addSubcommand(new TrackResultsEntity()); + addSubcommand(new CommandTreeHelp(this)); + } + @Override public String getName() { @@ -47,38 +70,289 @@ class CommandTrack extends CommandBase @Override public String getUsage(ICommandSender sender) { - return "commands.forge.usage.tracking"; + return "commands.forge.tracking.usage"; } - @Override - public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException + private static class StartTrackingCommand extends CommandBase { - if (args.length != 2) - { - throw new WrongUsageException("commands.forge.usage.tracking"); - } - String type = args[0]; - int duration = parseInt(args[1], 1, 60); - if ("te".equals(type)) + @Override + public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException { - ForgeTimeTracker.tileEntityTrackingDuration = duration; - ForgeTimeTracker.tileEntityTracking = true; - sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.tracking.te.enabled", duration)); + if (args.length != 2) + { + throw new WrongUsageException(getUsage(sender)); + } + String type = args[0]; + int duration = parseInt(args[1], 1, 60); + if ("te".equals(type)) + { + TimeTracker.TILE_ENTITY_UPDATE.reset(); + TimeTracker.TILE_ENTITY_UPDATE.enable(duration); + sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.tracking.te.enabled", duration)); + } + else if ("entity".equals(type)) + { + TimeTracker.ENTITY_UPDATE.reset(); + TimeTracker.ENTITY_UPDATE.enable(duration); + sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.tracking.entity.enabled", duration)); + } + else + { + throw new WrongUsageException(getUsage(sender)); + } } - else + + @Override + public List getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, @Nullable BlockPos targetPos) { - throw new WrongUsageException("commands.forge.usage.tracking"); + return Arrays.asList("te", "entity"); + } + + @Override + public String getName() + { + return "start"; + } + + @Override + public int getRequiredPermissionLevel() + { + return 2; + } + + @Override + public String getUsage(ICommandSender sender) + { + return "commands.forge.tracking.start.usage"; } } - @Override - public List getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, @Nullable BlockPos targetPos) + private static class ResetTrackingCommand extends CommandBase { - if (args.length == 1) + @Override + public String getName() { - return Collections.singletonList("te"); + return "reset"; + } + + @Override + public String getUsage(ICommandSender sender) + { + return "commands.forge.tracking.reset.usage"; + } + + @Override + public int getRequiredPermissionLevel() + { + return 2; + } + + @Override + public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException + { + if (args.length != 1) + { + throw new WrongUsageException(getUsage(sender)); + } + String type = args[0]; + if ("te".equals(type)) + { + TimeTracker.TILE_ENTITY_UPDATE.reset(); + sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.tracking.reset")); + } + else if ("entity".equals(type)) + { + TimeTracker.ENTITY_UPDATE.reset(); + sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.tracking.reset")); + } + else + { + throw new WrongUsageException(getUsage(sender)); + } + } + + @Override + public List getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, @Nullable BlockPos targetPos) + { + return Arrays.asList("te", "entity"); + } + } + + /** + * A base command for all the tracking results commands + * + * @param + */ + private static abstract class TrackResultsBaseCommand extends CommandBase + { + + private TimeTracker tracker; + + protected TrackResultsBaseCommand(TimeTracker tracker) + { + this.tracker = tracker; + } + + /** + * Returns the time objects recorded by the time tracker sorted by average time + * + * @return A list of time objects + */ + protected List> getSortedTimings() + { + ArrayList> list = new ArrayList<>(); + + list.addAll(tracker.getTimingData()); + list.sort(Comparator.comparingDouble(ForgeTimings::getAverageTimings)); + Collections.reverse(list); + + return list; + } + + @Override + public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException + { + List> timingsList = getSortedTimings(); + if (timingsList.isEmpty()) + { + sender.sendMessage(TextComponentHelper.createComponentTranslation(sender, "commands.forge.tracking.noData")); + } + else + { + timingsList.stream() + .filter(timings -> timings.getObject().get() != null) + .limit(10) + .forEach(timings -> sender.sendMessage(buildTrackString(sender, timings)) + ); + } + } + + protected abstract ITextComponent buildTrackString(ICommandSender sender, ForgeTimings data); + + /** + * Gets the time suffix for the provided time in nanoseconds + * + * @param time The time in nanoseconds + * @return The time suffix + */ + protected String getTimeSuffix(double time) + { + if (time < 1000) + { + return "µs"; + } + else + { + return "ms"; + } + } + + /** + * Translates a world dimension ID into a name + * + * @param dimId The dimension ID + * @return The name of the dimension + */ + protected String getWorldName(int dimId) + { + DimensionType type = DimensionManager.getProviderType(dimId); + if (type == null) + { + return "Dim " + dimId; + } + else + { + return type.getName(); + } + } + } + + private static class TrackResultsEntity extends TrackResultsBaseCommand + { + public TrackResultsEntity() + { + super(TimeTracker.ENTITY_UPDATE); + } + + @Override + public String getName() + { + return "entity"; + } + + @Override + public String getUsage(ICommandSender sender) + { + return "commands.forge.tracking.entity.usage"; + } + + @Override + protected ITextComponent buildTrackString(ICommandSender sender, ForgeTimings data) + { + Entity entity = data.getObject().get(); + if (entity == null) + return TextComponentHelper.createComponentTranslation(sender, "commands.forge.tracking.invalid"); + + BlockPos currentPos = entity.getPosition(); + String world = getWorldName(entity.world.provider.getDimension()); + double averageTimings = data.getAverageTimings(); + String tickTime = (averageTimings > 1000 ? TIME_FORMAT.format(averageTimings / 1000) : TIME_FORMAT.format(averageTimings)) + getTimeSuffix( + averageTimings); + + return TextComponentHelper.createComponentTranslation(sender, "commands.forge.tracking.timingEntry", entity.getName(), + world, currentPos.getX(), currentPos.getY(), currentPos.getZ(), tickTime); + } + } + + private static class TrackResultsTileEntity extends TrackResultsBaseCommand + { + + public TrackResultsTileEntity() + { + super(TimeTracker.TILE_ENTITY_UPDATE); + } + + @Override + public String getName() + { + return "te"; + } + + @Override + public String getUsage(ICommandSender sender) + { + return "commands.forge.tracking.te.usage"; + } + + @Override + protected ITextComponent buildTrackString(ICommandSender sender, ForgeTimings data) + { + TileEntity te = data.getObject().get(); + if (te == null) + return TextComponentHelper.createComponentTranslation(sender, "commands.forge.tracking.invalid"); + + String name = getTileEntityName(te); + BlockPos pos = te.getPos(); + + double averageTimings = data.getAverageTimings(); + String tickTime = (averageTimings > 1000 ? TIME_FORMAT.format(averageTimings / 1000) : TIME_FORMAT.format(averageTimings)) + getTimeSuffix( + averageTimings); + return TextComponentHelper + .createComponentTranslation(sender, "commands.forge.tracking.timingEntry", name, + getWorldName(te.getWorld().provider.getDimension()), + pos.getX(), pos.getY(), pos.getZ(), tickTime); + } + + private String getTileEntityName(TileEntity tileEntity) + { + ResourceLocation registryId = TileEntity.getKey(tileEntity.getClass()); + if (registryId == null) + return tileEntity.getClass().getSimpleName(); + else + { + return registryId.toString(); + } } - return Collections.emptyList(); } } diff --git a/src/main/java/net/minecraftforge/server/timings/ForgeTimings.java b/src/main/java/net/minecraftforge/server/timings/ForgeTimings.java new file mode 100644 index 0000000000..fa37a24d73 --- /dev/null +++ b/src/main/java/net/minecraftforge/server/timings/ForgeTimings.java @@ -0,0 +1,80 @@ +/* + * 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 + */ + +package net.minecraftforge.server.timings; + +import java.lang.ref.WeakReference; +import java.util.Arrays; + +/** + * ForgeTimings aggregates timings data collected by {@link TimeTracker} for an Object + * and performs operations for interpretation of the data. + * + * @param + */ +public class ForgeTimings +{ + + private WeakReference object; + + private int[] rawTimingData; + + public ForgeTimings(T object, int[] rawTimingData) + { + this.object = new WeakReference(object); + this.rawTimingData = rawTimingData; + } + + /** + * Retrieves the object that the timings are for + * + * @return The object + */ + public WeakReference getObject() + { + return object; + } + + + /** + * Averages the raw timings data collected + * + * @return An average of the raw timing data + */ + public double getAverageTimings() + { + double sum = 0.0; + + for (int data : rawTimingData) + { + sum += data; + } + + return sum / rawTimingData.length; + } + + /** + * Returns a copy of the raw timings data collected by the tracker + * @return The raw timing data + * @deprecated Added for compatibility, remove in 1.13 + */ + public int[] getRawTimingData(){ + return Arrays.copyOfRange(rawTimingData, 0, rawTimingData.length); + } +} diff --git a/src/main/java/net/minecraftforge/server/timings/TimeTracker.java b/src/main/java/net/minecraftforge/server/timings/TimeTracker.java new file mode 100644 index 0000000000..f48dbc36d2 --- /dev/null +++ b/src/main/java/net/minecraftforge/server/timings/TimeTracker.java @@ -0,0 +1,145 @@ +/* + * 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 + */ + +package net.minecraftforge.server.timings; + +import java.lang.ref.WeakReference; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.MapMaker; + +import net.minecraft.entity.Entity; +import net.minecraft.tileentity.TileEntity; + +/** + * A class to assist in the collection of data to measure the update times of ticking objects {currently Tile Entities and Entities} + * + * @param + */ +public class TimeTracker +{ + + /** + * A tracker for timing tile entity update + */ + public static final TimeTracker TILE_ENTITY_UPDATE = new TimeTracker<>(); + /** + * A tracker for timing entity updates + */ + public static final TimeTracker ENTITY_UPDATE = new TimeTracker<>(); + + private boolean enabled; + private int trackingDuration; + private Map timings = new MapMaker().weakKeys().makeMap(); + private WeakReference currentlyTracking; + private long trackTime; + private long timing; + + /** + * Returns the timings data recorded by the tracker + * + * @return An immutable list of timings data collected by this tracker + */ + public ImmutableList> getTimingData() + { + ImmutableList.Builder> builder = ImmutableList.builder(); + + for (Map.Entry entry : timings.entrySet()) + { + builder.add(new ForgeTimings<>(entry.getKey(), Arrays.copyOfRange(entry.getValue(), 0, 99))); + } + return builder.build(); + } + + /** + * Resets the tracker (clears timings and stops any in-progress timings) + */ + public void reset() + { + enabled = false; + trackTime = 0; + timings.clear(); + } + + /** + * Ends the timing of the currently tracking object + * + * @param tracking The object to stop timing + */ + public void trackEnd(T tracking) + { + if (!enabled) + return; + this.trackEnd(tracking, System.nanoTime()); + } + + /** + * Starts recording tracking data for the given duration in seconds + * + * @param duration The duration for the time to track + */ + public void enable(int duration) + { + this.trackingDuration = duration; + this.enabled = true; + } + + /** + * Starts timing of the provided object + * + * @param toTrack The object to start timing + */ + public void trackStart(T toTrack) + { + if (!enabled) + return; + this.trackStart(toTrack, System.nanoTime()); + } + + private void trackEnd(T object, long nanoTime) + { + if (currentlyTracking == null || currentlyTracking.get() != object) + { + currentlyTracking = null; + return; + } + int[] timings = this.timings.computeIfAbsent(object, k -> new int[101]); + int idx = timings[100] = (timings[100] + 1) % 100; + timings[idx] = (int) (nanoTime - timing); + } + + private void trackStart(T toTrack, long nanoTime) + { + if (trackTime == 0) + { + trackTime = nanoTime; + } + else if (trackTime + TimeUnit.NANOSECONDS.convert(trackingDuration, TimeUnit.SECONDS) < nanoTime) + { + enabled = false; + trackTime = 0; + } + + currentlyTracking = new WeakReference<>(toTrack); + timing = nanoTime; + } +} diff --git a/src/main/resources/assets/forge/lang/en_US.lang b/src/main/resources/assets/forge/lang/en_US.lang index ee27f1bc3d..38e3b60cc9 100644 --- a/src/main/resources/assets/forge/lang/en_US.lang +++ b/src/main/resources/assets/forge/lang/en_US.lang @@ -17,6 +17,15 @@ commands.forge.entity.list.single.header=Entity: %s Total: %d commands.forge.entity.list.multiple.header=Total: %d commands.forge.tracking.te.enabled=Tile Entity tracking enabled for %d seconds. +commands.forge.tracking.entity.enabled=Entity tracking enabled for %d seconds. +commands.forge.tracking.usage=Use /forge track help for more information on tracking subcommands. +commands.forge.tracking.start.usage=Use /forge track start +commands.forge.tracking.reset.usage=Use /forge track reset +commands.forge.tracking.reset=Timings data has been cleared! +commands.forge.tracking.te.usage=Use /forge track te + +commands.forge.tracking.timingEntry=%s - %s [%d, %d, %d]: %s +commands.forge.tracking.noData=No data has been recorded yet. commands.tree_base.invalid_cmd=Invalid subcommand '%s'! commands.tree_base.invalid_cmd.list_subcommands=Invalid subcommand '%s'! Available subcommands: %s commands.tree_base.available_subcommands=Available SubCommands: %s