Skip to content

Commit 163453c

Browse files
committed
RUM-11460 More feedback
1 parent ee17d14 commit 163453c

3 files changed

Lines changed: 56 additions & 11 deletions

File tree

DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/SwiftUI/ShapeResource.swift

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@
77
#if os(iOS)
88

99
import SwiftUI
10-
11-
#if canImport(CryptoKit)
12-
import CryptoKit
13-
#endif
10+
import CommonCrypto
1411

1512
@available(iOS 13.0, *)
1613
internal final class ShapeResource: NSObject {
@@ -24,13 +21,11 @@ internal final class ShapeResource: NSObject {
2421
}
2522

2623
private func makeIdentifier() -> String {
27-
#if canImport(CryptoKit)
28-
let hash = Insecure.MD5.hash(data: self.data)
29-
return hash.map { String(format: "%02hhx", $0) }.joined()
30-
#else
31-
// Should never execute since CryptoKit is available iOS 13
32-
fatalError("CryptoKit not available")
33-
#endif
24+
var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
25+
self.data.withUnsafeBytes { buffer in
26+
_ = CC_MD5(buffer.baseAddress, CC_LONG(buffer.count), &digest)
27+
}
28+
return digest.map { String(format: "%02hhx", $0) }.joined()
3429
}
3530

3631
private func makeData() -> Data {

DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/SwiftUI/ShapeResourceBuilder.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@
99
import Foundation
1010
import SwiftUI
1111

12+
/// A performance-optimized builder for creating SVG-based shape resources from SwiftUI paths.
13+
///
14+
/// `ShapeResourceBuilder` implements a multi-level caching strategy to eliminate expensive
15+
/// recomputations during UI updates and scrolling animations. This addresses performance
16+
/// issues that can occur when the same shapes are rendered repeatedly in Session Replay.
1217
@available(iOS 13.0, *)
1318
internal final class ShapeResourceBuilder {
19+
/// Cache key for SwiftUI path data, used to avoid redundant SVG path string generation.
1420
private class PathKey: NSObject {
1521
private let path: SwiftUI.Path
1622

@@ -32,6 +38,7 @@ internal final class ShapeResourceBuilder {
3238
}
3339
}
3440

41+
/// Composite cache key for complete shape resources, incorporating all visual properties.
3542
private class ResourceKey: NSObject {
3643
private let path: SwiftUI.Path
3744
private let color: ResolvedPaint
@@ -71,14 +78,33 @@ internal final class ShapeResourceBuilder {
7178
}
7279
}
7380

81+
/// Cache for SVG path data strings, preventing redundant path-to-SVG conversions.
7482
private let pathCache = NSCache<PathKey, NSString>()
83+
84+
/// Cache for complete shape resources, avoiding full SVG generation and hashing.
7585
private let resourceCache = NSCache<ResourceKey, ShapeResource>()
7686

7787
init() {
7888
pathCache.countLimit = 25
7989
resourceCache.countLimit = 50
8090
}
8191

92+
/// Creates or retrieves a cached SVG-based shape resource for the given parameters.
93+
///
94+
/// Generated SVG follows this structure:
95+
///
96+
/// ```xml
97+
/// <svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
98+
/// <path d="M 10 10 L 90 90 Z" fill="#FF0000FF" fill-rule="nonzero"/>
99+
/// </svg>
100+
/// ```
101+
///
102+
/// - Parameters:
103+
/// - path: The SwiftUI path defining the shape geometry
104+
/// - color: Resolved paint information including color and opacity
105+
/// - fillStyle: Fill style determining the fill rule (even-odd vs non-zero)
106+
/// - size: The target size for the SVG viewport
107+
/// - Returns: A `ShapeResource` containing the complete SVG markup
82108
func shapeResource(
83109
for path: SwiftUI.Path,
84110
color: ResolvedPaint,
@@ -108,6 +134,13 @@ internal final class ShapeResourceBuilder {
108134
return resource
109135
}
110136

137+
/// Retrieves or generates SVG path data string for the given SwiftUI path.
138+
///
139+
/// This method provides path-level caching to avoid expensive path-to-SVG conversion
140+
/// when the same path geometry is used with different visual properties (colors, sizes).
141+
///
142+
/// - Parameter path: The SwiftUI path to convert to SVG path data
143+
/// - Returns: SVG path data string (e.g., "M 10 10 L 90 90 Z")
111144
private func pathData(for path: SwiftUI.Path) -> String {
112145
let key = PathKey(path)
113146

DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/SwiftUI/ShapeResourceBuilderTests.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,23 @@ final class ShapeResourceBuilderTests: XCTestCase {
115115

116116
XCTAssertFalse(firstResource === secondResource)
117117
}
118+
119+
func testIdentifierStability() {
120+
// Given
121+
let svgString = """
122+
<svg width="50.000" height="50.000" xmlns="http://www.w3.org/2000/svg">
123+
<path d="M 0.000 0.000 L 50.000 50.000" fill="#00000000" fill-rule="evenodd"/>
124+
</svg>
125+
"""
126+
127+
// When
128+
let firstResource = ShapeResource(svgString: svgString)
129+
let secondResource = ShapeResource(svgString: svgString)
130+
131+
// Then
132+
XCTAssertEqual(firstResource.calculateIdentifier(), "33102ea8ca2ccf8c37ba97cdb3391587")
133+
XCTAssertEqual(firstResource.calculateIdentifier(), secondResource.calculateIdentifier())
134+
}
118135
}
119136

120137
#endif

0 commit comments

Comments
 (0)