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 { struct InnerTimelineView: View {
@ObservedObject var events: EventHolder var events: EventHolder
@ObservedObject var filteredEvents: EventHolder.FilteredHolder
var filteredEventHolderId: UUID
let state: DamusState let state: DamusState
let filter: (NostrEvent) -> Bool
init(events: EventHolder, damus: DamusState, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true) { init(events: EventHolder, damus: DamusState, filter: @escaping (NostrEvent) -> Bool, apply_mute_rules: Bool = true) {
self.events = events self.events = events
self.state = damus 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 { var event_options: EventViewOptions {
@@ -29,12 +33,11 @@ struct InnerTimelineView: View {
var body: some View { var body: some View {
LazyVStack(spacing: 0) { LazyVStack(spacing: 0) {
let events = self.events.events let events = self.filteredEvents.events
if events.isEmpty { if events.isEmpty {
EmptyTimelineView() EmptyTimelineView()
} else { } else {
let evs = events.filter(filter) let indexed = Array(zip(events, 0...))
let indexed = Array(zip(evs, 0...))
ForEach(indexed, id: \.0.id) { tup in ForEach(indexed, id: \.0.id) { tup in
let ev = tup.0 let ev = tup.0
let ind = tup.1 let ind = tup.1
@@ -62,6 +65,9 @@ struct InnerTimelineView: View {
} }
} }
} }
.onDisappear {
self.events.removeFilteredHolder(id: self.filteredEventHolderId)
}
//.padding(.horizontal) //.padding(.horizontal)
} }

View File

@@ -11,6 +11,7 @@ import Foundation
class EventHolder: ObservableObject, ScrollQueue { class EventHolder: ObservableObject, ScrollQueue {
private var has_event = Set<NoteId>() private var has_event = Set<NoteId>()
@Published var events: [NostrEvent] @Published var events: [NostrEvent]
var filteredHolders: [UUID: FilteredHolder] = [:]
var incoming: [NostrEvent] var incoming: [NostrEvent]
private(set) var should_queue = false private(set) var should_queue = false
var on_queue: ((NostrEvent) -> Void)? 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) { if insert_uniq_sorted_event_created(events: &self.events, new_ev: ev) {
return true return true
} }
for (id, filteredView) in self.filteredHolders {
filteredView.insert(event: ev)
}
return false return false
} }
@@ -85,6 +89,9 @@ class EventHolder: ObservableObject, ScrollQueue {
if insert_uniq_sorted_event_created(events: &events, new_ev: event) { if insert_uniq_sorted_event_created(events: &events, new_ev: event) {
changed = true changed = true
} }
for (id, filteredHolder) in self.filteredHolders {
filteredHolder.insert(event: event)
}
} }
if changed { if changed {
@@ -100,5 +107,46 @@ class EventHolder: ObservableObject, ScrollQueue {
func reset() { func reset() {
self.incoming = [] self.incoming = []
self.events = [] 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()
}
}
} }
} }