Skip to content

Commit

Permalink
Merge pull request #169 from Corpium/corp
Browse files Browse the repository at this point in the history
[Performance Improvement] Reduce IO reads during sign updates
  • Loading branch information
alex9849 authored Feb 4, 2025
2 parents 53aaec0 + 8d6f8cf commit fb04025
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 24 deletions.
12 changes: 0 additions & 12 deletions .github/FUNDING.yml

This file was deleted.

4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/target
target/

.project
.metadata
Expand All @@ -16,4 +16,4 @@ local.properties
*.iml
.idea
dependency-reduced-pom.xml
desktop.ini
desktop.ini
6 changes: 6 additions & 0 deletions advancedregionmarket/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@
<version>3.0.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<!-- high-performance caching library -->
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.2.0</version>
</dependency>
<!--Project Modules-->
<dependency>
<groupId>net.alex9849.advancedregionmarket</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
package net.alex9849.arm.adapters.util;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import java.time.Duration;
import java.util.Map;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.Statistic;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.profile.PlayerProfile;

/**
* Each call to {@link Bukkit#getOfflinePlayer(UUID)} results in a call to
* net.minecraft.world.level.storage.PlayerDataStorage#load() which contains resource consuming IO read operations.
* So we want to cache the retrieved offline players as well as the result of {@link OfflinePlayer#getName()}.
*/
public class OfflinePlayerCache {

private static final LoadingCache<UUID, OfflinePlayer> playerCache = Caffeine.newBuilder()
.expireAfterAccess(Duration.ofMinutes(30))
.build(uuid -> new OfflinePlayerProxy(Bukkit.getOfflinePlayer(uuid)));
/**
* Each call to {@link OfflinePlayer#getName()} of the org.bukkit.craftbukkit.CraftOfflinePlayer implementation
* results in a call to net.minecraft.world.level.storage.PlayerDataStorage#load().
* So we also want to cache the player names to reduce IO reads.
*/
private static final LoadingCache<OfflinePlayer, String> playerNameCache = Caffeine.newBuilder()
.expireAfterAccess(Duration.ofMinutes(30))
.build(OfflinePlayer::getName);

/**
* @param uuid the UUID of the player to retrieve
* @return The cached result of {@link Bukkit#getOfflinePlayer(UUID)} wrapped into a proxy object which allows
* caching further methods that perform IO read operations (i.e. {@link OfflinePlayer#getName()}).
* @see Bukkit#getOfflinePlayer(UUID)
*/
public static OfflinePlayer get(UUID uuid) {
return playerCache.get(uuid);
}

private static class OfflinePlayerProxy implements OfflinePlayer {
private final OfflinePlayer delegate;

private OfflinePlayerProxy(OfflinePlayer delegate) {
this.delegate = delegate;
}

@Override
public boolean isOnline() {
return delegate.isOnline();
}

/**
* This method uses the {@link #playerNameCache} to return cached player names or cache them on demand.
*/
@Override
public String getName() {
return playerNameCache.get(delegate);
}

@Override
public UUID getUniqueId() {
return delegate.getUniqueId();
}

@Override
public PlayerProfile getPlayerProfile() {
return delegate.getPlayerProfile();
}

@Override
public boolean isBanned() {
return delegate.isBanned();
}

@Override
public boolean isWhitelisted() {
return delegate.isWhitelisted();
}

@Override
public void setWhitelisted(boolean value) {
delegate.setWhitelisted(value);
}

@Override
public Player getPlayer() {
return delegate.getPlayer();
}

@Override
public long getFirstPlayed() {
return delegate.getFirstPlayed();
}

@Override
public long getLastPlayed() {
return delegate.getLastPlayed();
}

@Override
public boolean hasPlayedBefore() {
return delegate.hasPlayedBefore();
}

@Override
public Location getBedSpawnLocation() {
return delegate.getBedSpawnLocation();
}

@Override
public void incrementStatistic(Statistic statistic) throws IllegalArgumentException {
delegate.incrementStatistic(statistic);
}

@Override
public void decrementStatistic(Statistic statistic) throws IllegalArgumentException {
delegate.decrementStatistic(statistic);
}

@Override
public void incrementStatistic(Statistic statistic, int amount) throws IllegalArgumentException {
delegate.incrementStatistic(statistic, amount);
}

@Override
public void decrementStatistic(Statistic statistic, int amount) throws IllegalArgumentException {
delegate.decrementStatistic(statistic, amount);
}

@Override
public void setStatistic(Statistic statistic, int newValue) throws IllegalArgumentException {
delegate.setStatistic(statistic, newValue);
}

@Override
public int getStatistic(Statistic statistic) throws IllegalArgumentException {
return delegate.getStatistic(statistic);
}

@Override
public void incrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException {
delegate.incrementStatistic(statistic, material);
}

@Override
public void decrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException {
delegate.decrementStatistic(statistic, material);
}

@Override
public int getStatistic(Statistic statistic, Material material) throws IllegalArgumentException {
return delegate.getStatistic(statistic, material);
}

@Override
public void incrementStatistic(Statistic statistic, Material material, int amount) throws IllegalArgumentException {
delegate.incrementStatistic(statistic, material, amount);
}

@Override
public void decrementStatistic(Statistic statistic, Material material, int amount) throws IllegalArgumentException {
delegate.decrementStatistic(statistic, material, amount);
}

@Override
public void setStatistic(Statistic statistic, Material material, int newValue) throws IllegalArgumentException {
delegate.setStatistic(statistic, material, newValue);
}

@Override
public void incrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException {
delegate.incrementStatistic(statistic, entityType);
}

@Override
public void decrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException {
delegate.decrementStatistic(statistic, entityType);
}

@Override
public int getStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException {
return delegate.getStatistic(statistic, entityType);
}

@Override
public void incrementStatistic(Statistic statistic, EntityType entityType, int amount) throws IllegalArgumentException {
delegate.incrementStatistic(statistic, entityType, amount);
}

@Override
public void decrementStatistic(Statistic statistic, EntityType entityType, int amount) {
delegate.decrementStatistic(statistic, entityType, amount);
}

@Override
public void setStatistic(Statistic statistic, EntityType entityType, int newValue) {
delegate.setStatistic(statistic, entityType, newValue);
}

@Override
public Location getLastDeathLocation() {
return delegate.getLastDeathLocation();
}

@Override
public Map<String, Object> serialize() {
return delegate.serialize();
}

@Override
public boolean isOp() {
return delegate.isOp();
}

@Override
public void setOp(boolean value) {
delegate.setOp(value);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package net.alex9849.arm.inactivityexpiration;

import net.alex9849.arm.AdvancedRegionMarket;
import net.alex9849.arm.adapters.util.OfflinePlayerCache;
import net.alex9849.arm.regions.Region;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
Expand Down Expand Up @@ -55,7 +56,7 @@ public void run() {
HashMap<UUID, InactivityExpirationGroup> resetUuidMap = newBestResetAfterMap.get(regionWorld);
HashMap<UUID, InactivityExpirationGroup> takeoverUuidMap = newBestTakeoverAfterMap.get(regionWorld);
if (resetUuidMap.get(owner) == null) {
OfflinePlayer oPlayerOwner = Bukkit.getOfflinePlayer(owner);
OfflinePlayer oPlayerOwner = OfflinePlayerCache.get(owner);
resetUuidMap.put(owner, InactivityExpirationGroup.getBestResetAfterMs(oPlayerOwner, regionWorld));
takeoverUuidMap.put(owner, InactivityExpirationGroup.getBestTakeOverAfterMs(oPlayerOwner, regionWorld));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import net.alex9849.arm.Permission;
import net.alex9849.arm.adapters.WGRegion;
import net.alex9849.arm.adapters.signs.SignData;
import net.alex9849.arm.adapters.util.OfflinePlayerCache;
import net.alex9849.arm.events.PreExtendEvent;
import net.alex9849.arm.exceptions.*;
import net.alex9849.arm.regions.price.Autoprice.AutoPrice;
Expand Down Expand Up @@ -74,7 +75,7 @@ public void updateRegion() {
if (owner == null) {
this.extend();
} else {
OfflinePlayer oplayer = Bukkit.getOfflinePlayer(owner);
OfflinePlayer oplayer = OfflinePlayerCache.get(owner);
if (AdvancedRegionMarket.getInstance().getEcon().hasAccount(oplayer)) {
PreExtendEvent preExtendEvent = new PreExtendEvent(this);
Bukkit.getServer().getPluginManager().callEvent(preExtendEvent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import net.alex9849.arm.Permission;
import net.alex9849.arm.adapters.WGRegion;
import net.alex9849.arm.adapters.signs.SignData;
import net.alex9849.arm.adapters.util.OfflinePlayerCache;
import net.alex9849.arm.adapters.util.Saveable;
import net.alex9849.arm.adapters.util.StringReplacer;
import net.alex9849.arm.adapters.util.TimeUtil;
Expand Down Expand Up @@ -687,7 +688,7 @@ protected HashMap<String, Supplier<String>> getVariableReplacements() {
variableReplacements.put("%isinactivityreset%", () ->
Messages.convertYesNo(this.isInactivityReset()));
variableReplacements.put("%landlord%", () ->
Messages.getStringValue(this.getLandlord(), x -> Messages.getStringValue(Bukkit.getOfflinePlayer(x).getName(), y -> y, Messages.UNKNOWN_UUID), Messages.LANDLORD_SERVER));
Messages.getStringValue(this.getLandlord(), x -> Messages.getStringValue(OfflinePlayerCache.get(x).getName(), y -> y, Messages.UNKNOWN_UUID), Messages.LANDLORD_SERVER));
variableReplacements.put("%lastownerlogin%", () -> {
if (this.getLastLogin() == 0) {
return Messages.NEVER;
Expand All @@ -714,7 +715,7 @@ protected HashMap<String, Supplier<String>> getVariableReplacements() {
Messages.getStringList(this.subregions, x -> x.getRegion().getId(), ", "));
variableReplacements.put("%members%", () ->
Messages.getStringList(this.getRegion().getMembers(), x ->
Messages.getStringValue(Bukkit.getOfflinePlayer(x).getName(), y -> y, Messages.UNKNOWN_UUID)
Messages.getStringValue(OfflinePlayerCache.get(x).getName(), y -> y, Messages.UNKNOWN_UUID)
, ", "));
variableReplacements.put("%takeoverin-date%", () ->
this.getTakeoverCountdown(true, false, false));
Expand Down Expand Up @@ -806,7 +807,7 @@ public String getOwnerName() {
return null;
}
if (this.ownerName == null || this.ownerUUID != ownerUUID) {
this.ownerName = Bukkit.getOfflinePlayer(ownerUUID).getName();
this.ownerName = OfflinePlayerCache.get(ownerUUID).getName();
}
return this.ownerName;
}
Expand All @@ -816,7 +817,7 @@ protected void giveLandlordMoney(double amount) {
if (landlordUUID == null) {
return;
}
OfflinePlayer subRegionOwner = Bukkit.getOfflinePlayer(landlordUUID);
OfflinePlayer subRegionOwner = OfflinePlayerCache.get(landlordUUID);
AdvancedRegionMarket.getInstance().getEcon().depositPlayer(subRegionOwner, amount);
}

Expand Down Expand Up @@ -1063,12 +1064,12 @@ public void userSell(Player player, boolean noMoney) throws SchematicNotFoundExc

if (!noMoney && amount > 0 && owner != null) {
if (landlord != null) {
if (AdvancedRegionMarket.getInstance().getEcon().getBalance(Bukkit.getOfflinePlayer(landlord)) < amount) {
if (AdvancedRegionMarket.getInstance().getEcon().getBalance(OfflinePlayerCache.get(landlord)) < amount) {
throw new NotEnoughMoneyException(this.replaceVariables(Messages.SELLBACK_LANDLORD_NOT_ENOUGH_MONEY));
}
AdvancedRegionMarket.getInstance().getEcon().withdrawPlayer(Bukkit.getOfflinePlayer(landlord), amount);
AdvancedRegionMarket.getInstance().getEcon().withdrawPlayer(OfflinePlayerCache.get(landlord), amount);
}
AdvancedRegionMarket.getInstance().getEcon().depositPlayer(Bukkit.getOfflinePlayer(owner), amount);
AdvancedRegionMarket.getInstance().getEcon().depositPlayer(OfflinePlayerCache.get(owner), amount);
}

this.automaticResetRegion(ActionReason.USER_SELL, true);
Expand Down Expand Up @@ -1329,7 +1330,7 @@ public List<String> tabCompleteRegionMembers(String args) {

List<UUID> uuidList = this.getRegion().getMembers();
for (UUID uuids : uuidList) {
OfflinePlayer oplayer = Bukkit.getOfflinePlayer(uuids);
OfflinePlayer oplayer = OfflinePlayerCache.get(uuids);
if (oplayer != null && oplayer.getName().toLowerCase().startsWith(args)) {
returnme.add(oplayer.getName());
}
Expand Down

0 comments on commit fb04025

Please sign in to comment.