-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
323 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Koe - Native UDP-Queue extension | ||
|
||
An extension that provides an implementation of [JDA-NAS](https://github.com/sedmelluq/jda-nas) in Koe, | ||
which moves packet sending/scheduling logic outside JVM, therefore audio packets can be sent during GC pauses (as long as there's enough audio data in the queue). | ||
|
||
Note that custom codec support is limited, adds additional latency and proper usage of Netty already | ||
limits GC pressure because of much smaller number of allocations. | ||
|
||
### Usage | ||
|
||
Just add it to KoeOptions :^) | ||
|
||
```java | ||
var Koe = Koe.koe(KoeOptions.builder() | ||
.setFramePollerFactory(new UdpQueueFramePollerFactory()) | ||
.create()); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
dependencies { | ||
compileOnly project(':core') | ||
compileOnly group: 'org.jetbrains', name: 'annotations', version: '13.0' | ||
implementation 'com.sedmelluq:udp-queue:1.1.0-linux64' | ||
} |
108 changes: 108 additions & 0 deletions
108
ext-udpqueue/src/main/java/moe/kyokobot/koe/codec/udpqueue/UdpQueueFramePollerFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package moe.kyokobot.koe.codec.udpqueue; | ||
|
||
import com.sedmelluq.discord.lavaplayer.udpqueue.natives.UdpQueueManager; | ||
import com.sedmelluq.lava.common.tools.DaemonThreadFactory; | ||
import com.sedmelluq.lava.common.tools.ExecutorTools; | ||
import moe.kyokobot.koe.VoiceConnection; | ||
import moe.kyokobot.koe.codec.Codec; | ||
import moe.kyokobot.koe.codec.FramePoller; | ||
import moe.kyokobot.koe.codec.FramePollerFactory; | ||
import moe.kyokobot.koe.codec.OpusCodec; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.concurrent.ConcurrentHashMap.KeySetView; | ||
import java.util.concurrent.ScheduledExecutorService; | ||
import java.util.concurrent.ScheduledThreadPoolExecutor; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.atomic.AtomicLong; | ||
|
||
public class UdpQueueFramePollerFactory implements FramePollerFactory { | ||
private static final int DEFAULT_BUFFER_DURATION = 400; | ||
private static final int PACKET_INTERVAL = 20; | ||
private static final int MAXIMUM_PACKET_SIZE = 4096; | ||
|
||
private final int bufferDuration; | ||
private final AtomicLong identifierCounter = new AtomicLong(); | ||
private final Object lock = new Object(); | ||
private final KeySetView<UdpQueueOpusFramePoller, Boolean> pollers = ConcurrentHashMap.newKeySet(); | ||
private UdpQueueManager queueManager; | ||
private ScheduledExecutorService scheduler; | ||
|
||
public UdpQueueFramePollerFactory() { | ||
this(DEFAULT_BUFFER_DURATION); | ||
} | ||
|
||
public UdpQueueFramePollerFactory(int bufferDuration) { | ||
this.bufferDuration = bufferDuration; | ||
} | ||
|
||
private void initialiseQueueManager() { | ||
scheduler = new ScheduledThreadPoolExecutor(1, new DaemonThreadFactory("native-udp")); | ||
queueManager = new UdpQueueManager(bufferDuration / PACKET_INTERVAL, | ||
TimeUnit.MILLISECONDS.toNanos(PACKET_INTERVAL), MAXIMUM_PACKET_SIZE); | ||
|
||
scheduler.scheduleWithFixedDelay(this::populateQueues, 0, 40, TimeUnit.MILLISECONDS); | ||
|
||
Thread thread = new Thread(process(queueManager)); | ||
thread.setPriority((Thread.NORM_PRIORITY + Thread.MAX_PRIORITY) / 2); | ||
thread.setDaemon(true); | ||
thread.start(); | ||
} | ||
|
||
private ScheduledExecutorService shutdownQueueManager() { | ||
queueManager.close(); | ||
queueManager = null; | ||
|
||
ScheduledExecutorService currentScheduler = scheduler; | ||
scheduler = null; | ||
return currentScheduler; | ||
} | ||
|
||
void addInstance(UdpQueueOpusFramePoller poller) { | ||
synchronized (lock) { | ||
pollers.add(poller); | ||
|
||
if (queueManager == null) { | ||
initialiseQueueManager(); | ||
} | ||
} | ||
} | ||
|
||
void removeInstance(UdpQueueOpusFramePoller poller) { | ||
ScheduledExecutorService schedulerToShutDown = null; | ||
|
||
synchronized (lock) { | ||
if (pollers.remove(poller) && pollers.isEmpty() && queueManager != null) { | ||
schedulerToShutDown = shutdownQueueManager(); | ||
} | ||
} | ||
|
||
if (schedulerToShutDown != null) { | ||
ExecutorTools.shutdownExecutor(schedulerToShutDown, "native udp queue populator"); | ||
} | ||
} | ||
|
||
private void populateQueues() { | ||
UdpQueueManager manager = queueManager; /* avoid getfield opcode */ | ||
|
||
if (manager != null) { | ||
for (var system : pollers) { | ||
system.populateQueue(manager); | ||
} | ||
} | ||
} | ||
|
||
private static Runnable process(UdpQueueManager unbake) { | ||
return unbake::process; | ||
} | ||
|
||
@Override | ||
@Nullable | ||
public FramePoller createFramePoller(Codec codec, VoiceConnection connection) { | ||
if (OpusCodec.INSTANCE.equals(codec)) { | ||
return new UdpQueueOpusFramePoller(identifierCounter.incrementAndGet(), this, connection); | ||
} | ||
return null; | ||
} | ||
} |
69 changes: 69 additions & 0 deletions
69
ext-udpqueue/src/main/java/moe/kyokobot/koe/codec/udpqueue/UdpQueueOpusFramePoller.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package moe.kyokobot.koe.codec.udpqueue; | ||
|
||
import com.sedmelluq.discord.lavaplayer.udpqueue.natives.UdpQueueManager; | ||
import moe.kyokobot.koe.VoiceConnection; | ||
import moe.kyokobot.koe.codec.AbstractFramePoller; | ||
import moe.kyokobot.koe.codec.OpusCodec; | ||
import moe.kyokobot.koe.internal.handler.DiscordUDPConnection; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.net.InetSocketAddress; | ||
import java.util.concurrent.atomic.AtomicInteger; | ||
|
||
public class UdpQueueOpusFramePoller extends AbstractFramePoller { | ||
private static final Logger logger = LoggerFactory.getLogger(UdpQueueOpusFramePoller.class); | ||
|
||
private final long queueKey; | ||
private final UdpQueueFramePollerFactory factory; | ||
private AtomicInteger timestamp; | ||
|
||
public UdpQueueOpusFramePoller(long queueKey, UdpQueueFramePollerFactory factory, VoiceConnection connection) { | ||
super(connection); | ||
this.queueKey = queueKey; | ||
this.factory = factory; | ||
this.timestamp = new AtomicInteger(); | ||
} | ||
|
||
@Override | ||
public void start() { | ||
if (this.polling) { | ||
throw new IllegalStateException("Polling already started!"); | ||
} | ||
|
||
factory.addInstance(this); | ||
this.polling = true; | ||
} | ||
|
||
@Override | ||
public void stop() { | ||
if (this.polling) { | ||
factory.removeInstance(this); | ||
} | ||
this.polling = false; | ||
} | ||
|
||
void populateQueue(UdpQueueManager queueManager) { | ||
int remaining = queueManager.getRemainingCapacity(queueKey); | ||
//boolean emptyQueue = queueManager.getCapacity() - remaining > 0; | ||
|
||
var handler = (DiscordUDPConnection) connection.getConnectionHandler(); | ||
var sender = connection.getSender(); | ||
|
||
for (int i = 0; i < remaining; i++) { | ||
if (sender != null && handler != null && sender.canSendFrame()) { | ||
var buf = allocator.buffer(); | ||
int start = buf.writerIndex(); | ||
sender.retrieve(OpusCodec.INSTANCE, buf); | ||
int len = buf.writerIndex() - start; | ||
//handler.sendFrame(OpusCodec.PAYLOAD_TYPE, timestamp.getAndAdd(960), buf, len); | ||
var packet = handler.createPacket(OpusCodec.PAYLOAD_TYPE, timestamp.getAndAdd(960), buf, len); | ||
if (packet != null) { | ||
queueManager.queuePacket(queueKey, packet.nioBuffer(), | ||
(InetSocketAddress) handler.getServerAddress()); | ||
} | ||
buf.release(); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
rootProject.name = 'koe' | ||
|
||
include ':core' | ||
include ':ext-udpqueue' | ||
include ':testbot' | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
testbot/src/main/java/moe/kyokobot/koe/testbot/GCPressureGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package moe.kyokobot.koe.testbot; | ||
|
||
import moe.kyokobot.koe.KoeOptions; | ||
|
||
import java.util.ArrayList; | ||
|
||
public class GCPressureGenerator { | ||
@SuppressWarnings("squid:S1215") | ||
private static Thread pressureThread = new Thread(() -> { | ||
try { | ||
while (true) { | ||
try { | ||
{ | ||
var l = new ArrayList<int[]>(); | ||
for (int i = 0; i < 10000; i++) { | ||
l.add(new int[1024]); | ||
} | ||
l.stream().map(String::valueOf).count(); | ||
} | ||
{ | ||
for (int i = 0; i < 25000; i++) { | ||
var arr = "malksmdlkamsldmalksmdlkmasldmlkam32908092930180928308290488209830928081028013sldmlkamslkdmlakmsldkmlakmsldkmalsmdalksmldaads".split(String.valueOf(i)); | ||
} | ||
} | ||
{ | ||
for (int i = 0; i < 25000; i++) { | ||
new TestBot(null); | ||
} | ||
} | ||
|
||
long pre = System.currentTimeMillis(); | ||
System.gc(); | ||
System.out.printf("GC took %dms\n", System.currentTimeMillis() - pre); | ||
} catch (OutOfMemoryError e) { | ||
long pre = System.currentTimeMillis(); | ||
System.gc(); | ||
System.out.printf("OOM! GC took %dms\n", System.currentTimeMillis() - pre); | ||
} | ||
Thread.sleep(5000); | ||
} | ||
} catch (InterruptedException e) { | ||
Thread.currentThread().interrupt(); | ||
} | ||
}, "Koe TestBot - GC Pressure Tester"); | ||
|
||
public static boolean toggle() { | ||
if (pressureThread.isAlive()) { | ||
pressureThread.interrupt(); | ||
return false; | ||
} else { | ||
pressureThread.start(); | ||
return true; | ||
} | ||
} | ||
} |
5 changes: 4 additions & 1 deletion
5
...n/java/moe/kyokobot/koe/testbot/Main.java → ...kobot/koe/testbot/KoeTestBotLauncher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 20 additions & 0 deletions
20
testbot/src/main/java/moe/kyokobot/koe/testbot/UdpQueueTestBotLauncher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package moe.kyokobot.koe.testbot; | ||
|
||
import moe.kyokobot.koe.Koe; | ||
import moe.kyokobot.koe.KoeOptions; | ||
import moe.kyokobot.koe.codec.udpqueue.UdpQueueFramePollerFactory; | ||
|
||
public class UdpQueueTestBotLauncher { | ||
public static void main(String[] args) { | ||
var bot = new TestBot(System.getenv("TOKEN")) { | ||
@Override | ||
public Koe createKoe() { | ||
return Koe.koe(KoeOptions.builder() | ||
.setFramePollerFactory(new UdpQueueFramePollerFactory()) | ||
.create()); | ||
} | ||
}; | ||
Runtime.getRuntime().addShutdownHook(new Thread(bot::stop)); | ||
bot.start(); | ||
} | ||
} |