diff options
92 files changed, 19622 insertions, 0 deletions
diff --git a/modules/mono/SCsub b/modules/mono/SCsub new file mode 100644 index 0000000000..0af2056c5c --- /dev/null +++ b/modules/mono/SCsub @@ -0,0 +1,120 @@ +#!/usr/bin/env python + +Import('env') + + +def make_cs_files_header(src, dst): + with open(dst, 'wb') as header: + header.write('/* This is an automatically generated file; DO NOT EDIT! OK THX */\n') + header.write('#ifndef _CS_FILES_DATA_H\n') + header.write('#define _CS_FILES_DATA_H\n\n') + header.write('#include "map.h"\n') + header.write('#include "ustring.h"\n') + inserted_files = '' + import os + for file in os.listdir(src): + if file.endswith('.cs'): + with open(os.path.join(src, file), 'rb') as f: + buf = f.read() + decomp_size = len(buf) + import zlib + buf = zlib.compress(buf) + name = os.path.splitext(file)[0] + header.write('\nstatic const int _cs_' + name + '_compressed_size = ' + str(len(buf)) + ';\n') + header.write('static const int _cs_' + name + '_uncompressed_size = ' + str(decomp_size) + ';\n') + header.write('static const unsigned char _cs_' + name + '_compressed[] = { ') + for i, buf_idx in enumerate(range(len(buf))): + if i > 0: + header.write(', ') + header.write(str(ord(buf[buf_idx]))) + inserted_files += '\tr_files.insert(\"' + file + '\", ' \ + 'CompressedFile(_cs_' + name + '_compressed_size, ' \ + '_cs_' + name + '_uncompressed_size, ' \ + '_cs_' + name + '_compressed));\n' + header.write(' };\n') + header.write('\nstruct CompressedFile\n' '{\n' + '\tint compressed_size;\n' '\tint uncompressed_size;\n' '\tconst unsigned char* data;\n' + '\n\tCompressedFile(int p_comp_size, int p_uncomp_size, const unsigned char* p_data)\n' + '\t{\n' '\t\tcompressed_size = p_comp_size;\n' '\t\tuncompressed_size = p_uncomp_size;\n' + '\t\tdata = p_data;\n' '\t}\n' '\n\tCompressedFile() {}\n' '};\n' + '\nvoid get_compressed_files(Map<String, CompressedFile>& r_files)\n' '{\n' + inserted_files + '}\n' + ) + header.write('#endif // _CS_FILES_DATA_H') + + +env.add_source_files(env.modules_sources, '*.cpp') +env.add_source_files(env.modules_sources, 'mono_gd/*.cpp') +env.add_source_files(env.modules_sources, 'utils/*.cpp') + +if env['tools']: + env.add_source_files(env.modules_sources, 'editor/*.cpp') + make_cs_files_header('glue/cs_files', 'glue/cs_compressed.gen.h') + +vars = Variables() +vars.Add(BoolVariable('mono_glue', 'Build with the mono glue sources', True)) +vars.Update(env) + +# Glue sources +if env['mono_glue']: + env.add_source_files(env.modules_sources, 'glue/*.cpp') +else: + env.Append(CPPDEFINES = [ 'MONO_GLUE_DISABLED' ]) + +if ARGUMENTS.get('yolo_copy', False): + env.Append(CPPDEFINES = [ 'YOLO_COPY' ]) + +# Build GodotSharpTools solution + +import os +import subprocess +import mono_reg_utils as monoreg + + +def mono_build_solution(source, target, env): + if os.name == 'nt': + msbuild_tools_path = monoreg.find_msbuild_tools_path_reg() + if not msbuild_tools_path: + raise RuntimeError('Cannot find MSBuild Tools Path in the registry') + msbuild_path = os.path.join(msbuild_tools_path, 'MSBuild.exe') + else: + msbuild_path = 'msbuild' + + output_path = os.path.abspath(os.path.join(str(target[0]), os.pardir)) + + msbuild_args = [ + msbuild_path, + os.path.abspath(str(source[0])), + '/p:Configuration=Release', + '/p:OutputPath=' + output_path + ] + + msbuild_env = os.environ.copy() + + # Needed when running from Developer Command Prompt for VS + if 'PLATFORM' in msbuild_env: + del msbuild_env['PLATFORM'] + + msbuild_alt_paths = [ 'xbuild' ] + + while True: + try: + subprocess.check_call(msbuild_args, env = msbuild_env) + break + except subprocess.CalledProcessError: + raise RuntimeError('GodotSharpTools build failed') + except OSError: + if os.name != 'nt': + if not msbuild_alt_paths: + raise RuntimeError('Could not find commands msbuild or xbuild') + # Try xbuild + msbuild_args[0] = msbuild_alt_paths.pop(0) + else: + raise RuntimeError('Could not find command MSBuild.exe') + + +mono_sln_builder = Builder(action = mono_build_solution) +env.Append(BUILDERS = { 'MonoBuildSolution' : mono_sln_builder }) +env.MonoBuildSolution( + os.path.join(Dir('#bin').abspath, 'GodotSharpTools.dll'), + 'editor/GodotSharpTools/GodotSharpTools.sln' +) diff --git a/modules/mono/config.py b/modules/mono/config.py new file mode 100644 index 0000000000..9de199bb5a --- /dev/null +++ b/modules/mono/config.py @@ -0,0 +1,143 @@ + +import imp +import os +import sys +from shutil import copyfile + +from SCons.Script import BoolVariable, Environment, Variables + + +monoreg = imp.load_source('mono_reg_utils', 'modules/mono/mono_reg_utils.py') + + +def find_file_in_dir(directory, files, prefix='', extension=''): + if not extension.startswith('.'): + extension = '.' + extension + for curfile in files: + if os.path.isfile(os.path.join(directory, prefix + curfile + extension)): + return curfile + + return None + + +def can_build(platform): + if platform in ["javascript"]: + return False # Not yet supported + return True + + +def is_enabled(): + # The module is disabled by default. Use module_mono_enabled=yes to enable it. + return False + + +def configure(env): + env.use_ptrcall = True + + envvars = Variables() + envvars.Add(BoolVariable('mono_static', 'Statically link mono', False)) + envvars.Update(env) + + mono_static = env['mono_static'] + + mono_lib_names = ['mono-2.0-sgen', 'monosgen-2.0'] + + if env['platform'] == 'windows': + if mono_static: + raise RuntimeError('mono-static: Not supported on Windows') + + if env['bits'] == '32': + if os.getenv('MONO32_PREFIX'): + mono_root = os.getenv('MONO32_PREFIX') + elif os.name == 'nt': + mono_root = monoreg.find_mono_root_dir() + else: + if os.getenv('MONO64_PREFIX'): + mono_root = os.getenv('MONO64_PREFIX') + elif os.name == 'nt': + mono_root = monoreg.find_mono_root_dir() + + if mono_root is None: + raise RuntimeError('Mono installation directory not found') + + mono_lib_path = os.path.join(mono_root, 'lib') + + env.Append(LIBPATH=mono_lib_path) + env.Append(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0')) + + mono_lib_name = find_file_in_dir(mono_lib_path, mono_lib_names, extension='.lib') + + if mono_lib_name is None: + raise RuntimeError('Could not find mono library in: ' + mono_lib_path) + + if os.getenv('VCINSTALLDIR'): + env.Append(LINKFLAGS=mono_lib_name + Environment()['LIBSUFFIX']) + else: + env.Append(LIBS=mono_lib_name) + + mono_bin_path = os.path.join(mono_root, 'bin') + + mono_dll_name = find_file_in_dir(mono_bin_path, mono_lib_names, extension='.dll') + + mono_dll_src = os.path.join(mono_bin_path, mono_dll_name + '.dll') + mono_dll_dst = os.path.join('bin', mono_dll_name + '.dll') + copy_mono_dll = True + + if not os.path.isdir('bin'): + os.mkdir('bin') + elif os.path.exists(mono_dll_dst): + copy_mono_dll = False + + if copy_mono_dll: + copyfile(mono_dll_src, mono_dll_dst) + else: + mono_root = None + + if env['bits'] == '32': + if os.getenv('MONO32_PREFIX'): + mono_root = os.getenv('MONO32_PREFIX') + else: + if os.getenv('MONO64_PREFIX'): + mono_root = os.getenv('MONO64_PREFIX') + + if mono_root is not None: + mono_lib_path = os.path.join(mono_root, 'lib') + + env.Append(LIBPATH=mono_lib_path) + env.Append(CPPPATH=os.path.join(mono_root, 'include', 'mono-2.0')) + + mono_lib = find_file_in_dir(mono_lib_path, mono_lib_names, prefix='lib', extension='.a') + + if mono_lib is None: + raise RuntimeError('Could not find mono library in: ' + mono_lib_path) + + env.Append(CPPFLAGS=['-D_REENTRANT']) + + if mono_static: + mono_lib_file = os.path.join(mono_lib_path, 'lib' + mono_lib + '.a') + + if sys.platform == "darwin": + env.Append(LINKFLAGS=['-Wl,-force_load,' + mono_lib_file]) + elif sys.platform == "linux" or sys.platform == "linux2": + env.Append(LINKFLAGS=['-Wl,-whole-archive', mono_lib_file, '-Wl,-no-whole-archive']) + else: + raise RuntimeError('mono-static: Not supported on this platform') + else: + env.Append(LIBS=[mono_lib]) + + env.Append(LIBS=['m', 'rt', 'dl', 'pthread']) + else: + if mono_static: + raise RuntimeError('mono-static: Not supported with pkg-config. Specify a mono prefix manually') + + env.ParseConfig('pkg-config mono-2 --cflags --libs') + + env.Append(LINKFLAGS='-rdynamic') + + +def get_doc_classes(): + return ["@C#", "CSharpScript", "GodotSharp"] + + +def get_doc_path(): + return "doc_classes" diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp new file mode 100644 index 0000000000..67b4e67e2b --- /dev/null +++ b/modules/mono/csharp_script.cpp @@ -0,0 +1,1853 @@ +/*************************************************************************/ +/* csharp_script.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "csharp_script.h" + +#include <mono/metadata/threads.h> + +#include "os/file_access.h" +#include "os/os.h" +#include "os/thread.h" +#include "project_settings.h" + +#ifdef TOOLS_ENABLED +#include "editor/bindings_generator.h" +#include "editor/csharp_project.h" +#include "editor/editor_node.h" +#include "editor/godotsharp_editor.h" +#endif + +#include "godotsharp_dirs.h" +#include "mono_gd/gd_mono_class.h" +#include "mono_gd/gd_mono_marshal.h" +#include "signal_awaiter_utils.h" + +#define CACHED_STRING_NAME(m_var) (CSharpLanguage::get_singleton()->string_names.m_var) + +CSharpLanguage *CSharpLanguage::singleton = NULL; + +String CSharpLanguage::get_name() const { + + return "C#"; +} + +String CSharpLanguage::get_type() const { + + return "CSharpScript"; +} + +String CSharpLanguage::get_extension() const { + + return "cs"; +} + +Error CSharpLanguage::execute_file(const String &p_path) { + + // ?? + return OK; +} + +#ifdef TOOLS_ENABLED +void gdsharp_editor_init_callback() { + + EditorNode *editor = EditorNode::get_singleton(); + editor->add_child(memnew(GodotSharpEditor(editor))); +} +#endif + +void CSharpLanguage::init() { + + gdmono = memnew(GDMono); + gdmono->initialize(); + +#ifdef MONO_GLUE_DISABLED + WARN_PRINT("This binary is built with `mono_glue=no` and cannot be used for scripting"); +#endif + +#if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED) + if (gdmono->get_editor_tools_assembly() != NULL) { + List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); + BindingsGenerator::handle_cmdline_args(cmdline_args); + } +#endif + +#ifdef TOOLS_ENABLED + EditorNode::add_init_callback(&gdsharp_editor_init_callback); +#endif +} + +void CSharpLanguage::finish() { + + if (gdmono) { + memdelete(gdmono); + gdmono = NULL; + } +} + +void CSharpLanguage::get_reserved_words(List<String> *p_words) const { + + static const char *_reserved_words[] = { + // Reserved keywords + "abstract", + "as", + "base", + "bool", + "break", + "byte", + "case", + "catch", + "char", + "checked", + "class", + "const", + "continue", + "decimal", + "default", + "delegate", + "do", + "double", + "else", + "enum", + "event", + "explicit", + "extern", + "false", + "finally", + "fixed", + "float", + "for", + "forech", + "goto", + "if", + "implicit", + "in", + "int", + "interface", + "internal", + "is", + "lock", + "long", + "namespace", + "new", + "null", + "object", + "operator", + "out", + "override", + "params", + "private", + "protected", + "public", + "readonly", + "ref", + "return", + "sbyte", + "sealed", + "short", + "sizeof", + "stackalloc", + "static", + "string", + "struct", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "uint", + "ulong", + "unchecked", + "unsafe", + "ushort", + "using", + "virtual", + "volatile", + "void", + "while", + + // Contextual keywords. Not reserved words, but I guess we should include + // them because this seems to be used only for syntax highlighting. + "add", + "ascending", + "by", + "descending", + "dynamic", + "equals", + "from", + "get", + "global", + "group", + "in", + "into", + "join", + "let", + "on", + "orderby", + "partial", + "remove", + "select", + "set", + "value", + "var", + "where", + "yield", + 0 + }; + + const char **w = _reserved_words; + + while (*w) { + p_words->push_back(*w); + w++; + } +} + +void CSharpLanguage::get_comment_delimiters(List<String> *p_delimiters) const { + + p_delimiters->push_back("//"); // single-line comment + p_delimiters->push_back("/* */"); // delimited comment +} + +void CSharpLanguage::get_string_delimiters(List<String> *p_delimiters) const { + + p_delimiters->push_back("' '"); // character literal + p_delimiters->push_back("\" \""); // regular string literal + p_delimiters->push_back("@\" \""); // verbatim string literal +} + +Ref<Script> CSharpLanguage::get_template(const String &p_class_name, const String &p_base_class_name) const { + + String script_template = "using " BINDINGS_NAMESPACE ";\n" + "using System;\n" + "\n" + "public class %CLASS_NAME% : %BASE_CLASS_NAME%\n" + "{\n" + " // Member variables here, example:\n" + " // private int a = 2;\n" + " // private string b = \"textvar\";\n" + "\n" + " public override void _Ready()\n" + " {\n" + " // Called every time the node is added to the scene.\n" + " // Initialization here\n" + " \n" + " }\n" + "}\n"; + + script_template = script_template.replace("%BASE_CLASS_NAME%", p_base_class_name).replace("%CLASS_NAME%", p_class_name); + + Ref<CSharpScript> script; + script.instance(); + script->set_source_code(script_template); + + return script; +} + +Script *CSharpLanguage::create_script() const { + + return memnew(CSharpScript); +} + +bool CSharpLanguage::has_named_classes() const { + + return true; +} + +String CSharpLanguage::make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const { + + // FIXME + // Due to Godot's API limitation this just appends the function to the end of the file + // Another limitation is that the parameter types are not specified, so we must use System.Object + String s = "private void " + p_name + "("; + for (int i = 0; i < p_args.size(); i++) { + if (i > 0) + s += ", "; + s += "object " + p_args[i]; + } + s += ")\n{\n // Replace with function body\n}\n"; + + return s; +} + +void CSharpLanguage::frame() { + + const Ref<MonoGCHandle> &task_scheduler_handle = GDMonoUtils::mono_cache.task_scheduler_handle; + + if (task_scheduler_handle.is_valid()) { + MonoObject *task_scheduler = task_scheduler_handle->get_target(); + + if (task_scheduler) { + GDMonoUtils::GodotTaskScheduler_Activate thunk = CACHED_METHOD_THUNK(GodotTaskScheduler, Activate); + + ERR_FAIL_NULL(thunk); + + MonoObject *ex; + thunk(task_scheduler, &ex); + + if (ex) { + mono_print_unhandled_exception(ex); + ERR_FAIL(); + } + } + } +} + +struct CSharpScriptDepSort { + + // must support sorting so inheritance works properly (parent must be reloaded first) + bool operator()(const Ref<CSharpScript> &A, const Ref<CSharpScript> &B) const { + if (A == B) + return false; // shouldn't happen but.. + GDMonoClass *I = B->base; + while (I) { + if (I == A->script_class) { + // A is a base of B + return true; + } + + I = I->get_parent_class(); + } + + return false; // not a base + } +}; + +void CSharpLanguage::reload_all_scripts() { + +#ifdef DEBUG_ENABLED + +#ifndef NO_THREADS + lock->lock(); +#endif + + List<Ref<CSharpScript> > scripts; + + SelfList<CSharpScript> *elem = script_list.first(); + while (elem) { + if (elem->self()->get_path().is_resource_file()) { + scripts.push_back(Ref<CSharpScript>(elem->self())); //cast to gdscript to avoid being erased by accident + } + elem = elem->next(); + } + +#ifndef NO_THREADS + lock->unlock(); +#endif + + //as scripts are going to be reloaded, must proceed without locking here + + scripts.sort_custom<CSharpScriptDepSort>(); //update in inheritance dependency order + + for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) { + E->get()->load_source_code(E->get()->get_path()); + E->get()->reload(true); + } +#endif +} + +void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload) { + + (void)p_script; // UNUSED + +#ifdef TOOLS_ENABLED + reload_assemblies_if_needed(p_soft_reload); +#endif +} + +#ifdef TOOLS_ENABLED +void CSharpLanguage::reload_assemblies_if_needed(bool p_soft_reload) { + + if (gdmono->is_runtime_initialized()) { + + GDMonoAssembly *proj_assembly = gdmono->get_project_assembly(); + + if (proj_assembly) { + String proj_asm_path = proj_assembly->get_path(); + + if (!FileAccess::exists(proj_assembly->get_path())) { + // Maybe it wasn't loaded from the default path, so check this as well + String proj_asm_name = ProjectSettings::get_singleton()->get("application/config/name"); + proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(proj_asm_name); + if (!FileAccess::exists(proj_asm_path)) + return; // No assembly to load + } + + if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time()) + return; // Already up to date + } else { + String proj_asm_name = ProjectSettings::get_singleton()->get("application/config/name"); + if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(proj_asm_name))) + return; // No assembly to load + } + } + +#ifndef NO_THREADS + lock->lock(); +#endif + + List<Ref<CSharpScript> > scripts; + + SelfList<CSharpScript> *elem = script_list.first(); + while (elem) { + if (elem->self()->get_path().is_resource_file()) { + + scripts.push_back(Ref<CSharpScript>(elem->self())); //cast to CSharpScript to avoid being erased by accident + } + elem = elem->next(); + } + +#ifndef NO_THREADS + lock->unlock(); +#endif + + //when someone asks you why dynamically typed languages are easier to write.... + + Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > > to_reload; + + //as scripts are going to be reloaded, must proceed without locking here + + scripts.sort_custom<CSharpScriptDepSort>(); //update in inheritance dependency order + + for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) { + + to_reload.insert(E->get(), Map<ObjectID, List<Pair<StringName, Variant> > >()); + + if (!p_soft_reload) { + + //save state and remove script from instances + Map<ObjectID, List<Pair<StringName, Variant> > > &map = to_reload[E->get()]; + + while (E->get()->instances.front()) { + Object *obj = E->get()->instances.front()->get(); + //save instance info + List<Pair<StringName, Variant> > state; + if (obj->get_script_instance()) { + + obj->get_script_instance()->get_property_state(state); + + Ref<MonoGCHandle> gchandle = CAST_CSHARP_INSTANCE(obj->get_script_instance())->gchandle; + if (gchandle.is_valid()) + gchandle->release(); + + map[obj->get_instance_id()] = state; + obj->set_script(RefPtr()); + } + } + + //same thing for placeholders + while (E->get()->placeholders.size()) { + + Object *obj = E->get()->placeholders.front()->get()->get_owner(); + //save instance info + List<Pair<StringName, Variant> > state; + if (obj->get_script_instance()) { + obj->get_script_instance()->get_property_state(state); + map[obj->get_instance_id()] = state; + obj->set_script(RefPtr()); + } + } + + for (Map<ObjectID, List<Pair<StringName, Variant> > >::Element *F = E->get()->pending_reload_state.front(); F; F = F->next()) { + map[F->key()] = F->get(); //pending to reload, use this one instead + } + + E->get()->_clear(); + } + } + + if (gdmono->reload_scripts_domain() != OK) + return; + + for (Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > >::Element *E = to_reload.front(); E; E = E->next()) { + + Ref<CSharpScript> scr = E->key(); + scr->exports_invalidated = true; + scr->reload(p_soft_reload); + scr->update_exports(); + + //restore state if saved + for (Map<ObjectID, List<Pair<StringName, Variant> > >::Element *F = E->get().front(); F; F = F->next()) { + + Object *obj = ObjectDB::get_instance(F->key()); + if (!obj) + continue; + + if (!p_soft_reload) { + //clear it just in case (may be a pending reload state) + obj->set_script(RefPtr()); + } + obj->set_script(scr.get_ref_ptr()); + if (!obj->get_script_instance()) { + //failed, save reload state for next time if not saved + if (!scr->pending_reload_state.has(obj->get_instance_id())) { + scr->pending_reload_state[obj->get_instance_id()] = F->get(); + } + continue; + } + + for (List<Pair<StringName, Variant> >::Element *G = F->get().front(); G; G = G->next()) { + obj->get_script_instance()->set(G->get().first, G->get().second); + } + + scr->pending_reload_state.erase(obj->get_instance_id()); //as it reloaded, remove pending state + } + + //if instance states were saved, set them! + } +} +#endif + +void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const { + + p_extensions->push_back("cs"); +} + +#ifdef TOOLS_ENABLED +Error CSharpLanguage::open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) { + + return GodotSharpEditor::get_singleton()->open_in_external_editor(p_script, p_line, p_col); +} + +bool CSharpLanguage::overrides_external_editor() { + + return GodotSharpEditor::get_singleton()->overrides_external_editor(); +} +#endif + +void CSharpLanguage::thread_enter() { + +#if 0 + if (mono->is_runtime_initialized()) { + GDMonoUtils::attach_current_thread(); + } +#endif +} + +void CSharpLanguage::thread_exit() { + +#if 0 + if (mono->is_runtime_initialized()) { + GDMonoUtils::detach_current_thread(); + } +#endif +} + +bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) { + + // Break because of parse error + if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) { + // TODO + //_debug_parse_err_line = p_line; + //_debug_parse_err_file = p_file; + //_debug_error = p_error; + ScriptDebugger::get_singleton()->debug(this, false); + return true; + } else { + return false; + } +} + +bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) { + + if (ScriptDebugger::get_singleton() && Thread::get_caller_id() == Thread::get_main_id()) { + // TODO + //_debug_parse_err_line = -1; + //_debug_parse_err_file = ""; + //_debug_error = p_error; + ScriptDebugger::get_singleton()->debug(this, p_allow_continue); + return true; + } else { + return false; + } +} + +void CSharpLanguage::set_language_index(int p_idx) { + + ERR_FAIL_COND(lang_idx != -1); + lang_idx = p_idx; +} + +CSharpLanguage::CSharpLanguage() { + + ERR_FAIL_COND(singleton); + singleton = this; + + gdmono = NULL; + +#ifdef NO_THREADS + lock = NULL; + gchandle_bind_lock = NULL; +#else + lock = Mutex::create(); + script_bind_lock = Mutex::create(); +#endif + + lang_idx = -1; +} + +CSharpLanguage::~CSharpLanguage() { + + finish(); + + if (lock) { + memdelete(lock); + lock = NULL; + } + + if (script_bind_lock) { + memdelete(script_bind_lock); + script_bind_lock = NULL; + } + + singleton = NULL; +} + +void *CSharpLanguage::alloc_instance_binding_data(Object *p_object) { + +#ifdef DEBUG_ENABLED + // I don't trust you + if (p_object->get_script_instance()) + CRASH_COND(NULL != CAST_CSHARP_INSTANCE(p_object->get_script_instance())); +#endif + + StringName type_name = p_object->get_class_name(); + + GDMonoClass *type_class = GDMonoUtils::type_get_proxy_class(type_name); + + ERR_FAIL_NULL_V(type_class, NULL); + + MonoObject *mono_object = GDMonoUtils::create_managed_for_godot_object(type_class, type_name, p_object); + + ERR_FAIL_NULL_V(mono_object, NULL); + + // Tie managed to unmanaged + bool strong_handle = true; + Reference *ref = Object::cast_to<Reference>(p_object); + + if (ref) { + strong_handle = false; + + // Unsafe refcount increment. The managed instance also counts as a reference. + // This way if the unmanaged world has no references to our owner + // but the managed instance is alive, the refcount will be 1 instead of 0. + // See: _GodotSharp::_dispose_object(Object *p_object) + + ref->reference(); + } + + Ref<MonoGCHandle> gchandle = strong_handle ? MonoGCHandle::create_strong(mono_object) : + MonoGCHandle::create_weak(mono_object); + +#ifndef NO_THREADS + script_bind_lock->lock(); +#endif + + void *data = (void *)gchandle_bindings.insert(p_object, gchandle); + +#ifndef NO_THREADS + script_bind_lock->unlock(); +#endif + + return data; +} + +void CSharpLanguage::free_instance_binding_data(void *p_data) { + +#ifndef NO_THREADS + script_bind_lock->lock(); +#endif + + gchandle_bindings.erase((Map<Object *, Ref<MonoGCHandle> >::Element *)p_data); + +#ifndef NO_THREADS + script_bind_lock->unlock(); +#endif +} + +void CSharpInstance::_ml_call_reversed(GDMonoClass *klass, const StringName &p_method, const Variant **p_args, int p_argcount) { + + GDMonoClass *base = klass->get_parent_class(); + if (base && base != script->native) + _ml_call_reversed(base, p_method, p_args, p_argcount); + + GDMonoMethod *method = klass->get_method(p_method, p_argcount); + + if (method) { + method->invoke(get_mono_object(), p_args); + } +} + +CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const Ref<MonoGCHandle> &p_gchandle) { + + CSharpInstance *instance = memnew(CSharpInstance); + + Reference *ref = Object::cast_to<Reference>(p_owner); + + instance->base_ref = ref != NULL; + instance->script = Ref<CSharpScript>(p_script); + instance->owner = p_owner; + instance->gchandle = p_gchandle; + + if (instance->base_ref) + instance->_reference_owner_unsafe(); + + p_script->instances.insert(p_owner); + + return instance; +} + +MonoObject *CSharpInstance::get_mono_object() const { +#ifdef DEBUG_ENABLED + CRASH_COND(gchandle.is_null()); +#endif + return gchandle->get_target(); +} + +bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) { + + ERR_FAIL_COND_V(!script.is_valid(), false); + + GDMonoClass *top = script->script_class; + + while (top && top != script->native) { + GDMonoField *field = script->script_class->get_field(p_name); + + if (field) { + MonoObject *mono_object = get_mono_object(); + + ERR_EXPLAIN("Reference has been garbage collected?"); + ERR_FAIL_NULL_V(mono_object, false); + + field->set_value(mono_object, p_value); + + return true; + } + + top = top->get_parent_class(); + } + + // Call _set + + Variant name = p_name; + const Variant *args[2] = { &name, &p_value }; + + MonoObject *mono_object = get_mono_object(); + top = script->script_class; + + while (top && top != script->native) { + GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_set), 2); + + if (method) { + MonoObject *ret = method->invoke(mono_object, args); + + if (ret && UNBOX_BOOLEAN(ret)) + return true; + } + + top = top->get_parent_class(); + } + + return false; +} + +bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { + + ERR_FAIL_COND_V(!script.is_valid(), false); + + GDMonoClass *top = script->script_class; + + while (top && top != script->native) { + GDMonoField *field = top->get_field(p_name); + + if (field) { + MonoObject *mono_object = get_mono_object(); + + ERR_EXPLAIN("Reference has been garbage collected?"); + ERR_FAIL_NULL_V(mono_object, false); + + MonoObject *value = field->get_value(mono_object); + r_ret = GDMonoMarshal::mono_object_to_variant(value, field->get_type()); + return true; + } + + // Call _get + + GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_get), 1); + + if (method) { + Variant name = p_name; + const Variant *args[1] = { &name }; + + MonoObject *ret = method->invoke(get_mono_object(), args); + + if (ret) { + r_ret = GDMonoMarshal::mono_object_to_variant(ret); + return true; + } + } + + top = top->get_parent_class(); + } + + return false; +} + +void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { + + for (Map<StringName, PropertyInfo>::Element *E = script->member_info.front(); E; E = E->next()) { + p_properties->push_back(E->value()); + } +} + +Variant::Type CSharpInstance::get_property_type(const StringName &p_name, bool *r_is_valid) const { + + if (script->member_info.has(p_name)) { + if (r_is_valid) + *r_is_valid = true; + return script->member_info[p_name].type; + } + + if (r_is_valid) + *r_is_valid = false; + + return Variant::NIL; +} + +bool CSharpInstance::has_method(const StringName &p_method) const { + + if (!script.is_valid()) + return false; + + GDMonoClass *top = script->script_class; + + while (top && top != script->native) { + if (top->has_method(p_method)) { + return true; + } + + top = top->get_parent_class(); + } + + return false; +} + +Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) { + + MonoObject *mono_object = get_mono_object(); + + ERR_EXPLAIN("Reference has been garbage collected?"); + ERR_FAIL_NULL_V(mono_object, Variant()); + + if (!script.is_valid()) + return Variant(); + + GDMonoClass *top = script->script_class; + + while (top && top != script->native) { + GDMonoMethod *method = top->get_method(p_method, p_argcount); + + if (method) { + MonoObject *return_value = method->invoke(mono_object, p_args); + + if (return_value) { + return GDMonoMarshal::mono_object_to_variant(return_value, method->get_return_type()); + } else { + return Variant(); + } + } else if (p_method == CACHED_STRING_NAME(_awaited_signal_callback)) { + // shitty hack.. + // TODO move to its own function, thx + + if (p_argcount < 1) { + r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; + r_error.argument = 1; + return Variant(); + } + + Ref<SignalAwaiterHandle> awaiter = *p_args[p_argcount - 1]; + + if (awaiter.is_null()) { + r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = p_argcount - 1; + r_error.expected = Variant::OBJECT; + return Variant(); + } + + awaiter->set_completed(true); + + int extra_argc = p_argcount - 1; + MonoArray *extra_args = mono_array_new(SCRIPTS_DOMAIN, CACHED_CLASS_RAW(MonoObject), extra_argc); + + for (int i = 0; i < extra_argc; i++) { + MonoObject *boxed = GDMonoMarshal::variant_to_mono_object(*p_args[i]); + mono_array_set(extra_args, MonoObject *, i, boxed); + } + + GDMonoUtils::GodotObject__AwaitedSignalCallback thunk = CACHED_METHOD_THUNK(GodotObject, _AwaitedSignalCallback); + + MonoObject *ex = NULL; + thunk(mono_object, &extra_args, awaiter->get_target(), &ex); + + if (ex) { + mono_print_unhandled_exception(ex); + ERR_FAIL_V(Variant()); + } + + return Variant(); + } + + top = top->get_parent_class(); + } + + r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD; + + return Variant(); +} + +void CSharpInstance::call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount) { + + if (script.is_valid()) { + MonoObject *mono_object = get_mono_object(); + + GDMonoClass *top = script->script_class; + + while (top && top != script->native) { + GDMonoMethod *method = top->get_method(p_method, p_argcount); + + if (method) + method->invoke(mono_object, p_args); + + top = top->get_parent_class(); + } + } +} + +void CSharpInstance::call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount) { + + if (script.is_valid()) { + _ml_call_reversed(script->script_class, p_method, p_args, p_argcount); + } +} + +void CSharpInstance::_reference_owner_unsafe() { + +#ifdef DEBUG_ENABLED + CRASH_COND(!base_ref); +#endif + + // Unsafe refcount increment. The managed instance also counts as a reference. + // This way if the unmanaged world has no references to our owner + // but the managed instance is alive, the refcount will be 1 instead of 0. + // See: _unreference_owner_unsafe() + + // May not me referenced yet, so we must use init_ref() instead of reference() + Object::cast_to<Reference>(owner)->init_ref(); +} + +void CSharpInstance::_unreference_owner_unsafe() { + +#ifdef DEBUG_ENABLED + CRASH_COND(!base_ref); +#endif + + // Called from CSharpInstance::mono_object_disposed() or ~CSharpInstance() + + // Unsafe refcount decrement. The managed instance also counts as a reference. + // See: _reference_owner_unsafe() + + if (Object::cast_to<Reference>(owner)->unreference()) { + memdelete(owner); + owner = NULL; + } +} + +void CSharpInstance::mono_object_disposed() { + + if (base_ref) + _unreference_owner_unsafe(); +} + +void CSharpInstance::refcount_incremented() { + + CRASH_COND(!base_ref); + + Reference *ref_owner = Object::cast_to<Reference>(owner); + + if (ref_owner->reference_get_count() > 1) { // Remember the managed side holds a reference, hence 1 instead of 0 here + // The reference count was increased after the managed side was the only one referencing our owner. + // This means the owner is being referenced again by the unmanaged side, + // so the owner must hold the managed side alive again to avoid it from being GCed. + + // Release the current weak handle and replace it with a strong handle. + uint32_t strong_gchandle = MonoGCHandle::make_strong_handle(gchandle->get_target()); + gchandle->release(); + gchandle->set_handle(strong_gchandle); + } +} + +bool CSharpInstance::refcount_decremented() { + + CRASH_COND(!base_ref); + + Reference *ref_owner = Object::cast_to<Reference>(owner); + + int refcount = ref_owner->reference_get_count(); + + if (refcount == 1) { // Remember the managed side holds a reference, hence 1 instead of 0 here + // If owner owner is no longer referenced by the unmanaged side, + // the managed instance takes responsibility of deleting the owner when GCed. + + // Release the current strong handle and replace it with a weak handle. + uint32_t weak_gchandle = MonoGCHandle::make_weak_handle(gchandle->get_target()); + gchandle->release(); + gchandle->set_handle(weak_gchandle); + + return false; + } + + ref_dying = (refcount == 0); + + return ref_dying; +} + +ScriptInstance::RPCMode CSharpInstance::get_rpc_mode(const StringName &p_method) const { + + GDMonoClass *top = script->script_class; + + while (top && top != script->native) { + GDMonoMethod *method = top->get_method(p_method); + + if (method) { // TODO should we reject static methods? + // TODO cache result + if (method->has_attribute(CACHED_CLASS(RemoteAttribute))) + return RPC_MODE_REMOTE; + if (method->has_attribute(CACHED_CLASS(SyncAttribute))) + return RPC_MODE_SYNC; + if (method->has_attribute(CACHED_CLASS(MasterAttribute))) + return RPC_MODE_MASTER; + if (method->has_attribute(CACHED_CLASS(SlaveAttribute))) + return RPC_MODE_SLAVE; + } + + top = top->get_parent_class(); + } + + return RPC_MODE_DISABLED; +} + +ScriptInstance::RPCMode CSharpInstance::get_rset_mode(const StringName &p_variable) const { + + GDMonoClass *top = script->script_class; + + while (top && top != script->native) { + GDMonoField *field = top->get_field(p_variable); + + if (field) { // TODO should we reject static fields? + // TODO cache result + if (field->has_attribute(CACHED_CLASS(RemoteAttribute))) + return RPC_MODE_REMOTE; + if (field->has_attribute(CACHED_CLASS(SyncAttribute))) + return RPC_MODE_SYNC; + if (field->has_attribute(CACHED_CLASS(MasterAttribute))) + return RPC_MODE_MASTER; + if (field->has_attribute(CACHED_CLASS(SlaveAttribute))) + return RPC_MODE_SLAVE; + } + + top = top->get_parent_class(); + } + + return RPC_MODE_DISABLED; +} + +void CSharpInstance::notification(int p_notification) { + + Variant value = p_notification; + const Variant *args[1] = { &value }; + + call_multilevel(CACHED_STRING_NAME(_notification), args, 1); +} + +Ref<Script> CSharpInstance::get_script() const { + + return script; +} + +ScriptLanguage *CSharpInstance::get_language() { + + return CSharpLanguage::get_singleton(); +} + +CSharpInstance::CSharpInstance() { + + owner = NULL; + base_ref = false; + ref_dying = false; +} + +CSharpInstance::~CSharpInstance() { + + if (gchandle.is_valid()) { + gchandle->release(); // Make sure it's released + } + + if (base_ref && !ref_dying) { // it may be called from the owner's destructor +#ifdef DEBUG_ENABLED + CRASH_COND(!owner); // dunno, just in case +#endif + _unreference_owner_unsafe(); + } + + if (script.is_valid() && owner) { +#ifndef NO_THREADS + CSharpLanguage::singleton->lock->lock(); +#endif + +#ifdef DEBUG_ENABLED + // CSharpInstance must not be created unless it's going to be added to the list for sure + Set<Object *>::Element *match = script->instances.find(owner); + CRASH_COND(!match); + script->instances.erase(match); +#else + script->instances.erase(owner); +#endif + +#ifndef NO_THREADS + CSharpLanguage::singleton->lock->unlock(); +#endif + } +} + +#ifdef TOOLS_ENABLED +void CSharpScript::_placeholder_erased(PlaceHolderScriptInstance *p_placeholder) { + + placeholders.erase(p_placeholder); +} +#endif + +#ifdef TOOLS_ENABLED +void CSharpScript::_update_exports_values(Map<StringName, Variant> &values, List<PropertyInfo> &propnames) { + + if (base_cache.is_valid()) { + base_cache->_update_exports_values(values, propnames); + } + + for (Map<StringName, Variant>::Element *E = exported_members_defval_cache.front(); E; E = E->next()) { + values[E->key()] = E->get(); + } + + for (List<PropertyInfo>::Element *E = exported_members_cache.front(); E; E = E->next()) { + propnames.push_back(E->get()); + } +} +#endif + +bool CSharpScript::_update_exports() { + +#ifdef TOOLS_ENABLED + if (!valid) + return false; + + bool changed = false; + + if (exports_invalidated) { + exports_invalidated = false; + + changed = true; + + member_info.clear(); + exported_members_cache.clear(); + exported_members_defval_cache.clear(); + + const Vector<GDMonoField *> &fields = script_class->get_all_fields(); + + // We are creating a temporary new instance of the class here to get the default value + // TODO Workaround. Should be replaced with IL opcodes analysis + + MonoObject *tmp_object = mono_object_new(SCRIPTS_DOMAIN, script_class->get_raw()); + + if (tmp_object) { + CACHED_FIELD(GodotObject, ptr)->set_value_raw(tmp_object, tmp_object); // FIXME WTF is this workaround + + GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0); + MonoObject *ex = NULL; + ctor->invoke(tmp_object, NULL, &ex); + + if (ex) { + ERR_PRINT("Exception thrown from constructor of temporary MonoObject:"); + mono_print_unhandled_exception(ex); + tmp_object = NULL; + ERR_FAIL_V(false); + } + } else { + ERR_PRINT("Failed to create temporary MonoObject"); + return false; + } + + for (int i = 0; i < fields.size(); i++) { + GDMonoField *field = fields[i]; + + if (field->is_static() || field->get_visibility() != GDMono::PUBLIC) + continue; + + String name = field->get_name(); + StringName cname = name; + + Variant::Type type = GDMonoMarshal::managed_to_variant_type(field->get_type()); + + if (field->has_attribute(CACHED_CLASS(ExportAttribute))) { + MonoObject *attr = field->get_attribute(CACHED_CLASS(ExportAttribute)); + + // Field has Export attribute + int hint = CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr); + String hint_string = CACHED_FIELD(ExportAttribute, hint_string)->get_string_value(attr); + int usage = CACHED_FIELD(ExportAttribute, usage)->get_int_value(attr); + + PropertyInfo prop_info = PropertyInfo(type, name, PropertyHint(hint), hint_string, PropertyUsageFlags(usage)); + + member_info[cname] = prop_info; + exported_members_cache.push_back(prop_info); + + if (tmp_object) { + exported_members_defval_cache[cname] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object)); + } + } else { + member_info[cname] = PropertyInfo(type, name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE); + } + } + } + + if (placeholders.size()) { + // Update placeholders if any + Map<StringName, Variant> values; + List<PropertyInfo> propnames; + _update_exports_values(values, propnames); + + for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { + E->get()->update(propnames, values); + } + } + + return changed; +#endif + return false; +} + +void CSharpScript::_clear() { + + tool = false; + valid = false; + + base = NULL; + native = NULL; + script_class = NULL; +} + +Variant CSharpScript::call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error) { + + GDMonoClass *top = script_class; + + while (top && top != native) { + GDMonoMethod *method = top->get_method(p_method, p_argcount); + + if (method && method->is_static()) { + MonoObject *result = method->invoke(NULL, p_args); + + if (result) { + return GDMonoMarshal::mono_object_to_variant(result, method->get_return_type()); + } else { + return Variant(); + } + } + + top = top->get_parent_class(); + } + + // No static method found. Try regular instance calls + return Script::call(p_method, p_args, p_argcount, r_error); +} + +void CSharpScript::_resource_path_changed() { + + String path = get_path(); + + if (!path.empty()) { + name = get_path().get_file().get_basename(); + } +} + +void CSharpScript::_bind_methods() { + + ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &CSharpScript::_new, MethodInfo(Variant::OBJECT, "new")); +} + +Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class) { + + // This method should not fail + + CRASH_COND(!p_class); + + Ref<CSharpScript> script = memnew(CSharpScript); + + script->name = p_class->get_name(); + script->script_class = p_class; + script->native = GDMonoUtils::get_class_native_base(script->script_class); + + CRASH_COND(script->native == NULL); + + GDMonoClass *base = script->script_class->get_parent_class(); + + if (base != script->native) + script->base = base; + +#ifdef DEBUG_ENABLED + // For debug builds, we must fetch from all native base methods as well. + // Native base methods must be fetched before the current class. + // Not needed if the script class itself is a native class. + + if (script->script_class != script->native) { + GDMonoClass *native_top = script->native; + while (native_top) { + native_top->fetch_methods_with_godot_api_checks(script->native); + + if (native_top == CACHED_CLASS(GodotObject)) + break; + + native_top = native_top->get_parent_class(); + } + } +#endif + + script->script_class->fetch_methods_with_godot_api_checks(script->native); + + // Need to fetch method from base classes as well + GDMonoClass *top = script->script_class; + while (top && top != script->native) { + top->fetch_methods_with_godot_api_checks(script->native); + top = top->get_parent_class(); + } + + return script; +} + +bool CSharpScript::can_instance() const { + + // TODO does the second condition even make sense? + return valid || (!tool && !ScriptServer::is_scripting_enabled()); +} + +StringName CSharpScript::get_instance_base_type() const { + + if (native) + return native->get_name(); + else + return StringName(); +} + +CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Variant::CallError &r_error) { + + /* STEP 1, CREATE */ + + CSharpInstance *instance = memnew(CSharpInstance); + instance->base_ref = p_isref; + instance->script = Ref<CSharpScript>(this); + instance->owner = p_owner; + instance->owner->set_script_instance(instance); + + if (instance->base_ref) + instance->_reference_owner_unsafe(); + + /* STEP 2, INITIALIZE AND CONSTRUCT */ + + MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, script_class->get_raw()); + + if (!mono_object) { + instance->script = Ref<CSharpScript>(); + instance->owner->set_script_instance(NULL); + r_error.error = Variant::CallError::CALL_ERROR_INSTANCE_IS_NULL; + ERR_EXPLAIN("Failed to allocate memory for the object"); + ERR_FAIL_V(NULL); + } + +#ifndef NO_THREADS + CSharpLanguage::singleton->lock->lock(); +#endif + + instances.insert(instance->owner); + +#ifndef NO_THREADS + CSharpLanguage::singleton->lock->unlock(); +#endif + + CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, instance->owner); + + // Construct + GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount); + ctor->invoke(mono_object, p_args, NULL); + + // Tie managed to unmanaged + instance->gchandle = MonoGCHandle::create_strong(mono_object); + + /* STEP 3, PARTY */ + + //@TODO make thread safe + return instance; +} + +Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Variant::CallError &r_error) { + + if (!valid) { + r_error.error = Variant::CallError::CALL_ERROR_INVALID_METHOD; + return Variant(); + } + + r_error.error = Variant::CallError::CALL_OK; + REF ref; + Object *owner = NULL; + + ERR_FAIL_NULL_V(native, Variant()); + + owner = ClassDB::instance(NATIVE_GDMONOCLASS_NAME(native)); + + Reference *r = Object::cast_to<Reference>(owner); + if (r) { + ref = REF(r); + } + + CSharpInstance *instance = _create_instance(p_args, p_argcount, owner, r != NULL, r_error); + if (!instance) { + if (ref.is_null()) { + memdelete(owner); //no owner, sorry + } + return Variant(); + } + + if (ref.is_valid()) { + return ref; + } else { + return owner; + } +} + +ScriptInstance *CSharpScript::instance_create(Object *p_this) { + + if (!valid) + return NULL; + + if (!tool && !ScriptServer::is_scripting_enabled()) { +#ifdef TOOLS_ENABLED + PlaceHolderScriptInstance *si = memnew(PlaceHolderScriptInstance(CSharpLanguage::get_singleton(), Ref<Script>(this), p_this)); + placeholders.insert(si); + _update_exports(); + return si; +#else + return NULL; +#endif + } + + if (native) { + String native_name = native->get_name(); + if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) { + if (ScriptDebugger::get_singleton()) { + CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, "Script inherits from native type '" + native_name + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); + } + ERR_EXPLAIN("Script inherits from native type '" + native_name + "', so it can't be instanced in object of type: '" + p_this->get_class() + "'"); + ERR_FAIL_V(NULL); + } + } + + Variant::CallError unchecked_error; + return _create_instance(NULL, 0, p_this, Object::cast_to<Reference>(p_this), unchecked_error); +} + +bool CSharpScript::instance_has(const Object *p_this) const { + +#ifndef NO_THREADS + CSharpLanguage::singleton->lock->lock(); +#endif + + bool ret = instances.has((Object *)p_this); + +#ifndef NO_THREADS + CSharpLanguage::singleton->lock->unlock(); +#endif + + return ret; +} + +bool CSharpScript::has_source_code() const { + + return !source.empty(); +} + +String CSharpScript::get_source_code() const { + + return source; +} + +void CSharpScript::set_source_code(const String &p_code) { + + if (source == p_code) + return; + source = p_code; +#ifdef TOOLS_ENABLED + source_changed_cache = true; +#endif +} + +bool CSharpScript::has_method(const StringName &p_method) const { + + return script_class->has_method(p_method); +} + +Error CSharpScript::reload(bool p_keep_state) { + +#ifndef NO_THREADS + CSharpLanguage::singleton->lock->lock(); +#endif + + bool has_instances = instances.size(); + +#ifndef NO_THREADS + CSharpLanguage::singleton->lock->unlock(); +#endif + + ERR_FAIL_COND_V(!p_keep_state && has_instances, ERR_ALREADY_IN_USE); + + GDMonoAssembly *project_assembly = GDMono::get_singleton()->get_project_assembly(); + + if (project_assembly) { + script_class = project_assembly->get_object_derived_class(name); + valid = script_class != NULL; + + if (script_class) { + tool = script_class->has_attribute(CACHED_CLASS(ToolAttribute)); + + native = GDMonoUtils::get_class_native_base(script_class); + + CRASH_COND(native == NULL); + + GDMonoClass *base_class = script_class->get_parent_class(); + + if (base_class != native) + base = base_class; + +#ifdef DEBUG_ENABLED + // For debug builds, we must fetch from all native base methods as well. + // Native base methods must be fetched before the current class. + // Not needed if the script class itself is a native class. + + if (script_class != native) { + GDMonoClass *native_top = native; + while (native_top) { + native_top->fetch_methods_with_godot_api_checks(native); + + if (native_top == CACHED_CLASS(GodotObject)) + break; + + native_top = native_top->get_parent_class(); + } + } +#endif + + script_class->fetch_methods_with_godot_api_checks(native); + + // Need to fetch method from base classes as well + GDMonoClass *top = script_class; + while (top && top != native) { + top->fetch_methods_with_godot_api_checks(native); + top = top->get_parent_class(); + } + } + + return OK; + } + + return ERR_FILE_MISSING_DEPENDENCIES; +} + +String CSharpScript::get_node_type() const { + + return ""; // ? +} + +ScriptLanguage *CSharpScript::get_language() const { + + return CSharpLanguage::get_singleton(); +} + +bool CSharpScript::get_property_default_value(const StringName &p_property, Variant &r_value) const { + +#ifdef TOOLS_ENABLED + + const Map<StringName, Variant>::Element *E = exported_members_defval_cache.find(p_property); + if (E) { + r_value = E->get(); + return true; + } + + if (base_cache.is_valid()) { + return base_cache->get_property_default_value(p_property, r_value); + } + +#endif + return false; +} + +void CSharpScript::update_exports() { + +#ifdef TOOLS_ENABLED + _update_exports(); + + if (placeholders.size()) { + Map<StringName, Variant> values; + List<PropertyInfo> propnames; + _update_exports_values(values, propnames); + + for (Set<PlaceHolderScriptInstance *>::Element *E = placeholders.front(); E; E = E->next()) { + E->get()->update(propnames, values); + } + } +#endif +} + +Ref<Script> CSharpScript::get_base_script() const { + + // TODO search in metadata file once we have it, not important any way? + return Ref<Script>(); +} + +void CSharpScript::get_script_property_list(List<PropertyInfo> *p_list) const { + + for (Map<StringName, PropertyInfo>::Element *E = member_info.front(); E; E = E->next()) { + p_list->push_back(E->value()); + } +} + +int CSharpScript::get_member_line(const StringName &p_member) const { + + // TODO omnisharp + return -1; +} + +Error CSharpScript::load_source_code(const String &p_path) { + + PoolVector<uint8_t> sourcef; + Error err; + FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err); + ERR_FAIL_COND_V(err != OK, err); + + int len = f->get_len(); + sourcef.resize(len + 1); + PoolVector<uint8_t>::Write w = sourcef.write(); + int r = f->get_buffer(w.ptr(), len); + f->close(); + memdelete(f); + ERR_FAIL_COND_V(r != len, ERR_CANT_OPEN); + w[len] = 0; + + String s; + if (s.parse_utf8((const char *)w.ptr())) { + + ERR_EXPLAIN("Script '" + p_path + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode."); + ERR_FAIL_V(ERR_INVALID_DATA); + } + + source = s; + +#ifdef TOOLS_ENABLED + source_changed_cache = true; +#endif + + return OK; +} + +StringName CSharpScript::get_script_name() const { + + return name; +} + +CSharpScript::CSharpScript() + : script_list(this) { + + _clear(); + +#ifdef TOOLS_ENABLED + source_changed_cache = false; + exports_invalidated = true; +#endif + + _resource_path_changed(); + +#ifdef DEBUG_ENABLED + +#ifndef NO_THREADS + CSharpLanguage::get_singleton()->lock->lock(); +#endif + + CSharpLanguage::get_singleton()->script_list.add(&script_list); + +#ifndef NO_THREADS + CSharpLanguage::get_singleton()->lock->unlock(); +#endif + +#endif // DEBUG_ENABLED +} + +CSharpScript::~CSharpScript() { + +#ifdef DEBUG_ENABLED + +#ifndef NO_THREADS + CSharpLanguage::get_singleton()->lock->lock(); +#endif + + CSharpLanguage::get_singleton()->script_list.remove(&script_list); + +#ifndef NO_THREADS + CSharpLanguage::get_singleton()->lock->unlock(); +#endif + +#endif // DEBUG_ENABLED +} + +/*************** RESOURCE ***************/ + +RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p_original_path, Error *r_error) { + + if (r_error) + *r_error = ERR_FILE_CANT_OPEN; + + // TODO ignore anything inside bin/ and obj/ in tools builds? + + CSharpScript *script = memnew(CSharpScript); + + Ref<CSharpScript> scriptres(script); + +#if defined(DEBUG_ENABLED) || defined(TOOLS_ENABLED) + Error err = script->load_source_code(p_path); + ERR_FAIL_COND_V(err != OK, RES()); +#endif + + script->set_path(p_original_path); + script->reload(); + + if (r_error) + *r_error = OK; + + return scriptres; +} + +void ResourceFormatLoaderCSharpScript::get_recognized_extensions(List<String> *p_extensions) const { + + p_extensions->push_back("cs"); +} + +bool ResourceFormatLoaderCSharpScript::handles_type(const String &p_type) const { + + return p_type == "Script" || p_type == CSharpLanguage::get_singleton()->get_type(); +} + +String ResourceFormatLoaderCSharpScript::get_resource_type(const String &p_path) const { + + return p_path.get_extension().to_lower() == "cs" ? CSharpLanguage::get_singleton()->get_type() : ""; +} + +Error ResourceFormatSaverCSharpScript::save(const String &p_path, const RES &p_resource, uint32_t p_flags) { + + Ref<CSharpScript> sqscr = p_resource; + ERR_FAIL_COND_V(sqscr.is_null(), ERR_INVALID_PARAMETER); + + String source = sqscr->get_source_code(); + +#ifdef TOOLS_ENABLED + if (!FileAccess::exists(p_path)) { + // The file does not yet exists, let's assume the user just created this script + + String sln_path = GodotSharpDirs::get_project_sln_path(); + String csproj_path = GodotSharpDirs::get_project_csproj_path(); + + if (!FileAccess::exists(sln_path) || !FileAccess::exists(csproj_path)) { + // A solution does not yet exist, create a new one + + CRASH_COND(GodotSharpEditor::get_singleton() == NULL); + GodotSharpEditor::get_singleton()->call("_create_project_solution"); + } + + // Add the file to the C# project + if (FileAccess::exists(csproj_path)) { + CSharpProject::add_item(csproj_path, "Compile", ProjectSettings::get_singleton()->globalize_path(p_path)); + } else { + ERR_PRINT("C# project not found!"); + } + } +#endif + + Error err; + FileAccess *file = FileAccess::open(p_path, FileAccess::WRITE, &err); + ERR_FAIL_COND_V(err, err); + + file->store_string(source); + + if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) { + memdelete(file); + return ERR_CANT_CREATE; + } + + file->close(); + memdelete(file); + + if (ScriptServer::is_reload_scripts_on_save_enabled()) { + CSharpLanguage::get_singleton()->reload_tool_script(p_resource, false); + } + + return OK; +} + +void ResourceFormatSaverCSharpScript::get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const { + + if (Object::cast_to<CSharpScript>(p_resource.ptr())) { + p_extensions->push_back("cs"); + } +} + +bool ResourceFormatSaverCSharpScript::recognize(const RES &p_resource) const { + + return Object::cast_to<CSharpScript>(p_resource.ptr()) != NULL; +} + +CSharpLanguage::StringNameCache::StringNameCache() { + + _awaited_signal_callback = StaticCString::create("_AwaitedSignalCallback"); + _set = StaticCString::create("_set"); + _get = StaticCString::create("_get"); + _notification = StaticCString::create("_notification"); + dotctor = StaticCString::create(".ctor"); +} diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h new file mode 100644 index 0000000000..3fcc3bdf04 --- /dev/null +++ b/modules/mono/csharp_script.h @@ -0,0 +1,338 @@ +/*************************************************************************/ +/* csharp_script.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef CSHARP_SCRIPT_H +#define CSHARP_SCRIPT_H + +#include "io/resource_loader.h" +#include "io/resource_saver.h" +#include "script_language.h" +#include "self_list.h" + +#include "mono_gc_handle.h" +#include "mono_gd/gd_mono.h" +#include "mono_gd/gd_mono_header.h" +#include "mono_gd/gd_mono_internals.h" + +class CSharpScript; +class CSharpInstance; +class CSharpLanguage; + +#ifdef NO_SAFE_CAST +template <typename TScriptInstance, typename TScriptLanguage> +TScriptInstance *cast_script_instance(ScriptInstance *p_inst) { + return p_inst->get_language() == TScriptLanguage::get_singleton() ? static_cast<TScriptInstance *>(p_inst) : NULL; +} +#else +template <typename TScriptInstance, typename TScriptLanguage> +TScriptInstance *cast_script_instance(ScriptInstance *p_inst) { + return dynamic_cast<TScriptInstance *>(p_inst); +} +#endif + +#define CAST_CSHARP_INSTANCE(m_inst) (cast_script_instance<CSharpInstance, CSharpLanguage>(m_inst)) + +class CSharpScript : public Script { + + GDCLASS(CSharpScript, Script) + + friend class CSharpInstance; + friend class CSharpLanguage; + friend class CSharpScriptDepSort; + + bool tool; + bool valid; + + bool builtin; + + GDMonoClass *base; + GDMonoClass *native; + GDMonoClass *script_class; + + Ref<CSharpScript> base_cache; // TODO what's this for? + + Set<Object *> instances; + + String source; + StringName name; + + SelfList<CSharpScript> script_list; + +#ifdef TOOLS_ENABLED + List<PropertyInfo> exported_members_cache; // members_cache + Map<StringName, Variant> exported_members_defval_cache; // member_default_values_cache + Set<PlaceHolderScriptInstance *> placeholders; + bool source_changed_cache; + bool exports_invalidated; + + void _update_exports_values(Map<StringName, Variant> &values, List<PropertyInfo> &propnames); + virtual void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder); +#endif + +#ifdef DEBUG_ENABLED + Map<ObjectID, List<Pair<StringName, Variant> > > pending_reload_state; +#endif + + Map<StringName, PropertyInfo> member_info; + + void _clear(); + + bool _update_exports(); + CSharpInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_isref, Variant::CallError &r_error); + Variant _new(const Variant **p_args, int p_argcount, Variant::CallError &r_error); + + // Do not use unless you know what you are doing + friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *); + static Ref<CSharpScript> create_for_managed_type(GDMonoClass *p_class); + +protected: + static void _bind_methods(); + + Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error); + virtual void _resource_path_changed(); + +public: + virtual bool can_instance() const; + virtual StringName get_instance_base_type() const; + virtual ScriptInstance *instance_create(Object *p_this); + virtual bool instance_has(const Object *p_this) const; + + virtual bool has_source_code() const; + virtual String get_source_code() const; + virtual void set_source_code(const String &p_code); + + virtual Error reload(bool p_keep_state = false); + + /* TODO */ virtual bool has_script_signal(const StringName &p_signal) const { return false; } + /* TODO */ virtual void get_script_signal_list(List<MethodInfo> *r_signals) const {} + + /* TODO */ virtual bool get_property_default_value(const StringName &p_property, Variant &r_value) const; + virtual void get_script_property_list(List<PropertyInfo> *p_list) const; + virtual void update_exports(); + + virtual bool is_tool() const { return tool; } + virtual Ref<Script> get_base_script() const; + virtual String get_node_type() const; + virtual ScriptLanguage *get_language() const; + + /* TODO */ virtual void get_script_method_list(List<MethodInfo> *p_list) const {} + bool has_method(const StringName &p_method) const; + /* TODO */ MethodInfo get_method_info(const StringName &p_method) const { return MethodInfo(); } + + virtual int get_member_line(const StringName &p_member) const; + + Error load_source_code(const String &p_path); + + StringName get_script_name() const; + + CSharpScript(); + ~CSharpScript(); +}; + +class CSharpInstance : public ScriptInstance { + + friend class CSharpScript; + friend class CSharpLanguage; + Object *owner; + Ref<CSharpScript> script; + Ref<MonoGCHandle> gchandle; + bool base_ref; + bool ref_dying; + + void _ml_call_reversed(GDMonoClass *klass, const StringName &p_method, const Variant **p_args, int p_argcount); + + void _reference_owner_unsafe(); + void _unreference_owner_unsafe(); + + // Do not use unless you know what you are doing + friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *); + static CSharpInstance *create_for_managed_type(Object *p_owner, CSharpScript *p_script, const Ref<MonoGCHandle> &p_gchandle); + +public: + MonoObject *get_mono_object() const; + + virtual bool set(const StringName &p_name, const Variant &p_value); + virtual bool get(const StringName &p_name, Variant &r_ret) const; + virtual void get_property_list(List<PropertyInfo> *p_properties) const; + virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid) const; + + /* TODO */ virtual void get_method_list(List<MethodInfo> *p_list) const {} + virtual bool has_method(const StringName &p_method) const; + virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Variant::CallError &r_error); + virtual void call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount); + virtual void call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount); + + void mono_object_disposed(); + + void refcount_incremented(); + bool refcount_decremented(); + + RPCMode get_rpc_mode(const StringName &p_method) const; + RPCMode get_rset_mode(const StringName &p_variable) const; + + virtual void notification(int p_notification); + + virtual Ref<Script> get_script() const; + + virtual ScriptLanguage *get_language(); + + CSharpInstance(); + ~CSharpInstance(); +}; + +class CSharpLanguage : public ScriptLanguage { + + friend class CSharpScript; + friend class CSharpInstance; + + static CSharpLanguage *singleton; + + GDMono *gdmono; + SelfList<CSharpScript>::List script_list; + + Mutex *lock; + Mutex *script_bind_lock; + + Map<Ref<CSharpScript>, Map<ObjectID, List<Pair<StringName, Variant> > > > to_reload; + + Map<Object *, Ref<MonoGCHandle> > gchandle_bindings; + + struct StringNameCache { + + StringName _awaited_signal_callback; + StringName _set; + StringName _get; + StringName _notification; + StringName dotctor; // .ctor + + StringNameCache(); + }; + + StringNameCache string_names; + + int lang_idx; + +public: + _FORCE_INLINE_ int get_language_index() { return lang_idx; } + void set_language_index(int p_idx); + + _FORCE_INLINE_ static CSharpLanguage *get_singleton() { return singleton; } + + bool debug_break(const String &p_error, bool p_allow_continue = true); + bool debug_break_parse(const String &p_file, int p_line, const String &p_error); + +#ifdef TOOLS_ENABLED + void reload_assemblies_if_needed(bool p_soft_reload); +#endif + + virtual String get_name() const; + + /* LANGUAGE FUNCTIONS */ + virtual String get_type() const; + virtual String get_extension() const; + virtual Error execute_file(const String &p_path); + virtual void init(); + virtual void finish(); + + /* EDITOR FUNCTIONS */ + virtual void get_reserved_words(List<String> *p_words) const; + virtual void get_comment_delimiters(List<String> *p_delimiters) const; + virtual void get_string_delimiters(List<String> *p_delimiters) const; + virtual Ref<Script> get_template(const String &p_class_name, const String &p_base_class_name) const; + /* TODO */ virtual bool 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) const { return true; } + virtual Script *create_script() const; + virtual bool has_named_classes() const; + /* TODO? */ virtual int find_function(const String &p_function, const String &p_code) const { return -1; } + virtual String make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const; + /* TODO? */ Error complete_code(const String &p_code, const String &p_base_path, Object *p_owner, List<String> *r_options, String &r_call_hint) { return ERR_UNAVAILABLE; } + /* TODO? */ virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const {} + /* TODO */ virtual void add_global_constant(const StringName &p_variable, const Variant &p_value) {} + + /* DEBUGGER FUNCTIONS */ + /* TODO */ virtual String debug_get_error() const { return ""; } + /* TODO */ virtual int debug_get_stack_level_count() const { return 1; } + /* TODO */ virtual int debug_get_stack_level_line(int p_level) const { return 1; } + /* TODO */ virtual String debug_get_stack_level_function(int p_level) const { return ""; } + /* TODO */ virtual String debug_get_stack_level_source(int p_level) const { return ""; } + /* TODO */ virtual void debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {} + /* TODO */ virtual void debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {} + /* TODO */ virtual void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) {} + /* TODO */ virtual String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) { return ""; } + /* TODO */ virtual Vector<StackInfo> debug_get_current_stack_info() { return Vector<StackInfo>(); } + + /* PROFILING FUNCTIONS */ + /* TODO */ virtual void profiling_start() {} + /* TODO */ virtual void profiling_stop() {} + /* TODO */ virtual int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) { return 0; } + /* TODO */ virtual int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) { return 0; } + + virtual void frame(); + + /* TODO? */ virtual void get_public_functions(List<MethodInfo> *p_functions) const {} + /* TODO? */ virtual void get_public_constants(List<Pair<String, Variant> > *p_constants) const {} + + virtual void reload_all_scripts(); + virtual void reload_tool_script(const Ref<Script> &p_script, bool p_soft_reload); + + /* LOADER FUNCTIONS */ + virtual void get_recognized_extensions(List<String> *p_extensions) const; + +#ifdef TOOLS_ENABLED + virtual Error open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col); + virtual bool overrides_external_editor(); +#endif + + /* THREAD ATTACHING */ + virtual void thread_enter(); + virtual void thread_exit(); + + // Don't use these. I'm watching you + virtual void *alloc_instance_binding_data(Object *p_object); + virtual void free_instance_binding_data(void *p_data); + + CSharpLanguage(); + ~CSharpLanguage(); +}; + +class ResourceFormatLoaderCSharpScript : public ResourceFormatLoader { +public: + virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL); + virtual void get_recognized_extensions(List<String> *p_extensions) const; + virtual bool handles_type(const String &p_type) const; + virtual String get_resource_type(const String &p_path) const; +}; + +class ResourceFormatSaverCSharpScript : public ResourceFormatSaver { +public: + virtual Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0); + virtual void get_recognized_extensions(const RES &p_resource, List<String> *p_extensions) const; + virtual bool recognize(const RES &p_resource) const; +}; + +#endif // CSHARP_SCRIPT_H diff --git a/modules/mono/doc_classes/@C#.xml b/modules/mono/doc_classes/@C#.xml new file mode 100644 index 0000000000..487ba9835f --- /dev/null +++ b/modules/mono/doc_classes/@C#.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="@C#" category="Core" version="3.0.alpha.custom_build"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + </methods> + <constants> + </constants> +</class> diff --git a/modules/mono/doc_classes/CSharpScript.xml b/modules/mono/doc_classes/CSharpScript.xml new file mode 100644 index 0000000000..5f21c9774d --- /dev/null +++ b/modules/mono/doc_classes/CSharpScript.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="CSharpScript" inherits="Script" category="Core" version="3.0.alpha.custom_build"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="new" qualifiers="vararg"> + <return type="Object"> + </return> + <description> + </description> + </method> + </methods> + <constants> + </constants> +</class> diff --git a/modules/mono/doc_classes/GodotSharp.xml b/modules/mono/doc_classes/GodotSharp.xml new file mode 100644 index 0000000000..e7e06ddd8f --- /dev/null +++ b/modules/mono/doc_classes/GodotSharp.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="GodotSharp" inherits="Object" category="Core" version="3.0.alpha.custom_build"> + <brief_description> + </brief_description> + <description> + </description> + <tutorials> + </tutorials> + <demos> + </demos> + <methods> + <method name="attach_thread"> + <return type="void"> + </return> + <description> + Attaches the current thread to the mono runtime. + </description> + </method> + <method name="detach_thread"> + <return type="void"> + </return> + <description> + Detaches the current thread from the mono runtime. + </description> + </method> + <method name="is_domain_loaded"> + <return type="bool"> + </return> + <description> + Returns whether the scripts domain is loaded. + </description> + </method> + <method name="is_finalizing_domain"> + <return type="bool"> + </return> + <description> + Returns whether the scripts domain is being finalized. + </description> + </method> + </methods> + <constants> + </constants> +</class> diff --git a/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs b/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs new file mode 100644 index 0000000000..256e64ddde --- /dev/null +++ b/modules/mono/editor/GodotSharpTools/Build/BuildSystem.cs @@ -0,0 +1,335 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using System.Security; +using Microsoft.Build.Framework; + +namespace GodotSharpTools.Build +{ + public class BuildInstance : IDisposable + { + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static void godot_icall_BuildInstance_ExitCallback(string solution, string config, int exitCode); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal extern static string godot_icall_BuildInstance_get_MSBuildPath(); + + private static string MSBuildPath + { + get { return godot_icall_BuildInstance_get_MSBuildPath(); } + } + + private string solution; + private string config; + + private Process process; + + private int exitCode; + public int ExitCode { get { return exitCode; } } + + public bool IsRunning { get { return process != null && !process.HasExited; } } + + public BuildInstance(string solution, string config) + { + this.solution = solution; + this.config = config; + } + + public bool Build(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null) + { + string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customProperties); + + ProcessStartInfo startInfo = new ProcessStartInfo(MSBuildPath, compilerArgs); + + // No console output, thanks + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; + startInfo.UseShellExecute = false; + + // Needed when running from Developer Command Prompt for VS + RemovePlatformVariable(startInfo.EnvironmentVariables); + + using (Process process = new Process()) + { + process.StartInfo = startInfo; + + process.Start(); + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + process.WaitForExit(); + + exitCode = process.ExitCode; + } + + return true; + } + + public bool BuildAsync(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties = null) + { + if (process != null) + throw new InvalidOperationException("Already in use"); + + string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customProperties); + + ProcessStartInfo startInfo = new ProcessStartInfo("msbuild", compilerArgs); + + // No console output, thanks + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; + startInfo.UseShellExecute = false; + + // Needed when running from Developer Command Prompt for VS + RemovePlatformVariable(startInfo.EnvironmentVariables); + + process = new Process(); + process.StartInfo = startInfo; + process.EnableRaisingEvents = true; + process.Exited += new EventHandler(BuildProcess_Exited); + + process.Start(); + + return true; + } + + private string BuildArguments(string loggerAssemblyPath, string loggerOutputDir, string[] customProperties) + { + string arguments = string.Format("{0} /v:normal /t:Build /p:{1} /l:{2},{3};{4}", + solution, + "Configuration=" + config, + typeof(GodotBuildLogger).FullName, + loggerAssemblyPath, + loggerOutputDir + ); + + if (customProperties != null) + { + foreach (string customProperty in customProperties) + { + arguments += " /p:" + customProperty; + } + } + + return arguments; + } + + private void RemovePlatformVariable(StringDictionary environmentVariables) + { + // EnvironmentVariables is case sensitive? Seriously? + + List<string> platformEnvironmentVariables = new List<string>(); + + foreach (string env in environmentVariables.Keys) + { + if (env.ToUpper() == "PLATFORM") + platformEnvironmentVariables.Add(env); + } + + foreach (string env in platformEnvironmentVariables) + environmentVariables.Remove(env); + } + + private void BuildProcess_Exited(object sender, System.EventArgs e) + { + exitCode = process.ExitCode; + + godot_icall_BuildInstance_ExitCallback(solution, config, exitCode); + + Dispose(); + } + + public void Dispose() + { + if (process != null) + { + process.Dispose(); + process = null; + } + } + } + + public class GodotBuildLogger : ILogger + { + public string Parameters { get; set; } + public LoggerVerbosity Verbosity { get; set; } + + public void Initialize(IEventSource eventSource) + { + if (null == Parameters) + throw new LoggerException("Log directory was not set."); + + string[] parameters = Parameters.Split(';'); + + string logDir = parameters[0]; + + if (String.IsNullOrEmpty(logDir)) + throw new LoggerException("Log directory was not set."); + + if (parameters.Length > 1) + throw new LoggerException("Too many parameters passed."); + + string logFile = Path.Combine(logDir, "msbuild_log.txt"); + string issuesFile = Path.Combine(logDir, "msbuild_issues.csv"); + + try + { + if (!Directory.Exists(logDir)) + Directory.CreateDirectory(logDir); + + this.logStreamWriter = new StreamWriter(logFile); + this.issuesStreamWriter = new StreamWriter(issuesFile); + } + catch (Exception ex) + { + if + ( + ex is UnauthorizedAccessException + || ex is ArgumentNullException + || ex is PathTooLongException + || ex is DirectoryNotFoundException + || ex is NotSupportedException + || ex is ArgumentException + || ex is SecurityException + || ex is IOException + ) + { + throw new LoggerException("Failed to create log file: " + ex.Message); + } + else + { + // Unexpected failure + throw; + } + } + + eventSource.ProjectStarted += new ProjectStartedEventHandler(eventSource_ProjectStarted); + eventSource.TaskStarted += new TaskStartedEventHandler(eventSource_TaskStarted); + eventSource.MessageRaised += new BuildMessageEventHandler(eventSource_MessageRaised); + eventSource.WarningRaised += new BuildWarningEventHandler(eventSource_WarningRaised); + eventSource.ErrorRaised += new BuildErrorEventHandler(eventSource_ErrorRaised); + eventSource.ProjectFinished += new ProjectFinishedEventHandler(eventSource_ProjectFinished); + } + + void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e) + { + string line = String.Format("{0}({1},{2}): error {3}: {4}", e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message); + + if (e.ProjectFile.Length > 0) + line += string.Format(" [{0}]", e.ProjectFile); + + WriteLine(line); + + string errorLine = String.Format(@"error,{0},{1},{2},{3},{4},{5}", + e.File.CsvEscape(), e.LineNumber, e.ColumnNumber, + e.Code.CsvEscape(), e.Message.CsvEscape(), e.ProjectFile.CsvEscape()); + issuesStreamWriter.WriteLine(errorLine); + } + + void eventSource_WarningRaised(object sender, BuildWarningEventArgs e) + { + string line = String.Format("{0}({1},{2}): warning {3}: {4}", e.File, e.LineNumber, e.ColumnNumber, e.Code, e.Message, e.ProjectFile); + + if (e.ProjectFile != null && e.ProjectFile.Length > 0) + line += string.Format(" [{0}]", e.ProjectFile); + + WriteLine(line); + + string warningLine = String.Format(@"warning,{0},{1},{2},{3},{4},{5}", + e.File.CsvEscape(), e.LineNumber, e.ColumnNumber, + e.Code.CsvEscape(), e.Message.CsvEscape(), e.ProjectFile != null ? e.ProjectFile.CsvEscape() : string.Empty); + issuesStreamWriter.WriteLine(warningLine); + } + + void eventSource_MessageRaised(object sender, BuildMessageEventArgs e) + { + // BuildMessageEventArgs adds Importance to BuildEventArgs + // Let's take account of the verbosity setting we've been passed in deciding whether to log the message + if ((e.Importance == MessageImportance.High && IsVerbosityAtLeast(LoggerVerbosity.Minimal)) + || (e.Importance == MessageImportance.Normal && IsVerbosityAtLeast(LoggerVerbosity.Normal)) + || (e.Importance == MessageImportance.Low && IsVerbosityAtLeast(LoggerVerbosity.Detailed)) + ) + { + WriteLineWithSenderAndMessage(String.Empty, e); + } + } + + void eventSource_TaskStarted(object sender, TaskStartedEventArgs e) + { + // TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName + // To keep this log clean, this logger will ignore these events. + } + + void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e) + { + WriteLine(e.Message); + indent++; + } + + void eventSource_ProjectFinished(object sender, ProjectFinishedEventArgs e) + { + indent--; + WriteLine(e.Message); + } + + /// <summary> + /// Write a line to the log, adding the SenderName + /// </summary> + private void WriteLineWithSender(string line, BuildEventArgs e) + { + if (0 == String.Compare(e.SenderName, "MSBuild", true /*ignore case*/)) + { + // Well, if the sender name is MSBuild, let's leave it out for prettiness + WriteLine(line); + } + else + { + WriteLine(e.SenderName + ": " + line); + } + } + + /// <summary> + /// Write a line to the log, adding the SenderName and Message + /// (these parameters are on all MSBuild event argument objects) + /// </summary> + private void WriteLineWithSenderAndMessage(string line, BuildEventArgs e) + { + if (0 == String.Compare(e.SenderName, "MSBuild", true /*ignore case*/)) + { + // Well, if the sender name is MSBuild, let's leave it out for prettiness + WriteLine(line + e.Message); + } + else + { + WriteLine(e.SenderName + ": " + line + e.Message); + } + } + + private void WriteLine(string line) + { + for (int i = indent; i > 0; i--) + { + logStreamWriter.Write("\t"); + } + logStreamWriter.WriteLine(line); + } + + public void Shutdown() + { + logStreamWriter.Close(); + issuesStreamWriter.Close(); + } + + public bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity) + { + return this.Verbosity >= checkVerbosity; + } + + private StreamWriter logStreamWriter; + private StreamWriter issuesStreamWriter; + private int indent; + } +} diff --git a/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs b/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs new file mode 100644 index 0000000000..303be3b732 --- /dev/null +++ b/modules/mono/editor/GodotSharpTools/Editor/MonoDevelopInstance.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Diagnostics; + +namespace GodotSharpTools.Editor +{ + public class MonoDevelopInstance + { + private Process process; + private string solutionFile; + + public void Execute(string[] files) + { + bool newWindow = process == null || process.HasExited; + + List<string> args = new List<string>(); + + args.Add("--ipc-tcp"); + + if (newWindow) + args.Add("\"" + Path.GetFullPath(solutionFile) + "\""); + + foreach (var file in files) + { + int semicolonIndex = file.IndexOf(';'); + + string filePath = semicolonIndex < 0 ? file : file.Substring(0, semicolonIndex); + string cursor = semicolonIndex < 0 ? string.Empty : file.Substring(semicolonIndex); + + args.Add("\"" + Path.GetFullPath(filePath.NormalizePath()) + cursor + "\""); + } + + if (newWindow) + { + ProcessStartInfo startInfo = new ProcessStartInfo(MonoDevelopFile, string.Join(" ", args)); + process = Process.Start(startInfo); + } + else + { + Process.Start(MonoDevelopFile, string.Join(" ", args)); + } + } + + public MonoDevelopInstance(string solutionFile) + { + this.solutionFile = solutionFile; + } + + private static string MonoDevelopFile + { + get + { + return "monodevelop"; + } + } + } +} diff --git a/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj b/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj new file mode 100644 index 0000000000..981083a3c2 --- /dev/null +++ b/modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid> + <OutputType>Library</OutputType> + <RootNamespace>GodotSharpTools</RootNamespace> + <AssemblyName>GodotSharpTools</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug</OutputPath> + <DefineConstants>DEBUG;</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>full</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release</OutputPath> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + <ConsolePause>false</ConsolePause> + </PropertyGroup> + <ItemGroup> + <Reference Include="System" /> + <Reference Include="Microsoft.Build" /> + <Reference Include="Microsoft.Build.Framework" /> + </ItemGroup> + <ItemGroup> + <Compile Include="StringExtensions.cs" /> + <Compile Include="Build\BuildSystem.cs" /> + <Compile Include="Editor\MonoDevelopInstance.cs" /> + <Compile Include="Project\ProjectExtensions.cs" /> + <Compile Include="Project\ProjectGenerator.cs" /> + <Compile Include="Project\ProjectUtils.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> +</Project>
\ No newline at end of file diff --git a/modules/mono/editor/GodotSharpTools/GodotSharpTools.sln b/modules/mono/editor/GodotSharpTools/GodotSharpTools.sln new file mode 100644 index 0000000000..7eabcdff5d --- /dev/null +++ b/modules/mono/editor/GodotSharpTools/GodotSharpTools.sln @@ -0,0 +1,17 @@ +
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharpTools", "GodotSharpTools.csproj", "{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/modules/mono/editor/GodotSharpTools/GodotSharpTools.userprefs b/modules/mono/editor/GodotSharpTools/GodotSharpTools.userprefs new file mode 100644 index 0000000000..0cbafdc20d --- /dev/null +++ b/modules/mono/editor/GodotSharpTools/GodotSharpTools.userprefs @@ -0,0 +1,14 @@ +<Properties StartupItem="GodotSharpTools.csproj"> + <MonoDevelop.Ide.Workspace ActiveConfiguration="Debug" /> + <MonoDevelop.Ide.Workbench ActiveDocument="Build/BuildSystem.cs"> + <Files> + <File FileName="Build/ProjectExtensions.cs" Line="1" Column="1" /> + <File FileName="Build/ProjectGenerator.cs" Line="1" Column="1" /> + <File FileName="Build/BuildSystem.cs" Line="37" Column="14" /> + </Files> + </MonoDevelop.Ide.Workbench> + <MonoDevelop.Ide.DebuggingService.Breakpoints> + <BreakpointStore /> + </MonoDevelop.Ide.DebuggingService.Breakpoints> + <MonoDevelop.Ide.DebuggingService.PinnedWatches /> +</Properties>
\ No newline at end of file diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs b/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs new file mode 100644 index 0000000000..6a97731539 --- /dev/null +++ b/modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs @@ -0,0 +1,49 @@ +using System; +using Microsoft.Build.Construction; + +namespace GodotSharpTools.Project +{ + public static class ProjectExtensions + { + public static bool HasItem(this ProjectRootElement root, string itemType, string include) + { + string includeNormalized = include.NormalizePath(); + + foreach (var itemGroup in root.ItemGroups) + { + if (itemGroup.Condition.Length != 0) + continue; + + foreach (var item in itemGroup.Items) + { + if (item.ItemType == itemType) + { + if (item.Include.NormalizePath() == includeNormalized) + return true; + } + } + } + + return false; + } + + public static void AddItemChecked(this ProjectRootElement root, string itemType, string include) + { + if (!root.HasItem(itemType, include)) + { + root.AddItem(itemType, include); + } + } + + public static Guid GetGuid(this ProjectRootElement root) + { + foreach (var property in root.Properties) + { + if (property.Name == "ProjectGuid") + return Guid.Parse(property.Value); + } + + return Guid.Empty; + } + } +} diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs b/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs new file mode 100644 index 0000000000..6bf54a0156 --- /dev/null +++ b/modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs @@ -0,0 +1,216 @@ +using System; +using System.IO; +using Microsoft.Build.Construction; + +namespace GodotSharpTools.Project +{ + public static class ProjectGenerator + { + public static string GenCoreApiProject(string dir, string[] compileItems) + { + string path = Path.Combine(dir, CoreApiProject + ".csproj"); + + ProjectPropertyGroupElement mainGroup; + var root = CreateLibraryProject(CoreApiProject, out mainGroup); + + mainGroup.AddProperty("DocumentationFile", Path.Combine("$(OutputPath)", "$(AssemblyName).xml")); + mainGroup.SetProperty("RootNamespace", "Godot"); + + GenAssemblyInfoFile(root, dir, CoreApiProject, + new string[] { "[assembly: InternalsVisibleTo(\"" + EditorApiProject + "\")]" }, + new string[] { "System.Runtime.CompilerServices" }); + + foreach (var item in compileItems) + { + root.AddItem("Compile", item.RelativeToPath(dir).Replace("/", "\\")); + } + + root.Save(path); + + return root.GetGuid().ToString().ToUpper(); + } + + public static string GenEditorApiProject(string dir, string coreApiHintPath, string[] compileItems) + { + string path = Path.Combine(dir, EditorApiProject + ".csproj"); + + ProjectPropertyGroupElement mainGroup; + var root = CreateLibraryProject(EditorApiProject, out mainGroup); + + mainGroup.AddProperty("DocumentationFile", Path.Combine("$(OutputPath)", "$(AssemblyName).xml")); + mainGroup.SetProperty("RootNamespace", "Godot"); + + GenAssemblyInfoFile(root, dir, EditorApiProject); + + foreach (var item in compileItems) + { + root.AddItem("Compile", item.RelativeToPath(dir).Replace("/", "\\")); + } + + var coreApiRef = root.AddItem("Reference", CoreApiProject); + coreApiRef.AddMetadata("HintPath", coreApiHintPath); + coreApiRef.AddMetadata("Private", "False"); + + root.Save(path); + + return root.GetGuid().ToString().ToUpper(); + } + + public static string GenGameProject(string dir, string name, string[] compileItems) + { + string path = Path.Combine(dir, name + ".csproj"); + + ProjectPropertyGroupElement mainGroup; + var root = CreateLibraryProject(name, out mainGroup); + + mainGroup.SetProperty("OutputPath", Path.Combine(".mono", "temp", "bin", "$(Configuration)")); + mainGroup.SetProperty("BaseIntermediateOutputPath", Path.Combine(".mono", "temp", "obj")); + mainGroup.SetProperty("IntermediateOutputPath", Path.Combine("$(BaseIntermediateOutputPath)", "$(Configuration)")); + + var toolsGroup = root.AddPropertyGroup(); + toolsGroup.Condition = " '$(Configuration)|$(Platform)' == 'Tools|AnyCPU' "; + toolsGroup.AddProperty("DebugSymbols", "true"); + toolsGroup.AddProperty("DebugType", "full"); + toolsGroup.AddProperty("Optimize", "false"); + toolsGroup.AddProperty("DefineConstants", "DEBUG;TOOLS;"); + toolsGroup.AddProperty("ErrorReport", "prompt"); + toolsGroup.AddProperty("WarningLevel", "4"); + toolsGroup.AddProperty("ConsolePause", "false"); + + var coreApiRef = root.AddItem("Reference", CoreApiProject); + coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", CoreApiProject + ".dll")); + coreApiRef.AddMetadata("Private", "False"); + + var editorApiRef = root.AddItem("Reference", EditorApiProject); + editorApiRef.Condition = " '$(Configuration)' == 'Tools' "; + editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", EditorApiProject + ".dll")); + editorApiRef.AddMetadata("Private", "False"); + + GenAssemblyInfoFile(root, dir, name); + + foreach (var item in compileItems) + { + root.AddItem("Compile", item.RelativeToPath(dir).Replace("/", "\\")); + } + + root.Save(path); + + return root.GetGuid().ToString().ToUpper(); + } + + public static void GenAssemblyInfoFile(ProjectRootElement root, string dir, string name, string[] assemblyLines = null, string[] usingDirectives = null) + { + + string propertiesDir = Path.Combine(dir, "Properties"); + if (!Directory.Exists(propertiesDir)) + Directory.CreateDirectory(propertiesDir); + + string usingDirectivesText = string.Empty; + + if (usingDirectives != null) + { + foreach (var usingDirective in usingDirectives) + usingDirectivesText += "\nusing " + usingDirective + ";"; + } + + string assemblyLinesText = string.Empty; + + if (assemblyLines != null) + { + foreach (var assemblyLine in assemblyLines) + assemblyLinesText += string.Join("\n", assemblyLines) + "\n"; + } + + string content = string.Format(assemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText); + + string assemblyInfoFile = Path.Combine(propertiesDir, "AssemblyInfo.cs"); + + File.WriteAllText(assemblyInfoFile, content); + + root.AddItem("Compile", assemblyInfoFile.RelativeToPath(dir).Replace("/", "\\")); + } + + public static ProjectRootElement CreateLibraryProject(string name, out ProjectPropertyGroupElement mainGroup) + { + var root = ProjectRootElement.Create(); + root.DefaultTargets = "Build"; + + mainGroup = root.AddPropertyGroup(); + mainGroup.AddProperty("Configuration", "Debug").Condition = " '$(Configuration)' == '' "; + mainGroup.AddProperty("Platform", "AnyCPU").Condition = " '$(Platform)' == '' "; + mainGroup.AddProperty("ProjectGuid", "{" + Guid.NewGuid().ToString().ToUpper() + "}"); + mainGroup.AddProperty("OutputType", "Library"); + mainGroup.AddProperty("OutputPath", Path.Combine("bin", "$(Configuration)")); + mainGroup.AddProperty("RootNamespace", name); + mainGroup.AddProperty("AssemblyName", name); + mainGroup.AddProperty("TargetFrameworkVersion", "v4.5"); + + var debugGroup = root.AddPropertyGroup(); + debugGroup.Condition = " '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "; + debugGroup.AddProperty("DebugSymbols", "true"); + debugGroup.AddProperty("DebugType", "full"); + debugGroup.AddProperty("Optimize", "false"); + debugGroup.AddProperty("DefineConstants", "DEBUG;"); + debugGroup.AddProperty("ErrorReport", "prompt"); + debugGroup.AddProperty("WarningLevel", "4"); + debugGroup.AddProperty("ConsolePause", "false"); + + var releaseGroup = root.AddPropertyGroup(); + releaseGroup.Condition = " '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "; + releaseGroup.AddProperty("DebugType", "full"); + releaseGroup.AddProperty("Optimize", "true"); + releaseGroup.AddProperty("ErrorReport", "prompt"); + releaseGroup.AddProperty("WarningLevel", "4"); + releaseGroup.AddProperty("ConsolePause", "false"); + + // References + var referenceGroup = root.AddItemGroup(); + referenceGroup.AddItem("Reference", "System"); + + root.AddImport(Path.Combine("$(MSBuildBinPath)", "Microsoft.CSharp.targets").Replace("/", "\\")); + + return root; + } + + private static void AddItems(ProjectRootElement elem, string groupName, params string[] items) + { + var group = elem.AddItemGroup(); + + foreach (var item in items) + { + group.AddItem(groupName, item); + } + } + + public const string CoreApiProject = "GodotSharp"; + public const string EditorApiProject = "GodotSharpEditor"; + + private const string assemblyInfoTemplate = +@"using System.Reflection;{0} + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle(""{1}"")] +[assembly: AssemblyDescription("""")] +[assembly: AssemblyConfiguration("""")] +[assembly: AssemblyCompany("""")] +[assembly: AssemblyProduct("""")] +[assembly: AssemblyCopyright("""")] +[assembly: AssemblyTrademark("""")] +[assembly: AssemblyCulture("""")] + +// The assembly version has the format ""{{Major}}.{{Minor}}.{{Build}}.{{Revision}}"". +// The form ""{{Major}}.{{Minor}}.*"" will automatically update the build and revision, +// and ""{{Major}}.{{Minor}}.{{Build}}.*"" will update just the revision. + +[assembly: AssemblyVersion(""1.0.*"")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("""")] +{2}"; + } +} diff --git a/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs b/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs new file mode 100644 index 0000000000..a50b4fb064 --- /dev/null +++ b/modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs @@ -0,0 +1,17 @@ +using System; +using System.IO; +using Microsoft.Build.Construction; + +namespace GodotSharpTools.Project +{ + public static class ProjectUtils + { + public static void AddItemToProjectChecked(string projectPath, string itemType, string include) + { + var dir = Directory.GetParent(projectPath).FullName; + var root = ProjectRootElement.Open(projectPath); + root.AddItemChecked(itemType, include.RelativeToPath(dir).Replace("/", "\\")); + root.Save(); + } + } +} diff --git a/modules/mono/editor/GodotSharpTools/Properties/AssemblyInfo.cs b/modules/mono/editor/GodotSharpTools/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..7115d8fc71 --- /dev/null +++ b/modules/mono/editor/GodotSharpTools/Properties/AssemblyInfo.cs @@ -0,0 +1,27 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("GodotSharpTools")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("ignacio")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + diff --git a/modules/mono/editor/GodotSharpTools/StringExtensions.cs b/modules/mono/editor/GodotSharpTools/StringExtensions.cs new file mode 100644 index 0000000000..b66c86f8ce --- /dev/null +++ b/modules/mono/editor/GodotSharpTools/StringExtensions.cs @@ -0,0 +1,52 @@ +using System; +using System.IO; + +namespace GodotSharpTools +{ + public static class StringExtensions + { + public static string RelativeToPath(this string path, string dir) + { + // Make sure the directory ends with a path separator + dir = Path.Combine(dir, " ").TrimEnd(); + + if (Path.DirectorySeparatorChar == '\\') + dir = dir.Replace("/", "\\") + "\\"; + + Uri fullPath = new Uri(Path.GetFullPath(path), UriKind.Absolute); + Uri relRoot = new Uri(Path.GetFullPath(dir), UriKind.Absolute); + + return relRoot.MakeRelativeUri(fullPath).ToString(); + } + + public static string NormalizePath(this string path) + { + bool rooted = path.IsAbsolutePath(); + + path = path.Replace('\\', '/'); + + string[] parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + + path = string.Join(Path.DirectorySeparatorChar.ToString(), parts).Trim(); + + return rooted ? Path.DirectorySeparatorChar.ToString() + path : path; + } + + private static readonly string driveRoot = Path.GetPathRoot(Environment.CurrentDirectory); + + public static bool IsAbsolutePath(this string path) + { + return path.StartsWith("/") || path.StartsWith("\\") || path.StartsWith(driveRoot); + } + + public static string CsvEscape(this string value, char delimiter = ',') + { + bool hasSpecialChar = value.IndexOfAny(new char[] { '\"', '\n', '\r', delimiter }) != -1; + + if (hasSpecialChar) + return "\"" + value.Replace("\"", "\"\"") + "\""; + + return value; + } + } +} diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp new file mode 100644 index 0000000000..123f00ea10 --- /dev/null +++ b/modules/mono/editor/bindings_generator.cpp @@ -0,0 +1,2151 @@ +/*************************************************************************/ +/* bindings_generator.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "bindings_generator.h" + +#ifdef DEBUG_METHODS_ENABLED + +#include "global_constants.h" +#include "io/compression.h" +#include "os/dir_access.h" +#include "os/file_access.h" +#include "os/os.h" +#include "project_settings.h" +#include "ucaps.h" + +#include "../glue/cs_compressed.gen.h" +#include "../godotsharp_defs.h" +#include "../mono_gd/gd_mono_marshal.h" +#include "../utils/path_utils.h" +#include "../utils/string_utils.h" +#include "csharp_project.h" +#include "net_solution.h" + +#define CS_INDENT " " + +#define INDENT1 CS_INDENT +#define INDENT2 INDENT1 INDENT1 +#define INDENT3 INDENT2 INDENT1 +#define INDENT4 INDENT3 INDENT1 +#define INDENT5 INDENT4 INDENT1 + +#define MEMBER_BEGIN "\n" INDENT2 + +#define OPEN_BLOCK "{\n" +#define CLOSE_BLOCK "}\n" + +#define OPEN_BLOCK_L2 INDENT2 OPEN_BLOCK INDENT3 +#define OPEN_BLOCK_L3 INDENT3 OPEN_BLOCK INDENT4 +#define OPEN_BLOCK_L4 INDENT4 OPEN_BLOCK INDENT5 +#define CLOSE_BLOCK_L2 INDENT2 CLOSE_BLOCK +#define CLOSE_BLOCK_L3 INDENT3 CLOSE_BLOCK +#define CLOSE_BLOCK_L4 INDENT4 CLOSE_BLOCK + +#define LOCAL_RET "ret" + +#define CS_CLASS_NATIVECALLS "NativeCalls" +#define CS_CLASS_NATIVECALLS_EDITOR "EditorNativeCalls" +#define CS_FIELD_MEMORYOWN "memoryOwn" +#define CS_PARAM_METHODBIND "method" +#define CS_PARAM_INSTANCE "ptr" +#define CS_SMETHOD_GETINSTANCE "GetPtr" +#define CS_FIELD_SINGLETON "instance" +#define CS_PROP_SINGLETON "Instance" +#define CS_CLASS_SIGNALAWAITER "SignalAwaiter" +#define CS_METHOD_CALL "Call" + +#define GLUE_HEADER_FILE "glue_header.h" +#define ICALL_PREFIX "godot_icall_" +#define SINGLETON_ICALL_SUFFIX "_get_singleton" +#define ICALL_GET_METHODBIND ICALL_PREFIX "ClassDB_get_method" +#define ICALL_CONNECT_SIGNAL_AWAITER ICALL_PREFIX "Object_connect_signal_awaiter" +#define ICALL_OBJECT_DTOR ICALL_PREFIX "Object_Dtor" +#define C_LOCAL_PTRCALL_ARGS "call_args" +#define C_MACRO_OBJECT_CONSTRUCT "GODOTSHARP_INSTANCE_OBJECT" + +#define C_NS_MONOUTILS "GDMonoUtils" +#define C_NS_MONOINTERNALS "GDMonoInternals" +#define C_METHOD_TIE_MANAGED_TO_UNMANAGED C_NS_MONOINTERNALS "::tie_managed_to_unmanaged" +#define C_METHOD_UNMANAGED_GET_MANAGED C_NS_MONOUTILS "::unmanaged_get_managed" + +#define C_NS_MONOMARSHAL "GDMonoMarshal" +#define C_METHOD_MANAGED_TO_VARIANT C_NS_MONOMARSHAL "::mono_object_to_variant" +#define C_METHOD_MANAGED_FROM_VARIANT C_NS_MONOMARSHAL "::variant_to_mono_object" +#define C_METHOD_MONOSTR_TO_GODOT C_NS_MONOMARSHAL "::mono_string_to_godot" +#define C_METHOD_MONOSTR_FROM_GODOT C_NS_MONOMARSHAL "::mono_string_from_godot" +#define C_METHOD_MONOARRAY_TO(m_type) C_NS_MONOMARSHAL "::mono_array_to_" #m_type +#define C_METHOD_MONOARRAY_FROM(m_type) C_NS_MONOMARSHAL "::" #m_type "_to_mono_array" +#define C_METHOD_MANAGED_TO_DICT C_NS_MONOMARSHAL "::mono_object_to_Dictionary" +#define C_METHOD_MANAGED_FROM_DICT C_NS_MONOMARSHAL "::Dictionary_to_mono_object" + +const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN = "\t%0 %1_in = %1;\n"; + +bool BindingsGenerator::verbose_output = false; + +static bool is_csharp_keyword(const String &p_name) { + + // Reserved keywords + + return p_name == "abstract" || p_name == "as" || p_name == "base" || p_name == "bool" || + p_name == "break" || p_name == "byte" || p_name == "case" || p_name == "catch" || + p_name == "char" || p_name == "checked" || p_name == "class" || p_name == "const" || + p_name == "continue" || p_name == "decimal" || p_name == "default" || p_name == "delegate" || + p_name == "do" || p_name == "double" || p_name == "else" || p_name == "enum" || + p_name == "event" || p_name == "explicit" || p_name == "extern" || p_name == "false" || + p_name == "finally" || p_name == "fixed" || p_name == "float" || p_name == "for" || + p_name == "forech" || p_name == "goto" || p_name == "if" || p_name == "implicit" || + p_name == "in" || p_name == "int" || p_name == "interface" || p_name == "internal" || + p_name == "is" || p_name == "lock" || p_name == "long" || p_name == "namespace" || + p_name == "new" || p_name == "null" || p_name == "object" || p_name == "operator" || + p_name == "out" || p_name == "override" || p_name == "params" || p_name == "private" || + p_name == "protected" || p_name == "public" || p_name == "readonly" || p_name == "ref" || + p_name == "return" || p_name == "sbyte" || p_name == "sealed" || p_name == "short" || + p_name == "sizeof" || p_name == "stackalloc" || p_name == "static" || p_name == "string" || + p_name == "struct" || p_name == "switch" || p_name == "this" || p_name == "throw" || + p_name == "true" || p_name == "try" || p_name == "typeof" || p_name == "uint" || p_name == "ulong" || + p_name == "unchecked" || p_name == "unsafe" || p_name == "ushort" || p_name == "using" || + p_name == "virtual" || p_name == "volatile" || p_name == "void" || p_name == "while"; +} + +static bool is_singleton_black_listed(const String &p_type) { + + return p_type == "IP_Unix" || p_type == "InputDefault" || p_type == "AudioServerSW" || p_type == "PhysicsServerSW" || + p_type == "Physics2DServerSW" || p_type == "SpatialSoundServerSW" || p_type == "SpatialSound2DServerSW"; +} + +inline static String escape_csharp_keyword(const String &p_name) { + + return is_csharp_keyword(p_name) ? "@" + p_name : p_name; +} + +static String snake_to_pascal_case(const String &p_identifier) { + + String ret; + Vector<String> parts = p_identifier.split("_", true); + + for (int i = 0; i < parts.size(); i++) { + String part = parts[i]; + + if (part.length()) { + part[0] = _find_upper(part[0]); + ret += part; + } else { + if (i == 0 || i == (parts.size() - 1)) { + // Preserve underscores at the beginning and end + ret += "_"; + } else { + // Preserve contiguous underscores + if (parts[i - 1].length()) { + ret += "__"; + } else { + ret += "_"; + } + } + } + } + + return ret; +} + +static String snake_to_camel_case(const String &p_identifier) { + + String ret; + Vector<String> parts = p_identifier.split("_", true); + + for (int i = 0; i < parts.size(); i++) { + String part = parts[i]; + + if (part.length()) { + if (i != 0) + part[0] = _find_upper(part[0]); + ret += part; + } else { + if (i == 0 || i == (parts.size() - 1)) { + // Preserve underscores at the beginning and end + ret += "_"; + } else { + // Preserve contiguous underscores + if (parts[i - 1].length()) { + ret += "__"; + } else { + ret += "_"; + } + } + } + } + + return ret; +} + +void BindingsGenerator::_generate_header_icalls() { + + core_custom_icalls.clear(); + + core_custom_icalls.push_back(InternalCall(ICALL_GET_METHODBIND, "IntPtr", "string type, string method")); + core_custom_icalls.push_back(InternalCall(ICALL_OBJECT_DTOR, "void", "IntPtr ptr")); + + core_custom_icalls.push_back(InternalCall(ICALL_CONNECT_SIGNAL_AWAITER, "Error", + "IntPtr source, string signal, IntPtr target, " CS_CLASS_SIGNALAWAITER " awaiter")); + + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "NodePath_Ctor", "IntPtr", "string path")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "NodePath_Dtor", "void", "IntPtr ptr")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "NodePath_operator_String", "string", "IntPtr ptr")); + + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "RID_Ctor", "IntPtr", "IntPtr from")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "RID_Dtor", "void", "IntPtr ptr")); + + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "String_md5_buffer", "byte[]", "string str")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "String_md5_text", "string", "string str")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "String_rfind", "int", "string str, string what, int from")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "String_rfindn", "int", "string str, string what, int from")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "String_sha256_buffer", "byte[]", "string str")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "String_sha256_text", "string", "string str")); + + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_bytes2var", "object", "byte[] bytes")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_convert", "object", "object what, int type")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_hash", "int", "object var")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_instance_from_id", "Object", "int instance_id")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_print", "void", "object[] what")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_printerr", "void", "object[] what")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_printraw", "void", "object[] what")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_prints", "void", "object[] what")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_printt", "void", "object[] what")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_seed", "void", "int seed")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_str", "string", "object[] what")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_str2var", "object", "string str")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_type_exists", "bool", "string type")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_var2bytes", "byte[]", "object what")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_var2str", "string", "object var")); + core_custom_icalls.push_back(InternalCall(ICALL_PREFIX "Godot_weakref", "WeakRef", "IntPtr obj")); +} + +void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) { + + if (p_itype.base_name.length() && obj_types[p_itype.base_name].is_singleton && is_singleton_black_listed(p_itype.name)) + return; + + for (const List<MethodInterface>::Element *E = p_itype.methods.front(); E; E = E->next()) { + const MethodInterface &imethod = E->get(); + + if (imethod.is_virtual) + continue; + + const TypeInterface *return_type = _get_type_by_name_or_placeholder(imethod.return_type); + + String im_sig = "IntPtr " CS_PARAM_METHODBIND ", IntPtr " CS_PARAM_INSTANCE; + String im_unique_sig = imethod.return_type + ",IntPtr,IntPtr"; + + // Get arguments information + int i = 0; + for (const List<ArgumentInterface>::Element *F = imethod.arguments.front(); F; F = F->next()) { + const TypeInterface *arg_type = _get_type_by_name_or_placeholder(F->get().type); + + im_sig += ", "; + im_sig += arg_type->im_type_in; + im_sig += " arg"; + im_sig += itos(i + 1); + + im_unique_sig += ","; + im_unique_sig += get_unique_sig(*arg_type); + + i++; + } + + // godot_icall_{argc}_{icallcount} + String icall_method = ICALL_PREFIX + itos(imethod.arguments.size()) + "_" + itos(method_icalls.size()); + + InternalCall im_icall = InternalCall(p_itype.api_type, icall_method, return_type->im_type_out, im_sig, im_unique_sig); + + List<InternalCall>::Element *match = method_icalls.find(im_icall); + + if (match) { + if (p_itype.api_type != ClassDB::API_EDITOR) + match->get().editor_only = false; + method_icalls_map.insert(&E->get(), &match->get()); + } else { + List<InternalCall>::Element *added = method_icalls.push_back(im_icall); + method_icalls_map.insert(&E->get(), &added->get()); + } + } +} + +Error BindingsGenerator::generate_cs_core_project(const String &p_output_dir, bool p_verbose_output) { + + verbose_output = p_verbose_output; + + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); + + if (!DirAccess::exists(p_output_dir)) { + Error err = da->make_dir_recursive(p_output_dir); + ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); + } + + da->change_dir(p_output_dir); + da->make_dir("Core"); + da->make_dir("ObjectType"); + + String core_dir = path_join(p_output_dir, "Core"); + String obj_type_dir = path_join(p_output_dir, "ObjectType"); + + Vector<String> compile_items; + + NETSolution solution(API_ASSEMBLY_NAME); + + if (!solution.set_path(p_output_dir)) + return ERR_FILE_NOT_FOUND; + + for (Map<String, TypeInterface>::Element *E = obj_types.front(); E; E = E->next()) { + const TypeInterface &itype = E->get(); + + if (itype.api_type == ClassDB::API_EDITOR) + continue; + + String output_file = path_join(obj_type_dir, E->get().proxy_name + ".cs"); + Error err = _generate_cs_type(E->get(), output_file); + + if (err == ERR_SKIP) + continue; + + if (err != OK) + return err; + + compile_items.push_back(output_file); + } + +#define GENERATE_BUILTIN_TYPE(m_name) \ + { \ + String output_file = path_join(core_dir, #m_name ".cs"); \ + Error err = _generate_cs_type(builtin_types[#m_name], output_file); \ + if (err != OK) \ + return err; \ + compile_items.push_back(output_file); \ + } + + GENERATE_BUILTIN_TYPE(NodePath); + GENERATE_BUILTIN_TYPE(RID); + +#undef GENERATE_BUILTIN_TYPE + + // Generate source for GlobalConstants + + String constants_source; + int global_constants_count = GlobalConstants::get_global_constant_count(); + + if (global_constants_count > 0) { + Map<String, DocData::ClassDoc>::Element *match = EditorHelp::get_doc_data()->class_list.find("@Global Scope"); + + ERR_EXPLAIN("Could not find `@Global Scope` in DocData"); + ERR_FAIL_COND_V(!match, ERR_BUG); + + const DocData::ClassDoc &global_scope_doc = match->value(); + + for (int i = 0; i < global_constants_count; i++) { + const DocData::ConstantDoc &const_doc = global_scope_doc.constants[i]; + + if (i > 0) + constants_source += MEMBER_BEGIN; + + if (const_doc.description.size()) { + constants_source += "/// <summary>\n"; + + Vector<String> description_lines = const_doc.description.split("\n"); + + for (int i = 0; i < description_lines.size(); i++) { + if (description_lines[i].size()) { + constants_source += INDENT2 "/// "; + constants_source += description_lines[i].strip_edges().xml_escape(); + constants_source += "\n"; + } + } + + constants_source += INDENT2 "/// </summary>" MEMBER_BEGIN; + } + + constants_source += "public const int "; + constants_source += GlobalConstants::get_global_constant_name(i); + constants_source += " = "; + constants_source += itos(GlobalConstants::get_global_constant_value(i)); + constants_source += ";"; + } + } + + // Generate sources from compressed files + + Map<String, CompressedFile> compressed_files; + get_compressed_files(compressed_files); + + for (Map<String, CompressedFile>::Element *E = compressed_files.front(); E; E = E->next()) { + const String &file_name = E->key(); + const CompressedFile &file_data = E->value(); + + String output_file = path_join(core_dir, file_name); + + Vector<uint8_t> data; + data.resize(file_data.uncompressed_size); + Compression::decompress(data.ptr(), file_data.uncompressed_size, file_data.data, file_data.compressed_size, Compression::MODE_DEFLATE); + + if (file_name.get_basename() == BINDINGS_GLOBAL_SCOPE_CLASS) { + // GD.cs must be formatted to include the generated global constants + String data_str = String::utf8(reinterpret_cast<const char *>(data.ptr()), data.size()); + + Dictionary format_keys; + format_keys["GodotGlobalConstants"] = constants_source; + data_str = data_str.format(format_keys, "/*{_}*/"); + + CharString data_utf8 = data_str.utf8(); + data.resize(data_utf8.length()); + copymem(data.ptr(), reinterpret_cast<const uint8_t *>(data_utf8.get_data()), data_utf8.length()); + } + + FileAccessRef file = FileAccess::open(output_file, FileAccess::WRITE); + ERR_FAIL_COND_V(!file, ERR_FILE_CANT_WRITE); + file->store_buffer(data.ptr(), data.size()); + file->close(); + + compile_items.push_back(output_file); + } + + List<String> cs_icalls_content; + + cs_icalls_content.push_back("using System;\n" + "using System.Runtime.CompilerServices;\n" + "using System.Collections.Generic;\n" + "\n"); + cs_icalls_content.push_back("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); + cs_icalls_content.push_back(INDENT1 "internal static class " CS_CLASS_NATIVECALLS "\n" INDENT1 OPEN_BLOCK); + +#define ADD_INTERNAL_CALL(m_icall) \ + if (!m_icall.editor_only) { \ + cs_icalls_content.push_back(INDENT2 "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \ + cs_icalls_content.push_back(INDENT2 "internal extern static "); \ + cs_icalls_content.push_back(m_icall.im_type_out + " "); \ + cs_icalls_content.push_back(m_icall.name + "("); \ + cs_icalls_content.push_back(m_icall.im_sig + ");\n"); \ + } + + for (const List<InternalCall>::Element *E = core_custom_icalls.front(); E; E = E->next()) + ADD_INTERNAL_CALL(E->get()); + for (const List<InternalCall>::Element *E = method_icalls.front(); E; E = E->next()) + ADD_INTERNAL_CALL(E->get()); + +#undef ADD_INTERNAL_CALL + + cs_icalls_content.push_back(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); + + String internal_methods_file = path_join(core_dir, CS_CLASS_NATIVECALLS ".cs"); + + Error err = _save_file(internal_methods_file, cs_icalls_content); + if (err != OK) + return err; + + compile_items.push_back(internal_methods_file); + + String guid = CSharpProject::generate_core_api_project(p_output_dir, compile_items); + + solution.add_new_project(API_ASSEMBLY_NAME, guid); + + Error sln_error = solution.save(); + if (sln_error != OK) { + ERR_PRINT("Could not to save .NET solution."); + return sln_error; + } + + return OK; +} + +Error BindingsGenerator::generate_cs_editor_project(const String &p_output_dir, const String &p_core_dll_path, bool p_verbose_output) { + + verbose_output = p_verbose_output; + + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); + + if (!DirAccess::exists(p_output_dir)) { + Error err = da->make_dir_recursive(p_output_dir); + ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); + } + + da->change_dir(p_output_dir); + da->make_dir("Core"); + da->make_dir("ObjectType"); + + String core_dir = path_join(p_output_dir, "Core"); + String obj_type_dir = path_join(p_output_dir, "ObjectType"); + + Vector<String> compile_items; + + NETSolution solution(EDITOR_API_ASSEMBLY_NAME); + + if (!solution.set_path(p_output_dir)) + return ERR_FILE_NOT_FOUND; + + for (Map<String, TypeInterface>::Element *E = obj_types.front(); E; E = E->next()) { + const TypeInterface &itype = E->get(); + + if (itype.api_type != ClassDB::API_EDITOR) + continue; + + String output_file = path_join(obj_type_dir, E->get().proxy_name + ".cs"); + Error err = _generate_cs_type(E->get(), output_file); + + if (err == ERR_SKIP) + continue; + + if (err != OK) + return err; + + compile_items.push_back(output_file); + } + + List<String> cs_icalls_content; + + cs_icalls_content.push_back("using System;\n" + "using System.Runtime.CompilerServices;\n" + "using System.Collections.Generic;\n" + "\n"); + cs_icalls_content.push_back("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); + cs_icalls_content.push_back(INDENT1 "internal static class " CS_CLASS_NATIVECALLS_EDITOR "\n" INDENT1 OPEN_BLOCK); + +#define ADD_INTERNAL_CALL(m_icall) \ + if (m_icall.editor_only) { \ + cs_icalls_content.push_back(INDENT2 "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \ + cs_icalls_content.push_back(INDENT2 "internal extern static "); \ + cs_icalls_content.push_back(m_icall.im_type_out + " "); \ + cs_icalls_content.push_back(m_icall.name + "("); \ + cs_icalls_content.push_back(m_icall.im_sig + ");\n"); \ + } + + for (const List<InternalCall>::Element *E = editor_custom_icalls.front(); E; E = E->next()) + ADD_INTERNAL_CALL(E->get()); + for (const List<InternalCall>::Element *E = method_icalls.front(); E; E = E->next()) + ADD_INTERNAL_CALL(E->get()); + +#undef ADD_INTERNAL_CALL + + cs_icalls_content.push_back(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); + + String internal_methods_file = path_join(core_dir, CS_CLASS_NATIVECALLS_EDITOR ".cs"); + + Error err = _save_file(internal_methods_file, cs_icalls_content); + if (err != OK) + return err; + + compile_items.push_back(internal_methods_file); + + String guid = CSharpProject::generate_editor_api_project(p_output_dir, p_core_dll_path, compile_items); + + solution.add_new_project(EDITOR_API_ASSEMBLY_NAME, guid); + + Error sln_error = solution.save(); + if (sln_error != OK) { + ERR_PRINT("Could not to save .NET solution."); + return sln_error; + } + + return OK; +} + +// TODO: there are constants that hide inherited members. must explicitly use `new` to avoid warnings +// e.g.: warning CS0108: 'SpriteBase3D.FLAG_MAX' hides inherited member 'GeometryInstance.FLAG_MAX'. Use the new keyword if hiding was intended. +Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const String &p_output_file) { + + int method_bind_count = 0; + + bool is_derived_type = itype.base_name.length(); + + if (is_derived_type && obj_types[itype.base_name].is_singleton && is_singleton_black_listed(itype.name)) + return ERR_SKIP; + + List<InternalCall> &custom_icalls = itype.api_type == ClassDB::API_EDITOR ? editor_custom_icalls : core_custom_icalls; + + if (verbose_output) + OS::get_singleton()->print(String("Generating " + itype.proxy_name + ".cs...\n").utf8()); + + String ctor_method(ICALL_PREFIX + itype.proxy_name + "_Ctor"); + + List<String> cs_file; + + cs_file.push_back("using System;\n"); // IntPtr + + if (itype.requires_collections) + cs_file.push_back("using System.Collections.Generic;\n"); // Dictionary + + cs_file.push_back("\nnamespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); + + const DocData::ClassDoc *class_doc = itype.class_doc; + + if (class_doc && class_doc->description.size()) { + cs_file.push_back(INDENT1 "/// <summary>\n"); + + Vector<String> description_lines = class_doc->description.split("\n"); + + for (int i = 0; i < description_lines.size(); i++) { + if (description_lines[i].size()) { + cs_file.push_back(INDENT1 "/// "); + cs_file.push_back(description_lines[i].strip_edges().xml_escape()); + cs_file.push_back("\n"); + } + } + + cs_file.push_back(INDENT1 "/// </summary>\n"); + } + + cs_file.push_back(INDENT1 "public "); + cs_file.push_back(itype.is_singleton ? "static class " : "class "); + cs_file.push_back(itype.proxy_name); + + if (itype.is_singleton || !itype.is_object_type) { + cs_file.push_back("\n"); + } else if (!is_derived_type) { + cs_file.push_back(" : IDisposable\n"); + } else if (obj_types.has(itype.base_name)) { + cs_file.push_back(" : "); + cs_file.push_back(obj_types[itype.base_name].proxy_name); + cs_file.push_back("\n"); + } else { + ERR_PRINTS("Base type ' " + itype.base_name + "' does not exist"); + return ERR_INVALID_DATA; + } + + cs_file.push_back(INDENT1 "{"); + + if (class_doc) { + + // Add constants + + for (int i = 0; i < class_doc->constants.size(); i++) { + const DocData::ConstantDoc &const_doc = class_doc->constants[i]; + + if (const_doc.description.size()) { + cs_file.push_back(MEMBER_BEGIN "/// <summary>\n"); + + Vector<String> description_lines = const_doc.description.split("\n"); + + for (int i = 0; i < description_lines.size(); i++) { + if (description_lines[i].size()) { + cs_file.push_back(INDENT2 "/// "); + cs_file.push_back(description_lines[i].strip_edges().xml_escape()); + cs_file.push_back("\n"); + } + } + + cs_file.push_back(INDENT2 "/// </summary>"); + } + + cs_file.push_back(MEMBER_BEGIN "public const int "); + cs_file.push_back(const_doc.name); + cs_file.push_back(" = "); + cs_file.push_back(const_doc.value); + cs_file.push_back(";"); + } + + if (class_doc->constants.size()) + cs_file.push_back("\n"); + + // Add properties + + const Vector<DocData::PropertyDoc> &properties = itype.class_doc->properties; + + for (int i = 0; i < properties.size(); i++) { + const DocData::PropertyDoc &prop_doc = properties[i]; + + const MethodInterface *setter = itype.find_method_by_name(prop_doc.setter); + + // Search it in base types too + const TypeInterface *current_type = &itype; + while (!setter && current_type->base_name.length()) { + Map<String, TypeInterface>::Element *base_match = obj_types.find(current_type->base_name); + ERR_FAIL_NULL_V(base_match, ERR_BUG); + current_type = &base_match->get(); + setter = current_type->find_method_by_name(prop_doc.setter); + } + + const MethodInterface *getter = itype.find_method_by_name(prop_doc.getter); + + // Search it in base types too + current_type = &itype; + while (!getter && current_type->base_name.length()) { + Map<String, TypeInterface>::Element *base_match = obj_types.find(current_type->base_name); + ERR_FAIL_NULL_V(base_match, ERR_BUG); + current_type = &base_match->get(); + getter = current_type->find_method_by_name(prop_doc.getter); + } + + ERR_FAIL_COND_V(!setter && !getter, ERR_BUG); + + bool is_valid = false; + int prop_index = ClassDB::get_property_index(itype.name, prop_doc.name, &is_valid); + ERR_FAIL_COND_V(!is_valid, ERR_BUG); + + if (setter) { + int setter_argc = prop_index != -1 ? 2 : 1; + ERR_FAIL_COND_V(setter->arguments.size() != setter_argc, ERR_BUG); + } + + if (getter) { + int getter_argc = prop_index != -1 ? 1 : 0; + ERR_FAIL_COND_V(getter->arguments.size() != getter_argc, ERR_BUG); + } + + if (getter && setter) { + ERR_FAIL_COND_V(getter->return_type != setter->arguments.back()->get().type, ERR_BUG); + } + + // Let's not trust PropertyDoc::type + String proptype_name = getter ? getter->return_type : setter->arguments.back()->get().type; + + const TypeInterface *prop_itype = _get_type_by_name_or_null(proptype_name); + if (!prop_itype) { + // Try with underscore prefix + prop_itype = _get_type_by_name_or_null("_" + proptype_name); + } + + ERR_FAIL_NULL_V(prop_itype, ERR_BUG); + + String prop_proxy_name = escape_csharp_keyword(snake_to_pascal_case(prop_doc.name)); + + // Prevent property and enclosing type from sharing the same name + if (prop_proxy_name == itype.proxy_name) { + if (verbose_output) { + WARN_PRINTS("Name of property `" + prop_proxy_name + "` is ambiguous with the name of its class `" + + itype.proxy_name + "`. Renaming property to `" + prop_proxy_name + "_`"); + } + + prop_proxy_name += "_"; + } + + if (prop_doc.description.size()) { + cs_file.push_back(MEMBER_BEGIN "/// <summary>\n"); + + Vector<String> description_lines = prop_doc.description.split("\n"); + + for (int i = 0; i < description_lines.size(); i++) { + if (description_lines[i].size()) { + cs_file.push_back(INDENT2 "/// "); + cs_file.push_back(description_lines[i].strip_edges().xml_escape()); + cs_file.push_back("\n"); + } + } + + cs_file.push_back(INDENT2 "/// </summary>"); + } + + cs_file.push_back(MEMBER_BEGIN "public "); + + if (itype.is_singleton) + cs_file.push_back("static "); + + cs_file.push_back(prop_itype->cs_type); + cs_file.push_back(" "); + cs_file.push_back(prop_proxy_name.replace("/", "__")); + cs_file.push_back("\n" INDENT2 OPEN_BLOCK); + + if (getter) { + cs_file.push_back(INDENT3 "get\n" OPEN_BLOCK_L3); + cs_file.push_back("return "); + cs_file.push_back(getter->proxy_name + "("); + if (prop_index != -1) + cs_file.push_back(itos(prop_index)); + cs_file.push_back(");\n" CLOSE_BLOCK_L3); + } + + if (setter) { + cs_file.push_back(INDENT3 "set\n" OPEN_BLOCK_L3); + cs_file.push_back(setter->proxy_name + "("); + if (prop_index != -1) + cs_file.push_back(itos(prop_index) + ", "); + cs_file.push_back("value);\n" CLOSE_BLOCK_L3); + } + + cs_file.push_back(CLOSE_BLOCK_L2); + } + + if (class_doc->properties.size()) + cs_file.push_back("\n"); + } + + if (!itype.is_object_type) { + cs_file.push_back(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \"" + itype.name + "\";\n"); + cs_file.push_back(MEMBER_BEGIN "private bool disposed = false;\n"); + cs_file.push_back(MEMBER_BEGIN "internal IntPtr " BINDINGS_PTR_FIELD ";\n"); + + cs_file.push_back(MEMBER_BEGIN "internal static IntPtr " CS_SMETHOD_GETINSTANCE "("); + cs_file.push_back(itype.proxy_name); + cs_file.push_back(" instance)\n" OPEN_BLOCK_L2 "return instance == null ? IntPtr.Zero : instance." BINDINGS_PTR_FIELD ";\n" CLOSE_BLOCK_L2); + + // Add Destructor + cs_file.push_back(MEMBER_BEGIN "~"); + cs_file.push_back(itype.proxy_name); + cs_file.push_back("()\n" OPEN_BLOCK_L2 "Dispose(false);\n" CLOSE_BLOCK_L2); + + // Add the Dispose from IDisposable + cs_file.push_back(MEMBER_BEGIN "public void Dispose()\n" OPEN_BLOCK_L2 "Dispose(true);\n" INDENT3 "GC.SuppressFinalize(this);\n" CLOSE_BLOCK_L2); + + // Add the virtual Dispose + cs_file.push_back(MEMBER_BEGIN "public virtual void Dispose(bool disposing)\n" OPEN_BLOCK_L2 + "if (disposed) return;\n" INDENT3 + "if (" BINDINGS_PTR_FIELD " != IntPtr.Zero)\n" OPEN_BLOCK_L3 "NativeCalls.godot_icall_"); + cs_file.push_back(itype.proxy_name); + cs_file.push_back("_Dtor(" BINDINGS_PTR_FIELD ");\n" INDENT5 BINDINGS_PTR_FIELD " = IntPtr.Zero;\n" CLOSE_BLOCK_L3 INDENT3 + "GC.SuppressFinalize(this);\n" INDENT3 "disposed = true;\n" CLOSE_BLOCK_L2); + + cs_file.push_back(MEMBER_BEGIN "internal "); + cs_file.push_back(itype.proxy_name); + cs_file.push_back("(IntPtr " BINDINGS_PTR_FIELD ")\n" OPEN_BLOCK_L2 "this." BINDINGS_PTR_FIELD " = " BINDINGS_PTR_FIELD ";\n" CLOSE_BLOCK_L2); + + cs_file.push_back(MEMBER_BEGIN "public bool HasValidHandle()\n" OPEN_BLOCK_L2 + "return " BINDINGS_PTR_FIELD " == IntPtr.Zero;\n" CLOSE_BLOCK_L2); + } else if (itype.is_singleton) { + // Add the type name and the singleton pointer as static fields + + cs_file.push_back(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \""); + cs_file.push_back(itype.name); + cs_file.push_back("\";\n"); + + cs_file.push_back(INDENT2 "internal static IntPtr " BINDINGS_PTR_FIELD " = "); + cs_file.push_back(itype.api_type == ClassDB::API_EDITOR ? CS_CLASS_NATIVECALLS_EDITOR : CS_CLASS_NATIVECALLS); + cs_file.push_back("." ICALL_PREFIX); + cs_file.push_back(itype.name); + cs_file.push_back(SINGLETON_ICALL_SUFFIX "();\n"); + } else { + // Add member fields + + cs_file.push_back(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \""); + cs_file.push_back(itype.name); + cs_file.push_back("\";\n"); + + // Only the base class stores the pointer to the native object + // This pointer is expected to be and must be of type Object* + if (!is_derived_type) { + cs_file.push_back(MEMBER_BEGIN "private bool disposed = false;\n"); + cs_file.push_back(INDENT2 "internal IntPtr " BINDINGS_PTR_FIELD ";\n"); + cs_file.push_back(INDENT2 "internal bool " CS_FIELD_MEMORYOWN ";\n"); + } + + // Add default constructor + if (itype.is_instantiable) { + cs_file.push_back(MEMBER_BEGIN "public "); + cs_file.push_back(itype.proxy_name); + cs_file.push_back("() : this("); + cs_file.push_back(itype.memory_own ? "true" : "false"); + + // The default constructor may also be called by the engine when instancing existing native objects + // The engine will initialize the pointer field of the managed side before calling the constructor + // This is why we only allocate a new native object from the constructor if the pointer field is not set + cs_file.push_back(")\n" OPEN_BLOCK_L2 "if (" BINDINGS_PTR_FIELD " == IntPtr.Zero)\n" INDENT4 BINDINGS_PTR_FIELD " = "); + cs_file.push_back(itype.api_type == ClassDB::API_EDITOR ? CS_CLASS_NATIVECALLS_EDITOR : CS_CLASS_NATIVECALLS); + cs_file.push_back("." + ctor_method); + cs_file.push_back("(this);\n" CLOSE_BLOCK_L2); + } else { + // Hide the constructor + cs_file.push_back(MEMBER_BEGIN "internal "); + cs_file.push_back(itype.proxy_name); + cs_file.push_back("() {}\n"); + } + + // Add.. em.. trick constructor. Sort of. + cs_file.push_back(MEMBER_BEGIN "internal "); + cs_file.push_back(itype.proxy_name); + if (is_derived_type) { + cs_file.push_back("(bool " CS_FIELD_MEMORYOWN ") : base(" CS_FIELD_MEMORYOWN ") {}\n"); + } else { + cs_file.push_back("(bool " CS_FIELD_MEMORYOWN ")\n" OPEN_BLOCK_L2 + "this." CS_FIELD_MEMORYOWN " = " CS_FIELD_MEMORYOWN ";\n" CLOSE_BLOCK_L2); + } + + // Add methods + + if (!is_derived_type) { + cs_file.push_back(MEMBER_BEGIN "public bool HasValidHandle()\n" OPEN_BLOCK_L2 + "return " BINDINGS_PTR_FIELD " == IntPtr.Zero;\n" CLOSE_BLOCK_L2); + + cs_file.push_back(MEMBER_BEGIN "internal static IntPtr " CS_SMETHOD_GETINSTANCE "(Object instance)\n" OPEN_BLOCK_L2 + "return instance == null ? IntPtr.Zero : instance." BINDINGS_PTR_FIELD ";\n" CLOSE_BLOCK_L2); + } + + if (!is_derived_type) { + // Add destructor + cs_file.push_back(MEMBER_BEGIN "~"); + cs_file.push_back(itype.proxy_name); + cs_file.push_back("()\n" OPEN_BLOCK_L2 "Dispose(false);\n" CLOSE_BLOCK_L2); + + // Add the Dispose from IDisposable + cs_file.push_back(MEMBER_BEGIN "public void Dispose()\n" OPEN_BLOCK_L2 "Dispose(true);\n" INDENT3 "GC.SuppressFinalize(this);\n" CLOSE_BLOCK_L2); + + // Add the virtual Dispose + cs_file.push_back(MEMBER_BEGIN "public virtual void Dispose(bool disposing)\n" OPEN_BLOCK_L2 + "if (disposed) return;\n" INDENT3 + "if (" BINDINGS_PTR_FIELD " != IntPtr.Zero)\n" OPEN_BLOCK_L3 + "if (" CS_FIELD_MEMORYOWN ")\n" OPEN_BLOCK_L4 CS_FIELD_MEMORYOWN + " = false;\n" INDENT5 CS_CLASS_NATIVECALLS "." ICALL_OBJECT_DTOR + "(" BINDINGS_PTR_FIELD ");\n" INDENT5 BINDINGS_PTR_FIELD + " = IntPtr.Zero;\n" CLOSE_BLOCK_L4 CLOSE_BLOCK_L3 INDENT3 + "GC.SuppressFinalize(this);\n" INDENT3 "disposed = true;\n" CLOSE_BLOCK_L2); + + Map<String, TypeInterface>::Element *array_itype = builtin_types.find("Array"); + + if (!array_itype) { + ERR_PRINT("BUG: Array type interface not found!"); + return ERR_BUG; + } + + cs_file.push_back(MEMBER_BEGIN "private void _AwaitedSignalCallback("); + cs_file.push_back(array_itype->get().cs_type); + cs_file.push_back(" args, SignalAwaiter awaiter)\n" OPEN_BLOCK_L2 "awaiter.SignalCallback(args);\n" CLOSE_BLOCK_L2); + + Map<String, TypeInterface>::Element *object_itype = obj_types.find("Object"); + + if (!object_itype) { + ERR_PRINT("BUG: Array type interface not found!"); + return ERR_BUG; + } + + cs_file.push_back(MEMBER_BEGIN "public " CS_CLASS_SIGNALAWAITER " ToSignal("); + cs_file.push_back(object_itype->get().cs_type); + cs_file.push_back(" source, string signal)\n" OPEN_BLOCK_L2 + "return new " CS_CLASS_SIGNALAWAITER "(source, signal, this);\n" CLOSE_BLOCK_L2); + } + } + + Map<String, String>::Element *extra_member = extra_members.find(itype.name); + if (extra_member) + cs_file.push_back(extra_member->get()); + + for (const List<MethodInterface>::Element *E = itype.methods.front(); E; E = E->next()) { + const MethodInterface &imethod = E->get(); + + const TypeInterface *return_type = _get_type_by_name_or_placeholder(imethod.return_type); + + String method_bind_field = "method_bind_" + itos(method_bind_count); + + String icall_params = method_bind_field + ", " + sformat(itype.cs_in, "this"); + String arguments_sig; + String cs_in_statements; + + List<String> default_args_doc; + + // Retrieve information from the arguments + for (const List<ArgumentInterface>::Element *F = imethod.arguments.front(); F; F = F->next()) { + const ArgumentInterface &iarg = F->get(); + const TypeInterface *arg_type = _get_type_by_name_or_placeholder(iarg.type); + + // Add the current arguments to the signature + // If the argument has a default value which is not a constant, we will make it Nullable + { + if (F != imethod.arguments.front()) + arguments_sig += ", "; + + if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) + arguments_sig += "Nullable<"; + + arguments_sig += arg_type->cs_type; + + if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) + arguments_sig += "> "; + else + arguments_sig += " "; + + arguments_sig += iarg.name; + + if (iarg.default_argument.size()) { + if (iarg.def_param_mode != ArgumentInterface::CONSTANT) + arguments_sig += " = null"; + else + arguments_sig += " = " + sformat(iarg.default_argument, arg_type->cs_type); + } + } + + icall_params += ", "; + + if (iarg.default_argument.size() && iarg.def_param_mode != ArgumentInterface::CONSTANT) { + // The default value of an argument must be constant. Otherwise we make it Nullable and do the following: + // Type arg_in = arg.HasValue ? arg.Value : <non-const default value>; + String arg_in = iarg.name; + arg_in += "_in"; + + cs_in_statements += arg_type->cs_type; + cs_in_statements += " "; + cs_in_statements += arg_in; + cs_in_statements += " = "; + cs_in_statements += iarg.name; + + if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) + cs_in_statements += ".HasValue ? "; + else + cs_in_statements += " != null ? "; + + cs_in_statements += iarg.name; + + if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) + cs_in_statements += ".Value : "; + else + cs_in_statements += " : "; + + String def_arg = sformat(iarg.default_argument, arg_type->cs_type); + + cs_in_statements += def_arg; + cs_in_statements += ";\n" INDENT3; + + icall_params += arg_type->cs_in.empty() ? arg_in : sformat(arg_type->cs_in, arg_in); + + default_args_doc.push_back(INDENT2 "/// <param name=\"" + iarg.name + "\">If the param is null, then the default value is " + def_arg + "</param>\n"); + } else { + icall_params += arg_type->cs_in.empty() ? iarg.name : sformat(arg_type->cs_in, iarg.name); + } + } + + // Generate method + { + if (!imethod.is_virtual && !imethod.requires_object_call) { + cs_file.push_back(MEMBER_BEGIN "private "); + cs_file.push_back(itype.is_singleton ? "static IntPtr " : "IntPtr "); + cs_file.push_back(method_bind_field + " = " CS_CLASS_NATIVECALLS "." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \""); + cs_file.push_back(imethod.name); + cs_file.push_back("\");\n"); + } + + if (imethod.method_doc && imethod.method_doc->description.size()) { + cs_file.push_back(MEMBER_BEGIN "/// <summary>\n"); + + Vector<String> description_lines = imethod.method_doc->description.split("\n"); + + for (int i = 0; i < description_lines.size(); i++) { + if (description_lines[i].size()) { + cs_file.push_back(INDENT2 "/// "); + cs_file.push_back(description_lines[i].strip_edges().xml_escape()); + cs_file.push_back("\n"); + } + } + + for (List<String>::Element *E = default_args_doc.front(); E; E = E->next()) { + cs_file.push_back(E->get().xml_escape()); + } + + cs_file.push_back(INDENT2 "/// </summary>"); + } + + if (!imethod.is_internal) { + cs_file.push_back(MEMBER_BEGIN "[GodotMethod(\""); + cs_file.push_back(imethod.name); + cs_file.push_back("\")]"); + } + + cs_file.push_back(MEMBER_BEGIN); + cs_file.push_back(imethod.is_internal ? "internal " : "public "); + + if (itype.is_singleton) { + cs_file.push_back("static "); + } else if (imethod.is_virtual) { + cs_file.push_back("virtual "); + } + + cs_file.push_back(return_type->cs_type + " "); + cs_file.push_back(imethod.proxy_name + "("); + cs_file.push_back(arguments_sig + ")\n" OPEN_BLOCK_L2); + + if (imethod.is_virtual) { + // Godot virtual method must be overridden, therefore we return a default value by default. + + if (return_type->name == "void") { + cs_file.push_back("return;\n" CLOSE_BLOCK_L2); + } else { + cs_file.push_back("return default("); + cs_file.push_back(return_type->cs_type); + cs_file.push_back(");\n" CLOSE_BLOCK_L2); + } + + continue; + } + + if (imethod.requires_object_call) { + // Fallback to Godot's object.Call(string, params) + + cs_file.push_back(CS_METHOD_CALL "(\""); + cs_file.push_back(imethod.name); + cs_file.push_back("\""); + + for (const List<ArgumentInterface>::Element *F = imethod.arguments.front(); F; F = F->next()) { + cs_file.push_back(", "); + cs_file.push_back(F->get().name); + } + + cs_file.push_back(");\n" CLOSE_BLOCK_L2); + + continue; + } + + const Map<const MethodInterface *, const InternalCall *>::Element *match = method_icalls_map.find(&E->get()); + ERR_FAIL_NULL_V(match, ERR_BUG); + + const InternalCall *im_icall = match->value(); + + String im_call = im_icall->editor_only ? CS_CLASS_NATIVECALLS_EDITOR : CS_CLASS_NATIVECALLS; + im_call += "." + im_icall->name + "(" + icall_params + ");\n"; + + if (imethod.arguments.size()) + cs_file.push_back(cs_in_statements); + + if (return_type->name == "void") { + cs_file.push_back(im_call); + } else if (return_type->cs_out.empty()) { + cs_file.push_back("return " + im_call); + } else { + cs_file.push_back(return_type->im_type_out); + cs_file.push_back(" " LOCAL_RET " = "); + cs_file.push_back(im_call); + cs_file.push_back(INDENT3); + cs_file.push_back(sformat(return_type->cs_out, LOCAL_RET) + "\n"); + } + + cs_file.push_back(CLOSE_BLOCK_L2); + } + + method_bind_count++; + } + + if (itype.is_singleton) { + InternalCall singleton_icall = InternalCall(itype.api_type, ICALL_PREFIX + itype.name + SINGLETON_ICALL_SUFFIX, "IntPtr"); + + if (!find_icall_by_name(singleton_icall.name, custom_icalls)) + custom_icalls.push_back(singleton_icall); + } + + if (itype.is_instantiable) { + InternalCall ctor_icall = InternalCall(itype.api_type, ctor_method, "IntPtr", itype.proxy_name + " obj"); + + if (!find_icall_by_name(ctor_icall.name, custom_icalls)) + custom_icalls.push_back(ctor_icall); + } + + cs_file.push_back(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); + + return _save_file(p_output_file, cs_file); +} + +Error BindingsGenerator::generate_glue(const String &p_output_dir) { + + verbose_output = true; + + bool dir_exists = DirAccess::exists(p_output_dir); + ERR_EXPLAIN("The output directory does not exist."); + ERR_FAIL_COND_V(!dir_exists, ERR_FILE_BAD_PATH); + + List<String> cpp_file; + + cpp_file.push_back("#include \"" GLUE_HEADER_FILE "\"\n" + "\n"); + + List<const InternalCall *> generated_icall_funcs; + + for (Map<String, TypeInterface>::Element *type_elem = obj_types.front(); type_elem; type_elem = type_elem->next()) { + const TypeInterface &itype = type_elem->get(); + + if (itype.base_name.length() && obj_types[itype.base_name].is_singleton && is_singleton_black_listed(itype.name)) + continue; + + List<InternalCall> &custom_icalls = itype.api_type == ClassDB::API_EDITOR ? editor_custom_icalls : core_custom_icalls; + + OS::get_singleton()->print(String("Generating " + itype.name + "...\n").utf8()); + + String ctor_method(ICALL_PREFIX + itype.proxy_name + "_Ctor"); + + for (const List<MethodInterface>::Element *E = itype.methods.front(); E; E = E->next()) { + const MethodInterface &imethod = E->get(); + + if (imethod.is_virtual) + continue; + + bool ret_void = imethod.return_type == "void"; + + const TypeInterface *return_type = _get_type_by_name_or_placeholder(imethod.return_type); + + String argc_str = itos(imethod.arguments.size()); + + String c_func_sig = "MethodBind* " CS_PARAM_METHODBIND ", " + itype.c_type_in + " " CS_PARAM_INSTANCE; + String c_in_statements; + String c_args_var_content; + + // Get arguments information + int i = 0; + for (const List<ArgumentInterface>::Element *F = imethod.arguments.front(); F; F = F->next()) { + const ArgumentInterface &iarg = F->get(); + const TypeInterface *arg_type = _get_type_by_name_or_placeholder(iarg.type); + + String c_param_name = "arg" + itos(i + 1); + + if (imethod.is_vararg) { + if (i < imethod.arguments.size() - 1) { + c_in_statements += sformat(arg_type->c_in.size() ? arg_type->c_in : TypeInterface::DEFAULT_VARARG_C_IN, "Variant", c_param_name); + c_in_statements += "\t" C_LOCAL_PTRCALL_ARGS ".set(0, "; + c_in_statements += sformat("&%s_in", c_param_name); + c_in_statements += ");\n"; + } + } else { + if (i > 0) + c_args_var_content += ", "; + if (arg_type->c_in.size()) + c_in_statements += sformat(arg_type->c_in, arg_type->c_type, c_param_name); + c_args_var_content += sformat(arg_type->c_arg_in, c_param_name); + } + + c_func_sig += ", "; + c_func_sig += arg_type->c_type_in; + c_func_sig += " "; + c_func_sig += c_param_name; + + i++; + } + + const Map<const MethodInterface *, const InternalCall *>::Element *match = method_icalls_map.find(&E->get()); + ERR_FAIL_NULL_V(match, ERR_BUG); + + const InternalCall *im_icall = match->value(); + String icall_method = im_icall->name; + + if (!generated_icall_funcs.find(im_icall)) { + generated_icall_funcs.push_back(im_icall); + + if (im_icall->editor_only) + cpp_file.push_back("#ifdef TOOLS_ENABLED\n"); + + // Generate icall function + + cpp_file.push_back(ret_void ? "void " : return_type->c_type_out + " "); + cpp_file.push_back(icall_method); + cpp_file.push_back("("); + cpp_file.push_back(c_func_sig); + cpp_file.push_back(") " OPEN_BLOCK); + + String fail_ret = ret_void ? "" : ", " + (return_type->c_type_out.ends_with("*") ? "NULL" : return_type->c_type_out + "()"); + + if (!ret_void) { + String ptrcall_return_type; + String initialization; + + if (return_type->is_object_type) { + ptrcall_return_type = return_type->is_reference ? "Ref<Reference>" : return_type->c_type; + initialization = return_type->is_reference ? "" : " = NULL"; + } else { + ptrcall_return_type = return_type->c_type; + } + + cpp_file.push_back("\t" + ptrcall_return_type); + cpp_file.push_back(" " LOCAL_RET); + cpp_file.push_back(initialization + ";\n"); + cpp_file.push_back("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE); + cpp_file.push_back(fail_ret); + cpp_file.push_back(");\n"); + } else { + cpp_file.push_back("\tERR_FAIL_NULL(" CS_PARAM_INSTANCE ");\n"); + } + + if (imethod.arguments.size()) { + if (imethod.is_vararg) { + String err_fail_macro = ret_void ? "ERR_FAIL_COND" : "ERR_FAIL_COND_V"; + String vararg_arg = "arg" + argc_str; + String real_argc_str = itos(imethod.arguments.size() - 1); // Arguments count without vararg + + cpp_file.push_back("\tVector<Variant> varargs;\n" + "\tint vararg_length = mono_array_length("); + cpp_file.push_back(vararg_arg); + cpp_file.push_back(");\n\tint total_length = "); + cpp_file.push_back(real_argc_str); + cpp_file.push_back(" + vararg_length;\n\t"); + cpp_file.push_back(err_fail_macro); + cpp_file.push_back("(varargs.resize(vararg_length) != OK"); + cpp_file.push_back(fail_ret); + cpp_file.push_back(");\n\tVector<Variant*> " C_LOCAL_PTRCALL_ARGS ";\n\t"); + cpp_file.push_back(err_fail_macro); + cpp_file.push_back("(call_args.resize(total_length) != OK"); + cpp_file.push_back(fail_ret); + cpp_file.push_back(");\n"); + cpp_file.push_back(c_in_statements); + cpp_file.push_back("\tfor (int i = 0; i < vararg_length; i++) " OPEN_BLOCK + "\t\tMonoObject* elem = mono_array_get("); + cpp_file.push_back(vararg_arg); + cpp_file.push_back(", MonoObject*, i);\n" + "\t\tvarargs.set(i, GDMonoMarshal::mono_object_to_variant(elem));\n" + "\t\t" C_LOCAL_PTRCALL_ARGS ".set("); + cpp_file.push_back(real_argc_str); + cpp_file.push_back(" + i, &varargs[i]);\n\t" CLOSE_BLOCK); + } else { + cpp_file.push_back(c_in_statements); + cpp_file.push_back("\tconst void* " C_LOCAL_PTRCALL_ARGS "["); + cpp_file.push_back(argc_str + "] = { "); + cpp_file.push_back(c_args_var_content + " };\n"); + } + } + + if (imethod.is_vararg) { + cpp_file.push_back("\tVariant::CallError vcall_error;\n\t"); + + if (!ret_void) + cpp_file.push_back(LOCAL_RET " = "); + + cpp_file.push_back(CS_PARAM_METHODBIND "->call(" CS_PARAM_INSTANCE ", "); + cpp_file.push_back(imethod.arguments.size() ? "(const Variant**)" C_LOCAL_PTRCALL_ARGS ".ptr()" : "NULL"); + cpp_file.push_back(", total_length, vcall_error);\n"); + } else { + cpp_file.push_back("\t" CS_PARAM_METHODBIND "->ptrcall(" CS_PARAM_INSTANCE ", "); + cpp_file.push_back(imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ", " : "NULL, "); + cpp_file.push_back(!ret_void ? "&" LOCAL_RET ");\n" : "NULL);\n"); + } + + if (!ret_void) { + if (return_type->c_out.empty()) + cpp_file.push_back("\treturn " LOCAL_RET ";\n"); + else + cpp_file.push_back(sformat(return_type->c_out, return_type->c_type_out, LOCAL_RET, return_type->name)); + } + + cpp_file.push_back(CLOSE_BLOCK "\n"); + + if (im_icall->editor_only) + cpp_file.push_back("#endif // TOOLS_ENABLED\n"); + } + } + + if (itype.is_singleton) { + String singleton_icall_name = ICALL_PREFIX + itype.name + SINGLETON_ICALL_SUFFIX; + InternalCall singleton_icall = InternalCall(itype.api_type, singleton_icall_name, "IntPtr"); + + if (!find_icall_by_name(singleton_icall.name, custom_icalls)) + custom_icalls.push_back(singleton_icall); + + cpp_file.push_back("Object* "); + cpp_file.push_back(singleton_icall_name); + cpp_file.push_back("() " OPEN_BLOCK "\treturn ProjectSettings::get_singleton()->get_singleton_object(\""); + cpp_file.push_back(itype.proxy_name); + cpp_file.push_back("\");\n" CLOSE_BLOCK "\n"); + } + + if (itype.is_instantiable) { + InternalCall ctor_icall = InternalCall(itype.api_type, ctor_method, "IntPtr", itype.proxy_name + " obj"); + + if (!find_icall_by_name(ctor_icall.name, custom_icalls)) + custom_icalls.push_back(ctor_icall); + + cpp_file.push_back("Object* "); + cpp_file.push_back(ctor_method); + cpp_file.push_back("(MonoObject* obj) " OPEN_BLOCK + "\t" C_MACRO_OBJECT_CONSTRUCT "(instance, \""); + cpp_file.push_back(itype.name); + cpp_file.push_back("\");\n" + "\t" C_METHOD_TIE_MANAGED_TO_UNMANAGED "(obj, instance);\n" + "\treturn instance;\n" CLOSE_BLOCK "\n"); + } + } + + cpp_file.push_back("namespace GodotSharpBindings\n" OPEN_BLOCK); + cpp_file.push_back("uint64_t get_core_api_hash() { return "); + cpp_file.push_back(itos(GDMono::get_singleton()->get_api_core_hash()) + "; }\n"); + cpp_file.push_back("#ifdef TOOLS_ENABLED\n" + "uint64_t get_editor_api_hash() { return "); + cpp_file.push_back(itos(GDMono::get_singleton()->get_api_editor_hash()) + + "; }\n#endif // TOOLS_ENABLED\n"); + cpp_file.push_back("void register_generated_icalls() " OPEN_BLOCK); + +#define ADD_INTERNAL_CALL_REGISTRATION(m_icall) \ + { \ + cpp_file.push_back("\tmono_add_internal_call("); \ + cpp_file.push_back("\"" BINDINGS_NAMESPACE "."); \ + cpp_file.push_back(m_icall.editor_only ? CS_CLASS_NATIVECALLS_EDITOR : CS_CLASS_NATIVECALLS); \ + cpp_file.push_back("::"); \ + cpp_file.push_back(m_icall.name); \ + cpp_file.push_back("\", (void*)"); \ + cpp_file.push_back(m_icall.name); \ + cpp_file.push_back(");\n"); \ + } + + bool tools_sequence = false; + for (const List<InternalCall>::Element *E = core_custom_icalls.front(); E; E = E->next()) { + + if (tools_sequence) { + if (!E->get().editor_only) { + tools_sequence = false; + cpp_file.push_back("#endif\n"); + } + } else { + if (E->get().editor_only) { + cpp_file.push_back("#ifdef TOOLS_ENABLED\n"); + tools_sequence = true; + } + } + + ADD_INTERNAL_CALL_REGISTRATION(E->get()); + } + + if (tools_sequence) { + tools_sequence = false; + cpp_file.push_back("#endif\n"); + } + + cpp_file.push_back("#ifdef TOOLS_ENABLED\n"); + for (const List<InternalCall>::Element *E = editor_custom_icalls.front(); E; E = E->next()) + ADD_INTERNAL_CALL_REGISTRATION(E->get()); + cpp_file.push_back("#endif // TOOLS_ENABLED\n"); + + for (const List<InternalCall>::Element *E = method_icalls.front(); E; E = E->next()) { + + if (tools_sequence) { + if (!E->get().editor_only) { + tools_sequence = false; + cpp_file.push_back("#endif\n"); + } + } else { + if (E->get().editor_only) { + cpp_file.push_back("#ifdef TOOLS_ENABLED\n"); + tools_sequence = true; + } + } + + ADD_INTERNAL_CALL_REGISTRATION(E->get()); + } + + if (tools_sequence) { + tools_sequence = false; + cpp_file.push_back("#endif\n"); + } + +#undef ADD_INTERNAL_CALL_REGISTRATION + + cpp_file.push_back(CLOSE_BLOCK "}\n"); + + return _save_file(path_join(p_output_dir, "mono_glue.gen.cpp"), cpp_file); +} + +Error BindingsGenerator::_save_file(const String &p_path, const List<String> &p_content) { + + FileAccessRef file = FileAccess::open(p_path, FileAccess::WRITE); + + ERR_FAIL_COND_V(!file, ERR_FILE_CANT_WRITE); + + for (const List<String>::Element *E = p_content.front(); E; E = E->next()) { + file->store_string(E->get()); + } + + file->close(); + + return OK; +} + +const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_by_name_or_null(const String &p_name) { + + const Map<String, TypeInterface>::Element *match = builtin_types.find(p_name); + + if (match) + return &match->get(); + + match = obj_types.find(p_name); + + if (match) + return &match->get(); + + return NULL; +} + +const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_by_name_or_placeholder(const String &p_name) { + + const TypeInterface *found = _get_type_by_name_or_null(p_name); + + if (found) + return found; + + ERR_PRINTS(String() + "Type not found. Creating placeholder: " + p_name); + + const Map<String, TypeInterface>::Element *match = placeholder_types.find(p_name); + + if (match) + return &match->get(); + + TypeInterface placeholder; + TypeInterface::create_placeholder_type(placeholder, p_name); + + return &placeholder_types.insert(placeholder.name, placeholder)->get(); +} + +void BindingsGenerator::_populate_object_type_interfaces() { + + obj_types.clear(); + + List<StringName> class_list; + ClassDB::get_class_list(&class_list); + class_list.sort_custom<StringName::AlphCompare>(); + + StringName refclass_name = String("Reference"); + + while (class_list.size()) { + StringName type_cname = class_list.front()->get(); + + ClassDB::APIType api_type = ClassDB::get_api_type(type_cname); + + if (api_type == ClassDB::API_NONE) { + class_list.pop_front(); + continue; + } + + TypeInterface itype = TypeInterface::create_object_type(type_cname, api_type); + + itype.base_name = ClassDB::get_parent_class(type_cname); + itype.is_singleton = ProjectSettings::get_singleton()->has_singleton(itype.proxy_name); + itype.is_instantiable = ClassDB::can_instance(type_cname) && !itype.is_singleton; + itype.is_reference = ClassDB::is_parent_class(type_cname, refclass_name); + itype.memory_own = itype.is_reference; + + itype.c_out = "\treturn "; + itype.c_out += C_METHOD_UNMANAGED_GET_MANAGED; + itype.c_out += itype.is_reference ? "(%1.ptr());\n" : "(%1);\n"; + + itype.cs_in = itype.is_singleton ? BINDINGS_PTR_FIELD : "Object." CS_SMETHOD_GETINSTANCE "(%0)"; + + itype.c_type = "Object*"; + itype.c_type_in = itype.c_type; + itype.c_type_out = "MonoObject*"; + itype.cs_type = itype.proxy_name; + itype.im_type_in = "IntPtr"; + itype.im_type_out = itype.proxy_name; + + List<MethodInfo> virtual_method_list; + ClassDB::get_virtual_methods(type_cname, &virtual_method_list, true); + + List<MethodInfo> method_list; + ClassDB::get_method_list(type_cname, &method_list, true); + method_list.sort(); + + for (List<MethodInfo>::Element *E = method_list.front(); E; E = E->next()) { + const MethodInfo &method_info = E->get(); + + int argc = method_info.arguments.size(); + + if (method_info.name.empty()) + continue; + + MethodInterface imethod; + imethod.name = method_info.name; + + if (method_info.flags & METHOD_FLAG_VIRTUAL) + imethod.is_virtual = true; + + PropertyInfo return_info = method_info.return_val; + + MethodBind *m = imethod.is_virtual ? NULL : ClassDB::get_method(type_cname, method_info.name); + + imethod.is_vararg = m && m->is_vararg(); + + if (!m && !imethod.is_virtual) { + if (virtual_method_list.find(method_info)) { + // A virtual method without the virtual flag. This is a special case. + + // This type of method can only be found in Object derived types. + ERR_FAIL_COND(!itype.is_object_type); + + // There is no method bind, so let's fallback to Godot's object.Call(string, params) + imethod.requires_object_call = true; + + // The method Object.free is registered as a virtual method, but without the virtual flag. + // This is because this method is not supposed to be overridden, but called. + // We assume the return type is void. + imethod.return_type = "void"; + + // Actually, more methods like this may be added in the future, + // which could actually will return something differnet. + // Let's put this to notify us if that ever happens. + if (itype.name != "Object" || imethod.name != "free") { + WARN_PRINTS("Notification: New unexpected virtual non-overridable method found.\n" + "We only expected Object.free, but found " + + itype.name + "." + imethod.name); + } + } else { + ERR_PRINTS("Missing MethodBind for non-virtual method: " + itype.name + "." + imethod.name); + } + } else if (return_info.type == Variant::INT && return_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + //imethod.return_type = return_info.class_name; + imethod.return_type = "int"; + } else if (return_info.class_name != StringName()) { + imethod.return_type = return_info.class_name; + } else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { + imethod.return_type = return_info.hint_string; + } else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { + imethod.return_type = "Variant"; + } else if (return_info.type == Variant::NIL) { + imethod.return_type = "void"; + } else { + imethod.return_type = Variant::get_type_name(return_info.type); + } + + if (!itype.requires_collections && imethod.return_type == "Dictionary") + itype.requires_collections = true; + + for (int i = 0; i < argc; i++) { + PropertyInfo arginfo = method_info.arguments[i]; + + ArgumentInterface iarg; + iarg.name = arginfo.name; + + if (arginfo.type == Variant::INT && arginfo.usage & PROPERTY_USAGE_CLASS_IS_ENUM) { + //iarg.type = arginfo.class_name; + iarg.type = "int"; + } else if (arginfo.class_name != StringName()) { + iarg.type = arginfo.class_name; + } else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { + iarg.type = arginfo.hint_string; + } else if (arginfo.type == Variant::NIL) { + iarg.type = "Variant"; + } else { + iarg.type = Variant::get_type_name(arginfo.type); + } + + iarg.name = escape_csharp_keyword(snake_to_camel_case(iarg.name)); + + if (!itype.requires_collections && iarg.type == "Dictionary") + itype.requires_collections = true; + + if (m && m->has_default_argument(i)) { + _default_argument_from_variant(m->get_default_argument(i), iarg); + } + + imethod.add_argument(iarg); + } + + if (imethod.is_vararg) { + ArgumentInterface ivararg; + ivararg.type = "VarArg"; + ivararg.name = "@args"; + imethod.add_argument(ivararg); + } + + imethod.proxy_name = escape_csharp_keyword(snake_to_pascal_case(imethod.name)); + + // Prevent naming the property and its enclosing type from sharing the same name + if (imethod.proxy_name == itype.proxy_name) { + if (verbose_output) { + WARN_PRINTS("Name of method `" + imethod.proxy_name + "` is ambiguous with the name of its class `" + + itype.proxy_name + "`. Renaming method to `" + imethod.proxy_name + "_`"); + } + + imethod.proxy_name += "_"; + } + + if (itype.class_doc) { + for (int i = 0; i < itype.class_doc->methods.size(); i++) { + if (itype.class_doc->methods[i].name == imethod.name) { + imethod.method_doc = &itype.class_doc->methods[i]; + break; + } + } + } + + if (!imethod.is_virtual && imethod.name[0] == '_') { + const Vector<DocData::PropertyDoc> &properties = itype.class_doc->properties; + + for (int i = 0; i < properties.size(); i++) { + const DocData::PropertyDoc &prop_doc = properties[i]; + + if (prop_doc.getter == imethod.name || prop_doc.setter == imethod.name) { + imethod.is_internal = true; + itype.methods.push_back(imethod); + break; + } + } + } else { + itype.methods.push_back(imethod); + } + } + + obj_types.insert(itype.name, itype); + + class_list.pop_front(); + } +} + +void BindingsGenerator::_default_argument_from_variant(const Variant &p_val, ArgumentInterface &r_iarg) { + + r_iarg.default_argument = p_val; + + switch (p_val.get_type()) { + case Variant::NIL: + if (ClassDB::class_exists(r_iarg.type)) { + // Object type + r_iarg.default_argument = "null"; + } else { + // Variant + r_iarg.default_argument = "null"; + } + break; + // Atomic types + case Variant::BOOL: + r_iarg.default_argument = bool(p_val) ? "true" : "false"; + break; + case Variant::INT: + break; // Keep it + case Variant::REAL: +#ifndef REAL_T_IS_DOUBLE + r_iarg.default_argument += "f"; +#endif + break; + case Variant::STRING: + case Variant::NODE_PATH: + r_iarg.default_argument = "\"" + r_iarg.default_argument + "\""; + break; + case Variant::TRANSFORM: + if (p_val.operator Transform() == Transform()) + r_iarg.default_argument.clear(); + r_iarg.default_argument = "new %s(" + r_iarg.default_argument + ")"; + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; + break; + case Variant::PLANE: + case Variant::RECT3: + case Variant::COLOR: + r_iarg.default_argument = "new Color(1, 1, 1, 1)"; + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; + break; + case Variant::VECTOR2: + case Variant::RECT2: + case Variant::VECTOR3: + r_iarg.default_argument = "new %s" + r_iarg.default_argument; + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; + break; + case Variant::OBJECT: + if (p_val.is_zero()) { + r_iarg.default_argument = "null"; + break; + } + case Variant::DICTIONARY: + case Variant::_RID: + r_iarg.default_argument = "new %s()"; + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF; + break; + case Variant::ARRAY: + case Variant::POOL_BYTE_ARRAY: + case Variant::POOL_INT_ARRAY: + case Variant::POOL_REAL_ARRAY: + case Variant::POOL_STRING_ARRAY: + case Variant::POOL_VECTOR2_ARRAY: + case Variant::POOL_VECTOR3_ARRAY: + case Variant::POOL_COLOR_ARRAY: + r_iarg.default_argument = "new %s {}"; + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF; + break; + case Variant::TRANSFORM2D: + case Variant::BASIS: + case Variant::QUAT: + r_iarg.default_argument = Variant::get_type_name(p_val.get_type()) + ".Identity"; + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; + break; + default: {} + } + + if (r_iarg.def_param_mode == ArgumentInterface::CONSTANT && r_iarg.type == "Variant" && r_iarg.default_argument != "null") + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF; +} + +void BindingsGenerator::_populate_builtin_type_interfaces() { + + builtin_types.clear(); + + TypeInterface itype; + +#define INSERT_STRUCT_TYPE(m_type, m_type_in) \ + { \ + itype = TypeInterface::create_value_type(#m_type); \ + itype.c_in = "\tMARSHALLED_IN(" #m_type ", %1, %1_in);\n"; \ + itype.c_out = "\tMARSHALLED_OUT(" #m_type ", %1, ret_out)\n" \ + "\treturn mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(%2), ret_out);\n"; \ + itype.c_arg_in = "&%s_in"; \ + itype.c_type_in = m_type_in; \ + itype.cs_in = "ref %s"; \ + itype.cs_out = "return (" #m_type ")%0;"; \ + itype.im_type_out = "object"; \ + builtin_types.insert(#m_type, itype); \ + } + + INSERT_STRUCT_TYPE(Vector2, "real_t*") + INSERT_STRUCT_TYPE(Rect2, "real_t*") + INSERT_STRUCT_TYPE(Transform2D, "real_t*") + INSERT_STRUCT_TYPE(Vector3, "real_t*") + INSERT_STRUCT_TYPE(Basis, "real_t*") + INSERT_STRUCT_TYPE(Quat, "real_t*") + INSERT_STRUCT_TYPE(Transform, "real_t*") + INSERT_STRUCT_TYPE(Rect3, "real_t*") + INSERT_STRUCT_TYPE(Color, "real_t*") + INSERT_STRUCT_TYPE(Plane, "real_t*") + +#undef INSERT_STRUCT_TYPE + +#define INSERT_PRIMITIVE_TYPE(m_type) \ + { \ + itype = TypeInterface::create_value_type(#m_type); \ + itype.c_arg_in = "&%s"; \ + itype.c_type_in = #m_type; \ + itype.c_type_out = #m_type; \ + itype.im_type_in = #m_type; \ + itype.im_type_out = #m_type; \ + builtin_types.insert(#m_type, itype); \ + } + + INSERT_PRIMITIVE_TYPE(bool) + //INSERT_PRIMITIVE_TYPE(int) + + // int + itype = TypeInterface::create_value_type("int"); + itype.c_arg_in = "&%s_in"; + //* ptrcall only supports int64_t and uint64_t + itype.c_in = "\t%0 %1_in = (%0)%1;\n"; + itype.c_out = "\treturn (%0)%1;\n"; + itype.c_type = "int64_t"; + //*/ + itype.c_type_in = itype.name; + itype.c_type_out = itype.name; + itype.im_type_in = itype.name; + itype.im_type_out = itype.name; + builtin_types.insert(itype.name, itype); + +#undef INSERT_PRIMITIVE_TYPE + + // real_t + itype = TypeInterface(); +#ifdef REAL_T_IS_DOUBLE + itype.name = "double"; +#else + itype.name = "float"; +#endif + itype.proxy_name = itype.name; + itype.c_arg_in = "&%s_in"; + //* ptrcall only supports double + itype.c_in = "\t%0 %1_in = (%0)%1;\n"; + itype.c_out = "\treturn (%0)%1;\n"; + itype.c_type = "double"; + //*/ + itype.c_type_in = "real_t"; + itype.c_type_out = "real_t"; + itype.cs_type = itype.proxy_name; + itype.im_type_in = itype.proxy_name; + itype.im_type_out = itype.proxy_name; + builtin_types.insert(itype.name, itype); + + // String + itype = TypeInterface(); + itype.name = "String"; + itype.proxy_name = "string"; + itype.c_in = "\t%0 %1_in = " C_METHOD_MONOSTR_TO_GODOT "(%1);\n"; + itype.c_out = "\treturn " C_METHOD_MONOSTR_FROM_GODOT "(%1);\n"; + itype.c_arg_in = "&%s_in"; + itype.c_type = itype.name; + itype.c_type_in = "MonoString*"; + itype.c_type_out = "MonoString*"; + itype.cs_type = itype.proxy_name; + itype.im_type_in = itype.proxy_name; + itype.im_type_out = itype.proxy_name; + builtin_types.insert(itype.name, itype); + + // NodePath + itype = TypeInterface(); + itype.name = "NodePath"; + itype.proxy_name = "NodePath"; + itype.c_out = "\treturn memnew(NodePath(%1));\n"; + itype.c_type = itype.name; + itype.c_type_in = itype.c_type + "*"; + itype.c_type_out = itype.c_type + "*"; + itype.cs_type = itype.proxy_name; + itype.cs_in = "NodePath." CS_SMETHOD_GETINSTANCE "(%0)"; + itype.cs_out = "return new NodePath(%0);"; + itype.im_type_in = "IntPtr"; + itype.im_type_out = "IntPtr"; + _populate_builtin_type(itype, Variant::NODE_PATH); + extra_members.insert(itype.name, MEMBER_BEGIN "public NodePath() : this(string.Empty) {}\n" MEMBER_BEGIN "public NodePath(string path)\n" OPEN_BLOCK_L2 + "this." BINDINGS_PTR_FIELD " = NativeCalls.godot_icall_NodePath_Ctor(path);\n" CLOSE_BLOCK_L2 + MEMBER_BEGIN "public static implicit operator NodePath(string from)\n" OPEN_BLOCK_L2 "return new NodePath(from);\n" CLOSE_BLOCK_L2 + MEMBER_BEGIN "public static implicit operator string(NodePath from)\n" OPEN_BLOCK_L2 + "return NativeCalls." ICALL_PREFIX "NodePath_operator_String(NodePath." CS_SMETHOD_GETINSTANCE "(from));\n" CLOSE_BLOCK_L2); + builtin_types.insert(itype.name, itype); + + // RID + itype = TypeInterface(); + itype.name = "RID"; + itype.proxy_name = "RID"; + itype.c_out = "\treturn memnew(RID(%1));\n"; + itype.c_type = itype.name; + itype.c_type_in = itype.c_type + "*"; + itype.c_type_out = itype.c_type + "*"; + itype.cs_type = itype.proxy_name; + itype.cs_in = "RID." CS_SMETHOD_GETINSTANCE "(%0)"; + itype.cs_out = "return new RID(%0);"; + itype.im_type_in = "IntPtr"; + itype.im_type_out = "IntPtr"; + _populate_builtin_type(itype, Variant::_RID); + extra_members.insert(itype.name, MEMBER_BEGIN "internal RID()\n" OPEN_BLOCK_L2 + "this." BINDINGS_PTR_FIELD " = IntPtr.Zero;\n" CLOSE_BLOCK_L2); + builtin_types.insert(itype.name, itype); + + // Variant + itype = TypeInterface(); + itype.name = "Variant"; + itype.proxy_name = "object"; + itype.c_in = "\t%0 %1_in = " C_METHOD_MANAGED_TO_VARIANT "(%1);\n"; + itype.c_out = "\treturn " C_METHOD_MANAGED_FROM_VARIANT "(%1);\n"; + itype.c_arg_in = "&%s_in"; + itype.c_type = itype.name; + itype.c_type_in = "MonoObject*"; + itype.c_type_out = "MonoObject*"; + itype.cs_type = itype.proxy_name; + itype.im_type_in = "object"; + itype.im_type_out = itype.proxy_name; + builtin_types.insert(itype.name, itype); + + // VarArg (fictitious type to represent variable arguments) + itype = TypeInterface(); + itype.name = "VarArg"; + itype.proxy_name = "object[]"; + itype.c_in = "\t%0 %1_in = " C_METHOD_MONOARRAY_TO(Array) "(%1);\n"; + itype.c_arg_in = "&%s_in"; + itype.c_type = "Array"; + itype.c_type_in = "MonoArray*"; + itype.cs_type = "params object[]"; + itype.im_type_in = "object[]"; + builtin_types.insert(itype.name, itype); + +#define INSERT_ARRAY_FULL(m_name, m_type, m_proxy_t) \ + { \ + itype = TypeInterface(); \ + itype.name = #m_name; \ + itype.proxy_name = #m_proxy_t "[]"; \ + itype.c_in = "\t%0 %1_in = " C_METHOD_MONOARRAY_TO(m_type) "(%1);\n"; \ + itype.c_out = "\treturn " C_METHOD_MONOARRAY_FROM(m_type) "(%1);\n"; \ + itype.c_arg_in = "&%s_in"; \ + itype.c_type = #m_type; \ + itype.c_type_in = "MonoArray*"; \ + itype.c_type_out = "MonoArray*"; \ + itype.cs_type = itype.proxy_name; \ + itype.im_type_in = itype.proxy_name; \ + itype.im_type_out = itype.proxy_name; \ + builtin_types.insert(itype.name, itype); \ + } + +#define INSERT_ARRAY(m_type, m_proxy_t) INSERT_ARRAY_FULL(m_type, m_type, m_proxy_t) + + INSERT_ARRAY(Array, object); + INSERT_ARRAY(PoolIntArray, int); + INSERT_ARRAY_FULL(PoolByteArray, PoolByteArray, byte); + +#ifdef REAL_T_IS_DOUBLE + INSERT_ARRAY(PoolRealArray, double); +#else + INSERT_ARRAY(PoolRealArray, float); +#endif + + INSERT_ARRAY(PoolStringArray, string); + + INSERT_ARRAY(PoolColorArray, Color); + INSERT_ARRAY(PoolVector2Array, Vector2); + INSERT_ARRAY(PoolVector3Array, Vector3); + +#undef INSERT_ARRAY + + // Dictionary + itype = TypeInterface(); + itype.name = "Dictionary"; + itype.proxy_name = "Dictionary<object, object>"; + itype.c_in = "\t%0 %1_in = " C_METHOD_MANAGED_TO_DICT "(%1);\n"; + itype.c_out = "\treturn " C_METHOD_MANAGED_FROM_DICT "(%1);\n"; + itype.c_arg_in = "&%s_in"; + itype.c_type = itype.name; + itype.c_type_in = "MonoObject*"; + itype.c_type_out = "MonoObject*"; + itype.cs_type = itype.proxy_name; + itype.im_type_in = itype.proxy_name; + itype.im_type_out = itype.proxy_name; + builtin_types.insert(itype.name, itype); + + // void (fictitious type to represent the return type of methods that do not return anything) + itype = TypeInterface(); + itype.name = "void"; + itype.proxy_name = itype.name; + itype.c_type = itype.name; + itype.c_type_in = itype.c_type; + itype.c_type_out = itype.c_type; + itype.cs_type = itype.proxy_name; + itype.im_type_in = itype.proxy_name; + itype.im_type_out = itype.proxy_name; + builtin_types.insert(itype.name, itype); + + // Error + itype = TypeInterface(); + itype.name = "Error"; + itype.proxy_name = "Error"; + itype.c_type = itype.name; + itype.c_type_in = itype.c_type; + itype.c_type_out = itype.c_type; + itype.cs_type = itype.proxy_name; + itype.cs_in = "(int)%0"; + itype.cs_out = "return (Error)%s;"; + itype.im_type_in = "int"; + itype.im_type_out = "int"; + builtin_types.insert(itype.name, itype); +} + +void BindingsGenerator::_populate_builtin_type(TypeInterface &r_itype, Variant::Type vtype) { + + Variant::CallError cerror; + Variant v = Variant::construct(vtype, NULL, 0, cerror); + + List<MethodInfo> method_list; + v.get_method_list(&method_list); + method_list.sort(); + + for (List<MethodInfo>::Element *E = method_list.front(); E; E = E->next()) { + MethodInfo &mi = E->get(); + MethodInterface imethod; + + imethod.name = mi.name; + imethod.proxy_name = mi.name; + + for (int i = 0; i < mi.arguments.size(); i++) { + ArgumentInterface iarg; + PropertyInfo pi = mi.arguments[i]; + + iarg.name = pi.name; + + if (pi.type == Variant::NIL) + iarg.type = "Variant"; + else + iarg.type = Variant::get_type_name(pi.type); + + if (!r_itype.requires_collections && iarg.type == "Dictionary") + r_itype.requires_collections = true; + + if ((mi.default_arguments.size() - mi.arguments.size() + i) >= 0) + _default_argument_from_variant(Variant::construct(pi.type, NULL, 0, cerror), iarg); + + imethod.add_argument(iarg); + } + + if (mi.return_val.type == Variant::NIL) { + if (mi.return_val.name != "") + imethod.return_type = "Variant"; + } else { + imethod.return_type = Variant::get_type_name(mi.return_val.type); + } + + if (!r_itype.requires_collections && imethod.return_type == "Dictionary") + r_itype.requires_collections = true; + + if (r_itype.class_doc) { + for (int i = 0; i < r_itype.class_doc->methods.size(); i++) { + if (r_itype.class_doc->methods[i].name == imethod.name) { + imethod.method_doc = &r_itype.class_doc->methods[i]; + break; + } + } + } + + r_itype.methods.push_back(imethod); + } +} + +BindingsGenerator::BindingsGenerator() { + + EditorHelp::generate_doc(); + + _populate_object_type_interfaces(); + _populate_builtin_type_interfaces(); + _generate_header_icalls(); + + for (Map<String, TypeInterface>::Element *E = obj_types.front(); E; E = E->next()) + _generate_method_icalls(E->get()); + + _generate_method_icalls(builtin_types["NodePath"]); + _generate_method_icalls(builtin_types["RID"]); +} + +void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) { + + int options_count = 3; + + String mono_glue_option = "--generate-mono-glue"; + String cs_core_api_option = "--generate-cs-core-api"; + String cs_editor_api_option = "--generate-cs-editor-api"; + + verbose_output = true; + + const List<String>::Element *elem = p_cmdline_args.front(); + + while (elem && options_count) { + + if (elem->get() == mono_glue_option) { + + const List<String>::Element *path_elem = elem->next(); + + if (path_elem) { + get_singleton().generate_glue(path_elem->get()); + elem = elem->next(); + } else { + ERR_PRINTS("--generate-mono-glue: No output directory specified"); + } + + --options_count; + + } else if (elem->get() == cs_core_api_option) { + + const List<String>::Element *path_elem = elem->next(); + + if (path_elem) { + get_singleton().generate_cs_core_project(path_elem->get()); + elem = elem->next(); + } else { + ERR_PRINTS(cs_core_api_option + ": No output directory specified"); + } + + --options_count; + + } else if (elem->get() == cs_editor_api_option) { + + const List<String>::Element *path_elem = elem->next(); + + if (path_elem) { + if (path_elem->next()) { + get_singleton().generate_cs_editor_project(path_elem->get(), path_elem->next()->get()); + elem = path_elem->next(); + } else { + ERR_PRINTS(cs_editor_api_option + ": No hint path for the Core API dll specified"); + } + } else { + ERR_PRINTS(cs_editor_api_option + ": No output directory specified"); + } + + --options_count; + } + + elem = elem->next(); + } + + verbose_output = false; +} + +#endif diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h new file mode 100644 index 0000000000..437a566556 --- /dev/null +++ b/modules/mono/editor/bindings_generator.h @@ -0,0 +1,429 @@ +/*************************************************************************/ +/* bindings_generator.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef BINDINGS_GENERATOR_H +#define BINDINGS_GENERATOR_H + +#include "class_db.h" +#include "editor/doc/doc_data.h" +#include "editor/editor_help.h" + +#ifdef DEBUG_METHODS_ENABLED + +#include "ustring.h" + +class BindingsGenerator { + struct ArgumentInterface { + enum DefaultParamMode { + CONSTANT, + NULLABLE_VAL, + NULLABLE_REF + }; + + String type; + String name; + String default_argument; + DefaultParamMode def_param_mode; + + ArgumentInterface() { + def_param_mode = CONSTANT; + } + }; + + struct MethodInterface { + String name; + + /** + * Name of the C# method + */ + String proxy_name; + + /** + * [TypeInterface::name] of the return type + */ + String return_type; + + /** + * Determines if the method has a variable number of arguments (VarArg) + */ + bool is_vararg; + + /** + * Virtual methods ("virtual" as defined by the Godot API) are methods that by default do nothing, + * but can be overridden by the user to add custom functionality. + * e.g.: _ready, _process, etc. + */ + bool is_virtual; + + /** + * Determines if the call should fallback to Godot's object.Call(string, params) in C#. + */ + bool requires_object_call; + + /** + * Determines if the method visibility is `internal` (visible only to files in the same assembly). + * Currently, we only use this for methods that are not meant to be exposed, + * but are required by properties as getters or setters. + * Methods that are not meant to be exposed are those that begin with underscore and are not virtual. + */ + bool is_internal; + + List<ArgumentInterface> arguments; + + const DocData::MethodDoc *method_doc; + + void add_argument(const ArgumentInterface &argument) { + arguments.push_back(argument); + } + + MethodInterface() { + return_type = "void"; + is_vararg = false; + is_virtual = false; + requires_object_call = false; + is_internal = false; + method_doc = NULL; + } + }; + + struct TypeInterface { + /** + * Identifier name for this type. + * Also used to format [c_out]. + */ + String name; + + /** + * Identifier name of the base class. + */ + String base_name; + + /** + * Name of the C# class + */ + String proxy_name; + + ClassDB::APIType api_type; + + bool is_object_type; + bool is_singleton; + bool is_reference; + + /** + * Used only by Object-derived types. + * Determines if this type is not virtual (incomplete). + * e.g.: CanvasItem cannot be instantiated. + */ + bool is_instantiable; + + /** + * Used only by Object-derived types. + * Determines if the C# class owns the native handle and must free it somehow when disposed. + * e.g.: Reference types must notify when the C# instance is disposed, for proper refcounting. + */ + bool memory_own; + + /** + * Determines if the file must have a using directive for System.Collections.Generic + * e.g.: When the generated class makes use of Dictionary + */ + bool requires_collections; + + // !! The comments of the following fields make reference to other fields via square brackets, e.g.: [field_name] + // !! When renaming those fields, make sure to rename their references in the comments + + // --- C INTERFACE --- + + static const char *DEFAULT_VARARG_C_IN; + + /** + * One or more statements that manipulate the parameter before being passed as argument of a ptrcall. + * If the statement adds a local that must be passed as the argument instead of the parameter, + * the name of that local must be specified with [c_arg_in]. + * For variadic methods, this field is required and, if empty, [DEFAULT_VARARG_C_IN] is used instead. + * Formatting elements: + * %0: [c_type] of the parameter + * %1: name of the parameter + */ + String c_in; + + /** + * Determines the name of the variable that will be passed as argument to a ptrcall. + * By default the value equals the name of the parameter, + * this varies for types that require special manipulation via [c_in]. + * Formatting elements: + * %0 or %s: name of the parameter + */ + String c_arg_in; + + /** + * One or more statements that determine how a variable of this type is returned from a function. + * It must contain the return statement(s). + * Formatting elements: + * %0: [c_type_out] of the return type + * %1: name of the variable to be returned + * %2: [name] of the return type + */ + String c_out; + + /** + * The actual expected type, as seen (in most cases) in Variant copy constructors + * Used for the type of the return variable and to format [c_in]. + * The value must be the following depending of the type: + * Object-derived types: Object* + * Other types: [name] + * -- Exceptions -- + * VarArg (fictitious type to represent variable arguments): Array + * float: double (because ptrcall only supports double) + * int: int64_t (because ptrcall only supports int64_t and uint64_t) + * Reference types override this for the type of the return variable: Ref<Reference> + */ + String c_type; + + /** + * Determines the type used for parameters in function signatures. + */ + String c_type_in; + + /** + * Determines the return type used for function signatures. + * Also used to construct a default value to return in case of errors, + * and to format [c_out]. + */ + String c_type_out; + + // --- C# INTERFACE --- + + /** + * An expression that overrides the way the parameter is passed to the internal call. + * If empty, the parameter is passed as is. + * Formatting elements: + * %0 or %s: name of the parameter + */ + String cs_in; + + /** + * One or more statements that determine how a variable of this type is returned from a method. + * It must contain the return statement(s). + * Formatting elements: + * %0 or %s: name of the variable to be returned + */ + String cs_out; + + /** + * Type used for method signatures, both for parameters and the return type. + * Same as [proxy_name] except for variable arguments (VarArg). + */ + String cs_type; + + /** + * Type used for parameters of internal call methods. + */ + String im_type_in; + + /** + * Type used for the return type of internal call methods. + * If [cs_out] is not empty and the method return type is not void, + * it is also used for the type of the return variable. + */ + String im_type_out; + + const DocData::ClassDoc *class_doc; + + List<MethodInterface> methods; + + const MethodInterface *find_method_by_name(const String &p_name) const { + + for (const List<MethodInterface>::Element *E = methods.front(); E; E = E->next()) { + if (E->get().name == p_name) + return &E->get(); + } + + return NULL; + } + + static TypeInterface create_value_type(const String &p_name) { + TypeInterface itype; + + itype.name = p_name; + itype.proxy_name = p_name; + + itype.c_type = itype.name; + itype.c_type_in = "void*"; + itype.c_type_out = "MonoObject*"; + itype.cs_type = itype.proxy_name; + itype.im_type_in = "ref " + itype.proxy_name; + itype.im_type_out = itype.proxy_name; + itype.class_doc = &EditorHelp::get_doc_data()->class_list[itype.proxy_name]; + + return itype; + } + + static TypeInterface create_object_type(const String &p_name, ClassDB::APIType p_api_type) { + TypeInterface itype; + + itype.name = p_name; + itype.proxy_name = p_name.begins_with("_") ? p_name.substr(1, p_name.length()) : p_name; + itype.api_type = p_api_type; + itype.is_object_type = true; + itype.class_doc = &EditorHelp::get_doc_data()->class_list[itype.proxy_name]; + + return itype; + } + + static void create_placeholder_type(TypeInterface &r_itype, const String &p_name) { + r_itype.name = p_name; + r_itype.proxy_name = p_name; + + r_itype.c_type = r_itype.name; + r_itype.c_type_in = "MonoObject*"; + r_itype.c_type_out = "MonoObject*"; + r_itype.cs_type = r_itype.proxy_name; + r_itype.im_type_in = r_itype.proxy_name; + r_itype.im_type_out = r_itype.proxy_name; + } + + TypeInterface() { + + api_type = ClassDB::API_NONE; + + is_object_type = false; + is_singleton = false; + is_reference = false; + is_instantiable = false; + + memory_own = false; + requires_collections = false; + + c_arg_in = "%s"; + + class_doc = NULL; + } + }; + + struct InternalCall { + String name; + String im_type_out; // Return type for the C# method declaration. Also used as companion of [unique_siq] + String im_sig; // Signature for the C# method declaration + String unique_sig; // Unique signature to avoid duplicates in containers + bool editor_only; + + InternalCall() {} + + InternalCall(const String &p_name, const String &p_im_type_out, const String &p_im_sig = String(), const String &p_unique_sig = String()) { + name = p_name; + im_type_out = p_im_type_out; + im_sig = p_im_sig; + unique_sig = p_unique_sig; + editor_only = false; + } + + InternalCall(ClassDB::APIType api_type, const String &p_name, const String &p_im_type_out, const String &p_im_sig = String(), const String &p_unique_sig = String()) { + name = p_name; + im_type_out = p_im_type_out; + im_sig = p_im_sig; + unique_sig = p_unique_sig; + editor_only = api_type == ClassDB::API_EDITOR; + } + + inline bool operator==(const InternalCall &p_a) const { + return p_a.unique_sig == unique_sig; + } + }; + + static bool verbose_output; + + Map<String, TypeInterface> placeholder_types; + Map<String, TypeInterface> builtin_types; + Map<String, TypeInterface> obj_types; + + Map<String, String> extra_members; + + List<InternalCall> method_icalls; + Map<const MethodInterface *, const InternalCall *> method_icalls_map; + + List<InternalCall> core_custom_icalls; + List<InternalCall> editor_custom_icalls; + + const List<InternalCall>::Element *find_icall_by_name(const String &p_name, const List<InternalCall> &p_list) { + + const List<InternalCall>::Element *it = p_list.front(); + while (it) { + if (it->get().name == p_name) return it; + it = it->next(); + } + return NULL; + } + + inline String get_unique_sig(const TypeInterface &p_type) { + if (p_type.is_reference) + return "Ref"; + else if (p_type.is_object_type) + return "Obj"; + + return p_type.name; + } + + void _generate_header_icalls(); + void _generate_method_icalls(const TypeInterface &p_itype); + + const TypeInterface *_get_type_by_name_or_null(const String &p_name); + const TypeInterface *_get_type_by_name_or_placeholder(const String &p_name); + + void _default_argument_from_variant(const Variant &p_var, ArgumentInterface &r_iarg); + void _populate_builtin_type(TypeInterface &r_type, Variant::Type vtype); + + void _populate_object_type_interfaces(); + void _populate_builtin_type_interfaces(); + + Error _generate_cs_type(const TypeInterface &itype, const String &p_output_file); + + Error _save_file(const String &path, const List<String> &content); + + BindingsGenerator(); + + BindingsGenerator(const BindingsGenerator &); + BindingsGenerator &operator=(const BindingsGenerator &); + +public: + Error generate_cs_core_project(const String &p_output_dir, bool p_verbose_output = true); + Error generate_cs_editor_project(const String &p_output_dir, const String &p_core_dll_path, bool p_verbose_output = true); + Error generate_glue(const String &p_output_dir); + + static BindingsGenerator &get_singleton() { + static BindingsGenerator singleton; + return singleton; + } + + static void handle_cmdline_args(const List<String> &p_cmdline_args); +}; + +#endif + +#endif // BINDINGS_GENERATOR_H diff --git a/modules/mono/editor/csharp_project.cpp b/modules/mono/editor/csharp_project.cpp new file mode 100644 index 0000000000..bde5f0fd0b --- /dev/null +++ b/modules/mono/editor/csharp_project.cpp @@ -0,0 +1,120 @@ +/*************************************************************************/ +/* csharp_project.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "csharp_project.h" + +#include "os/os.h" +#include "project_settings.h" + +#include "../mono_gd/gd_mono_class.h" +#include "../mono_gd/gd_mono_marshal.h" + +namespace CSharpProject { + +String generate_core_api_project(const String &p_dir, const Vector<String> &p_files) { + + _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) + + GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator"); + + Variant dir = p_dir; + Variant compile_items = p_files; + const Variant *args[2] = { &dir, &compile_items }; + MonoObject *ex = NULL; + MonoObject *ret = klass->get_method("GenCoreApiProject", 2)->invoke(NULL, args, &ex); + + if (ex) { + mono_print_unhandled_exception(ex); + ERR_FAIL_V(String()); + } + + return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : ""; +} + +String generate_editor_api_project(const String &p_dir, const String &p_core_dll_path, const Vector<String> &p_files) { + + _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) + + GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator"); + + Variant dir = p_dir; + Variant core_dll_path = p_core_dll_path; + Variant compile_items = p_files; + const Variant *args[3] = { &dir, &core_dll_path, &compile_items }; + MonoObject *ex = NULL; + MonoObject *ret = klass->get_method("GenEditorApiProject", 3)->invoke(NULL, args, &ex); + + if (ex) { + mono_print_unhandled_exception(ex); + ERR_FAIL_V(String()); + } + + return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : ""; +} + +String generate_game_project(const String &p_dir, const String &p_name, const Vector<String> &p_files) { + + _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) + + GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator"); + + Variant dir = p_dir; + Variant name = p_name; + Variant compile_items = p_files; + const Variant *args[3] = { &dir, &name, &compile_items }; + MonoObject *ex = NULL; + MonoObject *ret = klass->get_method("GenGameProject", 3)->invoke(NULL, args, &ex); + + if (ex) { + mono_print_unhandled_exception(ex); + ERR_FAIL_V(String()); + } + + return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : ""; +} + +void add_item(const String &p_project_path, const String &p_item_type, const String &p_include) { + + _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) + + GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectUtils"); + + Variant project_path = p_project_path; + Variant item_type = p_item_type; + Variant include = p_include; + const Variant *args[3] = { &project_path, &item_type, &include }; + MonoObject *ex = NULL; + klass->get_method("AddItemToProjectChecked", 3)->invoke(NULL, args, &ex); + + if (ex) { + mono_print_unhandled_exception(ex); + ERR_FAIL(); + } +} +} // CSharpProject diff --git a/modules/mono/editor/csharp_project.h b/modules/mono/editor/csharp_project.h new file mode 100644 index 0000000000..4832d2251e --- /dev/null +++ b/modules/mono/editor/csharp_project.h @@ -0,0 +1,44 @@ +/*************************************************************************/ +/* csharp_project.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef CSHARP_PROJECT_H +#define CSHARP_PROJECT_H + +#include "ustring.h" + +namespace CSharpProject { + +String generate_core_api_project(const String &p_dir, const Vector<String> &p_files = Vector<String>()); +String generate_editor_api_project(const String &p_dir, const String &p_core_dll_path, const Vector<String> &p_files = Vector<String>()); +String generate_game_project(const String &p_dir, const String &p_name, const Vector<String> &p_files = Vector<String>()); + +void add_item(const String &p_project_path, const String &p_item_type, const String &p_include); +} + +#endif // CSHARP_PROJECT_H diff --git a/modules/mono/editor/godotsharp_builds.cpp b/modules/mono/editor/godotsharp_builds.cpp new file mode 100644 index 0000000000..e7d9c83421 --- /dev/null +++ b/modules/mono/editor/godotsharp_builds.cpp @@ -0,0 +1,440 @@ +/*************************************************************************/ +/* godotsharp_builds.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "godotsharp_builds.h" + +#include "../godotsharp_dirs.h" +#include "../mono_gd/gd_mono_class.h" +#include "../mono_gd/gd_mono_marshal.h" +#include "../utils/path_utils.h" +#include "bindings_generator.h" +#include "godotsharp_editor.h" +#include "main/main.h" + +void godot_icall_BuildInstance_ExitCallback(MonoString *p_solution, MonoString *p_config, int p_exit_code) { + + String solution = GDMonoMarshal::mono_string_to_godot(p_solution); + String config = GDMonoMarshal::mono_string_to_godot(p_config); + GodotSharpBuilds::get_singleton()->build_exit_callback(MonoBuildInfo(solution, config), p_exit_code); +} + +MonoString *godot_icall_BuildInstance_get_MSBuildPath() { + + GodotSharpBuilds::BuildTool build_tool = GodotSharpBuilds::BuildTool(int(EditorSettings::get_singleton()->get("mono/builds/build_tool"))); + +#ifdef WINDOWS_ENABLED + switch (build_tool) { + case GodotSharpBuilds::MSBUILD: { + static String msbuild_tools_path = MonoRegUtils::find_msbuild_tools_path(); + + if (msbuild_tools_path.length()) { + if (!msbuild_tools_path.ends_with("\\")) + msbuild_tools_path += "\\"; + + return GDMonoMarshal::mono_string_from_godot(msbuild_tools_path + "MSBuild.exe"); + } + + OS::get_singleton()->print("Cannot find System's MSBuild. Trying with Mono's...\n"); + } + case GodotSharpBuilds::MSBUILD_MONO: { + String msbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("msbuild.bat"); + + if (!FileAccess::exists(msbuild_path)) { + WARN_PRINTS("Cannot find msbuild ('mono/builds/build_tool'). Tried with path: " + msbuild_path); + } + + return GDMonoMarshal::mono_string_from_godot(msbuild_path); + } + case GodotSharpBuilds::XBUILD: { + String xbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("xbuild.bat"); + + if (!FileAccess::exists(xbuild_path)) { + WARN_PRINTS("Cannot find xbuild ('mono/builds/build_tool'). Tried with path: " + xbuild_path); + } + + return GDMonoMarshal::mono_string_from_godot(xbuild_path); + } + default: + ERR_EXPLAIN("You don't deserve to live"); + CRASH_NOW(); + } +#else + static bool msbuild_found = path_which("msbuild").length(); + + if (build_tool != GodotSharpBuilds::XBUILD && !msbuild_found) { + WARN_PRINT("Cannot find msbuild ('mono/builds/build_tool')."); + } + + return GDMonoMarshal::mono_string_from_godot(build_tool != GodotSharpBuilds::XBUILD ? "msbuild" : "xbuild"); +#endif +} + +void GodotSharpBuilds::_register_internal_calls() { + + mono_add_internal_call("GodotSharpTools.Build.BuildSystem::godot_icall_BuildInstance_ExitCallback", (void *)godot_icall_BuildInstance_ExitCallback); + mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MSBuildPath", (void *)godot_icall_BuildInstance_get_MSBuildPath); +} + +void GodotSharpBuilds::show_build_error_dialog(const String &p_message) { + + GodotSharpEditor::get_singleton()->show_error_dialog(p_message, "Build error"); + MonoBottomPanel::get_singleton()->show_build_tab(); +} + +bool GodotSharpBuilds::build_api_sln(const String &p_name, const String &p_api_sln_dir, const String &p_config) { + + String api_sln_file = p_api_sln_dir.plus_file(p_name + ".sln"); + String api_assembly_dir = p_api_sln_dir.plus_file("bin").plus_file(p_config); + String api_assembly_file = api_assembly_dir.plus_file(p_name + ".dll"); + + if (!FileAccess::exists(api_assembly_file)) { + MonoBuildInfo api_build_info(api_sln_file, p_config); + api_build_info.custom_props.push_back("NoWarn=1591"); // Ignore missing documentation warnings + + if (!GodotSharpBuilds::get_singleton()->build(api_build_info)) { + show_build_error_dialog("Failed to build " + p_name + " solution."); + return false; + } + } + + return true; +} + +bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name) { + + String assembly_file = p_assembly_name + ".dll"; + String assembly_src = p_src_dir.plus_file(assembly_file); + String assembly_dst = p_dst_dir.plus_file(assembly_file); + + if (!FileAccess::exists(assembly_dst) || FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst)) { + DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + + String xml_file = p_assembly_name + ".xml"; + if (da->copy(p_src_dir.plus_file(xml_file), p_dst_dir.plus_file(xml_file)) != OK) + WARN_PRINTS("Failed to copy " + xml_file); + + String pdb_file = p_assembly_name + ".pdb"; + if (da->copy(p_src_dir.plus_file(pdb_file), p_dst_dir.plus_file(pdb_file)) != OK) + WARN_PRINTS("Failed to copy " + pdb_file); + + Error err = da->copy(assembly_src, assembly_dst); + + memdelete(da); + + if (err != OK) { + show_build_error_dialog("Failed to copy " API_ASSEMBLY_NAME ".dll"); + return false; + } + } + + return true; +} + +bool GodotSharpBuilds::make_api_sln(GodotSharpBuilds::APIType p_api_type) { + + String api_name = p_api_type == API_CORE ? API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME; + String api_build_config = "Release"; + + EditorProgress pr("mono_build_release_" + api_name, "Building " + api_name + " solution...", 4); + + pr.step("Generating " + api_name + " solution"); + + uint64_t core_hash = GDMono::get_singleton()->get_api_core_hash(); + uint64_t editor_hash = GDMono::get_singleton()->get_api_editor_hash(); + + String core_api_sln_dir = GodotSharpDirs::get_mono_solutions_dir().plus_file(API_ASSEMBLY_NAME "_" + itos(core_hash)); + String editor_api_sln_dir = GodotSharpDirs::get_mono_solutions_dir().plus_file(EDITOR_API_ASSEMBLY_NAME "_" + itos(editor_hash)); + + String api_sln_dir = p_api_type == API_CORE ? core_api_sln_dir : editor_api_sln_dir; + String api_sln_file = api_sln_dir.plus_file(api_name + ".sln"); + + if (!DirAccess::exists(api_sln_dir) || !FileAccess::exists(api_sln_file)) { + String core_api_assembly; + + if (p_api_type == API_EDITOR) { + core_api_assembly = core_api_sln_dir.plus_file("bin") + .plus_file(api_build_config) + .plus_file(API_ASSEMBLY_NAME ".dll"); + } + +#ifndef DEBUG_METHODS_ENABLED +#error "How am I supposed to generate the bindings?" +#endif + + BindingsGenerator &gen = BindingsGenerator::get_singleton(); + bool gen_verbose = OS::get_singleton()->is_stdout_verbose(); + + Error err = p_api_type == API_CORE ? + gen.generate_cs_core_project(api_sln_dir, gen_verbose) : + gen.generate_cs_editor_project(api_sln_dir, core_api_assembly, gen_verbose); + + if (err != OK) { + show_build_error_dialog("Failed to generate " + api_name + " solution. Error: " + itos(err)); + return false; + } + } + + pr.step("Building " + api_name + " solution"); + + if (!GodotSharpBuilds::build_api_sln(api_name, api_sln_dir, api_build_config)) + return false; + + pr.step("Copying " + api_name + " assembly"); + + String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir(); + + // Create assemblies directory if needed + if (!DirAccess::exists(res_assemblies_dir)) { + DirAccess *da = DirAccess::create_for_path(res_assemblies_dir); + Error err = da->make_dir_recursive(res_assemblies_dir); + memdelete(da); + + if (err != OK) { + show_build_error_dialog("Failed to create assemblies directory. Error: " + itos(err)); + return false; + } + } + + // Copy the built assembly to the assemblies directory + String api_assembly_dir = api_sln_dir.plus_file("bin").plus_file(api_build_config); + if (!GodotSharpBuilds::copy_api_assembly(api_assembly_dir, res_assemblies_dir, api_name)) + return false; + + pr.step("Done"); + + return true; +} + +bool godotsharp_build_callback() { + + if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) + return true; // No solution to build + + if (!GodotSharpBuilds::make_api_sln(GodotSharpBuilds::API_CORE)) + return false; + + if (!GodotSharpBuilds::make_api_sln(GodotSharpBuilds::API_EDITOR)) + return false; + + EditorProgress pr("mono_project_debug_build", "Building project solution...", 2); + + pr.step("Building project solution"); + + MonoBuildInfo build_info(GodotSharpDirs::get_project_sln_path(), "Tools"); + if (!GodotSharpBuilds::get_singleton()->build(build_info)) { + GodotSharpBuilds::show_build_error_dialog("Failed to build project solution"); + return false; + } + + pr.step("Done"); + + return true; +} + +GodotSharpBuilds *GodotSharpBuilds::singleton = NULL; + +void GodotSharpBuilds::build_exit_callback(const MonoBuildInfo &p_build_info, int p_exit_code) { + + BuildProcess *match = builds.getptr(p_build_info); + ERR_FAIL_COND(!match); + + BuildProcess &bp = *match; + bp.on_exit(p_exit_code); +} + +void GodotSharpBuilds::restart_build(MonoBuildTab *p_build_tab) { +} + +void GodotSharpBuilds::stop_build(MonoBuildTab *p_build_tab) { +} + +bool GodotSharpBuilds::build(const MonoBuildInfo &p_build_info) { + + BuildProcess *match = builds.getptr(p_build_info); + + if (match) { + BuildProcess &bp = *match; + bp.start(true); + return bp.exit_code == 0; + } else { + BuildProcess bp = BuildProcess(p_build_info); + bp.start(true); + builds.set(p_build_info, bp); + return bp.exit_code == 0; + } +} + +bool GodotSharpBuilds::build_async(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback) { + + BuildProcess *match = builds.getptr(p_build_info); + + if (match) { + BuildProcess &bp = *match; + bp.start(); + return !bp.exited; // failed to start + } else { + BuildProcess bp = BuildProcess(p_build_info, p_callback); + bp.start(); + builds.set(p_build_info, bp); + return !bp.exited; // failed to start + } +} + +GodotSharpBuilds::GodotSharpBuilds() { + + singleton = this; + + EditorNode::get_singleton()->add_build_callback(&godotsharp_build_callback); + + // Build tool settings + EditorSettings *ed_settings = EditorSettings::get_singleton(); + if (!ed_settings->has("mono/builds/build_tool")) { + ed_settings->set("mono/builds/build_tool", MSBUILD); + } + ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/builds/build_tool", PROPERTY_HINT_ENUM, "MSBuild (System),MSBuild (Mono),xbuild")); +} + +GodotSharpBuilds::~GodotSharpBuilds() { + + singleton = NULL; +} + +void GodotSharpBuilds::BuildProcess::on_exit(int p_exit_code) { + + exited = true; + exit_code = p_exit_code; + build_tab->on_build_exit(p_exit_code == 0 ? MonoBuildTab::RESULT_SUCCESS : MonoBuildTab::RESULT_ERROR); + build_instance.unref(); + + if (exit_callback) + exit_callback(exit_code); +} + +void GodotSharpBuilds::BuildProcess::start(bool p_blocking) { + + _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) + + exit_code = -1; + + String logs_dir = GodotSharpDirs::get_build_logs_dir().plus_file(build_info.solution.md5_text() + "_" + build_info.configuration); + + if (build_tab) { + build_tab->on_build_start(); + } else { + build_tab = memnew(MonoBuildTab(build_info, logs_dir)); + MonoBottomPanel::get_singleton()->add_build_tab(build_tab); + } + + if (p_blocking) { + // Required in order to update the build tasks list + Main::iteration(); + } + + if (!exited) { + ERR_PRINT("BuildProcess::start called, but process still running"); + exited = true; + build_tab->on_build_exec_failed("!exited"); + return; + } + + exited = false; + + // Remove old issues file + + String issues_file = "msbuild_issues.csv"; + DirAccessRef d = DirAccess::create_for_path(logs_dir); + if (d->file_exists(issues_file)) { + Error err = d->remove(issues_file); + if (err != OK) { + ERR_PRINTS("Cannot remove file: " + logs_dir.plus_file(issues_file)); + exited = true; + build_tab->on_build_exec_failed("Cannot remove file: " + issues_file); + return; + } + } + + GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Build", "BuildInstance"); + + MonoObject *mono_object = mono_object_new(mono_domain_get(), klass->get_raw()); + + // Construct + + Variant solution = build_info.solution; + Variant config = build_info.configuration; + + const Variant *ctor_args[2] = { &solution, &config }; + + MonoObject *ex = NULL; + GDMonoMethod *ctor = klass->get_method(".ctor", 2); + ctor->invoke(mono_object, ctor_args, &ex); + + if (ex) { + exited = true; + build_tab->on_build_exec_failed("The build constructor threw an exception.\n" + GDMonoUtils::get_exception_name_and_message(ex)); + ERR_FAIL(); + } + + // Call Build + + Variant logger_assembly = OS::get_singleton()->get_executable_path().get_base_dir().plus_file(EDITOR_TOOLS_ASSEMBLY_NAME) + ".dll"; + Variant logger_output_dir = logs_dir; + Variant custom_props = build_info.custom_props; + + const Variant *args[3] = { &logger_assembly, &logger_output_dir, &custom_props }; + + ex = NULL; + GDMonoMethod *build_method = klass->get_method(p_blocking ? "Build" : "BuildAsync", 3); + build_method->invoke(mono_object, args, &ex); + + if (ex) { + exited = true; + build_tab->on_build_exec_failed("The build method threw an exception.\n" + GDMonoUtils::get_exception_name_and_message(ex)); + ERR_FAIL(); + } + + // Build returned + + if (p_blocking) { + exited = true; + exit_code = klass->get_field("exitCode")->get_int_value(mono_object); + build_tab->on_build_exit(exit_code == 0 ? MonoBuildTab::RESULT_SUCCESS : MonoBuildTab::RESULT_ERROR); + } else { + build_instance = MonoGCHandle::create_strong(mono_object); + exited = false; + } +} + +GodotSharpBuilds::BuildProcess::BuildProcess(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback) { + + build_info = p_build_info; + build_tab = NULL; + exit_callback = p_callback; + exited = true; + exit_code = -1; +} diff --git a/modules/mono/editor/godotsharp_builds.h b/modules/mono/editor/godotsharp_builds.h new file mode 100644 index 0000000000..6d5fa3b44a --- /dev/null +++ b/modules/mono/editor/godotsharp_builds.h @@ -0,0 +1,96 @@ +/*************************************************************************/ +/* godotsharp_builds.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef GODOTSHARP_BUILDS_H +#define GODOTSHARP_BUILDS_H + +#include "mono_bottom_panel.h" +#include "mono_build_info.h" + +typedef void (*GodotSharpBuild_ExitCallback)(int); + +class GodotSharpBuilds { + +private: + struct BuildProcess { + Ref<MonoGCHandle> build_instance; + MonoBuildInfo build_info; + MonoBuildTab *build_tab; + GodotSharpBuild_ExitCallback exit_callback; + bool exited; + int exit_code; + + void on_exit(int p_exit_code); + void start(bool p_blocking = false); + + BuildProcess() {} + BuildProcess(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback = NULL); + }; + + HashMap<MonoBuildInfo, BuildProcess, MonoBuildInfo::Hasher> builds; + + static GodotSharpBuilds *singleton; + + friend class GDMono; + static void _register_internal_calls(); + +public: + enum APIType { + API_CORE, + API_EDITOR + }; + + enum BuildTool { + MSBUILD, + MSBUILD_MONO, + XBUILD + }; + + _FORCE_INLINE_ static GodotSharpBuilds *get_singleton() { return singleton; } + + static void show_build_error_dialog(const String &p_message); + + void build_exit_callback(const MonoBuildInfo &p_build_info, int p_exit_code); + + void restart_build(MonoBuildTab *p_build_tab); + void stop_build(MonoBuildTab *p_build_tab); + + bool build(const MonoBuildInfo &p_build_info); + bool build_async(const MonoBuildInfo &p_build_info, GodotSharpBuild_ExitCallback p_callback = NULL); + + static bool build_api_sln(const String &p_name, const String &p_api_sln_dir, const String &p_config); + static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name); + + static bool make_api_sln(APIType p_api_type); + + GodotSharpBuilds(); + ~GodotSharpBuilds(); +}; + +#endif // GODOTSHARP_BUILDS_H diff --git a/modules/mono/editor/godotsharp_editor.cpp b/modules/mono/editor/godotsharp_editor.cpp new file mode 100644 index 0000000000..5aaf029495 --- /dev/null +++ b/modules/mono/editor/godotsharp_editor.cpp @@ -0,0 +1,256 @@ +/*************************************************************************/ +/* godotsharp_editor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "godotsharp_editor.h" + +#include "core/os/os.h" +#include "core/project_settings.h" +#include "scene/gui/control.h" +#include "scene/main/node.h" + +#include "../csharp_script.h" +#include "../godotsharp_dirs.h" +#include "../mono_gd/gd_mono.h" +#include "../utils/path_utils.h" +#include "bindings_generator.h" +#include "csharp_project.h" +#include "net_solution.h" + +#ifdef WINDOWS_ENABLED +#include "../utils/mono_reg_utils.h" +#endif + +class MonoReloadNode : public Node { + GDCLASS(MonoReloadNode, Node) + +protected: + void _notification(int p_what) { + switch (p_what) { + case MainLoop::NOTIFICATION_WM_FOCUS_IN: { + CSharpLanguage::get_singleton()->reload_assemblies_if_needed(true); + } break; + default: { + } break; + }; + } +}; + +GodotSharpEditor *GodotSharpEditor::singleton = NULL; + +bool GodotSharpEditor::_create_project_solution() { + + EditorProgress pr("create_csharp_solution", "Generating solution...", 2); + + pr.step("Generating C# project..."); + + String path = OS::get_singleton()->get_resource_dir(); + String name = ProjectSettings::get_singleton()->get("application/config/name"); + String guid = CSharpProject::generate_game_project(path, name); + + if (guid.length()) { + + NETSolution solution(name); + + if (!solution.set_path(path)) { + show_error_dialog("Failed to create solution."); + return false; + } + + Vector<String> extra_configs; + extra_configs.push_back("Tools"); + + solution.add_new_project(name, guid, extra_configs); + + Error sln_error = solution.save(); + + if (sln_error != OK) { + show_error_dialog("Failed to save solution."); + return false; + } + + if (!GodotSharpBuilds::make_api_sln(GodotSharpBuilds::API_CORE)) + return false; + + if (!GodotSharpBuilds::make_api_sln(GodotSharpBuilds::API_EDITOR)) + return false; + + pr.step("Done"); + + // Here, after all calls to progress_task_step + call_deferred("_remove_create_sln_menu_option"); + + } else { + show_error_dialog("Failed to create C# project."); + } + + return true; +} + +void GodotSharpEditor::_remove_create_sln_menu_option() { + + menu_popup->remove_item(menu_popup->get_item_index(MENU_CREATE_SLN)); + + if (menu_popup->get_item_count() == 0) + menu_button->hide(); + + bottom_panel_btn->show(); +} + +void GodotSharpEditor::_menu_option_pressed(int p_id) { + + switch (p_id) { + case MENU_CREATE_SLN: { + + _create_project_solution(); + } break; + default: + ERR_FAIL(); + } +} + +void GodotSharpEditor::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_create_project_solution"), &GodotSharpEditor::_create_project_solution); + ClassDB::bind_method(D_METHOD("_remove_create_sln_menu_option"), &GodotSharpEditor::_remove_create_sln_menu_option); + ClassDB::bind_method(D_METHOD("_menu_option_pressed", "id"), &GodotSharpEditor::_menu_option_pressed); +} + +void GodotSharpEditor::show_error_dialog(const String &p_message, const String &p_title) { + + error_dialog->set_title(p_title); + error_dialog->set_text(p_message); + error_dialog->popup_centered_minsize(); +} + +Error GodotSharpEditor::open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) { + + ExternalEditor editor = ExternalEditor(int(EditorSettings::get_singleton()->get("mono/editor/external_editor"))); + + switch (editor) { + case EDITOR_CODE: { + List<String> args; + args.push_back(ProjectSettings::get_singleton()->get_resource_path()); + + String script_path = ProjectSettings::get_singleton()->globalize_path(p_script->get_path()); + + if (p_line >= 0) { + args.push_back("-g"); + args.push_back(script_path + ":" + itos(p_line) + ":" + itos(p_col)); + } else { + args.push_back(script_path); + } + + static String program = path_which("code"); + + Error err = OS::get_singleton()->execute(program.length() ? program : "code", args, false); + + if (err != OK) { + ERR_PRINT("GodotSharp: Could not execute external editor"); + return err; + } + } break; + case EDITOR_MONODEVELOP: { + if (!monodevel_instance) + monodevel_instance = memnew(MonoDevelopInstance(GodotSharpDirs::get_project_sln_path())); + + String script_path = ProjectSettings::get_singleton()->globalize_path(p_script->get_path()); + monodevel_instance->execute(script_path); + } break; + case EDITOR_VISUAL_STUDIO: + // TODO + // devenv <PathToSolutionFolder> + // devenv /edit <PathToCsFile> /command "edit.goto <Line>" + // HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\SxS\VS7 + default: + return ERR_UNAVAILABLE; + } + + return OK; +} + +bool GodotSharpEditor::overrides_external_editor() { + + return ExternalEditor(int(EditorSettings::get_singleton()->get("mono/editor/external_editor"))) != EDITOR_NONE; +} + +GodotSharpEditor::GodotSharpEditor(EditorNode *p_editor) { + + singleton = this; + + monodevel_instance = NULL; + + editor = p_editor; + + error_dialog = memnew(AcceptDialog); + editor->get_gui_base()->add_child(error_dialog); + + bottom_panel_btn = editor->add_bottom_panel_item("Mono", memnew(MonoBottomPanel(editor))); + + godotsharp_builds = memnew(GodotSharpBuilds); + + editor->add_child(memnew(MonoReloadNode)); + + menu_button = memnew(MenuButton); + menu_button->set_text("Mono"); + menu_popup = menu_button->get_popup(); + + String sln_path = GodotSharpDirs::get_project_sln_path(); + String csproj_path = GodotSharpDirs::get_project_csproj_path(); + + if (!FileAccess::exists(sln_path) || !FileAccess::exists(csproj_path)) { + bottom_panel_btn->hide(); + menu_popup->add_item("Create C# solution", MENU_CREATE_SLN); + } + + menu_popup->connect("id_pressed", this, "_menu_option_pressed"); + + if (menu_popup->get_item_count() == 0) + menu_button->hide(); + + editor->get_menu_hb()->add_child(menu_button); + + // External editor settings + EditorSettings *ed_settings = EditorSettings::get_singleton(); + if (!ed_settings->has("mono/editor/external_editor")) { + ed_settings->set("mono/editor/external_editor", EDITOR_NONE); + } + ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/editor/external_editor", PROPERTY_HINT_ENUM, "None,MonoDevelop,Visual Studio,Visual Studio Code")); +} + +GodotSharpEditor::~GodotSharpEditor() { + + singleton = NULL; + + memdelete(godotsharp_builds); + + if (monodevel_instance) { + memdelete(monodevel_instance); + monodevel_instance = NULL; + } +} diff --git a/modules/mono/editor/godotsharp_editor.h b/modules/mono/editor/godotsharp_editor.h new file mode 100644 index 0000000000..1ecb8c7a94 --- /dev/null +++ b/modules/mono/editor/godotsharp_editor.h @@ -0,0 +1,87 @@ +/*************************************************************************/ +/* godotsharp_editor.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef GODOTSHARP_EDITOR_H +#define GODOTSHARP_EDITOR_H + +#include "godotsharp_builds.h" + +#include "monodevelop_instance.h" + +class GodotSharpEditor : public Node { + GDCLASS(GodotSharpEditor, Object) + + EditorNode *editor; + + MenuButton *menu_button; + PopupMenu *menu_popup; + + AcceptDialog *error_dialog; + + ToolButton *bottom_panel_btn; + + GodotSharpBuilds *godotsharp_builds; + + MonoDevelopInstance *monodevel_instance; + + bool _create_project_solution(); + + void _remove_create_sln_menu_option(); + + void _menu_option_pressed(int p_id); + + static GodotSharpEditor *singleton; + +protected: + static void _bind_methods(); + +public: + enum MenuOptions { + MENU_CREATE_SLN + }; + + enum ExternalEditor { + EDITOR_NONE, + EDITOR_MONODEVELOP, + EDITOR_VISUAL_STUDIO, + EDITOR_CODE, + }; + + _FORCE_INLINE_ static GodotSharpEditor *get_singleton() { return singleton; } + + void show_error_dialog(const String &p_message, const String &p_title = "Error"); + + Error open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col); + bool overrides_external_editor(); + + GodotSharpEditor(EditorNode *p_editor); + ~GodotSharpEditor(); +}; + +#endif // GODOTSHARP_EDITOR_H diff --git a/modules/mono/editor/mono_bottom_panel.cpp b/modules/mono/editor/mono_bottom_panel.cpp new file mode 100644 index 0000000000..07109eaac7 --- /dev/null +++ b/modules/mono/editor/mono_bottom_panel.cpp @@ -0,0 +1,441 @@ +/*************************************************************************/ +/* mono_bottom_panel.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "mono_bottom_panel.h" + +#include "../csharp_script.h" +#include "godotsharp_editor.h" + +MonoBottomPanel *MonoBottomPanel::singleton = NULL; + +void MonoBottomPanel::_update_build_tabs_list() { + + build_tabs_list->clear(); + + int current_tab = build_tabs->get_current_tab(); + + bool no_current_tab = current_tab < 0 || current_tab >= build_tabs->get_tab_count(); + + for (int i = 0; i < build_tabs->get_child_count(); i++) { + + MonoBuildTab *tab = Object::cast_to<MonoBuildTab>(build_tabs->get_child(i)); + + if (tab) { + String item_name = tab->build_info.solution.get_file().get_basename(); + item_name += " [" + tab->build_info.configuration + "]"; + + build_tabs_list->add_item(item_name, tab->get_icon_texture()); + + String item_tooltip = String("Solution: ") + tab->build_info.solution; + item_tooltip += String("\nConfiguration: ") + tab->build_info.configuration; + item_tooltip += String("\nStatus: "); + + if (tab->build_exited) { + item_tooltip += tab->build_result == MonoBuildTab::RESULT_SUCCESS ? "Succeeded" : "Errored"; + } else { + item_tooltip += "Running"; + } + + if (!tab->build_exited || !tab->build_result == MonoBuildTab::RESULT_SUCCESS) { + item_tooltip += "\nErrors: " + itos(tab->error_count); + } + + item_tooltip += "\nWarnings: " + itos(tab->warning_count); + + build_tabs_list->set_item_tooltip(i, item_tooltip); + + if (no_current_tab || current_tab == i) { + build_tabs_list->select(i); + _build_tab_item_selected(i); + } + } + } +} + +void MonoBottomPanel::add_build_tab(MonoBuildTab *p_build_tab) { + + build_tabs->add_child(p_build_tab); + raise_build_tab(p_build_tab); +} + +void MonoBottomPanel::raise_build_tab(MonoBuildTab *p_build_tab) { + + ERR_FAIL_COND(p_build_tab->get_parent() != build_tabs); + build_tabs->move_child(p_build_tab, 0); + _update_build_tabs_list(); +} + +void MonoBottomPanel::show_build_tab() { + + for (int i = 0; i < panel_tabs->get_tab_count(); i++) { + if (panel_tabs->get_tab_control(i) == panel_builds_tab) { + panel_tabs->set_current_tab(i); + editor->make_bottom_panel_item_visible(this); + return; + } + } + + ERR_PRINT("Builds tab not found"); +} + +void MonoBottomPanel::_build_tab_item_selected(int p_idx) { + + ERR_FAIL_INDEX(p_idx, build_tabs->get_tab_count()); + build_tabs->set_current_tab(p_idx); +} + +void MonoBottomPanel::_build_tab_changed(int p_idx) { + + if (p_idx < 0 || p_idx >= build_tabs->get_tab_count()) { + warnings_btn->set_visible(false); + errors_btn->set_visible(false); + } else { + warnings_btn->set_visible(true); + errors_btn->set_visible(true); + } +} + +void MonoBottomPanel::_warnings_toggled(bool p_pressed) { + + int current_tab = build_tabs->get_current_tab(); + ERR_FAIL_INDEX(current_tab, build_tabs->get_tab_count()); + MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_child(current_tab)); + build_tab->warnings_visible = p_pressed; + build_tab->_update_issues_list(); +} + +void MonoBottomPanel::_errors_toggled(bool p_pressed) { + + int current_tab = build_tabs->get_current_tab(); + ERR_FAIL_INDEX(current_tab, build_tabs->get_tab_count()); + MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_child(current_tab)); + build_tab->errors_visible = p_pressed; + build_tab->_update_issues_list(); +} + +void MonoBottomPanel::_notification(int p_what) { + + switch (p_what) { + + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + panel_tabs->add_style_override("panel", editor->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles")); + panel_tabs->add_style_override("tab_fg", editor->get_gui_base()->get_stylebox("DebuggerTabFG", "EditorStyles")); + panel_tabs->add_style_override("tab_bg", editor->get_gui_base()->get_stylebox("DebuggerTabBG", "EditorStyles")); + } break; + } +} + +void MonoBottomPanel::_bind_methods() { + + ClassDB::bind_method(D_METHOD("_warnings_toggled", "pressed"), &MonoBottomPanel::_warnings_toggled); + ClassDB::bind_method(D_METHOD("_errors_toggled", "pressed"), &MonoBottomPanel::_errors_toggled); + ClassDB::bind_method(D_METHOD("_build_tab_item_selected", "idx"), &MonoBottomPanel::_build_tab_item_selected); + ClassDB::bind_method(D_METHOD("_build_tab_changed", "idx"), &MonoBottomPanel::_build_tab_changed); +} + +MonoBottomPanel::MonoBottomPanel(EditorNode *p_editor) { + + singleton = this; + + editor = p_editor; + + set_v_size_flags(SIZE_EXPAND_FILL); + set_anchors_and_margins_preset(Control::PRESET_WIDE); + + panel_tabs = memnew(TabContainer); + panel_tabs->set_tab_align(TabContainer::ALIGN_LEFT); + panel_tabs->add_style_override("panel", editor->get_gui_base()->get_stylebox("DebuggerPanel", "EditorStyles")); + panel_tabs->add_style_override("tab_fg", editor->get_gui_base()->get_stylebox("DebuggerTabFG", "EditorStyles")); + panel_tabs->add_style_override("tab_bg", editor->get_gui_base()->get_stylebox("DebuggerTabBG", "EditorStyles")); + panel_tabs->set_custom_minimum_size(Size2(0, 228) * EDSCALE); + panel_tabs->set_v_size_flags(SIZE_EXPAND_FILL); + add_child(panel_tabs); + + { // Builds + panel_builds_tab = memnew(VBoxContainer); + panel_builds_tab->set_name(TTR("Builds")); + panel_builds_tab->set_h_size_flags(SIZE_EXPAND_FILL); + panel_tabs->add_child(panel_builds_tab); + + HBoxContainer *toolbar_hbc = memnew(HBoxContainer); + toolbar_hbc->set_h_size_flags(SIZE_EXPAND_FILL); + panel_builds_tab->add_child(toolbar_hbc); + + toolbar_hbc->add_spacer(); + + warnings_btn = memnew(ToolButton); + warnings_btn->set_text("Warnings"); + warnings_btn->set_toggle_mode(true); + warnings_btn->set_pressed(true); + warnings_btn->set_visible(false); + warnings_btn->set_focus_mode(FOCUS_NONE); + warnings_btn->connect("toggled", this, "_warnings_toggled"); + toolbar_hbc->add_child(warnings_btn); + + errors_btn = memnew(ToolButton); + errors_btn->set_text("Errors"); + errors_btn->set_toggle_mode(true); + errors_btn->set_pressed(true); + errors_btn->set_visible(false); + errors_btn->set_focus_mode(FOCUS_NONE); + errors_btn->connect("toggled", this, "_errors_toggled"); + toolbar_hbc->add_child(errors_btn); + + HSplitContainer *hsc = memnew(HSplitContainer); + hsc->set_h_size_flags(SIZE_EXPAND_FILL); + hsc->set_v_size_flags(SIZE_EXPAND_FILL); + panel_builds_tab->add_child(hsc); + + build_tabs_list = memnew(ItemList); + build_tabs_list->set_h_size_flags(SIZE_EXPAND_FILL); + build_tabs_list->connect("item_selected", this, "_build_tab_item_selected"); + hsc->add_child(build_tabs_list); + + build_tabs = memnew(TabContainer); + build_tabs->set_tab_align(TabContainer::ALIGN_LEFT); + build_tabs->set_h_size_flags(SIZE_EXPAND_FILL); + build_tabs->set_tabs_visible(false); + build_tabs->connect("tab_changed", this, "_build_tab_changed"); + hsc->add_child(build_tabs); + } +} + +MonoBottomPanel::~MonoBottomPanel() { + + singleton = NULL; +} + +void MonoBuildTab::_load_issues_from_file(const String &p_csv_file) { + + FileAccessRef f = FileAccess::open(p_csv_file, FileAccess::READ); + + if (!f) + return; + + while (!f->eof_reached()) { + Vector<String> csv_line = f->get_csv_line(); + + if (csv_line.size() == 1 && csv_line[0].empty()) + return; + + ERR_CONTINUE(csv_line.size() != 7); + + BuildIssue issue; + issue.warning = csv_line[0] == "warning"; + issue.file = csv_line[1]; + issue.line = csv_line[2].to_int(); + issue.column = csv_line[3].to_int(); + issue.code = csv_line[4]; + issue.message = csv_line[5]; + issue.project_file = csv_line[6]; + + if (issue.warning) + warning_count += 1; + else + error_count += 1; + + issues.push_back(issue); + } +} + +void MonoBuildTab::_update_issues_list() { + + issues_list->clear(); + + Ref<Texture> warning_icon = get_icon("Warning", "EditorIcons"); + Ref<Texture> error_icon = get_icon("Error", "EditorIcons"); + + for (int i = 0; i < issues.size(); i++) { + + const BuildIssue &issue = issues[i]; + + if (!(issue.warning ? warnings_visible : errors_visible)) + continue; + + String tooltip; + tooltip += String("Message: ") + issue.message; + tooltip += String("\nCode: ") + issue.code; + tooltip += String("\nType: ") + (issue.warning ? "warning" : "error"); + + String text; + + if (issue.file.length()) { + String sline = String::num_int64(issue.line); + String scolumn = String::num_int64(issue.column); + + text += issue.file + "("; + text += sline + ","; + text += scolumn + "): "; + + tooltip += "\nFile: " + issue.file; + tooltip += "\nLine: " + sline; + tooltip += "\nColumn: " + scolumn; + } + + if (issue.project_file.length()) { + tooltip += "\nProject: " + issue.project_file; + } + + text += issue.message; + + int line_break_idx = text.find("\n"); + issues_list->add_item(line_break_idx == -1 ? text : text.substr(0, line_break_idx), + issue.warning ? warning_icon : error_icon); + int index = issues_list->get_item_count() - 1; + issues_list->set_item_tooltip(index, tooltip); + issues_list->set_item_metadata(index, i); + } +} + +Ref<Texture> MonoBuildTab::get_icon_texture() const { + + // FIXME these icons were removed... find something better + + if (build_exited) { + if (build_result == RESULT_ERROR) { + return get_icon("DependencyChangedHl", "EditorIcons"); + } else { + return get_icon("DependencyOkHl", "EditorIcons"); + } + } else { + return get_icon("GraphTime", "EditorIcons"); + } +} + +MonoBuildInfo MonoBuildTab::get_build_info() { + + return build_info; +} + +void MonoBuildTab::on_build_start() { + + build_exited = false; + + issues.clear(); + warning_count = 0; + error_count = 0; + _update_issues_list(); + + MonoBottomPanel::get_singleton()->raise_build_tab(this); +} + +void MonoBuildTab::on_build_exit(BuildResult result) { + + build_exited = true; + build_result = result; + + _load_issues_from_file(logs_dir.plus_file("msbuild_issues.csv")); + _update_issues_list(); + + MonoBottomPanel::get_singleton()->raise_build_tab(this); +} + +void MonoBuildTab::on_build_exec_failed(const String &p_cause, const String &p_detailed) { + + build_exited = true; + build_result = RESULT_ERROR; + + issues_list->clear(); + + String tooltip; + + tooltip += "Message: " + (p_detailed.length() ? p_detailed : p_cause); + tooltip += "\nType: error"; + + int line_break_idx = p_cause.find("\n"); + issues_list->add_item(line_break_idx == -1 ? p_cause : p_cause.substr(0, line_break_idx), + get_icon("Error", "EditorIcons")); + int index = issues_list->get_item_count() - 1; + issues_list->set_item_tooltip(index, tooltip); + + MonoBottomPanel::get_singleton()->raise_build_tab(this); +} + +void MonoBuildTab::restart_build() { + + ERR_FAIL_COND(!build_exited); + GodotSharpBuilds::get_singleton()->restart_build(this); +} + +void MonoBuildTab::stop_build() { + + ERR_FAIL_COND(build_exited); + GodotSharpBuilds::get_singleton()->stop_build(this); +} + +void MonoBuildTab::_issue_activated(int p_idx) { + + ERR_FAIL_INDEX(p_idx, issues.size()); + + const BuildIssue &issue = issues[p_idx]; + + if (issue.project_file.empty() && issue.file.empty()) + return; + + String project_dir = issue.project_file.length() ? issue.project_file.get_base_dir() : build_info.solution.get_base_dir(); + + String file = project_dir.simplify_path().plus_file(issue.file.simplify_path()); + + if (!FileAccess::exists(file)) + return; + + file = ProjectSettings::get_singleton()->localize_path(file); + + if (file.begins_with("res://")) { + Ref<Script> script = ResourceLoader::load(file, CSharpLanguage::get_singleton()->get_type()); + + if (script.is_valid() && ScriptEditor::get_singleton()->edit(script, issue.line, issue.column)) { + EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT); + } + } +} + +void MonoBuildTab::_bind_methods() { + + ClassDB::bind_method("_issue_activated", &MonoBuildTab::_issue_activated); +} + +MonoBuildTab::MonoBuildTab(const MonoBuildInfo &p_build_info, const String &p_logs_dir) { + + build_info = p_build_info; + logs_dir = p_logs_dir; + + build_exited = false; + + issues_list = memnew(ItemList); + issues_list->set_v_size_flags(SIZE_EXPAND_FILL); + issues_list->connect("item_activated", this, "_issue_activated"); + add_child(issues_list); + + error_count = 0; + warning_count = 0; + + errors_visible = true; + warnings_visible = true; +} diff --git a/modules/mono/editor/mono_bottom_panel.h b/modules/mono/editor/mono_bottom_panel.h new file mode 100644 index 0000000000..909fa4b385 --- /dev/null +++ b/modules/mono/editor/mono_bottom_panel.h @@ -0,0 +1,145 @@ +/*************************************************************************/ +/* mono_bottom_panel.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef MONO_BOTTOM_PANEL_H +#define MONO_BOTTOM_PANEL_H + +#include "editor/editor_node.h" +#include "scene/gui/control.h" + +#include "mono_build_info.h" + +class MonoBuildTab; + +class MonoBottomPanel : public VBoxContainer { + + GDCLASS(MonoBottomPanel, VBoxContainer) + + EditorNode *editor; + + TabContainer *panel_tabs; + + VBoxContainer *panel_builds_tab; + + ItemList *build_tabs_list; + TabContainer *build_tabs; + + Button *warnings_btn; + Button *errors_btn; + + void _update_build_tabs_list(); + + void _build_tab_item_selected(int p_idx); + void _build_tab_changed(int p_idx); + + void _warnings_toggled(bool p_pressed); + void _errors_toggled(bool p_pressed); + + static MonoBottomPanel *singleton; + +protected: + void _notification(int p_what); + + static void _bind_methods(); + +public: + _FORCE_INLINE_ static MonoBottomPanel *get_singleton() { return singleton; } + + void add_build_tab(MonoBuildTab *p_build_tab); + void raise_build_tab(MonoBuildTab *p_build_tab); + + void show_build_tab(); + + MonoBottomPanel(EditorNode *p_editor = NULL); + ~MonoBottomPanel(); +}; + +class MonoBuildTab : public VBoxContainer { + + GDCLASS(MonoBuildTab, VBoxContainer) + +public: + enum BuildResult { + RESULT_ERROR, + RESULT_SUCCESS + }; + + struct BuildIssue { + bool warning; + String file; + int line; + int column; + String code; + String message; + String project_file; + }; + +private: + friend class MonoBottomPanel; + + bool build_exited; + BuildResult build_result; + + Vector<BuildIssue> issues; + ItemList *issues_list; + + int error_count; + int warning_count; + + bool errors_visible; + bool warnings_visible; + + String logs_dir; + + MonoBuildInfo build_info; + + void _load_issues_from_file(const String &p_csv_file); + void _update_issues_list(); + + void _issue_activated(int p_idx); + +protected: + static void _bind_methods(); + +public: + Ref<Texture> get_icon_texture() const; + + MonoBuildInfo get_build_info(); + + void on_build_start(); + void on_build_exit(BuildResult result); + void on_build_exec_failed(const String &p_cause, const String &p_detailed = String()); + + void restart_build(); + void stop_build(); + + MonoBuildTab(const MonoBuildInfo &p_build_info, const String &p_logs_dir); +}; + +#endif // MONO_BOTTOM_PANEL_H diff --git a/modules/mono/editor/mono_build_info.h b/modules/mono/editor/mono_build_info.h new file mode 100644 index 0000000000..f3b3e43b6d --- /dev/null +++ b/modules/mono/editor/mono_build_info.h @@ -0,0 +1,64 @@ +/*************************************************************************/ +/* mono_build_info.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef MONO_BUILD_INFO_H +#define MONO_BUILD_INFO_H + +#include "../mono_gd/gd_mono_utils.h" + +struct MonoBuildInfo { + + struct Hasher { + static _FORCE_INLINE_ uint32_t hash(const MonoBuildInfo &p_key) { + uint32_t hash = 0; + + GDMonoUtils::hash_combine(hash, p_key.solution.hash()); + GDMonoUtils::hash_combine(hash, p_key.configuration.hash()); + + return hash; + } + }; + + String solution; + String configuration; + Vector<String> custom_props; + + MonoBuildInfo() {} + + MonoBuildInfo(const String &p_solution, const String &p_config) { + solution = p_solution; + configuration = p_config; + } + + bool operator==(const MonoBuildInfo &p_b) const { + return p_b.solution == solution && p_b.configuration == configuration; + } +}; + +#endif // MONO_BUILD_INFO_H diff --git a/modules/mono/editor/monodevelop_instance.cpp b/modules/mono/editor/monodevelop_instance.cpp new file mode 100644 index 0000000000..a34d82ffcb --- /dev/null +++ b/modules/mono/editor/monodevelop_instance.cpp @@ -0,0 +1,81 @@ +/*************************************************************************/ +/* monodevelop_instance.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "monodevelop_instance.h" + +#include "../mono_gd/gd_mono.h" +#include "../mono_gd/gd_mono_class.h" + +void MonoDevelopInstance::execute(const Vector<String> &p_files) { + + ERR_FAIL_NULL(execute_method); + ERR_FAIL_COND(gc_handle.is_null()); + + MonoObject *ex = NULL; + + Variant files = p_files; + const Variant *args[1] = { &files }; + execute_method->invoke(gc_handle->get_target(), args, &ex); + + if (ex) { + mono_print_unhandled_exception(ex); + ERR_FAIL(); + } +} + +void MonoDevelopInstance::execute(const String &p_file) { + + Vector<String> files; + files.push_back(p_file); + execute(files); +} + +MonoDevelopInstance::MonoDevelopInstance(const String &p_solution) { + + _GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN) + + GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Editor", "MonoDevelopInstance"); + + MonoObject *obj = mono_object_new(TOOLS_DOMAIN, klass->get_raw()); + + GDMonoMethod *ctor = klass->get_method(".ctor", 1); + MonoObject *ex = NULL; + + Variant solution = p_solution; + const Variant *args[1] = { &solution }; + ctor->invoke(obj, args, &ex); + + if (ex) { + mono_print_unhandled_exception(ex); + ERR_FAIL(); + } + + gc_handle = MonoGCHandle::create_strong(obj); + execute_method = klass->get_method("Execute", 1); +} diff --git a/modules/mono/editor/monodevelop_instance.h b/modules/mono/editor/monodevelop_instance.h new file mode 100644 index 0000000000..9eb154eba1 --- /dev/null +++ b/modules/mono/editor/monodevelop_instance.h @@ -0,0 +1,50 @@ +/*************************************************************************/ +/* monodevelop_instance.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef MONODEVELOP_INSTANCE_H +#define MONODEVELOP_INSTANCE_H + +#include "reference.h" + +#include "../mono_gc_handle.h" +#include "../mono_gd/gd_mono_method.h" + +class MonoDevelopInstance { + + Ref<MonoGCHandle> gc_handle; + GDMonoMethod *execute_method; + +public: + void execute(const Vector<String> &p_files); + void execute(const String &p_files); + + MonoDevelopInstance(const String &p_solution); +}; + +#endif // MONODEVELOP_INSTANCE_H diff --git a/modules/mono/editor/net_solution.cpp b/modules/mono/editor/net_solution.cpp new file mode 100644 index 0000000000..fa60c310db --- /dev/null +++ b/modules/mono/editor/net_solution.cpp @@ -0,0 +1,130 @@ +/*************************************************************************/ +/* net_solution.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "net_solution.h" + +#include "os/dir_access.h" +#include "os/file_access.h" + +#include "../utils/path_utils.h" +#include "../utils/string_utils.h" +#include "csharp_project.h" + +#define SOLUTION_TEMPLATE \ + "Microsoft Visual Studio Solution File, Format Version 12.00\n" \ + "# Visual Studio 2012\n" \ + "%0\n" \ + "Global\n" \ + "\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n" \ + "%1\n" \ + "\tEndGlobalSection\n" \ + "\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n" \ + "%2\n" \ + "\tEndGlobalSection\n" \ + "EndGlobal\n" + +#define PROJECT_DECLARATION "Project(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"%0\", \"%1\", \"{%2}\"\nEndProject" + +#define SOLUTION_PLATFORMS_CONFIG "\t\%0|Any CPU = %0|Any CPU" + +#define PROJECT_PLATFORMS_CONFIG \ + "\t\t{%0}.%1|Any CPU.ActiveCfg = %1|Any CPU\n" \ + "\t\t{%0}.%1|Any CPU.Build.0 = %1|Any CPU" + +void NETSolution::add_new_project(const String &p_name, const String &p_guid, const Vector<String> &p_extra_configs) { + if (projects.has(p_name)) + WARN_PRINT("Overriding existing project."); + + ProjectInfo procinfo; + procinfo.guid = p_guid; + + procinfo.configs.push_back("Debug"); + procinfo.configs.push_back("Release"); + + for (int i = 0; i < p_extra_configs.size(); i++) { + procinfo.configs.push_back(p_extra_configs[i]); + } + + projects[p_name] = procinfo; +} + +Error NETSolution::save() { + bool dir_exists = DirAccess::exists(path); + ERR_EXPLAIN("The directory does not exist."); + ERR_FAIL_COND_V(!dir_exists, ERR_FILE_BAD_PATH); + + String projs_decl; + String sln_platform_cfg; + String proj_platform_cfg; + + for (Map<String, ProjectInfo>::Element *E = projects.front(); E; E = E->next()) { + const String &name = E->key(); + const ProjectInfo &procinfo = E->value(); + + projs_decl += sformat(PROJECT_DECLARATION, name, name + ".csproj", procinfo.guid); + + for (int i = 0; i < procinfo.configs.size(); i++) { + const String &config = procinfo.configs[i]; + + if (i != 0) { + sln_platform_cfg += "\n"; + proj_platform_cfg += "\n"; + } + + sln_platform_cfg += sformat(SOLUTION_PLATFORMS_CONFIG, config); + proj_platform_cfg += sformat(PROJECT_PLATFORMS_CONFIG, procinfo.guid, config); + } + } + + String content = sformat(SOLUTION_TEMPLATE, projs_decl, sln_platform_cfg, proj_platform_cfg); + + FileAccessRef file = FileAccess::open(path_join(path, name + ".sln"), FileAccess::WRITE); + ERR_FAIL_COND_V(!file, ERR_FILE_CANT_WRITE); + file->store_string(content); + file->close(); + + return OK; +} + +bool NETSolution::set_path(const String &p_existing_path) { + if (p_existing_path.is_abs_path()) { + path = p_existing_path; + } else { + String abspath; + if (!rel_path_to_abs(p_existing_path, abspath)) + return false; + path = abspath; + } + + return true; +} + +NETSolution::NETSolution(const String &p_name) { + name = p_name; +} diff --git a/modules/mono/editor/net_solution.h b/modules/mono/editor/net_solution.h new file mode 100644 index 0000000000..d7ccebb7df --- /dev/null +++ b/modules/mono/editor/net_solution.h @@ -0,0 +1,57 @@ +/*************************************************************************/ +/* net_solution.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef NET_SOLUTION_H +#define NET_SOLUTION_H + +#include "map.h" +#include "ustring.h" + +struct NETSolution { + String name; + + void add_new_project(const String &p_name, const String &p_guid, const Vector<String> &p_extra_configs = Vector<String>()); + + Error save(); + + bool set_path(const String &p_existing_path); + + NETSolution(const String &p_name); + +private: + struct ProjectInfo { + String guid; + Vector<String> configs; + }; + + String path; + Map<String, ProjectInfo> projects; +}; + +#endif // NET_SOLUTION_H diff --git a/modules/mono/glue/cs_files/Basis.cs b/modules/mono/glue/cs_files/Basis.cs new file mode 100644 index 0000000000..6a73ebd554 --- /dev/null +++ b/modules/mono/glue/cs_files/Basis.cs @@ -0,0 +1,475 @@ +using System; +using System.Runtime.InteropServices; + +namespace Godot +{ + [StructLayout(LayoutKind.Sequential)] + public struct Basis : IEquatable<Basis> + { + private static readonly Basis identity = new Basis + ( + new Vector3(1f, 0f, 0f), + new Vector3(0f, 1f, 0f), + new Vector3(0f, 0f, 1f) + ); + + private static readonly Basis[] orthoBases = new Basis[24] + { + new Basis(1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f), + new Basis(0f, -1f, 0f, 1f, 0f, 0f, 0f, 0f, 1f), + new Basis(-1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, 1f), + new Basis(0f, 1f, 0f, -1f, 0f, 0f, 0f, 0f, 1f), + new Basis(1f, 0f, 0f, 0f, 0f, -1f, 0f, 1f, 0f), + new Basis(0f, 0f, 1f, 1f, 0f, 0f, 0f, 1f, 0f), + new Basis(-1f, 0f, 0f, 0f, 0f, 1f, 0f, 1f, 0f), + new Basis(0f, 0f, -1f, -1f, 0f, 0f, 0f, 1f, 0f), + new Basis(1f, 0f, 0f, 0f, -1f, 0f, 0f, 0f, -1f), + new Basis(0f, 1f, 0f, 1f, 0f, 0f, 0f, 0f, -1f), + new Basis(-1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, -1f), + new Basis(0f, -1f, 0f, -1f, 0f, 0f, 0f, 0f, -1f), + new Basis(1f, 0f, 0f, 0f, 0f, 1f, 0f, -1f, 0f), + new Basis(0f, 0f, -1f, 1f, 0f, 0f, 0f, -1f, 0f), + new Basis(-1f, 0f, 0f, 0f, 0f, -1f, 0f, -1f, 0f), + new Basis(0f, 0f, 1f, -1f, 0f, 0f, 0f, -1f, 0f), + new Basis(0f, 0f, 1f, 0f, 1f, 0f, -1f, 0f, 0f), + new Basis(0f, -1f, 0f, 0f, 0f, 1f, -1f, 0f, 0f), + new Basis(0f, 0f, -1f, 0f, -1f, 0f, -1f, 0f, 0f), + new Basis(0f, 1f, 0f, 0f, 0f, -1f, -1f, 0f, 0f), + new Basis(0f, 0f, 1f, 0f, -1f, 0f, 1f, 0f, 0f), + new Basis(0f, 1f, 0f, 0f, 0f, 1f, 1f, 0f, 0f), + new Basis(0f, 0f, -1f, 0f, 1f, 0f, 1f, 0f, 0f), + new Basis(0f, -1f, 0f, 0f, 0f, -1f, 1f, 0f, 0f) + }; + + public Vector3 x; + public Vector3 y; + public Vector3 z; + + public static Basis Identity + { + get { return identity; } + } + + public Vector3 Scale + { + get + { + return new Vector3 + ( + new Vector3(this[0, 0], this[1, 0], this[2, 0]).length(), + new Vector3(this[0, 1], this[1, 1], this[2, 1]).length(), + new Vector3(this[0, 2], this[1, 2], this[2, 2]).length() + ); + } + } + + public Vector3 this[int index] + { + get + { + switch (index) + { + case 0: + return x; + case 1: + return y; + case 2: + return z; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + x = value; + return; + case 1: + y = value; + return; + case 2: + z = value; + return; + default: + throw new IndexOutOfRangeException(); + } + } + } + + public float this[int index, int axis] + { + get + { + switch (index) + { + case 0: + return x[axis]; + case 1: + return y[axis]; + case 2: + return z[axis]; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + x[axis] = value; + return; + case 1: + y[axis] = value; + return; + case 2: + z[axis] = value; + return; + default: + throw new IndexOutOfRangeException(); + } + } + } + + internal static Basis create_from_axes(Vector3 xAxis, Vector3 yAxis, Vector3 zAxis) + { + return new Basis + ( + new Vector3(xAxis.x, yAxis.x, zAxis.x), + new Vector3(xAxis.y, yAxis.y, zAxis.y), + new Vector3(xAxis.z, yAxis.z, zAxis.z) + ); + } + + public float determinant() + { + return this[0, 0] * (this[1, 1] * this[2, 2] - this[2, 1] * this[1, 2]) - + this[1, 0] * (this[0, 1] * this[2, 2] - this[2, 1] * this[0, 2]) + + this[2, 0] * (this[0, 1] * this[1, 2] - this[1, 1] * this[0, 2]); + } + + public Vector3 get_axis(int axis) + { + return new Vector3(this[0, axis], this[1, axis], this[2, axis]); + } + + public Vector3 get_euler() + { + Basis m = this.orthonormalized(); + + Vector3 euler; + + euler.y = Mathf.asin(m.x[2]); + + if (euler.y < Mathf.PI * 0.5f) + { + if (euler.y > -Mathf.PI * 0.5f) + { + euler.x = Mathf.atan2(-m.y[2], m.z[2]); + euler.z = Mathf.atan2(-m.x[1], m.x[0]); + } + else + { + euler.z = 0.0f; + euler.x = euler.z - Mathf.atan2(m.y[0], m.y[1]); + } + } + else + { + euler.z = 0f; + euler.x = Mathf.atan2(m.x[1], m.y[1]) - euler.z; + } + + return euler; + } + + public int get_orthogonal_index() + { + Basis orth = this; + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + float v = orth[i, j]; + + if (v > 0.5f) + v = 1.0f; + else if (v < -0.5f) + v = -1.0f; + else + v = 0f; + + orth[i, j] = v; + } + } + + for (int i = 0; i < 24; i++) + { + if (orthoBases[i] == orth) + return i; + } + + return 0; + } + + public Basis inverse() + { + Basis inv = this; + + float[] co = new float[3] + { + inv[1, 1] * inv[2, 2] - inv[1, 2] * inv[2, 1], + inv[1, 2] * inv[2, 0] - inv[1, 0] * inv[2, 2], + inv[1, 0] * inv[2, 1] - inv[1, 1] * inv[2, 0] + }; + + float det = inv[0, 0] * co[0] + inv[0, 1] * co[1] + inv[0, 2] * co[2]; + + if (det == 0) + { + return new Basis + ( + float.NaN, float.NaN, float.NaN, + float.NaN, float.NaN, float.NaN, + float.NaN, float.NaN, float.NaN + ); + } + + float s = 1.0f / det; + + inv = new Basis + ( + co[0] * s, + inv[0, 2] * inv[2, 1] - inv[0, 1] * inv[2, 2] * s, + inv[0, 1] * inv[1, 2] - inv[0, 2] * inv[1, 1] * s, + co[1] * s, + inv[0, 0] * inv[2, 2] - inv[0, 2] * inv[2, 0] * s, + inv[0, 2] * inv[1, 0] - inv[0, 0] * inv[1, 2] * s, + co[2] * s, + inv[0, 1] * inv[2, 0] - inv[0, 0] * inv[2, 1] * s, + inv[0, 0] * inv[1, 1] - inv[0, 1] * inv[1, 0] * s + ); + + return inv; + } + + public Basis orthonormalized() + { + Vector3 xAxis = get_axis(0); + Vector3 yAxis = get_axis(1); + Vector3 zAxis = get_axis(2); + + xAxis.normalize(); + yAxis = (yAxis - xAxis * (xAxis.dot(yAxis))); + yAxis.normalize(); + zAxis = (zAxis - xAxis * (xAxis.dot(zAxis)) - yAxis * (yAxis.dot(zAxis))); + zAxis.normalize(); + + return Basis.create_from_axes(xAxis, yAxis, zAxis); + } + + public Basis rotated(Vector3 axis, float phi) + { + return this * new Basis(axis, phi); + } + + public Basis scaled(Vector3 scale) + { + Basis m = this; + + m[0, 0] *= scale.x; + m[1, 0] *= scale.x; + m[2, 0] *= scale.x; + m[0, 1] *= scale.y; + m[1, 1] *= scale.y; + m[2, 1] *= scale.y; + m[0, 2] *= scale.z; + m[1, 2] *= scale.z; + m[2, 2] *= scale.z; + + return m; + } + + public float tdotx(Vector3 with) + { + return this[0, 0] * with[0] + this[1, 0] * with[1] + this[2, 0] * with[2]; + } + + public float tdoty(Vector3 with) + { + return this[0, 1] * with[0] + this[1, 1] * with[1] + this[2, 1] * with[2]; + } + + public float tdotz(Vector3 with) + { + return this[0, 2] * with[0] + this[1, 2] * with[1] + this[2, 2] * with[2]; + } + + public Basis transposed() + { + Basis tr = this; + + float temp = this[0, 1]; + this[0, 1] = this[1, 0]; + this[1, 0] = temp; + + temp = this[0, 2]; + this[0, 2] = this[2, 0]; + this[2, 0] = temp; + + temp = this[1, 2]; + this[1, 2] = this[2, 1]; + this[2, 1] = temp; + + return tr; + } + + public Vector3 xform(Vector3 v) + { + return new Vector3 + ( + this[0].dot(v), + this[1].dot(v), + this[2].dot(v) + ); + } + + public Vector3 xform_inv(Vector3 v) + { + return new Vector3 + ( + (this[0, 0] * v.x) + (this[1, 0] * v.y) + (this[2, 0] * v.z), + (this[0, 1] * v.x) + (this[1, 1] * v.y) + (this[2, 1] * v.z), + (this[0, 2] * v.x) + (this[1, 2] * v.y) + (this[2, 2] * v.z) + ); + } + + public Basis(Quat quat) + { + float s = 2.0f / quat.length_squared(); + + float xs = quat.x * s; + float ys = quat.y * s; + float zs = quat.z * s; + float wx = quat.w * xs; + float wy = quat.w * ys; + float wz = quat.w * zs; + float xx = quat.x * xs; + float xy = quat.x * ys; + float xz = quat.x * zs; + float yy = quat.y * ys; + float yz = quat.y * zs; + float zz = quat.z * zs; + + this.x = new Vector3(1.0f - (yy + zz), xy - wz, xz + wy); + this.y = new Vector3(xy + wz, 1.0f - (xx + zz), yz - wx); + this.z = new Vector3(xz - wy, yz + wx, 1.0f - (xx + yy)); + } + + public Basis(Vector3 axis, float phi) + { + Vector3 axis_sq = new Vector3(axis.x * axis.x, axis.y * axis.y, axis.z * axis.z); + + float cosine = Mathf.cos(phi); + float sine = Mathf.sin(phi); + + this.x = new Vector3 + ( + axis_sq.x + cosine * (1.0f - axis_sq.x), + axis.x * axis.y * (1.0f - cosine) - axis.z * sine, + axis.z * axis.x * (1.0f - cosine) + axis.y * sine + ); + + this.y = new Vector3 + ( + axis.x * axis.y * (1.0f - cosine) + axis.z * sine, + axis_sq.y + cosine * (1.0f - axis_sq.y), + axis.y * axis.z * (1.0f - cosine) - axis.x * sine + ); + + this.z = new Vector3 + ( + axis.z * axis.x * (1.0f - cosine) - axis.y * sine, + axis.y * axis.z * (1.0f - cosine) + axis.x * sine, + axis_sq.z + cosine * (1.0f - axis_sq.z) + ); + } + + public Basis(Vector3 xAxis, Vector3 yAxis, Vector3 zAxis) + { + this.x = xAxis; + this.y = yAxis; + this.z = zAxis; + } + + public Basis(float xx, float xy, float xz, float yx, float yy, float yz, float zx, float zy, float zz) + { + this.x = new Vector3(xx, xy, xz); + this.y = new Vector3(yx, yy, yz); + this.z = new Vector3(zx, zy, zz); + } + + public static Basis operator *(Basis left, Basis right) + { + return new Basis + ( + right.tdotx(left[0]), right.tdoty(left[0]), right.tdotz(left[0]), + right.tdotx(left[1]), right.tdoty(left[1]), right.tdotz(left[1]), + right.tdotx(left[2]), right.tdoty(left[2]), right.tdotz(left[2]) + ); + } + + public static bool operator ==(Basis left, Basis right) + { + return left.Equals(right); + } + + public static bool operator !=(Basis left, Basis right) + { + return !left.Equals(right); + } + + public override bool Equals(object obj) + { + if (obj is Basis) + { + return Equals((Basis)obj); + } + + return false; + } + + public bool Equals(Basis other) + { + return x.Equals(other.x) && y.Equals(other.y) && z.Equals(other.z); + } + + public override int GetHashCode() + { + return x.GetHashCode() ^ y.GetHashCode() ^ z.GetHashCode(); + } + + public override string ToString() + { + return String.Format("({0}, {1}, {2})", new object[] + { + this.x.ToString(), + this.y.ToString(), + this.z.ToString() + }); + } + + public string ToString(string format) + { + return String.Format("({0}, {1}, {2})", new object[] + { + this.x.ToString(format), + this.y.ToString(format), + this.z.ToString(format) + }); + } + } +} diff --git a/modules/mono/glue/cs_files/Color.cs b/modules/mono/glue/cs_files/Color.cs new file mode 100644 index 0000000000..df88a46832 --- /dev/null +++ b/modules/mono/glue/cs_files/Color.cs @@ -0,0 +1,590 @@ +using System;
+
+namespace Godot
+{
+ public struct Color : IEquatable<Color>
+ {
+ public float r;
+ public float g;
+ public float b;
+ public float a;
+
+ public int r8
+ {
+ get
+ {
+ return (int)(r * 255.0f);
+ }
+ }
+
+ public int g8
+ {
+ get
+ {
+ return (int)(g * 255.0f);
+ }
+ }
+
+ public int b8
+ {
+ get
+ {
+ return (int)(b * 255.0f);
+ }
+ }
+
+ public int a8
+ {
+ get
+ {
+ return (int)(a * 255.0f);
+ }
+ }
+
+ public float h
+ {
+ get
+ {
+ float max = Mathf.max(r, Mathf.max(g, b));
+ float min = Mathf.min(r, Mathf.min(g, b));
+
+ float delta = max - min;
+
+ if (delta == 0)
+ return 0;
+
+ float h;
+
+ if (r == max)
+ h = (g - b) / delta; // Between yellow & magenta
+ else if (g == max)
+ h = 2 + (b - r) / delta; // Between cyan & yellow
+ else
+ h = 4 + (r - g) / delta; // Between magenta & cyan
+
+ h /= 6.0f;
+
+ if (h < 0)
+ h += 1.0f;
+
+ return h;
+ }
+ set
+ {
+ this = from_hsv(value, s, v);
+ }
+ }
+
+ public float s
+ {
+ get
+ {
+ float max = Mathf.max(r, Mathf.max(g, b));
+ float min = Mathf.min(r, Mathf.min(g, b));
+
+ float delta = max - min;
+
+ return max != 0 ? delta / max : 0;
+ }
+ set
+ {
+ this = from_hsv(h, value, v);
+ }
+ }
+
+ public float v
+ {
+ get
+ {
+ return Mathf.max(r, Mathf.max(g, b));
+ }
+ set
+ {
+ this = from_hsv(h, s, value);
+ }
+ }
+
+ private static readonly Color black = new Color(0f, 0f, 0f, 1.0f);
+
+ public Color Black
+ {
+ get
+ {
+ return black;
+ }
+ }
+
+ public float this [int index]
+ {
+ get
+ {
+ switch (index)
+ {
+ case 0:
+ return r;
+ case 1:
+ return g;
+ case 2:
+ return b;
+ case 3:
+ return a;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ set
+ {
+ switch (index)
+ {
+ case 0:
+ r = value;
+ return;
+ case 1:
+ g = value;
+ return;
+ case 2:
+ b = value;
+ return;
+ case 3:
+ a = value;
+ return;
+ default:
+ throw new IndexOutOfRangeException();
+ }
+ }
+ }
+
+ public static void to_hsv(Color color, out float hue, out float saturation, out float value)
+ {
+ int max = Mathf.max(color.r8, Mathf.max(color.g8, color.b8));
+ int min = Mathf.min(color.r8, Mathf.min(color.g8, color.b8));
+
+ float delta = max - min;
+
+ if (delta == 0)
+ {
+ hue = 0;
+ }
+ else
+ {
+ if (color.r == max)
+ hue = (color.g - color.b) / delta; // Between yellow & magenta
+ else if (color.g == max)
+ hue = 2 + (color.b - color.r) / delta; // Between cyan & yellow
+ else
+ hue = 4 + (color.r - color.g) / delta; // Between magenta & cyan
+
+ hue /= 6.0f;
+
+ if (hue < 0)
+ hue += 1.0f;
+ }
+
+ saturation = (max == 0) ? 0 : 1f - (1f * min / max);
+ value = max / 255f;
+ }
+
+ public static Color from_hsv(float hue, float saturation, float value, float alpha = 1.0f)
+ {
+ if (saturation == 0)
+ {
+ // acp_hromatic (grey)
+ return new Color(value, value, value, alpha);
+ }
+
+ int i;
+ float f, p, q, t;
+
+ hue *= 6.0f;
+ hue %= 6f;
+ i = (int)hue;
+
+ f = hue - i;
+ p = value * (1 - saturation);
+ q = value * (1 - saturation * f);
+ t = value * (1 - saturation * (1 - f));
+
+ switch (i)
+ {
+ case 0: // Red is the dominant color
+ return new Color(value, t, p, alpha);
+ case 1: // Green is the dominant color
+ return new Color(q, value, p, alpha);
+ case 2:
+ return new Color(p, value, t, alpha);
+ case 3: // Blue is the dominant color
+ return new Color(p, q, value, alpha);
+ case 4:
+ return new Color(t, p, value, alpha);
+ default: // (5) Red is the dominant color
+ return new Color(value, p, q, alpha);
+ }
+ }
+
+ public Color blend(Color over)
+ {
+ Color res;
+
+ float sa = 1.0f - over.a;
+ res.a = a * sa + over.a;
+
+ if (res.a == 0)
+ {
+ return new Color(0, 0, 0, 0);
+ }
+ else
+ {
+ res.r = (r * a * sa + over.r * over.a) / res.a;
+ res.g = (g * a * sa + over.g * over.a) / res.a;
+ res.b = (b * a * sa + over.b * over.a) / res.a;
+ }
+
+ return res;
+ }
+
+ public Color contrasted()
+ {
+ return new Color(
+ (r + 0.5f) % 1.0f,
+ (g + 0.5f) % 1.0f,
+ (b + 0.5f) % 1.0f
+ );
+ }
+
+ public float gray()
+ {
+ return (r + g + b) / 3.0f;
+ }
+
+ public Color inverted()
+ {
+ return new Color(
+ 1.0f - r,
+ 1.0f - g,
+ 1.0f - b
+ );
+ }
+
+ public Color linear_interpolate(Color b, float t)
+ {
+ Color res = this;
+
+ res.r += (t * (b.r - this.r));
+ res.g += (t * (b.g - this.g));
+ res.b += (t * (b.b - this.b));
+ res.a += (t * (b.a - this.a));
+
+ return res;
+ }
+
+ public int to_32()
+ {
+ int c = (byte)(a * 255);
+ c <<= 8;
+ c |= (byte)(r * 255);
+ c <<= 8;
+ c |= (byte)(g * 255);
+ c <<= 8;
+ c |= (byte)(b * 255);
+
+ return c;
+ }
+
+ public int to_ARGB32()
+ {
+ int c = (byte)(a * 255);
+ c <<= 8;
+ c |= (byte)(r * 255);
+ c <<= 8;
+ c |= (byte)(g * 255);
+ c <<= 8;
+ c |= (byte)(b * 255);
+
+ return c;
+ }
+
+ public string to_html(bool include_alpha = true)
+ {
+ String txt = string.Empty;
+
+ txt += _to_hex(r);
+ txt += _to_hex(g);
+ txt += _to_hex(b);
+
+ if (include_alpha)
+ txt = _to_hex(a) + txt;
+
+ return txt;
+ }
+
+ public Color(float r, float g, float b, float a = 1.0f)
+ {
+ this.r = r;
+ this.g = g;
+ this.b = b;
+ this.a = a;
+ }
+
+ public Color(int rgba)
+ {
+ this.a = (rgba & 0xFF) / 255.0f;
+ rgba >>= 8;
+ this.b = (rgba & 0xFF) / 255.0f;
+ rgba >>= 8;
+ this.g = (rgba & 0xFF) / 255.0f;
+ rgba >>= 8;
+ this.r = (rgba & 0xFF) / 255.0f;
+ }
+
+ private static float _parse_col(string str, int ofs)
+ {
+ int ig = 0;
+
+ for (int i = 0; i < 2; i++)
+ {
+ int c = str[i + ofs];
+ int v = 0;
+
+ if (c >= '0' && c <= '9')
+ {
+ v = c - '0';
+ }
+ else if (c >= 'a' && c <= 'f')
+ {
+ v = c - 'a';
+ v += 10;
+ }
+ else if (c >= 'A' && c <= 'F')
+ {
+ v = c - 'A';
+ v += 10;
+ }
+ else
+ {
+ return -1;
+ }
+
+ if (i == 0)
+ ig += v * 16;
+ else
+ ig += v;
+ }
+
+ return ig;
+ }
+
+ private String _to_hex(float val)
+ {
+ int v = (int)Mathf.clamp(val * 255.0f, 0, 255);
+
+ string ret = string.Empty;
+
+ for (int i = 0; i < 2; i++)
+ {
+ char[] c = { (char)0, (char)0 };
+ int lv = v & 0xF;
+
+ if (lv < 10)
+ c[0] = (char)('0' + lv);
+ else
+ c[0] = (char)('a' + lv - 10);
+
+ v >>= 4;
+ ret = c + ret;
+ }
+
+ return ret;
+ }
+
+ internal static bool html_is_valid(string color)
+ {
+ if (color.Length == 0)
+ return false;
+
+ if (color[0] == '#')
+ color = color.Substring(1, color.Length - 1);
+
+ bool alpha = false;
+
+ if (color.Length == 8)
+ alpha = true;
+ else if (color.Length == 6)
+ alpha = false;
+ else
+ return false;
+
+ if (alpha)
+ {
+ if ((int)_parse_col(color, 0) < 0)
+ return false;
+ }
+
+ int from = alpha ? 2 : 0;
+
+ if ((int)_parse_col(color, from + 0) < 0)
+ return false;
+ if ((int)_parse_col(color, from + 2) < 0)
+ return false;
+ if ((int)_parse_col(color, from + 4) < 0)
+ return false;
+
+ return true;
+ }
+
+ public static Color Color8(byte r8, byte g8, byte b8, byte a8)
+ {
+ return new Color((float)r8 / 255f, (float)g8 / 255f, (float)b8 / 255f, (float)a8 / 255f);
+ }
+
+ public Color(string rgba)
+ {
+ if (rgba.Length == 0)
+ {
+ r = 0f;
+ g = 0f;
+ b = 0f;
+ a = 1.0f;
+ return;
+ }
+
+ if (rgba[0] == '#')
+ rgba = rgba.Substring(1);
+
+ bool alpha = false;
+
+ if (rgba.Length == 8)
+ {
+ alpha = true;
+ }
+ else if (rgba.Length == 6)
+ {
+ alpha = false;
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException("Invalid color code. Length is " + rgba.Length + " but a length of 6 or 8 is expected: " + rgba);
+ }
+
+ if (alpha)
+ {
+ a = _parse_col(rgba, 0);
+
+ if (a < 0)
+ throw new ArgumentOutOfRangeException("Invalid color code. Alpha is " + a + " but zero or greater is expected: " + rgba);
+ }
+ else
+ {
+ a = 1.0f;
+ }
+
+ int from = alpha ? 2 : 0;
+
+ r = _parse_col(rgba, from + 0);
+
+ if (r < 0)
+ throw new ArgumentOutOfRangeException("Invalid color code. Red is " + r + " but zero or greater is expected: " + rgba);
+
+ g = _parse_col(rgba, from + 2);
+
+ if (g < 0)
+ throw new ArgumentOutOfRangeException("Invalid color code. Green is " + g + " but zero or greater is expected: " + rgba);
+
+ b = _parse_col(rgba, from + 4);
+
+ if (b < 0)
+ throw new ArgumentOutOfRangeException("Invalid color code. Blue is " + b + " but zero or greater is expected: " + rgba);
+ }
+
+ public static bool operator ==(Color left, Color right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Color left, Color right)
+ {
+ return !left.Equals(right);
+ }
+
+ public static bool operator <(Color left, Color right)
+ {
+ if (left.r == right.r)
+ {
+ if (left.g == right.g)
+ {
+ if (left.b == right.b)
+ return (left.a < right.a);
+ else
+ return (left.b < right.b);
+ }
+ else
+ {
+ return left.g < right.g;
+ }
+ }
+
+ return left.r < right.r;
+ }
+
+ public static bool operator >(Color left, Color right)
+ {
+ if (left.r == right.r)
+ {
+ if (left.g == right.g)
+ {
+ if (left.b == right.b)
+ return (left.a > right.a);
+ else
+ return (left.b > right.b);
+ }
+ else
+ {
+ return left.g > right.g;
+ }
+ }
+
+ return left.r > right.r;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is Color)
+ {
+ return Equals((Color)obj);
+ }
+
+ return false;
+ }
+
+ public bool Equals(Color other)
+ {
+ return r == other.r && g == other.g && b == other.b && a == other.a;
+ }
+
+ public override int GetHashCode()
+ {
+ return r.GetHashCode() ^ g.GetHashCode() ^ b.GetHashCode() ^ a.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return String.Format("{0},{1},{2},{3}", new object[]
+ {
+ this.r.ToString(),
+ this.g.ToString(),
+ this.b.ToString(),
+ this.a.ToString()
+ });
+ }
+
+ public string ToString(string format)
+ {
+ return String.Format("{0},{1},{2},{3}", new object[]
+ {
+ this.r.ToString(format),
+ this.g.ToString(format),
+ this.b.ToString(format),
+ this.a.ToString(format)
+ });
+ }
+ }
+}
diff --git a/modules/mono/glue/cs_files/Error.cs b/modules/mono/glue/cs_files/Error.cs new file mode 100644 index 0000000000..3f4a92603d --- /dev/null +++ b/modules/mono/glue/cs_files/Error.cs @@ -0,0 +1,48 @@ +namespace Godot +{ + public enum Error : int + { + OK = 0, + FAILED = 1, + ERR_UNAVAILABLE = 2, + ERR_UNCONFIGURED = 3, + ERR_UNAUTHORIZED = 4, + ERR_PARAMETER_RANGE_ERROR = 5, + ERR_OUT_OF_MEMORY = 6, + ERR_FILE_NOT_FOUND = 7, + ERR_FILE_BAD_DRIVE = 8, + ERR_FILE_BAD_PATH = 9, + ERR_FILE_NO_PERMISSION = 10, + ERR_FILE_ALREADY_IN_USE = 11, + ERR_FILE_CANT_OPEN = 12, + ERR_FILE_CANT_WRITE = 13, + ERR_FILE_CANT_READ = 14, + ERR_FILE_UNRECOGNIZED = 15, + ERR_FILE_CORRUPT = 16, + ERR_FILE_MISSING_DEPENDENCIES = 17, + ERR_FILE_EOF = 18, + ERR_CANT_OPEN = 19, + ERR_CANT_CREATE = 20, + ERR_PARSE_ERROR = 43, + ERROR_QUERY_FAILED = 21, + ERR_ALREADY_IN_USE = 22, + ERR_LOCKED = 23, + ERR_TIMEOUT = 24, + ERR_CANT_AQUIRE_RESOURCE = 28, + ERR_INVALID_DATA = 30, + ERR_INVALID_PARAMETER = 31, + ERR_ALREADY_EXISTS = 32, + ERR_DOES_NOT_EXIST = 33, + ERR_DATABASE_CANT_READ = 34, + ERR_DATABASE_CANT_WRITE = 35, + ERR_COMPILATION_FAILED = 36, + ERR_METHOD_NOT_FOUND = 37, + ERR_LINK_FAILED = 38, + ERR_SCRIPT_FAILED = 39, + ERR_CYCLIC_LINK = 40, + ERR_BUSY = 44, + ERR_HELP = 46, + ERR_BUG = 47, + ERR_WTF = 49 + } +} diff --git a/modules/mono/glue/cs_files/ExportAttribute.cs b/modules/mono/glue/cs_files/ExportAttribute.cs new file mode 100644 index 0000000000..af3f603d6d --- /dev/null +++ b/modules/mono/glue/cs_files/ExportAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace Godot +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class ExportAttribute : Attribute + { + private int hint; + private string hint_string; + private int usage; + + public ExportAttribute(int hint = GD.PROPERTY_HINT_NONE, string hint_string = "", int usage = GD.PROPERTY_USAGE_DEFAULT) + { + this.hint = hint; + this.hint_string = hint_string; + this.usage = usage; + } + } +} diff --git a/modules/mono/glue/cs_files/GD.cs b/modules/mono/glue/cs_files/GD.cs new file mode 100644 index 0000000000..40a42d23b4 --- /dev/null +++ b/modules/mono/glue/cs_files/GD.cs @@ -0,0 +1,191 @@ +using System; + +namespace Godot +{ + public static class GD + { + /*{GodotGlobalConstants}*/ + + public static object bytes2var(byte[] bytes) + { + return NativeCalls.godot_icall_Godot_bytes2var(bytes); + } + + public static object convert(object what, int type) + { + return NativeCalls.godot_icall_Godot_convert(what, type); + } + + public static float db2linear(float db) + { + return (float)Math.Exp(db * 0.11512925464970228420089957273422); + } + + public static float dectime(float value, float amount, float step) + { + float sgn = value < 0 ? -1.0f : 1.0f; + float val = Mathf.abs(value); + val -= amount * step; + if (val < 0.0f) + val = 0.0f; + return val * sgn; + } + + public static FuncRef funcref(Object instance, string funcname) + { + var ret = new FuncRef(); + ret.SetInstance(instance); + ret.SetFunction(funcname); + return ret; + } + + public static int hash(object var) + { + return NativeCalls.godot_icall_Godot_hash(var); + } + + public static Object instance_from_id(int instance_id) + { + return NativeCalls.godot_icall_Godot_instance_from_id(instance_id); + } + + public static double linear2db(double linear) + { + return Math.Log(linear) * 8.6858896380650365530225783783321; + } + + public static Resource load(string path) + { + return ResourceLoader.Load(path); + } + + public static void print(params object[] what) + { + NativeCalls.godot_icall_Godot_print(what); + } + + public static void print_stack() + { + print(System.Environment.StackTrace); + } + + public static void printerr(params object[] what) + { + NativeCalls.godot_icall_Godot_printerr(what); + } + + public static void printraw(params object[] what) + { + NativeCalls.godot_icall_Godot_printraw(what); + } + + public static void prints(params object[] what) + { + NativeCalls.godot_icall_Godot_prints(what); + } + + public static void printt(params object[] what) + { + NativeCalls.godot_icall_Godot_printt(what); + } + + public static int[] range(int length) + { + int[] ret = new int[length]; + + for (int i = 0; i < length; i++) + { + ret[i] = i; + } + + return ret; + } + + public static int[] range(int from, int to) + { + if (to < from) + return new int[0]; + + int[] ret = new int[to - from]; + + for (int i = from; i < to; i++) + { + ret[i - from] = i; + } + + return ret; + } + + public static int[] range(int from, int to, int increment) + { + if (to < from && increment > 0) + return new int[0]; + if (to > from && increment < 0) + return new int[0]; + + // Calculate count + int count = 0; + + if (increment > 0) + count = ((to - from - 1) / increment) + 1; + else + count = ((from - to - 1) / -increment) + 1; + + int[] ret = new int[count]; + + if (increment > 0) + { + int idx = 0; + for (int i = from; i < to; i += increment) + { + ret[idx++] = i; + } + } + else + { + int idx = 0; + for (int i = from; i > to; i += increment) + { + ret[idx++] = i; + } + } + + return ret; + } + + public static void seed(int seed) + { + NativeCalls.godot_icall_Godot_seed(seed); + } + + public static string str(params object[] what) + { + return NativeCalls.godot_icall_Godot_str(what); + } + + public static object str2var(string str) + { + return NativeCalls.godot_icall_Godot_str2var(str); + } + + public static bool type_exists(string type) + { + return NativeCalls.godot_icall_Godot_type_exists(type); + } + + public static byte[] var2bytes(object var) + { + return NativeCalls.godot_icall_Godot_var2bytes(var); + } + + public static string var2str(object var) + { + return NativeCalls.godot_icall_Godot_var2str(var); + } + + public static WeakRef weakref(Object obj) + { + return NativeCalls.godot_icall_Godot_weakref(Object.GetPtr(obj)); + } + } +} diff --git a/modules/mono/glue/cs_files/GodotMethodAttribute.cs b/modules/mono/glue/cs_files/GodotMethodAttribute.cs new file mode 100644 index 0000000000..21333c8dab --- /dev/null +++ b/modules/mono/glue/cs_files/GodotMethodAttribute.cs @@ -0,0 +1,17 @@ +using System; + +namespace Godot +{ + [AttributeUsage(AttributeTargets.Method, Inherited = true)] + internal class GodotMethodAttribute : Attribute + { + private string methodName; + + public string MethodName { get { return methodName; } } + + public GodotMethodAttribute(string methodName) + { + this.methodName = methodName; + } + } +} diff --git a/modules/mono/glue/cs_files/GodotSynchronizationContext.cs b/modules/mono/glue/cs_files/GodotSynchronizationContext.cs new file mode 100644 index 0000000000..eb4d0bed1c --- /dev/null +++ b/modules/mono/glue/cs_files/GodotSynchronizationContext.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; + +namespace Godot +{ + public class GodotSynchronizationContext : SynchronizationContext + { + private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> queue = new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>(); + + public override void Post(SendOrPostCallback d, object state) + { + queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state)); + } + + public void ExecutePendingContinuations() + { + KeyValuePair<SendOrPostCallback, object> workItem; + while (queue.TryTake(out workItem)) + { + workItem.Key(workItem.Value); + } + } + } +} diff --git a/modules/mono/glue/cs_files/GodotTaskScheduler.cs b/modules/mono/glue/cs_files/GodotTaskScheduler.cs new file mode 100644 index 0000000000..f587645a49 --- /dev/null +++ b/modules/mono/glue/cs_files/GodotTaskScheduler.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Godot +{ + public class GodotTaskScheduler : TaskScheduler + { + private GodotSynchronizationContext Context { get; set; } + private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); + + public GodotTaskScheduler() + { + Context = new GodotSynchronizationContext(); + } + + protected sealed override void QueueTask(Task task) + { + lock (_tasks) + { + _tasks.AddLast(task); + } + } + + protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) + { + if (SynchronizationContext.Current != Context) + { + return false; + } + + if (taskWasPreviouslyQueued) + { + TryDequeue(task); + } + + return base.TryExecuteTask(task); + } + + protected sealed override bool TryDequeue(Task task) + { + lock (_tasks) + { + return _tasks.Remove(task); + } + } + + protected sealed override IEnumerable<Task> GetScheduledTasks() + { + lock (_tasks) + { + return _tasks.ToArray(); + } + } + + public void Activate() + { + SynchronizationContext.SetSynchronizationContext(Context); + ExecuteQueuedTasks(); + Context.ExecutePendingContinuations(); + } + + private void ExecuteQueuedTasks() + { + while (true) + { + Task task; + + lock (_tasks) + { + if (_tasks.Any()) + { + task = _tasks.First.Value; + _tasks.RemoveFirst(); + } + else + { + break; + } + } + + if (task != null) + { + if (!TryExecuteTask(task)) + { + throw new InvalidOperationException(); + } + } + } + } + } +} diff --git a/modules/mono/glue/cs_files/IAwaitable.cs b/modules/mono/glue/cs_files/IAwaitable.cs new file mode 100644 index 0000000000..0397957d00 --- /dev/null +++ b/modules/mono/glue/cs_files/IAwaitable.cs @@ -0,0 +1,12 @@ +namespace Godot +{ + public interface IAwaitable + { + IAwaiter GetAwaiter(); + } + + public interface IAwaitable<out TResult> + { + IAwaiter<TResult> GetAwaiter(); + } +} diff --git a/modules/mono/glue/cs_files/IAwaiter.cs b/modules/mono/glue/cs_files/IAwaiter.cs new file mode 100644 index 0000000000..73c71b5634 --- /dev/null +++ b/modules/mono/glue/cs_files/IAwaiter.cs @@ -0,0 +1,19 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Godot +{ + public interface IAwaiter : INotifyCompletion + { + bool IsCompleted { get; } + + void GetResult(); + } + + public interface IAwaiter<out TResult> : INotifyCompletion + { + bool IsCompleted { get; } + + TResult GetResult(); + } +} diff --git a/modules/mono/glue/cs_files/MarshalUtils.cs b/modules/mono/glue/cs_files/MarshalUtils.cs new file mode 100644 index 0000000000..5d40111339 --- /dev/null +++ b/modules/mono/glue/cs_files/MarshalUtils.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace Godot +{ + internal static class MarshalUtils + { + private static Dictionary<object, object> ArraysToDictionary(object[] keys, object[] values) + { + Dictionary<object, object> ret = new Dictionary<object, object>(); + + for (int i = 0; i < keys.Length; i++) + { + ret.Add(keys[i], values[i]); + } + + return ret; + } + + private static void DictionaryToArrays(Dictionary<object, object> from, out object[] keysTo, out object[] valuesTo) + { + Dictionary<object, object>.KeyCollection keys = from.Keys; + keysTo = new object[keys.Count]; + keys.CopyTo(keysTo, 0); + + Dictionary<object, object>.ValueCollection values = from.Values; + valuesTo = new object[values.Count]; + values.CopyTo(valuesTo, 0); + } + + private static Type GetDictionaryType() + { + return typeof(Dictionary<object, object>); + } + } +} diff --git a/modules/mono/glue/cs_files/Mathf.cs b/modules/mono/glue/cs_files/Mathf.cs new file mode 100644 index 0000000000..cb0eb1acdd --- /dev/null +++ b/modules/mono/glue/cs_files/Mathf.cs @@ -0,0 +1,234 @@ +using System; + +namespace Godot +{ + public static class Mathf + { + public const float PI = 3.14159274f; + public const float Epsilon = 1e-06f; + + private const float Deg2RadConst = 0.0174532924f; + private const float Rad2DegConst = 57.29578f; + + public static float abs(float s) + { + return Math.Abs(s); + } + + public static float acos(float s) + { + return (float)Math.Acos(s); + } + + public static float asin(float s) + { + return (float)Math.Asin(s); + } + + public static float atan(float s) + { + return (float)Math.Atan(s); + } + + public static float atan2(float x, float y) + { + return (float)Math.Atan2(x, y); + } + + public static float ceil(float s) + { + return (float)Math.Ceiling(s); + } + + public static float clamp(float val, float min, float max) + { + if (val < min) + { + return min; + } + else if (val > max) + { + return max; + } + + return val; + } + + public static float cos(float s) + { + return (float)Math.Cos(s); + } + + public static float cosh(float s) + { + return (float)Math.Cosh(s); + } + + public static int decimals(float step) + { + return decimals(step); + } + + public static int decimals(decimal step) + { + return BitConverter.GetBytes(decimal.GetBits(step)[3])[2]; + } + + public static float deg2rad(float deg) + { + return deg * Deg2RadConst; + } + + public static float ease(float s, float curve) + { + if (s < 0f) + { + s = 0f; + } + else if (s > 1.0f) + { + s = 1.0f; + } + + if (curve > 0f) + { + if (curve < 1.0f) + { + return 1.0f - pow(1.0f - s, 1.0f / curve); + } + + return pow(s, curve); + } + else if (curve < 0f) + { + if (s < 0.5f) + { + return pow(s * 2.0f, -curve) * 0.5f; + } + + return (1.0f - pow(1.0f - (s - 0.5f) * 2.0f, -curve)) * 0.5f + 0.5f; + } + + return 0f; + } + + public static float exp(float s) + { + return (float)Math.Exp(s); + } + + public static float floor(float s) + { + return (float)Math.Floor(s); + } + + public static float fposmod(float x, float y) + { + if (x >= 0f) + { + return x % y; + } + else + { + return y - (-x % y); + } + } + + public static float lerp(float from, float to, float weight) + { + return from + (to - from) * clamp(weight, 0f, 1f); + } + + public static float log(float s) + { + return (float)Math.Log(s); + } + + public static int max(int a, int b) + { + return (a > b) ? a : b; + } + + public static float max(float a, float b) + { + return (a > b) ? a : b; + } + + public static int min(int a, int b) + { + return (a < b) ? a : b; + } + + public static float min(float a, float b) + { + return (a < b) ? a : b; + } + + public static int nearest_po2(int val) + { + val--; + val |= val >> 1; + val |= val >> 2; + val |= val >> 4; + val |= val >> 8; + val |= val >> 16; + val++; + return val; + } + + public static float pow(float x, float y) + { + return (float)Math.Pow(x, y); + } + + public static float rad2deg(float rad) + { + return rad * Rad2DegConst; + } + + public static float round(float s) + { + return (float)Math.Round(s); + } + + public static float sign(float s) + { + return (s < 0f) ? -1f : 1f; + } + + public static float sin(float s) + { + return (float)Math.Sin(s); + } + + public static float sinh(float s) + { + return (float)Math.Sinh(s); + } + + public static float sqrt(float s) + { + return (float)Math.Sqrt(s); + } + + public static float stepify(float s, float step) + { + if (step != 0f) + { + s = floor(s / step + 0.5f) * step; + } + + return s; + } + + public static float tan(float s) + { + return (float)Math.Tan(s); + } + + public static float tanh(float s) + { + return (float)Math.Tanh(s); + } + } +} diff --git a/modules/mono/glue/cs_files/Plane.cs b/modules/mono/glue/cs_files/Plane.cs new file mode 100644 index 0000000000..ada6e465ac --- /dev/null +++ b/modules/mono/glue/cs_files/Plane.cs @@ -0,0 +1,209 @@ +using System;
+
+namespace Godot
+{
+ public struct Plane : IEquatable<Plane>
+ {
+ Vector3 normal;
+
+ public float x
+ {
+ get
+ {
+ return normal.x;
+ }
+ set
+ {
+ normal.x = value;
+ }
+ }
+
+ public float y
+ {
+ get
+ {
+ return normal.y;
+ }
+ set
+ {
+ normal.y = value;
+ }
+ }
+
+ public float z
+ {
+ get
+ {
+ return normal.z;
+ }
+ set
+ {
+ normal.z = value;
+ }
+ }
+
+ float d;
+
+ public Vector3 Center
+ {
+ get
+ {
+ return normal * d;
+ }
+ }
+
+ public float distance_to(Vector3 point)
+ {
+ return normal.dot(point) - d;
+ }
+
+ public Vector3 get_any_point()
+ {
+ return normal * d;
+ }
+
+ public bool has_point(Vector3 point, float epsilon = Mathf.Epsilon)
+ {
+ float dist = normal.dot(point) - d;
+ return Mathf.abs(dist) <= epsilon;
+ }
+
+ public Vector3 intersect_3(Plane b, Plane c)
+ {
+ float denom = normal.cross(b.normal).dot(c.normal);
+
+ if (Mathf.abs(denom) <= Mathf.Epsilon)
+ return new Vector3();
+
+ Vector3 result = (b.normal.cross(c.normal) * this.d) +
+ (c.normal.cross(normal) * b.d) +
+ (normal.cross(b.normal) * c.d);
+
+ return result / denom;
+ }
+
+ public Vector3 intersect_ray(Vector3 from, Vector3 dir)
+ {
+ float den = normal.dot(dir);
+
+ if (Mathf.abs(den) <= Mathf.Epsilon)
+ return new Vector3();
+
+ float dist = (normal.dot(from) - d) / den;
+
+ // This is a ray, before the emiting pos (from) does not exist
+ if (dist > Mathf.Epsilon)
+ return new Vector3();
+
+ return from + dir * -dist;
+ }
+
+ public Vector3 intersect_segment(Vector3 begin, Vector3 end)
+ {
+ Vector3 segment = begin - end;
+ float den = normal.dot(segment);
+
+ if (Mathf.abs(den) <= Mathf.Epsilon)
+ return new Vector3();
+
+ float dist = (normal.dot(begin) - d) / den;
+
+ if (dist < -Mathf.Epsilon || dist > (1.0f + Mathf.Epsilon))
+ return new Vector3();
+
+ return begin + segment * -dist;
+ }
+
+ public bool is_point_over(Vector3 point)
+ {
+ return normal.dot(point) > d;
+ }
+
+ public Plane normalized()
+ {
+ float len = normal.length();
+
+ if (len == 0)
+ return new Plane(0, 0, 0, 0);
+
+ return new Plane(normal / len, d / len);
+ }
+
+ public Vector3 project(Vector3 point)
+ {
+ return point - normal * distance_to(point);
+ }
+
+ public Plane(float a, float b, float c, float d)
+ {
+ normal = new Vector3(a, b, c);
+ this.d = d;
+ }
+
+ public Plane(Vector3 normal, float d)
+ {
+ this.normal = normal;
+ this.d = d;
+ }
+
+ public Plane(Vector3 v1, Vector3 v2, Vector3 v3)
+ {
+ normal = (v1 - v3).cross(v1 - v2);
+ normal.normalize();
+ d = normal.dot(v1);
+ }
+
+ public static Plane operator -(Plane plane)
+ {
+ return new Plane(-plane.normal, -plane.d);
+ }
+
+ public static bool operator ==(Plane left, Plane right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Plane left, Plane right)
+ {
+ return !left.Equals(right);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is Plane)
+ {
+ return Equals((Plane)obj);
+ }
+
+ return false;
+ }
+
+ public bool Equals(Plane other)
+ {
+ return normal == other.normal && d == other.d;
+ }
+
+ public override int GetHashCode()
+ {
+ return normal.GetHashCode() ^ d.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return String.Format("({0}, {1})", new object[]
+ {
+ this.normal.ToString(),
+ this.d.ToString()
+ });
+ }
+
+ public string ToString(string format)
+ {
+ return String.Format("({0}, {1})", new object[]
+ {
+ this.normal.ToString(format),
+ this.d.ToString(format)
+ });
+ }
+ }
+}
diff --git a/modules/mono/glue/cs_files/Quat.cs b/modules/mono/glue/cs_files/Quat.cs new file mode 100644 index 0000000000..6345239f47 --- /dev/null +++ b/modules/mono/glue/cs_files/Quat.cs @@ -0,0 +1,328 @@ +using System; +using System.Runtime.InteropServices; + +namespace Godot +{ + [StructLayout(LayoutKind.Sequential)] + public struct Quat : IEquatable<Quat> + { + private static readonly Quat identity = new Quat(0f, 0f, 0f, 1f); + + public float x; + public float y; + public float z; + public float w; + + public static Quat Identity + { + get { return identity; } + } + + public float this[int index] + { + get + { + switch (index) + { + case 0: + return x; + case 1: + return y; + case 2: + return z; + case 3: + return w; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + x = value; + break; + case 1: + y = value; + break; + case 2: + z = value; + break; + case 3: + w = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + } + + public Quat cubic_slerp(Quat b, Quat preA, Quat postB, float t) + { + float t2 = (1.0f - t) * t * 2f; + Quat sp = slerp(b, t); + Quat sq = preA.slerpni(postB, t); + return sp.slerpni(sq, t2); + } + + public float dot(Quat b) + { + return x * b.x + y * b.y + z * b.z + w * b.w; + } + + public Quat inverse() + { + return new Quat(-x, -y, -z, w); + } + + public float length() + { + return Mathf.sqrt(length_squared()); + } + + public float length_squared() + { + return dot(this); + } + + public Quat normalized() + { + return this / length(); + } + + public void set(float x, float y, float z, float w) + { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + public Quat slerp(Quat b, float t) + { + // Calculate cosine + float cosom = x * b.x + y * b.y + z * b.z + w * b.w; + + float[] to1 = new float[4]; + + // Adjust signs if necessary + if (cosom < 0.0) + { + cosom = -cosom; to1[0] = -b.x; + to1[1] = -b.y; + to1[2] = -b.z; + to1[3] = -b.w; + } + else + { + to1[0] = b.x; + to1[1] = b.y; + to1[2] = b.z; + to1[3] = b.w; + } + + float sinom, scale0, scale1; + + // Calculate coefficients + if ((1.0 - cosom) > Mathf.Epsilon) + { + // Standard case (Slerp) + float omega = Mathf.acos(cosom); + sinom = Mathf.sin(omega); + scale0 = Mathf.sin((1.0f - t) * omega) / sinom; + scale1 = Mathf.sin(t * omega) / sinom; + } + else + { + // Quaternions are very close so we can do a linear interpolation + scale0 = 1.0f - t; + scale1 = t; + } + + // Calculate final values + return new Quat + ( + scale0 * x + scale1 * to1[0], + scale0 * y + scale1 * to1[1], + scale0 * z + scale1 * to1[2], + scale0 * w + scale1 * to1[3] + ); + } + + public Quat slerpni(Quat b, float t) + { + float dot = this.dot(b); + + if (Mathf.abs(dot) > 0.9999f) + { + return this; + } + + float theta = Mathf.acos(dot); + float sinT = 1.0f / Mathf.sin(theta); + float newFactor = Mathf.sin(t * theta) * sinT; + float invFactor = Mathf.sin((1.0f - t) * theta) * sinT; + + return new Quat + ( + invFactor * this.x + newFactor * b.x, + invFactor * this.y + newFactor * b.y, + invFactor * this.z + newFactor * b.z, + invFactor * this.w + newFactor * b.w + ); + } + + public Vector3 xform(Vector3 v) + { + Quat q = this * v; + q *= this.inverse(); + return new Vector3(q.x, q.y, q.z); + } + + public Quat(float x, float y, float z, float w) + { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + public Quat(Vector3 axis, float angle) + { + float d = axis.length(); + + if (d == 0f) + { + x = 0f; + y = 0f; + z = 0f; + w = 0f; + } + else + { + float s = Mathf.sin(-angle * 0.5f) / d; + + x = axis.x * s; + y = axis.y * s; + z = axis.z * s; + w = Mathf.cos(-angle * 0.5f); + } + } + + public static Quat operator *(Quat left, Quat right) + { + return new Quat + ( + left.w * right.x + left.x * right.w + left.y * right.z - left.z * right.y, + left.w * right.y + left.y * right.w + left.z * right.x - left.x * right.z, + left.w * right.z + left.z * right.w + left.x * right.y - left.y * right.x, + left.w * right.w - left.x * right.x - left.y * right.y - left.z * right.z + ); + } + + public static Quat operator +(Quat left, Quat right) + { + return new Quat(left.x + right.x, left.y + right.y, left.z + right.z, left.w + right.w); + } + + public static Quat operator -(Quat left, Quat right) + { + return new Quat(left.x - right.x, left.y - right.y, left.z - right.z, left.w - right.w); + } + + public static Quat operator -(Quat left) + { + return new Quat(-left.x, -left.y, -left.z, -left.w); + } + + public static Quat operator *(Quat left, Vector3 right) + { + return new Quat + ( + left.w * right.x + left.y * right.z - left.z * right.y, + left.w * right.y + left.z * right.x - left.x * right.z, + left.w * right.z + left.x * right.y - left.y * right.x, + -left.x * right.x - left.y * right.y - left.z * right.z + ); + } + + public static Quat operator *(Vector3 left, Quat right) + { + return new Quat + ( + right.w * left.x + right.y * left.z - right.z * left.y, + right.w * left.y + right.z * left.x - right.x * left.z, + right.w * left.z + right.x * left.y - right.y * left.x, + -right.x * left.x - right.y * left.y - right.z * left.z + ); + } + + public static Quat operator *(Quat left, float right) + { + return new Quat(left.x * right, left.y * right, left.z * right, left.w * right); + } + + public static Quat operator *(float left, Quat right) + { + return new Quat(right.x * left, right.y * left, right.z * left, right.w * left); + } + + public static Quat operator /(Quat left, float right) + { + return left * (1.0f / right); + } + + public static bool operator ==(Quat left, Quat right) + { + return left.Equals(right); + } + + public static bool operator !=(Quat left, Quat right) + { + return !left.Equals(right); + } + + public override bool Equals(object obj) + { + if (obj is Vector2) + { + return Equals((Vector2)obj); + } + + return false; + } + + public bool Equals(Quat other) + { + return x == other.x && y == other.y && z == other.z && w == other.w; + } + + public override int GetHashCode() + { + return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode() ^ w.GetHashCode(); + } + + public override string ToString() + { + return String.Format("({0}, {1}, {2}, {3})", new object[] + { + this.x.ToString(), + this.y.ToString(), + this.z.ToString(), + this.w.ToString() + }); + } + + public string ToString(string format) + { + return String.Format("({0}, {1}, {2}, {3})", new object[] + { + this.x.ToString(format), + this.y.ToString(format), + this.z.ToString(format), + this.w.ToString(format) + }); + } + } +} diff --git a/modules/mono/glue/cs_files/RPCAttributes.cs b/modules/mono/glue/cs_files/RPCAttributes.cs new file mode 100644 index 0000000000..08841ffd76 --- /dev/null +++ b/modules/mono/glue/cs_files/RPCAttributes.cs @@ -0,0 +1,16 @@ +using System; + +namespace Godot +{ + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + public class RemoteAttribute : Attribute {} + + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + public class SyncAttribute : Attribute {} + + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + public class MasterAttribute : Attribute {} + + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)] + public class SlaveAttribute : Attribute {} +} diff --git a/modules/mono/glue/cs_files/Rect2.cs b/modules/mono/glue/cs_files/Rect2.cs new file mode 100644 index 0000000000..019342134a --- /dev/null +++ b/modules/mono/glue/cs_files/Rect2.cs @@ -0,0 +1,233 @@ +using System; +using System.Runtime.InteropServices; + +namespace Godot +{ + [StructLayout(LayoutKind.Sequential)] + public struct Rect2 : IEquatable<Rect2> + { + private Vector2 position; + private Vector2 size; + + public Vector2 Position + { + get { return position; } + set { position = value; } + } + + public Vector2 Size + { + get { return size; } + set { size = value; } + } + + public Vector2 End + { + get { return position + size; } + } + + public float Area + { + get { return get_area(); } + } + + public Rect2 clip(Rect2 b) + { + Rect2 newRect = b; + + if (!intersects(newRect)) + return new Rect2(); + + newRect.position.x = Mathf.max(b.position.x, position.x); + newRect.position.y = Mathf.max(b.position.y, position.y); + + Vector2 bEnd = b.position + b.size; + Vector2 end = position + size; + + newRect.size.x = Mathf.min(bEnd.x, end.x) - newRect.position.x; + newRect.size.y = Mathf.min(bEnd.y, end.y) - newRect.position.y; + + return newRect; + } + + public bool encloses(Rect2 b) + { + return (b.position.x >= position.x) && (b.position.y >= position.y) && + ((b.position.x + b.size.x) < (position.x + size.x)) && + ((b.position.y + b.size.y) < (position.y + size.y)); + } + + public Rect2 expand(Vector2 to) + { + Rect2 expanded = this; + + Vector2 begin = expanded.position; + Vector2 end = expanded.position + expanded.size; + + if (to.x < begin.x) + begin.x = to.x; + if (to.y < begin.y) + begin.y = to.y; + + if (to.x > end.x) + end.x = to.x; + if (to.y > end.y) + end.y = to.y; + + expanded.position = begin; + expanded.size = end - begin; + + return expanded; + } + + public float get_area() + { + return size.x * size.y; + } + + public Rect2 grow(float by) + { + Rect2 g = this; + + g.position.x -= by; + g.position.y -= by; + g.size.x += by * 2; + g.size.y += by * 2; + + return g; + } + + public Rect2 grow_individual(float left, float top, float right, float bottom) + { + Rect2 g = this; + + g.position.x -= left; + g.position.y -= top; + g.size.x += left + right; + g.size.y += top + bottom; + + return g; + } + + public Rect2 grow_margin(int margin, float by) + { + Rect2 g = this; + + g.grow_individual((GD.MARGIN_LEFT == margin) ? by : 0, + (GD.MARGIN_TOP == margin) ? by : 0, + (GD.MARGIN_RIGHT == margin) ? by : 0, + (GD.MARGIN_BOTTOM == margin) ? by : 0); + + return g; + } + + public bool has_no_area() + { + return size.x <= 0 || size.y <= 0; + } + + public bool has_point(Vector2 point) + { + if (point.x < position.x) + return false; + if (point.y < position.y) + return false; + + if (point.x >= (position.x + size.x)) + return false; + if (point.y >= (position.y + size.y)) + return false; + + return true; + } + + public bool intersects(Rect2 b) + { + if (position.x > (b.position.x + b.size.x)) + return false; + if ((position.x + size.x) < b.position.x) + return false; + if (position.y > (b.position.y + b.size.y)) + return false; + if ((position.y + size.y) < b.position.y) + return false; + + return true; + } + + public Rect2 merge(Rect2 b) + { + Rect2 newRect; + + newRect.position.x = Mathf.min(b.position.x, position.x); + newRect.position.y = Mathf.min(b.position.y, position.y); + + newRect.size.x = Mathf.max(b.position.x + b.size.x, position.x + size.x); + newRect.size.y = Mathf.max(b.position.y + b.size.y, position.y + size.y); + + newRect.size = newRect.size - newRect.position; // Make relative again + + return newRect; + } + + public Rect2(Vector2 position, Vector2 size) + { + this.position = position; + this.size = size; + } + + public Rect2(float x, float y, float width, float height) + { + this.position = new Vector2(x, y); + this.size = new Vector2(width, height); + } + + public static bool operator ==(Rect2 left, Rect2 right) + { + return left.Equals(right); + } + + public static bool operator !=(Rect2 left, Rect2 right) + { + return !left.Equals(right); + } + + public override bool Equals(object obj) + { + if (obj is Rect2) + { + return Equals((Rect2)obj); + } + + return false; + } + + public bool Equals(Rect2 other) + { + return position.Equals(other.position) && size.Equals(other.size); + } + + public override int GetHashCode() + { + return position.GetHashCode() ^ size.GetHashCode(); + } + + public override string ToString() + { + return String.Format("({0}, {1})", new object[] + { + this.position.ToString(), + this.size.ToString() + }); + } + + public string ToString(string format) + { + return String.Format("({0}, {1})", new object[] + { + this.position.ToString(format), + this.size.ToString(format) + }); + } + } +}
\ No newline at end of file diff --git a/modules/mono/glue/cs_files/Rect3.cs b/modules/mono/glue/cs_files/Rect3.cs new file mode 100644 index 0000000000..0d25de1ec6 --- /dev/null +++ b/modules/mono/glue/cs_files/Rect3.cs @@ -0,0 +1,477 @@ +using System;
+
+// file: core/math/rect3.h
+// commit: 7ad14e7a3e6f87ddc450f7e34621eb5200808451
+// file: core/math/rect3.cpp
+// commit: bd282ff43f23fe845f29a3e25c8efc01bd65ffb0
+// file: core/variant_call.cpp
+// commit: 5ad9be4c24e9d7dc5672fdc42cea896622fe5685
+
+namespace Godot
+{
+ public struct Rect3 : IEquatable<Rect3>
+ {
+ private Vector3 position;
+ private Vector3 size;
+
+ public Vector3 Position
+ {
+ get
+ {
+ return position;
+ }
+ }
+
+ public Vector3 Size
+ {
+ get
+ {
+ return size;
+ }
+ }
+
+ public Vector3 End
+ {
+ get
+ {
+ return position + size;
+ }
+ }
+
+ public bool encloses(Rect3 with)
+ {
+ Vector3 src_min = position;
+ Vector3 src_max = position + size;
+ Vector3 dst_min = with.position;
+ Vector3 dst_max = with.position + with.size;
+
+ return ((src_min.x <= dst_min.x) &&
+ (src_max.x > dst_max.x) &&
+ (src_min.y <= dst_min.y) &&
+ (src_max.y > dst_max.y) &&
+ (src_min.z <= dst_min.z) &&
+ (src_max.z > dst_max.z));
+ }
+
+ public Rect3 expand(Vector3 to_point)
+ {
+ Vector3 begin = position;
+ Vector3 end = position + size;
+
+ if (to_point.x < begin.x)
+ begin.x = to_point.x;
+ if (to_point.y < begin.y)
+ begin.y = to_point.y;
+ if (to_point.z < begin.z)
+ begin.z = to_point.z;
+
+ if (to_point.x > end.x)
+ end.x = to_point.x;
+ if (to_point.y > end.y)
+ end.y = to_point.y;
+ if (to_point.z > end.z)
+ end.z = to_point.z;
+
+ return new Rect3(begin, end - begin);
+ }
+
+ public float get_area()
+ {
+ return size.x * size.y * size.z;
+ }
+
+ public Vector3 get_endpoint(int idx)
+ {
+ switch (idx)
+ {
+ case 0:
+ return new Vector3(position.x, position.y, position.z);
+ case 1:
+ return new Vector3(position.x, position.y, position.z + size.z);
+ case 2:
+ return new Vector3(position.x, position.y + size.y, position.z);
+ case 3:
+ return new Vector3(position.x, position.y + size.y, position.z + size.z);
+ case 4:
+ return new Vector3(position.x + size.x, position.y, position.z);
+ case 5:
+ return new Vector3(position.x + size.x, position.y, position.z + size.z);
+ case 6:
+ return new Vector3(position.x + size.x, position.y + size.y, position.z);
+ case 7:
+ return new Vector3(position.x + size.x, position.y + size.y, position.z + size.z);
+ default:
+ throw new ArgumentOutOfRangeException(nameof(idx), String.Format("Index is {0}, but a value from 0 to 7 is expected.", idx));
+ }
+ }
+
+ public Vector3 get_longest_axis()
+ {
+ Vector3 axis = new Vector3(1f, 0f, 0f);
+ float max_size = size.x;
+
+ if (size.y > max_size)
+ {
+ axis = new Vector3(0f, 1f, 0f);
+ max_size = size.y;
+ }
+
+ if (size.z > max_size)
+ {
+ axis = new Vector3(0f, 0f, 1f);
+ max_size = size.z;
+ }
+
+ return axis;
+ }
+
+ public Vector3.Axis get_longest_axis_index()
+ {
+ Vector3.Axis axis = Vector3.Axis.X;
+ float max_size = size.x;
+
+ if (size.y > max_size)
+ {
+ axis = Vector3.Axis.Y;
+ max_size = size.y;
+ }
+
+ if (size.z > max_size)
+ {
+ axis = Vector3.Axis.Z;
+ max_size = size.z;
+ }
+
+ return axis;
+ }
+
+ public float get_longest_axis_size()
+ {
+ float max_size = size.x;
+
+ if (size.y > max_size)
+ max_size = size.y;
+
+ if (size.z > max_size)
+ max_size = size.z;
+
+ return max_size;
+ }
+
+ public Vector3 get_shortest_axis()
+ {
+ Vector3 axis = new Vector3(1f, 0f, 0f);
+ float max_size = size.x;
+
+ if (size.y < max_size)
+ {
+ axis = new Vector3(0f, 1f, 0f);
+ max_size = size.y;
+ }
+
+ if (size.z < max_size)
+ {
+ axis = new Vector3(0f, 0f, 1f);
+ max_size = size.z;
+ }
+
+ return axis;
+ }
+
+ public Vector3.Axis get_shortest_axis_index()
+ {
+ Vector3.Axis axis = Vector3.Axis.X;
+ float max_size = size.x;
+
+ if (size.y < max_size)
+ {
+ axis = Vector3.Axis.Y;
+ max_size = size.y;
+ }
+
+ if (size.z < max_size)
+ {
+ axis = Vector3.Axis.Z;
+ max_size = size.z;
+ }
+
+ return axis;
+ }
+
+ public float get_shortest_axis_size()
+ {
+ float max_size = size.x;
+
+ if (size.y < max_size)
+ max_size = size.y;
+
+ if (size.z < max_size)
+ max_size = size.z;
+
+ return max_size;
+ }
+
+ public Vector3 get_support(Vector3 dir)
+ {
+ Vector3 half_extents = size * 0.5f;
+ Vector3 ofs = position + half_extents;
+
+ return ofs + new Vector3(
+ (dir.x > 0f) ? -half_extents.x : half_extents.x,
+ (dir.y > 0f) ? -half_extents.y : half_extents.y,
+ (dir.z > 0f) ? -half_extents.z : half_extents.z);
+ }
+
+ public Rect3 grow(float by)
+ {
+ Rect3 res = this;
+
+ res.position.x -= by;
+ res.position.y -= by;
+ res.position.z -= by;
+ res.size.x += 2.0f * by;
+ res.size.y += 2.0f * by;
+ res.size.z += 2.0f * by;
+
+ return res;
+ }
+
+ public bool has_no_area()
+ {
+ return size.x <= 0f || size.y <= 0f || size.z <= 0f;
+ }
+
+ public bool has_no_surface()
+ {
+ return size.x <= 0f && size.y <= 0f && size.z <= 0f;
+ }
+
+ public bool has_point(Vector3 point)
+ {
+ if (point.x < position.x)
+ return false;
+ if (point.y < position.y)
+ return false;
+ if (point.z < position.z)
+ return false;
+ if (point.x > position.x + size.x)
+ return false;
+ if (point.y > position.y + size.y)
+ return false;
+ if (point.z > position.z + size.z)
+ return false;
+
+ return true;
+ }
+
+ public Rect3 intersection(Rect3 with)
+ {
+ Vector3 src_min = position;
+ Vector3 src_max = position + size;
+ Vector3 dst_min = with.position;
+ Vector3 dst_max = with.position + with.size;
+
+ Vector3 min, max;
+
+ if (src_min.x > dst_max.x || src_max.x < dst_min.x)
+ {
+ return new Rect3();
+ }
+ else
+ {
+ min.x = (src_min.x > dst_min.x) ? src_min.x : dst_min.x;
+ max.x = (src_max.x < dst_max.x) ? src_max.x : dst_max.x;
+ }
+
+ if (src_min.y > dst_max.y || src_max.y < dst_min.y)
+ {
+ return new Rect3();
+ }
+ else
+ {
+ min.y = (src_min.y > dst_min.y) ? src_min.y : dst_min.y;
+ max.y = (src_max.y < dst_max.y) ? src_max.y : dst_max.y;
+ }
+
+ if (src_min.z > dst_max.z || src_max.z < dst_min.z)
+ {
+ return new Rect3();
+ }
+ else
+ {
+ min.z = (src_min.z > dst_min.z) ? src_min.z : dst_min.z;
+ max.z = (src_max.z < dst_max.z) ? src_max.z : dst_max.z;
+ }
+
+ return new Rect3(min, max - min);
+ }
+
+ public bool intersects(Rect3 with)
+ {
+ if (position.x >= (with.position.x + with.size.x))
+ return false;
+ if ((position.x + size.x) <= with.position.x)
+ return false;
+ if (position.y >= (with.position.y + with.size.y))
+ return false;
+ if ((position.y + size.y) <= with.position.y)
+ return false;
+ if (position.z >= (with.position.z + with.size.z))
+ return false;
+ if ((position.z + size.z) <= with.position.z)
+ return false;
+
+ return true;
+ }
+
+ public bool intersects_plane(Plane plane)
+ {
+ Vector3[] points =
+ {
+ new Vector3(position.x, position.y, position.z),
+ new Vector3(position.x, position.y, position.z + size.z),
+ new Vector3(position.x, position.y + size.y, position.z),
+ new Vector3(position.x, position.y + size.y, position.z + size.z),
+ new Vector3(position.x + size.x, position.y, position.z),
+ new Vector3(position.x + size.x, position.y, position.z + size.z),
+ new Vector3(position.x + size.x, position.y + size.y, position.z),
+ new Vector3(position.x + size.x, position.y + size.y, position.z + size.z),
+ };
+
+ bool over = false;
+ bool under = false;
+
+ for (int i = 0; i < 8; i++)
+ {
+ if (plane.distance_to(points[i]) > 0)
+ over = true;
+ else
+ under = true;
+ }
+
+ return under && over;
+ }
+
+ public bool intersects_segment(Vector3 from, Vector3 to)
+ {
+ float min = 0f;
+ float max = 1f;
+
+ for (int i = 0; i < 3; i++)
+ {
+ float seg_from = from[i];
+ float seg_to = to[i];
+ float box_begin = position[i];
+ float box_end = box_begin + size[i];
+ float cmin, cmax;
+
+ if (seg_from < seg_to)
+ {
+ if (seg_from > box_end || seg_to < box_begin)
+ return false;
+
+ float length = seg_to - seg_from;
+ cmin = seg_from < box_begin ? (box_begin - seg_from) / length : 0f;
+ cmax = seg_to > box_end ? (box_end - seg_from) / length : 1f;
+ }
+ else
+ {
+ if (seg_to > box_end || seg_from < box_begin)
+ return false;
+
+ float length = seg_to - seg_from;
+ cmin = seg_from > box_end ? (box_end - seg_from) / length : 0f;
+ cmax = seg_to < box_begin ? (box_begin - seg_from) / length : 1f;
+ }
+
+ if (cmin > min)
+ {
+ min = cmin;
+ }
+
+ if (cmax < max)
+ max = cmax;
+ if (max < min)
+ return false;
+ }
+
+ return true;
+ }
+
+ public Rect3 merge(Rect3 with)
+ {
+ Vector3 beg_1 = position;
+ Vector3 beg_2 = with.position;
+ Vector3 end_1 = new Vector3(size.x, size.y, size.z) + beg_1;
+ Vector3 end_2 = new Vector3(with.size.x, with.size.y, with.size.z) + beg_2;
+
+ Vector3 min = new Vector3(
+ (beg_1.x < beg_2.x) ? beg_1.x : beg_2.x,
+ (beg_1.y < beg_2.y) ? beg_1.y : beg_2.y,
+ (beg_1.z < beg_2.z) ? beg_1.z : beg_2.z
+ );
+
+ Vector3 max = new Vector3(
+ (end_1.x > end_2.x) ? end_1.x : end_2.x,
+ (end_1.y > end_2.y) ? end_1.y : end_2.y,
+ (end_1.z > end_2.z) ? end_1.z : end_2.z
+ );
+
+ return new Rect3(min, max - min);
+ }
+
+ public Rect3(Vector3 position, Vector3 size)
+ {
+ this.position = position;
+ this.size = size;
+ }
+
+ public static bool operator ==(Rect3 left, Rect3 right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Rect3 left, Rect3 right)
+ {
+ return !left.Equals(right);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is Rect3)
+ {
+ return Equals((Rect3)obj);
+ }
+
+ return false;
+ }
+
+ public bool Equals(Rect3 other)
+ {
+ return position == other.position && size == other.size;
+ }
+
+ public override int GetHashCode()
+ {
+ return position.GetHashCode() ^ size.GetHashCode();
+ }
+
+ public override string ToString()
+ {
+ return String.Format("{0} - {1}", new object[]
+ {
+ this.position.ToString(),
+ this.size.ToString()
+ });
+ }
+
+ public string ToString(string format)
+ {
+ return String.Format("{0} - {1}", new object[]
+ {
+ this.position.ToString(format),
+ this.size.ToString(format)
+ });
+ }
+ }
+}
diff --git a/modules/mono/glue/cs_files/SignalAwaiter.cs b/modules/mono/glue/cs_files/SignalAwaiter.cs new file mode 100644 index 0000000000..19ccc26e79 --- /dev/null +++ b/modules/mono/glue/cs_files/SignalAwaiter.cs @@ -0,0 +1,59 @@ +using System; + +namespace Godot +{ + public class SignalAwaiter : IAwaiter<object[]>, IAwaitable<object[]> + { + private bool completed = false; + private object[] result = null; + private Action action = null; + + public SignalAwaiter(Godot.Object source, string signal, Godot.Object target) + { + NativeCalls.godot_icall_Object_connect_signal_awaiter( + Godot.Object.GetPtr(source), + signal, Godot.Object.GetPtr(target), this + ); + } + + public bool IsCompleted + { + get + { + return completed; + } + } + + public void OnCompleted(Action action) + { + this.action = action; + } + + public object[] GetResult() + { + return result; + } + + public IAwaiter<object[]> GetAwaiter() + { + return this; + } + + internal void SignalCallback(object[] args) + { + completed = true; + result = args; + + if (action != null) + { + action(); + } + } + + internal void FailureCallback() + { + action = null; + completed = true; + } + } +} diff --git a/modules/mono/glue/cs_files/StringExtensions.cs b/modules/mono/glue/cs_files/StringExtensions.cs new file mode 100644 index 0000000000..96041827aa --- /dev/null +++ b/modules/mono/glue/cs_files/StringExtensions.cs @@ -0,0 +1,962 @@ +//using System; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Security; +using System.Text; +using System.Text.RegularExpressions; + +namespace Godot +{ + public static class StringExtensions + { + private static int get_slice_count(this string instance, string splitter) + { + if (instance.empty() || splitter.empty()) + return 0; + + int pos = 0; + int slices = 1; + + while ((pos = instance.find(splitter, pos)) >= 0) + { + slices++; + pos += splitter.Length; + } + + return slices; + } + + private static string get_slicec(this string instance, char splitter, int slice) + { + if (!instance.empty() && slice >= 0) + { + int i = 0; + int prev = 0; + int count = 0; + + while (true) + { + if (instance[i] == 0 || instance[i] == splitter) + { + if (slice == count) + { + return instance.Substring(prev, i - prev); + } + else + { + count++; + prev = i + 1; + } + } + + i++; + } + } + + return string.Empty; + } + + // <summary> + // If the string is a path to a file, return the path to the file without the extension. + // </summary> + public static string basename(this string instance) + { + int index = instance.LastIndexOf('.'); + + if (index > 0) + return instance.Substring(0, index); + + return instance; + } + + // <summary> + // Return true if the strings begins with the given string. + // </summary> + public static bool begins_with(this string instance, string text) + { + return instance.StartsWith(text); + } + + // <summary> + // Return the bigrams (pairs of consecutive letters) of this string. + // </summary> + public static string[] bigrams(this string instance) + { + string[] b = new string[instance.Length - 1]; + + for (int i = 0; i < b.Length; i++) + { + b[i] = instance.Substring(i, 2); + } + + return b; + } + + // <summary> + // Return a copy of the string with special characters escaped using the C language standard. + // </summary> + public static string c_escape(this string instance) + { + StringBuilder sb = new StringBuilder(string.Copy(instance)); + + sb.Replace("\\", "\\\\"); + sb.Replace("\a", "\\a"); + sb.Replace("\b", "\\b"); + sb.Replace("\f", "\\f"); + sb.Replace("\n", "\\n"); + sb.Replace("\r", "\\r"); + sb.Replace("\t", "\\t"); + sb.Replace("\v", "\\v"); + sb.Replace("\'", "\\'"); + sb.Replace("\"", "\\\""); + sb.Replace("?", "\\?"); + + return sb.ToString(); + } + + // <summary> + // Return a copy of the string with escaped characters replaced by their meanings according to the C language standard. + // </summary> + public static string c_unescape(this string instance) + { + StringBuilder sb = new StringBuilder(string.Copy(instance)); + + sb.Replace("\\a", "\a"); + sb.Replace("\\b", "\b"); + sb.Replace("\\f", "\f"); + sb.Replace("\\n", "\n"); + sb.Replace("\\r", "\r"); + sb.Replace("\\t", "\t"); + sb.Replace("\\v", "\v"); + sb.Replace("\\'", "\'"); + sb.Replace("\\\"", "\""); + sb.Replace("\\?", "?"); + sb.Replace("\\\\", "\\"); + + return sb.ToString(); + } + + // <summary> + // Change the case of some letters. Replace underscores with spaces, convert all letters to lowercase then capitalize first and every letter following the space character. For [code]capitalize camelCase mixed_with_underscores[/code] it will return [code]Capitalize Camelcase Mixed With Underscores[/code]. + // </summary> + public static string capitalize(this string instance) + { + string aux = instance.Replace("_", " ").ToLower(); + string cap = string.Empty; + + for (int i = 0; i < aux.get_slice_count(" "); i++) + { + string slice = aux.get_slicec(' ', i); + if (slice.Length > 0) + { + slice = char.ToUpper(slice[0]) + slice.Substring(1); + if (i > 0) + cap += " "; + cap += slice; + } + } + + return cap; + } + + // <summary> + // Perform a case-sensitive comparison to another string, return -1 if less, 0 if equal and +1 if greater. + // </summary> + public static int casecmp_to(this string instance, string to) + { + if (instance.empty()) + return to.empty() ? 0 : -1; + + if (to.empty()) + return 1; + + int instance_idx = 0; + int to_idx = 0; + + while (true) + { + if (to[to_idx] == 0 && instance[instance_idx] == 0) + return 0; // We're equal + else if (instance[instance_idx] == 0) + return -1; // If this is empty, and the other one is not, then we're less... I think? + else if (to[to_idx] == 0) + return 1; // Otherwise the other one is smaller... + else if (instance[instance_idx] < to[to_idx]) // More than + return -1; + else if (instance[instance_idx] > to[to_idx]) // Less than + return 1; + + instance_idx++; + to_idx++; + } + } + + // <summary> + // Return true if the string is empty. + // </summary> + public static bool empty(this string instance) + { + return string.IsNullOrEmpty(instance); + } + + // <summary> + // Return true if the strings ends with the given string. + // </summary> + public static bool ends_with(this string instance, string text) + { + return instance.EndsWith(text); + } + + // <summary> + // Erase [code]chars[/code] characters from the string starting from [code]pos[/code]. + // </summary> + public static void erase(this StringBuilder instance, int pos, int chars) + { + instance.Remove(pos, chars); + } + + // <summary> + // If the string is a path to a file, return the extension. + // </summary> + public static string extension(this string instance) + { + int pos = instance.find_last("."); + + if (pos < 0) + return instance; + + return instance.Substring(pos + 1, instance.Length); + } + + // <summary> + // Find the first occurrence of a substring, return the starting position of the substring or -1 if not found. Optionally, the initial search index can be passed. + // </summary> + public static int find(this string instance, string what, int from = 0) + { + return instance.IndexOf(what, StringComparison.OrdinalIgnoreCase); + } + + // <summary> + // Find the last occurrence of a substring, return the starting position of the substring or -1 if not found. Optionally, the initial search index can be passed. + // </summary> + public static int find_last(this string instance, string what) + { + return instance.LastIndexOf(what, StringComparison.OrdinalIgnoreCase); + } + + // <summary> + // Find the first occurrence of a substring but search as case-insensitive, return the starting position of the substring or -1 if not found. Optionally, the initial search index can be passed. + // </summary> + public static int findn(this string instance, string what, int from = 0) + { + return instance.IndexOf(what, StringComparison.Ordinal); + } + + // <summary> + // If the string is a path to a file, return the base directory. + // </summary> + public static string get_base_dir(this string instance) + { + int basepos = instance.find("://"); + + string rs = string.Empty; + string @base = string.Empty; + + if (basepos != -1) + { + int end = basepos + 3; + rs = instance.Substring(end, instance.Length); + @base = instance.Substring(0, end); + } + else + { + if (instance.begins_with("/")) + { + rs = instance.Substring(1, instance.Length); + @base = "/"; + } + else + { + rs = instance; + } + } + + int sep = Mathf.max(rs.find_last("/"), rs.find_last("\\")); + + if (sep == -1) + return @base; + + return @base + rs.substr(0, sep); + } + + // <summary> + // If the string is a path to a file, return the file and ignore the base directory. + // </summary> + public static string get_file(this string instance) + { + int sep = Mathf.max(instance.find_last("/"), instance.find_last("\\")); + + if (sep == -1) + return instance; + + return instance.Substring(sep + 1, instance.Length); + } + + // <summary> + // Hash the string and return a 32 bits integer. + // </summary> + public static int hash(this string instance) + { + int index = 0; + int hashv = 5381; + int c; + + while ((c = (int)instance[index++]) != 0) + hashv = ((hashv << 5) + hashv) + c; // hash * 33 + c + + return hashv; + } + + // <summary> + // Convert a string containing an hexadecimal number into an int. + // </summary> + public static int hex_to_int(this string instance) + { + int sign = 1; + + if (instance[0] == '-') + { + sign = -1; + instance = instance.Substring(1); + } + + if (!instance.StartsWith("0x")) + return 0; + + return sign * int.Parse(instance.Substring(2), NumberStyles.HexNumber); + } + + // <summary> + // Insert a substring at a given position. + // </summary> + public static string insert(this string instance, int pos, string what) + { + return instance.Insert(pos, what); + } + + // <summary> + // If the string is a path to a file or directory, return true if the path is absolute. + // </summary> + public static bool is_abs_path(this string instance) + { + return System.IO.Path.IsPathRooted(instance); + } + + // <summary> + // If the string is a path to a file or directory, return true if the path is relative. + // </summary> + public static bool is_rel_path(this string instance) + { + return !System.IO.Path.IsPathRooted(instance); + } + + // <summary> + // Check whether this string is a subsequence of the given string. + // </summary> + public static bool is_subsequence_of(this string instance, string text, bool case_insensitive) + { + int len = instance.Length; + + if (len == 0) + return true; // Technically an empty string is subsequence of any string + + if (len > text.Length) + return false; + + int src = 0; + int tgt = 0; + + while (instance[src] != 0 && text[tgt] != 0) + { + bool match = false; + + if (case_insensitive) + { + char srcc = char.ToLower(instance[src]); + char tgtc = char.ToLower(text[tgt]); + match = srcc == tgtc; + } + else + { + match = instance[src] == text[tgt]; + } + if (match) + { + src++; + if (instance[src] == 0) + return true; + } + + tgt++; + } + + return false; + } + + // <summary> + // Check whether this string is a subsequence of the given string, considering case. + // </summary> + public static bool is_subsequence_of(this string instance, string text) + { + return instance.is_subsequence_of(text, false); + } + + // <summary> + // Check whether this string is a subsequence of the given string, without considering case. + // </summary> + public static bool is_subsequence_ofi(this string instance, string text) + { + return instance.is_subsequence_of(text, true); + } + + // <summary> + // Check whether the string contains a valid float. + // </summary> + public static bool is_valid_float(this string instance) + { + float f; + return float.TryParse(instance, out f); + } + + // <summary> + // Check whether the string contains a valid color in HTML notation. + // </summary> + public static bool is_valid_html_color(this string instance) + { + return Color.html_is_valid(instance); + } + + // <summary> + // Check whether the string is a valid identifier. As is common in programming languages, a valid identifier may contain only letters, digits and underscores (_) and the first character may not be a digit. + // </summary> + public static bool is_valid_identifier(this string instance) + { + int len = instance.Length; + + if (len == 0) + return false; + + for (int i = 0; i < len; i++) + { + if (i == 0) + { + if (instance[0] >= '0' && instance[0] <= '9') + return false; // Don't start with number plz + } + + bool valid_char = (instance[i] >= '0' && instance[i] <= '9') || (instance[i] >= 'a' && instance[i] <= 'z') || (instance[i] >= 'A' && instance[i] <= 'Z') || instance[i] == '_'; + + if (!valid_char) + return false; + } + + return true; + } + + // <summary> + // Check whether the string contains a valid integer. + // </summary> + public static bool is_valid_integer(this string instance) + { + int f; + return int.TryParse(instance, out f); + } + + // <summary> + // Check whether the string contains a valid IP address. + // </summary> + public static bool is_valid_ip_address(this string instance) + { + string[] ip = instance.split("."); + + if (ip.Length != 4) + return false; + + for (int i = 0; i < ip.Length; i++) + { + string n = ip[i]; + if (!n.is_valid_integer()) + return false; + + int val = n.to_int(); + if (val < 0 || val > 255) + return false; + } + + return true; + } + + // <summary> + // Return a copy of the string with special characters escaped using the JSON standard. + // </summary> + public static string json_escape(this string instance) + { + StringBuilder sb = new StringBuilder(string.Copy(instance)); + + sb.Replace("\\", "\\\\"); + sb.Replace("\b", "\\b"); + sb.Replace("\f", "\\f"); + sb.Replace("\n", "\\n"); + sb.Replace("\r", "\\r"); + sb.Replace("\t", "\\t"); + sb.Replace("\v", "\\v"); + sb.Replace("\"", "\\\""); + + return sb.ToString(); + } + + // <summary> + // Return an amount of characters from the left of the string. + // </summary> + public static string left(this string instance, int pos) + { + if (pos <= 0) + return string.Empty; + + if (pos >= instance.Length) + return instance; + + return instance.Substring(0, pos); + } + + /// <summary> + /// Return the length of the string in characters. + /// </summary> + public static int length(this string instance) + { + return instance.Length; + } + + // <summary> + // Do a simple expression match, where '*' matches zero or more arbitrary characters and '?' matches any single character except '.'. + // </summary> + public static bool expr_match(this string instance, string expr, bool case_sensitive) + { + if (expr.Length == 0 || instance.Length == 0) + return false; + + switch (expr[0]) + { + case '\0': + return instance[0] == 0; + case '*': + return expr_match(expr + 1, instance, case_sensitive) || (instance[0] != 0 && expr_match(expr, instance + 1, case_sensitive)); + case '?': + return instance[0] != 0 && instance[0] != '.' && expr_match(expr + 1, instance + 1, case_sensitive); + default: + return (case_sensitive ? instance[0] == expr[0] : char.ToUpper(instance[0]) == char.ToUpper(expr[0])) && + expr_match(expr + 1, instance + 1, case_sensitive); + } + } + + // <summary> + // Do a simple case sensitive expression match, using ? and * wildcards (see [method expr_match]). + // </summary> + public static bool match(this string instance, string expr) + { + return instance.expr_match(expr, true); + } + + // <summary> + // Do a simple case insensitive expression match, using ? and * wildcards (see [method expr_match]). + // </summary> + public static bool matchn(this string instance, string expr) + { + return instance.expr_match(expr, false); + } + + // <summary> + // Return the MD5 hash of the string as an array of bytes. + // </summary> + public static byte[] md5_buffer(this string instance) + { + return NativeCalls.godot_icall_String_md5_buffer(instance); + } + + // <summary> + // Return the MD5 hash of the string as a string. + // </summary> + public static string md5_text(this string instance) + { + return NativeCalls.godot_icall_String_md5_text(instance); + } + + // <summary> + // Perform a case-insensitive comparison to another string, return -1 if less, 0 if equal and +1 if greater. + // </summary> + public static int nocasecmp_to(this string instance, string to) + { + if (instance.empty()) + return to.empty() ? 0 : -1; + + if (to.empty()) + return 1; + + int instance_idx = 0; + int to_idx = 0; + + while (true) + { + if (to[to_idx] == 0 && instance[instance_idx] == 0) + return 0; // We're equal + else if (instance[instance_idx] == 0) + return -1; // If this is empty, and the other one is not, then we're less... I think? + else if (to[to_idx] == 0) + return 1; // Otherwise the other one is smaller.. + else if (char.ToUpper(instance[instance_idx]) < char.ToUpper(to[to_idx])) // More than + return -1; + else if (char.ToUpper(instance[instance_idx]) > char.ToUpper(to[to_idx])) // Less than + return 1; + + instance_idx++; + to_idx++; + } + } + + // <summary> + // Return the character code at position [code]at[/code]. + // </summary> + public static int ord_at(this string instance, int at) + { + return instance[at]; + } + + // <summary> + // Format a number to have an exact number of [code]digits[/code] after the decimal point. + // </summary> + public static string pad_decimals(this string instance, int digits) + { + int c = instance.find("."); + + if (c == -1) + { + if (digits <= 0) + return instance; + + instance += "."; + c = instance.Length - 1; + } + else + { + if (digits <= 0) + return instance.Substring(0, c); + } + + if (instance.Length - (c + 1) > digits) + { + instance = instance.Substring(0, c + digits + 1); + } + else + { + while (instance.Length - (c + 1) < digits) + { + instance += "0"; + } + } + + return instance; + } + + // <summary> + // Format a number to have an exact number of [code]digits[/code] before the decimal point. + // </summary> + public static string pad_zeros(this string instance, int digits) + { + string s = instance; + int end = s.find("."); + + if (end == -1) + end = s.Length; + + if (end == 0) + return s; + + int begin = 0; + + while (begin < end && (s[begin] < '0' || s[begin] > '9')) + { + begin++; + } + + if (begin >= end) + return s; + + while (end - begin < digits) + { + s = s.Insert(begin, "0"); + end++; + } + + return s; + } + + // <summary> + // Decode a percent-encoded string. See [method percent_encode]. + // </summary> + public static string percent_decode(this string instance) + { + return Uri.UnescapeDataString(instance); + } + + // <summary> + // Percent-encode a string. This is meant to encode parameters in a URL when sending a HTTP GET request and bodies of form-urlencoded POST request. + // </summary> + public static string percent_encode(this string instance) + { + return Uri.EscapeDataString(instance); + } + + // <summary> + // If the string is a path, this concatenates [code]file[/code] at the end of the string as a subpath. E.g. [code]"this/is".plus_file("path") == "this/is/path"[/code]. + // </summary> + public static string plus_file(this string instance, string file) + { + if (instance.Length > 0 && instance[instance.Length - 1] == '/') + return instance + file; + else + return instance + "/" + file; + } + + // <summary> + // Replace occurrences of a substring for different ones inside the string. + // </summary> + public static string replace(this string instance, string what, string forwhat) + { + return instance.Replace(what, forwhat); + } + + // <summary> + // Replace occurrences of a substring for different ones inside the string, but search case-insensitive. + // </summary> + public static string replacen(this string instance, string what, string forwhat) + { + return Regex.Replace(instance, what, forwhat, RegexOptions.IgnoreCase); + } + + // <summary> + // Perform a search for a substring, but start from the end of the string instead of the beginning. + // </summary> + public static int rfind(this string instance, string what, int from = -1) + { + return NativeCalls.godot_icall_String_rfind(instance, what, from); + } + + // <summary> + // Perform a search for a substring, but start from the end of the string instead of the beginning. Also search case-insensitive. + // </summary> + public static int rfindn(this string instance, string what, int from = -1) + { + return NativeCalls.godot_icall_String_rfindn(instance, what, from); + } + + // <summary> + // Return the right side of the string from a given position. + // </summary> + public static string right(this string instance, int pos) + { + if (pos >= instance.Length) + return instance; + + if (pos < 0) + return string.Empty; + + return instance.Substring(pos, (instance.Length - pos)); + } + + public static byte[] sha256_buffer(this string instance) + { + return NativeCalls.godot_icall_String_sha256_buffer(instance); + } + + // <summary> + // Return the SHA-256 hash of the string as a string. + // </summary> + public static string sha256_text(this string instance) + { + return NativeCalls.godot_icall_String_sha256_text(instance); + } + + // <summary> + // Return the similarity index of the text compared to this string. 1 means totally similar and 0 means totally dissimilar. + // </summary> + public static float similarity(this string instance, string text) + { + if (instance == text) + { + // Equal strings are totally similar + return 1.0f; + } + if (instance.Length < 2 || text.Length < 2) + { + // No way to calculate similarity without a single bigram + return 0.0f; + } + + string[] src_bigrams = instance.bigrams(); + string[] tgt_bigrams = text.bigrams(); + + int src_size = src_bigrams.Length; + int tgt_size = tgt_bigrams.Length; + + float sum = src_size + tgt_size; + float inter = 0; + + for (int i = 0; i < src_size; i++) + { + for (int j = 0; j < tgt_size; j++) + { + if (src_bigrams[i] == tgt_bigrams[j]) + { + inter++; + break; + } + } + } + + return (2.0f * inter) / sum; + } + + // <summary> + // Split the string by a divisor string, return an array of the substrings. Example "One,Two,Three" will return ["One","Two","Three"] if split by ",". + // </summary> + public static string[] split(this string instance, string divisor, bool allow_empty = true) + { + return instance.Split(new string[] { divisor }, StringSplitOptions.RemoveEmptyEntries); + } + + // <summary> + // Split the string in floats by using a divisor string, return an array of the substrings. Example "1,2.5,3" will return [1,2.5,3] if split by ",". + // </summary> + public static float[] split_floats(this string instance, string divisor, bool allow_empty = true) + { + List<float> ret = new List<float>(); + int from = 0; + int len = instance.Length; + + while (true) + { + int end = instance.find(divisor, from); + if (end < 0) + end = len; + if (allow_empty || (end > from)) + ret.Add(float.Parse(instance.Substring(from))); + if (end == len) + break; + + from = end + divisor.Length; + } + + return ret.ToArray(); + } + + private static readonly char[] non_printable = { + (char)00, (char)01, (char)02, (char)03, (char)04, (char)05, + (char)06, (char)07, (char)08, (char)09, (char)10, (char)11, + (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, + (char)18, (char)19, (char)20, (char)21, (char)22, (char)23, + (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, + (char)30, (char)31, (char)32 + }; + + // <summary> + // Return a copy of the string stripped of any non-printable character at the beginning and the end. The optional arguments are used to toggle stripping on the left and right edges respectively. + // </summary> + public static string strip_edges(this string instance, bool left = true, bool right = true) + { + if (left) + { + if (right) + return instance.Trim(non_printable); + else + return instance.TrimStart(non_printable); + } + else + { + return instance.TrimEnd(non_printable); + } + } + + // <summary> + // Return part of the string from the position [code]from[/code], with length [code]len[/code]. + // </summary> + public static string substr(this string instance, int from, int len) + { + return instance.Substring(from, len); + } + + // <summary> + // Convert the String (which is a character array) to PoolByteArray (which is an array of bytes). The conversion is speeded up in comparison to to_utf8() with the assumption that all the characters the String contains are only ASCII characters. + // </summary> + public static byte[] to_ascii(this string instance) + { + return Encoding.ASCII.GetBytes(instance); + } + + // <summary> + // Convert a string, containing a decimal number, into a [code]float[/code]. + // </summary> + public static float to_float(this string instance) + { + return float.Parse(instance); + } + + // <summary> + // Convert a string, containing an integer number, into an [code]int[/code]. + // </summary> + public static int to_int(this string instance) + { + return int.Parse(instance); + } + + // <summary> + // Return the string converted to lowercase. + // </summary> + public static string to_lower(this string instance) + { + return instance.ToLower(); + } + + // <summary> + // Return the string converted to uppercase. + // </summary> + public static string to_upper(this string instance) + { + return instance.ToUpper(); + } + + // <summary> + // Convert the String (which is an array of characters) to PoolByteArray (which is an array of bytes). The conversion is a bit slower than to_ascii(), but supports all UTF-8 characters. Therefore, you should prefer this function over to_ascii(). + // </summary> + public static byte[] to_utf8(this string instance) + { + return Encoding.UTF8.GetBytes(instance); + } + + // <summary> + // Return a copy of the string with special characters escaped using the XML standard. + // </summary> + public static string xml_escape(this string instance) + { + return SecurityElement.Escape(instance); + } + + // <summary> + // Return a copy of the string with escaped characters replaced by their meanings according to the XML standard. + // </summary> + public static string xml_unescape(this string instance) + { + return SecurityElement.FromString(instance).Text; + } + } +} diff --git a/modules/mono/glue/cs_files/ToolAttribute.cs b/modules/mono/glue/cs_files/ToolAttribute.cs new file mode 100644 index 0000000000..0275982c7f --- /dev/null +++ b/modules/mono/glue/cs_files/ToolAttribute.cs @@ -0,0 +1,7 @@ +using System; + +namespace Godot +{ + [AttributeUsage(AttributeTargets.Class)] + public class ToolAttribute : Attribute {} +} diff --git a/modules/mono/glue/cs_files/Transform.cs b/modules/mono/glue/cs_files/Transform.cs new file mode 100644 index 0000000000..2010f0b3af --- /dev/null +++ b/modules/mono/glue/cs_files/Transform.cs @@ -0,0 +1,168 @@ +using System; +using System.Runtime.InteropServices; + +namespace Godot +{ + [StructLayout(LayoutKind.Sequential)] + public struct Transform : IEquatable<Transform> + { + public Basis basis; + public Vector3 origin; + + public Transform affine_inverse() + { + Basis basisInv = basis.inverse(); + return new Transform(basisInv, basisInv.xform(-origin)); + } + + public Transform inverse() + { + Basis basisTr = basis.transposed(); + return new Transform(basisTr, basisTr.xform(-origin)); + } + + public Transform looking_at(Vector3 target, Vector3 up) + { + Transform t = this; + t.set_look_at(origin, target, up); + return t; + } + + public Transform orthonormalized() + { + return new Transform(basis.orthonormalized(), origin); + } + + public Transform rotated(Vector3 axis, float phi) + { + return this * new Transform(new Basis(axis, phi), new Vector3()); + } + + public Transform scaled(Vector3 scale) + { + return new Transform(basis.scaled(scale), origin * scale); + } + + public void set_look_at(Vector3 eye, Vector3 target, Vector3 up) + { + // Make rotation matrix + // Z vector + Vector3 zAxis = eye - target; + + zAxis.normalize(); + + Vector3 yAxis = up; + + Vector3 xAxis = yAxis.cross(zAxis); + + // Recompute Y = Z cross X + yAxis = zAxis.cross(xAxis); + + xAxis.normalize(); + yAxis.normalize(); + + basis = Basis.create_from_axes(xAxis, yAxis, zAxis); + + origin = eye; + } + + public Transform translated(Vector3 ofs) + { + return new Transform(basis, new Vector3 + ( + origin[0] += basis[0].dot(ofs), + origin[1] += basis[1].dot(ofs), + origin[2] += basis[2].dot(ofs) + )); + } + + public Vector3 xform(Vector3 v) + { + return new Vector3 + ( + basis[0].dot(v) + origin.x, + basis[1].dot(v) + origin.y, + basis[2].dot(v) + origin.z + ); + } + + public Vector3 xform_inv(Vector3 v) + { + Vector3 vInv = v - origin; + + return new Vector3 + ( + (basis[0, 0] * vInv.x) + (basis[1, 0] * vInv.y) + (basis[2, 0] * vInv.z), + (basis[0, 1] * vInv.x) + (basis[1, 1] * vInv.y) + (basis[2, 1] * vInv.z), + (basis[0, 2] * vInv.x) + (basis[1, 2] * vInv.y) + (basis[2, 2] * vInv.z) + ); + } + + public Transform(Vector3 xAxis, Vector3 yAxis, Vector3 zAxis, Vector3 origin) + { + this.basis = Basis.create_from_axes(xAxis, yAxis, zAxis); + this.origin = origin; + } + + public Transform(Basis basis, Vector3 origin) + { + this.basis = basis; + this.origin = origin; + } + + public static Transform operator *(Transform left, Transform right) + { + left.origin = left.xform(right.origin); + left.basis *= right.basis; + return left; + } + + public static bool operator ==(Transform left, Transform right) + { + return left.Equals(right); + } + + public static bool operator !=(Transform left, Transform right) + { + return !left.Equals(right); + } + + public override bool Equals(object obj) + { + if (obj is Transform) + { + return Equals((Transform)obj); + } + + return false; + } + + public bool Equals(Transform other) + { + return basis.Equals(other.basis) && origin.Equals(other.origin); + } + + public override int GetHashCode() + { + return basis.GetHashCode() ^ origin.GetHashCode(); + } + + public override string ToString() + { + return String.Format("{0} - {1}", new object[] + { + this.basis.ToString(), + this.origin.ToString() + }); + } + + public string ToString(string format) + { + return String.Format("{0} - {1}", new object[] + { + this.basis.ToString(format), + this.origin.ToString(format) + }); + } + } +} diff --git a/modules/mono/glue/cs_files/Transform2D.cs b/modules/mono/glue/cs_files/Transform2D.cs new file mode 100644 index 0000000000..526dc767c6 --- /dev/null +++ b/modules/mono/glue/cs_files/Transform2D.cs @@ -0,0 +1,356 @@ +using System; +using System.Runtime.InteropServices; + +namespace Godot +{ + [StructLayout(LayoutKind.Sequential)] + public struct Transform2D : IEquatable<Transform2D> + { + private static readonly Transform2D identity = new Transform2D + ( + new Vector2(1f, 0f), + new Vector2(0f, 1f), + new Vector2(0f, 0f) + ); + + public Vector2 x; + public Vector2 y; + public Vector2 o; + + public static Transform2D Identity + { + get { return identity; } + } + + public Vector2 Origin + { + get { return o; } + } + + public float Rotation + { + get { return Mathf.atan2(y.x, o.y); } + } + + public Vector2 Scale + { + get { return new Vector2(x.length(), y.length()); } + } + + public Vector2 this[int index] + { + get + { + switch (index) + { + case 0: + return x; + case 1: + return y; + case 2: + return o; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + x = value; + return; + case 1: + y = value; + return; + case 2: + o = value; + return; + default: + throw new IndexOutOfRangeException(); + } + } + } + + + public float this[int index, int axis] + { + get + { + switch (index) + { + case 0: + return x[axis]; + case 1: + return y[axis]; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + x[axis] = value; + return; + case 1: + y[axis] = value; + return; + default: + throw new IndexOutOfRangeException(); + } + } + } + + public Transform2D affine_inverse() + { + Transform2D inv = this; + + float det = this[0, 0] * this[1, 1] - this[1, 0] * this[0, 1]; + + if (det == 0) + { + return new Transform2D + ( + float.NaN, float.NaN, + float.NaN, float.NaN, + float.NaN, float.NaN + ); + } + + float idet = 1.0f / det; + + float temp = this[0, 0]; + this[0, 0] = this[1, 1]; + this[1, 1] = temp; + + this[0] *= new Vector2(idet, -idet); + this[1] *= new Vector2(-idet, idet); + + this[2] = basis_xform(-this[2]); + + return inv; + } + + public Vector2 basis_xform(Vector2 v) + { + return new Vector2(tdotx(v), tdoty(v)); + } + + public Vector2 basis_xform_inv(Vector2 v) + { + return new Vector2(x.dot(v), y.dot(v)); + } + + public Transform2D interpolate_with(Transform2D m, float c) + { + float r1 = Rotation; + float r2 = m.Rotation; + + Vector2 s1 = Scale; + Vector2 s2 = m.Scale; + + // Slerp rotation + Vector2 v1 = new Vector2(Mathf.cos(r1), Mathf.sin(r1)); + Vector2 v2 = new Vector2(Mathf.cos(r2), Mathf.sin(r2)); + + float dot = v1.dot(v2); + + // Clamp dot to [-1, 1] + dot = (dot < -1.0f) ? -1.0f : ((dot > 1.0f) ? 1.0f : dot); + + Vector2 v = new Vector2(); + + if (dot > 0.9995f) + { + // Linearly interpolate to avoid numerical precision issues + v = v1.linear_interpolate(v2, c).normalized(); + } + else + { + float angle = c * Mathf.acos(dot); + Vector2 v3 = (v2 - v1 * dot).normalized(); + v = v1 * Mathf.cos(angle) + v3 * Mathf.sin(angle); + } + + // Extract parameters + Vector2 p1 = Origin; + Vector2 p2 = m.Origin; + + // Construct matrix + Transform2D res = new Transform2D(Mathf.atan2(v.y, v.x), p1.linear_interpolate(p2, c)); + Vector2 scale = s1.linear_interpolate(s2, c); + res.x *= scale; + res.y *= scale; + + return res; + } + + public Transform2D inverse() + { + Transform2D inv = this; + + // Swap + float temp = inv.x.y; + inv.x.y = inv.y.x; + inv.y.x = temp; + + inv.o = inv.basis_xform(-inv.o); + + return inv; + } + + public Transform2D orthonormalized() + { + Transform2D on = this; + + Vector2 onX = on.x; + Vector2 onY = on.y; + + onX.normalize(); + onY = onY - onX * (onX.dot(onY)); + onY.normalize(); + + on.x = onX; + on.y = onY; + + return on; + } + + public Transform2D rotated(float phi) + { + return this * new Transform2D(phi, new Vector2()); + } + + public Transform2D scaled(Vector2 scale) + { + Transform2D copy = this; + copy.x *= scale; + copy.y *= scale; + copy.o *= scale; + return copy; + } + + private float tdotx(Vector2 with) + { + return this[0, 0] * with[0] + this[1, 0] * with[1]; + } + + private float tdoty(Vector2 with) + { + return this[0, 1] * with[0] + this[1, 1] * with[1]; + } + + public Transform2D translated(Vector2 offset) + { + Transform2D copy = this; + copy.o += copy.basis_xform(offset); + return copy; + } + + public Vector2 xform(Vector2 v) + { + return new Vector2(tdotx(v), tdoty(v)) + o; + } + + public Vector2 xform_inv(Vector2 v) + { + Vector2 vInv = v - o; + return new Vector2(x.dot(vInv), y.dot(vInv)); + } + + public Transform2D(Vector2 xAxis, Vector2 yAxis, Vector2 origin) + { + this.x = xAxis; + this.y = yAxis; + this.o = origin; + } + public Transform2D(float xx, float xy, float yx, float yy, float ox, float oy) + { + this.x = new Vector2(xx, xy); + this.y = new Vector2(yx, yy); + this.o = new Vector2(ox, oy); + } + + public Transform2D(float rot, Vector2 pos) + { + float cr = Mathf.cos(rot); + float sr = Mathf.sin(rot); + x.x = cr; + y.y = cr; + x.y = -sr; + y.x = sr; + o = pos; + } + + public static Transform2D operator *(Transform2D left, Transform2D right) + { + left.o = left.xform(right.o); + + float x0, x1, y0, y1; + + x0 = left.tdotx(right.x); + x1 = left.tdoty(right.x); + y0 = left.tdotx(right.y); + y1 = left.tdoty(right.y); + + left.x.x = x0; + left.x.y = x1; + left.y.x = y0; + left.y.y = y1; + + return left; + } + + public static bool operator ==(Transform2D left, Transform2D right) + { + return left.Equals(right); + } + + public static bool operator !=(Transform2D left, Transform2D right) + { + return !left.Equals(right); + } + + public override bool Equals(object obj) + { + if (obj is Transform2D) + { + return Equals((Transform2D)obj); + } + + return false; + } + + public bool Equals(Transform2D other) + { + return x.Equals(other.x) && y.Equals(other.y) && o.Equals(other.o); + } + + public override int GetHashCode() + { + return x.GetHashCode() ^ y.GetHashCode() ^ o.GetHashCode(); + } + + public override string ToString() + { + return String.Format("({0}, {1}, {2})", new object[] + { + this.x.ToString(), + this.y.ToString(), + this.o.ToString() + }); + } + + public string ToString(string format) + { + return String.Format("({0}, {1}, {2})", new object[] + { + this.x.ToString(format), + this.y.ToString(format), + this.o.ToString(format) + }); + } + } +} diff --git a/modules/mono/glue/cs_files/Vector2.cs b/modules/mono/glue/cs_files/Vector2.cs new file mode 100644 index 0000000000..28fedc365b --- /dev/null +++ b/modules/mono/glue/cs_files/Vector2.cs @@ -0,0 +1,362 @@ +using System; +using System.Runtime.InteropServices; + +// file: core/math/math_2d.h +// commit: 7ad14e7a3e6f87ddc450f7e34621eb5200808451 +// file: core/math/math_2d.cpp +// commit: 7ad14e7a3e6f87ddc450f7e34621eb5200808451 +// file: core/variant_call.cpp +// commit: 5ad9be4c24e9d7dc5672fdc42cea896622fe5685 + +namespace Godot +{ + [StructLayout(LayoutKind.Sequential)] + public struct Vector2 : IEquatable<Vector2> + { + public float x; + public float y; + + public float this[int index] + { + get + { + switch (index) + { + case 0: + return x; + case 1: + return y; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + x = value; + return; + case 1: + y = value; + return; + default: + throw new IndexOutOfRangeException(); + } + } + } + + internal void normalize() + { + float length = x * x + y * y; + + if (length != 0f) + { + length = Mathf.sqrt(length); + x /= length; + y /= length; + } + } + + private float cross(Vector2 b) + { + return x * b.y - y * b.x; + } + + public Vector2 abs() + { + return new Vector2(Mathf.abs(x), Mathf.abs(y)); + } + + public float angle() + { + return Mathf.atan2(y, x); + } + + public float angle_to(Vector2 to) + { + return Mathf.atan2(cross(to), dot(to)); + } + + public float angle_to_point(Vector2 to) + { + return Mathf.atan2(x - to.x, y - to.y); + } + + public float aspect() + { + return x / y; + } + + public Vector2 bounce(Vector2 n) + { + return -reflect(n); + } + + public Vector2 clamped(float length) + { + Vector2 v = this; + float l = this.length(); + + if (l > 0 && length < l) + { + v /= l; + v *= length; + } + + return v; + } + + public Vector2 cubic_interpolate(Vector2 b, Vector2 preA, Vector2 postB, float t) + { + Vector2 p0 = preA; + Vector2 p1 = this; + Vector2 p2 = b; + Vector2 p3 = postB; + + float t2 = t * t; + float t3 = t2 * t; + + return 0.5f * ((p1 * 2.0f) + + (-p0 + p2) * t + + (2.0f * p0 - 5.0f * p1 + 4 * p2 - p3) * t2 + + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3); + } + + public float distance_squared_to(Vector2 to) + { + return (x - to.x) * (x - to.x) + (y - to.y) * (y - to.y); + } + + public float distance_to(Vector2 to) + { + return Mathf.sqrt((x - to.x) * (x - to.x) + (y - to.y) * (y - to.y)); + } + + public float dot(Vector2 with) + { + return x * with.x + y * with.y; + } + + public Vector2 floor() + { + return new Vector2(Mathf.floor(x), Mathf.floor(y)); + } + + public bool is_normalized() + { + return Mathf.abs(length_squared() - 1.0f) < Mathf.Epsilon; + } + + public float length() + { + return Mathf.sqrt(x * x + y * y); + } + + public float length_squared() + { + return x * x + y * y; + } + + public Vector2 linear_interpolate(Vector2 b, float t) + { + Vector2 res = this; + + res.x += (t * (b.x - x)); + res.y += (t * (b.y - y)); + + return res; + } + + public Vector2 normalized() + { + Vector2 result = this; + result.normalize(); + return result; + } + + public Vector2 reflect(Vector2 n) + { + return 2.0f * n * dot(n) - this; + } + + public Vector2 rotated(float phi) + { + float rads = angle() + phi; + return new Vector2(Mathf.cos(rads), Mathf.sin(rads)) * length(); + } + + public Vector2 slide(Vector2 n) + { + return this - n * dot(n); + } + + public Vector2 snapped(Vector2 by) + { + return new Vector2(Mathf.stepify(x, by.x), Mathf.stepify(y, by.y)); + } + + public Vector2 tangent() + { + return new Vector2(y, -x); + } + + public Vector2(float x, float y) + { + this.x = x; + this.y = y; + } + + public static Vector2 operator +(Vector2 left, Vector2 right) + { + left.x += right.x; + left.y += right.y; + return left; + } + + public static Vector2 operator -(Vector2 left, Vector2 right) + { + left.x -= right.x; + left.y -= right.y; + return left; + } + + public static Vector2 operator -(Vector2 vec) + { + vec.x = -vec.x; + vec.y = -vec.y; + return vec; + } + + public static Vector2 operator *(Vector2 vec, float scale) + { + vec.x *= scale; + vec.y *= scale; + return vec; + } + + public static Vector2 operator *(float scale, Vector2 vec) + { + vec.x *= scale; + vec.y *= scale; + return vec; + } + + public static Vector2 operator *(Vector2 left, Vector2 right) + { + left.x *= right.x; + left.y *= right.y; + return left; + } + + public static Vector2 operator /(Vector2 vec, float scale) + { + vec.x /= scale; + vec.y /= scale; + return vec; + } + + public static Vector2 operator /(Vector2 left, Vector2 right) + { + left.x /= right.x; + left.y /= right.y; + return left; + } + + public static bool operator ==(Vector2 left, Vector2 right) + { + return left.Equals(right); + } + + public static bool operator !=(Vector2 left, Vector2 right) + { + return !left.Equals(right); + } + + public static bool operator <(Vector2 left, Vector2 right) + { + if (left.x.Equals(right.x)) + { + return left.y < right.y; + } + else + { + return left.x < right.x; + } + } + + public static bool operator >(Vector2 left, Vector2 right) + { + if (left.x.Equals(right.x)) + { + return left.y > right.y; + } + else + { + return left.x > right.x; + } + } + + public static bool operator <=(Vector2 left, Vector2 right) + { + if (left.x.Equals(right.x)) + { + return left.y <= right.y; + } + else + { + return left.x <= right.x; + } + } + + public static bool operator >=(Vector2 left, Vector2 right) + { + if (left.x.Equals(right.x)) + { + return left.y >= right.y; + } + else + { + return left.x >= right.x; + } + } + + public override bool Equals(object obj) + { + if (obj is Vector2) + { + return Equals((Vector2)obj); + } + + return false; + } + + public bool Equals(Vector2 other) + { + return x == other.x && y == other.y; + } + + public override int GetHashCode() + { + return y.GetHashCode() ^ x.GetHashCode(); + } + + public override string ToString() + { + return String.Format("({0}, {1})", new object[] + { + this.x.ToString(), + this.y.ToString() + }); + } + + public string ToString(string format) + { + return String.Format("({0}, {1})", new object[] + { + this.x.ToString(format), + this.y.ToString(format) + }); + } + } +} diff --git a/modules/mono/glue/cs_files/Vector3.cs b/modules/mono/glue/cs_files/Vector3.cs new file mode 100644 index 0000000000..c023cd83cf --- /dev/null +++ b/modules/mono/glue/cs_files/Vector3.cs @@ -0,0 +1,420 @@ +using System; +using System.Runtime.InteropServices; + +// file: core/math/vector3.h +// commit: bd282ff43f23fe845f29a3e25c8efc01bd65ffb0 +// file: core/math/vector3.cpp +// commit: 7ad14e7a3e6f87ddc450f7e34621eb5200808451 +// file: core/variant_call.cpp +// commit: 5ad9be4c24e9d7dc5672fdc42cea896622fe5685 + +namespace Godot +{ + [StructLayout(LayoutKind.Sequential)] + public struct Vector3 : IEquatable<Vector3> + { + public enum Axis + { + X = 0, + Y, + Z + } + + public float x; + public float y; + public float z; + + public float this[int index] + { + get + { + switch (index) + { + case 0: + return x; + case 1: + return y; + case 2: + return z; + default: + throw new IndexOutOfRangeException(); + } + } + set + { + switch (index) + { + case 0: + x = value; + return; + case 1: + y = value; + return; + case 2: + z = value; + return; + default: + throw new IndexOutOfRangeException(); + } + } + } + + internal void normalize() + { + float length = this.length(); + + if (length == 0f) + { + x = y = z = 0f; + } + else + { + x /= length; + y /= length; + z /= length; + } + } + + public Vector3 abs() + { + return new Vector3(Mathf.abs(x), Mathf.abs(y), Mathf.abs(z)); + } + + public float angle_to(Vector3 to) + { + return Mathf.atan2(cross(to).length(), dot(to)); + } + + public Vector3 bounce(Vector3 n) + { + return -reflect(n); + } + + public Vector3 ceil() + { + return new Vector3(Mathf.ceil(x), Mathf.ceil(y), Mathf.ceil(z)); + } + + public Vector3 cross(Vector3 b) + { + return new Vector3 + ( + (y * b.z) - (z * b.y), + (z * b.x) - (x * b.z), + (x * b.y) - (y * b.x) + ); + } + + public Vector3 cubic_interpolate(Vector3 b, Vector3 preA, Vector3 postB, float t) + { + Vector3 p0 = preA; + Vector3 p1 = this; + Vector3 p2 = b; + Vector3 p3 = postB; + + float t2 = t * t; + float t3 = t2 * t; + + return 0.5f * ( + (p1 * 2.0f) + (-p0 + p2) * t + + (2.0f * p0 - 5.0f * p1 + 4f * p2 - p3) * t2 + + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3 + ); + } + + public float distance_squared_to(Vector3 b) + { + return (b - this).length_squared(); + } + + public float distance_to(Vector3 b) + { + return (b - this).length(); + } + + public float dot(Vector3 b) + { + return x * b.x + y * b.y + z * b.z; + } + + public Vector3 floor() + { + return new Vector3(Mathf.floor(x), Mathf.floor(y), Mathf.floor(z)); + } + + public Vector3 inverse() + { + return new Vector3(1.0f / x, 1.0f / y, 1.0f / z); + } + + public bool is_normalized() + { + return Mathf.abs(length_squared() - 1.0f) < Mathf.Epsilon; + } + + public float length() + { + float x2 = x * x; + float y2 = y * y; + float z2 = z * z; + + return Mathf.sqrt(x2 + y2 + z2); + } + + public float length_squared() + { + float x2 = x * x; + float y2 = y * y; + float z2 = z * z; + + return x2 + y2 + z2; + } + + public Vector3 linear_interpolate(Vector3 b, float t) + { + return new Vector3 + ( + x + (t * (b.x - x)), + y + (t * (b.y - y)), + z + (t * (b.z - z)) + ); + } + + public Axis max_axis() + { + return x < y ? (y < z ? Axis.Z : Axis.Y) : (x < z ? Axis.Z : Axis.X); + } + + public Axis min_axis() + { + return x < y ? (x < z ? Axis.X : Axis.Z) : (y < z ? Axis.Y : Axis.Z); + } + + public Vector3 normalized() + { + Vector3 v = this; + v.normalize(); + return v; + } + + public Basis outer(Vector3 b) + { + return new Basis( + new Vector3(x * b.x, x * b.y, x * b.z), + new Vector3(y * b.x, y * b.y, y * b.z), + new Vector3(z * b.x, z * b.y, z * b.z) + ); + } + + public Vector3 reflect(Vector3 n) + { +#if DEBUG + if (!n.is_normalized()) + throw new ArgumentException(String.Format("{0} is not normalized", n), nameof(n)); +#endif + return 2.0f * n * dot(n) - this; + } + + public Vector3 rotated(Vector3 axis, float phi) + { + return new Basis(axis, phi).xform(this); + } + + public Vector3 slide(Vector3 n) + { + return this - n * dot(n); + } + + public Vector3 snapped(Vector3 by) + { + return new Vector3 + ( + Mathf.stepify(x, by.x), + Mathf.stepify(y, by.y), + Mathf.stepify(z, by.z) + ); + } + + public Basis to_diagonal_matrix() + { + return new Basis( + x, 0f, 0f, + 0f, y, 0f, + 0f, 0f, z + ); + } + + public Vector3(float x, float y, float z) + { + this.x = x; + this.y = y; + this.z = z; + } + + public static Vector3 operator +(Vector3 left, Vector3 right) + { + left.x += right.x; + left.y += right.y; + left.z += right.z; + return left; + } + + public static Vector3 operator -(Vector3 left, Vector3 right) + { + left.x -= right.x; + left.y -= right.y; + left.z -= right.z; + return left; + } + + public static Vector3 operator -(Vector3 vec) + { + vec.x = -vec.x; + vec.y = -vec.y; + vec.z = -vec.z; + return vec; + } + + public static Vector3 operator *(Vector3 vec, float scale) + { + vec.x *= scale; + vec.y *= scale; + vec.z *= scale; + return vec; + } + + public static Vector3 operator *(float scale, Vector3 vec) + { + vec.x *= scale; + vec.y *= scale; + vec.z *= scale; + return vec; + } + + public static Vector3 operator *(Vector3 left, Vector3 right) + { + left.x *= right.x; + left.y *= right.y; + left.z *= right.z; + return left; + } + + public static Vector3 operator /(Vector3 vec, float scale) + { + vec.x /= scale; + vec.y /= scale; + vec.z /= scale; + return vec; + } + + public static Vector3 operator /(Vector3 left, Vector3 right) + { + left.x /= right.x; + left.y /= right.y; + left.z /= right.z; + return left; + } + + public static bool operator ==(Vector3 left, Vector3 right) + { + return left.Equals(right); + } + + public static bool operator !=(Vector3 left, Vector3 right) + { + return !left.Equals(right); + } + + public static bool operator <(Vector3 left, Vector3 right) + { + if (left.x == right.x) + { + if (left.y == right.y) + return left.z < right.z; + else + return left.y < right.y; + } + + return left.x < right.x; + } + + public static bool operator >(Vector3 left, Vector3 right) + { + if (left.x == right.x) + { + if (left.y == right.y) + return left.z > right.z; + else + return left.y > right.y; + } + + return left.x > right.x; + } + + public static bool operator <=(Vector3 left, Vector3 right) + { + if (left.x == right.x) + { + if (left.y == right.y) + return left.z <= right.z; + else + return left.y < right.y; + } + + return left.x < right.x; + } + + public static bool operator >=(Vector3 left, Vector3 right) + { + if (left.x == right.x) + { + if (left.y == right.y) + return left.z >= right.z; + else + return left.y > right.y; + } + + return left.x > right.x; + } + + public override bool Equals(object obj) + { + if (obj is Vector3) + { + return Equals((Vector3)obj); + } + + return false; + } + + public bool Equals(Vector3 other) + { + return x == other.x && y == other.y && z == other.z; + } + + public override int GetHashCode() + { + return y.GetHashCode() ^ x.GetHashCode() ^ z.GetHashCode(); + } + + public override string ToString() + { + return String.Format("({0}, {1}, {2})", new object[] + { + this.x.ToString(), + this.y.ToString(), + this.z.ToString() + }); + } + + public string ToString(string format) + { + return String.Format("({0}, {1}, {2})", new object[] + { + this.x.ToString(format), + this.y.ToString(format), + this.z.ToString(format) + }); + } + } +} diff --git a/modules/mono/glue/glue_header.h b/modules/mono/glue/glue_header.h new file mode 100644 index 0000000000..0751a0160f --- /dev/null +++ b/modules/mono/glue/glue_header.h @@ -0,0 +1,302 @@ +/*************************************************************************/ +/* glue_header.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "../csharp_script.h" +#include "../mono_gd/gd_mono_class.h" +#include "../mono_gd/gd_mono_internals.h" +#include "../mono_gd/gd_mono_marshal.h" +#include "../signal_awaiter_utils.h" + +#include "bind/core_bind.h" +#include "class_db.h" +#include "io/marshalls.h" +#include "object.h" +#include "os/os.h" +#include "project_settings.h" +#include "reference.h" +#include "variant_parser.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_node.h" +#endif + +#define GODOTSHARP_INSTANCE_OBJECT(m_instance, m_type) \ + static ClassDB::ClassInfo *ci = NULL; \ + if (!ci) { \ + ci = ClassDB::classes.getptr(m_type); \ + } \ + Object *m_instance = ci->creation_func(); + +void godot_icall_Object_Dtor(Object *ptr) { + ERR_FAIL_NULL(ptr); + _GodotSharp::get_singleton()->queue_dispose(ptr); +} + +// -- ClassDB -- + +MethodBind *godot_icall_ClassDB_get_method(MonoString *p_type, MonoString *p_method) { + StringName type(GDMonoMarshal::mono_string_to_godot(p_type)); + StringName method(GDMonoMarshal::mono_string_to_godot(p_method)); + return ClassDB::get_method(type, method); +} + +// -- SignalAwaiter -- + +Error godot_icall_Object_connect_signal_awaiter(Object *p_source, MonoString *p_signal, Object *p_target, MonoObject *p_awaiter) { + String signal = GDMonoMarshal::mono_string_to_godot(p_signal); + return SignalAwaiterUtils::connect_signal_awaiter(p_source, signal, p_target, p_awaiter); +} + +// -- NodePath -- + +NodePath *godot_icall_NodePath_Ctor(MonoString *p_path) { + return memnew(NodePath(GDMonoMarshal::mono_string_to_godot(p_path))); +} + +void godot_icall_NodePath_Dtor(NodePath *p_ptr) { + ERR_FAIL_NULL(p_ptr); + _GodotSharp::get_singleton()->queue_dispose(p_ptr); +} + +MonoString *godot_icall_NodePath_operator_String(NodePath *p_np) { + return GDMonoMarshal::mono_string_from_godot(p_np->operator String()); +} + +MonoArray *godot_icall_String_md5_buffer(MonoString *p_str) { + Vector<uint8_t> ret = GDMonoMarshal::mono_string_to_godot(p_str).md5_buffer(); + // TODO Check possible Array/Vector<uint8_t> problem? + return GDMonoMarshal::Array_to_mono_array(Variant(ret)); +} + +// -- RID -- + +RID *godot_icall_RID_Ctor(Object *p_from) { + Resource *res_from = Object::cast_to<Resource>(p_from); + + if (res_from) + return memnew(RID(res_from->get_rid())); + + return memnew(RID); +} + +void godot_icall_RID_Dtor(RID *p_ptr) { + ERR_FAIL_NULL(p_ptr); + _GodotSharp::get_singleton()->queue_dispose(p_ptr); +} + +// -- String -- + +MonoString *godot_icall_String_md5_text(MonoString *p_str) { + String ret = GDMonoMarshal::mono_string_to_godot(p_str).md5_text(); + return GDMonoMarshal::mono_string_from_godot(ret); +} + +int godot_icall_String_rfind(MonoString *p_str, MonoString *p_what, int p_from) { + String what = GDMonoMarshal::mono_string_to_godot(p_what); + return GDMonoMarshal::mono_string_to_godot(p_str).rfind(what, p_from); +} + +int godot_icall_String_rfindn(MonoString *p_str, MonoString *p_what, int p_from) { + String what = GDMonoMarshal::mono_string_to_godot(p_what); + return GDMonoMarshal::mono_string_to_godot(p_str).rfindn(what, p_from); +} + +MonoArray *godot_icall_String_sha256_buffer(MonoString *p_str) { + Vector<uint8_t> ret = GDMonoMarshal::mono_string_to_godot(p_str).sha256_buffer(); + return GDMonoMarshal::Array_to_mono_array(Variant(ret)); +} + +MonoString *godot_icall_String_sha256_text(MonoString *p_str) { + String ret = GDMonoMarshal::mono_string_to_godot(p_str).sha256_text(); + return GDMonoMarshal::mono_string_from_godot(ret); +} + +// -- Global Scope -- + +MonoObject *godot_icall_Godot_bytes2var(MonoArray *p_bytes) { + Variant ret; + PoolByteArray varr = GDMonoMarshal::mono_array_to_PoolByteArray(p_bytes); + PoolByteArray::Read r = varr.read(); + Error err = decode_variant(ret, r.ptr(), varr.size(), NULL); + if (err != OK) { + ret = RTR("Not enough bytes for decoding bytes, or invalid format."); + } + return GDMonoMarshal::variant_to_mono_object(ret); +} + +MonoObject *godot_icall_Godot_convert(MonoObject *p_what, int p_type) { + Variant what = GDMonoMarshal::mono_object_to_variant(p_what); + const Variant *args[1] = { &what }; + Variant::CallError ce; + Variant ret = Variant::construct(Variant::Type(p_type), args, 1, ce); + ERR_FAIL_COND_V(ce.error != Variant::CallError::CALL_OK, NULL); + return GDMonoMarshal::variant_to_mono_object(ret); +} + +int godot_icall_Godot_hash(MonoObject *p_var) { + return GDMonoMarshal::mono_object_to_variant(p_var).hash(); +} + +MonoObject *godot_icall_Godot_instance_from_id(int p_instance_id) { + return GDMonoUtils::unmanaged_get_managed(ObjectDB::get_instance(p_instance_id)); +} + +void godot_icall_Godot_print(MonoArray *p_what) { + Array what = GDMonoMarshal::mono_array_to_Array(p_what); + String str; + for (int i = 0; i < what.size(); i++) + str += what[i].operator String(); + print_line(str); +} + +void godot_icall_Godot_printerr(MonoArray *p_what) { + Array what = GDMonoMarshal::mono_array_to_Array(p_what); + String str; + for (int i = 0; i < what.size(); i++) + str += what[i].operator String(); + OS::get_singleton()->printerr("%s\n", str.utf8().get_data()); +} + +void godot_icall_Godot_printraw(MonoArray *p_what) { + Array what = GDMonoMarshal::mono_array_to_Array(p_what); + String str; + for (int i = 0; i < what.size(); i++) + str += what[i].operator String(); + OS::get_singleton()->print("%s", str.utf8().get_data()); +} + +void godot_icall_Godot_prints(MonoArray *p_what) { + Array what = GDMonoMarshal::mono_array_to_Array(p_what); + String str; + for (int i = 0; i < what.size(); i++) { + if (i) + str += " "; + str += what[i].operator String(); + } + print_line(str); +} + +void godot_icall_Godot_printt(MonoArray *p_what) { + Array what = GDMonoMarshal::mono_array_to_Array(p_what); + String str; + for (int i = 0; i < what.size(); i++) { + if (i) + str += "\t"; + str += what[i].operator String(); + } + print_line(str); +} + +void godot_icall_Godot_seed(int p_seed) { + Math::seed(p_seed); +} + +MonoString *godot_icall_Godot_str(MonoArray *p_what) { + String str; + Array what = GDMonoMarshal::mono_array_to_Array(p_what); + + for (int i = 0; i < what.size(); i++) { + String os = what[i].operator String(); + + if (i == 0) + str = os; + else + str += os; + } + + return GDMonoMarshal::mono_string_from_godot(str); +} + +MonoObject *godot_icall_Godot_str2var(MonoString *p_str) { + Variant ret; + + VariantParser::StreamString ss; + ss.s = GDMonoMarshal::mono_string_to_godot(p_str); + + String errs; + int line; + Error err = VariantParser::parse(&ss, ret, errs, line); + if (err != OK) { + String err_str = "Parse error at line " + itos(line) + ": " + errs; + ERR_PRINTS(err_str); + ret = err_str; + } + + return GDMonoMarshal::variant_to_mono_object(ret); +} + +bool godot_icall_Godot_type_exists(MonoString *p_type) { + return ClassDB::class_exists(GDMonoMarshal::mono_string_to_godot(p_type)); +} + +MonoArray *godot_icall_Godot_var2bytes(MonoObject *p_var) { + Variant var = GDMonoMarshal::mono_object_to_variant(p_var); + + PoolByteArray barr; + int len; + Error err = encode_variant(var, NULL, len); + ERR_EXPLAIN("Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID)."); + ERR_FAIL_COND_V(err != OK, NULL); + + barr.resize(len); + { + PoolByteArray::Write w = barr.write(); + encode_variant(var, w.ptr(), len); + } + + return GDMonoMarshal::PoolByteArray_to_mono_array(barr); +} + +MonoString *godot_icall_Godot_var2str(MonoObject *p_var) { + String vars; + VariantWriter::write_to_string(GDMonoMarshal::mono_object_to_variant(p_var), vars); + return GDMonoMarshal::mono_string_from_godot(vars); +} + +MonoObject *godot_icall_Godot_weakref(Object *p_obj) { + if (!p_obj) + return NULL; + + Ref<WeakRef> wref; + Reference *ref = Object::cast_to<Reference>(p_obj); + + if (ref) { + REF r = ref; + if (!r.is_valid()) + return NULL; + + wref.instance(); + wref->set_ref(r); + } else { + wref.instance(); + wref->set_obj(p_obj); + } + + return GDMonoUtils::create_managed_for_godot_object(CACHED_CLASS(WeakRef), Reference::get_class_static(), Object::cast_to<Object>(wref.ptr())); +} diff --git a/modules/mono/godotsharp_defs.h b/modules/mono/godotsharp_defs.h new file mode 100644 index 0000000000..f941a4d6c5 --- /dev/null +++ b/modules/mono/godotsharp_defs.h @@ -0,0 +1,41 @@ +/*************************************************************************/ +/* godotsharp_defs.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef GODOTSHARP_DEFS_H +#define GODOTSHARP_DEFS_H + +#define BINDINGS_NAMESPACE "Godot" +#define BINDINGS_GLOBAL_SCOPE_CLASS "GD" +#define BINDINGS_PTR_FIELD "ptr" +#define BINDINGS_NATIVE_NAME_FIELD "nativeName" +#define API_ASSEMBLY_NAME "GodotSharp" +#define EDITOR_API_ASSEMBLY_NAME "GodotSharpEditor" +#define EDITOR_TOOLS_ASSEMBLY_NAME "GodotSharpTools" + +#endif // GODOTSHARP_DEFS_H diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp new file mode 100644 index 0000000000..0a2010e99d --- /dev/null +++ b/modules/mono/godotsharp_dirs.cpp @@ -0,0 +1,185 @@ +/*************************************************************************/ +/* godotsharp_dirs.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "godotsharp_dirs.h" + +#include "os/os.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#include "project_settings.h" +#include "version.h" +#endif + +namespace GodotSharpDirs { + +String _get_expected_build_config() { +#ifdef TOOLS_ENABLED + return "Tools"; +#else + +#ifdef DEBUG_ENABLED + return "Debug"; +#else + return "Release"; +#endif + +#endif +} + +String _get_mono_user_dir() { +#ifdef TOOLS_ENABLED + if (EditorSettings::get_singleton()) { + return EditorSettings::get_singleton()->get_settings_path().plus_file("mono"); + } else { + String settings_path; + + if (OS::get_singleton()->has_environment("APPDATA")) { + String app_data = OS::get_singleton()->get_environment("APPDATA").replace("\\", "/"); + settings_path = app_data.plus_file(String(_MKSTR(VERSION_SHORT_NAME)).capitalize()); + } else if (OS::get_singleton()->has_environment("HOME")) { + String home = OS::get_singleton()->get_environment("HOME"); + settings_path = home.plus_file("." + String(_MKSTR(VERSION_SHORT_NAME)).to_lower()); + } + + return settings_path.plus_file("mono"); + } +#else + return OS::get_singleton()->get_data_dir().plus_file("mono"); +#endif +} + +class _GodotSharpDirs { + +public: + String res_data_dir; + String res_metadata_dir; + String res_assemblies_dir; + String res_config_dir; + String res_temp_dir; + String res_temp_assemblies_base_dir; + String res_temp_assemblies_dir; + String mono_user_dir; + String mono_logs_dir; + +#ifdef TOOLS_ENABLED + String mono_solutions_dir; + String build_logs_dir; + String sln_filepath; + String csproj_filepath; +#endif + +private: + _GodotSharpDirs() { + res_data_dir = "res://.mono"; + res_metadata_dir = res_data_dir.plus_file("metadata"); + res_assemblies_dir = res_data_dir.plus_file("assemblies"); + res_config_dir = res_data_dir.plus_file("etc").plus_file("mono"); + + // TODO use paths from csproj + res_temp_dir = res_data_dir.plus_file("temp"); + res_temp_assemblies_base_dir = res_temp_dir.plus_file("bin"); + res_temp_assemblies_dir = res_temp_assemblies_base_dir.plus_file(_get_expected_build_config()); + + mono_user_dir = _get_mono_user_dir(); + mono_logs_dir = mono_user_dir.plus_file("mono_logs"); + +#ifdef TOOLS_ENABLED + mono_solutions_dir = mono_user_dir.plus_file("solutions"); + build_logs_dir = mono_user_dir.plus_file("build_logs"); + String base_path = String("res://") + ProjectSettings::get_singleton()->get("application/config/name"); + sln_filepath = ProjectSettings::get_singleton()->globalize_path(base_path + ".sln"); + csproj_filepath = ProjectSettings::get_singleton()->globalize_path(base_path + ".csproj"); +#endif + } + + _GodotSharpDirs(const _GodotSharpDirs &); + _GodotSharpDirs &operator=(const _GodotSharpDirs &); + +public: + static _GodotSharpDirs &get_singleton() { + static _GodotSharpDirs singleton; + return singleton; + } +}; + +String get_res_data_dir() { + return _GodotSharpDirs::get_singleton().res_data_dir; +} + +String get_res_metadata_dir() { + return _GodotSharpDirs::get_singleton().res_metadata_dir; +} + +String get_res_assemblies_dir() { + return _GodotSharpDirs::get_singleton().res_assemblies_dir; +} + +String get_res_config_dir() { + return _GodotSharpDirs::get_singleton().res_config_dir; +} + +String get_res_temp_dir() { + return _GodotSharpDirs::get_singleton().res_temp_dir; +} + +String get_res_temp_assemblies_base_dir() { + return _GodotSharpDirs::get_singleton().res_temp_assemblies_base_dir; +} + +String get_res_temp_assemblies_dir() { + return _GodotSharpDirs::get_singleton().res_temp_assemblies_dir; +} + +String get_mono_user_dir() { + return _GodotSharpDirs::get_singleton().mono_user_dir; +} + +String get_mono_logs_dir() { + return _GodotSharpDirs::get_singleton().mono_logs_dir; +} + +#ifdef TOOLS_ENABLED +String get_mono_solutions_dir() { + return _GodotSharpDirs::get_singleton().mono_solutions_dir; +} + +String get_build_logs_dir() { + return _GodotSharpDirs::get_singleton().build_logs_dir; +} + +String get_project_sln_path() { + return _GodotSharpDirs::get_singleton().sln_filepath; +} + +String get_project_csproj_path() { + return _GodotSharpDirs::get_singleton().csproj_filepath; +} +#endif +} diff --git a/modules/mono/godotsharp_dirs.h b/modules/mono/godotsharp_dirs.h new file mode 100644 index 0000000000..ba2c065210 --- /dev/null +++ b/modules/mono/godotsharp_dirs.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* godotsharp_dirs.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef GODOTSHARP_DIRS_H +#define GODOTSHARP_DIRS_H + +#include "ustring.h" + +namespace GodotSharpDirs { + +String get_res_data_dir(); +String get_res_metadata_dir(); +String get_res_assemblies_dir(); +String get_res_config_dir(); +String get_res_temp_dir(); +String get_res_temp_assemblies_base_dir(); +String get_res_temp_assemblies_dir(); + +String get_mono_user_dir(); +String get_mono_logs_dir(); + +#ifdef TOOLS_ENABLED +String get_mono_solutions_dir(); +String get_build_logs_dir(); +String get_custom_project_settings_dir(); +#endif + +String get_project_sln_path(); +String get_project_csproj_path(); +} + +#endif // GODOTSHARP_DIRS_H diff --git a/modules/mono/mono_gc_handle.cpp b/modules/mono/mono_gc_handle.cpp new file mode 100644 index 0000000000..d3ad968135 --- /dev/null +++ b/modules/mono/mono_gc_handle.cpp @@ -0,0 +1,77 @@ +/*************************************************************************/ +/* mono_gc_handle.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "mono_gc_handle.h" + +#include "mono_gd/gd_mono.h" + +uint32_t MonoGCHandle::make_strong_handle(MonoObject *p_object) { + + return mono_gchandle_new( + p_object, + false /* do not pin the object */ + ); +} + +uint32_t MonoGCHandle::make_weak_handle(MonoObject *p_object) { + + return mono_gchandle_new_weakref( + p_object, + true /* track_resurrection: allows us to invoke _notification(NOTIFICATION_PREDELETE) while disposing */ + ); +} + +Ref<MonoGCHandle> MonoGCHandle::create_strong(MonoObject *p_object) { + + return memnew(MonoGCHandle(make_strong_handle(p_object))); +} + +Ref<MonoGCHandle> MonoGCHandle::create_weak(MonoObject *p_object) { + + return memnew(MonoGCHandle(make_weak_handle(p_object))); +} + +void MonoGCHandle::release() { + + if (!released && GDMono::get_singleton()->is_runtime_initialized()) { + mono_gchandle_free(handle); + released = true; + } +} + +MonoGCHandle::MonoGCHandle(uint32_t p_handle) { + + released = false; + handle = p_handle; +} + +MonoGCHandle::~MonoGCHandle() { + + release(); +} diff --git a/modules/mono/mono_gc_handle.h b/modules/mono/mono_gc_handle.h new file mode 100644 index 0000000000..cf5b6cec21 --- /dev/null +++ b/modules/mono/mono_gc_handle.h @@ -0,0 +1,63 @@ +/*************************************************************************/ +/* mono_gc_handle.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef CSHARP_GC_HANDLE_H +#define CSHARP_GC_HANDLE_H + +#include <mono/jit/jit.h> + +#include "reference.h" + +class MonoGCHandle : public Reference { + + GDCLASS(MonoGCHandle, Reference) + + bool released; + uint32_t handle; + +public: + static uint32_t make_strong_handle(MonoObject *p_object); + static uint32_t make_weak_handle(MonoObject *p_object); + + static Ref<MonoGCHandle> create_strong(MonoObject *p_object); + static Ref<MonoGCHandle> create_weak(MonoObject *p_object); + + _FORCE_INLINE_ MonoObject *get_target() const { return released ? NULL : mono_gchandle_get_target(handle); } + + _FORCE_INLINE_ void set_handle(uint32_t p_handle) { + handle = p_handle; + released = false; + } + void release(); + + MonoGCHandle(uint32_t p_handle); + ~MonoGCHandle(); +}; + +#endif // CSHARP_GC_HANDLE_H diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp new file mode 100644 index 0000000000..77f01842bb --- /dev/null +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -0,0 +1,771 @@ +/*************************************************************************/ +/* gd_mono.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "gd_mono.h" + +#include <mono/metadata/mono-config.h> +#include <mono/metadata/mono-debug.h> + +#include "os/dir_access.h" +#include "os/file_access.h" +#include "os/os.h" +#include "os/thread.h" +#include "project_settings.h" + +#include "../csharp_script.h" +#include "../utils/path_utils.h" +#include "gd_mono_utils.h" + +#ifdef TOOLS_ENABLED +#include "../editor/godotsharp_editor.h" +#endif + +#ifdef MONO_PRINT_HANDLER_ENABLED +void gdmono_MonoPrintCallback(const char *string, mono_bool is_stdout) { + + if (is_stdout) { + OS::get_singleton()->print(string); + } else { + OS::get_singleton()->printerr(string); + } +} +#endif + +GDMono *GDMono::singleton = NULL; + +#ifdef DEBUG_ENABLED +static bool _wait_for_debugger_msecs(uint32_t p_msecs) { + + do { + if (mono_is_debugger_attached()) + return true; + + int last_tick = OS::get_singleton()->get_ticks_msec(); + + OS::get_singleton()->delay_usec((p_msecs < 25 ? p_msecs : 25) * 1000); + + int tdiff = OS::get_singleton()->get_ticks_msec() - last_tick; + + if (tdiff > p_msecs) { + p_msecs = 0; + } else { + p_msecs -= tdiff; + } + } while (p_msecs > 0); + + return mono_is_debugger_attached(); +} +#endif + +#ifdef TOOLS_ENABLED +// temporary workaround. should be provided from Main::setup/setup2 instead +bool _is_project_manager_requested() { + + List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); + for (List<String>::Element *E = cmdline_args.front(); E; E = E->next()) { + const String &arg = E->get(); + if (arg == "-p" || arg == "--project-manager") + return true; + } + + return false; +} +#endif + +#ifdef DEBUG_ENABLED +void gdmono_debug_init() { + + mono_debug_init(MONO_DEBUG_FORMAT_MONO); + + int da_port = GLOBAL_DEF("mono/debugger_agent/port", 23685); + bool da_suspend = GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false); + int da_timeout = GLOBAL_DEF("mono/debugger_agent/wait_timeout", 3000); + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint() || + ProjectSettings::get_singleton()->get_resource_path().empty() || + _is_project_manager_requested()) { + return; + } +#endif + + CharString da_args = String("--debugger-agent=transport=dt_socket,address=127.0.0.1:" + itos(da_port) + + ",embedding=1,server=y,suspend=" + (da_suspend ? "y,timeout=" + itos(da_timeout) : "n")) + .utf8(); + // --debugger-agent=help + const char *options[] = { + "--soft-breakpoints", + da_args.get_data() + }; + mono_jit_parse_options(2, (char **)options); +} +#endif + +void GDMono::initialize() { + + ERR_FAIL_NULL(Engine::get_singleton()); + + OS::get_singleton()->print("Initializing mono...\n"); + +#ifdef DEBUG_METHODS_ENABLED + _initialize_and_check_api_hashes(); +#endif + + GDMonoLog::get_singleton()->initialize(); + +#ifdef MONO_PRINT_HANDLER_ENABLED + mono_trace_set_print_handler(gdmono_MonoPrintCallback); + mono_trace_set_printerr_handler(gdmono_MonoPrintCallback); +#endif + +#ifdef WINDOWS_ENABLED + mono_reg_info = MonoRegUtils::find_mono(); + + CharString assembly_dir; + CharString config_dir; + + if (mono_reg_info.assembly_dir.length() && DirAccess::exists(mono_reg_info.assembly_dir)) { + assembly_dir = mono_reg_info.assembly_dir.utf8(); + } + + if (mono_reg_info.config_dir.length() && DirAccess::exists(mono_reg_info.config_dir)) { + config_dir = mono_reg_info.config_dir.utf8(); + } + + mono_set_dirs(assembly_dir.length() ? assembly_dir.get_data() : NULL, + config_dir.length() ? config_dir.get_data() : NULL); +#else + mono_set_dirs(NULL, NULL); +#endif + + GDMonoAssembly::initialize(); + +#ifdef DEBUG_ENABLED + gdmono_debug_init(); +#endif + + mono_config_parse(NULL); + + root_domain = mono_jit_init_version("GodotEngine.RootDomain", "v4.0.30319"); + + ERR_EXPLAIN("Mono: Failed to initialize runtime"); + ERR_FAIL_NULL(root_domain); + + GDMonoUtils::set_main_thread(GDMonoUtils::get_current_thread()); + + runtime_initialized = true; + + OS::get_singleton()->print("Mono: Runtime initialized\n"); + + // mscorlib assembly MUST be present at initialization + ERR_EXPLAIN("Mono: Failed to load mscorlib assembly"); + ERR_FAIL_COND(!_load_corlib_assembly()); + +#ifdef TOOLS_ENABLED + // The tools domain must be loaded here, before the scripts domain. + // Otherwise domain unload on the scripts domain will hang indefinitely. + + ERR_EXPLAIN("Mono: Failed to load tools domain"); + ERR_FAIL_COND(_load_tools_domain() != OK); + + // TODO move to editor init callback, and do it lazily when required before editor init (e.g.: bindings generation) + ERR_EXPLAIN("Mono: Failed to load Editor Tools assembly"); + ERR_FAIL_COND(!_load_editor_tools_assembly()); +#endif + + ERR_EXPLAIN("Mono: Failed to load scripts domain"); + ERR_FAIL_COND(_load_scripts_domain() != OK); + +#ifdef DEBUG_ENABLED + bool debugger_attached = _wait_for_debugger_msecs(500); + if (!debugger_attached && OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->printerr("Mono: Debugger wait timeout\n"); +#endif + + _register_internal_calls(); + + // The following assemblies are not required at initialization + _load_all_script_assemblies(); + + OS::get_singleton()->print("Mono: EVERYTHING OK\n"); +} + +#ifndef MONO_GLUE_DISABLED +namespace GodotSharpBindings { + +uint64_t get_core_api_hash(); +uint64_t get_editor_api_hash(); + +void register_generated_icalls(); +} // namespace GodotSharpBindings +#endif + +void GDMono::_register_internal_calls() { +#ifndef MONO_GLUE_DISABLED + GodotSharpBindings::register_generated_icalls(); +#endif + +#ifdef TOOLS_ENABLED + GodotSharpBuilds::_register_internal_calls(); +#endif +} + +#ifdef DEBUG_METHODS_ENABLED +void GDMono::_initialize_and_check_api_hashes() { + + api_core_hash = ClassDB::get_api_hash(ClassDB::API_CORE); + +#ifndef MONO_GLUE_DISABLED + if (api_core_hash != GodotSharpBindings::get_core_api_hash()) { + ERR_PRINT("Mono: Core API hash mismatch!"); + } +#endif + +#ifdef TOOLS_ENABLED + api_editor_hash = ClassDB::get_api_hash(ClassDB::API_EDITOR); + +#ifndef MONO_GLUE_DISABLED + if (api_editor_hash != GodotSharpBindings::get_editor_api_hash()) { + ERR_PRINT("Mono: Editor API hash mismatch!"); + } +#endif + +#endif // TOOLS_ENABLED +} +#endif // DEBUG_METHODS_ENABLED + +void GDMono::add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly) { + + assemblies[p_domain_id][p_assembly->get_name()] = p_assembly; +} + +bool GDMono::_load_assembly(const String &p_name, GDMonoAssembly **r_assembly) { + + CRASH_COND(!r_assembly); + + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->print((String() + "Mono: Loading assembly " + p_name + "...\n").utf8()); + + MonoImageOpenStatus status; + MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8()); + MonoAssembly *assembly = mono_assembly_load_full(aname, NULL, &status, false); + mono_assembly_name_free(aname); + + if (!assembly) + return false; + + uint32_t domain_id = mono_domain_get_id(mono_domain_get()); + + GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name); + + if (stored_assembly) { + // Loaded by our preload hook (status is not initialized when returning from a preload hook) + ERR_FAIL_COND_V((*stored_assembly)->get_assembly() != assembly, false); + *r_assembly = *stored_assembly; + } else { + ERR_FAIL_COND_V(status != MONO_IMAGE_OK, false); + + MonoImage *assembly_image = mono_assembly_get_image(assembly); + ERR_FAIL_NULL_V(assembly_image, false); + + const char *path = mono_image_get_filename(assembly_image); + + *r_assembly = memnew(GDMonoAssembly(p_name, path)); + Error error = (*r_assembly)->wrapper_for_image(assembly_image); + + if (error != OK) { + memdelete(*r_assembly); + *r_assembly = NULL; + ERR_FAIL_V(false); + } + } + + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->print(String("Mono: Assembly " + p_name + " loaded from path: " + (*r_assembly)->get_path() + "\n").utf8()); + + return true; +} + +bool GDMono::_load_corlib_assembly() { + + if (corlib_assembly) + return true; + + bool success = _load_assembly("mscorlib", &corlib_assembly); + + if (success) + GDMonoUtils::update_corlib_cache(); + + return success; +} + +bool GDMono::_load_core_api_assembly() { + + if (api_assembly) + return true; + + bool success = _load_assembly(API_ASSEMBLY_NAME, &api_assembly); + + if (success) + GDMonoUtils::update_godot_api_cache(); + + return success; +} + +#ifdef TOOLS_ENABLED +bool GDMono::_load_editor_api_assembly() { + + if (editor_api_assembly) + return true; + + return _load_assembly(EDITOR_API_ASSEMBLY_NAME, &editor_api_assembly); +} +#endif + +#ifdef TOOLS_ENABLED +bool GDMono::_load_editor_tools_assembly() { + + if (editor_tools_assembly) + return true; + + _GDMONO_SCOPE_DOMAIN_(tools_domain) + + return _load_assembly(EDITOR_TOOLS_ASSEMBLY_NAME, &editor_tools_assembly); +} +#endif + +bool GDMono::_load_project_assembly() { + + if (project_assembly) + return true; + + String project_assembly_name = ProjectSettings::get_singleton()->get("application/config/name"); + + bool success = _load_assembly(project_assembly_name, &project_assembly); + + if (success) + mono_assembly_set_main(project_assembly->get_assembly()); + + return success; +} + +bool GDMono::_load_all_script_assemblies() { + +#ifndef MONO_GLUE_DISABLED + if (!_load_core_api_assembly()) { + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->printerr("Mono: Failed to load Core API assembly\n"); + return false; + } else { +#ifdef TOOLS_ENABLED + if (!_load_editor_api_assembly()) { + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->printerr("Mono: Failed to load Editor API assembly\n"); + return false; + } +#endif + } + + if (!_load_project_assembly()) { + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->printerr("Mono: Failed to load project assembly\n"); + return false; + } + + return true; +#else + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->print("Mono: Glue disbled, ignoring script assemblies\n"); + + return true; +#endif +} + +Error GDMono::_load_scripts_domain() { + + ERR_FAIL_COND_V(scripts_domain != NULL, ERR_BUG); + + if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Mono: Loading scripts domain...\n"); + } + + scripts_domain = GDMonoUtils::create_domain("GodotEngine.ScriptsDomain"); + + ERR_EXPLAIN("Mono: Could not create scripts app domain"); + ERR_FAIL_NULL_V(scripts_domain, ERR_CANT_CREATE); + + mono_domain_set(scripts_domain, true); + + return OK; +} + +Error GDMono::_unload_scripts_domain() { + + ERR_FAIL_NULL_V(scripts_domain, ERR_BUG); + + if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Mono: Unloading scripts domain...\n"); + } + + _GodotSharp::get_singleton()->_dispose_callback(); + + if (mono_domain_get() != root_domain) + mono_domain_set(root_domain, true); + + finalizing_scripts_domain = true; + mono_domain_finalize(scripts_domain, 2000); + finalizing_scripts_domain = false; + + _domain_assemblies_cleanup(mono_domain_get_id(scripts_domain)); + + api_assembly = NULL; + project_assembly = NULL; +#ifdef TOOLS_ENABLED + editor_api_assembly = NULL; +#endif + + MonoDomain *domain = scripts_domain; + scripts_domain = NULL; + + _GodotSharp::get_singleton()->_dispose_callback(); + + MonoObject *ex = NULL; + mono_domain_try_unload(domain, &ex); + + if (ex) { + ERR_PRINT("Exception thrown when unloading scripts domain:"); + mono_print_unhandled_exception(ex); + return FAILED; + } + + return OK; +} + +#ifdef TOOLS_ENABLED +Error GDMono::_load_tools_domain() { + + ERR_FAIL_COND_V(tools_domain != NULL, ERR_BUG); + + if (OS::get_singleton()->is_stdout_verbose()) { + OS::get_singleton()->print("Mono: Loading tools domain...\n"); + } + + tools_domain = GDMonoUtils::create_domain("GodotEngine.ToolsDomain"); + + ERR_EXPLAIN("Mono: Could not create tools app domain"); + ERR_FAIL_NULL_V(tools_domain, ERR_CANT_CREATE); + + return OK; +} +#endif + +#ifdef TOOLS_ENABLED +Error GDMono::reload_scripts_domain() { + + ERR_FAIL_COND_V(!runtime_initialized, ERR_BUG); + + if (scripts_domain) { + Error err = _unload_scripts_domain(); + if (err != OK) { + ERR_PRINT("Mono: Failed to unload scripts domain"); + return err; + } + } + + Error err = _load_scripts_domain(); + if (err != OK) { + ERR_PRINT("Mono: Failed to load scripts domain"); + return err; + } + + if (!_load_all_script_assemblies()) { + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->printerr("Mono: Failed to load script assemblies\n"); + return ERR_CANT_OPEN; + } + + return OK; +} +#endif + +GDMonoClass *GDMono::get_class(MonoClass *p_raw_class) { + + MonoImage *image = mono_class_get_image(p_raw_class); + + if (image == corlib_assembly->get_image()) + return corlib_assembly->get_class(p_raw_class); + + uint32_t domain_id = mono_domain_get_id(mono_domain_get()); + HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[domain_id]; + + const String *k = NULL; + while ((k = domain_assemblies.next(k))) { + GDMonoAssembly *assembly = domain_assemblies.get(*k); + if (assembly->get_image() == image) { + GDMonoClass *klass = assembly->get_class(p_raw_class); + + if (klass) + return klass; + } + } + + return NULL; +} + +void GDMono::_domain_assemblies_cleanup(uint32_t p_domain_id) { + + HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[p_domain_id]; + + const String *k = NULL; + while ((k = domain_assemblies.next(k))) { + memdelete(domain_assemblies.get(*k)); + } + + assemblies.erase(p_domain_id); +} + +GDMono::GDMono() { + + singleton = this; + + gdmono_log = memnew(GDMonoLog); + + runtime_initialized = false; + finalizing_scripts_domain = false; + + root_domain = NULL; + scripts_domain = NULL; +#ifdef TOOLS_ENABLED + tools_domain = NULL; +#endif + + corlib_assembly = NULL; + api_assembly = NULL; + project_assembly = NULL; +#ifdef TOOLS_ENABLED + editor_api_assembly = NULL; + editor_tools_assembly = NULL; +#endif + +#ifdef DEBUG_METHODS_ENABLED + api_core_hash = 0; +#ifdef TOOLS_ENABLED + api_editor_hash = 0; +#endif +#endif +} + +GDMono::~GDMono() { + + if (runtime_initialized) { + + if (scripts_domain) { + + Error err = _unload_scripts_domain(); + if (err != OK) { + WARN_PRINT("Mono: Failed to unload scripts domain"); + } + } + + const uint32_t *k = NULL; + while ((k = assemblies.next(k))) { + HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies.get(*k); + + const String *kk = NULL; + while ((kk = domain_assemblies.next(kk))) { + memdelete(domain_assemblies.get(*kk)); + } + } + assemblies.clear(); + + GDMonoUtils::clear_cache(); + + OS::get_singleton()->print("Mono: Runtime cleanup...\n"); + + runtime_initialized = false; + mono_jit_cleanup(root_domain); + } + + if (gdmono_log) + memdelete(gdmono_log); +} + +_GodotSharp *_GodotSharp::singleton = NULL; + +void _GodotSharp::_dispose_object(Object *p_object) { + + if (p_object->get_script_instance()) { + CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(p_object->get_script_instance()); + if (cs_instance) { + cs_instance->mono_object_disposed(); + return; + } + } + + // Unsafe refcount decrement. The managed instance also counts as a reference. + // See: CSharpLanguage::alloc_instance_binding_data(Object *p_object) + if (Object::cast_to<Reference>(p_object)->unreference()) { + memdelete(p_object); + } +} + +void _GodotSharp::_dispose_callback() { + +#ifndef NO_THREADS + queue_mutex->lock(); +#endif + + for (List<Object *>::Element *E = obj_delete_queue.front(); E; E = E->next()) { + _dispose_object(E->get()); + } + + for (List<NodePath *>::Element *E = np_delete_queue.front(); E; E = E->next()) { + memdelete(E->get()); + } + + for (List<RID *>::Element *E = rid_delete_queue.front(); E; E = E->next()) { + memdelete(E->get()); + } + + obj_delete_queue.clear(); + np_delete_queue.clear(); + rid_delete_queue.clear(); + queue_empty = true; + +#ifndef NO_THREADS + queue_mutex->unlock(); +#endif +} + +void _GodotSharp::attach_thread() { + + GDMonoUtils::attach_current_thread(); +} + +void _GodotSharp::detach_thread() { + + GDMonoUtils::detach_current_thread(); +} + +bool _GodotSharp::is_finalizing_domain() { + + return GDMono::get_singleton()->is_finalizing_scripts_domain(); +} + +bool _GodotSharp::is_domain_loaded() { + + return GDMono::get_singleton()->get_scripts_domain() != NULL; +} + +#define ENQUEUE_FOR_DISPOSAL(m_queue, m_inst) \ + m_queue.push_back(m_inst); \ + if (queue_empty) { \ + queue_empty = false; \ + call_deferred("_dispose_callback"); \ + } + +void _GodotSharp::queue_dispose(Object *p_object) { + + if (Thread::get_main_id() == Thread::get_caller_id() && !GDMono::get_singleton()->is_finalizing_scripts_domain()) { + _dispose_object(p_object); + } else { +#ifndef NO_THREADS + queue_mutex->lock(); +#endif + + ENQUEUE_FOR_DISPOSAL(obj_delete_queue, p_object); + +#ifndef NO_THREADS + queue_mutex->unlock(); +#endif + } +} + +void _GodotSharp::queue_dispose(NodePath *p_node_path) { + + if (Thread::get_main_id() == Thread::get_caller_id() && !GDMono::get_singleton()->is_finalizing_scripts_domain()) { + memdelete(p_node_path); + } else { +#ifndef NO_THREADS + queue_mutex->lock(); +#endif + + ENQUEUE_FOR_DISPOSAL(np_delete_queue, p_node_path); + +#ifndef NO_THREADS + queue_mutex->unlock(); +#endif + } +} + +void _GodotSharp::queue_dispose(RID *p_rid) { + + if (Thread::get_main_id() == Thread::get_caller_id() && !GDMono::get_singleton()->is_finalizing_scripts_domain()) { + memdelete(p_rid); + } else { +#ifndef NO_THREADS + queue_mutex->lock(); +#endif + + ENQUEUE_FOR_DISPOSAL(rid_delete_queue, p_rid); + +#ifndef NO_THREADS + queue_mutex->unlock(); +#endif + } +} + +void _GodotSharp::_bind_methods() { + + ClassDB::bind_method(D_METHOD("attach_thread"), &_GodotSharp::attach_thread); + ClassDB::bind_method(D_METHOD("detach_thread"), &_GodotSharp::detach_thread); + + ClassDB::bind_method(D_METHOD("is_finalizing_domain"), &_GodotSharp::is_finalizing_domain); + ClassDB::bind_method(D_METHOD("is_domain_loaded"), &_GodotSharp::is_domain_loaded); + + ClassDB::bind_method(D_METHOD("_dispose_callback"), &_GodotSharp::_dispose_callback); +} + +_GodotSharp::_GodotSharp() { + + singleton = this; + queue_empty = true; +#ifndef NO_THREADS + queue_mutex = Mutex::create(); +#endif +} + +_GodotSharp::~_GodotSharp() { + + singleton = NULL; + + if (queue_mutex) { + memdelete(queue_mutex); + } +} diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h new file mode 100644 index 0000000000..ab96d575e6 --- /dev/null +++ b/modules/mono/mono_gd/gd_mono.h @@ -0,0 +1,224 @@ +/*************************************************************************/ +/* gd_mono.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef GD_MONO_H +#define GD_MONO_H + +#include "../godotsharp_defs.h" +#include "gd_mono_assembly.h" +#include "gd_mono_log.h" + +#ifdef WINDOWS_ENABLED +#include "../utils/mono_reg_utils.h" +#endif + +#define SCRIPTS_DOMAIN GDMono::get_singleton()->get_scripts_domain() +#ifdef TOOLS_ENABLED +#define TOOLS_DOMAIN GDMono::get_singleton()->get_tools_domain() +#endif + +class GDMono { + + bool runtime_initialized; + bool finalizing_scripts_domain; + + MonoDomain *root_domain; + MonoDomain *scripts_domain; +#ifdef TOOLS_ENABLED + MonoDomain *tools_domain; +#endif + + GDMonoAssembly *corlib_assembly; + GDMonoAssembly *api_assembly; + GDMonoAssembly *project_assembly; +#ifdef TOOLS_ENABLED + GDMonoAssembly *editor_api_assembly; + GDMonoAssembly *editor_tools_assembly; +#endif + + HashMap<uint32_t, HashMap<String, GDMonoAssembly *> > assemblies; + + void _domain_assemblies_cleanup(uint32_t p_domain_id); + + bool _load_corlib_assembly(); + bool _load_core_api_assembly(); +#ifdef TOOLS_ENABLED + bool _load_editor_api_assembly(); + bool _load_editor_tools_assembly(); +#endif + bool _load_project_assembly(); + + bool _load_all_script_assemblies(); + + void _register_internal_calls(); + + Error _load_scripts_domain(); + Error _unload_scripts_domain(); + +#ifdef TOOLS_ENABLED + Error _load_tools_domain(); +#endif + +#ifdef DEBUG_METHODS_ENABLED + uint64_t api_core_hash; +#ifdef TOOLS_ENABLED + uint64_t api_editor_hash; +#endif + void _initialize_and_check_api_hashes(); +#endif + + bool _load_assembly(const String &p_name, GDMonoAssembly **r_assembly); + + GDMonoLog *gdmono_log; + +#ifdef WINDOWS_ENABLED + MonoRegInfo mono_reg_info; +#endif + +protected: + static GDMono *singleton; + +public: +#ifdef DEBUG_METHODS_ENABLED + uint64_t get_api_core_hash() { return api_core_hash; } +#ifdef TOOLS_ENABLED + uint64_t get_api_editor_hash() { return api_editor_hash; } +#endif +#endif + + enum MemberVisibility { + PRIVATE, + PROTECTED_AND_INTERNAL, // FAM_AND_ASSEM + INTERNAL, // ASSEMBLY + PROTECTED, // FAMILY + PUBLIC + }; + + static GDMono *get_singleton() { return singleton; } + + void add_assembly(uint32_t p_domain_id, GDMonoAssembly *p_assembly); + + _FORCE_INLINE_ bool is_runtime_initialized() const { return runtime_initialized; } + _FORCE_INLINE_ bool is_finalizing_scripts_domain() const { return finalizing_scripts_domain; } + + _FORCE_INLINE_ MonoDomain *get_scripts_domain() { return scripts_domain; } +#ifdef TOOLS_ENABLED + _FORCE_INLINE_ MonoDomain *get_tools_domain() { return tools_domain; } +#endif + + _FORCE_INLINE_ GDMonoAssembly *get_corlib_assembly() const { return corlib_assembly; } + _FORCE_INLINE_ GDMonoAssembly *get_api_assembly() const { return api_assembly; } + _FORCE_INLINE_ GDMonoAssembly *get_project_assembly() const { return project_assembly; } +#ifdef TOOLS_ENABLED + _FORCE_INLINE_ GDMonoAssembly *get_editor_api_assembly() const { return editor_api_assembly; } + _FORCE_INLINE_ GDMonoAssembly *get_editor_tools_assembly() const { return editor_tools_assembly; } +#endif + +#ifdef WINDOWS_ENABLED + const MonoRegInfo &get_mono_reg_info() { return mono_reg_info; } +#endif + + GDMonoClass *get_class(MonoClass *p_raw_class); + +#ifdef TOOLS_ENABLED + Error reload_scripts_domain(); +#endif + + void initialize(); + + GDMono(); + ~GDMono(); +}; + +class GDMonoScopeDomain { + + MonoDomain *prev_domain; + +public: + GDMonoScopeDomain(MonoDomain *p_domain) { + MonoDomain *prev_domain = mono_domain_get(); + if (prev_domain != p_domain) { + this->prev_domain = prev_domain; + mono_domain_set(p_domain, false); + } else { + this->prev_domain = NULL; + } + } + + ~GDMonoScopeDomain() { + if (prev_domain) + mono_domain_set(prev_domain, false); + } +}; + +#define _GDMONO_SCOPE_DOMAIN_(m_mono_domain) \ + GDMonoScopeDomain __gdmono__scope__domain__(m_mono_domain); \ + (void)__gdmono__scope__domain__; + +class _GodotSharp : public Object { + GDCLASS(_GodotSharp, Object) + + friend class GDMono; + + void _dispose_object(Object *p_object); + + void _dispose_callback(); + + List<Object *> obj_delete_queue; + List<NodePath *> np_delete_queue; + List<RID *> rid_delete_queue; + + bool queue_empty; + +#ifndef NO_THREADS + Mutex *queue_mutex; +#endif + +protected: + static _GodotSharp *singleton; + static void _bind_methods(); + +public: + static _GodotSharp *get_singleton() { return singleton; } + + void attach_thread(); + void detach_thread(); + + bool is_finalizing_domain(); + bool is_domain_loaded(); + + void queue_dispose(Object *p_object); + void queue_dispose(NodePath *p_node_path); + void queue_dispose(RID *p_rid); + + _GodotSharp(); + ~_GodotSharp(); +}; + +#endif // GD_MONO_H diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp new file mode 100644 index 0000000000..a98537b9e1 --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -0,0 +1,327 @@ +/*************************************************************************/ +/* gd_mono_assembly.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "gd_mono_assembly.h" + +#include <mono/metadata/mono-debug.h> +#include <mono/metadata/tokentype.h> + +#include "list.h" +#include "os/file_access.h" +#include "os/os.h" + +#include "../godotsharp_dirs.h" +#include "gd_mono_class.h" + +MonoAssembly *gdmono_load_assembly_from(const String &p_name, const String &p_path) { + + MonoDomain *domain = mono_domain_get(); + + GDMonoAssembly *assembly = memnew(GDMonoAssembly(p_name, p_path)); + Error err = assembly->load(domain); + ERR_FAIL_COND_V(err != OK, NULL); + + GDMono::get_singleton()->add_assembly(mono_domain_get_id(domain), assembly); + + return assembly->get_assembly(); +} + +MonoAssembly *gdmono_MonoAssemblyPreLoad(MonoAssemblyName *aname, char **assemblies_path, void *user_data) { + + (void)user_data; // UNUSED + + MonoAssembly *assembly_loaded = mono_assembly_loaded(aname); + if (assembly_loaded) // Already loaded + return assembly_loaded; + + static Vector<String> search_dirs; + + if (search_dirs.empty()) { + search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_dir()); + search_dirs.push_back(GodotSharpDirs::get_res_assemblies_dir()); + search_dirs.push_back(OS::get_singleton()->get_resource_dir()); + search_dirs.push_back(OS::get_singleton()->get_executable_path().get_base_dir()); + + const char *rootdir = mono_assembly_getrootdir(); + if (rootdir) { + search_dirs.push_back(String(rootdir).plus_file("mono").plus_file("4.5")); + } + + while (assemblies_path) { + if (*assemblies_path) + search_dirs.push_back(*assemblies_path); + ++assemblies_path; + } + } + + String name = mono_assembly_name_get_name(aname); + bool has_extension = name.ends_with(".dll") || name.ends_with(".exe"); + + String path; + + for (int i = 0; i < search_dirs.size(); i++) { + const String &search_dir = search_dirs[i]; + + if (has_extension) { + path = search_dir.plus_file(name); + if (FileAccess::exists(path)) + return gdmono_load_assembly_from(name.get_basename(), path); + } else { + path = search_dir.plus_file(name + ".dll"); + if (FileAccess::exists(path)) + return gdmono_load_assembly_from(name, path); + + path = search_dir.plus_file(name + ".exe"); + if (FileAccess::exists(path)) + return gdmono_load_assembly_from(name, path); + } + } + + return NULL; +} + +void GDMonoAssembly::initialize() { + + mono_install_assembly_preload_hook(&gdmono_MonoAssemblyPreLoad, NULL); +} + +Error GDMonoAssembly::load(MonoDomain *p_domain) { + + ERR_FAIL_COND_V(loaded, ERR_FILE_ALREADY_IN_USE); + + uint64_t last_modified_time = FileAccess::get_modified_time(path); + + Vector<uint8_t> data = FileAccess::get_file_as_array(path); + ERR_FAIL_COND_V(data.empty(), ERR_FILE_CANT_READ); + + String image_filename(path); + + MonoImageOpenStatus status; + + image = mono_image_open_from_data_with_name( + (char *)&data[0], data.size(), + true, &status, false, + image_filename.utf8().get_data()); + + ERR_FAIL_COND_V(status != MONO_IMAGE_OK || image == NULL, ERR_FILE_CANT_OPEN); + +#ifdef DEBUG_ENABLED + String pdb_path(path + ".pdb"); + + if (!FileAccess::exists(pdb_path)) { + pdb_path = path.get_basename() + ".pdb"; // without .dll + + if (!FileAccess::exists(pdb_path)) + goto no_pdb; + } + + pdb_data.clear(); + pdb_data = FileAccess::get_file_as_array(pdb_path); + mono_debug_open_image_from_memory(image, &pdb_data[0], pdb_data.size()); + +no_pdb: + +#endif + + assembly = mono_assembly_load_from_full(image, image_filename.utf8().get_data(), &status, false); + + ERR_FAIL_COND_V(status != MONO_IMAGE_OK || assembly == NULL, ERR_FILE_CANT_OPEN); + + if (mono_image_get_entry_point(image)) { + // TODO should this be removed? do we want to call main? what other effects does this have? + mono_jit_exec(p_domain, assembly, 0, NULL); + } + + loaded = true; + modified_time = last_modified_time; + + return OK; +} + +Error GDMonoAssembly::wrapper_for_image(MonoImage *p_image) { + + ERR_FAIL_COND_V(loaded, ERR_FILE_ALREADY_IN_USE); + + assembly = mono_image_get_assembly(p_image); + ERR_FAIL_NULL_V(assembly, FAILED); + + image = p_image; + + mono_image_addref(image); + + loaded = true; + + return OK; +} + +void GDMonoAssembly::unload() { + + ERR_FAIL_COND(!loaded); + +#ifdef DEBUG_ENABLED + if (pdb_data.size()) { + mono_debug_close_image(image); + pdb_data.clear(); + } +#endif + + for (Map<MonoClass *, GDMonoClass *>::Element *E = cached_raw.front(); E; E = E->next()) { + memdelete(E->value()); + } + + cached_classes.clear(); + cached_raw.clear(); + + mono_image_close(image); + + assembly = NULL; + image = NULL; + loaded = false; +} + +GDMonoClass *GDMonoAssembly::get_class(const StringName &p_namespace, const StringName &p_name) { + + ERR_FAIL_COND_V(!loaded, NULL); + + ClassKey key(p_namespace, p_name); + + GDMonoClass **match = cached_classes.getptr(key); + + if (match) + return *match; + + MonoClass *mono_class = mono_class_from_name(image, String(p_namespace).utf8(), String(p_name).utf8()); + + if (!mono_class) + return NULL; + + GDMonoClass *wrapped_class = memnew(GDMonoClass(p_namespace, p_name, mono_class, this)); + + cached_classes[key] = wrapped_class; + cached_raw[mono_class] = wrapped_class; + + return wrapped_class; +} + +GDMonoClass *GDMonoAssembly::get_class(MonoClass *p_mono_class) { + + ERR_FAIL_COND_V(!loaded, NULL); + + Map<MonoClass *, GDMonoClass *>::Element *match = cached_raw.find(p_mono_class); + + if (match) + return match->value(); + + StringName namespace_name = mono_class_get_namespace(p_mono_class); + StringName class_name = mono_class_get_name(p_mono_class); + + GDMonoClass *wrapped_class = memnew(GDMonoClass(namespace_name, class_name, p_mono_class, this)); + + cached_classes[ClassKey(namespace_name, class_name)] = wrapped_class; + cached_raw[p_mono_class] = wrapped_class; + + return wrapped_class; +} + +GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class) { + + GDMonoClass *match = NULL; + + if (gdobject_class_cache_updated) { + Map<StringName, GDMonoClass *>::Element *result = gdobject_class_cache.find(p_class); + + if (result) + match = result->get(); + } else { + List<GDMonoClass *> nested_classes; + + int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF); + + for (int i = 1; i < rows; i++) { + MonoClass *mono_class = mono_class_get(image, (i + 1) | MONO_TOKEN_TYPE_DEF); + + if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class)) + continue; + + GDMonoClass *current = get_class(mono_class); + + if (!current) + continue; + + nested_classes.push_back(current); + + if (!match && current->get_name() == p_class) + match = current; + + while (!nested_classes.empty()) { + GDMonoClass *current_nested = nested_classes.front()->get(); + nested_classes.pop_back(); + + void *iter = NULL; + + while (true) { + MonoClass *raw_nested = mono_class_get_nested_types(current_nested->get_raw(), &iter); + + if (!raw_nested) + break; + + GDMonoClass *nested_class = get_class(raw_nested); + + if (nested_class) { + gdobject_class_cache.insert(nested_class->get_name(), nested_class); + nested_classes.push_back(nested_class); + } + } + } + + gdobject_class_cache.insert(current->get_name(), current); + } + + gdobject_class_cache_updated = true; + } + + return match; +} + +GDMonoAssembly::GDMonoAssembly(const String &p_name, const String &p_path) { + + loaded = false; + gdobject_class_cache_updated = false; + name = p_name; + path = p_path; + modified_time = 0; + assembly = NULL; + image = NULL; +} + +GDMonoAssembly::~GDMonoAssembly() { + + if (loaded) + unload(); +} diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h new file mode 100644 index 0000000000..89e091549c --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_assembly.h @@ -0,0 +1,113 @@ +/*************************************************************************/ +/* gd_mono_assembly.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef GD_MONO_ASSEMBLY_H +#define GD_MONO_ASSEMBLY_H + +#include <mono/jit/jit.h> +#include <mono/metadata/assembly.h> + +#include "gd_mono_utils.h" +#include "hash_map.h" +#include "map.h" +#include "ustring.h" + +class GDMonoAssembly { + + struct ClassKey { + struct Hasher { + static _FORCE_INLINE_ uint32_t hash(const ClassKey &p_key) { + uint32_t hash = 0; + + GDMonoUtils::hash_combine(hash, p_key.namespace_name.hash()); + GDMonoUtils::hash_combine(hash, p_key.class_name.hash()); + + return hash; + } + }; + + _FORCE_INLINE_ bool operator==(const ClassKey &p_a) const { + return p_a.class_name == class_name && p_a.namespace_name == namespace_name; + } + + ClassKey() {} + + ClassKey(const StringName &p_namespace_name, const StringName &p_class_name) { + namespace_name = p_namespace_name; + class_name = p_class_name; + } + + StringName namespace_name; + StringName class_name; + }; + + MonoAssembly *assembly; + MonoImage *image; + + bool loaded; + + String name; + String path; + uint64_t modified_time; + + HashMap<ClassKey, GDMonoClass *, ClassKey::Hasher> cached_classes; + Map<MonoClass *, GDMonoClass *> cached_raw; + + bool gdobject_class_cache_updated; + Map<StringName, GDMonoClass *> gdobject_class_cache; + +#ifdef DEBUG_ENABLED + Vector<uint8_t> pdb_data; +#endif + + friend class GDMono; + static void initialize(); + +public: + Error load(MonoDomain *p_domain); + Error wrapper_for_image(MonoImage *p_image); + void unload(); + + _FORCE_INLINE_ bool is_loaded() const { return loaded; } + _FORCE_INLINE_ MonoImage *get_image() const { return image; } + _FORCE_INLINE_ MonoAssembly *get_assembly() const { return assembly; } + _FORCE_INLINE_ String get_name() const { return name; } + _FORCE_INLINE_ String get_path() const { return path; } + _FORCE_INLINE_ uint64_t get_modified_time() const { return modified_time; } + + GDMonoClass *get_class(const StringName &p_namespace, const StringName &p_class); + GDMonoClass *get_class(MonoClass *p_mono_class); + + GDMonoClass *get_object_derived_class(const StringName &p_class); + + GDMonoAssembly(const String &p_name, const String &p_path = String()); + ~GDMonoAssembly(); +}; + +#endif // GD_MONO_ASSEMBLY_H diff --git a/modules/mono/mono_gd/gd_mono_class.cpp b/modules/mono/mono_gd/gd_mono_class.cpp new file mode 100644 index 0000000000..0134ace5d7 --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_class.cpp @@ -0,0 +1,381 @@ +/*************************************************************************/ +/* gd_mono_class.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "gd_mono_class.h" + +#include <mono/metadata/attrdefs.h> + +#include "gd_mono_assembly.h" + +MonoType *GDMonoClass::get_raw_type(GDMonoClass *p_class) { + + return mono_class_get_type(p_class->get_raw()); +} + +bool GDMonoClass::is_assignable_from(GDMonoClass *p_from) const { + + return mono_class_is_assignable_from(mono_class, p_from->mono_class); +} + +GDMonoClass *GDMonoClass::get_parent_class() { + + if (assembly) { + MonoClass *parent_mono_class = mono_class_get_parent(mono_class); + + if (parent_mono_class) { + return GDMono::get_singleton()->get_class(parent_mono_class); + } + } + + return NULL; +} + +bool GDMonoClass::has_method(const StringName &p_name) { + + return get_method(p_name) != NULL; +} + +bool GDMonoClass::has_attribute(GDMonoClass *p_attr_class) { + +#ifdef DEBUG_ENABLED + ERR_FAIL_NULL_V(p_attr_class, false); +#endif + + if (!attrs_fetched) + fetch_attributes(); + + if (!attributes) + return false; + + return mono_custom_attrs_has_attr(attributes, p_attr_class->get_raw()); +} + +MonoObject *GDMonoClass::get_attribute(GDMonoClass *p_attr_class) { + +#ifdef DEBUG_ENABLED + ERR_FAIL_NULL_V(p_attr_class, NULL); +#endif + + if (!attrs_fetched) + fetch_attributes(); + + if (!attributes) + return NULL; + + return mono_custom_attrs_get_attr(attributes, p_attr_class->get_raw()); +} + +void GDMonoClass::fetch_attributes() { + + ERR_FAIL_COND(attributes != NULL); + + attributes = mono_custom_attrs_from_class(get_raw()); + attrs_fetched = true; +} + +void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base) { + + CRASH_COND(!CACHED_CLASS(GodotObject)->is_assignable_from(this)); + + if (methods_fetched) + return; + + void *iter = NULL; + MonoMethod *raw_method = NULL; + while ((raw_method = mono_class_get_methods(get_raw(), &iter)) != NULL) { + StringName name = mono_method_get_name(raw_method); + + GDMonoMethod *method = get_method(raw_method, name); + ERR_CONTINUE(!method); + + if (method->get_name() != name) { + +#ifdef DEBUG_ENABLED + String fullname = method->get_ret_type_full_name() + " " + name + "(" + method->get_signature_desc(true) + ")"; + WARN_PRINTS("Method `" + fullname + "` is hidden by Godot API method. Should be `" + + method->get_full_name_no_class() + "`. In class `" + namespace_name + "." + class_name + "`."); +#endif + continue; + } + +#ifdef DEBUG_ENABLED + // For debug builds, we also fetched from native base classes as well before if this is not a native base class. + // This allows us to warn the user here if he is using snake_case by mistake. + + if (p_native_base != this) { + + GDMonoClass *native_top = p_native_base; + while (native_top) { + GDMonoMethod *m = native_top->get_method(name, method->get_parameters_count()); + + if (m && m->get_name() != name) { + // found + String fullname = m->get_ret_type_full_name() + " " + name + "(" + m->get_signature_desc(true) + ")"; + WARN_PRINTS("Method `" + fullname + "` should be `" + m->get_full_name_no_class() + + "`. In class `" + namespace_name + "." + class_name + "`."); + break; + } + + if (native_top == CACHED_CLASS(GodotObject)) + break; + + native_top = native_top->get_parent_class(); + } + } +#endif + + uint32_t flags = mono_method_get_flags(method->mono_method, NULL); + + if (!(flags & MONO_METHOD_ATTR_VIRTUAL)) + continue; + + // Virtual method of Godot Object derived type, let's try to find GodotMethod attribute + + GDMonoClass *top = p_native_base; + + while (top) { + GDMonoMethod *base_method = top->get_method(name, method->get_parameters_count()); + + if (base_method && base_method->has_attribute(CACHED_CLASS(GodotMethodAttribute))) { + // Found base method with GodotMethod attribute. + // We get the original API method name from this attribute. + // This name must point to the virtual method. + + MonoObject *attr = base_method->get_attribute(CACHED_CLASS(GodotMethodAttribute)); + + StringName godot_method_name = CACHED_FIELD(GodotMethodAttribute, methodName)->get_string_value(attr); +#ifdef DEBUG_ENABLED + CRASH_COND(godot_method_name == StringName()); +#endif + MethodKey key = MethodKey(godot_method_name, method->get_parameters_count()); + GDMonoMethod **existing_method = methods.getptr(key); + if (existing_method) + memdelete(*existing_method); // Must delete old one + methods.set(key, method); + + break; + } + + if (top == CACHED_CLASS(GodotObject)) + break; + + top = top->get_parent_class(); + } + } + + methods_fetched = true; +} + +GDMonoMethod *GDMonoClass::get_method(const StringName &p_name) { + + ERR_FAIL_COND_V(!methods_fetched, NULL); + + const MethodKey *k = NULL; + + while ((k = methods.next(k))) { + if (k->name == p_name) + return methods.get(*k); + } + + return NULL; +} + +GDMonoMethod *GDMonoClass::get_method(const StringName &p_name, int p_params_count) { + + MethodKey key = MethodKey(p_name, p_params_count); + + GDMonoMethod **match = methods.getptr(key); + + if (match) + return *match; + + if (methods_fetched) + return NULL; + + MonoMethod *raw_method = mono_class_get_method_from_name(mono_class, String(p_name).utf8().get_data(), p_params_count); + + if (raw_method) { + GDMonoMethod *method = memnew(GDMonoMethod(p_name, raw_method)); + methods.set(key, method); + + return method; + } + + return NULL; +} + +GDMonoMethod *GDMonoClass::get_method(MonoMethod *p_raw_method) { + + MonoMethodSignature *sig = mono_method_signature(p_raw_method); + + int params_count = mono_signature_get_param_count(sig); + StringName method_name = mono_method_get_name(p_raw_method); + + return get_method(p_raw_method, method_name, params_count); +} + +GDMonoMethod *GDMonoClass::get_method(MonoMethod *p_raw_method, const StringName &p_name) { + + MonoMethodSignature *sig = mono_method_signature(p_raw_method); + int params_count = mono_signature_get_param_count(sig); + return get_method(p_raw_method, p_name, params_count); +} + +GDMonoMethod *GDMonoClass::get_method(MonoMethod *p_raw_method, const StringName &p_name, int p_params_count) { + + ERR_FAIL_NULL_V(p_raw_method, NULL); + + MethodKey key = MethodKey(p_name, p_params_count); + + GDMonoMethod **match = methods.getptr(key); + + if (match) + return *match; + + GDMonoMethod *method = memnew(GDMonoMethod(p_name, p_raw_method)); + methods.set(key, method); + + return method; +} + +GDMonoMethod *GDMonoClass::get_method_with_desc(const String &p_description, bool p_include_namespace) { + + MonoMethodDesc *desc = mono_method_desc_new(p_description.utf8().get_data(), p_include_namespace); + MonoMethod *method = mono_method_desc_search_in_class(desc, mono_class); + mono_method_desc_free(desc); + + return get_method(method); +} + +GDMonoField *GDMonoClass::get_field(const StringName &p_name) { + + Map<StringName, GDMonoField *>::Element *result = fields.find(p_name); + + if (result) + return result->value(); + + if (fields_fetched) + return NULL; + + MonoClassField *raw_field = mono_class_get_field_from_name(mono_class, String(p_name).utf8().get_data()); + + if (raw_field) { + GDMonoField *field = memnew(GDMonoField(raw_field, this)); + fields.insert(p_name, field); + + return field; + } + + return NULL; +} + +const Vector<GDMonoField *> &GDMonoClass::get_all_fields() { + + if (fields_fetched) + return fields_list; + + void *iter = NULL; + MonoClassField *raw_field = NULL; + while ((raw_field = mono_class_get_fields(get_raw(), &iter)) != NULL) { + StringName name = mono_field_get_name(raw_field); + + Map<StringName, GDMonoField *>::Element *match = fields.find(name); + + if (match) { + fields_list.push_back(match->get()); + } else { + GDMonoField *field = memnew(GDMonoField(raw_field, this)); + fields.insert(name, field); + fields_list.push_back(field); + } + } + + fields_fetched = true; + + return fields_list; +} + +GDMonoClass::GDMonoClass(const StringName &p_namespace, const StringName &p_name, MonoClass *p_class, GDMonoAssembly *p_assembly) { + + namespace_name = p_namespace; + class_name = p_name; + mono_class = p_class; + assembly = p_assembly; + + attrs_fetched = false; + attributes = NULL; + + methods_fetched = false; + fields_fetched = false; +} + +GDMonoClass::~GDMonoClass() { + + if (attributes) { + mono_custom_attrs_free(attributes); + } + + for (Map<StringName, GDMonoField *>::Element *E = fields.front(); E; E = E->next()) { + memdelete(E->value()); + } + + { + // Ugly workaround... + // We may have duplicated values, because we redirect snake_case methods to PascalCasel (only Godot API methods). + // This way, we end with both the snake_case name and the PascalCasel name paired with the same method. + // Therefore, we must avoid deleting the same pointer twice. + + int offset = 0; + Vector<GDMonoMethod *> deleted_methods; + deleted_methods.resize(methods.size()); + + const MethodKey *k = NULL; + while ((k = methods.next(k))) { + GDMonoMethod *method = methods.get(*k); + + if (method) { + for (int i = 0; i < offset; i++) { + if (deleted_methods[i] == method) { + // Already deleted + goto already_deleted; + } + } + + deleted_methods[offset] = method; + ++offset; + + memdelete(method); + } + + already_deleted:; + } + + methods.clear(); + } +} diff --git a/modules/mono/mono_gd/gd_mono_class.h b/modules/mono/mono_gd/gd_mono_class.h new file mode 100644 index 0000000000..1e72553879 --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_class.h @@ -0,0 +1,124 @@ +/*************************************************************************/ +/* gd_mono_class.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef GD_MONO_CLASS_H +#define GD_MONO_CLASS_H + +#include <mono/metadata/debug-helpers.h> + +#include "map.h" +#include "ustring.h" + +#include "gd_mono_field.h" +#include "gd_mono_header.h" +#include "gd_mono_method.h" +#include "gd_mono_utils.h" + +class GDMonoClass { + struct MethodKey { + struct Hasher { + static _FORCE_INLINE_ uint32_t hash(const MethodKey &p_key) { + uint32_t hash = 0; + + GDMonoUtils::hash_combine(hash, p_key.name.hash()); + GDMonoUtils::hash_combine(hash, HashMapHasherDefault::hash(p_key.params_count)); + + return hash; + } + }; + + _FORCE_INLINE_ bool operator==(const MethodKey &p_a) const { + return p_a.params_count == params_count && p_a.name == name; + } + + MethodKey() {} + + MethodKey(const StringName &p_name, int p_params_count) { + name = p_name; + params_count = p_params_count; + } + + StringName name; + int params_count; + }; + + StringName namespace_name; + StringName class_name; + + MonoClass *mono_class; + GDMonoAssembly *assembly; + + bool attrs_fetched; + MonoCustomAttrInfo *attributes; + + bool methods_fetched; + HashMap<MethodKey, GDMonoMethod *, MethodKey::Hasher> methods; + + bool fields_fetched; + Map<StringName, GDMonoField *> fields; + Vector<GDMonoField *> fields_list; + + friend class GDMonoAssembly; + GDMonoClass(const StringName &p_namespace, const StringName &p_name, MonoClass *p_class, GDMonoAssembly *p_assembly); + +public: + static MonoType *get_raw_type(GDMonoClass *p_class); + + bool is_assignable_from(GDMonoClass *p_from) const; + + _FORCE_INLINE_ StringName get_namespace() const { return namespace_name; } + _FORCE_INLINE_ StringName get_name() const { return class_name; } + + _FORCE_INLINE_ MonoClass *get_raw() const { return mono_class; } + _FORCE_INLINE_ const GDMonoAssembly *get_assembly() const { return assembly; } + + GDMonoClass *get_parent_class(); + + bool has_method(const StringName &p_name); + + bool has_attribute(GDMonoClass *p_attr_class); + MonoObject *get_attribute(GDMonoClass *p_attr_class); + + void fetch_attributes(); + void fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base); + + GDMonoMethod *get_method(const StringName &p_name); + GDMonoMethod *get_method(const StringName &p_name, int p_params_count); + GDMonoMethod *get_method(MonoMethod *p_raw_method); + GDMonoMethod *get_method(MonoMethod *p_raw_method, const StringName &p_name); + GDMonoMethod *get_method(MonoMethod *p_raw_method, const StringName &p_name, int p_params_count); + GDMonoMethod *get_method_with_desc(const String &p_description, bool p_includes_namespace); + + GDMonoField *get_field(const StringName &p_name); + const Vector<GDMonoField *> &get_all_fields(); + + ~GDMonoClass(); +}; + +#endif // GD_MONO_CLASS_H diff --git a/modules/mono/mono_gd/gd_mono_field.cpp b/modules/mono/mono_gd/gd_mono_field.cpp new file mode 100644 index 0000000000..0c64380e31 --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_field.cpp @@ -0,0 +1,362 @@ +/*************************************************************************/ +/* gd_mono_field.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "gd_mono_field.h" + +#include <mono/metadata/attrdefs.h> + +#include "gd_mono_class.h" +#include "gd_mono_marshal.h" + +void GDMonoField::set_value_raw(MonoObject *p_object, void *p_ptr) { + mono_field_set_value(p_object, mono_field, &p_ptr); +} + +void GDMonoField::set_value(MonoObject *p_object, const Variant &p_value) { +#define SET_FROM_STRUCT_AND_BREAK(m_type) \ + { \ + const m_type &val = p_value.operator m_type(); \ + MARSHALLED_OUT(m_type, val, raw); \ + mono_field_set_value(p_object, mono_field, raw); \ + break; \ + } + +#define SET_FROM_PRIMITIVE(m_type) \ + { \ + m_type val = p_value.operator m_type(); \ + mono_field_set_value(p_object, mono_field, &val); \ + } + +#define SET_FROM_ARRAY_AND_BREAK(m_type) \ + { \ + MonoArray *managed = GDMonoMarshal::m_type##_to_mono_array(p_value.operator m_type()); \ + mono_field_set_value(p_object, mono_field, &managed); \ + break; \ + } + + switch (type.type_encoding) { + case MONO_TYPE_BOOLEAN: { + SET_FROM_PRIMITIVE(bool); + } break; + + case MONO_TYPE_I1: { + SET_FROM_PRIMITIVE(signed char); + } break; + case MONO_TYPE_I2: { + SET_FROM_PRIMITIVE(signed short); + } break; + case MONO_TYPE_I4: { + SET_FROM_PRIMITIVE(signed int); + } break; + case MONO_TYPE_I8: { + SET_FROM_PRIMITIVE(int64_t); + } break; + + case MONO_TYPE_U1: { + SET_FROM_PRIMITIVE(unsigned char); + } break; + case MONO_TYPE_U2: { + SET_FROM_PRIMITIVE(unsigned short); + } break; + case MONO_TYPE_U4: { + SET_FROM_PRIMITIVE(unsigned int); + } break; + case MONO_TYPE_U8: { + SET_FROM_PRIMITIVE(uint64_t); + } break; + + case MONO_TYPE_R4: { + SET_FROM_PRIMITIVE(float); + } break; + + case MONO_TYPE_R8: { + SET_FROM_PRIMITIVE(double); + } break; + + case MONO_TYPE_STRING: { + MonoString *mono_string = GDMonoMarshal::mono_string_from_godot(p_value); + mono_field_set_value(p_object, mono_field, mono_string); + } break; + + case MONO_TYPE_VALUETYPE: { + GDMonoClass *tclass = type.type_class; + + if (tclass == CACHED_CLASS(Vector2)) + SET_FROM_STRUCT_AND_BREAK(Vector2); + + if (tclass == CACHED_CLASS(Rect2)) + SET_FROM_STRUCT_AND_BREAK(Rect2); + + if (tclass == CACHED_CLASS(Transform2D)) + SET_FROM_STRUCT_AND_BREAK(Transform2D); + + if (tclass == CACHED_CLASS(Vector3)) + SET_FROM_STRUCT_AND_BREAK(Vector3); + + if (tclass == CACHED_CLASS(Basis)) + SET_FROM_STRUCT_AND_BREAK(Basis); + + if (tclass == CACHED_CLASS(Quat)) + SET_FROM_STRUCT_AND_BREAK(Quat); + + if (tclass == CACHED_CLASS(Transform)) + SET_FROM_STRUCT_AND_BREAK(Transform); + + if (tclass == CACHED_CLASS(Rect3)) + SET_FROM_STRUCT_AND_BREAK(Rect3); + + if (tclass == CACHED_CLASS(Color)) + SET_FROM_STRUCT_AND_BREAK(Color); + + if (tclass == CACHED_CLASS(Plane)) + SET_FROM_STRUCT_AND_BREAK(Plane); + + ERR_EXPLAIN(String() + "Attempted to set the value of a field of unmarshallable type: " + tclass->get_name()); + ERR_FAIL(); + } break; + + case MONO_TYPE_ARRAY: + case MONO_TYPE_SZARRAY: { + MonoArrayType *array_type = mono_type_get_array_type(GDMonoClass::get_raw_type(type.type_class)); + + if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) + SET_FROM_ARRAY_AND_BREAK(Array); + + if (array_type->eklass == CACHED_CLASS_RAW(uint8_t)) + SET_FROM_ARRAY_AND_BREAK(PoolByteArray); + + if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) + SET_FROM_ARRAY_AND_BREAK(PoolIntArray); + + if (array_type->eklass == REAL_T_MONOCLASS) + SET_FROM_ARRAY_AND_BREAK(PoolRealArray); + + if (array_type->eklass == CACHED_CLASS_RAW(String)) + SET_FROM_ARRAY_AND_BREAK(PoolStringArray); + + if (array_type->eklass == CACHED_CLASS_RAW(Vector2)) + SET_FROM_ARRAY_AND_BREAK(PoolVector2Array); + + if (array_type->eklass == CACHED_CLASS_RAW(Vector3)) + SET_FROM_ARRAY_AND_BREAK(PoolVector3Array); + + if (array_type->eklass == CACHED_CLASS_RAW(Color)) + SET_FROM_ARRAY_AND_BREAK(PoolColorArray); + + ERR_EXPLAIN(String() + "Attempted to convert Variant to a managed array of unmarshallable element type."); + ERR_FAIL(); + } break; + + case MONO_TYPE_CLASS: { + GDMonoClass *type_class = type.type_class; + + // GodotObject + if (CACHED_CLASS(GodotObject)->is_assignable_from(type_class)) { + MonoObject *managed = GDMonoUtils::unmanaged_get_managed(p_value.operator Object *()); + mono_field_set_value(p_object, mono_field, &managed); + break; + } + + if (CACHED_CLASS(NodePath) == type_class) { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator NodePath()); + mono_field_set_value(p_object, mono_field, &managed); + break; + } + + if (CACHED_CLASS(RID) == type_class) { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator RID()); + mono_field_set_value(p_object, mono_field, &managed); + break; + } + + ERR_EXPLAIN(String() + "Attempted to set the value of a field of unmarshallable type: " + type_class->get_name()); + ERR_FAIL(); + } break; + + case MONO_TYPE_OBJECT: { + GDMonoClass *type_class = type.type_class; + + // Variant + switch (p_value.get_type()) { + case Variant::BOOL: { + SET_FROM_PRIMITIVE(bool); + } break; + case Variant::INT: { + SET_FROM_PRIMITIVE(int); + } break; + case Variant::REAL: { +#ifdef REAL_T_IS_DOUBLE + SET_FROM_PRIMITIVE(double); +#else + SET_FROM_PRIMITIVE(float); +#endif + } break; + case Variant::STRING: { + MonoString *mono_string = GDMonoMarshal::mono_string_from_godot(p_value); + mono_field_set_value(p_object, mono_field, mono_string); + } break; + case Variant::VECTOR2: SET_FROM_STRUCT_AND_BREAK(Vector2); + case Variant::RECT2: SET_FROM_STRUCT_AND_BREAK(Rect2); + case Variant::VECTOR3: SET_FROM_STRUCT_AND_BREAK(Vector3); + case Variant::TRANSFORM2D: SET_FROM_STRUCT_AND_BREAK(Transform2D); + case Variant::PLANE: SET_FROM_STRUCT_AND_BREAK(Plane); + case Variant::QUAT: SET_FROM_STRUCT_AND_BREAK(Quat); + case Variant::RECT3: SET_FROM_STRUCT_AND_BREAK(Rect3); + case Variant::BASIS: SET_FROM_STRUCT_AND_BREAK(Basis); + case Variant::TRANSFORM: SET_FROM_STRUCT_AND_BREAK(Transform); + case Variant::COLOR: SET_FROM_STRUCT_AND_BREAK(Color); + case Variant::NODE_PATH: { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator NodePath()); + mono_field_set_value(p_object, mono_field, &managed); + } break; + case Variant::_RID: { + MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator RID()); + mono_field_set_value(p_object, mono_field, &managed); + } break; + case Variant::OBJECT: { + MonoObject *managed = GDMonoUtils::unmanaged_get_managed(p_value.operator Object *()); + mono_field_set_value(p_object, mono_field, managed); + break; + } + case Variant::DICTIONARY: { + MonoObject *managed = GDMonoMarshal::Dictionary_to_mono_object(p_value.operator Dictionary()); + mono_field_set_value(p_object, mono_field, &managed); + } break; + case Variant::ARRAY: SET_FROM_ARRAY_AND_BREAK(Array); + case Variant::POOL_BYTE_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolByteArray); + case Variant::POOL_INT_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolIntArray); + case Variant::POOL_REAL_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolRealArray); + case Variant::POOL_STRING_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolStringArray); + case Variant::POOL_VECTOR2_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolVector2Array); + case Variant::POOL_VECTOR3_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolVector3Array); + case Variant::POOL_COLOR_ARRAY: SET_FROM_ARRAY_AND_BREAK(PoolColorArray); +#undef SET_FROM_ARRAY_AND_BREAK + default: break; + } + } break; + + case MONO_TYPE_GENERICINST: { + if (CACHED_RAW_MONO_CLASS(Dictionary) == type.type_class->get_raw()) { + MonoObject *managed = GDMonoMarshal::Dictionary_to_mono_object(p_value.operator Dictionary()); + mono_field_set_value(p_object, mono_field, &managed); + break; + } + } break; + + default: { + ERR_PRINTS(String() + "Attempted to set the value of a field of unexpected type encoding: " + itos(type.type_encoding)); + } break; + } + +#undef SET_FROM_STRUCT_AND_BREAK +#undef SET_FROM_PRIMITIVE +} + +bool GDMonoField::get_bool_value(MonoObject *p_object) { + return UNBOX_BOOLEAN(get_value(p_object)); +} + +int GDMonoField::get_int_value(MonoObject *p_object) { + return UNBOX_INT32(get_value(p_object)); +} + +String GDMonoField::get_string_value(MonoObject *p_object) { + MonoObject *val = get_value(p_object); + return val ? GDMonoMarshal::mono_string_to_godot((MonoString *)val) : String(); +} + +bool GDMonoField::has_attribute(GDMonoClass *p_attr_class) { + ERR_FAIL_NULL_V(p_attr_class, false); + + if (!attrs_fetched) + fetch_attributes(); + + if (!attributes) + return false; + + return mono_custom_attrs_has_attr(attributes, p_attr_class->get_raw()); +} + +MonoObject *GDMonoField::get_attribute(GDMonoClass *p_attr_class) { + ERR_FAIL_NULL_V(p_attr_class, NULL); + + if (!attrs_fetched) + fetch_attributes(); + + if (!attributes) + return NULL; + + return mono_custom_attrs_get_attr(attributes, p_attr_class->get_raw()); +} + +void GDMonoField::fetch_attributes() { + ERR_FAIL_COND(attributes != NULL); + attributes = mono_custom_attrs_from_field(owner->get_raw(), get_raw()); + attrs_fetched = true; +} + +bool GDMonoField::is_static() { + return mono_field_get_flags(mono_field) & MONO_FIELD_ATTR_STATIC; +} + +GDMono::MemberVisibility GDMonoField::get_visibility() { + switch (mono_field_get_flags(mono_field) & MONO_FIELD_ATTR_FIELD_ACCESS_MASK) { + case MONO_FIELD_ATTR_PRIVATE: + return GDMono::PRIVATE; + case MONO_FIELD_ATTR_FAM_AND_ASSEM: + return GDMono::PROTECTED_AND_INTERNAL; + case MONO_FIELD_ATTR_ASSEMBLY: + return GDMono::INTERNAL; + case MONO_FIELD_ATTR_FAMILY: + return GDMono::PROTECTED; + case MONO_FIELD_ATTR_PUBLIC: + return GDMono::PUBLIC; + default: + ERR_FAIL_V(GDMono::PRIVATE); + } +} + +GDMonoField::GDMonoField(MonoClassField *p_raw_field, GDMonoClass *p_owner) { + owner = p_owner; + mono_field = p_raw_field; + name = mono_field_get_name(mono_field); + MonoType *field_type = mono_field_get_type(mono_field); + type.type_encoding = mono_type_get_type(field_type); + MonoClass *field_type_class = mono_class_from_mono_type(field_type); + type.type_class = GDMono::get_singleton()->get_class(field_type_class); + + attrs_fetched = false; + attributes = NULL; +} + +GDMonoField::~GDMonoField() { + if (attributes) { + mono_custom_attrs_free(attributes); + } +} diff --git a/modules/mono/mono_gd/gd_mono_field.h b/modules/mono/mono_gd/gd_mono_field.h new file mode 100644 index 0000000000..b7e1942d71 --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_field.h @@ -0,0 +1,74 @@ +/*************************************************************************/ +/* gd_mono_field.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef GDMONOFIELD_H +#define GDMONOFIELD_H + +#include "gd_mono.h" +#include "gd_mono_header.h" + +class GDMonoField { + GDMonoClass *owner; + MonoClassField *mono_field; + + String name; + ManagedType type; + + bool attrs_fetched; + MonoCustomAttrInfo *attributes; + +public: + _FORCE_INLINE_ String get_name() const { return name; } + _FORCE_INLINE_ ManagedType get_type() const { return type; } + + _FORCE_INLINE_ MonoClassField *get_raw() const { return mono_field; } + + void set_value_raw(MonoObject *p_object, void *p_ptr); + void set_value(MonoObject *p_object, const Variant &p_value); + + _FORCE_INLINE_ MonoObject *get_value(MonoObject *p_object) { + return mono_field_get_value_object(mono_domain_get(), mono_field, p_object); + } + + bool get_bool_value(MonoObject *p_object); + int get_int_value(MonoObject *p_object); + String get_string_value(MonoObject *p_object); + + bool has_attribute(GDMonoClass *p_attr_class); + MonoObject *get_attribute(GDMonoClass *p_attr_class); + void fetch_attributes(); + + bool is_static(); + GDMono::MemberVisibility get_visibility(); + + GDMonoField(MonoClassField *p_raw_field, GDMonoClass *p_owner); + ~GDMonoField(); +}; + +#endif // GDMONOFIELD_H diff --git a/modules/mono/mono_gd/gd_mono_header.h b/modules/mono/mono_gd/gd_mono_header.h new file mode 100644 index 0000000000..803d394f96 --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_header.h @@ -0,0 +1,59 @@ +/*************************************************************************/ +/* gd_mono_header.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef GD_MONO_HEADER_H +#define GD_MONO_HEADER_H + +#include "int_types.h" + +class GDMonoAssembly; +class GDMonoClass; +class GDMonoMethod; +class GDMonoField; + +struct ManagedType { + int type_encoding; + GDMonoClass *type_class; + + ManagedType() { + type_class = 0; + } +}; + +typedef union { + uint32_t _uint32; + float _float; +} mono_float; + +typedef union { + uint64_t _uint64; + float _double; +} mono_double; + +#endif // GD_MONO_HEADER_H diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp new file mode 100644 index 0000000000..cfe2148b80 --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_internals.cpp @@ -0,0 +1,66 @@ +/*************************************************************************/ +/* godotsharp_internals.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "gd_mono_internals.h" + +#include "../csharp_script.h" +#include "../mono_gc_handle.h" +#include "gd_mono_utils.h" + +namespace GDMonoInternals { + +void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { + + // This method should not fail + + CRASH_COND(!unmanaged); + + // All mono objects created from the managed world (e.g.: `new Player()`) + // need to have a CSharpScript in order for their methods to be callable from the unmanaged side + + Reference *ref = Object::cast_to<Reference>(unmanaged); + + GDMonoClass *klass = GDMonoUtils::get_object_class(managed); + + CRASH_COND(!klass); + + Ref<MonoGCHandle> gchandle = ref ? MonoGCHandle::create_weak(managed) : + MonoGCHandle::create_strong(managed); + + Ref<CSharpScript> script = CSharpScript::create_for_managed_type(klass); + + CRASH_COND(script.is_null()); + + ScriptInstance *si = CSharpInstance::create_for_managed_type(unmanaged, script.ptr(), gchandle); + + unmanaged->set_script_and_instance(script.get_ref_ptr(), si); + + return; +} +} diff --git a/modules/mono/mono_gd/gd_mono_internals.h b/modules/mono/mono_gd/gd_mono_internals.h new file mode 100644 index 0000000000..6bdf4a6c46 --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_internals.h @@ -0,0 +1,42 @@ +/*************************************************************************/ +/* godotsharp_internals.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef GD_MONO_INTERNALS_H +#define GD_MONO_INTERNALS_H + +#include <mono/jit/jit.h> + +#include "core/object.h" + +namespace GDMonoInternals { + +void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged); +} + +#endif // GD_MONO_INTERNALS_H diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp new file mode 100644 index 0000000000..e473348897 --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_log.cpp @@ -0,0 +1,175 @@ +/*************************************************************************/ +/* gd_mono_log.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "gd_mono_log.h" + +#include <mono/utils/mono-logger.h> +#include <stdlib.h> // abort + +#include "os/dir_access.h" +#include "os/os.h" + +#include "../godotsharp_dirs.h" + +static int log_level_get_id(const char *p_log_level) { + + const char *valid_log_levels[] = { "error", "critical", "warning", "message", "info", "debug", NULL }; + + int i = 0; + while (valid_log_levels[i]) { + if (!strcmp(valid_log_levels[i], p_log_level)) + return i; + i++; + } + + return -1; +} + +void gdmono_MonoLogCallback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data) { + + FileAccess *f = GDMonoLog::get_singleton()->get_log_file(); + + if (GDMonoLog::get_singleton()->get_log_level_id() >= log_level_get_id(log_level)) { + String text(message); + text += " (in domain "; + text += log_domain; + if (log_level) { + text += ", "; + text += log_level; + } + text += ")\n"; + + f->seek_end(); + f->store_string(text); + } + + if (fatal) { + ERR_PRINTS("Mono: FALTAL ERROR, ABORTING! Logfile: " + GDMonoLog::get_singleton()->get_log_file_path() + "\n"); + abort(); + } +} + +GDMonoLog *GDMonoLog::singleton = NULL; + +bool GDMonoLog::_try_create_logs_dir(const String &p_logs_dir) { + + if (!DirAccess::exists(p_logs_dir)) { + DirAccessRef diraccess = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + ERR_FAIL_COND_V(!diraccess, false); + Error logs_mkdir_err = diraccess->make_dir_recursive(p_logs_dir); + ERR_EXPLAIN("Failed to create mono logs directory"); + ERR_FAIL_COND_V(logs_mkdir_err != OK, false); + } + + return true; +} + +void GDMonoLog::_open_log_file(const String &p_file_path) { + + log_file = FileAccess::open(p_file_path, FileAccess::WRITE); + + ERR_EXPLAIN("Failed to create log file"); + ERR_FAIL_COND(!log_file); +} + +void GDMonoLog::_delete_old_log_files(const String &p_logs_dir) { + + static const uint64_t MAX_SECS = 5 * 86400; + + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + ERR_FAIL_COND(!da); + + Error err = da->change_dir(p_logs_dir); + ERR_FAIL_COND(err != OK); + + ERR_FAIL_COND(da->list_dir_begin() != OK); + + String current; + while ((current = da->get_next()).length()) { + if (da->current_is_dir()) + continue; + if (!current.ends_with(".txt")) + continue; + + String name = current.get_basename(); + uint64_t unixtime = (uint64_t)name.to_int64(); + + if (OS::get_singleton()->get_unix_time() - unixtime > MAX_SECS) { + da->remove(current); + } + } + + da->list_dir_end(); +} + +void GDMonoLog::initialize() { + +#ifdef DEBUG_ENABLED + const char *log_level = "debug"; +#else + const char *log_level = "warning"; +#endif + + String logs_dir = GodotSharpDirs::get_mono_logs_dir(); + + if (_try_create_logs_dir(logs_dir)) { + _delete_old_log_files(logs_dir); + + log_file_path = logs_dir.plus_file(String::num_int64(OS::get_singleton()->get_unix_time()) + ".txt"); + _open_log_file(log_file_path); + } + + mono_trace_set_level_string(log_level); + log_level_id = log_level_get_id(log_level); + + if (log_file) { + if (OS::get_singleton()->is_stdout_verbose()) + OS::get_singleton()->print(String("Mono: Logfile is " + log_file_path + "\n").utf8()); + mono_trace_set_log_handler(gdmono_MonoLogCallback, this); + } else { + OS::get_singleton()->printerr("Mono: No log file, using default log handler\n"); + } +} + +GDMonoLog::GDMonoLog() { + + singleton = this; + + log_level_id = -1; +} + +GDMonoLog::~GDMonoLog() { + + singleton = NULL; + + if (log_file) { + log_file->close(); + memdelete(log_file); + } +} diff --git a/modules/mono/mono_gd/gd_mono_log.h b/modules/mono/mono_gd/gd_mono_log.h new file mode 100644 index 0000000000..497f1e5317 --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_log.h @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* gd_mono_log.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef GD_MONO_LOG_H +#define GD_MONO_LOG_H + +#include "os/file_access.h" + +class GDMonoLog { + + int log_level_id; + + FileAccess *log_file; + String log_file_path; + + bool _try_create_logs_dir(const String &p_logs_dir); + void _open_log_file(const String &p_file_path); + void _delete_old_log_files(const String &p_logs_dir); + + static GDMonoLog *singleton; + +public: + _FORCE_INLINE_ static GDMonoLog *get_singleton() { return singleton; } + + void initialize(); + + _FORCE_INLINE_ FileAccess *get_log_file() { return log_file; } + _FORCE_INLINE_ String get_log_file_path() { return log_file_path; } + _FORCE_INLINE_ int get_log_level_id() { return log_level_id; } + + GDMonoLog(); + ~GDMonoLog(); +}; + +#endif // GD_MONO_LOG_H diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp new file mode 100644 index 0000000000..b5419952de --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_marshal.cpp @@ -0,0 +1,856 @@ +/*************************************************************************/ +/* gd_mono_marshal.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "gd_mono_marshal.h" + +#include "gd_mono.h" +#include "gd_mono_class.h" + +namespace GDMonoMarshal { + +#define RETURN_BOXED_STRUCT(m_t, m_var_in) \ + { \ + const m_t &m_in = m_var_in->operator m_t(); \ + MARSHALLED_OUT(m_t, m_in, raw); \ + return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(m_t), raw); \ + } + +#define RETURN_UNBOXED_STRUCT(m_t, m_var_in) \ + { \ + float *raw = UNBOX_FLOAT_PTR(m_var_in); \ + MARSHALLED_IN(m_t, raw, ret); \ + return ret; \ + } + +Variant::Type managed_to_variant_type(const ManagedType &p_type) { + switch (p_type.type_encoding) { + case MONO_TYPE_BOOLEAN: + return Variant::BOOL; + + case MONO_TYPE_I1: + return Variant::INT; + case MONO_TYPE_I2: + return Variant::INT; + case MONO_TYPE_I4: + return Variant::INT; + case MONO_TYPE_I8: + return Variant::INT; + + case MONO_TYPE_U1: + return Variant::INT; + case MONO_TYPE_U2: + return Variant::INT; + case MONO_TYPE_U4: + return Variant::INT; + case MONO_TYPE_U8: + return Variant::INT; + + case MONO_TYPE_R4: + return Variant::REAL; + case MONO_TYPE_R8: + return Variant::REAL; + + case MONO_TYPE_STRING: { + return Variant::STRING; + } break; + + case MONO_TYPE_VALUETYPE: { + GDMonoClass *tclass = p_type.type_class; + + if (tclass == CACHED_CLASS(Vector2)) + return Variant::VECTOR2; + + if (tclass == CACHED_CLASS(Rect2)) + return Variant::RECT2; + + if (tclass == CACHED_CLASS(Transform2D)) + return Variant::TRANSFORM2D; + + if (tclass == CACHED_CLASS(Vector3)) + return Variant::VECTOR3; + + if (tclass == CACHED_CLASS(Basis)) + return Variant::BASIS; + + if (tclass == CACHED_CLASS(Quat)) + return Variant::QUAT; + + if (tclass == CACHED_CLASS(Transform)) + return Variant::TRANSFORM; + + if (tclass == CACHED_CLASS(Rect3)) + return Variant::RECT3; + + if (tclass == CACHED_CLASS(Color)) + return Variant::COLOR; + + if (tclass == CACHED_CLASS(Plane)) + return Variant::PLANE; + } break; + + case MONO_TYPE_ARRAY: + case MONO_TYPE_SZARRAY: { + MonoArrayType *array_type = mono_type_get_array_type(GDMonoClass::get_raw_type(p_type.type_class)); + + if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) + return Variant::ARRAY; + + if (array_type->eklass == CACHED_CLASS_RAW(uint8_t)) + return Variant::POOL_BYTE_ARRAY; + + if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) + return Variant::POOL_INT_ARRAY; + + if (array_type->eklass == REAL_T_MONOCLASS) + return Variant::POOL_REAL_ARRAY; + + if (array_type->eklass == CACHED_CLASS_RAW(String)) + return Variant::POOL_STRING_ARRAY; + + if (array_type->eklass == CACHED_CLASS_RAW(Vector2)) + return Variant::POOL_VECTOR2_ARRAY; + + if (array_type->eklass == CACHED_CLASS_RAW(Vector3)) + return Variant::POOL_VECTOR3_ARRAY; + + if (array_type->eklass == CACHED_CLASS_RAW(Color)) + return Variant::POOL_COLOR_ARRAY; + } break; + + case MONO_TYPE_CLASS: { + GDMonoClass *type_class = p_type.type_class; + + // GodotObject + if (CACHED_CLASS(GodotObject)->is_assignable_from(type_class)) { + return Variant::OBJECT; + } + + if (CACHED_CLASS(NodePath) == type_class) { + return Variant::NODE_PATH; + } + + if (CACHED_CLASS(RID) == type_class) { + return Variant::_RID; + } + } break; + + case MONO_TYPE_GENERICINST: { + if (CACHED_RAW_MONO_CLASS(Dictionary) == p_type.type_class->get_raw()) { + return Variant::DICTIONARY; + } + } break; + } + + // No error, the caller will decide what to do in this case + return Variant::NIL; +} + +String mono_to_utf8_string(MonoString *p_mono_string) { + MonoError error; + char *utf8 = mono_string_to_utf8_checked(p_mono_string, &error); + + ERR_EXPLAIN("Conversion of MonoString to UTF8 failed."); + ERR_FAIL_COND_V(!mono_error_ok(&error), String()); + + String ret = String::utf8(utf8); + + mono_free(utf8); + + return ret; +} + +String mono_to_utf16_string(MonoString *p_mono_string) { + int len = mono_string_length(p_mono_string); + String ret; + + if (len == 0) + return ret; + + ret.resize(len + 1); + ret.set(len, 0); + + CharType *src = (CharType *)mono_string_chars(p_mono_string); + CharType *dst = &(ret.operator[](0)); + + for (int i = 0; i < len; i++) { + dst[i] = src[i]; + } + + return ret; +} + +MonoObject *variant_to_mono_object(const Variant *p_var) { + ManagedType type; + + type.type_encoding = MONO_TYPE_OBJECT; + + return variant_to_mono_object(p_var, type); +} + +MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_type) { + switch (p_type.type_encoding) { + case MONO_TYPE_BOOLEAN: { + MonoBoolean val = p_var->operator bool(); + return BOX_BOOLEAN(val); + } + + case MONO_TYPE_I1: { + char val = p_var->operator signed char(); + return BOX_INT8(val); + } + case MONO_TYPE_I2: { + short val = p_var->operator signed short(); + return BOX_INT16(val); + } + case MONO_TYPE_I4: { + int val = p_var->operator signed int(); + return BOX_INT32(val); + } + case MONO_TYPE_I8: { + int64_t val = p_var->operator int64_t(); + return BOX_INT64(val); + } + + case MONO_TYPE_U1: { + char val = p_var->operator unsigned char(); + return BOX_UINT8(val); + } + case MONO_TYPE_U2: { + short val = p_var->operator unsigned short(); + return BOX_UINT16(val); + } + case MONO_TYPE_U4: { + int val = p_var->operator unsigned int(); + return BOX_UINT32(val); + } + case MONO_TYPE_U8: { + uint64_t val = p_var->operator uint64_t(); + return BOX_UINT64(val); + } + + case MONO_TYPE_R4: { + float val = p_var->operator float(); + return BOX_FLOAT(val); + } + case MONO_TYPE_R8: { + double val = p_var->operator double(); + return BOX_DOUBLE(val); + } + + case MONO_TYPE_STRING: { + return (MonoObject *)mono_string_from_godot(p_var->operator String()); + } break; + + case MONO_TYPE_VALUETYPE: { + GDMonoClass *tclass = p_type.type_class; + + if (tclass == CACHED_CLASS(Vector2)) + RETURN_BOXED_STRUCT(Vector2, p_var); + + if (tclass == CACHED_CLASS(Rect2)) + RETURN_BOXED_STRUCT(Rect2, p_var); + + if (tclass == CACHED_CLASS(Transform2D)) + RETURN_BOXED_STRUCT(Transform2D, p_var); + + if (tclass == CACHED_CLASS(Vector3)) + RETURN_BOXED_STRUCT(Vector3, p_var); + + if (tclass == CACHED_CLASS(Basis)) + RETURN_BOXED_STRUCT(Basis, p_var); + + if (tclass == CACHED_CLASS(Quat)) + RETURN_BOXED_STRUCT(Quat, p_var); + + if (tclass == CACHED_CLASS(Transform)) + RETURN_BOXED_STRUCT(Transform, p_var); + + if (tclass == CACHED_CLASS(Rect3)) + RETURN_BOXED_STRUCT(Rect3, p_var); + + if (tclass == CACHED_CLASS(Color)) + RETURN_BOXED_STRUCT(Color, p_var); + + if (tclass == CACHED_CLASS(Plane)) + RETURN_BOXED_STRUCT(Plane, p_var); + } break; + + case MONO_TYPE_ARRAY: + case MONO_TYPE_SZARRAY: { + MonoArrayType *array_type = mono_type_get_array_type(GDMonoClass::get_raw_type(p_type.type_class)); + + if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) + return (MonoObject *)Array_to_mono_array(p_var->operator Array()); + + if (array_type->eklass == CACHED_CLASS_RAW(uint8_t)) + return (MonoObject *)PoolByteArray_to_mono_array(p_var->operator PoolByteArray()); + + if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) + return (MonoObject *)PoolIntArray_to_mono_array(p_var->operator PoolIntArray()); + + if (array_type->eklass == REAL_T_MONOCLASS) + return (MonoObject *)PoolRealArray_to_mono_array(p_var->operator PoolRealArray()); + + if (array_type->eklass == CACHED_CLASS_RAW(String)) + return (MonoObject *)PoolStringArray_to_mono_array(p_var->operator PoolStringArray()); + + if (array_type->eklass == CACHED_CLASS_RAW(Vector2)) + return (MonoObject *)PoolVector2Array_to_mono_array(p_var->operator PoolVector2Array()); + + if (array_type->eklass == CACHED_CLASS_RAW(Vector3)) + return (MonoObject *)PoolVector3Array_to_mono_array(p_var->operator PoolVector3Array()); + + if (array_type->eklass == CACHED_CLASS_RAW(Color)) + return (MonoObject *)PoolColorArray_to_mono_array(p_var->operator PoolColorArray()); + + ERR_EXPLAIN(String() + "Attempted to convert Variant to a managed array of unmarshallable element type."); + ERR_FAIL_V(NULL); + } break; + + case MONO_TYPE_CLASS: { + GDMonoClass *type_class = p_type.type_class; + + // GodotObject + if (CACHED_CLASS(GodotObject)->is_assignable_from(type_class)) { + return GDMonoUtils::unmanaged_get_managed(p_var->operator Object *()); + } + + if (CACHED_CLASS(NodePath) == type_class) { + return GDMonoUtils::create_managed_from(p_var->operator NodePath()); + } + + if (CACHED_CLASS(RID) == type_class) { + return GDMonoUtils::create_managed_from(p_var->operator RID()); + } + } break; + case MONO_TYPE_OBJECT: { + // Variant + switch (p_var->get_type()) { + case Variant::BOOL: { + MonoBoolean val = p_var->operator bool(); + return BOX_BOOLEAN(val); + } + case Variant::INT: { + int val = p_var->operator signed int(); + return BOX_INT32(val); + } + case Variant::REAL: { +#ifdef REAL_T_IS_DOUBLE + double val = p_var->operator double(); + return BOX_DOUBLE(val); +#else + float val = p_var->operator float(); + return BOX_FLOAT(val); +#endif + } + case Variant::STRING: + return (MonoObject *)mono_string_from_godot(p_var->operator String()); + case Variant::VECTOR2: + RETURN_BOXED_STRUCT(Vector2, p_var); + case Variant::RECT2: + RETURN_BOXED_STRUCT(Rect2, p_var); + case Variant::VECTOR3: + RETURN_BOXED_STRUCT(Vector3, p_var); + case Variant::TRANSFORM2D: + RETURN_BOXED_STRUCT(Transform2D, p_var); + case Variant::PLANE: + RETURN_BOXED_STRUCT(Plane, p_var); + case Variant::QUAT: + RETURN_BOXED_STRUCT(Quat, p_var); + case Variant::RECT3: + RETURN_BOXED_STRUCT(Rect3, p_var); + case Variant::BASIS: + RETURN_BOXED_STRUCT(Basis, p_var); + case Variant::TRANSFORM: + RETURN_BOXED_STRUCT(Transform, p_var); + case Variant::COLOR: + RETURN_BOXED_STRUCT(Color, p_var); + case Variant::NODE_PATH: + return GDMonoUtils::create_managed_from(p_var->operator NodePath()); + case Variant::_RID: + return GDMonoUtils::create_managed_from(p_var->operator RID()); + case Variant::OBJECT: { + return GDMonoUtils::unmanaged_get_managed(p_var->operator Object *()); + } + case Variant::DICTIONARY: + return Dictionary_to_mono_object(p_var->operator Dictionary()); + case Variant::ARRAY: + return (MonoObject *)Array_to_mono_array(p_var->operator Array()); + case Variant::POOL_BYTE_ARRAY: + return (MonoObject *)PoolByteArray_to_mono_array(p_var->operator PoolByteArray()); + case Variant::POOL_INT_ARRAY: + return (MonoObject *)PoolIntArray_to_mono_array(p_var->operator PoolIntArray()); + case Variant::POOL_REAL_ARRAY: + return (MonoObject *)PoolRealArray_to_mono_array(p_var->operator PoolRealArray()); + case Variant::POOL_STRING_ARRAY: + return (MonoObject *)PoolStringArray_to_mono_array(p_var->operator PoolStringArray()); + case Variant::POOL_VECTOR2_ARRAY: + return (MonoObject *)PoolVector2Array_to_mono_array(p_var->operator PoolVector2Array()); + case Variant::POOL_VECTOR3_ARRAY: + return (MonoObject *)PoolVector3Array_to_mono_array(p_var->operator PoolVector3Array()); + case Variant::POOL_COLOR_ARRAY: + return (MonoObject *)PoolColorArray_to_mono_array(p_var->operator PoolColorArray()); + default: + return NULL; + } + break; + case MONO_TYPE_GENERICINST: { + if (CACHED_RAW_MONO_CLASS(Dictionary) == p_type.type_class->get_raw()) { + return Dictionary_to_mono_object(p_var->operator Dictionary()); + } + } break; + } break; + } + + ERR_EXPLAIN(String() + "Attempted to convert Variant to an unmarshallable managed type. Name: \'" + + p_type.type_class->get_name() + "\' Encoding: " + itos(p_type.type_encoding)); + ERR_FAIL_V(NULL); +} + +Variant mono_object_to_variant(MonoObject *p_obj) { + if (!p_obj) + return Variant(); + + GDMonoClass *tclass = GDMono::get_singleton()->get_class(mono_object_get_class(p_obj)); + ERR_FAIL_COND_V(!tclass, Variant()); + + MonoType *raw_type = tclass->get_raw_type(tclass); + + ManagedType type; + + type.type_encoding = mono_type_get_type(raw_type); + type.type_class = tclass; + + return mono_object_to_variant(p_obj, type); +} + +Variant mono_object_to_variant(MonoObject *p_obj, const ManagedType &p_type) { + switch (p_type.type_encoding) { + case MONO_TYPE_BOOLEAN: + return (bool)UNBOX_BOOLEAN(p_obj); + + case MONO_TYPE_I1: + return UNBOX_INT8(p_obj); + case MONO_TYPE_I2: + return UNBOX_INT16(p_obj); + case MONO_TYPE_I4: + return UNBOX_INT32(p_obj); + case MONO_TYPE_I8: + return UNBOX_INT64(p_obj); + + case MONO_TYPE_U1: + return UNBOX_UINT8(p_obj); + case MONO_TYPE_U2: + return UNBOX_UINT16(p_obj); + case MONO_TYPE_U4: + return UNBOX_UINT32(p_obj); + case MONO_TYPE_U8: + return UNBOX_UINT64(p_obj); + + case MONO_TYPE_R4: + return UNBOX_FLOAT(p_obj); + case MONO_TYPE_R8: + return UNBOX_DOUBLE(p_obj); + + case MONO_TYPE_STRING: { + String str = mono_string_to_godot((MonoString *)p_obj); + return str; + } break; + + case MONO_TYPE_VALUETYPE: { + GDMonoClass *tclass = p_type.type_class; + + if (tclass == CACHED_CLASS(Vector2)) + RETURN_UNBOXED_STRUCT(Vector2, p_obj); + + if (tclass == CACHED_CLASS(Rect2)) + RETURN_UNBOXED_STRUCT(Rect2, p_obj); + + if (tclass == CACHED_CLASS(Transform2D)) + RETURN_UNBOXED_STRUCT(Transform2D, p_obj); + + if (tclass == CACHED_CLASS(Vector3)) + RETURN_UNBOXED_STRUCT(Vector3, p_obj); + + if (tclass == CACHED_CLASS(Basis)) + RETURN_UNBOXED_STRUCT(Basis, p_obj); + + if (tclass == CACHED_CLASS(Quat)) + RETURN_UNBOXED_STRUCT(Quat, p_obj); + + if (tclass == CACHED_CLASS(Transform)) + RETURN_UNBOXED_STRUCT(Transform, p_obj); + + if (tclass == CACHED_CLASS(Rect3)) + RETURN_UNBOXED_STRUCT(Rect3, p_obj); + + if (tclass == CACHED_CLASS(Color)) + RETURN_UNBOXED_STRUCT(Color, p_obj); + + if (tclass == CACHED_CLASS(Plane)) + RETURN_UNBOXED_STRUCT(Plane, p_obj); + } break; + + case MONO_TYPE_ARRAY: + case MONO_TYPE_SZARRAY: { + MonoArrayType *array_type = mono_type_get_array_type(GDMonoClass::get_raw_type(p_type.type_class)); + + if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) + return mono_array_to_Array((MonoArray *)p_obj); + + if (array_type->eklass == CACHED_CLASS_RAW(uint8_t)) + return mono_array_to_PoolByteArray((MonoArray *)p_obj); + + if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) + return mono_array_to_PoolIntArray((MonoArray *)p_obj); + + if (array_type->eklass == REAL_T_MONOCLASS) + return mono_array_to_PoolRealArray((MonoArray *)p_obj); + + if (array_type->eklass == CACHED_CLASS_RAW(String)) + return mono_array_to_PoolStringArray((MonoArray *)p_obj); + + if (array_type->eklass == CACHED_CLASS_RAW(Vector2)) + return mono_array_to_PoolVector2Array((MonoArray *)p_obj); + + if (array_type->eklass == CACHED_CLASS_RAW(Vector3)) + return mono_array_to_PoolVector3Array((MonoArray *)p_obj); + + if (array_type->eklass == CACHED_CLASS_RAW(Color)) + return mono_array_to_PoolColorArray((MonoArray *)p_obj); + + ERR_EXPLAIN(String() + "Attempted to convert a managed array of unmarshallable element type to Variant."); + ERR_FAIL_V(Variant()); + } break; + + case MONO_TYPE_CLASS: { + GDMonoClass *type_class = p_type.type_class; + + // GodotObject + if (CACHED_CLASS(GodotObject)->is_assignable_from(type_class)) { + GDMonoField *ptr_field = CACHED_FIELD(GodotObject, ptr); + + ERR_FAIL_NULL_V(ptr_field, Variant()); + + void *ptr_to_unmanaged = UNBOX_PTR(ptr_field->get_value(p_obj)); + + if (!ptr_to_unmanaged) // IntPtr.Zero + return Variant(); + + Object *object_ptr = static_cast<Object *>(ptr_to_unmanaged); + + if (!object_ptr) + return Variant(); + + return object_ptr; + } + + if (CACHED_CLASS(NodePath) == type_class) { + return UNBOX_PTR(CACHED_FIELD(NodePath, ptr)->get_value(p_obj)); + } + + if (CACHED_CLASS(RID) == type_class) { + return UNBOX_PTR(CACHED_FIELD(RID, ptr)->get_value(p_obj)); + } + } break; + + case MONO_TYPE_GENERICINST: { + if (CACHED_RAW_MONO_CLASS(Dictionary) == p_type.type_class->get_raw()) { + return mono_object_to_Dictionary(p_obj); + } + } break; + } + + ERR_EXPLAIN(String() + "Attempted to convert an unmarshallable managed type to Variant. Name: \'" + + p_type.type_class->get_name() + "\' Encoding: " + itos(p_type.type_encoding)); + ERR_FAIL_V(Variant()); +} + +MonoArray *Array_to_mono_array(const Array &p_array) { + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), p_array.size()); + + for (int i = 0; i < p_array.size(); i++) { + MonoObject *boxed = variant_to_mono_object(p_array[i]); + mono_array_set(ret, MonoObject *, i, boxed); + } + + return ret; +} + +Array mono_array_to_Array(MonoArray *p_array) { + Array ret; + int length = mono_array_length(p_array); + + for (int i = 0; i < length; i++) { + MonoObject *elem = mono_array_get(p_array, MonoObject *, i); + ret.push_back(mono_object_to_variant(elem)); + } + + return ret; +} + +// TODO Optimize reading/writing from/to PoolArrays + +MonoArray *PoolIntArray_to_mono_array(const PoolIntArray &p_array) { + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(int32_t), p_array.size()); + + for (int i = 0; i < p_array.size(); i++) { + mono_array_set(ret, int32_t, i, p_array[i]); + } + + return ret; +} + +PoolIntArray mono_array_to_PoolIntArray(MonoArray *p_array) { + PoolIntArray ret; + int length = mono_array_length(p_array); + + for (int i = 0; i < length; i++) { + int32_t elem = mono_array_get(p_array, int32_t, i); + ret.push_back(elem); + } + + return ret; +} + +MonoArray *PoolByteArray_to_mono_array(const PoolByteArray &p_array) { + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(uint8_t), p_array.size()); + + for (int i = 0; i < p_array.size(); i++) { + mono_array_set(ret, uint8_t, i, p_array[i]); + } + + return ret; +} + +PoolByteArray mono_array_to_PoolByteArray(MonoArray *p_array) { + PoolByteArray ret; + int length = mono_array_length(p_array); + + for (int i = 0; i < length; i++) { + uint8_t elem = mono_array_get(p_array, uint8_t, i); + ret.push_back(elem); + } + + return ret; +} + +MonoArray *PoolRealArray_to_mono_array(const PoolRealArray &p_array) { + MonoArray *ret = mono_array_new(mono_domain_get(), REAL_T_MONOCLASS, p_array.size()); + + for (int i = 0; i < p_array.size(); i++) { + mono_array_set(ret, real_t, i, p_array[i]); + } + + return ret; +} + +PoolRealArray mono_array_to_PoolRealArray(MonoArray *p_array) { + PoolRealArray ret; + int length = mono_array_length(p_array); + + for (int i = 0; i < length; i++) { + real_t elem = mono_array_get(p_array, real_t, i); + ret.push_back(elem); + } + + return ret; +} + +MonoArray *PoolStringArray_to_mono_array(const PoolStringArray &p_array) { + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(String), p_array.size()); + + for (int i = 0; i < p_array.size(); i++) { + MonoString *boxed = mono_string_from_godot(p_array[i]); + mono_array_set(ret, MonoString *, i, boxed); + } + + return ret; +} + +PoolStringArray mono_array_to_PoolStringArray(MonoArray *p_array) { + PoolStringArray ret; + int length = mono_array_length(p_array); + + for (int i = 0; i < length; i++) { + MonoString *elem = mono_array_get(p_array, MonoString *, i); + ret.push_back(mono_string_to_godot(elem)); + } + + return ret; +} + +MonoArray *PoolColorArray_to_mono_array(const PoolColorArray &p_array) { + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Color), p_array.size()); + + for (int i = 0; i < p_array.size(); i++) { +#ifdef YOLOCOPY + mono_array_set(ret, Color, i, p_array[i]); +#else + real_t *raw = (real_t *)mono_array_addr_with_size(ret, sizeof(real_t) * 4, i); + const Color &elem = p_array[i]; + raw[0] = elem.r; + raw[4] = elem.g; + raw[8] = elem.b; + raw[12] = elem.a; +#endif + } + + return ret; +} + +PoolColorArray mono_array_to_PoolColorArray(MonoArray *p_array) { + PoolColorArray ret; + int length = mono_array_length(p_array); + + for (int i = 0; i < length; i++) { + real_t *raw_elem = mono_array_get(p_array, real_t *, i); + MARSHALLED_IN(Color, raw_elem, elem); + ret.push_back(elem); + } + + return ret; +} + +MonoArray *PoolVector2Array_to_mono_array(const PoolVector2Array &p_array) { + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector2), p_array.size()); + + for (int i = 0; i < p_array.size(); i++) { +#ifdef YOLOCOPY + mono_array_set(ret, Vector2, i, p_array[i]); +#else + real_t *raw = (real_t *)mono_array_addr_with_size(ret, sizeof(real_t) * 2, i); + const Vector2 &elem = p_array[i]; + raw[0] = elem.x; + raw[4] = elem.y; +#endif + } + + return ret; +} + +PoolVector2Array mono_array_to_PoolVector2Array(MonoArray *p_array) { + PoolVector2Array ret; + int length = mono_array_length(p_array); + + for (int i = 0; i < length; i++) { + real_t *raw_elem = mono_array_get(p_array, real_t *, i); + MARSHALLED_IN(Vector2, raw_elem, elem); + ret.push_back(elem); + } + + return ret; +} + +MonoArray *PoolVector3Array_to_mono_array(const PoolVector3Array &p_array) { + MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector3), p_array.size()); + + for (int i = 0; i < p_array.size(); i++) { +#ifdef YOLOCOPY + mono_array_set(ret, Vector3, i, p_array[i]); +#else + real_t *raw = (real_t *)mono_array_addr_with_size(ret, sizeof(real_t) * 3, i); + const Vector3 &elem = p_array[i]; + raw[0] = elem.x; + raw[4] = elem.y; + raw[8] = elem.z; +#endif + } + + return ret; +} + +PoolVector3Array mono_array_to_PoolVector3Array(MonoArray *p_array) { + PoolVector3Array ret; + int length = mono_array_length(p_array); + + for (int i = 0; i < length; i++) { + real_t *raw_elem = mono_array_get(p_array, real_t *, i); + MARSHALLED_IN(Vector3, raw_elem, elem); + ret.push_back(elem); + } + + return ret; +} + +MonoObject *Dictionary_to_mono_object(const Dictionary &p_dict) { + MonoArray *keys = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), p_dict.size()); + MonoArray *values = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), p_dict.size()); + + int i = 0; + const Variant *dkey = NULL; + while ((dkey = p_dict.next(dkey))) { + mono_array_set(keys, MonoObject *, i, variant_to_mono_object(dkey)); + mono_array_set(values, MonoObject *, i, variant_to_mono_object(p_dict[*dkey])); + i++; + } + + GDMonoUtils::MarshalUtils_ArraysToDict arrays_to_dict = CACHED_METHOD_THUNK(MarshalUtils, ArraysToDictionary); + + MonoObject *ex = NULL; + MonoObject *ret = arrays_to_dict(keys, values, &ex); + + if (ex) { + mono_print_unhandled_exception(ex); + ERR_FAIL_V(NULL); + } + + return ret; +} + +Dictionary mono_object_to_Dictionary(MonoObject *p_dict) { + Dictionary ret; + + GDMonoUtils::MarshalUtils_DictToArrays dict_to_arrays = CACHED_METHOD_THUNK(MarshalUtils, DictionaryToArrays); + + MonoArray *keys = NULL; + MonoArray *values = NULL; + MonoObject *ex = NULL; + dict_to_arrays(p_dict, &keys, &values, &ex); + + if (ex) { + mono_print_unhandled_exception(ex); + ERR_FAIL_V(Dictionary()); + } + + int length = mono_array_length(keys); + + for (int i = 0; i < length; i++) { + MonoObject *key_obj = mono_array_get(keys, MonoObject *, i); + MonoObject *value_obj = mono_array_get(values, MonoObject *, i); + + Variant key = key_obj ? mono_object_to_variant(key_obj) : Variant(); + Variant value = value_obj ? mono_object_to_variant(value_obj) : Variant(); + + ret[key] = value; + } + + return ret; +} +} diff --git a/modules/mono/mono_gd/gd_mono_marshal.h b/modules/mono/mono_gd/gd_mono_marshal.h new file mode 100644 index 0000000000..5fbafa0acb --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_marshal.h @@ -0,0 +1,229 @@ +/*************************************************************************/ +/* gd_mono_marshal.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef GDMONOMARSHAL_H +#define GDMONOMARSHAL_H + +#include "gd_mono.h" +#include "gd_mono_utils.h" +#include "variant.h" + +namespace GDMonoMarshal { + +#define UNBOX_CHAR_PTR(x) (char *)mono_object_unbox(x) +#define UNBOX_FLOAT_PTR(x) (float *)mono_object_unbox(x) + +#define UNBOX_DOUBLE(x) *(double *)mono_object_unbox(x) +#define UNBOX_FLOAT(x) *(float *)mono_object_unbox(x) +#define UNBOX_INT64(x) *(int64_t *)mono_object_unbox(x) +#define UNBOX_INT32(x) *(int32_t *)mono_object_unbox(x) +#define UNBOX_INT16(x) *(int16_t *)mono_object_unbox(x) +#define UNBOX_INT8(x) *(int8_t *)mono_object_unbox(x) +#define UNBOX_UINT64(x) *(uint64_t *)mono_object_unbox(x) +#define UNBOX_UINT32(x) *(uint32_t *)mono_object_unbox(x) +#define UNBOX_UINT16(x) *(uint16_t *)mono_object_unbox(x) +#define UNBOX_UINT8(x) *(uint8_t *)mono_object_unbox(x) +#define UNBOX_BOOLEAN(x) *(MonoBoolean *)mono_object_unbox(x) +#define UNBOX_PTR(x) mono_object_unbox(x) + +#define BOX_DOUBLE(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(double), &x) +#define BOX_FLOAT(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(float), &x) +#define BOX_INT64(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(int64_t), &x) +#define BOX_INT32(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(int32_t), &x) +#define BOX_INT16(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(int16_t), &x) +#define BOX_INT8(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(int8_t), &x) +#define BOX_UINT64(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(uint64_t), &x) +#define BOX_UINT32(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(uint32_t), &x) +#define BOX_UINT16(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(uint16_t), &x) +#define BOX_UINT8(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(uint8_t), &x) +#define BOX_BOOLEAN(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(bool), &x) +#define BOX_PTR(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(IntPtr), x) + +Variant::Type managed_to_variant_type(const ManagedType &p_type); + +// String + +String mono_to_utf8_string(MonoString *p_mono_string); +String mono_to_utf16_string(MonoString *p_mono_string); + +_FORCE_INLINE_ String mono_string_to_godot(MonoString *p_mono_string) { + if (sizeof(CharType) == 2) + return mono_to_utf16_string(p_mono_string); + + return mono_to_utf8_string(p_mono_string); +} + +_FORCE_INLINE_ MonoString *mono_from_utf8_string(const String &p_string) { + return mono_string_new(mono_domain_get(), p_string.utf8().get_data()); +} + +_FORCE_INLINE_ MonoString *mono_from_utf16_string(const String &p_string) { + return mono_string_from_utf16((mono_unichar2 *)p_string.c_str()); +} + +_FORCE_INLINE_ MonoString *mono_string_from_godot(const String &p_string) { + if (sizeof(CharType) == 2) + return mono_from_utf16_string(p_string); + + return mono_from_utf8_string(p_string); +} + +// Variant + +MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_type); +MonoObject *variant_to_mono_object(const Variant *p_var); + +_FORCE_INLINE_ MonoObject *variant_to_mono_object(Variant p_var) { + return variant_to_mono_object(&p_var); +} + +Variant mono_object_to_variant(MonoObject *p_obj); +Variant mono_object_to_variant(MonoObject *p_obj, const ManagedType &p_type); + +// Array + +MonoArray *Array_to_mono_array(const Array &p_array); +Array mono_array_to_Array(MonoArray *p_array); + +// PoolIntArray + +MonoArray *PoolIntArray_to_mono_array(const PoolIntArray &p_array); +PoolIntArray mono_array_to_PoolIntArray(MonoArray *p_array); + +// PoolByteArray + +MonoArray *PoolByteArray_to_mono_array(const PoolByteArray &p_array); +PoolByteArray mono_array_to_PoolByteArray(MonoArray *p_array); + +// PoolRealArray + +MonoArray *PoolRealArray_to_mono_array(const PoolRealArray &p_array); +PoolRealArray mono_array_to_PoolRealArray(MonoArray *p_array); + +// PoolStringArray + +MonoArray *PoolStringArray_to_mono_array(const PoolStringArray &p_array); +PoolStringArray mono_array_to_PoolStringArray(MonoArray *p_array); + +// PoolColorArray + +MonoArray *PoolColorArray_to_mono_array(const PoolColorArray &p_array); +PoolColorArray mono_array_to_PoolColorArray(MonoArray *p_array); + +// PoolVector2Array + +MonoArray *PoolVector2Array_to_mono_array(const PoolVector2Array &p_array); +PoolVector2Array mono_array_to_PoolVector2Array(MonoArray *p_array); + +// PoolVector3Array + +MonoArray *PoolVector3Array_to_mono_array(const PoolVector3Array &p_array); +PoolVector3Array mono_array_to_PoolVector3Array(MonoArray *p_array); + +// Dictionary + +MonoObject *Dictionary_to_mono_object(const Dictionary &p_dict); +Dictionary mono_object_to_Dictionary(MonoObject *p_dict); + +#ifdef YOLO_COPY +#define MARSHALLED_OUT(m_t, m_in, m_out) m_t *m_out = (m_t *)&m_in; +#define MARSHALLED_IN(m_t, m_in, m_out) m_t m_out = *reinterpret_cast<m_t *>(m_in); +#else + +// Expects m_in to be of type float* + +#define MARSHALLED_OUT(m_t, m_in, m_out) MARSHALLED_OUT_##m_t(m_in, m_out) +#define MARSHALLED_IN(m_t, m_in, m_out) MARSHALLED_IN_##m_t(m_in, m_out) + +// Vector2 + +#define MARSHALLED_OUT_Vector2(m_in, m_out) real_t m_out[2] = { m_in.x, m_in.y }; +#define MARSHALLED_IN_Vector2(m_in, m_out) Vector2 m_out(m_in[0], m_in[1]); + +// Rect2 + +#define MARSHALLED_OUT_Rect2(m_in, m_out) real_t m_out[4] = { m_in.position.x, m_in.position.y, m_in.size.width, m_in.size.height }; +#define MARSHALLED_IN_Rect2(m_in, m_out) Rect2 m_out(m_in[0], m_in[1], m_in[2], m_in[3]); + +// Transform2D + +#define MARSHALLED_OUT_Transform2D(m_in, m_out) real_t m_out[6] = { m_in[0].x, m_in[0].y, m_in[1].x, m_in[1].y, m_in[2].x, m_in[2].y }; +#define MARSHALLED_IN_Transform2D(m_in, m_out) Transform2D m_out(m_in[0], m_in[1], m_in[2], m_in[3], m_in[4], m_in[5]); + +// Vector3 + +#define MARSHALLED_OUT_Vector3(m_in, m_out) real_t m_out[3] = { m_in.x, m_in.y, m_in.z }; +#define MARSHALLED_IN_Vector3(m_in, m_out) Vector3 m_out(m_in[0], m_in[1], m_in[2]); + +// Basis + +#define MARSHALLED_OUT_Basis(m_in, m_out) real_t m_out[9] = { \ + m_in[0].x, m_in[0].y, m_in[0].z, \ + m_in[1].x, m_in[1].y, m_in[1].z, \ + m_in[2].x, m_in[2].y, m_in[2].z \ +}; +#define MARSHALLED_IN_Basis(m_in, m_out) Basis m_out(m_in[0], m_in[1], m_in[2], m_in[3], m_in[4], m_in[5], m_in[6], m_in[7], m_in[8]); + +// Quat + +#define MARSHALLED_OUT_Quat(m_in, m_out) real_t m_out[4] = { m_in.x, m_in.y, m_in.z, m_in.w }; +#define MARSHALLED_IN_Quat(m_in, m_out) Quat m_out(m_in[0], m_in[1], m_in[2], m_in[3]); + +// Transform + +#define MARSHALLED_OUT_Transform(m_in, m_out) real_t m_out[12] = { \ + m_in.basis[0].x, m_in.basis[0].y, m_in.basis[0].z, \ + m_in.basis[1].x, m_in.basis[1].y, m_in.basis[1].z, \ + m_in.basis[2].x, m_in.basis[2].y, m_in.basis[2].z, \ + m_in.origin.x, m_in.origin.y, m_in.origin.z \ +}; +#define MARSHALLED_IN_Transform(m_in, m_out) Transform m_out( \ + Basis(m_in[0], m_in[1], m_in[2], m_in[3], m_in[4], m_in[5], m_in[6], m_in[7], m_in[8]), \ + Vector3(m_in[9], m_in[10], m_in[11])); + +// Rect3 + +#define MARSHALLED_OUT_Rect3(m_in, m_out) real_t m_out[6] = { m_in.position.x, m_in.position.y, m_in.position.z, m_in.size.x, m_in.size.y, m_in.size.z }; +#define MARSHALLED_IN_Rect3(m_in, m_out) Rect3 m_out(Vector3(m_in[0], m_in[1], m_in[2]), Vector3(m_in[3], m_in[4], m_in[5])); + +// Color + +#define MARSHALLED_OUT_Color(m_in, m_out) real_t m_out[4] = { m_in.r, m_in.g, m_in.b, m_in.a }; +#define MARSHALLED_IN_Color(m_in, m_out) Color m_out(m_in[0], m_in[1], m_in[2], m_in[3]); + +// Plane + +#define MARSHALLED_OUT_Plane(m_in, m_out) real_t m_out[4] = { m_in.normal.x, m_in.normal.y, m_in.normal.z, m_in.d }; +#define MARSHALLED_IN_Plane(m_in, m_out) Plane m_out(m_in[0], m_in[1], m_in[2], m_in[3]); + +#endif + +} // GDMonoMarshal + +#endif // GDMONOMARSHAL_H diff --git a/modules/mono/mono_gd/gd_mono_method.cpp b/modules/mono/mono_gd/gd_mono_method.cpp new file mode 100644 index 0000000000..6468e0d3d9 --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_method.cpp @@ -0,0 +1,192 @@ +/*************************************************************************/ +/* gd_mono_method.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "gd_mono_method.h" + +#include "gd_mono_class.h" +#include "gd_mono_marshal.h" + +void GDMonoMethod::_update_signature() { + // Apparently MonoMethodSignature needs not to be freed. + // mono_method_signature caches the result, we don't need to cache it ourselves. + + MonoMethodSignature *method_sig = mono_method_signature(mono_method); + _update_signature(method_sig); +} + +void GDMonoMethod::_update_signature(MonoMethodSignature *p_method_sig) { + is_instance = mono_signature_is_instance(p_method_sig); + params_count = mono_signature_get_param_count(p_method_sig); + + MonoType *ret_type = mono_signature_get_return_type(p_method_sig); + if (ret_type) { + return_type.type_encoding = mono_type_get_type(ret_type); + + if (return_type.type_encoding != MONO_TYPE_VOID) { + MonoClass *ret_type_class = mono_class_from_mono_type(ret_type); + return_type.type_class = GDMono::get_singleton()->get_class(ret_type_class); + } + } + + void *iter = NULL; + MonoType *param_raw_type; + while ((param_raw_type = mono_signature_get_params(p_method_sig, &iter)) != NULL) { + ManagedType param_type; + + param_type.type_encoding = mono_type_get_type(param_raw_type); + + if (param_type.type_encoding != MONO_TYPE_VOID) { + MonoClass *param_type_class = mono_class_from_mono_type(param_raw_type); + param_type.type_class = GDMono::get_singleton()->get_class(param_type_class); + } + + param_types.push_back(param_type); + } +} + +void *GDMonoMethod::get_thunk() { + return mono_method_get_unmanaged_thunk(mono_method); +} + +MonoObject *GDMonoMethod::invoke(MonoObject *p_object, const Variant **p_params, MonoObject **r_exc) { + if (get_return_type().type_encoding != MONO_TYPE_VOID || get_parameters_count() > 0) { + MonoArray *params = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), get_parameters_count()); + + for (int i = 0; i < params_count; i++) { + MonoObject *boxed_param = GDMonoMarshal::variant_to_mono_object(p_params[i], param_types[i]); + mono_array_set(params, MonoObject *, i, boxed_param); + } + + return mono_runtime_invoke_array(mono_method, p_object, params, r_exc); + } else { + mono_runtime_invoke(mono_method, p_object, NULL, r_exc); + return NULL; + } +} + +MonoObject *GDMonoMethod::invoke(MonoObject *p_object, MonoObject **r_exc) { + ERR_FAIL_COND_V(get_parameters_count() > 0, NULL); + return invoke_raw(p_object, NULL, r_exc); +} + +MonoObject *GDMonoMethod::invoke_raw(MonoObject *p_object, void **p_params, MonoObject **r_exc) { + return mono_runtime_invoke(mono_method, p_object, p_params, r_exc); +} + +bool GDMonoMethod::has_attribute(GDMonoClass *p_attr_class) { + ERR_FAIL_NULL_V(p_attr_class, false); + + if (!attrs_fetched) + fetch_attributes(); + + if (!attributes) + return false; + + return mono_custom_attrs_has_attr(attributes, p_attr_class->get_raw()); +} + +MonoObject *GDMonoMethod::get_attribute(GDMonoClass *p_attr_class) { + ERR_FAIL_NULL_V(p_attr_class, NULL); + + if (!attrs_fetched) + fetch_attributes(); + + if (!attributes) + return NULL; + + return mono_custom_attrs_get_attr(attributes, p_attr_class->get_raw()); +} + +void GDMonoMethod::fetch_attributes() { + ERR_FAIL_COND(attributes != NULL); + attributes = mono_custom_attrs_from_method(mono_method); + attrs_fetched = true; +} + +String GDMonoMethod::get_full_name(bool p_signature) const { + char *res = mono_method_full_name(mono_method, p_signature); + String full_name(res); + mono_free(res); + return full_name; +} + +String GDMonoMethod::get_full_name_no_class() const { + String res; + + MonoMethodSignature *method_sig = mono_method_signature(mono_method); + + char *ret_str = mono_type_full_name(mono_signature_get_return_type(method_sig)); + res += ret_str; + mono_free(ret_str); + + res += " "; + res += name; + res += "("; + + char *sig_desc = mono_signature_get_desc(method_sig, true); + res += sig_desc; + mono_free(sig_desc); + + res += ")"; + + return res; +} + +String GDMonoMethod::get_ret_type_full_name() const { + MonoMethodSignature *method_sig = mono_method_signature(mono_method); + char *ret_str = mono_type_full_name(mono_signature_get_return_type(method_sig)); + String res = ret_str; + mono_free(ret_str); + return res; +} + +String GDMonoMethod::get_signature_desc(bool p_namespaces) const { + MonoMethodSignature *method_sig = mono_method_signature(mono_method); + char *sig_desc = mono_signature_get_desc(method_sig, p_namespaces); + String res = sig_desc; + mono_free(sig_desc); + return res; +} + +GDMonoMethod::GDMonoMethod(StringName p_name, MonoMethod *p_method) { + name = p_name; + + mono_method = p_method; + + attrs_fetched = false; + attributes = NULL; + + _update_signature(); +} + +GDMonoMethod::~GDMonoMethod() { + if (attributes) { + mono_custom_attrs_free(attributes); + } +} diff --git a/modules/mono/mono_gd/gd_mono_method.h b/modules/mono/mono_gd/gd_mono_method.h new file mode 100644 index 0000000000..ea4bc8e707 --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_method.h @@ -0,0 +1,81 @@ +/*************************************************************************/ +/* gd_mono_method.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef GD_MONO_METHOD_H +#define GD_MONO_METHOD_H + +#include "gd_mono.h" +#include "gd_mono_header.h" + +class GDMonoMethod { + + StringName name; + + bool is_instance; + int params_count; + ManagedType return_type; + Vector<ManagedType> param_types; + + bool attrs_fetched; + MonoCustomAttrInfo *attributes; + + void _update_signature(); + void _update_signature(MonoMethodSignature *p_method_sig); + + friend class GDMonoClass; + + MonoMethod *mono_method; + +public: + _FORCE_INLINE_ StringName get_name() { return name; } + + _FORCE_INLINE_ bool is_static() { return !is_instance; } + _FORCE_INLINE_ int get_parameters_count() { return params_count; } + _FORCE_INLINE_ ManagedType get_return_type() { return return_type; } + + void *get_thunk(); + + MonoObject *invoke(MonoObject *p_object, const Variant **p_params, MonoObject **r_exc = NULL); + MonoObject *invoke(MonoObject *p_object, MonoObject **r_exc = NULL); + MonoObject *invoke_raw(MonoObject *p_object, void **p_params, MonoObject **r_exc = NULL); + + bool has_attribute(GDMonoClass *p_attr_class); + MonoObject *get_attribute(GDMonoClass *p_attr_class); + void fetch_attributes(); + + String get_full_name(bool p_signature = false) const; + String get_full_name_no_class() const; + String get_ret_type_full_name() const; + String get_signature_desc(bool p_namespaces = false) const; + + GDMonoMethod(StringName p_name, MonoMethod *p_method); + ~GDMonoMethod(); +}; + +#endif // GD_MONO_METHOD_H diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp new file mode 100644 index 0000000000..5deca8e64d --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_utils.cpp @@ -0,0 +1,367 @@ +/*************************************************************************/ +/* gd_mono_utils.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "gd_mono_utils.h" + +#include "os/dir_access.h" +#include "project_settings.h" +#include "reference.h" + +#include "../csharp_script.h" +#include "gd_mono.h" +#include "gd_mono_class.h" +#include "gd_mono_marshal.h" + +namespace GDMonoUtils { + +MonoCache mono_cache; + +#define CACHE_AND_CHECK(m_var, m_val) \ + { \ + m_var = m_val; \ + if (!m_var) ERR_PRINT("Mono Cache: Member " #m_var " is null. This is really bad!"); \ + } + +#define CACHE_CLASS_AND_CHECK(m_class, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.class_##m_class, m_val) +#define CACHE_NS_CLASS_AND_CHECK(m_ns, m_class, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.class_##m_ns##_##m_class, m_val) +#define CACHE_RAW_MONO_CLASS_AND_CHECK(m_class, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.rawclass_##m_class, m_val) +#define CACHE_FIELD_AND_CHECK(m_class, m_field, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.field_##m_class##_##m_field, m_val) +#define CACHE_METHOD_THUNK_AND_CHECK(m_class, m_method, m_val) CACHE_AND_CHECK(GDMonoUtils::mono_cache.methodthunk_##m_class##_##m_method, m_val) + +void MonoCache::clear_members() { + + class_MonoObject = NULL; + class_bool = NULL; + class_int8_t = NULL; + class_int16_t = NULL; + class_int32_t = NULL; + class_int64_t = NULL; + class_uint8_t = NULL; + class_uint16_t = NULL; + class_uint32_t = NULL; + class_uint64_t = NULL; + class_float = NULL; + class_double = NULL; + class_String = NULL; + class_IntPtr = NULL; + + rawclass_Dictionary = NULL; + + class_Vector2 = NULL; + class_Rect2 = NULL; + class_Transform2D = NULL; + class_Vector3 = NULL; + class_Basis = NULL; + class_Quat = NULL; + class_Transform = NULL; + class_Rect3 = NULL; + class_Color = NULL; + class_Plane = NULL; + class_NodePath = NULL; + class_RID = NULL; + class_GodotObject = NULL; + class_Node = NULL; + class_Control = NULL; + class_Spatial = NULL; + class_WeakRef = NULL; + class_MarshalUtils = NULL; + + class_ExportAttribute = NULL; + field_ExportAttribute_hint = NULL; + field_ExportAttribute_hint_string = NULL; + field_ExportAttribute_usage = NULL; + class_ToolAttribute = NULL; + class_RemoteAttribute = NULL; + class_SyncAttribute = NULL; + class_MasterAttribute = NULL; + class_SlaveAttribute = NULL; + class_GodotMethodAttribute = NULL; + field_GodotMethodAttribute_methodName = NULL; + + field_GodotObject_ptr = NULL; + field_NodePath_ptr = NULL; + field_Image_ptr = NULL; + field_RID_ptr = NULL; + + methodthunk_MarshalUtils_DictionaryToArrays = NULL; + methodthunk_MarshalUtils_ArraysToDictionary = NULL; + methodthunk_GodotObject__AwaitedSignalCallback = NULL; + methodthunk_SignalAwaiter_FailureCallback = NULL; + methodthunk_GodotTaskScheduler_Activate = NULL; + + task_scheduler_handle = Ref<MonoGCHandle>(); +} + +#define GODOT_API_CLASS(m_class) (GDMono::get_singleton()->get_api_assembly()->get_class(BINDINGS_NAMESPACE, #m_class)) + +void update_corlib_cache() { + + CACHE_CLASS_AND_CHECK(MonoObject, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_object_class())); + CACHE_CLASS_AND_CHECK(bool, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_boolean_class())); + CACHE_CLASS_AND_CHECK(int8_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_sbyte_class())); + CACHE_CLASS_AND_CHECK(int16_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_int16_class())); + CACHE_CLASS_AND_CHECK(int32_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_int32_class())); + CACHE_CLASS_AND_CHECK(int64_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_int64_class())); + CACHE_CLASS_AND_CHECK(uint8_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_byte_class())); + CACHE_CLASS_AND_CHECK(uint16_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_uint16_class())); + CACHE_CLASS_AND_CHECK(uint32_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_uint32_class())); + CACHE_CLASS_AND_CHECK(uint64_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_uint64_class())); + CACHE_CLASS_AND_CHECK(float, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_single_class())); + CACHE_CLASS_AND_CHECK(double, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_double_class())); + CACHE_CLASS_AND_CHECK(String, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_string_class())); + CACHE_CLASS_AND_CHECK(IntPtr, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_intptr_class())); +} + +void update_godot_api_cache() { + + CACHE_CLASS_AND_CHECK(Vector2, GODOT_API_CLASS(Vector2)); + CACHE_CLASS_AND_CHECK(Rect2, GODOT_API_CLASS(Rect2)); + CACHE_CLASS_AND_CHECK(Transform2D, GODOT_API_CLASS(Transform2D)); + CACHE_CLASS_AND_CHECK(Vector3, GODOT_API_CLASS(Vector3)); + CACHE_CLASS_AND_CHECK(Basis, GODOT_API_CLASS(Basis)); + CACHE_CLASS_AND_CHECK(Quat, GODOT_API_CLASS(Quat)); + CACHE_CLASS_AND_CHECK(Transform, GODOT_API_CLASS(Transform)); + CACHE_CLASS_AND_CHECK(Rect3, GODOT_API_CLASS(Rect3)); + CACHE_CLASS_AND_CHECK(Color, GODOT_API_CLASS(Color)); + CACHE_CLASS_AND_CHECK(Plane, GODOT_API_CLASS(Plane)); + CACHE_CLASS_AND_CHECK(NodePath, GODOT_API_CLASS(NodePath)); + CACHE_CLASS_AND_CHECK(RID, GODOT_API_CLASS(NodePath)); + CACHE_CLASS_AND_CHECK(GodotObject, GODOT_API_CLASS(Object)); + CACHE_CLASS_AND_CHECK(Node, GODOT_API_CLASS(Node)); + CACHE_CLASS_AND_CHECK(Control, GODOT_API_CLASS(Control)); + CACHE_CLASS_AND_CHECK(Spatial, GODOT_API_CLASS(Spatial)); + CACHE_CLASS_AND_CHECK(WeakRef, GODOT_API_CLASS(WeakRef)); + CACHE_CLASS_AND_CHECK(MarshalUtils, GODOT_API_CLASS(MarshalUtils)); + + // Attributes + CACHE_CLASS_AND_CHECK(ExportAttribute, GODOT_API_CLASS(ExportAttribute)); + CACHE_FIELD_AND_CHECK(ExportAttribute, hint, CACHED_CLASS(ExportAttribute)->get_field("hint")); + CACHE_FIELD_AND_CHECK(ExportAttribute, hint_string, CACHED_CLASS(ExportAttribute)->get_field("hint_string")); + CACHE_FIELD_AND_CHECK(ExportAttribute, usage, CACHED_CLASS(ExportAttribute)->get_field("usage")); + CACHE_CLASS_AND_CHECK(ToolAttribute, GODOT_API_CLASS(ToolAttribute)); + CACHE_CLASS_AND_CHECK(RemoteAttribute, GODOT_API_CLASS(RemoteAttribute)); + CACHE_CLASS_AND_CHECK(SyncAttribute, GODOT_API_CLASS(SyncAttribute)); + CACHE_CLASS_AND_CHECK(MasterAttribute, GODOT_API_CLASS(MasterAttribute)); + CACHE_CLASS_AND_CHECK(SlaveAttribute, GODOT_API_CLASS(SlaveAttribute)); + CACHE_CLASS_AND_CHECK(GodotMethodAttribute, GODOT_API_CLASS(GodotMethodAttribute)); + CACHE_FIELD_AND_CHECK(GodotMethodAttribute, methodName, CACHED_CLASS(GodotMethodAttribute)->get_field("methodName")); + + CACHE_FIELD_AND_CHECK(GodotObject, ptr, CACHED_CLASS(GodotObject)->get_field(BINDINGS_PTR_FIELD)); + CACHE_FIELD_AND_CHECK(NodePath, ptr, CACHED_CLASS(NodePath)->get_field(BINDINGS_PTR_FIELD)); + CACHE_FIELD_AND_CHECK(RID, ptr, CACHED_CLASS(RID)->get_field(BINDINGS_PTR_FIELD)); + + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, DictionaryToArrays, (MarshalUtils_DictToArrays)CACHED_CLASS(MarshalUtils)->get_method("DictionaryToArrays", 3)->get_thunk()); + CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, ArraysToDictionary, (MarshalUtils_ArraysToDict)CACHED_CLASS(MarshalUtils)->get_method("ArraysToDictionary", 2)->get_thunk()); + CACHE_METHOD_THUNK_AND_CHECK(GodotObject, _AwaitedSignalCallback, (GodotObject__AwaitedSignalCallback)CACHED_CLASS(GodotObject)->get_method("_AwaitedSignalCallback", 2)->get_thunk()); + CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, FailureCallback, (SignalAwaiter_FailureCallback)GODOT_API_CLASS(SignalAwaiter)->get_method("FailureCallback", 0)->get_thunk()); + CACHE_METHOD_THUNK_AND_CHECK(GodotTaskScheduler, Activate, (GodotTaskScheduler_Activate)GODOT_API_CLASS(GodotTaskScheduler)->get_method("Activate", 0)->get_thunk()); + + { + /* + * TODO Right now we only support Dictionary<object, object>. + * It would be great if we could support other key/value types + * without forcing the user to copy the entries. + */ + GDMonoMethod *method_get_dict_type = CACHED_CLASS(MarshalUtils)->get_method("GetDictionaryType", 0); + ERR_FAIL_NULL(method_get_dict_type); + MonoReflectionType *dict_refl_type = (MonoReflectionType *)method_get_dict_type->invoke(NULL); + ERR_FAIL_NULL(dict_refl_type); + MonoType *dict_type = mono_reflection_type_get_type(dict_refl_type); + ERR_FAIL_NULL(dict_type); + + CACHE_RAW_MONO_CLASS_AND_CHECK(Dictionary, mono_class_from_mono_type(dict_type)); + } + + MonoObject *task_scheduler = mono_object_new(SCRIPTS_DOMAIN, GODOT_API_CLASS(GodotTaskScheduler)->get_raw()); + mono_runtime_object_init(task_scheduler); + mono_cache.task_scheduler_handle = MonoGCHandle::create_strong(task_scheduler); +} + +void clear_cache() { + mono_cache.cleanup(); + mono_cache.clear_members(); +} + +MonoObject *unmanaged_get_managed(Object *unmanaged) { + if (unmanaged) { + if (unmanaged->get_script_instance()) { + CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(unmanaged->get_script_instance()); + + if (cs_instance) { + return cs_instance->get_mono_object(); + } + } + + // Only called if the owner does not have a CSharpInstance + void *data = unmanaged->get_script_instance_binding(CSharpLanguage::get_singleton()->get_language_index()); + + if (data) { + return ((Map<Object *, Ref<MonoGCHandle> >::Element *)data)->value()->get_target(); + } + } + + return NULL; +} + +void set_main_thread(MonoThread *p_thread) { + mono_thread_set_main(p_thread); +} + +void attach_current_thread() { + ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized()); + MonoThread *mono_thread = mono_thread_attach(SCRIPTS_DOMAIN); + ERR_FAIL_NULL(mono_thread); +} + +void detach_current_thread() { + ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized()); + MonoThread *mono_thread = mono_thread_current(); + ERR_FAIL_NULL(mono_thread); + mono_thread_detach(mono_thread); +} + +MonoThread *get_current_thread() { + return mono_thread_current(); +} + +GDMonoClass *get_object_class(MonoObject *p_object) { + return GDMono::get_singleton()->get_class(mono_object_get_class(p_object)); +} + +GDMonoClass *type_get_proxy_class(const StringName &p_type) { + String class_name = p_type; + + if (class_name[0] == '_') + class_name = class_name.substr(1, class_name.length()); + + GDMonoClass *klass = GDMono::get_singleton()->get_api_assembly()->get_class(BINDINGS_NAMESPACE, class_name); + +#ifdef TOOLS_ENABLED + if (!klass) { + return GDMono::get_singleton()->get_editor_api_assembly()->get_class(BINDINGS_NAMESPACE, class_name); + } +#endif + + return klass; +} + +GDMonoClass *get_class_native_base(GDMonoClass *p_class) { + GDMonoClass *klass = p_class; + + do { + const GDMonoAssembly *assembly = klass->get_assembly(); + if (assembly == GDMono::get_singleton()->get_api_assembly()) + return klass; +#ifdef TOOLS_ENABLED + if (assembly == GDMono::get_singleton()->get_editor_api_assembly()) + return klass; +#endif + } while ((klass = klass->get_parent_class()) != NULL); + + return NULL; +} + +MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringName &p_native, Object *p_object) { + String object_type = p_object->get_class_name(); + + if (object_type[0] == '_') + object_type = object_type.substr(1, object_type.length()); + + if (!ClassDB::is_parent_class(object_type, p_native)) { + ERR_EXPLAIN("Type inherits from native type '" + p_native + "', so it can't be instanced in object of type: '" + p_object->get_class() + "'"); + ERR_FAIL_V(NULL); + } + + MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_raw()); + ERR_FAIL_NULL_V(mono_object, NULL); + + CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, p_object); + + // Construct + mono_runtime_object_init(mono_object); + + return mono_object; +} + +MonoObject *create_managed_from(const NodePath &p_from) { + MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, CACHED_CLASS_RAW(NodePath)); + ERR_FAIL_NULL_V(mono_object, NULL); + + // Construct + mono_runtime_object_init(mono_object); + + CACHED_FIELD(NodePath, ptr)->set_value_raw(mono_object, memnew(NodePath(p_from))); + + return mono_object; +} + +MonoObject *create_managed_from(const RID &p_from) { + MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, CACHED_CLASS_RAW(RID)); + ERR_FAIL_NULL_V(mono_object, NULL); + + // Construct + mono_runtime_object_init(mono_object); + + CACHED_FIELD(RID, ptr)->set_value_raw(mono_object, memnew(RID(p_from))); + + return mono_object; +} + +MonoDomain *create_domain(const String &p_friendly_name) { + MonoDomain *domain = mono_domain_create_appdomain((char *)p_friendly_name.utf8().get_data(), NULL); + + if (domain) { + // Workaround to avoid this exception: + // System.Configuration.ConfigurationErrorsException: Error Initializing the configuration system. + // ---> System.ArgumentException: The 'ExeConfigFilename' argument cannot be null. + mono_domain_set_config(domain, ".", ""); + } + + return domain; +} + +String get_exception_name_and_message(MonoObject *p_ex) { + String res; + + MonoClass *klass = mono_object_get_class(p_ex); + MonoType *type = mono_class_get_type(klass); + + char *full_name = mono_type_full_name(type); + res += full_name; + mono_free(full_name); + + res += ": "; + + MonoProperty *prop = mono_class_get_property_from_name(klass, "Message"); + MonoString *msg = (MonoString *)mono_property_get_value(prop, p_ex, NULL, NULL); + res += GDMonoMarshal::mono_string_to_godot(msg); + + return res; +} +} diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h new file mode 100644 index 0000000000..f97f048aa9 --- /dev/null +++ b/modules/mono/mono_gd/gd_mono_utils.h @@ -0,0 +1,182 @@ +/*************************************************************************/ +/* gd_mono_utils.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef GD_MONOUTILS_H +#define GD_MONOUTILS_H + +#include <mono/metadata/threads.h> + +#include "../mono_gc_handle.h" +#include "gd_mono_header.h" + +#include "object.h" +#include "reference.h" + +namespace GDMonoUtils { + +typedef MonoObject *(*MarshalUtils_DictToArrays)(MonoObject *, MonoArray **, MonoArray **, MonoObject **); +typedef MonoObject *(*MarshalUtils_ArraysToDict)(MonoArray *, MonoArray *, MonoObject **); +typedef MonoObject *(*GodotObject__AwaitedSignalCallback)(MonoObject *, MonoArray **, MonoObject *, MonoObject **); +typedef MonoObject *(*SignalAwaiter_FailureCallback)(MonoObject *, MonoObject **); +typedef MonoObject *(*GodotTaskScheduler_Activate)(MonoObject *, MonoObject **); + +struct MonoCache { + // Format for cached classes in the Godot namespace: class_<Class> + // Macro: CACHED_CLASS(<Class>) + + // Format for cached classes in a different namespace: class_<Namespace>_<Class> + // Macro: CACHED_NS_CLASS(<Namespace>, <Class>) + + // ----------------------------------------------- + // corlib classes + + // Let's use the no-namespace format for these too + GDMonoClass *class_MonoObject; + GDMonoClass *class_bool; + GDMonoClass *class_int8_t; + GDMonoClass *class_int16_t; + GDMonoClass *class_int32_t; + GDMonoClass *class_int64_t; + GDMonoClass *class_uint8_t; + GDMonoClass *class_uint16_t; + GDMonoClass *class_uint32_t; + GDMonoClass *class_uint64_t; + GDMonoClass *class_float; + GDMonoClass *class_double; + GDMonoClass *class_String; + GDMonoClass *class_IntPtr; + + MonoClass *rawclass_Dictionary; + // ----------------------------------------------- + + GDMonoClass *class_Vector2; + GDMonoClass *class_Rect2; + GDMonoClass *class_Transform2D; + GDMonoClass *class_Vector3; + GDMonoClass *class_Basis; + GDMonoClass *class_Quat; + GDMonoClass *class_Transform; + GDMonoClass *class_Rect3; + GDMonoClass *class_Color; + GDMonoClass *class_Plane; + GDMonoClass *class_NodePath; + GDMonoClass *class_RID; + GDMonoClass *class_GodotObject; + GDMonoClass *class_Node; + GDMonoClass *class_Control; + GDMonoClass *class_Spatial; + GDMonoClass *class_WeakRef; + GDMonoClass *class_MarshalUtils; + + GDMonoClass *class_ExportAttribute; + GDMonoField *field_ExportAttribute_hint; + GDMonoField *field_ExportAttribute_hint_string; + GDMonoField *field_ExportAttribute_usage; + GDMonoClass *class_ToolAttribute; + GDMonoClass *class_RemoteAttribute; + GDMonoClass *class_SyncAttribute; + GDMonoClass *class_MasterAttribute; + GDMonoClass *class_SlaveAttribute; + GDMonoClass *class_GodotMethodAttribute; + GDMonoField *field_GodotMethodAttribute_methodName; + + GDMonoField *field_GodotObject_ptr; + GDMonoField *field_NodePath_ptr; + GDMonoField *field_Image_ptr; + GDMonoField *field_RID_ptr; + + MarshalUtils_DictToArrays methodthunk_MarshalUtils_DictionaryToArrays; + MarshalUtils_ArraysToDict methodthunk_MarshalUtils_ArraysToDictionary; + GodotObject__AwaitedSignalCallback methodthunk_GodotObject__AwaitedSignalCallback; + SignalAwaiter_FailureCallback methodthunk_SignalAwaiter_FailureCallback; + GodotTaskScheduler_Activate methodthunk_GodotTaskScheduler_Activate; + + Ref<MonoGCHandle> task_scheduler_handle; + + void clear_members(); + void cleanup() {} + + MonoCache() { + clear_members(); + } +}; + +extern MonoCache mono_cache; + +void update_corlib_cache(); +void update_godot_api_cache(); +void clear_cache(); + +_FORCE_INLINE_ void hash_combine(uint32_t &p_hash, const uint32_t &p_with_hash) { + p_hash ^= p_with_hash + 0x9e3779b9 + (p_hash << 6) + (p_hash >> 2); +} + +/** + * If the object has a csharp script, returns the target of the gchandle stored in the script instance + * Otherwise returns a newly constructed MonoObject* which is attached to the object + * Returns NULL on error + */ +MonoObject *unmanaged_get_managed(Object *unmanaged); + +void set_main_thread(MonoThread *p_thread); +void attach_current_thread(); +void detach_current_thread(); +MonoThread *get_current_thread(); + +GDMonoClass *get_object_class(MonoObject *p_object); +GDMonoClass *type_get_proxy_class(const StringName &p_type); +GDMonoClass *get_class_native_base(GDMonoClass *p_class); + +MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringName &p_native, Object *p_object); + +MonoObject *create_managed_from(const NodePath &p_from); +MonoObject *create_managed_from(const RID &p_from); + +MonoDomain *create_domain(const String &p_friendly_name); + +String get_exception_name_and_message(MonoObject *p_ex); + +} // GDMonoUtils + +#define NATIVE_GDMONOCLASS_NAME(m_class) (GDMonoMarshal::mono_string_to_godot((MonoString *)m_class->get_field("nativeName")->get_value(NULL))) + +#define CACHED_CLASS(m_class) (GDMonoUtils::mono_cache.class_##m_class) +#define CACHED_CLASS_RAW(m_class) (GDMonoUtils::mono_cache.class_##m_class->get_raw()) +#define CACHED_NS_CLASS(m_ns, m_class) (GDMonoUtils::mono_cache.class_##m_ns##_##m_class) +#define CACHED_RAW_MONO_CLASS(m_class) (GDMonoUtils::mono_cache.rawclass_##m_class) +#define CACHED_FIELD(m_class, m_field) (GDMonoUtils::mono_cache.field_##m_class##_##m_field) +#define CACHED_METHOD_THUNK(m_class, m_method) (GDMonoUtils::mono_cache.methodthunk_##m_class##_##m_method) + +#ifdef REAL_T_IS_DOUBLE +#define REAL_T_MONOCLASS CACHED_CLASS_RAW(double) +#else +#define REAL_T_MONOCLASS CACHED_CLASS_RAW(float) +#endif + +#endif // GD_MONOUTILS_H diff --git a/modules/mono/mono_reg_utils.py b/modules/mono/mono_reg_utils.py new file mode 100644 index 0000000000..6f1620ff49 --- /dev/null +++ b/modules/mono/mono_reg_utils.py @@ -0,0 +1,54 @@ +import os + +if os.name == 'nt': + import _winreg as winreg + + +def _reg_open_key(key, subkey): + try: + return winreg.OpenKey(key, subkey) + except (WindowsError, EnvironmentError) as e: + import platform + if platform.architecture()[0] == '32bit': + bitness_sam = winreg.KEY_WOW64_64KEY + else: + bitness_sam = winreg.KEY_WOW64_32KEY + return winreg.OpenKey(key, subkey, 0, winreg.KEY_READ | bitness_sam) + + +def _find_mono_in_reg(subkey): + try: + with _reg_open_key(winreg.HKEY_LOCAL_MACHINE, subkey) as hKey: + value, regtype = winreg.QueryValueEx(hKey, 'SdkInstallRoot') + return value + except (WindowsError, EnvironmentError) as e: + return None + +def _find_mono_in_reg_old(subkey): + try: + with _reg_open_key(winreg.HKEY_LOCAL_MACHINE, subkey) as hKey: + default_clr, regtype = winreg.QueryValueEx(hKey, 'DefaultCLR') + if default_clr: + return _find_mono_in_reg(subkey + '\\' + default_clr) + return None + except (WindowsError, EnvironmentError): + return None + + +def find_mono_root_dir(): + dir = _find_mono_in_reg(r'SOFTWARE\Mono') + if dir: + return dir + dir = _find_mono_in_reg_old(r'SOFTWARE\Novell\Mono') + if dir: + return dir + return None + + +def find_msbuild_tools_path_reg(): + try: + with _reg_open_key(winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE\Microsoft\MSBuild\ToolsVersions\4.0') as hKey: + value, regtype = winreg.QueryValueEx(hKey, 'MSBuildToolsPath') + return value + except (WindowsError, EnvironmentError) as e: + return None diff --git a/modules/mono/register_types.cpp b/modules/mono/register_types.cpp new file mode 100644 index 0000000000..2a84f0d1a6 --- /dev/null +++ b/modules/mono/register_types.cpp @@ -0,0 +1,71 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "register_types.h" + +#include "project_settings.h" + +#include "csharp_script.h" + +CSharpLanguage *script_language_cs = NULL; +ResourceFormatLoaderCSharpScript *resource_loader_cs = NULL; +ResourceFormatSaverCSharpScript *resource_saver_cs = NULL; + +_GodotSharp *_godotsharp = NULL; + +void register_mono_types() { + ClassDB::register_class<CSharpScript>(); + + _godotsharp = memnew(_GodotSharp); + + ProjectSettings::get_singleton()->add_singleton(ProjectSettings::Singleton("GodotSharp", _GodotSharp::get_singleton())); + + script_language_cs = memnew(CSharpLanguage); + script_language_cs->set_language_index(ScriptServer::get_language_count()); + ScriptServer::register_language(script_language_cs); + + resource_loader_cs = memnew(ResourceFormatLoaderCSharpScript); + ResourceLoader::add_resource_format_loader(resource_loader_cs); + resource_saver_cs = memnew(ResourceFormatSaverCSharpScript); + ResourceSaver::add_resource_format_saver(resource_saver_cs); +} + +void unregister_mono_types() { + ScriptServer::unregister_language(script_language_cs); + + if (script_language_cs) + memdelete(script_language_cs); + if (resource_loader_cs) + memdelete(resource_loader_cs); + if (resource_saver_cs) + memdelete(resource_saver_cs); + + if (_godotsharp) + memdelete(_godotsharp); +} diff --git a/modules/mono/register_types.h b/modules/mono/register_types.h new file mode 100644 index 0000000000..6cf706b944 --- /dev/null +++ b/modules/mono/register_types.h @@ -0,0 +1,31 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +void register_mono_types(); +void unregister_mono_types(); diff --git a/modules/mono/signal_awaiter_utils.cpp b/modules/mono/signal_awaiter_utils.cpp new file mode 100644 index 0000000000..012dd119b1 --- /dev/null +++ b/modules/mono/signal_awaiter_utils.cpp @@ -0,0 +1,77 @@ +/*************************************************************************/ +/* signal_awaiter_utils.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "signal_awaiter_utils.h" + +#include "mono_gd/gd_mono_utils.h" + +namespace SignalAwaiterUtils { + +Error connect_signal_awaiter(Object *p_source, const String &p_signal, Object *p_target, MonoObject *p_awaiter) { + + ERR_FAIL_NULL_V(p_source, ERR_INVALID_DATA); + ERR_FAIL_NULL_V(p_target, ERR_INVALID_DATA); + + uint32_t awaiter_handle = MonoGCHandle::make_strong_handle(p_awaiter); + Ref<SignalAwaiterHandle> sa_con = memnew(SignalAwaiterHandle(awaiter_handle)); + Vector<Variant> binds; + binds.push_back(sa_con); + Error err = p_source->connect(p_signal, p_target, "_AwaitedSignalCallback", binds, Object::CONNECT_ONESHOT); + + if (err != OK) { + // set it as completed to prevent it from calling the failure callback when deleted + // the awaiter will be aware of the failure by checking the returned error + sa_con->set_completed(true); + } + + return err; +} +} + +SignalAwaiterHandle::SignalAwaiterHandle(uint32_t p_handle) + : MonoGCHandle(p_handle) { +} + +SignalAwaiterHandle::~SignalAwaiterHandle() { + if (!completed) { + GDMonoUtils::SignalAwaiter_FailureCallback thunk = CACHED_METHOD_THUNK(SignalAwaiter, FailureCallback); + + MonoObject *awaiter = get_target(); + + if (awaiter) { + MonoObject *ex = NULL; + thunk(awaiter, &ex); + + if (ex) { + mono_print_unhandled_exception(ex); + ERR_FAIL_V(); + } + } + } +} diff --git a/modules/mono/signal_awaiter_utils.h b/modules/mono/signal_awaiter_utils.h new file mode 100644 index 0000000000..422ed4754f --- /dev/null +++ b/modules/mono/signal_awaiter_utils.h @@ -0,0 +1,53 @@ +/*************************************************************************/ +/* signal_awaiter_utils.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef SIGNAL_AWAITER_UTILS_H +#define SIGNAL_AWAITER_UTILS_H + +#include "mono_gc_handle.h" +#include "reference.h" + +namespace SignalAwaiterUtils { + +Error connect_signal_awaiter(Object *p_source, const String &p_signal, Object *p_target, MonoObject *p_awaiter); +} + +class SignalAwaiterHandle : public MonoGCHandle { + + bool completed; + +public: + _FORCE_INLINE_ bool is_completed() { return completed; } + _FORCE_INLINE_ void set_completed(bool p_completed) { completed = p_completed; } + + SignalAwaiterHandle(uint32_t p_handle); + ~SignalAwaiterHandle(); +}; + +#endif // SIGNAL_AWAITER_UTILS_H diff --git a/modules/mono/utils/mono_reg_utils.cpp b/modules/mono/utils/mono_reg_utils.cpp new file mode 100644 index 0000000000..2e90b3b716 --- /dev/null +++ b/modules/mono/utils/mono_reg_utils.cpp @@ -0,0 +1,228 @@ +/*************************************************************************/ +/* mono_reg_utils.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "mono_reg_utils.h" + +#ifdef WINDOWS_ENABLED + +#include "os/os.h" + +// Here, after os/os.h +#include <windows.h> + +namespace MonoRegUtils { + +template <int> +REGSAM bitness_sam_impl(); + +template <> +REGSAM bitness_sam_impl<4>() { + return KEY_WOW64_64KEY; +} + +template <> +REGSAM bitness_sam_impl<8>() { + return KEY_WOW64_32KEY; +} + +REGSAM _get_bitness_sam() { + return bitness_sam_impl<sizeof(size_t)>(); +} + +LONG _RegOpenKey(HKEY hKey, LPCWSTR lpSubKey, PHKEY phkResult) { + + LONG res = RegOpenKeyExW(hKey, lpSubKey, 0, KEY_READ, phkResult); + + if (res != ERROR_SUCCESS) + res = RegOpenKeyExW(hKey, lpSubKey, 0, KEY_READ | _get_bitness_sam(), phkResult); + + return res; +} + +LONG _RegKeyQueryString(HKEY hKey, const String &p_value_name, String &r_value) { + + Vector<WCHAR> buffer; + buffer.resize(512); + DWORD dwBufferSize = buffer.size(); + + LONG res = RegQueryValueExW(hKey, p_value_name.c_str(), 0, NULL, (LPBYTE)buffer.ptr(), &dwBufferSize); + + if (res == ERROR_MORE_DATA) { + // dwBufferSize now contains the actual size + Vector<WCHAR> buffer; + buffer.resize(dwBufferSize); + res = RegQueryValueExW(hKey, p_value_name.c_str(), 0, NULL, (LPBYTE)buffer.ptr(), &dwBufferSize); + } + + if (res == ERROR_SUCCESS) { + r_value = String(buffer.ptr(), buffer.size()); + } else { + r_value = String(); + } + + return res; +} + +LONG _find_mono_in_reg(const String &p_subkey, MonoRegInfo &r_info, bool p_old_reg = false) { + + HKEY hKey; + LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, p_subkey.c_str(), &hKey); + + if (res != ERROR_SUCCESS) + goto cleanup; + + if (!p_old_reg) { + res = _RegKeyQueryString(hKey, "Version", r_info.version); + if (res != ERROR_SUCCESS) + goto cleanup; + } + + res = _RegKeyQueryString(hKey, "SdkInstallRoot", r_info.install_root_dir); + if (res != ERROR_SUCCESS) + goto cleanup; + + res = _RegKeyQueryString(hKey, "FrameworkAssemblyDirectory", r_info.assembly_dir); + if (res != ERROR_SUCCESS) + goto cleanup; + + res = _RegKeyQueryString(hKey, "MonoConfigDir", r_info.config_dir); + if (res != ERROR_SUCCESS) + goto cleanup; + + if (r_info.install_root_dir.ends_with("\\")) + r_info.bin_dir = r_info.install_root_dir + "bin"; + else + r_info.bin_dir = r_info.install_root_dir + "\\bin"; + +cleanup: + RegCloseKey(hKey); + return res; +} + +LONG _find_mono_in_reg_old(const String &p_subkey, MonoRegInfo &r_info) { + + String default_clr; + + HKEY hKey; + LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, p_subkey.c_str(), &hKey); + + if (res != ERROR_SUCCESS) + goto cleanup; + + res = _RegKeyQueryString(hKey, "DefaultCLR", default_clr); + + if (res == ERROR_SUCCESS && default_clr.length()) { + r_info.version = default_clr; + res = _find_mono_in_reg(p_subkey + "\\" + default_clr, r_info, true); + } + +cleanup: + RegCloseKey(hKey); + return res; +} + +MonoRegInfo find_mono() { + + MonoRegInfo info; + + if (_find_mono_in_reg("Software\\Mono", info) == ERROR_SUCCESS) + return info; + + if (_find_mono_in_reg_old("Software\\Novell\\Mono", info) == ERROR_SUCCESS) + return info; + + ERR_PRINT("Cannot find mono in the registry"); + + return MonoRegInfo(); +} + +String find_msbuild_tools_path() { + + String msbuild_tools_path; + + // Try to find 15.0 with vswhere + + String vswhere_path = OS::get_singleton()->get_environment(sizeof(size_t) == 8 ? "ProgramFiles(x86)" : "ProgramFiles"); + vswhere_path += "\\Microsoft Visual Studio\\Installer\\vswhere.exe"; + + List<String> vswhere_args; + vswhere_args.push_back("-latest"); + vswhere_args.push_back("-requires"); + vswhere_args.push_back("Microsoft.Component.MSBuild"); + + String output; + int exit_code; + OS::get_singleton()->execute(vswhere_path, vswhere_args, true, NULL, &output, &exit_code); + + if (exit_code == 0) { + Vector<String> lines = output.split("\n"); + + for (int i = 0; i < lines.size(); i++) { + const String &line = lines[i]; + int sep_idx = line.find(":"); + + if (sep_idx > 0) { + String key = line.substr(0, sep_idx); // No need to trim + + if (key == "installationPath") { + String val = line.substr(sep_idx + 1, line.length()).strip_edges(); + + ERR_BREAK(val.empty()); + + if (!val.ends_with("\\")) { + val += "\\"; + } + + return val + "MSBuild\\15.0\\Bin"; + } + } + } + } + + // Try to find 14.0 in the Registry + + HKEY hKey; + LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\14.0", &hKey); + + if (res != ERROR_SUCCESS) + goto cleanup; + + res = _RegKeyQueryString(hKey, "MSBuildToolsPath", msbuild_tools_path); + + if (res != ERROR_SUCCESS) + goto cleanup; + +cleanup: + RegCloseKey(hKey); + + return msbuild_tools_path; +} +} // namespace MonoRegUtils + +#endif WINDOWS_ENABLED diff --git a/modules/mono/utils/mono_reg_utils.h b/modules/mono/utils/mono_reg_utils.h new file mode 100644 index 0000000000..4cc4965acb --- /dev/null +++ b/modules/mono/utils/mono_reg_utils.h @@ -0,0 +1,54 @@ +/*************************************************************************/ +/* mono_reg_utils.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef MONO_REG_UTILS_H +#define MONO_REG_UTILS_H + +#ifdef WINDOWS_ENABLED + +#include "ustring.h" + +struct MonoRegInfo { + + String version; + String install_root_dir; + String assembly_dir; + String config_dir; + String bin_dir; +}; + +namespace MonoRegUtils { + +MonoRegInfo find_mono(); +String find_msbuild_tools_path(); +} // MonoRegUtils + +#endif // WINDOWS_ENABLED + +#endif // MONO_REG_UTILS_H diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp new file mode 100644 index 0000000000..c8581f6122 --- /dev/null +++ b/modules/mono/utils/path_utils.cpp @@ -0,0 +1,111 @@ +/*************************************************************************/ +/* path_utils.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "path_utils.h" + +#include "os/dir_access.h" +#include "os/file_access.h" +#include "os/os.h" +#include "project_settings.h" + +#ifdef WINDOWS_ENABLED +#define ENV_PATH_SEP ";" +#else +#define ENV_PATH_SEP ":" +#include <limits.h> +#endif + +#include <stdlib.h> + +String path_which(const String &p_name) { + +#ifdef WINDOWS_ENABLED + Vector<String> exts = OS::get_singleton()->get_environment("PATHEXT").split(ENV_PATH_SEP, false); +#endif + Vector<String> env_path = OS::get_singleton()->get_environment("PATH").split(ENV_PATH_SEP, false); + + if (env_path.empty()) + return String(); + + for (int i = 0; i < env_path.size(); i++) { + String p = path_join(env_path[i], p_name); + + if (FileAccess::exists(p)) + return p; + +#ifdef WINDOWS_ENABLED + for (int j = 0; j < exts.size(); j++) { + String p2 = p + exts[j]; + + if (FileAccess::exists(p2)) + return p2; + } +#endif + } + + return String(); +} + +void fix_path(const String &p_path, String &r_out) { + r_out = p_path.replace("\\", "/"); + + while (true) { // in case of using 2 or more slash + String compare = r_out.replace("//", "/"); + if (r_out == compare) + break; + else + r_out = compare; + } +} + +bool rel_path_to_abs(const String &p_existing_path, String &r_abs_path) { +#ifdef WINDOWS_ENABLED + CharType ret[_MAX_PATH]; + if (_wfullpath(ret, p_existing_path.c_str(), _MAX_PATH)) { + String abspath = String(ret).replace("\\", "/"); + int pos = abspath.find(":/"); + if (pos != -1) { + r_abs_path = abspath.substr(pos - 1, abspath.length()); + } else { + r_abs_path = abspath; + } + return true; + } +#else + char ret[PATH_MAX]; + if (realpath(p_existing_path.utf8().get_data(), ret)) { + String retstr; + if (!retstr.parse_utf8(ret)) { + r_abs_path = retstr; + return true; + } + } +#endif + return false; +} diff --git a/modules/mono/utils/path_utils.h b/modules/mono/utils/path_utils.h new file mode 100644 index 0000000000..445604300d --- /dev/null +++ b/modules/mono/utils/path_utils.h @@ -0,0 +1,53 @@ +/*************************************************************************/ +/* path_utils.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef PATH_UTILS_H +#define PATH_UTILS_H + +#include "ustring.h" + +_FORCE_INLINE_ String path_join(const String &e1, const String &e2) { + return e1.plus_file(e2); +} + +_FORCE_INLINE_ String path_join(const String &e1, const String &e2, const String &e3) { + return e1.plus_file(e2).plus_file(e3); +} + +_FORCE_INLINE_ String path_join(const String &e1, const String &e2, const String &e3, const String &e4) { + return e1.plus_file(e2).plus_file(e3).plus_file(e4); +} + +String path_which(const String &p_name); + +void fix_path(const String &p_path, String &r_out); + +bool rel_path_to_abs(const String &p_existing_path, String &r_abs_path); + +#endif // PATH_UTILS_H diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp new file mode 100644 index 0000000000..de1a60dbd1 --- /dev/null +++ b/modules/mono/utils/string_utils.cpp @@ -0,0 +1,128 @@ +/*************************************************************************/ +/* string_utils.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 "string_utils.h" + +namespace { + +int sfind(const String &p_text, int p_from) { + if (p_from < 0) + return -1; + + int src_len = 2; + int len = p_text.length(); + + if (src_len == 0 || len == 0) + return -1; + + const CharType *src = p_text.c_str(); + + for (int i = p_from; i <= (len - src_len); i++) { + bool found = true; + + for (int j = 0; j < src_len; j++) { + int read_pos = i + j; + + if (read_pos >= len) { + ERR_PRINT("read_pos >= len"); + return -1; + }; + + switch (j) { + case 0: + found = src[read_pos] == '%'; + break; + case 1: { + CharType c = src[read_pos]; + found = src[read_pos] == 's' || (c >= '0' || c <= '4'); + break; + } + default: + found = false; + } + + if (!found) { + break; + } + } + + if (found) + return i; + } + + return -1; +} +} + +String sformat(const String &p_text, const Variant &p1, const Variant &p2, const Variant &p3, const Variant &p4, const Variant &p5) { + if (p_text.length() < 2) + return p_text; + + Array args; + + if (p1.get_type() != Variant::NIL) { + args.push_back(p1); + + if (p2.get_type() != Variant::NIL) { + args.push_back(p2); + + if (p3.get_type() != Variant::NIL) { + args.push_back(p3); + + if (p4.get_type() != Variant::NIL) { + args.push_back(p4); + + if (p5.get_type() != Variant::NIL) { + args.push_back(p5); + } + } + } + } + } + + String new_string; + + int findex = 0; + int search_from = 0; + int result = 0; + + while ((result = sfind(p_text, search_from)) >= 0) { + CharType c = p_text[result + 1]; + + int req_index = (c == 's' ? findex++ : c - '0'); + + new_string += p_text.substr(search_from, result - search_from); + new_string += args[req_index].operator String(); + search_from = result + 2; + } + + new_string += p_text.substr(search_from, p_text.length() - search_from); + + return new_string; +} diff --git a/modules/mono/utils/string_utils.h b/modules/mono/utils/string_utils.h new file mode 100644 index 0000000000..2f2c3c2d89 --- /dev/null +++ b/modules/mono/utils/string_utils.h @@ -0,0 +1,38 @@ +/*************************************************************************/ +/* string_utils.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#ifndef STRING_FORMAT_H +#define STRING_FORMAT_H + +#include "ustring.h" +#include "variant.h" + +String sformat(const String &p_text, const Variant &p1 = Variant(), const Variant &p2 = Variant(), const Variant &p3 = Variant(), const Variant &p4 = Variant(), const Variant &p5 = Variant()); + +#endif // STRING_FORMAT_H |