Тик мода должен идти по игровому тику, HUD должен виден при загрузке

Тик. Моды тикали из покадрового хука, то есть получали столько тиков,
сколько кадров успевает нарисовать машина. На 200 fps это в семь раз
быстрее игры: таймер мода уезжал, а счётчик тиков модов расходился с
миром, поверх которого он нарисован. Теперь тик вызывается рядом с
runTick(), внутри того же цикла, так что у мода ровно двадцать шагов в
секунду независимо от частоты кадров.

HUD. Экран загрузки мира рисует сам, мимо хвоста кадра. Пока генерируется
ландшафт, LoadingScreenRenderer рисует в собственный framebuffer, блитит
его в окно и сам вызывает updateDisplay() — до десяти раз в секунду, из
потока, который занят генерацией. Всё, что нарисовано из покадрового хука,
стирается следующим таким выводом, поэтому оверлей пропадал на всю
загрузку и возвращался уже в мире. Добавлен отдельный вход в тот же код
рисования оверлеев, вызываемый после блита и до вывода в окно.

Заодно разделены две ошибки в RsxBootstrap: сбой тика и сбой отрисовки
больше не выключают друг друга, у каждого своя защита.

Проверено вживую: оверлей виден на экране генерации ландшафта.
This commit is contained in:
sasheg dev
2026-10-19 01:02:30 +03:00
parent b6e847e981
commit 966a144a64
2 changed files with 79 additions and 15 deletions
+25
View File
@@ -1092,3 +1092,28 @@ boolean flag = this.onGround || d8 != d1 && d8 < 0.0D;
=== FILE net/minecraft/client/Minecraft.java
--- BEFORE
for (int k = 0; k < Math.min(10, this.timer.elapsedTicks); ++k) {
this.runTick();
}
--- AFTER
for (int k = 0; k < Math.min(10, this.timer.elapsedTicks); ++k) {
this.runTick();
net.rustnotmc.client.RsxBootstrap.clientTick();
}
=== FILE net/minecraft/client/LoadingScreenRenderer.java
--- BEFORE
this.framebuffer.framebufferRender(l * k, i1 * k);
}
this.mc.updateDisplay();
--- AFTER
this.framebuffer.framebufferRender(l * k, i1 * k);
}
net.rustnotmc.client.RsxBootstrap.renderLoadingScreen(this.mc);
this.mc.updateDisplay();
@@ -15,9 +15,10 @@ import net.rustnotmc.loader.ModLoader;
/**
* Where the client and the mod loader meet.
*
* The loader is a library, not an agent. The game calls in at two points: once
* during startup, and once per frame. Everything a mod does hangs off those two
* calls, so the loader never has to know how a game loop is shaped.
* The loader is a library, not an agent. The game calls in at three points:
* once during startup, once per game tick and once per frame. Everything a mod
* does hangs off those three calls, so the loader never has to know how a game
* loop is shaped.
*
* Every entry point is wrapped. A mod that throws must not be able to take the
* client down with it, and a loader that cannot start must not stop the game
@@ -33,6 +34,7 @@ public final class RsxBootstrap {
private static RsxClientServices client;
private static boolean broken;
private static boolean frameFailed;
private static boolean tickFailed;
private RsxBootstrap() {
}
@@ -134,28 +136,65 @@ public final class RsxBootstrap {
}
/**
* Runs scheduled mod work and draws registered overlays.
* Runs one game tick of mod work.
*
* Called once per frame with the main framebuffer bound and the game's own
* matrices already unwound, so the overlay sets up its own projection and
* leaves the state it did not set alone.
* Called from inside the client's tick loop, next to its own tick, so a mod
* sees the same twenty steps per second the game does. Driving this from
* the frame loop instead would hand a mod as many ticks as the machine can
* draw: at 200 fps a mod would run its timers seven times too fast, and any
* mod that counts ticks would disagree with the world it is drawn over.
*/
public static void renderFrame(Minecraft minecraft) {
if (loader == null || surface == null || !surface.available()) {
return;
}
if (frameFailed) {
public static synchronized void clientTick() {
if (loader == null || broken || tickFailed) {
return;
}
if (client != null) {
client.advanceFrame();
}
try {
loader.tick();
if (client != null) {
client.advanceTick();
}
} catch (Throwable throwable) {
tickFailed = true;
Log.error("RustNotMC: mod tick failed, disabling mod ticks", throwable);
}
}
/**
* Counts the frame and draws registered overlays.
*
* Called once per frame after the game has presented its own framebuffer and
* unwound its matrices, so the overlay sets up its own projection and leaves
* the state it did not set alone.
*/
public static void renderFrame(Minecraft minecraft) {
if (client != null) {
client.advanceFrame();
}
drawOverlay(minecraft);
}
/**
* Draws the overlays on top of the world loading screen.
*
* The loading screen is the one place that does not go through the frame
* tail. While terrain is generated it draws into a framebuffer of its own,
* blits that to the window and presents it itself, up to ten times a second,
* straight from the thread doing the generating. Anything drawn from the
* frame hook is wiped by the next of those presents, which is why an overlay
* used to vanish for the whole load and come back afterwards in the world.
*/
public static void renderLoadingScreen(Minecraft minecraft) {
drawOverlay(minecraft);
}
private static void drawOverlay(Minecraft minecraft) {
if (loader == null || surface == null || !surface.available()) {
return;
}
if (frameFailed) {
return;
}
try {
ScaledResolution scaled = new ScaledResolution(minecraft);
int width = scaled.getScaledWidth();
int height = scaled.getScaledHeight();