Skip to content

Latest commit

 

History

History

TopicReplyLayerLab

README.md

Topic reply text and icon layers

TopicReplyItemView is the clickable topic label embedded in chat rows. Its layout producer builds plain attributed text with a generated rounded background and one embedded topic icon. It previously allocated a child TextView, that view's drawing layer, and an embedded-icon container. The clickable outer Control remains native; the text now uses TextViewLayer, with the icon attached directly to that layer. Each populated label removes one NSView and two CALayers.

The renderer retains the original logical text bounds, drawing bleed, whole-point horizontal rounding, and icon offsets. Text updates preserve the current origin; the subsequent layout pass owns animated centering. Bounds/position animations preserve the drawing insets. The parent control keeps click scaling and actions and now exposes the existing label text as its accessibility label. Icon replacement/removal stops playback explicitly. Playback visibility uses the intersection of the logical text frame and the control's visible rectangle, preserving the previous key-window policy. Attachment/detachment updates playback immediately; existing chat-row callbacks still handle scrolling/window activity without adding per-label observers.

Validation scope

prepare.py compiles the complete native view from 6ecf012c1 and the complete current view against real TGUIKit/AppKit/Core Animation/Core Text. It renames classes, adjusts visibility, and adds read-only test accessors. The layout fixture uses the production sequence of attributed text, embedded item, measurement, and generateAutoBlock. Account/model services and actual emoji decoding are replaced by stand-ins; a generated asymmetric image validates icon placement and orientation.

Eleven functional tests cover view/layer reduction in an attached window, icon reuse/replacement/removal, click handling and labels, window attachment/detachment/key policy, clipping, animated centering, title-change animation timing, backing-scale changes, and object release. Thirty-nine exact bitmap comparisons cover wrapping, Arabic, CJK, family emoji, 1x/2x/3x, and empty/iconless cases. Native/candidate visibility is also compared with a centered label clipped at six horizontal offsets.

This does not build the full app or validate real Lottie decoding, account-backed topic-file generation, or end-to-end chat scrolling. Those services are unchanged by this rendering change.

Measurement scope

Nine alternating native/layer batches measure construction of 400 complete topic-view fixtures and 2,000 rebinds per batch, then repeat in reverse order. Unique layouts and rounded backgrounds are premeasured. Icon images are generated by the same fixture in both paths. Fresh-process memory measurements retain 2,000 detached topic views after warming both implementations, excluding the prebuilt model/layout memory. The measured savings apply to this component and fixture; they are not app-wide performance estimates.

Fixture measurement Native, first / reversed Layers, first / reversed
Construction, µs per topic view 19.62 / 19.28 10.45 / 10.45
Rebind, µs per update 1.383 / 1.361 0.315 / 0.318
Incremental memory, 2,000 views 28.97 / 28.90 MB 19.58 / 19.58 MB

Construction improves 46–47%, updates improve about 77%, and incremental retained memory drops 32.3–32.4%. The repeat memory run reverses process order.

All 11 functional tests pass in release, Address Sanitizer, and Thread Sanitizer. The release timing run executes 13 tests with one memory benchmark skipped; sanitizers skip both benchmarks. Whole-file syntax parsing passes. All 409 frozen source/dependency files remain unchanged across validation.

Raw batches, isolated memory samples, validation logs, source/dependency hashes, and the source delta are in results/.

Reproduce

python3 tools/perflab/TopicReplyLayerLab/prepare.py
swift test --package-path tools/perflab/TopicReplyLayerLab -c release
TOPIC_TIMING=1 swift test --package-path tools/perflab/TopicReplyLayerLab -c release
TOPIC_TIMING=1 TOPIC_REVERSE=1 swift test --package-path tools/perflab/TopicReplyLayerLab -c release
TOPIC_MEMORY=native swift test --package-path tools/perflab/TopicReplyLayerLab -c release --filter BenchmarkTests.testRetainedMemory
TOPIC_MEMORY=layers swift test --package-path tools/perflab/TopicReplyLayerLab -c release --filter BenchmarkTests.testRetainedMemory
ASAN_OPTIONS=detect_leaks=0 swift test --package-path tools/perflab/TopicReplyLayerLab --sanitize address
swift test --package-path tools/perflab/TopicReplyLayerLab --sanitize thread

Use separate scratch paths and run builds/benchmarks sequentially. TOPIC_RESULT_PREFIX chooses a JSON output prefix. Benchmarks are disabled by default and under sanitizers. Address Sanitizer uses detect_leaks=0; explicit weak-reference assertions cover label/icon release.

Topic icon identity and reuse

Generated reply icons previously used only the topic color as their binding ID, so a changed first letter could retain the old icon. Separator and vertical-tab views accepted either a matching custom file ID or thread ID; that also retained stale generated icons after renames/recolors and could prevent switching to a custom icon. General icons and template emoji could keep old theme tints.

A shared identity now records account, icon source kind, and the inputs that affect its appearance. Generated icons use the first grapheme and color; General uses its tint; custom emoji use file ID and template tint. Missing metadata is distinct from an empty title and includes the fallback thread ID. Reply layouts use the generated file's canonical ID, and zero custom IDs follow the generated-icon path. The helper remains in an existing ChatUI source file so Xcode does not need a newly discovered source file.

Eleven new tests cover these transitions, source-ID collisions, account changes, multi-scalar initials, late metadata, playback removal, and retained identity when appearance stays unchanged. The initial baseline run produced 23 failed assertions. The final playback-removal assertion additionally marks the old layer playable before replacement. All 22 functional tests pass in release, Address Sanitizer, and Thread Sanitizer; the original 39 bitmap pairs still match exactly. All 415 frozen compiler inputs remained unchanged through final validation. Address Sanitizer disables leak scanning; the existing explicit ownership tests remain enabled.

The reply view and helper compile against real TGUIKit. Separator and vertical tests compile the exact production topic-binding branches. The account, models, file factory, and decoder are stand-ins; the generated reply-ID expression is extracted directly from production. This does not compile the complete account-backed layout initializer, separator/vertical controllers, or the main application. The three complete production files pass Swift syntax parsing.

Nine alternating benchmark batches, repeated in reverse order, measure the binding work below. Values are microseconds per bind, shown as first/reversed-order medians. Layouts are premeasured and emoji images are static stand-ins.

Binding Before µs After µs
Reply, unchanged icon 0.2825 / 0.2723 0.2882 / 0.2876
Separator, unchanged 0.0059 / 0.0059 0.0313 / 0.0313
Vertical tab, unchanged 0.0120 / 0.0126 0.0430 / 0.0411
Separator, another thread with identical icon 11.2206 / 10.9377 0.0308 / 0.0311
Vertical tab, another thread with identical icon 11.4763 / 11.3520 0.0400 / 0.0417

Correctness checks add about 6–31 nanoseconds to unchanged bindings in these runs. Rebinding a different thread with the identical rendered icon retains the existing icon: 1,001 file/view constructions become one across 1,000 updates. That is an isolated reuse case, not an app-wide speedup. The identity value has a 32-byte stride; total retained process memory for this fix was not measured. The original layer-migration measurements above remain historical results.

Raw logs, source delta, input hashes, and timing batches are in results/icon-identity/. Reproduce with:

python3 tools/perflab/TopicReplyLayerLab/prepare.py
TOPIC_IDENTITY_TIMING=1 swift test --package-path tools/perflab/TopicReplyLayerLab -c release
TOPIC_IDENTITY_TIMING=1 TOPIC_REVERSE=1 swift test --package-path tools/perflab/TopicReplyLayerLab -c release --filter IconIdentityBenchmarkTests
ASAN_OPTIONS=detect_leaks=0 swift test --package-path tools/perflab/TopicReplyLayerLab --sanitize address
swift test --package-path tools/perflab/TopicReplyLayerLab --sanitize thread

Use separate scratch paths and sequential builds. To reproduce the old behavior, prepare with --identity-baseline and run --filter IconIdentityTests; then regenerate without that flag before validating the candidate.