1 module gfm.assimp.assimp;
2 
3 import std.conv,
4        std.string,
5        std.array : join;
6 
7 import std.experimental.logger;
8 
9 import derelict.assimp3.assimp,
10        derelict.util.exception;
11 
12 /// The one exception type thrown in this wrapper.
13 /// A failing ASSIMP function should <b>always</b> throw an AssimpException.
14 class AssimpException : Exception
15 {
16     public
17     {
18         @safe pure nothrow this(string message, string file =__FILE__, size_t line = __LINE__, Throwable next = null)
19         {
20             super(message, file, line, next);
21         }
22     }
23 }
24 
25 /// Create one to use the ASSIMP libary.
26 /// Owns both the loader and logging redirection.
27 /// This object is passed around to other ASSIMP wrapper objects
28 /// to ensure library loading.
29 final class Assimp
30 {
31     public
32     {
33         /// Load ASSIMP library, redirect logging to our logger.
34         /// You can pass a null logger if you don't want logging.
35         /// Throws: AssimpException on error.
36         this(Logger logger)
37         {
38             _logger = logger is null ? new NullLogger() : logger;
39 
40             try
41             {
42                 DerelictASSIMP3.load();
43             }
44             catch(DerelictException e)
45             {
46                 throw new AssimpException(e.msg);
47             }
48 
49             _libInitialized = true;
50 
51             // enable verbose logging by default
52             aiEnableVerboseLogging(AI_TRUE);
53 
54             // route Assimp logging to our own
55             _logStream.callback = &loggingCallbackAssimp;
56             _logStream.user = cast(char*)(cast(void*)this);
57             aiAttachLogStream(&_logStream);
58         }
59 
60         /// Releases the ASSIMP library and all resources.
61         /// All resources should have been released at this point,
62         /// since you won't be able to call any ASSIMP function afterwards.
63         ~this()
64         {
65             if (_libInitialized)
66             {
67                 debug ensureNotInGC("Assimp");
68                 aiDetachLogStream(&_logStream);
69                 _libInitialized = false;
70             }
71         }
72        
73         /// Returns: ASSIMP version string as returned by the dynamic library.
74         string getVersion()
75         {
76             string compileFlags()
77             {
78                 string[] res;
79                 uint flags = aiGetCompileFlags();
80                 if ((flags & ASSIMP_CFLAGS_SHARED) != 0)
81                     res ~= "shared";
82                 if ((flags & ASSIMP_CFLAGS_STLPORT) != 0)
83                     res ~= "stl-port";
84                 if ((flags & ASSIMP_CFLAGS_DEBUG) != 0)
85                     res ~= "debug";
86                 if ((flags & ASSIMP_CFLAGS_NOBOOST) != 0)
87                     res ~= "no-boost";
88                 if ((flags & ASSIMP_CFLAGS_SINGLETHREADED) != 0)
89                     res ~= "single-threaded";
90                 return join(res, ", ");
91             }
92             return format("v%s.%s_r%s (%s)", aiGetVersionMajor(), aiGetVersionMinor(), aiGetVersionRevision(), compileFlags());
93         }
94 
95         /// Returns: A string with legal copyright and licensing information about Assimp.
96         const(char)[] getLegalString()
97         {
98             const(char)* legalZ = aiGetLegalString();
99             return fromStringz(legalZ);
100         }
101     }
102 
103     package
104     {
105         Logger _logger;
106 
107         // exception mechanism that shall be used by every module here
108         void throwAssimpException(string callThatFailed)
109         {
110             string message = format("%s failed: %s", callThatFailed, getErrorString());
111             throw new AssimpException(message);
112         }
113 
114         const(char)[] getErrorString()
115         {
116             const(char)* errorZ = aiGetErrorString();
117             return fromStringz(errorZ);
118         }
119     }
120 
121     private
122     {
123         bool _libInitialized;
124         aiLogStream _logStream;
125     }
126 }
127 
128 extern (C) private
129 {
130     void loggingCallbackAssimp(const(char)* message, char* user) nothrow
131     {
132         Assimp assimp = cast(Assimp)user;
133         try
134         {
135             Logger logger = assimp._logger;
136             logger.infof("assimp: %s");
137         }
138         catch(Exception e)
139         {
140             // ignoring IO exceptions, format errors, etc... to be nothrow
141             // making the whole Log interface nothrow is not that trivial
142         }
143     }
144 }
145 
146 /// Crash if the GC is running.
147 /// Useful in destructors to avoid reliance GC resource release.
148 package void ensureNotInGC(string resourceName) nothrow
149 {
150     import core.exception;
151     try
152     {
153         import core.memory;
154         cast(void) GC.malloc(1); // not ideal since it allocates
155         return;
156     }
157     catch(InvalidMemoryOperationError e)
158     {
159 
160         import core.stdc.stdio;
161         fprintf(stderr, "Error: clean-up of %s incorrectly depends on destructors called by the GC.\n", resourceName.ptr);
162         assert(false);
163     }
164 }