Route delivery status through the store; delete the location index

ConversationStore maintains an exact messageID -> Set<ConversationID>
map at every mutation point (append/upsert/remove/migrate/trim/clear),
so delivery updates are ID-only lookups that fan out to mirrored
ephemeral/stable copies. ChatDeliveryCoordinator shrinks 327 -> 119
lines: the positional location index, its growth-detection/rebuild
machinery, and the duplicate no-downgrade check are deleted - the rule
now lives in exactly one place. The middle-insertion regression tests
are rewritten against the store since stale positional locations are
structurally impossible now.

delivery updates: 38k -> 262k/s (~6.9x); ingest pipelines unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jack
2026-06-11 13:26:00 +02:00
co-authored by Claude Fable 5
parent 99d1d1dccd
commit 7fb1f4a219
6 changed files with 546 additions and 404 deletions
@@ -304,7 +304,7 @@ struct ChatViewModelDeliveryStatusTests {
}
@Test @MainActor
func deliveryStatus_indexRefreshesAfterPrivateChatReorder() async {
func deliveryStatus_survivesPrivateChatReorder() async {
let (viewModel, transport) = makeTestableViewModel()
let peerID = PeerID(str: "0102030405060708")
let messageID = "reordered-msg-1"
@@ -335,8 +335,8 @@ struct ChatViewModelDeliveryStatusTests {
#expect(isSent(viewModel.deliveryCoordinator.deliveryStatus(for: messageID)))
// A late arrival with an older timestamp is inserted before the
// target by the store, shifting its position and invalidating the
// delivery coordinator's location index.
// target by the store, shifting its position; the store's ID-keyed
// indexes must keep the target updatable.
viewModel.seedPrivateChat([olderMessage], for: peerID)
#expect(viewModel.privateChats[peerID]?.map(\.id) == ["older-msg", messageID])
@@ -383,16 +383,31 @@ struct ChatViewModelDeliveryStatusTests {
// MARK: - Mock Delivery Context
/// Lightweight stand-in for `ChatDeliveryContext` proving that
/// `ChatDeliveryCoordinator` is testable without constructing a `ChatViewModel`.
/// `ChatDeliveryCoordinator` is testable without constructing a
/// `ChatViewModel`: the delivery surface forwards to a real
/// `ConversationStore` (the coordinator is a thin mapper over store
/// intents), and assertions read store state.
@MainActor
private final class MockChatDeliveryContext: ChatDeliveryContext {
var messages: [BitchatMessage] = []
var privateChats: [PeerID: [BitchatMessage]] = [:]
let store = ConversationStore()
var sentReadReceipts: Set<String> = []
var isStartupPhase = false
private(set) var notifyUIChangedCount = 0
private(set) var markedDeliveredMessageIDs: [String] = []
@discardableResult
func setDeliveryStatus(_ status: DeliveryStatus, forMessageID messageID: String) -> Bool {
store.setDeliveryStatus(status, forMessageID: messageID)
}
func deliveryStatus(forMessageID messageID: String) -> DeliveryStatus? {
store.deliveryStatus(forMessageID: messageID)
}
func privateMessageIDs() -> Set<String> {
store.directMessageIDs()
}
func pruneSentReadReceipts(keeping validMessageIDs: Set<String>) -> Int {
let oldCount = sentReadReceipts.count
sentReadReceipts = sentReadReceipts.intersection(validMessageIDs)
@@ -406,29 +421,19 @@ private final class MockChatDeliveryContext: ChatDeliveryContext {
func markMessageDelivered(_ messageID: String) {
markedDeliveredMessageIDs.append(messageID)
}
@discardableResult
func setPrivateDeliveryStatus(_ status: DeliveryStatus, forMessageID messageID: String, peerID: PeerID) -> Bool {
guard var chat = privateChats[peerID],
let index = chat.firstIndex(where: { $0.id == messageID }) else {
return false
}
if Conversation.shouldSkipStatusUpdate(current: chat[index].deliveryStatus, new: status) {
return false
}
chat[index].deliveryStatus = status
privateChats[peerID] = chat
return true
}
}
@MainActor
private func makePrivateMessage(id: String, status: DeliveryStatus) -> BitchatMessage {
private func makePrivateMessage(
id: String,
status: DeliveryStatus,
timestamp: Date = Date()
) -> BitchatMessage {
BitchatMessage(
id: id,
sender: "me",
content: "Test message",
timestamp: Date(),
timestamp: timestamp,
isRelay: false,
isPrivate: true,
recipientNickname: "Peer",
@@ -437,10 +442,28 @@ private func makePrivateMessage(id: String, status: DeliveryStatus) -> BitchatMe
)
}
@MainActor
private func makePublicMessage(
id: String,
status: DeliveryStatus,
timestamp: Date = Date()
) -> BitchatMessage {
BitchatMessage(
id: id,
sender: "me",
content: "Public message",
timestamp: timestamp,
isRelay: false,
isPrivate: false,
deliveryStatus: status
)
}
// MARK: - Coordinator Tests Against Mock Context
/// Exercises `ChatDeliveryCoordinator` against `MockChatDeliveryContext`
/// the exemplar for the narrow-dependency coordinator pattern.
/// the exemplar for the narrow-dependency coordinator pattern. State is
/// seeded into and asserted against the mock's `ConversationStore`.
struct ChatDeliveryCoordinatorContextTests {
@Test @MainActor
@@ -449,7 +472,7 @@ struct ChatDeliveryCoordinatorContextTests {
let coordinator = ChatDeliveryCoordinator(context: context)
let peerID = PeerID(str: "0102030405060708")
let messageID = "mock-msg-1"
context.privateChats[peerID] = [makePrivateMessage(id: messageID, status: .sent)]
context.store.append(makePrivateMessage(id: messageID, status: .sent), to: .directPeer(peerID))
let didUpdate = coordinator.updateMessageDeliveryStatus(
messageID,
@@ -457,7 +480,7 @@ struct ChatDeliveryCoordinatorContextTests {
)
#expect(didUpdate)
#expect(isDelivered(context.privateChats[peerID]?.first?.deliveryStatus))
#expect(isDelivered(context.store.conversation(for: .directPeer(peerID)).message(withID: messageID)?.deliveryStatus))
#expect(context.notifyUIChangedCount == 1)
#expect(context.markedDeliveredMessageIDs == [messageID])
}
@@ -468,13 +491,16 @@ struct ChatDeliveryCoordinatorContextTests {
let coordinator = ChatDeliveryCoordinator(context: context)
let peerID = PeerID(str: "0102030405060708")
let messageID = "mock-msg-2"
context.privateChats[peerID] = [makePrivateMessage(id: messageID, status: .delivered(to: "Peer", at: Date()))]
context.store.append(
makePrivateMessage(id: messageID, status: .delivered(to: "Peer", at: Date())),
to: .directPeer(peerID)
)
coordinator.didReceiveReadReceipt(
ReadReceipt(originalMessageID: messageID, readerID: peerID, readerNickname: "Peer")
)
#expect(isRead(context.privateChats[peerID]?.first?.deliveryStatus))
#expect(isRead(coordinator.deliveryStatus(for: messageID)))
#expect(context.notifyUIChangedCount == 1)
#expect(context.markedDeliveredMessageIDs == [messageID])
}
@@ -483,22 +509,12 @@ struct ChatDeliveryCoordinatorContextTests {
func sentStatus_doesNotMarkDeliveredAndUnknownMessageDoesNotNotify() async {
let context = MockChatDeliveryContext()
let coordinator = ChatDeliveryCoordinator(context: context)
context.messages = [
BitchatMessage(
id: "public-mock-1",
sender: "me",
content: "Public message",
timestamp: Date(),
isRelay: false,
isPrivate: false,
deliveryStatus: .sending
)
]
context.store.append(makePublicMessage(id: "public-mock-1", status: .sending), to: .mesh)
// .sent is not a confirmed receipt must not reach markMessageDelivered.
let didUpdate = coordinator.updateMessageDeliveryStatus("public-mock-1", status: .sent)
#expect(didUpdate)
#expect(isSent(context.messages.first?.deliveryStatus))
#expect(isSent(context.store.conversation(for: .mesh).message(withID: "public-mock-1")?.deliveryStatus))
#expect(context.markedDeliveredMessageIDs.isEmpty)
#expect(context.notifyUIChangedCount == 1)
@@ -508,57 +524,98 @@ struct ChatDeliveryCoordinatorContextTests {
#expect(context.notifyUIChangedCount == 1)
}
// The old positional `messageLocationIndex` could go stale when a late
// arrival was inserted mid-array (count grew but indexed locations
// shifted). The store's per-conversation ID index is reindexed inside the
// same mutation, so staleness is structurally impossible these tests
// pin the equivalent behavior through the new path: after out-of-order
// insertion, updates keyed by ID still land on the right messages.
@Test @MainActor
func middleInsertedMessage_isFoundAfterIndexWasBuilt() async {
func middleInsertedMessage_isStillUpdatableByID() async {
let context = MockChatDeliveryContext()
let coordinator = ChatDeliveryCoordinator(context: context)
let makePublic = { (id: String) in
BitchatMessage(
id: id,
sender: "me",
content: "Public message",
timestamp: Date(),
isRelay: false,
isPrivate: false,
deliveryStatus: .sending
)
}
context.messages = [makePublic("public-a"), makePublic("public-b")]
context.store.append(
makePublicMessage(id: "public-a", status: .sending, timestamp: Date(timeIntervalSince1970: 10)),
to: .mesh
)
context.store.append(
makePublicMessage(id: "public-b", status: .sending, timestamp: Date(timeIntervalSince1970: 30)),
to: .mesh
)
// Build the incremental index.
#expect(coordinator.updateMessageDeliveryStatus("public-a", status: .sent))
// Out-of-order arrival: PublicMessagePipeline inserts by timestamp,
// so the count grows while the tail ID stays the same.
context.messages.insert(makePublic("public-mid"), at: 1)
// Out-of-order arrival: the store inserts by timestamp, shifting the
// tail's position.
context.store.append(
makePublicMessage(id: "public-mid", status: .sending, timestamp: Date(timeIntervalSince1970: 20)),
to: .mesh
)
let mesh = context.store.conversation(for: .mesh)
#expect(mesh.messages.map(\.id) == ["public-a", "public-mid", "public-b"])
// The inserted message must be locatable, and the shifted tail must
// not be updated through a stale index entry.
// Both the inserted message and the shifted tail stay updatable.
#expect(coordinator.updateMessageDeliveryStatus("public-mid", status: .sent))
#expect(isSent(context.messages[1].deliveryStatus))
#expect(isSent(mesh.message(withID: "public-mid")?.deliveryStatus))
#expect(coordinator.updateMessageDeliveryStatus("public-b", status: .sent))
#expect(isSent(context.messages[2].deliveryStatus))
#expect(isSent(mesh.message(withID: "public-b")?.deliveryStatus))
}
@Test @MainActor
func middleInsertedPrivateMessage_isFoundAfterIndexWasBuilt() async {
func middleInsertedPrivateMessage_isStillUpdatableByID() async {
let context = MockChatDeliveryContext()
let coordinator = ChatDeliveryCoordinator(context: context)
let peerID = PeerID(str: "0102030405060708")
context.privateChats[peerID] = [
makePrivateMessage(id: "pm-a", status: .sending),
makePrivateMessage(id: "pm-b", status: .sending)
]
let conversationID = ConversationID.directPeer(peerID)
context.store.append(
makePrivateMessage(id: "pm-a", status: .sending, timestamp: Date(timeIntervalSince1970: 10)),
to: conversationID
)
context.store.append(
makePrivateMessage(id: "pm-b", status: .sending, timestamp: Date(timeIntervalSince1970: 30)),
to: conversationID
)
#expect(coordinator.updateMessageDeliveryStatus("pm-a", status: .sent))
// Timestamp re-sort (sanitizeChat) can place a late arrival mid-array.
context.privateChats[peerID]?.insert(makePrivateMessage(id: "pm-mid", status: .sending), at: 1)
// A late arrival with an older timestamp lands mid-array.
context.store.append(
makePrivateMessage(id: "pm-mid", status: .sending, timestamp: Date(timeIntervalSince1970: 20)),
to: conversationID
)
let chat = context.store.conversation(for: conversationID)
#expect(chat.messages.map(\.id) == ["pm-a", "pm-mid", "pm-b"])
#expect(coordinator.updateMessageDeliveryStatus("pm-mid", status: .sent))
#expect(isSent(context.privateChats[peerID]?[1].deliveryStatus))
#expect(isSent(chat.message(withID: "pm-mid")?.deliveryStatus))
#expect(coordinator.updateMessageDeliveryStatus("pm-b", status: .sent))
#expect(isSent(context.privateChats[peerID]?[2].deliveryStatus))
#expect(isSent(chat.message(withID: "pm-b")?.deliveryStatus))
}
@Test @MainActor
func mirroredPrivateCopies_bothReceiveDeliveryUpdate() async {
let context = MockChatDeliveryContext()
let coordinator = ChatDeliveryCoordinator(context: context)
let stablePeerID = PeerID(str: String(repeating: "ab", count: 32))
let ephemeralPeerID = PeerID(str: "0102030405060708")
let messageID = "mirrored-mock-1"
// Step 2's keying mirrors a private message into both the stable-key
// and ephemeral-peer conversations (distinct copies here to prove
// per-conversation application, not shared-reference aliasing).
context.store.append(makePrivateMessage(id: messageID, status: .sent), to: .directPeer(stablePeerID))
context.store.append(makePrivateMessage(id: messageID, status: .sent), to: .directPeer(ephemeralPeerID))
let didUpdate = coordinator.updateMessageDeliveryStatus(
messageID,
status: .delivered(to: "Peer", at: Date())
)
#expect(didUpdate)
#expect(isDelivered(context.store.conversation(for: .directPeer(stablePeerID)).message(withID: messageID)?.deliveryStatus))
#expect(isDelivered(context.store.conversation(for: .directPeer(ephemeralPeerID)).message(withID: messageID)?.deliveryStatus))
#expect(context.markedDeliveredMessageIDs == [messageID])
}
@Test @MainActor
@@ -566,7 +623,7 @@ struct ChatDeliveryCoordinatorContextTests {
let context = MockChatDeliveryContext()
let coordinator = ChatDeliveryCoordinator(context: context)
let peerID = PeerID(str: "0102030405060708")
context.privateChats[peerID] = [makePrivateMessage(id: "keep-receipt", status: .sent)]
context.store.append(makePrivateMessage(id: "keep-receipt", status: .sent), to: .directPeer(peerID))
context.sentReadReceipts = ["keep-receipt", "drop-receipt"]
// Startup phase: cleanup must be a no-op.
+155
View File
@@ -562,4 +562,159 @@ struct ConversationStoreTests {
#expect(!conversation.containsMessage(withID: "one"))
#expect(store.append(makeMessage(id: "one", timestamp: 2000), to: id))
}
// MARK: - Store-level message-ID conversation map (ID-only delivery)
@Test("ID map tracks append, upsert, and removal")
@MainActor
func messageIDMapTracksAppendAndRemoval() {
let store = ConversationStore()
let direct = makeDirectConversationID("aa")
#expect(store.conversationIDs(forMessageID: "m1").isEmpty)
store.append(makeMessage(id: "m1", timestamp: 1), to: .mesh)
#expect(store.conversationIDs(forMessageID: "m1") == [.mesh])
// Upsert-as-append registers; upsert-in-place does not duplicate.
store.upsertByID(makeMessage(id: "d1", timestamp: 1, isPrivate: true), in: direct)
store.upsertByID(makeMessage(id: "d1", timestamp: 1, isPrivate: true, deliveryStatus: .sent), in: direct)
#expect(store.conversationIDs(forMessageID: "d1") == [direct])
store.removeMessage(withID: "m1", from: .mesh)
#expect(store.conversationIDs(forMessageID: "m1").isEmpty)
store.removeMessages(from: direct, where: { $0.id == "d1" })
#expect(store.conversationIDs(forMessageID: "d1").isEmpty)
}
@Test("ID map handles multi-conversation membership (mirrored private copies)")
@MainActor
func messageIDMapHandlesMirroredCopies() {
let store = ConversationStore()
let stable = makeDirectConversationID("stable")
let ephemeral = makeDirectConversationID("ephemeral")
// Step 2's keying mirrors one private message into the stable-key
// AND ephemeral-peer conversations (distinct copies here to prove
// per-conversation bookkeeping).
store.upsertByID(makeMessage(id: "dm-1", timestamp: 1, isPrivate: true, deliveryStatus: .sent), in: stable)
store.upsertByID(makeMessage(id: "dm-1", timestamp: 1, isPrivate: true, deliveryStatus: .sent), in: ephemeral)
#expect(store.conversationIDs(forMessageID: "dm-1") == [stable, ephemeral])
// An ID-only delivery update reaches BOTH copies.
#expect(store.setDeliveryStatus(.delivered(to: "bob", at: Date()), forMessageID: "dm-1"))
for id in [stable, ephemeral] {
guard case .delivered = store.conversation(for: id).message(withID: "dm-1")?.deliveryStatus else {
Issue.record("expected .delivered in \(id)")
return
}
}
// Removing one copy keeps the other resolvable.
store.removeMessage(withID: "dm-1", from: ephemeral)
#expect(store.conversationIDs(forMessageID: "dm-1") == [stable])
}
@Test("ID-only setDeliveryStatus enforces no-downgrade and unknown IDs")
@MainActor
func idOnlyDeliveryStatusRules() {
let store = ConversationStore()
let direct = makeDirectConversationID("aa")
store.append(
makeMessage(id: "dm-1", timestamp: 1, isPrivate: true, deliveryStatus: .read(by: "bob", at: Date())),
to: direct
)
// Unknown message.
#expect(!store.setDeliveryStatus(.sent, forMessageID: "missing"))
#expect(store.deliveryStatus(forMessageID: "missing") == nil)
// Downgrade from .read is refused; status is readable by ID alone.
#expect(!store.setDeliveryStatus(.delivered(to: "bob", at: Date()), forMessageID: "dm-1"))
guard case .read = store.deliveryStatus(forMessageID: "dm-1") else {
Issue.record("expected .read to survive the downgrade attempt")
return
}
}
@Test("ID map follows migration between conversations")
@MainActor
func messageIDMapFollowsMigration() {
let store = ConversationStore()
let source = makeDirectConversationID("ephemeral")
let destination = makeDirectConversationID("stable")
store.append(makeMessage(id: "dm-1", timestamp: 1, isPrivate: true), to: source)
store.append(makeMessage(id: "dm-2", timestamp: 2, isPrivate: true), to: source)
// Already present in the destination: migration dedups, and the map
// must not retain a stale source membership.
store.append(makeMessage(id: "dm-2", timestamp: 2, isPrivate: true), to: destination)
store.migrateConversation(from: source, to: destination)
#expect(store.conversationIDs(forMessageID: "dm-1") == [destination])
#expect(store.conversationIDs(forMessageID: "dm-2") == [destination])
// Delivery updates keep flowing after the handoff.
#expect(store.setDeliveryStatus(.sent, forMessageID: "dm-1"))
}
@Test("ID map drops trimmed messages")
@MainActor
func messageIDMapDropsTrimmedMessages() {
let store = ConversationStore()
let id = ConversationID.geohash("u4pruyd")
let conversation = store.conversation(for: id)
store.append(makeMessage(id: "first", timestamp: 1), to: id)
for index in 0..<conversation.cap {
store.append(makeMessage(id: "filler-\(index)", timestamp: 2 + TimeInterval(index)), to: id)
}
// "first" fell off the cap: the map must forget it.
#expect(store.conversationIDs(forMessageID: "first").isEmpty)
#expect(!store.setDeliveryStatus(.sent, forMessageID: "first"))
// Survivors stay resolvable.
#expect(store.conversationIDs(forMessageID: "filler-0") == [id])
}
@Test("ID map is emptied by clear, removeConversation, and clearAll")
@MainActor
func messageIDMapClearedWithConversations() {
let store = ConversationStore()
let direct = makeDirectConversationID("aa")
store.append(makeMessage(id: "m1", timestamp: 1), to: .mesh)
store.append(makeMessage(id: "d1", timestamp: 1, isPrivate: true), to: direct)
store.clear(.mesh)
#expect(store.conversationIDs(forMessageID: "m1").isEmpty)
#expect(store.conversationIDs(forMessageID: "d1") == [direct])
store.removeConversation(direct)
#expect(store.conversationIDs(forMessageID: "d1").isEmpty)
store.append(makeMessage(id: "m2", timestamp: 2), to: .mesh)
store.clearAll()
#expect(store.conversationIDs(forMessageID: "m2").isEmpty)
}
@Test("shared message instance across conversations stays consistent")
@MainActor
func sharedInstanceMirroredCopiesStayConsistent() {
let store = ConversationStore()
let stable = makeDirectConversationID("stable")
let ephemeral = makeDirectConversationID("ephemeral")
// The production mirroring path upserts the SAME BitchatMessage
// instance (reference type) into both conversations.
let message = makeMessage(id: "dm-1", timestamp: 1, isPrivate: true, deliveryStatus: .sent)
store.upsertByID(message, in: stable)
store.upsertByID(message, in: ephemeral)
#expect(store.setDeliveryStatus(.delivered(to: "bob", at: Date()), forMessageID: "dm-1"))
for id in [stable, ephemeral] {
guard case .delivered = store.conversation(for: id).message(withID: "dm-1")?.deliveryStatus else {
Issue.record("expected .delivered in \(id)")
return
}
}
}
}
@@ -194,26 +194,18 @@ final class PerformanceBaselineTests: XCTestCase {
reportThroughput("gcs.buildAndDecode", samples: samples, operations: repsPerPass, unit: "filters")
}
// MARK: - 4a. Delivery location index (incremental path)
// MARK: - 4a. Delivery status updates through the coordinator (store path)
/// `ChatDeliveryCoordinator.updateMessageDeliveryStatus` against a warm
/// location index: 2000 public + 50x40 private messages, 500 status
/// updates per pass. Statuses alternate sent <-> delivered so every call
/// performs a real update (never the skip path).
/// `ChatDeliveryCoordinator.updateMessageDeliveryStatus` over the
/// `ConversationStore`'s message-ID conversation map: 2000 public
/// (split mesh + geohash to stay under the per-conversation cap) + 50x40
/// private messages, 500 status updates per pass. Statuses alternate
/// sent <-> delivered so every call performs a real update (never the
/// skip path).
func testDeliveryStatusIncrementalUpdates() {
let context = PerfDeliveryContext.makeCorpus(publicCount: 2000, peerCount: 50, messagesPerPeer: 40)
let coordinator = ChatDeliveryCoordinator(context: context)
// Warm the index so the measure block exercises the incremental path.
XCTAssertTrue(coordinator.updateMessageDeliveryStatus(context.messages[0].id, status: .sent))
// 250 public + 250 private IDs spread across the corpus.
var targetIDs: [String] = []
for i in stride(from: 0, to: 2000, by: 8) { targetIDs.append(context.messages[i].id) }
let peerIDs = context.privateChats.keys.sorted { $0.id < $1.id }
for (offset, peer) in peerIDs.enumerated() where offset < 25 {
guard let chat = context.privateChats[peer] else { continue }
for i in stride(from: 0, to: chat.count, by: 4) { targetIDs.append(chat[i].id) }
}
let targetIDs = context.makeTargetIDs(publicTargets: 250, privateTargets: 250)
XCTAssertEqual(targetIDs.count, 500)
let fixedDate = Date(timeIntervalSince1970: 1_700_000_000)
@@ -235,46 +227,38 @@ final class PerformanceBaselineTests: XCTestCase {
reportThroughput("delivery.incrementalUpdate", samples: samples, operations: targetIDs.count, unit: "updates")
}
// MARK: - 4b. Delivery location index (rebuild path)
// MARK: - 4b. Delivery status updates against the store directly
/// The expensive path: a non-append insertion (out-of-order arrival at
/// the head of the timeline) invalidates the index, so the next status
/// update triggers a full rebuild over ~4000 message locations.
func testDeliveryStatusIndexRebuild() {
/// `ConversationStore.setDeliveryStatus(_:forMessageID:)` at the same
/// scale as 4a, without the coordinator/context wrapping the store-side
/// cost of an ID-only delivery update (map lookup + per-conversation
/// ID-index apply + change emission). Replaces the deleted
/// `delivery.indexRebuild` benchmark: the positional location index and
/// its rebuild path no longer exist; the store's ID indexes are
/// maintained inside each mutation, so there is no rebuild to measure.
func testDeliveryStatusStoreUpdates() {
let context = PerfDeliveryContext.makeCorpus(publicCount: 2000, peerCount: 50, messagesPerPeer: 40)
let coordinator = ChatDeliveryCoordinator(context: context)
// Warm the index before the first insertion invalidates it.
XCTAssertTrue(coordinator.updateMessageDeliveryStatus(context.messages[0].id, status: .sent))
let store = context.store
let targetIDs = context.makeTargetIDs(publicTargets: 250, privateTargets: 250)
XCTAssertEqual(targetIDs.count, 500)
// Pre-built head insertions, one consumed per measure pass.
let insertions: [BitchatMessage] = (0..<32).map { i in
BitchatMessage(
id: "insert-\(i)",
sender: "alice",
content: "out-of-order arrival \(i)",
timestamp: Date(timeIntervalSince1970: 1_690_000_000),
isRelay: false
)
}
let probeID = context.messages[1000].id
let fixedDate = Date(timeIntervalSince1970: 1_700_000_000)
var pass = 0
var toggle = false
var samples: [TimeInterval] = []
measure {
precondition(pass < insertions.count, "add more pre-built insertions")
context.messages.insert(insertions[pass], at: 0)
pass += 1
toggle.toggle()
let status: DeliveryStatus = toggle ? .delivered(to: "peer", at: fixedDate) : .sent
let start = Date()
let updated = coordinator.updateMessageDeliveryStatus(probeID, status: status)
var updated = 0
for id in targetIDs where store.setDeliveryStatus(status, forMessageID: id) {
updated += 1
}
samples.append(Date().timeIntervalSince(start))
XCTAssertTrue(updated)
XCTAssertEqual(updated, targetIDs.count)
}
reportThroughput("delivery.indexRebuild", samples: samples, operations: 1, unit: "rebuilds")
reportThroughput("delivery.storeUpdate", samples: samples, operations: targetIDs.count, unit: "updates")
}
// MARK: - 5. Message formatting
@@ -638,50 +622,63 @@ private final class PerfNostrContext: ChatNostrContext {
// MARK: - Mock ChatDeliveryContext
/// Minimal `ChatDeliveryContext` over a real `ConversationStore` (the
/// coordinator is a thin mapper onto store intents, so the measured cost is
/// the store's ID-map delivery path).
@MainActor
private final class PerfDeliveryContext: ChatDeliveryContext {
var messages: [BitchatMessage] = []
var privateChats: [PeerID: [BitchatMessage]] = [:]
let store = ConversationStore()
var sentReadReceipts: Set<String> = []
var isStartupPhase: Bool { false }
private var publicIDs: [String] = []
private var privateIDsByPeer: [(peerID: PeerID, messageIDs: [String])] = []
func notifyUIChanged() {}
func markMessageDelivered(_ messageID: String) {}
@discardableResult
func setDeliveryStatus(_ status: DeliveryStatus, forMessageID messageID: String) -> Bool {
store.setDeliveryStatus(status, forMessageID: messageID)
}
func deliveryStatus(forMessageID messageID: String) -> DeliveryStatus? {
store.deliveryStatus(forMessageID: messageID)
}
func privateMessageIDs() -> Set<String> {
store.directMessageIDs()
}
func pruneSentReadReceipts(keeping validMessageIDs: Set<String>) -> Int {
let oldCount = sentReadReceipts.count
sentReadReceipts = sentReadReceipts.intersection(validMessageIDs)
return oldCount - sentReadReceipts.count
}
@discardableResult
func setPrivateDeliveryStatus(_ status: DeliveryStatus, forMessageID messageID: String, peerID: PeerID) -> Bool {
guard var chat = privateChats[peerID],
let index = chat.firstIndex(where: { $0.id == messageID }) else {
return false
}
chat[index].deliveryStatus = status
privateChats[peerID] = chat
return true
}
/// 2000 public + `peerCount` x `messagesPerPeer` private messages with
/// deterministic IDs and timestamps.
/// `publicCount` public messages (split evenly between mesh and one
/// geohash conversation so the corpus stays under the per-conversation
/// cap) + `peerCount` x `messagesPerPeer` private messages, seeded into
/// the store with deterministic IDs and timestamps.
static func makeCorpus(publicCount: Int, peerCount: Int, messagesPerPeer: Int) -> PerfDeliveryContext {
let context = PerfDeliveryContext()
let base = Date(timeIntervalSince1970: 1_700_000_000)
context.messages = (0..<publicCount).map { i in
BitchatMessage(
let geohashID = ConversationID.geohash("u4pruyd")
for i in 0..<publicCount {
let message = BitchatMessage(
id: "pub-\(i)",
sender: "peer\(i % 20)",
content: "public message number \(i)",
timestamp: base.addingTimeInterval(Double(i)),
isRelay: false
)
context.store.append(message, to: i % 2 == 0 ? .mesh : geohashID)
context.publicIDs.append(message.id)
}
for p in 0..<peerCount {
let peerID = PeerID(str: String(format: "%016x", 0xA000_0000 + p))
context.privateChats[peerID] = (0..<messagesPerPeer).map { i in
BitchatMessage(
var messageIDs: [String] = []
for i in 0..<messagesPerPeer {
let message = BitchatMessage(
id: "dm-\(p)-\(i)",
sender: "peer\(p)",
content: "private message \(i) for peer \(p)",
@@ -691,10 +688,32 @@ private final class PerfDeliveryContext: ChatDeliveryContext {
recipientNickname: "me",
senderPeerID: peerID
)
context.store.append(message, to: .directPeer(peerID))
messageIDs.append(message.id)
}
context.privateIDsByPeer.append((peerID, messageIDs))
}
return context
}
/// Deterministic update targets spread across the corpus: `publicTargets`
/// public IDs plus `privateTargets` private IDs.
func makeTargetIDs(publicTargets: Int, privateTargets: Int) -> [String] {
var targetIDs: [String] = []
let publicStride = max(1, publicIDs.count / publicTargets)
for i in stride(from: 0, to: publicIDs.count, by: publicStride) where targetIDs.count < publicTargets {
targetIDs.append(publicIDs[i])
}
var privateCount = 0
outer: for (_, messageIDs) in privateIDsByPeer {
for i in stride(from: 0, to: messageIDs.count, by: 4) {
guard privateCount < privateTargets else { break outer }
targetIDs.append(messageIDs[i])
privateCount += 1
}
}
return targetIDs
}
}
// MARK: - End-to-end pipeline fixture