feat: replace array with rtree

This commit is contained in:
dscyrescotti
2024-06-05 22:58:15 +07:00
parent e4dc567819
commit 90dfe97f9e
8 changed files with 54 additions and 34 deletions

View File

@@ -11,7 +11,7 @@ import CoreData
import Foundation
final class GraphicContext: @unchecked Sendable {
var strokes: [any Stroke] = []
var tree: RTree = RTree<PenStroke>(maxEntries: 8)
var object: GraphicContextObject?
var currentStroke: (any Stroke)?
@@ -37,20 +37,26 @@ final class GraphicContext: @unchecked Sendable {
]
}
func undoGraphic() {
guard !strokes.isEmpty else { return }
let stroke = strokes.removeLast()
withPersistence(\.backgroundContext) { [stroke] context in
stroke.stroke(as: PenStroke.self)?.object?.graphicContext = nil
try context.saveIfNeeded()
func undoGraphic(for event: HistoryEvent) {
switch event {
case .stroke(let stroke):
guard let _stroke = stroke.stroke(as: PenStroke.self) else { return }
let deletedStroke = tree.remove(_stroke, in: _stroke.strokeBox)
withPersistence(\.backgroundContext) { [stroke = deletedStroke] context in
stroke?.stroke(as: PenStroke.self)?.object?.graphicContext = nil
try context.saveIfNeeded()
}
previousStroke = nil
}
previousStroke = nil
}
func redoGraphic(for event: HistoryEvent) {
switch event {
case .stroke(let stroke):
strokes.append(stroke)
if let stroke = stroke.stroke(as: PenStroke.self) {
tree.insert(stroke, in: stroke.strokeBox)
}
withPersistence(\.backgroundContext) { [weak self, stroke] context in
stroke.stroke(as: PenStroke.self)?.object?.graphicContext = self?.object
try context.saveIfNeeded()
@@ -65,9 +71,10 @@ extension GraphicContext {
guard let object else { return }
let queue = OperationQueue()
queue.qualityOfService = .userInteractive
self.strokes = object.strokes.compactMap { stroke -> PenStroke? in
guard let stroke = stroke as? StrokeObject else { return nil }
object.strokes.forEach { stroke in
guard let stroke = stroke as? StrokeObject else { return }
let _stroke = PenStroke(object: stroke)
tree.insert(_stroke, in: _stroke.strokeBox)
if _stroke.isVisible(in: bounds) {
let id = stroke.objectID
queue.addOperation {
@@ -85,14 +92,13 @@ extension GraphicContext {
context.refresh(stroke, mergeChanges: false)
}
}
return _stroke
}
queue.waitUntilAllOperationsAreFinished()
}
func loadQuads(_ bounds: CGRect) {
for stroke in self.strokes {
guard stroke.isVisible(in: bounds), stroke.isEmpty else { continue }
for stroke in self.tree.search(box: bounds.box) {
guard stroke.isEmpty else { continue }
stroke.stroke(as: PenStroke.self)?.loadQuads()
}
}
@@ -135,7 +141,6 @@ extension GraphicContext {
graphicContext?.strokes.add(stroke)
_stroke.object = stroke
}
strokes.append(stroke)
currentStroke = stroke
currentPoint = point
currentStroke?.begin(at: point)
@@ -152,8 +157,9 @@ extension GraphicContext {
}
func endStroke(at point: CGPoint) {
guard currentPoint != nil, let currentStroke else { return }
guard currentPoint != nil, let currentStroke = currentStroke?.stroke(as: PenStroke.self) else { return }
currentStroke.finish(at: point)
tree.insert(currentStroke, in: currentStroke.strokeBox)
withPersistence(\.backgroundContext) { [currentStroke] context in
guard let stroke = currentStroke.stroke(as: PenStroke.self) else { return }
stroke.object?.bounds = stroke.bounds
@@ -168,10 +174,10 @@ extension GraphicContext {
}
func cancelStroke() {
if !strokes.isEmpty {
let stroke = strokes.removeLast()
withPersistence(\.backgroundContext) { [graphicContext = object, _stroke = stroke] context in
if let stroke = _stroke.stroke(as: PenStroke.self)?.object {
if !tree.isEmpty, let stroke = currentStroke?.stroke(as: PenStroke.self) {
let _stroke = tree.remove(stroke, in: stroke.strokeBox)
withPersistence(\.backgroundContext) { [graphicContext = object, _stroke] context in
if let stroke = _stroke?.stroke(as: PenStroke.self)?.object {
graphicContext?.strokes.remove(stroke)
context.delete(stroke)
}

View File

@@ -123,10 +123,6 @@ extension Canvas {
func setGraphicRenderType(_ renderType: GraphicContext.RenderType) {
graphicContext.renderType = renderType
}
func getNewlyAddedStroke() -> (any Stroke)? {
graphicContext.strokes.last
}
}
// MARK: - Rendering

View File

@@ -8,7 +8,7 @@
import MetalKit
import Foundation
protocol Stroke: AnyObject, Drawable, Hashable, Equatable {
protocol Stroke: AnyObject, Drawable, Hashable, Equatable, Comparable {
var id: UUID { get set }
var bounds: [CGFloat] { get set }
var color: [CGFloat] { get set }
@@ -43,6 +43,10 @@ extension Stroke {
return CGRect(x: x, y: y, width: width, height: height)
}
var strokeBox: Box {
Box(minX: bounds[0], minY: bounds[1], maxX: bounds[2], maxY: bounds[3])
}
func isVisible(in bounds: CGRect) -> Bool {
bounds.contains(strokeBounds) || bounds.intersects(strokeBounds)
}
@@ -107,6 +111,10 @@ extension Stroke {
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
static func < (lhs: Self, rhs: Self) -> Bool {
lhs.createdAt < rhs.createdAt
}
}
extension Stroke {

View File

@@ -23,12 +23,12 @@ class History: ObservableObject {
redoStack.isEmpty
}
func undo() -> Bool {
func undo() -> HistoryEvent? {
guard let event = undoStack.popLast() else {
return false
return nil
}
addRedo(event)
return true
return event
}
func redo() -> HistoryEvent? {

View File

@@ -18,6 +18,10 @@ class RTree<T> where T: Equatable & Comparable {
self.root = Node<T>.createNode()
}
var isEmpty: Bool {
root.children.isEmpty
}
// MARK: - Retrival
func traverse() -> [T] {
_traverse(from: root)
@@ -40,7 +44,7 @@ class RTree<T> where T: Equatable & Comparable {
return result
}
func _merge(_ left: [T], _ right: [T]) -> [T] {
private func _merge(_ left: [T], _ right: [T]) -> [T] {
var mergedArray: [T] = []
var leftIndex = 0
var rightIndex = 0

View File

@@ -45,7 +45,9 @@ class GraphicRenderPass: RenderPass {
let graphicContext = canvas.graphicContext
if renderer.redrawsGraphicRender {
canvas.setGraphicRenderType(.finished)
for stroke in graphicContext.strokes {
let strokes = graphicContext.tree.search(box: canvas.bounds.box)
print(strokes.count)
for stroke in strokes {
if graphicContext.previousStroke === stroke || graphicContext.currentStroke === stroke {
continue
}

View File

@@ -141,8 +141,8 @@ extension CanvasViewController {
func updateDocumentBounds() {
var bounds = scrollView.bounds.muliply(by: drawingView.ratio / scrollView.zoomScale)
let xDelta = bounds.minX * 0.05
let yDelta = bounds.minY * 0.05
let xDelta = bounds.minX * 0.0
let yDelta = bounds.minY * 0.0
bounds.origin.x -= xDelta
bounds.origin.y -= yDelta
bounds.size.width += xDelta * 2
@@ -323,9 +323,9 @@ extension CanvasViewController {
extension CanvasViewController {
func historyUndid() {
guard history.undo() else { return }
guard let event = history.undo() else { return }
drawingView.disableUserInteraction()
canvas.graphicContext.undoGraphic()
canvas.graphicContext.undoGraphic(for: event)
renderer.redrawsGraphicRender = true
renderer.resize(on: renderView, to: renderView.drawableSize)
renderView.draw()

View File

@@ -20,4 +20,8 @@ extension CGRect {
func muliply(by factor: CGFloat) -> CGRect {
CGRect(origin: origin.muliply(by: factor), size: size.multiply(by: factor))
}
var box: Box {
Box(minX: minX, minY: minY, maxX: maxX, maxY: maxY)
}
}