Jelajahi Sumber

Merge pull request #23162 from neikeq/cc

Proper support for namespaces and other enhancement/fixes
Ignacio Etcheverry 6 tahun lalu
induk
melakukan
d47cec43f2

+ 1 - 1
.travis.yml

@@ -38,7 +38,7 @@ matrix:
             - mono
           packages:
             - &linux_deps [libasound2-dev, libfreetype6-dev, libgl1-mesa-dev, libglu1-mesa-dev, libx11-dev, libxcursor-dev, libxi-dev, libxinerama-dev, libxrandr-dev]
-            - &linux_mono_deps [mono-devel, msbuild]
+            - &linux_mono_deps [mono-devel, msbuild, nuget]
 
         coverity_scan:
           project:

+ 100 - 8
modules/mono/SCsub

@@ -44,7 +44,7 @@ def make_cs_files_header(src, dst, version_dst):
                         if i > 0:
                             header.write(', ')
                         header.write(byte_to_str(buf[buf_idx]))
-                    inserted_files += '\tr_files.insert("' + filepath_src_rel + '", ' \
+                    inserted_files += '\tr_files.insert("' + filepath_src_rel.replace('\\', '\\\\') + '", ' \
                                         'CompressedFile(_cs_' + name + '_compressed_size, ' \
                                         '_cs_' + name + '_uncompressed_size, ' \
                                         '_cs_' + name + '_compressed));\n'
@@ -102,6 +102,75 @@ env_mono = conf.Finish()
 import os
 
 
+def find_nuget_unix():
+    import os.path
+    import sys
+
+    hint_dirs = ['/opt/novell/mono/bin']
+    if sys.platform == 'darwin':
+        hint_dirs = ['/Library/Frameworks/Mono.framework/Versions/Current/bin', '/usr/local/var/homebrew/linked/mono/bin'] + hint_dirs
+
+    for hint_dir in hint_dirs:
+        hint_path = os.path.join(hint_dir, 'nuget')
+        if os.path.isfile(hint_path):
+            return hint_path
+        elif os.path.isfile(hint_path + '.exe'):
+            return hint_path + '.exe'
+
+    for hint_dir in os.environ['PATH'].split(os.pathsep):
+        hint_dir = hint_dir.strip('"')
+        hint_path = os.path.join(hint_dir, 'nuget')
+        if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+            return hint_path
+        if os.path.isfile(hint_path + '.exe') and os.access(hint_path + '.exe', os.X_OK):
+            return hint_path + '.exe'
+
+    return None
+
+
+def find_nuget_windows():
+    import mono_reg_utils as monoreg
+
+    mono_root = ''
+    bits = env['bits']
+
+    if bits == '32':
+        if os.getenv('MONO32_PREFIX'):
+            mono_root = os.getenv('MONO32_PREFIX')
+        else:
+            mono_root = monoreg.find_mono_root_dir(bits)
+    else:
+        if os.getenv('MONO64_PREFIX'):
+            mono_root = os.getenv('MONO64_PREFIX')
+        else:
+            mono_root = monoreg.find_mono_root_dir(bits)
+
+    if mono_root:
+        mono_bin_dir = os.path.join(mono_root, 'bin')
+        nuget_mono = os.path.join(mono_bin_dir, 'nuget.bat')
+
+        if os.path.isfile(nuget_mono):
+            return nuget_mono
+
+    # Standalone NuGet
+
+    for hint_dir in os.environ['PATH'].split(os.pathsep):
+        hint_dir = hint_dir.strip('"')
+        hint_path = os.path.join(hint_dir, 'nuget.exe')
+        if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+            return hint_path
+
+    if 'NUGET_PATH' in os.environ:
+        hint_path = os.environ['NUGET_PATH']
+        if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+            return hint_path
+        hint_path = os.path.join(hint_path, 'nuget.exe')
+        if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK):
+            return hint_path
+
+    return None
+
+
 def find_msbuild_unix(filename):
     import os.path
     import sys
@@ -176,14 +245,17 @@ def mono_build_solution(source, target, env):
     import mono_reg_utils as monoreg
     from shutil import copyfile
 
-    framework_path = ''
+    sln_path = os.path.abspath(str(source[0]))
+    target_path = os.path.abspath(str(target[0]))
 
+    framework_path = ''
     msbuild_env = os.environ.copy()
 
     # Needed when running from Developer Command Prompt for VS
     if 'PLATFORM' in msbuild_env:
         del msbuild_env['PLATFORM']
 
+    # Find MSBuild
     if os.name == 'nt':
         msbuild_info = find_msbuild_windows()
         if msbuild_info is None:
@@ -213,11 +285,27 @@ def mono_build_solution(source, target, env):
 
     print('MSBuild path: ' + msbuild_path)
 
+    # Find NuGet
+    nuget_path = find_nuget_windows() if os.name == 'nt' else find_nuget_unix()
+    if nuget_path is None:
+        raise RuntimeError('Cannot find NuGet executable')
+
+    print('NuGet path: ' + nuget_path)
+
+    # Do NuGet restore
+
+    try:
+        subprocess.check_call([nuget_path, 'restore', sln_path])
+    except subprocess.CalledProcessError:
+        raise RuntimeError('GodotSharpTools: NuGet restore failed')
+
+    # Build solution
+
     build_config = 'Release'
 
     msbuild_args = [
         msbuild_path,
-        os.path.abspath(str(source[0])),
+        sln_path,
         '/p:Configuration=' + build_config,
     ]
 
@@ -227,20 +315,24 @@ def mono_build_solution(source, target, env):
     try:
         subprocess.check_call(msbuild_args, env=msbuild_env)
     except subprocess.CalledProcessError:
-        raise RuntimeError('GodotSharpTools build failed')
+        raise RuntimeError('GodotSharpTools: Build failed')
+
+    # Copy files
 
-    src_dir = os.path.abspath(os.path.join(str(source[0]), os.pardir, 'bin', build_config))
-    dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir))
+    src_dir = os.path.abspath(os.path.join(sln_path, os.pardir, 'bin', build_config))
+    dst_dir = os.path.abspath(os.path.join(target_path, os.pardir))
+    asm_file = 'GodotSharpTools.dll'
 
     if not os.path.isdir(dst_dir):
         if os.path.exists(dst_dir):
             raise RuntimeError('Target directory is a file')
         os.makedirs(dst_dir)
 
-    asm_file = 'GodotSharpTools.dll'
-
     copyfile(os.path.join(src_dir, asm_file), os.path.join(dst_dir, asm_file))
 
+    # Dependencies
+    copyfile(os.path.join(src_dir, "DotNet.Glob.dll"), os.path.join(dst_dir, "DotNet.Glob.dll"))
+
 if env['tools']:
     output_dir = Dir('#bin').abspath
     editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools')

+ 92 - 42
modules/mono/csharp_script.cpp

@@ -32,6 +32,7 @@
 
 #include <mono/metadata/threads.h>
 
+#include "core/io/json.h"
 #include "core/os/file_access.h"
 #include "core/os/os.h"
 #include "core/os/thread.h"
@@ -42,7 +43,6 @@
 #include "editor/csharp_project.h"
 #include "editor/editor_node.h"
 #include "editor/godotsharp_editor.h"
-#include "utils/string_utils.h"
 #endif
 
 #include "godotsharp_dirs.h"
@@ -50,6 +50,7 @@
 #include "mono_gd/gd_mono_marshal.h"
 #include "signal_awaiter_utils.h"
 #include "utils/macros.h"
+#include "utils/string_utils.h"
 #include "utils/thread_local.h"
 
 #define CACHED_STRING_NAME(m_var) (CSharpLanguage::get_singleton()->get_string_names().m_var)
@@ -370,6 +371,7 @@ bool CSharpLanguage::supports_builtin_mode() const {
 	return false;
 }
 
+#ifdef TOOLS_ENABLED
 static String variant_type_to_managed_name(const String &p_var_type_name) {
 
 	if (p_var_type_name.empty())
@@ -432,8 +434,7 @@ static String variant_type_to_managed_name(const String &p_var_type_name) {
 	return p_var_type_name;
 }
 
-String CSharpLanguage::make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const {
-#ifdef TOOLS_ENABLED
+String CSharpLanguage::make_function(const String &, 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
 	// - Use fully qualified name if there is ambiguity
@@ -449,10 +450,12 @@ String CSharpLanguage::make_function(const String &p_class, const String &p_name
 	s += ")\n{\n    // Replace with function body.\n}\n";
 
 	return s;
+}
 #else
+String CSharpLanguage::make_function(const String &, const String &, const PoolStringArray &) const {
 	return String();
-#endif
 }
+#endif
 
 String CSharpLanguage::_get_indentation() const {
 #ifdef TOOLS_ENABLED
@@ -822,6 +825,49 @@ void CSharpLanguage::reload_assemblies_if_needed(bool p_soft_reload) {
 }
 #endif
 
+void CSharpLanguage::project_assembly_loaded() {
+
+	scripts_metadata.clear();
+
+	String scripts_metadata_filename = "scripts_metadata.";
+
+#ifdef TOOLS_ENABLED
+	scripts_metadata_filename += Engine::get_singleton()->is_editor_hint() ? "editor" : "editor_player";
+#else
+#ifdef DEBUG_ENABLED
+	scripts_metadata_filename += "debug";
+#else
+	scripts_metadata_filename += "release";
+#endif
+#endif
+
+	String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file(scripts_metadata_filename);
+
+	if (FileAccess::exists(scripts_metadata_path)) {
+		String old_json;
+
+		Error ferr = read_all_file_utf8(scripts_metadata_path, old_json);
+		ERR_FAIL_COND(ferr != OK);
+
+		Variant old_dict_var;
+		String err_str;
+		int err_line;
+		Error json_err = JSON::parse(old_json, old_dict_var, err_str, err_line);
+		if (json_err != OK) {
+			ERR_PRINTS("Failed to parse metadata file: '" + err_str + "' (" + String::num_int64(err_line) + ")");
+			return;
+		}
+
+		scripts_metadata = old_dict_var.operator Dictionary();
+
+		print_verbose("Successfully loaded scripts metadata");
+	} else {
+		if (!Engine::get_singleton()->is_editor_hint()) {
+			ERR_PRINT("Missing scripts metadata file");
+		}
+	}
+}
+
 void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const {
 
 	p_extensions->push_back("cs");
@@ -2210,10 +2256,27 @@ bool CSharpScript::can_instance() const {
 #endif
 
 #ifdef TOOLS_ENABLED
-	return valid && (tool || ScriptServer::is_scripting_enabled());
+	bool extra_cond = tool || ScriptServer::is_scripting_enabled();
 #else
-	return valid;
+	bool extra_cond = true;
 #endif
+
+	// FIXME Need to think this through better.
+	// For tool scripts, this will never fire if the class is not found. That's because we
+	// don't know if it's a tool script if we can't find the class to access the attributes.
+	if (extra_cond && !script_class) {
+		if (GDMono::get_singleton()->get_project_assembly() == NULL) {
+			// The project assembly is not loaded
+			ERR_EXPLAIN("Cannot instance script because the project assembly is not loaded. Script: " + get_path());
+			ERR_FAIL_V(NULL);
+		} else {
+			// The project assembly is loaded, but the class could not found
+			ERR_EXPLAIN("Cannot instance script because the class '" + name + "' could not be found. Script: " + get_path());
+			ERR_FAIL_V(NULL);
+		}
+	}
+
+	return valid && extra_cond;
 }
 
 StringName CSharpScript::get_instance_base_type() const {
@@ -2319,20 +2382,6 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) {
 	CRASH_COND(!valid);
 #endif
 
-	if (!script_class) {
-		if (GDMono::get_singleton()->get_project_assembly() == NULL) {
-			// The project assembly is not loaded
-			ERR_EXPLAIN("Cannot instance script because the project assembly is not loaded. Script: " + get_path());
-			ERR_FAIL_V(NULL);
-		} else {
-			// The project assembly is loaded, but the class could not found
-			ERR_EXPLAIN("Cannot instance script because the class '" + name + "' could not be found. Script: " + get_path());
-			ERR_FAIL_V(NULL);
-		}
-	}
-
-	ERR_FAIL_COND_V(!valid, NULL);
-
 	if (native) {
 		String native_name = native->get_name();
 		if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) {
@@ -2420,7 +2469,23 @@ Error CSharpScript::reload(bool p_keep_state) {
 	GDMonoAssembly *project_assembly = GDMono::get_singleton()->get_project_assembly();
 
 	if (project_assembly) {
-		script_class = project_assembly->get_object_derived_class(name);
+		const Variant *script_metadata_var = CSharpLanguage::get_singleton()->get_scripts_metadata().getptr(get_path());
+		if (script_metadata_var) {
+			Dictionary script_metadata = script_metadata_var->operator Dictionary()["class"];
+			const Variant *namespace_ = script_metadata.getptr("namespace");
+			const Variant *class_name = script_metadata.getptr("class_name");
+			ERR_FAIL_NULL_V(namespace_, ERR_BUG);
+			ERR_FAIL_NULL_V(class_name, ERR_BUG);
+			GDMonoClass *klass = project_assembly->get_class(namespace_->operator String(), class_name->operator String());
+			if (klass) {
+				bool obj_type = CACHED_CLASS(GodotObject)->is_assignable_from(klass);
+				ERR_FAIL_COND_V(!obj_type, ERR_BUG);
+				script_class = klass;
+			}
+		} else {
+			// Missing script metadata. Fallback to legacy method
+			script_class = project_assembly->get_object_derived_class(name);
+		}
 
 		valid = script_class != NULL;
 
@@ -2548,29 +2613,14 @@ int CSharpScript::get_member_line(const StringName &p_member) const {
 
 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);
+	Error ferr = read_all_file_utf8(p_path, source);
+	if (ferr != OK) {
+		if (ferr == ERR_INVALID_DATA) {
+			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(ferr);
 	}
 
-	source = s;
-
 #ifdef TOOLS_ENABLED
 	source_changed_cache = true;
 #endif

+ 7 - 1
modules/mono/csharp_script.h

@@ -67,7 +67,7 @@ class CSharpScript : public Script {
 
 	friend class CSharpInstance;
 	friend class CSharpLanguage;
-	friend class CSharpScriptDepSort;
+	friend struct CSharpScriptDepSort;
 
 	bool tool;
 	bool valid;
@@ -275,6 +275,8 @@ class CSharpLanguage : public ScriptLanguage {
 
 	int lang_idx;
 
+	Dictionary scripts_metadata;
+
 public:
 	StringNameCache string_names;
 
@@ -295,6 +297,10 @@ public:
 	void reload_assemblies_if_needed(bool p_soft_reload);
 #endif
 
+	void project_assembly_loaded();
+
+	_FORCE_INLINE_ const Dictionary &get_scripts_metadata() { return scripts_metadata; }
+
 	virtual String get_name() const;
 
 	/* LANGUAGE FUNCTIONS */

+ 6 - 0
modules/mono/editor/GodotSharpTools/GodotSharpTools.csproj

@@ -31,6 +31,9 @@
     <Reference Include="System" />
     <Reference Include="Microsoft.Build" />
     <Reference Include="Microsoft.Build.Framework" />
+    <Reference Include="DotNet.Glob, Version=2.1.1.0, Culture=neutral, PublicKeyToken=b68cc888b4f632d1, processorArchitecture=MSIL">
+      <HintPath>packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath>
+    </Reference>
   </ItemGroup>
   <ItemGroup>
     <Compile Include="StringExtensions.cs" />
@@ -43,5 +46,8 @@
     <Compile Include="Utils\OS.cs" />
     <Compile Include="Editor\GodotSharpExport.cs" />
   </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
 </Project>

+ 12 - 4
modules/mono/editor/GodotSharpTools/Project/ProjectExtensions.cs

@@ -1,4 +1,5 @@
 using System;
+using DotNet.Globbing;
 using Microsoft.Build.Construction;
 
 namespace GodotSharpTools.Project
@@ -7,7 +8,10 @@ namespace GodotSharpTools.Project
     {
         public static bool HasItem(this ProjectRootElement root, string itemType, string include)
         {
-            string includeNormalized = include.NormalizePath();
+            GlobOptions globOptions = new GlobOptions();
+            globOptions.Evaluation.CaseInsensitive = false;
+
+            string normalizedInclude = include.NormalizePath();
 
             foreach (var itemGroup in root.ItemGroups)
             {
@@ -16,10 +20,14 @@ namespace GodotSharpTools.Project
 
                 foreach (var item in itemGroup.Items)
                 {
-                    if (item.ItemType == itemType)
+                    if (item.ItemType != itemType)
+                        continue;
+
+                    var glob = Glob.Parse(item.Include.NormalizePath(), globOptions);
+
+                    if (glob.IsMatch(normalizedInclude))
                     {
-                        if (item.Include.NormalizePath() == includeNormalized)
-                            return true;
+                        return true;
                     }
                 }
             }

+ 5 - 0
modules/mono/editor/GodotSharpTools/Project/ProjectGenerator.cs

@@ -6,6 +6,9 @@ namespace GodotSharpTools.Project
 {
     public static class ProjectGenerator
     {
+        const string CoreApiProjectGuid = "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}";
+        const string EditorApiProjectGuid = "{8FBEC238-D944-4074-8548-B3B524305905}";
+
         public static string GenCoreApiProject(string dir, string[] compileItems)
         {
             string path = Path.Combine(dir, CoreApiProject + ".csproj");
@@ -15,6 +18,7 @@ namespace GodotSharpTools.Project
 
             mainGroup.AddProperty("DocumentationFile", Path.Combine("$(OutputPath)", "$(AssemblyName).xml"));
             mainGroup.SetProperty("RootNamespace", "Godot");
+            mainGroup.SetProperty("ProjectGuid", CoreApiProjectGuid);
 
             GenAssemblyInfoFile(root, dir, CoreApiProject,
                                 new string[] { "[assembly: InternalsVisibleTo(\"" + EditorApiProject + "\")]" },
@@ -39,6 +43,7 @@ namespace GodotSharpTools.Project
 
             mainGroup.AddProperty("DocumentationFile", Path.Combine("$(OutputPath)", "$(AssemblyName).xml"));
             mainGroup.SetProperty("RootNamespace", "Godot");
+            mainGroup.SetProperty("ProjectGuid", EditorApiProjectGuid);
 
             GenAssemblyInfoFile(root, dir, EditorApiProject);
 

+ 56 - 2
modules/mono/editor/GodotSharpTools/Project/ProjectUtils.cs

@@ -1,5 +1,6 @@
-using System;
+using System.Collections.Generic;
 using System.IO;
+using DotNet.Globbing;
 using Microsoft.Build.Construction;
 
 namespace GodotSharpTools.Project
@@ -10,8 +11,61 @@ namespace GodotSharpTools.Project
         {
             var dir = Directory.GetParent(projectPath).FullName;
             var root = ProjectRootElement.Open(projectPath);
-            if (root.AddItemChecked(itemType, include.RelativeToPath(dir).Replace("/", "\\")))
+            var normalizedInclude = include.RelativeToPath(dir).Replace("/", "\\");
+
+            if (root.AddItemChecked(itemType, normalizedInclude))
                 root.Save();
         }
+
+        private static string[] GetAllFilesRecursive(string rootDirectory, string mask)
+        {
+            string[] files = Directory.GetFiles(rootDirectory, mask, SearchOption.AllDirectories);
+
+            // We want relative paths
+            for (int i = 0; i < files.Length; i++) {
+                files[i] = files[i].RelativeToPath(rootDirectory);
+            }
+
+            return files;
+        }
+
+        public static string[] GetIncludeFiles(string projectPath, string itemType)
+        {
+            var result = new List<string>();
+            var existingFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs");
+
+            GlobOptions globOptions = new GlobOptions();
+            globOptions.Evaluation.CaseInsensitive = false;
+
+            var root = ProjectRootElement.Open(projectPath);
+
+            foreach (var itemGroup in root.ItemGroups)
+            {
+                if (itemGroup.Condition.Length != 0)
+                    continue;
+
+                foreach (var item in itemGroup.Items)
+                {
+                    if (item.ItemType != itemType)
+                        continue;
+
+                    string normalizedInclude = item.Include.NormalizePath();
+
+                    var glob = Glob.Parse(normalizedInclude, globOptions);
+
+                    // TODO Check somehow if path has no blog to avoid the following loop...
+
+                    foreach (var existingFile in existingFiles)
+                    {
+                        if (glob.IsMatch(existingFile))
+                        {
+                            result.Add(existingFile);
+                        }
+                    }
+                }
+            }
+
+            return result.ToArray();
+        }
     }
 }

+ 3 - 1
modules/mono/editor/GodotSharpTools/StringExtensions.cs

@@ -36,7 +36,9 @@ namespace GodotSharpTools
 
         public static bool IsAbsolutePath(this string path)
         {
-            return path.StartsWith("/") || path.StartsWith("\\") || path.StartsWith(driveRoot);
+            return path.StartsWith("/", StringComparison.Ordinal) ||
+                       path.StartsWith("\\", StringComparison.Ordinal) ||
+                       path.StartsWith(driveRoot, StringComparison.Ordinal);
         }
 
         public static string CsvEscape(this string value, char delimiter = ',')

+ 4 - 0
modules/mono/editor/GodotSharpTools/packages.config

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="DotNet.Glob" version="2.1.1" targetFramework="net45" />
+</packages>

+ 109 - 0
modules/mono/editor/csharp_project.cpp

@@ -30,11 +30,15 @@
 
 #include "csharp_project.h"
 
+#include "core/io/json.h"
 #include "core/os/os.h"
 #include "core/project_settings.h"
 
+#include "../csharp_script.h"
 #include "../mono_gd/gd_mono_class.h"
 #include "../mono_gd/gd_mono_marshal.h"
+#include "../utils/string_utils.h"
+#include "script_class_parser.h"
 
 namespace CSharpProject {
 
@@ -118,4 +122,109 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str
 		ERR_FAIL();
 	}
 }
+
+Error generate_scripts_metadata(const String &p_project_path, const String &p_output_path) {
+
+	_GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
+
+	GDMonoClass *project_utils = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectUtils");
+
+	void *args[2] = {
+		GDMonoMarshal::mono_string_from_godot(p_project_path),
+		GDMonoMarshal::mono_string_from_godot("Compile")
+	};
+
+	MonoException *exc = NULL;
+	MonoArray *ret = (MonoArray *)project_utils->get_method("GetIncludeFiles", 2)->invoke_raw(NULL, args, &exc);
+
+	if (exc) {
+		GDMonoUtils::debug_print_unhandled_exception(exc);
+		ERR_FAIL_V(FAILED);
+	}
+
+	PoolStringArray project_files = GDMonoMarshal::mono_array_to_PoolStringArray(ret);
+	PoolStringArray::Read r = project_files.read();
+
+	Dictionary old_dict = CSharpLanguage::get_singleton()->get_scripts_metadata();
+	Dictionary new_dict;
+
+	for (int i = 0; i < project_files.size(); i++) {
+		const String &project_file = ("res://" + r[i]).simplify_path();
+
+		uint64_t modified_time = FileAccess::get_modified_time(project_file);
+
+		const Variant *old_file_var = old_dict.getptr(project_file);
+		if (old_file_var) {
+			Dictionary old_file_dict = old_file_var->operator Dictionary();
+
+			if (old_file_dict["modified_time"].operator uint64_t() == modified_time) {
+				// No changes so no need to parse again
+				new_dict[project_file] = old_file_dict;
+				continue;
+			}
+		}
+
+		ScriptClassParser scp;
+		Error err = scp.parse_file(project_file);
+		if (err != OK) {
+			ERR_EXPLAIN("Failed to determine namespace and class for script: " + project_file);
+			ERR_FAIL_V(err);
+		}
+
+		Vector<ScriptClassParser::ClassDecl> classes = scp.get_classes();
+
+		bool found = false;
+		Dictionary class_dict;
+
+		String search_name = project_file.get_file().get_basename();
+
+		for (int j = 0; j < classes.size(); j++) {
+			const ScriptClassParser::ClassDecl &class_decl = classes[j];
+
+			if (class_decl.base.size() == 0)
+				continue; // Does not inherit nor implement anything, so it can't be a script class
+
+			String class_cmp;
+
+			if (class_decl.nested) {
+				class_cmp = class_decl.name.get_slice(".", class_decl.name.get_slice_count(".") - 1);
+			} else {
+				class_cmp = class_decl.name;
+			}
+
+			if (class_cmp != search_name)
+				continue;
+
+			class_dict["namespace"] = class_decl.namespace_;
+			class_dict["class_name"] = class_decl.name;
+			class_dict["nested"] = class_decl.nested;
+
+			found = true;
+			break;
+		}
+
+		if (found) {
+			Dictionary file_dict;
+			file_dict["modified_time"] = modified_time;
+			file_dict["class"] = class_dict;
+			new_dict[project_file] = file_dict;
+		}
+	}
+
+	if (new_dict.size()) {
+		String json = JSON::print(new_dict, "", false);
+
+		Error ferr;
+		FileAccess *f = FileAccess::open(p_output_path, FileAccess::WRITE, &ferr);
+		ERR_EXPLAIN("Cannot open file for writing: " + p_output_path);
+		ERR_FAIL_COND_V(ferr != OK, ferr);
+		f->store_string(json);
+		f->flush();
+		f->close();
+		memdelete(f);
+	}
+
+	return OK;
+}
+
 } // namespace CSharpProject

+ 3 - 0
modules/mono/editor/csharp_project.h

@@ -40,6 +40,9 @@ String generate_editor_api_project(const String &p_dir, const String &p_core_dll
 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);
+
+Error generate_scripts_metadata(const String &p_project_path, const String &p_output_path);
+
 } // namespace CSharpProject
 
 #endif // CSHARP_PROJECT_H

+ 14 - 3
modules/mono/editor/godotsharp_builds.cpp

@@ -39,6 +39,7 @@
 #include "../mono_gd/gd_mono_marshal.h"
 #include "../utils/path_utils.h"
 #include "bindings_generator.h"
+#include "csharp_project.h"
 #include "godotsharp_editor.h"
 
 #define PROP_NAME_MSBUILD_MONO "MSBuild (Mono)"
@@ -268,7 +269,7 @@ bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String &
 	if (!FileAccess::exists(assembly_dst) ||
 			FileAccess::get_modified_time(assembly_src) > FileAccess::get_modified_time(assembly_dst) ||
 			GDMono::get_singleton()->metadata_is_api_assembly_invalidated(p_api_type)) {
-		DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+		DirAccessRef 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)
@@ -280,8 +281,6 @@ bool GodotSharpBuilds::copy_api_assembly(const String &p_src_dir, const String &
 
 		Error err = da->copy(assembly_src, assembly_dst);
 
-		memdelete(da);
-
 		if (err != OK) {
 			show_build_error_dialog("Failed to copy " + assembly_file);
 			return false;
@@ -398,6 +397,18 @@ bool GodotSharpBuilds::build_project_blocking(const String &p_config) {
 
 bool GodotSharpBuilds::editor_build_callback() {
 
+	String scripts_metadata_path_editor = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor");
+	String scripts_metadata_path_player = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor_player");
+
+	Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path_editor);
+	ERR_FAIL_COND_V(metadata_err != OK, false);
+
+	DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+	Error copy_err = da->copy(scripts_metadata_path_editor, scripts_metadata_path_player);
+
+	ERR_EXPLAIN("Failed to copy scripts metadata file");
+	ERR_FAIL_COND_V(copy_err != OK, false);
+
 	return build_project_blocking("Tools");
 }
 

+ 13 - 6
modules/mono/editor/godotsharp_export.cpp

@@ -37,6 +37,7 @@
 #include "../godotsharp_dirs.h"
 #include "../mono_gd/gd_mono_class.h"
 #include "../mono_gd/gd_mono_marshal.h"
+#include "csharp_project.h"
 #include "godotsharp_builds.h"
 
 static MonoString *godot_icall_GodotSharpExport_GetTemplatesDir() {
@@ -86,6 +87,12 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug
 
 	String build_config = p_debug ? "Debug" : "Release";
 
+	String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata." + String(p_debug ? "debug" : "release"));
+	Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path);
+	ERR_FAIL_COND(metadata_err != OK);
+
+	ERR_FAIL_COND(!_add_file(scripts_metadata_path, scripts_metadata_path));
+
 	ERR_FAIL_COND(!GodotSharpBuilds::build_project_blocking(build_config));
 
 	// Add dependency assemblies
@@ -124,7 +131,7 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug
 	for (Map<String, String>::Element *E = dependencies.front(); E; E = E->next()) {
 		String depend_src_path = E->value();
 		String depend_dst_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(depend_src_path.get_file());
-		ERR_FAIL_COND(!_add_assembly(depend_src_path, depend_dst_path));
+		ERR_FAIL_COND(!_add_file(depend_src_path, depend_dst_path));
 	}
 
 	// Mono specific export template extras (data dir)
@@ -155,7 +162,7 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug
 	}
 }
 
-bool GodotSharpExport::_add_assembly(const String &p_src_path, const String &p_dst_path) {
+bool GodotSharpExport::_add_file(const String &p_src_path, const String &p_dst_path, bool p_remap) {
 
 	FileAccessRef f = FileAccess::open(p_src_path, FileAccess::READ);
 	ERR_FAIL_COND_V(!f, false);
@@ -164,7 +171,7 @@ bool GodotSharpExport::_add_assembly(const String &p_src_path, const String &p_d
 	data.resize(f->get_len());
 	f->get_buffer(data.ptrw(), data.size());
 
-	add_file(p_dst_path, data, false);
+	add_file(p_dst_path, data, p_remap);
 
 	return true;
 }
@@ -191,21 +198,21 @@ Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, c
 			if (has_extension) {
 				path = search_dir.plus_file(ref_name);
 				if (FileAccess::exists(path)) {
-					GDMono::get_singleton()->load_assembly_from(ref_name.get_basename(), search_dir, ref_aname, &ref_assembly, true);
+					GDMono::get_singleton()->load_assembly_from(ref_name.get_basename(), path, &ref_assembly, true);
 					if (ref_assembly != NULL)
 						break;
 				}
 			} else {
 				path = search_dir.plus_file(ref_name + ".dll");
 				if (FileAccess::exists(path)) {
-					GDMono::get_singleton()->load_assembly_from(ref_name, search_dir, ref_aname, &ref_assembly, true);
+					GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true);
 					if (ref_assembly != NULL)
 						break;
 				}
 
 				path = search_dir.plus_file(ref_name + ".exe");
 				if (FileAccess::exists(path)) {
-					GDMono::get_singleton()->load_assembly_from(ref_name, search_dir, ref_aname, &ref_assembly, true);
+					GDMono::get_singleton()->load_assembly_from(ref_name, path, &ref_assembly, true);
 					if (ref_assembly != NULL)
 						break;
 				}

+ 1 - 1
modules/mono/editor/godotsharp_export.h

@@ -41,7 +41,7 @@ class GodotSharpExport : public EditorExportPlugin {
 
 	MonoAssemblyName *aname_prealloc;
 
-	bool _add_assembly(const String &p_src_path, const String &p_dst_path);
+	bool _add_file(const String &p_src_path, const String &p_dst_path, bool p_remap = false);
 
 	Error _get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Map<String, String> &r_dependencies);
 

+ 6 - 0
modules/mono/editor/mono_bottom_panel.cpp

@@ -31,6 +31,8 @@
 #include "mono_bottom_panel.h"
 
 #include "../csharp_script.h"
+#include "../godotsharp_dirs.h"
+#include "csharp_project.h"
 #include "godotsharp_editor.h"
 
 MonoBottomPanel *MonoBottomPanel::singleton = NULL;
@@ -148,6 +150,10 @@ void MonoBottomPanel::_errors_toggled(bool p_pressed) {
 
 void MonoBottomPanel::_build_project_pressed() {
 
+	String scripts_metadata_path = GodotSharpDirs::get_res_metadata_dir().plus_file("scripts_metadata.editor");
+	Error metadata_err = CSharpProject::generate_scripts_metadata(GodotSharpDirs::get_project_csproj_path(), scripts_metadata_path);
+	ERR_FAIL_COND(metadata_err != OK);
+
 	GodotSharpBuilds::get_singleton()->build_project_blocking("Tools");
 
 	MonoReloadNode::get_singleton()->restart_reload_timer();

+ 1 - 1
modules/mono/editor/net_solution.cpp

@@ -52,7 +52,7 @@
 
 #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 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" \

+ 486 - 0
modules/mono/editor/script_class_parser.cpp

@@ -0,0 +1,486 @@
+#include "script_class_parser.h"
+
+#include "core/map.h"
+#include "core/os/os.h"
+
+#include "../utils/string_utils.h"
+
+ScriptClassParser::Token ScriptClassParser::get_token() {
+
+	while (true) {
+		switch (code[idx]) {
+			case '\n': {
+				line++;
+				idx++;
+				break;
+			};
+			case 0: {
+				return TK_EOF;
+			} break;
+			case '{': {
+				idx++;
+				return TK_CURLY_BRACKET_OPEN;
+			};
+			case '}': {
+				idx++;
+				return TK_CURLY_BRACKET_CLOSE;
+			};
+			case '[': {
+				idx++;
+				return TK_BRACKET_OPEN;
+			};
+			case ']': {
+				idx++;
+				return TK_BRACKET_CLOSE;
+			};
+			case '<': {
+				idx++;
+				return TK_OP_LESS;
+			};
+			case '>': {
+				idx++;
+				return TK_OP_GREATER;
+			};
+			case ':': {
+				idx++;
+				return TK_COLON;
+			};
+			case ',': {
+				idx++;
+				return TK_COMMA;
+			};
+			case '.': {
+				idx++;
+				return TK_PERIOD;
+			};
+			case '#': {
+				//compiler directive
+				while (code[idx] != '\n' && code[idx] != 0) {
+					idx++;
+				}
+				continue;
+			} break;
+			case '/': {
+				switch (code[idx + 1]) {
+					case '*': { // block comment
+						idx += 2;
+						while (true) {
+							if (code[idx] == 0) {
+								error_str = "Unterminated comment";
+								error = true;
+								return TK_ERROR;
+							} else if (code[idx] == '*' && code[idx + 1] == '/') {
+								idx += 2;
+								break;
+							} else if (code[idx] == '\n') {
+								line++;
+							}
+
+							idx++;
+						}
+
+					} break;
+					case '/': { // line comment skip
+						while (code[idx] != '\n' && code[idx] != 0) {
+							idx++;
+						}
+
+					} break;
+					default: {
+						value = "/";
+						idx++;
+						return TK_SYMBOL;
+					}
+				}
+
+				continue; // a comment
+			} break;
+			case '\'':
+			case '"': {
+				bool verbatim = idx != 0 && code[idx - 1] == '@';
+
+				CharType begin_str = code[idx];
+				idx++;
+				String tk_string = String();
+				while (true) {
+					if (code[idx] == 0) {
+						error_str = "Unterminated String";
+						error = true;
+						return TK_ERROR;
+					} else if (code[idx] == begin_str) {
+						if (verbatim && code[idx + 1] == '"') { // `""` is verbatim string's `\"`
+							idx += 2; // skip next `"` as well
+							continue;
+						}
+
+						idx += 1;
+						break;
+					} else if (code[idx] == '\\' && !verbatim) {
+						//escaped characters...
+						idx++;
+						CharType next = code[idx];
+						if (next == 0) {
+							error_str = "Unterminated String";
+							error = true;
+							return TK_ERROR;
+						}
+						CharType res = 0;
+
+						switch (next) {
+							case 'b': res = 8; break;
+							case 't': res = 9; break;
+							case 'n': res = 10; break;
+							case 'f': res = 12; break;
+							case 'r':
+								res = 13;
+								break;
+							case '\"': res = '\"'; break;
+							case '\\':
+								res = '\\';
+								break;
+							default: {
+								res = next;
+							} break;
+						}
+
+						tk_string += res;
+
+					} else {
+						if (code[idx] == '\n')
+							line++;
+						tk_string += code[idx];
+					}
+					idx++;
+				}
+
+				value = tk_string;
+
+				return TK_STRING;
+			} break;
+			default: {
+				if (code[idx] <= 32) {
+					idx++;
+					break;
+				}
+
+				if ((code[idx] >= 33 && code[idx] <= 47) || (code[idx] >= 58 && code[idx] <= 63) || (code[idx] >= 91 && code[idx] <= 94) || code[idx] == 96 || (code[idx] >= 123 && code[idx] <= 127)) {
+					value = String::chr(code[idx]);
+					idx++;
+					return TK_SYMBOL;
+				}
+
+				if (code[idx] == '-' || (code[idx] >= '0' && code[idx] <= '9')) {
+					//a number
+					const CharType *rptr;
+					double number = String::to_double(&code[idx], &rptr);
+					idx += (rptr - &code[idx]);
+					value = number;
+					return TK_NUMBER;
+
+				} else if ((code[idx] == '@' && code[idx + 1] != '"') || code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || code[idx] > 127) {
+					String id;
+
+					id += code[idx];
+					idx++;
+
+					while (code[idx] == '_' || (code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || (code[idx] >= '0' && code[idx] <= '9') || code[idx] > 127) {
+						id += code[idx];
+						idx++;
+					}
+
+					value = id;
+					return TK_IDENTIFIER;
+				} else if (code[idx] == '@' && code[idx + 1] == '"') {
+					// begin of verbatim string
+					idx++;
+				} else {
+					error_str = "Unexpected character.";
+					error = true;
+					return TK_ERROR;
+				}
+			}
+		}
+	}
+}
+
+Error ScriptClassParser::_skip_type_parameters() {
+
+	Token tk;
+
+	while (true) {
+		tk = get_token();
+
+		if (tk == TK_IDENTIFIER) {
+			tk = get_token();
+
+			if (tk == TK_OP_LESS) {
+				Error err = _skip_type_parameters();
+				if (err)
+					return err;
+				continue;
+			} else if (tk != TK_COMMA) {
+				error_str = "Unexpected token: " + itos(tk);
+				error = true;
+				return ERR_PARSE_ERROR;
+			}
+		} else if (tk == TK_OP_LESS) {
+			error_str = "Expected identifier before `<`.";
+			error = true;
+			return ERR_PARSE_ERROR;
+		} else if (tk == TK_OP_GREATER) {
+			return OK;
+		} else {
+			error_str = "Unexpected token: " + itos(tk);
+			error = true;
+			return ERR_PARSE_ERROR;
+		}
+	}
+}
+
+Error ScriptClassParser::_parse_class_base(Vector<String> &r_base) {
+
+	Token tk;
+
+	while (true) {
+		tk = get_token();
+
+		if (tk == TK_IDENTIFIER) {
+			bool generic = false;
+
+			String name = value;
+
+			tk = get_token();
+
+			if (tk == TK_OP_LESS) {
+				generic = true;
+				Error err = _skip_type_parameters();
+				if (err)
+					return err;
+			} else if (tk == TK_COMMA) {
+				Error err = _parse_class_base(r_base);
+				if (err)
+					return err;
+			} else if (tk != TK_CURLY_BRACKET_OPEN) {
+				error_str = "Unexpected token: " + itos(tk);
+				error = true;
+				return ERR_PARSE_ERROR;
+			}
+
+			r_base.push_back(!generic ? name : String()); // no generics, please
+
+			return OK;
+		} else {
+			error_str = "Unexpected token: " + itos(tk);
+			error = true;
+			return ERR_PARSE_ERROR;
+		}
+	}
+}
+
+Error ScriptClassParser::_parse_namespace_name(String &r_name, int &r_curly_stack) {
+
+	Token tk = get_token();
+
+	if (tk == TK_IDENTIFIER) {
+		r_name += String(value);
+	} else {
+		error_str = "Unexpected token: " + itos(tk);
+		error = true;
+		return ERR_PARSE_ERROR;
+	}
+
+	tk = get_token();
+
+	if (tk == TK_PERIOD) {
+		r_name += ".";
+		return _parse_namespace_name(r_name, r_curly_stack);
+	} else if (tk == TK_CURLY_BRACKET_OPEN) {
+		r_curly_stack++;
+		return OK;
+	} else {
+		error_str = "Unexpected token: " + itos(tk);
+		error = true;
+		return ERR_PARSE_ERROR;
+	}
+}
+
+Error ScriptClassParser::parse(const String &p_code) {
+
+	code = p_code;
+	idx = 0;
+	line = 0;
+	error_str = String();
+	error = false;
+	value = Variant();
+	classes.clear();
+
+	Token tk = get_token();
+
+	Map<int, NameDecl> name_stack;
+	int curly_stack = 0;
+	int type_curly_stack = 0;
+
+	while (!error && tk != TK_EOF) {
+		if (tk == TK_IDENTIFIER && String(value) == "class") {
+			tk = get_token();
+
+			if (tk == TK_IDENTIFIER) {
+				String name = value;
+				int at_level = type_curly_stack;
+
+				ClassDecl class_decl;
+
+				for (Map<int, NameDecl>::Element *E = name_stack.front(); E; E = E->next()) {
+					const NameDecl &name_decl = E->value();
+
+					if (name_decl.type == NameDecl::NAMESPACE_DECL) {
+						if (E != name_stack.front())
+							class_decl.namespace_ += ".";
+						class_decl.namespace_ += name_decl.name;
+					} else {
+						class_decl.name += name_decl.name + ".";
+					}
+				}
+
+				class_decl.name += name;
+				class_decl.nested = type_curly_stack > 0;
+
+				bool generic = false;
+
+				while (true) {
+					tk = get_token();
+
+					if (tk == TK_COLON) {
+						Error err = _parse_class_base(class_decl.base);
+						if (err)
+							return err;
+
+						curly_stack++;
+						type_curly_stack++;
+
+						break;
+					} else if (tk == TK_CURLY_BRACKET_OPEN) {
+						curly_stack++;
+						type_curly_stack++;
+						break;
+					} else if (tk == TK_OP_LESS && !generic) {
+						generic = true;
+
+						Error err = _skip_type_parameters();
+						if (err)
+							return err;
+					} else {
+						error_str = "Unexpected token: " + itos(tk);
+						error = true;
+						return ERR_PARSE_ERROR;
+					}
+				}
+
+				NameDecl name_decl;
+				name_decl.name = name;
+				name_decl.type = NameDecl::CLASS_DECL;
+				name_stack[at_level] = name_decl;
+
+				if (!generic) { // no generics, thanks
+					classes.push_back(class_decl);
+				} else if (OS::get_singleton()->is_stdout_verbose()) {
+					String full_name = class_decl.namespace_;
+					if (full_name.length())
+						full_name += ".";
+					full_name += class_decl.name;
+					OS::get_singleton()->print(String("Ignoring generic class declaration: " + class_decl.name).utf8());
+				}
+			}
+		} else if (tk == TK_IDENTIFIER && String(value) == "struct") {
+			String name;
+			int at_level = type_curly_stack;
+			while (true) {
+				tk = get_token();
+				if (tk == TK_IDENTIFIER && name.empty()) {
+					name = String(value);
+				} else if (tk == TK_CURLY_BRACKET_OPEN) {
+					if (name.empty()) {
+						error_str = "Expected identifier after keyword `struct`. Found `{`.";
+						error = true;
+						return ERR_PARSE_ERROR;
+					}
+
+					curly_stack++;
+					type_curly_stack++;
+					break;
+				} else if (tk == TK_EOF) {
+					error_str = "Expected `{` after struct decl. Found `EOF`.";
+					error = true;
+					return ERR_PARSE_ERROR;
+				}
+			}
+
+			NameDecl name_decl;
+			name_decl.name = name;
+			name_decl.type = NameDecl::STRUCT_DECL;
+			name_stack[at_level] = name_decl;
+		} else if (tk == TK_IDENTIFIER && String(value) == "namespace") {
+			if (type_curly_stack > 0) {
+				error_str = "Found namespace nested inside type.";
+				error = true;
+				return ERR_PARSE_ERROR;
+			}
+
+			String name;
+			int at_level = curly_stack;
+
+			Error err = _parse_namespace_name(name, curly_stack);
+			if (err)
+				return err;
+
+			NameDecl name_decl;
+			name_decl.name = name;
+			name_decl.type = NameDecl::NAMESPACE_DECL;
+			name_stack[at_level] = name_decl;
+		} else if (tk == TK_CURLY_BRACKET_OPEN) {
+			curly_stack++;
+		} else if (tk == TK_CURLY_BRACKET_CLOSE) {
+			curly_stack--;
+			if (name_stack.has(curly_stack)) {
+				if (name_stack[curly_stack].type != NameDecl::NAMESPACE_DECL)
+					type_curly_stack--;
+				name_stack.erase(curly_stack);
+			}
+		}
+
+		tk = get_token();
+	}
+
+	if (!error && tk == TK_EOF && curly_stack > 0) {
+		error_str = "Reached EOF with missing close curly brackets.";
+		error = true;
+	}
+
+	if (error)
+		return ERR_PARSE_ERROR;
+
+	return OK;
+}
+
+Error ScriptClassParser::parse_file(const String &p_filepath) {
+
+	String source;
+
+	Error ferr = read_all_file_utf8(p_filepath, source);
+	if (ferr != OK) {
+		if (ferr == ERR_INVALID_DATA) {
+			ERR_EXPLAIN("File '" + p_filepath + "' contains invalid unicode (utf-8), so it was not loaded. Please ensure that scripts are saved in valid utf-8 unicode.");
+		}
+		ERR_FAIL_V(ferr);
+	}
+
+	return parse(source);
+}
+
+String ScriptClassParser::get_error() {
+	return error_str;
+}
+
+Vector<ScriptClassParser::ClassDecl> ScriptClassParser::get_classes() {
+	return classes;
+}

+ 74 - 0
modules/mono/editor/script_class_parser.h

@@ -0,0 +1,74 @@
+#ifndef SCRIPT_CLASS_PARSER_H
+#define SCRIPT_CLASS_PARSER_H
+
+#include "core/ustring.h"
+#include "core/variant.h"
+#include "core/vector.h"
+
+class ScriptClassParser {
+
+public:
+	struct NameDecl {
+		enum Type {
+			NAMESPACE_DECL,
+			CLASS_DECL,
+			STRUCT_DECL
+		};
+
+		String name;
+		Type type;
+	};
+
+	struct ClassDecl {
+		String name;
+		String namespace_;
+		Vector<String> base;
+		bool nested;
+		bool has_script_attr;
+	};
+
+private:
+	String code;
+	int idx;
+	int line;
+	String error_str;
+	bool error;
+	Variant value;
+
+	Vector<ClassDecl> classes;
+
+	enum Token {
+		TK_BRACKET_OPEN,
+		TK_BRACKET_CLOSE,
+		TK_CURLY_BRACKET_OPEN,
+		TK_CURLY_BRACKET_CLOSE,
+		TK_PERIOD,
+		TK_COLON,
+		TK_COMMA,
+		TK_SYMBOL,
+		TK_IDENTIFIER,
+		TK_STRING,
+		TK_NUMBER,
+		TK_OP_LESS,
+		TK_OP_GREATER,
+		TK_EOF,
+		TK_ERROR
+	};
+
+	Token get_token();
+
+	Error _skip_type_parameters();
+
+	Error _parse_class_base(Vector<String> &r_base);
+	Error _parse_namespace_name(String &r_name, int &r_curly_stack);
+
+public:
+	Error parse(const String &p_code);
+	Error parse_file(const String &p_filepath);
+
+	String get_error();
+
+	Vector<ClassDecl> get_classes();
+};
+
+#endif // SCRIPT_CLASS_PARSER_H

+ 47 - 15
modules/mono/mono_gd/gd_mono.cpp

@@ -377,34 +377,24 @@ GDMonoAssembly **GDMono::get_loaded_assembly(const String &p_name) {
 
 bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly) {
 
-	return load_assembly_from(p_name, String(), r_assembly, p_refonly);
-}
-
-bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly) {
-
-	return load_assembly_from(p_name, String(), p_aname, r_assembly, p_refonly);
-}
-
-bool GDMono::load_assembly_from(const String &p_name, const String &p_basedir, GDMonoAssembly **r_assembly, bool p_refonly) {
-
 	CRASH_COND(!r_assembly);
 
 	MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8());
-	bool result = load_assembly_from(p_name, p_basedir, aname, r_assembly, p_refonly);
+	bool result = load_assembly(p_name, aname, r_assembly, p_refonly);
 	mono_assembly_name_free(aname);
 	mono_free(aname);
 
 	return result;
 }
 
-bool GDMono::load_assembly_from(const String &p_name, const String &p_basedir, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly) {
+bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly) {
 
 	CRASH_COND(!r_assembly);
 
 	print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "...");
 
 	MonoImageOpenStatus status = MONO_IMAGE_OK;
-	MonoAssembly *assembly = mono_assembly_load_full(p_aname, p_basedir.length() ? p_basedir.utf8().get_data() : NULL, &status, p_refonly);
+	MonoAssembly *assembly = mono_assembly_load_full(p_aname, NULL, &status, p_refonly);
 
 	if (!assembly)
 		return false;
@@ -425,6 +415,32 @@ bool GDMono::load_assembly_from(const String &p_name, const String &p_basedir, M
 	return true;
 }
 
+bool GDMono::load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly) {
+
+	CRASH_COND(!r_assembly);
+
+	print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "...");
+
+	GDMonoAssembly *assembly = GDMonoAssembly::load_from(p_name, p_path, p_refonly);
+
+	if (!assembly)
+		return false;
+
+#ifdef DEBUG_ENABLED
+	uint32_t domain_id = mono_domain_get_id(mono_domain_get());
+	GDMonoAssembly **stored_assembly = assemblies[domain_id].getptr(p_name);
+
+	ERR_FAIL_COND_V(stored_assembly == NULL, false);
+	ERR_FAIL_COND_V(*stored_assembly != assembly, false);
+#endif
+
+	*r_assembly = assembly;
+
+	print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path());
+
+	return true;
+}
+
 APIAssembly::Version APIAssembly::Version::get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, APIAssembly::Type p_api_type) {
 	APIAssembly::Version api_assembly_version;
 
@@ -480,7 +496,14 @@ bool GDMono::_load_core_api_assembly() {
 	}
 #endif
 
-	bool success = load_assembly(API_ASSEMBLY_NAME, &core_api_assembly);
+	String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(API_ASSEMBLY_NAME ".dll");
+
+	if (!FileAccess::exists(assembly_path))
+		return false;
+
+	bool success = load_assembly_from(API_ASSEMBLY_NAME,
+			assembly_path,
+			&core_api_assembly);
 
 	if (success) {
 #ifdef MONO_GLUE_ENABLED
@@ -510,7 +533,14 @@ bool GDMono::_load_editor_api_assembly() {
 		return false;
 	}
 
-	bool success = load_assembly(EDITOR_API_ASSEMBLY_NAME, &editor_api_assembly);
+	String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
+
+	if (!FileAccess::exists(assembly_path))
+		return false;
+
+	bool success = load_assembly_from(EDITOR_API_ASSEMBLY_NAME,
+			assembly_path,
+			&editor_api_assembly);
 
 	if (success) {
 #ifdef MONO_GLUE_ENABLED
@@ -551,6 +581,8 @@ bool GDMono::_load_project_assembly() {
 
 	if (success) {
 		mono_assembly_set_main(project_assembly->get_assembly());
+
+		CSharpLanguage::get_singleton()->project_assembly_loaded();
 	} else {
 		if (OS::get_singleton()->is_stdout_verbose())
 			print_error("Mono: Failed to load project assembly");

+ 1 - 2
modules/mono/mono_gd/gd_mono.h

@@ -199,8 +199,7 @@ public:
 
 	bool load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly = false);
 	bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly = false);
-	bool load_assembly_from(const String &p_name, const String &p_basedir, GDMonoAssembly **r_assembly, bool p_refonly = false);
-	bool load_assembly_from(const String &p_name, const String &p_basedir, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly = false);
+	bool load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly = false);
 
 	Error finalize_and_unload_domain(MonoDomain *p_domain);
 

+ 14 - 0
modules/mono/mono_gd/gd_mono_assembly.cpp

@@ -457,6 +457,20 @@ GDMonoClass *GDMonoAssembly::get_object_derived_class(const StringName &p_class)
 	return match;
 }
 
+GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_path, bool p_refonly) {
+
+	GDMonoAssembly **loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name);
+	if (loaded_asm)
+		return *loaded_asm;
+#ifdef DEBUG_ENABLED
+	CRASH_COND(!FileAccess::exists(p_path));
+#endif
+	no_search = true;
+	GDMonoAssembly *res = _load_assembly_from(p_name, p_path, p_refonly);
+	no_search = false;
+	return res;
+}
+
 GDMonoAssembly::GDMonoAssembly(const String &p_name, const String &p_path) {
 
 	loaded = false;

+ 2 - 0
modules/mono/mono_gd/gd_mono_assembly.h

@@ -128,6 +128,8 @@ public:
 
 	static void fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config = String());
 
+	static GDMonoAssembly *load_from(const String &p_name, const String &p_path, bool p_refonly);
+
 	GDMonoAssembly(const String &p_name, const String &p_path = String());
 	~GDMonoAssembly();
 };

+ 1 - 1
modules/mono/signal_awaiter_utils.cpp

@@ -141,7 +141,7 @@ SignalAwaiterHandle::~SignalAwaiterHandle() {
 
 			if (exc) {
 				GDMonoUtils::set_pending_exception(exc);
-				ERR_FAIL_V();
+				ERR_FAIL();
 			}
 		}
 	}

+ 28 - 0
modules/mono/utils/string_utils.cpp

@@ -30,6 +30,8 @@
 
 #include "string_utils.h"
 
+#include "core/os/file_access.h"
+
 namespace {
 
 int sfind(const String &p_text, int p_from) {
@@ -128,6 +130,7 @@ String sformat(const String &p_text, const Variant &p1, const Variant &p2, const
 	return new_string;
 }
 
+#ifdef TOOLS_ENABLED
 bool is_csharp_keyword(const String &p_name) {
 
 	// Reserved keywords
@@ -156,3 +159,28 @@ bool is_csharp_keyword(const String &p_name) {
 String escape_csharp_keyword(const String &p_name) {
 	return is_csharp_keyword(p_name) ? "@" + p_name : p_name;
 }
+#endif
+
+Error read_all_file_utf8(const String &p_path, String &r_content) {
+	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 source;
+	if (source.parse_utf8((const char *)w.ptr())) {
+		ERR_FAIL_V(ERR_INVALID_DATA);
+	}
+
+	r_content = source;
+	return OK;
+}

+ 2 - 0
modules/mono/utils/string_utils.h

@@ -42,4 +42,6 @@ bool is_csharp_keyword(const String &p_name);
 String escape_csharp_keyword(const String &p_name);
 #endif
 
+Error read_all_file_utf8(const String &p_path, String &r_content);
+
 #endif // STRING_FORMAT_H