hannobraun / fornjot

Early-stage b-rep CAD kernel, written in the Rust programming language.
https://www.fornjot.app/
Other
2.02k stars 114 forks source link

Introduce new notion of object ID that survives updates #1904

Open hannobraun opened 1 year ago

hannobraun commented 1 year ago

Objects, the elements that make up shapes in fj-core, are immutable. When an object is updated, a new version of it is created.

Objects are identified by an ID. This ID doesn't survive such an update, as from a technical perspective, the new object is an entirely new thing. This can be highly inconvenient. Check out this code, for example: https://github.com/hannobraun/fornjot/blob/c29e2a3501d900b4ef2542d4b393be02411ee212/crates/fj-core/src/operations/build/shell.rs#L38-L98

This is part of the code that builds a tetrahedron from triangles. Where it joins the triangles together, it needs to do so using indices to refer to HalfEdges within Cycles. This was inconvenient to write, and now it's hard to read. It would be much nicer, if Handles to already existing HalfEdges could be used here instead, but this is not possible: The HalfEdges are being updated, so the Handles lose their meaning in the later steps of that code.

I have an idea on how to solve this: In addition to the ID, have some some additional identifying characteristic that survives object updates. Maybe name it "family" or "ancestry" or something like that. For a completely new object, ID and "family" would be identical. For each object created by updating an existing one, the "family" is kept.

Then APIs like this "join" API could use the "family" instead of the ID to identify an object.

hannobraun commented 1 year ago

I'm looking into this now.

hannobraun commented 1 year ago

I didn't update this issue! Sorry!

I haven't been working on this in a while, as I've been sidetracked by other work. I'm un-assigning myself for now, but I expect to pick this up later.


In the meantime I've had an idea for how to specifically use this family/ancestry (I've started to call it "lineage") ID. One way would be to just check the lineage instead of the ID for all comparisons within update operations. While I can't think of a specific reason not to do it, I don't like it. I think this will have unintended consequences sooner or later.

So instead, I have another idea that makes this more explicit: Instead of accepting a bare Handle as an argument of update methods, accept an enum (or rather, an impl Into<this new enum> that can either represent the ID or the lineage. A Handle could convert into the ID variant, preserving the current behavior. Maybe Handle could get a lineage method that returns a type representing the lineage, and that could convert into the lineage variant of the new type.

Not quite sure about all the specifics yet, but this idea has the advantage of preserving the stricter "compare by ID" behavior within update methods, while enabling the looser "compare by lineage" behavior to be enabled explicitly.

hannobraun commented 9 months ago

Since I last updated this issue, a new concept has emerged: That of "layers", which enrich the object graph with additional attributes, without the object graph having to know about that (thus sectioning the complexity, keeping the object graph light and simple). See https://github.com/hannobraun/fornjot/issues/2116 and https://github.com/hannobraun/fornjot/issues/2117 for more information on that.

This relates to this issue, as lineage information could be kept in a separate layer, thus enabling the functionality I've described here, without adding more complexity to Handle or the central object storage.

hannobraun commented 7 months ago

It looks like I almost implemented this accidentally, while I worked on https://github.com/hannobraun/fornjot/issues/2117.

There's a derive operation now, which encodes exactly the information that is needed to track lineage. So far, this is only used to update the presentation layer, but it should be fairly trivial now to add a "lineage" layer that tracks this information and provides support for queries like ("do objects A and B share the same lineage").

Then there's https://github.com/hannobraun/fornjot/issues/2164, which tracks the introduction of better selector infrastructure. It might be best to wait until that issue is implemented, then take another look at this one.