/* * This file gets compiled as a shared library that act as the entry point * to the pythonscript plugin. * It should be loaded by Pandemonium's GDNative system (see the `pythonscript.gdnlib` * file in the example/test projects). * As part of the loading, GDNative will call the `pandemonium_gdnative_init` * function which will in turn initialize the CPython interpreter then register * Python as a new language using Pandemonium's Pluginscript system. */ #define PY_SSIZE_T_CLEAN #include #ifndef _WIN32 #include #endif #include #include #include "_pandemonium_api.h" static const char *PYTHONSCRIPT_RECOGNIZED_EXTENSIONS[] = { "py", "pyc", "pyo", "pyd", 0 }; static const char *PYTHONSCRIPT_RESERVED_WORDS[] = { "False", "None", "True", "and", "as", "assert", "break", "class", "continue", "def", "del", "elif", "else", "except", "finally", "for", "from", "global", "if", "import", "in", "is", "lambda", "nonlocal", "not", "or", "pass", "raise", "return", "try", "while", "with", "yield", 0 }; static const char *PYTHONSCRIPT_COMMENT_DELIMITERS[] = { "#", "\"\"\"\"\"\"", 0 }; static const char *PYTHONSCRIPT_STRING_DELIMITERS[] = { "\" \"", "' '", 0 }; static pandemonium_pluginscript_language_desc desc; static PyThreadState *gilstate = NULL; /* * Global variables exposing Pandemonium API to the pandemonium.hazmat cython module. * Hence we must initialized them before loading `_pandemonium`/`pandemonium` modules * (which both depend on `pandemonium.hazmat`). */ #ifdef _WIN32 #define PYTHONSCRIPT_EXPORT __declspec(dllexport) #else #define PYTHONSCRIPT_EXPORT #endif PYTHONSCRIPT_EXPORT const pandemonium_gdnative_core_api_struct *pythonscript_gdapi10 = NULL; PYTHONSCRIPT_EXPORT const pandemonium_gdnative_ext_nativescript_api_struct *pythonscript_gdapi_ext_nativescript = NULL; PYTHONSCRIPT_EXPORT const pandemonium_gdnative_ext_pluginscript_api_struct *pythonscript_gdapi_ext_pluginscript = NULL; PYTHONSCRIPT_EXPORT const pandemonium_gdnative_ext_android_api_struct *pythonscript_gdapi_ext_android = NULL; static void _register_gdapi(const pandemonium_gdnative_init_options *options) { pythonscript_gdapi10 = (const pandemonium_gdnative_core_api_struct *)options->api_struct; /* if (pythonscript_gdapi10->next) { pythonscript_gdapi11 = (const pandemonium_gdnative_core_1_1_api_struct *)pythonscript_gdapi10->next; if (pythonscript_gdapi11->next) { pythonscript_gdapi12 = (const pandemonium_gdnative_core_1_2_api_struct *)pythonscript_gdapi11->next; } } */ for (unsigned int i = 0; i < pythonscript_gdapi10->num_extensions; i++) { const pandemonium_gdnative_api_struct *ext = pythonscript_gdapi10->extensions[i]; switch (ext->type) { case GDNATIVE_EXT_NATIVESCRIPT: pythonscript_gdapi_ext_nativescript = (const pandemonium_gdnative_ext_nativescript_api_struct *)ext; break; case GDNATIVE_EXT_PLUGINSCRIPT: pythonscript_gdapi_ext_pluginscript = (const pandemonium_gdnative_ext_pluginscript_api_struct *)ext; break; case GDNATIVE_EXT_ANDROID: pythonscript_gdapi_ext_android = (const pandemonium_gdnative_ext_android_api_struct *)ext; break; default: break; } } } GDN_EXPORT void pandemonium_gdnative_init(pandemonium_gdnative_init_options *options) { // Registering the api should be the very first thing to do ! _register_gdapi(options); // Now those macros are usable #define GD_PRINT(c_msg) \ { \ pandemonium_string gd_msg; \ pythonscript_gdapi10->pandemonium_string_new_with_wide_string( \ &gd_msg, c_msg, -1); \ pythonscript_gdapi10->pandemonium_print(&gd_msg); \ pythonscript_gdapi10->pandemonium_string_destroy(&gd_msg); \ } #define GD_ERROR_PRINT(msg) \ { \ pythonscript_gdapi10->pandemonium_print_error(msg, __func__, __FILE__, __LINE__); \ } // Check for mandatory plugins /* if (!pythonscript_gdapi10 || !pythonscript_gdapi11 || !pythonscript_gdapi12) { GD_ERROR_PRINT("Pandemonium-Python requires GDNative API >= v1.2"); return; } */ if (!pythonscript_gdapi_ext_pluginscript) { GD_ERROR_PRINT("Pluginscript extension not available"); return; } #ifndef _WIN32 // Make sure the shared library has all it symbols loaded // (strange bug with libpython3.x.so otherwise...) { pandemonium_char_string wpstr = pythonscript_gdapi10->pandemonium_string_utf8(options->active_library_path); const char *path = pythonscript_gdapi10->pandemonium_char_string_get_data(&wpstr); dlopen(path, RTLD_NOW | RTLD_GLOBAL); } const char *err = dlerror(); if (err) { GD_ERROR_PRINT(err); return; } #endif // Initialize CPython interpreter // Retrieve path and set pythonhome { static wchar_t pythonhome[300]; pandemonium_string _pythonhome = pythonscript_gdapi10->pandemonium_string_get_base_dir(options->active_library_path); pandemonium_char_16_string wpstr = pythonscript_gdapi10->pandemonium_string_utf16(&_pythonhome); const char16_t *phc = pythonscript_gdapi10->pandemonium_char_16_string_get_data(&wpstr); // Ugly hack, because wchar_t's size varies by platform. TODO Should probably add back the wchar_t charstring + getters/setters int i = 0; while (phc[i] != '\0' && i < 299) { pythonhome[i] = phc[i]; ++i; } pythonhome[i] = '\0'; pythonscript_gdapi10->pandemonium_string_destroy(&_pythonhome); Py_SetPythonHome(pythonhome); } // TODO: site.USER_SITE seems to point to an invalid location in ~/.local // // Add current dir to PYTHONPATH // wchar_t *path = Py_GetPath(); // int new_path_len = wcslen(path) + 3; // wchar_t new_path[new_path_len * sizeof(wchar_t)]; // wcsncpy(new_path, L".:", new_path_len); // wcsncpy(new_path + 2, path, new_path_len - 2); // Py_SetPath(new_path); // PyRun_SimpleString("import sys\nprint('PYTHON_PATH:', sys.path)\n"); Py_SetProgramName(L"pandemonium"); // Initialize interpreter but skip initialization registration of signal handlers Py_InitializeEx(0); // PyEval_InitThreads acquires the GIL, so we must release it later. // Since python3.7 PyEval_InitThreads is automatically called by Py_InitializeEx, but it's better to leave it here // to be explicit. Calling it again does nothing. PyEval_InitThreads(); int ret = import__pandemonium(); if (ret != 0) { GD_ERROR_PRINT("Cannot load pandemonium python module"); return; } desc.name = "Python"; desc.type = "Python"; desc.extension = "py"; desc.recognized_extensions = PYTHONSCRIPT_RECOGNIZED_EXTENSIONS; desc.init = pythonscript_init; desc.finish = pythonscript_finish; desc.reserved_words = PYTHONSCRIPT_RESERVED_WORDS; desc.comment_delimiters = PYTHONSCRIPT_COMMENT_DELIMITERS; desc.string_delimiters = PYTHONSCRIPT_STRING_DELIMITERS; desc.has_named_classes = false; desc.add_global_constant = pythonscript_add_global_constant; desc.script_desc.init = pythonscript_script_init; desc.script_desc.finish = pythonscript_script_finish; desc.script_desc.instance_desc.init = pythonscript_instance_init; desc.script_desc.instance_desc.finish = pythonscript_instance_finish; desc.script_desc.instance_desc.set_prop = pythonscript_instance_set_prop; desc.script_desc.instance_desc.get_prop = pythonscript_instance_get_prop; desc.script_desc.instance_desc.call_method = pythonscript_instance_call_method; desc.script_desc.instance_desc.notification = pythonscript_instance_notification; desc.script_desc.instance_desc.refcount_incremented = NULL; desc.script_desc.instance_desc.refcount_decremented = NULL; if (options->in_editor) { desc.get_template_source_code = pythonscript_get_template_source_code; desc.validate = pythonscript_validate; desc.find_function = pythonscript_find_function; desc.make_function = pythonscript_make_function; desc.complete_code = pythonscript_complete_code; desc.auto_indent_code = pythonscript_auto_indent_code; desc.debug_get_error = pythonscript_debug_get_error; desc.debug_get_stack_level_count = pythonscript_debug_get_stack_level_count; desc.debug_get_stack_level_line = pythonscript_debug_get_stack_level_line; desc.debug_get_stack_level_function = pythonscript_debug_get_stack_level_function; desc.debug_get_stack_level_source = pythonscript_debug_get_stack_level_source; desc.debug_get_stack_level_locals = pythonscript_debug_get_stack_level_locals; desc.debug_get_stack_level_members = pythonscript_debug_get_stack_level_members; desc.debug_get_globals = pythonscript_debug_get_globals; desc.debug_parse_stack_level_expression = pythonscript_debug_parse_stack_level_expression; desc.profiling_start = pythonscript_profiling_start; desc.profiling_stop = pythonscript_profiling_stop; desc.profiling_get_accumulated_data = pythonscript_profiling_get_accumulated_data; desc.profiling_get_frame_data = pythonscript_profiling_get_frame_data; desc.profiling_frame = pythonscript_profiling_frame; } pythonscript_gdapi_ext_pluginscript->pandemonium_pluginscript_register_language(&desc); // Release the Kraken... er I mean the GIL ! gilstate = PyEval_SaveThread(); } GDN_EXPORT void pandemonium_gdnative_singleton() { } GDN_EXPORT void pandemonium_gdnative_terminate() { // Re-acquire the gil in order to finalize properly PyEval_RestoreThread(gilstate); int ret = Py_FinalizeEx(); if (ret != 0) { GD_ERROR_PRINT("Cannot finalize python interpreter"); } }