diff --git a/Sources/SwiftMath/MathBundle/MathFont.swift b/Sources/SwiftMath/MathBundle/MathFont.swift index 81fc7c1..d1d501a 100644 --- a/Sources/SwiftMath/MathBundle/MathFont.swift +++ b/Sources/SwiftMath/MathBundle/MathFont.swift @@ -76,7 +76,9 @@ private class BundleManager { private var ctFonts = [CTFontSizePair: CTFont]() private var rawMathTables = [MathFont: NSDictionary]() - private let threadSafeQueue = DispatchQueue(label: "com.smartmath.mathfont.threadsafequeue", attributes: .concurrent) + private let threadSafeQueue = DispatchQueue(label: "com.smartmath.mathfont.threadsafequeue", + qos: .userInitiated, + attributes: .concurrent) private func registerCGFont(mathFont: MathFont) throws { guard let frameworkBundleURL = Bundle.module.url(forResource: "mathFonts", withExtension: "bundle"), diff --git a/Sources/SwiftMath/MathRender/MTMathAtomFactory.swift b/Sources/SwiftMath/MathRender/MTMathAtomFactory.swift index 6b79c0a..cdbde2f 100644 --- a/Sources/SwiftMath/MathRender/MTMathAtomFactory.swift +++ b/Sources/SwiftMath/MathRender/MTMathAtomFactory.swift @@ -62,6 +62,7 @@ public class MTMathAtomFactory { "rfloor" : "\u{230B}" ] + private static let delimValueLock = NSLock() static var _delimValueToName = [String: String]() public static var delimValueToName: [String: String] { if _delimValueToName.isEmpty { @@ -78,7 +79,11 @@ public class MTMathAtomFactory { } output[value] = key } - _delimValueToName = output + delimValueLock.lock() + defer { delimValueLock.unlock() } + if _delimValueToName.isEmpty { + _delimValueToName = output + } } return _delimValueToName } @@ -98,6 +103,7 @@ public class MTMathAtomFactory { "widetilde" : "\u{0303}" ] + private static let accentValueLock = NSLock() static var _accentValueToName: [String: String]? = nil public static var accentValueToName: [String: String] { if _accentValueToName == nil { @@ -115,7 +121,11 @@ public class MTMathAtomFactory { } output[value] = key } - _accentValueToName = output + accentValueLock.lock() + defer { accentValueLock.unlock() } + if _accentValueToName == nil { + _accentValueToName = output + } } return _accentValueToName! } @@ -390,6 +400,7 @@ public class MTMathAtomFactory { "scriptscriptstyle" : MTMathStyle(style: .scriptOfScript), ] + private static let textToLatexLock = NSLock() static var _textToLatexSymbolName: [String: String]? = nil public static var textToLatexSymbolName: [String: String] { get { @@ -413,13 +424,17 @@ public class MTMathAtomFactory { } output[atom.nucleus] = key } - self._textToLatexSymbolName = output + textToLatexLock.lock() + defer { textToLatexLock.unlock() } + if self._textToLatexSymbolName == nil { + self._textToLatexSymbolName = output + } } return self._textToLatexSymbolName! } - set { - self._textToLatexSymbolName = newValue - } + // set { + // self._textToLatexSymbolName = newValue + // } } // public static let sharedInstance = MTMathAtomFactory() @@ -603,8 +618,13 @@ public class MTMathAtomFactory { e.g. to define a symbol for "lcm" one can call: `MTMathAtomFactory.add(latexSymbol:"lcm", value:MTMathAtomFactory.operatorWithName("lcm", limits: false))` */ public static func add(latexSymbol name: String, value: MTMathAtom) { + let _ = Self.textToLatexSymbolName + // above force textToLatexSymbolName to instantiate first, _textToLatexSymbolName also initialized. + textToLatexLock.lock() + defer { textToLatexLock.unlock() } supportedLatexSymbols[name] = value - Self.textToLatexSymbolName[value.nucleus] = name + // below update the underlying dictionary entry. + Self._textToLatexSymbolName?[value.nucleus] = name } /** Returns a large opertor for the given name. If limits is true, limits are set up on diff --git a/Sources/SwiftMath/MathRender/MTTypesetter.swift b/Sources/SwiftMath/MathRender/MTTypesetter.swift index c766109..791c1a8 100644 --- a/Sources/SwiftMath/MathRender/MTTypesetter.swift +++ b/Sources/SwiftMath/MathRender/MTTypesetter.swift @@ -21,9 +21,15 @@ enum InterElementSpaceType : Int { } var interElementSpaceArray = [[InterElementSpaceType]]() +private let interElementLock = NSLock() func getInterElementSpaces() -> [[InterElementSpaceType]] { if interElementSpaceArray.isEmpty { + + interElementLock.lock() + defer { interElementLock.unlock() } + guard interElementSpaceArray.isEmpty else { return interElementSpaceArray } + interElementSpaceArray = // ordinary operator binary relation open close punct fraction [ [.none, .thin, .nsMedium, .nsThick, .none, .none, .none, .nsThin], // ordinary diff --git a/Tests/SwiftMathTests/ConcurrencyThreadsafeTests.swift b/Tests/SwiftMathTests/ConcurrencyThreadsafeTests.swift index 3de0b64..e79f7c5 100644 --- a/Tests/SwiftMathTests/ConcurrencyThreadsafeTests.swift +++ b/Tests/SwiftMathTests/ConcurrencyThreadsafeTests.swift @@ -1,8 +1,47 @@ // -// File.swift +// ConcurrencyThreadsafeTests.swift // // // Created by Peter Tang on 26/9/2023. // -import Foundation +import XCTest +@testable import SwiftMath + +final class ConcurrencyThreadsafeTests: XCTestCase { + + private let executionQueue = DispatchQueue(label: "com.swiftmath.concurrencytests", attributes: .concurrent) + private let executionGroup = DispatchGroup() + + let totalCases = 20 + var testCount = 0 + + func testSwiftMathConcurrentScript() throws { + for caseNumber in 0 ..< totalCases { + helperConcurrency(caseNumber, in: executionGroup, on: executionQueue) { + let result1 = getInterElementSpaces() + let result2 = MTMathAtomFactory.delimValueToName + let result3 = MTMathAtomFactory.accentValueToName + let result4 = MTMathAtomFactory.textToLatexSymbolName + XCTAssertNotNil(result1) + XCTAssertNotNil(result2) + XCTAssertNotNil(result3) + XCTAssertNotNil(result4) + } + } + executionGroup.notify(queue: .main) { [weak self] in + // print("All test cases completed: \(self?.testCount ?? 0)") + } + executionGroup.wait() + } + func helperConcurrency(_ count: Int, in group: DispatchGroup, on queue: DispatchQueue, _ testClosure: @escaping () -> (Void)) { + let workitem = DispatchWorkItem { + testClosure() + } + workitem.notify(queue: .main) { [weak self] in + self?.testCount += 1 + } + queue.async(group: group, execute: workitem) + } + +}