How to store "cached" data in the document from the scenehook?
-
Hey - this is probably most directly directed to Ferdinand, as it is based on something he mentioned in another thread a while ago.
In a code snippet, the following is explained:This is the 'bad' way to solve this, it would be better to store the downloaded assets in the scene graph via NodeData::GetBranchInfo, but that would be another support cases to flesh that out.
This refers to a
maxon::HashMap<const maxon::Url, const BaseObject*> data
variable used as an in-memory cache of downloaded data. Like we discussed in the thread, perhaps one day it would be good to store the downloaded data with the document. One example could be that the object is created on a machine with Internet access, then needs to be worked on in a machine that doesn't. Or the user just wants to make sure they have the version of the url content that was current when the file was originally made.So this is the "another support case to flesh that out" - I have searched, but not found a clear example of what I have to implement in my scene hook to write this cache along with the document file when the user saves, and then automatically load it back in when the document is reopened.
For what it is worth, my current implementation of the scenehook stores the data in the HashMap as described.
Is there an example available on how to include this data into the document on disk? The reason the data is stored in a HashMap is that it contains a mapping of URLs to content, where the content is just a string with the data returned from the web server / file system.
Thanks for any insights!
-
Hello @Havremunken,
no, there is no code example for what you are trying to do. There is the ActiveObject C++ Example, which is supposed to demonstrate branching but does IMHO a terrible job at it. When you want to look at some resources, I would recommend these (although none of them will cover your subject exactly):
- unpacking-animation-data-wrapped-by-the-motion-system-nla: Here I explained how our own NLA system stores data in branches of a scene.
- GeListHead Manual: Covers some technical details.
- mxutils.RecurseGraph: Python only function, but its documentation and playing around with it could you help to understand what branching is in Cinema 4D.
What is Branching
Branching is how Cinema 4D realizes arbitrary entity relationships, i.e., a scene graph. By default, it seems a bit like Cinema 4D only implements hierarchical relationships via
GeListNode
, but that is not true, as there is also the functionGeListNode::GetBranchInfo
. So, while a node can have a parent, a list of children, and up to two siblings, it can also have a list of other relations which are explicitly not hierarchical.A
BaseDocument
is for example also a node, its previous and next siblings realize the list of loaded documents. The branches of a document realize all its content, theObase
branch holds for example theGeListHead
(a special type ofGeListNode
for the root of a branch) to which all top level objects of the scene are parented (which then hold their local hierarchies). The 'trick' is then that every node in a scene has such branches, an object has branches for its tags, tracks, and more; a material has branches for its shaders, tracks, and more, and so on.Please see the links I have posted above for details.
How Do I Implement Branching?
Generally, at least without getting hacky, you cannot implement branches for nodes that you do not own. So, you can not add a foo branch to documents or objects in general, you can only add a foo branch to the specific
MyFooObjectData
plugin you implement (or any other node type you implement). Since, it worked apparently quite well last time, I will just write some mock code of what you are trying to do (very high level and obviously untested and uncompiled).// Plugin ID for the generic asset node and at the same time its branch ID. T is not only the prefix // for tag types but also generic node types, e.g., Tbasedocument, Tpluginlayer, Tgelistnode, etc. static const Int32 Tassetnode = 1030001; // Realizes your asset manager just as before but this time it internalizes its asset data. // // Since a scene hook is a node and part of the scene graph, it can have children and branches. // We therefore are going to establish an Tassetnode branch in the scene hook. class AssetAccessManager : public SceneHookData { INSTANCEOF(AssetAccessManager, SceneHookData) // The head of our custom asset branch. private: GeListHead* _assetHead; public: // Returns either an existing asset branch head for the AssetAccessManager node or creates a new one. GeListHead* GetAssetHead() { iferr_scope_handler { return nullptr; }; // We already established an asset head before. if (_assetHead) return _assetHead; // Get the scene hook node for #this SceneHookData and try to find its asset branch. const GeListNode* node = Get(); if (!node) return nullptr; Bool result = node->GetBranchInfo( [_assetHead](const BranchInfo& info) -> maxon::Result<maxon::Bool> { // Check if the branch is an asset branch. if (info.id != Tassetnode) return false; // Continue searching/iterating. // We found the asset branch, we can stop iterating. _assetHead = info.head; return true; }, GETBRANCHINFO::NONE) iferr_return; if (_assetHead) return _assetHead; // Create a new branch head for this asset manager's asset branch. _assetHead = GeListHead::Alloc(); if (!_assetHead) return nullptr; // Attach the branch to the scene hook. _assetHead->SetParent(node); return _assetHead; } // Implements the branch info for your node, so that Cinema 4D knows about the asset branch. // It is important to implement this, without it Cinema 4D will crash. Int32 FlowTag::GetBranchInfo(GeListNode* node, BranchInfo* info, Int32 max, GETBRANCHINFO flags) { GeListHead* assetHead = GetAssetHead(); if (!assetHead) return 0; info[0].head = assetHead; // The head for the new branch. info[0].id = Tassetnode; // The ID of the branch. This is usually the ID of the // types of nodes to be found in the branch, e.g., Obase. info[0].name = "myAssetData"_s; // A label for humans for the branch, make sure it is // reasonably unique, there might be bad code out there // which tries to find branches by name. info[0].flags = BRANCHINFOFLAGS::NONE; // The flags for this branch, see docs for details. return 1 // The number of branches we have defined. } // Realizes a custom function to add assets to the asset manager and with that to the scene. BaseList2D* AddAsset(const Url& url) { // Instantiate an asset node, it fully encapsulates the loading and storage of asset data. BaseList2D* asset = BaseList2D::Alloc(Tassetnode); if (!asset) return nullptr; // Load the asset data from the URL, this is done via a custom message, just as discussed in // example from the old thread. asset->Message(MSG_LOAD_ASSET, (void*)&url); // Add the asset to the asset branch. We realize here just a plain list, but we could also have // complex hierarchies, or assets which have branches themselves. GeListHead* assetHead = GetAssetHead(); if (!assetHead) return nullptr; asset->InsertUnderLast(assetHead); return asset; } }; // Realizes the asset node. // // We realize here a plain NodeData, the base type of ObjectData, TagData, SceneHookData, etc. We // could also realize a specialized node, but custom branches are one of the few cases where it can // make sense to realize a plain node. But you could also realize a specialized node, e.g., // MyAssetObjectData. You can also store existing node types in your branches, e.g., it is totally // valid to implement your own Obase (i.e., object) branch. Might be better in your asset case, I do // not know. In that case you could only serialize the data an object does serialize, points, // polygons, tags, tracks, etc., but would not have to implement your own node. Pulling up an asset // would then also not work via a message but via a custom function, e.g., static BaseObject* // LoadAsset(const Url& url). // // See also: // SDK/plugins/example.main/source/object/objectdata_hyperfile.cpp class MyAssetData : public NodeData { INSTANCEOF(AssetAccessManager, SceneHookData) private: // The asset data, this is just a mock, you would have to implement this. This example is extra // stupid, because something like an Int32 is easily stored in the data container of a node. An // option which always exists, and while slightly less performant than customizing reading and // writing the node, it is also much easier to implement. Int32 _assetData; // Loads the asset data from a URL. Bool LoadAsset(const Url& url) { _assetData = 42; return true; } public: // Handles messages for the asset node. Bool Message(GeListNode* node, Int32 type, void* data) { switch (type) { case MSG_LOAD_ASSET: { // Load the asset data from the URL. Url* url = reinterpret_cast<Url*>(data); if (!url) return false; return LoadAsset(*url); } break; } return true; } // Serializes the asset node. What I am showing here should only be done when we have to serialize // truly complex data, as for example a texture with meta data. For that specific case one could // ask how sensible it is to blow up scene sizes by internalizing textures in the scene graph. // Then one could end up with a compromise, where one stores only information in the scene about // where a localized texture has been stored, i.e., we just reinvented texture files :D. // When you implement serialization, you must always implement Read, Write, and CopyTo. See the // NodeData manual and SDK for details, I am too lazy to even mock-up all of them here, I am only // going to demo Read/Write. // Writes all data of this node to the scene file that is not already stored in the node's data // container. Bool Write(GeListNode* node, HyperFile* hf) { if (!hf) return false; // Write the asset data to the scene file. if (hf->WriteInt32(_assetData) == 0) return false; return true; } // Reads all data of this node from the scene file that is not already stored in the node's data // container. Bool Read(GeListNode* node, HyperFile* hf, Int32 level) { if (!hf) return false; // Read the asset data from the scene file. if (hf->ReadInt32(&_assetData) == 0) return false; return true; } }
I hope this gets you going. I cannot stress enough that this is a sktech.This will quite likely not compile, but it demonstrates the core methods and principles. Feel free to come back with concrete code yours when you need help.
But I currently do not have the time to write a full example for this from scratch.
Cheeers,
FerdinandCheers,
Ferdinand -
Hi Ferdinand, and thanks for all the fish!
No worries about the psuedo code, this is precisely what I needed to understand what I need to implement in order to store my data with the document. I am going to try to write this into my project, and will of course return soon if something happens that I can't figure out.
Again, many thanks!