3
0
Mirror von https://github.com/ViaVersion/ViaVersion.git synchronisiert 2024-10-08 11:10:06 +02:00

Optimize protocol path finding

Not perfect, but better. This prevents the path checks from exponentially increasing (if it weren't for the maxProtocolPathSize fail safe).

By default, a path will never go to a protocol version that puts it farther from the desired server protocol version, even if a path existed.
Otherwise as well as previously, *all* possible paths will be checked until a fitting one is found.

Negative examples if the new boolean is set to true:
    A possible path from 3 to 5 in order of 3->10->5 will be dismissed.
    A possible path from 5 to 3 in order of 5->0->3 will be dismissed.

Negative examples if set to false:
    While searching for a path from 3 to 5, 3->2->1 could be checked first before 3->4->5 is found.
    While searching for a path from 5 to 3, 5->6->7 could be checked first before 5->4->3 is found.

Assuming custom platforms like Bedrock protocol use the normal registering methods, they will have to change the boolean to false to revert to previous behavior (tho still somewhat better optimized).
Dieser Commit ist enthalten in:
KennyTV 2021-06-01 17:52:48 +02:00
Ursprung 4011aee280
Commit 104fa4e29f
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 6BE3B555EBC5982B
10 geänderte Dateien mit 317 neuen und 46 gelöschten Zeilen

Datei anzeigen

@ -109,6 +109,7 @@ public interface ProtocolManager {
* @param protocol protocol to register * @param protocol protocol to register
* @param clientVersion supported client protocol versions * @param clientVersion supported client protocol versions
* @param serverVersion output server protocol version the protocol converts to * @param serverVersion output server protocol version the protocol converts to
* @throws IllegalArgumentException if the client protocol version is equal to the server protocol version
*/ */
void registerProtocol(Protocol protocol, ProtocolVersion clientVersion, ProtocolVersion serverVersion); void registerProtocol(Protocol protocol, ProtocolVersion clientVersion, ProtocolVersion serverVersion);
@ -118,6 +119,7 @@ public interface ProtocolManager {
* @param protocol protocol to register * @param protocol protocol to register
* @param supportedClientVersion supported client protocol versions * @param supportedClientVersion supported client protocol versions
* @param serverVersion output server protocol version the protocol converts to * @param serverVersion output server protocol version the protocol converts to
* @throws IllegalArgumentException if a supported client protocol version is equal to the server protocol version
*/ */
void registerProtocol(Protocol protocol, List<Integer> supportedClientVersion, int serverVersion); void registerProtocol(Protocol protocol, List<Integer> supportedClientVersion, int serverVersion);
@ -141,6 +143,37 @@ public interface ProtocolManager {
*/ */
@Nullable List<ProtocolPathEntry> getProtocolPath(int clientVersion, int serverVersion); @Nullable List<ProtocolPathEntry> getProtocolPath(int clientVersion, int serverVersion);
/**
* Returns whether protocol path calculation expects the path to come closer to the expected version with each entry, true by default.
* <p>
* In practice, this means a path will never go to a protocol version that puts it farther from the desired
* server protocol version, even if a path existed.
* If this is set to false, *all* possible paths will be checked until a fitting one is found.
* <p>
* Negative examples if this returns true:
* <ul>
* A possible path from 3 to 5 in order of 3->10->5 will be dismissed.
* A possible path from 5 to 3 in order of 5->0->3 will be dismissed.
* </ul>
* <p>
* Negative examples if this returns false:
* <ul>
* While searching for a path from 3 to 5, 3->2->1 could be checked first before 3->4->5 is found.
* While searching for a path from 5 to 3, 5->6->7 could be checked first before 5->4->3 is found.
* </ul>
*
* @return whether protocol path calculation expects the path to come closer to the expected version with each entry
*/
boolean onlyCheckLoweringPathEntries();
/**
* Sets whether protocol path calculation expects the path to come closer to the expected version with each entry.
*
* @param onlyCheckLoweringPathEntries whether protocol path calculation expects the path to come closer to the expected version with each entry
* @see #onlyCheckLoweringPathEntries()
*/
void setOnlyCheckLoweringPathEntries(boolean onlyCheckLoweringPathEntries);
/** /**
* Returns the maximum protocol path size applied to {@link #getProtocolPath(int, int)}. * Returns the maximum protocol path size applied to {@link #getProtocolPath(int, int)}.
* *

Datei anzeigen

@ -113,26 +113,26 @@ public class ProtocolVersion {
/** /**
* Returns whether a protocol with the given protocol version is registered. * Returns whether a protocol with the given protocol version is registered.
* *
* @param id protocol version * @param version protocol version
* @return true if this protocol version has been registered * @return true if this protocol version has been registered
*/ */
public static boolean isRegistered(int id) { public static boolean isRegistered(int version) {
return VERSIONS.containsKey(id); return VERSIONS.containsKey(version);
} }
/** /**
* Returns a {@link ProtocolVersion} instance, even if this protocol version * Returns a {@link ProtocolVersion} instance, even if this protocol version
* has not been registered. See {@link #isRegistered(int)} berorehand or {@link #isKnown()}. * has not been registered. See {@link #isRegistered(int)} berorehand or {@link #isKnown()}.
* *
* @param id protocol version * @param version protocol version
* @return registered or unknown {@link ProtocolVersion} * @return registered or unknown {@link ProtocolVersion}
*/ */
public static @NonNull ProtocolVersion getProtocol(int id) { public static @NonNull ProtocolVersion getProtocol(int version) {
ProtocolVersion protocolVersion = VERSIONS.get(id); ProtocolVersion protocolVersion = VERSIONS.get(version);
if (protocolVersion != null) { if (protocolVersion != null) {
return protocolVersion; return protocolVersion;
} else { } else {
return new ProtocolVersion(id, "Unknown (" + id + ")"); return new ProtocolVersion(version, "Unknown (" + version + ")");
} }
} }

Datei anzeigen

@ -65,8 +65,10 @@ import com.viaversion.viaversion.protocols.protocol1_9to1_8.Protocol1_9To1_8;
import com.viaversion.viaversion.protocols.protocol1_9to1_9_1.Protocol1_9To1_9_1; import com.viaversion.viaversion.protocols.protocol1_9to1_9_1.Protocol1_9To1_9_1;
import com.viaversion.viaversion.util.Pair; import com.viaversion.viaversion.util.Pair;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import us.myles.ViaVersion.api.protocol.ProtocolRegistry; import us.myles.ViaVersion.api.protocol.ProtocolRegistry;
@ -107,6 +109,7 @@ public class ProtocolManagerImpl implements ProtocolManager {
private boolean mappingsLoaded; private boolean mappingsLoaded;
private ServerProtocolVersion serverProtocolVersion = new ServerProtocolVersionSingleton(-1); private ServerProtocolVersion serverProtocolVersion = new ServerProtocolVersionSingleton(-1);
private boolean onlyCheckLoweringPathEntries = true;
private int maxProtocolPathSize = 50; private int maxProtocolPathSize = 50;
public ProtocolManagerImpl() { public ProtocolManagerImpl() {
@ -173,8 +176,11 @@ public class ProtocolManagerImpl implements ProtocolManager {
protocols.put(protocol.getClass(), protocol); protocols.put(protocol.getClass(), protocol);
for (int version : supportedClientVersion) { for (int clientVersion : supportedClientVersion) {
Int2ObjectMap<Protocol> protocolMap = registryMap.computeIfAbsent(version, s -> new Int2ObjectOpenHashMap<>(2)); // Throw an error if supported client version = server version
Preconditions.checkArgument(clientVersion != serverVersion);
Int2ObjectMap<Protocol> protocolMap = registryMap.computeIfAbsent(clientVersion, s -> new Int2ObjectOpenHashMap<>(2));
protocolMap.put(serverVersion, protocol); protocolMap.put(serverVersion, protocol);
} }
@ -212,32 +218,40 @@ public class ProtocolManagerImpl implements ProtocolManager {
supportedVersions.clear(); supportedVersions.clear();
supportedVersions.add(serverProtocolVersion.lowestSupportedVersion()); supportedVersions.add(serverProtocolVersion.lowestSupportedVersion());
for (ProtocolVersion versions : ProtocolVersion.getProtocols()) { for (ProtocolVersion version : ProtocolVersion.getProtocols()) {
List<ProtocolPathEntry> paths = getProtocolPath(versions.getVersion(), serverProtocolVersion.lowestSupportedVersion()); List<ProtocolPathEntry> protocolPath = getProtocolPath(version.getVersion(), serverProtocolVersion.lowestSupportedVersion());
if (paths == null) continue; if (protocolPath == null) continue;
supportedVersions.add(versions.getVersion());
for (ProtocolPathEntry path : paths) { supportedVersions.add(version.getVersion());
supportedVersions.add(path.getOutputProtocolVersion()); for (ProtocolPathEntry pathEntry : protocolPath) {
supportedVersions.add(pathEntry.getOutputProtocolVersion());
} }
} }
} }
@Override @Override
public @Nullable List<ProtocolPathEntry> getProtocolPath(int clientVersion, int serverVersion) { public @Nullable List<ProtocolPathEntry> getProtocolPath(int clientVersion, int serverVersion) {
ProtocolPathKey protocolKey = new ProtocolPathKeyImpl(clientVersion, serverVersion); if (clientVersion == serverVersion) return null; // Nothing to do!
// Check cache // Check cache
ProtocolPathKey protocolKey = new ProtocolPathKeyImpl(clientVersion, serverVersion);
List<ProtocolPathEntry> protocolList = pathCache.get(protocolKey); List<ProtocolPathEntry> protocolList = pathCache.get(protocolKey);
if (protocolList != null) { if (protocolList != null) {
return protocolList; return protocolList;
} }
// Generate path // Calculate path
List<ProtocolPathEntry> outputPath = getProtocolPath(new ArrayList<>(), clientVersion, serverVersion); Int2ObjectSortedMap<Protocol> outputPath = getProtocolPath(new Int2ObjectLinkedOpenHashMap<>(), clientVersion, serverVersion);
// If it found a path, cache it. if (outputPath == null) {
if (outputPath != null) { return null;
pathCache.put(protocolKey, outputPath);
} }
return outputPath;
List<ProtocolPathEntry> path = new ArrayList<>(outputPath.size());
for (Int2ObjectMap.Entry<Protocol> entry : outputPath.int2ObjectEntrySet()) {
path.add(new ProtocolPathEntryImpl(entry.getIntKey(), entry.getValue()));
}
pathCache.put(protocolKey, path);
return path;
} }
/** /**
@ -248,44 +262,41 @@ public class ProtocolManagerImpl implements ProtocolManager {
* @param serverVersion desired output version * @param serverVersion desired output version
* @return path that has been generated, null if failed * @return path that has been generated, null if failed
*/ */
private @Nullable List<ProtocolPathEntry> getProtocolPath(List<ProtocolPathEntry> current, int clientVersion, int serverVersion) { private @Nullable Int2ObjectSortedMap<Protocol> getProtocolPath(Int2ObjectSortedMap<Protocol> current, int clientVersion, int serverVersion) {
//TODO optimize?
if (clientVersion == serverVersion) return null; // We're already there
if (current.size() > maxProtocolPathSize) return null; // Fail safe, protocol too complicated. if (current.size() > maxProtocolPathSize) return null; // Fail safe, protocol too complicated.
// First check if there is any protocols for this // First, check if there is any protocols for this
Int2ObjectMap<Protocol> inputMap = registryMap.get(clientVersion); Int2ObjectMap<Protocol> toServerProtocolMap = registryMap.get(clientVersion);
if (inputMap == null) { if (toServerProtocolMap == null) {
return null; // Not supported return null; // Not supported
} }
// Next check there isn't an obvious path // Next, check if there is a direct, single Protocol path
Protocol protocol = inputMap.get(serverVersion); Protocol protocol = toServerProtocolMap.get(serverVersion);
if (protocol != null) { if (protocol != null) {
current.add(new ProtocolPathEntryImpl(serverVersion, protocol)); current.put(serverVersion, protocol);
return current; // Easy solution return current; // Easy solution
} }
// There might be a more advanced solution... So we'll see if any of the others can get us there // There might be a more advanced solution... So we'll see if any of the others can get us there
List<ProtocolPathEntry> shortest = null; Int2ObjectSortedMap<Protocol> shortest = null;
for (Int2ObjectMap.Entry<Protocol> entry : inputMap.int2ObjectEntrySet()) { for (Int2ObjectMap.Entry<Protocol> entry : toServerProtocolMap.int2ObjectEntrySet()) {
// Ensure it wasn't caught by the other loop // Ensure we don't go back to already contained versions
if (entry.getIntKey() == serverVersion) continue; int translatedToVersion = entry.getIntKey();
if (current.containsKey(translatedToVersion)) continue;
ProtocolPathEntry pathEntry = new ProtocolPathEntryImpl(entry.getIntKey(), entry.getValue()); // Check if the new version is farther away than the current client version
// Ensure no recursion if (onlyCheckLoweringPathEntries && Math.abs(serverVersion - translatedToVersion) > Math.abs(serverVersion - clientVersion)) {
if (current.contains(pathEntry)) continue; continue;
}
// Create a copy // Create a copy
List<ProtocolPathEntry> newCurrent = new ArrayList<>(current); Int2ObjectSortedMap<Protocol> newCurrent = new Int2ObjectLinkedOpenHashMap<>(current);
newCurrent.add(pathEntry); newCurrent.put(translatedToVersion, entry.getValue());
// Calculate the rest of the protocol using the current path entry // Calculate the rest of the protocol starting from translatedToVersion and take the shortest
newCurrent = getProtocolPath(newCurrent, entry.getIntKey(), serverVersion); newCurrent = getProtocolPath(newCurrent, translatedToVersion, serverVersion);
if (newCurrent != null && (shortest == null || newCurrent.size() < shortest.size())) {
// If it's shorter then choose it
if (newCurrent != null
&& (shortest == null || shortest.size() > newCurrent.size())) {
shortest = newCurrent; shortest = newCurrent;
} }
} }
@ -342,6 +353,16 @@ public class ProtocolManagerImpl implements ProtocolManager {
return Collections.unmodifiableSortedSet(new TreeSet<>(supportedVersions)); return Collections.unmodifiableSortedSet(new TreeSet<>(supportedVersions));
} }
@Override
public void setOnlyCheckLoweringPathEntries(boolean onlyCheckLoweringPathEntries) {
this.onlyCheckLoweringPathEntries = onlyCheckLoweringPathEntries;
}
@Override
public boolean onlyCheckLoweringPathEntries() {
return onlyCheckLoweringPathEntries;
}
@Override @Override
public int getMaxProtocolPathSize() { public int getMaxProtocolPathSize() {
return maxProtocolPathSize; return maxProtocolPathSize;

Datei anzeigen

@ -0,0 +1,30 @@
/*
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
* Copyright (C) 2016-2021 ViaVersion and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viaversion.common.dummy;
import com.viaversion.viaversion.ViaManagerImpl;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.protocol.ProtocolManagerImpl;
public final class DummyInitializer {
public static void init() {
Via.init(new ViaManagerImpl(new TestPlatform(), null, null, null));
((ProtocolManagerImpl) Via.getManager().getProtocolManager()).registerProtocols();
}
}

Datei anzeigen

@ -0,0 +1,47 @@
/*
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
* Copyright (C) 2016-2021 ViaVersion and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viaversion.common.dummy;
import com.viaversion.viaversion.configuration.AbstractViaConfig;
import java.io.File;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public final class TestConfig extends AbstractViaConfig {
public TestConfig(File configFile) {
super(configFile);
}
@Override
public URL getDefaultConfigURL() {
return null;
}
@Override
protected void handleConfig(Map<String, Object> config) {
}
@Override
public List<String> getUnsupportedOptions() {
return Collections.emptyList();
}
}

Datei anzeigen

@ -0,0 +1,140 @@
/*
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
* Copyright (C) 2016-2021 ViaVersion and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viaversion.common.dummy;
import com.google.gson.JsonObject;
import com.viaversion.viaversion.ViaAPIBase;
import com.viaversion.viaversion.api.ViaAPI;
import com.viaversion.viaversion.api.command.ViaCommandSender;
import com.viaversion.viaversion.api.configuration.ConfigurationProvider;
import com.viaversion.viaversion.api.configuration.ViaVersionConfig;
import com.viaversion.viaversion.api.platform.PlatformTask;
import com.viaversion.viaversion.api.platform.ViaPlatform;
import io.netty.buffer.ByteBuf;
import java.io.File;
import java.util.UUID;
import java.util.logging.Logger;
public final class TestPlatform implements ViaPlatform {
private static final Logger log = Logger.getGlobal();
private final TestConfig testConfig = new TestConfig(null);
@Override
public Logger getLogger() {
return log;
}
@Override
public String getPlatformName() {
return "Test";
}
@Override
public String getPlatformVersion() {
return "test";
}
@Override
public String getPluginVersion() {
return "test";
}
@Override
public PlatformTask runAsync(Runnable runnable) {
return null;
}
@Override
public PlatformTask runSync(Runnable runnable) {
return null;
}
@Override
public PlatformTask runSync(Runnable runnable, long ticks) {
return null;
}
@Override
public PlatformTask runRepeatingSync(Runnable runnable, long ticks) {
return null;
}
@Override
public ViaCommandSender[] getOnlinePlayers() {
return new ViaCommandSender[0];
}
@Override
public void sendMessage(UUID uuid, String message) {
}
@Override
public boolean kickPlayer(UUID uuid, String message) {
return false;
}
@Override
public boolean isPluginEnabled() {
return false;
}
@Override
public ViaAPI getApi() {
return new ViaAPIBase() {
@Override
public int getPlayerVersion(Object player) {
return 0;
}
@Override
public void sendRawPacket(Object player, ByteBuf packet) {
}
};
}
@Override
public ViaVersionConfig getConf() {
return testConfig;
}
@Override
public ConfigurationProvider getConfigurationProvider() {
return null;
}
@Override
public File getDataFolder() {
return null;
}
@Override
public void onReload() {
}
@Override
public JsonObject getDump() {
return null;
}
@Override
public boolean isOldClientsAllowed() {
return false;
}
}