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

Refactor of way serialization of layers is done within a maya scene file #53

Merged
merged 32 commits into from
Jan 16, 2018

Conversation

pmolodo
Copy link
Contributor

@pmolodo pmolodo commented Dec 13, 2017

Removed per-layer nodes, and mapping of inter-relationships between them, as they were not needed to handle serialization. Instead now uses a single layerManager node to store serializations (one per maya scene.) Fixes some stability issues, and some issues with serializations being lost in some cases.

previously, was using connection callbacks, which is fragile; now uses normal dependency graph push / pull of data to trigger updates of stage / prim
simplifies serialization, makes it more reliable in edge cases
...and not for every proxyShape in the scene, every time any reference is loaded
on the transform node, a connection is made from the stageOut to the stageIn using the modifier, but then the primPath is set without it.  Therefore, the primPath set happens FIRST, when there is no stage
Copy link
Contributor

@dbaz dbaz left a comment

Choose a reason for hiding this comment

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

Thanks for your time! There is a few comments below for discussion.

Seems like we may not need layer.h anymore? nuke it if you get the chance or we will do it at some point.

I'll talk with the team about the singleton style part of the LayerManager and let you know. Talked with Eoin, he mentioned he talked to you about it and it was OK'd by us.

In regards to the mutex locks in the LayerManager, are you guys expecting, or are, adding layers in different threads? Just for my interest

@@ -153,8 +208,8 @@ void Global::onPluginLoad()
m_fileNew = MSceneMessage::addCallback(MSceneMessage::kAfterNew, onFileNew);
m_preSave = MSceneMessage::addCallback(MSceneMessage::kBeforeSave, preFileSave);
m_postSave = MSceneMessage::addCallback(MSceneMessage::kAfterSave, postFileSave);
m_preOpen = MSceneMessage::addCallback(MSceneMessage::kBeforeOpen, preFileOpen);
m_postOpen = MSceneMessage::addCallback(MSceneMessage::kAfterOpen, postFileOpen);
m_preRead = MSceneMessage::addCallback(MSceneMessage::kBeforeFileRead, preFileRead);
Copy link
Contributor

Choose a reason for hiding this comment

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

From quickly browsing the docs it looks like k(Before|After)FileRead also triggers when a Reference in Maya is created. So if I had a variantSet and switched it to a variant that contained a Prim of type MayaReference, which will load a MayaReference, it will trigger preFileRead(); From my understanding. Will that cause problems in your postFileRead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, this should work fine - we control whether assets are brought in as usd/hydra or MayaReference types with variants in our own pipe, and it hasn't caused problems. In theory, if your referenced scene in turn had it's own ProxyShape, you'd want this to trigger anyway (though I wouldn't really recommend doing that, it's not really heavily tested).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, and regarding layer.h - it's there just to provide a stub implementation of the layer nodes. They no longer do anything, but the node is still provided by the plugin so that if you open a scene that was saved using old-style-layer serialization, it won't error - all the same attributes, etc are there.

Of course, any serialized layer changes you'd made in the old format will no longer be recognized / will be lost. At some point in the future, we could potentially write some upgrade / transition code, which reads the old layer nodes. Actually, probably not even be hard to do... would just had to add a loop inside of the postRead callback that loads all the layers...

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good about the references not being a problem still!

Sure, no harm in leaving the node code there I guess, we will remove it at some point.

Cheers!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, and regarding the mutex locks - as far as I know, we're not loading layers in a multithreaded manner. The locks are more from paranoia... I don't know they COULDN'T be loaded in a multithreaded manner (I haven't really looked into the way maya's new multi-threaded node evaluation works, for instance), and it's essentially a globally shared resource, so I figured better be safe.

return MObject::kNullObj;
}
////----------------------------------------------------------------------------------------------------------------------
//MObject LayerCommandBase::getSelectedNode(const MArgDatabase& args, const MTypeId typeId)
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder to just delete this commented out code before merging

MGlobal::displayError("Cannot query many names on muted layers (they layers haven't been imported into Maya)");
return MS::kFailure;
}
// TODO: figure out what to do with "-identifiers" flag. Unlike other
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm thinking that we should make it like the others and return the identifier when the flag is on and the displayName otherwise, and make sure we document it in the ChangeLog

I'll talk with the other peeps and see what they think

@@ -1041,6 +674,9 @@ AL_MAYA_DEFINE_COMMAND(LayerSave, AL_usdmaya);
MSyntax LayerSave::createSyntax()
{
MSyntax syn = setUpCommonSyntax();
// TODO: -l is currently required, convert to an arg... but need
Copy link
Contributor

Choose a reason for hiding this comment

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

Your comment about converting -l to an arg sounds good

}
return status;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you need to do a dgmod.doit() at the end?

@@ -636,6 +623,18 @@ class ProxyShape
/// \param[in] changedPaths are child paths that existed previously and may not be existing now.
void onPrimResync(SdfPath primPath, const SdfPathVector& changedPaths);


// \brief Serialize information unique to this shape
void serializeThis();
Copy link
Contributor

Choose a reason for hiding this comment

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

Drop the This I think it is self documenting that it will save itself, since is a member method

@@ -962,12 +952,11 @@ void ProxyShape::variantSelectionListener(SdfNotice::LayersDidChange const& noti
}

//----------------------------------------------------------------------------------------------------------------------
void ProxyShape::reloadStage(MPlug& plug)
void ProxyShape::loadStage()
Copy link
Contributor

@dbaz dbaz Dec 21, 2017

Choose a reason for hiding this comment

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

I liked the reloadStage() name, because when stuff changes, such as the m_fileName, the name made a bit more sense in that context, because you want to reload the stage. What do you think @robthebloke ?

Copy link
Contributor

Choose a reason for hiding this comment

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

@dbaz I still like loadStage because: If the file name changes, its reading from a whole new source and USD treats it like a different stage in the cache. Part of the motivation for changing it was that it was getting called repeatedly (for other reasons than the file name change) and each time it was adding a new stage to the stage cache because they each had a unique anonymous session layer. Naming it loadStage() discourages using it simply to refresh the data on the same stage.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, sure. Thanks for the explanation.

if(plug == m_primPath)
{
MDataBlock dataBlock = forceCache(*(MDGContext *)&context);
MString path = handle.asString();
outputStringValue(dataBlock, m_primPath, path);
SdfPath primPath(path.asChar());
StageData* data = inputDataValue<StageData>(dataBlock, m_inStageData);
if(data)
if (data && data-> stage)
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove the space 'data-> stage'

@@ -116,7 +116,7 @@ void TransformationMatrix::setPrim(const UsdPrim& prim)
m_xform = UsdGeomXform();
}
m_time = UsdTimeCode(UsdTimeCode::Default());
m_flags = 0;
m_flags &= kPreservationMask;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should stay = 0, there is some code that will inspect the prim and set the correct flags, I hope?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For most of the flags, yes, they are read from the prim - however, there are two flags, kPushToPrimEnabled and kReadAnimated, that are controlled not by the prim, but by attributes on the node. These should NOT be reset when the prim is reset, thus the need for this flag.

I document this above the definition of kPreservationMask, but I'll copy that here for clarity.



//----------------------------------------------------------------------------------------------------------------------
// TODO: make it take an optional MDGModifier
Copy link
Contributor

Choose a reason for hiding this comment

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

sounds handy


/// \brief Find the already-existing non-referenced LayerManager node in the scene, or return a nullptr
/// \return the found LayerManager, or a nullptr
static LayerManager* findManager();
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we have one method, findOrCreateManager()? and remove findManager? If we a buying into a singleton, I'd expect it to be created once the first time I access the LayerManager, or straight up create a LayerManager Node when the AL_USDMaya plugin is loaded and we have a scene. Otherwise I think havint the two methods can complicate the code, since it seems like some code should call findOrCreateManager and some code should call findManager, but who really should create it?

Copy link
Contributor Author

@pmolodo pmolodo Dec 23, 2017

Choose a reason for hiding this comment

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

Not sure I completely follow what you mean by this:

"some code should call findOrCreateManager and some code should call findManager, but who really should create it?"

As for why I want two methods - basically, findManager() doubles as a "doesManagerExist()" query. Yes, we could take the approach of always force-creating a manager whenever we do any sort of querying (or even on plugin load), but I'm strongly against this, because this involves creating a node in the scene. That then means that your scene now requires this plugin, even if it really doesn't - ie, if you have AL_USDMaya loaded, open a new scene, then save out a blank scene, it now has a requirement to load AL_USDMaya.

We've had a few plugins from third-party vendors that operated like this, and they're highly annoying - the plugin ends up acting like a virus that infects scenes: when anyone opens such a scene, then opens and saves out another scene - even if that other scene doesn't need the plugin at all - they spread the "infection" to the other scene.

In our two studios, we may not care much, because I'm guessing we'll both nearly always want this plugin loaded, anyway, but it's good plugin-hygiene to only create nodes if the scene in question actually needs them.

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess I was seeing the LayerManager as the style of singleton where if you "access it, you create it" because otherwise you will have 2 methods as you have, a FindAndCreate and a Find, which I think can cause confusion about when the creation of the LayerManager should happen. i.e. when should I call FindAndCreate?

But I see why you have done it this way, because you only want to create it when there is actually a ProxyShape in the scene which seems fair enough!

@alekamca
Copy link
Contributor

I like the idea of the singleton LayerManager in general, but I wanted to flag that we've also made a bunch of changes with the Layer nodes (many of which we were planning to contribute back). I'm sure we can find a way to make this new way work for us, but I thought I'd share what we're trying to do in case it throws up any red flags.

We've added a lot of guards related to handling cases where something bad has happened to the usd file associated with the proxy shape, including missing layer files. This is much more likely for us because we also are storing unresolved partial paths for some layers to support the way we publish assets. Long story short, we utilize the process of layer initialization in this PR (#28 sorry--still getting around to cleaning this up and resubmitting) to detect if something has gone wrong at the layer level. If it has, we notify the associated ProxyShape and put it into an error state (with hopefully enough debug information to help users find their way out of the problem).

@pmolodo
Copy link
Contributor Author

pmolodo commented Dec 22, 2017

@alekamca - Hmm... I can see your concern. The previous node system actually traversed / mapped out the whole layer hierarchy / inter-relationships, and recorded them in the maya nodes... initially just for the purposes of visualization, but it wasn't necessary for serialization, so I removed this traversal in my PR.

However, I think this sort of roll-your-own layer traversal is probably not preferred anyway... and I image there must be a way either be notified of errors on layer load, or finding them after the fact. I'll ask in the USD mailing list...

@alekamca
Copy link
Contributor

@elrond79 Yes, I agree that we should be using whatever facilities USD would prefer us to use. My only other thought is that by encoding the layer hierarchy in the maya scene, we can also tell whether or not the hierarchy has changed. We aren't doing this right now, but I can imagine cases where this would be useful to know, otherwise our layer deserialization may not make sense.

Somewhat related, I don't know that we even care to have the maya files carry around this layer serialization anyways. We might prefer a preFileSave callback that just saves the USD stage, so it might all be irrelevant.

@chadrik
Copy link

chadrik commented Dec 22, 2017

My only other thought is that by encoding the layer hierarchy in the maya scene, we can also tell whether or not the hierarchy has changed.

@alekamca I believe that continually rewiring the node network to reflect the stage requires a lot of work to make accurate (and may not even be possible based on lack of valid layer identifiers over time) and Maya is not very resilient when it comes to transient nodes and connections. It's just asking for trouble without much benefit. That's my high level understanding of the problem: I'll let @elrond79 fill in the details.

Somewhat related, I don't know that we even care to have the maya files carry around this layer serialization anyways. We might prefer a preFileSave callback that just saves the USD stage, so it might all be irrelevant.

Yes, we also considered this, and there's a lot to like about storing this data in a side-car file that can be accessed outside of Maya. It'll take some wrangling to make sure that callbacks are always firing at the right moments so that data is not lost. Xgen pre-2017 is a good example of how this approach can go wrong, but I think it's a solvable problem and we'd love to see this become an option.

@pmolodo
Copy link
Contributor Author

pmolodo commented Dec 23, 2017

@dbaz - ok, I think that addresses all of your notes, save for the issue with re-serialization of session layers, as I noted above. I'll hopefully have a fix for that soon...

Thanks greatly for the extra eyes on this, always appreciated!

@dbaz
Copy link
Contributor

dbaz commented Jan 1, 2018

thanks @elrond79 all seems good. I just have a question about the session layer saving code and if you are going to do the change you mentioned now or later?

@pmolodo
Copy link
Contributor Author

pmolodo commented Jan 3, 2018

I'm starting working on it now, should hopefully be done in a few days...

@chadrik
Copy link

chadrik commented Jan 3, 2018

@dbaz @robthebloke Would you prefer to wait for that change, or merge this now and take the change as a second PR?

@dbaz
Copy link
Contributor

dbaz commented Jan 3, 2018

Prefer it to be done during this PR since I want to reduce the possibility of serialisation/deserialisation maya file issues for people.

Conflicts:
	lib/AL_USDMaya/AL/usdmaya/nodes/ProxyShape.cpp
…K_ERROR_RETURN inserted

makes sense for AL_MAYA_CHECK_ERROR_RETURN / AL_MAYA_CHECK_ERROR_RETURN_NULL_MOBJECT to be
next to each other

(also makes merge with pr/use_PxrUsdMayaXformStack easier)
...because boost::multi_index won't allow us to have two identifiers for same layer
also, if have a transform with multiple shapes, check all to try to find a proxyShape (not just the first)
...not just the ones already in the LayerManager
also, added a helper to allow unconditionally adding flags to the command to execute - CommandGuiHelper::addExecuteText
...now that I've realized I can just forward declare LayerManager, since I only need a pointer for the header...
...then, when we go to serialize we do one last check, to see if current target is dirty
@pmolodo
Copy link
Contributor Author

pmolodo commented Jan 13, 2018

Ok, made it so session layers are now serialized on the LayerManager, which should resolve the duplicate-serialization. Also fixed a few other bugs (one where layers were not being serialized in some situations, and one with the SetEditTarget gui).

Copy link
Contributor

@dbaz dbaz left a comment

Choose a reason for hiding this comment

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

Cheers! Few small comments, mainly the ones referring to compilation/maya compatibility issues I'm getting when building.

{
MStringArray result;
MSelectionList sl;
AL_MAYA_CHECK_ERROR_RETURN_VAL(MGlobal::getActiveSelectionList(sl), result,
Copy link
Contributor

Choose a reason for hiding this comment

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

Where is AL_MAYA_CHECK_ERROR_RETURN_VAL declared? I'm getting a compilation error regarding it

MPlug anonymousPlug = layersPlug0.child(tempNonConst, &status);
ASSERT_TRUE(status);

ASSERT_EQ(MString(realLayer->GetIdentifier().c_str()), idPlug.asString(&status));
Copy link
Contributor

Choose a reason for hiding this comment

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

This asString isn't compatible with maya2017 Update3, can we use asString(DGContext::fsNormal, &status), also for the asBool calls?

{
// Just throwing, if we ever need to use a string with weird chars, need to
// update this function...
throw std::logic_error("CommandGuiHelper::addExecuteText may only be called with normal printable characters");
Copy link
Contributor

Choose a reason for hiding this comment

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

Having a bit more information about which character is the 'bad' one would be nice, Could we print out the index of the character in the string?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, could make this a std::cerr and a return. Seems a bit harsh to throw an exception for this case.

note that addLayer/removeLayer changed to take a LayerHandle (WeakPtr) instead of a RefPtr, due to behavor where python wrapping downgrades a RefPtr to a WeakPtr after it has been passed to a function taking a RefPtr
migrated from pr/use_PxrUsdMayaXformStack branch
@pmolodo
Copy link
Contributor Author

pmolodo commented Jan 16, 2018

Ok, adressed your notes - thanks!
Also added a wrapper for the LayerManager (7209fae) since we needed that over here...

@dbaz
Copy link
Contributor

dbaz commented Jan 16, 2018

Thanks! I will look at merging this ASAP

@dbaz dbaz merged commit 9fd4545 into AnimalLogic:develop Jan 16, 2018
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.

None yet

5 participants