pythonLoaderFileType.cxx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. /**
  2. * PANDA 3D SOFTWARE
  3. * Copyright (c) Carnegie Mellon University. All rights reserved.
  4. *
  5. * All use of this software is subject to the terms of the revised BSD
  6. * license. You should have received a copy of this license along
  7. * with this source code in a file named "LICENSE."
  8. *
  9. * @file pythonLoaderFileType.cxx
  10. * @author rdb
  11. * @date 2019-07-29
  12. */
  13. #include "pythonLoaderFileType.h"
  14. #ifdef HAVE_PYTHON
  15. #include "bamCacheRecord.h"
  16. #include "modelRoot.h"
  17. #include "pythonThread.h"
  18. #include "py_panda.h"
  19. #include "virtualFileSystem.h"
  20. extern struct Dtool_PyTypedObject Dtool_BamCacheRecord;
  21. extern struct Dtool_PyTypedObject Dtool_Filename;
  22. extern struct Dtool_PyTypedObject Dtool_LoaderOptions;
  23. extern struct Dtool_PyTypedObject Dtool_PandaNode;
  24. extern struct Dtool_PyTypedObject Dtool_PythonLoaderFileType;
  25. TypeHandle PythonLoaderFileType::_type_handle;
  26. /**
  27. * This constructor expects init() to be called manually.
  28. */
  29. PythonLoaderFileType::
  30. PythonLoaderFileType() {
  31. init_type();
  32. }
  33. /**
  34. * This constructor expects a single pkg_resources.EntryPoint argument for a
  35. * deferred loader.
  36. */
  37. PythonLoaderFileType::
  38. PythonLoaderFileType(std::string extension, PyObject *entry_point) :
  39. _extension(std::move(extension)),
  40. _entry_point(Py_NewRef(entry_point)) {
  41. init_type();
  42. }
  43. /**
  44. * Destructor.
  45. */
  46. PythonLoaderFileType::
  47. ~PythonLoaderFileType() {
  48. if (_entry_point != nullptr || _load_func != nullptr || _save_func != nullptr) {
  49. #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
  50. PyGILState_STATE gstate;
  51. gstate = PyGILState_Ensure();
  52. #endif
  53. Py_CLEAR(_entry_point);
  54. Py_CLEAR(_load_func);
  55. Py_CLEAR(_save_func);
  56. #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
  57. PyGILState_Release(gstate);
  58. #endif
  59. }
  60. }
  61. /**
  62. * Initializes the fields from the given Python loader object.
  63. */
  64. bool PythonLoaderFileType::
  65. init(PyObject *loader) {
  66. nassertr(loader != nullptr, false);
  67. nassertr(_load_func == nullptr, false);
  68. nassertr(_save_func == nullptr, false);
  69. // Check the extensions member. If we already have a registered extension,
  70. // it must occur in the list.
  71. PyObject *extensions = PyObject_GetAttrString(loader, "extensions");
  72. if (extensions != nullptr) {
  73. if (PyUnicode_Check(extensions)) {
  74. Dtool_Raise_TypeError("extensions list should be a list or tuple");
  75. Py_DECREF(extensions);
  76. return false;
  77. }
  78. PyObject *tuple = PySequence_Tuple(extensions);
  79. Py_ssize_t num_items = PyTuple_GET_SIZE(tuple);
  80. Py_DECREF(extensions);
  81. if (num_items == 0) {
  82. PyErr_SetString(PyExc_ValueError, "extensions list may not be empty");
  83. Py_DECREF(tuple);
  84. return false;
  85. }
  86. bool found_extension = false;
  87. for (Py_ssize_t i = 0; i < num_items; ++i) {
  88. PyObject *extension = PyTuple_GET_ITEM(tuple, i);
  89. const char *extension_str;
  90. Py_ssize_t extension_len;
  91. extension_str = PyUnicode_AsUTF8AndSize(extension, &extension_len);
  92. if (extension_str == nullptr) {
  93. Py_DECREF(tuple);
  94. return false;
  95. }
  96. if (_extension.empty()) {
  97. _extension.assign(extension_str, extension_len);
  98. found_extension = true;
  99. } else {
  100. std::string new_extension(extension_str, extension_len);
  101. if (_extension == new_extension) {
  102. found_extension = true;
  103. } else {
  104. if (!_additional_extensions.empty()) {
  105. _additional_extensions += ' ';
  106. }
  107. _additional_extensions += new_extension;
  108. }
  109. }
  110. }
  111. Py_DECREF(tuple);
  112. if (!found_extension) {
  113. PyObject *repr = PyObject_Repr(loader);
  114. loader_cat.error()
  115. << "Registered extension '" << _extension
  116. << "' does not occur in extensions list of "
  117. << PyUnicode_AsUTF8(repr) << "\n";
  118. Py_DECREF(repr);
  119. return false;
  120. }
  121. } else {
  122. return false;
  123. }
  124. PyObject *supports_compressed = PyObject_GetAttrString(loader, "supports_compressed");
  125. if (supports_compressed != nullptr) {
  126. if (supports_compressed == Py_True) {
  127. _supports_compressed = true;
  128. }
  129. else if (supports_compressed == Py_False) {
  130. _supports_compressed = false;
  131. }
  132. else {
  133. Dtool_Raise_TypeError("supports_compressed must be a bool");
  134. Py_DECREF(supports_compressed);
  135. return false;
  136. }
  137. Py_DECREF(supports_compressed);
  138. }
  139. else {
  140. PyErr_Clear();
  141. }
  142. _load_func = PyObject_GetAttrString(loader, "load_file");
  143. PyErr_Clear();
  144. _save_func = PyObject_GetAttrString(loader, "save_file");
  145. PyErr_Clear();
  146. if (_load_func == nullptr && _save_func == nullptr) {
  147. PyErr_Format(PyExc_TypeError,
  148. "loader plug-in %R does not define load_file or save_file function",
  149. loader);
  150. return false;
  151. }
  152. // We don't need this any more.
  153. Py_CLEAR(_entry_point);
  154. return true;
  155. }
  156. /**
  157. * Ensures that the referenced Python module is loaded.
  158. */
  159. bool PythonLoaderFileType::
  160. ensure_loaded() const {
  161. if (_load_func != nullptr || _save_func != nullptr) {
  162. return true;
  163. }
  164. nassertr_always(_entry_point != nullptr, false);
  165. #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
  166. PyGILState_STATE gstate;
  167. gstate = PyGILState_Ensure();
  168. #endif
  169. if (loader_cat.is_info()) {
  170. PyObject *repr = PyObject_Repr(_entry_point);
  171. loader_cat.info()
  172. << "loading file type module: "
  173. << PyUnicode_AsUTF8(repr) << "\n";
  174. Py_DECREF(repr);
  175. }
  176. PyObject *result = PyObject_CallMethod(_entry_point, (char *)"load", nullptr);
  177. bool success = false;
  178. if (result != nullptr) {
  179. success = ((PythonLoaderFileType *)this)->init(result);
  180. } else {
  181. PyErr_Clear();
  182. PyObject *repr = PyObject_Repr(_entry_point);
  183. loader_cat.error()
  184. << "unable to load "
  185. << PyUnicode_AsUTF8(repr) << "\n";
  186. Py_DECREF(repr);
  187. }
  188. #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
  189. PyGILState_Release(gstate);
  190. #endif
  191. return success;
  192. }
  193. /**
  194. *
  195. */
  196. std::string PythonLoaderFileType::
  197. get_name() const {
  198. return "Python loader";
  199. }
  200. /**
  201. *
  202. */
  203. std::string PythonLoaderFileType::
  204. get_extension() const {
  205. return _extension;
  206. }
  207. /**
  208. * Returns a space-separated list of extension, in addition to the one
  209. * returned by get_extension(), that are recognized by this converter.
  210. */
  211. std::string PythonLoaderFileType::
  212. get_additional_extensions() const {
  213. return _additional_extensions;
  214. }
  215. /**
  216. * Returns true if this file type can transparently load compressed files
  217. * (with a .pz or .gz extension), false otherwise.
  218. */
  219. bool PythonLoaderFileType::
  220. supports_compressed() const {
  221. return ensure_loaded() && _supports_compressed;
  222. }
  223. /**
  224. * Returns true if the file type can be used to load files, and load_file() is
  225. * supported. Returns false if load_file() is unimplemented and will always
  226. * fail.
  227. */
  228. bool PythonLoaderFileType::
  229. supports_load() const {
  230. return ensure_loaded() && _load_func != nullptr;
  231. }
  232. /**
  233. * Returns true if the file type can be used to save files, and save_file() is
  234. * supported. Returns false if save_file() is unimplemented and will always
  235. * fail.
  236. */
  237. bool PythonLoaderFileType::
  238. supports_save() const {
  239. return ensure_loaded() && _save_func != nullptr;
  240. }
  241. /**
  242. *
  243. */
  244. PT(PandaNode) PythonLoaderFileType::
  245. load_file(const Filename &path, const LoaderOptions &options,
  246. BamCacheRecord *record) const {
  247. // Let's check whether the file even exists before calling Python.
  248. VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
  249. PT(VirtualFile) vfile = vfs->get_file(path);
  250. if (vfile == nullptr) {
  251. return nullptr;
  252. }
  253. if (!supports_load()) {
  254. return nullptr;
  255. }
  256. if (record != nullptr) {
  257. record->add_dependent_file(vfile);
  258. }
  259. #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
  260. PyGILState_STATE gstate;
  261. gstate = PyGILState_Ensure();
  262. #endif
  263. // Wrap the arguments.
  264. PyObject *args = PyTuple_New(3);
  265. PyTuple_SET_ITEM(args, 0, DTool_CreatePyInstance((void *)&path, Dtool_Filename, false, true));
  266. PyTuple_SET_ITEM(args, 1, DTool_CreatePyInstance((void *)&options, Dtool_LoaderOptions, false, true));
  267. if (record != nullptr) {
  268. record->ref();
  269. PyTuple_SET_ITEM(args, 2, DTool_CreatePyInstanceTyped((void *)record, Dtool_BamCacheRecord, true, false, record->get_type_index()));
  270. } else {
  271. PyTuple_SET_ITEM(args, 2, Py_NewRef(Py_None));
  272. }
  273. PT(PandaNode) node;
  274. PyObject *result = PythonThread::call_python_func(_load_func, args);
  275. if (result != nullptr) {
  276. if (DtoolInstance_Check(result)) {
  277. node = (PandaNode *)DtoolInstance_UPCAST(result, Dtool_PandaNode);
  278. }
  279. Py_DECREF(result);
  280. }
  281. Py_DECREF(args);
  282. if (node == nullptr) {
  283. PyObject *exc_type = PyErr_Occurred();
  284. if (!exc_type) {
  285. loader_cat.error()
  286. << "load_file must return valid PandaNode or raise exception\n";
  287. } else {
  288. loader_cat.error()
  289. << "Loading " << path.get_basename()
  290. << " failed with " << ((PyTypeObject *)exc_type)->tp_name << " exception.\n";
  291. PyErr_Clear();
  292. }
  293. }
  294. #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
  295. PyGILState_Release(gstate);
  296. #endif
  297. if (node != nullptr && node->is_of_type(ModelRoot::get_class_type())) {
  298. ModelRoot *model_root = DCAST(ModelRoot, node.p());
  299. model_root->set_fullpath(path);
  300. model_root->set_timestamp(vfile->get_timestamp());
  301. }
  302. return node;
  303. }
  304. /**
  305. *
  306. */
  307. bool PythonLoaderFileType::
  308. save_file(const Filename &path, const LoaderOptions &options,
  309. PandaNode *node) const {
  310. if (!supports_save()) {
  311. return false;
  312. }
  313. nassertr(node != nullptr, false);
  314. node->ref();
  315. #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
  316. PyGILState_STATE gstate;
  317. gstate = PyGILState_Ensure();
  318. #endif
  319. // Wrap the arguments.
  320. PyObject *args = PyTuple_New(3);
  321. PyTuple_SET_ITEM(args, 0, DTool_CreatePyInstance((void *)&path, Dtool_Filename, false, true));
  322. PyTuple_SET_ITEM(args, 1, DTool_CreatePyInstance((void *)&options, Dtool_LoaderOptions, false, true));
  323. PyTuple_SET_ITEM(args, 2, DTool_CreatePyInstanceTyped((void *)node, Dtool_PandaNode, true, false, node->get_type_index()));
  324. PyObject *result = PythonThread::call_python_func(_load_func, args);
  325. Py_DECREF(args);
  326. if (result != nullptr) {
  327. Py_DECREF(result);
  328. } else {
  329. PyErr_Clear();
  330. loader_cat.error()
  331. << "save_file failed with an exception.\n";
  332. }
  333. #if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
  334. PyGILState_Release(gstate);
  335. #endif
  336. return (result != nullptr);
  337. }
  338. #endif // HAVE_PYTHON