ForgePatch/src/main/java/net/minecraftforge/fml/StartupQuery.java

326 lines
10 KiB
Java

/*
* Minecraft Forge
* Copyright (c) 2018.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fml;
import java.util.Iterator;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Supplier;
import net.minecraft.client.Minecraft;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.server.dedicated.PendingCommand;
import net.minecraftforge.fml.client.gui.GuiConfirmation;
import net.minecraftforge.fml.client.gui.GuiNotification;
import net.minecraftforge.fml.common.thread.EffectiveSide;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.fml.server.ServerLifecycleHooks;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import javax.annotation.Nullable;
public class StartupQuery {
// internal class/functionality, do not use
private static final Logger LOGGER = LogManager.getLogger();
private static final Marker SQ = MarkerManager.getMarker("STARTUPQUERY");
public static boolean confirm(String text)
{
StartupQuery query = new StartupQuery(text, new AtomicBoolean());
query.execute();
return query.getResult();
}
private InterruptedException exception;
public static void notify(String text)
{
StartupQuery query = new StartupQuery(text, null);
query.execute();
}
public static void abort()
{
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
if (server != null) server.initiateShutdown();
aborted = true; // to abort loading and go back to the main menu
throw new AbortedException(); // to halt the server
}
public static void reset()
{
pending = null;
aborted = false;
}
public static boolean check()
{
if (pending != null)
{
try
{
try
{
SidedProvider.STARTUPQUERY.<Consumer<StartupQuery>>get().accept(pending);
}
catch (RuntimeException e)
{
}
pending.throwException();
}
catch (InterruptedException e)
{
LOGGER.warn(SQ, "query interrupted");
abort();
}
pending = null;
}
return !aborted;
}
private void throwException() throws InterruptedException
{
if (exception != null) throw exception;
}
private static volatile StartupQuery pending;
private static volatile boolean aborted = false;
private StartupQuery(String text, @Nullable AtomicBoolean result)
{
this.text = text;
this.result = result;
}
@Nullable
public Boolean getResult()
{
return result == null ? null : result.get();
}
public void setResult(boolean result)
{
this.result.set(result);
}
public String getText()
{
return text;
}
public boolean isSynchronous()
{
return synchronous;
}
public void finish()
{
signal.countDown();
}
private void execute()
{
String prop = System.getProperty("fml.queryResult");
if (result != null && prop != null)
{
LOGGER.info(SQ, "Using fml.queryResult {} to answer the following query:\n{}", prop, text);
if (prop.equalsIgnoreCase("confirm"))
{
setResult(true);
return;
}
else if (prop.equalsIgnoreCase("cancel"))
{
setResult(false);
return;
}
LOGGER.warn(SQ, "Invalid value for fml.queryResult: {}, expected confirm or cancel", prop);
}
synchronous = false;
pending = this; // let the other thread start the query
// from the integrated server thread: the client will eventually check pending and execute the query
// from the client thread: synchronous execution
// dedicated server: command handling in mc is synchronous, execute the server-side query directly
if (FMLEnvironment.dist.isDedicatedServer() || EffectiveSide.get() == LogicalSide.CLIENT)
{
synchronous = true;
check();
}
try
{
signal.await();
reset();
}
catch (InterruptedException e)
{
LOGGER.warn(SQ, "query interrupted");
abort();
}
}
private String text;
@Nullable
private AtomicBoolean result;
private CountDownLatch signal = new CountDownLatch(1);
private volatile boolean synchronous;
/**
* Exception not being caught by the crash report generation logic.
*/
public static class AbortedException extends RuntimeException
{
private static final long serialVersionUID = -5933665223696833921L;
private AbortedException()
{
super();
}
}
public static class QueryWrapper
{
public static Consumer<StartupQuery> clientQuery(Supplier<Minecraft> clientSupplier)
{
return (query) -> {
Minecraft client = clientSupplier.get();
if (query.getResult() == null)
{
client.displayGuiScreen(new GuiNotification(query));
}
else
{
client.displayGuiScreen(new GuiConfirmation(query));
}
if (query.isSynchronous())
{
while (client.currentScreen instanceof GuiNotification)
{
if (Thread.interrupted())
{
query.exception = new InterruptedException();
throw new RuntimeException();
}
try
{
Thread.sleep(50);
}
catch (InterruptedException ie)
{
query.exception = ie;
}
}
}
};
}
public static Consumer<StartupQuery> dedicatedServerQuery(Supplier<DedicatedServer> serverSupplier)
{
return (query) -> {
DedicatedServer server = serverSupplier.get();
if (query.getResult() == null)
{
LOGGER.warn(SQ, query.getText());
query.finish();
}
else
{
String text = query.getText() +
"\n\nRun the command /fml confirm or or /fml cancel to proceed." +
"\nAlternatively start the server with -Dfml.queryResult=confirm or -Dfml.queryResult=cancel to preselect the answer.";
LOGGER.warn(SQ, text);
if (!query.isSynchronous()) return; // no-op until mc does commands in another thread (if ever)
boolean done = false;
while (!done && server.isServerRunning())
{
if (Thread.interrupted())
{
query.exception = new InterruptedException();
throw new RuntimeException();
}
DedicatedServer dedServer = (DedicatedServer)server;
// rudimentary command processing, check for fml confirm/cancel and stop commands
synchronized (dedServer.pendingCommandList)
{
for (Iterator<PendingCommand> it = dedServer.pendingCommandList.iterator(); it.hasNext(); )
{
String cmd = it.next().command.trim().toLowerCase();
if (cmd.equals("/fml confirm"))
{
LOGGER.info(SQ, "confirmed");
query.setResult(true);
done = true;
it.remove();
}
else if (cmd.equals("/fml cancel"))
{
LOGGER.info(SQ, "cancelled");
query.setResult(false);
done = true;
it.remove();
}
else if (cmd.equals("/stop"))
{
StartupQuery.abort();
}
}
}
try
{
Thread.sleep(10L);
}
catch (InterruptedException ie)
{
query.exception = ie;
}
}
query.finish();
}
};
}
}
}