Jump to content

[1.12] Threading crash when accessing chunks from GUI


Recommended Posts

Posted

Hi all,

 

I have a GUI in my mod that displays a map of nearby chunks with their load status and whether or not they are "force loaded" by my mod. In 1.7, it worked fine, in 1.12, it occasionally crashes with this error:

 

net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:835)

 

(Sorry, I don't have the full stack trace on hand, I'll add it when I can)

 

This error actually happens in the main thread, not my GUI code, and I've had it before when doing actions in other threads that are not thread-safe.

 

The code I'm running in my GUI is this:

 

private void drawChunkMap(final int i, final int j) {

		if (Mill.serverWorlds.isEmpty()) {
			return;
		}

		final int windowXstart = (width - getXSize()) / 2;
		final int windowYstart = (height - getYSize()) / 2;

		final World world = Mill.serverWorlds.get(0).world;
		final MillWorld mw = Mill.serverWorlds.get(0);

		GL11.glDisable(2896 /* GL_LIGHTING */);
		GL11.glDisable(2929 /* GL_DEPTH_TEST */);

		final int startX = (getXSize() - (chunkMapSizeInBlocks / 8)) / 2;
		final int startY = (getYSize() - (chunkMapSizeInBlocks / 8)) / 2;

		final int posXstart = (player.chunkCoordX * 16) - (chunkMapSizeInBlocks / 2);
		final int posZstart = (player.chunkCoordZ * 16) - (chunkMapSizeInBlocks / 2);

		final int mouseX = (((i - startX - windowXstart) / 2) * 16) + posXstart;
		final int mouseZ = (((j - startY - windowYstart) / 2) * 16) + posZstart;

		drawGradientRect(startX - 2, startY - 2, startX + (chunkMapSizeInBlocks / 8) + 2,
				startY + (chunkMapSizeInBlocks / 8) + 2, 0x20000000, 0x20000000);

		final ArrayList<String> labels = new ArrayList<String>();

		for (int x = posXstart; x < (posXstart + chunkMapSizeInBlocks); x += 16) {
			for (int z = posZstart; z < (posZstart + chunkMapSizeInBlocks); z += 16) {

				int colour = 0;
				if (!world.isChunkGeneratedAt(x / 16, z / 16)) {
					colour = 0x40111111;
				} else {
					final Chunk chunk = world.getChunkProvider().provideChunk(x / 16, z / 16);
					if (chunk.isLoaded()) {
						colour = 0xc000ff00;
					} else {
						colour = 0xc0ff0000;
					}
					drawPixel(startX + ((x - posXstart) / 8), startY + ((z - posZstart) / 8), colour);

					if ((mouseX == x) && (mouseZ == z)) {
						labels.add(MLN.string("chunk.chunkcoords", "" + (x / 16) + "/" + (z / 16)));
					}
				}

			}
		}

		// copy to avoid ConcurrentModificationException
		final ArrayList<Building> buildings = new ArrayList<Building>(mw.allBuildings());

		for (final Building b : buildings) {
			if (b.isTownhall && (b.winfo != null) && (b.villageType != null)) {
				for (int x = b.winfo.mapStartX; x < (b.winfo.mapStartX + b.winfo.length); x += 16) {
					for (int z = b.winfo.mapStartZ; z < (b.winfo.mapStartZ + b.winfo.width); z += 16) {
						if ((x >= posXstart) && (x <= (posXstart + chunkMapSizeInBlocks)) && (z >= posZstart)
								&& (z <= (posZstart + chunkMapSizeInBlocks))) {
							int colour;

							if (b.villageType.lonebuilding) {
								colour = 0xf0990099;
							} else {
								colour = 0xf00000ff;
							}

							drawPixel(startX + ((x - posXstart) / 8) + 1, startY + ((z - posZstart) / 8) + 1, colour);

							if ((mouseX == x) && (mouseZ == z)) {
								labels.add(MLN.string("chunk.village", b.getVillageQualifiedName()));
							}
						}
					}
				}
			}
		}

		boolean labelForced = false;

		for (final ChunkPos cc : ForgeChunkManager.getPersistentChunksFor(world).keys()) {
			if (((cc.x * 16) >= posXstart) && ((cc.x * 16) <= (posXstart + chunkMapSizeInBlocks))
					&& ((cc.z * 16) >= posZstart) && ((cc.z * 16) <= (posZstart + chunkMapSizeInBlocks))) {
				drawPixel(startX + (((cc.x * 16) - posXstart) / 8), startY + (((cc.z * 16) - posZstart) / 8) + 1,
						0xf0ffffff);

				if ((mouseX == (cc.x * 16)) && (mouseZ == (cc.z * 16)) && !labelForced) {
					labels.add(MLN.string("chunk.chunkforced"));
					labelForced = true;
				}
			}
		}

		if (!labels.isEmpty()) {
			int stringlength = 0;

			for (final String s : labels) {
				final int w = fontRenderer.getStringWidth(s);
				if (w > stringlength) {
					stringlength = w;
				}
			}

			drawGradientRect((i - 3 - windowXstart) + 10, j - 3 - windowYstart,
					((i + stringlength + 3) - windowXstart) + 10, (j + (11 * labels.size())) - windowYstart, 0xc0000000,
					0xc0000000);

			for (int si = 0; si < labels.size(); si++) {
				fontRenderer.drawString(labels.get(si), (i - windowXstart) + 10, (j - windowYstart) + (11 * si),
						0x909090);
			}

		}

		GL11.glEnable(2896 /* GL_LIGHTING */);
		GL11.glEnable(2929 /* GL_DEPTH_TEST */);

	}

 

Can anybody confirm that it is my calls to the chunk provider that are forbidden from a GUI? And if that's the case, is there a recommanded way of dealing with this? I could have the data be pre-calculated in the main thread, but it would be an ugly hack...

 

Thanks

Posted (edited)

The full error would be good. But I am suspecting a dictionary change while you are reading the chunks. But I can only confirm with the whole error.

Edit: I am stupid. You are not modifying anything. But yet, a full error would help :D

Edited by SatyPardus
Posted

I realise I'm not supposed to do this, but as a debugging tool for SP-only usage it worked fine in 1.7. I'm just wondering if anybody can confirm that yes, you can no longer access chunks for other threads than the main one in 1.12. The Client-Server thing is not the real issue there, I assume I'd have the same problems if I was running that code in a thread split from the main server thread.

 

After that I could of course fix it by using packets, it's just a lot more work for a very side feature.

 

And those static references are because my mod requires a large amount of world-specific data that can't be accessed via Minecraft means like DimensionManager.

Posted

It worked in 1.7. I've had thousands of players over the four years of the 1.7 version with this bug never reported, I saw it myself and had it reported to me several times for 1.12 in two days of having an alpha out.

 

WorldSavedData is great but not suitable for saving the amount of data I need. It's not the perfect solution for everything. For one thing it requires saving everything to NBT, which is not cool for saving thousands of building points for planned constructions. Plus it does't allow you to finally control what gets sent to the client and what doesn't; in my case just sending everything would be a waste of bandwidth.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.