From 98db090f682e77f57926888d06908a94c21b89d3 Mon Sep 17 00:00:00 2001 From: Paulo Medeiros Date: Mon, 25 Nov 2024 23:26:52 +0100 Subject: [PATCH] remove golbal dicts in favour of input (more functioal approach) --- src/vaev-driver/render.cpp | 16 ++- src/vaev-layout/block.cpp | 16 +-- src/vaev-layout/fragmentainer.h | 48 --------- src/vaev-layout/input_output.h | 166 ++++++++++++++++++++------------ src/vaev-layout/layout.cpp | 17 +--- 5 files changed, 129 insertions(+), 134 deletions(-) diff --git a/src/vaev-driver/render.cpp b/src/vaev-driver/render.cpp index c6a07ee..c4e3416 100644 --- a/src/vaev-driver/render.cpp +++ b/src/vaev-driver/render.cpp @@ -152,6 +152,8 @@ Vec> print(Markup::Document const &dom, Style::Media const & auto root = Layout::Frag(); + Layout::Breakpoint prevBreakpoint{.endIdx = 0}, currBreakpoint; + while (true) { tree.fc.createNextFragmentainer(); tree.fc.isDiscoveryMode = true; @@ -162,15 +164,18 @@ Vec> print(Markup::Document const &dom, Style::Media const & .knownSize = {vp.small.width, NONE}, .availableSpace = {vp.small.width, 0_px}, .containingBlock = {vp.small.width, vp.small.height}, + .bt = Layout::BreakpointTraverser(&prevBreakpoint), } ); + // tree.fc.breakStack = {&outDiscovery.breakpoint.unwrap()}; if (outDiscovery.completelyLaidOut) { - tree.fc.visitedWholeBox.put(&tree.root, true); + // tree.fc.breakStack = {nullptr}; + currBreakpoint = Layout::Breakpoint::buildClassB(1, false); + } else { + currBreakpoint = outDiscovery.breakpoint.unwrap(); } - tree.fc.pushFlagsVisitedWholeBox(tree.root); - tree.fc.isDiscoveryMode = false; auto outReal = Layout::layout( tree, @@ -180,11 +185,14 @@ Vec> print(Markup::Document const &dom, Style::Media const & .knownSize = {vp.small.width, NONE}, .availableSpace = {vp.small.width, 0_px}, .containingBlock = {vp.small.width, vp.small.height}, + .bt = Layout::BreakpointTraverser(&prevBreakpoint, &currBreakpoint), } ); - if (tree.fc.getStartPosition(tree.root) == 1) + if (outReal.completelyLaidOut) break; + + std::swap(prevBreakpoint, currBreakpoint); } Vec> pages; diff --git a/src/vaev-layout/block.cpp b/src/vaev-layout/block.cpp index f3c34c1..08b9f49 100644 --- a/src/vaev-layout/block.cpp +++ b/src/vaev-layout/block.cpp @@ -17,7 +17,7 @@ void maybeProcessChildBreakpoint(FragmentationContext &fc, Breakpoint ¤tBr // BREAK CLASS X (recursive case) currentBreakpoint.overrideIfBetter( Breakpoint::buildFromChild( - maybeChildBreakpoint.unwrap(), + std::move(maybeChildBreakpoint.unwrap()), childIndex + 1, currBoxIsBreakAvoid ) @@ -66,7 +66,6 @@ Res processBreakpointsAfterChild(FragmentationContext &fc, Breakpo childBox.style->break_->after == BreakBetween::AVOID or (not isLastChild and parentBox.children()[childIndex + 1].style->break_->before == BreakBetween::AVOID); - fc.visitedWholeBox.put(&childBox, true); // FIXME currentBreakpoint.overrideIfBetter( Breakpoint::buildClassB( childIndex + 1, @@ -80,16 +79,18 @@ Res processBreakpointsAfterChild(FragmentationContext &fc, Breakpo return Output{ .size = currentBoxSize, .completelyLaidOut = false, - .breakpoint = Breakpoint::buildForced(childIndex + 1) + .breakpoint = Breakpoint::buildForced( + childIndex + 1 + ) }; } return Ok(NONE); } -Res processBreakpointsBeforeChild(usize endAt, Vec2Px currentSize, bool forcedBreakBefore, bool isFirstChild) { +Res processBreakpointsBeforeChild(usize endAt, Vec2Px currentSize, bool forcedBreakBefore, usize startAt) { // FORCED BREAK - if (forcedBreakBefore and not isFirstChild) { + if (forcedBreakBefore and not(startAt == endAt)) { return Output{ .size = currentSize, .completelyLaidOut = false, @@ -126,7 +127,7 @@ struct BlockFormatingContext { i, Vec2Px{inlineSize, blockSize}, c.style->break_->before == BreakBetween::PAGE, - i == startAt + startAt ) ); @@ -138,7 +139,8 @@ struct BlockFormatingContext { .fragment = input.fragment, .availableSpace = {inlineSize, 0_px}, .containingBlock = {inlineSize, input.knownSize.y.unwrapOr(0_px)}, - .ancestralsBorderPadding = input.ancestralsBorderPadding + .bt = input.bt.traverseInsideUsingIthChild(i), + .ancestralsBorderPadding = input.ancestralsBorderPadding, }; auto margin = computeMargins(tree, c, childInput); diff --git a/src/vaev-layout/fragmentainer.h b/src/vaev-layout/fragmentainer.h index dd46b0c..ba85964 100644 --- a/src/vaev-layout/fragmentainer.h +++ b/src/vaev-layout/fragmentainer.h @@ -33,10 +33,6 @@ struct FragmentationContext { Vec2Px defaultSize = {Limits::MAX, Limits::MAX}; - // TODO: discuss this being global with team - // TODO: merge maps into map of tuples (better memory access pattern) - Map layoutUntilComitted, layoutUntilNotComitted; - FragmentationContext() {} FragmentationContext(Vec2Px defaultSize) : defaultSize(defaultSize) {} @@ -47,28 +43,6 @@ struct FragmentationContext { // NOTE: a bit rudimentar, but necessary while some displays do not have fragmentation implemented usize monolithicCount = 0; - Map visitedWholeBox; - - void _pushFlagsVisitedWholeBox(Box &b, bool subtreeIsActive) { - if (subtreeIsActive) { - setDiscoveredEndPosition(b, b.children().len()); - } - - if (b.children().len() == 0) - return; - - bool meOrsiblingToRightIsFull = subtreeIsActive; - for (usize i = b.children().len(); i >= 1; --i) { - meOrsiblingToRightIsFull |= visitedWholeBox.has(&(b.children()[i - 1])); - _pushFlagsVisitedWholeBox(b.children()[i - 1], meOrsiblingToRightIsFull); - } - } - - void pushFlagsVisitedWholeBox(Box &root) { - _pushFlagsVisitedWholeBox(root, visitedWholeBox.has(&root)); - visitedWholeBox.clear(); - } - void enterMonolithicBox() { monolithicCount++; } @@ -92,28 +66,6 @@ struct FragmentationContext { bool allowBreak() { return not hasInfiniteDimensions() and monolithicCount == 0; } - - usize getStartPosition(Box &box) { - if (not layoutUntilComitted.has(&box)) - return 0; - else - return layoutUntilComitted.get(&box); - } - - usize getDiscoveredEndPosition(Box &box) { - // FIXME: this should not exist once all formatting contexts implement fragmentation - if (not layoutUntilNotComitted.has(&box)) - return 0; - return layoutUntilNotComitted.get(&box); - } - - void setDiscoveredEndPosition(Box &box, usize pos) { - layoutUntilNotComitted.put(&box, pos); - } - - void setLaidOutEndPosition(Box &box, usize pos) { - layoutUntilComitted.put(&box, pos); - } }; } // namespace Vaev::Layout diff --git a/src/vaev-layout/input_output.h b/src/vaev-layout/input_output.h index 7dacef5..4476c19 100644 --- a/src/vaev-layout/input_output.h +++ b/src/vaev-layout/input_output.h @@ -5,57 +5,6 @@ namespace Vaev::Layout { -/// Input to the layout algorithm. -struct Input { - /// Parent fragment where the layout will be attached. - MutCursor fragment = nullptr; - IntrinsicSize intrinsic = IntrinsicSize::AUTO; - Math::Vec2> knownSize = {}; - Vec2Px position = {}; - Vec2Px availableSpace = {}; - Vec2Px containingBlock = {}; - - // TODO: instead of stringing this around, maybe change this (and check method of fragmentainer) to a - // "availableSpaceInFragmentainer" parameter - Vec2Px ancestralsBorderPadding = {}; - - Input withFragment(MutCursor f) const { - auto copy = *this; - copy.fragment = f; - return copy; - } - - Input withIntrinsic(IntrinsicSize i) const { - auto copy = *this; - copy.intrinsic = i; - return copy; - } - - Input withKnownSize(Math::Vec2> size) const { - auto copy = *this; - copy.knownSize = size; - return copy; - } - - Input withPosition(Vec2Px pos) const { - auto copy = *this; - copy.position = pos; - return copy; - } - - Input withAvailableSpace(Vec2Px space) const { - auto copy = *this; - copy.availableSpace = space; - return copy; - } - - Input withContainingBlock(Vec2Px block) const { - auto copy = *this; - copy.containingBlock = block; - return copy; - } -}; - // NOTE: all these comments might be erased once we are secure on the strucutre and have proper documentation // TODO: consider adding classification for breakpoints, what would make appeal computing easier and less error prone @@ -63,8 +12,7 @@ struct Breakpoint { static usize const AVOID_APPEAL = 1; static usize const CLASS_B_APPEAL = 2; - // only children with (index < endChildren) will be laid out - usize endChildren = 0; + usize endIdx = 0; // appeal = 0: an overflow occured; we want the earliest breakpoint possible // appeal > 0: no overflow occured: we want the latest breakpoint possible with biggest appeal @@ -74,6 +22,8 @@ struct Breakpoint { // attribution could should be encapsulated in this class, instead of exposed to code usize appeal = 0; + Vec child = {}; + void overrideIfBetter(Breakpoint &&BPWithMoreContent) { if (appeal == 0 and BPWithMoreContent.appeal == 0) return; @@ -83,21 +33,26 @@ struct Breakpoint { } void repr(Io::Emit &e) const { - e("(endChildren: {} appeal: {})", endChildren, appeal); + e("(end: {} appeal: {}", endIdx, appeal); + if (child.len() == 0) + e("; no child)"); + else + e("; child : {})", child[0]); } - static Breakpoint buildForced(usize endIndex) { + static Breakpoint buildForced(usize endIdx) { return Breakpoint{ - .endChildren = endIndex, + .endIdx = endIdx, // since this is a FORCED break, it will have maximum appeal .appeal = Limits::MAX }; } - static Breakpoint buildFromChild(Breakpoint childBreakpoint, usize endIndex, bool isAvoid) { + static Breakpoint buildFromChild(Breakpoint &&childBreakpoint, usize endIdx, bool isAvoid) { Breakpoint b{ - .endChildren = endIndex, - .appeal = childBreakpoint.appeal + .endIdx = endIdx, + .appeal = childBreakpoint.appeal, + .child = {std::move(childBreakpoint)} }; if (isAvoid) @@ -106,9 +61,9 @@ struct Breakpoint { return b; } - static Breakpoint buildClassB(usize endIndex, bool isAvoid) { + static Breakpoint buildClassB(usize endIdx, bool isAvoid) { Breakpoint b{ - .endChildren = endIndex, + .endIdx = endIdx, .appeal = isAvoid ? AVOID_APPEAL : CLASS_B_APPEAL }; @@ -118,12 +73,99 @@ struct Breakpoint { static Breakpoint buildOverflow() { // this is a placeholder breakpoint and should be overriden return { - .endChildren = 0, + .endIdx = 0, .appeal = 0 }; } }; +struct BreakpointTraverser { + MutCursor prevIteration, currIteration; + + BreakpointTraverser( + MutCursor prev = nullptr, + MutCursor curr = nullptr + ) : prevIteration(prev), currIteration(curr) {} + + BreakpointTraverser traverseInsideUsingIthChild(usize i) { + BreakpointTraverser deeperBPT; + if (prevIteration and prevIteration->child.len() > 0 and i + 1 == prevIteration->endIdx) { + deeperBPT.prevIteration = &prevIteration->child[0]; + } + + if (currIteration and currIteration->child.len() > 0 and i + 1 == currIteration->endIdx) { + deeperBPT.currIteration = &currIteration->child[0]; + } + + return deeperBPT; + } + + Opt getStart() { + if (prevIteration == nullptr) + return NONE; + return prevIteration->endIdx - (prevIteration->child.len() ? 1 : 0); + } + + Opt getEnd() { + if (currIteration == nullptr) + return NONE; + return currIteration->endIdx; + } +}; + +/// Input to the layout algorithm. +struct Input { + /// Parent fragment where the layout will be attached. + MutCursor fragment = nullptr; + IntrinsicSize intrinsic = IntrinsicSize::AUTO; + Math::Vec2> knownSize = {}; + Vec2Px position = {}; + Vec2Px availableSpace = {}; + Vec2Px containingBlock = {}; + + BreakpointTraverser bt = {}; + + // TODO: instead of stringing this around, maybe change this (and check method of fragmentainer) to a + // "availableSpaceInFragmentainer" parameter + Vec2Px ancestralsBorderPadding = {}; + + Input withFragment(MutCursor f) const { + auto copy = *this; + copy.fragment = f; + return copy; + } + + Input withIntrinsic(IntrinsicSize i) const { + auto copy = *this; + copy.intrinsic = i; + return copy; + } + + Input withKnownSize(Math::Vec2> size) const { + auto copy = *this; + copy.knownSize = size; + return copy; + } + + Input withPosition(Vec2Px pos) const { + auto copy = *this; + copy.position = pos; + return copy; + } + + Input withAvailableSpace(Vec2Px space) const { + auto copy = *this; + copy.availableSpace = space; + return copy; + } + + Input withContainingBlock(Vec2Px block) const { + auto copy = *this; + copy.containingBlock = block; + return copy; + } +}; + struct Output { // size of subtree maximizing displayed content while respecting // - endchild constraint or diff --git a/src/vaev-layout/layout.cpp b/src/vaev-layout/layout.cpp index 8138e5f..de6d47d 100644 --- a/src/vaev-layout/layout.cpp +++ b/src/vaev-layout/layout.cpp @@ -177,7 +177,7 @@ Opt shouldAbortFragmentingBeforeLayout(FragmentationContext &fc, Input i } void maybeSetMonolithicBreakpoint(FragmentationContext &fc, bool isMonolticDisplay, bool childCompletelyLaidOut, usize boxChildrenLen, Opt &outputBreakpoint) { - if (not(fc.monolithicCount == 1 and isMonolticDisplay) or not fc.hasInfiniteDimensions()) + if (not(fc.monolithicCount == 1 and isMonolticDisplay) or fc.hasInfiniteDimensions()) return; if (not childCompletelyLaidOut) @@ -185,7 +185,7 @@ void maybeSetMonolithicBreakpoint(FragmentationContext &fc, bool isMonolticDispl // NOTE: wont abstract this since this is currently a workaround since we dont have fragmentation for table,flex Breakpoint bottomOfContentBreakForTopMonolitic{ - .endChildren = boxChildrenLen, + .endIdx = boxChildrenLen, .appeal = 2 }; @@ -220,8 +220,7 @@ Output layout(Tree &tree, Box &box, Input input) { input.position = input.position + borders.topStart() + padding.topStart(); input.ancestralsBorderPadding = input.ancestralsBorderPadding + borders.bottomEnd() + padding.bottomEnd(); - usize startAt = tree.fc.allowBreak() ? tree.fc.getStartPosition(box) : 0; - + usize startAt = tree.fc.allowBreak() ? input.bt.getStart().unwrapOr(0) : 0; if (tree.fc.isDiscoveryMode) { bool isMonolticDisplay = box.style->display == Display::Inside::FLEX or @@ -255,18 +254,13 @@ Output layout(Tree &tree, Box &box, Input input) { out.size = size + padding.all() + borders.all(); - if (tree.fc.allowBreak()) { - usize discoveredEndPosition = out.breakpoint ? out.breakpoint.unwrap().endChildren : box.children().len(); - tree.fc.setDiscoveredEndPosition(box, discoveredEndPosition); - } - if (isMonolticDisplay) tree.fc.leaveMonolithicBox(); return out; } else { Opt stopAt = tree.fc.allowBreak() - ? Opt{tree.fc.getDiscoveredEndPosition(box)} + ? input.bt.getEnd() : NONE; auto parentFrag = input.fragment; @@ -291,9 +285,6 @@ Output layout(Tree &tree, Box &box, Input input) { parentFrag->add(std::move(currFrag)); } - if (tree.fc.allowBreak()) - tree.fc.setLaidOutEndPosition(box, out.lastChildCompletelyLaidOut ? stopAt.unwrap() : stopAt.unwrap() - 1); - return Output{ .size = size, .completelyLaidOut = out.completelyLaidOut