2023-05-23 19:06:25 +02:00
|
|
|
/*
|
|
|
|
* This file gets compiled as a shared library that act as the entry point
|
|
|
|
* to the pythonscript plugin.
|
2023-06-02 11:31:19 +02:00
|
|
|
* It should be loaded by Pandemonium's GDNative system (see the `pythonscript.gdnlib`
|
2023-05-23 19:06:25 +02:00
|
|
|
* file in the example/test projects).
|
2023-06-02 11:05:45 +02:00
|
|
|
* As part of the loading, GDNative will call the `pandemonium_gdnative_init`
|
2023-05-23 19:06:25 +02:00
|
|
|
* function which will in turn initialize the CPython interpreter then register
|
2023-06-02 11:31:19 +02:00
|
|
|
* Python as a new language using Pandemonium's Pluginscript system.
|
2023-05-23 19:06:25 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#define PY_SSIZE_T_CLEAN
|
|
|
|
#include <Python.h>
|
|
|
|
|
|
|
|
#ifndef _WIN32
|
|
|
|
#include <dlfcn.h>
|
|
|
|
#endif
|
|
|
|
#include <wchar.h>
|
|
|
|
|
|
|
|
#include <gdnative_api_struct.gen.h>
|
|
|
|
|
2023-06-02 11:05:45 +02:00
|
|
|
#include "_pandemonium_api.h"
|
2023-05-23 19:06:25 +02:00
|
|
|
|
|
|
|
static const char *PYTHONSCRIPT_RECOGNIZED_EXTENSIONS[] = { "py", "pyc", "pyo", "pyd", 0 };
|
|
|
|
static const char *PYTHONSCRIPT_RESERVED_WORDS[] = {
|
2023-06-02 17:59:23 +02:00
|
|
|
"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
|
2023-05-23 19:06:25 +02:00
|
|
|
};
|
|
|
|
static const char *PYTHONSCRIPT_COMMENT_DELIMITERS[] = { "#", "\"\"\"\"\"\"", 0 };
|
|
|
|
static const char *PYTHONSCRIPT_STRING_DELIMITERS[] = { "\" \"", "' '", 0 };
|
2023-06-02 11:05:45 +02:00
|
|
|
static pandemonium_pluginscript_language_desc desc;
|
2023-05-23 19:06:25 +02:00
|
|
|
static PyThreadState *gilstate = NULL;
|
|
|
|
|
|
|
|
/*
|
2023-06-02 11:31:19 +02:00
|
|
|
* Global variables exposing Pandemonium API to the pandemonium.hazmat cython module.
|
2023-06-02 11:13:10 +02:00
|
|
|
* Hence we must initialized them before loading `_pandemonium`/`pandemonium` modules
|
|
|
|
* (which both depend on `pandemonium.hazmat`).
|
2023-05-23 19:06:25 +02:00
|
|
|
*/
|
|
|
|
#ifdef _WIN32
|
2023-06-02 17:59:23 +02:00
|
|
|
#define PYTHONSCRIPT_EXPORT __declspec(dllexport)
|
2023-05-23 19:06:25 +02:00
|
|
|
#else
|
2023-06-02 17:59:23 +02:00
|
|
|
#define PYTHONSCRIPT_EXPORT
|
2023-05-23 19:06:25 +02:00
|
|
|
#endif
|
2023-06-02 11:05:45 +02:00
|
|
|
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;
|
2023-05-23 19:06:25 +02:00
|
|
|
|
2023-06-02 11:05:45 +02:00
|
|
|
static void _register_gdapi(const pandemonium_gdnative_init_options *options) {
|
2023-06-02 17:59:23 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2023-05-23 19:06:25 +02:00
|
|
|
}
|
|
|
|
|
2023-06-02 11:05:45 +02:00
|
|
|
GDN_EXPORT void pandemonium_gdnative_init(pandemonium_gdnative_init_options *options) {
|
2023-06-02 17:59:23 +02:00
|
|
|
// 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;
|
|
|
|
}
|
2023-05-23 19:06:25 +02:00
|
|
|
|
|
|
|
#ifndef _WIN32
|
2023-06-02 17:59:23 +02:00
|
|
|
// Make sure the shared library has all it symbols loaded
|
|
|
|
// (strange bug with libpython3.x.so otherwise...)
|
|
|
|
{
|
2023-06-03 00:20:32 +02:00
|
|
|
pandemonium_char_string wpstr = pythonscript_gdapi10->pandemonium_string_utf8(options->active_library_path);
|
|
|
|
const char *path = pythonscript_gdapi10->pandemonium_char_string_get_data(&wpstr);
|
|
|
|
|
2023-06-02 17:59:23 +02:00
|
|
|
dlopen(path, RTLD_NOW | RTLD_GLOBAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *err = dlerror();
|
|
|
|
if (err) {
|
|
|
|
GD_ERROR_PRINT(err);
|
|
|
|
return;
|
|
|
|
}
|
2023-05-23 19:06:25 +02:00
|
|
|
#endif
|
|
|
|
|
2023-06-02 17:59:23 +02:00
|
|
|
// Initialize CPython interpreter
|
|
|
|
|
|
|
|
// Retrieve path and set pythonhome
|
|
|
|
{
|
|
|
|
static wchar_t pythonhome[300];
|
2023-06-03 00:20:32 +02:00
|
|
|
pandemonium_string _pythonhome = pythonscript_gdapi10->pandemonium_string_get_base_dir(options->active_library_path);
|
2023-06-02 17:59:23 +02:00
|
|
|
|
|
|
|
pandemonium_char_16_string wpstr = pythonscript_gdapi10->pandemonium_string_utf16(&_pythonhome);
|
2023-06-03 00:20:32 +02:00
|
|
|
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';
|
|
|
|
|
2023-06-02 17:59:23 +02:00
|
|
|
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();
|
2023-05-23 19:06:25 +02:00
|
|
|
}
|
|
|
|
|
2023-06-02 11:05:45 +02:00
|
|
|
GDN_EXPORT void pandemonium_gdnative_singleton() {
|
2023-05-23 19:06:25 +02:00
|
|
|
}
|
|
|
|
|
2023-06-02 11:05:45 +02:00
|
|
|
GDN_EXPORT void pandemonium_gdnative_terminate() {
|
2023-06-02 17:59:23 +02:00
|
|
|
// Re-acquire the gil in order to finalize properly
|
|
|
|
PyEval_RestoreThread(gilstate);
|
2023-05-23 19:06:25 +02:00
|
|
|
|
2023-06-02 17:59:23 +02:00
|
|
|
int ret = Py_FinalizeEx();
|
|
|
|
if (ret != 0) {
|
|
|
|
GD_ERROR_PRINT("Cannot finalize python interpreter");
|
|
|
|
}
|
2023-05-23 19:06:25 +02:00
|
|
|
}
|