From dac97826f7d0c9b9135c1a08c4d5f16b61494bd1 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Wed, 26 Aug 2015 10:58:51 +0200 Subject: Renamed output directory to Server --- MCServer/Plugins/APIDump/UsingChunkStays.html | 167 -------------------------- 1 file changed, 167 deletions(-) delete mode 100644 MCServer/Plugins/APIDump/UsingChunkStays.html (limited to 'MCServer/Plugins/APIDump/UsingChunkStays.html') diff --git a/MCServer/Plugins/APIDump/UsingChunkStays.html b/MCServer/Plugins/APIDump/UsingChunkStays.html deleted file mode 100644 index a7cb5302a..000000000 --- a/MCServer/Plugins/APIDump/UsingChunkStays.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - Cuberite - Using ChunkStays - - - - - - - -
-

Using ChunkStays

-

- A plugin may need to manipulate data in arbitrary chunks, and it needs a way to make the server - guarantee that the chunks are available in memory.

- -

The problem

-

- Usually when plugins want to manipulate larger areas of world data, they need to make sure that the - server has the appropriate chunks loaded in the memory. When the data being manipulated can be further - away from the connected players, or the data is being manipulated from a console handler, there is a - real chance that the chunks are not loaded.

-

- This gets even more important when using the cBlockArea class for reading - and writing. Those functions will fail when any of the required chunks aren't valid. This means that - either the block area has incomplete data (Read() failed) or incomplete data has been written to the - world (Write() failed). Recovery from this is near impossible - you can't simply read or write again - later, because the world may have changed in the meantime.

- -

The solution

-

- The naive solution would be to monitor chunk loads and unloads, and postpone the operations until all - the chunks are available. This would be quite ineffective and also very soon it would become very - difficult to maintain, if there were multiple code paths requiring this handling.

-

- An alternate approach has been implemented, accessible through a single (somewhat hidden) function - call: cWorld:ChunkStay(). All that this call basically does is, it tells the - server "Load these chunks for me, and call this callback function once you have them all." And the - server does exactly that - it remembers the callback and asks the world loader / generator to provide - the chunks. Once the chunks become available, it calls the callback function for the plugin.

-

- There are a few gotcha-s, though. If the code that was requesting the read or write had access to some - of the volatile objects, such as cPlayer or - cEntity objects, those cannot be accessed by the callback anymore, because - they may have become invalid in the meantime - the player may have disconnected, the entity may have - despawned. So the callback must use the longer way to access such objects, such as calling - cWorld:DoWithEntityByID() or - cWorld:DoWithPlayer().

- -

The example

-

- As a simple example, consider a theoretical plugin that allows a player to save the immediate - surroundings of the spawn into a schematic file. The player issues a command to initiate the save, and - the plugin reads a 50 x 50 x 50 block area around the spawn into a cBlockArea and saves it on the disk - as "_spawn.schematic". When it's done with the saving, it wants to send a message to the - player to let them know the command has succeeded.

-

- The first attempt shows the naive approach. It simply reads the block area and saves it, then sends the - message. I'll repeat once more, this code is the wrong way to do it!

-
-function HandleCommandSaveSpawn(a_Split, a_Player)
-	-- Get the coords for the spawn:
-	local SpawnX = a_Player:GetWorld():GetSpawnX()
-	local SpawnY = a_Player:GetWorld():GetSpawnY()
-	local SpawnZ = a_Player:GetWorld():GetSpawnZ()
-	local Bounds = cCuboid(SpawnX - 25, SpawnY - 25, SpawnZ - 25, SpawnX + 25, SpawnY + 25, SpawnZ + 25)
-	Bounds:ClampY(0, 255)
-	
-	-- Read the area around spawn into a cBlockArea, save to file:
-	local Area = cBlockArea()
-	local FileName = a_Player:GetName() .. "_spawn.schematic"
-	Area:Read(a_Player:GetWorld(), Bounds, cBlockArea.baTypes + cBlockArea.baMetas)
-	Area:SaveToSchematicFile(FileName)
-	
-	-- Notify the player:
-	a_Player:SendMessage(cCompositeChat("The spawn has been saved", mtInfo))
-	return true
-end
-
-

- Now if the player goes exploring far and uses the command to save their spawn, the chunks aren't - loaded, so the BlockArea reading fails, the BlockArea contains bad data. Note that the plugin fails to - do any error checking and if the area isn't read from the world, it happily saves the incomplete data - and says "hey, everything's right", althought it has just trashed any previous backup of the spawn - schematic with nonsense data.

-
-

- The following script uses the ChunkStay method to alleviate chunk-related problems. This is the - right way of doing it:

-
-function HandleCommandSaveSpawn(a_Split, a_Player)
-	-- Get the coords for the spawn:
-	local SpawnX = a_Player:GetWorld():GetSpawnX()
-	local SpawnY = a_Player:GetWorld():GetSpawnY()
-	local SpawnZ = a_Player:GetWorld():GetSpawnZ()
-	local Bounds = cCuboid(SpawnX - 25, SpawnY - 25, SpawnZ - 25, SpawnX + 25, SpawnY + 25, SpawnZ + 25)
-	Bounds:ClampY(0, 255)
-	
-	-- Get a list of chunks that we need loaded:
-	local MinChunkX = math.floor((SpawnX - 25) / 16)
-	local MaxChunkX = math.ceil ((SpawnX + 25) / 16)
-	local MinChunkZ = math.floor((SpawnZ - 25) / 16)
-	local MaxChunkZ = math.ceil ((SpawnZ + 25) / 16)
-	local Chunks = {}
-	for x = MinChunkX, MaxChunkX do
-		for z = MinChunkZ, MaxChunkZ do
-			table.insert(Chunks, {x, z})
-		end
-	end  -- for x
-	
-	-- Store the player's name and world to use in the callback, because the a_Player object may no longer be valid:
-	local PlayerName = a_Player:GetName()
-	local World = a_Player:GetWorld()
-	
-	-- This is the callback that is executed once all the chunks are loaded:
-	local OnAllChunksAvailable = function()
-		-- Read the area around spawn into a cBlockArea, save to file:
-		local Area = cBlockArea()
-		local FileName = PlayerName .. "_spawn.schematic"
-		if (Area:Read(World, Bounds, cBlockArea.baTypes + cBlockArea.baMetas)) then
-			Area:SaveToSchematicFile(FileName)
-			Msg = cCompositeChat("The spawn has been saved", mtInfo)
-		else
-			Msg = cCompositeChat("Cannot save the spawn", mtFailure)
-		end
-		
-		-- Notify the player:
-		-- Note that we cannot use a_Player here, because it may no longer be valid (if the player disconnected before the command completes)
-		World:DoWithPlayer(PlayerName,
-			function (a_CBPlayer)
-				a_CBPlayer:SendMessage(Msg)
-			end
-		)
-	end
-	
-	-- Ask the server to load our chunks and notify us once it's done:
-	World:ChunkStay(Chunks, nil, OnAllChunksAvailable)
-	
-	-- Note that code here may get executed before the callback is called!
-	-- The ChunkStay says "once you have the chunks", not "wait until you have the chunks"
-	-- So you can't notify the player here, because the saving needn't have occurred yet.
-	
-	return true
-end
-
-

- Note that this code does its error checking of the Area:Read() function, and it will not overwrite the - previous file unless it actually has the correct data. If you're wondering how the reading could fail - when we've got the chunks loaded, there's still the issue of free RAM - if the memory for the area - cannot be allocated, it cannot be read even with all the chunks present. So we still do need that - check.

- -

The conclusion

-

- Although it makes the code a little bit longer and is a bit more difficult to grasp at first, the - ChunkStay is a useful technique to add to your repertoire. It is to be used whenever you need access to - chunks that may potentially be inaccessible, and you really need the data.

-

Possibly the biggest hurdle in using the ChunkStay is the fact that it does its work in the - background, thus invalidating all cPlayer and cEntity objects your function may hold, so you need to - re-acquire them from their IDs and names. This is the penalty for using multi-threaded code.

- -
- - -- cgit v1.2.3