I have a lot of interest in declarative and reactive UI frameworks right now, but currently the only take on this concept in Roblox is Roact. Roact is fine, but I don’t enjoy using it much and it’s too slow to handle tweens and animations at 60fps, especially on mobile (instance refs and animation libraries are cheating!). Many advocates for Roact would argue that performance is the price you pay for declarative and reactive code, yet, I’ve always wondered if we can have our cake and eat it too.
So I built Nimbus, an experimental, declarative and reactive UI framework which is actually pretty fast - fast enough that my S8 Plus can handle some nice tweens at full 60fps, declared the exact same way you would declare any other changing property. It’s designed to take advantage of Roblox’s event-driven model and has some pretty unorthodox ideas inside.
In Nimbus, you build your UI out of widgets (similarly to Roact and others). These widgets are stateful and are responsible for managing the instances and connections they need to render correctly. The kinda crazy part is that, once you make a widget, you’re not allowed to change it externally at all. No changing children, no mutating properties, nothing. Your widgets have full and exclusive control over themselves. This solves the big issue of widgets changing unpredictably at any time, but introduces a new issue; you can’t affect the widgets anymore. If you had a counter for example, you’d be unable to update it without writing your own stateful component.
So Nimbus introduces an object called a Binding to solve this issue. Bindings give you a neat, consistent way of representing changing values in a way which can be kept track of by whoever’s using it. They’re made of two parts - a compute function which returns the current value whenever you call it, and an update event which you can fire to notify anyone using the binding to call the compute function again to get the new value. That’s a large oversimplification of course, but good enough for now.
Bindings are extremely flexible and versatile - they can be used with practically any changing value you can think of. Clock time, a Rodux store, user input, whatever you like!
If you pass a widget a binding, for example using a binding for the Text property of a TextLabel widget, the widget will be able to easily set the Text property on-screen, and keep that Text property up-to-date. This is how all change is handled in Nimbus.
To support this core idea, I also built some special ‘logical’ widgets which mirror the functionality of control statements like
while. They can be used with bindings and placed directly inside other widgets, since they are themselves widgets. I also built up some other abstractions, for example a State function which is like React’s
useState, but for bindings, so you can easily store some simple state safely.
This is very much a work-in-progress idea, and I’m not ready to release the source code (it’s barely functional and not documented completely) but I did make a sample to-do app (source on GitHub) to see if it’s a viable idea - it is! Have a go here; it works really well on mobile! I even threw in a small bit of tweening fun with the top bar
Nimbus is far from complete, but your feedback and ideas will be invaluable! I’m going to be looking at ways of making working with bindings more natural (operator overloading, anyone?) and maybe experimenting with some other ideas - and who knows, we could actually have a really good UI framework by the end of it