diff --git a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauMemberUpdate.java b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauMemberUpdate.java index d3416e08..d8827c4b 100644 --- a/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauMemberUpdate.java +++ b/BauSystem/BauSystem_Main/src/de/steamwar/bausystem/features/world/BauMemberUpdate.java @@ -28,6 +28,7 @@ import de.steamwar.bausystem.utils.bossbar.BossBarService; import de.steamwar.linkage.Linked; import de.steamwar.network.packets.PacketHandler; import de.steamwar.network.packets.server.BaumemberUpdatePacket; +import de.steamwar.sql.BauweltMember; import io.papermc.paper.event.player.PlayerPickBlockEvent; import org.bukkit.Bukkit; import org.bukkit.boss.BarColor; @@ -57,6 +58,8 @@ public class BauMemberUpdate extends PacketHandler implements Listener { } public static void baumemberUpdate() { + // Packet handlers run in no guaranteed order, so don't rely on CoreNetworkHandler having cleared the cache yet. + BauweltMember.clear(); Set newSpectator = new HashSet<>(); Set newBuilder = new HashSet<>(); Bukkit.getOnlinePlayers().forEach(player -> { diff --git a/CommonCore/SQL/src/de/steamwar/sql/BauweltMember.kt b/CommonCore/SQL/src/de/steamwar/sql/BauweltMember.kt index 8c94f52b..71c63d5c 100644 --- a/CommonCore/SQL/src/de/steamwar/sql/BauweltMember.kt +++ b/CommonCore/SQL/src/de/steamwar/sql/BauweltMember.kt @@ -30,6 +30,7 @@ import org.jetbrains.exposed.v1.dao.CompositeEntity import org.jetbrains.exposed.v1.dao.CompositeEntityClass import org.jetbrains.exposed.v1.jdbc.insertIgnore import java.util.* +import java.util.concurrent.ConcurrentHashMap object BauweltMemberTable : CompositeIdTable("BauweltMember") { val bauweltId = reference("BauweltID", SteamwarUserTable).index() @@ -48,10 +49,12 @@ object BauweltMemberTable : CompositeIdTable("BauweltMember") { class BauweltMember(id: EntityID) : CompositeEntity(id) { companion object : CompositeEntityClass(BauweltMemberTable) { - private val cache = mutableMapOf() + // Keyed by (bauweltId, memberId). Optional.empty() caches "is not a member", which is the + // common case for spectators and would otherwise hit the DB on every permission check. + private val cache = ConcurrentHashMap, Optional>() private fun cache(member: BauweltMember) = - cache.put(member.memberID, member) + cache.put(member.bauweltID to member.memberID, Optional.of(member)) @JvmStatic fun clear() = @@ -75,20 +78,20 @@ class BauweltMember(id: EntityID) : CompositeEntity(id) { it[worldEdit] = false it[world] = false } - } + }.also { cache.remove(ownerId.value to newMemberId.value) } @JvmStatic @Deprecated("Use getBauMember(bauwelt: Int, member: Int)") fun getBauMember(bauwelt: UUID, member: UUID) = - useDb { - find { (bauweltId eq SteamwarUser.get(bauwelt)!!.id) and (BauweltMemberTable.memberId eq SteamwarUser.get(member)!!.id) }.firstOrNull()?.also { cache(it) } - } + getBauMember(SteamwarUser.get(bauwelt)!!.id.value, SteamwarUser.get(member)!!.id.value) @JvmStatic - fun getBauMember(bauwelt: Int, member: Int) = - useDb { - find { (bauweltId eq bauwelt) and (BauweltMemberTable.memberId eq member) }.firstOrNull()?.also { cache(it) } - } + fun getBauMember(bauwelt: Int, member: Int): BauweltMember? = + cache.computeIfAbsent(bauwelt to member) { + Optional.ofNullable(useDb { + find { (bauweltId eq bauwelt) and (BauweltMemberTable.memberId eq member) }.firstOrNull() + }) + }.orElse(null) @JvmStatic @Deprecated("Use getMembers(bauwelt: Int)") @@ -140,8 +143,11 @@ class BauweltMember(id: EntityID) : CompositeEntity(id) { fun isWorld() = world - fun remove() = + fun remove() { + val key = bauweltID to memberID useDb { delete() } + cache.remove(key) + } } \ No newline at end of file diff --git a/VelocityCore/src/de/steamwar/velocitycore/commands/BauCommand.java b/VelocityCore/src/de/steamwar/velocitycore/commands/BauCommand.java index d97e1e46..04529020 100644 --- a/VelocityCore/src/de/steamwar/velocitycore/commands/BauCommand.java +++ b/VelocityCore/src/de/steamwar/velocitycore/commands/BauCommand.java @@ -19,6 +19,7 @@ package de.steamwar.velocitycore.commands; +import com.velocitypowered.api.proxy.server.RegisteredServer; import de.steamwar.command.PreviousArguments; import de.steamwar.command.SWCommand; import de.steamwar.command.TypeMapper; @@ -45,7 +46,9 @@ import de.steamwar.velocitycore.network.NetworkSender; import de.steamwar.velocitycore.util.BauLock; import java.io.File; +import java.util.ArrayList; import java.util.Collection; +import java.util.UUID; import java.util.function.Consumer; @Linked @@ -72,6 +75,7 @@ public class BauCommand extends SWCommand { @Register(value = "addmember", description = "BAU_ADDMEMBER_USAGE") public void addmember(Chatter sender, @Validator("addMemberTarget") SteamwarUser target) { BauweltMember.addMember(sender.user().getUUID(), target.getUUID()); + sendBaumemberUpdate(sender.user().getUUID()); sender.system("BAU_ADDMEMBER_ADDED"); Chatter.of(target.getUUID()).system("BAU_ADDMEMBER_ADDED_TARGET", sender); } @@ -161,10 +165,7 @@ public class BauCommand extends SWCommand { withMember(owner, user, target -> { setter.accept(target); - Bauserver bauserver = Bauserver.get(owner.user().getUUID()); - if (bauserver != null) { - bauserver.getRegisteredServer().getPlayersConnected().stream().findAny().ifPresent(player -> NetworkSender.send(player, new BaumemberUpdatePacket())); - } + sendBaumemberUpdate(owner.user().getUUID()); Chatter.of(user.getUUID()).system("BAU_MEMBER_SET_TARGET", owner, new Message(permName)); owner.system("BAU_MEMBER_SET", new Message(permName)); @@ -184,9 +185,7 @@ public class BauCommand extends SWCommand { } }); - if (bauserver != null) { - bauserver.getRegisteredServer().getPlayersConnected().stream().findAny().ifPresent(player -> NetworkSender.send(player, new BaumemberUpdatePacket())); - } + sendBaumemberUpdate(owner.user().getUUID()); try { deletePlayerData(owner, user); @@ -289,6 +288,23 @@ public class BauCommand extends SWCommand { BauLock.setLocked(sender, BauLockState.OPEN); } + /** + * Tells the owner's Bauserver and every dev server to drop their cached {@link BauweltMember}s, + * since those run in separate processes and would otherwise keep serving stale permissions. + * Plugin messages need a connected player, so servers without players are skipped. + */ + private static void sendBaumemberUpdate(UUID owner) { + Bauserver bauserver = Bauserver.get(owner); + if (bauserver != null) { + sendBaumemberUpdate(bauserver.getRegisteredServer()); + } + new ArrayList<>(DevCommand.getDevServers().values()).forEach(BauCommand::sendBaumemberUpdate); + } + + private static void sendBaumemberUpdate(RegisteredServer server) { + server.getPlayersConnected().stream().findAny().ifPresent(player -> NetworkSender.send(player, new BaumemberUpdatePacket())); + } + private static void withMember(Chatter owner, SteamwarUser member, Consumer function) { if (member == null) { owner.system("UNKNOWN_PLAYER");