Now that #64 has landed and AtomData stored arrays are frozen on assignment, the derived global_range and global_labels values cannot go stale without going through __setitem__. This opens up a small refactor that simplifies the container.
Current state
AtomData computes global_range and global_labels lazily and caches the results in _range_cache / _labels_cache. _invalidate(key) is called from __setitem__ and __delitem__ to drop stale entries on the supported update paths. Lazy-with-invalidation was the only safe design when stored arrays were mutable, because in-place mutation of the buffer could silently desync the caches.
Proposal
Compute global_range and global_labels eagerly in __setitem__, store them in plain dicts, and retire _invalidate:
__setitem__ populates self._ranges[key] and self._labels[key] alongside self._data[key].
__delitem__ removes the key from all three dicts.
global_range and global_labels become O(1) dict lookups with no cache-presence branches.
- The
_invalidate helper is deleted.
Trade-offs
- For: The stale-cache foot-gun becomes mechanically impossible rather than merely documented-as-impossible.
_invalidate and the if key in cache branches disappear. The derivation of range/labels happens at exactly one site (assignment) rather than scattered across first-query code paths.
- Against: Compute cost is paid eagerly even for keys whose range/labels are never queried. In practice the rendering pipeline does query them for colourmap normalisation, so the lazy path is rarely "saved" work. Tiny extra memory for storing the derived values on every key.
Out of scope
This is a pure refactor with no observable behaviour change, so the existing tests for global_range and global_labels should keep passing untouched. No public API changes.
Now that #64 has landed and
AtomDatastored arrays are frozen on assignment, the derivedglobal_rangeandglobal_labelsvalues cannot go stale without going through__setitem__. This opens up a small refactor that simplifies the container.Current state
AtomDatacomputesglobal_rangeandglobal_labelslazily and caches the results in_range_cache/_labels_cache._invalidate(key)is called from__setitem__and__delitem__to drop stale entries on the supported update paths. Lazy-with-invalidation was the only safe design when stored arrays were mutable, because in-place mutation of the buffer could silently desync the caches.Proposal
Compute
global_rangeandglobal_labelseagerly in__setitem__, store them in plain dicts, and retire_invalidate:__setitem__populatesself._ranges[key]andself._labels[key]alongsideself._data[key].__delitem__removes the key from all three dicts.global_rangeandglobal_labelsbecome O(1) dict lookups with no cache-presence branches._invalidatehelper is deleted.Trade-offs
_invalidateand theif key in cachebranches disappear. The derivation of range/labels happens at exactly one site (assignment) rather than scattered across first-query code paths.Out of scope
This is a pure refactor with no observable behaviour change, so the existing tests for
global_rangeandglobal_labelsshould keep passing untouched. No public API changes.