Skip to content

Commit 0a46938

Browse files
committed
Serialize linkable entities incrementally for performance
1 parent 82e15c1 commit 0a46938

File tree

6 files changed

+52
-9
lines changed

6 files changed

+52
-9
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 : [any RenderReference]]()
8281
var coverageInfo = [CoverageDataEntry]()
8382
let coverageFilterClosure = documentationCoverageOptions.generateFilterClosure()
@@ -151,16 +150,18 @@ package enum ConvertActionConverter {
151150
let nodeLinkSummaries = entity.externallyLinkableElementSummaries(context: context, renderNode: renderNode, includeTaskGroups: true)
152151
let nodeIndexingRecords = try renderNode.indexingRecords(onPage: identifier)
153152

153+
for linkSummary in nodeLinkSummaries {
154+
try outputConsumer.consumeIncremental(linkableElementSummary: linkSummary)
155+
}
154156
resultsGroup.async(queue: resultsSyncQueue) {
155157
assets.merge(renderNode.assetReferences, uniquingKeysWith: +)
156-
linkSummaries.append(contentsOf: nodeLinkSummaries)
157158
indexingRecords.append(contentsOf: nodeIndexingRecords)
158159
}
159160
} else if FeatureFlags.current.isLinkHierarchySerializationEnabled {
160161
let nodeLinkSummaries = entity.externallyLinkableElementSummaries(context: context, renderNode: renderNode, includeTaskGroups: false)
161162

162-
resultsGroup.async(queue: resultsSyncQueue) {
163-
linkSummaries.append(contentsOf: nodeLinkSummaries)
163+
for linkSummary in nodeLinkSummaries {
164+
try outputConsumer.consumeIncremental(linkableElementSummary: linkSummary)
164165
}
165166
}
166167
} catch {
@@ -180,7 +181,7 @@ package enum ConvertActionConverter {
180181
if emitDigest {
181182
signposter.withIntervalSignpost("Emit digest", id: signposter.makeSignpostID()) {
182183
do {
183-
try outputConsumer.consume(linkableElementSummaries: linkSummaries)
184+
try outputConsumer.finishedConsumingLinkElementSummaries()
184185
try outputConsumer.consume(indexingRecords: indexingRecords)
185186
try outputConsumer.consume(assets: assets)
186187
} catch {
@@ -196,7 +197,7 @@ package enum ConvertActionConverter {
196197
try outputConsumer.consume(linkResolutionInformation: serializableLinkInformation)
197198

198199
if !emitDigest {
199-
try outputConsumer.consume(linkableElementSummaries: linkSummaries)
200+
try outputConsumer.finishedConsumingLinkElementSummaries()
200201
}
201202
} catch {
202203
recordProblem(from: error, in: &conversionProblems, withIdentifier: "link-resolver")

Sources/SwiftDocC/Infrastructure/ConvertOutputConsumer.swift

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

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

3238
/// Consumes the indexing records produced during a conversion.
@@ -60,8 +66,15 @@ public extension ConvertOutputConsumer {
6066
func consume(linkResolutionInformation: SerializableLinkResolutionInformation) throws {}
6167
}
6268

69+
// Default implementations to avoid a source breaking change from introducing new protocol requirements
70+
public extension ConvertOutputConsumer {
71+
func consumeIncremental(linkableElementSummary: LinkDestinationSummary) throws {}
72+
func finishedConsumingLinkElementSummaries() throws {}
73+
}
74+
6375
// Default implementation so that conforming types don't need to implement deprecated API.
6476
public extension ConvertOutputConsumer {
77+
func consume(linkableElementSummaries: [LinkDestinationSummary]) throws {}
6578
func _deprecated_consume(problems: [Problem]) throws {}
6679
}
6780

Sources/SwiftDocC/Utility/FeatureFlags.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public struct FeatureFlags: Codable {
2020
/// Whether or not support for emitting a serialized version of the local link resolution information is enabled.
2121
public var isLinkHierarchySerializationEnabled = true
2222

23-
@available(*, deprecated, renamed: "isLinkHierarchySerializationEnabled", message: "Use 'isLinkHierarchySerializationEnabled' instead. This deprecated API will be removed after 6.2 is released")
23+
@available(*, deprecated, renamed: "isLinkHierarchySerializationEnabled", message: "Use 'isLinkHierarchySerializationEnabled' instead. This deprecated API will be removed after 6.3 is released")
2424
public var isExperimentalLinkHierarchySerializationEnabled: Bool {
2525
get { isLinkHierarchySerializationEnabled }
2626
set { isLinkHierarchySerializationEnabled = newValue }

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,33 @@ struct ConvertFileWritingConsumer: ConvertOutputConsumer, ExternalNodeConsumer {
152152
}
153153
}
154154

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

Sources/SwiftDocCUtilities/ArgumentParsing/Subcommands/Convert.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ extension Docc {
496496

497497
// This flag only exist to allow developers to pass the previous '--enable-experimental-...' flag without errors.
498498
@Flag(name: .customLong("enable-experimental-external-link-support"), help: .hidden)
499-
@available(*, deprecated, message: "This deprecated API will be removed after 6.2 is released")
499+
@available(*, deprecated, message: "This deprecated API will be removed after 6.3 is released")
500500
var enableExperimentalLinkHierarchySerialization = false
501501

502502
@Flag(help: .hidden)
@@ -595,7 +595,7 @@ extension Docc {
595595
set { featureFlags.enableLinkHierarchySerialization = newValue }
596596
}
597597

598-
@available(*, deprecated, renamed: "enableLinkHierarchySerialization", message: "Use 'enableLinkHierarchySerialization' instead. This deprecated API will be removed after 6.2 is released")
598+
@available(*, deprecated, renamed: "enableLinkHierarchySerialization", message: "Use 'enableLinkHierarchySerialization' instead. This deprecated API will be removed after 6.3 is released")
599599
public var enableExperimentalLinkHierarchySerialization: Bool {
600600
get { enableLinkHierarchySerialization }
601601
set { enableLinkHierarchySerialization = newValue }

Tests/SwiftDocCTests/TestRenderNodeOutputConsumer.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class TestRenderNodeOutputConsumer: ConvertOutputConsumer, ExternalNodeConsumer
2323

2424
func consume(assetsInBundle bundle: DocumentationBundle) throws { }
2525
func consume(linkableElementSummaries: [LinkDestinationSummary]) throws { }
26+
func consumeIncremental(linkableElementSummary: LinkDestinationSummary) throws { }
27+
func finishedConsumingLinkElementSummaries() throws { }
2628
func consume(indexingRecords: [IndexingRecord]) throws { }
2729
func consume(assets: [RenderReferenceType: [any RenderReference]]) throws { }
2830
func consume(benchmarks: Benchmark) throws { }

0 commit comments

Comments
 (0)