Skip to content

Commit

Permalink
Use Fabric test threading system
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexander01998 committed Dec 12, 2024
1 parent 6e221a2 commit 5633fcd
Show file tree
Hide file tree
Showing 7 changed files with 635 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/**
* Copied from
* https://github.com/FabricMC/fabric/blob/453d4f91c7a4e0e7f34116755c8acb2c20c53aea/fabric-api-base/src/testmodClient/java/net/fabricmc/fabric/test/base/client/mixin/MinecraftClientMixin.java
* which at the time of writing is not yet part of the API.
*/
/*
* 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.wurstclient.mixin;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import com.google.common.base.Preconditions;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;

import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.resource.ResourcePackManager;
import net.minecraft.server.SaveLoader;
import net.minecraft.world.level.storage.LevelStorage;
import net.wurstclient.test.WurstE2ETestClient;
import net.wurstclient.test.fabric.ThreadingImpl;

@Mixin(MinecraftClient.class)
public class FabricTestMinecraftClientMixin
{
@Unique
private Runnable deferredTask = null;

@WrapMethod(method = "run")
private void onRun(Operation<Void> original)
{
if(WurstE2ETestClient.IS_AUTO_TEST)
{
if(ThreadingImpl.isClientRunning)
throw new IllegalStateException("Client is already running");

ThreadingImpl.isClientRunning = true;
ThreadingImpl.PHASER.register();
}

try
{
original.call();
}finally
{
if(WurstE2ETestClient.IS_AUTO_TEST)
{
ThreadingImpl.clientCanAcceptTasks = false;
ThreadingImpl.PHASER.arriveAndDeregister();
ThreadingImpl.isClientRunning = false;
}
}
}

@Inject(method = "render",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/MinecraftClient;runTasks()V"))
private void preRunTasks(CallbackInfo ci)
{
if(WurstE2ETestClient.IS_AUTO_TEST)
{
ThreadingImpl.enterPhase(ThreadingImpl.PHASE_SERVER_TASKS);
// server tasks happen here
ThreadingImpl.enterPhase(ThreadingImpl.PHASE_CLIENT_TASKS);
}
}

@Inject(method = "render",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/MinecraftClient;runTasks()V",
shift = At.Shift.AFTER))
private void postRunTasks(CallbackInfo ci)
{
if(WurstE2ETestClient.IS_AUTO_TEST)
{
ThreadingImpl.clientCanAcceptTasks = true;
ThreadingImpl.enterPhase(ThreadingImpl.PHASE_TEST);

if(ThreadingImpl.testThread != null)
while(true)
{
try
{
ThreadingImpl.CLIENT_SEMAPHORE.acquire();
}catch(InterruptedException e)
{
throw new RuntimeException(e);
}

if(ThreadingImpl.taskToRun == null)
break;
ThreadingImpl.taskToRun.run();
}

ThreadingImpl.enterPhase(ThreadingImpl.PHASE_TICK);

Runnable deferredTask = this.deferredTask;
this.deferredTask = null;

if(deferredTask != null)
deferredTask.run();
}
}

@Inject(method = "startIntegratedServer",
at = @At("HEAD"),
cancellable = true)
private void deferStartIntegratedServer(LevelStorage.Session session,
ResourcePackManager dataPackManager, SaveLoader saveLoader,
boolean newWorld, CallbackInfo ci)
{
if(WurstE2ETestClient.IS_AUTO_TEST && ThreadingImpl.taskToRun != null)
{
// don't start the integrated server (which busywaits) inside a task
deferredTask =
() -> MinecraftClient.getInstance().startIntegratedServer(
session, dataPackManager, saveLoader, newWorld);
ci.cancel();
}
}

@Inject(method = "startIntegratedServer",
at = @At(value = "INVOKE",
target = "Ljava/lang/Thread;sleep(J)V",
remap = false))
private void onStartIntegratedServerBusyWait(CallbackInfo ci)
{
if(WurstE2ETestClient.IS_AUTO_TEST)
{
// give the server a chance to tick too
preRunTasks(ci);
postRunTasks(ci);
}
}

@Inject(method = "disconnect(Lnet/minecraft/client/gui/screen/Screen;Z)V",
at = @At("HEAD"),
cancellable = true)
private void deferDisconnect(Screen disconnectionScreen,
boolean transferring, CallbackInfo ci)
{
if(WurstE2ETestClient.IS_AUTO_TEST
&& MinecraftClient.getInstance().getServer() != null
&& ThreadingImpl.taskToRun != null)
{
// don't disconnect (which busywaits) inside a task
deferredTask = () -> MinecraftClient.getInstance()
.disconnect(disconnectionScreen, transferring);
ci.cancel();
}
}

@Inject(method = "disconnect(Lnet/minecraft/client/gui/screen/Screen;Z)V",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/MinecraftClient;render(Z)V",
shift = At.Shift.AFTER))
private void onDisconnectBusyWait(CallbackInfo ci)
{
if(WurstE2ETestClient.IS_AUTO_TEST)
{
// give the server a chance to tick too
preRunTasks(ci);
postRunTasks(ci);
}
}

@Inject(method = "getInstance", at = @At("HEAD"))
private static void checkThreadOnGetInstance(
CallbackInfoReturnable<MinecraftClient> cir)
{
if(WurstE2ETestClient.IS_AUTO_TEST)
// TODO: add suggestion of runOnClient etc when API methods are
// added
Preconditions.checkState(
Thread.currentThread() != ThreadingImpl.testThread,
"MinecraftClient.getInstance() cannot be called from the test thread");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* Copied from
* https://github.com/FabricMC/fabric/blob/453d4f91c7a4e0e7f34116755c8acb2c20c53aea/fabric-api-base/src/testmodClient/java/net/fabricmc/fabric/test/base/client/mixin/MinecraftServerMixin.java
* which at the time of writing is not yet part of the API.
*/
/*
* 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.wurstclient.mixin;

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 com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;

import net.minecraft.server.MinecraftServer;
import net.wurstclient.test.WurstE2ETestClient;
import net.wurstclient.test.fabric.ThreadingImpl;

@Mixin(MinecraftServer.class)
public class FabricTestMinecraftServerMixin
{
@WrapMethod(method = "runServer")
private void onRunServer(Operation<Void> original)
{
if(WurstE2ETestClient.IS_AUTO_TEST)
{
if(ThreadingImpl.isServerRunning)
throw new IllegalStateException("Server is already running");

ThreadingImpl.isServerRunning = true;
ThreadingImpl.PHASER.register();
}

try
{
original.call();
}finally
{
if(WurstE2ETestClient.IS_AUTO_TEST)
{
ThreadingImpl.serverCanAcceptTasks = false;
ThreadingImpl.PHASER.arriveAndDeregister();
ThreadingImpl.isServerRunning = false;
}
}
}

@Inject(method = "runServer",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/server/MinecraftServer;runTasksTillTickEnd()V"))
private void preRunTasks(CallbackInfo ci)
{
if(WurstE2ETestClient.IS_AUTO_TEST)
ThreadingImpl.enterPhase(ThreadingImpl.PHASE_SERVER_TASKS);
}

@Inject(method = "runServer",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/server/MinecraftServer;runTasksTillTickEnd()V",
shift = At.Shift.AFTER))
private void postRunTasks(CallbackInfo ci)
{
if(WurstE2ETestClient.IS_AUTO_TEST)
{
ThreadingImpl.enterPhase(ThreadingImpl.PHASE_CLIENT_TASKS);
// client tasks happen here

ThreadingImpl.serverCanAcceptTasks = true;
ThreadingImpl.enterPhase(ThreadingImpl.PHASE_TEST);

if(ThreadingImpl.testThread != null)
while(true)
{
try
{
ThreadingImpl.SERVER_SEMAPHORE.acquire();
}catch(InterruptedException e)
{
throw new RuntimeException(e);
}

if(ThreadingImpl.taskToRun == null)
break;
ThreadingImpl.taskToRun.run();
}

ThreadingImpl.enterPhase(ThreadingImpl.PHASE_TICK);
}
}
}
2 changes: 1 addition & 1 deletion src/main/java/net/wurstclient/test/ModifyCmdTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public static void testModifyCmd()
throw new RuntimeException("NBT data is incorrect");
});
runWurstCommand("viewnbt");
takeScreenshot("modify_command_result");
takeScreenshot("modify_command_result", 3);

// Clean up
runChatCommand("clear");
Expand Down
Loading

0 comments on commit 5633fcd

Please sign in to comment.