Skip to content

Commit 7eaf202

Browse files
committed
update portfolio
1 parent f99c341 commit 7eaf202

3 files changed

Lines changed: 203 additions & 13 deletions

File tree

docs/assets/neb0.png

27.2 KB
Loading

docs/assets/neb1.png

539 KB
Loading

docs/portfolio.md

Lines changed: 203 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,19 @@ slug: /portfolio
99

1010
> By Kexing Yu
1111
12-
## Introduction
12+
This page includes two personal projects, ***[MadParticle](https://modrinth.com/mod/mad-particle)*** and ***NotEnoughBandwidth*** (still in active developing), both are Minecraft Java Edition mods. I am the lead creator of these projects, responsible for planning, design, programming, publication, and maintenance. Both projects are still under active development.
1313

14-
This page contains two core classes from my proudest Minecraft mod, ***[MadParticle](https://modrinth.com/mod/mad-particle)*** : `NeoInstancedRenderManager` and `MultiThreadedEqualObjectLinkedOpenHashSetQueue`.
14+
## MadParticle
1515

16-
*MadParticle* was originally designed as a decorative particle mod for Minecraft, allowing players to create visually rich in-game scenes through a large number of configurable parameters. Unlike particle systems in Unreal or Unity, Minecraft’s particles are relatively heavier, which function more like client-side actors/entities than VFX effects.
16+
MadParticle was originally designed as a decorative particle mod for Minecraft, allowing players to create visually rich in-game scenes through a large number of configurable parameters.
17+
18+
Unlike particle systems in Unreal or Unity, Minecraft's particles are relatively heavier, which function more like client-side actors/entities than VFX effects. Therefore, MadParticle also provides a highly optimized particle system that replaces the vanilla particle rendering and computation, delivering a 15–20x performance improvement.
19+
20+
:::info
21+
22+
If intersted, the complete source code of the project is available at https://github.com/USS-Shenzhou/MadParticle (My online alias is USS_Shenzhou).
23+
24+
:::
1725

1826
### Demonstration video
1927

@@ -37,7 +45,7 @@ And there is a list of my other projects on the [Home page](/).
3745

3846
### Performance comparison
3947

40-
Later, in response to player and other developers' feedback, I began optimizing the vanilla particle system. The current version of MadParticle now achieves a 10–20x performance improvement over the vanilla Minecraft implementation.
48+
In response to player and other developers' feedback, I began optimizing the vanilla particle system. The current version of MadParticle now achieves a 10–20x performance improvement over the vanilla Minecraft implementation.
4149

4250
<img src={require('./assets/portfolio_1.png').default} alt="MadParticle performance comparison"/>
4351

@@ -52,20 +60,17 @@ The MadParticle project has been under active development for three years, and t
5260

5361
> This image illustrates a major recent architectural overhaul of MadParticle compared to the vanilla system — shifting from a mainly main-thread architecture to an almost fully multithreaded one.
5462
55-
## *NeoInstancedRenderManager.java*
56-
57-
:::info
58-
59-
If intersted, the complete source code of the project is available at https://github.com/USS-Shenzhou/MadParticle (My online alias is USS_Shenzhou).
60-
61-
:::
63+
### *NeoInstancedRenderManager*
6264

6365
`NeoInstancedRenderManager` is a highly optimized particle rendering system primarily based on instanced rendering, with some specializations tailored to the Minecraft rendering pipeline.
6466

6567
Although instanced rendering significantly reduces the overall rendering time, filling the VBO still consumes substantial processing resources. To address this, parallel VBOs were introduced, where each thread is preassigned a starting address and a task of roughly equal size (see `MultiThreadedEqualObjectLinkedOpenHashSetQueue` below).
6668

6769
Additionally, a ring-based OpenGL `PersistentMappedBuffer` is employed to eliminate blocking during data uploads. Through these designs, this class maximizes hardware utilization (excluding hyper-threading considerations) and achieves performance far exceeding that of vanilla Minecraft.
6870

71+
<details>
72+
<summary>NeoInstancedRenderManager.java</summary>
73+
6974
```java
7075
// The import statements at the beginning of classes have been omitted.
7176

@@ -385,7 +390,9 @@ public class NeoInstancedRenderManager {
385390
}
386391
```
387392

388-
## *MultiThreadedEqualObjectLinkedOpenHashSetQueue.java*
393+
</details>
394+
395+
### *MultiThreadedEqualObjectLinkedOpenHashSetQueue*
389396

390397
`MultiThreadedEqualObjectLinkedOpenHashSetQueue` is a multithreaded container, essentially composed of an array of `ObjectLinkedOpenHashSet`.
391398

@@ -399,6 +406,9 @@ Specifically:
399406

400407
Building on these features, the container class described below was created to meet MadParticle’s high-performance processing needs.
401408

409+
<details>
410+
<summary>NeoInstancedRenderManager.java</summary>
411+
402412
```java
403413
// The import statements at the beginning of classes have been omitted.
404414

@@ -634,4 +644,184 @@ public class MultiThreadedEqualObjectLinkedOpenHashSetQueue<E> implements Queue<
634644
CompletableFuture.allOf(futures).join();
635645
}
636646
}
637-
```
647+
```
648+
649+
</details>
650+
651+
652+
## NotEnoughBandwidth
653+
654+
NotEnoughBandwidth (NEB) is a project focused on optimizing network traffic. In vanilla Minecraft, most packets carry less than 10 bytes of actual payload, while a large portion of bandwidth is wasted on TCP headers and application-layer headers. In areas where bandwidth is costly, the vanilla networking mechanism creates significant financial pressure for Minecraft server owners.
655+
656+
NEB aggregates game packets in short intervals, removes redundant headers, replaces string-based packet identifiers with integer indexes, and introduces improved ZSTD compression. These approaches greatly reduce the network bandwidth usage of Minecraft servers.
657+
658+
In the test dataset, NEB can reduce packet bandwidth consumption to 4.4% of vanilla Minecraft; even when compared with vanilla’s built-in gzip compression, it still uses only 11% of the original bandwidth.
659+
660+
| method | C2S(byte) | S2C(byte) | compress ratio (to raw) | compress ratio (to gzip) |
661+
|----------------------------------|-----------|-------------|-------------------------|--------------------------|
662+
| uncompressed | 1,202,270 | 526,080,229 | 100.00% | |
663+
| vanilla (gzip) | 1,187,810 | 209,674,132 | 39.99% | 100.00% |
664+
| ZSTD+packing | 938,366 | 44,497,817 | 8.62% | 21.55% |
665+
| aggregation+packing+ZSTD | 828,416 | 22,371,107 | 4.40% | 11.00% |
666+
| | | | | |
667+
| net package amount | 36,880 | 2,027,018 | 100.00% | |
668+
| net package amount (aggregated) | 28,484 | 38,949 | 3.27% | |
669+
670+
NEB has undergone multiple rounds of testing on real multiplayer servers. In production environments, the measured compression ratio typically fluctuates between 14% and 18%, fully achieving its intended design goals.
671+
672+
<p style={{textAlign:"center"}}>
673+
<img src={require('./assets/neb0.png').default} alt="NEB raw data" style={{zoom:0.8}} />
674+
</p>
675+
676+
<img src={require('./assets/neb1.png').default} alt="NEB log"/>
677+
678+
### *PacketAggregationPacket*
679+
680+
`PacketAggregationPacket.java` is one of the core components of NEB. By introducing a new type of network packet and implementing most relevant logic within it, NEB can perform network optimizations transparently with almost no impact on the behavior of other mods.
681+
682+
683+
<details>
684+
<summary>PacketAggregationPacket.java</summary>
685+
686+
```java
687+
/**
688+
* @author USS_Shenzhou
689+
*/
690+
@MethodsReturnNonnullByDefault
691+
public class PacketAggregationPacket implements CustomPacketPayload {
692+
public static final Type<PacketAggregationPacket> TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(ModConstants.MOD_ID, "packet_aggregation_packet"));
693+
694+
@Override
695+
public Type<? extends CustomPacketPayload> type() {
696+
return TYPE;
697+
}
698+
699+
private final Map<ResourceLocation, ArrayList<Packet<?>>> packets;
700+
private final FriendlyByteBuf buf;
701+
// only used for encode
702+
private ProtocolInfo<?> protocolInfo;
703+
704+
public PacketAggregationPacket(Map<ResourceLocation, ArrayList<Packet<?>>> packets, ProtocolInfo<?> protocolInfo) {
705+
this.packets = packets;
706+
this.buf = new FriendlyByteBuf(Unpooled.buffer());
707+
this.protocolInfo = protocolInfo;
708+
}
709+
710+
/**
711+
* <pre>
712+
* ┌---┬---┬---┬---┬----┬----┬----┬----┬-...-┬---┬---┬---┬----┬----┬----┬----┐
713+
* │ S │ b │ t │ n │ s0 │ d0 │ s1 │ d1 │ ... │ b │ t │ n │ s0 │ d0 │ s1 │ d1 │
714+
* └---┴---┴---┴---┴----┴----┴----┴----┴-...-┴---┴---┴---┴----┴----┴----┴----┘
715+
* └--------all packets of type A--------┘└-----all packets of type B----┘
716+
* └------------------------------compressed-----------------------------┘
717+
*
718+
* S = varint, size of compressed buf
719+
* b = bool, whether using indexed type
720+
* t = medium or ResLoc, type
721+
* n = varint, subpacket amount of this type
722+
* s = varint, size of this subpacket
723+
* d = bytes, data of this subpacket
724+
* </pre>
725+
*/
726+
public void encode(FriendlyByteBuf buffer) {
727+
FriendlyByteBuf rawBuf = new FriendlyByteBuf(ByteBufAllocator.DEFAULT.buffer());
728+
packets.forEach((tag, packets) -> {
729+
encodePackets(rawBuf, tag, packets);
730+
});
731+
var compressedBuf = new FriendlyByteBuf(ByteBufHelper.compress(rawBuf));
732+
logCompressRatio(rawBuf, compressedBuf);
733+
// S
734+
buffer.writeVarInt(rawBuf.readableBytes());
735+
buffer.writeBytes(compressedBuf);
736+
737+
rawBuf.release();
738+
compressedBuf.release();
739+
}
740+
741+
private static void logCompressRatio(FriendlyByteBuf rawBuf, FriendlyByteBuf compressedBuf) {
742+
if (ConfigHelper.getConfigRead(NotEnoughBandwidthConfig.class).debugLog) {
743+
LogUtils.getLogger().debug("Packet aggregation compressed: {} bytes-> {} bytes ( {} %).",
744+
rawBuf.readableBytes(),
745+
compressedBuf.readableBytes(),
746+
String.format("%.2f", 100f * compressedBuf.readableBytes() / rawBuf.readableBytes())
747+
);
748+
} else {
749+
LogUtils.getLogger().trace("Packet aggregation compressed: {} bytes-> {} bytes ( {} %).",
750+
rawBuf.readableBytes(),
751+
compressedBuf.readableBytes(),
752+
String.format("%.2f", 100f * compressedBuf.readableBytes() / rawBuf.readableBytes())
753+
);
754+
}
755+
}
756+
757+
private void encodePackets(FriendlyByteBuf raw, ResourceLocation type, Collection<Packet<?>> packets) {
758+
int nebIndex = NamespaceIndexManager.getNebIndexNotTight(type);
759+
// b, t
760+
if (nebIndex != 0) {
761+
raw.writeBoolean(true);
762+
raw.writeMedium(nebIndex);
763+
} else {
764+
raw.writeBoolean(false);
765+
raw.writeResourceLocation(type);
766+
}
767+
// n
768+
raw.writeVarInt(packets.size());
769+
for (var packet : packets) {
770+
encodePacket(raw, packet);
771+
}
772+
}
773+
774+
@SuppressWarnings({"unchecked", "rawtypes"})
775+
private void encodePacket(FriendlyByteBuf raw, Packet<?> packet) {
776+
var b = Unpooled.buffer();
777+
protocolInfo.codec().encode(b, (Packet) packet);
778+
// s
779+
raw.writeVarInt(b.readableBytes());
780+
// d
781+
raw.writeBytes(b);
782+
b.release();
783+
}
784+
785+
public PacketAggregationPacket(FriendlyByteBuf buffer) {
786+
this.protocolInfo = null;
787+
this.packets = new HashMap<>();
788+
// S
789+
int size = buffer.readVarInt();
790+
this.buf = new FriendlyByteBuf(ByteBufHelper.decompress(buffer.retainedDuplicate(), size));
791+
buffer.readerIndex(buffer.writerIndex());
792+
}
793+
794+
public void handler(IPayloadContext context) {
795+
this.protocolInfo = context.connection().getInboundProtocol();
796+
while (this.buf.readableBytes() > 0) {
797+
decodePackets(this.buf, context.listener());
798+
}
799+
this.buf.release();
800+
}
801+
802+
private void decodePackets(FriendlyByteBuf buf, ICommonPacketListener listener) {
803+
// b, t
804+
var type = buf.readBoolean()
805+
? NamespaceIndexManager.getResourceLocation(buf.readUnsignedMedium() & 0x00ffffff, false)
806+
: buf.readResourceLocation();
807+
// n
808+
var amount = buf.readVarInt();
809+
for (var i = 0; i < amount; i++) {
810+
decodePacket(buf, listener);
811+
}
812+
}
813+
814+
@SuppressWarnings("unchecked")
815+
private void decodePacket(FriendlyByteBuf buf, ICommonPacketListener listener) {
816+
// s
817+
var size = buf.readVarInt();
818+
// d
819+
var data = buf.readRetainedSlice(size);
820+
var packet = (Packet<ICommonPacketListener>) protocolInfo.codec().decode(data);
821+
packet.handle(listener);
822+
data.release();
823+
}
824+
}
825+
```
826+
827+
</details>

0 commit comments

Comments
 (0)