2
0
Эх сурвалжийг харах

Merge pull request #1205 from dsnopek/4.1-cherrypicks-1

Cherry-picks for the godot-cpp 4.1 branch - 1st batch
David Snopek 2 жил өмнө
parent
commit
28494f0bd5

+ 33 - 6
SConstruct

@@ -20,14 +20,19 @@ def normalize_path(val):
     return val if os.path.isabs(val) else os.path.join(env.Dir("#").abspath, val)
 
 
-def validate_api_file(key, val, env):
+def validate_file(key, val, env):
     if not os.path.isfile(normalize_path(val)):
-        raise UserError("GDExtension API file ('%s') does not exist: %s" % (key, val))
+        raise UserError("'%s' is not a file: %s" % (key, val))
 
 
-def validate_gdextension_dir(key, val, env):
+def validate_dir(key, val, env):
     if not os.path.isdir(normalize_path(val)):
-        raise UserError("GDExtension directory ('%s') does not exist: %s" % (key, val))
+        raise UserError("'%s' is not a directory: %s" % (key, val))
+
+
+def validate_parent_dir(key, val, env):
+    if not os.path.isdir(normalize_path(os.path.dirname(val))):
+        raise UserError("'%s' is not a directory: %s" % (key, os.path.dirname(val)))
 
 
 def get_gdextension_dir(env):
@@ -115,7 +120,7 @@ opts.Add(
         key="gdextension_dir",
         help="Path to a custom directory containing GDExtension interface header and API JSON file",
         default=env.get("gdextension_dir", None),
-        validator=validate_gdextension_dir,
+        validator=validate_dir,
     )
 )
 opts.Add(
@@ -123,7 +128,7 @@ opts.Add(
         key="custom_api_file",
         help="Path to a custom GDExtension API JSON file (takes precedence over `gdextension_dir`)",
         default=env.get("custom_api_file", None),
-        validator=validate_api_file,
+        validator=validate_file,
     )
 )
 opts.Add(
@@ -151,6 +156,23 @@ opts.Add(
     )
 )
 
+# compiledb
+opts.Add(
+    BoolVariable(
+        key="compiledb",
+        help="Generate compilation DB (`compile_commands.json`) for external tools",
+        default=env.get("compiledb", False),
+    )
+)
+opts.Add(
+    PathVariable(
+        key="compiledb_file",
+        help="Path to a custom `compile_commands.json` file",
+        default=env.get("compiledb_file", "compile_commands.json"),
+        validator=validate_parent_dir,
+    )
+)
+
 # Add platform options
 tools = {}
 for pl in platforms:
@@ -242,6 +264,11 @@ else:
 if env["precision"] == "double":
     env.Append(CPPDEFINES=["REAL_T_IS_DOUBLE"])
 
+# compile_commands.json
+if env.get("compiledb", False):
+    env.Tool("compilation_db")
+    env.Alias("compiledb", env.CompilationDatabase(normalize_path(env["compiledb_file"])))
+
 # Generate bindings
 env.Append(BUILDERS={"GenerateBindings": Builder(action=scons_generate_bindings, emitter=scons_emit_files)})
 

+ 12 - 9
binding_generator.py

@@ -1453,16 +1453,22 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
 
     if is_singleton:
         result.append(f"{class_name} *{class_name}::get_singleton() {{")
-        result.append(f"\tconst StringName _gde_class_name = {class_name}::get_class_static();")
+        # We assume multi-threaded access is OK because each assignment will assign the same value every time
+        result.append(f"\tstatic {class_name} *singleton = nullptr;")
+        result.append("\tif (unlikely(singleton == nullptr)) {")
         result.append(
-            "\tstatic GDExtensionObjectPtr singleton_obj = internal::gdextension_interface_global_get_singleton(_gde_class_name._native_ptr());"
+            f"\t\tGDExtensionObjectPtr singleton_obj = internal::gdextension_interface_global_get_singleton({class_name}::get_class_static()._native_ptr());"
         )
         result.append("#ifdef DEBUG_ENABLED")
-        result.append("\tERR_FAIL_COND_V(singleton_obj == nullptr, nullptr);")
+        result.append("\t\tERR_FAIL_COND_V(singleton_obj == nullptr, nullptr);")
         result.append("#endif // DEBUG_ENABLED")
         result.append(
-            f"\tstatic {class_name} *singleton = reinterpret_cast<{class_name} *>(internal::gdextension_interface_object_get_instance_binding(singleton_obj, internal::token, &{class_name}::_gde_binding_callbacks));"
+            f"\t\tsingleton = reinterpret_cast<{class_name} *>(internal::gdextension_interface_object_get_instance_binding(singleton_obj, internal::token, &{class_name}::_gde_binding_callbacks));"
         )
+        result.append("#ifdef DEBUG_ENABLED")
+        result.append("\t\tERR_FAIL_COND_V(singleton == nullptr, nullptr);")
+        result.append("#endif // DEBUG_ENABLED")
+        result.append("\t}")
         result.append("\treturn singleton;")
         result.append("}")
         result.append("")
@@ -1480,10 +1486,8 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us
             result.append(method_signature + " {")
 
             # Method body.
-            result.append(f"\tconst StringName _gde_class_name = {class_name}::get_class_static();")
-            result.append(f'\tconst StringName _gde_method_name = "{method["name"]}";')
             result.append(
-                f'\tstatic GDExtensionMethodBindPtr _gde_method_bind = internal::gdextension_interface_classdb_get_method_bind(_gde_class_name._native_ptr(), _gde_method_name._native_ptr(), {method["hash"]});'
+                f'\tstatic GDExtensionMethodBindPtr _gde_method_bind = internal::gdextension_interface_classdb_get_method_bind({class_name}::get_class_static()._native_ptr(), StringName("{method["name"]}")._native_ptr(), {method["hash"]});'
             )
             method_call = "\t"
             has_return = "return_value" in method and method["return_value"]["type"] != "void"
@@ -1773,9 +1777,8 @@ def generate_utility_functions(api, output_dir):
 
         # Function body.
 
-        source.append(f'\tconst StringName _gde_function_name = "{function["name"]}";')
         source.append(
-            f'\tstatic GDExtensionPtrUtilityFunction _gde_function = internal::gdextension_interface_variant_get_ptr_utility_function(_gde_function_name._native_ptr(), {function["hash"]});'
+            f'\tstatic GDExtensionPtrUtilityFunction _gde_function = internal::gdextension_interface_variant_get_ptr_utility_function(StringName("{function["name"]}")._native_ptr(), {function["hash"]});'
         )
         has_return = "return_type" in function and function["return_type"] != "void"
         if has_return:

+ 7 - 1
include/godot_cpp/templates/cowdata.hpp

@@ -32,13 +32,13 @@
 #define GODOT_COWDATA_HPP
 
 #include <godot_cpp/classes/global_constants.hpp>
-#include <godot_cpp/core/class_db.hpp>
 #include <godot_cpp/core/error_macros.hpp>
 #include <godot_cpp/core/math.hpp>
 #include <godot_cpp/core/memory.hpp>
 #include <godot_cpp/templates/safe_refcount.hpp>
 
 #include <cstring>
+#include <new>
 
 namespace godot {
 
@@ -48,6 +48,9 @@ class Vector;
 template <class T, class V>
 class VMap;
 
+template <class T>
+class CharStringT;
+
 // Silence a false positive warning (see GH-52119).
 #if defined(__GNUC__) && !defined(__clang__)
 #pragma GCC diagnostic push
@@ -62,6 +65,9 @@ class CowData {
 	template <class TV, class VV>
 	friend class VMap;
 
+	template <class TS>
+	friend class CharStringT;
+
 private:
 	mutable T *_ptr = nullptr;
 

+ 71 - 54
include/godot_cpp/variant/char_string.hpp

@@ -31,83 +31,100 @@
 #ifndef GODOT_CHAR_STRING_HPP
 #define GODOT_CHAR_STRING_HPP
 
+#include <godot_cpp/templates/cowdata.hpp>
+
 #include <cstddef>
 #include <cstdint>
 
 namespace godot {
 
-class CharString {
-	friend class String;
-
-	const char *_data = nullptr;
-	int _length = 0;
-
-	CharString(const char *str, int length);
-
-public:
-	int length() const;
-	const char *get_data() const;
-
-	CharString(CharString &&p_str);
-	void operator=(CharString &&p_str);
-	CharString() {}
-	~CharString();
-};
+template <class T>
+class CharStringT;
 
-class Char16String {
-	friend class String;
+template <class T>
+class CharProxy {
+	template <class TS>
+	friend class CharStringT;
 
-	const char16_t *_data = nullptr;
-	int _length = 0;
+	const int _index;
+	CowData<T> &_cowdata;
+	static inline const T _null = 0;
 
-	Char16String(const char16_t *str, int length);
+	_FORCE_INLINE_ CharProxy(const int &p_index, CowData<T> &p_cowdata) :
+			_index(p_index),
+			_cowdata(p_cowdata) {}
 
 public:
-	int length() const;
-	const char16_t *get_data() const;
-
-	Char16String(Char16String &&p_str);
-	void operator=(Char16String &&p_str);
-	Char16String() {}
-	~Char16String();
-};
+	_FORCE_INLINE_ CharProxy(const CharProxy<T> &p_other) :
+			_index(p_other._index),
+			_cowdata(p_other._cowdata) {}
 
-class Char32String {
-	friend class String;
+	_FORCE_INLINE_ operator T() const {
+		if (unlikely(_index == _cowdata.size())) {
+			return _null;
+		}
 
-	const char32_t *_data = nullptr;
-	int _length = 0;
+		return _cowdata.get(_index);
+	}
 
-	Char32String(const char32_t *str, int length);
+	_FORCE_INLINE_ const T *operator&() const {
+		return _cowdata.ptr() + _index;
+	}
 
-public:
-	int length() const;
-	const char32_t *get_data() const;
+	_FORCE_INLINE_ void operator=(const T &p_other) const {
+		_cowdata.set(_index, p_other);
+	}
 
-	Char32String(Char32String &&p_str);
-	void operator=(Char32String &&p_str);
-	Char32String() {}
-	~Char32String();
+	_FORCE_INLINE_ void operator=(const CharProxy<T> &p_other) const {
+		_cowdata.set(_index, p_other.operator T());
+	}
 };
 
-class CharWideString {
+template <class T>
+class CharStringT {
 	friend class String;
 
-	const wchar_t *_data = nullptr;
-	int _length = 0;
-
-	CharWideString(const wchar_t *str, int length);
+	CowData<T> _cowdata;
+	static inline const T _null = 0;
 
 public:
-	int length() const;
-	const wchar_t *get_data() const;
-
-	CharWideString(CharWideString &&p_str);
-	void operator=(CharWideString &&p_str);
-	CharWideString() {}
-	~CharWideString();
+	_FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); }
+	_FORCE_INLINE_ const T *ptr() const { return _cowdata.ptr(); }
+	_FORCE_INLINE_ int size() const { return _cowdata.size(); }
+	Error resize(int p_size) { return _cowdata.resize(p_size); }
+
+	_FORCE_INLINE_ T get(int p_index) const { return _cowdata.get(p_index); }
+	_FORCE_INLINE_ void set(int p_index, const T &p_elem) { _cowdata.set(p_index, p_elem); }
+	_FORCE_INLINE_ const T &operator[](int p_index) const {
+		if (unlikely(p_index == _cowdata.size())) {
+			return _null;
+		}
+
+		return _cowdata.get(p_index);
+	}
+	_FORCE_INLINE_ CharProxy<T> operator[](int p_index) { return CharProxy<T>(p_index, _cowdata); }
+
+	_FORCE_INLINE_ CharStringT() {}
+	_FORCE_INLINE_ CharStringT(const CharStringT<T> &p_str) { _cowdata._ref(p_str._cowdata); }
+	_FORCE_INLINE_ void operator=(const CharStringT<T> &p_str) { _cowdata._ref(p_str._cowdata); }
+	_FORCE_INLINE_ CharStringT(const T *p_cstr) { copy_from(p_cstr); }
+
+	void operator=(const T *p_cstr);
+	bool operator<(const CharStringT<T> &p_right) const;
+	CharStringT<T> &operator+=(T p_char);
+	int length() const { return size() ? size() - 1 : 0; }
+	const T *get_data() const;
+	operator const T *() const { return get_data(); };
+
+protected:
+	void copy_from(const T *p_cstr);
 };
 
+typedef CharStringT<char> CharString;
+typedef CharStringT<char16_t> Char16String;
+typedef CharStringT<char32_t> Char32String;
+typedef CharStringT<wchar_t> CharWideString;
+
 } // namespace godot
 
 #endif // GODOT_CHAR_STRING_HPP

+ 107 - 98
src/variant/char_string.cpp

@@ -38,116 +38,120 @@
 #include <godot_cpp/godot.hpp>
 
 #include <cmath>
+#include <string>
 
 namespace godot {
 
-int CharString::length() const {
-	return _length;
-}
-
-const char *CharString::get_data() const {
-	return _data;
-}
-
-CharString::CharString(CharString &&p_str) {
-	SWAP(_length, p_str._length);
-	SWAP(_data, p_str._data);
-}
+template <typename L, typename R>
+_FORCE_INLINE_ bool is_str_less(const L *l_ptr, const R *r_ptr) {
+	while (true) {
+		const char32_t l = *l_ptr;
+		const char32_t r = *r_ptr;
+
+		if (l == 0 && r == 0) {
+			return false;
+		} else if (l == 0) {
+			return true;
+		} else if (r == 0) {
+			return false;
+		} else if (l < r) {
+			return true;
+		} else if (l > r) {
+			return false;
+		}
 
-void CharString::operator=(CharString &&p_str) {
-	SWAP(_length, p_str._length);
-	SWAP(_data, p_str._data);
+		l_ptr++;
+		r_ptr++;
+	}
 }
 
-CharString::CharString(const char *str, int length) :
-		_data(str), _length(length) {}
-
-CharString::~CharString() {
-	if (_data != nullptr) {
-		memdelete_arr(_data);
+template <class T>
+bool CharStringT<T>::operator<(const CharStringT<T> &p_right) const {
+	if (length() == 0) {
+		return p_right.length() != 0;
 	}
-}
 
-int Char16String::length() const {
-	return _length;
+	return is_str_less(get_data(), p_right.get_data());
 }
 
-const char16_t *Char16String::get_data() const {
-	return _data;
-}
+template <class T>
+CharStringT<T> &CharStringT<T>::operator+=(T p_char) {
+	const int lhs_len = length();
+	resize(lhs_len + 2);
 
-Char16String::Char16String(Char16String &&p_str) {
-	SWAP(_length, p_str._length);
-	SWAP(_data, p_str._data);
-}
+	T *dst = ptrw();
+	dst[lhs_len] = p_char;
+	dst[lhs_len + 1] = 0;
 
-void Char16String::operator=(Char16String &&p_str) {
-	SWAP(_length, p_str._length);
-	SWAP(_data, p_str._data);
+	return *this;
 }
 
-Char16String::Char16String(const char16_t *str, int length) :
-		_data(str), _length(length) {}
+template <class T>
+void CharStringT<T>::operator=(const T *p_cstr) {
+	copy_from(p_cstr);
+}
 
-Char16String::~Char16String() {
-	if (_data != nullptr) {
-		memdelete_arr(_data);
+template <>
+const char *CharStringT<char>::get_data() const {
+	if (size()) {
+		return &operator[](0);
+	} else {
+		return "";
 	}
 }
 
-int Char32String::length() const {
-	return _length;
+template <>
+const char16_t *CharStringT<char16_t>::get_data() const {
+	if (size()) {
+		return &operator[](0);
+	} else {
+		return u"";
+	}
 }
 
-const char32_t *Char32String::get_data() const {
-	return _data;
+template <>
+const char32_t *CharStringT<char32_t>::get_data() const {
+	if (size()) {
+		return &operator[](0);
+	} else {
+		return U"";
+	}
 }
 
-Char32String::Char32String(Char32String &&p_str) {
-	SWAP(_length, p_str._length);
-	SWAP(_data, p_str._data);
+template <>
+const wchar_t *CharStringT<wchar_t>::get_data() const {
+	if (size()) {
+		return &operator[](0);
+	} else {
+		return L"";
+	}
 }
 
-void Char32String::operator=(Char32String &&p_str) {
-	SWAP(_length, p_str._length);
-	SWAP(_data, p_str._data);
-}
+template <class T>
+void CharStringT<T>::copy_from(const T *p_cstr) {
+	if (!p_cstr) {
+		resize(0);
+		return;
+	}
 
-Char32String::Char32String(const char32_t *str, int length) :
-		_data(str), _length(length) {}
+	size_t len = std::char_traits<T>::length(p_cstr);
 
-Char32String::~Char32String() {
-	if (_data != nullptr) {
-		memdelete_arr(_data);
+	if (len == 0) {
+		resize(0);
+		return;
 	}
-}
 
-int CharWideString::length() const {
-	return _length;
-}
+	Error err = resize(++len); // include terminating null char
 
-const wchar_t *CharWideString::get_data() const {
-	return _data;
-}
-
-CharWideString::CharWideString(CharWideString &&p_str) {
-	SWAP(_length, p_str._length);
-	SWAP(_data, p_str._data);
-}
+	ERR_FAIL_COND_MSG(err != OK, "Failed to copy C-string.");
 
-void CharWideString::operator=(CharWideString &&p_str) {
-	SWAP(_length, p_str._length);
-	SWAP(_data, p_str._data);
+	memcpy(ptrw(), p_cstr, len);
 }
 
-CharWideString::CharWideString(const wchar_t *str, int length) :
-		_data(str), _length(length) {}
-
-CharWideString::~CharWideString() {
-	if (_data != nullptr) {
-		memdelete_arr(_data);
-	}
-}
+template class CharStringT<char>;
+template class CharStringT<char16_t>;
+template class CharStringT<char32_t>;
+template class CharStringT<wchar_t>;
 
 // Custom String functions that are not part of bound API.
 // It's easier to have them written in C++ directly than in a Python script that generates them.
@@ -228,56 +232,61 @@ String rtoss(double p_val) {
 CharString String::utf8() const {
 	int length = internal::gdextension_interface_string_to_utf8_chars(_native_ptr(), nullptr, 0);
 	int size = length + 1;
-	char *cstr = memnew_arr(char, size);
-	internal::gdextension_interface_string_to_utf8_chars(_native_ptr(), cstr, length);
+	CharString str;
+	str.resize(size);
+	internal::gdextension_interface_string_to_utf8_chars(_native_ptr(), str.ptrw(), length);
 
-	cstr[length] = '\0';
+	str[length] = '\0';
 
-	return CharString(cstr, length);
+	return str;
 }
 
 CharString String::ascii() const {
 	int length = internal::gdextension_interface_string_to_latin1_chars(_native_ptr(), nullptr, 0);
 	int size = length + 1;
-	char *cstr = memnew_arr(char, size);
-	internal::gdextension_interface_string_to_latin1_chars(_native_ptr(), cstr, length);
+	CharString str;
+	str.resize(size);
+	internal::gdextension_interface_string_to_latin1_chars(_native_ptr(), str.ptrw(), length);
 
-	cstr[length] = '\0';
+	str[length] = '\0';
 
-	return CharString(cstr, length);
+	return str;
 }
 
 Char16String String::utf16() const {
 	int length = internal::gdextension_interface_string_to_utf16_chars(_native_ptr(), nullptr, 0);
 	int size = length + 1;
-	char16_t *cstr = memnew_arr(char16_t, size);
-	internal::gdextension_interface_string_to_utf16_chars(_native_ptr(), cstr, length);
+	Char16String str;
+	str.resize(size);
+	internal::gdextension_interface_string_to_utf16_chars(_native_ptr(), str.ptrw(), length);
 
-	cstr[length] = '\0';
+	str[length] = '\0';
 
-	return Char16String(cstr, length);
+	return str;
 }
 
 Char32String String::utf32() const {
 	int length = internal::gdextension_interface_string_to_utf32_chars(_native_ptr(), nullptr, 0);
 	int size = length + 1;
-	char32_t *cstr = memnew_arr(char32_t, size);
-	internal::gdextension_interface_string_to_utf32_chars(_native_ptr(), cstr, length);
+	Char32String str;
+	str.resize(size);
+	internal::gdextension_interface_string_to_utf32_chars(_native_ptr(), str.ptrw(), length);
 
-	cstr[length] = '\0';
+	str[length] = '\0';
 
-	return Char32String(cstr, length);
+	return str;
 }
 
 CharWideString String::wide_string() const {
 	int length = internal::gdextension_interface_string_to_wide_chars(_native_ptr(), nullptr, 0);
 	int size = length + 1;
-	wchar_t *cstr = memnew_arr(wchar_t, size);
-	internal::gdextension_interface_string_to_wide_chars(_native_ptr(), cstr, length);
+	CharWideString str;
+	str.resize(size);
+	internal::gdextension_interface_string_to_wide_chars(_native_ptr(), str.ptrw(), length);
 
-	cstr[length] = '\0';
+	str[length] = '\0';
 
-	return CharWideString(cstr, length);
+	return str;
 }
 
 String &String::operator=(const char *p_str) {

+ 4 - 0
test/project/main.gd

@@ -82,6 +82,10 @@ func _ready():
 	# UtilityFunctions::str()
 	assert_equal(example.test_str_utility(), "Hello, World! The answer is 42")
 
+	# Test converting string to char* and doing comparison.
+	assert_equal(example.test_string_is_fourty_two("blah"), false)
+	assert_equal(example.test_string_is_fourty_two("fourty two"), true)
+
 	# PackedArray iterators
 	assert_equal(example.test_vector_ops(), 105)
 

+ 5 - 0
test/src/example.cpp

@@ -138,6 +138,7 @@ void Example::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("test_node_argument"), &Example::test_node_argument);
 	ClassDB::bind_method(D_METHOD("test_string_ops"), &Example::test_string_ops);
 	ClassDB::bind_method(D_METHOD("test_str_utility"), &Example::test_str_utility);
+	ClassDB::bind_method(D_METHOD("test_string_is_fourty_two"), &Example::test_string_is_fourty_two);
 	ClassDB::bind_method(D_METHOD("test_vector_ops"), &Example::test_vector_ops);
 
 	ClassDB::bind_method(D_METHOD("test_bitfield", "flags"), &Example::test_bitfield);
@@ -299,6 +300,10 @@ String Example::test_str_utility() const {
 	return UtilityFunctions::str("Hello, ", "World", "! The answer is ", 42);
 }
 
+bool Example::test_string_is_fourty_two(const String &p_string) const {
+	return strcmp(p_string.utf8().ptr(), "fourty two") == 0;
+}
+
 int Example::test_vector_ops() const {
 	PackedInt32Array arr;
 	arr.push_back(10);

+ 1 - 0
test/src/example.h

@@ -117,6 +117,7 @@ public:
 	Example *test_node_argument(Example *p_node) const;
 	String test_string_ops() const;
 	String test_str_utility() const;
+	bool test_string_is_fourty_two(const String &p_str) const;
 	int test_vector_ops() const;
 
 	BitField<Flags> test_bitfield(BitField<Flags> flags);

+ 2 - 0
tools/android.py

@@ -100,3 +100,5 @@ def generate(env):
     )
     env.Append(CCFLAGS=arch_info["ccflags"])
     env.Append(LINKFLAGS=["--target=" + arch_info["target"] + env["android_api_level"], "-march=" + arch_info["march"]])
+
+    env.Append(CPPDEFINES=["ANDROID_ENABLED", "UNIX_ENABLED"])

+ 31 - 6
tools/ios.py

@@ -1,7 +1,6 @@
 import os
 import sys
 import subprocess
-import ios_osxcross
 from SCons.Variables import *
 
 if sys.version_info < (3,):
@@ -16,6 +15,10 @@ else:
         return codecs.utf_8_decode(x)[0]
 
 
+def has_ios_osxcross():
+    return "OSXCROSS_IOS" in os.environ
+
+
 def options(opts):
     opts.Add(BoolVariable("ios_simulator", "Target iOS Simulator", False))
     opts.Add("ios_min_version", "Target minimum iphoneos/iphonesimulator version", "10.0")
@@ -25,17 +28,18 @@ def options(opts):
         "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain",
     )
     opts.Add("IOS_SDK_PATH", "Path to the iOS SDK", "")
-    ios_osxcross.options(opts)
+
+    if has_ios_osxcross():
+        opts.Add("ios_triple", "Triple for ios toolchain", "")
 
 
 def exists(env):
-    return sys.platform == "darwin" or ios_osxcross.exists(env)
+    return sys.platform == "darwin" or has_ios_osxcross()
 
 
 def generate(env):
     if env["arch"] not in ("universal", "arm64", "x86_64"):
-        print("Only universal, arm64, and x86_64 are supported on iOS. Exiting.")
-        Exit()
+        raise ValueError("Only universal, arm64, and x86_64 are supported on iOS. Exiting.")
 
     if env["ios_simulator"]:
         sdk_name = "iphonesimulator"
@@ -64,7 +68,26 @@ def generate(env):
         env["ENV"]["PATH"] = env["IOS_TOOLCHAIN_PATH"] + "/Developer/usr/bin/:" + env["ENV"]["PATH"]
 
     else:
-        ios_osxcross.generate(env)
+        # OSXCross
+        compiler_path = "$IOS_TOOLCHAIN_PATH/usr/bin/${ios_triple}"
+        env["CC"] = compiler_path + "clang"
+        env["CXX"] = compiler_path + "clang++"
+        env["AR"] = compiler_path + "ar"
+        env["RANLIB"] = compiler_path + "ranlib"
+        env["SHLIBSUFFIX"] = ".dylib"
+
+        env.Prepend(
+            CPPPATH=[
+                "$IOS_SDK_PATH/usr/include",
+                "$IOS_SDK_PATH/System/Library/Frameworks/AudioUnit.framework/Headers",
+            ]
+        )
+
+        env.Append(CCFLAGS=["-stdlib=libc++"])
+
+        binpath = os.path.join(env["IOS_TOOLCHAIN_PATH"], "usr", "bin")
+        if binpath not in env["ENV"]["PATH"]:
+            env.PrependENVPath("PATH", binpath)
 
     if env["arch"] == "universal":
         if env["ios_simulator"]:
@@ -79,3 +102,5 @@ def generate(env):
 
     env.Append(CCFLAGS=["-isysroot", env["IOS_SDK_PATH"]])
     env.Append(LINKFLAGS=["-isysroot", env["IOS_SDK_PATH"], "-F" + env["IOS_SDK_PATH"]])
+
+    env.Append(CPPDEFINES=["IOS_ENABLED", "UNIX_ENABLED"])

+ 0 - 26
tools/ios_osxcross.py

@@ -1,26 +0,0 @@
-import os
-
-
-def options(opts):
-    opts.Add("ios_triple", "Triple for ios toolchain", "")
-
-
-def exists(env):
-    return "OSXCROSS_IOS" in os.environ
-
-
-def generate(env):
-    compiler_path = "$IOS_TOOLCHAIN_PATH/usr/bin/${ios_triple}"
-    env["CC"] = compiler_path + "clang"
-    env["CXX"] = compiler_path + "clang++"
-    env["AR"] = compiler_path + "ar"
-    env["RANLIB"] = compiler_path + "ranlib"
-    env["SHLIBSUFFIX"] = ".dylib"
-
-    env.Prepend(
-        CPPPATH=[
-            "$IOS_SDK_PATH/usr/include",
-            "$IOS_SDK_PATH/System/Library/Frameworks/AudioUnit.framework/Headers",
-        ]
-    )
-    env.Append(CCFLAGS=["-stdlib=libc++"])

+ 2 - 0
tools/javascript.py

@@ -43,3 +43,5 @@ def generate(env):
         env.Append(CCFLAGS=["-O0", "-g"])
     elif env["target"] == "release":
         env.Append(CCFLAGS=["-O3"])
+
+    env.Append(CPPDEFINES=["WEB_ENABLED", "UNIX_ENABLED"])

+ 2 - 0
tools/linux.py

@@ -32,3 +32,5 @@ def generate(env):
     elif env["arch"] == "rv64":
         env.Append(CCFLAGS=["-march=rv64gc"])
         env.Append(LINKFLAGS=["-march=rv64gc"])
+
+    env.Append(CPPDEFINES=["LINUX_ENABLED", "UNIX_ENABLED"])

+ 27 - 5
tools/macos.py

@@ -1,16 +1,20 @@
 import os
 import sys
-import macos_osxcross
+
+
+def has_osxcross():
+    return "OSXCROSS_ROOT" in os.environ
 
 
 def options(opts):
     opts.Add("macos_deployment_target", "macOS deployment target", "default")
     opts.Add("macos_sdk_path", "macOS SDK path", "")
-    macos_osxcross.options(opts)
+    if has_osxcross():
+        opts.Add("osxcross_sdk", "OSXCross SDK version", "darwin16")
 
 
 def exists(env):
-    return sys.platform == "darwin" or macos_osxcross.exists(env)
+    return sys.platform == "darwin" or has_osxcross()
 
 
 def generate(env):
@@ -23,9 +27,25 @@ def generate(env):
         env["CXX"] = "clang++"
         env["CC"] = "clang"
     else:
-        # Use osxcross
-        macos_osxcross.generate(env)
+        # OSXCross
+        root = os.environ.get("OSXCROSS_ROOT", "")
+        if env["arch"] == "arm64":
+            basecmd = root + "/target/bin/arm64-apple-" + env["osxcross_sdk"] + "-"
+        else:
+            basecmd = root + "/target/bin/x86_64-apple-" + env["osxcross_sdk"] + "-"
+
+        env["CC"] = basecmd + "clang"
+        env["CXX"] = basecmd + "clang++"
+        env["AR"] = basecmd + "ar"
+        env["RANLIB"] = basecmd + "ranlib"
+        env["AS"] = basecmd + "as"
 
+        binpath = os.path.join(root, "target", "bin")
+        if binpath not in env["ENV"]["PATH"]:
+            # Add OSXCROSS bin folder to PATH (required for linking).
+            env.PrependENVPath("PATH", binpath)
+
+    # Common flags
     if env["arch"] == "universal":
         env.Append(LINKFLAGS=["-arch", "x86_64", "-arch", "arm64"])
         env.Append(CCFLAGS=["-arch", "x86_64", "-arch", "arm64"])
@@ -48,3 +68,5 @@ def generate(env):
             "-Wl,-undefined,dynamic_lookup",
         ]
     )
+
+    env.Append(CPPDEFINES=["MACOS_ENABLED", "UNIX_ENABLED"])

+ 0 - 28
tools/macos_osxcross.py

@@ -1,28 +0,0 @@
-import os
-
-
-def options(opts):
-    opts.Add("osxcross_sdk", "OSXCross SDK version", "darwin16")
-
-
-def exists(env):
-    return "OSXCROSS_ROOT" in os.environ
-
-
-def generate(env):
-    root = os.environ.get("OSXCROSS_ROOT", "")
-    if env["arch"] == "arm64":
-        basecmd = root + "/target/bin/arm64-apple-" + env["osxcross_sdk"] + "-"
-    else:
-        basecmd = root + "/target/bin/x86_64-apple-" + env["osxcross_sdk"] + "-"
-
-    env["CC"] = basecmd + "clang"
-    env["CXX"] = basecmd + "clang++"
-    env["AR"] = basecmd + "ar"
-    env["RANLIB"] = basecmd + "ranlib"
-    env["AS"] = basecmd + "as"
-
-    binpath = os.path.join(root, "target", "bin")
-    if binpath not in env["ENV"]["PATH"]:
-        # Add OSXCROSS bin folder to PATH (required for linking).
-        env["ENV"]["PATH"] = "%s:%s" % (binpath, env["ENV"]["PATH"])

+ 6 - 6
tools/targets.py

@@ -60,17 +60,17 @@ def generate(env):
             env.Append(CCFLAGS=["/Zi", "/FS"])
             env.Append(LINKFLAGS=["/DEBUG:FULL"])
 
-        if env["optimize"] == "speed" or env["optimize"] == "speed_trace":
+        if env["optimize"] == "speed":
             env.Append(CCFLAGS=["/O2"])
             env.Append(LINKFLAGS=["/OPT:REF"])
+        elif env["optimize"] == "speed_trace":
+            env.Append(CCFLAGS=["/O2"])
+            env.Append(LINKFLAGS=["/OPT:REF", "/OPT:NOICF"])
         elif env["optimize"] == "size":
             env.Append(CCFLAGS=["/O1"])
             env.Append(LINKFLAGS=["/OPT:REF"])
-
-        if env["optimize"] == "debug" or env["optimize"] == "none":
-            env.Append(CCFLAGS=["/MDd", "/Od"])
-        else:
-            env.Append(CCFLAGS=["/MD"])
+        elif env["optimize"] == "debug" or env["optimize"] == "none":
+            env.Append(CCFLAGS=["/Od"])
 
     else:
         if env["debug_symbols"]:

+ 30 - 10
tools/windows.py

@@ -9,6 +9,7 @@ from SCons.Variables import *
 def options(opts):
     opts.Add(BoolVariable("use_mingw", "Use the MinGW compiler instead of MSVC - only effective on Windows", False))
     opts.Add(BoolVariable("use_clang_cl", "Use the clang driver instead of MSVC - only effective on Windows", False))
+    opts.Add(BoolVariable("use_static_cpp", "Link MinGW/MSVC C++ runtime libraries statically", True))
 
 
 def exists(env):
@@ -37,6 +38,11 @@ def generate(env):
             env["CC"] = "clang-cl"
             env["CXX"] = "clang-cl"
 
+        if env["use_static_cpp"]:
+            env.Append(CCFLAGS=["/MT"])
+        else:
+            env.Append(CCFLAGS=["/MD"])
+
     elif sys.platform == "win32" or sys.platform == "msys":
         env["use_mingw"] = True
         mingw.generate(env)
@@ -45,6 +51,18 @@ def generate(env):
         env["SHLIBPREFIX"] = ""
         # Want dll suffix
         env["SHLIBSUFFIX"] = ".dll"
+
+        env.Append(CCFLAGS=["-Wwrite-strings"])
+        env.Append(LINKFLAGS=["-Wl,--no-undefined"])
+        if env["use_static_cpp"]:
+            env.Append(
+                LINKFLAGS=[
+                    "-static",
+                    "-static-libgcc",
+                    "-static-libstdc++",
+                ]
+            )
+
         # Long line hack. Use custom spawn, quick AR append (to avoid files with the same names to override each other).
         my_spawn.configure(env)
 
@@ -60,13 +78,15 @@ def generate(env):
         # Want dll suffix
         env["SHLIBSUFFIX"] = ".dll"
 
-        # These options are for a release build even using target=debug
-        env.Append(CCFLAGS=["-O3", "-Wwrite-strings"])
-        env.Append(
-            LINKFLAGS=[
-                "--static",
-                "-Wl,--no-undefined",
-                "-static-libgcc",
-                "-static-libstdc++",
-            ]
-        )
+        env.Append(CCFLAGS=["-Wwrite-strings"])
+        env.Append(LINKFLAGS=["-Wl,--no-undefined"])
+        if env["use_static_cpp"]:
+            env.Append(
+                LINKFLAGS=[
+                    "-static",
+                    "-static-libgcc",
+                    "-static-libstdc++",
+                ]
+            )
+
+    env.Append(CPPDEFINES=["WINDOWS_ENABLED"])