Reuse cached mention regex in parseMentions (#812)

This commit is contained in:
jack
2025-10-17 23:02:58 +02:00
committed by GitHub
parent eb35608fa1
commit fb43a8b0f5
2 changed files with 76 additions and 25 deletions
+25 -6
View File
@@ -33,13 +33,32 @@ final class GeoRelayDirectory {
/// Returns up to `count` relay URLs (wss://) closest to the given coordinate. /// Returns up to `count` relay URLs (wss://) closest to the given coordinate.
func closestRelays(toLat lat: Double, lon: Double, count: Int = 5) -> [String] { func closestRelays(toLat lat: Double, lon: Double, count: Int = 5) -> [String] {
guard !entries.isEmpty else { return [] } guard !entries.isEmpty, count > 0 else { return [] }
let sorted = entries
.sorted { a, b in if entries.count <= count {
haversineKm(lat, lon, a.lat, a.lon) < haversineKm(lat, lon, b.lat, b.lon) return entries
.sorted { a, b in
haversineKm(lat, lon, a.lat, a.lon) < haversineKm(lat, lon, b.lat, b.lon)
}
.map { "wss://\($0.host)" }
}
var best: [(entry: Entry, distance: Double)] = []
best.reserveCapacity(count)
for entry in entries {
let distance = haversineKm(lat, lon, entry.lat, entry.lon)
if best.count < count {
let idx = best.firstIndex { $0.distance > distance } ?? best.count
best.insert((entry, distance), at: idx)
} else if let worstDistance = best.last?.distance, distance < worstDistance {
let idx = best.firstIndex { $0.distance > distance } ?? best.count
best.insert((entry, distance), at: idx)
best.removeLast()
} }
.prefix(count) }
return sorted.map { "wss://\($0.host)" }
return best.map { "wss://\($0.entry.host)" }
} }
// MARK: - Remote Fetch // MARK: - Remote Fetch
+51 -19
View File
@@ -197,20 +197,37 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
// Persistent recent content map (LRU) to speed near-duplicate checks // Persistent recent content map (LRU) to speed near-duplicate checks
private var contentLRUMap: [String: Date] = [:] private var contentLRUMap: [String: Date] = [:]
private var contentLRUOrder: [String] = [] private var contentLRUOrder: [String] = []
private var contentLRUHead = 0
private let contentLRUCap = TransportConfig.contentLRUCap private let contentLRUCap = TransportConfig.contentLRUCap
private func recordContentKey(_ key: String, timestamp: Date) { private func recordContentKey(_ key: String, timestamp: Date) {
if contentLRUMap[key] == nil { contentLRUOrder.append(key) } if contentLRUMap[key] == nil { contentLRUOrder.append(key) }
contentLRUMap[key] = timestamp contentLRUMap[key] = timestamp
if contentLRUOrder.count > contentLRUCap { trimContentLRUIfNeeded()
let overflow = contentLRUOrder.count - contentLRUCap }
for _ in 0..<overflow {
if let victim = contentLRUOrder.first { private func trimContentLRUIfNeeded() {
contentLRUOrder.removeFirst() let activeCount = contentLRUOrder.count - contentLRUHead
contentLRUMap.removeValue(forKey: victim) guard activeCount > contentLRUCap else { return }
}
} let overflow = activeCount - contentLRUCap
for _ in 0..<overflow {
guard let victim = popOldestContentKey() else { break }
contentLRUMap.removeValue(forKey: victim)
} }
} }
private func popOldestContentKey() -> String? {
guard contentLRUHead < contentLRUOrder.count else { return nil }
let victim = contentLRUOrder[contentLRUHead]
contentLRUHead += 1
// Periodically compact the backing storage to avoid unbounded growth.
if contentLRUHead >= 32 && contentLRUHead * 2 >= contentLRUOrder.count {
contentLRUOrder.removeFirst(contentLRUHead)
contentLRUHead = 0
}
return victim
}
// MARK: - Published Properties // MARK: - Published Properties
@Published var messages: [BitchatMessage] = [] @Published var messages: [BitchatMessage] = []
@@ -348,6 +365,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
// PeerManager replaced by UnifiedPeerService // PeerManager replaced by UnifiedPeerService
private var processedNostrEvents = Set<String>() // Simple deduplication private var processedNostrEvents = Set<String>() // Simple deduplication
private var processedNostrEventOrder: [String] = [] private var processedNostrEventOrder: [String] = []
private var processedNostrEventHead = 0
private let maxProcessedNostrEvents = TransportConfig.uiProcessedNostrEventsCap private let maxProcessedNostrEvents = TransportConfig.uiProcessedNostrEventsCap
private let userDefaults = UserDefaults.standard private let userDefaults = UserDefaults.standard
private let keychain: KeychainManagerProtocol private let keychain: KeychainManagerProtocol
@@ -2168,16 +2186,31 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
private func recordProcessedEvent(_ id: String) { private func recordProcessedEvent(_ id: String) {
processedNostrEvents.insert(id) processedNostrEvents.insert(id)
processedNostrEventOrder.append(id) processedNostrEventOrder.append(id)
if processedNostrEventOrder.count > maxProcessedNostrEvents { trimProcessedNostrEventsIfNeeded()
let overflow = processedNostrEventOrder.count - maxProcessedNostrEvents }
for _ in 0..<overflow {
if let old = processedNostrEventOrder.first { private func trimProcessedNostrEventsIfNeeded() {
processedNostrEventOrder.removeFirst() let activeCount = processedNostrEventOrder.count - processedNostrEventHead
processedNostrEvents.remove(old) guard activeCount > maxProcessedNostrEvents else { return }
}
} let overflow = activeCount - maxProcessedNostrEvents
for _ in 0..<overflow {
guard let old = popOldestProcessedEvent() else { break }
processedNostrEvents.remove(old)
} }
} }
private func popOldestProcessedEvent() -> String? {
guard processedNostrEventHead < processedNostrEventOrder.count else { return nil }
let value = processedNostrEventOrder[processedNostrEventHead]
processedNostrEventHead += 1
if processedNostrEventHead >= 32 && processedNostrEventHead * 2 >= processedNostrEventOrder.count {
processedNostrEventOrder.removeFirst(processedNostrEventHead)
processedNostrEventHead = 0
}
return value
}
/// Sends an encrypted private message to a specific peer. /// Sends an encrypted private message to a specific peer.
/// - Parameters: /// - Parameters:
@@ -4826,11 +4859,10 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
private func parseMentions(from content: String) -> [String] { private func parseMentions(from content: String) -> [String] {
// Allow optional disambiguation suffix '#abcd' for duplicate nicknames // Allow optional disambiguation suffix '#abcd' for duplicate nicknames
let pattern = "@([\\p{L}0-9_]+(?:#[a-fA-F0-9]{4})?)" let regex = Regexes.mention
let regex = try? NSRegularExpression(pattern: pattern, options: [])
let nsContent = content as NSString let nsContent = content as NSString
let nsLen = nsContent.length let nsLen = nsContent.length
let matches = regex?.matches(in: content, options: [], range: NSRange(location: 0, length: nsLen)) ?? [] let matches = regex.matches(in: content, options: [], range: NSRange(location: 0, length: nsLen))
var mentions: [String] = [] var mentions: [String] = []
let peerNicknames = meshService.getPeerNicknames() let peerNicknames = meshService.getPeerNicknames()