gdnative_python/pythonscript/pythonscript.c

267 lines
9.5 KiB
C

/*
* 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 <Python.h>
#ifndef _WIN32
#include <dlfcn.h>
#endif
#include <wchar.h>
#include <gdnative_api_struct.gen.h>
#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");
}
}