Skip to content

Commit a5f823e

Browse files
committed
Serialize linkable entities incrementally for performance
1 parent 3dab3b9 commit a5f823e

File tree

5 files changed

+50
-6
lines changed

5 files changed

+50
-6
lines changed

Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ package enum ConvertActionConverter {
7777

7878
// Arrays to gather additional metadata if `emitDigest` is `true`.
7979
var indexingRecords = [IndexingRecord]()
80-
var linkSummaries = [LinkDestinationSummary]()
8180
var assets = [RenderReferenceType : [RenderReference]]()
8281
var coverageInfo = [CoverageDataEntry]()
8382
let coverageFilterClosure = documentationCoverageOptions.generateFilterClosure()
@@ -142,16 +141,18 @@ package enum ConvertActionConverter {
142141
let nodeLinkSummaries = entity.externallyLinkableElementSummaries(context: context, renderNode: renderNode, includeTaskGroups: true)
143142
let nodeIndexingRecords = try renderNode.indexingRecords(onPage: identifier)
144143

144+
for linkSummary in nodeLinkSummaries {
145+
try outputConsumer.consumeIncremental(linkableElementSummary: linkSummary)
146+
}
145147
resultsGroup.async(queue: resultsSyncQueue) {
146148
assets.merge(renderNode.assetReferences, uniquingKeysWith: +)
147-
linkSummaries.append(contentsOf: nodeLinkSummaries)
148149
indexingRecords.append(contentsOf: nodeIndexingRecords)
149150
}
150151
} else if FeatureFlags.current.isLinkHierarchySerializationEnabled {
151152
let nodeLinkSummaries = entity.externallyLinkableElementSummaries(context: context, renderNode: renderNode, includeTaskGroups: false)
152153

153-
resultsGroup.async(queue: resultsSyncQueue) {
154-
linkSummaries.append(contentsOf: nodeLinkSummaries)
154+
for linkSummary in nodeLinkSummaries {
155+
try outputConsumer.consumeIncremental(linkableElementSummary: linkSummary)
155156
}
156157
}
157158
} catch {
@@ -171,7 +172,7 @@ package enum ConvertActionConverter {
171172
if emitDigest {
172173
signposter.withIntervalSignpost("Emit digest", id: signposter.makeSignpostID()) {
173174
do {
174-
try outputConsumer.consume(linkableElementSummaries: linkSummaries)
175+
try outputConsumer.finishedConsumingLinkElementSummaries()
175176
try outputConsumer.consume(indexingRecords: indexingRecords)
176177
try outputConsumer.consume(assets: assets)
177178
} catch {
@@ -187,7 +188,7 @@ package enum ConvertActionConverter {
187188
try outputConsumer.consume(linkResolutionInformation: serializableLinkInformation)
188189

189190
if !emitDigest {
190-
try outputConsumer.consume(linkableElementSummaries: linkSummaries)
191+
try outputConsumer.finishedConsumingLinkElementSummaries()
191192
}
192193
} catch {
193194
recordProblem(from: error, in: &conversionProblems, withIdentifier: "link-resolver")

Sources/SwiftDocC/Infrastructure/ConvertOutputConsumer.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ public protocol ConvertOutputConsumer {
2626
func consume(assetsInBundle bundle: DocumentationBundle) throws
2727

2828
/// Consumes the linkable element summaries produced during a conversion.
29+
/// > Warning: This method might be called concurrently.
30+
func consumeIncremental(linkableElementSummary: LinkDestinationSummary) throws
31+
/// Consumes the linkable element summaries produced during a conversion.
32+
func finishedConsumingLinkElementSummaries() throws
33+
34+
@available(*, deprecated, renamed: "consume(linkableElementSummary:)", message: "Use 'consume(linkableElementSummary:)' instead. This deprecated API will be removed after 6.2 is released")
2935
func consume(linkableElementSummaries: [LinkDestinationSummary]) throws
3036

3137
/// Consumes the indexing records produced during a conversion.
@@ -58,3 +64,9 @@ public extension ConvertOutputConsumer {
5864
func consume(buildMetadata: BuildMetadata) throws {}
5965
func consume(linkResolutionInformation: SerializableLinkResolutionInformation) throws {}
6066
}
67+
68+
// Default implementations to avoid a source breaking change from introducing new protocol requirements
69+
public extension ConvertOutputConsumer {
70+
func consume(linkableElementSummary: LinkDestinationSummary) throws {}
71+
func finishedConsumingLinkElementSummaries() throws {}
72+
}

Sources/SwiftDocCUtilities/Action/Actions/Convert/ConvertFileWritingConsumer.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,33 @@ struct ConvertFileWritingConsumer: ConvertOutputConsumer {
147147
}
148148
}
149149

150+
private var linkableElementsData = Synchronized(Data())
151+
152+
/// Consumes one linkable element summary produced during a conversion.
153+
func consumeIncremental(linkableElementSummary: LinkDestinationSummary) throws {
154+
let data = try encode(linkableElementSummary)
155+
linkableElementsData.sync {
156+
if !$0.isEmpty {
157+
$0.append(Data(",".utf8))
158+
}
159+
$0.append(data)
160+
}
161+
}
162+
163+
/// Finishes consuming the linkable element summaries produced during a conversion.
164+
func finishedConsumingLinkElementSummaries() throws {
165+
let linkableElementsURL = targetFolder.appendingPathComponent(Self.linkableEntitiesFileName, isDirectory: false)
166+
let data = linkableElementsData.sync { accumulatedData in
167+
var data = Data()
168+
swap(&data, &accumulatedData)
169+
data.insert(UTF8.CodeUnit(ascii: "["), at: 0)
170+
data.append(UTF8.CodeUnit(ascii: "]"))
171+
172+
return data
173+
}
174+
try fileManager.createFile(at: linkableElementsURL, contents: data)
175+
}
176+
150177
func consume(linkableElementSummaries summaries: [LinkDestinationSummary]) throws {
151178
let linkableElementsURL = targetFolder.appendingPathComponent(Self.linkableEntitiesFileName, isDirectory: false)
152179
let data = try encode(summaries)

Tests/SwiftDocCTests/Converter/DocumentationConverterTests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class DocumentationConverterTests: XCTestCase {
2222
func consume(problems: [Problem]) throws { }
2323
func consume(assetsInBundle bundle: DocumentationBundle) throws {}
2424
func consume(linkableElementSummaries: [LinkDestinationSummary]) throws {}
25+
func consumeIncremental(linkableElementSummary: LinkDestinationSummary) throws {}
26+
func finishedConsumingLinkElementSummaries() throws {}
2527
func consume(indexingRecords: [IndexingRecord]) throws {}
2628
func consume(assets: [RenderReferenceType: [RenderReference]]) throws {}
2729
func consume(benchmarks: Benchmark) throws {}

Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class TestRenderNodeOutputConsumer: ConvertOutputConsumer {
2424
func consume(problems: [Problem]) throws { }
2525
func consume(assetsInBundle bundle: DocumentationBundle) throws { }
2626
func consume(linkableElementSummaries: [LinkDestinationSummary]) throws { }
27+
func consumeIncremental(linkableElementSummary: LinkDestinationSummary) throws { }
28+
func finishedConsumingLinkElementSummaries() throws { }
2729
func consume(indexingRecords: [IndexingRecord]) throws { }
2830
func consume(assets: [RenderReferenceType: [RenderReference]]) throws { }
2931
func consume(benchmarks: Benchmark) throws { }

0 commit comments

Comments
 (0)