Skip to content

Commit 4042477

Browse files
committed
"docs(masonry): Add algorithm for tree parsing"
1 parent 65c7fad commit 4042477

File tree

1 file changed

+171
-0
lines changed

1 file changed

+171
-0
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
# Tower Parsing & Layout Algorithm
2+
3+
This document describes a **stack-based post-order traversal** that computes each brick’s SVG path, bounding box, and notch‐connection points **after** all of its children have been measured. It handles arbitrarily deep nesting, distinguishes **expression** vs. **simple** vs. **compound** bricks, and avoids JavaScript call‐stack limits.
4+
5+
---
6+
7+
## Data Structures
8+
9+
```ts
10+
// Represents one node in the tree (only UUID is needed here)
11+
type TreeNode = {
12+
uuid: string
13+
}
14+
15+
// Holds the computed metrics for a brick
16+
type Metrics = {
17+
path: string
18+
bbox: { w: number; h: number }
19+
connectionPoints: ConnectionPoints
20+
}
21+
22+
// Frame in our explicit stack
23+
type Frame = {
24+
node: TreeNode
25+
visited: boolean
26+
}
27+
28+
// Work‐in‐progress structures
29+
const metricsMap: Map<string, Metrics> = new Map()
30+
const stack: Frame[] = []
31+
```
32+
33+
The model must expose:
34+
35+
```ts
36+
getRoots(): string[]
37+
38+
getBrickType(uuid: string): 'expression' | 'simple' | 'compound'
39+
40+
getExpressionArgs(uuid: string): string[]
41+
42+
getNestedBlocks(uuid: string): string[]
43+
44+
// Optionally for simple/compound
45+
type StatementChildren = { top: string; bottom: string }
46+
getStatementChildren(uuid: string): StatementChildren
47+
48+
getBrickProps(uuid: string): {
49+
label: string
50+
type: 'expression' | 'simple' | 'compound'
51+
strokeWidth: number
52+
scale: number
53+
topNotch: boolean
54+
bottomNotch: boolean
55+
fontSize: number
56+
}
57+
```
58+
59+
Shared utilities:
60+
61+
```ts
62+
generatePath(config): string
63+
getBoundingBox(config): { w: number; h: number }
64+
deriveNotches(path, bbox): ConnectionPoints
65+
measureLabel(text, fontSize): { w: number; h: number; ascent: number; descent: number }
66+
```
67+
68+
---
69+
70+
## Algorithm Steps
71+
72+
1. **Initialize Work Structures**
73+
74+
- Empty metrics map
75+
76+
```pseudo
77+
metricsMap ← {}
78+
stack ← []
79+
```
80+
81+
- Seed roots
82+
83+
```pseudo
84+
for each rootUuid in model.getRoots():
85+
stack.push({ node: { uuid: rootUuid }, visited: false })
86+
```
87+
88+
We mark `visited = false` on first encounter (“children not yet handled”) and will re-push the same node with `visited = true` (“ready to compute”) after its children.
89+
90+
2. **Process Frames Until Done**
91+
92+
```pseudo
93+
while stack is not empty:
94+
(currentNode, visited) ← stack.pop()
95+
96+
if visited == false:
97+
// --- First encounter: enqueue children, defer parent ---
98+
stack.push({ node: currentNode, visited: true })
99+
100+
// 1) Determine children based on brick type
101+
type ← model.getBrickType(currentNode.uuid)
102+
if type == 'expression':
103+
children ← []
104+
else if type == 'simple':
105+
children ← model.getExpressionArgs(currentNode.uuid)
106+
+ model.getStatementChildren(currentNode.uuid)
107+
else if type == 'compound':
108+
children ← model.getNestedBlocks(currentNode.uuid)
109+
+ model.getExpressionArgs(currentNode.uuid)
110+
+ model.getStatementChildren(currentNode.uuid)
111+
112+
// 2) Push children so they come off *before* the parent’s second visit
113+
for each childUuid in reverse(children):
114+
stack.push({ node: { uuid: childUuid }, visited: false })
115+
116+
else:
117+
// --- Second encounter: all children are ready, compute metrics ---
118+
// 1) Gather child bounding boxes
119+
childBBoxes ← []
120+
for each cUuid in model.getExpressionArgs(currentNode.uuid)
121+
+ model.getNestedBlocks(currentNode.uuid)
122+
+ model.getStatementChildren(currentNode.uuid):
123+
childBBoxes.push(metricsMap[cUuid].bbox)
124+
125+
// 2) Fetch brick props and measure its label
126+
props ← model.getBrickProps(currentNode.uuid)
127+
labelBox ← measureLabel(props.label, props.fontSize)
128+
129+
// 3) Assemble config
130+
config = {
131+
type: props.type,
132+
strokeWidth: props.strokeWidth,
133+
scaleFactor: props.scale,
134+
bBoxLabel: { w: labelBox.w, h: labelBox.h },
135+
bBoxArgs: childBBoxes,
136+
hasNotchAbove: props.topNotch,
137+
hasNotchBelow: props.bottomNotch
138+
}
139+
140+
// 4) Compute path, bbox, notch‐coords
141+
path ← generatePath(config)
142+
bbox ← getBoundingBox(config)
143+
connectionPoints ← deriveNotches(path, bbox)
144+
145+
// 5) Store into metricsMap
146+
metricsMap[currentNode.uuid] = { path, bbox, connectionPoints }
147+
```
148+
149+
**Key invariant**: When a node’s frame is popped with `visited = true`, all of its children (and their entire subtrees) have already been computed and stored in `metricsMap`.
150+
151+
3. **Completion**
152+
153+
When the stack empties, `metricsMap` contains the final layout data for every brick:
154+
155+
- SVG outline (`path`)
156+
- Dimensions (`bbox`)
157+
- Notch coordinates (`connectionPoints`)
158+
159+
You can now feed these into your React components or canvas renderer in a single, child‐first batch.
160+
161+
---
162+
163+
## Why This Approach
164+
165+
- **Post‐order traversal** ensures each statement/compound block sizes itself around fully measured plugged‐in bricks.
166+
- **Explicit stack** avoids recursion limits—safe for arbitrary nesting depth.
167+
- **Type‐aware child selection** respects the two “directions” of plug‐ins:
168+
- Expression bricks never act as parents (no children).
169+
- Simple bricks only host expression‐slot arguments (and top/bottom chain).
170+
- Compound bricks first nest entire inner‐blocks, then expression args, then statement chaining.
171+
- **No extra bookkeeping**: the “two‐push visited‐flag” trick implicitly tracks when children are done without counters or complex state.

0 commit comments

Comments
 (0)