Finish implementing the first live-api for sponge

This commit is contained in:
Blue (Lukas Rieger) 2020-08-06 17:24:36 +02:00
parent b64941783f
commit b286670953
10 changed files with 245 additions and 187 deletions

View File

@ -43,7 +43,7 @@
import org.bukkit.plugin.java.JavaPlugin;
import de.bluecolored.bluemap.common.plugin.Plugin;
import de.bluecolored.bluemap.common.plugin.serverinterface.PlayerState;
import de.bluecolored.bluemap.common.plugin.serverinterface.Player;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
import de.bluecolored.bluemap.core.logger.Logger;
@ -184,13 +184,13 @@ public static BukkitPlugin getInstance() {
}
@Override
public Collection<PlayerState> getOnlinePlayers() {
public Collection<Player> getOnlinePlayers() {
// TODO Implement
return Collections.emptyList();
}
@Override
public Optional<PlayerState> getPlayer(UUID uuid) {
public Optional<Player> getPlayer(UUID uuid) {
// TODO Implement
return Optional.empty();
}

View File

@ -32,7 +32,7 @@
import com.google.gson.stream.JsonWriter;
import de.bluecolored.bluemap.common.plugin.serverinterface.Gamemode;
import de.bluecolored.bluemap.common.plugin.serverinterface.PlayerState;
import de.bluecolored.bluemap.common.plugin.serverinterface.Player;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
import de.bluecolored.bluemap.core.webserver.HttpRequest;
import de.bluecolored.bluemap.core.webserver.HttpRequestHandler;
@ -51,6 +51,7 @@ public LiveAPIRequestHandler(ServerInterface server, HttpRequestHandler notFound
this.liveAPIRequests = new HashMap<>();
this.liveAPIRequests.put("live", this::handleLivePingRequest);
this.liveAPIRequests.put("live/players", this::handlePlayersRequest);
}
@ -62,6 +63,12 @@ public HttpResponse handle(HttpRequest request) {
return this.notFoundHandler.handle(request);
}
public HttpResponse handleLivePingRequest(HttpRequest request) {
HttpResponse response = new HttpResponse(HttpStatusCode.OK);
response.setData("{\"status\":\"OK\"}");
return response;
}
public HttpResponse handlePlayersRequest(HttpRequest request) {
if (!request.getMethod().equalsIgnoreCase("GET")) return new HttpResponse(HttpStatusCode.BAD_REQUEST);
@ -72,7 +79,7 @@ public HttpResponse handlePlayersRequest(HttpRequest request) {
json.beginObject();
json.name("players").beginArray();
for (PlayerState player : server.getOnlinePlayers()) {
for (Player player : server.getOnlinePlayers()) {
if (player.isInvisible()) continue;
if (player.isSneaking()) continue;

View File

@ -27,85 +27,38 @@
import java.util.UUID;
import com.flowpowered.math.vector.Vector3d;
import com.google.common.base.MoreObjects;
import de.bluecolored.bluemap.common.plugin.text.Text;
public class PlayerState {
public interface Player {
private final UUID uuid;
protected Text name;
protected UUID world;
protected Vector3d position = Vector3d.ZERO;
protected boolean online = false;
protected boolean sneaking = false;
protected boolean invisible = false;
protected Gamemode gamemode = Gamemode.SURVIVAL;
public UUID getUuid();
public PlayerState(UUID uuid, Text name, UUID world, Vector3d position) {
this.uuid = uuid;
this.name = name;
this.world = world;
this.position = position;
}
public Text getName();
public UUID getUuid() {
return uuid;
}
public UUID getWorld();
public Text getName() {
return name;
}
public Vector3d getPosition();
public UUID getWorld() {
return world;
}
public Vector3d getPosition() {
return position;
}
public boolean isOnline() {
return online;
}
public boolean isOnline();
/**
* Return <code>true</code> if the player is sneaking.
* <p><i>If the player is offline the value of this method is undetermined.</i></p>
* @return
*/
public boolean isSneaking() {
return sneaking;
}
public boolean isSneaking();
/**
* Returns <code>true</code> if the player has an invisibillity effect
* <p><i>If the player is offline the value of this method is undetermined.</i></p>
*/
public boolean isInvisible() {
return invisible;
}
public boolean isInvisible();
/**
* Returns the {@link Gamemode} this player is in
* <p><i>If the player is offline the value of this method is undetermined.</i></p>
*/
public Gamemode getGamemode() {
return gamemode;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("uuid", uuid)
//.add("name", name)
//.add("world", world)
//.add("position", position)
//.add("online", online)
//.add("sneaking", sneaking)
//.add("invisible", invisible)
//.add("gamemode", gamemode)
.toString();
}
public Gamemode getGamemode();
}

View File

@ -76,15 +76,15 @@ default boolean isMetricsEnabled(boolean configValue) {
}
/**
* Returns a collection of players that are currently online
* Returns a collection of the states of players that are currently online
*/
Collection<PlayerState> getOnlinePlayers();
Collection<Player> getOnlinePlayers();
/**
* Returns the player with that UUID if present<br>
* this method is only guaranteed to return a player if the player is currently online.
* Returns the state of the player with that UUID if present<br>
* this method is only guaranteed to return a {@link PlayerState} if the player is currently online.
*/
Optional<PlayerState> getPlayer(UUID uuid);
Optional<Player> getPlayer(UUID uuid);
}

View File

@ -136,6 +136,7 @@ public void setFrom(TileRenderer tileRenderer, String mapId) {
public void setFrom(World world, String mapId) {
set(world.getSpawnPoint().getX(), "maps", mapId, "startPos", "x");
set(world.getSpawnPoint().getZ(), "maps", mapId, "startPos", "z");
set(world.getUUID().toString(), "maps", mapId, "world");
}
public void setFrom(MapConfig mapConfig, String mapId) {

View File

@ -26,7 +26,10 @@
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
@ -39,6 +42,7 @@
import de.bluecolored.bluemap.common.plugin.Plugin;
import de.bluecolored.bluemap.common.plugin.commands.Commands;
import de.bluecolored.bluemap.common.plugin.serverinterface.Player;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
import de.bluecolored.bluemap.core.logger.Logger;
@ -147,4 +151,16 @@ public File getConfigFolder() {
return new File("config/bluemap");
}
@Override
public Collection<Player> getOnlinePlayers() {
// TODO Implement
return Collections.emptyList();
}
@Override
public Optional<Player> getPlayer(UUID uuid) {
// TODO Implement
return Optional.empty();
}
}

View File

@ -44,7 +44,7 @@
import de.bluecolored.bluemap.common.plugin.Plugin;
import de.bluecolored.bluemap.common.plugin.commands.Commands;
import de.bluecolored.bluemap.common.plugin.serverinterface.PlayerState;
import de.bluecolored.bluemap.common.plugin.serverinterface.Player;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
import de.bluecolored.bluemap.core.logger.Logger;
@ -237,13 +237,13 @@ public Commands<CommandSource> getCommands() {
}
@Override
public Collection<PlayerState> getOnlinePlayers() {
public Collection<Player> getOnlinePlayers() {
// TODO Implement
return Collections.emptyList();
}
@Override
public Optional<PlayerState> getPlayer(UUID uuid) {
public Optional<Player> getPlayer(UUID uuid) {
// TODO Implement
return Optional.empty();
}

View File

@ -0,0 +1,151 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.sponge;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.data.key.Keys;
import org.spongepowered.api.effect.potion.PotionEffect;
import org.spongepowered.api.effect.potion.PotionEffectTypes;
import org.spongepowered.api.entity.living.player.gamemode.GameMode;
import org.spongepowered.api.entity.living.player.gamemode.GameModes;
import com.flowpowered.math.vector.Vector3d;
import de.bluecolored.bluemap.common.plugin.serverinterface.Gamemode;
import de.bluecolored.bluemap.common.plugin.serverinterface.Player;
import de.bluecolored.bluemap.common.plugin.text.Text;
public class SpongePlayer implements Player {
private static final Map<GameMode, Gamemode> GAMEMODE_MAP = new HashMap<>(5);
static {
GAMEMODE_MAP.put(GameModes.ADVENTURE, Gamemode.ADVENTURE);
GAMEMODE_MAP.put(GameModes.SURVIVAL, Gamemode.SURVIVAL);
GAMEMODE_MAP.put(GameModes.CREATIVE, Gamemode.CREATIVE);
GAMEMODE_MAP.put(GameModes.SPECTATOR, Gamemode.SPECTATOR);
GAMEMODE_MAP.put(GameModes.NOT_SET, Gamemode.SURVIVAL);
}
private UUID uuid;
private Text name;
private UUID world;
private Vector3d position;
private boolean online;
private boolean sneaking;
private boolean invisible;
private Gamemode gamemode;
private WeakReference<org.spongepowered.api.entity.living.player.Player> delegate;
public SpongePlayer(org.spongepowered.api.entity.living.player.Player delegate) {
this.uuid = delegate.getUniqueId();
this.delegate = new WeakReference<>(delegate);
update();
}
@Override
public UUID getUuid() {
return this.uuid;
}
@Override
public Text getName() {
return this.name;
}
@Override
public UUID getWorld() {
return this.world;
}
@Override
public Vector3d getPosition() {
return this.position;
}
@Override
public boolean isOnline() {
return this.online;
}
@Override
public boolean isSneaking() {
return this.sneaking;
}
@Override
public boolean isInvisible() {
return this.invisible;
}
@Override
public Gamemode getGamemode() {
return this.gamemode;
}
/**
* API access, only call on server thread!
*/
public void update() {
org.spongepowered.api.entity.living.player.Player player = delegate.get();
if (player == null) {
player = Sponge.getServer().getPlayer(uuid).orElse(null);
if (player == null) {
this.online = false;
return;
}
delegate = new WeakReference<>(player);
}
this.gamemode = GAMEMODE_MAP.get(player.get(Keys.GAME_MODE).orElse(GameModes.NOT_SET));
boolean invis = player.get(Keys.VANISH).orElse(false);
if (!invis && player.get(Keys.INVISIBLE).orElse(false)) invis = true;
if (!invis) {
Optional<List<PotionEffect>> effects = player.get(Keys.POTION_EFFECTS);
if (effects.isPresent()) {
for (PotionEffect effect : effects.get()) {
if (effect.getType().equals(PotionEffectTypes.INVISIBILITY) && effect.getDuration() > 0) invis = true;
}
}
}
this.invisible = invis;
this.name = Text.of(player.getName());
this.online = player.isOnline();
this.position = player.getPosition();
this.sneaking = player.get(Keys.IS_SNEAKING).orElse(false);
this.world = player.getWorld().getUniqueId();
}
}

View File

@ -1,76 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.sponge;
import java.lang.ref.WeakReference;
import org.spongepowered.api.data.key.Keys;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.entity.living.player.gamemode.GameMode;
import org.spongepowered.api.entity.living.player.gamemode.GameModes;
import de.bluecolored.bluemap.common.plugin.serverinterface.Gamemode;
import de.bluecolored.bluemap.common.plugin.serverinterface.PlayerState;
import de.bluecolored.bluemap.common.plugin.text.Text;
public class SpongePlayerState extends PlayerState {
private WeakReference<Player> playerRef;
public SpongePlayerState(Player player) {
super(player.getUniqueId(), Text.of(player.getName()), player.getWorld().getUniqueId(), player.getPosition());
this.playerRef = new WeakReference<Player>(player);
update();
}
void update() {
Player player = playerRef.get();
if (player == null) {
this.online = false;
return;
}
this.online = player.isOnline();
if (this.online) {
this.name = Text.of(player.getName());
this.world = player.getWorld().getUniqueId();
this.position = player.getPosition();
GameMode gm = player.get(Keys.GAME_MODE).orElse(GameModes.NOT_SET);
if (gm == GameModes.SURVIVAL) this.gamemode = Gamemode.SURVIVAL;
else if (gm == GameModes.CREATIVE) this.gamemode = Gamemode.CREATIVE;
else if (gm == GameModes.SPECTATOR) this.gamemode = Gamemode.SPECTATOR;
else if (gm == GameModes.ADVENTURE) this.gamemode = Gamemode.ADVENTURE;
else this.gamemode = Gamemode.SURVIVAL;
this.invisible = player.get(Keys.INVISIBLE).orElse(false);
this.sneaking = player.get(Keys.IS_SNEAKING).orElse(false);
}
}
}

View File

@ -27,14 +27,13 @@
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.inject.Inject;
@ -48,12 +47,13 @@
import org.spongepowered.api.event.network.ClientConnectionEvent;
import org.spongepowered.api.plugin.PluginContainer;
import org.spongepowered.api.scheduler.SpongeExecutorService;
import org.spongepowered.api.scheduler.Task;
import org.spongepowered.api.util.Tristate;
import org.spongepowered.api.world.World;
import org.spongepowered.api.world.storage.WorldProperties;
import de.bluecolored.bluemap.common.plugin.Plugin;
import de.bluecolored.bluemap.common.plugin.serverinterface.PlayerState;
import de.bluecolored.bluemap.common.plugin.serverinterface.Player;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
import de.bluecolored.bluemap.core.logger.Logger;
@ -61,7 +61,7 @@
import net.querz.nbt.CompoundTag;
import net.querz.nbt.NBTUtil;
@org.spongepowered.api.plugin.Plugin(
@org.spongepowered.api.plugin.Plugin (
id = Plugin.PLUGIN_ID,
name = Plugin.PLUGIN_NAME,
authors = { "Blue (Lukas Rieger)" },
@ -80,17 +80,18 @@ public class SpongePlugin implements ServerInterface {
private Plugin bluemap;
private SpongeCommands commands;
private SpongeExecutorService syncExecutor;
private SpongeExecutorService asyncExecutor;
private long lastPlayerUpdate = -1;
private Map<UUID, PlayerState> onlinePlayers;
private int playerUpdateIndex = 0;
private Map<UUID, Player> onlinePlayerMap;
private List<SpongePlayer> onlinePlayerList;
@Inject
public SpongePlugin(org.slf4j.Logger logger) {
Logger.global = new Slf4jLogger(logger);
this.onlinePlayers = new ConcurrentHashMap<>();
this.onlinePlayerMap = new ConcurrentHashMap<>();
this.onlinePlayerList = new ArrayList<>();
this.bluemap = new Plugin("sponge", this);
this.commands = new SpongeCommands(bluemap);
@ -99,7 +100,6 @@ public SpongePlugin(org.slf4j.Logger logger) {
@Listener
public void onServerStart(GameStartingServerEvent evt) {
asyncExecutor = Sponge.getScheduler().createAsyncExecutor(this);
syncExecutor = Sponge.getScheduler().createSyncExecutor(this);
//save all world properties to generate level_sponge.dat files
for (WorldProperties properties : Sponge.getServer().getAllWorldProperties()) {
@ -111,6 +111,12 @@ public void onServerStart(GameStartingServerEvent evt) {
Sponge.getCommandManager().register(this, command, command.getLabel());
}
//start updating players
Task.builder()
.intervalTicks(1)
.execute(this::updateSomePlayers)
.submit(this);
asyncExecutor.execute(() -> {
try {
Logger.global.logInfo("Loading...");
@ -145,12 +151,16 @@ public void onServerReload(GameReloadEvent evt) {
@Listener
public void onPlayerJoin(ClientConnectionEvent.Join evt) {
onlinePlayers.put(evt.getTargetEntity().getUniqueId(), new SpongePlayerState(evt.getTargetEntity()));
SpongePlayer player = new SpongePlayer(evt.getTargetEntity());
onlinePlayerMap.put(evt.getTargetEntity().getUniqueId(), player);
onlinePlayerList.add(player);
}
@Listener
public void onPlayerLeave(ClientConnectionEvent.Disconnect evt) {
onlinePlayers.remove(evt.getTargetEntity().getUniqueId());
UUID playerUUID = evt.getTargetEntity().getUniqueId();
onlinePlayerMap.remove(playerUUID);
onlinePlayerList.removeIf(p -> p.getUuid().equals(playerUUID));
}
@Override
@ -191,38 +201,13 @@ public File getConfigFolder() {
}
@Override
public Collection<PlayerState> getOnlinePlayers() {
updatePlayers();
return onlinePlayers.values();
public Collection<Player> getOnlinePlayers() {
return onlinePlayerMap.values();
}
@Override
public Optional<PlayerState> getPlayer(UUID uuid) {
updatePlayers();
return Optional.ofNullable(onlinePlayers.get(uuid));
}
private synchronized void updatePlayers() {
if (lastPlayerUpdate + 1000 > System.currentTimeMillis()) return; //only update once a second
if (Sponge.getServer().isMainThread()) {
updatePlayersSync();
} else {
try {
syncExecutor.submit(this::updatePlayersSync).get(3, TimeUnit.SECONDS);
} catch (TimeoutException | InterruptedException ignore) {
} catch (ExecutionException e) {
Logger.global.logError("Unexpected exception trying to update player-states!", e);
}
}
lastPlayerUpdate = System.currentTimeMillis();
}
private void updatePlayersSync() {
for (PlayerState player : onlinePlayers.values()) {
if (player instanceof SpongePlayerState) ((SpongePlayerState) player).update();
}
public Optional<Player> getPlayer(UUID uuid) {
return Optional.ofNullable(onlinePlayerMap.get(uuid));
}
@Override
@ -238,4 +223,25 @@ public boolean isMetricsEnabled(boolean configValue) {
return Sponge.getMetricsConfigManager().getGlobalCollectionState().asBoolean();
}
/**
* Only update some of the online players each tick to minimize performance impact on the server-thread.
* Only call this method on the server-thread.
*/
private void updateSomePlayers() {
int onlinePlayerCount = onlinePlayerList.size();
if (onlinePlayerCount == 0) return;
int playersToBeUpdated = onlinePlayerCount / 20; //with 20 tps, each player is updated once a second
if (playersToBeUpdated == 0) playersToBeUpdated = 1;
for (int i = 0; i < playersToBeUpdated; i++) {
playerUpdateIndex++;
if (playerUpdateIndex >= 20 && playerUpdateIndex >= onlinePlayerCount) playerUpdateIndex = 0;
if (playerUpdateIndex < onlinePlayerCount) {
onlinePlayerList.get(i).update();
}
}
}
}