[1.0 Decision] Bulk-edit convention for future array/buffer APIs (edit() context manager vs explicit sync) #328

Closed
opened 2026-07-02 12:29:37 +00:00 by john · 1 comment
Owner

From the 2026-07-02 pre-1.0 memory-model review. The buffer-protocol APIs themselves are additive (post-1.0-safe), but the idiom should be chosen once, before apply_threshold/fill_rect/future numpy views accumulate three inconsistent conventions.

Problem

Zero-copy views (memoryview / numpy) let Python write cell data while bypassing the setters that maintain invariants:

  • per-cell markDirty(x,y) -> chunk render-texture invalidation (src/GridLayers.h:42-79)
  • walkable/transparent -> single-cell TCOD sync + transparency_generation bump + fov_dirty (src/GridData.cpp:133-140, src/UIGridPoint.cpp:90)

The one existing zero-copy export, DiscreteMap.mask() (src/PyDiscreteMap.cpp:1315-1317), dodges this only because DiscreteMap has no dirty flags.

Options

  1. Context manager: with layer.edit() as view: -- yields a writable view, marks affected state dirty (conservatively: whole layer / full TCOD resync) on __exit__. Scoped, explicit, hard to misuse.
  2. Explicit sync: raw views are always available (layer.view / grid.walkable_view); after writing, the user must call layer.mark_dirty() / grid.sync(). More flexible, easier to forget.

Per the project's fail-early principle, no automatic change detection -- pick an explicit contract.

Blocks

  • Buffer protocol for ColorLayer/TileLayer (#335).
  • Any future grid.walkable array view (depends on GridData SoA refactor, #332).
From the 2026-07-02 pre-1.0 memory-model review. The buffer-protocol APIs themselves are additive (post-1.0-safe), but the **idiom should be chosen once**, before `apply_threshold`/`fill_rect`/future numpy views accumulate three inconsistent conventions. ## Problem Zero-copy views (memoryview / numpy) let Python write cell data while bypassing the setters that maintain invariants: - per-cell `markDirty(x,y)` -> chunk render-texture invalidation (`src/GridLayers.h:42-79`) - walkable/transparent -> single-cell TCOD sync + `transparency_generation` bump + `fov_dirty` (`src/GridData.cpp:133-140`, `src/UIGridPoint.cpp:90`) The one existing zero-copy export, `DiscreteMap.mask()` (`src/PyDiscreteMap.cpp:1315-1317`), dodges this only because DiscreteMap has no dirty flags. ## Options 1. **Context manager**: `with layer.edit() as view:` -- yields a writable view, marks affected state dirty (conservatively: whole layer / full TCOD resync) on `__exit__`. Scoped, explicit, hard to misuse. 2. **Explicit sync**: raw views are always available (`layer.view` / `grid.walkable_view`); after writing, the user must call `layer.mark_dirty()` / `grid.sync()`. More flexible, easier to forget. Per the project's fail-early principle, **no automatic change detection** -- pick an explicit contract. ## Blocks - Buffer protocol for ColorLayer/TileLayer (#335). - Any future `grid.walkable` array view (depends on GridData SoA refactor, #332).
Author
Owner

DECIDED 2026-07-02: Option 1 — edit() context manager is THE bulk-edit convention.

with layer.edit() as view: yields the writable view; __exit__ conservatively invalidates (whole-layer markDirty / full TCOD resync + generation bump as applicable). Forgetting to sync is impossible by construction, which is the fail-early principle applied to the API surface. Read-only zero-copy views (like DiscreteMap.mask()) may still be exposed without ceremony.

No always-writable raw view + mark_dirty() primitives will be shipped; if a real need for persistent writable views emerges post-1.0, primitives can be added additively without breaking the edit() idiom.

This unblocks #335 (ColorLayer/TileLayer buffers) and sets the convention #334 and any future grid.walkable view (#332) must follow. Convention will be recorded in the compatibility/docs pass (#327/#330/#326), which closes this issue.

**DECIDED 2026-07-02: Option 1 — `edit()` context manager is THE bulk-edit convention.** `with layer.edit() as view:` yields the writable view; `__exit__` conservatively invalidates (whole-layer markDirty / full TCOD resync + generation bump as applicable). Forgetting to sync is impossible by construction, which is the fail-early principle applied to the API surface. Read-only zero-copy views (like `DiscreteMap.mask()`) may still be exposed without ceremony. No always-writable raw view + `mark_dirty()` primitives will be shipped; if a real need for persistent writable views emerges post-1.0, primitives can be added additively without breaking the `edit()` idiom. This unblocks #335 (ColorLayer/TileLayer buffers) and sets the convention #334 and any future `grid.walkable` view (#332) must follow. Convention will be recorded in the compatibility/docs pass (#327/#330/#326), which closes this issue.
john closed this issue 2026-07-03 03:45:58 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
john/McRogueFace#328
No description provided.