Измерения - отдельные миры
Эта страница объясняет, где должен работать ваш код, чтобы видеть игроков и блоки, кому разрешено писать состояние, общее для всех измерений, и как игрок попадает из одного измерения в другое.
В Paper World - объект, до которого плагин дотянется откуда угодно. В mcrs измерение - отдельная симуляция, и код главного приложения не может трогать ее напрямую. Примеры взяты из файлов репозитория, вне репозиторных тестов мы их не запускали.
Одно главное приложение, один sub-app на измерение
Заголовок раздела «Одно главное приложение, один sub-app на измерение»У сервера одно главное приложение Bevy, App. Каждое измерение работает как SubApp, то есть приложение Bevy, вложенное в главное, и у него свой World со своими сущностями и ресурсами.1 В одном общем мире каждая система обходила бы сущности всех измерений и отфильтровывала их по компоненту измерения. А еще измерения не могли бы тикать параллельно или позже переехать на другую машину.
Для вашего кода из этого кое-что следует. Система мира измерения видит только сущности этого измерения. Неизменяемые реестры передаются измерениям как значения Arc, а изменяемое состояние между ними не разделяется.
Кроме того, у мира измерения нет ресурса Time, и стандартное расписание Main он не запускает. Он выполняет First, PreUpdate, расписания Fixed*, Update, PostUpdate и Last по одному разу за тик хоста. Поэтому измерение считает тики и не может зависеть от настенных часов, как сказано в doc-комментарии в sub_app.rs.
Плагин попадает в измерение через add_dimension_plugin
Заголовок раздела «Плагин попадает в измерение через add_dimension_plugin»Плагин, добавленный через add_plugins, попадает только в главное приложение, где он не видит ни игроков, ни блоков. Чтобы поместить плагин в измерения, используйте трейт AddDimensionPlugins:2
pub trait AddDimensionPlugins { fn add_dimension_plugin(&mut self, plugin: impl Plugin + Clone) -> &mut Self;
fn add_dimension_plugin_to( &mut self, dimension: impl Into<ResourceKeyBuf<Dimension>>, plugin: impl Plugin + Clone, ) -> &mut Self;}crates/mcrs_minecraft_engine_world/src/world/sub_app.rs:112-120
add_dimension_plugin(plugin)собирает копию плагина в каждом мире измерения.add_dimension_plugin_to(key, plugin)собирает его только в измерении с этим ключом, напримерOVERWORLD.
Плагин должен реализовать Clone, потому что каждый мир собирает собственный клон. Миры собирают клоны в том порядке, в каком плагины были добавлены. Добавляйте плагин до того, как измерения появятся, потому что уже работающее измерение более поздний плагин не получит.
Еще одно правило касается уникальных плагинов: так в Bevy называют плагин, который приложение может содержать только один раз. Уникальный плагин, который вы добавили для одного измерения, заменяет в этом мире уникальные плагины с тем же именем, добавленные для всех измерений. Любой другой уникальный плагин, имя которого в мире уже есть, пропускается, а сервер пишет ошибку в лог.
Тест из репозитория проверяет каждое правило. Он добавляет один счетчик в каждый мир, а другой счетчик только в верхний мир. Затем он проверяет, что в Незере второго счетчика нет, а верхний мир получил значение плагина, добавленного для его ключа.
#[test]fn dimension_plugins_run_in_every_world_or_in_the_world_of_their_key() { let mut app = host_app::make_host_app(); app.add_dimension_plugin(CountTicks::<true>) .add_dimension_plugin(Rate(1)) .add_dimension_plugin_to(OVERWORLD, SeesSpawn) .add_dimension_plugin_to(OVERWORLD, AddsCountTicks) .add_dimension_plugin_to(OVERWORLD, CountTicks::<false>) .add_dimension_plugin_to(OVERWORLD, Rate(2)); host_app::materialise_sub_apps( &mut app, &[ ("minecraft:overworld", "minecraft:overworld"), ("minecraft:the_nether", "minecraft:the_nether"), ], ); for _ in 0..5 { app.update(); }
assert!(ticks::<true>(&mut app, "minecraft:overworld").is_some_and(|n| n > 0)); assert!(ticks::<true>(&mut app, "minecraft:the_nether").is_some_and(|n| n > 0)); assert!(ticks::<false>(&mut app, "minecraft:overworld").is_some_and(|n| n > 0)); assert_eq!(ticks::<false>(&mut app, "minecraft:the_nether"), None); assert_eq!( world(&mut app, "minecraft:overworld") .resource::<RateSet>() .0, 2 ); assert_eq!( world(&mut app, "minecraft:the_nether") .resource::<RateSet>() .0, 1 );crates/mcrs_minecraft_server/tests/it/dimension_plugins.rs:90-125
У глобального состояния один писатель
Заголовок раздела «У глобального состояния один писатель»Каждое измерение читает игровое время, часы мира, погоду, игровые правила, границу мира и таблицы счета. У каждой части этого состояния один владелец, и пишет ее только он.3 В одном процессе владелец - главное приложение.
Измерение держит снапшот с версией, доступный только для чтения, и получает новый снапшот, только когда версия меняется. Когда измерению нужно изменение, например из-за команды или смены погоды, оно шлет сообщение владельцу.
Для плагина это значит, что внутри измерения не нужно заводить собственную копию игровых часов и двигать ее, потому что копии разойдутся. То же относится к тестовому окружению. Когда оно задает игровые правила, погоду или часы, оно пишет через владельца, а не в мир измерения.
Игрок переходит между измерениями как сообщение
Заголовок раздела «Игрок переходит между измерениями как сообщение»Система одного измерения не может дотянуться до другого мира, поэтому сервер не может перенести сущность игрока из одного мира в другой. Портал или любой другой переход, решенный внутри тика, пишет запрос:1
#[derive(Message, Clone, Debug)]pub struct ChangeDimensionRequest { pub player: Entity, pub to: ResourceKeyBuf<Dimension>, pub transform: Transform,}crates/mcrs_minecraft_engine_world/src/entity/player/mod.rs:19-24
В конце тика старый мир забирает переносимое состояние игрока, удаляет игрока и передает хосту уход. В новом мире игрок появляется на следующем тике. Правила описаны в разделе “Change of dimension” документа docs/command-dimensions.md.
Тот же документ описывает, как команды работают между мирами. Команда выполняется в одном мире. Ее эффекты в других мирах передаются сообщениями, и хост пересылает их между тиками. Как обработчик записывает такой эффект, показано на странице Добавить команду.
Создать измерение во время работы пока нельзя
Заголовок раздела «Создать измерение во время работы пока нельзя»Дальше: Убрать и заменить ванильные части.
Footnotes
Заголовок раздела «Footnotes»-
Частично#362 Решение о том, что каждое измерение работает как под-приложение со своим миром. ↩ ↩2
-
Работает#280 Позволяет пользовательским плагинам работать в каждом мире измерения. ↩ ↩2
-
Работает#357 Решение о том, что глобальное изменяемое состояние пишет один владелец. ↩
-
В работе#546 Планирует создание и удаление миров измерений на ходу и перенос игроков между ними. ↩
-
В работе#281 Планирует источник чанков как заменяемую точку расширения с пустым и плоским источниками. ↩
-
Частично#283 Отслеживает публичные события игрока, которые плагин может отменить. ↩
-
В работе#142 Отслеживает, что игроки приходят в целевое измерение полноценными игроками. ↩
Источники и проверка
Проверено: 26.4-snapshot-7, 51d45c26a, 2026-11-09
Переведено с версии 51d45c26a