This is an early display window system for forge. (#9558)

This commit is contained in:
cpw 2023-06-19 07:01:45 -04:00 committed by GitHub
parent 551feaf019
commit 031d008826
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
76 changed files with 3065 additions and 1058 deletions

View file

@ -57,6 +57,9 @@ This software contains a partial repackaging of javaxdelta, a BSD licensed progr
binary differences and applying them, sourced from the subversion at http://sourceforge.net/projects/javaxdelta/
authored by genman, heikok, pivot.
The only changes are to replace some Trove collection types with standard Java collections, and repackaged.
This software includes the Monocraft font from https://github.com/IdreesInc/Monocraft/ for use in the early loading
display.
=========================================================================

View file

@ -568,7 +568,7 @@ project(':fmlonly') {
ext {
MCP_ARTIFACT = project(':mcp').mcp.config.get()
PACKED_DEPS = [':fmlcore:jar', ':fmlloader:jar', ':javafmllanguage:jar', ':lowcodelanguage:jar', ':mclanguage:jar']
PACKED_DEPS = [':fmlcore:jar', ':fmlloader:jar', ':fmlearlydisplay:jar', ':javafmllanguage:jar', ':lowcodelanguage:jar', ':mclanguage:jar']
MAVEN_PATH = "${group.replace('.', '/')}/${project.name}/${VERSION}"
}
@ -596,6 +596,7 @@ project(':fmlonly') {
dependencies {
implementation project(':fmlcore')
implementation project(':fmlloader')
implementation project(':fmlearlydisplay')
implementation project(':javafmllanguage')
implementation project(':lowcodelanguage')
implementation project(':mclanguage')
@ -632,7 +633,7 @@ project(':fmlonly') {
sharedFmlonlyForge.call(project)
task launcherJson(type: LauncherJson) {
packedDependencies = [':fmlloader:jar']
packedDependencies = [':fmlloader:jar',':fmlearlydisplay:jar']
doFirst {
def fmlonly_client = project(':fmlonly').patcher.runs.fmlonly_client
json = [
@ -931,7 +932,7 @@ project(':forge') {
// Essentially, the same as the old, except dropping the first number, and the builds are no longer unique.
MCP_ARTIFACT = project(':mcp').mcp.config.get()
VERSION_JSON = project(':mcp').file('build/mcp/downloadJson/version.json')
PACKED_DEPS = [':fmlcore:jar', ':fmlloader:jar', ':javafmllanguage:jar', ':lowcodelanguage:jar', ':mclanguage:jar']
PACKED_DEPS = [':fmlcore:jar', ':fmlloader:jar', ':fmlearlydisplay:jar', ':javafmllanguage:jar', ':lowcodelanguage:jar', ':mclanguage:jar']
MAVEN_PATH = "${group.replace('.', '/')}/${project.name}/${VERSION}"
}
@ -951,6 +952,7 @@ project(':forge') {
testImplementation 'org.hamcrest:hamcrest-all:1.3' // needs advanced matching for list order
implementation project(':fmlcore')
implementation project(':fmlloader')
implementation project(':fmlearlydisplay')
implementation project(':javafmllanguage')
implementation project(':lowcodelanguage')
implementation project(':mclanguage')
@ -1328,7 +1330,7 @@ project(':forge') {
}
task launcherJson(type: LauncherJson) {
packedDependencies = [':fmlloader:jar']
packedDependencies = [':fmlloader:jar',':fmlearlydisplay:jar']
doFirst {
def forge_client = project(':forge').patcher.runs.forge_client
json = [

View file

@ -7,12 +7,14 @@ package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.ToIntFunction;
/**
* A mod loading state. During mod loading, the mod loader transitions between states in a defined sorted list of states,
@ -44,25 +46,33 @@ public interface IModLoadingState {
*/
Function<ModList, String> message();
/**
* @return a function that computes the size of this transition based on the size of the modlist.
* Used to compute progress.
*/
ToIntFunction<ModList> size();
/**
* {@return an optional runnable, which runs before starting the transition from this state to the next}
* @see #buildTransition(Executor, Executor, Function, Function)
* @see #buildTransition(Executor, Executor, ProgressMeter, Function, Function)
*/
Optional<Consumer<ModList>> inlineRunnable();
/**
* Builds the transition task for this state with a blank pre-sync and post-sync task.
*
* @param syncExecutor a synchronous executor
* @param <T> a type of event fired on the mod-specific event bus
* @param syncExecutor a synchronous executor
* @param parallelExecutor a parallel executor
* @param <T> a type of event fired on the mod-specific event bus
* @param progressBar a progress meter for tracking progress
* @return a transition task for this state
* @see #buildTransition(Executor, Executor, Function, Function)
* @see #buildTransition(Executor, Executor, ProgressMeter, Function, Function)
*/
default <T extends Event & IModBusEvent>
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
final Executor parallelExecutor) {
return buildTransition(syncExecutor, parallelExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar) {
return buildTransition(syncExecutor, parallelExecutor, progressBar,
e -> CompletableFuture.runAsync(() -> {}, e),
e -> CompletableFuture.runAsync(() -> {}, e));
}
@ -71,16 +81,18 @@ public interface IModLoadingState {
* Builds the transition task for this state. The pre-sync and post-sync task functions allow the transition builder
* to run these tasks on the same executor as the actual event dispatch and pre/post hooks.
*
* @param syncExecutor a synchronous executor
* @param <T> a type of event fired on the mod-specific event bus
* @param syncExecutor a synchronous executor
* @param parallelExecutor a parallel executor
* @param preSyncTask a function which returns a task to run before event pre-dispatch hook
* @param postSyncTask a function which returns a task to run after event post-dispatch hook
* @param <T> a type of event fired on the mod-specific event bus
* @param progressBar a progress meter for tracking progress
* @param preSyncTask a function which returns a task to run before event pre-dispatch hook
* @param postSyncTask a function which returns a task to run after event post-dispatch hook
* @return a transition task for this state
*/
<T extends Event & IModBusEvent>
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask);
}

View file

@ -7,6 +7,7 @@ package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import java.util.ArrayList;
import java.util.List;
@ -24,20 +25,26 @@ public interface IModStateTransition {
}
default <T extends Event & IModBusEvent>
CompletableFuture<Void> build(final Executor syncExecutor,
CompletableFuture<Void> build(final String name,
final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
this.eventFunctionStream().get()
.map(f -> (EventGenerator<T>) f)
.reduce((head, tail) -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, head, ModLoadingStage::currentState, tail))
.ifPresent(last -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, last, nextModLoadingStage(), null));
.reduce((head, tail) -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, progressBar, head, ModLoadingStage::currentState, tail))
.ifPresent(last -> addCompletableFutureTaskForModDispatch(syncExecutor, parallelExecutor, futures, progressBar, last, nextModLoadingStage(), null));
final CompletableFuture<Void> preSyncTaskCF = preSyncTask.apply(syncExecutor);
final CompletableFuture<Void> eventDispatchCF = ModList.gather(futures).thenCompose(ModList::completableFutureFromExceptionList);
final CompletableFuture<Void> postEventDispatchCF = preSyncTaskCF
.thenApplyAsync(n -> {
progressBar.label(progressBar.name() + ": dispatching "+name);
return null;
}, parallelExecutor)
.thenComposeAsync(n -> eventDispatchCF, parallelExecutor)
.thenApply(r -> {
postSyncTask.apply(syncExecutor);
@ -54,6 +61,7 @@ public interface IModStateTransition {
EventGenerator<T> addCompletableFutureTaskForModDispatch(final Executor syncExecutor,
final Executor parallelExecutor,
final List<CompletableFuture<Void>> completableFutures,
final ProgressMeter progressBar,
final EventGenerator<T> eventGenerator,
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> nextState,
final EventGenerator<T> nextGenerator) {
@ -62,7 +70,7 @@ public interface IModStateTransition {
var preDispatchHook = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>) preDispatchHook();
completableFutures.add(preDispatchHook.apply(selectedExecutor, eventGenerator));
completableFutures.add(ModList.get().futureVisitor(eventGenerator, nextState).apply(threadSelector().apply(syncExecutor, parallelExecutor)));
completableFutures.add(ModList.get().futureVisitor(eventGenerator, progressBar, nextState).apply(threadSelector().apply(syncExecutor, parallelExecutor)));
var postDispatchHook = (BiFunction<Executor, EventGenerator<T>, CompletableFuture<Void>>) postDispatchHook();
completableFutures.add(postDispatchHook.apply(selectedExecutor, eventGenerator));

View file

@ -9,6 +9,7 @@ import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.config.IConfigEvent;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.forgespi.language.IModInfo;
import java.util.EnumMap;
@ -113,6 +114,7 @@ public abstract class ModContainer
public static <T extends Event & IModBusEvent> CompletableFuture<Void> buildTransitionHandler(
final ModContainer target,
final IModStateTransition.EventGenerator<T> eventGenerator,
final ProgressMeter progressBar,
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> stateChangeHandler,
final Executor executor) {
return CompletableFuture
@ -123,6 +125,7 @@ public abstract class ModContainer
}, executor)
.whenComplete((mc, exception) -> {
target.modLoadingStage = stateChangeHandler.apply(target.modLoadingStage, exception);
progressBar.increment();
ModLoadingContext.get().setActiveContainer(null);
});
}

View file

@ -7,6 +7,7 @@ package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.ModFileScanData;
@ -114,10 +115,11 @@ public class ModList
<T extends Event & IModBusEvent> Function<Executor, CompletableFuture<Void>> futureVisitor(
final IModStateTransition.EventGenerator<T> eventGenerator,
final ProgressMeter progressBar,
final BiFunction<ModLoadingStage, Throwable, ModLoadingStage> stateChange) {
return executor -> gather(
this.mods.stream()
.map(mod -> ModContainer.buildTransitionHandler(mod, eventGenerator, stateChange, executor))
.map(mod -> ModContainer.buildTransitionHandler(mod, eventGenerator, progressBar, stateChange, executor))
.collect(Collectors.toList()))
.thenComposeAsync(ModList::completableFutureFromExceptionList, executor);
}

View file

@ -10,11 +10,13 @@ import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.fml.loading.moddiscovery.InvalidModIdentifier;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.IModLanguageProvider;
import net.minecraftforge.forgespi.locating.ForgeFeature;
@ -81,7 +83,7 @@ public class ModLoader
private final ModStateManager stateManager;
private boolean loadingStateValid;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private final Optional<Consumer<String>> statusConsumer = StartupMessageManager.modLoaderConsumer();
private final Optional<Consumer<String>> statusConsumer = StartupNotificationManager.modLoaderConsumer();
private final Set<IModLoadingState> completedStates = new HashSet<>();
private ModList modList;
@ -133,20 +135,19 @@ public class ModLoader
* @param periodicTask Optional periodic task to perform on the main thread while other activities run
*/
public void gatherAndInitializeMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
ForgeFeature.registerFeature("java_version", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.SERVER, System.getProperty("java.version")));
ForgeFeature.registerFeature("java_version", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.BOTH, System.getProperty("java.version")));
ForgeFeature.registerFeature("openGLVersion", ForgeFeature.VersionFeatureTest.forVersionString(IModInfo.DependencySide.CLIENT, ImmediateWindowHandler.getGLVersion()));
loadingStateValid = true;
statusConsumer.ifPresent(c->c.accept("Waiting for scan to complete"));
FMLLoader.backgroundScanHandler.waitForScanToComplete(periodicTask);
statusConsumer.ifPresent(c->c.accept("Loading mods"));
final ModList modList = ModList.of(loadingModList.getModFiles().stream().map(ModFileInfo::getFile).toList(),
loadingModList.getMods());
if (!this.loadingExceptions.isEmpty()) {
LOGGER.fatal(CORE, "Error during pre-loading phase", loadingExceptions.get(0));
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
throw new LoadingFailedException(loadingExceptions);
}
statusConsumer.ifPresent(c->c.accept("Validating features"));
List<? extends ForgeFeature.Bound> failedBounds = loadingModList.getMods().stream()
.map(ModInfo::getForgeFeatures)
.flatMap(Collection::stream)
@ -155,6 +156,7 @@ public class ModLoader
if (!failedBounds.isEmpty()) {
LOGGER.fatal(CORE, "Failed to validate feature bounds for mods");
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
throw new LoadingFailedException(failedBounds.stream()
@ -162,7 +164,6 @@ public class ModLoader
.toList());
}
statusConsumer.ifPresent(c->c.accept("Building Mod List"));
final List<ModContainer> modContainers = loadingModList.getModFiles().stream()
.map(ModFileInfo::getFile)
.map(this::buildMods)
@ -170,48 +171,42 @@ public class ModLoader
.toList();
if (!loadingExceptions.isEmpty()) {
LOGGER.fatal(CORE, "Failed to initialize mod containers", loadingExceptions.get(0));
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
modList.setLoadedMods(Collections.emptyList());
loadingStateValid = false;
throw new LoadingFailedException(loadingExceptions);
}
modList.setLoadedMods(modContainers);
this.modList = modList;
statusConsumer.ifPresent(c->c.accept("Dispatching gathering events"));
stateManager.getStates(ModLoadingPhase.GATHER).forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask));
statusConsumer.ifPresent(c->c.accept("Gathering phase complete"));
var stateList = stateManager.getStates(ModLoadingPhase.GATHER);
var progress = StartupMessageManager.addProgressBar("Mod Gather", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
progress.complete();
}
public void loadMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
statusConsumer.ifPresent(c->c.accept("Performing load phase"));
stateManager.getStates(ModLoadingPhase.LOAD).forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask));
statusConsumer.ifPresent(c->c.accept("Mod setup complete"));
var stateList = stateManager.getStates(ModLoadingPhase.LOAD);
var progress = StartupMessageManager.addProgressBar("Mod Loading", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
progress.complete();
}
public void finishMods(final ModWorkManager.DrivenExecutor syncExecutor, final Executor parallelExecutor, final Runnable periodicTask) {
statusConsumer.ifPresent(c->c.accept("Performing completion phase"));
stateManager.getStates(ModLoadingPhase.COMPLETE).forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask));
var stateList = stateManager.getStates(ModLoadingPhase.COMPLETE);
var progress = StartupMessageManager.addProgressBar("Mod Complete", stateList.stream().mapToInt(mls -> mls.size().applyAsInt(modList)).sum());
stateList.forEach(mls->dispatchAndHandleError(mls, syncExecutor, parallelExecutor, periodicTask, progress));
statusConsumer.ifPresent(c->c.accept(String.format("Mod loading complete - %d mods loaded", this.modList.size())));
progress.complete();
}
private void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker) {
private void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker, final ProgressMeter progressBar) {
if (!isLoadingStateValid()) {
LOGGER.error("Cowardly refusing to process mod state change request from {}", state);
return;
}
statusConsumer.ifPresent(c->c.accept(state.message().apply(this.modList)));
progressBar.label(progressBar.name()+ " working");
state.inlineRunnable().ifPresent(a->a.accept(this.modList));
state.buildTransition(syncExecutor, parallelExecutor).ifPresent(t->waitForTransition(state, syncExecutor, ticker, t));
completedStates.add(state);
}
private void dispatchAndHandleError(IModLoadingState state, ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor, final Runnable ticker, Function<Executor, CompletableFuture<Void>> preSyncTask, Function<Executor, CompletableFuture<Void>> postSyncTask) {
if (!isLoadingStateValid()) {
LOGGER.error("Cowardly refusing to process mod state change request from {}", state);
return;
}
statusConsumer.ifPresent(c->c.accept(state.message().apply(this.modList)));
state.inlineRunnable().ifPresent(a->a.accept(this.modList));
state.buildTransition(syncExecutor, parallelExecutor, preSyncTask, postSyncTask).ifPresent(t->waitForTransition(state, syncExecutor, ticker, t));
state.buildTransition(syncExecutor, parallelExecutor, progressBar).ifPresent(t->waitForTransition(state, syncExecutor, ticker, t));
completedStates.add(state);
}
@ -229,6 +224,7 @@ public class ModLoader
.collect(Collectors.toList());
if (!notModLoading.isEmpty()) {
LOGGER.fatal("Encountered non-modloading exceptions!", e);
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
throw e;
}
@ -237,6 +233,7 @@ public class ModLoader
.map(ModLoadingException.class::cast)
.collect(Collectors.toList());
LOGGER.fatal(LOADING,"Failed to complete lifecycle event {}, {} errors found", state.name(), modLoadingExceptions.size());
statusConsumer.ifPresent(c->c.accept("ERROR DURING MOD LOADING"));
throw new LoadingFailedException(modLoadingExceptions);
}
}

View file

@ -7,12 +7,14 @@ package net.minecraftforge.fml;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.fml.event.IModBusEvent;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.ToIntFunction;
/**
* Implementation of the {@link IModLoadingState} interface.
@ -26,6 +28,7 @@ import java.util.function.Function;
*/
public record ModLoadingState(String name, String previous,
Function<ModList, String> message,
ToIntFunction<ModList> size,
ModLoadingPhase phase,
Optional<Consumer<ModList>> inlineRunnable,
Optional<IModStateTransition> transition) implements IModLoadingState {
@ -33,9 +36,10 @@ public record ModLoadingState(String name, String previous,
public <T extends Event & IModBusEvent>
Optional<CompletableFuture<Void>> buildTransition(final Executor syncExecutor,
final Executor parallelExecutor,
final ProgressMeter progressBar,
final Function<Executor, CompletableFuture<Void>> preSyncTask,
final Function<Executor, CompletableFuture<Void>> postSyncTask) {
return transition.map(t -> t.build(syncExecutor, parallelExecutor, preSyncTask, postSyncTask));
return transition.map(t -> t.build(name, syncExecutor, parallelExecutor, progressBar, preSyncTask, postSyncTask));
}
/**
@ -47,7 +51,7 @@ public record ModLoadingState(String name, String previous,
* @param phase the mod loading phase the state belongs to
*/
public static ModLoadingState empty(final String name, final String previous, final ModLoadingPhase phase) {
return new ModLoadingState(name, previous, ml -> "", phase, Optional.empty(), Optional.empty());
return new ModLoadingState(name, previous, ml -> "", f->0, phase, Optional.empty(), Optional.empty());
}
/**
@ -62,7 +66,7 @@ public record ModLoadingState(String name, String previous,
*/
public static ModLoadingState withTransition(final String name, final String previous, final ModLoadingPhase phase,
final IModStateTransition transition) {
return new ModLoadingState(name, previous, ml -> "Processing transition " + name, phase, Optional.empty(), Optional.of(transition));
return new ModLoadingState(name, previous, ml -> "Processing transition " + name, ModList::size, phase, Optional.empty(), Optional.of(transition));
}
/**
@ -78,7 +82,7 @@ public record ModLoadingState(String name, String previous,
public static ModLoadingState withTransition(final String name, final String previous,
final Function<ModList, String> message, final ModLoadingPhase phase,
final IModStateTransition transition) {
return new ModLoadingState(name, previous, message, phase, Optional.empty(), Optional.of(transition));
return new ModLoadingState(name, previous, message, ModList::size, phase, Optional.empty(), Optional.of(transition));
}
/**
@ -93,6 +97,6 @@ public record ModLoadingState(String name, String previous,
*/
public static ModLoadingState withInline(final String name, final String previous, final ModLoadingPhase phase,
final Consumer<ModList> inline) {
return new ModLoadingState(name, previous, ml -> "Processing work " + name, phase, Optional.of(inline), Optional.empty());
return new ModLoadingState(name, previous, ml -> "Processing work " + name, ml->0, phase, Optional.of(inline), Optional.empty());
}
}

View file

@ -93,7 +93,7 @@ public class ModWorkManager {
private static ForkJoinPool parallelThreadPool;
public static Executor parallelExecutor() {
if (parallelThreadPool == null) {
final int loadingThreadCount = FMLConfig.loadingThreadCount();
final int loadingThreadCount = FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.MAX_THREADS);
LOGGER.debug(LOADING, "Using {} threads for parallel mod-loading", loadingThreadCount);
parallelThreadPool = new ForkJoinPool(loadingThreadCount, ModWorkManager::newForkJoinWorkerThread, null, false);
}

View file

@ -5,19 +5,26 @@
package net.minecraftforge.fml;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import java.util.Optional;
import java.util.function.Consumer;
public class StartupMessageManager {
public static void addModMessage(final String message) {
net.minecraftforge.fml.loading.progress.StartupMessageManager.addModMessage(message);
StartupNotificationManager.addModMessage(message);
}
public static Optional<Consumer<String>> modLoaderConsumer() {
return net.minecraftforge.fml.loading.progress.StartupMessageManager.modLoaderConsumer();
return StartupNotificationManager.modLoaderConsumer();
}
public static Optional<Consumer<String>> mcLoaderConsumer() {
return net.minecraftforge.fml.loading.progress.StartupMessageManager.mcLoaderConsumer();
return StartupNotificationManager.mcLoaderConsumer();
}
public static ProgressMeter addProgressBar(final String barName, final int count) {
return StartupNotificationManager.addProgressBar(barName, count);
}
}

View file

@ -104,7 +104,7 @@ public class VersionChecker
@Override
public void run()
{
if (!FMLConfig.runVersionCheck())
if (!FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.VERSION_CHECK))
{
LOGGER.info("Global Forge version check system disabled, no further processing.");
return;

View file

@ -26,7 +26,7 @@ import static net.minecraftforge.fml.config.ConfigTracker.CONFIG;
public class ConfigFileTypeHandler {
private static final Logger LOGGER = LogUtils.getLogger();
static ConfigFileTypeHandler TOML = new ConfigFileTypeHandler();
private static final Path defaultConfigPath = FMLPaths.GAMEDIR.get().resolve(FMLConfig.defaultConfigPath());
private static final Path defaultConfigPath = FMLPaths.GAMEDIR.get().resolve(FMLConfig.getConfigValue(FMLConfig.ConfigValue.DEFAULT_CONFIG_PATH));
public Function<ModConfig, CommentedFileConfig> reader(Path configBasePath) {
return (c) -> {

View file

@ -0,0 +1,93 @@
plugins {
id 'com.github.ben-manes.versions'
id 'org.javamodularity.moduleplugin' version '1.8.7' apply false
id 'org.cadixdev.licenser'
}
apply plugin: 'java-library'
apply plugin: 'jacoco'
apply plugin: 'org.javamodularity.moduleplugin'
import org.gradle.internal.os.OperatingSystem
switch (OperatingSystem.current()) {
case OperatingSystem.LINUX:
project.ext.lwjglNatives = "natives-linux"
break
case OperatingSystem.MAC_OS:
project.ext.lwjglNatives = "natives-macos"
break
case OperatingSystem.WINDOWS:
project.ext.lwjglNatives = "natives-windows"
break
}
dependencyUpdates.rejectVersionIf { isNonStable(it.candidate.version) }
java.withSourcesJar()
dependencies {
compileOnly('org.jetbrains:annotations:23.0.0')
implementation(project(':fmlloader'))
implementation(project(':fmlcore'))
implementation('org.lwjgl:lwjgl:3.3.1')
implementation('org.lwjgl:lwjgl-glfw:3.3.1')
implementation('org.lwjgl:lwjgl-opengl:3.3.1')
implementation('org.lwjgl:lwjgl-stb:3.3.1')
implementation('org.lwjgl:lwjgl-tinyfd:3.3.1')
implementation('org.slf4j:slf4j-api:1.8.0-beta4')
implementation("net.sf.jopt-simple:jopt-simple:${JOPT_SIMPLE_VERSION}")
testImplementation('org.junit.jupiter:junit-jupiter-api:5.8.2')
testImplementation('org.powermock:powermock-core:2.0.9')
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.8.2')
testRuntimeOnly('org.slf4j:slf4j-jdk14:1.8.0-beta4')
testRuntimeOnly("org.lwjgl:lwjgl::$lwjglNatives")
testRuntimeOnly("org.lwjgl:lwjgl-glfw::$lwjglNatives")
testRuntimeOnly("org.lwjgl:lwjgl-opengl::$lwjglNatives")
testRuntimeOnly("org.lwjgl:lwjgl-stb::$lwjglNatives")
}
test {
useJUnitPlatform()
}
ext {
MANIFESTS = [
'': [
'Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
'Git-Commit': GIT_INFO.abbreviatedId,
'Git-Branch': GIT_INFO.branch,
'Build-Number': "${System.getenv('BUILD_NUMBER')?:0}",
] as LinkedHashMap,
'net/minecraftforge/fml/earlydisplay/': [
'Specification-Title': 'FMLEarlyDisplay',
'Specification-Vendor': 'Forge Development LLC',
'Specification-Version': '1',
'Implementation-Title': 'FML Early Display',
'Implementation-Version': '1.0',
'Implementation-Vendor': 'Forge'
] as LinkedHashMap
]
}
jar.doFirst {
MANIFESTS.each { pkg, values ->
if (pkg == '')
manifest.attributes(values)
else
manifest.attributes(values, pkg)
}
}
tasks.withType(JavaCompile) {
options.compilerArgs << '-Xlint:unchecked'
}
license {
header = rootProject.file('LICENSE-header.txt')
include 'net/minecraftforge/'
}
publishing.publications.mavenJava(MavenPublication) {
from components.java
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
public enum ColourScheme {
RED(new Colour(239, 50, 61), new Colour(255, 255, 255)),
BLACK(new Colour(0, 0, 0), new Colour(255, 255, 255));
private final Colour background;
private final Colour foreground;
ColourScheme(final Colour background, final Colour foreground) {
this.background = background;
this.foreground = foreground;
}
public Colour background() {
return background;
}
public Colour foreground() {
return foreground;
}
public record Colour(int red, int green, int blue) {
public float redf() {
return ((float)red)/255f;
}
public float greenf() {
return ((float)green)/255f;
}
public float bluef() {
return ((float)blue)/255f;
}
public int packedint(int a) {
return ((a & 0xff) << 24) | ((blue & 0xff) << 16) | ((green & 0xff) << 8) | (red & 0xff);
}
}
}

View file

@ -0,0 +1,561 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
import joptsimple.OptionParser;
import net.minecraftforge.fml.loading.FMLConfig;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.FMLPaths;
import net.minecraftforge.fml.loading.ImmediateWindowProvider;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.PointerBuffer;
import org.lwjgl.glfw.GLFWImage;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.stb.STBImage;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.tinyfd.TinyFileDialogs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Desktop;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL.createCapabilities;
import static org.lwjgl.opengl.GL32C.*;
/**
* The Loading Window that is opened Immediately after Forge starts.
* It is called from the ModDirTransformerDiscoverer, the soonest method that ModLauncher calls into Forge code.
* In this way, we can be sure that this will not run before any transformer or injection.
*
* The window itself is spun off into a secondary thread, and is handed off to the main game by Forge.
*
* Because it is created so early, this thread will "absorb" the context from OpenGL.
* Therefore, it is of utmost importance that the Context is made Current for the main thread before handoff,
* otherwise OS X will crash out.
*
* Based on the prior ClientVisualization, with some personal touches.
*/
public class DisplayWindow implements ImmediateWindowProvider {
private static final int[][] GL_VERSIONS = new int[][] {{4,6}, {4,5}, {4,4}, {4,3}, {4,2}, {4,1}, {4,0}, {3,3}, {3,2}};
private static final Logger LOGGER = LoggerFactory.getLogger("EARLYDISPLAY");
private final AtomicBoolean tickSemaphore = new AtomicBoolean(true);
private ColourScheme colourScheme;
private ElementShader elementShader;
private RenderElement.DisplayContext context;
private List<RenderElement> elements;
private int framecount;
private EarlyFramebuffer framebuffer;
private ScheduledFuture<?> windowTick;
private PerformanceInfo performanceInfo;
private ScheduledFuture<?> performanceTick;
// The GL ID of the window. Used for all operations
private long window;
// The thread that contains and ticks the window while Forge is loading mods
private ScheduledExecutorService renderScheduler;
private int fbWidth;
private int fbHeight;
private int fbScale;
private int winWidth;
private int winHeight;
private int winX;
private int winY;
private final Semaphore renderLock = new Semaphore(1);
private boolean maximized;
private String glVersion;
private SimpleFont font;
@Override
public String name() {
return "fmlearlywindow";
}
@Override
public Runnable initialize(String[] arguments) {
final OptionParser parser = new OptionParser();
var mcversionopt = parser.accepts("fml.mcVersion").withRequiredArg().ofType(String.class);
var forgeversionopt = parser.accepts("fml.forgeVersion").withRequiredArg().ofType(String.class);
var widthopt = parser.accepts("width")
.withRequiredArg().ofType(Integer.class)
.defaultsTo(FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_WIDTH));
var heightopt = parser.accepts("height")
.withRequiredArg().ofType(Integer.class)
.defaultsTo(FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_HEIGHT));
var maximizedopt = parser.accepts("earlywindow.maximized");
parser.allowsUnrecognizedOptions();
var parsed = parser.parse(arguments);
winWidth = parsed.valueOf(widthopt);
winHeight = parsed.valueOf(heightopt);
FMLConfig.updateConfig(FMLConfig.ConfigValue.EARLY_WINDOW_WIDTH, winWidth);
FMLConfig.updateConfig(FMLConfig.ConfigValue.EARLY_WINDOW_HEIGHT, winHeight);
fbScale = FMLConfig.getIntConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_FBSCALE);
try {
var optionLines = Files.readAllLines(FMLPaths.GAMEDIR.get().resolve(Paths.get("options.txt")));
var options = optionLines.stream().map(l->l.split(":")).filter(a->a.length == 2).collect(Collectors.toMap(a->a[0], a->a[1]));
var colourScheme = Boolean.parseBoolean(options.getOrDefault("darkMojangStudiosBackground", "false"));
this.colourScheme = colourScheme ? ColourScheme.BLACK : ColourScheme.RED;
} catch (IOException ioe) {
// No options
this.colourScheme = ColourScheme.RED; // default to red colourscheme
}
this.maximized = parsed.has(maximizedopt) || FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_MAXIMIZED);
var forgeVersion = parsed.valueOf(forgeversionopt);
StartupNotificationManager.modLoaderConsumer().ifPresent(c->c.accept("Forge loading "+ forgeVersion));
performanceInfo = new PerformanceInfo();
return start(parsed.valueOf(mcversionopt), forgeVersion);
}
// The width and height of the framebuffer that we're rendering to.
/**
* The main render loop.
* renderThread executes this.
*
* Performs initialization and then ticks the screen at 20 fps.
* When the thread is killed, context is destroyed.
*/
private void renderThreadFunc() {
if (!renderLock.tryAcquire()) return;
try {
tickSemaphore.set(true);
glfwMakeContextCurrent(window);
framebuffer.activate();
glViewport(0, 0, this.context.scaledWidth(), this.context.scaledHeight());
this.context.elementShader().activate();
this.context.elementShader().updateScreenSizeUniform(this.context.scaledWidth(), this.context.scaledHeight());
glClearColor(colourScheme.background().redf(), colourScheme.background().greenf(), colourScheme.background().bluef(), 1f);
paintFramebuffer();
this.context.elementShader().clear();
framebuffer.deactivate();
glViewport(0, 0, fbWidth, fbHeight);
framebuffer.draw(this.fbWidth, this.fbHeight);
// Swap buffers; we're done
glfwSwapBuffers(window);
} finally {
glfwMakeContextCurrent(0);
renderLock.release();
}
}
/**
* Render initialization methods called by the Render Thread.
* It compiles the fragment and vertex shaders for rendering text with STB, and sets up basic render framework.
*
* Nothing fancy, we just want to draw and render text.
*/
private void initRender(final @Nullable String mcVersion, final String forgeVersion) {
// This thread owns the GL render context now. We should make a note of that.
glfwMakeContextCurrent(window);
// Wait for one frame to be complete before swapping; enable vsync in other words.
glfwSwapInterval(1);
createCapabilities();
elementShader = new ElementShader();
try {
elementShader.init();
} catch (Throwable t) {
LOGGER.error("Crash during shader initialization", t);
crashElegantly("An error occurred initializing shaders.");
}
// Set the clear color based on the colour scheme
glClearColor(colourScheme.background().redf(), colourScheme.background().greenf(), colourScheme.background().bluef(), 1f);
// we always render to an 854x480 texture and then fit that to the screen - with a scale factor
this.context = new RenderElement.DisplayContext(854, 480, fbScale, elementShader, colourScheme, performanceInfo);
framebuffer = new EarlyFramebuffer(this.context);
try {
this.font = new SimpleFont("Monocraft.ttf", fbScale, 200000, 1 + RenderElement.INDEX_TEXTURE_OFFSET);
} catch (Throwable t) {
LOGGER.error("Crash during font initialization", t);
crashElegantly("An error occurred initializing a font for rendering. "+t.getMessage());
}
this.elements = new ArrayList<>(Arrays.asList(
RenderElement.squir(),
RenderElement.anvil(font),
RenderElement.logMessageOverlay(font),
RenderElement.forgeVersionOverlay(font, mcVersion+"-"+forgeVersion.split("-")[0]),
RenderElement.performanceBar(font),
RenderElement.progressBars(font)
));
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glfwMakeContextCurrent(0);
this.windowTick = renderScheduler.scheduleAtFixedRate(this::renderThreadFunc, 50, 50, TimeUnit.MILLISECONDS);
this.performanceTick = renderScheduler.scheduleAtFixedRate(performanceInfo::update, 0, 500, TimeUnit.MILLISECONDS);
}
/**
* Called every frame by the Render Thread to draw to the screen.
*/
void paintFramebuffer() {
// Clear the screen to our color
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
this.elements.removeIf(element -> !element.render(context, framecount));
if (tickSemaphore.compareAndSet(true, false)) // we only increment the framecount on a periodic basis
framecount++;
}
public void render(int alpha) {
var currentVAO = glGetInteger(GL_VERTEX_ARRAY_BINDING);
var currentFB = glGetInteger(GL_READ_FRAMEBUFFER_BINDING);
glfwSwapInterval(0);
glViewport(0, 0, this.context.scaledWidth(), this.context.scaledHeight());
RenderElement.globalAlpha = alpha;
framebuffer.activate();
glClearColor(colourScheme.background().redf(), colourScheme.background().greenf(), colourScheme.background().bluef(), alpha / 255f);
elementShader.activate();
elementShader.updateScreenSizeUniform(this.context.scaledWidth(), this.context.scaledHeight());
paintFramebuffer();
elementShader.clear();
framebuffer.deactivate();
glBindVertexArray(currentVAO);
glBindFramebuffer(GL_FRAMEBUFFER, currentFB);
}
/**
* Start the window and Render Thread; we're ready to go.
*/
public Runnable start(@Nullable String mcVersion, final String forgeVersion) {
initWindow(mcVersion);
renderScheduler = Executors.newSingleThreadScheduledExecutor();
renderScheduler.schedule(() -> initRender(mcVersion, forgeVersion), 1, TimeUnit.MILLISECONDS);
return this::periodicTick;
}
private static final String ERROR_URL = "https://links.minecraftforge.net/early-display-errors";
@Override
public String getGLVersion() {
return this.glVersion;
}
private void crashElegantly(String errorDetails) {
String qrText;
try (var is = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("/glfailure.txt")))) {
qrText = is.lines().collect(Collectors.joining("\n"));
} catch (IOException ioe) {
qrText = "";
}
StringBuilder msgBuilder = new StringBuilder(2000);
msgBuilder.append("Failed to initialize graphics window with current settings.\n");
msgBuilder.append("\n\n");
msgBuilder.append("Failure details:\n");
msgBuilder.append(errorDetails);
msgBuilder.append("\n\n");
msgBuilder.append("If you click yes, we will try and open " + ERROR_URL + " in your default browser");
LOGGER.error("ERROR DISPLAY\n"+msgBuilder.toString());
var res = TinyFileDialogs.tinyfd_messageBox("Minecraft: Forge",msgBuilder.toString(), "yesno", "error", false);
if (res) {
try {
Desktop.getDesktop().browse(URI.create(ERROR_URL));
} catch (IOException ioe) {
TinyFileDialogs.tinyfd_messageBox("Minecraft: Forge", "Sadly, we couldn't open your browser.\nVisit " + ERROR_URL, "ok", "error", false);
}
}
System.exit(1);
}
/**
* Called to initialize the window when preparing for the Render Thread.
*
* The act of calling glfwInit here creates a concurrency issue; GL doesn't know whether we're gonna call any
* GL functions from the secondary thread and the main thread at the same time.
*
* It's then our job to make sure this doesn't happen, only calling GL functions where the Context is Current.
* As long as we can verify that, then GL (and things like OS X) have no complaints with doing this.
*
* @param mcVersion Minecraft Version
* @return The selected GL profile as an integer pair
*/
public void initWindow(@Nullable String mcVersion) {
// Initialize GLFW with a time guard, in case something goes wrong
long glfwInitBegin = System.nanoTime();
if (!glfwInit()) {
crashElegantly("We are unable to initialize the graphics system.\nglfwInit failed.\n");
throw new IllegalStateException("Unable to initialize GLFW");
}
long glfwInitEnd = System.nanoTime();
if (glfwInitEnd - glfwInitBegin > 1e9) {
LOGGER.error("WARNING : glfwInit took {} seconds to start.", (glfwInitEnd - glfwInitBegin) / 1.0e9);
}
// Clear the Last Exception (#7285 - Prevent Vanilla throwing an IllegalStateException due to invalid controller mappings)
handleLastGLFWError((error, description) -> LOGGER.error(String.format("Suppressing Last GLFW error: [0x%X]%s", error, description)));
// Set window hints for the new window we're gonna create.
glfwDefaultWindowHints();
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
if (mcVersion != null) {
// this emulates what we would get without early progress window
// as vanilla never sets these, so GLFW uses the first window title
// set them explicitly to avoid it using "FML early loading progress" as the class
String vanillaWindowTitle = "Minecraft* " + mcVersion;
glfwWindowHintString(GLFW_X11_CLASS_NAME, vanillaWindowTitle);
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, vanillaWindowTitle);
}
long primaryMonitor = glfwGetPrimaryMonitor();
if (primaryMonitor == 0) {
LOGGER.error("Failed to find a primary monitor - this means LWJGL isn't working properly");
crashElegantly("Failed to locate a primary monitor.\nglfwGetPrimaryMonitor failed.\n");
throw new IllegalStateException("Can't find a primary monitor");
}
GLFWVidMode vidmode = glfwGetVideoMode(primaryMonitor);
if (vidmode == null) {
LOGGER.error("Failed to get the current display video mode.");
crashElegantly("Failed to get current display resolution.\nglfwGetVideoMode failed.\n");
throw new IllegalStateException("Can't get a resolution");
}
long window;
int versidx= 0;
final String[] lastGLError=new String[GL_VERSIONS.length];
do {
LOGGER.info("Trying GL version "+GL_VERSIONS[versidx][0]+"."+GL_VERSIONS[versidx][1]);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, GL_VERSIONS[versidx][0]); // we try our versions one at a time
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, GL_VERSIONS[versidx][1]);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
window = glfwCreateWindow(winWidth, winHeight, "Minecraft: Forge Loading...", 0L, 0L);
var erridx = versidx;
handleLastGLFWError((error, description) -> lastGLError[erridx] = String.format("Trying %d.%d: GLFW error: [0x%X]%s", GL_VERSIONS[erridx][0], GL_VERSIONS[erridx][1], error, description));
if (lastGLError[versidx] != null) {
LOGGER.trace(lastGLError[versidx]);
}
versidx++;
if (versidx== GL_VERSIONS.length) {
LOGGER.error("Failed to find any valid GLFW profile. "+lastGLError[0]);
crashElegantly("Failed to find a valid GLFW profile.\nWe tried "+
Arrays.stream(GL_VERSIONS).map(p->p[0]+"."+p[1]).collect(Collectors.joining(", "))+
" but none of them worked.\n"+String.join("\n", Arrays.asList(lastGLError)));
throw new IllegalStateException("Failed to create a GLFW window with any profile");
}
} while (window == 0);
var requestedVersion = GL_VERSIONS[versidx-1][0]+"."+GL_VERSIONS[versidx-1][1];
var maj = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MAJOR);
var min = glfwGetWindowAttrib(window, GLFW_CONTEXT_VERSION_MINOR);
var gotVersion = maj+"."+min;
LOGGER.info("Requested GL version "+requestedVersion+" got version "+gotVersion);
this.glVersion = gotVersion;
this.window = window;
int[] x = new int[1];
int[] y = new int[1];
glfwGetMonitorPos(primaryMonitor, x, y);
int monitorX = x[0];
int monitorY = y[0];
// glfwSetWindowSizeLimits(window, 854, 480, GLFW_DONT_CARE, GLFW_DONT_CARE);
if (this.maximized) {
glfwMaximizeWindow(window);
}
glfwGetWindowSize(window, x, y);
this.winWidth = x[0];
this.winHeight = y[0];
glfwSetWindowPos(window, (vidmode.width() - this.winWidth) / 2 + monitorX, (vidmode.height() - this.winHeight) / 2 + monitorY);
// Attempt setting the icon
int[] channels = new int[1];
try (var glfwImgBuffer = GLFWImage.create(MemoryUtil.getAllocator().malloc(GLFWImage.SIZEOF), 1)) {
final ByteBuffer imgBuffer;
try (GLFWImage glfwImages = GLFWImage.malloc()) {
imgBuffer = STBHelper.loadImageFromClasspath("forge_logo.png", 20000, x, y, channels);
glfwImgBuffer.put(glfwImages.set(x[0], y[0], imgBuffer));
glfwSetWindowIcon(window, glfwImgBuffer);
STBImage.stbi_image_free(imgBuffer);
}
} catch (NullPointerException e) {
System.err.println("Failed to load forge logo");
}
handleLastGLFWError((error, description) -> LOGGER.debug(String.format("Suppressing GLFW icon error: [0x%X]%s", error, description)));
glfwSetFramebufferSizeCallback(window, this::fbResize);
glfwSetWindowPosCallback(window, this::winMove);
glfwSetWindowSizeCallback(window, this::winResize);
// Show the window
glfwShowWindow(window);
glfwGetWindowPos(window, x, y);
this.winX = x[0];
this.winY = y[0];
glfwGetFramebufferSize(window, x, y);
this.fbWidth = x[0];
this.fbHeight = y[0];
glfwPollEvents();
}
private void winResize(long window, int width, int height) {
if (window == this.window && width != 0 && height != 0) {
this.winWidth = width;
this.winHeight = height;
}
}
private void fbResize(long window, int width, int height) {
if (window == this.window && width != 0 && height != 0) {
this.fbWidth = width;
this.fbHeight = height;
}
}
private void winMove(long window, int x, int y) {
if (window == this.window) {
this.winX = x;
this.winY = y;
}
}
private void handleLastGLFWError(BiConsumer<Integer, String> handler) {
try (MemoryStack memorystack = MemoryStack.stackPush()) {
PointerBuffer pointerbuffer = memorystack.mallocPointer(1);
int error = org.lwjgl.glfw.GLFW.glfwGetError(pointerbuffer);
if (error != GLFW_NO_ERROR) {
long pDescription = pointerbuffer.get();
String description = pDescription == 0L ? "" : MemoryUtil.memUTF8(pDescription);
handler.accept(error, description);
}
}
}
/**
* Hand-off the window to the vanilla game.
* Called on the main thread instead of the game's initialization.
*
* @return the Window we own.
*/
public long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitorSupplier) {
// we have to spin wait for the window ticker
while (!this.windowTick.isDone()) {
this.windowTick.cancel(false);
}
var tries = 0;
var renderlockticket = false;
do {
try {
renderlockticket = renderLock.tryAcquire(100, TimeUnit.MILLISECONDS);
if (++tries > 9) {
Thread.dumpStack();
crashElegantly("We seem to be having trouble handing off the window, tried for 1 second");
}
} catch (InterruptedException e) {
Thread.interrupted();
}
} while (!renderlockticket);
// schedule a 50 ms ticker to try and smooth out the rendering
renderScheduler.scheduleAtFixedRate(()->tickSemaphore.set(true), 50, 50, TimeUnit.MILLISECONDS);
glfwMakeContextCurrent(window);
// Set the title to what the game wants
glfwSetWindowTitle(window, title.get());
glfwSwapInterval(0);
// Clean up our hooks
glfwSetFramebufferSizeCallback(window, null).free();
glfwSetWindowPosCallback(window, null).free();
glfwSetWindowSizeCallback(window, null).free();
return window;
}
@Override
public boolean positionWindow(final Optional<Object> monitor, final IntConsumer widthSetter, final IntConsumer heightSetter, final IntConsumer xSetter, final IntConsumer ySetter) {
widthSetter.accept(this.winWidth);
heightSetter.accept(this.winHeight);
xSetter.accept(this.winX);
ySetter.accept(this.winY);
return true;
}
@Override
public void updateFramebufferSize(final IntConsumer width, final IntConsumer height) {
width.accept(this.fbWidth);
height.accept(this.fbHeight);
}
private Method loadingOverlay;
@SuppressWarnings("unchecked")
@Override
public <T> Supplier<T> loadingOverlay(final Supplier<?> mc, final Supplier<?> ri, final Consumer<Optional<Throwable>> ex, final boolean fade) {
try {
return (Supplier<T>)loadingOverlay.invoke(null, mc, ri, ex, this);
} catch (Throwable e) {
throw new IllegalStateException("How did you get here?", e);
}
}
@Override
public void updateModuleReads(final ModuleLayer layer) {
var fm = layer.findModule("forge").orElseThrow();
getClass().getModule().addReads(fm);
var clz = FMLLoader.getGameLayer().findModule("forge").map(l->Class.forName(l, "net.minecraftforge.client.loading.ForgeLoadingOverlay")).orElseThrow();
var methods = Arrays.stream(clz.getMethods()).filter(m-> Modifier.isStatic(m.getModifiers())).collect(Collectors.toMap(Method::getName, Function.identity()));
loadingOverlay = methods.get("newInstance");
}
public int getFramebufferTextureId() {
return framebuffer.getTexture();
}
public RenderElement.DisplayContext context() {
return this.context;
}
@Override
public void periodicTick() {
glfwPollEvents();
}
public void addMojangTexture(final int textureId) {
this.elements.add(0, RenderElement.mojang(textureId, framecount));
// this.elements.get(0).retire(framecount + 1);
}
public void close() {
// Close the Render Scheduler thread
renderScheduler.shutdown();
this.framebuffer.close();
this.context.elementShader().close();
SimpleBufferBuilder.destroy();
}
}

View file

@ -0,0 +1,67 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
import java.nio.IntBuffer;
import static net.minecraftforge.fml.earlydisplay.RenderElement.clamp;
import static org.lwjgl.opengl.GL32C.*;
public class EarlyFramebuffer {
private final int framebuffer;
private final int texture;
private final RenderElement.DisplayContext context;
EarlyFramebuffer(final RenderElement.DisplayContext context) {
this.context = context;
this.framebuffer = glGenFramebuffers();
this.texture = glGenTextures();
glBindFramebuffer(GL_FRAMEBUFFER, this.framebuffer);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, this.texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, context.width() * context.scale(), context.height() * context.scale(), 0, GL_RGBA, GL_UNSIGNED_BYTE, (IntBuffer)null);
glTexParameterIi(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterIi(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, this.texture, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void activate() {
glBindFramebuffer(GL_FRAMEBUFFER, this.framebuffer);
}
void deactivate() {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void draw(int windowFBWidth, int windowFBHeight) {
var wscale = ((float)windowFBWidth / this.context.width());
var hscale = ((float)windowFBHeight / this.context.height());
var scale = this.context.scale() * Math.min(wscale, hscale) / 2f;
var wleft = (int)(windowFBWidth * 0.5f - scale * this.context.width());
var wtop = (int)(windowFBHeight * 0.5f - scale * this.context.height());
var wright = (int)(windowFBWidth * 0.5f + scale * this.context.width());
var wbottom = (int)(windowFBHeight * 0.5f + scale * this.context.height());
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, this.framebuffer);
final var colour = this.context.colourScheme().background();
glClearColor(colour.redf(), colour.greenf(), colour.bluef(), 1f);
glClear(GL_COLOR_BUFFER_BIT);
// src Y are flipped, since our FB is flipped
glBlitFramebuffer(0, this.context.height() * this.context.scale(), this.context.width() * this.context.scale(), 0, clamp(wleft, 0, windowFBWidth), clamp(wtop, 0, windowFBHeight), clamp(wright, 0, windowFBWidth), clamp(wbottom, 0, windowFBHeight), GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
int getTexture() {
return this.texture;
}
public void close() {
glDeleteTextures(this.texture);
glDeleteFramebuffers(this.framebuffer);
}
}

View file

@ -0,0 +1,117 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
import static org.lwjgl.opengl.GL32C.*;
public class ElementShader {
private int program;
private int textureUniform;
private int screenSizeUniform;
private int renderTypeUniform;
public void init() {
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
// Bind the source of our shaders to the ones created above
glShaderSource(fragmentShader, """
#version 150 core
uniform sampler2D tex;
uniform int rendertype;
in vec2 fTex;
in vec4 fColour;
out vec4 fragColor;
void main() {
if (rendertype == 0)
fragColor = vec4(1,1,1,texture(tex, fTex).r) * fColour;
if (rendertype == 1)
fragColor = texture(tex, fTex) * fColour;
if (rendertype == 2)
fragColor = fColour;
}
""");
glShaderSource(vertexShader, """
#version 150 core
in vec2 position;
in vec2 tex;
in vec4 colour;
uniform vec2 screenSize;
out vec2 fTex;
out vec4 fColour;
void main() {
fTex = tex;
fColour = colour;
gl_Position = vec4((position/screenSize) * 2 - 1, 0.0, 1.0);
}
""");
// Compile the vertex and fragment elementShader so that we can use them
glCompileShader(vertexShader);
if (glGetShaderi(vertexShader, GL_COMPILE_STATUS) == GL_FALSE) {
throw new IllegalStateException("VertexShader linkage failure. \n" + glGetShaderInfoLog(vertexShader));
}
glCompileShader(fragmentShader);
if (glGetShaderi(fragmentShader, GL_COMPILE_STATUS) == GL_FALSE) {
throw new IllegalStateException("FragmentShader linkage failure. \n" + glGetShaderInfoLog(fragmentShader));
}
var program = glCreateProgram();
glBindAttribLocation(program, 0, "position");
glBindAttribLocation(program, 1, "tex");
glBindAttribLocation(program, 2, "colour");
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
if (glGetProgrami(program, GL_LINK_STATUS) == GL_FALSE) {
throw new RuntimeException("ShaderProgram linkage failure. \n" + glGetProgramInfoLog(program));
}
this.program = program;
glDetachShader(program, vertexShader);
glDetachShader(program, fragmentShader);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
textureUniform = glGetUniformLocation(program, "tex");
screenSizeUniform = glGetUniformLocation(program, "screenSize");
renderTypeUniform = glGetUniformLocation(program, "rendertype");
activate();
}
public void activate() {
glUseProgram(program);
}
public void updateTextureUniform(int textureNumber) {
glUniform1i(textureUniform, textureNumber);
}
public void updateScreenSizeUniform(int width, int height) {
glUniform2f(screenSizeUniform, width, height);
}
public void updateRenderTypeUniform(RenderType type) {
glUniform1i(renderTypeUniform, type.ordinal());
}
public void clear() {
glUseProgram(0);
}
public void close() {
glDeleteProgram(program);
}
public enum RenderType {
FONT, TEXTURE, BAR;
}
public int program() {
return program;
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
import com.sun.management.OperatingSystemMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
public class PerformanceInfo {
private final OperatingSystemMXBean osBean;
private final MemoryMXBean memoryBean;
float memory;
private String text;
PerformanceInfo() {
osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class);
memoryBean = ManagementFactory.getMemoryMXBean();
}
void update() {
final MemoryUsage heapusage = memoryBean.getHeapMemoryUsage();
memory = (float) heapusage.getUsed() / heapusage.getMax();
var cpuLoad = osBean.getProcessCpuLoad();
String cpuText;
if (cpuLoad == -1) {
cpuText = String.format("*CPU: %.1f%%", osBean.getCpuLoad() * 100f);
} else {
cpuText = String.format("CPU: %.1f%%", cpuLoad * 100f);
}
text = String.format("Heap: %d/%d MB (%.1f%%) OffHeap: %d MB %s", heapusage.getUsed() >> 20, heapusage.getMax() >> 20, memory * 100.0, memoryBean.getNonHeapMemoryUsage().getUsed() >> 20, cpuText);
}
String text() {
return text;
}
float memory() {
return memory;
}
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
public class QuadHelper {
public static void loadQuad(SimpleBufferBuilder bb, float x0, float x1, float y0, float y1, float u0, float u1, float v0, float v1, int colour) {
bb.pos(x0, y0).tex(u0, v0).colour(colour).endVertex();
bb.pos(x1, y0).tex(u1, v0).colour(colour).endVertex();
bb.pos(x0, y1).tex(u0, v1).colour(colour).endVertex();
bb.pos(x1, y1).tex(u1, v1).colour(colour).endVertex();
}
public static void loadQuad(SimpleBufferBuilder bb, float x0, float x1, float y0, float y1, float u0, float u1, float v0, float v1) {
bb.pos(x0, y0).tex(u0, v0).endVertex();
bb.pos(x1, y0).tex(u1, v0).endVertex();
bb.pos(x0, y1).tex(u0, v1).endVertex();
bb.pos(x1, y1).tex(u1, v1).endVertex();
}
}

View file

@ -0,0 +1,343 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
import net.minecraftforge.fml.loading.progress.Message;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import static org.lwjgl.opengl.GL32C.*;
public class RenderElement {
static final int INDEX_TEXTURE_OFFSET = 5;
private final SimpleBufferBuilder bb;
private final Renderer renderer;
static int globalAlpha = 255;
private int retireCount;
interface Renderer {
void accept(SimpleBufferBuilder bb, DisplayContext context, int frame);
default Renderer then(Renderer r) {
if (r == null) return this;
return (bb, ctx, frame) -> {
r.accept(bb, ctx, frame);
this.accept(bb, ctx, frame);
};
}
}
interface TextureRenderer {
void accept(SimpleBufferBuilder bb, DisplayContext context, int[] size, int frame);
}
interface Initializer extends Supplier<Renderer> {}
interface TextGenerator {
void accept(SimpleBufferBuilder bb, SimpleFont fh, DisplayContext ctx);
}
public record DisplayContext(int width, int height, int scale, ElementShader elementShader, ColourScheme colourScheme, PerformanceInfo performance) {
public int scaledWidth() {
return scale() * width();
}
public int scaledHeight() {
return scale() * height();
}
}
public RenderElement(final Initializer rendererInitializer) {
this.bb = new SimpleBufferBuilder(1);
this.renderer = rendererInitializer.get();
}
public boolean render(DisplayContext ctx, int count) {
this.renderer.accept(bb, ctx, count);
return this.retireCount == 0 || this.retireCount < count;
}
public void retire(final int frame) {
this.retireCount = frame;
}
private static void startupLogMessages(SimpleBufferBuilder bb, SimpleFont font, DisplayContext context) {
List<StartupNotificationManager.AgeMessage> messages = StartupNotificationManager.getMessages();
List<SimpleFont.DisplayText> texts = new ArrayList<>();
for (int i = messages.size() - 1; i >= 0; i--) {
final StartupNotificationManager.AgeMessage pair = messages.get(i);
final float fade = clamp((4000.0f - (float) pair.age() - ( i - 4 ) * 1000.0f) / 5000.0f, 0.0f, 1.0f);
if (fade <0.01f) continue;
Message msg = pair.message();
int colour = Math.min((int)(fade * 255f), globalAlpha) << 24 | 0xFFFFFF;
texts.add(new SimpleFont.DisplayText(msg.getText()+"\n", colour));
}
font.generateVerticesForTexts(10, context.scaledHeight() - texts.size() * font.lineSpacing() + font.descent() - 10, bb, texts.toArray(SimpleFont.DisplayText[]::new));
}
public static RenderElement monag() {
return new RenderElement(RenderElement.initializeTexture("monagstudios.png", 45000, 4, (bb, ctx, sz, frame) -> {
var size = 256;
var x0 = (ctx.width() - 2 * size) / 2;
var y0 = 64;
QuadHelper.loadQuad(bb, x0, x0+size, y0, y0+size/2f, 0f, 1f, 0f, 0.5f, 0xFFFFFFFF);
QuadHelper.loadQuad(bb, x0+size, x0+2*size, y0, y0+size/2f, 0f, 1f, 0.5f, 1f, 0xFFFFFFFF);
}));
}
public static RenderElement mojang(final int textureId, final int frameStart) {
return new RenderElement(()->(bb, ctx, frame) -> {
var size = 256 * ctx.scale();
var x0 = (ctx.scaledWidth() - 2 * size) / 2;
var y0 = 64 * ctx.scale() + 32;
ctx.elementShader().updateTextureUniform(0);
ctx.elementShader().updateRenderTypeUniform(ElementShader.RenderType.TEXTURE);
var fade = Math.min((frame - frameStart) * 10, 255);
glBindTexture(GL_TEXTURE_2D, textureId);
bb.begin(SimpleBufferBuilder.Format.POS_TEX_COLOR, SimpleBufferBuilder.Mode.QUADS);
QuadHelper.loadQuad(bb, x0, x0+size, y0, y0+size/2f, 0f, 1f, 0f, 0.5f, (fade << 24) | 0xFFFFFF);
QuadHelper.loadQuad(bb, x0+size, x0+2*size, y0, y0+size/2f, 0f, 1f, 0.5f, 1f, (fade << 24) | 0xFFFFFF);
bb.draw();
glBindTexture(GL_TEXTURE_2D, 0);
});
}
public static RenderElement logMessageOverlay(SimpleFont font) {
return new RenderElement(RenderElement.initializeText(font, RenderElement::startupLogMessages));
}
public static RenderElement forgeVersionOverlay(SimpleFont font, String version) {
return new RenderElement(RenderElement.initializeText(font, (bb, fnt, ctx)->
font.generateVerticesForTexts(ctx.scaledWidth() - font.stringWidth(version) - 10,
ctx.scaledHeight() - font.lineSpacing() + font.descent() - 10, bb,
new SimpleFont.DisplayText(version, ctx.colourScheme.foreground().packedint(RenderElement.globalAlpha)))));
}
public static RenderElement squir() {
return new RenderElement(RenderElement.initializeTexture("squirrel.png", 45000, 3, (bb, context, size, frame) -> {
var inset = 5f;
var x0 = inset;
var x1 = inset + size[0] * context.scale();
var y0 = inset;
var y1 = inset + size[1] * context.scale();
int fade = (int) (Math.cos(frame * Math.PI / 16) * 16) + 16;
// int fade = 0xff;
var colour = (Math.min(fade, globalAlpha) & 0xff) << 24 | 0xffffff;
QuadHelper.loadQuad(bb, x0, x1, y0, y1, 0f, 1f, 0f, 1f, colour);
}));
}
public static RenderElement anvil(SimpleFont font) {
return new RenderElement(RenderElement.initializeTexture("forge_anvil.png", 20000, 2, (bb, context, size, frame) -> {
var x0 = context.scaledWidth() - size[0] * context.scale();
var x1 = context.scaledWidth();
var y0 = context.scaledHeight() - size[0] * context.scale() - font.descent() - font.lineSpacing();
var y1 = context.scaledHeight() - font.descent() - font.lineSpacing();
int frameidx = frame % 32;
float framepos = (frameidx * (float)size[0]) / size[1];
float framesize = size[0] / (float)size[1];
QuadHelper.loadQuad(bb, x0, x1, y0, y1, 0f, 1f, framepos, framepos+framesize, globalAlpha << 24 | 0xFFFFFF);
}));
}
public static RenderElement progressBars(SimpleFont font) {
return new RenderElement(() -> (bb, ctx, frame) -> RenderElement.startupProgressBars(font, bb, ctx, frame));
}
public static RenderElement performanceBar(SimpleFont font) {
return new RenderElement(() -> (bb, ctx, frame) -> RenderElement.memoryInfo(font, bb, ctx, frame));
}
public static void startupProgressBars(SimpleFont font, final SimpleBufferBuilder buffer, final DisplayContext context, final int frameNumber) {
Renderer acc = null;
var barCount = 2;
List<ProgressMeter> currentProgress = StartupNotificationManager.getCurrentProgress();
var size = currentProgress.size();
var alpha = 0xFF;
for (int i = 0; i < barCount && i < size; i++) {
final ProgressMeter pm = currentProgress.get(i);
Renderer barRenderer = barRenderer(i, alpha, font, pm, context);
acc = barRenderer.then(acc);
alpha >>= 1;
}
if (acc != null)
acc.accept(buffer, context, frameNumber);
}
private static final int BAR_HEIGHT = 20;
private static Renderer barRenderer(int cnt, int alpha, SimpleFont font, ProgressMeter pm, DisplayContext context) {
var barSpacing = font.lineSpacing() - font.descent() + BAR_HEIGHT;
var y = 250 * context.scale() + cnt * barSpacing;
var colour = (alpha << 24) | 0xFFFFFF;
Renderer bar;
if (pm.steps() == 0) {
bar = progressBar(ctx->new int[] {50, y + font.lineSpacing() - font.descent(), context.scaledWidth() - 100}, f->colour, RenderElement::indeterminateBar);
} else {
bar = progressBar(ctx -> new int[]{50, y + font.lineSpacing() - font.descent(), ctx.scaledWidth() - 100}, f -> colour, f -> new float[]{0f, pm.progress()});
}
Renderer label = (bb, ctx, frame) -> renderText(font, text(50, y, pm.label().getText(), colour), bb, ctx);
return bar.then(label);
}
private static float[] indeterminateBar(int frame) {
if (RenderElement.globalAlpha != 0xFF) {
return new float[] {0f,1f};
} else {
var progress = frame % 100;
return new float[]{clamp((progress - 2) / 100f, 0f, 1f), clamp((progress + 2) / 100f, 0f, 1f)};
}
}
private static void memoryInfo(SimpleFont font, final SimpleBufferBuilder buffer, final DisplayContext context, final int frameNumber) {
var y = 10 * context.scale();
PerformanceInfo pi = context.performance();
final int colour = hsvToRGB((1.0f - (float)Math.pow(pi.memory(), 1.5f)) / 3f, 1.0f, 0.5f);
var bar = progressBar(ctx -> new int[]{50, y, ctx.scaledWidth() - 100}, f -> colour, f -> new float[]{0f, pi.memory()});
var width = font.stringWidth(pi.text());
Renderer label = (bb, ctx, frame) -> renderText(font, text(ctx.scaledWidth() / 2 - width / 2, y + 18, pi.text(), context.colourScheme.foreground().packedint(globalAlpha)), bb, ctx);
bar.then(label).accept(buffer, context, frameNumber);
}
interface ColourFunction {
int colour(int frame);
}
interface ProgressDisplay {
float[] progress(int frame);
}
interface BarPosition {
int[] location(DisplayContext context);
}
public static Renderer progressBar(BarPosition position, ColourFunction colourFunction, ProgressDisplay progressDisplay) {
return (bb, context, frame) -> {
var colour = colourFunction.colour(frame);
var alpha = (colour & 0xFF000000) >> 24;
context.elementShader().updateTextureUniform(0);
context.elementShader().updateRenderTypeUniform(ElementShader.RenderType.BAR);
var progress = progressDisplay.progress(frame);
bb.begin(SimpleBufferBuilder.Format.POS_TEX_COLOR, SimpleBufferBuilder.Mode.QUADS);
var inset = 2;
var pos = position.location(context);
var x0 = pos[0];
var x1 = pos[0] + pos[2] + 4 * inset;
var y0 = pos[1];
var y1 = y0 + BAR_HEIGHT;
QuadHelper.loadQuad(bb, x0, x1, y0, y1, 0f, 0f, 0f, 0f, context.colourScheme().foreground().packedint(alpha));
x0 += inset;
x1 -= inset;
y0 += inset;
y1 -= inset;
QuadHelper.loadQuad(bb, x0, x1, y0, y1, 0f, 0f, 0f, 0f, context.colourScheme().background().packedint(RenderElement.globalAlpha));
x1 = x0 + inset + (int)(progress[1] * pos[2]);
x0 += inset + progress[0] * pos[2];
y0 += inset;
y1 -= inset;
QuadHelper.loadQuad(bb, x0, x1, y0, y1, 0f, 0f, 0f, 0f, colour);
bb.draw();
};
}
private static Initializer initializeText(SimpleFont font, TextGenerator textGenerator) {
return () -> (bb, context, frame) -> renderText(font, textGenerator, bb, context);
}
private static void renderText(final SimpleFont font, final TextGenerator textGenerator, final SimpleBufferBuilder bb, final DisplayContext context) {
context.elementShader().updateTextureUniform(font.textureNumber());
context.elementShader().updateRenderTypeUniform(ElementShader.RenderType.FONT);
bb.begin(SimpleBufferBuilder.Format.POS_TEX_COLOR, SimpleBufferBuilder.Mode.QUADS);
textGenerator.accept(bb, font, context);
bb.draw();
}
private static TextGenerator text(int x, int y, String text, int colour) {
return (bb, font, context) -> font.generateVerticesForTexts(x, y, bb, new SimpleFont.DisplayText(text, colour));
}
private static Initializer initializeTexture(final String textureFileName, int size, int textureNumber, TextureRenderer positionAndColour) {
return ()->{
int[] imgSize = STBHelper.loadTextureFromClasspath(textureFileName, size, GL_TEXTURE0 + textureNumber + INDEX_TEXTURE_OFFSET);
return (bb, ctx, frame) -> {
ctx.elementShader().updateTextureUniform(textureNumber + INDEX_TEXTURE_OFFSET);
ctx.elementShader().updateRenderTypeUniform(ElementShader.RenderType.TEXTURE);
renderTexture(bb, ctx, frame, imgSize, positionAndColour);
};
};
}
private static void renderTexture(SimpleBufferBuilder bb, DisplayContext context, int frame, int[] size, TextureRenderer positionAndColour) {
bb.begin(SimpleBufferBuilder.Format.POS_TEX_COLOR, SimpleBufferBuilder.Mode.QUADS);
positionAndColour.accept(bb, context, size, frame);
bb.draw();
}
public static float clamp(float num, float min, float max) {
if (num < min) {
return min;
} else {
return num > max ? max : num;
}
}
public static int clamp(int num, int min, int max) {
if (num < min) {
return min;
} else {
return num > max ? max : num;
}
}
public static int hsvToRGB(float hue, float saturation, float value) {
int i = (int)(hue * 6.0F) % 6;
float f = hue * 6.0F - (float)i;
float f1 = value * (1.0F - saturation);
float f2 = value * (1.0F - f * saturation);
float f3 = value * (1.0F - (1.0F - f) * saturation);
float f4;
float f5;
float f6;
switch(i) {
case 0:
f4 = value;
f5 = f3;
f6 = f1;
break;
case 1:
f4 = f2;
f5 = value;
f6 = f1;
break;
case 2:
f4 = f1;
f5 = value;
f6 = f3;
break;
case 3:
f4 = f1;
f5 = f2;
f6 = value;
break;
case 4:
f4 = f3;
f5 = f1;
f6 = value;
break;
case 5:
f4 = value;
f5 = f1;
f6 = f2;
break;
default:
throw new RuntimeException("Something went wrong when converting from HSV to RGB. Input was " + hue + ", " + saturation + ", " + value);
}
int j = clamp((int)(f4 * 255.0F), 0, 255);
int k = clamp((int)(f5 * 255.0F), 0, 255);
int l = clamp((int)(f6 * 255.0F), 0, 255);
return 0xFF << 24 | j << 16 | k << 8 | l;
}
}

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
import org.lwjgl.BufferUtils;
import org.lwjgl.stb.STBImage;
import org.lwjgl.system.MemoryUtil;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.util.Objects;
import static org.lwjgl.opengl.GL32C.*;
public class STBHelper {
public static ByteBuffer readFromClasspath(final String name, int initialCapacity) {
ByteBuffer buf;
try (var channel = Channels.newChannel(
Objects.requireNonNull(STBHelper.class.getClassLoader().getResourceAsStream(name), "The resource "+name+" cannot be found"))) {
buf = BufferUtils.createByteBuffer(initialCapacity);
while (true) {
var readbytes = channel.read(buf);
if (readbytes == -1) break;
if (buf.remaining() == 0) { // extend the buffer by 50%
var newBuf = BufferUtils.createByteBuffer(buf.capacity() * 3 / 2);
buf.flip();
newBuf.put(buf);
buf = newBuf;
}
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
buf.flip();
return MemoryUtil.memSlice(buf); // we trim the final buffer to the size of the content
}
public static int[] loadTextureFromClasspath(String file, int size, int textureNumber) {
int[] lw = new int[1];
int[] lh = new int[1];
int[] lc = new int[1];
var img = loadImageFromClasspath(file, size, lw, lh, lc);
var texid = glGenTextures();
glActiveTexture(textureNumber);
glBindTexture(GL_TEXTURE_2D, texid);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, lw[0], lh[0], 0, GL_RGBA, GL_UNSIGNED_BYTE, img);
glActiveTexture(GL_TEXTURE0);
MemoryUtil.memFree(img);
return new int[] {lw[0], lh[0]};
}
public static ByteBuffer loadImageFromClasspath(String file, int size, int[] width, int[] height, int[] channels) {
ByteBuffer buf = STBHelper.readFromClasspath(file, size);
return STBImage.stbi_load_from_memory(buf, width, height, channels, 4);
}
}

View file

@ -0,0 +1,517 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
import org.lwjgl.system.MemoryUtil;
import java.io.Closeable;
import java.nio.ByteBuffer;
import java.util.Arrays;
import static org.lwjgl.opengl.GL32C.*;
/**
* A very simple, Mojang inspired BufferBuilder.
* <em>This has been customized for 2d rendering such as text and simple planar textures</em>
* <p>
* Not bound to any specific format, ideally should be held onto for re-use.
* <p>
* Can be used for 'immediate mode' style rendering using {@link #draw()}, or
* upload to external vertex arrays for proper instancing using {@link #finishAndUpload()}.
* <p>
* This is a Triangles only buffer, all data uploaded is in Triangles.
* Quads are converted to triangles using {@code 0, 1, 2, 0, 2, 3}.
* <p>
* Any given {@link Format} should have its individual {@link Element} components
* buffered in the order specified by the {@link Format},
* followed by an {@link #endVertex()} call to prepare for the next vertex.
* <p>
* It is illegal to buffer primitives in any format other than the one specified to
* {@link #begin(Format, Mode)}.
*
* @author covers1624
*/
public class SimpleBufferBuilder implements Closeable {
private static final MemoryUtil.MemoryAllocator ALLOCATOR = MemoryUtil.getAllocator(false);
private static final int[] VERTEX_ARRAYS = new int[Format.values().length];
private static final int[] VERTEX_BUFFERS = new int[Format.values().length];
private static final int[] VERTEX_BUFFER_LENGTHS = new int[Format.values().length];
private static int elementBuffer = 0;
private static int elementBufferVertexLength = 0;
static {
Arrays.fill(VERTEX_ARRAYS, 0);
Arrays.fill(VERTEX_BUFFERS, 0);
Arrays.fill(VERTEX_BUFFER_LENGTHS, 0);
}
private long bufferAddr; // Pointer to the backing buffer.
private ByteBuffer buffer; // ByteBuffer view of the backing buffer.
private Format format; // The current format we are buffering.
private Mode mode; // The current mode we are buffering.
private boolean building; // If we are building the buffer.
private int elementIndex; // The current element index we are buffering. if elementIndex == format.types.length, we expect 'endVertex'
private int index; // The current index into the buffer we are writing to.
private int vertices; // The number of complete vertices we have buffered.
/**
* Create a new SimpleBufferBuilder with an initial capacity.
* <p>
* The buffer will be doubled as required.
* <p>
* Generally picking a small number, around 128/256 should be a
* safe bet. Provided you cache your buffers, it should not mean much overall.
*
* @param capacity The initial capacity in bytes.
*/
public SimpleBufferBuilder(int capacity) {
bufferAddr = ALLOCATOR.malloc(capacity);
buffer = MemoryUtil.memByteBuffer(bufferAddr, capacity);
}
public static void destroy() {
glDeleteBuffers(VERTEX_BUFFERS);
glDeleteBuffers(elementBuffer);
glDeleteVertexArrays(VERTEX_ARRAYS);
}
private static void ensureElementBufferLength(int vertices) {
if (elementBufferVertexLength >= vertices) {
return;
}
// treating it as immutable storage, even though it's not
final var newElementBuffer = glGenBuffers();
var newElementBufferVertexLength = Math.max(1024, elementBufferVertexLength);
while (newElementBufferVertexLength < vertices) {
newElementBufferVertexLength *= 2;
}
final var oldIndexCount = elementBufferVertexLength + elementBufferVertexLength / 2;
final var newIndexCount = newElementBufferVertexLength + newElementBufferVertexLength / 2;
// allocate new buffer
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, newElementBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, newIndexCount * 4L, GL_STATIC_DRAW);
// mapping avoids creating additional CPU copies of the data
// unsynchronized is fine because this is a brand-new buffer, and the old contents will be copied in afterward
// also can invalidate the whole buffer too, similarly because brand new, don't care what was there before
final var mappingOffset = oldIndexCount * 4;
final var mappingSize = (newIndexCount - oldIndexCount) * 4;
final var mappedBuffer = glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER, mappingOffset, mappingSize, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
if(mappedBuffer == null){
throw new NullPointerException("OpenGL buffer mapping failed");
}
final int quads = newElementBufferVertexLength / 4;
final int oldQuads = elementBufferVertexLength / 4;
// generate indices for the extension to the buffer
for (int i = oldQuads; i < quads; i++) {
// Quads are a bit different, we need to emit 2 triangles such that
// when combined they make up a single quad.
mappedBuffer.putInt(i * 4 + 0).putInt(i * 4 + 1).putInt(i * 4 + 2);
mappedBuffer.putInt(i * 4 + 1).putInt(i * 4 + 3).putInt(i * 4 + 2);
}
glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
if (elementBuffer != 0) {
// copy old data from previous element buffer
glBindBuffer(GL_COPY_READ_BUFFER, elementBuffer);
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_ELEMENT_ARRAY_BUFFER, 0, 0, mappingOffset);
glBindBuffer(GL_COPY_READ_BUFFER, 0);
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDeleteBuffers(elementBuffer);
elementBuffer = newElementBuffer;
elementBufferVertexLength = newElementBufferVertexLength;
}
/**
* Start building a new set of vertex data in the
* given format and mode.
*
* @param format The format to start building in.
* @param mode The mode to start building in.
*/
public SimpleBufferBuilder begin(Format format, Mode mode) {
if (bufferAddr == MemoryUtil.NULL) {
throw new IllegalStateException("Buffer has been freed."); // You already free'd the buffer
}
if (building) {
throw new IllegalStateException("Already building."); // Your already building verticies.
}
this.format = format;
this.mode = mode;
building = true;
elementIndex = 0;
ensureSpace(format.stride);
// Rewind ready for new data.
buffer.rewind();
buffer.limit(buffer.capacity());
return this;
}
/**
* Buffer a position element.
*
* @param x The x.
* @param y The y.
* @param z The z.
* @return The same builder.
*/
public SimpleBufferBuilder pos(float x, float y) {
if (!building) throw new IllegalStateException("Not building."); // You did not call begin.
if (elementIndex == format.types.length) throw new IllegalStateException("Expected endVertex"); // we have reached the end of elements to buffer for this vertex, we expected an endVertex call.
if (format.types[elementIndex] != Element.POS) throw new IllegalArgumentException("Expected " + format.types[elementIndex]); // You called the wrong method for the format order.
// Assumes that our POS element specifies the FLOAT data type.
buffer.putFloat(index + 0, x);
buffer.putFloat(index + 4, y);
// Increment index for the number of bytes we wrote and increment the element index.
index += format.types[elementIndex].width;
elementIndex++;
return this;
}
/**
* Buffer a texture element.
*
* @param u The u.
* @param v The v.
* @return The same builder.
*/
public SimpleBufferBuilder tex(float u, float v) {
if (!building) throw new IllegalStateException("Not building."); // You did not call begin.
if (elementIndex == format.types.length) throw new IllegalStateException("Expected endVertex"); // we have reached the end of elements to buffer for this vertex, we expected an endVertex call.
if (format.types[elementIndex] != Element.TEX) throw new IllegalArgumentException("Expected " + format.types[elementIndex]); // You called the wrong method for the format order.
// Assumes our TEX element specifies the FLOAT data type.
buffer.putFloat(index + 0, u);
buffer.putFloat(index + 4, v);
// Increment index for the number of bytes we wrote and increment the element index.
index += format.types[elementIndex].width;
elementIndex++;
return this;
}
/**
* Buffer a color element.
*
* @param r The red component. (0-1)
* @param g The green component. (0-1)
* @param b The blue component. (0-1)
* @param a The alpha component. (0-1)
* @return The same buffer.
*/
public SimpleBufferBuilder colour(float r, float g, float b, float a) {
// Expand floats to 0-255 and forward.
return colour((byte) (r * 255F), (byte) (g * 255F), (byte) (b * 255F), (byte) (a * 255F));
}
/**
* @see ColourScheme.Colour#packedint(int)
* @param packedColor an ABGR packed int
* @return the same buffer.
*/
public SimpleBufferBuilder colour(int packedColor) {
if (!building) throw new IllegalStateException("Not building."); // You did not call begin.
if (elementIndex == format.types.length) throw new IllegalStateException("Expected endVertex"); // we have reached the end of elements to buffer for this vertex, we expected an endVertex call.
if (format.types[elementIndex] != Element.COLOR) throw new IllegalArgumentException("Expected " + format.types[elementIndex]); // You called the wrong method for the format order.
// Assumes our COLOR element specifies the UNSIGNED_BYTE data type.
buffer.putInt(index + 0, packedColor);
// Increment index for the number of bytes we wrote and increment the element index.
index += format.types[elementIndex].width;
elementIndex++;
return this;
}
/**
* Buffer a color element.
*
* @param r The red component. (0-255)
* @param g The green component. (0-255)
* @param b The blue component. (0-255)
* @param a The alpha component. (0-255)
* @return The same buffer.
*/
public SimpleBufferBuilder colour(byte r, byte g, byte b, byte a) {
if (!building) throw new IllegalStateException("Not building."); // You did not call begin.
if (elementIndex == format.types.length) throw new IllegalStateException("Expected endVertex"); // we have reached the end of elements to buffer for this vertex, we expected an endVertex call.
if (format.types[elementIndex] != Element.COLOR) throw new IllegalArgumentException("Expected " + format.types[elementIndex]); // You called the wrong method for the format order.
// Assumes our COLOR element specifies the UNSIGNED_BYTE data type.
buffer.put(index + 0, r);
buffer.put(index + 1, g);
buffer.put(index + 2, b);
buffer.put(index + 3, a);
// Increment index for the number of bytes we wrote and increment the element index.
index += format.types[elementIndex].width;
elementIndex++;
return this;
}
/**
* End building the current vertex and prepare for the next.
*
* @return The same builder.
*/
public SimpleBufferBuilder endVertex() {
if (!building) throw new IllegalStateException("Not building."); // You did not call begin.
if (elementIndex != format.types.length) throw new IllegalStateException("Expected " + format.types[elementIndex]); // You did not finish building the vertex.
// Reset elementIndex
elementIndex = 0;
// Increment the number of vertices we have so far buffered.
vertices++;
// Make sure there is space for the next vertex.
ensureSpace(format.stride);
return this;
}
// Checks there is enough space in the buffer for specified number of bytes.
// If there is not enough space, the buffer is increased by 50%.
private void ensureSpace(int newBytes) {
int cap = buffer.capacity();
if (index + newBytes > cap) {
int newCap = Math.max(3 * cap / 2, 3 * newBytes / 2);
bufferAddr = ALLOCATOR.realloc(bufferAddr, newCap);
buffer = MemoryUtil.memByteBuffer(bufferAddr, newCap);
buffer.rewind();
}
}
/**
* Upload the current buffer.
* <p>
* This will bind a {@link org.lwjgl.opengl.GL32C#GL_ARRAY_BUFFER} and {@link org.lwjgl.opengl.GL32C#GL_ELEMENT_ARRAY_BUFFER}
* <p>
* The vertex data and index data is uploaded to their respective buffers.
* <p>
* Uploading the buffers finishes drawing and resets for the next buffer operation.
* <p>
* This should not be called in conjunction with {@link #draw()}
*
* @return The number of indexes that were uploaded.
*/
public int finishAndUpload() {
if (!building) throw new IllegalStateException("Not building.");
int indices;
try {
if (elementIndex == format.types.length) throw new IllegalStateException("Expected endVertex"); // You didn't finish building your vertex.
if (elementIndex != 0) throw new IllegalStateException("Not finished building vertex, Expected: " + format.types[elementIndex]); // You didn't finish building your vertex data.
if (vertices == 0) return 0; // No vertices buffered, lets not do anything.
if (vertices % mode.vertices != 0) throw new IllegalStateException("Does not contain vertices aligned to " + mode); // You did not put in enough vertices to cleanly slice the data into TRIANGLES/QUADS
// Reset position to 0, limit the buffer to our index.
buffer.position(0);
buffer.limit(index);
// Upload the raw vertex data in dynamic mode.
final int vbo = VERTEX_BUFFERS[format.ordinal()];
final int vboSize = VERTEX_BUFFER_LENGTHS[format.ordinal()];
glBindBuffer(GL_ARRAY_BUFFER, vbo);
if (vboSize < index) {
// expand buffer, it's not big enough
var newVBOSize = Math.max(1024, vboSize);
while (newVBOSize < index){
newVBOSize *= 2;
}
// because everything is overwritten anyway, we can do an in-place reallocation
glBufferData(GL_ARRAY_BUFFER, newVBOSize, GL_DYNAMIC_DRAW);
VERTEX_BUFFER_LENGTHS[format.ordinal()] = newVBOSize;
}
glBufferSubData(GL_ARRAY_BUFFER, 0, buffer);
// The number of indices for triangles is equal to our vertex count, as that is
// what we operate in. However, for Quads, we have exactly vertices + vertices / 2
// vertices once we convert the quads to triangles.
indices = mode == Mode.TRIANGLES ? vertices : vertices + vertices / 2;
if (mode == Mode.QUADS) {
ensureElementBufferLength(vertices);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer);
}
return indices;
} finally {
// Reset builder state for next begin call.
building = false;
vertices = 0;
index = 0;
}
}
/**
* Upload and draw this buffer using one of a number of re-usable set of buffers.
* <p>
* This will immediately upload the buffer, resetting this builder for the next
* buffer operation, and draw the uploaded data.
* <p>
* You will need to bind shaders, textures, etc, before calling this function.
*/
public void draw() {
if (!building) throw new IllegalStateException("Not building.");
int vao = VERTEX_ARRAYS[format.ordinal()];
int vbo = VERTEX_BUFFERS[format.ordinal()];
if (vao == 0) {
// These 3 buffers are paired, you can't allocate one without the others.
assert vbo == 0;
// Make new vertex array and buffers!
vao = glGenVertexArrays();
vbo = glGenBuffers();
// Cache the vertex array and buffers for future re-use.
VERTEX_ARRAYS[format.ordinal()] = vao;
VERTEX_BUFFERS[format.ordinal()] = vbo;
// Ask our Format to set up its data layout for the vertex array.
// but only once, the VAO saves this state
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
format.bind();
format.enable();
}
// Bind the vertex array and buffers!
glBindVertexArray(vao);
// Upload the data.
int indices = finishAndUpload();
if (mode == Mode.QUADS) {
glDrawElements(GL_TRIANGLES, indices, GL_UNSIGNED_INT, 0);
} else {
glDrawArrays(GL_TRIANGLES, 0, indices);
}
// Unbind the vertex array.
glBindVertexArray(0);
}
/**
* Clear this builder's cached buffer.
* <p>
* If you are completely done, call {@link #destroy()}
*/
@Override
public void close() {
ALLOCATOR.free(bufferAddr);
bufferAddr = MemoryUtil.NULL;
}
/**
* Represents a primitive mode that this builder is capable of buffering in.
*/
public enum Mode {
TRIANGLES(3),
QUADS(4),
;
public final int vertices;
Mode(int vertices) {
this.vertices = vertices;
}
}
/**
* Specifies a vertex element with a specific data type, number of primitives and a size in bytes.
*/
public enum Element {
POS(GL_FLOAT, 2, 2 * 4),
TEX(GL_FLOAT, 2, 2 * 4),
COLOR(GL_UNSIGNED_BYTE, 4, 4);
public final int glType;
public final int count;
public final int width;
Element(int glType, int count, int width) {
this.glType = glType;
this.count = count;
this.width = width;
}
}
/**
* Specifies a combination of vertex elements.
*/
public enum Format {
POS(Element.POS),
POS_TEX(Element.POS, Element.TEX),
POS_COLOR(Element.POS, Element.COLOR),
POS_TEX_COLOR(Element.POS, Element.TEX, Element.COLOR);
private final Element[] types;
public final int stride;
Format(Element... types) {
this.types = types;
// Stride is the width of each vertex in bytes.
stride = Arrays.stream(types).mapToInt(e -> e.width).sum();
}
/**
* set up the attribute pointers for this format.
* <p>
* Assumes that an array buffer is already bound and ready to go.
*/
public void bind() {
int offset = 0;
// Set up the pointers that tell GL where our interleaved
// vertex data is in the buffers.
for (int i = 0; i < types.length; i++) {
Element type = types[i];
switch (type.glType) {
case GL_FLOAT -> glVertexAttribPointer(i, type.count, GL_FLOAT, false, stride, offset);
case GL_UNSIGNED_BYTE -> glVertexAttribPointer(i, type.count, GL_UNSIGNED_BYTE, true, stride, offset);
default -> throw new IllegalStateException("Unknown glType, I don't know how to bind this vertex element: " + type);
}
// add to the offset for the next element.
offset += type.width;
}
}
/**
* Enables the vertex attributes this format contains.
*/
public void enable() {
for (int i = 0; i < types.length; i++) {
glEnableVertexAttribArray(i);
}
}
/**
* Disables the vertex attributes this format contains.
*/
public void disable() {
for (int i = 0; i < types.length; i++) {
glDisableVertexAttribArray(i);
}
}
}
}

View file

@ -0,0 +1,182 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.earlydisplay;
import org.lwjgl.BufferUtils;
import org.lwjgl.stb.STBTTAlignedQuad;
import org.lwjgl.stb.STBTTFontinfo;
import org.lwjgl.stb.STBTTPackContext;
import org.lwjgl.stb.STBTTPackRange;
import org.lwjgl.stb.STBTTPackedchar;
import org.lwjgl.system.MemoryStack;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import static org.lwjgl.opengl.GL32C.*;
import static org.lwjgl.stb.STBTruetype.*;
import static org.lwjgl.system.MemoryUtil.NULL;
public class SimpleFont {
private final int textureNumber;
private final int lineSpacing;
private final int descent;
private final int GLYPH_COUNT = 127-32;
private Glyph[] glyphs;
private record Glyph(char c, int charwidth, int[] pos, float[] uv) {
Pos loadQuad(Pos pos, int colour, SimpleBufferBuilder bb) {
final var x0 = pos.x() + pos()[0];
final var y0 = pos.y() + pos()[1];
final var x1 = pos.x() + pos()[2];
final var y1 = pos.y() + pos()[3];
bb.pos(x0, y0).tex(uv()[0], uv()[1]).colour(colour).endVertex();
bb.pos(x1, y0).tex(uv()[2], uv()[1]).colour(colour).endVertex();
bb.pos(x0, y1).tex(uv()[0], uv()[3]).colour(colour).endVertex();
bb.pos(x1, y1).tex(uv()[2], uv()[3]).colour(colour).endVertex();
return new Pos(pos.x()+charwidth(), pos.y(), pos.minx());
}
}
/**
* Build the font and store it in the textureNumber location
*/
public SimpleFont(String fontName, int scale, int bufferSize, int textureNumber) {
ByteBuffer buf = STBHelper.readFromClasspath(fontName, bufferSize);
var info = STBTTFontinfo.create();
if (!stbtt_InitFont(info, buf)) {
throw new IllegalStateException("Bad font");
}
var ascent = new float[1];
var descent = new float[1];
var lineGap = new float[1];
int fontSize = 24;
stbtt_GetScaledFontVMetrics(buf, 0, fontSize, ascent, descent, lineGap);
this.lineSpacing = (int)(ascent[0] - descent[0] + lineGap[0]);
this.descent = (int)Math.floor(descent[0]);
int fontTextureId = glGenTextures();
glActiveTexture(GL_TEXTURE0+textureNumber);
this.textureNumber = textureNumber;
glBindTexture(GL_TEXTURE_2D, fontTextureId);
try (var packedchars = STBTTPackedchar.malloc(GLYPH_COUNT)) {
int texwidth = 256;
int texheight = 128;
try (STBTTPackRange.Buffer packRanges = STBTTPackRange.malloc(1)) {
var bitmap = BufferUtils.createByteBuffer(texwidth * texheight);
try (STBTTPackRange packRange = STBTTPackRange.malloc()) {
packRanges.put(packRange.set(fontSize, 32, null, GLYPH_COUNT, packedchars, (byte) 1, (byte) 1));
packRanges.flip();
}
try (STBTTPackContext pc = STBTTPackContext.malloc()) {
stbtt_PackBegin(pc, bitmap, texwidth, texheight, 0, 1, NULL);
stbtt_PackSetOversampling(pc, 1, 1);
stbtt_PackSetSkipMissingCodepoints(pc, true);
stbtt_PackFontRanges(pc, buf, 0, packRanges);
stbtt_PackEnd(pc);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, texwidth, texheight, 0, GL_RED, GL_UNSIGNED_BYTE, bitmap);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
}
glActiveTexture(GL_TEXTURE0);
try (var q = STBTTAlignedQuad.malloc()) {
float[] x = new float[1];
float[] y = new float[1];
glyphs = new Glyph[GLYPH_COUNT];
for (int i = 0; i < GLYPH_COUNT; i++) {
x[0] = 0f;
y[0] = fontSize;
stbtt_GetPackedQuad(packedchars, texwidth, texheight, i, x, y, q, true);
glyphs[i] = new Glyph((char) (i + 32), (int) (x[0] - 0f), new int[]{(int) q.x0(), (int) q.y0(), (int) q.x1(), (int) q.y1()}, new float[]{q.s0(), q.t0(), q.s1(), q.t1()});
}
}
}
}
int lineSpacing() {
return lineSpacing;
}
int textureNumber() {
return textureNumber;
}
int descent() {
return descent;
}
public int stringWidth(String text) {
var bytes = text.getBytes(StandardCharsets.US_ASCII);
int len = 0;
for (int i = 0; i < bytes.length; i++) {
final byte c = bytes[i];
len += switch (c) {
case '\n', '\t' -> 0;
case ' ' -> glyphs[0].charwidth();
default -> {
if (c - 32 < this.GLYPH_COUNT && c > 32) {
yield this.glyphs[c - 32].charwidth();
} else {
yield 0;
}
}
};
}
return len;
}
private record Pos(int x, int y, int minx) {}
/**
* A piece of text to display
*
* @param string The text
* @param colour The colour of the text as an RGBA packed int
*/
public record DisplayText(String string, int colour) {
private byte[] asBytes() {
return string.getBytes(StandardCharsets.US_ASCII);
}
Pos generateStringArray(SimpleFont font, Pos pos, SimpleBufferBuilder bb) {
for (int i = 0; i < asBytes().length; i++) {
byte c = asBytes()[i];
pos = switch (c) {
case '\n' -> new Pos(pos.minx(), pos.y()+font.lineSpacing(), pos.minx());
case '\t' -> new Pos(pos.x()+font.glyphs[0].charwidth() * 4, pos.y(), pos.minx());
case ' ' -> new Pos(pos.x()+font.glyphs[0].charwidth(), pos.y(), pos.minx());
default -> {
if (c-32 < font.GLYPH_COUNT && c > 32) {
pos = font.glyphs[c - 32].loadQuad(pos, colour(), bb);
}
yield pos;
}
};
}
return pos;
}
}
/**
* Generate vertices for a set of display texts
* @param x The starting screen x coordinate
* @param y The starting screen y coordinate
* @param texts Some {@link DisplayText} to display
* @return a {@link SimpleBufferBuilder} that can draw the texts
*/
public SimpleBufferBuilder generateVerticesForTexts(int x, int y, SimpleBufferBuilder textBB, DisplayText... texts) {
var pos = new Pos(x, y, x);
for (DisplayText text : texts) {
pos = text.generateStringArray(this, pos, textBB);
}
return textBB;
}
}

View file

@ -0,0 +1 @@
net.minecraftforge.fml.earlydisplay.DisplayWindow

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

View file

@ -0,0 +1,13 @@
█▀▀▀▀▀█ ▀▄▄▄▀█ █ █▀▀▀▀▀█
█ ███ █ ███ ▀███▀ █ ███ █
█ ▀▀▀ █ ▀ ▄ ███▀█ █ ▀▀▀ █
▀▀▀▀▀▀▀ ▀ █ ▀ ▀▄█ ▀▀▀▀▀▀▀
█▀█▀▄▄▀▄▀ █▄▀▄ ▀▀▀ ▄▀▀▀▄▀
▄█▄▄ ▀▀███▀██▄ █▀ ▀▄▄
▀█▄▀ ▀▀▄▄▀▀ █▀█▄▄████ ▀▀█
▄▀▀▄▀ ▀▄▀▄█ ▀ ▀▀▀▄█ ▀▀▄
▀ ▀▀ ▄ ▄██▀ ▄█▀▀▀█▀█▀█
█▀▀▀▀▀█ ▄█▄▄▀▀▄█ ▀ █ ▀▀▀
█ ███ █ ▄▄ ▄ █ ▀██▀██▄██
█ ▀▀▀ █ ██▄███▀█ █ █ █▀
▀▀▀▀▀▀▀ ▀▀ ▀▀▀ ▀▀▀▀▀▀▀▀

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View file

@ -5,6 +5,8 @@
package net.minecraftforge.fml.loading;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -14,6 +16,7 @@ public class BackgroundWaiter {
private static ExecutorService runner = Executors.newSingleThreadExecutor();
public static void runAndTick(Runnable r, Runnable tick) {
ImmediateWindowHandler.updateProgress("Loading bootstrap resources");
final Future<?> work = runner.submit(r);
do {
tick.run();

View file

@ -5,7 +5,9 @@
package net.minecraftforge.fml.loading;
import com.electronwill.nightconfig.core.CommentedConfig;
import com.electronwill.nightconfig.core.ConfigSpec;
import com.electronwill.nightconfig.core.UnmodifiableCommentedConfig;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.electronwill.nightconfig.core.file.FileNotFoundAction;
import com.electronwill.nightconfig.core.io.ParsingException;
@ -15,21 +17,78 @@ import org.slf4j.Logger;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public class FMLConfig
{
public enum ConfigValue {
SPLASHSCREEN("splashscreen", Boolean.TRUE, "should we show the early splashscreen"),
MAX_THREADS("maxThreads", -1, "Max threads for early initialization parallelism, -1 is based on processor count", FMLConfig::maxThreads),
VERSION_CHECK("versionCheck", Boolean.TRUE, "Enable forge global version checking"),
DEFAULT_CONFIG_PATH("defaultConfigPath", "defaultconfigs", "Default config path for servers"),
DISABLE_OPTIMIZED_DFU("disableOptimizedDFU", Boolean.TRUE, "Disables Optimized DFU client-side - already disabled on servers"),
EARLY_WINDOW_PROVIDER("earlyWindowProvider", "fmlearlywindow", "Early window provider"),
EARLY_WINDOW_WIDTH("earlyWindowWidth", 854, "Early window width"),
EARLY_WINDOW_HEIGHT("earlyWindowHeight", 480, "Early window height"),
EARLY_WINDOW_FBSCALE("earlyWindowFBScale", 2, "Early window framebuffer scale"),
EARLY_WINDOW_MAXIMIZED("earlyWindowMaximized", true, "Early window starts maximized")
;
private final String entry;
private final Object defaultValue;
private final String comment;
private final Class<?> valueType;
private final Function<Object, Object> entryFunction;
ConfigValue(final String entry, final Object defaultValue, final String comment) {
this(entry, defaultValue, comment, Function.identity());
}
ConfigValue(final String entry, final Object defaultValue, final String comment, Function<Object, Object> entryFunction) {
this.entry = entry;
this.defaultValue = defaultValue;
this.comment = comment;
this.valueType = defaultValue.getClass();
this.entryFunction = entryFunction;
}
void buildConfigEntry(ConfigSpec spec, CommentedConfig commentedConfig) {
if (this.defaultValue instanceof List<?> list) {
spec.defineList(this.entry, list, e -> e instanceof String);
} else {
spec.define(this.entry, this.defaultValue);
}
commentedConfig.setComment(this.entry, this.comment);
}
@SuppressWarnings("unchecked")
private <T> T getConfigValue(CommentedFileConfig config) {
return (T) this.entryFunction.apply(config.get(this.entry));
}
public <T> void updateValue(final CommentedFileConfig configData, final T value) {
configData.set(this.entry, value);
}
}
private static Object maxThreads(final Object value) {
int val = (Integer)value;
if (val <= 0) return Runtime.getRuntime().availableProcessors();
else return val;
}
private static final Logger LOGGER = LogUtils.getLogger();
private static FMLConfig INSTANCE = new FMLConfig();
private static ConfigSpec configSpec = new ConfigSpec();
private static final FMLConfig INSTANCE = new FMLConfig();
private static final ConfigSpec configSpec = new ConfigSpec();
private static final CommentedConfig configComments = CommentedConfig.inMemory();
static {
configSpec.define("splashscreen", Boolean.TRUE);
configSpec.define("maxThreads", -1);
configSpec.define("versionCheck", Boolean.TRUE);
configSpec.define("defaultConfigPath", "defaultconfigs");
configSpec.define("disableOptimizedDFU", Boolean.TRUE);
for (ConfigValue cv: ConfigValue.values()) {
cv.buildConfigEntry(configSpec, configComments);
}
}
private CommentedFileConfig configData;
@ -38,7 +97,6 @@ public class FMLConfig
{
configData = CommentedFileConfig.builder(configFile).sync()
.onFileNotFound(FileNotFoundAction.copyData(Objects.requireNonNull(getClass().getResourceAsStream("/META-INF/defaultfmlconfig.toml"))))
.autosave().autoreload()
.writingMode(WritingMode.REPLACE)
.build();
try
@ -47,13 +105,14 @@ public class FMLConfig
}
catch (ParsingException e)
{
throw new RuntimeException("Failed to load FML config from " + configFile.toString(), e);
throw new RuntimeException("Failed to load FML config from " + configFile, e);
}
if (!configSpec.isCorrect(configData)) {
LOGGER.warn(CORE, "Configuration file {} is not correct. Correcting", configFile);
configSpec.correct(configData, (action, path, incorrectValue, correctedValue) ->
LOGGER.warn(CORE, "Incorrect key {} was corrected from {} to {}", path, incorrectValue, correctedValue));
LOGGER.info(CORE, "Incorrect key {} was corrected from {} to {}", path, incorrectValue, correctedValue));
}
configData.putAllComments(configComments);
configData.save();
}
@ -64,33 +123,34 @@ public class FMLConfig
if (LOGGER.isTraceEnabled(CORE))
{
LOGGER.trace(CORE, "Loaded FML config from {}", FMLPaths.FMLCONFIG.get());
LOGGER.trace(CORE, "Splash screen is {}", FMLConfig.splashScreenEnabled());
LOGGER.trace(CORE, "Max threads for mod loading computed at {}", FMLConfig.loadingThreadCount());
LOGGER.trace(CORE, "Version check is {}", FMLConfig.runVersionCheck());
LOGGER.trace(CORE, "Default config paths at {}", FMLConfig.defaultConfigPath());
for (ConfigValue cv: ConfigValue.values()) {
LOGGER.trace(CORE, "FMLConfig {} is {}", cv.entry, cv.getConfigValue(INSTANCE.configData));
}
}
FMLPaths.getOrCreateGameRelativePath(Paths.get(FMLConfig.defaultConfigPath()));
FMLPaths.getOrCreateGameRelativePath(Paths.get(FMLConfig.getConfigValue(ConfigValue.DEFAULT_CONFIG_PATH)));
}
public static boolean splashScreenEnabled() {
return INSTANCE.configData.<Boolean>getOptional("splashscreen").orElse(Boolean.FALSE);
public static String getConfigValue(ConfigValue v) {
return v.getConfigValue(INSTANCE.configData);
}
public static int loadingThreadCount() {
int val = INSTANCE.configData.<Integer>getOptional("maxThreads").orElse(-1);
if (val <= 0) return Runtime.getRuntime().availableProcessors();
return val;
public static boolean getBoolConfigValue(ConfigValue v) {
return v.getConfigValue(INSTANCE.configData);
}
public static boolean runVersionCheck() {
return INSTANCE.configData.<Boolean>getOptional("versionCheck").orElse(Boolean.TRUE);
public static int getIntConfigValue(ConfigValue v) {
return v.getConfigValue(INSTANCE.configData);
}
public static <A> List<A> getListConfigValue(ConfigValue v) {
return v.getConfigValue(INSTANCE.configData);
}
public static <T> void updateConfig(ConfigValue v, T value) {
v.updateValue(INSTANCE.configData, value);
INSTANCE.configData.save();
}
public static String defaultConfigPath() {
return INSTANCE.configData.<String>getOptional("defaultConfigPath").orElse("defaultconfigs");
}
public static boolean isOptimizedDFUDisabled() {
return INSTANCE.configData.<Boolean>getOptional("disableOptimizedDFU").orElse(Boolean.TRUE);
return getConfigValue(ConfigValue.DEFAULT_CONFIG_PATH);
}
}

View file

@ -16,8 +16,7 @@ import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModValidator;
import net.minecraftforge.accesstransformer.service.AccessTransformerService;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.loading.progress.EarlyProgressVisualization;
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.fml.loading.targets.CommonLaunchHandler;
import net.minecraftforge.forgespi.Environment;
import net.minecraftforge.forgespi.coremod.ICoreModProvider;
@ -152,7 +151,6 @@ public class FMLLoader
versionInfo = new VersionInfo(arguments);
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Early Loading!"));
accessTransformer.getExtension().accept(Pair.of(naming, "srg"));
LOGGER.debug(CORE,"Received command line version data : {}", versionInfo);
@ -169,7 +167,6 @@ public class FMLLoader
}
public static List<ITransformationService.Resource> completeScan(IModuleLayerManager layerManager) {
progressWindowTick = EarlyProgressVisualization.INSTANCE.accept(dist, commonLaunchHandler.isData(), versionInfo.mcVersion());
moduleLayerManager = layerManager;
languageLoadingProvider = new LanguageLoadingProvider();
backgroundScanHandler = modValidator.stage2Validation();
@ -205,9 +202,10 @@ public class FMLLoader
return dist;
}
public static void beforeStart(ClassLoader launchClassLoader)
public static void beforeStart(ModuleLayer gameLayer)
{
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Launching minecraft"));
ImmediateWindowHandler.acceptGameLayer(gameLayer);
ImmediateWindowHandler.updateProgress("Launching minecraft");
progressWindowTick.run();
}

View file

@ -0,0 +1,155 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
public class ImmediateWindowHandler {
private static final Logger LOGGER = LogManager.getLogger();
private static ImmediateWindowProvider provider;
private static ProgressMeter earlyProgress;
public static void load(final String launchTarget, final String[] arguments) {
if (!List.of("forgeclient", "forgeclientuserdev", "forgeclientdev").contains(launchTarget)) {
provider = new DummyProvider();
LOGGER.info("ImmediateWindowProvider not loading because launch target is {}", launchTarget);
} else if (!FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.SPLASHSCREEN)) {
provider = new DummyProvider();
LOGGER.info("ImmediateWindowProvider not loading because splash screen is disabled");
} else {
final var providername = FMLConfig.getConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_PROVIDER);
LOGGER.info("Loading ImmediateWindowProvider {}", providername);
final var maybeProvider = ServiceLoader.load(ImmediateWindowProvider.class)
.stream()
.map(ServiceLoader.Provider::get)
.filter(p -> Objects.equals(p.name(), providername))
.findFirst();
provider = maybeProvider.or(() -> {
LOGGER.info("Failed to find ImmediateWindowProvider {}, disabling", providername);
return Optional.of(new DummyProvider());
}).orElseThrow();
}
// Only update config if the provider isn't the dummy provider
if (!Objects.equals(provider.name(), "dummyprovider"))
FMLConfig.updateConfig(FMLConfig.ConfigValue.EARLY_WINDOW_PROVIDER, provider.name());
FMLLoader.progressWindowTick = provider.initialize(arguments);
earlyProgress = StartupNotificationManager.addProgressBar("EARLY", 0);
earlyProgress.label("Bootstrapping Minecraft");
}
public static long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor) {
return provider.setupMinecraftWindow(width, height, title, monitor);
}
public static boolean positionWindow(Optional<Object> monitor,IntConsumer widthSetter, IntConsumer heightSetter, IntConsumer xSetter, IntConsumer ySetter) {
return provider.positionWindow(monitor, widthSetter, heightSetter, xSetter, ySetter);
}
public static void updateFBSize(IntConsumer width, IntConsumer height) {
provider.updateFramebufferSize(width, height);
}
public static <T> Supplier<T> loadingOverlay(Supplier<?> mc, Supplier<?> ri, Consumer<Optional<Throwable>> ex, boolean fade) {
earlyProgress.complete();
return provider.loadingOverlay(mc, ri, ex, fade);
}
public static void acceptGameLayer(final ModuleLayer layer) {
provider.updateModuleReads(layer);
}
public static void renderTick() {
provider.periodicTick();
}
public static String getGLVersion() {
return provider.getGLVersion();
}
public static void updateProgress(final String message) {
earlyProgress.label(message);
}
private record DummyProvider() implements ImmediateWindowProvider {
private static Method NV_HANDOFF;
private static Method NV_POSITION;
private static Method NV_OVERLAY;
@Override
public String name() {
return "dummyprovider";
}
@Override
public Runnable initialize(String[] args) {
return () -> {};
}
@Override
public void updateFramebufferSize(final IntConsumer width, final IntConsumer height) {
}
@Override
public long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor) {
try {
var longsupplier = (LongSupplier)NV_HANDOFF.invoke(null, width, height, title, monitor);
return longsupplier.getAsLong();
} catch (Throwable e) {
throw new IllegalStateException("How did you get here?", e);
}
}
public boolean positionWindow(Optional<Object> monitor, IntConsumer widthSetter, IntConsumer heightSetter, IntConsumer xSetter, IntConsumer ySetter) {
try {
return (boolean)NV_POSITION.invoke(null, monitor, widthSetter, heightSetter, xSetter, ySetter);
} catch (Throwable e) {
throw new IllegalStateException("How did you get here?", e);
}
}
@SuppressWarnings("unchecked")
public <T> Supplier<T> loadingOverlay(Supplier<?> mc, Supplier<?> ri, Consumer<Optional<Throwable>> ex, boolean fade) {
try {
return (Supplier<T>) NV_OVERLAY.invoke(null, mc, ri, ex, fade);
} catch (Throwable e) {
throw new IllegalStateException("How did you get here?", e);
}
}
@Override
public String getGLVersion() {
return "3.2"; // The default minecraft GL version
}
@Override
public void updateModuleReads(final ModuleLayer layer) {
var fm = layer.findModule("forge");
if (fm.isPresent()) {
getClass().getModule().addReads(fm.get());
var clz = fm.map(l -> Class.forName(l, "net.minecraftforge.client.loading.NoVizFallback")).orElseThrow();
var methods = Arrays.stream(clz.getMethods()).filter(m -> Modifier.isStatic(m.getModifiers())).collect(Collectors.toMap(Method::getName, Function.identity()));
NV_HANDOFF = methods.get("windowHandoff");
NV_OVERLAY = methods.get("loadingOverlay");
NV_POSITION = methods.get("windowPositioning");
}
}
@Override
public void periodicTick() {
// NOOP
}
}
}

View file

@ -0,0 +1,117 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
/**
* This is for allowing the plugging in of alternative early display implementations.
*
* They can be selected through the config value "earlyWindowProvider" which defaults to "fmlearlywindow" implemented by {@link net.minecraftforge.fml.earlydisplay.DisplayWindow}
*
* There are a few key things to keep in mind if following through on implementation. You cannot access the game state as it
* literally DOES NOT EXIST at the time this object is constructed. You have to be very careful about managing the handoff
* to mojang, be sure that if you're trying to tick your window in a background thread (a nice idea!) that you properly
* transition to the main thread before handoff is complete. Do note that in general, you should construct your GL objects
* on the MAIN thread before starting your ticker, to ensure MacOS compatibility.
*
* No doubt many more things can be said here.
*/
public interface ImmediateWindowProvider {
/**
* @return The name of this window provider. Do NOT use fmlearlywindow.
*/
String name();
/**
* This is called very early on to initialize ourselves. Use this to initialize the window and other GL core resources.
*
* One thing we want to ensure is that we try and create the highest GL_PROFILE we can accomplish.
* GLFW_CONTEXT_VERSION_MAJOR,GLFW_CONTEXT_VERSION_MINOR should be as high as possible on the created window,
* and it should have all the typical profile settings.
*
* @param arguments The arguments provided to the Java process. This is the entire command line, so you can process
* stuff from it.
* @return A runnable that will be periodically ticked by FML during startup ON THE MAIN THREAD. This is usually
* a good place to put glfwPollEvents() tests.
*/
Runnable initialize(String[] arguments);
/**
* This will be called during the handoff to minecraft to update minecraft with the size of the framebuffer we have.
* Generally won't be called because Minecraft figures it out for itself.
* @param width Consumer of the framebuffer width
* @param height Consumer of the framebuffer height
*/
void updateFramebufferSize(IntConsumer width, IntConsumer height);
/**
* This is called to setup the minecraft window, as if Mojang had done it themselves in their Window class. This
* handoff is difficult to get right - you have to make sure that any activities you're doing to the window are finished
* prior to returning. You should try and setup the width and height as Mojang expects - the suppliers give you all that
* information. Alternatively, you can force Mojang to update from the current position of the window in {@link #positionWindow(Optional, IntConsumer, IntConsumer, IntConsumer, IntConsumer)}
* instead. This might give a more seamless experience.
*
* @param width This is the width of the window Mojang expects
* @param height This is the height of the Window Mojang expects.
* @param title This is the title for the window.
* @param monitor This is the monitor it should appear on.
* @return The window id
*/
long setupMinecraftWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor);
/**
* This is called after window handoff to allow us to tell Mojang about our window's position. This might give a
* preferrable user experience to users, because we just tell Mojang our truth, rather than accept theirs.
* @param monitor This is the monitor we're rendering on. Note that this is the Mojang monitor object. You might have trouble unwrapping it.
* @param widthSetter This sets the width on the Mojang side
* @param heightSetter This sets the height on the Mojang side
* @param xSetter This sets the x coordinate on the Mojang side
* @param ySetter This sets the y coordinate on the Mojang side
* @return true if you've handled the window positioning - this skips the "forced fullscreen" code until a later stage
*/
boolean positionWindow(Optional<Object> monitor, IntConsumer widthSetter, IntConsumer heightSetter, IntConsumer xSetter, IntConsumer ySetter);
/**
* Return a Supplier of an object extending the LoadingOverlay class from Mojang. This is what will be used once
* the Mojang window code has taken over rendering of the window, to render the later stages of the loading process.
*
* @param mc This supplies the Minecraft object
* @param ri This supplies the ReloadInstance object that tells us when the loading is finished
* @param ex This Consumes the final state of the loading - if it's an error you pass it the Throwable, otherwise you
* pass Optional.empty()
* @param fade This is the fade flag passed to LoadingOverlay. You probably want to ignore it.
* @param <T> This is the type LoadingOverlay to allow type binding on the Mojang side
* @return A supplier of your later LoadingOverlay screen.
*/
<T> Supplier<T> loadingOverlay(Supplier<?> mc, Supplier<?> ri, Consumer<Optional<Throwable>> ex, boolean fade);
/**
* This is called during the module loading process to allow us to find objects inside the GAME layer, such as a
* later loading screen.
* @param layer This is the GAME layer from ModLauncher
*/
void updateModuleReads(ModuleLayer layer);
/**
* This is called periodically during the loading process to "tick" the window. It is typically the same as the Runnable
* from {@link #initialize(String[])}
*/
void periodicTick();
/**
* This is called to construct a {@link net.minecraftforge.forgespi.locating.ForgeFeature} for the GL_VERSION we
* managed to create for the window. Should be a string of the format {MAJOR}.{MINOR}, such as 4.6, 4.5 or such.
*
* @return the GL profile we created
*/
String getGLVersion();
}

View file

@ -9,7 +9,7 @@ import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.IModuleLayerManager;
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.forgespi.language.IModLanguageProvider;
import net.minecraftforge.fml.loading.moddiscovery.ExplodedDirectoryLocator;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
@ -83,7 +83,7 @@ public class LanguageLoadingProvider
private void loadLanguageProviders() {
LOGGER.debug(CORE, "Found {} language providers", ServiceLoaderUtils.streamServiceLoader(()->serviceLoader, sce->LOGGER.error("Problem with language loaders")).count());
serviceLoader.forEach(languageProviders::add);
ImmediateWindowHandler.updateProgress("Loading language providers");
languageProviders.forEach(lp -> {
final Path lpPath;
try {
@ -98,7 +98,7 @@ public class LanguageLoadingProvider
throw new RuntimeException("Failed to find implementation version for language provider "+ lp.name());
}
LOGGER.debug(CORE, "Found language provider {}, version {}", lp.name(), impl);
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Loaded language provider "+lp.name()+ " " + impl));
ImmediateWindowHandler.updateProgress("Loaded language provider "+lp.name()+ " " + impl);
languageProviderMap.put(lp.name(), new ModLanguageWrapper(lp, new DefaultArtifactVersion(impl)));
});
}

View file

@ -7,6 +7,7 @@ package net.minecraftforge.fml.loading;
import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.SecureJar;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import cpw.mods.modlauncher.api.NamedPath;
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
@ -18,6 +19,8 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.zip.ZipFile;
import java.util.Set;
public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService {
@ -28,6 +31,18 @@ public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService
"net.minecraftforge.forgespi.locating.IDependencyLocator"
);
@Override
public List<NamedPath> candidates(final Path gameDirectory, final String launchTarget) {
FMLPaths.loadAbsolutePaths(gameDirectory);
FMLConfig.load();
return candidates(gameDirectory);
}
@Override
public void earlyInitialization(final String launchTarget, final String[] arguments) {
ImmediateWindowHandler.load(launchTarget, arguments);
}
@Override
public List<NamedPath> candidates(final Path gameDirectory) {
ModDirTransformerDiscoverer.scan(gameDirectory);

View file

@ -6,8 +6,10 @@
package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.forgespi.language.ModFileScanData;
import org.slf4j.Logger;
@ -60,6 +62,7 @@ public class BackgroundScanHandler
throw new IllegalStateException("Scanner has shutdown");
}
status = ScanStatus.RUNNING;
ImmediateWindowHandler.updateProgress("Scanning mod candidates");
allFiles.add(file);
pendingFiles.add(file);
final CompletableFuture<ModFileScanData> future = CompletableFuture.supplyAsync(file::compileContent, modContentScanner)

View file

@ -12,9 +12,10 @@ import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.IModuleLayerManager;
import cpw.mods.modlauncher.util.ServiceLoaderUtils;
import net.minecraftforge.fml.loading.EarlyLoadingException;
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.fml.loading.UniqueModListBuilder;
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.forgespi.Environment;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.locating.IDependencyLocator;
@ -38,7 +39,7 @@ public class ModDiscoverer {
public ModDiscoverer(Map<String, ?> arguments) {
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.MODDIRECTORYFACTORY.get(), v->ModsFolderLocator::new);
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.PROGRESSMESSAGE.get(), v-> StartupMessageManager.locatorConsumer().orElseGet(()-> s->{}));
Launcher.INSTANCE.environment().computePropertyIfAbsent(Environment.Keys.PROGRESSMESSAGE.get(), v-> StartupNotificationManager.locatorConsumer().orElseGet(()-> s->{}));
final var moduleLayerManager = Launcher.INSTANCE.environment().findModuleLayerManager().orElseThrow();
modLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IModLocator.class);
dependencyLocators = ServiceLoader.load(moduleLayerManager.getLayer(IModuleLayerManager.Layer.SERVICE).orElseThrow(), IDependencyLocator.class);
@ -66,7 +67,7 @@ public class ModDiscoverer {
List<EarlyLoadingException.ExceptionData> discoveryErrorData = new ArrayList<>();
boolean successfullyLoadedMods = true;
List<IModFileInfo> brokenFiles = new ArrayList<>();
ImmediateWindowHandler.updateProgress("Discovering mod files");
//Loop all mod locators to get the prime mods to load from.
for (IModLocator locator : modLocatorList) {
try {
@ -168,7 +169,6 @@ public class ModDiscoverer {
var locatedModFiles = locatedFiles.stream().filter(ModFile.class::isInstance).map(ModFile.class::cast).toList();
for (IModFile mf : locatedModFiles) {
LOGGER.info(LogMarkers.SCAN, "Found mod file {} of type {} with provider {}", mf.getFileName(), mf.getType(), mf.getProvider());
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Found mod file "+mf.getFileName()+" of type "+mf.getType()));
}
loadedFiles.addAll(locatedModFiles);
}

View file

@ -10,7 +10,7 @@ import com.mojang.logging.LogUtils;
import cpw.mods.jarhandling.SecureJar;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.forgespi.language.IModLanguageProvider;
@ -157,7 +157,6 @@ public class ModFile implements IModFile {
if (throwable != null) {
this.scanError = throwable;
}
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Completed deep scan of "+this.getFileName()));
}
public void setFileProperties(Map<String, Object> fileProperties) {

View file

@ -8,11 +8,8 @@ package net.minecraftforge.fml.loading.moddiscovery;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.IModuleLayerManager;
import cpw.mods.modlauncher.api.ITransformationService;
import net.minecraftforge.fml.loading.EarlyLoadingException;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.fml.loading.ModSorter;
import net.minecraftforge.fml.loading.progress.StartupMessageManager;
import net.minecraftforge.fml.loading.*;
import net.minecraftforge.fml.loading.progress.StartupNotificationManager;
import net.minecraftforge.forgespi.language.IModFileInfo;
import net.minecraftforge.forgespi.locating.IModFile;
import org.jetbrains.annotations.NotNull;
@ -52,7 +49,7 @@ public class ModValidator {
if (LOGGER.isDebugEnabled(LogMarkers.SCAN)) {
LOGGER.debug(LogMarkers.SCAN, "Found {} mod files with {} mods", candidateMods.size(), candidateMods.stream().mapToInt(mf -> mf.getModInfos().size()).sum());
}
StartupMessageManager.modLoaderConsumer().ifPresent(c->c.accept("Found "+ candidateMods.size()+" modfiles to load"));
ImmediateWindowHandler.updateProgress("Found "+candidateMods.size()+" mod candidates");
}
@NotNull

View file

@ -1,358 +0,0 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
/*
package net.minecraftforge.fml.loading.progress;
import com.google.common.io.ByteStreams;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.lwjgl.PointerBuffer;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
import org.lwjgl.stb.STBEasyFont;
import org.lwjgl.stb.STBImage;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryUsage;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.glfw.GLFW.glfwCreateWindow;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.system.MemoryStack.stackPush;
import static org.lwjgl.system.MemoryUtil.NULL;
class ClientVisualization implements EarlyProgressVisualization.Visualization {
private final int screenWidth = 854;
private final int screenHeight = 480;
private long window;
private Thread renderThread = new Thread(this::renderThreadFunc);
private boolean running = true;
private GLFWFramebufferSizeCallback framebufferSizeCallback;
private int[] fbSize;
private void initWindow(@Nullable String mcVersion) {
GLFWErrorCallback.createPrint(System.err).set();
long glfwInitBegin = System.nanoTime();
if (!glfwInit()) {
throw new IllegalStateException("Unable to initialize GLFW");
}
long glfwInitEnd = System.nanoTime();
if (glfwInitEnd - glfwInitBegin > 1e9) {
LogManager.getLogger().fatal("WARNING : glfwInit took {} seconds to start.", (glfwInitEnd-glfwInitBegin) / 1.0e9);
}
// Clear the Last Exception (#7285 - Prevent Vanilla throwing an IllegalStateException due to invalid controller mappings)
handleLastGLFWError((error, description) -> LogManager.getLogger().error(String.format("Suppressing Last GLFW error: [0x%X]%s", error, description)));
glfwDefaultWindowHints();
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
if (mcVersion != null)
{
// this emulates what we would get without early progress window
// as vanilla never sets these, so GLFW uses the first window title
// set them explicitly to avoid it using "FML early loading progress" as the class
String vanillaWindowTitle = "Minecraft* " + mcVersion;
glfwWindowHintString(GLFW_X11_CLASS_NAME, vanillaWindowTitle);
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, vanillaWindowTitle);
}
window = glfwCreateWindow(screenWidth, screenHeight, "FML early loading progress", NULL, NULL);
if (window == NULL) {
throw new RuntimeException("Failed to create the GLFW window"); // ignore it and make the GUI optional?
}
framebufferSizeCallback = GLFWFramebufferSizeCallback.create(this::fbResize);
try (MemoryStack stack = stackPush()) {
IntBuffer pWidth = stack.mallocInt(1);
IntBuffer pHeight = stack.mallocInt(1);
IntBuffer monPosLeft = stack.mallocInt(1);
IntBuffer monPosTop = stack.mallocInt(1);
glfwGetWindowSize(window, pWidth, pHeight);
// try to center the window, this is a best-effort as there may not be
// a primary monitor and we might not even be on the primary monitor...
long primaryMonitor = glfwGetPrimaryMonitor();
if (primaryMonitor != NULL)
{
GLFWVidMode vidmode = glfwGetVideoMode(primaryMonitor);
glfwGetMonitorPos(primaryMonitor, monPosLeft, monPosTop);
glfwSetWindowPos(
window,
(vidmode.width() - pWidth.get(0)) / 2 + monPosLeft.get(0),
(vidmode.height() - pHeight.get(0)) / 2 + monPosTop.get(0)
);
}
IntBuffer iconWidth = stack.mallocInt(1);
IntBuffer iconHeight = stack.mallocInt(1);
IntBuffer iconChannels = stack.mallocInt(1);
final GLFWImage.Buffer glfwImages = GLFWImage.mallocStack(1, stack);
byte[] icon;
try {
icon = ByteStreams.toByteArray(getClass().getClassLoader().getResourceAsStream("forge_icon.png"));
final ByteBuffer iconBuf = stack.malloc(icon.length);
iconBuf.put(icon);
((Buffer)iconBuf).position(0);
final ByteBuffer imgBuffer = STBImage.stbi_load_from_memory(iconBuf, iconWidth, iconHeight, iconChannels, 4);
if (imgBuffer == null) {
throw new NullPointerException("Failed to load window icon"); // fall down to catch block
}
glfwImages.position(0);
glfwImages.width(iconWidth.get(0));
glfwImages.height(iconHeight.get(0));
((Buffer)imgBuffer).position(0);
glfwImages.pixels(imgBuffer);
glfwImages.position(0);
glfwSetWindowIcon(window, glfwImages);
STBImage.stbi_image_free(imgBuffer);
} catch (NullPointerException | IOException e) {
System.err.println("Failed to load forge logo");
}
}
int[] w = new int[1];
int[] h = new int[1];
glfwGetFramebufferSize(window, w, h);
fbSize = new int[] {w[0], h[0]};
glfwSetFramebufferSizeCallback(window, framebufferSizeCallback);
glfwShowWindow(window);
glfwPollEvents();
}
private void renderProgress() {
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// glMatrixMode(GL_PROJECTION);
// glLoadIdentity();
// glOrtho(0.0D, screenWidth, screenHeight, 0.0D, -1000.0D, 1000.0D);
// glMatrixMode(GL_MODELVIEW);
// glLoadIdentity();
//
// glEnableClientState(GL11.GL_VERTEX_ARRAY);
// glEnable(GL_BLEND);
// renderBackground();
// renderMessages();
// glfwSwapBuffers(window);
}
private static float clamp(float num, float min, float max) {
if (num < min) {
return min;
} else {
return num > max ? max : num;
}
}
private static int clamp(int num, int min, int max) {
if (num < min) {
return min;
} else {
return num > max ? max : num;
}
}
private static int hsvToRGB(float hue, float saturation, float value) {
int i = (int)(hue * 6.0F) % 6;
float f = hue * 6.0F - (float)i;
float f1 = value * (1.0F - saturation);
float f2 = value * (1.0F - f * saturation);
float f3 = value * (1.0F - (1.0F - f) * saturation);
float f4;
float f5;
float f6;
switch(i) {
case 0:
f4 = value;
f5 = f3;
f6 = f1;
break;
case 1:
f4 = f2;
f5 = value;
f6 = f1;
break;
case 2:
f4 = f1;
f5 = value;
f6 = f3;
break;
case 3:
f4 = f1;
f5 = f2;
f6 = value;
break;
case 4:
f4 = f3;
f5 = f1;
f6 = value;
break;
case 5:
f4 = value;
f5 = f1;
f6 = f2;
break;
default:
throw new RuntimeException("Something went wrong when converting from HSV to RGB. Input was " + hue + ", " + saturation + ", " + value);
}
int j = clamp((int)(f4 * 255.0F), 0, 255);
int k = clamp((int)(f5 * 255.0F), 0, 255);
int l = clamp((int)(f6 * 255.0F), 0, 255);
return j << 16 | k << 8 | l;
}
private void renderBackground() {
glBegin(GL_QUADS);
glColor4f(239F / 255F, 50F / 255F, 61F / 255F, 255F / 255F); //Color from ResourceLoadProgressGui
glVertex2f(0, 0);
glVertex2f(0, screenHeight);
glVertex2f(screenWidth, screenHeight);
glVertex2f(screenWidth, 0);
glEnd();
}
private void fbResize(long window, int width, int height) {
if (window == this.window && width != 0 && height != 0) {
fbSize = new int[] {width, height};
}
}
private void handleLastGLFWError(BiConsumer<Integer, String> handler) {
try (MemoryStack memorystack = MemoryStack.stackPush()) {
PointerBuffer pointerbuffer = memorystack.mallocPointer(1);
int error = GLFW.glfwGetError(pointerbuffer);
if (error != GLFW_NO_ERROR) {
long pDescription = pointerbuffer.get();
String description = pDescription == 0L ? "" : MemoryUtil.memUTF8(pDescription);
handler.accept(error, description);
}
}
}
private void renderMessages() {
List<Pair<Integer, StartupMessageManager.Message>> messages = StartupMessageManager.getMessages();
for (int i = 0; i < messages.size(); i++) {
final Pair<Integer, StartupMessageManager.Message> pair = messages.get(i);
final float fade = clamp((4000.0f - (float) pair.getLeft() - ( i - 4 ) * 1000.0f) / 5000.0f, 0.0f, 1.0f);
if (fade <0.01f) continue;
StartupMessageManager.Message msg = pair.getRight();
renderMessage(msg.getText(), msg.getTypeColour(), ((screenHeight - 15) / 20) - i, fade);
}
renderMemoryInfo();
}
@Override
public void updateFBSize(final IntConsumer width, final IntConsumer height) {
width.accept(this.fbSize[0]);
height.accept(this.fbSize[1]);
}
private static final float[] memorycolour = new float[] { 0.0f, 0.0f, 0.0f};
private void renderMemoryInfo() {
final MemoryUsage heapusage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
final MemoryUsage offheapusage = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage();
final float pctmemory = (float) heapusage.getUsed() / heapusage.getMax();
String memory = String.format("Memory Heap: %d / %d MB (%.1f%%) OffHeap: %d MB", heapusage.getUsed() >> 20, heapusage.getMax() >> 20, pctmemory * 100.0, offheapusage.getUsed() >> 20);
final int i = hsvToRGB((1.0f - (float)Math.pow(pctmemory, 1.5f)) / 3f, 1.0f, 0.5f);
memorycolour[2] = ((i) & 0xFF) / 255.0f;
memorycolour[1] = ((i >> 8 ) & 0xFF) / 255.0f;
memorycolour[0] = ((i >> 16 ) & 0xFF) / 255.0f;
renderMessage(memory, memorycolour, 1, 1.0f);
}
private void renderMessage(final String message, final float[] colour, int row, float alpha) {
ByteBuffer charBuffer = MemoryUtil.memAlloc(message.length() * 270);
int quads = STBEasyFont.stb_easy_font_print(0, 0, message, null, charBuffer);
glVertexPointer(3, GL11.GL_FLOAT, 16, charBuffer);
glEnable(GL_BLEND);
GL14.glBlendColor(0,0,0, alpha);
glBlendFunc(GL14.GL_CONSTANT_ALPHA, GL14.GL_ONE_MINUS_CONSTANT_ALPHA);
glColor3f(colour[0], colour[1], colour[2]);
glPushMatrix();
glTranslatef(10, row * 20, 0);
glScalef(2, 2, 1);
glDrawArrays(GL11.GL_QUADS, 0, quads * 4);
glPopMatrix();
MemoryUtil.memFree(charBuffer);
}
private void renderThreadFunc() {
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
GL.createCapabilities();
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
while (running) {
renderProgress();
try {
Thread.sleep(50);
} catch (InterruptedException ignored) {
break;
}
}
glfwMakeContextCurrent(0);
}
@Override
public Runnable start(@Nullable String mcVersion) {
initWindow(mcVersion);
renderThread.setDaemon(true); // Don't hang the game if it terminates before handoff (i.e. datagen)
renderThread.start();
return org.lwjgl.glfw.GLFW::glfwPollEvents;
}
@Override
public long handOffWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitorSupplier) {
running = false;
try {
renderThread.join();
} catch (InterruptedException ignored) {
}
glfwSetWindowTitle(window, title.get());
glfwSetWindowSize(window, width.getAsInt(), height.getAsInt());
if (monitorSupplier.getAsLong() != 0L)
glfwSetWindowMonitor(window, monitorSupplier.getAsLong(), 0, 0, width.getAsInt(), height.getAsInt(), GLFW_DONT_CARE);
glfwMakeContextCurrent(window);
GL.createCapabilities();
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
renderProgress();
glfwSwapInterval(0);
glfwSwapBuffers(window);
glfwSwapInterval(1);
final GLFWFramebufferSizeCallback previous = glfwSetFramebufferSizeCallback(window, null);
previous.free();
return window;
}
}
*/

View file

@ -1,66 +0,0 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.progress;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.api.distmarker.Dist;
import org.jetbrains.annotations.Nullable;
import java.util.function.IntConsumer;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
public enum EarlyProgressVisualization {
INSTANCE;
private Visualization visualization;
public Runnable accept(Dist dist, boolean isData)
{
return accept(dist, isData, null);
}
public Runnable accept(final Dist dist, final boolean isData, @Nullable String mcVersion) {
// visualization = !isData && dist.isClient() && Boolean.parseBoolean(System.getProperty("fml.earlyprogresswindow", "true")) ? new ClientVisualization() : new NoVisualization();
visualization = new NoVisualization();
return visualization.start(mcVersion);
}
public long handOffWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, final LongSupplier monitor) {
return visualization.handOffWindow(width, height, title, monitor);
}
public void updateFBSize(IntConsumer width, IntConsumer height) {
visualization.updateFBSize(width, height);
}
interface Visualization {
Runnable start(@Nullable String mcVersion);
default long handOffWindow(final IntSupplier width, final IntSupplier height, final Supplier<String> title, LongSupplier monitorSupplier) {
return FMLLoader.getGameLayer().findModule("forge")
.map(l->Class.forName(l, "net.minecraftforge.client.loading.NoVizFallback"))
.map(LamdbaExceptionUtils.rethrowFunction(c->c.getMethod("fallback", IntSupplier.class, IntSupplier.class, Supplier.class, LongSupplier.class)))
.map(LamdbaExceptionUtils.rethrowFunction(m->(LongSupplier)m.invoke(null, width, height, title, monitorSupplier)))
.map(LongSupplier::getAsLong)
.orElseThrow(()->new IllegalStateException("Why are you here?"));
}
default void updateFBSize(IntConsumer width, IntConsumer height) {
}
}
private static class NoVisualization implements Visualization {
@Override
public Runnable start(@Nullable String mcVersion) {
return () -> {};
}
}
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.progress;
public class Message {
private final String text;
private final MessageType type;
private final long timestamp;
public Message(final String text, final MessageType type) {
this.text = text;
this.type = type;
this.timestamp = System.nanoTime();
}
public String getText() {
return text;
}
MessageType getType() {
return type;
}
long timestamp() {
return timestamp;
}
public float[] getTypeColour() {
return type.colour();
}
enum MessageType {
MC(1.0f, 1.0f, 1.0f),
ML(0.0f, 0.0f, 0.5f),
LOC(0.0f, 0.5f, 0.0f),
MOD(0.5f, 0.0f, 0.0f);
private final float[] colour;
MessageType(final float r, final float g, final float b) {
colour = new float[] {r,g,b};
}
public float[] colour() {
return colour;
}
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.progress;
import java.util.concurrent.atomic.AtomicInteger;
public final class ProgressMeter {
private final String name;
private final int steps;
private AtomicInteger current;
private Message label;
public ProgressMeter(String name, int steps, int current, Message label) {
this.name = name;
this.steps = steps;
this.current = new AtomicInteger(current);
this.label = label;
}
public String name() {
return name;
}
public int steps() {
return steps;
}
public int current() {
return current.get();
}
public Message label() {
return label;
}
public void increment() {
this.current.incrementAndGet();
}
public void complete() {
StartupNotificationManager.popBar(this);
}
public float progress() {
return current.get()/(float)steps;
}
public void setAbsolute(final int absolute) {
this.current.set(absolute);
}
public void label(final String message) {
this.label = new Message(message, Message.MessageType.ML);
}
}

View file

@ -1,112 +0,0 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.progress;
import com.google.common.base.Ascii;
import com.google.common.base.CharMatcher;
import org.apache.commons.lang3.tuple.Pair;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class StartupMessageManager {
private static volatile EnumMap<MessageType, List<Message>> messages = new EnumMap<>(MessageType.class);
public static List<Pair<Integer,Message>> getMessages() {
final long ts = System.nanoTime();
return messages.values().stream().flatMap(Collection::stream).
sorted(Comparator.comparingLong(Message::getTimestamp).thenComparing(Message::getText).reversed()).
map(m -> Pair.of((int) ((ts - m.timestamp) / 1e6), m)).
limit(5).
collect(Collectors.toList());
}
public static class Message {
private final String text;
private final MessageType type;
private final long timestamp;
public Message(final String text, final MessageType type) {
this.text = text;
this.type = type;
this.timestamp = System.nanoTime();
}
public String getText() {
return text;
}
MessageType getType() {
return type;
}
long getTimestamp() {
return timestamp;
}
public float[] getTypeColour() {
return type.colour();
}
}
enum MessageType {
MC(1.0f, 1.0f, 1.0f),
ML(0.0f, 0.0f, 0.5f),
LOC(0.0f, 0.5f, 0.0f),
MOD(0.5f, 0.0f, 0.0f);
private final float[] colour;
MessageType(final float r, final float g, final float b) {
colour = new float[] {r,g,b};
}
public float[] colour() {
return colour;
}
}
private synchronized static void addMessage(MessageType type, String message, int maxSize)
{
EnumMap<MessageType, List<Message>> newMessages = new EnumMap<>(messages);
newMessages.compute(type, (key, existingList) -> {
List<Message> newList = new ArrayList<>();
if (existingList != null)
{
if (maxSize < 0)
{
newList.addAll(existingList);
}
else
{
newList.addAll(existingList.subList(0, Math.min(existingList.size(), maxSize)));
}
}
newList.add(new Message(message, type));
return newList;
});
messages = newMessages;
}
public static void addModMessage(final String message) {
final String safeMessage = Ascii.truncate(CharMatcher.ascii().retainFrom(message),80,"~");
addMessage(MessageType.MOD, safeMessage, 20);
}
public static Optional<Consumer<String>> modLoaderConsumer() {
return Optional.of(s-> addMessage(MessageType.ML, s, -1));
}
public static Optional<Consumer<String>> locatorConsumer() {
return Optional.of(s -> addMessage(MessageType.LOC, s, -1));
}
public static Optional<Consumer<String>> mcLoaderConsumer() {
return Optional.of(s-> addMessage(MessageType.MC, s, -1));
}
}

View file

@ -0,0 +1,89 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.fml.loading.progress;
import com.google.common.base.Ascii;
import com.google.common.base.CharMatcher;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class StartupNotificationManager {
private static volatile EnumMap<Message.MessageType, List<Message>> messages = new EnumMap<>(Message.MessageType.class);
private static final Deque<ProgressMeter> progressMeters = new ArrayDeque<>();
public static List<ProgressMeter> getCurrentProgress() {
synchronized (progressMeters) {
return progressMeters.stream().toList();
}
}
public static ProgressMeter addProgressBar(final String barName, final int count) {
var pm = new ProgressMeter(barName, count, 0, new Message(barName, Message.MessageType.ML));
synchronized (progressMeters) {
progressMeters.push(pm);
}
return pm;
}
public static void popBar(final ProgressMeter progressMeter) {
synchronized (progressMeters) {
progressMeters.remove(progressMeter);
}
}
public record AgeMessage(int age, Message message) {}
public static List<AgeMessage> getMessages() {
final long ts = System.nanoTime();
return messages.values().stream().flatMap(Collection::stream)
.sorted(Comparator.comparingLong(Message::timestamp).thenComparing(Message::getText).reversed())
.map(m -> new AgeMessage((int) ((ts - m.timestamp()) / 1000000), m))
.limit(2)
.toList();
}
private synchronized static void addMessage(Message.MessageType type, String message, int maxSize)
{
EnumMap<Message.MessageType, List<Message>> newMessages = new EnumMap<>(messages);
newMessages.compute(type, (key, existingList) -> {
List<Message> newList = new ArrayList<>();
if (existingList != null)
{
if (maxSize < 0)
{
newList.addAll(existingList);
}
else
{
newList.addAll(existingList.subList(0, Math.min(existingList.size(), maxSize)));
}
}
newList.add(new Message(message, type));
return newList;
});
messages = newMessages;
}
public static void addModMessage(final String message) {
final String safeMessage = Ascii.truncate(CharMatcher.ascii().retainFrom(message),80,"~");
addMessage(Message.MessageType.MOD, safeMessage, 20);
}
public static Optional<Consumer<String>> modLoaderConsumer() {
return Optional.of(s-> addMessage(Message.MessageType.ML, s, -1));
}
public static Optional<Consumer<String>> locatorConsumer() {
return Optional.of(s -> addMessage(Message.MessageType.LOC, s, -1));
}
public static Optional<Consumer<String>> mcLoaderConsumer() {
return Optional.of(s-> addMessage(Message.MessageType.MC, s, -1));
}
}

View file

@ -21,12 +21,8 @@ public abstract class CommonClientLaunchHandler extends CommonLaunchHandler {
@Override public boolean isProduction() { return true; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
return () -> {
var args = preLaunch(arguments, layer);
Class.forName(layer.findModule("minecraft").orElseThrow(),"net.minecraft.client.main.Main").getMethod("main", String[].class).invoke(null, (Object)args);
};
protected ServiceRunner makeService(final String[] arguments, final ModuleLayer gameLayer) {
return ()->clientService(arguments, gameLayer);
}
@Override

View file

@ -6,6 +6,7 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.jarhandling.SecureJar;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.fml.loading.FileUtils;
import java.io.File;
@ -127,4 +128,12 @@ public abstract class CommonDevLaunchHandler extends CommonLaunchHandler {
// Generate a time-based random number, to mimic how n.m.client.Main works
return Long.toString(System.nanoTime() % (int) Math.pow(10, length));
}
@Override
protected ServiceRunner makeService(final String[] arguments, final ModuleLayer gameLayer) {
var args = preLaunch(arguments, gameLayer);
return ()->devService(args, gameLayer);
}
abstract void devService(final String[] arguments, final ModuleLayer gameLayer) throws Throwable;
}

View file

@ -8,6 +8,8 @@ package net.minecraftforge.fml.loading.targets;
import com.mojang.logging.LogUtils;
import cpw.mods.modlauncher.api.ILaunchHandlerService;
import cpw.mods.modlauncher.api.ITransformingClassLoaderBuilder;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.LogMarkers;
import net.minecraftforge.api.distmarker.Dist;
import org.apache.logging.log4j.core.LoggerContext;
@ -84,4 +86,28 @@ public abstract class CommonLaunchHandler implements ILaunchHandlerService {
//modClassPaths.forEach((modlabel,paths) -> explodedTargets.add(new ExplodedDirectoryLocator.ExplodedMod(modlabel, paths)));
return modClassPaths;
}
@Override
public ServiceRunner launchService(final String[] arguments, final ModuleLayer gameLayer) {
FMLLoader.beforeStart(gameLayer);
return makeService(arguments, gameLayer);
}
protected abstract ServiceRunner makeService(final String[] arguments, final ModuleLayer gameLayer);
protected void clientService(final String[] arguments, final ModuleLayer layer) throws Throwable {
runTarget("net.minecraft.client.main.Main", arguments, layer);
}
protected void serverService(final String[] arguments, final ModuleLayer layer) throws Throwable {
runTarget("net.minecraft.server.Main", arguments, layer);
}
protected void dataService(final String[] arguments, final ModuleLayer layer) throws Throwable {
runTarget("net.minecraft.data.Main", arguments, layer);
}
protected void runTarget(final String target, final String[] arguments, final ModuleLayer layer) throws Throwable {
Class.forName(layer.findModule("minecraft").orElseThrow(),target).getMethod("main", String[].class).invoke(null, (Object)arguments);
}
}

View file

@ -23,12 +23,8 @@ public abstract class CommonServerLaunchHandler extends CommonLaunchHandler {
@Override public boolean isProduction() { return true; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
return () -> {
var args = preLaunch(arguments, layer);
Class.forName(layer.findModule("minecraft").orElseThrow(),"net.minecraft.server.Main").getMethod("main", String[].class).invoke(null, (Object)args);
};
protected ServiceRunner makeService(String[] arguments, ModuleLayer layer) {
return () -> serverService(arguments, layer);
}
@Override

View file

@ -5,20 +5,14 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
public class FMLClientDevLaunchHandler extends CommonDevLaunchHandler {
@Override public String name() { return "fmlclientdev"; }
@Override public Dist getDist() { return Dist.CLIENT; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
return () -> {
var args = preLaunch(arguments, layer);
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.client.main.Main").getMethod("main", String[].class).invoke(null, (Object) args);
};
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
clientService(arguments, layer);
}
}

View file

@ -5,11 +5,8 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
public class FMLClientUserdevLaunchHandler extends FMLUserdevLaunchHandler {
@Override
public String name() { return "fmlclientuserdev"; }
@ -18,11 +15,7 @@ public class FMLClientUserdevLaunchHandler extends FMLUserdevLaunchHandler {
public Dist getDist() { return Dist.CLIENT; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
return () -> {
var args = preLaunch(arguments, layer);
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.client.main.Main").getMethod("main", String[].class).invoke(null, (Object) args);
};
protected void devService(String[] arguments, ModuleLayer layer) throws Throwable {
clientService(arguments, layer);
}
}

View file

@ -5,11 +5,8 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
public class FMLDataUserdevLaunchHandler extends FMLUserdevLaunchHandler {
@Override
public String name() { return "fmldatauserdev"; }
@ -21,11 +18,7 @@ public class FMLDataUserdevLaunchHandler extends FMLUserdevLaunchHandler {
public boolean isData() { return true; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
return () -> {
var args = preLaunch(arguments, layer);
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.data.Main").getMethod("main", String[].class).invoke(null, (Object) args);
};
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
dataService(arguments, layer);
}
}

View file

@ -5,20 +5,14 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
public class FMLServerDevLaunchHandler extends CommonDevLaunchHandler {
@Override public String name() { return "fmlserverdev"; }
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
return () -> {
var args = preLaunch(arguments, layer);
Class.forName(layer.findModule("minecraft").orElseThrow(),"net.minecraft.server.Main").getMethod("main", String[].class).invoke(null, (Object)args);
};
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
serverService(arguments, layer);
}
}

View file

@ -5,21 +5,14 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
public class FMLServerUserdevLaunchHandler extends FMLUserdevLaunchHandler {
@Override public String name() { return "fmlserveruserdev"; }
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
return () -> {
var args = preLaunch(arguments, layer);
Class.forName(layer.findModule("minecraft").orElseThrow(),"net.minecraft.server.Main").getMethod("main", String[].class).invoke(null, (Object)args);
};
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
serverService(arguments, layer);
}
}

View file

@ -5,20 +5,14 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
public class ForgeClientDevLaunchHandler extends CommonDevLaunchHandler {
@Override public String name() { return "forgeclientdev"; }
@Override public Dist getDist() { return Dist.CLIENT; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
return () -> {
var args = preLaunch(arguments, layer);
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.client.main.Main").getMethod("main", String[].class).invoke(null, (Object) args);
};
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
clientService(arguments, layer);
}
}

View file

@ -5,11 +5,8 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
public class ForgeClientUserdevLaunchHandler extends ForgeUserdevLaunchHandler {
@Override
public String name() { return "forgeclientuserdev"; }
@ -18,11 +15,7 @@ public class ForgeClientUserdevLaunchHandler extends ForgeUserdevLaunchHandler {
public Dist getDist() { return Dist.CLIENT; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
return () -> {
var args = preLaunch(arguments, layer);
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.client.main.Main").getMethod("main", String[].class).invoke(null, (Object) args);
};
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
clientService(arguments, layer);
}
}

View file

@ -5,22 +5,15 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
public class ForgeDataDevLaunchHandler extends CommonDevLaunchHandler {
@Override public String name() { return "forgedatadev"; }
@Override public Dist getDist() { return Dist.CLIENT; }
@Override public boolean isData() { return true; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
return () -> {
var args = preLaunch(arguments, layer);
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.data.Main").getMethod("main", String[].class).invoke(null, (Object) args);
};
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.data.Main").getMethod("main", String[].class).invoke(null, (Object) arguments);
}
}

View file

@ -5,11 +5,8 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
public class ForgeDataUserdevLaunchHandler extends ForgeUserdevLaunchHandler {
@Override
public String name() { return "forgedatauserdev"; }
@ -21,11 +18,7 @@ public class ForgeDataUserdevLaunchHandler extends ForgeUserdevLaunchHandler {
public boolean isData() { return true; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
return () -> {
var args = preLaunch(arguments, layer);
Class.forName(layer.findModule("minecraft").orElseThrow(), "net.minecraft.data.Main").getMethod("main", String[].class).invoke(null, (Object) args);
};
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
dataService(arguments, layer);
}
}

View file

@ -5,21 +5,14 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
public class ForgeGametestDevLaunchHandler extends CommonDevLaunchHandler {
@Override public String name() { return "forgegametestserverdev"; }
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
return () -> {
var args = preLaunch(arguments, layer);
Class.forName(layer.findModule("forge").orElseThrow(), "net.minecraftforge.gametest.GameTestMain").getMethod("main", String[].class).invoke(null, (Object)args);
};
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
Class.forName(layer.findModule("forge").orElseThrow(), "net.minecraftforge.gametest.GameTestMain").getMethod("main", String[].class).invoke(null, (Object)arguments);
}
}

View file

@ -5,21 +5,14 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
public class ForgeGametestUserdevLaunchHandler extends ForgeUserdevLaunchHandler {
@Override public String name() { return "forgegametestserveruserdev"; }
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
return () -> {
var args = preLaunch(arguments, layer);
Class.forName(layer.findModule("forge").orElseThrow(), "net.minecraftforge.gametest.GameTestMain").getMethod("main", String[].class).invoke(null, (Object)args);
};
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
Class.forName(layer.findModule("forge").orElseThrow(), "net.minecraftforge.gametest.GameTestMain").getMethod("main", String[].class).invoke(null, (Object)arguments);
}
}

View file

@ -5,20 +5,14 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
public class ForgeServerDevLaunchHandler extends CommonDevLaunchHandler {
@Override public String name() { return "forgeserverdev"; }
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
return () -> {
var args = preLaunch(arguments, layer);
Class.forName(layer.findModule("minecraft").orElseThrow(),"net.minecraft.server.Main").getMethod("main", String[].class).invoke(null, (Object)args);
};
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
serverService(arguments, layer);
}
}

View file

@ -5,21 +5,14 @@
package net.minecraftforge.fml.loading.targets;
import cpw.mods.modlauncher.api.ServiceRunner;
import net.minecraftforge.api.distmarker.Dist;
import java.util.concurrent.Callable;
public class ForgeServerUserdevLaunchHandler extends ForgeUserdevLaunchHandler {
@Override public String name() { return "forgeserveruserdev"; }
@Override public Dist getDist() { return Dist.DEDICATED_SERVER; }
@Override
public ServiceRunner launchService(String[] arguments, ModuleLayer layer) {
return () -> {
var args = preLaunch(arguments, layer);
Class.forName(layer.findModule("minecraft").orElseThrow(),"net.minecraft.server.Main").getMethod("main", String[].class).invoke(null, (Object)args);
};
public void devService(String[] arguments, ModuleLayer layer) throws Throwable {
serverService(arguments, layer);
}
}

View file

@ -1,8 +1,21 @@
# does the splashscreen run
splashscreen = true
# max threads for parallel loading : -1 uses Runtime#availableProcessors
maxThreads = -1
# Enable forge global version checking
#Enable forge global version checking
versionCheck = true
# Disables Optimized DFU client-side.
#Disables Optimized DFU client-side
disableOptimizedDFU = true
#Default config path for servers
defaultConfigPath = "defaultconfigs"
#Max threads for early initialization parallelism, -1 is based on processor count
maxThreads = -1
#should we show the early splashscreen
splashscreen = true
#Early window provider (default fmlearlywindow)
earlyWindowProvider = "fmlearlywindow"
#Initial window height
earlyWindowHeight = 480
#Initial window width
earlyWindowWidth = 854
# Start the early window as "maximized"
earlyWindowMaximized = false
# The rendering scale of the early window
earlyWindowFBScale = 1

View file

@ -27,48 +27,19 @@ import java.util.concurrent.TimeUnit;
@OnlyIn(Dist.CLIENT)
public class ClientModLoader
{
private static final Logger LOGGER = LogManager.getLogger();
private static boolean loading;
private static Minecraft mc;
private static boolean loadingComplete;
private static LoadingFailedException error;
// private static EarlyLoaderGUI earlyLoaderGUI;
private static class SpacedRunnable implements Runnable {
static final long NANO_SLEEP_TIME = TimeUnit.MILLISECONDS.toNanos(50);
private final Runnable wrapped;
private long lastRun;
private SpacedRunnable(final Runnable wrapped) {
this.wrapped = wrapped;
this.lastRun = System.nanoTime() - NANO_SLEEP_TIME;
}
@Override
public void run() {
if (System.nanoTime() - this.lastRun > NANO_SLEEP_TIME) {
wrapped.run();
this.lastRun = System.nanoTime();
}
}
}
public static void begin(final Minecraft minecraft, final PackRepository defaultResourcePacks, final ReloadableResourceManager mcResourceManager)
{
// force log4j to shutdown logging in a shutdown hook. This is because we disable default shutdown hook so the server properly logs it's shutdown
Runtime.getRuntime().addShutdownHook(new Thread(LogManager::shutdown));
loading = true;
ClientModLoader.mc = minecraft;
// LogicalSidedProvider.setClient(()->minecraft);
// LanguageHook.loadForgeAndMCLangs();
// earlyLoaderGUI = new EarlyLoaderGUI(minecraft.getWindow());
createRunnableWithCatch(()->ModLoader.get().gatherAndInitializeMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), ()->{})).run();
mcResourceManager.registerReloadListener(ClientModLoader::onResourceReload);
// if (error == null) {
//// ResourcePackLoader.loadResourcePacks(defaultResourcePacks, ClientModLoader::buildPackFinder);
//// DatapackCodec.DEFAULT.addModPacks(ResourcePackLoader.getPackNames());
//// mcResourceManager.registerReloadListener(BrandingControl.resourceManagerReloadListener());
//// ModelLoaderRegistry.init();
// }
}
private static CompletableFuture<Void> onResourceReload(final PreparableReloadListener.PreparationBarrier stage, final ResourceManager resourceManager, final ProfilerFiller prepareProfiler, final ProfilerFiller executeProfiler, final Executor asyncExecutor, final Executor syncExecutor) {
@ -90,15 +61,9 @@ public class ClientModLoader
private static void startModLoading(ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor) {
if (error!=null) throw error;
// earlyLoaderGUI.handleElsewhere();
createRunnableWithCatch(() -> ModLoader.get().loadMods(syncExecutor, parallelExecutor, ()->{})).run();
}
private static void postSidedRunnable() {
// LOGGER.debug(LOADING, "Running post client event work");
// RenderingRegistry.loadEntityRenderers(mc.getEntityRenderDispatcher());
}
private static void finishModLoading(ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor)
{
if (error!=null) throw error;
@ -109,80 +74,8 @@ public class ClientModLoader
syncExecutor.execute(()->mc.options.load());
}
public static boolean completeModLoading()
{
// RenderSystem.disableTexture();
// RenderSystem.enableTexture();
// List<ModLoadingWarning> warnings = ModLoader.get().getWarnings();
// boolean showWarnings = true;
// try {
// showWarnings = ForgeConfig.CLIENT.showLoadWarnings.get();
// } catch (NullPointerException e) {
// // We're in an early error state, config is not available. Assume true.
// }
// if (!showWarnings) {
// //User disabled warning screen, as least log them
// if (!warnings.isEmpty()) {
// LOGGER.warn(LOADING, "Mods loaded with {} warning(s)", warnings.size());
// warnings.forEach(warning -> LOGGER.warn(LOADING, warning.formatToString()));
// }
// warnings = Collections.emptyList(); //Clear warnings, as the user does not want to see them
// }
// File dumpedLocation = null;
// if (error == null) {
// // We can finally start the forge eventbus up
// MinecraftForge.EVENT_BUS.start();
// } else {
// // Double check we have the langs loaded for forge
// LanguageHook.loadForgeAndMCLangs();
// dumpedLocation = CrashReportExtender.dumpModLoadingCrashReport(LOGGER, error, mc.gameDirectory);
// }
// if (error != null || !warnings.isEmpty()) {
// mc.setScreen(new LoadingErrorScreen(error, warnings, dumpedLocation));
// return true;
// } else {
// ClientHooks.logMissingTextureErrors();
// return false;
// }
return true;
}
public static void renderProgressText() {
// earlyLoaderGUI.renderFromGUI();
}
public static boolean isLoading()
{
return loading;
}
// private static ResourcePackLoader.IPackInfoFinder buildPackFinder(Map<ModFile, ? extends ModFileResourcePack> modResourcePacks, BiConsumer<? super ModFileResourcePack, ResourcePackInfo> packSetter) {
// return (packList, factory) -> clientPackFinder(modResourcePacks, packSetter, packList, factory);
// }
//
// private static void clientPackFinder(Map<ModFile, ? extends ModFileResourcePack> modResourcePacks, BiConsumer<? super ModFileResourcePack, ResourcePackInfo> packSetter, Consumer<ResourcePackInfo> consumer, ResourcePackInfo.IFactory factory) {
// List<ModFileResourcePack> hiddenPacks = new ArrayList<>();
// for (Entry<ModFile, ? extends ModFileResourcePack> e : modResourcePacks.entrySet())
// {
// IModInfo mod = e.getKey().getModInfos().get(0);
// if (Objects.equals(mod.getModId(), "minecraft")) continue; // skip the minecraft "mod"
// final String name = "mod:" + mod.getModId();
// final ResourcePackInfo packInfo = ResourcePackInfo.create(name, false, e::getValue, factory, ResourcePackInfo.Priority.BOTTOM, IPackNameDecorator.DEFAULT);
// if (packInfo == null) {
// // Vanilla only logs an error, instead of propagating, so handle null and warn that something went wrong
// ModLoader.get().addWarning(new ModLoadingWarning(mod, ModLoadingStage.ERROR, "fml.modloading.brokenresources", e.getKey()));
// continue;
// }
// packSetter.accept(e.getValue(), packInfo);
// LOGGER.debug(CORE, "Generating PackInfo named {} for mod file {}", name, e.getKey().getFilePath());
// if (mod.getOwningFile().showAsResourcePack()) {
// consumer.accept(packInfo);
// } else {
// hiddenPacks.add(e.getValue());
// }
// }
// final ResourcePackInfo packInfo = ResourcePackInfo.create("mod_resources", true, () -> new DelegatingResourcePack("mod_resources", "Mod Resources",
// new PackMetadataSection(new TranslationTextComponent("fml.resources.modresources", hiddenPacks.size()), 6),
// hiddenPacks), factory, ResourcePackInfo.Priority.BOTTOM, IPackNameDecorator.DEFAULT);
// consumer.accept(packInfo);
// }
}

View file

@ -1,19 +1,28 @@
--- a/com/mojang/blaze3d/platform/Window.java
+++ b/com/mojang/blaze3d/platform/Window.java
@@ -83,7 +_,7 @@
@@ -83,7 +_,8 @@
GLFW.glfwWindowHint(139267, 2);
GLFW.glfwWindowHint(139272, 204801);
GLFW.glfwWindowHint(139270, 1);
- this.f_85349_ = GLFW.glfwCreateWindow(this.f_85359_, this.f_85360_, p_85376_, this.f_85355_ && monitor != null ? monitor.m_84954_() : 0L, 0L);
+ this.f_85349_ = net.minecraftforge.fml.loading.progress.EarlyProgressVisualization.INSTANCE.handOffWindow(()->this.f_85359_, ()->this.f_85360_, ()->p_85376_, ()->this.f_85355_ && monitor != null ? monitor.m_84954_() : 0L);
+ this.f_85349_ = net.minecraftforge.fml.loading.ImmediateWindowHandler.setupMinecraftWindow(()->this.f_85359_, ()->this.f_85360_, ()->p_85376_, ()->this.f_85355_ && monitor != null ? monitor.m_84954_() : 0L);
+ if (!net.minecraftforge.fml.loading.ImmediateWindowHandler.positionWindow(Optional.ofNullable(monitor), w->this.f_85359_ = this.f_85352_ = w, h->this.f_85360_ = this.f_85353_ = h, x->this.f_85357_ = this.f_85350_ = x, y->this.f_85358_ = this.f_85351_ = y)) {
if (monitor != null) {
VideoMode videomode = monitor.m_84948_(this.f_85355_ ? this.f_85354_ : Optional.empty());
this.f_85350_ = this.f_85357_ = monitor.m_84951_() + videomode.m_85332_() / 2 - this.f_85359_ / 2;
@@ -95,6 +_,7 @@
this.f_85350_ = this.f_85357_ = aint1[0];
this.f_85351_ = this.f_85358_ = aint[0];
}
+ }
GLFW.glfwMakeContextCurrent(this.f_85349_);
Locale locale = Locale.getDefault(Category.FORMAT);
@@ -238,6 +_,7 @@
GLFW.glfwGetFramebufferSize(this.f_85349_, aint, aint1);
this.f_85361_ = aint[0] > 0 ? aint[0] : 1;
this.f_85362_ = aint1[0] > 0 ? aint1[0] : 1;
+ if (this.f_85362_ == 0 || this.f_85361_==0) net.minecraftforge.fml.loading.progress.EarlyProgressVisualization.INSTANCE.updateFBSize(w->this.f_85361_=w, h->this.f_85362_=h);
+ if (this.f_85362_ == 0 || this.f_85361_==0) net.minecraftforge.fml.loading.ImmediateWindowHandler.updateFBSize(w->this.f_85361_=w, h->this.f_85362_=h);
}
private void m_85427_(long p_85428_, int p_85429_, int p_85430_) {

View file

@ -17,16 +17,17 @@
this.f_91033_ = p_91084_.f_101908_.f_101926_;
this.f_91034_ = !p_91084_.f_101908_.f_101929_;
this.f_91035_ = !p_91084_.f_101908_.f_101930_;
@@ -450,7 +_,7 @@
@@ -449,15 +_,15 @@
}
this.f_90990_.m_85380_(this.f_91066_.m_232035_().m_231551_());
+ // FORGE: Move mouse and keyboard handler setup further below
this.f_91067_ = new MouseHandler(this);
- this.f_91067_.m_91524_(this.f_90990_.m_85439_());
+ // FORGE: Move mouse handler setup further below
this.f_91068_ = new KeyboardHandler(this);
this.f_91068_.m_90887_(this.f_90990_.m_85439_());
- this.f_91068_.m_90887_(this.f_90990_.m_85439_());
RenderSystem.initRenderer(this.f_91066_.f_92035_, false);
@@ -458,6 +_,7 @@
this.f_91042_ = new MainTarget(this.f_90990_.m_85441_(), this.f_90990_.m_85442_());
this.f_91042_.m_83931_(0.0F, 0.0F, 0.0F, 0.0F);
this.f_91042_.m_83954_(f_91002_);
this.f_91036_ = new ReloadableResourceManager(PackType.CLIENT_RESOURCES);
@ -49,14 +50,15 @@
this.f_91036_.m_7217_(this.f_91061_);
this.f_91053_ = new PaintingTextureManager(this.f_90987_);
this.f_91036_.m_7217_(this.f_91053_);
@@ -516,7 +_,9 @@
@@ -516,7 +_,10 @@
this.f_91047_ = new GpuWarnlistManager();
this.f_91036_.m_7217_(this.f_91047_);
this.f_91036_.m_7217_(this.f_205120_);
- this.f_91065_ = new Gui(this, this.f_90995_);
+ this.f_91065_ = new net.minecraftforge.client.gui.overlay.ForgeGui(this);
+ // FORGE: Moved mouse handler setup below ingame gui creation to prevent NPEs in mouse handler.
+ // FORGE: Moved keyboard and mouse handler setup below ingame gui creation to prevent NPEs in them.
+ this.f_91067_.m_91524_(this.f_90990_.m_85439_());
+ this.f_91068_.m_90887_(this.f_90990_.m_85439_());
this.f_91064_ = new DebugRenderer(this);
RealmsClient realmsclient = RealmsClient.m_239151_(this);
this.f_238717_ = new RealmsDataFetcher(realmsclient);
@ -68,6 +70,15 @@
this.f_90990_.m_85409_(this.f_91066_.m_231817_().m_231551_());
this.f_90990_.m_85424_(this.f_91066_.m_232123_().m_231551_());
this.f_90990_.m_85426_();
@@ -554,7 +_,7 @@
this.f_167847_.m_168557_(ResourceLoadStateTracker.ReloadReason.INITIAL, list);
ReloadInstance reloadinstance = this.f_91036_.m_142463_(Util.m_183991_(), this, f_90983_, list);
GameLoadTimesEvent.f_285635_.m_285833_(TelemetryProperty.f_285629_);
- this.m_91150_(new LoadingOverlay(this, reloadinstance, (p_210745_) -> {
+ this.m_91150_(net.minecraftforge.fml.loading.ImmediateWindowHandler.<LoadingOverlay>loadingOverlay(()->this, ()->reloadinstance, (p_210745_) -> {
Util.m_137521_(p_210745_, this::m_91239_, () -> {
if (SharedConstants.f_136183_) {
this.m_91273_();
@@ -562,20 +_,23 @@
this.f_167847_.m_168556_();
@ -87,7 +98,8 @@
+ this.m_278684_(realmsclient, reloadinstance, p_91084_.f_278410_);
+ }
});
}, false));
- }, false));
+ }, false).get());
this.f_278504_ = QuickPlayLog.m_278648_(p_91084_.f_278410_.f_278493_());
- if (this.m_239929_()) {
- this.m_91152_(BanNoticeScreen.m_239967_((p_278873_) -> {

View file

@ -1,13 +1,5 @@
--- a/net/minecraft/client/gui/screens/LoadingOverlay.java
+++ b/net/minecraft/client/gui/screens/LoadingOverlay.java
@@ -123,6 +_,7 @@
int k1 = (int)((double)p_281839_.m_280206_() * 0.8325D);
float f6 = this.f_96164_.m_7750_();
this.f_96167_ = Mth.m_14036_(this.f_96167_ * 0.95F + f6 * 0.050000012F, 0.0F, 1.0F);
+ net.minecraftforge.client.loading.ClientModLoader.renderProgressText();
if (f < 1.0F) {
this.m_96182_(p_281839_, i / 2 - j1, k1 - 5, i / 2 + j1, k1 + 5, 1.0F - Mth.m_14036_(f, 0.0F, 1.0F));
}
@@ -132,6 +_,7 @@
}

View file

@ -27,6 +27,7 @@ include 'fmlcore'
include 'mclanguage'
include 'javafmllanguage'
include 'lowcodelanguage'
include 'fmlearlydisplay'
include ':mcp'
include ':clean'

View file

@ -50,7 +50,7 @@ public class TitleScreenModUpdateIndicator extends Screen
@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick)
{
if (showNotification == null || !showNotification.shouldDraw() || !FMLConfig.runVersionCheck())
if (showNotification == null || !showNotification.shouldDraw() || !FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.VERSION_CHECK))
{
return;
}

View file

@ -25,6 +25,7 @@ import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.world.level.DataPackConfig;
import net.minecraftforge.event.AddPackFindersEvent;
import net.minecraftforge.fml.*;
import net.minecraftforge.fml.loading.ImmediateWindowHandler;
import net.minecraftforge.internal.BrandingControl;
import net.minecraftforge.logging.CrashReportExtender;
import net.minecraftforge.common.util.LogicalSidedProvider;
@ -59,7 +60,6 @@ public class ClientModLoader
private static Minecraft mc;
private static boolean loadingComplete;
private static LoadingFailedException error;
private static EarlyLoaderGUI earlyLoaderGUI;
private static class SpacedRunnable implements Runnable {
static final long NANO_SLEEP_TIME = TimeUnit.MILLISECONDS.toNanos(50);
@ -87,8 +87,7 @@ public class ClientModLoader
ClientModLoader.mc = minecraft;
LogicalSidedProvider.setClient(()->minecraft);
LanguageHook.loadForgeAndMCLangs();
earlyLoaderGUI = new EarlyLoaderGUI(minecraft);
createRunnableWithCatch(()->ModLoader.get().gatherAndInitializeMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), new SpacedRunnable(earlyLoaderGUI::renderTick))).run();
createRunnableWithCatch(()->ModLoader.get().gatherAndInitializeMods(ModWorkManager.syncExecutor(), ModWorkManager.parallelExecutor(), new SpacedRunnable(ImmediateWindowHandler::renderTick))).run();
if (error == null) {
ResourcePackLoader.loadResourcePacks(defaultResourcePacks, ClientModLoader::buildPackFinder);
ModLoader.get().postEvent(new AddPackFindersEvent(PackType.CLIENT_RESOURCES, defaultResourcePacks::addPackFinder));
@ -116,13 +115,12 @@ public class ClientModLoader
}
private static void startModLoading(ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor) {
earlyLoaderGUI.handleElsewhere();
createRunnableWithCatch(() -> ModLoader.get().loadMods(syncExecutor, parallelExecutor, new SpacedRunnable(earlyLoaderGUI::renderTick))).run();
createRunnableWithCatch(() -> ModLoader.get().loadMods(syncExecutor, parallelExecutor, new SpacedRunnable(ImmediateWindowHandler::renderTick))).run();
}
private static void finishModLoading(ModWorkManager.DrivenExecutor syncExecutor, Executor parallelExecutor)
{
createRunnableWithCatch(() -> ModLoader.get().finishMods(syncExecutor, parallelExecutor, new SpacedRunnable(earlyLoaderGUI::renderTick))).run();
createRunnableWithCatch(() -> ModLoader.get().finishMods(syncExecutor, parallelExecutor, new SpacedRunnable(ImmediateWindowHandler::renderTick))).run();
loading = false;
loadingComplete = true;
// reload game settings on main thread
@ -172,9 +170,6 @@ public class ClientModLoader
}
}
public static void renderProgressText() {
earlyLoaderGUI.renderFromGUI();
}
public static boolean isLoading()
{
return loading;

View file

@ -1,140 +0,0 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.loading;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.platform.Window;
import net.minecraft.client.Minecraft;
import net.minecraft.util.Mth;
import net.minecraftforge.fml.StartupMessageManager;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
import org.lwjgl.stb.STBEasyFont;
import org.lwjgl.system.MemoryUtil;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryUsage;
import java.nio.ByteBuffer;
import java.util.Locale;
public class EarlyLoaderGUI {
private final Minecraft minecraft;
private final Window window;
private boolean handledElsewhere;
public EarlyLoaderGUI(final Minecraft minecraft) {
this.minecraft = minecraft;
this.window = minecraft.getWindow();
}
private void setupMatrix() {
RenderSystem.clear(256, Minecraft.ON_OSX);
GL11.glMatrixMode(5889);
GL11.glLoadIdentity();
GL11.glOrtho(0.0D, window.getWidth() / window.getGuiScale(), window.getHeight() / window.getGuiScale(), 0.0D, 1000.0D, 3000.0D);
GL11.glMatrixMode(5888);
GL11.glLoadIdentity();
GL11.glTranslatef(0.0F, 0.0F, -2000.0F);
}
public void handleElsewhere() {
this.handledElsewhere = true;
}
void renderFromGUI() {
renderMessages();
}
void renderTick() {
if (handledElsewhere) return;
// int guiScale = window.calculateScale(0, false);
// window.setGuiScale(guiScale);
//
// RenderSystem.clearColor(1.0f, 1.0f, 1.0f, 1.0f);
// RenderSystem.clear(GL11.GL_COLOR_BUFFER_BIT, Minecraft.ON_OSX);
// GL11.glPushMatrix();
// setupMatrix();
// renderBackground();
// renderMessages();
// window.updateDisplay();
// GL11.glPopMatrix();
}
private void renderBackground() {
GL11.glBegin(GL11.GL_QUADS);
boolean isDarkBackground = minecraft.options.darkMojangStudiosBackground().get();
GL11.glColor4f(isDarkBackground ? 0 : (239F / 255F), isDarkBackground ? 0 : (50F / 255F), isDarkBackground ? 0 : (61F / 255F), 1); //Color from LoadingOverlay
GL11.glVertex3f(0, 0, -10);
GL11.glVertex3f(0, window.getGuiScaledHeight(), -10);
GL11.glVertex3f(window.getGuiScaledWidth(), window.getGuiScaledHeight(), -10);
GL11.glVertex3f(window.getGuiScaledWidth(), 0, -10);
GL11.glEnd();
}
private void renderMessages() {
// List<Pair<Integer, StartupMessageManager.Message>> messages = StartupMessageManager.getMessages();
// for (int i = 0; i < messages.size(); i++) {
// boolean nofade = i == 0;
// final Pair<Integer, StartupMessageManager.Message> pair = messages.get(i);
// final float fade = MathHelper.clamp((4000.0f - (float) pair.getLeft() - ( i - 4 ) * 1000.0f) / 5000.0f, 0.0f, 1.0f);
// if (fade <0.01f && !nofade) continue;
// StartupMessageManager.Message msg = pair.getRight();
// renderMessage(msg.getText(), msg.getTypeColour(), ((window.getGuiScaledHeight() - 15) / 10) - i + 1, nofade ? 1.0f : fade);
// }
// List<Pair<Integer, StartupMessageManager.Message>> messages = StartupMessageManager.getMessages();
// for (int i = 0; i < messages.size(); i++) {
// boolean nofade = i == 0;
// final Pair<Integer, StartupMessageManager.Message> pair = messages.get(i);
// final float fade = Mth.clamp((4000.0f - (float) pair.getLeft() - ( i - 4 ) * 1000.0f) / 5000.0f, 0.0f, 1.0f);
// if (fade <0.01f && !nofade) continue;
// StartupMessageManager.Message msg = pair.getRight();
// renderMessage(msg.getText(), msg.getTypeColour(), ((window.getGuiScaledHeight() - 15) / 10) - i + 1, nofade ? 1.0f : fade);
// }
// renderMemoryInfo();
}
private static final float[] memorycolour = new float[] { 0.0f, 0.0f, 0.0f};
private void renderMemoryInfo() {
final MemoryUsage heapusage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
final MemoryUsage offheapusage = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage();
final float pctmemory = (float) heapusage.getUsed() / heapusage.getMax();
String memory = String.format(Locale.ENGLISH, "Memory Heap: %d / %d MB (%.1f%%) OffHeap: %d MB", heapusage.getUsed() >> 20, heapusage.getMax() >> 20, pctmemory * 100.0, offheapusage.getUsed() >> 20);
final int i = Mth.hsvToRgb((1.0f - (float)Math.pow(pctmemory, 1.5f)) / 3f, 1.0f, 0.5f);
memorycolour[2] = ((i) & 0xFF) / 255.0f;
memorycolour[1] = ((i >> 8 ) & 0xFF) / 255.0f;
memorycolour[0] = ((i >> 16 ) & 0xFF) / 255.0f;
renderMessage(memory, memorycolour, 1, 1.0f);
}
void renderMessage(final String message, final float[] colour, int line, float alpha) {
// GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
// ByteBuffer charBuffer = MemoryUtil.memAlloc(message.length() * 270);
// int quads = STBEasyFont.stb_easy_font_print(0, 0, message, null, charBuffer);
// GL14.glVertexPointer(2, GL11.GL_FLOAT, 16, charBuffer);
//
// RenderSystem.enableBlend();
// RenderSystem.disableTexture();
// // STBEasyFont's quads are in reverse order or what OGGL expects, so it gets culled for facing the wrong way.
// // So Disable culling https://github.com/MinecraftForge/MinecraftForge/pull/6824
// RenderSystem.disableCull();
// GL14.glBlendColor(0,0,0, alpha);
// RenderSystem.blendFunc(GlStateManager.SourceFactor.CONSTANT_ALPHA, GlStateManager.DestFactor.ONE_MINUS_CONSTANT_ALPHA);
// GL11.glColor3f(colour[0],colour[1],colour[2]);
// GL11.glPushMatrix();
// GL11.glTranslatef(10, line * 10, 0);
// GL11.glScalef(1, 1, 0);
// GL11.glDrawArrays(GL11.GL_QUADS, 0, quads * 4);
// GL11.glPopMatrix();
//
// RenderSystem.enableCull();
// GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY);
// MemoryUtil.memFree(charBuffer);
}
}

View file

@ -0,0 +1,165 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.client.loading;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.BufferUploader;
import com.mojang.blaze3d.vertex.BufferVertexConsumer;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.Tesselator;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.mojang.blaze3d.vertex.VertexSorting;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.LoadingOverlay;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ReloadInstance;
import net.minecraftforge.fml.StartupMessageManager;
import net.minecraftforge.fml.earlydisplay.ColourScheme;
import net.minecraftforge.fml.earlydisplay.DisplayWindow;
import net.minecraftforge.fml.loading.progress.ProgressMeter;
import org.jetbrains.annotations.NotNull;
import org.joml.Matrix4f;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;
import static com.mojang.blaze3d.platform.GlConst.*;
import static net.minecraft.util.Mth.clamp;
import static org.lwjgl.opengl.GL30C.glViewport;
import static org.lwjgl.opengl.GL30C.glTexParameterIi;
/**
* This is an implementation of the LoadingOverlay that calls back into the early window rendering, as part of the
* game loading cycle. We completely replace the {@link #render(GuiGraphics, int, int, float)} call from the parent
* with one of our own, that allows us to blend our early loading screen into the main window, in the same manner as
* the Mojang screen. It also allows us to see and tick appropriately as the later stages of the loading system run.
*
* It is somewhat a copy of the superclass render method.
*/
public class ForgeLoadingOverlay extends LoadingOverlay {
private final Minecraft minecraft;
private final ReloadInstance reload;
private final Consumer<Optional<Throwable>> onFinish;
private final DisplayWindow displayWindow;
private final ProgressMeter progress;
private long fadeOutStart = -1L;
public ForgeLoadingOverlay(final Minecraft mc, final ReloadInstance reloader, final Consumer<Optional<Throwable>> errorConsumer, DisplayWindow displayWindow) {
super(mc, reloader, errorConsumer, false);
this.minecraft = mc;
this.reload = reloader;
this.onFinish = errorConsumer;
this.displayWindow = displayWindow;
displayWindow.addMojangTexture(mc.getTextureManager().getTexture(new ResourceLocation("textures/gui/title/mojangstudios.png")).getId());
this.progress = StartupMessageManager.addProgressBar("Minecraft Progress", 100);
}
public static Supplier<LoadingOverlay> newInstance(Supplier<Minecraft> mc, Supplier<ReloadInstance> ri, Consumer<Optional<Throwable>> handler, DisplayWindow window) {
return ()->new ForgeLoadingOverlay(mc.get(), ri.get(), handler, window);
}
@Override
public void render(final @NotNull GuiGraphics graphics, final int mouseX, final int mouseY, final float partialTick) {
long millis = Util.getMillis();
float fadeouttimer = this.fadeOutStart > -1L ? (float)(millis - this.fadeOutStart) / 1000.0F : -1.0F;
progress.setAbsolute(clamp((int)(this.reload.getActualProgress() * 100f), 0, 100));
var fade = 1.0F - clamp(fadeouttimer - 1.0F, 0.0F, 1.0F);
var colour = this.displayWindow.context().colourScheme().background();
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, fade);
if (fadeouttimer >= 1.0F) {
if (this.minecraft.screen != null) {
this.minecraft.screen.render(graphics, 0, 0, partialTick);
}
displayWindow.render(0xff);
} else {
GlStateManager._clearColor(colour.redf(), colour.greenf(), colour.bluef(), 1f);
GlStateManager._clear(GL_COLOR_BUFFER_BIT, Minecraft.ON_OSX);
displayWindow.render(0xFF);
}
RenderSystem.enableBlend();
RenderSystem.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
var fbWidth = this.minecraft.getWindow().getWidth();
var fbHeight = this.minecraft.getWindow().getHeight();
glViewport(0, 0, fbWidth, fbHeight);
final var twidth = this.displayWindow.context().width();
final var theight = this.displayWindow.context().height();
var wscale = (float)fbWidth / twidth;
var hscale = (float)fbHeight / theight;
var scale = this.displayWindow.context().scale() * Math.min(wscale, hscale) / 2f;
var wleft = clamp(fbWidth * 0.5f - scale * twidth, 0, fbWidth);
var wtop = clamp(fbHeight * 0.5f - scale * theight, 0, fbHeight);
var wright = clamp(fbWidth * 0.5f + scale * twidth, 0, fbWidth);
var wbottom = clamp(fbHeight * 0.5f + scale * theight, 0, fbHeight);
GlStateManager.glActiveTexture(GL_TEXTURE0);
RenderSystem.disableCull();
BufferBuilder bufferbuilder = Tesselator.getInstance().getBuilder();
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, fade);
RenderSystem.getModelViewMatrix().identity();
RenderSystem.setProjectionMatrix(new Matrix4f().setOrtho(0.0F, fbWidth, 0.0F, fbHeight, 0.1f, -0.1f), VertexSorting.ORTHOGRAPHIC_Z);
RenderSystem.setShader(GameRenderer::getPositionColorShader);
// This is fill in around the edges - it's empty solid colour
bufferbuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
// top box from hpos
addQuad(bufferbuilder, 0, fbWidth, wtop, fbHeight, colour, fade);
// bottom box to hpos
addQuad(bufferbuilder, 0, fbWidth, 0, wtop, colour, fade);
// left box to wpos
addQuad(bufferbuilder, 0, wleft, wtop, wbottom, colour, fade);
// right box from wpos
addQuad(bufferbuilder, wright, fbWidth, wtop, wbottom, colour, fade);
BufferUploader.drawWithShader(bufferbuilder.end());
// This is the actual screen data from the loading screen
RenderSystem.enableBlend();
RenderSystem.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
RenderSystem.setShader(GameRenderer::getPositionTexColorShader);
RenderSystem.setShaderTexture(0, displayWindow.getFramebufferTextureId());
bufferbuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR);
bufferbuilder.vertex(wleft, wbottom, 0f).uv(0, 0).color(1f, 1f, 1f, fade).endVertex();
bufferbuilder.vertex(wright, wbottom, 0f).uv(1, 0).color(1f, 1f, 1f, fade).endVertex();
bufferbuilder.vertex(wright, wtop, 0f).uv(1, 1).color(1f, 1f, 1f, fade).endVertex();
bufferbuilder.vertex(wleft, wtop, 0f).uv(0, 1).color(1f, 1f, 1f, fade).endVertex();
glTexParameterIi(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterIi(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
BufferUploader.drawWithShader(bufferbuilder.end());
RenderSystem.defaultBlendFunc();
RenderSystem.disableBlend();
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1f);
if (fadeouttimer >= 2.0F) {
this.minecraft.setOverlay(null);
this.displayWindow.close();
}
if (this.fadeOutStart == -1L && this.reload.isDone()) {
progress.complete();
this.fadeOutStart = Util.getMillis();
try {
this.reload.checkExceptions();
this.onFinish.accept(Optional.empty());
} catch (Throwable throwable) {
this.onFinish.accept(Optional.of(throwable));
}
if (this.minecraft.screen != null) {
this.minecraft.screen.init(this.minecraft, this.minecraft.getWindow().getGuiScaledWidth(), this.minecraft.getWindow().getGuiScaledHeight());
}
}
}
private static void addQuad(BufferVertexConsumer bufferbuilder, float x0, float x1, float y0, float y1, ColourScheme.Colour colour, float fade) {
bufferbuilder.vertex(x0, y0, 0f).color(colour.redf(), colour.greenf(), colour.bluef(), fade).endVertex();
bufferbuilder.vertex(x0, y1, 0f).color(colour.redf(), colour.greenf(), colour.bluef(), fade).endVertex();
bufferbuilder.vertex(x1, y1, 0f).color(colour.redf(), colour.greenf(), colour.bluef(), fade).endVertex();
bufferbuilder.vertex(x1, y0, 0f).color(colour.redf(), colour.greenf(), colour.bluef(), fade).endVertex();
}
}

View file

@ -5,12 +5,25 @@
package net.minecraftforge.client.loading;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import com.mojang.blaze3d.platform.Monitor;
import com.mojang.blaze3d.platform.VideoMode;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.LoadingOverlay;
import net.minecraft.server.packs.resources.ReloadInstance;
import java.util.Optional;
import java.util.function.*;
public final class NoVizFallback {
public static LongSupplier fallback(IntSupplier width, IntSupplier height, Supplier<String> title, LongSupplier monitor) {
public static LongSupplier windowHandoff(IntSupplier width, IntSupplier height, Supplier<String> title, LongSupplier monitor) {
return ()->org.lwjgl.glfw.GLFW.glfwCreateWindow(width.getAsInt(), height.getAsInt(), title.get(), monitor.getAsLong(), 0L);
}
public static Supplier<LoadingOverlay> loadingOverlay(Supplier<Minecraft> mc, Supplier<ReloadInstance> ri, Consumer<Optional<Throwable>> ex, boolean fadein) {
return () -> new LoadingOverlay(mc.get(), ri.get(), ex, fadein);
}
public static Boolean windowPositioning(Optional<Monitor> monitor, IntConsumer widthSetter, IntConsumer heightSetter, IntConsumer xSetter, IntConsumer ySetter) {
return Boolean.FALSE;
}
}

View file

@ -360,7 +360,6 @@ public class GameData
ordered.addAll(keySet.stream().sorted(ResourceLocation::compareNamespaced).toList());
RuntimeException aggregate = new RuntimeException();
for (ResourceLocation rootRegistryName : ordered)
{
try
@ -386,7 +385,6 @@ public class GameData
aggregate.addSuppressed(t);
}
}
if (aggregate.getSuppressed().length > 0)
{
LOGGER.fatal("Failed to register some entries, see suppressed exceptions for details", aggregate);
@ -771,9 +769,7 @@ public class GameData
}
private static void fireRemapEvent(final Map<ResourceLocation, Map<ResourceLocation, IdMappingEvent.IdRemapping>> remaps, final boolean isFreezing) {
StartupMessageManager.modLoaderConsumer().ifPresent(s->s.accept("Remapping mod data"));
MinecraftForge.EVENT_BUS.post(new IdMappingEvent(remaps, isFreezing));
StartupMessageManager.modLoaderConsumer().ifPresent(s->s.accept("Remap complete"));
}
//Has to be split because of generics, Yay!