Hacker News new | ask | show | jobs
by Chris_Newton 1956 days ago
What you talking about is very similar to MVVM

At first when I adopted this architectural pattern, it was a development of classic MVC so already used the terms “model” and “view”. I actually did adopt the term “view-model” for what I’m referring to here as the presentation data layer. After all, it was the connection between the view and model, so what else should it be called? :-)

However, while there certainly are similarities between the architecture I described above and MVVM — the VM can collect data from the Model and reformat it for the View, and the VM might manage some aspects of the view state — they also differ in some important ways.

Most significantly, MVVM has a linear V–VM–M relationship between the components. The V and M never communicate directly in either direction.

In the architecture I described, the rendering code might depend on both state layers, accessing both application state directly and derived or view-specific data from the presentation state. This avoids any need for boilerplate in the middle layer if nothing special needs to be done with the application data before it’s used for rendering.

Likewise, in response to events triggered from something in the rendered output, messages might be sent to the application and/or presentation state layers so they could update accordingly. The distinction is always clear: any event that can affect application data gets sent there, while anything that affects view state goes to the presentation data layer. In practice, it’s unusual for a single UI event to affect both, so usually only one message is needed. (There is an implicit assumption being made here that the starting point for handling events triggered by user interactions is defined as part of the rendering, which is not necessarily always the case, but let’s gloss over those details for now or this comment will get insanely long.)

In MVVM, there may also be direct two-way data-binding between the V and VM. The architecture I described never assumes that kind of 1:1 relationship between something in the rendered output and the underlying data.

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.

This is another place where I think the responsibilities in the architecture I described are allocated differently. I want any non-trivial calculations to be in the presentation data layer, and I want the presentation rendering layer to be as dumb as possible. This separates data processing from I/O, which is something I favour as a general principle for organising programs.

In this case, it has the specific advantage that you might have very different strategies for testing them. The presentation data layer is pure computation and can be easily unit tested. The I/O is all about rendering and, in some cases, simulating actions on the rendered output that trigger some sort of message to be sent, so for this you might want some sort of snapshot-based testing or simulated UI environment.

You might also want to incorporate totally different third party libraries or depend on different platform APIs to implement each step. For example, switching out one UI rendering library for another or updating to integrate with some new-style platform API probably shouldn’t affect any of the data (including presentation data like diagram layouts or animation logic) unless we’re specifically shifting responsibility for something like handling animations to a dependency rather than our own code or vice versa.

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.

Sure. One of the nice things about the architecture I described is that it’s entirely neutral about these things. If your requirements so dictate, there is nothing to stop you implementing a more elaborate design at any level of the system. For example, that might include running time-consuming parts of the presentation data logic in separate threads, having changes in the presentation data triggered by timers or other system events, delegating some aspects to hardware acceleration where available, or using any sort of sophisticated caching or scheduling implementation for better performance.

Much of this would add unnecessary complexity in a simple CRUD application GUI. If you’re building a complex, real-time visualisation of an incoming data stream or you’re implementing a game engine that needs to draw part of a large game world with many moving parts, presumably you might have a different perspective on how much extra complexity in the design is acceptable if it gets you the performance you need.

1 comments

> The V and M never communicate directly in either direction.

There’s nothing wrong in dropping a layer there.

That intermediate VM layer makes sense when the data being presented is not precisely what’s stored in the model. Good place to implement filters or pagination. Good place to validate user input. However, in other cases one doesn’t need any of that, in which case there’s nothing wrong with data binding directly to models.

> the rendering code might depend on both state layers, accessing both application state directly and derived or view-specific data from the presentation state.

In .NET with MVVM that’s easily doable, expose a property on VM that returns the model, and this will work.

> In practice, it’s unusual for a single UI event to affect both

In the apps I work on this happens all the time. User clicks “do something” button, the model needs to start doing that, in the meantime the view needs to disable that button and show “doing something, please wait…” with a progress bar.

For this reason, I normally handle such higher-level events in VMs and call models from there.

> The presentation data layer is pure computation and can be easily unit tested.

I think strongly typed languages handle 90% of what people usually unit test for, with better developer’s UX. If you change something that broke 33 places of other code, a compiler will tell you precisely which 33 places you gonna need to fix.

> switching out one UI rendering library for another or updating to integrate with some new-style platform API probably shouldn’t affect any of the data

I think that only works when switching to something extremely similar. I did when porting iPhone code to iPad, or WPF XAML to UWP XAML, but these were very similar platforms. UI libraries are complicated; replacing them with something else is huge amount of work.

> including presentation data like diagram layouts or animation logic

I don’t believe these pieces are portable in practice. Both coupled with UI libraries too much. For the diagram, half of the 2D rendering libraries are immediate mode other half are retained mode. Animations are totally different as well, CSS animations in HTML don’t have much in common with visual state manager in XAML.

No doubt there are many different architectures that can work well in this sort of situation. I’m just arguing that, contrary to your earlier comment that I first replied to, an immediate mode UI can be one of them even when the requirements are not so simple.

In the apps I work on this happens all the time. User clicks “do something” button, the model needs to start doing that, in the meantime the view needs to disable that button and show “doing something, please wait…” with a progress bar. For this reason, I normally handle such higher-level events in VMs and call models from there.

I used too strong a word before. As you point out, situations where you’d want to update both the application and presentation data aren’t really unusual. You gave one good example. Another might be controlling a modal UI, like closing a dialog box or moving to the next step of a wizard, when maybe you also want to commit data user has entered in a temporary form to the permanent application state. In these cases, handling a UI event would indeed need to get the relevant information to both of the other layers one way or another. This still easily fits within the architecture pattern I was describing, though.

I think strongly typed languages handle 90% of what people usually unit test for, with better developer’s UX.

I am a big fan of using expressive type systems to prevent defects, particularly as an alternative to huge unit test suites full of boilerplate that still don’t do as good a job.

However, no mainstream type system will verify that for a given list in your application data and a given sorting option chosen in the UI, a function in your presentation data layer has generated the correct top 10 items in order. (No unit test can ever verify that property in general either, of course, but at least we can test some representative example cases.)

I think that only works when switching to something extremely similar. I did when porting iPhone code to iPad, or WPF XAML to UWP XAML, but these were very similar platforms. UI libraries are complicated; replacing them with something else is huge amount of work.

I suppose that depends on what you consider extremely similar. Around 10–15 years ago, when what we call “single page applications” today were starting to gain serious traction, if you wanted an interactive visual representation of your data, you probably used one of the proprietary embedded technologies like Flash, Java or ActiveX. But if you were drawing, say, an org chart, you were still ultimately just putting boxes and lines and text in certain places within the allocated area on the page. Today we have tools like SVG, canvas and WebGL available handle the drawing part, but everything we’d want to display in that drawing might still be the same.

I don’t believe these pieces are portable in practice. Both coupled with UI libraries too much. For the diagram, half of the 2D rendering libraries are immediate mode other half are retained mode. Animations are totally different as well, CSS animations in HTML don’t have much in common with visual state manager in XAML.

I think perhaps we have very different types of animation in mind. From the above, I’m guessing you’re thinking of things like pulsing a button when it’s pressed or a little progress spinner while downloading some data? I’m thinking of things like smoothly moving all of the boxes on that org chart to their new positions, including animating the shapes of the paths between them, when the user does something that moves the chart around. Another example might be animating weather data overlaid over a map graphic through the next few hours when the user presses a “play” button. That is, I’m talking about animations where the data to be shown at each step needs to be determined according to complex requirements, not just playing a simple, predetermined effect that a UI library or platform API might handle for you almost for free anyway.

> immediate mode UI can be one of them even when the requirements are not so simple

Just because you can doesn't mean you should.

> a function in your presentation data layer has generated the correct top 10 items in order

There're too many things which can go wrong with visuals. Incorrect order has smaller probability than a bug in a style somewhere which makes data invisible. For the 2nd reason you want to test that thing somehow else anyway: unit tests don't catch rendering bugs. Some other automated tests can in theory, but in practice maintaining these tests wastes a lot of time i.e. expensive.

> Around 10–15 years ago, when what we call “single page applications” today were starting to gain serious traction

Traction is overrated. Around 20 years ago Outlook Web Access (a server-side component of Exchange server) already was a modern single page application, written in HTML and JavaScript. All the required functionality was already there in IE, and even documented nicely on MSDN.

> everything we’d want to display in that drawing might still be the same.

Totally different. SVG renders vector 2D graphics, WebGL is a 3D GPU API. Try to render something slightly more complicated than rectangle of solid color, and you'll see. One example is rectangle with rounded corners: trivially simple in SVG, relatively hard on GPU. A Mandelbrot set is opposite example.

> animations where the data to be shown at each step needs to be determined according to complex requirements

Doesn't matter if the data is static or not. Once you know the data, you do not want to run your code at 60 Hz (or 240Hz if you're really unlucky) computing functions of time and updating things. It's almost always a good idea to use the GUI framework for playing animations. They do better with rendering and power saving.

Just because you can doesn't mean you should.

Sure, but that’s not an argument for not doing it either. As I’ve been trying to explain, I’ve had practical success building applications using the kind of software architecture I described.

I don’t like to get into personal backgrounds in these discussions, because as a rule I think arguments should stand on their own merit. However, just for some perspective, I wrote my first professional GUI around three decades ago. More to the immediate point, the longest I’ve maintained and extended a single GUI built using the architectural pattern I described was over a decade, and that one was probably more complicated in its requirements than a lot of web applications you’d see today. So although it’s just one anecdotal data point, I think it’s a good demonstration that this kind of architecture can work well over an extended period.

There're too many things which can go wrong with visuals. Incorrect order has smaller probability than a bug in a style somewhere which makes data invisible. For the 2nd reason you want to test that thing somehow else anyway: unit tests don't catch rendering bugs.

You seem to be making some implicit assumptions about the platform here with the reference to styling. In any case, this is why I mentioned that you might want a different testing strategy for the presentation rendering layer to the code that collects the presentation data.

Totally different. SVG renders vector 2D graphics, WebGL is a 3D GPU API. Try to render something slightly more complicated than rectangle of solid color, and you'll see.

Sorry, I’m not sure I understand the point you’re trying to make here. I was saying that web-based GUIs might have moved from rendering graphical content using one of the plugins a few years ago to rendering it using one of the native technologies today, without necessarily changing the content itself. Obviously which native technology you’d choose would depend on what kind of visuals you were creating and how you wanted any interactions with them to work.

Once you know the data, you do not want to run your code at 60 Hz (or 240Hz if you're really unlucky) computing functions of time and updating things. It's almost always a good idea to use the GUI framework for playing animations. They do better with rendering and power saving.

To the best of my knowledge, the GUI framework you are describing does not exist. You might be able to extract some basic movements that could be handed off to something running at a lower level with hardware acceleration, but ultimately you still need code to define the way the layout should animate according to whatever rules your system uses, for the same reason you need to define a static layout before or after the animation that way.

I’m not sure what you’re getting at with “doing better with rendering and power saving”. Can you clarify?

> I think it’s a good demonstration that this kind of architecture can work well over an extended period.

I think for me the figure is about 8 years of development of 1 GUI software, with MVVM and .NET.

> implicit assumptions about the platform here with the reference to styling

It's 2021, pretty much all of them support styling now.

> I was saying that web-based GUIs might have moved from rendering graphical content using one of the plugins a few years ago to rendering it using one of the native technologies today, without necessarily changing the content itself.

None of them were able to reuse much code while switching just the renderer: ActiveX written in C++, Flash in ActionScript.

> the GUI framework you are describing does not exist

WPF and UWP definitely exist. I think modern HTML+CSS can do that too, but I ain't a web nor electron developer and not sure.

> ultimately you still need code to define the way the layout should animate according to whatever rules your system uses

It's the matter how you define them. When you offload playing animations to a framework, you tell the framework which properties to animate and how: define function of time (typically supported ones include step functions, polylines, and Bezier), pass metadata about timing and repeats, etc. That's for the hard case when animations depend on the data, when they do not they're made offline by art people.

> Can you clarify?

When you do them manually, i.e. computing functions of time and changing visuals, following happens.

1. Your code runs at refresh rate of the monitor, typically 60Hz, but for high-end gaming monitors can be 240Hz. Even if you aren't animating anything, this alone prevents certain power saving modes from engaging.

2. If you manually change positions of things not just color, your updates gonna invalidate layout.

3. There're multiple ways of measuring time with different tradeoffs about precision and when it's running versus paused.

A framework is in a better position because it drives the main loop. It knows how many nanoseconds passed since the last frame. It knows when to render at 240Hz and when to sleep on GetMessage() because there's nothing to update. If it knows which properties are animated, it can integrate animations with layout for reasonable performance cost.

I think for me the figure is about 8 years of development of 1 GUI software, with MVVM and .NET.

Great. As I said before, there are many different architectures that can work well.

None of them were able to reuse much code while switching just the renderer: ActiveX written in C++, Flash in ActionScript.

But if you’re calling that renderer from JavaScript that has already done everything except the rendering, which would be the case with the kind of architecture I was describing if you were just using the plugin for the final rendering layer…

It's the matter how you define them. When you offload playing animations to a framework, you tell the framework which properties to animate and how: define function of time (typically supported ones include step functions, polylines, and Bezier), pass metadata about timing and repeats, etc. That's for the hard case when animations depend on the data, when they do not they're made offline by art people.

Right. But where do you get those data-driven paths from so you can use them as your key frames to drive your animation? That is what is generated in the presentation data layer in this example. No GUI framework can do this for you, because no GUI framework knows the rules of your system for how to lay out a particular diagram and how a transition between states should appear.

Actually moving lines or rotating text or whatever is then little more than an implementation detail, which can be done manually by the presentation layers or handed off to some platform-provided animation system, as the situation dictates.

Your code runs at refresh rate of the monitor, typically 60Hz, but for high-end gaming monitors can be 240Hz. Even if you aren't animating anything, this alone prevents certain power saving modes from engaging.

This seems like such an extreme case that it’s no longer very useful as an example. Who is using a 240Hz high-end gaming monitor, wanting our UI to produce animations that can keep up with that frequency of updates, yet concerned about some unspecified power saving mode not engaging if we do the maths manually when it would have engaged if the work had been delegated to some system service?

If you manually change positions of things not just color, your updates gonna invalidate layout.

We’re talking about immediate mode rendering. What “layout” is there to invalidate?