1
0

_App.swift 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. // TODO: This could possibly be renamed to ``SceneGraph`` now that that's basically the role
  2. // it has taken on since introducing scenes.
  3. /// A top-level wrapper providing an entry point for the app. Exists to be able to persist
  4. /// the view graph alongside the app (we can't do that on a user's ``App`` implementation because
  5. /// we can only add computed properties).
  6. @MainActor
  7. class _App<AppRoot: App> {
  8. /// The app being run.
  9. let app: AppRoot
  10. /// An instance of the app's selected backend.
  11. let backend: AppRoot.Backend
  12. /// The root of the app's scene graph.
  13. var sceneGraphRoot: AppRoot.Body.Node?
  14. /// Cancellables for observations of the app's state properties.
  15. var cancellables: [Cancellable]
  16. /// The root level environment.
  17. var environment: EnvironmentValues
  18. /// The dynamic property updater for ``app``.
  19. var dynamicPropertyUpdater: DynamicPropertyUpdater<AppRoot>
  20. /// Wraps a user's app implementation.
  21. init(_ app: AppRoot, backend: AppRoot.Backend) {
  22. self.backend = backend
  23. self.app = app
  24. self.environment = EnvironmentValues(backend: backend)
  25. self.cancellables = []
  26. dynamicPropertyUpdater = DynamicPropertyUpdater(for: app)
  27. }
  28. func refreshSceneGraph() {
  29. // TODO: Do we have to update dynamic properties on state changes?
  30. // We can probably get away with only doing it when the root
  31. // environment changes.
  32. dynamicPropertyUpdater.update(app, with: environment, previousValue: nil)
  33. if let sceneGraphRoot {
  34. let result = sceneGraphRoot.updateNode(app.body, environment: environment)
  35. if let backend = backend as? any BackendFeatures.ApplicationMenus {
  36. backend.setApplicationMenu(
  37. result.preferences.commands.resolve(),
  38. environment: environment
  39. )
  40. }
  41. sceneGraphRoot.update(
  42. backend: backend,
  43. environment: environment
  44. )
  45. }
  46. }
  47. /// Runs the app using the app's selected backend.
  48. func run() {
  49. backend.runMainLoop { [self] in
  50. let baseEnvironment = EnvironmentValues(backend: backend)
  51. environment = backend.computeRootEnvironment(
  52. defaultEnvironment: baseEnvironment
  53. )
  54. dynamicPropertyUpdater.update(app, with: environment, previousValue: nil)
  55. forEachField(of: app) { name, _, fieldValue in
  56. #if DEBUG
  57. if name == "state", fieldValue is ObservableObject {
  58. logger.warning(
  59. """
  60. the App.state protocol requirement has been removed in favour of \
  61. SwiftUI-style @State annotations; decorate \(AppRoot.self).state \
  62. with the @State property wrapper to restore previous behaviour
  63. """
  64. )
  65. }
  66. #endif
  67. guard let value = fieldValue as? any ObservableProperty else {
  68. return // i.e. continue
  69. }
  70. let cancellable =
  71. value.didChange.observeAsUIUpdater(backend: backend) { [weak self] in
  72. self?.refreshSceneGraph()
  73. }
  74. cancellables.append(cancellable)
  75. }
  76. let rootNode = AppRoot.Body.Node(
  77. from: app.body,
  78. backend: backend,
  79. environment: environment
  80. )
  81. backend.setRootEnvironmentChangeHandler {
  82. self.environment = self.backend.computeRootEnvironment(
  83. defaultEnvironment: baseEnvironment
  84. )
  85. self.refreshSceneGraph()
  86. }
  87. let result = rootNode.updateNode(nil, environment: environment)
  88. // Update application-wide menu
  89. if let backend = backend as? any BackendFeatures.ApplicationMenus {
  90. backend.setApplicationMenu(
  91. result.preferences.commands.resolve(),
  92. environment: environment
  93. )
  94. }
  95. rootNode.update(backend: backend, environment: environment)
  96. self.sceneGraphRoot = rootNode
  97. }
  98. }
  99. }