Design question: manually maintaining consistency vs dataflow extension a la cells?

I’m running into this issue when developing my structural editor in Nyxt. There are some attribute of the DOM that I would like to depend on some other attributes of some other DOM elements or Lisp objects in general, reactively.

I see that there are two way to achieve this. Either by coding some hooks manually (e.g. change-attribute-hook, change-parent-hook, change-children-hook), or through an existing dataflow extension like cells which do all the things under the hood.

The latter approach looks much nicer, but it requires more changes to existing code (replace define-class with defmodel). Also, one thing that strikes me is that inside plump itself, the parent-child relation is maintained using the former approach. If we still reuse plump DOM then we will have to have an inconsistent mix of both approach.

Or we could write our own DOM classes similar to plump model but using cells (and using CLOS ofc). We will also have more control over it. I’m not a fan of plump’s hash-table per node and using string everywhere. It also won’t be terribly lots of effort – plump/dom’s CLOS hierarchy is like 50 lines of code. @aartaka what do you think?

I’m pretty sure either of the ways would work just fine if you’re doing an extension. After all, you re-define CLOS classes to be whatever you want them to be at any moment. Given that cells is CLOS classes under the pretty defmodel facade, it all should work just fine.

If you want your structural editor to become part of Nyxt, though, then there should be a good reason to use e.g. cells and rewrite our DOM representation, as other extensions might already depend on the existing model.

There are a few aspects of plump/dom that I feel really sub-optimal, both in performance and design aspect:

  1. Attribute are stored using hash-table per node. Allocating hash-table and accessing them incurs a relatively huge cost when there’re only a few elements, which is typically the case for DOM. A plist/alist by default is much faster. This may be insignificant for just constructing a DOM infrequently and use it read-only, but will become a bottle neck in the future when more packages that manipulate DOM emerge.
  2. The overall design is kind of unstructured. A major pain point for now: the class attribute is stored as a plain space-separated string. A list of symbols make much more sense.
  3. Things (including tag names and attribute names) are generally stored as strings, which reflect 2.

Also, plump is an external dependency that we don’t control, even though we’re using only a small part of it.

I’d say all of them are rather tolerable if it’s hard to fix them. But the fix seems to very easy. The DOM interface is quite small and I estimate it can be re-implemented in ~100 LoC, with all the issues above fixed, and will fix any issue that Nyxt encounters in the future.

Regarding cells, I find its code and model too convoluted, so I just rolled my own ;) GitHub - BlueFlo0d/lwcells: Light Weight Cells

I don’t see any problem in having external dependencies that we don’t control. After all, we can’t do everything ourselves :slight_smile:

Let us actually discuss Plump/cells/whatever design and performance on the level of practical use-cases. What are the problems that your strucutral editor encounters in the course of implementation that Plump DOM cannot address? Are there performance penalties that it brings?

What are the problems that your strucutral editor encounters in the course of implementation that Plump DOM cannot address? Are there performance penalties that it brings?
The above 3 pretty much summarizes my current problem.

The performance side is currently just a speculation as I’m not editing read codebase yet, but think about it yourself, I think you will reach the same conclusion – this will become an issue when Nyxt someday becomes an application environment for making UI from Lisp. Try whatever usecase, plist/alist is consistently >10 times faster than hashtable for <10 elements (which is mostly the case for DOM). Imagine if Electron developers have their UI runs 10 times faster!

2 and 3 is a problem about the model, and it’s currently a real practical problem.

Using symbols and lists will not only be more structured and make code nicer, it will also be strictly faster than using unstructured (space-separated) strings.

Maybe one more merit: we can get rid of the change-class hack in nyxt’s dom code.

Lisp is fast enough to not think about performance until it gets laggy, so let’s leave Plump performance aside :slight_smile:

Class fetching can be hacked using :around method for attributes accessor or via an application-specific helper function. It’s no dead lock, if that’s critical for your use-case.

Can you specify what breakages/ugliness does the string-stored data cause to your editor at the moment? Maybe we can come up with a way to solve those without altering the model.

Don’t get me wrong, I’d love to have a perfect page model in Nyxt, but ditching a fine model and spending learning&maintenance hours for reasons solvable otherwise doesn’t sound reasonable to me :slight_smile:

Lisp is fast enough to not think about performance until it gets laggy, so let’s leave Plump performance aside :slight_smile:

SBCL is in general no faster than JavaScript V8, while DOM update is known to be a problem for JS programmers (albeit for a different reason, but that shows that its a very frequent operation). But sure.

Class fetching can be hacked using :around method for attributes accessor or via an application-specific helper function. It’s no dead lock, if that’s critical for your use-case.
Can you specify what breakages/ugliness does the string-stored data cause to your editor at the moment? Maybe we can come up with a way to solve those without altering the model.

I get it, and I know I can hack it by redefining lots of stuff and simulate a better interface. It’s Lisp afterall. But why not just have a better model?

Don’t get me wrong, I’d love to have a perfect page model in Nyxt, but ditching a fine model and spending learning&maintenance hours for reasons solvable otherwise doesn’t sound reasonable to me :slight_smile:

The DOM model itself that we’re using from plump is <50 LoC, and we have bunch of workarounds/hacks in our code to retrofitting it already, and we will have more. I think ditching it will result in less code, less hacks and ultimately less learning hours for new comers and maintenance hours. But maybe I should stop talking for now. After I sorted out all the design problem I will come back with a patch and you can then assess it.

Also I want to talk about cells. Have you looked at the implementation? It depends on hell lots of global states, and IMO conflicts very badly with Nyxt’s design (according to @ambrevar should localize states). And one practical pitfall resulting from this: it seems (AFAIK, but not sure, because I don’t understand all the code) to have little error recovery ability, (cells-reset) will leave most existing cells in broken state. l Do you think we’d better roll our own?

To extend on this (sorry if I’m adding oil to the fire :p) SBCL and V8 play in the same ballpark in terms of performance in my experience, but anyways V8 is not used by WebKitGTK if I’m not mistaken. SBCL seems much more performant than the WebKitGTK interpreter.