Merge pull request #30282 from neikeq/editor_in_cs_equals_win

Re-write mono module editor code in C#
This commit is contained in:
Rémi Verschelde 2019-07-05 10:29:19 +02:00 committed by GitHub
commit 6e9cb44004
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
97 changed files with 5708 additions and 4125 deletions

View file

@ -83,6 +83,10 @@ String ProjectSettings::localize_path(const String &p_path) const {
// `plus_file("")` is an easy way to ensure we have a trailing '/'.
const String res_path = resource_path.plus_file("");
// DirAccess::get_current_dir() is not guaranteed to return a path that with a trailing '/',
// so we must make sure we have it as well in order to compare with 'res_path'.
cwd = cwd.plus_file("");
if (!cwd.begins_with(res_path)) {
return p_path;
};

View file

@ -70,7 +70,7 @@ bool Reference::reference() {
if (get_script_instance()) {
get_script_instance()->refcount_incremented();
}
if (instance_binding_count > 0) {
if (instance_binding_count > 0 && !ScriptServer::are_languages_finished()) {
for (int i = 0; i < MAX_SCRIPT_INSTANCE_BINDINGS; i++) {
if (_script_instance_bindings[i]) {
ScriptServer::get_language(i)->refcount_incremented_instance_binding(this);
@ -91,7 +91,7 @@ bool Reference::unreference() {
bool script_ret = get_script_instance()->refcount_decremented();
die = die && script_ret;
}
if (instance_binding_count > 0) {
if (instance_binding_count > 0 && !ScriptServer::are_languages_finished()) {
for (int i = 0; i < MAX_SCRIPT_INSTANCE_BINDINGS; i++) {
if (_script_instance_bindings[i]) {
bool script_ret = ScriptServer::get_language(i)->refcount_decremented_instance_binding(this);

View file

@ -220,7 +220,7 @@ EditorSelection *EditorInterface::get_selection() {
return EditorNode::get_singleton()->get_editor_selection();
}
EditorSettings *EditorInterface::get_editor_settings() {
Ref<EditorSettings> EditorInterface::get_editor_settings() {
return EditorSettings::get_singleton();
}

View file

@ -87,7 +87,7 @@ public:
EditorSelection *get_selection();
//EditorImportExport *get_import_export();
EditorSettings *get_editor_settings();
Ref<EditorSettings> get_editor_settings();
EditorResourcePreview *get_resource_previewer();
EditorFileSystem *get_resource_file_system();

View file

@ -1579,6 +1579,8 @@ void EditorSettings::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_recent_dirs"), &EditorSettings::get_recent_dirs);
ADD_SIGNAL(MethodInfo("settings_changed"));
BIND_CONSTANT(NOTIFICATION_EDITOR_SETTINGS_CHANGED);
}
EditorSettings::EditorSettings() {

View file

@ -119,7 +119,7 @@ public:
void set_spatial_node(Spatial *p_node);
Spatial *get_spatial_node() const { return spatial_node; }
EditorSpatialGizmoPlugin *get_plugin() const { return gizmo_plugin; }
Ref<EditorSpatialGizmoPlugin> get_plugin() const { return gizmo_plugin; }
Vector3 get_handle_pos(int p_idx) const;
bool intersect_frustum(const Camera *p_camera, const Vector<Plane> &p_frustum);
bool intersect_ray(Camera *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal, int *r_gizmo_handle = NULL, bool p_sec_first = false);

View file

@ -1,5 +1,8 @@
#!/usr/bin/env python
import build_scripts.tls_configure as tls_configure
import build_scripts.mono_configure as mono_configure
Import('env')
Import('env_modules')
@ -26,27 +29,36 @@ if env_mono['mono_glue']:
import os.path
if not os.path.isfile('glue/mono_glue.gen.cpp'):
raise RuntimeError('Missing mono glue sources. Did you forget to generate them?')
raise RuntimeError("Mono glue sources not found. Did you forget to run '--generate-mono-glue'?")
if env_mono['tools'] or env_mono['target'] != 'release':
env_mono.Append(CPPDEFINES=['GD_MONO_HOT_RELOAD'])
# Configure Thread Local Storage
import build_scripts.tls_configure as tls_configure
conf = Configure(env_mono)
tls_configure.configure(conf)
env_mono = conf.Finish()
# Configure Mono
import build_scripts.mono_configure as mono_configure
mono_configure.configure(env, env_mono)
# Build GodotSharpTools
# Build Godot API solution
import build_scripts.godotsharptools_build as godotsharptools_build
if env_mono['tools'] and env_mono['mono_glue']:
import build_scripts.api_solution_build as api_solution_build
api_solution_build.build(env_mono)
godotsharptools_build.build(env_mono)
# Build GodotTools
if env_mono['tools']:
import build_scripts.godot_tools_build as godot_tools_build
if env_mono['mono_glue']:
godot_tools_build.build(env_mono)
else:
# Building without the glue sources so the Godot API solution may be missing.
# GodotTools depends on the Godot API solution. As such, we will only build
# GodotTools.ProjectEditor which doesn't depend on the Godot API solution and
# is required by the bindings generator in order to be able to generated it.
godot_tools_build.build_project_editor_only(env_mono)

0
modules/mono/__init__.py Normal file
View file

View file

@ -0,0 +1,66 @@
# Build the Godot API solution
import os
from SCons.Script import Dir
def build_api_solution(source, target, env):
# source and target elements are of type SCons.Node.FS.File, hence why we convert them to str
module_dir = env['module_dir']
solution_path = os.path.join(module_dir, 'glue/Managed/Generated/GodotSharp.sln')
if not os.path.isfile(solution_path):
raise RuntimeError("Godot API solution not found. Did you forget to run '--generate-mono-glue'?")
build_config = env['solution_build_config']
extra_msbuild_args = ['/p:NoWarn=1591'] # Ignore missing documentation warnings
from .solution_builder import build_solution
build_solution(env, solution_path, build_config, extra_msbuild_args=extra_msbuild_args)
# Copy targets
core_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, 'GodotSharp', 'bin', build_config))
editor_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, 'GodotSharpEditor', 'bin', build_config))
dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir))
if not os.path.isdir(dst_dir):
assert not os.path.isfile(dst_dir)
os.makedirs(dst_dir)
def copy_target(target_path):
from shutil import copy
filename = os.path.basename(target_path)
src_path = os.path.join(core_src_dir, filename)
if not os.path.isfile(src_path):
src_path = os.path.join(editor_src_dir, filename)
copy(src_path, target_path)
for scons_target in target:
copy_target(str(scons_target))
def build(env_mono):
assert env_mono['tools']
target_filenames = [
'GodotSharp.dll', 'GodotSharp.pdb', 'GodotSharp.xml',
'GodotSharpEditor.dll', 'GodotSharpEditor.pdb', 'GodotSharpEditor.xml'
]
for build_config in ['Debug', 'Release']:
output_dir = Dir('#bin').abspath
editor_api_dir = os.path.join(output_dir, 'GodotSharp', 'Api', build_config)
targets = [os.path.join(editor_api_dir, filename) for filename in target_filenames]
cmd = env_mono.CommandNoCache(targets, [], build_api_solution,
module_dir=os.getcwd(), solution_build_config=build_config)
env_mono.AlwaysBuild(cmd)

View file

@ -0,0 +1,108 @@
# Build GodotTools solution
import os
from SCons.Script import Dir
def build_godot_tools(source, target, env):
# source and target elements are of type SCons.Node.FS.File, hence why we convert them to str
module_dir = env['module_dir']
solution_path = os.path.join(module_dir, 'editor/GodotTools/GodotTools.sln')
build_config = 'Debug' if env['target'] == 'debug' else 'Release'
from . solution_builder import build_solution, nuget_restore
nuget_restore(env, solution_path)
build_solution(env, solution_path, build_config)
# Copy targets
solution_dir = os.path.abspath(os.path.join(solution_path, os.pardir))
src_dir = os.path.join(solution_dir, 'GodotTools', 'bin', build_config)
dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir))
if not os.path.isdir(dst_dir):
assert not os.path.isfile(dst_dir)
os.makedirs(dst_dir)
def copy_target(target_path):
from shutil import copy
filename = os.path.basename(target_path)
copy(os.path.join(src_dir, filename), target_path)
for scons_target in target:
copy_target(str(scons_target))
def build_godot_tools_project_editor(source, target, env):
# source and target elements are of type SCons.Node.FS.File, hence why we convert them to str
module_dir = env['module_dir']
project_name = 'GodotTools.ProjectEditor'
csproj_dir = os.path.join(module_dir, 'editor/GodotTools', project_name)
csproj_path = os.path.join(csproj_dir, project_name + '.csproj')
build_config = 'Debug' if env['target'] == 'debug' else 'Release'
from . solution_builder import build_solution, nuget_restore
# Make sure to restore NuGet packages in the project directory for the project to find it
nuget_restore(env, os.path.join(csproj_dir, 'packages.config'), '-PackagesDirectory',
os.path.join(csproj_dir, 'packages'))
build_solution(env, csproj_path, build_config)
# Copy targets
src_dir = os.path.join(csproj_dir, 'bin', build_config)
dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir))
if not os.path.isdir(dst_dir):
assert not os.path.isfile(dst_dir)
os.makedirs(dst_dir)
def copy_target(target_path):
from shutil import copy
filename = os.path.basename(target_path)
copy(os.path.join(src_dir, filename), target_path)
for scons_target in target:
copy_target(str(scons_target))
def build(env_mono):
assert env_mono['tools']
output_dir = Dir('#bin').abspath
editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools')
editor_api_dir = os.path.join(output_dir, 'GodotSharp', 'Api', 'Debug')
source_filenames = ['GodotSharp.dll', 'GodotSharpEditor.dll']
sources = [os.path.join(editor_api_dir, filename) for filename in source_filenames]
target_filenames = ['GodotTools.dll', 'GodotTools.BuildLogger.dll', 'GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll']
if env_mono['target'] == 'debug':
target_filenames += ['GodotTools.pdb', 'GodotTools.BuildLogger.dll', 'GodotTools.ProjectEditor.dll', 'GodotTools.Core.dll']
targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames]
cmd = env_mono.CommandNoCache(targets, sources, build_godot_tools, module_dir=os.getcwd())
env_mono.AlwaysBuild(cmd)
def build_project_editor_only(env_mono):
assert env_mono['tools']
output_dir = Dir('#bin').abspath
editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools')
target_filenames = ['GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll']
targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames]
cmd = env_mono.CommandNoCache(targets, [], build_godot_tools_project_editor, module_dir=os.getcwd())
env_mono.AlwaysBuild(cmd)

View file

@ -1,10 +1,8 @@
import imp
import os
import os.path
import sys
import subprocess
from distutils.version import LooseVersion
from SCons.Script import Dir, Environment
if os.name == 'nt':
@ -58,6 +56,12 @@ def configure(env, env_mono):
mono_lib_names = ['mono-2.0-sgen', 'monosgen-2.0']
is_travis = os.environ.get('TRAVIS') == 'true'
if is_travis:
# Travis CI may have a Mono version lower than 5.12
env_mono.Append(CPPDEFINES=['NO_PENDING_EXCEPTIONS'])
if is_android and not env['android_arch'] in android_arch_dirs:
raise RuntimeError('This module does not support for the specified \'android_arch\': ' + env['android_arch'])
@ -83,9 +87,6 @@ def configure(env, env_mono):
print('Found Mono root directory: ' + mono_root)
mono_version = mono_root_try_find_mono_version(mono_root)
configure_for_mono_version(env_mono, mono_version)
mono_lib_path = os.path.join(mono_root, 'lib')
env.Append(LIBPATH=mono_lib_path)
@ -164,9 +165,6 @@ def configure(env, env_mono):
if mono_root:
print('Found Mono root directory: ' + mono_root)
mono_version = mono_root_try_find_mono_version(mono_root)
configure_for_mono_version(env_mono, mono_version)
mono_lib_path = os.path.join(mono_root, 'lib')
env.Append(LIBPATH=mono_lib_path)
@ -209,9 +207,6 @@ def configure(env, env_mono):
# TODO: Add option to force using pkg-config
print('Mono root directory not found. Using pkg-config instead')
mono_version = pkgconfig_try_find_mono_version()
configure_for_mono_version(env_mono, mono_version)
env.ParseConfig('pkg-config monosgen-2 --libs')
env_mono.ParseConfig('pkg-config monosgen-2 --cflags')
@ -401,17 +396,6 @@ def copy_mono_shared_libs(env, mono_root, target_mono_root_dir):
copy_if_exists(os.path.join(mono_root, 'lib', lib_file_name), target_mono_lib_dir)
def configure_for_mono_version(env, mono_version):
if mono_version is None:
if os.getenv('MONO_VERSION'):
mono_version = os.getenv('MONO_VERSION')
else:
raise RuntimeError("Mono JIT compiler version not found; specify one manually with the 'MONO_VERSION' environment variable")
print('Found Mono JIT compiler version: ' + str(mono_version))
if mono_version >= LooseVersion('5.12.0'):
env.Append(CPPDEFINES=['HAS_PENDING_EXCEPTIONS'])
def pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext):
tmpenv = Environment()
tmpenv.AppendENVPath('PKG_CONFIG_PATH', os.getenv('PKG_CONFIG_PATH'))
@ -421,36 +405,3 @@ def pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext):
if name_found and os.path.isdir(os.path.join(hint_dir, '..', 'include', 'mono-2.0')):
return os.path.join(hint_dir, '..')
return ''
def pkgconfig_try_find_mono_version():
from compat import decode_utf8
lines = subprocess.check_output(['pkg-config', 'monosgen-2', '--modversion']).splitlines()
greater_version = None
for line in lines:
try:
version = LooseVersion(decode_utf8(line))
if greater_version is None or version > greater_version:
greater_version = version
except ValueError:
pass
return greater_version
def mono_root_try_find_mono_version(mono_root):
from compat import decode_utf8
mono_bin = os.path.join(mono_root, 'bin')
if os.path.isfile(os.path.join(mono_bin, 'mono')):
mono_binary = os.path.join(mono_bin, 'mono')
elif os.path.isfile(os.path.join(mono_bin, 'mono.exe')):
mono_binary = os.path.join(mono_bin, 'mono.exe')
else:
return None
output = subprocess.check_output([mono_binary, '--version'])
first_line = decode_utf8(output.splitlines()[0])
try:
return LooseVersion(first_line.split()[len('Mono JIT compiler version'.split())])
except (ValueError, IndexError):
return None

View file

@ -1,8 +1,8 @@
# Build GodotSharpTools solution
import os
from SCons.Script import Builder, Dir
verbose = False
def find_nuget_unix():
@ -131,12 +131,46 @@ def find_msbuild_windows(env):
return None
def mono_build_solution(source, target, env):
import subprocess
from shutil import copyfile
def run_command(command, args, env_override=None, name=None):
def cmd_args_to_str(cmd_args):
return ' '.join([arg if not ' ' in arg else '"%s"' % arg for arg in cmd_args])
sln_path = os.path.abspath(str(source[0]))
target_path = os.path.abspath(str(target[0]))
args = [command] + args
if name is None:
name = os.path.basename(command)
if verbose:
print("Running '%s': %s" % (name, cmd_args_to_str(args)))
import subprocess
try:
if env_override is None:
subprocess.check_call(args)
else:
subprocess.check_call(args, env=env_override)
except subprocess.CalledProcessError as e:
raise RuntimeError("'%s' exited with error code: %s" % (name, e.returncode))
def nuget_restore(env, *args):
global verbose
verbose = env['verbose']
# Find NuGet
nuget_path = find_nuget_windows(env) 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
run_command(nuget_path, ['restore'] + list(args), name='nuget restore')
def build_solution(env, solution_path, build_config, extra_msbuild_args=[]):
global verbose
verbose = env['verbose']
framework_path = ''
msbuild_env = os.environ.copy()
@ -175,64 +209,10 @@ def mono_build_solution(source, target, env):
print('MSBuild path: ' + msbuild_path)
# Find NuGet
nuget_path = find_nuget_windows(env) 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 = [solution_path, '/p:Configuration=' + build_config]
msbuild_args += ['/p:FrameworkPathOverride=' + framework_path] if framework_path else []
msbuild_args += extra_msbuild_args
msbuild_args = [
msbuild_path,
sln_path,
'/p:Configuration=' + build_config,
]
if framework_path:
msbuild_args += ['/p:FrameworkPathOverride=' + framework_path]
try:
subprocess.check_call(msbuild_args, env=msbuild_env)
except subprocess.CalledProcessError:
raise RuntimeError('GodotSharpTools: Build failed')
# Copy files
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)
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"))
def build(env_mono):
if not env_mono['tools']:
return
output_dir = Dir('#bin').abspath
editor_tools_dir = os.path.join(output_dir, 'GodotSharp', 'Tools')
mono_sln_builder = Builder(action=mono_build_solution)
env_mono.Append(BUILDERS={'MonoBuildSolution': mono_sln_builder})
env_mono.MonoBuildSolution(
os.path.join(editor_tools_dir, 'GodotSharpTools.dll'),
'editor/GodotSharpTools/GodotSharpTools.sln'
)
run_command(msbuild_path, msbuild_args, env_override=msbuild_env, name='msbuild')

View file

@ -42,9 +42,9 @@
#include "editor/bindings_generator.h"
#include "editor/csharp_project.h"
#include "editor/editor_node.h"
#include "editor/godotsharp_editor.h"
#endif
#include "editor/editor_internal_calls.h"
#include "godotsharp_dirs.h"
#include "mono_gd/gd_mono_class.h"
#include "mono_gd/gd_mono_marshal.h"
@ -65,8 +65,8 @@ static bool _create_project_solution_if_needed() {
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);
return GodotSharpEditor::get_singleton()->call("_create_project_solution");
CRASH_COND(CSharpLanguage::get_singleton()->get_godotsharp_editor() == NULL);
return CSharpLanguage::get_singleton()->get_godotsharp_editor()->call("CreateProjectSolution");
}
return true;
@ -96,14 +96,6 @@ 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);
@ -114,14 +106,12 @@ void CSharpLanguage::init() {
#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);
}
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);
EditorNode::add_init_callback(&_editor_init_callback);
GLOBAL_DEF("mono/export/include_scripts_content", false);
#endif
@ -664,7 +654,7 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft
CRASH_COND(!Engine::get_singleton()->is_editor_hint());
#ifdef TOOLS_ENABLED
MonoReloadNode::get_singleton()->restart_reload_timer();
get_godotsharp_editor()->get_node(NodePath("HotReloadAssemblyWatcher"))->call("RestartTimer");
#endif
#ifdef GD_MONO_HOT_RELOAD
@ -731,58 +721,93 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
SCOPED_MUTEX_LOCK(script_instances_mutex);
for (SelfList<CSharpScript> *elem = script_list.first(); elem; elem = elem->next()) {
if (elem->self()->get_path().is_resource_file()) {
// Cast to CSharpScript to avoid being erased by accident
scripts.push_back(Ref<CSharpScript>(elem->self()));
}
// Cast to CSharpScript to avoid being erased by accident
scripts.push_back(Ref<CSharpScript>(elem->self()));
}
}
List<Ref<CSharpScript> > to_reload;
// We need to keep reference instances alive during reloading
List<Ref<Reference> > ref_instances;
for (Map<Object *, CSharpScriptBinding>::Element *E = script_bindings.front(); E; E = E->next()) {
CSharpScriptBinding &script_binding = E->value();
Reference *ref = Object::cast_to<Reference>(script_binding.owner);
if (ref) {
ref_instances.push_back(Ref<Reference>(ref));
}
}
// 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()) {
Ref<CSharpScript> &script = E->get();
to_reload.push_back(script);
if (script->get_path().empty()) {
script->tied_class_name_for_reload = script->script_class->get_name();
script->tied_class_namespace_for_reload = script->script_class->get_namespace();
}
// Script::instances are deleted during managed object disposal, which happens on domain finalize.
// Only placeholders are kept. Therefore we need to keep a copy before that happens.
for (Set<Object *>::Element *F = script->instances.front(); F; F = F->next()) {
script->pending_reload_instances.insert(F->get()->get_instance_id());
Object *obj = F->get();
script->pending_reload_instances.insert(obj->get_instance_id());
Reference *ref = Object::cast_to<Reference>(obj);
if (ref) {
ref_instances.push_back(Ref<Reference>(ref));
}
}
#ifdef TOOLS_ENABLED
for (Set<PlaceHolderScriptInstance *>::Element *F = script->placeholders.front(); F; F = F->next()) {
script->pending_reload_instances.insert(F->get()->get_owner()->get_instance_id());
Object *obj = F->get()->get_owner();
script->pending_reload_instances.insert(obj->get_instance_id());
Reference *ref = Object::cast_to<Reference>(obj);
if (ref) {
ref_instances.push_back(Ref<Reference>(ref));
}
}
#endif
// FIXME: What about references? Need to keep them alive if only managed code references them.
// Save state and remove script from instances
Map<ObjectID, CSharpScript::StateBackup> &owners_map = script->pending_reload_state;
while (script->instances.front()) {
Object *obj = script->instances.front()->get();
// Save instance info
CSharpScript::StateBackup state;
for (Set<Object *>::Element *F = script->instances.front(); F; F = F->next()) {
Object *obj = F->get();
ERR_CONTINUE(!obj->get_script_instance());
// TODO: Proper state backup (Not only variants, serialize managed state of scripts)
obj->get_script_instance()->get_property_state(state.properties);
CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance());
Ref<MonoGCHandle> gchandle = CAST_CSHARP_INSTANCE(obj->get_script_instance())->gchandle;
if (gchandle.is_valid())
gchandle->release();
// Call OnBeforeSerialize
if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener)))
obj->get_script_instance()->call_multilevel(string_names.on_before_serialize);
// Save instance info
CSharpScript::StateBackup state;
// TODO: Proper state backup (Not only variants, serialize managed state of scripts)
csi->get_properties_state_for_reloading(state.properties);
owners_map[obj->get_instance_id()] = state;
}
}
// After the state of all instances is saved, clear scripts and script instances
for (List<Ref<CSharpScript> >::Element *E = scripts.front(); E; E = E->next()) {
Ref<CSharpScript> &script = E->get();
while (script->instances.front()) {
Object *obj = script->instances.front()->get();
obj->set_script(RefPtr()); // Remove script and existing script instances (placeholder are not removed before domain reload)
}
@ -825,26 +850,76 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
scr->pending_reload_state.erase(obj_id);
}
}
return;
}
for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) {
List<Ref<CSharpScript> > to_reload_state;
Ref<CSharpScript> scr = E->get();
for (List<Ref<CSharpScript> >::Element *E = to_reload.front(); E; E = E->next()) {
Ref<CSharpScript> script = E->get();
if (!script->get_path().empty()) {
#ifdef TOOLS_ENABLED
scr->exports_invalidated = true;
script->exports_invalidated = true;
#endif
scr->signals_invalidated = true;
scr->reload(p_soft_reload);
scr->update_exports();
script->signals_invalidated = true;
script->reload(p_soft_reload);
script->update_exports();
} else {
const StringName &class_namespace = script->tied_class_namespace_for_reload;
const StringName &class_name = script->tied_class_name_for_reload;
GDMonoAssembly *project_assembly = gdmono->get_project_assembly();
GDMonoAssembly *tools_assembly = gdmono->get_tools_assembly();
// Search in project and tools assemblies first as those are the most likely to have the class
GDMonoClass *script_class = (project_assembly ? project_assembly->get_class(class_namespace, class_name) : NULL);
if (!script_class) {
script_class = (tools_assembly ? tools_assembly->get_class(class_namespace, class_name) : NULL);
}
if (!script_class) {
script_class = gdmono->get_class(class_namespace, class_name);
}
if (!script_class) {
// The class was removed, can't reload
script->pending_reload_instances.clear();
continue;
}
bool obj_type = CACHED_CLASS(GodotObject)->is_assignable_from(script_class);
if (!obj_type) {
// The class no longer inherits Godot.Object, can't reload
script->pending_reload_instances.clear();
continue;
}
GDMonoClass *native = GDMonoUtils::get_class_native_base(script_class);
Ref<CSharpScript> new_script = CSharpScript::create_for_managed_type(script_class, native);
CRASH_COND(new_script.is_null());
new_script->pending_reload_instances = script->pending_reload_instances;
new_script->pending_reload_state = script->pending_reload_state;
script = new_script;
}
String native_name = NATIVE_GDMONOCLASS_NAME(script->native);
{
for (Set<ObjectID>::Element *F = scr->pending_reload_instances.front(); F; F = F->next()) {
for (Set<ObjectID>::Element *F = script->pending_reload_instances.front(); F; F = F->next()) {
ObjectID obj_id = F->get();
Object *obj = ObjectDB::get_instance(obj_id);
if (!obj) {
scr->pending_reload_state.erase(obj_id);
script->pending_reload_state.erase(obj_id);
continue;
}
if (!ClassDB::is_parent_class(obj->get_class_name(), native_name)) {
// No longer inherits the same compatible type, can't reload
script->pending_reload_state.erase(obj_id);
continue;
}
@ -856,28 +931,20 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
// Non-placeholder script instances are removed in godot_icall_Object_Disposed.
CRASH_COND(!si->is_placeholder());
if (scr->is_tool() || ScriptServer::is_scripting_enabled()) {
if (script->is_tool() || ScriptServer::is_scripting_enabled()) {
// Replace placeholder with a script instance
CSharpScript::StateBackup &state_backup = scr->pending_reload_state[obj_id];
CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id];
// Backup placeholder script instance state before replacing it with a script instance
si->get_property_state(state_backup.properties);
ScriptInstance *script_instance = scr->instance_create(obj);
ScriptInstance *script_instance = script->instance_create(obj);
if (script_instance) {
scr->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si));
script->placeholders.erase(static_cast<PlaceHolderScriptInstance *>(si));
obj->set_script_instance(script_instance);
}
// TODO: Restore serialized state
for (List<Pair<StringName, Variant> >::Element *G = state_backup.properties.front(); G; G = G->next()) {
script_instance->set(G->get().first, G->get().second);
}
scr->pending_reload_state.erase(obj_id);
}
continue;
@ -887,19 +954,42 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
#endif
// Re-create script instance
obj->set_script(scr.get_ref_ptr()); // will create the script instance as well
obj->set_script(script.get_ref_ptr()); // will create the script instance as well
}
}
// TODO: Restore serialized state
to_reload_state.push_back(script);
}
for (List<Pair<StringName, Variant> >::Element *G = scr->pending_reload_state[obj_id].properties.front(); G; G = G->next()) {
obj->get_script_instance()->set(G->get().first, G->get().second);
}
for (List<Ref<CSharpScript> >::Element *E = to_reload_state.front(); E; E = E->next()) {
Ref<CSharpScript> script = E->get();
scr->pending_reload_state.erase(obj_id);
for (Set<ObjectID>::Element *F = script->pending_reload_instances.front(); F; F = F->next()) {
ObjectID obj_id = F->get();
Object *obj = ObjectDB::get_instance(obj_id);
if (!obj) {
script->pending_reload_state.erase(obj_id);
continue;
}
scr->pending_reload_instances.clear();
ERR_CONTINUE(!obj->get_script_instance());
// TODO: Restore serialized state
CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id];
for (List<Pair<StringName, Variant> >::Element *G = state_backup.properties.front(); G; G = G->next()) {
obj->get_script_instance()->set(G->get().first, G->get().second);
}
// Call OnAfterDeserialization
CSharpInstance *csi = CAST_CSHARP_INSTANCE(obj->get_script_instance());
if (csi && csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener)))
obj->get_script_instance()->call_multilevel(string_names.on_after_deserialize);
}
script->pending_reload_instances.clear();
}
#ifdef TOOLS_ENABLED
@ -964,12 +1054,12 @@ void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const
#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);
return (Error)(int)get_godotsharp_editor()->call("OpenInExternalEditor", p_script, p_line, p_col);
}
bool CSharpLanguage::overrides_external_editor() {
return GodotSharpEditor::get_singleton()->overrides_external_editor();
return get_godotsharp_editor()->call("OverridesExternalEditor");
}
#endif
@ -1027,6 +1117,34 @@ void CSharpLanguage::_on_scripts_domain_unloaded() {
scripts_metadata_invalidated = true;
}
#ifdef TOOLS_ENABLED
void CSharpLanguage::_editor_init_callback() {
register_editor_internal_calls();
// Initialize GodotSharpEditor
GDMonoClass *editor_klass = GDMono::get_singleton()->get_tools_assembly()->get_class("GodotTools", "GodotSharpEditor");
CRASH_COND(editor_klass == NULL);
MonoObject *mono_object = mono_object_new(mono_domain_get(), editor_klass->get_mono_ptr());
CRASH_COND(mono_object == NULL);
MonoException *exc = NULL;
GDMonoUtils::runtime_object_init(mono_object, editor_klass, &exc);
UNHANDLED_EXCEPTION(exc);
EditorPlugin *godotsharp_editor = Object::cast_to<EditorPlugin>(GDMonoMarshal::mono_object_to_variant(mono_object));
CRASH_COND(godotsharp_editor == NULL);
// Enable it as a plugin
EditorNode::add_editor_plugin(godotsharp_editor);
godotsharp_editor->enable_plugin();
get_singleton()->godotsharp_editor = godotsharp_editor;
}
#endif
void CSharpLanguage::set_language_index(int p_idx) {
ERR_FAIL_COND(lang_idx != -1);
@ -1084,6 +1202,8 @@ CSharpLanguage::CSharpLanguage() {
lang_idx = -1;
scripts_metadata_invalidated = true;
godotsharp_editor = NULL;
}
CSharpLanguage::~CSharpLanguage() {
@ -1139,6 +1259,7 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b
r_script_binding.type_name = type_name;
r_script_binding.wrapper_class = type_class; // cache
r_script_binding.gchandle = MonoGCHandle::create_strong(mono_object);
r_script_binding.owner = p_object;
// Tie managed to unmanaged
Reference *ref = Object::cast_to<Reference>(p_object);
@ -1223,6 +1344,9 @@ void CSharpLanguage::refcount_incremented_instance_binding(Object *p_object) {
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
if (!script_binding.inited)
return;
if (ref_owner->reference_get_count() > 1 && gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
// 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,
@ -1247,14 +1371,17 @@ bool CSharpLanguage::refcount_decremented_instance_binding(Object *p_object) {
CRASH_COND(!ref_owner);
#endif
int refcount = ref_owner->reference_get_count();
void *data = p_object->get_script_instance_binding(get_language_index());
CRASH_COND(!data);
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
Ref<MonoGCHandle> &gchandle = script_binding.gchandle;
int refcount = ref_owner->reference_get_count();
if (!script_binding.inited)
return refcount == 0;
if (refcount == 1 && gchandle.is_valid() && !gchandle->is_weak()) { // The managed side also holds a reference, hence 1 instead of 0
// If owner owner is no longer referenced by the unmanaged side,
// the managed instance takes responsibility of deleting the owner when GCed.
@ -1417,6 +1544,31 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const {
return false;
}
void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Variant> > &r_state) {
List<PropertyInfo> pinfo;
get_property_list(&pinfo);
for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
Pair<StringName, Variant> state_pair;
state_pair.first = E->get().name;
ManagedType managedType;
GDMonoField *field = script->script_class->get_field(state_pair.first);
if (!field)
continue; // Properties ignored. We get the property baking fields instead.
managedType = field->get_type();
if (GDMonoMarshal::managed_to_variant_type(managedType) != Variant::NIL) { // If we can marshal it
if (get(state_pair.first, state_pair.second)) {
r_state.push_back(state_pair);
}
}
}
}
void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const {
for (Map<StringName, PropertyInfo>::Element *E = script->member_info.front(); E; E = E->next()) {
@ -1614,17 +1766,18 @@ MonoObject *CSharpInstance::_internal_new_managed() {
ERR_FAIL_NULL_V(owner, NULL);
ERR_FAIL_COND_V(script.is_null(), NULL);
MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, script->script_class->get_mono_ptr());
MonoObject *mono_object = mono_object_new(mono_domain_get(), script->script_class->get_mono_ptr());
if (!mono_object) {
// Important to clear this before destroying the script instance here
script = Ref<CSharpScript>();
owner = NULL;
bool die = _unreference_owner_unsafe();
// Not ok for the owner to die here. If there is a situation where this can happen, it will be considered a bug.
CRASH_COND(die == true);
owner = NULL;
ERR_EXPLAIN("Failed to allocate memory for the object");
ERR_FAIL_V(NULL);
}
@ -1940,7 +2093,16 @@ CSharpInstance::~CSharpInstance() {
CRASH_COND(data == NULL);
CSharpScriptBinding &script_binding = ((Map<Object *, CSharpScriptBinding>::Element *)data)->get();
CRASH_COND(!script_binding.inited);
if (!script_binding.inited) {
SCOPED_MUTEX_LOCK(CSharpLanguage::get_singleton()->get_language_bind_mutex());
if (!script_binding.inited) { // Other thread may have set it up
// Already had a binding that needs to be setup
CSharpLanguage::get_singleton()->setup_csharp_script_binding(script_binding, owner);
CRASH_COND(!script_binding.inited);
}
}
bool die = _unreference_owner_unsafe();
CRASH_COND(die == true); // The "instance binding" should be holding a reference
@ -1984,6 +2146,52 @@ void CSharpScript::_update_exports_values(Map<StringName, Variant> &values, List
}
#endif
void CSharpScript::_update_member_info_no_exports() {
if (exports_invalidated) {
exports_invalidated = false;
member_info.clear();
GDMonoClass *top = script_class;
while (top && top != native) {
PropertyInfo prop_info;
bool exported;
const Vector<GDMonoField *> &fields = top->get_all_fields();
for (int i = fields.size() - 1; i >= 0; i--) {
GDMonoField *field = fields[i];
if (_get_member_export(field, /* inspect export: */ false, prop_info, exported)) {
StringName member_name = field->get_name();
member_info[member_name] = prop_info;
exported_members_cache.push_front(prop_info);
exported_members_defval_cache[member_name] = Variant();
}
}
const Vector<GDMonoProperty *> &properties = top->get_all_properties();
for (int i = properties.size() - 1; i >= 0; i--) {
GDMonoProperty *property = properties[i];
if (_get_member_export(property, /* inspect export: */ false, prop_info, exported)) {
StringName member_name = property->get_name();
member_info[member_name] = prop_info;
exported_members_cache.push_front(prop_info);
exported_members_defval_cache[member_name] = Variant();
}
}
top = top->get_parent_class();
}
}
}
bool CSharpScript::_update_exports() {
#ifdef TOOLS_ENABLED
@ -2008,7 +2216,7 @@ bool CSharpScript::_update_exports() {
// Here we create a temporary managed instance of the class to get the initial values
MonoObject *tmp_object = mono_object_new(SCRIPTS_DOMAIN, script_class->get_mono_ptr());
MonoObject *tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr());
if (!tmp_object) {
ERR_PRINT("Failed to allocate temporary MonoObject");
@ -2049,18 +2257,18 @@ bool CSharpScript::_update_exports() {
for (int i = fields.size() - 1; i >= 0; i--) {
GDMonoField *field = fields[i];
if (_get_member_export(field, prop_info, exported)) {
StringName name = field->get_name();
if (_get_member_export(field, /* inspect export: */ true, prop_info, exported)) {
StringName member_name = field->get_name();
if (exported) {
member_info[name] = prop_info;
member_info[member_name] = prop_info;
exported_members_cache.push_front(prop_info);
if (tmp_object) {
exported_members_defval_cache[name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object));
exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object));
}
} else {
member_info[name] = prop_info;
member_info[member_name] = prop_info;
}
}
}
@ -2070,25 +2278,25 @@ bool CSharpScript::_update_exports() {
for (int i = properties.size() - 1; i >= 0; i--) {
GDMonoProperty *property = properties[i];
if (_get_member_export(property, prop_info, exported)) {
StringName name = property->get_name();
if (_get_member_export(property, /* inspect export: */ true, prop_info, exported)) {
StringName member_name = property->get_name();
if (exported) {
member_info[name] = prop_info;
member_info[member_name] = prop_info;
exported_members_cache.push_front(prop_info);
if (tmp_object) {
MonoException *exc = NULL;
MonoObject *ret = property->get_value(tmp_object, &exc);
if (exc) {
exported_members_defval_cache[name] = Variant();
exported_members_defval_cache[member_name] = Variant();
GDMonoUtils::debug_print_unhandled_exception(exc);
} else {
exported_members_defval_cache[name] = GDMonoMarshal::mono_object_to_variant(ret);
exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(ret);
}
}
} else {
member_info[name] = prop_info;
member_info[member_name] = prop_info;
}
}
}
@ -2197,7 +2405,7 @@ bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Ve
* Returns false if there was an error, otherwise true.
* If there was an error, r_prop_info and r_exported are not assigned any value.
*/
bool CSharpScript::_get_member_export(IMonoClassMember *p_member, PropertyInfo &r_prop_info, bool &r_exported) {
bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported) {
// Goddammit, C++. All I wanted was some nested functions.
#define MEMBER_FULL_QUALIFIED_NAME(m_member) \
@ -2222,26 +2430,30 @@ bool CSharpScript::_get_member_export(IMonoClassMember *p_member, PropertyInfo &
CRASH_NOW();
}
Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type);
if (!p_member->has_attribute(CACHED_CLASS(ExportAttribute))) {
r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE);
r_exported = false;
return true;
}
bool exported = p_member->has_attribute(CACHED_CLASS(ExportAttribute));
if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_PROPERTY) {
GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member);
if (!property->has_getter()) {
ERR_PRINTS("Read-only property cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
if (exported)
ERR_PRINTS("Read-only property cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
return false;
}
if (!property->has_setter()) {
ERR_PRINTS("Set-only property (without getter) cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
if (exported)
ERR_PRINTS("Write-only property (without getter) cannot be exported: " + MEMBER_FULL_QUALIFIED_NAME(p_member));
return false;
}
}
Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type);
if (!p_inspect_export || !exported) {
r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE);
r_exported = false;
return true;
}
MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute));
PropertyHint hint = PROPERTY_HINT_NONE;
@ -2463,9 +2675,9 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GD
// This method should not fail
CRASH_COND(!p_class);
CRASH_COND(p_class == NULL);
// TODO: Cache the 'CSharpScript' associated with this 'p_class' instead of allocating a new one every time
// TODO OPTIMIZE: Cache the 'CSharpScript' associated with this 'p_class' instead of allocating a new one every time
Ref<CSharpScript> script = memnew(CSharpScript);
script->name = p_class->get_name();
@ -2479,6 +2691,20 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GD
if (base != script->native)
script->base = base;
script->valid = true;
script->tool = script->script_class->has_attribute(CACHED_CLASS(ToolAttribute));
if (!script->tool) {
GDMonoClass *nesting_class = script->script_class->get_nesting_class();
script->tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute));
}
#if TOOLS_ENABLED
if (!script->tool) {
script->tool = script->script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly();
}
#endif
#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.
@ -2507,6 +2733,7 @@ Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GD
}
script->load_script_signals(script->script_class, script->native);
script->_update_member_info_no_exports();
return script;
}
@ -2516,7 +2743,8 @@ bool CSharpScript::can_instance() const {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint()) {
if (get_path().find("::") == -1) { // Ignore if built-in script. Can happen if the file is deleted...
// Hack to lower the risk of attached scripts not being added to the C# project
if (!get_path().empty() && get_path().find("::") == -1) { // Ignore if built-in script. Can happen if the file is deleted...
if (_create_project_solution_if_needed()) {
CSharpProject::add_item(GodotSharpDirs::get_project_csproj_path(),
"Compile",
@ -2568,7 +2796,9 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount);
if (ctor == NULL) {
if (p_argcount == 0) {
ERR_PRINTS("Cannot create script instance because the class does not define a parameterless constructor: " + get_path());
String path = get_path();
ERR_PRINTS("Cannot create script instance. The class '" + script_class->get_full_name() +
"' does not define a parameterless constructor." + (path.empty() ? String() : ". Path: " + path));
}
ERR_EXPLAIN("Constructor not found");
@ -2610,7 +2840,7 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg
/* STEP 2, INITIALIZE AND CONSTRUCT */
MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, script_class->get_mono_ptr());
MonoObject *mono_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr());
if (!mono_object) {
// Important to clear this before destroying the script instance here
@ -2691,7 +2921,7 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) {
#endif
if (native) {
String native_name = native->get_name();
String native_name = NATIVE_GDMONOCLASS_NAME(native);
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() + "'");
@ -2817,11 +3047,22 @@ Error CSharpScript::reload(bool p_keep_state) {
if (script_class) {
#ifdef DEBUG_ENABLED
print_verbose("Found class " + script_class->get_namespace() + "." + script_class->get_name() + " for script " + get_path());
print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path());
#endif
tool = script_class->has_attribute(CACHED_CLASS(ToolAttribute));
if (!tool) {
GDMonoClass *nesting_class = script_class->get_nesting_class();
tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute));
}
#if TOOLS_ENABLED
if (!tool) {
tool = script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly();
}
#endif
native = GDMonoUtils::get_class_native_base(script_class);
CRASH_COND(native == NULL);
@ -3019,7 +3260,8 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p
#endif
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint() && mono_domain_get() == NULL) {
MonoDomain *domain = mono_domain_get();
if (Engine::get_singleton()->is_editor_hint() && domain == NULL) {
CRASH_COND(Thread::get_caller_id() == Thread::get_main_id());
@ -3027,8 +3269,8 @@ RES ResourceFormatLoaderCSharpScript::load(const String &p_path, const String &p
// because this may be called by one of the editor's worker threads.
// Attach this thread temporarily to reload the script.
if (SCRIPTS_DOMAIN) {
MonoThread *mono_thread = mono_thread_attach(SCRIPTS_DOMAIN);
if (domain) {
MonoThread *mono_thread = mono_thread_attach(domain);
CRASH_COND(mono_thread == NULL);
script->reload();
mono_thread_detach(mono_thread);
@ -3128,5 +3370,7 @@ CSharpLanguage::StringNameCache::StringNameCache() {
_get_property_list = StaticCString::create("_get_property_list");
_notification = StaticCString::create("_notification");
_script_source = StaticCString::create("script/source");
on_before_serialize = StaticCString::create("OnBeforeSerialize");
on_after_deserialize = StaticCString::create("OnAfterDeserialize");
dotctor = StaticCString::create(".ctor");
}

View file

@ -41,6 +41,10 @@
#include "mono_gd/gd_mono_header.h"
#include "mono_gd/gd_mono_internals.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_plugin.h"
#endif
class CSharpScript;
class CSharpInstance;
class CSharpLanguage;
@ -92,6 +96,8 @@ class CSharpScript : public Script {
Set<ObjectID> pending_reload_instances;
Map<ObjectID, StateBackup> pending_reload_state;
StringName tied_class_name_for_reload;
StringName tied_class_namespace_for_reload;
#endif
String source;
@ -125,9 +131,10 @@ class CSharpScript : public Script {
void load_script_signals(GDMonoClass *p_class, GDMonoClass *p_native_class);
bool _get_signal(GDMonoClass *p_class, GDMonoClass *p_delegate, Vector<Argument> &params);
void _update_member_info_no_exports();
bool _update_exports();
#ifdef TOOLS_ENABLED
bool _get_member_export(IMonoClassMember *p_member, PropertyInfo &r_prop_info, bool &r_exported);
bool _get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported);
static int _try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string);
#endif
@ -226,6 +233,8 @@ class CSharpInstance : public ScriptInstance {
MultiplayerAPI::RPCMode _member_get_rpc_mode(IMonoClassMember *p_member) const;
void get_properties_state_for_reloading(List<Pair<StringName, Variant> > &r_state);
public:
MonoObject *get_mono_object() const;
@ -276,6 +285,7 @@ struct CSharpScriptBinding {
StringName type_name;
GDMonoClass *wrapper_class;
Ref<MonoGCHandle> gchandle;
Object *owner;
};
class CSharpLanguage : public ScriptLanguage {
@ -305,6 +315,8 @@ class CSharpLanguage : public ScriptLanguage {
StringName _notification;
StringName _script_source;
StringName dotctor; // .ctor
StringName on_before_serialize; // OnBeforeSerialize
StringName on_after_deserialize; // OnAfterDeserialize
StringNameCache();
};
@ -324,6 +336,12 @@ class CSharpLanguage : public ScriptLanguage {
friend class GDMono;
void _on_scripts_domain_unloaded();
#ifdef TOOLS_ENABLED
EditorPlugin *godotsharp_editor;
static void _editor_init_callback();
#endif
public:
StringNameCache string_names;
@ -336,6 +354,8 @@ public:
_FORCE_INLINE_ static CSharpLanguage *get_singleton() { return singleton; }
_FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const { return godotsharp_editor; }
static void release_script_gchandle(Ref<MonoGCHandle> &p_gchandle);
static void release_script_gchandle(MonoObject *p_expected_obj, Ref<MonoGCHandle> &p_gchandle);

View file

@ -1,2 +0,0 @@
# nuget packages
packages

View file

@ -1,425 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using Microsoft.Build.Framework;
namespace GodotSharpTools.Build
{
public class BuildInstance : IDisposable
{
[MethodImpl(MethodImplOptions.InternalCall)]
private extern static void godot_icall_BuildInstance_ExitCallback(string solution, string config, int exitCode);
[MethodImpl(MethodImplOptions.InternalCall)]
private extern static string godot_icall_BuildInstance_get_MSBuildPath();
[MethodImpl(MethodImplOptions.InternalCall)]
private extern static string godot_icall_BuildInstance_get_MonoWindowsBinDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private extern static bool godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows();
[MethodImpl(MethodImplOptions.InternalCall)]
private extern static bool godot_icall_BuildInstance_get_PrintBuildOutput();
private static string GetMSBuildPath()
{
string msbuildPath = godot_icall_BuildInstance_get_MSBuildPath();
if (msbuildPath == null)
throw new FileNotFoundException("Cannot find the MSBuild executable.");
return msbuildPath;
}
private static string MonoWindowsBinDir
{
get
{
string monoWinBinDir = godot_icall_BuildInstance_get_MonoWindowsBinDir();
if (monoWinBinDir == null)
throw new FileNotFoundException("Cannot find the Windows Mono binaries directory.");
return monoWinBinDir;
}
}
private static bool UsingMonoMSBuildOnWindows
{
get
{
return godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows();
}
}
private static bool PrintBuildOutput
{
get
{
return godot_icall_BuildInstance_get_PrintBuildOutput();
}
}
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)
{
List<string> customPropertiesList = new List<string>();
if (customProperties != null)
customPropertiesList.AddRange(customProperties);
string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customPropertiesList);
ProcessStartInfo startInfo = new ProcessStartInfo(GetMSBuildPath(), compilerArgs);
bool redirectOutput = !IsDebugMSBuildRequested() && !PrintBuildOutput;
if (!redirectOutput) // TODO: or if stdout verbose
Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}");
startInfo.RedirectStandardOutput = redirectOutput;
startInfo.RedirectStandardError = redirectOutput;
startInfo.UseShellExecute = false;
if (UsingMonoMSBuildOnWindows)
{
// These environment variables are required for Mono's MSBuild to find the compilers.
// We use the batch files in Mono's bin directory to make sure the compilers are executed with mono.
string monoWinBinDir = MonoWindowsBinDir;
startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat"));
startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat"));
startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat"));
}
// Needed when running from Developer Command Prompt for VS
RemovePlatformVariable(startInfo.EnvironmentVariables);
using (Process process = new Process())
{
process.StartInfo = startInfo;
process.Start();
if (redirectOutput)
{
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");
List<string> customPropertiesList = new List<string>();
if (customProperties != null)
customPropertiesList.AddRange(customProperties);
string compilerArgs = BuildArguments(loggerAssemblyPath, loggerOutputDir, customPropertiesList);
ProcessStartInfo startInfo = new ProcessStartInfo(GetMSBuildPath(), compilerArgs);
bool redirectOutput = !IsDebugMSBuildRequested() && !PrintBuildOutput;
if (!redirectOutput) // TODO: or if stdout verbose
Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}");
startInfo.RedirectStandardOutput = redirectOutput;
startInfo.RedirectStandardError = redirectOutput;
startInfo.UseShellExecute = false;
if (UsingMonoMSBuildOnWindows)
{
// These environment variables are required for Mono's MSBuild to find the compilers.
// We use the batch files in Mono's bin directory to make sure the compilers are executed with mono.
string monoWinBinDir = MonoWindowsBinDir;
startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat"));
startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat"));
startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat"));
}
// 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();
if (redirectOutput)
{
process.BeginOutputReadLine();
process.BeginErrorReadLine();
}
return true;
}
private string BuildArguments(string loggerAssemblyPath, string loggerOutputDir, List<string> customProperties)
{
string arguments = string.Format(@"""{0}"" /v:normal /t:Rebuild ""/p:{1}"" ""/l:{2},{3};{4}""",
solution,
"Configuration=" + config,
typeof(GodotBuildLogger).FullName,
loggerAssemblyPath,
loggerOutputDir
);
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();
}
private static bool IsDebugMSBuildRequested()
{
return Environment.GetEnvironmentVariable("GODOT_DEBUG_MSBUILD")?.Trim() == "1";
}
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(new[] { ';' });
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;
}
}

View file

@ -1,75 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
namespace GodotSharpTools.Editor
{
public static class GodotSharpExport
{
public static void _ExportBegin(string[] features, bool debug, string path, int flags)
{
var featureSet = new HashSet<string>(features);
if (PlatformHasTemplateDir(featureSet))
{
string templateDirName = "data.mono";
if (featureSet.Contains("Windows"))
{
templateDirName += ".windows";
templateDirName += featureSet.Contains("64") ? ".64" : ".32";
}
else if (featureSet.Contains("X11"))
{
templateDirName += ".x11";
templateDirName += featureSet.Contains("64") ? ".64" : ".32";
}
else
{
throw new NotSupportedException("Target platform not supported");
}
templateDirName += debug ? ".release_debug" : ".release";
string templateDirPath = Path.Combine(GetTemplatesDir(), templateDirName);
if (!Directory.Exists(templateDirPath))
throw new FileNotFoundException("Data template directory not found");
string outputDir = new FileInfo(path).Directory.FullName;
string outputDataDir = Path.Combine(outputDir, GetDataDirName());
if (Directory.Exists(outputDataDir))
Directory.Delete(outputDataDir, recursive: true); // Clean first
Directory.CreateDirectory(outputDataDir);
foreach (string dir in Directory.GetDirectories(templateDirPath, "*", SearchOption.AllDirectories))
{
Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(templateDirPath.Length + 1)));
}
foreach (string file in Directory.GetFiles(templateDirPath, "*", SearchOption.AllDirectories))
{
File.Copy(file, Path.Combine(outputDataDir, file.Substring(templateDirPath.Length + 1)));
}
}
}
public static bool PlatformHasTemplateDir(HashSet<string> featureSet)
{
// OSX export templates are contained in a zip, so we place
// our custom template inside it and let Godot do the rest.
return !featureSet.Any(f => new[] {"OSX", "Android"}.Contains(f));
}
[MethodImpl(MethodImplOptions.InternalCall)]
extern static string GetTemplatesDir();
[MethodImpl(MethodImplOptions.InternalCall)]
extern static string GetDataDirName();
}
}

View file

@ -1,17 +0,0 @@
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

View file

@ -1,62 +0,0 @@
using System;
using System.Linq;
using System.Runtime.CompilerServices;
namespace GodotSharpTools.Utils
{
public static class OS
{
[MethodImpl(MethodImplOptions.InternalCall)]
extern static string GetPlatformName();
const string HaikuName = "Haiku";
const string OSXName = "OSX";
const string ServerName = "Server";
const string UWPName = "UWP";
const string WindowsName = "Windows";
const string X11Name = "X11";
public static bool IsHaiku()
{
return HaikuName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsOSX()
{
return OSXName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsServer()
{
return ServerName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsUWP()
{
return UWPName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsWindows()
{
return WindowsName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsX11()
{
return X11Name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
static bool? IsUnixCache = null;
static readonly string[] UnixPlatforms = new string[] { HaikuName, OSXName, ServerName, X11Name };
public static bool IsUnix()
{
if (IsUnixCache.HasValue)
return IsUnixCache.Value;
string osName = GetPlatformName();
IsUnixCache = UnixPlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase));
return IsUnixCache.Value;
}
}
}

View file

@ -0,0 +1,356 @@
# Rider
.idea/
# Visual Studio Code
.vscode/
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/

View file

@ -0,0 +1,186 @@
using System;
using System.IO;
using System.Security;
using Microsoft.Build.Framework;
using GodotTools.Core;
namespace GodotTools.BuildLogger
{
public class GodotBuildLogger : ILogger
{
public static readonly string AssemblyPath = Path.GetFullPath(typeof(GodotBuildLogger).Assembly.Location);
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.");
var parameters = Parameters.Split(new[] {';'});
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);
logStreamWriter = new StreamWriter(logFile);
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 += eventSource_ProjectStarted;
eventSource.TaskStarted += eventSource_TaskStarted;
eventSource.MessageRaised += eventSource_MessageRaised;
eventSource.WarningRaised += eventSource_WarningRaised;
eventSource.ErrorRaised += eventSource_ErrorRaised;
eventSource.ProjectFinished += eventSource_ProjectFinished;
}
void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e)
{
string line = $"{e.File}({e.LineNumber},{e.ColumnNumber}): error {e.Code}: {e.Message}";
if (e.ProjectFile.Length > 0)
line += $" [{e.ProjectFile}]";
WriteLine(line);
string errorLine = $@"error,{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 = $"{e.File}({e.LineNumber},{e.ColumnNumber}): warning {e.Code}: {e.Message}";
if (!string.IsNullOrEmpty(e.ProjectFile))
line += $" [{e.ProjectFile}]";
WriteLine(line);
string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber},{e.Code.CsvEscape()}," +
$@"{e.Message.CsvEscape()},{(e.ProjectFile != null ? e.ProjectFile.CsvEscape() : string.Empty)}";
issuesStreamWriter.WriteLine(warningLine);
}
private 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);
}
}
private void eventSource_TaskStarted(object sender, TaskStartedEventArgs e)
{
// TaskStartedEventArgs adds ProjectFile, TaskFile, TaskName
// To keep this log clean, this logger will ignore these events.
}
private void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e)
{
WriteLine(e.Message);
indent++;
}
private 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", StringComparison.OrdinalIgnoreCase))
{
// 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", StringComparison.OrdinalIgnoreCase))
{
// 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();
}
private bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity)
{
return Verbosity >= checkVerbosity;
}
private StreamWriter logStreamWriter;
private StreamWriter issuesStreamWriter;
private int indent;
}
}

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{6CE9A984-37B1-4F8A-8FE9-609F05F071B3}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>GodotTools.BuildLogger</RootNamespace>
<AssemblyName>GodotTools.BuildLogger</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>portable</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Build.Framework" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="GodotBuildLogger.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj">
<Project>{639e48bd-44e5-4091-8edd-22d36dc0768d}</Project>
<Name>GodotTools.Core</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View file

@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("GodotTools.BuildLogger")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("Godot Engine contributors")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("6CE9A984-37B1-4F8A-8FE9-609F05F071B3")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View file

@ -0,0 +1,38 @@
<?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>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>GodotTools.Core</RootNamespace>
<AssemblyName>GodotTools.Core</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' ">
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="ProcessExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StringExtensions.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,38 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace GodotTools.Core
{
public static class ProcessExtensions
{
public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default(CancellationToken))
{
var tcs = new TaskCompletionSource<bool>();
void ProcessExited(object sender, EventArgs e)
{
tcs.TrySetResult(true);
}
process.EnableRaisingEvents = true;
process.Exited += ProcessExited;
try
{
if (process.HasExited)
return;
using (cancellationToken.Register(() => tcs.TrySetCanceled()))
{
await tcs.Task;
}
}
finally
{
process.Exited -= ProcessExited;
}
}
}
}

View file

@ -0,0 +1,26 @@
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("GodotTools.Core")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("Godot Engine contributors")]
[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("")]

View file

@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace GodotSharpTools
namespace GodotTools.Core
{
public static class StringExtensions
{
@ -25,7 +26,7 @@ namespace GodotSharpTools
path = path.Replace('\\', '/');
string[] parts = path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
string[] parts = path.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
path = string.Join(Path.DirectorySeparatorChar.ToString(), parts).Trim();
@ -37,18 +38,40 @@ namespace GodotSharpTools
public static bool IsAbsolutePath(this string path)
{
return path.StartsWith("/", StringComparison.Ordinal) ||
path.StartsWith("\\", StringComparison.Ordinal) ||
path.StartsWith(driveRoot, StringComparison.Ordinal);
path.StartsWith("\\", StringComparison.Ordinal) ||
path.StartsWith(driveRoot, StringComparison.Ordinal);
}
public static string CsvEscape(this string value, char delimiter = ',')
{
bool hasSpecialChar = value.IndexOfAny(new char[] { '\"', '\n', '\r', delimiter }) != -1;
bool hasSpecialChar = value.IndexOfAny(new char[] {'\"', '\n', '\r', delimiter}) != -1;
if (hasSpecialChar)
return "\"" + value.Replace("\"", "\"\"") + "\"";
return value;
}
public static string ToSafeDirName(this string dirName, bool allowDirSeparator)
{
var invalidChars = new List<string> {":", "*", "?", "\"", "<", ">", "|"};
if (allowDirSeparator)
{
// Directory separators are allowed, but disallow ".." to avoid going up the filesystem
invalidChars.Add("..");
}
else
{
invalidChars.Add("/");
}
string safeDirName = dirName.Replace("\\", "/").Trim();
foreach (string invalidChar in invalidChars)
safeDirName = safeDirName.Replace(invalidChar, "-");
return safeDirName;
}
}
}

View file

@ -0,0 +1,15 @@
namespace GodotTools
{
public static class ApiAssemblyNames
{
public const string SolutionName = "GodotSharp";
public const string Core = "GodotSharp";
public const string Editor = "GodotSharpEditor";
}
public enum ApiAssemblyType
{
Core,
Editor
}
}

View file

@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.IO;
namespace GodotTools.ProjectEditor
{
public static class ApiSolutionGenerator
{
public static void GenerateApiSolution(string solutionDir,
string coreProjDir, IEnumerable<string> coreCompileItems,
string editorProjDir, IEnumerable<string> editorCompileItems)
{
var solution = new DotNetSolution(ApiAssemblyNames.SolutionName);
solution.DirectoryPath = solutionDir;
// GodotSharp project
const string coreApiAssemblyName = ApiAssemblyNames.Core;
string coreGuid = ProjectGenerator.GenCoreApiProject(coreProjDir, coreCompileItems);
var coreProjInfo = new DotNetSolution.ProjectInfo
{
Guid = coreGuid,
PathRelativeToSolution = Path.Combine(coreApiAssemblyName, $"{coreApiAssemblyName}.csproj")
};
coreProjInfo.Configs.Add("Debug");
coreProjInfo.Configs.Add("Release");
solution.AddNewProject(coreApiAssemblyName, coreProjInfo);
// GodotSharpEditor project
const string editorApiAssemblyName = ApiAssemblyNames.Editor;
string editorGuid = ProjectGenerator.GenEditorApiProject(editorProjDir,
$"../{coreApiAssemblyName}/{coreApiAssemblyName}.csproj", editorCompileItems);
var editorProjInfo = new DotNetSolution.ProjectInfo();
editorProjInfo.Guid = editorGuid;
editorProjInfo.PathRelativeToSolution = Path.Combine(editorApiAssemblyName, $"{editorApiAssemblyName}.csproj");
editorProjInfo.Configs.Add("Debug");
editorProjInfo.Configs.Add("Release");
solution.AddNewProject(editorApiAssemblyName, editorProjInfo);
// Save solution
solution.Save();
}
}
}

View file

@ -0,0 +1,122 @@
using GodotTools.Core;
using System.Collections.Generic;
using System.IO;
namespace GodotTools.ProjectEditor
{
public class DotNetSolution
{
private string directoryPath;
private readonly Dictionary<string, ProjectInfo> projects = new Dictionary<string, ProjectInfo>();
public string Name { get; }
public string DirectoryPath
{
get => directoryPath;
set => directoryPath = value.IsAbsolutePath() ? value : Path.GetFullPath(value);
}
public class ProjectInfo
{
public string Guid;
public string PathRelativeToSolution;
public List<string> Configs = new List<string>();
}
public void AddNewProject(string name, ProjectInfo projectInfo)
{
projects[name] = projectInfo;
}
public bool HasProject(string name)
{
return projects.ContainsKey(name);
}
public ProjectInfo GetProjectInfo(string name)
{
return projects[name];
}
public bool RemoveProject(string name)
{
return projects.Remove(name);
}
public void Save()
{
if (!Directory.Exists(DirectoryPath))
throw new FileNotFoundException("The solution directory does not exist.");
string projectsDecl = string.Empty;
string slnPlatformsCfg = string.Empty;
string projPlatformsCfg = string.Empty;
bool isFirstProject = true;
foreach (var pair in projects)
{
string name = pair.Key;
ProjectInfo projectInfo = pair.Value;
if (!isFirstProject)
projectsDecl += "\n";
projectsDecl += string.Format(ProjectDeclaration,
name, projectInfo.PathRelativeToSolution.Replace("/", "\\"), projectInfo.Guid);
for (int i = 0; i < projectInfo.Configs.Count; i++)
{
string config = projectInfo.Configs[i];
if (i != 0 || !isFirstProject)
{
slnPlatformsCfg += "\n";
projPlatformsCfg += "\n";
}
slnPlatformsCfg += string.Format(SolutionPlatformsConfig, config);
projPlatformsCfg += string.Format(ProjectPlatformsConfig, projectInfo.Guid, config);
}
isFirstProject = false;
}
string solutionPath = Path.Combine(DirectoryPath, Name + ".sln");
string content = string.Format(SolutionTemplate, projectsDecl, slnPlatformsCfg, projPlatformsCfg);
File.WriteAllText(solutionPath, content);
}
public DotNetSolution(string name)
{
Name = name;
}
const string SolutionTemplate =
@"Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
{0}
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
{1}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2}
EndGlobalSection
EndGlobal
";
const string ProjectDeclaration =
@"Project(""{{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}}"") = ""{0}"", ""{1}"", ""{{{2}}}""
EndProject";
const string SolutionPlatformsConfig =
@" {0}|Any CPU = {0}|Any CPU";
const string ProjectPlatformsConfig =
@" {{{0}}}.{1}|Any CPU.ActiveCfg = {1}|Any CPU
{{{0}}}.{1}|Any CPU.Build.0 = {1}|Any CPU";
}
}

View file

@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<?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>
<RootNamespace>GodotTools.ProjectEditor</RootNamespace>
<AssemblyName>GodotTools.ProjectEditor</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath>
</PropertyGroup>
@ -21,7 +21,6 @@
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>portable</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
@ -31,25 +30,35 @@
<ItemGroup>
<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>
<!--
When building Godot with 'mono_glue=no' SCons will build this project alone instead of the
entire solution. $(SolutionDir) is not defined in that case, so we need to workaround that.
We make SCons restore the NuGet packages in the project directory instead in this case.
-->
<HintPath Condition=" '$(SolutionDir)' != '' ">$(SolutionDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath>
<HintPath>$(ProjectDir)\packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath>
<HintPath>packages\DotNet.Glob.2.1.1\lib\net45\DotNet.Glob.dll</HintPath> <!-- Are you happy CI? -->
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="StringExtensions.cs" />
<Compile Include="Build\BuildSystem.cs" />
<Compile Include="Editor\MonoDevelopInstance.cs" />
<Compile Include="Project\ProjectExtensions.cs" />
<Compile Include="Project\IdentifierUtils.cs" />
<Compile Include="Project\ProjectGenerator.cs" />
<Compile Include="Project\ProjectUtils.cs" />
<Compile Include="ApiAssembliesInfo.cs" />
<Compile Include="ApiSolutionGenerator.cs" />
<Compile Include="DotNetSolution.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Utils\OS.cs" />
<Compile Include="Editor\GodotSharpExport.cs" />
<Compile Include="IdentifierUtils.cs" />
<Compile Include="ProjectExtensions.cs" />
<Compile Include="ProjectGenerator.cs" />
<Compile Include="ProjectUtils.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj">
<Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project>
<Name>GodotTools.Core</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>
</Project>

View file

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Text;
namespace GodotSharpTools.Project
namespace GodotTools.ProjectEditor
{
public static class IdentifierUtils
{
@ -12,7 +12,7 @@ namespace GodotSharpTools.Project
if (string.IsNullOrEmpty(qualifiedIdentifier))
throw new ArgumentException($"{nameof(qualifiedIdentifier)} cannot be empty", nameof(qualifiedIdentifier));
string[] identifiers = qualifiedIdentifier.Split(new[] { '.' });
string[] identifiers = qualifiedIdentifier.Split('.');
for (int i = 0; i < identifiers.Length; i++)
{
@ -66,8 +66,6 @@ namespace GodotSharpTools.Project
if (identifierBuilder.Length > startIndex || @char == '_')
identifierBuilder.Append(@char);
break;
default:
break;
}
}
@ -97,14 +95,14 @@ namespace GodotSharpTools.Project
}
else
{
if (_doubleUnderscoreKeywords.Contains(value))
if (DoubleUnderscoreKeywords.Contains(value))
return true;
}
return _keywords.Contains(value);
return Keywords.Contains(value);
}
static HashSet<string> _doubleUnderscoreKeywords = new HashSet<string>
private static readonly HashSet<string> DoubleUnderscoreKeywords = new HashSet<string>
{
"__arglist",
"__makeref",
@ -112,7 +110,7 @@ namespace GodotSharpTools.Project
"__refvalue",
};
static HashSet<string> _keywords = new HashSet<string>
private static readonly HashSet<string> Keywords = new HashSet<string>
{
"as",
"do",

View file

@ -1,8 +1,9 @@
using GodotTools.Core;
using System;
using DotNet.Globbing;
using Microsoft.Build.Construction;
namespace GodotSharpTools.Project
namespace GodotTools.ProjectEditor
{
public static class ProjectExtensions
{

View file

@ -1,17 +1,19 @@
using GodotTools.Core;
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Build.Construction;
namespace GodotSharpTools.Project
namespace GodotTools.ProjectEditor
{
public static class ProjectGenerator
{
public const string CoreApiProjectName = "GodotSharp";
public const string EditorApiProjectName = "GodotSharpEditor";
const string CoreApiProjectGuid = "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}";
const string EditorApiProjectGuid = "{8FBEC238-D944-4074-8548-B3B524305905}";
private const string CoreApiProjectName = "GodotSharp";
private const string EditorApiProjectName = "GodotSharpEditor";
private const string CoreApiProjectGuid = "{AEBF0036-DA76-4341-B651-A3F2856AB2FA}";
private const string EditorApiProjectGuid = "{8FBEC238-D944-4074-8548-B3B524305905}";
public static string GenCoreApiProject(string dir, string[] compileItems)
public static string GenCoreApiProject(string dir, IEnumerable<string> compileItems)
{
string path = Path.Combine(dir, CoreApiProjectName + ".csproj");
@ -24,8 +26,8 @@ namespace GodotSharpTools.Project
mainGroup.SetProperty("BaseIntermediateOutputPath", "obj");
GenAssemblyInfoFile(root, dir, CoreApiProjectName,
new string[] { "[assembly: InternalsVisibleTo(\"" + EditorApiProjectName + "\")]" },
new string[] { "System.Runtime.CompilerServices" });
new[] {"[assembly: InternalsVisibleTo(\"" + EditorApiProjectName + "\")]"},
new[] {"System.Runtime.CompilerServices"});
foreach (var item in compileItems)
{
@ -37,7 +39,7 @@ namespace GodotSharpTools.Project
return CoreApiProjectGuid;
}
public static string GenEditorApiProject(string dir, string coreApiProjPath, string[] compileItems)
public static string GenEditorApiProject(string dir, string coreApiProjPath, IEnumerable<string> compileItems)
{
string path = Path.Combine(dir, EditorApiProjectName + ".csproj");
@ -64,7 +66,7 @@ namespace GodotSharpTools.Project
return EditorApiProjectGuid;
}
public static string GenGameProject(string dir, string name, string[] compileItems)
public static string GenGameProject(string dir, string name, IEnumerable<string> compileItems)
{
string path = Path.Combine(dir, name + ".csproj");
@ -74,6 +76,8 @@ namespace GodotSharpTools.Project
mainGroup.SetProperty("OutputPath", Path.Combine(".mono", "temp", "bin", "$(Configuration)"));
mainGroup.SetProperty("BaseIntermediateOutputPath", Path.Combine(".mono", "temp", "obj"));
mainGroup.SetProperty("IntermediateOutputPath", Path.Combine("$(BaseIntermediateOutputPath)", "$(Configuration)"));
mainGroup.SetProperty("ApiConfiguration", "Debug").Condition = " '$(Configuration)' != 'Release' ";
mainGroup.SetProperty("ApiConfiguration", "Release").Condition = " '$(Configuration)' == 'Release' ";
var toolsGroup = root.AddPropertyGroup();
toolsGroup.Condition = " '$(Configuration)|$(Platform)' == 'Tools|AnyCPU' ";
@ -86,11 +90,13 @@ namespace GodotSharpTools.Project
toolsGroup.AddProperty("ConsolePause", "false");
var coreApiRef = root.AddItem("Reference", CoreApiProjectName);
coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", CoreApiProjectName + ".dll"));
coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", CoreApiProjectName + ".dll"));
coreApiRef.AddMetadata("Private", "False");
var editorApiRef = root.AddItem("Reference", EditorApiProjectName);
editorApiRef.Condition = " '$(Configuration)' == 'Tools' ";
editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", EditorApiProjectName + ".dll"));
editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", EditorApiProjectName + ".dll"));
editorApiRef.AddMetadata("Private", "False");
@ -108,7 +114,6 @@ namespace GodotSharpTools.Project
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);
@ -124,12 +129,9 @@ namespace GodotSharpTools.Project
string assemblyLinesText = string.Empty;
if (assemblyLines != null)
{
foreach (var assemblyLine in assemblyLines)
assemblyLinesText += string.Join("\n", assemblyLines) + "\n";
}
assemblyLinesText += string.Join("\n", assemblyLines) + "\n";
string content = string.Format(assemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText);
string content = string.Format(AssemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText);
string assemblyInfoFile = Path.Combine(propertiesDir, "AssemblyInfo.cs");
@ -194,8 +196,8 @@ namespace GodotSharpTools.Project
}
}
private const string assemblyInfoTemplate =
@"using System.Reflection;{0}
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.

View file

@ -1,9 +1,10 @@
using GodotTools.Core;
using System.Collections.Generic;
using System.IO;
using DotNet.Globbing;
using Microsoft.Build.Construction;
namespace GodotSharpTools.Project
namespace GodotTools.ProjectEditor
{
public static class ProjectUtils
{
@ -53,7 +54,7 @@ namespace GodotSharpTools.Project
var glob = Glob.Parse(normalizedInclude, globOptions);
// TODO Check somehow if path has no blog to avoid the following loop...
// TODO Check somehow if path has no blob to avoid the following loop...
foreach (var existingFile in existingFiles)
{

View file

@ -4,12 +4,12 @@ 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: AssemblyTitle("GodotTools.ProjectEditor")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("ignacio")]
[assembly: AssemblyCopyright("Godot Engine contributors")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

View file

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

View file

@ -0,0 +1,172 @@
using GodotTools.Core;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using GodotTools.BuildLogger;
using GodotTools.Internals;
using GodotTools.Utils;
using Directory = System.IO.Directory;
namespace GodotTools.Build
{
public static class BuildSystem
{
private static string GetMsBuildPath()
{
string msbuildPath = MsBuildFinder.FindMsBuild();
if (msbuildPath == null)
throw new FileNotFoundException("Cannot find the MSBuild executable.");
return msbuildPath;
}
private static string MonoWindowsBinDir
{
get
{
string monoWinBinDir = Path.Combine(Internal.MonoWindowsInstallRoot, "bin");
if (!Directory.Exists(monoWinBinDir))
throw new FileNotFoundException("Cannot find the Windows Mono install bin directory.");
return monoWinBinDir;
}
}
private static Godot.EditorSettings EditorSettings =>
GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
private static bool UsingMonoMsBuildOnWindows
{
get
{
if (OS.IsWindows())
{
return (GodotSharpBuilds.BuildTool) EditorSettings.GetSetting("mono/builds/build_tool")
== GodotSharpBuilds.BuildTool.MsBuildMono;
}
return false;
}
}
private static bool PrintBuildOutput =>
(bool) EditorSettings.GetSetting("mono/builds/print_build_output");
private static Process LaunchBuild(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
{
var customPropertiesList = new List<string>();
if (customProperties != null)
customPropertiesList.AddRange(customProperties);
string compilerArgs = BuildArguments(solution, config, loggerOutputDir, customPropertiesList);
var startInfo = new ProcessStartInfo(GetMsBuildPath(), compilerArgs);
bool redirectOutput = !IsDebugMsBuildRequested() && !PrintBuildOutput;
if (!redirectOutput || Godot.OS.IsStdoutVerbose())
Console.WriteLine($"Running: \"{startInfo.FileName}\" {startInfo.Arguments}");
startInfo.RedirectStandardOutput = redirectOutput;
startInfo.RedirectStandardError = redirectOutput;
startInfo.UseShellExecute = false;
if (UsingMonoMsBuildOnWindows)
{
// These environment variables are required for Mono's MSBuild to find the compilers.
// We use the batch files in Mono's bin directory to make sure the compilers are executed with mono.
string monoWinBinDir = MonoWindowsBinDir;
startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat"));
startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat"));
startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat"));
}
// Needed when running from Developer Command Prompt for VS
RemovePlatformVariable(startInfo.EnvironmentVariables);
var process = new Process {StartInfo = startInfo};
process.Start();
if (redirectOutput)
{
process.BeginOutputReadLine();
process.BeginErrorReadLine();
}
return process;
}
public static int Build(MonoBuildInfo monoBuildInfo)
{
return Build(monoBuildInfo.Solution, monoBuildInfo.Configuration,
monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties);
}
public static async Task<int> BuildAsync(MonoBuildInfo monoBuildInfo)
{
return await BuildAsync(monoBuildInfo.Solution, monoBuildInfo.Configuration,
monoBuildInfo.LogsDirPath, monoBuildInfo.CustomProperties);
}
public static int Build(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
{
using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties))
{
process.WaitForExit();
return process.ExitCode;
}
}
public static async Task<int> BuildAsync(string solution, string config, string loggerOutputDir, IEnumerable<string> customProperties = null)
{
using (var process = LaunchBuild(solution, config, loggerOutputDir, customProperties))
{
await process.WaitForExitAsync();
return process.ExitCode;
}
}
private static string BuildArguments(string solution, string config, string loggerOutputDir, List<string> customProperties)
{
string arguments = $@"""{solution}"" /v:normal /t:Rebuild ""/p:{"Configuration=" + config}"" " +
$@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{loggerOutputDir}""";
foreach (string customProperty in customProperties)
{
arguments += " /p:" + customProperty;
}
return arguments;
}
private static void RemovePlatformVariable(StringDictionary environmentVariables)
{
// EnvironmentVariables is case sensitive? Seriously?
var 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 static bool IsDebugMsBuildRequested()
{
return Environment.GetEnvironmentVariable("GODOT_DEBUG_MSBUILD")?.Trim() == "1";
}
}
}

View file

@ -0,0 +1,210 @@
using System;
using System.Collections.Generic;
using System.IO;
using Godot;
using GodotTools.Internals;
using Directory = System.IO.Directory;
using Environment = System.Environment;
using File = System.IO.File;
using Path = System.IO.Path;
using OS = GodotTools.Utils.OS;
namespace GodotTools.Build
{
public static class MsBuildFinder
{
private static string _msbuildToolsPath = string.Empty;
private static string _msbuildUnixPath = string.Empty;
private static string _xbuildUnixPath = string.Empty;
public static string FindMsBuild()
{
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
var buildTool = (GodotSharpBuilds.BuildTool) editorSettings.GetSetting("mono/builds/build_tool");
if (OS.IsWindows())
{
switch (buildTool)
{
case GodotSharpBuilds.BuildTool.MsBuildVs:
{
if (_msbuildToolsPath.Empty() || !File.Exists(_msbuildToolsPath))
{
// Try to search it again if it wasn't found last time or if it was removed from its location
_msbuildToolsPath = FindMsBuildToolsPathOnWindows();
if (_msbuildToolsPath.Empty())
{
throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildVs}'. Tried with path: {_msbuildToolsPath}");
}
}
if (!_msbuildToolsPath.EndsWith("\\"))
_msbuildToolsPath += "\\";
return Path.Combine(_msbuildToolsPath, "MSBuild.exe");
}
case GodotSharpBuilds.BuildTool.MsBuildMono:
{
string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat");
if (!File.Exists(msbuildPath))
{
throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameMsbuildMono}'. Tried with path: {msbuildPath}");
}
return msbuildPath;
}
case GodotSharpBuilds.BuildTool.XBuild:
{
string xbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "xbuild.bat");
if (!File.Exists(xbuildPath))
{
throw new FileNotFoundException($"Cannot find executable for '{GodotSharpBuilds.PropNameXbuild}'. Tried with path: {xbuildPath}");
}
return xbuildPath;
}
default:
throw new IndexOutOfRangeException("Invalid build tool in editor settings");
}
}
if (OS.IsUnix())
{
if (buildTool == GodotSharpBuilds.BuildTool.XBuild)
{
if (_xbuildUnixPath.Empty() || !File.Exists(_xbuildUnixPath))
{
// Try to search it again if it wasn't found last time or if it was removed from its location
_xbuildUnixPath = FindBuildEngineOnUnix("msbuild");
}
if (_xbuildUnixPath.Empty())
{
throw new FileNotFoundException($"Cannot find binary for '{GodotSharpBuilds.PropNameXbuild}'");
}
}
else
{
if (_msbuildUnixPath.Empty() || !File.Exists(_msbuildUnixPath))
{
// Try to search it again if it wasn't found last time or if it was removed from its location
_msbuildUnixPath = FindBuildEngineOnUnix("msbuild");
}
if (_msbuildUnixPath.Empty())
{
throw new FileNotFoundException($"Cannot find binary for '{GodotSharpBuilds.PropNameMsbuildMono}'");
}
}
return buildTool != GodotSharpBuilds.BuildTool.XBuild ? _msbuildUnixPath : _xbuildUnixPath;
}
throw new PlatformNotSupportedException();
}
private static IEnumerable<string> MsBuildHintDirs
{
get
{
var result = new List<string>();
if (OS.IsOSX())
{
result.Add("/Library/Frameworks/Mono.framework/Versions/Current/bin/");
result.Add("/usr/local/var/homebrew/linked/mono/bin/");
}
result.Add("/opt/novell/mono/bin/");
return result;
}
}
private static string FindBuildEngineOnUnix(string name)
{
string ret = OS.PathWhich(name);
if (!ret.Empty())
return ret;
string retFallback = OS.PathWhich($"{name}.exe");
if (!retFallback.Empty())
return retFallback;
foreach (string hintDir in MsBuildHintDirs)
{
string hintPath = Path.Combine(hintDir, name);
if (File.Exists(hintPath))
return hintPath;
}
return string.Empty;
}
private static string FindMsBuildToolsPathOnWindows()
{
if (!OS.IsWindows())
throw new PlatformNotSupportedException();
// Try to find 15.0 with vswhere
string vsWherePath = Environment.GetEnvironmentVariable(Internal.GodotIs32Bits() ? "ProgramFiles" : "ProgramFiles(x86)");
vsWherePath += "\\Microsoft Visual Studio\\Installer\\vswhere.exe";
var vsWhereArgs = new[] {"-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"};
var outputArray = new Godot.Collections.Array<string>();
int exitCode = Godot.OS.Execute(vsWherePath, vsWhereArgs,
blocking: true, output: (Godot.Collections.Array) outputArray);
if (exitCode == 0)
return string.Empty;
if (outputArray.Count == 0)
return string.Empty;
var lines = outputArray[1].Split('\n');
foreach (string line in lines)
{
int sepIdx = line.IndexOf(':');
if (sepIdx <= 0)
continue;
string key = line.Substring(0, sepIdx); // No need to trim
if (key != "installationPath")
continue;
string value = line.Substring(sepIdx + 1).StripEdges();
if (value.Empty())
throw new FormatException("installationPath value is empty");
if (!value.EndsWith("\\"))
value += "\\";
// Since VS2019, the directory is simply named "Current"
string msbuildDir = Path.Combine(value, "MSBuild\\Current\\Bin");
if (Directory.Exists(msbuildDir))
return msbuildDir;
// Directory name "15.0" is used in VS 2017
return Path.Combine(value, "MSBuild\\15.0\\Bin");
}
return string.Empty;
}
}
}

View file

@ -0,0 +1,115 @@
using Godot;
using System;
using System.Collections.Generic;
using Godot.Collections;
using GodotTools.Internals;
using GodotTools.ProjectEditor;
using File = GodotTools.Utils.File;
using Directory = GodotTools.Utils.Directory;
namespace GodotTools
{
public static class CSharpProject
{
public static string GenerateGameProject(string dir, string name, IEnumerable<string> files = null)
{
try
{
return ProjectGenerator.GenGameProject(dir, name, files);
}
catch (Exception e)
{
GD.PushError(e.ToString());
return string.Empty;
}
}
public static void AddItem(string projectPath, string itemType, string include)
{
if (!(bool) Internal.GlobalDef("mono/project/auto_update_project", true))
return;
ProjectUtils.AddItemToProjectChecked(projectPath, itemType, include);
}
private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static ulong ConvertToTimestamp(this DateTime value)
{
TimeSpan elapsedTime = value - Epoch;
return (ulong) elapsedTime.TotalSeconds;
}
public static void GenerateScriptsMetadata(string projectPath, string outputPath)
{
if (File.Exists(outputPath))
File.Delete(outputPath);
var oldDict = Internal.GetScriptsMetadataOrNothing();
var newDict = new Godot.Collections.Dictionary<string, object>();
foreach (var includeFile in ProjectUtils.GetIncludeFiles(projectPath, "Compile"))
{
string projectIncludeFile = ("res://" + includeFile).SimplifyGodotPath();
ulong modifiedTime = File.GetLastWriteTime(projectIncludeFile).ConvertToTimestamp();
if (oldDict.TryGetValue(projectIncludeFile, out var oldFileVar))
{
var oldFileDict = (Dictionary) oldFileVar;
if (ulong.TryParse((string) oldFileDict["modified_time"], out ulong storedModifiedTime))
{
if (storedModifiedTime == modifiedTime)
{
// No changes so no need to parse again
newDict[projectIncludeFile] = oldFileDict;
continue;
}
}
}
ScriptClassParser.ParseFileOrThrow(projectIncludeFile, out var classes);
string searchName = System.IO.Path.GetFileNameWithoutExtension(projectIncludeFile);
var classDict = new Dictionary();
foreach (var classDecl in classes)
{
if (classDecl.BaseCount == 0)
continue; // Does not inherit nor implement anything, so it can't be a script class
string classCmp = classDecl.Nested ?
classDecl.Name.Substring(classDecl.Name.LastIndexOf(".", StringComparison.Ordinal) + 1) :
classDecl.Name;
if (classCmp != searchName)
continue;
classDict["namespace"] = classDecl.Namespace;
classDict["class_name"] = classDecl.Name;
classDict["nested"] = classDecl.Nested;
break;
}
if (classDict.Count == 0)
continue; // Not found
newDict[projectIncludeFile] = new Dictionary {["modified_time"] = $"{modifiedTime}", ["class"] = classDict};
}
if (newDict.Count > 0)
{
string json = JSON.Print(newDict);
string baseDir = outputPath.GetBaseDir();
if (!Directory.Exists(baseDir))
Directory.CreateDirectory(baseDir);
File.WriteAllText(outputPath, json);
}
}
}
}

View file

@ -0,0 +1,396 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using GodotTools.Build;
using GodotTools.Internals;
using GodotTools.Utils;
using Error = Godot.Error;
using File = GodotTools.Utils.File;
using Directory = GodotTools.Utils.Directory;
namespace GodotTools
{
public static class GodotSharpBuilds
{
private static readonly List<MonoBuildInfo> BuildsInProgress = new List<MonoBuildInfo>();
public const string PropNameMsbuildMono = "MSBuild (Mono)";
public const string PropNameMsbuildVs = "MSBuild (VS Build Tools)";
public const string PropNameXbuild = "xbuild (Deprecated)";
public const string MsBuildIssuesFileName = "msbuild_issues.csv";
public const string MsBuildLogFileName = "msbuild_log.txt";
public enum BuildTool
{
MsBuildMono,
MsBuildVs,
XBuild // Deprecated
}
private static void RemoveOldIssuesFile(MonoBuildInfo buildInfo)
{
var issuesFile = GetIssuesFilePath(buildInfo);
if (!File.Exists(issuesFile))
return;
File.Delete(issuesFile);
}
private static string _ApiFolderName(ApiAssemblyType apiType)
{
ulong apiHash = apiType == ApiAssemblyType.Core ?
Internal.GetCoreApiHash() :
Internal.GetEditorApiHash();
return $"{apiHash}_{BindingsGenerator.Version}_{BindingsGenerator.CsGlueVersion}";
}
private static void ShowBuildErrorDialog(string message)
{
GodotSharpEditor.Instance.ShowErrorDialog(message, "Build error");
GodotSharpEditor.Instance.MonoBottomPanel.ShowBuildTab();
}
public static void RestartBuild(MonoBuildTab buildTab) => throw new NotImplementedException();
public static void StopBuild(MonoBuildTab buildTab) => throw new NotImplementedException();
private static string GetLogFilePath(MonoBuildInfo buildInfo)
{
return Path.Combine(buildInfo.LogsDirPath, MsBuildLogFileName);
}
private static string GetIssuesFilePath(MonoBuildInfo buildInfo)
{
return Path.Combine(Godot.ProjectSettings.LocalizePath(buildInfo.LogsDirPath), MsBuildIssuesFileName);
}
private static void PrintVerbose(string text)
{
if (Godot.OS.IsStdoutVerbose())
Godot.GD.Print(text);
}
public static bool Build(MonoBuildInfo buildInfo)
{
if (BuildsInProgress.Contains(buildInfo))
throw new InvalidOperationException("A build is already in progress");
BuildsInProgress.Add(buildInfo);
try
{
MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo);
buildTab.OnBuildStart();
// Required in order to update the build tasks list
Internal.GodotMainIteration();
try
{
RemoveOldIssuesFile(buildInfo);
}
catch (IOException e)
{
buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}");
Console.Error.WriteLine(e);
}
try
{
int exitCode = BuildSystem.Build(buildInfo);
if (exitCode != 0)
PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error);
return exitCode == 0;
}
catch (Exception e)
{
buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}");
Console.Error.WriteLine(e);
return false;
}
}
finally
{
BuildsInProgress.Remove(buildInfo);
}
}
public static async Task<bool> BuildAsync(MonoBuildInfo buildInfo)
{
if (BuildsInProgress.Contains(buildInfo))
throw new InvalidOperationException("A build is already in progress");
BuildsInProgress.Add(buildInfo);
try
{
MonoBuildTab buildTab = GodotSharpEditor.Instance.MonoBottomPanel.GetBuildTabFor(buildInfo);
try
{
RemoveOldIssuesFile(buildInfo);
}
catch (IOException e)
{
buildTab.OnBuildExecFailed($"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}");
Console.Error.WriteLine(e);
}
try
{
int exitCode = await BuildSystem.BuildAsync(buildInfo);
if (exitCode != 0)
PrintVerbose($"MSBuild exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}");
buildTab.OnBuildExit(exitCode == 0 ? MonoBuildTab.BuildResults.Success : MonoBuildTab.BuildResults.Error);
return exitCode == 0;
}
catch (Exception e)
{
buildTab.OnBuildExecFailed($"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}");
Console.Error.WriteLine(e);
return false;
}
}
finally
{
BuildsInProgress.Remove(buildInfo);
}
}
public static bool BuildApiSolution(string apiSlnDir, string config)
{
string apiSlnFile = Path.Combine(apiSlnDir, $"{ApiAssemblyNames.SolutionName}.sln");
string coreApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Core, "bin", config);
string coreApiAssemblyFile = Path.Combine(coreApiAssemblyDir, $"{ApiAssemblyNames.Core}.dll");
string editorApiAssemblyDir = Path.Combine(apiSlnDir, ApiAssemblyNames.Editor, "bin", config);
string editorApiAssemblyFile = Path.Combine(editorApiAssemblyDir, $"{ApiAssemblyNames.Editor}.dll");
if (File.Exists(coreApiAssemblyFile) && File.Exists(editorApiAssemblyFile))
return true; // The assemblies are in the output folder; assume the solution is already built
var apiBuildInfo = new MonoBuildInfo(apiSlnFile, config);
// TODO Replace this global NoWarn with '#pragma warning' directives on generated files,
// once we start to actively document manually maintained C# classes
apiBuildInfo.CustomProperties.Add("NoWarn=1591"); // Ignore missing documentation warnings
if (Build(apiBuildInfo))
return true;
ShowBuildErrorDialog($"Failed to build {ApiAssemblyNames.SolutionName} solution.");
return false;
}
private static bool CopyApiAssembly(string srcDir, string dstDir, string assemblyName, ApiAssemblyType apiType)
{
// Create destination directory if needed
if (!Directory.Exists(dstDir))
{
try
{
Directory.CreateDirectory(dstDir);
}
catch (IOException e)
{
ShowBuildErrorDialog($"Failed to create destination directory for the API assemblies. Exception message: {e.Message}");
return false;
}
}
string assemblyFile = assemblyName + ".dll";
string assemblySrc = Path.Combine(srcDir, assemblyFile);
string assemblyDst = Path.Combine(dstDir, assemblyFile);
if (!File.Exists(assemblyDst) || File.GetLastWriteTime(assemblySrc) > File.GetLastWriteTime(assemblyDst) ||
Internal.MetadataIsApiAssemblyInvalidated(apiType))
{
string xmlFile = $"{assemblyName}.xml";
string pdbFile = $"{assemblyName}.pdb";
try
{
File.Copy(Path.Combine(srcDir, xmlFile), Path.Combine(dstDir, xmlFile));
}
catch (IOException e)
{
Godot.GD.PushWarning(e.ToString());
}
try
{
File.Copy(Path.Combine(srcDir, pdbFile), Path.Combine(dstDir, pdbFile));
}
catch (IOException e)
{
Godot.GD.PushWarning(e.ToString());
}
try
{
File.Copy(assemblySrc, assemblyDst);
}
catch (IOException e)
{
ShowBuildErrorDialog($"Failed to copy {assemblyFile}. Exception message: {e.Message}");
return false;
}
Internal.MetadataSetApiAssemblyInvalidated(apiType, false);
}
return true;
}
public static bool MakeApiAssembly(ApiAssemblyType apiType, string config)
{
string apiName = apiType == ApiAssemblyType.Core ? ApiAssemblyNames.Core : ApiAssemblyNames.Editor;
string editorPrebuiltApiDir = Path.Combine(GodotSharpDirs.DataEditorPrebuiltApiDir, config);
string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, config);
if (File.Exists(Path.Combine(editorPrebuiltApiDir, $"{apiName}.dll")))
{
using (var copyProgress = new EditorProgress("mono_copy_prebuilt_api_assembly", $"Copying prebuilt {apiName} assembly...", 1))
{
copyProgress.Step($"Copying {apiName} assembly", 0);
return CopyApiAssembly(editorPrebuiltApiDir, resAssembliesDir, apiName, apiType);
}
}
const string apiSolutionName = ApiAssemblyNames.SolutionName;
using (var pr = new EditorProgress($"mono_build_release_{apiSolutionName}", $"Building {apiSolutionName} solution...", 3))
{
pr.Step($"Generating {apiSolutionName} solution", 0);
string apiSlnDir = Path.Combine(GodotSharpDirs.MonoSolutionsDir, _ApiFolderName(ApiAssemblyType.Core));
string apiSlnFile = Path.Combine(apiSlnDir, $"{apiSolutionName}.sln");
if (!Directory.Exists(apiSlnDir) || !File.Exists(apiSlnFile))
{
var bindingsGenerator = new BindingsGenerator();
if (!Godot.OS.IsStdoutVerbose())
bindingsGenerator.LogPrintEnabled = false;
Error err = bindingsGenerator.GenerateCsApi(apiSlnDir);
if (err != Error.Ok)
{
ShowBuildErrorDialog($"Failed to generate {apiSolutionName} solution. Error: {err}");
return false;
}
}
pr.Step($"Building {apiSolutionName} solution", 1);
if (!BuildApiSolution(apiSlnDir, config))
return false;
pr.Step($"Copying {apiName} assembly", 2);
// Copy the built assembly to the assemblies directory
string apiAssemblyDir = Path.Combine(apiSlnDir, apiName, "bin", config);
if (!CopyApiAssembly(apiAssemblyDir, resAssembliesDir, apiName, apiType))
return false;
}
return true;
}
public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines)
{
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return true; // No solution to build
string apiConfig = config == "Release" ? "Release" : "Debug";
if (!MakeApiAssembly(ApiAssemblyType.Core, apiConfig))
return false;
if (!MakeApiAssembly(ApiAssemblyType.Editor, apiConfig))
return false;
using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1))
{
pr.Step("Building project solution", 0);
var buildInfo = new MonoBuildInfo(GodotSharpDirs.ProjectSlnPath, config);
// Add Godot defines
string constants = OS.IsWindows() ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\"";
foreach (var godotDefine in godotDefines)
constants += $"GODOT_{godotDefine.ToUpper().Replace("-", "_").Replace(" ", "_").Replace(";", "_")};";
if (Internal.GodotIsRealTDouble())
constants += "GODOT_REAL_T_IS_DOUBLE;";
constants += OS.IsWindows() ? "\"" : "\\\"";
buildInfo.CustomProperties.Add(constants);
if (!Build(buildInfo))
{
ShowBuildErrorDialog("Failed to build project solution");
return false;
}
}
return true;
}
public static bool EditorBuildCallback()
{
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return true; // No solution to build
string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor");
string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player");
CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
if (File.Exists(editorScriptsMetadataPath))
File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
var godotDefines = new[]
{
Godot.OS.GetName(),
Internal.GodotIs32Bits() ? "32" : "64"
};
return BuildProjectBlocking("Tools", godotDefines);
}
public static void Initialize()
{
// Build tool settings
Internal.EditorDef("mono/builds/build_tool", OS.IsWindows() ? BuildTool.MsBuildVs : BuildTool.MsBuildMono);
var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
{
["type"] = Godot.Variant.Type.Int,
["name"] = "mono/builds/build_tool",
["hint"] = Godot.PropertyHint.Enum,
["hint_string"] = OS.IsWindows() ?
$"{PropNameMsbuildMono},{PropNameMsbuildVs},{PropNameXbuild}" :
$"{PropNameMsbuildMono},{PropNameXbuild}"
});
Internal.EditorDef("mono/builds/print_build_output", false);
}
}
}

View file

@ -0,0 +1,538 @@
using Godot;
using GodotTools.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using GodotTools.Internals;
using GodotTools.ProjectEditor;
using File = GodotTools.Utils.File;
using Path = System.IO.Path;
using OS = GodotTools.Utils.OS;
namespace GodotTools
{
public class GodotSharpEditor : EditorPlugin, ISerializationListener
{
private EditorSettings editorSettings;
private PopupMenu menuPopup;
private AcceptDialog errorDialog;
private AcceptDialog aboutDialog;
private CheckBox aboutDialogCheckBox;
private ToolButton bottomPanelBtn;
private MonoDevelopInstance monoDevelopInstance;
private MonoDevelopInstance visualStudioForMacInstance;
public MonoBottomPanel MonoBottomPanel { get; private set; }
private bool CreateProjectSolution()
{
using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...", 2)) // TTR("Generating solution...")
{
pr.Step("Generating C# project..."); // TTR("Generating C# project...")
string resourceDir = ProjectSettings.GlobalizePath("res://");
string path = resourceDir;
string name = (string) ProjectSettings.GetSetting("application/config/name");
if (name.Empty())
name = "UnnamedProject";
string guid = CSharpProject.GenerateGameProject(path, name);
if (guid.Length > 0)
{
var solution = new DotNetSolution(name)
{
DirectoryPath = path
};
var projectInfo = new DotNetSolution.ProjectInfo
{
Guid = guid,
PathRelativeToSolution = name + ".csproj",
Configs = new List<string> {"Debug", "Release", "Tools"}
};
solution.AddNewProject(name, projectInfo);
try
{
solution.Save();
}
catch (IOException e)
{
ShowErrorDialog($"Failed to save solution. Exception message: {e.Message}"); // TTR
return false;
}
string apiConfig = "Debug";
if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Core, apiConfig))
return false;
if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Editor, apiConfig))
return false;
pr.Step("Done"); // TTR("Done")
// Here, after all calls to progress_task_step
CallDeferred(nameof(_RemoveCreateSlnMenuOption));
}
else
{
ShowErrorDialog("Failed to create C# project."); // TTR
}
return true;
}
}
private static int _makeApiSolutionsAttempts = 100;
private static bool _makeApiSolutionsRecursionGuard = false;
private void _MakeApiSolutionsIfNeeded()
{
// I'm sick entirely of ProgressDialog
if (Internal.IsMessageQueueFlushing() || Engine.GetMainLoop() == null)
{
if (_makeApiSolutionsAttempts == 0) // This better never happen or I swear...
throw new TimeoutException();
if (Engine.GetMainLoop() != null)
{
if (!Engine.GetMainLoop().IsConnected("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded)))
Engine.GetMainLoop().Connect("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded));
}
else
{
CallDeferred(nameof(_MakeApiSolutionsIfNeededImpl));
}
_makeApiSolutionsAttempts--;
return;
}
// Recursion guard needed because signals don't play well with ProgressDialog either, but unlike
// the message queue, with signals the collateral damage should be minimal in the worst case.
if (!_makeApiSolutionsRecursionGuard)
{
_makeApiSolutionsRecursionGuard = true;
// Oneshot signals don't play well with ProgressDialog either, so we do it this way instead
if (Engine.GetMainLoop().IsConnected("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded)))
Engine.GetMainLoop().Disconnect("idle_frame", this, nameof(_MakeApiSolutionsIfNeeded));
_MakeApiSolutionsIfNeededImpl();
_makeApiSolutionsRecursionGuard = false;
}
}
private void _MakeApiSolutionsIfNeededImpl()
{
// If the project has a solution and C# project make sure the API assemblies are present and up to date
string api_config = "Debug";
string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, api_config);
if (!File.Exists(Path.Combine(resAssembliesDir, $"{ApiAssemblyNames.Core}.dll")) ||
Internal.MetadataIsApiAssemblyInvalidated(ApiAssemblyType.Core))
{
if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Core, api_config))
return;
}
if (!File.Exists(Path.Combine(resAssembliesDir, $"{ApiAssemblyNames.Editor}.dll")) ||
Internal.MetadataIsApiAssemblyInvalidated(ApiAssemblyType.Editor))
{
if (!GodotSharpBuilds.MakeApiAssembly(ApiAssemblyType.Editor, api_config))
return; // Redundant? I don't think so!
}
}
private void _RemoveCreateSlnMenuOption()
{
menuPopup.RemoveItem(menuPopup.GetItemIndex((int) MenuOptions.CreateSln));
bottomPanelBtn.Show();
}
private void _ShowAboutDialog()
{
bool showOnStart = (bool) editorSettings.GetSetting("mono/editor/show_info_on_start");
aboutDialogCheckBox.Pressed = showOnStart;
aboutDialog.PopupCenteredMinsize();
}
private void _ToggleAboutDialogOnStart(bool enabled)
{
bool showOnStart = (bool) editorSettings.GetSetting("mono/editor/show_info_on_start");
if (showOnStart != enabled)
editorSettings.SetSetting("mono/editor/show_info_on_start", enabled);
}
private void _MenuOptionPressed(MenuOptions id)
{
switch (id)
{
case MenuOptions.CreateSln:
CreateProjectSolution();
break;
case MenuOptions.AboutCSharp:
_ShowAboutDialog();
break;
default:
throw new ArgumentOutOfRangeException(nameof(id), id, "Invalid menu option");
}
}
private void _BuildSolutionPressed()
{
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
{
if (!CreateProjectSolution())
return; // Failed to create solution
}
Instance.MonoBottomPanel.BuildProjectPressed();
}
public override void _Notification(int what)
{
base._Notification(what);
if (what == NotificationReady)
{
bool showInfoDialog = (bool) editorSettings.GetSetting("mono/editor/show_info_on_start");
if (showInfoDialog)
{
aboutDialog.PopupExclusive = true;
_ShowAboutDialog();
// Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive from that time on.
aboutDialog.PopupExclusive = false;
}
}
}
public enum MenuOptions
{
CreateSln,
AboutCSharp,
}
public enum ExternalEditor
{
None,
VisualStudio, // TODO (Windows-only)
VisualStudioForMac, // Mac-only
MonoDevelop,
VsCode
}
public void ShowErrorDialog(string message, string title = "Error")
{
errorDialog.WindowTitle = title;
errorDialog.DialogText = message;
errorDialog.PopupCenteredMinsize();
}
private static string _vsCodePath = string.Empty;
private static readonly string[] VsCodeNames =
{
"code", "code-oss", "vscode", "vscode-oss", "visual-studio-code", "visual-studio-code-oss"
};
public Error OpenInExternalEditor(Script script, int line, int col)
{
var editor = (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor");
switch (editor)
{
case ExternalEditor.VsCode:
{
if (_vsCodePath.Empty() || !File.Exists(_vsCodePath))
{
// Try to search it again if it wasn't found last time or if it was removed from its location
_vsCodePath = VsCodeNames.SelectFirstNotNull(OS.PathWhich, orElse: string.Empty);
}
var args = new List<string>();
bool osxAppBundleInstalled = false;
if (OS.IsOSX())
{
// The package path is '/Applications/Visual Studio Code.app'
const string vscodeBundleId = "com.microsoft.VSCode";
osxAppBundleInstalled = Internal.IsOsxAppBundleInstalled(vscodeBundleId);
if (osxAppBundleInstalled)
{
args.Add("-b");
args.Add(vscodeBundleId);
// The reusing of existing windows made by the 'open' command might not choose a wubdiw that is
// editing our folder. It's better to ask for a new window and let VSCode do the window management.
args.Add("-n");
// The open process must wait until the application finishes (which is instant in VSCode's case)
args.Add("--wait-apps");
args.Add("--args");
}
}
var resourcePath = ProjectSettings.GlobalizePath("res://");
args.Add(resourcePath);
string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
if (line >= 0)
{
args.Add("-g");
args.Add($"{scriptPath}:{line + 1}:{col}");
}
else
{
args.Add(scriptPath);
}
string command;
if (OS.IsOSX())
{
if (!osxAppBundleInstalled && _vsCodePath.Empty())
{
GD.PushError("Cannot find code editor: VSCode");
return Error.FileNotFound;
}
command = osxAppBundleInstalled ? "/usr/bin/open" : _vsCodePath;
}
else
{
if (_vsCodePath.Empty())
{
GD.PushError("Cannot find code editor: VSCode");
return Error.FileNotFound;
}
command = _vsCodePath;
}
try
{
OS.RunProcess(command, args);
}
catch (Exception e)
{
GD.PushError($"Error when trying to run code editor: VSCode. Exception message: '{e.Message}'");
}
break;
}
case ExternalEditor.VisualStudioForMac:
goto case ExternalEditor.MonoDevelop;
case ExternalEditor.MonoDevelop:
{
MonoDevelopInstance GetMonoDevelopInstance(string solutionPath)
{
if (OS.IsOSX() && editor == ExternalEditor.VisualStudioForMac)
{
if (visualStudioForMacInstance == null)
visualStudioForMacInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.VisualStudioForMac);
return visualStudioForMacInstance;
}
if (monoDevelopInstance == null)
monoDevelopInstance = new MonoDevelopInstance(solutionPath, MonoDevelopInstance.EditorId.MonoDevelop);
return monoDevelopInstance;
}
string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
if (line >= 0)
scriptPath += $";{line + 1};{col}";
GetMonoDevelopInstance(GodotSharpDirs.ProjectSlnPath).Execute(scriptPath);
break;
}
case ExternalEditor.None:
return Error.Unavailable;
default:
throw new ArgumentOutOfRangeException();
}
return Error.Ok;
}
public bool OverridesExternalEditor()
{
return (ExternalEditor) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditor.None;
}
public override bool Build()
{
return GodotSharpBuilds.EditorBuildCallback();
}
public override void EnablePlugin()
{
base.EnablePlugin();
if (Instance != null)
throw new InvalidOperationException();
Instance = this;
var editorInterface = GetEditorInterface();
var editorBaseControl = editorInterface.GetBaseControl();
editorSettings = editorInterface.GetEditorSettings();
errorDialog = new AcceptDialog();
editorBaseControl.AddChild(errorDialog);
MonoBottomPanel = new MonoBottomPanel();
bottomPanelBtn = AddControlToBottomPanel(MonoBottomPanel, "Mono"); // TTR("Mono")
AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"});
menuPopup = new PopupMenu();
menuPopup.Hide();
menuPopup.SetAsToplevel(true);
AddToolSubmenuItem("Mono", menuPopup);
// TODO: Remove or edit this info dialog once Mono support is no longer in alpha
{
menuPopup.AddItem("About C# support", (int) MenuOptions.AboutCSharp); // TTR("About C# support")
aboutDialog = new AcceptDialog();
editorBaseControl.AddChild(aboutDialog);
aboutDialog.WindowTitle = "Important: C# support is not feature-complete";
// We don't use DialogText as the default AcceptDialog Label doesn't play well with the TextureRect and CheckBox
// we'll add. Instead we add containers and a new autowrapped Label inside.
// Main VBoxContainer (icon + label on top, checkbox at bottom)
var aboutVBox = new VBoxContainer();
aboutDialog.AddChild(aboutVBox);
// HBoxContainer for icon + label
var aboutHBox = new HBoxContainer();
aboutVBox.AddChild(aboutHBox);
var aboutIcon = new TextureRect();
aboutIcon.Texture = aboutIcon.GetIcon("NodeWarning", "EditorIcons");
aboutHBox.AddChild(aboutIcon);
var aboutLabel = new Label();
aboutHBox.AddChild(aboutLabel);
aboutLabel.RectMinSize = new Vector2(600, 150) * Internal.EditorScale;
aboutLabel.SizeFlagsVertical = (int) Control.SizeFlags.ExpandFill;
aboutLabel.Autowrap = true;
aboutLabel.Text =
"C# support in Godot Engine is in late alpha stage and, while already usable, " +
"it is not meant for use in production.\n\n" +
"Projects can be exported to Linux, macOS and Windows, but not yet to mobile or web platforms. " +
"Bugs and usability issues will be addressed gradually over future releases, " +
"potentially including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" +
"If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, MSBuild version, IDE, etc.:\n\n" +
" https://github.com/godotengine/godot/issues\n\n" +
"Your critical feedback at this stage will play a great role in shaping the C# support in future releases, so thank you!";
Internal.EditorDef("mono/editor/show_info_on_start", true);
// CheckBox in main container
aboutDialogCheckBox = new CheckBox {Text = "Show this warning when starting the editor"};
aboutDialogCheckBox.Connect("toggled", this, nameof(_ToggleAboutDialogOnStart));
aboutVBox.AddChild(aboutDialogCheckBox);
}
if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath))
{
// Defer this task because EditorProgress calls Main::iterarion() and the main loop is not yet initialized.
CallDeferred(nameof(_MakeApiSolutionsIfNeeded));
}
else
{
bottomPanelBtn.Hide();
menuPopup.AddItem("Create C# solution", (int) MenuOptions.CreateSln); // TTR("Create C# solution")
}
menuPopup.Connect("id_pressed", this, nameof(_MenuOptionPressed));
var buildButton = new ToolButton
{
Text = "Build",
HintTooltip = "Build solution",
FocusMode = Control.FocusModeEnum.None
};
buildButton.Connect("pressed", this, nameof(_BuildSolutionPressed));
AddControlToContainer(CustomControlContainer.Toolbar, buildButton);
// External editor settings
Internal.EditorDef("mono/editor/external_editor", ExternalEditor.None);
string settingsHintStr = "Disabled";
if (OS.IsWindows())
{
settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" +
$",Visual Studio Code:{(int) ExternalEditor.VsCode}";
}
else if (OS.IsOSX())
{
settingsHintStr += $",Visual Studio:{(int) ExternalEditor.VisualStudioForMac}" +
$",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" +
$",Visual Studio Code:{(int) ExternalEditor.VsCode}";
}
else if (OS.IsUnix())
{
settingsHintStr += $",MonoDevelop:{(int) ExternalEditor.MonoDevelop}" +
$",Visual Studio Code:{(int) ExternalEditor.VsCode}";
}
editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
{
["type"] = Variant.Type.Int,
["name"] = "mono/editor/external_editor",
["hint"] = PropertyHint.Enum,
["hint_string"] = settingsHintStr
});
// Export plugin
AddExportPlugin(new GodotSharpExport());
GodotSharpBuilds.Initialize();
}
public void OnBeforeSerialize()
{
}
public void OnAfterDeserialize()
{
Instance = this;
}
// Singleton
public static GodotSharpEditor Instance { get; private set; }
private GodotSharpEditor()
{
}
}
}

View file

@ -0,0 +1,197 @@
using Godot;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using GodotTools.Core;
using GodotTools.Internals;
using Directory = GodotTools.Utils.Directory;
using File = GodotTools.Utils.File;
using Path = System.IO.Path;
namespace GodotTools
{
public class GodotSharpExport : EditorExportPlugin
{
private void AddFile(string srcPath, string dstPath, bool remap = false)
{
AddFile(dstPath, File.ReadAllBytes(srcPath), remap);
}
public override void _ExportFile(string path, string type, string[] features)
{
base._ExportFile(path, type, features);
if (type != Internal.CSharpLanguageType)
return;
if (Path.GetExtension(path) != $".{Internal.CSharpLanguageExtension}")
throw new ArgumentException($"Resource of type {Internal.CSharpLanguageType} has an invalid file extension: {path}", nameof(path));
// TODO What if the source file is not part of the game's C# project
bool includeScriptsContent = (bool) ProjectSettings.GetSetting("mono/export/include_scripts_content");
if (!includeScriptsContent)
{
// We don't want to include the source code on exported games
AddFile(path, new byte[] { }, remap: false);
Skip();
}
}
public override void _ExportBegin(string[] features, bool isDebug, string path, int flags)
{
base._ExportBegin(features, isDebug, path, flags);
try
{
_ExportBeginImpl(features, isDebug, path, flags);
}
catch (Exception e)
{
GD.PushError($"Failed to export project. Exception message: {e.Message}");
Console.Error.WriteLine(e);
}
}
public void _ExportBeginImpl(string[] features, bool isDebug, string path, int flags)
{
// TODO Right now there is no way to stop the export process with an error
if (File.Exists(GodotSharpDirs.ProjectSlnPath))
{
string buildConfig = isDebug ? "Debug" : "Release";
string scriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, $"scripts_metadata.{(isDebug ? "debug" : "release")}");
CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, scriptsMetadataPath);
AddFile(scriptsMetadataPath, scriptsMetadataPath);
// Turn export features into defines
var godotDefines = features;
if (!GodotSharpBuilds.BuildProjectBlocking(buildConfig, godotDefines))
{
GD.PushError("Failed to build project");
return;
}
// Add dependency assemblies
var dependencies = new Godot.Collections.Dictionary<string, string>();
var projectDllName = (string) ProjectSettings.GetSetting("application/config/name");
if (projectDllName.Empty())
{
projectDllName = "UnnamedProject";
}
string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig);
string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll");
dependencies[projectDllName] = projectDllSrcPath;
{
string templatesDir = Internal.FullTemplatesDir;
string androidBclDir = Path.Combine(templatesDir, "android-bcl");
string customLibDir = features.Contains("Android") && Directory.Exists(androidBclDir) ? androidBclDir : string.Empty;
GetExportedAssemblyDependencies(projectDllName, projectDllSrcPath, buildConfig, customLibDir, dependencies);
}
string apiConfig = isDebug ? "Debug" : "Release";
string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, apiConfig);
foreach (var dependency in dependencies)
{
string dependSrcPath = dependency.Value;
string dependDstPath = Path.Combine(resAssembliesDir, dependSrcPath.GetFile());
AddFile(dependSrcPath, dependDstPath);
}
}
// Mono specific export template extras (data dir)
ExportDataDirectory(features, isDebug, path);
}
private static void ExportDataDirectory(IEnumerable<string> features, bool debug, string path)
{
var featureSet = new HashSet<string>(features);
if (!PlatformHasTemplateDir(featureSet))
return;
string templateDirName = "data.mono";
if (featureSet.Contains("Windows"))
{
templateDirName += ".windows";
templateDirName += featureSet.Contains("64") ? ".64" : ".32";
}
else if (featureSet.Contains("X11"))
{
templateDirName += ".x11";
templateDirName += featureSet.Contains("64") ? ".64" : ".32";
}
else
{
throw new NotSupportedException("Target platform not supported");
}
templateDirName += debug ? ".release_debug" : ".release";
string templateDirPath = Path.Combine(Internal.FullTemplatesDir, templateDirName);
if (!Directory.Exists(templateDirPath))
throw new FileNotFoundException("Data template directory not found");
string outputDir = new FileInfo(path).Directory?.FullName ??
throw new FileNotFoundException("Base directory not found");
string outputDataDir = Path.Combine(outputDir, DataDirName);
if (Directory.Exists(outputDataDir))
Directory.Delete(outputDataDir, recursive: true); // Clean first
Directory.CreateDirectory(outputDataDir);
foreach (string dir in Directory.GetDirectories(templateDirPath, "*", SearchOption.AllDirectories))
{
Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(templateDirPath.Length + 1)));
}
foreach (string file in Directory.GetFiles(templateDirPath, "*", SearchOption.AllDirectories))
{
File.Copy(file, Path.Combine(outputDataDir, file.Substring(templateDirPath.Length + 1)));
}
}
private static bool PlatformHasTemplateDir(IEnumerable<string> featureSet)
{
// OSX export templates are contained in a zip, so we place
// our custom template inside it and let Godot do the rest.
return !featureSet.Any(f => new[] {"OSX", "Android"}.Contains(f));
}
private static string DataDirName
{
get
{
var appName = (string) ProjectSettings.GetSetting("application/config/name");
string appNameSafe = appName.ToSafeDirName(allowDirSeparator: false);
return $"data_{appNameSafe}";
}
}
private static void GetExportedAssemblyDependencies(string projectDllName, string projectDllSrcPath,
string buildConfig, string customLibDir, Godot.Collections.Dictionary<string, string> dependencies) =>
internal_GetExportedAssemblyDependencies(projectDllName, projectDllSrcPath, buildConfig, customLibDir, dependencies);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_GetExportedAssemblyDependencies(string projectDllName, string projectDllSrcPath,
string buildConfig, string customLibDir, Godot.Collections.Dictionary<string, string> dependencies);
}
}

View file

@ -0,0 +1,81 @@
<?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>{27B00618-A6F2-4828-B922-05CAEB08C286}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>GodotTools</RootNamespace>
<AssemblyName>GodotTools</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath>
<GodotApiConfiguration>Debug</GodotApiConfiguration>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</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' ">
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="GodotSharp">
<HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharp.dll</HintPath>
</Reference>
<Reference Include="GodotSharpEditor">
<HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharpEditor.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Build\MsBuildFinder.cs" />
<Compile Include="Internals\BindingsGenerator.cs" />
<Compile Include="Internals\EditorProgress.cs" />
<Compile Include="Internals\GodotSharpDirs.cs" />
<Compile Include="Internals\Internal.cs" />
<Compile Include="Internals\ScriptClassParser.cs" />
<Compile Include="MonoDevelopInstance.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Build\BuildSystem.cs" />
<Compile Include="Utils\Directory.cs" />
<Compile Include="Utils\File.cs" />
<Compile Include="Utils\OS.cs" />
<Compile Include="GodotSharpEditor.cs" />
<Compile Include="GodotSharpBuilds.cs" />
<Compile Include="HotReloadAssemblyWatcher.cs" />
<Compile Include="MonoBuildInfo.cs" />
<Compile Include="MonoBuildTab.cs" />
<Compile Include="MonoBottomPanel.cs" />
<Compile Include="GodotSharpExport.cs" />
<Compile Include="CSharpProject.cs" />
<Compile Include="Utils\CollectionExtensions.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GodotTools.BuildLogger\GodotTools.BuildLogger.csproj">
<Project>{6ce9a984-37b1-4f8a-8fe9-609f05f071b3}</Project>
<Name>GodotTools.BuildLogger</Name>
</ProjectReference>
<ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj">
<Project>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</Project>
<Name>GodotTools.ProjectEditor</Name>
</ProjectReference>
<ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj">
<Project>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</Project>
<Name>GodotTools.Core</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Editor" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,47 @@
using Godot;
using GodotTools.Internals;
namespace GodotTools
{
public class HotReloadAssemblyWatcher : Node
{
private Timer watchTimer;
public override void _Notification(int what)
{
if (what == MainLoop.NotificationWmFocusIn)
{
RestartTimer();
if (Internal.IsAssembliesReloadingNeeded())
Internal.ReloadAssemblies(softReload: false);
}
}
private void TimerTimeout()
{
if (Internal.IsAssembliesReloadingNeeded())
Internal.ReloadAssemblies(softReload: false);
}
public void RestartTimer()
{
watchTimer.Stop();
watchTimer.Start();
}
public override void _Ready()
{
base._Ready();
watchTimer = new Timer
{
OneShot = false,
WaitTime = (float) Internal.EditorDef("mono/assembly_watch_interval_sec", 0.5)
};
watchTimer.Connect("timeout", this, nameof(TimerTimeout));
AddChild(watchTimer);
watchTimer.Start();
}
}
}

View file

@ -0,0 +1,87 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace GodotTools.Internals
{
public class BindingsGenerator : IDisposable
{
class BindingsGeneratorSafeHandle : SafeHandle
{
public BindingsGeneratorSafeHandle(IntPtr handle) : base(IntPtr.Zero, true)
{
this.handle = handle;
}
public override bool IsInvalid => handle == IntPtr.Zero;
protected override bool ReleaseHandle()
{
internal_Dtor(handle);
return true;
}
}
private BindingsGeneratorSafeHandle safeHandle;
private bool disposed = false;
public bool LogPrintEnabled
{
get => internal_LogPrintEnabled(GetPtr());
set => internal_SetLogPrintEnabled(GetPtr(), value);
}
public static uint Version => internal_Version();
public static uint CsGlueVersion => internal_CsGlueVersion();
public Godot.Error GenerateCsApi(string outputDir) => internal_GenerateCsApi(GetPtr(), outputDir);
internal IntPtr GetPtr()
{
if (disposed)
throw new ObjectDisposedException(GetType().FullName);
return safeHandle.DangerousGetHandle();
}
public void Dispose()
{
if (disposed)
return;
if (safeHandle != null && !safeHandle.IsInvalid)
{
safeHandle.Dispose();
safeHandle = null;
}
disposed = true;
}
public BindingsGenerator()
{
safeHandle = new BindingsGeneratorSafeHandle(internal_Ctor());
}
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern IntPtr internal_Ctor();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_Dtor(IntPtr handle);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_LogPrintEnabled(IntPtr handle);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_SetLogPrintEnabled(IntPtr handle, bool enabled);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern Godot.Error internal_GenerateCsApi(IntPtr handle, string outputDir);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern uint internal_Version();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern uint internal_CsGlueVersion();
}
}

View file

@ -0,0 +1,50 @@
using System;
using System.Runtime.CompilerServices;
using Godot;
namespace GodotTools.Internals
{
public class EditorProgress : IDisposable
{
public string Task { get; }
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_Create(string task, string label, int amount, bool canCancel);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_Dispose(string task);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_Step(string task, string state, int step, bool forceRefresh);
public EditorProgress(string task, string label, int amount, bool canCancel = false)
{
Task = task;
internal_Create(task, label, amount, canCancel);
}
~EditorProgress()
{
// Should never rely on the GC to dispose EditorProgress.
// It should be disposed immediately when the task finishes.
GD.PushError("EditorProgress disposed by the Garbage Collector");
Dispose();
}
public void Dispose()
{
internal_Dispose(Task);
GC.SuppressFinalize(this);
}
public void Step(string state, int step = -1, bool forceRefresh = true)
{
internal_Step(Task, state, step, forceRefresh);
}
public bool TryStep(string state, int step = -1, bool forceRefresh = true)
{
return internal_Step(Task, state, step, forceRefresh);
}
}
}

View file

@ -0,0 +1,91 @@
using System.Runtime.CompilerServices;
namespace GodotTools.Internals
{
public static class GodotSharpDirs
{
public static string ResDataDir => internal_ResDataDir();
public static string ResMetadataDir => internal_ResMetadataDir();
public static string ResAssembliesBaseDir => internal_ResAssembliesBaseDir();
public static string ResAssembliesDir => internal_ResAssembliesDir();
public static string ResConfigDir => internal_ResConfigDir();
public static string ResTempDir => internal_ResTempDir();
public static string ResTempAssembliesBaseDir => internal_ResTempAssembliesBaseDir();
public static string ResTempAssembliesDir => internal_ResTempAssembliesDir();
public static string MonoUserDir => internal_MonoUserDir();
public static string MonoLogsDir => internal_MonoLogsDir();
#region Tools-only
public static string MonoSolutionsDir => internal_MonoSolutionsDir();
public static string BuildLogsDirs => internal_BuildLogsDirs();
public static string ProjectSlnPath => internal_ProjectSlnPath();
public static string ProjectCsProjPath => internal_ProjectCsProjPath();
public static string DataEditorToolsDir => internal_DataEditorToolsDir();
public static string DataEditorPrebuiltApiDir => internal_DataEditorPrebuiltApiDir();
#endregion
public static string DataMonoEtcDir => internal_DataMonoEtcDir();
public static string DataMonoLibDir => internal_DataMonoLibDir();
#region Windows-only
public static string DataMonoBinDir => internal_DataMonoBinDir();
#endregion
#region Internal
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResDataDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResMetadataDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResAssembliesBaseDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResAssembliesDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResConfigDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResTempDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResTempAssembliesBaseDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ResTempAssembliesDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_MonoUserDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_MonoLogsDir();
#region Tools-only
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_MonoSolutionsDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_BuildLogsDirs();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ProjectSlnPath();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_ProjectCsProjPath();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_DataEditorToolsDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_DataEditorPrebuiltApiDir();
#endregion
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_DataMonoEtcDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_DataMonoLibDir();
#region Windows-only
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_DataMonoBinDir();
#endregion
#endregion
}
}

View file

@ -0,0 +1,127 @@
using System;
using System.Runtime.CompilerServices;
using Godot;
using Godot.Collections;
namespace GodotTools.Internals
{
public static class Internal
{
public const string CSharpLanguageType = "CSharpScript";
public const string CSharpLanguageExtension = "cs";
public static float EditorScale => internal_EditorScale();
public static object GlobalDef(string setting, object defaultValue, bool restartIfChanged = false) =>
internal_GlobalDef(setting, defaultValue, restartIfChanged);
public static object EditorDef(string setting, object defaultValue, bool restartIfChanged = false) =>
internal_EditorDef(setting, defaultValue, restartIfChanged);
public static string FullTemplatesDir =>
internal_FullTemplatesDir();
public static string SimplifyGodotPath(this string path) => internal_SimplifyGodotPath(path);
public static bool IsOsxAppBundleInstalled(string bundleId) => internal_IsOsxAppBundleInstalled(bundleId);
public static bool MetadataIsApiAssemblyInvalidated(ApiAssemblyType apiType) =>
internal_MetadataIsApiAssemblyInvalidated(apiType);
public static void MetadataSetApiAssemblyInvalidated(ApiAssemblyType apiType, bool invalidated) =>
internal_MetadataSetApiAssemblyInvalidated(apiType, invalidated);
public static bool IsMessageQueueFlushing() => internal_IsMessageQueueFlushing();
public static bool GodotIs32Bits() => internal_GodotIs32Bits();
public static bool GodotIsRealTDouble() => internal_GodotIsRealTDouble();
public static void GodotMainIteration() => internal_GodotMainIteration();
public static ulong GetCoreApiHash() => internal_GetCoreApiHash();
public static ulong GetEditorApiHash() => internal_GetEditorApiHash();
public static bool IsAssembliesReloadingNeeded() => internal_IsAssembliesReloadingNeeded();
public static void ReloadAssemblies(bool softReload) => internal_ReloadAssemblies(softReload);
public static void ScriptEditorDebuggerReloadScripts() => internal_ScriptEditorDebuggerReloadScripts();
public static bool ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus = true) =>
internal_ScriptEditorEdit(resource, line, col, grabFocus);
public static void EditorNodeShowScriptScreen() => internal_EditorNodeShowScriptScreen();
public static Dictionary<string, object> GetScriptsMetadataOrNothing() =>
internal_GetScriptsMetadataOrNothing(typeof(Dictionary<string, object>));
public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot();
// Internal Calls
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern float internal_EditorScale();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern object internal_GlobalDef(string setting, object defaultValue, bool restartIfChanged);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern object internal_EditorDef(string setting, object defaultValue, bool restartIfChanged);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_FullTemplatesDir();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_SimplifyGodotPath(this string path);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_IsOsxAppBundleInstalled(string bundleId);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_MetadataIsApiAssemblyInvalidated(ApiAssemblyType apiType);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_MetadataSetApiAssemblyInvalidated(ApiAssemblyType apiType, bool invalidated);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_IsMessageQueueFlushing();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_GodotIs32Bits();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_GodotIsRealTDouble();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_GodotMainIteration();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern ulong internal_GetCoreApiHash();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern ulong internal_GetEditorApiHash();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_IsAssembliesReloadingNeeded();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_ReloadAssemblies(bool softReload);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_ScriptEditorDebuggerReloadScripts();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool internal_ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void internal_EditorNodeShowScriptScreen();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern Dictionary<string, object> internal_GetScriptsMetadataOrNothing(Type dictType);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern string internal_MonoWindowsInstallRoot();
}
}

View file

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Godot;
using Godot.Collections;
namespace GodotTools.Internals
{
public static class ScriptClassParser
{
public class ClassDecl
{
public string Name { get; }
public string Namespace { get; }
public bool Nested { get; }
public int BaseCount { get; }
public ClassDecl(string name, string @namespace, bool nested, int baseCount)
{
Name = name;
Namespace = @namespace;
Nested = nested;
BaseCount = baseCount;
}
}
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern Error internal_ParseFile(string filePath, Array<Dictionary> classes);
public static void ParseFileOrThrow(string filePath, out IEnumerable<ClassDecl> classes)
{
var classesArray = new Array<Dictionary>();
var error = internal_ParseFile(filePath, classesArray);
if (error != Error.Ok)
throw new Exception($"Failed to determine namespace and class for script: {filePath}. Parse error: {error}");
var classesList = new List<ClassDecl>();
foreach (var classDeclDict in classesArray)
{
classesList.Add(new ClassDecl(
(string) classDeclDict["name"],
(string) classDeclDict["namespace"],
(bool) classDeclDict["nested"],
(int) classDeclDict["base_count"]
));
}
classes = classesList;
}
}
}

View file

@ -0,0 +1,342 @@
using Godot;
using System;
using System.IO;
using Godot.Collections;
using GodotTools.Internals;
using File = GodotTools.Utils.File;
using Path = System.IO.Path;
namespace GodotTools
{
public class MonoBottomPanel : VBoxContainer
{
private EditorInterface editorInterface;
private TabContainer panelTabs;
private VBoxContainer panelBuildsTab;
private ItemList buildTabsList;
private TabContainer buildTabs;
private ToolButton warningsBtn;
private ToolButton errorsBtn;
private Button viewLogBtn;
private void _UpdateBuildTabsList()
{
buildTabsList.Clear();
int currentTab = buildTabs.CurrentTab;
bool noCurrentTab = currentTab < 0 || currentTab >= buildTabs.GetTabCount();
for (int i = 0; i < buildTabs.GetChildCount(); i++)
{
var tab = (MonoBuildTab) buildTabs.GetChild(i);
if (tab == null)
continue;
string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution);
itemName += " [" + tab.BuildInfo.Configuration + "]";
buildTabsList.AddItem(itemName, tab.IconTexture);
string itemTooltip = "Solution: " + tab.BuildInfo.Solution;
itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration;
itemTooltip += "\nStatus: ";
if (tab.BuildExited)
itemTooltip += tab.BuildResult == MonoBuildTab.BuildResults.Success ? "Succeeded" : "Errored";
else
itemTooltip += "Running";
if (!tab.BuildExited || tab.BuildResult == MonoBuildTab.BuildResults.Error)
itemTooltip += $"\nErrors: {tab.ErrorCount}";
itemTooltip += $"\nWarnings: {tab.WarningCount}";
buildTabsList.SetItemTooltip(i, itemTooltip);
if (noCurrentTab || currentTab == i)
{
buildTabsList.Select(i);
_BuildTabsItemSelected(i);
}
}
}
public MonoBuildTab GetBuildTabFor(MonoBuildInfo buildInfo)
{
foreach (var buildTab in new Array<MonoBuildTab>(buildTabs.GetChildren()))
{
if (buildTab.BuildInfo.Equals(buildInfo))
return buildTab;
}
var newBuildTab = new MonoBuildTab(buildInfo);
AddBuildTab(newBuildTab);
return newBuildTab;
}
private void _BuildTabsItemSelected(int idx)
{
if (idx < 0 || idx >= buildTabs.GetTabCount())
throw new IndexOutOfRangeException();
buildTabs.CurrentTab = idx;
if (!buildTabs.Visible)
buildTabs.Visible = true;
warningsBtn.Visible = true;
errorsBtn.Visible = true;
viewLogBtn.Visible = true;
}
private void _BuildTabsNothingSelected()
{
if (buildTabs.GetTabCount() != 0)
{
// just in case
buildTabs.Visible = false;
// This callback is called when clicking on the empty space of the list.
// ItemList won't deselect the items automatically, so we must do it ourselves.
buildTabsList.UnselectAll();
}
warningsBtn.Visible = false;
errorsBtn.Visible = false;
viewLogBtn.Visible = false;
}
private void _WarningsToggled(bool pressed)
{
int currentTab = buildTabs.CurrentTab;
if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
throw new InvalidOperationException("No tab selected");
var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab);
buildTab.WarningsVisible = pressed;
buildTab.UpdateIssuesList();
}
private void _ErrorsToggled(bool pressed)
{
int currentTab = buildTabs.CurrentTab;
if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
throw new InvalidOperationException("No tab selected");
var buildTab = (MonoBuildTab) buildTabs.GetChild(currentTab);
buildTab.ErrorsVisible = pressed;
buildTab.UpdateIssuesList();
}
public void BuildProjectPressed()
{
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return; // No solution to build
string editorScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor");
string playerScriptsMetadataPath = Path.Combine(GodotSharpDirs.ResMetadataDir, "scripts_metadata.editor_player");
CSharpProject.GenerateScriptsMetadata(GodotSharpDirs.ProjectCsProjPath, editorScriptsMetadataPath);
if (File.Exists(editorScriptsMetadataPath))
{
try
{
File.Copy(editorScriptsMetadataPath, playerScriptsMetadataPath);
}
catch (IOException e)
{
GD.PushError($"Failed to copy scripts metadata file. Exception message: {e.Message}");
return;
}
}
var godotDefines = new[]
{
OS.GetName(),
Internal.GodotIs32Bits() ? "32" : "64"
};
bool buildSuccess = GodotSharpBuilds.BuildProjectBlocking("Tools", godotDefines);
if (!buildSuccess)
return;
// Notify running game for hot-reload
Internal.ScriptEditorDebuggerReloadScripts();
// Hot-reload in the editor
GodotSharpEditor.Instance.GetNode<HotReloadAssemblyWatcher>("HotReloadAssemblyWatcher").RestartTimer();
if (Internal.IsAssembliesReloadingNeeded())
Internal.ReloadAssemblies(softReload: false);
}
private void _ViewLogPressed()
{
if (!buildTabsList.IsAnythingSelected())
return;
var selectedItems = buildTabsList.GetSelectedItems();
if (selectedItems.Length != 1)
throw new InvalidOperationException($"Expected 1 selected item, got {selectedItems.Length}");
int selectedItem = selectedItems[0];
var buildTab = (MonoBuildTab) buildTabs.GetTabControl(selectedItem);
OS.ShellOpen(Path.Combine(buildTab.BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildLogFileName));
}
public override void _Notification(int what)
{
base._Notification(what);
if (what == EditorSettings.NotificationEditorSettingsChanged)
{
var editorBaseControl = editorInterface.GetBaseControl();
panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles"));
panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles"));
panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles"));
}
}
public void AddBuildTab(MonoBuildTab buildTab)
{
buildTabs.AddChild(buildTab);
RaiseBuildTab(buildTab);
}
public void RaiseBuildTab(MonoBuildTab buildTab)
{
if (buildTab.GetParent() != buildTabs)
throw new InvalidOperationException("Build tab is not in the tabs list");
buildTabs.MoveChild(buildTab, 0);
_UpdateBuildTabsList();
}
public void ShowBuildTab()
{
for (int i = 0; i < panelTabs.GetTabCount(); i++)
{
if (panelTabs.GetTabControl(i) == panelBuildsTab)
{
panelTabs.CurrentTab = i;
GodotSharpEditor.Instance.MakeBottomPanelItemVisible(this);
return;
}
}
GD.PushError("Builds tab not found");
}
public override void _Ready()
{
base._Ready();
editorInterface = GodotSharpEditor.Instance.GetEditorInterface();
var editorBaseControl = editorInterface.GetBaseControl();
SizeFlagsVertical = (int) SizeFlags.ExpandFill;
SetAnchorsAndMarginsPreset(LayoutPreset.Wide);
panelTabs = new TabContainer
{
TabAlign = TabContainer.TabAlignEnum.Left,
RectMinSize = new Vector2(0, 228) * Internal.EditorScale,
SizeFlagsVertical = (int) SizeFlags.ExpandFill
};
panelTabs.AddStyleboxOverride("panel", editorBaseControl.GetStylebox("DebuggerPanel", "EditorStyles"));
panelTabs.AddStyleboxOverride("tab_fg", editorBaseControl.GetStylebox("DebuggerTabFG", "EditorStyles"));
panelTabs.AddStyleboxOverride("tab_bg", editorBaseControl.GetStylebox("DebuggerTabBG", "EditorStyles"));
AddChild(panelTabs);
{
// Builds tab
panelBuildsTab = new VBoxContainer
{
Name = "Builds", // TTR
SizeFlagsHorizontal = (int) SizeFlags.ExpandFill
};
panelTabs.AddChild(panelBuildsTab);
var toolBarHBox = new HBoxContainer {SizeFlagsHorizontal = (int) SizeFlags.ExpandFill};
panelBuildsTab.AddChild(toolBarHBox);
var buildProjectBtn = new Button
{
Text = "Build Project", // TTR
FocusMode = FocusModeEnum.None
};
buildProjectBtn.Connect("pressed", this, nameof(BuildProjectPressed));
toolBarHBox.AddChild(buildProjectBtn);
toolBarHBox.AddSpacer(begin: false);
warningsBtn = new ToolButton
{
Text = "Warnings", // TTR
ToggleMode = true,
Pressed = true,
Visible = false,
FocusMode = FocusModeEnum.None
};
warningsBtn.Connect("toggled", this, nameof(_WarningsToggled));
toolBarHBox.AddChild(warningsBtn);
errorsBtn = new ToolButton
{
Text = "Errors", // TTR
ToggleMode = true,
Pressed = true,
Visible = false,
FocusMode = FocusModeEnum.None
};
errorsBtn.Connect("toggled", this, nameof(_ErrorsToggled));
toolBarHBox.AddChild(errorsBtn);
toolBarHBox.AddSpacer(begin: false);
viewLogBtn = new Button
{
Text = "View log", // TTR
FocusMode = FocusModeEnum.None,
Visible = false
};
viewLogBtn.Connect("pressed", this, nameof(_ViewLogPressed));
toolBarHBox.AddChild(viewLogBtn);
var hsc = new HSplitContainer
{
SizeFlagsHorizontal = (int) SizeFlags.ExpandFill,
SizeFlagsVertical = (int) SizeFlags.ExpandFill
};
panelBuildsTab.AddChild(hsc);
buildTabsList = new ItemList {SizeFlagsHorizontal = (int) SizeFlags.ExpandFill};
buildTabsList.Connect("item_selected", this, nameof(_BuildTabsItemSelected));
buildTabsList.Connect("nothing_selected", this, nameof(_BuildTabsNothingSelected));
hsc.AddChild(buildTabsList);
buildTabs = new TabContainer
{
TabAlign = TabContainer.TabAlignEnum.Left,
SizeFlagsHorizontal = (int) SizeFlags.ExpandFill,
TabsVisible = false
};
hsc.AddChild(buildTabs);
}
}
}
}

View file

@ -0,0 +1,47 @@
using System;
using Godot;
using Godot.Collections;
using GodotTools.Internals;
using Path = System.IO.Path;
namespace GodotTools
{
[Serializable]
public sealed class MonoBuildInfo : Reference // TODO Remove Reference once we have proper serialization
{
public string Solution { get; }
public string Configuration { get; }
public Array<string> CustomProperties { get; } = new Array<string>(); // TODO Use List once we have proper serialization
public string LogsDirPath => Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.MD5Text()}_{Configuration}");
public override bool Equals(object obj)
{
if (obj is MonoBuildInfo other)
return other.Solution == Solution && other.Configuration == Configuration;
return false;
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 29 + Solution.GetHashCode();
hash = hash * 29 + Configuration.GetHashCode();
return hash;
}
}
private MonoBuildInfo()
{
}
public MonoBuildInfo(string solution, string configuration)
{
Solution = solution;
Configuration = configuration;
}
}
}

View file

@ -0,0 +1,260 @@
using Godot;
using System;
using Godot.Collections;
using GodotTools.Internals;
using File = GodotTools.Utils.File;
using Path = System.IO.Path;
namespace GodotTools
{
public class MonoBuildTab : VBoxContainer
{
public enum BuildResults
{
Error,
Success
}
[Serializable]
private class BuildIssue : Reference // TODO Remove Reference once we have proper serialization
{
public bool Warning { get; set; }
public string File { get; set; }
public int Line { get; set; }
public int Column { get; set; }
public string Code { get; set; }
public string Message { get; set; }
public string ProjectFile { get; set; }
}
private readonly Array<BuildIssue> issues = new Array<BuildIssue>(); // TODO Use List once we have proper serialization
private ItemList issuesList;
public bool BuildExited { get; private set; } = false;
public BuildResults? BuildResult { get; private set; } = null;
public int ErrorCount { get; private set; } = 0;
public int WarningCount { get; private set; } = 0;
public bool ErrorsVisible { get; set; } = true;
public bool WarningsVisible { get; set; } = true;
public Texture IconTexture
{
get
{
if (!BuildExited)
return GetIcon("Stop", "EditorIcons");
if (BuildResult == BuildResults.Error)
return GetIcon("StatusError", "EditorIcons");
return GetIcon("StatusSuccess", "EditorIcons");
}
}
public MonoBuildInfo BuildInfo { get; private set; }
private void _LoadIssuesFromFile(string csvFile)
{
using (var file = new Godot.File())
{
Error openError = file.Open(csvFile, Godot.File.ModeFlags.Read);
if (openError != Error.Ok)
return;
while (!file.EofReached())
{
string[] csvColumns = file.GetCsvLine();
if (csvColumns.Length == 1 && csvColumns[0].Empty())
return;
if (csvColumns.Length != 7)
{
GD.PushError($"Expected 7 columns, got {csvColumns.Length}");
continue;
}
var issue = new BuildIssue
{
Warning = csvColumns[0] == "warning",
File = csvColumns[1],
Line = int.Parse(csvColumns[2]),
Column = int.Parse(csvColumns[3]),
Code = csvColumns[4],
Message = csvColumns[5],
ProjectFile = csvColumns[6]
};
if (issue.Warning)
WarningCount += 1;
else
ErrorCount += 1;
issues.Add(issue);
}
}
}
private void _IssueActivated(int idx)
{
if (idx < 0 || idx >= issuesList.GetItemCount())
throw new IndexOutOfRangeException("Item list index out of range");
// Get correct issue idx from issue list
int issueIndex = (int) issuesList.GetItemMetadata(idx);
if (idx < 0 || idx >= issues.Count)
throw new IndexOutOfRangeException("Issue index out of range");
BuildIssue issue = issues[issueIndex];
if (issue.ProjectFile.Empty() && issue.File.Empty())
return;
string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : BuildInfo.Solution.GetBaseDir();
string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath());
if (!File.Exists(file))
return;
file = ProjectSettings.LocalizePath(file);
if (file.StartsWith("res://"))
{
var script = (Script) ResourceLoader.Load(file, typeHint: Internal.CSharpLanguageType);
if (script != null && Internal.ScriptEditorEdit(script, issue.Line, issue.Column))
Internal.EditorNodeShowScriptScreen();
}
}
public void UpdateIssuesList()
{
issuesList.Clear();
using (var warningIcon = GetIcon("Warning", "EditorIcons"))
using (var errorIcon = GetIcon("Error", "EditorIcons"))
{
for (int i = 0; i < issues.Count; i++)
{
BuildIssue issue = issues[i];
if (!(issue.Warning ? WarningsVisible : ErrorsVisible))
continue;
string tooltip = string.Empty;
tooltip += $"Message: {issue.Message}";
if (!issue.Code.Empty())
tooltip += $"\nCode: {issue.Code}";
tooltip += $"\nType: {(issue.Warning ? "warning" : "error")}";
string text = string.Empty;
if (!issue.File.Empty())
{
text += $"{issue.File}({issue.Line},{issue.Column}): ";
tooltip += $"\nFile: {issue.File}";
tooltip += $"\nLine: {issue.Line}";
tooltip += $"\nColumn: {issue.Column}";
}
if (!issue.ProjectFile.Empty())
tooltip += $"\nProject: {issue.ProjectFile}";
text += issue.Message;
int lineBreakIdx = text.IndexOf("\n", StringComparison.Ordinal);
string itemText = lineBreakIdx == -1 ? text : text.Substring(0, lineBreakIdx);
issuesList.AddItem(itemText, issue.Warning ? warningIcon : errorIcon);
int index = issuesList.GetItemCount() - 1;
issuesList.SetItemTooltip(index, tooltip);
issuesList.SetItemMetadata(index, i);
}
}
}
public void OnBuildStart()
{
BuildExited = false;
issues.Clear();
WarningCount = 0;
ErrorCount = 0;
UpdateIssuesList();
GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this);
}
public void OnBuildExit(BuildResults result)
{
BuildExited = true;
BuildResult = result;
_LoadIssuesFromFile(Path.Combine(BuildInfo.LogsDirPath, GodotSharpBuilds.MsBuildIssuesFileName));
UpdateIssuesList();
GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this);
}
public void OnBuildExecFailed(string cause)
{
BuildExited = true;
BuildResult = BuildResults.Error;
issuesList.Clear();
var issue = new BuildIssue {Message = cause, Warning = false};
ErrorCount += 1;
issues.Add(issue);
UpdateIssuesList();
GodotSharpEditor.Instance.MonoBottomPanel.RaiseBuildTab(this);
}
public void RestartBuild()
{
if (!BuildExited)
throw new InvalidOperationException("Build already started");
GodotSharpBuilds.RestartBuild(this);
}
public void StopBuild()
{
if (!BuildExited)
throw new InvalidOperationException("Build is not in progress");
GodotSharpBuilds.StopBuild(this);
}
public override void _Ready()
{
base._Ready();
issuesList = new ItemList {SizeFlagsVertical = (int) SizeFlags.ExpandFill};
issuesList.Connect("item_activated", this, nameof(_IssueActivated));
AddChild(issuesList);
}
private MonoBuildTab()
{
}
public MonoBuildTab(MonoBuildInfo buildInfo)
{
BuildInfo = buildInfo;
}
}
}

View file

@ -1,11 +1,11 @@
using GodotTools.Core;
using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using GodotTools.Internals;
namespace GodotSharpTools.Editor
namespace GodotTools
{
public class MonoDevelopInstance
{
@ -15,24 +15,24 @@ namespace GodotSharpTools.Editor
VisualStudioForMac = 1
}
readonly string solutionFile;
readonly EditorId editorId;
private readonly string solutionFile;
private readonly EditorId editorId;
Process process;
private Process process;
public void Execute(string[] files)
public void Execute(params string[] files)
{
bool newWindow = process == null || process.HasExited;
List<string> args = new List<string>();
var args = new List<string>();
string command;
if (Utils.OS.IsOSX())
{
string bundleId = codeEditorBundleIds[editorId];
string bundleId = CodeEditorBundleIds[editorId];
if (IsApplicationBundleInstalled(bundleId))
if (Internal.IsOsxAppBundleInstalled(bundleId))
{
command = "open";
@ -47,12 +47,12 @@ namespace GodotSharpTools.Editor
}
else
{
command = codeEditorPaths[editorId];
command = CodeEditorPaths[editorId];
}
}
else
{
command = codeEditorPaths[editorId];
command = CodeEditorPaths[editorId];
}
args.Add("--ipc-tcp");
@ -72,7 +72,7 @@ namespace GodotSharpTools.Editor
if (newWindow)
{
process = Process.Start(new ProcessStartInfo()
process = Process.Start(new ProcessStartInfo
{
FileName = command,
Arguments = string.Join(" ", args),
@ -81,12 +81,12 @@ namespace GodotSharpTools.Editor
}
else
{
Process.Start(new ProcessStartInfo()
Process.Start(new ProcessStartInfo
{
FileName = command,
Arguments = string.Join(" ", args),
UseShellExecute = false
});
})?.Dispose();
}
}
@ -99,45 +99,42 @@ namespace GodotSharpTools.Editor
this.editorId = editorId;
}
[MethodImpl(MethodImplOptions.InternalCall)]
private extern static bool IsApplicationBundleInstalled(string bundleId);
static readonly IReadOnlyDictionary<EditorId, string> codeEditorPaths;
static readonly IReadOnlyDictionary<EditorId, string> codeEditorBundleIds;
private static readonly IReadOnlyDictionary<EditorId, string> CodeEditorPaths;
private static readonly IReadOnlyDictionary<EditorId, string> CodeEditorBundleIds;
static MonoDevelopInstance()
{
if (Utils.OS.IsOSX())
{
codeEditorPaths = new Dictionary<EditorId, string>
CodeEditorPaths = new Dictionary<EditorId, string>
{
// Rely on PATH
{ EditorId.MonoDevelop, "monodevelop" },
{ EditorId.VisualStudioForMac, "VisualStudio" }
{EditorId.MonoDevelop, "monodevelop"},
{EditorId.VisualStudioForMac, "VisualStudio"}
};
codeEditorBundleIds = new Dictionary<EditorId, string>
CodeEditorBundleIds = new Dictionary<EditorId, string>
{
// TODO EditorId.MonoDevelop
{ EditorId.VisualStudioForMac, "com.microsoft.visual-studio" }
{EditorId.VisualStudioForMac, "com.microsoft.visual-studio"}
};
}
else if (Utils.OS.IsWindows())
{
codeEditorPaths = new Dictionary<EditorId, string>
CodeEditorPaths = new Dictionary<EditorId, string>
{
// XamarinStudio is no longer a thing, and the latest version is quite old
// MonoDevelop is available from source only on Windows. The recommendation
// is to use Visual Studio instead. Since there are no official builds, we
// will rely on custom MonoDevelop builds being added to PATH.
{ EditorId.MonoDevelop, "MonoDevelop.exe" }
{EditorId.MonoDevelop, "MonoDevelop.exe"}
};
}
else if (Utils.OS.IsUnix())
{
codeEditorPaths = new Dictionary<EditorId, string>
CodeEditorPaths = new Dictionary<EditorId, string>
{
// Rely on PATH
{ EditorId.MonoDevelop, "monodevelop" }
{EditorId.MonoDevelop, "monodevelop"}
};
}
}

View file

@ -0,0 +1,26 @@
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("GodotTools")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("Godot Engine contributors")]
[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("")]

View file

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
namespace GodotTools.Utils
{
public static class CollectionExtensions
{
public static T SelectFirstNotNull<T>(this IEnumerable<T> enumerable, Func<T, T> predicate, T orElse = null)
where T : class
{
foreach (T elem in enumerable)
{
if (predicate(elem) != null)
return elem;
}
return orElse;
}
}
}

View file

@ -0,0 +1,40 @@
using System.IO;
using Godot;
namespace GodotTools.Utils
{
public static class Directory
{
private static string GlobalizePath(this string path)
{
return ProjectSettings.GlobalizePath(path);
}
public static bool Exists(string path)
{
return System.IO.Directory.Exists(path.GlobalizePath());
}
/// Create directory recursively
public static DirectoryInfo CreateDirectory(string path)
{
return System.IO.Directory.CreateDirectory(path.GlobalizePath());
}
public static void Delete(string path, bool recursive)
{
System.IO.Directory.Delete(path.GlobalizePath(), recursive);
}
public static string[] GetDirectories(string path, string searchPattern, SearchOption searchOption)
{
return System.IO.Directory.GetDirectories(path.GlobalizePath(), searchPattern, searchOption);
}
public static string[] GetFiles(string path, string searchPattern, SearchOption searchOption)
{
return System.IO.Directory.GetFiles(path.GlobalizePath(), searchPattern, searchOption);
}
}
}

View file

@ -0,0 +1,43 @@
using System;
using Godot;
namespace GodotTools.Utils
{
public static class File
{
private static string GlobalizePath(this string path)
{
return ProjectSettings.GlobalizePath(path);
}
public static void WriteAllText(string path, string contents)
{
System.IO.File.WriteAllText(path.GlobalizePath(), contents);
}
public static bool Exists(string path)
{
return System.IO.File.Exists(path.GlobalizePath());
}
public static DateTime GetLastWriteTime(string path)
{
return System.IO.File.GetLastWriteTime(path.GlobalizePath());
}
public static void Delete(string path)
{
System.IO.File.Delete(path.GlobalizePath());
}
public static void Copy(string sourceFileName, string destFileName)
{
System.IO.File.Copy(sourceFileName.GlobalizePath(), destFileName.GlobalizePath(), overwrite: true);
}
public static byte[] ReadAllBytes(string path)
{
return System.IO.File.ReadAllBytes(path.GlobalizePath());
}
}
}

View file

@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
namespace GodotTools.Utils
{
public static class OS
{
[MethodImpl(MethodImplOptions.InternalCall)]
extern static string GetPlatformName();
const string HaikuName = "Haiku";
const string OSXName = "OSX";
const string ServerName = "Server";
const string UWPName = "UWP";
const string WindowsName = "Windows";
const string X11Name = "X11";
public static bool IsHaiku()
{
return HaikuName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsOSX()
{
return OSXName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsServer()
{
return ServerName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsUWP()
{
return UWPName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsWindows()
{
return WindowsName.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
public static bool IsX11()
{
return X11Name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase);
}
private static bool? _isUnixCache;
private static readonly string[] UnixPlatforms = {HaikuName, OSXName, ServerName, X11Name};
public static bool IsUnix()
{
if (_isUnixCache.HasValue)
return _isUnixCache.Value;
string osName = GetPlatformName();
_isUnixCache = UnixPlatforms.Any(p => p.Equals(osName, StringComparison.OrdinalIgnoreCase));
return _isUnixCache.Value;
}
public static char PathSep => IsWindows() ? ';' : ':';
public static string PathWhich(string name)
{
string[] windowsExts = IsWindows() ? Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) : null;
string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep);
var searchDirs = new List<string>();
if (pathDirs != null)
searchDirs.AddRange(pathDirs);
searchDirs.Add(System.IO.Directory.GetCurrentDirectory()); // last in the list
foreach (var dir in searchDirs)
{
string path = Path.Combine(dir, name);
if (IsWindows() && windowsExts != null)
{
foreach (var extension in windowsExts)
{
string pathWithExtension = path + extension;
if (File.Exists(pathWithExtension))
return pathWithExtension;
}
}
else
{
if (File.Exists(path))
return path;
}
}
return null;
}
public static void RunProcess(string command, IEnumerable<string> arguments)
{
string CmdLineArgsToString(IEnumerable<string> args)
{
return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg));
}
ProcessStartInfo startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments))
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
};
using (Process process = Process.Start(startInfo))
{
if (process == null)
throw new Exception("No process was started");
process.BeginOutputReadLine();
process.BeginErrorReadLine();
}
}
}
}

View file

@ -861,26 +861,22 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) {
p_output.append("\n#pragma warning restore CS1591\n");
}
Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution) {
String proj_dir = p_solution_dir.plus_file(CORE_API_ASSEMBLY_NAME);
Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir, Vector<String> &r_compile_items) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
if (!DirAccess::exists(proj_dir)) {
Error err = da->make_dir_recursive(proj_dir);
if (!DirAccess::exists(p_proj_dir)) {
Error err = da->make_dir_recursive(p_proj_dir);
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
}
da->change_dir(proj_dir);
da->change_dir(p_proj_dir);
da->make_dir("Core");
da->make_dir("ObjectType");
String core_dir = path_join(proj_dir, "Core");
String obj_type_dir = path_join(proj_dir, "ObjectType");
Vector<String> compile_items;
String core_dir = path_join(p_proj_dir, "Core");
String obj_type_dir = path_join(p_proj_dir, "ObjectType");
// Generate source file for global scope constants and enums
{
@ -891,7 +887,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir,
if (save_err != OK)
return save_err;
compile_items.push_back(output_file);
r_compile_items.push_back(output_file);
}
for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) {
@ -909,7 +905,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir,
if (err != OK)
return err;
compile_items.push_back(output_file);
r_compile_items.push_back(output_file);
}
// Generate sources from compressed files
@ -939,7 +935,7 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir,
file->store_buffer(data.ptr(), data.size());
file->close();
compile_items.push_back(output_file);
r_compile_items.push_back(output_file);
}
StringBuilder cs_icalls_content;
@ -981,43 +977,27 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir,
if (err != OK)
return err;
compile_items.push_back(internal_methods_file);
String guid = CSharpProject::generate_core_api_project(proj_dir, compile_items);
DotNetSolution::ProjectInfo proj_info;
proj_info.guid = guid;
proj_info.relpath = String(CORE_API_ASSEMBLY_NAME).plus_file(CORE_API_ASSEMBLY_NAME ".csproj");
proj_info.configs.push_back("Debug");
proj_info.configs.push_back("Release");
r_solution.add_new_project(CORE_API_ASSEMBLY_NAME, proj_info);
_log("The solution and C# project for the Core API was generated successfully\n");
r_compile_items.push_back(internal_methods_file);
return OK;
}
Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution) {
String proj_dir = p_solution_dir.plus_file(EDITOR_API_ASSEMBLY_NAME);
Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir, Vector<String> &r_compile_items) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V(!da, ERR_CANT_CREATE);
if (!DirAccess::exists(proj_dir)) {
Error err = da->make_dir_recursive(proj_dir);
if (!DirAccess::exists(p_proj_dir)) {
Error err = da->make_dir_recursive(p_proj_dir);
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
}
da->change_dir(proj_dir);
da->change_dir(p_proj_dir);
da->make_dir("Core");
da->make_dir("ObjectType");
String core_dir = path_join(proj_dir, "Core");
String obj_type_dir = path_join(proj_dir, "ObjectType");
Vector<String> compile_items;
String core_dir = path_join(p_proj_dir, "Core");
String obj_type_dir = path_join(p_proj_dir, "ObjectType");
for (OrderedHashMap<StringName, TypeInterface>::Element E = obj_types.front(); E; E = E.next()) {
const TypeInterface &itype = E.get();
@ -1034,7 +1014,7 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir
if (err != OK)
return err;
compile_items.push_back(output_file);
r_compile_items.push_back(output_file);
}
StringBuilder cs_icalls_content;
@ -1077,58 +1057,56 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_solution_dir
if (err != OK)
return err;
compile_items.push_back(internal_methods_file);
String guid = CSharpProject::generate_editor_api_project(proj_dir, "../" CORE_API_ASSEMBLY_NAME "/" CORE_API_ASSEMBLY_NAME ".csproj", compile_items);
DotNetSolution::ProjectInfo proj_info;
proj_info.guid = guid;
proj_info.relpath = String(EDITOR_API_ASSEMBLY_NAME).plus_file(EDITOR_API_ASSEMBLY_NAME ".csproj");
proj_info.configs.push_back("Debug");
proj_info.configs.push_back("Release");
r_solution.add_new_project(EDITOR_API_ASSEMBLY_NAME, proj_info);
_log("The solution and C# project for the Editor API was generated successfully\n");
r_compile_items.push_back(internal_methods_file);
return OK;
}
Error BindingsGenerator::generate_cs_api(const String &p_output_dir) {
String output_dir = DirAccess::get_full_path(p_output_dir, DirAccess::ACCESS_FILESYSTEM);
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);
if (!DirAccess::exists(output_dir)) {
Error err = da->make_dir_recursive(output_dir);
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
}
DotNetSolution solution(API_SOLUTION_NAME);
if (!solution.set_path(p_output_dir))
return ERR_FILE_NOT_FOUND;
Error proj_err;
proj_err = generate_cs_core_project(p_output_dir, solution);
// Generate GodotSharp source files
String core_proj_dir = output_dir.plus_file(CORE_API_ASSEMBLY_NAME);
Vector<String> core_compile_items;
proj_err = generate_cs_core_project(core_proj_dir, core_compile_items);
if (proj_err != OK) {
ERR_PRINT("Generation of the Core API C# project failed");
return proj_err;
}
proj_err = generate_cs_editor_project(p_output_dir, solution);
// Generate GodotSharpEditor source files
String editor_proj_dir = output_dir.plus_file(EDITOR_API_ASSEMBLY_NAME);
Vector<String> editor_compile_items;
proj_err = generate_cs_editor_project(editor_proj_dir, editor_compile_items);
if (proj_err != OK) {
ERR_PRINT("Generation of the Editor API C# project failed");
return proj_err;
}
Error sln_error = solution.save();
if (sln_error != OK) {
ERR_PRINT("Failed to save API solution");
return sln_error;
// Generate solution
if (!CSharpProject::generate_api_solution(output_dir,
core_proj_dir, core_compile_items, editor_proj_dir, editor_compile_items)) {
return ERR_CANT_CREATE;
}
_log("The solution for the Godot API was generated successfully\n");
return OK;
}
@ -1311,8 +1289,9 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
output.append(MEMBER_BEGIN "private static Godot.Object singleton;\n");
output.append(MEMBER_BEGIN "public static Godot.Object Singleton\n" INDENT2 "{\n" INDENT3
"get\n" INDENT3 "{\n" INDENT4 "if (singleton == null)\n" INDENT5
"singleton = Engine.GetSingleton(" BINDINGS_NATIVE_NAME_FIELD ");\n" INDENT4
"return singleton;\n" INDENT3 "}\n" INDENT2 "}\n");
"singleton = Engine.GetSingleton(typeof(");
output.append(itype.proxy_name);
output.append(").Name);\n" INDENT4 "return singleton;\n" INDENT3 "}\n" INDENT2 "}\n");
output.append(MEMBER_BEGIN "private const string " BINDINGS_NATIVE_NAME_FIELD " = \"");
output.append(itype.name);
@ -2347,6 +2326,13 @@ void BindingsGenerator::_populate_object_type_interfaces() {
imethod.return_type.is_enum = true;
} else if (return_info.class_name != StringName()) {
imethod.return_type.cname = return_info.class_name;
if (!imethod.is_virtual && ClassDB::is_parent_class(return_info.class_name, name_cache.type_Reference) && return_info.hint != PROPERTY_HINT_RESOURCE_TYPE) {
/* clang-format off */
ERR_PRINTS("Return type is reference but hint is not " _STR(PROPERTY_HINT_RESOURCE_TYPE) "."
" Are you returning a reference type by pointer? Method: " + itype.name + "." + imethod.name);
/* clang-format on */
ERR_FAIL();
}
} else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
imethod.return_type.cname = return_info.hint_string;
} else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
@ -3018,36 +3004,49 @@ void BindingsGenerator::_initialize() {
void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) {
const int NUM_OPTIONS = 2;
String mono_glue_option = "--generate-mono-glue";
String cs_api_option = "--generate-cs-api";
String generate_all_glue_option = "--generate-mono-glue";
String generate_cs_glue_option = "--generate-mono-cs-glue";
String generate_cpp_glue_option = "--generate-mono-cpp-glue";
String mono_glue_path;
String cs_api_path;
String glue_dir_path;
String cs_dir_path;
String cpp_dir_path;
int options_left = NUM_OPTIONS;
const List<String>::Element *elem = p_cmdline_args.front();
while (elem && options_left) {
if (elem->get() == mono_glue_option) {
if (elem->get() == generate_all_glue_option) {
const List<String>::Element *path_elem = elem->next();
if (path_elem) {
mono_glue_path = path_elem->get();
glue_dir_path = path_elem->get();
elem = elem->next();
} else {
ERR_PRINTS(mono_glue_option + ": No output directory specified");
ERR_PRINTS(generate_all_glue_option + ": No output directory specified (expected path to {GODOT_ROOT}/modules/mono/glue)");
}
--options_left;
} else if (elem->get() == cs_api_option) {
} else if (elem->get() == generate_cs_glue_option) {
const List<String>::Element *path_elem = elem->next();
if (path_elem) {
cs_api_path = path_elem->get();
cs_dir_path = path_elem->get();
elem = elem->next();
} else {
ERR_PRINTS(cs_api_option + ": No output directory specified");
ERR_PRINTS(generate_cs_glue_option + ": No output directory specified");
}
--options_left;
} else if (elem->get() == generate_cpp_glue_option) {
const List<String>::Element *path_elem = elem->next();
if (path_elem) {
cpp_dir_path = path_elem->get();
elem = elem->next();
} else {
ERR_PRINTS(generate_cpp_glue_option + ": No output directory specified");
}
--options_left;
@ -3056,18 +3055,26 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args)
elem = elem->next();
}
if (mono_glue_path.length() || cs_api_path.length()) {
if (glue_dir_path.length() || cs_dir_path.length() || cpp_dir_path.length()) {
BindingsGenerator bindings_generator;
bindings_generator.set_log_print_enabled(true);
if (mono_glue_path.length()) {
if (bindings_generator.generate_glue(mono_glue_path) != OK)
ERR_PRINTS(mono_glue_option + ": Failed to generate mono glue");
if (glue_dir_path.length()) {
if (bindings_generator.generate_glue(glue_dir_path) != OK)
ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C++ glue");
if (bindings_generator.generate_cs_api(glue_dir_path.plus_file("Managed/Generated")) != OK)
ERR_PRINTS(generate_all_glue_option + ": Failed to generate the C# API");
}
if (cs_api_path.length()) {
if (bindings_generator.generate_cs_api(cs_api_path) != OK)
ERR_PRINTS(cs_api_option + ": Failed to generate the C# API");
if (cs_dir_path.length()) {
if (bindings_generator.generate_cs_api(cs_dir_path) != OK)
ERR_PRINTS(generate_cs_glue_option + ": Failed to generate the C# API");
}
if (cpp_dir_path.length()) {
if (bindings_generator.generate_glue(cpp_dir_path) != OK)
ERR_PRINTS(generate_cpp_glue_option + ": Failed to generate the C++ glue");
}
// Exit once done

View file

@ -33,7 +33,6 @@
#include "core/class_db.h"
#include "core/string_builder.h"
#include "dotnet_solution.h"
#include "editor/doc/doc_data.h"
#include "editor/editor_help.h"
@ -614,12 +613,13 @@ class BindingsGenerator {
void _initialize();
public:
Error generate_cs_core_project(const String &p_solution_dir, DotNetSolution &r_solution);
Error generate_cs_editor_project(const String &p_solution_dir, DotNetSolution &r_solution);
Error generate_cs_core_project(const String &p_proj_dir, Vector<String> &r_compile_files);
Error generate_cs_editor_project(const String &p_proj_dir, Vector<String> &r_compile_items);
Error generate_cs_api(const String &p_output_dir);
Error generate_glue(const String &p_output_dir);
void set_log_print_enabled(bool p_enabled) { log_print_enabled = p_enabled; }
_FORCE_INLINE_ bool is_log_print_enabled() { return log_print_enabled; }
_FORCE_INLINE_ void set_log_print_enabled(bool p_enabled) { log_print_enabled = p_enabled; }
static uint32_t get_version();

View file

@ -44,66 +44,54 @@
namespace CSharpProject {
String generate_core_api_project(const String &p_dir, const Vector<String> &p_files) {
bool generate_api_solution_impl(const String &p_solution_dir, const String &p_core_proj_dir, const Vector<String> &p_core_compile_items,
const String &p_editor_proj_dir, const Vector<String> &p_editor_compile_items,
GDMonoAssembly *p_tools_project_editor_assembly) {
_GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
GDMonoClass *klass = p_tools_project_editor_assembly->get_class("GodotTools.ProjectEditor", "ApiSolutionGenerator");
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 };
Variant solution_dir = p_solution_dir;
Variant core_proj_dir = p_core_proj_dir;
Variant core_compile_items = p_core_compile_items;
Variant editor_proj_dir = p_editor_proj_dir;
Variant editor_compile_items = p_editor_compile_items;
const Variant *args[5] = { &solution_dir, &core_proj_dir, &core_compile_items, &editor_proj_dir, &editor_compile_items };
MonoException *exc = NULL;
MonoObject *ret = klass->get_method("GenCoreApiProject", 2)->invoke(NULL, args, &exc);
klass->get_method("GenerateApiSolution", 5)->invoke(NULL, args, &exc);
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
ERR_FAIL_V(String());
ERR_FAIL_V(false);
}
return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String();
return true;
}
String generate_editor_api_project(const String &p_dir, const String &p_core_proj_path, const Vector<String> &p_files) {
bool generate_api_solution(const String &p_solution_dir, const String &p_core_proj_dir, const Vector<String> &p_core_compile_items,
const String &p_editor_proj_dir, const Vector<String> &p_editor_compile_items) {
_GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
if (GDMono::get_singleton()->get_tools_project_editor_assembly()) {
return generate_api_solution_impl(p_solution_dir, p_core_proj_dir, p_core_compile_items,
p_editor_proj_dir, p_editor_compile_items,
GDMono::get_singleton()->get_tools_project_editor_assembly());
} else {
MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.ApiSolutionGenerationDomain");
CRASH_COND(temp_domain == NULL);
_GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(temp_domain);
GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectGenerator");
_GDMONO_SCOPE_DOMAIN_(temp_domain);
Variant dir = p_dir;
Variant core_proj_path = p_core_proj_path;
Variant compile_items = p_files;
const Variant *args[3] = { &dir, &core_proj_path, &compile_items };
MonoException *exc = NULL;
MonoObject *ret = klass->get_method("GenEditorApiProject", 3)->invoke(NULL, args, &exc);
GDMonoAssembly *tools_project_editor_assembly = NULL;
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
ERR_FAIL_V(String());
if (!GDMono::get_singleton()->load_assembly("GodotTools.ProjectEditor", &tools_project_editor_assembly)) {
ERR_EXPLAIN("Failed to load assembly: 'GodotTools.ProjectEditor'");
ERR_FAIL_V(false);
}
return generate_api_solution_impl(p_solution_dir, p_core_proj_dir, p_core_compile_items,
p_editor_proj_dir, p_editor_compile_items,
tools_project_editor_assembly);
}
return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String();
}
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 };
MonoException *exc = NULL;
MonoObject *ret = klass->get_method("GenGameProject", 3)->invoke(NULL, args, &exc);
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
ERR_FAIL_V(String());
}
return ret ? GDMonoMarshal::mono_string_to_godot((MonoString *)ret) : String();
}
void add_item(const String &p_project_path, const String &p_item_type, const String &p_include) {
@ -111,9 +99,9 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str
if (!GLOBAL_DEF("mono/project/auto_update_project", true))
return;
_GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
GDMonoAssembly *tools_project_editor_assembly = GDMono::get_singleton()->get_tools_project_editor_assembly();
GDMonoClass *klass = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Project", "ProjectUtils");
GDMonoClass *klass = tools_project_editor_assembly->get_class("GodotTools.ProjectEditor", "ProjectUtils");
Variant project_path = p_project_path;
Variant item_type = p_item_type;
@ -128,126 +116,4 @@ void add_item(const String &p_project_path, const String &p_item_type, const Str
}
}
Error generate_scripts_metadata(const String &p_project_path, const String &p_output_path) {
_GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
if (FileAccess::exists(p_output_path)) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
Error rm_err = da->remove(p_output_path);
ERR_EXPLAIN("Failed to remove old scripts metadata file");
ERR_FAIL_COND_V(rm_err != OK, rm_err);
}
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_or_nothing();
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_PRINTS("Parse error: " + scp.get_error());
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);
String base_dir = p_output_path.get_base_dir();
if (!DirAccess::exists(base_dir)) {
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
Error err = da->make_dir_recursive(base_dir);
ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE);
}
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

View file

@ -35,14 +35,11 @@
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_proj_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>());
bool generate_api_solution(const String &p_solution_dir, const String &p_core_proj_dir, const Vector<String> &p_core_compile_items,
const String &p_editor_proj_dir, const Vector<String> &p_editor_compile_items);
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

View file

@ -1,140 +0,0 @@
/*************************************************************************/
/* dotnet_solution.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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 "dotnet_solution.h"
#include "core/os/dir_access.h"
#include "core/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 DotNetSolution::add_new_project(const String &p_name, const ProjectInfo &p_project_info) {
projects[p_name] = p_project_info;
}
bool DotNetSolution::has_project(const String &p_name) const {
return projects.find(p_name) != NULL;
}
const DotNetSolution::ProjectInfo &DotNetSolution::get_project_info(const String &p_name) const {
return projects[p_name];
}
bool DotNetSolution::remove_project(const String &p_name) {
return projects.erase(p_name);
}
Error DotNetSolution::save() {
bool dir_exists = DirAccess::exists(path);
ERR_EXPLAIN("The directory does not exist.");
ERR_FAIL_COND_V(!dir_exists, ERR_FILE_NOT_FOUND);
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 &proj_info = E->value();
bool is_front = E == projects.front();
if (!is_front)
projs_decl += "\n";
projs_decl += sformat(PROJECT_DECLARATION, name, proj_info.relpath.replace("/", "\\"), proj_info.guid);
for (int i = 0; i < proj_info.configs.size(); i++) {
const String &config = proj_info.configs[i];
if (i != 0 || !is_front) {
sln_platform_cfg += "\n";
proj_platform_cfg += "\n";
}
sln_platform_cfg += sformat(SOLUTION_PLATFORMS_CONFIG, config);
proj_platform_cfg += sformat(PROJECT_PLATFORMS_CONFIG, proj_info.guid, config);
}
}
String content = sformat(SOLUTION_TEMPLATE, projs_decl, sln_platform_cfg, proj_platform_cfg);
FileAccess *file = FileAccess::open(path_join(path, name + ".sln"), FileAccess::WRITE);
ERR_FAIL_NULL_V(file, ERR_FILE_CANT_WRITE);
file->store_string(content);
file->close();
memdelete(file);
return OK;
}
bool DotNetSolution::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;
}
String DotNetSolution::get_path() {
return path;
}
DotNetSolution::DotNetSolution(const String &p_name) {
name = p_name;
}

View file

@ -1,63 +0,0 @@
/*************************************************************************/
/* dotnet_solution.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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 "core/map.h"
#include "core/ustring.h"
struct DotNetSolution {
String name;
struct ProjectInfo {
String guid;
String relpath; // Must be relative to the solution directory
Vector<String> configs;
};
void add_new_project(const String &p_name, const ProjectInfo &p_project_info);
bool has_project(const String &p_name) const;
const ProjectInfo &get_project_info(const String &p_name) const;
bool remove_project(const String &p_name);
Error save();
bool set_path(const String &p_existing_path);
String get_path();
DotNetSolution(const String &p_name);
private:
String path;
Map<String, ProjectInfo> projects;
};
#endif // NET_SOLUTION_H

View file

@ -0,0 +1,429 @@
/*************************************************************************/
/* editor_internal_calls.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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 "editor_internal_calls.h"
#include "core/message_queue.h"
#include "core/os/os.h"
#include "core/version.h"
#include "editor/editor_node.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/script_editor_debugger.h"
#include "main/main.h"
#include "../csharp_script.h"
#include "../glue/cs_glue_version.gen.h"
#include "../godotsharp_dirs.h"
#include "../mono_gd/gd_mono_marshal.h"
#include "../utils/osx_utils.h"
#include "bindings_generator.h"
#include "godotsharp_export.h"
#include "script_class_parser.h"
MonoString *godot_icall_GodotSharpDirs_ResDataDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_data_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResMetadataDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_metadata_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResAssembliesBaseDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_assemblies_base_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResAssembliesDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_assemblies_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResConfigDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_config_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResTempDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_assemblies_base_dir());
}
MonoString *godot_icall_GodotSharpDirs_ResTempAssembliesDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_assemblies_dir());
}
MonoString *godot_icall_GodotSharpDirs_MonoUserDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_user_dir());
}
MonoString *godot_icall_GodotSharpDirs_MonoLogsDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_logs_dir());
}
MonoString *godot_icall_GodotSharpDirs_MonoSolutionsDir() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_solutions_dir());
#else
return NULL;
#endif
}
MonoString *godot_icall_GodotSharpDirs_BuildLogsDirs() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_build_logs_dir());
#else
return NULL;
#endif
}
MonoString *godot_icall_GodotSharpDirs_ProjectSlnPath() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_sln_path());
#else
return NULL;
#endif
}
MonoString *godot_icall_GodotSharpDirs_ProjectCsProjPath() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_csproj_path());
#else
return NULL;
#endif
}
MonoString *godot_icall_GodotSharpDirs_DataEditorToolsDir() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_tools_dir());
#else
return NULL;
#endif
}
MonoString *godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir() {
#ifdef TOOLS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_prebuilt_api_dir());
#else
return NULL;
#endif
}
MonoString *godot_icall_GodotSharpDirs_DataMonoEtcDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_etc_dir());
}
MonoString *godot_icall_GodotSharpDirs_DataMonoLibDir() {
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_lib_dir());
}
MonoString *godot_icall_GodotSharpDirs_DataMonoBinDir() {
#ifdef WINDOWS_ENABLED
return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_bin_dir());
#else
return NULL;
#endif
}
void godot_icall_EditorProgress_Create(MonoString *p_task, MonoString *p_label, int32_t p_amount, MonoBoolean p_can_cancel) {
String task = GDMonoMarshal::mono_string_to_godot(p_task);
String label = GDMonoMarshal::mono_string_to_godot(p_label);
EditorNode::progress_add_task(task, label, p_amount, (bool)p_can_cancel);
}
void godot_icall_EditorProgress_Dispose(MonoString *p_task) {
String task = GDMonoMarshal::mono_string_to_godot(p_task);
EditorNode::progress_end_task(task);
}
MonoBoolean godot_icall_EditorProgress_Step(MonoString *p_task, MonoString *p_state, int32_t p_step, MonoBoolean p_force_refresh) {
String task = GDMonoMarshal::mono_string_to_godot(p_task);
String state = GDMonoMarshal::mono_string_to_godot(p_state);
return EditorNode::progress_task_step(task, state, p_step, (bool)p_force_refresh);
}
BindingsGenerator *godot_icall_BindingsGenerator_Ctor() {
return memnew(BindingsGenerator);
}
void godot_icall_BindingsGenerator_Dtor(BindingsGenerator *p_handle) {
memdelete(p_handle);
}
MonoBoolean godot_icall_BindingsGenerator_LogPrintEnabled(BindingsGenerator *p_handle) {
return p_handle->is_log_print_enabled();
}
void godot_icall_BindingsGenerator_SetLogPrintEnabled(BindingsGenerator p_handle, MonoBoolean p_enabled) {
p_handle.set_log_print_enabled(p_enabled);
}
int32_t godot_icall_BindingsGenerator_GenerateCsApi(BindingsGenerator *p_handle, MonoString *p_output_dir) {
String output_dir = GDMonoMarshal::mono_string_to_godot(p_output_dir);
return p_handle->generate_cs_api(output_dir);
}
uint32_t godot_icall_BindingsGenerator_Version() {
return BindingsGenerator::get_version();
}
uint32_t godot_icall_BindingsGenerator_CsGlueVersion() {
return CS_GLUE_VERSION;
}
int32_t godot_icall_ScriptClassParser_ParseFile(MonoString *p_filepath, MonoObject *p_classes) {
String filepath = GDMonoMarshal::mono_string_to_godot(p_filepath);
ScriptClassParser scp;
Error err = scp.parse_file(filepath);
if (err == OK) {
Array classes = GDMonoMarshal::mono_object_to_variant(p_classes);
const Vector<ScriptClassParser::ClassDecl> &class_decls = scp.get_classes();
for (int i = 0; i < class_decls.size(); i++) {
const ScriptClassParser::ClassDecl &classDecl = class_decls[i];
Dictionary classDeclDict;
classDeclDict["name"] = classDecl.name;
classDeclDict["namespace"] = classDecl.namespace_;
classDeclDict["nested"] = classDecl.nested;
classDeclDict["base_count"] = classDecl.base.size();
classes.push_back(classDeclDict);
}
}
return err;
}
uint32_t godot_icall_GodotSharpExport_GetExportedAssemblyDependencies(MonoString *p_project_dll_name, MonoString *p_project_dll_src_path,
MonoString *p_build_config, MonoString *p_custom_lib_dir, MonoObject *r_dependencies) {
String project_dll_name = GDMonoMarshal::mono_string_to_godot(p_project_dll_name);
String project_dll_src_path = GDMonoMarshal::mono_string_to_godot(p_project_dll_src_path);
String build_config = GDMonoMarshal::mono_string_to_godot(p_build_config);
String custom_lib_dir = GDMonoMarshal::mono_string_to_godot(p_custom_lib_dir);
Dictionary dependencies = GDMonoMarshal::mono_object_to_variant(r_dependencies);
return GodotSharpExport::get_exported_assembly_dependencies(project_dll_name, project_dll_src_path, build_config, custom_lib_dir, dependencies);
}
float godot_icall_Internal_EditorScale() {
return EDSCALE;
}
MonoObject *godot_icall_Internal_GlobalDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed);
return GDMonoMarshal::variant_to_mono_object(result);
}
MonoObject *godot_icall_Internal_EditorDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) {
String setting = GDMonoMarshal::mono_string_to_godot(p_setting);
Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value);
Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed);
return GDMonoMarshal::variant_to_mono_object(result);
}
MonoString *godot_icall_Internal_FullTemplatesDir() {
String full_templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG);
return GDMonoMarshal::mono_string_from_godot(full_templates_dir);
}
MonoString *godot_icall_Internal_SimplifyGodotPath(MonoString *p_path) {
String path = GDMonoMarshal::mono_string_to_godot(p_path);
return GDMonoMarshal::mono_string_from_godot(path.simplify_path());
}
MonoBoolean godot_icall_Internal_IsOsxAppBundleInstalled(MonoString *p_bundle_id) {
#ifdef OSX_ENABLED
String bundle_id = GDMonoMarshal::mono_string_to_godot(p_bundle_id);
return (MonoBoolean)osx_is_app_bundle_installed;
#else
(void)p_bundle_id; // UNUSED
return (MonoBoolean) false;
#endif
}
MonoBoolean godot_icall_Internal_MetadataIsApiAssemblyInvalidated(int32_t p_api_type) {
return GDMono::get_singleton()->metadata_is_api_assembly_invalidated((APIAssembly::Type)p_api_type);
}
void godot_icall_Internal_MetadataSetApiAssemblyInvalidated(int32_t p_api_type, MonoBoolean p_invalidated) {
GDMono::get_singleton()->metadata_set_api_assembly_invalidated((APIAssembly::Type)p_api_type, (bool)p_invalidated);
}
MonoBoolean godot_icall_Internal_IsMessageQueueFlushing() {
return (MonoBoolean)MessageQueue::get_singleton()->is_flushing();
}
MonoBoolean godot_icall_Internal_GodotIs32Bits() {
return sizeof(void *) == 4;
}
MonoBoolean godot_icall_Internal_GodotIsRealTDouble() {
#ifdef REAL_T_IS_DOUBLE
return (MonoBoolean) true;
#else
return (MonoBoolean) false;
#endif
}
void godot_icall_Internal_GodotMainIteration() {
Main::iteration();
}
uint64_t godot_icall_Internal_GetCoreApiHash() {
return ClassDB::get_api_hash(ClassDB::API_CORE);
}
uint64_t godot_icall_Internal_GetEditorApiHash() {
return ClassDB::get_api_hash(ClassDB::API_EDITOR);
}
MonoBoolean godot_icall_Internal_IsAssembliesReloadingNeeded() {
#ifdef GD_MONO_HOT_RELOAD
return (MonoBoolean)CSharpLanguage::get_singleton()->is_assembly_reloading_needed();
#else
return (MonoBoolean) false;
#endif
}
void godot_icall_Internal_ReloadAssemblies(MonoBoolean p_soft_reload) {
#ifdef GD_MONO_HOT_RELOAD
_GodotSharp::get_singleton()->call_deferred("_reload_assemblies", (bool)p_soft_reload);
#endif
}
void godot_icall_Internal_ScriptEditorDebuggerReloadScripts() {
ScriptEditor::get_singleton()->get_debugger()->reload_scripts();
}
MonoBoolean godot_icall_Internal_ScriptEditorEdit(MonoObject *p_resource, int32_t p_line, int32_t p_col, MonoBoolean p_grab_focus) {
Ref<Resource> resource = GDMonoMarshal::mono_object_to_variant(p_resource);
return (MonoBoolean)ScriptEditor::get_singleton()->edit(resource, p_line, p_col, (bool)p_grab_focus);
}
void godot_icall_Internal_EditorNodeShowScriptScreen() {
EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT);
}
MonoObject *godot_icall_Internal_GetScriptsMetadataOrNothing(MonoReflectionType *p_dict_reftype) {
Dictionary maybe_metadata = CSharpLanguage::get_singleton()->get_scripts_metadata_or_nothing();
MonoType *dict_type = mono_reflection_type_get_type(p_dict_reftype);
uint32_t type_encoding = mono_type_get_type(dict_type);
MonoClass *type_class_raw = mono_class_from_mono_type(dict_type);
GDMonoClass *type_class = GDMono::get_singleton()->get_class(type_class_raw);
return GDMonoMarshal::variant_to_mono_object(maybe_metadata, ManagedType(type_encoding, type_class));
}
MonoString *godot_icall_Internal_MonoWindowsInstallRoot() {
#ifdef WINDOWS_ENABLED
String install_root_dir = GDMono::get_singleton()->get_mono_reg_info().install_root_dir;
return GDMonoMarshal::mono_string_from_godot(install_root_dir);
#else
return NULL;
#endif
}
MonoString *godot_icall_Utils_OS_GetPlatformName() {
String os_name = OS::get_singleton()->get_name();
return GDMonoMarshal::mono_string_from_godot(os_name);
}
void register_editor_internal_calls() {
// GodotSharpDirs
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResDataDir", (void *)godot_icall_GodotSharpDirs_ResDataDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResMetadataDir", (void *)godot_icall_GodotSharpDirs_ResMetadataDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesBaseDir", (void *)godot_icall_GodotSharpDirs_ResAssembliesBaseDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesDir", (void *)godot_icall_GodotSharpDirs_ResAssembliesDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResConfigDir", (void *)godot_icall_GodotSharpDirs_ResConfigDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempDir", (void *)godot_icall_GodotSharpDirs_ResTempDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesBaseDir", (void *)godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesDir", (void *)godot_icall_GodotSharpDirs_ResTempAssembliesDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoUserDir", (void *)godot_icall_GodotSharpDirs_MonoUserDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoLogsDir", (void *)godot_icall_GodotSharpDirs_MonoLogsDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoSolutionsDir", (void *)godot_icall_GodotSharpDirs_MonoSolutionsDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_BuildLogsDirs", (void *)godot_icall_GodotSharpDirs_BuildLogsDirs);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectSlnPath", (void *)godot_icall_GodotSharpDirs_ProjectSlnPath);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectCsProjPath", (void *)godot_icall_GodotSharpDirs_ProjectCsProjPath);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorToolsDir", (void *)godot_icall_GodotSharpDirs_DataEditorToolsDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorPrebuiltApiDir", (void *)godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoEtcDir", (void *)godot_icall_GodotSharpDirs_DataMonoEtcDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoLibDir", (void *)godot_icall_GodotSharpDirs_DataMonoLibDir);
mono_add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoBinDir", (void *)godot_icall_GodotSharpDirs_DataMonoBinDir);
// EditorProgress
mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Create", (void *)godot_icall_EditorProgress_Create);
mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Dispose", (void *)godot_icall_EditorProgress_Dispose);
mono_add_internal_call("GodotTools.Internals.EditorProgress::internal_Step", (void *)godot_icall_EditorProgress_Step);
// BiningsGenerator
mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Ctor", (void *)godot_icall_BindingsGenerator_Ctor);
mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Dtor", (void *)godot_icall_BindingsGenerator_Dtor);
mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_LogPrintEnabled", (void *)godot_icall_BindingsGenerator_LogPrintEnabled);
mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_SetLogPrintEnabled", (void *)godot_icall_BindingsGenerator_SetLogPrintEnabled);
mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_GenerateCsApi", (void *)godot_icall_BindingsGenerator_GenerateCsApi);
mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_Version", (void *)godot_icall_BindingsGenerator_Version);
mono_add_internal_call("GodotTools.Internals.BindingsGenerator::internal_CsGlueVersion", (void *)godot_icall_BindingsGenerator_CsGlueVersion);
// ScriptClassParser
mono_add_internal_call("GodotTools.Internals.ScriptClassParser::internal_ParseFile", (void *)godot_icall_ScriptClassParser_ParseFile);
// GodotSharpExport
mono_add_internal_call("GodotTools.GodotSharpExport::internal_GetExportedAssemblyDependencies", (void *)godot_icall_GodotSharpExport_GetExportedAssemblyDependencies);
// Internals
mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorScale", (void *)godot_icall_Internal_EditorScale);
mono_add_internal_call("GodotTools.Internals.Internal::internal_GlobalDef", (void *)godot_icall_Internal_GlobalDef);
mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorDef", (void *)godot_icall_Internal_EditorDef);
mono_add_internal_call("GodotTools.Internals.Internal::internal_FullTemplatesDir", (void *)godot_icall_Internal_FullTemplatesDir);
mono_add_internal_call("GodotTools.Internals.Internal::internal_SimplifyGodotPath", (void *)godot_icall_Internal_SimplifyGodotPath);
mono_add_internal_call("GodotTools.Internals.Internal::internal_IsOsxAppBundleInstalled", (void *)godot_icall_Internal_IsOsxAppBundleInstalled);
mono_add_internal_call("GodotTools.Internals.Internal::internal_MetadataIsApiAssemblyInvalidated", (void *)godot_icall_Internal_MetadataIsApiAssemblyInvalidated);
mono_add_internal_call("GodotTools.Internals.Internal::internal_MetadataSetApiAssemblyInvalidated", (void *)godot_icall_Internal_MetadataSetApiAssemblyInvalidated);
mono_add_internal_call("GodotTools.Internals.Internal::internal_IsMessageQueueFlushing", (void *)godot_icall_Internal_IsMessageQueueFlushing);
mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIs32Bits", (void *)godot_icall_Internal_GodotIs32Bits);
mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotIsRealTDouble", (void *)godot_icall_Internal_GodotIsRealTDouble);
mono_add_internal_call("GodotTools.Internals.Internal::internal_GodotMainIteration", (void *)godot_icall_Internal_GodotMainIteration);
mono_add_internal_call("GodotTools.Internals.Internal::internal_GetCoreApiHash", (void *)godot_icall_Internal_GetCoreApiHash);
mono_add_internal_call("GodotTools.Internals.Internal::internal_GetEditorApiHash", (void *)godot_icall_Internal_GetEditorApiHash);
mono_add_internal_call("GodotTools.Internals.Internal::internal_IsAssembliesReloadingNeeded", (void *)godot_icall_Internal_IsAssembliesReloadingNeeded);
mono_add_internal_call("GodotTools.Internals.Internal::internal_ReloadAssemblies", (void *)godot_icall_Internal_ReloadAssemblies);
mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebuggerReloadScripts", (void *)godot_icall_Internal_ScriptEditorDebuggerReloadScripts);
mono_add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", (void *)godot_icall_Internal_ScriptEditorEdit);
mono_add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", (void *)godot_icall_Internal_EditorNodeShowScriptScreen);
mono_add_internal_call("GodotTools.Internals.Internal::internal_GetScriptsMetadataOrNothing", (void *)godot_icall_Internal_GetScriptsMetadataOrNothing);
mono_add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", (void *)godot_icall_Internal_MonoWindowsInstallRoot);
// Utils.OS
mono_add_internal_call("GodotTools.Utils.OS::GetPlatformName", (void *)godot_icall_Utils_OS_GetPlatformName);
}

View file

@ -1,5 +1,5 @@
/*************************************************************************/
/* mono_build_info.h */
/* editor_internal_calls.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -28,28 +28,9 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef MONO_BUILD_INFO_H
#define MONO_BUILD_INFO_H
#ifndef EDITOR_INTERNAL_CALL_H
#define EDITOR_INTERNAL_CALL_H
#include "core/ustring.h"
#include "core/vector.h"
void register_editor_internal_calls();
struct MonoBuildInfo {
struct Hasher {
static uint32_t hash(const MonoBuildInfo &p_key);
};
String solution;
String configuration;
Vector<String> custom_props;
bool operator==(const MonoBuildInfo &p_b) const;
String get_log_dirpath();
MonoBuildInfo();
MonoBuildInfo(const String &p_solution, const String &p_config);
};
#endif // MONO_BUILD_INFO_H
#endif // EDITOR_INTERNAL_CALL_H

View file

@ -1,632 +0,0 @@
/*************************************************************************/
/* godotsharp_builds.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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 "core/os/os.h"
#include "core/vector.h"
#include "main/main.h"
#include "../glue/cs_glue_version.gen.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 "csharp_project.h"
#include "godotsharp_editor.h"
#define PROP_NAME_MSBUILD_MONO "MSBuild (Mono)"
#define PROP_NAME_MSBUILD_VS "MSBuild (VS Build Tools)"
#define PROP_NAME_XBUILD "xbuild (Deprecated)"
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);
}
static Vector<const char *> _get_msbuild_hint_dirs() {
Vector<const char *> ret;
#ifdef OSX_ENABLED
ret.push_back("/Library/Frameworks/Mono.framework/Versions/Current/bin/");
ret.push_back("/usr/local/var/homebrew/linked/mono/bin/");
#endif
ret.push_back("/opt/novell/mono/bin/");
return ret;
}
#ifdef UNIX_ENABLED
String _find_build_engine_on_unix(const String &p_name) {
String ret = path_which(p_name);
if (ret.length())
return ret;
String ret_fallback = path_which(p_name + ".exe");
if (ret_fallback.length())
return ret_fallback;
static Vector<const char *> locations = _get_msbuild_hint_dirs();
for (int i = 0; i < locations.size(); i++) {
String hint_path = locations[i] + p_name;
if (FileAccess::exists(hint_path)) {
return hint_path;
}
}
return String();
}
#endif
MonoString *godot_icall_BuildInstance_get_MSBuildPath() {
GodotSharpBuilds::BuildTool build_tool = GodotSharpBuilds::BuildTool(int(EditorSettings::get_singleton()->get("mono/builds/build_tool")));
#if defined(WINDOWS_ENABLED)
switch (build_tool) {
case GodotSharpBuilds::MSBUILD_VS: {
static String msbuild_tools_path;
if (msbuild_tools_path.empty() || !FileAccess::exists(msbuild_tools_path)) {
// Try to search it again if it wasn't found last time or if it was removed from its location
msbuild_tools_path = MonoRegUtils::find_msbuild_tools_path();
if (msbuild_tools_path.empty()) {
ERR_PRINTS("Cannot find executable for '" PROP_NAME_MSBUILD_VS "'. Tried with path: " + msbuild_tools_path);
return NULL;
}
}
if (!msbuild_tools_path.ends_with("\\"))
msbuild_tools_path += "\\";
return GDMonoMarshal::mono_string_from_godot(msbuild_tools_path + "MSBuild.exe");
} break;
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)) {
ERR_PRINTS("Cannot find executable for '" PROP_NAME_MSBUILD_MONO "'. Tried with path: " + msbuild_path);
return NULL;
}
return GDMonoMarshal::mono_string_from_godot(msbuild_path);
} break;
case GodotSharpBuilds::XBUILD: {
String xbuild_path = GDMono::get_singleton()->get_mono_reg_info().bin_dir.plus_file("xbuild.bat");
if (!FileAccess::exists(xbuild_path)) {
ERR_PRINTS("Cannot find executable for '" PROP_NAME_XBUILD "'. Tried with path: " + xbuild_path);
return NULL;
}
return GDMonoMarshal::mono_string_from_godot(xbuild_path);
} break;
default:
ERR_EXPLAIN("You don't deserve to live");
CRASH_NOW();
}
#elif defined(UNIX_ENABLED)
static String msbuild_path;
static String xbuild_path;
if (build_tool == GodotSharpBuilds::XBUILD) {
if (xbuild_path.empty() || !FileAccess::exists(xbuild_path)) {
// Try to search it again if it wasn't found last time or if it was removed from its location
xbuild_path = _find_build_engine_on_unix("msbuild");
}
if (xbuild_path.empty()) {
ERR_PRINT("Cannot find binary for '" PROP_NAME_XBUILD "'");
return NULL;
}
} else {
if (msbuild_path.empty() || !FileAccess::exists(msbuild_path)) {
// Try to search it again if it wasn't found last time or if it was removed from its location
msbuild_path = _find_build_engine_on_unix("msbuild");
}
if (msbuild_path.empty()) {
ERR_PRINT("Cannot find binary for '" PROP_NAME_MSBUILD_MONO "'");
return NULL;
}
}
return GDMonoMarshal::mono_string_from_godot(build_tool != GodotSharpBuilds::XBUILD ? msbuild_path : xbuild_path);
#else
(void)build_tool; // UNUSED
ERR_EXPLAIN("Not implemented on this platform");
ERR_FAIL_V(NULL);
#endif
}
MonoString *godot_icall_BuildInstance_get_MonoWindowsBinDir() {
#if defined(WINDOWS_ENABLED)
const MonoRegInfo &mono_reg_info = GDMono::get_singleton()->get_mono_reg_info();
if (mono_reg_info.bin_dir.length()) {
return GDMonoMarshal::mono_string_from_godot(mono_reg_info.bin_dir);
}
ERR_EXPLAIN("Cannot find Mono's binaries directory in the registry");
ERR_FAIL_V(NULL);
#else
return NULL;
#endif
}
MonoBoolean godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows() {
#if defined(WINDOWS_ENABLED)
return GodotSharpBuilds::BuildTool(int(EditorSettings::get_singleton()->get("mono/builds/build_tool"))) == GodotSharpBuilds::MSBUILD_MONO;
#else
return false;
#endif
}
MonoBoolean godot_icall_BuildInstance_get_PrintBuildOutput() {
return (bool)EDITOR_GET("mono/builds/print_build_output");
}
void GodotSharpBuilds::register_internal_calls() {
static bool registered = false;
ERR_FAIL_COND(registered);
registered = true;
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);
mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_MonoWindowsBinDir", (void *)godot_icall_BuildInstance_get_MonoWindowsBinDir);
mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows", (void *)godot_icall_BuildInstance_get_UsingMonoMSBuildOnWindows);
mono_add_internal_call("GodotSharpTools.Build.BuildInstance::godot_icall_BuildInstance_get_PrintBuildOutput", (void *)godot_icall_BuildInstance_get_PrintBuildOutput);
}
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_api_sln_dir, const String &p_config) {
String api_sln_file = p_api_sln_dir.plus_file(API_SOLUTION_NAME ".sln");
String core_api_assembly_dir = p_api_sln_dir.plus_file(CORE_API_ASSEMBLY_NAME).plus_file("bin").plus_file(p_config);
String core_api_assembly_file = core_api_assembly_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
String editor_api_assembly_dir = p_api_sln_dir.plus_file(EDITOR_API_ASSEMBLY_NAME).plus_file("bin").plus_file(p_config);
String editor_api_assembly_file = editor_api_assembly_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
if (!FileAccess::exists(core_api_assembly_file) || !FileAccess::exists(editor_api_assembly_file)) {
MonoBuildInfo api_build_info(api_sln_file, p_config);
// TODO Replace this global NoWarn with '#pragma warning' directives on generated files,
// once we start to actively document manually maintained C# classes
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 " API_SOLUTION_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, APIAssembly::Type p_api_type) {
// Create destination directory if needed
if (!DirAccess::exists(p_dst_dir)) {
DirAccess *da = DirAccess::create_for_path(p_dst_dir);
Error err = da->make_dir_recursive(p_dst_dir);
memdelete(da);
if (err != OK) {
show_build_error_dialog("Failed to create destination directory for the API assemblies. Error: " + itos(err));
return false;
}
}
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) ||
GDMono::get_singleton()->metadata_is_api_assembly_invalidated(p_api_type)) {
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)
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);
if (err != OK) {
show_build_error_dialog("Failed to copy " + assembly_file);
return false;
}
GDMono::get_singleton()->metadata_set_api_assembly_invalidated(p_api_type, false);
}
return true;
}
String GodotSharpBuilds::_api_folder_name(APIAssembly::Type p_api_type) {
uint64_t api_hash = p_api_type == APIAssembly::API_CORE ?
GDMono::get_singleton()->get_api_core_hash() :
GDMono::get_singleton()->get_api_editor_hash();
return String::num_uint64(api_hash) +
"_" + String::num_uint64(BindingsGenerator::get_version()) +
"_" + String::num_uint64(CS_GLUE_VERSION);
}
bool GodotSharpBuilds::make_api_assembly(APIAssembly::Type p_api_type) {
String api_name = p_api_type == APIAssembly::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME;
String editor_prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir();
String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir();
if (FileAccess::exists(editor_prebuilt_api_dir.plus_file(api_name + ".dll"))) {
EditorProgress pr("mono_copy_prebuilt_api_assembly", "Copying prebuilt " + api_name + " assembly...", 1);
pr.step("Copying " + api_name + " assembly", 0);
return GodotSharpBuilds::copy_api_assembly(editor_prebuilt_api_dir, res_assemblies_dir, api_name, p_api_type);
}
String api_build_config = "Release";
EditorProgress pr("mono_build_release_" API_SOLUTION_NAME, "Building " API_SOLUTION_NAME " solution...", 3);
pr.step("Generating " API_SOLUTION_NAME " solution", 0);
String api_sln_dir = GodotSharpDirs::get_mono_solutions_dir()
.plus_file(_api_folder_name(APIAssembly::API_CORE));
String api_sln_file = api_sln_dir.plus_file(API_SOLUTION_NAME ".sln");
if (!DirAccess::exists(api_sln_dir) || !FileAccess::exists(api_sln_file)) {
BindingsGenerator bindings_generator;
if (!OS::get_singleton()->is_stdout_verbose()) {
bindings_generator.set_log_print_enabled(false);
}
Error err = bindings_generator.generate_cs_api(api_sln_dir);
if (err != OK) {
show_build_error_dialog("Failed to generate " API_SOLUTION_NAME " solution. Error: " + itos(err));
return false;
}
}
pr.step("Building " API_SOLUTION_NAME " solution", 1);
if (!GodotSharpBuilds::build_api_sln(api_sln_dir, api_build_config))
return false;
pr.step("Copying " + api_name + " assembly", 2);
// Copy the built assembly to the assemblies directory
String api_assembly_dir = api_sln_dir.plus_file(api_name).plus_file("bin").plus_file(api_build_config);
if (!GodotSharpBuilds::copy_api_assembly(api_assembly_dir, res_assemblies_dir, api_name, p_api_type))
return false;
return true;
}
bool GodotSharpBuilds::build_project_blocking(const String &p_config, const Vector<String> &p_godot_defines) {
if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path()))
return true; // No solution to build
if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE))
return false;
if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR))
return false;
EditorProgress pr("mono_project_debug_build", "Building project solution...", 1);
pr.step("Building project solution", 0);
MonoBuildInfo build_info(GodotSharpDirs::get_project_sln_path(), p_config);
// Add Godot defines
#ifdef WINDOWS_ENABLED
String constants = "GodotDefineConstants=\"";
#else
String constants = "GodotDefineConstants=\\\"";
#endif
for (int i = 0; i < p_godot_defines.size(); i++) {
constants += "GODOT_" + p_godot_defines[i].to_upper().replace("-", "_").replace(" ", "_").replace(";", "_") + ";";
}
#ifdef REAL_T_IS_DOUBLE
constants += "GODOT_REAL_T_IS_DOUBLE;";
#endif
#ifdef WINDOWS_ENABLED
constants += "\"";
#else
constants += "\\\"";
#endif
build_info.custom_props.push_back(constants);
if (!GodotSharpBuilds::get_singleton()->build(build_info)) {
GodotSharpBuilds::show_build_error_dialog("Failed to build project solution");
return false;
}
return true;
}
bool GodotSharpBuilds::editor_build_callback() {
if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path()))
return true; // No solution to build
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);
if (FileAccess::exists(scripts_metadata_path_editor)) {
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);
}
Vector<String> godot_defines;
godot_defines.push_back(OS::get_singleton()->get_name());
godot_defines.push_back(sizeof(void *) == 4 ? "32" : "64");
return build_project_blocking("Tools", godot_defines);
}
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_NULL(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(&GodotSharpBuilds::editor_build_callback);
// Build tool settings
EditorSettings *ed_settings = EditorSettings::get_singleton();
#ifdef WINDOWS_ENABLED
EDITOR_DEF("mono/builds/build_tool", MSBUILD_VS);
#else
EDITOR_DEF("mono/builds/build_tool", MSBUILD_MONO);
#endif
ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/builds/build_tool", PROPERTY_HINT_ENUM,
PROP_NAME_MSBUILD_MONO
#ifdef WINDOWS_ENABLED
"," PROP_NAME_MSBUILD_VS
#endif
"," PROP_NAME_XBUILD));
EDITOR_DEF("mono/builds/print_build_output", false);
}
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 log_dirpath = build_info.get_log_dirpath();
if (build_tab) {
build_tab->on_build_start();
} else {
build_tab = memnew(MonoBuildTab(build_info, log_dirpath));
MonoBottomPanel::get_singleton()->add_build_tab(build_tab);
}
if (p_blocking) {
// Required in order to update the build tasks list
Main::iteration();
}
if (!exited) {
exited = true;
String message = "Tried to start build process, but it is already running";
build_tab->on_build_exec_failed(message);
ERR_EXPLAIN(message);
ERR_FAIL();
}
exited = false;
// Remove old issues file
String issues_file = get_msbuild_issues_filename();
DirAccessRef d = DirAccess::create_for_path(log_dirpath);
if (d->file_exists(issues_file)) {
Error err = d->remove(issues_file);
if (err != OK) {
exited = true;
String file_path = ProjectSettings::get_singleton()->localize_path(log_dirpath).plus_file(issues_file);
String message = "Cannot remove issues file: " + file_path;
build_tab->on_build_exec_failed(message);
ERR_EXPLAIN(message);
ERR_FAIL();
}
}
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_mono_ptr());
// Construct
Variant solution = build_info.solution;
Variant config = build_info.configuration;
const Variant *ctor_args[2] = { &solution, &config };
MonoException *exc = NULL;
GDMonoMethod *ctor = klass->get_method(".ctor", 2);
ctor->invoke(mono_object, ctor_args, &exc);
if (exc) {
exited = true;
GDMonoUtils::debug_unhandled_exception(exc);
String message = "The build constructor threw an exception.\n" + GDMonoUtils::get_exception_name_and_message(exc);
build_tab->on_build_exec_failed(message);
ERR_EXPLAIN(message);
ERR_FAIL();
}
// Call Build
String logger_assembly_path = GDMono::get_singleton()->get_editor_tools_assembly()->get_path();
Variant logger_assembly = ProjectSettings::get_singleton()->globalize_path(logger_assembly_path);
Variant logger_output_dir = log_dirpath;
Variant custom_props = build_info.custom_props;
const Variant *args[3] = { &logger_assembly, &logger_output_dir, &custom_props };
exc = NULL;
GDMonoMethod *build_method = klass->get_method(p_blocking ? "Build" : "BuildAsync", 3);
build_method->invoke(mono_object, args, &exc);
if (exc) {
exited = true;
GDMonoUtils::debug_unhandled_exception(exc);
String message = "The build method threw an exception.\n" + GDMonoUtils::get_exception_name_and_message(exc);
build_tab->on_build_exec_failed(message);
ERR_EXPLAIN(message);
ERR_FAIL();
}
// Build returned
if (p_blocking) {
exited = true;
exit_code = klass->get_field("exitCode")->get_int_value(mono_object);
if (exit_code != 0) {
String log_filepath = build_info.get_log_dirpath().plus_file(get_msbuild_log_filename());
print_verbose("MSBuild exited with code: " + itos(exit_code) + ". Log file: " + log_filepath);
}
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) {
}

View file

@ -1,103 +0,0 @@
/*************************************************************************/
/* godotsharp_builds.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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_gd/gd_mono.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 String _api_folder_name(APIAssembly::Type p_api_type);
static GodotSharpBuilds *singleton;
public:
enum BuildTool {
MSBUILD_MONO,
#ifdef WINDOWS_ENABLED
MSBUILD_VS,
#endif
XBUILD // Deprecated
};
_FORCE_INLINE_ static GodotSharpBuilds *get_singleton() { return singleton; }
static void register_internal_calls();
static void show_build_error_dialog(const String &p_message);
static const char *get_msbuild_issues_filename() { return "msbuild_issues.csv"; }
static const char *get_msbuild_log_filename() { return "msbuild_log.txt"; }
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_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, APIAssembly::Type p_api_type);
static bool make_api_assembly(APIAssembly::Type p_api_type);
static bool build_project_blocking(const String &p_config, const Vector<String> &p_godot_defines);
static bool editor_build_callback();
GodotSharpBuilds();
~GodotSharpBuilds();
};
#endif // GODOTSHARP_BUILDS_H

View file

@ -1,582 +0,0 @@
/*************************************************************************/
/* godotsharp_editor.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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/message_queue.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 "../mono_gd/gd_mono_marshal.h"
#include "../utils/path_utils.h"
#include "bindings_generator.h"
#include "csharp_project.h"
#include "dotnet_solution.h"
#include "godotsharp_export.h"
#ifdef OSX_ENABLED
#include "../utils/osx_utils.h"
#endif
#ifdef WINDOWS_ENABLED
#include "../utils/mono_reg_utils.h"
#endif
GodotSharpEditor *GodotSharpEditor::singleton = NULL;
bool GodotSharpEditor::_create_project_solution() {
EditorProgress pr("create_csharp_solution", TTR("Generating solution..."), 2);
pr.step(TTR("Generating C# project..."));
String path = OS::get_singleton()->get_resource_dir();
String appname = ProjectSettings::get_singleton()->get("application/config/name");
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
if (appname_safe.empty()) {
appname_safe = "UnnamedProject";
}
String guid = CSharpProject::generate_game_project(path, appname_safe);
if (guid.length()) {
DotNetSolution solution(appname_safe);
if (!solution.set_path(path)) {
show_error_dialog(TTR("Failed to create solution."));
return false;
}
DotNetSolution::ProjectInfo proj_info;
proj_info.guid = guid;
proj_info.relpath = appname_safe + ".csproj";
proj_info.configs.push_back("Debug");
proj_info.configs.push_back("Release");
proj_info.configs.push_back("Tools");
solution.add_new_project(appname_safe, proj_info);
Error sln_error = solution.save();
if (sln_error != OK) {
show_error_dialog(TTR("Failed to save solution."));
return false;
}
if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE))
return false;
if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR))
return false;
pr.step(TTR("Done"));
// Here, after all calls to progress_task_step
call_deferred("_remove_create_sln_menu_option");
} else {
show_error_dialog(TTR("Failed to create C# project."));
}
return true;
}
void GodotSharpEditor::_make_api_solutions_if_needed() {
// I'm sick entirely of ProgressDialog
static int attempts_left = 100;
if (MessageQueue::get_singleton()->is_flushing() || !SceneTree::get_singleton()) {
ERR_FAIL_COND(attempts_left == 0); // You've got to be kidding
if (SceneTree::get_singleton()) {
SceneTree::get_singleton()->connect("idle_frame", this, "_make_api_solutions_if_needed", Vector<Variant>());
} else {
call_deferred("_make_api_solutions_if_needed");
}
attempts_left--;
return;
}
// Recursion guard needed because signals don't play well with ProgressDialog either, but unlike
// the message queue, with signals the collateral damage should be minimal in the worst case.
static bool recursion_guard = false;
if (!recursion_guard) {
recursion_guard = true;
// Oneshot signals don't play well with ProgressDialog either, so we do it this way instead
SceneTree::get_singleton()->disconnect("idle_frame", this, "_make_api_solutions_if_needed");
_make_api_solutions_if_needed_impl();
recursion_guard = false;
}
}
void GodotSharpEditor::_make_api_solutions_if_needed_impl() {
// If the project has a solution and C# project make sure the API assemblies are present and up to date
String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir();
if (!FileAccess::exists(res_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll")) ||
GDMono::get_singleton()->metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) {
if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_CORE))
return;
}
if (!FileAccess::exists(res_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll")) ||
GDMono::get_singleton()->metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) {
if (!GodotSharpBuilds::make_api_assembly(APIAssembly::API_EDITOR))
return; // Redundant? I don't think so
}
}
void GodotSharpEditor::_remove_create_sln_menu_option() {
menu_popup->remove_item(menu_popup->get_item_index(MENU_CREATE_SLN));
bottom_panel_btn->show();
}
void GodotSharpEditor::_show_about_dialog() {
bool show_on_start = EDITOR_GET("mono/editor/show_info_on_start");
about_dialog_checkbox->set_pressed(show_on_start);
about_dialog->popup_centered_minsize();
}
void GodotSharpEditor::_toggle_about_dialog_on_start(bool p_enabled) {
bool show_on_start = EDITOR_GET("mono/editor/show_info_on_start");
if (show_on_start != p_enabled) {
EditorSettings::get_singleton()->set_setting("mono/editor/show_info_on_start", p_enabled);
}
}
void GodotSharpEditor::_build_solution_pressed() {
if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path())) {
if (!_create_project_solution())
return; // Failed to create solution
}
MonoBottomPanel::get_singleton()->call("_build_project_pressed");
}
void GodotSharpEditor::_menu_option_pressed(int p_id) {
switch (p_id) {
case MENU_CREATE_SLN: {
_create_project_solution();
} break;
case MENU_ABOUT_CSHARP: {
_show_about_dialog();
} break;
default:
ERR_FAIL();
}
}
void GodotSharpEditor::_notification(int p_notification) {
switch (p_notification) {
case NOTIFICATION_READY: {
bool show_info_dialog = EDITOR_GET("mono/editor/show_info_on_start");
if (show_info_dialog) {
about_dialog->set_exclusive(true);
_show_about_dialog();
// Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive then.
about_dialog->set_exclusive(false);
}
}
}
}
void GodotSharpEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_build_solution_pressed"), &GodotSharpEditor::_build_solution_pressed);
ClassDB::bind_method(D_METHOD("_create_project_solution"), &GodotSharpEditor::_create_project_solution);
ClassDB::bind_method(D_METHOD("_make_api_solutions_if_needed"), &GodotSharpEditor::_make_api_solutions_if_needed);
ClassDB::bind_method(D_METHOD("_remove_create_sln_menu_option"), &GodotSharpEditor::_remove_create_sln_menu_option);
ClassDB::bind_method(D_METHOD("_toggle_about_dialog_on_start"), &GodotSharpEditor::_toggle_about_dialog_on_start);
ClassDB::bind_method(D_METHOD("_menu_option_pressed", "id"), &GodotSharpEditor::_menu_option_pressed);
}
MonoBoolean godot_icall_MonoDevelopInstance_IsApplicationBundleInstalled(MonoString *p_bundle_id) {
#ifdef OSX_ENABLED
return (MonoBoolean)osx_is_app_bundle_installed(GDMonoMarshal::mono_string_to_godot(p_bundle_id));
#else
(void)p_bundle_id; // UNUSED
ERR_FAIL_V(false);
#endif
}
MonoString *godot_icall_Utils_OS_GetPlatformName() {
return GDMonoMarshal::mono_string_from_godot(OS::get_singleton()->get_name());
}
void GodotSharpEditor::register_internal_calls() {
static bool registered = false;
ERR_FAIL_COND(registered);
registered = true;
mono_add_internal_call("GodotSharpTools.Editor.MonoDevelopInstance::IsApplicationBundleInstalled", (void *)godot_icall_MonoDevelopInstance_IsApplicationBundleInstalled);
mono_add_internal_call("GodotSharpTools.Utils.OS::GetPlatformName", (void *)godot_icall_Utils_OS_GetPlatformName);
GodotSharpBuilds::register_internal_calls();
GodotSharpExport::register_internal_calls();
}
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_VSCODE: {
static String vscode_path;
if (vscode_path.empty() || !FileAccess::exists(vscode_path)) {
// Try to search it again if it wasn't found last time or if it was removed from its location
bool found = false;
// TODO: Use initializer lists once C++11 is allowed
static Vector<String> vscode_names;
if (vscode_names.empty()) {
vscode_names.push_back("code");
vscode_names.push_back("code-oss");
vscode_names.push_back("vscode");
vscode_names.push_back("vscode-oss");
vscode_names.push_back("visual-studio-code");
vscode_names.push_back("visual-studio-code-oss");
}
for (int i = 0; i < vscode_names.size(); i++) {
vscode_path = path_which(vscode_names[i]);
if (!vscode_path.empty()) {
found = true;
break;
}
}
if (!found)
vscode_path.clear(); // Not found, clear so next time the empty() check is enough
}
List<String> args;
#ifdef OSX_ENABLED
// The package path is '/Applications/Visual Studio Code.app'
static const String vscode_bundle_id = "com.microsoft.VSCode";
static bool osx_app_bundle_installed = osx_is_app_bundle_installed(vscode_bundle_id);
if (osx_app_bundle_installed) {
args.push_back("-b");
args.push_back(vscode_bundle_id);
// The reusing of existing windows made by the 'open' command might not choose a wubdiw that is
// editing our folder. It's better to ask for a new window and let VSCode do the window management.
args.push_back("-n");
// The open process must wait until the application finishes (which is instant in VSCode's case)
args.push_back("--wait-apps");
args.push_back("--args");
}
#endif
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 + 1) + ":" + itos(p_col));
} else {
args.push_back(script_path);
}
#ifdef OSX_ENABLED
ERR_EXPLAIN("Cannot find code editor: VSCode");
ERR_FAIL_COND_V(!osx_app_bundle_installed && vscode_path.empty(), ERR_FILE_NOT_FOUND);
String command = osx_app_bundle_installed ? "/usr/bin/open" : vscode_path;
#else
ERR_EXPLAIN("Cannot find code editor: VSCode");
ERR_FAIL_COND_V(vscode_path.empty(), ERR_FILE_NOT_FOUND);
String command = vscode_path;
#endif
Error err = OS::get_singleton()->execute(command, args, false);
if (err != OK) {
ERR_PRINT("Error when trying to execute code editor: VSCode");
return err;
}
} break;
#ifdef OSX_ENABLED
case EDITOR_VISUALSTUDIO_MAC:
// [[fallthrough]];
#endif
case EDITOR_MONODEVELOP: {
#ifdef OSX_ENABLED
bool is_visualstudio = editor == EDITOR_VISUALSTUDIO_MAC;
MonoDevelopInstance **instance = is_visualstudio ?
&visualstudio_mac_instance :
&monodevelop_instance;
MonoDevelopInstance::EditorId editor_id = is_visualstudio ?
MonoDevelopInstance::VISUALSTUDIO_FOR_MAC :
MonoDevelopInstance::MONODEVELOP;
#else
MonoDevelopInstance **instance = &monodevelop_instance;
MonoDevelopInstance::EditorId editor_id = MonoDevelopInstance::MONODEVELOP;
#endif
if (!*instance)
*instance = memnew(MonoDevelopInstance(GodotSharpDirs::get_project_sln_path(), editor_id));
String script_path = ProjectSettings::get_singleton()->globalize_path(p_script->get_path());
if (p_line >= 0) {
script_path += ";" + itos(p_line + 1) + ";" + itos(p_col);
}
(*instance)->execute(script_path);
} break;
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;
monodevelop_instance = NULL;
#ifdef OSX_ENABLED
visualstudio_mac_instance = NULL;
#endif
editor = p_editor;
error_dialog = memnew(AcceptDialog);
editor->get_gui_base()->add_child(error_dialog);
bottom_panel_btn = editor->add_bottom_panel_item(TTR("Mono"), memnew(MonoBottomPanel(editor)));
godotsharp_builds = memnew(GodotSharpBuilds);
editor->add_child(memnew(MonoReloadNode));
menu_popup = memnew(PopupMenu);
menu_popup->hide();
menu_popup->set_as_toplevel(true);
menu_popup->set_pass_on_modal_close_click(false);
editor->add_tool_submenu_item("Mono", menu_popup);
// TODO: Remove or edit this info dialog once Mono support is no longer in alpha
{
menu_popup->add_item(TTR("About C# support"), MENU_ABOUT_CSHARP);
about_dialog = memnew(AcceptDialog);
editor->get_gui_base()->add_child(about_dialog);
about_dialog->set_title("Important: C# support is not feature-complete");
// We don't use set_text() as the default AcceptDialog Label doesn't play well with the TextureRect and CheckBox
// we'll add. Instead we add containers and a new autowrapped Label inside.
// Main VBoxContainer (icon + label on top, checkbox at bottom)
VBoxContainer *about_vbc = memnew(VBoxContainer);
about_dialog->add_child(about_vbc);
// HBoxContainer for icon + label
HBoxContainer *about_hbc = memnew(HBoxContainer);
about_vbc->add_child(about_hbc);
TextureRect *about_icon = memnew(TextureRect);
about_hbc->add_child(about_icon);
Ref<Texture> about_icon_tex = about_icon->get_icon("NodeWarning", "EditorIcons");
about_icon->set_texture(about_icon_tex);
Label *about_label = memnew(Label);
about_hbc->add_child(about_label);
about_label->set_custom_minimum_size(Size2(600, 150) * EDSCALE);
about_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
about_label->set_autowrap(true);
String about_text =
String("C# support in Godot Engine is in late alpha stage and, while already usable, ") +
"it is not meant for use in production.\n\n" +
"Projects can be exported to Linux, macOS and Windows, but not yet to mobile or web platforms. " +
"Bugs and usability issues will be addressed gradually over future releases, " +
"potentially including compatibility breaking changes as new features are implemented for a better overall C# experience.\n\n" +
"If you experience issues with this Mono build, please report them on Godot's issue tracker with details about your system, MSBuild version, IDE, etc.:\n\n" +
" https://github.com/godotengine/godot/issues\n\n" +
"Your critical feedback at this stage will play a great role in shaping the C# support in future releases, so thank you!";
about_label->set_text(about_text);
EDITOR_DEF("mono/editor/show_info_on_start", true);
// CheckBox in main container
about_dialog_checkbox = memnew(CheckBox);
about_vbc->add_child(about_dialog_checkbox);
about_dialog_checkbox->set_text("Show this warning when starting the editor");
about_dialog_checkbox->connect("toggled", this, "_toggle_about_dialog_on_start");
}
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)) {
// Defer this task because EditorProgress calls Main::iterarion() and the main loop is not yet initialized.
call_deferred("_make_api_solutions_if_needed");
} else {
bottom_panel_btn->hide();
menu_popup->add_item(TTR("Create C# solution"), MENU_CREATE_SLN);
}
menu_popup->connect("id_pressed", this, "_menu_option_pressed");
ToolButton *build_button = memnew(ToolButton);
build_button->set_text("Build");
build_button->set_tooltip("Build solution");
build_button->set_focus_mode(Control::FOCUS_NONE);
build_button->connect("pressed", this, "_build_solution_pressed");
editor->get_menu_hb()->add_child(build_button);
// External editor settings
EditorSettings *ed_settings = EditorSettings::get_singleton();
EDITOR_DEF("mono/editor/external_editor", EDITOR_NONE);
String settings_hint_str = "Disabled";
#if defined(WINDOWS_ENABLED)
settings_hint_str += ",MonoDevelop,Visual Studio Code";
#elif defined(OSX_ENABLED)
settings_hint_str += ",Visual Studio,MonoDevelop,Visual Studio Code";
#elif defined(UNIX_ENABLED)
settings_hint_str += ",MonoDevelop,Visual Studio Code";
#endif
ed_settings->add_property_hint(PropertyInfo(Variant::INT, "mono/editor/external_editor", PROPERTY_HINT_ENUM, settings_hint_str));
// Export plugin
Ref<GodotSharpExport> godotsharp_export;
godotsharp_export.instance();
EditorExport::get_singleton()->add_export_plugin(godotsharp_export);
}
GodotSharpEditor::~GodotSharpEditor() {
singleton = NULL;
memdelete(godotsharp_builds);
if (monodevelop_instance) {
memdelete(monodevelop_instance);
monodevelop_instance = NULL;
}
}
MonoReloadNode *MonoReloadNode::singleton = NULL;
void MonoReloadNode::_reload_timer_timeout() {
if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) {
CSharpLanguage::get_singleton()->reload_assemblies(false);
}
}
void MonoReloadNode::restart_reload_timer() {
reload_timer->stop();
reload_timer->start();
}
void MonoReloadNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("_reload_timer_timeout"), &MonoReloadNode::_reload_timer_timeout);
}
void MonoReloadNode::_notification(int p_what) {
switch (p_what) {
case MainLoop::NOTIFICATION_WM_FOCUS_IN: {
restart_reload_timer();
if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) {
CSharpLanguage::get_singleton()->reload_assemblies(false);
}
} break;
default: {
} break;
};
}
MonoReloadNode::MonoReloadNode() {
singleton = this;
reload_timer = memnew(Timer);
add_child(reload_timer);
reload_timer->set_one_shot(false);
reload_timer->set_wait_time(EDITOR_DEF("mono/assembly_watch_interval_sec", 0.5));
reload_timer->connect("timeout", this, "_reload_timer_timeout");
reload_timer->start();
}
MonoReloadNode::~MonoReloadNode() {
singleton = NULL;
}

View file

@ -1,134 +0,0 @@
/*************************************************************************/
/* godotsharp_editor.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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, Node);
EditorNode *editor;
MenuButton *menu_button;
PopupMenu *menu_popup;
AcceptDialog *error_dialog;
AcceptDialog *about_dialog;
CheckBox *about_dialog_checkbox;
ToolButton *bottom_panel_btn;
GodotSharpBuilds *godotsharp_builds;
MonoDevelopInstance *monodevelop_instance;
#ifdef OSX_ENABLED
MonoDevelopInstance *visualstudio_mac_instance;
#endif
bool _create_project_solution();
void _make_api_solutions_if_needed();
void _make_api_solutions_if_needed_impl();
void _remove_create_sln_menu_option();
void _show_about_dialog();
void _toggle_about_dialog_on_start(bool p_enabled);
void _menu_option_pressed(int p_id);
void _build_solution_pressed();
static GodotSharpEditor *singleton;
protected:
void _notification(int p_notification);
static void _bind_methods();
public:
enum MenuOptions {
MENU_CREATE_SLN,
MENU_ABOUT_CSHARP,
};
enum ExternalEditor {
EDITOR_NONE,
#if defined(WINDOWS_ENABLED)
//EDITOR_VISUALSTUDIO, // TODO
EDITOR_MONODEVELOP,
EDITOR_VSCODE
#elif defined(OSX_ENABLED)
EDITOR_VISUALSTUDIO_MAC,
EDITOR_MONODEVELOP,
EDITOR_VSCODE
#elif defined(UNIX_ENABLED)
EDITOR_MONODEVELOP,
EDITOR_VSCODE
#endif
};
_FORCE_INLINE_ static GodotSharpEditor *get_singleton() { return singleton; }
static void register_internal_calls();
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();
};
class MonoReloadNode : public Node {
GDCLASS(MonoReloadNode, Node);
Timer *reload_timer;
void _reload_timer_timeout();
static MonoReloadNode *singleton;
protected:
static void _bind_methods();
void _notification(int p_what);
public:
_FORCE_INLINE_ static MonoReloadNode *get_singleton() { return singleton; }
void restart_reload_timer();
MonoReloadNode();
~MonoReloadNode();
};
#endif // GODOTSHARP_EDITOR_H

View file

@ -30,180 +30,28 @@
#include "godotsharp_export.h"
#include "core/version.h"
#include <mono/metadata/image.h>
#include "../csharp_script.h"
#include "../godotsharp_defs.h"
#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"
#include "../mono_gd/gd_mono.h"
#include "../mono_gd/gd_mono_assembly.h"
static MonoString *godot_icall_GodotSharpExport_GetTemplatesDir() {
String current_version = VERSION_FULL_CONFIG;
String templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(current_version);
return GDMonoMarshal::mono_string_from_godot(ProjectSettings::get_singleton()->globalize_path(templates_dir));
String get_assemblyref_name(MonoImage *p_image, int index) {
const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF);
uint32_t cols[MONO_ASSEMBLYREF_SIZE];
mono_metadata_decode_row(table_info, index, cols, MONO_ASSEMBLYREF_SIZE);
return String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME]));
}
static MonoString *godot_icall_GodotSharpExport_GetDataDirName() {
String appname = ProjectSettings::get_singleton()->get("application/config/name");
String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
return GDMonoMarshal::mono_string_from_godot("data_" + appname_safe);
}
void GodotSharpExport::register_internal_calls() {
static bool registered = false;
ERR_FAIL_COND(registered);
registered = true;
mono_add_internal_call("GodotSharpTools.Editor.GodotSharpExport::GetTemplatesDir", (void *)godot_icall_GodotSharpExport_GetTemplatesDir);
mono_add_internal_call("GodotSharpTools.Editor.GodotSharpExport::GetDataDirName", (void *)godot_icall_GodotSharpExport_GetDataDirName);
}
void GodotSharpExport::_export_file(const String &p_path, const String &p_type, const Set<String> &) {
if (p_type != CSharpLanguage::get_singleton()->get_type())
return;
ERR_FAIL_COND(p_path.get_extension() != CSharpLanguage::get_singleton()->get_extension());
// TODO what if the source file is not part of the game's C# project
if (!GLOBAL_GET("mono/export/include_scripts_content")) {
// We don't want to include the source code on exported games
add_file(p_path, Vector<uint8_t>(), false);
skip();
}
}
void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug, const String &p_path, int p_flags) {
// TODO right now there is no way to stop the export process with an error
ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized());
ERR_FAIL_NULL(TOOLS_DOMAIN);
ERR_FAIL_NULL(GDMono::get_singleton()->get_editor_tools_assembly());
if (FileAccess::exists(GodotSharpDirs::get_project_sln_path())) {
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));
// Turn export features into defines
Vector<String> godot_defines;
for (Set<String>::Element *E = p_features.front(); E; E = E->next()) {
godot_defines.push_back(E->get());
}
ERR_FAIL_COND(!GodotSharpBuilds::build_project_blocking(build_config, godot_defines));
// Add dependency assemblies
Map<String, String> dependencies;
String project_dll_name = ProjectSettings::get_singleton()->get("application/config/name");
String project_dll_name_safe = OS::get_singleton()->get_safe_dir_name(project_dll_name);
if (project_dll_name_safe.empty()) {
project_dll_name_safe = "UnnamedProject";
}
String project_dll_src_dir = GodotSharpDirs::get_res_temp_assemblies_base_dir().plus_file(build_config);
String project_dll_src_path = project_dll_src_dir.plus_file(project_dll_name_safe + ".dll");
dependencies.insert(project_dll_name_safe, project_dll_src_path);
{
MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain");
ERR_FAIL_NULL(export_domain);
_GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain);
_GDMONO_SCOPE_DOMAIN_(export_domain);
GDMonoAssembly *scripts_assembly = NULL;
bool load_success = GDMono::get_singleton()->load_assembly_from(project_dll_name_safe,
project_dll_src_path, &scripts_assembly, /* refonly: */ true);
ERR_EXPLAIN("Cannot load assembly (refonly): " + project_dll_name_safe);
ERR_FAIL_COND(!load_success);
Vector<String> search_dirs;
String templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG);
String android_bcl_dir = templates_dir.plus_file("android-bcl");
String custom_lib_dir;
if (p_features.find("Android") && DirAccess::exists(android_bcl_dir)) {
custom_lib_dir = android_bcl_dir;
}
GDMonoAssembly::fill_search_dirs(search_dirs, build_config, custom_lib_dir);
Error depend_error = _get_assembly_dependencies(scripts_assembly, search_dirs, dependencies);
ERR_FAIL_COND(depend_error != OK);
}
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_file(depend_src_path, depend_dst_path));
}
}
// Mono specific export template extras (data dir)
GDMonoClass *export_class = GDMono::get_singleton()->get_editor_tools_assembly()->get_class("GodotSharpTools.Editor", "GodotSharpExport");
ERR_FAIL_NULL(export_class);
GDMonoMethod *export_begin_method = export_class->get_method("_ExportBegin", 4);
ERR_FAIL_NULL(export_begin_method);
MonoArray *features = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(String), p_features.size());
int i = 0;
for (const Set<String>::Element *E = p_features.front(); E; E = E->next()) {
MonoString *boxed = GDMonoMarshal::mono_string_from_godot(E->get());
mono_array_setref(features, i, boxed);
i++;
}
MonoBoolean debug = p_debug;
MonoString *path = GDMonoMarshal::mono_string_from_godot(p_path);
uint32_t flags = p_flags;
void *args[4] = { features, &debug, path, &flags };
MonoException *exc = NULL;
export_begin_method->invoke_raw(NULL, args, &exc);
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
ERR_FAIL();
}
}
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);
Vector<uint8_t> data;
data.resize(f->get_len());
f->get_buffer(data.ptrw(), data.size());
add_file(p_dst_path, data, p_remap);
return true;
}
Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Map<String, String> &r_dependencies) {
Error GodotSharpExport::get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies) {
MonoImage *image = p_assembly->get_image();
for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) {
MonoAssemblyName *ref_aname = aname_prealloc;
mono_assembly_get_assemblyref(image, i, ref_aname);
String ref_name = mono_assembly_name_get_name(ref_aname);
String ref_name = get_assemblyref_name(image, i);
if (r_dependencies.find(ref_name))
if (r_dependencies.has(ref_name))
continue;
GDMonoAssembly *ref_assembly = NULL;
@ -242,9 +90,9 @@ Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, c
ERR_FAIL_V(ERR_CANT_RESOLVE);
}
r_dependencies.insert(ref_name, ref_assembly->get_path());
r_dependencies[ref_name] = ref_assembly->get_path();
Error err = _get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies);
Error err = get_assembly_dependencies(ref_assembly, p_search_dirs, r_dependencies);
if (err != OK) {
ERR_EXPLAIN("Cannot load one of the dependencies for the assembly: " + ref_name);
ERR_FAIL_V(err);
@ -254,14 +102,22 @@ Error GodotSharpExport::_get_assembly_dependencies(GDMonoAssembly *p_assembly, c
return OK;
}
GodotSharpExport::GodotSharpExport() {
// MonoAssemblyName is an incomplete type (internal to mono), so we can't allocate it ourselves.
// There isn't any api to allocate an empty one either, so we need to do it this way.
aname_prealloc = mono_assembly_name_new("whatever");
mono_assembly_name_free(aname_prealloc); // "it does not frees the object itself, only the name members" (typo included)
}
Error GodotSharpExport::get_exported_assembly_dependencies(const String &p_project_dll_name, const String &p_project_dll_src_path, const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_dependencies) {
MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain");
ERR_FAIL_NULL_V(export_domain, FAILED);
_GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain);
GodotSharpExport::~GodotSharpExport() {
if (aname_prealloc)
mono_free(aname_prealloc);
_GDMONO_SCOPE_DOMAIN_(export_domain);
GDMonoAssembly *scripts_assembly = NULL;
bool load_success = GDMono::get_singleton()->load_assembly_from(p_project_dll_name,
p_project_dll_src_path, &scripts_assembly, /* refonly: */ true);
ERR_EXPLAIN("Cannot load assembly (refonly): " + p_project_dll_name);
ERR_FAIL_COND_V(!load_success, ERR_CANT_RESOLVE);
Vector<String> search_dirs;
GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_lib_dir);
return get_assembly_dependencies(scripts_assembly, search_dirs, r_dependencies);
}

View file

@ -31,29 +31,19 @@
#ifndef GODOTSHARP_EXPORT_H
#define GODOTSHARP_EXPORT_H
#include <mono/metadata/image.h>
#include "editor/editor_export.h"
#include "core/dictionary.h"
#include "core/error_list.h"
#include "core/ustring.h"
#include "../mono_gd/gd_mono_header.h"
class GodotSharpExport : public EditorExportPlugin {
namespace GodotSharpExport {
MonoAssemblyName *aname_prealloc;
Error get_exported_assembly_dependencies(const String &p_project_dll_name,
const String &p_project_dll_src_path, const String &p_build_config,
const String &p_custom_lib_dir, Dictionary &r_dependencies);
Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies);
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);
protected:
virtual void _export_file(const String &p_path, const String &p_type, const Set<String> &p_features);
virtual void _export_begin(const Set<String> &p_features, bool p_debug, const String &p_path, int p_flags);
public:
static void register_internal_calls();
GodotSharpExport();
~GodotSharpExport();
};
} // namespace GodotSharpExport
#endif // GODOTSHARP_EXPORT_H

View file

@ -1,528 +0,0 @@
/*************************************************************************/
/* mono_bottom_panel.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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 "editor/plugins/script_editor_plugin.h"
#include "editor/script_editor_debugger.h"
#include "../csharp_script.h"
#include "../godotsharp_dirs.h"
#include "csharp_project.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 = "Solution: " + tab->build_info.solution;
item_tooltip += "\nConfiguration: " + tab->build_info.configuration;
item_tooltip += "\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_ERROR) {
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_tabs_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_tabs_item_selected(int p_idx) {
ERR_FAIL_INDEX(p_idx, build_tabs->get_tab_count());
build_tabs->set_current_tab(p_idx);
if (!build_tabs->is_visible())
build_tabs->set_visible(true);
warnings_btn->set_visible(true);
errors_btn->set_visible(true);
view_log_btn->set_visible(true);
}
void MonoBottomPanel::_build_tabs_nothing_selected() {
if (build_tabs->get_tab_count() != 0) { // just in case
build_tabs->set_visible(false);
// This callback is called when clicking on the empty space of the list.
// ItemList won't deselect the items automatically, so we must do it ourselves.
build_tabs_list->unselect_all();
}
warnings_btn->set_visible(false);
errors_btn->set_visible(false);
view_log_btn->set_visible(false);
}
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::_build_project_pressed() {
if (!FileAccess::exists(GodotSharpDirs::get_project_sln_path()))
return; // No solution to build
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(metadata_err != OK);
if (FileAccess::exists(scripts_metadata_path_editor)) {
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(copy_err != OK);
}
Vector<String> godot_defines;
godot_defines.push_back(OS::get_singleton()->get_name());
godot_defines.push_back((sizeof(void *) == 4 ? "32" : "64"));
bool build_success = GodotSharpBuilds::get_singleton()->build_project_blocking("Tools", godot_defines);
if (build_success) {
// Notify running game for hot-reload
ScriptEditor::get_singleton()->get_debugger()->reload_scripts();
// Hot-reload in the editor
MonoReloadNode::get_singleton()->restart_reload_timer();
if (CSharpLanguage::get_singleton()->is_assembly_reloading_needed()) {
CSharpLanguage::get_singleton()->reload_assemblies(false);
}
}
}
void MonoBottomPanel::_view_log_pressed() {
if (build_tabs_list->is_anything_selected()) {
Vector<int> selected_items = build_tabs_list->get_selected_items();
CRASH_COND(selected_items.size() != 1);
int selected_item = selected_items[0];
MonoBuildTab *build_tab = Object::cast_to<MonoBuildTab>(build_tabs->get_tab_control(selected_item));
ERR_FAIL_NULL(build_tab);
String log_dirpath = build_tab->get_build_info().get_log_dirpath();
OS::get_singleton()->shell_open(log_dirpath.plus_file(GodotSharpBuilds::get_msbuild_log_filename()));
}
}
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("_build_project_pressed"), &MonoBottomPanel::_build_project_pressed);
ClassDB::bind_method(D_METHOD("_view_log_pressed"), &MonoBottomPanel::_view_log_pressed);
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_tabs_item_selected", "idx"), &MonoBottomPanel::_build_tabs_item_selected);
ClassDB::bind_method(D_METHOD("_build_tabs_nothing_selected"), &MonoBottomPanel::_build_tabs_nothing_selected);
}
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);
Button *build_project_btn = memnew(Button);
build_project_btn->set_text(TTR("Build Project"));
build_project_btn->set_focus_mode(FOCUS_NONE);
build_project_btn->connect("pressed", this, "_build_project_pressed");
toolbar_hbc->add_child(build_project_btn);
toolbar_hbc->add_spacer();
warnings_btn = memnew(ToolButton);
warnings_btn->set_text(TTR("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(TTR("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);
toolbar_hbc->add_spacer();
view_log_btn = memnew(Button);
view_log_btn->set_text(TTR("View log"));
view_log_btn->set_focus_mode(FOCUS_NONE);
view_log_btn->set_visible(false);
view_log_btn->connect("pressed", this, "_view_log_pressed");
toolbar_hbc->add_child(view_log_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_tabs_item_selected");
build_tabs_list->connect("nothing_selected", this, "_build_tabs_nothing_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);
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;
if (issue.code.length()) {
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 {
if (build_exited) {
if (build_result == RESULT_ERROR) {
return get_icon("StatusError", "EditorIcons");
} else {
return get_icon("StatusSuccess", "EditorIcons");
}
} else {
return get_icon("Stop", "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(GodotSharpBuilds::get_msbuild_issues_filename()));
_update_issues_list();
MonoBottomPanel::get_singleton()->raise_build_tab(this);
}
void MonoBuildTab::on_build_exec_failed(const String &p_cause) {
build_exited = true;
build_result = RESULT_ERROR;
issues_list->clear();
BuildIssue issue;
issue.message = p_cause;
issue.warning = false;
error_count += 1;
issues.push_back(issue);
_update_issues_list();
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_list->get_item_count());
// Get correct issue idx from issue list
int issue_idx = this->issues_list->get_item_metadata(p_idx);
ERR_FAIL_INDEX(issue_idx, issues.size());
const BuildIssue &issue = issues[issue_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_exited(false),
issues_list(memnew(ItemList)),
error_count(0),
warning_count(0),
errors_visible(true),
warnings_visible(true),
logs_dir(p_logs_dir),
build_info(p_build_info) {
issues_list->set_v_size_flags(SIZE_EXPAND_FILL);
issues_list->connect("item_activated", this, "_issue_activated");
add_child(issues_list);
}

View file

@ -1,150 +0,0 @@
/*************************************************************************/
/* mono_bottom_panel.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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;
ToolButton *warnings_btn;
ToolButton *errors_btn;
Button *view_log_btn;
void _update_build_tabs_list();
void _build_tabs_item_selected(int p_idx);
void _build_tabs_nothing_selected();
void _warnings_toggled(bool p_pressed);
void _errors_toggled(bool p_pressed);
void _build_project_pressed();
void _view_log_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);
void restart_build();
void stop_build();
MonoBuildTab(const MonoBuildInfo &p_build_info, const String &p_logs_dir);
};
#endif // MONO_BOTTOM_PANEL_H

View file

@ -1,62 +0,0 @@
/*************************************************************************/
/* mono_build_info.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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_build_info.h"
#include "../godotsharp_dirs.h"
#include "../mono_gd/gd_mono_utils.h"
uint32_t MonoBuildInfo::Hasher::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;
}
bool MonoBuildInfo::operator==(const MonoBuildInfo &p_b) const {
return p_b.solution == solution && p_b.configuration == configuration;
}
String MonoBuildInfo::get_log_dirpath() {
return GodotSharpDirs::get_build_logs_dir().plus_file(solution.md5_text() + "_" + configuration);
}
MonoBuildInfo::MonoBuildInfo() {}
MonoBuildInfo::MonoBuildInfo(const String &p_solution, const String &p_config) {
solution = p_solution;
configuration = p_config;
}

View file

@ -1,85 +0,0 @@
/*************************************************************************/
/* monodevelop_instance.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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) {
_GDMONO_SCOPE_DOMAIN_(TOOLS_DOMAIN)
ERR_FAIL_NULL(execute_method);
ERR_FAIL_COND(gc_handle.is_null());
MonoException *exc = NULL;
Variant files = p_files;
const Variant *args[1] = { &files };
execute_method->invoke(gc_handle->get_target(), args, &exc);
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
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, EditorId p_editor_id) {
_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_mono_ptr());
GDMonoMethod *ctor = klass->get_method(".ctor", 2);
MonoException *exc = NULL;
Variant solution = p_solution;
Variant editor_id = p_editor_id;
const Variant *args[2] = { &solution, &editor_id };
ctor->invoke(obj, args, &exc);
if (exc) {
GDMonoUtils::debug_print_unhandled_exception(exc);
ERR_FAIL();
}
gc_handle = MonoGCHandle::create_strong(obj);
execute_method = klass->get_method("Execute", 1);
}

View file

@ -1,56 +0,0 @@
/*************************************************************************/
/* monodevelop_instance.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 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 "core/reference.h"
#include "../mono_gc_handle.h"
#include "../mono_gd/gd_mono_method.h"
class MonoDevelopInstance {
Ref<MonoGCHandle> gc_handle;
GDMonoMethod *execute_method;
public:
enum EditorId {
MONODEVELOP = 0,
VISUALSTUDIO_FOR_MAC = 1
};
void execute(const Vector<String> &p_files);
void execute(const String &p_file);
MonoDevelopInstance(const String &p_solution, EditorId p_editor_id);
};
#endif // MONODEVELOP_INSTANCE_H

2
modules/mono/glue/Managed/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
# Generated Godot API solution folder
Generated

View file

@ -2,27 +2,27 @@ using System;
namespace Godot
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class RemoteAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class SyncAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class MasterAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class PuppetAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class SlaveAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class RemoteSyncAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class MasterSyncAttribute : Attribute {}
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public class PuppetSyncAttribute : Attribute {}
}

View file

@ -0,0 +1,8 @@
namespace Godot
{
public interface ISerializationListener
{
void OnBeforeSerialize();
void OnAfterDeserialize();
}
}

View file

@ -299,14 +299,14 @@ namespace Godot
if (basepos != -1)
{
var end = basepos + 3;
rs = instance.Substring(end, instance.Length);
rs = instance.Substring(end);
@base = instance.Substring(0, end);
}
else
{
if (instance.BeginsWith("/"))
{
rs = instance.Substring(1, instance.Length);
rs = instance.Substring(1);
@base = "/";
}
else
@ -333,7 +333,7 @@ namespace Godot
if (sep == -1)
return instance;
return instance.Substring(sep + 1, instance.Length);
return instance.Substring(sep + 1);
}
// <summary>
@ -911,7 +911,8 @@ namespace Godot
// </summary>
public static string Substr(this string instance, int from, int len)
{
return instance.Substring(from, len);
int max = instance.Length - from;
return instance.Substring(from, len > max ? max : len);
}
// <summary>

View file

@ -68,12 +68,12 @@ MonoString *godot_icall_String_sha256_text(MonoString *p_str) {
}
void godot_register_string_icalls() {
mono_add_internal_call("Godot.String::godot_icall_String_md5_buffer", (void *)godot_icall_String_md5_buffer);
mono_add_internal_call("Godot.String::godot_icall_String_md5_text", (void *)godot_icall_String_md5_text);
mono_add_internal_call("Godot.String::godot_icall_String_rfind", (void *)godot_icall_String_rfind);
mono_add_internal_call("Godot.String::godot_icall_String_rfindn", (void *)godot_icall_String_rfindn);
mono_add_internal_call("Godot.String::godot_icall_String_sha256_buffer", (void *)godot_icall_String_sha256_buffer);
mono_add_internal_call("Godot.String::godot_icall_String_sha256_text", (void *)godot_icall_String_sha256_text);
mono_add_internal_call("Godot.StringExtensions::godot_icall_String_md5_buffer", (void *)godot_icall_String_md5_buffer);
mono_add_internal_call("Godot.StringExtensions::godot_icall_String_md5_text", (void *)godot_icall_String_md5_text);
mono_add_internal_call("Godot.StringExtensions::godot_icall_String_rfind", (void *)godot_icall_String_rfind);
mono_add_internal_call("Godot.StringExtensions::godot_icall_String_rfindn", (void *)godot_icall_String_rfindn);
mono_add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_buffer", (void *)godot_icall_String_sha256_buffer);
mono_add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_text", (void *)godot_icall_String_sha256_text);
}
#endif // MONO_GLUE_ENABLED

View file

@ -39,7 +39,8 @@
#define API_SOLUTION_NAME "GodotSharp"
#define CORE_API_ASSEMBLY_NAME "GodotSharp"
#define EDITOR_API_ASSEMBLY_NAME "GodotSharpEditor"
#define EDITOR_TOOLS_ASSEMBLY_NAME "GodotSharpTools"
#define TOOLS_ASSEMBLY_NAME "GodotTools"
#define TOOLS_PROJECT_EDITOR_ASSEMBLY_NAME "GodotTools.ProjectEditor"
#define BINDINGS_CLASS_NATIVECALLS "NativeCalls"
#define BINDINGS_CLASS_NATIVECALLS_EDITOR "EditorNativeCalls"

View file

@ -59,6 +59,20 @@ String _get_expected_build_config() {
#endif
}
String _get_expected_api_build_config() {
#ifdef TOOLS_ENABLED
return "Debug";
#else
#ifdef DEBUG_ENABLED
return "Debug";
#else
return "Release";
#endif
#endif
}
String _get_mono_user_dir() {
#ifdef TOOLS_ENABLED
if (EditorSettings::get_singleton()) {
@ -88,6 +102,7 @@ class _GodotSharpDirs {
public:
String res_data_dir;
String res_metadata_dir;
String res_assemblies_base_dir;
String res_assemblies_dir;
String res_config_dir;
String res_temp_dir;
@ -118,7 +133,8 @@ 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_assemblies_base_dir = res_data_dir.plus_file("assemblies");
res_assemblies_dir = res_assemblies_base_dir.plus_file(_get_expected_api_build_config());
res_config_dir = res_data_dir.plus_file("etc").plus_file("mono");
// TODO use paths from csproj
@ -231,6 +247,10 @@ String get_res_metadata_dir() {
return _GodotSharpDirs::get_singleton().res_metadata_dir;
}
String get_res_assemblies_base_dir() {
return _GodotSharpDirs::get_singleton().res_assemblies_base_dir;
}
String get_res_assemblies_dir() {
return _GodotSharpDirs::get_singleton().res_assemblies_dir;
}

View file

@ -37,6 +37,7 @@ namespace GodotSharpDirs {
String get_res_data_dir();
String get_res_metadata_dir();
String get_res_assemblies_base_dir();
String get_res_assemblies_dir();
String get_res_config_dir();
String get_res_temp_dir();

View file

@ -52,7 +52,6 @@
#include "gd_mono_utils.h"
#ifdef TOOLS_ENABLED
#include "../editor/godotsharp_editor.h"
#include "main/main.h"
#endif
@ -99,7 +98,7 @@ void gdmono_profiler_init() {
#ifdef DEBUG_ENABLED
static bool _wait_for_debugger_msecs(uint32_t p_msecs) {
bool _wait_for_debugger_msecs(uint32_t p_msecs) {
do {
if (mono_is_debugger_attached())
@ -129,16 +128,17 @@ void gdmono_debug_init() {
bool da_suspend = GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false);
int da_timeout = GLOBAL_DEF("mono/debugger_agent/wait_timeout", 3000);
CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8();
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint() ||
ProjectSettings::get_singleton()->get_resource_path().empty() ||
Main::is_project_manager()) {
return;
if (da_args.size() == 0)
return;
}
#endif
CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8();
if (da_args.length() == 0) {
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"))
@ -207,6 +207,10 @@ void GDMono::initialize() {
print_verbose("Mono: Initializing module...");
char *runtime_build_info = mono_get_runtime_build_info();
print_verbose("Mono JIT compiler version " + String(runtime_build_info));
mono_free(runtime_build_info);
#ifdef DEBUG_METHODS_ENABLED
_initialize_and_check_api_hashes();
#endif
@ -339,18 +343,6 @@ void GDMono::initialize() {
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);
@ -365,8 +357,15 @@ void GDMono::initialize() {
// The following assemblies are not required at initialization
#ifdef MONO_GLUE_ENABLED
if (_load_api_assemblies()) {
// Everything is fine with the api assemblies, load the project assembly
// Everything is fine with the api assemblies, load the tools and project assemblies
#if defined(TOOLS_ENABLED)
ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies");
ERR_FAIL_COND(!_load_tools_assemblies());
#endif
_load_project_assembly();
} else {
if ((core_api_assembly && (core_api_assembly_out_of_sync || !GDMonoUtils::mono_cache.godot_api_cache_updated))
#ifdef TOOLS_ENABLED
@ -427,10 +426,6 @@ void GDMono::_register_internal_calls() {
#ifdef MONO_GLUE_ENABLED
GodotSharpBindings::register_generated_icalls();
#endif
#ifdef TOOLS_ENABLED
GodotSharpEditor::register_internal_calls();
#endif
}
void GDMono::_initialize_and_check_api_hashes() {
@ -569,6 +564,50 @@ bool GDMono::_load_corlib_assembly() {
return success;
}
static bool copy_api_assembly(const String &p_src_dir, const String &p_dst_dir, const String &p_assembly_name, APIAssembly::Type p_api_type) {
// Create destination directory if needed
if (!DirAccess::exists(p_dst_dir)) {
DirAccess *da = DirAccess::create_for_path(p_dst_dir);
Error err = da->make_dir_recursive(p_dst_dir);
memdelete(da);
if (err != OK) {
ERR_PRINTS("Failed to create destination directory for the API assemblies. Error: " + itos(err));
return false;
}
}
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) ||
GDMono::get_singleton()->metadata_is_api_assembly_invalidated(p_api_type)) {
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)
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);
if (err != OK) {
ERR_PRINTS("Failed to copy " + assembly_file);
return false;
}
GDMono::get_singleton()->metadata_set_api_assembly_invalidated(p_api_type, false);
}
return true;
}
bool GDMono::_load_core_api_assembly() {
if (core_api_assembly)
@ -576,19 +615,31 @@ bool GDMono::_load_core_api_assembly() {
#ifdef TOOLS_ENABLED
if (metadata_is_api_assembly_invalidated(APIAssembly::API_CORE)) {
print_verbose("Mono: Skipping loading of Core API assembly because it was invalidated");
return false;
String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
String prebuilt_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll");
String invalidated_dll_path = get_invalidated_api_assembly_path(APIAssembly::API_CORE);
if (!FileAccess::exists(prebuilt_dll_path) ||
FileAccess::get_modified_time(invalidated_dll_path) == FileAccess::get_modified_time(prebuilt_dll_path)) {
print_verbose("Mono: Skipping loading of Core API assembly because it was invalidated");
return false;
} else {
// Copy the prebuilt Api
String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir();
if (!copy_api_assembly(prebuilt_api_dir, res_assemblies_dir, CORE_API_ASSEMBLY_NAME, APIAssembly::API_CORE) ||
!copy_api_assembly(prebuilt_api_dir, res_assemblies_dir, EDITOR_API_ASSEMBLY_NAME, APIAssembly::API_EDITOR)) {
print_verbose("Mono: Failed to copy prebuilt API. Skipping loading of Core API assembly because it was invalidated");
return false;
}
}
}
#endif
String assembly_path = GodotSharpDirs::get_res_assemblies_dir().plus_file(CORE_API_ASSEMBLY_NAME ".dll");
if (!FileAccess::exists(assembly_path))
return false;
bool success = load_assembly_from(CORE_API_ASSEMBLY_NAME,
assembly_path,
&core_api_assembly);
bool success = (FileAccess::exists(assembly_path) &&
load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &core_api_assembly)) ||
load_assembly(CORE_API_ASSEMBLY_NAME, &core_api_assembly);
if (success) {
#ifdef MONO_GLUE_ENABLED
@ -616,18 +667,29 @@ bool GDMono::_load_editor_api_assembly() {
return true;
if (metadata_is_api_assembly_invalidated(APIAssembly::API_EDITOR)) {
print_verbose("Mono: Skipping loading of Editor API assembly because it was invalidated");
return false;
String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug");
String prebuilt_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll");
String invalidated_dll_path = get_invalidated_api_assembly_path(APIAssembly::API_EDITOR);
if (!FileAccess::exists(prebuilt_dll_path) ||
FileAccess::get_modified_time(invalidated_dll_path) == FileAccess::get_modified_time(prebuilt_dll_path)) {
print_verbose("Mono: Skipping loading of Editor API assembly because it was invalidated");
return false;
} else {
// Copy the prebuilt editor Api (no need to copy the core api if we got to this point)
String res_assemblies_dir = GodotSharpDirs::get_res_assemblies_dir();
if (!copy_api_assembly(prebuilt_api_dir, res_assemblies_dir, EDITOR_API_ASSEMBLY_NAME, APIAssembly::API_EDITOR)) {
print_verbose("Mono: Failed to copy prebuilt API. Skipping loading of Editor API assembly because it was invalidated");
return false;
}
}
}
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);
bool success = (FileAccess::exists(assembly_path) &&
load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &editor_api_assembly)) ||
load_assembly(EDITOR_API_ASSEMBLY_NAME, &editor_api_assembly);
if (success) {
#ifdef MONO_GLUE_ENABLED
@ -643,14 +705,15 @@ bool GDMono::_load_editor_api_assembly() {
#endif
#ifdef TOOLS_ENABLED
bool GDMono::_load_editor_tools_assembly() {
bool GDMono::_load_tools_assemblies() {
if (editor_tools_assembly)
if (tools_assembly && tools_project_editor_assembly)
return true;
_GDMONO_SCOPE_DOMAIN_(tools_domain)
bool success = load_assembly(TOOLS_ASSEMBLY_NAME, &tools_assembly) &&
load_assembly(TOOLS_PROJECT_EDITOR_ASSEMBLY_NAME, &tools_project_editor_assembly);
return load_assembly(EDITOR_TOOLS_ASSEMBLY_NAME, &editor_tools_assembly);
return success;
}
#endif
@ -781,6 +844,14 @@ bool GDMono::metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type)
return metadata->get_value(section, "invalidated", false) && modified_time <= stored_modified_time;
}
String GDMono::get_invalidated_api_assembly_path(APIAssembly::Type p_api_type) {
return GodotSharpDirs::get_res_assemblies_dir()
.plus_file(p_api_type == APIAssembly::API_CORE ?
CORE_API_ASSEMBLY_NAME ".dll" :
EDITOR_API_ASSEMBLY_NAME ".dll");
}
#endif
Error GDMono::_load_scripts_domain() {
@ -826,6 +897,8 @@ Error GDMono::_unload_scripts_domain() {
project_assembly = NULL;
#ifdef TOOLS_ENABLED
editor_api_assembly = NULL;
tools_assembly = NULL;
tools_project_editor_assembly = NULL;
#endif
core_api_assembly_out_of_sync = false;
@ -848,22 +921,6 @@ Error GDMono::_unload_scripts_domain() {
return OK;
}
#ifdef TOOLS_ENABLED
Error GDMono::_load_tools_domain() {
ERR_FAIL_COND_V(tools_domain != NULL, ERR_BUG);
print_verbose("Mono: Loading tools domain...");
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 GD_MONO_HOT_RELOAD
Error GDMono::reload_scripts_domain() {
@ -925,6 +982,11 @@ Error GDMono::reload_scripts_domain() {
}
}
#ifdef TOOLS_ENABLED
ERR_EXPLAIN("Mono: Failed to load GodotTools assemblies");
ERR_FAIL_COND_V(!_load_tools_assemblies(), ERR_CANT_OPEN);
#endif
if (!_load_project_assembly()) {
return ERR_CANT_OPEN;
}
@ -939,7 +1001,7 @@ Error GDMono::reload_scripts_domain() {
Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) {
CRASH_COND(p_domain == NULL);
CRASH_COND(p_domain == SCRIPTS_DOMAIN); // Should use _unload_scripts_domain() instead
CRASH_COND(p_domain == GDMono::get_singleton()->get_scripts_domain()); // Should use _unload_scripts_domain() instead
String domain_name = mono_domain_get_friendly_name(p_domain);
@ -956,18 +1018,12 @@ Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) {
_domain_assemblies_cleanup(mono_domain_get_id(p_domain));
#ifdef TOOLS_ENABLED
if (p_domain == tools_domain) {
editor_tools_assembly = NULL;
}
#endif
MonoException *exc = NULL;
mono_domain_try_unload(p_domain, (MonoObject **)&exc);
if (exc) {
ERR_PRINTS("Exception thrown when unloading domain `" + domain_name + "`");
GDMonoUtils::debug_unhandled_exception(exc);
GDMonoUtils::debug_print_unhandled_exception(exc);
return FAILED;
}
@ -998,6 +1054,22 @@ GDMonoClass *GDMono::get_class(MonoClass *p_raw_class) {
return NULL;
}
GDMonoClass *GDMono::get_class(const StringName &p_namespace, const StringName &p_name) {
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);
GDMonoClass *klass = assembly->get_class(p_namespace, p_name);
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];
@ -1038,9 +1110,6 @@ GDMono::GDMono() {
root_domain = NULL;
scripts_domain = NULL;
#ifdef TOOLS_ENABLED
tools_domain = NULL;
#endif
core_api_assembly_out_of_sync = false;
#ifdef TOOLS_ENABLED
@ -1052,7 +1121,8 @@ GDMono::GDMono() {
project_assembly = NULL;
#ifdef TOOLS_ENABLED
editor_api_assembly = NULL;
editor_tools_assembly = NULL;
tools_assembly = NULL;
tools_project_editor_assembly = NULL;
#endif
api_core_hash = 0;
@ -1064,16 +1134,6 @@ GDMono::GDMono() {
GDMono::~GDMono() {
if (is_runtime_initialized()) {
#ifdef TOOLS_ENABLED
if (tools_domain) {
Error err = finalize_and_unload_domain(tools_domain);
if (err != OK) {
ERR_PRINT("Mono: Failed to unload tools domain");
}
}
#endif
if (scripts_domain) {
Error err = _unload_scripts_domain();
if (err != OK) {
@ -1128,14 +1188,14 @@ int32_t _GodotSharp::get_domain_id() {
int32_t _GodotSharp::get_scripts_domain_id() {
MonoDomain *domain = SCRIPTS_DOMAIN;
MonoDomain *domain = GDMono::get_singleton()->get_scripts_domain();
CRASH_COND(!domain); // User must check if scripts domain is loaded before calling this method
return mono_domain_get_id(domain);
}
bool _GodotSharp::is_scripts_domain_loaded() {
return GDMono::get_singleton()->is_runtime_initialized() && SCRIPTS_DOMAIN != NULL;
return GDMono::get_singleton()->is_runtime_initialized() && GDMono::get_singleton()->get_scripts_domain() != NULL;
}
bool _GodotSharp::_is_domain_finalizing_for_unload(int32_t p_domain_id) {
@ -1157,7 +1217,7 @@ bool _GodotSharp::is_domain_finalizing_for_unload(MonoDomain *p_domain) {
if (!p_domain)
return true;
if (p_domain == SCRIPTS_DOMAIN && GDMono::get_singleton()->is_finalizing_scripts_domain())
if (p_domain == GDMono::get_singleton()->get_scripts_domain() && GDMono::get_singleton()->is_finalizing_scripts_domain())
return true;
return mono_domain_is_unloading(p_domain);
}
@ -1172,6 +1232,12 @@ bool _GodotSharp::is_runtime_initialized() {
return GDMono::get_singleton()->is_runtime_initialized();
}
void _GodotSharp::_reload_assemblies(bool p_soft_reload) {
#ifdef GD_MONO_HOT_RELOAD
CSharpLanguage::get_singleton()->reload_assemblies(p_soft_reload);
#endif
}
void _GodotSharp::_bind_methods() {
ClassDB::bind_method(D_METHOD("attach_thread"), &_GodotSharp::attach_thread);
@ -1184,6 +1250,7 @@ void _GodotSharp::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_runtime_shutting_down"), &_GodotSharp::is_runtime_shutting_down);
ClassDB::bind_method(D_METHOD("is_runtime_initialized"), &_GodotSharp::is_runtime_initialized);
ClassDB::bind_method(D_METHOD("_reload_assemblies"), &_GodotSharp::_reload_assemblies);
}
_GodotSharp::_GodotSharp() {

View file

@ -78,11 +78,6 @@ struct Version {
String to_string(Type p_type);
} // namespace APIAssembly
#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;
@ -90,9 +85,6 @@ class GDMono {
MonoDomain *root_domain;
MonoDomain *scripts_domain;
#ifdef TOOLS_ENABLED
MonoDomain *tools_domain;
#endif
bool core_api_assembly_out_of_sync;
#ifdef TOOLS_ENABLED
@ -104,7 +96,8 @@ class GDMono {
GDMonoAssembly *project_assembly;
#ifdef TOOLS_ENABLED
GDMonoAssembly *editor_api_assembly;
GDMonoAssembly *editor_tools_assembly;
GDMonoAssembly *tools_assembly;
GDMonoAssembly *tools_project_editor_assembly;
#endif
HashMap<uint32_t, HashMap<String, GDMonoAssembly *> > assemblies;
@ -115,7 +108,7 @@ class GDMono {
bool _load_core_api_assembly();
#ifdef TOOLS_ENABLED
bool _load_editor_api_assembly();
bool _load_editor_tools_assembly();
bool _load_tools_assemblies();
#endif
bool _load_project_assembly();
@ -132,10 +125,6 @@ class GDMono {
Error _load_scripts_domain();
Error _unload_scripts_domain();
#ifdef TOOLS_ENABLED
Error _load_tools_domain();
#endif
uint64_t api_core_hash;
#ifdef TOOLS_ENABLED
uint64_t api_editor_hash;
@ -170,6 +159,7 @@ public:
#ifdef TOOLS_ENABLED
void metadata_set_api_assembly_invalidated(APIAssembly::Type p_api_type, bool p_invalidated);
bool metadata_is_api_assembly_invalidated(APIAssembly::Type p_api_type);
String get_invalidated_api_assembly_path(APIAssembly::Type p_api_type);
#endif
static GDMono *get_singleton() { return singleton; }
@ -185,16 +175,14 @@ public:
_FORCE_INLINE_ bool is_finalizing_scripts_domain() { 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_core_api_assembly() const { return core_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; }
_FORCE_INLINE_ GDMonoAssembly *get_tools_assembly() const { return tools_assembly; }
_FORCE_INLINE_ GDMonoAssembly *get_tools_project_editor_assembly() const { return tools_project_editor_assembly; }
#endif
#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED)
@ -202,6 +190,7 @@ public:
#endif
GDMonoClass *get_class(MonoClass *p_raw_class);
GDMonoClass *get_class(const StringName &p_namespace, const StringName &p_name);
#ifdef GD_MONO_HOT_RELOAD
Error reload_scripts_domain();
@ -276,6 +265,8 @@ class _GodotSharp : public Object {
List<NodePath *> np_delete_queue;
List<RID *> rid_delete_queue;
void _reload_assemblies(bool p_soft_reload);
protected:
static _GodotSharp *singleton;
static void _bind_methods();

View file

@ -46,6 +46,20 @@ bool GDMonoAssembly::in_preload = false;
Vector<String> GDMonoAssembly::search_dirs;
static String _get_expected_api_build_config() {
#ifdef TOOLS_ENABLED
return "Debug";
#else
#ifdef DEBUG_ENABLED
return "Debug";
#else
return "Release";
#endif
#endif
}
void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config, const String &p_custom_bcl_dir) {
String framework_dir;
@ -67,11 +81,19 @@ void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const Strin
r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_dir());
}
String api_config = p_custom_config.empty() ? _get_expected_api_build_config() :
(p_custom_config == "Release" ? "Release" : "Debug");
r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_base_dir().plus_file(api_config));
r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_dir());
r_search_dirs.push_back(OS::get_singleton()->get_resource_dir());
r_search_dirs.push_back(OS::get_singleton()->get_executable_path().get_base_dir());
#ifdef TOOLS_ENABLED
r_search_dirs.push_back(GodotSharpDirs::get_data_editor_tools_dir());
// For GodotTools to find the api assemblies
r_search_dirs.push_back(GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug"));
#endif
}

View file

@ -41,7 +41,7 @@ String GDMonoClass::get_full_name(MonoClass *p_mono_class) {
MonoException *exc = NULL;
MonoString *str = GDMonoUtils::object_to_string((MonoObject *)type_obj, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return GDMonoMarshal::mono_string_to_godot(str);
}
@ -74,16 +74,13 @@ bool GDMonoClass::is_assignable_from(GDMonoClass *p_from) const {
}
GDMonoClass *GDMonoClass::get_parent_class() {
MonoClass *parent_mono_class = mono_class_get_parent(mono_class);
return parent_mono_class ? GDMono::get_singleton()->get_class(parent_mono_class) : NULL;
}
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;
GDMonoClass *GDMonoClass::get_nesting_class() {
MonoClass *nesting_type = mono_class_get_nesting_type(mono_class);
return nesting_type ? GDMono::get_singleton()->get_class(nesting_type) : NULL;
}
#ifdef TOOLS_ENABLED

View file

@ -121,6 +121,7 @@ public:
_FORCE_INLINE_ const GDMonoAssembly *get_assembly() const { return assembly; }
GDMonoClass *get_parent_class();
GDMonoClass *get_nesting_class();
#ifdef TOOLS_ENABLED
Vector<MonoClassField *> get_enum_fields();

View file

@ -315,7 +315,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
// The order in which we check the following interfaces is very important (dictionaries and generics first)
MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type_class->get_mono_type());
MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
MonoReflectionType *key_reftype, *value_reftype;
if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) {
@ -340,9 +340,15 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
}
if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
mono_field_set_value(p_object, mono_field, managed);
break;
if (GDMonoUtils::tools_godot_api_check()) {
MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
mono_field_set_value(p_object, mono_field, managed);
break;
} else {
MonoObject *managed = (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_value.operator Array());
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());
@ -450,7 +456,7 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
} break;
case MONO_TYPE_GENERICINST: {
MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type.type_class->get_mono_type());
MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type.type_class->get_mono_type());
if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) {
MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), type.type_class);
@ -489,9 +495,15 @@ void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_
}
if (type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
mono_field_set_value(p_object, mono_field, managed);
break;
if (GDMonoUtils::tools_godot_api_check()) {
MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array));
mono_field_set_value(p_object, mono_field, managed);
break;
} else {
MonoObject *managed = (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_value.operator Array());
mono_field_set_value(p_object, mono_field, managed);
break;
}
}
} break;

View file

@ -74,15 +74,14 @@ void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) {
script_binding.type_name = NATIVE_GDMONOCLASS_NAME(klass);
script_binding.wrapper_class = klass;
script_binding.gchandle = MonoGCHandle::create_strong(managed);
script_binding.owner = unmanaged;
Reference *kref = Object::cast_to<Reference>(unmanaged);
if (kref) {
if (ref) {
// 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: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr)
kref->reference();
ref->reference();
}
// The object was just created, no script instance binding should have been attached

View file

@ -159,7 +159,7 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
// The order in which we check the following interfaces is very important (dictionaries and generics first)
MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type_class->get_mono_type());
MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype)) {
return Variant::DICTIONARY;
@ -179,7 +179,7 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
} break;
case MONO_TYPE_GENERICINST: {
MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_type.type_class->get_mono_type());
MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type());
if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) {
return Variant::DICTIONARY;
@ -217,7 +217,7 @@ Variant::Type managed_to_variant_type(const ManagedType &p_type) {
bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_elem_type) {
switch (p_array_type.type_encoding) {
case MONO_TYPE_GENERICINST: {
MonoReflectionType *array_reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_array_type.type_class->get_mono_type());
MonoReflectionType *array_reftype = mono_type_get_object(mono_domain_get(), p_array_type.type_class->get_mono_type());
if (GDMonoUtils::Marshal::type_is_generic_array(array_reftype)) {
MonoReflectionType *elem_reftype;
@ -244,7 +244,7 @@ bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_
bool try_get_dictionary_key_value_types(const ManagedType &p_dictionary_type, ManagedType &r_key_type, ManagedType &r_value_type) {
switch (p_dictionary_type.type_encoding) {
case MONO_TYPE_GENERICINST: {
MonoReflectionType *dict_reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_dictionary_type.type_class->get_mono_type());
MonoReflectionType *dict_reftype = mono_type_get_object(mono_domain_get(), p_dictionary_type.type_class->get_mono_type());
if (GDMonoUtils::Marshal::type_is_generic_dictionary(dict_reftype)) {
MonoReflectionType *key_reftype;
@ -539,7 +539,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
// The order in which we check the following interfaces is very important (dictionaries and generics first)
MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type_class->get_mono_type());
MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
MonoReflectionType *key_reftype, *value_reftype;
if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype, &key_reftype, &value_reftype)) {
@ -558,7 +558,11 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
}
if (type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
if (GDMonoUtils::tools_godot_api_check()) {
return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
} else {
return (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_var->operator Array());
}
}
} break;
case MONO_TYPE_OBJECT: {
@ -652,7 +656,7 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
}
break;
case MONO_TYPE_GENERICINST: {
MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, p_type.type_class->get_mono_type());
MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type());
if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) {
return GDMonoUtils::create_managed_from(p_var->operator Dictionary(), p_type.type_class);
@ -681,7 +685,11 @@ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_ty
}
if (p_type.type_class->implements_interface(CACHED_CLASS(System_Collections_IEnumerable))) {
return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
if (GDMonoUtils::tools_godot_api_check()) {
return GDMonoUtils::create_managed_from(p_var->operator Array(), CACHED_CLASS(Array));
} else {
return (MonoObject *)GDMonoMarshal::Array_to_mono_array(p_var->operator Array());
}
}
} break;
} break;
@ -831,20 +839,20 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
if (CACHED_CLASS(Array) == type_class) {
MonoException *exc = NULL;
Array *ptr = invoke_method_thunk(CACHED_METHOD_THUNK(Array, GetPtr), p_obj, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return ptr ? Variant(*ptr) : Variant();
}
if (CACHED_CLASS(Dictionary) == type_class) {
MonoException *exc = NULL;
Dictionary *ptr = invoke_method_thunk(CACHED_METHOD_THUNK(Dictionary, GetPtr), p_obj, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return ptr ? Variant(*ptr) : Variant();
}
// The order in which we check the following interfaces is very important (dictionaries and generics first)
MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type_class->get_mono_type());
MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type_class->get_mono_type());
if (GDMonoUtils::Marshal::generic_idictionary_is_assignable_from(reftype)) {
return GDMonoUtils::Marshal::generic_idictionary_to_dictionary(p_obj);
@ -864,19 +872,19 @@ Variant mono_object_to_variant(MonoObject *p_obj) {
} break;
case MONO_TYPE_GENERICINST: {
MonoReflectionType *reftype = mono_type_get_object(SCRIPTS_DOMAIN, type.type_class->get_mono_type());
MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), type.type_class->get_mono_type());
if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) {
MonoException *exc = NULL;
MonoObject *ret = type.type_class->get_method("GetPtr")->invoke(p_obj, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return *unbox<Dictionary *>(ret);
}
if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) {
MonoException *exc = NULL;
MonoObject *ret = type.type_class->get_method("GetPtr")->invoke(p_obj, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return *unbox<Array *>(ret);
}

View file

@ -125,6 +125,7 @@ void MonoCache::clear_godot_api_cache() {
class_Array = NULL;
class_Dictionary = NULL;
class_MarshalUtils = NULL;
class_ISerializationListener = NULL;
#ifdef DEBUG_ENABLED
class_DebuggingUtils = NULL;
@ -242,6 +243,7 @@ void update_godot_api_cache() {
CACHE_CLASS_AND_CHECK(Array, GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Array));
CACHE_CLASS_AND_CHECK(Dictionary, GODOT_API_NS_CLAS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary));
CACHE_CLASS_AND_CHECK(MarshalUtils, GODOT_API_CLASS(MarshalUtils));
CACHE_CLASS_AND_CHECK(ISerializationListener, GODOT_API_CLASS(ISerializationListener));
#ifdef DEBUG_ENABLED
CACHE_CLASS_AND_CHECK(DebuggingUtils, GODOT_API_CLASS(DebuggingUtils));
@ -302,7 +304,7 @@ void update_godot_api_cache() {
#endif
// TODO Move to CSharpLanguage::init() and do handle disposal
MonoObject *task_scheduler = mono_object_new(SCRIPTS_DOMAIN, GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr());
MonoObject *task_scheduler = mono_object_new(mono_domain_get(), GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr());
GDMonoUtils::runtime_object_init(task_scheduler, GODOT_API_CLASS(GodotTaskScheduler));
mono_cache.task_scheduler_handle = MonoGCHandle::create_strong(task_scheduler);
@ -371,7 +373,6 @@ MonoObject *unmanaged_get_managed(Object *unmanaged) {
// 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: godot_icall_Reference_Dtor(MonoObject *p_obj, Object *p_ptr)
ref->reference();
}
@ -384,7 +385,7 @@ void set_main_thread(MonoThread *p_thread) {
void attach_current_thread() {
ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized());
MonoThread *mono_thread = mono_thread_attach(SCRIPTS_DOMAIN);
MonoThread *mono_thread = mono_thread_attach(mono_domain_get());
ERR_FAIL_NULL(mono_thread);
}
@ -448,17 +449,12 @@ GDMonoClass *get_class_native_base(GDMonoClass *p_class) {
}
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)) {
if (!ClassDB::is_parent_class(p_object->get_class_name(), 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_mono_ptr());
MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr());
ERR_FAIL_NULL_V(mono_object, NULL);
CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, p_object);
@ -470,7 +466,7 @@ MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringNa
}
MonoObject *create_managed_from(const NodePath &p_from) {
MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, CACHED_CLASS_RAW(NodePath));
MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(NodePath));
ERR_FAIL_NULL_V(mono_object, NULL);
// Construct
@ -482,7 +478,7 @@ MonoObject *create_managed_from(const NodePath &p_from) {
}
MonoObject *create_managed_from(const RID &p_from) {
MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, CACHED_CLASS_RAW(RID));
MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(RID));
ERR_FAIL_NULL_V(mono_object, NULL);
// Construct
@ -494,7 +490,7 @@ MonoObject *create_managed_from(const RID &p_from) {
}
MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) {
MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr());
MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr());
ERR_FAIL_NULL_V(mono_object, NULL);
// Search constructor that takes a pointer as parameter
@ -518,13 +514,13 @@ MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) {
MonoException *exc = NULL;
GDMonoUtils::runtime_invoke(m, mono_object, args, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return mono_object;
}
MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class) {
MonoObject *mono_object = mono_object_new(SCRIPTS_DOMAIN, p_class->get_mono_ptr());
MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr());
ERR_FAIL_NULL_V(mono_object, NULL);
// Search constructor that takes a pointer as parameter
@ -548,7 +544,7 @@ MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class)
MonoException *exc = NULL;
GDMonoUtils::runtime_invoke(m, mono_object, args, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return mono_object;
}
@ -667,7 +663,10 @@ void print_unhandled_exception(MonoException *p_exc) {
}
void set_pending_exception(MonoException *p_exc) {
#ifdef HAS_PENDING_EXCEPTIONS
#ifdef NO_PENDING_EXCEPTIONS
debug_unhandled_exception(p_exc);
GD_UNREACHABLE();
#else
if (get_runtime_invoke_count() == 0) {
debug_unhandled_exception(p_exc);
GD_UNREACHABLE();
@ -677,9 +676,6 @@ void set_pending_exception(MonoException *p_exc) {
ERR_PRINTS("Exception thrown from managed code, but it could not be set as pending:");
GDMonoUtils::debug_print_unhandled_exception(p_exc);
}
#else
debug_unhandled_exception(p_exc);
GD_UNREACHABLE();
#endif
}
@ -755,113 +751,137 @@ void dispose(MonoObject *p_mono_object, MonoException **r_exc) {
namespace Marshal {
MonoBoolean type_is_generic_array(MonoReflectionType *p_reftype) {
#ifdef MONO_GLUE_ENABLED
#ifdef TOOLS_ENABLED
#define NO_GLUE_RET(m_ret) \
{ \
if (!mono_cache.godot_api_cache_updated) return m_ret; \
}
#else
#define NO_GLUE_RET(m_ret) \
{}
#endif
#else
#define NO_GLUE_RET(m_ret) \
{ return m_ret; }
#endif
bool type_is_generic_array(MonoReflectionType *p_reftype) {
NO_GLUE_RET(false);
TypeIsGenericArray thunk = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericArray);
MonoException *exc = NULL;
MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
return res;
UNHANDLED_EXCEPTION(exc);
return (bool)res;
}
MonoBoolean type_is_generic_dictionary(MonoReflectionType *p_reftype) {
bool type_is_generic_dictionary(MonoReflectionType *p_reftype) {
NO_GLUE_RET(false);
TypeIsGenericDictionary thunk = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericDictionary);
MonoException *exc = NULL;
MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
return res;
UNHANDLED_EXCEPTION(exc);
return (bool)res;
}
void array_get_element_type(MonoReflectionType *p_array_reftype, MonoReflectionType **r_elem_reftype) {
ArrayGetElementType thunk = CACHED_METHOD_THUNK(MarshalUtils, ArrayGetElementType);
MonoException *exc = NULL;
invoke_method_thunk(thunk, p_array_reftype, r_elem_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
}
void dictionary_get_key_value_types(MonoReflectionType *p_dict_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) {
DictionaryGetKeyValueTypes thunk = CACHED_METHOD_THUNK(MarshalUtils, DictionaryGetKeyValueTypes);
MonoException *exc = NULL;
invoke_method_thunk(thunk, p_dict_reftype, r_key_reftype, r_value_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
}
MonoBoolean generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype) {
bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype) {
NO_GLUE_RET(false);
GenericIEnumerableIsAssignableFromType thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIEnumerableIsAssignableFromType);
MonoException *exc = NULL;
MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
return res;
UNHANDLED_EXCEPTION(exc);
return (bool)res;
}
MonoBoolean generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype) {
bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype) {
NO_GLUE_RET(false);
GenericIDictionaryIsAssignableFromType thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryIsAssignableFromType);
MonoException *exc = NULL;
MonoBoolean res = invoke_method_thunk(thunk, p_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
return res;
UNHANDLED_EXCEPTION(exc);
return (bool)res;
}
MonoBoolean generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype) {
bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype) {
NO_GLUE_RET(false);
GenericIEnumerableIsAssignableFromType_with_info thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIEnumerableIsAssignableFromType_with_info);
MonoException *exc = NULL;
MonoBoolean res = invoke_method_thunk(thunk, p_reftype, r_elem_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
return res;
UNHANDLED_EXCEPTION(exc);
return (bool)res;
}
MonoBoolean generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) {
bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) {
NO_GLUE_RET(false);
GenericIDictionaryIsAssignableFromType_with_info thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryIsAssignableFromType_with_info);
MonoException *exc = NULL;
MonoBoolean res = invoke_method_thunk(thunk, p_reftype, r_key_reftype, r_value_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
return res;
UNHANDLED_EXCEPTION(exc);
return (bool)res;
}
Array enumerable_to_array(MonoObject *p_enumerable) {
NO_GLUE_RET(Array());
Array result;
EnumerableToArray thunk = CACHED_METHOD_THUNK(MarshalUtils, EnumerableToArray);
MonoException *exc = NULL;
invoke_method_thunk(thunk, p_enumerable, &result, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return result;
}
Dictionary idictionary_to_dictionary(MonoObject *p_idictionary) {
NO_GLUE_RET(Dictionary());
Dictionary result;
IDictionaryToDictionary thunk = CACHED_METHOD_THUNK(MarshalUtils, IDictionaryToDictionary);
MonoException *exc = NULL;
invoke_method_thunk(thunk, p_idictionary, &result, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return result;
}
Dictionary generic_idictionary_to_dictionary(MonoObject *p_generic_idictionary) {
NO_GLUE_RET(Dictionary());
Dictionary result;
GenericIDictionaryToDictionary thunk = CACHED_METHOD_THUNK(MarshalUtils, GenericIDictionaryToDictionary);
MonoException *exc = NULL;
invoke_method_thunk(thunk, p_generic_idictionary, &result, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return result;
}
GDMonoClass *make_generic_array_type(MonoReflectionType *p_elem_reftype) {
NO_GLUE_RET(NULL);
MakeGenericArrayType thunk = CACHED_METHOD_THUNK(MarshalUtils, MakeGenericArrayType);
MonoException *exc = NULL;
MonoReflectionType *reftype = invoke_method_thunk(thunk, p_elem_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return GDMono::get_singleton()->get_class(mono_class_from_mono_type(mono_reflection_type_get_type(reftype)));
}
GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype) {
NO_GLUE_RET(NULL);
MakeGenericDictionaryType thunk = CACHED_METHOD_THUNK(MarshalUtils, MakeGenericDictionaryType);
MonoException *exc = NULL;
MonoReflectionType *reftype = invoke_method_thunk(thunk, p_key_reftype, p_value_reftype, &exc);
UNLIKELY_UNHANDLED_EXCEPTION(exc);
UNHANDLED_EXCEPTION(exc);
return GDMono::get_singleton()->get_class(mono_class_from_mono_type(mono_reflection_type_get_type(reftype)));
}
} // namespace Marshal
// namespace Marshal
} // namespace GDMonoUtils

View file

@ -41,7 +41,7 @@
#include "core/object.h"
#include "core/reference.h"
#define UNLIKELY_UNHANDLED_EXCEPTION(m_exc) \
#define UNHANDLED_EXCEPTION(m_exc) \
if (unlikely(m_exc != NULL)) { \
GDMonoUtils::debug_unhandled_exception(m_exc); \
GD_UNREACHABLE(); \
@ -78,16 +78,16 @@ typedef void (*GenericIDictionaryToDictionary)(MonoObject *, Dictionary *, MonoE
namespace Marshal {
MonoBoolean type_is_generic_array(MonoReflectionType *p_reftype);
MonoBoolean type_is_generic_dictionary(MonoReflectionType *p_reftype);
bool type_is_generic_array(MonoReflectionType *p_reftype);
bool type_is_generic_dictionary(MonoReflectionType *p_reftype);
void array_get_element_type(MonoReflectionType *p_array_reftype, MonoReflectionType **r_elem_reftype);
void dictionary_get_key_value_types(MonoReflectionType *p_dict_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype);
MonoBoolean generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype);
MonoBoolean generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype);
MonoBoolean generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype);
MonoBoolean generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype);
bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype);
bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype);
bool generic_ienumerable_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_elem_reftype);
bool generic_idictionary_is_assignable_from(MonoReflectionType *p_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype);
GDMonoClass *make_generic_array_type(MonoReflectionType *p_elem_reftype);
GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype);
@ -157,6 +157,7 @@ struct MonoCache {
GDMonoClass *class_Array;
GDMonoClass *class_Dictionary;
GDMonoClass *class_MarshalUtils;
GDMonoClass *class_ISerializationListener;
#ifdef DEBUG_ENABLED
GDMonoClass *class_DebuggingUtils;
@ -235,10 +236,19 @@ void update_godot_api_cache();
inline void clear_corlib_cache() {
mono_cache.clear_corlib_cache();
}
inline void clear_godot_api_cache() {
mono_cache.clear_godot_api_cache();
}
_FORCE_INLINE_ bool tools_godot_api_check() {
#ifdef TOOLS_ENABLED
return mono_cache.godot_api_cache_updated;
#else
return true; // Assume it's updated if this was called, otherwise it's a bug
#endif
}
_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);
}

View file

@ -91,7 +91,7 @@ Variant SignalAwaiterHandle::_signal_callback(const Variant **p_args, int p_argc
set_completed(true);
int signal_argc = p_argcount - 1;
MonoArray *signal_args = mono_array_new(SCRIPTS_DOMAIN, CACHED_CLASS_RAW(MonoObject), signal_argc);
MonoArray *signal_args = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), signal_argc);
for (int i = 0; i < signal_argc; i++) {
MonoObject *boxed = GDMonoMarshal::variant_to_mono_object(*p_args[i]);