From 84b10ee7d24f6797b956a2a080b1f660dfde53df Mon Sep 17 00:00:00 2001 From: Colin Campbell Date: Tue, 5 Mar 2024 20:55:13 -0600 Subject: [PATCH 1/2] Change the renderer to an observed object. --- Sources/LaTeXSwiftUI/LaTeX.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/LaTeXSwiftUI/LaTeX.swift b/Sources/LaTeXSwiftUI/LaTeX.swift index 8ffa607..b641e5a 100644 --- a/Sources/LaTeXSwiftUI/LaTeX.swift +++ b/Sources/LaTeXSwiftUI/LaTeX.swift @@ -159,7 +159,7 @@ public struct LaTeX: View { // MARK: Private properties /// The view's renderer. - @StateObject private var renderer: Renderer + @ObservedObject private var renderer: Renderer /// The view's preload task, if any. @State private var preloadTask: Task<(), Never>? @@ -171,7 +171,7 @@ public struct LaTeX: View { /// - Parameter latex: The LaTeX input. public init(_ latex: String) { self.latex = latex - _renderer = StateObject(wrappedValue: Renderer(latex: latex)) + _renderer = ObservedObject(wrappedValue: Renderer(latex: latex)) } // MARK: View body From b5652b0d853be58641828ffd020bcf61fa132b93 Mon Sep 17 00:00:00 2001 From: Colin Campbell Date: Tue, 5 Mar 2024 21:24:37 -0600 Subject: [PATCH 2/2] Proper fix and keep renderer a state object. --- Sources/LaTeXSwiftUI/LaTeX.swift | 6 ++- Sources/LaTeXSwiftUI/Models/Renderer.swift | 52 ++++++++++++------- .../Views/ComponentBlockText.swift | 2 +- .../Views/ComponentBlocksText.swift | 2 +- .../Views/ComponentBlocksViews.swift | 2 +- 5 files changed, 40 insertions(+), 24 deletions(-) diff --git a/Sources/LaTeXSwiftUI/LaTeX.swift b/Sources/LaTeXSwiftUI/LaTeX.swift index b641e5a..7ad9404 100644 --- a/Sources/LaTeXSwiftUI/LaTeX.swift +++ b/Sources/LaTeXSwiftUI/LaTeX.swift @@ -159,7 +159,7 @@ public struct LaTeX: View { // MARK: Private properties /// The view's renderer. - @ObservedObject private var renderer: Renderer + @StateObject private var renderer = Renderer() /// The view's preload task, if any. @State private var preloadTask: Task<(), Never>? @@ -171,7 +171,6 @@ public struct LaTeX: View { /// - Parameter latex: The LaTeX input. public init(_ latex: String) { self.latex = latex - _renderer = ObservedObject(wrappedValue: Renderer(latex: latex)) } // MARK: View body @@ -239,6 +238,7 @@ extension LaTeX { /// cached. private func isCached() -> Bool { renderer.isCached( + latex: latex, unencodeHTML: unencodeHTML, parsingMode: parsingMode, processEscapes: processEscapes, @@ -250,6 +250,7 @@ extension LaTeX { /// Renders the view's components. @Sendable private func renderAsync() async { await renderer.render( + latex: latex, unencodeHTML: unencodeHTML, parsingMode: parsingMode, processEscapes: processEscapes, @@ -263,6 +264,7 @@ extension LaTeX { /// - Returns: The rendered components. private func renderSync() -> [ComponentBlock] { renderer.renderSync( + latex: latex, unencodeHTML: unencodeHTML, parsingMode: parsingMode, processEscapes: processEscapes, diff --git a/Sources/LaTeXSwiftUI/Models/Renderer.swift b/Sources/LaTeXSwiftUI/Models/Renderer.swift index a708c97..6d228de 100644 --- a/Sources/LaTeXSwiftUI/Models/Renderer.swift +++ b/Sources/LaTeXSwiftUI/Models/Renderer.swift @@ -38,10 +38,22 @@ import Cocoa /// values. internal class Renderer: ObservableObject { - // MARK: Public properties + // MARK: Types + + /// A set of values used to create an array of parsed component blocks. + struct ParsingSource: Equatable { + + /// The LaTeX input. + let latex: String + + /// Whether or not the HTML should be unencoded. + let unencodeHTML: Bool + + /// The parsing mode. + let parsingMode: LaTeX.ParsingMode + } - /// The view's input string. - let latex: String + // MARK: Public properties /// Whether or not the view's blocks have been rendered. @MainActor @Published var rendered: Bool = false @@ -57,18 +69,12 @@ internal class Renderer: ObservableObject { /// The LaTeX input's parsed blocks. private var _parsedBlocks: [ComponentBlock]? = nil + /// The set of values used to create the parsed blocks. + private var _parsingSource: ParsingSource? = nil + /// Semaphore for thread-safe access to `_parsedBlocks`. private var _parsedBlocksSemaphore = DispatchSemaphore(value: 1) - // MARK: Initializers - - /// Initializes a render state with an input string. - /// - /// - Parameter latex: The view's input string. - init(latex: String) { - self.latex = latex - } - } // MARK: Public methods @@ -78,14 +84,15 @@ extension Renderer { /// Returns whether the view's components are cached. /// /// - Parameters: + /// - latex: The LaTeX input string. /// - unencodeHTML: The `unencodeHTML` environment variable. /// - parsingMode: The `parsingMode` environment variable. /// - processEscapes: The `processEscapes` environment variable. /// - errorMode: The `errorMode` environment variable. /// - font: The `font environment` variable. /// - displayScale: The `displayScale` environment variable. - /// - texOptions: The `texOptions` environment variable. func isCached( + latex: String, unencodeHTML: Bool, parsingMode: LaTeX.ParsingMode, processEscapes: Bool, @@ -95,7 +102,7 @@ extension Renderer { ) -> Bool { let texOptions = TeXInputProcessorOptions(processEscapes: processEscapes, errorMode: errorMode) return blocksExistInCache( - parsedBlocks(unencodeHTML: unencodeHTML, parsingMode: parsingMode), + parsedBlocks(latex: latex, unencodeHTML: unencodeHTML, parsingMode: parsingMode), font: font, displayScale: displayScale, texOptions: texOptions) @@ -104,14 +111,15 @@ extension Renderer { /// Renders the view's components synchronously. /// /// - Parameters: + /// - latex: The LaTeX input string. /// - unencodeHTML: The `unencodeHTML` environment variable. /// - parsingMode: The `parsingMode` environment variable. /// - processEscapes: The `processEscapes` environment variable. /// - errorMode: The `errorMode` environment variable. /// - font: The `font environment` variable. /// - displayScale: The `displayScale` environment variable. - /// - texOptions: The `texOptions` environment variable. func renderSync( + latex: String, unencodeHTML: Bool, parsingMode: LaTeX.ParsingMode, processEscapes: Bool, @@ -121,7 +129,7 @@ extension Renderer { ) -> [ComponentBlock] { let texOptions = TeXInputProcessorOptions(processEscapes: processEscapes, errorMode: errorMode) return render( - blocks: parsedBlocks(unencodeHTML: unencodeHTML, parsingMode: parsingMode), + blocks: parsedBlocks(latex: latex, unencodeHTML: unencodeHTML, parsingMode: parsingMode), font: font, displayScale: displayScale, texOptions: texOptions) @@ -130,14 +138,15 @@ extension Renderer { /// Renders the view's components asynchronously. /// /// - Parameters: + /// - latex: The LaTeX input string. /// - unencodeHTML: The `unencodeHTML` environment variable. /// - parsingMode: The `parsingMode` environment variable. /// - processEscapes: The `processEscapes` environment variable. /// - errorMode: The `errorMode` environment variable. /// - font: The `font environment` variable. /// - displayScale: The `displayScale` environment variable. - /// - texOptions: The `texOptions` environment variable. func render( + latex: String, unencodeHTML: Bool, parsingMode: LaTeX.ParsingMode, processEscapes: Bool, @@ -156,7 +165,7 @@ extension Renderer { let texOptions = TeXInputProcessorOptions(processEscapes: processEscapes, errorMode: errorMode) let renderedBlocks = await render( - blocks: parsedBlocks(unencodeHTML: unencodeHTML, parsingMode: parsingMode), + blocks: parsedBlocks(latex: latex, unencodeHTML: unencodeHTML, parsingMode: parsingMode), font: font, displayScale: displayScale, texOptions: texOptions) @@ -313,21 +322,26 @@ extension Renderer { /// Gets the LaTeX input's parsed blocks. /// /// - Parameters: + /// - latex: The LaTeX input string. /// - unencodeHTML: The `unencodeHTML` environment variable. /// - parsingMode: The `parsingMode` environment variable. /// - Returns: The parsed blocks. private func parsedBlocks( + latex: String, unencodeHTML: Bool, parsingMode: LaTeX.ParsingMode ) -> [ComponentBlock] { _parsedBlocksSemaphore.wait() defer { _parsedBlocksSemaphore.signal() } - if let _parsedBlocks { + + let currentSource = ParsingSource(latex: latex, unencodeHTML: unencodeHTML, parsingMode: parsingMode) + if let _parsedBlocks, _parsingSource == currentSource { return _parsedBlocks } let blocks = Parser.parse(unencodeHTML ? latex.htmlUnescape() : latex, mode: parsingMode) _parsedBlocks = blocks + _parsingSource = currentSource return blocks } diff --git a/Sources/LaTeXSwiftUI/Views/ComponentBlockText.swift b/Sources/LaTeXSwiftUI/Views/ComponentBlockText.swift index 9c5b2a8..e56e0e9 100644 --- a/Sources/LaTeXSwiftUI/Views/ComponentBlockText.swift +++ b/Sources/LaTeXSwiftUI/Views/ComponentBlockText.swift @@ -72,6 +72,6 @@ struct ComponentBlockTextPreviews: PreviewProvider { static var previews: some View { ComponentBlockText(block: ComponentBlock(components: [ Component(text: "Hello, World!", type: .text) - ]), renderer: Renderer(latex: "Hello, World!")) + ]), renderer: Renderer()) } } diff --git a/Sources/LaTeXSwiftUI/Views/ComponentBlocksText.swift b/Sources/LaTeXSwiftUI/Views/ComponentBlocksText.swift index f20157f..95a091f 100644 --- a/Sources/LaTeXSwiftUI/Views/ComponentBlocksText.swift +++ b/Sources/LaTeXSwiftUI/Views/ComponentBlocksText.swift @@ -57,6 +57,6 @@ struct ComponentBlocksTextPreviews: PreviewProvider { ComponentBlocksText(blocks: [ComponentBlock(components: [ Component(text: "Hello, World!", type: .text) ])], forceInline: false) - .environmentObject(Renderer(latex: "Hello, World!")) + .environmentObject(Renderer()) } } diff --git a/Sources/LaTeXSwiftUI/Views/ComponentBlocksViews.swift b/Sources/LaTeXSwiftUI/Views/ComponentBlocksViews.swift index 375bc2e..c87db37 100644 --- a/Sources/LaTeXSwiftUI/Views/ComponentBlocksViews.swift +++ b/Sources/LaTeXSwiftUI/Views/ComponentBlocksViews.swift @@ -97,6 +97,6 @@ struct ComponentBlocksViewsPreviews: PreviewProvider { ComponentBlocksViews(blocks: [ComponentBlock(components: [ Component(text: "Hello, World!", type: .text) ])]) - .environmentObject(Renderer(latex: "Hello, World!")) + .environmentObject(Renderer()) } }