Jump to content

[1.8] [SOLVED] Waiting for a message to be processed


SnowyEgret

Recommended Posts

I am having problems with the asynchronous nature of a message.

 

Setting sand to dirt like this:

world.setBlockState(pos, Blocks.dirt.getDefaultState());
System.out.println("state=" + world.getBlockState(pos));

 

results in:

[15:05:48] [server thread/INFO] [sTDOUT]: [ds.plato.Test:test:33]: state=minecraft:dirt[snowy=false,variant=dirt]

 

where as:

Plato.network.sendToServer(new SetBlockStateMessage(pos, Blocks.dirt.getDefaultState()));
System.out.println("state=" + world.getBlockState(pos));

 

results in:

[14:58:06] [server thread/INFO] [sTDOUT]: [ds.plato.Test:test:33]: state=minecraft:sand[variant=sand]
[14:58:06] [server thread/INFO] [sTDOUT]: [ds.plato.network.SetBlockStateMessageHandler:processMessage:25]: state=minecraft:dirt[snowy=false,variant=dirt]

 

How can I wait for the server to process the message?

 

Here is my class SetBlockStateMessageHandler:

 

package ds.plato.network;

import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;

//https://github.com/TheGreyGhost/MinecraftByExample/tree/master/src/main/java/minecraftbyexample/mbe60_network_messages
public class SetBlockStateMessageHandler implements IMessageHandler<SetBlockStateMessage, IMessage> {

@Override
public IMessage onMessage(final SetBlockStateMessage message, MessageContext ctx) {
	final EntityPlayerMP player = ctx.getServerHandler().playerEntity;
	player.getServerForPlayer().addScheduledTask(new Runnable() {
		public void run() {
			processMessage(message, player);
		}
	});
	return null;
}

private void processMessage(SetBlockStateMessage message, EntityPlayerMP player) {
	System.out.println("state="+message.getState());
	World world = player.worldObj;
	world.setBlockState(new BlockPos(message.getX(), message.getY(), message.getZ()), message.getState());
}
}

Link to comment
Share on other sites

Packet is like one way ticket, you can't expect something to come back if you don't make it come back.

 

In onMessage instead of returning null you can return a packet that will be sent from server->client.

That would be figuratively "waiting for answer".

1.7.10 is no longer supported by forge, you are on your own.

Link to comment
Share on other sites

Ok, now my handler returns a message:

 

package ds.plato.network;

import io.netty.buffer.ByteBuf;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;

//https://github.com/TheGreyGhost/MinecraftByExample/tree/master/src/main/java/minecraftbyexample/mbe60_network_messages
public class SetBlockStateMessageHandler implements IMessageHandler<SetBlockStateMessage, IMessage> {

@Override
public IMessage onMessage(final SetBlockStateMessage message, MessageContext ctx) {
	final EntityPlayerMP player = ctx.getServerHandler().playerEntity;
	player.getServerForPlayer().addScheduledTask(new Runnable() {
		public void run() {
			processMessage(message, player);
		}
	});
	return new DoneMessage();
}

private void processMessage(SetBlockStateMessage message, EntityPlayerMP player) {
	System.out.println("state="+message.getState());
	World world = player.worldObj;
	world.setBlockState(new BlockPos(message.getX(), message.getY(), message.getZ()), message.getState());
}
}

 

I have registered a handler:

 

	network.registerMessage(DoneMessageHandler.class, DoneMessage.class, 4, Side.CLIENT);

 

How do I wait until the message is returned? SimpleNetworkWrapper.sendToServer returns void.

Link to comment
Share on other sites

But what if there are many things I might want to do after the message is complete?

 

I have this running:

 

public class SetBlockStateDoneMessageHandler implements IMessageHandler<SetBlockStateDoneMessage, IMessage> {

@Override
public IMessage onMessage(final SetBlockStateDoneMessage message, MessageContext ctx) {
	Plato.setBlockMessageDone = true;
	return null;
}
}

 

where the message is sent like this:

 

	Plato.network.sendToServer(new SetBlockStateMessage(pos, Blocks.dirt.getDefaultState()));
	try {
		synchronized (this) {
			while (Plato.setBlockMessageDone == false) {
				wait(10);
			}
			System.out.println("state=" + world.getBlockState(pos));
		}
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	Plato.setBlockMessageDone = false;

 

but I had to remove the Runnable from SetBlockStateMessageHandler as advised by TGG in mbe60:

 

public class SetBlockStateMessageHandler implements IMessageHandler<SetBlockStateMessage, IMessage> {

@Override
public IMessage onMessage(final SetBlockStateMessage message, MessageContext ctx) {
	final EntityPlayerMP player = ctx.getServerHandler().playerEntity;
	// player.getServerForPlayer().addScheduledTask(new Runnable() {
	// public void run() {
	processMessage(message, player);
	// }
	// });
	return new SetBlockStateDoneMessage();
}

private void processMessage(SetBlockStateMessage message, EntityPlayerMP player) {
	player.worldObj.setBlockState(message.getPos(), message.getState());
}
}

Link to comment
Share on other sites

Bruh, first of all, NEVER EVER use "wait" in minecraft, unless you know what you are doing that is.

 

The logic here is simple:

You make 2 packets and 2 handlers.

 

Client -> Server

Client sends pakcet to server telling it to change block on some pos.

Server Handler

Changes block and return new packet (which will go server -> client)

Server -> Client

From Server 1st packet's handler you send a packet to client

Client Handler

You get response message and do whatever the hell you want there.

 

EDIT

To be more precise:

Plato.setBlockMessageDone = true;

In this exact place - you perform anything you would as a "after block is set".

If you need any references from outside, which I assume you want to have - you need to either send them or where to find them (x/y/z for example).

 

EDIT 2

Minecraft methods don't wait for something to happen (receive packet in this case). But you can setup ticking execution.

You would have to make some static list of Runnnables on client that would get filled before sending packet to server about something. Then utilize ClientTickEvent to try to execute every Runnable from that list, every tick (mind event.phase), if the boolean is true. Now - only thing you need to receive from server is "true" to some Runnable's ID.

Note that this way - your objects in Runnable must be WeakReference and you cannot expect them to not be null as you don't know when action will occur.

1.7.10 is no longer supported by forge, you are on your own.

Link to comment
Share on other sites

Thanks Ernio,

 

The logic here is simple:

You make 2 packets and 2 handlers.

 

Client -> Server

Client sends pakcet to server telling it to change block on some pos.

Server Handler

Changes block and return new packet (which will go server -> client)

Server -> Client

From Server 1st packet's handler you send a packet to client

Client Handler

You get response message and do whatever the hell you want there.

 

This would be fine if there was one typical response on the client after setting a block via message to the server, but that is not the case. There are many situations where I set blocks, and the next thing I do is usually different.

 

Bruh, first of all, NEVER EVER use "wait" in minecraft, unless you know what you are doing that is.

 

Trying to determine if this is a "NEVER, NEVER" case or a "know what you are doing case". It would help me understand if you could tell me what sort of problems I might typically run into using wait.

Link to comment
Share on other sites

Thanks guys for patience. I should have started my mod in multiplayer from the beginning. I dealing with this now  :)

 

I can understand why you would not want to pause the server thread, but why not call wait() on the client thread?

 

Most of the time I am setting blocks from onItemUse() on the server thread, and the server updates the client (flag 3 on setBlockState). Sometimes I'm on the client thread, either from a MousEvent or a KeyInputEvent. In these cases I am sending a series of messages to the server asking it to set a BlockState for me at a BlockPos. Whether I name my message SetBlockStateMessage or IHavePressedTheDKeyMessage, it comes down to the same thing, no? I still have to let the server know what state to set and at what position. The important thing is that I'm letting the server set block states on the client. Am I wrong?

 

Just as a note, in my test class that I presented at the beginning of my thread, I was calling wait() on the server thread. That was not my intention. I have rewritten the test so that it is running on the client thread. I get the client thread for my println on the console now:

 

[21:23:37] [Client thread/INFO] [sTDOUT]: [ds.plato.event.Test:<init>:23]: state=minecraft:dirt[snowy=false,variant=dirt]

Link to comment
Share on other sites

Uhm... SinglePlayer as such does not exist. SinglePlayer is MultiPlayer with one player.

 

I'll rephrase : I should have been testing my mod over my lan with two players from the beginning.

 

Same thing. The way I understand it, the client needs to render the world every frame, it needs to update animations, your player position when you walk, entities on screen, etc. If you just call wait, all that stops.

 

Not entirely the same thing. Pausing the client thread would only delay that player's game, no? Pausing the server thread would delay every player's game. If my wait is 10 milliseconds a few times every minute, I might decide that I can live with that. The important thing is that I'm not imposing it on other players in the game.

 

No, you are not "letting the server set block states"

 

When I said "I am letting the server set block states on the client" I meant that the client is not setting it's own block then asking the server to set its block. Instead, it is sending a message to the server to set the block and the change is propagated back to the client as in World.setBlockState(BlockPos, IBlockState) implies flag=3, which will update both server and client. I am letting the server do its work to keep clients in sync.

 

Calling setBlock on the server (in that message handler) will the automatically propagate the change to all clients, including the player who actually pressed the key.

 

But this is precisely what I want.

 

Anyway, I appreciate your approach to solving the problem. You are saying, instead of messing around with stuff like wait(), try things a different way and your problems will just go away. And I have been busy trying do so. The problem I am running into is that there is a context for every client thread that the server must have in order to set the blocks. The way I have things set up now, each client manages its own context. The context is complex and it is infeasible sending it back and forth in a message.

 

Link to comment
Share on other sites

Dieseiben, I am really not trying to be stubborn here. I will accept as gospel that wait is bad. Fine, I'll look for another way. Thank you for the advice.

 

As for as a blanket statement like "The client has no say in what happens in the world", Try as I might, I simply cannot understand it.

 

If there was only a server, nothing would happen in the world. Anything that happens in the world originates from the player, in the client thread. The player plays the game. The server serves. This is starting to sound like some sort of free will debate. :)

 

If the statement were "The client doesn't have final say about what happens in the world", then I could understand it. If this *is* what you mean, would a test like this satisfy you? (please note the renamed message class):

 

private void processRequest(PolitePlaceBlockRequest request, EntityPlayerMP player) {
	World world = player.worldObj;
	BlockPos pos = request.getPos();
	IBlockState state = request.getState();
	if (canPlayerPlaceBlock(player, pos, state)) {
		world.setBlockState(pos, state);
	}
}

 

As far as all the player's context being on the server, allow me to be more specific. Each player's client thread has a SelectionManager, a PickManager, and an UndoManager. There might be tens of thousands of selections and even more undos. If there were 10 players in the game, with a set of managers for each player, the server would be over burdened. Note that if a player undos, it is only his work that is undone. I really can not see this working with the managers on the server.

Link to comment
Share on other sites

The undoManager maintains a list of transactions modifying the world. The player can undo and redo any changes he makes to the world. If in one transaction he creates a huge cube 100x100x100 blocks, there is a transaction with a million previous block states in it. When ctrl-z is pressed the world is set to the state before the transaction.

 

If the undoManager is remembering the last 10 transactions he might be asking for a lot of memory. The player would get a feel for how far he can push his machine.

 

Likewise for the selectionManager. When blocks are placed they are selected, and the last selection is remembered.

 

If you think a server could deal with this, I am coming around to idea of them being on the server. I suppose I would maintain a map of player to managers constructed whenever a new player joins the game. It certainly would reduce network traffic. If the player were to hit the delete key with 100,000 blocks selected, that would be a lot of SetBlockStateMessages to the server.

 

 

 

 

 

Link to comment
Share on other sites

I will now have to stop you before you hit a brick wall so hard that you are gonna fell into alcoholism and drugs and look like this:

 

58046888.jpg

 

You most certailny don't want to change world per-block. And if you do - you don't want to do that with "send to players" flag.

The lag will be so massive you won't be even able to live through it.

 

Few facts:

- You don't want to store your "undo" data on client.

- You also don't want to store that data on server - more directly - not in RAM, but you CAN.

- You too don't want to use setBlockState to replace blocks, and if so - you most certainly don't want to use "update to all players" flag (3 I belive).

 

Now what you want:

- Store "undo" selection on server - I'd REALLY consider storing anything beyond some array size to NBT data and save it direcly to disk as schematic. In case player want to load that - indeed, reading from disk will be slower, but I don't imagine you want to store e.g 100MB of map in RAM per-player and per-UndoStorage.

-Redesign your "restoration" logic - create ServerTickEvent that will be triggered by player action and which will restore terrain from mentioned server-side storages and/or disk-data (NBT). Now - using TickEvent would allow you to restore LARGE terrains without getting huge-ass lags. (x blocks per tick).

- You will VERY soon notice that using "update" flag with a lot of setState methods will cause massive lags, don't do that - change your blocks server-side and send whole chunks to clients - that way you will reduce packets to vanilla-world-loading traffic.

- You might also notice that the higher block is (in world) - the more time it will take to set it (in huge numbers) - that is all about light updates - you ca also bypass this by not using update flags per every block, but after whole reload.

1.7.10 is no longer supported by forge, you are on your own.

Link to comment
Share on other sites

Thanks for the advice Ernio (and deisieben), I will get to work on that today.

 

I would like a general strategy for the server to set blocks, whether it is from an undo or a change.

 

If I understand correctly, on the server side, I should call setBlockState for each block with no client update, then in my subscription to ServerTickEvent I update the client on every tick?

 

What method do I call to update the client? Is there one available that knows what blocks need updating, or do I have to keep track of that myself?

 

create ServerTickEvent that will be triggered by player action

 

Do ServerTickEvents need to be created, or do you mean subscribe to ServerTickEvent?

 

 

 

Link to comment
Share on other sites

As long as block number is not huge-ass, you can set in in "right now" time (meaning current tick), but if that number will get too big (you'll have to test what amount is "too big") then you should split restoration into few ticks. To do that you will need to create restorationQueue system that will be ran from ServerTickEvent (yes, subscribe) and trigerred on player command. I belive you know how to code nice queue - you will be simply getting part of all blocks from restoration and change them per-tick, not all at once, until restoration is done.

 

As mentioned before, to get performance you will need to get into internals:

Chunk chunk = world.getChunkFromChunkCoords(chunkX, chunkZ);
	for (ExtendedBlockStorage storage : chunk.getBlockStorageArray())

This will allow you to edit block-data DIRECTLY. Note that you might need 2 or even 3 phases to make sure that e.g torches are placed after walls around it.

Also remember about TileEntity support.

 

What method do I call to update the client?

Since you don't really want to sent update per every block:

At the end of tick in which you performed mentioned internal restoration process you will need to grab all the chunks that were edited and send them in one terran-loading packet (the one that vanilla sends to clients when they travel across the world).

I don't really know exact method (last time I had fun with direct world edits was before 1.5), so I can't help you here.

Hint: Just find the vanilla packet and look into callbacks.

1.7.10 is no longer supported by forge, you are on your own.

Link to comment
Share on other sites

The way I see it, there are two issues here:

 

1) develop a strategy for the server to write and read undo data to and from disk when undo data is too large to manage in ram.

 

2) develop a strategy for the server to set blocks and update clients in chunks. This is applied to both undo data (which may have been read from disk), and also to new changes to the terrain (which will in turn create new undo data).

 

Does this make sense to you? I'd like to tackle them one at a time, probably 2 first. There are temporary fixes for 1, like just throwing out the undo if it is too large.

 

At the end of tick in which you performed mentioned internal restoration process you will need to grab all the chunks that were edited and send them in one terran-loading packet (the one that vanilla sends to clients when they travel across the world).

 

One chunk = 16x16x256 = 65,536 blocks.

 

Say at the end of one tick I've set 10,000 blocks spread over 10 chunks. That's 1,000 changes per chunk. Is it really more efficient to send 10 whole chunks (655,360 blocks)? Is there a way to send just my 10,000 changes to the client in one packet?

 

 

Link to comment
Share on other sites

1. That is simple as hell :D Check size of changes and if too big - use:

CompressedStreamTools.writeCompressed(someNbt, new FileOutputStream(f));

NBTTagCompound nbt = CompressedStreamTools.readCompressed(new FileInputStream(f));

 

2. You need to understand the most important fact here: sending 10000 updates of single block is MUCH more of a burden for both sides than sending 1 update with 10000 blocks.

Compression is your friend (really good one) - how do you think vanilla sends whole world to clients?

You will need to know how block is written to bits and which one (bit) is what. Compressed chunk is almost nothing (a necessity) for minecraft to handle.

Alredy noted: lookup vanilla's world sending packets.

 

As to "super-compression" which you tackled at end of post - sending not-full chunk data but only given blocks positions (e.g 500 out of 65536 block).

That will be hard and won't change much (again - look at vanilla).

To send chunk you need [16][256][16] array of blocks that will with it's size define block pos (e.g [7][78][6]), any attempt at lowering that size will still require you to save the block pos for given pos.

Better option:

What you can do is to know exacly how much blocks were changed for given chunk. If the number of changes is small you can simply send sinuglar block-change packets (instead of whole chunk).

 

One more thing - Chunk is indeed 16x256x16, but chunk storage is not - it's 16x16x16, which you would know if you looked up code from my previous post.

1.7.10 is no longer supported by forge, you are on your own.

Link to comment
Share on other sites

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.



×
×
  • Create New...

Important Information

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