Offload note filtering computations from the view body render function

This attempts to improve the performance of InnerTimelineView by
performing event filtering computations on "EventHolder.insert" instead
of on each view body re-render, to improve SwiftUI performance.

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
Daniel D’Aquino
2025-10-08 15:10:47 -07:00
parent c80d4f146c
commit 70d0d9dacf
2 changed files with 60 additions and 6 deletions

View File

@@ -9,14 +9,18 @@ import SwiftUI
struct InnerTimelineView: View {
@ObservedObject var events: EventHolder
var events: EventHolder
@ObservedObject var filteredEvents: EventHolder.FilteredHolder
var filteredEventHolderId: UUID
let state: DamusState
let filter: (NostrEvent) -> Bool
init(events: EventHolder, damus: DamusState, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true) {
self.events = events
self.state = damus
self.filter = apply_mute_rules ? { filter($0) && !damus.mutelist_manager.is_event_muted($0) } : filter
let filter = apply_mute_rules ? { filter($0) && !damus.mutelist_manager.is_event_muted($0) } : filter
let filteredEvents = EventHolder.FilteredHolder(filter: filter)
self.filteredEvents = filteredEvents
self.filteredEventHolderId = events.add(filteredHolder: filteredEvents)
}
var event_options: EventViewOptions {
@@ -29,12 +33,11 @@ struct InnerTimelineView: View {
var body: some View {
LazyVStack(spacing: 0) {
let events = self.events.events
let events = self.filteredEvents.events
if events.isEmpty {
EmptyTimelineView()
} else {
let evs = events.filter(filter)
let indexed = Array(zip(evs, 0...))
let indexed = Array(zip(events, 0...))
ForEach(indexed, id: \.0.id) { tup in
let ev = tup.0
let ind = tup.1
@@ -62,6 +65,9 @@ struct InnerTimelineView: View {
}
}
}
.onDisappear {
self.events.removeFilteredHolder(id: self.filteredEventHolderId)
}
//.padding(.horizontal)
}

View File

@@ -11,6 +11,7 @@ import Foundation
class EventHolder: ObservableObject, ScrollQueue {
private var has_event = Set<NoteId>()
@Published var events: [NostrEvent]
var filteredHolders: [UUID: FilteredHolder] = [:]
var incoming: [NostrEvent]
private(set) var should_queue = false
var on_queue: ((NostrEvent) -> Void)?
@@ -58,6 +59,9 @@ class EventHolder: ObservableObject, ScrollQueue {
if insert_uniq_sorted_event_created(events: &self.events, new_ev: ev) {
return true
}
for (id, filteredView) in self.filteredHolders {
filteredView.insert(event: ev)
}
return false
}
@@ -85,6 +89,9 @@ class EventHolder: ObservableObject, ScrollQueue {
if insert_uniq_sorted_event_created(events: &events, new_ev: event) {
changed = true
}
for (id, filteredHolder) in self.filteredHolders {
filteredHolder.insert(event: event)
}
}
if changed {
@@ -100,5 +107,46 @@ class EventHolder: ObservableObject, ScrollQueue {
func reset() {
self.incoming = []
self.events = []
for (id, filteredHolder) in filteredHolders {
filteredHolder.update(events: [])
}
}
@MainActor
func add(filteredHolder: FilteredHolder) -> UUID {
let id = UUID()
self.filteredHolders[id] = filteredHolder
filteredHolder.update(events: self.events)
return id
}
@MainActor
func removeFilteredHolder(id: UUID) {
self.filteredHolders[id] = nil
}
class FilteredHolder: ObservableObject {
@Published private(set) var events: [NostrEvent]
let filter: (NostrEvent) -> Bool
init(filter: @escaping (NostrEvent) -> Bool) {
self.events = []
self.filter = filter
}
func update(events: [NostrEvent]) {
self.events = events.filter(self.filter)
}
func insert(event: NostrEvent) {
guard self.filter(event) else { return }
var changed = false
if insert_uniq_sorted_event_created(events: &events, new_ev: event) {
changed = true
}
if changed {
self.objectWillChange.send()
}
}
}
}