You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
graph TD
A["Document
(GenServer)
PID: #PID<0.120.0>"] --> B["vml
(Element GenServer)
PID: #PID<0.121.0>"]
B --> C["head
(Element GenServer)
PID: #PID<0.122.0>"]
B --> D["body
(Element GenServer)
PID: #PID<0.123.0>"]
D --> F["Text
(Element GenServer)
PID: #PID<0.125.0>"]
F --> H["'Hello World'
(Text GenServer)
PID: #PID<0.127.0>"]
classDef document fill:#e1f5fe,stroke:#01579b,stroke-width:2px
classDef element fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
classDef text fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px
class A document
class B,C,D,F element
class H text
Loading
The core concept that the DOM tree is compirsed of Document that contains Nodes. Nodes can be Document, Element, or Text (currently).
The current implementation
As of this writing I implemented this quickly with the following:
When a new DOM is created each child will first be created. Document, Element, and Text are all valid nodes as they use GenDOM.Node. When a new Node is created a name is autogenerated for the GenServer. (I am considering dropping this in favor or just pure PIDs soon)
Then a Node should "join" a parent through one of the following:
append_child
insert_before
replace_child
When these functions are called the child is added in the correct position in child_nodes. The node is then passed to the parent's track callback which will Process.monitor the child and add its name to the all_child_namesMapSet field of the state. Then GenServer.cast(parent.parent, {:track, child}) and the child is passed up the tree where each parent is going to track all of it's descendants. We do this so that we can implement faster querying for nodes. I suspect this will also be beneficial for the DOM Event system and event bubbling.
Process groups?
Internally the LVN team has discussed the idea of using Process groups instead of the above implementation. Where each parent is a group leader, and all children are in the group of each. However, the Elixir implementation of this doesn't permit multiple groups. And the :pg Erlang implementation doesn't guarantee ordering, let alone allow for node insertion at certain indices or replacing children. Looking at the Erlang implementation of :pg and I can easily replicate and extend this in Elixir for a custom Process group implementation that supports our needs: https://github.com/erlang/otp/blob/OTP-28.0/lib/kernel/src/pg.erl#L703-L718
However, would this be any better? I don't yet have benchmarks on the current implementation and so far it meets our needs. But it may not be the most idiomatic way to go about this.
graph TD A["Document (GenServer) PID: #PID<0.120.0>"] --> B["vml (Element GenServer) PID: #PID<0.121.0>"] B --> C["head (Element GenServer) PID: #PID<0.122.0>"] B --> D["body (Element GenServer) PID: #PID<0.123.0>"] D --> F["Text (Element GenServer) PID: #PID<0.125.0>"] F --> H["'Hello World' (Text GenServer) PID: #PID<0.127.0>"] classDef document fill:#e1f5fe,stroke:#01579b,stroke-width:2px classDef element fill:#f3e5f5,stroke:#4a148c,stroke-width:2px classDef text fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px class A document class B,C,D,F element class H textThe core concept that the DOM tree is compirsed of
Documentthat containsNodes.Nodescan beDocument,Element, orText(currently).The current implementation
As of this writing I implemented this quickly with the following:
When a new DOM is created each
childwill first be created.Document,Element, andTextare all valid nodes as theyuse GenDOM.Node. When a newNodeis created anameis autogenerated for theGenServer. (I am considering dropping this in favor or just pure PIDs soon)Then a
Nodeshould "join" a parent through one of the following:append_childinsert_beforereplace_childWhen these functions are called the child is added in the correct position in
child_nodes. Thenodeis then passed to the parent'strackcallback which willProcess.monitorthe child and add its name to theall_child_namesMapSetfield of the state. ThenGenServer.cast(parent.parent, {:track, child})and the child is passed up the tree where each parent is going to track all of it's descendants. We do this so that we can implement faster querying for nodes. I suspect this will also be beneficial for the DOM Event system and event bubbling.Process groups?
Internally the LVN team has discussed the idea of using Process groups instead of the above implementation. Where each parent is a group leader, and all children are in the group of each. However, the Elixir implementation of this doesn't permit multiple groups. And the
:pgErlang implementation doesn't guarantee ordering, let alone allow for node insertion at certain indices or replacing children. Looking at the Erlang implementation of:pgand I can easily replicate and extend this in Elixir for a custom Process group implementation that supports our needs: https://github.com/erlang/otp/blob/OTP-28.0/lib/kernel/src/pg.erl#L703-L718However, would this be any better? I don't yet have benchmarks on the current implementation and so far it meets our needs. But it may not be the most idiomatic way to go about this.