engine.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. package app
  2. import (
  3. "context"
  4. "net/url"
  5. "sort"
  6. "sync"
  7. "time"
  8. "github.com/maxence-charriere/go-app/v9/pkg/errors"
  9. )
  10. type engine struct {
  11. // The number of frame per seconds.
  12. FrameRate int
  13. // The page.
  14. Page Page
  15. // Reports whether the engine runs on server-side.
  16. IsServerSide bool
  17. // The storage use as local storage.
  18. LocalStorage BrowserStorage
  19. // The storage used as session storage.
  20. SessionStorage BrowserStorage
  21. // The function used to resolve static resource paths.
  22. StaticResourceResolver func(string) string
  23. // The body of the page.
  24. Body HTMLBody
  25. // The action handlers that are not associated with a component and are
  26. // executed asynchronously.
  27. ActionHandlers map[string]ActionHandler
  28. initOnce sync.Once
  29. startOnce sync.Once
  30. closeOnce sync.Once
  31. wait sync.WaitGroup
  32. componentUpdateMutex sync.RWMutex
  33. dispatches chan Dispatch
  34. componentUpdates map[Composer]bool
  35. componentUpdateQueue []componentUpdate
  36. deferables []Dispatch
  37. actions actionManager
  38. states *store
  39. isFirstMount bool
  40. }
  41. func (e *engine) Context() Context {
  42. return makeContext(e.Body)
  43. }
  44. func (e *engine) Dispatch(d Dispatch) {
  45. if d.Source == nil {
  46. d.Source = e.Body
  47. }
  48. e.dispatches <- d
  49. }
  50. func (e *engine) Emit(src UI, fn func()) {
  51. e.Dispatch(Dispatch{
  52. Mode: Next,
  53. Source: src,
  54. Function: func(ctx Context) {
  55. if fn != nil {
  56. fn()
  57. }
  58. e.componentUpdateMutex.RLock()
  59. compo := getComponent(src)
  60. if canUpdate, ok := e.componentUpdates[compo]; ok && !canUpdate {
  61. e.componentUpdateMutex.RUnlock()
  62. return
  63. }
  64. e.componentUpdateMutex.RUnlock()
  65. for c := compo; c != nil; c = getComponent(c.getParent()) {
  66. e.addComponentUpdate(c)
  67. }
  68. },
  69. })
  70. }
  71. func (e *engine) Handle(actionName string, src UI, h ActionHandler) {
  72. e.actions.handle(actionName, false, src, h)
  73. }
  74. func (e *engine) Post(a Action) {
  75. e.Async(func() {
  76. e.actions.post(a)
  77. })
  78. }
  79. func (e *engine) SetState(state string, v any, opts ...StateOption) {
  80. e.states.Set(state, v, opts...)
  81. }
  82. func (e *engine) GetState(state string, recv any) {
  83. e.states.Get(state, recv)
  84. }
  85. func (e *engine) DelState(state string) {
  86. e.states.Del(state)
  87. }
  88. func (e *engine) ObserveState(state string, elem UI) Observer {
  89. return e.states.Observe(state, elem)
  90. }
  91. func (e *engine) Async(fn func()) {
  92. e.wait.Add(1)
  93. go func() {
  94. fn()
  95. e.wait.Done()
  96. }()
  97. }
  98. func (e *engine) Wait() {
  99. e.wait.Wait()
  100. }
  101. func (e *engine) Consume() {
  102. for {
  103. e.Wait()
  104. select {
  105. case d := <-e.dispatches:
  106. e.handleDispatch(d)
  107. default:
  108. e.handleFrame()
  109. return
  110. }
  111. }
  112. }
  113. func (e *engine) ConsumeNext() {
  114. e.Wait()
  115. e.handleDispatch(<-e.dispatches)
  116. e.handleFrame()
  117. }
  118. func (e *engine) Close() {
  119. e.closeOnce.Do(func() {
  120. e.Consume()
  121. e.Wait()
  122. dismount(e.Body)
  123. e.Body = nil
  124. e.states.Close()
  125. })
  126. }
  127. func (e *engine) PreRender() {
  128. e.Dispatch(Dispatch{
  129. Mode: Update,
  130. Source: e.Body,
  131. Function: func(ctx Context) {
  132. ctx.Src().preRender(e.Page)
  133. },
  134. })
  135. }
  136. func (e *engine) Mount(v UI) {
  137. e.Dispatch(Dispatch{
  138. Mode: Update,
  139. Source: e.Body,
  140. Function: func(ctx Context) {
  141. if e.isFirstMount {
  142. if err := e.Body.(*htmlBody).replaceChildAt(0, v); err != nil {
  143. panic(errors.New("mounting first ui element failed").Wrap(err))
  144. }
  145. e.isFirstMount = false
  146. return
  147. }
  148. if firstChild := e.Body.getChildren()[0]; canUpdate(firstChild, v) {
  149. if err := update(firstChild, v); err != nil {
  150. panic(errors.New("mounting ui element failed").Wrap(err))
  151. }
  152. return
  153. }
  154. if err := e.Body.(*htmlBody).replaceChildAt(0, v); err != nil {
  155. panic(errors.New("mounting ui element failed").Wrap(err))
  156. }
  157. },
  158. })
  159. }
  160. func (e *engine) Nav(u *url.URL) {
  161. if p, ok := e.Page.(*requestPage); ok {
  162. p.ReplaceURL(u)
  163. }
  164. e.Dispatch(Dispatch{
  165. Mode: Update,
  166. Source: e.Body,
  167. Function: func(ctx Context) {
  168. ctx.Src().onComponentEvent(nav{})
  169. },
  170. })
  171. }
  172. func (e *engine) AppUpdate() {
  173. e.Dispatch(Dispatch{
  174. Mode: Update,
  175. Source: e.Body,
  176. Function: func(ctx Context) {
  177. ctx.Src().onComponentEvent(appUpdate{})
  178. },
  179. })
  180. }
  181. func (e *engine) AppInstallChange() {
  182. e.Dispatch(Dispatch{
  183. Mode: Update,
  184. Source: e.Body,
  185. Function: func(ctx Context) {
  186. ctx.Src().onComponentEvent(appInstallChange{})
  187. },
  188. })
  189. }
  190. func (e *engine) AppResize() {
  191. e.Dispatch(Dispatch{
  192. Mode: Update,
  193. Source: e.Body,
  194. Function: func(ctx Context) {
  195. ctx.Src().onComponentEvent(resize{})
  196. },
  197. })
  198. }
  199. func (e *engine) init() {
  200. e.initOnce.Do(func() {
  201. if e.FrameRate <= 0 {
  202. e.FrameRate = 60
  203. }
  204. if e.Page == nil {
  205. u, _ := url.Parse("https://test.go-app.dev")
  206. e.Page = &requestPage{url: u}
  207. }
  208. if e.LocalStorage == nil {
  209. e.LocalStorage = newMemoryStorage()
  210. }
  211. if e.SessionStorage == nil {
  212. e.SessionStorage = newMemoryStorage()
  213. }
  214. if e.StaticResourceResolver == nil {
  215. e.StaticResourceResolver = func(path string) string {
  216. return path
  217. }
  218. }
  219. if e.Body == nil {
  220. body := Body().privateBody(Div())
  221. if err := mount(e, body); err != nil {
  222. panic(errors.New("mounting engine default body failed").Wrap(err))
  223. }
  224. e.Body = body
  225. }
  226. e.dispatches = make(chan Dispatch, 4096)
  227. e.componentUpdates = make(map[Composer]bool)
  228. e.componentUpdateQueue = make([]componentUpdate, 0, 32)
  229. e.deferables = make([]Dispatch, 32)
  230. e.states = newStore(e)
  231. e.isFirstMount = true
  232. for actionName, handler := range e.ActionHandlers {
  233. e.actions.handle(actionName, true, e.Body, handler)
  234. }
  235. })
  236. }
  237. func (e *engine) getCurrentPage() Page {
  238. return e.Page
  239. }
  240. func (e *engine) getLocalStorage() BrowserStorage {
  241. return e.LocalStorage
  242. }
  243. func (e *engine) getSessionStorage() BrowserStorage {
  244. return e.SessionStorage
  245. }
  246. func (e *engine) isServerSide() bool {
  247. return e.IsServerSide
  248. }
  249. func (e *engine) resolveStaticResource(path string) string {
  250. return e.StaticResourceResolver(path)
  251. }
  252. func (e *engine) addComponentUpdate(c Composer) {
  253. if c == nil || !c.Mounted() {
  254. return
  255. }
  256. e.componentUpdates[c] = true
  257. }
  258. func (e *engine) removeComponentUpdate(c Composer) {
  259. delete(e.componentUpdates, c)
  260. }
  261. func (e *engine) preventComponentUpdate(c Composer) {
  262. e.componentUpdateMutex.Lock()
  263. defer e.componentUpdateMutex.Unlock()
  264. e.componentUpdates[c] = false
  265. }
  266. func (e *engine) addDeferable(d Dispatch) {
  267. e.deferables = append(e.deferables, d)
  268. }
  269. func (e *engine) start(ctx context.Context) {
  270. e.startOnce.Do(func() {
  271. frameDuration := time.Second / time.Duration(e.FrameRate)
  272. currentFrameDuration := frameDuration
  273. frames := time.NewTicker(frameDuration)
  274. cleanups := time.NewTicker(time.Minute)
  275. defer cleanups.Stop()
  276. for {
  277. select {
  278. case <-ctx.Done():
  279. return
  280. case d := <-e.dispatches:
  281. if currentFrameDuration != frameDuration {
  282. currentFrameDuration = frameDuration
  283. frames.Reset(currentFrameDuration)
  284. }
  285. e.handleDispatch(d)
  286. case <-frames.C:
  287. e.handleFrame()
  288. if len(e.dispatches) == 0 {
  289. if currentFrameDuration < time.Hour {
  290. currentFrameDuration *= 2
  291. }
  292. frames.Reset(currentFrameDuration)
  293. }
  294. case <-cleanups.C:
  295. e.actions.closeUnusedHandlers()
  296. e.states.Cleanup()
  297. }
  298. }
  299. })
  300. }
  301. func (e *engine) handleDispatch(d Dispatch) {
  302. switch d.Mode {
  303. case Update:
  304. d.do()
  305. e.addComponentUpdate(getComponent(d.Source))
  306. case Defer:
  307. e.deferables = append(e.deferables, d)
  308. case Next:
  309. d.do()
  310. }
  311. }
  312. func (e *engine) handleFrame() {
  313. e.handleComponentUpdates()
  314. e.handleDeferables()
  315. }
  316. func (e *engine) handleComponentUpdates() {
  317. e.componentUpdateMutex.Lock()
  318. defer e.componentUpdateMutex.Unlock()
  319. for c, canUpdate := range e.componentUpdates {
  320. if c.Mounted() && canUpdate {
  321. e.componentUpdateQueue = append(e.componentUpdateQueue, componentUpdate{
  322. component: c,
  323. priority: getComponentPriority(c),
  324. })
  325. }
  326. }
  327. sort.Slice(e.componentUpdateQueue, func(i, j int) bool {
  328. return e.componentUpdateQueue[i].priority < e.componentUpdateQueue[j].priority
  329. })
  330. for i, u := range e.componentUpdateQueue {
  331. if _, ok := e.componentUpdates[u.component]; !ok || !u.component.Mounted() {
  332. e.removeComponentUpdate(u.component)
  333. e.componentUpdateQueue[i] = componentUpdate{}
  334. continue
  335. }
  336. if err := u.component.updateRoot(); err != nil {
  337. panic(err)
  338. }
  339. e.removeComponentUpdate(u.component)
  340. e.componentUpdateQueue[i] = componentUpdate{}
  341. }
  342. e.componentUpdateQueue = e.componentUpdateQueue[:0]
  343. }
  344. func (e *engine) handleDeferables() {
  345. for i := range e.deferables {
  346. e.deferables[i].do()
  347. e.deferables[i] = Dispatch{}
  348. }
  349. e.deferables = e.deferables[:0]
  350. }
  351. func getComponent(n UI) Composer {
  352. for node := n; node != nil; node = node.getParent() {
  353. if c, isCompo := node.(Composer); isCompo {
  354. return c
  355. }
  356. }
  357. return nil
  358. }
  359. func getComponentPriority(c Composer) int {
  360. depth := 1
  361. for parent := c.getParent(); parent != nil; parent = parent.getParent() {
  362. depth++
  363. }
  364. return depth
  365. }
  366. type componentUpdate struct {
  367. component Composer
  368. priority int
  369. }