以每秒多次循环遍历数万到数十万个位置
Loop through tens to hundreds of thousands locations at multiples times per second
正在尝试在这里制作一个 minecraft 插件。我正在尝试制作自定义矿石生成器。发电机是立方体,外面没有外墙,中间是矿石。
当我创建一个生成器时,我不会保存每个位置对象,因为每个玩家都可以拥有多个生成器,这很快就会产生大量的块。我所做的是将边界和立方体内部的最小和最大位置保存在一个名为 CubeLocation 的对象中,该对象也存储 cubeId。
所以我将立方体保存在 HashMap 中 - (这不是玩家的 ID,但我在创建每个代码时生成一个随机的 uuid)。
而且我还将 Location 保存在 HashSet
现在解决我的问题。我需要检查损坏块的位置是否在立方体内部。我现在有的是这样的
public class LocationManager {
private HashMap<UUID, PlayerCube> cubes;
private HashSet<CubeLocation> cubeLocations;
private HashMap<UUID, PlayerHold> playerHolds;
private HashMap<UUID, Integer> tasks;
private HashMap<UUID, UUID> lastKnownGenerator;
public LocationManager() {
cubes = new HashMap<>();
cubeLocations = new HashSet<>();
playerHolds = new HashMap<>();
tasks = new HashMap<>();
lastKnownGenerator = new HashMap<>();
}
public HashMap<UUID, PlayerCube> getCubes(){
return cubes;
}
public HashSet<CubeLocation> getCubeLocations(){
return cubeLocations;
}
public HashMap<UUID, PlayerHold> getPlayerHolds(){
return playerHolds;
}
public HashMap<UUID, Integer> getTasks(){
return tasks;
}
public HashMap<UUID, UUID> getLastKnownGenerator(){
return lastKnownGenerator;
}
public String[] isLocationInsideCube(UUID player, Location loc) {
UUID world = loc.getWorld().getUID();
int x = loc.getBlockX();
int y = loc.getBlockY();
int z = loc.getBlockZ();
String arr[] = {null, null};
UUID last = lastKnownGenerator.get(player);
if(last != null) {
CubeLocation cubeLoc = cubes.get(last).getCubeLocation();
lastKnownGenerator.remove(player);
if(cubeLoc != null && cubeLoc.isInside(world, x, y, z)) {
if(cubeLoc.isCubeLocation(world, x, y, z)) {
arr[1] = last.toString();
return arr;
}
if(arr[0] == null && cubeLoc.isBorderLocation(world, x, y, z)) {
arr[0] = last.toString();
return arr;
}
}
}
for(CubeLocation cubeLoc : cubeLocations) {
if(!cubeLoc.isInside(world, x, y, z))
continue;
String id = cubeLoc.getCubeId().toString();
if(arr[1] == null && cubeLoc.isCubeLocation(world, x, y, z)) {
arr[1] = id;
break;
}
if(arr[0] == null && cubeLoc.isBorderLocation(world, x, y, z)) {
arr[0] = id;
break;
}
}
return arr;
}
}
这是 CubeLocation
public class CubeLocation {
private UUID world;
private int minX;
private int maxX;
private int minY;
private int maxY;
private int minZ;
private int maxZ;
private int bMinX;
private int bMaxX;
private int bMinY;
private int bMaxY;
private int bMinZ;
private int bMaxZ;
private UUID cubeId;
public CubeLocation(UUID world, UUID cubeId) {
this.world = world;
this.cubeId = cubeId;
}
public void setCoords(int x1, int x2, int y1, int y2, int z1, int z2) {
//Border
this.bMinX = x1;
this.bMaxX = x2;
this.bMinY = y1;
this.bMaxY = y2;
this.bMinZ = z1;
this.bMaxZ = z2;
//Cube
this.minX = x1 + 1;
this.maxX = x2 - 1;
this.minY = y1 + 1;
this.maxY = y2 - 1;
this.minZ = z1 + 1;
this.maxZ = z2 - 1;
}
public UUID getCubeId() {
return cubeId;
}
public boolean isInside(UUID world, int x, int y, int z) {
if(!world.equals(this.world))
return false;
return (x >= bMinX && x <= bMaxX) && (y >= bMinY && y <= bMaxY) && (z >= bMinZ && z <= bMaxZ);
}
public boolean isBorderLocation(UUID world, int x, int y, int z) {
if(!world.equals(this.world))
return false;
if(y == bMinY || y == bMaxY) {
return (x == bMinX && (z >= bMinZ || z <= bMaxZ)) || (x == bMaxX && (z >= bMinZ || z <= bMaxZ)) || (z == bMinZ && (x >= bMinX || x <= bMaxX)) || (z == bMaxZ && (x >= bMinX || x <= bMaxX));
}
return (x == bMinX && z == bMinZ) || (x == bMaxX && z == bMinZ) || (x == bMinX && z == bMaxZ) || (x == bMaxX && z == bMaxZ);
}
public boolean isCubeLocation(UUID world, int x, int y, int z) {
if(!world.equals(this.world))
return false;
return (x >= minX && x <= maxX) && (y >= minY && y <= maxY) && (z >= minZ && z <= maxZ);
}
}
这也是我将边框与立方体分开保存的原因。因为我不希望玩家能够自己打破边界,但又不考虑墙壁(无论如何这不是问题,但我想解释一下)。
问题是每次一个块被破坏时,它都会检查每个 CubeLocation 以检查该位置是否是一个生成器。这意味着每次破坏方块时,它都会每次都进行此检查(这就是我添加 lastKnownGenerator 的原因,它会首先检查这个以查看玩家是否在同一个生成器中破坏方块,因此它没有再次循环)。
然而,如果玩家破坏了不在发电机内的方块(例如在矿井、房屋或 idk 中),这将无济于事,它将对每个破碎的方块进行循环。这还不是最糟糕的部分,最糟糕的是 BlockBreakEvent(当玩家破坏方块时触发的事件)可以被单个玩家每秒触发几十次(如果你的镐、铲子或斧头)不仅如此,它还可以同时被多个玩家触发。
想象一下,世界上总共放置了大约 30k 个立方体,大约有 30 名玩家拥有完整的附魔,每人每秒挖出 20 个方块。这意味着插件每秒检查 600k 坐标。这样不行,太浪费了
您也不能单独保存每个位置或用 block.setMetadata(key, FixedMetadataValue(plugin, value)); 标记一个块;因为你必须循环(假设我们的所有生成器都是 11x11x11,每个生成器 1331 个)30.000 * 1331,这是一个高达 39.930.000 个块,所以它比我正在做的更糟糕。
保存立方体并检查损坏的方块是否在立方体内部的合适方法是什么?我想做的事情完全错了吗?欢迎提出任何建议!
我建议你做多件事:
- 少用变量。它不会直接固定项目的数量,但会有助于提高性能。这也主要是一个建议,而不是使代码更快地更具可读性。例如,您可以添加一个中心的位置,然后存储大小。所以你只需要 5 个变量:
public int centerX, centerY, centerZ;
public int sizeBorder, sizeCube;
有了这些,如果你有 30k 个实例,你将节省 7*30000,即 210k 个变量。
- 使用块。这真的可以帮助你。 objective是按照chunk来存储cube。
首先,你需要获取块 X/Z :
public static getChunkX(Location loc) {
rreturn floor(loc.getX()) >> 4;
}
/**
* NumberConversions#floor(double), used by Location#getBlockX(), Location#getBlockZ()
*
* @param num the number to floor
* @return the floored number
*/
public static int floor(double num) {
int floor = (int) num;
return floor == num ? floor : floor - (int) (Double.doubleToRawLongBits(num) >>> 63);
}
(Source)
现在,您应该声明将包含所有内容的变量:
HashMap<Integer, HashMap<Integer, CubeLocation>> cubePerChunk = new HashMap<>();
这是一个 table 的数组 table。想了解更多可以参考this
Location myLoc = ...;
List<CubeLocation> allCubesInChunk = cubePerChunk.get(getChunkX(myLoc)).get(getChunkZ(myLoc));
现在,您可以对所有多维数据集位置执行您所做的操作,但是您将只有该块中的项目,而不是 30k 项目的列表,因此少于 5 个。
要存储值,您应该获取 4 个点的块并尝试将其添加到所有列表中。如果所有点都在同一个块中,您将只存储一次项目,否则它将在它所在的所有块中可用。代码示例:
public void loadCube(CubeLocation loadedCube) {
for(Location loc : loadedCube.getAllXZPoints()) {
List<CubeLocation> allCubesInChunk = cubePerChunk.computeIfAbsent(getChunkX(loc), HashMap::new)
.computeIfAbsent(getChunkZ(loc), ArrayList::new);
if(!allCubesInChunk.contains(loadedCube)) // if not already added
allCubesInChunk.add(loadedCube); // add it
}
}
只是,关于getAllPoints()
方法:
只是return所有点数不计入Y的东西,如:
public List<Location> getAllXZPoints() {
// w is your world
return Arrays.asList(new Location(w, minX, minY, minZ), new Location(w, maxX, minY, minZ),
new Location(w, minX, minY, maxZ), new Location(w, minX, minY, maxZ));
}
再给你一些提示 dmf,当你在这里寻找性能时,你有两点可以做,那就是内存优化,基本上你会使用更少的内存,这基本上是 Elikill 已经做到的在这里告诉你^^。
第二件事是你使用 NMS,因为你正在设置(我相信,很多块),最好是你访问你可以访问的最深的 nms 方法,你可以在主龙头上找到一些教程关于如何快速放置方块的网站(或者你可以去看看 FAWE src)。
另一件事是,要检查损坏的方块是否在立方体内部,您所要做的就是获取立方体的最大点,获取立方体的最小点,
并检查块的 x 是否 <= max x,
和相同的过程,对于所有坐标,x y z.
请不要调用 .getAllPoints() 来检查块是否在立方体内部,那将是您可以编写的最糟糕的情况。
正在尝试在这里制作一个 minecraft 插件。我正在尝试制作自定义矿石生成器。发电机是立方体,外面没有外墙,中间是矿石。
当我创建一个生成器时,我不会保存每个位置对象,因为每个玩家都可以拥有多个生成器,这很快就会产生大量的块。我所做的是将边界和立方体内部的最小和最大位置保存在一个名为 CubeLocation 的对象中,该对象也存储 cubeId。
所以我将立方体保存在 HashMap
而且我还将 Location 保存在 HashSet 现在解决我的问题。我需要检查损坏块的位置是否在立方体内部。我现在有的是这样的 这是 CubeLocation 这也是我将边框与立方体分开保存的原因。因为我不希望玩家能够自己打破边界,但又不考虑墙壁(无论如何这不是问题,但我想解释一下)。 问题是每次一个块被破坏时,它都会检查每个 CubeLocation 以检查该位置是否是一个生成器。这意味着每次破坏方块时,它都会每次都进行此检查(这就是我添加 lastKnownGenerator 的原因,它会首先检查这个以查看玩家是否在同一个生成器中破坏方块,因此它没有再次循环)。 然而,如果玩家破坏了不在发电机内的方块(例如在矿井、房屋或 idk 中),这将无济于事,它将对每个破碎的方块进行循环。这还不是最糟糕的部分,最糟糕的是 BlockBreakEvent(当玩家破坏方块时触发的事件)可以被单个玩家每秒触发几十次(如果你的镐、铲子或斧头)不仅如此,它还可以同时被多个玩家触发。 想象一下,世界上总共放置了大约 30k 个立方体,大约有 30 名玩家拥有完整的附魔,每人每秒挖出 20 个方块。这意味着插件每秒检查 600k 坐标。这样不行,太浪费了 您也不能单独保存每个位置或用 block.setMetadata(key, FixedMetadataValue(plugin, value)); 标记一个块;因为你必须循环(假设我们的所有生成器都是 11x11x11,每个生成器 1331 个)30.000 * 1331,这是一个高达 39.930.000 个块,所以它比我正在做的更糟糕。 保存立方体并检查损坏的方块是否在立方体内部的合适方法是什么?我想做的事情完全错了吗?欢迎提出任何建议!public class LocationManager {
private HashMap<UUID, PlayerCube> cubes;
private HashSet<CubeLocation> cubeLocations;
private HashMap<UUID, PlayerHold> playerHolds;
private HashMap<UUID, Integer> tasks;
private HashMap<UUID, UUID> lastKnownGenerator;
public LocationManager() {
cubes = new HashMap<>();
cubeLocations = new HashSet<>();
playerHolds = new HashMap<>();
tasks = new HashMap<>();
lastKnownGenerator = new HashMap<>();
}
public HashMap<UUID, PlayerCube> getCubes(){
return cubes;
}
public HashSet<CubeLocation> getCubeLocations(){
return cubeLocations;
}
public HashMap<UUID, PlayerHold> getPlayerHolds(){
return playerHolds;
}
public HashMap<UUID, Integer> getTasks(){
return tasks;
}
public HashMap<UUID, UUID> getLastKnownGenerator(){
return lastKnownGenerator;
}
public String[] isLocationInsideCube(UUID player, Location loc) {
UUID world = loc.getWorld().getUID();
int x = loc.getBlockX();
int y = loc.getBlockY();
int z = loc.getBlockZ();
String arr[] = {null, null};
UUID last = lastKnownGenerator.get(player);
if(last != null) {
CubeLocation cubeLoc = cubes.get(last).getCubeLocation();
lastKnownGenerator.remove(player);
if(cubeLoc != null && cubeLoc.isInside(world, x, y, z)) {
if(cubeLoc.isCubeLocation(world, x, y, z)) {
arr[1] = last.toString();
return arr;
}
if(arr[0] == null && cubeLoc.isBorderLocation(world, x, y, z)) {
arr[0] = last.toString();
return arr;
}
}
}
for(CubeLocation cubeLoc : cubeLocations) {
if(!cubeLoc.isInside(world, x, y, z))
continue;
String id = cubeLoc.getCubeId().toString();
if(arr[1] == null && cubeLoc.isCubeLocation(world, x, y, z)) {
arr[1] = id;
break;
}
if(arr[0] == null && cubeLoc.isBorderLocation(world, x, y, z)) {
arr[0] = id;
break;
}
}
return arr;
}
}
public class CubeLocation {
private UUID world;
private int minX;
private int maxX;
private int minY;
private int maxY;
private int minZ;
private int maxZ;
private int bMinX;
private int bMaxX;
private int bMinY;
private int bMaxY;
private int bMinZ;
private int bMaxZ;
private UUID cubeId;
public CubeLocation(UUID world, UUID cubeId) {
this.world = world;
this.cubeId = cubeId;
}
public void setCoords(int x1, int x2, int y1, int y2, int z1, int z2) {
//Border
this.bMinX = x1;
this.bMaxX = x2;
this.bMinY = y1;
this.bMaxY = y2;
this.bMinZ = z1;
this.bMaxZ = z2;
//Cube
this.minX = x1 + 1;
this.maxX = x2 - 1;
this.minY = y1 + 1;
this.maxY = y2 - 1;
this.minZ = z1 + 1;
this.maxZ = z2 - 1;
}
public UUID getCubeId() {
return cubeId;
}
public boolean isInside(UUID world, int x, int y, int z) {
if(!world.equals(this.world))
return false;
return (x >= bMinX && x <= bMaxX) && (y >= bMinY && y <= bMaxY) && (z >= bMinZ && z <= bMaxZ);
}
public boolean isBorderLocation(UUID world, int x, int y, int z) {
if(!world.equals(this.world))
return false;
if(y == bMinY || y == bMaxY) {
return (x == bMinX && (z >= bMinZ || z <= bMaxZ)) || (x == bMaxX && (z >= bMinZ || z <= bMaxZ)) || (z == bMinZ && (x >= bMinX || x <= bMaxX)) || (z == bMaxZ && (x >= bMinX || x <= bMaxX));
}
return (x == bMinX && z == bMinZ) || (x == bMaxX && z == bMinZ) || (x == bMinX && z == bMaxZ) || (x == bMaxX && z == bMaxZ);
}
public boolean isCubeLocation(UUID world, int x, int y, int z) {
if(!world.equals(this.world))
return false;
return (x >= minX && x <= maxX) && (y >= minY && y <= maxY) && (z >= minZ && z <= maxZ);
}
}
我建议你做多件事:
- 少用变量。它不会直接固定项目的数量,但会有助于提高性能。这也主要是一个建议,而不是使代码更快地更具可读性。例如,您可以添加一个中心的位置,然后存储大小。所以你只需要 5 个变量:
public int centerX, centerY, centerZ;
public int sizeBorder, sizeCube;
有了这些,如果你有 30k 个实例,你将节省 7*30000,即 210k 个变量。
- 使用块。这真的可以帮助你。 objective是按照chunk来存储cube。
首先,你需要获取块 X/Z :
public static getChunkX(Location loc) {
rreturn floor(loc.getX()) >> 4;
}
/**
* NumberConversions#floor(double), used by Location#getBlockX(), Location#getBlockZ()
*
* @param num the number to floor
* @return the floored number
*/
public static int floor(double num) {
int floor = (int) num;
return floor == num ? floor : floor - (int) (Double.doubleToRawLongBits(num) >>> 63);
}
(Source)
现在,您应该声明将包含所有内容的变量:
HashMap<Integer, HashMap<Integer, CubeLocation>> cubePerChunk = new HashMap<>();
这是一个 table 的数组 table。想了解更多可以参考this
Location myLoc = ...;
List<CubeLocation> allCubesInChunk = cubePerChunk.get(getChunkX(myLoc)).get(getChunkZ(myLoc));
现在,您可以对所有多维数据集位置执行您所做的操作,但是您将只有该块中的项目,而不是 30k 项目的列表,因此少于 5 个。
要存储值,您应该获取 4 个点的块并尝试将其添加到所有列表中。如果所有点都在同一个块中,您将只存储一次项目,否则它将在它所在的所有块中可用。代码示例:
public void loadCube(CubeLocation loadedCube) {
for(Location loc : loadedCube.getAllXZPoints()) {
List<CubeLocation> allCubesInChunk = cubePerChunk.computeIfAbsent(getChunkX(loc), HashMap::new)
.computeIfAbsent(getChunkZ(loc), ArrayList::new);
if(!allCubesInChunk.contains(loadedCube)) // if not already added
allCubesInChunk.add(loadedCube); // add it
}
}
只是,关于getAllPoints()
方法:
只是return所有点数不计入Y的东西,如:
public List<Location> getAllXZPoints() {
// w is your world
return Arrays.asList(new Location(w, minX, minY, minZ), new Location(w, maxX, minY, minZ),
new Location(w, minX, minY, maxZ), new Location(w, minX, minY, maxZ));
}
再给你一些提示 dmf,当你在这里寻找性能时,你有两点可以做,那就是内存优化,基本上你会使用更少的内存,这基本上是 Elikill 已经做到的在这里告诉你^^。
第二件事是你使用 NMS,因为你正在设置(我相信,很多块),最好是你访问你可以访问的最深的 nms 方法,你可以在主龙头上找到一些教程关于如何快速放置方块的网站(或者你可以去看看 FAWE src)。
另一件事是,要检查损坏的方块是否在立方体内部,您所要做的就是获取立方体的最大点,获取立方体的最小点, 并检查块的 x 是否 <= max x, 和相同的过程,对于所有坐标,x y z.
请不要调用 .getAllPoints() 来检查块是否在立方体内部,那将是您可以编写的最糟糕的情况。