mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 17:21:49 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
660a170f61 |
No files matched your search
@@ -77,6 +77,37 @@ pub struct DebugCharts {
|
||||
previous_tick: Option<Instant>,
|
||||
bytes_seen: Option<i64>,
|
||||
}
|
||||
/// `Connection.getAverageSentPackets` / `getAverageReceivedPackets`: once a second
|
||||
/// (`Connection.tick`, every 20 ticks from the first) each average moves a quarter of
|
||||
/// the way to the packets counted since the last time.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct PacketRates {
|
||||
/// The connection's totals at the last update.
|
||||
seen: Option<(i64, i64)>,
|
||||
ticks: u32,
|
||||
pub sent: f32,
|
||||
pub received: f32,
|
||||
}
|
||||
impl PacketRates {
|
||||
/// One client tick, with the connection's sent and received packet totals.
|
||||
pub fn tick(&mut self, sent: i64, received: i64) {
|
||||
let tick = self.ticks;
|
||||
self.ticks = self.ticks.wrapping_add(1);
|
||||
if !tick.is_multiple_of(20) {
|
||||
return;
|
||||
}
|
||||
let (last_sent, last_received) = self.seen.unwrap_or((0, 0));
|
||||
// Mth.lerp(0.75, count, average).
|
||||
let lerp = |count: i64, average: f32| {
|
||||
let count = (count.max(0)) as f32;
|
||||
count + 0.75 * (average - count)
|
||||
};
|
||||
self.sent = lerp(sent - last_sent, self.sent);
|
||||
self.received = lerp(received - last_received, self.received);
|
||||
self.seen = Some((sent, received));
|
||||
}
|
||||
}
|
||||
|
||||
impl DebugCharts {
|
||||
/// `toggleFpsCharts`; `overlay` is the debug overlay's visibility.
|
||||
pub fn toggle_fps(&mut self, overlay: &mut bool) {
|
||||
@@ -480,6 +511,19 @@ fn shaded(out: &mut Vec<GuiDraw>, font: &Font, s: &str, x: i32, y: i32) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn packet_rates_move_a_quarter_of_the_way_each_second() {
|
||||
let mut rates = super::PacketRates::default();
|
||||
// The first tick already counts (Connection.tick's tickCount++ % 20 == 0).
|
||||
rates.tick(40, 400);
|
||||
assert_eq!((rates.sent, rates.received), (10.0, 100.0));
|
||||
for _ in 1..20 {
|
||||
rates.tick(1000, 1000);
|
||||
assert_eq!((rates.sent, rates.received), (10.0, 100.0));
|
||||
}
|
||||
rates.tick(80, 400);
|
||||
assert_eq!((rates.sent, rates.received), (17.5, 75.0));
|
||||
}
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -187,20 +187,25 @@ impl Game<'_> {
|
||||
/// they show, `BandwidthDebugMonitor.tick` always.
|
||||
pub(super) fn tick_debug_charts(&mut self) {
|
||||
let shown = self.debug && self.debug_charts.network;
|
||||
let Some(r) = &self.remote else {
|
||||
let Some(r) = &mut self.remote else {
|
||||
// The integrated server answers within the tick.
|
||||
if shown {
|
||||
self.debug_charts.ping.log(0);
|
||||
}
|
||||
return;
|
||||
};
|
||||
let received = r
|
||||
.session
|
||||
.counters
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.map(|c| c.bytes_in.load(std::sync::atomic::Ordering::Relaxed));
|
||||
let totals = r.session.counters.lock().unwrap().as_ref().map(|c| {
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
(
|
||||
c.bytes_in.load(Relaxed),
|
||||
c.sent.load(Relaxed),
|
||||
c.received.load(Relaxed),
|
||||
)
|
||||
});
|
||||
let received = totals.map(|(bytes, sent, packets)| {
|
||||
r.packet_rates.tick(sent, packets);
|
||||
bytes
|
||||
});
|
||||
if let Some(total) = received {
|
||||
self.debug_charts.log_bandwidth(total);
|
||||
}
|
||||
@@ -403,8 +408,27 @@ impl Game<'_> {
|
||||
],
|
||||
);
|
||||
}
|
||||
// tps: DebugEntryTps for the integrated server (it counts no packets).
|
||||
if self.player.is_some() && self.remote.is_none() {
|
||||
// tps: DebugEntryTps. Single player without its server thread has no connection, so
|
||||
// it counts no packets.
|
||||
let rates = self.remote.as_ref().map_or((0.0, 0.0), |r| {
|
||||
(r.packet_rates.sent, r.packet_rates.received)
|
||||
});
|
||||
if let Some(r) = self.remote.as_ref().filter(|r| !r.local) {
|
||||
let status = if self.clock.stepping() {
|
||||
" (frozen - stepping)"
|
||||
} else if self.clock.frozen() {
|
||||
" (frozen)"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let brand = r.session.brand.lock().unwrap().clone();
|
||||
d.line(format!(
|
||||
"\"{}\" server{status}, {:.0} tx, {:.0} rx",
|
||||
brand.as_deref().unwrap_or("null"),
|
||||
rates.0,
|
||||
rates.1
|
||||
));
|
||||
} else if self.player.is_some() || self.remote.is_some() {
|
||||
let (target, status) = if self.clock.sprinting() {
|
||||
("-".to_owned(), " (sprinting)")
|
||||
} else if self.clock.stepping() {
|
||||
@@ -418,8 +442,8 @@ impl Game<'_> {
|
||||
(format!("{:.1}", 1000.0 / self.clock.rate()), "")
|
||||
};
|
||||
d.line(format!(
|
||||
"Integrated server @ {:.1}/{target} ms{status}, 0 tx, 0 rx",
|
||||
self.debug_charts.smoothed_tick_ms
|
||||
"Integrated server @ {:.1}/{target} ms{status}, {:.0} tx, {:.0} rx",
|
||||
self.debug_charts.smoothed_tick_ms, rates.0, rates.1
|
||||
));
|
||||
}
|
||||
let (left, right) = d.columns(&self.debug_chart_lines());
|
||||
|
||||
@@ -88,6 +88,10 @@ pub(super) struct Remote {
|
||||
was_sprinting: bool,
|
||||
carried: Option<usize>,
|
||||
loaded_sent: bool,
|
||||
/// Single player's integrated server, joined through memory.
|
||||
pub local: bool,
|
||||
/// The packets per second F3 shows.
|
||||
pub packet_rates: horizon_client::debug_chart::PacketRates,
|
||||
/// Open container: (window id, menu name).
|
||||
menu: Option<(i32, String)>,
|
||||
/// The open menu's title as the server named it.
|
||||
@@ -198,6 +202,7 @@ impl Game<'_> {
|
||||
// A signed-in account always plays under its own name.
|
||||
let auth = horizon_client::auth::current();
|
||||
let name = auth.as_ref().map_or(name, |a| a.name.as_str());
|
||||
let is_local = local.is_some();
|
||||
let session = match local {
|
||||
Some(pipe) => Session::connect_local(pipe, name, view, lang.clone()),
|
||||
None => {
|
||||
@@ -271,6 +276,8 @@ impl Game<'_> {
|
||||
was_sprinting: false,
|
||||
carried: None,
|
||||
loaded_sent: false,
|
||||
local: is_local,
|
||||
packet_rates: Default::default(),
|
||||
menu: None,
|
||||
menu_title: None,
|
||||
state_id: 0,
|
||||
|
||||
@@ -56,6 +56,8 @@ pub struct Session {
|
||||
pub address: String,
|
||||
pub name: String,
|
||||
pub counters: Arc<Mutex<Option<Arc<Counters>>>>,
|
||||
/// The server's minecraft:brand payload (ClientCommonPacketListenerImpl.serverBrand).
|
||||
pub brand: Arc<Mutex<Option<String>>>,
|
||||
/// HORIZON_NET_DEBUG=1 logs every packet sent from the game thread.
|
||||
debug: bool,
|
||||
}
|
||||
@@ -90,7 +92,9 @@ impl Session {
|
||||
let (send, events) = channel();
|
||||
let writer = Arc::new(Mutex::new(None));
|
||||
let counters = Arc::new(Mutex::new(None));
|
||||
let (thread_writer, thread_counters) = (writer.clone(), counters.clone());
|
||||
let brand = Arc::new(Mutex::new(None));
|
||||
let (thread_writer, thread_counters, thread_brand) =
|
||||
(writer.clone(), counters.clone(), brand.clone());
|
||||
let (address_owned, name_owned) = (address.to_owned(), name.to_owned());
|
||||
let thread = std::thread::Builder::new()
|
||||
.name("horizon-net".into())
|
||||
@@ -105,6 +109,7 @@ impl Session {
|
||||
&send,
|
||||
&thread_writer,
|
||||
&thread_counters,
|
||||
&thread_brand,
|
||||
);
|
||||
let from_server = result.is_ok();
|
||||
let reason = result.unwrap_or_else(|e| e);
|
||||
@@ -125,6 +130,7 @@ impl Session {
|
||||
address: address.to_owned(),
|
||||
name: name.to_owned(),
|
||||
counters,
|
||||
brand,
|
||||
debug,
|
||||
}
|
||||
}
|
||||
@@ -251,6 +257,7 @@ fn run(
|
||||
send: &Sender<Event>,
|
||||
shared_writer: &Mutex<Option<ConnWriter>>,
|
||||
shared_counters: &Mutex<Option<Arc<Counters>>>,
|
||||
server_brand: &Mutex<Option<String>>,
|
||||
) -> Result<String, String> {
|
||||
let (host, port, mut c) = if let Some(pipe) = local {
|
||||
(address.to_owned(), 0, Connection::local(pipe))
|
||||
@@ -374,7 +381,7 @@ fn run(
|
||||
.map_err(e)?;
|
||||
let (mut reader, writer) = c.split();
|
||||
*shared_writer.lock().unwrap() = Some(writer);
|
||||
let registries = configure(&mut reader, shared_writer, lang)?;
|
||||
let registries = configure(&mut reader, shared_writer, lang, server_brand)?;
|
||||
let _ = send.send(Event::Joined {
|
||||
registries: Arc::new(registries),
|
||||
uuid: profile.uuid,
|
||||
@@ -413,10 +420,13 @@ fn run(
|
||||
}
|
||||
play::Clientbound::StartConfiguration(_) => {
|
||||
reply(play::Serverbound::ConfigurationAcknowledged(Empty));
|
||||
let registries = configure(&mut reader, shared_writer, lang)?;
|
||||
let registries = configure(&mut reader, shared_writer, lang, server_brand)?;
|
||||
let _ = send.send(Event::Reconfigured(Arc::new(registries)));
|
||||
}
|
||||
play::Clientbound::Disconnect(d) => return Ok(plain(&d.reason, lang)),
|
||||
play::Clientbound::CustomPayload(p) if p.channel == "minecraft:brand" => {
|
||||
set_brand(server_brand, &p.data);
|
||||
}
|
||||
other => {
|
||||
if send.send(Event::Packet(Box::new(other))).is_err() {
|
||||
return Ok("Disconnected".into());
|
||||
@@ -432,6 +442,7 @@ fn configure(
|
||||
reader: &mut ConnReader,
|
||||
writer: &Mutex<Option<ConnWriter>>,
|
||||
lang: &Language,
|
||||
server_brand: &Mutex<Option<String>>,
|
||||
) -> Result<Dynamic, String> {
|
||||
let mut registries = Dynamic::default();
|
||||
let reply = |p: cfg::Serverbound| -> Result<(), String> {
|
||||
@@ -452,6 +463,9 @@ fn configure(
|
||||
}))?
|
||||
}
|
||||
cfg::Clientbound::RegistryData(r) => registries.add(r),
|
||||
cfg::Clientbound::CustomPayload(p) if p.channel == "minecraft:brand" => {
|
||||
set_brand(server_brand, &p.data);
|
||||
}
|
||||
cfg::Clientbound::CodeOfConduct(_) => {
|
||||
reply(cfg::Serverbound::AcceptCodeOfConduct(Empty))?
|
||||
}
|
||||
@@ -477,6 +491,13 @@ fn configure(
|
||||
}
|
||||
}
|
||||
|
||||
/// BrandPayload: the name the server gives itself, shown on F3.
|
||||
fn set_brand(shared: &Mutex<Option<String>>, data: &[u8]) {
|
||||
if let Ok(brand) = horizon_protocol::buf::Reader::new(data).string(32767) {
|
||||
*shared.lock().unwrap() = Some(brand);
|
||||
}
|
||||
}
|
||||
|
||||
/// The minecraft:brand payload: a string naming the client.
|
||||
fn brand() -> Vec<u8> {
|
||||
let mut w = horizon_protocol::buf::Writer::new();
|
||||
|
||||
Reference in new issue
Block a user