Valheim
You are viewing a potentially older version of this package. View Latest Version
Install with App

Details

Date Uploaded
3 weeks ago
Downloads
1.2K
Size
104KB
ADDatHost Valheim hosting
30% off

BeanheimNet

Networking fixes for Valheim 1.0 dedicated servers and clients, with built-in telemetry. One DLL, installed on the server and on every client. No version lock: a player without it still connects and plays vanilla.

The problem

Valheim keeps at most 10 KB of object updates in flight per player and pins Steam's send rate at 150 KB/s. A player 150 ms away can receive at most about 68 KB/s, and the same limit caps what each player uploads. Building pieces are always sent before creatures, carts and dropped items. Fish and ambient animals write an update every frame they move. On a busy server this shows up as creatures that stutter or teleport, carts that freeze and snap, drops that appear seconds late and hits that land late.

What it does

Everything players cannot see ships on. The two features that change what players see, Cleanup and ItemMerge, ship off.

Feature Effect Default
SendWindow Sizes each player's in-flight window from their measured ping instead of the fixed 10 KB on
SteamTransport Raises Steam's per-connection send rate from 150 KB/s to 1 MB/s on
Scheduler Sends to every connected player each interval instead of one player per frame. Scheduler.ServerTargetFrameRate also raises a dedicated server's frame cap from 30 to 60; it follows the master switch, not this one on
RelayThrottle Limits how often fish, far creatures, far items, floating logs and sound effects are re-sent. Near a player only fish (6 updates/s) and sound effects (10/s) are slowed on
Priority Creatures, carts and drops next to a player no longer wait behind a distant building backlog on
Deadband Owners skip updates for movement under a centimetre or a degree, and send one exact stop when something comes to rest on
Telemetry Per-player stats every 10 s in the BepInEx log and in BepInEx/BeanheimNet-stats.log, plus network-health lines (see Telemetry below) on
Items Periodic report of dropped items on the server; optional cleanup of old floating junk report on, cleanup off
ItemMerge Merges nearby plain stacks on the client that owns them off
Diagnostics Frame profiler and reflection sampler for chasing long frames off

Before and after

Version 0.3.0, two players, one on LAN and one at 48 ms ping, at a large base on a heavily modded server. Features were switched off for ten minutes and back on for ten, live, with telemetry running throughout. The server and the LAN player's client were switched; the other client stayed on 0.2.2 the whole time.

Features off Features on
Server frame time 47 ms (frame cap 30 fps) 29 ms (frame cap 60 fps)
Server CPU, share of one core 61 % 69 %
Updates of moving objects near you arriving within 100 ms of the last 6 % 75 %
Time a moving object near a player waits before it is relayed, 95th percentile 114–121 ms 75–96 ms
Longest single wait for such an object 13.7 s 1.7 s
Deferred updates in the worst 10 s interval, remote player 440,227 131,635
Remote player's receive rate while entering an area median 60 KB/s, max 68 (the vanilla ceiling) median 91 KB/s, max 154
The LAN client's upload 121 KB/s against the 150 KB/s Steam cap, 1.2 million deferred 57 KB/s, 7,247 deferred
Updates the server receives at rest 759/s 384/s
Client frame time 13.9 ms 13.7 ms

Deferred counts an update once for every send round it waits.

Not everything is better. With features on the server used 8 points more of one core. The remote player's app ping, a round trip through the game's own send queue (Steam's ping was 47 ms), was 131 ms while entering an area with features on and 106 ms with them off, because a larger window queues more; at rest it was 106 ms and 108 ms. The LAN player's fell from 33 ms to 16 ms, one server frame. The LAN client also counted more position corrections of 2 m or more with features on (805 against 644), in a window where it received far more updates of nearby moving objects.

Setup

  • Install on the server and on every client (BepInEx 5).
  • Config: BepInEx/config/helix.beanheim.net.cfg. Edits are picked up within 5 s. No restart.
  • On/off switches are in the numbered sections. Tuning values are in [99 Advanced], named Section.Key (for example Scheduler.SendIntervalSeconds). Upgrading from 0.2.1 or 0.2.2 moves them there once and keeps the values you had changed. A 0.2.0 cfg uses older section names and starts at the defaults.
  • Some defaults differ by side: a dedicated server gets a 60 fps frame cap, Scheduler.SendIntervalSeconds 0.033 and RelayThrottle.OtherHz 0,5,3; clients keep 0.05 and 0,0,0. Values are written into the file, so a cfg copied from one side to the other carries that side's values.
  • Dedicated servers do not read console input. Use the one-shot keys under [11 Triggers] (Census, ItemReport, ItemCleanupOnce, Stats): set one to true, it runs and resets itself.
  • Clients: console command bnet stats | census | items | cleanup dryrun|run | reload | config.
  • Rollback: [01 General] Enabled = false, live, makes every patch behave like vanilla. Delete the plugin folder to uninstall. Nothing persists in the world except Cleanup deletions and ItemMerge merges, and only if they were enabled. 0.2.2 does not read [99 Advanced]: to downgrade, put back the cfg you had before 0.3.0.

Advanced

Settings

The tuning values below are in [99 Advanced] under the section name shown.

  • SendWindow: window = ping × TargetRateKBps (300) × BdpFactor (1.25), clamped between MinWindowBytes (32768) and MaxWindowBytes (65536); vanilla 10240 when there is no ping reading. Includes queue-drain compatibility for mods that wait on the socket queue (ServerSync, Jotunn, ConditionalConfigSync) and raises Jotunn's MaximumSendQueueSize while on. Refuses to install if ZDOMan.SendZDOs does not have the expected shape.
  • Scheduler: SendIntervalSeconds (0.033 on a dedicated server, 0.05 on clients) caps send rounds per player per second; FrameBudgetMs (4) bounds the time spent per frame. ServerTargetFrameRate (60; 0 = vanilla 30) applies to a dedicated server only and follows the master switch, not the Scheduler switch. In the measurement above it was switched together with the other features, so the frame time and CPU there are for everything at once.
  • SteamTransport: SendRateMin and SendRateMax set to SendRateKBps (1024). Steam requires them equal. Applied globally and to live connections, and read back. SendBufferBytes (0 = Steam's default) sets Steam's send buffer; the original size is put back when it returns to 0.
  • RelayThrottle: rates in updates per second as near/far/beyond: fish 6/3/2, creature ∞/10/5, item ∞/5/3, floating ∞/5/3, effect 10/5/3, other ∞/5/3 on a dedicated server and ∞/∞/∞ on clients. Bands at NearMeters (40) and FarMeters (100). Distance is measured from the receiving player on the server and from the nearest loaded player on the client upload. Never throttled: never-sent objects, ownership changes, force-sends, players, ships, carts and non-Default object types. Nothing is dropped; a held update goes in the next round.
  • Priority: score = distance − AgeWeight (1.5) × seconds since the last sync − SolidBiasMeters (64) for building pieces − NewNearBonusMeters (200) for never-sent objects inside NearMeters. Foreign-owned Prioritized objects and terrain keep their top tiers. Uses the live character position instead of the one the client reports every 2 s.
  • Deadband: item 0.02 m / 2°, creature 0.01 m / 1°, other 0.01 m / 1°, velocity 0.05 m/s. Measured against the last written value, not the last frame. When an object comes to rest its velocity, and its body velocity, is written as exactly zero once, so other players' games stop moving it. Never players, carts or Prioritized objects. A ship with nobody aboard gets its own band, 0.05 m / 2°, while it is still; vanilla ships are Prioritized, so this reaches only modded ships of the default object type.
  • Items: Cleanup deletes items older than MaxAgeHours (6) of played time, farther than ProtectRadiusMeters (200) from every player, not within BaseProxyRadiusMeters of a BaseProxyPrefabs piece, in water when OnlyInWater is set, and not matching ProtectedPatterns. An item with custom data (every EpicLoot enchantment), a quality above 1, a max stack of 1 (all gear) or unreadable item data is never deleted. Every condition is checked again at the moment of deletion. Every deletion is logged.
  • ItemMerge: same name, quality 1, variant 0, no custom data, within MergeRadiusMeters (5), checked every MergeIntervalSeconds (30). Vanilla auto-stacks at 4 m, once per item and only while more than 200 drops are loaded.

Reading the stats line

  • deferred: updates that did not fit the window this round. Sustained non-zero with two or more players means the window is the limit. With one player it is always 0.
  • refused: rounds that sent nothing because the queue was above the window.
  • rtt is Steam's ping; app is a ping through the game's own send queue. app includes one server frame and one client frame (about 16 ms with the server at 60 fps, 33–76 ms at 30), so the queueing delay is app − rtt − floor.
  • throttled: updates held back by RelayThrottle this interval. They are sent later, not dropped.
  • frame avg on a server line is the server's frame time; frames >50ms N >100ms N counts long frames in the interval. sched frames/served/budget-breaks appear when Scheduler is on.
  • gc N heap N MB cpu N%: garbage collections of any generation in the interval, managed heap size, and process-wide CPU as a share of one core. A long frame with low cpu is waiting, not working.
  • steam-status ok N not-ok N last <EResult> appears when Steam's connection-status call fails; that player's window stays vanilla until it works.
  • Vanilla's own Connections N ZDOS:X sent:Y recv:Z line is the cross-check. Both counters are 0 with one player.
  • To measure a change: turn one toggle off in the cfg, play ten minutes, compare the lines, turn it back on.

Telemetry

These lines only read; nothing is sent or changed. Each has a toggle in [08 Telemetry], on by default.

  • peerx <player> (server, after each peer line; PeerExtras): qtime is Steam's queue time and loss the local and remote packet loss. relay delay near is how long a moving object near that player waited before it was relayed (95th percentile and max); pending>250 counts each time an object near them went more than 250 ms without being relayed. owners near self/other/none is who owns the creatures within 40 m of them. owner changes N p2p N counts creature ownership changes involving them, and how many moved straight between two players. helm!=owner reads the whole interval when, at the check, this player holds the helm of a ship somebody else owns, and 0 otherwise (-1 if that part could not be installed). silence max is the longest gap between pings from that player.
  • client (client, after the stats line; ClientExtras): gaps near is the time between updates of moving objects near you, bucketed <100/<250/<500/<1000/≥1000 ms. jump is how far an update moved an object from where your game expected it (≥0.5 m, ≥2 m), and snaps an object moving 4.9 m or more in one step, split by creature, player and ship; other objects count in the totals only. A move of 100 m or more is a teleport and is not counted. nettime corr is the clock corrections from the server, inst pending and created/s the objects waiting to be created, zdodata the object data received.
  • desync (server; DesyncCounters): ghost-miss counts copies left out of date after an object crossed a zone border. misrouted counts damage aimed at a creature, fish or player that reached nobody able to apply it: another player who does not own the target (peer), the server (server) or nobody (none). rpc top lists routed RPCs by name with their recipient counts.
  • census ghosts and census types (server, at boot and on the Census trigger): planned (*_planned) pieces by zone and age, and every object counted by class and network type.

Diagnostics

All off by default, in [12 Diagnostics], and live.

  • Profiler (both sides): a prof line per interval with the time per frame by phase, the busiest update methods, BeanheimNet's own hooks and its own overhead (self). Its hooks are installed a few per frame when it turns on and removed when it turns off.
  • ReflectionSampler (both sides): a refl line counting reflection calls on the main thread and naming the code that makes them in long frames. It captures a call stack for a small sample of calls, so leave it off unless you are chasing long frames.
  • GcTimeSliceMs (dedicated server): Unity's incremental garbage-collector time slice in milliseconds, for measuring long frames. 0 leaves Unity's value alone.

Compatibility

Every private game member is bound once at startup. A missing member disables only the features that need it, with a logged reason. Every patch body is wrapped: an exception logs once per minute and that call falls back to vanilla. If Smoothbrain Network, ValheimTune, NetworkPerformanceSystem, ReturnToSender, BetterNetworking, BetterNetworking_Valheim, VBNetTweaks, FiresGhettoNetworking, SkadiNet, WarheimNetwork or NetworkTweaks is loaded, only telemetry is installed; the frame-rate setting, ItemMerge and item cleanup stand down too. At boot the plugin lists every other Harmony patch owner of the methods it touches ([owners] lines).

Credits

Some ideas come from Network Performance System, FiresGhettoNetworking, LeanNet and Smoothbrain Network. Ideas only; no code was copied.

Thunderstore development is made possible with ads. Please consider making an exception to your adblock.