34 Commits

Author SHA1 Message Date
Sotr
c2748ea2df Corrects check in last change 2018-08-05 20:14:08 +08:00
Sotr
e563233ff3 Fixes spawner modify feature 2018-08-05 20:11:31 +08:00
Sotr
9a5de6cf31 [ci skip] Corrects status branch 2018-08-05 06:47:35 +08:00
Sotr
9f7b490f61 Removed dupe locks 2018-08-05 04:30:47 +08:00
Sotr
6dceb465c3 Upstream Paper 2018-08-05 03:12:31 +08:00
Sotr
17ba164cbc Upstream Paper 2018-08-04 23:27:42 +08:00
Sotr
bb19cf0f7a [CI-SKIP] Update Readme 2018-08-04 23:05:06 +08:00
Sotr
0a59c8ce5f Should fix a NPE 2018-08-03 17:01:43 +08:00
Sotr
5d5bb381c0 Fixes CME cause by plugin 2018-08-02 13:18:21 +08:00
Sotr
05cc09ff51 Threadsafe EntityTracker w/ cleanup 2018-08-02 01:00:50 +08:00
Sotr
2ba4bc2755 Better handle locks for EntityTracker and misc 2018-08-02 00:51:56 +08:00
Sotr
9b5b40c002 Fixes tick rate 2018-08-02 00:10:06 +08:00
Sotr
a0545a756d Remove unneed synchronization 2018-08-02 00:07:48 +08:00
Sotr
6e62515d11 Import nms 2018-08-01 23:55:57 +08:00
Sotr
ff100c348e Multi-threaded chunk saving (not really) 2018-08-01 23:41:33 +08:00
Sotr
a25ff5dd93 Save a bit performance 2018-08-01 20:39:21 +08:00
Sotr
c6dbae3c24 [CI-SKIP] Add demo server 2018-08-01 05:53:52 +08:00
Sotr
b2bafb826f Fixes typo 2018-08-01 05:35:03 +08:00
Sotr
fb20bb3113 Fixup versioning 2018-08-01 05:26:19 +08:00
Sotr
927d946dba That not fast 2018-08-01 04:22:49 +08:00
Sotr
9ceec10f18 [CI-SKIP] Add header and clean comment 2018-08-01 04:11:20 +08:00
Sotr
74353989e4 Paper 1.13 Packport: World EntityHuman Lookup Optimizations 2018-08-01 04:01:17 +08:00
Sotr
c2705d4722 Upstream Paper w/ Removes async lighting 2018-08-01 04:00:46 +08:00
Sotr
b743d7dc4c Upstream Paper 2018-07-30 21:56:14 +08:00
Sotr
601ec9e90d This must be handle manually 2018-07-30 01:47:52 +08:00
Sotr
6dcd61f2c7 🤔 2018-07-30 01:33:52 +08:00
Sotr
c42528f4f1 Acutally fixed it 2018-07-30 00:54:50 +08:00
Sotr
402d9d2536 Brand new async lighting system w/ Harmless chunk unload optimization 2018-07-30 00:40:58 +08:00
Sotr
6ba139a83e Removes async lighting - Close GH-33 2018-07-29 23:24:01 +08:00
Sotr
0690cb1c9f FastBitSet 2018-07-29 03:04:37 +08:00
Sotr
b8879db641 Upstream Paper missing 2018-07-29 03:01:38 +08:00
Sotr
a3cc6062b6 Upstream Paper 2018-07-29 02:10:25 +08:00
Sotr
dba9fad220 Make PlayerChunkMap threadsafe 2018-07-29 01:32:34 +08:00
Sotr
af7444df09 Upstream Paper 2018-07-28 22:38:20 +08:00
38 changed files with 5067 additions and 3674 deletions

View File

@@ -1,8 +1,8 @@
# <img src="https://i.loli.net/2018/05/17/5afd869c443ef.png" alt="Akarin Face" align="right">Akarin
[![Powered by](https://img.shields.io/badge/Powered_by-Akarin_project-ee6aa7.svg?style=flat)](https://akarin.io)
[![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/fw2pJAj)
[![bStats](https://img.shields.io/badge/bStats-Torch-0099ff.svg?style=flat)](https://bstats.org/plugin/bukkit/Torch)
[![Build Status](http://ci.pcd.ac.cn/job/Akarin/badge/icon)](http://ci.ilummc.com/job/Akarin/)
[![Circle CI](https://circleci.com/gh/Akarin-project/Akarin/tree/master.svg?style=svg)](https://circleci.com/gh/Akarin-project/Akarin/tree/master)
[![Circle CI](https://circleci.com/gh/Akarin-project/Akarin/tree/master.svg?style=svg)](https://circleci.com/gh/Akarin-project/Akarin/tree/ver/1.12.2)
Akarin is currently **under heavy development** and contributions are welcome!
@@ -24,8 +24,8 @@ Get Akarin
---
### Download
#### Recommended
+ [**Jenkins**](http://ci.ilummc.com/job/Akarin/) - Kudos to [Izzel_Aliz](https://github.com/IzzelAliz)
+ [**Circle CI**](https://circleci.com/gh/Akarin-project/Akarin/tree/master) - Checkout the 'Artifacts' tab of the latest build
+ ~~[**Jenkins**](http://ci.ilummc.com/job/Akarin/)~~ - Kudos to [Izzel_Aliz](https://github.com/IzzelAliz)
+ [**Circle CI**](https://circleci.com/gh/Akarin-project/Akarin/tree/ver/1.12.2) - Checkout the 'Artifacts' tab of the latest build
*Open an [Issue](https://github.com/Akarin-project/Akarin/issues) or a [Pull Request](https://github.com/Akarin-project/Akarin/pulls) if you want to add your website here*
@@ -47,6 +47,7 @@ Get Akarin
Demo Servers
---
* `demo.akarin.io` (official)
* `omc.hk` (auth required)
*Open an [Issue](https://github.com/Akarin-project/Akarin/issues) or a [Pull Request](https://github.com/Akarin-project/Akarin/pulls) if you want to add your website here*
@@ -54,12 +55,6 @@ Contributing
---
* Akarin uses [Mixin](https://github.com/SpongePowered/Mixin) to modify the code. You can checkout the `sources` folder to see more.
* Add your name to the [LICENSE](https://github.com/Akarin-project/Akarin/blob/master/LICENSE.md) if you want to publish your code under the [MIT License](https://github.com/Akarin-project/Akarin/blob/master/licenses/MIT.md).
* If you want to join the [Akarin-project](https://github.com/Akarin-project) team, you can send us an email with your experience and necessary information.
Contact
---
[Discord](https://discord.gg/D3Rsukh)
Email: `kira@kira.moe`
* If you want to join the [Akarin-project](https://github.com/Akarin-project) team, you can [send](mailto://kira@kira.moe) us an email with your experience and necessary information.
![Akarin project](https://i.loli.net/2018/05/13/5af7fbbfbcddf.png)

View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>akarin</artifactId>
<packaging>jar</packaging>
<version>1.12.2-R0.4-RELEASE</version>
<version>1.12.2-R0.4-SNAPSHOT</version>
<name>Akarin</name>
<url>https://github.com/Akarin-project/Akarin</url>
@@ -134,7 +134,7 @@
<dependency>
<groupId>io.akarin</groupId>
<artifactId>legacylauncher</artifactId>
<version>1.25</version>
<version>1.26</version>
</dependency>
<dependency>
<groupId>org.spongepowered</groupId>
@@ -287,15 +287,15 @@
</transformers>
<!-- Akarin - Avoid signature failure -->
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>

View File

@@ -5,6 +5,7 @@ import java.lang.reflect.Method;
import java.util.Queue;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import org.apache.logging.log4j.LogManager;
@@ -96,9 +97,9 @@ public abstract class Akari {
*/
public final static Timing worldTiming = getTiming("Akarin - Full World Tick");
public final static Timing entityCallbackTiming = getTiming("Akarin - Entity Callback");
public final static Timing entityCallbackTiming = getTiming("Akarin - Entity Parallell Await");
public final static Timing callbackTiming = getTiming("Akarin - Callback");
public final static Timing callbackTiming = getTiming("Akarin - Callback Queue");
private static Timing getTiming(String name) {
try {

View File

@@ -1,28 +0,0 @@
package io.akarin.api.internal.mixin;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import net.minecraft.server.Chunk;
import net.minecraft.server.EnumSkyBlock;
public interface IMixinChunk {
AtomicInteger getPendingLightUpdates();
long getLightUpdateTime();
boolean areNeighborsLoaded();
@Nullable Chunk getNeighborChunk(int index);
CopyOnWriteArrayList<Short> getQueuedLightingUpdates(EnumSkyBlock type);
List<Chunk> getNeighbors();
void setNeighborChunk(int index, @Nullable Chunk chunk);
void setLightUpdateTime(long time);
}

View File

@@ -1,16 +0,0 @@
package io.akarin.api.internal.mixin;
import java.util.List;
import java.util.concurrent.ExecutorService;
import net.minecraft.server.BlockPosition;
import net.minecraft.server.Chunk;
import net.minecraft.server.EnumSkyBlock;
public interface IMixinWorldServer {
boolean updateLightAsync(EnumSkyBlock lightType, BlockPosition pos, Chunk chunk);
boolean checkLightAsync(EnumSkyBlock lightType, BlockPosition pos, Chunk currentChunk, List<Chunk> neighbors);
ExecutorService getLightingExecutor();
}

View File

@@ -185,16 +185,6 @@ public class AkarinGlobalConfig {
keepAliveTimeout = getSeconds(getString("core.keep-alive-response-timeout", "30s")) * 1000;
}
public static int asyncLightingThreads;
private static void asyncLightingThreads() {
asyncLightingThreads = getInt("core.async-lighting.executor-threads", 4);
}
public static boolean asyncLightingWorkStealing;
private static void asyncLightingWorkStealing() {
asyncLightingWorkStealing = getBoolean("core.async-lighting.use-work-stealing", false);
}
public static boolean throwOnAsyncCaught;
private static void throwOnAsyncCaught() {
throwOnAsyncCaught = getBoolean("core.thread-safe.async-catcher.throw-on-caught", true);
@@ -294,4 +284,9 @@ public class AkarinGlobalConfig {
private static void forceHardcoreDifficulty() {
forceHardcoreDifficulty = getBoolean("alternative.force-difficulty-on-hardcore", true);
}
public static int fileIOThreads;
private static void fileIOThreads() {
fileIOThreads = getInt("core.chunk-save-threads", 2);
}
}

View File

@@ -0,0 +1,56 @@
package io.akarin.server.mixin.core;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import com.destroystokyo.paper.PaperConfig;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.akarin.server.core.AkarinGlobalConfig;
import net.minecraft.server.FileIOThread;
import net.minecraft.server.IAsyncChunkSaver;
@Mixin(value = FileIOThread.class, remap = false)
public abstract class MixinFileIOThread {
private final Executor executor = Executors.newFixedThreadPool(AkarinGlobalConfig.fileIOThreads, new ThreadFactoryBuilder().setNameFormat("Akarin File IO Thread - %1$d").setPriority(1).build());
private final AtomicInteger queuedChunkCounter = new AtomicInteger(0);
@Shadow(aliases = "e") private volatile boolean isAwaitFinish;
@Overwrite // OBFHELPER: saveChunk
public void a(IAsyncChunkSaver iasyncchunksaver) {
queuedChunkCounter.incrementAndGet();
executor.execute(() -> writeChunk(iasyncchunksaver));
}
/**
* Process a chunk, re-add to the queue if unsuccessful
*/
private void writeChunk(IAsyncChunkSaver iasyncchunksaver) {
if (!iasyncchunksaver.a()) { // PAIL: WriteNextIO() -> Returns if the write was unsuccessful
queuedChunkCounter.decrementAndGet();
if (PaperConfig.enableFileIOThreadSleep) {
try {
Thread.sleep(isAwaitFinish ? 0L : 2L);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
} else {
writeChunk(iasyncchunksaver);
}
}
@Overwrite // OBFHELPER: waitForFinish
public void b() throws InterruptedException {
isAwaitFinish = true;
while (queuedChunkCounter.get() != 0) Thread.sleep(9L);
isAwaitFinish = false;
}
}

View File

@@ -1,23 +0,0 @@
package io.akarin.server.mixin.core;
import org.spongepowered.asm.lib.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import io.akarin.server.core.AkarinGlobalConfig;
import net.minecraft.server.Block;
import net.minecraft.server.Blocks;
import net.minecraft.server.ItemMonsterEgg;
@Mixin(value = ItemMonsterEgg.class, remap = false)
public abstract class MixinItemMonsterEgg {
@Redirect(method = "a*", at = @At(
value = "FIELD",
target = "net/minecraft/server/Blocks.MOB_SPAWNER:Lnet/minecraft/server/Block;",
opcode = Opcodes.GETSTATIC
))
private boolean configurable(Block target) {
return target == Blocks.MOB_SPAWNER && AkarinGlobalConfig.allowSpawnerModify;
}
}

View File

@@ -6,7 +6,6 @@ import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import org.bukkit.World;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.chunkio.ChunkIOExecutor;
import org.bukkit.event.inventory.InventoryMoveItemEvent;
@@ -214,6 +213,8 @@ public abstract class MixinMinecraftServer {
WorldServer world = worlds.get(i < worlds.size() ? i : 0);
synchronized (((IMixinLockProvider) world).lock()) {
tickEntities(world);
world.getTracker().updatePlayers();
world.explosionDensityCache.clear(); // Paper - Optimize explosions
}
}
}, null);
@@ -222,7 +223,6 @@ public abstract class MixinMinecraftServer {
WorldServer world = worlds.get(i);
synchronized (((IMixinLockProvider) world).lock()) {
tickWorld(world);
world.explosionDensityCache.clear(); // Paper - Optimize explosions
}
}
@@ -243,11 +243,6 @@ public abstract class MixinMinecraftServer {
while ((runnable = Akari.callbackQueue.poll()) != null) runnable.run();
Akari.callbackTiming.stopTiming();
for (int i = 0; i < worlds.size(); ++i) {
WorldServer world = worlds.get(i);
tickUnsafeSync(world);
}
MinecraftTimings.connectionTimer.startTiming();
serverConnection().c();
MinecraftTimings.connectionTimer.stopTiming();
@@ -266,12 +261,4 @@ public abstract class MixinMinecraftServer {
}
MinecraftTimings.tickablesTimer.stopTiming();
}
public void tickUnsafeSync(WorldServer world) {
world.timings.doChunkMap.startTiming();
world.manager.flush();
world.timings.doChunkMap.stopTiming();
world.getTracker().updatePlayers();
}
}

View File

@@ -37,8 +37,8 @@ public abstract class MixinTimingHandler {
@SuppressWarnings({"rawtypes", "unchecked"})
@Inject(method = "startTiming", at = @At("HEAD"), cancellable = true)
public void onStartTiming(CallbackInfoReturnable ci) {
if (!Akari.isPrimaryThread(false)) ci.setReturnValue(this); // Avoid modify any field
public void onStartTiming(CallbackInfoReturnable cir) {
if (!Akari.isPrimaryThread(false)) cir.setReturnValue(this); // Avoid modify any field
}
@Overwrite

View File

@@ -1,21 +1,58 @@
package io.akarin.server.mixin.core;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Set;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.command.defaults.VersionCommand;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import com.google.common.base.Charsets;
import io.akarin.api.internal.Akari;
import io.akarin.server.core.AkarinGlobalConfig;
import net.minecraft.server.MCUtil;
@Mixin(value = VersionCommand.class, remap = false)
public abstract class MixinVersionCommand {
@Shadow private static int getFromRepo(String repo, String hash) { return 0; }
@Overwrite
private static int getFromRepo(String repo, String hash) {
try {
HttpURLConnection connection = (HttpURLConnection) new URL("https://api.github.com/repos/" + repo + "/compare/ver/1.12.2..." + hash).openConnection();
connection.connect();
if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) return -2; // Unknown commit
try (
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8))
) {
JSONObject obj = (JSONObject) new JSONParser().parse(reader);
String status = (String) obj.get("status");
switch (status) {
case "identical":
return 0;
case "behind":
return ((Number) obj.get("behind_by")).intValue();
default:
return -1;
}
} catch (ParseException | NumberFormatException e) {
e.printStackTrace();
return -1;
}
} catch (IOException e) {
e.printStackTrace();
return -1;
}
}
/**
* Match current version with repository and calculate the distance

View File

@@ -16,9 +16,9 @@ public abstract class MixinWorldManager {
@Overwrite
public void a(Entity entity) {
this.world.getTracker().entriesLock.lock(); // Akarin
this.world.getTracker().entriesLock.writeLock().lock(); // Akarin
this.world.getTracker().track(entity);
this.world.getTracker().entriesLock.unlock(); // Akarin
this.world.getTracker().entriesLock.writeLock().unlock(); // Akarin
if (entity instanceof EntityPlayer) {
this.world.worldProvider.a((EntityPlayer) entity);

View File

@@ -1,20 +1,11 @@
package io.akarin.server.mixin.core;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import io.akarin.api.internal.mixin.IMixinLockProvider;
import net.minecraft.server.WorldServer;
@Mixin(value = WorldServer.class, remap = false)
public abstract class MixinWorldServer implements IMixinLockProvider {
@Redirect(method = "doTick()V", at = @At(
value = "INVOKE",
target = "net/minecraft/server/PlayerChunkMap.flush()V"
))
public void onFlush() {} // Migrated to main thread
private final Object tickLock = new Object();
@Override

View File

@@ -1,130 +0,0 @@
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package io.akarin.server.mixin.cps;
import java.util.List;
import javax.annotation.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.google.common.collect.Lists;
import io.akarin.api.internal.mixin.IMixinChunk;
import net.minecraft.server.BlockPosition;
import net.minecraft.server.Chunk;
import net.minecraft.server.EnumDirection;
import net.minecraft.server.MCUtil;
import net.minecraft.server.World;
@Mixin(value = Chunk.class, remap = false)
public abstract class MixinChunk implements IMixinChunk {
private Chunk[] neighborChunks = new Chunk[4];
private static final EnumDirection[] CARDINAL_DIRECTIONS = new EnumDirection[] {EnumDirection.NORTH, EnumDirection.SOUTH, EnumDirection.EAST, EnumDirection.WEST};
@Shadow @Final public World world;
@Shadow @Final public int locX;
@Shadow @Final public int locZ;
@Override
public Chunk getNeighborChunk(int index) {
return this.neighborChunks[index];
}
@Override
public void setNeighborChunk(int index, @Nullable Chunk chunk) {
this.neighborChunks[index] = chunk;
}
@Override
public List<Chunk> getNeighbors() {
List<Chunk> neighborList = Lists.newArrayList();
for (Chunk neighbor : this.neighborChunks) {
if (neighbor != null) {
neighborList.add(neighbor);
}
}
return neighborList;
}
@Override
public boolean areNeighborsLoaded() {
for (int i = 0; i < 4; i++) {
if (this.neighborChunks[i] == null) {
return false;
}
}
return true;
}
private static int directionToIndex(EnumDirection direction) {
switch (direction) {
case NORTH:
return 0;
case SOUTH:
return 1;
case EAST:
return 2;
case WEST:
return 3;
default:
throw new IllegalArgumentException("Unexpected direction");
}
}
@Inject(method = "addEntities", at = @At("RETURN"))
public void onLoadReturn(CallbackInfo ci) {
BlockPosition origin = new BlockPosition(locX, 0, locZ);
for (EnumDirection direction : CARDINAL_DIRECTIONS) {
BlockPosition shift = origin.shift(direction);
Chunk neighbor = MCUtil.getLoadedChunkWithoutMarkingActive(world.getChunkProvider(), shift.getX(), shift.getZ());
if (neighbor != null) {
int neighborIndex = directionToIndex(direction);
int oppositeNeighborIndex = directionToIndex(direction.opposite());
this.setNeighborChunk(neighborIndex, neighbor);
((IMixinChunk) neighbor).setNeighborChunk(oppositeNeighborIndex, (Chunk) (Object) this);
}
}
}
@Inject(method = "removeEntities", at = @At("RETURN"))
public void onUnload(CallbackInfo ci) {
BlockPosition origin = new BlockPosition(locX, 0, locZ);
for (EnumDirection direction : CARDINAL_DIRECTIONS) {
BlockPosition shift = origin.shift(direction);
Chunk neighbor = MCUtil.getLoadedChunkWithoutMarkingActive(world.getChunkProvider(), shift.getX(), shift.getZ());
if (neighbor != null) {
int neighborIndex = directionToIndex(direction);
int oppositeNeighborIndex = directionToIndex(direction.opposite());
this.setNeighborChunk(neighborIndex, null);
((IMixinChunk) neighbor).setNeighborChunk(oppositeNeighborIndex, null);
}
}
}
}

View File

@@ -40,6 +40,7 @@ public abstract class MixinChunkProviderServer {
long unloadAfter = world.paperConfig.delayChunkUnloadsBy;
SlackActivityAccountant activityAccountant = world.getMinecraftServer().slackActivityAccountant;
activityAccountant.startActivity(0.5);
ObjectIterator<Entry<Chunk>> it = chunks.long2ObjectEntrySet().fastIterator();
int remainingChunks = chunks.size();
int targetSize = Math.min(remainingChunks - 100, (int) (remainingChunks * UNLOAD_QUEUE_RESIZE_FACTOR)); // Paper - Make more aggressive
@@ -47,23 +48,19 @@ public abstract class MixinChunkProviderServer {
while (it.hasNext()) {
Entry<Chunk> entry = it.next();
Chunk chunk = entry.getValue();
if (chunk == null) continue;
if (chunk.isUnloading()) {
if (chunk != null && chunk.isUnloading()) {
if (chunk.scheduledForUnload != null) {
if (now - chunk.scheduledForUnload > unloadAfter) {
chunk.scheduledForUnload = null;
} else continue;
if (now - chunk.scheduledForUnload <= unloadAfter) continue;
}
if (!unloadChunk(chunk, true)) { // Event cancelled
// If a plugin cancelled it, we shouldn't trying unload it for a while
chunk.setShouldUnload(false);
continue;
if (unloadChunk(chunk, true)) {
it.remove();
}
chunk.setShouldUnload(false);
chunk.scheduledForUnload = null;
it.remove();
if (--remainingChunks <= targetSize || activityAccountant.activityTimeIsExhausted()) break; // more slack since the target size not work as intended
if (--remainingChunks <= targetSize && activityAccountant.activityTimeIsExhausted()) break;
}
}
activityAccountant.endActivity();

View File

@@ -1,671 +0,0 @@
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package io.akarin.server.mixin.lighting;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import io.akarin.api.internal.Akari;
import io.akarin.api.internal.mixin.IMixinChunk;
import io.akarin.api.internal.mixin.IMixinWorldServer;
import net.minecraft.server.BlockPosition;
import net.minecraft.server.Blocks;
import net.minecraft.server.Chunk;
import net.minecraft.server.ChunkSection;
import net.minecraft.server.EnumDirection;
import net.minecraft.server.EnumSkyBlock;
import net.minecraft.server.IBlockData;
import net.minecraft.server.MCUtil;
import net.minecraft.server.TileEntity;
import net.minecraft.server.World;
import net.minecraft.server.BlockPosition.MutableBlockPosition;
@Mixin(value = Chunk.class, remap = false, priority = 1001)
public abstract class MixinChunk implements IMixinChunk {
// Keeps track of block positions in this chunk currently queued for sky light update
private CopyOnWriteArrayList<Short> queuedSkyLightingUpdates = new CopyOnWriteArrayList<>();
// Keeps track of block positions in this chunk currently queued for block light update
private CopyOnWriteArrayList<Short> queuedBlockLightingUpdates = new CopyOnWriteArrayList<>();
private AtomicInteger pendingLightUpdates = new AtomicInteger();
private long lightUpdateTime;
private static ExecutorService lightExecutorService;
@Shadow(aliases = "m") private boolean isGapLightingUpdated;
@Shadow(aliases = "r") private boolean ticked;
@Shadow @Final private ChunkSection[] sections;
@Shadow @Final public int locX;
@Shadow @Final public int locZ;
@Shadow @Final public World world;
@Shadow @Final public int[] heightMap;
/** Which columns need their skylightMaps updated. */
@Shadow(aliases = "i") @Final private boolean[] updateSkylightColumns;
/** Queue containing the BlockPosition of tile entities queued for creation */
@Shadow(aliases = "y") @Final private ConcurrentLinkedQueue<BlockPosition> tileEntityPosQueue;
/** Boolean value indicating if the terrain is populated. */
@Shadow(aliases = "done") private boolean isTerrainPopulated;
@Shadow(aliases = "lit") private boolean isLightPopulated;
/** Lowest value in the heightmap. */
@Shadow(aliases = "v") private int heightMapMinimum;
@Shadow(aliases = "b") public abstract int getHeightValue(int x, int z);
@Shadow(aliases = "g") @Nullable public abstract TileEntity createNewTileEntity(BlockPosition pos);
@Shadow(aliases = "a") @Nullable public abstract TileEntity getTileEntity(BlockPosition pos, Chunk.EnumTileEntityState state);
@Shadow @Final public abstract IBlockData getBlockData(BlockPosition pos);
@Shadow @Final public abstract IBlockData getBlockData(int x, int y, int z);
@Shadow public abstract boolean isUnloading();
/** Checks the height of a block next to a sky-visible block and schedules a lighting update as necessary */
@Shadow(aliases = "b") public abstract void checkSkylightNeighborHeight(int x, int z, int maxValue);
@Shadow(aliases = "a") public abstract void updateSkylightNeighborHeight(int x, int z, int startY, int endY);
@Shadow(aliases = "z") public abstract void setSkylightUpdated();
@Shadow(aliases = "g") public abstract int getTopFilledSegment();
@Shadow public abstract void markDirty();
@Inject(method = "<init>", at = @At("RETURN"))
public void onConstruct(World worldIn, int x, int z, CallbackInfo ci) {
lightExecutorService = ((IMixinWorldServer) worldIn).getLightingExecutor();
}
@Override
public AtomicInteger getPendingLightUpdates() {
return this.pendingLightUpdates;
}
@Override
public long getLightUpdateTime() {
return this.lightUpdateTime;
}
@Override
public void setLightUpdateTime(long time) {
this.lightUpdateTime = time;
}
@Inject(method = "b(Z)V", at = @At("HEAD"), cancellable = true)
private void onTickHead(boolean skipRecheckGaps, CallbackInfo ci) {
final List<Chunk> neighbors = this.getSurroundingChunks();
if (this.isGapLightingUpdated && this.world.worldProvider.m() && !skipRecheckGaps && !neighbors.isEmpty()) { // OBFHELPER: hasSkyLight
lightExecutorService.execute(() -> {
this.recheckGapsAsync(neighbors);
});
this.isGapLightingUpdated = false;
}
this.ticked = true;
if ((true || !this.isLightPopulated) && this.isTerrainPopulated && !neighbors.isEmpty()) {
lightExecutorService.execute(() -> {
this.checkLightAsync(neighbors);
});
// set to true to avoid requeuing the same task when not finished
this.isLightPopulated = true;
}
while (!this.tileEntityPosQueue.isEmpty()) {
BlockPosition blockpos = this.tileEntityPosQueue.poll();
if (this.getTileEntity(blockpos, Chunk.EnumTileEntityState.CHECK) == null && this.getBlockData(blockpos).getBlock().isTileEntity()) { // OBFHELPER: getTileEntity
TileEntity tileentity = this.createNewTileEntity(blockpos);
this.world.setTileEntity(blockpos, tileentity);
this.world.b(blockpos, blockpos); // OBFHELPER: markBlockRangeForRenderUpdate
}
}
ci.cancel();
}
@Redirect(method = "b(III)V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.getHighestBlockYAt(Lnet/minecraft/server/BlockPosition;)Lnet/minecraft/server/BlockPosition;"))
private BlockPosition onCheckSkylightGetHeight(World world, BlockPosition pos) {
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, null);
if (chunk == null) {
return BlockPosition.ZERO;
}
return new BlockPosition(pos.getX(), chunk.b(pos.getX() & 15, pos.getZ() & 15), pos.getZ()); // OBFHELPER: getHeightValue
}
@Redirect(method = "a(IIII)V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.areChunksLoaded(Lnet/minecraft/server/BlockPosition;I)Z"))
private boolean onAreaLoadedSkyLightNeighbor(World world, BlockPosition pos, int radius) {
return this.isAreaLoaded();
}
@Redirect(method = "a(IIII)V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.c(Lnet/minecraft/server/EnumSkyBlock;Lnet/minecraft/server/BlockPosition;)Z"))
private boolean onCheckLightForSkylightNeighbor(World world, EnumSkyBlock enumSkyBlock, BlockPosition pos) {
return this.checkWorldLightFor(enumSkyBlock, pos);
}
@Inject(method = "h(Z)V", at = @At("HEAD"), cancellable = true)
private void onRecheckGaps(CallbackInfo ci) {
if (this.world.getMinecraftServer().isStopped() || lightExecutorService.isShutdown()) {
return;
}
if (this.isUnloading()) {
return;
}
final List<Chunk> neighborChunks = this.getSurroundingChunks();
if (neighborChunks.isEmpty()) {
this.isGapLightingUpdated = true;
return;
}
if (Akari.isPrimaryThread()) {
try {
lightExecutorService.execute(() -> {
this.recheckGapsAsync(neighborChunks);
});
} catch (RejectedExecutionException ex) {
// This could happen if ServerHangWatchdog kills the server
// between the start of the method and the execute() call.
if (!this.world.getMinecraftServer().isStopped() && !lightExecutorService.isShutdown()) {
throw ex;
}
}
} else {
this.recheckGapsAsync(neighborChunks);
}
ci.cancel();
}
/**
* Rechecks chunk gaps async.
*
* @param neighbors A thread-safe list of surrounding neighbor chunks
*/
private void recheckGapsAsync(List<Chunk> neighbors) {
this.isLightPopulated = false;
for (int i = 0; i < 16; ++i) {
for (int j = 0; j < 16; ++j) {
if (this.updateSkylightColumns[i + j * 16]) {
this.updateSkylightColumns[i + j * 16] = false;
int k = this.getHeightValue(i, j);
int l = this.locX * 16 + i;
int i1 = this.locZ * 16 + j;
int j1 = Integer.MAX_VALUE;
for (EnumDirection enumfacing : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
final Chunk chunk = this.getLightChunk((l + enumfacing.getAdjacentX()) >> 4, (i1 + enumfacing.getAdjacentZ()) >> 4, neighbors);
if (chunk == null || chunk.isUnloading()) {
continue;
}
j1 = Math.min(j1, chunk.w()); // OBFHELPER: getLowestHeight
}
this.checkSkylightNeighborHeight(l, i1, j1);
for (EnumDirection enumfacing1 : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
this.checkSkylightNeighborHeight(l + enumfacing1.getAdjacentX(), i1 + enumfacing1.getAdjacentZ(), k);
}
}
}
this.isGapLightingUpdated = false;
}
}
@Redirect(method = "n()V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.getType(Lnet/minecraft/server/BlockPosition;)Lnet/minecraft/server/IBlockData;"))
private IBlockData onRelightChecksGetBlockData(World world, BlockPosition pos) {
Chunk chunk = MCUtil.getLoadedChunkWithoutMarkingActive(world.getChunkProvider(), pos.getX() >> 4, pos.getZ() >> 4);
final IMixinChunk spongeChunk = (IMixinChunk) chunk;
if (chunk == null || chunk.isUnloading() || !spongeChunk.areNeighborsLoaded()) {
return Blocks.AIR.getBlockData();
}
return chunk.getBlockData(pos);
}
@Redirect(method = "n()V", at = @At(value = "INVOKE", target = "net/minecraft/server/World.w(Lnet/minecraft/server/BlockPosition;)Z"))
private boolean onRelightChecksCheckLight(World world, BlockPosition pos) {
return this.checkWorldLight(pos);
}
// Avoids grabbing chunk async during light check
@Redirect(method = "e(II)Z", at = @At(value = "INVOKE", target = "net/minecraft/server/World.w(Lnet/minecraft/server/BlockPosition;)Z"))
private boolean onCheckLightWorld(World world, BlockPosition pos) {
return this.checkWorldLight(pos);
}
@Inject(method = "o()V", at = @At("HEAD"), cancellable = true)
private void checkLightHead(CallbackInfo ci) {
if (this.world.getMinecraftServer().isStopped() || lightExecutorService.isShutdown()) {
return;
}
if (this.isUnloading()) {
return;
}
final List<Chunk> neighborChunks = this.getSurroundingChunks();
if (neighborChunks.isEmpty()) {
this.isLightPopulated = false;
return;
}
if (Akari.isPrimaryThread()) {
try {
lightExecutorService.execute(() -> {
this.checkLightAsync(neighborChunks);
});
} catch (RejectedExecutionException ex) {
// This could happen if ServerHangWatchdog kills the server
// between the start of the method and the execute() call.
if (!this.world.getMinecraftServer().isStopped() && !lightExecutorService.isShutdown()) {
throw ex;
}
}
} else {
this.checkLightAsync(neighborChunks);
}
ci.cancel();
}
/**
* Checks light async.
*
* @param neighbors A thread-safe list of surrounding neighbor chunks
*/
private void checkLightAsync(List<Chunk> neighbors) {
this.isTerrainPopulated = true;
this.isLightPopulated = true;
BlockPosition blockpos = new BlockPosition(this.locX << 4, 0, this.locZ << 4);
if (this.world.worldProvider.m()) { // OBFHELPER: hasSkyLight
CHECK_LIGHT:
for (int i = 0; i < 16; ++i) {
for (int j = 0; j < 16; ++j) {
if (!this.checkLightAsync(i, j, neighbors)) {
this.isLightPopulated = false;
break CHECK_LIGHT;
}
}
}
if (this.isLightPopulated) {
for (EnumDirection enumfacing : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
int k = enumfacing.c() == EnumDirection.EnumAxisDirection.POSITIVE ? 16 : 1; // OBFHELPER: getAxisDirection
final BlockPosition pos = blockpos.shift(enumfacing, k);
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, neighbors);
if (chunk == null) {
continue;
}
chunk.a(enumfacing.opposite()); // OBFHELPER: checkLightSide
}
this.setSkylightUpdated();
}
}
}
/**
* Checks light async.
*
* @param x The x position of chunk
* @param z The z position of chunk
* @param neighbors A thread-safe list of surrounding neighbor chunks
* @return True if light update was successful, false if not
*/
private boolean checkLightAsync(int x, int z, List<Chunk> neighbors) {
int i = this.getTopFilledSegment();
boolean flag = false;
boolean flag1 = false;
MutableBlockPosition blockpos$mutableblockpos = new MutableBlockPosition((this.locX << 4) + x, 0, (this.locZ << 4) + z);
for (int j = i + 16 - 1; j > this.world.getSeaLevel() || j > 0 && !flag1; --j) {
blockpos$mutableblockpos.setValues(blockpos$mutableblockpos.getX(), j, blockpos$mutableblockpos.getZ());
int k = this.getBlockData(blockpos$mutableblockpos).c(); // OBFHELPER: getLightOpacity
if (k == 255 && blockpos$mutableblockpos.getY() < this.world.getSeaLevel()) {
flag1 = true;
}
if (!flag && k > 0) {
flag = true;
} else if (flag && k == 0 && !this.checkWorldLight(blockpos$mutableblockpos, neighbors)) {
return false;
}
}
for (int l = blockpos$mutableblockpos.getY(); l > 0; --l) {
blockpos$mutableblockpos.setValues(blockpos$mutableblockpos.getX(), l, blockpos$mutableblockpos.getZ());
if (this.getBlockData(blockpos$mutableblockpos).d() > 0) { // getLightValue
this.checkWorldLight(blockpos$mutableblockpos, neighbors);
}
}
return true;
}
/**
* Thread-safe method to retrieve a chunk during async light updates.
*
* @param chunkX The x position of chunk.
* @param chunkZ The z position of chunk.
* @param neighbors A thread-safe list of surrounding neighbor chunks
* @return The chunk if available, null if not
*/
private Chunk getLightChunk(int chunkX, int chunkZ, List<Chunk> neighbors) {
final Chunk currentChunk = (Chunk) (Object) this;
if (currentChunk.a(chunkX, chunkZ)) { // OBFHELPER: isAtLocation
if (currentChunk.isUnloading()) {
return null;
}
return currentChunk;
}
if (neighbors == null) {
neighbors = this.getSurroundingChunks();
if (neighbors.isEmpty()) {
return null;
}
}
for (Chunk neighbor : neighbors) {
if (neighbor.a(chunkX, chunkZ)) { // OBFHELPER: isAtLocation
if (neighbor.isUnloading()) {
return null;
}
return neighbor;
}
}
return null;
}
/**
* Checks if surrounding chunks are loaded thread-safe.
*
* @return True if surrounded chunks are loaded, false if not
*/
private boolean isAreaLoaded() {
if (!this.areNeighborsLoaded()) {
return false;
}
// add diagonal chunks
final Chunk southEastChunk = ((IMixinChunk) this.getNeighborChunk(0)).getNeighborChunk(2);
if (southEastChunk == null) {
return false;
}
final Chunk southWestChunk = ((IMixinChunk) this.getNeighborChunk(0)).getNeighborChunk(3);
if (southWestChunk == null) {
return false;
}
final Chunk northEastChunk = ((IMixinChunk) this.getNeighborChunk(1)).getNeighborChunk(2);
if (northEastChunk == null) {
return false;
}
final Chunk northWestChunk = ((IMixinChunk) this.getNeighborChunk(1)).getNeighborChunk(3);
if (northWestChunk == null) {
return false;
}
return true;
}
/**
* Gets surrounding chunks thread-safe.
*
* @return The list of surrounding chunks, empty list if not loaded
*/
private List<Chunk> getSurroundingChunks() {
if (!this.areNeighborsLoaded()) {
return Collections.emptyList();
}
// add diagonal chunks
final Chunk southEastChunk = ((IMixinChunk) this.getNeighborChunk(0)).getNeighborChunk(2);
if (southEastChunk == null) {
return Collections.emptyList();
}
final Chunk southWestChunk = ((IMixinChunk) this.getNeighborChunk(0)).getNeighborChunk(3);
if (southWestChunk == null) {
return Collections.emptyList();
}
final Chunk northEastChunk = ((IMixinChunk) this.getNeighborChunk(1)).getNeighborChunk(2);
if (northEastChunk == null) {
return Collections.emptyList();
}
final Chunk northWestChunk = ((IMixinChunk) this.getNeighborChunk(1)).getNeighborChunk(3);
if (northWestChunk == null) {
return Collections.emptyList();
}
List<Chunk> chunkList = this.getNeighbors();
chunkList.add(southEastChunk);
chunkList.add(southWestChunk);
chunkList.add(northEastChunk);
chunkList.add(northWestChunk);
return chunkList;
}
@Inject(method = "c(III)V", at = @At("HEAD"), cancellable = true)
private void onRelightBlock(int x, int y, int z, CallbackInfo ci) {
lightExecutorService.execute(() -> {
this.relightBlockAsync(x, y, z);
});
ci.cancel();
}
/**
* Relight's a block async.
*
* @param x The x position
* @param y The y position
* @param z The z position
*/
private void relightBlockAsync(int x, int y, int z) {
int i = this.heightMap[z << 4 | x] & 255;
int j = i;
if (y > i) {
j = y;
}
while (j > 0 && this.getBlockData(x, j - 1, z).c() == 0) { // OBFHELPER: getLightOpacity
--j;
}
if (j != i) {
this.markBlocksDirtyVerticalAsync(x + this.locX * 16, z + this.locZ * 16, j, i);
this.heightMap[z << 4 | x] = j;
int k = this.locX * 16 + x;
int l = this.locZ * 16 + z;
if (this.world.worldProvider.m()) { // OBFHELPER: hasSkyLight
if (j < i) {
for (int j1 = j; j1 < i; ++j1) {
ChunkSection extendedblockstorage2 = this.sections[j1 >> 4];
if (extendedblockstorage2 != Chunk.EMPTY_CHUNK_SECTION) {
extendedblockstorage2.a(x, j1 & 15, z, 15); // OBFHELPER: setSkyLight
// this.world.m(new BlockPosition((this.locX << 4) + x, j1, (this.locZ << 4) + z)); // OBFHELPER: notifyLightSet - client side
}
}
} else {
for (int i1 = i; i1 < j; ++i1) {
ChunkSection extendedblockstorage = this.sections[i1 >> 4];
if (extendedblockstorage != Chunk.EMPTY_CHUNK_SECTION) {
extendedblockstorage.a(x, i1 & 15, z, 0); // OBFHELPER: setSkyLight
// this.world.m(new BlockPosition((this.locX << 4) + x, i1, (this.locZ << 4) + z)); // OBFHELPER: notifyLightSet - client side
}
}
}
int k1 = 15;
while (j > 0 && k1 > 0) {
--j;
int i2 = this.getBlockData(x, j, z).c();
if (i2 == 0) {
i2 = 1;
}
k1 -= i2;
if (k1 < 0) {
k1 = 0;
}
ChunkSection extendedblockstorage1 = this.sections[j >> 4];
if (extendedblockstorage1 != Chunk.EMPTY_CHUNK_SECTION) {
extendedblockstorage1.a(x, j & 15, z, k1); // OBFHELPER: setSkyLight
}
}
}
int l1 = this.heightMap[z << 4 | x];
int j2 = i;
int k2 = l1;
if (l1 < i) {
j2 = l1;
k2 = i;
}
if (l1 < this.heightMapMinimum) {
this.heightMapMinimum = l1;
}
if (this.world.worldProvider.m()) { // OBFHELPER: hasSkyLight
for (EnumDirection enumfacing : EnumDirection.EnumDirectionLimit.HORIZONTAL) {
this.updateSkylightNeighborHeight(k + enumfacing.getAdjacentX(), l + enumfacing.getAdjacentZ(), j2, k2); // OBFHELPER: updateSkylightNeighborHeight
}
this.updateSkylightNeighborHeight(k, l, j2, k2);
}
this.markDirty();
}
}
/**
* Marks a vertical line of blocks as dirty async.
* Instead of calling world directly, we pass chunk safely for async light method.
*
* @param x1
* @param z1
* @param x2
* @param z2
*/
private void markBlocksDirtyVerticalAsync(int x1, int z1, int x2, int z2) {
if (x2 > z2) {
int i = z2;
z2 = x2;
x2 = i;
}
if (this.world.worldProvider.m()) { // OBFHELPER: hasSkyLight
for (int j = x2; j <= z2; ++j) {
final BlockPosition pos = new BlockPosition(x1, j, z1);
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, null);
if (chunk == null) {
continue;
}
((IMixinWorldServer) this.world).updateLightAsync(EnumSkyBlock.SKY, new BlockPosition(x1, j, z1), chunk);
}
}
this.world.b(x1, x2, z1, x1, z2, z1); // OBFHELPER: markBlockRangeForRenderUpdate
}
/**
* Checks world light thread-safe.
*
* @param lightType The type of light to check
* @param pos The block position
* @return True if light update was successful, false if not
*/
private boolean checkWorldLightFor(EnumSkyBlock lightType, BlockPosition pos) {
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, null);
if (chunk == null) {
return false;
}
return ((IMixinWorldServer) this.world).updateLightAsync(lightType, pos, chunk);
}
private boolean checkWorldLight(BlockPosition pos) {
return this.checkWorldLight(pos, null);
}
/**
* Checks world light async.
*
* @param pos The block position
* @param neighbors A thread-safe list of surrounding neighbor chunks
* @return True if light update was successful, false if not
*/
private boolean checkWorldLight(BlockPosition pos, List<Chunk> neighbors) {
boolean flag = false;
final Chunk chunk = this.getLightChunk(pos.getX() >> 4, pos.getZ() >> 4, neighbors);
if (chunk == null) {
return false;
}
if (this.world.worldProvider.m()) { // OBFHELPER: hasSkyLight
flag |= ((IMixinWorldServer) this.world).updateLightAsync(EnumSkyBlock.SKY, pos, chunk);
}
flag = flag | ((IMixinWorldServer) this.world).updateLightAsync(EnumSkyBlock.BLOCK, pos, chunk);
return flag;
}
/**
* Gets the list of block positions currently queued for lighting updates.
*
* @param type The light type
* @return The list of queued block positions, empty if none
*/
@Override
public CopyOnWriteArrayList<Short> getQueuedLightingUpdates(EnumSkyBlock type) {
if (type == EnumSkyBlock.SKY) {
return this.queuedSkyLightingUpdates;
}
return this.queuedBlockLightingUpdates;
}
}

View File

@@ -1,51 +0,0 @@
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package io.akarin.server.mixin.lighting;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.Redirect;
import io.akarin.api.internal.mixin.IMixinChunk;
import net.minecraft.server.ChunkProviderServer;
import net.minecraft.server.WorldServer;
@Mixin(value = ChunkProviderServer.class, remap = false, priority = 1001)
public abstract class MixinChunkProviderServer {
@Shadow @Final public WorldServer world;
@Redirect(method = "unloadChunks", at = @At(
value = "INVOKE",
target = "Lnet/minecraft/server/Chunk;isUnloading()Z"
))
public boolean shouldUnload(IMixinChunk chunk) {
if (chunk.getPendingLightUpdates().get() > 0 || this.world.getTime() - chunk.getLightUpdateTime() < 20) {
return false;
}
return true;
}
}

View File

@@ -1,45 +0,0 @@
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package io.akarin.server.mixin.lighting;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import net.minecraft.server.BlockPosition;
import net.minecraft.server.Chunk;
import net.minecraft.server.EnumSkyBlock;
import net.minecraft.server.IChunkProvider;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.World;
@Mixin(value = World.class, remap = false, priority = 1002)
public abstract class MixinWorld {
@Shadow protected IChunkProvider chunkProvider;
@Shadow int[] J; // OBFHELPER: lightUpdateBlockList
@Shadow(aliases = "c") public abstract boolean checkLightFor(EnumSkyBlock lightType, BlockPosition pos);
@Shadow public abstract MinecraftServer getMinecraftServer();
@Shadow public abstract boolean areChunksLoaded(BlockPosition center, int radius, boolean allowEmpty);
@Shadow public abstract Chunk getChunkIfLoaded(int x, int z);
}

View File

@@ -1,375 +0,0 @@
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package io.akarin.server.mixin.lighting;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.annotation.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.akarin.api.internal.Akari;
import io.akarin.api.internal.mixin.IMixinChunk;
import io.akarin.api.internal.mixin.IMixinWorldServer;
import io.akarin.server.core.AkarinGlobalConfig;
import net.minecraft.server.BlockPosition;
import net.minecraft.server.Chunk;
import net.minecraft.server.EnumDirection;
import net.minecraft.server.EnumSkyBlock;
import net.minecraft.server.IBlockData;
import net.minecraft.server.MCUtil;
import net.minecraft.server.MathHelper;
import net.minecraft.server.WorldServer;
import net.minecraft.server.BlockPosition.PooledBlockPosition;
@Mixin(value = WorldServer.class, remap = false, priority = 1002)
public abstract class MixinWorldServer extends MixinWorld implements IMixinWorldServer {
private static final int NUM_XZ_BITS = 4;
private static final int NUM_SHORT_Y_BITS = 8;
private static final short XZ_MASK = 0xF;
private static final short Y_SHORT_MASK = 0xFF;
private final static ExecutorService lightExecutorService = getExecutorService();
private static ExecutorService getExecutorService() {
return AkarinGlobalConfig.asyncLightingWorkStealing ?
Executors.newFixedThreadPool(AkarinGlobalConfig.asyncLightingThreads, new ThreadFactoryBuilder().setNameFormat("Akarin Async Light Thread").build())
:
Executors.newWorkStealingPool(AkarinGlobalConfig.asyncLightingThreads);
}
@Override
public boolean checkLightFor(EnumSkyBlock lightType, BlockPosition pos) { // OBFHELPER: checkLightFor
return updateLightAsync(lightType, pos, null);
}
@Override
public boolean checkLightAsync(EnumSkyBlock lightType, BlockPosition pos, Chunk currentChunk, List<Chunk> neighbors) {
// Sponge - This check is not needed as neighbors are checked in updateLightAsync
if (false && !this.areChunksLoaded(pos, 17, false)) {
return false;
} else {
final IMixinChunk spongeChunk = (IMixinChunk) currentChunk;
int recheckIndex = 0;
int blockIndex = 0;
int current = this.getLightForAsync(lightType, pos, currentChunk, neighbors); // Sponge - use thread safe method
int rawLight = this.getRawBlockLightAsync(lightType, pos, currentChunk, neighbors); // Sponge - use thread safe method
int x = pos.getX();
int y = pos.getY();
int z = pos.getZ();
if (rawLight > current) {
this.J[blockIndex++] = 133152; // OBFHELPER: lightUpdateBlockList
} else if (rawLight < current) {
this.J[blockIndex++] = 133152 | current << 18; // OBFHELPER: lightUpdateBlockList
while (recheckIndex < blockIndex) {
int blockData = this.J[recheckIndex++]; // OBFHELPER: lightUpdateBlockList
int i2 = (blockData & 63) - 32 + x;
int j2 = (blockData >> 6 & 63) - 32 + y;
int k2 = (blockData >> 12 & 63) - 32 + z;
int l2 = blockData >> 18 & 15;
BlockPosition blockpos = new BlockPosition(i2, j2, k2);
int lightLevel = this.getLightForAsync(lightType, blockpos, currentChunk, neighbors); // Sponge - use thread safe method
if (lightLevel == l2) {
this.setLightForAsync(lightType, blockpos, 0, currentChunk, neighbors); // Sponge - use thread safe method
if (l2 > 0) {
int j3 = MathHelper.a(i2 - x); // abs
int k3 = MathHelper.a(j2 - y);
int l3 = MathHelper.a(k2 - z);
if (j3 + k3 + l3 < 17) {
PooledBlockPosition mutableBlockpos = PooledBlockPosition.aquire();
for (EnumDirection enumfacing : EnumDirection.values()) {
int i4 = i2 + enumfacing.getAdjacentX();
int j4 = j2 + enumfacing.getAdjacentX();
int k4 = k2 + enumfacing.getAdjacentX();
mutableBlockpos.setValues(i4, j4, k4);
// Sponge start - get chunk safely
final Chunk pooledChunk = this.getLightChunk(mutableBlockpos, currentChunk, neighbors);
if (pooledChunk == null) {
continue;
}
int opacity = Math.max(1, pooledChunk.getBlockData(mutableBlockpos).c()); // OBFHELPER: getLightOpacity
lightLevel = this.getLightForAsync(lightType, mutableBlockpos, currentChunk, neighbors);
// Sponge end
if (lightLevel == l2 - opacity && blockIndex < this.J.length) { // OBFHELPER: lightUpdateBlockList
this.J[blockIndex++] = i4 - x + 32 | j4 - y + 32 << 6 | k4 - z + 32 << 12 | l2 - opacity << 18; // OBFHELPER: lightUpdateBlockList
}
}
mutableBlockpos.free();
}
}
}
}
recheckIndex = 0;
}
while (recheckIndex < blockIndex) {
int i5 = this.J[recheckIndex++]; // OBFHELPER: lightUpdateBlockList
int j5 = (i5 & 63) - 32 + x;
int k5 = (i5 >> 6 & 63) - 32 + y;
int l5 = (i5 >> 12 & 63) - 32 + z;
BlockPosition blockpos1 = new BlockPosition(j5, k5, l5);
int i6 = this.getLightForAsync(lightType, blockpos1, currentChunk, neighbors); // Sponge - use thread safe method
int j6 = this.getRawBlockLightAsync(lightType, blockpos1, currentChunk, neighbors); // Sponge - use thread safe method
if (j6 != i6) {
this.setLightForAsync(lightType, blockpos1, j6, currentChunk, neighbors); // Sponge - use thread safe method
if (j6 > i6) {
int k6 = Math.abs(j5 - x);
int l6 = Math.abs(k5 - y);
int i7 = Math.abs(l5 - z);
boolean flag = blockIndex < this.J.length - 6; // OBFHELPER: lightUpdateBlockList
if (k6 + l6 + i7 < 17 && flag) {
// Sponge start - use thread safe method getLightForAsync
if (this.getLightForAsync(lightType, blockpos1.west(), currentChunk, neighbors) < j6) {
this.J[blockIndex++] = j5 - 1 - x + 32 + (k5 - y + 32 << 6) + (l5 - z + 32 << 12); // OBFHELPER: lightUpdateBlockList
}
if (this.getLightForAsync(lightType, blockpos1.east(), currentChunk, neighbors) < j6) {
this.J[blockIndex++] = j5 + 1 - x + 32 + (k5 - y + 32 << 6) + (l5 - z + 32 << 12); // OBFHELPER: lightUpdateBlockList
}
if (this.getLightForAsync(lightType, blockpos1.down(), currentChunk, neighbors) < j6) {
this.J[blockIndex++] = j5 - x + 32 + (k5 - 1 - y + 32 << 6) + (l5 - z + 32 << 12); // OBFHELPER: lightUpdateBlockList
}
if (this.getLightForAsync(lightType, blockpos1.up(), currentChunk, neighbors) < j6) {
this.J[blockIndex++] = j5 - x + 32 + (k5 + 1 - y + 32 << 6) + (l5 - z + 32 << 12); // OBFHELPER: lightUpdateBlockList
}
if (this.getLightForAsync(lightType, blockpos1.north(), currentChunk, neighbors) < j6) {
this.J[blockIndex++] = j5 - x + 32 + (k5 - y + 32 << 6) + (l5 - 1 - z + 32 << 12); // OBFHELPER: lightUpdateBlockList
}
if (this.getLightForAsync(lightType, blockpos1.south(), currentChunk, neighbors) < j6) {
this.J[blockIndex++] = j5 - x + 32 + (k5 - y + 32 << 6) + (l5 + 1 - z + 32 << 12); // OBFHELPER: lightUpdateBlockList
}
// Sponge end
}
}
}
}
// Sponge start - Asynchronous light updates
spongeChunk.getQueuedLightingUpdates(lightType).remove((Short) this.blockPosToShort(pos));
spongeChunk.getPendingLightUpdates().decrementAndGet();
for (Chunk neighborChunk : neighbors) {
final IMixinChunk neighbor = (IMixinChunk) neighborChunk;
neighbor.getPendingLightUpdates().decrementAndGet();
}
// Sponge end
return true;
}
}
@Override
public boolean updateLightAsync(EnumSkyBlock lightType, BlockPosition pos, @Nullable Chunk currentChunk) {
if (this.getMinecraftServer().isStopped() || lightExecutorService.isShutdown()) {
return false;
}
if (currentChunk == null) {
currentChunk = MCUtil.getLoadedChunkWithoutMarkingActive(chunkProvider, pos.getX() >> 4, pos.getZ() >> 4);
}
final IMixinChunk spongeChunk = (IMixinChunk) currentChunk;
if (currentChunk == null || currentChunk.isUnloading() || !spongeChunk.areNeighborsLoaded()) {
return false;
}
final short shortPos = this.blockPosToShort(pos);
if (spongeChunk.getQueuedLightingUpdates(lightType).contains(shortPos)) {
return false;
}
final Chunk chunk = currentChunk;
spongeChunk.getQueuedLightingUpdates(lightType).add(shortPos);
spongeChunk.getPendingLightUpdates().incrementAndGet();
spongeChunk.setLightUpdateTime(chunk.getWorld().getTime());
List<Chunk> neighbors = spongeChunk.getNeighbors();
// add diagonal chunks
Chunk southEastChunk = ((IMixinChunk) spongeChunk.getNeighborChunk(0)).getNeighborChunk(2);
Chunk southWestChunk = ((IMixinChunk) spongeChunk.getNeighborChunk(0)).getNeighborChunk(3);
Chunk northEastChunk = ((IMixinChunk) spongeChunk.getNeighborChunk(1)).getNeighborChunk(2);
Chunk northWestChunk = ((IMixinChunk) spongeChunk.getNeighborChunk(1)).getNeighborChunk(3);
if (southEastChunk != null) {
neighbors.add(southEastChunk);
}
if (southWestChunk != null) {
neighbors.add(southWestChunk);
}
if (northEastChunk != null) {
neighbors.add(northEastChunk);
}
if (northWestChunk != null) {
neighbors.add(northWestChunk);
}
for (Chunk neighborChunk : neighbors) {
final IMixinChunk neighbor = (IMixinChunk) neighborChunk;
neighbor.getPendingLightUpdates().incrementAndGet();
neighbor.setLightUpdateTime(chunk.getWorld().getTime());
}
if (Akari.isPrimaryThread()) { // Akarin
lightExecutorService.execute(() -> {
this.checkLightAsync(lightType, pos, chunk, neighbors);
});
} else {
this.checkLightAsync(lightType, pos, chunk, neighbors);
}
return true;
}
@Override
public ExecutorService getLightingExecutor() {
return lightExecutorService;
}
// Thread safe methods to retrieve a chunk during async light updates
// Each method avoids calling getLoadedChunk and instead accesses the passed neighbor chunk list to avoid concurrency issues
public Chunk getLightChunk(BlockPosition pos, Chunk currentChunk, List<Chunk> neighbors) {
if (currentChunk.a(pos.getX() >> 4, pos.getZ() >> 4)) { // OBFHELPER: isAtLocation
if (currentChunk.isUnloading()) {
return null;
}
return currentChunk;
}
for (Chunk neighbor : neighbors) {
if (neighbor.a(pos.getX() >> 4, pos.getZ() >> 4)) { // OBFHELPER: isAtLocation
if (neighbor.isUnloading()) {
return null;
}
return neighbor;
}
}
return null;
}
private int getLightForAsync(EnumSkyBlock lightType, BlockPosition pos, Chunk currentChunk, List<Chunk> neighbors) {
if (pos.getY() < 0) {
pos = new BlockPosition(pos.getX(), 0, pos.getZ());
}
if (!pos.isValidLocation()) {
return lightType.c; // OBFHELPER: defaultLightValue
}
final Chunk chunk = this.getLightChunk(pos, currentChunk, neighbors);
if (chunk == null || chunk.isUnloading()) {
return lightType.c; // OBFHELPER: defaultLightValue
}
return chunk.getBrightness(lightType, pos);
}
private int getRawBlockLightAsync(EnumSkyBlock lightType, BlockPosition pos, Chunk currentChunk, List<Chunk> neighbors) {
final Chunk chunk = getLightChunk(pos, currentChunk, neighbors);
if (chunk == null || chunk.isUnloading()) {
return lightType.c; // OBFHELPER: defaultLightValue
}
if (lightType == EnumSkyBlock.SKY && chunk.c(pos)) { // OBFHELPER: canSeeSky
return 15;
} else {
IBlockData blockData = chunk.getBlockData(pos);
int blockLight = blockData.d(); // getLightValue
int rawLight = lightType == EnumSkyBlock.SKY ? 0 : blockLight;
int opacity = blockData.c(); // OBFHELPER: getLightOpacity
if (opacity >= 15 && blockLight > 0) {
opacity = 1;
}
if (opacity < 1) {
opacity = 1;
}
if (opacity >= 15) {
return 0;
} else if (rawLight >= 14) {
return rawLight;
} else {
for (EnumDirection facing : EnumDirection.values()) {
BlockPosition blockpos = pos.shift(facing);
int current = this.getLightForAsync(lightType, blockpos, currentChunk, neighbors) - opacity;
if (current > rawLight) {
rawLight = current;
}
if (rawLight >= 14) {
return rawLight;
}
}
return rawLight;
}
}
}
public void setLightForAsync(EnumSkyBlock type, BlockPosition pos, int lightValue, Chunk currentChunk, List<Chunk> neighbors) {
if (pos.isValidLocation()) {
final Chunk chunk = this.getLightChunk(pos, currentChunk, neighbors);
if (chunk != null && !chunk.isUnloading()) {
chunk.a(type, pos, lightValue); // OBFHELPER: setLightFor
// this.notifyLightSet(pos); // client side
}
}
}
private short blockPosToShort(BlockPosition pos) {
short serialized = (short) setNibble(0, pos.getX() & XZ_MASK, 0, NUM_XZ_BITS);
serialized = (short) setNibble(serialized, pos.getY() & Y_SHORT_MASK, 1, NUM_SHORT_Y_BITS);
serialized = (short) setNibble(serialized, pos.getZ() & XZ_MASK, 3, NUM_XZ_BITS);
return serialized;
}
/**
* Modifies bits in an integer.
*
* @param num Integer to modify
* @param data Bits of data to add
* @param which Index of nibble to start at
* @param bitsToReplace The number of bits to replace starting from nibble index
* @return The modified integer
*/
private int setNibble(int num, int data, int which, int bitsToReplace) {
return (num & ~(bitsToReplace << (which * 4)) | (data << (which * 4)));
}
}

View File

@@ -147,6 +147,7 @@ public abstract class PandaRedstoneWire extends Block {
while (!turnOff.isEmpty()) {
BlockPosition pos = turnOff.remove(0);
if (pos == null) continue; // Akarin
IBlockData state = worldIn.getType(pos);
int oldPower = state.get(BlockRedstoneWire.POWER).intValue();
this.canProvidePower = false;
@@ -185,6 +186,7 @@ public abstract class PandaRedstoneWire extends Block {
// Now all needed wires are turned off. Time to turn them on again if there is a power source.
while (!this.turnOn.isEmpty()) {
BlockPosition pos = this.turnOn.remove(0);
if (pos == null) continue; // Akarin
IBlockData state = worldIn.getType(pos);
int oldPower = state.get(BlockRedstoneWire.POWER).intValue();
this.canProvidePower = false;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,623 @@
package net.minecraft.server;
import com.google.common.collect.Maps;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import java.util.concurrent.ConcurrentLinkedQueue; // Paper
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
// Spigot start
import java.util.function.Supplier;
import org.spigotmc.SupplierUtils;
// Spigot end
/**
* Akarin Changes Note
* 1) Removes unneed synchronization (performance)
*/
public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
private ConcurrentLinkedQueue<QueuedChunk> queue = new ConcurrentLinkedQueue<>(); // Paper - Chunk queue improvements
private final Object lock = new Object(); // Paper - Chunk queue improvements
private static final Logger a = LogManager.getLogger();
private final Map<ChunkCoordIntPair, Supplier<NBTTagCompound>> b = Maps.newConcurrentMap(); // Spigot
// CraftBukkit
// private final Set<ChunkCoordIntPair> c = Collections.newSetFromMap(Maps.newConcurrentMap());
private final File d;
private final DataConverterManager e;
// private boolean f;
// CraftBukkit
private static final double SAVE_QUEUE_TARGET_SIZE = 625; // Spigot
public ChunkRegionLoader(File file, DataConverterManager dataconvertermanager) {
this.d = file;
this.e = dataconvertermanager;
}
// Paper start
private long queuedSaves = 0;
private final java.util.concurrent.atomic.AtomicLong processedSaves = new java.util.concurrent.atomic.AtomicLong(0L);
public int getQueueSize() { return queue.size(); }
public long getQueuedSaves() { return queuedSaves; }
public long getProcessedSaves() { return processedSaves.longValue(); }
// Paper end
// CraftBukkit start - Add async variant, provide compatibility
@Nullable
public Chunk a(World world, int i, int j) throws IOException {
world.timings.syncChunkLoadDataTimer.startTiming(); // Spigot
Object[] data = loadChunk(world, i, j);
world.timings.syncChunkLoadDataTimer.stopTiming(); // Spigot
if (data != null) {
Chunk chunk = (Chunk) data[0];
NBTTagCompound nbttagcompound = (NBTTagCompound) data[1];
loadEntities(chunk, nbttagcompound.getCompound("Level"), world);
return chunk;
}
return null;
}
public Object[] loadChunk(World world, int i, int j) throws IOException {
// CraftBukkit end
ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j);
NBTTagCompound nbttagcompound = SupplierUtils.getIfExists(this.b.get(chunkcoordintpair)); // Spigot
if (nbttagcompound == null) {
// CraftBukkit start
nbttagcompound = RegionFileCache.d(this.d, i, j);
if (nbttagcompound == null) {
return null;
}
nbttagcompound = this.e.a((DataConverterType) DataConverterTypes.CHUNK, nbttagcompound);
// CraftBukkit end
}
return this.a(world, i, j, nbttagcompound);
}
public boolean chunkExists(int i, int j) {
ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j);
Supplier<NBTTagCompound> nbttagcompound = this.b.get(chunkcoordintpair); // Spigot
return nbttagcompound != null ? true : RegionFileCache.chunkExists(this.d, i, j);
}
@Nullable
protected Object[] a(World world, int i, int j, NBTTagCompound nbttagcompound) { // CraftBukkit - return Chunk -> Object[]
if (!nbttagcompound.hasKeyOfType("Level", 10)) {
ChunkRegionLoader.a.error("Chunk file at {},{} is missing level data, skipping", Integer.valueOf(i), Integer.valueOf(j));
return null;
} else {
NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Level");
if (!nbttagcompound1.hasKeyOfType("Sections", 9)) {
ChunkRegionLoader.a.error("Chunk file at {},{} is missing block data, skipping", Integer.valueOf(i), Integer.valueOf(j));
return null;
} else {
Chunk chunk = this.a(world, nbttagcompound1);
if (!chunk.a(i, j)) {
ChunkRegionLoader.a.error("Chunk file at {},{} is in the wrong location; relocating. (Expected {}, {}, got {}, {})", Integer.valueOf(i), Integer.valueOf(j), Integer.valueOf(i), Integer.valueOf(j), Integer.valueOf(chunk.locX), Integer.valueOf(chunk.locZ));
nbttagcompound1.setInt("xPos", i);
nbttagcompound1.setInt("zPos", j);
// CraftBukkit start - Have to move tile entities since we don't load them at this stage
NBTTagList tileEntities = nbttagcompound.getCompound("Level").getList("TileEntities", 10);
if (tileEntities != null) {
for (int te = 0; te < tileEntities.size(); te++) {
NBTTagCompound tileEntity = (NBTTagCompound) tileEntities.get(te);
int x = tileEntity.getInt("x") - chunk.locX * 16;
int z = tileEntity.getInt("z") - chunk.locZ * 16;
tileEntity.setInt("x", i * 16 + x);
tileEntity.setInt("z", j * 16 + z);
}
}
// CraftBukkit end
chunk = this.a(world, nbttagcompound1);
}
// CraftBukkit start
Object[] data = new Object[2];
data[0] = chunk;
data[1] = nbttagcompound;
return data;
// CraftBukkit end
}
}
}
public void saveChunk(World world, Chunk chunk, boolean unloaded) throws IOException, ExceptionWorldConflict { // Spigot
world.checkSession();
try {
NBTTagCompound nbttagcompound = new NBTTagCompound();
NBTTagCompound nbttagcompound1 = new NBTTagCompound();
nbttagcompound.set("Level", nbttagcompound1);
nbttagcompound.setInt("DataVersion", 1343);
// Spigot start
final long worldTime = world.getTime();
final boolean worldHasSkyLight = world.worldProvider.m();
saveEntities(nbttagcompound1, chunk, world);
Supplier<NBTTagCompound> completion = new Supplier<NBTTagCompound>() {
public NBTTagCompound get() {
saveBody(nbttagcompound1, chunk, worldTime, worldHasSkyLight);
return nbttagcompound;
}
};
this.a(chunk.k(), SupplierUtils.createUnivaluedSupplier(completion, unloaded && this.b.size() < SAVE_QUEUE_TARGET_SIZE));
// Spigot end
} catch (Exception exception) {
ChunkRegionLoader.a.error("Failed to save chunk", exception);
}
}
protected void a(ChunkCoordIntPair chunkcoordintpair, Supplier<NBTTagCompound> nbttagcompound) { // Spigot
// CraftBukkit
// if (!this.c.contains(chunkcoordintpair))
synchronized (lock) { // Paper - Chunk queue improvements
this.b.put(chunkcoordintpair, nbttagcompound);
}
queuedSaves++; // Paper
queue.add(new QueuedChunk(chunkcoordintpair, nbttagcompound)); // Paper - Chunk queue improvements
FileIOThread.a().a(this);
}
public boolean a() {
// CraftBukkit start
return this.processSaveQueueEntry(false);
}
private /*synchronized*/ boolean processSaveQueueEntry(boolean logCompletion) { // Akarin - remove synchronization
// CraftBukkit start
// Paper start - Chunk queue improvements
QueuedChunk chunk = queue.poll();
if (chunk == null) {
// Paper - end
if (logCompletion) {
// CraftBukkit end
ChunkRegionLoader.a.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", this.d.getName());
}
return false;
} else {
ChunkCoordIntPair chunkcoordintpair = chunk.coords; // Paper - Chunk queue improvements
processedSaves.incrementAndGet(); // Paper
boolean flag;
try {
// this.c.add(chunkcoordintpair);
NBTTagCompound nbttagcompound = SupplierUtils.getIfExists(chunk.compoundSupplier); // Spigot // Paper
// CraftBukkit
if (nbttagcompound != null) {
int attempts = 0; Exception laste = null; while (attempts++ < 5) { // Paper
try {
this.b(chunkcoordintpair, nbttagcompound);
laste = null; break; // Paper
} catch (Exception exception) {
//ChunkRegionLoader.a.error("Failed to save chunk", exception); // Paper
laste = exception; // Paper
}
try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();} } // Paper
if (laste != null) { com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(laste); MinecraftServer.LOGGER.error("Failed to save chunk", laste); } // Paper
}
synchronized (lock) { if (this.b.get(chunkcoordintpair) == chunk.compoundSupplier) { this.b.remove(chunkcoordintpair); } }// Paper - This will not equal if a newer version is still pending
flag = true;
} finally {
//this.b.remove(chunkcoordintpair, value); // CraftBukkit // Spigot // Paper
}
return flag;
}
}
private void b(ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) throws IOException {
// CraftBukkit start
RegionFileCache.e(this.d, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound);
/*
NBTCompressedStreamTools.a(nbttagcompound, (DataOutput) dataoutputstream);
dataoutputstream.close();
*/
// CraftBukkit end
}
public void b(World world, Chunk chunk) throws IOException {}
public void b() {}
public void c() {
try {
// this.f = true; // CraftBukkit
while (true) {
if (this.processSaveQueueEntry(true)) { // CraftBukkit
continue;
}
break; // CraftBukkit - Fix infinite loop when saving chunks
}
} finally {
// this.f = false; // CraftBukkit
}
}
public static void a(DataConverterManager dataconvertermanager) {
dataconvertermanager.a(DataConverterTypes.CHUNK, new DataInspector() {
public NBTTagCompound a(DataConverter dataconverter, NBTTagCompound nbttagcompound, int i) {
if (nbttagcompound.hasKeyOfType("Level", 10)) {
NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("Level");
NBTTagList nbttaglist;
int j;
if (nbttagcompound1.hasKeyOfType("Entities", 9)) {
nbttaglist = nbttagcompound1.getList("Entities", 10);
for (j = 0; j < nbttaglist.size(); ++j) {
nbttaglist.a(j, dataconverter.a(DataConverterTypes.ENTITY, (NBTTagCompound) nbttaglist.i(j), i));
}
}
if (nbttagcompound1.hasKeyOfType("TileEntities", 9)) {
nbttaglist = nbttagcompound1.getList("TileEntities", 10);
for (j = 0; j < nbttaglist.size(); ++j) {
nbttaglist.a(j, dataconverter.a(DataConverterTypes.BLOCK_ENTITY, (NBTTagCompound) nbttaglist.i(j), i));
}
}
}
return nbttagcompound;
}
});
}
private static void saveBody(NBTTagCompound nbttagcompound, Chunk chunk, long worldTime, boolean worldHasSkyLight) { // Spigot
nbttagcompound.setInt("xPos", chunk.locX);
nbttagcompound.setInt("zPos", chunk.locZ);
nbttagcompound.setLong("LastUpdate", worldTime); // Spigot
nbttagcompound.setIntArray("HeightMap", chunk.r());
nbttagcompound.setBoolean("TerrainPopulated", chunk.isDone());
nbttagcompound.setBoolean("LightPopulated", chunk.v());
nbttagcompound.setLong("InhabitedTime", chunk.x());
ChunkSection[] achunksection = chunk.getSections();
NBTTagList nbttaglist = new NBTTagList();
boolean flag = worldHasSkyLight; // Spigot
ChunkSection[] achunksection1 = achunksection;
int i = achunksection.length;
NBTTagCompound nbttagcompound1;
for (int j = 0; j < i; ++j) {
ChunkSection chunksection = achunksection1[j];
if (chunksection != Chunk.a) {
nbttagcompound1 = new NBTTagCompound();
nbttagcompound1.setByte("Y", (byte) (chunksection.getYPosition() >> 4 & 255));
byte[] abyte = new byte[4096];
NibbleArray nibblearray = new NibbleArray();
NibbleArray nibblearray1 = chunksection.getBlocks().exportData(abyte, nibblearray);
nbttagcompound1.setByteArray("Blocks", abyte);
nbttagcompound1.setByteArray("Data", nibblearray.asBytes());
if (nibblearray1 != null) {
nbttagcompound1.setByteArray("Add", nibblearray1.asBytes());
}
nbttagcompound1.setByteArray("BlockLight", chunksection.getEmittedLightArray().asBytes());
if (flag) {
nbttagcompound1.setByteArray("SkyLight", chunksection.getSkyLightArray().asBytes());
} else {
nbttagcompound1.setByteArray("SkyLight", new byte[chunksection.getEmittedLightArray().asBytes().length]);
}
nbttaglist.add(nbttagcompound1);
}
}
nbttagcompound.set("Sections", nbttaglist);
nbttagcompound.setByteArray("Biomes", chunk.getBiomeIndex());
// Spigot start - End this method here and split off entity saving to another method
}
private static void saveEntities(NBTTagCompound nbttagcompound, Chunk chunk, World world) {
int i;
NBTTagCompound nbttagcompound1;
// Spigot end
chunk.g(false);
NBTTagList nbttaglist1 = new NBTTagList();
Iterator iterator;
List<Entity> toUpdate = new java.util.ArrayList<>(); // Paper
for (i = 0; i < chunk.getEntitySlices().length; ++i) {
iterator = chunk.getEntitySlices()[i].iterator();
while (iterator.hasNext()) {
Entity entity = (Entity) iterator.next();
// Paper start
if ((int)Math.floor(entity.locX) >> 4 != chunk.locX || (int)Math.floor(entity.locZ) >> 4 != chunk.locZ) {
LogManager.getLogger().warn(entity + " is not in this chunk, skipping save. This a bug fix to a vanilla bug. Do not report this to PaperMC please.");
toUpdate.add(entity);
continue;
}
if (entity.dead) {
continue;
}
// Paper end
nbttagcompound1 = new NBTTagCompound();
if (entity.d(nbttagcompound1)) {
chunk.g(true);
nbttaglist1.add(nbttagcompound1);
}
}
}
// Paper start - move entities to the correct chunk
for (Entity entity : toUpdate) {
world.entityJoinedWorld(entity, false);
}
// Paper end
nbttagcompound.set("Entities", nbttaglist1);
NBTTagList nbttaglist2 = new NBTTagList();
iterator = chunk.getTileEntities().values().iterator();
while (iterator.hasNext()) {
TileEntity tileentity = (TileEntity) iterator.next();
nbttagcompound1 = tileentity.save(new NBTTagCompound());
nbttaglist2.add(nbttagcompound1);
}
nbttagcompound.set("TileEntities", nbttaglist2);
List list = world.a(chunk, false);
if (list != null) {
long k = world.getTime();
NBTTagList nbttaglist3 = new NBTTagList();
Iterator iterator1 = list.iterator();
while (iterator1.hasNext()) {
NextTickListEntry nextticklistentry = (NextTickListEntry) iterator1.next();
NBTTagCompound nbttagcompound2 = new NBTTagCompound();
MinecraftKey minecraftkey = (MinecraftKey) Block.REGISTRY.b(nextticklistentry.a());
nbttagcompound2.setString("i", minecraftkey == null ? "" : minecraftkey.toString());
nbttagcompound2.setInt("x", nextticklistentry.a.getX());
nbttagcompound2.setInt("y", nextticklistentry.a.getY());
nbttagcompound2.setInt("z", nextticklistentry.a.getZ());
nbttagcompound2.setInt("t", (int) (nextticklistentry.b - k));
nbttagcompound2.setInt("p", nextticklistentry.c);
nbttaglist3.add(nbttagcompound2);
}
nbttagcompound.set("TileTicks", nbttaglist3);
}
}
private Chunk a(World world, NBTTagCompound nbttagcompound) {
int i = nbttagcompound.getInt("xPos");
int j = nbttagcompound.getInt("zPos");
Chunk chunk = new Chunk(world, i, j);
chunk.a(nbttagcompound.getIntArray("HeightMap"));
chunk.d(nbttagcompound.getBoolean("TerrainPopulated"));
chunk.e(nbttagcompound.getBoolean("LightPopulated"));
chunk.c(nbttagcompound.getLong("InhabitedTime"));
NBTTagList nbttaglist = nbttagcompound.getList("Sections", 10);
boolean flag = true;
ChunkSection[] achunksection = new ChunkSection[16];
boolean flag1 = world.worldProvider.m();
for (int k = 0; k < nbttaglist.size(); ++k) {
NBTTagCompound nbttagcompound1 = nbttaglist.get(k);
byte b0 = nbttagcompound1.getByte("Y");
ChunkSection chunksection = new ChunkSection(b0 << 4, flag1, world.chunkPacketBlockController.getPredefinedBlockData(chunk, b0)); // Paper - Anti-Xray - Add predefined block data
byte[] abyte = nbttagcompound1.getByteArray("Blocks");
NibbleArray nibblearray = new NibbleArray(nbttagcompound1.getByteArray("Data"));
NibbleArray nibblearray1 = nbttagcompound1.hasKeyOfType("Add", 7) ? new NibbleArray(nbttagcompound1.getByteArray("Add")) : null;
chunksection.getBlocks().a(abyte, nibblearray, nibblearray1);
chunksection.a(new NibbleArray(nbttagcompound1.getByteArray("BlockLight")));
if (flag1) {
chunksection.b(new NibbleArray(nbttagcompound1.getByteArray("SkyLight")));
}
chunksection.recalcBlockCounts();
achunksection[b0] = chunksection;
}
chunk.a(achunksection);
if (nbttagcompound.hasKeyOfType("Biomes", 7)) {
chunk.a(nbttagcompound.getByteArray("Biomes"));
}
// CraftBukkit start - End this method here and split off entity loading to another method
return chunk;
}
public void loadEntities(Chunk chunk, NBTTagCompound nbttagcompound, World world) {
// CraftBukkit end
world.timings.syncChunkLoadNBTTimer.startTiming(); // Spigot
NBTTagList nbttaglist1 = nbttagcompound.getList("Entities", 10);
for (int l = 0; l < nbttaglist1.size(); ++l) {
NBTTagCompound nbttagcompound2 = nbttaglist1.get(l);
a(nbttagcompound2, world, chunk);
chunk.g(true);
}
NBTTagList nbttaglist2 = nbttagcompound.getList("TileEntities", 10);
for (int i1 = 0; i1 < nbttaglist2.size(); ++i1) {
NBTTagCompound nbttagcompound3 = nbttaglist2.get(i1);
TileEntity tileentity = TileEntity.create(world, nbttagcompound3);
if (tileentity != null) {
chunk.a(tileentity);
}
}
if (nbttagcompound.hasKeyOfType("TileTicks", 9)) {
NBTTagList nbttaglist3 = nbttagcompound.getList("TileTicks", 10);
for (int j1 = 0; j1 < nbttaglist3.size(); ++j1) {
NBTTagCompound nbttagcompound4 = nbttaglist3.get(j1);
Block block;
if (nbttagcompound4.hasKeyOfType("i", 8)) {
block = Block.getByName(nbttagcompound4.getString("i"));
} else {
block = Block.getById(nbttagcompound4.getInt("i"));
}
world.b(new BlockPosition(nbttagcompound4.getInt("x"), nbttagcompound4.getInt("y"), nbttagcompound4.getInt("z")), block, nbttagcompound4.getInt("t"), nbttagcompound4.getInt("p"));
}
}
world.timings.syncChunkLoadNBTTimer.stopTiming(); // Spigot
// return chunk; // CraftBukkit
}
@Nullable
public static Entity a(NBTTagCompound nbttagcompound, World world, Chunk chunk) {
Entity entity = a(nbttagcompound, world);
if (entity == null) {
return null;
} else {
chunk.a(entity);
if (nbttagcompound.hasKeyOfType("Passengers", 9)) {
NBTTagList nbttaglist = nbttagcompound.getList("Passengers", 10);
for (int i = 0; i < nbttaglist.size(); ++i) {
Entity entity1 = a(nbttaglist.get(i), world, chunk);
if (entity1 != null) {
entity1.a(entity, true);
}
}
}
return entity;
}
}
@Nullable
// CraftBukkit start
public static Entity a(NBTTagCompound nbttagcompound, World world, double d0, double d1, double d2, boolean flag) {
return spawnEntity(nbttagcompound, world, d0, d1, d2, flag, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
}
public static Entity spawnEntity(NBTTagCompound nbttagcompound, World world, double d0, double d1, double d2, boolean flag, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
// CraftBukkit end
Entity entity = a(nbttagcompound, world);
if (entity == null) {
return null;
} else {
entity.setPositionRotation(d0, d1, d2, entity.yaw, entity.pitch);
if (flag && !world.addEntity(entity, spawnReason)) { // CraftBukkit
return null;
} else {
if (nbttagcompound.hasKeyOfType("Passengers", 9)) {
NBTTagList nbttaglist = nbttagcompound.getList("Passengers", 10);
for (int i = 0; i < nbttaglist.size(); ++i) {
Entity entity1 = a(nbttaglist.get(i), world, d0, d1, d2, flag);
if (entity1 != null) {
entity1.a(entity, true);
}
}
}
return entity;
}
}
}
@Nullable
protected static Entity a(NBTTagCompound nbttagcompound, World world) {
try {
return EntityTypes.a(nbttagcompound, world);
} catch (RuntimeException runtimeexception) {
return null;
}
}
// CraftBukkit start
public static void a(Entity entity, World world) {
a(entity, world, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
}
public static void a(Entity entity, World world, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
if (!entity.valid && world.addEntity(entity, reason) && entity.isVehicle()) { // Paper
// CraftBukkit end
Iterator iterator = entity.bF().iterator();
while (iterator.hasNext()) {
Entity entity1 = (Entity) iterator.next();
a(entity1, world);
}
}
}
@Nullable
public static Entity a(NBTTagCompound nbttagcompound, World world, boolean flag) {
Entity entity = a(nbttagcompound, world);
if (entity == null) {
return null;
} else if (flag && !world.addEntity(entity)) {
return null;
} else {
if (nbttagcompound.hasKeyOfType("Passengers", 9)) {
NBTTagList nbttaglist = nbttagcompound.getList("Passengers", 10);
for (int i = 0; i < nbttaglist.size(); ++i) {
Entity entity1 = a(nbttaglist.get(i), world, flag);
if (entity1 != null) {
entity1.a(entity, true);
}
}
}
return entity;
}
}
// Paper start - Chunk queue improvements
private static class QueuedChunk {
public ChunkCoordIntPair coords;
public Supplier<NBTTagCompound> compoundSupplier;
public QueuedChunk(ChunkCoordIntPair coords, Supplier<NBTTagCompound> compoundSupplier) {
this.coords = coords;
this.compoundSupplier = compoundSupplier;
}
}
// Paper end
}

View File

@@ -85,7 +85,7 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper
private static final List<ItemStack> b = Collections.emptyList();
private static final AxisAlignedBB c = new AxisAlignedBB(0.0D, 0.0D, 0.0D, 0.0D, 0.0D, 0.0D);
private static double f = 1.0D;
private static int entityCount;
private static int entityCount = 1; // Paper - MC-111480 - ID 0 is treated as special for DataWatchers, start 1
private int id;
public boolean i; public boolean blocksEntitySpawning() { return i; } // Paper - OBFHELPER
public final List<Entity> passengers;
@@ -128,6 +128,7 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper
protected boolean E;
private boolean aw;
public boolean dead;
public boolean shouldBeRemoved; // Paper
public float width;
public float length;
public float I;
@@ -344,6 +345,7 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper
this.locX = d0;
this.locY = d1;
this.locZ = d2;
if (valid) world.entityJoinedWorld(this, false); // Paper - ensure Entity is moved to its proper chunk
float f = this.width / 2.0F;
float f1 = this.length;
@@ -989,6 +991,7 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper
this.locX = (axisalignedbb.a + axisalignedbb.d) / 2.0D;
this.locY = axisalignedbb.b;
this.locZ = (axisalignedbb.c + axisalignedbb.f) / 2.0D;
if (valid) world.entityJoinedWorld(this, false); // Paper - ensure Entity is moved to its proper chunk
}
protected SoundEffect ae() {
@@ -1791,7 +1794,7 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper
*/
public Chunk getCurrentChunk() {
final Chunk chunk = currentChunk != null ? currentChunk.get() : null;
return chunk != null && chunk.isLoaded() ? chunk : null;
return chunk != null && chunk.isLoaded() ? chunk : (isAddedToChunk() ? world.getChunkIfLoaded(getChunkX(), getChunkZ()) : null);
}
/**
* Returns the chunk at the location, using the entities local cache if avail
@@ -1813,21 +1816,26 @@ public abstract class Entity implements ICommandListener, KeyedObject { // Paper
public Chunk getChunkAtLocation() {
return getCurrentChunkAt((int)Math.floor(locX) >> 4, (int)Math.floor(locZ) >> 4);
}
public final MinecraftKey entityKey = EntityTypes.getKey(this);
public final String entityKeyString = entityKey != null ? entityKey.toString() : null;
private String entityKeyString = null;
private MinecraftKey entityKey = getMinecraftKey();
@Override
public MinecraftKey getMinecraftKey() {
if (entityKey == null) {
entityKey = EntityTypes.getKey(this);
entityKeyString = entityKey != null ? entityKey.toString() : null;
}
return entityKey;
}
@Override
public String getMinecraftKeyString() {
getMinecraftKey(); // Try to load if it doesn't exists. see: https://github.com/PaperMC/Paper/issues/1280
return entityKeyString;
}
@Nullable
public final String getSaveID() {
return entityKeyString;
return getMinecraftKeyString();
// Paper end
}

View File

@@ -745,9 +745,9 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
if (entity instanceof EntityPlayer) {
WorldServer worldServer = (WorldServer) entity.getWorld();
worldServer.tracker.untrackEntity(this);
worldServer.tracker.entriesLock.lock(); // Akarin
worldServer.tracker.entriesLock.writeLock().lock(); // Akarin - ProtocolSupport will overwrite track method
worldServer.tracker.track(this);
worldServer.tracker.entriesLock.unlock(); // Akarin
worldServer.tracker.entriesLock.writeLock().unlock(); // Akarin - ProtocolSupport will overwrite track method
}
// Paper end

View File

@@ -2,27 +2,30 @@ package net.minecraft.server;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.googlecode.concurentlocks.ReentrantReadWriteUpdateLock;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.concurrent.ThreadSafe;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Akarin Changes Note
* 1) Add lock for entries set operations (safety issue)
* 1) Made collections and entry access thread-safe (safety issue)
*/
@ThreadSafe // Akarin
public class EntityTracker {
private static final Logger a = LogManager.getLogger();
private final WorldServer world;
private final Set<EntityTrackerEntry> c = Sets.newHashSet();
public final ReentrantLock entriesLock = new ReentrantLock(); // Akarin - add lock
public final ReentrantReadWriteUpdateLock entriesLock = new ReentrantReadWriteUpdateLock(); // Akarin - add lock
public final IntHashMap<EntityTrackerEntry> trackedEntities = new IntHashMap();
private int e;
private volatile int e; // Akarin - volatile
public EntityTracker(WorldServer worldserver) {
this.world = worldserver;
@@ -38,7 +41,7 @@ public class EntityTracker {
this.addEntity(entity, 512, 2);
EntityPlayer entityplayer = (EntityPlayer) entity;
Iterator iterator = this.c.iterator();
entriesLock.lock(); // Akarin
// entriesLock.writeLock().lock(); // Akarin - locked in EntityPlayer
while (iterator.hasNext()) {
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
@@ -47,7 +50,7 @@ public class EntityTracker {
entitytrackerentry.updatePlayer(entityplayer);
}
}
entriesLock.unlock(); // Akarin
// entriesLock.writeLock().unlock(); // Akarin - locked in EntityPlayer
} else if (entity instanceof EntityFishingHook) {
this.addEntity(entity, 64, 5, true);
} else if (entity instanceof EntityArrow) {
@@ -118,18 +121,18 @@ public class EntityTracker {
org.spigotmc.AsyncCatcher.catchOp( "entity track"); // Spigot
i = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i); // Spigot
try {
// entriesLock.writeLock().lock(); // Akarin - locked from track method
if (this.trackedEntities.b(entity.getId())) {
throw new IllegalStateException("Entity is already tracked!");
}
EntityTrackerEntry entitytrackerentry = new EntityTrackerEntry(entity, i, this.e, j, flag);
entriesLock.lock(); // Akarin
this.c.add(entitytrackerentry);
this.trackedEntities.a(entity.getId(), entitytrackerentry);
entitytrackerentry.scanPlayers(this.world.players);
entriesLock.unlock(); // Akarin
// entriesLock.writeLock().unlock(); // Akarin - locked from track method
} catch (Throwable throwable) {
CrashReport crashreport = CrashReport.a(throwable, "Adding entity to track");
CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Entity To Track");
@@ -166,34 +169,32 @@ public class EntityTracker {
public void untrackEntity(Entity entity) {
org.spigotmc.AsyncCatcher.catchOp( "entity untrack"); // Spigot
entriesLock.writeLock().lock(); // Akarin
if (entity instanceof EntityPlayer) {
EntityPlayer entityplayer = (EntityPlayer) entity;
Iterator iterator = this.c.iterator();
entriesLock.lock(); // Akarin
while (iterator.hasNext()) {
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
entitytrackerentry.a(entityplayer);
}
entriesLock.unlock(); // Akarin
}
EntityTrackerEntry entitytrackerentry1 = this.trackedEntities.d(entity.getId());
if (entitytrackerentry1 != null) {
entriesLock.lock(); // Akarin
this.c.remove(entitytrackerentry1);
entitytrackerentry1.a();
entriesLock.unlock(); // Akarin
}
entriesLock.writeLock().unlock(); // Akarin
}
public void updatePlayers() {
ArrayList arraylist = Lists.newArrayList();
Iterator iterator = this.c.iterator();
world.timings.tracker1.startTiming(); // Spigot
entriesLock.lock(); // Akarin
entriesLock.writeLock().lock(); // Akarin
while (iterator.hasNext()) {
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
@@ -221,14 +222,14 @@ public class EntityTracker {
}
}
}
entriesLock.unlock(); // Akarin
entriesLock.writeLock().unlock(); // Akarin
world.timings.tracker2.stopTiming(); // Spigot
}
public void a(EntityPlayer entityplayer) {
Iterator iterator = this.c.iterator();
entriesLock.lock(); // Akarin
entriesLock.writeLock().lock(); // Akarin
while (iterator.hasNext()) {
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
@@ -239,11 +240,13 @@ public class EntityTracker {
entitytrackerentry.updatePlayer(entityplayer);
}
}
entriesLock.unlock(); // Akarin
entriesLock.writeLock().unlock(); // Akarin
}
public void a(Entity entity, Packet<?> packet) {
entriesLock.readLock().lock(); // Akarin
EntityTrackerEntry entitytrackerentry = this.trackedEntities.get(entity.getId());
entriesLock.readLock().unlock(); // Akarin
if (entitytrackerentry != null) {
entitytrackerentry.broadcast(packet);
@@ -252,7 +255,9 @@ public class EntityTracker {
}
public void sendPacketToEntity(Entity entity, Packet<?> packet) {
entriesLock.readLock().lock(); // Akarin
EntityTrackerEntry entitytrackerentry = this.trackedEntities.get(entity.getId());
entriesLock.readLock().unlock(); // Akarin
if (entitytrackerentry != null) {
entitytrackerentry.broadcastIncludingSelf(packet);
@@ -262,21 +267,21 @@ public class EntityTracker {
public void untrackPlayer(EntityPlayer entityplayer) {
Iterator iterator = this.c.iterator();
entriesLock.lock(); // Akarin
entriesLock.writeLock().lock();
while (iterator.hasNext()) {
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
entitytrackerentry.clear(entityplayer);
}
entriesLock.unlock(); // Akarin
entriesLock.writeLock().unlock();
}
public void a(EntityPlayer entityplayer, Chunk chunk) {
ArrayList arraylist = Lists.newArrayList();
ArrayList arraylist1 = Lists.newArrayList();
Iterator iterator = this.c.iterator();
entriesLock.lock(); // Akarin
entriesLock.writeLock().lock(); // Akarin
while (iterator.hasNext()) {
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
@@ -293,7 +298,7 @@ public class EntityTracker {
}
}
}
entriesLock.unlock(); // Akarin
entriesLock.writeLock().unlock(); // Akarin
Entity entity1;
@@ -320,13 +325,13 @@ public class EntityTracker {
public void a(int i) {
this.e = (i - 1) * 16;
Iterator iterator = this.c.iterator();
entriesLock.lock(); // Akarin
entriesLock.readLock().lock(); // Akarin
while (iterator.hasNext()) {
EntityTrackerEntry entitytrackerentry = (EntityTrackerEntry) iterator.next();
entitytrackerentry.a(this.e);
}
entriesLock.unlock(); // Akarin
entriesLock.readLock().unlock(); // Akarin
}
}

View File

@@ -1,658 +0,0 @@
package net.minecraft.server;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
// CraftBukkit start
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerVelocityEvent;
// CraftBukkit end
/**
* Akarin Changes Note
* 1) Make trackedPlayerMap thread-safe (safety issue)
*/
public class EntityTrackerEntry {
private static final Logger c = LogManager.getLogger();
private final Entity tracker;
private final int e;
private int f;
private final int g;
private long xLoc;
private long yLoc;
private long zLoc;
private int yRot;
private int xRot;
private int headYaw;
private double n;
private double o;
private double p;
public int a;
private double q;
private double r;
private double s;
private boolean isMoving;
private final boolean u;
private int v;
private List<Entity> w = Collections.emptyList();
private boolean x;
private boolean y;
public boolean b;
// Paper start
// Replace trackedPlayers Set with a Map. The value is true until the player receives
// their first update (which is forced to have absolute coordinates), false afterward.
public java.util.Map<EntityPlayer, Boolean> trackedPlayerMap = new java.util.concurrent.ConcurrentHashMap<EntityPlayer, Boolean>(); // Akarin - make concurrent
public Set<EntityPlayer> trackedPlayers = trackedPlayerMap.keySet();
// Paper end
public EntityTrackerEntry(Entity entity, int i, int j, int k, boolean flag) {
entity.tracker = this; // Paper
this.tracker = entity;
this.e = i;
this.f = j;
this.g = k;
this.u = flag;
this.xLoc = EntityTracker.a(entity.locX);
this.yLoc = EntityTracker.a(entity.locY);
this.zLoc = EntityTracker.a(entity.locZ);
this.yRot = MathHelper.d(entity.yaw * 256.0F / 360.0F);
this.xRot = MathHelper.d(entity.pitch * 256.0F / 360.0F);
this.headYaw = MathHelper.d(entity.getHeadRotation() * 256.0F / 360.0F);
this.y = entity.onGround;
}
public boolean equals(Object object) {
return object instanceof EntityTrackerEntry ? ((EntityTrackerEntry) object).tracker.getId() == this.tracker.getId() : false;
}
public int hashCode() {
return this.tracker.getId();
}
public void track(List<EntityHuman> list) {
this.b = false;
if (!this.isMoving || this.tracker.d(this.q, this.r, this.s) > 16.0D) {
this.q = this.tracker.locX;
this.r = this.tracker.locY;
this.s = this.tracker.locZ;
this.isMoving = true;
this.b = true;
this.scanPlayers(list);
}
List list1 = this.tracker.bF();
if (!list1.equals(this.w)) {
this.w = list1;
this.broadcastIncludingSelf(new PacketPlayOutMount(this.tracker)); // CraftBukkit
}
// PAIL : rename
if (this.tracker instanceof EntityItemFrame && this.a % 20 == 0) { // Paper
EntityItemFrame entityitemframe = (EntityItemFrame) this.tracker;
ItemStack itemstack = entityitemframe.getItem();
if (itemstack != null && itemstack.getItem() instanceof ItemWorldMap) { // Paper - moved back up
WorldMap worldmap = Items.FILLED_MAP.getSavedMap(itemstack, this.tracker.world);
Iterator iterator = this.trackedPlayers.iterator(); // CraftBukkit
while (iterator.hasNext()) {
EntityHuman entityhuman = (EntityHuman) iterator.next();
EntityPlayer entityplayer = (EntityPlayer) entityhuman;
worldmap.a(entityplayer, itemstack);
Packet packet = Items.FILLED_MAP.a(itemstack, this.tracker.world, (EntityHuman) entityplayer);
if (packet != null) {
entityplayer.playerConnection.sendPacket(packet);
}
}
}
this.d();
}
if (this.a % this.g == 0 || this.tracker.impulse || this.tracker.getDataWatcher().a()) {
int i;
if (this.tracker.isPassenger()) {
i = MathHelper.d(this.tracker.yaw * 256.0F / 360.0F);
int j = MathHelper.d(this.tracker.pitch * 256.0F / 360.0F);
boolean flag = Math.abs(i - this.yRot) >= 1 || Math.abs(j - this.xRot) >= 1;
if (flag) {
this.broadcast(new PacketPlayOutEntity.PacketPlayOutEntityLook(this.tracker.getId(), (byte) i, (byte) j, this.tracker.onGround));
this.yRot = i;
this.xRot = j;
}
this.xLoc = EntityTracker.a(this.tracker.locX);
this.yLoc = EntityTracker.a(this.tracker.locY);
this.zLoc = EntityTracker.a(this.tracker.locZ);
this.d();
this.x = true;
} else {
++this.v;
long k = EntityTracker.a(this.tracker.locX);
long l = EntityTracker.a(this.tracker.locY);
long i1 = EntityTracker.a(this.tracker.locZ);
int j1 = MathHelper.d(this.tracker.yaw * 256.0F / 360.0F);
int k1 = MathHelper.d(this.tracker.pitch * 256.0F / 360.0F);
long l1 = k - this.xLoc;
long i2 = l - this.yLoc;
long j2 = i1 - this.zLoc;
Object object = null;
boolean flag1 = l1 * l1 + i2 * i2 + j2 * j2 >= 128L || this.a % 60 == 0;
boolean flag2 = Math.abs(j1 - this.yRot) >= 1 || Math.abs(k1 - this.xRot) >= 1;
if (this.a > 0 || this.tracker instanceof EntityArrow) { // Paper - Moved up
// CraftBukkit start - Code moved from below
if (flag1) {
this.xLoc = k;
this.yLoc = l;
this.zLoc = i1;
}
if (flag2) {
this.yRot = j1;
this.xRot = k1;
}
// CraftBukkit end
if (l1 >= -32768L && l1 < 32768L && i2 >= -32768L && i2 < 32768L && j2 >= -32768L && j2 < 32768L && this.v <= 400 && !this.x && this.y == this.tracker.onGround) {
if ((!flag1 || !flag2) && !(this.tracker instanceof EntityArrow)) {
if (flag1) {
object = new PacketPlayOutEntity.PacketPlayOutRelEntityMove(this.tracker.getId(), l1, i2, j2, this.tracker.onGround);
} else if (flag2) {
object = new PacketPlayOutEntity.PacketPlayOutEntityLook(this.tracker.getId(), (byte) j1, (byte) k1, this.tracker.onGround);
}
} else {
object = new PacketPlayOutEntity.PacketPlayOutRelEntityMoveLook(this.tracker.getId(), l1, i2, j2, (byte) j1, (byte) k1, this.tracker.onGround);
}
} else {
this.y = this.tracker.onGround;
this.v = 0;
// CraftBukkit start - Refresh list of who can see a player before sending teleport packet
if (this.tracker instanceof EntityPlayer) {
this.scanPlayers(new java.util.ArrayList(this.trackedPlayers));
}
// CraftBukkit end
this.c();
object = new PacketPlayOutEntityTeleport(this.tracker);
}
}
boolean flag3 = this.u;
if (this.tracker instanceof EntityLiving && ((EntityLiving) this.tracker).cP()) {
flag3 = true;
}
if (flag3 && this.a > 0) {
double d0 = this.tracker.motX - this.n;
double d1 = this.tracker.motY - this.o;
double d2 = this.tracker.motZ - this.p;
double d3 = 0.02D;
double d4 = d0 * d0 + d1 * d1 + d2 * d2;
if (d4 > 4.0E-4D || d4 > 0.0D && this.tracker.motX == 0.0D && this.tracker.motY == 0.0D && this.tracker.motZ == 0.0D) {
this.n = this.tracker.motX;
this.o = this.tracker.motY;
this.p = this.tracker.motZ;
this.broadcast(new PacketPlayOutEntityVelocity(this.tracker.getId(), this.n, this.o, this.p));
}
}
if (object != null) {
// Paper start - ensure fresh viewers get an absolute position on their first update,
// since we can't be certain what position they received in the spawn packet.
if (object instanceof PacketPlayOutEntityTeleport) {
this.broadcast((Packet) object);
} else {
PacketPlayOutEntityTeleport teleportPacket = null;
for (java.util.Map.Entry<EntityPlayer, Boolean> viewer : trackedPlayerMap.entrySet()) {
if (viewer.getValue()) {
viewer.setValue(false);
if (teleportPacket == null) {
teleportPacket = new PacketPlayOutEntityTeleport(this.tracker);
}
viewer.getKey().playerConnection.sendPacket(teleportPacket);
} else {
viewer.getKey().playerConnection.sendPacket((Packet) object);
}
}
}
// Paper end
}
this.d();
/* CraftBukkit start - Code moved up
if (flag1) {
this.xLoc = k;
this.yLoc = l;
this.zLoc = i1;
}
if (flag2) {
this.yRot = j1;
this.xRot = k1;
}
// CraftBukkit end */
this.x = false;
}
i = MathHelper.d(this.tracker.getHeadRotation() * 256.0F / 360.0F);
if (Math.abs(i - this.headYaw) >= 1) {
this.broadcast(new PacketPlayOutEntityHeadRotation(this.tracker, (byte) i));
this.headYaw = i;
}
this.tracker.impulse = false;
}
++this.a;
if (this.tracker.velocityChanged) {
// CraftBukkit start - Create PlayerVelocity event
boolean cancelled = false;
if (this.tracker instanceof EntityPlayer) {
Player player = (Player) this.tracker.getBukkitEntity();
org.bukkit.util.Vector velocity = player.getVelocity();
PlayerVelocityEvent event = new PlayerVelocityEvent(player, velocity.clone());
this.tracker.world.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
cancelled = true;
} else if (!velocity.equals(event.getVelocity())) {
player.setVelocity(event.getVelocity());
}
}
if (!cancelled) {
this.broadcastIncludingSelf(new PacketPlayOutEntityVelocity(this.tracker));
}
// CraftBukkit end
this.tracker.velocityChanged = false;
}
}
private void d() {
DataWatcher datawatcher = this.tracker.getDataWatcher();
if (datawatcher.a()) {
this.broadcastIncludingSelf(new PacketPlayOutEntityMetadata(this.tracker.getId(), datawatcher, false));
}
if (this.tracker instanceof EntityLiving) {
AttributeMapServer attributemapserver = (AttributeMapServer) ((EntityLiving) this.tracker).getAttributeMap();
Set set = attributemapserver.getAttributes();
if (!set.isEmpty()) {
// CraftBukkit start - Send scaled max health
if (this.tracker instanceof EntityPlayer) {
((EntityPlayer) this.tracker).getBukkitEntity().injectScaledMaxHealth(set, false);
}
// CraftBukkit end
this.broadcastIncludingSelf(new PacketPlayOutUpdateAttributes(this.tracker.getId(), set));
}
set.clear();
}
}
public void broadcast(Packet<?> packet) {
Iterator iterator = this.trackedPlayers.iterator();
while (iterator.hasNext()) {
EntityPlayer entityplayer = (EntityPlayer) iterator.next();
entityplayer.playerConnection.sendPacket(packet);
}
}
public void broadcastIncludingSelf(Packet<?> packet) {
this.broadcast(packet);
if (this.tracker instanceof EntityPlayer) {
((EntityPlayer) this.tracker).playerConnection.sendPacket(packet);
}
}
public void a() {
Iterator iterator = this.trackedPlayers.iterator();
while (iterator.hasNext()) {
EntityPlayer entityplayer = (EntityPlayer) iterator.next();
this.tracker.c(entityplayer);
entityplayer.c(this.tracker);
}
}
public void a(EntityPlayer entityplayer) {
if (this.trackedPlayers.contains(entityplayer)) {
this.tracker.c(entityplayer);
entityplayer.c(this.tracker);
this.trackedPlayers.remove(entityplayer);
}
}
public void updatePlayer(EntityPlayer entityplayer) {
org.spigotmc.AsyncCatcher.catchOp( "player tracker update"); // Spigot
if (entityplayer != this.tracker) {
if (this.c(entityplayer)) {
if (!this.trackedPlayers.contains(entityplayer) && (this.e(entityplayer) || this.tracker.attachedToPlayer)) {
// CraftBukkit start - respect vanish API
if (this.tracker instanceof EntityPlayer) {
Player player = ((EntityPlayer) this.tracker).getBukkitEntity();
if (!entityplayer.getBukkitEntity().canSee(player)) {
return;
}
}
entityplayer.removeQueue.remove(Integer.valueOf(this.tracker.getId()));
// CraftBukkit end
this.trackedPlayerMap.put(entityplayer, true); // Paper
Packet packet = this.e();
entityplayer.playerConnection.sendPacket(packet);
if (!this.tracker.getDataWatcher().d()) {
entityplayer.playerConnection.sendPacket(new PacketPlayOutEntityMetadata(this.tracker.getId(), this.tracker.getDataWatcher(), true));
}
boolean flag = this.u;
if (this.tracker instanceof EntityLiving) {
AttributeMapServer attributemapserver = (AttributeMapServer) ((EntityLiving) this.tracker).getAttributeMap();
Collection collection = attributemapserver.c();
// CraftBukkit start - If sending own attributes send scaled health instead of current maximum health
if (this.tracker.getId() == entityplayer.getId()) {
((EntityPlayer) this.tracker).getBukkitEntity().injectScaledMaxHealth(collection, false);
}
// CraftBukkit end
if (!collection.isEmpty()) {
entityplayer.playerConnection.sendPacket(new PacketPlayOutUpdateAttributes(this.tracker.getId(), collection));
}
if (((EntityLiving) this.tracker).cP()) {
flag = true;
}
}
this.n = this.tracker.motX;
this.o = this.tracker.motY;
this.p = this.tracker.motZ;
if (flag && !(packet instanceof PacketPlayOutSpawnEntityLiving)) {
entityplayer.playerConnection.sendPacket(new PacketPlayOutEntityVelocity(this.tracker.getId(), this.tracker.motX, this.tracker.motY, this.tracker.motZ));
}
if (this.tracker instanceof EntityLiving) {
EnumItemSlot[] aenumitemslot = EnumItemSlot.values();
int i = aenumitemslot.length;
for (int j = 0; j < i; ++j) {
EnumItemSlot enumitemslot = aenumitemslot[j];
ItemStack itemstack = ((EntityLiving) this.tracker).getEquipment(enumitemslot);
if (!itemstack.isEmpty()) {
entityplayer.playerConnection.sendPacket(new PacketPlayOutEntityEquipment(this.tracker.getId(), enumitemslot, itemstack));
}
}
}
if (this.tracker instanceof EntityHuman) {
EntityHuman entityhuman = (EntityHuman) this.tracker;
if (entityhuman.isSleeping()) {
entityplayer.playerConnection.sendPacket(new PacketPlayOutBed(entityhuman, new BlockPosition(this.tracker)));
}
}
// CraftBukkit start - Fix for nonsensical head yaw
this.headYaw = MathHelper.d(this.tracker.getHeadRotation() * 256.0F / 360.0F);
this.broadcast(new PacketPlayOutEntityHeadRotation(this.tracker, (byte) headYaw));
// CraftBukkit end
if (this.tracker instanceof EntityLiving) {
EntityLiving entityliving = (EntityLiving) this.tracker;
Iterator iterator = entityliving.getEffects().iterator();
while (iterator.hasNext()) {
MobEffect mobeffect = (MobEffect) iterator.next();
entityplayer.playerConnection.sendPacket(new PacketPlayOutEntityEffect(this.tracker.getId(), mobeffect));
}
}
if (!this.tracker.bF().isEmpty()) {
entityplayer.playerConnection.sendPacket(new PacketPlayOutMount(this.tracker));
}
if (this.tracker.isPassenger()) {
entityplayer.playerConnection.sendPacket(new PacketPlayOutMount(this.tracker.bJ()));
}
this.tracker.b(entityplayer);
entityplayer.d(this.tracker);
updatePassengers(entityplayer); // Paper
}
} else if (this.trackedPlayers.contains(entityplayer)) {
this.trackedPlayers.remove(entityplayer);
this.tracker.c(entityplayer);
entityplayer.c(this.tracker);
updatePassengers(entityplayer); // Paper
}
}
}
public boolean c(EntityPlayer entityplayer) {
// Paper start
if (tracker.isPassenger()) {
return isTrackedBy(tracker.getVehicle(), entityplayer);
} else if (hasPassengerInRange(tracker, entityplayer)) {
return true;
}
return isInRangeOfPlayer(entityplayer);
}
private static boolean hasPassengerInRange(Entity entity, EntityPlayer entityplayer) {
if (!entity.isVehicle()) {
return false;
}
for (Entity passenger : entity.passengers) {
if (passenger.tracker != null && passenger.tracker.isInRangeOfPlayer(entityplayer)) {
return true;
}
if (passenger.isVehicle()) {
if (hasPassengerInRange(passenger, entityplayer)) {
return true;
}
}
}
return false;
}
private static boolean isTrackedBy(Entity entity, EntityPlayer entityplayer) {
return entity == entityplayer || entity.tracker != null && entity.tracker.trackedPlayers.contains(entityplayer);
}
private void updatePassengers(EntityPlayer player) {
if (tracker.isVehicle()) {
tracker.passengers.forEach((e) -> {
if (e.tracker != null) {
e.tracker.updatePlayer(player);
}
});
player.playerConnection.sendPacket(new PacketPlayOutMount(this.tracker));
}
}
private boolean isInRangeOfPlayer(EntityPlayer entityplayer) {
// Paper end
double d0 = entityplayer.locX - (double) this.xLoc / 4096.0D;
double d1 = entityplayer.locZ - (double) this.zLoc / 4096.0D;
int i = Math.min(this.e, this.f);
return d0 >= (double) (-i) && d0 <= (double) i && d1 >= (double) (-i) && d1 <= (double) i && this.tracker.a(entityplayer);
}
private boolean e(EntityPlayer entityplayer) {
return entityplayer.x().getPlayerChunkMap().a(entityplayer, this.tracker.ab, this.tracker.ad);
}
public void scanPlayers(List<EntityHuman> list) {
for (int i = 0; i < list.size(); ++i) {
this.updatePlayer((EntityPlayer) list.get(i));
}
}
private Packet<?> e() {
if (this.tracker.dead) {
// CraftBukkit start - Remove useless error spam, just return
// EntityTrackerEntry.d.warn("Fetching addPacket for removed entity");
return null;
// CraftBukkit end
}
if (this.tracker instanceof EntityPlayer) {
return new PacketPlayOutNamedEntitySpawn((EntityHuman) this.tracker);
} else if (this.tracker instanceof IAnimal) {
this.headYaw = MathHelper.d(this.tracker.getHeadRotation() * 256.0F / 360.0F);
return new PacketPlayOutSpawnEntityLiving((EntityLiving) this.tracker);
} else if (this.tracker instanceof EntityPainting) {
return new PacketPlayOutSpawnEntityPainting((EntityPainting) this.tracker);
} else if (this.tracker instanceof EntityItem) {
return new PacketPlayOutSpawnEntity(this.tracker, 2, 1);
} else if (this.tracker instanceof EntityMinecartAbstract) {
EntityMinecartAbstract entityminecartabstract = (EntityMinecartAbstract) this.tracker;
return new PacketPlayOutSpawnEntity(this.tracker, 10, entityminecartabstract.v().a());
} else if (this.tracker instanceof EntityBoat) {
return new PacketPlayOutSpawnEntity(this.tracker, 1);
} else if (this.tracker instanceof EntityExperienceOrb) {
return new PacketPlayOutSpawnEntityExperienceOrb((EntityExperienceOrb) this.tracker);
} else if (this.tracker instanceof EntityFishingHook) {
EntityHuman entityhuman = ((EntityFishingHook) this.tracker).l();
return new PacketPlayOutSpawnEntity(this.tracker, 90, entityhuman == null ? this.tracker.getId() : entityhuman.getId());
} else {
Entity entity;
if (this.tracker instanceof EntitySpectralArrow) {
entity = ((EntitySpectralArrow) this.tracker).shooter;
return new PacketPlayOutSpawnEntity(this.tracker, 91, 1 + (entity == null ? this.tracker.getId() : entity.getId()));
} else if (this.tracker instanceof EntityTippedArrow) {
entity = ((EntityArrow) this.tracker).shooter;
return new PacketPlayOutSpawnEntity(this.tracker, 60, 1 + (entity == null ? this.tracker.getId() : entity.getId()));
} else if (this.tracker instanceof EntitySnowball) {
return new PacketPlayOutSpawnEntity(this.tracker, 61);
} else if (this.tracker instanceof EntityLlamaSpit) {
return new PacketPlayOutSpawnEntity(this.tracker, 68);
} else if (this.tracker instanceof EntityPotion) {
return new PacketPlayOutSpawnEntity(this.tracker, 73);
} else if (this.tracker instanceof EntityThrownExpBottle) {
return new PacketPlayOutSpawnEntity(this.tracker, 75);
} else if (this.tracker instanceof EntityEnderPearl) {
return new PacketPlayOutSpawnEntity(this.tracker, 65);
} else if (this.tracker instanceof EntityEnderSignal) {
return new PacketPlayOutSpawnEntity(this.tracker, 72);
} else if (this.tracker instanceof EntityFireworks) {
return new PacketPlayOutSpawnEntity(this.tracker, 76);
} else if (this.tracker instanceof EntityFireball) {
EntityFireball entityfireball = (EntityFireball) this.tracker;
PacketPlayOutSpawnEntity packetplayoutspawnentity = null;
byte b0 = 63;
if (this.tracker instanceof EntitySmallFireball) {
b0 = 64;
} else if (this.tracker instanceof EntityDragonFireball) {
b0 = 93;
} else if (this.tracker instanceof EntityWitherSkull) {
b0 = 66;
}
if (entityfireball.shooter != null) {
packetplayoutspawnentity = new PacketPlayOutSpawnEntity(this.tracker, b0, ((EntityFireball) this.tracker).shooter.getId());
} else {
packetplayoutspawnentity = new PacketPlayOutSpawnEntity(this.tracker, b0, 0);
}
packetplayoutspawnentity.a((int) (entityfireball.dirX * 8000.0D));
packetplayoutspawnentity.b((int) (entityfireball.dirY * 8000.0D));
packetplayoutspawnentity.c((int) (entityfireball.dirZ * 8000.0D));
return packetplayoutspawnentity;
} else if (this.tracker instanceof EntityShulkerBullet) {
PacketPlayOutSpawnEntity packetplayoutspawnentity1 = new PacketPlayOutSpawnEntity(this.tracker, 67, 0);
packetplayoutspawnentity1.a((int) (this.tracker.motX * 8000.0D));
packetplayoutspawnentity1.b((int) (this.tracker.motY * 8000.0D));
packetplayoutspawnentity1.c((int) (this.tracker.motZ * 8000.0D));
return packetplayoutspawnentity1;
} else if (this.tracker instanceof EntityEgg) {
return new PacketPlayOutSpawnEntity(this.tracker, 62);
} else if (this.tracker instanceof EntityEvokerFangs) {
return new PacketPlayOutSpawnEntity(this.tracker, 79);
} else if (this.tracker instanceof EntityTNTPrimed) {
return new PacketPlayOutSpawnEntity(this.tracker, 50);
} else if (this.tracker instanceof EntityEnderCrystal) {
return new PacketPlayOutSpawnEntity(this.tracker, 51);
} else if (this.tracker instanceof EntityFallingBlock) {
EntityFallingBlock entityfallingblock = (EntityFallingBlock) this.tracker;
return new PacketPlayOutSpawnEntity(this.tracker, 70, Block.getCombinedId(entityfallingblock.getBlock()));
} else if (this.tracker instanceof EntityArmorStand) {
return new PacketPlayOutSpawnEntity(this.tracker, 78);
} else if (this.tracker instanceof EntityItemFrame) {
EntityItemFrame entityitemframe = (EntityItemFrame) this.tracker;
return new PacketPlayOutSpawnEntity(this.tracker, 71, entityitemframe.direction.get2DRotationValue(), entityitemframe.getBlockPosition());
} else if (this.tracker instanceof EntityLeash) {
EntityLeash entityleash = (EntityLeash) this.tracker;
return new PacketPlayOutSpawnEntity(this.tracker, 77, 0, entityleash.getBlockPosition());
} else if (this.tracker instanceof EntityAreaEffectCloud) {
return new PacketPlayOutSpawnEntity(this.tracker, 3);
} else {
throw new IllegalArgumentException("Don\'t know how to add " + this.tracker.getClass() + "!");
}
}
}
public void clear(EntityPlayer entityplayer) {
org.spigotmc.AsyncCatcher.catchOp( "player tracker clear"); // Spigot
if (this.trackedPlayers.contains(entityplayer)) {
this.trackedPlayers.remove(entityplayer);
this.tracker.c(entityplayer);
entityplayer.c(this.tracker);
updatePassengers(entityplayer); // Paper
}
}
public Entity b() {
return this.tracker;
}
public void a(int i) {
this.f = i;
}
public void c() {
this.isMoving = false;
}
}

View File

@@ -0,0 +1,81 @@
package net.minecraft.server;
import java.util.List;
/**
* Akarin Changes Note
* 1) Multi-threaded chunk saving (performance)
*/
public class FileIOThread implements Runnable {
private static final FileIOThread a = new FileIOThread();
private final List<IAsyncChunkSaver> b = /*Collections.synchronizedList(Lists.newArrayList())*/ null; // Akarin - I don't think any plugin rely on this
private volatile long c;
private volatile long d;
private volatile boolean e;
private FileIOThread() {
// Thread thread = new Thread(this, "File IO Thread"); // Akarin
// thread.setPriority(1); // Akarin
// thread.start(); // Akarin
}
public static FileIOThread a() {
return FileIOThread.a;
}
public void run() {
while (true) {
this.c();
}
}
private void c() {
for (int i = 0; i < this.b.size(); ++i) {
IAsyncChunkSaver iasyncchunksaver = (IAsyncChunkSaver) this.b.get(i);
boolean flag = iasyncchunksaver.a();
if (!flag) {
this.b.remove(i--);
++this.d;
}
// Paper start - Add toggle
if (com.destroystokyo.paper.PaperConfig.enableFileIOThreadSleep) {
try {
Thread.sleep(this.e ? 0L : 2L);
} catch (InterruptedException interruptedexception) {
interruptedexception.printStackTrace();
}
}
// Paper end
}
if (this.b.isEmpty()) {
try {
Thread.sleep(25L);
} catch (InterruptedException interruptedexception1) {
interruptedexception1.printStackTrace();
}
}
}
public void a(IAsyncChunkSaver iasyncchunksaver) {
if (!this.b.contains(iasyncchunksaver)) {
++this.c;
this.b.add(iasyncchunksaver);
}
}
public void b() throws InterruptedException {
this.e = true;
while (this.c != this.d) {
Thread.sleep(10L);
}
this.e = false;
}
}

View File

@@ -0,0 +1,243 @@
package net.minecraft.server;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
import io.akarin.server.core.AkarinGlobalConfig;
/**
* Akarin Changes Note
* 1) Restricted spawner modify (feature)
*/
public class ItemMonsterEgg extends Item {
public ItemMonsterEgg() {
this.b(CreativeModeTab.f);
}
public String b(ItemStack itemstack) {
String s = ("" + LocaleI18n.get(this.getName() + ".name")).trim();
String s1 = EntityTypes.a(h(itemstack));
if (s1 != null) {
s = s + " " + LocaleI18n.get("entity." + s1 + ".name");
}
return s;
}
public EnumInteractionResult a(EntityHuman entityhuman, World world, BlockPosition blockposition, EnumHand enumhand, EnumDirection enumdirection, float f, float f1, float f2) {
ItemStack itemstack = entityhuman.b(enumhand);
if (world.isClientSide) {
return EnumInteractionResult.SUCCESS;
} else if (!entityhuman.a(blockposition.shift(enumdirection), enumdirection, itemstack)) {
return EnumInteractionResult.FAIL;
} else {
IBlockData iblockdata = world.getType(blockposition);
Block block = iblockdata.getBlock();
if (block == Blocks.MOB_SPAWNER && (AkarinGlobalConfig.allowSpawnerModify || entityhuman.isCreativeAndOp())) { // Akarin
TileEntity tileentity = world.getTileEntity(blockposition);
if (tileentity instanceof TileEntityMobSpawner) {
MobSpawnerAbstract mobspawnerabstract = ((TileEntityMobSpawner) tileentity).getSpawner();
mobspawnerabstract.setMobName(h(itemstack));
tileentity.update();
world.notify(blockposition, iblockdata, iblockdata, 3);
if (!entityhuman.abilities.canInstantlyBuild) {
itemstack.subtract(1);
}
return EnumInteractionResult.SUCCESS;
}
}
BlockPosition blockposition1 = blockposition.shift(enumdirection);
double d0 = this.a(world, blockposition1);
Entity entity = a(world, h(itemstack), (double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + d0, (double) blockposition1.getZ() + 0.5D);
if (entity != null) {
if (entity instanceof EntityLiving && itemstack.hasName()) {
entity.setCustomName(itemstack.getName());
}
a(world, entityhuman, itemstack, entity);
if (!entityhuman.abilities.canInstantlyBuild) {
itemstack.subtract(1);
}
}
return EnumInteractionResult.SUCCESS;
}
}
protected double a(World world, BlockPosition blockposition) {
AxisAlignedBB axisalignedbb = (new AxisAlignedBB(blockposition)).b(0.0D, -1.0D, 0.0D);
List list = world.getCubes((Entity) null, axisalignedbb);
if (list.isEmpty()) {
return 0.0D;
} else {
double d0 = axisalignedbb.b;
AxisAlignedBB axisalignedbb1;
for (Iterator iterator = list.iterator(); iterator.hasNext(); d0 = Math.max(axisalignedbb1.e, d0)) {
axisalignedbb1 = (AxisAlignedBB) iterator.next();
}
return d0 - (double) blockposition.getY();
}
}
public static void a(World world, @Nullable EntityHuman entityhuman, ItemStack itemstack, @Nullable Entity entity) {
MinecraftServer minecraftserver = world.getMinecraftServer();
if (minecraftserver != null && entity != null) {
NBTTagCompound nbttagcompound = itemstack.getTag();
if (nbttagcompound != null && nbttagcompound.hasKeyOfType("EntityTag", 10)) {
if (!world.isClientSide && entity.bC() && (entityhuman == null || !minecraftserver.getPlayerList().isOp(entityhuman.getProfile()))) {
return;
}
NBTTagCompound nbttagcompound1 = entity.save(new NBTTagCompound());
UUID uuid = entity.getUniqueID();
nbttagcompound1.a(nbttagcompound.getCompound("EntityTag"));
entity.a(uuid);
entity.f(nbttagcompound1);
}
}
}
public InteractionResultWrapper<ItemStack> a(World world, EntityHuman entityhuman, EnumHand enumhand) {
ItemStack itemstack = entityhuman.b(enumhand);
if (world.isClientSide) {
return new InteractionResultWrapper(EnumInteractionResult.PASS, itemstack);
} else {
MovingObjectPosition movingobjectposition = this.a(world, entityhuman, true);
if (movingobjectposition != null && movingobjectposition.type == MovingObjectPosition.EnumMovingObjectType.BLOCK) {
BlockPosition blockposition = movingobjectposition.a();
if (!(world.getType(blockposition).getBlock() instanceof BlockFluids)) {
return new InteractionResultWrapper(EnumInteractionResult.PASS, itemstack);
} else if (world.a(entityhuman, blockposition) && entityhuman.a(blockposition, movingobjectposition.direction, itemstack)) {
Entity entity = a(world, h(itemstack), (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D);
if (entity == null) {
return new InteractionResultWrapper(EnumInteractionResult.PASS, itemstack);
} else {
if (entity instanceof EntityLiving && itemstack.hasName()) {
entity.setCustomName(itemstack.getName());
}
a(world, entityhuman, itemstack, entity);
if (!entityhuman.abilities.canInstantlyBuild) {
itemstack.subtract(1);
}
entityhuman.b(StatisticList.b((Item) this));
return new InteractionResultWrapper(EnumInteractionResult.SUCCESS, itemstack);
}
} else {
return new InteractionResultWrapper(EnumInteractionResult.FAIL, itemstack);
}
} else {
return new InteractionResultWrapper(EnumInteractionResult.PASS, itemstack);
}
}
}
@Nullable
public static Entity a(World world, @Nullable MinecraftKey minecraftkey, double d0, double d1, double d2) {
return spawnCreature(world, minecraftkey, d0, d1, d2, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG);
}
@Nullable
public static Entity spawnCreature(World world, @Nullable MinecraftKey minecraftkey, double d0, double d1, double d2, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
if (minecraftkey != null && EntityTypes.eggInfo.containsKey(minecraftkey)) {
Entity entity = null;
for (int i = 0; i < 1; ++i) {
entity = EntityTypes.a(minecraftkey, world);
if (entity instanceof EntityInsentient) {
EntityInsentient entityinsentient = (EntityInsentient) entity;
entity.setPositionRotation(d0, d1, d2, MathHelper.g(world.random.nextFloat() * 360.0F), 0.0F);
entityinsentient.aP = entityinsentient.yaw;
entityinsentient.aN = entityinsentient.yaw;
entityinsentient.prepare(world.D(new BlockPosition(entityinsentient)), (GroupDataEntity) null);
// CraftBukkit start - don't return an entity when CreatureSpawnEvent is canceled
if (!world.addEntity(entity, spawnReason)) {
entity = null;
} else {
entityinsentient.D();
}
// CraftBukkit end
}
}
return entity;
} else {
return null;
}
}
public void a(CreativeModeTab creativemodetab, NonNullList<ItemStack> nonnulllist) {
if (this.a(creativemodetab)) {
Iterator iterator = EntityTypes.eggInfo.values().iterator();
while (iterator.hasNext()) {
EntityTypes.MonsterEggInfo entitytypes_monsteregginfo = (EntityTypes.MonsterEggInfo) iterator.next();
ItemStack itemstack = new ItemStack(this, 1);
a(itemstack, entitytypes_monsteregginfo.a);
nonnulllist.add(itemstack);
}
}
}
public static void a(ItemStack itemstack, MinecraftKey minecraftkey) {
NBTTagCompound nbttagcompound = itemstack.hasTag() ? itemstack.getTag() : new NBTTagCompound();
NBTTagCompound nbttagcompound1 = new NBTTagCompound();
nbttagcompound1.setString("id", minecraftkey.toString());
nbttagcompound.set("EntityTag", nbttagcompound1);
itemstack.setTag(nbttagcompound);
}
@Nullable
public static MinecraftKey h(ItemStack itemstack) {
NBTTagCompound nbttagcompound = itemstack.getTag();
if (nbttagcompound == null) {
return null;
} else if (!nbttagcompound.hasKeyOfType("EntityTag", 10)) {
return null;
} else {
NBTTagCompound nbttagcompound1 = nbttagcompound.getCompound("EntityTag");
if (!nbttagcompound1.hasKeyOfType("id", 8)) {
return null;
} else {
String s = nbttagcompound1.getString("id");
MinecraftKey minecraftkey = new MinecraftKey(s);
if (!s.contains(":")) {
nbttagcompound1.setString("id", minecraftkey.toString());
}
return minecraftkey;
}
}
}
}

View File

@@ -21,7 +21,6 @@ import io.netty.util.concurrent.GenericFutureListener;
import java.net.SocketAddress;
import java.util.Queue;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.annotation.Nullable;
import javax.crypto.SecretKey;
import org.apache.commons.lang3.ArrayUtils;

View File

@@ -0,0 +1,601 @@
package net.minecraft.server;
import co.aikar.timings.Timing;
import com.google.common.base.Predicate;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
// CraftBukkit start
import java.util.LinkedList;
// CraftBukkit end
/**
* Akarin Changes Note
* 1) Make whole class thread-safe (safety issue)
*/
@ThreadSafe // Akarin - idk why we need do so!!
public class PlayerChunkMap {
private static final Predicate<EntityPlayer> a = new Predicate() {
public boolean a(@Nullable EntityPlayer entityplayer) {
return entityplayer != null && !entityplayer.isSpectator();
}
public boolean apply(@Nullable Object object) {
return this.a((EntityPlayer) object);
}
};
private static final Predicate<EntityPlayer> b = new Predicate() {
public boolean a(@Nullable EntityPlayer entityplayer) {
return entityplayer != null && (!entityplayer.isSpectator() || entityplayer.x().getGameRules().getBoolean("spectatorsGenerateChunks"));
}
public boolean apply(@Nullable Object object) {
return this.a((EntityPlayer) object);
}
};
private final WorldServer world;
private final List<EntityPlayer> managedPlayers = Lists.newArrayList();
private final ReentrantReadWriteLock managedPlayersLock = new ReentrantReadWriteLock(); // Akarin - add lock
private final Long2ObjectMap<PlayerChunk> e = new Long2ObjectOpenHashMap(4096);
private final Set<PlayerChunk> f = Sets.newHashSet();
private final List<PlayerChunk> g = Lists.newLinkedList();
private final List<PlayerChunk> h = Lists.newLinkedList();
private final List<PlayerChunk> i = Lists.newCopyOnWriteArrayList(); // Akarin - bad plugin will access this
private AtomicInteger j = new AtomicInteger(); public int getViewDistance() { return j.get(); } // Paper OBFHELPER // Akarin - atmoic
private long k;
private AtomicBoolean l = new AtomicBoolean(true); // Akarin - atmoic
private AtomicBoolean m = new AtomicBoolean(true); // Akarin - atmoic
private boolean wasNotEmpty; // CraftBukkit - add field
public PlayerChunkMap(WorldServer worldserver) {
this.world = worldserver;
this.a(worldserver.spigotConfig.viewDistance); // Spigot
}
public WorldServer getWorld() {
return this.world;
}
public Iterator<Chunk> b() {
final Iterator iterator = this.i.iterator();
return new AbstractIterator<Chunk>() {
protected Chunk a() {
while (true) {
if (iterator.hasNext()) {
PlayerChunk playerchunk = (PlayerChunk) iterator.next();
Chunk chunk = playerchunk.f();
if (chunk == null) {
continue;
}
if (!chunk.v() && chunk.isDone()) {
return chunk;
}
if (!chunk.j()) {
return chunk;
}
if (!playerchunk.a(128.0D, PlayerChunkMap.a)) {
continue;
}
return chunk;
}
return (Chunk) this.endOfData();
}
}
protected Chunk computeNext() {
return this.a();
}
};
}
public synchronized void flush() { // Akarin - synchronized
long i = this.world.getTime();
int j;
PlayerChunk playerchunk;
if (i - this.k > 8000L) {
try (Timing ignored = world.timings.doChunkMapUpdate.startTiming()) { // Paper
this.k = i;
for (j = 0; j < this.i.size(); ++j) {
playerchunk = (PlayerChunk) this.i.get(j);
playerchunk.d();
playerchunk.c();
}
} // Paper timing
}
if (!this.f.isEmpty()) {
try (Timing ignored = world.timings.doChunkMapToUpdate.startTiming()) { // Paper
Iterator iterator = this.f.iterator();
while (iterator.hasNext()) {
playerchunk = (PlayerChunk) iterator.next();
playerchunk.d();
}
this.f.clear();
} // Paper timing
}
if (this.l.get() && i % 4L == 0L) {
this.l.getAndSet(false);
try (Timing ignored = world.timings.doChunkMapSortMissing.startTiming()) { // Paper
Collections.sort(this.h, new Comparator() {
public int a(PlayerChunk playerchunk, PlayerChunk playerchunk1) {
return ComparisonChain.start().compare(playerchunk.g(), playerchunk1.g()).result();
}
public int compare(Object object, Object object1) {
return this.a((PlayerChunk) object, (PlayerChunk) object1);
}
});
} // Paper timing
}
if (this.m.get() && i % 4L == 2L) {
this.m.getAndSet(false);
try (Timing ignored = world.timings.doChunkMapSortSendToPlayers.startTiming()) { // Paper
Collections.sort(this.g, new Comparator() {
public int a(PlayerChunk playerchunk, PlayerChunk playerchunk1) {
return ComparisonChain.start().compare(playerchunk.g(), playerchunk1.g()).result();
}
public int compare(Object object, Object object1) {
return this.a((PlayerChunk) object, (PlayerChunk) object1);
}
});
} // Paper timing
}
if (!this.h.isEmpty()) {
try (Timing ignored = world.timings.doChunkMapPlayersNeedingChunks.startTiming()) { // Paper
// Spigot start
org.spigotmc.SlackActivityAccountant activityAccountant = this.world.getMinecraftServer().slackActivityAccountant;
activityAccountant.startActivity(0.5);
int chunkGensAllowed = world.paperConfig.maxChunkGensPerTick; // Paper
// Spigot end
Iterator iterator1 = this.h.iterator();
while (iterator1.hasNext()) {
PlayerChunk playerchunk1 = (PlayerChunk) iterator1.next();
if (playerchunk1.f() == null) {
boolean flag = playerchunk1.a(PlayerChunkMap.b);
// Paper start
if (flag && !playerchunk1.chunkExists && chunkGensAllowed-- <= 0) {
continue;
}
// Paper end
if (playerchunk1.a(flag)) {
iterator1.remove();
if (playerchunk1.b()) {
this.g.remove(playerchunk1);
}
if (activityAccountant.activityTimeIsExhausted()) { // Spigot
break;
}
}
// CraftBukkit start - SPIGOT-2891: remove once chunk has been provided
} else {
iterator1.remove();
}
// CraftBukkit end
}
activityAccountant.endActivity(); // Spigot
} // Paper timing
}
if (!this.g.isEmpty()) {
j = world.paperConfig.maxChunkSendsPerTick; // Paper
try (Timing ignored = world.timings.doChunkMapPendingSendToPlayers.startTiming()) { // Paper
Iterator iterator2 = this.g.iterator();
while (iterator2.hasNext()) {
PlayerChunk playerchunk2 = (PlayerChunk) iterator2.next();
if (playerchunk2.b()) {
iterator2.remove();
--j;
if (j < 0) {
break;
}
}
}
} // Paper timing
}
managedPlayersLock.readLock().lock(); // Akarin
if (this.managedPlayers.isEmpty()) {
try (Timing ignored = world.timings.doChunkMapUnloadChunks.startTiming()) { // Paper
WorldProvider worldprovider = this.world.worldProvider;
if (!worldprovider.e() && !this.world.savingDisabled) { // Paper - respect saving disabled setting
this.world.getChunkProviderServer().b();
}
} // Paper timing
}
managedPlayersLock.readLock().unlock(); // Akarin
}
public synchronized boolean a(int i, int j) { // Akarin - synchronized
long k = d(i, j);
return this.e.get(k) != null;
}
@Nullable
public synchronized PlayerChunk getChunk(int i, int j) { // Akarin - synchronized
return (PlayerChunk) this.e.get(d(i, j));
}
private PlayerChunk c(int i, int j) {
long k = d(i, j);
PlayerChunk playerchunk = (PlayerChunk) this.e.get(k);
if (playerchunk == null) {
playerchunk = new PlayerChunk(this, i, j);
this.e.put(k, playerchunk);
this.i.add(playerchunk);
if (playerchunk.f() == null) {
this.h.add(playerchunk);
}
if (!playerchunk.b()) {
this.g.add(playerchunk);
}
}
return playerchunk;
}
// CraftBukkit start - add method
public final boolean isChunkInUse(int x, int z) {
PlayerChunk pi = getChunk(x, z);
if (pi != null) {
return (pi.c.size() > 0);
}
return false;
}
// CraftBukkit end
public void flagDirty(BlockPosition blockposition) {
int i = blockposition.getX() >> 4;
int j = blockposition.getZ() >> 4;
PlayerChunk playerchunk = this.getChunk(i, j);
if (playerchunk != null) {
playerchunk.a(blockposition.getX() & 15, blockposition.getY(), blockposition.getZ() & 15);
}
}
public void addPlayer(EntityPlayer entityplayer) {
int i = (int) entityplayer.locX >> 4;
int j = (int) entityplayer.locZ >> 4;
entityplayer.d = entityplayer.locX;
entityplayer.e = entityplayer.locZ;
// CraftBukkit start - Load nearby chunks first
List<ChunkCoordIntPair> chunkList = new LinkedList<ChunkCoordIntPair>();
// Paper start - Player view distance API
int viewDistance = entityplayer.getViewDistance();
for (int k = i - viewDistance; k <= i + viewDistance; ++k) {
for (int l = j - viewDistance; l <= j + viewDistance; ++l) {
// Paper end
chunkList.add(new ChunkCoordIntPair(k, l));
}
}
Collections.sort(chunkList, new ChunkCoordComparator(entityplayer));
synchronized (this) { // Akarin - synchronized
for (ChunkCoordIntPair pair : chunkList) {
this.c(pair.x, pair.z).a(entityplayer);
}
} // Akarin
// CraftBukkit end
managedPlayersLock.writeLock().lock(); // Akarin
this.managedPlayers.add(entityplayer);
managedPlayersLock.writeLock().unlock(); // Akarin
this.e();
}
public void removePlayer(EntityPlayer entityplayer) {
int i = (int) entityplayer.d >> 4;
int j = (int) entityplayer.e >> 4;
// Paper start - Player view distance API
int viewDistance = entityplayer.getViewDistance();
for (int k = i - viewDistance; k <= i + viewDistance; ++k) {
for (int l = j - viewDistance; l <= j + viewDistance; ++l) {
// Paper end
PlayerChunk playerchunk = this.getChunk(k, l);
if (playerchunk != null) {
playerchunk.b(entityplayer);
}
}
}
managedPlayersLock.writeLock().lock(); // Akarin
this.managedPlayers.remove(entityplayer);
managedPlayersLock.writeLock().unlock(); // Akarin
this.e();
}
private boolean a(int i, int j, int k, int l, int i1) {
int j1 = i - k;
int k1 = j - l;
return j1 >= -i1 && j1 <= i1 ? k1 >= -i1 && k1 <= i1 : false;
}
public void movePlayer(EntityPlayer entityplayer) {
int i = (int) entityplayer.locX >> 4;
int j = (int) entityplayer.locZ >> 4;
double d0 = entityplayer.d - entityplayer.locX;
double d1 = entityplayer.e - entityplayer.locZ;
double d2 = d0 * d0 + d1 * d1;
if (d2 >= 64.0D) {
int k = (int) entityplayer.d >> 4;
int l = (int) entityplayer.e >> 4;
final int viewDistance = entityplayer.getViewDistance(); // Paper - Player view distance API
int i1 = Math.max(getViewDistance(), viewDistance); // Paper - Player view distance API
int j1 = i - k;
int k1 = j - l;
List<ChunkCoordIntPair> chunksToLoad = new LinkedList<ChunkCoordIntPair>(); // CraftBukkit
if (j1 != 0 || k1 != 0) {
for (int l1 = i - i1; l1 <= i + i1; ++l1) {
for (int i2 = j - i1; i2 <= j + i1; ++i2) {
if (!this.a(l1, i2, k, l, viewDistance)) { // Paper - Player view distance API
// this.c(l1, i2).a(entityplayer);
chunksToLoad.add(new ChunkCoordIntPair(l1, i2)); // CraftBukkit
}
if (!this.a(l1 - j1, i2 - k1, i, j, i1)) {
PlayerChunk playerchunk = this.getChunk(l1 - j1, i2 - k1);
if (playerchunk != null) {
playerchunk.b(entityplayer);
}
}
}
}
entityplayer.d = entityplayer.locX;
entityplayer.e = entityplayer.locZ;
this.e();
// CraftBukkit start - send nearest chunks first
Collections.sort(chunksToLoad, new ChunkCoordComparator(entityplayer));
synchronized (this) { // Akarin - synchronized
for (ChunkCoordIntPair pair : chunksToLoad) {
this.c(pair.x, pair.z).a(entityplayer);
}
} // Akarin
// CraftBukkit end
}
}
}
public boolean a(EntityPlayer entityplayer, int i, int j) {
PlayerChunk playerchunk = this.getChunk(i, j);
return playerchunk != null && playerchunk.d(entityplayer) && playerchunk.e();
}
public final void setViewDistanceForAll(int viewDistance) { this.a(viewDistance); } // Paper - OBFHELPER
// Paper start - Separate into two methods
public void a(int i) {
i = MathHelper.clamp(i, 3, 32);
if (i != this.j.get()) { // Akarin - atmoic
int j = i - this.j.get(); // Akarin - atmoic
managedPlayersLock.readLock().lock(); // Akarin
ArrayList arraylist = Lists.newArrayList(this.managedPlayers);
managedPlayersLock.readLock().unlock(); // Akarin
Iterator iterator = arraylist.iterator();
while (iterator.hasNext()) {
EntityPlayer entityplayer = (EntityPlayer) iterator.next();
this.setViewDistance(entityplayer, i, false); // Paper - Split, don't mark sort pending, we'll handle it after
}
this.j.getAndSet(i); // Akarin - atmoic
this.e();
}
}
public void setViewDistance(EntityPlayer entityplayer, int i) {
this.setViewDistance(entityplayer, i, true); // Mark sort pending by default so we don't have to remember to do so all the time
}
// Copied from above with minor changes
public void setViewDistance(EntityPlayer entityplayer, int i, boolean markSort) {
i = MathHelper.clamp(i, 3, 32);
int oldViewDistance = entityplayer.getViewDistance();
if (i != oldViewDistance) {
int j = i - oldViewDistance;
int k = (int) entityplayer.locX >> 4;
int l = (int) entityplayer.locZ >> 4;
int i1;
int j1;
if (j > 0) {
synchronized (this) { // Akarin - synchronized
for (i1 = k - i; i1 <= k + i; ++i1) {
for (j1 = l - i; j1 <= l + i; ++j1) {
PlayerChunk playerchunk = this.c(i1, j1);
if (!playerchunk.d(entityplayer)) {
playerchunk.a(entityplayer);
}
}
}
} // Akarin
} else {
synchronized (this) { // Akarin - synchronized
for (i1 = k - oldViewDistance; i1 <= k + oldViewDistance; ++i1) {
for (j1 = l - oldViewDistance; j1 <= l + oldViewDistance; ++j1) {
if (!this.a(i1, j1, k, l, i)) {
this.c(i1, j1).b(entityplayer);
}
}
}
} // Akarin
if (markSort) {
this.e();
}
}
}
}
// Paper end
private void e() {
this.l.getAndSet(true); // Akarin - atmoic
this.m.getAndSet(true); // Akarin - atmoic
}
public static int getFurthestViewableBlock(int i) {
return i * 16 - 16;
}
private static long d(int i, int j) {
return (long) i + 2147483647L | (long) j + 2147483647L << 32;
}
public synchronized void a(PlayerChunk playerchunk) { // Akarin - synchronized
// org.spigotmc.AsyncCatcher.catchOp("Async Player Chunk Add"); // Paper // Akarin
this.f.add(playerchunk);
}
public synchronized void b(PlayerChunk playerchunk) { // Akarin - synchronized
org.spigotmc.AsyncCatcher.catchOp("Async Player Chunk Remove"); // Paper
ChunkCoordIntPair chunkcoordintpair = playerchunk.a();
long i = d(chunkcoordintpair.x, chunkcoordintpair.z);
playerchunk.c();
this.e.remove(i);
this.i.remove(playerchunk);
this.f.remove(playerchunk);
this.g.remove(playerchunk);
this.h.remove(playerchunk);
Chunk chunk = playerchunk.f();
if (chunk != null) {
// Paper start - delay chunk unloads
if (world.paperConfig.delayChunkUnloadsBy <= 0) {
this.getWorld().getChunkProviderServer().unload(chunk);
} else {
chunk.scheduledForUnload = System.currentTimeMillis();
}
// Paper end
}
}
// CraftBukkit start - Sorter to load nearby chunks first
private static class ChunkCoordComparator implements java.util.Comparator<ChunkCoordIntPair> {
private int x;
private int z;
public ChunkCoordComparator (EntityPlayer entityplayer) {
x = (int) entityplayer.locX >> 4;
z = (int) entityplayer.locZ >> 4;
}
public int compare(ChunkCoordIntPair a, ChunkCoordIntPair b) {
if (a.equals(b)) {
return 0;
}
// Subtract current position to set center point
int ax = a.x - this.x;
int az = a.z - this.z;
int bx = b.x - this.x;
int bz = b.z - this.z;
int result = ((ax - bx) * (ax + bx)) + ((az - bz) * (az + bz));
if (result != 0) {
return result;
}
if (ax < 0) {
if (bx < 0) {
return bz - az;
} else {
return -1;
}
} else {
if (bx < 0) {
return 1;
} else {
return az - bz;
}
}
}
}
// CraftBukkit end
// Paper start - Player view distance API
public void updateViewDistance(EntityPlayer player, int distanceIn) {
final int oldViewDistance = player.getViewDistance();
// This represents the view distance that we will set on the player
// It can exist as a negative value
int playerViewDistance = MathHelper.clamp(distanceIn, 3, 32);
// This value is the one we actually use to update the chunk map
// We don't ever want this to be a negative
int toSet = playerViewDistance;
if (distanceIn < 0) {
playerViewDistance = -1;
toSet = world.getPlayerChunkMap().getViewDistance();
}
if (toSet != oldViewDistance) {
// Order matters
this.setViewDistance(player, toSet);
player.setViewDistance(playerViewDistance);
}
}
// Paper end
}

View File

@@ -81,6 +81,7 @@ public class PlayerConnection implements PacketListenerPlayIn, ITickable {
// CraftBukkit start - multithreaded fields
private volatile int chatThrottle;
private static final AtomicIntegerFieldUpdater chatSpamField = AtomicIntegerFieldUpdater.newUpdater(PlayerConnection.class, "chatThrottle");
private final java.util.concurrent.atomic.AtomicInteger tabSpamLimiter = new java.util.concurrent.atomic.AtomicInteger(); // Paper - configurable tab spam limits
// CraftBukkit end
private int j;
private final IntHashMap<Short> k = new IntHashMap();
@@ -212,6 +213,7 @@ public class PlayerConnection implements PacketListenerPlayIn, ITickable {
this.minecraftServer.methodProfiler.b();
// CraftBukkit start
for (int spam; (spam = this.chatThrottle) > 0 && !chatSpamField.compareAndSet(this, spam, spam - 1); ) ;
if (tabSpamLimiter.get() > 0) tabSpamLimiter.getAndDecrement(); // Paper - split to seperate variable
/* Use thread-safe field access instead
if (this.chatThrottle > 0) {
--this.chatThrottle;
@@ -2293,7 +2295,7 @@ public class PlayerConnection implements PacketListenerPlayIn, ITickable {
// Paper start - async tab completion
public void a(PacketPlayInTabComplete packet) {
// CraftBukkit start
if (chatSpamField.addAndGet(this, 10) > 500 && !this.minecraftServer.getPlayerList().isOp(this.player.getProfile())) {
if (tabSpamLimiter.addAndGet(com.destroystokyo.paper.PaperConfig.tabSpamIncrement) > com.destroystokyo.paper.PaperConfig.tabSpamLimit && !this.minecraftServer.getPlayerList().isOp(this.player.getProfile())) { // Paper start - split and make configurable
minecraftServer.postToMainThread(() -> this.disconnect(new ChatMessage("disconnect.spam", new Object[0])));
return;
}

View File

@@ -2,12 +2,14 @@ package net.minecraft.server;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterators;
import java.util.BitSet;
import java.util.Iterator;
import javax.annotation.Nullable;
/**
* Akarin Changes Note
* 1) BitSet for faster access (performance)
* 1) FastBitSet for faster access (performance)
*/
public class RegistryID<K> implements Registry {
@@ -24,7 +26,7 @@ public class RegistryID<K> implements Registry {
this.b = (K[]) (new Object[i]);
this.c = new int[i];
this.d = (K[]) (new Object[i]);
this.usedIds = new java.util.BitSet(); // Akarin - 1.13 backport
this.usedIds = new BitSet(); // Akarin - 1.13 backport
}
public int getId(@Nullable K k0) {

File diff suppressed because it is too large Load Diff

View File

@@ -1194,7 +1194,7 @@ public class WorldServer extends World implements IAsyncTaskHandler {
if (this.entitiesByUUID.containsKey(uuid)) {
Entity entity1 = this.entitiesByUUID.get(uuid);
if (this.f.contains(entity1) || entity1.dead) { // Paper - overwrite the current dead one
if (this.f.contains(entity1) || entity1.dead) { // Paper - if dupe is dead, overwrite
this.f.remove(entity1);
} else {
if (!(entity instanceof EntityHuman)) {

View File

@@ -1107,10 +1107,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
EntityTracker tracker = ((WorldServer) entity.world).tracker;
// Paper end
tracker.entriesLock.updateLock().lock(); // Akarin
EntityTrackerEntry entry = tracker.trackedEntities.get(other.getId());
if (entry != null) {
tracker.entriesLock.writeLock().lock(); // Akarin
entry.clear(getHandle());
tracker.entriesLock.writeLock().unlock(); // Akarin
}
tracker.entriesLock.updateLock().unlock(); // Akarin
// Remove the hidden player from this player user list, if they're on it
if (other.sentListPacket) {
@@ -1157,12 +1161,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
getHandle().playerConnection.sendPacket(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, other));
tracker.entriesLock.updateLock().lock(); // Akarin
EntityTrackerEntry entry = tracker.trackedEntities.get(other.getId());
if (entry != null && !entry.trackedPlayers.contains(getHandle())) {
tracker.entriesLock.lock(); // Akarin
tracker.entriesLock.writeLock().lock(); // Akarin
entry.updatePlayer(getHandle());
tracker.entriesLock.unlock(); // Akarin
tracker.entriesLock.writeLock().unlock(); // Akarin
}
tracker.entriesLock.updateLock().unlock(); // Akarin
}
// Paper start
private void reregisterPlayer(EntityPlayer player) {

View File

@@ -19,13 +19,13 @@
"core.MixinCommandKick",
"core.MixinCraftServer",
"core.MixinWorldServer",
"core.MixinFileIOThread",
"core.MixinWorldManager",
"core.MixinCommandBanIp",
"core.MixinChunkSection",
"core.MixinAsyncCatcher",
"core.MixinTimingHandler",
"core.MixinVersionCommand",
"core.MixinItemMonsterEgg",
"core.MixinMinecraftServer",
"core.MixinChunkIOExecutor",
"core.MixinPlayerConnectionUtils",

View File

@@ -1,15 +0,0 @@
{
"required": true,
"minVersion": "0.7.10",
"package": "io.akarin.server.mixin",
"target": "@env(DEFAULT)",
"compatibilityLevel": "JAVA_8",
"server": [
"cps.MixinChunk",
"lighting.MixinChunk",
"lighting.MixinWorld",
"lighting.MixinWorldServer",
"lighting.MixinChunkProviderServer",
]
}