Несколько модов одновременно и живая работа с игрой
Главное: модлоадер физически не умел запускать больше одного мода. Родной
загрузчик игры запоминает тик-хук в статическом поле, которое перезаписывается
на каждой загрузке:
do = new ilxq(); // свой загрузчик на мод
int = method2; // статическое поле: помнит только ПОСЛЕДНИЙ мод
Из-за этого getRunnableList() вызывался у одного мода. Остальные грузились,
их onLoad отрабатывал, задачи висели в очереди - и их никто никогда не
разбирал. Молча.
Чинится без обфускации: в мосте одна очередь на всех и один drain. Какой бы
special.Main игра ни позвала, он выполняет работу всех модов. Владелец задачи
запоминается в ThreadLocal в момент onLoad, поэтому в логе видно, чей код
упал. Счетчик падений тоже переехал с записи в очереди на саму задачу: очередь
чистится каждый тик, и счетчик на ней обнулялся бы раньше, чем дошел до
предела.
API:
- Rsx.Player и Rsx.World: getName/getX/getY/getZ/getHealth/getMaxHealth/
isAlive/isDead и getTime/isClient. Раньше player() отдавал Object, и мод
вытаскивал имя из toString() обфусцированного класса.
- События: Rsx.on("tick"|"worldChange"|"playerJoin"|"playerLeave"|
"screenOpen"|"screenClose"). Заменяют polling.
- Rsx.everyTicks(n) - периодичность по тикам, не плывет и не двоит после лага.
- RsxMod.apiVersion() и onError(): мод заявляет версию API и сам решает, что
делать с падением. Без этого сломанный мод заливал лог.
- Задача, упавшая 5 раз подряд, отбрасывается целиком.
- Rsx.error() и именованные every/post - чтобы в логе было видно, чё это.
Профиль перегенерирован: в него попали члены Entity (posX/posY/posZ/isDead,
getName/getEntityId/isEntityAlive), EntityLivingBase (здоровье) и World
(isRemote/getWorldTime), которых раньше не было. Сгенерированы по SRG-именам
через make_profile.py, поэтому переживут очередную реобфускацию.
Тест-стенд переписан под три мода и воспроизводит обе особенности вендорного
загрузчика: новый classloader на jar и перезаписываемое статическое поле.
Контрольный прогон с отключённым drain роняет 10 проверок из 17, так что
проверки действительно завязаны на этот механизм.
Заодно: сломанный фейковый загрузчик делил один classloader на все моды,
и такой тест не мог поймать проблему с изоляцией.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
package com.example;
|
||||
|
||||
import net.rustexremake.api.Rsx;
|
||||
import net.rustexremake.api.RsxMod;
|
||||
|
||||
/**
|
||||
* Example mod.
|
||||
*
|
||||
* Note what is NOT here: no obfuscated class name, no field name, no reflection.
|
||||
* The mod asks for logical names, and the bridge maps them onto the build that
|
||||
* is installed. Build this once - it keeps working across vendor updates.
|
||||
*
|
||||
* Everything below runs on the game's own thread, so touching game state is
|
||||
* legal. The mod sees a typed view of the player and the world, never the
|
||||
* obfuscated objects behind them.
|
||||
*/
|
||||
public class ExampleMod implements RsxMod {
|
||||
|
||||
private int ticks;
|
||||
|
||||
@Override
|
||||
public int apiVersion() {
|
||||
// what this mod was written against; the bridge warns if the numbers
|
||||
// ever stop matching, instead of the mod breaking quietly
|
||||
return Rsx.API_VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
Rsx.info("ExampleMod загружен, сборка " + Rsx.buildId()
|
||||
+ ", API " + Rsx.apiVersion());
|
||||
|
||||
// Events beat polling: this fires only when something actually changes.
|
||||
Rsx.on("worldChange", () -> Rsx.info("мир сменился"));
|
||||
Rsx.on("playerJoin", () -> Rsx.info("вошли: " + Rsx.player().getName()));
|
||||
Rsx.on("playerLeave", () -> Rsx.info("вышли"));
|
||||
|
||||
// Counted in game ticks, so it never drifts and never fires twice after
|
||||
// a lag. This is the one to reach for by default.
|
||||
Rsx.everyTicks(20, this::onSecond);
|
||||
|
||||
// Milliseconds are available for things that are not gameplay:
|
||||
// the interval is wall-clock, so a lagging game thread makes it drift.
|
||||
Rsx.every(5000L, () -> Rsx.info("тиков: " + ticks + ", bridge tick: " + Rsx.tick()));
|
||||
}
|
||||
|
||||
/** Runs once per game second, on the game thread. */
|
||||
private void onSecond() {
|
||||
ticks++;
|
||||
|
||||
Rsx.Player player = Rsx.player();
|
||||
if (!player.isPresent()) {
|
||||
return;
|
||||
}
|
||||
Rsx.World world = Rsx.world();
|
||||
|
||||
Rsx.info(String.format(
|
||||
"секунда %d | игрок: %s (%.1f/%.1f/%.1f) hp=%.1f/%.1f | мир: t=%d%s",
|
||||
ticks,
|
||||
player.getName(),
|
||||
player.getX(), player.getY(), player.getZ(),
|
||||
player.getHealth(), player.getMaxHealth(),
|
||||
world.getTime(),
|
||||
world.isClient() ? "" : " [сервер]"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable error) {
|
||||
// вызывается, когда задача мода упала; по умолчанию только пишет в лог
|
||||
Rsx.error("ExampleMod: упало, " + error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnload() {
|
||||
Rsx.info("ExampleMod выгружается");
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package com.example;
|
||||
|
||||
import net.rustexremake.api.Rsx;
|
||||
import net.rustexremake.api.RsxMod;
|
||||
|
||||
/**
|
||||
* Example mod.
|
||||
*
|
||||
* Note what is NOT here: no obfuscated class name, no field name, no reflection.
|
||||
* The mod asks for logical names, and the bridge maps them onto the build that
|
||||
* is installed. Build this once - it keeps working across vendor updates.
|
||||
*/
|
||||
public class ExampleMod implements RsxMod {
|
||||
|
||||
private int ticks;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
Rsx.info("ExampleMod загружен, билд " + Rsx.buildId());
|
||||
|
||||
// Repeating task. It is queued by a timer thread but always executed by
|
||||
// the game's own tick, so touching game state here is safe.
|
||||
Rsx.every(1000L, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ticks++;
|
||||
Rsx.info("tick " + ticks
|
||||
+ " | игрок: " + Rsx.player()
|
||||
+ " | мир: " + Rsx.world()
|
||||
+ " | экран: " + Rsx.currentScreen());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnload() {
|
||||
Rsx.info("ExampleMod выгружен");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user