mirror of
https://github.com/GTNewHorizons/ForgeMultipart.git
synced 2026-11-13 17:04:25 +00:00
refactor: port multipart packet handlers
This commit is contained in:
1 parent
bed000cacf
commit
fdfd7763dd
10 files changed
+538
-257
No files matched your search
@@ -1043,3 +1043,12 @@ generated-trait compatibility work. Scala-runtime removal remains a later projec
|
||||
direct iterator loop.
|
||||
- All 191 plain-JVM and all 98 Java 8 Forge dedicated-server tests pass. This startup-only proxy is not a meaningful
|
||||
focused performance target; `multipart/handler/packethandlers.scala` is the last Scala handler unit and is next.
|
||||
- Characterized the ForgeMultipart packet-handler unit against untouched Scala. Eleven plain-JVM cases freeze all six
|
||||
emitted retained types, exact facade/companion interfaces, channel identity, private prefixed state accessors,
|
||||
ordered registry and desync disconnects, control-key packets, coordinate streams and both update terminators.
|
||||
- Ported the five top-level types and nested byte stream directly to Java. The three Scala mutable collection
|
||||
descriptors remain where reflection already observes them, while direct loops replace the anonymous `MultiMap` and
|
||||
thirteen closure classes without changing watcher, batching, framing or cleanup behavior.
|
||||
- A clean build passes all 202 plain-JVM tests and the Java 8 Forge server passes all 98 tests. All six retained public
|
||||
surfaces match the reference by name and descriptor; only fourteen unreferenced compiler artifacts disappear.
|
||||
`microblock/ItemMicroPart.scala` is next.
|
||||
@@ -2257,3 +2257,61 @@ private implementation class.
|
||||
names/descriptors and side annotations. The sole class-list removal is the private closure above.
|
||||
- Item-renderer registration, material icon loading, client packet registration and Angelica integration require a
|
||||
client and remain on the manual checklist.
|
||||
|
||||
## 2026-09-01 — ForgeMultipart packet-handler Java port
|
||||
|
||||
The shared packet-handler base, both client/server facade-companion pairs and the nested byte stream are now Java.
|
||||
The multipart proxy continues to register `MultipartCPH$.MODULE$` and `MultipartSPH$.MODULE$`, preserving the exact
|
||||
singleton objects CodeChickenLib receives.
|
||||
|
||||
### Observable behavior
|
||||
|
||||
No known runtime divergence. The ordinary channel remains `MultipartMod$.MODULE$` and the registry channel remains
|
||||
`ForgeMultipart`. Registry packet type 1 still replaces the client ID map and disconnects with translation key
|
||||
`multipart.missing` plus every missing name in server order. Client desynchronization exceptions beginning `DC: `
|
||||
still become a plain-text disconnect with that marker removed; other runtime exceptions still escape unchanged.
|
||||
|
||||
Chunk-description packet type 2 still reads chunk coordinates, an unsigned tile count, signed local x/z bytes and
|
||||
each tile description in sequence. Update packet type 3 still reads absolute coordinates until `Integer.MAX_VALUE`
|
||||
and part updates until unsigned byte 255. The server callback still maps packet type 1 to the sender's control-key
|
||||
state, and unknown packet types on either side still throw `scala.MatchError`.
|
||||
|
||||
Server handshakes still send the complete part-ID map on packet type 1. Tile streams remain cached by world and
|
||||
position, begin with the absolute coordinate, and reject client worlds before publishing state. World unload still
|
||||
removes only server state. End-of-tick processing still flushes scheduled packets first, sends accumulated updates
|
||||
only to players watching each chunk, clears every per-world update map, sends descriptions for newly watched chunks,
|
||||
promotes those watches and finally clears the pending-watch map. Description packets retain their exact logical
|
||||
framing and deferred CodeChickenLib compression.
|
||||
|
||||
The direct Java loops avoid the closure objects previously created by the Scala comprehensions. No focused profile
|
||||
claim is made because the current functional profiling fixture does not generate sustained watched-chunk multipart
|
||||
traffic; a representative full-pack capture remains the authority.
|
||||
|
||||
### ABI and source compatibility
|
||||
|
||||
`MultipartPH` retains its public constructor, `MultipartMod$`-typed `channel` field/accessor and registry-channel
|
||||
accessor. `MultipartCPH` and `MultipartSPH` retain every static forwarder. Their companions retain `MODULE$`, extend
|
||||
`MultipartPH`, implement the same CodeChickenLib interfaces in the same order, and expose the same instance methods.
|
||||
`MultipartSPH$` also retains the exact private prefixed `updateMap`, `chunkWatchers` and `newWatchers` fields and their
|
||||
public mangled accessors with Scala `Map`/`HashMap` descriptors.
|
||||
|
||||
`MultipartSPH.MCByteStream` remains a public, non-final static member of the facade, so its binary name is still
|
||||
`MultipartSPH$MCByteStream`. It retains the `(ByteArrayOutputStream)` constructor, private final `bout` field,
|
||||
`MCDataOutputWrapper` superclass and `getBytes()[B`. No audited shipping consumer references any handler type.
|
||||
|
||||
Both jars retain these six runtime classes. Accepted classfile-only differences are removed Scala signature/marker
|
||||
attributes, private constructors on the static facades, companion class initializers without Scala's `ACC_PUBLIC`
|
||||
flag, and removal of the anonymous `MultiMap` plus thirteen `$$anonfun$` classes. The fourteen removed classes are
|
||||
private compiler artifacts with no downstream reference.
|
||||
|
||||
### Validation
|
||||
|
||||
- `MultipartPacketHandlerCharacterizationTest` and `MultipartPacketHandlerBehaviorTest`: 11 plain-JVM tests,
|
||||
unchanged from the Scala baseline.
|
||||
- Existing `MultipartEventHandlerFunctionalTest` and `MultipartHelperFunctionalTest` cases retain unload cleanup,
|
||||
watcher/tick ordering and exact logical chunk-description framing.
|
||||
- Clean complete plain-JVM suite: 202 tests, 0 failures, 0 errors.
|
||||
- Java 8 Forge dedicated-server suite: 98 tests, 0 failures, 0 errors.
|
||||
- Raw `javap` comparison confirms identical callable public names/descriptors for all six retained classes. The exact
|
||||
class-list diff contains only the fourteen compiler artifacts described above.
|
||||
- Real client chunk-description application, update dispatch and rendering remain on the manual checklist.
|
||||
+18
-14
@@ -12,8 +12,8 @@ what breaks, and what is left. The other documents hold the reasoning.
|
||||
| `JAVA_MIGRATION_MANUAL_CHECKS.md` | What no automated test can cover, and must be checked by hand in a client |
|
||||
| `JAVA_MIGRATION_PROFILE.md` | The focused baseline, first measured result, findings, and exact rerun command |
|
||||
|
||||
Branch: `algent/java`. Base: `master`. 111 commits including the API-cleanup plan and separate characterization and
|
||||
port commits for both proxy hierarchies.
|
||||
Branch: `algent/java`. Base: `master`. 113 commits including the API-cleanup plan and separate characterization and
|
||||
port commits for both proxy hierarchies and the multipart packet-handler unit.
|
||||
|
||||
## The one rule that matters
|
||||
|
||||
@@ -67,7 +67,7 @@ awk -F'"' '/<testsuite /{t+=$4;f+=$8;e+=$10} END{print "tests="t" failures="f" e
|
||||
grep -o 'tests="[0-9]*" skipped="[0-9]*" failures="[0-9]*" errors="[0-9]*"' run/server/junit-out/TEST-*.xml
|
||||
```
|
||||
|
||||
Current baseline: **191 plain-JVM tests and 98 Java 8 Forge dedicated-server tests passing.** The ignored local server
|
||||
Current baseline: **202 plain-JVM tests and 98 Java 8 Forge dedicated-server tests passing.** The ignored local server
|
||||
EULA is accepted in this checkout.
|
||||
|
||||
### ABI diff against the reference
|
||||
@@ -249,8 +249,8 @@ All eight load-bearing `$class` helpers from the inventory, both registries, and
|
||||
`TRedstoneTile`, `TTileChangeTile`, `TFluidHandlerTile`, `TIInventoryTile`/`JInventoryTile`, `TileMultipartClient`, and
|
||||
`TRandomDisplayTickTile` Java-trait ports, plus `MultipartCompatiblity`/`MCPCCompatModule`, `MultipartMod`,
|
||||
`MultipartEventHandler`, `MicroblockMod`, `MicroblockEventHandler`, and the complete `MicroblockPH`/
|
||||
`MicroblockCPH`/`MicroblockSPH` packet-handler unit, plus `MultipartSaveLoad`, `MissingMicroMaterial`,
|
||||
`DefaultContent`, and `GrassMicroMaterial`/`TopMicroMaterial`.
|
||||
`MicroblockCPH`/`MicroblockSPH` and `MultipartPH`/`MultipartCPH`/`MultipartSPH` packet-handler units, plus
|
||||
`MultipartSaveLoad`, `MissingMicroMaterial`, `DefaultContent`, and `GrassMicroMaterial`/`TopMicroMaterial`.
|
||||
The complete `MultipartProxy` and `MicroblockProxy` server/client hierarchies and static facades are also Java.
|
||||
|
||||
Plus the six marker interfaces: `TSlottedPart`, `IRandomDisplayTick`, `INeighborTileChange`, `TRandomUpdateTick`,
|
||||
@@ -266,7 +266,7 @@ across the port. It is also where the two shim constraints in the gotchas list w
|
||||
`rayTraceAll`'s index production is now characterized, closing the gap the read-path cleanup left: the index written
|
||||
into `ExtendedMOP.data` is what `reduceMOP` hands back to every click, activate, harvest and pick block.
|
||||
|
||||
125 Java files, 26 Scala files, ~4,519 Scala lines left (non-blank; that is the metric this figure has always used).
|
||||
130 Java files, 25 Scala files, ~4,295 Scala lines left (non-blank; that is the metric this figure has always used).
|
||||
|
||||
## What is left, and in what order
|
||||
|
||||
@@ -415,19 +415,23 @@ field-only side annotation; Forge strips the two lifecycle overrides and facade
|
||||
falls back to the server implementation. Actual item-renderer registration, client packets and Angelica integration
|
||||
remain on the manual checklist.
|
||||
|
||||
**Medium.** The remaining multipart packet handler, `ItemMicroPart`,
|
||||
`MicroblockPlacement`, `PlacementGrids`, `BlockMicroMaterial`, `ConfigContent`,
|
||||
and the remaining material/render units.
|
||||
The ForgeMultipart packet-handler unit is now Java. Its shared base, both static facades, both companion singletons,
|
||||
nested byte stream, `MultipartMod$` channel descriptor and all three mangled state accessors retain their exact public
|
||||
or reflective shapes. Eleven plain-JVM tests freeze the ABI, ordered registry disconnect, desync disconnect, control
|
||||
packet, coordinate stream and both update terminators; existing Forge tests retain unload cleanup, deferred watcher
|
||||
promotion and exact chunk-description framing. Direct loops replace fourteen unreferenced Scala closure/anonymous
|
||||
classes. Real client description/update application remains on the manual checklist.
|
||||
|
||||
**Medium.** `ItemMicroPart`, `MicroblockPlacement`, `PlacementGrids`, `BlockMicroMaterial`, `ConfigContent`, and the
|
||||
remaining material/render units.
|
||||
|
||||
**Phase 5 is complete.** There are no Scala files left in `multipart/scalatraits/`. The client pair required the first
|
||||
narrow generator relaxation: Java-trait parent linearization, explicit field-accessor recognition, and exclusion of
|
||||
transient runtime caches from generated copying.
|
||||
|
||||
Take `multipart/handler/packethandlers.scala` next. At 224 nonblank lines it is the last Scala file in either handler
|
||||
package and owns central registry, tile-description, tile-update and chunk-watch traffic. Freeze all facade/companion
|
||||
interfaces, the `MultipartMod$.MODULE$` channel descriptor, private prefixed watcher/update-map accessors already used
|
||||
by tests, disconnect behavior, packet framing/terminators, unload cleanup and watch/tick ordering. Keep real client
|
||||
decode/render integration on the manual checklist, and keep the characterization and port commits separate.
|
||||
Take `microblock/ItemMicroPart.scala` next. It is the smallest remaining self-contained medium-risk gameplay unit and
|
||||
owns item placement, creative subitems, names/icons and microblock recipe lookup. Freeze its class hierarchy, public
|
||||
surface, placement dispatch, shape/material damage packing and client-only icon/subitem behavior before porting it.
|
||||
|
||||
**Phase 6/7, last.** `Microblock` and the microblock shape hierarchy, `MicroblockGenerator`, `MultipartGenerator`, and
|
||||
all of `multipart/asm/`. The ASM subsystem should be last; freeze generated-class fixtures before touching it.
|
||||
|
||||
@@ -23,6 +23,7 @@ release that includes those ports.
|
||||
| Change a mixed multipart tile containing static and dynamically rendered parts | Static parts remain in the block render, dynamic parts remain in the TESR pass, and neither disappears after a part update | `TileMultipartClient` render caches |
|
||||
| Watch a multipart torch long enough to emit display particles | Only parts implementing `IRandomDisplayTick` emit particles, with no duplicate or missing callbacks | `TRandomDisplayTickTile.randomDisplayTick` |
|
||||
| Start a client, join a server, place/update a multipart and use the multipart control key | Block and generated-tile rendering work, client packets arrive, and control-key state changes are sent once without duplicate registrations | `MultipartProxy_clientImpl.postInit` / `onTileClassBuilt` |
|
||||
| Move into view of a chunk containing several multipart tiles, then add, update and remove parts | The initial chunk description reconstructs every tile and compressed updates apply without missing, duplicated or ghost parts | `MultipartCPH` / `MultipartSPH` |
|
||||
| Start a client with 3D saws enabled, inspect all three saw tiers and the microblock item, then repeat with Angelica installed | Each renderer is registered once, saw models use the correct tier, microblock icons load, client packets register, and the Angelica hook initializes without errors | `MicroblockProxy_clientImpl.init` / `postInit` |
|
||||
|
||||
## Placement and interaction
|
||||
@@ -50,6 +51,7 @@ release that includes those ports.
|
||||
| Inspect a mixed multipart tile with Waila | Providers receive every saved part ID and show the expected part data | reflected block identity and tile NBT `parts`/`id` layout |
|
||||
| Load a world saved before the port | All microblocks and covers keep their material and shape | `MicroMaterialRegistry` id map |
|
||||
| Join a server whose material set differs from the client | The client is disconnected with the missing material list, not a crash | `readIDMap` |
|
||||
| Join a server whose multipart type set differs from the client | The client is disconnected with the ordered missing-part list, not a crash | `MultipartCPH.handlePartRegistration` |
|
||||
|
||||
## Not yet covered anywhere
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package codechicken.multipart.handler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.play.INetHandlerPlayClient;
|
||||
import net.minecraft.network.play.server.S40PacketDisconnect;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
import net.minecraft.util.ChatComponentTranslation;
|
||||
import net.minecraft.world.ChunkCoordIntPair;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import codechicken.lib.packet.PacketCustom;
|
||||
import codechicken.lib.packet.PacketCustom.IClientPacketHandler;
|
||||
import codechicken.lib.vec.BlockCoord;
|
||||
import codechicken.multipart.MultiPartRegistry;
|
||||
import codechicken.multipart.TileMultipart;
|
||||
import scala.MatchError;
|
||||
|
||||
public final class MultipartCPH$ extends MultipartPH implements IClientPacketHandler {
|
||||
|
||||
public static final MultipartCPH$ MODULE$ = new MultipartCPH$();
|
||||
|
||||
private MultipartCPH$() {}
|
||||
|
||||
@Override
|
||||
public void handlePacket(PacketCustom packet, Minecraft minecraft, INetHandlerPlayClient netHandler) {
|
||||
try {
|
||||
int type = packet.getType();
|
||||
switch (type) {
|
||||
case 1:
|
||||
handlePartRegistration(packet, netHandler);
|
||||
return;
|
||||
case 2:
|
||||
handleCompressedTileDesc(packet, minecraft.theWorld);
|
||||
return;
|
||||
case 3:
|
||||
handleCompressedTileData(packet, minecraft.theWorld);
|
||||
return;
|
||||
default:
|
||||
throw new MatchError(type);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
if (e.getMessage() != null && e.getMessage().startsWith("DC: ")) {
|
||||
netHandler
|
||||
.handleDisconnect(new S40PacketDisconnect(new ChatComponentText(e.getMessage().substring(4))));
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public void handlePartRegistration(PacketCustom packet, INetHandlerPlayClient netHandler) {
|
||||
List<String> missing = MultiPartRegistry.readIDMap(packet);
|
||||
if (!missing.isEmpty()) {
|
||||
netHandler.handleDisconnect(
|
||||
new S40PacketDisconnect(
|
||||
new ChatComponentTranslation("multipart.missing", String.join(", ", missing))));
|
||||
}
|
||||
}
|
||||
|
||||
public void handleCompressedTileDesc(PacketCustom packet, World world) {
|
||||
ChunkCoordIntPair chunk = new ChunkCoordIntPair(packet.readInt(), packet.readInt());
|
||||
int count = packet.readUShort();
|
||||
for (int i = 0; i < count; i++) {
|
||||
TileMultipart.handleDescPacket(
|
||||
world,
|
||||
new BlockCoord(
|
||||
packet.readByte() + (chunk.chunkXPos << 4),
|
||||
packet.readInt(),
|
||||
packet.readByte() + (chunk.chunkZPos << 4)),
|
||||
packet);
|
||||
}
|
||||
}
|
||||
|
||||
public void handleCompressedTileData(PacketCustom packet, World world) {
|
||||
int x = packet.readInt();
|
||||
while (x != Integer.MAX_VALUE) {
|
||||
BlockCoord position = new BlockCoord(x, packet.readInt(), packet.readInt());
|
||||
int part = packet.readUByte();
|
||||
while (part < 255) {
|
||||
TileMultipart.handlePacket(position, world, part, packet);
|
||||
part = packet.readUByte();
|
||||
}
|
||||
x = packet.readInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package codechicken.multipart.handler;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.play.INetHandlerPlayClient;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import codechicken.lib.packet.PacketCustom;
|
||||
|
||||
public final class MultipartCPH {
|
||||
|
||||
private MultipartCPH() {}
|
||||
|
||||
public static MultipartMod$ channel() {
|
||||
return MultipartCPH$.MODULE$.channel();
|
||||
}
|
||||
|
||||
public static String registryChannel() {
|
||||
return MultipartCPH$.MODULE$.registryChannel();
|
||||
}
|
||||
|
||||
public static void handlePacket(PacketCustom packet, Minecraft minecraft, INetHandlerPlayClient netHandler) {
|
||||
MultipartCPH$.MODULE$.handlePacket(packet, minecraft, netHandler);
|
||||
}
|
||||
|
||||
public static void handlePartRegistration(PacketCustom packet, INetHandlerPlayClient netHandler) {
|
||||
MultipartCPH$.MODULE$.handlePartRegistration(packet, netHandler);
|
||||
}
|
||||
|
||||
public static void handleCompressedTileDesc(PacketCustom packet, World world) {
|
||||
MultipartCPH$.MODULE$.handleCompressedTileDesc(packet, world);
|
||||
}
|
||||
|
||||
public static void handleCompressedTileData(PacketCustom packet, World world) {
|
||||
MultipartCPH$.MODULE$.handleCompressedTileData(packet, world);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package codechicken.multipart.handler;
|
||||
|
||||
public class MultipartPH {
|
||||
|
||||
private final MultipartMod$ channel = MultipartMod$.MODULE$;
|
||||
private final String registryChannel = "ForgeMultipart";
|
||||
|
||||
public MultipartMod$ channel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public String registryChannel() {
|
||||
return registryChannel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package codechicken.multipart.handler;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.network.NetHandlerPlayServer;
|
||||
import net.minecraft.network.play.INetHandlerPlayServer;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.world.ChunkCoordIntPair;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
|
||||
import codechicken.lib.packet.PacketCustom;
|
||||
import codechicken.lib.packet.PacketCustom.IHandshakeHandler;
|
||||
import codechicken.lib.packet.PacketCustom.IServerPacketHandler;
|
||||
import codechicken.lib.vec.BlockCoord;
|
||||
import codechicken.multipart.ControlKeyModifer;
|
||||
import codechicken.multipart.MultiPartRegistry;
|
||||
import codechicken.multipart.PacketScheduler;
|
||||
import codechicken.multipart.TileMultipart;
|
||||
import scala.MatchError;
|
||||
import scala.Option;
|
||||
import scala.Tuple2;
|
||||
import scala.collection.mutable.HashMap;
|
||||
import scala.collection.mutable.HashSet;
|
||||
import scala.collection.mutable.Map;
|
||||
import scala.collection.mutable.Set;
|
||||
|
||||
public final class MultipartSPH$ extends MultipartPH implements IServerPacketHandler, IHandshakeHandler {
|
||||
|
||||
public static final MultipartSPH$ MODULE$ = new MultipartSPH$();
|
||||
|
||||
private final Map<World, Map<BlockCoord, MultipartSPH.MCByteStream>> codechicken$multipart$handler$MultipartSPH$$updateMap = new HashMap<>();
|
||||
private final HashMap<Object, Set<ChunkCoordIntPair>> codechicken$multipart$handler$MultipartSPH$$chunkWatchers = new HashMap<>();
|
||||
private final Map<Object, LinkedList<ChunkCoordIntPair>> codechicken$multipart$handler$MultipartSPH$$newWatchers = new HashMap<>();
|
||||
|
||||
private MultipartSPH$() {}
|
||||
|
||||
public Map<World, Map<BlockCoord, MultipartSPH.MCByteStream>> codechicken$multipart$handler$MultipartSPH$$updateMap() {
|
||||
return codechicken$multipart$handler$MultipartSPH$$updateMap;
|
||||
}
|
||||
|
||||
public HashMap<Object, Set<ChunkCoordIntPair>> codechicken$multipart$handler$MultipartSPH$$chunkWatchers() {
|
||||
return codechicken$multipart$handler$MultipartSPH$$chunkWatchers;
|
||||
}
|
||||
|
||||
public Map<Object, LinkedList<ChunkCoordIntPair>> codechicken$multipart$handler$MultipartSPH$$newWatchers() {
|
||||
return codechicken$multipart$handler$MultipartSPH$$newWatchers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePacket(PacketCustom packet, EntityPlayerMP sender, INetHandlerPlayServer netHandler) {
|
||||
int type = packet.getType();
|
||||
if (type == 1) {
|
||||
ControlKeyModifer.map().put(sender, packet.readBoolean());
|
||||
return;
|
||||
}
|
||||
throw new MatchError(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handshakeRecieved(NetHandlerPlayServer netHandler) {
|
||||
PacketCustom packet = new PacketCustom(registryChannel(), 1);
|
||||
MultiPartRegistry.writeIDMap(packet);
|
||||
netHandler.sendPacket(packet.toPacket());
|
||||
}
|
||||
|
||||
public void onWorldUnload(World world) {
|
||||
if (!world.isRemote) {
|
||||
codechicken$multipart$handler$MultipartSPH$$updateMap.remove(world);
|
||||
}
|
||||
}
|
||||
|
||||
public MultipartSPH.MCByteStream getTileStream(World world, BlockCoord position) {
|
||||
Option<Map<BlockCoord, MultipartSPH.MCByteStream>> worldOption = codechicken$multipart$handler$MultipartSPH$$updateMap
|
||||
.get(world);
|
||||
Map<BlockCoord, MultipartSPH.MCByteStream> worldUpdates;
|
||||
if (worldOption.isEmpty()) {
|
||||
if (world.isRemote) {
|
||||
throw new IllegalArgumentException("Cannot use MultipartSPH on a client world");
|
||||
}
|
||||
worldUpdates = new HashMap<>();
|
||||
codechicken$multipart$handler$MultipartSPH$$updateMap.put(world, worldUpdates);
|
||||
} else {
|
||||
worldUpdates = worldOption.get();
|
||||
}
|
||||
|
||||
Option<MultipartSPH.MCByteStream> streamOption = worldUpdates.get(position);
|
||||
if (!streamOption.isEmpty()) {
|
||||
return streamOption.get();
|
||||
}
|
||||
|
||||
MultipartSPH.MCByteStream stream = new MultipartSPH.MCByteStream(new ByteArrayOutputStream());
|
||||
stream.writeCoord(position);
|
||||
worldUpdates.put(position, stream);
|
||||
return stream;
|
||||
}
|
||||
|
||||
public void onTickEnd(scala.collection.Seq<EntityPlayerMP> players) {
|
||||
PacketScheduler.sendScheduled();
|
||||
|
||||
scala.collection.Iterator<EntityPlayerMP> playerIterator = players.iterator();
|
||||
while (playerIterator.hasNext()) {
|
||||
EntityPlayerMP player = playerIterator.next();
|
||||
Object playerId = player.getEntityId();
|
||||
Option<Set<ChunkCoordIntPair>> chunkOption = codechicken$multipart$handler$MultipartSPH$$chunkWatchers
|
||||
.get(playerId);
|
||||
if (chunkOption.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Option<Map<BlockCoord, MultipartSPH.MCByteStream>> updatesOption = codechicken$multipart$handler$MultipartSPH$$updateMap
|
||||
.get(player.worldObj);
|
||||
if (updatesOption.isEmpty() || updatesOption.get().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Set<ChunkCoordIntPair> chunks = chunkOption.get();
|
||||
PacketCustom packet = new PacketCustom(channel(), 3).compress();
|
||||
boolean send = false;
|
||||
scala.collection.Iterator<Tuple2<BlockCoord, MultipartSPH.MCByteStream>> updateIterator = updatesOption
|
||||
.get().iterator();
|
||||
while (updateIterator.hasNext()) {
|
||||
Tuple2<BlockCoord, MultipartSPH.MCByteStream> update = updateIterator.next();
|
||||
BlockCoord position = update._1();
|
||||
if (chunks.contains(new ChunkCoordIntPair(position.x >> 4, position.z >> 4))) {
|
||||
send = true;
|
||||
packet.writeByteArray(update._2().getBytes());
|
||||
packet.writeByte(255);
|
||||
}
|
||||
}
|
||||
if (send) {
|
||||
packet.writeInt(Integer.MAX_VALUE);
|
||||
packet.sendToPlayer(player);
|
||||
}
|
||||
}
|
||||
|
||||
scala.collection.Iterator<Tuple2<World, Map<BlockCoord, MultipartSPH.MCByteStream>>> worldIterator = codechicken$multipart$handler$MultipartSPH$$updateMap
|
||||
.iterator();
|
||||
while (worldIterator.hasNext()) {
|
||||
worldIterator.next()._2().clear();
|
||||
}
|
||||
|
||||
playerIterator = players.iterator();
|
||||
while (playerIterator.hasNext()) {
|
||||
EntityPlayerMP player = playerIterator.next();
|
||||
Object playerId = player.getEntityId();
|
||||
Option<LinkedList<ChunkCoordIntPair>> chunksOption = codechicken$multipart$handler$MultipartSPH$$newWatchers
|
||||
.get(playerId);
|
||||
if (chunksOption.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (ChunkCoordIntPair chunkPosition : chunksOption.get()) {
|
||||
Chunk chunk = player.worldObj.getChunkFromChunkCoords(chunkPosition.chunkXPos, chunkPosition.chunkZPos);
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<TileEntity> tiles = ((java.util.Map<Object, TileEntity>) chunk.chunkTileEntityMap).values()
|
||||
.iterator();
|
||||
PacketCustom packet = getDescPacket(chunk, tiles);
|
||||
if (packet != null) {
|
||||
packet.sendToPlayer(player);
|
||||
}
|
||||
addChunkWatcher(playerId, chunkPosition);
|
||||
}
|
||||
}
|
||||
codechicken$multipart$handler$MultipartSPH$$newWatchers.clear();
|
||||
}
|
||||
|
||||
public void onChunkWatch(EntityPlayer player, ChunkCoordIntPair chunk) {
|
||||
Object playerId = player.getEntityId();
|
||||
Option<LinkedList<ChunkCoordIntPair>> option = codechicken$multipart$handler$MultipartSPH$$newWatchers
|
||||
.get(playerId);
|
||||
LinkedList<ChunkCoordIntPair> chunks;
|
||||
if (option.isEmpty()) {
|
||||
chunks = new LinkedList<>();
|
||||
codechicken$multipart$handler$MultipartSPH$$newWatchers.put(playerId, chunks);
|
||||
} else {
|
||||
chunks = option.get();
|
||||
}
|
||||
chunks.add(chunk);
|
||||
}
|
||||
|
||||
public void onChunkUnWatch(EntityPlayer player, ChunkCoordIntPair chunk) {
|
||||
Object playerId = player.getEntityId();
|
||||
Option<LinkedList<ChunkCoordIntPair>> pending = codechicken$multipart$handler$MultipartSPH$$newWatchers
|
||||
.get(playerId);
|
||||
if (!pending.isEmpty()) {
|
||||
pending.get().remove(chunk);
|
||||
}
|
||||
|
||||
Option<Set<ChunkCoordIntPair>> active = codechicken$multipart$handler$MultipartSPH$$chunkWatchers.get(playerId);
|
||||
if (!active.isEmpty()) {
|
||||
Set<ChunkCoordIntPair> chunks = active.get();
|
||||
chunks.remove(chunk);
|
||||
if (chunks.isEmpty()) {
|
||||
codechicken$multipart$handler$MultipartSPH$$chunkWatchers.remove(playerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PacketCustom getDescPacket(Chunk chunk, Iterator<TileEntity> tiles) {
|
||||
MultipartSPH.MCByteStream stream = new MultipartSPH.MCByteStream(new ByteArrayOutputStream());
|
||||
int count = 0;
|
||||
while (tiles.hasNext()) {
|
||||
TileEntity tile = tiles.next();
|
||||
if (tile instanceof TileMultipart) {
|
||||
stream.writeByte(tile.xCoord & 0xF);
|
||||
stream.writeInt(tile.yCoord);
|
||||
stream.writeByte(tile.zCoord & 0xF);
|
||||
((TileMultipart) tile).writeDesc(stream);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count == 0) {
|
||||
return null;
|
||||
}
|
||||
return new PacketCustom(channel(), 2).compress().writeInt(chunk.xPosition).writeInt(chunk.zPosition)
|
||||
.writeShort(count).writeByteArray(stream.getBytes());
|
||||
}
|
||||
|
||||
private void addChunkWatcher(Object playerId, ChunkCoordIntPair chunk) {
|
||||
Option<Set<ChunkCoordIntPair>> option = codechicken$multipart$handler$MultipartSPH$$chunkWatchers.get(playerId);
|
||||
Set<ChunkCoordIntPair> chunks;
|
||||
if (option.isEmpty()) {
|
||||
chunks = new HashSet<>();
|
||||
codechicken$multipart$handler$MultipartSPH$$chunkWatchers.put(playerId, chunks);
|
||||
} else {
|
||||
chunks = option.get();
|
||||
}
|
||||
chunks.add(chunk);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package codechicken.multipart.handler;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.util.Iterator;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.network.NetHandlerPlayServer;
|
||||
import net.minecraft.network.play.INetHandlerPlayServer;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.world.ChunkCoordIntPair;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
|
||||
import codechicken.lib.data.MCDataOutputWrapper;
|
||||
import codechicken.lib.packet.PacketCustom;
|
||||
import codechicken.lib.vec.BlockCoord;
|
||||
|
||||
public final class MultipartSPH {
|
||||
|
||||
private MultipartSPH() {}
|
||||
|
||||
public static class MCByteStream extends MCDataOutputWrapper {
|
||||
|
||||
private final ByteArrayOutputStream bout;
|
||||
|
||||
public MCByteStream(ByteArrayOutputStream bout) {
|
||||
super(new DataOutputStream(bout));
|
||||
this.bout = bout;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bout.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static MultipartMod$ channel() {
|
||||
return MultipartSPH$.MODULE$.channel();
|
||||
}
|
||||
|
||||
public static String registryChannel() {
|
||||
return MultipartSPH$.MODULE$.registryChannel();
|
||||
}
|
||||
|
||||
public static void handlePacket(PacketCustom packet, EntityPlayerMP sender, INetHandlerPlayServer netHandler) {
|
||||
MultipartSPH$.MODULE$.handlePacket(packet, sender, netHandler);
|
||||
}
|
||||
|
||||
public static void handshakeRecieved(NetHandlerPlayServer netHandler) {
|
||||
MultipartSPH$.MODULE$.handshakeRecieved(netHandler);
|
||||
}
|
||||
|
||||
public static void onWorldUnload(World world) {
|
||||
MultipartSPH$.MODULE$.onWorldUnload(world);
|
||||
}
|
||||
|
||||
public static MCByteStream getTileStream(World world, BlockCoord position) {
|
||||
return MultipartSPH$.MODULE$.getTileStream(world, position);
|
||||
}
|
||||
|
||||
public static void onTickEnd(scala.collection.Seq<EntityPlayerMP> players) {
|
||||
MultipartSPH$.MODULE$.onTickEnd(players);
|
||||
}
|
||||
|
||||
public static void onChunkWatch(EntityPlayer player, ChunkCoordIntPair chunk) {
|
||||
MultipartSPH$.MODULE$.onChunkWatch(player, chunk);
|
||||
}
|
||||
|
||||
public static void onChunkUnWatch(EntityPlayer player, ChunkCoordIntPair chunk) {
|
||||
MultipartSPH$.MODULE$.onChunkUnWatch(player, chunk);
|
||||
}
|
||||
|
||||
public static PacketCustom getDescPacket(Chunk chunk, Iterator<TileEntity> tiles) {
|
||||
return MultipartSPH$.MODULE$.getDescPacket(chunk, tiles);
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
package codechicken.multipart.handler
|
||||
|
||||
import codechicken.lib.packet.PacketCustom.{
|
||||
IHandshakeHandler,
|
||||
IClientPacketHandler,
|
||||
IServerPacketHandler
|
||||
}
|
||||
import codechicken.lib.packet.PacketCustom
|
||||
import net.minecraft.client.Minecraft
|
||||
import codechicken.multipart.MultiPartRegistry
|
||||
import net.minecraft.entity.player.EntityPlayerMP
|
||||
import codechicken.multipart.ControlKeyModifer
|
||||
import net.minecraft.world.World
|
||||
import net.minecraft.world.chunk.Chunk
|
||||
import java.util.{Map => JMap}
|
||||
import java.util.Iterator
|
||||
import net.minecraft.tileentity.TileEntity
|
||||
import codechicken.multipart.TileMultipart
|
||||
import net.minecraft.entity.player.EntityPlayer
|
||||
import scala.collection.mutable.{Map, Set, HashMap, MultiMap}
|
||||
import codechicken.lib.vec.BlockCoord
|
||||
import codechicken.lib.data.MCDataOutputWrapper
|
||||
import java.io.DataOutputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import net.minecraft.world.ChunkCoordIntPair
|
||||
import codechicken.multipart.PacketScheduler
|
||||
import java.util.LinkedList
|
||||
import scala.collection.JavaConversions._
|
||||
import net.minecraft.network.play.{INetHandlerPlayServer, INetHandlerPlayClient}
|
||||
import net.minecraft.network.play.server.S40PacketDisconnect
|
||||
import net.minecraft.util.{ChatComponentText, ChatComponentTranslation}
|
||||
import net.minecraft.network.NetHandlerPlayServer
|
||||
|
||||
class MultipartPH {
|
||||
val channel = MultipartMod$.MODULE$
|
||||
val registryChannel = "ForgeMultipart"
|
||||
}
|
||||
|
||||
object MultipartCPH extends MultipartPH with IClientPacketHandler {
|
||||
def handlePacket(
|
||||
packet: PacketCustom,
|
||||
mc: Minecraft,
|
||||
netHandler: INetHandlerPlayClient
|
||||
) {
|
||||
try {
|
||||
packet.getType match {
|
||||
case 1 => handlePartRegistration(packet, netHandler)
|
||||
case 2 => handleCompressedTileDesc(packet, mc.theWorld)
|
||||
case 3 => handleCompressedTileData(packet, mc.theWorld)
|
||||
}
|
||||
} catch {
|
||||
case e: RuntimeException
|
||||
if e.getMessage != null && e.getMessage.startsWith("DC: ") =>
|
||||
netHandler.handleDisconnect(
|
||||
new S40PacketDisconnect(
|
||||
new ChatComponentText(e.getMessage.substring(4))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def handlePartRegistration(
|
||||
packet: PacketCustom,
|
||||
netHandler: INetHandlerPlayClient
|
||||
) {
|
||||
val missing = MultiPartRegistry.readIDMap(packet)
|
||||
if (!missing.isEmpty)
|
||||
netHandler.handleDisconnect(
|
||||
new S40PacketDisconnect(
|
||||
new ChatComponentTranslation(
|
||||
"multipart.missing",
|
||||
missing.mkString(", ")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def handleCompressedTileDesc(packet: PacketCustom, world: World) {
|
||||
val cc = new ChunkCoordIntPair(packet.readInt, packet.readInt)
|
||||
val num = packet.readUShort
|
||||
for (i <- 0 until num)
|
||||
TileMultipart.handleDescPacket(
|
||||
world,
|
||||
new BlockCoord(
|
||||
packet.readByte + (cc.chunkXPos << 4),
|
||||
packet.readInt,
|
||||
packet.readByte + (cc.chunkZPos << 4)
|
||||
),
|
||||
packet
|
||||
)
|
||||
}
|
||||
|
||||
def handleCompressedTileData(packet: PacketCustom, world: World) {
|
||||
var x = packet.readInt
|
||||
while (x != Int.MaxValue) {
|
||||
val pos = new BlockCoord(x, packet.readInt, packet.readInt)
|
||||
var i = packet.readUByte
|
||||
while (i < 255) {
|
||||
TileMultipart.handlePacket(pos, world, i, packet)
|
||||
i = packet.readUByte
|
||||
}
|
||||
x = packet.readInt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object MultipartSPH
|
||||
extends MultipartPH
|
||||
with IServerPacketHandler
|
||||
with IHandshakeHandler {
|
||||
class MCByteStream(bout: ByteArrayOutputStream)
|
||||
extends MCDataOutputWrapper(new DataOutputStream(bout)) {
|
||||
def getBytes = bout.toByteArray
|
||||
}
|
||||
|
||||
private val updateMap = Map[World, Map[BlockCoord, MCByteStream]]()
|
||||
|
||||
/** These maps are keyed by entityID so that new player instances with the
|
||||
* same entity id don't conflict world references
|
||||
*/
|
||||
private val chunkWatchers = new HashMap[Int, Set[ChunkCoordIntPair]]
|
||||
with MultiMap[Int, ChunkCoordIntPair]
|
||||
private val newWatchers = Map[Int, LinkedList[ChunkCoordIntPair]]()
|
||||
|
||||
def handlePacket(
|
||||
packet: PacketCustom,
|
||||
sender: EntityPlayerMP,
|
||||
netHandler: INetHandlerPlayServer
|
||||
) {
|
||||
packet.getType match {
|
||||
case 1 => ControlKeyModifer.map.put(sender, packet.readBoolean)
|
||||
}
|
||||
}
|
||||
|
||||
def handshakeRecieved(netHandler: NetHandlerPlayServer) {
|
||||
val packet = new PacketCustom(registryChannel, 1)
|
||||
MultiPartRegistry.writeIDMap(packet)
|
||||
netHandler.sendPacket(packet.toPacket)
|
||||
}
|
||||
|
||||
def onWorldUnload(world: World) {
|
||||
if (!world.isRemote)
|
||||
updateMap.remove(world)
|
||||
}
|
||||
|
||||
def getTileStream(world: World, pos: BlockCoord) =
|
||||
updateMap
|
||||
.getOrElseUpdate(
|
||||
world, {
|
||||
if (world.isRemote)
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot use MultipartSPH on a client world"
|
||||
)
|
||||
Map()
|
||||
}
|
||||
)
|
||||
.getOrElseUpdate(
|
||||
pos, {
|
||||
val s = new MCByteStream(new ByteArrayOutputStream)
|
||||
s.writeCoord(pos)
|
||||
s
|
||||
}
|
||||
)
|
||||
|
||||
def onTickEnd(players: Seq[EntityPlayerMP]) {
|
||||
PacketScheduler.sendScheduled()
|
||||
|
||||
for (p <- players if chunkWatchers.containsKey(p.getEntityId)) {
|
||||
updateMap.get(p.worldObj) match {
|
||||
case Some(m) if !m.isEmpty =>
|
||||
val chunks = chunkWatchers(p.getEntityId)
|
||||
val packet = new PacketCustom(channel, 3).compress()
|
||||
var send = false
|
||||
for (
|
||||
(pos, stream) <- m
|
||||
if chunks(new ChunkCoordIntPair(pos.x >> 4, pos.z >> 4))
|
||||
) {
|
||||
send = true
|
||||
packet.writeByteArray(stream.getBytes)
|
||||
packet.writeByte(255) // terminator
|
||||
}
|
||||
if (send) {
|
||||
packet.writeInt(Int.MaxValue) // terminator
|
||||
packet.sendToPlayer(p)
|
||||
}
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
updateMap.foreach(_._2.clear())
|
||||
for (p <- players if newWatchers.containsKey(p.getEntityId)) {
|
||||
for (c <- newWatchers(p.getEntityId)) {
|
||||
val chunk = p.worldObj.getChunkFromChunkCoords(c.chunkXPos, c.chunkZPos)
|
||||
val pkt = getDescPacket(
|
||||
chunk,
|
||||
chunk.chunkTileEntityMap
|
||||
.asInstanceOf[JMap[_, TileEntity]]
|
||||
.values
|
||||
.iterator
|
||||
)
|
||||
if (pkt != null) pkt.sendToPlayer(p)
|
||||
chunkWatchers.addBinding(p.getEntityId, c)
|
||||
}
|
||||
}
|
||||
newWatchers.clear()
|
||||
}
|
||||
|
||||
def onChunkWatch(p: EntityPlayer, c: ChunkCoordIntPair) {
|
||||
newWatchers.getOrElseUpdate(p.getEntityId, new LinkedList).add(c)
|
||||
}
|
||||
|
||||
def onChunkUnWatch(p: EntityPlayer, c: ChunkCoordIntPair) {
|
||||
newWatchers.get(p.getEntityId) match {
|
||||
case Some(chunks) => chunks.remove(c)
|
||||
case _ =>
|
||||
}
|
||||
chunkWatchers.removeBinding(p.getEntityId, c)
|
||||
}
|
||||
|
||||
def getDescPacket(chunk: Chunk, it: Iterator[TileEntity]): PacketCustom = {
|
||||
val s = new MCByteStream(new ByteArrayOutputStream)
|
||||
|
||||
var num = 0
|
||||
while (it.hasNext) {
|
||||
val tile = it.next
|
||||
if (tile.isInstanceOf[TileMultipart]) {
|
||||
s.writeByte(tile.xCoord & 0xf)
|
||||
s.writeInt(tile.yCoord)
|
||||
s.writeByte(tile.zCoord & 0xf)
|
||||
tile.asInstanceOf[TileMultipart].writeDesc(s)
|
||||
num += 1
|
||||
}
|
||||
}
|
||||
if (num != 0) {
|
||||
return new PacketCustom(channel, 2)
|
||||
.compress()
|
||||
.writeInt(chunk.xPosition)
|
||||
.writeInt(chunk.zPosition)
|
||||
.writeShort(num)
|
||||
.writeByteArray(s.getBytes)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user