mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
MeshMap performance: quick wins (#1490)
* MeshMap: change onMapCameraChange frequency to .onEnd so that zooming doesn't cause continuous SwiftUI reevaluation on every frame * MeshMapContent: factor out reducedPrecisionMapCircles into a separate function * MeshMapContent: when multiple reducedPrecisionCircles have the same (lat,lon,radius), just draw one (big perf boost in dense areas)
This commit is contained in:
parent
59d106ac1e
commit
8df71404b3
2 changed files with 51 additions and 13 deletions
|
|
@ -15,6 +15,12 @@ struct IdentifiableOverlay: Identifiable {
|
|||
var id: ObjectIdentifier { ObjectIdentifier(overlay as AnyObject) }
|
||||
}
|
||||
|
||||
struct ReducedPrecisionMapCircleKey: Hashable {
|
||||
let latitudeI: Int32
|
||||
let longitudeI: Int32
|
||||
let precisionBits: Int32
|
||||
}
|
||||
|
||||
struct MeshMapContent: MapContent {
|
||||
|
||||
/// Parameters
|
||||
|
|
@ -43,7 +49,7 @@ struct MeshMapContent: MapContent {
|
|||
@FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)],
|
||||
predicate: NSPredicate(format: "enabled == true", ""), animation: .none)
|
||||
private var routes: FetchedResults<RouteEntity>
|
||||
|
||||
|
||||
@MapContentBuilder
|
||||
var positionAnnotations: some MapContent {
|
||||
ForEach(positions, id: \.id) { position in
|
||||
|
|
@ -60,16 +66,7 @@ struct MeshMapContent: MapContent {
|
|||
|
||||
let nodeColor = UIColor(hex: UInt32(position.nodePosition?.num ?? 0))
|
||||
let positionName = position.nodePosition?.user?.longName ?? "?"
|
||||
/// Reduced Precision Map Circle
|
||||
if 12...15 ~= position.precisionBits {
|
||||
let pp = PositionPrecision(rawValue: Int(position.precisionBits))
|
||||
let radius: CLLocationDistance = pp?.precisionMeters ?? 0
|
||||
if radius > 0.0 {
|
||||
MapCircle(center: position.coordinate, radius: radius)
|
||||
.foregroundStyle(Color(nodeColor).opacity(0.25))
|
||||
.stroke(.white, lineWidth: 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Use a hash of the position ID to stagger animation delays for each node, preventing synchronized animations and improving visual distinction.
|
||||
let calculatedDelay = Double(position.id.hashValue % 100) / 100.0 * 0.5
|
||||
|
||||
|
|
@ -91,7 +88,46 @@ struct MeshMapContent: MapContent {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var reducedPrecisionCircleItems: [(nodeNum: Int64, circleKey: ReducedPrecisionMapCircleKey)] {
|
||||
// Precompute *unique* reduced-precision circles so we don't have to redraw tons of identical (center, radius) circles in dense map areas. (Since they're all transparent, this causes severe FPS drop when zoomed into areas where there are a ton of overlapping circles.)
|
||||
var lowestNumForKey: [ReducedPrecisionMapCircleKey: Int64] = [:]
|
||||
// Populate a dict where the key is (lat, lon, bits) and the value is the *lowest* node.num seen for that key.
|
||||
// That lowest node.num value is used to create a stable color for the MapCircle and stable id for ForEach.
|
||||
for position in positions {
|
||||
// Same filter criteria as positionAnnotations:
|
||||
if (!showFavorites || (position.nodePosition?.favorite == true)) && !(position.nodePosition?.ignored == true) {
|
||||
if 12...15 ~= position.precisionBits {
|
||||
let nodeNum = position.nodePosition?.num ?? 0
|
||||
let key = ReducedPrecisionMapCircleKey(latitudeI: position.latitudeI, longitudeI: position.longitudeI, precisionBits: position.precisionBits)
|
||||
if let existing = lowestNumForKey[key] {
|
||||
if nodeNum < existing { lowestNumForKey[key] = nodeNum }
|
||||
} else {
|
||||
lowestNumForKey[key] = nodeNum
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sort by nodeNum just to keep draw order stable.
|
||||
return lowestNumForKey.map { ($0.value, $0.key) }.sorted { $0.nodeNum < $1.nodeNum }
|
||||
}
|
||||
|
||||
@MapContentBuilder
|
||||
var reducedPrecisionMapCircles: some MapContent {
|
||||
ForEach(reducedPrecisionCircleItems, id: \.nodeNum) { item in
|
||||
let circleKey = item.circleKey
|
||||
let nodeNum = item.nodeNum
|
||||
let radius = PositionPrecision(rawValue: Int(circleKey.precisionBits))?.precisionMeters ?? 0
|
||||
if radius > 0.0 {
|
||||
let center = CLLocationCoordinate2D(latitude: Double(circleKey.latitudeI) / 1e7, longitude: Double(circleKey.longitudeI) / 1e7)
|
||||
let nodeColor = UIColor(hex: UInt32(nodeNum))
|
||||
MapCircle(center: center, radius: radius)
|
||||
.foregroundStyle(Color(nodeColor).opacity(0.25))
|
||||
.stroke(.white, lineWidth: 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MapContentBuilder
|
||||
var routeAnnotations: some MapContent {
|
||||
ForEach(routes) { route in
|
||||
|
|
@ -167,6 +203,7 @@ struct MeshMapContent: MapContent {
|
|||
}
|
||||
|
||||
positionAnnotations
|
||||
reducedPrecisionMapCircles
|
||||
routeAnnotations
|
||||
waypointAnnotations
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,8 @@ struct MeshMap: View {
|
|||
}
|
||||
.controlSize(.regular)
|
||||
.offset(y: 100)
|
||||
.onMapCameraChange(frequency: MapCameraUpdateFrequency.continuous, { context in
|
||||
.onMapCameraChange(frequency: MapCameraUpdateFrequency.onEnd, { context in
|
||||
// distance is only used for long-press waypoint creation, so we don't need continuous updates which touch @State and force rerenders as we pan and (for distance in particular) zoom around the map. onEnd is more than enough.
|
||||
distance = context.camera.distance
|
||||
})
|
||||
.onTapGesture(count: 1, perform: { position in
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue