4 ms·
I haven't worked with Qt so I don't know how the API looks, But on a complex app, to see "dirty bitflags", "intermediate values", "redrew the UI in the middle
by valand 4y ago
I haven't worked with Qt so I don't know how the API looks,
But on a complex app, to see "dirty bitflags", "intermediate values", "redrew the UI in the middle of mutating document state", I feel what you feel, and I want to share what helps me and my team overcome this kind of complexity.
This happened a few years ago to my team. We alleviated this with several solutions. The most effective solutions is to 1.) separate UI logic/compositive component from UI visual/appearance component, 2.) separate UI component from actors.
1.) Separating logic from visual helps developers define problems separately, especially those around the required states, step-functions, and lifecycle. We made this separation because of three things: 1.) We observed that our programmers are simply divided into these two categories, 2.) The dependencies of these two sets of components are simply different (e.g. logic --depends-on-> actors and ownership/lifetime management modules, while visuals depends on DOM, rendering, setting up callbacks), 3.) It is actually good to have a high cohesion between UI and logic modules. You may want to use the same UI for a slightly different logic, and vice versa.
2.) Separating UI component from actors helps a lot with decoupling state changes and re-rendering, and actually gives developer a chance to separate/abstract/generalize concerns when things have gotten too complex to be written in a UI component.
Actors is what I call any kinds of data that can act, have their own agency. It can be what you call a service, worker. It can have a Timer-based or queue-based internal lifecycle-management that does not require interaction from user (very useful for things like updating background data at interval)
UI component can both "own" or "borrow" actors. "owned" actors dies when its parent component is destroyed, while "borrowed" actors does not die when its borrowing component is destroyed.
We decouple actors from UI component and set up bridge between them. UI component can signal actors by function call, and actors can get back to the UI component by using either a return value or event/callback system.
And the last but not least, writing app in a smartly typed language combined with functional domain modelling helps a lot (see about it here https://www.youtube.com/watch?v=Up7LcbGZFuo https://www.youtube.com/watch?v=Up7LcbGZFuo).
- nyanpasu64 4y agoSorry, I don't understand what you're getting at. When timers or user actions are triggered, what code runs, where is the state it modifies located (next to the specific timer/action, within a module or dialog's object, or globally), how does the function determine which parts of the UI to reload from state, and when does it reload the UI? (Is this explained in the video or not? I haven't watched it yet.)
- valand 4y agoAbout the video, I should have made it clear that the video only talks about functional domain modelling. Before we got into where state should be and when is UI reload/rerender triggered. I'm going to tell you a little imaginary problem. Imagine you are building an application for downloading several huge files. 1.) This app must have a download page to trigger the downloading of various files as well as monitor their statuses. 2.) a system to manage multiple downloads. It can queue multiple downloads but only one should run at a time. But, regardless of the download page is open or not, the downloader should run its download. 3.) a persistent notification system for the app to notify the user (e.g. if a download succeeded or failed). This notification should be persisting, meaning that if the user does not dismiss a notification, it will stay there. The UI for the notification system looks like a smart phone's one. 4.) There are several other pages in the app. Before we got into asking where state should be placed, we should examine what actors are there. There are the "downloader", the "download page", the "notification system", the "notification UI". Because we have these several actors, we need to assume that all of these have several local states. All of these actors need to be in the component tree, but not necessarily bound to the rendering lifecycle. The dependency arrows look like this: - "download page"=>"downloader"=>"notification system" - "notification UI" => "notification system" These dependency arrows form the component tree automatically. Now, let's examine the solution: - The state that tracks the notification should be in the notification system. The user-facing message in a notification item should be a copy that is put into the memory of the notification system. - The state that tracks the queue of multiple downloads and the function that schedule the downloads should live in the downloader. These scheduler will have its own timer-like mechanism to notify itself that it needs to run other task if one is finished. - The download page "listens" to events and "borrows" data from the "downloader". So if the downloader makes a change, the downloader page will change too. - Last, the notification UI. The notification UI lives longer than the download page, because the user can switch between page but the notification UI stays on. The notification UI listens to the notification system for changes in its state and borrows its state. - If you pay attention to the dependency arrow, notification UI and and download page both are the descendant of the notification system, but only notification UI react to "change-signal" from the notification system. Download page should not react to any signal from notification UI (e.g. no rerender). - This is an indicator that the notification system must not be bound into the re-render lifecycle of the tree of components. Notification UI should explicitly subscribe to the notification system in order to allow other page to ignore the notification system. In JavaScript/TypeScript it is pretty easy to implement a callback-based event emitter that can be attached/detached. - The notification system and the downloader is what I call an actor that can, but not always, be bound into the render lifecycle. It lives with the component that spawns and destroy it, but is detached from it.
- phibz 4y agoYou're probably going to seriously confuse someone familiar with the "actor pattern". Look it up.
- valand 4y agoI realize that I probably contribute in overloading the term actor. This "thing" is partially influenced by actor pattern. The other influences are entity component system and Alan Kay's original of OOP.