Creaking hearts: build the mob world only for the heart's update

A heart built the mob world (every player, the teams, the weather) each tick,
but reads it only in the update about once a second. server_tick is split at
that point; the game builds the world for the update only. The heart's
creaking, asked for a few times a tick, is looked up where it was last found
before scanning every mob.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
bulkandClaude Opus 5.5 committed 2026-11-07 17:33:25 +00:00
1 parent 2bad442a93
commit a1cf6491dd
2 files changed
+80 -18

No files matched your search

+53 -15
View File
@@ -38,19 +38,45 @@ struct Host<'a> {
creaking: Option<Arc<MobType>>,
next_entity: &'a mut u32,
spawning: bool,
/// Where a mob was last found ([`CreakingState::slots`]).
slots: &'a std::cell::RefCell<horizon_client::fast_hash::FxHashMap<u32, usize>>,
}
impl Host<'_> {
/// The slot of the live mob `id` in `mobs`: where it was last found, or
/// by a scan. Ids are unique, so the first match is the only one.
fn slot(&self, id: u32) -> Option<usize> {
let found = |slot: usize| {
self.mobs
.get(slot)
.is_some_and(|m| m.id == id && !m.removed)
};
if let Some(&slot) = self.slots.borrow().get(&id)
&& found(slot)
{
return Some(slot);
}
let slot = self.mobs.iter().position(|m| m.id == id && !m.removed)?;
let mut slots = self.slots.borrow_mut();
// Only hearts' creakings are asked for; ones long gone are dropped.
if slots.len() >= 4096 {
slots.clear();
}
slots.insert(id, slot);
Some(slot)
}
}
impl HeartHost for Host<'_> {
fn mob(&self, id: u32) -> Option<&Mob> {
self.mobs
.iter()
.chain(self.added.iter())
.find(|m| m.id == id && !m.removed)
match self.slot(id) {
Some(slot) => Some(&self.mobs[slot]),
None => self.added.iter().find(|m| m.id == id && !m.removed),
}
}
fn mob_mut(&mut self, id: u32) -> Option<&mut Mob> {
self.mobs
.iter_mut()
.chain(self.added.iter_mut())
.find(|m| m.id == id && !m.removed)
match self.slot(id) {
Some(slot) => Some(&mut self.mobs[slot]),
None => self.added.iter_mut().find(|m| m.id == id && !m.removed),
}
}
fn mob_for_uuid(&self, uuid: [i32; 4]) -> Option<u32> {
self.uuids
@@ -94,8 +120,9 @@ impl Game<'_> {
}
/// Runs `f` on the heart at `key` with the game's mobs and level random,
/// then applies what the heart reported. `others`: whether `f` reads the
/// other mobs through the mob world (without, it holds none: building them
/// costs a pass over every mob).
/// mob world, which is then built with the other mobs (`f` gets None
/// otherwise: the world reads every player, the teams and the weather, and
/// most heart ticks need none of it).
fn with_heart(
&mut self,
key: Pos,
@@ -103,7 +130,7 @@ impl Game<'_> {
others: bool,
f: impl FnOnce(
&mut CreakingHeart,
&horizon_client::mobs::MobWorld,
Option<&horizon_client::mobs::MobWorld>,
&mut LegacyRandom,
&mut Host,
&mut Vec<HeartEvent>,
@@ -123,7 +150,7 @@ impl Game<'_> {
};
let mut events = Vec::new();
let added = {
let w = self.mob_world_base(&snapshot);
let w = others.then(|| self.mob_world_base(&snapshot));
let mut host = Host {
mobs: &mut mobs,
added: Vec::new(),
@@ -131,8 +158,9 @@ impl Game<'_> {
creaking,
next_entity: &mut next_entity,
spawning,
slots: &self.creaking.slots,
};
f(heart, &w, &mut random, &mut host, &mut events);
f(heart, w.as_ref(), &mut random, &mut host, &mut events);
host.added
};
mobs.extend(added);
@@ -178,7 +206,9 @@ impl Game<'_> {
return;
};
let _ = self.with_heart(key, &mut heart, true, |h, w, lr, host, out| {
creaking_heart::creaking_hurt(h, home, w, lr, host, out);
if let Some(w) = w {
creaking_heart::creaking_hurt(h, home, w, lr, host, out);
}
});
self.simulation
.world
@@ -293,7 +323,12 @@ impl Game<'_> {
let pos = [key.0, key.1, key.2];
let others = creaking_heart::tick_updates(&heart);
self.with_heart(key, &mut heart, others, |h, w, lr, host, out| {
creaking_heart::server_tick(h, pos, w, lr, host, out);
if creaking_heart::server_tick_start(h, pos, lr, host, out) {
debug_assert!(w.is_some(), "tick_updates said no update");
if let Some(w) = w {
creaking_heart::server_tick_update(h, pos, w, lr, host, out);
}
}
})?;
}
self.simulation
@@ -386,6 +421,9 @@ pub(super) struct CreakingState {
revision: Option<u64>,
/// Which block states are creaking hearts, by state id.
hearts: Vec<bool>,
/// The slot in the mob list where each heart's creaking was last found:
/// a heart asks for it a few times a tick. A hint, checked by id.
slots: std::cell::RefCell<horizon_client::fast_hash::FxHashMap<u32, usize>>,
}
impl CreakingState {
@@ -507,6 +507,20 @@ pub fn server_tick(
host: &mut impl HeartHost,
out: &mut Vec<HeartEvent>,
) {
if server_tick_start(heart, pos, lr, host, out) {
server_tick_update(heart, pos, w, lr, host, out);
}
}
/// [`server_tick`] up to its update, which reads no mob world: whether the
/// update follows (as [`tick_updates`] said before it).
pub fn server_tick_start(
heart: &mut CreakingHeart,
pos: [i32; 3],
lr: &mut LegacyRandom,
host: &mut impl HeartHost,
out: &mut Vec<HeartEvent>,
) -> bool {
heart.ticks_existed += 1;
let signal = output_signal(heart, pos, host);
if heart.output_signal != signal {
@@ -549,9 +563,19 @@ pub fn server_tick(
}
let ticker = heart.ticker;
heart.ticker -= 1;
if ticker >= 0 {
return;
}
ticker < 0
}
/// The rest of [`server_tick`], once [`server_tick_start`] says so:
/// `updateCreakingState` and the protector checks.
pub fn server_tick_update(
heart: &mut CreakingHeart,
pos: [i32; 3],
w: &MobWorld,
lr: &mut LegacyRandom,
host: &mut impl HeartHost,
out: &mut Vec<HeartEvent>,
) {
heart.ticker = lr.next_int_bound(5).unwrap_or(0) + 20;
let mut b = Blocks {
w,