Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor NavigationView #446

Merged
merged 1 commit into from
Sep 16, 2021
Merged

Conversation

ezraberch
Copy link
Member

This PR refactors NavigationView in the TokamakStaticHTML renderer. While this should have no functional effect, it fixes the crash in #445. This is likely due to an underlying bug in swiftwasm.

@ezraberch ezraberch added bug Something isn't working DOM/HTML renderer Tokamak in the browser labels Sep 10, 2021
@MaxDesiatov
Copy link
Collaborator

Just a quick note: if the workaround in #367 (comment) also makes the crash go away (I didn't have time to verify that myself yet), this isn't a bug in SwiftWasm, but the fact that Tokamak consumes a lot of stack memory, which causes a stack overflow.

@jhoughjr
Copy link

jhoughjr commented Sep 10, 2021 via email

@ezraberch
Copy link
Member Author

Just a quick note: if the workaround in #367 (comment) also makes the crash go away (I didn't have time to verify that myself yet), this isn't a bug in SwiftWasm, but the fact that Tokamak consumes a lot of stack memory, which causes a stack overflow.

That does make it work, but here's why I think there's still an underlying wasm issue.

If the problematic code is moved to an unreachable code path...

              if toolbarContent.isEmpty {
                HTML("div", ["class": "_tokamak-toolbar-content _tokamak-toolbar-leading"]) {
                  title.font(.headline)
                }
              } else if true {
                HTML("div", ["class": "_tokamak-toolbar-content _tokamak-toolbar-leading"]) {
                  title.font(.headline)
                }
              } else {
                HTML("div", ["class": "_tokamak-toolbar-content _tokamak-toolbar-leading"]) {
                  items(from: toolbarContent, at: .navigationBarLeading)
                  items(from: toolbarContent, at: .navigation)
                  title
                    .font(.headline)
                  items(from: toolbarContent, at: .navigationBarTrailing)
                  items(from: toolbarContent, at: .automatic, .primaryAction)
                  items(from: toolbarContent, at: .destructiveAction)
                    .foregroundColor(.red)
                }
                HTML("div", ["class": "_tokamak-toolbar-content _tokamak-toolbar-center"]) {
                  items(from: toolbarContent, at: .principal, .status)
                }
                HTML("div", ["class": "_tokamak-toolbar-content _tokamak-toolbar-trailing"]) {
                  items(from: toolbarContent, at: .cancellationAction)
                  items(from: toolbarContent, at: .confirmationAction)
                    .foregroundColor(.accentColor)
                }
              }

NavigationView.swift:39:17: warning: will never be executed

...then the stack overflow remains. However, commenting out the code in the unreachable block will eliminate the overflow.

@MaxDesiatov
Copy link
Collaborator

MaxDesiatov commented Sep 14, 2021

My hunch that's how result builders work. if statement there is transformed into an expression, which allocates all of the view values in both branches to yield a value of _ConditionalContent. In that sense there's no truly "unreachable" blocks with result builders, the only way to prevent them from being eagerly evaluated and blowing up the stack is to hide them in a separate View type, or to comment them out completely.

Creating a new View type makes that view "lazy", meaning that its body content will only evaluate when that view is supposed to be rendered.

I may be wrong here. Just looking at the proposal, it doesn't say anything anywhere about if branches being eager or lazy. We may be able to come up with a separate test case that proves this though. Something like

if true {
  Foo()
} else {
  Bar()
}

and then both Foo and Bar initializers should have a print statement in them to show which of the branches are evaluated. Sorry, I'm a bit limited with time this week to try it myself. Just spitballing some ideas here.

@ezraberch
Copy link
Member Author

To test your theory, I used the following function:

  func printAndReturnText(_ text: String) -> Text {
    print(text)
    return Text(text)
  }

Then I added a call in each of the three code paths. With TokamakDemo, I get just the second code path. With the minimal test case, I get no print statements at all.

@MaxDesiatov
Copy link
Collaborator

Hm, fair enough. @kateinoigakukun do you have any ideas? Could this be something related to how function builders work, or is there something else going on that isn't a simple stack overflow?

Copy link
Collaborator

@MaxDesiatov MaxDesiatov left a comment

Choose a reason for hiding this comment

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

On a second thought, merging this as a temporary solution is definitely worth it 👍

@kateinoigakukun
Copy link
Contributor

Sorry for slow response. I'll take a look later but it's ok to merge for now.

@ezraberch ezraberch merged commit 8788fd6 into TokamakUI:main Sep 16, 2021
@revolter
Copy link

it fixes the crash in #445

For me, this PR didn't actually fix the crash in #445.

@revolter
Copy link

revolter commented Jan 2, 2022

For me, this PR didn't actually fix #445.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working DOM/HTML renderer Tokamak in the browser
Development

Successfully merging this pull request may close these issues.

5 participants