-
Notifications
You must be signed in to change notification settings - Fork 85
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
TreeSitter Performance And Stability (#263)
### Description Stabilizes the highlighting and editing experience for large documents and slow tree-sitter languages. All async operations run using an updated execution manager that is safer, faster, and much easier to use. #### Safety Improvements: - Explicitly performs getting text for tree-sitter on the main thread. - Parses on the main thread, timing out every few ms to avoid clogging up the main thread. - To avoid potential corrupted tree-sitter state, the state object is now copied for each edit and applied to the client at the end of the edit. If the edit operation is canceled, the half-parsed state is thrown away. - `HighlightProviding` now has `@MainActor` marked callbacks and protocol required functions. In async contexts these will throw a compiler error if not called on the main thread. #### Performance Improvements: - If running asynchronously, tree-sitter edits cancel all previous edits. If an edit is canceled, the edit is added to an atomic queue. The next edit that isn't cancelled will pick up and apply all the queued edits. - This causes a massive performance improvement as tree-sitter's parser gets very stuck if the text doesn't match the tree-sitter tree. By keeping the text and edits in sync we reduce edit parse time drastically. - Instead of using a serial dispatch queue, the executor now uses Swift's shared thread pool via Tasks. On top of that, because we're controlling when tasks execute in a queue, operations that access the tree-sitter tree can now run in parallel. #### Highlighter Changes: - The `HighlightProviding` callbacks now return a `Result` object. If the result is a failure and returns a cancelled error, the highlighter now re-invalidates the queried ranges. This means when highlights are cancelled because of some other async operation, they are always eventually fulfilled. - The highlighter now logs errors from it's providers. #### TreeSitter Execution: - Operations make use of Swift `Task`s to execute, allowing us to use task cancellation, priority, etc. - Operations are now cancellable by priority, so reset operations can cancel all edits, highlights and resets, edits can cancel all edits and highlights, etc. - Cancelling an operation now has many more checks to ensure cancelled tasks don't perform extra work (while parsing, before starting an operation, while waiting in the queue). ### Related Issues * N/A ### Checklist - [x] I read and understood the [contributing guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md) as well as the [code of conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md) - [x] The issues this PR addresses are related to each other - [x] My changes generate no new warnings - [x] My code builds and runs on my machine - [x] My changes are all related to the related issue above - [x] I documented my code ### Screenshots > Demo: Writing a simple C program with main, a string, and a few keywords in a large C file. > These use sqlite3.c for demos. It's just a large C file that I often use for performance demos. Current editing experience. Note incorrect highlights, extremely slow highlighting and maxed thread use. https://github.com/user-attachments/assets/348ba55f-4a27-4c53-8030-d1450c7c9327 New editing experience for large files, with metrics: https://github.com/user-attachments/assets/230e765a-345e-44ec-9054-b6da765032d9
- Loading branch information
1 parent
fbabc59
commit 7d08e74
Showing
21 changed files
with
973 additions
and
386 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
Sources/CodeEditSourceEditor/Extensions/DispatchQueue+dispatchMainIfNot.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// | ||
// DispatchQueue+dispatchMainIfNot.swift | ||
// CodeEditSourceEditor | ||
// | ||
// Created by Khan Winter on 9/2/24. | ||
// | ||
|
||
import Foundation | ||
|
||
/// Helper methods for dispatching (sync or async) on the main queue only if the calling thread is not already the | ||
/// main queue. | ||
|
||
extension DispatchQueue { | ||
/// Executes the work item on the main thread, dispatching asynchronously if the thread is not the main thread. | ||
/// - Parameter item: The work item to execute on the main thread. | ||
static func dispatchMainIfNot(_ item: @escaping () -> Void) { | ||
if Thread.isMainThread { | ||
item() | ||
} else { | ||
DispatchQueue.main.async { | ||
item() | ||
} | ||
} | ||
} | ||
|
||
/// Executes the work item on the main thread, keeping control on the calling thread until the work item is | ||
/// executed if not already on the main thread. | ||
/// - Parameter item: The work item to execute. | ||
/// - Returns: The value of the work item. | ||
static func syncMainIfNot<T>(_ item: @escaping () -> T) -> T { | ||
if Thread.isMainThread { | ||
return item() | ||
} else { | ||
return DispatchQueue.main.sync { | ||
return item() | ||
} | ||
} | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
Sources/CodeEditSourceEditor/Extensions/Result+ThrowOrReturn.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// | ||
// Result+ThrowOrReturn.swift | ||
// CodeEditSourceEditor | ||
// | ||
// Created by Khan Winter on 9/2/24. | ||
// | ||
|
||
import Foundation | ||
|
||
extension Result { | ||
func throwOrReturn() throws -> Success { | ||
switch self { | ||
case let .success(success): | ||
return success | ||
case let .failure(failure): | ||
throw failure | ||
} | ||
} | ||
|
||
var isSuccess: Bool { | ||
if case .success = self { | ||
return true | ||
} else { | ||
return false | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.