Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

CHASM: tree node serialization/deserialization methods #7409

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

alexshtin
Copy link
Member

@alexshtin alexshtin commented Mar 1, 2025

What changed?

Added methods to serialized/deserialized CHASM components to its proto representation.
Added many TODOs to address later.

Why?

Part of CHASM worksream.

How did you test it?

Added unit tests.

Potential risks

No risks. Feature is still at the very early stage.

Documentation

I wish.

Is hotfix candidate?

No.

@alexshtin alexshtin requested a review from a team as a code owner March 1, 2025 01:30
@alexshtin alexshtin marked this pull request as draft March 1, 2025 01:32
Copy link
Member

@yycptt yycptt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't review the serialize logic yet but I feel it's better to split it into a separate PR given how complex it is (we need to update tree structure as well). Let's focusing on getting the decoding part fully implemented?

@alexshtin alexshtin force-pushed the feature/chasm-tree branch from 79920a2 to f727af1 Compare March 15, 2025 00:01
@alexshtin
Copy link
Member Author

Sorry, I did rebase and most of the comments are "Outdated" now. Which is actually true because I changed a lot. I addressed all comments though.

@alexshtin alexshtin changed the title CHASM: basic serialization/deserialization helpers CHASM: tree node serialization/deserialization methods Mar 15, 2025
@alexshtin alexshtin marked this pull request as ready for review March 15, 2025 00:17
Copy link
Member

@yycptt yycptt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind give an overview of what are the main helper methods introduced by this PR and how they should be used by the closeTransaction() implementation?

// IsDirty returns true if any node rooted at Node n has been modified.
// The result will be reset to false after a call to CloseTransaction().
func (n *Node) IsDirty() bool {
panic("not implemented")
return n.dirty
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs to recursively check all child nodes. The dirty flag is only for the current node, otherwise we can't compute mutations at the end of a transaction.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Not part of this PR though. I am adding your comment as TODO there.

continue
}

// chasm.Field field must be a pointer, i.e. *chasm.Field[T].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it has to be a pointer. As long as the registered component that's using chasm.Field is a pointer we should be good I think.
Is there a case you have in mind that doesn't work when Field is used without a pointer?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just don't want to support both cases and existing Field constructors return *Field. I agree that Field look more natural in component definition than *Field. I am flipping it to use Field and forbid *Field.

Comment on lines +486 to +487
// TODO: error or just go around?
// return serviceerror.NewInternal("unsupported field type in component " + fieldT.String())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's be strict in the beginning I'd say. It's always easier to relax a check than enforcing one.

}

n.serializedValue.GetComponentAttributes().Data = blob
n.serializedValue.GetComponentAttributes().Type = nodeValueT.Elem().String() // type name w/o *
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be the registered component name, no go type name.


n.serializedValue.GetComponentAttributes().Data = blob
n.serializedValue.GetComponentAttributes().Type = nodeValueT.Elem().String() // type name w/o *
if n.serializedValue.GetInitialVersionedTransition() == nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initial versioned transition should only be set when the creating a new chasm node. It looks like you can do this as part of setValue(), though I am not 100% sure when that method will be called.

Then LastUpdateVersionedTransition is updated every time the node is mutated. Guess you can do it here.

if err := childNode.deleteChildren(removedPaths, nil, append(currentPath, childName)); err != nil {
return err
}
path, err := n.pathEncoder.Encode(n, append(currentPath, childName))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
path, err := n.pathEncoder.Encode(n, append(currentPath, childName))
path, err := n.pathEncoder.Encode(childNode, append(currentPath, childName))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦‍♂️

panic("not implemented")
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about newly added fields? when will we add them to the child map?

Is that logic reflectSubcomponents or setValues()? I guess I am a bit confused where/when those two methods will be invoked.


type (
// TestComponent is a sample CHASM component used in tests.
// It would be nice to move it another package, but this creates a circular dependency.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you move the tests that need these and these test components into a separate package (like chasm_test)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to do it. Unfortunately in this case I will need to export almost everything which I don't want to do. I hope later, I will figure out how to write better tests w/o accessing many unexported fields.
But I am 100% with you on this (registry_test.go is in chasm_test), and I will do it later.

return f.Name
}

func (n *Node) reflectSubcomponents() error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • (minor) How about, updateNodeTree or updateSubcomponents?
  • can we add a doc on what this method does? Maybe something like, "creates the internal node tree structure based on the root node's value?"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to setChildNodes (similar to setValue). Added doc.

// - when a child is removed, all its children are removed too.
//
// Returns slice of paths to removed nodes.
func (n *Node) syncChildren() ([]string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When is this called in a node's/component's lifecycle? Is it just within CloseTransaction to get a list of removed paths, after an application deletes some of its nodes?

Comment on lines +205 to +207
for i := 0; i < nodeValueT.Elem().NumField(); i++ {
fieldV := nodeValueV.Elem().Field(i)
fieldT := fieldV.Type()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered creating a Walk method for walking a node's tree, to share this logic between reflectSubcomponents/{de}serializeComponentNode/syncChildrenInternal?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't but I guess I should. I am trying to make it simple and dumb for now. Adding TODO for now.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants