First and foremost, LogStream and AssimpImporter are now two separate concepts. Previously you had to attach a log stream to your importer. Now its a stand alone object where you call the Attach() and Detach() methods yourself. You don’t have to worry about cleaning up active log streams yourself, they can very well be fire and forget. The library handles cleanup if need be, but there are static methods exposed to detach all active log streams or to query all of them. This was a necessary change to remove the lock synchronization from the importer.
All AssimpImporter’s were lock-based because each importer had its own set of log streams that would attach before import then detach afterwords. In a multithreaded scenario you’d run into exceptions if this wasn’t the case. Most likely because the unmanaged Assimp library that I distribute is compiled with the single threaded flag. That means the adding/removing of unmanaged logstreams would not have a synchronization primitive. You can of course recompile the unmanaged library, but I prefer the managed wrapper to be able to handle any situation. As a stop gap measure, I did the synchronization at the importer level – unfortunately that meant that multithreaded importing would be useless, as each parallel import would perform as if they were sequential.
So it was a bad design choice. What we have now is more in line with what’s going on in the unmanaged library. For our managed logstreams, we’re still doing some locking on attaching/detaching, in order to be able to handle any unmanaged Assimp compile configuration. Even with the single threaded compile flag, the assimp C API is (or appears to be) threadsafe. After the last release (3.0) import/export functions took in a property store. So each call to import/export means you’re getting a different native importer, a different map of properties, etc. So its a lock-less solution.
The AssimpImporter now operates under the same design. Think of it as a “context”, you should have one per thread. It’s not thread-safe, but multiple importers running parallel will no longer trip each other up (or synchronize with one another). I used one of my existing unit tests that tests importing different models on three separate threads. The new design compared to the old design took about half as much time! I think this will make some developers that use the wrapper very excited!
In addition to the importer changes, I have refactored the AssimpLibrary implementation – the stuff that handles the actual interop with the C API. If you’re not aware, I moved past the traditional P/Invoke paradignm where you have static methods marked extern. Instead I employ an unmanaged function pointer approach, where we load the library, get proc addresses to functions, then marshal a delegate. It works quite well and has enabled the wrapper to be AnyCPU. This implementation always assumed Windows, which posed a problem to properly supporting Mono and getting AssimpNet multi-platform. I’ve refactored the plumbing a bit so its now possible to change the underlying implementation depending on the environment. So far I have the default Windows one, as well as a Linux implementation. The linux implementation, and compiling against the Mono runtime libraries is still untested!
Also, there are a few breaking changes in some property names with the AssimpLibrary, nothing major. More stylistic (bool properties prefixed with “Is”), but also to rectify a naming conflict with two new events that have been added to the library. If anyone is running off of the trunk, please let me know if there are any issues.
And if you want to go crazy, see if you can compile against Mono and get this baby running on a Linux machine! I’d love to know how it turns out! You’ll need to compile the unmanaged library too.