Problem
So, since I've been working on assets and even more on static override, I came over and over across annoying limitations and oddness of our current ID copying code. To summarize current status:
- Main datablock copying duplicates ID part itself, inserts it into given Main database, and then call databock type specific copying function that will take care of duplicating sub-data, increment other used IDs usercount, etc.
- Then, since we have some needs of other behaviors now and then, we have some specific stuff, one to copy ID without putting it into a Main database, a few specific copying logic for materials, textures etc., used during rendering, that do less 'complete' copying (e.g. by not incrementing other used IDs usercount), etc.
- Some datablock types are not copyable at all with generic functions from BKE_library (main one being Scene, but also Sound, VFont, and (but that’s probably not a big issue for now) WindowManager, Screen, Library, and WorkSpace in 2.8).
That somewhat messy and incomplete situation has been annoying me ever since I reworked/factorized other parts of ID management code, but always decided that was low-priority TODO. Now that am working on static override, am hitting issues here more and more, having to add stupid hacks to work around them. And now looks like @Sergey Sharybin (sergey) is also facing same kind of issues while working on DEG/CoW.
Proposal
So looks like it’s time to tackle this, and would rather have some consensus on general plan before I start implementing it. I think we need a flexible system allowing to add more copy types/options as need arise, so I would go with flags. I would also try to avoid as much as possible API breakage, due to how many other branches would be impacted, so keeping current API mostly, as wrapper around new one (we can always cleanup later, once transition to 2.8 is finished).
So something like that in main BKE_library api: BKE_id_copy(struct Main *bmain, const struct ID *id, struct ID **newid, const bool test, const int flag);
NULL flag would be same default, full copy as currently with id_copy(). Options would be mix of generic ones, and some specific to some IDs/usages, e.g.:
enum { /* Generic options (should be handled by all ID types copying). */ LIB_ID_COPY_NO_MAIN = 1 << 0, /* Create copy outside of any main database - similar to 'localize' functions of materials etc. */ LIB_ID_COPY_NO_USER_REFCOUNT = 1 << 1, /* Do not affect user refcount of datablocks used by copied one. */ LIB_ID_COPY_NO_DEG_TAG = 1 << 2, /* Do not tag duplicated ID for update in depsgraph. */ LIB_ID_COPY_NO_ALLOCATE = 1 << 3, /* Assume given 'newid' already points to allocated memory for whole datablock (ID + data) - USE WITH CAUTION! */ /* Specific options to some ID types or usages, may be ignored by unrelated ID copying functions. */ LIB_ID_COPY_NO_PROXY_CLEAR = 1 << 8, /* Object only, needed by make_local code. */ LIB_ID_COPY_NO_PREVIEW = 1 << 9, /* Do not copy preview data, when supported. */ /* ... Most likely more needed ;) */ };
Then add new BKE_material_copy_ex(), BKE_object_copy_ex() etc. that will take same flags as parameter, and behave accordingly.
I think we should also store those copying flags in ID, as runtime data, it has at least two benefits:
- We can simplify freeing (since freeing func could check those flags and know whether it should decrement user count, and/or try to remove ID from main…).
- More importantly, we can assert at blenfile write time we do not store some 'temp' runtime copy into file (i.e. a copy done without main or usercount)!
Finally, once ready, would aim for this branch to be put into master - but after 2.79 release obviously!
Note that, even though new copying with NO_MAIN/NO_USER_REFCOUNT should behave very similar to them, I do not plan to include existing localize functions in that refactor for now. Ultimately this should be possible, but those have very specific behavior for very specific use case, so would rather not touch them at first.
Things to check
Read again NodeTree localize function, and that one is using a mutex… so was wondering, especially in the DEG/CoW context, whether we may need some 'lock during copy' protection in general? Or should this be handled by DEG itself already?