Merge branch 'master' into fix-fabric-world-manager-performance

This commit is contained in:
chris 2024-08-06 01:37:03 +02:00 committed by GitHub
commit 293bad55bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
128 changed files with 3074 additions and 2020 deletions

View file

@ -25,30 +25,21 @@
package org.geysermc.geyser.platform.mod;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.entity.player.Player;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserBootstrap;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.api.command.Command;
import org.geysermc.geyser.api.extension.Extension;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandManager;
import org.geysermc.geyser.command.CommandRegistry;
import org.geysermc.geyser.configuration.GeyserConfiguration;
import org.geysermc.geyser.dump.BootstrapDumpInfo;
import org.geysermc.geyser.level.WorldManager;
import org.geysermc.geyser.ping.GeyserLegacyPingPassthrough;
import org.geysermc.geyser.ping.IGeyserPingPassthrough;
import org.geysermc.geyser.platform.mod.command.GeyserModCommandExecutor;
import org.geysermc.geyser.platform.mod.platform.GeyserModPlatform;
import org.geysermc.geyser.platform.mod.world.GeyserModWorldManager;
import org.geysermc.geyser.text.GeyserLocale;
@ -59,7 +50,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.SocketAddress;
import java.nio.file.Path;
import java.util.Map;
import java.util.UUID;
@RequiredArgsConstructor
@ -70,13 +60,15 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
private final GeyserModPlatform platform;
@Getter
private GeyserImpl geyser;
private Path dataFolder;
@Setter
@Setter @Getter
private MinecraftServer server;
private GeyserCommandManager geyserCommandManager;
@Setter
private CommandRegistry commandRegistry;
private GeyserModConfiguration geyserConfig;
private GeyserModInjector geyserInjector;
private final GeyserModLogger geyserLogger = new GeyserModLogger();
@ -94,10 +86,6 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
this.geyserLogger.setDebug(geyserConfig.isDebugMode());
GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger);
this.geyser = GeyserImpl.load(this.platform.platformType(), this);
// Create command manager here, since the permission handler on neo needs it
this.geyserCommandManager = new GeyserCommandManager(geyser);
this.geyserCommandManager.init();
}
public void onGeyserEnable() {
@ -130,50 +118,6 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
if (isServer()) {
this.geyserInjector.initializeLocalChannel(this);
}
// Start command building
// Set just "geyser" as the help command
GeyserModCommandExecutor helpExecutor = new GeyserModCommandExecutor(geyser,
(GeyserCommand) geyser.commandManager().getCommands().get("help"));
LiteralArgumentBuilder<CommandSourceStack> builder = Commands.literal("geyser").executes(helpExecutor);
// Register all subcommands as valid
for (Map.Entry<String, Command> command : geyser.commandManager().getCommands().entrySet()) {
GeyserModCommandExecutor executor = new GeyserModCommandExecutor(geyser, (GeyserCommand) command.getValue());
builder.then(Commands.literal(command.getKey())
.executes(executor)
// Could also test for Bedrock but depending on when this is called it may backfire
.requires(executor::testPermission)
// Allows parsing of arguments; e.g. for /geyser dump logs or the connectiontest command
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> executor.runWithArgs(context, StringArgumentType.getString(context, "args")))
.requires(executor::testPermission)));
}
server.getCommands().getDispatcher().register(builder);
// Register extension commands
for (Map.Entry<Extension, Map<String, Command>> extensionMapEntry : geyser.commandManager().extensionCommands().entrySet()) {
Map<String, Command> extensionCommands = extensionMapEntry.getValue();
if (extensionCommands.isEmpty()) {
continue;
}
// Register help command for just "/<extensionId>"
GeyserModCommandExecutor extensionHelpExecutor = new GeyserModCommandExecutor(geyser,
(GeyserCommand) extensionCommands.get("help"));
LiteralArgumentBuilder<CommandSourceStack> extCmdBuilder = Commands.literal(extensionMapEntry.getKey().description().id()).executes(extensionHelpExecutor);
for (Map.Entry<String, Command> command : extensionCommands.entrySet()) {
GeyserModCommandExecutor executor = new GeyserModCommandExecutor(geyser, (GeyserCommand) command.getValue());
extCmdBuilder.then(Commands.literal(command.getKey())
.executes(executor)
.requires(executor::testPermission)
.then(Commands.argument("args", StringArgumentType.greedyString())
.executes(context -> executor.runWithArgs(context, StringArgumentType.getString(context, "args")))
.requires(executor::testPermission)));
}
server.getCommands().getDispatcher().register(extCmdBuilder);
}
}
@Override
@ -206,8 +150,8 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
}
@Override
public GeyserCommandManager getGeyserCommandManager() {
return geyserCommandManager;
public CommandRegistry getCommandRegistry() {
return commandRegistry;
}
@Override
@ -235,6 +179,7 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
return this.server.getServerVersion();
}
@SuppressWarnings("ConstantConditions") // Certain IDEA installations think that ip cannot be null
@NonNull
@Override
public String getServerBindAddress() {
@ -270,10 +215,6 @@ public abstract class GeyserModBootstrap implements GeyserBootstrap {
return this.platform.resolveResource(resource);
}
public abstract boolean hasPermission(@NonNull Player source, @NonNull String permissionNode);
public abstract boolean hasPermission(@NonNull CommandSourceStack source, @NonNull String permissionNode, int permissionLevel);
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean loadConfig() {
try {

View file

@ -25,17 +25,18 @@
package org.geysermc.geyser.platform.mod;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.world.entity.player.Player;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.platform.mod.command.ModCommandSender;
import org.geysermc.geyser.Permissions;
import org.geysermc.geyser.platform.mod.command.ModCommandSource;
import org.geysermc.geyser.util.VersionCheckUtils;
public final class GeyserModUpdateListener {
public static void onPlayReady(Player player) {
CommandSourceStack stack = player.createCommandSourceStack();
if (GeyserModBootstrap.getInstance().hasPermission(stack, Constants.UPDATE_PERMISSION, 2)) {
VersionCheckUtils.checkForGeyserUpdate(() -> new ModCommandSender(stack));
// Should be creating this in the supplier, but we need it for the permission check.
// Not a big deal currently because ModCommandSource doesn't load locale, so don't need to try to wait for it.
ModCommandSource source = new ModCommandSource(player.createCommandSourceStack());
if (source.hasPermission(Permissions.CHECK_UPDATE)) {
VersionCheckUtils.checkForGeyserUpdate(() -> source);
}
}

View file

@ -1,75 +0,0 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
*
* 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.mod.command;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandExecutor;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import java.util.Collections;
public class GeyserModCommandExecutor extends GeyserCommandExecutor implements Command<CommandSourceStack> {
private final GeyserCommand command;
public GeyserModCommandExecutor(GeyserImpl geyser, GeyserCommand command) {
super(geyser, Collections.singletonMap(command.name(), command));
this.command = command;
}
public boolean testPermission(CommandSourceStack source) {
return GeyserModBootstrap.getInstance().hasPermission(source, command.permission(), command.isSuggestedOpOnly() ? 2 : 0);
}
@Override
public int run(CommandContext<CommandSourceStack> context) {
return runWithArgs(context, "");
}
public int runWithArgs(CommandContext<CommandSourceStack> context, String args) {
CommandSourceStack source = context.getSource();
ModCommandSender sender = new ModCommandSender(source);
GeyserSession session = getGeyserSession(sender);
if (!testPermission(source)) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.permission_fail", sender.locale()));
return 0;
}
if (command.isBedrockOnly() && session == null) {
sender.sendMessage(ChatColor.RED + GeyserLocale.getPlayerLocaleString("geyser.bootstrap.command.bedrock_only", sender.locale()));
return 0;
}
command.execute(session, sender, args.split(" "));
return 0;
}
}

View file

@ -31,19 +31,21 @@ import net.minecraft.core.RegistryAccess;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.text.ChatColor;
import java.util.Objects;
import java.util.UUID;
public class ModCommandSender implements GeyserCommandSource {
public class ModCommandSource implements GeyserCommandSource {
private final CommandSourceStack source;
public ModCommandSender(CommandSourceStack source) {
public ModCommandSource(CommandSourceStack source) {
this.source = source;
// todo find locale?
}
@Override
@ -75,8 +77,24 @@ public class ModCommandSender implements GeyserCommandSource {
return !(source.getEntity() instanceof ServerPlayer);
}
@Override
public @Nullable UUID playerUuid() {
if (source.getEntity() instanceof ServerPlayer player) {
return player.getUUID();
}
return null;
}
@Override
public boolean hasPermission(String permission) {
return GeyserModBootstrap.getInstance().hasPermission(source, permission, source.getServer().getOperatorUserPermissionLevel());
// Unlike other bootstraps; we delegate to cloud here too:
// On NeoForge; we'd have to keep track of all PermissionNodes - cloud already does that
// For Fabric, we won't need to include the Fabric Permissions API anymore - cloud already does that too :p
return GeyserImpl.getInstance().commandRegistry().hasPermission(this, permission);
}
@Override
public Object handle() {
return source;
}
}

View file

@ -0,0 +1,127 @@
/*
* Copyright (c) 2024 GeyserMC. http://geysermc.org
*
* 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.platform.mod.mixin.server;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Share;
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.piston.PistonBaseBlock;
import net.minecraft.world.level.block.state.BlockState;
import org.cloudburstmc.math.vector.Vector3i;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.PistonCache;
import org.geysermc.geyser.translator.level.block.entity.PistonBlockEntity;
import org.geysermc.mcprotocollib.protocol.data.game.level.block.value.PistonValueType;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Mixin(PistonBaseBlock.class)
public class PistonBaseBlockMixin {
@Shadow
@Final
private boolean isSticky;
@ModifyExpressionValue(method = "moveBlocks",
at = @At(value = "INVOKE", target = "Lcom/google/common/collect/Maps;newHashMap()Ljava/util/HashMap;")
)
private HashMap<BlockPos, BlockState> geyser$onMapCreate(HashMap<BlockPos, BlockState> original, @Share("pushBlocks") LocalRef<Map<BlockPos, BlockState>> localRef) {
localRef.set(original);
return original;
}
@Inject(method = "moveBlocks",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/piston/PistonStructureResolver;getToDestroy()Ljava/util/List;")
)
private void geyser$onBlocksMove(Level level, BlockPos blockPos, Direction direction, boolean isExtending, CallbackInfoReturnable<Boolean> cir, @Share("pushBlocks") LocalRef<Map<BlockPos, BlockState>> localRef) {
PistonValueType type = isExtending ? PistonValueType.PUSHING : PistonValueType.PULLING;
boolean sticky = this.isSticky;
Object2ObjectMap<Vector3i, org.geysermc.geyser.level.block.type.BlockState> attachedBlocks = new Object2ObjectArrayMap<>();
boolean blocksFilled = false;
for (Map.Entry<UUID, GeyserSession> entry : GeyserImpl.getInstance().getSessionManager().getSessions().entrySet()) {
Player player = level.getPlayerByUUID(entry.getKey());
//noinspection resource
if (player == null || !player.level().equals(level)) {
continue;
}
GeyserSession session = entry.getValue();
int dX = Math.abs(blockPos.getX() - player.getBlockX()) >> 4;
int dZ = Math.abs(blockPos.getZ() - player.getBlockZ()) >> 4;
if ((dX * dX + dZ * dZ) > session.getServerRenderDistance() * session.getServerRenderDistance()) {
// Ignore pistons outside the player's render distance
continue;
}
// Trying to grab the blocks from the world like other platforms would result in the moving piston block
// being returned instead.
if (!blocksFilled) {
Map<BlockPos, net.minecraft.world.level.block.state.BlockState> blocks = localRef.get();
for (Map.Entry<BlockPos, BlockState> blockStateEntry : blocks.entrySet()) {
int blockStateId = Block.BLOCK_STATE_REGISTRY.getId(blockStateEntry.getValue());
org.geysermc.geyser.level.block.type.BlockState state = org.geysermc.geyser.level.block.type.BlockState.of(blockStateId);
attachedBlocks.put(geyser$fromBlockPos(blockStateEntry.getKey()), state);
}
blocksFilled = true;
}
org.geysermc.geyser.level.physics.Direction orientation = org.geysermc.geyser.level.physics.Direction.VALUES[direction.ordinal()];
Vector3i position = geyser$fromBlockPos(blockPos);
session.executeInEventLoop(() -> {
PistonCache pistonCache = session.getPistonCache();
PistonBlockEntity blockEntity = pistonCache.getPistons().computeIfAbsent(position, pos ->
new PistonBlockEntity(session, position, orientation, sticky, !isExtending));
blockEntity.setAction(type, attachedBlocks);
});
}
}
@Unique
private static Vector3i geyser$fromBlockPos(BlockPos pos) {
return Vector3i.from(pos.getX(), pos.getY(), pos.getZ());
}
}

View file

@ -49,7 +49,6 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3i;
import org.geysermc.geyser.level.GeyserWorldManager;
import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.platform.mod.GeyserModBootstrap;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.MinecraftKey;
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
@ -113,12 +112,6 @@ public class GeyserModWorldManager extends GeyserWorldManager {
return SharedConstants.getCurrentVersion().getProtocolVersion() == GameProtocol.getJavaProtocolVersion();
}
@Override
public boolean hasPermission(GeyserSession session, String permission) {
ServerPlayer player = getPlayer(session);
return GeyserModBootstrap.getInstance().hasPermission(player, permission);
}
@Override
public GameMode getDefaultGameMode(GeyserSession session) {
return GameMode.byId(server.getDefaultGameType().getId());

View file

@ -5,6 +5,7 @@
"compatibilityLevel": "JAVA_17",
"mixins": [
"server.BlockPlaceMixin",
"server.PistonBaseBlockMixin",
"server.ServerConnectionListenerMixin"
],
"server": [