Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Improve Core.Data Serialization and Add Remember Node #14111

Merged
merged 46 commits into from
Nov 14, 2023

Conversation

saintentropy
Copy link
Contributor

@saintentropy saintentropy commented Jun 26, 2023

Purpose

The purpose of this PR is to improve the implementation of the Data.ParseJson and Data.StringifyJson to support json serialization of most Dynamo OOB data types. Today these Data nodes fail to serialize or later to deserialize most Objects vs Value types. Geometry for example always fails in StringifyJson. This PR will add support for the following Data found in Dynamo OOB.

  • ProtoGeometry types with the exception of Topology (as Topology does not make as much sence in isolation from the source)
    - Primitive Types (BoundingBox, CoordinateSystem, Plane, Vector, Arc, Circle, Ellipse, EllipseArc, Line, NurbsCurve, PolyCurve, Polygon, Point, and NurbsSurface) are supposed via schema defined in Autodesk Schema Service
    - Dynamo Geometry Types (Rectangle, Cuboid, Cone, Cylinder, Sphere, and Mesh) are supported via Dynamo specific schema.
    - Complex Geometry Types (PolySurface, Helix, Curves, Surfaces, Solids, TSplines) are supported via SAB and TSM

  • Dynamo Core Types including Color, Location and Image (via png)

This PR also moves the implementation of the Remember node from the Generative Design package to Core. The implementation of the Remember Node is identical but the serialization format has been updated to use Data.ParseJson and Data.StringifyJson. The version in GD will be marked as deprecated and removed from the package in future releases. Due to the format change we are not migrating the node.

image

Declarations

Check these if you believe they are true

  • The codebase is in a better state after this PR
  • Is documented according to the standards
  • The level of testing this PR includes is appropriate
  • User facing strings, if any, are extracted into *.resx files
  • All tests pass using the self-service CI.
  • Snapshot of UI changes, if any.
  • Changes to the API follow Semantic Versioning and are documented in the API Changes document.
  • This PR modifies some build requirements and the readme is updated
  • This PR contains no files larger than 50 MB

Release Notes

Data.ParseJson and Data.StringifyJson nodes have been improved to support serialization of most Dynamo standard data types. This includes all Geometry types except Topology and Color, Location and Image.

Data.Remember node has been moved from the GenerativeDesign package to Dynamo core library. The new node has improved accuracy in how it stores and restores data.

Reviewers

@sm6srw @QilongTang Please assign

FYIs

@mjkkirschner @twastvedt @LongNguyenP

@mjkkirschner
Copy link
Member

@saintentropy this build is broken.

@LongNguyenP
Copy link
Contributor

The build failed because Jenkins could not get the correct LibG nuget package with the json (de)serializaition method, which is actually not available yet.

[NodeName("Remember")]
[NodeDescription(nameof(Properties.Resources.RememberDescription), typeof(Properties.Resources))]
[NodeCategory("Core.Data")]
[InPortNames(">")]
Copy link
Contributor

Choose a reason for hiding this comment

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

??

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Node is designed to look like a watch node with ">" port strings

#if NET6_0_OR_GREATER
[SupportedOSPlatform("windows")]
#endif
private class ImageConverter : JsonConverter
Copy link
Member

Choose a reason for hiding this comment

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

perhaps this should be called PNGImageConverter?

{
private string cache = "";

[JsonProperty("Cache")]
Copy link
Member

Choose a reason for hiding this comment

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

why do you need this when the property has the same name?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch

{
switch (e.PropertyName)
{
case "State":
Copy link
Member

Choose a reason for hiding this comment

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

nameof?

}
}

if (jObject.ContainsKey("typeid"))
Copy link
Member

Choose a reason for hiding this comment

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

whats the difference between serializations that contain "typeid" and "$typeid" ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In ASP json that us $typeid as reserved keyword for the type. In the changeset world they used typeid


case "dynamo.data:location-1.0.0":
return DynamoUnits.Location.ByLatitudeAndLongitude(
(double)jObject["Latitude"],
Copy link
Member

Choose a reason for hiding this comment

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

curious - what are your thoughts about this vs adding protected or internal json constructors and just letting newtonsoft figure it out?

I'm fine with this for now, just wanted to evaluate the +/-s

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that is better approach. I was waiting for the .NET7 so we could introduce an interface to support serialization registration similar to the IGraphicItem. (static abstract is now supported in interfaces NET7 and above). I can do this now without the interface for the internal types just to set the pattern.

Copy link
Member

Choose a reason for hiding this comment

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

ah I see, so we'll have to wait on the interface approach until after we move to .net 8... I guess you could do it now and change the language target of this csproj to 11?

Copy link
Member

Choose a reason for hiding this comment

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

What would the interface look like?

// Verify objects match when serializing / de-serializing geometry type
AssertPreviewValue("abb39e07-db08-45cf-9438-478defffbf68", true);

// Verify current failure cases are all false
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 understand this comment.

}

[Test]
[Category("Failure")]
Copy link
Member

Choose a reason for hiding this comment

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

why does this fail? Can you add a TODO, task number etc?

}

[Test]
[Category("Failure")]
Copy link
Member

Choose a reason for hiding this comment

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

why does this fail - etc

}

[Test]
[Category("Failure")]
Copy link
Member

Choose a reason for hiding this comment

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

why does this fail

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Failures are waiting for the upstream PR in LibG

[Category("UnitTests")]
public void RoundTripForColorReturnsSameResult()
{
var color = DSCore.Color.ByARGB(25, 30, 35, 40);
Copy link
Member

@mjkkirschner mjkkirschner Nov 10, 2023

Choose a reason for hiding this comment

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

Is there any benefit to the preceding tests being implemented using graphs instead of implementing them to how these tests are written without involving graph deserialization? I find them easier to debug. The graph ones in my opinion are opaque.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah... Sorry I was just following the pattern of the other tests for Json. Also added benefit of much greater coverage. We can create a follow up task and pull all the tests into unit test style if it is critical

Copy link
Member

Choose a reason for hiding this comment

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

nah, not critical, maybe just preferred for future tests.

public static bool CanObjectBeCached(object inputObject)
{
if (inputObject == null
|| (inputObject is string inputString && inputString == "null")
Copy link
Member

Choose a reason for hiding this comment

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

when do we have a "null" string?

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 an empty string?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Empty string is ok :-)


[Test]
[Category("UnitTests")]
public void CanObjectBeCachedRejectsEmptyString()
Copy link
Member

Choose a reason for hiding this comment

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

this test seems to have the wrong name.

@@ -0,0 +1,4 @@
## Remember - Documentation
This documentation file is auto generated by NodeDocumentationMarkdownGenerator, Version=2.13.0.2212, Culture=neutral, PublicKeyToken=null.
Copy link
Member

Choose a reason for hiding this comment

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

this seems like a great node to supply better documentation for.
FYI @hwahlstrom

@@ -141,6 +141,12 @@
<data name="EnumDateOfWeekWednesday" xml:space="preserve">
<value>Wednesday</value>
</data>
<data name="Exception_Deserialize_Unsupported_Type" xml:space="preserve">
<value>The stored data can not be rebuilt.</value>
Copy link
Member

Choose a reason for hiding this comment

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

loaded?

}
catch(Exception ex)
{
throw new NotSupportedException(string.Format(Properties.Resources.Exception_Serialize_Unsupported_Type, inputObject.GetType().FullName));
Copy link
Member

Choose a reason for hiding this comment

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

would it be helpful to also log the real exception to the console or concat it?

}
}

private class ColorConveter : JsonConverter
Copy link
Member

Choose a reason for hiding this comment

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

why use a bunch of converters for this instead of decorating the class with attributes. I guess I get a bit of a code smell when the serialization and deserialization are handled in different places, one using a converter and one using regular constructors. Can it be unified?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the main reason is the overlap with the node -> file serialization and the node object -> data serialization. I hope to introduce the interface so that it can be explicit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nevermind.... file stuff doesn't apply to ZT. But would be handled by the interface

resultAst.Add(AstFactory.BuildAssignment(functionCallIndent, functionCall));

//Next add the first key value pair to the output port
var getFirstKey = AstFactory.BuildFunctionCall(BuiltinDictionaryTypeName, BuiltinDictionaryGet,
Copy link
Member

Choose a reason for hiding this comment

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

I think the ASTFactory somewhere has methods for building these calls easily.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not that I am aware of or could find. The main thing we do here is that we need to pass the data from the inputPort and the cached string into and back out of the node every time. The dictionary is used so that we can only present to the ports data on the node and keep the cache data internal. I think we look long and hard at the time we wrote this and didn't have any out of the box options of dictionaries in ASTFactory.

Copy link
Member

Choose a reason for hiding this comment

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

look for:
DictionaryExpressionBuilder

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahhh. Thanks. I did look at that. Interesting but not exactly what we need. The dictionary is already set up by the function we call Remember() as the return type. The AST construction we are doing is to look up specific keys later... More specifically DictionaryBuilder embeds the constuctor DesignScript.Builtin.Dictionary.ByKeysValues whereas we need the method DesignScript.Builtin.Dictionary.ValueAtKey. There could be a helper for us but not this one.

# Conflicts:
#	src/Libraries/CoreNodeModels/CoreNodeModelsImages.resx
#	src/Libraries/CoreNodeModels/Properties/Resources.en-US.resx
#	src/Libraries/CoreNodeModels/Properties/Resources.resx
#	test/Tools/docGeneratorTestFiles/sampledictionarycontent/Dynamo_Nodes_Documentation.json
Copy link
Member

@mjkkirschner mjkkirschner left a comment

Choose a reason for hiding this comment

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

@saintentropy lets ship it.

@saintentropy
Copy link
Contributor Author

Cool... Will merge when CICD confirms

@saintentropy
Copy link
Contributor Author

@saintentropy saintentropy merged commit 8af516c into DynamoDS:master Nov 14, 2023
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants