mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-09 10:39:46 -10:00
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:
1 parent
2bad442a93
commit
a1cf6491dd
2 files changed
+80
-18
No files matched your search
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user