mirror of
git://jb55.com/damus
synced 2024-09-19 19:46:51 +00:00
Optimize json processing and preloading
- Preload events when added to the EventHolder queue - Remove relative time formatting from preloader. Just do it when event appears - Process incoming json in a background queue by default Changelog-Fixed: Fix wrong relative times on events Changelog-Changed: Preload events when they are queued
This commit is contained in:
parent
3b541f2ec1
commit
39a324fd1e
@ -48,12 +48,18 @@ class HomeModel: ObservableObject {
|
||||
|
||||
@Published var new_events: NewEventsBits = NewEventsBits()
|
||||
@Published var notifications = NotificationsModel()
|
||||
@Published var events = EventHolder()
|
||||
@Published var events: EventHolder = EventHolder()
|
||||
|
||||
init() {
|
||||
self.damus_state = DamusState.empty
|
||||
filter_events()
|
||||
self.setup_debouncer()
|
||||
filter_events()
|
||||
events.on_queue = preloader
|
||||
//self.events = EventHolder(on_queue: preloader)
|
||||
}
|
||||
|
||||
func preloader(ev: NostrEvent) {
|
||||
preload_events(state: self.damus_state, events: [ev])
|
||||
}
|
||||
|
||||
var pool: RelayPool {
|
||||
@ -528,7 +534,7 @@ class HomeModel: ObservableObject {
|
||||
}
|
||||
|
||||
// TODO: will we need to process this in other places like zap request contents, etc?
|
||||
process_image_metadata(cache: damus_state.events, ev: ev)
|
||||
process_image_metadatas(cache: damus_state.events, ev: ev)
|
||||
damus_state.replies.count_replies(ev)
|
||||
damus_state.events.insert(ev)
|
||||
|
||||
@ -950,15 +956,12 @@ func handle_incoming_dms(prev_events: NewEventsBits, dms: DirectMessagesModel, o
|
||||
}
|
||||
|
||||
if inserted {
|
||||
Task.init {
|
||||
let new_dms = Array(dms.dms.filter({ $0.events.count > 0 })).sorted { a, b in
|
||||
return a.events.last!.created_at > b.events.last!.created_at
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
|
||||
dms.dms = new_dms
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new_events
|
||||
}
|
||||
|
@ -8,20 +8,27 @@
|
||||
import Foundation
|
||||
|
||||
class ProfileModel: ObservableObject, Equatable {
|
||||
var events: EventHolder = EventHolder()
|
||||
@Published var contacts: NostrEvent? = nil
|
||||
@Published var following: Int = 0
|
||||
@Published var relays: [String: RelayInfo]? = nil
|
||||
@Published var progress: Int = 0
|
||||
|
||||
var events: EventHolder
|
||||
let pubkey: String
|
||||
let damus: DamusState
|
||||
|
||||
|
||||
var seen_event: Set<String> = Set()
|
||||
var sub_id = UUID().description
|
||||
var prof_subid = UUID().description
|
||||
|
||||
init(pubkey: String, damus: DamusState) {
|
||||
self.pubkey = pubkey
|
||||
self.damus = damus
|
||||
self.events = EventHolder(on_queue: { ev in
|
||||
preload_events(state: damus, events: [ev])
|
||||
})
|
||||
}
|
||||
|
||||
func follows(pubkey: String) -> Bool {
|
||||
guard let contacts = self.contacts else {
|
||||
return false
|
||||
@ -47,11 +54,6 @@ class ProfileModel: ObservableObject, Equatable {
|
||||
return .pubkey(pubkey)
|
||||
}
|
||||
|
||||
init(pubkey: String, damus: DamusState) {
|
||||
self.pubkey = pubkey
|
||||
self.damus = damus
|
||||
}
|
||||
|
||||
static func == (lhs: ProfileModel, rhs: ProfileModel) -> Bool {
|
||||
return lhs.pubkey == rhs.pubkey
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import Foundation
|
||||
|
||||
/// The data model for the SearchHome view, typically something global-like
|
||||
class SearchHomeModel: ObservableObject {
|
||||
var events: EventHolder = EventHolder()
|
||||
var events: EventHolder
|
||||
@Published var loading: Bool = false
|
||||
|
||||
var seen_pubkey: Set<String> = Set()
|
||||
@ -21,6 +21,9 @@ class SearchHomeModel: ObservableObject {
|
||||
|
||||
init(damus_state: DamusState) {
|
||||
self.damus_state = damus_state
|
||||
self.events = EventHolder(on_queue: { ev in
|
||||
preload_events(state: damus_state, events: [ev])
|
||||
})
|
||||
}
|
||||
|
||||
func get_base_filter() -> NostrFilter {
|
||||
|
@ -10,7 +10,7 @@ import Foundation
|
||||
|
||||
class SearchModel: ObservableObject {
|
||||
let state: DamusState
|
||||
var events: EventHolder = EventHolder()
|
||||
var events: EventHolder
|
||||
@Published var loading: Bool = false
|
||||
@Published var channel_name: String? = nil
|
||||
|
||||
@ -22,6 +22,9 @@ class SearchModel: ObservableObject {
|
||||
init(state: DamusState, search: NostrFilter) {
|
||||
self.state = state
|
||||
self.search = search
|
||||
self.events = EventHolder(on_queue: { ev in
|
||||
preload_events(state: state, events: [ev])
|
||||
})
|
||||
}
|
||||
|
||||
func filter_muted() {
|
||||
|
@ -63,7 +63,7 @@ final class RelayConnection {
|
||||
last_connection_attempt = Date().timeIntervalSince1970
|
||||
|
||||
subscriptionToken = socket.subject
|
||||
.receive(on: DispatchQueue.main)
|
||||
.receive(on: DispatchQueue.global(qos: .default))
|
||||
.sink { [weak self] completion in
|
||||
switch completion {
|
||||
case .failure(let error):
|
||||
@ -97,27 +97,35 @@ final class RelayConnection {
|
||||
private func receive(event: WebSocketEvent) {
|
||||
switch event {
|
||||
case .connected:
|
||||
backoff = 1.0
|
||||
DispatchQueue.main.async {
|
||||
self.backoff = 1.0
|
||||
self.isConnected = true
|
||||
self.isConnecting = false
|
||||
}
|
||||
case .message(let message):
|
||||
self.receive(message: message)
|
||||
case .disconnected(let closeCode, let reason):
|
||||
if closeCode != .normalClosure {
|
||||
print("⚠️ Warning: RelayConnection (\(self.url)) closed with code \(closeCode), reason: \(String(describing: reason))")
|
||||
}
|
||||
isConnected = false
|
||||
isConnecting = false
|
||||
reconnect()
|
||||
DispatchQueue.main.async {
|
||||
self.isConnected = false
|
||||
self.isConnecting = false
|
||||
self.reconnect()
|
||||
}
|
||||
case .error(let error):
|
||||
print("⚠️ Warning: RelayConnection (\(self.url)) error: \(error)")
|
||||
isConnected = false
|
||||
isConnecting = false
|
||||
backoff *= 1.5
|
||||
reconnect_in(after: backoff)
|
||||
DispatchQueue.main.async {
|
||||
self.isConnected = false
|
||||
self.isConnecting = false
|
||||
self.backoff *= 1.5
|
||||
self.reconnect_in(after: self.backoff)
|
||||
}
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.handleEvent(.ws_event(event))
|
||||
}
|
||||
}
|
||||
|
||||
func reconnect() {
|
||||
guard !isConnecting else {
|
||||
@ -136,14 +144,12 @@ final class RelayConnection {
|
||||
private func receive(message: URLSessionWebSocketTask.Message) {
|
||||
switch message {
|
||||
case .string(let messageString):
|
||||
DispatchQueue.global(qos: .default).async {
|
||||
if let ev = decode_nostr_event(txt: messageString) {
|
||||
DispatchQueue.main.async {
|
||||
self.handleEvent(.nostr_event(ev))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
case .data(let messageData):
|
||||
if let messageString = String(data: messageData, encoding: .utf8) {
|
||||
receive(message: .string(messageString))
|
||||
|
@ -300,8 +300,9 @@ func should_preload_translation(event: NostrEvent, our_keypair: Keypair, current
|
||||
|
||||
struct PreloadPlan {
|
||||
let data: EventData
|
||||
let img_metadata: [ImageMetadata]
|
||||
let event: NostrEvent
|
||||
let load_artifacts: Bool
|
||||
var load_artifacts: Bool
|
||||
let load_translations: Bool
|
||||
let load_preview: Bool
|
||||
}
|
||||
@ -314,7 +315,8 @@ func load_preview(artifacts: NoteArtifacts) async -> Preview? {
|
||||
return Preview(meta: meta)
|
||||
}
|
||||
|
||||
func get_preload_plan(cache: EventData, ev: NostrEvent, our_keypair: Keypair, settings: UserSettingsStore) -> PreloadPlan? {
|
||||
func get_preload_plan(evcache: EventCache, ev: NostrEvent, our_keypair: Keypair, settings: UserSettingsStore) -> PreloadPlan? {
|
||||
let cache = evcache.get_cache_data(ev.id)
|
||||
let load_artifacts = cache.artifacts.should_preload
|
||||
if load_artifacts {
|
||||
cache.artifacts_model.state = .loading
|
||||
@ -325,16 +327,28 @@ func get_preload_plan(cache: EventData, ev: NostrEvent, our_keypair: Keypair, se
|
||||
cache.translations_model.state = .translating
|
||||
}
|
||||
|
||||
let load_urls = event_image_metadata(ev: ev)
|
||||
.reduce(into: [ImageMetadata]()) { to_load, meta in
|
||||
let cached = evcache.lookup_img_metadata(url: meta.url)
|
||||
guard cached == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
let m = ImageMetadataState(state: .processing, meta: meta)
|
||||
evcache.store_img_metadata(url: meta.url, meta: m)
|
||||
to_load.append(meta)
|
||||
}
|
||||
|
||||
let load_preview = cache.preview.should_preload
|
||||
if load_preview {
|
||||
cache.preview_model.state = .loading
|
||||
}
|
||||
|
||||
if !load_artifacts && !load_translations && !load_preview {
|
||||
if !load_artifacts && !load_translations && !load_preview && load_urls.count == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return PreloadPlan(data: cache, event: ev, load_artifacts: load_artifacts, load_translations: load_translations, load_preview: load_preview)
|
||||
return PreloadPlan(data: cache, img_metadata: load_urls, event: ev, load_artifacts: load_artifacts, load_translations: load_translations, load_preview: load_preview)
|
||||
}
|
||||
|
||||
func preload_image(url: URL) {
|
||||
@ -351,17 +365,31 @@ func preload_image(url: URL) {
|
||||
}
|
||||
}
|
||||
|
||||
func preload_event(plan: PreloadPlan, profiles: Profiles, our_keypair: Keypair, settings: UserSettingsStore) async {
|
||||
var artifacts: NoteArtifacts? = plan.data.artifacts.artifacts
|
||||
|
||||
print("Preloading event \(plan.event.content)")
|
||||
|
||||
func preload_pfp(profiles: Profiles, pubkey: String) {
|
||||
// preload pfp
|
||||
if let profile = profiles.lookup(id: plan.event.pubkey),
|
||||
if let profile = profiles.lookup(id: pubkey),
|
||||
let picture = profile.picture,
|
||||
let url = URL(string: picture) {
|
||||
preload_image(url: url)
|
||||
}
|
||||
}
|
||||
|
||||
func preload_event(plan: PreloadPlan, state: DamusState) async {
|
||||
var artifacts: NoteArtifacts? = plan.data.artifacts.artifacts
|
||||
let settings = state.settings
|
||||
let profiles = state.profiles
|
||||
let our_keypair = state.keypair
|
||||
|
||||
print("Preloading event \(plan.event.content)")
|
||||
|
||||
for meta in plan.img_metadata {
|
||||
process_image_metadata(cache: state.events, meta: meta, ev: plan.event)
|
||||
}
|
||||
|
||||
preload_pfp(profiles: profiles, pubkey: plan.event.pubkey)
|
||||
if let inner_ev = plan.event.get_inner_event(cache: state.events), inner_ev.pubkey != plan.event.pubkey {
|
||||
preload_pfp(profiles: profiles, pubkey: inner_ev.pubkey)
|
||||
}
|
||||
|
||||
if artifacts == nil && plan.load_artifacts {
|
||||
let arts = render_note_content(ev: plan.event, profiles: profiles, privkey: our_keypair.privkey)
|
||||
@ -398,28 +426,37 @@ func preload_event(plan: PreloadPlan, profiles: Profiles, our_keypair: Keypair,
|
||||
translations = await translate_note(profiles: profiles, privkey: our_keypair.privkey, event: plan.event, settings: settings, note_lang: note_language)
|
||||
}
|
||||
|
||||
let timeago = format_relative_time(plan.event.created_at)
|
||||
let ts = translations
|
||||
if plan.data.translations_model.note_language == nil || ts != nil {
|
||||
DispatchQueue.main.async {
|
||||
if let ts {
|
||||
plan.data.translations_model.state = ts
|
||||
}
|
||||
plan.data.relative_time.value = timeago
|
||||
if plan.data.translations_model.note_language != note_language {
|
||||
plan.data.translations_model.note_language = note_language
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func preload_events(event_cache: EventCache, events: [NostrEvent], profiles: Profiles, our_keypair: Keypair, settings: UserSettingsStore) {
|
||||
}
|
||||
|
||||
func preload_events(state: DamusState, events: [NostrEvent]) {
|
||||
let event_cache = state.events
|
||||
let our_keypair = state.keypair
|
||||
let settings = state.settings
|
||||
|
||||
let plans = events.compactMap { ev in
|
||||
get_preload_plan(cache: event_cache.get_cache_data(ev.id), ev: ev, our_keypair: our_keypair, settings: settings)
|
||||
get_preload_plan(evcache: event_cache, ev: ev, our_keypair: our_keypair, settings: settings)
|
||||
}
|
||||
|
||||
if plans.count == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
Task.init {
|
||||
for plan in plans {
|
||||
await preload_event(plan: plan, profiles: profiles, our_keypair: our_keypair, settings: settings)
|
||||
await preload_event(plan: plan, state: state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ class EventHolder: ObservableObject, ScrollQueue {
|
||||
@Published var events: [NostrEvent]
|
||||
@Published var incoming: [NostrEvent]
|
||||
var should_queue: Bool
|
||||
var on_queue: ((NostrEvent) -> Void)?
|
||||
|
||||
func set_should_queue(_ val: Bool) {
|
||||
self.should_queue = val
|
||||
@ -35,6 +36,15 @@ class EventHolder: ObservableObject, ScrollQueue {
|
||||
self.events = []
|
||||
self.incoming = []
|
||||
self.has_event = Set()
|
||||
self.on_queue = nil
|
||||
}
|
||||
|
||||
init(on_queue: @escaping (NostrEvent) -> ()) {
|
||||
self.should_queue = false
|
||||
self.events = []
|
||||
self.incoming = []
|
||||
self.has_event = Set()
|
||||
self.on_queue = on_queue
|
||||
}
|
||||
|
||||
init(events: [NostrEvent], incoming: [NostrEvent]) {
|
||||
@ -42,6 +52,7 @@ class EventHolder: ObservableObject, ScrollQueue {
|
||||
self.events = events
|
||||
self.incoming = incoming
|
||||
self.has_event = Set()
|
||||
self.on_queue = nil
|
||||
}
|
||||
|
||||
func filter(_ isIncluded: (NostrEvent) -> Bool) {
|
||||
@ -76,6 +87,8 @@ class EventHolder: ObservableObject, ScrollQueue {
|
||||
return false
|
||||
}
|
||||
|
||||
on_queue?(ev)
|
||||
|
||||
has_event.insert(ev.id)
|
||||
|
||||
incoming.append(ev)
|
||||
|
@ -174,34 +174,55 @@ func calculate_image_metadata(url: URL, img: UIImage, blurhash: String) -> Image
|
||||
}
|
||||
|
||||
|
||||
func process_image_metadata(cache: EventCache, ev: NostrEvent) {
|
||||
for tag in ev.tags {
|
||||
guard tag.count >= 2 && tag[0] == "imeta" else {
|
||||
continue
|
||||
func event_image_metadata(ev: NostrEvent) -> [ImageMetadata] {
|
||||
return ev.tags.reduce(into: [ImageMetadata]()) { meta, tag in
|
||||
guard tag.count >= 2 && tag[0] == "imeta",
|
||||
let data = ImageMetadata(tag: tag) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let meta = ImageMetadata(tag: tag) else {
|
||||
continue
|
||||
meta.append(data)
|
||||
}
|
||||
}
|
||||
|
||||
func process_image_metadatas(cache: EventCache, ev: NostrEvent) {
|
||||
for meta in event_image_metadata(ev: ev) {
|
||||
guard cache.lookup_img_metadata(url: meta.url) == nil else {
|
||||
continue
|
||||
}
|
||||
|
||||
let state = ImageMetadataState(state: .processing, meta: meta)
|
||||
let state = ImageMetadataState(state: meta.blurhash == nil ? .not_needed : .processing, meta: meta)
|
||||
cache.store_img_metadata(url: meta.url, meta: state)
|
||||
|
||||
if let blurhash = meta.blurhash {
|
||||
Task.init {
|
||||
guard let blurhash = meta.blurhash else {
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
guard let img = await process_blurhash(blurhash: blurhash, size: meta.dim?.size) else {
|
||||
return
|
||||
}
|
||||
Task { @MainActor in
|
||||
state.state = .processed(img)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func process_image_metadata(cache: EventCache, meta: ImageMetadata, ev: NostrEvent) {
|
||||
guard let blurhash = meta.blurhash else {
|
||||
return
|
||||
}
|
||||
Task {
|
||||
let img = await process_blurhash(blurhash: blurhash, size: meta.dim?.size)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if let img {
|
||||
state.state = .processed(img)
|
||||
let state = ImageMetadataState(state: .processed(img), meta: meta)
|
||||
cache.store_img_metadata(url: meta.url, meta: state)
|
||||
} else {
|
||||
state.state = .failed
|
||||
}
|
||||
}
|
||||
let state = ImageMetadataState(state: .failed, meta: meta)
|
||||
cache.store_img_metadata(url: meta.url, meta: state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -159,12 +159,28 @@ struct NoteContentView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func load() async {
|
||||
guard let plan = get_preload_plan(cache: damus_state.events.get_cache_data(event.id), ev: event, our_keypair: damus_state.keypair, settings: damus_state.settings) else {
|
||||
return
|
||||
func load(force_artifacts: Bool = false) {
|
||||
// always reload artifacts on load
|
||||
let plan = get_preload_plan(evcache: damus_state.events, ev: event, our_keypair: damus_state.keypair, settings: damus_state.settings)
|
||||
|
||||
// TODO: make this cleaner
|
||||
Task {
|
||||
// this is surprisingly slow
|
||||
let rel = format_relative_time(event.created_at)
|
||||
Task { @MainActor in
|
||||
self.damus_state.events.get_cache_data(event.id).relative_time.value = rel
|
||||
}
|
||||
|
||||
await preload_event(plan: plan, profiles: damus_state.profiles, our_keypair: damus_state.keypair, settings: damus_state.settings)
|
||||
if var plan {
|
||||
if force_artifacts {
|
||||
plan.load_artifacts = true
|
||||
}
|
||||
await preload_event(plan: plan, state: damus_state)
|
||||
} else if force_artifacts {
|
||||
let arts = render_note_content(ev: event, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey)
|
||||
self.artifacts_model.state = .loaded(arts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@ -176,10 +192,7 @@ struct NoteContentView: View {
|
||||
switch block {
|
||||
case .mention(let m):
|
||||
if m.type == .pubkey && m.ref.ref_id == profile.pubkey {
|
||||
self.artifacts_model.state = .loading
|
||||
Task.init {
|
||||
await load()
|
||||
}
|
||||
load(force_artifacts: true)
|
||||
return
|
||||
}
|
||||
case .relay: return
|
||||
@ -190,8 +203,8 @@ struct NoteContentView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.task {
|
||||
await load()
|
||||
.onAppear {
|
||||
load()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ struct InnerTimelineView: View {
|
||||
indexed[safe: ind+5]?.0
|
||||
].compactMap({ $0 }))
|
||||
|
||||
preload_events(event_cache: state.events, events: to_preload, profiles: state.profiles, our_keypair: state.keypair, settings: state.settings)
|
||||
preload_events(state: state, events: to_preload)
|
||||
}
|
||||
|
||||
ThiccDivider()
|
||||
|
Loading…
Reference in New Issue
Block a user