-
Notifications
You must be signed in to change notification settings - Fork 0
Sets #4
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
base: master
Are you sure you want to change the base?
Conversation
donboie
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this is the first step and I hope not too contencious. I'll work on fixing what I percive to be problems & propose how I shold continue.
src/IECoreScene/SceneCache.cpp
Outdated
| { | ||
| ReaderImplementation *nc = const_cast<ReaderImplementation *>(this); | ||
| ConstPathMatcherDataPtr childSets = nc->child( c, SceneInterface::NullIfMissing )->readSet( name ); | ||
| pathMatcherData->writable().addPaths(childSets->readable(), { c }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will add a level to PathMatcher data after we leave a recursion level which I think can be avoided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand. I think it's probably worth not using PathMatcherData at all during the recursion, as we're just making a wrapper that we throw away at each step. Instead we could return PathMatcher directly at each recursion and just wrap it in the outer layer. Perhaps that what you were getting at?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead we could return PathMatcher directly at each recursion and just wrap it in the outer layer. Perhaps that what you were getting at?
I don't think so. I was going to recurse to find all the PathMatchersData and return a list of [ ( prefix , PathMatcherData ), ... ] which I could perform PathMatcher.addPaths on the number of defined sets rather than depth I have to recurse to find a set.
Or perahps I return nothing and pass the PathMatcher in the recursion and just add to it when I find a local set.
Just wanted to mention I'll try to replace PathMatcherData with PathMatcher in anycase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or perahps I return nothing and pass the PathMatcher in the recursion and just add to it when I find a local set.
Sounds fine.
I don't think so.
Obviously I haven't tested, but I would expect my suggestion to have comparable performance to the one you suggest above. In case it isn't obvious what I'm suggesting, I used the same approach recently in the SceneGadget.
src/IECoreScene/SceneCache.cpp
Outdated
|
|
||
| for( const auto &c : children ) | ||
| { | ||
| ReaderImplementation *nc = const_cast<ReaderImplementation *>(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a variation of the const problem I had with the SceneInterface getRoot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, so this is because ReaderImplementation::child() is non-const? What happens if we make it const?
src/IECoreScene/SceneCache.cpp
Outdated
| } | ||
| } | ||
|
|
||
| std::set<SceneInterface::Name> unique(setNames.begin(), setNames.end()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we should add a typedef setSceneInterface::Name NameSet to SceneInterface? What do you think to updating the setNames return to NameSet?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My guess is that using std::set to make things unique is going to be a fair bit slower than a combo of std::sort and std::unique done in-place on the vector itself. I can see the logic in using a datatype that enforces uniqueness for the return value, but I suspect this is of less practical use to the caller than performance is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Referring to code like this:
NameList children;
childNames( children );
How do you feel about updating functions which return containers as reference parameters to functions which directly return them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'd like that, but I'd also leave it out of the sets work. Let's just focus on the sets for now. Maybe there's even an argument for making setNames() be pass-by-reference and then updating everything all at once later?
And I guess pass-by-reference is still better if I happen to have a suitable container already, for instance if I had a list of locations to iterate over, and at each step needed to call setNames(). I could just make a container outside the loop and then reuse it for each step.
johnhaddon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Don,
This is great - feels much better starting simply with "the bits we'll keep in the future" than diving straight in to the really complex tags<->sets interaction. I was overwhelmed by the complexity of the earlier stab, although the fact that I've now had a proper night's sleep is probably helping too.
I suspect we're going to want to do something similar to what we did for tags, so at each location we store a "descendant set names" record which we use to limit the traversal. But starting with the basics works great for me.
Once you've taken care of the few details we've highlighted, what do you see as the next step? I think it'd be great to implement sets for USD, hopefully as another uncontentious step, and to validate the sets API against another implementation before we get in too deep.
Cheers...
John
src/IECoreScene/SceneCache.cpp
Outdated
| if( setsIO ) | ||
| { | ||
| IndexedIO::EntryIDList localSetNames; | ||
| setsIO->entryIds(localSetNames, IndexedIO::Directory); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we read straight into setNames here, rather than make a temporary, fill it, and copy?
src/IECoreScene/SceneCache.cpp
Outdated
| } | ||
| } | ||
|
|
||
| std::set<SceneInterface::Name> unique(setNames.begin(), setNames.end()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My guess is that using std::set to make things unique is going to be a fair bit slower than a combo of std::sort and std::unique done in-place on the vector itself. I can see the logic in using a datatype that enforces uniqueness for the return value, but I suspect this is of less practical use to the caller than performance is.
src/IECoreScene/SceneCache.cpp
Outdated
|
|
||
| for( const auto &c : children ) | ||
| { | ||
| ReaderImplementation *nc = const_cast<ReaderImplementation *>(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, so this is because ReaderImplementation::child() is non-const? What happens if we make it const?
src/IECoreScene/SceneCache.cpp
Outdated
| { | ||
| ReaderImplementation *nc = const_cast<ReaderImplementation *>(this); | ||
| ConstPathMatcherDataPtr childSets = nc->child( c, SceneInterface::NullIfMissing )->readSet( name ); | ||
| pathMatcherData->writable().addPaths(childSets->readable(), { c }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand. I think it's probably worth not using PathMatcherData at all during the recursion, as we're just making a wrapper that we throw away at each step. Instead we could return PathMatcher directly at each recursion and just wrap it in the outer layer. Perhaps that what you were getting at?
src/IECoreScene/SceneCache.cpp
Outdated
| { | ||
| IndexedIOPtr setsIO = m_indexedIO->subdirectory( setsEntry, IndexedIO::CreateIfMissing ); | ||
|
|
||
| const Object *o = set; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is annoying, isn't it? I think the problem is that the derived class save( SaveContext *context ) const protected method masks the base class save( IndexedIOPtr ioInterface, const IndexedIO::EntryID &name ) public method. All my fault, and I've been meaning to fix it.
For what it's worth I tend to use set->Object::save() to do it in one line and hopefully make it a bit more obvious why the jiggery pokery is needed.
|
I've address everything you've pointed out in 8415872 but I'm bothered by the ReaderImplementation::child(...) const function requiring a const cast to construct another ReaderImplementation but pointing to himself here: cortex/src/IECoreScene/SceneCache.cpp Line 627 in 8415872
I have a implementation of this which I'll cherry pick & clean up.
I'm happy to do this as it should be a simple case. |
|
I've added USD reading and writing of sets in a813f23. USD seems to have a limitation in that you can't add the location where the set is being writen to to the set. I suppose we should not write these in SceneCache also and emit the same warning as I've done here. So I can either continue with futher file format support in Alembic or tackle linked scenes, what do you think? Also I've found what looks like a bug in AddCollection in USD on a set named something like 'a:b(c)' which we have at Image Engine from Jabuka, perhaps I'll offer up a PR. I assume this work should stay in my branch for some time or can they go into master peicemeal? |
johnhaddon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added USD reading and writing of sets in a813f23.
Nice one.
USD seems to have a limitation in that you can't add the location where the set is being writen to to the set. I suppose we should not write these in SceneCache also and emit the same warning as I've done here.
That doesn't seem a particularly unreasonable restriction, but on the other hand it seems a bit unnecessary outside of USD, and it is possible to create a set with / in it in Gaffer. How about we just stick with the warning for USD only for now, and see if anything becomes a problem in practice?
| void readTags( NameList &tags, int filter ) const override; | ||
| void writeTags( const NameList &tags ) override; | ||
|
|
||
| NameList setNames() const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll need to add override here to keep Clang happy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I offer 30e9a8f to gods of CLANG!
|
Argh! GitHub sucked my review away from me before I was done. I'll copy my half-finished comment here and finish it all in one place...
Nice one.
That doesn't seem a particularly unreasonable restriction, but on the other hand it seems a bit unnecessary outside of USD, and it is possible to create a set with / in it in Gaffer. How about we just stick with the warning for USD only for now, and see if anything becomes a problem in practice?
Whatever's best for you...
Definitely sounds worth it...
Yes, I think so. I think the point to merge would be the point at which we have enough for Gaffer to stop talking tags and use sets exclusively. Bunch of random thoughts :
Cheers... |
I'll probably work on the concrete Alembic implementation first followed by a sketch out my ideas for LinkedScenes. Regarding progressing with this work here, are you happy for this PR to just be appended to?
I'm leaning towards chaning the to PathMatcher, but what if the PathMatcher implemention changes to make copying them more expensive?
Agreed. I think I read something that implied that the methods we use in reading GetCollection is thread safe where as the AddCollection variant isn't. Typical! I can't find the comment / doc page now.
Would it need to be a hash of file name, set name and location? |
For sure.
I don't think it can - we're totally reliant on those performance characteristics elsewhere...
There's also that thread on the mailing list where Spiff was telling us about a new API for finding collections.... https://groups.google.com/forum/#!topic/usd-interest/O-cKw3yr-YY UsdPrim::GetAppliedSchemas? Not sure if that's any improvement over what you're already doing though...
Yes, of course. I was thinking of Gaffer where we only read sets from the root. I think it is worth having an official way of getting the set hash out of the |
Ah. I'll add a todo to the USD implementation.
I do it now then to make the LinkedScene & Alembic implementations simpler.
So the API change would be more than creating a new member in the enum
I'm reminded of a previous discussions on changing the splitting up the hashing functions on SceneInterface, perhaps I should do this on master now before adding this setHash function? |
Having a dedicated method sounds good. We shouldn't need
Yeah, splitting up the hash method would be good. I'm tempted to say just add the |
Of course sets can't be time varying can they:
|
46c3266 to
2ef0639
Compare
|
Hi John, I've pushed this on with a few commits and wanted to have quick discussion before I attempt to progress futher. In summary I've added a noddy hashSet function, AlembicScene implementation, childSetNames to exit early from recursion and finally a sketch of the LinkedScene implementation. Regarding the LinkedScene implementation, the crux of the issue is to quickly query sets in the scenes which are linked to. In 40a7a47 there is a function called LinkedScene::linkLocations which gets all the link locations as a pathmatcher and I was thinking this can be stored in the LinkedScene cache on save and we can rely on this being quick for the setNames & readSet implementations. What do you think? There a few other places I'm going to fix up so I can flip the SceneInterface set functions to pure virtual:
Have I missed any? Cheers |
johnhaddon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
Regarding the LinkedScene implementation, the crux of the issue is to quickly query sets in the scenes which are linked to. In 40a7a47 there is a function called LinkedScene::linkLocations which gets all the link locations as a pathmatcher and I was thinking this can be stored in the LinkedScene cache on save and we can rely on this being quick for the setNames & readSet implementations. What do you think?
I'm no expert when it comes to LinkedScenes, but that sounds entirely reasonable to me.
There a few other places I'm going to fix up so I can flip the SceneInterface set functions to pure virtual...Have I missed any...
Caribou's TrackedLiveScene maybe?
Cheers...
John
src/IECoreScene/LinkedScene.cpp
Outdated
| } | ||
|
|
||
| /// serialise this into the linked scene cache so it can just be loaded directly without having to traverse the entire scene | ||
| IECore::PathMatcherDataPtr LinkedScene::linkLocations() const |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to return PathMatcherData here - we can rely on PathMatcher's lazy-copy-on-write and just return a PathMatcher directly. I think we planned to do this for readSet() and writeSet() too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we planned to do this for readSet() and writeSet() too?
Oh yeah - addressed this in my latest changes.
| NameList childSets = readChildSets(); | ||
|
|
||
| // todo need to replace this linear scan with something with faster query performance | ||
| if( std::find( childSets.begin(), childSets.end(), name ) != childSets.end() ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not happy about this linear scan to work out if we should recurse. Perhaps another approach would be to store a PathMatcher of locations with sets?
src/IECoreVDB/VDBScene.cpp
Outdated
|
|
||
| void hashSet( const Name &setName, IECore::MurmurHash &h ) const override | ||
| { | ||
| h.append( fileName() ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really the hash doesn't depend on anything here and I should not append anything to the hash but is that safe to do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I should have thought of this earlier. All other hashes (Object, ComputeNode, SceneInterface::hash()) are first initialised by appending the TypeId of the thing in question, to avoid collisions between hashes generated by distinct types. So, in this case SceneInterface::hashSet() should remain pure virtual, but should have an implementation that just does h.append( typeId() ). Then all derived classes should first call the base class and then append anything they need.
Once that's done we could omit the fileName here because the inclusion of the TypeId will keep the hash distinct from any other SceneInterface that doesn't implement sets.
Does that make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great. Thanks for the explanation. Just one question...
So, in this case SceneInterface::hashSet() should remain pure virtual, but should have an implementation that just does h.append( typeId() )
I'm a little confused if the SceneInterface::hashSet() remains pure virtual then where should the h.append( typeId() ) go?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe pure virtual isn't quite the right terminology...this is what I meant...what would you call that?
SceneInterface
{
virtual void hashSet( name, h ) = 0; // Force derived classes to provide an override
}
SceneInterface::hashSet( name, h ) // But still provide a base class implementation they can call
{
h.append( typeId() );
}
VDBScene::hashSet( name, h )
{
SceneInterface::hashSet( name, h ); // Call base class from derived class
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what would you call that?
pure virtual and I'm an idiot.
class Foo
{
virtual void test() = 0; // I've always assumed this means derived class must implement & there can't be implementation in Foo !!!
};
Thanks for spelling it out.
readSet, writeSet & setNames functions added.
+ This will be used by the LinkedScene to write link locations on the root.
+ Kept the recurse for links code as it's probably going to be useful for upgrading old scenes.
andrewkaufman
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @donboie, looks great so far. I guess other than fine tuning the details, the main remaining question is what to do about non-gaffer workflows that involve tags. I think these are the options we've discussed:
- (a) Write both sets and tags for a while
- Pros
- Less immediate work
- We don't bottleneck our progress on tracking down and updating those workflows
- Gaffer can start reading sets only, because that pulls in the tags if they're in the file
- Cons
- Most/all production scenes will continue to contain only tags
- because they're made from DCCs, not from Gaffer
- Most/all production scenes will continue to contain only tags
- Pros
- (b) Update the non-gaffer workflows to use the sets api only
- Pros
- Moves us to where we want to be
- Makes terminology consistent between DCCs / Gaffer Power Users / Developers
- Cons
- Workflows are likely scattered in projects you're not too familiar with
- and that need to be dual compatible with Cortex 9/10 for a while longer
- Workflows are likely scattered in projects you're not too familiar with
- Pros
- (c) Re-implement readTag to call readSet
- Pros
- Files written with sets (Gaffer SceneWriter) will work with the non-gaffer workflows
- Cons
- We don't want/need this code in the long run
- Is it really much less effort than updating the other workflows?
- Pros
I know I've been the main criticizer of (b), but I'm starting to wonder if I'm being overly cautious. We certainly need updates in Jabuka, and likely in RigToolbox/AnimToolbox/CfxToolbox, all of which need to be dual compatible. But are there really any others? Maybe a few Houdini OTLs. So maybe we can ask Ivan, Stefan, and Lucien to help porting those workflows over to sets, and we'd get there fairly quickly...
|
|
||
| #include "IECore/Export.h" | ||
| #include "IECore/Object.h" | ||
| #include "IECore/PathMatcherData.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just need PathMatcher.h now
| /// Reads the named set. All paths returned are relative to the current location. | ||
| virtual IECore::PathMatcher readSet( const Name &name ) const = 0; | ||
| /// Writes a set at the current location. All paths are specified relative to the current location. | ||
| virtual void writeSet( const Name &name, IECore::PathMatcher set ) = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i expected this to take a const PathMatcher &
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@johnhaddon are you in agreement with @andrewkaufman on this?
|
|
||
| void SceneInterface::hashSet( const Name& setName, IECore::MurmurHash &h ) const | ||
| { | ||
| h.append( typeId() ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should probably hash the setName here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just wanted to jump in quickly and say I don't think so. Two reasons - an implementation might not support sets in which case all sets have the same hash, or might be smart enough to know that two sets are identical, in which case the base class just made it impossible to report that via the hash...
|
|
||
| names.insert( names.end(), setNames.begin(), setNames.end() ); | ||
|
|
||
| // ensure our set names are unique |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the case we don't have tags, the ReaderImplementation already sorted and unique-ified for us, right? It'd be nice to avoid the extra work if we don't need it.
|
|
||
| h.append( m_root->fileName() ); | ||
| h.append( &path[0], path.size() ); | ||
| h.append( name ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feels like SceneInterface::hashSet should take care of name for us
| return NameList(); | ||
| } | ||
|
|
||
| IECore::PathMatcher readSet( const Name &name ) const override |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think its fine to not support this for now, but just wondering, didn't they introduce some concept of grouping? Would that map to sets?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we have a single location in our VDBScene we'd have to split the VDBGrids based on point groups for sets to work. I suspect filtering on groups would be perferable.
|
|
||
| IECore::PathMatcher LiveScene::readSet( const Name &name ) const | ||
| { | ||
| throw Exception( "IECoreHoudini::LiveScene::readSet not supported" ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is maya, not houdini
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, seems like we can and should support this, the same we we do for tags:
- actual maya sets
- custom ieSet_whatever maya attrs
- registration hooks for external plugins to monkey with the sets (asset managers)
Maybe just a todo for now so we remember to come back to it before moving out of alpha? Although, we need this before Caribou MainSceneInput can work, once Gaffer stops interpreting tags as sets, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was leaving this incase the general discussion on tags vs sets might of changed what was required here.
|
|
||
| IECore::PathMatcher LiveScene::readSet( const Name &name ) const | ||
| { | ||
| throw Exception( "IECoreHoudini::LiveScene::readSet not supported" ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment about remembering to come back to this.
|
I've encountered a problem with the Set API when we copy from one scene to another. Users of the API have no simple way of knowing if a set is defined on a particular location. How about an additional parameter to define if we include children in our set name or membership queries? Without the above we can only copy the set at the root where it might not have been originally defined or recurse down the scene looking for where sets are defined by calling setNames on every location. |
Sounds good. I don't think This does raise the issue that a SceneReader->SceneWriter round trip in Gaffer will have moved all the sets to the root though. Are we concerned about that? |
Good point, agreed.
I can't think of a problem with this but I'm concerned something might pop up. @andrewkaufman ? |
I think if we're writing Tags still using that sneaky env var, we won't notice any issues until the internal Maya workflows are updated to write Sets. When we get about to doing that, I anticipate we'll usually be writing those Sets at the root anyways, unless I'm forgetting some essential workflow... I guess the big exception to that is for LinkedScenes, but a SceneReader->SceneWriter round trip totally obliterates the links anyways, so obliterating the child Sets doesn't seem any more problematic. Of topic, but should/could the SceneWriter maintain links somehow? Perhaps Capsules could carry along a fileName and the SceneWriter could use that to re-create links somehow? Maybe that's all nonsense... lets discuss somewhere else rather than further mudding this discussion... |
Hey John,
Here is the first commit of my set changes where I just read & write sets directly to the IO as you suggested in the the other PR.
Cheers