Optimize Portico state and collection updates

This commit is contained in:
Brendan Szymanski 2026-08-11 11:27:24 -04:00
parent af7fa6656d
commit 3919f1fe34
150 changed files with 2129 additions and 2387 deletions

View file

@ -16,6 +16,48 @@ import Testing
#expect(seen == [2, 3])
}
@Test func reentrantSetDoesNotClobberOuterNotify() {
let box = StateBox(0)
var log: [Int] = []
var didReenter = false
for _ in 0..<3 {
_ = box.subscribe { value in
log.append(value)
if !didReenter {
didReenter = true
box.set(99)
}
}
}
box.set(1)
#expect(log.count == 6)
#expect(log.filter { $0 == 1 }.count == 3)
#expect(log.filter { $0 == 99 }.count == 3)
let nestedIndexes = log.indices.filter { log[$0] == 99 }
#expect(nestedIndexes.count == 3)
#expect(nestedIndexes.last! - nestedIndexes.first! == 2)
#expect(box.peek() == 99)
}
@Test func setIfChangedSkipsEqualWrites() {
let box = StateBox(7)
var callCount = 0
_ = box.subscribe { _ in callCount += 1 }
let binding = Binding(box)
binding.setIfChanged(7)
#expect(callCount == 0)
#expect(box.peek() == 7)
binding.setIfChanged(8)
#expect(callCount == 1)
#expect(box.peek() == 8)
}
@Test func cancelStopsNotifications() {
let box = StateBox(0)
var callCount = 0

View file

@ -26,6 +26,81 @@ import Adw
#expect(box.peek() == false)
}
@Test func detailedNotifyDoesNotCrossProperties() {
guard Gtk.initCheck() else { return }
let context = MountContext()
let widget = Gtk.Switch()
let active = StateBox(false)
let sensitive = StateBox(true)
var activeSetCount = 0
var sensitiveSetCount = 0
let activeBinding = Binding<Bool>(
get: { active.peek() },
set: { value in
activeSetCount += 1
active.set(value)
}
)
let sensitiveBinding = Binding<Bool>(
get: { sensitive.peek() },
set: { value in
sensitiveSetCount += 1
sensitive.set(value)
}
)
Portico.bindProperty(
widget,
activeBinding,
registry: context.registry,
notifyDetail: "active",
read: { widget.getActive() },
write: { widget.setActive(isActive: $0) }
)
Portico.bindProperty(
widget,
sensitiveBinding,
registry: context.registry,
notifyDetail: "sensitive",
read: { widget.getSensitive() },
write: { widget.setSensitive(sensitive: $0) }
)
// Keep active out of sync without sending a widget notification. A
// broadcast handler would now observe active=true and invoke its setter
// when the unrelated sensitive property changes.
widget.setActive(isActive: true)
active.set(false)
activeSetCount = 0
sensitiveSetCount = 0
widget.setSensitive(sensitive: false)
#expect(activeSetCount == 0)
#expect(sensitiveSetCount == 1)
#expect(active.peek() == false)
#expect(sensitive.peek() == false)
}
@Test func unresolvableDetailFallsBackToBroadcast() {
guard Gtk.initCheck() else { return }
let box = StateBox(false)
let context = MountContext()
let widget = Gtk.Switch()
Portico.bindProperty(
widget,
Binding(box),
registry: context.registry,
notifyDetail: "not-a-real-property",
read: { widget.getActive() },
write: { widget.setActive(isActive: $0) }
)
widget.setActive(isActive: true)
#expect(box.peek() == true)
}
// MARK: - SpinRow value
/// Adw.SpinRow.value propagates both directions via notify.