#Layout warning from tinymst

1 messages · Page 1 of 1 (latest)

uneven heart
#
#let __state = state("typki-data", ("deck": (("", false),), "note-type": (none,), "existing-guids": ("",)))

#let note(guid) = {
  context {
    let data = __state.get()
    if guid in data.at("existing-guids") {
      panic("Guid " + guid + "already exists!")
    } else {
      data.at("existing-guids").push(guid)
      __state.update(data)
    }
  }
}

#note("test1")
#note("test2")
#note("test3")
#note("test4")
#note("test5")

This code produces this warning:
layout did not converge within 5 attempts Hint: check if any states or queries are updating themselves
Any idea why?

#

@wispy dawn @cyan hearth @hasty mural I opened a post here, since it appears to be a more difficult issue.

wispy dawn
#

good choice

cyan hearth
#

did you try the solution

#

with __state.update(x => y)

uneven heart
#

@cyan hearth doing it like that shuts down the panic.

This should panic, but does not:

#let __state = state("typki-data", ("deck": (("", false),), "note-type": (none,), "existing-guids": ("",)))

#let note(guid) = {
  __state.update(state => {
    if guid in state.existing-guids {
      panic("die")
    } else {
      state.existing-guids.push(guid)
    }
    state
  })
}

#note("test1")
#note("test2")
#note("test3")
#note("test4")
#note("test4")
hasty mural
#

As mentioned in the chat "the pattern value = state.get() and then state.update(...) should always be avoided" -- @remote loom says that operation always costs a layout iteration and I'm trying to see and understand why (and that also checks out, five notes needed to hit the warning)

uneven heart
#

It seems panics do not work in __state.update(x =>y):

#let __state = state("typki-data", ("deck": (("", false),), "note-type": (none,), "existing-guids": ("",)))

#let note(guid) = {
  __state.update(state => {
    panic()
    if guid in state.existing-guids {
      panic("die")
    } else {
      state.existing-guids.push(guid)
    }
    state
  })
}

#note("test4")
hasty mural
#

yeah that's a bit of a quirk in Typst (it can swallow errors and try again in the next layout iteration). I think you need to split the existance check from the update

#

?r

#let __state = state("typki-data", ("deck": (("", false),), "note-type": (none,), "existing-guids": ("",)))

#let note(guid) = {
  context {
    let data = __state.get()
    if guid in data.at("existing-guids") {
      panic("Guid " + guid + "already exists!")
    }
  }
  __state.update(map => {
    map.existing-guids.push(guid)
    map
  })
}

#note("test1")
#note("test2")
#note("test3")
#note("test4")
#note("test5")

#context __state.get()
hasty mural
# hasty mural As mentioned in the chat "the pattern value = state.get() and then state.update(...

I think I see why, or have an intuition for why. Having the update depend on a contexted value (the state.get()) delays the update so much that it only takes effect for other code in the next layout iteration.
What we want to have is a series of state.update objects in the document that can all be added together (their combined result computed) so that their result is input to the context that needs to inspect the state.
The state update result is not ready in time for that (in the current layout iteration) if there are updates that depend on contexted values.

Like tinger said, like the docs say, we should put state.update() outside context to help us do this right.

uneven heart
remote loom
#

Updates are an artifact of layout, and layout is pure

#

So layout input can't change during layout

#

So basically Typst collects updates during layout, but all state values are the same as when layout started. No updates are applied at all in the first iteration

#

Then at the end Typst checks if something changed, if so run again with the new values, which stay the same until the end of the next iteration

#

If you use more than one state.update(state.get()), there will be one iteration per update, cuz in the first iteration state.get() returns the initial value (no updates are applied), then in the second iteration state.get() is updated with the value in the first update and stays that way for all other updates, in the third iteration one more update will change and so on

#

But if you use state.update(x => y) Typst can just apply all updates in a row in the second iteration

#

Cuz Typst is then sure that the new value only depends on the previous

#

An arbitrary value from state.get() can come from anywhere so it can't optimize that

#

Maybe not the best explanation but hopefully that clears it up a bit

hasty mural
#

thanks!