Skip to content

[BrushGraph 2/7] Add converters and repository#61

Open
maxmmitchell wants to merge 2 commits intobrush-graph/1-datafrom
brush-graph/2-persistence
Open

[BrushGraph 2/7] Add converters and repository#61
maxmmitchell wants to merge 2 commits intobrush-graph/1-datafrom
brush-graph/2-persistence

Conversation

@maxmmitchell
Copy link
Copy Markdown

Description

This is the second PR in the Brush Graph stack. It introduces persistence and conversion logic to translate the interactive graph data model into Android Ink Proto formats.

Details

  • Mapping definitions between the UI-friendly BrushGraph and the tree-structured ProtoBrushFamily.
  • Deduplication algorithms to reduce the number of identical nodes on-screen, while maintaining semantically identical brushes.
  • BrushGraphRepository abstracts the persistence and conversion details away from the ViewModel, providing a clean API for loading and saving.
  • Centralizes failure points during serialization/deserialization.

Dependencies

  • Target: brush-graph/1-data

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a graph-based data model for custom brushes, providing utilities to convert between the BrushGraph model and the functional BrushFamily proto. It includes a BrushGraphRepository for state management, validation, and persistence, along with comprehensive unit tests. The review feedback highlights several performance concerns regarding $O(N^2)$ complexity in graph conversion and deduplication logic. Additionally, there are recommendations to improve testability by injecting coroutine scopes, enhance maintainability by removing magic strings, and increase robustness by decoupling port reordering logic from UI-specific index offsets.

_graphIssues.update { (it + newIssue).distinctBy { issue -> Triple(issue.displayMessage, issue.nodeId, issue.severity) } }
}

fun validate(): Boolean {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The validate() method is called synchronously on every graph modification (add node, add edge, etc.). Since GraphValidator.validateAll performs extensive checks over all nodes and edges, this can block the main thread and cause UI stutters for larger graphs. Consider offloading validation to a background thread or debouncing the updates if immediate synchronous feedback isn't required for all operations.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we do need validate() to be called on every graph modification, as we are constantly updating the brush being used. Delaying validation could cause a gap between when the brush family updates, and when the user receives notice their brush has errors. So a brush could be broken and the user wouldn't know before using it. Validate also needs to block conversion (to avoid creating an invalid brush), so there'd be a lag between when the graph is modified and when the brush is updated.


val isFromTexture = fromIndex in 0 until T
val isToTexture = toIndex in 0 until T
val isFromColor = fromIndex in (T + 1) until (T + 1 + data.colorPortIds.size)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for reordering Paint ports relies on hardcoded assumptions about the index offsets (e.g., T + 1). This tightly couples the repository logic to the specific visual representation of ports in the UI. If the UI layout changes, this logic will break. Consider passing the specific port IDs or using a more robust way to identify the target list.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an argument to be made that this logic belongs at the UI layer. But although it does wind up influencing the UI, it is fundamentally enforcing rules about how the ports are stored in the data layer, so I think using the indices is OK. Perhaps this ordering should be completely maintained at the UI layer instead, but then we need to hold onto the portIds in the data layer, but then also hold an ordering for them in the UI layer. I'm not sure this is any better.

@maxmmitchell maxmmitchell force-pushed the brush-graph/2-persistence branch 4 times, most recently from ec8b51b to 4f5748b Compare April 28, 2026 04:58
@maxmmitchell maxmmitchell force-pushed the brush-graph/2-persistence branch from 4f5748b to bd61e05 Compare April 28, 2026 05:00
@maxmmitchell maxmmitchell marked this pull request as ready for review April 28, 2026 07:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant