mirror of
https://github.com/Minestom/Minestom.git
synced 2024-10-04 17:37:40 +02:00
Merge branch 'master' of https://github.com/Minestom/Minestom
Conflicts: src/main/java/net/minestom/server/entity/Player.java src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java src/main/java/net/minestom/server/utils/PacketUtils.java
This commit is contained in:
commit
9a64a0a409
31
.github/workflows/check-pr-style.yml
vendored
Normal file
31
.github/workflows/check-pr-style.yml
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
name: Check PR code style
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.11
|
||||
- name: Run java checkstyle
|
||||
uses: nikitasavinov/checkstyle-action@d87d526a914fc5cb0b003908e35038dbb2d6e1b7
|
||||
with:
|
||||
# Report level for reviewdog [info,warning,error]
|
||||
level: info
|
||||
# Reporter of reviewdog command [github-pr-check,github-pr-review]
|
||||
reporter: github-pr-check
|
||||
# Filtering for the reviewdog command [added,diff_context,file,nofilter].
|
||||
filter_mode: added
|
||||
# Exit code for reviewdog when errors are found [true,false].
|
||||
fail_on_error: false
|
||||
# Checkstyle config file
|
||||
checkstyle_config: minestom_checks.xml
|
||||
checkstyle_version: "8.37"
|
14
.github/workflows/tests.yml
vendored
14
.github/workflows/tests.yml
vendored
@ -19,7 +19,17 @@ jobs:
|
||||
java-version: 1.11
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
- name: Setup gradle cache
|
||||
uses: burrunan/gradle-cache-action@v1
|
||||
with:
|
||||
save-generated-gradle-jars: false
|
||||
save-local-build-cache: false
|
||||
save-gradle-dependencies-cache: true
|
||||
save-maven-dependencies-cache: true
|
||||
# Ignore some of the paths when caching Maven Local repository
|
||||
maven-local-ignore-paths: |
|
||||
net/minestom/
|
||||
- name: Build Minestom
|
||||
run: ./gradlew build
|
||||
- name: Run tests
|
||||
run: ./gradlew classes testClasses
|
||||
- name: Run Minestom tests
|
||||
run: ./gradlew test
|
||||
|
@ -6,6 +6,7 @@ plugins {
|
||||
id 'maven-publish'
|
||||
id 'net.ltgt.apt' version '0.10'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.4.10'
|
||||
id 'checkstyle'
|
||||
}
|
||||
|
||||
group 'net.minestom.server'
|
||||
@ -36,7 +37,7 @@ allprojects {
|
||||
maven { url 'https://jitpack.io' }
|
||||
maven {
|
||||
name 'sponge'
|
||||
url 'http://repo.spongepowered.org/maven'
|
||||
url 'https://repo.spongepowered.org/maven'
|
||||
}
|
||||
}
|
||||
javadoc {
|
||||
@ -45,6 +46,11 @@ allprojects {
|
||||
addBooleanOption('html5', true)
|
||||
}
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion "8.37"
|
||||
configFile file("${projectDir}/minestom_checks.xml")
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
1
gradle/wrapper/gradle-wrapper.properties
vendored
1
gradle/wrapper/gradle-wrapper.properties
vendored
@ -4,3 +4,4 @@ distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
|
||||
distributionSha256Sum=0f316a67b971b7b571dac7215dcf2591a30994b3450e0629925ffcfe2c68cc5c
|
||||
|
303
minestom_checks.xml
Normal file
303
minestom_checks.xml
Normal file
@ -0,0 +1,303 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
|
||||
<module name = "Checker">
|
||||
|
||||
<property name="charset" value="UTF-8"/>
|
||||
|
||||
<property name="severity" value="warning"/>
|
||||
|
||||
<property name="fileExtensions" value="java, properties, xml"/>
|
||||
|
||||
<module name="SuppressWarningsFilter" />
|
||||
<module name="SuppressionSingleFilter">
|
||||
<property name="files" value="autogenerated"/>
|
||||
<property name="checks" value=".*"/>
|
||||
</module>
|
||||
|
||||
<!-- Excludes all 'module-info.java' files -->
|
||||
<!-- See https://checkstyle.org/config_filefilters.html -->
|
||||
<module name="BeforeExecutionExclusionFileFilter">
|
||||
<property name="fileNamePattern" value="module\-info\.java$"/>
|
||||
</module>
|
||||
<!-- Checks for whitespace -->
|
||||
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
|
||||
<module name="FileTabCharacter">
|
||||
<property name="eachLine" value="true"/>
|
||||
</module>
|
||||
|
||||
<!-- Checks whether files end with a new line. -->
|
||||
<!-- See https://checkstyle.org/config_misc.html#NewlineAtEndOfFile -->
|
||||
<module name="NewlineAtEndOfFile"/>
|
||||
|
||||
<!-- Miscellaneous other checks. -->
|
||||
<!-- See https://checkstyle.org/config_misc.html -->
|
||||
<module name="RegexpSingleline">
|
||||
<property name="format" value="\s+$"/>
|
||||
<property name="minimum" value="0"/>
|
||||
<property name="maximum" value="0"/>
|
||||
<property name="message" value="Line has trailing spaces."/>
|
||||
</module>
|
||||
|
||||
<module name="LineLength">
|
||||
<property name="fileExtensions" value="java"/>
|
||||
<property name="max" value="120"/>
|
||||
<property name="ignorePattern" value="^package.*|^import.*|^ *\* *.+$|a href|href|http://|https://|ftp://"/>
|
||||
</module>
|
||||
|
||||
<module name="TreeWalker">
|
||||
|
||||
<module name="SuppressWarningsHolder" />
|
||||
<module name="SuppressionCommentFilter">
|
||||
<property name="onCommentFormat" value="\@formatter\:on" />
|
||||
<property name="offCommentFormat" value="\@formatter\:off" />
|
||||
</module>
|
||||
|
||||
<module name="OuterTypeFilename"/>
|
||||
<module name="IllegalTokenText">
|
||||
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
|
||||
<property name="format"
|
||||
value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
|
||||
<property name="message"
|
||||
value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
|
||||
</module>
|
||||
<module name="AvoidEscapedUnicodeCharacters">
|
||||
<property name="allowEscapesForControlCharacters" value="true"/>
|
||||
<property name="allowByTailComment" value="true"/>
|
||||
<property name="allowNonPrintableEscapes" value="true"/>
|
||||
</module>
|
||||
|
||||
<!-- Checks for imports -->
|
||||
<!-- See https://checkstyle.org/config_import.html -->
|
||||
<module name="AvoidStarImport"/>
|
||||
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
|
||||
<module name="RedundantImport"/>
|
||||
<module name="UnusedImports">
|
||||
<property name="processJavadoc" value="true"/>
|
||||
</module>
|
||||
<module name="CustomImportOrder">
|
||||
<property name="sortImportsInGroupAlphabetically" value="true"/>
|
||||
<property name="separateLineBetweenGroups" value="true"/>
|
||||
<property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/>
|
||||
</module>
|
||||
|
||||
<!-- Modifier Checks -->
|
||||
<!-- See https://checkstyle.org/config_modifiers.html -->
|
||||
<module name="ModifierOrder"/>
|
||||
<module name="RedundantModifier"/>
|
||||
|
||||
<module name="FinalClass"/>
|
||||
<module name="TodoComment">
|
||||
<property name="severity" value="info" />
|
||||
<property name="format" value="(TODO)|(FIXME)"/>
|
||||
</module>
|
||||
|
||||
<module name="OneTopLevelClass"/>
|
||||
<module name="NoLineWrap"/>
|
||||
<module name="EmptyBlock">
|
||||
<property name="option" value="TEXT"/>
|
||||
<property name="tokens"
|
||||
value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
|
||||
</module>
|
||||
<module name="NeedBraces"/>
|
||||
<module name="LeftCurly"/>
|
||||
<module name="RightCurly">
|
||||
<property name="id" value="RightCurlySame"/>
|
||||
<property name="tokens"
|
||||
value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE,
|
||||
LITERAL_DO"/>
|
||||
</module>
|
||||
<module name="RightCurly">
|
||||
<property name="id" value="RightCurlyAlone"/>
|
||||
<property name="option" value="alone"/>
|
||||
<property name="tokens"
|
||||
value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT,
|
||||
INSTANCE_INIT"/>
|
||||
</module>
|
||||
<module name="WhitespaceAround">
|
||||
<property name="allowEmptyConstructors" value="true"/>
|
||||
<property name="allowEmptyLambdas" value="true"/>
|
||||
<property name="allowEmptyMethods" value="true"/>
|
||||
<property name="allowEmptyTypes" value="true"/>
|
||||
<property name="allowEmptyLoops" value="true"/>
|
||||
<message key="ws.notFollowed"
|
||||
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
|
||||
<message key="ws.notPreceded"
|
||||
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
|
||||
</module>
|
||||
<module name="OneStatementPerLine"/>
|
||||
<module name="MultipleVariableDeclarations"/>
|
||||
<module name="ArrayTypeStyle"/>
|
||||
<module name="MissingSwitchDefault"/>
|
||||
<module name="FallThrough"/>
|
||||
<module name="UpperEll"/>
|
||||
<module name="EmptyLineSeparator">
|
||||
<property name="allowNoEmptyLineBetweenFields" value="true"/>
|
||||
</module>
|
||||
<module name="SeparatorWrap">
|
||||
<property name="id" value="SeparatorWrapDot"/>
|
||||
<property name="tokens" value="DOT"/>
|
||||
<property name="option" value="nl"/>
|
||||
</module>
|
||||
<module name="SeparatorWrap">
|
||||
<property name="id" value="SeparatorWrapComma"/>
|
||||
<property name="tokens" value="COMMA"/>
|
||||
<property name="option" value="EOL"/>
|
||||
</module>
|
||||
<module name="SeparatorWrap">
|
||||
<!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/258 -->
|
||||
<property name="id" value="SeparatorWrapEllipsis"/>
|
||||
<property name="tokens" value="ELLIPSIS"/>
|
||||
<property name="option" value="EOL"/>
|
||||
</module>
|
||||
<module name="SeparatorWrap">
|
||||
<!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/259 -->
|
||||
<property name="id" value="SeparatorWrapArrayDeclarator"/>
|
||||
<property name="tokens" value="ARRAY_DECLARATOR"/>
|
||||
<property name="option" value="EOL"/>
|
||||
</module>
|
||||
<module name="SeparatorWrap">
|
||||
<property name="id" value="SeparatorWrapMethodRef"/>
|
||||
<property name="tokens" value="METHOD_REF"/>
|
||||
<property name="option" value="nl"/>
|
||||
</module>
|
||||
<module name="PackageName">
|
||||
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Package name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="TypeName">
|
||||
<message key="name.invalidPattern"
|
||||
value="Type name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="MemberName">
|
||||
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Member name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="ParameterName">
|
||||
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Parameter name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="LambdaParameterName">
|
||||
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Lambda parameter name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="CatchParameterName">
|
||||
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="LocalVariableName">
|
||||
<property name="tokens" value="VARIABLE_DEF"/>
|
||||
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="ClassTypeParameterName">
|
||||
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Class type name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="MethodTypeParameterName">
|
||||
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Method type name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="InterfaceTypeParameterName">
|
||||
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Interface type name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="NoFinalizer"/>
|
||||
<module name="GenericWhitespace">
|
||||
<message key="ws.followed"
|
||||
value="GenericWhitespace ''{0}'' is followed by whitespace."/>
|
||||
<message key="ws.preceded"
|
||||
value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
|
||||
<message key="ws.illegalFollow"
|
||||
value="GenericWhitespace ''{0}'' should followed by whitespace."/>
|
||||
<message key="ws.notPreceded"
|
||||
value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
|
||||
</module>
|
||||
<module name="Indentation">
|
||||
<property name="basicOffset" value="4"/>
|
||||
<property name="braceAdjustment" value="0"/>
|
||||
<property name="caseIndent" value="4"/>
|
||||
<property name="throwsIndent" value="8"/>
|
||||
<property name="lineWrappingIndentation" value="8"/>
|
||||
<property name="arrayInitIndent" value="4"/>
|
||||
</module>
|
||||
<module name="AbbreviationAsWordInName">
|
||||
<property name="ignoreFinal" value="false"/>
|
||||
<property name="allowedAbbreviationLength" value="1"/>
|
||||
</module>
|
||||
<module name="OverloadMethodsDeclarationOrder"/>
|
||||
<module name="VariableDeclarationUsageDistance"/>
|
||||
<module name="MethodParamPad"/>
|
||||
<module name="NoWhitespaceBefore">
|
||||
<property name="tokens"
|
||||
value="COMMA, SEMI, POST_INC, POST_DEC, DOT, ELLIPSIS, METHOD_REF"/>
|
||||
<property name="allowLineBreaks" value="true"/>
|
||||
</module>
|
||||
<module name="ParenPad"/>
|
||||
<module name="OperatorWrap">
|
||||
<property name="option" value="NL"/>
|
||||
<property name="tokens"
|
||||
value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR,
|
||||
LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF "/>
|
||||
</module>
|
||||
<module name="AnnotationLocation">
|
||||
<property name="id" value="AnnotationLocationMostCases"/>
|
||||
<property name="tokens"
|
||||
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
|
||||
</module>
|
||||
<module name="AnnotationLocation">
|
||||
<property name="id" value="AnnotationLocationVariables"/>
|
||||
<property name="tokens" value="VARIABLE_DEF"/>
|
||||
<property name="allowSamelineMultipleAnnotations" value="true"/>
|
||||
</module>
|
||||
<module name="NonEmptyAtclauseDescription"/>
|
||||
<module name="InvalidJavadocPosition"/>
|
||||
<module name="JavadocTagContinuationIndentation" />
|
||||
<module name="SummaryJavadoc">
|
||||
<property name="forbiddenSummaryFragments"
|
||||
value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
|
||||
</module>
|
||||
<module name="JavadocParagraph">
|
||||
<property name="allowNewlineParagraph" value="false" />
|
||||
</module>
|
||||
<module name="AtclauseOrder">
|
||||
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
|
||||
<property name="target"
|
||||
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
|
||||
</module>
|
||||
<module name="JavadocMethod">
|
||||
<property name="scope" value="public"/>
|
||||
<property name="allowMissingParamTags" value="true"/>
|
||||
<property name="allowMissingReturnTag" value="true"/>
|
||||
<property name="allowedAnnotations" value="Override, Test"/>
|
||||
</module>
|
||||
<module name="MissingJavadocMethod">
|
||||
<property name="scope" value="public"/>
|
||||
<property name="minLineCount" value="2"/>
|
||||
<property name="allowedAnnotations" value="Override, Test"/>
|
||||
</module>
|
||||
<module name="MethodName">
|
||||
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
|
||||
<message key="name.invalidPattern"
|
||||
value="Method name ''{0}'' must match pattern ''{1}''."/>
|
||||
</module>
|
||||
<module name="SingleLineJavadoc">
|
||||
<property name="ignoreInlineTags" value="false"/>
|
||||
</module>
|
||||
<module name="EmptyCatchBlock">
|
||||
<property name="exceptionVariableName" value="expected"/>
|
||||
</module>
|
||||
<module name="CommentsIndentation"/>
|
||||
</module>
|
||||
</module>
|
@ -43,6 +43,7 @@ import net.minestom.server.storage.StorageManager;
|
||||
import net.minestom.server.timer.SchedulerManager;
|
||||
import net.minestom.server.utils.MathUtils;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
import net.minestom.server.utils.cache.TemporaryCache;
|
||||
import net.minestom.server.utils.thread.MinestomThread;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import net.minestom.server.world.Difficulty;
|
||||
@ -77,7 +78,7 @@ public final class MinecraftServer {
|
||||
public static final String THREAD_NAME_TICK = "Ms-Tick";
|
||||
|
||||
public static final String THREAD_NAME_BLOCK_BATCH = "Ms-BlockBatchPool";
|
||||
public static final int THREAD_COUNT_BLOCK_BATCH = 2;
|
||||
public static final int THREAD_COUNT_BLOCK_BATCH = 4;
|
||||
|
||||
public static final String THREAD_NAME_SCHEDULER = "Ms-SchedulerPool";
|
||||
public static final int THREAD_COUNT_SCHEDULER = 1;
|
||||
@ -125,7 +126,7 @@ public final class MinecraftServer {
|
||||
private static boolean initialized;
|
||||
private static boolean started;
|
||||
|
||||
private static int chunkViewDistance = 10;
|
||||
private static int chunkViewDistance = 8;
|
||||
private static int entityViewDistance = 5;
|
||||
private static int compressionThreshold = 256;
|
||||
private static ResponseDataConsumer responseDataConsumer;
|
||||
@ -674,6 +675,7 @@ public final class MinecraftServer {
|
||||
LOGGER.info("Shutting down all thread pools.");
|
||||
benchmarkManager.disable();
|
||||
commandManager.stopConsoleThread();
|
||||
TemporaryCache.REMOVER_SERVICE.shutdown();
|
||||
MinestomThread.shutdownAll();
|
||||
LOGGER.info("Minestom server stopped successfully.");
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.minestom.server.entity;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.advancements.AdvancementTab;
|
||||
import net.minestom.server.attribute.Attribute;
|
||||
@ -57,7 +56,6 @@ import net.minestom.server.utils.callback.OptionalCallback;
|
||||
import net.minestom.server.utils.chunk.ChunkCallback;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import net.minestom.server.utils.instance.InstanceUtils;
|
||||
import net.minestom.server.utils.player.PlayerUtils;
|
||||
import net.minestom.server.utils.time.CooldownUtils;
|
||||
import net.minestom.server.utils.time.TimeUnit;
|
||||
import net.minestom.server.utils.time.UpdateOption;
|
||||
@ -83,7 +81,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
/**
|
||||
* @see #getPlayerSynchronizationGroup()
|
||||
*/
|
||||
private static volatile int playerSynchronizationGroup = 100;
|
||||
private static volatile int playerSynchronizationGroup = 50;
|
||||
|
||||
/**
|
||||
* For the number of viewers that a player has, the position synchronization packet will be sent
|
||||
@ -341,15 +339,8 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
|
||||
@Override
|
||||
public void update(long time) {
|
||||
|
||||
// Flush all pending packets
|
||||
if (PlayerUtils.isNettyClient(this)) {
|
||||
Channel channel = ((NettyPlayerConnection) playerConnection).getChannel();
|
||||
channel.eventLoop().execute(channel::flush);
|
||||
}
|
||||
|
||||
// Network tick verification
|
||||
playerConnection.updateStats();
|
||||
// Network tick
|
||||
this.playerConnection.update();
|
||||
|
||||
// Process received packets
|
||||
ClientPlayPacket packet;
|
||||
@ -706,7 +697,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
sendDimension(instanceDimensionType);
|
||||
}
|
||||
|
||||
final long[] visibleChunks = ChunkUtils.getChunksInRange(position, getChunkRange());
|
||||
final long[] visibleChunks = ChunkUtils.getChunksInRange(firstSpawn ? getRespawnPoint() : position, getChunkRange());
|
||||
final int length = visibleChunks.length;
|
||||
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
@ -750,12 +741,17 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
*/
|
||||
private void spawnPlayer(Instance instance, boolean firstSpawn) {
|
||||
this.viewableEntities.forEach(entity -> entity.removeViewer(this));
|
||||
super.setInstance(instance);
|
||||
|
||||
if (firstSpawn) {
|
||||
teleport(getRespawnPoint());
|
||||
this.position = getRespawnPoint();
|
||||
this.cacheX = position.getX();
|
||||
this.cacheY = position.getY();
|
||||
this.cacheZ = position.getZ();
|
||||
updatePlayerPosition();
|
||||
}
|
||||
|
||||
super.setInstance(instance);
|
||||
|
||||
PlayerSpawnEvent spawnEvent = new PlayerSpawnEvent(this, instance, firstSpawn);
|
||||
callEvent(PlayerSpawnEvent.class, spawnEvent);
|
||||
}
|
||||
@ -1166,8 +1162,7 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
/**
|
||||
* Gets the player display name in the tab-list.
|
||||
*
|
||||
* @return the player display name,
|
||||
* null means that {@link #getUsername()} is displayed
|
||||
* @return the player display name, null means that {@link #getUsername()} is displayed
|
||||
*/
|
||||
@Nullable
|
||||
public ColoredText getDisplayName() {
|
||||
@ -1377,11 +1372,11 @@ public class Player extends LivingEntity implements CommandSender {
|
||||
* <p>
|
||||
* Can be altered by the {@link PlayerRespawnEvent#setRespawnPosition(Position)}.
|
||||
*
|
||||
* @return the default respawn point
|
||||
* @return a copy of the default respawn point
|
||||
*/
|
||||
@NotNull
|
||||
public Position getRespawnPoint() {
|
||||
return respawnPoint;
|
||||
return respawnPoint.copy();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,13 @@
|
||||
package net.minestom.server.entity.type.animal;
|
||||
|
||||
import net.minestom.server.entity.EntityCreature;
|
||||
import net.minestom.server.entity.EntityType;
|
||||
import net.minestom.server.entity.type.Animal;
|
||||
import net.minestom.server.utils.Position;
|
||||
|
||||
public class EntityLlama extends EntityCreature implements Animal {
|
||||
public EntityLlama(Position spawnPosition) {
|
||||
super(EntityType.LLAMA, spawnPosition);
|
||||
setBoundingBox(0.45f, 0.9375f, 0.45f);
|
||||
}
|
||||
}
|
@ -61,6 +61,8 @@ public abstract class Chunk implements Viewable, DataContainer {
|
||||
|
||||
public static final int BIOME_COUNT = 1024; // 4x4x4 blocks group
|
||||
|
||||
private final UUID identifier;
|
||||
|
||||
@NotNull
|
||||
protected final Biome[] biomes;
|
||||
protected final int chunkX, chunkZ;
|
||||
@ -79,6 +81,7 @@ public abstract class Chunk implements Viewable, DataContainer {
|
||||
protected Data data;
|
||||
|
||||
public Chunk(@Nullable Biome[] biomes, int chunkX, int chunkZ, boolean shouldGenerate) {
|
||||
this.identifier = UUID.randomUUID();
|
||||
this.chunkX = chunkX;
|
||||
this.chunkZ = chunkZ;
|
||||
this.shouldGenerate = shouldGenerate;
|
||||
@ -191,6 +194,17 @@ public abstract class Chunk implements Viewable, DataContainer {
|
||||
@NotNull
|
||||
public abstract Set<Integer> getBlockEntities();
|
||||
|
||||
/**
|
||||
* Gets the last time that this chunk changed.
|
||||
* <p>
|
||||
* "Change" means here data used in {@link ChunkDataPacket}.
|
||||
* It is necessary to see if the cached version of this chunk can be used
|
||||
* instead of re writing and compressing everything.
|
||||
*
|
||||
* @return the last change time in milliseconds
|
||||
*/
|
||||
public abstract long getLastChangeTime();
|
||||
|
||||
/**
|
||||
* Serializes the chunk into bytes.
|
||||
*
|
||||
@ -259,6 +273,18 @@ public abstract class Chunk implements Viewable, DataContainer {
|
||||
return getCustomBlock(x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the unique identifier of this chunk.
|
||||
* <p>
|
||||
* WARNING: this UUID is not persistent but randomized once the object is instantiate.
|
||||
*
|
||||
* @return the chunk identifier
|
||||
*/
|
||||
@NotNull
|
||||
public UUID getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public Biome[] getBiomes() {
|
||||
return biomes;
|
||||
}
|
||||
@ -448,7 +474,7 @@ public abstract class Chunk implements Viewable, DataContainer {
|
||||
|
||||
// TODO do not hardcode light
|
||||
{
|
||||
UpdateLightPacket updateLightPacket = new UpdateLightPacket();
|
||||
UpdateLightPacket updateLightPacket = new UpdateLightPacket(getIdentifier(), getLastChangeTime());
|
||||
updateLightPacket.chunkX = getChunkX();
|
||||
updateLightPacket.chunkZ = getChunkZ();
|
||||
updateLightPacket.skyLightMask = 0x3FFF0;
|
||||
|
@ -57,6 +57,8 @@ public class DynamicChunk extends Chunk {
|
||||
// Block entities
|
||||
protected final Set<Integer> blockEntities = new CopyOnWriteArraySet<>();
|
||||
|
||||
private long lastChangeTime;
|
||||
|
||||
public DynamicChunk(@Nullable Biome[] biomes, int chunkX, int chunkZ,
|
||||
@NotNull PaletteStorage blockPalette, @NotNull PaletteStorage customBlockPalette) {
|
||||
super(biomes, chunkX, chunkZ, true);
|
||||
@ -86,8 +88,8 @@ public class DynamicChunk extends Chunk {
|
||||
// True if the block is not complete air without any custom block capabilities
|
||||
final boolean hasBlock = blockStateId != 0 || customBlockId != 0;
|
||||
|
||||
this.blockPalette.setBlockAt(x, y, z, blockStateId);
|
||||
this.customBlockPalette.setBlockAt(x, y, z, customBlockId);
|
||||
setBlockAt(blockPalette, x, y, z, blockStateId);
|
||||
setBlockAt(customBlockPalette, x, y, z, customBlockId);
|
||||
|
||||
if (!hasBlock) {
|
||||
// Block has been deleted, clear cache and return
|
||||
@ -155,23 +157,23 @@ public class DynamicChunk extends Chunk {
|
||||
|
||||
@Override
|
||||
public short getBlockStateId(int x, int y, int z) {
|
||||
return this.blockPalette.getBlockAt(x, y, z);
|
||||
return getBlockAt(blockPalette, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getCustomBlockId(int x, int y, int z) {
|
||||
return customBlockPalette.getBlockAt(x, y, z);
|
||||
return getBlockAt(customBlockPalette, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void refreshBlockValue(int x, int y, int z, short blockStateId, short customBlockId) {
|
||||
this.blockPalette.setBlockAt(x, y, z, blockStateId);
|
||||
this.customBlockPalette.setBlockAt(x, y, z, customBlockId);
|
||||
setBlockAt(blockPalette, x, y, z, blockStateId);
|
||||
setBlockAt(customBlockPalette, x, y, z, customBlockId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void refreshBlockStateId(int x, int y, int z, short blockStateId) {
|
||||
this.blockPalette.setBlockAt(x, y, z, blockStateId);
|
||||
setBlockAt(blockPalette, x, y, z, blockStateId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -195,6 +197,11 @@ public class DynamicChunk extends Chunk {
|
||||
return blockEntities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastChangeTime() {
|
||||
return lastChangeTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize this {@link Chunk} based on {@link #readChunk(BinaryReader, ChunkCallback)}
|
||||
* <p>
|
||||
@ -242,8 +249,8 @@ public class DynamicChunk extends Chunk {
|
||||
for (byte z = 0; z < CHUNK_SIZE_Z; z++) {
|
||||
final int index = getBlockIndex(x, y, z);
|
||||
|
||||
final short blockStateId = blockPalette.getBlockAt(x, y, z);
|
||||
final short customBlockId = customBlockPalette.getBlockAt(x, y, z);
|
||||
final short blockStateId = getBlockAt(blockPalette, x, y, z);
|
||||
final short customBlockId = getBlockAt(customBlockPalette, x, y, z);
|
||||
|
||||
// No block at the position
|
||||
if (blockStateId == 0 && customBlockId == 0)
|
||||
@ -376,7 +383,7 @@ public class DynamicChunk extends Chunk {
|
||||
@NotNull
|
||||
@Override
|
||||
protected ChunkDataPacket createFreshPacket() {
|
||||
ChunkDataPacket fullDataPacket = new ChunkDataPacket();
|
||||
ChunkDataPacket fullDataPacket = new ChunkDataPacket(getIdentifier(), getLastChangeTime());
|
||||
fullDataPacket.biomes = biomes.clone();
|
||||
fullDataPacket.chunkX = chunkX;
|
||||
fullDataPacket.chunkZ = chunkZ;
|
||||
@ -400,4 +407,13 @@ public class DynamicChunk extends Chunk {
|
||||
|
||||
return dynamicChunk;
|
||||
}
|
||||
|
||||
private short getBlockAt(@NotNull PaletteStorage paletteStorage, int x, int y, int z) {
|
||||
return paletteStorage.getBlockAt(x, y, z);
|
||||
}
|
||||
|
||||
private void setBlockAt(@NotNull PaletteStorage paletteStorage, int x, int y, int z, short blockId) {
|
||||
paletteStorage.setBlockAt(x, y, z, blockId);
|
||||
this.lastChangeTime = System.currentTimeMillis();
|
||||
}
|
||||
}
|
@ -2,23 +2,21 @@ package net.minestom.server.listener.manager;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
import net.minestom.server.network.packet.client.ClientPlayPacket;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Interface used to add a listener for incoming/outgoing packets with
|
||||
* {@link ConnectionManager#onPacketReceive(PacketConsumer)} and {@link ConnectionManager#onPacketSend(PacketConsumer)}.
|
||||
*
|
||||
* @param <T> the packet type
|
||||
* Interface used to add a listener for incoming packets with {@link ConnectionManager#onPacketReceive(ClientPacketConsumer)}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface PacketConsumer<T> {
|
||||
public interface ClientPacketConsumer {
|
||||
|
||||
/**
|
||||
* Called when a packet is received/sent from/to a client.
|
||||
* Called when a packet is received from a client.
|
||||
*
|
||||
* @param player the player concerned by the packet
|
||||
* @param packetController the packet controller, can be used to cancel the packet
|
||||
* @param packet the packet
|
||||
*/
|
||||
void accept(@NotNull Player player, @NotNull PacketController packetController, @NotNull T packet);
|
||||
void accept(@NotNull Player player, @NotNull PacketController packetController, @NotNull ClientPlayPacket packet);
|
||||
}
|
@ -1,9 +1,14 @@
|
||||
package net.minestom.server.listener.manager;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.network.packet.client.ClientPlayPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Used to control the output of a packet in {@link PacketConsumer#accept(Player, PacketController, Object)}.
|
||||
* Used to control the output of a packet in {@link ClientPacketConsumer#accept(Player, PacketController, ClientPlayPacket)}
|
||||
* and {@link ServerPacketConsumer#accept(Collection, PacketController, ServerPacket)}.
|
||||
*/
|
||||
public class PacketController {
|
||||
|
||||
|
@ -11,6 +11,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@ -73,8 +75,8 @@ public final class PacketListenerManager {
|
||||
}
|
||||
|
||||
final PacketController packetController = new PacketController();
|
||||
for (PacketConsumer<ClientPlayPacket> packetConsumer : CONNECTION_MANAGER.getReceivePacketConsumers()) {
|
||||
packetConsumer.accept(player, packetController, packet);
|
||||
for (ClientPacketConsumer clientPacketConsumer : CONNECTION_MANAGER.getReceivePacketConsumers()) {
|
||||
clientPacketConsumer.accept(player, packetController, packet);
|
||||
}
|
||||
|
||||
if (packetController.isCancel())
|
||||
@ -85,17 +87,21 @@ public final class PacketListenerManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the consumers from {@link ConnectionManager#onPacketSend(PacketConsumer)}.
|
||||
* Executes the consumers from {@link ConnectionManager#onPacketSend(ServerPacketConsumer)}.
|
||||
*
|
||||
* @param packet the packet to process
|
||||
* @param player the player which should receive the packet
|
||||
* @param <T> the packet type
|
||||
* @param packet the packet to process
|
||||
* @param players the players which should receive the packet
|
||||
* @return true if the packet is not cancelled, false otherwise
|
||||
*/
|
||||
public <T extends ServerPacket> boolean processServerPacket(@NotNull T packet, @NotNull Player player) {
|
||||
public boolean processServerPacket(@NotNull ServerPacket packet, @NotNull Collection<Player> players) {
|
||||
final List<ServerPacketConsumer> consumers = CONNECTION_MANAGER.getSendPacketConsumers();
|
||||
if (consumers.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final PacketController packetController = new PacketController();
|
||||
for (PacketConsumer<ServerPacket> packetConsumer : CONNECTION_MANAGER.getSendPacketConsumers()) {
|
||||
packetConsumer.accept(player, packetController, packet);
|
||||
for (ServerPacketConsumer serverPacketConsumer : consumers) {
|
||||
serverPacketConsumer.accept(players, packetController, packet);
|
||||
}
|
||||
|
||||
return !packetController.isCancel();
|
||||
|
@ -0,0 +1,25 @@
|
||||
package net.minestom.server.listener.manager;
|
||||
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Interface used to add a listener for outgoing packets with {@link ConnectionManager#onPacketSend(ServerPacketConsumer)}.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ServerPacketConsumer {
|
||||
|
||||
/**
|
||||
* Called when a packet is sent to a client.
|
||||
*
|
||||
* @param players the players who will receive the packet
|
||||
* @param packetController the packet controller, can be used for cancelling
|
||||
* @param packet the packet to send
|
||||
*/
|
||||
void accept(@NotNull Collection<Player> players, @NotNull PacketController packetController, @NotNull ServerPacket packet);
|
||||
|
||||
}
|
@ -123,10 +123,10 @@ public enum MapColors {
|
||||
|
||||
// From the wiki: https://minecraft.gamepedia.com/Map_item_format
|
||||
// Map Color ID Multiply R,G,B By = Multiplier
|
||||
//Base Color ID×4 + 0 180 0.71
|
||||
//Base Color ID×4 + 1 220 0.86
|
||||
//Base Color ID×4 + 2 255 (same color) 1
|
||||
//Base Color ID×4 + 3 135 0.53
|
||||
//Base Color ID*4 + 0 180 0.71
|
||||
//Base Color ID*4 + 1 220 0.86
|
||||
//Base Color ID*4 + 2 255 (same color) 1
|
||||
//Base Color ID*4 + 3 135 0.53
|
||||
|
||||
/**
|
||||
* Returns the color index with RGB multiplied by 0.53, to use on a map
|
||||
|
@ -3,10 +3,9 @@ package net.minestom.server.network;
|
||||
import net.minestom.server.chat.JsonMessage;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.entity.fakeplayer.FakePlayer;
|
||||
import net.minestom.server.listener.manager.PacketConsumer;
|
||||
import net.minestom.server.network.packet.client.ClientPlayPacket;
|
||||
import net.minestom.server.listener.manager.ClientPacketConsumer;
|
||||
import net.minestom.server.listener.manager.ServerPacketConsumer;
|
||||
import net.minestom.server.network.packet.client.login.LoginStartPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.login.LoginSuccessPacket;
|
||||
import net.minestom.server.network.packet.server.play.ChatMessagePacket;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
@ -29,9 +28,9 @@ public final class ConnectionManager {
|
||||
private final Map<PlayerConnection, Player> connectionPlayerMap = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
// All the consumers to call once a packet is received
|
||||
private final List<PacketConsumer<ClientPlayPacket>> receivePacketConsumers = new CopyOnWriteArrayList<>();
|
||||
private final List<ClientPacketConsumer> receiveClientPacketConsumers = new CopyOnWriteArrayList<>();
|
||||
// All the consumers to call once a packet is sent
|
||||
private final List<PacketConsumer<ServerPacket>> sendPacketConsumers = new CopyOnWriteArrayList<>();
|
||||
private final List<ServerPacketConsumer> sendClientPacketConsumers = new CopyOnWriteArrayList<>();
|
||||
// The uuid provider once a player login
|
||||
private UuidProvider uuidProvider;
|
||||
// The player provider to have your own Player implementation
|
||||
@ -145,43 +144,39 @@ public final class ConnectionManager {
|
||||
/**
|
||||
* Gets all the listeners which are called for each packet received.
|
||||
*
|
||||
* @return an unmodifiable list of packet's consumers
|
||||
* @return a list of packet's consumers
|
||||
*/
|
||||
@NotNull
|
||||
public List<PacketConsumer<ClientPlayPacket>> getReceivePacketConsumers() {
|
||||
return Collections.unmodifiableList(receivePacketConsumers);
|
||||
public List<ClientPacketConsumer> getReceivePacketConsumers() {
|
||||
return receiveClientPacketConsumers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a consumer to call once a packet is received.
|
||||
*
|
||||
* @param packetConsumer the packet consumer
|
||||
* @param clientPacketConsumer the packet consumer
|
||||
*/
|
||||
public void onPacketReceive(@NotNull PacketConsumer<ClientPlayPacket> packetConsumer) {
|
||||
this.receivePacketConsumers.add(packetConsumer);
|
||||
public void onPacketReceive(@NotNull ClientPacketConsumer clientPacketConsumer) {
|
||||
this.receiveClientPacketConsumers.add(clientPacketConsumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the listeners which are called for each packet sent.
|
||||
*
|
||||
* @return an unmodifiable list of packet's consumers
|
||||
* @return a list of packet's consumers
|
||||
*/
|
||||
@NotNull
|
||||
public List<PacketConsumer<ServerPacket>> getSendPacketConsumers() {
|
||||
return Collections.unmodifiableList(sendPacketConsumers);
|
||||
public List<ServerPacketConsumer> getSendPacketConsumers() {
|
||||
return Collections.unmodifiableList(sendClientPacketConsumers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a consumer to call once a packet is sent.
|
||||
* <p>
|
||||
* Be aware that it is possible for the same packet instance to be used multiple time,
|
||||
* changing the object fields could lead to issues.
|
||||
* (consider canceling the packet instead and send your own)
|
||||
*
|
||||
* @param packetConsumer the packet consumer
|
||||
* @param serverPacketConsumer the packet consumer
|
||||
*/
|
||||
public void onPacketSend(@NotNull PacketConsumer<ServerPacket> packetConsumer) {
|
||||
this.sendPacketConsumers.add(packetConsumer);
|
||||
public void onPacketSend(@NotNull ServerPacketConsumer serverPacketConsumer) {
|
||||
this.sendClientPacketConsumers.add(serverPacketConsumer);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -12,18 +12,40 @@ import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.ServerSocketChannel;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.traffic.ChannelTrafficShapingHandler;
|
||||
import io.netty.handler.traffic.GlobalChannelTrafficShapingHandler;
|
||||
import io.netty.handler.traffic.TrafficCounter;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.network.PacketProcessor;
|
||||
import net.minestom.server.network.netty.channel.ClientChannel;
|
||||
import net.minestom.server.network.netty.codec.LegacyPingHandler;
|
||||
import net.minestom.server.network.netty.codec.PacketDecoder;
|
||||
import net.minestom.server.network.netty.codec.PacketEncoder;
|
||||
import net.minestom.server.network.netty.codec.PacketFramer;
|
||||
import net.minestom.server.network.netty.codec.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
public class NettyServer {
|
||||
public final class NettyServer {
|
||||
|
||||
private static final long DEFAULT_COMPRESSED_CHANNEL_WRITE_LIMIT = 600_000L;
|
||||
private static final long DEFAULT_COMPRESSED_CHANNEL_READ_LIMIT = 100_000L;
|
||||
|
||||
private static final long DEFAULT_UNCOMPRESSED_CHANNEL_WRITE_LIMIT = 15_000_000L;
|
||||
private static final long DEFAULT_UNCOMPRESSED_CHANNEL_READ_LIMIT = 1_000_000L;
|
||||
|
||||
public static final String TRAFFIC_LIMITER_HANDLER_NAME = "traffic-limiter"; // Read/write
|
||||
public static final String LEGACY_PING_HANDLER_NAME = "legacy-ping"; // Read
|
||||
|
||||
public static final String ENCRYPT_HANDLER_NAME = "encrypt"; // Write
|
||||
public static final String DECRYPT_HANDLER_NAME = "decrypt"; // Read
|
||||
|
||||
public static final String GROUPED_PACKET_HANDLER_NAME = "grouped-packet"; // Write
|
||||
public static final String FRAMER_HANDLER_NAME = "framer"; // Read/write
|
||||
|
||||
public static final String COMPRESSOR_HANDLER_NAME = "compressor"; // Read/write
|
||||
|
||||
public static final String DECODER_HANDLER_NAME = "decoder"; // Read
|
||||
public static final String ENCODER_HANDLER_NAME = "encoder"; // Write
|
||||
public static final String CLIENT_CHANNEL_NAME = "handler"; // Read
|
||||
|
||||
private final EventLoopGroup boss, worker;
|
||||
private final ServerBootstrap bootstrap;
|
||||
@ -33,9 +55,12 @@ public class NettyServer {
|
||||
private String address;
|
||||
private int port;
|
||||
|
||||
// Options
|
||||
private long writeLimit = 750_000L;
|
||||
private long readLimit = 750_000L;
|
||||
private final GlobalChannelTrafficShapingHandler globalTrafficHandler;
|
||||
|
||||
/**
|
||||
* Scheduler used by {@code globalTrafficHandler}.
|
||||
*/
|
||||
private final ScheduledExecutorService trafficScheduler = Executors.newScheduledThreadPool(1);
|
||||
|
||||
public NettyServer(@NotNull PacketProcessor packetProcessor) {
|
||||
Class<? extends ServerChannel> channel;
|
||||
@ -61,37 +86,65 @@ public class NettyServer {
|
||||
.group(boss, worker)
|
||||
.channel(channel);
|
||||
|
||||
this.globalTrafficHandler = new GlobalChannelTrafficShapingHandler(trafficScheduler, 200) {
|
||||
@Override
|
||||
protected void doAccounting(TrafficCounter counter) {
|
||||
// TODO proper monitoring API
|
||||
//System.out.println("data " + counter.lastWriteThroughput() / 1000 + " " + counter.lastReadThroughput() / 1000);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
protected void initChannel(@NotNull SocketChannel ch) {
|
||||
ChannelConfig config = ch.config();
|
||||
config.setOption(ChannelOption.TCP_NODELAY, true);
|
||||
config.setOption(ChannelOption.SO_SNDBUF, 1_000_000);
|
||||
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
|
||||
ChannelTrafficShapingHandler channelTrafficShapingHandler =
|
||||
new ChannelTrafficShapingHandler(writeLimit, readLimit, 200);
|
||||
|
||||
pipeline.addLast("traffic-limiter", channelTrafficShapingHandler);
|
||||
pipeline.addLast(TRAFFIC_LIMITER_HANDLER_NAME, globalTrafficHandler);
|
||||
|
||||
// First check should verify if the packet is a legacy ping (from 1.6 version and earlier)
|
||||
// Removed from the pipeline later in LegacyPingHandler if unnecessary (>1.6)
|
||||
pipeline.addLast("legacy-ping", new LegacyPingHandler());
|
||||
pipeline.addLast(LEGACY_PING_HANDLER_NAME, new LegacyPingHandler());
|
||||
|
||||
// Used to bypass all the previous handlers by directly sending a framed buffer
|
||||
pipeline.addLast(GROUPED_PACKET_HANDLER_NAME, new GroupedPacketHandler());
|
||||
|
||||
// Adds packetLength at start | Reads framed bytebuf
|
||||
pipeline.addLast("framer", new PacketFramer(packetProcessor));
|
||||
pipeline.addLast(FRAMER_HANDLER_NAME, new PacketFramer(packetProcessor));
|
||||
|
||||
// Reads bytebuf and creating inbound packet
|
||||
pipeline.addLast("decoder", new PacketDecoder());
|
||||
pipeline.addLast(DECODER_HANDLER_NAME, new PacketDecoder());
|
||||
|
||||
// Writes packet to bytebuf
|
||||
pipeline.addLast("encoder", new PacketEncoder());
|
||||
pipeline.addLast(ENCODER_HANDLER_NAME, new PacketEncoder());
|
||||
|
||||
pipeline.addLast("handler", new ClientChannel(packetProcessor));
|
||||
pipeline.addLast(CLIENT_CHANNEL_NAME, new ClientChannel(packetProcessor));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void start(String address, int port) {
|
||||
/**
|
||||
* Binds the address to start the server.
|
||||
*
|
||||
* @param address the server address
|
||||
* @param port the server port
|
||||
*/
|
||||
public void start(@NotNull String address, int port) {
|
||||
|
||||
{
|
||||
final boolean compression = MinecraftServer.getCompressionThreshold() != 0;
|
||||
if (compression) {
|
||||
globalTrafficHandler.setWriteChannelLimit(DEFAULT_COMPRESSED_CHANNEL_WRITE_LIMIT);
|
||||
globalTrafficHandler.setReadChannelLimit(DEFAULT_COMPRESSED_CHANNEL_READ_LIMIT);
|
||||
} else {
|
||||
globalTrafficHandler.setWriteChannelLimit(DEFAULT_UNCOMPRESSED_CHANNEL_WRITE_LIMIT);
|
||||
globalTrafficHandler.setReadChannelLimit(DEFAULT_UNCOMPRESSED_CHANNEL_READ_LIMIT);
|
||||
}
|
||||
}
|
||||
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
|
||||
@ -127,58 +180,27 @@ public class NettyServer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the server write limit.
|
||||
* Gets the traffic handler, used to control channel and global bandwidth.
|
||||
* <p>
|
||||
* Used when you want to limit the bandwidth used by a single connection.
|
||||
* Can also prevent the networking threads from being unresponsive.
|
||||
* The object can be modified as specified by Netty documentation.
|
||||
*
|
||||
* @return the write limit in bytes
|
||||
* @return the global traffic handler
|
||||
*/
|
||||
public long getWriteLimit() {
|
||||
return writeLimit;
|
||||
@NotNull
|
||||
public GlobalChannelTrafficShapingHandler getGlobalTrafficHandler() {
|
||||
return globalTrafficHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the server write limit
|
||||
* <p>
|
||||
* WARNING: the change will only apply to new connections, the current ones will not be updated.
|
||||
*
|
||||
* @param writeLimit the new write limit in bytes, 0 to disable
|
||||
* @see #getWriteLimit()
|
||||
* Stops the server and the various services.
|
||||
*/
|
||||
public void setWriteLimit(long writeLimit) {
|
||||
this.writeLimit = writeLimit;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the server read limit.
|
||||
* <p>
|
||||
* Used when you want to limit the bandwidth used by a single connection.
|
||||
* Can also prevent the networking threads from being unresponsive.
|
||||
*
|
||||
* @return the read limit in bytes
|
||||
*/
|
||||
public long getReadLimit() {
|
||||
return readLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the server read limit
|
||||
* <p>
|
||||
* WARNING: the change will only apply to new connections, the current ones will not be updated.
|
||||
*
|
||||
* @param readLimit the new read limit in bytes, 0 to disable
|
||||
* @see #getWriteLimit()
|
||||
*/
|
||||
public void setReadLimit(long readLimit) {
|
||||
this.readLimit = readLimit;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
serverChannel.close();
|
||||
this.serverChannel.close();
|
||||
|
||||
worker.shutdownGracefully();
|
||||
boss.shutdownGracefully();
|
||||
this.worker.shutdownGracefully();
|
||||
this.boss.shutdownGracefully();
|
||||
|
||||
this.trafficScheduler.shutdown();
|
||||
this.globalTrafficHandler.release();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
package net.minestom.server.network.netty.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import net.minestom.server.network.netty.packet.FramedPacket;
|
||||
|
||||
public class GroupedPacketHandler extends MessageToByteEncoder<FramedPacket> {
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, FramedPacket msg, ByteBuf out) {
|
||||
out.writeBytes(msg.body.retainedSlice());
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageCodec;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
import net.minestom.server.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
@ -33,7 +34,7 @@ public class PacketCompressor extends ByteToMessageCodec<ByteBuf> {
|
||||
|
||||
private final byte[] buffer = new byte[8192];
|
||||
|
||||
private final Deflater deflater = new Deflater();
|
||||
private final Deflater deflater = new Deflater(3);
|
||||
private final Inflater inflater = new Inflater();
|
||||
|
||||
public PacketCompressor(int threshold) {
|
||||
@ -42,24 +43,7 @@ public class PacketCompressor extends ByteToMessageCodec<ByteBuf> {
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf from, ByteBuf to) {
|
||||
final int packetLength = from.readableBytes();
|
||||
|
||||
if (packetLength < this.threshold) {
|
||||
Utils.writeVarIntBuf(to, 0);
|
||||
to.writeBytes(from);
|
||||
} else {
|
||||
Utils.writeVarIntBuf(to, packetLength);
|
||||
|
||||
deflater.setInput(from.nioBuffer());
|
||||
deflater.finish();
|
||||
|
||||
while (!deflater.finished()) {
|
||||
final int length = deflater.deflate(buffer);
|
||||
to.writeBytes(buffer, 0, length);
|
||||
}
|
||||
|
||||
deflater.reset();
|
||||
}
|
||||
PacketUtils.compressBuffer(deflater, buffer, from, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -7,6 +7,7 @@ import io.netty.handler.codec.CorruptedFrameException;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.network.PacketProcessor;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
import net.minestom.server.utils.Utils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -25,17 +26,7 @@ public class PacketFramer extends ByteToMessageCodec<ByteBuf> {
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf from, ByteBuf to) {
|
||||
final int packetSize = from.readableBytes();
|
||||
final int headerSize = Utils.getVarIntSize(packetSize);
|
||||
|
||||
if (headerSize > 3) {
|
||||
throw new IllegalStateException("Unable to fit " + headerSize + " into 3");
|
||||
}
|
||||
|
||||
to.ensureWritable(packetSize + headerSize);
|
||||
|
||||
Utils.writeVarIntBuf(to, packetSize);
|
||||
to.writeBytes(from, from.readerIndex(), packetSize);
|
||||
PacketUtils.frameBuffer(from, to);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,18 @@
|
||||
package net.minestom.server.network.netty.packet;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Represents a packet which is already framed.
|
||||
* Can be used if you want to send the exact same buffer to multiple clients without processing it more than once.
|
||||
*/
|
||||
public class FramedPacket {
|
||||
|
||||
public final ByteBuf body;
|
||||
|
||||
public FramedPacket(@NotNull ByteBuf body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
}
|
@ -39,7 +39,7 @@ public class LoginStartPacket implements ClientPreplayPacket {
|
||||
// Compression
|
||||
final int threshold = MinecraftServer.getCompressionThreshold();
|
||||
if (threshold > 0) {
|
||||
nettyPlayerConnection.enableCompression(threshold);
|
||||
nettyPlayerConnection.startCompression();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,16 +13,21 @@ import net.minestom.server.utils.BlockPosition;
|
||||
import net.minestom.server.utils.BufUtils;
|
||||
import net.minestom.server.utils.Utils;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import net.minestom.server.utils.cache.CacheablePacket;
|
||||
import net.minestom.server.utils.cache.TemporaryPacketCache;
|
||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||
import net.minestom.server.world.biomes.Biome;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChunkDataPacket implements ServerPacket {
|
||||
public class ChunkDataPacket implements ServerPacket, CacheablePacket {
|
||||
|
||||
private static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager();
|
||||
private static final TemporaryPacketCache CACHE = new TemporaryPacketCache(10000L);
|
||||
|
||||
public boolean fullChunk;
|
||||
public Biome[] biomes;
|
||||
@ -40,6 +45,15 @@ public class ChunkDataPacket implements ServerPacket {
|
||||
private static final int MAX_BITS_PER_ENTRY = 16;
|
||||
private static final int MAX_BUFFER_SIZE = (Short.BYTES + Byte.BYTES + 5 * Byte.BYTES + (4096 * MAX_BITS_PER_ENTRY / Long.SIZE * Long.BYTES)) * CHUNK_SECTION_COUNT + 256 * Integer.BYTES;
|
||||
|
||||
// Cacheable data
|
||||
private UUID identifier;
|
||||
private long lastUpdate;
|
||||
|
||||
public ChunkDataPacket(@Nullable UUID identifier, long lastUpdate) {
|
||||
this.identifier = identifier;
|
||||
this.lastUpdate = lastUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NotNull BinaryWriter writer) {
|
||||
writer.writeInt(chunkX);
|
||||
@ -122,4 +136,19 @@ public class ChunkDataPacket implements ServerPacket {
|
||||
public int getId() {
|
||||
return ServerPacketIdentifier.CHUNK_DATA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemporaryPacketCache getCache() {
|
||||
return CACHE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastUpdateTime() {
|
||||
return lastUpdate;
|
||||
}
|
||||
}
|
@ -3,11 +3,17 @@ package net.minestom.server.network.packet.server.play;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import net.minestom.server.utils.cache.CacheablePacket;
|
||||
import net.minestom.server.utils.cache.TemporaryPacketCache;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class UpdateLightPacket implements ServerPacket {
|
||||
public class UpdateLightPacket implements ServerPacket, CacheablePacket {
|
||||
|
||||
private static final TemporaryPacketCache CACHE = new TemporaryPacketCache(10000L);
|
||||
|
||||
public int chunkX;
|
||||
public int chunkZ;
|
||||
@ -23,6 +29,15 @@ public class UpdateLightPacket implements ServerPacket {
|
||||
public List<byte[]> skyLight;
|
||||
public List<byte[]> blockLight;
|
||||
|
||||
// Cacheable data
|
||||
private UUID identifier;
|
||||
private long lastUpdate;
|
||||
|
||||
public UpdateLightPacket(@Nullable UUID identifier, long lastUpdate) {
|
||||
this.identifier = identifier;
|
||||
this.lastUpdate = lastUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(@NotNull BinaryWriter writer) {
|
||||
writer.writeVarInt(chunkX);
|
||||
@ -53,4 +68,19 @@ public class UpdateLightPacket implements ServerPacket {
|
||||
public int getId() {
|
||||
return ServerPacketIdentifier.UPDATE_LIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemporaryPacketCache getCache() {
|
||||
return CACHE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastUpdateTime() {
|
||||
return lastUpdate;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.minestom.server.network.player;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
@ -8,9 +9,14 @@ import net.minestom.server.extras.mojangAuth.Decrypter;
|
||||
import net.minestom.server.extras.mojangAuth.Encrypter;
|
||||
import net.minestom.server.extras.mojangAuth.MojangCrypt;
|
||||
import net.minestom.server.network.ConnectionState;
|
||||
import net.minestom.server.network.netty.NettyServer;
|
||||
import net.minestom.server.network.netty.codec.PacketCompressor;
|
||||
import net.minestom.server.network.netty.packet.FramedPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.login.SetCompressionPacket;
|
||||
import net.minestom.server.utils.PacketUtils;
|
||||
import net.minestom.server.utils.cache.CacheablePacket;
|
||||
import net.minestom.server.utils.cache.TemporaryCache;
|
||||
import net.minestom.server.utils.validate.Check;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@ -57,6 +63,14 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
this.remoteAddress = channel.remoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
// Flush
|
||||
this.channel.flush();
|
||||
// Network stats
|
||||
super.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the encryption key and add the codecs to the pipeline.
|
||||
*
|
||||
@ -66,21 +80,26 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
public void setEncryptionKey(@NotNull SecretKey secretKey) {
|
||||
Check.stateCondition(encrypted, "Encryption is already enabled!");
|
||||
this.encrypted = true;
|
||||
channel.pipeline().addBefore("framer", "decrypt", new Decrypter(MojangCrypt.getCipher(2, secretKey)));
|
||||
channel.pipeline().addBefore("framer", "encrypt", new Encrypter(MojangCrypt.getCipher(1, secretKey)));
|
||||
channel.pipeline().addBefore(NettyServer.FRAMER_HANDLER_NAME, NettyServer.DECRYPT_HANDLER_NAME,
|
||||
new Decrypter(MojangCrypt.getCipher(2, secretKey)));
|
||||
channel.pipeline().addBefore(NettyServer.FRAMER_HANDLER_NAME, NettyServer.ENCRYPT_HANDLER_NAME,
|
||||
new Encrypter(MojangCrypt.getCipher(1, secretKey)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables compression and add a new codec to the pipeline.
|
||||
*
|
||||
* @param threshold the threshold for a packet to be compressible
|
||||
* @throws IllegalStateException if encryption is already enabled for this connection
|
||||
*/
|
||||
public void enableCompression(int threshold) {
|
||||
public void startCompression() {
|
||||
Check.stateCondition(compressed, "Compression is already enabled!");
|
||||
final int threshold = MinecraftServer.getCompressionThreshold();
|
||||
Check.stateCondition(threshold == 0, "Compression cannot be enabled because the threshold is equal to 0");
|
||||
|
||||
this.compressed = true;
|
||||
sendPacket(new SetCompressionPacket(threshold));
|
||||
channel.pipeline().addAfter("framer", "compressor", new PacketCompressor(threshold));
|
||||
channel.pipeline().addAfter(NettyServer.FRAMER_HANDLER_NAME, NettyServer.COMPRESSOR_HANDLER_NAME,
|
||||
new PacketCompressor(threshold));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,26 +112,63 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
@Override
|
||||
public void sendPacket(@NotNull ServerPacket serverPacket) {
|
||||
if (shouldSendPacket(serverPacket)) {
|
||||
if (getPlayer() != null) { // Flush on player update
|
||||
if (MinecraftServer.processingNettyErrors())
|
||||
channel.write(serverPacket).addListener(future -> {
|
||||
if (!future.isSuccess()) {
|
||||
future.cause().printStackTrace();
|
||||
if (getPlayer() != null) {
|
||||
// Flush happen during #update()
|
||||
if (serverPacket instanceof CacheablePacket) {
|
||||
CacheablePacket cacheablePacket = (CacheablePacket) serverPacket;
|
||||
final UUID identifier = cacheablePacket.getIdentifier();
|
||||
|
||||
if (identifier == null) {
|
||||
// This packet explicitly said to do not retrieve the cache
|
||||
if (MinecraftServer.processingNettyErrors())
|
||||
channel.write(serverPacket).addListener(future -> {
|
||||
if (!future.isSuccess()) {
|
||||
future.cause().printStackTrace();
|
||||
}
|
||||
});
|
||||
else
|
||||
channel.write(serverPacket, channel.voidPromise());
|
||||
} else {
|
||||
// Try to retrieve the cached buffer
|
||||
TemporaryCache<ByteBuf> temporaryCache = cacheablePacket.getCache();
|
||||
ByteBuf buffer = temporaryCache.retrieve(identifier);
|
||||
if (buffer == null) {
|
||||
// Buffer not found, create and cache it
|
||||
final long time = System.currentTimeMillis();
|
||||
buffer = PacketUtils.createFramedPacket(serverPacket);
|
||||
temporaryCache.cacheObject(identifier, buffer, time);
|
||||
}
|
||||
});
|
||||
else {
|
||||
channel.write(serverPacket, channel.voidPromise());
|
||||
|
||||
FramedPacket framedPacket = new FramedPacket(buffer);
|
||||
if (MinecraftServer.processingNettyErrors())
|
||||
channel.write(framedPacket).addListener(future -> {
|
||||
if (!future.isSuccess()) {
|
||||
future.cause().printStackTrace();
|
||||
}
|
||||
});
|
||||
else
|
||||
channel.write(framedPacket, channel.voidPromise());
|
||||
}
|
||||
|
||||
} else {
|
||||
if (MinecraftServer.processingNettyErrors())
|
||||
channel.write(serverPacket).addListener(future -> {
|
||||
if (!future.isSuccess()) {
|
||||
future.cause().printStackTrace();
|
||||
}
|
||||
});
|
||||
else
|
||||
channel.write(serverPacket, channel.voidPromise());
|
||||
}
|
||||
} else {
|
||||
if (MinecraftServer.processingNettyErrors())
|
||||
channel.writeAndFlush(serverPacket).addListener(future -> {
|
||||
if (!future.isSuccess()) {
|
||||
future.cause().printStackTrace();
|
||||
}
|
||||
});
|
||||
else {
|
||||
channel.writeAndFlush(serverPacket, channel.voidPromise());
|
||||
}
|
||||
if (MinecraftServer.processingNettyErrors())
|
||||
channel.writeAndFlush(serverPacket).addListener(future -> {
|
||||
if (!future.isSuccess()) {
|
||||
future.cause().printStackTrace();
|
||||
}
|
||||
});
|
||||
else
|
||||
channel.writeAndFlush(serverPacket, channel.voidPromise());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,7 +192,7 @@ public class NettyPlayerConnection extends PlayerConnection {
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
channel.close();
|
||||
this.channel.close();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
@ -4,8 +4,8 @@ import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.chat.ChatColor;
|
||||
import net.minestom.server.chat.ColoredText;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.listener.manager.PacketConsumer;
|
||||
import net.minestom.server.listener.manager.PacketListenerManager;
|
||||
import net.minestom.server.listener.manager.ServerPacketConsumer;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
import net.minestom.server.network.ConnectionState;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
@ -15,6 +15,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
@ -45,7 +46,7 @@ public abstract class PlayerConnection {
|
||||
/**
|
||||
* Updates values related to the network connection.
|
||||
*/
|
||||
public void updateStats() {
|
||||
public void update() {
|
||||
// Check rate limit
|
||||
if (MinecraftServer.getRateLimit() > 0) {
|
||||
tickCounter++;
|
||||
@ -92,7 +93,7 @@ public abstract class PlayerConnection {
|
||||
/**
|
||||
* Serializes the packet and send it to the client.
|
||||
* <p>
|
||||
* Also responsible for executing {@link ConnectionManager#onPacketSend(PacketConsumer)} consumers.
|
||||
* Also responsible for executing {@link ConnectionManager#onPacketSend(ServerPacketConsumer)} consumers.
|
||||
*
|
||||
* @param serverPacket the packet to send
|
||||
* @see #shouldSendPacket(ServerPacket)
|
||||
@ -100,7 +101,8 @@ public abstract class PlayerConnection {
|
||||
public abstract void sendPacket(@NotNull ServerPacket serverPacket);
|
||||
|
||||
protected boolean shouldSendPacket(@NotNull ServerPacket serverPacket) {
|
||||
return player == null || PACKET_LISTENER_MANAGER.processServerPacket(serverPacket, player);
|
||||
return player == null ||
|
||||
PACKET_LISTENER_MANAGER.processServerPacket(serverPacket, Collections.singleton(player));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,13 +2,19 @@ package net.minestom.server.utils;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minestom.server.MinecraftServer;
|
||||
import net.minestom.server.entity.Player;
|
||||
import net.minestom.server.listener.manager.PacketListenerManager;
|
||||
import net.minestom.server.network.netty.packet.FramedPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
|
||||
import net.minestom.server.network.player.NettyPlayerConnection;
|
||||
import net.minestom.server.network.player.PlayerConnection;
|
||||
import net.minestom.server.utils.binary.BinaryWriter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
/**
|
||||
* Utils class for packets. Including writing a {@link ServerPacket} into a {@link ByteBuf}
|
||||
@ -16,21 +22,41 @@ import java.util.Collection;
|
||||
*/
|
||||
public final class PacketUtils {
|
||||
|
||||
private static final PacketListenerManager PACKET_LISTENER_MANAGER = MinecraftServer.getPacketListenerManager();
|
||||
|
||||
private static Deflater deflater = new Deflater(3);
|
||||
private static byte[] buffer = new byte[8192];
|
||||
|
||||
private PacketUtils() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a {@link ServerPacket} to multiple players. Mostly used for convenience.
|
||||
* Sends a {@link ServerPacket} to multiple players.
|
||||
* <p>
|
||||
* Be aware that this will cause the send packet listeners to be given the exact same packet object.
|
||||
* Can drastically improve performance since the packet will not have to be processed as much.
|
||||
*
|
||||
* @param players the players to send the packet to
|
||||
* @param packet the packet to send to the players
|
||||
*/
|
||||
public static void sendGroupedPacket(@NotNull Collection<Player> players, @NotNull ServerPacket packet) {
|
||||
for (Player player : players) {
|
||||
player.getPlayerConnection().sendPacket(packet);
|
||||
if (players.isEmpty())
|
||||
return;
|
||||
|
||||
final boolean success = PACKET_LISTENER_MANAGER.processServerPacket(packet, players);
|
||||
if (success) {
|
||||
final ByteBuf finalBuffer = createFramedPacket(packet);
|
||||
final FramedPacket framedPacket = new FramedPacket(finalBuffer);
|
||||
|
||||
for (Player player : players) {
|
||||
final PlayerConnection playerConnection = player.getPlayerConnection();
|
||||
if (playerConnection instanceof NettyPlayerConnection) {
|
||||
final NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) playerConnection;
|
||||
nettyPlayerConnection.getChannel().write(framedPacket);
|
||||
} else {
|
||||
playerConnection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,4 +125,92 @@ public final class PacketUtils {
|
||||
return writer.getBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Frames a buffer for it to be understood by a Minecraft client.
|
||||
* <p>
|
||||
* The content of {@code packetBuffer} can be either a compressed or uncompressed packet buffer,
|
||||
* it depends of it the client did receive a {@link net.minestom.server.network.packet.server.login.SetCompressionPacket} packet before.
|
||||
*
|
||||
* @param packetBuffer the buffer containing compressed or uncompressed packet data
|
||||
* @param frameTarget the buffer which will receive the framed version of {@code from}
|
||||
*/
|
||||
public static void frameBuffer(@NotNull ByteBuf packetBuffer, @NotNull ByteBuf frameTarget) {
|
||||
final int packetSize = packetBuffer.readableBytes();
|
||||
final int headerSize = Utils.getVarIntSize(packetSize);
|
||||
|
||||
if (headerSize > 3) {
|
||||
throw new IllegalStateException("Unable to fit " + headerSize + " into 3");
|
||||
}
|
||||
|
||||
frameTarget.ensureWritable(packetSize + headerSize);
|
||||
|
||||
Utils.writeVarIntBuf(frameTarget, packetSize);
|
||||
frameTarget.writeBytes(packetBuffer, packetBuffer.readerIndex(), packetSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress using zlib the content of a packet.
|
||||
* <p>
|
||||
* {@code packetBuffer} needs to be the packet content without any header (if you want to use it to write a Minecraft packet).
|
||||
*
|
||||
* @param deflater the deflater for zlib compression
|
||||
* @param buffer a cached buffer which will be used to store temporary the deflater output
|
||||
* @param packetBuffer the buffer containing all the packet fields
|
||||
* @param compressionTarget the buffer which will receive the compressed version of {@code packetBuffer}
|
||||
*/
|
||||
public static void compressBuffer(@NotNull Deflater deflater, @NotNull byte[] buffer, @NotNull ByteBuf packetBuffer, @NotNull ByteBuf compressionTarget) {
|
||||
final int packetLength = packetBuffer.readableBytes();
|
||||
|
||||
if (packetLength < MinecraftServer.getCompressionThreshold()) {
|
||||
Utils.writeVarIntBuf(compressionTarget, 0);
|
||||
compressionTarget.writeBytes(packetBuffer);
|
||||
} else {
|
||||
Utils.writeVarIntBuf(compressionTarget, packetLength);
|
||||
|
||||
deflater.setInput(packetBuffer.nioBuffer());
|
||||
deflater.finish();
|
||||
|
||||
while (!deflater.finished()) {
|
||||
final int length = deflater.deflate(buffer);
|
||||
compressionTarget.writeBytes(buffer, 0, length);
|
||||
}
|
||||
|
||||
deflater.reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a "framed packet" (packet which can be send and understood by a Minecraft client)
|
||||
* from a server packet.
|
||||
* <p>
|
||||
* Can be used if you want to store a raw buffer and send it later without the additional writing cost.
|
||||
* Compression is applied if {@link MinecraftServer#getCompressionThreshold()} is greater than 0.
|
||||
*
|
||||
* @param serverPacket the server packet to write
|
||||
* @return the framed packet from the server one
|
||||
*/
|
||||
public static ByteBuf createFramedPacket(@NotNull ServerPacket serverPacket) {
|
||||
ByteBuf packetBuf = writePacket(serverPacket);
|
||||
|
||||
// TODO use pooled buffers instead of unpooled ones
|
||||
if (MinecraftServer.getCompressionThreshold() > 0) {
|
||||
|
||||
ByteBuf compressedBuf = Unpooled.buffer();
|
||||
ByteBuf framedBuf = Unpooled.buffer();
|
||||
synchronized (deflater) {
|
||||
compressBuffer(deflater, buffer, packetBuf, compressedBuf);
|
||||
}
|
||||
|
||||
frameBuffer(compressedBuf, framedBuf);
|
||||
|
||||
return framedBuf;
|
||||
} else {
|
||||
ByteBuf framedBuf = Unpooled.buffer();
|
||||
frameBuffer(packetBuf, framedBuf);
|
||||
|
||||
return framedBuf;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
45
src/main/java/net/minestom/server/utils/cache/CacheablePacket.java
vendored
Normal file
45
src/main/java/net/minestom/server/utils/cache/CacheablePacket.java
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
package net.minestom.server.utils.cache;
|
||||
|
||||
import net.minestom.server.network.packet.server.ServerPacket;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Implemented by {@link ServerPacket server packets} which can be temporary cached in memory to be re-sent later
|
||||
* without having to go through all the writing and compression.
|
||||
* <p>
|
||||
* {@link #getIdentifier()} is to differenciate this packet from the others of the same type,
|
||||
* and {@link #getLastUpdateTime()} to know if one packet is newer than the previous one.
|
||||
*/
|
||||
public interface CacheablePacket {
|
||||
|
||||
/**
|
||||
* Gets the cache linked to this packet.
|
||||
* <p>
|
||||
* WARNING: the cache needs to be shared between all the object instances, tips is to make it static.
|
||||
*
|
||||
* @return the temporary packet cache
|
||||
*/
|
||||
@NotNull
|
||||
TemporaryPacketCache getCache();
|
||||
|
||||
/**
|
||||
* Gets the identifier of this packet.
|
||||
* <p>
|
||||
* Used to verify if this packet is already cached or not.
|
||||
*
|
||||
* @return this packet identifier, null to do not retrieve the cache
|
||||
*/
|
||||
@Nullable
|
||||
UUID getIdentifier();
|
||||
|
||||
/**
|
||||
* Gets the last time this packet changed.
|
||||
*
|
||||
* @return the last packet update time in milliseconds
|
||||
*/
|
||||
long getLastUpdateTime();
|
||||
|
||||
}
|
75
src/main/java/net/minestom/server/utils/cache/TemporaryCache.java
vendored
Normal file
75
src/main/java/net/minestom/server/utils/cache/TemporaryCache.java
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
package net.minestom.server.utils.cache;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Cache objects with a timeout.
|
||||
*
|
||||
* @param <T> the object type to cache
|
||||
*/
|
||||
public class TemporaryCache<T> {
|
||||
|
||||
public static final ScheduledExecutorService REMOVER_SERVICE = Executors.newScheduledThreadPool(1);
|
||||
|
||||
// Identifier = Cached object
|
||||
protected ConcurrentHashMap<UUID, T> cache = new ConcurrentHashMap<>();
|
||||
// Identifier = time
|
||||
protected ConcurrentHashMap<UUID, Long> cacheTime = new ConcurrentHashMap<>();
|
||||
|
||||
private long keepTime;
|
||||
|
||||
/**
|
||||
* Creates a new temporary cache.
|
||||
*
|
||||
* @param keepTime the time before considering an object unused in milliseconds
|
||||
* @see #getKeepTime()
|
||||
*/
|
||||
public TemporaryCache(long keepTime) {
|
||||
this.keepTime = keepTime;
|
||||
REMOVER_SERVICE.scheduleAtFixedRate(() -> {
|
||||
final boolean removed = cacheTime.values().removeIf(time -> System.currentTimeMillis() > time + keepTime);
|
||||
if (removed) {
|
||||
this.cache.entrySet().removeIf(entry -> !cacheTime.containsKey(entry.getKey()));
|
||||
}
|
||||
}, keepTime, keepTime, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches an object
|
||||
*
|
||||
* @param identifier the object identifier
|
||||
* @param value the object to cache
|
||||
* @param time the current time in milliseconds
|
||||
*/
|
||||
public synchronized void cacheObject(@NotNull UUID identifier, T value, long time) {
|
||||
this.cache.put(identifier, value);
|
||||
this.cacheTime.put(identifier, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an object from cache.
|
||||
*
|
||||
* @param identifier the object identifier
|
||||
* @return the retrieved object or null if not found
|
||||
*/
|
||||
@Nullable
|
||||
public T retrieve(@NotNull UUID identifier) {
|
||||
return cache.get(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time an object will be kept without being retrieved
|
||||
*
|
||||
* @return the keep time in milliseconds
|
||||
*/
|
||||
public long getKeepTime() {
|
||||
return keepTime;
|
||||
}
|
||||
}
|
12
src/main/java/net/minestom/server/utils/cache/TemporaryPacketCache.java
vendored
Normal file
12
src/main/java/net/minestom/server/utils/cache/TemporaryPacketCache.java
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
package net.minestom.server.utils.cache;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
/**
|
||||
* Convenient superclass of {@link TemporaryCache} explicitly for packet to store a {@link ByteBuf}.
|
||||
*/
|
||||
public class TemporaryPacketCache extends TemporaryCache<ByteBuf> {
|
||||
public TemporaryPacketCache(long keepTime) {
|
||||
super(keepTime);
|
||||
}
|
||||
}
|
@ -11,15 +11,11 @@ public final class DebugUtils {
|
||||
public final static Logger LOGGER = LoggerFactory.getLogger(DebugUtils.class);
|
||||
|
||||
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
||||
private static final int OFFSET = 2; // Used to do not show DebugUtils in the stack trace
|
||||
|
||||
/**
|
||||
* Prints the current thread stack trace elements.
|
||||
*
|
||||
* @param maxLine the maximum number of stack trace element
|
||||
*/
|
||||
public static synchronized void printStackTrace(int maxLine) {
|
||||
maxLine += OFFSET;
|
||||
public static synchronized void printStackTrace() {
|
||||
StackTraceElement[] elements = Thread.currentThread().getStackTrace();
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
@ -27,7 +23,7 @@ public final class DebugUtils {
|
||||
stringBuilder.append("START STACKTRACE");
|
||||
stringBuilder.append(LINE_SEPARATOR);
|
||||
|
||||
for (int i = OFFSET; i < maxLine; i++) {
|
||||
for (int i = 0; i < Integer.MAX_VALUE; i++) {
|
||||
if (i >= elements.length)
|
||||
break;
|
||||
|
||||
@ -42,11 +38,4 @@ public final class DebugUtils {
|
||||
LOGGER.info(stringBuilder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the current thread stack trace elements.
|
||||
*/
|
||||
public static synchronized void printStackTrace() {
|
||||
printStackTrace(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import net.minestom.server.instance.*;
|
||||
import net.minestom.server.instance.batch.ChunkBatch;
|
||||
import net.minestom.server.instance.block.Block;
|
||||
import net.minestom.server.network.ConnectionManager;
|
||||
import net.minestom.server.network.netty.NettyServer;
|
||||
import net.minestom.server.utils.Position;
|
||||
import net.minestom.server.world.biomes.Biome;
|
||||
|
||||
@ -37,14 +36,6 @@ public class MainDemo {
|
||||
});
|
||||
});
|
||||
|
||||
// OPTIONAL: optimize networking to prevent having unresponsive threads
|
||||
{
|
||||
NettyServer nettyServer = MinecraftServer.getNettyServer();
|
||||
// Set the maximum bandwidth out and in to 500KB/s, largely enough for a single client
|
||||
nettyServer.setWriteLimit(500_000);
|
||||
nettyServer.setReadLimit(500_000);
|
||||
}
|
||||
|
||||
// Start the server
|
||||
minecraftServer.start("localhost", 25565);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import net.minestom.server.command.builder.Arguments;
|
||||
import net.minestom.server.command.builder.Command;
|
||||
import net.minestom.server.command.builder.arguments.Argument;
|
||||
import net.minestom.server.command.builder.arguments.ArgumentType;
|
||||
import net.minestom.server.entity.Player;
|
||||
|
||||
public class TestCommand extends Command {
|
||||
|
||||
@ -19,11 +20,12 @@ public class TestCommand extends Command {
|
||||
});
|
||||
|
||||
setDefaultExecutor((source, args) -> {
|
||||
System.out.println("DEFAULT");
|
||||
System.gc();
|
||||
source.sendMessage("Explicit GC executed!");
|
||||
});
|
||||
|
||||
addSyntax((source, args) -> {
|
||||
Player player = (Player) source;
|
||||
System.out.println("ARG 1");
|
||||
}, test);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user