cf09952b6b
The issue is that ResourceFormatLoaderText is a singleton. It was created in a faulty way in ResourceFormatLoaderNativeScript::load It was created on the stack, which caused the static singleton pointer to be overwritten. This causes then segmentation faults if the singleton is used later on. IMO singleton creation needs to made safer to avoid other similar issues in the future.
1746 lines
48 KiB
C++
1746 lines
48 KiB
C++
/*************************************************************************/
|
|
/* nativescript.cpp */
|
|
/*************************************************************************/
|
|
/* This file is part of: */
|
|
/* GODOT ENGINE */
|
|
/* https://godotengine.org */
|
|
/*************************************************************************/
|
|
/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */
|
|
/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */
|
|
/* */
|
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
/* a copy of this software and associated documentation files (the */
|
|
/* "Software"), to deal in the Software without restriction, including */
|
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
/* the following conditions: */
|
|
/* */
|
|
/* The above copyright notice and this permission notice shall be */
|
|
/* included in all copies or substantial portions of the Software. */
|
|
/* */
|
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
/*************************************************************************/
|
|
|
|
#include "nativescript.h"
|
|
|
|
#include "gdnative/gdnative.h"
|
|
|
|
#include "core/global_constants.h"
|
|
#include "core/io/file_access_encrypted.h"
|
|
#include "core/os/file_access.h"
|
|
#include "core/os/os.h"
|
|
#include "core/project_settings.h"
|
|
|
|
#include "scene/main/scene_tree.h"
|
|
#include "scene/resources/scene_format_text.h"
|
|
|
|
#include <stdlib.h>
|
|
|
|
#ifndef NO_THREADS
|
|
#include "core/os/thread.h"
|
|
#endif
|
|
|
|
#if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED)
|
|
#include "api_generator.h"
|
|
#endif
|
|
|
|
#ifdef TOOLS_ENABLED
|
|
#include "editor/editor_node.h"
|
|
#endif
|
|
|
|
void NativeScript::_bind_methods() {
|
|
ClassDB::bind_method(D_METHOD("set_class_name", "class_name"), &NativeScript::set_class_name);
|
|
ClassDB::bind_method(D_METHOD("get_class_name"), &NativeScript::get_class_name);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_library", "library"), &NativeScript::set_library);
|
|
ClassDB::bind_method(D_METHOD("get_library"), &NativeScript::get_library);
|
|
|
|
ClassDB::bind_method(D_METHOD("set_script_class_name", "class_name"), &NativeScript::set_script_class_name);
|
|
ClassDB::bind_method(D_METHOD("get_script_class_name"), &NativeScript::get_script_class_name);
|
|
ClassDB::bind_method(D_METHOD("set_script_class_icon_path", "icon_path"), &NativeScript::set_script_class_icon_path);
|
|
ClassDB::bind_method(D_METHOD("get_script_class_icon_path"), &NativeScript::get_script_class_icon_path);
|
|
|
|
ClassDB::bind_method(D_METHOD("get_class_documentation"), &NativeScript::get_class_documentation);
|
|
ClassDB::bind_method(D_METHOD("get_method_documentation", "method"), &NativeScript::get_method_documentation);
|
|
ClassDB::bind_method(D_METHOD("get_signal_documentation", "signal_name"), &NativeScript::get_signal_documentation);
|
|
ClassDB::bind_method(D_METHOD("get_property_documentation", "path"), &NativeScript::get_property_documentation);
|
|
|
|
ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "class_name"), "set_class_name", "get_class_name");
|
|
ADD_PROPERTYNZ(PropertyInfo(Variant::OBJECT, "library", PROPERTY_HINT_RESOURCE_TYPE, "GDNativeLibrary"), "set_library", "get_library");
|
|
ADD_GROUP("Script Class", "script_class_");
|
|
ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "script_class_name"), "set_script_class_name", "get_script_class_name");
|
|
ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "script_class_icon_path", PROPERTY_HINT_FILE), "set_script_class_icon_path", "get_script_class_icon_path");
|
|
|
|
ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &NativeScript::_new, MethodInfo(Variant::OBJECT, "new"));
|
|
}
|
|
|
|
#define NSL NativeScriptLanguage::get_singleton()
|
|
|
|
#ifdef TOOLS_ENABLED
|
|
|
|
void NativeScript::_update_placeholder(PlaceHolderScriptInstance *p_placeholder) {
|
|
NativeScriptDesc *script_data = get_script_desc();
|
|
|
|
ERR_FAIL_COND(!script_data);
|
|
|
|
List<PropertyInfo> info;
|
|
get_script_property_list(&info);
|
|
Map<StringName, Variant> values;
|
|
for (List<PropertyInfo>::Element *E = info.front(); E; E = E->next()) {
|
|
Variant value;
|
|
get_property_default_value(E->get().name, value);
|
|
values[E->get().name] = value;
|
|
}
|
|
|
|
p_placeholder->update(info, values);
|
|
}
|
|
|
|
void NativeScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) {
|
|
placeholders.erase(p_placeholder);
|
|
}
|
|
|
|
#endif
|
|
|
|
void NativeScript::set_class_name(String p_class_name) {
|
|
class_name = p_class_name;
|
|
}
|
|
|
|
String NativeScript::get_class_name() const {
|
|
return class_name;
|
|
}
|
|
|
|
void NativeScript::set_library(Ref<GDNativeLibrary> p_library) {
|
|
if (!library.is_null()) {
|
|
WARN_PRINT("library on NativeScript already set. Do nothing.");
|
|
return;
|
|
}
|
|
library = p_library;
|
|
lib_path = library->get_current_library_path();
|
|
|
|
#ifndef NO_THREADS
|
|
if (Thread::get_caller_id() != Thread::get_main_id()) {
|
|
NSL->defer_init_library(p_library, this);
|
|
} else
|
|
#endif
|
|
{
|
|
NSL->init_library(p_library);
|
|
NSL->register_script(this);
|
|
}
|
|
}
|
|
|
|
Ref<GDNativeLibrary> NativeScript::get_library() const {
|
|
return library;
|
|
}
|
|
|
|
void NativeScript::set_script_class_name(String p_type) {
|
|
script_class_name = p_type;
|
|
}
|
|
|
|
String NativeScript::get_script_class_name() const {
|
|
return script_class_name;
|
|
}
|
|
|
|
void NativeScript::set_script_class_icon_path(String p_icon_path) {
|
|
script_class_icon_path = p_icon_path;
|
|
}
|
|
|
|
String NativeScript::get_script_class_icon_path() const {
|
|
return script_class_icon_path;
|
|
}
|
|
|
|
bool NativeScript::can_instance() const {
|
|
|
|
NativeScriptDesc *script_data = get_script_desc();
|
|
|
|
#ifdef TOOLS_ENABLED
|
|
|
|
return script_data || (!is_tool() && !ScriptServer::is_scripting_enabled());
|
|
#else
|
|
return script_data;
|
|
#endif
|
|
}
|
|
|
|
Ref<Script> NativeScript::get_base_script() const {
|
|
NativeScriptDesc *script_data = get_script_desc();
|
|
|
|
if (!script_data)
|
|
return Ref<Script>();
|
|
|
|
NativeScript *script = (NativeScript *)NSL->create_script();
|
|
Ref<NativeScript> ns = Ref<NativeScript>(script);
|
|
ERR_FAIL_COND_V(!ns.is_valid(), Ref<Script>());
|
|
|
|
ns->set_class_name(script_data->base);
|
|
ns->set_library(get_library());
|
|
return ns;
|
|
}
|
|
|
|
StringName NativeScript::get_instance_base_type() const {
|
|
NativeScriptDesc *script_data = get_script_desc();
|
|
|
|
if (!script_data)
|
|
return "";
|
|
|
|
return script_data->base_native_type;
|
|
}
|
|
|
|
ScriptInstance *NativeScript::instance_create(Object *p_this) {
|
|
|
|
NativeScriptDesc *script_data = get_script_desc();
|
|
|
|
if (!script_data) {
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef TOOLS_ENABLED
|
|
if (!ScriptServer::is_scripting_enabled() && !is_tool()) {
|
|
// placeholder for nodes. For tools we want the rool thing.
|
|
|
|
PlaceHolderScriptInstance *sins = memnew(PlaceHolderScriptInstance(NSL, Ref<Script>(this), p_this));
|
|
placeholders.insert(sins);
|
|
|
|
if (script_data->create_func.create_func) {
|
|
script_data->create_func.create_func(
|
|
(godot_object *)p_this,
|
|
script_data->create_func.method_data);
|
|
}
|
|
|
|
_update_placeholder(sins);
|
|
|
|
return sins;
|
|
}
|
|
#endif
|
|
|
|
NativeScriptInstance *nsi = memnew(NativeScriptInstance);
|
|
|
|
nsi->owner = p_this;
|
|
nsi->script = Ref<NativeScript>(this);
|
|
|
|
#ifndef TOOLS_ENABLED
|
|
if (!ScriptServer::is_scripting_enabled()) {
|
|
nsi->userdata = NULL;
|
|
} else {
|
|
nsi->userdata = script_data->create_func.create_func((godot_object *)p_this, script_data->create_func.method_data);
|
|
}
|
|
#else
|
|
nsi->userdata = script_data->create_func.create_func((godot_object *)p_this, script_data->create_func.method_data);
|
|
#endif
|
|
|
|
#ifndef NO_THREADS
|
|
owners_lock->lock();
|
|
#endif
|
|
|
|
instance_owners.insert(p_this);
|
|
|
|
#ifndef NO_THREADS
|
|
owners_lock->unlock();
|
|
#endif
|
|
|
|
return nsi;
|
|
}
|
|
|
|
bool NativeScript::instance_has(const Object *p_this) const {
|
|
return instance_owners.has((Object *)p_this);
|
|
}
|
|
|
|
bool NativeScript::has_source_code() const {
|
|
return false;
|
|
}
|
|
|
|
String NativeScript::get_source_code() const {
|
|
return "";
|
|
}
|
|
|
|
void NativeScript::set_source_code(const String &p_code) {
|
|
}
|
|
|
|
Error NativeScript::reload(bool p_keep_state) {
|
|
return FAILED;
|
|
}
|
|
|
|
bool NativeScript::has_method(const StringName &p_method) const {
|
|
NativeScriptDesc *script_data = get_script_desc();
|
|
|
|
while (script_data) {
|
|
if (script_data->methods.has(p_method))
|
|
return true;
|
|
|
|
script_data = script_data->base_data;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
MethodInfo NativeScript::get_method_info(const StringName &p_method) const {
|
|
NativeScriptDesc *script_data = get_script_desc();
|
|
|
|
if (!script_data)
|
|
return MethodInfo();
|
|
|
|
while (script_data) {
|
|
Map<StringName, NativeScriptDesc::Method>::Element *M = script_data->methods.find(p_method);
|
|
|
|
if (M)
|
|
return M->get().info;
|
|
|
|
script_data = script_data->base_data;
|
|
}
|
|
return MethodInfo();
|
|
}
|
|
|
|
bool NativeScript::is_tool() const {
|
|
NativeScriptDesc *script_data = get_script_desc();
|
|
|
|
if (script_data)
|
|
return script_data->is_tool;
|
|
|
|
return false;
|
|
}
|
|
|
|
ScriptLanguage *NativeScript::get_language() const {
|
|
return NativeScriptLanguage::get_singleton();
|
|
}
|
|
|
|
bool NativeScript::has_script_signal(const StringName &p_signal) const {
|
|
NativeScriptDesc *script_data = get_script_desc();
|
|
|
|
while (script_data) {
|
|
if (script_data->signals_.has(p_signal))
|
|
return true;
|
|
script_data = script_data->base_data;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void NativeScript::get_script_signal_list(List<MethodInfo> *r_signals) const {
|
|
NativeScriptDesc *script_data = get_script_desc();
|
|
|
|
if (!script_data)
|
|
return;
|
|
|
|
Set<MethodInfo> signals_;
|
|
|
|
while (script_data) {
|
|
|
|
for (Map<StringName, NativeScriptDesc::Signal>::Element *S = script_data->signals_.front(); S; S = S->next()) {
|
|
signals_.insert(S->get().signal);
|
|
}
|
|
|
|
script_data = script_data->base_data;
|
|
}
|
|
|
|
for (Set<MethodInfo>::Element *E = signals_.front(); E; E = E->next()) {
|
|
r_signals->push_back(E->get());
|
|
}
|
|
}
|
|
|
|
bool NativeScript::get_property_default_value(const StringName &p_property, Variant &r_value) const {
|
|
NativeScriptDesc *script_data = get_script_desc();
|
|
|
|
OrderedHashMap<StringName, NativeScriptDesc::Property>::Element P;
|
|
while (!P && script_data) {
|
|
P = script_data->properties.find(p_property);
|
|
script_data = script_data->base_data;
|
|
}
|
|
if (!P)
|
|
return false;
|
|
|
|
r_value = P.get().default_value;
|
|
return true;
|
|
}
|
|
|
|
void NativeScript::update_exports() {
|
|
}
|
|
|
|
void NativeScript::get_script_method_list(List<MethodInfo> *p_list) const {
|
|
NativeScriptDesc *script_data = get_script_desc();
|
|
|
|
if (!script_data)
|
|
return;
|
|
|
|
Set<MethodInfo> methods;
|
|
|
|
while (script_data) {
|
|
|
|
for (Map<StringName, NativeScriptDesc::Method>::Element *E = script_data->methods.front(); E; E = E->next()) {
|
|
methods.insert(E->get().info);
|
|
}
|
|
|
|
script_data = script_data->base_data;
|
|
}
|
|
|
|
for (Set<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
|
|
p_list->push_back(E->get());
|
|
}
|
|
}
|
|
|
|
void NativeScript::get_script_property_list(List<PropertyInfo> *p_list) const {
|
|
NativeScriptDesc *script_data = get_script_desc();
|
|
|
|
Set<StringName> existing_properties;
|
|
List<PropertyInfo>::Element *original_back = p_list->back();
|
|
while (script_data) {
|
|
List<PropertyInfo>::Element *insert_position = original_back;
|
|
|
|
for (OrderedHashMap<StringName, NativeScriptDesc::Property>::Element E = script_data->properties.front(); E; E = E.next()) {
|
|
if (!existing_properties.has(E.key())) {
|
|
insert_position = p_list->insert_after(insert_position, E.get().info);
|
|
existing_properties.insert(E.key());
|
|
}
|
|
}
|
|
script_data = script_data->base_data;
|
|
}
|
|
}
|
|
|
|
String NativeScript::get_class_documentation() const {
|
|
NativeScriptDesc *script_data = get_script_desc();
|
|
|
|
if (!script_data) {
|
|
ERR_EXPLAIN("Attempt to get class documentation on invalid NativeScript");
|
|
ERR_FAIL_V("");
|
|
}
|
|
|
|
return script_data->documentation;
|
|
}
|
|
|
|
String NativeScript::get_method_documentation(const StringName &p_method) const {
|
|
NativeScriptDesc *script_data = get_script_desc();
|
|
|
|
if (!script_data) {
|
|
ERR_EXPLAIN("Attempt to get method documentation on invalid NativeScript");
|
|
ERR_FAIL_V("");
|
|
}
|
|
|
|
while (script_data) {
|
|
|
|
Map<StringName, NativeScriptDesc::Method>::Element *method = script_data->methods.find(p_method);
|
|
|
|
if (method) {
|
|
return method->get().documentation;
|
|
}
|
|
|
|
script_data = script_data->base_data;
|
|
}
|
|
|
|
ERR_EXPLAIN("Attempt to get method documentation for non-existent method");
|
|
ERR_FAIL_V("");
|
|
}
|
|
|
|
String NativeScript::get_signal_documentation(const StringName &p_signal_name) const {
|
|
NativeScriptDesc *script_data = get_script_desc();
|
|
|
|
if (!script_data) {
|
|
ERR_EXPLAIN("Attempt to get signal documentation on invalid NativeScript");
|
|
ERR_FAIL_V("");
|
|
}
|
|
|
|
while (script_data) {
|
|
|
|
Map<StringName, NativeScriptDesc::Signal>::Element *signal = script_data->signals_.find(p_signal_name);
|
|
|
|
if (signal) {
|
|
return signal->get().documentation;
|
|
}
|
|
|
|
script_data = script_data->base_data;
|
|
}
|
|
|
|
ERR_EXPLAIN("Attempt to get signal documentation for non-existent signal");
|
|
ERR_FAIL_V("");
|
|
}
|
|
|
|
String NativeScript::get_property_documentation(const StringName &p_path) const {
|
|
NativeScriptDesc *script_data = get_script_desc();
|
|
|
|
if (!script_data) {
|
|
ERR_EXPLAIN("Attempt to get property documentation on invalid NativeScript");
|
|
ERR_FAIL_V("");
|
|
}
|
|
|
|
while (script_data) {
|
|
|
|
OrderedHashMap<StringName, NativeScriptDesc::Property>::Element property = script_data->properties.find(p_path);
|
|
|
|
if (property) {
|
|
return property.get().documentation;
|
|
}
|
|
|
|
script_data = script_data->base_data;
|
|
}
|
|
|
|
ERR_EXPLAIN("Attempt to get property documentation for non-existent signal");
|
|
ERR_FAIL_V("");
|
|
}
|
|
|
|
Variant NativeScript::_new(const Variant **p_args, int p_argcount, Variant::CallError &r_error) {
|
|
|
|
if (lib_path.empty() || class_name.empty() || library.is_null()) {
|
|
r_error.error = Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL;
|
|
return Variant();
|
|
}
|
|
|
|
NativeScriptDesc *script_data = get_script_desc();
|
|
|
|
if (!script_data) {
|
|
r_error.error = Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL;
|
|
return Variant();
|
|
}
|
|
|
|
r_error.error = Variant::CallError::CALL_OK;
|
|
|
|
REF ref;
|
|
Object *owner = NULL;
|
|
|
|
if (!(script_data->base_native_type == "")) {
|
|
owner = ClassDB::instance(script_data->base_native_type);
|
|
} else {
|
|
owner = memnew(Reference);
|
|
}
|
|
|
|
if (!owner) {
|
|
r_error.error = Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL;
|
|
return Variant();
|
|
}
|
|
|
|
Reference *r = Object::cast_to<Reference>(owner);
|
|
if (r) {
|
|
ref = REF(r);
|
|
}
|
|
|
|
NativeScriptInstance *instance = (NativeScriptInstance *)instance_create(owner);
|
|
|
|
owner->set_script_instance(instance);
|
|
|
|
if (!instance) {
|
|
if (ref.is_null()) {
|
|
memdelete(owner); //no owner, sorry
|
|
}
|
|
return Variant();
|
|
}
|
|
|
|
if (ref.is_valid()) {
|
|
return ref;
|
|
} else {
|
|
return owner;
|
|
}
|
|
}
|
|
|
|
NativeScript::NativeScript() {
|
|
library = Ref<GDNative>();
|
|
lib_path = "";
|
|
class_name = "";
|
|
#ifndef NO_THREADS
|
|
owners_lock = Mutex::create();
|
|
#endif
|
|
}
|
|
|
|
NativeScript::~NativeScript() {
|
|
NSL->unregister_script(this);
|
|
|
|
#ifndef NO_THREADS
|
|
memdelete(owners_lock);
|
|
#endif
|
|
}
|
|
|
|
#define GET_SCRIPT_DESC() script->get_script_desc()
|
|
|
|
void NativeScriptInstance::_ml_call_reversed(NativeScriptDesc *script_data, const StringName &p_method, const Variant **p_args, int p_argcount) {
|
|
if (script_data->base_data) {
|
|
_ml_call_reversed(script_data->base_data, p_method, p_args, p_argcount);
|
|
}
|
|
|
|
Map<StringName, NativeScriptDesc::Method>::Element *E = script_data->methods.find(p_method);
|
|
if (E) {
|
|
godot_variant res = E->get().method.method((godot_object *)owner, E->get().method.method_data, userdata, p_argcount, (godot_variant **)p_args);
|
|
godot_variant_destroy(&res);
|
|
}
|
|
}
|
|
|
|
bool NativeScriptInstance::set(const StringName &p_name, const Variant &p_value) {
|
|
NativeScriptDesc *script_data = GET_SCRIPT_DESC();
|
|
|
|
while (script_data) {
|
|
OrderedHashMap<StringName, NativeScriptDesc::Property>::Element P = script_data->properties.find(p_name);
|
|
if (P) {
|
|
P.get().setter.set_func((godot_object *)owner,
|
|
P.get().setter.method_data,
|
|
userdata,
|
|
(godot_variant *)&p_value);
|
|
return true;
|
|
}
|
|
|
|
Map<StringName, NativeScriptDesc::Method>::Element *E = script_data->methods.find("_set");
|
|
if (E) {
|
|
Variant name = p_name;
|
|
const Variant *args[2] = { &name, &p_value };
|
|
|
|
godot_variant result;
|
|
result = E->get().method.method((godot_object *)owner,
|
|
E->get().method.method_data,
|
|
userdata,
|
|
2,
|
|
(godot_variant **)args);
|
|
bool handled = *(Variant *)&result;
|
|
godot_variant_destroy(&result);
|
|
if (handled) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
script_data = script_data->base_data;
|
|
}
|
|
return false;
|
|
}
|
|
bool NativeScriptInstance::get(const StringName &p_name, Variant &r_ret) const {
|
|
NativeScriptDesc *script_data = GET_SCRIPT_DESC();
|
|
|
|
while (script_data) {
|
|
OrderedHashMap<StringName, NativeScriptDesc::Property>::Element P = script_data->properties.find(p_name);
|
|
if (P) {
|
|
godot_variant value;
|
|
value = P.get().getter.get_func((godot_object *)owner,
|
|
P.get().getter.method_data,
|
|
userdata);
|
|
r_ret = *(Variant *)&value;
|
|
godot_variant_destroy(&value);
|
|
return true;
|
|
}
|
|
|
|
Map<StringName, NativeScriptDesc::Method>::Element *E = script_data->methods.find("_get");
|
|
if (E) {
|
|
Variant name = p_name;
|
|
const Variant *args[1] = { &name };
|
|
|
|
godot_variant result;
|
|
result = E->get().method.method((godot_object *)owner,
|
|
E->get().method.method_data,
|
|
userdata,
|
|
1,
|
|
(godot_variant **)args);
|
|
r_ret = *(Variant *)&result;
|
|
godot_variant_destroy(&result);
|
|
if (r_ret.get_type() != Variant::NIL) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
script_data = script_data->base_data;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void NativeScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const {
|
|
script->get_script_property_list(p_properties);
|
|
|
|
NativeScriptDesc *script_data = GET_SCRIPT_DESC();
|
|
|
|
while (script_data) {
|
|
|
|
Map<StringName, NativeScriptDesc::Method>::Element *E = script_data->methods.find("_get_property_list");
|
|
if (E) {
|
|
|
|
godot_variant result;
|
|
result = E->get().method.method((godot_object *)owner,
|
|
E->get().method.method_data,
|
|
userdata,
|
|
0,
|
|
NULL);
|
|
Variant res = *(Variant *)&result;
|
|
godot_variant_destroy(&result);
|
|
|
|
if (res.get_type() != Variant::ARRAY) {
|
|
ERR_EXPLAIN("_get_property_list must return an array of dictionaries");
|
|
ERR_FAIL();
|
|
}
|
|
|
|
Array arr = res;
|
|
for (int i = 0; i < arr.size(); i++) {
|
|
Dictionary d = arr[i];
|
|
|
|
ERR_CONTINUE(!d.has("name"));
|
|
ERR_CONTINUE(!d.has("type"));
|
|
|
|
PropertyInfo info;
|
|
|
|
info.type = Variant::Type(d["type"].operator int64_t());
|
|
ERR_CONTINUE(info.type < 0 || info.type >= Variant::VARIANT_MAX);
|
|
|
|
info.name = d["name"];
|
|
ERR_CONTINUE(info.name == "");
|
|
|
|
if (d.has("hint")) {
|
|
info.hint = PropertyHint(d["hint"].operator int64_t());
|
|
}
|
|
|
|
if (d.has("hint_string")) {
|
|
info.hint_string = d["hint_string"];
|
|
}
|
|
|
|
if (d.has("usage")) {
|
|
info.usage = d["usage"];
|
|
}
|
|
|
|
p_properties->push_back(info);
|
|
}
|
|
}
|
|
|
|
script_data = script_data->base_data;
|
|
}
|
|
return;
|
|
}
|
|
|
|
Variant::Type NativeScriptInstance::get_property_type(const StringName &p_name, bool *r_is_valid) const {
|
|
|
|
NativeScriptDesc *script_data = GET_SCRIPT_DESC();
|
|
|
|
while (script_data) {
|
|
|
|
OrderedHashMap<StringName, NativeScriptDesc::Property>::Element P = script_data->properties.find(p_name);
|
|
if (P) {
|
|
*r_is_valid = true;
|
|
return P.get().info.type;
|
|
}
|
|
|
|
script_data = script_data->base_data;
|
|
}
|
|
return Variant::NIL;
|
|
}
|
|
|
|
void NativeScriptInstance::get_method_list(List<MethodInfo> *p_list) const {
|
|
script->get_script_method_list(p_list);
|
|
}
|
|
|
|
bool NativeScriptInstance::has_method(const StringName &p_method) const {
|
|
return script->has_method(p_method);
|
|
}
|
|
|
|
Variant NativeScriptInstance::call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) {
|
|
|
|
NativeScriptDesc *script_data = GET_SCRIPT_DESC();
|
|
|
|
while (script_data) {
|
|
Map<StringName, NativeScriptDesc::Method>::Element *E = script_data->methods.find(p_method);
|
|
if (E) {
|
|
godot_variant result;
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
current_method_call = p_method;
|
|
#endif
|
|
|
|
result = E->get().method.method((godot_object *)owner,
|
|
E->get().method.method_data,
|
|
userdata,
|
|
p_argcount,
|
|
(godot_variant **)p_args);
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
current_method_call = "";
|
|
#endif
|
|
|
|
Variant res = *(Variant *)&result;
|
|
godot_variant_destroy(&result);
|
|
r_error.error = Variant::CallError::CALL_OK;
|
|
return res;
|
|
}
|
|
|
|
script_data = script_data->base_data;
|
|
}
|
|
|
|
r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD;
|
|
return Variant();
|
|
}
|
|
|
|
void NativeScriptInstance::notification(int p_notification) {
|
|
#ifdef DEBUG_ENABLED
|
|
if (p_notification == MainLoop::NOTIFICATION_CRASH) {
|
|
if (current_method_call != StringName("")) {
|
|
ERR_PRINTS("NativeScriptInstance detected crash on method: " + current_method_call);
|
|
current_method_call = "";
|
|
}
|
|
}
|
|
#endif
|
|
|
|
Variant value = p_notification;
|
|
const Variant *args[1] = { &value };
|
|
call_multilevel("_notification", args, 1);
|
|
}
|
|
|
|
void NativeScriptInstance::refcount_incremented() {
|
|
Variant::CallError err;
|
|
call("_refcount_incremented", NULL, 0, err);
|
|
if (err.error != Variant::CallError::CALL_OK && err.error != Variant::CallError::CALL_ERROR_INVALID_METHOD) {
|
|
ERR_PRINT("Failed to invoke _refcount_incremented - should not happen");
|
|
}
|
|
}
|
|
|
|
bool NativeScriptInstance::refcount_decremented() {
|
|
Variant::CallError err;
|
|
Variant ret = call("_refcount_decremented", NULL, 0, err);
|
|
if (err.error != Variant::CallError::CALL_OK && err.error != Variant::CallError::CALL_ERROR_INVALID_METHOD) {
|
|
ERR_PRINT("Failed to invoke _refcount_decremented - should not happen");
|
|
return true; // assume we can destroy the object
|
|
}
|
|
if (err.error == Variant::CallError::CALL_ERROR_INVALID_METHOD) {
|
|
// the method does not exist, default is true
|
|
return true;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Ref<Script> NativeScriptInstance::get_script() const {
|
|
return script;
|
|
}
|
|
|
|
MultiplayerAPI::RPCMode NativeScriptInstance::get_rpc_mode(const StringName &p_method) const {
|
|
|
|
NativeScriptDesc *script_data = GET_SCRIPT_DESC();
|
|
|
|
while (script_data) {
|
|
|
|
Map<StringName, NativeScriptDesc::Method>::Element *E = script_data->methods.find(p_method);
|
|
if (E) {
|
|
switch (E->get().rpc_mode) {
|
|
case GODOT_METHOD_RPC_MODE_DISABLED:
|
|
return MultiplayerAPI::RPC_MODE_DISABLED;
|
|
case GODOT_METHOD_RPC_MODE_REMOTE:
|
|
return MultiplayerAPI::RPC_MODE_REMOTE;
|
|
case GODOT_METHOD_RPC_MODE_MASTER:
|
|
return MultiplayerAPI::RPC_MODE_MASTER;
|
|
case GODOT_METHOD_RPC_MODE_PUPPET:
|
|
return MultiplayerAPI::RPC_MODE_PUPPET;
|
|
case GODOT_METHOD_RPC_MODE_REMOTESYNC:
|
|
return MultiplayerAPI::RPC_MODE_REMOTESYNC;
|
|
case GODOT_METHOD_RPC_MODE_MASTERSYNC:
|
|
return MultiplayerAPI::RPC_MODE_MASTERSYNC;
|
|
case GODOT_METHOD_RPC_MODE_PUPPETSYNC:
|
|
return MultiplayerAPI::RPC_MODE_PUPPETSYNC;
|
|
default:
|
|
return MultiplayerAPI::RPC_MODE_DISABLED;
|
|
}
|
|
}
|
|
|
|
script_data = script_data->base_data;
|
|
}
|
|
|
|
return MultiplayerAPI::RPC_MODE_DISABLED;
|
|
}
|
|
|
|
MultiplayerAPI::RPCMode NativeScriptInstance::get_rset_mode(const StringName &p_variable) const {
|
|
|
|
NativeScriptDesc *script_data = GET_SCRIPT_DESC();
|
|
|
|
while (script_data) {
|
|
|
|
OrderedHashMap<StringName, NativeScriptDesc::Property>::Element E = script_data->properties.find(p_variable);
|
|
if (E) {
|
|
switch (E.get().rset_mode) {
|
|
case GODOT_METHOD_RPC_MODE_DISABLED:
|
|
return MultiplayerAPI::RPC_MODE_DISABLED;
|
|
case GODOT_METHOD_RPC_MODE_REMOTE:
|
|
return MultiplayerAPI::RPC_MODE_REMOTE;
|
|
case GODOT_METHOD_RPC_MODE_MASTER:
|
|
return MultiplayerAPI::RPC_MODE_MASTER;
|
|
case GODOT_METHOD_RPC_MODE_PUPPET:
|
|
return MultiplayerAPI::RPC_MODE_PUPPET;
|
|
case GODOT_METHOD_RPC_MODE_REMOTESYNC:
|
|
return MultiplayerAPI::RPC_MODE_REMOTESYNC;
|
|
case GODOT_METHOD_RPC_MODE_MASTERSYNC:
|
|
return MultiplayerAPI::RPC_MODE_MASTERSYNC;
|
|
case GODOT_METHOD_RPC_MODE_PUPPETSYNC:
|
|
return MultiplayerAPI::RPC_MODE_PUPPETSYNC;
|
|
default:
|
|
return MultiplayerAPI::RPC_MODE_DISABLED;
|
|
}
|
|
}
|
|
|
|
script_data = script_data->base_data;
|
|
}
|
|
|
|
return MultiplayerAPI::RPC_MODE_DISABLED;
|
|
}
|
|
|
|
ScriptLanguage *NativeScriptInstance::get_language() {
|
|
return NativeScriptLanguage::get_singleton();
|
|
}
|
|
|
|
void NativeScriptInstance::call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount) {
|
|
NativeScriptDesc *script_data = GET_SCRIPT_DESC();
|
|
|
|
while (script_data) {
|
|
Map<StringName, NativeScriptDesc::Method>::Element *E = script_data->methods.find(p_method);
|
|
if (E) {
|
|
godot_variant res = E->get().method.method((godot_object *)owner,
|
|
E->get().method.method_data,
|
|
userdata,
|
|
p_argcount,
|
|
(godot_variant **)p_args);
|
|
godot_variant_destroy(&res);
|
|
}
|
|
script_data = script_data->base_data;
|
|
}
|
|
}
|
|
|
|
void NativeScriptInstance::call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount) {
|
|
NativeScriptDesc *script_data = GET_SCRIPT_DESC();
|
|
|
|
if (script_data) {
|
|
_ml_call_reversed(script_data, p_method, p_args, p_argcount);
|
|
}
|
|
}
|
|
|
|
NativeScriptInstance::~NativeScriptInstance() {
|
|
|
|
NativeScriptDesc *script_data = GET_SCRIPT_DESC();
|
|
|
|
if (!script_data)
|
|
return;
|
|
|
|
script_data->destroy_func.destroy_func((godot_object *)owner, script_data->destroy_func.method_data, userdata);
|
|
|
|
if (owner) {
|
|
|
|
#ifndef NO_THREADS
|
|
script->owners_lock->lock();
|
|
#endif
|
|
|
|
script->instance_owners.erase(owner);
|
|
|
|
#ifndef NO_THREADS
|
|
script->owners_lock->unlock();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
NativeScriptLanguage *NativeScriptLanguage::singleton;
|
|
|
|
void NativeScriptLanguage::_unload_stuff(bool p_reload) {
|
|
|
|
Map<String, Ref<GDNative> > erase_and_unload;
|
|
|
|
for (Map<String, Map<StringName, NativeScriptDesc> >::Element *L = library_classes.front(); L; L = L->next()) {
|
|
|
|
String lib_path = L->key();
|
|
Map<StringName, NativeScriptDesc> classes = L->get();
|
|
|
|
if (p_reload) {
|
|
|
|
Map<String, Ref<GDNative> >::Element *E = library_gdnatives.find(lib_path);
|
|
Ref<GDNative> gdn;
|
|
|
|
if (E) {
|
|
gdn = E->get();
|
|
}
|
|
|
|
bool should_reload = false;
|
|
|
|
if (gdn.is_valid()) {
|
|
Ref<GDNativeLibrary> lib = gdn->get_library();
|
|
if (lib.is_valid()) {
|
|
should_reload = lib->is_reloadable();
|
|
}
|
|
}
|
|
|
|
if (!should_reload) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
Map<String, Ref<GDNative> >::Element *E = library_gdnatives.find(lib_path);
|
|
Ref<GDNative> gdn;
|
|
|
|
if (E) {
|
|
gdn = E->get();
|
|
}
|
|
|
|
for (Map<StringName, NativeScriptDesc>::Element *C = classes.front(); C; C = C->next()) {
|
|
|
|
// free property stuff first
|
|
for (OrderedHashMap<StringName, NativeScriptDesc::Property>::Element P = C->get().properties.front(); P; P = P.next()) {
|
|
if (P.get().getter.free_func)
|
|
P.get().getter.free_func(P.get().getter.method_data);
|
|
|
|
if (P.get().setter.free_func)
|
|
P.get().setter.free_func(P.get().setter.method_data);
|
|
}
|
|
|
|
// free method stuff
|
|
for (Map<StringName, NativeScriptDesc::Method>::Element *M = C->get().methods.front(); M; M = M->next()) {
|
|
if (M->get().method.free_func)
|
|
M->get().method.free_func(M->get().method.method_data);
|
|
}
|
|
|
|
// free constructor/destructor
|
|
if (C->get().create_func.free_func)
|
|
C->get().create_func.free_func(C->get().create_func.method_data);
|
|
|
|
if (C->get().destroy_func.free_func)
|
|
C->get().destroy_func.free_func(C->get().destroy_func.method_data);
|
|
}
|
|
|
|
erase_and_unload.insert(lib_path, gdn);
|
|
}
|
|
|
|
for (Map<String, Ref<GDNative> >::Element *E = erase_and_unload.front(); E; E = E->next()) {
|
|
String lib_path = E->key();
|
|
Ref<GDNative> gdn = E->get();
|
|
|
|
library_classes.erase(lib_path);
|
|
|
|
if (gdn.is_valid() && gdn->get_library().is_valid()) {
|
|
Ref<GDNativeLibrary> lib = gdn->get_library();
|
|
void *terminate_fn;
|
|
Error err = gdn->get_symbol(lib->get_symbol_prefix() + _terminate_call_name, terminate_fn, true);
|
|
|
|
if (err == OK) {
|
|
void (*terminate)(void *) = (void (*)(void *))terminate_fn;
|
|
|
|
terminate((void *)&lib_path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NativeScriptLanguage::NativeScriptLanguage() {
|
|
NativeScriptLanguage::singleton = this;
|
|
#ifndef NO_THREADS
|
|
has_objects_to_register = false;
|
|
mutex = Mutex::create();
|
|
#endif
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
profiling = false;
|
|
#endif
|
|
|
|
_init_call_type = "nativescript_init";
|
|
_init_call_name = "nativescript_init";
|
|
_terminate_call_name = "nativescript_terminate";
|
|
_noarg_call_type = "nativescript_no_arg";
|
|
_frame_call_name = "nativescript_frame";
|
|
#ifndef NO_THREADS
|
|
_thread_enter_call_name = "nativescript_thread_enter";
|
|
_thread_exit_call_name = "nativescript_thread_exit";
|
|
#endif
|
|
}
|
|
|
|
NativeScriptLanguage::~NativeScriptLanguage() {
|
|
|
|
for (Map<String, Ref<GDNative> >::Element *L = NSL->library_gdnatives.front(); L; L = L->next()) {
|
|
|
|
if (L->get().is_valid())
|
|
L->get()->terminate();
|
|
}
|
|
|
|
NSL->library_classes.clear();
|
|
NSL->library_gdnatives.clear();
|
|
NSL->library_script_users.clear();
|
|
|
|
#ifndef NO_THREADS
|
|
memdelete(mutex);
|
|
#endif
|
|
}
|
|
|
|
String NativeScriptLanguage::get_name() const {
|
|
return "NativeScript";
|
|
}
|
|
|
|
void _add_reload_node() {
|
|
#ifdef TOOLS_ENABLED
|
|
NativeReloadNode *rn = memnew(NativeReloadNode);
|
|
EditorNode::get_singleton()->add_child(rn);
|
|
#endif
|
|
}
|
|
|
|
void NativeScriptLanguage::init() {
|
|
|
|
#if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED)
|
|
|
|
List<String> args = OS::get_singleton()->get_cmdline_args();
|
|
|
|
List<String>::Element *E = args.find("--gdnative-generate-json-api");
|
|
|
|
if (E && E->next()) {
|
|
if (generate_c_api(E->next()->get()) != OK) {
|
|
ERR_PRINT("Failed to generate C API\n");
|
|
}
|
|
exit(0);
|
|
}
|
|
#endif
|
|
|
|
#ifdef TOOLS_ENABLED
|
|
EditorNode::add_init_callback(&_add_reload_node);
|
|
#endif
|
|
}
|
|
String NativeScriptLanguage::get_type() const {
|
|
return "NativeScript";
|
|
}
|
|
String NativeScriptLanguage::get_extension() const {
|
|
return "gdns";
|
|
}
|
|
Error NativeScriptLanguage::execute_file(const String &p_path) {
|
|
return OK; // Qué?
|
|
}
|
|
void NativeScriptLanguage::finish() {
|
|
_unload_stuff();
|
|
}
|
|
void NativeScriptLanguage::get_reserved_words(List<String> *p_words) const {
|
|
}
|
|
void NativeScriptLanguage::get_comment_delimiters(List<String> *p_delimiters) const {
|
|
}
|
|
void NativeScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const {
|
|
}
|
|
|
|
Ref<Script> NativeScriptLanguage::get_template(const String &p_class_name, const String &p_base_class_name) const {
|
|
NativeScript *s = memnew(NativeScript);
|
|
s->set_class_name(p_class_name);
|
|
return Ref<NativeScript>(s);
|
|
}
|
|
bool NativeScriptLanguage::validate(const String &p_script, int &r_line_error, int &r_col_error, String &r_test_error, const String &p_path, List<String> *r_functions, List<ScriptLanguage::Warning> *r_warnings, Set<int> *r_safe_lines) const {
|
|
return true;
|
|
}
|
|
|
|
Script *NativeScriptLanguage::create_script() const {
|
|
NativeScript *script = memnew(NativeScript);
|
|
return script;
|
|
}
|
|
bool NativeScriptLanguage::has_named_classes() const {
|
|
return true;
|
|
}
|
|
bool NativeScriptLanguage::supports_builtin_mode() const {
|
|
return true;
|
|
}
|
|
int NativeScriptLanguage::find_function(const String &p_function, const String &p_code) const {
|
|
return -1;
|
|
}
|
|
String NativeScriptLanguage::make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const {
|
|
return "";
|
|
}
|
|
void NativeScriptLanguage::auto_indent_code(String &p_code, int p_from_line, int p_to_line) const {
|
|
}
|
|
void NativeScriptLanguage::add_global_constant(const StringName &p_variable, const Variant &p_value) {
|
|
}
|
|
|
|
// Debugging stuff here. Not used for now.
|
|
String NativeScriptLanguage::debug_get_error() const {
|
|
return "";
|
|
}
|
|
int NativeScriptLanguage::debug_get_stack_level_count() const {
|
|
return -1;
|
|
}
|
|
int NativeScriptLanguage::debug_get_stack_level_line(int p_level) const {
|
|
return -1;
|
|
}
|
|
String NativeScriptLanguage::debug_get_stack_level_function(int p_level) const {
|
|
return "";
|
|
}
|
|
String NativeScriptLanguage::debug_get_stack_level_source(int p_level) const {
|
|
return "";
|
|
}
|
|
void NativeScriptLanguage::debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {
|
|
}
|
|
void NativeScriptLanguage::debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {
|
|
}
|
|
void NativeScriptLanguage::debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {
|
|
}
|
|
String NativeScriptLanguage::debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) {
|
|
return "";
|
|
}
|
|
// Debugging stuff end.
|
|
|
|
void NativeScriptLanguage::reload_all_scripts() {
|
|
}
|
|
|
|
void NativeScriptLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) {
|
|
}
|
|
void NativeScriptLanguage::get_recognized_extensions(List<String> *p_extensions) const {
|
|
p_extensions->push_back("gdns");
|
|
}
|
|
|
|
void NativeScriptLanguage::get_public_functions(List<MethodInfo> *p_functions) const {
|
|
}
|
|
|
|
void NativeScriptLanguage::get_public_constants(List<Pair<String, Variant> > *p_constants) const {
|
|
}
|
|
|
|
void NativeScriptLanguage::profiling_start() {
|
|
#ifdef DEBUG_ENABLED
|
|
#ifndef NO_THREADS
|
|
MutexLock lock(mutex);
|
|
#endif
|
|
|
|
profile_data.clear();
|
|
profiling = true;
|
|
#endif
|
|
}
|
|
|
|
void NativeScriptLanguage::profiling_stop() {
|
|
#ifdef DEBUG_ENABLED
|
|
#ifndef NO_THREADS
|
|
MutexLock lock(mutex);
|
|
#endif
|
|
|
|
profiling = false;
|
|
#endif
|
|
}
|
|
|
|
int NativeScriptLanguage::profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) {
|
|
#ifdef DEBUG_ENABLED
|
|
#ifndef NO_THREADS
|
|
MutexLock lock(mutex);
|
|
#endif
|
|
int current = 0;
|
|
|
|
for (Map<StringName, ProfileData>::Element *d = profile_data.front(); d; d = d->next()) {
|
|
if (current >= p_info_max)
|
|
break;
|
|
|
|
p_info_arr[current].call_count = d->get().call_count;
|
|
p_info_arr[current].self_time = d->get().self_time;
|
|
p_info_arr[current].total_time = d->get().total_time;
|
|
p_info_arr[current].signature = d->get().signature;
|
|
current++;
|
|
}
|
|
|
|
return current;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
int NativeScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) {
|
|
#ifdef DEBUG_ENABLED
|
|
#ifndef NO_THREADS
|
|
MutexLock lock(mutex);
|
|
#endif
|
|
int current = 0;
|
|
|
|
for (Map<StringName, ProfileData>::Element *d = profile_data.front(); d; d = d->next()) {
|
|
if (current >= p_info_max)
|
|
break;
|
|
|
|
if (d->get().last_frame_call_count) {
|
|
p_info_arr[current].call_count = d->get().last_frame_call_count;
|
|
p_info_arr[current].self_time = d->get().last_frame_self_time;
|
|
p_info_arr[current].total_time = d->get().last_frame_total_time;
|
|
p_info_arr[current].signature = d->get().signature;
|
|
current++;
|
|
}
|
|
}
|
|
|
|
return current;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
void NativeScriptLanguage::profiling_add_data(StringName p_signature, uint64_t p_time) {
|
|
#ifdef DEBUG_ENABLED
|
|
#ifndef NO_THREADS
|
|
MutexLock lock(mutex);
|
|
#endif
|
|
|
|
Map<StringName, ProfileData>::Element *d = profile_data.find(p_signature);
|
|
if (d) {
|
|
d->get().call_count += 1;
|
|
d->get().total_time += p_time;
|
|
d->get().frame_call_count += 1;
|
|
d->get().frame_total_time += p_time;
|
|
} else {
|
|
ProfileData data;
|
|
|
|
data.signature = p_signature;
|
|
data.call_count = 1;
|
|
data.self_time = 0;
|
|
data.total_time = p_time;
|
|
data.frame_call_count = 1;
|
|
data.frame_self_time = 0;
|
|
data.frame_total_time = p_time;
|
|
data.last_frame_call_count = 0;
|
|
data.last_frame_self_time = 0;
|
|
data.last_frame_total_time = 0;
|
|
|
|
profile_data.insert(p_signature, data);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int NativeScriptLanguage::register_binding_functions(godot_instance_binding_functions p_binding_functions) {
|
|
|
|
// find index
|
|
|
|
int idx = -1;
|
|
|
|
for (int i = 0; i < binding_functions.size(); i++) {
|
|
if (!binding_functions[i].first) {
|
|
// free, we'll take it
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (idx == -1) {
|
|
idx = binding_functions.size();
|
|
binding_functions.resize(idx + 1);
|
|
}
|
|
|
|
// set the functions
|
|
binding_functions.write[idx].first = true;
|
|
binding_functions.write[idx].second = p_binding_functions;
|
|
|
|
return idx;
|
|
}
|
|
|
|
void NativeScriptLanguage::unregister_binding_functions(int p_idx) {
|
|
ERR_FAIL_INDEX(p_idx, binding_functions.size());
|
|
|
|
for (Set<Vector<void *> *>::Element *E = binding_instances.front(); E; E = E->next()) {
|
|
Vector<void *> &binding_data = *E->get();
|
|
|
|
if (binding_data[p_idx] && binding_functions[p_idx].second.free_instance_binding_data)
|
|
binding_functions[p_idx].second.free_instance_binding_data(binding_functions[p_idx].second.data, binding_data[p_idx]);
|
|
}
|
|
|
|
binding_functions.write[p_idx].first = false;
|
|
|
|
if (binding_functions[p_idx].second.free_func)
|
|
binding_functions[p_idx].second.free_func(binding_functions[p_idx].second.data);
|
|
}
|
|
|
|
void *NativeScriptLanguage::get_instance_binding_data(int p_idx, Object *p_object) {
|
|
ERR_FAIL_INDEX_V(p_idx, binding_functions.size(), NULL);
|
|
|
|
if (!binding_functions[p_idx].first) {
|
|
ERR_EXPLAIN("Tried to get binding data for a nativescript binding that does not exist");
|
|
ERR_FAIL_V(NULL);
|
|
}
|
|
|
|
Vector<void *> *binding_data = (Vector<void *> *)p_object->get_script_instance_binding(lang_idx);
|
|
|
|
if (!binding_data)
|
|
return NULL; // should never happen.
|
|
|
|
if (binding_data->size() <= p_idx) {
|
|
// okay, add new elements here.
|
|
int old_size = binding_data->size();
|
|
|
|
binding_data->resize(p_idx + 1);
|
|
|
|
for (int i = old_size; i <= p_idx; i++) {
|
|
(*binding_data).write[i] = NULL;
|
|
}
|
|
}
|
|
|
|
if (!(*binding_data)[p_idx]) {
|
|
|
|
const void *global_type_tag = global_type_tags[p_idx].get(p_object->get_class_name());
|
|
|
|
// no binding data yet, soooooo alloc new one \o/
|
|
(*binding_data).write[p_idx] = binding_functions[p_idx].second.alloc_instance_binding_data(binding_functions[p_idx].second.data, global_type_tag, (godot_object *)p_object);
|
|
}
|
|
|
|
return (*binding_data)[p_idx];
|
|
}
|
|
|
|
void *NativeScriptLanguage::alloc_instance_binding_data(Object *p_object) {
|
|
|
|
Vector<void *> *binding_data = new Vector<void *>;
|
|
|
|
binding_data->resize(binding_functions.size());
|
|
|
|
for (int i = 0; i < binding_functions.size(); i++) {
|
|
(*binding_data).write[i] = NULL;
|
|
}
|
|
|
|
binding_instances.insert(binding_data);
|
|
|
|
return (void *)binding_data;
|
|
}
|
|
|
|
void NativeScriptLanguage::free_instance_binding_data(void *p_data) {
|
|
|
|
if (!p_data)
|
|
return;
|
|
|
|
Vector<void *> &binding_data = *(Vector<void *> *)p_data;
|
|
|
|
for (int i = 0; i < binding_data.size(); i++) {
|
|
if (!binding_data[i])
|
|
continue;
|
|
|
|
if (binding_functions[i].first && binding_functions[i].second.free_instance_binding_data) {
|
|
binding_functions[i].second.free_instance_binding_data(binding_functions[i].second.data, binding_data[i]);
|
|
}
|
|
}
|
|
|
|
binding_instances.erase(&binding_data);
|
|
|
|
delete &binding_data;
|
|
}
|
|
|
|
void NativeScriptLanguage::refcount_incremented_instance_binding(Object *p_object) {
|
|
|
|
void *data = p_object->get_script_instance_binding(lang_idx);
|
|
|
|
if (!data)
|
|
return;
|
|
|
|
Vector<void *> &binding_data = *(Vector<void *> *)data;
|
|
|
|
for (int i = 0; i < binding_data.size(); i++) {
|
|
if (!binding_data[i])
|
|
continue;
|
|
|
|
if (!binding_functions[i].first)
|
|
continue;
|
|
|
|
if (binding_functions[i].second.refcount_incremented_instance_binding) {
|
|
binding_functions[i].second.refcount_incremented_instance_binding(binding_data[i], p_object);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool NativeScriptLanguage::refcount_decremented_instance_binding(Object *p_object) {
|
|
|
|
void *data = p_object->get_script_instance_binding(lang_idx);
|
|
|
|
if (!data)
|
|
return true;
|
|
|
|
Vector<void *> &binding_data = *(Vector<void *> *)data;
|
|
|
|
bool can_die = true;
|
|
|
|
for (int i = 0; i < binding_data.size(); i++) {
|
|
if (!binding_data[i])
|
|
continue;
|
|
|
|
if (!binding_functions[i].first)
|
|
continue;
|
|
|
|
if (binding_functions[i].second.refcount_decremented_instance_binding) {
|
|
can_die = can_die && binding_functions[i].second.refcount_decremented_instance_binding(binding_data[i], p_object);
|
|
}
|
|
}
|
|
|
|
return can_die;
|
|
}
|
|
|
|
void NativeScriptLanguage::set_global_type_tag(int p_idx, StringName p_class_name, const void *p_type_tag) {
|
|
if (!global_type_tags.has(p_idx)) {
|
|
global_type_tags.insert(p_idx, HashMap<StringName, const void *>());
|
|
}
|
|
|
|
HashMap<StringName, const void *> &tags = global_type_tags[p_idx];
|
|
|
|
tags.set(p_class_name, p_type_tag);
|
|
}
|
|
|
|
const void *NativeScriptLanguage::get_global_type_tag(int p_idx, StringName p_class_name) const {
|
|
if (!global_type_tags.has(p_idx))
|
|
return NULL;
|
|
|
|
const HashMap<StringName, const void *> &tags = global_type_tags[p_idx];
|
|
|
|
const void *tag = tags.get(p_class_name);
|
|
|
|
return tag;
|
|
}
|
|
|
|
#ifndef NO_THREADS
|
|
void NativeScriptLanguage::defer_init_library(Ref<GDNativeLibrary> lib, NativeScript *script) {
|
|
MutexLock lock(mutex);
|
|
libs_to_init.insert(lib);
|
|
scripts_to_register.insert(script);
|
|
has_objects_to_register = true;
|
|
}
|
|
#endif
|
|
|
|
void NativeScriptLanguage::init_library(const Ref<GDNativeLibrary> &lib) {
|
|
#ifndef NO_THREADS
|
|
MutexLock lock(mutex);
|
|
#endif
|
|
// See if this library was "registered" already.
|
|
const String &lib_path = lib->get_current_library_path();
|
|
ERR_EXPLAIN(lib->get_name() + " does not have a library for the current platform");
|
|
ERR_FAIL_COND(lib_path.length() == 0);
|
|
Map<String, Ref<GDNative> >::Element *E = library_gdnatives.find(lib_path);
|
|
|
|
if (!E) {
|
|
Ref<GDNative> gdn;
|
|
gdn.instance();
|
|
gdn->set_library(lib);
|
|
|
|
// TODO check the return value?
|
|
gdn->initialize();
|
|
|
|
library_gdnatives.insert(lib_path, gdn);
|
|
|
|
library_classes.insert(lib_path, Map<StringName, NativeScriptDesc>());
|
|
|
|
if (!library_script_users.has(lib_path))
|
|
library_script_users.insert(lib_path, Set<NativeScript *>());
|
|
|
|
void *proc_ptr;
|
|
|
|
Error err = gdn->get_symbol(lib->get_symbol_prefix() + _init_call_name, proc_ptr);
|
|
|
|
if (err != OK) {
|
|
ERR_PRINT(String("No " + _init_call_name + " in \"" + lib_path + "\" found").utf8().get_data());
|
|
} else {
|
|
((void (*)(godot_string *))proc_ptr)((godot_string *)&lib_path);
|
|
}
|
|
} else {
|
|
// already initialized. Nice.
|
|
}
|
|
}
|
|
|
|
void NativeScriptLanguage::register_script(NativeScript *script) {
|
|
#ifndef NO_THREADS
|
|
MutexLock lock(mutex);
|
|
#endif
|
|
library_script_users[script->lib_path].insert(script);
|
|
}
|
|
|
|
void NativeScriptLanguage::unregister_script(NativeScript *script) {
|
|
#ifndef NO_THREADS
|
|
MutexLock lock(mutex);
|
|
#endif
|
|
Map<String, Set<NativeScript *> >::Element *S = library_script_users.find(script->lib_path);
|
|
if (S) {
|
|
S->get().erase(script);
|
|
if (S->get().size() == 0) {
|
|
library_script_users.erase(S);
|
|
}
|
|
}
|
|
#ifndef NO_THREADS
|
|
scripts_to_register.erase(script);
|
|
#endif
|
|
}
|
|
|
|
void NativeScriptLanguage::call_libraries_cb(const StringName &name) {
|
|
// library_gdnatives is modified only from the main thread, so it's safe not to use mutex here
|
|
for (Map<String, Ref<GDNative> >::Element *L = library_gdnatives.front(); L; L = L->next()) {
|
|
|
|
if (L->get().is_null()) {
|
|
continue;
|
|
}
|
|
|
|
if (L->get()->is_initialized()) {
|
|
|
|
void *proc_ptr;
|
|
Error err = L->get()->get_symbol(L->get()->get_library()->get_symbol_prefix() + name, proc_ptr);
|
|
|
|
if (!err) {
|
|
((void (*)())proc_ptr)();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void NativeScriptLanguage::frame() {
|
|
#ifndef NO_THREADS
|
|
if (has_objects_to_register) {
|
|
MutexLock lock(mutex);
|
|
for (Set<Ref<GDNativeLibrary> >::Element *L = libs_to_init.front(); L; L = L->next()) {
|
|
init_library(L->get());
|
|
}
|
|
libs_to_init.clear();
|
|
for (Set<NativeScript *>::Element *S = scripts_to_register.front(); S; S = S->next()) {
|
|
register_script(S->get());
|
|
}
|
|
scripts_to_register.clear();
|
|
has_objects_to_register = false;
|
|
}
|
|
#endif
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
{
|
|
#ifndef NO_THREADS
|
|
MutexLock lock(mutex);
|
|
#endif
|
|
|
|
for (Map<StringName, ProfileData>::Element *d = profile_data.front(); d; d = d->next()) {
|
|
d->get().last_frame_call_count = d->get().frame_call_count;
|
|
d->get().last_frame_self_time = d->get().frame_self_time;
|
|
d->get().last_frame_total_time = d->get().frame_total_time;
|
|
d->get().frame_call_count = 0;
|
|
d->get().frame_self_time = 0;
|
|
d->get().frame_total_time = 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
call_libraries_cb(_frame_call_name);
|
|
}
|
|
|
|
#ifndef NO_THREADS
|
|
|
|
void NativeScriptLanguage::thread_enter() {
|
|
call_libraries_cb(_thread_enter_call_name);
|
|
}
|
|
|
|
void NativeScriptLanguage::thread_exit() {
|
|
call_libraries_cb(_thread_exit_call_name);
|
|
}
|
|
|
|
#endif // NO_THREADS
|
|
|
|
bool NativeScriptLanguage::handles_global_class_type(const String &p_type) const {
|
|
return p_type == "NativeScript";
|
|
}
|
|
|
|
String NativeScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const {
|
|
Ref<NativeScript> script = ResourceLoader::load(p_path, "NativeScript");
|
|
if (script.is_valid()) {
|
|
if (r_base_type)
|
|
*r_base_type = script->get_instance_base_type();
|
|
if (r_icon_path)
|
|
*r_icon_path = script->get_script_class_icon_path();
|
|
return script->get_script_class_name();
|
|
}
|
|
if (r_base_type)
|
|
*r_base_type = String();
|
|
if (r_icon_path)
|
|
*r_icon_path = String();
|
|
return String();
|
|
}
|
|
|
|
void NativeReloadNode::_bind_methods() {
|
|
ClassDB::bind_method(D_METHOD("_notification"), &NativeReloadNode::_notification);
|
|
}
|
|
|
|
void NativeReloadNode::_notification(int p_what) {
|
|
#ifdef TOOLS_ENABLED
|
|
|
|
switch (p_what) {
|
|
case MainLoop::NOTIFICATION_WM_FOCUS_OUT: {
|
|
|
|
if (unloaded)
|
|
break;
|
|
#ifndef NO_THREADS
|
|
MutexLock lock(NSL->mutex);
|
|
#endif
|
|
NSL->_unload_stuff(true);
|
|
|
|
for (Map<String, Ref<GDNative> >::Element *L = NSL->library_gdnatives.front(); L; L = L->next()) {
|
|
|
|
Ref<GDNative> gdn = L->get();
|
|
|
|
if (gdn.is_null()) {
|
|
continue;
|
|
}
|
|
|
|
if (!gdn->get_library()->is_reloadable()) {
|
|
continue;
|
|
}
|
|
|
|
gdn->terminate();
|
|
}
|
|
|
|
unloaded = true;
|
|
|
|
} break;
|
|
|
|
case MainLoop::NOTIFICATION_WM_FOCUS_IN: {
|
|
|
|
if (!unloaded)
|
|
break;
|
|
#ifndef NO_THREADS
|
|
MutexLock lock(NSL->mutex);
|
|
#endif
|
|
Set<StringName> libs_to_remove;
|
|
for (Map<String, Ref<GDNative> >::Element *L = NSL->library_gdnatives.front(); L; L = L->next()) {
|
|
|
|
Ref<GDNative> gdn = L->get();
|
|
|
|
if (gdn.is_null()) {
|
|
continue;
|
|
}
|
|
|
|
if (!gdn->get_library()->is_reloadable()) {
|
|
continue;
|
|
}
|
|
|
|
if (!gdn->initialize()) {
|
|
libs_to_remove.insert(L->key());
|
|
continue;
|
|
}
|
|
|
|
NSL->library_classes.insert(L->key(), Map<StringName, NativeScriptDesc>());
|
|
|
|
// here the library registers all the classes and stuff.
|
|
|
|
void *proc_ptr;
|
|
Error err = gdn->get_symbol(gdn->get_library()->get_symbol_prefix() + "nativescript_init", proc_ptr);
|
|
if (err != OK) {
|
|
ERR_PRINT(String("No godot_nativescript_init in \"" + L->key() + "\" found").utf8().get_data());
|
|
} else {
|
|
((void (*)(void *))proc_ptr)((void *)&L->key());
|
|
}
|
|
|
|
for (Map<String, Set<NativeScript *> >::Element *U = NSL->library_script_users.front(); U; U = U->next()) {
|
|
for (Set<NativeScript *>::Element *S = U->get().front(); S; S = S->next()) {
|
|
NativeScript *script = S->get();
|
|
|
|
if (script->placeholders.size() == 0)
|
|
continue;
|
|
|
|
for (Set<PlaceHolderScriptInstance *>::Element *P = script->placeholders.front(); P; P = P->next()) {
|
|
script->_update_placeholder(P->get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
unloaded = false;
|
|
|
|
for (Set<StringName>::Element *R = libs_to_remove.front(); R; R = R->next()) {
|
|
NSL->library_gdnatives.erase(R->get());
|
|
}
|
|
|
|
} break;
|
|
default: {
|
|
};
|
|
}
|
|
#endif
|
|
}
|
|
|
|
RES ResourceFormatLoaderNativeScript::load(const String &p_path, const String &p_original_path, Error *r_error) {
|
|
return ResourceFormatLoaderText::singleton->load(p_path, p_original_path, r_error);
|
|
}
|
|
|
|
void ResourceFormatLoaderNativeScript::get_recognized_extensions(List<String> *p_extensions) const {
|
|
p_extensions->push_back("gdns");
|
|
}
|
|
|
|
bool ResourceFormatLoaderNativeScript::handles_type(const String &p_type) const {
|
|
return (p_type == "Script" || p_type == "NativeScript");
|
|
}
|
|
|
|
String ResourceFormatLoaderNativeScript::get_resource_type(const String &p_path) const {
|
|
String el = p_path.get_extension().to_lower();
|
|
if (el == "gdns")
|
|
return "NativeScript";
|
|
return "";
|
|
}
|
|
|
|
Error ResourceFormatSaverNativeScript::save(const String &p_path, const RES &p_resource, uint32_t p_flags) {
|
|
ResourceFormatSaverText rfst;
|
|
return rfst.save(p_path, p_resource, p_flags);
|
|
}
|
|
|
|
bool ResourceFormatSaverNativeScript::recognize(const RES &p_resource) const {
|
|
return Object::cast_to<NativeScript>(*p_resource) != NULL;
|
|
}
|
|
|
|
void ResourceFormatSaverNativeScript::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const {
|
|
if (Object::cast_to<NativeScript>(*p_resource)) {
|
|
p_extensions->push_back("gdns");
|
|
}
|
|
}
|