Skip to content

Massively improve performance by rewriting the network core & rendering using burst jobs#864

Merged
Phantomical merged 11 commits into
RemoteTechnologiesGroup:developfrom
Phantomical:burst-graph
Jul 7, 2026
Merged

Massively improve performance by rewriting the network core & rendering using burst jobs#864
Phantomical merged 11 commits into
RemoteTechnologiesGroup:developfrom
Phantomical:burst-graph

Conversation

@Phantomical

@Phantomical Phantomical commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

The networking core of RemoteTech is quite slow, and the rendering is even slower. This commit rips all that out and replaces it with a set of burst-compiled jobs that run the network update in the background, along with more burst jobs that generate meshes for the network links and cone rendering. On my 200-vessel test save this is quite literally 20,000x faster.

While there are a few places that this affects the public API, this should be mostly backwards compatible. The external API namespace is completely unchanged, but some of the interfaces have had new methods added and there were a few [KSPField]s that have been converted to properties.

Here's the big changes:

  • The network update is now done by a series of jobs launched from NetworkState.ScheduleNetworkUpdate. There are a couple of managed jobs that read in the state of vessel satellites, ground stations, and celestial bodies, and then a chain of native jobs that do all the steps of the update. Most of burst code is in NetworkUpdate, but there are other jobs that handle some of the parallel parts (BuildEdgesJob and MultiSourceDijkstraJob).
  • All of the network queries now read the resulting data out of the NetworkState instance in NetworkManager. This is always one FixedUpdate behind the actual game state, since the new state is calculated in the background.
  • Network link and transmission cone rendering now works by using burst jobs to build a single mesh for each in the background. We then set the mesh data in OnPreCull and make a single Graphics.DrawMesh call with the whole mesh. The jobs get kicked off in LateUpdate, and so the whole thing overall ends up being quite cheap on CPU.
  • Filtering for the command station markers also has its own custom job. We still use immediate mode graphics to draw these since it wasn't a big cost when I was profiling it.
  • Where otherwise possible I have ripped out code that uses linq and replaced it with references to the network store.

Also, I have introduced two new dependencies: KSPBurst and SerializationFix. This will limit RemoteTech to only being supported on 1.12. People using RT on older versions can continue to use older versions of RT.

Put all together the end result is that the overhead of the network update is approximately fixed (about 0.2ms/FixedUpdate) and we can afford to run the whole update every time. There's still some work to do on performance elsewhere, but I think everything here is about as good as it is going to get.

Performance Numbers

All the numbers here are recorded on a 200-vessel, where every vessel is connected into the network. This is about the worst case possible, and likely to be worse than the save of any existing player using RemoteTech. The game becomes unplayable on the existing version long before anyone could get to this point.
image

Here's what the profiles look like with develop. In all cases this is with the map view open
image

And here's what the profile looks like on this branch
image

It's hard to see where RT is running in that image because it's too fast :), so here's some zoomed in views of the individual parts:

  • FixedUpdate
image
  • LateUpdate
image
  • OnPreCull and OnGUI
image

Reference Unity.Burst, Unity.Collections, Unity.Mathematics and Unity.Jobs
from KSPBurst so the network model can be rewritten against the job system,
and pull in Krafs.Publicizer to expose UnityEngine.Object.m_CachedPtr (for
thread-safe null checks) and List<T>._items (for an AsSpan helper).

Expose internals to the RemoteTech.Tests and RemoteTech.InGameTests
assemblies, bump the language version to 13, and replace the .slnx with a
plain .sln.
Later work is going to need a number of utilities. This commit adds:
* an ArrayMap class that stores keys/values in arrays internally,
* an ArrayMinHeap struct that provides a burst-compatible min-heap,
* a number of extension helper methods for various types
* an ObjectHandle struct for passing managed types to burst jobs
* a IJobParallelForBatchDefer interface and extensions
Replace the managed graph/route computation with an unmanaged pipeline built
on the Unity job system. Satellite and antenna state is projected into native
arrays (NetworkState) once per update, and connectivity, routing and the
render geometry are computed by Burst jobs (NetworkUpdate, Edges,
NetworkUpdateMath, and the line/cone/mark mesh jobs).

Antennas publish their state through a pinned AntennaData registered with
StateManager, so the jobs can read them without touching managed memory.
ISatellite gains a flat SatelliteState snapshot and Antennas becomes an
IReadOnlyList; NetworkLink/NetworkRoute become readonly struct views over the
store rather than heap-allocated objects. The range models are unified behind
IRangeModel so the job path and the managed path share one implementation.

NetworkManager drives the pipeline and NetworkRenderer draws the computed
meshes directly, replacing the per-frame NetworkCone/NetworkLine GameObjects.
RemoteTech.Tests runs the Burst/NativeArray math headlessly under xunit by
backing native arrays with Marshal.AllocHGlobal (NativeArena), covering the
range/geometry math. RemoteTech.InGameTests is a KSP.Testing plugin with an
in-game runner UI that exercises the jobs against real Unity Collections
(adjacency, edges, Dijkstra, mark/cone geometry, hashing).
Route the public API methods through the precomputed NetworkState instead
of the old on-demand LINQ/route materialization:

- Add a GetRoute passthrough to the NetworkManager read seam.
- GetFirstHopToKSC / GetControlPath now read the groundOnly route directly
  via GetRoute rather than filtering + Min()-ing over BuildConnections.
- HasDirectGroundStation / GetClosestDirectGroundStation now test the
  satellite's direct adjacency row (GetLinks) for a ground-station target.
  This treats any direct link to a ground station as qualifying (not only
  first hops of a shortest route) and drops the empty/self-route NRE and
  the empty-sequence Min() throw.
- Replace the O(n) Satellites.Where(...).FirstOrDefault() guid scans with
  the O(1) Satellites[id] indexer; GetName / GetRangeDistance use the
  Network[id] indexer so ground stations still resolve.

GetSignalDelayToSatellite's arbitrary A->B pathfind is left on
NetworkPathfinder; NetworkState has no point-to-point equivalent yet.
Relay capability now determines whether a node may forward a signal (transit),
not whether a link exists. IsEdgeTraversable previously required both endpoints
of every edge to be relay-capable, which wrongly disconnected powered non-relay
endpoints (e.g. passive-SPU vessels) — a route's own endpoints never forward, so
they should not need to relay.

ComputeAdjacencyLists now includes an edge whenever both endpoints are powered.
The relay check moves into a per-node CanTransit bitmask: the multi-source
Dijkstra records a node's score but only expands out of it when it can transit,
and roots (route endpoints) always expand.
Replace GetSignalDelayToSatellite's on-demand A* (NetworkPathfinder) with a
Burst NetworkPathfindJob run over the persisted NetworkState edge table, exposed
as NetworkState.ShortestDelayBetween plus a NetworkManager passthrough. Add the
NetworkUpdateMath.EncodePairIndex helper the job uses to index the triangular
edge table.
The multi-path overlay drew every tree edge in the command-station
forest, so a vessel hidden by the map view filter still showed its green
route back to control. Seed the on-path set by walking the predecessor
forest from each visible vessel instead, so a route is drawn only when a
visible vessel sits at its far end.
The migration onto the Burst NetworkState left the old managed routing
orphaned. Delete the A* NetworkPathfinder and the explicit-mode half of
NetworkRoute it fed (the freestanding-path ctor, Empty, Contains, and the
BidirectionalEdge it compared against), leaving the live value-type view
into the Dijkstra forests. Drop the unused NetworkManager.FindNeighbors /
ConnectionCache / Count, the NetworkState.GetRouteLength / GetRouteOrigin
guid wrappers (callers use the internal index versions), the unused
VesselSatellite.Connections seam, and the never-referenced Dish type.
@Phantomical Phantomical merged commit 003ce96 into RemoteTechnologiesGroup:develop Jul 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant