├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle ├── sonar-project.properties └── src ├── main └── io.github.joealisson.mmocore │ ├── io │ └── github │ │ └── joealisson │ │ └── mmocore │ │ ├── Buffer.java │ │ ├── Client.java │ │ ├── ClientFactory.java │ │ ├── Connection.java │ │ ├── ConnectionBuilder.java │ │ ├── ConnectionConfig.java │ │ ├── ConnectionFilter.java │ │ ├── ConnectionHandler.java │ │ ├── Connector.java │ │ ├── PacketExecutor.java │ │ ├── PacketHandler.java │ │ ├── ReadHandler.java │ │ ├── ReadableBuffer.java │ │ ├── ReadablePacket.java │ │ ├── ResourcePool.java │ │ ├── WritableBuffer.java │ │ ├── WritablePacket.java │ │ ├── WriteHandler.java │ │ └── internal │ │ ├── ArrayPacketBuffer.java │ │ ├── BufferPool.java │ │ ├── DynamicPacketBuffer.java │ │ ├── InternalWritableBuffer.java │ │ ├── MMOThreadFactory.java │ │ ├── NotWrittenBufferException.java │ │ ├── SinglePacketBuffer.java │ │ └── fairness │ │ ├── FairnessController.java │ │ ├── FairnessStrategy.java │ │ ├── MultiBucketStrategy.java │ │ └── SingleBucketStrategy.java │ └── module-info.java └── test ├── java └── io │ └── github │ └── joealisson │ └── mmocore │ ├── ArrayPacketBufferTest.java │ ├── AsyncClient.java │ ├── AsyncClientBroadcastPacket.java │ ├── AsyncClientBroadcastReceiverPacket.java │ ├── AsyncClientClosePacket.java │ ├── AsyncClientClosedConnection.java │ ├── AsyncClientFairnessPacket.java │ ├── AsyncClientFairnessReceivePacket.java │ ├── AsyncClientFairnessRepliedPacket.java │ ├── AsyncClientFairnessRespondPacket.java │ ├── AsyncClientPingPacket.java │ ├── AsyncClientPongPacket.java │ ├── AsyncServerClosePacket.java │ ├── AsyncServerClosedConnection.java │ ├── AsyncServerPingPacket.java │ ├── AsyncServerPongPacket.java │ ├── ClientTest.java │ ├── CommunicationTest.java │ ├── ConfigurationTest.java │ ├── ConnectionHandlerTest.java │ ├── ConnectionTest.java │ ├── DynamicPacketBufferTest.java │ ├── GenericClientHandler.java │ ├── ReadHandlerTest.java │ ├── ReadingThrottlingHelper.java │ ├── SinglePacketBufferTest.java │ ├── WriteHandlerTest.java │ └── util │ ├── Comparator.java │ └── ValueMismatchException.java └── resources ├── async-mmocore-wrong.properties └── async-mmocore.properties /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - '**' 6 | tags-ignore: 7 | - '**' 8 | pull_request: 9 | types: 10 | - opened 11 | - synchronize 12 | - reopened 13 | 14 | jobs: 15 | build: 16 | name: Build 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v2 20 | with: 21 | fetch-depth: 0 22 | - name: Set up JDK 11 23 | uses: actions/setup-java@v1 24 | with: 25 | java-version: 11 26 | - name: Cache Gradle packages 27 | uses: actions/cache@v1 28 | with: 29 | path: ~/.gradle/caches 30 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} 31 | restore-keys: ${{ runner.os }}-gradle 32 | - name: Cache SonarCloud packages 33 | uses: actions/cache@v1 34 | with: 35 | path: ~/.sonar/cache 36 | key: ${{ runner.os }}-sonar 37 | restore-keys: ${{ runner.os }}-sonar 38 | - name: Build and analyze 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 42 | ossrhUsername: ${{ secrets.SONATYPE_USER }} 43 | ossrhPassword: ${{ secrets.SONATYPE_PWD }} 44 | run: ./gradlew build sonarqube 45 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | release: 4 | types: 5 | - published 6 | 7 | jobs: 8 | build: 9 | name: Build 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | - name: Set up JDK 11 16 | uses: actions/setup-java@v1 17 | with: 18 | java-version: 11 19 | - name: Cache Gradle packages 20 | uses: actions/cache@v1 21 | with: 22 | path: ~/.gradle/caches 23 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} 24 | restore-keys: ${{ runner.os }}-gradle 25 | - name: Configure GPG Key 26 | run: | 27 | mkdir -p /home/runner/.gnupg/ 28 | printf "$GPG_SIGNING_KEY" | base64 --decode > /home/runner/.gnupg/secring.gpg 29 | gpg --import /home/runner/.gnupg/secring.gpg 30 | env: 31 | GPG_SIGNING_KEY: ${{ secrets.SIGN_RING_FILE }} 32 | - name: Publish 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | ossrhUsername: ${{ secrets.SONATYPE_USER }} 36 | ossrhPassword: ${{ secrets.SONATYPE_PWD }} 37 | signingKeyId: ${{ secrets.SIGN_KEY_ID }} 38 | signingPassword: ${{ secrets.SIGN_PWD }} 39 | run: ./gradlew publish -PossrhUsername="$ossrhUsername" -PossrhPassword="$ossrhPassword" -Psigning.keyId="$signingKeyId" -Psigning.password="$signingPassword" -Psigning.secretKeyRingFile=/home/runner/.gnupg/secring.gpg 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle 2 | build/ 3 | 4 | # IntelliJ 5 | out/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # async-mmocore 2 | ![Code Badge](https://img.shields.io/badge/Project-L2J-red.svg?logo=github&logoColor=white) 3 | ![Code Badge](https://img.shields.io/badge/Powered_by-Java_11-lightgray.svg?logo=java&logoColor=white) 4 | [![Code Badge](https://img.shields.io/badge/Versioning-Semantic-green.svg?logo=git&logoColor=white)](https://semver.org/) 5 | [![License badge](https://img.shields.io/badge/license-GPL-blue.svg?logo=gnu&logoColor=white)](https://opensource.org/licenses/AGPL-3.0) 6 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.joealisson/async-mmocore/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.joealisson/async-mmocore) 7 | 8 | ###### Quality Metrics 9 | 10 | ![Build](https://github.com/JoeAlisson/async-mmocore/workflows/Build/badge.svg) 11 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=JoeAlisson_async-mmocore2&metric=alert_status)](https://sonarcloud.io/dashboard?id=JoeAlisson_async-mmocore2) 12 | [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=JoeAlisson_async-mmocore2&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=JoeAlisson_async-mmocore2) 13 | [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=JoeAlisson_async-mmocore2&metric=security_rating)](https://sonarcloud.io/dashboard?id=JoeAlisson_async-mmocore2) 14 | [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=JoeAlisson_async-mmocore2&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=JoeAlisson_async-mmocore2) 15 | [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=JoeAlisson_async-mmocore2&metric=coverage)](https://sonarcloud.io/dashboard?id=JoeAlisson_async-mmocore2) 16 | [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=JoeAlisson_async-mmocore2&metric=bugs)](https://sonarcloud.io/dashboard?id=JoeAlisson_async-mmocore2) 17 | [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=JoeAlisson_async-mmocore2&metric=code_smells)](https://sonarcloud.io/dashboard?id=JoeAlisson_async-mmocore2) 18 | 19 | 20 | #### The Goal 21 | 22 | The _**Async-mmocore**_ is primarily designed to **Massive Multiplayer Online (MMO) Game Servers**. 23 | The Goal of the Async-mmocore is to provide an easy way to handle MMO connections to a server abstracting the networking layer complexity. 24 | 25 | #### The Requirements 26 | 27 | The _**Async-mmocore**_ is built on top of [Java NIO.2 API](https://openjdk.java.net/projects/nio/) using Asynchronous Socket Channels. It is recommended Java 11+ to build and run. 28 | 29 | ###### The ReadablePacket and WritablePacket Classes 30 | 31 | These classes, herein referenced as **packets**, are the abstraction of data send through the network. 32 | All packets must have a **Header** and an optional **payload**. 33 | 34 | The header is a **Short** number the carries out the size the packet. 35 | The payload is the essential information to the server or client. The packet must be composed by at maximum 32767 bytes. 36 | Packets greater than this can be lead to unexpected behaviour. 37 | 38 | #### The Basics to Use 39 | 40 | * ##### Define a Client Implementation 41 | 42 | The client Class is a representation of an external connection. Thus, it's the unique source of incoming packets and the target of the outcome packets. 43 | 44 | The Client Class must implement the [abstract class Client](https://github.com/JoeAlisson/async-mmocore/blob/master/src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/Client.java) 45 | 46 | ```java 47 | public class ClientImpl extends Client> { 48 | 49 | public ClientImpl(Connection connection) { 50 | super(connection); 51 | } 52 | 53 | @Override 54 | public boolean decrypt(Buffer data, int offset, int size) { 55 | return myCrypter.decrypt(data, offset, size); 56 | } 57 | 58 | @Override 59 | public boolean encrypt(Buffer data, int offset, int size) { 60 | return myCrypter.encrypt(data, offset, size); 61 | } 62 | 63 | @Override 64 | protected void onDisconnection() { 65 | saveDataAndReleaseResources(); 66 | } 67 | 68 | @Override 69 | public void onConnected() { 70 | doTheInitialJob(); 71 | } 72 | 73 | public void sendPacket(WritablePacket packet) { 74 | writePacket(packet); 75 | } 76 | } 77 | ``` 78 | 79 | * ##### Define a Client Factory Implementation 80 | 81 | The Client Factory instantiate the new incoming connections. 82 | 83 | The Client Factory must implement the [interface ClientFactory](https://github.com/JoeAlisson/async-mmocore/blob/master/src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/ClientFactory.java) 84 | 85 | ```java 86 | public class ClientFactoryImpl implements ClientFactory { 87 | 88 | @Override 89 | public ClientImpl create(Connection connection) { 90 | return new ClientImpl(connection); 91 | } 92 | } 93 | ``` 94 | 95 | * ##### Define a Packet Handler Implementation 96 | 97 | The Packet Handler converts the incoming data into a **ReadablePacket**. 98 | 99 | The Packet Handler must implement the [interface PacketHandler](https://github.com/JoeAlisson/async-mmocore/blob/master/src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/PacketHandler.java) 100 | ```java 101 | public class PacketHandlerImpl implements PacketHandler { 102 | 103 | @Override 104 | public ReadablePacket handlePacket(ReadableBuffer buffer, ClientImpl client) { 105 | ReadablePacket packet = convertToPacket(buffer, client); 106 | return packet; 107 | } 108 | } 109 | 110 | ``` 111 | 112 | * ##### Define a Packet Executor Implementation 113 | 114 | The Packet Executor executes the incoming Packets. 115 | 116 | **Although the packet can be executed in the same Thread, it's HIGHLY recommended that the Executors executes the packet on an apart Thread. 117 | That's because the Thread that calls the _execute_ method is the same which process the network I/O operations. Thus, these threads must be short-living and execute only non-blocking operations.** 118 | 119 | The Packet Executor must implement the [interface PacketExecutor](https://github.com/JoeAlisson/async-mmocore/blob/master/src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/PacketExecutor.java) 120 | 121 | ```java 122 | public class PacketExecutorImpl implements PacketExecutor { 123 | 124 | @Override 125 | public void execute(ReadablePacket packet) { 126 | threadPoolExecutor.execute(packet); 127 | } 128 | } 129 | ``` 130 | 131 | * ##### Listen Connections 132 | 133 | To listen Connections it's necessary to build a ConnectionHandler 134 | 135 | ```java 136 | public class ServerHandler { 137 | public void startListen(String host, int port) { 138 | ConnectionHandler connectionHandler = ConnectionBuilder.create(new InetSocketAddress(host, port), new ClientFactoryImpl(), new PacketHandlerImpl(), new PacketExecutorImpl()).build(); 139 | connectionHandler.start(); 140 | } 141 | } 142 | 143 | ``` 144 | 145 | * ##### Sending a Packet 146 | 147 | To send a Packet it's necessary to implement the [abstract class WritablePacket](https://github.com/JoeAlisson/async-mmocore/blob/master/src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/WritablePacket.java) 148 | 149 | ```java 150 | public class ServerInfo implements WritablePacket { 151 | @Override 152 | protected boolean write(ClientImpl client, WritableBuffer buffer) { 153 | buffer.writeByte(this.getServerId()); 154 | buffer.writeString(this.getServerName()); 155 | buffer.writeLong(this.getServerCurrentTime()); 156 | buffer.writeInt(this.getServerCurrentUsers()); 157 | return true; 158 | } 159 | } 160 | ``` 161 | After it just send it through the client 162 | 163 | ```java 164 | public class ServerHandler { 165 | public void sendServerInfoToClient(ClientImpl client) { 166 | client.sendPacket(new ServerInfo()); 167 | } 168 | } 169 | ``` 170 | 171 | * ##### Receiving a Packet 172 | 173 | The receiving packet is almost all done by the **Async-mmocore**. The only part to be implemented to fully read is the steps described in [Define a Packet Handler Implementation](#define-a-packet-handler-implementation) and [Define a Packet Executor Implementation](#define-a-packet-executor-implementation) sections. 174 | ```java 175 | public class ReceivedServerInfo implements ReadablePacket { 176 | 177 | @Override 178 | protected boolean read() { 179 | this.serverId = readByte(); 180 | this.serverName = readString(); 181 | this.serverCurrentTime = readLong(); 182 | this.serverCurrentUsers = readInt(); 183 | return true; 184 | } 185 | 186 | @Override 187 | public void run() { 188 | showServerInfoToClient(); 189 | } 190 | } 191 | ``` 192 | 193 | #### Client Side 194 | 195 | The class Connector provides client side asynchronous connection support. It works just like ConnectionBuilder, so you must define the ClientFactory, the PacketHandler and the PacketExecutor implementations. 196 | 197 | ```java 198 | public class ConnectionFactory { 199 | 200 | public static ClientImpl create(String host, int port) { 201 | ClientImpl client = Connector.create(clientFactory, packetHandler, packetExecutor).connect(new InetSocketAddress(host, port)); 202 | return client; 203 | } 204 | 205 | } 206 | ``` -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import java.text.SimpleDateFormat 2 | 3 | plugins { 4 | id 'java-library' 5 | id 'idea' 6 | id 'eclipse' 7 | id 'maven-publish' 8 | id 'signing' 9 | id 'net.nemerosa.versioning' version '2.14.0' 10 | id 'jacoco' 11 | id 'org.sonarqube' version '3.2.0' 12 | } 13 | 14 | group = 'io.github.joealisson' 15 | version = '3.4.0' 16 | 17 | sourceCompatibility = JavaVersion.VERSION_11 18 | 19 | ext.moduleName = 'io.github.joealisson.mmocore' 20 | ext.revision = versioning.info.build 21 | ext.buildJDK = "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']})".toString() 22 | 23 | sourceSets { 24 | main { 25 | java { 26 | srcDirs = ['src/main/io.github.joealisson.mmocore'] 27 | } 28 | resources { 29 | srcDirs = ['src/main/resources'] 30 | } 31 | } 32 | } 33 | 34 | repositories { 35 | mavenCentral() 36 | } 37 | 38 | dependencies { 39 | api 'org.slf4j:slf4j-api:2.0.0-alpha1' 40 | 41 | testImplementation 'junit:junit:4.13.2' 42 | testImplementation 'org.awaitility:awaitility:4.0.3' 43 | } 44 | 45 | afterEvaluate { 46 | compileJava { 47 | inputs.property("moduleName", moduleName) 48 | doFirst { 49 | options.compilerArgs = ['--module-path', classpath.asPath ] 50 | options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" 51 | classpath = files() 52 | } 53 | } 54 | } 55 | 56 | jar { 57 | manifest { 58 | attributes('Built-By' : System.getProperty('user.name'), 59 | 'Build-Timestamp': new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()), 60 | 'Build-Revision' : revision, 61 | 'Build-Version' : archiveVersion, 62 | 'Build-Jdk' : buildJDK, 63 | 'Automatic-Module-Name': moduleName) 64 | } 65 | } 66 | 67 | task jarSources(type:Jar){ 68 | archiveClassifier = "sources" 69 | from sourceSets.main.allSource 70 | } 71 | 72 | javadoc { 73 | exclude 'module-info.java' 74 | } 75 | 76 | task javadocJar(type: Jar) { 77 | archiveClassifier = 'javadoc' 78 | from javadoc 79 | } 80 | 81 | jacocoTestReport { 82 | reports { 83 | xml.enabled true 84 | } 85 | } 86 | 87 | plugins.withType(JacocoPlugin) { 88 | tasks["test"].finalizedBy 'jacocoTestReport' 89 | } 90 | 91 | 92 | publishing { 93 | publications { 94 | 95 | maven(MavenPublication) { 96 | from components.java 97 | 98 | artifact jarSources 99 | artifact javadocJar 100 | 101 | tasks.withType(Jar) { 102 | from(project.projectDir) { 103 | include 'LICENSE' 104 | into 'META-INF' 105 | } 106 | } 107 | 108 | pom { 109 | name = 'Async Mmocore' 110 | description = 'The Goal of the async-mmocore is to provide a way to handle MMO connections abstracting the networking layer complexity.' 111 | url = 'https://github.com/JoeAlisson/async-mmocore' 112 | licenses { 113 | license { 114 | name = 'GNU General Public License v3.0' 115 | url = 'https://opensource.org/licenses/AGPL-3.0' 116 | } 117 | } 118 | developers { 119 | developer { 120 | id = 'JoeAlisson' 121 | name = 'Alisson Oliveira' 122 | email = 'joe.alisson@gmail.com' 123 | } 124 | } 125 | scm { 126 | connection = 'scm:git:git://github.com/JoeAlisson/async-mmocore.git' 127 | developerConnection = 'scm:git:ssh://github.com:JoeAlisson/async-mmocore.git' 128 | url = 'https://github.com/JoeAlisson/async-mmocore/tree/master' 129 | } 130 | } 131 | } 132 | } 133 | 134 | repositories { 135 | maven { 136 | def releasesRepoUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 137 | def snapshotsRepoUrl = "https://oss.sonatype.org/content/repositories/snapshots/" 138 | url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl 139 | credentials { 140 | username ossrhUsername 141 | password ossrhPassword 142 | } 143 | } 144 | } 145 | } 146 | 147 | signing { 148 | sign publishing.publications.maven 149 | } 150 | 151 | sonarqube { 152 | properties { 153 | property "sonar.projectKey", "JoeAlisson_async-mmocore2" 154 | property "sonar.organization", "joealisson-github" 155 | property "sonar.host.url", "https://sonarcloud.io" 156 | } 157 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | signing.keyId=KEY 2 | signing.password=PWD 3 | signing.secretKeyRingFile=KEYFILE 4 | 5 | ossrhUsername=USER 6 | ossrhPassword=PWD 7 | 8 | systemProp.org.gradle.internal.publish.checksums.insecure=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JoeAlisson/async-mmocore/23a485b5c4512f97cdb78886026546bfa0e1c9ee/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'async-mmocore' -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.host.url=https://sonarcloud.io 2 | # must be unique in a given SonarQube instance 3 | sonar.projectKey=JoeAlisson_async-mmocore 4 | # this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1. 5 | sonar.projectName=Async Mmocore 6 | sonar.projectVersion=3.3.0 7 | 8 | sonar.language=java 9 | sonar.java.source=11 10 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 11 | # This property is optional if sonar.modules is set. 12 | sonar.sources=src/main/io.github.joealisson.mmocore/ 13 | sonar.tests=src/test/java/ 14 | sonar.java.binaries=./build/classes 15 | 16 | sonar.coverage.exclusions=**/build/docs**,**/build/reports/**,**/build/test-results/**,**/src/test/** 17 | sonar.exclusions=**/build/docs**,**/build/reports/**,**/build/test-results/**,**/src/test/** 18 | 19 | # Encoding of the source code. Default is default system encoding 20 | sonar.sourceEncoding=UTF-8 21 | 22 | sonar.links.homepage=https://github.com/JoeAlisson/async-mmocore 23 | sonar.links.ci=https://travis-ci.org/JoeAlisson/async-mmocore 24 | sonar.links.scm=https://github.com/JoeAlisson/async-mmocore 25 | sonar.links.issue=https://github.com/JoeAlisson/async-mmocore/issues -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/Buffer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | /** 22 | * @author JoeAlisson 23 | */ 24 | public interface Buffer { 25 | 26 | /** 27 | * Read byte from the buffer at index.
28 | * 8bit integer (00) 29 | * 30 | * @param index index to read from 31 | * @return the byte value at the index. 32 | */ 33 | byte readByte(int index); 34 | 35 | /** 36 | * Write byte to the buffer at index.
37 | * 8bit integer (00) 38 | * 39 | * @param index index to write to 40 | * @param value to be written 41 | */ 42 | void writeByte(int index, byte value); 43 | 44 | /** 45 | * Read short from the buffer at index.
46 | * 16bit integer (00 00) 47 | * 48 | * @param index index to read from 49 | * @return the short value at index. 50 | */ 51 | short readShort(int index); 52 | 53 | /** 54 | * Write int to the buffer at index.
55 | * 16bit integer (00 00) 56 | * 57 | * @param index index to write to 58 | * @param value to be written 59 | */ 60 | void writeShort(int index, short value); 61 | 62 | /** 63 | * Read int from the buffer at index.
64 | * 32bit integer (00 00 00 00) 65 | * 66 | * @param index index to read from 67 | * @return the int value at index. 68 | */ 69 | int readInt(int index); 70 | 71 | /** 72 | * Write int to the buffer at index.
73 | * 32bit integer (00 00 00 00) 74 | * 75 | * @param index index to write to 76 | * @param value to be written 77 | */ 78 | void writeInt(int index, int value); 79 | 80 | /** 81 | * @return the buffer's limit 82 | */ 83 | int limit(); 84 | 85 | /** 86 | * Change the buffer's limit 87 | * 88 | * @param newLimit the new limit 89 | */ 90 | void limit(int newLimit); 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/ClientFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | /** 22 | * @author JoeAlisson 23 | */ 24 | @FunctionalInterface 25 | public interface ClientFactory>> { 26 | 27 | /** 28 | * This method must create a Client using the connection parameter. 29 | * 30 | * @param connection - the underlying connection to client. 31 | * 32 | * @return a client implementation. 33 | */ 34 | T create(Connection connection); 35 | } 36 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/Connection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import java.io.IOException; 25 | import java.net.InetSocketAddress; 26 | import java.nio.ByteBuffer; 27 | import java.nio.channels.AsynchronousSocketChannel; 28 | import java.util.concurrent.TimeUnit; 29 | 30 | import static java.util.Objects.nonNull; 31 | 32 | /** 33 | * @author JoeAlisson 34 | */ 35 | public class Connection>> { 36 | 37 | private static final Logger LOGGER = LoggerFactory.getLogger(Connection.class); 38 | 39 | private final AsynchronousSocketChannel channel; 40 | private final ReadHandler readHandler; 41 | private final WriteHandler writeHandler; 42 | final ConnectionConfig config; 43 | private T client; 44 | 45 | private ByteBuffer readingBuffer; 46 | private ByteBuffer[] writingBuffers; 47 | 48 | Connection(AsynchronousSocketChannel channel, ReadHandler readHandler, WriteHandler writeHandler, ConnectionConfig config) { 49 | this.channel = channel; 50 | this.readHandler = readHandler; 51 | this.writeHandler = writeHandler; 52 | this.config = config; 53 | } 54 | 55 | void setClient(T client) { 56 | this.client = client; 57 | } 58 | 59 | final void read() { 60 | if(channel.isOpen()) { 61 | channel.read(readingBuffer, client, readHandler); 62 | } 63 | } 64 | 65 | final void readHeader() { 66 | if(channel.isOpen()) { 67 | releaseReadingBuffer(); 68 | readingBuffer = config.resourcePool.getHeaderBuffer(); 69 | read(); 70 | } 71 | } 72 | 73 | void read(int size) { 74 | if(channel.isOpen()) { 75 | readingBuffer = config.resourcePool.recycleAndGetNew(readingBuffer, size); 76 | read(); 77 | } 78 | } 79 | 80 | final boolean write(ByteBuffer[] buffers) { 81 | if(!channel.isOpen()) { 82 | return false; 83 | } 84 | writingBuffers = buffers; 85 | write(); 86 | return true; 87 | } 88 | 89 | final void write() { 90 | if(channel.isOpen() && nonNull(writingBuffers)) { 91 | channel.write(writingBuffers, 0, writingBuffers.length, -1, TimeUnit.MILLISECONDS, client, writeHandler); 92 | } else if(nonNull(client)) { 93 | client.finishWriting(); 94 | } 95 | } 96 | 97 | ByteBuffer getReadingBuffer() { 98 | return readingBuffer; 99 | } 100 | 101 | private void releaseReadingBuffer() { 102 | if(nonNull(readingBuffer)) { 103 | config.resourcePool.recycleBuffer(readingBuffer); 104 | readingBuffer = null; 105 | } 106 | } 107 | 108 | boolean releaseWritingBuffer() { 109 | boolean released = false; 110 | if(nonNull(writingBuffers)) { 111 | for (ByteBuffer buffer : writingBuffers) { 112 | config.resourcePool.recycleBuffer(buffer); 113 | released = true; 114 | } 115 | writingBuffers = null; 116 | } 117 | return released; 118 | } 119 | 120 | void close() { 121 | releaseReadingBuffer(); 122 | releaseWritingBuffer(); 123 | try { 124 | if(channel.isOpen()) { 125 | channel.close(); 126 | } 127 | } catch (IOException e) { 128 | LOGGER.warn(e.getMessage(), e); 129 | } finally { 130 | client = null; 131 | } 132 | } 133 | 134 | String getRemoteAddress() { 135 | try { 136 | InetSocketAddress address = (InetSocketAddress) channel.getRemoteAddress(); 137 | return address.getAddress().getHostAddress(); 138 | } catch (IOException e) { 139 | return ""; 140 | } 141 | } 142 | 143 | boolean isOpen() { 144 | return channel.isOpen(); 145 | } 146 | 147 | ResourcePool getResourcePool() { 148 | return config.resourcePool; 149 | } 150 | 151 | int dropPacketThreshold() { 152 | return config.dropPacketThreshold; 153 | } 154 | 155 | boolean isAutoReadingEnabled() { 156 | return config.autoReading; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/ConnectionBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import java.io.IOException; 22 | import java.net.InetSocketAddress; 23 | 24 | import static java.lang.Thread.MAX_PRIORITY; 25 | import static java.lang.Thread.MIN_PRIORITY; 26 | 27 | /** 28 | * Builds the {@link ConnectionHandler} responsible to manage all incoming connections. 29 | * 30 | * @author JoeAlisson 31 | */ 32 | public class ConnectionBuilder>> { 33 | 34 | private ConnectionConfig config; 35 | private ReadHandler readerHandler; 36 | private ClientFactory clientFactory; 37 | 38 | /** 39 | * Creates a ConnectionBuilder holding the minimum requirements to create a ConnectionHandler. 40 | * 41 | * @param address - The socket address to listen the incoming connections. 42 | * @param clientFactory - The factory responsible to create a new Client when a new connection is accepted. 43 | * @param packetHandler - The handle responsible to convert the data received into a {@link ReadablePacket} 44 | * @param executor - The responsible to execute the incoming packets. 45 | * @param - The Type of client that ConnectionBuilder will handle. 46 | * 47 | * @return A ConnectionBuilder with default configuration. 48 | * 49 | */ 50 | public static >> ConnectionBuilder create(InetSocketAddress address, ClientFactory clientFactory, PacketHandler packetHandler, PacketExecutor executor) { 51 | ConnectionBuilder builder = new ConnectionBuilder<>(); 52 | builder.config = new ConnectionConfig(address); 53 | builder.readerHandler = new ReadHandler<>(packetHandler, executor); 54 | builder.clientFactory = clientFactory; 55 | return builder; 56 | } 57 | 58 | /** 59 | * Sets a filter to be used on incoming connections. 60 | * The filter must decide if a connection is acceptable or not. 61 | * 62 | * @param filter - the {@link ConnectionFilter} to be set. 63 | * 64 | * @return this. 65 | */ 66 | public ConnectionBuilder filter(ConnectionFilter filter) { 67 | this.config.acceptFilter = filter; 68 | return this; 69 | } 70 | 71 | /** 72 | * Configures the connection to use CachedThreadPool as defined in {@link java.nio.channels.AsynchronousChannelGroup#withCachedThreadPool(java.util.concurrent.ExecutorService, int)}. 73 | * 74 | * The default behaviour is to use a fixed thread poll as defined in {@link java.nio.channels.AsynchronousChannelGroup#withFixedThreadPool(int, java.util.concurrent.ThreadFactory)}. 75 | * 76 | * @param cached use a cached thread pool if true, otherwise use fixed thread pool 77 | * @return this 78 | */ 79 | public ConnectionBuilder useCachedThreadPool(boolean cached) { 80 | this.config.useCachedThreadPool = cached; 81 | return this; 82 | } 83 | 84 | /** 85 | * Set the size of the threadPool used to manage the connections and data sending. 86 | * 87 | * If the thread pool is cached this method defines the corePoolSize of ({@link java.util.concurrent.ThreadPoolExecutor}) 88 | * If the thread pool is fixed this method defines the amount of threads 89 | * 90 | * The min accepted value is 1. 91 | * The default value is the quantity of available processors minus 2. 92 | * 93 | * @param size - the size of thread pool to be Set 94 | * 95 | * @return this 96 | */ 97 | public ConnectionBuilder threadPoolSize(int size) { 98 | this.config.threadPoolSize = size; 99 | return this; 100 | } 101 | 102 | /** 103 | * Set the size of max threads allowed in the cached thread pool. 104 | * The execution we be rejected when all the threads in the cached thread pool is busy after reaching the max thread allowed. 105 | * 106 | * This config is ignored when a fixed thread pool is used. 107 | * 108 | * @param size the max cached threads in the cached thread pool. 109 | * @return this 110 | */ 111 | public ConnectionBuilder maxCachedThreads(int size) { 112 | this.config.maxCachedThreads = size; 113 | return this; 114 | } 115 | 116 | /** 117 | * Defines if small outgoing packets must be combined to be sent all at once. This improves the network performance, 118 | * but can cause lags on clients waiting for the packet. 119 | * 120 | * The default value is false. 121 | * 122 | * @param useNagle - true if the Nagle's algorithm must be used. 123 | * 124 | * @return this. 125 | */ 126 | public ConnectionBuilder useNagle(boolean useNagle) { 127 | this.config.useNagle = useNagle; 128 | return this; 129 | } 130 | 131 | /** 132 | * Sets the shutdown wait time in milliseconds. 133 | * 134 | * The default value is 5 seconds. 135 | * 136 | * @param waitTime - the wait time to close all connections resources after a {@link ConnectionHandler#shutdown()} is called. 137 | * 138 | * @return this 139 | */ 140 | public ConnectionBuilder shutdownWaitTime(long waitTime) { 141 | config.shutdownWaitTime = waitTime; 142 | return this; 143 | } 144 | 145 | /** 146 | * Add a new {@link java.nio.ByteBuffer} grouping pool 147 | * 148 | * @param size the max amount of {@link java.nio.ByteBuffer} supported 149 | * @param bufferSize the {@link java.nio.ByteBuffer}'s size inside the pool. 150 | * 151 | * @return this 152 | */ 153 | public ConnectionBuilder addBufferPool(int size, int bufferSize) { 154 | config.newBufferGroup(size, bufferSize); 155 | return this; 156 | } 157 | 158 | /** 159 | * Define the factor of pre-initialized {@link java.nio.ByteBuffer} inside a pool. 160 | * 161 | * @param factor the factor of initialized buffers 162 | * @return this 163 | */ 164 | public ConnectionBuilder initBufferPoolFactor(float factor) { 165 | config.initBufferPoolFactor = factor; 166 | return this; 167 | } 168 | 169 | /** 170 | * Define the size of dynamic buffer's segment. A segment is used to increase the Buffer when needed. 171 | * 172 | * @param size of dynamic buffer segment 173 | * @return this 174 | */ 175 | public ConnectionBuilder bufferSegmentSize(int size) { 176 | config.resourcePool.setBufferSegmentSize(size); 177 | return this; 178 | } 179 | 180 | /** 181 | * Define the threshold to allow the client to drop disposable packets. 182 | * 183 | * When the client has queued more than {@code threshold} disposable packets will be disposed. 184 | * 185 | * @param threshold the minimum value to drop packets. The default value is 250 186 | * @return this 187 | */ 188 | public ConnectionBuilder dropPacketThreshold(int threshold) { 189 | config.dropPacketThreshold = threshold; 190 | return this; 191 | } 192 | 193 | /** 194 | * Define the MMO Threads' Priority. 195 | * The value should be between {@link Thread#MIN_PRIORITY} and {@link Thread#MAX_PRIORITY}. 196 | * 197 | * @see Thread#setPriority(int) 198 | * 199 | * @param priority the thread priority 200 | * @return this 201 | */ 202 | public ConnectionBuilder threadPriority(int priority) { 203 | if (priority > MAX_PRIORITY || priority < MIN_PRIORITY) { 204 | throw new IllegalArgumentException(); 205 | } 206 | config.threadPriority = priority; 207 | return this; 208 | } 209 | 210 | /** 211 | * Define if the auto reading should be disabled. 212 | * 213 | * if the auto reading is enabled the server will read the client's packet automatically. 214 | * Otherwise, the server needs to call the {@link Client#read()}] when the client packet should be read. 215 | * 216 | * The Auto Reading is enabled by default 217 | * 218 | * @param value true if auto reading should be disabled. Otherwise false. 219 | * @return this 220 | */ 221 | public ConnectionBuilder disableAutoReading(boolean value) { 222 | config.autoReading = !value; 223 | return this; 224 | } 225 | 226 | /** 227 | * Define the amount of buckets used in the fairness controller. 228 | * 229 | * The fairness controller is a mechanism used to maintain the network threads fairness among the clients. 230 | * The default value is 1, is recommended to increase this value depending on high concurrency networks applications. 231 | * 232 | * @param buckets the amount of buckets to use 233 | * @return this 234 | */ 235 | public ConnectionBuilder fairnessBuckets(int buckets) { 236 | config.fairnessBuckets = buckets; 237 | return this; 238 | } 239 | 240 | /** 241 | * Builds a new ConnectionHandler based on the options configured. 242 | * 243 | * @return a ConnectionHandler 244 | * 245 | * @throws IOException - If the Socket Address configured can't be used. 246 | */ 247 | public ConnectionHandler build() throws IOException { 248 | return new ConnectionHandler<>(config.complete(), clientFactory, readerHandler); 249 | } 250 | } -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/ConnectionConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import io.github.joealisson.mmocore.internal.BufferPool; 22 | import io.github.joealisson.mmocore.internal.fairness.FairnessController; 23 | 24 | import java.io.IOException; 25 | import java.io.InputStream; 26 | import java.net.SocketAddress; 27 | import java.nio.file.Files; 28 | import java.nio.file.Path; 29 | import java.nio.file.Paths; 30 | import java.util.Properties; 31 | import java.util.regex.Matcher; 32 | import java.util.regex.Pattern; 33 | 34 | import static java.lang.Math.max; 35 | import static java.lang.Runtime.getRuntime; 36 | import static java.util.Objects.nonNull; 37 | 38 | /** 39 | * @author JoeAlisson 40 | */ 41 | class ConnectionConfig { 42 | 43 | private static final int MINIMUM_POOL_GROUPS = 3; 44 | private static final Pattern BUFFER_POOL_PROPERTY = Pattern.compile("(bufferPool\\.\\w+?\\.)size", Pattern.CASE_INSENSITIVE); 45 | 46 | public static final int HEADER_SIZE = 2; 47 | 48 | ResourcePool resourcePool; 49 | ConnectionFilter acceptFilter; 50 | SocketAddress address; 51 | 52 | float initBufferPoolFactor; 53 | long shutdownWaitTime = 5000; 54 | int threadPoolSize; 55 | boolean useNagle; 56 | int dropPacketThreshold = 250; 57 | boolean useCachedThreadPool; 58 | int maxCachedThreads = Integer.MAX_VALUE; 59 | int threadPriority = Thread.NORM_PRIORITY; 60 | boolean autoReading = true; 61 | int fairnessBuckets = 1; 62 | FairnessController fairnessController; 63 | 64 | ConnectionConfig(SocketAddress address) { 65 | this.address = address; 66 | threadPoolSize = max(2, getRuntime().availableProcessors() - 2); 67 | resourcePool = new ResourcePool(); 68 | resourcePool.addBufferPool(HEADER_SIZE, new BufferPool(100, HEADER_SIZE)); 69 | 70 | String systemProperty = System.getProperty("async-mmocore.configurationFile"); 71 | if(nonNull(systemProperty) && !systemProperty.trim().isEmpty()) { 72 | loadProperties(systemProperty); 73 | } 74 | } 75 | 76 | private void loadProperties(String propertyFileName) { 77 | try(final InputStream inputStream = resolvePropertyFile(propertyFileName)) { 78 | if(nonNull(inputStream)) { 79 | Properties properties = new Properties(); 80 | properties.load(inputStream); 81 | configure(properties); 82 | } else { 83 | throw new IllegalArgumentException("Cannot find property file: " + propertyFileName); 84 | } 85 | } catch (IOException e) { 86 | throw new IllegalArgumentException("Failed to read property file", e); 87 | } 88 | } 89 | 90 | private InputStream resolvePropertyFile(String propertyFileName) throws IOException { 91 | final Path path = Paths.get(propertyFileName); 92 | if(Files.isRegularFile(path)) { 93 | return Files.newInputStream(path); 94 | } 95 | InputStream stream = ClassLoader.getSystemResourceAsStream(propertyFileName); 96 | return nonNull(stream) ? stream : getClass().getResourceAsStream(propertyFileName); 97 | } 98 | 99 | private void configure(Properties properties) { 100 | shutdownWaitTime = parseInt(properties, "shutdownWaitTime", 5) * 1000L; 101 | useCachedThreadPool = parseBoolean(properties, "useCachedThreadPool", useCachedThreadPool); 102 | threadPoolSize = Math.max(1, parseInt(properties, "threadPoolSize", threadPoolSize)); 103 | maxCachedThreads = parseInt(properties, "maxCachedThreads", maxCachedThreads); 104 | threadPriority = parseInt(properties, "threadPriority", threadPriority); 105 | initBufferPoolFactor = parseFloat(properties, "bufferPool.initFactor", 0); 106 | dropPacketThreshold = parseInt(properties, "dropPacketThreshold", 200); 107 | resourcePool.setBufferSegmentSize(parseInt(properties, "bufferSegmentSize", resourcePool.getSegmentSize())); 108 | fairnessBuckets = parseInt(properties, "fairnessBuckets", fairnessBuckets); 109 | 110 | properties.stringPropertyNames().forEach(property -> { 111 | Matcher matcher = BUFFER_POOL_PROPERTY.matcher(property); 112 | if(matcher.matches()) { 113 | int size = parseInt(properties, property, 10); 114 | int bufferSize = parseInt(properties, matcher.group(1) + "bufferSize", 1024); 115 | newBufferGroup(size, bufferSize); 116 | } 117 | }); 118 | 119 | newBufferGroup(100, resourcePool.getSegmentSize()); 120 | } 121 | 122 | private boolean parseBoolean(Properties properties, String propertyName, boolean defaultValue) { 123 | try { 124 | return Boolean.parseBoolean(properties.getProperty(propertyName)); 125 | } catch (Exception e) { 126 | return defaultValue; 127 | } 128 | } 129 | 130 | private int parseInt(Properties properties, String propertyName, int defaultValue) { 131 | try{ 132 | return Integer.parseInt(properties.getProperty(propertyName)); 133 | } catch (Exception e) { 134 | return defaultValue; 135 | } 136 | } 137 | 138 | private float parseFloat(Properties properties, String propertyName, float defaultValue) { 139 | try{ 140 | return Float.parseFloat(properties.getProperty(propertyName)); 141 | } catch (Exception e) { 142 | return defaultValue; 143 | } 144 | } 145 | 146 | public void newBufferGroup(int groupSize, int bufferSize) { 147 | resourcePool.addBufferPool(bufferSize, new BufferPool(groupSize, bufferSize)); 148 | } 149 | 150 | public ConnectionConfig complete() { 151 | completeBuffersPool(); 152 | resourcePool.initializeBuffers(initBufferPoolFactor); 153 | fairnessController = FairnessController.init(fairnessBuckets); 154 | return this; 155 | } 156 | 157 | private void completeBuffersPool() { 158 | int missingPools = MINIMUM_POOL_GROUPS - resourcePool.bufferPoolSize(); 159 | 160 | for (int i = 0; i < missingPools; i++) { 161 | int bufferSize = 256 << i; 162 | resourcePool.addBufferPool(bufferSize,new BufferPool(10, bufferSize)); 163 | } 164 | } 165 | } -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/ConnectionFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import java.nio.channels.AsynchronousSocketChannel; 22 | 23 | /** 24 | * The filter of incoming connections. 25 | * 26 | * @author JoeAlisson 27 | */ 28 | @FunctionalInterface 29 | public interface ConnectionFilter { 30 | 31 | /** 32 | * This method must decide if a Connection can be accepted or not. 33 | * 34 | * @param channel - the channel to be filtered 35 | * 36 | * @return if a the channel is acceptable. 37 | */ 38 | boolean accept(AsynchronousSocketChannel channel); 39 | } 40 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/ConnectionHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import io.github.joealisson.mmocore.internal.MMOThreadFactory; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | import java.io.IOException; 26 | import java.net.StandardSocketOptions; 27 | import java.nio.channels.*; 28 | import java.util.concurrent.ExecutorService; 29 | import java.util.concurrent.SynchronousQueue; 30 | import java.util.concurrent.ThreadPoolExecutor; 31 | import java.util.concurrent.TimeUnit; 32 | 33 | import static java.util.Objects.isNull; 34 | import static java.util.Objects.nonNull; 35 | 36 | /** 37 | * @author JoeAlisson 38 | */ 39 | public final class ConnectionHandler>> { 40 | 41 | private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionHandler.class); 42 | 43 | private final AsynchronousChannelGroup group; 44 | private final AsynchronousServerSocketChannel listener; 45 | private final ConnectionConfig config; 46 | private final WriteHandler writeHandler; 47 | private final ReadHandler readHandler; 48 | private final ClientFactory clientFactory; 49 | 50 | ConnectionHandler(ConnectionConfig config, ClientFactory clientFactory, ReadHandler readHandler) throws IOException { 51 | this.config = config; 52 | this.readHandler = readHandler; 53 | this.clientFactory = clientFactory; 54 | writeHandler = new WriteHandler<>(); 55 | group = createChannelGroup(); 56 | listener = openServerSocket(config); 57 | } 58 | 59 | private AsynchronousChannelGroup createChannelGroup() throws IOException { 60 | if(config.useCachedThreadPool) { 61 | LOGGER.debug("Channel group is using CachedThreadPool"); 62 | ExecutorService threadPool = new ThreadPoolExecutor(config.threadPoolSize, config.maxCachedThreads, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), new MMOThreadFactory("Server", config.threadPriority)); 63 | return AsynchronousChannelGroup.withCachedThreadPool(threadPool, 0); 64 | } 65 | LOGGER.debug("Channel group is using FixedThreadPool"); 66 | return AsynchronousChannelGroup.withFixedThreadPool(config.threadPoolSize, new MMOThreadFactory("Server", config.threadPriority)); 67 | } 68 | 69 | private AsynchronousServerSocketChannel openServerSocket(ConnectionConfig config) throws IOException { 70 | var socketChannel = group.provider().openAsynchronousServerSocketChannel(group); 71 | socketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true); 72 | socketChannel.bind(config.address); 73 | return socketChannel; 74 | } 75 | 76 | /** 77 | * Start to listen connections. 78 | */ 79 | public void start() { 80 | listener.accept(null, new AcceptConnectionHandler()); 81 | } 82 | 83 | /** 84 | * Shutdown the connection listener, the thread pool and all associated resources. 85 | * 86 | * This method closes all established connections. 87 | */ 88 | public void shutdown() { 89 | LOGGER.debug("Shutting ConnectionHandler down"); 90 | boolean terminated = false; 91 | try { 92 | listener.close(); 93 | group.shutdown(); 94 | terminated = group.awaitTermination(config.shutdownWaitTime, TimeUnit.MILLISECONDS); 95 | group.shutdownNow(); 96 | } catch (InterruptedException e) { 97 | LOGGER.warn(e.getMessage(), e); 98 | Thread.currentThread().interrupt(); 99 | } catch (Exception e) { 100 | LOGGER.warn(e.getMessage(), e); 101 | } 102 | LOGGER.debug("ConnectionHandler was shutdown with success status {}", terminated); 103 | } 104 | 105 | /** 106 | * Return the current use of Resource Buffers Pools 107 | * 108 | * API Note: This method exists mainly to support debugging, where you want to see the use of Buffers resource. 109 | * 110 | * @return the resource buffers pool stats 111 | */ 112 | public String resourceStats() { 113 | return config.resourcePool.stats(); 114 | } 115 | 116 | private class AcceptConnectionHandler implements CompletionHandler { 117 | @Override 118 | public void completed(AsynchronousSocketChannel clientChannel, Void attachment) { 119 | listenConnections(); 120 | processNewConnection(clientChannel); 121 | } 122 | 123 | private void listenConnections() { 124 | if(listener.isOpen()) 125 | listener.accept(null, this); 126 | } 127 | 128 | @Override 129 | public void failed(Throwable t, Void attachment) { 130 | LOGGER.warn(t.getMessage(), t); 131 | listenConnections(); 132 | } 133 | 134 | private void processNewConnection(AsynchronousSocketChannel channel) { 135 | if(nonNull(channel) && channel.isOpen()) { 136 | try { 137 | connectToChannel(channel); 138 | } catch (ClosedChannelException e) { 139 | LOGGER.debug(e.getMessage(), e); 140 | } catch (Exception e) { 141 | LOGGER.error(e.getMessage(), e); 142 | closeChannel(channel); 143 | } 144 | } 145 | } 146 | 147 | private void closeChannel(AsynchronousSocketChannel channel) { 148 | try { 149 | channel.close(); 150 | } catch (IOException e) { 151 | LOGGER.warn(e.getMessage(), e); 152 | } 153 | } 154 | 155 | private void connectToChannel(AsynchronousSocketChannel channel) throws IOException { 156 | LOGGER.debug("Connecting to {}", channel); 157 | if(acceptConnection(channel)) { 158 | T client = createClient(channel); 159 | client.onConnected(); 160 | client.read(); 161 | } else { 162 | LOGGER.debug("Rejected connection"); 163 | closeChannel(channel); 164 | } 165 | } 166 | 167 | private boolean acceptConnection(AsynchronousSocketChannel channel) { 168 | return isNull(config.acceptFilter) || config.acceptFilter.accept(channel); 169 | } 170 | 171 | private T createClient(AsynchronousSocketChannel channel) throws IOException { 172 | channel.setOption(StandardSocketOptions.TCP_NODELAY, !config.useNagle); 173 | Connection connection = new Connection<>(channel, readHandler, writeHandler, config); 174 | T client = clientFactory.create(connection); 175 | connection.setClient(client); 176 | return client; 177 | } 178 | } 179 | } -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/PacketExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | /** 22 | * This class is responsible to execute the incoming packets. 23 | * 24 | * @author JoeAlisson 25 | */ 26 | @FunctionalInterface 27 | public interface PacketExecutor>> { 28 | 29 | /** 30 | * Executes the packet. 31 | * 32 | * Its highly recommended to execute long running code or blocking code in a another thread. 33 | * 34 | * @param packet the packet to be executed. 35 | * 36 | */ 37 | void execute(ReadablePacket packet); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/PacketHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | /** 22 | * This class is responsible to handler the incoming data. Converting it to a packet. 23 | * 24 | * @author JoeAlisson 25 | */ 26 | @FunctionalInterface 27 | public interface PacketHandler>> { 28 | 29 | /** 30 | * Convert the data into a packet. 31 | * 32 | * @param buffer - the buffer with data to be converted. 33 | * @param client - the client who sends the data 34 | * 35 | * @return A Packet related to the data received. 36 | */ 37 | ReadablePacket handlePacket(ReadableBuffer buffer, T client); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/ReadHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import java.nio.ByteBuffer; 25 | import java.nio.channels.CompletionHandler; 26 | 27 | import static io.github.joealisson.mmocore.ConnectionConfig.HEADER_SIZE; 28 | import static java.util.Objects.nonNull; 29 | 30 | /** 31 | * @author JoeAlisson 32 | */ 33 | class ReadHandler>> implements CompletionHandler { 34 | 35 | private static final Logger LOGGER = LoggerFactory.getLogger(ReadHandler.class); 36 | 37 | private final PacketHandler packetHandler; 38 | private final PacketExecutor executor; 39 | 40 | ReadHandler(PacketHandler packetHandler, PacketExecutor executor) { 41 | this.packetHandler = packetHandler; 42 | this.executor = executor; 43 | } 44 | 45 | @Override 46 | public void completed(Integer bytesRead, T client) { 47 | if(!client.isConnected()) { 48 | return; 49 | } 50 | 51 | LOGGER.debug("Reading {} from {}", bytesRead, client); 52 | if(bytesRead < 0 ) { 53 | client.disconnect(); 54 | return; 55 | } 56 | 57 | if(bytesRead < client.getExpectedReadSize()) { 58 | client.resumeRead(bytesRead); 59 | return; 60 | } 61 | 62 | if(client.isReadingPayload()) { 63 | handlePayload(client); 64 | } else { 65 | handleHeader(client); 66 | } 67 | } 68 | 69 | private void handleHeader(T client) { 70 | ByteBuffer buffer = client.getConnection().getReadingBuffer(); 71 | buffer.flip(); 72 | int dataSize = Short.toUnsignedInt(buffer.getShort()) - HEADER_SIZE; 73 | if(dataSize > 0) { 74 | client.readPayload(dataSize); 75 | } else { 76 | client.read(); 77 | } 78 | } 79 | 80 | private void handlePayload(T client) { 81 | ByteBuffer buffer = client.getConnection().getReadingBuffer(); 82 | buffer.flip(); 83 | parseAndExecutePacket(client, buffer); 84 | client.isReading = false; 85 | if(client.canReadNextPacket()) { 86 | client.read(); 87 | } 88 | } 89 | 90 | private void parseAndExecutePacket(T client, ByteBuffer incomingBuffer) { 91 | LOGGER.debug("Trying to parse data"); 92 | 93 | try { 94 | ReadableBuffer buffer = ReadableBuffer.of(incomingBuffer); 95 | boolean decrypted = client.decrypt(buffer, 0, buffer.remaining()); 96 | 97 | if (decrypted) { 98 | ReadablePacket packet = packetHandler.handlePacket(buffer, client); 99 | LOGGER.debug("Data parsed to packet {}", packet); 100 | if (nonNull(packet)) { 101 | packet.init(client, buffer); 102 | execute(packet); 103 | } 104 | } 105 | } catch (Exception e) { 106 | LOGGER.warn(e.getMessage(), e); 107 | } 108 | } 109 | 110 | private void execute(ReadablePacket packet) { 111 | if(packet.read()) { 112 | LOGGER.debug("packet {} was read from client {}", packet, packet.client); 113 | executor.execute(packet); 114 | } 115 | } 116 | 117 | @Override 118 | public void failed(Throwable e, T client) { 119 | LOGGER.debug("Failed to read from {}", client, e); 120 | client.disconnect(); 121 | } 122 | } -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/ReadableBuffer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import io.github.joealisson.mmocore.internal.SinglePacketBuffer; 22 | 23 | import java.nio.ByteBuffer; 24 | 25 | /** 26 | * @author JoeAlisson 27 | */ 28 | public interface ReadableBuffer extends Buffer { 29 | 30 | /** 31 | * Reads raw byte from the buffer 32 | * @return byte read 33 | */ 34 | byte readByte(); 35 | 36 | /** 37 | * Reads short from the buffer.
38 | * 16bit integer (00 00) 39 | * @return short read 40 | */ 41 | short readShort(); 42 | 43 | /** 44 | * Reads char from the buffer.
45 | * 16bit integer (00 00) 46 | * @return char read 47 | */ 48 | char readChar(); 49 | 50 | /** 51 | * Reads int from the buffer.
52 | * 32bit integer (00 00 00 00) 53 | * @return int read 54 | */ 55 | int readInt(); 56 | 57 | /** 58 | * Reads float from the buffer.
59 | * 32bit float (00 00 00 00) 60 | * @return float read 61 | */ 62 | float readFloat(); 63 | 64 | /** 65 | * Reads long from the buffer.
66 | * 64bit integer (00 00 00 00 00 00 00 00) 67 | * @return long read 68 | */ 69 | long readLong(); 70 | 71 | /** 72 | * Reads double from the buffer.
73 | * 64bit float (00 00 00 00 00 00 00 00) 74 | * @return double read 75 | */ 76 | double readDouble(); 77 | 78 | /** 79 | * 80 | * Reads as many bytes as the length of the array. 81 | * @param dst : the byte array which will be filled with the data. 82 | */ 83 | void readBytes(byte[] dst); 84 | 85 | /** 86 | * 87 | * Reads as many bytes as the given length (len). Starts to fill the 88 | * byte array from the given offset to offset + len. 89 | * @param dst : the byte array which will be filled with the data. 90 | * @param offset : starts to fill the byte array from the given offset. 91 | * @param length : the given length of bytes to be read. 92 | */ 93 | void readBytes(byte[] dst, int offset, int length); 94 | 95 | /** 96 | * @return the available bytes amount to be read 97 | */ 98 | int remaining(); 99 | 100 | /** 101 | * create a Readable buffer based on buffer 102 | * 103 | * @param buffer the under layer buffer 104 | * @return a Readable Buffer 105 | */ 106 | static ReadableBuffer of(ByteBuffer buffer) { 107 | return new SinglePacketBuffer(buffer); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/ReadablePacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import java.nio.charset.StandardCharsets; 22 | 23 | import static java.lang.Byte.toUnsignedInt; 24 | 25 | /** 26 | * This class represents a Packet received from the client. 27 | * 28 | * All data received must have a header with 2 bytes and an optional payload. 29 | * 30 | * The first and second bytes is a 16 bits integer holding the size of the packet. 31 | * 32 | * @author JoeAlisson 33 | */ 34 | public abstract class ReadablePacket>> implements Runnable { 35 | 36 | private ReadableBuffer buffer; 37 | protected T client; 38 | 39 | protected ReadablePacket() { 40 | // no direct instances 41 | } 42 | 43 | void init(T client, ReadableBuffer buffer) { 44 | this.client = client; 45 | this.buffer = buffer; 46 | } 47 | 48 | /** 49 | * 50 | * @return the available data to be read 51 | */ 52 | protected final int available() { 53 | return buffer.remaining(); 54 | } 55 | 56 | /** 57 | * 58 | * Reads as many bytes as the length of the array. 59 | * @param dst : the byte array which will be filled with the data. 60 | */ 61 | protected final void readBytes(final byte[] dst) { 62 | buffer.readBytes(dst,0, dst.length); 63 | } 64 | 65 | /** 66 | * 67 | * Reads as many bytes as the given length (len). Starts to fill the 68 | * byte array from the given offset to offset + len. 69 | * @param dst : the byte array which will be filled with the data. 70 | * @param offset : starts to fill the byte array from the given offset. 71 | * @param length : the given length of bytes to be read. 72 | */ 73 | protected final void readBytes(final byte[] dst, final int offset, final int length) { 74 | buffer.readBytes(dst, offset, length); 75 | } 76 | 77 | /** 78 | * Reads raw byte from the buffer 79 | * @return byte read 80 | */ 81 | protected final byte readByte() { 82 | return buffer.readByte(); 83 | } 84 | 85 | /** 86 | * Reads byte from the buffer 87 | * @return true if byte is not equal 0 88 | */ 89 | protected final boolean readBoolean() { 90 | return readByte() != 0; 91 | } 92 | 93 | /** 94 | * Reads char from the buffer 95 | * @return char read 96 | */ 97 | protected final char readChar() { 98 | return buffer.readChar(); 99 | } 100 | 101 | /** 102 | * Reads byte from the buffer.
103 | * 8bit integer (00) 104 | * @return unsigned int read 105 | */ 106 | protected final int readUnsignedByte() { 107 | return toUnsignedInt(readByte()); 108 | } 109 | 110 | /** 111 | * Reads short from the buffer.
112 | * 16bit integer (00 00) 113 | * @return short read 114 | */ 115 | protected final short readShort() { 116 | return buffer.readShort(); 117 | } 118 | 119 | /** 120 | * Reads short from the buffer.
121 | * 16bit integer (00 00) 122 | * @return true if the short is not equals 0 123 | */ 124 | protected final boolean readShortAsBoolean() { 125 | return readShort() != 0; 126 | } 127 | 128 | /** 129 | * Reads float from the buffer.
130 | * 32bit precision float (00 00 00 00) 131 | * @return float read 132 | */ 133 | protected final float readFloat() { 134 | return buffer.readFloat(); 135 | } 136 | 137 | /** 138 | * Reads int from the buffer.
139 | * 32bit integer (00 00 00 00) 140 | * @return int read 141 | */ 142 | protected final int readInt() { 143 | return buffer.readInt(); 144 | 145 | } 146 | 147 | /** 148 | * Reads int from the buffer.
149 | * 32bit integer (00 00 00 00) 150 | * @return true if int is not equals 0 151 | */ 152 | protected final boolean readIntAsBoolean() { 153 | return readInt() != 0; 154 | } 155 | 156 | /** 157 | * Reads long from the buffer.
158 | * 64bit integer (00 00 00 00 00 00 00 00) 159 | * @return long read 160 | */ 161 | protected final long readLong() { 162 | return buffer.readLong(); 163 | } 164 | 165 | /** 166 | * Reads double from the buffer.
167 | * 64bit double precision float (00 00 00 00 00 00 00 00) 168 | * @return double read 169 | */ 170 | protected final double readDouble() { 171 | return buffer.readDouble(); 172 | } 173 | 174 | /** 175 | * Reads String from the buffer. 176 | * @return String read 177 | */ 178 | protected final String readString() { 179 | StringBuilder builder = new StringBuilder(); 180 | char c; 181 | while((c = readChar()) != '\000') { 182 | builder.append(c); 183 | } 184 | return builder.toString(); 185 | } 186 | 187 | /** 188 | * Reads a predefined length String from the buffer. 189 | * @return String read 190 | */ 191 | protected final String readSizedString() { 192 | int size = readShort() * 2; 193 | byte[] data = new byte[size]; 194 | readBytes(data); 195 | return new String(data, 0, size, StandardCharsets.UTF_16LE); 196 | } 197 | 198 | public T getClient() { 199 | return client; 200 | } 201 | 202 | protected abstract boolean read(); 203 | } -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/ResourcePool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import io.github.joealisson.mmocore.internal.BufferPool; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | import java.nio.ByteBuffer; 26 | import java.nio.ByteOrder; 27 | import java.util.HashMap; 28 | import java.util.Map; 29 | 30 | import static java.util.Objects.isNull; 31 | import static java.util.Objects.nonNull; 32 | 33 | /** 34 | * @author JoeAlisson 35 | */ 36 | public class ResourcePool { 37 | 38 | private static final Logger LOGGER = LoggerFactory.getLogger(ResourcePool.class); 39 | 40 | private final Map bufferPools; 41 | private int[] bufferSizes; 42 | private int bufferSegmentSize; 43 | 44 | ResourcePool() { 45 | bufferSizes = new int[] { 2, 64 }; 46 | bufferPools = new HashMap<>(4); 47 | bufferSegmentSize = 64; 48 | } 49 | 50 | ByteBuffer getHeaderBuffer() { 51 | return getSizedBuffer(ConnectionConfig.HEADER_SIZE); 52 | } 53 | 54 | ByteBuffer getSegmentBuffer() { 55 | return getSizedBuffer(bufferSegmentSize); 56 | } 57 | 58 | public ByteBuffer getBuffer(int size) { 59 | return getSizedBuffer(determineBufferSize(size)); 60 | } 61 | 62 | ByteBuffer recycleAndGetNew(ByteBuffer buffer, int newSize) { 63 | int bufferSize = determineBufferSize(newSize); 64 | if(nonNull(buffer)) { 65 | if(buffer.clear().limit() == bufferSize) { 66 | return buffer.limit(newSize); 67 | } 68 | recycleBuffer(buffer); 69 | } 70 | return getSizedBuffer(bufferSize).limit(newSize); 71 | } 72 | 73 | private ByteBuffer getSizedBuffer(int size) { 74 | BufferPool pool = bufferPools.get(size); 75 | ByteBuffer buffer = null; 76 | if(nonNull(pool)) { 77 | buffer = pool.get(); 78 | } 79 | if(isNull(buffer)) { 80 | buffer = ByteBuffer.allocateDirect(size).order(ByteOrder.LITTLE_ENDIAN); 81 | } 82 | return buffer; 83 | } 84 | 85 | private int determineBufferSize(int size) { 86 | for (int bufferSize : bufferSizes) { 87 | if(size <= bufferSize) { 88 | return bufferSize; 89 | } 90 | } 91 | LOGGER.warn("There is no buffer pool handling buffer size {}", size); 92 | return size; 93 | } 94 | 95 | public void recycleBuffer(ByteBuffer buffer) { 96 | if (nonNull(buffer)) { 97 | BufferPool pool = bufferPools.get(buffer.capacity()); 98 | if(isNull(pool) || !pool.recycle(buffer)) { 99 | LOGGER.debug("buffer was not recycled {} in pool {}", buffer, pool); 100 | } 101 | } 102 | } 103 | 104 | public int getSegmentSize() { 105 | return bufferSegmentSize; 106 | } 107 | 108 | void addBufferPool(int bufferSize, BufferPool bufferPool) { 109 | bufferPools.putIfAbsent(bufferSize, bufferPool); 110 | } 111 | 112 | int bufferPoolSize() { 113 | return bufferPools.size(); 114 | } 115 | 116 | void initializeBuffers(float initBufferPoolFactor) { 117 | if(initBufferPoolFactor > 0) { 118 | bufferPools.values().forEach(pool -> pool.initialize(initBufferPoolFactor)); 119 | } 120 | bufferSizes = bufferPools.keySet().stream().sorted().mapToInt(Integer::intValue).toArray(); 121 | } 122 | 123 | void setBufferSegmentSize(int size) { 124 | bufferSegmentSize = size; 125 | } 126 | 127 | String stats() { 128 | var sb = new StringBuilder(); 129 | for (BufferPool pool : bufferPools.values()) { 130 | sb.append(pool.toString()).append("\n"); 131 | } 132 | return sb.toString(); 133 | } 134 | 135 | } -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/WritableBuffer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import java.nio.charset.StandardCharsets; 22 | 23 | import static java.util.Objects.isNull; 24 | import static java.util.Objects.nonNull; 25 | 26 | /** 27 | * @author JoeAlisson 28 | */ 29 | public abstract class WritableBuffer implements Buffer { 30 | 31 | /** 32 | * Write a byte to the buffer.
33 | * 8bit integer (00) 34 | * 35 | * If the underlying data array can't hold a new byte its size is increased 20% 36 | * 37 | * @param value to be written 38 | */ 39 | public abstract void writeByte(final byte value); 40 | 41 | /** 42 | * Write a int to the buffer, the int is casted to a byte; 43 | * 44 | * @param value to be written 45 | */ 46 | public void writeByte(final int value) { 47 | writeByte((byte) value); 48 | } 49 | 50 | /** 51 | * Write boolean to the buffer.
52 | * If the value is true so write a byte with value 1, otherwise 0 53 | * 8bit integer (00) 54 | * @param value to be written 55 | */ 56 | public void writeByte(final boolean value) { 57 | writeByte((byte) (value ? 0x01 : 0x00)); 58 | } 59 | 60 | /** 61 | * Write byte[] to the buffer.
62 | * 8bit integer array (00 ...) 63 | * @param value to be written 64 | */ 65 | public abstract void writeBytes(final byte... value); 66 | 67 | /** 68 | * Write short to the buffer.
69 | * 16bit integer (00 00) 70 | * @param value to be written 71 | */ 72 | public abstract void writeShort(final short value); 73 | 74 | /** 75 | * Write short to the buffer.
76 | * 16bit integer (00 00) 77 | * @param value to be written 78 | */ 79 | public void writeShort(final int value) { 80 | writeShort((short) value); 81 | } 82 | 83 | /** 84 | * Write boolean to the buffer.
85 | * If the value is true so write a byte with value 1, otherwise 0 86 | * 16bit integer (00 00) 87 | * @param value to be written 88 | */ 89 | public void writeShort(final boolean value) { 90 | writeShort((short) (value ? 0x01 : 0x00)); 91 | } 92 | 93 | /** 94 | * Write char to the buffer.
95 | * 16 bit char 96 | * 97 | * @param value the char to be put on data. 98 | */ 99 | public abstract void writeChar(final char value); 100 | 101 | /** 102 | * Write int to the buffer.
103 | * 32bit integer (00 00 00 00) 104 | * @param value to be written 105 | */ 106 | public abstract void writeInt(final int value); 107 | 108 | /** 109 | * Write boolean to the buffer.
110 | * If the value is true so write a byte with value 1, otherwise 0 111 | * 32bit integer (00 00 00 00) 112 | * @param value to be written 113 | */ 114 | public void writeInt(final boolean value) { 115 | writeInt(value ? 0x01 : 0x00); 116 | } 117 | 118 | /** 119 | * Write float to the buffer.
120 | * 32bit float point number (00 00 00 00) 121 | * @param value to be written 122 | */ 123 | public abstract void writeFloat(final float value); 124 | 125 | /** 126 | * Write long to the buffer.
127 | * 64bit integer (00 00 00 00 00 00 00 00) 128 | * @param value to be written 129 | */ 130 | public abstract void writeLong(final long value); 131 | 132 | /** 133 | * Write double to the buffer.
134 | * 64bit double precision float (00 00 00 00 00 00 00 00) 135 | * @param value to be written 136 | */ 137 | public abstract void writeDouble(final double value); 138 | 139 | /** 140 | * Write a String to the buffer with a null termination (\000). 141 | * Each character is a 16bit char 142 | * 143 | * @param text to be written 144 | */ 145 | public void writeString(final CharSequence text) { 146 | if(isNull(text)) { 147 | writeChar('\000'); 148 | return; 149 | } 150 | writeStringWithCharset(text); 151 | writeChar('\000'); 152 | } 153 | 154 | private void writeStringWithCharset(CharSequence text) { 155 | writeBytes(text.toString().getBytes(StandardCharsets.UTF_16LE)); 156 | } 157 | 158 | /** 159 | * Write String to the buffer preceded by a short 16 bit with String length and no null termination. 160 | * Each character is a 16bit char. 161 | * 162 | * @param text to be written 163 | */ 164 | public void writeSizedString(final CharSequence text) { 165 | if(nonNull(text) && text.length() > 0) { 166 | writeShort(text.length()); 167 | writeStringWithCharset(text); 168 | } else { 169 | writeShort(0); 170 | } 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/WritablePacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import io.github.joealisson.mmocore.internal.ArrayPacketBuffer; 22 | import io.github.joealisson.mmocore.internal.InternalWritableBuffer; 23 | import io.github.joealisson.mmocore.internal.NotWrittenBufferException; 24 | 25 | import static java.util.Objects.nonNull; 26 | 27 | /** 28 | * This class represents a Packet that can be sent to clients. 29 | * 30 | * All data sent must have a header with 2 bytes and an optional payload. 31 | * 32 | * The first and second bytes is a 16 bits integer holding the size of the packet. 33 | * 34 | * @author JoeAlisson 35 | */ 36 | public abstract class WritablePacket>> { 37 | 38 | private volatile boolean broadcast; 39 | private ArrayPacketBuffer broadcastCacheBuffer; 40 | 41 | protected WritablePacket() { } 42 | 43 | InternalWritableBuffer writeData(T client) throws NotWrittenBufferException { 44 | if(broadcast) { 45 | return writeDataWithCache(client); 46 | } 47 | return writeDataToBuffer(client); 48 | } 49 | 50 | private synchronized InternalWritableBuffer writeDataWithCache(T client) throws NotWrittenBufferException { 51 | if (nonNull(broadcastCacheBuffer)) { 52 | return InternalWritableBuffer.dynamicOf(broadcastCacheBuffer, client.getResourcePool()); 53 | } else { 54 | InternalWritableBuffer buffer = writeDataToBuffer(client); 55 | if(buffer instanceof ArrayPacketBuffer) { 56 | broadcastCacheBuffer = (ArrayPacketBuffer) buffer; 57 | buffer = InternalWritableBuffer.dynamicOf(broadcastCacheBuffer, client.getResourcePool()); 58 | } 59 | return buffer; 60 | } 61 | } 62 | 63 | private InternalWritableBuffer writeDataToBuffer(T client) throws NotWrittenBufferException { 64 | InternalWritableBuffer buffer = choosePacketBuffer(client); 65 | 66 | buffer.position(ConnectionConfig.HEADER_SIZE); 67 | if (write(client, buffer)) { 68 | buffer.mark(); 69 | return buffer; 70 | } 71 | buffer.releaseResources(); 72 | throw new NotWrittenBufferException(); 73 | } 74 | 75 | private InternalWritableBuffer choosePacketBuffer(T client) { 76 | if(broadcast) { 77 | return InternalWritableBuffer.arrayBacked(client.getResourcePool()); 78 | } 79 | return InternalWritableBuffer.dynamicOf(client.getResourcePool().getSegmentBuffer(), client.getResourcePool()); 80 | } 81 | 82 | void writeHeader(InternalWritableBuffer buffer, int header) { 83 | buffer.writeShort(0, (short) header); 84 | } 85 | 86 | /** 87 | * Mark this packet as broadcast. A broadcast packet is sent to more than one client. 88 | * 89 | * Caution: This method should be called before {@link Client#writePacket(WritablePacket)} 90 | * 91 | * A broadcast packet will create a Buffer cache where the data is written once and only the copy is sent to the client. 92 | * note: Each copy will be encrypted to each client 93 | * 94 | * @param broadcast true if the packet is sent to more than one client 95 | */ 96 | public void sendInBroadcast(boolean broadcast) { 97 | this.broadcast = broadcast; 98 | } 99 | 100 | /** 101 | * If this method returns true, the packet will be considered disposable. 102 | * 103 | * @param client client to send data to 104 | * @return if the packet is disposable or not. 105 | */ 106 | public boolean canBeDropped(T client) { 107 | return false; 108 | } 109 | 110 | /** 111 | * Writes the data to the client 112 | * 113 | * @return the packet was written successful 114 | * @param client client to send data 115 | * @param buffer where the data is written into 116 | */ 117 | protected abstract boolean write(T client, WritableBuffer buffer); 118 | 119 | @Override 120 | public String toString() { 121 | return getClass().getSimpleName(); 122 | } 123 | } -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/WriteHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import java.io.IOException; 25 | import java.nio.channels.CompletionHandler; 26 | 27 | /** 28 | * @author JoeAlisson 29 | */ 30 | class WriteHandler>> implements CompletionHandler { 31 | 32 | private static final Logger LOGGER = LoggerFactory.getLogger(WriteHandler.class); 33 | 34 | @Override 35 | public void completed(Long result, T client) { 36 | if(result < 0) { 37 | LOGGER.warn("Couldn't send data to client {}", client); 38 | if(client.isConnected()) { 39 | client.disconnect(); 40 | } 41 | return; 42 | } 43 | 44 | if(result < client.getDataSentSize() && result > 0) { 45 | LOGGER.debug("Still {} data to send. Trying to send", result); 46 | client.resumeSend(result); 47 | } else { 48 | client.finishWriting(); 49 | } 50 | } 51 | 52 | @Override 53 | public void failed(Throwable e, T client) { 54 | if(! (e instanceof IOException)) { 55 | LOGGER.warn(e.getMessage(), e); 56 | } 57 | client.disconnect(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/internal/ArrayPacketBuffer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore.internal; 20 | 21 | import io.github.joealisson.mmocore.ReadableBuffer; 22 | import io.github.joealisson.mmocore.ResourcePool; 23 | 24 | import java.nio.ByteBuffer; 25 | import java.util.Arrays; 26 | 27 | import static java.lang.Byte.toUnsignedInt; 28 | import static java.lang.Byte.toUnsignedLong; 29 | import static java.lang.Double.doubleToRawLongBits; 30 | import static java.lang.Double.longBitsToDouble; 31 | import static java.lang.Float.intBitsToFloat; 32 | import static java.util.Objects.isNull; 33 | 34 | /** 35 | * @author JoeAlisson 36 | */ 37 | public class ArrayPacketBuffer extends InternalWritableBuffer implements ReadableBuffer { 38 | 39 | private final ResourcePool resourcePool; 40 | private byte[] data; 41 | private int index; 42 | private int limit; 43 | 44 | /** 45 | * Create a ArrayPacketBuffer 46 | * 47 | * @param size the initial buffer size 48 | * @param resourcePool the resource pool used to get ByteBuffers 49 | */ 50 | public ArrayPacketBuffer(int size, ResourcePool resourcePool) { 51 | data = new byte[size]; 52 | this.resourcePool = resourcePool; 53 | } 54 | 55 | @Override 56 | public void writeByte(byte value) { 57 | writeByte(index++, value); 58 | } 59 | 60 | @Override 61 | public void writeByte(int index, byte value) { 62 | ensureSize(index + 1); 63 | data[index] = value; 64 | } 65 | 66 | private void ensureSize(int size) { 67 | if(data.length < size) { 68 | data = Arrays.copyOf(data, (int) ((data.length + size) * 1.2)); 69 | limit = data.length; 70 | } 71 | } 72 | 73 | @Override 74 | public void writeBytes(byte[] bytes) { 75 | if(isNull(bytes)) { 76 | return; 77 | } 78 | ensureSize(index + bytes.length); 79 | 80 | System.arraycopy(bytes, 0, data, index, bytes.length); 81 | index += bytes.length; 82 | } 83 | 84 | @Override 85 | public void writeShort(short value) { 86 | writeShort(index, value); 87 | index += 2; 88 | } 89 | 90 | @Override 91 | public void writeShort(int index, short value) { 92 | ensureSize(index + 2); 93 | data[index++] = (byte) value; 94 | data[index] = (byte) (value >>> 8); 95 | } 96 | 97 | @Override 98 | public void writeChar(char value) { 99 | writeShort((short) value); 100 | } 101 | 102 | 103 | @Override 104 | public void writeInt(int value) { 105 | writeInt(index, value); 106 | index += 4; 107 | } 108 | 109 | @Override 110 | public void writeInt(int index, int value) { 111 | ensureSize(index + 4); 112 | data[index++] = (byte) value; 113 | data[index++] = (byte) (value >>> 8); 114 | data[index++] = (byte) (value >>> 16); 115 | data[index] = (byte) (value >>> 24); 116 | } 117 | 118 | @Override 119 | public void writeFloat(float value) { 120 | writeInt(Float.floatToRawIntBits(value)); 121 | } 122 | 123 | @Override 124 | public void writeLong(long value) { 125 | ensureSize(index + 8); 126 | data[index++] = (byte) value; 127 | data[index++] = (byte) (value >>> 8); 128 | data[index++] = (byte) (value >>> 16); 129 | data[index++] = (byte) (value >>> 24); 130 | data[index++] = (byte) (value >>> 32); 131 | data[index++] = (byte) (value >>> 40); 132 | data[index++] = (byte) (value >>> 48); 133 | data[index++] = (byte) (value >>> 56); 134 | } 135 | 136 | @Override 137 | public void writeDouble(double value) { 138 | writeLong(doubleToRawLongBits(value)); 139 | } 140 | 141 | @Override 142 | public int position() { 143 | return index; 144 | } 145 | 146 | @Override 147 | public void position(int pos) { 148 | index = pos; 149 | } 150 | 151 | @Override 152 | public byte readByte() { 153 | return data[index++]; 154 | } 155 | 156 | @Override 157 | public byte readByte(int index) { 158 | return data[index]; 159 | } 160 | 161 | @Override 162 | public short readShort() { 163 | return (short) (readUnsigned(index++) | readUnsigned(index++) << 8); 164 | } 165 | 166 | @Override 167 | public short readShort(int index) { 168 | return (short) (readUnsigned(index++) | readUnsigned(index) << 8); 169 | } 170 | 171 | public char readChar() { 172 | return (char) readShort(); 173 | } 174 | 175 | private int readUnsigned(int index) { 176 | return toUnsignedInt(data[index]); 177 | } 178 | 179 | @Override 180 | public int readInt() { 181 | return readUnsigned(index++) | 182 | readUnsigned(index++) << 8 | 183 | readUnsigned(index++) << 16 | 184 | readUnsigned(index++) << 24; 185 | } 186 | 187 | @Override 188 | public float readFloat() { 189 | return intBitsToFloat(readInt()); 190 | } 191 | 192 | @Override 193 | public long readLong() { 194 | return toUnsignedLong(data[index++]) | 195 | toUnsignedLong(data[index++]) << 8 | 196 | toUnsignedLong(data[index++]) << 16 | 197 | toUnsignedLong(data[index++]) << 24 | 198 | toUnsignedLong(data[index++]) << 32 | 199 | toUnsignedLong(data[index++]) << 40 | 200 | toUnsignedLong(data[index++]) << 48 | 201 | toUnsignedLong(data[index++]) << 56; 202 | } 203 | 204 | @Override 205 | public double readDouble() { 206 | return longBitsToDouble(readLong()); 207 | } 208 | 209 | @Override 210 | public void readBytes(byte[] dst) { 211 | readBytes(dst, 0, dst.length); 212 | } 213 | 214 | @Override 215 | public void readBytes(byte[] dst, int offset, int length) { 216 | System.arraycopy(data, index, dst, offset, length); 217 | index += length; 218 | } 219 | 220 | @Override 221 | public int readInt(int index) { 222 | return readUnsigned(index++) | readUnsigned(index++) << 8 | readUnsigned(index++) << 16 | readUnsigned(index) << 24; 223 | } 224 | 225 | @Override 226 | public int limit() { 227 | return limit; 228 | } 229 | 230 | @Override 231 | public void limit(int newLimit) { 232 | ensureSize(newLimit); 233 | limit = newLimit; 234 | } 235 | 236 | public void mark() { 237 | limit = index; 238 | } 239 | 240 | @Override 241 | public ByteBuffer[] toByteBuffers() { 242 | return new ByteBuffer[] { toByteBuffer() }; 243 | } 244 | 245 | ByteBuffer toByteBuffer() { 246 | ByteBuffer buffer = resourcePool.getBuffer(limit); 247 | buffer.put(data, 0, limit); 248 | return buffer.flip(); 249 | } 250 | 251 | @Override 252 | public void releaseResources() { 253 | index = 0; 254 | limit = data.length; 255 | } 256 | 257 | @Override 258 | public int remaining() { 259 | return limit - index; 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/internal/BufferPool.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore.internal; 20 | 21 | import java.nio.ByteBuffer; 22 | import java.nio.ByteOrder; 23 | import java.util.Queue; 24 | import java.util.concurrent.ConcurrentLinkedQueue; 25 | 26 | /** 27 | * @author JoeAlisson 28 | */ 29 | public class BufferPool { 30 | 31 | private final Queue buffers = new ConcurrentLinkedQueue<>(); 32 | private final int maxSize; 33 | private final int bufferSize; 34 | private int estimateSize; 35 | 36 | /** 37 | * Create a Buffer Pool 38 | * 39 | * @param maxSize the pool max size 40 | * @param bufferSize the size of the buffers kept in Buffer Pool 41 | */ 42 | public BufferPool(int maxSize, int bufferSize) { 43 | this.maxSize = maxSize; 44 | this.bufferSize = bufferSize; 45 | } 46 | 47 | /** 48 | * Initialize the buffer pool 49 | * 50 | * @param factor The factor used to pre allocate ByteBuffers 51 | */ 52 | public void initialize(float factor) { 53 | final int amount = (int) Math.min(maxSize, maxSize * factor); 54 | for (int i = 0; i < amount; i++) { 55 | buffers.offer(ByteBuffer.allocateDirect(bufferSize).order(ByteOrder.LITTLE_ENDIAN)); 56 | } 57 | estimateSize = amount; 58 | } 59 | 60 | /** 61 | * Recycle a ByteBuffer 62 | * 63 | * if the pool has less than max buffer amount, the buffer is added in the pool. otherwise, it will be discarded. 64 | * 65 | * @param buffer the ByteBuffer to be recycled 66 | * @return true if the buffer was recycled, false otherwise 67 | */ 68 | public boolean recycle(ByteBuffer buffer) { 69 | var recycle = estimateSize < maxSize; 70 | if(recycle) { 71 | buffers.offer(buffer.clear()); 72 | estimateSize++; 73 | } 74 | return recycle; 75 | } 76 | 77 | /** 78 | * get a ByteBuffer from the pool 79 | * @return a ByteBuffer or null if the pool is empty 80 | */ 81 | public ByteBuffer get() { 82 | estimateSize--; 83 | return buffers.poll(); 84 | } 85 | 86 | @Override 87 | public String toString() { 88 | return "Pool {maxSize=" + maxSize + ", bufferSize=" + bufferSize + ", estimateUse=" +estimateSize + '}'; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/internal/InternalWritableBuffer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore.internal; 20 | 21 | import io.github.joealisson.mmocore.ResourcePool; 22 | import io.github.joealisson.mmocore.WritableBuffer; 23 | 24 | import java.nio.ByteBuffer; 25 | 26 | /** 27 | * @author JoeAlisson 28 | */ 29 | public abstract class InternalWritableBuffer extends WritableBuffer { 30 | 31 | /** 32 | * @return the current buffer position 33 | */ 34 | public abstract int position(); 35 | 36 | /** 37 | * set the buffer position 38 | * 39 | * @param pos the new buffer position 40 | */ 41 | public abstract void position(int pos); 42 | 43 | /** 44 | * mark the end of buffer's content 45 | */ 46 | public abstract void mark(); 47 | 48 | /** 49 | * transform the Writable Buffer into a array of ByteBuffers 50 | * 51 | * @return an array of ByteBuffers with WritableBuffers' content 52 | */ 53 | public abstract ByteBuffer[] toByteBuffers(); 54 | 55 | /** 56 | * release the resources used 57 | */ 58 | public abstract void releaseResources(); 59 | 60 | /** 61 | * Create a new Dynamic Buffer that increases as needed 62 | * 63 | * @param buffer the initial under layer buffer 64 | * @param resourcePool the resource pool used to get new buffers when needed 65 | * @return a new Dynamic Buffer 66 | */ 67 | public static InternalWritableBuffer dynamicOf(ByteBuffer buffer, ResourcePool resourcePool) { 68 | return new DynamicPacketBuffer(buffer, resourcePool); 69 | } 70 | 71 | /** 72 | * Create a new Dynamic Buffer that increases as needed based on ArrayPacketBuffer 73 | * 74 | * @param buffer the base buffer 75 | * @param resourcePool the resource pool used to get new buffers when needed 76 | * @return a new Dynamic buffer 77 | */ 78 | public static InternalWritableBuffer dynamicOf(ArrayPacketBuffer buffer, ResourcePool resourcePool) { 79 | var copy = new DynamicPacketBuffer(buffer.toByteBuffer(), resourcePool); 80 | copy.limit(buffer.limit()); 81 | return copy; 82 | } 83 | 84 | /** 85 | * Create a new buffer backed by array 86 | * @param resourcePool the resource pool used to get new buffers 87 | * @return a Buffer backed by array 88 | */ 89 | public static InternalWritableBuffer arrayBacked(ResourcePool resourcePool) { 90 | return new ArrayPacketBuffer(resourcePool.getSegmentSize(), resourcePool); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/internal/MMOThreadFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore.internal; 20 | 21 | import java.util.concurrent.ThreadFactory; 22 | import java.util.concurrent.atomic.AtomicInteger; 23 | 24 | /** 25 | * @author JoeAlisson 26 | */ 27 | public class MMOThreadFactory implements ThreadFactory { 28 | 29 | private static final AtomicInteger poolNumber = new AtomicInteger(1); 30 | private final AtomicInteger threadNumber = new AtomicInteger(1); 31 | private final String namePrefix; 32 | private final int priority; 33 | 34 | public MMOThreadFactory(String name, int priority){ 35 | namePrefix = name + "-MMO-pool-" +poolNumber.getAndIncrement() + "-thread-"; 36 | this.priority = priority; 37 | } 38 | 39 | @Override 40 | public Thread newThread(Runnable r) { 41 | Thread thread = new Thread(null, r,namePrefix +threadNumber.getAndIncrement(), 0); 42 | thread.setPriority(priority); 43 | thread.setDaemon(false); 44 | return thread; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/internal/NotWrittenBufferException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore.internal; 20 | 21 | /** 22 | * @author JoeAlisson 23 | */ 24 | public class NotWrittenBufferException extends Exception { 25 | } 26 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/internal/SinglePacketBuffer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore.internal; 20 | 21 | import io.github.joealisson.mmocore.ReadableBuffer; 22 | 23 | import java.nio.ByteBuffer; 24 | 25 | /** 26 | * @author JoeAlisson 27 | */ 28 | public class SinglePacketBuffer implements ReadableBuffer { 29 | 30 | private final ByteBuffer buffer; 31 | 32 | public SinglePacketBuffer(ByteBuffer buffer) { 33 | this.buffer = buffer; 34 | } 35 | 36 | @Override 37 | public byte readByte() { 38 | return buffer.get(); 39 | } 40 | 41 | @Override 42 | public short readShort() { 43 | return buffer.getShort(); 44 | } 45 | 46 | @Override 47 | public char readChar() { 48 | return buffer.getChar(); 49 | } 50 | 51 | @Override 52 | public int readInt() { 53 | return buffer.getInt(); 54 | } 55 | 56 | @Override 57 | public float readFloat() { 58 | return buffer.getFloat(); 59 | } 60 | 61 | @Override 62 | public long readLong() { 63 | return buffer.getLong(); 64 | } 65 | 66 | @Override 67 | public double readDouble() { 68 | return buffer.getDouble(); 69 | } 70 | 71 | @Override 72 | public void readBytes(byte[] dst) { 73 | buffer.get(dst); 74 | } 75 | 76 | @Override 77 | public void readBytes(byte[] dst, int offset, int length) { 78 | buffer.get(dst, offset, length); 79 | } 80 | 81 | @Override 82 | public int remaining() { 83 | return buffer.remaining(); 84 | } 85 | 86 | @Override 87 | public byte readByte(int index) { 88 | return buffer.get(index); 89 | } 90 | 91 | @Override 92 | public void writeByte(int index, byte value) { 93 | buffer.put(index, value); 94 | } 95 | 96 | @Override 97 | public short readShort(int index) { 98 | return buffer.getShort(index); 99 | } 100 | 101 | @Override 102 | public void writeShort(int index, short value) { 103 | buffer.putShort(index, value); 104 | } 105 | 106 | @Override 107 | public int limit() { 108 | return buffer.limit(); 109 | } 110 | 111 | @Override 112 | public void limit(int newLimit) { 113 | buffer.limit(newLimit); 114 | } 115 | 116 | @Override 117 | public int readInt(int index) { 118 | return buffer.getInt(index); 119 | } 120 | 121 | @Override 122 | public void writeInt(int index, int value) { 123 | buffer.putInt(index, value); 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/internal/fairness/FairnessController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore.internal.fairness; 20 | 21 | import io.github.joealisson.mmocore.Client; 22 | 23 | import java.util.function.Consumer; 24 | 25 | /** 26 | * @author JoeAlisson 27 | */ 28 | public class FairnessController { 29 | 30 | private FairnessStrategy strategy; 31 | 32 | private FairnessController() { 33 | // only construct by init 34 | } 35 | 36 | /** 37 | * Create a Fairness Controller using the fairnessBuckets 38 | * 39 | * @param fairnessBuckets the amount of buckets used in FairnessController 40 | * @return the fairness Controller 41 | */ 42 | public static FairnessController init(int fairnessBuckets) { 43 | FairnessController controller = new FairnessController(); 44 | if(fairnessBuckets <= 1) { 45 | controller.strategy = new SingleBucketStrategy(); 46 | } else { 47 | controller.strategy = new MultiBucketStrategy(fairnessBuckets); 48 | } 49 | return controller; 50 | } 51 | 52 | /** 53 | * Add the client to the fairnessController and execute the action to the next client 54 | * 55 | * @param client the client to be added to Fairness Controller 56 | * @param action the action to execute to the next fair client 57 | */ 58 | public void nextFairAction(Client client, Consumer> action) { 59 | strategy.doNextAction(client, action); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/internal/fairness/FairnessStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore.internal.fairness; 20 | 21 | import io.github.joealisson.mmocore.Client; 22 | 23 | import java.util.function.Consumer; 24 | 25 | /** 26 | * @author JoeAlisson 27 | */ 28 | interface FairnessStrategy { 29 | 30 | void doNextAction(Client client, Consumer> action); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/internal/fairness/MultiBucketStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore.internal.fairness; 20 | 21 | import io.github.joealisson.mmocore.Client; 22 | 23 | import java.util.Queue; 24 | import java.util.concurrent.ConcurrentLinkedQueue; 25 | import java.util.function.Consumer; 26 | 27 | /** 28 | * @author JoeAlisson 29 | */ 30 | class MultiBucketStrategy implements FairnessStrategy { 31 | 32 | private final Queue>[] readyBuckets; 33 | private final int fairnessBuckets; 34 | private int nextOffer; 35 | private int nextPoll; 36 | 37 | @SuppressWarnings("unchecked") 38 | MultiBucketStrategy(int fairnessBuckets) { 39 | readyBuckets = new ConcurrentLinkedQueue[fairnessBuckets]; 40 | this.fairnessBuckets = fairnessBuckets; 41 | for (int i = 0; i < fairnessBuckets; i++) { 42 | readyBuckets[i] = new ConcurrentLinkedQueue<>(); 43 | } 44 | } 45 | 46 | @Override 47 | public void doNextAction(Client client, Consumer> action) { 48 | int offer = nextOffer++ % fairnessBuckets; 49 | Queue> nextBucket = readyBuckets[offer]; 50 | nextBucket.offer(client); 51 | 52 | int poll = nextPoll++ % fairnessBuckets; 53 | 54 | nextBucket = readyBuckets[poll]; 55 | Client next = nextBucket.poll(); 56 | if(next != null) { 57 | action.accept(next); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/io/github/joealisson/mmocore/internal/fairness/SingleBucketStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore.internal.fairness; 20 | 21 | import io.github.joealisson.mmocore.Client; 22 | 23 | import java.util.concurrent.ConcurrentLinkedQueue; 24 | import java.util.function.Consumer; 25 | 26 | /** 27 | * @author JoeAlisson 28 | */ 29 | public class SingleBucketStrategy implements FairnessStrategy { 30 | 31 | private final ConcurrentLinkedQueue> readyClients = new ConcurrentLinkedQueue<>(); 32 | 33 | @Override 34 | public void doNextAction(Client client, Consumer> action) { 35 | readyClients.offer(client); 36 | Client next = readyClients.poll(); 37 | if(next != null) { 38 | action.accept(next); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/io.github.joealisson.mmocore/module-info.java: -------------------------------------------------------------------------------- 1 | module io.github.joealisson.mmocore { 2 | requires org.slf4j; 3 | 4 | exports io.github.joealisson.mmocore; 5 | } -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/ArrayPacketBufferTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import io.github.joealisson.mmocore.internal.ArrayPacketBuffer; 22 | import io.github.joealisson.mmocore.internal.InternalWritableBuffer; 23 | import org.junit.Assert; 24 | import org.junit.Test; 25 | 26 | import java.nio.ByteBuffer; 27 | 28 | /** 29 | * @author JoeAlisson 30 | */ 31 | public class ArrayPacketBufferTest { 32 | 33 | @Test 34 | public void testIntegrity() { 35 | ConnectionConfig config = new ConnectionConfig(null); 36 | config.complete(); 37 | ArrayPacketBuffer buffer = new ArrayPacketBuffer(10, config.resourcePool); 38 | 39 | buffer.writeByte((byte) 1); 40 | buffer.writeBytes(new byte[] {2, 3, 4, 5}); 41 | buffer.writeShort((short) 6); 42 | buffer.writeChar('A'); 43 | buffer.writeInt(7); 44 | buffer.writeFloat(8.5f); 45 | buffer.writeLong(9); 46 | buffer.writeDouble(10.5); 47 | buffer.writeBytes(null); 48 | 49 | buffer.mark(); 50 | int position = buffer.position(); 51 | Assert.assertEquals(33, position); 52 | Assert.assertEquals(position, buffer.limit()); 53 | 54 | buffer.position(0); 55 | Assert.assertEquals(position, buffer.remaining()); 56 | 57 | Assert.assertEquals(1, buffer.readByte()); 58 | byte[] data = new byte[4]; 59 | buffer.readBytes(data); 60 | Assert.assertEquals(2, data[0]); 61 | Assert.assertEquals(3, data[1]); 62 | Assert.assertEquals(4, data[2]); 63 | Assert.assertEquals(5, data[3]); 64 | Assert.assertEquals(6, buffer.readShort()); 65 | Assert.assertEquals('A', buffer.readChar()); 66 | Assert.assertEquals(7, buffer.readInt()); 67 | Assert.assertEquals(8.5f, buffer.readFloat(), 0); 68 | Assert.assertEquals(9, buffer.readLong()); 69 | Assert.assertEquals(10.5, buffer.readDouble(), 0); 70 | 71 | Assert.assertEquals(1, buffer.readByte(0)); 72 | Assert.assertEquals(6, buffer.readShort(5)); 73 | Assert.assertEquals(7, buffer.readInt(9)); 74 | 75 | int limit = buffer.limit(); 76 | buffer.limit(limit * 2); 77 | 78 | ByteBuffer[] byteBuffers = buffer.toByteBuffers(); 79 | 80 | Assert.assertEquals(1,byteBuffers.length); 81 | ByteBuffer byteBuffer = byteBuffers[0]; 82 | 83 | Assert.assertEquals(limit*2, byteBuffer.remaining()); 84 | 85 | } 86 | 87 | @Test 88 | public void testReleaseResources() { 89 | ConnectionConfig config = new ConnectionConfig(null); 90 | InternalWritableBuffer buffer = InternalWritableBuffer.arrayBacked(config.resourcePool); 91 | 92 | buffer.writeShort((short) 10); 93 | buffer.mark(); 94 | Assert.assertEquals(buffer.position(), buffer.limit()); 95 | 96 | buffer.releaseResources(); 97 | Assert.assertEquals(0, buffer.position()); 98 | Assert.assertEquals(config.resourcePool.getSegmentSize(), buffer.limit()); 99 | } 100 | 101 | } -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/AsyncClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import java.util.List; 22 | 23 | import static java.util.Objects.nonNull; 24 | 25 | /** 26 | * @author JoeAlisson 27 | */ 28 | public class AsyncClient extends Client> { 29 | 30 | public ReadableBuffer receivedPacket; 31 | 32 | public AsyncClient(Connection connection) { 33 | super(connection); 34 | } 35 | 36 | @Override 37 | public boolean decrypt(Buffer data, int offset, int size) { 38 | return true; 39 | } 40 | 41 | @Override 42 | public boolean encrypt(Buffer data, int offset, int size) { 43 | return true; 44 | } 45 | 46 | @Override 47 | protected void onDisconnection() { 48 | 49 | } 50 | 51 | @Override 52 | public void onConnected() { 53 | 54 | } 55 | 56 | public void sendPacket(WritablePacket packet) { 57 | if(nonNull(packet)) { 58 | System.out.println("Sending " + packet.toString()); 59 | } 60 | writePacket(packet); 61 | } 62 | 63 | public void sendPackets(WritablePacket... packets) { 64 | writePackets(List.of(packets)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/AsyncClientBroadcastPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | /** 22 | * @author JoeAlisson 23 | */ 24 | public class AsyncClientBroadcastPacket extends WritablePacket { 25 | 26 | @Override 27 | protected boolean write(AsyncClient client, WritableBuffer buffer) { 28 | buffer.writeByte(0x10); 29 | buffer.writeLong(Long.MAX_VALUE); 30 | buffer.writeDouble(Double.MAX_VALUE); 31 | buffer.writeInt(Integer.MAX_VALUE); 32 | buffer.writeFloat(Float.MAX_VALUE); 33 | buffer.writeShort(Short.MAX_VALUE); 34 | buffer.writeByte(Byte.MAX_VALUE); 35 | buffer.writeString("Ping"); 36 | buffer.writeString(null); 37 | buffer.writeSizedString("Packet"); 38 | buffer.writeSizedString(null); 39 | buffer.writeByte(true); 40 | buffer.writeByte(false); 41 | buffer.writeShort(true); 42 | buffer.writeShort(false); 43 | buffer.writeInt(true); 44 | buffer.writeInt(false); 45 | buffer.writeBytes((byte[]) null); 46 | buffer.writeBytes(new byte[4]); 47 | return true; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/AsyncClientBroadcastReceiverPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import io.github.joealisson.mmocore.util.Comparator; 22 | 23 | /** 24 | * @author JoeAlisson 25 | */ 26 | public class AsyncClientBroadcastReceiverPacket extends ReadablePacket { 27 | 28 | private long varLong; 29 | private double varDouble; 30 | private float varFloat; 31 | private int varInt; 32 | private short varShort; 33 | private byte varByte; 34 | private String varString; 35 | private String varSizedString; 36 | 37 | @Override 38 | protected boolean read() { 39 | varLong = readLong(); 40 | varDouble = readDouble(); 41 | varInt = readInt(); 42 | varFloat = readFloat(); 43 | varShort = readShort(); 44 | varByte = readByte(); 45 | varString = readString(); 46 | String empty = readString(); 47 | varSizedString = readSizedString(); 48 | empty = readSizedString(); 49 | return true; 50 | } 51 | 52 | @Override 53 | public void run() { 54 | try { 55 | Comparator.assertEquals(Long.MAX_VALUE, varLong); 56 | Comparator.assertEquals(Double.MAX_VALUE, varDouble); 57 | Comparator.assertEquals(Integer.MAX_VALUE, varInt); 58 | Comparator.assertEquals(Float.MAX_VALUE, varFloat); 59 | Comparator.assertEquals(Short.MAX_VALUE, varShort); 60 | Comparator.assertEquals(Byte.MAX_VALUE, varByte); 61 | Comparator.assertEquals("Ping", varString); 62 | Comparator.assertEquals("Packet", varSizedString); 63 | CommunicationTest.incrementPacketSent(); 64 | } catch (Exception e) { 65 | CommunicationTest.shutdown(false); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/AsyncClientClosePacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | /** 22 | * @author JoeAlisson 23 | */ 24 | public class AsyncClientClosePacket extends WritablePacket { 25 | @Override 26 | protected boolean write(AsyncClient client, WritableBuffer buffer) { 27 | buffer.writeByte(0x03); 28 | return true; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/AsyncClientClosedConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | /** 22 | * @author JoeAlisson 23 | */ 24 | public class AsyncClientClosedConnection extends ReadablePacket { 25 | private final byte[] bytes; 26 | 27 | public AsyncClientClosedConnection(byte[] bytes) { 28 | this.bytes = bytes; 29 | } 30 | 31 | @Override 32 | protected boolean read() { 33 | return true; 34 | } 35 | 36 | @Override 37 | public void run() { 38 | client.close(new WritablePacket<>() { 39 | @Override 40 | protected boolean write(AsyncClient client, WritableBuffer buffer) { 41 | buffer.writeBytes(bytes); 42 | return false; 43 | } 44 | }); 45 | client.close(); 46 | CommunicationTest.shutdown(true); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/AsyncClientFairnessPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | /** 22 | * @author JoeAlisson 23 | */ 24 | public class AsyncClientFairnessPacket extends WritablePacket { 25 | 26 | @Override 27 | protected boolean write(AsyncClient client, WritableBuffer buffer) { 28 | buffer.writeByte(0x20); 29 | buffer.writeLong(Long.MAX_VALUE); 30 | buffer.writeDouble(Double.MAX_VALUE); 31 | buffer.writeInt(Integer.MAX_VALUE); 32 | buffer.writeFloat(Float.MAX_VALUE); 33 | buffer.writeShort(Short.MAX_VALUE); 34 | buffer.writeByte(Byte.MAX_VALUE); 35 | buffer.writeString("Ping"); 36 | buffer.writeString(null); 37 | buffer.writeSizedString("Packet"); 38 | buffer.writeSizedString(null); 39 | buffer.writeByte(true); 40 | buffer.writeByte(false); 41 | buffer.writeShort(true); 42 | buffer.writeShort(false); 43 | buffer.writeInt(true); 44 | buffer.writeInt(false); 45 | buffer.writeBytes((byte[]) null); 46 | buffer.writeBytes(new byte[4]); 47 | return true; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/AsyncClientFairnessReceivePacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import io.github.joealisson.mmocore.util.Comparator; 22 | 23 | /** 24 | * @author JoeAlisson 25 | */ 26 | public class AsyncClientFairnessReceivePacket extends ReadablePacket { 27 | 28 | private long varLong; 29 | private double varDouble; 30 | private float varFloat; 31 | private int varInt; 32 | private short varShort; 33 | private byte varByte; 34 | private String varString; 35 | private String varSizedString; 36 | 37 | @Override 38 | protected boolean read() { 39 | varLong = readLong(); 40 | varDouble = readDouble(); 41 | varInt = readInt(); 42 | varFloat = readFloat(); 43 | varShort = readShort(); 44 | varByte = readByte(); 45 | varString = readString(); 46 | String empty = readString(); 47 | varSizedString = readSizedString(); 48 | return true; 49 | } 50 | 51 | @Override 52 | public void run() { 53 | try { 54 | Comparator.assertEquals(Long.MAX_VALUE, varLong); 55 | Comparator.assertEquals(Double.MAX_VALUE, varDouble); 56 | Comparator.assertEquals(Integer.MAX_VALUE, varInt); 57 | Comparator.assertEquals(Float.MAX_VALUE, varFloat); 58 | Comparator.assertEquals(Short.MAX_VALUE, varShort); 59 | Comparator.assertEquals(Byte.MAX_VALUE, varByte); 60 | Comparator.assertEquals("Ping", varString); 61 | Comparator.assertEquals("Packet", varSizedString); 62 | client.sendPacket(new AsyncClientFairnessRespondPacket()); 63 | 64 | } catch (Exception e) { 65 | CommunicationTest.shutdown(false); 66 | } 67 | 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/AsyncClientFairnessRepliedPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | /** 22 | * @author JoeAlisson 23 | */ 24 | public class AsyncClientFairnessRepliedPacket extends ReadablePacket { 25 | 26 | @Override 27 | protected boolean read() { 28 | return true; 29 | } 30 | 31 | @Override 32 | public void run() { 33 | CommunicationTest.incrementPacketSent(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/AsyncClientFairnessRespondPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | /** 22 | * @author JoeAlisson 23 | */ 24 | public class AsyncClientFairnessRespondPacket extends WritablePacket { 25 | 26 | @Override 27 | protected boolean write(AsyncClient client, WritableBuffer buffer) { 28 | buffer.writeByte(0x21); 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/AsyncClientPingPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | /** 22 | * @author JoeAlisson 23 | */ 24 | public class AsyncClientPingPacket extends WritablePacket { 25 | 26 | @Override 27 | protected boolean write(AsyncClient client, WritableBuffer buffer) { 28 | buffer.writeByte(0x01); 29 | buffer.writeLong(Long.MAX_VALUE); 30 | buffer.writeDouble(Double.MAX_VALUE); 31 | buffer.writeInt(Integer.MAX_VALUE); 32 | buffer.writeFloat(Float.MAX_VALUE); 33 | buffer.writeShort(Short.MAX_VALUE); 34 | buffer.writeByte(Byte.MAX_VALUE); 35 | buffer.writeString("Ping"); 36 | buffer.writeString(null); 37 | buffer.writeSizedString("Packet"); 38 | buffer.writeSizedString(null); 39 | buffer.writeByte(true); 40 | buffer.writeByte(false); 41 | buffer.writeShort(true); 42 | buffer.writeShort(false); 43 | buffer.writeInt(true); 44 | buffer.writeInt(false); 45 | buffer.writeBytes(null); 46 | buffer.writeBytes(new byte[4]); 47 | return true; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/AsyncClientPongPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import io.github.joealisson.mmocore.util.Comparator; 22 | 23 | /** 24 | * @author JoeAlisson 25 | */ 26 | public class AsyncClientPongPacket extends ReadablePacket { 27 | 28 | private long varLong; 29 | private double varDouble; 30 | private float varFloat; 31 | private int varInt; 32 | private short varShort; 33 | private byte varByte; 34 | private String varString; 35 | private String varSizedString; 36 | 37 | @Override 38 | protected boolean read() { 39 | varLong = readLong(); 40 | varDouble = readDouble(); 41 | varInt = readInt(); 42 | varFloat = readFloat(); 43 | varShort = readShort(); 44 | varByte = readByte(); 45 | varString = readString(); 46 | varSizedString = readSizedString(); 47 | return true; 48 | } 49 | 50 | @Override 51 | public void run() { 52 | try { 53 | Comparator.assertEquals(Long.MAX_VALUE, varLong); 54 | Comparator.assertEquals(Double.MAX_VALUE, varDouble); 55 | Comparator.assertEquals(Integer.MAX_VALUE, varInt); 56 | Comparator.assertEquals(Float.MAX_VALUE, varFloat); 57 | Comparator.assertEquals(Short.MAX_VALUE, varShort); 58 | Comparator.assertEquals(Byte.MAX_VALUE, varByte); 59 | Comparator.assertEquals("Pong", varString); 60 | Comparator.assertEquals("Packet", varSizedString); 61 | } catch (Exception e) { 62 | CommunicationTest.shutdown(false); 63 | } 64 | 65 | getClient().sendPacket(new AsyncClientClosePacket()); 66 | } 67 | } -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/AsyncServerClosePacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | /** 22 | * @author JoeAlisson 23 | */ 24 | public class AsyncServerClosePacket extends ReadablePacket { 25 | 26 | @Override 27 | protected boolean read() { 28 | return true; 29 | } 30 | 31 | @Override 32 | public void run() { 33 | client.sendPacket(new AsyncServerClosedConnection()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/AsyncServerClosedConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | /** 22 | * @author JoeAlisson 23 | */ 24 | public class AsyncServerClosedConnection extends WritablePacket { 25 | 26 | @Override 27 | protected boolean write(AsyncClient client, WritableBuffer buffer) { 28 | buffer.writeByte(0x04); 29 | buffer.writeShort(0x01); 30 | buffer.writeInt(0x02); 31 | buffer.writeString(client.getHostAddress()); 32 | buffer.writeBytes(new byte[20]); 33 | return true; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/AsyncServerPingPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import io.github.joealisson.mmocore.util.Comparator; 22 | 23 | /** 24 | * @author JoeAlisson 25 | */ 26 | public class AsyncServerPingPacket extends ReadablePacket { 27 | 28 | private long varLong; 29 | private double varDouble; 30 | private float varFloat; 31 | private int varInt; 32 | private short varShort; 33 | private byte varByte; 34 | private String varString; 35 | private String varSizedString; 36 | private String emptyString; 37 | private String emptySizedString; 38 | private boolean trueByteBoolean; 39 | private boolean falseByteBoolean; 40 | private boolean trueShortBoolean; 41 | private boolean falseShortBoolean; 42 | private boolean trueIntBoolean; 43 | private boolean falseIntBoolean; 44 | 45 | @Override 46 | protected boolean read() { 47 | if(available() > 0) { 48 | varLong = readLong(); 49 | varDouble = readDouble(); 50 | varInt = readInt(); 51 | varFloat = readFloat(); 52 | varShort = readShort(); 53 | varByte = (byte) readUnsignedByte(); 54 | varString = readString(); 55 | emptyString = readString(); 56 | varSizedString = readSizedString(); 57 | emptySizedString = readSizedString(); 58 | trueByteBoolean = readBoolean(); 59 | falseByteBoolean = readBoolean(); 60 | trueShortBoolean = readShortAsBoolean(); 61 | falseShortBoolean = readShortAsBoolean(); 62 | trueIntBoolean = readIntAsBoolean(); 63 | falseIntBoolean = readIntAsBoolean(); 64 | readBytes(new byte[4], 0, 4); 65 | } 66 | return true; 67 | } 68 | 69 | @Override 70 | public void run() { 71 | try { 72 | Comparator.assertEquals(Long.MAX_VALUE, varLong); 73 | Comparator.assertEquals(Double.MAX_VALUE, varDouble); 74 | Comparator.assertEquals(Integer.MAX_VALUE, varInt); 75 | Comparator.assertEquals(Float.MAX_VALUE, varFloat); 76 | Comparator.assertEquals(Short.MAX_VALUE, varShort); 77 | Comparator.assertEquals(Byte.MAX_VALUE, varByte); 78 | Comparator.assertEquals("Ping", varString); 79 | Comparator.assertEquals("", emptyString); 80 | Comparator.assertEquals("Packet", varSizedString); 81 | Comparator.assertEquals("", emptySizedString); 82 | Comparator.assertTrue(trueByteBoolean); 83 | Comparator.assertFalse(falseByteBoolean); 84 | Comparator.assertTrue(trueShortBoolean); 85 | Comparator.assertFalse(falseShortBoolean); 86 | Comparator.assertTrue(trueIntBoolean); 87 | Comparator.assertFalse(falseIntBoolean); 88 | } catch (Exception e) { 89 | CommunicationTest.shutdown(false); 90 | } 91 | 92 | client.sendPacket(new AsyncServerPongPacket()); 93 | } 94 | } -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/AsyncServerPongPacket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | /** 22 | * @author JoeAlisson 23 | */ 24 | public class AsyncServerPongPacket extends WritablePacket { 25 | 26 | @Override 27 | protected boolean write(AsyncClient client, WritableBuffer buffer) { 28 | buffer.writeByte(0x02); 29 | buffer.writeLong(Long.MAX_VALUE); 30 | buffer.writeDouble(Double.MAX_VALUE); 31 | buffer.writeInt(Integer.MAX_VALUE); 32 | buffer.writeFloat(Float.MAX_VALUE); 33 | buffer.writeShort(Short.MAX_VALUE); 34 | buffer.writeByte(Byte.MAX_VALUE); 35 | buffer.writeString("Pong"); 36 | buffer.writeSizedString("Packet"); 37 | return true; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/CommunicationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import org.awaitility.Awaitility; 22 | import org.junit.After; 23 | import org.junit.Assert; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | 27 | import java.io.IOException; 28 | import java.net.InetSocketAddress; 29 | import java.util.List; 30 | import java.util.concurrent.CompletableFuture; 31 | import java.util.concurrent.ExecutionException; 32 | import java.util.concurrent.TimeUnit; 33 | import java.util.concurrent.atomic.AtomicBoolean; 34 | import java.util.concurrent.atomic.AtomicInteger; 35 | 36 | import static org.junit.Assert.fail; 37 | 38 | /** 39 | * @author JoeAlisson 40 | */ 41 | public class CommunicationTest { 42 | 43 | private static final AtomicBoolean shutdown = new AtomicBoolean(false); 44 | private static boolean success = true; 45 | private ConnectionBuilder builder; 46 | private Connector connector; 47 | private final InetSocketAddress listenAddress = new InetSocketAddress(9090); 48 | private ConnectionHandler connectionHandler; 49 | 50 | private static AtomicInteger packetsSent; 51 | private static int PACKET_SENT_TO_SUCCESS = 10; 52 | 53 | static void shutdown(boolean success) { 54 | shutdown.getAndSet(true); 55 | CommunicationTest.success = success; 56 | } 57 | 58 | static void incrementPacketSent() { 59 | if(packetsSent.incrementAndGet() == PACKET_SENT_TO_SUCCESS) { 60 | shutdown(true); 61 | } 62 | } 63 | 64 | @Before 65 | public void SetUp() { 66 | packetsSent = new AtomicInteger(); 67 | GenericClientHandler handler = new GenericClientHandler(); 68 | 69 | builder = ConnectionBuilder.create(listenAddress, AsyncClient::new, handler, handler).filter(channel -> true).threadPoolSize(2).useNagle(false) 70 | .shutdownWaitTime(500).addBufferPool(10,300).initBufferPoolFactor(0.2f).bufferSegmentSize(256).threadPriority(10); 71 | connector = Connector.create(AsyncClient::new, handler, handler).addBufferPool(10, 300).initBufferPoolFactor(0.2f) 72 | .bufferSegmentSize(128).useCachedThreadPool(true).threadPoolSize(2).maxCachedThreads(4).threadPriority(10); 73 | packetsSent.set(0); 74 | shutdown.set(false); 75 | } 76 | 77 | @Test 78 | public void testIntegration() throws IOException, ExecutionException, InterruptedException { 79 | connectionHandler = builder.build(); 80 | connectionHandler.start(); 81 | 82 | AsyncClient client = connector.connect("localhost", 9090); 83 | client.sendPacket(new AsyncClientPingPacket()); 84 | 85 | Awaitility.waitAtMost(10, TimeUnit.SECONDS).untilTrue(shutdown); 86 | 87 | connectionHandler.shutdown(); 88 | if(!success) { 89 | fail(); 90 | } 91 | } 92 | 93 | @Test 94 | public void testIntegrationWithCachedThreadPool() throws IOException, ExecutionException, InterruptedException { 95 | connectionHandler = builder.useCachedThreadPool(true).maxCachedThreads(4).build(); 96 | connectionHandler.start(); 97 | 98 | AsyncClient client = connector.connect("localhost", 9090); 99 | client.sendPacket(new AsyncClientPingPacket()); 100 | 101 | Awaitility.waitAtMost(10, TimeUnit.SECONDS).untilTrue(shutdown); 102 | 103 | connectionHandler.shutdown(); 104 | if(!success) { 105 | fail(); 106 | } 107 | } 108 | 109 | @Test 110 | public void testBroadcast() throws IOException, ExecutionException, InterruptedException { 111 | connectionHandler = builder.build(); 112 | connectionHandler.start(); 113 | packetsSent.set(0); 114 | AsyncClient[] clients = new AsyncClient[PACKET_SENT_TO_SUCCESS]; 115 | for (int i = 0; i < clients.length; i++) { 116 | clients[i] = connector.connect("localhost", 9090); 117 | } 118 | 119 | AsyncClientBroadcastPacket packet = new AsyncClientBroadcastPacket(); 120 | packet.sendInBroadcast(true); 121 | 122 | for (AsyncClient client : clients) { 123 | client.sendPacket(packet); 124 | } 125 | 126 | Awaitility.waitAtMost(10, TimeUnit.SECONDS).untilTrue(shutdown); 127 | 128 | for (AsyncClient client : clients) { 129 | client.close(); 130 | } 131 | 132 | connectionHandler.shutdown(); 133 | if(packetsSent.get() != clients.length) { 134 | fail(); 135 | } 136 | } 137 | 138 | @Test 139 | public void testDisposable() throws IOException, ExecutionException, InterruptedException { 140 | int disposeThreshold = 5; 141 | PACKET_SENT_TO_SUCCESS = 100; 142 | packetsSent.set(0); 143 | connectionHandler = builder.dropPacketThreshold(disposeThreshold).build(); 144 | connectionHandler.start(); 145 | 146 | AsyncClient client = connector.dropPacketThreshold(disposeThreshold).connect("localhost", 9090); 147 | AsyncClientBroadcastPacket[] packets = new AsyncClientBroadcastPacket[100]; 148 | for (int i = 0; i < 100; i++) { 149 | packets[i] = new AsyncClientBroadcastPacket(); 150 | } 151 | AsyncDisposablePacket packet = new AsyncDisposablePacket(); 152 | client.sendPackets(packets); 153 | 154 | Assert.assertTrue(client.getEstimateQueueSize() > disposeThreshold); 155 | client.sendPacket(packet); 156 | client.sendPacket(new AsyncClientBroadcastPacket()); 157 | 158 | Awaitility.waitAtMost(10, TimeUnit.SECONDS).untilTrue(shutdown); 159 | 160 | connectionHandler.shutdown(); 161 | if(packetsSent.get() != packets.length + 1) { 162 | fail(); 163 | } 164 | } 165 | 166 | @Test 167 | public void testReadingThrottling() throws IOException { 168 | InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1",9090); 169 | ConnectionHandler handler = ConnectionBuilder.create(socketAddress, ReadingThrottlingHelper::create, ReadingThrottlingHelper::handlePacket, ReadingThrottlingHelper::execute).disableAutoReading(true).build(); 170 | try { 171 | handler.start(); 172 | ReadingThrottlingHelper.RTClient client = Connector.create(ReadingThrottlingHelper.RTClient::new, ReadingThrottlingHelper::handlePacket, ReadingThrottlingHelper::execute).disableAutoReading(true).connect(socketAddress); 173 | 174 | client.writePacket(ReadingThrottlingHelper.ping()); 175 | client.writePacket(ReadingThrottlingHelper.ping2nd()); 176 | ReadingThrottlingHelper.RTClient receivingClient = ReadingThrottlingHelper.lastClient; 177 | 178 | Awaitility.waitAtMost(3, TimeUnit.SECONDS).untilTrue(receivingClient.readableAgain); 179 | Assert.assertTrue(receivingClient.hasMinimumTimeBetweenPackets()); 180 | 181 | 182 | receivingClient.readableAgain.set(false); 183 | client.writePackets(List.of(ReadingThrottlingHelper.ping(), ReadingThrottlingHelper.ping2nd())); 184 | receivingClient.readNextPacket(); 185 | 186 | Awaitility.waitAtMost(3, TimeUnit.SECONDS).untilTrue(receivingClient.readableAgain); 187 | Assert.assertTrue(receivingClient.hasMinimumTimeBetweenPackets()); 188 | 189 | client.close(); 190 | receivingClient.close(); 191 | 192 | } catch (ExecutionException | InterruptedException | IOException e) { 193 | e.printStackTrace(); 194 | } finally { 195 | handler.shutdown(); 196 | } 197 | } 198 | 199 | @Test 200 | public void testFairnessController() throws IOException, ExecutionException, InterruptedException { 201 | GenericClientHandler handler = new GenericClientHandler(); 202 | connectionHandler = ConnectionBuilder.create(listenAddress, AsyncClient::new, handler, handler).fairnessBuckets(4).build(); 203 | connectionHandler.start(); 204 | 205 | PACKET_SENT_TO_SUCCESS = 10; 206 | packetsSent.set(0); 207 | 208 | AsyncClient[] clients = new AsyncClient[PACKET_SENT_TO_SUCCESS]; 209 | for (int i = 0; i < clients.length; i++) { 210 | clients[i] = connector.connect("localhost", 9090); 211 | } 212 | 213 | AsyncClientFairnessPacket packet = new AsyncClientFairnessPacket(); 214 | 215 | for (AsyncClient client : clients) { 216 | CompletableFuture.runAsync(() -> client.sendPacket(packet)); 217 | } 218 | 219 | Awaitility.waitAtMost(10, TimeUnit.SECONDS).untilTrue(shutdown); 220 | 221 | for (AsyncClient client : clients) { 222 | client.close(); 223 | } 224 | 225 | connectionHandler.shutdown(); 226 | if(packetsSent.get() != clients.length) { 227 | fail(); 228 | } 229 | } 230 | 231 | static class AsyncDisposablePacket extends WritablePacket { 232 | 233 | @Override 234 | protected boolean write(AsyncClient client, WritableBuffer buffer) { 235 | shutdown(false); 236 | throw new IllegalStateException(); 237 | } 238 | 239 | @Override 240 | public boolean canBeDropped(AsyncClient client) { 241 | return true; 242 | } 243 | } 244 | 245 | @After 246 | public void tearDown() { 247 | if(!shutdown.get() && connectionHandler != null) { 248 | connectionHandler.shutdown(); 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/ConfigurationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import org.junit.After; 22 | import org.junit.Test; 23 | 24 | import java.net.InetSocketAddress; 25 | 26 | import static java.lang.Math.max; 27 | import static java.lang.Runtime.getRuntime; 28 | import static org.junit.Assert.assertEquals; 29 | import static org.junit.Assert.assertTrue; 30 | 31 | /** 32 | * @author JoeAlisson 33 | */ 34 | public class ConfigurationTest { 35 | 36 | @Test 37 | public void testCorrectConfigurations() { 38 | System.setProperty("async-mmocore.configurationFile", "/async-mmocore.properties"); 39 | ConnectionConfig config = new ConnectionConfig(null); 40 | config.complete(); 41 | assertTrue(config.resourcePool.bufferPoolSize() >= 2); 42 | assertEquals(0.2f, config.initBufferPoolFactor, 0); 43 | assertEquals(50 * 1000L, config.shutdownWaitTime); 44 | assertEquals(6, config.threadPoolSize); 45 | 46 | } 47 | 48 | @Test(expected = IllegalArgumentException.class) 49 | public void testNonExistentConfigurationFile() { 50 | System.setProperty("async-mmocore.configurationFile", "/async-mmocore-nonexistent.properties"); 51 | new ConnectionConfig(null); 52 | } 53 | 54 | @Test 55 | public void testWrongValuesConfigurations() { 56 | System.setProperty("async-mmocore.configurationFile", "/async-mmocore-wrong.properties"); 57 | ConnectionConfig config = new ConnectionConfig(null); 58 | config.complete(); 59 | assertEquals(5 * 1000L, config.shutdownWaitTime); 60 | assertEquals(max(2, getRuntime().availableProcessors() - 2), config.threadPoolSize); 61 | } 62 | 63 | @Test(expected = IllegalArgumentException.class) 64 | public void testBuilderThreadPriorityLower() { 65 | var socketAddress = new InetSocketAddress("127.0.0.1", 9090); 66 | ConnectionBuilder.create(socketAddress, AsyncClient::new, null, null).threadPriority(-1); 67 | } 68 | 69 | @Test(expected = IllegalArgumentException.class) 70 | public void testBuilderThreadPriorityHigher() { 71 | var socketAddress = new InetSocketAddress("127.0.0.1", 9090); 72 | ConnectionBuilder.create(socketAddress, AsyncClient::new, null, null).threadPriority(100); 73 | } 74 | 75 | @Test(expected = IllegalArgumentException.class) 76 | public void testConnectorThreadPriorityLower() { 77 | Connector.create(AsyncClient::new, null, null).threadPriority(-1); 78 | } 79 | 80 | @Test(expected = IllegalArgumentException.class) 81 | public void testConnectorThreadPriorityHigher() { 82 | Connector.create(AsyncClient::new, null, null).threadPriority(100); 83 | } 84 | 85 | @After 86 | public void tearDown() { 87 | System.setProperty("async-mmocore.configurationFile", ""); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/ConnectionHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import org.awaitility.Awaitility; 22 | import org.junit.Assert; 23 | import org.junit.Test; 24 | 25 | import java.io.IOException; 26 | import java.net.InetSocketAddress; 27 | import java.util.concurrent.ExecutionException; 28 | import java.util.concurrent.TimeUnit; 29 | 30 | import static java.util.Objects.nonNull; 31 | 32 | /** 33 | * @author JoeAlisson 34 | */ 35 | public class ConnectionHandlerTest { 36 | 37 | @Test 38 | public void testCachedThreadPoolHandler() throws IOException, ExecutionException, InterruptedException { 39 | InetSocketAddress listenAddress = new InetSocketAddress(9090); 40 | GenericClientHandler handler = new GenericClientHandler(); 41 | ConnectionBuilder builder = ConnectionBuilder.create(listenAddress, AsyncClient::new, handler, handler).useCachedThreadPool(true).shutdownWaitTime(100); 42 | ConnectionHandler connectionHandler = builder.build(); 43 | Connector connector = Connector.create(AsyncClient::new, handler, handler); 44 | try { 45 | connectionHandler.start(); 46 | AsyncClient client = connector.connect(null, 9090); 47 | 48 | client.sendPacket(new AsyncClientClosePacket()); 49 | Awaitility.waitAtMost(30, TimeUnit.SECONDS).until(() -> !client.isConnected()); 50 | Assert.assertFalse(client.isConnected()); 51 | }finally { 52 | connectionHandler.shutdown(); 53 | } 54 | } 55 | 56 | @Test 57 | public void testRefuseConnection() throws IOException, ExecutionException, InterruptedException { 58 | InetSocketAddress listenAddress = new InetSocketAddress(9090); 59 | GenericClientHandler handler = new GenericClientHandler(); 60 | ConnectionBuilder builder = ConnectionBuilder.create(listenAddress, AsyncClient::new, handler, handler).filter(channel -> false).shutdownWaitTime(100); 61 | ConnectionHandler connectionHandler = builder.build(); 62 | Connector connector = Connector.create(AsyncClient::new, handler, handler); 63 | try { 64 | connectionHandler.start(); 65 | AsyncClient client = connector.connect("", 9090); 66 | 67 | Awaitility.waitAtMost(30, TimeUnit.SECONDS).until(() -> !client.isConnected()); 68 | Assert.assertFalse(client.isConnected()); 69 | }finally { 70 | connectionHandler.shutdown(); 71 | } 72 | } 73 | 74 | @Test 75 | public void testNonAcceptedConnection() throws IOException, ExecutionException, InterruptedException { 76 | InetSocketAddress listenAddress = new InetSocketAddress(9090); 77 | GenericClientHandler handler = new GenericClientHandler(); 78 | ThrowableFactory factory = new ThrowableFactory(new NullPointerException()); 79 | ConnectionBuilder builder = ConnectionBuilder.create(listenAddress, factory, null, null).shutdownWaitTime(100); 80 | ConnectionHandler connectionHandler = builder.build(); 81 | Connector connector = Connector.create(AsyncClient::new, handler, handler); 82 | try { 83 | connectionHandler.start(); 84 | AsyncClient client = connector.connect("127.0.0.1", 9090); 85 | Awaitility.waitAtMost(30, TimeUnit.SECONDS).until(() -> !client.isConnected()); 86 | Assert.assertFalse(client.isConnected()); 87 | }finally { 88 | connectionHandler.shutdown(); 89 | } 90 | } 91 | 92 | @Test 93 | public void testResourceStats() throws IOException { 94 | var template = "Pool {maxSize=%d, bufferSize=%d, estimateUse=%d}"; 95 | var listenAddress = new InetSocketAddress(9090); 96 | var handler = new GenericClientHandler(); 97 | ConnectionHandler connectionHandler = null; 98 | 99 | try { 100 | connectionHandler = createConnectionHandler(listenAddress, handler, 0); 101 | checkStats(template, connectionHandler, 0, 0, 0); 102 | } finally { 103 | if(nonNull(connectionHandler)) 104 | connectionHandler.shutdown(); 105 | } 106 | 107 | try { 108 | connectionHandler = createConnectionHandler(listenAddress, handler, 0.5f); 109 | checkStats(template, connectionHandler, 2, 4, 6); 110 | } finally { 111 | if(nonNull(connectionHandler)) 112 | connectionHandler.shutdown(); 113 | } 114 | 115 | 116 | } 117 | 118 | private ConnectionHandler createConnectionHandler(InetSocketAddress listenAddress, GenericClientHandler handler, float initFactor) throws IOException { 119 | ConnectionHandler connectionHandler; 120 | connectionHandler = ConnectionBuilder.create(listenAddress, AsyncClient::new, handler, handler).shutdownWaitTime(100).initBufferPoolFactor(initFactor) 121 | .addBufferPool(4, 5) 122 | .addBufferPool(8, 6) 123 | .addBufferPool(12, 7).build(); 124 | return connectionHandler; 125 | } 126 | 127 | private void checkStats(String template, ConnectionHandler connectionHandler, int estimate1, int estimate2, int estimate3) { 128 | String stats = connectionHandler.resourceStats(); 129 | Assert.assertTrue(stats.contains(String.format(template, 4, 5, estimate1))); 130 | Assert.assertTrue(stats.contains(String.format(template, 8, 6, estimate2))); 131 | Assert.assertTrue(stats.contains(String.format(template, 12, 7, estimate3))); 132 | } 133 | 134 | static class ThrowableFactory implements ClientFactory { 135 | 136 | private final RuntimeException exeception; 137 | 138 | ThrowableFactory(RuntimeException e) { 139 | this.exeception = e; 140 | } 141 | 142 | @Override 143 | public ThrowableClient create(Connection connection) { 144 | return new ThrowableClient(connection, exeception); 145 | } 146 | } 147 | 148 | static class ThrowableClient extends Client> { 149 | 150 | private final RuntimeException exception; 151 | 152 | public ThrowableClient(Connection connection, RuntimeException e) { 153 | super(connection); 154 | this.exception=e; 155 | } 156 | 157 | @Override 158 | public boolean encrypt(Buffer data, int offset, int size) { 159 | return false; 160 | } 161 | 162 | @Override 163 | public boolean decrypt(Buffer data, int offset, int size) { 164 | return false; 165 | } 166 | 167 | @Override 168 | protected void onDisconnection() { 169 | 170 | } 171 | 172 | @Override 173 | public void onConnected() { 174 | throw exception; 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/ConnectionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import org.awaitility.Awaitility; 22 | import org.junit.After; 23 | import org.junit.Assert; 24 | import org.junit.Test; 25 | 26 | import java.io.IOException; 27 | import java.net.InetSocketAddress; 28 | import java.nio.ByteBuffer; 29 | import java.nio.channels.AsynchronousSocketChannel; 30 | import java.util.concurrent.ExecutionException; 31 | import java.util.concurrent.TimeUnit; 32 | 33 | import static java.util.Objects.nonNull; 34 | 35 | /** 36 | * @author JoeAlisson 37 | */ 38 | public class ConnectionTest { 39 | 40 | private AsyncClient connectionClient; 41 | 42 | @Test 43 | public void testWriteWithClosedChannel() throws IOException, ExecutionException, InterruptedException { 44 | InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 9090); 45 | ConnectionHandler handler = ConnectionBuilder.create(socketAddress, AsyncClient::new, null, null).shutdownWaitTime(100).build(); 46 | try { 47 | handler.start(); 48 | AsyncClient client = Connector.create(AsyncClient::new, null, null).connect(socketAddress); 49 | Connection connection = client.getConnection(); 50 | connection.close(); 51 | 52 | ByteBuffer buffer = ByteBuffer.allocateDirect(10); 53 | buffer.putLong(80); 54 | buffer.flip(); 55 | connection.write(); 56 | connection.write(new ByteBuffer[] {buffer}); 57 | 58 | Assert.assertFalse(connection.isOpen()); 59 | Assert.assertEquals("", connection.getRemoteAddress()); 60 | } finally { 61 | handler.shutdown(); 62 | } 63 | } 64 | 65 | @Test 66 | public void testIsOpenWithNoConnectedChannel() throws IOException { 67 | AsynchronousSocketChannel channel = AsynchronousSocketChannel.open(); 68 | Connection connection = new Connection<>(channel,null, null, new ConnectionConfig(null)); 69 | channel.close(); 70 | Assert.assertFalse(connection.isOpen()); 71 | } 72 | 73 | @Test 74 | public void testRemoteAddressWithClosedChannel() throws IOException, ExecutionException, InterruptedException { 75 | InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 9090); 76 | ConnectionHandler handler = ConnectionBuilder.create(socketAddress, AsyncClient::new, null, null).shutdownWaitTime(100).build(); 77 | try { 78 | handler.start(); 79 | AsyncClient client = Connector.create(AsyncClient::new, null, null).connect(socketAddress); 80 | Connection connection = client.getConnection(); 81 | connection.close(); 82 | 83 | Assert.assertFalse(connection.isOpen()); 84 | Assert.assertEquals("", connection.getRemoteAddress()); 85 | } finally { 86 | handler.shutdown(); 87 | } 88 | } 89 | 90 | @Test 91 | public void testWriteBeforeClose() throws IOException, ExecutionException, InterruptedException { 92 | InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 9090); 93 | ConnectionHandler handler = ConnectionBuilder.create(socketAddress, this::buildClient, (buffer, client1) -> { client1.receivedPacket = buffer ; return null;}, null).shutdownWaitTime(100).build(); 94 | try { 95 | handler.start(); 96 | AsyncClient client = Connector.create(AsyncClient::new, null, null).connect(socketAddress); 97 | 98 | client.close(new AsyncClientClosePacket()); 99 | Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> nonNull(connectionClient.receivedPacket)); 100 | } finally { 101 | handler.shutdown(); 102 | } 103 | } 104 | 105 | private AsyncClient buildClient(Connection tConnection) { 106 | connectionClient = new AsyncClient(tConnection); 107 | return connectionClient; 108 | } 109 | 110 | @After 111 | public void tearDown() { 112 | connectionClient = null; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/DynamicPacketBufferTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import io.github.joealisson.mmocore.internal.DynamicPacketBuffer; 22 | import io.github.joealisson.mmocore.internal.InternalWritableBuffer; 23 | import org.junit.Assert; 24 | import org.junit.Test; 25 | 26 | import java.nio.ByteBuffer; 27 | 28 | /** 29 | * @author JoeAlisson 30 | */ 31 | public class DynamicPacketBufferTest { 32 | 33 | @Test 34 | public void testIncrease() { 35 | ConnectionConfig config = new ConnectionConfig(null); 36 | config.newBufferGroup(4, 32); 37 | config.newBufferGroup(4, 64); 38 | config.newBufferGroup(4, 128); 39 | config.newBufferGroup(4, 256); 40 | config.newBufferGroup(4, 512); 41 | config.newBufferGroup(4, 1024); 42 | config.complete(); 43 | DynamicPacketBuffer packetBuffer = new DynamicPacketBuffer(ByteBuffer.allocate(32), config.resourcePool); 44 | ByteBuffer[] initialBuffers = packetBuffer.toByteBuffers(); 45 | for (int i = 0; i < 32; i++) { 46 | packetBuffer.writeBytes(new byte[i * 64]); 47 | } 48 | ByteBuffer[] endBuffers = packetBuffer.toByteBuffers(); 49 | Assert.assertTrue(initialBuffers.length < endBuffers.length); 50 | 51 | } 52 | 53 | @Test 54 | public void testIntegrity() { 55 | ConnectionConfig config = new ConnectionConfig(null); 56 | config.newBufferGroup(4, 32); 57 | InternalWritableBuffer packetBuffer = InternalWritableBuffer.dynamicOf(ByteBuffer.allocate(32), config.resourcePool); 58 | 59 | packetBuffer.writeByte((byte) 1); 60 | packetBuffer.writeShort((short) 2); 61 | packetBuffer.writeChar('A'); 62 | packetBuffer.writeInt(3); 63 | packetBuffer.writeFloat(4); 64 | packetBuffer.writeLong(5); 65 | packetBuffer.writeDouble(6); 66 | 67 | Assert.assertEquals(1, packetBuffer.readByte(0)); 68 | Assert.assertEquals(2, packetBuffer.readShort(1)); 69 | Assert.assertEquals(3, packetBuffer.readInt(5)); 70 | 71 | packetBuffer.writeByte(4, (byte) 5); 72 | packetBuffer.writeShort(10, (short) 6); 73 | packetBuffer.writeInt(0, 40); 74 | 75 | Assert.assertEquals(5, packetBuffer.readByte(4)); 76 | Assert.assertEquals(6, packetBuffer.readShort(10)); 77 | Assert.assertEquals(40, packetBuffer.readInt(0)); 78 | } 79 | 80 | @Test(expected = IndexOutOfBoundsException.class) 81 | public void testNegativeIndex() { 82 | ConnectionConfig config = new ConnectionConfig(null); 83 | DynamicPacketBuffer packetBuffer = new DynamicPacketBuffer(ByteBuffer.allocate(32), config.resourcePool); 84 | packetBuffer.writeInt(-1, 10); 85 | } 86 | 87 | @Test(expected = IndexOutOfBoundsException.class) 88 | public void testOutOfBoundIndex() { 89 | ConnectionConfig config = new ConnectionConfig(null); 90 | DynamicPacketBuffer packetBuffer = new DynamicPacketBuffer(ByteBuffer.allocate(2), config.resourcePool); 91 | packetBuffer.writeInt(100, 10); 92 | } 93 | 94 | @Test(expected = IndexOutOfBoundsException.class) 95 | public void testOutOfBoundReadIndex() { 96 | ConnectionConfig config = new ConnectionConfig(null); 97 | DynamicPacketBuffer packetBuffer = new DynamicPacketBuffer(ByteBuffer.allocate(2), config.resourcePool); 98 | packetBuffer.readInt(0); 99 | } 100 | 101 | @Test 102 | public void testBufferLimitWriting() { 103 | ConnectionConfig config = new ConnectionConfig(null); 104 | config.complete(); 105 | DynamicPacketBuffer packetBuffer = new DynamicPacketBuffer(ByteBuffer.allocate(2), config.resourcePool); 106 | 107 | packetBuffer.writeInt(1000000); 108 | 109 | int shortLimit = packetBuffer.limit() - 1; 110 | packetBuffer.position(shortLimit); 111 | packetBuffer.writeShort((short) 20000); 112 | 113 | int longLimit = packetBuffer.limit() - 1; 114 | packetBuffer.position(longLimit); 115 | packetBuffer.writeLong(10000); 116 | 117 | Assert.assertEquals(1000000, packetBuffer.readInt(0)); 118 | Assert.assertEquals(20000, packetBuffer.readShort(shortLimit)); 119 | Assert.assertEquals(10000, packetBuffer.readInt(longLimit)); 120 | } 121 | 122 | @Test 123 | public void testBufferLimit() { 124 | ConnectionConfig config = new ConnectionConfig(null); 125 | config.complete(); 126 | DynamicPacketBuffer packetBuffer = new DynamicPacketBuffer(ByteBuffer.allocate(32), config.resourcePool); 127 | 128 | packetBuffer.writeBytes(new byte[10]); 129 | int pos = packetBuffer.position(); 130 | packetBuffer.mark(); 131 | Assert.assertEquals(pos, packetBuffer.limit()); 132 | 133 | packetBuffer.limit(64); 134 | Assert.assertTrue(packetBuffer.capacity() >= 64); 135 | } 136 | 137 | 138 | @Test 139 | public void testMultipleBufferLimits() { 140 | ConnectionConfig config = new ConnectionConfig(null); 141 | config.complete(); 142 | DynamicPacketBuffer packetBuffer = new DynamicPacketBuffer(ByteBuffer.allocate(32), config.resourcePool); 143 | 144 | int initial = 10; 145 | byte end = 20; 146 | 147 | packetBuffer.writeInt(initial); 148 | packetBuffer.writeBytes(new byte[27]); 149 | packetBuffer.writeByte(end); 150 | 151 | packetBuffer.mark(); 152 | 153 | Assert.assertEquals(32, packetBuffer.limit()); 154 | Assert.assertEquals(initial, packetBuffer.readInt(0)); 155 | Assert.assertEquals(end, packetBuffer.readByte(31)); 156 | 157 | var buffers = packetBuffer.toByteBuffers(); 158 | Assert.assertEquals(1, buffers.length); 159 | 160 | var firstBuffer = buffers[0]; 161 | 162 | Assert.assertEquals(initial, firstBuffer.getInt()); 163 | firstBuffer.get(new byte[27]); 164 | Assert.assertEquals(end, firstBuffer.get()); 165 | 166 | 167 | packetBuffer.writeInt(initial); 168 | packetBuffer.writeBytes(new byte[27]); 169 | packetBuffer.writeByte(end); 170 | 171 | packetBuffer.mark(); 172 | 173 | Assert.assertEquals(64, packetBuffer.limit()); 174 | Assert.assertEquals(initial, packetBuffer.readInt(32)); 175 | Assert.assertEquals(end, packetBuffer.readByte(63)); 176 | 177 | buffers = packetBuffer.toByteBuffers(); 178 | Assert.assertEquals(2, buffers.length); 179 | 180 | var secondBuffer = buffers[1]; 181 | Assert.assertEquals(initial, secondBuffer.getInt()); 182 | secondBuffer.get(new byte[27]); 183 | Assert.assertEquals(end, secondBuffer.get()); 184 | } 185 | 186 | @Test 187 | public void testSplitValue() { 188 | ConnectionConfig config = new ConnectionConfig(null); 189 | config.complete(); 190 | ResourcePool resourcePool = config.resourcePool; 191 | 192 | 193 | DynamicPacketBuffer packetBuffer = new DynamicPacketBuffer(ByteBuffer.allocate(1), resourcePool); 194 | packetBuffer.writeShort((short) 10); 195 | Assert.assertEquals(10, packetBuffer.readShort(0)); 196 | 197 | for (int i = 1; i <= 4; i++) { 198 | packetBuffer = new DynamicPacketBuffer(ByteBuffer.allocate(i), resourcePool); 199 | packetBuffer.writeInt(10); 200 | Assert.assertEquals(10, packetBuffer.readInt(0)); 201 | } 202 | 203 | for (int i = 1; i <= 4; i++) { 204 | packetBuffer = new DynamicPacketBuffer(ByteBuffer.allocate(i), resourcePool); 205 | packetBuffer.writeFloat(10.5f); 206 | Assert.assertEquals(10.5f, packetBuffer.readFloat(0), 0f); 207 | } 208 | 209 | for (int i = 1; i <= 8; i++) { 210 | packetBuffer = new DynamicPacketBuffer(ByteBuffer.allocate(i), resourcePool); 211 | packetBuffer.writeLong(10); 212 | Assert.assertEquals(10, packetBuffer.readLong(0)); 213 | } 214 | 215 | for (int i = 1; i <= 8; i++) { 216 | packetBuffer = new DynamicPacketBuffer(ByteBuffer.allocate(i), resourcePool); 217 | packetBuffer.writeDouble(10.5); 218 | Assert.assertEquals(10.5, packetBuffer.readDouble(0), 0); 219 | } 220 | 221 | byte[] data = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; 222 | byte[] read = new byte[data.length]; 223 | 224 | for (int i = 1; i < data.length; i++) { 225 | packetBuffer = new DynamicPacketBuffer(ByteBuffer.allocate(i), resourcePool); 226 | packetBuffer.writeBytes(data); 227 | packetBuffer.readBytes(0, read); 228 | Assert.assertArrayEquals(data, read); 229 | } 230 | } 231 | } -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/GenericClientHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import java.util.concurrent.Executors; 22 | import java.util.concurrent.LinkedBlockingQueue; 23 | import java.util.concurrent.ThreadPoolExecutor; 24 | import java.util.concurrent.TimeUnit; 25 | 26 | /** 27 | * @author JoeAlisson 28 | */ 29 | public class GenericClientHandler implements PacketHandler, PacketExecutor { 30 | 31 | private ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 2, 15L,TimeUnit.SECONDS, new LinkedBlockingQueue<>(), Executors.defaultThreadFactory()); 32 | 33 | @Override 34 | public ReadablePacket handlePacket(ReadableBuffer buffer, AsyncClient client) { 35 | int opcode = Byte.toUnsignedInt(buffer.readByte()); 36 | ReadablePacket packet = null; 37 | if(opcode == 0x01) { 38 | packet = new AsyncServerPingPacket(); 39 | } else if(opcode == 0x02) { 40 | packet = new AsyncClientPongPacket(); 41 | } else if(opcode == 0x03) { 42 | packet = new AsyncServerClosePacket(); 43 | } else if (opcode == 0x04) { 44 | byte[] bytes = new byte[0]; 45 | if(buffer.remaining() >= 2) { 46 | short op2 = buffer.readShort(); 47 | if(op2 == 0x01) { 48 | int op3 = buffer.readInt(); 49 | if(op3 == 0x02) { 50 | bytes = new byte[buffer.remaining()]; 51 | buffer.readBytes(bytes); 52 | } 53 | } 54 | } 55 | packet = new AsyncClientClosedConnection(bytes); 56 | } 57 | else if(opcode == 0x10) { 58 | packet = new AsyncClientBroadcastReceiverPacket(); 59 | } else if(opcode == 0x20) { 60 | packet = new AsyncClientFairnessReceivePacket(); 61 | } else if(opcode == 0x21) { 62 | packet = new AsyncClientFairnessRepliedPacket(); 63 | } 64 | return packet; 65 | } 66 | 67 | @Override 68 | public void execute(ReadablePacket packet) { 69 | threadPool.execute(packet); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/ReadHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import org.junit.Assert; 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | 25 | import java.io.IOException; 26 | import java.net.InetSocketAddress; 27 | import java.nio.ByteBuffer; 28 | import java.util.concurrent.ExecutionException; 29 | 30 | import static io.github.joealisson.mmocore.ConnectionConfig.HEADER_SIZE; 31 | 32 | /** 33 | * @author JoeAlisson 34 | */ 35 | public class ReadHandlerTest { 36 | 37 | private ConnectionBuilder connectionBuilder; 38 | private InetSocketAddress address; 39 | private Connector connector; 40 | 41 | @Before 42 | public void setUp() { 43 | address = new InetSocketAddress("127.0.0.1", 9090); 44 | connectionBuilder = ConnectionBuilder.create(address, AsyncClient::new, (data, client) -> null, (packet) -> { }); 45 | connector = Connector.create(AsyncClient::new, null, null); 46 | } 47 | 48 | @Test 49 | public void testFailed() throws InterruptedException, ExecutionException, IOException { 50 | ConnectionHandler connectionHandler = connectionBuilder.shutdownWaitTime(100).build(); 51 | try { 52 | connectionHandler.start(); 53 | AsyncClient client = connector.connect(address); 54 | ReadHandler handler = new ReadHandler<>(null, null); 55 | handler.failed(new IllegalAccessException(), client); 56 | boolean connected = client.isConnected(); 57 | Assert.assertFalse(connected); 58 | } finally { 59 | connectionHandler.shutdown(); 60 | } 61 | } 62 | 63 | @Test 64 | public void testCompletedDisconnected() throws InterruptedException, ExecutionException, IOException { 65 | ConnectionHandler connectionHandler = connectionBuilder.shutdownWaitTime(100).build(); 66 | try { 67 | connectionHandler.start(); 68 | AsyncClient client = connector.connect(address); 69 | Assert.assertTrue(client.isConnected()); 70 | client.disconnect(); 71 | Assert.assertFalse(client.isConnected()); 72 | ReadHandler handler = new ReadHandler<>(null, null); 73 | handler.completed(2, client); 74 | Assert.assertEquals(HEADER_SIZE, client.getExpectedReadSize()); 75 | } finally { 76 | connectionHandler.shutdown(); 77 | } 78 | } 79 | 80 | @Test 81 | public void testCompletedWithoutData() throws InterruptedException, ExecutionException, IOException { 82 | ConnectionHandler connectionHandler = connectionBuilder.shutdownWaitTime(100).build(); 83 | connectionHandler.start(); 84 | AsyncClient client = connector.connect(address); 85 | try { 86 | ReadHandler handler = new ReadHandler<>(null, null); 87 | handler.completed(2, client); 88 | Assert.fail("Exception is Expected"); 89 | } catch (Exception e) { 90 | boolean connected = client.isConnected(); 91 | Assert.assertTrue(connected); 92 | } finally { 93 | connectionHandler.shutdown(); 94 | } 95 | } 96 | 97 | @Test 98 | public void testCompletedWithoutEnoughData() throws InterruptedException, ExecutionException, IOException { 99 | ConnectionHandler connectionHandler = connectionBuilder.shutdownWaitTime(100).build(); 100 | connectionHandler.start(); 101 | AsyncClient client = connector.connect(address); 102 | try { 103 | ReadHandler handler = new ReadHandler<>(null, null); 104 | ByteBuffer buffer = client.getConnection().getReadingBuffer(); 105 | buffer.putShort((short) 10); 106 | handler.completed(2, client); 107 | } catch (Exception e) { 108 | boolean connected = client.isConnected(); 109 | Assert.assertTrue(connected); 110 | } finally { 111 | connectionHandler.shutdown(); 112 | } 113 | } 114 | 115 | @Test 116 | public void testTwoPacketOnce() throws IOException, ExecutionException, InterruptedException { 117 | ConnectionHandler connectionHandler = connectionBuilder.shutdownWaitTime(100).build(); 118 | connectionHandler.start(); 119 | AsyncClient client = connector.connect(address); 120 | try { 121 | ReadHandler handler = new ReadHandler<>( (data, client1) -> null, (packet) -> { }); 122 | ByteBuffer buffer = client.getConnection().getReadingBuffer(); 123 | buffer.putShort((short) 4); 124 | buffer.put((byte) 10); 125 | buffer.putShort((short) 20); 126 | buffer.putShort((short) 10); 127 | 128 | handler.completed(7, client); 129 | } catch (Exception e) { 130 | boolean connected = client.isConnected(); 131 | Assert.assertTrue(connected); 132 | } finally { 133 | connectionHandler.shutdown(); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/ReadingThrottlingHelper.java: -------------------------------------------------------------------------------- 1 | package io.github.joealisson.mmocore; 2 | 3 | import java.time.Instant; 4 | import java.time.temporal.ChronoUnit; 5 | import java.util.concurrent.CompletableFuture; 6 | import java.util.concurrent.TimeUnit; 7 | import java.util.concurrent.atomic.AtomicBoolean; 8 | 9 | class ReadingThrottlingHelper { 10 | 11 | static RTClient lastClient; 12 | 13 | public static RTClient create(Connection connection) { 14 | lastClient = new RTClient(connection); 15 | return lastClient; 16 | } 17 | 18 | 19 | public static void execute(ReadablePacket packet) { 20 | packet.run(); 21 | } 22 | 23 | static class RTClient extends Client> { 24 | 25 | public Instant pingReceived; 26 | public Instant ping2ndReceived; 27 | public AtomicBoolean readableAgain = new AtomicBoolean(false); 28 | 29 | protected RTClient(Connection connection) { 30 | super(connection); 31 | } 32 | 33 | public boolean hasMinimumTimeBetweenPackets() { 34 | return ChronoUnit.SECONDS.between(pingReceived, ping2ndReceived) >= 2; 35 | } 36 | 37 | @Override 38 | public boolean encrypt(Buffer data, int offset, int size) { 39 | return true; 40 | } 41 | 42 | @Override 43 | public boolean decrypt(Buffer data, int offset, int size) { 44 | return true; 45 | } 46 | 47 | @Override 48 | protected void onDisconnection() { 49 | 50 | } 51 | 52 | @Override 53 | public void onConnected() { 54 | 55 | } 56 | 57 | } 58 | 59 | public static WritablePacket ping2nd() { 60 | return new WritablePacket<>() { 61 | @Override 62 | protected boolean write(RTClient client, WritableBuffer buffer) { 63 | buffer.writeByte(2); 64 | buffer.writeString("PING 2ND"); 65 | return true; 66 | } 67 | }; 68 | } 69 | 70 | public static WritablePacket ping() { 71 | return new WritablePacket<>() { 72 | @Override 73 | protected boolean write(RTClient client, WritableBuffer buffer) { 74 | buffer.writeByte(1); 75 | buffer.writeString("PING"); 76 | return true; 77 | } 78 | }; 79 | } 80 | 81 | public static ReadablePacket handlePacket(ReadableBuffer readableBuffer, RTClient client) { 82 | byte opcode = readableBuffer.readByte(); 83 | if(opcode == 1) { 84 | return readablePing(); 85 | } else if (opcode == 2) { 86 | return readablePing2nd(); 87 | } 88 | return null; 89 | } 90 | 91 | private static ReadablePacket readablePing2nd() { 92 | return new ReadablePacket<>() { 93 | @Override 94 | protected boolean read() { 95 | return true; 96 | } 97 | 98 | @Override 99 | public void run() { 100 | client.ping2ndReceived = Instant.now(); 101 | client.readableAgain.set(true); 102 | } 103 | }; 104 | } 105 | 106 | private static ReadablePacket readablePing() { 107 | return new ReadablePacket<>() { 108 | @Override 109 | protected boolean read() { 110 | return true; 111 | } 112 | 113 | @Override 114 | public void run() { 115 | client.pingReceived = Instant.now(); 116 | CompletableFuture.delayedExecutor(2500, TimeUnit.MILLISECONDS).execute(client::readNextPacket); 117 | } 118 | }; 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/SinglePacketBufferTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import io.github.joealisson.mmocore.internal.SinglePacketBuffer; 22 | import org.junit.Assert; 23 | import org.junit.Test; 24 | 25 | import java.nio.ByteBuffer; 26 | 27 | /** 28 | * @author JoeAlisson 29 | */ 30 | public class SinglePacketBufferTest { 31 | 32 | @Test 33 | public void testIntegrity() { 34 | ByteBuffer buffer = ByteBuffer.allocate(100); 35 | buffer.put((byte) 1); 36 | buffer.put(new byte[] {2, 3, 4, 5}); 37 | buffer.putShort((short) 6); 38 | buffer.putChar('A'); 39 | buffer.putInt(7); 40 | buffer.putFloat(8.5f); 41 | buffer.putLong(9); 42 | buffer.putDouble(10.5); 43 | 44 | SinglePacketBuffer packetBuffer = new SinglePacketBuffer(buffer.flip()); 45 | 46 | Assert.assertEquals(1, packetBuffer.readByte()); 47 | byte[] data = new byte[4]; 48 | packetBuffer.readBytes(data); 49 | Assert.assertEquals(2, data[0]); 50 | Assert.assertEquals(3, data[1]); 51 | Assert.assertEquals(4, data[2]); 52 | Assert.assertEquals(5, data[3]); 53 | Assert.assertEquals(6, packetBuffer.readShort()); 54 | Assert.assertEquals('A', packetBuffer.readChar()); 55 | Assert.assertEquals(7, packetBuffer.readInt()); 56 | Assert.assertEquals(8.5f, packetBuffer.readFloat(), 0); 57 | Assert.assertEquals(9, packetBuffer.readLong()); 58 | Assert.assertEquals(10.5, packetBuffer.readDouble(), 0); 59 | 60 | 61 | packetBuffer.writeByte(0, (byte) 2); 62 | packetBuffer.writeShort(5, (short) 8); 63 | packetBuffer.writeInt(9, 9); 64 | 65 | Assert.assertEquals(2, packetBuffer.readByte(0)); 66 | Assert.assertEquals(8, packetBuffer.readShort(5)); 67 | Assert.assertEquals(9, packetBuffer.readInt(9)); 68 | } 69 | 70 | @Test 71 | public void testLimits() { 72 | ByteBuffer buffer = ByteBuffer.allocate(100); 73 | SinglePacketBuffer packetBuffer = new SinglePacketBuffer(buffer); 74 | 75 | Assert.assertEquals(100, packetBuffer.limit()); 76 | 77 | packetBuffer.limit(50); 78 | Assert.assertEquals(50, packetBuffer.limit()); 79 | } 80 | 81 | @Test(expected = IllegalArgumentException.class) 82 | public void testOutOfLimits(){ 83 | ByteBuffer buffer = ByteBuffer.allocate(100); 84 | SinglePacketBuffer packetBuffer = new SinglePacketBuffer(buffer); 85 | packetBuffer.limit(150); 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/WriteHandlerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore; 20 | 21 | import org.junit.Assert; 22 | import org.junit.Test; 23 | 24 | import java.io.IOException; 25 | import java.net.InetSocketAddress; 26 | import java.util.concurrent.ExecutionException; 27 | 28 | /** 29 | * @author JoeAlisson 30 | */ 31 | public class WriteHandlerTest { 32 | 33 | @Test 34 | public void testCompletedWithNegative() throws InterruptedException, ExecutionException, IOException { 35 | InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 9090); 36 | ConnectionHandler handler = ConnectionBuilder.create(socketAddress, AsyncClient::new, null, null).shutdownWaitTime(100).build(); 37 | try { 38 | handler.start(); 39 | AsyncClient client = Connector.create(AsyncClient::new, null, null).connect(socketAddress); 40 | WriteHandler writeHandler = new WriteHandler<>(); 41 | writeHandler.completed(-1L, client); 42 | boolean connected = client.isConnected(); 43 | Assert.assertFalse(connected); 44 | } finally { 45 | handler.shutdown(); 46 | } 47 | } 48 | 49 | @Test 50 | public void testCompleted() throws InterruptedException, ExecutionException, IOException { 51 | InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1",9090); 52 | ConnectionHandler handler = ConnectionBuilder.create(socketAddress, AsyncClient::new, null, null).shutdownWaitTime(100).build(); 53 | try { 54 | handler.start(); 55 | AsyncClient client = Connector.create(AsyncClient::new, null, null).connect(socketAddress); 56 | client.sendPacket(new AsyncClientPingPacket()); 57 | WriteHandler writeHandler = new WriteHandler<>(); 58 | client.getConnection().releaseWritingBuffer(); 59 | writeHandler.completed(2L, client); 60 | client.disconnect(); 61 | Assert.assertTrue(client.getDataSentSize() > 0); 62 | } finally { 63 | handler.shutdown(); 64 | } 65 | } 66 | 67 | @Test 68 | public void testFailed() throws InterruptedException, IOException, ExecutionException { 69 | InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 9090); 70 | ConnectionHandler handler = ConnectionBuilder.create(socketAddress, AsyncClient::new, null, null).shutdownWaitTime(100).build(); 71 | try { 72 | handler.start(); 73 | AsyncClient client = Connector.create(AsyncClient::new, null, null).connect(socketAddress); 74 | WriteHandler writeHandler = new WriteHandler<>(); 75 | writeHandler.failed(new IllegalArgumentException(), client); 76 | boolean connected = client.isConnected(); 77 | Assert.assertFalse(connected); 78 | } finally { 79 | handler.shutdown(); 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/util/Comparator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore.util; 20 | 21 | import static java.util.Objects.isNull; 22 | import static java.util.Objects.nonNull; 23 | 24 | /** 25 | * @author JoeAlisson 26 | */ 27 | public class Comparator { 28 | 29 | 30 | public static void assertEquals(long expected, long actual) throws ValueMismatchException { 31 | if(expected != actual) { 32 | mismatch(expected, actual); 33 | } 34 | } 35 | 36 | public static void assertEquals(double expected, double actual) throws ValueMismatchException { 37 | if(Double.compare(expected, actual) != 0) { 38 | mismatch(expected, actual); 39 | } 40 | } 41 | 42 | public static void assertEquals(String expected, String actual) throws ValueMismatchException { 43 | if(isNull(expected) && nonNull(actual) || !expected.equals(actual)) { 44 | mismatch(expected, actual); 45 | } 46 | } 47 | 48 | public static void assertFalse(boolean actual) throws ValueMismatchException { 49 | if(actual) { 50 | mismatch(false, true); 51 | } 52 | } 53 | 54 | public static void assertTrue(boolean actual) throws ValueMismatchException { 55 | if(!actual) { 56 | mismatch(true, false); 57 | } 58 | } 59 | 60 | private static void mismatch(Object expected, Object actual) throws ValueMismatchException { 61 | throw new ValueMismatchException(expected.toString(), actual.toString()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/io/github/joealisson/mmocore/util/ValueMismatchException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2019-2021 Async-mmocore 3 | * 4 | * This file is part of the Async-mmocore project. 5 | * 6 | * Async-mmocore is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Async-mmocore is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | package io.github.joealisson.mmocore.util; 20 | 21 | /** 22 | * @author JoeAlisson 23 | */ 24 | public class ValueMismatchException extends Exception { 25 | 26 | public ValueMismatchException(String message) { 27 | super(message); 28 | } 29 | 30 | public ValueMismatchException(String expected, String actual) { 31 | super(String.format("Expected: %s, but actual is: %s", expected, actual)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/resources/async-mmocore-wrong.properties: -------------------------------------------------------------------------------- 1 | shutdownWaitTime = other 2 | threadPoolSize = six 3 | -------------------------------------------------------------------------------- /src/test/resources/async-mmocore.properties: -------------------------------------------------------------------------------- 1 | shutdownWaitTime = 50 2 | threadPoolSize = 6 3 | 4 | bufferPool.initFactor=0.2 5 | bufferPool.small.size=10 6 | bufferPool.small.bufferSize=256 7 | --------------------------------------------------------------------------------