From 966a144a644a10f2648e50b2cb2f5104e968e3a7 Mon Sep 17 00:00:00 2001 From: sasheg dev <152582704+SashegDev@users.noreply.github.com> Date: Mon, 19 Oct 2026 01:02:30 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A2=D0=B8=D0=BA=20=D0=BC=D0=BE=D0=B4=D0=B0?= =?UTF-8?q?=20=D0=B4=D0=BE=D0=BB=D0=B6=D0=B5=D0=BD=20=D0=B8=D0=B4=D1=82?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=BE=20=D0=B8=D0=B3=D1=80=D0=BE=D0=B2=D0=BE?= =?UTF-8?q?=D0=BC=D1=83=20=D1=82=D0=B8=D0=BA=D1=83,=20HUD=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=BB=D0=B6=D0=B5=D0=BD=20=D0=B2=D0=B8=D0=B4=D0=B5=D0=BD=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA?= =?UTF-8?q?=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Тик. Моды тикали из покадрового хука, то есть получали столько тиков, сколько кадров успевает нарисовать машина. На 200 fps это в семь раз быстрее игры: таймер мода уезжал, а счётчик тиков модов расходился с миром, поверх которого он нарисован. Теперь тик вызывается рядом с runTick(), внутри того же цикла, так что у мода ровно двадцать шагов в секунду независимо от частоты кадров. HUD. Экран загрузки мира рисует сам, мимо хвоста кадра. Пока генерируется ландшафт, LoadingScreenRenderer рисует в собственный framebuffer, блитит его в окно и сам вызывает updateDisplay() — до десяти раз в секунду, из потока, который занят генерацией. Всё, что нарисовано из покадрового хука, стирается следующим таким выводом, поэтому оверлей пропадал на всю загрузку и возвращался уже в мире. Добавлен отдельный вход в тот же код рисования оверлеев, вызываемый после блита и до вывода в окно. Заодно разделены две ошибки в RsxBootstrap: сбой тика и сбой отрисовки больше не выключают друг друга, у каждого своя защита. Проверено вживую: оверлей виден на экране генерации ландшафта. --- game-fork/patches/decompiler-fixes.txt | 25 +++++++ .../net/rustnotmc/client/RsxBootstrap.java | 69 +++++++++++++++---- 2 files changed, 79 insertions(+), 15 deletions(-) diff --git a/game-fork/patches/decompiler-fixes.txt b/game-fork/patches/decompiler-fixes.txt index cf763e1..998beee 100644 --- a/game-fork/patches/decompiler-fixes.txt +++ b/game-fork/patches/decompiler-fixes.txt @@ -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(); diff --git a/game-fork/src-overlay/net/rustnotmc/client/RsxBootstrap.java b/game-fork/src-overlay/net/rustnotmc/client/RsxBootstrap.java index 5fcb85c..0324778 100644 --- a/game-fork/src-overlay/net/rustnotmc/client/RsxBootstrap.java +++ b/game-fork/src-overlay/net/rustnotmc/client/RsxBootstrap.java @@ -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();