[HN Gopher] The complexity that lives in the GUI
___________________________________________________________________
The complexity that lives in the GUI
Author : yes_but_no
Score : 304 points
Date : 2021-02-14 10:41 UTC (12 hours ago)
(HTM) web link (blog.royalsloth.eu)
(TXT) w3m dump (blog.royalsloth.eu)
| dale_glass wrote:
| What kind of GUI app has a performance problem with the message
| bus?
|
| We're in the age of 4K, 60 FPS rendering. If any GUI application
| has a message bus that's strained enough to impact performance,
| then either the application isn't made for humans (because if all
| that stuff is doing anything it'd result in a screen updating far
| faster than it could be read), or there's some horrible bug
| somewhere that produces a flood.
| rwmj wrote:
| In reality none, but developers often think it's not a "real"
| message bus unless you have to install it on a separate cluster
| of machines (like AMQP), or it breaks your desktop randomly
| (dbus). The idea that a message bus could be part of the
| application and very lightweight is unexpected.
| spion wrote:
| > I still don't know what the proper solution to this problem
| would be. Keep your state manipulations as simple as possible and
| try not to share any data between different models. Every time I
| went forward with some fancy listener-binding mechanisms, I've
| ended up causing subtle circular listener recalculations that
| were extremely hard to debug.
|
| The answer for me has been pervasive use of MobX computeds
| everywhere. See https://mobx.js.org/getting-started
| smhg wrote:
| > There is also another way of making GUIs called Immediate Mode
| that is commonly used for drawing user interfaces in games. In
| this mode the GUI components are no longer subscribing and
| waiting for events to come, but are instead a part of the main
| loop that runs at 60 fps and re-render themselves based on the
| current "global" state.
|
| > Immediate Mode GUIs somehow never reached the critical mass and
| you are probably not going to find it outside of the games
| industry.
|
| Isn't this what React is built on? I think this was part of the
| 'original' selling point by Pete Hunt:
| https://youtu.be/x7cQ3mrcKaY (2013). Around 20:00 in that video
| he compares React with the Doom3 engine.
| [deleted]
| jonathanaird wrote:
| Yes, Flutter does this as well and they're both very popular.
| When done properly, UI is a pure function of state and you have
| some simple mechanism for notifying the UI that state has
| changed.
| millstone wrote:
| How does this work with e.g. a text editor? It doesn't seem
| practical to have your UI be a pure function of an input
| which is 100+ MBs of text and formatting.
| strictfp wrote:
| You can also use the original web page model; the state is on the
| server, every button click generates an action to change state,
| and the updated ui is regenerated fron scratch based on the new
| state.
| jonathanstrange wrote:
| Here is my personal opinion on it (not sure if it's the right
| one, though): Write the GUI independently of the model, let the
| GUI components update and communicate among each other as they
| like, and perform some validation in these GUI components.
| Translate between model and view only at one well-defined point
| at which there is also a final validation at the model side, and
| make sure model and view are only loosely coupled.
|
| Good GUIs have way too many requirements to be controlled (via a
| controller) by the model. As a typical example, the fact that a
| button should only be activated if there is data in a textbox has
| usually nothing to do with the underlying model. The model should
| only ever contain valid and consistent data.
| jayd16 wrote:
| I agree but inevitably you get team members who want to "reduce
| complexity" by removing similar datatypes. One man's clean
| adapter is another's DRY violation.
| bob1029 wrote:
| This is basically our approach with Blazor now. I am not sure
| Microsoft has even picked up on or documented our pattern yet,
| but we do something where:
|
| 1) Define a domain model + service(s) that fundamentally
| addresses the logical business functionality without any notion
| of a specific UI or its stateful nature. This service should be
| written in terms of a singleton and injected as such. This is
| something that should be able to be 100% unit testable, and
| could be exposed as a prototype in a console application or
| back a JSON API if necessary (i.e. pretend you are writing a
| SAAS).
|
| 2) Define UI state service(s) that take dependency on one or
| more domain services. These should be scoped around the logical
| UI activity that is being carried out, and no further. The goal
| is to minimize their extent because as we know more state to
| manage means more pain. These would be injected as Scoped
| dependencies (in our use of Server-Side Blazor), such that a
| unique instance is available per user session. Examples of
| these might be things like LoginService, UserStateService,
| ShoppingCartService, CheckoutService, etc. These services are
| not allowed to directly alter the domain model, and must pass
| through domain service methods on injected members. Keep in
| mind that DI is really powerful, so you can even have a
| hierarchy of these types if you want to better organize UI
| event propagation and handling.
|
| 3) Define the actual UI (razor components). These use the
| @inject statement to pull in the UI state services from 2
| above. In each components' OnRender method, we subscribe to
| relevant update event(s) in the UI service(s) in order to know
| when to redraw the component (i.e. StateHasChanged). Typically,
| we find that components usually only inject one or 2 UI state
| services at a time. Needing to subscribe to many UI state
| events in a single component might be a sign you should create
| a new one to better manage that interaction.
|
| This is our approach for isolating the domain services from the
| actual UI interactions and state around them. We find that with
| this approach, our UI is quite barren in terms of code. It
| really is pure HTML/CSS (with a tiny JS shim) and some minor
| interfacing to methods and properties on the UI state services.
| This is the closest realistic thing I've seen so far in terms
| of the fabled frontend/backend isolation principle. Most of the
| "nasty" happens in the middle tier above, but it is still well-
| organized and easy to test. By enforcing immutability on the
| domain services, we ensure discipline with how the UI state
| services must consume the model. Blazor then goes on to
| eliminate entire classes of ridiculous bullshit by not forcing
| us to produce and then consume an arbitrary wire protocol to
| get to our data.
| pbourke wrote:
| Very interesting - thanks for posting this.
|
| > By enforcing immutability on the domain services, we ensure
| discipline with how the UI state services must consume the
| model
|
| Could you expand on that part? Do you mean that your domain
| services use an append-only approach to manage state?
| bob1029 wrote:
| Not necessarily append-only, but making sure that when we
| get a copy of something from a method - i.e. GetUser(),
| that the UI or its state services cannot cause mutations to
| propagate back down into the store without first going
| through another explicit method like UpdateUserName().
|
| We don't play around with event sourcing right now, but it
| might be feasible at and above the UI state services if we
| wanted to do things like replay a user interaction with our
| app. The only caveat is that our domain services are very
| very messy in terms of side effects, so the practical
| utility is bounded to isolated UI testing.
| UnpossibleJim wrote:
| First, thanks for this response. It's super interesting and
| get's my mind spun up in a lot of directions it wasn't going
| before.
|
| Second, with this type of modeling on the separation of GUIs
| and system, what type of movement do you think there will be
| in Microsoft and Google going towards an even more minimal
| computer, almost entirely reliant on the cloud space for
| compute power. Google's machines are practically there, but
| from what you've mentioned above seems like a more fully
| "realized" approach than Google's.
| kaoD wrote:
| This sounds exactly like what I settled on as React+Redux best
| practices. There's UI logic+data (that belongs in components)
| and business logic+data (that belongs in the Redux store)
| loosely coupled via actions and selectors.
| panic wrote:
| I've found the best way to handle this problem is a variation on
| "lift the state up", but instead of binding synchronous listeners
| to state changes in the model, have these state changes mark any
| involved views as "dirty". Then, after all events have been
| processed for the current run loop, go through all the dirty
| views and call a single update function on each.
|
| For the example given in the article, the update function could
| look something like def View.update():
| if model.lightTurnedOn: self.backgroundColor = red
| else: self.backgroundColor = ibmGray
|
| This way, all view property changes happen in one place, where
| you can read the code and understand how the view will appear in
| each possible state. Circular listener loops are impossible, and
| view properties for animations can even be computed by calling
| update twice (once before and once after the state change).
| BiteCode_dev wrote:
| That's basically what modern JS frontend frameworks do as well.
| I suppose as soon as something becomes too complex, keeping
| track of all interactions is just too complicated and
| rerendering the entire world efficiently looks like an easier
| problem to deal with.
|
| I like the current trend of going back to renderless components
| as well. This way you separate the state changes from the way
| it looks like. Feels like each component is a miniature MVC
| framework with front and a back.
| panic wrote:
| _> I suppose as soon as something becomes too complex,
| keeping track of all interactions is just too complicated and
| rerendering the entire world efficiently looks like an easier
| problem to deal with._
|
| In fact, it can actually be less work, since you're
| coalescing changes into a single update() call rather than
| sprinkling them across observer callbacks. Also, if your
| update function starts running too slowly, you can always
| make it more precise by keeping track of which states have
| changed internally to the view. For example, if setting the
| background color takes a long time for whatever reason, you
| can do something like this: def
| View.update(): if self.lightWasTurnedOn !=
| model.lightTurnedOn: if model.lightTurnedOn:
| self.backgroundColor = red else:
| self.backgroundColor = ibmGray
| self.lightWasTurnedOn = model.lightTurnedOn
|
| Now backgroundColor will only be set if lightTurnedOn
| actually changed since the last update.
| badsectoracula wrote:
| FWIW this is a common approach - you can see it even in Win32
| API's invalidation mode (which goes back to the 80s) where you
| mark parts of the UI as "invalid" and eventually (when you
| start pumping events again in the main loop) the window system
| combines all the invalid areas and sends you a message to paint
| ("validate") the window.
|
| Several toolkits and frameworks provide for "after all other
| events have been processed" hooks/events for such logic, e.g.
| Delphi has the TApplication.OnIdle event and later versions as
| well as Lazarus/FreePascal have dedicated controls for this
| event and "idle timers" meant to be used for updating any
| invalidated parts of the UI after all other events have
| finished. Similarly wxWidgets has wxEVT_UPDATE_UI and i'm
| almost certain that Qt has something similar too - though i
| can't find it now.
| mwcampbell wrote:
| > Immediate Mode GUIs somehow never reached the critical mass and
| you are probably not going to find it outside of the games
| industry.
|
| And that's a good thing, because so far, AFAIK, no one has
| implemented accessibility (e.g. for screen readers) in an
| immediate-mode GUI. I hope to work on that problem sometime soon.
| codeflo wrote:
| I've recently become interested in immediate mode UIs, and find
| that there are surprising similarities to React. In both cases,
| components are just functions that must explicitly render their
| child components and pass down any shared state. It's
| conceptually a very clean way to handle UI state.
|
| However, React introduces a lot of complexity to avoid
| unnecessary DOM updates, which makes me wonder about the
| viability of an immediate mode GUI in the browser using canvas.
| wdfx wrote:
| I've worked on an app with an IMGUI on canvas. It was amazingly
| fast and responsive. Nothing which was built after that to try
| and replace it was anywhere near as performant.
| TeMPOraL wrote:
| Given how IMGUI is a bunch of branches with code that redraws
| the same pixels all the time, 60x per second, I'm still
| surprised this is considered very (if not most) efficient UI.
| It would imply retained-mode UI frameworks are _strongly_
| undershooting their theoretically possible performance.
| flohofwoe wrote:
| See for yourself ;)
|
| https://floooh.github.io/sokol-html5/imgui-highdpi-
| sapp.html
|
| There are also demos for other immediate-mode UI systems on
| the parent page (Nuklear and microui):
|
| https://floooh.github.io/sokol-html5/
|
| As I wrote in another thread, Immediate Mode UI doesn't
| imply how the UI rendering is implemented, only how the API
| works.
| fassssst wrote:
| The problem, as always, is how to make immediate mode stuff
| work with accessibility tools.
|
| The value of essentially creating and mutating a tree structure
| like the DOM is that things like screen readers and UI
| automation tools can read it. With canvas they just see a big
| bitmap.
| [deleted]
| rzzzt wrote:
| These are WebGL/WASM based:
|
| - https://github.com/Erkaman/pnp-gui
|
| - https://github.com/jnmaloney/WebGui
|
| I guess accessibility is what takes the biggest hit with these
| implementations.
| hermitcrab wrote:
| I find that Qt signals and slots works pretty well for managing
| complexity in GUIs. In this case you would connect a signal that
| is emitted when inventory table changes state to a slot that
| changes the appearance in the user avatar. This would probably be
| done in the pane/dialog that contains them both. They 2
| components would remain nicely decoupled.
|
| This approach isn't without it's own challenges of course. For
| example it is sometimes hard to keep track of what is going on in
| complex applications with cascades of signals and slots. Some
| people also hate the fact that signals and slots use auto
| generated code, but I have never really found that to be a
| problem in practise.
| nyanpasu64 wrote:
| If one user interaction triggers a "on user interaction"
| signal, which causes "value changed" signals to fire, is it
| possible that a widget which depends on multiple of these to
| get redrawn multiple times?
|
| I'm optimistic about Qt 6's QProperty (I don't know how it
| compares to FRP or, as someone else mentioned, MobX), but Qt 6
| currently does not have KDE libraries, or Linux themes to fit
| into desktop environments or distros.
| hermitcrab wrote:
| Yes, potentially, if that is how you program it. But
| typically you call QWidget::update() which repaints the
| widget next time through the event loop, if anything changes.
| Calling QWidget::update() several times will usually only
| call one repaint.
|
| One thing you have to watch out for it that programmatic
| changes can fire signals. If you don't want this you have to
| add
| QObject::blockSignals(true);...QObject::blockSignals(false);
| around your call.
| CarVac wrote:
| I agree, but isn't that just a message bus (mentioned in the
| article)?
| hermitcrab wrote:
| I never really thought of it in those terms. But I guess
| signals and slots are just way to publish/subscribe to a
| message bus. It is quite a nice abstraction of it IMHO (which
| is perhaps why it didn't occur to me!).
| joezydeco wrote:
| It is, he's just pointing out that the Qt developers figured
| this out a long time ago. Web developers like to think
| they're pioneers when it comes to this stuff.
| FpUser wrote:
| >"the GUI always ends up being a ridiculous mess"
|
| Well no. There are applications with nice well thought out GUIs.
|
| >"Congratulations, a large amount of your effort will go towards
| resolving weird message bus problems as opposed to writing the
| business logic of your app"
|
| Sorry but I do not resolve "weird message problems". I use my own
| publish-subscribe mostly asynchronous message bus for my GUI apps
| (actually I use it also for non GUI parts as well). Components
| (visible or not and including running threads) can subscribe to
| events. It does not exhibit any performance / memory problems and
| in combination with the global app state object makes programming
| interactions a piece of cake.
| nikisweeting wrote:
| Every codebase is clean and elegant when in the head of a
| single person. How big is your codebase and how many people
| work on it?
| FpUser wrote:
| >"Every codebase is clean and elegant when in the head of a
| single person."
|
| Simply not true. I recently had to salvage business rules
| from a codebase written by single person over the course of
| many years. It was probably one of the messiest code I've
| ever seen.
|
| >"How big is your codebase and how many people work on it"
|
| Depends on a project. On some I work alone. Some had 2-3
| persons. The biggest team I've ever had to lead was about 35
| people (not all developers). Properly organizing and
| splitting work and using mostly experienced people the
| resulting code was very decent and quite possible to grasp.
| Also well documented.
| nikisweeting wrote:
| I don't mean objectively clean and elegant, I mean in the
| eye of the beholder it's easy for a tiny codebase to be
| clean.
|
| But a system that may be clean and elegant for a 1-person
| gig may end up falling to bits if you attempt to have 100
| people working on it.
| vikingcaffiene wrote:
| I've written front end for nearly all of my 13 years in this
| field. I've written some absolute dumpster fires in my time and
| have also written some very large and complex UI's successfully.
| I would like to think I have some insight to add here. I agree
| with a lot of the points brought up in this article. I'd like to
| add a few more points about why I think FE can be such a pain to
| get right:
|
| 1. Mixed concerns and a lack of application layering. In React
| code bases and others like them, its not uncommon for me to find
| business logic embedded directly inside the components
| themselves. This makes the component inherently coupled to the
| specific implementation its being used and can only be re-usable
| in other contexts by passing in modifier flags to the props etc.
| In my opinion, components should be mostly of the stateless
| flavor and delegate anything not directly related to their
| immediate UI concerns elsewhere. This increases the likelihood of
| re-usability of your components and makes testing business and
| component logic much much more straight forward.
|
| 2. This might just be my personal experience, but I've noticed a
| bit of a dismissive attitude around design patterns and
| traditional computer science principles among my front end
| brethren. YAGNI and all that. While I think its fair that the UI
| != to the back end, I think frequently the baby gets thrown out
| with the bath water. For instance, I frequently leverage
| dependency injection in my front end work as a way to make my
| code more flexible and testable. It's hard to sell the benefits
| of something like DI until your application grows in complexity
| to the point that you are dealing with things like nested mocks
| in your tests and/or need to make a non trivial change to some
| piece of functionality. I've been seeing the winds start to shift
| a bit on this which is encouraging.
|
| 3. Most of the time there is little to no overall _conceptual
| integrity_ to a given front end code base. Its uncommon for me to
| come into an existing code base and have anyone be able to tell
| me what the architecture is comprised of. Things like what logic
| lives where and why. I'm not saying people _don't_ do this, but
| in the more gnarly code bases I've encountered, this "accidental
| architecture" is more common.
|
| 4. Front end is still see as "easy" and the place you stick the
| less experienced engineers first. I sincerely hope this doesn't
| come off like I am gatekeeping or anything. I work with some
| absolutely brilliant people who are only a year or two into their
| career and much smarter than me. IMO its less about skill and
| more about having been burned enough times to know where
| unexpected complexity lie.
|
| I love front end. Its challenging and interesting and maddening.
| My hope is that it will continue to mature and grow to the point
| that it gets the same respect as other disciplines within this
| field. These days I work full stack so its not all I do, but it
| will always be my favorite. :)
| ChrisMarshallNY wrote:
| This is a great discussion on the challenges of designing UI for
| complex applications.
|
| In the aggregate, what ends up being most effective for me, is
| rapid prototyping, and "paving the bare spots."[0]
|
| I find that I do a terrible job of predicting user mental models.
|
| The rub with prototypes, is that they can't be lash-ups, as they
| _inevitably_ end up as ship code. This means that a lot of good
| code will get binned. It just needs to be accepted and
| anticipated. Sometimes, I can create a standalone project for
| code that needs to go, but I still feel has a future.
|
| So there's always a need for fundamental high quality.
|
| What has been useful to me, is Apple's TestFlight[1]. I write
| Apple software, and TestFlight is their beta distribution system.
|
| I start a project at a very nascent stage, and use TestFlight to
| prototype it. I use an "always beta" quality approach, so the app
| is constantly at ship quality; although incomplete.
|
| It forces me to maintain high quality, and allows all
| stakeholders to participate, even at very early stages. It's
| _extremely_ motivating. The level of enthusiasm is off the
| charts. In fact, the biggest challenge is keeping people in low
| orbit, so they don't start thinking that you are a "WIZZARD"
| [sic].
|
| It also makes shopping around for funding and support easy. You
| just loop people into the TestFlight group. Since the app is
| already at high quality, there's no need for chaperones or
| sacrifices to The Demo Gods.
|
| I like that it keeps development totally focused on the actual
| user experience. They look at the application entirely
| differently from me.
|
| [0] https://littlegreenviper.com/miscellany/the-road-most-
| travel...
|
| [1] https://developer.apple.com/testflight/
| tebbers wrote:
| Great post and that first link is brilliant, thank you for
| sharing. I agree with you regarding your attitude to developing
| software - just get it out there first and then adjust the UX
| later.
| mattgreenrocks wrote:
| I'm not fond of the reasoning employed in the message bus section
| that decries the fact that misuse results in a big mess.
|
| No programming paradigm can stand up to rushed/flawed mental
| models.
|
| The domain can become quite complex; it is wishful thinking to
| believe that a single approach could drain it of all complexity.
| [deleted]
| [deleted]
| hyberbole_1234 wrote:
| The best approach I've seen is separating State and Views and
| having some form of property diffing. struct
| LightState { var isLightOn: Bool }
| class LightViewController { let lightView =
| UIView() } class StateDirector<
| LightState, LightViewController> { let
| lightState: LightState init(state: LightState) {
| self.lightState = state } func bind(
| view: LightViewController ) { //
| Every time is turned on changed call this
| LightState.add( listener: self, for
| keyPath: \.isTurnedOn, handle:
| .method(Self.handleLightChange) } func
| handleLightChange(isOn: Bool) {
| view.lightView.backgroundColor = isOn ? .green : .red
| } }
|
| This allows clear separation of State, View and Changes.
|
| You can then just model your state in your reducer and "simulate
| your views" inside Unit tests
| diggan wrote:
| I'm fairly sure I've said this before here and elsewhere, but
| bears repeating, especially for this post.
|
| Statecharts is currently probably the most undervalued tool when
| it comes to programming GUIs with state. Statecharts are a
| continuation of state machines, but with less footguns and better
| abstractions to be able to build larger systems.
|
| In the end, you either build a GUI that are using state machines
| implicitly, or explicitly. Tends to be less prone to bugs if you
| do so explicitly.
|
| If you're interested, here is some starting points (copied from
| an older comment of mine):
|
| Here is the initial paper from David Harel: STATECHARTS: A VISUAL
| FORMALISM FOR COMPLEX SYSTEMS (1987) -
| https://www.inf.ed.ac.uk/teaching/courses/seoc/2005_2006/res...
|
| Website with lots of info and resources:
| https://statecharts.github.io/
|
| And finally a very well made JS library by David Khourshid that
| gives you lots of power leveraging statecharts:
| https://github.com/davidkpiano
|
| While we're at it, here are some links to previous submissions on
| HN regarding statecharts with lots of useful and interesting
| information/experiences:
|
| - https://news.ycombinator.com/item?id=18483704
|
| - https://news.ycombinator.com/item?id=15835005
|
| - https://news.ycombinator.com/item?id=21867990
|
| - https://news.ycombinator.com/item?id=16606379
|
| - https://news.ycombinator.com/item?id=22093176
| sillysaurusx wrote:
| I really don't mean to be dismissive of your contribution, so
| please take this in the best light possible:
|
| Yuck. Use continuations with anonymous fns that tell the GUI
| what to do next. The state is in the closure implicitly!
|
| I wish I had an articulate counterargument, but just look at
| this: https://xstate.js.org/docs/#promise-example
|
| _Statecharts are a formalism for modeling stateful, reactive
| systems._
|
| Beautiful is in the eye of the beholder, and never moreso when
| the programmer is blind to the cost of repetition and state.
|
| Arc got it right. http://www.paulgraham.com/arcchallenge.html
|
| _Write a program that causes the url said
| (e.g.http://localhost:port/said) to produce a page with an
| input field and a submit button. When the submit button is
| pressed, that should produce a second page with a single link
| saying "click here." When that is clicked it should lead to a
| third page that says "you said: ..." where ... is whatever the
| user typed in the original input field. The third page must
| only show what the user actually typed. I.e. the value entered
| in the input field must not be passed in the url, or it would
| be possible to change the behavior of the final page by editing
| the url._ (defop said req (aform
| [onlink "click here" (pr "you said: " (arg _ "foo"))]
| (input "foo") (submit)))
| diggan wrote:
| In no way was that dismissive, I thank you for providing
| another perspective, that's always welcome in my book!
|
| I don't necessarily agree with all the implementation details
| of xstate, in particular to where the logic tend to be
| located in practice, and the reliance on the Actor model for
| many things in the wild. I rather try to guide people to
| Statecharts as a paradigm overall, and if you happen to use
| JS, I think xstate is probably the most mature library there.
| But as all libraries/frameworks, they can be over-relied
| upon.
|
| If you're in the Clojure/Script world, which is where I
| mainly locate myself, then https://lucywang000.github.io/clj-
| statecharts/ is all you need and so far the library I've had
| the best luck with.
| davidkpiano wrote:
| XState creator here, what kinds of implementation details
| do you not agree with? Curious to know.
| sillysaurusx wrote:
| I came back to edit my comment and remove the harsh words,
| but you left a very kind response. Sorry.
|
| This is actually a fascinating example:
| https://lucywang000.github.io/clj-statecharts/docs/get-
| start...
|
| ... because it highlights precisely my criticism.
| ;; define the machine (def machine
| (fsm/machine {:id :lights :initial
| :red :context nil :states
| {:green {:on {:timer {:target :yellow
| :actions (fn [& _]
| (println "transitioned to :yellow!"))
| }}} :yellow {:on {:timer
| :red}} :red {:on {:timer
| :green}}} :on {:power-outage :red}
| }))
|
| I just ... don't understand why anyone would do it this
| way. The code itself already says what to do. Adding this
| sort of data only subtracts from clarity with no additional
| flexibility.
|
| You might argue that the data model makes it flexible. But
| I look at that and go, that's what a function is for. The
| only thing you need is `(println "transitioned to
| :yellow!")` inside of a function called transition-to-
| yellow, or if you're feeling adventurous, a function called
| transition-to which takes yellow as an argument.
| diggan wrote:
| I guess your comment highlight the real problem of trying
| to describe tools for handling complex scenarios with
| just simple examples, they often don't make sense because
| the first thought is always "Why not just have one
| function instead of all of that?".
|
| If you're just printing some text to the console when
| doing one thing, then surely one function is enough. Once
| you start having some state and different views depending
| on values, normal state machines might be enough. But
| eventually, your application might grow so much in scope
| that you want to have state machines nested in other
| state machines, and you want to avoid the classic "state
| explosion" that you end up with when using state
| machines, then Statecharts are a very useful tool.
|
| But before that, do the simplest thing that can work for
| your use case, complexity is evil and all that...
| Statecharts are simply a tool for handling complex and
| intertwined state, not for simple stoplights. Unless
| those stoplights are all connected to each other and also
| control/read other state machines, then Statecharts might
| be right for you.
|
| Not sure if you took a look at the links I put before,
| but the https://statecharts.github.io/ website has a "Why
| should you use statecharts?" section with small
| descriptions and links to more elaborate information as
| well. Might give you a better view than what I can give.
| sillysaurusx wrote:
| Fair! I think we just have different perspectives. HN is
| enormously complex (it has far more complexity than most
| people realize or truly appreciate), yet it handles every
| case without any state machine:
| https://github.com/shawwn/arc/blob/arc3.1/news.arc
|
| And it's nothing but a long list of functions that use
| closures.
|
| I did take a look at your examples. I just ... well,
| we'll have to agree to disagree, and I'll try not to be
| so harsh in my criticisms of what I perceive to be ugly
| ideas. Just because I think an idea is bad, doesn't mean
| it's bad.
|
| But there is one specific critique: the state machine
| approach will make programs longer, and consciously
| choosing to make programs longer seems fraught with
| danger. Every additional character you type is an
| additional character for bugs to hide in.
|
| This moves the subjective "I don't like the style" to
| something concrete: What's the shortest program you can
| write with a state machine? My argument is that it's
| "significantly longer than the equivalent program without
| a state machine."
| eternalban wrote:
| Given a bug-free spec to code generator, your only source
| of bugs can be in the state chart specification, so not
| bugs but rather errors.
| Folcon wrote:
| From my perspective, it's better as a data structure
| because I can do more with it.
|
| It's much easier for me to introspect, and I can easily
| build dynamic state-machines by changing a data
| structure, take a look at interceptors[0] as an example.
| There the stack is dynamically alterable based on what is
| within the request and each piece of middleware can look
| at the current context, analyse it and behave
| accordingly.
|
| I write a lot of workflow based systems with dynamically
| changing functionality based on user input. This sort of
| thing is invaluable in that context.
|
| - [0]: http://pedestal.io/guides/what-is-an-interceptor
| Folcon wrote:
| Thank you for this, I didn't know this existed for
| clojurescript =)... Much appreciated!
| bmitc wrote:
| There is an edX course that covers state charts. It's
| excellent, and Davis Harel is one of the co-instructors.
|
| https://www.edx.org/course/programming-for-everyone-an-intro...
| pixel_tracing wrote:
| This isn't really solving issues for most teams, how do you
| handle maintenance and tech debt of these state charts? And
| why only javascript example? You're missing mobile
| diggan wrote:
| Statecharts are the solution to technical debt, not the
| source, as you're formalizing the possible states the user
| can be in, and "locking" it to that. You can even apply
| analysis to your codebase to figure out if you're actually
| covering all possible states and transitions.
|
| If you take a look at the syllabus of the course, it's not
| about JavaScript, it's about the formalism and
| understanding the core concepts, without locking you to a
| particular technology. Whatever they are teaching in the
| course, you can apply to JavaScript/Swift as much as you
| can apply it to Desktop/Mobile.
|
| Disclaimer: haven't actually taken the course, but planning
| to and I've read the description of it.
| pixel_tracing wrote:
| This isn't answering my original question, how do you get
| teams to adopt this?
|
| Until this process is made easy this will always just be
| a fever dream of fools in ivory towers
| diggan wrote:
| I'm sorry, your "original" question doesn't seem to exist
| in your previous comments, so hard for me to answer it...
|
| You get people to adopt technology or paradigms by
| explaining the benefits and drawbacks of adopting that
| set of technology/paradigm and then discussing it
| together with your team/s. Not sure why it would be
| different for Statecharts compared to any other paradigm?
|
| What process is too hard for you now exactly, to describe
| states or something? You're already doing this implicitly
| when you write UI code with state. Statecharts only
| changes it from being implicit to being explicit. If
| you're having a hard time actually naming your states,
| you can use tools like https://sketch.systems/ to explore
| what you're building with a simple DSL, then implement it
| properly in the programming language of your choice.
| pixel_tracing wrote:
| Teams and technical debt go hand in hand. I don't mean to
| sound snarky but this isn't really practical for large
| teams to adopt at large companies.
|
| 1. writing code adds "debt" 2. Your solution is to now
| add state charts too which also adds "debt"
|
| Where are these state charts tracked? Who maintains them?
| When product asks engineering to change code => you now
| also update state charts. Added technical debt.
|
| If this is difficult for you to understand (the problem
| I'm describing is very common at large companies) I'm
| happy to expand more on it.
|
| Thoughts?
| diggan wrote:
| I'm not sure if you're a developer or not, I'm just gonna
| assume you're not in order to hopefully be able to
| describe things better.
|
| Yes, writing code can add debt, but not all code is debt.
| "Debty" code is code that can be better, but was needed
| in order to take shortcuts temporarily, probably in order
| to trade moving faster now against moving faster in the
| future. If you're taking shortcuts you're gonna have to
| fix it in the future, or deal with having to move
| slower/more careful because of it.
|
| And yes, the solution to code debt is to go back and fix
| it properly. When it comes to UI programming, I'd argue
| that doing implicit state machines with conditionals
| scattered all over the place (which is the de facto
| standard today everywhere I look), is code debt, which
| can be fixed by REPLACING it with explicit Statecharts.
| It can also be fixed in other ways obviously, but besides
| the point here.
|
| The developers would obviously be responsible for the
| code they write and the Statecharts are all handled in
| the same source control system your developers already
| use (typically Git today), so nothing really changes
| here.
|
| And yes, when you figure out that you have to change the
| code to do something different, you're gonna have to ask
| developer to change the code. The same goes for updating
| Statecharts (that also exist in the code). If you have to
| change the states/transitions, you're gonna have to
| update the code that handles the states/transitions. This
| is the same no matter if you use Statecharts or not.
|
| In the end, Statecharts is not a programming language
| itself, it's just a way of doing programming. Basically
| like how functional programming is a way of programming,
| or object oriented programming is one way, Statecharts is
| a different way where you're upfront with what states
| exist in the application.
| pixel_tracing wrote:
| Again you've described a paradigm like functional
| programming but failed to address my original question.
| I've been programming for over a decade now, and have
| seen this symptom over and over again. Someone sees shiny
| new paradigm => realizes is holy grail => fails to see
| how it fits into _actual_ working teams. My problem is
| that you are going to have a hard time selling this
| academic approach to _PRACTICAL_ teams doing every day
| work with every day deadlines and bottom line business
| dollars. Until you make this seamless and easy the extra
| work is just going to be _extra_ work which will be
| ignored.
|
| I say this as a FAN of Statecharts and functional
| programming. I myself love this idea. I am looking and
| prodding people like you for SOLUTIONS to make teams
| adopt this.
|
| So far you've failed to convince me on how to sell this
| to teams or make it easy to integrate.
| diggan wrote:
| > So far you've failed to convince me on how to sell this
| to teams or make it easy to integrate.
|
| Yeah, because I'm not trying to convince you of anything.
| It's a tool in your toolbox, use it when you think it's
| advantageous, otherwise use your other tools. I couldn't
| care less of what you chose to do or how you "sell this
| to teams". I'm a developer who simply chooses the best
| tool for the job, sometimes that's Statecharts and
| sometimes it's something else. Also don't have any idea
| about what ideas are circling around in "Academia" as I'm
| far away from that ecosystem and only focus on shipping
| working products.
|
| If you're looking for something "easy" in particular,
| then whatever you don't know is gonna be hard. Such is
| the life of a developer, where sometimes the simpler way
| is harder but worth it in the end. React was hard for
| people to grok in the beginning as well, but that doesn't
| mean it's bad, it just means people are not familiar with
| it. If you're just looking for easy solutions then yeah,
| feel free to stop improving and continue use the stuff
| you already know or is familiar to you.
|
| So in the end, do what you will with the knowledge and
| experience I've shared with you, I have zero interest in
| selling you anything and I'm simply discussing stuff here
| on HN as I'm curious about things and want to have
| discussions with people who are also curious about
| things, but this discussion stopped being productive a
| long time ago.
| diggan wrote:
| That's amazing, didn't know that, thanks a lot for sharing!
| Fitting that it starts today as well :)
| bmitc wrote:
| No problem! And as just a note, it starts everyday since it
| is a reoccurring self-paced course. But if I remember
| correctly, the instructors still answer questions.
|
| I really enjoyed learning about the hierarchical state
| machines using statecharts.
| chris_wot wrote:
| Um, isn't this what the Mediator pattern was designed to solve?
|
| https://en.wikipedia.org/wiki/Mediator_pattern
| onion-soup wrote:
| Don't libraries like react solve this?
| tomaszs wrote:
| React makes it even harder actually. Angular has some ways to
| help. Ember has it. Vue - not so much
| protoman3000 wrote:
| A related question, how do GUIs actually resolve quickly on which
| element under the cursor a click lands?
|
| How is the click propagated recursively through every component
| and is the position compared repeatedly at every step?
| badsectoracula wrote:
| They just enumerate all child windows, these events are not
| frequent enough for this to be a performance bottleneck. The
| check is something along the lines of Win*
| GetChildAt(Win* w, int x, int y) { size_t
| i; if (x >= w->ScreenX1 && y >= w->ScreenY1 &&
| x <= w->ScreenX2 && y <= w->ScreenY2) { for
| (i=0; i < w->ChildCount; i++) { Win* child
| = GetChildAt(w->Child[i], x, y); if (child)
| return child; } return w;
| } return NULL; }
|
| Call this on the root window and you have the deepest child at
| the given coordinates.
|
| (though there is usually a bit extra logic for handling, e.g.,
| invisible and disabled windows)
| pixel_tracing wrote:
| I believe this can be resolved using a quad tree to resolve
| point locations and map them to a view in a view hierarchy
| rwmj wrote:
| I remember in my first real job I wrote a GUI for an RTOS (all
| the way up starting with the hardware, device driver, ...). Not
| knowing anything about how GUIs worked, or about events, I had a
| main loop which redrew the GUI on every action. This article
| tells me I wasn't completely wrong, these are called "Immediate
| Mode" GUIs!
| c-smile wrote:
| TL;DR: there is no one silver bullet for the UI in general.
|
| As a rule practical and manageable UIs use all these approaches
| at the same time.
|
| Components, class based and/or functional (fe: UserSection,
| InventoryTable ), are in principle loosely coupled. They may not
| know about each other and use messages to communicate.
|
| The Flight by Twitter ( https://flightjs.github.io/ ) was/is
| probably the first _practical_ thing that pioneered that
| approach.
|
| The Flight is not even a framework but rather a design principle.
|
| In fact it is an anti-framework - it postulates that in practice
| each component may have its own optimal internal architecture.
| One component is better to use React-ivity, another - Angular-ish
| data binding, etc. On some, immediate mode drawing is the most
| natural.
|
| Smaller the task (component) - greater the chance to find silver
| bullet for it.
| 1vuio0pswjnm7 wrote:
| I rarely ever use a GUI. Certainly not for any recreatonal web
| use. Unless I'm using a GUI, I do not load the graphics layer. I
| stay in textmode. No X11, Wayland or whatever is compiled in.
| Ideally I have a designated computer on the local network that
| has a graphics layer, a full corporate GUI OS. When I need
| graphics to view stuff I can send it over the local network to
| the GUI computer. Otherwise I keep recreational use on text-only
| computers, away from the whiz-bang corporateOS-controlled
| computer.
| tomaszs wrote:
| After 20 years of working with frontends mostly I consider that
| there are three causes of problems with GUI:
|
| - it is badly designed. If state is too hard to manage it means
| often that the design is bad
|
| - state is not the most important part of the UI. Interaction and
| being elastic to changes are the most important things
|
| - frontend is often overengineered in a bad way. When you use bad
| solutions like React or Redux there is no change there won't be
| any problems.
| ggm wrote:
| Neither is between two choices: he's presenting Three.
|
| Small point, but it grated.
| pixel_tracing wrote:
| Article presents a bunch of problems on UI but no solutions...
| perplexing.
| phtrivier wrote:
| This seems to miss the 'ui=f(state, effects)' model that's sort
| of what's behind react+redux or elm or similar ideas.
|
| Or is that really the same as immediate mode ?
| RoyalSloth wrote:
| Author here. This is not related to this discussion, but does
| anybody know what causes these lines to appear in my server logs?
| <REDACTED> - - [14/Feb/2021:22:26:45 +0100] "GET /posts/the-
| complexity-that-lives-in-the-gui/ HTTP/1.1" 200 16798 "-"
| "HackerNews/1391 CFNetwork/1220.1 Darwin/20.3.0" <REDACTED>
| - - [14/Feb/2021:22:26:45 +0100] "GET /posts/the-complexity-that-
| lives-in-the-gui/ HTTP/1.1" 200 16798 "-" "HackerNews/1391
| CFNetwork/1220.1 Darwin/20.3.0" <REDACTED> - -
| [14/Feb/2021:22:26:45 +0100] "GET /posts/the-complexity-that-
| lives-in-the-gui/ HTTP/1.1" 200 16798 "-" "HackerNews/1391
| CFNetwork/1220.1 Darwin/20.3.0"
|
| The requests usually come from a certain ip multiple times until
| fail2ban bans it. It's not just one offender, there are multiple
| behaving like that.
| tn1 wrote:
| It's probably an iOS app client for HN. CFNetwork is like their
| URLConnection I think. You're blocking people who want to read
| your article!
|
| As for why it occurs so often in quick succession, perhaps
| there's a bug in the app causing it to fetch several times
| instead of once.
| RoyalSloth wrote:
| Thanks, I thought it was a bug in some app and just wanted to
| be sure, so I don't have to babysit the server.
|
| If anybody knows which app is that, please tell the
| maintainer that they have a serious bug. It's night here, so
| I am logging off.
| valand wrote:
| Another option is injecting an optional dependency without
| connecting the box.
|
| I see OP doesn't mention this option, but is slightly related to
| both option 1 (connect the box) and 3 (message bus / event
| emitter). This option is similar to how OS provides an API to
| user space application program. For example Windows provides an
| API to an application to flash its window without exposing all of
| its state like mentioned in option 1.
| https://docs.microsoft.com/en-us/windows/win32/api/winuser/n...
|
| Here's the detail:
|
| A self-powered object, let's call it workingIndicatorObject, can
| be introduced to work on the working indicator. It provides 1.)
| `getState() -> WORKING/NOT-WORKING` a function to get its state,
| and 2.) `register() -> None`, a function to register user
| activities. These functions are dependencies for UserSection
| component and InventoryTable component respectively. In term of
| lifetime and hierarchy, it precedes and outlives both components.
|
| The workingIndicatorObject MUST have its own lifecycle to
| regulate its internal state. Its core lifecycle MUST NOT be
| managed under the same event loop as the GUI components, assuming
| the program is written on top of a framework that has it (React,
| Vue, etc). This is to assure that it doesn't directly affect the
| components managed by the framework (loose coupling). Although, a
| binding MAY be made between its state and the framework-managed
| event loop, for example, in React environment, wrapping it as a
| React hook. An EventEmitter can also be provided for a component
| to listen to its internal event loop.
|
| Injecting it as a dependency can use features like React Context
| or Vue's Provide/Inject pattern. Its consumer must treat it as an
| optional dependency for it to be safe. For example, the
| UserSection component can omit drawing the "working" indicator if
| it doesn't receive the `getState` function as a dependency.
|
| Internally, the workingIndicatorObject can use a set. The set is
| used to store a unique value (a Symbol in Javascript). `register`
| function creates a Symbol, stores it in the set, and deletes it
| after $TIMEOUT, while firing event both at addition and deletion.
| `getState` function is a function that returns `set.size()`. When
| bound to the component, an addition to the set fires an "update"
| command to the component, which redraw the component along its
| internal state. This is just one example of implementation and
| there are other simpler ways to have the same behaving
| workingIndicatorObject.
|
| This approach allows UserSection and InventoryTable to only know
| `getState()` and `register()` function and nothing else, and
| having both of them optional. Using static type system such as
| TypeScript can help a lot in this since we can pin down the
| signatures of both function to `null | () => WORKING |
| NOT_WORKING` and `null | () => void`, where we can enforce both
| dependent components to check if it exists and to call it with
| the correct types, otherwise the compiler yells.
| cpill wrote:
| > I'd love to hear what the functional programming camp has to
| say about this problem, but I guess they are too busy with
| inventing yet another $20 term for a 5 cent concept
|
| hA! Hit the nail on the head :P
| brundolf wrote:
| The fundamental challenge of GUIs is that they have state as a
| core concern. Unlike most systems, state is not an implementation
| detail that can be refactored away. It is central to the problem
| domain. The _end user_ sees and cares about having a stateful
| system.
|
| The hardest part of dealing with state in a complex system is
| maintaining consistency in different places. Some instances of
| this this can be avoided by creating single-sources-of-truth, but
| in other cases you can't unify your state, or you're dealing with
| an external stateful system (like the DOM), and you have no
| choice but to find a way to keep separate pieces of state in
| sync.
|
| I should probably write a blog post on this
| rwmj wrote:
| The way Tcl/Tk dealt with this was nice: You could watch the
| value in a variable, getting notified when the variable
| changed. GUI controls could therefore exactly reflect the
| content of variables. Of course this comes with a certain
| hidden overhead.
|
| This complete example will toggle the checkbox every second
| (1000ms), or the user can click to update the variable. The
| checkbox watches variable "v". #!/usr/bin/wish
| checkbutton .button -variable v -text "Click me" pack
| .button proc run {} { global v
| after 1000 run set v [expr !$v] } run
| Jasper_ wrote:
| This was the "two-way data binding" model that Angular used
| and React famously knocked down. It's been part of UI
| toolkits for a long time. Remember Object.observe?
| brundolf wrote:
| MobX is the equivalent for the space of reactive web
| frameworks, and I agree it's a fantastic model. The cost is
| that it requires a little "magic", but the benefit is that
| the entire question of synchronizing state virtually
| disappears, leaving you to focus on modeling and controlling
| state (which are still nontrivial)
| trulyme wrote:
| No. Just no. You simply can't use "MobX" and "fantastic" in
| the same sentence.
|
| There are just so many problems with MobX. For example
| reactions - ever tried figuring out why some value changes
| in a bigger app? Not to mention abysmal tooling (compared
| to Redux DevTools). But the biggest problem is that
| everything is... sprinkled with magic. Just give me
| Redux(-toolkit), thank you very much, I can understand
| these much more deeply. /rant
|
| If I sound confrontational, sorry about that... I just had
| the misfortune of inheriting a MobX app. Magic indeed.
| dustingetz wrote:
| I/O, not state (well they are the same thing mostly). Async,
| latency, concurrency, failure, effects
| tenaciousDaniel wrote:
| You could conceive of a GUI as merely a projection of internal
| state. If you do, then a GUI could be a completely stateless
| layer that exists solely to render visual content
| parametrically.
|
| I've been developing a convention in React over the last year
| that uses this idea and it's very very nice. I'm also trying to
| write a platform-agnostic UI language specifically for
| designers that makes this a first class concept.
| davidcuddeback wrote:
| > You could conceive of a GUI as merely a projection of
| internal state.
|
| That mental model has been the most natural for me as well.
| It really clicked for me when reading Trygve Reenskaug's
| descriptions of MVC [1], particularly this paragraph
| (emphasis mine) and the illustration that follows:
|
| > The essential purpose of MVC is to bridge the gap between
| the human user's mental model and the digital model that
| exists in the computer. _The ideal MVC solution supports the
| user illusion of seeing and manipulating the domain
| information directly._ The structure is useful if the user
| needs to see the same model element simultaneously in
| different contexts and /or from different viewpoints.
|
| [1]:
| https://folk.universitetetioslo.no/trygver/themes/mvc/mvc-
| in...
| mdoms wrote:
| So Redux.
| riquito wrote:
| Unless you rewrite the content of every input and textarea
| and reposition the cursor at every input event, or monitor
| which element has focus at any given time, you have some DOM
| components with an internal state.
| theknocker wrote:
| People do this sometimes. It can work out pretty well.
| erikpukinskis wrote:
| Which... you mostly can do that with React.
|
| Focus is the hardest one, because that can move around
| really erratically. But even that sometimes has to be done
| in state. IE11 doesn't have descendant focus selectors so
| at my last job I wrote a hook for our dropdown children to
| dump their focus state into a React context so our
| renderers could stay pure and not rely on CSS and therefore
| DOM state.
|
| Just last week I implemented a hook to reposition a tooltip
| based on the mouse cursor.
|
| You do need to think about DOM state a little when doing
| these things. But I would argue that's somewhat separate
| from the activity of building a React UI. It's pretty rare
| you can't just render purely based on the React state,
| using off-the-shelf hooks.
| gusmd wrote:
| That's essentially declarative programming for GUIs, like,
| say, Flutter does. You have some state provider, and the GUI
| is fully re-rendered when it is notified of a state change.
| There's many different actual implementations of this
| paradigm, but that's it in a nutshell. I've been having lots
| of fun learning this, coming from a more traditional MVC
| pattern in Qt and the likes.
|
| See this: https://flutter.dev/docs/get-started/flutter-
| for/declarative
| legulere wrote:
| What you describe is the model and view part of the MVC
| pattern: The view is just a projection of the model.
|
| How does the UI get notified of changes? (like the article
| discusses some changes might come from different part of the
| UI, or might even come from external like in a chat client)
|
| How do you handle actions of the user? (Of course in the
| controller in MVC, but how does it work exactly?)
| brundolf wrote:
| You're just redefining "GUI" as "the MVC View layer". It's
| good to make the View a pure projection - this is what React
| approximates - but the state doesn't stop being a concern of
| the GUI, it just gets pushed to other places in the system
| (the internal state of HTML elements, the app's store whether
| that's in Redux or otherwise, even the server if we're
| talking about a server-rendered app (I would actually argue
| the main benefit of a client-rendered app is that it's much
| less onerous to add GUI state; of course the flip-side is
| that you end up with a lot _more_ GUI state because it 's so
| easy to add))
| jayd16 wrote:
| Agreed. To some degree UI programming faces many of the same
| problems you see when building a distributed system.
| marcus_holmes wrote:
| I was wondering why this was all so much easier back in VB than
| it is in the browser, and I think you're right: we've got to
| keep the state in the DOM and the "useful" state in the program
| in sync. Whereas in VB there was only one program so only one
| source of truth (though it was possible to get your GUI
| desynced from your database, you kinda had to work hard to do
| that).
|
| I think this is what Elm solved (and then by plagiarising the
| same approach, React and Vue). Make the interface declarative
| rather than imperative and voila - state is automatically in
| sync.
| scarredwaits wrote:
| Good explanation, but the article reads as if React never
| happened. Maybe it was written by someone who's only worked on
| desktop apps?
| cdaringe wrote:
| Agreed. A nice concise way to summarize my gut synopsis.
| MaxBarraclough wrote:
| > Clicking on buttons will start triggering events which will
| modify the state in the model that will in turn start triggering
| event listeners causing your GUI to flash like a christmas tree.
| The problem of data bindings and change listeners is that they
| make it really easy to introduce a hidden circular event
| listeners that will trigger one another multiple times (event A
| changes state B and change of state B triggers event A).
|
| Agree with both of these points. You can no longer treat
| assignment simply as the way you mutate data, you also have to
| anticipate its effects in the data-binding system.
|
| I imagine the circular event problem could be addressed with
| static analysis, but I don't know of any framework that does
| this.
| MontagFTB wrote:
| This is a flaw in how the widgets have been implemented. When
| they serve as both the view and the controller, a common
| misarchitecture is to "loop back" a value change in the view
| back into the controller. This loop should _only_ happen as a
| result of the user interacting with the UI. To fix this, the
| controller does not modify its value authoritatively. Rather,
| it makes a request of the model to change the value it
| represents, then gets a confirmation or correction from the
| model once the logical state of the application has stabilized.
| jffhn wrote:
| Had a similar issue while using a JTree, where programmatic
| nodes modifications generated events as if user had clicked on
| the tree, such as I had to override and disable a few JTree
| treatments to avoid triggering a wave of undesired side
| effects.
|
| That's why in some framework I'm working on, the only events
| are those from input devices (keyboard, etc.) and windowing,
| views don't generate any and are just here to paint the state
| and indicate to the controller where things are on the screen.
| amelius wrote:
| Isn't this what React and precursors were invented for?
| MaxBarraclough wrote:
| I have to plead ignorance here. Does it have a way of
| detecting circular events?
| amelius wrote:
| The idea is to separate the state of the application from
| the presentation, which in practice leads to not having
| circular dependencies in the first place. And you can
| update the display after all changes are finished, so you
| won't get the flashing xmas tree effect.
| TeMPOraL wrote:
| I thought TFA meant flashing in the metaphorical sense -
| it's the code paths that light up, not things on the
| screen.
|
| Circular dependencies are an unfortunate fact of life,
| and I wish we had tools to deal with them, instead of
| desperately trying to avoid them.
|
| A good test for a UI paradigm is, how do you handle a UI
| like this: A = [50] [====| ] B
| = [10] [| ] C = [60] [=====| ] A +
| B = C
|
| Where [===| ] things are sliders, all three are
| changeable, and the relation A+B=C must always hold.
| kilburn wrote:
| The problem is under-specified. When you move a slider,
| how should the other two adjust? There are many possible
| solutions and you haven't specified any preference here.
|
| The easiest solution is to maintain a fixed order of
| preference, something like: values =
| [a:0, b:0, c:0] fn onChange(key, value) {
| values[key] = value for (k,v) in values {
| if (k !== key) values[k] = adjust(k) } }
| fn adjust(key) { switch(key) { case
| 'a': return max(0, values[c] - values[b]) case
| 'b': return max(0, values[c] - values[a]) case
| 'c': return values[a] + values[b] } }
|
| The alternative is to maintain the latest selections made
| by the user and use that as the iteration order.
|
| Whatever approach you go with, the "single source of
| truth" approach of react/vue/svelte + state (whether it
| is state in hooks, redux, mobx or whatever) holds. The
| "values" above is the source of truth, and the components
| just reflect that.
|
| In other words: from a state point of view you don't have
| three sliders each with a value but a "three-valued
| component that happens to be shown as three sliders".
| amelius wrote:
| You can add a constraint solver on top of it. So when
| e.g. C changes, you solve for A and B. You then update
| the state of A, B, C and redraw. This kind of interaction
| can be handled by a separate library which is not
| necessarily part of a GUI system.
| auggierose wrote:
| A constraint solver is overkill here, in particular as
| you cannot solve for A and B uniquely, you might want to
| add some further rule, like A and B should grow
| proportionally, when C is modified.
|
| But otherwise, yes, just use a framework that separates
| State from UI and has bidirectional updates, like
| SwiftUI.
| diggan wrote:
| React itself doesn't have any way of "listening" to events,
| so the data is only flowing in one direction always. Redux
| et al introduces more things on top of that, where you can
| fire off events ("Actions" in Redux terms) that
| react/trigger other events, but doesn't really have
| anything to do with React itself.
|
| Then that some people make React components change state on
| render/mount/update, is a different failure, but not really
| a failure of React as much as a failure of the one using
| React.
|
| But in general no, React doesn't really prevent you from
| having a component updating and re-rendering because some
| state changed, and because of that, changes the state again
| and thus re-renders again, forever.
| walkingpigeons wrote:
| At the end of the article it said something about immediate mode.
| I do think React or Vue are kind of working like this now? Both
| requires you define your state of the each component and define
| how it should look like based on the state values. When you
| update the state it will then update the view automatically as
| well based on what you've defined (JSX/template). It is not
| 30/60FPS though, it is re-rendered when it knows there is a state
| change (i.e. setState is triggered)
| heycosmo wrote:
| I have a simple rule for GUI design: build trees not graphs.
| Write components that accept a state snapshot and broadcast
| changes. If component A listens for state changes from B, then A
| is a parent node of B. If A sends state to B, then A is a parent
| of B. Components reconcile state before broadcasting changes
| toward the root of the tree.
|
| Often there is a price paid in brevity, but I believe it is worth
| it. It may seem annoying to propagate a click explicitly through
| 5 parent components just to sum clicks into a count widget, but
| as soon as a short circuit is made, you've created a graph, and
| you lose the ability to isolate GUI sub-trees for
| testing/debugging.
| nikisweeting wrote:
| This makes visual redesigns take foreeeeever though. Imagine
| moving a component from the main area of your app into a menu
| dropdown in the navbar, now you have to tear out all those
| props that you painstakingly passed down through 10
| intermediary layers.
| flohofwoe wrote:
| IME, working with an immediate mode UI framework automatically
| gets rid of most such "architecture astronaut" problems.
|
| But I found that it's almost impossible to describe to someone
| used to event-/callback-driven UIs _why exactly_ that is. You
| really need to try it yourself on a non-trivial UI to "get it".
| amelius wrote:
| Yes, but immediate mode causes more CPU load (or draining of
| batteries) since you have to redraw everything all the time.
| forrestthewoods wrote:
| > immediate mode causes more CPU load
|
| Does it? Computers are FAST. Webdev stacks are many things,
| but it ain't fast or performant.
|
| Has anyone built a moderately complex UI in a good immediate
| mode UI and in a good retained UI? I'd be very curious to
| know the actual results.
| panic wrote:
| It's easy to skip drawing frames when no state changes, and
| for frames where state does change, you can use techniques
| like rxi's cached software rendering to avoid redrawing the
| entire screen:
| https://rxi.github.io/cached_software_rendering.html
| amelius wrote:
| How does it handle scrolling? This is typically very fast
| and smooth (performed by bitblt and redrawing only the
| newly exposed part on every frame), but in immediate mode
| you'd have to redraw the entire screen on every frame.
| panic wrote:
| Modern renderers typically draw scrollable content into a
| separate texture (or collection of smaller textures that
| tile the scrollable region) and use the GPU to scroll
| rather than bitblting on the CPU. You can use the same
| technique to render the scrollable part of the UI in an
| immediate mode system.
| flohofwoe wrote:
| I think this whole efficiency thing is a common
| misconception. Only the public API appears "immediate
| mode", the internal implementation doesn't need to be,
| and usually isn't (e.g. it keeps mutating state between
| frames instead of building everything from scratch
| again).
|
| The user-side code basically describes what the UI should
| look like in the current frame, those "instructions" are
| recorded, and this recording is reasonably cheap.
|
| The UI backend can then figure out how to "diff" the new
| instruction stream against the current internal state and
| render this with the least changes to the screen.
|
| _However_ some immediate mode UI systems came to the
| conclusion that it might actually be cheaper to just
| render most things from scratch instead of spending lots
| of processing resources to figure out what needs to be
| updated.
|
| In conclusion: "Immediate Mode UI" doesn't say anything
| how the UI is actually rendered or generally how the
| internals are implemented, it only describes how the
| public API works.
| amelius wrote:
| Perhaps I should give a different example: a listbox
| filled with 1 million items, with a scrollbar.
|
| If the public API requires you to give a new paint
| command on every frame (everytime the scrollbar is
| dragged), then regardless of whether the underlying
| rendering engine performs each of these paint commands,
| you still have to run through every item of the list (and
| so does the diff'ing code), making this a O(N) operation
| on every frame.
| flohofwoe wrote:
| From what I've seen (in Dear ImGui), this is solved by
| not giving the 1 million items to the API, instead you
| can query what range of the list is currently visible,
| and only describe those to the API (in Dear ImGui this is
| called a ListClipper).
|
| But I guess different UI frameworks have different
| solution for this. Creating and updating a 1 million item
| list wouldn't be a cheap operation in a traditional UI
| system either.
| jcelerier wrote:
| > Creating and updating a 1 million item list wouldn't be
| a cheap operation in a traditional UI system either.
|
| IDK, works pretty well with Qt's item model system
| coldtea wrote:
| That's becuase it too (in all probability) doesn't render
| 1 million offscreen elements to show the handful (100?
| 1000?) on screen elements.
|
| So it does it just like an immediate mode GUI does it,
| and only renders what it must.
|
| Not sure about QT specifically, but all other non-
| immediate mode GUIs I know use the same trick, and
| immediate mode GUIs like React also use it.
| amelius wrote:
| Ok. Yes my point is not that you can't find efficient
| ways around the limitations, but my fear is that you end
| up with a system that is less ergonomic in the end.
| nikki93 wrote:
| These aren't just workarounds. It's often the natural
| immediate mode api. A lot of immediate mode "scroll view"
| systems just have a child lambda passed with an index for
| which element to render, and it only calls the lambda for
| what's actually in view. (eg: the Gio API in Go)
|
| Concrete examples of code that is not immediate mode api,
| vs. code that is immediate mode api, both implementing
| the same thing, can help discuss / reflect on that fear.
| IME immediate mode is great for mapping tree-ish data to
| tree-ish UI; when things have a lot of nontree linkages
| or reordering (eg. when you do drag and drop) it gets
| trickier. React's JSX / createElement API also feels
| somewhat immediate mode, tbh; the updates are just
| scheduled to fire on state changes.
| amelius wrote:
| > A lot of immediate mode "scroll view" systems just have
| a child lambda passed with an index for which element to
| render
|
| This sounds a lot like paintEvent() in traditional OO-
| style GUI systems; i.e. event-driven.
|
| So my understanding now is that with immediate-mode
| callbacks happen within the scope of a function-call
| rather than from some event-loop. I probably have to look
| into it deeper to get a good understanding. It is still
| unclear where state is stored (e.g. for the position of a
| scroll-view), and if state is passed through the function
| call tree on every frame.
| nikki93 wrote:
| Yeah the in depth look helps before forming conclusions
| like "you have to render everything every frame." Key
| thing is immediate mode can often mean just the sense of
| the API, not necessarily the draw scheduling.
|
| re: widget-local state -- React is one of the most
| popular models for that. Basically if widgets are
| identified over time by the sequence of ids (includes
| array indices like React keys) on the path to them from
| the root, state is coherent across that identity, and
| mount / unmount events exist at the boundary of the
| identity. Callstack / push-pop based APIs like ImGUI
| maintain this sequence either impicitly or explicitly in
| the stack. Then there is some API to read / write to the
| state store (like React hooks or ImGUI generic state
| storage) with optional update triggering in async APIs
| like React's.
| wdfx wrote:
| It's not difficult to implement a 'virtual' list which
| renders only the items which are visible. It's a little
| trickier if your list items are not all the same height,
| but not impossible.
| layer8 wrote:
| That doesn't seem too different anymore to the
| invalidate/paint paradigm of traditional UI frameworks
| (e.g. win32 GDI).
| jameshart wrote:
| What if you have your 'immediate mode' produce not a direct
| rendering of the GUI, but a virtual object model describing
| what the structure of the GUI should be at that moment? Then
| you diff that object model against the actual object model of
| the GUI and apply the changes....
|
| That's how react works. It's effectively an 'immediate mode'
| abstraction above a component based UI.
|
| That avoids the component connecting and wiring problems, and
| creates the simple determinism that makes immediate mode
| systems easier to reason about.
| panic wrote:
| Immediate mode is great, but it can be hard to implement nice
| custom event handling on top of it. For example, say you have a
| collection of buttons. You want to be able to click on one
| button (the button highlights), drag onto another button (that
| button highlights and the first button unhighlights), and
| release on that second button to trigger its action. This is a
| real issue I ran into while building an immediate-mode UI... I
| found it hard to implement an interaction like this without a
| reified "layout" that I could query as the mouse moved around.
| boterock wrote:
| I was doing something like that with Godot [1], the approach
| I took was setting some global "dragged_item" when the drag
| started, and then each drop target would check if the
| dragged_item was compatible and if the mouse was within its
| bounds. This way each drop target would do the check instead
| of some drag manager having to check whatever was under the
| cursor.
|
| [1] https://github.com/0xafbf/aether/tree/master/addons/godot
| _im...
| Const-me wrote:
| I used and implemented immediate mode GUIs, and I think it's
| only good for simple stuff.
|
| Accessibility is hard. The OS needs to know visual tree to be
| able to conclude things like "this rectangle is a clickable
| button with Hello World text".
|
| Complex layouts are hard. Desktop software have been doing
| fluid layouts decades before the term was coined for web apps,
| because different screen resolutions, and because some windows
| are user-resizable. Layout can be expensive (one reason is
| measuring pixel size of text), you want to reuse the data
| between frames.
|
| Animations are hard. Users expect them because smartphones
| introduced them back in 2007: https://streamable.com/okvhl Note
| the kinetic scrolling, and soft "bumps" when trying to scroll
| to the end.
|
| Drag and drop is very hard.
| Chris_Newton wrote:
| When building more complicated UIs, an architectural pattern
| I have often found useful is to have two distinct levels of
| state/data behind the rendering code, so my hierarchy looks
| something like this:
|
| 1. Application data
|
| 2. Presentation data
|
| 3. Presentation rendering
|
| The first is the single source of truth for your application
| state, what we often call a "model" or "store". It's where
| you represent the data from your problem domain, which is
| usually also the data that needs to be persistent.
|
| The second is where you collect any additional data needed
| for a particular way of presenting some or all of your
| application data. This can come from the current state of the
| view (list sort order, current page for a paginated table,
| position and zoom level over a map, etc.) or the underlying
| application data (building a sorted version of a list, laying
| out a diagram, etc.) or any combination of the two (finding
| the top 10 best sellers from the application data, according
| to the user's current sort order set in the UI). This is
| often a relatively simple part of the system, but there is no
| reason it has to be: it could just as well include setting up
| a complicated scene for rendering, or co-ordinating a
| complicated animation.
|
| The final part is the rendering code, which is a translation
| from the application and presentation data into whatever
| presentation is required. There isn't any complicated logic
| here, and usually no state is being maintained at this level
| either. The data required for rendering all comes ready-
| prepared from the lower layers. Any interactions that should
| change the view or application state are immediately passed
| down to the lower layers to be handled.
|
| The important idea is that _everything_ you would do to keep
| things organised around the application data also applies to
| the presentation data. Each can expose its current state
| relatively directly for reading by any part of the system
| that needs to know. Each can require changes of state to be
| made through a defined interface, which might be some sort of
| command /action handler pattern to keep the design open and
| flexible. Each can be observable, so other parts of the
| system can react to changes in its state.
|
| It just happens that now, instead of a single cycle where
| application data gets rendered and changes from the UI get
| passed back to the application data layer, we have two
| cycles. One goes from application data through presentation
| data to rendering, with changes going back to the application
| data layer. The other just goes from the presentation data to
| the rendering and back.
|
| I have found this kind of architecture "plays nicely" with
| almost any other requirements. For example, data sync with a
| server if our GUI is the front end of a web app can easily be
| handled in a separate module elsewhere in the system. It can
| observe the application data to know when to upload changes.
| It can update the application data according to any
| downloaded information via the usual interface for changing
| the state.
|
| I have also found this kind of architecture to be very
| flexible and to fit with almost any choice of libraries for
| things like state modelling, server comms, rendering the
| actual view, etc. Or, if your requirements are either too
| simple to justify bringing in those dependencies or too
| complicated for a ready-made library to do what you need, you
| have a systematic overall structure in place to implement
| whatever you need directly, using all the same software
| design and scaling techniques you normally would.
| Const-me wrote:
| What you talking about is very similar to MVVM as
| implemented in various MS XAML frameworks: https://en.wikip
| edia.org/wiki/Model%E2%80%93view%E2%80%93vie... IMO that's
| very good approach to the problem.
|
| > laying out a diagram
|
| I think layout belongs to the views (you call them
| presentation rendering).
|
| View models provide data to be visualized, but it's rarely
| a good idea to compute pixel positions, or handle low-level
| input there. These things are coupled with views too much.
|
| Similar to animations. Unless the app being developed is a
| video editor or similar where animation is the core
| functionality, animations don't affect models nor view
| models, they merely prettify GUI state transitions or
| provide progress indication. No need to wire them all the
| way into view models. Some frameworks even run them on a
| separate thread called "compositor thread" so they play
| fine even if the main thread is blocked or starved for CPU
| time.
| flohofwoe wrote:
| Apart from accessibility, isn't this mostly caused by the
| "immediate mode API" versus "immediate mode implementation"
| confusion I described in another sub-thread?
|
| There's nothing in the idea that forbids immediate mode UI
| frameworks from keeping any amount of internal state between
| frames to keep track of changes over time (like animations or
| drag'n'drop actions), the difference to traditional UI
| frameworks is just that this persistent state tree is hidden
| from the outside world.
|
| Layout problems can be solved by running several passes over
| the UI description before rendering happens.
|
| For accessibility, the ball is mainly in the court of the
| operating system and browser vendors. There need to be
| accessibility APIs which let user interface frameworks
| connect to screen readers and other accessibility features
| (this is not a problem limited to immediate mode UIs, but
| custom UIs in general).
| Const-me wrote:
| > There's nothing in the idea that forbids immediate mode
| UI frameworks from keeping any amount of internal state
| between frames to keep track of changes over time
|
| When you call ImGui::Button("Hello World") it has no way of
| telling if it's the same button as on the previous frame,
| or a new one. You're simply not providing that data to the
| framework.
|
| It's often good enough for rendering, they don't really
| care if it's an old or new button as long as the GPU
| renders identical pixels. When you need state however
| (animations certainly do), the approach is no longer
| useful.
|
| A framework might use heuristics like match text of the
| button or relative order of things, but none of them is
| reliable enough. Buttons may change text, they may be
| dynamically shown or hidden, things may be reordered in
| runtime.
|
| > Layout problems can be solved by running several passes
| over the UI description before rendering happens.
|
| You're correct that it's technically solvable. It's just
| becomes computationally expensive for complex GUIs. You
| can't reliably attach state to visual elements, gonna have
| to re-measure them every frame.
|
| > There need to be accessibility APIs which let user
| interface frameworks connect to screen readers
|
| Windows has it for decades now. For Win32 everything is
| automatic and implemented by the OS. Good custom-painted
| GUI frameworks like WPF or UWP handle WM_GETOBJECT message
| and return IAccessible COM interface. That COM interface
| implements reflection of the complete GUI, exposing an
| objects hierarchy. I'm not saying it's impossible to do
| with immediate rendering, just very hard without explicit
| visual tree that persists through the lifetime of the
| window.
| shrimpx wrote:
| > When you call ImGui::Button("Hello World") it has no
| way of telling if it's the same button as on the previous
| frame, or a new one.
|
| Can you solve this by adding stable IDs to the API, so
| you'd call `ImGui::Button("my-button-id", button_text)`?
| Const-me wrote:
| To consistently assign these IDs you gonna have a tree on
| your side of the API. You'll then have two visual trees,
| one on your side on the API, another one on the
| framework's side of the API. And then you gonna be
| debugging things.
|
| Who and when should destroy backend visual nodes? If you
| won't use an ID in some frame, does it indicate the
| backend should drop the related state? What if an
| animation is still playing on the element in question?
| What if the missing control re-appears later i.e. was
| only hidden temporarily? What should backend do if you
| reuse same ID for different things?
|
| One can implement simple stuff using any approach,
| immediate GUI is often the simplest of them. It's when
| use cases are complicated you need a full-blown retrained
| mode OO framework. Win32, HTML DOM, MS XAML, QT, UIKit
| are all implemented that way, that's not a coincidence.
| enqk wrote:
| these APIs are UI Automation on windows and NSAccessibility
| on macos
| badsectoracula wrote:
| I've tried it at the past and "get it" but that doesn't mean i
| "agree with it" - to me immediate mode GUIs are the graphical
| equivalent to... Running = -1 WHILE
| Running PRINT "1) Add record" IF HasRecords
| THEN PRINT "2) Delete record" PRINT "3)
| Edit record" END IF PRINT "H) Help"
| PRINT "X) Exit" INPUT "Choice: ", I$ IF
| I$="1" THEN AddRecord IF (I$="2") AND HasRecords THEN
| DeleteRecord IF (I$="3") AND HasRecords THEN
| EditRecord IF I$="H" THEN ShowHelp IF
| I$="X" THEN Running = 0 WEND
|
| ...which, sure, it is ridiculously simple (like imguis) but it
| can very quickly become unwieldy the more you ask from your
| user interface. There is a reason why UIs moved beyond that.
| flohofwoe wrote:
| I think when you look at projects like Tracy, there's no
| question that immediate mode UIs also work well for non-
| trivial use cases:
|
| https://github.com/wolfpld/tracy
| badsectoracula wrote:
| My point isn't that you can't make them work for non-
| trivial cases, after all as long as you can draw something
| on screen you can do pretty much anything. The point is how
| well that is done.
|
| For example from a quick look at the code it looks like
| this project needs to draw in a bunch of unrelated to each
| other libraries just to get some basic GUI functionality
| you'd find even in toolkits from the 80s provide (e.g.
| drawing text or using file dialogs).
|
| And check these sources [0] and [1], this is doing at least
| as much bookkeeping as in a "retained" GUI toolkit - except
| it also has to do things that such a toolkit would do
| automatically, like explicitly drawing the widgets [2].
| People at the past were complaining how tools like Visual
| Basic were mixing presentation and logic, yet the worst you
| could do in these tools is to have the logic in event
| handlers (...which if you think a bit about it at least
| that does make a bit of sense), yet here is actual
| application logic being part of the drawing code in [3]
| (this is inside a method View::DrawPlayback, scroll a bit
| upwards to find it).
|
| Now sure, someone might say that this isn't the best
| example of immediate GUIs... but this is the one that was
| brought up as a good example. And TBH all it convinced me
| is that if (for some reason) i had to use ImGui (the
| library), to spend some time building a retained GUI
| wrapper around it :-P.
|
| [0] https://github.com/wolfpld/tracy/blob/master/server/Tra
| cySou...
|
| [1] https://github.com/wolfpld/tracy/blob/master/server/Tra
| cyVie...
|
| [2] https://github.com/wolfpld/tracy/blob/d8c1dae9e120c2780
| 1e762...
|
| [3] https://github.com/wolfpld/tracy/blob/d8c1dae9e120c2780
| 1e762...
| jcelerier wrote:
| > , there's no question that immediate mode UIs also work
| well
|
| I sincerely hope no one looks at the screenshot on that
| page and thinks "this is something that works well". This
| UI screams "I've been made that way because that was the
| easiest way in the tool I'm built with" (which is _bad_ -
| good tools should not dictate the form of the resulting
| product)
| flohofwoe wrote:
| How would a profiling tool UI need to look differently in
| your opinion?
|
| And why do you think the UI looks that way because of
| restrictions of the UI framework?
|
| I'm quite sure if the UI would have been written with
| (for instance) Qt or React, it would look exactly the
| same, because the UI requirements would be the same
| (minus the different default style). The question is
| whether this could have been achieved with less, or
| "cleaner" code (which I doubt).
| jcelerier wrote:
| example with Qt: https://www.youtube.com/watch?v=6ogEkQ-
| vKt4
| keeganpoppen wrote:
| seems pretty information dense to me. tufte would
| approve. but surely he's no match.
| coldtea wrote:
| > _I sincerely hope no one looks at the screenshot on
| that page and thinks "this is something that works well".
| This UI screams "I've been made that way because that was
| the easiest way in the tool I'm built with"_
|
| That's where you'd be wrong. Profilers traditionally have
| looked like this regardless of the tool/GUI lib/GUI
| paradigm they'd been made with.
|
| It's the domain need (to show lots of information,
| graphs, timelines, stack trees, etc, to compare) that
| asks for this...
| jcelerier wrote:
| > Profilers traditionally have looked like this
| regardless of the tool/GUI lib/GUI paradigm they'd been
| made with.
|
| the profiler I use most (hotspot) definitely looks much
| cleaner: https://www.youtube.com/watch?v=6ogEkQ-vKt4
| coldtea wrote:
| It's the same thing split in N tabs.
|
| Not a distinction made because of immediate vs retained
| GUI.
| erwincoumans wrote:
| Agreed, in particular Dear Imgui is pretty nice, and high
| performance.
| nikisweeting wrote:
| Excellent writing, this is a great article to show a junior
| frontend engineer who would otherwise spend their next years
| having to crystalize these ideas on their own. I wish I had read
| this 8 years ago.
| IshKebab wrote:
| This is a really good take on the (still unsolved IMO) problem,
| though I suspect it only makes sense to people who have
| experienced all the issues personally, since so many of them are
| "it gets really awkward with big programs" type problems.
|
| He did miss out a pretty significant flaw of message busses - the
| producers and consumers of messages are completely decoupled,
| which makes debugging really annoying because you don't have
| anything like a stack trace. You just know that your component
| received a message, and good luck trying to figure out where it
| was sent from and why.
|
| That's also a big problem with magical reactivity systems like
| Vue. Your stack trace is pretty much "yeah something changed
| somewhere so we're updating this component. Don't try and figure
| it out."
| pixel_tracing wrote:
| There's always a stack trace. The question is figuring out deep
| your eventing layer is. Also dispatching an event on one thread
| and receiving it in a component on another thread essentially
| negates the trace in this sense too (sometimes depending on
| your environment).
|
| A solution I've always had is to build your message bus with
| logging in mind initially
| IshKebab wrote:
| > There's always a stack trace.
|
| I think you misunderstood. Of course there's always a stack
| trace; you're still executing code. But with message buses
| and magic reactivity systems your stack trace always just
| goes to `mainEventLoop()` or `processEvents()` or whatever.
|
| It doesn't go to the thing that actually caused the change as
| it would if you used direct function calls. I'm not saying
| it's a deal breaker, it's just a notable downside of those
| architectures.
| RoyalSloth wrote:
| > the producers and consumers of messages are completely
| decoupled, which makes debugging really annoying because you
| don't have anything like a stack trace
|
| This is actually a really good point. I don't know why you were
| downvoted.
| erdo wrote:
| So nice to see this universal issue being discussed, it doesn't
| seem to get as much attention as it deserves.
|
| IMO, a great solution here is along the lines of "Lift the state
| up" / "MV(X)". But... there is a vital detail which is usually
| missed when deciding how exactly the state in your M gets passed
| to your V for display: you must refresh your entire V when M
| changes in any way, not just the bit of V that you think changed.
| It's the only way to completely remove these difficult to test,
| hard to spot edge cases that the article discusses.
|
| This is almost impossible to talk about without specific
| examples, so a while back I wrote such an example that I think
| distills the core problem, and demonstrates how refreshing the
| entire V not only solves the problem but typically takes less
| code to do it: https://dev.to/erdo/tutorial-spot-the-deliberate-
| bug-165k
| continuational wrote:
| I ran into a buch of similar problems in the past personally, so
| much that I ended up writing my own library.
|
| The home page is a bit ugly, but it contains a range of examples
| that are commonly awkward to implement in other UI libraries:
|
| http://www.react4s.org/
| madhadron wrote:
| I mean, of course you lift the state. It's just like a database.
| You have a normalized model containing the state, and the GUI is
| a view on it. Events from the GUI trigger transactions that
| update the state and then every element is triggered to update
| itself based on the new state. That should never trigger a loop
| of events, because a view updating itself from the model should
| never trigger transactions on the database.
|
| Many complicated views have their own internal models for things
| like where they are scrolled, what columns are shown, or what
| elements of a tree are expanded. But those compound views are
| written so that, from the outside, they appear exactly the same
| as any other view.
|
| > I'd love to hear what the functional programming camp has to
| say about this problem
|
| It's called functional reactive programming.
| erdo wrote:
| Agree completely other than the functional bit (but I'm not
| sure if that was a recommendation or just Google keyword help).
| At least for Android, I've found that maintaining state when
| handling screen rotations etc becomes a real headache if the
| GUI binding code is functional (there is nowhere nice and clear
| to hang the state without storing it in an rxStream or
| similar). So far I favour reactive code absolutely, and
| functional (ish - I'm still using DI for example) code right up
| to the view layer, but then drop out for the last mile of data
| binding
| nikisweeting wrote:
| This is the key line that the original article is missing imo,
| which solves much of the headache that they claim exists with
| message passing/raising state:
|
| > a view updating itself from the model should never trigger
| transactions on the database
|
| i.e. a view rendering from state/messages, should never re-emit
| new state/messages.
| Chris_Newton wrote:
| I thought the article was too dismissive of concepts like
| MVC, immediate mode and functional programming, given that
| ideas coming from those directions might offer the answers
| the original author is searching for.
|
| The separation of an underlying data model from any
| particular way of presenting its current state is a powerful
| idea that has proven its value many times. We've used it to
| build user interfaces, from early GUIs when we didn't yet
| have the kinds of libraries and platform APIs we enjoy today,
| through games with unique rendering requirements and a need
| to keep frame rates up, to modern web and mobile app
| architectures. The same principle is useful for building non-
| user interfaces, too, in areas like embedded systems where
| you are "presenting" by controlling some hardware component
| or distributed systems where you are "presenting" to some
| other software component as part of the larger system.
___________________________________________________________________
(page generated 2021-02-14 23:01 UTC)