Skip to content

Commit 8692f41

Browse files
committed
Use a Lock
1 parent 8db24b1 commit 8692f41

File tree

2 files changed

+13
-31
lines changed

2 files changed

+13
-31
lines changed

Sources/CodeEditTextView/TextLayoutManager/TextLayoutManager+Layout.swift

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,6 @@ extension TextLayoutManager {
1515
let maxWidth: CGFloat
1616
}
1717

18-
/// Asserts that the caller is not in an active layout pass.
19-
/// See docs on ``isInLayout`` for more details.
20-
private func assertNotInLayout() {
21-
#if DEBUG // This is redundant, but it keeps the flag debug-only too which helps prevent misuse.
22-
assert(!isInLayout, "layoutLines called while already in a layout pass. This is a programmer error.")
23-
#endif
24-
}
25-
2618
// MARK: - Layout Lines
2719

2820
/// Lays out all visible lines
@@ -63,14 +55,14 @@ extension TextLayoutManager {
6355
/// pass is already ongoing, internal data structures will be broken. In debug builds, this is checked with a simple
6456
/// boolean and assertion.
6557
///
66-
/// To help ensure this property, all view modifications are performed within a `CATransaction`. This guarantees that macOS calls
67-
/// `layout` on any related views only after we’ve finished inserting and removing line fragment views. Otherwise,
68-
/// inserting a line fragment view could trigger a layout pass prematurely and cause this method to re-enter.
69-
///
58+
/// To help ensure this property, all view modifications are performed within a `CATransaction`. This guarantees
59+
/// that macOS calls `layout` on any related views only after we’ve finished inserting and removing line fragment
60+
/// views. Otherwise, inserting a line fragment view could trigger a layout pass prematurely and cause this method
61+
/// to re-enter.
7062
/// - Warning: This is probably not what you're looking for. If you need to invalidate layout, or update lines, this
7163
/// is not the way to do so. This should only be called when macOS performs layout.
7264
public func layoutLines(in rect: NSRect? = nil) { // swiftlint:disable:this function_body_length
73-
assertNotInLayout()
65+
layoutLock.lock()
7466
guard let visibleRect = rect ?? delegate?.visibleRect,
7567
!isInTransaction,
7668
let textStorage else {
@@ -81,9 +73,6 @@ extension TextLayoutManager {
8173
// tree modifications caused by this method are atomic, so macOS won't call `layout` while we're already doing
8274
// that
8375
CATransaction.begin()
84-
#if DEBUG
85-
isInLayout = true
86-
#endif
8776

8877
let minY = max(visibleRect.minY - verticalLayoutPadding, 0)
8978
let maxY = max(visibleRect.maxY + verticalLayoutPadding, 0)
@@ -133,9 +122,6 @@ extension TextLayoutManager {
133122
newVisibleLines.insert(linePosition.data.id)
134123
}
135124

136-
#if DEBUG
137-
isInLayout = false
138-
#endif
139125
// Enqueue any lines not used in this layout pass.
140126
viewReuseQueue.enqueueViews(notInSet: usedFragmentIDs)
141127

@@ -149,9 +135,6 @@ extension TextLayoutManager {
149135
// Commit the view tree changes we just made.
150136
CATransaction.commit()
151137

152-
// These are fine to update outside of `isInLayout` as our internal data structures are finalized at this point
153-
// so laying out again won't break our line storage or visible line.
154-
155138
if maxFoundLineWidth > maxLineWidth {
156139
maxLineWidth = maxFoundLineWidth
157140
}
@@ -163,6 +146,7 @@ extension TextLayoutManager {
163146
if originalHeight != lineStorage.height || layoutView?.frame.size.height != lineStorage.height {
164147
delegate?.layoutManagerHeightDidUpdate(newHeight: lineStorage.height)
165148
}
149+
layoutLock.unlock()
166150
}
167151

168152
// MARK: - Layout Single Line
@@ -215,10 +199,11 @@ extension TextLayoutManager {
215199
let relativeMinY = max(layoutData.minY - position.yPos, 0)
216200
let relativeMaxY = max(layoutData.maxY - position.yPos, relativeMinY)
217201

218-
for lineFragmentPosition in line.lineFragments.linesStartingAt(
219-
relativeMinY,
220-
until: relativeMaxY
221-
) {
202+
// for lineFragmentPosition in line.lineFragments.linesStartingAt(
203+
// relativeMinY,
204+
// until: relativeMaxY
205+
// ) {
206+
for lineFragmentPosition in line.lineFragments {
222207
let lineFragment = lineFragmentPosition.data
223208

224209
layoutFragmentView(for: lineFragmentPosition, at: position.yPos + lineFragmentPosition.yPos)

Sources/CodeEditTextView/TextLayoutManager/TextLayoutManager.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,10 @@ public class TextLayoutManager: NSObject {
7878
public var isInTransaction: Bool {
7979
transactionCounter > 0
8080
}
81-
#if DEBUG
81+
8282
/// Guard variable for an assertion check in debug builds.
8383
/// Ensures that layout calls are not overlapping, potentially causing layout issues.
84-
/// This is used over a lock, as locks in performant code such as this would be detrimental to performance.
85-
/// Also only included in debug builds. DO NOT USE for checking if layout is active or not. That is an anti-pattern.
86-
var isInLayout: Bool = false
87-
#endif
84+
var layoutLock: NSLock = NSLock()
8885

8986
weak var layoutView: NSView?
9087

0 commit comments

Comments
 (0)