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

Roadmap for Embedded Support #557

Open
8 tasks
filip-sakel opened this issue Jun 12, 2024 · 3 comments
Open
8 tasks

Roadmap for Embedded Support #557

filip-sakel opened this issue Jun 12, 2024 · 3 comments

Comments

@filip-sakel
Copy link
Contributor

filip-sakel commented Jun 12, 2024

Embedded mode was recently introduced as a subset of Swift for constrained environments, such as the Web where bundle size has to be really small. With Embedded Wasm, we can get binary sizes of ~100 KB for simple apps. Though not perfect, it’s still a huge improvement over the > 7 MB of current Tokamak web apps.

However, there are some challenges with the SwiftUI public API and Tokamak’s internal implementation. For one, SwiftUI publicly uses KeyPath (e.g. for custom environment values) which are unavailable in Embedded. This is a complex issue and will be discussed below in more depth. SwiftUI also uses metatype identifiers for identity and type metadata for AnyHashable. Further, Tokamak relies on type metadata internally. We use reflection to find dynamic properties in views. We make heavy use of existentials for type erasure and to change between different implementation (e.g. AnyColorBox). And we also rely on type casting (often with existentials) for things like finding primitive views in the FiberRenderer. Finally, Tokamak relies on modules like OpenCombine and Foundation which do not currently support Embedded mode.

There are thankfully a couple of solutions. Most type erasers can be rewritten to be backed by closures instead of existentials. When existentials are used to provide different implementations, we can simply write an enum with all possible implementations which should also be more efficient. Also, instead of relying on type casting to traverse the view tree, we can add requirements directly to the View protocol and rewrite the renderer. Finally, features like reflection and metatype object identifiers can be replaced with macros. To retain compatibility with SwiftUI (where View conformance doesn’t require a macro), we could also use a pre-build plugin that automatically adds the macro attribute to View and other stateful-type declarations.

KeyPaths are quite complex to implement. They use non-final classes which are unsupported in Embedded Swift and it’s unclear if the metadata they require will ever be part of Embedded mode either. Instead, when we encounter a key path in .environmentValue(\.myKeyPath, newValue) we could transform myKeyPath into a custom object that is Hashable and offers a setter and getter. This transformation could happen with a macro that could be implicitly added by the aforementioned pre-build plugin. This is by no means a clean solution so please feel free to propose other solutions.

To make all these changes, a lot of internal components will have to change. Though it will be challenging, it is also an opportunity to simplify the codebase, which among other issues has two renderers. I propose that based on the current FiberRenderer, we re-architect a simple renderer that not only works in Embedded, but also goes further in terms of implementing SwiftUI’s layout views and features (such as alignment guides). This rewrite should also strive to have more comprehensive testing.

I currently have a prototype implementation of the renderer itself with support for Embedded and more expansive layout operations than the current Fiber renderer. There are a lot of steps left:

  • Support targeted updates (instead of remounting the entire node tree)
  • 2. Expose different Layout operations as modifiers
  • 3. Implement overlay and background layouts
  • 4. Implement GeometryReader with coordinate systems
  • 5. Implement preferences and layout values
  • 6. Add support for visual effects (brightness, shadow, etc.)
  • 7. Write macros for types using dynamic properties, metatype identity, and for transforming key paths
  • 8. Rewire Button, Shape and other common views to use the new renderer
@mortenbekditlevsen
Copy link
Contributor

What about String? That's also not available in embedded Swift, is it?

@kkebo
Copy link

kkebo commented Jun 12, 2024

@mortenbekditlevsen No, String is already available in the main snapshot toolchain.

@ahti
Copy link
Contributor

ahti commented Aug 13, 2024

@filip-sakel do you have the prototype implementation up somewhere? I'd be interested in having a look at how targeted updates (and preferences/layout values) could fit in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

4 participants