mirror of
https://github.com/MinecraftForge/MinecraftForge
synced 2026-08-22 04:26:10 -04:00
This is an early display window system for forge. (#9558)
This commit is contained in:
parent
551feaf019
commit
031d008826
76 changed files with 3065 additions and 1058 deletions
|
|
@ -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.
|
||||
=========================================================================
|
||||
|
||||
|
||||
|
|
|
|||
10
build.gradle
10
build.gradle
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) -> {
|
||||
|
|
|
|||
93
fmlearlydisplay/build.gradle
Normal file
93
fmlearlydisplay/build.gradle
Normal 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
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
net.minecraftforge.fml.earlydisplay.DisplayWindow
|
||||
BIN
fmlearlydisplay/src/main/resources/Monocraft.ttf
Normal file
BIN
fmlearlydisplay/src/main/resources/Monocraft.ttf
Normal file
Binary file not shown.
BIN
fmlearlydisplay/src/main/resources/forge_anvil.png
Normal file
BIN
fmlearlydisplay/src/main/resources/forge_anvil.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
fmlearlydisplay/src/main/resources/forge_logo.png
Normal file
BIN
fmlearlydisplay/src/main/resources/forge_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
fmlearlydisplay/src/main/resources/forgejwst.png
Normal file
BIN
fmlearlydisplay/src/main/resources/forgejwst.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 MiB |
13
fmlearlydisplay/src/main/resources/glfailure.txt
Normal file
13
fmlearlydisplay/src/main/resources/glfailure.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
█▀▀▀▀▀█ ▀▄▄▄▀█ █ █▀▀▀▀▀█
|
||||
█ ███ █ ███ ▀███▀ █ ███ █
|
||||
█ ▀▀▀ █ ▀ ▄ ███▀█ █ ▀▀▀ █
|
||||
▀▀▀▀▀▀▀ ▀ █ ▀ ▀▄█ ▀▀▀▀▀▀▀
|
||||
█▀█▀▄▄▀▄▀ █▄▀▄ ▀▀▀ ▄▀▀▀▄▀
|
||||
▄█▄▄ ▀▀███▀██▄ █▀ ▀▄▄
|
||||
▀█▄▀ ▀▀▄▄▀▀ █▀█▄▄████ ▀▀█
|
||||
▄▀▀▄▀ ▀▄▀▄█ ▀ ▀▀▀▄█ ▀▀▄
|
||||
▀ ▀▀ ▄ ▄██▀ ▄█▀▀▀█▀█▀█
|
||||
█▀▀▀▀▀█ ▄█▄▄▀▀▄█ ▀ █ ▀▀▀
|
||||
█ ███ █ ▄▄ ▄ █ ▀██▀██▄██
|
||||
█ ▀▀▀ █ ██▄███▀█ █ █ █▀
|
||||
▀▀▀▀▀▀▀ ▀▀ ▀▀▀ ▀▀▀▀▀▀▀▀
|
||||
BIN
fmlearlydisplay/src/main/resources/squirrel.png
Normal file
BIN
fmlearlydisplay/src/main/resources/squirrel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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)));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
@ -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 () -> {};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_) {
|
||||
|
|
|
|||
|
|
@ -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_) -> {
|
||||
|
|
|
|||
|
|
@ -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 @@
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ include 'fmlcore'
|
|||
include 'mclanguage'
|
||||
include 'javafmllanguage'
|
||||
include 'lowcodelanguage'
|
||||
include 'fmlearlydisplay'
|
||||
|
||||
include ':mcp'
|
||||
include ':clean'
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue