-
Notifications
You must be signed in to change notification settings - Fork 424
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ensure all packets are handled in the tick after it was sent
- Loading branch information
1 parent
2e944fb
commit 707004e
Showing
7 changed files
with
315 additions
and
1 deletion.
There are no files selected for viewing
176 changes: 176 additions & 0 deletions
176
...-api-v1/src/client/java/net/fabricmc/fabric/impl/client/gametest/NetworkSynchronizer.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,176 @@ | ||
/* | ||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package net.fabricmc.fabric.impl.client.gametest; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.atomic.AtomicBoolean; | ||
import java.util.concurrent.atomic.AtomicInteger; | ||
import java.util.concurrent.locks.Condition; | ||
import java.util.concurrent.locks.Lock; | ||
import java.util.concurrent.locks.ReentrantLock; | ||
|
||
import com.google.common.collect.ConcurrentHashMultiset; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import net.minecraft.util.Unit; | ||
import net.minecraft.util.thread.ThreadExecutor; | ||
|
||
public final class NetworkSynchronizer { | ||
private static final Logger LOGGER = LoggerFactory.getLogger("fabric-client-gametest-api-v1"); | ||
private static final boolean DISABLED = System.getProperty("fabric.client.gametest.disableNetworkSynchronizer") != null; | ||
|
||
private final ThreadLocal<Unit> isNettyThread = new ThreadLocal<>(); | ||
private final AtomicInteger inFlightPackets = new AtomicInteger(); | ||
private final ConcurrentHashMultiset<RunnableBox> mainThreadPacketHandlers = ConcurrentHashMultiset.create(); | ||
private final Lock morePacketsLock = new ReentrantLock(); | ||
private final Condition morePacketsCondition = morePacketsLock.newCondition(); | ||
private final AtomicBoolean invalid = new AtomicBoolean(); | ||
private boolean isRunningNetworkTasks = false; | ||
|
||
public void preSendPacket() { | ||
if (DISABLED) { | ||
return; | ||
} | ||
|
||
inFlightPackets.incrementAndGet(); | ||
} | ||
|
||
public void preNettyHandlePacket() { | ||
if (DISABLED) { | ||
return; | ||
} | ||
|
||
isNettyThread.set(Unit.INSTANCE); | ||
} | ||
|
||
public void postNettyHandlePacket() { | ||
if (DISABLED) { | ||
return; | ||
} | ||
|
||
int remainingInFlightPackets = inFlightPackets.decrementAndGet(); | ||
|
||
if (remainingInFlightPackets < 0) { | ||
markInvalid(); | ||
return; | ||
} | ||
|
||
isNettyThread.remove(); | ||
|
||
if (remainingInFlightPackets == 0) { | ||
signalMorePackets(); | ||
} | ||
} | ||
|
||
public void postTaskAdded(Runnable task) { | ||
if (DISABLED) { | ||
return; | ||
} | ||
|
||
if (isNettyThread.get() != null) { | ||
mainThreadPacketHandlers.add(new RunnableBox(task)); | ||
signalMorePackets(); | ||
} | ||
} | ||
|
||
public void postTaskRun(Runnable task) { | ||
if (DISABLED) { | ||
return; | ||
} | ||
|
||
checkInvalid(); | ||
mainThreadPacketHandlers.remove(new RunnableBox(task)); | ||
} | ||
|
||
public void waitForPacketHandlers(ThreadExecutor<?> executor) { | ||
if (DISABLED) { | ||
return; | ||
} | ||
|
||
while (inFlightPackets.get() > 0 || !mainThreadPacketHandlers.isEmpty()) { | ||
while (inFlightPackets.get() > 0 && mainThreadPacketHandlers.isEmpty()) { | ||
morePacketsLock.lock(); | ||
|
||
try { | ||
if (!morePacketsCondition.await(10, TimeUnit.SECONDS)) { | ||
markInvalid(); | ||
checkInvalid(); | ||
} | ||
} catch (InterruptedException e) { | ||
throw new RuntimeException(e); | ||
} finally { | ||
morePacketsLock.unlock(); | ||
} | ||
} | ||
|
||
isRunningNetworkTasks = true; | ||
|
||
try { | ||
executor.runTasks(mainThreadPacketHandlers::isEmpty); | ||
} finally { | ||
isRunningNetworkTasks = false; | ||
} | ||
} | ||
} | ||
|
||
public void reset() { | ||
inFlightPackets.set(0); | ||
mainThreadPacketHandlers.clear(); | ||
signalMorePackets(); | ||
} | ||
|
||
public boolean isRunningNetworkTasks() { | ||
return isRunningNetworkTasks; | ||
} | ||
|
||
private void signalMorePackets() { | ||
morePacketsLock.lock(); | ||
morePacketsCondition.signal(); | ||
morePacketsLock.unlock(); | ||
} | ||
|
||
private void markInvalid() { | ||
if (!invalid.getAndSet(true)) { | ||
LOGGER.error("Detected interfacing with packets at a lower level. Please disable network synchronization by setting the fabric.client.gametest.disableNetworkSynchronizer system property"); | ||
signalMorePackets(); | ||
} | ||
} | ||
|
||
private void checkInvalid() { | ||
if (invalid.get()) { | ||
throw new AssertionError("Network synchronizer in invalid state, see earlier log messages"); | ||
} | ||
} | ||
|
||
// Wraps a runnable to always use identity hashCode and equals | ||
private record RunnableBox(Runnable runnable) { | ||
@Override | ||
public boolean equals(Object other) { | ||
if (!(other instanceof RunnableBox(Runnable otherRunnable))) { | ||
return false; | ||
} | ||
|
||
return otherRunnable == this.runnable; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return System.identityHashCode(this.runnable); | ||
} | ||
} | ||
} |
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
59 changes: 59 additions & 0 deletions
59
...i-v1/src/client/java/net/fabricmc/fabric/mixin/client/gametest/ClientConnectionMixin.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,59 @@ | ||
/* | ||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package net.fabricmc.fabric.mixin.client.gametest; | ||
|
||
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; | ||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation; | ||
import io.netty.channel.ChannelHandlerContext; | ||
import org.spongepowered.asm.mixin.Final; | ||
import org.spongepowered.asm.mixin.Mixin; | ||
import org.spongepowered.asm.mixin.Shadow; | ||
import org.spongepowered.asm.mixin.injection.At; | ||
import org.spongepowered.asm.mixin.injection.Inject; | ||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; | ||
|
||
import net.minecraft.network.ClientConnection; | ||
import net.minecraft.network.NetworkSide; | ||
import net.minecraft.network.packet.Packet; | ||
|
||
import net.fabricmc.fabric.impl.client.gametest.NetworkSynchronizer; | ||
import net.fabricmc.fabric.impl.client.gametest.ThreadingImpl; | ||
|
||
@Mixin(ClientConnection.class) | ||
public class ClientConnectionMixin { | ||
@Shadow | ||
@Final | ||
private NetworkSide side; | ||
|
||
@WrapMethod(method = "channelRead0(Lio/netty/channel/ChannelHandlerContext;Lnet/minecraft/network/packet/Packet;)V") | ||
private void onNettyReceivePacket(ChannelHandlerContext context, Packet<?> packet, Operation<Void> original) { | ||
NetworkSynchronizer synchronizer = side == NetworkSide.CLIENTBOUND ? ThreadingImpl.CLIENTBOUND_SYNCHRONIZER : ThreadingImpl.SERVERBOUND_SYNCHRONIZER; | ||
synchronizer.preNettyHandlePacket(); | ||
|
||
try { | ||
original.call(context, packet); | ||
} finally { | ||
synchronizer.postNettyHandlePacket(); | ||
} | ||
} | ||
|
||
@Inject(method = "sendImmediately", at = @At("HEAD")) | ||
private void onSendPacket(CallbackInfo ci) { | ||
NetworkSynchronizer synchronizer = side == NetworkSide.CLIENTBOUND ? ThreadingImpl.SERVERBOUND_SYNCHRONIZER : ThreadingImpl.CLIENTBOUND_SYNCHRONIZER; | ||
synchronizer.preSendPacket(); | ||
} | ||
} |
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
51 changes: 51 additions & 0 deletions
51
...api-v1/src/client/java/net/fabricmc/fabric/mixin/client/gametest/ThreadExecutorMixin.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,51 @@ | ||
/* | ||
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package net.fabricmc.fabric.mixin.client.gametest; | ||
|
||
import org.spongepowered.asm.mixin.Mixin; | ||
import org.spongepowered.asm.mixin.injection.At; | ||
import org.spongepowered.asm.mixin.injection.Inject; | ||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; | ||
|
||
import net.minecraft.client.MinecraftClient; | ||
import net.minecraft.server.MinecraftServer; | ||
import net.minecraft.util.thread.ThreadExecutor; | ||
|
||
import net.fabricmc.fabric.impl.client.gametest.ThreadingImpl; | ||
|
||
@Mixin(ThreadExecutor.class) | ||
public class ThreadExecutorMixin { | ||
@Inject(method = "send", at = @At(value = "INVOKE", target = "Ljava/util/Queue;add(Ljava/lang/Object;)Z", remap = false, shift = At.Shift.AFTER)) | ||
private void onPacketHandlerSchedule(Runnable task, CallbackInfo ci) { | ||
switch ((Object) this) { | ||
case MinecraftClient $ -> ThreadingImpl.CLIENTBOUND_SYNCHRONIZER.postTaskAdded(task); | ||
case MinecraftServer $ -> ThreadingImpl.SERVERBOUND_SYNCHRONIZER.postTaskAdded(task); | ||
default -> { | ||
} | ||
} | ||
} | ||
|
||
@Inject(method = "executeTask", at = @At(value = "INVOKE", target = "Ljava/lang/Runnable;run()V", remap = false, shift = At.Shift.AFTER)) | ||
private void onPacketHandlerRun(Runnable task, CallbackInfo ci) { | ||
switch ((Object) this) { | ||
case MinecraftClient $ -> ThreadingImpl.CLIENTBOUND_SYNCHRONIZER.postTaskRun(task); | ||
case MinecraftServer $ -> ThreadingImpl.SERVERBOUND_SYNCHRONIZER.postTaskRun(task); | ||
default -> { | ||
} | ||
} | ||
} | ||
} |
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