Skip to content

Commit

Permalink
Made all metadata calls async.
Browse files Browse the repository at this point in the history
Removed ChunkBusyExceptio, ChunkNotLoadedException and ChunkAlreadyLoadedException
  • Loading branch information
itsMatoosh committed Dec 14, 2021
1 parent f35098c commit 5b5ec10
Show file tree
Hide file tree
Showing 15 changed files with 160 additions and 253 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ repositories {
}
dependencies {
implementation 'com.github.itsMatoosh:block-metadata:1.3.1'
implementation 'com.github.itsMatoosh:block-metadata:1.4.0'
}
```
### Adding Block Metadata by Maven
Expand All @@ -28,7 +28,7 @@ Modify your pom.xml file to include the following:
<dependency>
<groupId>com.github.itsMatoosh</groupId>
<artifactId>block-metadata</artifactId>
<version>1.3.1</version>
<version>1.4.0</version>
</dependency>
```

Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
}

group 'me.matoosh'
version '1.3.1'
version '1.4.0'

sourceCompatibility = JavaVersion.VERSION_1_8

Expand Down
185 changes: 83 additions & 102 deletions src/main/java/me/matoosh/blockmetadata/BlockMetadataStorage.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
import me.matoosh.blockmetadata.event.BlockMoveHandler;
import me.matoosh.blockmetadata.event.ChunkLoadHandler;
import me.matoosh.blockmetadata.event.PluginDisableHandler;
import me.matoosh.blockmetadata.exception.ChunkAlreadyLoadedException;
import me.matoosh.blockmetadata.exception.ChunkBusyException;
import me.matoosh.blockmetadata.exception.ChunkNotLoadedException;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.block.Block;
Expand All @@ -30,7 +27,6 @@
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;

/**
Expand Down Expand Up @@ -131,113 +127,124 @@ public BlockMetadataStorage(JavaPlugin plugin, Path dataPath) {
* Get metadata of a block.
* @param block The block.
* @return Current metadata of the block. Null if no data stored.
* @throws ChunkBusyException Thrown if the chunk is busy.
*/
public T getMetadata(@NonNull Block block) throws ChunkBusyException {
public CompletableFuture<T> getMetadata(@NonNull Block block) {
// get chunk
Map<String, T> metadata = getMetadataInChunk(block.getChunk());
if (metadata == null) {
// no data for this chunk
return null;
}
return getMetadataInChunk(block.getChunk()).thenApply((metadata) -> {
if (metadata == null) {
// no data for this chunk
return null;
}

// get block metadata
return metadata.get(getBlockKeyInChunk(block));
// get block metadata
return metadata.get(getBlockKeyInChunk(block));
});
}

/**
* Set metadata of a block.
* @param block The block.
* @param metadata Metadata to set to the block.
* @throws ChunkBusyException Thrown if the chunk is busy.
* @param data Metadata to set to the block.
*/
public void setMetadata(@NonNull Block block, T metadata) throws ChunkBusyException {
if (metadata == null) {
public CompletableFuture<Void> setMetadata(@NonNull Block block, T data) {
if (data == null) {
// clear metadata
removeMetadata(block);
return removeMetadata(block).thenApply((s) -> null);
} else {
// set metadata
modifyMetadataInChunk(block.getChunk()).put(getBlockKeyInChunk(block), metadata);
return getMetadataInChunk(block.getChunk()).thenApply((d) -> {
// make sure theres a map to put data in
if (d == null) {
d = new HashMap<>();
}
d.put(getBlockKeyInChunk(block), data);

// set chunk as dirty
LoadedChunkData loadedChunkData = loadedChunks.get(block.getChunk());
loadedChunkData.setDirty(true);

// update the data
metadata.put(block.getChunk(), d);

return null;
});
}
}

/**
* Removes metadata for a block.
* @param block The block.
* @throws ChunkBusyException Thrown if the chunk is busy.
* @return The removed metadata value. Null if no value was stored.
*/
public T removeMetadata(@NonNull Block block) throws ChunkBusyException {
// cache chunk
public CompletableFuture<T> removeMetadata(@NonNull Block block) {
Chunk chunk = block.getChunk();
return getMetadataInChunk(chunk).thenApply((metadata) -> {
// no metadata in chunk
if (metadata == null) {
return null;
}

// check if there are durabilities in the chunk
if (!hasMetadataForChunk(chunk)) {
return null;
}

// get chunk
Map<String, T> metadata = modifyMetadataInChunk(chunk);

// remove from map
T value = metadata.remove(getBlockKeyInChunk(block));
// remove metadata value from map
T value = metadata.remove(getBlockKeyInChunk(block));

// check if last value
if (metadata.size() < 1) {
// remove chunk from storage
removeMetadataForChunk(chunk);
}
// if this was the last one left remove metadata entry for this chunk
if (metadata.size() == 0) {
removeMetadataForChunk(chunk);
}

return value;
return value;
});
}

/**
* Checks whether there are metadata stored for a given chunk.
* @param chunk The chunk to check.
* @return Whether there are metadata stored for the given chunk.
* @throws ChunkBusyException Thrown if the chunk is busy.
*/
public boolean hasMetadataForChunk(@NonNull Chunk chunk) throws ChunkBusyException {
ensureChunkReady(chunk);
return metadata.containsKey(chunk);
public CompletableFuture<Boolean> hasMetadataForChunk(@NonNull Chunk chunk) {
return loadChunk(chunk).thenApply((s) -> metadata.containsKey(chunk));
}

/**
* Removes all metadata stored for a given chunk.
* @param chunk The chunk to remove metadata from.
* @throws ChunkBusyException Thrown if the chunk is busy.
*/
public void removeMetadataForChunk(@NonNull Chunk chunk) throws ChunkBusyException {
ensureChunkReady(chunk);
metadata.remove(chunk);
LoadedChunkData loadedChunkData = loadedChunks.get(chunk);
loadedChunkData.setDirty(true);
public CompletableFuture<Map<String, T>> removeMetadataForChunk(@NonNull Chunk chunk) {
return loadChunk(chunk).thenRun(() -> {
// set chunk as dirty
LoadedChunkData loadedChunkData = loadedChunks.get(chunk);
loadedChunkData.setDirty(true);
}).thenApply((s) -> metadata.remove(chunk));
}

/**
* Gets metadata of blocks in a chunk to be modified.
* Sets the chunk as dirty.
* Gets metadata of blocks in a chunk.
* @param chunk The chunk in which the metadata are.
* @return Map of metadata.
* @throws ChunkBusyException Thrown if the chunk is busy.
*/
public Map<String, T> modifyMetadataInChunk(@NonNull Chunk chunk) throws ChunkBusyException {
ensureChunkReady(chunk);
Map<String, T> data = metadata.computeIfAbsent(chunk, k -> new HashMap<>());
LoadedChunkData loadedChunkData = loadedChunks.get(chunk);
loadedChunkData.setDirty(true);
return data;
public CompletableFuture<Map<String, T>> getMetadataInChunk(@NonNull Chunk chunk) {
return loadChunk(chunk).thenApply((s) -> metadata.get(chunk));
}


/**
* Gets metadata of blocks in a chunk.
* @param chunk The chunk in which the metadata are.
* @return Map of metadata.
* @throws ChunkBusyException Thrown if the chunk is busy.
* Sets all the data in a given chunk to the specified data.
* @param chunk The chunk for which to set metadata.
* @param data The metadata map.
*/
public Map<String, T> getMetadataInChunk(@NonNull Chunk chunk) throws ChunkBusyException {
ensureChunkReady(chunk);
return metadata.get(chunk);
public CompletableFuture<Void> setMetadataInChunk(@NonNull Chunk chunk, Map<String, T> data) {
return loadChunk(chunk).thenRun(() -> {
// set chunk as dirt
LoadedChunkData loadedChunkData = loadedChunks.get(chunk);
loadedChunkData.setDirty(true);

// update the data
if (data == null || data.size() == 0) {
metadata.remove(chunk);
} else {
metadata.put(chunk, data);
}
});
}

/**
Expand Down Expand Up @@ -358,7 +365,7 @@ private CompletableFuture<Map<String, Map<String, T>>> readRegionData(Path regio
* @return The number of bytes that were written to disk.
*/
private CompletableFuture<Void> bufferRegionData(
@NonNull Path regionFile, @NonNull Map<String, Map<String, T>> data) {
@NonNull Path regionFile, Map<String, Map<String, T>> data) {
RegionTask regionTask = busyRegions.get(regionFile);
if (regionTask != null) {
regionTask.setBuffer(data);
Expand All @@ -376,7 +383,7 @@ private CompletableFuture<Void> bufferRegionData(
* @return The number of bytes that were written to disk.
*/
private CompletableFuture<Void> writeRegionData(
@NonNull Path regionFile, @NonNull Map<String, Map<String, T>> data) {
@NonNull Path regionFile, Map<String, Map<String, T>> data) {
// remove old file to overwrite
return CompletableFuture.supplyAsync(() -> {
try {
Expand All @@ -385,6 +392,9 @@ private CompletableFuture<Void> writeRegionData(
return false;
}
}).thenApply((s) -> {
// remove empty region files
if (data == null) return null;

// serialize to yaml
try {
return mapper.writeValueAsString(data);
Expand All @@ -401,8 +411,7 @@ private CompletableFuture<Void> writeRegionData(
* Persists metadata of specified chunks on disk.
* @param chunks Chunks to persist metadata for.
*/
public CompletableFuture<Void> persistChunks(Chunk... chunks)
throws ChunkNotLoadedException {
public CompletableFuture<Void> persistChunks(Chunk... chunks) {
CompletableFuture<Void>[] tasks = new CompletableFuture[chunks.length];
for (int i = 0; i < chunks.length; i++) {
tasks[i] = persistChunk(chunks[i]);
Expand All @@ -413,10 +422,8 @@ public CompletableFuture<Void> persistChunks(Chunk... chunks)
/**
* Persists chunk metadata on disk and unloads the metadata from memory.
* @param chunk The chunk to persist.
* @throws ChunkNotLoadedException Thrown if the chunk isn't loaded.
*/
public CompletableFuture<Void> persistChunk(@NonNull Chunk chunk)
throws ChunkNotLoadedException {
public CompletableFuture<Void> persistChunk(@NonNull Chunk chunk) {
// check if chunk dirty
if (!isChunkDirty(chunk)) {
// not dirty, nothing to save to disk
Expand Down Expand Up @@ -488,8 +495,7 @@ private CompletableFuture<Void> persistChunkAsync(@NonNull Chunk chunk) {
* Loads chunk metadata for each specified chunk.
* @param chunks Chunks to load metadata for.
*/
public CompletableFuture<Void> loadChunks(Chunk... chunks)
throws ChunkAlreadyLoadedException {
public CompletableFuture<Void> loadChunks(Chunk... chunks) {
CompletableFuture<Void>[] tasks = new CompletableFuture[chunks.length];
for (int i = 0; i < chunks.length; i++) {
tasks[i] = loadChunk(chunks[i]);
Expand All @@ -501,10 +507,10 @@ public CompletableFuture<Void> loadChunks(Chunk... chunks)
* Loads chunk metadata into memory.
* @param chunk The chunk.
*/
public CompletableFuture<Void> loadChunk(@NonNull Chunk chunk) throws ChunkAlreadyLoadedException {
public CompletableFuture<Void> loadChunk(@NonNull Chunk chunk) {
// check if chunk loaded
if (isChunkLoaded(chunk)) {
throw new ChunkAlreadyLoadedException();
return CompletableFuture.completedFuture(null);
}

// check if chunk is already loading
Expand Down Expand Up @@ -550,31 +556,6 @@ private CompletableFuture<Void> loadChunkAsync(@NonNull Chunk chunk) {
});
}

/**
* Ensures that a chunk is ready to be interacted with.
* Loads chunk if not loaded.
* @param chunk The chunk.
* @throws ChunkBusyException Thrown if the chunk is busy.
*/
public void ensureChunkReady(@NonNull Chunk chunk) throws ChunkBusyException {
// ensure chunk is loaded
if (!isChunkLoaded(chunk)) {
// load chunk first
try {
loadChunk(chunk).get();
if (!chunk.isLoaded()) {
chunk.load();
}
} catch (InterruptedException | ExecutionException | ChunkAlreadyLoadedException ex) {
// should not happen
ex.printStackTrace();
}
} else if (isChunkBusy(chunk)) {
// ensure chunk is not busy
throw new ChunkBusyException();
}
}

/**
* Checks whether the specified chunk is busy.
* A chunk is busy if it's being saved.
Expand Down Expand Up @@ -610,10 +591,10 @@ public boolean isChunkLoaded(Chunk chunk) {
* @param chunk The chunk.
* @return Whether the chunk is dirty.
*/
public boolean isChunkDirty(Chunk chunk) throws ChunkNotLoadedException {
public boolean isChunkDirty(Chunk chunk) {
LoadedChunkData loadedChunkData = loadedChunks.get(chunk);
if (loadedChunkData == null) {
throw new ChunkNotLoadedException();
return false;
}
return loadedChunkData.isDirty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import lombok.RequiredArgsConstructor;
import me.matoosh.blockmetadata.BlockMetadataStorage;
import me.matoosh.blockmetadata.exception.ChunkBusyException;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
Expand All @@ -22,31 +21,31 @@ public class BlockDestroyHandler<T extends Serializable> implements Listener {
private final BlockMetadataStorage<T> storage;

@EventHandler(priority = EventPriority.LOW)
public void onBlockDestroy(BlockBreakEvent event) throws ChunkBusyException {
public void onBlockDestroy(BlockBreakEvent event) {
if (event.isCancelled()) {
return;
}
storage.removeMetadata(event.getBlock());
}

@EventHandler(priority = EventPriority.LOW)
public void onEntityChangeBlock(EntityChangeBlockEvent event) throws ChunkBusyException {
public void onEntityChangeBlock(EntityChangeBlockEvent event) {
if (event.isCancelled()) {
return;
}
storage.removeMetadata(event.getBlock());
}

@EventHandler(priority = EventPriority.LOW)
public void onBlockBurn(BlockBurnEvent event) throws ChunkBusyException {
public void onBlockBurn(BlockBurnEvent event) {
if (event.isCancelled()) {
return;
}
storage.removeMetadata(event.getBlock());
}

@EventHandler(priority = EventPriority.LOW)
public void onBlockFade(BlockFadeEvent event) throws ChunkBusyException {
public void onBlockFade(BlockFadeEvent event) {
if (event.isCancelled()) {
return;
}
Expand Down
Loading

0 comments on commit 5b5ec10

Please sign in to comment.