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 }