【解決済】[1.10.2]一括採掘で発生するItemEntityに関しConcurrentModificationが出る

Modding・サーバPlugin制作・ツール制作など、開発関連の質問があればこちらにお願い致します。
フォーラムルール
質問関連フォーラムで質問する時は、必ず次のトピックを一読/厳守お願い致します。
viewtopic.php?f=5&t=999
  • (PostNo.315002)

【解決済】[1.10.2]一括採掘で発生するItemEntityに関しConcurrentModificationが出る

投稿記事by remiliaMarine » 2018年3月24日(土) 10:02

一括採掘をするツールを作ろうとしているのですが、ブロックを破壊した際まれに (数十~数百回に一回) ConcurrentModificationExceptionにより落ちてしまいます。

この例外はMapやListの要素が取り除かれた場合に発生することがあるようなので、関係する変数
1). Set<EntityTrackerEntry> net.minecraft.entity.EntityTracker.trackedEntities
2). ClassInheritanceMultiMap<Entity>[] net.minecraft.world.chunk.Chunk.entityLists
から要素が取り除かれる条件について調べ確認してみたのですが、よくわかりませんでした。
プレイヤーがリスポーンする際や、Chunkがアンロードされるときに取り除かれるようですが、ブロックを破壊した直後に落ちるので関係ないと思われます。実際、念のため、あまり移動しないようにしながら一括採掘を試したのですがクラッシュしました。ほかの取り除かれる条件については見つけられませんでした。
 また、ItemEntityがスポーンした直後にsetDeadされて取り除かれているのではないかと思い、イベントで、新たにスポーンしたitemStackがnullもしくはitem.stackSize<=0のものをcancellするようにしてもクラッシュしました。

クラッシュレポート 数種類の変数に関して例外が発生しているようなので、クラレポの一例を載せます。
---- Minecraft Crash Report ----
// Don't do that.

Time: 18/03/24 10:01
Description: Ticking entity

java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at com.google.common.collect.Iterators$7.computeNext(Iterators.java:646)
at com.google.common.collect.AbstractIterator.tryToComputeNext(AbstractIterator.java:143)
at com.google.common.collect.AbstractIterator.hasNext(AbstractIterator.java:138)
at net.minecraft.world.chunk.Chunk.getEntitiesOfTypeWithinAAAB(Chunk.java:993)
at net.minecraft.world.World.getEntitiesWithinAABB(World.java:3306)
at net.minecraft.world.World.getEntitiesWithinAABB(World.java:3289)
at net.minecraft.entity.item.EntityItem.searchForOtherItemsNearby(EntityItem.java:187)
at net.minecraft.entity.item.EntityItem.onUpdate(EntityItem.java:140)
at net.minecraft.world.World.updateEntityWithOptionalForce(World.java:2118)
at net.minecraft.world.WorldServer.updateEntityWithOptionalForce(WorldServer.java:875)
at net.minecraft.world.World.updateEntity(World.java:2085)
at net.minecraft.world.World.updateEntities(World.java:1898)
at net.minecraft.world.WorldServer.updateEntities(WorldServer.java:647)
at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:784)
at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:688)
at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:156)
at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:537)
at java.lang.Thread.run(Unknown Source)


A detailed walkthrough of the error, its code path and all known details is as follows:
---------------------------------------------------------------------------------------

-- Head --
Thread: Server thread
Stacktrace:
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at com.google.common.collect.Iterators$7.computeNext(Iterators.java:646)
at com.google.common.collect.AbstractIterator.tryToComputeNext(AbstractIterator.java:143)
at com.google.common.collect.AbstractIterator.hasNext(AbstractIterator.java:138)
at net.minecraft.world.chunk.Chunk.getEntitiesOfTypeWithinAAAB(Chunk.java:993)
at net.minecraft.world.World.getEntitiesWithinAABB(World.java:3306)
at net.minecraft.world.World.getEntitiesWithinAABB(World.java:3289)
at net.minecraft.entity.item.EntityItem.searchForOtherItemsNearby(EntityItem.java:187)
at net.minecraft.entity.item.EntityItem.onUpdate(EntityItem.java:140)
at net.minecraft.world.World.updateEntityWithOptionalForce(World.java:2118)
at net.minecraft.world.WorldServer.updateEntityWithOptionalForce(WorldServer.java:875)
at net.minecraft.world.World.updateEntity(World.java:2085)

-- Entity being ticked --
Details:
Entity Type: Item (net.minecraft.entity.item.EntityItem)
Entity ID: 12992
Entity Name: item.tile.dirt.default
Entity's Exact location: 1283.24, 62.85, 4763.12
Entity's Block location: World: (1283,62,4763), Chunk: (at 3,3,11 in 80,297; contains blocks 1280,0,4752 to 1295,255,4767), Region: (2,9; contains chunks 64,288 to 95,319, blocks 1024,0,4608 to 1535,255,5119)
Entity's Momentum: -0.00, -0.27, -0.02
Entity's Passengers: []
Entity's Vehicle: ~~ERROR~~ NullPointerException: null
Stacktrace:
at net.minecraft.world.World.updateEntities(World.java:1898)
at net.minecraft.world.WorldServer.updateEntities(WorldServer.java:647)

-- Affected level --
Details:
Level name: New World
All players: 1 total; [EntityPlayerMP['RemiliaMarine'/126, l='New World', x=1282.50, y=61.00, z=4762.65]]
Chunk stats: ServerChunkCache: 892 Drop: 0
Level seed: -6128968327004720996
Level generator: ID 01 - flat, ver 0. Features enabled: true
Level generator options: 3;minecraft:bedrock,59*minecraft:stone,3*minecraft:dirt,minecraft:grass;1;village,biome_1,decoration,stronghold,mineshaft,dungeon
Level spawn location: World: (1290,4,190), Chunk: (at 10,0,14 in 80,11; contains blocks 1280,0,176 to 1295,255,191), Region: (2,0; contains chunks 64,0 to 95,31, blocks 1024,0,0 to 1535,255,511)
Level time: 32832 game time, 55832 day time
Level dimension: 0
Level storage version: 0x04ABD - Anvil
Level weather: Rain time: 40564 (now: false), thunder time: 127821 (now: false)
Level game mode: Game mode: creative (ID 1). Hardcore: false. Cheats: true
Stacktrace:
at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:784)
at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:688)
at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:156)
at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:537)
at java.lang.Thread.run(Unknown Source)

-- System Details --
Details:
Minecraft Version: 1.10.2
Operating System: Windows 10 (amd64) version 10.0
Java Version: 1.8.0_162, Oracle Corporation
Java VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation
Memory: 472277016 bytes (450 MB) / 1515716608 bytes (1445 MB) up to 3758096384 bytes (3584 MB)
JVM Flags: 0 total;
IntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0
FML: MCP 9.32 Powered by Forge 12.18.3.2185 4 mods loaded, 4 mods active
States: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored
UCHIJAAAA mcp{9.19} [Minecraft Coder Pack] (minecraft.jar)
UCHIJAAAA FML{8.0.99.99} [Forge Mod Loader] (forgeSrc-1.10.2-12.18.3.2185.jar)
UCHIJAAAA Forge{12.18.3.2185} [Minecraft Forge] (forgeSrc-1.10.2-12.18.3.2185.jar)
UCHIJAAAA tofucraft{0.5.0-MC1.10.2} [TofuCraft-RmEdition] (bin)
Loaded coremods (and transformers):
GL info: ~~ERROR~~ RuntimeException: No OpenGL context found in the current thread.
Profiler Position: N/A (disabled)
Player Count: 1 / 8; [EntityPlayerMP['RemiliaMarine'/126, l='New World', x=1282.50, y=61.00, z=4762.65]]
Type: Integrated Server (map_client.txt)
Is Modded: Definitely; Client brand changed to 'fml,forge'


ソースは下記のようになっています。
ざっくり言うと、ItemSpadeを継承したItemDiamondTofuSpadeのonBlockStartBreakが呼ばれたときにServerに広さと位置、破壊されたブロックの情報を持つパケットを送り、Serverで対象の領域を求めて範囲内のブロックそれぞれに対し、プレイヤーが破壊したブロックと同じアイテムをドロップするかを確認した上で、net.minecraft.server.management.PlayerInteractionManager.tryHarvestBlockで行っている処理とほぼ同じものを行う、という仕組みです。

BatchTestCore (@modのあるクラス)
package remiliaMarine.tofu;

import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.item.Item;
import net.minecraft.item.Item.ToolMaterial;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.client.model.obj.OBJLoader;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.registry.GameRegistry;
import remiliaMarine.tofu.item.ItemDiamondTofuSpade;
import remiliaMarine.tofu.network.PacketManager;

@Mod(
modid = BatchTestCore.MODID,
name = BatchTestCore.MOD_NAME,
version = BatchTestCore.VERSION,
acceptedMinecraftVersions = BatchTestCore.MOD_ACCEPTED_MC_VERSIONS
)
public class BatchTestCore {
public static final String MODID = "tofucraft";
public static final String VERSION = "0.5.0-MC1.10.2";
public static final String MOD_NAME = "TofuCraft-RMEdition";
public static final String MOD_ACCEPTED_MC_VERSIONS = "[1.10.2]";
public static final String RES = "tofucraft:"; //Resource domain

public static Item batchTool;

@EventHandler
public void preInit(FMLPreInitializationEvent event)
{
// Register basic features
OBJLoader.INSTANCE.addDomain(BatchTestCore.MODID);

//TcItemLoader
BatchTestCore.batchTool = new ItemDiamondTofuSpade(ToolMaterial.DIAMOND).setUnlocalizedName("tofucraft:toolDiamondShovel");
GameRegistry.register(batchTool, new ResourceLocation(BatchTestCore.MODID, "toolDiamondShovel"));

if (event.getSide().isClient()) {
ModelLoader.setCustomModelResourceLocation(batchTool, 0, new ModelResourceLocation("tofucraft:toolDiamondShovel", "inventory"));
}
}

@EventHandler
public void init(FMLInitializationEvent event) {
// Register Packets
PacketManager.init(MODID);
}
}
ItemDiamondTofuSpade (ツールのクラス)
package remiliaMarine.tofu.item;

import java.util.Set;

import com.google.common.collect.ImmutableSet;

import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemSpade;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;

public class ItemDiamondTofuSpade extends ItemSpade {

private DiamondTofuToolHandler impl;

public ItemDiamondTofuSpade(ToolMaterial par2EnumToolMaterial)
{
super(par2EnumToolMaterial);
this.impl = new DiamondTofuToolHandler(this);
this.setCreativeTab(CreativeTabs.TOOLS);
}

@Override
public boolean onBlockStartBreak(ItemStack stack, BlockPos pos, EntityPlayer owner)
{
World world = owner.getEntityWorld();
if (world.isRemote)
{
this.impl.onBlockStartBreak(stack, world, world.getBlockState(pos).getBlock(), pos, owner);
}
return false;
}

@Override
public Set<String> getToolClasses(ItemStack stack)
{
return ImmutableSet.of("shovel");
}
}
DiamondTofuToolHandler
package remiliaMarine.tofu.item;

import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemTool;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.world.World;
import net.minecraftforge.fml.client.FMLClientHandler;
import remiliaMarine.tofu.network.PacketDispatcher;
import remiliaMarine.tofu.network.packet.PacketBatchDigging;

public class DiamondTofuToolHandler {

private ItemTool tool;

public DiamondTofuToolHandler(ItemTool tool)
{
this.tool = tool;
}

public void onBlockStartBreak(ItemStack stack, World world, Block blockDestroyed, BlockPos pos, EntityPlayer owner)
{
IBlockState stateDestroyed = world.getBlockState(pos);
if (tool.getStrVsBlock(stack, stateDestroyed) > 1.0F)
{
RayTraceResult rtr = FMLClientHandler.instance().getClient().objectMouseOver;
if (rtr == null) return;
Area area = getDigArea(owner, stack);
PacketDispatcher.packet(new PacketBatchDigging(area.w, area.d, area.h, pos, blockDestroyed, stateDestroyed, rtr.sideHit)).sendToServer();
}
}

public static Area getDigArea(EntityLivingBase owner, ItemStack stack)
{
Area area = new Area();
area.w = 1;
area.d = 2;
area.h = 1;
return area;
}

public static class Area
{
public int w, d, h;
}
}
BatchDigging
package remiliaMarine.tofu.item;

import java.util.Collections;
import java.util.List;

import com.google.common.collect.Lists;

import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.Blocks;
import net.minecraft.init.Enchantments;
import net.minecraft.item.ItemStack;
import net.minecraft.network.play.server.SPacketBlockChange;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.oredict.OreDictionary;
import remiliaMarine.tofu.network.packet.PacketBatchDigging;

public class BatchDigging {

private static List<List<ItemStack>> blockGroupingRegistry = Lists.newArrayList();

private ItemStack itemStackDestroyed;

static
{
addBlockGroup(new ItemStack(Blocks.DIRT, 1, 0), new ItemStack(Blocks.DIRT, 1, 2), new ItemStack(Blocks.GRASS, 1, 0));
}

public static void addBlockGroup(ItemStack... stacks)
{
List<ItemStack> group = Lists.newArrayList();
Collections.addAll(group, stacks);
blockGroupingRegistry.add(group);
}

private static Area calcArea(BlockPos pos, int w, int d, int h, EnumFacing sideHit)
{
// Convert WDH to practical XYZ
EnumFacing dir = sideHit.getOpposite();
int x1, y1, z1, x2, y2, z2;
switch (dir)
{
case UP:
x1 = -w;
x2 = w;
y1 = 0;
y2 = d;
z1 = -h;
z2 = h;
break;
case DOWN:
x1 = -w;
x2 = w;
y1 = -d;
y2 = 0;
z1 = -h;
z2 = h;
break;

case WEST:
x1 = -d;
x2 = 0;
y1 = -h;
y2 = h;
z1 = -w;
z2 = w;
break;
case EAST:
x1 = 0;
x2 = d;
y1 = -h;
y2 = h;
z1 = -w;
z2 = w;
break;

case NORTH:
x1 = -w;
x2 = w;
y1 = -h;
y2 = h;
z1 = -d;
z2 = 0;
break;

case SOUTH:
x1 = -w;
x2 = w;
y1 = -h;
y2 = h;
z1 = 0;
z2 = d;
break;

default:
return null;
}

// Add base coordinate
int bx = pos.getX();
int by = pos.getY();
int bz = pos.getZ();

return new Area(bx + x1, by + y1, bz + z1, bx + x2, by + y2, bz + z2);
}

public static AxisAlignedBB getDiggingArea(BlockPos pos, int w, int d, int h, EnumFacing sideHit)
{
Area area = calcArea(pos, w, d, h, sideHit);
return new AxisAlignedBB(area.x1, area.y1, area.z1, area.x2 + 1.0D, area.y2 + 1.0D, area.z2 + 1.0D);
}

public int w;
public int d;
public int h;
public BlockPos pos;
public Block blockId;
public IBlockState blockMeta;
public EnumFacing sideHit;
private EntityPlayer owner;

public BatchDigging(PacketBatchDigging packet, EntityPlayer owner)
{
this.w = packet.w;
this.d = packet.d;
this.h = packet.h;
this.pos = packet.pos;
this.blockId = Block.getBlockById(packet.blockId);
this.blockMeta = Block.getStateById(packet.blockMeta);
this.sideHit = packet.sideHit;
this.owner = owner;
this.itemStackDestroyed = new ItemStack(blockId, 1, blockId.damageDropped(blockMeta));
}

public static boolean isUnitedBlock(ItemStack block1, ItemStack block2)
{
if (block1.isItemEqual(block2)) return true;

for (List<ItemStack> group : blockGroupingRegistry)
{
boolean matched1 = false;
boolean matched2 = false;
for (ItemStack stack : group)
{
if (OreDictionary.itemMatches(stack, block1, false)) matched1 = true;
if (OreDictionary.itemMatches(stack, block2, false)) matched2 = true;
}
if (matched1 && matched2) return true;
}
return false;
}

public int execute()
{
World world = this.owner.worldObj;

Area area = calcArea(this.pos, this.w, this.d, this.h, this.sideHit);

int numBlocksDestroyed = 0;

for (int x = area.x1; x <= area.x2; x++)
{
for (int y = area.y1; y <= area.y2; y++)
{
for (int z = area.z1; z <= area.z2; z++)
{
if (this.destroyBlock(world, new BlockPos(x, y, z)))
{
numBlocksDestroyed++;
}
}
}
}

return numBlocksDestroyed;
}

private boolean destroyBlock(World world, BlockPos pos)
{
IBlockState blockState = world.getBlockState(pos);
Block block = blockState.getBlock();
int meta = block.getMetaFromState(blockState);

if (isUnitedBlock(itemStackDestroyed, new ItemStack(block, 1, meta)))
{
if (owner instanceof EntityPlayerMP)
{
EntityPlayerMP player = (EntityPlayerMP) owner;
int exp = ForgeHooks.onBlockBreakEvent(world, player.interactionManager.getGameType(), player, pos);
if (exp == -1)
{
return false;
}
else
{
world.playEvent(player, 2001, pos, Block.getStateId(blockState));

boolean isCreative = player.capabilities.isCreativeMode;
boolean flag;
if (isCreative)
{
flag = this.removeBlock(world, pos);
player.connection.sendPacket(new SPacketBlockChange(world, pos));
}
else
{
flag = this.removeBlock(world, pos);

if (flag)
{
block.harvestBlock(world, player, pos, blockState, null, null);
}
}

// Drop experience
if (!isCreative && flag)
{
block.dropXpOnBlockBreak(world, pos, exp);
}
return flag;
}
}
else
{
int fortune = EnchantmentHelper.getEnchantmentLevel(Enchantments.FORTUNE, owner.getHeldItemMainhand());
world.setBlockToAir(pos);
block.dropBlockAsItem(world, pos, blockState, fortune);
return true;
}
}
return false;
}

private boolean removeBlock(World world, BlockPos pos)
{
IBlockState blockstate = world.getBlockState(pos);
Block block = blockstate.getBlock();
block.onBlockHarvested(world, pos, blockstate, owner);
boolean flag = block.removedByPlayer(blockstate, world, pos, owner, true);

if (flag)
{
block.onBlockDestroyedByPlayer(world, pos, blockstate);
}

return flag;
}

private static class Area
{
final int x1, y1, z1, x2, y2, z2;

public Area(int x1, int y1, int z1, int x2, int y2, int z2)
{
this.x1 = x1;
this.y1 = y1;
this.z1 = z1;
this.x2 = x2;
this.y2 = y2;
this.z2 = z2;
}
}
}
以下パケット関連 パケットを使うほかの要素はちゃんと動いたので関係ないと思われますが念のため載せておきます
PacketManager
package remiliaMarine.tofu.network;

import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.network.simpleimpl.SimpleNetworkWrapper;
import net.minecraftforge.fml.relauncher.Side;
import remiliaMarine.tofu.network.packet.PacketBatchDigging;

public class PacketManager {
private static SimpleNetworkWrapper networkHandler = null;
private static int id = 0;

public static void init(String modId)
{
networkHandler = NetworkRegistry.INSTANCE.newSimpleChannel(modId);
registerPacket(PacketBatchDigging.class);
}

public static SimpleNetworkWrapper getNetworkHandler()
{
return networkHandler;
}

private PacketManager() {}

@SuppressWarnings("unchecked")
private static void registerPacket(Class<? extends AbstractPacket> packetClass)
{
Class<AbstractPacket> message = (Class<AbstractPacket>)packetClass;
if (MessageToServer.class.isAssignableFrom(packetClass))
{
networkHandler.registerMessage(packetClass, message, id, Side.SERVER);
//ModLog.debug("Registered Packet: %s at ID %d", packetClass.getName(), id);
id++;
}

if (MessageToClient.class.isAssignableFrom(packetClass))
{
networkHandler.registerMessage(packetClass, message, id, Side.CLIENT);
//ModLog.debug("Registered Packet: %s at ID %d", packetClass.getName(), id);
id++;
}
}

}
PacketBatchDigging
package remiliaMarine.tofu.network.packet;

import io.netty.buffer.ByteBuf;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import remiliaMarine.tofu.item.BatchDigging;
import remiliaMarine.tofu.network.AbstractPacket;
import remiliaMarine.tofu.network.MessageToServer;

public class PacketBatchDigging extends AbstractPacket implements MessageToServer {

public int w;
public int d;
public int h;
public BlockPos pos;
public int blockId;
public int blockMeta;
public EnumFacing sideHit;

public PacketBatchDigging() {}

public PacketBatchDigging(int w, int d, int h, BlockPos pos, Block block, IBlockState state, EnumFacing sideHit)
{
this.w = w;
this.d = d;
this.h = h;
this.pos = pos;
this.blockId = Block.getIdFromBlock(block);
this.blockMeta = Block.getStateId(state);
this.sideHit = sideHit;
}

@Override
public IMessage handleServerSide(EntityPlayer player)
{
new BatchDigging(this, player).execute();
return null;
}

@Override
public void encodeInto(ByteBuf buffer)
{
buffer.writeShort(w);
buffer.writeShort(d);
buffer.writeShort(h);
buffer.writeLong(this.pos.toLong());
buffer.writeShort(blockId);
buffer.writeByte(blockMeta);
buffer.writeByte(sideHit.getIndex());
}

@Override
public void decodeInto(ByteBuf buffer)
{
this.w = buffer.readShort();
this.d = buffer.readShort();
this.h = buffer.readShort();
this.pos = BlockPos.fromLong(buffer.readLong());
this.blockId = buffer.readShort();
this.blockMeta = buffer.readByte();
this.sideHit = EnumFacing.getFront(buffer.readByte());
}

}
AbstractPacket
package remiliaMarine.tofu.network;

import io.netty.buffer.ByteBuf;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.network.NetHandlerPlayServer;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;

public abstract class AbstractPacket implements IMessage, IMessageHandler<AbstractPacket, IMessage> {

public void fromBytes(ByteBuf buf)
{
this.decodeInto(buf);
}

public void toBytes(ByteBuf buf)
{
this.encodeInto(buf);
}

@Override
public IMessage onMessage(AbstractPacket message, MessageContext ctx)
{
EntityPlayer player;
IMessage reply = null;
switch (FMLCommonHandler.instance().getEffectiveSide()) {
case CLIENT:
if (message instanceof MessageToClient)
{
player = this.getClientPlayer();
if(player == null) break;
reply = ((MessageToClient)message).handleClientSide(player);
}
break;

case SERVER:
if (message instanceof MessageToServer)
{
player = ((NetHandlerPlayServer) ctx.netHandler).playerEntity;
reply = ((MessageToServer)message).handleServerSide(player);
}
break;

default:
}
return reply;
}

@SideOnly(Side.CLIENT)
private EntityPlayer getClientPlayer()
{
return Minecraft.getMinecraft().thePlayer;
}

public abstract void encodeInto(ByteBuf buffer);

public abstract void decodeInto(ByteBuf buffer);

}
MessageToServer
package remiliaMarine.tofu.network;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;

public interface MessageToServer {
public IMessage handleServerSide(EntityPlayer player);
}
PacketDispatcher
package remiliaMarine.tofu.network;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.network.simpleimpl.SimpleNetworkWrapper;

/**
* Handles PacketPipeline and dispatches a packet
*
* @author Tsuteto
*
*/
public class PacketDispatcher {
private final SimpleNetworkWrapper networkHandler;
private final AbstractPacket packet;

public static PacketDispatcher packet(AbstractPacket packet)
{
return new PacketDispatcher(packet);
}

private PacketDispatcher(AbstractPacket packet)
{
this.networkHandler = PacketManager.getNetworkHandler();
this.packet = packet;
}

public void sendToServer()
{
networkHandler.sendToServer(packet);
}

public void sendToPlayer(EntityPlayer player)
{
networkHandler.sendTo(packet, (EntityPlayerMP) player);
}

public void sendToPlayer(EntityPlayerMP player)
{
networkHandler.sendTo(packet, player);
}

public void sendToAllInDimension(int dimId)
{
networkHandler.sendToDimension(packet, dimId);
}

public void sendToAllAround(double X, double Y, double Z, int range, int dimensionId)
{
NetworkRegistry.TargetPoint targetPoint = new NetworkRegistry.TargetPoint(dimensionId, X, Y, Z, range);
networkHandler.sendToAllAround(packet, targetPoint);
}

public void sendPacketToAllPlayers()
{
networkHandler.sendToAll(packet);
}

}

上のソースと同じ内容です
main.zip
(50.47 KiB) ダウンロード数: 0 回


追加で質問なのですが、処理を追いかけるとtryHarvestBlockを実行するようになっていますがServer側ではonBlockStartBreakが呼ばれないようです。ブロックが破壊された際の処理の流れを教えていただけると嬉しいです。

よろしくお願いいたします。
最後に編集したユーザー remiliaMarine [ 2018年3月26日(月) 16:23 ], 累計 1 回
ゲリラMOD最高!アドオンですhttp://forum.minecraftuser.jp/viewtopic.php?f=13&t=35057 豆腐Craftのマイクラ1.10.2版も作業中
ツイッター始めました。更新が遅い!と思ったら見てみると今何やってるかわかるかもしれません。
アバター
remiliaMarine
ID:ae703c41
石掘り
 
記事: 77
登録日時: 2017年8月27日(日) 17:18

  • (PostNo.315020)

Re: [1.10.2]一括採掘で発生するItemEntityに関してConcurrentModificationが出る

投稿記事by defeatedcrow » 2018年3月24日(土) 15:31

クラッシュレポートを見た感じだと、
>at net.minecraft.entity.item.EntityItem.searchForOtherItemsNearby(EntityItem.java:187)
とあるので、あなたのModアイテムの破壊処理に並行して、バニラで近いドロップアイテム同士をまとめる処理が起きているようですね…
Iteratorは回転中に含む要素を変化させると落ちるので、バニラのこの処理がIteratorを回している最中にドロップの追加か削除が起きてるんじゃないかな~と予想します
(この種のクラッシュははじめてみるのですが、そんな感じがします)

対策ですが、dig処理時、ブロック1つ破壊するごとにドロップさせておくと多分こうなるので、
・EntityItemにpickupDelayを設定(一定時間拾えないクールタイムですが、ドロップをまとめる処理に対しても働きます)
・dig処理時にドロップ予定のItemStackを一旦Listなどに保持しておいて、破壊完了後にまとめてドロップ
あたりで対処できるんじゃないかなーと思います
カラスの敗残兵です。AppleMilkTea他、少々のMODを作成しています。
トピック: AppleMilkTea(~1.7.10) / HeatAndClimate(1.10.2)
作者Wiki / github / twitter
アバター
defeatedcrow
ID:0790d4e9
ラピスラズリ収集家
 
記事: 1038
登録日時: 2014年1月08日(水) 13:48
お住まい: 北関東

  • (PostNo.315023)

Re: [1.10.2]一括採掘で発生するItemEntityに関してConcurrentModificationが出る

投稿記事by remiliaMarine » 2018年3月24日(土) 17:07

defeatedcrow さんが書きました:クラッシュレポートを見た感じだと、
>at net.minecraft.entity.item.EntityItem.searchForOtherItemsNearby(EntityItem.java:187)
とあるので、あなたのModアイテムの破壊処理に並行して、バニラで近いドロップアイテム同士をまとめる処理が起きているようですね…
Iteratorは回転中に含む要素を変化させると落ちるので、バニラのこの処理がIteratorを回している最中にドロップの追加か削除が起きてるんじゃないかな~と予想します
(この種のクラッシュははじめてみるのですが、そんな感じがします)

対策ですが、dig処理時、ブロック1つ破壊するごとにドロップさせておくと多分こうなるので、
・EntityItemにpickupDelayを設定(一定時間拾えないクールタイムですが、ドロップをまとめる処理に対しても働きます)
・dig処理時にドロップ予定のItemStackを一旦Listなどに保持しておいて、破壊完了後にまとめてドロップ
あたりで対処できるんじゃないかなーと思います


お返事ありがとうございます。

まとめてドロップするようにしたところ、net.minecraft.world.chunk.Chunk.entityListsに関するクラッシュの頻度は結構下がったのですが、
エンティティを動かす時に接触するエンティティを調べようとして例外が発生しました。
---- Minecraft Crash Report ----
// Uh... Did I do that?

Time: 18/03/24 16:57
Description: Ticking entity

java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at com.google.common.collect.Iterators$3.next(Iterators.java:163)
at net.minecraft.world.chunk.Chunk.getEntitiesWithinAABBForEntity(Chunk.java:951)
at net.minecraft.world.World.getEntitiesInAABBexcluding(World.java:3246)
at net.minecraft.world.World.getEntitiesWithinAABBExcludingEntity(World.java:3226)
at net.minecraft.world.World.getCollisionBoxes(World.java:1372)
at net.minecraft.entity.Entity.moveEntity(Entity.java:746)
at net.minecraft.entity.item.EntityItem.onUpdate(EntityItem.java:125)
at net.minecraft.world.World.updateEntityWithOptionalForce(World.java:2118)
at net.minecraft.world.WorldServer.updateEntityWithOptionalForce(WorldServer.java:875)
at net.minecraft.world.World.updateEntity(World.java:2085)
at net.minecraft.world.World.updateEntities(World.java:1898)
at net.minecraft.world.WorldServer.updateEntities(WorldServer.java:647)
at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:784)
at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:688)
at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:156)
at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:537)
at java.lang.Thread.run(Unknown Source)


A detailed walkthrough of the error, its code path and all known details is as follows:
---------------------------------------------------------------------------------------

-- Head --
Thread: Server thread
Stacktrace:
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at com.google.common.collect.Iterators$3.next(Iterators.java:163)
at net.minecraft.world.chunk.Chunk.getEntitiesWithinAABBForEntity(Chunk.java:951)
at net.minecraft.world.World.getEntitiesInAABBexcluding(World.java:3246)
at net.minecraft.world.World.getEntitiesWithinAABBExcludingEntity(World.java:3226)
at net.minecraft.world.World.getCollisionBoxes(World.java:1372)
at net.minecraft.entity.Entity.moveEntity(Entity.java:746)
at net.minecraft.entity.item.EntityItem.onUpdate(EntityItem.java:125)
at net.minecraft.world.World.updateEntityWithOptionalForce(World.java:2118)
at net.minecraft.world.WorldServer.updateEntityWithOptionalForce(WorldServer.java:875)
at net.minecraft.world.World.updateEntity(World.java:2085)

-- Entity being ticked --
Details:
Entity Type: Item (net.minecraft.entity.item.EntityItem)
Entity ID: 39243
Entity Name: item.tile.dirt.default
Entity's Exact location: 1281.88, 61.00, 7741.88
Entity's Block location: World: (1281,61,7741), Chunk: (at 1,3,13 in 80,483; contains blocks 1280,0,7728 to 1295,255,7743), Region: (2,15; contains chunks 64,480 to 95,511, blocks 1024,0,7680 to 1535,255,8191)
Entity's Momentum: 0.00, -0.04, 0.00
Entity's Passengers: []
Entity's Vehicle: ~~ERROR~~ NullPointerException: null
Stacktrace:
at net.minecraft.world.World.updateEntities(World.java:1898)
at net.minecraft.world.WorldServer.updateEntities(WorldServer.java:647)

-- Affected level --
Details:
Level name: New World
All players: 1 total; [EntityPlayerMP['RemiliaMarine'/140, l='New World', x=1281.43, y=61.00, z=7751.65]]
Chunk stats: ServerChunkCache: 889 Drop: 0
Level seed: -6128968327004720996
Level generator: ID 01 - flat, ver 0. Features enabled: true
Level generator options: 3;minecraft:bedrock,59*minecraft:stone,3*minecraft:dirt,minecraft:grass;1;village,biome_1,decoration,stronghold,mineshaft,dungeon
Level spawn location: World: (1290,4,190), Chunk: (at 10,0,14 in 80,11; contains blocks 1280,0,176 to 1295,255,191), Region: (2,0; contains chunks 64,0 to 95,31, blocks 1024,0,0 to 1535,255,511)
Level time: 45340 game time, 79340 day time
Level dimension: 0
Level storage version: 0x04ABD - Anvil
Level weather: Rain time: 28056 (now: false), thunder time: 115313 (now: false)
Level game mode: Game mode: creative (ID 1). Hardcore: false. Cheats: true
Stacktrace:
at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:784)
at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:688)
at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:156)
at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:537)
at java.lang.Thread.run(Unknown Source)

-- System Details --
Details:
Minecraft Version: 1.10.2
Operating System: Windows 10 (amd64) version 10.0
Java Version: 1.8.0_162, Oracle Corporation
Java VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation
Memory: 844360904 bytes (805 MB) / 1422393344 bytes (1356 MB) up to 3758096384 bytes (3584 MB)
JVM Flags: 0 total;
IntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0
FML: MCP 9.32 Powered by Forge 12.18.3.2185 4 mods loaded, 4 mods active
States: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored
UCHIJAAAA mcp{9.19} [Minecraft Coder Pack] (minecraft.jar)
UCHIJAAAA FML{8.0.99.99} [Forge Mod Loader] (forgeSrc-1.10.2-12.18.3.2185.jar)
UCHIJAAAA Forge{12.18.3.2185} [Minecraft Forge] (forgeSrc-1.10.2-12.18.3.2185.jar)
UCHIJAAAA tofucraft{0.5.0-MC1.10.2} [TofuCraft-RmEdition] (bin)
Loaded coremods (and transformers):
GL info: ~~ERROR~~ RuntimeException: No OpenGL context found in the current thread.
Profiler Position: N/A (disabled)
Player Count: 1 / 8; [EntityPlayerMP['RemiliaMarine'/140, l='New World', x=1281.43, y=61.00, z=7751.65]]
Type: Integrated Server (map_client.txt)
Is Modded: Definitely; Client brand changed to 'fml,forge'

そのほかにも、net.minecraft.entity.EntityTracker.trackedEntitiesを参照する際にも例外が発生する様です。こちらはプレイヤーに関するものであり、アイテムをまとめる処理の他にも問題があったようですので、もう少し原因を探ってみます。
---- Minecraft Crash Report ----
// Oops.

Time: 18/03/24 16:08
Description: Exception ticking world

java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(Unknown Source)
at java.util.HashMap$KeyIterator.next(Unknown Source)
at net.minecraft.entity.EntityTracker.sendLeashedEntitiesInChunk(EntityTracker.java:385)
at net.minecraft.server.management.PlayerChunkMapEntry.sentToPlayers(PlayerChunkMapEntry.java:165)
at net.minecraft.server.management.PlayerChunkMap.tick(PlayerChunkMap.java:212)
at net.minecraft.world.WorldServer.tick(WorldServer.java:231)
at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:773)
at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:688)
at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:156)
at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:537)
at java.lang.Thread.run(Unknown Source)


A detailed walkthrough of the error, its code path and all known details is as follows:
---------------------------------------------------------------------------------------

-- Head --
Thread: Server thread
Stacktrace:
at java.util.HashMap$HashIterator.nextNode(Unknown Source)
at java.util.HashMap$KeyIterator.next(Unknown Source)
at net.minecraft.entity.EntityTracker.sendLeashedEntitiesInChunk(EntityTracker.java:385)
at net.minecraft.server.management.PlayerChunkMapEntry.sentToPlayers(PlayerChunkMapEntry.java:165)
at net.minecraft.server.management.PlayerChunkMap.tick(PlayerChunkMap.java:212)
at net.minecraft.world.WorldServer.tick(WorldServer.java:231)

-- Affected level --
Details:
Level name: New World
All players: 1 total; [EntityPlayerMP['RemiliaMarine'/203, l='New World', x=1282.53, y=61.00, z=5840.16]]
Chunk stats: ServerChunkCache: 877 Drop: 0
Level seed: -6128968327004720996
Level generator: ID 01 - flat, ver 0. Features enabled: true
Level generator options: 3;minecraft:bedrock,59*minecraft:stone,3*minecraft:dirt,minecraft:grass;1;village,biome_1,decoration,stronghold,mineshaft,dungeon
Level spawn location: World: (1290,4,190), Chunk: (at 10,0,14 in 80,11; contains blocks 1280,0,176 to 1295,255,191), Region: (2,0; contains chunks 64,0 to 95,31, blocks 1024,0,0 to 1535,255,511)
Level time: 37288 game time, 60288 day time
Level dimension: 0
Level storage version: 0x04ABD - Anvil
Level weather: Rain time: 36108 (now: false), thunder time: 123365 (now: false)
Level game mode: Game mode: creative (ID 1). Hardcore: false. Cheats: true
Stacktrace:
at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:773)
at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:688)
at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:156)
at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:537)
at java.lang.Thread.run(Unknown Source)

-- System Details --
Details:
Minecraft Version: 1.10.2
Operating System: Windows 10 (amd64) version 10.0
Java Version: 1.8.0_162, Oracle Corporation
Java VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation
Memory: 1012973648 bytes (966 MB) / 1796734976 bytes (1713 MB) up to 3758096384 bytes (3584 MB)
JVM Flags: 0 total;
IntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0
FML: MCP 9.32 Powered by Forge 12.18.3.2185 4 mods loaded, 4 mods active
States: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored
UCHIJAAAA mcp{9.19} [Minecraft Coder Pack] (minecraft.jar)
UCHIJAAAA FML{8.0.99.99} [Forge Mod Loader] (forgeSrc-1.10.2-12.18.3.2185.jar)
UCHIJAAAA Forge{12.18.3.2185} [Minecraft Forge] (forgeSrc-1.10.2-12.18.3.2185.jar)
UCHIJAAAA tofucraft{0.5.0-MC1.10.2} [TofuCraft-RmEdition] (bin)
Loaded coremods (and transformers):
GL info: ~~ERROR~~ RuntimeException: No OpenGL context found in the current thread.
Profiler Position: N/A (disabled)
Player Count: 1 / 8; [EntityPlayerMP['RemiliaMarine'/203, l='New World', x=1282.53, y=61.00, z=5840.16]]
Type: Integrated Server (map_client.txt)
Is Modded: Definitely; Client brand changed to 'fml,forge'
---- Minecraft Crash Report ----
// Ooh. Shiny.

Time: 18/03/24 16:17
Description: Exception in server tick loop

java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(Unknown Source)
at java.util.HashMap$KeyIterator.next(Unknown Source)
at net.minecraft.entity.EntityTracker.updateTrackedEntities(EntityTracker.java:285)
at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:796)
at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:688)
at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:156)
at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:537)
at java.lang.Thread.run(Unknown Source)


A detailed walkthrough of the error, its code path and all known details is as follows:
---------------------------------------------------------------------------------------

-- System Details --
Details:
Minecraft Version: 1.10.2
Operating System: Windows 10 (amd64) version 10.0
Java Version: 1.8.0_162, Oracle Corporation
Java VM Version: Java HotSpot(TM) 64-Bit Server VM (mixed mode), Oracle Corporation
Memory: 903418128 bytes (861 MB) / 1533542400 bytes (1462 MB) up to 3758096384 bytes (3584 MB)
JVM Flags: 0 total;
IntCache: cache: 0, tcache: 0, allocated: 0, tallocated: 0
FML: MCP 9.32 Powered by Forge 12.18.3.2185 4 mods loaded, 4 mods active
States: 'U' = Unloaded 'L' = Loaded 'C' = Constructed 'H' = Pre-initialized 'I' = Initialized 'J' = Post-initialized 'A' = Available 'D' = Disabled 'E' = Errored
UCHIJAAAA mcp{9.19} [Minecraft Coder Pack] (minecraft.jar)
UCHIJAAAA FML{8.0.99.99} [Forge Mod Loader] (forgeSrc-1.10.2-12.18.3.2185.jar)
UCHIJAAAA Forge{12.18.3.2185} [Minecraft Forge] (forgeSrc-1.10.2-12.18.3.2185.jar)
UCHIJAAAA tofucraft{0.5.0-MC1.10.2} [TofuCraft-RmEdition] (bin)
Loaded coremods (and transformers):
GL info: ~~ERROR~~ RuntimeException: No OpenGL context found in the current thread.
Profiler Position: N/A (disabled)
Player Count: 1 / 8; [EntityPlayerMP['RemiliaMarine'/128, l='New World', x=1282.57, y=61.00, z=5888.11]]
Type: Integrated Server (map_client.txt)
Is Modded: Definitely; Client brand changed to 'fml,forge'
ゲリラMOD最高!アドオンですhttp://forum.minecraftuser.jp/viewtopic.php?f=13&t=35057 豆腐Craftのマイクラ1.10.2版も作業中
ツイッター始めました。更新が遅い!と思ったら見てみると今何やってるかわかるかもしれません。
アバター
remiliaMarine
ID:ae703c41
石掘り
 
記事: 77
登録日時: 2017年8月27日(日) 17:18

  • (PostNo.315029)

Re: [1.10.2]一括採掘で発生するItemEntityに関してConcurrentModificationが出る

投稿記事by remiliaMarine » 2018年3月24日(土) 20:48

プレイヤーが変なタイミングで追加、削除されているのではないかと思い、パケット関連も確認しなおしてみたのですが、プレイヤーをリスポーンさせたりするような、怪しい点は見つかりませんでした。呼び出し階層を確認しても、特にそれらしい個所は見つからず再び行き詰っている状態です。

もし一気にブロックが破壊されるときの注意点などがありましたらご教示くだされば幸いです。
ゲリラMOD最高!アドオンですhttp://forum.minecraftuser.jp/viewtopic.php?f=13&t=35057 豆腐Craftのマイクラ1.10.2版も作業中
ツイッター始めました。更新が遅い!と思ったら見てみると今何やってるかわかるかもしれません。
アバター
remiliaMarine
ID:8723cd50
石掘り
 
記事: 77
登録日時: 2017年8月27日(日) 17:18

  • (PostNo.315064)

Re: [1.10.2]一括採掘で発生するItemEntityに関してConcurrentModificationが出る

投稿記事by defeatedcrow » 2018年3月25日(日) 18:15

EntityItemのクールタイムも合わせてやってみたほうがいいのかもしれませんね(推測ですが)
なぜEntityPlayerを疑っているのかはわかりませんが、
クラッシュレポートを見る限りでは
コード: 全て選択
at net.minecraft.world.World.getCollisionBoxes(World.java:1372)
at net.minecraft.entity.Entity.moveEntity(Entity.java:746)
at net.minecraft.entity.item.EntityItem.onUpdate(EntityItem.java:125)

この辺、EntityItemの当たり判定処理が依然何かやってそうですので、処理をたどってみたら良いんじゃないかと思います

こういった個々の機能については、そもそも国内にModder何人いるんだよという状況では対処法に既知情報は殆ど無い上
ぶっちゃけこの機能に関しては貴方自身が先駆のジャンルです
(一応、一括破壊は自分も実装していますが、パケットもスレッド処理も使いませんしアルゴリズムもクソなやつしか作れないので)
みんなそれぞれ手探り状態なので、教示役を買って出られるような余裕のあるModder多分居ないです

あとコード類はスポイラーでインデントが崩壊するよりはcodeでくくったほうが見やすいかと…
カラスの敗残兵です。AppleMilkTea他、少々のMODを作成しています。
トピック: AppleMilkTea(~1.7.10) / HeatAndClimate(1.10.2)
作者Wiki / github / twitter
アバター
defeatedcrow
ID:0790d4e9
ラピスラズリ収集家
 
記事: 1038
登録日時: 2014年1月08日(水) 13:48
お住まい: 北関東

  • (PostNo.315070)

Re: [1.10.2]一括採掘で発生するItemEntityに関してConcurrentModificationが出る

投稿記事by remiliaMarine » 2018年3月25日(日) 19:01

defeatedcrow さんが書きました:EntityItemのクールタイムも合わせてやってみたほうがいいのかもしれませんね(推測ですが)
なぜEntityPlayerを疑っているのかはわかりませんが、
クラッシュレポートを見る限りでは
コード: 全て選択
at net.minecraft.world.World.getCollisionBoxes(World.java:1372)
at net.minecraft.entity.Entity.moveEntity(Entity.java:746)
at net.minecraft.entity.item.EntityItem.onUpdate(EntityItem.java:125)

この辺、EntityItemの当たり判定処理が依然何かやってそうですので、処理をたどってみたら良いんじゃないかと思います

EntityItemのクールタイムはもともと10 tick設定されていて、一番最初に試しに20, 40 tickにして見たのですが、あまり変化が無いようでした。

EntityPlayerを疑っていたのは、EntityTracker.trackedEntitiesの要素のEntityTrackerEntryのEntityが、確認なしにEntityPlayerにキャストされている箇所を見たような気がしたためです。ただ、さらに良く調べてみたら、他のエンティティが含まれていないとは言えなさそうだったので多分勘違いだと思います。

defeatedcrow さんが書きました:こういった個々の機能については、そもそも国内にModder何人いるんだよという状況では対処法に既知情報は殆ど無い上
ぶっちゃけこの機能に関しては貴方自身が先駆のジャンルです
(一応、一括破壊は自分も実装していますが、パケットもスレッド処理も使いませんしアルゴリズムもクソなやつしか作れないので)
みんなそれぞれ手探り状態なので、教示役を買って出られるような余裕のあるModder多分居ないです

単純な仕組みで同じものを実装できるならそれに越したことはないので、どうしてもダメそうなら基本的なところから作り直すかもしれません。
返信を読んで思いついたのですが、パケット受信時の処理は、別スレッドでタイミングを合わせずに行っているのかもしれないので、タイミングを合わせるかパケットを使わない方法を試してみます。

defeatedcrow さんが書きました:あとコード類はスポイラーでインデントが崩壊するよりはcodeでくくったほうが見やすいかと…

次コードを含む投稿をするときにはそう致します。教えてくださってありがとうございます。
ゲリラMOD最高!アドオンですhttp://forum.minecraftuser.jp/viewtopic.php?f=13&t=35057 豆腐Craftのマイクラ1.10.2版も作業中
ツイッター始めました。更新が遅い!と思ったら見てみると今何やってるかわかるかもしれません。
アバター
remiliaMarine
ID:8723cd50
石掘り
 
記事: 77
登録日時: 2017年8月27日(日) 17:18

  • (PostNo.315098)

Re: [1.10.2]一括採掘で発生するItemEntityに関してConcurrentModificationが出る

投稿記事by remiliaMarine » 2018年3月26日(月) 16:23

処理を追っていたのですが、確かめるのが早いということで手っ取り早く実験したところ、1.7.10ではパケットの処理がServetTickと関連しているが1.10.2では独立しているという結論が出ました。(初めから実験してれば良かった)

ServerTickEventのPhase.STARTからhandleServerSideが実行されるまでの時間をt, そのtickにかかった時間をTとして、t/Tの値をプロットしてみると、次のグラフのようになりました。
画像

1.7.10
大体がtickの初めのほうに集中していることから、やはりtickとパケットのhandleServerSideは同期していると考えられます。右のほうに大きく離れている値はラグった際のものだと思われます。少し右にずれている値は、tickの後半の処理が短く済んだためだと思います。
画像

1.10.2
なんとなくtickの前半に集中しているような気もしますが、それでも1.7.10に比べればずっとばらけているので、実質同期していないと考えてよいと思います。
画像

具体的にソースを見て処理を追ったわけではありませんが、1.10.2では、パケットの処理とtick処理は同期していないと分かったので、一般に、パケットの処理を実際に行うタイミングは、handleServerSide からではなく、tickに同期したEventを用いて実行するのがよいかと思います。

今回の一括採掘では、一旦パケットをリストに入れておいて、UpdateTimeLightAndEntityesの実行後であるServerTickEventのPhase.ENDのタイミングで実行するようにしました。
具体的な変更点としては、BatchDiggingへの
コード: 全て選択
    private static List<Tuple<PacketBatchDigging, EntityPlayer>> bookedBatch= Lists.newArrayList();

   public static void bookBatch(PacketBatchDigging packet, EntityPlayer owner) {
      bookedBatch.add(new Tuple<PacketBatchDigging, EntityPlayer>(packet, owner));
   }
   
   public static void onProperTiming() {
      while (!bookedBatch.isEmpty()) {
         Tuple<PacketBatchDigging, EntityPlayer> elem = bookedBatch.remove(0);
         new BatchDigging(elem.getFirst(), elem.getSecond()).execute();
      }
   }
の追加、パケットからはexecute()の代わりにbookBatch()を呼び出し、Eventシステムを使ってonProperTiming()を実行しています。無駄にBatchDiggingのインスタンスを生成していて効率が悪そうですが、とりあえず動くようにすることを優先しました。

defeatedcrowさん、おかげさまで無事解決することができました。ありがとうございました。
ゲリラMOD最高!アドオンですhttp://forum.minecraftuser.jp/viewtopic.php?f=13&t=35057 豆腐Craftのマイクラ1.10.2版も作業中
ツイッター始めました。更新が遅い!と思ったら見てみると今何やってるかわかるかもしれません。
アバター
remiliaMarine
ID:8723cd50
石掘り
 
記事: 77
登録日時: 2017年8月27日(日) 17:18

  • (PostNo.315103)

Re: 【解決済】[1.10.2]一括採掘で発生するItemEntityに関しConcurrentModificatio

投稿記事by defeatedcrow » 2018年3月26日(月) 19:20

おお、すごい検証データ

こちらこそ大してお役に立てていなくてすみません
解決したようでよかったです
カラスの敗残兵です。AppleMilkTea他、少々のMODを作成しています。
トピック: AppleMilkTea(~1.7.10) / HeatAndClimate(1.10.2)
作者Wiki / github / twitter
アバター
defeatedcrow
ID:0790d4e9
ラピスラズリ収集家
 
記事: 1038
登録日時: 2014年1月08日(水) 13:48
お住まい: 北関東

  • (PostNo.315105)

Re: 【解決済】[1.10.2]一括採掘で発生するItemEntityに関しConcurrentModificatio

投稿記事by A.K. » 2018年3月26日(月) 20:43

解決済みとなっているので、不要のコメントですが、検索されてくる方のためにコメントを残しておきます。

Item#onBlockStartBreakメソッドのタイミングで一括破壊対象を決定しサーバ側で破壊処理を行いたいという要求に対して、パケットを利用し、Tick処理で対処されていますが、要求された機能に対して、パケットもTick処理も必要ではありません。

そもそも、Item#onBlockStartBreakメソッドはClientとServerの両方で呼ばれるメソッドですので、Server側に限定して、破壊対象を選択し、
そのままPlayerInteractionManager#tryHarvestBlockを呼べば期待通りの処理が行えます。
アイテムの固有機能ではなく、もっと汎用的に実装されたい場合は、PlayerInteractEvent#LeftClickBlockというイベントを購読し、その中で必要な処理を行うことで、同様のことが実現できます。
もしも、Tick毎に破壊していきたいのであれば、破壊対象をコレクションとして保持するタスククラスを作成し、破壊対象を確定したのち、タスクを生成して、タスクのキューに登録、Tick処理イベントを購読し、キューに積まれたタスクの破壊対象コレクションの要素を取得して破壊処理を行うなどの実装で実現できるでしょう。

以下に上記のような実装のコードが置いてあります。
https://github.com/aksource/ChainDestruction
もじんぐしたい。。。。
アバター
A.K.
ID:b6f41f5f
ラピスラズリ収集家
 
記事: 1409
登録日時: 2012年9月03日(月) 19:34


Return to 質問:開発・制作関連

x